/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import * as React from 'react'; import { useLayoutEffect, useEffect, forwardRef, useState, useRef } from 'react'; import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import * as ReactDOM from 'react-dom'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getNearestNodeFromDOMNode } from 'lexical'; function hasWindow() { return typeof window !== 'undefined'; } function getNodeName(node) { if (isNode(node)) { return (node.nodeName || '').toLowerCase(); } // Mocked nodes in testing environments may not be instances of Node. By // returning `#document` an infinite loop won't occur. // https://github.com/floating-ui/floating-ui/issues/2317 return '#document'; } function getWindow(node) { var _node$ownerDocument; return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; } function getDocumentElement(node) { var _ref; return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; } function isNode(value) { if (!hasWindow()) { return false; } return value instanceof Node || value instanceof getWindow(value).Node; } function isElement(value) { if (!hasWindow()) { return false; } return value instanceof Element || value instanceof getWindow(value).Element; } 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; } function isOverflowElement(element) { const { overflow, overflowX, overflowY, display } = getComputedStyle$1(element); return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); } function isTableElement(element) { return ['table', 'td', 'th'].includes(getNodeName(element)); } function isTopLayer(element) { return [':popover-open', ':modal'].some(selector => { try { return element.matches(selector); } catch (e) { return false; } }); } function isContainingBlock(elementOrCss) { const webkit = isWebKit(); const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block // https://drafts.csswg.org/css-transforms-2/#individual-transforms return ['transform', 'translate', 'scale', 'rotate', 'perspective'].some(value => css[value] ? css[value] !== 'none' : false) || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'translate', 'scale', 'rotate', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); } function getContainingBlock(element) { let currentNode = getParentNode(element); while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { if (isContainingBlock(currentNode)) { return currentNode; } else if (isTopLayer(currentNode)) { return null; } currentNode = getParentNode(currentNode); } return null; } function isWebKit() { if (typeof CSS === 'undefined' || !CSS.supports) return false; return CSS.supports('-webkit-backdrop-filter', 'none'); } function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { if (isElement(element)) { return { scrollLeft: element.scrollLeft, scrollTop: element.scrollTop }; } return { scrollLeft: element.scrollX, scrollTop: element.scrollY }; } function getParentNode(node) { if (getNodeName(node) === 'html') { return node; } const result = // Step into the shadow DOM of the parent of a slotted node. node.assignedSlot || // DOM Element detected. node.parentNode || // ShadowRoot detected. isShadowRoot(node) && node.host || // Fallback. getDocumentElement(node); return isShadowRoot(result) ? result.host : result; } function getNearestOverflowAncestor(node) { const parentNode = getParentNode(node); if (isLastTraversableNode(parentNode)) { return node.ownerDocument ? node.ownerDocument.body : node.body; } if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) { return parentNode; } return getNearestOverflowAncestor(parentNode); } function getOverflowAncestors(node, list, traverseIframes) { var _node$ownerDocument2; if (list === void 0) { list = []; } if (traverseIframes === void 0) { traverseIframes = true; } const scrollableAncestor = getNearestOverflowAncestor(node); const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); const win = getWindow(scrollableAncestor); if (isBody) { const frameElement = getFrameElement(win); return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []); } return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes)); } function getFrameElement(win) { return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null; } /** * Custom positioning reference element. * @see https://floating-ui.com/docs/virtual-elements */ const min = Math.min; const max = Math.max; const round = Math.round; const floor = Math.floor; const createCoords = v => ({ x: v, y: v }); const oppositeSideMap = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; const oppositeAlignmentMap = { start: 'end', end: 'start' }; function clamp(start, value, end) { return max(start, min(value, end)); } function evaluate(value, param) { return typeof value === 'function' ? value(param) : value; } function getSide(placement) { return placement.split('-')[0]; } function getAlignment(placement) { return placement.split('-')[1]; } function getOppositeAxis(axis) { return axis === 'x' ? 'y' : 'x'; } function getAxisLength(axis) { return axis === 'y' ? 'height' : 'width'; } function getSideAxis(placement) { return ['top', 'bottom'].includes(getSide(placement)) ? 'y' : 'x'; } function getAlignmentAxis(placement) { return getOppositeAxis(getSideAxis(placement)); } function getAlignmentSides(placement, rects, rtl) { if (rtl === void 0) { rtl = false; } const alignment = getAlignment(placement); const alignmentAxis = getAlignmentAxis(placement); const length = getAxisLength(alignmentAxis); let mainAlignmentSide = alignmentAxis === 'x' ? alignment === (rtl ? 'end' : 'start') ? 'right' : 'left' : alignment === 'start' ? 'bottom' : 'top'; if (rects.reference[length] > rects.floating[length]) { mainAlignmentSide = getOppositePlacement(mainAlignmentSide); } return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; } function getExpandedPlacements(placement) { const oppositePlacement = getOppositePlacement(placement); return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; } function getOppositeAlignmentPlacement(placement) { return placement.replace(/start|end/g, alignment => oppositeAlignmentMap[alignment]); } function getSideList(side, isStart, rtl) { const lr = ['left', 'right']; const rl = ['right', 'left']; const tb = ['top', 'bottom']; const bt = ['bottom', 'top']; switch (side) { case 'top': case 'bottom': if (rtl) return isStart ? rl : lr; return isStart ? lr : rl; case 'left': case 'right': return isStart ? tb : bt; default: return []; } } function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { const alignment = getAlignment(placement); let list = getSideList(getSide(placement), direction === 'start', rtl); if (alignment) { list = list.map(side => side + "-" + alignment); if (flipAlignment) { list = list.concat(list.map(getOppositeAlignmentPlacement)); } } return list; } function getOppositePlacement(placement) { return placement.replace(/left|right|bottom|top/g, side => oppositeSideMap[side]); } function expandPaddingObject(padding) { return { top: 0, right: 0, bottom: 0, left: 0, ...padding }; } function getPaddingObject(padding) { return typeof padding !== 'number' ? expandPaddingObject(padding) : { top: padding, right: padding, bottom: padding, left: padding }; } function rectToClientRect(rect) { const { x, y, width, height } = rect; return { width, height, top: y, left: x, right: x + width, bottom: y + height, x, y }; } /*! * 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,
,