@jobber/components 8.15.0 → 8.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,64 +1,13 @@
1
1
  import * as React from 'react';
2
- import { u as useDialogRootContext, g as useFloatingParentNodeId, F as FloatingRootStore, P as PopupTriggerMap, h as useFloatingTree, i as getTarget, j as isTypeableElement, m as matchesFocusVisible, k as isTargetInsideEnabledTrigger, l as createAttribute, n as activeElement, o as contains, T as TYPEABLE_SELECTOR, p as getNodeChildren, q as getFloatingFocusElement, r as isTypeableCombobox, s as isIndexOutOfListBounds, t as getMinListIndex, v as getMaxListIndex, w as createGridCellMap, x as isListIndexDisabled, y as getGridNavigatedIndex, z as getGridCellIndices, A as getGridCellIndexOfCorner, B as ARROW_DOWN, C as ARROW_LEFT, E as ARROW_RIGHT, G as findNonDisabledListIndex, H as enqueueFocus, I as ARROW_UP, J as isElementVisible, K as DISABLED_TRANSITIONS_STYLE, L as popupStateMapping, M as COMPOSITE_KEYS, N as FloatingFocusManager, O as FloatingPortal, Q as useDirection, R as POPUP_COLLISION_AVOIDANCE, U as InternalBackdrop, V as inertValue, W as FloatingNode, X as DROPDOWN_COLLISION_AVOIDANCE, Y as popupStoreSelectors, Z as createSelector, _ as ReactStore, $ as createInitialPopupStoreState, a0 as FloatingTreeStore, a1 as fastComponent, a2 as useOnFirstRender, a3 as useFloatingNodeId, a4 as useOpenInteractionType, a5 as useImplicitActiveTrigger, a6 as useOpenStateTransitions, a7 as useScrollLock, a8 as useSyncedFloatingRootContext, a9 as useDismiss, aa as useRole, ab as TYPEAHEAD_RESET_MS, ac as useInteractions, ad as FloatingTree, ae as fastComponentRef, af as useTriggerDataForwarding, ag as useClick, ah as getTabbableBeforeElement, ai as isOutsideEvent, aj as getTabbableAfterElement, ak as getNextTabbable, al as pressableTriggerOpenStateMapping, am as FocusGuard, an as PATIENT_CLICK_THRESHOLD, ao as useTriggerRegistration, ap as triggerOpenStateMapping } from './ScrollAreaViewport-es.js';
3
- import { u as useRenderElement, a as useRefWithInit, f as formatErrorMessage, m as mergeProps, b as useMergedRefs, E as EMPTY_OBJECT, d as EMPTY_ARRAY } from './useRenderElement-es.js';
4
- import { y as useButton, k as createChangeEventDetails, C as closePress, D as isMouseLikePointerType, E as useId, d as useIsoLayoutEffect, b as useTimeout, F as triggerFocus, G as isMac, H as isSafari, s as ownerDocument, I as triggerPress, J as escapeKey, f as useOnMount, T as Timeout, a as useStableCallback, K as triggerHover, i as useValueAsRef, A as stopEvent, L as listNavigation, M as focusOut, N as isVirtualClick, O as isVirtualPointerEvent, u as useBaseUiId, P as itemPress, t as transitionStatusMapping, e as useOpenChangeComplete, Q as outsidePress, R as useAnimationsFinished, S as siblingOpen, h as useControlled, c as useTransitionStatus, U as imperativeAction, V as useCompositeRootContext, W as cancelOpen } from './useValueChanged-es.js';
2
+ import { u as useFloatingParentNodeId, F as FloatingRootStore, P as PopupTriggerMap, g as useFloatingTree, h as getTarget, i as isTypeableElement, m as matchesFocusVisible, j as isTargetInsideEnabledTrigger, k as createAttribute, l as activeElement, n as contains, T as TYPEABLE_SELECTOR, o as getNodeChildren, p as getFloatingFocusElement, q as isTypeableCombobox, r as isIndexOutOfListBounds, s as getMinListIndex, t as getMaxListIndex, v as createGridCellMap, w as isListIndexDisabled, x as getGridNavigatedIndex, y as getGridCellIndices, z as getGridCellIndexOfCorner, A as ARROW_DOWN, B as ARROW_LEFT, C as ARROW_RIGHT, E as findNonDisabledListIndex, G as enqueueFocus, H as ARROW_UP, I as isElementVisible, J as useDirection, K as DISABLED_TRANSITIONS_STYLE, L as useDialogRootContext, M as popupStateMapping, N as COMPOSITE_KEYS, O as FloatingFocusManager, Q as FloatingPortal, R as POPUP_COLLISION_AVOIDANCE, U as InternalBackdrop, V as inertValue, W as FloatingNode, X as DROPDOWN_COLLISION_AVOIDANCE, Y as popupStoreSelectors, Z as createSelector, _ as ReactStore, $ as createInitialPopupStoreState, a0 as FloatingTreeStore, a1 as fastComponent, a2 as useOnFirstRender, a3 as useFloatingNodeId, a4 as useOpenInteractionType, a5 as useImplicitActiveTrigger, a6 as useOpenStateTransitions, a7 as useScrollLock, a8 as useSyncedFloatingRootContext, a9 as useDismiss, aa as useRole, ab as TYPEAHEAD_RESET_MS, ac as useInteractions, ad as FloatingTree, ae as fastComponentRef, af as useTriggerDataForwarding, ag as useClick, ah as getTabbableBeforeElement, ai as isOutsideEvent, aj as getTabbableAfterElement, ak as getNextTabbable, al as pressableTriggerOpenStateMapping, am as FocusGuard, an as PATIENT_CLICK_THRESHOLD, ao as useTriggerRegistration, ap as triggerOpenStateMapping } from './ScrollAreaViewport-es.js';
3
+ import { a as useRefWithInit, E as EMPTY_OBJECT, u as useRenderElement, f as formatErrorMessage, m as mergeProps, b as useMergedRefs, d as EMPTY_ARRAY } from './useRenderElement-es.js';
4
+ import { C as isMouseLikePointerType, D as useId, d as useIsoLayoutEffect, b as useTimeout, k as createChangeEventDetails, E as triggerFocus, F as isMac, G as isSafari, s as ownerDocument, H as triggerPress, I as escapeKey, f as useOnMount, T as Timeout, a as useStableCallback, J as triggerHover, i as useValueAsRef, A as stopEvent, K as listNavigation, L as focusOut, M as isVirtualClick, N as isVirtualPointerEvent, y as useButton, O as closePress, u as useBaseUiId, P as itemPress, t as transitionStatusMapping, e as useOpenChangeComplete, Q as outsidePress, R as useAnimationsFinished, S as siblingOpen, h as useControlled, c as useTransitionStatus, U as imperativeAction, V as useCompositeRootContext, W as cancelOpen } from './clamp-es.js';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
6
  import { b as isElement, k as getWindow, a as isHTMLElement, d as isLastTraversableNode, e as getParentNode } from './floating-ui.utils.dom-es.js';
7
7
  import { e as evaluate, h as getPaddingObject, b as getAlignmentAxis, d as getAlignment, q as getAxisLength, n as clamp, c as getSide, g as getSideAxis } from './index.esm-es.js';
8
8
  import { u as useFloating$1, h as hide$1, f as flip, d as size, b as autoUpdate, o as offset, s as shift, l as limitShift } from './floating-ui.react-dom-es.js';
9
9
  import * as ReactDOM from 'react-dom';
10
10
 
