@jobber/components 9.5.3 → 9.6.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.
Files changed (178) hide show
  1. package/Combobox.d.ts +1 -0
  2. package/Combobox.js +17 -0
  3. package/dist/Autocomplete/index.cjs +0 -2
  4. package/dist/Autocomplete/index.mjs +0 -2
  5. package/dist/Autocomplete-cjs.js +4 -5
  6. package/dist/Autocomplete-es.js +1 -2
  7. package/dist/Card/index.cjs +2 -2
  8. package/dist/Card/index.mjs +2 -2
  9. package/dist/Chip/index.cjs +0 -2
  10. package/dist/Chip/index.mjs +0 -2
  11. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
  12. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
  13. package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
  14. package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
  15. package/dist/Chips/index.cjs +0 -2
  16. package/dist/Chips/index.mjs +0 -2
  17. package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
  18. package/dist/Combobox/Combobox.context.d.ts +34 -0
  19. package/dist/Combobox/Combobox.d.ts +69 -0
  20. package/dist/Combobox/Combobox.footer.d.ts +7 -0
  21. package/dist/Combobox/Combobox.items.d.ts +25 -0
  22. package/dist/Combobox/Combobox.responsive.d.ts +54 -0
  23. package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
  24. package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
  25. package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
  26. package/dist/Combobox/Combobox.types.d.ts +166 -0
  27. package/dist/Combobox/index.cjs +35 -0
  28. package/dist/Combobox/index.d.ts +2 -0
  29. package/dist/Combobox/index.mjs +29 -0
  30. package/dist/Combobox-cjs.js +675 -0
  31. package/dist/Combobox-es.js +673 -0
  32. package/dist/ComboboxPrimitive-cjs.js +88 -47
  33. package/dist/ComboboxPrimitive-es.js +39 -23
  34. package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
  35. package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
  36. package/dist/ConfirmationModal/index.cjs +0 -2
  37. package/dist/ConfirmationModal/index.mjs +0 -2
  38. package/dist/ContentBlock/index.cjs +1 -0
  39. package/dist/ContentBlock/index.mjs +1 -0
  40. package/dist/ContentBlock-cjs.js +2 -18
  41. package/dist/ContentBlock-es.js +1 -17
  42. package/dist/DataDump/index.cjs +2 -2
  43. package/dist/DataDump/index.mjs +2 -2
  44. package/dist/DataList/components/DataListActions/index.cjs +0 -2
  45. package/dist/DataList/components/DataListActions/index.mjs +0 -2
  46. package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
  47. package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
  48. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
  49. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
  50. package/dist/DataList/components/DataListFilters/index.cjs +0 -2
  51. package/dist/DataList/components/DataListFilters/index.mjs +0 -2
  52. package/dist/DataList/components/DataListHeader/index.cjs +0 -2
  53. package/dist/DataList/components/DataListHeader/index.mjs +0 -2
  54. package/dist/DataList/components/DataListItem/index.cjs +0 -2
  55. package/dist/DataList/components/DataListItem/index.mjs +0 -2
  56. package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
  57. package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
  58. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
  59. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
  60. package/dist/DataList/components/DataListItems/index.cjs +0 -2
  61. package/dist/DataList/components/DataListItems/index.mjs +0 -2
  62. package/dist/DataList/components/DataListLayout/index.cjs +0 -2
  63. package/dist/DataList/components/DataListLayout/index.mjs +0 -2
  64. package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
  65. package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
  66. package/dist/DataList/index.cjs +0 -2
  67. package/dist/DataList/index.mjs +0 -2
  68. package/dist/DatePicker/index.cjs +0 -2
  69. package/dist/DatePicker/index.mjs +0 -2
  70. package/dist/DatePicker-cjs.js +4 -5
  71. package/dist/DatePicker-es.js +1 -2
  72. package/dist/Dialog/index.cjs +2 -2
  73. package/dist/Dialog/index.mjs +2 -2
  74. package/dist/DrawerDescription-cjs.js +268 -99
  75. package/dist/DrawerDescription-es.js +270 -103
  76. package/dist/DrawerTitle-cjs.js +2 -6
  77. package/dist/DrawerTitle-es.js +3 -7
  78. package/dist/FieldDescription-cjs.js +184 -181
  79. package/dist/FieldDescription-es.js +187 -184
  80. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
  81. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
  82. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
  83. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
  84. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
  85. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
  86. package/dist/FilterPicker/index.cjs +0 -2
  87. package/dist/FilterPicker/index.mjs +0 -2
  88. package/dist/FilterPickerContent-cjs.js +4 -5
  89. package/dist/FilterPickerContent-es.js +1 -2
  90. package/dist/FormatFile/index.cjs +0 -2
  91. package/dist/FormatFile/index.mjs +0 -2
  92. package/dist/Gallery/index.cjs +0 -2
  93. package/dist/Gallery/index.mjs +0 -2
  94. package/dist/InputDate/index.cjs +0 -2
  95. package/dist/InputDate/index.mjs +0 -2
  96. package/dist/InputNumber/index.cjs +2 -2
  97. package/dist/InputNumber/index.mjs +2 -2
  98. package/dist/InputNumber/types.d.ts +5 -0
  99. package/dist/InputNumber-cjs.js +2 -2
  100. package/dist/InputNumber-es.js +2 -2
  101. package/dist/LightBox/index.cjs +0 -2
  102. package/dist/LightBox/index.mjs +0 -2
  103. package/dist/Menu/index.cjs +2 -2
  104. package/dist/Menu/index.mjs +2 -2
  105. package/dist/Menu-cjs.js +4 -5
  106. package/dist/Menu-es.js +1 -2
  107. package/dist/MenuSubmenuTrigger-cjs.js +206 -164
  108. package/dist/MenuSubmenuTrigger-es.js +194 -152
  109. package/dist/Modal/index.cjs +0 -2
  110. package/dist/Modal/index.mjs +1 -3
  111. package/dist/NumberFieldInput-cjs.js +187 -308
  112. package/dist/NumberFieldInput-es.js +187 -308
  113. package/dist/Page/index.cjs +2 -2
  114. package/dist/Page/index.mjs +2 -2
  115. package/dist/Popover/index.cjs +0 -2
  116. package/dist/Popover/index.mjs +0 -2
  117. package/dist/Popover-cjs.js +7 -8
  118. package/dist/Popover-es.js +1 -2
  119. package/dist/ScrollAreaViewport-cjs.js +747 -1093
  120. package/dist/ScrollAreaViewport-es.js +734 -1081
  121. package/dist/Select/index.cjs +2 -2
  122. package/dist/Select/index.mjs +2 -2
  123. package/dist/SelectGroupLabel-cjs.js +207 -306
  124. package/dist/SelectGroupLabel-es.js +203 -302
  125. package/dist/SelectPrimitive-cjs.js +11 -17
  126. package/dist/SelectPrimitive-es.js +1 -7
  127. package/dist/Tooltip/index.cjs +0 -2
  128. package/dist/Tooltip/index.mjs +0 -2
  129. package/dist/Tooltip-cjs.js +6 -7
  130. package/dist/Tooltip-es.js +1 -2
  131. package/dist/buttonRenderAdapter-cjs.js +2 -1
  132. package/dist/buttonRenderAdapter-es.js +2 -1
  133. package/dist/dialogReturnFocus-cjs.js +1 -1
  134. package/dist/dialogReturnFocus-es.js +2 -2
  135. package/dist/docs/InputNumber/InputNumber.md +2 -2
  136. package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
  137. package/dist/floating-ui.react-cjs.js +2557 -185
  138. package/dist/floating-ui.react-es.js +2512 -149
  139. package/dist/getMappedBreakpointWidth-cjs.js +21 -0
  140. package/dist/getMappedBreakpointWidth-es.js +18 -0
  141. package/dist/index.cjs +15 -10
  142. package/dist/index.d.mts +1 -0
  143. package/dist/index.d.ts +1 -0
  144. package/dist/index.mjs +14 -10
  145. package/dist/mergeClassName-cjs.js +12 -0
  146. package/dist/mergeClassName-es.js +10 -0
  147. package/dist/primitives/BottomSheet/index.cjs +1 -1
  148. package/dist/primitives/BottomSheet/index.mjs +1 -1
  149. package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
  150. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
  151. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
  152. package/dist/primitives/SelectPrimitive/index.cjs +1 -2
  153. package/dist/primitives/SelectPrimitive/index.mjs +1 -2
  154. package/dist/primitives/index.cjs +3 -3
  155. package/dist/primitives/index.mjs +3 -3
  156. package/dist/scrollEdges-cjs.js +1 -11
  157. package/dist/scrollEdges-es.js +1 -11
  158. package/dist/scrollable-cjs.js +58 -0
  159. package/dist/scrollable-es.js +54 -0
  160. package/dist/styles.css +759 -42
  161. package/dist/unstyledPrimitives/index.cjs +720 -478
  162. package/dist/unstyledPrimitives/index.mjs +663 -421
  163. package/dist/useCompositeListItem-cjs.js +2108 -361
  164. package/dist/useCompositeListItem-es.js +2076 -331
  165. package/dist/useLabel-cjs.js +2 -3
  166. package/dist/useLabel-es.js +2 -3
  167. package/dist/useRenderElement-cjs.js +9 -7
  168. package/dist/useRenderElement-es.js +9 -7
  169. package/dist/useScrollLock-cjs.js +819 -344
  170. package/dist/useScrollLock-es.js +732 -296
  171. package/dist/useScrollToActive-cjs.js +4 -5
  172. package/dist/useScrollToActive-es.js +1 -2
  173. package/dist/utils/meta/meta.json +43 -1
  174. package/package.json +8 -3
  175. package/dist/floating-ui.react-dom-cjs.js +0 -2103
  176. package/dist/floating-ui.react-dom-es.js +0 -2072
  177. package/dist/floating-ui.utils-cjs.js +0 -342
  178. package/dist/floating-ui.utils-es.js +0 -300
