6251 lines
204 KiB
JavaScript
6251 lines
204 KiB
JavaScript
import { c as bind_props, d as spread_props, h as hasContext, f as getContext, j as setContext, p as props_id, k as derived, l as getAllContexts, m as attributes, n as lifecycle_function_unavailable, o as escape_html } from './index2-DHYCG7Ly.js';
|
|
import 'clsx';
|
|
import { o as on } from './root-BEb1kut4.js';
|
|
import './state.svelte-CsO7NG8X.js';
|
|
import { c as cn } from './utils2-B05Dmz_H.js';
|
|
import { b as boxWith, c as createBitsAttrs, s as simpleBox, a as styleToString, d as createId, m as mergeProps, e as attachRef, f as boxFrom, g as executeCallbacks, i as isWritableSymbol, B as BoxSymbol, h as isObject, j as boxFlatten, t as toReadonlyBox, k as isBox, l as isWritableBox, n as getDataOpenClosed, o as boolToEmptyStrOrUndef, p as boolToStr, q as cssToStyleObj, L as Label, r as getDataChecked, u as getAriaChecked, v as boolToTrueOrUndef, w as composeHandlers } from './label-CsPa3Fds.js';
|
|
import 'tailwind-merge';
|
|
|
|
/*!
|
|
* tabbable 6.4.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)
|
|
var candidateSelectors = ['input:not([inert]):not([inert] *)', 'select:not([inert]):not([inert] *)', 'textarea:not([inert]):not([inert] *)', 'a[href]:not([inert]):not([inert] *)', 'button:not([inert]):not([inert] *)', '[tabindex]:not(slot):not([inert]):not([inert] *)', 'audio[controls]:not([inert]):not([inert] *)', 'video[controls]:not([inert]):not([inert] *)', '[contenteditable]:not([contenteditable="false"]):not([inert]):not([inert] *)', 'details>summary:first-of-type:not([inert]):not([inert] *)', 'details:not([inert]):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 {Node} [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
|
|
var result = inert || lookUp && node && (
|
|
// closest does not exist on shadow roots, so we fall back to a manual
|
|
// lookup upward, in case it is not defined.
|
|
typeof node.closest === 'function' ? node.closest('[inert]') : _isInert(node.parentNode));
|
|
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 (either by itself or via its parent), then 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.<Element|CandidateScope>}
|
|
*/
|
|
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, <details/>, <audio controls/> and <video controls/> elements get a default
|
|
// `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM,
|
|
// yet they are still part of the regular tab order; in FF, they get a default
|
|
// `tabIndex` of 0; since Chrome still puts those elements in the regular tab
|
|
// order, consider their tab index to be 0.
|
|
// Also browsers do not return `tabIndex` correctly for contentEditable nodes;
|
|
// so if they don't have a tabindex attribute specifically set, assume it's 0.
|
|
if ((/^(AUDIO|VIDEO|DETAILS)$/.test(node.tagName) || isContentEditable(node)) && !hasTabIndex(node)) {
|
|
return 0;
|
|
}
|
|
}
|
|
return node.tabIndex;
|
|
};
|
|
|
|
/**
|
|
* Determine the tab index of a given node __for sort order purposes__.
|
|
* @param {HTMLElement} node
|
|
* @param {boolean} [isScope] True for a custom element with shadow root or slot that, by default,
|
|
* has tabIndex -1, but needs to be sorted by document order in order for its content to be
|
|
* inserted into the correct sort position.
|
|
* @returns {number} Tab order (negative, 0, or positive number).
|
|
*/
|
|
var getSortOrderTabIndex = function getSortOrderTabIndex(node, isScope) {
|
|
var tabIndex = getTabIndex(node);
|
|
if (tabIndex < 0 && isScope && !hasTabIndex(node)) {
|
|
return 0;
|
|
}
|
|
return tabIndex;
|
|
};
|
|
var sortOrderedTabbables = function sortOrderedTabbables(a, b) {
|
|
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
|
|
};
|
|
var isInput = function isInput(node) {
|
|
return node.tagName === 'INPUT';
|
|
};
|
|
var isHiddenInput = function isHiddenInput(node) {
|
|
return isInput(node) && node.type === 'hidden';
|
|
};
|
|
var isDetailsWithSummary = function isDetailsWithSummary(node) {
|
|
var r = node.tagName === 'DETAILS' && Array.prototype.slice.apply(node.children).some(function (child) {
|
|
return child.tagName === 'SUMMARY';
|
|
});
|
|
return r;
|
|
};
|
|
var getCheckedRadio = function getCheckedRadio(nodes, form) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (nodes[i].checked && nodes[i].form === form) {
|
|
return nodes[i];
|
|
}
|
|
}
|
|
};
|
|
var isTabbableRadio = function isTabbableRadio(node) {
|
|
if (!node.name) {
|
|
return true;
|
|
}
|
|
var radioScope = node.form || getRootNode(node);
|
|
var queryRadios = function queryRadios(name) {
|
|
return radioScope.querySelectorAll('input[type="radio"][name="' + name + '"]');
|
|
};
|
|
var radioSet;
|
|
if (typeof window !== 'undefined' && typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') {
|
|
radioSet = queryRadios(window.CSS.escape(node.name));
|
|
} else {
|
|
try {
|
|
radioSet = queryRadios(node.name);
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s', err.message);
|
|
return false;
|
|
}
|
|
}
|
|
var checked = getCheckedRadio(radioSet, node.form);
|
|
return !checked || checked === node;
|
|
};
|
|
var isRadio = function isRadio(node) {
|
|
return isInput(node) && node.type === 'radio';
|
|
};
|
|
var isNonTabbableRadio = function isNonTabbableRadio(node) {
|
|
return isRadio(node) && !isTabbableRadio(node);
|
|
};
|
|
|
|
// determines if a node is ultimately attached to the window's document
|
|
var isNodeAttached = function isNodeAttached(node) {
|
|
var _nodeRoot;
|
|
// The root node is the shadow root if the node is in a shadow DOM; some document otherwise
|
|
// (but NOT _the_ document; see second 'If' comment below for more).
|
|
// If rootNode is shadow root, it'll have a host, which is the element to which the shadow
|
|
// is attached, and the one we need to check if it's in the document or not (because the
|
|
// shadow, and all nodes it contains, is never considered in the document since shadows
|
|
// behave like self-contained DOMs; but if the shadow's HOST, which is part of the document,
|
|
// is hidden, or is not in the document itself but is detached, it will affect the shadow's
|
|
// visibility, including all the nodes it contains). The host could be any normal node,
|
|
// or a custom element (i.e. web component). Either way, that's the one that is considered
|
|
// part of the document, not the shadow root, nor any of its children (i.e. the node being
|
|
// tested).
|
|
// To further complicate things, we have to look all the way up until we find a shadow HOST
|
|
// that is attached (or find none) because the node might be in nested shadows...
|
|
// If rootNode is not a shadow root, it won't have a host, and so rootNode should be the
|
|
// document (per the docs) and while it's a Document-type object, that document does not
|
|
// appear to be the same as the node's `ownerDocument` for some reason, so it's safer
|
|
// to ignore the rootNode at this point, and use `node.ownerDocument`. Otherwise,
|
|
// using `rootNode.contains(node)` will _always_ be true we'll get false-positives when
|
|
// node is actually detached.
|
|
// NOTE: If `nodeRootHost` or `node` happens to be the `document` itself (which is possible
|
|
// if a tabbable/focusable node was quickly added to the DOM, focused, and then removed
|
|
// from the DOM as in https://github.com/focus-trap/focus-trap-react/issues/905), then
|
|
// `ownerDocument` will be `null`, hence the optional chaining on it.
|
|
var nodeRoot = node && getRootNode(node);
|
|
var nodeRootHost = (_nodeRoot = nodeRoot) === null || _nodeRoot === void 0 ? void 0 : _nodeRoot.host;
|
|
|
|
// in some cases, a detached node will return itself as the root instead of a document or
|
|
// shadow root object, in which case, we shouldn't try to look further up the host chain
|
|
var attached = false;
|
|
if (nodeRoot && nodeRoot !== node) {
|
|
var _nodeRootHost, _nodeRootHost$ownerDo, _node$ownerDocument;
|
|
attached = !!((_nodeRootHost = nodeRootHost) !== null && _nodeRootHost !== void 0 && (_nodeRootHost$ownerDo = _nodeRootHost.ownerDocument) !== null && _nodeRootHost$ownerDo !== void 0 && _nodeRootHost$ownerDo.contains(nodeRootHost) || node !== null && node !== void 0 && (_node$ownerDocument = node.ownerDocument) !== null && _node$ownerDocument !== void 0 && _node$ownerDocument.contains(node));
|
|
while (!attached && nodeRootHost) {
|
|
var _nodeRoot2, _nodeRootHost2, _nodeRootHost2$ownerD;
|
|
// since it's not attached and we have a root host, the node MUST be in a nested shadow DOM,
|
|
// which means we need to get the host's host and check if that parent host is contained
|
|
// in (i.e. attached to) the document
|
|
nodeRoot = getRootNode(nodeRootHost);
|
|
nodeRootHost = (_nodeRoot2 = nodeRoot) === null || _nodeRoot2 === void 0 ? void 0 : _nodeRoot2.host;
|
|
attached = !!((_nodeRootHost2 = nodeRootHost) !== null && _nodeRootHost2 !== void 0 && (_nodeRootHost2$ownerD = _nodeRootHost2.ownerDocument) !== null && _nodeRootHost2$ownerD !== void 0 && _nodeRootHost2$ownerD.contains(nodeRootHost));
|
|
}
|
|
}
|
|
return attached;
|
|
};
|
|
var isZeroArea = function isZeroArea(node) {
|
|
var _node$getBoundingClie = node.getBoundingClientRect(),
|
|
width = _node$getBoundingClie.width,
|
|
height = _node$getBoundingClie.height;
|
|
return width === 0 && height === 0;
|
|
};
|
|
var isHidden = function isHidden(node, _ref) {
|
|
var displayCheck = _ref.displayCheck,
|
|
getShadowRoot = _ref.getShadowRoot;
|
|
if (displayCheck === 'full-native') {
|
|
if ('checkVisibility' in node) {
|
|
// Chrome >= 105, Edge >= 105, Firefox >= 106, Safari >= 17.4
|
|
// @see https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility#browser_compatibility
|
|
var visible = node.checkVisibility({
|
|
// Checking opacity might be desirable for some use cases, but natively,
|
|
// opacity zero elements _are_ focusable and tabbable.
|
|
checkOpacity: false,
|
|
opacityProperty: false,
|
|
contentVisibilityAuto: true,
|
|
visibilityProperty: true,
|
|
// This is an alias for `visibilityProperty`. Contemporary browsers
|
|
// support both. However, this alias has wider browser support (Chrome
|
|
// >= 105 and Firefox >= 106, vs. Chrome >= 121 and Firefox >= 122), so
|
|
// we include it anyway.
|
|
checkVisibilityCSS: true
|
|
});
|
|
return !visible;
|
|
}
|
|
// Fall through to manual visibility checks
|
|
}
|
|
|
|
// NOTE: visibility will be `undefined` if node is detached from the document
|
|
// (see notes about this further down), which means we will consider it visible
|
|
// (this is legacy behavior from a very long way back)
|
|
// NOTE: we check this regardless of `displayCheck="none"` because this is a
|
|
// _visibility_ check, not a _display_ check
|
|
if (getComputedStyle(node).visibility === 'hidden') {
|
|
return true;
|
|
}
|
|
var isDirectSummary = matches.call(node, 'details>summary:first-of-type');
|
|
var nodeUnderDetails = isDirectSummary ? node.parentElement : node;
|
|
if (matches.call(nodeUnderDetails, 'details:not([open]) *')) {
|
|
return true;
|
|
}
|
|
if (!displayCheck || displayCheck === 'full' ||
|
|
// full-native can run this branch when it falls through in case
|
|
// Element#checkVisibility is unsupported
|
|
displayCheck === 'full-native' || displayCheck === 'legacy-full') {
|
|
if (typeof getShadowRoot === 'function') {
|
|
// figure out if we should consider the node to be in an undisclosed shadow and use the
|
|
// 'non-zero-area' fallback
|
|
var originalNode = node;
|
|
while (node) {
|
|
var parentElement = node.parentElement;
|
|
var rootNode = getRootNode(node);
|
|
if (parentElement && !parentElement.shadowRoot && getShadowRoot(parentElement) === true // check if there's an undisclosed shadow
|
|
) {
|
|
// node has an undisclosed shadow which means we can only treat it as a black box, so we
|
|
// fall back to a non-zero-area test
|
|
return isZeroArea(node);
|
|
} else if (node.assignedSlot) {
|
|
// iterate up slot
|
|
node = node.assignedSlot;
|
|
} else if (!parentElement && rootNode !== node.ownerDocument) {
|
|
// cross shadow boundary
|
|
node = rootNode.host;
|
|
} else {
|
|
// iterate up normal dom
|
|
node = parentElement;
|
|
}
|
|
}
|
|
node = originalNode;
|
|
}
|
|
// else, `getShadowRoot` might be true, but all that does is enable shadow DOM support
|
|
// (i.e. it does not also presume that all nodes might have undisclosed shadows); or
|
|
// it might be a falsy value, which means shadow DOM support is disabled
|
|
|
|
// Since we didn't find it sitting in an undisclosed shadow (or shadows are disabled)
|
|
// now we can just test to see if it would normally be visible or not, provided it's
|
|
// attached to the main document.
|
|
// NOTE: We must consider case where node is inside a shadow DOM and given directly to
|
|
// `isTabbable()` or `isFocusable()` -- regardless of `getShadowRoot` option setting.
|
|
|
|
if (isNodeAttached(node)) {
|
|
// this works wherever the node is: if there's at least one client rect, it's
|
|
// somehow displayed; it also covers the CSS 'display: contents' case where the
|
|
// node itself is hidden in place of its contents; and there's no need to search
|
|
// up the hierarchy either
|
|
return !node.getClientRects().length;
|
|
}
|
|
|
|
// Else, the node isn't attached to the document, which means the `getClientRects()`
|
|
// API will __always__ return zero rects (this can happen, for example, if React
|
|
// is used to render nodes onto a detached tree, as confirmed in this thread:
|
|
// https://github.com/facebook/react/issues/9117#issuecomment-284228870)
|
|
//
|
|
// It also means that even window.getComputedStyle(node).display will return `undefined`
|
|
// because styles are only computed for nodes that are in the document.
|
|
//
|
|
// NOTE: THIS HAS BEEN THE CASE FOR YEARS. It is not new, nor is it caused by tabbable
|
|
// somehow. Though it was never stated officially, anyone who has ever used tabbable
|
|
// APIs on nodes in detached containers has actually implicitly used tabbable in what
|
|
// was later (as of v5.2.0 on Apr 9, 2021) called `displayCheck="none"` mode -- essentially
|
|
// considering __everything__ to be visible because of the innability to determine styles.
|
|
//
|
|
// v6.0.0: As of this major release, the default 'full' option __no longer treats detached
|
|
// nodes as visible with the 'none' fallback.__
|
|
if (displayCheck !== 'legacy-full') {
|
|
return true; // hidden
|
|
}
|
|
// else, fallback to 'none' mode and consider the node visible
|
|
} else if (displayCheck === 'non-zero-area') {
|
|
// NOTE: Even though this tests that the node's client rect is non-zero to determine
|
|
// whether it's displayed, and that a detached node will __always__ have a zero-area
|
|
// client rect, we don't special-case for whether the node is attached or not. In
|
|
// this mode, we do want to consider nodes that have a zero area to be hidden at all
|
|
// times, and that includes attached or not.
|
|
return isZeroArea(node);
|
|
}
|
|
|
|
// visible, as far as we can tell, or per current `displayCheck=none` mode, we assume
|
|
// it's visible
|
|
return false;
|
|
};
|
|
|
|
// form fields (nested) inside a disabled fieldset are not focusable/tabbable
|
|
// unless they are in the _first_ <legend> element of the top-most disabled
|
|
// fieldset
|
|
var isDisabledFromFieldset = function isDisabledFromFieldset(node) {
|
|
if (/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(node.tagName)) {
|
|
var parentNode = node.parentElement;
|
|
// check if `node` is contained in a disabled <fieldset>
|
|
while (parentNode) {
|
|
if (parentNode.tagName === 'FIELDSET' && parentNode.disabled) {
|
|
// look for the first <legend> among the children of the disabled <fieldset>
|
|
for (var i = 0; i < parentNode.children.length; i++) {
|
|
var child = parentNode.children.item(i);
|
|
// when the first <legend> (in document order) is found
|
|
if (child.tagName === 'LEGEND') {
|
|
// if its parent <fieldset> is not nested in another disabled <fieldset>,
|
|
// return whether `node` is a descendant of its first <legend>
|
|
return matches.call(parentNode, 'fieldset[disabled] *') ? true : !child.contains(node);
|
|
}
|
|
}
|
|
// the disabled <fieldset> containing `node` has no <legend>
|
|
return true;
|
|
}
|
|
parentNode = parentNode.parentElement;
|
|
}
|
|
}
|
|
|
|
// else, node's tabbable/focusable state should not be affected by a fieldset's
|
|
// enabled/disabled state
|
|
return false;
|
|
};
|
|
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) {
|
|
if (node.disabled || isHiddenInput(node) || isHidden(node, options) ||
|
|
// For a details element with a summary, the summary element gets the focus
|
|
isDetailsWithSummary(node) || isDisabledFromFieldset(node)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable(options, node) {
|
|
if (isNonTabbableRadio(node) || getTabIndex(node) < 0 || !isNodeMatchingSelectorFocusable(options, node)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
var isShadowRootTabbable = function isShadowRootTabbable(shadowHostNode) {
|
|
var tabIndex = parseInt(shadowHostNode.getAttribute('tabindex'), 10);
|
|
if (isNaN(tabIndex) || tabIndex >= 0) {
|
|
return true;
|
|
}
|
|
// If a custom element has an explicit negative tabindex,
|
|
// browsers will not allow tab targeting said element's children.
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* @param {Array.<Element|CandidateScope>} candidates
|
|
* @returns Element[]
|
|
*/
|
|
var _sortByOrder = function sortByOrder(candidates) {
|
|
var regularTabbables = [];
|
|
var orderedTabbables = [];
|
|
candidates.forEach(function (item, i) {
|
|
var isScope = !!item.scopeParent;
|
|
var element = isScope ? item.scopeParent : item;
|
|
var candidateTabindex = getSortOrderTabIndex(element, isScope);
|
|
var elements = isScope ? _sortByOrder(item.candidates) : element;
|
|
if (candidateTabindex === 0) {
|
|
isScope ? regularTabbables.push.apply(regularTabbables, elements) : regularTabbables.push(element);
|
|
} else {
|
|
orderedTabbables.push({
|
|
documentOrder: i,
|
|
tabIndex: candidateTabindex,
|
|
item: item,
|
|
isScope: isScope,
|
|
content: elements
|
|
});
|
|
}
|
|
});
|
|
return orderedTabbables.sort(sortOrderedTabbables).reduce(function (acc, sortable) {
|
|
sortable.isScope ? acc.push.apply(acc, sortable.content) : acc.push(sortable.content);
|
|
return acc;
|
|
}, []).concat(regularTabbables);
|
|
};
|
|
var tabbable = function tabbable(container, options) {
|
|
options = options || {};
|
|
var candidates;
|
|
if (options.getShadowRoot) {
|
|
candidates = _getCandidatesIteratively([container], options.includeContainer, {
|
|
filter: isNodeMatchingSelectorTabbable.bind(null, options),
|
|
flatten: false,
|
|
getShadowRoot: options.getShadowRoot,
|
|
shadowRootFilter: isShadowRootTabbable
|
|
});
|
|
} else {
|
|
candidates = getCandidates(container, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options));
|
|
}
|
|
return _sortByOrder(candidates);
|
|
};
|
|
var focusable = function focusable(container, options) {
|
|
options = options || {};
|
|
var candidates;
|
|
if (options.getShadowRoot) {
|
|
candidates = _getCandidatesIteratively([container], options.includeContainer, {
|
|
filter: isNodeMatchingSelectorFocusable.bind(null, options),
|
|
flatten: true,
|
|
getShadowRoot: options.getShadowRoot
|
|
});
|
|
} else {
|
|
candidates = getCandidates(container, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options));
|
|
}
|
|
return candidates;
|
|
};
|
|
var isTabbable = function isTabbable(node, options) {
|
|
options = options || {};
|
|
if (!node) {
|
|
throw new Error('No node provided');
|
|
}
|
|
if (matches.call(node, candidateSelector) === false) {
|
|
return false;
|
|
}
|
|
return isNodeMatchingSelectorTabbable(options, node);
|
|
};
|
|
var focusableCandidateSelector = /* #__PURE__ */candidateSelectors.concat('iframe:not([inert]):not([inert] *)').join(',');
|
|
var isFocusable = function isFocusable(node, options) {
|
|
options = options || {};
|
|
if (!node) {
|
|
throw new Error('No node provided');
|
|
}
|
|
if (matches.call(node, focusableCandidateSelector) === false) {
|
|
return false;
|
|
}
|
|
return isNodeMatchingSelectorFocusable(options, node);
|
|
};
|
|
|
|
/**
|
|
* Custom positioning reference element.
|
|
* @see https://floating-ui.com/docs/virtual-elements
|
|
*/
|
|
|
|
const sides = ['top', 'right', 'bottom', 'left'];
|
|
const min = Math.min;
|
|
const max = Math.max;
|
|
const round = Math.round;
|
|
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';
|
|
}
|
|
const yAxisSides = /*#__PURE__*/new Set(['top', 'bottom']);
|
|
function getSideAxis(placement) {
|
|
return yAxisSides.has(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]);
|
|
}
|
|
const lrPlacement = ['left', 'right'];
|
|
const rlPlacement = ['right', 'left'];
|
|
const tbPlacement = ['top', 'bottom'];
|
|
const btPlacement = ['bottom', 'top'];
|
|
function getSideList(side, isStart, rtl) {
|
|
switch (side) {
|
|
case 'top':
|
|
case 'bottom':
|
|
if (rtl) return isStart ? rlPlacement : lrPlacement;
|
|
return isStart ? lrPlacement : rlPlacement;
|
|
case 'left':
|
|
case 'right':
|
|
return isStart ? tbPlacement : btPlacement;
|
|
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
|
|
};
|
|
}
|
|
|
|
function computeCoordsFromPlacement(_ref, placement, rtl) {
|
|
let {
|
|
reference,
|
|
floating
|
|
} = _ref;
|
|
const sideAxis = getSideAxis(placement);
|
|
const alignmentAxis = getAlignmentAxis(placement);
|
|
const alignLength = getAxisLength(alignmentAxis);
|
|
const side = getSide(placement);
|
|
const isVertical = sideAxis === 'y';
|
|
const commonX = reference.x + reference.width / 2 - floating.width / 2;
|
|
const commonY = reference.y + reference.height / 2 - floating.height / 2;
|
|
const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;
|
|
let coords;
|
|
switch (side) {
|
|
case 'top':
|
|
coords = {
|
|
x: commonX,
|
|
y: reference.y - floating.height
|
|
};
|
|
break;
|
|
case 'bottom':
|
|
coords = {
|
|
x: commonX,
|
|
y: reference.y + reference.height
|
|
};
|
|
break;
|
|
case 'right':
|
|
coords = {
|
|
x: reference.x + reference.width,
|
|
y: commonY
|
|
};
|
|
break;
|
|
case 'left':
|
|
coords = {
|
|
x: reference.x - floating.width,
|
|
y: commonY
|
|
};
|
|
break;
|
|
default:
|
|
coords = {
|
|
x: reference.x,
|
|
y: reference.y
|
|
};
|
|
}
|
|
switch (getAlignment(placement)) {
|
|
case 'start':
|
|
coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);
|
|
break;
|
|
case 'end':
|
|
coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1);
|
|
break;
|
|
}
|
|
return coords;
|
|
}
|
|
|
|
/**
|
|
* Resolves with an object of overflow side offsets that determine how much the
|
|
* element is overflowing a given clipping boundary on each side.
|
|
* - positive = overflowing the boundary by that number of pixels
|
|
* - negative = how many pixels left before it will overflow
|
|
* - 0 = lies flush with the boundary
|
|
* @see https://floating-ui.com/docs/detectOverflow
|
|
*/
|
|
async function detectOverflow(state, options) {
|
|
var _await$platform$isEle;
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
const {
|
|
x,
|
|
y,
|
|
platform,
|
|
rects,
|
|
elements,
|
|
strategy
|
|
} = state;
|
|
const {
|
|
boundary = 'clippingAncestors',
|
|
rootBoundary = 'viewport',
|
|
elementContext = 'floating',
|
|
altBoundary = false,
|
|
padding = 0
|
|
} = evaluate(options, state);
|
|
const paddingObject = getPaddingObject(padding);
|
|
const altContext = elementContext === 'floating' ? 'reference' : 'floating';
|
|
const element = elements[altBoundary ? altContext : elementContext];
|
|
const clippingClientRect = rectToClientRect(await platform.getClippingRect({
|
|
element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))),
|
|
boundary,
|
|
rootBoundary,
|
|
strategy
|
|
}));
|
|
const rect = elementContext === 'floating' ? {
|
|
x,
|
|
y,
|
|
width: rects.floating.width,
|
|
height: rects.floating.height
|
|
} : rects.reference;
|
|
const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating));
|
|
const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || {
|
|
x: 1,
|
|
y: 1
|
|
} : {
|
|
x: 1,
|
|
y: 1
|
|
};
|
|
const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({
|
|
elements,
|
|
rect,
|
|
offsetParent,
|
|
strategy
|
|
}) : rect);
|
|
return {
|
|
top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
|
|
bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
|
|
left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
|
|
right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Computes the `x` and `y` coordinates that will place the floating element
|
|
* next to a given reference element.
|
|
*
|
|
* This export does not have any `platform` interface logic. You will need to
|
|
* write one for the platform you are using Floating UI with.
|
|
*/
|
|
const computePosition$1 = async (reference, floating, config) => {
|
|
const {
|
|
placement = 'bottom',
|
|
strategy = 'absolute',
|
|
middleware = [],
|
|
platform
|
|
} = config;
|
|
const validMiddleware = middleware.filter(Boolean);
|
|
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating));
|
|
let rects = await platform.getElementRects({
|
|
reference,
|
|
floating,
|
|
strategy
|
|
});
|
|
let {
|
|
x,
|
|
y
|
|
} = computeCoordsFromPlacement(rects, placement, rtl);
|
|
let statefulPlacement = placement;
|
|
let middlewareData = {};
|
|
let resetCount = 0;
|
|
for (let i = 0; i < validMiddleware.length; i++) {
|
|
var _platform$detectOverf;
|
|
const {
|
|
name,
|
|
fn
|
|
} = validMiddleware[i];
|
|
const {
|
|
x: nextX,
|
|
y: nextY,
|
|
data,
|
|
reset
|
|
} = await fn({
|
|
x,
|
|
y,
|
|
initialPlacement: placement,
|
|
placement: statefulPlacement,
|
|
strategy,
|
|
middlewareData,
|
|
rects,
|
|
platform: {
|
|
...platform,
|
|
detectOverflow: (_platform$detectOverf = platform.detectOverflow) != null ? _platform$detectOverf : detectOverflow
|
|
},
|
|
elements: {
|
|
reference,
|
|
floating
|
|
}
|
|
});
|
|
x = nextX != null ? nextX : x;
|
|
y = nextY != null ? nextY : y;
|
|
middlewareData = {
|
|
...middlewareData,
|
|
[name]: {
|
|
...middlewareData[name],
|
|
...data
|
|
}
|
|
};
|
|
if (reset && resetCount <= 50) {
|
|
resetCount++;
|
|
if (typeof reset === 'object') {
|
|
if (reset.placement) {
|
|
statefulPlacement = reset.placement;
|
|
}
|
|
if (reset.rects) {
|
|
rects = reset.rects === true ? await platform.getElementRects({
|
|
reference,
|
|
floating,
|
|
strategy
|
|
}) : reset.rects;
|
|
}
|
|
({
|
|
x,
|
|
y
|
|
} = computeCoordsFromPlacement(rects, statefulPlacement, rtl));
|
|
}
|
|
i = -1;
|
|
}
|
|
}
|
|
return {
|
|
x,
|
|
y,
|
|
placement: statefulPlacement,
|
|
strategy,
|
|
middlewareData
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Provides data to position an inner element of the floating element so that it
|
|
* appears centered to the reference element.
|
|
* @see https://floating-ui.com/docs/arrow
|
|
*/
|
|
const arrow$1 = options => ({
|
|
name: 'arrow',
|
|
options,
|
|
async fn(state) {
|
|
const {
|
|
x,
|
|
y,
|
|
placement,
|
|
rects,
|
|
platform,
|
|
elements,
|
|
middlewareData
|
|
} = state;
|
|
// Since `element` is required, we don't Partial<> the type.
|
|
const {
|
|
element,
|
|
padding = 0
|
|
} = evaluate(options, state) || {};
|
|
if (element == null) {
|
|
return {};
|
|
}
|
|
const paddingObject = getPaddingObject(padding);
|
|
const coords = {
|
|
x,
|
|
y
|
|
};
|
|
const axis = getAlignmentAxis(placement);
|
|
const length = getAxisLength(axis);
|
|
const arrowDimensions = await platform.getDimensions(element);
|
|
const isYAxis = axis === 'y';
|
|
const minProp = isYAxis ? 'top' : 'left';
|
|
const maxProp = isYAxis ? 'bottom' : 'right';
|
|
const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
|
|
const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
|
|
const startDiff = coords[axis] - rects.reference[axis];
|
|
const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element));
|
|
let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0;
|
|
|
|
// DOM platform can return `window` as the `offsetParent`.
|
|
if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) {
|
|
clientSize = elements.floating[clientProp] || rects.floating[length];
|
|
}
|
|
const centerToReference = endDiff / 2 - startDiff / 2;
|
|
|
|
// If the padding is large enough that it causes the arrow to no longer be
|
|
// centered, modify the padding so that it is centered.
|
|
const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
|
|
const minPadding = min(paddingObject[minProp], largestPossiblePadding);
|
|
const maxPadding = min(paddingObject[maxProp], largestPossiblePadding);
|
|
|
|
// Make sure the arrow doesn't overflow the floating element if the center
|
|
// point is outside the floating element's bounds.
|
|
const min$1 = minPadding;
|
|
const max = clientSize - arrowDimensions[length] - maxPadding;
|
|
const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
|
|
const offset = clamp(min$1, center, max);
|
|
|
|
// If the reference is small enough that the arrow's padding causes it to
|
|
// to point to nothing for an aligned placement, adjust the offset of the
|
|
// floating element itself. To ensure `shift()` continues to take action,
|
|
// a single reset is performed when this is true.
|
|
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
|
|
const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0;
|
|
return {
|
|
[axis]: coords[axis] + alignmentOffset,
|
|
data: {
|
|
[axis]: offset,
|
|
centerOffset: center - offset - alignmentOffset,
|
|
...(shouldAddOffset && {
|
|
alignmentOffset
|
|
})
|
|
},
|
|
reset: shouldAddOffset
|
|
};
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Optimizes the visibility of the floating element by flipping the `placement`
|
|
* in order to keep it in view when the preferred placement(s) will overflow the
|
|
* clipping boundary. Alternative to `autoPlacement`.
|
|
* @see https://floating-ui.com/docs/flip
|
|
*/
|
|
const flip$1 = function (options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
return {
|
|
name: 'flip',
|
|
options,
|
|
async fn(state) {
|
|
var _middlewareData$arrow, _middlewareData$flip;
|
|
const {
|
|
placement,
|
|
middlewareData,
|
|
rects,
|
|
initialPlacement,
|
|
platform,
|
|
elements
|
|
} = state;
|
|
const {
|
|
mainAxis: checkMainAxis = true,
|
|
crossAxis: checkCrossAxis = true,
|
|
fallbackPlacements: specifiedFallbackPlacements,
|
|
fallbackStrategy = 'bestFit',
|
|
fallbackAxisSideDirection = 'none',
|
|
flipAlignment = true,
|
|
...detectOverflowOptions
|
|
} = evaluate(options, state);
|
|
|
|
// If a reset by the arrow was caused due to an alignment offset being
|
|
// added, we should skip any logic now since `flip()` has already done its
|
|
// work.
|
|
// https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643
|
|
if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
|
|
return {};
|
|
}
|
|
const side = getSide(placement);
|
|
const initialSideAxis = getSideAxis(initialPlacement);
|
|
const isBasePlacement = getSide(initialPlacement) === initialPlacement;
|
|
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
|
|
const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement));
|
|
const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== 'none';
|
|
if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) {
|
|
fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
|
|
}
|
|
const placements = [initialPlacement, ...fallbackPlacements];
|
|
const overflow = await platform.detectOverflow(state, detectOverflowOptions);
|
|
const overflows = [];
|
|
let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
|
|
if (checkMainAxis) {
|
|
overflows.push(overflow[side]);
|
|
}
|
|
if (checkCrossAxis) {
|
|
const sides = getAlignmentSides(placement, rects, rtl);
|
|
overflows.push(overflow[sides[0]], overflow[sides[1]]);
|
|
}
|
|
overflowsData = [...overflowsData, {
|
|
placement,
|
|
overflows
|
|
}];
|
|
|
|
// One or more sides is overflowing.
|
|
if (!overflows.every(side => side <= 0)) {
|
|
var _middlewareData$flip2, _overflowsData$filter;
|
|
const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
|
|
const nextPlacement = placements[nextIndex];
|
|
if (nextPlacement) {
|
|
const ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== getSideAxis(nextPlacement) : false;
|
|
if (!ignoreCrossAxisOverflow ||
|
|
// We leave the current main axis only if every placement on that axis
|
|
// overflows the main axis.
|
|
overflowsData.every(d => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) {
|
|
// Try next placement and re-run the lifecycle.
|
|
return {
|
|
data: {
|
|
index: nextIndex,
|
|
overflows: overflowsData
|
|
},
|
|
reset: {
|
|
placement: nextPlacement
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// First, find the candidates that fit on the mainAxis side of overflow,
|
|
// then find the placement that fits the best on the main crossAxis side.
|
|
let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement;
|
|
|
|
// Otherwise fallback.
|
|
if (!resetPlacement) {
|
|
switch (fallbackStrategy) {
|
|
case 'bestFit':
|
|
{
|
|
var _overflowsData$filter2;
|
|
const placement = (_overflowsData$filter2 = overflowsData.filter(d => {
|
|
if (hasFallbackAxisSideDirection) {
|
|
const currentSideAxis = getSideAxis(d.placement);
|
|
return currentSideAxis === initialSideAxis ||
|
|
// Create a bias to the `y` side axis due to horizontal
|
|
// reading directions favoring greater width.
|
|
currentSideAxis === 'y';
|
|
}
|
|
return true;
|
|
}).map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0];
|
|
if (placement) {
|
|
resetPlacement = placement;
|
|
}
|
|
break;
|
|
}
|
|
case 'initialPlacement':
|
|
resetPlacement = initialPlacement;
|
|
break;
|
|
}
|
|
}
|
|
if (placement !== resetPlacement) {
|
|
return {
|
|
reset: {
|
|
placement: resetPlacement
|
|
}
|
|
};
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
};
|
|
};
|
|
|
|
function getSideOffsets(overflow, rect) {
|
|
return {
|
|
top: overflow.top - rect.height,
|
|
right: overflow.right - rect.width,
|
|
bottom: overflow.bottom - rect.height,
|
|
left: overflow.left - rect.width
|
|
};
|
|
}
|
|
function isAnySideFullyClipped(overflow) {
|
|
return sides.some(side => overflow[side] >= 0);
|
|
}
|
|
/**
|
|
* Provides data to hide the floating element in applicable situations, such as
|
|
* when it is not in the same clipping context as the reference element.
|
|
* @see https://floating-ui.com/docs/hide
|
|
*/
|
|
const hide$1 = function (options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
return {
|
|
name: 'hide',
|
|
options,
|
|
async fn(state) {
|
|
const {
|
|
rects,
|
|
platform
|
|
} = state;
|
|
const {
|
|
strategy = 'referenceHidden',
|
|
...detectOverflowOptions
|
|
} = evaluate(options, state);
|
|
switch (strategy) {
|
|
case 'referenceHidden':
|
|
{
|
|
const overflow = await platform.detectOverflow(state, {
|
|
...detectOverflowOptions,
|
|
elementContext: 'reference'
|
|
});
|
|
const offsets = getSideOffsets(overflow, rects.reference);
|
|
return {
|
|
data: {
|
|
referenceHiddenOffsets: offsets,
|
|
referenceHidden: isAnySideFullyClipped(offsets)
|
|
}
|
|
};
|
|
}
|
|
case 'escaped':
|
|
{
|
|
const overflow = await platform.detectOverflow(state, {
|
|
...detectOverflowOptions,
|
|
altBoundary: true
|
|
});
|
|
const offsets = getSideOffsets(overflow, rects.floating);
|
|
return {
|
|
data: {
|
|
escapedOffsets: offsets,
|
|
escaped: isAnySideFullyClipped(offsets)
|
|
}
|
|
};
|
|
}
|
|
default:
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
const originSides = /*#__PURE__*/new Set(['left', 'top']);
|
|
|
|
// For type backwards-compatibility, the `OffsetOptions` type was also
|
|
// Derivable.
|
|
|
|
async function convertValueToCoords(state, options) {
|
|
const {
|
|
placement,
|
|
platform,
|
|
elements
|
|
} = state;
|
|
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
|
|
const side = getSide(placement);
|
|
const alignment = getAlignment(placement);
|
|
const isVertical = getSideAxis(placement) === 'y';
|
|
const mainAxisMulti = originSides.has(side) ? -1 : 1;
|
|
const crossAxisMulti = rtl && isVertical ? -1 : 1;
|
|
const rawValue = evaluate(options, state);
|
|
|
|
// eslint-disable-next-line prefer-const
|
|
let {
|
|
mainAxis,
|
|
crossAxis,
|
|
alignmentAxis
|
|
} = typeof rawValue === 'number' ? {
|
|
mainAxis: rawValue,
|
|
crossAxis: 0,
|
|
alignmentAxis: null
|
|
} : {
|
|
mainAxis: rawValue.mainAxis || 0,
|
|
crossAxis: rawValue.crossAxis || 0,
|
|
alignmentAxis: rawValue.alignmentAxis
|
|
};
|
|
if (alignment && typeof alignmentAxis === 'number') {
|
|
crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis;
|
|
}
|
|
return isVertical ? {
|
|
x: crossAxis * crossAxisMulti,
|
|
y: mainAxis * mainAxisMulti
|
|
} : {
|
|
x: mainAxis * mainAxisMulti,
|
|
y: crossAxis * crossAxisMulti
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Modifies the placement by translating the floating element along the
|
|
* specified axes.
|
|
* A number (shorthand for `mainAxis` or distance), or an axes configuration
|
|
* object may be passed.
|
|
* @see https://floating-ui.com/docs/offset
|
|
*/
|
|
const offset$1 = function (options) {
|
|
if (options === void 0) {
|
|
options = 0;
|
|
}
|
|
return {
|
|
name: 'offset',
|
|
options,
|
|
async fn(state) {
|
|
var _middlewareData$offse, _middlewareData$arrow;
|
|
const {
|
|
x,
|
|
y,
|
|
placement,
|
|
middlewareData
|
|
} = state;
|
|
const diffCoords = await convertValueToCoords(state, options);
|
|
|
|
// If the placement is the same and the arrow caused an alignment offset
|
|
// then we don't need to change the positioning coordinates.
|
|
if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
|
|
return {};
|
|
}
|
|
return {
|
|
x: x + diffCoords.x,
|
|
y: y + diffCoords.y,
|
|
data: {
|
|
...diffCoords,
|
|
placement
|
|
}
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Optimizes the visibility of the floating element by shifting it in order to
|
|
* keep it in view when it will overflow the clipping boundary.
|
|
* @see https://floating-ui.com/docs/shift
|
|
*/
|
|
const shift$1 = function (options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
return {
|
|
name: 'shift',
|
|
options,
|
|
async fn(state) {
|
|
const {
|
|
x,
|
|
y,
|
|
placement,
|
|
platform
|
|
} = state;
|
|
const {
|
|
mainAxis: checkMainAxis = true,
|
|
crossAxis: checkCrossAxis = false,
|
|
limiter = {
|
|
fn: _ref => {
|
|
let {
|
|
x,
|
|
y
|
|
} = _ref;
|
|
return {
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
},
|
|
...detectOverflowOptions
|
|
} = evaluate(options, state);
|
|
const coords = {
|
|
x,
|
|
y
|
|
};
|
|
const overflow = await platform.detectOverflow(state, detectOverflowOptions);
|
|
const crossAxis = getSideAxis(getSide(placement));
|
|
const mainAxis = getOppositeAxis(crossAxis);
|
|
let mainAxisCoord = coords[mainAxis];
|
|
let crossAxisCoord = coords[crossAxis];
|
|
if (checkMainAxis) {
|
|
const minSide = mainAxis === 'y' ? 'top' : 'left';
|
|
const maxSide = mainAxis === 'y' ? 'bottom' : 'right';
|
|
const min = mainAxisCoord + overflow[minSide];
|
|
const max = mainAxisCoord - overflow[maxSide];
|
|
mainAxisCoord = clamp(min, mainAxisCoord, max);
|
|
}
|
|
if (checkCrossAxis) {
|
|
const minSide = crossAxis === 'y' ? 'top' : 'left';
|
|
const maxSide = crossAxis === 'y' ? 'bottom' : 'right';
|
|
const min = crossAxisCoord + overflow[minSide];
|
|
const max = crossAxisCoord - overflow[maxSide];
|
|
crossAxisCoord = clamp(min, crossAxisCoord, max);
|
|
}
|
|
const limitedCoords = limiter.fn({
|
|
...state,
|
|
[mainAxis]: mainAxisCoord,
|
|
[crossAxis]: crossAxisCoord
|
|
});
|
|
return {
|
|
...limitedCoords,
|
|
data: {
|
|
x: limitedCoords.x - x,
|
|
y: limitedCoords.y - y,
|
|
enabled: {
|
|
[mainAxis]: checkMainAxis,
|
|
[crossAxis]: checkCrossAxis
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
};
|
|
/**
|
|
* Built-in `limiter` that will stop `shift()` at a certain point.
|
|
*/
|
|
const limitShift$1 = function (options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
return {
|
|
options,
|
|
fn(state) {
|
|
const {
|
|
x,
|
|
y,
|
|
placement,
|
|
rects,
|
|
middlewareData
|
|
} = state;
|
|
const {
|
|
offset = 0,
|
|
mainAxis: checkMainAxis = true,
|
|
crossAxis: checkCrossAxis = true
|
|
} = evaluate(options, state);
|
|
const coords = {
|
|
x,
|
|
y
|
|
};
|
|
const crossAxis = getSideAxis(placement);
|
|
const mainAxis = getOppositeAxis(crossAxis);
|
|
let mainAxisCoord = coords[mainAxis];
|
|
let crossAxisCoord = coords[crossAxis];
|
|
const rawOffset = evaluate(offset, state);
|
|
const computedOffset = typeof rawOffset === 'number' ? {
|
|
mainAxis: rawOffset,
|
|
crossAxis: 0
|
|
} : {
|
|
mainAxis: 0,
|
|
crossAxis: 0,
|
|
...rawOffset
|
|
};
|
|
if (checkMainAxis) {
|
|
const len = mainAxis === 'y' ? 'height' : 'width';
|
|
const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis;
|
|
const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis;
|
|
if (mainAxisCoord < limitMin) {
|
|
mainAxisCoord = limitMin;
|
|
} else if (mainAxisCoord > limitMax) {
|
|
mainAxisCoord = limitMax;
|
|
}
|
|
}
|
|
if (checkCrossAxis) {
|
|
var _middlewareData$offse, _middlewareData$offse2;
|
|
const len = mainAxis === 'y' ? 'width' : 'height';
|
|
const isOriginSide = originSides.has(getSide(placement));
|
|
const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis);
|
|
const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0);
|
|
if (crossAxisCoord < limitMin) {
|
|
crossAxisCoord = limitMin;
|
|
} else if (crossAxisCoord > limitMax) {
|
|
crossAxisCoord = limitMax;
|
|
}
|
|
}
|
|
return {
|
|
[mainAxis]: mainAxisCoord,
|
|
[crossAxis]: crossAxisCoord
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Provides data that allows you to change the size of the floating element —
|
|
* for instance, prevent it from overflowing the clipping boundary or match the
|
|
* width of the reference element.
|
|
* @see https://floating-ui.com/docs/size
|
|
*/
|
|
const size$1 = function (options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
return {
|
|
name: 'size',
|
|
options,
|
|
async fn(state) {
|
|
var _state$middlewareData, _state$middlewareData2;
|
|
const {
|
|
placement,
|
|
rects,
|
|
platform,
|
|
elements
|
|
} = state;
|
|
const {
|
|
apply = () => {},
|
|
...detectOverflowOptions
|
|
} = evaluate(options, state);
|
|
const overflow = await platform.detectOverflow(state, detectOverflowOptions);
|
|
const side = getSide(placement);
|
|
const alignment = getAlignment(placement);
|
|
const isYAxis = getSideAxis(placement) === 'y';
|
|
const {
|
|
width,
|
|
height
|
|
} = rects.floating;
|
|
let heightSide;
|
|
let widthSide;
|
|
if (side === 'top' || side === 'bottom') {
|
|
heightSide = side;
|
|
widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right';
|
|
} else {
|
|
widthSide = side;
|
|
heightSide = alignment === 'end' ? 'top' : 'bottom';
|
|
}
|
|
const maximumClippingHeight = height - overflow.top - overflow.bottom;
|
|
const maximumClippingWidth = width - overflow.left - overflow.right;
|
|
const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight);
|
|
const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth);
|
|
const noShift = !state.middlewareData.shift;
|
|
let availableHeight = overflowAvailableHeight;
|
|
let availableWidth = overflowAvailableWidth;
|
|
if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) {
|
|
availableWidth = maximumClippingWidth;
|
|
}
|
|
if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) {
|
|
availableHeight = maximumClippingHeight;
|
|
}
|
|
if (noShift && !alignment) {
|
|
const xMin = max(overflow.left, 0);
|
|
const xMax = max(overflow.right, 0);
|
|
const yMin = max(overflow.top, 0);
|
|
const yMax = max(overflow.bottom, 0);
|
|
if (isYAxis) {
|
|
availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right));
|
|
} else {
|
|
availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom));
|
|
}
|
|
}
|
|
await apply({
|
|
...state,
|
|
availableWidth,
|
|
availableHeight
|
|
});
|
|
const nextDimensions = await platform.getDimensions(elements.floating);
|
|
if (width !== nextDimensions.width || height !== nextDimensions.height) {
|
|
return {
|
|
reset: {
|
|
rects: true
|
|
}
|
|
};
|
|
}
|
|
return {};
|
|
}
|
|
};
|
|
};
|
|
|
|
function hasWindow() {
|
|
return typeof window !== 'undefined';
|
|
}
|
|
function getNodeName(node) {
|
|
if (isNode$1(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$1(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$1(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement;
|
|
}
|
|
function isNode$1(value) {
|
|
if (!hasWindow()) {
|
|
return false;
|
|
}
|
|
return value instanceof Node || value instanceof getWindow$1(value).Node;
|
|
}
|
|
function isElement$1(value) {
|
|
if (!hasWindow()) {
|
|
return false;
|
|
}
|
|
return value instanceof Element || value instanceof getWindow$1(value).Element;
|
|
}
|
|
function isHTMLElement$2(value) {
|
|
if (!hasWindow()) {
|
|
return false;
|
|
}
|
|
return value instanceof HTMLElement || value instanceof getWindow$1(value).HTMLElement;
|
|
}
|
|
function isShadowRoot$1(value) {
|
|
if (!hasWindow() || typeof ShadowRoot === 'undefined') {
|
|
return false;
|
|
}
|
|
return value instanceof ShadowRoot || value instanceof getWindow$1(value).ShadowRoot;
|
|
}
|
|
const invalidOverflowDisplayValues = /*#__PURE__*/new Set(['inline', 'contents']);
|
|
function isOverflowElement(element) {
|
|
const {
|
|
overflow,
|
|
overflowX,
|
|
overflowY,
|
|
display
|
|
} = getComputedStyle$1(element);
|
|
return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !invalidOverflowDisplayValues.has(display);
|
|
}
|
|
const tableElements = /*#__PURE__*/new Set(['table', 'td', 'th']);
|
|
function isTableElement(element) {
|
|
return tableElements.has(getNodeName(element));
|
|
}
|
|
const topLayerSelectors = [':popover-open', ':modal'];
|
|
function isTopLayer(element) {
|
|
return topLayerSelectors.some(selector => {
|
|
try {
|
|
return element.matches(selector);
|
|
} catch (_e) {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
const transformProperties = ['transform', 'translate', 'scale', 'rotate', 'perspective'];
|
|
const willChangeValues = ['transform', 'translate', 'scale', 'rotate', 'perspective', 'filter'];
|
|
const containValues = ['paint', 'layout', 'strict', 'content'];
|
|
function isContainingBlock(elementOrCss) {
|
|
const webkit = isWebKit();
|
|
const css = isElement$1(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 transformProperties.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) || willChangeValues.some(value => (css.willChange || '').includes(value)) || containValues.some(value => (css.contain || '').includes(value));
|
|
}
|
|
function getContainingBlock(element) {
|
|
let currentNode = getParentNode(element);
|
|
while (isHTMLElement$2(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');
|
|
}
|
|
const lastTraversableNodeNames = /*#__PURE__*/new Set(['html', 'body', '#document']);
|
|
function isLastTraversableNode(node) {
|
|
return lastTraversableNodeNames.has(getNodeName(node));
|
|
}
|
|
function getComputedStyle$1(element) {
|
|
return getWindow$1(element).getComputedStyle(element);
|
|
}
|
|
function getNodeScroll(element) {
|
|
if (isElement$1(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$1(node) && node.host ||
|
|
// Fallback.
|
|
getDocumentElement(node);
|
|
return isShadowRoot$1(result) ? result.host : result;
|
|
}
|
|
function getNearestOverflowAncestor(node) {
|
|
const parentNode = getParentNode(node);
|
|
if (isLastTraversableNode(parentNode)) {
|
|
return node.ownerDocument ? node.ownerDocument.body : node.body;
|
|
}
|
|
if (isHTMLElement$2(parentNode) && isOverflowElement(parentNode)) {
|
|
return parentNode;
|
|
}
|
|
return getNearestOverflowAncestor(parentNode);
|
|
}
|
|
function getOverflowAncestors(node, list, traverseIframes) {
|
|
var _node$ownerDocument2;
|
|
if (list === void 0) {
|
|
list = [];
|
|
}
|
|
const scrollableAncestor = getNearestOverflowAncestor(node);
|
|
const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body);
|
|
const win = getWindow$1(scrollableAncestor);
|
|
if (isBody) {
|
|
getFrameElement(win);
|
|
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], []);
|
|
}
|
|
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, []));
|
|
}
|
|
function getFrameElement(win) {
|
|
return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
|
|
}
|
|
|
|
function getCssDimensions(element) {
|
|
const css = getComputedStyle$1(element);
|
|
// In testing environments, the `width` and `height` properties are empty
|
|
// strings for SVG elements, returning NaN. Fallback to `0` in this case.
|
|
let width = parseFloat(css.width) || 0;
|
|
let height = parseFloat(css.height) || 0;
|
|
const hasOffset = isHTMLElement$2(element);
|
|
const offsetWidth = hasOffset ? element.offsetWidth : width;
|
|
const offsetHeight = hasOffset ? element.offsetHeight : height;
|
|
const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
|
|
if (shouldFallback) {
|
|
width = offsetWidth;
|
|
height = offsetHeight;
|
|
}
|
|
return {
|
|
width,
|
|
height,
|
|
$: shouldFallback
|
|
};
|
|
}
|
|
|
|
function unwrapElement(element) {
|
|
return !isElement$1(element) ? element.contextElement : element;
|
|
}
|
|
|
|
function getScale(element) {
|
|
const domElement = unwrapElement(element);
|
|
if (!isHTMLElement$2(domElement)) {
|
|
return createCoords(1);
|
|
}
|
|
const rect = domElement.getBoundingClientRect();
|
|
const {
|
|
width,
|
|
height,
|
|
$
|
|
} = getCssDimensions(domElement);
|
|
let x = ($ ? round(rect.width) : rect.width) / width;
|
|
let y = ($ ? round(rect.height) : rect.height) / height;
|
|
|
|
// 0, NaN, or Infinity should always fallback to 1.
|
|
|
|
if (!x || !Number.isFinite(x)) {
|
|
x = 1;
|
|
}
|
|
if (!y || !Number.isFinite(y)) {
|
|
y = 1;
|
|
}
|
|
return {
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
|
|
const noOffsets = /*#__PURE__*/createCoords(0);
|
|
function getVisualOffsets(element) {
|
|
const win = getWindow$1(element);
|
|
if (!isWebKit() || !win.visualViewport) {
|
|
return noOffsets;
|
|
}
|
|
return {
|
|
x: win.visualViewport.offsetLeft,
|
|
y: win.visualViewport.offsetTop
|
|
};
|
|
}
|
|
function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) {
|
|
if (isFixed === void 0) {
|
|
isFixed = false;
|
|
}
|
|
if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow$1(element)) {
|
|
return false;
|
|
}
|
|
return isFixed;
|
|
}
|
|
|
|
function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
|
|
if (includeScale === void 0) {
|
|
includeScale = false;
|
|
}
|
|
if (isFixedStrategy === void 0) {
|
|
isFixedStrategy = false;
|
|
}
|
|
const clientRect = element.getBoundingClientRect();
|
|
const domElement = unwrapElement(element);
|
|
let scale = createCoords(1);
|
|
if (includeScale) {
|
|
if (offsetParent) {
|
|
if (isElement$1(offsetParent)) {
|
|
scale = getScale(offsetParent);
|
|
}
|
|
} else {
|
|
scale = getScale(element);
|
|
}
|
|
}
|
|
const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0);
|
|
let x = (clientRect.left + visualOffsets.x) / scale.x;
|
|
let y = (clientRect.top + visualOffsets.y) / scale.y;
|
|
let width = clientRect.width / scale.x;
|
|
let height = clientRect.height / scale.y;
|
|
if (domElement) {
|
|
const win = getWindow$1(domElement);
|
|
const offsetWin = offsetParent && isElement$1(offsetParent) ? getWindow$1(offsetParent) : offsetParent;
|
|
let currentWin = win;
|
|
let currentIFrame = getFrameElement(currentWin);
|
|
while (currentIFrame && offsetParent && offsetWin !== currentWin) {
|
|
const iframeScale = getScale(currentIFrame);
|
|
const iframeRect = currentIFrame.getBoundingClientRect();
|
|
const css = getComputedStyle$1(currentIFrame);
|
|
const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
|
|
const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
|
|
x *= iframeScale.x;
|
|
y *= iframeScale.y;
|
|
width *= iframeScale.x;
|
|
height *= iframeScale.y;
|
|
x += left;
|
|
y += top;
|
|
currentWin = getWindow$1(currentIFrame);
|
|
currentIFrame = getFrameElement(currentWin);
|
|
}
|
|
}
|
|
return rectToClientRect({
|
|
width,
|
|
height,
|
|
x,
|
|
y
|
|
});
|
|
}
|
|
|
|
// If <html> has a CSS width greater than the viewport, then this will be
|
|
// incorrect for RTL.
|
|
function getWindowScrollBarX(element, rect) {
|
|
const leftScroll = getNodeScroll(element).scrollLeft;
|
|
if (!rect) {
|
|
return getBoundingClientRect(getDocumentElement(element)).left + leftScroll;
|
|
}
|
|
return rect.left + leftScroll;
|
|
}
|
|
|
|
function getHTMLOffset(documentElement, scroll) {
|
|
const htmlRect = documentElement.getBoundingClientRect();
|
|
const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect);
|
|
const y = htmlRect.top + scroll.scrollTop;
|
|
return {
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
|
|
function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
|
|
let {
|
|
elements,
|
|
rect,
|
|
offsetParent,
|
|
strategy
|
|
} = _ref;
|
|
const isFixed = strategy === 'fixed';
|
|
const documentElement = getDocumentElement(offsetParent);
|
|
const topLayer = elements ? isTopLayer(elements.floating) : false;
|
|
if (offsetParent === documentElement || topLayer && isFixed) {
|
|
return rect;
|
|
}
|
|
let scroll = {
|
|
scrollLeft: 0,
|
|
scrollTop: 0
|
|
};
|
|
let scale = createCoords(1);
|
|
const offsets = createCoords(0);
|
|
const isOffsetParentAnElement = isHTMLElement$2(offsetParent);
|
|
if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
|
|
if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
|
|
scroll = getNodeScroll(offsetParent);
|
|
}
|
|
if (isHTMLElement$2(offsetParent)) {
|
|
const offsetRect = getBoundingClientRect(offsetParent);
|
|
scale = getScale(offsetParent);
|
|
offsets.x = offsetRect.x + offsetParent.clientLeft;
|
|
offsets.y = offsetRect.y + offsetParent.clientTop;
|
|
}
|
|
}
|
|
const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
|
|
return {
|
|
width: rect.width * scale.x,
|
|
height: rect.height * scale.y,
|
|
x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x,
|
|
y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y
|
|
};
|
|
}
|
|
|
|
function getClientRects(element) {
|
|
return Array.from(element.getClientRects());
|
|
}
|
|
|
|
// Gets the entire size of the scrollable document area, even extending outside
|
|
// of the `<html>` and `<body>` rect bounds if horizontally scrollable.
|
|
function getDocumentRect(element) {
|
|
const html = getDocumentElement(element);
|
|
const scroll = getNodeScroll(element);
|
|
const body = element.ownerDocument.body;
|
|
const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth);
|
|
const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
|
|
let x = -scroll.scrollLeft + getWindowScrollBarX(element);
|
|
const y = -scroll.scrollTop;
|
|
if (getComputedStyle$1(body).direction === 'rtl') {
|
|
x += max(html.clientWidth, body.clientWidth) - width;
|
|
}
|
|
return {
|
|
width,
|
|
height,
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
|
|
// Safety check: ensure the scrollbar space is reasonable in case this
|
|
// calculation is affected by unusual styles.
|
|
// Most scrollbars leave 15-18px of space.
|
|
const SCROLLBAR_MAX = 25;
|
|
function getViewportRect(element, strategy) {
|
|
const win = getWindow$1(element);
|
|
const html = getDocumentElement(element);
|
|
const visualViewport = win.visualViewport;
|
|
let width = html.clientWidth;
|
|
let height = html.clientHeight;
|
|
let x = 0;
|
|
let y = 0;
|
|
if (visualViewport) {
|
|
width = visualViewport.width;
|
|
height = visualViewport.height;
|
|
const visualViewportBased = isWebKit();
|
|
if (!visualViewportBased || visualViewportBased && strategy === 'fixed') {
|
|
x = visualViewport.offsetLeft;
|
|
y = visualViewport.offsetTop;
|
|
}
|
|
}
|
|
const windowScrollbarX = getWindowScrollBarX(html);
|
|
// <html> `overflow: hidden` + `scrollbar-gutter: stable` reduces the
|
|
// visual width of the <html> but this is not considered in the size
|
|
// of `html.clientWidth`.
|
|
if (windowScrollbarX <= 0) {
|
|
const doc = html.ownerDocument;
|
|
const body = doc.body;
|
|
const bodyStyles = getComputedStyle(body);
|
|
const bodyMarginInline = doc.compatMode === 'CSS1Compat' ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0;
|
|
const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline);
|
|
if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
|
|
width -= clippingStableScrollbarWidth;
|
|
}
|
|
} else if (windowScrollbarX <= SCROLLBAR_MAX) {
|
|
// If the <body> scrollbar is on the left, the width needs to be extended
|
|
// by the scrollbar amount so there isn't extra space on the right.
|
|
width += windowScrollbarX;
|
|
}
|
|
return {
|
|
width,
|
|
height,
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
|
|
const absoluteOrFixed = /*#__PURE__*/new Set(['absolute', 'fixed']);
|
|
// Returns the inner client rect, subtracting scrollbars if present.
|
|
function getInnerBoundingClientRect(element, strategy) {
|
|
const clientRect = getBoundingClientRect(element, true, strategy === 'fixed');
|
|
const top = clientRect.top + element.clientTop;
|
|
const left = clientRect.left + element.clientLeft;
|
|
const scale = isHTMLElement$2(element) ? getScale(element) : createCoords(1);
|
|
const width = element.clientWidth * scale.x;
|
|
const height = element.clientHeight * scale.y;
|
|
const x = left * scale.x;
|
|
const y = top * scale.y;
|
|
return {
|
|
width,
|
|
height,
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {
|
|
let rect;
|
|
if (clippingAncestor === 'viewport') {
|
|
rect = getViewportRect(element, strategy);
|
|
} else if (clippingAncestor === 'document') {
|
|
rect = getDocumentRect(getDocumentElement(element));
|
|
} else if (isElement$1(clippingAncestor)) {
|
|
rect = getInnerBoundingClientRect(clippingAncestor, strategy);
|
|
} else {
|
|
const visualOffsets = getVisualOffsets(element);
|
|
rect = {
|
|
x: clippingAncestor.x - visualOffsets.x,
|
|
y: clippingAncestor.y - visualOffsets.y,
|
|
width: clippingAncestor.width,
|
|
height: clippingAncestor.height
|
|
};
|
|
}
|
|
return rectToClientRect(rect);
|
|
}
|
|
function hasFixedPositionAncestor(element, stopNode) {
|
|
const parentNode = getParentNode(element);
|
|
if (parentNode === stopNode || !isElement$1(parentNode) || isLastTraversableNode(parentNode)) {
|
|
return false;
|
|
}
|
|
return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
|
|
}
|
|
|
|
// A "clipping ancestor" is an `overflow` element with the characteristic of
|
|
// clipping (or hiding) child elements. This returns all clipping ancestors
|
|
// of the given element up the tree.
|
|
function getClippingElementAncestors(element, cache) {
|
|
const cachedResult = cache.get(element);
|
|
if (cachedResult) {
|
|
return cachedResult;
|
|
}
|
|
let result = getOverflowAncestors(element, []).filter(el => isElement$1(el) && getNodeName(el) !== 'body');
|
|
let currentContainingBlockComputedStyle = null;
|
|
const elementIsFixed = getComputedStyle$1(element).position === 'fixed';
|
|
let currentNode = elementIsFixed ? getParentNode(element) : element;
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
|
|
while (isElement$1(currentNode) && !isLastTraversableNode(currentNode)) {
|
|
const computedStyle = getComputedStyle$1(currentNode);
|
|
const currentNodeIsContaining = isContainingBlock(currentNode);
|
|
if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
|
|
currentContainingBlockComputedStyle = null;
|
|
}
|
|
const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && absoluteOrFixed.has(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
|
|
if (shouldDropCurrentNode) {
|
|
// Drop non-containing blocks.
|
|
result = result.filter(ancestor => ancestor !== currentNode);
|
|
} else {
|
|
// Record last containing block for next iteration.
|
|
currentContainingBlockComputedStyle = computedStyle;
|
|
}
|
|
currentNode = getParentNode(currentNode);
|
|
}
|
|
cache.set(element, result);
|
|
return result;
|
|
}
|
|
|
|
// Gets the maximum area that the element is visible in due to any number of
|
|
// clipping ancestors.
|
|
function getClippingRect(_ref) {
|
|
let {
|
|
element,
|
|
boundary,
|
|
rootBoundary,
|
|
strategy
|
|
} = _ref;
|
|
const elementClippingAncestors = boundary === 'clippingAncestors' ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary);
|
|
const clippingAncestors = [...elementClippingAncestors, rootBoundary];
|
|
const firstClippingAncestor = clippingAncestors[0];
|
|
const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
|
|
const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
|
|
accRect.top = max(rect.top, accRect.top);
|
|
accRect.right = min(rect.right, accRect.right);
|
|
accRect.bottom = min(rect.bottom, accRect.bottom);
|
|
accRect.left = max(rect.left, accRect.left);
|
|
return accRect;
|
|
}, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
|
|
return {
|
|
width: clippingRect.right - clippingRect.left,
|
|
height: clippingRect.bottom - clippingRect.top,
|
|
x: clippingRect.left,
|
|
y: clippingRect.top
|
|
};
|
|
}
|
|
|
|
function getDimensions(element) {
|
|
const {
|
|
width,
|
|
height
|
|
} = getCssDimensions(element);
|
|
return {
|
|
width,
|
|
height
|
|
};
|
|
}
|
|
|
|
function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
|
|
const isOffsetParentAnElement = isHTMLElement$2(offsetParent);
|
|
const documentElement = getDocumentElement(offsetParent);
|
|
const isFixed = strategy === 'fixed';
|
|
const rect = getBoundingClientRect(element, true, isFixed, offsetParent);
|
|
let scroll = {
|
|
scrollLeft: 0,
|
|
scrollTop: 0
|
|
};
|
|
const offsets = createCoords(0);
|
|
|
|
// If the <body> scrollbar appears on the left (e.g. RTL systems). Use
|
|
// Firefox with layout.scrollbar.side = 3 in about:config to test this.
|
|
function setLeftRTLScrollbarOffset() {
|
|
offsets.x = getWindowScrollBarX(documentElement);
|
|
}
|
|
if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
|
|
if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
|
|
scroll = getNodeScroll(offsetParent);
|
|
}
|
|
if (isOffsetParentAnElement) {
|
|
const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent);
|
|
offsets.x = offsetRect.x + offsetParent.clientLeft;
|
|
offsets.y = offsetRect.y + offsetParent.clientTop;
|
|
} else if (documentElement) {
|
|
setLeftRTLScrollbarOffset();
|
|
}
|
|
}
|
|
if (isFixed && !isOffsetParentAnElement && documentElement) {
|
|
setLeftRTLScrollbarOffset();
|
|
}
|
|
const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
|
|
const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x;
|
|
const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y;
|
|
return {
|
|
x,
|
|
y,
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
}
|
|
|
|
function isStaticPositioned(element) {
|
|
return getComputedStyle$1(element).position === 'static';
|
|
}
|
|
|
|
function getTrueOffsetParent(element, polyfill) {
|
|
if (!isHTMLElement$2(element) || getComputedStyle$1(element).position === 'fixed') {
|
|
return null;
|
|
}
|
|
if (polyfill) {
|
|
return polyfill(element);
|
|
}
|
|
let rawOffsetParent = element.offsetParent;
|
|
|
|
// Firefox returns the <html> element as the offsetParent if it's non-static,
|
|
// while Chrome and Safari return the <body> element. The <body> element must
|
|
// be used to perform the correct calculations even if the <html> element is
|
|
// non-static.
|
|
if (getDocumentElement(element) === rawOffsetParent) {
|
|
rawOffsetParent = rawOffsetParent.ownerDocument.body;
|
|
}
|
|
return rawOffsetParent;
|
|
}
|
|
|
|
// Gets the closest ancestor positioned element. Handles some edge cases,
|
|
// such as table ancestors and cross browser bugs.
|
|
function getOffsetParent(element, polyfill) {
|
|
const win = getWindow$1(element);
|
|
if (isTopLayer(element)) {
|
|
return win;
|
|
}
|
|
if (!isHTMLElement$2(element)) {
|
|
let svgOffsetParent = getParentNode(element);
|
|
while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) {
|
|
if (isElement$1(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) {
|
|
return svgOffsetParent;
|
|
}
|
|
svgOffsetParent = getParentNode(svgOffsetParent);
|
|
}
|
|
return win;
|
|
}
|
|
let offsetParent = getTrueOffsetParent(element, polyfill);
|
|
while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) {
|
|
offsetParent = getTrueOffsetParent(offsetParent, polyfill);
|
|
}
|
|
if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) {
|
|
return win;
|
|
}
|
|
return offsetParent || getContainingBlock(element) || win;
|
|
}
|
|
|
|
const getElementRects = async function (data) {
|
|
const getOffsetParentFn = this.getOffsetParent || getOffsetParent;
|
|
const getDimensionsFn = this.getDimensions;
|
|
const floatingDimensions = await getDimensionsFn(data.floating);
|
|
return {
|
|
reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy),
|
|
floating: {
|
|
x: 0,
|
|
y: 0,
|
|
width: floatingDimensions.width,
|
|
height: floatingDimensions.height
|
|
}
|
|
};
|
|
};
|
|
|
|
function isRTL(element) {
|
|
return getComputedStyle$1(element).direction === 'rtl';
|
|
}
|
|
|
|
const platform = {
|
|
convertOffsetParentRelativeRectToViewportRelativeRect,
|
|
getDocumentElement,
|
|
getClippingRect,
|
|
getOffsetParent,
|
|
getElementRects,
|
|
getClientRects,
|
|
getDimensions,
|
|
getScale,
|
|
isElement: isElement$1,
|
|
isRTL
|
|
};
|
|
|
|
/**
|
|
* Modifies the placement by translating the floating element along the
|
|
* specified axes.
|
|
* A number (shorthand for `mainAxis` or distance), or an axes configuration
|
|
* object may be passed.
|
|
* @see https://floating-ui.com/docs/offset
|
|
*/
|
|
const offset = offset$1;
|
|
|
|
/**
|
|
* Optimizes the visibility of the floating element by shifting it in order to
|
|
* keep it in view when it will overflow the clipping boundary.
|
|
* @see https://floating-ui.com/docs/shift
|
|
*/
|
|
const shift = shift$1;
|
|
|
|
/**
|
|
* Optimizes the visibility of the floating element by flipping the `placement`
|
|
* in order to keep it in view when the preferred placement(s) will overflow the
|
|
* clipping boundary. Alternative to `autoPlacement`.
|
|
* @see https://floating-ui.com/docs/flip
|
|
*/
|
|
const flip = flip$1;
|
|
|
|
/**
|
|
* Provides data that allows you to change the size of the floating element —
|
|
* for instance, prevent it from overflowing the clipping boundary or match the
|
|
* width of the reference element.
|
|
* @see https://floating-ui.com/docs/size
|
|
*/
|
|
const size = size$1;
|
|
|
|
/**
|
|
* Provides data to hide the floating element in applicable situations, such as
|
|
* when it is not in the same clipping context as the reference element.
|
|
* @see https://floating-ui.com/docs/hide
|
|
*/
|
|
const hide = hide$1;
|
|
|
|
/**
|
|
* Provides data to position an inner element of the floating element so that it
|
|
* appears centered to the reference element.
|
|
* @see https://floating-ui.com/docs/arrow
|
|
*/
|
|
const arrow = arrow$1;
|
|
|
|
/**
|
|
* Built-in `limiter` that will stop `shift()` at a certain point.
|
|
*/
|
|
const limitShift = limitShift$1;
|
|
|
|
/**
|
|
* Computes the `x` and `y` coordinates that will place the floating element
|
|
* next to a given reference element.
|
|
*/
|
|
const computePosition = (reference, floating, options) => {
|
|
// This caches the expensive `getClippingElementAncestors` function so that
|
|
// multiple lifecycle resets re-use the same result. It only lives for a
|
|
// single call. If other functions become expensive, we can add them as well.
|
|
const cache = new Map();
|
|
const mergedOptions = {
|
|
platform,
|
|
...options
|
|
};
|
|
const platformWithCache = {
|
|
...mergedOptions.platform,
|
|
_c: cache
|
|
};
|
|
return computePosition$1(reference, floating, {
|
|
...mergedOptions,
|
|
platform: platformWithCache
|
|
});
|
|
};
|
|
|
|
function mount() {
|
|
lifecycle_function_unavailable("mount");
|
|
}
|
|
function unmount() {
|
|
lifecycle_function_unavailable("unmount");
|
|
}
|
|
async function tick() {
|
|
}
|
|
function box(initialValue) {
|
|
let current = initialValue;
|
|
return {
|
|
[BoxSymbol]: true,
|
|
[isWritableSymbol]: true,
|
|
get current() {
|
|
return current;
|
|
},
|
|
set current(v) {
|
|
current = v;
|
|
}
|
|
};
|
|
}
|
|
box.from = boxFrom;
|
|
box.with = boxWith;
|
|
box.flatten = boxFlatten;
|
|
box.readonly = toReadonlyBox;
|
|
box.isBox = isBox;
|
|
box.isWritableBox = isWritableBox;
|
|
const srOnlyStyles = {
|
|
position: "absolute",
|
|
width: "1px",
|
|
height: "1px",
|
|
padding: "0",
|
|
margin: "-1px",
|
|
overflow: "hidden",
|
|
clip: "rect(0, 0, 0, 0)",
|
|
whiteSpace: "nowrap",
|
|
borderWidth: "0",
|
|
transform: "translateX(-100%)"
|
|
};
|
|
const srOnlyStylesString = styleToString(srOnlyStyles);
|
|
const defaultWindow = void 0;
|
|
function getActiveElement$1(document2) {
|
|
let activeElement = document2.activeElement;
|
|
while (activeElement?.shadowRoot) {
|
|
const node = activeElement.shadowRoot.activeElement;
|
|
if (node === activeElement)
|
|
break;
|
|
else
|
|
activeElement = node;
|
|
}
|
|
return activeElement;
|
|
}
|
|
const SvelteMap = globalThis.Map;
|
|
function createSubscriber(_) {
|
|
return () => {
|
|
};
|
|
}
|
|
class ActiveElement {
|
|
#document;
|
|
#subscribe;
|
|
constructor(options = {}) {
|
|
const { window: window2 = defaultWindow, document: document2 = window2?.document } = options;
|
|
if (window2 === void 0) return;
|
|
this.#document = document2;
|
|
this.#subscribe = createSubscriber();
|
|
}
|
|
get current() {
|
|
this.#subscribe?.();
|
|
if (!this.#document) return null;
|
|
return getActiveElement$1(this.#document);
|
|
}
|
|
}
|
|
new ActiveElement();
|
|
function isFunction(value) {
|
|
return typeof value === "function";
|
|
}
|
|
class Context {
|
|
#name;
|
|
#key;
|
|
/**
|
|
* @param name The name of the context.
|
|
* This is used for generating the context key and error messages.
|
|
*/
|
|
constructor(name) {
|
|
this.#name = name;
|
|
this.#key = Symbol(name);
|
|
}
|
|
/**
|
|
* The key used to get and set the context.
|
|
*
|
|
* It is not recommended to use this value directly.
|
|
* Instead, use the methods provided by this class.
|
|
*/
|
|
get key() {
|
|
return this.#key;
|
|
}
|
|
/**
|
|
* Checks whether this has been set in the context of a parent component.
|
|
*
|
|
* Must be called during component initialisation.
|
|
*/
|
|
exists() {
|
|
return hasContext(this.#key);
|
|
}
|
|
/**
|
|
* Retrieves the context that belongs to the closest parent component.
|
|
*
|
|
* Must be called during component initialisation.
|
|
*
|
|
* @throws An error if the context does not exist.
|
|
*/
|
|
get() {
|
|
const context = getContext(this.#key);
|
|
if (context === void 0) {
|
|
throw new Error(`Context "${this.#name}" not found`);
|
|
}
|
|
return context;
|
|
}
|
|
/**
|
|
* Retrieves the context that belongs to the closest parent component,
|
|
* or the given fallback value if the context does not exist.
|
|
*
|
|
* Must be called during component initialisation.
|
|
*/
|
|
getOr(fallback) {
|
|
const context = getContext(this.#key);
|
|
if (context === void 0) {
|
|
return fallback;
|
|
}
|
|
return context;
|
|
}
|
|
/**
|
|
* Associates the given value with the current component and returns it.
|
|
*
|
|
* Must be called during component initialisation.
|
|
*/
|
|
set(context) {
|
|
return setContext(this.#key, context);
|
|
}
|
|
}
|
|
function runWatcher(sources, flush, effect, options = {}) {
|
|
const { lazy = false } = options;
|
|
}
|
|
function watch(sources, effect, options) {
|
|
runWatcher(sources, "post", effect, options);
|
|
}
|
|
function watchPre(sources, effect, options) {
|
|
runWatcher(sources, "pre", effect, options);
|
|
}
|
|
watch.pre = watchPre;
|
|
function get$1(value) {
|
|
if (isFunction(value)) {
|
|
return value();
|
|
}
|
|
return value;
|
|
}
|
|
class ElementSize {
|
|
// no need to use `$state` here since we are using createSubscriber
|
|
#size = { width: 0, height: 0 };
|
|
#observed = false;
|
|
#options;
|
|
#node;
|
|
#window;
|
|
// we use a derived here to extract the width so that if the width doesn't change we don't get a state update
|
|
// which we would get if we would just use a getter since the version of the subscriber will be changing
|
|
#width = derived(() => {
|
|
this.#subscribe()?.();
|
|
return this.getSize().width;
|
|
});
|
|
// we use a derived here to extract the height so that if the height doesn't change we don't get a state update
|
|
// which we would get if we would just use a getter since the version of the subscriber will be changing
|
|
#height = derived(() => {
|
|
this.#subscribe()?.();
|
|
return this.getSize().height;
|
|
});
|
|
// we need to use a derived here because the class will be created before the node is bound to the ref
|
|
#subscribe = derived(() => {
|
|
const node$ = get$1(this.#node);
|
|
if (!node$) return;
|
|
return createSubscriber();
|
|
});
|
|
constructor(node, options = { box: "border-box" }) {
|
|
this.#window = options.window ?? defaultWindow;
|
|
this.#options = options;
|
|
this.#node = node;
|
|
this.#size = { width: 0, height: 0 };
|
|
}
|
|
calculateSize() {
|
|
const element = get$1(this.#node);
|
|
if (!element || !this.#window) {
|
|
return;
|
|
}
|
|
const offsetWidth = element.offsetWidth;
|
|
const offsetHeight = element.offsetHeight;
|
|
if (this.#options.box === "border-box") {
|
|
return { width: offsetWidth, height: offsetHeight };
|
|
}
|
|
const style = this.#window.getComputedStyle(element);
|
|
const paddingWidth = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
|
|
const paddingHeight = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
|
|
const borderWidth = parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
|
|
const borderHeight = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
const contentWidth = offsetWidth - paddingWidth - borderWidth;
|
|
const contentHeight = offsetHeight - paddingHeight - borderHeight;
|
|
return { width: contentWidth, height: contentHeight };
|
|
}
|
|
getSize() {
|
|
return this.#observed ? this.#size : this.calculateSize() ?? this.#size;
|
|
}
|
|
get current() {
|
|
this.#subscribe()?.();
|
|
return this.getSize();
|
|
}
|
|
get width() {
|
|
return this.#width();
|
|
}
|
|
get height() {
|
|
return this.#height();
|
|
}
|
|
}
|
|
function afterSleep(ms, cb) {
|
|
return setTimeout(cb, ms);
|
|
}
|
|
function afterTick(fn) {
|
|
tick().then(fn);
|
|
}
|
|
const ELEMENT_NODE = 1;
|
|
const DOCUMENT_NODE = 9;
|
|
const DOCUMENT_FRAGMENT_NODE = 11;
|
|
function isHTMLElement$1(node) {
|
|
return isObject(node) && node.nodeType === ELEMENT_NODE && typeof node.nodeName === "string";
|
|
}
|
|
function isDocument(node) {
|
|
return isObject(node) && node.nodeType === DOCUMENT_NODE;
|
|
}
|
|
function isWindow(node) {
|
|
return isObject(node) && node.constructor?.name === "VisualViewport";
|
|
}
|
|
function isNode(node) {
|
|
return isObject(node) && node.nodeType !== void 0;
|
|
}
|
|
function isShadowRoot(node) {
|
|
return isNode(node) && node.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in node;
|
|
}
|
|
function contains(parent, child) {
|
|
if (!parent || !child)
|
|
return false;
|
|
if (!isHTMLElement$1(parent) || !isHTMLElement$1(child))
|
|
return false;
|
|
const rootNode = child.getRootNode?.();
|
|
if (parent === child)
|
|
return true;
|
|
if (parent.contains(child))
|
|
return true;
|
|
if (rootNode && isShadowRoot(rootNode)) {
|
|
let next = child;
|
|
while (next) {
|
|
if (parent === next)
|
|
return true;
|
|
next = next.parentNode || next.host;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function getDocument(node) {
|
|
if (isDocument(node))
|
|
return node;
|
|
if (isWindow(node))
|
|
return node.document;
|
|
return node?.ownerDocument ?? document;
|
|
}
|
|
function getWindow(node) {
|
|
if (isShadowRoot(node))
|
|
return getWindow(node.host);
|
|
if (isDocument(node))
|
|
return node.defaultView ?? window;
|
|
if (isHTMLElement$1(node))
|
|
return node.ownerDocument?.defaultView ?? window;
|
|
return window;
|
|
}
|
|
function getActiveElement(rootNode) {
|
|
let activeElement = rootNode.activeElement;
|
|
while (activeElement?.shadowRoot) {
|
|
const el = activeElement.shadowRoot.activeElement;
|
|
if (el === activeElement)
|
|
break;
|
|
else
|
|
activeElement = el;
|
|
}
|
|
return activeElement;
|
|
}
|
|
class DOMContext {
|
|
element;
|
|
#root = derived(() => {
|
|
if (!this.element.current) return document;
|
|
const rootNode = this.element.current.getRootNode() ?? document;
|
|
return rootNode;
|
|
});
|
|
get root() {
|
|
return this.#root();
|
|
}
|
|
set root($$value) {
|
|
return this.#root($$value);
|
|
}
|
|
constructor(element) {
|
|
if (typeof element === "function") {
|
|
this.element = boxWith(element);
|
|
} else {
|
|
this.element = element;
|
|
}
|
|
}
|
|
getDocument = () => {
|
|
return getDocument(this.root);
|
|
};
|
|
getWindow = () => {
|
|
return this.getDocument().defaultView ?? window;
|
|
};
|
|
getActiveElement = () => {
|
|
return getActiveElement(this.root);
|
|
};
|
|
isActiveElement = (node) => {
|
|
return node === this.getActiveElement();
|
|
};
|
|
getElementById(id) {
|
|
return this.root.getElementById(id);
|
|
}
|
|
querySelector = (selector) => {
|
|
if (!this.root) return null;
|
|
return this.root.querySelector(selector);
|
|
};
|
|
querySelectorAll = (selector) => {
|
|
if (!this.root) return [];
|
|
return this.root.querySelectorAll(selector);
|
|
};
|
|
setTimeout = (callback, delay) => {
|
|
return this.getWindow().setTimeout(callback, delay);
|
|
};
|
|
clearTimeout = (timeoutId) => {
|
|
return this.getWindow().clearTimeout(timeoutId);
|
|
};
|
|
}
|
|
const ARROW_DOWN = "ArrowDown";
|
|
const ARROW_LEFT = "ArrowLeft";
|
|
const ARROW_RIGHT = "ArrowRight";
|
|
const ARROW_UP = "ArrowUp";
|
|
const END = "End";
|
|
const ENTER = "Enter";
|
|
const ESCAPE = "Escape";
|
|
const HOME = "Home";
|
|
const PAGE_DOWN = "PageDown";
|
|
const PAGE_UP = "PageUp";
|
|
const SPACE = " ";
|
|
const TAB = "Tab";
|
|
function getElemDirection(elem) {
|
|
const style = window.getComputedStyle(elem);
|
|
const direction = style.getPropertyValue("direction");
|
|
return direction;
|
|
}
|
|
function getNextKey(dir = "ltr", orientation = "horizontal") {
|
|
return {
|
|
horizontal: dir === "rtl" ? ARROW_LEFT : ARROW_RIGHT,
|
|
vertical: ARROW_DOWN
|
|
}[orientation];
|
|
}
|
|
function getPrevKey(dir = "ltr", orientation = "horizontal") {
|
|
return {
|
|
horizontal: dir === "rtl" ? ARROW_RIGHT : ARROW_LEFT,
|
|
vertical: ARROW_UP
|
|
}[orientation];
|
|
}
|
|
function getDirectionalKeys(dir = "ltr", orientation = "horizontal") {
|
|
if (!["ltr", "rtl"].includes(dir))
|
|
dir = "ltr";
|
|
if (!["horizontal", "vertical"].includes(orientation))
|
|
orientation = "horizontal";
|
|
return {
|
|
nextKey: getNextKey(dir, orientation),
|
|
prevKey: getPrevKey(dir, orientation)
|
|
};
|
|
}
|
|
const isBrowser = typeof document !== "undefined";
|
|
const isIOS = getIsIOS();
|
|
function getIsIOS() {
|
|
return isBrowser && window?.navigator?.userAgent && (/iP(ad|hone|od)/.test(window.navigator.userAgent) || // The new iPad Pro Gen3 does not identify itself as iPad, but as Macintosh.
|
|
window?.navigator?.maxTouchPoints > 2 && /iPad|Macintosh/.test(window?.navigator.userAgent));
|
|
}
|
|
function isHTMLElement(element) {
|
|
return element instanceof HTMLElement;
|
|
}
|
|
function isElement(element) {
|
|
return element instanceof Element;
|
|
}
|
|
function isElementOrSVGElement(element) {
|
|
return element instanceof Element || element instanceof SVGElement;
|
|
}
|
|
function isNotNull(value) {
|
|
return value !== null;
|
|
}
|
|
function isSelectableInput(element) {
|
|
return element instanceof HTMLInputElement && "select" in element;
|
|
}
|
|
class RovingFocusGroup {
|
|
#opts;
|
|
#currentTabStopId = box(null);
|
|
constructor(opts) {
|
|
this.#opts = opts;
|
|
}
|
|
getCandidateNodes() {
|
|
return [];
|
|
}
|
|
focusFirstCandidate() {
|
|
const items = this.getCandidateNodes();
|
|
if (!items.length)
|
|
return;
|
|
items[0]?.focus();
|
|
}
|
|
handleKeydown(node, e, both = false) {
|
|
const rootNode = this.#opts.rootNode.current;
|
|
if (!rootNode || !node)
|
|
return;
|
|
const items = this.getCandidateNodes();
|
|
if (!items.length)
|
|
return;
|
|
const currentIndex = items.indexOf(node);
|
|
const dir = getElemDirection(rootNode);
|
|
const { nextKey, prevKey } = getDirectionalKeys(dir, this.#opts.orientation.current);
|
|
const loop = this.#opts.loop.current;
|
|
const keyToIndex = {
|
|
[nextKey]: currentIndex + 1,
|
|
[prevKey]: currentIndex - 1,
|
|
[HOME]: 0,
|
|
[END]: items.length - 1
|
|
};
|
|
if (both) {
|
|
const altNextKey = nextKey === ARROW_DOWN ? ARROW_RIGHT : ARROW_DOWN;
|
|
const altPrevKey = prevKey === ARROW_UP ? ARROW_LEFT : ARROW_UP;
|
|
keyToIndex[altNextKey] = currentIndex + 1;
|
|
keyToIndex[altPrevKey] = currentIndex - 1;
|
|
}
|
|
let itemIndex = keyToIndex[e.key];
|
|
if (itemIndex === void 0)
|
|
return;
|
|
e.preventDefault();
|
|
if (itemIndex < 0 && loop) {
|
|
itemIndex = items.length - 1;
|
|
} else if (itemIndex === items.length && loop) {
|
|
itemIndex = 0;
|
|
}
|
|
const itemToFocus = items[itemIndex];
|
|
if (!itemToFocus)
|
|
return;
|
|
itemToFocus.focus();
|
|
this.#currentTabStopId.current = itemToFocus.id;
|
|
this.#opts.onCandidateFocus?.(itemToFocus);
|
|
return itemToFocus;
|
|
}
|
|
getTabIndex(node) {
|
|
const items = this.getCandidateNodes();
|
|
const anyActive = this.#currentTabStopId.current !== null;
|
|
if (node && !anyActive && items[0] === node) {
|
|
this.#currentTabStopId.current = node.id;
|
|
return 0;
|
|
} else if (node?.id === this.#currentTabStopId.current) {
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
setCurrentTabStopId(id) {
|
|
this.#currentTabStopId.current = id;
|
|
}
|
|
focusCurrentTabStop() {
|
|
const currentTabStopId = this.#currentTabStopId.current;
|
|
if (!currentTabStopId)
|
|
return;
|
|
const currentTabStop = this.#opts.rootNode.current?.querySelector(`#${currentTabStopId}`);
|
|
if (!currentTabStop || !isHTMLElement(currentTabStop))
|
|
return;
|
|
currentTabStop.focus();
|
|
}
|
|
}
|
|
class AnimationsComplete {
|
|
#opts;
|
|
#currentFrame = null;
|
|
constructor(opts) {
|
|
this.#opts = opts;
|
|
}
|
|
#cleanup() {
|
|
if (!this.#currentFrame)
|
|
return;
|
|
window.cancelAnimationFrame(this.#currentFrame);
|
|
this.#currentFrame = null;
|
|
}
|
|
run(fn) {
|
|
this.#cleanup();
|
|
const node = this.#opts.ref.current;
|
|
if (!node)
|
|
return;
|
|
if (typeof node.getAnimations !== "function") {
|
|
this.#executeCallback(fn);
|
|
return;
|
|
}
|
|
this.#currentFrame = window.requestAnimationFrame(() => {
|
|
const animations = node.getAnimations();
|
|
if (animations.length === 0) {
|
|
this.#executeCallback(fn);
|
|
return;
|
|
}
|
|
Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {
|
|
this.#executeCallback(fn);
|
|
});
|
|
});
|
|
}
|
|
#executeCallback(fn) {
|
|
const execute = () => {
|
|
fn();
|
|
};
|
|
if (this.#opts.afterTick) {
|
|
afterTick(execute);
|
|
} else {
|
|
execute();
|
|
}
|
|
}
|
|
}
|
|
class PresenceManager {
|
|
#opts;
|
|
#enabled;
|
|
#afterAnimations;
|
|
#shouldRender = false;
|
|
constructor(opts) {
|
|
this.#opts = opts;
|
|
this.#shouldRender = opts.open.current;
|
|
this.#enabled = opts.enabled ?? true;
|
|
this.#afterAnimations = new AnimationsComplete({ ref: this.#opts.ref, afterTick: this.#opts.open });
|
|
watch(() => this.#opts.open.current, (isOpen) => {
|
|
if (isOpen) this.#shouldRender = true;
|
|
if (!this.#enabled) return;
|
|
this.#afterAnimations.run(() => {
|
|
if (isOpen === this.#opts.open.current) {
|
|
if (!this.#opts.open.current) {
|
|
this.#shouldRender = false;
|
|
}
|
|
this.#opts.onComplete?.();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
get shouldRender() {
|
|
return this.#shouldRender;
|
|
}
|
|
}
|
|
function noop() {
|
|
}
|
|
const BitsConfigContext = new Context("BitsConfig");
|
|
function getBitsConfig() {
|
|
const fallback = new BitsConfigState(null, {});
|
|
return BitsConfigContext.getOr(fallback).opts;
|
|
}
|
|
class BitsConfigState {
|
|
opts;
|
|
constructor(parent, opts) {
|
|
const resolveConfigOption = createConfigResolver(parent, opts);
|
|
this.opts = {
|
|
defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo),
|
|
defaultLocale: resolveConfigOption((config) => config.defaultLocale)
|
|
};
|
|
}
|
|
}
|
|
function createConfigResolver(parent, currentOpts) {
|
|
return (getter) => {
|
|
const configOption = boxWith(() => {
|
|
const value = getter(currentOpts)?.current;
|
|
if (value !== void 0)
|
|
return value;
|
|
if (parent === null)
|
|
return void 0;
|
|
return getter(parent.opts)?.current;
|
|
});
|
|
return configOption;
|
|
};
|
|
}
|
|
function createPropResolver(configOption, fallback) {
|
|
return (getProp) => {
|
|
const config = getBitsConfig();
|
|
return boxWith(() => {
|
|
const propValue = getProp();
|
|
if (propValue !== void 0)
|
|
return propValue;
|
|
const option = configOption(config).current;
|
|
if (option !== void 0)
|
|
return option;
|
|
return fallback;
|
|
});
|
|
};
|
|
}
|
|
const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, "body");
|
|
function Portal($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { to: toProp, children, disabled } = $$props;
|
|
const to = resolvePortalToProp(() => toProp);
|
|
getAllContexts();
|
|
let target = getTarget();
|
|
function getTarget() {
|
|
if (!isBrowser || disabled) return null;
|
|
let localTarget = null;
|
|
if (typeof to.current === "string") {
|
|
const target2 = document.querySelector(to.current);
|
|
localTarget = target2;
|
|
} else {
|
|
localTarget = to.current;
|
|
}
|
|
return localTarget;
|
|
}
|
|
let instance;
|
|
function unmountInstance() {
|
|
if (instance) {
|
|
unmount();
|
|
instance = null;
|
|
}
|
|
}
|
|
watch([() => target, () => disabled], ([target2, disabled2]) => {
|
|
if (!target2 || disabled2) {
|
|
unmountInstance();
|
|
return;
|
|
}
|
|
instance = mount();
|
|
return () => {
|
|
unmountInstance();
|
|
};
|
|
});
|
|
if (disabled) {
|
|
$$renderer2.push("<!--[-->");
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!---->`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
});
|
|
}
|
|
class CustomEventDispatcher {
|
|
eventName;
|
|
options;
|
|
constructor(eventName, options = { bubbles: true, cancelable: true }) {
|
|
this.eventName = eventName;
|
|
this.options = options;
|
|
}
|
|
createEvent(detail) {
|
|
return new CustomEvent(this.eventName, {
|
|
...this.options,
|
|
detail
|
|
});
|
|
}
|
|
dispatch(element, detail) {
|
|
const event = this.createEvent(detail);
|
|
element.dispatchEvent(event);
|
|
return event;
|
|
}
|
|
listen(element, callback, options) {
|
|
const handler = (event) => {
|
|
callback(event);
|
|
};
|
|
return on(element, this.eventName, handler, options);
|
|
}
|
|
}
|
|
function debounce(fn, wait = 500) {
|
|
let timeout = null;
|
|
const debounced = (...args) => {
|
|
if (timeout !== null) {
|
|
clearTimeout(timeout);
|
|
}
|
|
timeout = setTimeout(() => {
|
|
fn(...args);
|
|
}, wait);
|
|
};
|
|
debounced.destroy = () => {
|
|
if (timeout !== null) {
|
|
clearTimeout(timeout);
|
|
timeout = null;
|
|
}
|
|
};
|
|
return debounced;
|
|
}
|
|
function isOrContainsTarget(node, target) {
|
|
return node === target || node.contains(target);
|
|
}
|
|
function getOwnerDocument(el) {
|
|
return el?.ownerDocument ?? document;
|
|
}
|
|
function isClickTrulyOutside(event, contentNode) {
|
|
const { clientX, clientY } = event;
|
|
const rect = contentNode.getBoundingClientRect();
|
|
return clientX < rect.left || clientX > rect.right || clientY < rect.top || clientY > rect.bottom;
|
|
}
|
|
const FIRST_KEYS = [ARROW_DOWN, PAGE_UP, HOME];
|
|
const LAST_KEYS = [ARROW_UP, PAGE_DOWN, END];
|
|
const FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS];
|
|
function focus(element, { select = false } = {}) {
|
|
if (!element || !element.focus)
|
|
return;
|
|
const doc = getDocument(element);
|
|
if (doc.activeElement === element)
|
|
return;
|
|
const previouslyFocusedElement = doc.activeElement;
|
|
element.focus({ preventScroll: true });
|
|
if (element !== previouslyFocusedElement && isSelectableInput(element) && select) {
|
|
element.select();
|
|
}
|
|
}
|
|
function focusFirst(candidates, { select = false } = {}, getActiveElement2) {
|
|
const previouslyFocusedElement = getActiveElement2();
|
|
for (const candidate of candidates) {
|
|
focus(candidate, { select });
|
|
if (getActiveElement2() !== previouslyFocusedElement)
|
|
return true;
|
|
}
|
|
}
|
|
let isUsingKeyboard = false;
|
|
class IsUsingKeyboard {
|
|
static _refs = 0;
|
|
// Reference counting to avoid multiple listeners.
|
|
static _cleanup;
|
|
constructor() {
|
|
}
|
|
get current() {
|
|
return isUsingKeyboard;
|
|
}
|
|
set current(value) {
|
|
isUsingKeyboard = value;
|
|
}
|
|
}
|
|
function getTabbableOptions() {
|
|
return {
|
|
getShadowRoot: true,
|
|
displayCheck: (
|
|
// JSDOM does not support the `tabbable` library. To solve this we can
|
|
// check if `ResizeObserver` is a real function (not polyfilled), which
|
|
// determines if the current environment is JSDOM-like.
|
|
typeof ResizeObserver === "function" && ResizeObserver.toString().includes("[native code]") ? "full" : "none"
|
|
)
|
|
};
|
|
}
|
|
function getTabbableFrom(currentNode, direction) {
|
|
if (!isTabbable(currentNode, getTabbableOptions())) {
|
|
return getTabbableFromFocusable(currentNode, direction);
|
|
}
|
|
const doc = getDocument(currentNode);
|
|
const allTabbable = tabbable(doc.body, getTabbableOptions());
|
|
if (direction === "prev")
|
|
allTabbable.reverse();
|
|
const activeIndex = allTabbable.indexOf(currentNode);
|
|
if (activeIndex === -1)
|
|
return doc.body;
|
|
const nextTabbableElements = allTabbable.slice(activeIndex + 1);
|
|
return nextTabbableElements[0];
|
|
}
|
|
function getTabbableFromFocusable(currentNode, direction) {
|
|
const doc = getDocument(currentNode);
|
|
if (!isFocusable(currentNode, getTabbableOptions()))
|
|
return doc.body;
|
|
const allFocusable = focusable(doc.body, getTabbableOptions());
|
|
if (direction === "prev")
|
|
allFocusable.reverse();
|
|
const activeIndex = allFocusable.indexOf(currentNode);
|
|
if (activeIndex === -1)
|
|
return doc.body;
|
|
const nextFocusableElements = allFocusable.slice(activeIndex + 1);
|
|
return nextFocusableElements.find((node) => isTabbable(node, getTabbableOptions())) ?? doc.body;
|
|
}
|
|
function getNextMatch(values, search, currentMatch) {
|
|
const lowerSearch = search.toLowerCase();
|
|
if (lowerSearch.endsWith(" ")) {
|
|
const searchWithoutSpace = lowerSearch.slice(0, -1);
|
|
const matchesWithoutSpace = values.filter((value) => value.toLowerCase().startsWith(searchWithoutSpace));
|
|
if (matchesWithoutSpace.length <= 1) {
|
|
return getNextMatch(values, searchWithoutSpace, currentMatch);
|
|
}
|
|
const currentMatchLowercase = currentMatch?.toLowerCase();
|
|
if (currentMatchLowercase && currentMatchLowercase.startsWith(searchWithoutSpace) && currentMatchLowercase.charAt(searchWithoutSpace.length) === " " && search.trim() === searchWithoutSpace) {
|
|
return currentMatch;
|
|
}
|
|
const spacedMatches = values.filter((value) => value.toLowerCase().startsWith(lowerSearch));
|
|
if (spacedMatches.length > 0) {
|
|
const currentMatchIndex2 = currentMatch ? values.indexOf(currentMatch) : -1;
|
|
let wrappedMatches = wrapArray(spacedMatches, Math.max(currentMatchIndex2, 0));
|
|
const nextMatch2 = wrappedMatches.find((match) => match !== currentMatch);
|
|
return nextMatch2 || currentMatch;
|
|
}
|
|
}
|
|
const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
|
|
const normalizedSearch = isRepeated ? search[0] : search;
|
|
const normalizedLowerSearch = normalizedSearch.toLowerCase();
|
|
const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
|
|
let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
|
|
const excludeCurrentMatch = normalizedSearch.length === 1;
|
|
if (excludeCurrentMatch)
|
|
wrappedValues = wrappedValues.filter((v) => v !== currentMatch);
|
|
const nextMatch = wrappedValues.find((value) => value?.toLowerCase().startsWith(normalizedLowerSearch));
|
|
return nextMatch !== currentMatch ? nextMatch : void 0;
|
|
}
|
|
function wrapArray(array, startIndex) {
|
|
return array.map((_, index) => array[(startIndex + index) % array.length]);
|
|
}
|
|
const defaultOptions = { afterMs: 1e4, onChange: noop };
|
|
function boxAutoReset(defaultValue, options) {
|
|
const { afterMs, onChange, getWindow: getWindow2 } = { ...defaultOptions, ...options };
|
|
let timeout = null;
|
|
let value = defaultValue;
|
|
function resetAfter() {
|
|
return getWindow2().setTimeout(
|
|
() => {
|
|
value = defaultValue;
|
|
onChange?.(defaultValue);
|
|
},
|
|
afterMs
|
|
);
|
|
}
|
|
return boxWith(() => value, (v) => {
|
|
value = v;
|
|
onChange?.(v);
|
|
if (timeout) getWindow2().clearTimeout(timeout);
|
|
timeout = resetAfter();
|
|
});
|
|
}
|
|
class DOMTypeahead {
|
|
#opts;
|
|
#search;
|
|
#onMatch = derived(() => {
|
|
if (this.#opts.onMatch) return this.#opts.onMatch;
|
|
return (node) => node.focus();
|
|
});
|
|
#getCurrentItem = derived(() => {
|
|
if (this.#opts.getCurrentItem) return this.#opts.getCurrentItem;
|
|
return this.#opts.getActiveElement;
|
|
});
|
|
constructor(opts) {
|
|
this.#opts = opts;
|
|
this.#search = boxAutoReset("", { afterMs: 1e3, getWindow: opts.getWindow });
|
|
this.handleTypeaheadSearch = this.handleTypeaheadSearch.bind(this);
|
|
this.resetTypeahead = this.resetTypeahead.bind(this);
|
|
}
|
|
handleTypeaheadSearch(key, candidates) {
|
|
if (!candidates.length) return;
|
|
this.#search.current = this.#search.current + key;
|
|
const currentItem = this.#getCurrentItem()();
|
|
const currentMatch = candidates.find((item) => item === currentItem)?.textContent?.trim() ?? "";
|
|
const values = candidates.map((item) => item.textContent?.trim() ?? "");
|
|
const nextMatch = getNextMatch(values, this.#search.current, currentMatch);
|
|
const newItem = candidates.find((item) => item.textContent?.trim() === nextMatch);
|
|
if (newItem) this.#onMatch()(newItem);
|
|
return newItem;
|
|
}
|
|
resetTypeahead() {
|
|
this.#search.current = "";
|
|
}
|
|
get search() {
|
|
return this.#search.current;
|
|
}
|
|
}
|
|
class GraceArea {
|
|
#opts;
|
|
#enabled;
|
|
#isPointerInTransit;
|
|
#pointerGraceArea = null;
|
|
constructor(opts) {
|
|
this.#opts = opts;
|
|
this.#enabled = derived(() => this.#opts.enabled());
|
|
this.#isPointerInTransit = boxAutoReset(false, {
|
|
afterMs: opts.transitTimeout ?? 300,
|
|
onChange: (value) => {
|
|
if (!this.#enabled()) return;
|
|
this.#opts.setIsPointerInTransit?.(value);
|
|
},
|
|
getWindow: () => getWindow(this.#opts.triggerNode())
|
|
});
|
|
watch([opts.triggerNode, opts.contentNode, opts.enabled], ([triggerNode, contentNode, enabled]) => {
|
|
if (!triggerNode || !contentNode || !enabled) return;
|
|
const handleTriggerLeave = (e) => {
|
|
this.#createGraceArea(e, contentNode);
|
|
};
|
|
const handleContentLeave = (e) => {
|
|
this.#createGraceArea(e, triggerNode);
|
|
};
|
|
return executeCallbacks(on(triggerNode, "pointerleave", handleTriggerLeave), on(contentNode, "pointerleave", handleContentLeave));
|
|
});
|
|
watch(() => this.#pointerGraceArea, () => {
|
|
const handleTrackPointerGrace = (e) => {
|
|
if (!this.#pointerGraceArea) return;
|
|
const target = e.target;
|
|
if (!isElement(target)) return;
|
|
const pointerPosition = { x: e.clientX, y: e.clientY };
|
|
const hasEnteredTarget = opts.triggerNode()?.contains(target) || opts.contentNode()?.contains(target);
|
|
const isPointerOutsideGraceArea = !isPointInPolygon(pointerPosition, this.#pointerGraceArea);
|
|
if (hasEnteredTarget) {
|
|
this.#removeGraceArea();
|
|
} else if (isPointerOutsideGraceArea) {
|
|
this.#removeGraceArea();
|
|
opts.onPointerExit();
|
|
}
|
|
};
|
|
const doc = getDocument(opts.triggerNode() ?? opts.contentNode());
|
|
if (!doc) return;
|
|
return on(doc, "pointermove", handleTrackPointerGrace);
|
|
});
|
|
}
|
|
#removeGraceArea() {
|
|
this.#pointerGraceArea = null;
|
|
this.#isPointerInTransit.current = false;
|
|
}
|
|
#createGraceArea(e, hoverTarget) {
|
|
const currentTarget = e.currentTarget;
|
|
if (!isHTMLElement(currentTarget)) return;
|
|
const exitPoint = { x: e.clientX, y: e.clientY };
|
|
const exitSide = getExitSideFromRect(exitPoint, currentTarget.getBoundingClientRect());
|
|
const paddedExitPoints = getPaddedExitPoints(exitPoint, exitSide);
|
|
const hoverTargetPoints = getPointsFromRect(hoverTarget.getBoundingClientRect());
|
|
const graceArea = getHull([...paddedExitPoints, ...hoverTargetPoints]);
|
|
this.#pointerGraceArea = graceArea;
|
|
this.#isPointerInTransit.current = true;
|
|
}
|
|
}
|
|
function getExitSideFromRect(point, rect) {
|
|
const top = Math.abs(rect.top - point.y);
|
|
const bottom = Math.abs(rect.bottom - point.y);
|
|
const right = Math.abs(rect.right - point.x);
|
|
const left = Math.abs(rect.left - point.x);
|
|
switch (Math.min(top, bottom, right, left)) {
|
|
case left:
|
|
return "left";
|
|
case right:
|
|
return "right";
|
|
case top:
|
|
return "top";
|
|
case bottom:
|
|
return "bottom";
|
|
default:
|
|
throw new Error("unreachable");
|
|
}
|
|
}
|
|
function getPaddedExitPoints(exitPoint, exitSide, padding = 5) {
|
|
const tipPadding = padding * 1.5;
|
|
switch (exitSide) {
|
|
case "top":
|
|
return [
|
|
{ x: exitPoint.x - padding, y: exitPoint.y + padding },
|
|
{ x: exitPoint.x, y: exitPoint.y - tipPadding },
|
|
{ x: exitPoint.x + padding, y: exitPoint.y + padding }
|
|
];
|
|
case "bottom":
|
|
return [
|
|
{ x: exitPoint.x - padding, y: exitPoint.y - padding },
|
|
{ x: exitPoint.x, y: exitPoint.y + tipPadding },
|
|
{ x: exitPoint.x + padding, y: exitPoint.y - padding }
|
|
];
|
|
case "left":
|
|
return [
|
|
{ x: exitPoint.x + padding, y: exitPoint.y - padding },
|
|
{ x: exitPoint.x - tipPadding, y: exitPoint.y },
|
|
{ x: exitPoint.x + padding, y: exitPoint.y + padding }
|
|
];
|
|
case "right":
|
|
return [
|
|
{ x: exitPoint.x - padding, y: exitPoint.y - padding },
|
|
{ x: exitPoint.x + tipPadding, y: exitPoint.y },
|
|
{ x: exitPoint.x - padding, y: exitPoint.y + padding }
|
|
];
|
|
}
|
|
}
|
|
function getPointsFromRect(rect) {
|
|
const { top, right, bottom, left } = rect;
|
|
return [
|
|
{ x: left, y: top },
|
|
{ x: right, y: top },
|
|
{ x: right, y: bottom },
|
|
{ x: left, y: bottom }
|
|
];
|
|
}
|
|
function isPointInPolygon(point, polygon) {
|
|
const { x, y } = point;
|
|
let inside = false;
|
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
const xi = polygon[i].x;
|
|
const yi = polygon[i].y;
|
|
const xj = polygon[j].x;
|
|
const yj = polygon[j].y;
|
|
const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
|
|
if (intersect) inside = !inside;
|
|
}
|
|
return inside;
|
|
}
|
|
function getHull(points) {
|
|
const newPoints = points.slice();
|
|
newPoints.sort((a, b) => {
|
|
if (a.x < b.x) return -1;
|
|
else if (a.x > b.x) return 1;
|
|
else if (a.y < b.y) return -1;
|
|
else if (a.y > b.y) return 1;
|
|
else return 0;
|
|
});
|
|
return getHullPresorted(newPoints);
|
|
}
|
|
function getHullPresorted(points) {
|
|
if (points.length <= 1) return points.slice();
|
|
const upperHull = [];
|
|
for (let i = 0; i < points.length; i++) {
|
|
const p = points[i];
|
|
while (upperHull.length >= 2) {
|
|
const q = upperHull[upperHull.length - 1];
|
|
const r = upperHull[upperHull.length - 2];
|
|
if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop();
|
|
else break;
|
|
}
|
|
upperHull.push(p);
|
|
}
|
|
upperHull.pop();
|
|
const lowerHull = [];
|
|
for (let i = points.length - 1; i >= 0; i--) {
|
|
const p = points[i];
|
|
while (lowerHull.length >= 2) {
|
|
const q = lowerHull[lowerHull.length - 1];
|
|
const r = lowerHull[lowerHull.length - 2];
|
|
if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop();
|
|
else break;
|
|
}
|
|
lowerHull.push(p);
|
|
}
|
|
lowerHull.pop();
|
|
if (upperHull.length === 1 && lowerHull.length === 1 && upperHull[0].x === lowerHull[0].x && upperHull[0].y === lowerHull[0].y) return upperHull;
|
|
else return upperHull.concat(lowerHull);
|
|
}
|
|
const CONTEXT_MENU_TRIGGER_ATTR = "data-context-menu-trigger";
|
|
const CONTEXT_MENU_CONTENT_ATTR = "data-context-menu-content";
|
|
const MenuRootContext = new Context("Menu.Root");
|
|
const MenuMenuContext = new Context("Menu.Root | Menu.Sub");
|
|
const MenuContentContext = new Context("Menu.Content");
|
|
const MenuOpenEvent = new CustomEventDispatcher("bitsmenuopen", { bubbles: false, cancelable: true });
|
|
const menuAttrs = createBitsAttrs({
|
|
component: "menu",
|
|
parts: [
|
|
"trigger",
|
|
"content",
|
|
"sub-trigger",
|
|
"item",
|
|
"group",
|
|
"group-heading",
|
|
"checkbox-group",
|
|
"checkbox-item",
|
|
"radio-group",
|
|
"radio-item",
|
|
"separator",
|
|
"sub-content",
|
|
"arrow"
|
|
]
|
|
});
|
|
class MenuRootState {
|
|
static create(opts) {
|
|
const root = new MenuRootState(opts);
|
|
return MenuRootContext.set(root);
|
|
}
|
|
opts;
|
|
isUsingKeyboard = new IsUsingKeyboard();
|
|
ignoreCloseAutoFocus = false;
|
|
isPointerInTransit = false;
|
|
constructor(opts) {
|
|
this.opts = opts;
|
|
}
|
|
getBitsAttr = (part) => {
|
|
return menuAttrs.getAttr(part, this.opts.variant.current);
|
|
};
|
|
}
|
|
class MenuMenuState {
|
|
static create(opts, root) {
|
|
return MenuMenuContext.set(new MenuMenuState(opts, root, null));
|
|
}
|
|
opts;
|
|
root;
|
|
parentMenu;
|
|
contentId = boxWith(() => "");
|
|
contentNode = null;
|
|
contentPresence;
|
|
triggerNode = null;
|
|
constructor(opts, root, parentMenu) {
|
|
this.opts = opts;
|
|
this.root = root;
|
|
this.parentMenu = parentMenu;
|
|
this.contentPresence = new PresenceManager({
|
|
ref: boxWith(() => this.contentNode),
|
|
open: this.opts.open,
|
|
onComplete: () => {
|
|
this.opts.onOpenChangeComplete.current(this.opts.open.current);
|
|
}
|
|
});
|
|
if (parentMenu) {
|
|
watch(() => parentMenu.opts.open.current, () => {
|
|
if (parentMenu.opts.open.current) return;
|
|
this.opts.open.current = false;
|
|
});
|
|
}
|
|
}
|
|
toggleOpen() {
|
|
this.opts.open.current = !this.opts.open.current;
|
|
}
|
|
onOpen() {
|
|
this.opts.open.current = true;
|
|
}
|
|
onClose() {
|
|
this.opts.open.current = false;
|
|
}
|
|
}
|
|
class MenuContentState {
|
|
static create(opts) {
|
|
return MenuContentContext.set(new MenuContentState(opts, MenuMenuContext.get()));
|
|
}
|
|
opts;
|
|
parentMenu;
|
|
rovingFocusGroup;
|
|
domContext;
|
|
attachment;
|
|
search = "";
|
|
#timer = 0;
|
|
#handleTypeaheadSearch;
|
|
mounted = false;
|
|
#isSub;
|
|
constructor(opts, parentMenu) {
|
|
this.opts = opts;
|
|
this.parentMenu = parentMenu;
|
|
this.domContext = new DOMContext(opts.ref);
|
|
this.attachment = attachRef(this.opts.ref, (v) => {
|
|
if (this.parentMenu.contentNode !== v) {
|
|
this.parentMenu.contentNode = v;
|
|
}
|
|
});
|
|
parentMenu.contentId = opts.id;
|
|
this.#isSub = opts.isSub ?? false;
|
|
this.onkeydown = this.onkeydown.bind(this);
|
|
this.onblur = this.onblur.bind(this);
|
|
this.onfocus = this.onfocus.bind(this);
|
|
this.handleInteractOutside = this.handleInteractOutside.bind(this);
|
|
new GraceArea({
|
|
contentNode: () => this.parentMenu.contentNode,
|
|
triggerNode: () => this.parentMenu.triggerNode,
|
|
enabled: () => this.parentMenu.opts.open.current && Boolean(this.parentMenu.triggerNode?.hasAttribute(this.parentMenu.root.getBitsAttr("sub-trigger"))),
|
|
onPointerExit: () => {
|
|
this.parentMenu.opts.open.current = false;
|
|
},
|
|
setIsPointerInTransit: (value) => {
|
|
this.parentMenu.root.isPointerInTransit = value;
|
|
}
|
|
});
|
|
this.#handleTypeaheadSearch = new DOMTypeahead({
|
|
getActiveElement: () => this.domContext.getActiveElement(),
|
|
getWindow: () => this.domContext.getWindow()
|
|
}).handleTypeaheadSearch;
|
|
this.rovingFocusGroup = new RovingFocusGroup({
|
|
rootNode: boxWith(() => this.parentMenu.contentNode),
|
|
candidateAttr: this.parentMenu.root.getBitsAttr("item"),
|
|
loop: this.opts.loop,
|
|
orientation: boxWith(() => "vertical")
|
|
});
|
|
watch(() => this.parentMenu.contentNode, (contentNode) => {
|
|
if (!contentNode) return;
|
|
const handler = () => {
|
|
afterTick(() => {
|
|
if (!this.parentMenu.root.isUsingKeyboard.current) return;
|
|
this.rovingFocusGroup.focusFirstCandidate();
|
|
});
|
|
};
|
|
return MenuOpenEvent.listen(contentNode, handler);
|
|
});
|
|
}
|
|
#getCandidateNodes() {
|
|
const node = this.parentMenu.contentNode;
|
|
if (!node) return [];
|
|
const candidates = Array.from(node.querySelectorAll(`[${this.parentMenu.root.getBitsAttr("item")}]:not([data-disabled])`));
|
|
return candidates;
|
|
}
|
|
#isPointerMovingToSubmenu() {
|
|
return this.parentMenu.root.isPointerInTransit;
|
|
}
|
|
onCloseAutoFocus = (e) => {
|
|
this.opts.onCloseAutoFocus.current?.(e);
|
|
if (e.defaultPrevented || this.#isSub) return;
|
|
if (this.parentMenu.triggerNode && isTabbable(this.parentMenu.triggerNode)) {
|
|
e.preventDefault();
|
|
this.parentMenu.triggerNode.focus();
|
|
}
|
|
};
|
|
handleTabKeyDown(e) {
|
|
let rootMenu = this.parentMenu;
|
|
while (rootMenu.parentMenu !== null) {
|
|
rootMenu = rootMenu.parentMenu;
|
|
}
|
|
if (!rootMenu.triggerNode) return;
|
|
e.preventDefault();
|
|
const nodeToFocus = getTabbableFrom(rootMenu.triggerNode, e.shiftKey ? "prev" : "next");
|
|
if (nodeToFocus) {
|
|
this.parentMenu.root.ignoreCloseAutoFocus = true;
|
|
rootMenu.onClose();
|
|
afterTick(() => {
|
|
nodeToFocus.focus();
|
|
afterTick(() => {
|
|
this.parentMenu.root.ignoreCloseAutoFocus = false;
|
|
});
|
|
});
|
|
} else {
|
|
this.domContext.getDocument().body.focus();
|
|
}
|
|
}
|
|
onkeydown(e) {
|
|
if (e.defaultPrevented) return;
|
|
if (e.key === TAB) {
|
|
this.handleTabKeyDown(e);
|
|
return;
|
|
}
|
|
const target = e.target;
|
|
const currentTarget = e.currentTarget;
|
|
if (!isHTMLElement(target) || !isHTMLElement(currentTarget)) return;
|
|
const isKeydownInside = target.closest(`[${this.parentMenu.root.getBitsAttr("content")}]`)?.id === this.parentMenu.contentId.current;
|
|
const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;
|
|
const isCharacterKey = e.key.length === 1;
|
|
const kbdFocusedEl = this.rovingFocusGroup.handleKeydown(target, e);
|
|
if (kbdFocusedEl) return;
|
|
if (e.code === "Space") return;
|
|
const candidateNodes = this.#getCandidateNodes();
|
|
if (isKeydownInside) {
|
|
if (!isModifierKey && isCharacterKey) {
|
|
this.#handleTypeaheadSearch(e.key, candidateNodes);
|
|
}
|
|
}
|
|
if (e.target?.id !== this.parentMenu.contentId.current) return;
|
|
if (!FIRST_LAST_KEYS.includes(e.key)) return;
|
|
e.preventDefault();
|
|
if (LAST_KEYS.includes(e.key)) {
|
|
candidateNodes.reverse();
|
|
}
|
|
focusFirst(candidateNodes, { select: false }, () => this.domContext.getActiveElement());
|
|
}
|
|
onblur(e) {
|
|
if (!isElement(e.currentTarget)) return;
|
|
if (!isElement(e.target)) return;
|
|
if (!e.currentTarget.contains?.(e.target)) {
|
|
this.domContext.getWindow().clearTimeout(this.#timer);
|
|
this.search = "";
|
|
}
|
|
}
|
|
onfocus(_) {
|
|
if (!this.parentMenu.root.isUsingKeyboard.current) return;
|
|
afterTick(() => this.rovingFocusGroup.focusFirstCandidate());
|
|
}
|
|
onItemEnter() {
|
|
return this.#isPointerMovingToSubmenu();
|
|
}
|
|
onItemLeave(e) {
|
|
if (e.currentTarget.hasAttribute(this.parentMenu.root.getBitsAttr("sub-trigger"))) return;
|
|
if (this.#isPointerMovingToSubmenu() || this.parentMenu.root.isUsingKeyboard.current) return;
|
|
const contentNode = this.parentMenu.contentNode;
|
|
contentNode?.focus();
|
|
this.rovingFocusGroup.setCurrentTabStopId("");
|
|
}
|
|
onTriggerLeave() {
|
|
if (this.#isPointerMovingToSubmenu()) return true;
|
|
return false;
|
|
}
|
|
handleInteractOutside(e) {
|
|
if (!isElementOrSVGElement(e.target)) return;
|
|
const triggerId = this.parentMenu.triggerNode?.id;
|
|
if (e.target.id === triggerId) {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
if (e.target.closest(`#${triggerId}`)) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
get shouldRender() {
|
|
return this.parentMenu.contentPresence.shouldRender;
|
|
}
|
|
#snippetProps = derived(() => ({ open: this.parentMenu.opts.open.current }));
|
|
get snippetProps() {
|
|
return this.#snippetProps();
|
|
}
|
|
set snippetProps($$value) {
|
|
return this.#snippetProps($$value);
|
|
}
|
|
#props = derived(() => ({
|
|
id: this.opts.id.current,
|
|
role: "menu",
|
|
"aria-orientation": "vertical",
|
|
[this.parentMenu.root.getBitsAttr("content")]: "",
|
|
"data-state": getDataOpenClosed(this.parentMenu.opts.open.current),
|
|
onkeydown: this.onkeydown,
|
|
onblur: this.onblur,
|
|
onfocus: this.onfocus,
|
|
dir: this.parentMenu.root.opts.dir.current,
|
|
style: { pointerEvents: "auto", contain: "layout style" },
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
popperProps = { onCloseAutoFocus: (e) => this.onCloseAutoFocus(e) };
|
|
}
|
|
class MenuSeparatorState {
|
|
static create(opts) {
|
|
return new MenuSeparatorState(opts, MenuRootContext.get());
|
|
}
|
|
opts;
|
|
root;
|
|
attachment;
|
|
constructor(opts, root) {
|
|
this.opts = opts;
|
|
this.root = root;
|
|
this.attachment = attachRef(this.opts.ref);
|
|
}
|
|
#props = derived(() => ({
|
|
id: this.opts.id.current,
|
|
role: "group",
|
|
[this.root.getBitsAttr("separator")]: "",
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
class DropdownMenuTriggerState {
|
|
static create(opts) {
|
|
return new DropdownMenuTriggerState(opts, MenuMenuContext.get());
|
|
}
|
|
opts;
|
|
parentMenu;
|
|
attachment;
|
|
constructor(opts, parentMenu) {
|
|
this.opts = opts;
|
|
this.parentMenu = parentMenu;
|
|
this.attachment = attachRef(this.opts.ref, (v) => this.parentMenu.triggerNode = v);
|
|
}
|
|
onclick = (e) => {
|
|
if (this.opts.disabled.current || e.detail !== 0) return;
|
|
this.parentMenu.toggleOpen();
|
|
e.preventDefault();
|
|
};
|
|
onpointerdown = (e) => {
|
|
if (this.opts.disabled.current) return;
|
|
if (e.pointerType === "touch") return e.preventDefault();
|
|
if (e.button === 0 && e.ctrlKey === false) {
|
|
this.parentMenu.toggleOpen();
|
|
if (!this.parentMenu.opts.open.current) e.preventDefault();
|
|
}
|
|
};
|
|
onpointerup = (e) => {
|
|
if (this.opts.disabled.current) return;
|
|
if (e.pointerType === "touch") {
|
|
e.preventDefault();
|
|
this.parentMenu.toggleOpen();
|
|
}
|
|
};
|
|
onkeydown = (e) => {
|
|
if (this.opts.disabled.current) return;
|
|
if (e.key === SPACE || e.key === ENTER) {
|
|
this.parentMenu.toggleOpen();
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
if (e.key === ARROW_DOWN) {
|
|
this.parentMenu.onOpen();
|
|
e.preventDefault();
|
|
}
|
|
};
|
|
#ariaControls = derived(() => {
|
|
if (this.parentMenu.opts.open.current && this.parentMenu.contentId.current) return this.parentMenu.contentId.current;
|
|
return void 0;
|
|
});
|
|
#props = derived(() => ({
|
|
id: this.opts.id.current,
|
|
disabled: this.opts.disabled.current,
|
|
"aria-haspopup": "menu",
|
|
"aria-expanded": boolToStr(this.parentMenu.opts.open.current),
|
|
"aria-controls": this.#ariaControls(),
|
|
"data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),
|
|
"data-state": getDataOpenClosed(this.parentMenu.opts.open.current),
|
|
[this.parentMenu.root.getBitsAttr("trigger")]: "",
|
|
//
|
|
onclick: this.onclick,
|
|
onpointerdown: this.onpointerdown,
|
|
onpointerup: this.onpointerup,
|
|
onkeydown: this.onkeydown,
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
globalThis.bitsDismissableLayers ??= /* @__PURE__ */ new Map();
|
|
class DismissibleLayerState {
|
|
static create(opts) {
|
|
return new DismissibleLayerState(opts);
|
|
}
|
|
opts;
|
|
#interactOutsideProp;
|
|
#behaviorType;
|
|
#interceptedEvents = { pointerdown: false };
|
|
#isResponsibleLayer = false;
|
|
#isFocusInsideDOMTree = false;
|
|
#documentObj = void 0;
|
|
#onFocusOutside;
|
|
#unsubClickListener = noop;
|
|
constructor(opts) {
|
|
this.opts = opts;
|
|
this.#behaviorType = opts.interactOutsideBehavior;
|
|
this.#interactOutsideProp = opts.onInteractOutside;
|
|
this.#onFocusOutside = opts.onFocusOutside;
|
|
let unsubEvents = noop;
|
|
const cleanup = () => {
|
|
this.#resetState();
|
|
globalThis.bitsDismissableLayers.delete(this);
|
|
this.#handleInteractOutside.destroy();
|
|
unsubEvents();
|
|
};
|
|
watch([() => this.opts.enabled.current, () => this.opts.ref.current], () => {
|
|
if (!this.opts.enabled.current || !this.opts.ref.current) return;
|
|
afterSleep(1, () => {
|
|
if (!this.opts.ref.current) return;
|
|
globalThis.bitsDismissableLayers.set(this, this.#behaviorType);
|
|
unsubEvents();
|
|
unsubEvents = this.#addEventListeners();
|
|
});
|
|
return cleanup;
|
|
});
|
|
}
|
|
#handleFocus = (event) => {
|
|
if (event.defaultPrevented) return;
|
|
if (!this.opts.ref.current) return;
|
|
afterTick(() => {
|
|
if (!this.opts.ref.current || this.#isTargetWithinLayer(event.target)) return;
|
|
if (event.target && !this.#isFocusInsideDOMTree) {
|
|
this.#onFocusOutside.current?.(event);
|
|
}
|
|
});
|
|
};
|
|
#addEventListeners() {
|
|
return executeCallbacks(
|
|
/**
|
|
* CAPTURE INTERACTION START
|
|
* mark interaction-start event as intercepted.
|
|
* mark responsible layer during interaction start
|
|
* to avoid checking if is responsible layer during interaction end
|
|
* when a new floating element may have been opened.
|
|
*/
|
|
on(this.#documentObj, "pointerdown", executeCallbacks(this.#markInterceptedEvent, this.#markResponsibleLayer), { capture: true }),
|
|
/**
|
|
* BUBBLE INTERACTION START
|
|
* Mark interaction-start event as non-intercepted. Debounce `onInteractOutsideStart`
|
|
* to avoid prematurely checking if other events were intercepted.
|
|
*/
|
|
on(this.#documentObj, "pointerdown", executeCallbacks(this.#markNonInterceptedEvent, this.#handleInteractOutside)),
|
|
/**
|
|
* HANDLE FOCUS OUTSIDE
|
|
*/
|
|
on(this.#documentObj, "focusin", this.#handleFocus)
|
|
);
|
|
}
|
|
#handleDismiss = (e) => {
|
|
let event = e;
|
|
if (event.defaultPrevented) {
|
|
event = createWrappedEvent(e);
|
|
}
|
|
this.#interactOutsideProp.current(e);
|
|
};
|
|
#handleInteractOutside = debounce(
|
|
(e) => {
|
|
if (!this.opts.ref.current) {
|
|
this.#unsubClickListener();
|
|
return;
|
|
}
|
|
const isEventValid = this.opts.isValidEvent.current(e, this.opts.ref.current) || isValidEvent(e, this.opts.ref.current);
|
|
if (!this.#isResponsibleLayer || this.#isAnyEventIntercepted() || !isEventValid) {
|
|
this.#unsubClickListener();
|
|
return;
|
|
}
|
|
let event = e;
|
|
if (event.defaultPrevented) {
|
|
event = createWrappedEvent(event);
|
|
}
|
|
if (this.#behaviorType.current !== "close" && this.#behaviorType.current !== "defer-otherwise-close") {
|
|
this.#unsubClickListener();
|
|
return;
|
|
}
|
|
if (e.pointerType === "touch") {
|
|
this.#unsubClickListener();
|
|
this.#unsubClickListener = on(this.#documentObj, "click", this.#handleDismiss, { once: true });
|
|
} else {
|
|
this.#interactOutsideProp.current(event);
|
|
}
|
|
},
|
|
10
|
|
);
|
|
#markInterceptedEvent = (e) => {
|
|
this.#interceptedEvents[e.type] = true;
|
|
};
|
|
#markNonInterceptedEvent = (e) => {
|
|
this.#interceptedEvents[e.type] = false;
|
|
};
|
|
#markResponsibleLayer = () => {
|
|
if (!this.opts.ref.current) return;
|
|
this.#isResponsibleLayer = isResponsibleLayer(this.opts.ref.current);
|
|
};
|
|
#isTargetWithinLayer = (target) => {
|
|
if (!this.opts.ref.current) return false;
|
|
return isOrContainsTarget(this.opts.ref.current, target);
|
|
};
|
|
#resetState = debounce(
|
|
() => {
|
|
for (const eventType in this.#interceptedEvents) {
|
|
this.#interceptedEvents[eventType] = false;
|
|
}
|
|
this.#isResponsibleLayer = false;
|
|
},
|
|
20
|
|
);
|
|
#isAnyEventIntercepted() {
|
|
const i = Object.values(this.#interceptedEvents).some(Boolean);
|
|
return i;
|
|
}
|
|
#onfocuscapture = () => {
|
|
this.#isFocusInsideDOMTree = true;
|
|
};
|
|
#onblurcapture = () => {
|
|
this.#isFocusInsideDOMTree = false;
|
|
};
|
|
props = {
|
|
onfocuscapture: this.#onfocuscapture,
|
|
onblurcapture: this.#onblurcapture
|
|
};
|
|
}
|
|
function getTopMostDismissableLayer(layersArr = [...globalThis.bitsDismissableLayers]) {
|
|
return layersArr.findLast(([_, { current: behaviorType }]) => behaviorType === "close" || behaviorType === "ignore");
|
|
}
|
|
function isResponsibleLayer(node) {
|
|
const layersArr = [...globalThis.bitsDismissableLayers];
|
|
const topMostLayer = getTopMostDismissableLayer(layersArr);
|
|
if (topMostLayer) return topMostLayer[0].opts.ref.current === node;
|
|
const [firstLayerNode] = layersArr[0];
|
|
return firstLayerNode.opts.ref.current === node;
|
|
}
|
|
function isValidEvent(e, node) {
|
|
const target = e.target;
|
|
if (!isElementOrSVGElement(target)) return false;
|
|
const targetIsContextMenuTrigger = Boolean(target.closest(`[${CONTEXT_MENU_TRIGGER_ATTR}]`));
|
|
if ("button" in e && e.button > 0 && !targetIsContextMenuTrigger) return false;
|
|
if ("button" in e && e.button === 0 && targetIsContextMenuTrigger) return true;
|
|
const nodeIsContextMenu = Boolean(node.closest(`[${CONTEXT_MENU_CONTENT_ATTR}]`));
|
|
if (targetIsContextMenuTrigger && nodeIsContextMenu) return false;
|
|
const ownerDocument = getOwnerDocument(target);
|
|
const isValid = ownerDocument.documentElement.contains(target) && !isOrContainsTarget(node, target) && isClickTrulyOutside(e, node);
|
|
return isValid;
|
|
}
|
|
function createWrappedEvent(e) {
|
|
const capturedCurrentTarget = e.currentTarget;
|
|
const capturedTarget = e.target;
|
|
let newEvent;
|
|
if (e instanceof PointerEvent) {
|
|
newEvent = new PointerEvent(e.type, e);
|
|
} else {
|
|
newEvent = new PointerEvent("pointerdown", e);
|
|
}
|
|
let isPrevented = false;
|
|
const wrappedEvent = new Proxy(newEvent, {
|
|
get: (target, prop) => {
|
|
if (prop === "currentTarget") {
|
|
return capturedCurrentTarget;
|
|
}
|
|
if (prop === "target") {
|
|
return capturedTarget;
|
|
}
|
|
if (prop === "preventDefault") {
|
|
return () => {
|
|
isPrevented = true;
|
|
if (typeof target.preventDefault === "function") {
|
|
target.preventDefault();
|
|
}
|
|
};
|
|
}
|
|
if (prop === "defaultPrevented") {
|
|
return isPrevented;
|
|
}
|
|
if (prop in target) {
|
|
return target[prop];
|
|
}
|
|
return e[prop];
|
|
}
|
|
});
|
|
return wrappedEvent;
|
|
}
|
|
function Dismissible_layer($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
interactOutsideBehavior = "close",
|
|
onInteractOutside = noop,
|
|
onFocusOutside = noop,
|
|
id,
|
|
children,
|
|
enabled,
|
|
isValidEvent: isValidEvent2 = () => false,
|
|
ref
|
|
} = $$props;
|
|
const dismissibleLayerState = DismissibleLayerState.create({
|
|
id: boxWith(() => id),
|
|
interactOutsideBehavior: boxWith(() => interactOutsideBehavior),
|
|
onInteractOutside: boxWith(() => onInteractOutside),
|
|
enabled: boxWith(() => enabled),
|
|
onFocusOutside: boxWith(() => onFocusOutside),
|
|
isValidEvent: boxWith(() => isValidEvent2),
|
|
ref
|
|
});
|
|
children?.($$renderer2, { props: dismissibleLayerState.props });
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
globalThis.bitsEscapeLayers ??= /* @__PURE__ */ new Map();
|
|
class EscapeLayerState {
|
|
static create(opts) {
|
|
return new EscapeLayerState(opts);
|
|
}
|
|
opts;
|
|
domContext;
|
|
constructor(opts) {
|
|
this.opts = opts;
|
|
this.domContext = new DOMContext(this.opts.ref);
|
|
let unsubEvents = noop;
|
|
watch(() => opts.enabled.current, (enabled) => {
|
|
if (enabled) {
|
|
globalThis.bitsEscapeLayers.set(this, opts.escapeKeydownBehavior);
|
|
unsubEvents = this.#addEventListener();
|
|
}
|
|
return () => {
|
|
unsubEvents();
|
|
globalThis.bitsEscapeLayers.delete(this);
|
|
};
|
|
});
|
|
}
|
|
#addEventListener = () => {
|
|
return on(this.domContext.getDocument(), "keydown", this.#onkeydown, { passive: false });
|
|
};
|
|
#onkeydown = (e) => {
|
|
if (e.key !== ESCAPE || !isResponsibleEscapeLayer(this)) return;
|
|
const clonedEvent = new KeyboardEvent(e.type, e);
|
|
e.preventDefault();
|
|
const behaviorType = this.opts.escapeKeydownBehavior.current;
|
|
if (behaviorType !== "close" && behaviorType !== "defer-otherwise-close") return;
|
|
this.opts.onEscapeKeydown.current(clonedEvent);
|
|
};
|
|
}
|
|
function isResponsibleEscapeLayer(instance) {
|
|
const layersArr = [...globalThis.bitsEscapeLayers];
|
|
const topMostLayer = layersArr.findLast(([_, { current: behaviorType }]) => behaviorType === "close" || behaviorType === "ignore");
|
|
if (topMostLayer) return topMostLayer[0] === instance;
|
|
const [firstLayerNode] = layersArr[0];
|
|
return firstLayerNode === instance;
|
|
}
|
|
function Escape_layer($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
escapeKeydownBehavior = "close",
|
|
onEscapeKeydown = noop,
|
|
children,
|
|
enabled,
|
|
ref
|
|
} = $$props;
|
|
EscapeLayerState.create({
|
|
escapeKeydownBehavior: boxWith(() => escapeKeydownBehavior),
|
|
onEscapeKeydown: boxWith(() => onEscapeKeydown),
|
|
enabled: boxWith(() => enabled),
|
|
ref
|
|
});
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
class FocusScopeManager {
|
|
static instance;
|
|
#scopeStack = simpleBox([]);
|
|
#focusHistory = /* @__PURE__ */ new WeakMap();
|
|
#preFocusHistory = /* @__PURE__ */ new WeakMap();
|
|
static getInstance() {
|
|
if (!this.instance) {
|
|
this.instance = new FocusScopeManager();
|
|
}
|
|
return this.instance;
|
|
}
|
|
register(scope) {
|
|
const current = this.getActive();
|
|
if (current && current !== scope) {
|
|
current.pause();
|
|
}
|
|
const activeElement = document.activeElement;
|
|
if (activeElement && activeElement !== document.body) {
|
|
this.#preFocusHistory.set(scope, activeElement);
|
|
}
|
|
this.#scopeStack.current = this.#scopeStack.current.filter((s) => s !== scope);
|
|
this.#scopeStack.current.unshift(scope);
|
|
}
|
|
unregister(scope) {
|
|
this.#scopeStack.current = this.#scopeStack.current.filter((s) => s !== scope);
|
|
const next = this.getActive();
|
|
if (next) {
|
|
next.resume();
|
|
}
|
|
}
|
|
getActive() {
|
|
return this.#scopeStack.current[0];
|
|
}
|
|
setFocusMemory(scope, element) {
|
|
this.#focusHistory.set(scope, element);
|
|
}
|
|
getFocusMemory(scope) {
|
|
return this.#focusHistory.get(scope);
|
|
}
|
|
isActiveScope(scope) {
|
|
return this.getActive() === scope;
|
|
}
|
|
setPreFocusMemory(scope, element) {
|
|
this.#preFocusHistory.set(scope, element);
|
|
}
|
|
getPreFocusMemory(scope) {
|
|
return this.#preFocusHistory.get(scope);
|
|
}
|
|
clearPreFocusMemory(scope) {
|
|
this.#preFocusHistory.delete(scope);
|
|
}
|
|
}
|
|
class FocusScope {
|
|
#paused = false;
|
|
#container = null;
|
|
#manager = FocusScopeManager.getInstance();
|
|
#cleanupFns = [];
|
|
#opts;
|
|
constructor(opts) {
|
|
this.#opts = opts;
|
|
}
|
|
get paused() {
|
|
return this.#paused;
|
|
}
|
|
pause() {
|
|
this.#paused = true;
|
|
}
|
|
resume() {
|
|
this.#paused = false;
|
|
}
|
|
#cleanup() {
|
|
for (const fn of this.#cleanupFns) {
|
|
fn();
|
|
}
|
|
this.#cleanupFns = [];
|
|
}
|
|
mount(container) {
|
|
if (this.#container) {
|
|
this.unmount();
|
|
}
|
|
this.#container = container;
|
|
this.#manager.register(this);
|
|
this.#setupEventListeners();
|
|
this.#handleOpenAutoFocus();
|
|
}
|
|
unmount() {
|
|
if (!this.#container) return;
|
|
this.#cleanup();
|
|
this.#handleCloseAutoFocus();
|
|
this.#manager.unregister(this);
|
|
this.#manager.clearPreFocusMemory(this);
|
|
this.#container = null;
|
|
}
|
|
#handleOpenAutoFocus() {
|
|
if (!this.#container) return;
|
|
const event = new CustomEvent("focusScope.onOpenAutoFocus", { bubbles: false, cancelable: true });
|
|
this.#opts.onOpenAutoFocus.current(event);
|
|
if (!event.defaultPrevented) {
|
|
requestAnimationFrame(() => {
|
|
if (!this.#container) return;
|
|
const firstTabbable = this.#getFirstTabbable();
|
|
if (firstTabbable) {
|
|
firstTabbable.focus();
|
|
this.#manager.setFocusMemory(this, firstTabbable);
|
|
} else {
|
|
this.#container.focus();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
#handleCloseAutoFocus() {
|
|
const event = new CustomEvent("focusScope.onCloseAutoFocus", { bubbles: false, cancelable: true });
|
|
this.#opts.onCloseAutoFocus.current?.(event);
|
|
if (!event.defaultPrevented) {
|
|
const preFocusedElement = this.#manager.getPreFocusMemory(this);
|
|
if (preFocusedElement && document.contains(preFocusedElement)) {
|
|
try {
|
|
preFocusedElement.focus();
|
|
} catch {
|
|
document.body.focus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#setupEventListeners() {
|
|
if (!this.#container || !this.#opts.trap.current) return;
|
|
const container = this.#container;
|
|
const doc = container.ownerDocument;
|
|
const handleFocus = (e) => {
|
|
if (this.#paused || !this.#manager.isActiveScope(this)) return;
|
|
const target = e.target;
|
|
if (!target) return;
|
|
const isInside = container.contains(target);
|
|
if (isInside) {
|
|
this.#manager.setFocusMemory(this, target);
|
|
} else {
|
|
const lastFocused = this.#manager.getFocusMemory(this);
|
|
if (lastFocused && container.contains(lastFocused) && isFocusable(lastFocused)) {
|
|
e.preventDefault();
|
|
lastFocused.focus();
|
|
} else {
|
|
const firstTabbable = this.#getFirstTabbable();
|
|
const firstFocusable = this.#getAllFocusables()[0];
|
|
(firstTabbable || firstFocusable || container).focus();
|
|
}
|
|
}
|
|
};
|
|
const handleKeydown = (e) => {
|
|
if (!this.#opts.loop || this.#paused || e.key !== "Tab") return;
|
|
if (!this.#manager.isActiveScope(this)) return;
|
|
const tabbables = this.#getTabbables();
|
|
if (tabbables.length === 0) return;
|
|
const first = tabbables[0];
|
|
const last = tabbables[tabbables.length - 1];
|
|
if (!e.shiftKey && doc.activeElement === last) {
|
|
e.preventDefault();
|
|
first.focus();
|
|
} else if (e.shiftKey && doc.activeElement === first) {
|
|
e.preventDefault();
|
|
last.focus();
|
|
}
|
|
};
|
|
this.#cleanupFns.push(on(doc, "focusin", handleFocus, { capture: true }), on(container, "keydown", handleKeydown));
|
|
const observer = new MutationObserver(() => {
|
|
const lastFocused = this.#manager.getFocusMemory(this);
|
|
if (lastFocused && !container.contains(lastFocused)) {
|
|
const firstTabbable = this.#getFirstTabbable();
|
|
const firstFocusable = this.#getAllFocusables()[0];
|
|
const elementToFocus = firstTabbable || firstFocusable;
|
|
if (elementToFocus) {
|
|
elementToFocus.focus();
|
|
this.#manager.setFocusMemory(this, elementToFocus);
|
|
} else {
|
|
container.focus();
|
|
}
|
|
}
|
|
});
|
|
observer.observe(container, { childList: true, subtree: true });
|
|
this.#cleanupFns.push(() => observer.disconnect());
|
|
}
|
|
#getTabbables() {
|
|
if (!this.#container) return [];
|
|
return tabbable(this.#container, { includeContainer: false, getShadowRoot: true });
|
|
}
|
|
#getFirstTabbable() {
|
|
const tabbables = this.#getTabbables();
|
|
return tabbables[0] || null;
|
|
}
|
|
#getAllFocusables() {
|
|
if (!this.#container) return [];
|
|
return focusable(this.#container, { includeContainer: false, getShadowRoot: true });
|
|
}
|
|
static use(opts) {
|
|
let scope = null;
|
|
watch([() => opts.ref.current, () => opts.enabled.current], ([ref, enabled]) => {
|
|
if (ref && enabled) {
|
|
if (!scope) {
|
|
scope = new FocusScope(opts);
|
|
}
|
|
scope.mount(ref);
|
|
} else if (scope) {
|
|
scope.unmount();
|
|
scope = null;
|
|
}
|
|
});
|
|
return {
|
|
get props() {
|
|
return { tabindex: -1 };
|
|
}
|
|
};
|
|
}
|
|
}
|
|
function Focus_scope($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
enabled = false,
|
|
trapFocus = false,
|
|
loop = false,
|
|
onCloseAutoFocus = noop,
|
|
onOpenAutoFocus = noop,
|
|
focusScope,
|
|
ref
|
|
} = $$props;
|
|
const focusScopeState = FocusScope.use({
|
|
enabled: boxWith(() => enabled),
|
|
trap: boxWith(() => trapFocus),
|
|
loop,
|
|
onCloseAutoFocus: boxWith(() => onCloseAutoFocus),
|
|
onOpenAutoFocus: boxWith(() => onOpenAutoFocus),
|
|
ref
|
|
});
|
|
focusScope?.($$renderer2, { props: focusScopeState.props });
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
globalThis.bitsTextSelectionLayers ??= /* @__PURE__ */ new Map();
|
|
class TextSelectionLayerState {
|
|
static create(opts) {
|
|
return new TextSelectionLayerState(opts);
|
|
}
|
|
opts;
|
|
domContext;
|
|
#unsubSelectionLock = noop;
|
|
constructor(opts) {
|
|
this.opts = opts;
|
|
this.domContext = new DOMContext(opts.ref);
|
|
let unsubEvents = noop;
|
|
watch(() => this.opts.enabled.current, (isEnabled) => {
|
|
if (isEnabled) {
|
|
globalThis.bitsTextSelectionLayers.set(this, this.opts.enabled);
|
|
unsubEvents();
|
|
unsubEvents = this.#addEventListeners();
|
|
}
|
|
return () => {
|
|
unsubEvents();
|
|
this.#resetSelectionLock();
|
|
globalThis.bitsTextSelectionLayers.delete(this);
|
|
};
|
|
});
|
|
}
|
|
#addEventListeners() {
|
|
return executeCallbacks(on(this.domContext.getDocument(), "pointerdown", this.#pointerdown), on(this.domContext.getDocument(), "pointerup", composeHandlers(this.#resetSelectionLock, this.opts.onPointerUp.current)));
|
|
}
|
|
#pointerdown = (e) => {
|
|
const node = this.opts.ref.current;
|
|
const target = e.target;
|
|
if (!isHTMLElement(node) || !isHTMLElement(target) || !this.opts.enabled.current) return;
|
|
if (!isHighestLayer(this) || !contains(node, target)) return;
|
|
this.opts.onPointerDown.current(e);
|
|
if (e.defaultPrevented) return;
|
|
this.#unsubSelectionLock = preventTextSelectionOverflow(node, this.domContext.getDocument().body);
|
|
};
|
|
#resetSelectionLock = () => {
|
|
this.#unsubSelectionLock();
|
|
this.#unsubSelectionLock = noop;
|
|
};
|
|
}
|
|
const getUserSelect = (node) => node.style.userSelect || node.style.webkitUserSelect;
|
|
function preventTextSelectionOverflow(node, body) {
|
|
const originalBodyUserSelect = getUserSelect(body);
|
|
const originalNodeUserSelect = getUserSelect(node);
|
|
setUserSelect(body, "none");
|
|
setUserSelect(node, "text");
|
|
return () => {
|
|
setUserSelect(body, originalBodyUserSelect);
|
|
setUserSelect(node, originalNodeUserSelect);
|
|
};
|
|
}
|
|
function setUserSelect(node, value) {
|
|
node.style.userSelect = value;
|
|
node.style.webkitUserSelect = value;
|
|
}
|
|
function isHighestLayer(instance) {
|
|
const layersArr = [...globalThis.bitsTextSelectionLayers];
|
|
if (!layersArr.length) return false;
|
|
const highestLayer = layersArr.at(-1);
|
|
if (!highestLayer) return false;
|
|
return highestLayer[0] === instance;
|
|
}
|
|
function Text_selection_layer($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
preventOverflowTextSelection = true,
|
|
onPointerDown = noop,
|
|
onPointerUp = noop,
|
|
id,
|
|
children,
|
|
enabled,
|
|
ref
|
|
} = $$props;
|
|
TextSelectionLayerState.create({
|
|
id: boxWith(() => id),
|
|
onPointerDown: boxWith(() => onPointerDown),
|
|
onPointerUp: boxWith(() => onPointerUp),
|
|
enabled: boxWith(() => enabled && preventOverflowTextSelection),
|
|
ref
|
|
});
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
globalThis.bitsIdCounter ??= { current: 0 };
|
|
function useId(prefix = "bits") {
|
|
globalThis.bitsIdCounter.current++;
|
|
return `${prefix}-${globalThis.bitsIdCounter.current}`;
|
|
}
|
|
class SharedState {
|
|
#factory;
|
|
#subscribers = 0;
|
|
#state;
|
|
#scope;
|
|
constructor(factory) {
|
|
this.#factory = factory;
|
|
}
|
|
#dispose() {
|
|
this.#subscribers -= 1;
|
|
if (this.#scope && this.#subscribers <= 0) {
|
|
this.#scope();
|
|
this.#state = void 0;
|
|
this.#scope = void 0;
|
|
}
|
|
}
|
|
get(...args) {
|
|
this.#subscribers += 1;
|
|
if (this.#state === void 0) {
|
|
this.#scope = () => {
|
|
};
|
|
}
|
|
return this.#state;
|
|
}
|
|
}
|
|
const lockMap = new SvelteMap();
|
|
let initialBodyStyle = null;
|
|
let cleanupTimeoutId = null;
|
|
let isInCleanupTransition = false;
|
|
const anyLocked = boxWith(() => {
|
|
for (const value of lockMap.values()) {
|
|
if (value) return true;
|
|
}
|
|
return false;
|
|
});
|
|
let cleanupScheduledAt = null;
|
|
const bodyLockStackCount = new SharedState(() => {
|
|
function resetBodyStyle() {
|
|
return;
|
|
}
|
|
function cancelPendingCleanup() {
|
|
if (cleanupTimeoutId === null) return;
|
|
window.clearTimeout(cleanupTimeoutId);
|
|
cleanupTimeoutId = null;
|
|
}
|
|
function scheduleCleanupIfNoNewLocks(delay, callback) {
|
|
cancelPendingCleanup();
|
|
isInCleanupTransition = true;
|
|
cleanupScheduledAt = Date.now();
|
|
const currentCleanupId = cleanupScheduledAt;
|
|
const cleanupFn = () => {
|
|
cleanupTimeoutId = null;
|
|
if (cleanupScheduledAt !== currentCleanupId) return;
|
|
if (!isAnyLocked(lockMap)) {
|
|
isInCleanupTransition = false;
|
|
callback();
|
|
} else {
|
|
isInCleanupTransition = false;
|
|
}
|
|
};
|
|
const actualDelay = delay === null ? 24 : delay;
|
|
cleanupTimeoutId = window.setTimeout(cleanupFn, actualDelay);
|
|
}
|
|
function ensureInitialStyleCaptured() {
|
|
if (initialBodyStyle === null && lockMap.size === 0 && !isInCleanupTransition) {
|
|
initialBodyStyle = document.body.getAttribute("style");
|
|
}
|
|
}
|
|
watch(() => anyLocked.current, () => {
|
|
if (!anyLocked.current) return;
|
|
ensureInitialStyleCaptured();
|
|
isInCleanupTransition = false;
|
|
const htmlStyle = getComputedStyle(document.documentElement);
|
|
const bodyStyle = getComputedStyle(document.body);
|
|
const hasStableGutter = htmlStyle.scrollbarGutter?.includes("stable") || bodyStyle.scrollbarGutter?.includes("stable");
|
|
const verticalScrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
const paddingRight = Number.parseInt(bodyStyle.paddingRight ?? "0", 10);
|
|
const config = {
|
|
padding: paddingRight + verticalScrollbarWidth,
|
|
margin: Number.parseInt(bodyStyle.marginRight ?? "0", 10)
|
|
};
|
|
if (verticalScrollbarWidth > 0 && !hasStableGutter) {
|
|
document.body.style.paddingRight = `${config.padding}px`;
|
|
document.body.style.marginRight = `${config.margin}px`;
|
|
document.body.style.setProperty("--scrollbar-width", `${verticalScrollbarWidth}px`);
|
|
}
|
|
document.body.style.overflow = "hidden";
|
|
if (isIOS) {
|
|
on(
|
|
document,
|
|
"touchmove",
|
|
(e) => {
|
|
if (e.target !== document.documentElement) return;
|
|
if (e.touches.length > 1) return;
|
|
e.preventDefault();
|
|
},
|
|
{ passive: false }
|
|
);
|
|
}
|
|
afterTick(() => {
|
|
document.body.style.pointerEvents = "none";
|
|
document.body.style.overflow = "hidden";
|
|
});
|
|
});
|
|
return {
|
|
get lockMap() {
|
|
return lockMap;
|
|
},
|
|
resetBodyStyle,
|
|
scheduleCleanupIfNoNewLocks,
|
|
cancelPendingCleanup,
|
|
ensureInitialStyleCaptured
|
|
};
|
|
});
|
|
class BodyScrollLock {
|
|
#id = useId();
|
|
#initialState;
|
|
#restoreScrollDelay = () => null;
|
|
#countState;
|
|
locked;
|
|
constructor(initialState, restoreScrollDelay = () => null) {
|
|
this.#initialState = initialState;
|
|
this.#restoreScrollDelay = restoreScrollDelay;
|
|
this.#countState = bodyLockStackCount.get();
|
|
if (!this.#countState) return;
|
|
this.#countState.cancelPendingCleanup();
|
|
this.#countState.ensureInitialStyleCaptured();
|
|
this.#countState.lockMap.set(this.#id, this.#initialState ?? false);
|
|
this.locked = boxWith(() => this.#countState.lockMap.get(this.#id) ?? false, (v) => this.#countState.lockMap.set(this.#id, v));
|
|
}
|
|
}
|
|
function isAnyLocked(map) {
|
|
for (const [_, value] of map) {
|
|
if (value) return true;
|
|
}
|
|
return false;
|
|
}
|
|
function Scroll_lock($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { preventScroll = true, restoreScrollDelay = null } = $$props;
|
|
if (preventScroll) {
|
|
new BodyScrollLock(preventScroll, () => restoreScrollDelay);
|
|
}
|
|
});
|
|
}
|
|
const avatarAttrs = createBitsAttrs({ component: "avatar", parts: ["root", "image", "fallback"] });
|
|
const AvatarRootContext = new Context("Avatar.Root");
|
|
class AvatarRootState {
|
|
static create(opts) {
|
|
return AvatarRootContext.set(new AvatarRootState(opts));
|
|
}
|
|
opts;
|
|
domContext;
|
|
attachment;
|
|
constructor(opts) {
|
|
this.opts = opts;
|
|
this.domContext = new DOMContext(this.opts.ref);
|
|
this.loadImage = this.loadImage.bind(this);
|
|
this.attachment = attachRef(this.opts.ref);
|
|
}
|
|
loadImage(src, crossorigin, referrerPolicy) {
|
|
if (this.opts.loadingStatus.current === "loaded") return;
|
|
let imageTimerId;
|
|
const image = new Image();
|
|
image.src = src;
|
|
if (crossorigin !== void 0) image.crossOrigin = crossorigin;
|
|
if (referrerPolicy) image.referrerPolicy = referrerPolicy;
|
|
this.opts.loadingStatus.current = "loading";
|
|
image.onload = () => {
|
|
imageTimerId = this.domContext.setTimeout(
|
|
() => {
|
|
this.opts.loadingStatus.current = "loaded";
|
|
},
|
|
this.opts.delayMs.current
|
|
);
|
|
};
|
|
image.onerror = () => {
|
|
this.opts.loadingStatus.current = "error";
|
|
};
|
|
return () => {
|
|
if (!imageTimerId) return;
|
|
this.domContext.clearTimeout(imageTimerId);
|
|
};
|
|
}
|
|
#props = derived(() => ({
|
|
id: this.opts.id.current,
|
|
[avatarAttrs.root]: "",
|
|
"data-status": this.opts.loadingStatus.current,
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
class AvatarFallbackState {
|
|
static create(opts) {
|
|
return new AvatarFallbackState(opts, AvatarRootContext.get());
|
|
}
|
|
opts;
|
|
root;
|
|
attachment;
|
|
constructor(opts, root) {
|
|
this.opts = opts;
|
|
this.root = root;
|
|
this.attachment = attachRef(this.opts.ref);
|
|
}
|
|
#style = derived(() => this.root.opts.loadingStatus.current === "loaded" ? { display: "none" } : void 0);
|
|
get style() {
|
|
return this.#style();
|
|
}
|
|
set style($$value) {
|
|
return this.#style($$value);
|
|
}
|
|
#props = derived(() => ({
|
|
style: this.style,
|
|
"data-status": this.root.opts.loadingStatus.current,
|
|
[avatarAttrs.fallback]: "",
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
function Avatar$1($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
delayMs = 0,
|
|
loadingStatus = "loading",
|
|
onLoadingStatusChange,
|
|
child,
|
|
children,
|
|
id = createId(uid),
|
|
ref = null,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const rootState = AvatarRootState.create({
|
|
delayMs: boxWith(() => delayMs),
|
|
loadingStatus: boxWith(() => loadingStatus, (v) => {
|
|
if (loadingStatus !== v) {
|
|
loadingStatus = v;
|
|
onLoadingStatusChange?.(v);
|
|
}
|
|
}),
|
|
id: boxWith(() => id),
|
|
ref: boxWith(() => ref, (v) => ref = v)
|
|
});
|
|
const mergedProps = mergeProps(restProps, rootState.props);
|
|
if (child) {
|
|
$$renderer2.push("<!--[-->");
|
|
child($$renderer2, { props: mergedProps });
|
|
$$renderer2.push(`<!---->`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
$$renderer2.push(`<div${attributes({ ...mergedProps })}>`);
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!----></div>`);
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
bind_props($$props, { loadingStatus, ref });
|
|
});
|
|
}
|
|
function Avatar_fallback$1($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
children,
|
|
child,
|
|
id = createId(uid),
|
|
ref = null,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const fallbackState = AvatarFallbackState.create({
|
|
id: boxWith(() => id),
|
|
ref: boxWith(() => ref, (v) => ref = v)
|
|
});
|
|
const mergedProps = mergeProps(restProps, fallbackState.props);
|
|
if (child) {
|
|
$$renderer2.push("<!--[-->");
|
|
child($$renderer2, { props: mergedProps });
|
|
$$renderer2.push(`<!---->`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
$$renderer2.push(`<span${attributes({ ...mergedProps })}>`);
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!----></span>`);
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Hidden_input($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { value = void 0, $$slots, $$events, ...restProps } = $$props;
|
|
const mergedProps = mergeProps(restProps, {
|
|
"aria-hidden": "true",
|
|
tabindex: -1,
|
|
style: srOnlyStylesString
|
|
});
|
|
if (mergedProps.type === "checkbox") {
|
|
$$renderer2.push("<!--[-->");
|
|
$$renderer2.push(`<input${attributes({ ...mergedProps, value }, void 0, void 0, void 0, 4)}/>`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
$$renderer2.push(`<input${attributes({ value, ...mergedProps }, void 0, void 0, void 0, 4)}/>`);
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
bind_props($$props, { value });
|
|
});
|
|
}
|
|
function get(valueOrGetValue) {
|
|
return typeof valueOrGetValue === "function" ? valueOrGetValue() : valueOrGetValue;
|
|
}
|
|
function getDPR(element) {
|
|
if (typeof window === "undefined") return 1;
|
|
const win = element.ownerDocument.defaultView || window;
|
|
return win.devicePixelRatio || 1;
|
|
}
|
|
function roundByDPR(element, value) {
|
|
const dpr = getDPR(element);
|
|
return Math.round(value * dpr) / dpr;
|
|
}
|
|
function getFloatingContentCSSVars(name) {
|
|
return {
|
|
[`--bits-${name}-content-transform-origin`]: `var(--bits-floating-transform-origin)`,
|
|
[`--bits-${name}-content-available-width`]: `var(--bits-floating-available-width)`,
|
|
[`--bits-${name}-content-available-height`]: `var(--bits-floating-available-height)`,
|
|
[`--bits-${name}-anchor-width`]: `var(--bits-floating-anchor-width)`,
|
|
[`--bits-${name}-anchor-height`]: `var(--bits-floating-anchor-height)`
|
|
};
|
|
}
|
|
function useFloating(options) {
|
|
const openOption = get(options.open) ?? true;
|
|
const middlewareOption = get(options.middleware);
|
|
const transformOption = get(options.transform) ?? true;
|
|
const placementOption = get(options.placement) ?? "bottom";
|
|
const strategyOption = get(options.strategy) ?? "absolute";
|
|
const sideOffsetOption = get(options.sideOffset) ?? 0;
|
|
const alignOffsetOption = get(options.alignOffset) ?? 0;
|
|
const reference = options.reference;
|
|
let x = 0;
|
|
let y = 0;
|
|
const floating = simpleBox(null);
|
|
let strategy = strategyOption;
|
|
let placement = placementOption;
|
|
let middlewareData = {};
|
|
let isPositioned = false;
|
|
const floatingStyles = (() => {
|
|
const xVal = floating.current ? roundByDPR(floating.current, x) : x;
|
|
const yVal = floating.current ? roundByDPR(floating.current, y) : y;
|
|
if (transformOption) {
|
|
return {
|
|
position: strategy,
|
|
left: "0",
|
|
top: "0",
|
|
transform: `translate(${xVal}px, ${yVal}px)`,
|
|
...floating.current && getDPR(floating.current) >= 1.5 && { willChange: "transform" }
|
|
};
|
|
}
|
|
return { position: strategy, left: `${xVal}px`, top: `${yVal}px` };
|
|
})();
|
|
function update() {
|
|
if (reference.current === null || floating.current === null) return;
|
|
computePosition(reference.current, floating.current, {
|
|
middleware: middlewareOption,
|
|
placement: placementOption,
|
|
strategy: strategyOption
|
|
}).then((position) => {
|
|
if (!openOption && x !== 0 && y !== 0) {
|
|
const maxExpectedOffset = Math.max(Math.abs(sideOffsetOption), Math.abs(alignOffsetOption), 15);
|
|
if (position.x <= maxExpectedOffset && position.y <= maxExpectedOffset) return;
|
|
}
|
|
x = position.x;
|
|
y = position.y;
|
|
strategy = position.strategy;
|
|
placement = position.placement;
|
|
middlewareData = position.middlewareData;
|
|
isPositioned = true;
|
|
});
|
|
}
|
|
return {
|
|
floating,
|
|
reference,
|
|
get strategy() {
|
|
return strategy;
|
|
},
|
|
get placement() {
|
|
return placement;
|
|
},
|
|
get middlewareData() {
|
|
return middlewareData;
|
|
},
|
|
get isPositioned() {
|
|
return isPositioned;
|
|
},
|
|
get floatingStyles() {
|
|
return floatingStyles;
|
|
},
|
|
get update() {
|
|
return update;
|
|
}
|
|
};
|
|
}
|
|
const OPPOSITE_SIDE = { top: "bottom", right: "left", bottom: "top", left: "right" };
|
|
const FloatingRootContext = new Context("Floating.Root");
|
|
const FloatingContentContext = new Context("Floating.Content");
|
|
const FloatingTooltipRootContext = new Context("Floating.Root");
|
|
class FloatingRootState {
|
|
static create(tooltip = false) {
|
|
return tooltip ? FloatingTooltipRootContext.set(new FloatingRootState()) : FloatingRootContext.set(new FloatingRootState());
|
|
}
|
|
anchorNode = simpleBox(null);
|
|
customAnchorNode = simpleBox(null);
|
|
triggerNode = simpleBox(null);
|
|
constructor() {
|
|
}
|
|
}
|
|
class FloatingContentState {
|
|
static create(opts, tooltip = false) {
|
|
return tooltip ? FloatingContentContext.set(new FloatingContentState(opts, FloatingTooltipRootContext.get())) : FloatingContentContext.set(new FloatingContentState(opts, FloatingRootContext.get()));
|
|
}
|
|
opts;
|
|
root;
|
|
// nodes
|
|
contentRef = simpleBox(null);
|
|
wrapperRef = simpleBox(null);
|
|
arrowRef = simpleBox(null);
|
|
contentAttachment = attachRef(this.contentRef);
|
|
wrapperAttachment = attachRef(this.wrapperRef);
|
|
arrowAttachment = attachRef(this.arrowRef);
|
|
// ids
|
|
arrowId = simpleBox(useId());
|
|
#transformedStyle = derived(() => {
|
|
if (typeof this.opts.style === "string") return cssToStyleObj(this.opts.style);
|
|
if (!this.opts.style) return {};
|
|
});
|
|
#updatePositionStrategy = void 0;
|
|
#arrowSize = new ElementSize(() => this.arrowRef.current ?? void 0);
|
|
#arrowWidth = derived(() => this.#arrowSize?.width ?? 0);
|
|
#arrowHeight = derived(() => this.#arrowSize?.height ?? 0);
|
|
#desiredPlacement = derived(() => this.opts.side?.current + (this.opts.align.current !== "center" ? `-${this.opts.align.current}` : ""));
|
|
#boundary = derived(() => Array.isArray(this.opts.collisionBoundary.current) ? this.opts.collisionBoundary.current : [this.opts.collisionBoundary.current]);
|
|
#hasExplicitBoundaries = derived(() => this.#boundary().length > 0);
|
|
get hasExplicitBoundaries() {
|
|
return this.#hasExplicitBoundaries();
|
|
}
|
|
set hasExplicitBoundaries($$value) {
|
|
return this.#hasExplicitBoundaries($$value);
|
|
}
|
|
#detectOverflowOptions = derived(() => ({
|
|
padding: this.opts.collisionPadding.current,
|
|
boundary: this.#boundary().filter(isNotNull),
|
|
altBoundary: this.hasExplicitBoundaries
|
|
}));
|
|
get detectOverflowOptions() {
|
|
return this.#detectOverflowOptions();
|
|
}
|
|
set detectOverflowOptions($$value) {
|
|
return this.#detectOverflowOptions($$value);
|
|
}
|
|
#availableWidth = void 0;
|
|
#availableHeight = void 0;
|
|
#anchorWidth = void 0;
|
|
#anchorHeight = void 0;
|
|
#middleware = derived(() => [
|
|
offset({
|
|
mainAxis: this.opts.sideOffset.current + this.#arrowHeight(),
|
|
alignmentAxis: this.opts.alignOffset.current
|
|
}),
|
|
this.opts.avoidCollisions.current && shift({
|
|
mainAxis: true,
|
|
crossAxis: false,
|
|
limiter: this.opts.sticky.current === "partial" ? limitShift() : void 0,
|
|
...this.detectOverflowOptions
|
|
}),
|
|
this.opts.avoidCollisions.current && flip({ ...this.detectOverflowOptions }),
|
|
size({
|
|
...this.detectOverflowOptions,
|
|
apply: ({ rects, availableWidth, availableHeight }) => {
|
|
const { width: anchorWidth, height: anchorHeight } = rects.reference;
|
|
this.#availableWidth = availableWidth;
|
|
this.#availableHeight = availableHeight;
|
|
this.#anchorWidth = anchorWidth;
|
|
this.#anchorHeight = anchorHeight;
|
|
}
|
|
}),
|
|
this.arrowRef.current && arrow({
|
|
element: this.arrowRef.current,
|
|
padding: this.opts.arrowPadding.current
|
|
}),
|
|
transformOrigin({
|
|
arrowWidth: this.#arrowWidth(),
|
|
arrowHeight: this.#arrowHeight()
|
|
}),
|
|
this.opts.hideWhenDetached.current && hide({ strategy: "referenceHidden", ...this.detectOverflowOptions })
|
|
].filter(Boolean));
|
|
get middleware() {
|
|
return this.#middleware();
|
|
}
|
|
set middleware($$value) {
|
|
return this.#middleware($$value);
|
|
}
|
|
floating;
|
|
#placedSide = derived(() => getSideFromPlacement(this.floating.placement));
|
|
get placedSide() {
|
|
return this.#placedSide();
|
|
}
|
|
set placedSide($$value) {
|
|
return this.#placedSide($$value);
|
|
}
|
|
#placedAlign = derived(() => getAlignFromPlacement(this.floating.placement));
|
|
get placedAlign() {
|
|
return this.#placedAlign();
|
|
}
|
|
set placedAlign($$value) {
|
|
return this.#placedAlign($$value);
|
|
}
|
|
#arrowX = derived(() => this.floating.middlewareData.arrow?.x ?? 0);
|
|
get arrowX() {
|
|
return this.#arrowX();
|
|
}
|
|
set arrowX($$value) {
|
|
return this.#arrowX($$value);
|
|
}
|
|
#arrowY = derived(() => this.floating.middlewareData.arrow?.y ?? 0);
|
|
get arrowY() {
|
|
return this.#arrowY();
|
|
}
|
|
set arrowY($$value) {
|
|
return this.#arrowY($$value);
|
|
}
|
|
#cannotCenterArrow = derived(() => this.floating.middlewareData.arrow?.centerOffset !== 0);
|
|
get cannotCenterArrow() {
|
|
return this.#cannotCenterArrow();
|
|
}
|
|
set cannotCenterArrow($$value) {
|
|
return this.#cannotCenterArrow($$value);
|
|
}
|
|
contentZIndex;
|
|
#arrowBaseSide = derived(() => OPPOSITE_SIDE[this.placedSide]);
|
|
get arrowBaseSide() {
|
|
return this.#arrowBaseSide();
|
|
}
|
|
set arrowBaseSide($$value) {
|
|
return this.#arrowBaseSide($$value);
|
|
}
|
|
#wrapperProps = derived(() => ({
|
|
id: this.opts.wrapperId.current,
|
|
"data-bits-floating-content-wrapper": "",
|
|
style: {
|
|
...this.floating.floatingStyles,
|
|
transform: this.floating.isPositioned ? this.floating.floatingStyles.transform : "translate(0, -200%)",
|
|
minWidth: "max-content",
|
|
zIndex: this.contentZIndex,
|
|
"--bits-floating-transform-origin": `${this.floating.middlewareData.transformOrigin?.x} ${this.floating.middlewareData.transformOrigin?.y}`,
|
|
"--bits-floating-available-width": `${this.#availableWidth}px`,
|
|
"--bits-floating-available-height": `${this.#availableHeight}px`,
|
|
"--bits-floating-anchor-width": `${this.#anchorWidth}px`,
|
|
"--bits-floating-anchor-height": `${this.#anchorHeight}px`,
|
|
...this.floating.middlewareData.hide?.referenceHidden && { visibility: "hidden", "pointer-events": "none" },
|
|
...this.#transformedStyle()
|
|
},
|
|
dir: this.opts.dir.current,
|
|
...this.wrapperAttachment
|
|
}));
|
|
get wrapperProps() {
|
|
return this.#wrapperProps();
|
|
}
|
|
set wrapperProps($$value) {
|
|
return this.#wrapperProps($$value);
|
|
}
|
|
#props = derived(() => ({
|
|
"data-side": this.placedSide,
|
|
"data-align": this.placedAlign,
|
|
style: styleToString({ ...this.#transformedStyle() }),
|
|
...this.contentAttachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
#arrowStyle = derived(() => ({
|
|
position: "absolute",
|
|
left: this.arrowX ? `${this.arrowX}px` : void 0,
|
|
top: this.arrowY ? `${this.arrowY}px` : void 0,
|
|
[this.arrowBaseSide]: 0,
|
|
"transform-origin": { top: "", right: "0 0", bottom: "center 0", left: "100% 0" }[this.placedSide],
|
|
transform: {
|
|
top: "translateY(100%)",
|
|
right: "translateY(50%) rotate(90deg) translateX(-50%)",
|
|
bottom: "rotate(180deg)",
|
|
left: "translateY(50%) rotate(-90deg) translateX(50%)"
|
|
}[this.placedSide],
|
|
visibility: this.cannotCenterArrow ? "hidden" : void 0
|
|
}));
|
|
get arrowStyle() {
|
|
return this.#arrowStyle();
|
|
}
|
|
set arrowStyle($$value) {
|
|
return this.#arrowStyle($$value);
|
|
}
|
|
constructor(opts, root) {
|
|
this.opts = opts;
|
|
this.root = root;
|
|
this.#updatePositionStrategy = opts.updatePositionStrategy;
|
|
if (opts.customAnchor) {
|
|
this.root.customAnchorNode.current = opts.customAnchor.current;
|
|
}
|
|
watch(() => opts.customAnchor.current, (customAnchor) => {
|
|
this.root.customAnchorNode.current = customAnchor;
|
|
});
|
|
this.floating = useFloating({
|
|
strategy: () => this.opts.strategy.current,
|
|
placement: () => this.#desiredPlacement(),
|
|
middleware: () => this.middleware,
|
|
reference: this.root.anchorNode,
|
|
open: () => this.opts.enabled.current,
|
|
sideOffset: () => this.opts.sideOffset.current,
|
|
alignOffset: () => this.opts.alignOffset.current
|
|
});
|
|
watch(() => this.contentRef.current, (contentNode) => {
|
|
if (!contentNode || !this.opts.enabled.current) return;
|
|
const win = getWindow(contentNode);
|
|
const rafId = win.requestAnimationFrame(() => {
|
|
if (this.contentRef.current !== contentNode || !this.opts.enabled.current) return;
|
|
const zIndex = win.getComputedStyle(contentNode).zIndex;
|
|
if (zIndex !== this.contentZIndex) {
|
|
this.contentZIndex = zIndex;
|
|
}
|
|
});
|
|
return () => {
|
|
win.cancelAnimationFrame(rafId);
|
|
};
|
|
});
|
|
}
|
|
}
|
|
class FloatingAnchorState {
|
|
static create(opts, tooltip = false) {
|
|
return tooltip ? new FloatingAnchorState(opts, FloatingTooltipRootContext.get()) : new FloatingAnchorState(opts, FloatingRootContext.get());
|
|
}
|
|
opts;
|
|
root;
|
|
constructor(opts, root) {
|
|
this.opts = opts;
|
|
this.root = root;
|
|
if (opts.virtualEl && opts.virtualEl.current) {
|
|
root.triggerNode = boxFrom(opts.virtualEl.current);
|
|
} else {
|
|
root.triggerNode = opts.ref;
|
|
}
|
|
}
|
|
}
|
|
function transformOrigin(options) {
|
|
return {
|
|
name: "transformOrigin",
|
|
options,
|
|
fn(data) {
|
|
const { placement, rects, middlewareData } = data;
|
|
const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
|
|
const isArrowHidden = cannotCenterArrow;
|
|
const arrowWidth = isArrowHidden ? 0 : options.arrowWidth;
|
|
const arrowHeight = isArrowHidden ? 0 : options.arrowHeight;
|
|
const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
|
|
const noArrowAlign = { start: "0%", center: "50%", end: "100%" }[placedAlign];
|
|
const arrowXCenter = (middlewareData.arrow?.x ?? 0) + arrowWidth / 2;
|
|
const arrowYCenter = (middlewareData.arrow?.y ?? 0) + arrowHeight / 2;
|
|
let x = "";
|
|
let y = "";
|
|
if (placedSide === "bottom") {
|
|
x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
|
|
y = `${-arrowHeight}px`;
|
|
} else if (placedSide === "top") {
|
|
x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
|
|
y = `${rects.floating.height + arrowHeight}px`;
|
|
} else if (placedSide === "right") {
|
|
x = `${-arrowHeight}px`;
|
|
y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
|
|
} else if (placedSide === "left") {
|
|
x = `${rects.floating.width + arrowHeight}px`;
|
|
y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
|
|
}
|
|
return { data: { x, y } };
|
|
}
|
|
};
|
|
}
|
|
function getSideAndAlignFromPlacement(placement) {
|
|
const [side, align = "center"] = placement.split("-");
|
|
return [side, align];
|
|
}
|
|
function getSideFromPlacement(placement) {
|
|
return getSideAndAlignFromPlacement(placement)[0];
|
|
}
|
|
function getAlignFromPlacement(placement) {
|
|
return getSideAndAlignFromPlacement(placement)[1];
|
|
}
|
|
function Floating_layer($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { children, tooltip = false } = $$props;
|
|
FloatingRootState.create(tooltip);
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
function Floating_layer_anchor($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { id, children, virtualEl, ref, tooltip = false } = $$props;
|
|
FloatingAnchorState.create(
|
|
{
|
|
id: boxWith(() => id),
|
|
virtualEl: boxWith(() => virtualEl),
|
|
ref
|
|
},
|
|
tooltip
|
|
);
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
function Floating_layer_content($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
content,
|
|
side = "bottom",
|
|
sideOffset = 0,
|
|
align = "center",
|
|
alignOffset = 0,
|
|
id,
|
|
arrowPadding = 0,
|
|
avoidCollisions = true,
|
|
collisionBoundary = [],
|
|
collisionPadding = 0,
|
|
hideWhenDetached = false,
|
|
onPlaced = () => {
|
|
},
|
|
sticky = "partial",
|
|
updatePositionStrategy = "optimized",
|
|
strategy = "fixed",
|
|
dir = "ltr",
|
|
style = {},
|
|
wrapperId = useId(),
|
|
customAnchor = null,
|
|
enabled,
|
|
tooltip = false
|
|
} = $$props;
|
|
const contentState = FloatingContentState.create(
|
|
{
|
|
side: boxWith(() => side),
|
|
sideOffset: boxWith(() => sideOffset),
|
|
align: boxWith(() => align),
|
|
alignOffset: boxWith(() => alignOffset),
|
|
id: boxWith(() => id),
|
|
arrowPadding: boxWith(() => arrowPadding),
|
|
avoidCollisions: boxWith(() => avoidCollisions),
|
|
collisionBoundary: boxWith(() => collisionBoundary),
|
|
collisionPadding: boxWith(() => collisionPadding),
|
|
hideWhenDetached: boxWith(() => hideWhenDetached),
|
|
onPlaced: boxWith(() => onPlaced),
|
|
sticky: boxWith(() => sticky),
|
|
updatePositionStrategy: boxWith(() => updatePositionStrategy),
|
|
strategy: boxWith(() => strategy),
|
|
dir: boxWith(() => dir),
|
|
style: boxWith(() => style),
|
|
enabled: boxWith(() => enabled),
|
|
wrapperId: boxWith(() => wrapperId),
|
|
customAnchor: boxWith(() => customAnchor)
|
|
},
|
|
tooltip
|
|
);
|
|
const mergedProps = mergeProps(contentState.wrapperProps, { style: { pointerEvents: "auto" } });
|
|
content?.($$renderer2, { props: contentState.props, wrapperProps: mergedProps });
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
function Floating_layer_content_static($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { content } = $$props;
|
|
content?.($$renderer2, { props: {}, wrapperProps: {} });
|
|
$$renderer2.push(`<!---->`);
|
|
});
|
|
}
|
|
function Popper_content($$renderer, $$props) {
|
|
let {
|
|
content,
|
|
isStatic = false,
|
|
onPlaced,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
if (isStatic) {
|
|
$$renderer.push("<!--[-->");
|
|
Floating_layer_content_static($$renderer, { content });
|
|
} else {
|
|
$$renderer.push("<!--[!-->");
|
|
Floating_layer_content($$renderer, spread_props([{ content, onPlaced }, restProps]));
|
|
}
|
|
$$renderer.push(`<!--]-->`);
|
|
}
|
|
function Popper_layer_inner($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
popper,
|
|
onEscapeKeydown,
|
|
escapeKeydownBehavior,
|
|
preventOverflowTextSelection,
|
|
id,
|
|
onPointerDown,
|
|
onPointerUp,
|
|
side,
|
|
sideOffset,
|
|
align,
|
|
alignOffset,
|
|
arrowPadding,
|
|
avoidCollisions,
|
|
collisionBoundary,
|
|
collisionPadding,
|
|
sticky,
|
|
hideWhenDetached,
|
|
updatePositionStrategy,
|
|
strategy,
|
|
dir,
|
|
preventScroll,
|
|
wrapperId,
|
|
style,
|
|
onPlaced,
|
|
onInteractOutside,
|
|
onCloseAutoFocus,
|
|
onOpenAutoFocus,
|
|
onFocusOutside,
|
|
interactOutsideBehavior = "close",
|
|
loop,
|
|
trapFocus = true,
|
|
isValidEvent: isValidEvent2 = () => false,
|
|
customAnchor = null,
|
|
isStatic = false,
|
|
enabled,
|
|
ref,
|
|
tooltip = false,
|
|
contentPointerEvents = "auto",
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
{
|
|
let content = function($$renderer3, { props: floatingProps, wrapperProps }) {
|
|
if (restProps.forceMount && enabled) {
|
|
$$renderer3.push("<!--[-->");
|
|
Scroll_lock($$renderer3, { preventScroll });
|
|
} else if (!restProps.forceMount) {
|
|
$$renderer3.push("<!--[1-->");
|
|
Scroll_lock($$renderer3, { preventScroll });
|
|
} else {
|
|
$$renderer3.push("<!--[!-->");
|
|
}
|
|
$$renderer3.push(`<!--]--> `);
|
|
{
|
|
let focusScope = function($$renderer4, { props: focusScopeProps }) {
|
|
Escape_layer($$renderer4, {
|
|
onEscapeKeydown,
|
|
escapeKeydownBehavior,
|
|
enabled,
|
|
ref,
|
|
children: ($$renderer5) => {
|
|
{
|
|
let children = function($$renderer6, { props: dismissibleProps }) {
|
|
Text_selection_layer($$renderer6, {
|
|
id,
|
|
preventOverflowTextSelection,
|
|
onPointerDown,
|
|
onPointerUp,
|
|
enabled,
|
|
ref,
|
|
children: ($$renderer7) => {
|
|
popper?.($$renderer7, {
|
|
props: mergeProps(restProps, floatingProps, dismissibleProps, focusScopeProps, { style: { pointerEvents: contentPointerEvents } }),
|
|
wrapperProps
|
|
});
|
|
$$renderer7.push(`<!---->`);
|
|
}
|
|
});
|
|
};
|
|
Dismissible_layer($$renderer5, {
|
|
id,
|
|
onInteractOutside,
|
|
onFocusOutside,
|
|
interactOutsideBehavior,
|
|
isValidEvent: isValidEvent2,
|
|
enabled,
|
|
ref,
|
|
children
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
Focus_scope($$renderer3, {
|
|
onOpenAutoFocus,
|
|
onCloseAutoFocus,
|
|
loop,
|
|
enabled,
|
|
trapFocus,
|
|
forceMount: restProps.forceMount,
|
|
ref,
|
|
focusScope
|
|
});
|
|
}
|
|
$$renderer3.push(`<!---->`);
|
|
};
|
|
Popper_content($$renderer2, {
|
|
isStatic,
|
|
id,
|
|
side,
|
|
sideOffset,
|
|
align,
|
|
alignOffset,
|
|
arrowPadding,
|
|
avoidCollisions,
|
|
collisionBoundary,
|
|
collisionPadding,
|
|
sticky,
|
|
hideWhenDetached,
|
|
updatePositionStrategy,
|
|
strategy,
|
|
dir,
|
|
wrapperId,
|
|
style,
|
|
onPlaced,
|
|
customAnchor,
|
|
enabled,
|
|
tooltip,
|
|
content,
|
|
$$slots: { content: true }
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function Popper_layer($$renderer, $$props) {
|
|
let {
|
|
popper,
|
|
open,
|
|
onEscapeKeydown,
|
|
escapeKeydownBehavior,
|
|
preventOverflowTextSelection,
|
|
id,
|
|
onPointerDown,
|
|
onPointerUp,
|
|
side,
|
|
sideOffset,
|
|
align,
|
|
alignOffset,
|
|
arrowPadding,
|
|
avoidCollisions,
|
|
collisionBoundary,
|
|
collisionPadding,
|
|
sticky,
|
|
hideWhenDetached,
|
|
updatePositionStrategy,
|
|
strategy,
|
|
dir,
|
|
preventScroll,
|
|
wrapperId,
|
|
style,
|
|
onPlaced,
|
|
onInteractOutside,
|
|
onCloseAutoFocus,
|
|
onOpenAutoFocus,
|
|
onFocusOutside,
|
|
interactOutsideBehavior = "close",
|
|
loop,
|
|
trapFocus = true,
|
|
isValidEvent: isValidEvent2 = () => false,
|
|
customAnchor = null,
|
|
isStatic = false,
|
|
ref,
|
|
shouldRender,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
if (shouldRender) {
|
|
$$renderer.push("<!--[-->");
|
|
Popper_layer_inner($$renderer, spread_props([
|
|
{
|
|
popper,
|
|
onEscapeKeydown,
|
|
escapeKeydownBehavior,
|
|
preventOverflowTextSelection,
|
|
id,
|
|
onPointerDown,
|
|
onPointerUp,
|
|
side,
|
|
sideOffset,
|
|
align,
|
|
alignOffset,
|
|
arrowPadding,
|
|
avoidCollisions,
|
|
collisionBoundary,
|
|
collisionPadding,
|
|
sticky,
|
|
hideWhenDetached,
|
|
updatePositionStrategy,
|
|
strategy,
|
|
dir,
|
|
preventScroll,
|
|
wrapperId,
|
|
style,
|
|
onPlaced,
|
|
customAnchor,
|
|
isStatic,
|
|
enabled: open,
|
|
onInteractOutside,
|
|
onCloseAutoFocus,
|
|
onOpenAutoFocus,
|
|
interactOutsideBehavior,
|
|
loop,
|
|
trapFocus,
|
|
isValidEvent: isValidEvent2,
|
|
onFocusOutside,
|
|
forceMount: false,
|
|
ref
|
|
},
|
|
restProps
|
|
]));
|
|
} else {
|
|
$$renderer.push("<!--[!-->");
|
|
}
|
|
$$renderer.push(`<!--]-->`);
|
|
}
|
|
function Popper_layer_force_mount($$renderer, $$props) {
|
|
let {
|
|
popper,
|
|
onEscapeKeydown,
|
|
escapeKeydownBehavior,
|
|
preventOverflowTextSelection,
|
|
id,
|
|
onPointerDown,
|
|
onPointerUp,
|
|
side,
|
|
sideOffset,
|
|
align,
|
|
alignOffset,
|
|
arrowPadding,
|
|
avoidCollisions,
|
|
collisionBoundary,
|
|
collisionPadding,
|
|
sticky,
|
|
hideWhenDetached,
|
|
updatePositionStrategy,
|
|
strategy,
|
|
dir,
|
|
preventScroll,
|
|
wrapperId,
|
|
style,
|
|
onPlaced,
|
|
onInteractOutside,
|
|
onCloseAutoFocus,
|
|
onOpenAutoFocus,
|
|
onFocusOutside,
|
|
interactOutsideBehavior = "close",
|
|
loop,
|
|
trapFocus = true,
|
|
isValidEvent: isValidEvent2 = () => false,
|
|
customAnchor = null,
|
|
isStatic = false,
|
|
enabled,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
Popper_layer_inner($$renderer, spread_props([
|
|
{
|
|
popper,
|
|
onEscapeKeydown,
|
|
escapeKeydownBehavior,
|
|
preventOverflowTextSelection,
|
|
id,
|
|
onPointerDown,
|
|
onPointerUp,
|
|
side,
|
|
sideOffset,
|
|
align,
|
|
alignOffset,
|
|
arrowPadding,
|
|
avoidCollisions,
|
|
collisionBoundary,
|
|
collisionPadding,
|
|
sticky,
|
|
hideWhenDetached,
|
|
updatePositionStrategy,
|
|
strategy,
|
|
dir,
|
|
preventScroll,
|
|
wrapperId,
|
|
style,
|
|
onPlaced,
|
|
customAnchor,
|
|
isStatic,
|
|
enabled,
|
|
onInteractOutside,
|
|
onCloseAutoFocus,
|
|
onOpenAutoFocus,
|
|
interactOutsideBehavior,
|
|
loop,
|
|
trapFocus,
|
|
isValidEvent: isValidEvent2,
|
|
onFocusOutside
|
|
},
|
|
restProps,
|
|
{ forceMount: true }
|
|
]));
|
|
}
|
|
function Menu_separator($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
ref = null,
|
|
id = createId(uid),
|
|
child,
|
|
children,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const separatorState = MenuSeparatorState.create({
|
|
id: boxWith(() => id),
|
|
ref: boxWith(() => ref, (v) => ref = v)
|
|
});
|
|
const mergedProps = mergeProps(restProps, separatorState.props);
|
|
if (child) {
|
|
$$renderer2.push("<!--[-->");
|
|
child($$renderer2, { props: mergedProps });
|
|
$$renderer2.push(`<!---->`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
$$renderer2.push(`<div${attributes({ ...mergedProps })}>`);
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!----></div>`);
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Menu($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
open = false,
|
|
dir = "ltr",
|
|
onOpenChange = noop,
|
|
onOpenChangeComplete = noop,
|
|
_internal_variant: variant = "dropdown-menu",
|
|
children
|
|
} = $$props;
|
|
const root = MenuRootState.create({
|
|
variant: boxWith(() => variant),
|
|
dir: boxWith(() => dir),
|
|
onClose: () => {
|
|
open = false;
|
|
onOpenChange(false);
|
|
}
|
|
});
|
|
MenuMenuState.create(
|
|
{
|
|
open: boxWith(() => open, (v) => {
|
|
open = v;
|
|
onOpenChange(v);
|
|
}),
|
|
onOpenChangeComplete: boxWith(() => onOpenChangeComplete)
|
|
},
|
|
root
|
|
);
|
|
Floating_layer($$renderer2, {
|
|
children: ($$renderer3) => {
|
|
children?.($$renderer3);
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
});
|
|
bind_props($$props, { open });
|
|
});
|
|
}
|
|
function Dropdown_menu_content$1($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
id = createId(uid),
|
|
child,
|
|
children,
|
|
ref = null,
|
|
loop = true,
|
|
onInteractOutside = noop,
|
|
onEscapeKeydown = noop,
|
|
onCloseAutoFocus = noop,
|
|
forceMount = false,
|
|
trapFocus = false,
|
|
style,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const contentState = MenuContentState.create({
|
|
id: boxWith(() => id),
|
|
loop: boxWith(() => loop),
|
|
ref: boxWith(() => ref, (v) => ref = v),
|
|
onCloseAutoFocus: boxWith(() => onCloseAutoFocus)
|
|
});
|
|
const mergedProps = mergeProps(restProps, contentState.props);
|
|
function handleInteractOutside(e) {
|
|
contentState.handleInteractOutside(e);
|
|
if (e.defaultPrevented) return;
|
|
onInteractOutside(e);
|
|
if (e.defaultPrevented) return;
|
|
if (e.target && e.target instanceof Element) {
|
|
const subContentSelector = `[${contentState.parentMenu.root.getBitsAttr("sub-content")}]`;
|
|
if (e.target.closest(subContentSelector)) return;
|
|
}
|
|
contentState.parentMenu.onClose();
|
|
}
|
|
function handleEscapeKeydown(e) {
|
|
onEscapeKeydown(e);
|
|
if (e.defaultPrevented) return;
|
|
contentState.parentMenu.onClose();
|
|
}
|
|
if (forceMount) {
|
|
$$renderer2.push("<!--[-->");
|
|
{
|
|
let popper = function($$renderer3, { props, wrapperProps }) {
|
|
const finalProps = mergeProps(props, { style: getFloatingContentCSSVars("dropdown-menu") }, { style });
|
|
if (child) {
|
|
$$renderer3.push("<!--[-->");
|
|
child($$renderer3, {
|
|
props: finalProps,
|
|
wrapperProps,
|
|
...contentState.snippetProps
|
|
});
|
|
$$renderer3.push(`<!---->`);
|
|
} else {
|
|
$$renderer3.push("<!--[!-->");
|
|
$$renderer3.push(`<div${attributes({ ...wrapperProps })}><div${attributes({ ...finalProps })}>`);
|
|
children?.($$renderer3);
|
|
$$renderer3.push(`<!----></div></div>`);
|
|
}
|
|
$$renderer3.push(`<!--]-->`);
|
|
};
|
|
Popper_layer_force_mount($$renderer2, spread_props([
|
|
mergedProps,
|
|
contentState.popperProps,
|
|
{
|
|
ref: contentState.opts.ref,
|
|
enabled: contentState.parentMenu.opts.open.current,
|
|
onInteractOutside: handleInteractOutside,
|
|
onEscapeKeydown: handleEscapeKeydown,
|
|
trapFocus,
|
|
loop,
|
|
forceMount: true,
|
|
id,
|
|
shouldRender: contentState.shouldRender,
|
|
popper,
|
|
$$slots: { popper: true }
|
|
}
|
|
]));
|
|
}
|
|
} else if (!forceMount) {
|
|
$$renderer2.push("<!--[1-->");
|
|
{
|
|
let popper = function($$renderer3, { props, wrapperProps }) {
|
|
const finalProps = mergeProps(props, { style: getFloatingContentCSSVars("dropdown-menu") }, { style });
|
|
if (child) {
|
|
$$renderer3.push("<!--[-->");
|
|
child($$renderer3, {
|
|
props: finalProps,
|
|
wrapperProps,
|
|
...contentState.snippetProps
|
|
});
|
|
$$renderer3.push(`<!---->`);
|
|
} else {
|
|
$$renderer3.push("<!--[!-->");
|
|
$$renderer3.push(`<div${attributes({ ...wrapperProps })}><div${attributes({ ...finalProps })}>`);
|
|
children?.($$renderer3);
|
|
$$renderer3.push(`<!----></div></div>`);
|
|
}
|
|
$$renderer3.push(`<!--]-->`);
|
|
};
|
|
Popper_layer($$renderer2, spread_props([
|
|
mergedProps,
|
|
contentState.popperProps,
|
|
{
|
|
ref: contentState.opts.ref,
|
|
open: contentState.parentMenu.opts.open.current,
|
|
onInteractOutside: handleInteractOutside,
|
|
onEscapeKeydown: handleEscapeKeydown,
|
|
trapFocus,
|
|
loop,
|
|
forceMount: false,
|
|
id,
|
|
shouldRender: contentState.shouldRender,
|
|
popper,
|
|
$$slots: { popper: true }
|
|
}
|
|
]));
|
|
}
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Menu_trigger($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
id = createId(uid),
|
|
ref = null,
|
|
child,
|
|
children,
|
|
disabled = false,
|
|
type = "button",
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const triggerState = DropdownMenuTriggerState.create({
|
|
id: boxWith(() => id),
|
|
disabled: boxWith(() => disabled ?? false),
|
|
ref: boxWith(() => ref, (v) => ref = v)
|
|
});
|
|
const mergedProps = mergeProps(restProps, triggerState.props, { type });
|
|
Floating_layer_anchor($$renderer2, {
|
|
id,
|
|
ref: triggerState.opts.ref,
|
|
children: ($$renderer3) => {
|
|
if (child) {
|
|
$$renderer3.push("<!--[-->");
|
|
child($$renderer3, { props: mergedProps });
|
|
$$renderer3.push(`<!---->`);
|
|
} else {
|
|
$$renderer3.push("<!--[!-->");
|
|
$$renderer3.push(`<button${attributes({ ...mergedProps })}>`);
|
|
children?.($$renderer3);
|
|
$$renderer3.push(`<!----></button>`);
|
|
}
|
|
$$renderer3.push(`<!--]-->`);
|
|
}
|
|
});
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
const switchAttrs = createBitsAttrs({ component: "switch", parts: ["root", "thumb"] });
|
|
const SwitchRootContext = new Context("Switch.Root");
|
|
class SwitchRootState {
|
|
static create(opts) {
|
|
return SwitchRootContext.set(new SwitchRootState(opts));
|
|
}
|
|
opts;
|
|
attachment;
|
|
constructor(opts) {
|
|
this.opts = opts;
|
|
this.attachment = attachRef(opts.ref);
|
|
this.onkeydown = this.onkeydown.bind(this);
|
|
this.onclick = this.onclick.bind(this);
|
|
}
|
|
#toggle() {
|
|
this.opts.checked.current = !this.opts.checked.current;
|
|
}
|
|
onkeydown(e) {
|
|
if (!(e.key === ENTER || e.key === SPACE) || this.opts.disabled.current) return;
|
|
e.preventDefault();
|
|
this.#toggle();
|
|
}
|
|
onclick(_) {
|
|
if (this.opts.disabled.current) return;
|
|
this.#toggle();
|
|
}
|
|
#sharedProps = derived(() => ({
|
|
"data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),
|
|
"data-state": getDataChecked(this.opts.checked.current),
|
|
"data-required": boolToEmptyStrOrUndef(this.opts.required.current)
|
|
}));
|
|
get sharedProps() {
|
|
return this.#sharedProps();
|
|
}
|
|
set sharedProps($$value) {
|
|
return this.#sharedProps($$value);
|
|
}
|
|
#snippetProps = derived(() => ({ checked: this.opts.checked.current }));
|
|
get snippetProps() {
|
|
return this.#snippetProps();
|
|
}
|
|
set snippetProps($$value) {
|
|
return this.#snippetProps($$value);
|
|
}
|
|
#props = derived(() => ({
|
|
...this.sharedProps,
|
|
id: this.opts.id.current,
|
|
role: "switch",
|
|
disabled: boolToTrueOrUndef(this.opts.disabled.current),
|
|
"aria-checked": getAriaChecked(this.opts.checked.current),
|
|
"aria-required": boolToStr(this.opts.required.current),
|
|
[switchAttrs.root]: "",
|
|
onclick: this.onclick,
|
|
onkeydown: this.onkeydown,
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
class SwitchInputState {
|
|
static create() {
|
|
return new SwitchInputState(SwitchRootContext.get());
|
|
}
|
|
root;
|
|
#shouldRender = derived(() => this.root.opts.name.current !== void 0);
|
|
get shouldRender() {
|
|
return this.#shouldRender();
|
|
}
|
|
set shouldRender($$value) {
|
|
return this.#shouldRender($$value);
|
|
}
|
|
constructor(root) {
|
|
this.root = root;
|
|
}
|
|
#props = derived(() => ({
|
|
type: "checkbox",
|
|
name: this.root.opts.name.current,
|
|
value: this.root.opts.value.current,
|
|
checked: this.root.opts.checked.current,
|
|
disabled: this.root.opts.disabled.current,
|
|
required: this.root.opts.required.current
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
class SwitchThumbState {
|
|
static create(opts) {
|
|
return new SwitchThumbState(opts, SwitchRootContext.get());
|
|
}
|
|
opts;
|
|
root;
|
|
attachment;
|
|
constructor(opts, root) {
|
|
this.opts = opts;
|
|
this.root = root;
|
|
this.attachment = attachRef(opts.ref);
|
|
}
|
|
#snippetProps = derived(() => ({ checked: this.root.opts.checked.current }));
|
|
get snippetProps() {
|
|
return this.#snippetProps();
|
|
}
|
|
set snippetProps($$value) {
|
|
return this.#snippetProps($$value);
|
|
}
|
|
#props = derived(() => ({
|
|
...this.root.sharedProps,
|
|
id: this.opts.id.current,
|
|
[switchAttrs.thumb]: "",
|
|
...this.attachment
|
|
}));
|
|
get props() {
|
|
return this.#props();
|
|
}
|
|
set props($$value) {
|
|
return this.#props($$value);
|
|
}
|
|
}
|
|
function Switch_input($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const inputState = SwitchInputState.create();
|
|
if (inputState.shouldRender) {
|
|
$$renderer2.push("<!--[-->");
|
|
Hidden_input($$renderer2, spread_props([inputState.props]));
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
});
|
|
}
|
|
function Switch$1($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
child,
|
|
children,
|
|
ref = null,
|
|
id = createId(uid),
|
|
disabled = false,
|
|
required = false,
|
|
checked = false,
|
|
value = "on",
|
|
name = void 0,
|
|
type = "button",
|
|
onCheckedChange = noop,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const rootState = SwitchRootState.create({
|
|
checked: boxWith(() => checked, (v) => {
|
|
checked = v;
|
|
onCheckedChange?.(v);
|
|
}),
|
|
disabled: boxWith(() => disabled ?? false),
|
|
required: boxWith(() => required),
|
|
value: boxWith(() => value),
|
|
name: boxWith(() => name),
|
|
id: boxWith(() => id),
|
|
ref: boxWith(() => ref, (v) => ref = v)
|
|
});
|
|
const mergedProps = mergeProps(restProps, rootState.props, { type });
|
|
if (child) {
|
|
$$renderer2.push("<!--[-->");
|
|
child($$renderer2, { props: mergedProps, ...rootState.snippetProps });
|
|
$$renderer2.push(`<!---->`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
$$renderer2.push(`<button${attributes({ ...mergedProps })}>`);
|
|
children?.($$renderer2, rootState.snippetProps);
|
|
$$renderer2.push(`<!----></button>`);
|
|
}
|
|
$$renderer2.push(`<!--]--> `);
|
|
Switch_input($$renderer2);
|
|
$$renderer2.push(`<!---->`);
|
|
bind_props($$props, { ref, checked });
|
|
});
|
|
}
|
|
function Switch_thumb($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
const uid = props_id($$renderer2);
|
|
let {
|
|
child,
|
|
children,
|
|
ref = null,
|
|
id = createId(uid),
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
const thumbState = SwitchThumbState.create({
|
|
id: boxWith(() => id),
|
|
ref: boxWith(() => ref, (v) => ref = v)
|
|
});
|
|
const mergedProps = mergeProps(restProps, thumbState.props);
|
|
if (child) {
|
|
$$renderer2.push("<!--[-->");
|
|
child($$renderer2, { props: mergedProps, ...thumbState.snippetProps });
|
|
$$renderer2.push(`<!---->`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
$$renderer2.push(`<span${attributes({ ...mergedProps })}>`);
|
|
children?.($$renderer2, thumbState.snippetProps);
|
|
$$renderer2.push(`<!----></span>`);
|
|
}
|
|
$$renderer2.push(`<!--]-->`);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Avatar($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
ref = null,
|
|
loadingStatus = "loading",
|
|
class: className,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
$$renderer3.push("<!---->");
|
|
Avatar$1?.($$renderer3, spread_props([
|
|
{
|
|
"data-slot": "avatar",
|
|
class: cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)
|
|
},
|
|
restProps,
|
|
{
|
|
get ref() {
|
|
return ref;
|
|
},
|
|
set ref($$value) {
|
|
ref = $$value;
|
|
$$settled = false;
|
|
},
|
|
get loadingStatus() {
|
|
return loadingStatus;
|
|
},
|
|
set loadingStatus($$value) {
|
|
loadingStatus = $$value;
|
|
$$settled = false;
|
|
}
|
|
}
|
|
]));
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { ref, loadingStatus });
|
|
});
|
|
}
|
|
function Avatar_fallback($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
ref = null,
|
|
class: className,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
$$renderer3.push("<!---->");
|
|
Avatar_fallback$1?.($$renderer3, spread_props([
|
|
{
|
|
"data-slot": "avatar-fallback",
|
|
class: cn("bg-muted flex size-full items-center justify-center rounded-full", className)
|
|
},
|
|
restProps,
|
|
{
|
|
get ref() {
|
|
return ref;
|
|
},
|
|
set ref($$value) {
|
|
ref = $$value;
|
|
$$settled = false;
|
|
}
|
|
}
|
|
]));
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Dropdown_menu($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { open = false, $$slots, $$events, ...restProps } = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
$$renderer3.push("<!---->");
|
|
Menu?.($$renderer3, spread_props([
|
|
restProps,
|
|
{
|
|
get open() {
|
|
return open;
|
|
},
|
|
set open($$value) {
|
|
open = $$value;
|
|
$$settled = false;
|
|
}
|
|
}
|
|
]));
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { open });
|
|
});
|
|
}
|
|
function Dropdown_menu_portal($$renderer, $$props) {
|
|
let { $$slots, $$events, ...restProps } = $$props;
|
|
$$renderer.push("<!---->");
|
|
Portal?.($$renderer, spread_props([restProps]));
|
|
$$renderer.push(`<!---->`);
|
|
}
|
|
function Dropdown_menu_content($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
ref = null,
|
|
sideOffset = 4,
|
|
portalProps,
|
|
class: className,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
Dropdown_menu_portal($$renderer3, spread_props([
|
|
portalProps,
|
|
{
|
|
children: ($$renderer4) => {
|
|
$$renderer4.push("<!---->");
|
|
Dropdown_menu_content$1?.($$renderer4, spread_props([
|
|
{
|
|
"data-slot": "dropdown-menu-content",
|
|
sideOffset,
|
|
class: cn("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--bits-dropdown-menu-content-available-height) min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md outline-none", className)
|
|
},
|
|
restProps,
|
|
{
|
|
get ref() {
|
|
return ref;
|
|
},
|
|
set ref($$value) {
|
|
ref = $$value;
|
|
$$settled = false;
|
|
}
|
|
}
|
|
]));
|
|
$$renderer4.push(`<!---->`);
|
|
},
|
|
$$slots: { default: true }
|
|
}
|
|
]));
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Dropdown_menu_separator($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
ref = null,
|
|
class: className,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
$$renderer3.push("<!---->");
|
|
Menu_separator?.($$renderer3, spread_props([
|
|
{
|
|
"data-slot": "dropdown-menu-separator",
|
|
class: cn("bg-border -mx-1 my-1 h-px", className)
|
|
},
|
|
restProps,
|
|
{
|
|
get ref() {
|
|
return ref;
|
|
},
|
|
set ref($$value) {
|
|
ref = $$value;
|
|
$$settled = false;
|
|
}
|
|
}
|
|
]));
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Dropdown_menu_trigger($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { ref = null, $$slots, $$events, ...restProps } = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
$$renderer3.push("<!---->");
|
|
Menu_trigger?.($$renderer3, spread_props([
|
|
{ "data-slot": "dropdown-menu-trigger" },
|
|
restProps,
|
|
{
|
|
get ref() {
|
|
return ref;
|
|
},
|
|
set ref($$value) {
|
|
ref = $$value;
|
|
$$settled = false;
|
|
}
|
|
}
|
|
]));
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { ref });
|
|
});
|
|
}
|
|
function Switch($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let {
|
|
ref = null,
|
|
class: className,
|
|
checked = false,
|
|
$$slots,
|
|
$$events,
|
|
...restProps
|
|
} = $$props;
|
|
let $$settled = true;
|
|
let $$inner_renderer;
|
|
function $$render_inner($$renderer3) {
|
|
$$renderer3.push("<!---->");
|
|
Switch$1?.($$renderer3, spread_props([
|
|
{
|
|
"data-slot": "switch",
|
|
class: cn("data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className)
|
|
},
|
|
restProps,
|
|
{
|
|
get ref() {
|
|
return ref;
|
|
},
|
|
set ref($$value) {
|
|
ref = $$value;
|
|
$$settled = false;
|
|
},
|
|
get checked() {
|
|
return checked;
|
|
},
|
|
set checked($$value) {
|
|
checked = $$value;
|
|
$$settled = false;
|
|
},
|
|
children: ($$renderer4) => {
|
|
$$renderer4.push("<!---->");
|
|
Switch_thumb?.($$renderer4, {
|
|
"data-slot": "switch-thumb",
|
|
class: cn("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")
|
|
});
|
|
$$renderer4.push(`<!---->`);
|
|
},
|
|
$$slots: { default: true }
|
|
}
|
|
]));
|
|
$$renderer3.push(`<!---->`);
|
|
}
|
|
do {
|
|
$$settled = true;
|
|
$$inner_renderer = $$renderer2.copy();
|
|
$$render_inner($$inner_renderer);
|
|
} while (!$$settled);
|
|
$$renderer2.subsume($$inner_renderer);
|
|
bind_props($$props, { ref, checked });
|
|
});
|
|
}
|
|
function _layout($$renderer, $$props) {
|
|
$$renderer.component(($$renderer2) => {
|
|
let { data, children } = $$props;
|
|
let darkmode = false;
|
|
let onDarkmodeChange = () => {
|
|
};
|
|
$$renderer2.push(`<div class="app-shell">`);
|
|
if (data.user) {
|
|
$$renderer2.push("<!--[-->");
|
|
$$renderer2.push(`<header class="flex items-center justify-between w-full"><h1 class="text-lg font-semibold">Base</h1> `);
|
|
$$renderer2.push("<!---->");
|
|
Dropdown_menu?.($$renderer2, {
|
|
children: ($$renderer3) => {
|
|
$$renderer3.push("<!---->");
|
|
Dropdown_menu_trigger?.($$renderer3, {
|
|
class: "rounded-full border border-input bg-background hover:bg-accent",
|
|
onclick: (e) => e.preventDefault(),
|
|
children: ($$renderer4) => {
|
|
$$renderer4.push("<!---->");
|
|
Avatar?.($$renderer4, {
|
|
class: "h-8 w-8",
|
|
children: ($$renderer5) => {
|
|
$$renderer5.push("<!---->");
|
|
Avatar_fallback?.($$renderer5, {
|
|
class: "text-xs",
|
|
children: ($$renderer6) => {
|
|
$$renderer6.push(`<!---->${escape_html(data.user.name ? String(data.user.name).slice(0, 2).toUpperCase() : data.user.email ? String(data.user.email).slice(0, 2).toUpperCase() : "?")}`);
|
|
},
|
|
$$slots: { default: true }
|
|
});
|
|
$$renderer5.push(`<!---->`);
|
|
},
|
|
$$slots: { default: true }
|
|
});
|
|
$$renderer4.push(`<!---->`);
|
|
},
|
|
$$slots: { default: true }
|
|
});
|
|
$$renderer3.push(`<!----> `);
|
|
$$renderer3.push("<!---->");
|
|
Dropdown_menu_content?.($$renderer3, {
|
|
align: "end",
|
|
class: "w-48",
|
|
children: ($$renderer4) => {
|
|
$$renderer4.push(`<div class="relative flex cursor-default items-center gap-2 rounded-sm py-1.5 px-2 text-sm outline-none select-none" role="button" tabindex="0">`);
|
|
Label($$renderer4, {
|
|
for: "darkmode-switch",
|
|
class: "flex-1 cursor-pointer",
|
|
children: ($$renderer5) => {
|
|
$$renderer5.push(`<!---->Dark mode`);
|
|
},
|
|
$$slots: { default: true }
|
|
});
|
|
$$renderer4.push(`<!----> `);
|
|
Switch($$renderer4, {
|
|
id: "darkmode-switch",
|
|
checked: darkmode,
|
|
onCheckedChange: (v) => v !== void 0 && onDarkmodeChange()
|
|
});
|
|
$$renderer4.push(`<!----></div> `);
|
|
$$renderer4.push("<!---->");
|
|
Dropdown_menu_separator?.($$renderer4, {});
|
|
$$renderer4.push(`<!----> <form method="POST" action="/logout" class="contents"><button type="submit" class="relative flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50">Log out</button></form>`);
|
|
},
|
|
$$slots: { default: true }
|
|
});
|
|
$$renderer3.push(`<!---->`);
|
|
},
|
|
$$slots: { default: true }
|
|
});
|
|
$$renderer2.push(`<!----></header>`);
|
|
} else {
|
|
$$renderer2.push("<!--[!-->");
|
|
}
|
|
$$renderer2.push(`<!--]--> <main class="flex-1 flex flex-col gap-4 min-h-0">`);
|
|
children?.($$renderer2);
|
|
$$renderer2.push(`<!----></main></div>`);
|
|
});
|
|
}
|
|
|
|
export { _layout as default };
|
|
//# sourceMappingURL=_layout.svelte-C9R7TwNZ.js.map
|