@floating-ui/react
Version:
Floating UI for React
1,433 lines (1,386 loc) • 227 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom'), require('@floating-ui/react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom', '@floating-ui/react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FloatingUIReact = {}, global.React, global.ReactDOM, global.FloatingUIReactDOM));
})(this, (function (exports, React, ReactDOM, reactDom) { 'use strict';
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM);
/**
* Merges an array of refs into a single memoized callback ref or `null`.
* @see https://floating-ui.com/docs/react-utils#usemergerefs
*/
function useMergeRefs(refs) {
const cleanupRef = React__namespace.useRef(undefined);
const refEffect = React__namespace.useCallback(instance => {
const cleanups = refs.map(ref => {
if (ref == null) {
return;
}
if (typeof ref === 'function') {
const refCallback = ref;
const refCleanup = refCallback(instance);
return typeof refCleanup === 'function' ? refCleanup : () => {
refCallback(null);
};
}
ref.current = instance;
return () => {
ref.current = null;
};
});
return () => {
cleanups.forEach(refCleanup => refCleanup == null ? void 0 : refCleanup());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, refs);
return React__namespace.useMemo(() => {
if (refs.every(ref => ref == null)) {
return null;
}
return value => {
if (cleanupRef.current) {
cleanupRef.current();
cleanupRef.current = undefined;
}
if (value != null) {
cleanupRef.current = refEffect(value);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, refs);
}
// https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
const SafeReact = {
...React__namespace
};
const useInsertionEffect = SafeReact.useInsertionEffect;
const useSafeInsertionEffect = useInsertionEffect || (fn => fn());
function useEffectEvent(callback) {
const ref = React__namespace.useRef(() => {
{
throw new Error('Cannot call an event handler while rendering.');
}
});
useSafeInsertionEffect(() => {
ref.current = callback;
});
return React__namespace.useCallback(function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return ref.current == null ? void 0 : ref.current(...args);
}, []);
}
function hasWindow() {
return typeof window !== 'undefined';
}
function getNodeName(node) {
if (isNode(node)) {
return (node.nodeName || '').toLowerCase();
}
// Mocked nodes in testing environments may not be instances of Node. By
// returning `#document` an infinite loop won't occur.
// https://github.com/floating-ui/floating-ui/issues/2317
return '#document';
}
function getWindow(node) {
var _node$ownerDocument;
return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
}
function getDocumentElement(node) {
var _ref;
return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement;
}
function isNode(value) {
if (!hasWindow()) {
return false;
}
return value instanceof Node || value instanceof getWindow(value).Node;
}
function isElement(value) {
if (!hasWindow()) {
return false;
}
return value instanceof Element || value instanceof getWindow(value).Element;
}
function isHTMLElement(value) {
if (!hasWindow()) {
return false;
}
return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement;
}
function isShadowRoot(value) {
if (!hasWindow() || typeof ShadowRoot === 'undefined') {
return false;
}
return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot;
}
function isWebKit() {
if (typeof CSS === 'undefined' || !CSS.supports) return false;
return CSS.supports('-webkit-backdrop-filter', 'none');
}
function isLastTraversableNode(node) {
return ['html', 'body', '#document'].includes(getNodeName(node));
}
function getComputedStyle$1(element) {
return getWindow(element).getComputedStyle(element);
}
function getParentNode(node) {
if (getNodeName(node) === 'html') {
return node;
}
const result =
// Step into the shadow DOM of the parent of a slotted node.
node.assignedSlot ||
// DOM Element detected.
node.parentNode ||
// ShadowRoot detected.
isShadowRoot(node) && node.host ||
// Fallback.
getDocumentElement(node);
return isShadowRoot(result) ? result.host : result;
}
function activeElement(doc) {
let activeElement = doc.activeElement;
while (((_activeElement = activeElement) == null || (_activeElement = _activeElement.shadowRoot) == null ? void 0 : _activeElement.activeElement) != null) {
var _activeElement;
activeElement = activeElement.shadowRoot.activeElement;
}
return activeElement;
}
function contains(parent, child) {
if (!parent || !child) {
return false;
}
const rootNode = child.getRootNode == null ? void 0 : child.getRootNode();
// First, attempt with faster native method
if (parent.contains(child)) {
return true;
}
// then fallback to custom implementation with Shadow DOM support
if (rootNode && isShadowRoot(rootNode)) {
let next = child;
while (next) {
if (parent === next) {
return true;
}
// @ts-ignore
next = next.parentNode || next.host;
}
}
// Give up, the result is false
return false;
}
// Avoid Chrome DevTools blue warning.
function getPlatform() {
const uaData = navigator.userAgentData;
if (uaData != null && uaData.platform) {
return uaData.platform;
}
return navigator.platform;
}
function getUserAgent$1() {
const uaData = navigator.userAgentData;
if (uaData && Array.isArray(uaData.brands)) {
return uaData.brands.map(_ref => {
let {
brand,
version
} = _ref;
return brand + "/" + version;
}).join(' ');
}
return navigator.userAgent;
}
// License: https://github.com/adobe/react-spectrum/blob/b35d5c02fe900badccd0cf1a8f23bb593419f238/packages/@react-aria/utils/src/isVirtualEvent.ts
function isVirtualClick(event) {
// FIXME: Firefox is now emitting a deprecation warning for `mozInputSource`.
// Try to find a workaround for this. `react-aria` source still has the check.
if (event.mozInputSource === 0 && event.isTrusted) {
return true;
}
if (isAndroid() && event.pointerType) {
return event.type === 'click' && event.buttons === 1;
}
return event.detail === 0 && !event.pointerType;
}
function isVirtualPointerEvent(event) {
if (isJSDOM$1()) return false;
return !isAndroid() && event.width === 0 && event.height === 0 || isAndroid() && event.width === 1 && event.height === 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'mouse' ||
// iOS VoiceOver returns 0.333• for width/height.
event.width < 1 && event.height < 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'touch';
}
function isSafari() {
// Chrome DevTools does not complain about navigator.vendor
return /apple/i.test(navigator.vendor);
}
function isAndroid() {
const re = /android/i;
return re.test(getPlatform()) || re.test(getUserAgent$1());
}
function isMac() {
return getPlatform().toLowerCase().startsWith('mac') && !navigator.maxTouchPoints;
}
function isJSDOM$1() {
return getUserAgent$1().includes('jsdom/');
}
function isMouseLikePointerType(pointerType, strict) {
// On some Linux machines with Chromium, mouse inputs return a `pointerType`
// of "pen": https://github.com/floating-ui/floating-ui/issues/2015
const values = ['mouse', 'pen'];
if (!strict) {
values.push('', undefined);
}
return values.includes(pointerType);
}
function isReactEvent(event) {
return 'nativeEvent' in event;
}
function isRootElement(element) {
return element.matches('html,body');
}
function getDocument(node) {
return (node == null ? void 0 : node.ownerDocument) || document;
}
function isEventTargetWithin(event, node) {
if (node == null) {
return false;
}
if ('composedPath' in event) {
return event.composedPath().includes(node);
}
// TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't
const e = event;
return e.target != null && node.contains(e.target);
}
function getTarget(event) {
if ('composedPath' in event) {
return event.composedPath()[0];
}
// TS thinks `event` is of type never as it assumes all browsers support
// `composedPath()`, but browsers without shadow DOM don't.
return event.target;
}
const TYPEABLE_SELECTOR = "input:not([type='hidden']):not([disabled])," + "[contenteditable]:not([contenteditable='false']),textarea:not([disabled])";
function isTypeableElement(element) {
return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
}
function stopEvent(event) {
event.preventDefault();
event.stopPropagation();
}
function isTypeableCombobox(element) {
if (!element) return false;
return element.getAttribute('role') === 'combobox' && isTypeableElement(element);
}
/**
* Custom positioning reference element.
* @see https://floating-ui.com/docs/virtual-elements
*/
const min = Math.min;
const max = Math.max;
const round = Math.round;
const floor = Math.floor;
function evaluate(value, param) {
return typeof value === 'function' ? value(param) : value;
}
const ARROW_UP = 'ArrowUp';
const ARROW_DOWN = 'ArrowDown';
const ARROW_LEFT = 'ArrowLeft';
const ARROW_RIGHT = 'ArrowRight';
function isDifferentRow(index, cols, prevRow) {
return Math.floor(index / cols) !== prevRow;
}
function isIndexOutOfBounds(listRef, index) {
return index < 0 || index >= listRef.current.length;
}
function getMinIndex(listRef, disabledIndices) {
return findNonDisabledIndex(listRef, {
disabledIndices
});
}
function getMaxIndex(listRef, disabledIndices) {
return findNonDisabledIndex(listRef, {
decrement: true,
startingIndex: listRef.current.length,
disabledIndices
});
}
function findNonDisabledIndex(listRef, _temp) {
let {
startingIndex = -1,
decrement = false,
disabledIndices,
amount = 1
} = _temp === void 0 ? {} : _temp;
const list = listRef.current;
let index = startingIndex;
do {
index += decrement ? -amount : amount;
} while (index >= 0 && index <= list.length - 1 && isDisabled(list, index, disabledIndices));
return index;
}
function getGridNavigatedIndex(elementsRef, _ref) {
let {
event,
orientation,
loop,
rtl,
cols,
disabledIndices,
minIndex,
maxIndex,
prevIndex,
stopEvent: stop = false
} = _ref;
let nextIndex = prevIndex;
if (event.key === ARROW_UP) {
stop && stopEvent(event);
if (prevIndex === -1) {
nextIndex = maxIndex;
} else {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: nextIndex,
amount: cols,
decrement: true,
disabledIndices
});
if (loop && (prevIndex - cols < minIndex || nextIndex < 0)) {
const col = prevIndex % cols;
const maxCol = maxIndex % cols;
const offset = maxIndex - (maxCol - col);
if (maxCol === col) {
nextIndex = maxIndex;
} else {
nextIndex = maxCol > col ? offset : offset - cols;
}
}
}
if (isIndexOutOfBounds(elementsRef, nextIndex)) {
nextIndex = prevIndex;
}
}
if (event.key === ARROW_DOWN) {
stop && stopEvent(event);
if (prevIndex === -1) {
nextIndex = minIndex;
} else {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex,
amount: cols,
disabledIndices
});
if (loop && prevIndex + cols > maxIndex) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex % cols - cols,
amount: cols,
disabledIndices
});
}
}
if (isIndexOutOfBounds(elementsRef, nextIndex)) {
nextIndex = prevIndex;
}
}
// Remains on the same row/column.
if (orientation === 'both') {
const prevRow = floor(prevIndex / cols);
if (event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)) {
stop && stopEvent(event);
if (prevIndex % cols !== cols - 1) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex,
disabledIndices
});
if (loop && isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex - prevIndex % cols - 1,
disabledIndices
});
}
} else if (loop) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex - prevIndex % cols - 1,
disabledIndices
});
}
if (isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = prevIndex;
}
}
if (event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT)) {
stop && stopEvent(event);
if (prevIndex % cols !== 0) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex,
decrement: true,
disabledIndices
});
if (loop && isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex + (cols - prevIndex % cols),
decrement: true,
disabledIndices
});
}
} else if (loop) {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex + (cols - prevIndex % cols),
decrement: true,
disabledIndices
});
}
if (isDifferentRow(nextIndex, cols, prevRow)) {
nextIndex = prevIndex;
}
}
const lastRow = floor(maxIndex / cols) === prevRow;
if (isIndexOutOfBounds(elementsRef, nextIndex)) {
if (loop && lastRow) {
nextIndex = event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT) ? maxIndex : findNonDisabledIndex(elementsRef, {
startingIndex: prevIndex - prevIndex % cols - 1,
disabledIndices
});
} else {
nextIndex = prevIndex;
}
}
}
return nextIndex;
}
/** For each cell index, gets the item index that occupies that cell */
function buildCellMap(sizes, cols, dense) {
const cellMap = [];
let startIndex = 0;
sizes.forEach((_ref2, index) => {
let {
width,
height
} = _ref2;
if (width > cols) {
{
throw new Error("[Floating UI]: Invalid grid - item width at index " + index + " is greater than grid columns");
}
}
let itemPlaced = false;
if (dense) {
startIndex = 0;
}
while (!itemPlaced) {
const targetCells = [];
for (let i = 0; i < width; i++) {
for (let j = 0; j < height; j++) {
targetCells.push(startIndex + i + j * cols);
}
}
if (startIndex % cols + width <= cols && targetCells.every(cell => cellMap[cell] == null)) {
targetCells.forEach(cell => {
cellMap[cell] = index;
});
itemPlaced = true;
} else {
startIndex++;
}
}
});
// convert into a non-sparse array
return [...cellMap];
}
/** Gets cell index of an item's corner or -1 when index is -1. */
function getCellIndexOfCorner(index, sizes, cellMap, cols, corner) {
if (index === -1) return -1;
const firstCellIndex = cellMap.indexOf(index);
const sizeItem = sizes[index];
switch (corner) {
case 'tl':
return firstCellIndex;
case 'tr':
if (!sizeItem) {
return firstCellIndex;
}
return firstCellIndex + sizeItem.width - 1;
case 'bl':
if (!sizeItem) {
return firstCellIndex;
}
return firstCellIndex + (sizeItem.height - 1) * cols;
case 'br':
return cellMap.lastIndexOf(index);
}
}
/** Gets all cell indices that correspond to the specified indices */
function getCellIndices(indices, cellMap) {
return cellMap.flatMap((index, cellIndex) => indices.includes(index) ? [cellIndex] : []);
}
function isDisabled(list, index, disabledIndices) {
if (disabledIndices) {
return disabledIndices.includes(index);
}
const element = list[index];
return element == null || element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true';
}
var index = typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect;
function sortByDocumentPosition(a, b) {
const position = a.compareDocumentPosition(b);
if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
return -1;
}
if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
return 1;
}
return 0;
}
const FloatingListContext = /*#__PURE__*/React__namespace.createContext({
register: () => {},
unregister: () => {},
map: /*#__PURE__*/new Map(),
elementsRef: {
current: []
}
});
/**
* Provides context for a list of items within the floating element.
* @see https://floating-ui.com/docs/FloatingList
*/
function FloatingList(props) {
const {
children,
elementsRef,
labelsRef
} = props;
const [nodes, setNodes] = React__namespace.useState(() => new Set());
const register = React__namespace.useCallback(node => {
setNodes(prevSet => new Set(prevSet).add(node));
}, []);
const unregister = React__namespace.useCallback(node => {
setNodes(prevSet => {
const set = new Set(prevSet);
set.delete(node);
return set;
});
}, []);
const map = React__namespace.useMemo(() => {
const newMap = new Map();
const sortedNodes = Array.from(nodes.keys()).sort(sortByDocumentPosition);
sortedNodes.forEach((node, index) => {
newMap.set(node, index);
});
return newMap;
}, [nodes]);
return /*#__PURE__*/React__namespace.createElement(FloatingListContext.Provider, {
value: React__namespace.useMemo(() => ({
register,
unregister,
map,
elementsRef,
labelsRef
}), [register, unregister, map, elementsRef, labelsRef])
}, children);
}
/**
* Used to register a list item and its index (DOM position) in the
* `FloatingList`.
* @see https://floating-ui.com/docs/FloatingList#uselistitem
*/
function useListItem(props) {
if (props === void 0) {
props = {};
}
const {
label
} = props;
const {
register,
unregister,
map,
elementsRef,
labelsRef
} = React__namespace.useContext(FloatingListContext);
const [index$1, setIndex] = React__namespace.useState(null);
const componentRef = React__namespace.useRef(null);
const ref = React__namespace.useCallback(node => {
componentRef.current = node;
if (index$1 !== null) {
elementsRef.current[index$1] = node;
if (labelsRef) {
var _node$textContent;
const isLabelDefined = label !== undefined;
labelsRef.current[index$1] = isLabelDefined ? label : (_node$textContent = node == null ? void 0 : node.textContent) != null ? _node$textContent : null;
}
}
}, [index$1, elementsRef, labelsRef, label]);
index(() => {
const node = componentRef.current;
if (node) {
register(node);
return () => {
unregister(node);
};
}
}, [register, unregister]);
index(() => {
const index = componentRef.current ? map.get(componentRef.current) : null;
if (index != null) {
setIndex(index);
}
}, [map]);
return React__namespace.useMemo(() => ({
ref,
index: index$1 == null ? -1 : index$1
}), [index$1, ref]);
}
function renderJsx(render, computedProps) {
if (typeof render === 'function') {
return render(computedProps);
}
if (render) {
return /*#__PURE__*/React__namespace.cloneElement(render, computedProps);
}
return /*#__PURE__*/React__namespace.createElement("div", computedProps);
}
const CompositeContext = /*#__PURE__*/React__namespace.createContext({
activeIndex: 0,
onNavigate: () => {}
});
const horizontalKeys = [ARROW_LEFT, ARROW_RIGHT];
const verticalKeys = [ARROW_UP, ARROW_DOWN];
const allKeys = [...horizontalKeys, ...verticalKeys];
/**
* Creates a single tab stop whose items are navigated by arrow keys, which
* provides list navigation outside of floating element contexts.
*
* This is useful to enable navigation of a list of items that aren’t part of a
* floating element. A menubar is an example of a composite, with each reference
* element being an item.
* @see https://floating-ui.com/docs/Composite
*/
const Composite = /*#__PURE__*/React__namespace.forwardRef(function Composite(props, forwardedRef) {
const {
render,
orientation = 'both',
loop = true,
rtl = false,
cols = 1,
disabledIndices,
activeIndex: externalActiveIndex,
onNavigate: externalSetActiveIndex,
itemSizes,
dense = false,
...domProps
} = props;
const [internalActiveIndex, internalSetActiveIndex] = React__namespace.useState(0);
const activeIndex = externalActiveIndex != null ? externalActiveIndex : internalActiveIndex;
const onNavigate = useEffectEvent(externalSetActiveIndex != null ? externalSetActiveIndex : internalSetActiveIndex);
const elementsRef = React__namespace.useRef([]);
const renderElementProps = render && typeof render !== 'function' ? render.props : {};
const contextValue = React__namespace.useMemo(() => ({
activeIndex,
onNavigate
}), [activeIndex, onNavigate]);
const isGrid = cols > 1;
function handleKeyDown(event) {
if (!allKeys.includes(event.key)) return;
let nextIndex = activeIndex;
const minIndex = getMinIndex(elementsRef, disabledIndices);
const maxIndex = getMaxIndex(elementsRef, disabledIndices);
const horizontalEndKey = rtl ? ARROW_LEFT : ARROW_RIGHT;
const horizontalStartKey = rtl ? ARROW_RIGHT : ARROW_LEFT;
if (isGrid) {
const sizes = itemSizes || Array.from({
length: elementsRef.current.length
}, () => ({
width: 1,
height: 1
}));
// To calculate movements on the grid, we use hypothetical cell indices
// as if every item was 1x1, then convert back to real indices.
const cellMap = buildCellMap(sizes, cols, dense);
const minGridIndex = cellMap.findIndex(index => index != null && !isDisabled(elementsRef.current, index, disabledIndices));
// last enabled index
const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isDisabled(elementsRef.current, index, disabledIndices) ? cellIndex : foundIndex, -1);
const maybeNextIndex = cellMap[getGridNavigatedIndex({
current: cellMap.map(itemIndex => itemIndex ? elementsRef.current[itemIndex] : null)
}, {
event,
orientation,
loop,
rtl,
cols,
// treat undefined (empty grid spaces) as disabled indices so we
// don't end up in them
disabledIndices: getCellIndices([...(disabledIndices || elementsRef.current.map((_, index) => isDisabled(elementsRef.current, index) ? index : undefined)), undefined], cellMap),
minIndex: minGridIndex,
maxIndex: maxGridIndex,
prevIndex: getCellIndexOfCorner(activeIndex > maxIndex ? minIndex : activeIndex, sizes, cellMap, cols,
// use a corner matching the edge closest to the direction we're
// moving in so we don't end up in the same item. Prefer
// top/left over bottom/right.
event.key === ARROW_DOWN ? 'bl' : event.key === horizontalEndKey ? 'tr' : 'tl')
})];
if (maybeNextIndex != null) {
nextIndex = maybeNextIndex;
}
}
const toEndKeys = {
horizontal: [horizontalEndKey],
vertical: [ARROW_DOWN],
both: [horizontalEndKey, ARROW_DOWN]
}[orientation];
const toStartKeys = {
horizontal: [horizontalStartKey],
vertical: [ARROW_UP],
both: [horizontalStartKey, ARROW_UP]
}[orientation];
const preventedKeys = isGrid ? allKeys : {
horizontal: horizontalKeys,
vertical: verticalKeys,
both: allKeys
}[orientation];
if (nextIndex === activeIndex && [...toEndKeys, ...toStartKeys].includes(event.key)) {
if (loop && nextIndex === maxIndex && toEndKeys.includes(event.key)) {
nextIndex = minIndex;
} else if (loop && nextIndex === minIndex && toStartKeys.includes(event.key)) {
nextIndex = maxIndex;
} else {
nextIndex = findNonDisabledIndex(elementsRef, {
startingIndex: nextIndex,
decrement: toStartKeys.includes(event.key),
disabledIndices
});
}
}
if (nextIndex !== activeIndex && !isIndexOutOfBounds(elementsRef, nextIndex)) {
var _elementsRef$current$;
event.stopPropagation();
if (preventedKeys.includes(event.key)) {
event.preventDefault();
}
onNavigate(nextIndex);
(_elementsRef$current$ = elementsRef.current[nextIndex]) == null || _elementsRef$current$.focus();
}
}
const computedProps = {
...domProps,
...renderElementProps,
ref: forwardedRef,
'aria-orientation': orientation === 'both' ? undefined : orientation,
onKeyDown(e) {
domProps.onKeyDown == null || domProps.onKeyDown(e);
renderElementProps.onKeyDown == null || renderElementProps.onKeyDown(e);
handleKeyDown(e);
}
};
return /*#__PURE__*/React__namespace.createElement(CompositeContext.Provider, {
value: contextValue
}, /*#__PURE__*/React__namespace.createElement(FloatingList, {
elementsRef: elementsRef
}, renderJsx(render, computedProps)));
});
/**
* @see https://floating-ui.com/docs/Composite
*/
const CompositeItem = /*#__PURE__*/React__namespace.forwardRef(function CompositeItem(props, forwardedRef) {
const {
render,
...domProps
} = props;
const renderElementProps = render && typeof render !== 'function' ? render.props : {};
const {
activeIndex,
onNavigate
} = React__namespace.useContext(CompositeContext);
const {
ref,
index
} = useListItem();
const mergedRef = useMergeRefs([ref, forwardedRef, renderElementProps.ref]);
const isActive = activeIndex === index;
const computedProps = {
...domProps,
...renderElementProps,
ref: mergedRef,
tabIndex: isActive ? 0 : -1,
'data-active': isActive ? '' : undefined,
onFocus(e) {
domProps.onFocus == null || domProps.onFocus(e);
renderElementProps.onFocus == null || renderElementProps.onFocus(e);
onNavigate(index);
}
};
return renderJsx(render, computedProps);
});
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
let serverHandoffComplete = false;
let count = 0;
const genId = () => // Ensure the id is unique with multiple independent versions of Floating UI
// on <React 18
"floating-ui-" + Math.random().toString(36).slice(2, 6) + count++;
function useFloatingId() {
const [id, setId] = React__namespace.useState(() => serverHandoffComplete ? genId() : undefined);
index(() => {
if (id == null) {
setId(genId());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React__namespace.useEffect(() => {
serverHandoffComplete = true;
}, []);
return id;
}
const useReactId = SafeReact.useId;
/**
* Uses React 18's built-in `useId()` when available, or falls back to a
* slightly less performant (requiring a double render) implementation for
* earlier React versions.
* @see https://floating-ui.com/docs/react-utils#useid
*/
const useId = useReactId || useFloatingId;
let devMessageSet;
{
devMessageSet = /*#__PURE__*/new Set();
}
function warn() {
var _devMessageSet;
for (var _len = arguments.length, messages = new Array(_len), _key = 0; _key < _len; _key++) {
messages[_key] = arguments[_key];
}
const message = "Floating UI: " + messages.join(' ');
if (!((_devMessageSet = devMessageSet) != null && _devMessageSet.has(message))) {
var _devMessageSet2;
(_devMessageSet2 = devMessageSet) == null || _devMessageSet2.add(message);
console.warn(message);
}
}
function error() {
var _devMessageSet3;
for (var _len2 = arguments.length, messages = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
messages[_key2] = arguments[_key2];
}
const message = "Floating UI: " + messages.join(' ');
if (!((_devMessageSet3 = devMessageSet) != null && _devMessageSet3.has(message))) {
var _devMessageSet4;
(_devMessageSet4 = devMessageSet) == null || _devMessageSet4.add(message);
console.error(message);
}
}
/**
* Renders a pointing arrow triangle.
* @see https://floating-ui.com/docs/FloatingArrow
*/
const FloatingArrow = /*#__PURE__*/React__namespace.forwardRef(function FloatingArrow(props, ref) {
const {
context: {
placement,
elements: {
floating
},
middlewareData: {
arrow,
shift
}
},
width = 14,
height = 7,
tipRadius = 0,
strokeWidth = 0,
staticOffset,
stroke,
d,
style: {
transform,
...restStyle
} = {},
...rest
} = props;
{
if (!ref) {
warn('The `ref` prop is required for `FloatingArrow`.');
}
}
const clipPathId = useId();
const [isRTL, setIsRTL] = React__namespace.useState(false);
// https://github.com/floating-ui/floating-ui/issues/2932
index(() => {
if (!floating) return;
const isRTL = getComputedStyle$1(floating).direction === 'rtl';
if (isRTL) {
setIsRTL(true);
}
}, [floating]);
if (!floating) {
return null;
}
const [side, alignment] = placement.split('-');
const isVerticalSide = side === 'top' || side === 'bottom';
let computedStaticOffset = staticOffset;
if (isVerticalSide && shift != null && shift.x || !isVerticalSide && shift != null && shift.y) {
computedStaticOffset = null;
}
// Strokes must be double the border width, this ensures the stroke's width
// works as you'd expect.
const computedStrokeWidth = strokeWidth * 2;
const halfStrokeWidth = computedStrokeWidth / 2;
const svgX = width / 2 * (tipRadius / -8 + 1);
const svgY = height / 2 * tipRadius / 4;
const isCustomShape = !!d;
const yOffsetProp = computedStaticOffset && alignment === 'end' ? 'bottom' : 'top';
let xOffsetProp = computedStaticOffset && alignment === 'end' ? 'right' : 'left';
if (computedStaticOffset && isRTL) {
xOffsetProp = alignment === 'end' ? 'left' : 'right';
}
const arrowX = (arrow == null ? void 0 : arrow.x) != null ? computedStaticOffset || arrow.x : '';
const arrowY = (arrow == null ? void 0 : arrow.y) != null ? computedStaticOffset || arrow.y : '';
const dValue = d || 'M0,0' + (" H" + width) + (" L" + (width - svgX) + "," + (height - svgY)) + (" Q" + width / 2 + "," + height + " " + svgX + "," + (height - svgY)) + ' Z';
const rotation = {
top: isCustomShape ? 'rotate(180deg)' : '',
left: isCustomShape ? 'rotate(90deg)' : 'rotate(-90deg)',
bottom: isCustomShape ? '' : 'rotate(180deg)',
right: isCustomShape ? 'rotate(-90deg)' : 'rotate(90deg)'
}[side];
return /*#__PURE__*/React__namespace.createElement("svg", _extends({}, rest, {
"aria-hidden": true,
ref: ref,
width: isCustomShape ? width : width + computedStrokeWidth,
height: width,
viewBox: "0 0 " + width + " " + (height > width ? height : width),
style: {
position: 'absolute',
pointerEvents: 'none',
[xOffsetProp]: arrowX,
[yOffsetProp]: arrowY,
[side]: isVerticalSide || isCustomShape ? '100%' : "calc(100% - " + computedStrokeWidth / 2 + "px)",
transform: [rotation, transform].filter(t => !!t).join(' '),
...restStyle
}
}), computedStrokeWidth > 0 && /*#__PURE__*/React__namespace.createElement("path", {
clipPath: "url(#" + clipPathId + ")",
fill: "none",
stroke: stroke
// Account for the stroke on the fill path rendered below.
,
strokeWidth: computedStrokeWidth + (d ? 0 : 1),
d: dValue
}), /*#__PURE__*/React__namespace.createElement("path", {
stroke: computedStrokeWidth && !d ? rest.fill : 'none',
d: dValue
}), /*#__PURE__*/React__namespace.createElement("clipPath", {
id: clipPathId
}, /*#__PURE__*/React__namespace.createElement("rect", {
x: -halfStrokeWidth,
y: halfStrokeWidth * (isCustomShape ? -1 : 1),
width: width + computedStrokeWidth,
height: width
})));
});
function createEventEmitter() {
const map = new Map();
return {
emit(event, data) {
var _map$get;
(_map$get = map.get(event)) == null || _map$get.forEach(listener => listener(data));
},
on(event, listener) {
if (!map.has(event)) {
map.set(event, new Set());
}
map.get(event).add(listener);
},
off(event, listener) {
var _map$get2;
(_map$get2 = map.get(event)) == null || _map$get2.delete(listener);
}
};
}
const FloatingNodeContext = /*#__PURE__*/React__namespace.createContext(null);
const FloatingTreeContext = /*#__PURE__*/React__namespace.createContext(null);
/**
* Returns the parent node id for nested floating elements, if available.
* Returns `null` for top-level floating elements.
*/
const useFloatingParentNodeId = () => {
var _React$useContext;
return ((_React$useContext = React__namespace.useContext(FloatingNodeContext)) == null ? void 0 : _React$useContext.id) || null;
};
/**
* Returns the nearest floating tree context, if available.
*/
const useFloatingTree = () => React__namespace.useContext(FloatingTreeContext);
/**
* Registers a node into the `FloatingTree`, returning its id.
* @see https://floating-ui.com/docs/FloatingTree
*/
function useFloatingNodeId(customParentId) {
const id = useId();
const tree = useFloatingTree();
const reactParentId = useFloatingParentNodeId();
const parentId = customParentId || reactParentId;
index(() => {
if (!id) return;
const node = {
id,
parentId
};
tree == null || tree.addNode(node);
return () => {
tree == null || tree.removeNode(node);
};
}, [tree, id, parentId]);
return id;
}
/**
* Provides parent node context for nested floating elements.
* @see https://floating-ui.com/docs/FloatingTree
*/
function FloatingNode(props) {
const {
children,
id
} = props;
const parentId = useFloatingParentNodeId();
return /*#__PURE__*/React__namespace.createElement(FloatingNodeContext.Provider, {
value: React__namespace.useMemo(() => ({
id,
parentId
}), [id, parentId])
}, children);
}
/**
* Provides context for nested floating elements when they are not children of
* each other on the DOM.
* This is not necessary in all cases, except when there must be explicit communication between parent and child floating elements. It is necessary for:
* - The `bubbles` option in the `useDismiss()` Hook
* - Nested virtual list navigation
* - Nested floating elements that each open on hover
* - Custom communication between parent and child floating elements
* @see https://floating-ui.com/docs/FloatingTree
*/
function FloatingTree(props) {
const {
children
} = props;
const nodesRef = React__namespace.useRef([]);
const addNode = React__namespace.useCallback(node => {
nodesRef.current = [...nodesRef.current, node];
}, []);
const removeNode = React__namespace.useCallback(node => {
nodesRef.current = nodesRef.current.filter(n => n !== node);
}, []);
const [events] = React__namespace.useState(() => createEventEmitter());
return /*#__PURE__*/React__namespace.createElement(FloatingTreeContext.Provider, {
value: React__namespace.useMemo(() => ({
nodesRef,
addNode,
removeNode,
events
}), [addNode, removeNode, events])
}, children);
}
function createAttribute(name) {
return "data-floating-ui-" + name;
}
function clearTimeoutIfSet(timeoutRef) {
if (timeoutRef.current !== -1) {
clearTimeout(timeoutRef.current);
timeoutRef.current = -1;
}
}
function useLatestRef(value) {
const ref = React.useRef(value);
index(() => {
ref.current = value;
});
return ref;
}
const safePolygonIdentifier = /*#__PURE__*/createAttribute('safe-polygon');
function getDelay(value, prop, pointerType) {
if (pointerType && !isMouseLikePointerType(pointerType)) {
return 0;
}
if (typeof value === 'number') {
return value;
}
if (typeof value === 'function') {
const result = value();
if (typeof result === 'number') {
return result;
}
return result == null ? void 0 : result[prop];
}
return value == null ? void 0 : value[prop];
}
function getRestMs(value) {
if (typeof value === 'function') {
return value();
}
return value;
}
/**
* Opens the floating element while hovering over the reference element, like
* CSS `:hover`.
* @see https://floating-ui.com/docs/useHover
*/
function useHover(context, props) {
if (props === void 0) {
props = {};
}
const {
open,
onOpenChange,
dataRef,
events,
elements
} = context;
const {
enabled = true,
delay = 0,
handleClose = null,
mouseOnly = false,
restMs = 0,
move = true
} = props;
const tree = useFloatingTree();
const parentId = useFloatingParentNodeId();
const handleCloseRef = useLatestRef(handleClose);
const delayRef = useLatestRef(delay);
const openRef = useLatestRef(open);
const restMsRef = useLatestRef(restMs);
const pointerTypeRef = React__namespace.useRef();
const timeoutRef = React__namespace.useRef(-1);
const handlerRef = React__namespace.useRef();
const restTimeoutRef = React__namespace.useRef(-1);
const blockMouseMoveRef = React__namespace.useRef(true);
const performedPointerEventsMutationRef = React__namespace.useRef(false);
const unbindMouseMoveRef = React__namespace.useRef(() => {});
const restTimeoutPendingRef = React__namespace.useRef(false);
const isHoverOpen = React__namespace.useCallback(() => {
var _dataRef$current$open;
const type = (_dataRef$current$open = dataRef.current.openEvent) == null ? void 0 : _dataRef$current$open.type;
return (type == null ? void 0 : type.includes('mouse')) && type !== 'mousedown';
}, [dataRef]);
// When closing before opening, clear the delay timeouts to cancel it
// from showing.
React__namespace.useEffect(() => {
if (!enabled) return;
function onOpenChange(_ref) {
let {
open
} = _ref;
if (!open) {
clearTimeoutIfSet(timeoutRef);
clearTimeoutIfSet(restTimeoutRef);
blockMouseMoveRef.current = true;
restTimeoutPendingRef.current = false;
}
}
events.on('openchange', onOpenChange);
return () => {
events.off('openchange', onOpenChange);
};
}, [enabled, events]);
React__namespace.useEffect(() => {
if (!enabled) return;
if (!handleCloseRef.current) return;
if (!open) return;
function onLeave(event) {
if (isHoverOpen()) {
onOpenChange(false, event, 'hover');
}
}
const html = getDocument(elements.floating).documentElement;
html.addEventListener('mouseleave', onLeave);
return () => {
html.removeEventListener('mouseleave', onLeave);
};
}, [elements.floating, open, onOpenChange, enabled, handleCloseRef, isHoverOpen]);
const closeWithDelay = React__namespace.useCallback(function (event, runElseBranch, reason) {
if (runElseBranch === void 0) {
runElseBranch = true;
}
if (reason === void 0) {
reason = 'hover';
}
const closeDelay = getDelay(delayRef.current, 'close', pointerTypeRef.current);
if (closeDelay && !handlerRef.current) {
clearTimeoutIfSet(timeoutRef);
timeoutRef.current = window.setTimeout(() => onOpenChange(false, event, reason), closeDelay);
} else if (runElseBranch) {
clearTimeoutIfSet(timeoutRef);
onOpenChange(false, event, reason);
}
}, [delayRef, onOpenChange]);
const cleanupMouseMoveHandler = useEffectEvent(() => {
unbindMouseMoveRef.current();
handlerRef.current = undefined;
});
const clearPointerEvents = useEffectEvent(() => {
if (performedPointerEventsMutationRef.current) {
const body = getDocument(elements.floating).body;
body.style.pointerEvents = '';
body.removeAttribute(safePolygonIdentifier);
performedPointerEventsMutationRef.current = false;
}
});
const isClickLikeOpenEvent = useEffectEvent(() => {
return dataRef.current.openEvent ? ['click', 'mousedown'].includes(dataRef.current.openEvent.type) : false;
});
// Registering the mouse events on the reference directly to bypass React's
// delegation system. If the cursor was on a disabled element and then entered
// the reference (no gap), `mouseenter` doesn't fire in the delegation system.
React__namespace.useEffect(() => {
if (!enabled) return;
function onReferenceMouseEnter(event) {
clearTimeoutIfSet(timeoutRef);
blockMouseMoveRef.current = false;
if (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current) || getRestMs(restMsRef.current) > 0 && !getDelay(delayRef.current, 'open')) {
return;
}
const openDelay = getDelay(delayRef.current, 'open', pointerTypeRef.current);
if (openDelay) {
timeoutRef.current = window.setTimeout(() => {
if (!openRef.current) {
onOpenChange(true, event, 'hover');
}
}, openDelay);
} else if (!open) {
onOpenChange(true, event, 'hover');
}
}
function onReferenceMouseLeave(event) {
if (isClickLikeOpenEvent()) {
clearPointerEvents();
return;
}
unbindMouseMoveRef.current();
const doc = getDocument(elements.floating);
clearTimeoutIfSet(restTimeoutRef);
restTimeoutPendingRef.current = false;
if (handleCloseRef.current && dataRef.current.floatingContext) {
// Prevent clearing `onScrollMouseLeave` timeout.
if (!open) {
clearTimeoutIfSet(timeoutRef);
}
handlerRef.current = handleCloseRef.current({
...dataRef.current.floatingContext,
tree,
x: event.clientX,
y: event.clientY,
onClose() {
clearPointerEvents();
cleanupMouseMoveHandler();
if (!isClickLikeOpenEvent()) {
closeWithDelay(event, true, 'safe-polygon');
}
}
});
const handler = handlerRef.current;
doc.addEventListener('mousemove', handler);
unbindMouseMoveRef.current = () => {
doc.removeEventListener('mousemove', handler);
};
return;
}
// Allow interactivity without `safePolygon` on touch devices. With a
// pointer, a short close delay is an alternative, so it should work
// consistently.
const shouldClose = pointerTypeRef.current === 'touch' ? !contains(elements.floating, event.relatedTarget) : true;
if (shouldClose) {
closeWithDelay(event);
}
}
// Ensure the floating element closes after scrolling even if the pointer
// did not move.
// https://github.com/floating-ui/floating-ui/discussions/1692
function onScrollMouseLeave(event) {
if (isClickLikeOpenEvent()) return;
if (!dataRef.current.floatingContext) return;
handleCloseRef.current == null || handleCloseRef.current({
...dataRef.current.floatingContext,
tree,
x: event.clientX,
y: event.clientY,
onClose() {
clearPointerEvents();
cleanupMouseMoveHandler();
if (!isClickLikeOpenEvent()) {
closeWithDelay(event);
}
}
})(event);
}
function onFloatingMouseEnter() {
clearTimeoutIfSet(timeoutRef);
}
function onFloatingMouseLeave(event) {
if (!isClickLikeOpenEvent()) {
closeWithDelay(event, false);
}
}
if (isElement(elements.domReference)) {
const reference = elements.domReference;
const floating = elements.floating;
if (open) {
reference.addEventListener('mouseleave', onScrollMouseLeave);
}
if (move) {
reference.addEventListener('mousemove', onReferenceMouseEnter, {
once: true
});
}
reference.addEventListener('mouseenter', onReferenceMouseEnter);
reference.addEventListener('mouseleave', onReferenceMouseLeave);
if (floating) {
floating.addEventListener('mouseleave', onScrollMouseLeave);
floating.addEventListener('mouseenter', onFloatingMouseEnter);
floating.addEventListener('mouseleave', onFloatingMouseLeave);
}
return () => {
if (open) {
reference.removeEventListener('mouseleave', onScrollMouseLeave);
}
if (move) {
reference.removeEventListener('mousemove', onReferenceMouseEnter);
}
reference.removeEventListener('mouseenter', onReferenceMouseEnter);
reference.removeEventListener('mouseleave', onReferenceMouseLeave);
if (floating) {
floating.removeEventListener('mouseleave', onScrollMouseLeave);
floating.removeEventListener('mouseenter', onFloatingMouseEnter);
floating.removeEventListener('mouseleave', onFloatingMouseLeave);
}
};
}
}, [elements, enabled, context, mouseOnly, move, closeWithDelay, cleanupMouseMoveHandler, clearPointerEvents, onOpenChange, open, openRef, tree, delayRef, handleCloseRef, dataRef, isClickLikeOpenEvent, restMsRef]);
// Block pointer-events of every element other than the reference and floating
// while the floating element is open and has a `handleClose` handler. Also
// handles nested floating elements.
// https://github.com/floating-ui/floating-ui/issues/1722
index(() => {
var _handleCloseRef$curre;
if (!enabled) return;
if (open && (_handleCloseRef$curre = handleCloseRef.current) != null && _handleCloseRef$curre.__options.blockPointerEvents && isHoverOpen()) {
performedPointerEventsMutationRef.current = true;