11
- /**
12
- * A button that closes the dialog.
13
- * Renders a `<button>` element.
14
- *
15
- * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
16
- */
17
- const DialogClose = /*#__PURE__*/React.forwardRef(function DialogClose(componentProps, forwardedRef) {
18
- const {
19
- render,
20
- className,
21
- disabled = false,
22
- nativeButton = true,
23
- ...elementProps
24
- } = componentProps;
25
- const {
26
- store
27
- } = useDialogRootContext();
28
- const open = store.useState('open');
29
- function handleClick(event) {
30
- if (open) {
31
- store.setOpen(false, createChangeEventDetails(closePress, event.nativeEvent));
32
- }
33
- }
34
- const {
35
- getButtonProps,
36
- buttonRef
37
- } = useButton({
38
- disabled,
39
- native: nativeButton
40
- });
41
- const state = {
42
- disabled
43
- };
44
- return useRenderElement('button', componentProps, {
45
- state,
46
- ref: [forwardedRef, buttonRef],
47
- props: [{
48
- onClick: handleClick
49
- }, elementProps, getButtonProps]
50
- });
51
- });
52
- if (process.env.NODE_ENV !== "production") DialogClose.displayName = "DialogClose";
53
-
54
- /**
55
- * A button that closes the drawer.
56
- * Renders a `<button>` element.
57
- *
58
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
59
- */
60
- const DrawerClose = DialogClose;
61
-
62
11
  function resolveValue(value, pointerType) {
63
12
  if (pointerType != null && !isMouseLikePointerType(pointerType)) {
64
13
  return 0;
@@ -1869,752 +1818,294 @@ function safePolygon(options = {}) {
1869
1818
  return fn;
1870
1819
  }
1871
1820
 
1872
- /**
1873
- * A heading that labels the dialog.
1874
- * Renders an `<h2>` element.
1875
- *
1876
- * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
1877
- */
1878
- const DialogTitle = /*#__PURE__*/React.forwardRef(function DialogTitle(componentProps, forwardedRef) {
1879
- const {
1880
- render,
1881
- className,
1882
- id: idProp,
1883
- ...elementProps
1884
- } = componentProps;
1885
- const {
1886
- store
1887
- } = useDialogRootContext();
1888
- const id = useBaseUiId(idProp);
1889
- store.useSyncedValueWithCleanup('titleElementId', id);
1890
- return useRenderElement('h2', componentProps, {
1891
- ref: forwardedRef,
1892
- props: [{
1893
- id
1894
- }, elementProps]
1895
- });
1896
- });
1897
- if (process.env.NODE_ENV !== "production") DialogTitle.displayName = "DialogTitle";
1898
-
1899
- /**
1900
- * A heading that labels the drawer.
1901
- * Renders an `<h2>` element.
1902
- *
1903
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
1904
- */
1905
- const DrawerTitle = DialogTitle;
1821
+ function getPseudoElementBounds(element) {
1822
+ const elementRect = element.getBoundingClientRect();
1906
1823
 
1907
- const MenuPositionerContext = /*#__PURE__*/React.createContext(undefined);
1908
- if (process.env.NODE_ENV !== "production") MenuPositionerContext.displayName = "MenuPositionerContext";
1909
- function useMenuPositionerContext(optional) {
1910
- const context = React.useContext(MenuPositionerContext);
1911
- if (context === undefined && !optional) {
1912
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuPositionerContext is missing. MenuPositioner parts must be placed within <Menu.Positioner>.' : formatErrorMessage(33));
1824
+ // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)"
1825
+ if (process.env.NODE_ENV !== 'production') {
1826
+ return elementRect;
1913
1827
  }
1914
- return context;
1915
- }
1916
-
1917
- const MenuRootContext = /*#__PURE__*/React.createContext(undefined);
1918
- if (process.env.NODE_ENV !== "production") MenuRootContext.displayName = "MenuRootContext";
1919
- function useMenuRootContext(optional) {
1920
- const context = React.useContext(MenuRootContext);
1921
- if (context === undefined && !optional) {
1922
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuRootContext is missing. Menu parts must be placed within <Menu.Root>.' : formatErrorMessage(36));
1828
+ const beforeStyles = window.getComputedStyle(element, '::before');
1829
+ const afterStyles = window.getComputedStyle(element, '::after');
1830
+ const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';
1831
+ if (!hasPseudoElements) {
1832
+ return elementRect;
1923
1833
  }
1924
- return context;
1834
+
1835
+ // Get dimensions of pseudo-elements
1836
+ const beforeWidth = parseFloat(beforeStyles.width) || 0;
1837
+ const beforeHeight = parseFloat(beforeStyles.height) || 0;
1838
+ const afterWidth = parseFloat(afterStyles.width) || 0;
1839
+ const afterHeight = parseFloat(afterStyles.height) || 0;
1840
+
1841
+ // Calculate max dimensions including pseudo-elements
1842
+ const totalWidth = Math.max(elementRect.width, beforeWidth, afterWidth);
1843
+ const totalHeight = Math.max(elementRect.height, beforeHeight, afterHeight);
1844
+
1845
+ // Calculate the differences to extend the bounds
1846
+ const widthDiff = totalWidth - elementRect.width;
1847
+ const heightDiff = totalHeight - elementRect.height;
1848
+ return {
1849
+ left: elementRect.left - widthDiff / 2,
1850
+ right: elementRect.right + widthDiff / 2,
1851
+ top: elementRect.top - heightDiff / 2,
1852
+ bottom: elementRect.bottom + heightDiff / 2
1853
+ };
1925
1854
  }
1926
1855
 
1927
- const ContextMenuRootContext = /*#__PURE__*/React.createContext(undefined);
1928
- if (process.env.NODE_ENV !== "production") ContextMenuRootContext.displayName = "ContextMenuRootContext";
1929
- function useContextMenuRootContext(optional = true) {
1930
- const context = React.useContext(ContextMenuRootContext);
1931
- if (context === undefined && !optional) {
1932
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ContextMenuRootContext is missing. ContextMenu parts must be placed within <ContextMenu.Root>.' : formatErrorMessage(25));
1856
+ const CompositeListContext = /*#__PURE__*/React.createContext({
1857
+ register: () => {},
1858
+ unregister: () => {},
1859
+ subscribeMapChange: () => {
1860
+ return () => {};
1861
+ },
1862
+ elementsRef: {
1863
+ current: []
1864
+ },
1865
+ nextIndexRef: {
1866
+ current: 0
1933
1867
  }
1934
- return context;
1868
+ });
1869
+ if (process.env.NODE_ENV !== "production") CompositeListContext.displayName = "CompositeListContext";
1870
+ function useCompositeListContext() {
1871
+ return React.useContext(CompositeListContext);
1935
1872
  }
1936
1873
 
1874
+ /* eslint-disable no-bitwise */
1937
1875
  /**
1938
- * Returns common props shared by all menu item types.
1939
- * This hook extracts the shared logic for id, role, tabIndex, onKeyDown,
1940
- * onMouseMove, onClick, and onMouseUp handlers.
1876
+ * Provides context for a list of items in a composite component.
1877
+ * @internal
1941
1878
  */
1942
- function useMenuItemCommonProps(params) {
1943
- const {
1944
- closeOnClick,
1945
- highlighted,
1946
- id,
1947
- nodeId,
1948
- store,
1949
- typingRef,
1950
- itemRef,
1951
- itemMetadata
1952
- } = params;
1879
+ function CompositeList(props) {
1953
1880
  const {
1954
- events: menuEvents
1955
- } = store.useState('floatingTreeRoot');
1956
- const contextMenuContext = useContextMenuRootContext(true);
1957
- const isContextMenu = contextMenuContext !== undefined;
1958
- return React.useMemo(() => ({
1959
- id,
1960
- role: 'menuitem',
1961
- tabIndex: highlighted ? 0 : -1,
1962
- onKeyDown(event) {
1963
- if (event.key === ' ' && typingRef?.current) {
1964
- event.preventDefault();
1965
- }
1966
- },
1967
- onMouseMove(event) {
1968
- if (!nodeId) {
1969
- return;
1970
- }
1971
-
1972
- // Inform the floating tree that a menu item within this menu was hovered/moved over
1973
- // so unrelated descendant submenus can be closed.
1974
- menuEvents.emit('itemhover', {
1975
- nodeId,
1976
- target: event.currentTarget
1977
- });
1978
- },
1979
- onClick(event) {
1980
- if (closeOnClick) {
1981
- menuEvents.emit('close', {
1982
- domEvent: event,
1983
- reason: itemPress
1984
- });
1985
- }
1986
- },
1987
- onMouseUp(event) {
1988
- if (contextMenuContext) {
1989
- const initialCursorPoint = contextMenuContext.initialCursorPointRef.current;
1990
- contextMenuContext.initialCursorPointRef.current = null;
1991
- if (isContextMenu && initialCursorPoint && Math.abs(event.clientX - initialCursorPoint.x) <= 1 && Math.abs(event.clientY - initialCursorPoint.y) <= 1) {
1992
- return;
1993
- }
1881
+ children,
1882
+ elementsRef,
1883
+ labelsRef,
1884
+ onMapChange: onMapChangeProp
1885
+ } = props;
1886
+ const onMapChange = useStableCallback(onMapChangeProp);
1887
+ const nextIndexRef = React.useRef(0);
1888
+ const listeners = useRefWithInit(createListeners).current;
1994
1889
 
1995
- // On non-macOS platforms, this mouseup belongs to the right-click gesture
1996
- // that opened the context menu, so it must not activate an item.
1997
- if (isContextMenu && !isMac && event.button === 2) {
1998
- return;
1999
- }
2000
- }
2001
- if (itemRef.current && store.context.allowMouseUpTriggerRef.current && (!isContextMenu || event.button === 2)) {
2002
- // This fires whenever the user clicks on the trigger, moves the cursor, and releases it over the item.
2003
- // We trigger the click and override the `closeOnClick` preference to always close the menu.
2004
- if (!itemMetadata || itemMetadata.type === 'regular-item') {
2005
- itemRef.current.click();
2006
- }
2007
- }
2008
- }
2009
- }), [closeOnClick, highlighted, id, menuEvents, nodeId, store, typingRef, itemRef, contextMenuContext, isContextMenu, itemMetadata]);
2010
- }
1890
+ // We use a stable `map` to avoid O(n^2) re-allocation costs for large lists.
1891
+ // `mapTick` is our re-render trigger mechanism. We also need to update the
1892
+ // elements and label refs, but there's a lot of async work going on and sometimes
1893
+ // the effect that handles `onMapChange` gets called after those refs have been
1894
+ // filled, and we don't want to lose those values by setting their lengths to `0`.
1895
+ // We also need to have them at the proper length because floating-ui uses that
1896
+ // information for list navigation.
2011
1897
 
2012
- const REGULAR_ITEM = {
2013
- type: 'regular-item'
2014
- };
2015
- function useMenuItem(params) {
2016
- const {
2017
- closeOnClick,
2018
- disabled = false,
2019
- highlighted,
2020
- id,
2021
- store,
2022
- typingRef = store.context.typingRef,
2023
- nativeButton,
2024
- itemMetadata,
2025
- nodeId
2026
- } = params;
2027
- const itemRef = React.useRef(null);
2028
- const {
2029
- getButtonProps,
2030
- buttonRef
2031
- } = useButton({
2032
- disabled,
2033
- focusableWhenDisabled: true,
2034
- native: nativeButton,
2035
- composite: true
1898
+ const map = useRefWithInit(createMap).current;
1899
+ // `mapTick` uses a counter rather than objects for low precision-loss risk and better memory efficiency
1900
+ const [mapTick, setMapTick] = React.useState(0);
1901
+ const lastTickRef = React.useRef(mapTick);
1902
+ const register = useStableCallback((node, metadata) => {
1903
+ map.set(node, metadata ?? null);
1904
+ lastTickRef.current += 1;
1905
+ setMapTick(lastTickRef.current);
2036
1906
  });
2037
- const commonProps = useMenuItemCommonProps({
2038
- closeOnClick,
2039
- highlighted,
2040
- id,
2041
- nodeId,
2042
- store,
2043
- typingRef,
2044
- itemRef,
2045
- itemMetadata
1907
+ const unregister = useStableCallback(node => {
1908
+ map.delete(node);
1909
+ lastTickRef.current += 1;
1910
+ setMapTick(lastTickRef.current);
2046
1911
  });
2047
- const getItemProps = React.useCallback(externalProps => {
2048
- return mergeProps(commonProps, {
2049
- onMouseEnter() {
2050
- if (itemMetadata.type !== 'submenu-trigger') {
2051
- return;
2052
- }
2053
- itemMetadata.setActive();
1912
+ const sortedMap = React.useMemo(() => {
1913
+ const newMap = new Map();
1914
+ // Filter out disconnected elements before sorting to avoid inconsistent
1915
+ // compareDocumentPosition results when elements are detached from the DOM.
1916
+ const sortedNodes = Array.from(map.keys()).filter(node => node.isConnected).sort(sortByDocumentPosition);
1917
+ sortedNodes.forEach((node, index) => {
1918
+ const metadata = map.get(node) ?? {};
1919
+ newMap.set(node, {
1920
+ ...metadata,
1921
+ index
1922
+ });
1923
+ });
1924
+ return newMap;
1925
+ }, [map, mapTick]);
1926
+ useIsoLayoutEffect(() => {
1927
+ if (typeof MutationObserver !== 'function' || sortedMap.size === 0) {
1928
+ return undefined;
1929
+ }
1930
+ const mutationObserver = new MutationObserver(entries => {
1931
+ const diff = new Set();
1932
+ const updateDiff = node => diff.has(node) ? diff.delete(node) : diff.add(node);
1933
+ entries.forEach(entry => {
1934
+ entry.removedNodes.forEach(updateDiff);
1935
+ entry.addedNodes.forEach(updateDiff);
1936
+ });
1937
+ if (diff.size === 0) {
1938
+ lastTickRef.current += 1;
1939
+ setMapTick(lastTickRef.current);
2054
1940
  }
2055
- }, externalProps, getButtonProps);
2056
- }, [commonProps, getButtonProps, itemMetadata]);
2057
- const mergedRef = useMergedRefs(itemRef, buttonRef);
2058
- return React.useMemo(() => ({
2059
- getItemProps,
2060
- itemRef: mergedRef
2061
- }), [getItemProps, mergedRef]);
2062
- }
2063
-
2064
- const CompositeListContext = /*#__PURE__*/React.createContext({
2065
- register: () => {},
2066
- unregister: () => {},
2067
- subscribeMapChange: () => {
2068
- return () => {};
2069
- },
2070
- elementsRef: {
2071
- current: []
2072
- },
2073
- nextIndexRef: {
2074
- current: 0
2075
- }
2076
- });
2077
- if (process.env.NODE_ENV !== "production") CompositeListContext.displayName = "CompositeListContext";
2078
- function useCompositeListContext() {
2079
- return React.useContext(CompositeListContext);
2080
- }
2081
-
2082
- let IndexGuessBehavior = /*#__PURE__*/function (IndexGuessBehavior) {
2083
- IndexGuessBehavior[IndexGuessBehavior["None"] = 0] = "None";
2084
- IndexGuessBehavior[IndexGuessBehavior["GuessFromOrder"] = 1] = "GuessFromOrder";
2085
- return IndexGuessBehavior;
2086
- }({});
2087
-
2088
- /**
2089
- * Used to register a list item and its index (DOM position) in the `CompositeList`.
2090
- */
2091
- function useCompositeListItem(params = {}) {
2092
- const {
2093
- label,
2094
- metadata,
2095
- textRef,
2096
- indexGuessBehavior,
2097
- index: externalIndex
2098
- } = params;
2099
- const {
1941
+ });
1942
+ sortedMap.forEach((_, node) => {
1943
+ if (node.parentElement) {
1944
+ mutationObserver.observe(node.parentElement, {
1945
+ childList: true
1946
+ });
1947
+ }
1948
+ });
1949
+ return () => {
1950
+ mutationObserver.disconnect();
1951
+ };
1952
+ }, [sortedMap]);
1953
+ useIsoLayoutEffect(() => {
1954
+ const shouldUpdateLengths = lastTickRef.current === mapTick;
1955
+ if (shouldUpdateLengths) {
1956
+ if (elementsRef.current.length !== sortedMap.size) {
1957
+ elementsRef.current.length = sortedMap.size;
1958
+ }
1959
+ if (labelsRef && labelsRef.current.length !== sortedMap.size) {
1960
+ labelsRef.current.length = sortedMap.size;
1961
+ }
1962
+ nextIndexRef.current = sortedMap.size;
1963
+ }
1964
+ onMapChange(sortedMap);
1965
+ }, [onMapChange, sortedMap, elementsRef, labelsRef, mapTick]);
1966
+ useIsoLayoutEffect(() => {
1967
+ return () => {
1968
+ elementsRef.current = [];
1969
+ };
1970
+ }, [elementsRef]);
1971
+ useIsoLayoutEffect(() => {
1972
+ return () => {
1973
+ if (labelsRef) {
1974
+ labelsRef.current = [];
1975
+ }
1976
+ };
1977
+ }, [labelsRef]);
1978
+ const subscribeMapChange = useStableCallback(fn => {
1979
+ listeners.add(fn);
1980
+ return () => {
1981
+ listeners.delete(fn);
1982
+ };
1983
+ });
1984
+ useIsoLayoutEffect(() => {
1985
+ listeners.forEach(l => l(sortedMap));
1986
+ }, [listeners, sortedMap]);
1987
+ const contextValue = React.useMemo(() => ({
2100
1988
  register,
2101
1989
  unregister,
2102
1990
  subscribeMapChange,
2103
1991
  elementsRef,
2104
1992
  labelsRef,
2105
1993
  nextIndexRef
2106
- } = useCompositeListContext();
2107
- const indexRef = React.useRef(-1);
2108
- const [index, setIndex] = React.useState(externalIndex ?? (indexGuessBehavior === IndexGuessBehavior.GuessFromOrder ? () => {
2109
- if (indexRef.current === -1) {
2110
- const newIndex = nextIndexRef.current;
2111
- nextIndexRef.current += 1;
2112
- indexRef.current = newIndex;
2113
- }
2114
- return indexRef.current;
2115
- } : -1));
2116
- const componentRef = React.useRef(null);
2117
- const ref = React.useCallback(node => {
2118
- componentRef.current = node;
2119
- if (index !== -1 && node !== null) {
2120
- elementsRef.current[index] = node;
2121
- if (labelsRef) {
2122
- const isLabelDefined = label !== undefined;
2123
- labelsRef.current[index] = isLabelDefined ? label : textRef?.current?.textContent ?? node.textContent;
2124
- }
2125
- }
2126
- }, [index, elementsRef, labelsRef, label, textRef]);
2127
- useIsoLayoutEffect(() => {
2128
- if (externalIndex != null) {
2129
- return undefined;
2130
- }
2131
- const node = componentRef.current;
2132
- if (node) {
2133
- register(node, metadata);
2134
- return () => {
2135
- unregister(node);
2136
- };
2137
- }
2138
- return undefined;
2139
- }, [externalIndex, register, unregister, metadata]);
2140
- useIsoLayoutEffect(() => {
2141
- if (externalIndex != null) {
2142
- return undefined;
2143
- }
2144
- return subscribeMapChange(map => {
2145
- const i = componentRef.current ? map.get(componentRef.current)?.index : null;
2146
- if (i != null) {
2147
- setIndex(i);
2148
- }
2149
- });
2150
- }, [externalIndex, subscribeMapChange, setIndex]);
2151
- return React.useMemo(() => ({
2152
- ref,
2153
- index
2154
- }), [index, ref]);
1994
+ }), [register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef]);
1995
+ return /*#__PURE__*/jsx(CompositeListContext.Provider, {
1996
+ value: contextValue,
1997
+ children: children
1998
+ });
1999
+ }
2000
+ function createMap() {
2001
+ return new Map();
2002
+ }
2003
+ function createListeners() {
2004
+ return new Set();
2005
+ }
2006
+ function sortByDocumentPosition(a, b) {
2007
+ const position = a.compareDocumentPosition(b);
2008
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2009
+ return -1;
2010
+ }
2011
+ if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
2012
+ return 1;
2013
+ }
2014
+ return 0;
2155
2015
  }
2156
2016
 
2157
- let MenuCheckboxItemDataAttributes = /*#__PURE__*/function (MenuCheckboxItemDataAttributes) {
2158
- /**
2159
- * Present when the menu checkbox item is checked.
2160
- */
2161
- MenuCheckboxItemDataAttributes["checked"] = "data-checked";
2162
- /**
2163
- * Present when the menu checkbox item is not checked.
2164
- */
2165
- MenuCheckboxItemDataAttributes["unchecked"] = "data-unchecked";
2166
- /**
2167
- * Present when the menu checkbox item is disabled.
2168
- */
2169
- MenuCheckboxItemDataAttributes["disabled"] = "data-disabled";
2170
- /**
2171
- * Present when the menu checkbox item is highlighted.
2172
- */
2173
- MenuCheckboxItemDataAttributes["highlighted"] = "data-highlighted";
2174
- return MenuCheckboxItemDataAttributes;
2175
- }({});
2176
-
2177
- const itemMapping = {
2178
- checked(value) {
2179
- if (value) {
2180
- return {
2181
- [MenuCheckboxItemDataAttributes.checked]: ''
2182
- };
2183
- }
2184
- return {
2185
- [MenuCheckboxItemDataAttributes.unchecked]: ''
2017
+ /**
2018
+ * Fork of the original `arrow` middleware from Floating UI that allows
2019
+ * configuring the offset parent.
2020
+ */
2021
+ const baseArrow = options => ({
2022
+ name: 'arrow',
2023
+ options,
2024
+ async fn(state) {
2025
+ const {
2026
+ x,
2027
+ y,
2028
+ placement,
2029
+ rects,
2030
+ platform,
2031
+ elements,
2032
+ middlewareData
2033
+ } = state;
2034
+ // Since `element` is required, we don't Partial<> the type.
2035
+ const {
2036
+ element,
2037
+ padding = 0,
2038
+ offsetParent = 'real'
2039
+ } = evaluate(options, state) || {};
2040
+ if (element == null) {
2041
+ return {};
2042
+ }
2043
+ const paddingObject = getPaddingObject(padding);
2044
+ const coords = {
2045
+ x,
2046
+ y
2186
2047
  };
2187
- },
2188
- ...transitionStatusMapping
2189
- };
2190
-
2191
- const MenuGroupContext = /*#__PURE__*/React.createContext(undefined);
2192
- if (process.env.NODE_ENV !== "production") MenuGroupContext.displayName = "MenuGroupContext";
2193
- function useMenuGroupRootContext() {
2194
- const context = React.useContext(MenuGroupContext);
2195
- if (context === undefined) {
2196
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.' : formatErrorMessage(31));
2197
- }
2198
- return context;
2199
- }
2048
+ const axis = getAlignmentAxis(placement);
2049
+ const length = getAxisLength(axis);
2050
+ const arrowDimensions = await platform.getDimensions(element);
2051
+ const isYAxis = axis === 'y';
2052
+ const minProp = isYAxis ? 'top' : 'left';
2053
+ const maxProp = isYAxis ? 'bottom' : 'right';
2054
+ const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
2055
+ const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
2056
+ const startDiff = coords[axis] - rects.reference[axis];
2057
+ const arrowOffsetParent = offsetParent === 'real' ? await platform.getOffsetParent?.(element) : elements.floating;
2058
+ let clientSize = elements.floating[clientProp] || rects.floating[length];
2200
2059
 
2201
- const MenuGroup = /*#__PURE__*/React.forwardRef(function MenuGroup(componentProps, forwardedRef) {
2202
- const {
2203
- render,
2204
- className,
2205
- ...elementProps
2206
- } = componentProps;
2207
- const [labelId, setLabelId] = React.useState(undefined);
2208
- const context = React.useMemo(() => ({
2209
- setLabelId
2210
- }), [setLabelId]);
2211
- const element = useRenderElement('div', componentProps, {
2212
- ref: forwardedRef,
2213
- props: {
2214
- role: 'group',
2215
- 'aria-labelledby': labelId,
2216
- ...elementProps
2060
+ // DOM platform can return `window` as the `offsetParent`.
2061
+ if (!clientSize || !(await platform.isElement?.(arrowOffsetParent))) {
2062
+ clientSize = elements.floating[clientProp] || rects.floating[length];
2217
2063
  }
2218
- });
2219
- return /*#__PURE__*/jsx(MenuGroupContext.Provider, {
2220
- value: context,
2221
- children: element
2222
- });
2223
- });
2224
- if (process.env.NODE_ENV !== "production") MenuGroup.displayName = "MenuGroup";
2064
+ const centerToReference = endDiff / 2 - startDiff / 2;
2225
2065
 
2226
- /**
2227
- * An accessible label that is automatically associated with its parent group.
2228
- * Renders a `<div>` element.
2229
- *
2230
- * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2231
- */
2232
- const MenuGroupLabel = /*#__PURE__*/React.forwardRef(function MenuGroupLabelComponent(componentProps, forwardedRef) {
2233
- const {
2234
- className,
2235
- render,
2236
- id: idProp,
2237
- ...elementProps
2238
- } = componentProps;
2239
- const id = useBaseUiId(idProp);
2240
- const {
2241
- setLabelId
2242
- } = useMenuGroupRootContext();
2243
- useIsoLayoutEffect(() => {
2244
- setLabelId(id);
2245
- return () => {
2246
- setLabelId(undefined);
2066
+ // If the padding is large enough that it causes the arrow to no longer be
2067
+ // centered, modify the padding so that it is centered.
2068
+ const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
2069
+ const minPadding = Math.min(paddingObject[minProp], largestPossiblePadding);
2070
+ const maxPadding = Math.min(paddingObject[maxProp], largestPossiblePadding);
2071
+
2072
+ // Make sure the arrow doesn't overflow the floating element if the center
2073
+ // point is outside the floating element's bounds.
2074
+ const min = minPadding;
2075
+ const max = clientSize - arrowDimensions[length] - maxPadding;
2076
+ const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
2077
+ const offset = clamp(min, center, max);
2078
+
2079
+ // If the reference is small enough that the arrow's padding causes it to
2080
+ // to point to nothing for an aligned placement, adjust the offset of the
2081
+ // floating element itself. To ensure `shift()` continues to take action,
2082
+ // a single reset is performed when this is true.
2083
+ const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
2084
+ // eslint-disable-next-line no-nested-ternary
2085
+ const alignmentOffset = shouldAddOffset ? center < min ? center - min : center - max : 0;
2086
+ return {
2087
+ [axis]: coords[axis] + alignmentOffset,
2088
+ data: {
2089
+ [axis]: offset,
2090
+ centerOffset: center - offset - alignmentOffset,
2091
+ ...(shouldAddOffset && {
2092
+ alignmentOffset
2093
+ })
2094
+ },
2095
+ reset: shouldAddOffset
2247
2096
  };
2248
- }, [setLabelId, id]);
2249
- return useRenderElement('div', componentProps, {
2250
- ref: forwardedRef,
2251
- props: {
2252
- id,
2253
- role: 'presentation',
2254
- ...elementProps
2255
- }
2256
- });
2097
+ }
2257
2098
  });
2258
- if (process.env.NODE_ENV !== "production") MenuGroupLabel.displayName = "MenuGroupLabel";
2259
2099
 
2260
2100
  /**
2261
- * An individual interactive item in the menu.
2262
- * Renders a `<div>` element.
2263
- *
2264
- * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2101
+ * Provides data to position an inner element of the floating element so that it
2102
+ * appears centered to the reference element.
2103
+ * This wraps the core `arrow` middleware to allow React refs as the element.
2104
+ * @see https://floating-ui.com/docs/arrow
2265
2105
  */
2266
- const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(componentProps, forwardedRef) {
2267
- const {
2268
- render,
2269
- className,
2270
- id: idProp,
2271
- label,
2272
- nativeButton = false,
2273
- disabled = false,
2274
- closeOnClick = true,
2275
- ...elementProps
2276
- } = componentProps;
2277
- const listItem = useCompositeListItem({
2278
- label
2279
- });
2280
- const menuPositionerContext = useMenuPositionerContext(true);
2281
- const id = useBaseUiId(idProp);
2282
- const {
2283
- store
2284
- } = useMenuRootContext();
2285
- const highlighted = store.useState('isActive', listItem.index);
2286
- const itemProps = store.useState('itemProps');
2287
- const {
2288
- getItemProps,
2289
- itemRef
2290
- } = useMenuItem({
2291
- closeOnClick,
2292
- disabled,
2293
- highlighted,
2294
- id,
2295
- store,
2296
- nativeButton,
2297
- nodeId: menuPositionerContext?.nodeId,
2298
- itemMetadata: REGULAR_ITEM
2299
- });
2300
- const state = {
2301
- disabled,
2302
- highlighted
2303
- };
2304
- return useRenderElement('div', componentProps, {
2305
- state,
2306
- props: [itemProps, elementProps, getItemProps],
2307
- ref: [itemRef, forwardedRef, listItem.ref]
2308
- });
2309
- });
2310
- if (process.env.NODE_ENV !== "production") MenuItem.displayName = "MenuItem";
2311
-
2312
- /**
2313
- * A link in the menu that can be used to navigate to a different page or section.
2314
- * Renders an `<a>` element.
2315
- *
2316
- * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2317
- */
2318
- const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(componentProps, forwardedRef) {
2319
- const {
2320
- render,
2321
- className,
2322
- id: idProp,
2323
- label,
2324
- closeOnClick = false,
2325
- ...elementProps
2326
- } = componentProps;
2327
- const linkRef = React.useRef(null);
2328
- const listItem = useCompositeListItem({
2329
- label
2330
- });
2331
- const menuPositionerContext = useMenuPositionerContext(true);
2332
- const nodeId = menuPositionerContext?.nodeId;
2333
- const id = useBaseUiId(idProp);
2334
- const {
2335
- store
2336
- } = useMenuRootContext();
2337
- const highlighted = store.useState('isActive', listItem.index);
2338
- const itemProps = store.useState('itemProps');
2339
- const typingRef = store.context.typingRef;
2340
- const {
2341
- getButtonProps,
2342
- buttonRef
2343
- } = useButton({
2344
- native: false,
2345
- composite: true
2346
- });
2347
- const commonProps = useMenuItemCommonProps({
2348
- closeOnClick,
2349
- highlighted,
2350
- id,
2351
- nodeId,
2352
- store,
2353
- typingRef,
2354
- itemRef: linkRef
2355
- });
2356
- function getItemProps(externalProps) {
2357
- return mergeProps(commonProps, externalProps, getButtonProps);
2358
- }
2359
- const state = React.useMemo(() => ({
2360
- highlighted
2361
- }), [highlighted]);
2362
- return useRenderElement('a', componentProps, {
2363
- state,
2364
- props: [itemProps, elementProps, getItemProps],
2365
- ref: [linkRef, buttonRef, forwardedRef, listItem.ref]
2366
- });
2367
- });
2368
- if (process.env.NODE_ENV !== "production") MenuLinkItem.displayName = "MenuLinkItem";
2369
-
2370
- const ToolbarRootContext = /*#__PURE__*/React.createContext(undefined);
2371
- if (process.env.NODE_ENV !== "production") ToolbarRootContext.displayName = "ToolbarRootContext";
2372
- function useToolbarRootContext(optional) {
2373
- const context = React.useContext(ToolbarRootContext);
2374
- if (context === undefined && !optional) {
2375
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ToolbarRootContext is missing. Toolbar parts must be placed within <Toolbar.Root>.' : formatErrorMessage(69));
2376
- }
2377
- return context;
2378
- }
2379
-
2380
- function getDisabledMountTransitionStyles(transitionStatus) {
2381
- return transitionStatus === 'starting' ? DISABLED_TRANSITIONS_STYLE : EMPTY_OBJECT;
2382
- }
2383
-
2384
- const stateAttributesMapping = {
2385
- ...popupStateMapping,
2386
- ...transitionStatusMapping
2387
- };
2388
-
2389
- /**
2390
- * A container for the menu items.
2391
- * Renders a `<div>` element.
2392
- *
2393
- * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2394
- */
2395
- const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProps, forwardedRef) {
2396
- const {
2397
- render,
2398
- className,
2399
- finalFocus,
2400
- ...elementProps
2401
- } = componentProps;
2402
- const {
2403
- store
2404
- } = useMenuRootContext();
2405
- const {
2406
- side,
2407
- align
2408
- } = useMenuPositionerContext();
2409
- const insideToolbar = useToolbarRootContext(true) != null;
2410
- const open = store.useState('open');
2411
- const transitionStatus = store.useState('transitionStatus');
2412
- const popupProps = store.useState('popupProps');
2413
- const mounted = store.useState('mounted');
2414
- const instantType = store.useState('instantType');
2415
- const triggerElement = store.useState('activeTriggerElement');
2416
- const parent = store.useState('parent');
2417
- const lastOpenChangeReason = store.useState('lastOpenChangeReason');
2418
- const rootId = store.useState('rootId');
2419
- const floatingContext = store.useState('floatingRootContext');
2420
- const floatingTreeRoot = store.useState('floatingTreeRoot');
2421
- const closeDelay = store.useState('closeDelay');
2422
- const activeTriggerElement = store.useState('activeTriggerElement');
2423
- const isContextMenu = parent.type === 'context-menu';
2424
- useOpenChangeComplete({
2425
- open,
2426
- ref: store.context.popupRef,
2427
- onComplete() {
2428
- if (open) {
2429
- store.context.onOpenChangeComplete?.(true);
2430
- }
2431
- }
2432
- });
2433
- React.useEffect(() => {
2434
- function handleClose(event) {
2435
- store.setOpen(false, createChangeEventDetails(event.reason, event.domEvent));
2436
- }
2437
- floatingTreeRoot.events.on('close', handleClose);
2438
- return () => {
2439
- floatingTreeRoot.events.off('close', handleClose);
2440
- };
2441
- }, [floatingTreeRoot.events, store]);
2442
- const hoverEnabled = store.useState('hoverEnabled');
2443
- const disabled = store.useState('disabled');
2444
- useHoverFloatingInteraction(floatingContext, {
2445
- enabled: hoverEnabled && !disabled && !isContextMenu && parent.type !== 'menubar',
2446
- closeDelay
2447
- });
2448
- const state = {
2449
- transitionStatus,
2450
- side,
2451
- align,
2452
- open,
2453
- nested: parent.type === 'menu',
2454
- instant: instantType
2455
- };
2456
- const setPopupElement = React.useCallback(element => {
2457
- store.set('popupElement', element);
2458
- }, [store]);
2459
- const element = useRenderElement('div', componentProps, {
2460
- state,
2461
- ref: [forwardedRef, store.context.popupRef, setPopupElement],
2462
- stateAttributesMapping,
2463
- props: [popupProps, {
2464
- onKeyDown(event) {
2465
- if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {
2466
- event.stopPropagation();
2467
- }
2468
- }
2469
- }, getDisabledMountTransitionStyles(transitionStatus), elementProps, {
2470
- 'data-rootownerid': rootId
2471
- }]
2472
- });
2473
- let returnFocus = parent.type === undefined || isContextMenu;
2474
- if (triggerElement || parent.type === 'menubar' && lastOpenChangeReason !== outsidePress) {
2475
- returnFocus = true;
2476
- }
2477
- return /*#__PURE__*/jsx(FloatingFocusManager, {
2478
- context: floatingContext,
2479
- modal: isContextMenu,
2480
- disabled: !mounted,
2481
- returnFocus: finalFocus === undefined ? returnFocus : finalFocus,
2482
- initialFocus: parent.type !== 'menu',
2483
- restoreFocus: true,
2484
- externalTree: parent.type !== 'menubar' ? floatingTreeRoot : undefined,
2485
- previousFocusableElement: activeTriggerElement,
2486
- nextFocusableElement: parent.type === undefined ? store.context.triggerFocusTargetRef : undefined,
2487
- beforeContentFocusGuardRef: parent.type === undefined ? store.context.beforeContentFocusGuardRef : undefined,
2488
- children: element
2489
- });
2490
- });
2491
- if (process.env.NODE_ENV !== "production") MenuPopup.displayName = "MenuPopup";
2492
-
2493
- const MenuPortalContext = /*#__PURE__*/React.createContext(undefined);
2494
- if (process.env.NODE_ENV !== "production") MenuPortalContext.displayName = "MenuPortalContext";
2495
- function useMenuPortalContext() {
2496
- const value = React.useContext(MenuPortalContext);
2497
- if (value === undefined) {
2498
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Menu.Portal> is missing.' : formatErrorMessage(32));
2499
- }
2500
- return value;
2501
- }
2502
-
2503
- const MenuPortal = /*#__PURE__*/React.forwardRef(function MenuPortal(props, forwardedRef) {
2504
- const {
2505
- keepMounted = false,
2506
- ...portalProps
2507
- } = props;
2508
- const {
2509
- store
2510
- } = useMenuRootContext();
2511
- const mounted = store.useState('mounted');
2512
- const shouldRender = mounted || keepMounted;
2513
- if (!shouldRender) {
2514
- return null;
2515
- }
2516
- return /*#__PURE__*/jsx(MenuPortalContext.Provider, {
2517
- value: keepMounted,
2518
- children: /*#__PURE__*/jsx(FloatingPortal, {
2519
- ref: forwardedRef,
2520
- ...portalProps
2521
- })
2522
- });
2523
- });
2524
- if (process.env.NODE_ENV !== "production") MenuPortal.displayName = "MenuPortal";
2525
-
2526
- /**
2527
- * Fork of the original `arrow` middleware from Floating UI that allows
2528
- * configuring the offset parent.
2529
- */
2530
- const baseArrow = options => ({
2531
- name: 'arrow',
2532
- options,
2533
- async fn(state) {
2534
- const {
2535
- x,
2536
- y,
2537
- placement,
2538
- rects,
2539
- platform,
2540
- elements,
2541
- middlewareData
2542
- } = state;
2543
- // Since `element` is required, we don't Partial<> the type.
2544
- const {
2545
- element,
2546
- padding = 0,
2547
- offsetParent = 'real'
2548
- } = evaluate(options, state) || {};
2549
- if (element == null) {
2550
- return {};
2551
- }
2552
- const paddingObject = getPaddingObject(padding);
2553
- const coords = {
2554
- x,
2555
- y
2556
- };
2557
- const axis = getAlignmentAxis(placement);
2558
- const length = getAxisLength(axis);
2559
- const arrowDimensions = await platform.getDimensions(element);
2560
- const isYAxis = axis === 'y';
2561
- const minProp = isYAxis ? 'top' : 'left';
2562
- const maxProp = isYAxis ? 'bottom' : 'right';
2563
- const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
2564
- const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
2565
- const startDiff = coords[axis] - rects.reference[axis];
2566
- const arrowOffsetParent = offsetParent === 'real' ? await platform.getOffsetParent?.(element) : elements.floating;
2567
- let clientSize = elements.floating[clientProp] || rects.floating[length];
2568
-
2569
- // DOM platform can return `window` as the `offsetParent`.
2570
- if (!clientSize || !(await platform.isElement?.(arrowOffsetParent))) {
2571
- clientSize = elements.floating[clientProp] || rects.floating[length];
2572
- }
2573
- const centerToReference = endDiff / 2 - startDiff / 2;
2574
-
2575
- // If the padding is large enough that it causes the arrow to no longer be
2576
- // centered, modify the padding so that it is centered.
2577
- const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
2578
- const minPadding = Math.min(paddingObject[minProp], largestPossiblePadding);
2579
- const maxPadding = Math.min(paddingObject[maxProp], largestPossiblePadding);
2580
-
2581
- // Make sure the arrow doesn't overflow the floating element if the center
2582
- // point is outside the floating element's bounds.
2583
- const min = minPadding;
2584
- const max = clientSize - arrowDimensions[length] - maxPadding;
2585
- const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
2586
- const offset = clamp(min, center, max);
2587
-
2588
- // If the reference is small enough that the arrow's padding causes it to
2589
- // to point to nothing for an aligned placement, adjust the offset of the
2590
- // floating element itself. To ensure `shift()` continues to take action,
2591
- // a single reset is performed when this is true.
2592
- const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
2593
- // eslint-disable-next-line no-nested-ternary
2594
- const alignmentOffset = shouldAddOffset ? center < min ? center - min : center - max : 0;
2595
- return {
2596
- [axis]: coords[axis] + alignmentOffset,
2597
- data: {
2598
- [axis]: offset,
2599
- centerOffset: center - offset - alignmentOffset,
2600
- ...(shouldAddOffset && {
2601
- alignmentOffset
2602
- })
2603
- },
2604
- reset: shouldAddOffset
2605
- };
2606
- }
2607
- });
2608
-
2609
- /**
2610
- * Provides data to position an inner element of the floating element so that it
2611
- * appears centered to the reference element.
2612
- * This wraps the core `arrow` middleware to allow React refs as the element.
2613
- * @see https://floating-ui.com/docs/arrow
2614
- */
2615
- const arrow = (options, deps) => ({
2616
- ...baseArrow(options),
2617
- options: [options, deps]
2106
+ const arrow = (options, deps) => ({
2107
+ ...baseArrow(options),
2108
+ options: [options, deps]
2618
2109
  });
2619
2110
 
2620
2111
  const hide = {
@@ -3094,149 +2585,693 @@ function isRef(param) {
3094
2585
  return param != null && 'current' in param;
3095
2586
  }
3096
2587
 
3097
- /* eslint-disable no-bitwise */
3098
- /**
3099
- * Provides context for a list of items in a composite component.
3100
- * @internal
3101
- */
3102
- function CompositeList(props) {
2588
+ function getDisabledMountTransitionStyles(transitionStatus) {
2589
+ return transitionStatus === 'starting' ? DISABLED_TRANSITIONS_STYLE : EMPTY_OBJECT;
2590
+ }
2591
+
2592
+ let IndexGuessBehavior = /*#__PURE__*/function (IndexGuessBehavior) {
2593
+ IndexGuessBehavior[IndexGuessBehavior["None"] = 0] = "None";
2594
+ IndexGuessBehavior[IndexGuessBehavior["GuessFromOrder"] = 1] = "GuessFromOrder";
2595
+ return IndexGuessBehavior;
2596
+ }({});
2597
+
2598
+ /**
2599
+ * Used to register a list item and its index (DOM position) in the `CompositeList`.
2600
+ */
2601
+ function useCompositeListItem(params = {}) {
3103
2602
  const {
3104
- children,
2603
+ label,
2604
+ metadata,
2605
+ textRef,
2606
+ indexGuessBehavior,
2607
+ index: externalIndex
2608
+ } = params;
2609
+ const {
2610
+ register,
2611
+ unregister,
2612
+ subscribeMapChange,
3105
2613
  elementsRef,
3106
2614
  labelsRef,
3107
- onMapChange: onMapChangeProp
3108
- } = props;
3109
- const onMapChange = useStableCallback(onMapChangeProp);
3110
- const nextIndexRef = React.useRef(0);
3111
- const listeners = useRefWithInit(createListeners).current;
3112
-
3113
- // We use a stable `map` to avoid O(n^2) re-allocation costs for large lists.
3114
- // `mapTick` is our re-render trigger mechanism. We also need to update the
3115
- // elements and label refs, but there's a lot of async work going on and sometimes
3116
- // the effect that handles `onMapChange` gets called after those refs have been
3117
- // filled, and we don't want to lose those values by setting their lengths to `0`.
3118
- // We also need to have them at the proper length because floating-ui uses that
3119
- // information for list navigation.
3120
-
3121
- const map = useRefWithInit(createMap).current;
3122
- // `mapTick` uses a counter rather than objects for low precision-loss risk and better memory efficiency
3123
- const [mapTick, setMapTick] = React.useState(0);
3124
- const lastTickRef = React.useRef(mapTick);
3125
- const register = useStableCallback((node, metadata) => {
3126
- map.set(node, metadata ?? null);
3127
- lastTickRef.current += 1;
3128
- setMapTick(lastTickRef.current);
3129
- });
3130
- const unregister = useStableCallback(node => {
3131
- map.delete(node);
3132
- lastTickRef.current += 1;
3133
- setMapTick(lastTickRef.current);
3134
- });
3135
- const sortedMap = React.useMemo(() => {
3136
- const newMap = new Map();
3137
- // Filter out disconnected elements before sorting to avoid inconsistent
3138
- // compareDocumentPosition results when elements are detached from the DOM.
3139
- const sortedNodes = Array.from(map.keys()).filter(node => node.isConnected).sort(sortByDocumentPosition);
3140
- sortedNodes.forEach((node, index) => {
3141
- const metadata = map.get(node) ?? {};
3142
- newMap.set(node, {
3143
- ...metadata,
3144
- index
3145
- });
3146
- });
3147
- return newMap;
3148
- }, [map, mapTick]);
2615
+ nextIndexRef
2616
+ } = useCompositeListContext();
2617
+ const indexRef = React.useRef(-1);
2618
+ const [index, setIndex] = React.useState(externalIndex ?? (indexGuessBehavior === IndexGuessBehavior.GuessFromOrder ? () => {
2619
+ if (indexRef.current === -1) {
2620
+ const newIndex = nextIndexRef.current;
2621
+ nextIndexRef.current += 1;
2622
+ indexRef.current = newIndex;
2623
+ }
2624
+ return indexRef.current;
2625
+ } : -1));
2626
+ const componentRef = React.useRef(null);
2627
+ const ref = React.useCallback(node => {
2628
+ componentRef.current = node;
2629
+ if (index !== -1 && node !== null) {
2630
+ elementsRef.current[index] = node;
2631
+ if (labelsRef) {
2632
+ const isLabelDefined = label !== undefined;
2633
+ labelsRef.current[index] = isLabelDefined ? label : textRef?.current?.textContent ?? node.textContent;
2634
+ }
2635
+ }
2636
+ }, [index, elementsRef, labelsRef, label, textRef]);
3149
2637
  useIsoLayoutEffect(() => {
3150
- if (typeof MutationObserver !== 'function' || sortedMap.size === 0) {
2638
+ if (externalIndex != null) {
3151
2639
  return undefined;
3152
2640
  }
3153
- const mutationObserver = new MutationObserver(entries => {
3154
- const diff = new Set();
3155
- const updateDiff = node => diff.has(node) ? diff.delete(node) : diff.add(node);
3156
- entries.forEach(entry => {
3157
- entry.removedNodes.forEach(updateDiff);
3158
- entry.addedNodes.forEach(updateDiff);
3159
- });
3160
- if (diff.size === 0) {
3161
- lastTickRef.current += 1;
3162
- setMapTick(lastTickRef.current);
3163
- }
3164
- });
3165
- sortedMap.forEach((_, node) => {
3166
- if (node.parentElement) {
3167
- mutationObserver.observe(node.parentElement, {
3168
- childList: true
3169
- });
3170
- }
3171
- });
3172
- return () => {
3173
- mutationObserver.disconnect();
3174
- };
3175
- }, [sortedMap]);
2641
+ const node = componentRef.current;
2642
+ if (node) {
2643
+ register(node, metadata);
2644
+ return () => {
2645
+ unregister(node);
2646
+ };
2647
+ }
2648
+ return undefined;
2649
+ }, [externalIndex, register, unregister, metadata]);
3176
2650
  useIsoLayoutEffect(() => {
3177
- const shouldUpdateLengths = lastTickRef.current === mapTick;
3178
- if (shouldUpdateLengths) {
3179
- if (elementsRef.current.length !== sortedMap.size) {
3180
- elementsRef.current.length = sortedMap.size;
2651
+ if (externalIndex != null) {
2652
+ return undefined;
2653
+ }
2654
+ return subscribeMapChange(map => {
2655
+ const i = componentRef.current ? map.get(componentRef.current)?.index : null;
2656
+ if (i != null) {
2657
+ setIndex(i);
3181
2658
  }
3182
- if (labelsRef && labelsRef.current.length !== sortedMap.size) {
3183
- labelsRef.current.length = sortedMap.size;
2659
+ });
2660
+ }, [externalIndex, subscribeMapChange, setIndex]);
2661
+ return React.useMemo(() => ({
2662
+ ref,
2663
+ index
2664
+ }), [index, ref]);
2665
+ }
2666
+
2667
+ /**
2668
+ * A button that closes the dialog.
2669
+ * Renders a `<button>` element.
2670
+ *
2671
+ * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
2672
+ */
2673
+ const DialogClose = /*#__PURE__*/React.forwardRef(function DialogClose(componentProps, forwardedRef) {
2674
+ const {
2675
+ render,
2676
+ className,
2677
+ disabled = false,
2678
+ nativeButton = true,
2679
+ ...elementProps
2680
+ } = componentProps;
2681
+ const {
2682
+ store
2683
+ } = useDialogRootContext();
2684
+ const open = store.useState('open');
2685
+ function handleClick(event) {
2686
+ if (open) {
2687
+ store.setOpen(false, createChangeEventDetails(closePress, event.nativeEvent));
2688
+ }
2689
+ }
2690
+ const {
2691
+ getButtonProps,
2692
+ buttonRef
2693
+ } = useButton({
2694
+ disabled,
2695
+ native: nativeButton
2696
+ });
2697
+ const state = {
2698
+ disabled
2699
+ };
2700
+ return useRenderElement('button', componentProps, {
2701
+ state,
2702
+ ref: [forwardedRef, buttonRef],
2703
+ props: [{
2704
+ onClick: handleClick
2705
+ }, elementProps, getButtonProps]
2706
+ });
2707
+ });
2708
+ if (process.env.NODE_ENV !== "production") DialogClose.displayName = "DialogClose";
2709
+
2710
+ /**
2711
+ * A button that closes the drawer.
2712
+ * Renders a `<button>` element.
2713
+ *
2714
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
2715
+ */
2716
+ const DrawerClose = DialogClose;
2717
+
2718
+ /**
2719
+ * A heading that labels the dialog.
2720
+ * Renders an `<h2>` element.
2721
+ *
2722
+ * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
2723
+ */
2724
+ const DialogTitle = /*#__PURE__*/React.forwardRef(function DialogTitle(componentProps, forwardedRef) {
2725
+ const {
2726
+ render,
2727
+ className,
2728
+ id: idProp,
2729
+ ...elementProps
2730
+ } = componentProps;
2731
+ const {
2732
+ store
2733
+ } = useDialogRootContext();
2734
+ const id = useBaseUiId(idProp);
2735
+ store.useSyncedValueWithCleanup('titleElementId', id);
2736
+ return useRenderElement('h2', componentProps, {
2737
+ ref: forwardedRef,
2738
+ props: [{
2739
+ id
2740
+ }, elementProps]
2741
+ });
2742
+ });
2743
+ if (process.env.NODE_ENV !== "production") DialogTitle.displayName = "DialogTitle";
2744
+
2745
+ /**
2746
+ * A heading that labels the drawer.
2747
+ * Renders an `<h2>` element.
2748
+ *
2749
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
2750
+ */
2751
+ const DrawerTitle = DialogTitle;
2752
+
2753
+ const MenuPositionerContext = /*#__PURE__*/React.createContext(undefined);
2754
+ if (process.env.NODE_ENV !== "production") MenuPositionerContext.displayName = "MenuPositionerContext";
2755
+ function useMenuPositionerContext(optional) {
2756
+ const context = React.useContext(MenuPositionerContext);
2757
+ if (context === undefined && !optional) {
2758
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuPositionerContext is missing. MenuPositioner parts must be placed within <Menu.Positioner>.' : formatErrorMessage(33));
2759
+ }
2760
+ return context;
2761
+ }
2762
+
2763
+ const MenuRootContext = /*#__PURE__*/React.createContext(undefined);
2764
+ if (process.env.NODE_ENV !== "production") MenuRootContext.displayName = "MenuRootContext";
2765
+ function useMenuRootContext(optional) {
2766
+ const context = React.useContext(MenuRootContext);
2767
+ if (context === undefined && !optional) {
2768
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuRootContext is missing. Menu parts must be placed within <Menu.Root>.' : formatErrorMessage(36));
2769
+ }
2770
+ return context;
2771
+ }
2772
+
2773
+ const ContextMenuRootContext = /*#__PURE__*/React.createContext(undefined);
2774
+ if (process.env.NODE_ENV !== "production") ContextMenuRootContext.displayName = "ContextMenuRootContext";
2775
+ function useContextMenuRootContext(optional = true) {
2776
+ const context = React.useContext(ContextMenuRootContext);
2777
+ if (context === undefined && !optional) {
2778
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ContextMenuRootContext is missing. ContextMenu parts must be placed within <ContextMenu.Root>.' : formatErrorMessage(25));
2779
+ }
2780
+ return context;
2781
+ }
2782
+
2783
+ /**
2784
+ * Returns common props shared by all menu item types.
2785
+ * This hook extracts the shared logic for id, role, tabIndex, onKeyDown,
2786
+ * onMouseMove, onClick, and onMouseUp handlers.
2787
+ */
2788
+ function useMenuItemCommonProps(params) {
2789
+ const {
2790
+ closeOnClick,
2791
+ highlighted,
2792
+ id,
2793
+ nodeId,
2794
+ store,
2795
+ typingRef,
2796
+ itemRef,
2797
+ itemMetadata
2798
+ } = params;
2799
+ const {
2800
+ events: menuEvents
2801
+ } = store.useState('floatingTreeRoot');
2802
+ const contextMenuContext = useContextMenuRootContext(true);
2803
+ const isContextMenu = contextMenuContext !== undefined;
2804
+ return React.useMemo(() => ({
2805
+ id,
2806
+ role: 'menuitem',
2807
+ tabIndex: highlighted ? 0 : -1,
2808
+ onKeyDown(event) {
2809
+ if (event.key === ' ' && typingRef?.current) {
2810
+ event.preventDefault();
2811
+ }
2812
+ },
2813
+ onMouseMove(event) {
2814
+ if (!nodeId) {
2815
+ return;
2816
+ }
2817
+
2818
+ // Inform the floating tree that a menu item within this menu was hovered/moved over
2819
+ // so unrelated descendant submenus can be closed.
2820
+ menuEvents.emit('itemhover', {
2821
+ nodeId,
2822
+ target: event.currentTarget
2823
+ });
2824
+ },
2825
+ onClick(event) {
2826
+ if (closeOnClick) {
2827
+ menuEvents.emit('close', {
2828
+ domEvent: event,
2829
+ reason: itemPress
2830
+ });
2831
+ }
2832
+ },
2833
+ onMouseUp(event) {
2834
+ if (contextMenuContext) {
2835
+ const initialCursorPoint = contextMenuContext.initialCursorPointRef.current;
2836
+ contextMenuContext.initialCursorPointRef.current = null;
2837
+ if (isContextMenu && initialCursorPoint && Math.abs(event.clientX - initialCursorPoint.x) <= 1 && Math.abs(event.clientY - initialCursorPoint.y) <= 1) {
2838
+ return;
2839
+ }
2840
+
2841
+ // On non-macOS platforms, this mouseup belongs to the right-click gesture
2842
+ // that opened the context menu, so it must not activate an item.
2843
+ if (isContextMenu && !isMac && event.button === 2) {
2844
+ return;
2845
+ }
2846
+ }
2847
+ if (itemRef.current && store.context.allowMouseUpTriggerRef.current && (!isContextMenu || event.button === 2)) {
2848
+ // This fires whenever the user clicks on the trigger, moves the cursor, and releases it over the item.
2849
+ // We trigger the click and override the `closeOnClick` preference to always close the menu.
2850
+ if (!itemMetadata || itemMetadata.type === 'regular-item') {
2851
+ itemRef.current.click();
2852
+ }
2853
+ }
2854
+ }
2855
+ }), [closeOnClick, highlighted, id, menuEvents, nodeId, store, typingRef, itemRef, contextMenuContext, isContextMenu, itemMetadata]);
2856
+ }
2857
+
2858
+ const REGULAR_ITEM = {
2859
+ type: 'regular-item'
2860
+ };
2861
+ function useMenuItem(params) {
2862
+ const {
2863
+ closeOnClick,
2864
+ disabled = false,
2865
+ highlighted,
2866
+ id,
2867
+ store,
2868
+ typingRef = store.context.typingRef,
2869
+ nativeButton,
2870
+ itemMetadata,
2871
+ nodeId
2872
+ } = params;
2873
+ const itemRef = React.useRef(null);
2874
+ const {
2875
+ getButtonProps,
2876
+ buttonRef
2877
+ } = useButton({
2878
+ disabled,
2879
+ focusableWhenDisabled: true,
2880
+ native: nativeButton,
2881
+ composite: true
2882
+ });
2883
+ const commonProps = useMenuItemCommonProps({
2884
+ closeOnClick,
2885
+ highlighted,
2886
+ id,
2887
+ nodeId,
2888
+ store,
2889
+ typingRef,
2890
+ itemRef,
2891
+ itemMetadata
2892
+ });
2893
+ const getItemProps = React.useCallback(externalProps => {
2894
+ return mergeProps(commonProps, {
2895
+ onMouseEnter() {
2896
+ if (itemMetadata.type !== 'submenu-trigger') {
2897
+ return;
2898
+ }
2899
+ itemMetadata.setActive();
2900
+ }
2901
+ }, externalProps, getButtonProps);
2902
+ }, [commonProps, getButtonProps, itemMetadata]);
2903
+ const mergedRef = useMergedRefs(itemRef, buttonRef);
2904
+ return React.useMemo(() => ({
2905
+ getItemProps,
2906
+ itemRef: mergedRef
2907
+ }), [getItemProps, mergedRef]);
2908
+ }
2909
+
2910
+ let MenuCheckboxItemDataAttributes = /*#__PURE__*/function (MenuCheckboxItemDataAttributes) {
2911
+ /**
2912
+ * Present when the menu checkbox item is checked.
2913
+ */
2914
+ MenuCheckboxItemDataAttributes["checked"] = "data-checked";
2915
+ /**
2916
+ * Present when the menu checkbox item is not checked.
2917
+ */
2918
+ MenuCheckboxItemDataAttributes["unchecked"] = "data-unchecked";
2919
+ /**
2920
+ * Present when the menu checkbox item is disabled.
2921
+ */
2922
+ MenuCheckboxItemDataAttributes["disabled"] = "data-disabled";
2923
+ /**
2924
+ * Present when the menu checkbox item is highlighted.
2925
+ */
2926
+ MenuCheckboxItemDataAttributes["highlighted"] = "data-highlighted";
2927
+ return MenuCheckboxItemDataAttributes;
2928
+ }({});
2929
+
2930
+ const itemMapping = {
2931
+ checked(value) {
2932
+ if (value) {
2933
+ return {
2934
+ [MenuCheckboxItemDataAttributes.checked]: ''
2935
+ };
2936
+ }
2937
+ return {
2938
+ [MenuCheckboxItemDataAttributes.unchecked]: ''
2939
+ };
2940
+ },
2941
+ ...transitionStatusMapping
2942
+ };
2943
+
2944
+ const MenuGroupContext = /*#__PURE__*/React.createContext(undefined);
2945
+ if (process.env.NODE_ENV !== "production") MenuGroupContext.displayName = "MenuGroupContext";
2946
+ function useMenuGroupRootContext() {
2947
+ const context = React.useContext(MenuGroupContext);
2948
+ if (context === undefined) {
2949
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.' : formatErrorMessage(31));
2950
+ }
2951
+ return context;
2952
+ }
2953
+
2954
+ const MenuGroup = /*#__PURE__*/React.forwardRef(function MenuGroup(componentProps, forwardedRef) {
2955
+ const {
2956
+ render,
2957
+ className,
2958
+ ...elementProps
2959
+ } = componentProps;
2960
+ const [labelId, setLabelId] = React.useState(undefined);
2961
+ const context = React.useMemo(() => ({
2962
+ setLabelId
2963
+ }), [setLabelId]);
2964
+ const element = useRenderElement('div', componentProps, {
2965
+ ref: forwardedRef,
2966
+ props: {
2967
+ role: 'group',
2968
+ 'aria-labelledby': labelId,
2969
+ ...elementProps
2970
+ }
2971
+ });
2972
+ return /*#__PURE__*/jsx(MenuGroupContext.Provider, {
2973
+ value: context,
2974
+ children: element
2975
+ });
2976
+ });
2977
+ if (process.env.NODE_ENV !== "production") MenuGroup.displayName = "MenuGroup";
2978
+
2979
+ /**
2980
+ * An accessible label that is automatically associated with its parent group.
2981
+ * Renders a `<div>` element.
2982
+ *
2983
+ * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2984
+ */
2985
+ const MenuGroupLabel = /*#__PURE__*/React.forwardRef(function MenuGroupLabelComponent(componentProps, forwardedRef) {
2986
+ const {
2987
+ className,
2988
+ render,
2989
+ id: idProp,
2990
+ ...elementProps
2991
+ } = componentProps;
2992
+ const id = useBaseUiId(idProp);
2993
+ const {
2994
+ setLabelId
2995
+ } = useMenuGroupRootContext();
2996
+ useIsoLayoutEffect(() => {
2997
+ setLabelId(id);
2998
+ return () => {
2999
+ setLabelId(undefined);
3000
+ };
3001
+ }, [setLabelId, id]);
3002
+ return useRenderElement('div', componentProps, {
3003
+ ref: forwardedRef,
3004
+ props: {
3005
+ id,
3006
+ role: 'presentation',
3007
+ ...elementProps
3008
+ }
3009
+ });
3010
+ });
3011
+ if (process.env.NODE_ENV !== "production") MenuGroupLabel.displayName = "MenuGroupLabel";
3012
+
3013
+ /**
3014
+ * An individual interactive item in the menu.
3015
+ * Renders a `<div>` element.
3016
+ *
3017
+ * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
3018
+ */
3019
+ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(componentProps, forwardedRef) {
3020
+ const {
3021
+ render,
3022
+ className,
3023
+ id: idProp,
3024
+ label,
3025
+ nativeButton = false,
3026
+ disabled = false,
3027
+ closeOnClick = true,
3028
+ ...elementProps
3029
+ } = componentProps;
3030
+ const listItem = useCompositeListItem({
3031
+ label
3032
+ });
3033
+ const menuPositionerContext = useMenuPositionerContext(true);
3034
+ const id = useBaseUiId(idProp);
3035
+ const {
3036
+ store
3037
+ } = useMenuRootContext();
3038
+ const highlighted = store.useState('isActive', listItem.index);
3039
+ const itemProps = store.useState('itemProps');
3040
+ const {
3041
+ getItemProps,
3042
+ itemRef
3043
+ } = useMenuItem({
3044
+ closeOnClick,
3045
+ disabled,
3046
+ highlighted,
3047
+ id,
3048
+ store,
3049
+ nativeButton,
3050
+ nodeId: menuPositionerContext?.nodeId,
3051
+ itemMetadata: REGULAR_ITEM
3052
+ });
3053
+ const state = {
3054
+ disabled,
3055
+ highlighted
3056
+ };
3057
+ return useRenderElement('div', componentProps, {
3058
+ state,
3059
+ props: [itemProps, elementProps, getItemProps],
3060
+ ref: [itemRef, forwardedRef, listItem.ref]
3061
+ });
3062
+ });
3063
+ if (process.env.NODE_ENV !== "production") MenuItem.displayName = "MenuItem";
3064
+
3065
+ /**
3066
+ * A link in the menu that can be used to navigate to a different page or section.
3067
+ * Renders an `<a>` element.
3068
+ *
3069
+ * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
3070
+ */
3071
+ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(componentProps, forwardedRef) {
3072
+ const {
3073
+ render,
3074
+ className,
3075
+ id: idProp,
3076
+ label,
3077
+ closeOnClick = false,
3078
+ ...elementProps
3079
+ } = componentProps;
3080
+ const linkRef = React.useRef(null);
3081
+ const listItem = useCompositeListItem({
3082
+ label
3083
+ });
3084
+ const menuPositionerContext = useMenuPositionerContext(true);
3085
+ const nodeId = menuPositionerContext?.nodeId;
3086
+ const id = useBaseUiId(idProp);
3087
+ const {
3088
+ store
3089
+ } = useMenuRootContext();
3090
+ const highlighted = store.useState('isActive', listItem.index);
3091
+ const itemProps = store.useState('itemProps');
3092
+ const typingRef = store.context.typingRef;
3093
+ const {
3094
+ getButtonProps,
3095
+ buttonRef
3096
+ } = useButton({
3097
+ native: false,
3098
+ composite: true
3099
+ });
3100
+ const commonProps = useMenuItemCommonProps({
3101
+ closeOnClick,
3102
+ highlighted,
3103
+ id,
3104
+ nodeId,
3105
+ store,
3106
+ typingRef,
3107
+ itemRef: linkRef
3108
+ });
3109
+ function getItemProps(externalProps) {
3110
+ return mergeProps(commonProps, externalProps, getButtonProps);
3111
+ }
3112
+ const state = React.useMemo(() => ({
3113
+ highlighted
3114
+ }), [highlighted]);
3115
+ return useRenderElement('a', componentProps, {
3116
+ state,
3117
+ props: [itemProps, elementProps, getItemProps],
3118
+ ref: [linkRef, buttonRef, forwardedRef, listItem.ref]
3119
+ });
3120
+ });
3121
+ if (process.env.NODE_ENV !== "production") MenuLinkItem.displayName = "MenuLinkItem";
3122
+
3123
+ const ToolbarRootContext = /*#__PURE__*/React.createContext(undefined);
3124
+ if (process.env.NODE_ENV !== "production") ToolbarRootContext.displayName = "ToolbarRootContext";
3125
+ function useToolbarRootContext(optional) {
3126
+ const context = React.useContext(ToolbarRootContext);
3127
+ if (context === undefined && !optional) {
3128
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: ToolbarRootContext is missing. Toolbar parts must be placed within <Toolbar.Root>.' : formatErrorMessage(69));
3129
+ }
3130
+ return context;
3131
+ }
3132
+
3133
+ const stateAttributesMapping = {
3134
+ ...popupStateMapping,
3135
+ ...transitionStatusMapping
3136
+ };
3137
+
3138
+ /**
3139
+ * A container for the menu items.
3140
+ * Renders a `<div>` element.
3141
+ *
3142
+ * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
3143
+ */
3144
+ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProps, forwardedRef) {
3145
+ const {
3146
+ render,
3147
+ className,
3148
+ finalFocus,
3149
+ ...elementProps
3150
+ } = componentProps;
3151
+ const {
3152
+ store
3153
+ } = useMenuRootContext();
3154
+ const {
3155
+ side,
3156
+ align
3157
+ } = useMenuPositionerContext();
3158
+ const insideToolbar = useToolbarRootContext(true) != null;
3159
+ const open = store.useState('open');
3160
+ const transitionStatus = store.useState('transitionStatus');
3161
+ const popupProps = store.useState('popupProps');
3162
+ const mounted = store.useState('mounted');
3163
+ const instantType = store.useState('instantType');
3164
+ const triggerElement = store.useState('activeTriggerElement');
3165
+ const parent = store.useState('parent');
3166
+ const lastOpenChangeReason = store.useState('lastOpenChangeReason');
3167
+ const rootId = store.useState('rootId');
3168
+ const floatingContext = store.useState('floatingRootContext');
3169
+ const floatingTreeRoot = store.useState('floatingTreeRoot');
3170
+ const closeDelay = store.useState('closeDelay');
3171
+ const activeTriggerElement = store.useState('activeTriggerElement');
3172
+ const isContextMenu = parent.type === 'context-menu';
3173
+ useOpenChangeComplete({
3174
+ open,
3175
+ ref: store.context.popupRef,
3176
+ onComplete() {
3177
+ if (open) {
3178
+ store.context.onOpenChangeComplete?.(true);
3184
3179
  }
3185
- nextIndexRef.current = sortedMap.size;
3186
3180
  }
3187
- onMapChange(sortedMap);
3188
- }, [onMapChange, sortedMap, elementsRef, labelsRef, mapTick]);
3189
- useIsoLayoutEffect(() => {
3190
- return () => {
3191
- elementsRef.current = [];
3192
- };
3193
- }, [elementsRef]);
3194
- useIsoLayoutEffect(() => {
3195
- return () => {
3196
- if (labelsRef) {
3197
- labelsRef.current = [];
3198
- }
3199
- };
3200
- }, [labelsRef]);
3201
- const subscribeMapChange = useStableCallback(fn => {
3202
- listeners.add(fn);
3181
+ });
3182
+ React.useEffect(() => {
3183
+ function handleClose(event) {
3184
+ store.setOpen(false, createChangeEventDetails(event.reason, event.domEvent));
3185
+ }
3186
+ floatingTreeRoot.events.on('close', handleClose);
3203
3187
  return () => {
3204
- listeners.delete(fn);
3188
+ floatingTreeRoot.events.off('close', handleClose);
3205
3189
  };
3190
+ }, [floatingTreeRoot.events, store]);
3191
+ const hoverEnabled = store.useState('hoverEnabled');
3192
+ const disabled = store.useState('disabled');
3193
+ useHoverFloatingInteraction(floatingContext, {
3194
+ enabled: hoverEnabled && !disabled && !isContextMenu && parent.type !== 'menubar',
3195
+ closeDelay
3206
3196
  });
3207
- useIsoLayoutEffect(() => {
3208
- listeners.forEach(l => l(sortedMap));
3209
- }, [listeners, sortedMap]);
3210
- const contextValue = React.useMemo(() => ({
3211
- register,
3212
- unregister,
3213
- subscribeMapChange,
3214
- elementsRef,
3215
- labelsRef,
3216
- nextIndexRef
3217
- }), [register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef]);
3218
- return /*#__PURE__*/jsx(CompositeListContext.Provider, {
3219
- value: contextValue,
3220
- children: children
3197
+ const state = {
3198
+ transitionStatus,
3199
+ side,
3200
+ align,
3201
+ open,
3202
+ nested: parent.type === 'menu',
3203
+ instant: instantType
3204
+ };
3205
+ const setPopupElement = React.useCallback(element => {
3206
+ store.set('popupElement', element);
3207
+ }, [store]);
3208
+ const element = useRenderElement('div', componentProps, {
3209
+ state,
3210
+ ref: [forwardedRef, store.context.popupRef, setPopupElement],
3211
+ stateAttributesMapping,
3212
+ props: [popupProps, {
3213
+ onKeyDown(event) {
3214
+ if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {
3215
+ event.stopPropagation();
3216
+ }
3217
+ }
3218
+ }, getDisabledMountTransitionStyles(transitionStatus), elementProps, {
3219
+ 'data-rootownerid': rootId
3220
+ }]
3221
3221
  });
3222
- }
3223
- function createMap() {
3224
- return new Map();
3225
- }
3226
- function createListeners() {
3227
- return new Set();
3228
- }
3229
- function sortByDocumentPosition(a, b) {
3230
- const position = a.compareDocumentPosition(b);
3231
- if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
3232
- return -1;
3222
+ let returnFocus = parent.type === undefined || isContextMenu;
3223
+ if (triggerElement || parent.type === 'menubar' && lastOpenChangeReason !== outsidePress) {
3224
+ returnFocus = true;
3233
3225
  }
3234
- if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
3235
- return 1;
3226
+ return /*#__PURE__*/jsx(FloatingFocusManager, {
3227
+ context: floatingContext,
3228
+ modal: isContextMenu,
3229
+ disabled: !mounted,
3230
+ returnFocus: finalFocus === undefined ? returnFocus : finalFocus,
3231
+ initialFocus: parent.type !== 'menu',
3232
+ restoreFocus: true,
3233
+ externalTree: parent.type !== 'menubar' ? floatingTreeRoot : undefined,
3234
+ previousFocusableElement: activeTriggerElement,
3235
+ nextFocusableElement: parent.type === undefined ? store.context.triggerFocusTargetRef : undefined,
3236
+ beforeContentFocusGuardRef: parent.type === undefined ? store.context.beforeContentFocusGuardRef : undefined,
3237
+ children: element
3238
+ });
3239
+ });
3240
+ if (process.env.NODE_ENV !== "production") MenuPopup.displayName = "MenuPopup";
3241
+
3242
+ const MenuPortalContext = /*#__PURE__*/React.createContext(undefined);
3243
+ if (process.env.NODE_ENV !== "production") MenuPortalContext.displayName = "MenuPortalContext";
3244
+ function useMenuPortalContext() {
3245
+ const value = React.useContext(MenuPortalContext);
3246
+ if (value === undefined) {
3247
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Menu.Portal> is missing.' : formatErrorMessage(32));
3236
3248
  }
3237
- return 0;
3249
+ return value;
3238
3250
  }
3239
3251
 
3252
+ const MenuPortal = /*#__PURE__*/React.forwardRef(function MenuPortal(props, forwardedRef) {
3253
+ const {
3254
+ keepMounted = false,
3255
+ ...portalProps
3256
+ } = props;
3257
+ const {
3258
+ store
3259
+ } = useMenuRootContext();
3260
+ const mounted = store.useState('mounted');
3261
+ const shouldRender = mounted || keepMounted;
3262
+ if (!shouldRender) {
3263
+ return null;
3264
+ }
3265
+ return /*#__PURE__*/jsx(MenuPortalContext.Provider, {
3266
+ value: keepMounted,
3267
+ children: /*#__PURE__*/jsx(FloatingPortal, {
3268
+ ref: forwardedRef,
3269
+ ...portalProps
3270
+ })
3271
+ });
3272
+ });
3273
+ if (process.env.NODE_ENV !== "production") MenuPortal.displayName = "MenuPortal";
3274
+
3240
3275
  const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(componentProps, forwardedRef) {
3241
3276
  const {
3242
3277
  anchor: anchorProp,
@@ -4247,41 +4282,6 @@ function MenuSubmenuRoot(props) {
4247
4282
  });
4248
4283
  }
4249
4284
 
4250
- function getPseudoElementBounds(element) {
4251
- const elementRect = element.getBoundingClientRect();
4252
-
4253
- // Avoid "Not implemented: window.getComputedStyle(elt, pseudoElt)"
4254
- if (process.env.NODE_ENV !== 'production') {
4255
- return elementRect;
4256
- }
4257
- const beforeStyles = window.getComputedStyle(element, '::before');
4258
- const afterStyles = window.getComputedStyle(element, '::after');
4259
- const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';
4260
- if (!hasPseudoElements) {
4261
- return elementRect;
4262
- }
4263
-
4264
- // Get dimensions of pseudo-elements
4265
- const beforeWidth = parseFloat(beforeStyles.width) || 0;
4266
- const beforeHeight = parseFloat(beforeStyles.height) || 0;
4267
- const afterWidth = parseFloat(afterStyles.width) || 0;
4268
- const afterHeight = parseFloat(afterStyles.height) || 0;
4269
-
4270
- // Calculate max dimensions including pseudo-elements
4271
- const totalWidth = Math.max(elementRect.width, beforeWidth, afterWidth);
4272
- const totalHeight = Math.max(elementRect.height, beforeHeight, afterHeight);
4273
-
4274
- // Calculate the differences to extend the bounds
4275
- const widthDiff = totalWidth - elementRect.width;
4276
- const heightDiff = totalHeight - elementRect.height;
4277
- return {
4278
- left: elementRect.left - widthDiff / 2,
4279
- right: elementRect.right + widthDiff / 2,
4280
- top: elementRect.top - heightDiff / 2,
4281
- bottom: elementRect.bottom + heightDiff / 2
4282
- };
4283
- }
4284
-
4285
4285
  function useCompositeItem(params = {}) {
4286
4286
  const {
4287
4287
  highlightItemOnHover,
@@ -4797,4 +4797,4 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
4797
4797
  });
4798
4798
  if (process.env.NODE_ENV !== "production") MenuSubmenuTrigger.displayName = "MenuSubmenuTrigger";
4799
4799
 
4800
- export { getDisabledMountTransitionStyles as A, CompositeList as C, DrawerTitle as D, IndexGuessBehavior as I, MenuTrigger as M, REGULAR_ITEM as R, DrawerClose as a, MenuLinkItem as b, MenuItem as c, MenuRadioItem as d, MenuRadioItemIndicator as e, MenuSubmenuTrigger as f, MenuRoot as g, MenuPortal as h, MenuPositioner as i, MenuPopup as j, MenuRadioGroup as k, MenuGroup as l, MenuSubmenuRoot as m, MenuGroupLabel as n, useMenuPositionerContext as o, useContextMenuRootContext as p, useCompositeListItem as q, useMenuItem as r, itemMapping as s, MenuStore as t, useMenuRootContext as u, useFloatingRootContext as v, useListNavigation as w, useTypeahead as x, getPseudoElementBounds as y, useAnchorPositioning as z };
4800
+ export { MenuStore as A, CompositeList as C, DrawerTitle as D, IndexGuessBehavior as I, MenuTrigger as M, REGULAR_ITEM as R, DrawerClose as a, MenuLinkItem as b, MenuItem as c, MenuRadioItem as d, MenuRadioItemIndicator as e, MenuSubmenuTrigger as f, MenuRoot as g, MenuPortal as h, MenuPositioner as i, MenuPopup as j, MenuRadioGroup as k, MenuGroup as l, MenuSubmenuRoot as m, MenuGroupLabel as n, useListNavigation as o, useTypeahead as p, getPseudoElementBounds as q, useAnchorPositioning as r, getDisabledMountTransitionStyles as s, useCompositeListItem as t, useFloatingRootContext as u, useMenuRootContext as v, useMenuPositionerContext as w, useContextMenuRootContext as x, useMenuItem as y, itemMapping as z };