(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FloatingUIReactUtils = {}, global.React)); })(this, (function (exports, React) { 'use strict'; function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React); function hasWindow() { return typeof window !== 'undefined'; } function getWindow(node) { var _node$ownerDocument; return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; } function isHTMLElement(value) { if (!hasWindow()) { return false; } return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement; } function isShadowRoot(value) { if (!hasWindow() || typeof ShadowRoot === 'undefined') { return false; } return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot; } // Avoid Chrome DevTools blue warning. function getPlatform() { const uaData = navigator.userAgentData; if (uaData != null && uaData.platform) { return uaData.platform; } return navigator.platform; } function getUserAgent() { const uaData = navigator.userAgentData; if (uaData && Array.isArray(uaData.brands)) { return uaData.brands.map(_ref => { let { brand, version } = _ref; return brand + "/" + version; }).join(' '); } return navigator.userAgent; } function isSafari() { // Chrome DevTools does not complain about navigator.vendor return /apple/i.test(navigator.vendor); } function isAndroid() { const re = /android/i; return re.test(getPlatform()) || re.test(getUserAgent()); } function isMac() { return getPlatform().toLowerCase().startsWith('mac') && !navigator.maxTouchPoints; } function isJSDOM() { return getUserAgent().includes('jsdom/'); } const FOCUSABLE_ATTRIBUTE = 'data-floating-ui-focusable'; const TYPEABLE_SELECTOR = "input:not([type='hidden']):not([disabled])," + "[contenteditable]:not([contenteditable='false']),textarea:not([disabled])"; const ARROW_LEFT = 'ArrowLeft'; const ARROW_RIGHT = 'ArrowRight'; const ARROW_UP = 'ArrowUp'; const ARROW_DOWN = 'ArrowDown'; function activeElement(doc) { let activeElement = doc.activeElement; while (((_activeElement = activeElement) == null || (_activeElement = _activeElement.shadowRoot) == null ? void 0 : _activeElement.activeElement) != null) { var _activeElement; activeElement = activeElement.shadowRoot.activeElement; } return activeElement; } function contains(parent, child) { if (!parent || !child) { return false; } const rootNode = child.getRootNode == null ? void 0 : child.getRootNode(); // First, attempt with faster native method if (parent.contains(child)) { return true; } // then fallback to custom implementation with Shadow DOM support if (rootNode && isShadowRoot(rootNode)) { let next = child; while (next) { if (parent === next) { return true; } // @ts-ignore next = next.parentNode || next.host; } } // Give up, the result is false return false; } function getTarget(event) { if ('composedPath' in event) { return event.composedPath()[0]; } // TS thinks `event` is of type never as it assumes all browsers support // `composedPath()`, but browsers without shadow DOM don't. return event.target; } function isEventTargetWithin(event, node) { if (node == null) { return false; } if ('composedPath' in event) { return event.composedPath().includes(node); } // TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't const e = event; return e.target != null && node.contains(e.target); } function isRootElement(element) { return element.matches('html,body'); } function getDocument(node) { return (node == null ? void 0 : node.ownerDocument) || document; } function isTypeableElement(element) { return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR); } function isTypeableCombobox(element) { if (!element) return false; return element.getAttribute('role') === 'combobox' && isTypeableElement(element); } function matchesFocusVisible(element) { // We don't want to block focus from working with `visibleOnly` // (JSDOM doesn't match `:focus-visible` when the element has `:focus`) if (!element || isJSDOM()) return true; try { return element.matches(':focus-visible'); } catch (_e) { return true; } } function getFloatingFocusElement(floatingElement) { if (!floatingElement) { return null; } // Try to find the element that has `{...getFloatingProps()}` spread on it. // This indicates the floating element is acting as a positioning wrapper, and // so focus should be managed on the child element with the event handlers and // aria props. return floatingElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? floatingElement : floatingElement.querySelector("[" + FOCUSABLE_ATTRIBUTE + "]") || floatingElement; } function getNodeChildren(nodes, id, onlyOpenChildren) { if (onlyOpenChildren === void 0) { onlyOpenChildren = true; } const directChildren = nodes.filter(node => { var _node$context; return node.parentId === id && (!onlyOpenChildren || ((_node$context = node.context) == null ? void 0 : _node$context.open)); }); return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]); } function getDeepestNode(nodes, id) { let deepestNodeId; let maxDepth = -1; function findDeepest(nodeId, depth) { if (depth > maxDepth) { deepestNodeId = nodeId; maxDepth = depth; } const children = getNodeChildren(nodes, nodeId); children.forEach(child => { findDeepest(child.id, depth + 1); }); } findDeepest(id, 0); return nodes.find(node => node.id === deepestNodeId); } function getNodeAncestors(nodes, id) { var _nodes$find; let allAncestors = []; let currentParentId = (_nodes$find = nodes.find(node => node.id === id)) == null ? void 0 : _nodes$find.parentId; while (currentParentId) { const currentNode = nodes.find(node => node.id === currentParentId); currentParentId = currentNode == null ? void 0 : currentNode.parentId; if (currentNode) { allAncestors = allAncestors.concat(currentNode); } } return allAncestors; } function stopEvent(event) { event.preventDefault(); event.stopPropagation(); } function isReactEvent(event) { return 'nativeEvent' in event; } // License: https://github.com/adobe/react-spectrum/blob/b35d5c02fe900badccd0cf1a8f23bb593419f238/packages/@react-aria/utils/src/isVirtualEvent.ts function isVirtualClick(event) { // FIXME: Firefox is now emitting a deprecation warning for `mozInputSource`. // Try to find a workaround for this. `react-aria` source still has the check. if (event.mozInputSource === 0 && event.isTrusted) { return true; } if (isAndroid() && event.pointerType) { return event.type === 'click' && event.buttons === 1; } return event.detail === 0 && !event.pointerType; } function isVirtualPointerEvent(event) { if (isJSDOM()) return false; return !isAndroid() && event.width === 0 && event.height === 0 || isAndroid() && event.width === 1 && event.height === 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'mouse' || // iOS VoiceOver returns 0.333• for width/height. event.width < 1 && event.height < 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'touch'; } function isMouseLikePointerType(pointerType, strict) { // On some Linux machines with Chromium, mouse inputs return a `pointerType` // of "pen": https://github.com/floating-ui/floating-ui/issues/2015 const values = ['mouse', 'pen']; if (!strict) { values.push('', undefined); } return values.includes(pointerType); } var isClient = typeof document !== 'undefined'; var noop = function noop() {}; var index = isClient ? React.useLayoutEffect : noop; // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379 const SafeReact = { ...React__namespace }; function useLatestRef(value) { const ref = React__namespace.useRef(value); index(() => { ref.current = value; }); return ref; } const useInsertionEffect = SafeReact.useInsertionEffect; const useSafeInsertionEffect = useInsertionEffect || (fn => fn()); function useEffectEvent(callback) { const ref = React__namespace.useRef(() => { { throw new Error('Cannot call an event handler while rendering.'); } }); useSafeInsertionEffect(() => { ref.current = callback; }); return React__namespace.useCallback(function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return ref.current == null ? void 0 : ref.current(...args); }, []); } /** * Custom positioning reference element. * @see https://floating-ui.com/docs/virtual-elements */ const floor = Math.floor; function isDifferentGridRow(index, cols, prevRow) { return Math.floor(index / cols) !== prevRow; } function isIndexOutOfListBounds(listRef, index) { return index < 0 || index >= listRef.current.length; } function getMinListIndex(listRef, disabledIndices) { return findNonDisabledListIndex(listRef, { disabledIndices }); } function getMaxListIndex(listRef, disabledIndices) { return findNonDisabledListIndex(listRef, { decrement: true, startingIndex: listRef.current.length, disabledIndices }); } function findNonDisabledListIndex(listRef, _temp) { let { startingIndex = -1, decrement = false, disabledIndices, amount = 1 } = _temp === void 0 ? {} : _temp; let index = startingIndex; do { index += decrement ? -amount : amount; } while (index >= 0 && index <= listRef.current.length - 1 && isListIndexDisabled(listRef, index, disabledIndices)); return index; } function getGridNavigatedIndex(listRef, _ref) { let { event, orientation, loop, rtl, cols, disabledIndices, minIndex, maxIndex, prevIndex, stopEvent: stop = false } = _ref; let nextIndex = prevIndex; if (event.key === ARROW_UP) { stop && stopEvent(event); if (prevIndex === -1) { nextIndex = maxIndex; } else { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: nextIndex, amount: cols, decrement: true, disabledIndices }); if (loop && (prevIndex - cols < minIndex || nextIndex < 0)) { const col = prevIndex % cols; const maxCol = maxIndex % cols; const offset = maxIndex - (maxCol - col); if (maxCol === col) { nextIndex = maxIndex; } else { nextIndex = maxCol > col ? offset : offset - cols; } } } if (isIndexOutOfListBounds(listRef, nextIndex)) { nextIndex = prevIndex; } } if (event.key === ARROW_DOWN) { stop && stopEvent(event); if (prevIndex === -1) { nextIndex = minIndex; } else { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex, amount: cols, disabledIndices }); if (loop && prevIndex + cols > maxIndex) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex % cols - cols, amount: cols, disabledIndices }); } } if (isIndexOutOfListBounds(listRef, nextIndex)) { nextIndex = prevIndex; } } // Remains on the same row/column. if (orientation === 'both') { const prevRow = floor(prevIndex / cols); if (event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)) { stop && stopEvent(event); if (prevIndex % cols !== cols - 1) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex, disabledIndices }); if (loop && isDifferentGridRow(nextIndex, cols, prevRow)) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex - prevIndex % cols - 1, disabledIndices }); } } else if (loop) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex - prevIndex % cols - 1, disabledIndices }); } if (isDifferentGridRow(nextIndex, cols, prevRow)) { nextIndex = prevIndex; } } if (event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT)) { stop && stopEvent(event); if (prevIndex % cols !== 0) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex, decrement: true, disabledIndices }); if (loop && isDifferentGridRow(nextIndex, cols, prevRow)) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex + (cols - prevIndex % cols), decrement: true, disabledIndices }); } } else if (loop) { nextIndex = findNonDisabledListIndex(listRef, { startingIndex: prevIndex + (cols - prevIndex % cols), decrement: true, disabledIndices }); } if (isDifferentGridRow(nextIndex, cols, prevRow)) { nextIndex = prevIndex; } } const lastRow = floor(maxIndex / cols) === prevRow; if (isIndexOutOfListBounds(listRef, nextIndex)) { if (loop && lastRow) { nextIndex = event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT) ? maxIndex : findNonDisabledListIndex(listRef, { startingIndex: prevIndex - prevIndex % cols - 1, disabledIndices }); } else { nextIndex = prevIndex; } } } return nextIndex; } /** For each cell index, gets the item index that occupies that cell */ function createGridCellMap(sizes, cols, dense) { const cellMap = []; let startIndex = 0; sizes.forEach((_ref2, index) => { let { width, height } = _ref2; if (width > cols) { { throw new Error("[Floating UI]: Invalid grid - item width at index " + index + " is greater than grid columns"); } } let itemPlaced = false; if (dense) { startIndex = 0; } while (!itemPlaced) { const targetCells = []; for (let i = 0; i < width; i++) { for (let j = 0; j < height; j++) { targetCells.push(startIndex + i + j * cols); } } if (startIndex % cols + width <= cols && targetCells.every(cell => cellMap[cell] == null)) { targetCells.forEach(cell => { cellMap[cell] = index; }); itemPlaced = true; } else { startIndex++; } } }); // convert into a non-sparse array return [...cellMap]; } /** Gets cell index of an item's corner or -1 when index is -1. */ function getGridCellIndexOfCorner(index, sizes, cellMap, cols, corner) { if (index === -1) return -1; const firstCellIndex = cellMap.indexOf(index); const sizeItem = sizes[index]; switch (corner) { case 'tl': return firstCellIndex; case 'tr': if (!sizeItem) { return firstCellIndex; } return firstCellIndex + sizeItem.width - 1; case 'bl': if (!sizeItem) { return firstCellIndex; } return firstCellIndex + (sizeItem.height - 1) * cols; case 'br': return cellMap.lastIndexOf(index); } } /** Gets all cell indices that correspond to the specified indices */ function getGridCellIndices(indices, cellMap) { return cellMap.flatMap((index, cellIndex) => indices.includes(index) ? [cellIndex] : []); } function isListIndexDisabled(listRef, index, disabledIndices) { if (typeof disabledIndices === 'function') { return disabledIndices(index); } else if (disabledIndices) { return disabledIndices.includes(index); } const element = listRef.current[index]; return element == null || element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true'; } /*! * tabbable 6.2.0 * @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE */ // NOTE: separate `:not()` selectors has broader browser support than the newer // `:not([inert], [inert] *)` (Feb 2023) // CAREFUL: JSDom does not support `:not([inert] *)` as a selector; using it causes // the entire query to fail, resulting in no nodes found, which will break a lot // of things... so we have to rely on JS to identify nodes inside an inert container var candidateSelectors = ['input:not([inert])', 'select:not([inert])', 'textarea:not([inert])', 'a[href]:not([inert])', 'button:not([inert])', '[tabindex]:not(slot):not([inert])', 'audio[controls]:not([inert])', 'video[controls]:not([inert])', '[contenteditable]:not([contenteditable="false"]):not([inert])', 'details>summary:first-of-type:not([inert])', 'details:not([inert])']; var candidateSelector = /* #__PURE__ */candidateSelectors.join(','); var NoElement = typeof Element === 'undefined'; var matches = NoElement ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; var getRootNode = !NoElement && Element.prototype.getRootNode ? function (element) { var _element$getRootNode; return element === null || element === void 0 ? void 0 : (_element$getRootNode = element.getRootNode) === null || _element$getRootNode === void 0 ? void 0 : _element$getRootNode.call(element); } : function (element) { return element === null || element === void 0 ? void 0 : element.ownerDocument; }; /** * Determines if a node is inert or in an inert ancestor. * @param {Element} [node] * @param {boolean} [lookUp] If true and `node` is not inert, looks up at ancestors to * see if any of them are inert. If false, only `node` itself is considered. * @returns {boolean} True if inert itself or by way of being in an inert ancestor. * False if `node` is falsy. */ var isInert = function isInert(node, lookUp) { var _node$getAttribute; if (lookUp === void 0) { lookUp = true; } // CAREFUL: JSDom does not support inert at all, so we can't use the `HTMLElement.inert` // JS API property; we have to check the attribute, which can either be empty or 'true'; // if it's `null` (not specified) or 'false', it's an active element var inertAtt = node === null || node === void 0 ? void 0 : (_node$getAttribute = node.getAttribute) === null || _node$getAttribute === void 0 ? void 0 : _node$getAttribute.call(node, 'inert'); var inert = inertAtt === '' || inertAtt === 'true'; // NOTE: this could also be handled with `node.matches('[inert], :is([inert] *)')` // if it weren't for `matches()` not being a function on shadow roots; the following // code works for any kind of node // CAREFUL: JSDom does not appear to support certain selectors like `:not([inert] *)` // so it likely would not support `:is([inert] *)` either... var result = inert || lookUp && node && isInert(node.parentNode); // recursive return result; }; /** * Determines if a node's content is editable. * @param {Element} [node] * @returns True if it's content-editable; false if it's not or `node` is falsy. */ var isContentEditable = function isContentEditable(node) { var _node$getAttribute2; // CAREFUL: JSDom does not support the `HTMLElement.isContentEditable` API so we have // to use the attribute directly to check for this, which can either be empty or 'true'; // if it's `null` (not specified) or 'false', it's a non-editable element var attValue = node === null || node === void 0 ? void 0 : (_node$getAttribute2 = node.getAttribute) === null || _node$getAttribute2 === void 0 ? void 0 : _node$getAttribute2.call(node, 'contenteditable'); return attValue === '' || attValue === 'true'; }; /** * @param {Element} el container to check in * @param {boolean} includeContainer add container to check * @param {(node: Element) => boolean} filter filter candidates * @returns {Element[]} */ var getCandidates = function getCandidates(el, includeContainer, filter) { // even if `includeContainer=false`, we still have to check it for inertness because // if it's inert, all its children are inert if (isInert(el)) { return []; } var candidates = Array.prototype.slice.apply(el.querySelectorAll(candidateSelector)); if (includeContainer && matches.call(el, candidateSelector)) { candidates.unshift(el); } candidates = candidates.filter(filter); return candidates; }; /** * @callback GetShadowRoot * @param {Element} element to check for shadow root * @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available. */ /** * @callback ShadowRootFilter * @param {Element} shadowHostNode the element which contains shadow content * @returns {boolean} true if a shadow root could potentially contain valid candidates. */ /** * @typedef {Object} CandidateScope * @property {Element} scopeParent contains inner candidates * @property {Element[]} candidates list of candidates found in the scope parent */ /** * @typedef {Object} IterativeOptions * @property {GetShadowRoot|boolean} getShadowRoot true if shadow support is enabled; falsy if not; * if a function, implies shadow support is enabled and either returns the shadow root of an element * or a boolean stating if it has an undisclosed shadow root * @property {(node: Element) => boolean} filter filter candidates * @property {boolean} flatten if true then result will flatten any CandidateScope into the returned list * @property {ShadowRootFilter} shadowRootFilter filter shadow roots; */ /** * @param {Element[]} elements list of element containers to match candidates from * @param {boolean} includeContainer add container list to check * @param {IterativeOptions} options * @returns {Array.} */ var getCandidatesIteratively = function getCandidatesIteratively(elements, includeContainer, options) { var candidates = []; var elementsToCheck = Array.from(elements); while (elementsToCheck.length) { var element = elementsToCheck.shift(); if (isInert(element, false)) { // no need to look up since we're drilling down // anything inside this container will also be inert continue; } if (element.tagName === 'SLOT') { // add shadow dom slot scope (slot itself cannot be focusable) var assigned = element.assignedElements(); var content = assigned.length ? assigned : element.children; var nestedCandidates = getCandidatesIteratively(content, true, options); if (options.flatten) { candidates.push.apply(candidates, nestedCandidates); } else { candidates.push({ scopeParent: element, candidates: nestedCandidates }); } } else { // check candidate element var validCandidate = matches.call(element, candidateSelector); if (validCandidate && options.filter(element) && (includeContainer || !elements.includes(element))) { candidates.push(element); } // iterate over shadow content if possible var shadowRoot = element.shadowRoot || // check for an undisclosed shadow typeof options.getShadowRoot === 'function' && options.getShadowRoot(element); // no inert look up because we're already drilling down and checking for inertness // on the way down, so all containers to this root node should have already been // vetted as non-inert var validShadowRoot = !isInert(shadowRoot, false) && (!options.shadowRootFilter || options.shadowRootFilter(element)); if (shadowRoot && validShadowRoot) { // add shadow dom scope IIF a shadow root node was given; otherwise, an undisclosed // shadow exists, so look at light dom children as fallback BUT create a scope for any // child candidates found because they're likely slotted elements (elements that are // children of the web component element (which has the shadow), in the light dom, but // slotted somewhere _inside_ the undisclosed shadow) -- the scope is created below, // _after_ we return from this recursive call var _nestedCandidates = getCandidatesIteratively(shadowRoot === true ? element.children : shadowRoot.children, true, options); if (options.flatten) { candidates.push.apply(candidates, _nestedCandidates); } else { candidates.push({ scopeParent: element, candidates: _nestedCandidates }); } } else { // there's not shadow so just dig into the element's (light dom) children // __without__ giving the element special scope treatment elementsToCheck.unshift.apply(elementsToCheck, element.children); } } } return candidates; }; /** * @private * Determines if the node has an explicitly specified `tabindex` attribute. * @param {HTMLElement} node * @returns {boolean} True if so; false if not. */ var hasTabIndex = function hasTabIndex(node) { return !isNaN(parseInt(node.getAttribute('tabindex'), 10)); }; /** * Determine the tab index of a given node. * @param {HTMLElement} node * @returns {number} Tab order (negative, 0, or positive number). * @throws {Error} If `node` is falsy. */ var getTabIndex = function getTabIndex(node) { if (!node) { throw new Error('No node provided'); } if (node.tabIndex < 0) { // in Chrome,
,