@@ -1,12 +1,11 @@
1
1
  import * as React from 'react';
2
- import { a as useRefWithInit, f as formatErrorMessage, u as useRenderElement, m as mergeProps, b as useMergedRefs, E as EMPTY_OBJECT, c as EMPTY_ARRAY, w as warn } from './useRenderElement-es.js';
3
- import { aw as isMouseLikePointerType, k as useTimeout, ax as mergeCleanups, s as addEventListener, g as getTarget, ay as isTypeableElement, az as matchesFocusVisible, aA as isTargetInsideEnabledTrigger, d as createChangeEventDetails, aB as triggerFocus, aC as createAttribute, D as activeElement, o as ownerDocument, _ as contains, aD as mac, a6 as webkit, aE as triggerPress, aF as escapeKey, H as useOnMount, T as Timeout, ae as useFloatingTree, ab as useFloatingParentNodeId, b as useStableCallback, a as useIsoLayoutEffect, aG as isInteractiveElement, aH as getNodeChildren, aI as triggerHover, q as useValueAsRef, p as popupStateMapping, t as transitionStatusMapping, a9 as itemPress, c as useButton, u as useBaseUiId, f as useOpenChangeComplete, X as outsidePress, i as FloatingFocusManager, a0 as FloatingPortal, aJ as useAnimationsFinished, aK as POPUP_COLLISION_AVOIDANCE, a1 as InternalBackdrop, a2 as inertValue, aL as FloatingNode, a3 as DROPDOWN_COLLISION_AVOIDANCE, aM as siblingOpen, n as useControlled, m as useTransitionStatus, N as createSelector, aN as ReactStore, ad as PopupTriggerMap, aO as FloatingTreeStore, aP as fastComponent, aQ as useInitialOpenSync, aa as useId, aR as useFloatingNodeId, O as useOpenInteractionType, aS as useImplicitActiveTrigger, aT as useOpenStateTransitions, aU as attachPreventUnmountOnClose, aV as setPopupOpenState, W as focusOut, aj as listNavigation, aW as useSyncedFloatingRootContext, j as imperativeAction, R as useDismiss, a4 as useDirection, aX as TYPEAHEAD_RESET_MS, F as FOCUSABLE_POPUP_PROPS, aY as usePopupInteractionProps, aZ as FloatingTree, a_ as useCompositeRootContext, a$ as getTabbableBeforeElement, b0 as isOutsideEvent, b1 as getTabbableAfterElement, b2 as getNextTabbable, b3 as fastComponentRef, b4 as useTriggerDataForwarding, $ as cancelOpen, Q as useClick, Y as pressableTriggerOpenStateMapping, b5 as FocusGuard, b6 as PATIENT_CLICK_THRESHOLD, b7 as useTriggerRegistration, S as SafeReact, b8 as triggerOpenStateMapping } from './useScrollLock-es.js';
2
+ import { a as useRefWithInit, f as formatErrorMessage, u as useRenderElement, m as mergeProps, b as useMergedRefs, N as NOOP, E as EMPTY_OBJECT, c as EMPTY_ARRAY, w as warn } from './useRenderElement-es.js';
3
+ import { bl as isMouseLikePointerType, q as useTimeout, I as getWindow, bm as mergeCleanups, y as addEventListener, e as getTarget, au as isElement, bn as isTypeableElement, bo as matchesFocusVisible, bp as isTargetInsideEnabledTrigger, h as createChangeEventDetails, bq as triggerFocus, br as createAttribute, D as activeElement, o as ownerDocument, a0 as contains, bs as mac, a9 as webkit, i as isHTMLElement, bt as triggerPress, bu as escapeKey, H as useOnMount, T as Timeout, aM as useFloatingTree, aJ as useFloatingParentNodeId, d as useStableCallback, c as useIsoLayoutEffect, bv as isInteractiveElement, bw as getNodeChildren, bx as triggerHover, w as useValueAsRef, p as popupTransitionStateMapping, ad as itemPress, by as dispatchClickWithModifiers, f as useButton, t as transitionStatusMapping, u as useBaseUiId, l as useOpenChangeComplete, Z as outsidePress, n as FloatingFocusManager, a2 as FloatingPortal, bz as useAnimationsFinished, bA as POPUP_COLLISION_AVOIDANCE, a3 as InternalBackdrop, a4 as inertValue, bB as FloatingNode, a5 as DROPDOWN_COLLISION_AVOIDANCE, bC as siblingOpen, v as useControlled, s as useTransitionStatus, aL as PopupTriggerMap, bD as FloatingTreeStore, X as ReactStore, bE as fastComponent, aI as useId, bF as useFloatingNodeId, R as useOpenInteractionType, b8 as useImplicitActiveTrigger, b9 as useOpenStateTransitions, bG as attachPreventUnmountOnClose, b5 as setPopupOpenState, Y as focusOut, aS as listNavigation, bH as useSyncedFloatingRootContext, k as imperativeAction, W as useDismiss, a7 as useDirection, bI as TYPEAHEAD_RESET_MS, F as FOCUSABLE_POPUP_PROPS, b4 as usePopupInteractionProps, ba as PopupHandleAttachment, bJ as FloatingTree, bK as useCompositeRootContext, a as isLastTraversableNode, b as getParentNode, bL as getTabbableBeforeElement, bM as isOutsideEvent, bN as getTabbableAfterElement, bO as getNextTabbable, bP as fastComponentRef, bb as useTriggerDataForwarding, a1 as cancelOpen, V as useClick, _ as pressableTriggerOpenStateMapping, bQ as FocusGuard, bR as PATIENT_CLICK_THRESHOLD, bS as useTriggerRegistration, S as SafeReact, be as triggerOpenStateMapping, bT as voiceOver } from './useScrollLock-es.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
- import { z as useCompositeListItem, y as getDisabledMountTransitionStyles, v as useAnchorPositioning, B as adaptiveOrigin, t as useAnchoredPopupScrollLock, w as usePositioner, C as CompositeList, j as useListNavigation, k as useTypeahead, o as getPseudoElementBounds } from './useCompositeListItem-es.js';
5
+ import { A as useCompositeListItem, z as getDisabledMountTransitionStyles, w as useAnchorPositioning, v as useAnchoredPopupScrollLock, x as usePositioner, C as CompositeList, j as useListNavigation, k as useTypeahead, p as isMouseWithinBounds } from './useCompositeListItem-es.js';
6
6
  import { u as useToolbarRootContext, l as isElementDisabled } from './SelectGroupLabel-es.js';
7
7
  import { C as COMPOSITE_KEYS } from './scrollEdges-es.js';
8
- import { K as getWindow, b as isElement, i as isHTMLElement, d as isLastTraversableNode, e as getParentNode } from './floating-ui.utils-es.js';
9
- import { p as popupStoreSelectors, n as createInitialPopupStoreState } from './ScrollAreaViewport-es.js';
8
+ import { N as NullStore, o as createInitialPopupStoreState, p as popupStoreSelectors, q as usePopupHandleStore } from './ScrollAreaViewport-es.js';
10
9
  import * as ReactDOM from 'react-dom';
11
10
 
12
11
  function resolveValue(value, pointerType) {
@@ -438,7 +437,8 @@ function useHoverReferenceInteraction(context, props = {}) {
438
437
  isActiveTrigger = true,
439
438
  getHandleCloseContext,
440
439
  isClosing,
441
- shouldOpen: shouldOpenProp
440
+ shouldOpen: shouldOpenProp,
441
+ guardStaleOpen = false
442
442
  } = props;
443
443
  const store = 'rootStore' in context ? context.rootStore : context;
444
444
  const {
@@ -640,13 +640,24 @@ function useHoverReferenceInteraction(context, props = {}) {
640
640
  closeWithDelay(event);
641
641
  }
642
642
  }
643
+
644
+ // Backup cancellation for Chrome's dropped `mouseleave` — see `guardStaleOpen`.
645
+ function onMouseOut(event) {
646
+ if (contains(trigger, event.relatedTarget)) {
647
+ return; // moved within the trigger's own subtree
648
+ }
649
+ instance.openChangeTimeout.clear();
650
+ instance.restTimeout.clear();
651
+ instance.restTimeoutPending = false;
652
+ }
653
+ const staleOpenGuard = guardStaleOpen ? addEventListener(trigger, 'mouseout', onMouseOut) : undefined;
643
654
  if (move) {
644
655
  return mergeCleanups(addEventListener(trigger, 'mousemove', onMouseEnter, {
645
656
  once: true
646
- }), addEventListener(trigger, 'mouseenter', onMouseEnter), addEventListener(trigger, 'mouseleave', onMouseLeave));
657
+ }), addEventListener(trigger, 'mouseenter', onMouseEnter), addEventListener(trigger, 'mouseleave', onMouseLeave), staleOpenGuard);
647
658
  }
648
- return mergeCleanups(addEventListener(trigger, 'mouseenter', onMouseEnter), addEventListener(trigger, 'mouseleave', onMouseLeave));
649
- }, [cleanupMouseMoveHandler, clearPointerEvents, dataRef, delayRef, store, enabled, handleCloseRef, instance, isActiveTrigger, isOverInactiveTrigger, isClickLikeOpenEvent$1, mouseOnly, move, restMsRef, triggerElementRef, tree, enabledRef, getHandleCloseContext, isClosingRef, checkShouldOpen]);
659
+ return mergeCleanups(addEventListener(trigger, 'mouseenter', onMouseEnter), addEventListener(trigger, 'mouseleave', onMouseLeave), staleOpenGuard);
660
+ }, [cleanupMouseMoveHandler, clearPointerEvents, dataRef, delayRef, store, enabled, handleCloseRef, instance, isActiveTrigger, isOverInactiveTrigger, isClickLikeOpenEvent$1, mouseOnly, move, restMsRef, triggerElementRef, tree, enabledRef, getHandleCloseContext, isClosingRef, checkShouldOpen, guardStaleOpen]);
650
661
  return React.useMemo(() => {
651
662
  if (!enabled) {
652
663
  return undefined;
@@ -987,11 +998,6 @@ function useContextMenuRootContext(optional = true) {
987
998
  return context;
988
999
  }
989
1000
 
990
- const stateAttributesMapping$1 = {
991
- ...popupStateMapping,
992
- ...transitionStatusMapping
993
- };
994
-
995
1001
  /**
996
1002
  * An overlay displayed beneath the menu popup.
997
1003
  * Renders a `<div>` element.
@@ -1020,7 +1026,7 @@ const MenuBackdrop = /*#__PURE__*/React.forwardRef(function MenuBackdrop(compone
1020
1026
  return useRenderElement('div', componentProps, {
1021
1027
  ref: contextMenuContext?.backdropRef ? [forwardedRef, contextMenuContext.backdropRef] : forwardedRef,
1022
1028
  state,
1023
- stateAttributesMapping: stateAttributesMapping$1,
1029
+ stateAttributesMapping: popupTransitionStateMapping,
1024
1030
  props: [{
1025
1031
  role: 'presentation',
1026
1032
  hidden: !mounted,
@@ -1102,8 +1108,12 @@ function useMenuItemCommonProps(params) {
1102
1108
  if (itemRef.current && store.context.allowMouseUpTriggerRef.current && (!isContextMenu || event.button === 2)) {
1103
1109
  // This fires whenever the user clicks on the trigger, moves the cursor, and releases it over the item.
1104
1110
  // We trigger the click and override the `closeOnClick` preference to always close the menu.
1105
- if (!itemMetadata || itemMetadata.type === 'regular-item') {
1106
- itemRef.current.click();
1111
+ if (itemMetadata.type === 'regular-item') {
1112
+ // `detail: 1` marks this as a mouse-gesture click so MenuRoot doesn't
1113
+ // treat it as a keyboard activation (`detail === 0` → `data-instant`).
1114
+ dispatchClickWithModifiers(itemRef.current, event, {
1115
+ detail: 1
1116
+ });
1107
1117
  }
1108
1118
  }
1109
1119
  }
@@ -1116,7 +1126,7 @@ const REGULAR_ITEM = {
1116
1126
  function useMenuItem(params) {
1117
1127
  const {
1118
1128
  closeOnClick,
1119
- disabled: disabledProp = false,
1129
+ disabled,
1120
1130
  highlighted,
1121
1131
  id,
1122
1132
  store,
@@ -1125,8 +1135,6 @@ function useMenuItem(params) {
1125
1135
  itemMetadata,
1126
1136
  nodeId
1127
1137
  } = params;
1128
- const rootDisabled = store.useState('disabled');
1129
- const disabled = disabledProp || rootDisabled;
1130
1138
  const itemRef = React.useRef(null);
1131
1139
  const {
1132
1140
  getButtonProps,
@@ -1250,7 +1258,7 @@ const MenuGroupLabel = /*#__PURE__*/React.forwardRef(function MenuGroupLabel(com
1250
1258
  useIsoLayoutEffect(() => {
1251
1259
  setLabelId(id);
1252
1260
  return () => {
1253
- setLabelId(undefined);
1261
+ setLabelId(currentId => currentId === id ? undefined : currentId);
1254
1262
  };
1255
1263
  }, [setLabelId, id]);
1256
1264
  return useRenderElement('div', componentProps, {
@@ -1277,12 +1285,13 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(componentProps,
1277
1285
  id: idProp,
1278
1286
  label,
1279
1287
  nativeButton = false,
1280
- disabled = false,
1288
+ disabled: disabledProp = false,
1281
1289
  closeOnClick = true,
1282
1290
  style,
1283
1291
  ...elementProps
1284
1292
  } = componentProps;
1285
1293
  const listItem = useCompositeListItem({
1294
+ guess: true,
1286
1295
  label
1287
1296
  });
1288
1297
  const menuPositionerContext = useMenuPositionerContext(true);
@@ -1290,6 +1299,8 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(componentProps,
1290
1299
  const {
1291
1300
  store
1292
1301
  } = useMenuRootContext();
1302
+ const rootDisabled = store.useState('disabled');
1303
+ const disabled = disabledProp || rootDisabled;
1293
1304
  const highlighted = store.useState('isActive', listItem.index);
1294
1305
  const itemProps = store.useState('itemProps');
1295
1306
  const {
@@ -1335,6 +1346,7 @@ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(compone
1335
1346
  } = componentProps;
1336
1347
  const linkRef = React.useRef(null);
1337
1348
  const listItem = useCompositeListItem({
1349
+ guess: true,
1338
1350
  label
1339
1351
  });
1340
1352
  const menuPositionerContext = useMenuPositionerContext(true);
@@ -1360,7 +1372,8 @@ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(compone
1360
1372
  nodeId,
1361
1373
  store,
1362
1374
  typingRef,
1363
- itemRef: linkRef
1375
+ itemRef: linkRef,
1376
+ itemMetadata: REGULAR_ITEM
1364
1377
  });
1365
1378
  function getItemProps(externalProps) {
1366
1379
  return mergeProps(commonProps, externalProps, getButtonProps);
@@ -1376,17 +1389,6 @@ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(compone
1376
1389
  });
1377
1390
  if (process.env.NODE_ENV !== "production") MenuLinkItem.displayName = "MenuLinkItem";
1378
1391
 
1379
- const stateAttributesMapping = {
1380
- ...popupStateMapping,
1381
- ...transitionStatusMapping
1382
- };
1383
-
1384
- /**
1385
- * A container for the menu items.
1386
- * Renders a `<div>` element.
1387
- *
1388
- * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
1389
- */
1390
1392
  const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProps, forwardedRef) {
1391
1393
  const {
1392
1394
  render,
@@ -1408,14 +1410,13 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1408
1410
  const popupProps = store.useState('popupProps');
1409
1411
  const mounted = store.useState('mounted');
1410
1412
  const instantType = store.useState('instantType');
1411
- const triggerElement = store.useState('activeTriggerElement');
1413
+ const activeTriggerElement = store.useState('activeTriggerElement');
1412
1414
  const parent = store.useState('parent');
1413
1415
  const lastOpenChangeReason = store.useState('lastOpenChangeReason');
1414
1416
  const rootId = store.useState('rootId');
1415
1417
  const floatingContext = store.useState('floatingRootContext');
1416
1418
  const floatingTreeRoot = store.useState('floatingTreeRoot');
1417
1419
  const closeDelay = store.useState('closeDelay');
1418
- const activeTriggerElement = store.useState('activeTriggerElement');
1419
1420
  const hoverEnabled = store.useState('hoverEnabled');
1420
1421
  const disabled = store.useState('disabled');
1421
1422
  const openMethod = store.useState('openMethod');
@@ -1442,9 +1443,7 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1442
1443
  enabled: hoverEnabled && !disabled && !isContextMenu && parent.type !== 'menubar',
1443
1444
  closeDelay
1444
1445
  });
1445
- const setPopupElement = React.useCallback(element => {
1446
- store.set('popupElement', element);
1447
- }, [store]);
1446
+ const setPopupElement = store.useStateSetter('popupElement');
1448
1447
  const state = {
1449
1448
  transitionStatus,
1450
1449
  side,
@@ -1456,7 +1455,7 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1456
1455
  const element = useRenderElement('div', componentProps, {
1457
1456
  state,
1458
1457
  ref: [forwardedRef, store.context.popupRef, setPopupElement],
1459
- stateAttributesMapping,
1458
+ stateAttributesMapping: popupTransitionStateMapping,
1460
1459
  props: [popupProps, {
1461
1460
  onKeyDown(event) {
1462
1461
  if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {
@@ -1468,7 +1467,7 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1468
1467
  }]
1469
1468
  });
1470
1469
  let returnFocus = parent.type === undefined || isContextMenu;
1471
- if (triggerElement || parent.type === 'menubar' && lastOpenChangeReason !== outsidePress) {
1470
+ if (activeTriggerElement || parent.type === 'menubar' && lastOpenChangeReason !== outsidePress) {
1472
1471
  returnFocus = true;
1473
1472
  }
1474
1473
  return /*#__PURE__*/jsx(FloatingFocusManager, {
@@ -1556,13 +1555,13 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1556
1555
  const transitionStatus = store.useState('transitionStatus');
1557
1556
  const positionerElement = store.useState('positionerElement');
1558
1557
  const instantType = store.useState('instantType');
1559
- const hasViewport = store.useState('hasViewport');
1558
+ const adaptiveOrigin = store.useState('adaptiveOrigin');
1560
1559
  const lastOpenChangeReason = store.useState('lastOpenChangeReason');
1561
1560
  const floatingNodeId = store.useState('floatingNodeId');
1562
1561
  const floatingParentNodeId = store.useState('floatingParentNodeId');
1563
1562
  const domReference = floatingRootContext.useState('domReferenceElement');
1564
1563
  const previousTriggerRef = React.useRef(null);
1565
- const runOnceAnimationsFinish = useAnimationsFinished(positionerElement, false, false);
1564
+ const runOnceAnimationsFinish = useAnimationsFinished(positionerElement);
1566
1565
  let anchor = anchorProp;
1567
1566
  let sideOffset = sideOffsetProp;
1568
1567
  let alignOffset = alignOffsetProp;
@@ -1604,9 +1603,12 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1604
1603
  keepMounted,
1605
1604
  disableAnchorTracking,
1606
1605
  collisionAvoidance,
1607
- shiftCrossAxis: contextMenu && !('side' in collisionAvoidance && collisionAvoidance.side === 'flip'),
1606
+ shift: contextMenu ? {
1607
+ crossAxis: !('side' in collisionAvoidance && collisionAvoidance.side === 'flip'),
1608
+ rootBoundary: 'layoutViewport'
1609
+ } : undefined,
1608
1610
  externalTree: floatingTreeRoot,
1609
- adaptiveOrigin: hasViewport ? adaptiveOrigin : undefined
1611
+ adaptiveOrigin
1610
1612
  });
1611
1613
  React.useEffect(() => {
1612
1614
  function onMenuOpenChange(details) {
@@ -1846,6 +1848,7 @@ const MenuRadioItem = /*#__PURE__*/React.forwardRef(function MenuRadioItem(compo
1846
1848
  ...elementProps
1847
1849
  } = componentProps;
1848
1850
  const listItem = useCompositeListItem({
1851
+ guess: true,
1849
1852
  label
1850
1853
  });
1851
1854
  const menuPositionerContext = useMenuPositionerContext(true);
@@ -1860,7 +1863,8 @@ const MenuRadioItem = /*#__PURE__*/React.forwardRef(function MenuRadioItem(compo
1860
1863
  setValue: setSelectedValue,
1861
1864
  disabled: groupDisabled
1862
1865
  } = useMenuRadioGroupContext();
1863
- const disabled = groupDisabled || disabledProp;
1866
+ const rootDisabled = store.useState('disabled');
1867
+ const disabled = disabledProp || groupDisabled || rootDisabled;
1864
1868
  const checked = selectedValue === value;
1865
1869
  const {
1866
1870
  getItemProps,
@@ -1882,7 +1886,7 @@ const MenuRadioItem = /*#__PURE__*/React.forwardRef(function MenuRadioItem(compo
1882
1886
  }), [disabled, highlighted, checked]);
1883
1887
  function handleClick(event) {
1884
1888
  const details = createChangeEventDetails(itemPress, event.nativeEvent, undefined, {
1885
- preventUnmountOnClose() {}
1889
+ preventUnmountOnClose: NOOP
1886
1890
  });
1887
1891
  setSelectedValue(value, details);
1888
1892
  }
@@ -1921,6 +1925,7 @@ const MenuRadioItemIndicator = /*#__PURE__*/React.forwardRef(function MenuRadioI
1921
1925
  const indicatorRef = React.useRef(null);
1922
1926
  const {
1923
1927
  transitionStatus,
1928
+ mounted,
1924
1929
  setMounted
1925
1930
  } = useTransitionStatus(item.checked);
1926
1931
  useOpenChangeComplete({
@@ -1946,7 +1951,7 @@ const MenuRadioItemIndicator = /*#__PURE__*/React.forwardRef(function MenuRadioI
1946
1951
  'aria-hidden': true,
1947
1952
  ...elementProps
1948
1953
  },
1949
- enabled: keepMounted || item.checked
1954
+ enabled: keepMounted || mounted
1950
1955
  });
1951
1956
  return element;
1952
1957
  });
@@ -1964,36 +1969,35 @@ function useMenubarContext(optional) {
1964
1969
 
1965
1970
  const selectors = {
1966
1971
  ...popupStoreSelectors,
1967
- disabled: createSelector(state => state.parent.type === 'menubar' ? state.parent.context.disabled || state.disabled : state.disabled),
1968
- modal: createSelector(state => (state.parent.type === undefined || state.parent.type === 'context-menu') && (state.modal ?? true)),
1969
- openMethod: createSelector(state => state.openMethod),
1970
- allowMouseEnter: createSelector(state => state.allowMouseEnter),
1971
- highlightItemOnHover: createSelector(state => state.highlightItemOnHover),
1972
- stickIfOpen: createSelector(state => state.stickIfOpen),
1973
- parent: createSelector(state => state.parent),
1974
- rootId: createSelector(state => {
1972
+ disabled: state => state.parent.type === 'menubar' ? state.parent.context.disabled || state.disabled : state.disabled,
1973
+ modal: state => (state.parent.type === undefined || state.parent.type === 'context-menu') && (state.modal ?? true),
1974
+ openMethod: state => state.openMethod,
1975
+ allowMouseEnter: state => state.allowMouseEnter,
1976
+ highlightItemOnHover: state => state.highlightItemOnHover,
1977
+ parent: state => state.parent,
1978
+ rootId: state => {
1975
1979
  if (state.parent.type === 'menu') {
1976
1980
  return state.parent.store.select('rootId');
1977
1981
  }
1978
1982
  return state.parent.type !== undefined ? state.parent.context.rootId : state.rootId;
1979
- }),
1980
- activeIndex: createSelector(state => state.activeIndex),
1981
- isActive: createSelector((state, itemIndex) => state.activeIndex === itemIndex),
1982
- hoverEnabled: createSelector(state => state.hoverEnabled),
1983
- instantType: createSelector(state => state.instantType),
1984
- lastOpenChangeReason: createSelector(state => state.openChangeReason),
1985
- floatingTreeRoot: createSelector(state => {
1983
+ },
1984
+ activeIndex: state => state.activeIndex,
1985
+ isActive: (state, itemIndex) => state.activeIndex === itemIndex,
1986
+ hoverEnabled: state => state.hoverEnabled,
1987
+ instantType: state => state.instantType,
1988
+ lastOpenChangeReason: state => state.openChangeReason,
1989
+ floatingTreeRoot: state => {
1986
1990
  if (state.parent.type === 'menu') {
1987
1991
  return state.parent.store.select('floatingTreeRoot');
1988
1992
  }
1989
1993
  return state.floatingTreeRoot;
1990
- }),
1991
- floatingNodeId: createSelector(state => state.floatingNodeId),
1992
- floatingParentNodeId: createSelector(state => state.floatingParentNodeId),
1993
- itemProps: createSelector(state => state.itemProps),
1994
- closeDelay: createSelector(state => state.closeDelay),
1995
- hasViewport: createSelector(state => state.hasViewport),
1996
- keyboardEventRelay: createSelector(state => {
1994
+ },
1995
+ floatingNodeId: state => state.floatingNodeId,
1996
+ floatingParentNodeId: state => state.floatingParentNodeId,
1997
+ itemProps: state => state.itemProps,
1998
+ closeDelay: state => state.closeDelay,
1999
+ adaptiveOrigin: state => state.adaptiveOrigin,
2000
+ keyboardEventRelay: state => {
1997
2001
  if (state.keyboardEventRelay) {
1998
2002
  return state.keyboardEventRelay;
1999
2003
  }
@@ -2001,33 +2005,23 @@ const selectors = {
2001
2005
  return state.parent.store.select('keyboardEventRelay');
2002
2006
  }
2003
2007
  return undefined;
2004
- })
2008
+ }
2005
2009
  };
2010
+
2011
+ /**
2012
+ * The store view that detached handle-backed triggers read from. Both the real `MenuStore` and the
2013
+ * inert fallback store satisfy it, so a trigger can read from whichever store the handle currently
2014
+ * exposes. Narrowed to the members a trigger actually uses — the trigger-data members plus `setOpen`
2015
+ * (called by the focus guards) — so the exposed surface can't bypass the open-change pipeline; on
2016
+ * the detached fallback store every one of these mutations is a no-op.
2017
+ */
2018
+
2006
2019
  class MenuStore extends ReactStore {
2007
2020
  constructor(initialState) {
2008
2021
  super({
2009
2022
  ...createInitialState(),
2010
2023
  ...initialState
2011
- }, {
2012
- positionerRef: /*#__PURE__*/React.createRef(),
2013
- popupRef: /*#__PURE__*/React.createRef(),
2014
- typingRef: {
2015
- current: false
2016
- },
2017
- itemDomElements: {
2018
- current: []
2019
- },
2020
- itemLabels: {
2021
- current: []
2022
- },
2023
- allowMouseUpTriggerRef: {
2024
- current: false
2025
- },
2026
- triggerFocusTargetRef: /*#__PURE__*/React.createRef(),
2027
- beforeContentFocusGuardRef: /*#__PURE__*/React.createRef(),
2028
- onOpenChangeComplete: undefined,
2029
- triggerElements: new PopupTriggerMap()
2030
- }, selectors);
2024
+ }, createInitialContext(), selectors);
2031
2025
 
2032
2026
  // Set up propagation of state from parent menu if applicable.
2033
2027
  this.unsubscribeParentListener = this.observe('parent', parent => {
@@ -2063,15 +2057,44 @@ class MenuStore extends ReactStore {
2063
2057
  eventDetails
2064
2058
  });
2065
2059
  }
2066
- static useStore(externalStore, initialState) {
2067
- // eslint-disable-next-line react-hooks/rules-of-hooks
2068
- const internalStore = useRefWithInit(() => {
2069
- return new MenuStore(initialState);
2070
- }).current;
2071
- return externalStore ?? internalStore;
2072
- }
2073
2060
  unsubscribeParentListener = null;
2074
2061
  }
2062
+
2063
+ /**
2064
+ * Creates the inert fallback store used by detached handle-backed triggers while no `Menu.Root` is
2065
+ * attached. It preserves a menu-specific trigger registry in context so detached triggers can
2066
+ * register before migrating to the live root store. `setOpen` is a no-op (matching the inert
2067
+ * reads/writes of `NullStore`), so a trigger can hand the store to focus-guard helpers that expect
2068
+ * `setOpen` without it ever taking effect while detached.
2069
+ */
2070
+ function createNullMenuStore() {
2071
+ const store = new NullStore(Object.freeze(createInitialState()), Object.freeze(createInitialContext()), selectors);
2072
+ return Object.assign(store, {
2073
+ setOpen: NOOP
2074
+ });
2075
+ }
2076
+ function createInitialContext() {
2077
+ return {
2078
+ positionerRef: /*#__PURE__*/React.createRef(),
2079
+ popupRef: /*#__PURE__*/React.createRef(),
2080
+ typingRef: {
2081
+ current: false
2082
+ },
2083
+ itemDomElements: {
2084
+ current: []
2085
+ },
2086
+ itemLabels: {
2087
+ current: []
2088
+ },
2089
+ allowMouseUpTriggerRef: {
2090
+ current: false
2091
+ },
2092
+ triggerFocusTargetRef: /*#__PURE__*/React.createRef(),
2093
+ beforeContentFocusGuardRef: /*#__PURE__*/React.createRef(),
2094
+ onOpenChangeComplete: undefined,
2095
+ triggerElements: new PopupTriggerMap()
2096
+ };
2097
+ }
2075
2098
  function createInitialState() {
2076
2099
  return {
2077
2100
  ...createInitialPopupStoreState(),
@@ -2080,7 +2103,6 @@ function createInitialState() {
2080
2103
  openMethod: null,
2081
2104
  allowMouseEnter: false,
2082
2105
  highlightItemOnHover: true,
2083
- stickIfOpen: true,
2084
2106
  parent: {
2085
2107
  type: undefined
2086
2108
  },
@@ -2095,7 +2117,7 @@ function createInitialState() {
2095
2117
  itemProps: EMPTY_OBJECT,
2096
2118
  keyboardEventRelay: undefined,
2097
2119
  closeDelay: 0,
2098
- hasViewport: false
2120
+ adaptiveOrigin: undefined
2099
2121
  };
2100
2122
  }
2101
2123
 
@@ -2154,14 +2176,13 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2154
2176
  type: undefined
2155
2177
  };
2156
2178
  }, [contextMenuContext, parentMenuRootContext, menubarContext, isSubmenu]);
2157
- const store = MenuStore.useStore(handle?.store, {
2179
+ const store = useMenuRootStore({
2158
2180
  open: defaultOpen,
2159
2181
  openProp,
2160
2182
  activeTriggerId: defaultTriggerIdProp,
2161
2183
  triggerIdProp,
2162
2184
  parent: parentFromContext
2163
2185
  });
2164
- useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerIdProp);
2165
2186
  store.useControlledProp('openProp', openProp);
2166
2187
  store.useControlledProp('triggerIdProp', triggerIdProp);
2167
2188
  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
@@ -2206,10 +2227,7 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2206
2227
  const {
2207
2228
  forceUnmount
2208
2229
  } = useOpenStateTransitions(open, store, () => {
2209
- store.update({
2210
- allowMouseEnter: false,
2211
- stickIfOpen: true
2212
- });
2230
+ store.set('allowMouseEnter', false);
2213
2231
  });
2214
2232
  useIsoLayoutEffect(() => {
2215
2233
  if (contextMenuContext && !parentMenuRootContext) {
@@ -2257,6 +2275,12 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2257
2275
  }, [open, hoverEnabled, store]);
2258
2276
  const setOpen = useStableCallback((nextOpen, eventDetails) => {
2259
2277
  const reason = eventDetails.reason;
2278
+
2279
+ // Read the store directly, as relayed tree events and stale hover timers can request
2280
+ // a close after the state changed but before this component re-rendered.
2281
+ if (!nextOpen && !store.select('open')) {
2282
+ return;
2283
+ }
2260
2284
  if (open === nextOpen && eventDetails.trigger === activeTriggerElement && lastOpenChangeReason === reason) {
2261
2285
  return;
2262
2286
  }
@@ -2289,13 +2313,17 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2289
2313
  allowTouchToCloseRef.current = true;
2290
2314
  allowTouchToCloseTimeout.clear();
2291
2315
  }
2292
- const isKeyboardClick = (reason === triggerPress || reason === itemPress) && nativeEvent.detail === 0 && nativeEvent?.isTrusted;
2316
+
2317
+ // Keyboard and assistive-technology activations produce `detail === 0` clicks;
2318
+ // mouse-gesture clicks (including the synthesized drag-release click from
2319
+ // `useMenuItemCommonProps`) carry `detail >= 1`.
2320
+ const isKeyboardClick = (reason === triggerPress || reason === itemPress) && nativeEvent.detail === 0;
2293
2321
  const isDismissClose = !nextOpen && (reason === escapeKey || reason == null);
2294
2322
  const updatedState = {
2295
2323
  open: nextOpen,
2296
2324
  openChangeReason: reason
2297
2325
  };
2298
- openEventRef.current = eventDetails.event ?? null;
2326
+ openEventRef.current = eventDetails.event;
2299
2327
  setPopupOpenState(updatedState, nextOpen, eventDetails.trigger, shouldPreventUnmountOnClose());
2300
2328
  store.update(updatedState);
2301
2329
  if (parent.type === 'menubar' && (reason === triggerFocus || reason === focusOut || reason === triggerHover || reason === listNavigation || reason === siblingOpen)) {
@@ -2313,7 +2341,11 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2313
2341
  onOpenChange: setOpen
2314
2342
  });
2315
2343
  const floatingEvents = floatingRootContext.context.events;
2316
- React.useEffect(() => {
2344
+
2345
+ // Registered in a layout effect (not a passive one) so `setOpen` emits from imperative
2346
+ // `MenuHandle.open()` calls made in the same commit this root mounts — e.g. from another layout
2347
+ // effect during a route-transition handoff — are received instead of being silently dropped.
2348
+ useIsoLayoutEffect(() => {
2317
2349
  const handleSetOpenEvent = ({
2318
2350
  open: nextOpen,
2319
2351
  eventDetails
@@ -2442,11 +2474,14 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2442
2474
  store,
2443
2475
  parent: parentFromContext
2444
2476
  }), [store, parentFromContext]);
2445
- const content = /*#__PURE__*/jsx(MenuRootContext.Provider, {
2477
+ const content = /*#__PURE__*/jsxs(MenuRootContext.Provider, {
2446
2478
  value: context,
2447
- children: typeof children === 'function' ? children({
2479
+ children: [handle && /*#__PURE__*/jsx(PopupHandleAttachment, {
2480
+ handle: handle,
2481
+ store: store
2482
+ }), typeof children === 'function' ? children({
2448
2483
  payload
2449
- }) : children
2484
+ }) : children]
2450
2485
  });
2451
2486
  if (parent.type === undefined || parent.type === 'context-menu') {
2452
2487
  // set up a FloatingTree to provide the context to nested menus
@@ -2458,6 +2493,15 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2458
2493
  return content;
2459
2494
  });
2460
2495
  if (process.env.NODE_ENV !== "production") MenuRoot.displayName = "MenuRoot";
2496
+ function useMenuRootStore(initialState) {
2497
+ // The store is owned by this Root instance and created exactly once. It is not tied to the handle:
2498
+ // the handle attaches to it, so swapping the handle re-attaches rather than recreating state.
2499
+ // Default values are only initial values; controlled values and root state are synced after creation.
2500
+ // Unlike other popups, Menu wires its floating root context separately (it relays open changes
2501
+ // through an event).
2502
+ const store = useRefWithInit(() => new MenuStore(initialState)).current;
2503
+ return store;
2504
+ }
2461
2505
 
2462
2506
  /**
2463
2507
  * Groups all parts of a submenu.
@@ -2514,9 +2558,6 @@ function useCompositeItem(params = {}) {
2514
2558
  };
2515
2559
  }
2516
2560
 
2517
- /**
2518
- * @internal
2519
- */
2520
2561
  function CompositeItem(componentProps) {
2521
2562
  const {
2522
2563
  render,
@@ -2538,7 +2579,8 @@ function CompositeItem(componentProps) {
2538
2579
  });
2539
2580
  return useRenderElement(tag, componentProps, {
2540
2581
  state,
2541
- ref: [...refs, compositeRef],
2582
+ // The composite ref attaches first so an outer item wins when nested items share a DOM node.
2583
+ ref: [compositeRef, ...refs],
2542
2584
  props: [compositeProps, ...props, elementProps],
2543
2585
  stateAttributesMapping
2544
2586
  });
@@ -2546,7 +2588,7 @@ function CompositeItem(componentProps) {
2546
2588
 
2547
2589
  function findRootOwnerId(node) {
2548
2590
  if (isHTMLElement(node) && node.hasAttribute('data-rootownerid')) {
2549
- return node.getAttribute('data-rootownerid') ?? undefined;
2591
+ return node.getAttribute('data-rootownerid');
2550
2592
  }
2551
2593
  if (isLastTraversableNode(node)) {
2552
2594
  return undefined;
@@ -2638,14 +2680,6 @@ function useMixedToggleClickHandler(params) {
2638
2680
  }, [enabled, mouseDownAction, open]);
2639
2681
  }
2640
2682
 
2641
- const BOUNDARY_OFFSET = 2;
2642
-
2643
- /**
2644
- * A button that opens the menu.
2645
- * Renders a `<button>` element.
2646
- *
2647
- * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2648
- */
2649
2683
  const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwardedRef) {
2650
2684
  const {
2651
2685
  render,
@@ -2662,7 +2696,8 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2662
2696
  ...elementProps
2663
2697
  } = componentProps;
2664
2698
  const rootContext = useMenuRootContext(true);
2665
- const store = handle?.store ?? rootContext?.store;
2699
+ const handleStore = usePopupHandleStore(handle);
2700
+ const store = handleStore ?? rootContext?.store;
2666
2701
  if (!store) {
2667
2702
  throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Menu.Trigger> must be either used within a <Menu.Root> component or provided with a handle.' : formatErrorMessage(85));
2668
2703
  }
@@ -2722,8 +2757,7 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2722
2757
  if (mouseUpTarget != null && findRootOwnerId(mouseUpTarget) === store.select('rootId')) {
2723
2758
  return;
2724
2759
  }
2725
- const bounds = getPseudoElementBounds(triggerRef.current);
2726
- if (mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET && mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET) {
2760
+ if (isMouseWithinBounds(mouseEvent, triggerRef.current)) {
2727
2761
  return;
2728
2762
  }
2729
2763
  floatingTreeRoot.events.emit('close', {
@@ -2742,7 +2776,7 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2742
2776
  const parentMenubarHasSubmenuOpen = isInMenubar && parent.context.hasSubmenuOpen;
2743
2777
  const openOnHover = openOnHoverProp ?? parentMenubarHasSubmenuOpen;
2744
2778
  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {
2745
- enabled: openOnHover && !disabled && parent.type !== 'context-menu' && (!isInMenubar || parentMenubarHasSubmenuOpen && !isMountedByThisTrigger),
2779
+ enabled: openOnHover && !disabled && (!isInMenubar || parentMenubarHasSubmenuOpen && !isMountedByThisTrigger),
2746
2780
  handleClose: safePolygon({
2747
2781
  blockPointerEvents: !isInMenubar
2748
2782
  }),
@@ -2763,7 +2797,7 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2763
2797
  // only when `isOpenedByThisTrigger` changes.
2764
2798
  const stickIfOpen = useStickIfOpen(isOpenedByThisTrigger, store.select('lastOpenChangeReason'));
2765
2799
  const click = useClick(floatingRootContext, {
2766
- enabled: !disabled && parent.type !== 'context-menu',
2800
+ enabled: !disabled,
2767
2801
  event: isOpenedByThisTrigger && isInMenubar ? 'click' : 'mousedown',
2768
2802
  toggle: true,
2769
2803
  ignoreMouse: false,
@@ -2858,7 +2892,7 @@ function useStickIfOpen(open, openReason) {
2858
2892
  const stickIfOpenTimeout = useTimeout();
2859
2893
  const [stickIfOpen, setStickIfOpen] = React.useState(false);
2860
2894
  useIsoLayoutEffect(() => {
2861
- if (open && openReason === 'trigger-hover') {
2895
+ if (open && openReason === triggerHover) {
2862
2896
  // Only allow "patient" clicks to close the menu if it's open.
2863
2897
  // If they clicked within 500ms of the menu opening, keep it open.
2864
2898
  setStickIfOpen(true);
@@ -2873,8 +2907,6 @@ function useStickIfOpen(open, openReason) {
2873
2907
  return stickIfOpen;
2874
2908
  }
2875
2909
  function useMenuParent() {
2876
- const contextMenuContext = useContextMenuRootContext(true);
2877
- const parentContext = useMenuRootContext(true);
2878
2910
  const menubarContext = useMenubarContext(true);
2879
2911
  const parent = React.useMemo(() => {
2880
2912
  if (menubarContext) {
@@ -2883,23 +2915,17 @@ function useMenuParent() {
2883
2915
  context: menubarContext
2884
2916
  };
2885
2917
  }
2886
-
2887
- // Ensure this is not a Menu nested inside ContextMenu.Trigger.
2888
- // ContextMenu parentContext is always undefined as ContextMenu.Root is instantiated with
2889
- // <MenuRootContext.Provider value={undefined}>
2890
- if (contextMenuContext && !parentContext) {
2891
- return {
2892
- type: 'context-menu',
2893
- context: contextMenuContext
2894
- };
2895
- }
2896
2918
  return {
2897
2919
  type: undefined
2898
2920
  };
2899
- }, [contextMenuContext, parentContext, menubarContext]);
2921
+ }, [menubarContext]);
2900
2922
  return parent;
2901
2923
  }
2902
2924
 
2925
+ const VOICE_OVER_EXPANDED_PROPS = {
2926
+ 'aria-expanded': undefined
2927
+ };
2928
+
2903
2929
  /**
2904
2930
  * A menu item that opens a submenu.
2905
2931
  * Renders a `<div>` element.
@@ -2920,7 +2946,12 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTri
2920
2946
  disabled: disabledProp = false,
2921
2947
  ...elementProps
2922
2948
  } = componentProps;
2949
+ const submenuRootContext = useMenuSubmenuRootContext();
2950
+ if (!submenuRootContext?.parentMenu) {
2951
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Menu.SubmenuTrigger> must be placed in <Menu.SubmenuRoot>.' : formatErrorMessage(37));
2952
+ }
2923
2953
  const listItem = useCompositeListItem({
2954
+ guess: true,
2924
2955
  label
2925
2956
  });
2926
2957
  const menuPositionerContext = useMenuPositionerContext();
@@ -2949,10 +2980,6 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTri
2949
2980
  triggerElementRef.current = el;
2950
2981
  store.set('activeTriggerElement', el);
2951
2982
  }, [store]);
2952
- const submenuRootContext = useMenuSubmenuRootContext();
2953
- if (!submenuRootContext?.parentMenu) {
2954
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Menu.SubmenuTrigger> must be placed in <Menu.SubmenuRoot>.' : formatErrorMessage(37));
2955
- }
2956
2983
  store.useSyncedValue('closeDelay', closeDelay);
2957
2984
  const parentMenuStore = submenuRootContext.parentMenu;
2958
2985
  const rootDisabled = store.useState('disabled');
@@ -2960,7 +2987,7 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTri
2960
2987
  const disabled = disabledProp || rootDisabled || parentDisabled;
2961
2988
  if (process.env.NODE_ENV !== 'production') {
2962
2989
  // eslint-disable-next-line react-hooks/rules-of-hooks
2963
- useIsoLayoutEffect(() => {
2990
+ React.useEffect(() => {
2964
2991
  const element = triggerElementRef.current;
2965
2992
  if (element && isElementDisabled(element) && !disabled) {
2966
2993
  const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
@@ -3008,7 +3035,10 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTri
3008
3035
  shouldOpen: delay > 0 ? () => parentMenuStore.select('allowMouseEnter') : undefined,
3009
3036
  triggerElementRef,
3010
3037
  externalTree: floatingTreeRoot,
3011
- isClosing: () => store.select('transitionStatus') === 'ending'
3038
+ isClosing: () => store.select('transitionStatus') === 'ending',
3039
+ // Chrome can drop the trigger's `mouseleave` during a fast pointer sweep,
3040
+ // leaving a stale submenu open (see #5152) — cancel from `mouseout` too.
3041
+ guardStaleOpen: true
3012
3042
  });
3013
3043
  const click = useClick(floatingRootContext, {
3014
3044
  enabled: !disabled,
@@ -3025,10 +3055,22 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTri
3025
3055
  highlighted,
3026
3056
  open
3027
3057
  };
3058
+ const openMethod = store.useState('openMethod');
3059
+ const lastOpenChangeReason = store.useState('lastOpenChangeReason');
3060
+ // Arrow keys open the submenu through list navigation without dispatching a click, so
3061
+ // `openMethod` stays null there; Enter and Space do dispatch one and report `keyboard`.
3062
+ const openedByKeyboard = lastOpenChangeReason === listNavigation || openMethod === 'keyboard';
3063
+ const shouldOmitExpanded = open && openedByKeyboard && voiceOver;
3028
3064
  const element = useRenderElement('div', componentProps, {
3029
3065
  state,
3030
3066
  stateAttributesMapping: triggerOpenStateMapping,
3031
- props: [localInteractionProps, hoverProps, rootTriggerProps, itemProps, {
3067
+ props: [localInteractionProps, hoverProps, rootTriggerProps, itemProps,
3068
+ // Opening a submenu changes the trigger's expanded state while the trigger still holds
3069
+ // focus, and VoiceOver announces that state change instead of the submenu item that focus
3070
+ // moves to a moment later, so the first item is never announced. Dropping the state while
3071
+ // the submenu is open avoids the announcement without claiming the submenu is collapsed;
3072
+ // `aria-haspopup` still conveys that the item opens a submenu.
3073
+ shouldOmitExpanded ? VOICE_OVER_EXPANDED_PROPS : undefined, {
3032
3074
  'aria-controls': popupId,
3033
3075
  tabIndex: open || highlighted ? 0 : -1,
3034
3076
  onBlur() {
@@ -3043,4 +3085,4 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTri
3043
3085
  });
3044
3086
  if (process.env.NODE_ENV !== "production") MenuSubmenuTrigger.displayName = "MenuSubmenuTrigger";
3045
3087
 
3046
- export { MenuTrigger as M, REGULAR_ITEM as R, MenuLinkItem as a, MenuItem as b, MenuRadioItem as c, MenuRadioItemIndicator as d, MenuSubmenuTrigger as e, MenuRoot as f, MenuPortal as g, MenuBackdrop as h, MenuPositioner as i, MenuPopup as j, MenuRadioGroup as k, MenuGroup as l, MenuSubmenuRoot as m, MenuGroupLabel as n, useMenuPositionerContext as o, useMenuItem as p, itemMapping as q, MenuStore as r, useMenuRootContext as u };
3088
+ export { MenuTrigger as M, REGULAR_ITEM as R, MenuLinkItem as a, MenuItem as b, MenuRadioItem as c, MenuRadioItemIndicator as d, MenuSubmenuTrigger as e, MenuRoot as f, MenuPortal as g, MenuBackdrop as h, MenuPositioner as i, MenuPopup as j, MenuRadioGroup as k, MenuGroup as l, MenuSubmenuRoot as m, MenuGroupLabel as n, useMenuPositionerContext as o, useMenuItem as p, itemMapping as q, createNullMenuStore as r, useMenuRootContext as u };