@jobber/components 8.23.0 → 8.23.1

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 (134) hide show
  1. package/dist/Autocomplete/index.cjs +1 -1
  2. package/dist/Autocomplete/index.mjs +1 -1
  3. package/dist/Card/index.cjs +4 -4
  4. package/dist/Card/index.mjs +4 -4
  5. package/dist/Chip/index.cjs +1 -1
  6. package/dist/Chip/index.mjs +1 -1
  7. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
  8. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
  9. package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
  10. package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
  11. package/dist/Chips/index.cjs +1 -1
  12. package/dist/Chips/index.mjs +1 -1
  13. package/dist/ComboboxChipRemove-cjs.js +668 -379
  14. package/dist/ComboboxChipRemove-es.js +508 -219
  15. package/dist/ConfirmationModal/index.cjs +1 -1
  16. package/dist/ConfirmationModal/index.mjs +1 -1
  17. package/dist/DataDump/index.cjs +4 -4
  18. package/dist/DataDump/index.mjs +4 -4
  19. package/dist/DataList/components/DataListActions/index.cjs +1 -1
  20. package/dist/DataList/components/DataListActions/index.mjs +1 -1
  21. package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
  22. package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
  23. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
  24. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
  25. package/dist/DataList/components/DataListFilters/index.cjs +1 -1
  26. package/dist/DataList/components/DataListFilters/index.mjs +1 -1
  27. package/dist/DataList/components/DataListHeader/index.cjs +1 -1
  28. package/dist/DataList/components/DataListHeader/index.mjs +1 -1
  29. package/dist/DataList/components/DataListItem/index.cjs +1 -1
  30. package/dist/DataList/components/DataListItem/index.mjs +1 -1
  31. package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
  32. package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
  33. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
  34. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
  35. package/dist/DataList/components/DataListItems/index.cjs +1 -1
  36. package/dist/DataList/components/DataListItems/index.mjs +1 -1
  37. package/dist/DataList/components/DataListLayout/index.cjs +1 -1
  38. package/dist/DataList/components/DataListLayout/index.mjs +1 -1
  39. package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
  40. package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
  41. package/dist/DataList/index.cjs +1 -1
  42. package/dist/DataList/index.mjs +1 -1
  43. package/dist/DatePicker/index.cjs +1 -1
  44. package/dist/DatePicker/index.mjs +1 -1
  45. package/dist/Dialog/index.cjs +3 -3
  46. package/dist/Dialog/index.mjs +3 -3
  47. package/dist/DrawerDescription-cjs.js +32 -33
  48. package/dist/DrawerDescription-es.js +19 -20
  49. package/dist/DrawerTitle-cjs.js +7 -5
  50. package/dist/DrawerTitle-es.js +7 -5
  51. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
  52. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
  53. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
  54. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
  55. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
  56. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
  57. package/dist/FilterPicker/index.cjs +1 -1
  58. package/dist/FilterPicker/index.mjs +1 -1
  59. package/dist/FormFieldWrapper-cjs.js +2 -2
  60. package/dist/FormFieldWrapper-es.js +2 -2
  61. package/dist/FormatFile/index.cjs +1 -1
  62. package/dist/FormatFile/index.mjs +1 -1
  63. package/dist/Gallery/index.cjs +1 -1
  64. package/dist/Gallery/index.mjs +1 -1
  65. package/dist/InputDate/index.cjs +1 -1
  66. package/dist/InputDate/index.mjs +1 -1
  67. package/dist/InputNumberExperimental-cjs.js +319 -102
  68. package/dist/InputNumberExperimental-es.js +321 -104
  69. package/dist/LightBox/index.cjs +1 -1
  70. package/dist/LightBox/index.mjs +1 -1
  71. package/dist/Menu/index.cjs +4 -4
  72. package/dist/Menu/index.mjs +4 -4
  73. package/dist/MenuSubmenuTrigger-cjs.js +545 -498
  74. package/dist/MenuSubmenuTrigger-es.js +490 -443
  75. package/dist/Modal/index.cjs +1 -1
  76. package/dist/Modal/index.mjs +1 -1
  77. package/dist/NumberFieldInput-cjs.js +609 -496
  78. package/dist/NumberFieldInput-es.js +612 -498
  79. package/dist/Page/index.cjs +4 -4
  80. package/dist/Page/index.mjs +4 -4
  81. package/dist/Popover/index.cjs +1 -1
  82. package/dist/Popover/index.mjs +1 -1
  83. package/dist/ScrollAreaViewport-cjs.js +326 -601
  84. package/dist/ScrollAreaViewport-es.js +278 -548
  85. package/dist/SelectGroupLabel-cjs.js +494 -528
  86. package/dist/SelectGroupLabel-es.js +411 -445
  87. package/dist/Separator-cjs.js +1 -0
  88. package/dist/Separator-es.js +1 -0
  89. package/dist/Tooltip/index.cjs +1 -1
  90. package/dist/Tooltip/index.mjs +1 -1
  91. package/dist/docs/InputTime/InputTime.md +8 -0
  92. package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
  93. package/dist/floating-ui.react-cjs.js +586 -10
  94. package/dist/floating-ui.react-dom-cjs.js +92 -92
  95. package/dist/floating-ui.react-dom-es.js +1 -1
  96. package/dist/floating-ui.react-es.js +580 -4
  97. package/dist/floating-ui.utils-cjs.js +159 -0
  98. package/dist/floating-ui.utils-es.js +136 -0
  99. package/dist/index.cjs +4 -4
  100. package/dist/index.mjs +4 -4
  101. package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
  102. package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
  103. package/dist/primitives/BottomSheet/index.cjs +3 -3
  104. package/dist/primitives/BottomSheet/index.mjs +3 -3
  105. package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
  106. package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
  107. package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
  108. package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
  109. package/dist/primitives/SelectPrimitive/index.cjs +5 -5
  110. package/dist/primitives/SelectPrimitive/index.mjs +5 -5
  111. package/dist/primitives/index.cjs +4 -4
  112. package/dist/primitives/index.mjs +4 -4
  113. package/dist/resolveAriaLabelledBy-cjs.js +5 -0
  114. package/dist/resolveAriaLabelledBy-es.js +5 -1
  115. package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
  116. package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
  117. package/dist/styles.css +9 -3
  118. package/dist/unstyledPrimitives/index.cjs +1217 -639
  119. package/dist/unstyledPrimitives/index.mjs +1163 -585
  120. package/dist/useButton-cjs.js +283 -317
  121. package/dist/useButton-es.js +277 -298
  122. package/dist/useCompositeListItem-cjs.js +328 -279
  123. package/dist/useCompositeListItem-es.js +293 -246
  124. package/dist/useLabel-cjs.js +11 -1
  125. package/dist/useLabel-es.js +11 -2
  126. package/dist/useLabelableId-cjs.js +37 -91
  127. package/dist/useLabelableId-es.js +37 -90
  128. package/dist/useRenderElement-cjs.js +81 -26
  129. package/dist/useRenderElement-es.js +81 -27
  130. package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
  131. package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
  132. package/package.json +3 -3
  133. package/dist/index.esm-cjs.js +0 -738
  134. package/dist/index.esm-es.js +0 -712
@@ -1,13 +1,13 @@
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 } from './useRenderElement-es.js';
3
- import { H as createAttribute, b as useFloatingTree, u as useFloatingParentNodeId, J as getNodeChildren, p as popupStateMapping, F as FloatingFocusManager, y as FloatingPortal, K as POPUP_COLLISION_AVOIDANCE, I as InternalBackdrop, A as inertValue, L as FloatingNode, B as DROPDOWN_COLLISION_AVOIDANCE, o as createSelector, R as ReactStore, P as PopupTriggerMap, M as FloatingTreeStore, N as fastComponent, w as useOnFirstRender, O as useFloatingNodeId, q as useOpenInteractionType, z as useScrollLock, t as useDismiss, n as useDirection, T as TYPEAHEAD_RESET_MS, v as useInteractions, Q as FloatingTree, U as fastComponentRef, s as useClick, V as getTabbableBeforeElement, W as isOutsideEvent, X as getTabbableAfterElement, Y as getNextTabbable, x as pressableTriggerOpenStateMapping, Z as FocusGuard, _ as PATIENT_CLICK_THRESHOLD, G as triggerOpenStateMapping } from './InternalBackdrop-es.js';
4
- import { a0 as isMouseLikePointerType, h as useTimeout, g as getTarget, a1 as isTypeableElement, a2 as matchesFocusVisible, a3 as isTargetInsideEnabledTrigger, d as createChangeEventDetails, a4 as triggerFocus, q as activeElement, r as contains, a5 as isMac, a6 as isSafari, o as ownerDocument, a7 as triggerPress, a8 as escapeKey, D as useOnMount, a9 as TYPEABLE_SELECTOR, T as Timeout, b as useStableCallback, a as useIsoLayoutEffect, aa as triggerHover, n as useValueAsRef, t as transitionStatusMapping, Z as itemPress, c as useButton, u as useBaseUiId, f as useOpenChangeComplete, U as outsidePress, ab as useAnimationsFinished, ac as siblingOpen, E as useControlled, k as useTransitionStatus, l as useId, y as focusOut, v as listNavigation, i as imperativeAction, ad as useCompositeRootContext, V as cancelOpen } from './useButton-es.js';
2
+ import { a as useRefWithInit, f as formatErrorMessage, u as useRenderElement, m as mergeProps, c as useMergedRefs, E as EMPTY_OBJECT, b as EMPTY_ARRAY, w as warn } from './useRenderElement-es.js';
3
+ import { X as mergeCleanups, Y as isTypeableElement, Z as matchesFocusVisible, _ as isTargetInsideEnabledTrigger, $ as createAttribute, J as webkit, d as useFloatingTree, u as useFloatingParentNodeId, a0 as isInteractiveElement, a1 as getNodeChildren, p as popupStateMapping, a as FloatingFocusManager, E as FloatingPortal, a2 as POPUP_COLLISION_AVOIDANCE, I as InternalBackdrop, G as inertValue, a3 as FloatingNode, H as DROPDOWN_COLLISION_AVOIDANCE, t as createSelector, R as ReactStore, P as PopupTriggerMap, a4 as FloatingTreeStore, a5 as fastComponent, a6 as useInitialOpenSync, a7 as useFloatingNodeId, v as useOpenInteractionType, L as useImplicitActiveTrigger, M as useOpenStateTransitions, a8 as attachPreventUnmountOnClose, O as setPopupOpenState, a9 as useSyncedFloatingRootContext, y as useDismiss, r as useDirection, aa as TYPEAHEAD_RESET_MS, F as FOCUSABLE_POPUP_PROPS, N as usePopupInteractionProps, ab as FloatingTree, ac as getTabbableBeforeElement, ad as isOutsideEvent, ae as getTabbableAfterElement, af as getNextTabbable, ag as fastComponentRef, T as useTriggerDataForwarding, x as useClick, B as pressableTriggerOpenStateMapping, ah as FocusGuard, ai as PATIENT_CLICK_THRESHOLD, aj as useTriggerRegistration, W as triggerOpenStateMapping } from './useScrollLock-es.js';
4
+ import { Y as isMouseLikePointerType, h as useTimeout, B as addEventListener, g as getTarget, d as createChangeEventDetails, Z as triggerFocus, p as activeElement, o as ownerDocument, q as contains, _ as mac, $ as triggerPress, a0 as escapeKey, L as useOnMount, T as Timeout, b as useStableCallback, a as useIsoLayoutEffect, a1 as triggerHover, m as useValueAsRef, t as transitionStatusMapping, V as itemPress, c as useButton, u as useBaseUiId, f as useOpenChangeComplete, Q as outsidePress, a2 as useAnimationsFinished, a3 as siblingOpen, z as useControlled, k as useTransitionStatus, l as useId, v as focusOut, r as listNavigation, i as imperativeAction, a4 as useCompositeRootContext, R as cancelOpen, S as SafeReact } from './useButton-es.js';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
- import { e as useCompositeListItem, d as getDisabledMountTransitionStyles, c as useAnchorPositioning, f as adaptiveOrigin, C as CompositeList, a as useListNavigation, b as useTypeahead, g as getPseudoElementBounds } from './useCompositeListItem-es.js';
7
- import { u as useToolbarRootContext } from './ToolbarRootContext-es.js';
8
- import { C as COMPOSITE_KEYS } from './CSPContext-es.js';
6
+ import { h as useCompositeListItem, f as getDisabledMountTransitionStyles, d as useAnchorPositioning, i as adaptiveOrigin, c as useAnchoredPopupScrollLock, e as usePositioner, C as CompositeList, a as useListNavigation, b as useTypeahead, g as getPseudoElementBounds } from './useCompositeListItem-es.js';
7
+ import { u as useToolbarRootContext, i as isElementDisabled } from './isElementDisabled-es.js';
8
+ import { C as COMPOSITE_KEYS } from './scrollEdges-es.js';
9
9
  import { o as getWindow, b as isElement, a as isHTMLElement, d as isLastTraversableNode, e as getParentNode } from './floating-ui.utils.dom-es.js';
10
- import { p as popupStoreSelectors, l as createInitialPopupStoreState, m as useImplicitActiveTrigger, n as useOpenStateTransitions, o as useSyncedFloatingRootContext, q as useRole, r as useTriggerDataForwarding, s as useTriggerRegistration } from './ScrollAreaViewport-es.js';
10
+ import { p as popupStoreSelectors, l as createInitialPopupStoreState } from './ScrollAreaViewport-es.js';
11
11
  import * as ReactDOM from 'react-dom';
12
12
 
13
13
  function resolveValue(value, pointerType) {
@@ -35,28 +35,31 @@ function getRestMs(value) {
35
35
  function isClickLikeOpenEvent(openEventType, interactedInside) {
36
36
  return interactedInside || openEventType === 'click' || openEventType === 'mousedown';
37
37
  }
38
+ function isHoverOpenEvent(openEventType) {
39
+ return openEventType?.includes('mouse') && openEventType !== 'mousedown';
40
+ }
38
41
 
39
- const isMacSafari = isMac && isSafari;
42
+ const isMacSafari = mac && webkit;
40
43
  /**
41
44
  * Opens the floating element while the reference element has focus, like CSS
42
45
  * `:focus`.
43
46
  * @see https://floating-ui.com/docs/useFocus
44
47
  */
45
48
  function useFocus(context, props = {}) {
49
+ const {
50
+ enabled = true,
51
+ delay
52
+ } = props;
46
53
  const store = 'rootStore' in context ? context.rootStore : context;
47
54
  const {
48
55
  events,
49
56
  dataRef
50
57
  } = store.context;
51
- const {
52
- enabled = true,
53
- delay
54
- } = props;
55
58
  const blockFocusRef = React.useRef(false);
56
59
  // Track which reference should be blocked from re-opening after Escape/press dismissal.
57
60
  const blockedReferenceRef = React.useRef(null);
58
- const timeout = useTimeout();
59
61
  const keyboardModalityRef = React.useRef(true);
62
+ const timeout = useTimeout();
60
63
  React.useEffect(() => {
61
64
  const domReference = store.select('domReferenceElement');
62
65
  if (!enabled) {
@@ -79,18 +82,7 @@ function useFocus(context, props = {}) {
79
82
  function onPointerDown() {
80
83
  keyboardModalityRef.current = false;
81
84
  }
82
- win.addEventListener('blur', onBlur);
83
- if (isMacSafari) {
84
- win.addEventListener('keydown', onKeyDown, true);
85
- win.addEventListener('pointerdown', onPointerDown, true);
86
- }
87
- return () => {
88
- win.removeEventListener('blur', onBlur);
89
- if (isMacSafari) {
90
- win.removeEventListener('keydown', onKeyDown, true);
91
- win.removeEventListener('pointerdown', onPointerDown, true);
92
- }
93
- };
85
+ return mergeCleanups(addEventListener(win, 'blur', onBlur), isMacSafari && addEventListener(win, 'keydown', onKeyDown, true), isMacSafari && addEventListener(win, 'pointerdown', onPointerDown, true));
94
86
  }, [store, enabled]);
95
87
  React.useEffect(() => {
96
88
  if (!enabled) {
@@ -110,101 +102,100 @@ function useFocus(context, props = {}) {
110
102
  events.off('openchange', onOpenChangeLocal);
111
103
  };
112
104
  }, [events, enabled, store]);
113
- const reference = React.useMemo(() => ({
114
- onMouseLeave() {
105
+ const reference = React.useMemo(() => {
106
+ function resetBlockedFocus() {
115
107
  blockFocusRef.current = false;
116
108
  blockedReferenceRef.current = null;
117
- },
118
- onFocus(event) {
119
- const focusTarget = event.currentTarget;
120
- if (blockFocusRef.current) {
121
- if (blockedReferenceRef.current === focusTarget) {
122
- return;
123
- }
124
- blockFocusRef.current = false;
125
- blockedReferenceRef.current = null;
126
- }
127
- const target = getTarget(event.nativeEvent);
128
- if (isElement(target)) {
129
- // Safari fails to match `:focus-visible` if focus was initially
130
- // outside the document.
131
- if (isMacSafari && !event.relatedTarget) {
132
- if (!keyboardModalityRef.current && !isTypeableElement(target)) {
109
+ }
110
+ return {
111
+ onMouseLeave() {
112
+ resetBlockedFocus();
113
+ },
114
+ onFocus(event) {
115
+ const focusTarget = event.currentTarget;
116
+ if (blockFocusRef.current) {
117
+ if (blockedReferenceRef.current === focusTarget) {
133
118
  return;
134
119
  }
135
- } else if (!matchesFocusVisible(target)) {
136
- return;
120
+ resetBlockedFocus();
137
121
  }
138
- }
139
- const movedFromOtherEnabledTrigger = isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements);
140
- const {
141
- nativeEvent,
142
- currentTarget
143
- } = event;
144
- const delayValue = typeof delay === 'function' ? delay() : delay;
145
- if (store.select('open') && movedFromOtherEnabledTrigger || delayValue === 0 || delayValue === undefined) {
146
- store.setOpen(true, createChangeEventDetails(triggerFocus, nativeEvent, currentTarget));
147
- return;
148
- }
149
- timeout.start(delayValue, () => {
150
- if (blockFocusRef.current) {
151
- return;
122
+ const target = getTarget(event.nativeEvent);
123
+ if (isElement(target)) {
124
+ // Safari fails to match `:focus-visible` if focus was initially
125
+ // outside the document.
126
+ if (isMacSafari && !event.relatedTarget) {
127
+ if (!keyboardModalityRef.current && !isTypeableElement(target)) {
128
+ return;
129
+ }
130
+ } else if (!matchesFocusVisible(target)) {
131
+ return;
132
+ }
152
133
  }
153
- store.setOpen(true, createChangeEventDetails(triggerFocus, nativeEvent, currentTarget));
154
- });
155
- },
156
- onBlur(event) {
157
- blockFocusRef.current = false;
158
- blockedReferenceRef.current = null;
159
- const relatedTarget = event.relatedTarget;
160
- const nativeEvent = event.nativeEvent;
161
-
162
- // Hit the non-modal focus management portal guard. Focus will be
163
- // moved into the floating element immediately after.
164
- const movedToFocusGuard = isElement(relatedTarget) && relatedTarget.hasAttribute(createAttribute('focus-guard')) && relatedTarget.getAttribute('data-type') === 'outside';
165
-
166
- // Wait for the window blur listener to fire.
167
- timeout.start(0, () => {
168
- const domReference = store.select('domReferenceElement');
169
- const activeEl = activeElement(domReference ? domReference.ownerDocument : document);
170
-
171
- // Focus left the page, keep it open.
172
- if (!relatedTarget && activeEl === domReference) {
134
+ const movedFromOtherEnabledTrigger = isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements);
135
+ const {
136
+ nativeEvent,
137
+ currentTarget
138
+ } = event;
139
+ const delayValue = typeof delay === 'function' ? delay() : delay;
140
+ if (store.select('open') && movedFromOtherEnabledTrigger || delayValue === 0 || delayValue === undefined) {
141
+ store.setOpen(true, createChangeEventDetails(triggerFocus, nativeEvent, currentTarget));
173
142
  return;
174
143
  }
144
+ timeout.start(delayValue, () => {
145
+ if (blockFocusRef.current) {
146
+ return;
147
+ }
148
+ store.setOpen(true, createChangeEventDetails(triggerFocus, nativeEvent, currentTarget));
149
+ });
150
+ },
151
+ onBlur(event) {
152
+ resetBlockedFocus();
153
+ const relatedTarget = event.relatedTarget;
154
+ const nativeEvent = event.nativeEvent;
155
+
156
+ // Hit the non-modal focus management portal guard. Focus will be
157
+ // moved into the floating element immediately after.
158
+ const movedToFocusGuard = isElement(relatedTarget) && relatedTarget.hasAttribute(createAttribute('focus-guard')) && relatedTarget.getAttribute('data-type') === 'outside';
159
+
160
+ // Wait for the window blur listener to fire.
161
+ timeout.start(0, () => {
162
+ const domReference = store.select('domReferenceElement');
163
+ const activeEl = activeElement(ownerDocument(domReference));
164
+
165
+ // Focus left the page, keep it open.
166
+ if (!relatedTarget && activeEl === domReference) {
167
+ return;
168
+ }
175
169
 
176
- // When focusing the reference element (e.g. regular click), then
177
- // clicking into the floating element, prevent it from hiding.
178
- // Note: it must be focusable, e.g. `tabindex="-1"`.
179
- // We can not rely on relatedTarget to point to the correct element
180
- // as it will only point to the shadow host of the newly focused element
181
- // and not the element that actually has received focus if it is located
182
- // inside a shadow root.
183
- if (contains(dataRef.current.floatingContext?.refs.floating.current, activeEl) || contains(domReference, activeEl) || movedToFocusGuard) {
184
- return;
185
- }
170
+ // When focusing the reference element (e.g. regular click), then
171
+ // clicking into the floating element, prevent it from hiding.
172
+ // Note: it must be focusable, e.g. `tabindex="-1"`.
173
+ // We can not rely on relatedTarget to point to the correct element
174
+ // as it will only point to the shadow host of the newly focused element
175
+ // and not the element that actually has received focus if it is located
176
+ // inside a shadow root.
177
+ if (contains(dataRef.current.floatingContext?.refs.floating.current, activeEl) || contains(domReference, activeEl) || movedToFocusGuard) {
178
+ return;
179
+ }
186
180
 
187
- // If the next focused element is one of the triggers, do not close
188
- // the floating element. The focus handler of that trigger will
189
- // handle the open state.
190
- const nextFocusedElement = relatedTarget ?? activeEl;
191
- if (isTargetInsideEnabledTrigger(nextFocusedElement, store.context.triggerElements)) {
192
- return;
193
- }
194
- store.setOpen(false, createChangeEventDetails(triggerFocus, nativeEvent));
195
- });
196
- }
197
- }), [dataRef, store, timeout, delay]);
181
+ // If the next focused element is one of the triggers, do not close
182
+ // the floating element. The focus handler of that trigger will
183
+ // handle the open state.
184
+ const nextFocusedElement = relatedTarget ?? activeEl;
185
+ if (isTargetInsideEnabledTrigger(nextFocusedElement, store.context.triggerElements)) {
186
+ return;
187
+ }
188
+ store.setOpen(false, createChangeEventDetails(triggerFocus, nativeEvent));
189
+ });
190
+ }
191
+ };
192
+ }, [dataRef, delay, store, timeout]);
198
193
  return React.useMemo(() => enabled ? {
199
194
  reference,
200
195
  trigger: reference
201
196
  } : {}, [enabled, reference]);
202
197
  }
203
198
 
204
- const interactiveSelector = `button,a,[role="button"],select,[tabindex]:not([tabindex="-1"]),${TYPEABLE_SELECTOR}`;
205
- function isInteractiveElement(element) {
206
- return element ? Boolean(element.closest(interactiveSelector)) : false;
207
- }
208
199
  class HoverInteraction {
209
200
  constructor() {
210
201
  this.pointerType = undefined;
@@ -231,13 +222,18 @@ class HoverInteraction {
231
222
  return this.dispose;
232
223
  };
233
224
  }
225
+ const pointerEventsMutationOwnerByScopeElement = new WeakMap();
234
226
  function clearSafePolygonPointerEventsMutation(instance) {
235
227
  if (!instance.performedPointerEventsMutation) {
236
228
  return;
237
229
  }
238
- instance.pointerEventsScopeElement?.style.removeProperty('pointer-events');
239
- instance.pointerEventsReferenceElement?.style.removeProperty('pointer-events');
240
- instance.pointerEventsFloatingElement?.style.removeProperty('pointer-events');
230
+ const scopeElement = instance.pointerEventsScopeElement;
231
+ if (scopeElement && pointerEventsMutationOwnerByScopeElement.get(scopeElement) === instance) {
232
+ instance.pointerEventsScopeElement?.style.removeProperty('pointer-events');
233
+ instance.pointerEventsReferenceElement?.style.removeProperty('pointer-events');
234
+ instance.pointerEventsFloatingElement?.style.removeProperty('pointer-events');
235
+ pointerEventsMutationOwnerByScopeElement.delete(scopeElement);
236
+ }
241
237
  instance.performedPointerEventsMutation = false;
242
238
  instance.pointerEventsScopeElement = null;
243
239
  instance.pointerEventsReferenceElement = null;
@@ -249,18 +245,23 @@ function applySafePolygonPointerEventsMutation(instance, options) {
249
245
  referenceElement,
250
246
  floatingElement
251
247
  } = options;
248
+ const existingOwner = pointerEventsMutationOwnerByScopeElement.get(scopeElement);
249
+ if (existingOwner && existingOwner !== instance) {
250
+ clearSafePolygonPointerEventsMutation(existingOwner);
251
+ }
252
252
  clearSafePolygonPointerEventsMutation(instance);
253
253
  instance.performedPointerEventsMutation = true;
254
254
  instance.pointerEventsScopeElement = scopeElement;
255
255
  instance.pointerEventsReferenceElement = referenceElement;
256
256
  instance.pointerEventsFloatingElement = floatingElement;
257
+ pointerEventsMutationOwnerByScopeElement.set(scopeElement, instance);
257
258
  scopeElement.style.pointerEvents = 'none';
258
259
  referenceElement.style.pointerEvents = 'auto';
259
260
  floatingElement.style.pointerEvents = 'auto';
260
261
  }
261
262
  function useHoverInteractionSharedState(store) {
262
- const instance = useRefWithInit(HoverInteraction.create).current;
263
263
  const data = store.context.dataRef.current;
264
+ const instance = useRefWithInit(() => data.hoverInteractionState ?? HoverInteraction.create()).current;
264
265
  if (!data.hoverInteractionState) {
265
266
  data.hoverInteractionState = instance;
266
267
  }
@@ -272,6 +273,11 @@ function useHoverInteractionSharedState(store) {
272
273
  * Provides hover interactions that should be attached to the floating element.
273
274
  */
274
275
  function useHoverFloatingInteraction(context, parameters = {}) {
276
+ const {
277
+ enabled = true,
278
+ closeDelay: closeDelayProp = 0,
279
+ nodeId: nodeIdProp
280
+ } = parameters;
275
281
  const store = 'rootStore' in context ? context.rootStore : context;
276
282
  const open = store.useState('open');
277
283
  const floatingElement = store.useState('floatingElement');
@@ -279,47 +285,19 @@ function useHoverFloatingInteraction(context, parameters = {}) {
279
285
  const {
280
286
  dataRef
281
287
  } = store.context;
282
- const {
283
- enabled = true,
284
- closeDelay: closeDelayProp = 0
285
- } = parameters;
286
- const instance = useHoverInteractionSharedState(store);
287
288
  const tree = useFloatingTree();
288
289
  const parentId = useFloatingParentNodeId();
290
+ const instance = useHoverInteractionSharedState(store);
291
+ const childClosedTimeout = useTimeout();
289
292
  const isClickLikeOpenEvent$1 = useStableCallback(() => {
290
293
  return isClickLikeOpenEvent(dataRef.current.openEvent?.type, instance.interactedInside);
291
294
  });
292
295
  const isHoverOpen = useStableCallback(() => {
293
- const type = dataRef.current.openEvent?.type;
294
- return type?.includes('mouse') && type !== 'mousedown';
295
- });
296
- const isRelatedTargetInsideEnabledTrigger = useStableCallback(target => {
297
- return isTargetInsideEnabledTrigger(target, store.context.triggerElements);
296
+ return isHoverOpenEvent(dataRef.current.openEvent?.type);
298
297
  });
299
- const closeWithDelay = React.useCallback(event => {
300
- const closeDelay = getDelay(closeDelayProp, 'close', instance.pointerType);
301
- const close = () => {
302
- store.setOpen(false, createChangeEventDetails(triggerHover, event));
303
- tree?.events.emit('floating.closed', event);
304
- };
305
- if (closeDelay) {
306
- instance.openChangeTimeout.start(closeDelay, close);
307
- } else {
308
- instance.openChangeTimeout.clear();
309
- close();
310
- }
311
- }, [closeDelayProp, store, instance, tree]);
312
298
  const clearPointerEvents = useStableCallback(() => {
313
299
  clearSafePolygonPointerEventsMutation(instance);
314
300
  });
315
- const handleInteractInside = useStableCallback(event => {
316
- const target = getTarget(event);
317
- if (!isInteractiveElement(target)) {
318
- instance.interactedInside = false;
319
- return;
320
- }
321
- instance.interactedInside = target?.closest('[aria-haspopup]') != null;
322
- });
323
301
  useIsoLayoutEffect(() => {
324
302
  if (!open) {
325
303
  instance.pointerType = undefined;
@@ -343,7 +321,13 @@ function useHoverFloatingInteraction(context, parameters = {}) {
343
321
  if (parentFloating) {
344
322
  parentFloating.style.pointerEvents = '';
345
323
  }
346
- const scopeElement = instance.handleCloseOptions?.getScope?.() ?? instance.pointerEventsScopeElement ?? parentFloating ?? ref.closest('[data-rootownerid]') ?? doc.body;
324
+
325
+ // A keep-mounted submenu can appear in the tree before it opens, so a
326
+ // cached scope or parent lookup may resolve to the submenu itself. That
327
+ // would not shield sibling items in the parent menu.
328
+ const cachedScopeElement = instance.pointerEventsScopeElement !== floatingEl ? instance.pointerEventsScopeElement : null;
329
+ const parentScopeElement = parentFloating !== floatingEl ? parentFloating : null;
330
+ const scopeElement = instance.handleCloseOptions?.getScope?.() ?? cachedScopeElement ?? parentScopeElement ?? ref.closest('[data-rootownerid]') ?? doc.body;
347
331
  applySafePolygonPointerEventsMutation(instance, {
348
332
  scopeElement,
349
333
  referenceElement: ref,
@@ -355,11 +339,34 @@ function useHoverFloatingInteraction(context, parameters = {}) {
355
339
  }
356
340
  return undefined;
357
341
  }, [enabled, open, domReferenceElement, floatingElement, instance, isHoverOpen, tree, parentId, clearPointerEvents]);
358
- const childClosedTimeout = useTimeout();
359
342
  React.useEffect(() => {
360
343
  if (!enabled) {
361
344
  return undefined;
362
345
  }
346
+ function hasParentChildren() {
347
+ return !!(tree && parentId && getNodeChildren(tree.nodesRef.current, parentId).length > 0);
348
+ }
349
+ function closeWithDelay(event) {
350
+ const closeDelay = getDelay(closeDelayProp, 'close', instance.pointerType);
351
+ const close = () => {
352
+ store.setOpen(false, createChangeEventDetails(triggerHover, event));
353
+ tree?.events.emit('floating.closed', event);
354
+ };
355
+ if (closeDelay) {
356
+ instance.openChangeTimeout.start(closeDelay, close);
357
+ } else {
358
+ instance.openChangeTimeout.clear();
359
+ close();
360
+ }
361
+ }
362
+ function handleInteractInside(event) {
363
+ const target = getTarget(event);
364
+ if (!isInteractiveElement(target)) {
365
+ instance.interactedInside = false;
366
+ return;
367
+ }
368
+ instance.interactedInside = target?.closest('[aria-haspopup]') != null;
369
+ }
363
370
  function onFloatingMouseEnter() {
364
371
  instance.openChangeTimeout.clear();
365
372
  childClosedTimeout.clear();
@@ -367,15 +374,21 @@ function useHoverFloatingInteraction(context, parameters = {}) {
367
374
  clearPointerEvents();
368
375
  }
369
376
  function onFloatingMouseLeave(event) {
370
- if (tree && parentId && getNodeChildren(tree.nodesRef.current, parentId).length > 0) {
377
+ if (hasParentChildren() && tree) {
371
378
  tree.events.on('floating.closed', onNodeClosed);
372
379
  return;
373
380
  }
374
- if (isRelatedTargetInsideEnabledTrigger(event.relatedTarget)) {
381
+ if (isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements)) {
375
382
  // If the mouse is leaving the reference element to another trigger, don't explicitly close the popup
376
383
  // as it will be moved.
377
384
  return;
378
385
  }
386
+ const currentNodeId = dataRef.current.floatingContext?.nodeId ?? nodeIdProp;
387
+ const relatedTarget = event.relatedTarget;
388
+ const isMovingIntoDescendantFloating = tree && currentNodeId && isElement(relatedTarget) && getNodeChildren(tree.nodesRef.current, currentNodeId, false).some(node => contains(node.context?.elements.floating, relatedTarget));
389
+ if (isMovingIntoDescendantFloating) {
390
+ return;
391
+ }
379
392
 
380
393
  // If the safePolygon handler is active, let it handle the close logic.
381
394
  if (instance.handler) {
@@ -383,12 +396,12 @@ function useHoverFloatingInteraction(context, parameters = {}) {
383
396
  return;
384
397
  }
385
398
  clearPointerEvents();
386
- if (!isClickLikeOpenEvent$1()) {
399
+ if (isHoverOpen() && !isClickLikeOpenEvent$1()) {
387
400
  closeWithDelay(event);
388
401
  }
389
402
  }
390
403
  function onNodeClosed(event) {
391
- if (!tree || !parentId || getNodeChildren(tree.nodesRef.current, parentId).length > 0) {
404
+ if (!tree || !parentId || hasParentChildren()) {
392
405
  return;
393
406
  }
394
407
  // Allow the mouseenter event to fire in case child was closed because mouse moved into parent.
@@ -399,20 +412,10 @@ function useHoverFloatingInteraction(context, parameters = {}) {
399
412
  });
400
413
  }
401
414
  const floating = floatingElement;
402
- if (floating) {
403
- floating.addEventListener('mouseenter', onFloatingMouseEnter);
404
- floating.addEventListener('mouseleave', onFloatingMouseLeave);
405
- floating.addEventListener('pointerdown', handleInteractInside, true);
406
- }
407
- return () => {
408
- if (floating) {
409
- floating.removeEventListener('mouseenter', onFloatingMouseEnter);
410
- floating.removeEventListener('mouseleave', onFloatingMouseLeave);
411
- floating.removeEventListener('pointerdown', handleInteractInside, true);
412
- }
415
+ return mergeCleanups(floating && addEventListener(floating, 'mouseenter', onFloatingMouseEnter), floating && addEventListener(floating, 'mouseleave', onFloatingMouseLeave), floating && addEventListener(floating, 'pointerdown', handleInteractInside, true), () => {
413
416
  tree?.events.off('floating.closed', onNodeClosed);
414
- };
415
- }, [enabled, floatingElement, store, dataRef, isClickLikeOpenEvent$1, isRelatedTargetInsideEnabledTrigger, closeWithDelay, clearPointerEvents, handleInteractInside, instance, tree, parentId, childClosedTimeout]);
417
+ });
418
+ }, [enabled, floatingElement, store, dataRef, closeDelayProp, nodeIdProp, isHoverOpen, isClickLikeOpenEvent$1, clearPointerEvents, instance, tree, parentId, childClosedTimeout]);
416
419
  }
417
420
 
418
421
  const EMPTY_REF = {
@@ -424,11 +427,6 @@ const EMPTY_REF = {
424
427
  * elements.
425
428
  */
426
429
  function useHoverReferenceInteraction(context, props = {}) {
427
- const store = 'rootStore' in context ? context.rootStore : context;
428
- const {
429
- dataRef,
430
- events
431
- } = store.context;
432
430
  const {
433
431
  enabled = true,
434
432
  delay = 0,
@@ -439,23 +437,29 @@ function useHoverReferenceInteraction(context, props = {}) {
439
437
  triggerElementRef = EMPTY_REF,
440
438
  externalTree,
441
439
  isActiveTrigger = true,
442
- getHandleCloseContext
440
+ getHandleCloseContext,
441
+ isClosing,
442
+ shouldOpen: shouldOpenProp
443
443
  } = props;
444
+ const store = 'rootStore' in context ? context.rootStore : context;
445
+ const {
446
+ dataRef,
447
+ events
448
+ } = store.context;
444
449
  const tree = useFloatingTree(externalTree);
445
450
  const instance = useHoverInteractionSharedState(store);
451
+ const isHoverCloseActiveRef = React.useRef(false);
446
452
  const handleCloseRef = useValueAsRef(handleClose);
447
453
  const delayRef = useValueAsRef(delay);
448
454
  const restMsRef = useValueAsRef(restMs);
449
455
  const enabledRef = useValueAsRef(enabled);
450
- if (isActiveTrigger) {
451
- // eslint-disable-next-line no-underscore-dangle
452
- instance.handleCloseOptions = handleCloseRef.current?.__options;
453
- }
456
+ const shouldOpenRef = useValueAsRef(shouldOpenProp);
457
+ const isClosingRef = useValueAsRef(isClosing);
454
458
  const isClickLikeOpenEvent$1 = useStableCallback(() => {
455
459
  return isClickLikeOpenEvent(dataRef.current.openEvent?.type, instance.interactedInside);
456
460
  });
457
- const isRelatedTargetInsideEnabledTrigger = useStableCallback(target => {
458
- return isTargetInsideEnabledTrigger(target, store.context.triggerElements);
461
+ const checkShouldOpen = useStableCallback(() => {
462
+ return shouldOpenRef.current?.() !== false;
459
463
  });
460
464
  const isOverInactiveTrigger = useStableCallback((currentDomReference, currentTarget, target) => {
461
465
  const allTriggers = store.context.triggerElements;
@@ -472,19 +476,6 @@ function useHoverReferenceInteraction(context, props = {}) {
472
476
  const targetElement = target;
473
477
  return allTriggers.hasMatchingElement(trigger => contains(trigger, targetElement)) && (!currentDomReference || !contains(currentDomReference, targetElement));
474
478
  });
475
- const closeWithDelay = React.useCallback((event, runElseBranch = true) => {
476
- const closeDelay = getDelay(delayRef.current, 'close', instance.pointerType);
477
- if (closeDelay) {
478
- instance.openChangeTimeout.start(closeDelay, () => {
479
- store.setOpen(false, createChangeEventDetails(triggerHover, event));
480
- tree?.events.emit('floating.closed', event);
481
- });
482
- } else if (runElseBranch) {
483
- instance.openChangeTimeout.clear();
484
- store.setOpen(false, createChangeEventDetails(triggerHover, event));
485
- tree?.events.emit('floating.closed', event);
486
- }
487
- }, [delayRef, store, instance, tree]);
488
479
  const cleanupMouseMoveHandler = useStableCallback(() => {
489
480
  if (!instance.handler) {
490
481
  return;
@@ -493,10 +484,14 @@ function useHoverReferenceInteraction(context, props = {}) {
493
484
  doc.removeEventListener('mousemove', instance.handler);
494
485
  instance.handler = undefined;
495
486
  });
496
- React.useEffect(() => cleanupMouseMoveHandler, [cleanupMouseMoveHandler]);
497
487
  const clearPointerEvents = useStableCallback(() => {
498
488
  clearSafePolygonPointerEventsMutation(instance);
499
489
  });
490
+ if (isActiveTrigger) {
491
+ // eslint-disable-next-line no-underscore-dangle
492
+ instance.handleCloseOptions = handleCloseRef.current?.__options;
493
+ }
494
+ React.useEffect(() => cleanupMouseMoveHandler, [cleanupMouseMoveHandler]);
500
495
 
501
496
  // When closing before opening, clear the delay timeouts to cancel it
502
497
  // from showing.
@@ -506,11 +501,14 @@ function useHoverReferenceInteraction(context, props = {}) {
506
501
  }
507
502
  function onOpenChangeLocal(details) {
508
503
  if (!details.open) {
504
+ isHoverCloseActiveRef.current = details.reason === triggerHover;
509
505
  cleanupMouseMoveHandler();
510
506
  instance.openChangeTimeout.clear();
511
507
  instance.restTimeout.clear();
512
508
  instance.blockMouseMove = true;
513
509
  instance.restTimeoutPending = false;
510
+ } else {
511
+ isHoverCloseActiveRef.current = false;
514
512
  }
515
513
  }
516
514
  events.on('openchange', onOpenChangeLocal);
@@ -522,6 +520,19 @@ function useHoverReferenceInteraction(context, props = {}) {
522
520
  if (!enabled) {
523
521
  return undefined;
524
522
  }
523
+ function closeWithDelay(event, runElseBranch = true) {
524
+ const closeDelay = getDelay(delayRef.current, 'close', instance.pointerType);
525
+ if (closeDelay) {
526
+ instance.openChangeTimeout.start(closeDelay, () => {
527
+ store.setOpen(false, createChangeEventDetails(triggerHover, event));
528
+ tree?.events.emit('floating.closed', event);
529
+ });
530
+ } else if (runElseBranch) {
531
+ instance.openChangeTimeout.clear();
532
+ store.setOpen(false, createChangeEventDetails(triggerHover, event));
533
+ tree?.events.emit('floating.closed', event);
534
+ }
535
+ }
525
536
  const trigger = triggerElementRef.current ?? (isActiveTrigger ? store.select('domReferenceElement') : null);
526
537
  if (!isElement(trigger)) {
527
538
  return undefined;
@@ -536,27 +547,57 @@ function useHoverReferenceInteraction(context, props = {}) {
536
547
  // Only rest delay is set; there's no fallback delay.
537
548
  // This will be handled by `onMouseMove`.
538
549
  const restMsValue = getRestMs(restMsRef.current);
539
- if (restMsValue > 0 && !getDelay(delayRef.current, 'open')) {
540
- return;
541
- }
542
550
  const openDelay = getDelay(delayRef.current, 'open', instance.pointerType);
543
- const triggerNode = event.currentTarget ?? null;
551
+ const eventTarget = getTarget(event);
552
+ const currentTarget = event.currentTarget ?? null;
544
553
  const currentDomReference = store.select('domReferenceElement');
545
- const isOverInactive = triggerNode == null ? false : isOverInactiveTrigger(currentDomReference, triggerNode, event.target);
554
+ let triggerNode = currentTarget;
555
+
556
+ // Wrapper/delegated mode: resolve the actual trigger from the event target.
557
+ if (isElement(eventTarget) && !store.context.triggerElements.hasElement(eventTarget)) {
558
+ for (const triggerElement of store.context.triggerElements.elements()) {
559
+ if (contains(triggerElement, eventTarget)) {
560
+ triggerNode = triggerElement;
561
+ break;
562
+ }
563
+ }
564
+ }
565
+
566
+ // Wrapper/delegated mode fallback: if the wrapper contains the active trigger,
567
+ // treat this as re-entering that active trigger.
568
+ if (isElement(currentTarget) && isElement(currentDomReference) && !store.context.triggerElements.hasElement(currentTarget) && contains(currentTarget, currentDomReference)) {
569
+ triggerNode = currentDomReference;
570
+ }
571
+ const isOverInactive = triggerNode == null ? false : isOverInactiveTrigger(currentDomReference, triggerNode, eventTarget);
546
572
  const isOpen = store.select('open');
573
+ const isInClosingTransition = isClosingRef.current?.() ?? store.select('transitionStatus') === 'ending';
574
+ const isHoverCloseTransition = !isOpen && isInClosingTransition && isHoverCloseActiveRef.current;
575
+ const isReenteringSameTriggerDuringCloseTransition = !isOverInactive && isElement(triggerNode) && isElement(currentDomReference) && contains(currentDomReference, triggerNode) && isHoverCloseTransition;
576
+ const isRestOnlyDelay = restMsValue > 0 && !openDelay;
577
+ const shouldOpenImmediately = isOverInactive && (isOpen || isHoverCloseTransition) || isReenteringSameTriggerDuringCloseTransition;
547
578
  const shouldOpen = !isOpen || isOverInactive;
548
579
 
549
- // When moving between triggers while already open, open immediately without delay
550
- if (isOverInactive && isOpen) {
551
- store.setOpen(true, createChangeEventDetails(triggerHover, event, triggerNode));
552
- } else if (openDelay) {
580
+ // Open immediately when moving between triggers while open, or during
581
+ // a hover-driven close transition (including same-trigger re-entry).
582
+ if (shouldOpenImmediately) {
583
+ if (checkShouldOpen()) {
584
+ store.setOpen(true, createChangeEventDetails(triggerHover, event, triggerNode));
585
+ }
586
+ return;
587
+ }
588
+ if (isRestOnlyDelay) {
589
+ return;
590
+ }
591
+ if (openDelay) {
553
592
  instance.openChangeTimeout.start(openDelay, () => {
554
- if (shouldOpen) {
593
+ if (shouldOpen && checkShouldOpen()) {
555
594
  store.setOpen(true, createChangeEventDetails(triggerHover, event, triggerNode));
556
595
  }
557
596
  });
558
597
  } else if (shouldOpen) {
559
- store.setOpen(true, createChangeEventDetails(triggerHover, event, triggerNode));
598
+ if (checkShouldOpen()) {
599
+ store.setOpen(true, createChangeEventDetails(triggerHover, event, triggerNode));
600
+ }
560
601
  }
561
602
  }
562
603
  function onMouseLeave(event) {
@@ -570,8 +611,7 @@ function useHoverReferenceInteraction(context, props = {}) {
570
611
  instance.restTimeout.clear();
571
612
  instance.restTimeoutPending = false;
572
613
  const handleCloseContextBase = dataRef.current.floatingContext ?? getHandleCloseContext?.();
573
- const ignoreRelatedTargetTrigger = isRelatedTargetInsideEnabledTrigger(event.relatedTarget);
574
- if (ignoreRelatedTargetTrigger) {
614
+ if (isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements)) {
575
615
  return;
576
616
  }
577
617
  if (handleCloseRef.current && handleCloseContextBase) {
@@ -602,20 +642,12 @@ function useHoverReferenceInteraction(context, props = {}) {
602
642
  }
603
643
  }
604
644
  if (move) {
605
- trigger.addEventListener('mousemove', onMouseEnter, {
645
+ return mergeCleanups(addEventListener(trigger, 'mousemove', onMouseEnter, {
606
646
  once: true
607
- });
647
+ }), addEventListener(trigger, 'mouseenter', onMouseEnter), addEventListener(trigger, 'mouseleave', onMouseLeave));
608
648
  }
609
- trigger.addEventListener('mouseenter', onMouseEnter);
610
- trigger.addEventListener('mouseleave', onMouseLeave);
611
- return () => {
612
- if (move) {
613
- trigger.removeEventListener('mousemove', onMouseEnter);
614
- }
615
- trigger.removeEventListener('mouseenter', onMouseEnter);
616
- trigger.removeEventListener('mouseleave', onMouseLeave);
617
- };
618
- }, [cleanupMouseMoveHandler, clearPointerEvents, dataRef, delayRef, closeWithDelay, store, enabled, handleCloseRef, instance, isActiveTrigger, isOverInactiveTrigger, isClickLikeOpenEvent$1, isRelatedTargetInsideEnabledTrigger, mouseOnly, move, restMsRef, triggerElementRef, tree, enabledRef, getHandleCloseContext]);
649
+ return mergeCleanups(addEventListener(trigger, 'mouseenter', onMouseEnter), addEventListener(trigger, 'mouseleave', onMouseLeave));
650
+ }, [cleanupMouseMoveHandler, clearPointerEvents, dataRef, delayRef, store, enabled, handleCloseRef, instance, isActiveTrigger, isOverInactiveTrigger, isClickLikeOpenEvent$1, mouseOnly, move, restMsRef, triggerElementRef, tree, enabledRef, getHandleCloseContext, isClosingRef, checkShouldOpen]);
619
651
  return React.useMemo(() => {
620
652
  if (!enabled) {
621
653
  return undefined;
@@ -637,6 +669,17 @@ function useHoverReferenceInteraction(context, props = {}) {
637
669
  if (mouseOnly && !isMouseLikePointerType(instance.pointerType)) {
638
670
  return;
639
671
  }
672
+ if (currentOpen && isOverInactive && instance.handleCloseOptions?.blockPointerEvents) {
673
+ const floatingElement = store.select('floatingElement');
674
+ if (floatingElement) {
675
+ const scopeElement = instance.handleCloseOptions?.getScope?.() ?? trigger.ownerDocument.body;
676
+ applySafePolygonPointerEventsMutation(instance, {
677
+ scopeElement,
678
+ referenceElement: trigger,
679
+ floatingElement
680
+ });
681
+ }
682
+ }
640
683
  const restMsValue = getRestMs(restMsRef.current);
641
684
  if (currentOpen && !isOverInactive || restMsValue === 0) {
642
685
  return;
@@ -654,7 +697,7 @@ function useHoverReferenceInteraction(context, props = {}) {
654
697
  return;
655
698
  }
656
699
  const latestOpen = store.select('open');
657
- if (!instance.blockMouseMove && (!latestOpen || isOverInactive)) {
700
+ if (!instance.blockMouseMove && (!latestOpen || isOverInactive) && checkShouldOpen()) {
658
701
  store.setOpen(true, createChangeEventDetails(triggerHover, nativeEvent, trigger));
659
702
  }
660
703
  }
@@ -670,7 +713,7 @@ function useHoverReferenceInteraction(context, props = {}) {
670
713
  }
671
714
  }
672
715
  };
673
- }, [enabled, instance, isClickLikeOpenEvent$1, isOverInactiveTrigger, mouseOnly, store, restMsRef]);
716
+ }, [enabled, instance, isClickLikeOpenEvent$1, isOverInactiveTrigger, mouseOnly, store, restMsRef, checkShouldOpen]);
674
717
  }
675
718
 
676
719
  /* eslint-disable no-nested-ternary */
@@ -909,6 +952,7 @@ function safePolygon(options = {}) {
909
952
 
910
953
  // eslint-disable-next-line no-underscore-dangle
911
954
  fn.__options = {
955
+ ...options,
912
956
  blockPointerEvents
913
957
  };
914
958
  return fn;
@@ -957,8 +1001,9 @@ const stateAttributesMapping$1 = {
957
1001
  */
958
1002
  const MenuBackdrop = /*#__PURE__*/React.forwardRef(function MenuBackdrop(componentProps, forwardedRef) {
959
1003
  const {
960
- className,
961
1004
  render,
1005
+ className,
1006
+ style,
962
1007
  ...elementProps
963
1008
  } = componentProps;
964
1009
  const {
@@ -1009,12 +1054,13 @@ function useMenuItemCommonProps(params) {
1009
1054
  const {
1010
1055
  events: menuEvents
1011
1056
  } = store.useState('floatingTreeRoot');
1057
+ const open = store.useState('open');
1012
1058
  const contextMenuContext = useContextMenuRootContext(true);
1013
1059
  const isContextMenu = contextMenuContext !== undefined;
1014
1060
  return React.useMemo(() => ({
1015
1061
  id,
1016
1062
  role: 'menuitem',
1017
- tabIndex: highlighted ? 0 : -1,
1063
+ tabIndex: open && highlighted ? 0 : -1,
1018
1064
  onKeyDown(event) {
1019
1065
  if (event.key === ' ' && typingRef?.current) {
1020
1066
  event.preventDefault();
@@ -1050,7 +1096,7 @@ function useMenuItemCommonProps(params) {
1050
1096
 
1051
1097
  // On non-macOS platforms, this mouseup belongs to the right-click gesture
1052
1098
  // that opened the context menu, so it must not activate an item.
1053
- if (isContextMenu && !isMac && event.button === 2) {
1099
+ if (isContextMenu && !mac && event.button === 2) {
1054
1100
  return;
1055
1101
  }
1056
1102
  }
@@ -1062,7 +1108,7 @@ function useMenuItemCommonProps(params) {
1062
1108
  }
1063
1109
  }
1064
1110
  }
1065
- }), [closeOnClick, highlighted, id, menuEvents, nodeId, store, typingRef, itemRef, contextMenuContext, isContextMenu, itemMetadata]);
1111
+ }), [closeOnClick, highlighted, id, menuEvents, nodeId, open, store, typingRef, itemRef, contextMenuContext, isContextMenu, itemMetadata]);
1066
1112
  }
1067
1113
 
1068
1114
  const REGULAR_ITEM = {
@@ -1071,7 +1117,7 @@ const REGULAR_ITEM = {
1071
1117
  function useMenuItem(params) {
1072
1118
  const {
1073
1119
  closeOnClick,
1074
- disabled = false,
1120
+ disabled: disabledProp = false,
1075
1121
  highlighted,
1076
1122
  id,
1077
1123
  store,
@@ -1080,6 +1126,8 @@ function useMenuItem(params) {
1080
1126
  itemMetadata,
1081
1127
  nodeId
1082
1128
  } = params;
1129
+ const rootDisabled = store.useState('disabled');
1130
+ const disabled = disabledProp || rootDisabled;
1083
1131
  const itemRef = React.useRef(null);
1084
1132
  const {
1085
1133
  getButtonProps,
@@ -1156,7 +1204,7 @@ if (process.env.NODE_ENV !== "production") MenuGroupContext.displayName = "MenuG
1156
1204
  function useMenuGroupRootContext() {
1157
1205
  const context = React.useContext(MenuGroupContext);
1158
1206
  if (context === undefined) {
1159
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.' : formatErrorMessage(31));
1207
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuGroupContext is missing. Menu group parts must be used within <Menu.Group> or <Menu.RadioGroup>.' : formatErrorMessage(31));
1160
1208
  }
1161
1209
  return context;
1162
1210
  }
@@ -1165,12 +1213,10 @@ const MenuGroup = /*#__PURE__*/React.forwardRef(function MenuGroup(componentProp
1165
1213
  const {
1166
1214
  render,
1167
1215
  className,
1216
+ style,
1168
1217
  ...elementProps
1169
1218
  } = componentProps;
1170
1219
  const [labelId, setLabelId] = React.useState(undefined);
1171
- const context = React.useMemo(() => ({
1172
- setLabelId
1173
- }), [setLabelId]);
1174
1220
  const element = useRenderElement('div', componentProps, {
1175
1221
  ref: forwardedRef,
1176
1222
  props: {
@@ -1180,7 +1226,7 @@ const MenuGroup = /*#__PURE__*/React.forwardRef(function MenuGroup(componentProp
1180
1226
  }
1181
1227
  });
1182
1228
  return /*#__PURE__*/jsx(MenuGroupContext.Provider, {
1183
- value: context,
1229
+ value: setLabelId,
1184
1230
  children: element
1185
1231
  });
1186
1232
  });
@@ -1192,17 +1238,16 @@ if (process.env.NODE_ENV !== "production") MenuGroup.displayName = "MenuGroup";
1192
1238
  *
1193
1239
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
1194
1240
  */
1195
- const MenuGroupLabel = /*#__PURE__*/React.forwardRef(function MenuGroupLabelComponent(componentProps, forwardedRef) {
1241
+ const MenuGroupLabel = /*#__PURE__*/React.forwardRef(function MenuGroupLabel(componentProps, forwardedRef) {
1196
1242
  const {
1197
- className,
1198
1243
  render,
1244
+ className,
1245
+ style,
1199
1246
  id: idProp,
1200
1247
  ...elementProps
1201
1248
  } = componentProps;
1202
1249
  const id = useBaseUiId(idProp);
1203
- const {
1204
- setLabelId
1205
- } = useMenuGroupRootContext();
1250
+ const setLabelId = useMenuGroupRootContext();
1206
1251
  useIsoLayoutEffect(() => {
1207
1252
  setLabelId(id);
1208
1253
  return () => {
@@ -1235,6 +1280,7 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(componentProps,
1235
1280
  nativeButton = false,
1236
1281
  disabled = false,
1237
1282
  closeOnClick = true,
1283
+ style,
1238
1284
  ...elementProps
1239
1285
  } = componentProps;
1240
1286
  const listItem = useCompositeListItem({
@@ -1257,7 +1303,7 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(componentProps,
1257
1303
  id,
1258
1304
  store,
1259
1305
  nativeButton,
1260
- nodeId: menuPositionerContext?.nodeId,
1306
+ nodeId: menuPositionerContext?.context.nodeId,
1261
1307
  itemMetadata: REGULAR_ITEM
1262
1308
  });
1263
1309
  const state = {
@@ -1285,6 +1331,7 @@ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(compone
1285
1331
  id: idProp,
1286
1332
  label,
1287
1333
  closeOnClick = false,
1334
+ style,
1288
1335
  ...elementProps
1289
1336
  } = componentProps;
1290
1337
  const linkRef = React.useRef(null);
@@ -1292,7 +1339,7 @@ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(compone
1292
1339
  label
1293
1340
  });
1294
1341
  const menuPositionerContext = useMenuPositionerContext(true);
1295
- const nodeId = menuPositionerContext?.nodeId;
1342
+ const nodeId = menuPositionerContext?.context.nodeId;
1296
1343
  const id = useBaseUiId(idProp);
1297
1344
  const {
1298
1345
  store
@@ -1319,9 +1366,9 @@ const MenuLinkItem = /*#__PURE__*/React.forwardRef(function MenuLinkItem(compone
1319
1366
  function getItemProps(externalProps) {
1320
1367
  return mergeProps(commonProps, externalProps, getButtonProps);
1321
1368
  }
1322
- const state = React.useMemo(() => ({
1369
+ const state = {
1323
1370
  highlighted
1324
- }), [highlighted]);
1371
+ };
1325
1372
  return useRenderElement('a', componentProps, {
1326
1373
  state,
1327
1374
  props: [itemProps, elementProps, getItemProps],
@@ -1345,6 +1392,7 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1345
1392
  const {
1346
1393
  render,
1347
1394
  className,
1395
+ style,
1348
1396
  finalFocus,
1349
1397
  ...elementProps
1350
1398
  } = componentProps;
@@ -1369,6 +1417,9 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1369
1417
  const floatingTreeRoot = store.useState('floatingTreeRoot');
1370
1418
  const closeDelay = store.useState('closeDelay');
1371
1419
  const activeTriggerElement = store.useState('activeTriggerElement');
1420
+ const hoverEnabled = store.useState('hoverEnabled');
1421
+ const disabled = store.useState('disabled');
1422
+ const openMethod = store.useState('openMethod');
1372
1423
  const isContextMenu = parent.type === 'context-menu';
1373
1424
  useOpenChangeComplete({
1374
1425
  open,
@@ -1388,12 +1439,13 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1388
1439
  floatingTreeRoot.events.off('close', handleClose);
1389
1440
  };
1390
1441
  }, [floatingTreeRoot.events, store]);
1391
- const hoverEnabled = store.useState('hoverEnabled');
1392
- const disabled = store.useState('disabled');
1393
1442
  useHoverFloatingInteraction(floatingContext, {
1394
1443
  enabled: hoverEnabled && !disabled && !isContextMenu && parent.type !== 'menubar',
1395
1444
  closeDelay
1396
1445
  });
1446
+ const setPopupElement = React.useCallback(element => {
1447
+ store.set('popupElement', element);
1448
+ }, [store]);
1397
1449
  const state = {
1398
1450
  transitionStatus,
1399
1451
  side,
@@ -1402,9 +1454,6 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1402
1454
  nested: parent.type === 'menu',
1403
1455
  instant: instantType
1404
1456
  };
1405
- const setPopupElement = React.useCallback(element => {
1406
- store.set('popupElement', element);
1407
- }, [store]);
1408
1457
  const element = useRenderElement('div', componentProps, {
1409
1458
  state,
1410
1459
  ref: [forwardedRef, store.context.popupRef, setPopupElement],
@@ -1425,6 +1474,7 @@ const MenuPopup = /*#__PURE__*/React.forwardRef(function MenuPopup(componentProp
1425
1474
  }
1426
1475
  return /*#__PURE__*/jsx(FloatingFocusManager, {
1427
1476
  context: floatingContext,
1477
+ openInteractionType: openMethod,
1428
1478
  modal: isContextMenu,
1429
1479
  disabled: !mounted,
1430
1480
  returnFocus: finalFocus === undefined ? returnFocus : finalFocus,
@@ -1488,6 +1538,7 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1488
1538
  sticky = false,
1489
1539
  disableAnchorTracking = false,
1490
1540
  collisionAvoidance: collisionAvoidanceProp = DROPDOWN_COLLISION_AVOIDANCE,
1541
+ style,
1491
1542
  ...elementProps
1492
1543
  } = componentProps;
1493
1544
  const {
@@ -1501,6 +1552,7 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1501
1552
  const mounted = store.useState('mounted');
1502
1553
  const open = store.useState('open');
1503
1554
  const modal = store.useState('modal');
1555
+ const openMethod = store.useState('openMethod');
1504
1556
  const triggerElement = store.useState('activeTriggerElement');
1505
1557
  const transitionStatus = store.useState('transitionStatus');
1506
1558
  const positionerElement = store.useState('positionerElement');
@@ -1532,7 +1584,7 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1532
1584
  computedAlign = computedAlign ?? 'start';
1533
1585
  collisionAvoidance = componentProps.collisionAvoidance ?? POPUP_COLLISION_AVOIDANCE;
1534
1586
  } else if (parent.type === 'menubar') {
1535
- computedSide = computedSide ?? 'bottom';
1587
+ computedSide = computedSide ?? (parent.context.orientation === 'vertical' ? 'inline-end' : 'bottom');
1536
1588
  computedAlign = computedAlign ?? 'start';
1537
1589
  }
1538
1590
  const contextMenu = parent.type === 'context-menu';
@@ -1557,20 +1609,6 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1557
1609
  externalTree: floatingTreeRoot,
1558
1610
  adaptiveOrigin: hasViewport ? adaptiveOrigin : undefined
1559
1611
  });
1560
- const positionerProps = React.useMemo(() => {
1561
- const hiddenStyles = {};
1562
- if (!open) {
1563
- hiddenStyles.pointerEvents = 'none';
1564
- }
1565
- return {
1566
- role: 'presentation',
1567
- hidden: !mounted,
1568
- style: {
1569
- ...positioner.positionerStyles,
1570
- ...hiddenStyles
1571
- }
1572
- };
1573
- }, [open, mounted, positioner.positionerStyles]);
1574
1612
  React.useEffect(() => {
1575
1613
  function onMenuOpenChange(details) {
1576
1614
  if (details.open) {
@@ -1678,19 +1716,16 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1678
1716
  nested: parent.type === 'menu',
1679
1717
  instant: instantType
1680
1718
  };
1681
- const contextValue = React.useMemo(() => ({
1682
- side: positioner.side,
1683
- align: positioner.align,
1684
- arrowRef: positioner.arrowRef,
1685
- arrowUncentered: positioner.arrowUncentered,
1686
- arrowStyles: positioner.arrowStyles,
1687
- nodeId: positioner.context.nodeId
1688
- }), [positioner.side, positioner.align, positioner.arrowRef, positioner.arrowUncentered, positioner.arrowStyles, positioner.context.nodeId]);
1689
- const element = useRenderElement('div', componentProps, {
1690
- state,
1691
- stateAttributesMapping: popupStateMapping,
1692
- ref: [forwardedRef, store.useStateSetter('positionerElement')],
1693
- props: [positionerProps, getDisabledMountTransitionStyles(transitionStatus), elementProps]
1719
+ const menubarModal = parent.type === 'menubar' && parent.context.modal;
1720
+ const popupModal = modal && lastOpenChangeReason !== triggerHover;
1721
+ useAnchoredPopupScrollLock(open && (menubarModal || popupModal), openMethod === 'touch', positionerElement, triggerElement);
1722
+ const element = usePositioner(componentProps, state, {
1723
+ styles: positioner.positionerStyles,
1724
+ transitionStatus,
1725
+ props: elementProps,
1726
+ refs: [forwardedRef, store.useStateSetter('positionerElement')],
1727
+ hidden: !mounted,
1728
+ inert: !open
1694
1729
  });
1695
1730
  const shouldRenderBackdrop = mounted && parent.type !== 'menu' && (parent.type !== 'menubar' && modal && lastOpenChangeReason !== triggerHover || parent.type === 'menubar' && parent.context.modal);
1696
1731
 
@@ -1702,7 +1737,7 @@ const MenuPositioner = /*#__PURE__*/React.forwardRef(function MenuPositioner(com
1702
1737
  backdropCutout = triggerElement;
1703
1738
  }
1704
1739
  return /*#__PURE__*/jsxs(MenuPositionerContext.Provider, {
1705
- value: contextValue,
1740
+ value: positioner,
1706
1741
  children: [shouldRenderBackdrop && /*#__PURE__*/jsx(InternalBackdrop, {
1707
1742
  ref: parent.type === 'context-menu' || parent.type === 'nested-context-menu' ? parent.context.internalBackdropRef : null,
1708
1743
  inert: inertValue(!open),
@@ -1743,16 +1778,18 @@ const MenuRadioGroup = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1743
1778
  defaultValue,
1744
1779
  onValueChange: onValueChangeProp,
1745
1780
  disabled = false,
1781
+ style,
1782
+ 'aria-labelledby': ariaLabelledByProp,
1746
1783
  ...elementProps
1747
1784
  } = componentProps;
1785
+ const [labelId, setLabelId] = React.useState(undefined);
1748
1786
  const [value, setValueUnwrapped] = useControlled({
1749
1787
  controlled: valueProp,
1750
1788
  default: defaultValue,
1751
1789
  name: 'MenuRadioGroup'
1752
1790
  });
1753
- const onValueChange = useStableCallback(onValueChangeProp);
1754
1791
  const setValue = useStableCallback((newValue, eventDetails) => {
1755
- onValueChange?.(newValue, eventDetails);
1792
+ onValueChangeProp?.(newValue, eventDetails);
1756
1793
  if (eventDetails.isCanceled) {
1757
1794
  return;
1758
1795
  }
@@ -1766,6 +1803,7 @@ const MenuRadioGroup = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1766
1803
  ref: forwardedRef,
1767
1804
  props: {
1768
1805
  role: 'group',
1806
+ 'aria-labelledby': ariaLabelledByProp ?? labelId,
1769
1807
  'aria-disabled': disabled || undefined,
1770
1808
  ...elementProps
1771
1809
  }
@@ -1775,9 +1813,12 @@ const MenuRadioGroup = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(fun
1775
1813
  setValue,
1776
1814
  disabled
1777
1815
  }), [value, setValue, disabled]);
1778
- return /*#__PURE__*/jsx(MenuRadioGroupContext.Provider, {
1779
- value: context,
1780
- children: element
1816
+ return /*#__PURE__*/jsx(MenuGroupContext.Provider, {
1817
+ value: setLabelId,
1818
+ children: /*#__PURE__*/jsx(MenuRadioGroupContext.Provider, {
1819
+ value: context,
1820
+ children: element
1821
+ })
1781
1822
  });
1782
1823
  }));
1783
1824
  if (process.env.NODE_ENV !== "production") MenuRadioGroup.displayName = "MenuRadioGroup";
@@ -1802,6 +1843,7 @@ const MenuRadioItem = /*#__PURE__*/React.forwardRef(function MenuRadioItem(compo
1802
1843
  disabled: disabledProp = false,
1803
1844
  closeOnClick = false,
1804
1845
  value,
1846
+ style,
1805
1847
  ...elementProps
1806
1848
  } = componentProps;
1807
1849
  const listItem = useCompositeListItem({
@@ -1831,7 +1873,7 @@ const MenuRadioItem = /*#__PURE__*/React.forwardRef(function MenuRadioItem(compo
1831
1873
  id,
1832
1874
  store,
1833
1875
  nativeButton,
1834
- nodeId: menuPositionerContext?.nodeId,
1876
+ nodeId: menuPositionerContext?.context.nodeId,
1835
1877
  itemMetadata: REGULAR_ITEM
1836
1878
  });
1837
1879
  const state = React.useMemo(() => ({
@@ -1839,13 +1881,12 @@ const MenuRadioItem = /*#__PURE__*/React.forwardRef(function MenuRadioItem(compo
1839
1881
  highlighted,
1840
1882
  checked
1841
1883
  }), [disabled, highlighted, checked]);
1842
- const handleClick = useStableCallback(event => {
1843
- const details = {
1844
- ...createChangeEventDetails(itemPress, event.nativeEvent),
1845
- preventUnmountOnClose: () => {}
1846
- };
1884
+ function handleClick(event) {
1885
+ const details = createChangeEventDetails(itemPress, event.nativeEvent, undefined, {
1886
+ preventUnmountOnClose() {}
1887
+ });
1847
1888
  setSelectedValue(value, details);
1848
- });
1889
+ }
1849
1890
  const element = useRenderElement('div', componentProps, {
1850
1891
  state,
1851
1892
  stateAttributesMapping: itemMapping,
@@ -1873,6 +1914,7 @@ const MenuRadioItemIndicator = /*#__PURE__*/React.forwardRef(function MenuRadioI
1873
1914
  const {
1874
1915
  render,
1875
1916
  className,
1917
+ style,
1876
1918
  keepMounted = false,
1877
1919
  ...elementProps
1878
1920
  } = componentProps;
@@ -1925,7 +1967,9 @@ const selectors = {
1925
1967
  ...popupStoreSelectors,
1926
1968
  disabled: createSelector(state => state.parent.type === 'menubar' ? state.parent.context.disabled || state.disabled : state.disabled),
1927
1969
  modal: createSelector(state => (state.parent.type === undefined || state.parent.type === 'context-menu') && (state.modal ?? true)),
1970
+ openMethod: createSelector(state => state.openMethod),
1928
1971
  allowMouseEnter: createSelector(state => state.allowMouseEnter),
1972
+ highlightItemOnHover: createSelector(state => state.highlightItemOnHover),
1929
1973
  stickIfOpen: createSelector(state => state.stickIfOpen),
1930
1974
  parent: createSelector(state => state.parent),
1931
1975
  rootId: createSelector(state => {
@@ -2034,7 +2078,9 @@ function createInitialState() {
2034
2078
  ...createInitialPopupStoreState(),
2035
2079
  disabled: false,
2036
2080
  modal: true,
2081
+ openMethod: null,
2037
2082
  allowMouseEnter: false,
2083
+ highlightItemOnHover: true,
2038
2084
  stickIfOpen: true,
2039
2085
  parent: {
2040
2086
  type: undefined
@@ -2116,46 +2162,19 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2116
2162
  triggerIdProp,
2117
2163
  parent: parentFromContext
2118
2164
  });
2119
-
2120
- // Support initially open state when uncontrolled
2121
- useOnFirstRender(() => {
2122
- if (openProp === undefined && store.state.open === false && defaultOpen === true) {
2123
- store.update({
2124
- open: true,
2125
- activeTriggerId: defaultTriggerIdProp
2126
- });
2127
- }
2128
- });
2165
+ useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerIdProp);
2129
2166
  store.useControlledProp('openProp', openProp);
2130
2167
  store.useControlledProp('triggerIdProp', triggerIdProp);
2131
2168
  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
2169
+ const rootId = useId();
2170
+ const floatingId = useId();
2132
2171
  const floatingTreeRoot = store.useState('floatingTreeRoot');
2133
2172
  const floatingNodeIdFromContext = useFloatingNodeId(floatingTreeRoot);
2134
2173
  const floatingParentNodeIdFromContext = useFloatingParentNodeId();
2135
- useIsoLayoutEffect(() => {
2136
- if (contextMenuContext && !parentMenuRootContext) {
2137
- // This is a context menu root.
2138
- // It doesn't support detached triggers yet, so we have to sync the parent context manually.
2139
- store.update({
2140
- parent: {
2141
- type: 'context-menu',
2142
- context: contextMenuContext
2143
- },
2144
- floatingNodeId: floatingNodeIdFromContext,
2145
- floatingParentNodeId: floatingParentNodeIdFromContext
2146
- });
2147
- } else if (parentMenuRootContext) {
2148
- store.update({
2149
- floatingNodeId: floatingNodeIdFromContext,
2150
- floatingParentNodeId: floatingParentNodeIdFromContext
2151
- });
2152
- }
2153
- }, [contextMenuContext, parentMenuRootContext, floatingNodeIdFromContext, floatingParentNodeIdFromContext, store]);
2154
2174
  const open = store.useState('open');
2155
2175
  const activeTriggerElement = store.useState('activeTriggerElement');
2156
2176
  const positionerElement = store.useState('positionerElement');
2157
2177
  const hoverEnabled = store.useState('hoverEnabled');
2158
- const modal = store.useState('modal');
2159
2178
  const disabled = store.useState('disabled');
2160
2179
  const lastOpenChangeReason = store.useState('lastOpenChangeReason');
2161
2180
  const parent = store.useState('parent');
@@ -2163,22 +2182,27 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2163
2182
  const payload = store.useState('payload');
2164
2183
  const floatingParentNodeId = store.useState('floatingParentNodeId');
2165
2184
  const openEventRef = React.useRef(null);
2185
+ const allowOutsidePressDismissalRef = React.useRef(parent.type !== 'context-menu');
2186
+ const allowOutsidePressDismissalTimeout = useTimeout();
2187
+ const allowTouchToCloseRef = React.useRef(true);
2188
+ const allowTouchToCloseTimeout = useTimeout();
2166
2189
  const nested = floatingParentNodeId != null;
2167
- let floatingEvents;
2168
2190
  if (process.env.NODE_ENV !== 'production') {
2169
2191
  if (parent.type !== undefined && modalProp !== undefined) {
2170
2192
  console.warn('Base UI: The `modal` prop is not supported on nested menus. It will be ignored.');
2171
2193
  }
2172
2194
  }
2173
- store.useSyncedValues({
2174
- disabled: disabledProp,
2175
- modal: parent.type === undefined ? modalProp : undefined,
2176
- rootId: useId()
2177
- });
2178
2195
  const {
2179
2196
  openMethod,
2180
2197
  triggerProps: interactionTypeProps
2181
2198
  } = useOpenInteractionType(open);
2199
+ store.useSyncedValues({
2200
+ disabled: disabledProp,
2201
+ highlightItemOnHover,
2202
+ modal: parent.type === undefined ? modalProp : undefined,
2203
+ openMethod,
2204
+ rootId
2205
+ });
2182
2206
  useImplicitActiveTrigger(store);
2183
2207
  const {
2184
2208
  forceUnmount
@@ -2188,8 +2212,25 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2188
2212
  stickIfOpen: true
2189
2213
  });
2190
2214
  });
2191
- const allowOutsidePressDismissalRef = React.useRef(parent.type !== 'context-menu');
2192
- const allowOutsidePressDismissalTimeout = useTimeout();
2215
+ useIsoLayoutEffect(() => {
2216
+ if (contextMenuContext && !parentMenuRootContext) {
2217
+ // This is a context menu root.
2218
+ // It doesn't support detached triggers yet, so we have to sync the parent context manually.
2219
+ store.update({
2220
+ parent: {
2221
+ type: 'context-menu',
2222
+ context: contextMenuContext
2223
+ },
2224
+ floatingNodeId: floatingNodeIdFromContext,
2225
+ floatingParentNodeId: floatingParentNodeIdFromContext
2226
+ });
2227
+ } else if (parentMenuRootContext) {
2228
+ store.update({
2229
+ floatingNodeId: floatingNodeIdFromContext,
2230
+ floatingParentNodeId: floatingParentNodeIdFromContext
2231
+ });
2232
+ }
2233
+ }, [contextMenuContext, parentMenuRootContext, floatingNodeIdFromContext, floatingParentNodeIdFromContext, store]);
2193
2234
  React.useEffect(() => {
2194
2235
  if (!open) {
2195
2236
  openEventRef.current = null;
@@ -2210,22 +2251,17 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2210
2251
  allowOutsidePressDismissalRef.current = true;
2211
2252
  });
2212
2253
  }, [allowOutsidePressDismissalTimeout, open, parent.type]);
2213
- useScrollLock(open && modal && lastOpenChangeReason !== triggerHover && openMethod !== 'touch', positionerElement);
2214
2254
  useIsoLayoutEffect(() => {
2215
2255
  if (!open && !hoverEnabled) {
2216
2256
  store.set('hoverEnabled', true);
2217
2257
  }
2218
2258
  }, [open, hoverEnabled, store]);
2219
- const allowTouchToCloseRef = React.useRef(true);
2220
- const allowTouchToCloseTimeout = useTimeout();
2221
2259
  const setOpen = useStableCallback((nextOpen, eventDetails) => {
2222
2260
  const reason = eventDetails.reason;
2223
2261
  if (open === nextOpen && eventDetails.trigger === activeTriggerElement && lastOpenChangeReason === reason) {
2224
2262
  return;
2225
2263
  }
2226
- eventDetails.preventUnmountOnClose = () => {
2227
- store.set('preventUnmountingOnClose', true);
2228
- };
2264
+ const shouldPreventUnmountOnClose = attachPreventUnmountOnClose(eventDetails);
2229
2265
 
2230
2266
  // Do not immediately reset the activeTriggerId to allow
2231
2267
  // exit animations to play and focus to be returned correctly.
@@ -2236,31 +2272,12 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2236
2272
  if (eventDetails.isCanceled) {
2237
2273
  return;
2238
2274
  }
2239
- const details = {
2240
- open: nextOpen,
2241
- nativeEvent: eventDetails.event,
2242
- reason: eventDetails.reason,
2243
- nested
2244
- };
2245
- floatingEvents?.emit('openchange', details);
2275
+ store.state.floatingRootContext.dispatchOpenChange(nextOpen, eventDetails);
2246
2276
  const nativeEvent = eventDetails.event;
2247
2277
  if (nextOpen === false && nativeEvent?.type === 'click' && nativeEvent.pointerType === 'touch' && !allowTouchToCloseRef.current) {
2248
2278
  return;
2249
2279
  }
2250
2280
 
2251
- // Workaround `enableFocusInside` in Floating UI setting `tabindex=0` of a non-highlighted
2252
- // option upon close when tabbing out due to `keepMounted=true`:
2253
- // https://github.com/floating-ui/floating-ui/pull/3004/files#diff-962a7439cdeb09ea98d4b622a45d517bce07ad8c3f866e089bda05f4b0bbd875R194-R199
2254
- // This otherwise causes options to retain `tabindex=0` incorrectly when the popup is closed
2255
- // when tabbing outside.
2256
- if (!nextOpen && activeIndex !== null) {
2257
- const activeOption = store.context.itemDomElements.current[activeIndex];
2258
- // Wait for Floating UI's focus effect to have fired
2259
- queueMicrotask(() => {
2260
- activeOption?.setAttribute('tabindex', '-1');
2261
- });
2262
- }
2263
-
2264
2281
  // Prevent the menu from closing on mobile devices that have a delayed click event.
2265
2282
  // In some cases the menu, when tapped, will fire the focus event first and then the click event.
2266
2283
  // Without this guard, the menu will close immediately after opening.
@@ -2280,14 +2297,7 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2280
2297
  openChangeReason: reason
2281
2298
  };
2282
2299
  openEventRef.current = eventDetails.event ?? null;
2283
-
2284
- // If a popup is closing, the `trigger` may be null.
2285
- // We want to keep the previous value so that exit animations are played and focus is returned correctly.
2286
- const newTriggerId = eventDetails.trigger?.id ?? null;
2287
- if (newTriggerId || nextOpen) {
2288
- updatedState.activeTriggerId = newTriggerId;
2289
- updatedState.activeTriggerElement = eventDetails.trigger ?? null;
2290
- }
2300
+ setPopupOpenState(updatedState, nextOpen, eventDetails.trigger, shouldPreventUnmountOnClose());
2291
2301
  store.update(updatedState);
2292
2302
  if (parent.type === 'menubar' && (reason === triggerFocus || reason === focusOut || reason === triggerHover || reason === listNavigation || reason === siblingOpen)) {
2293
2303
  store.set('instantType', 'group');
@@ -2297,33 +2307,13 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2297
2307
  store.set('instantType', undefined);
2298
2308
  }
2299
2309
  });
2300
- const createMenuEventDetails = React.useCallback(reason => {
2301
- const details = createChangeEventDetails(reason);
2302
- details.preventUnmountOnClose = () => {
2303
- store.set('preventUnmountingOnClose', true);
2304
- };
2305
- return details;
2306
- }, [store]);
2307
- const handleImperativeClose = React.useCallback(() => {
2308
- store.setOpen(false, createMenuEventDetails(imperativeAction));
2309
- }, [store, createMenuEventDetails]);
2310
- React.useImperativeHandle(actionsRef, () => ({
2311
- unmount: forceUnmount,
2312
- close: handleImperativeClose
2313
- }), [forceUnmount, handleImperativeClose]);
2314
- let ctx;
2315
- if (parent.type === 'context-menu') {
2316
- ctx = parent.context;
2317
- }
2318
- React.useImperativeHandle(ctx?.positionerRef, () => positionerElement, [positionerElement]);
2319
- React.useImperativeHandle(ctx?.actionsRef, () => ({
2320
- setOpen
2321
- }), [setOpen]);
2322
2310
  const floatingRootContext = useSyncedFloatingRootContext({
2323
2311
  popupStore: store,
2312
+ floatingId,
2313
+ nested: floatingParentNodeIdFromContext != null,
2324
2314
  onOpenChange: setOpen
2325
2315
  });
2326
- floatingEvents = floatingRootContext.context.events;
2316
+ const floatingEvents = floatingRootContext.context.events;
2327
2317
  React.useEffect(() => {
2328
2318
  const handleSetOpenEvent = ({
2329
2319
  open: nextOpen,
@@ -2334,6 +2324,21 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2334
2324
  floatingEvents?.off('setOpen', handleSetOpenEvent);
2335
2325
  };
2336
2326
  }, [floatingEvents, setOpen]);
2327
+ const handleImperativeClose = React.useCallback(() => {
2328
+ store.setOpen(false, createChangeEventDetails(imperativeAction));
2329
+ }, [store]);
2330
+ React.useImperativeHandle(actionsRef, () => ({
2331
+ unmount: forceUnmount,
2332
+ close: handleImperativeClose
2333
+ }), [forceUnmount, handleImperativeClose]);
2334
+ let ctx;
2335
+ if (parent.type === 'context-menu') {
2336
+ ctx = parent.context;
2337
+ }
2338
+ React.useImperativeHandle(ctx?.positionerRef, () => positionerElement, [positionerElement]);
2339
+ React.useImperativeHandle(ctx?.actionsRef, () => ({
2340
+ setOpen
2341
+ }), [setOpen]);
2337
2342
  const dismiss = useDismiss(floatingRootContext, {
2338
2343
  enabled: !disabled,
2339
2344
  bubbles: {
@@ -2347,9 +2352,6 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2347
2352
  },
2348
2353
  externalTree: nested ? floatingTreeRoot : undefined
2349
2354
  });
2350
- const role = useRole(floatingRootContext, {
2351
- role: 'menu'
2352
- });
2353
2355
  const direction = useDirection();
2354
2356
  const setActiveIndex = React.useCallback(index => {
2355
2357
  if (store.select('activeIndex') === index) {
@@ -2372,10 +2374,11 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2372
2374
  externalTree: nested ? floatingTreeRoot : undefined,
2373
2375
  focusItemOnHover: highlightItemOnHover
2374
2376
  });
2375
- const onTypingChange = React.useCallback(nextTyping => {
2377
+ const onTyping = React.useCallback(nextTyping => {
2376
2378
  store.context.typingRef.current = nextTyping;
2377
2379
  }, [store]);
2378
2380
  const typeahead = useTypeahead(floatingRootContext, {
2381
+ enabled: !disabled,
2379
2382
  listRef: store.context.itemLabels,
2380
2383
  elementsRef: store.context.itemDomElements,
2381
2384
  activeIndex,
@@ -2385,34 +2388,28 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2385
2388
  store.set('activeIndex', index);
2386
2389
  }
2387
2390
  },
2388
- onTypingChange
2391
+ onTyping
2389
2392
  });
2390
- const {
2391
- getReferenceProps,
2392
- getFloatingProps,
2393
- getItemProps,
2394
- getTriggerProps
2395
- } = useInteractions([dismiss, role, listNavigation$1, typeahead]);
2396
2393
  const activeTriggerProps = React.useMemo(() => {
2397
- const mergedProps = mergeProps(getReferenceProps(), {
2394
+ const mergedProps = mergeProps(typeahead.reference, listNavigation$1.reference, dismiss.reference, {
2398
2395
  onMouseMove() {
2399
2396
  store.set('allowMouseEnter', true);
2400
2397
  }
2401
2398
  }, interactionTypeProps);
2402
- delete mergedProps.role;
2399
+ mergedProps['aria-haspopup'] = 'menu';
2400
+ mergedProps['aria-expanded'] = open;
2403
2401
  return mergedProps;
2404
- }, [getReferenceProps, store, interactionTypeProps]);
2402
+ }, [store, typeahead.reference, listNavigation$1.reference, dismiss.reference, interactionTypeProps, open]);
2405
2403
  const inactiveTriggerProps = React.useMemo(() => {
2406
- const triggerProps = getTriggerProps();
2407
- if (!triggerProps) {
2408
- return triggerProps;
2409
- }
2410
- const mergedProps = mergeProps(triggerProps, interactionTypeProps);
2411
- delete mergedProps.role;
2412
- delete mergedProps['aria-controls'];
2404
+ const mergedProps = mergeProps(listNavigation$1.trigger, dismiss.trigger, interactionTypeProps);
2405
+ mergedProps['aria-haspopup'] = 'menu';
2406
+ mergedProps['aria-expanded'] = false;
2413
2407
  return mergedProps;
2414
- }, [getTriggerProps, interactionTypeProps]);
2415
- const popupProps = React.useMemo(() => getFloatingProps({
2408
+ }, [listNavigation$1.trigger, dismiss.trigger, interactionTypeProps]);
2409
+ const popupProps = React.useMemo(() => mergeProps(FOCUSABLE_POPUP_PROPS, {
2410
+ id: floatingId,
2411
+ role: 'menu',
2412
+ 'aria-labelledby': activeTriggerElement?.id,
2416
2413
  onMouseMove() {
2417
2414
  store.set('allowMouseEnter', true);
2418
2415
  if (parent.type === 'menu') {
@@ -2433,9 +2430,9 @@ const MenuRoot = fastComponent(function MenuRoot(props) {
2433
2430
  relay(event);
2434
2431
  }
2435
2432
  }
2436
- }), [getFloatingProps, parent.type, store]);
2437
- const itemProps = React.useMemo(() => getItemProps(), [getItemProps]);
2438
- store.useSyncedValues({
2433
+ }, typeahead.floating, listNavigation$1.floating, dismiss.floating), [activeTriggerElement, floatingId, parent.type, store, typeahead.floating, listNavigation$1.floating, dismiss.floating]);
2434
+ const itemProps = listNavigation$1.item ?? EMPTY_OBJECT;
2435
+ usePopupInteractionProps(store, {
2439
2436
  floatingRootContext,
2440
2437
  activeTriggerProps,
2441
2438
  inactiveTriggerProps,
@@ -2465,7 +2462,7 @@ if (process.env.NODE_ENV !== "production") MenuRoot.displayName = "MenuRoot";
2465
2462
 
2466
2463
  /**
2467
2464
  * Groups all parts of a submenu.
2468
- * Doesnt render its own HTML element.
2465
+ * Doesn't render its own HTML element.
2469
2466
  *
2470
2467
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2471
2468
  */
@@ -2495,7 +2492,7 @@ function useCompositeItem(params = {}) {
2495
2492
  const isHighlighted = highlightedIndex === index;
2496
2493
  const itemRef = React.useRef(null);
2497
2494
  const mergedRef = useMergedRefs(ref, itemRef);
2498
- const compositeProps = React.useMemo(() => ({
2495
+ const compositeProps = {
2499
2496
  tabIndex: isHighlighted ? 0 : -1,
2500
2497
  onFocus() {
2501
2498
  onHighlightedIndexChange(index);
@@ -2510,7 +2507,7 @@ function useCompositeItem(params = {}) {
2510
2507
  item.focus();
2511
2508
  }
2512
2509
  }
2513
- }), [isHighlighted, onHighlightedIndexChange, index, highlightItemOnHover]);
2510
+ };
2514
2511
  return {
2515
2512
  compositeProps,
2516
2513
  compositeRef: mergedRef,
@@ -2525,6 +2522,7 @@ function CompositeItem(componentProps) {
2525
2522
  const {
2526
2523
  render,
2527
2524
  className,
2525
+ style,
2528
2526
  state = EMPTY_OBJECT,
2529
2527
  props = EMPTY_ARRAY,
2530
2528
  refs = EMPTY_ARRAY,
@@ -2557,6 +2555,53 @@ function findRootOwnerId(node) {
2557
2555
  return findRootOwnerId(getParentNode(node));
2558
2556
  }
2559
2557
 
2558
+ /**
2559
+ * Minimal store interface required by the focus guard hook.
2560
+ * Both PopoverStore and MenuStore satisfy this interface.
2561
+ */
2562
+
2563
+ /**
2564
+ * Provides focus guard handlers for popup triggers (Popover, Menu).
2565
+ *
2566
+ * When the popup is open, invisible focus guard elements are placed before and after
2567
+ * the trigger. These handlers close the popup and move focus to the appropriate
2568
+ * tabbable element when the guards receive focus (i.e. when the user tabs out).
2569
+ */
2570
+ function useTriggerFocusGuards(store, triggerElementRef) {
2571
+ const preFocusGuardRef = React.useRef(null);
2572
+ function handlePreFocusGuardFocus(event) {
2573
+ ReactDOM.flushSync(() => {
2574
+ store.setOpen(false, createChangeEventDetails(focusOut, event.nativeEvent, event.currentTarget));
2575
+ });
2576
+ const previousTabbable = getTabbableBeforeElement(preFocusGuardRef.current);
2577
+ previousTabbable?.focus();
2578
+ }
2579
+ function handleFocusTargetFocus(event) {
2580
+ const positionerElement = store.select('positionerElement');
2581
+ if (positionerElement && isOutsideEvent(event, positionerElement)) {
2582
+ store.context.beforeContentFocusGuardRef.current?.focus();
2583
+ } else {
2584
+ ReactDOM.flushSync(() => {
2585
+ store.setOpen(false, createChangeEventDetails(focusOut, event.nativeEvent, event.currentTarget));
2586
+ });
2587
+ let nextTabbable = getTabbableAfterElement(store.context.triggerFocusTargetRef.current || triggerElementRef.current);
2588
+ while (nextTabbable !== null && contains(positionerElement, nextTabbable)) {
2589
+ const prevTabbable = nextTabbable;
2590
+ nextTabbable = getNextTabbable(nextTabbable);
2591
+ if (nextTabbable === prevTabbable) {
2592
+ break;
2593
+ }
2594
+ }
2595
+ nextTabbable?.focus();
2596
+ }
2597
+ }
2598
+ return {
2599
+ preFocusGuardRef,
2600
+ handlePreFocusGuardFocus,
2601
+ handleFocusTargetFocus
2602
+ };
2603
+ }
2604
+
2560
2605
  /**
2561
2606
  * Returns `click` and `mousedown` handlers that fix the behavior of triggers of popups that are toggled by different events.
2562
2607
  * For example, a button that opens a popup on mousedown and closes it on click.
@@ -2606,6 +2651,7 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2606
2651
  const {
2607
2652
  render,
2608
2653
  className,
2654
+ style,
2609
2655
  disabled: disabledProp = false,
2610
2656
  nativeButton = true,
2611
2657
  id: idProp,
@@ -2625,6 +2671,7 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2625
2671
  const isTriggerActive = store.useState('isTriggerActive', thisTriggerId);
2626
2672
  const floatingRootContext = store.useState('floatingRootContext');
2627
2673
  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);
2674
+ const popupId = store.useState('triggerPopupId', thisTriggerId);
2628
2675
  const triggerElementRef = React.useRef(null);
2629
2676
  const parent = useMenuParent();
2630
2677
  const compositeRootContext = useCompositeRootContext(true);
@@ -2708,7 +2755,8 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2708
2755
  },
2709
2756
  triggerElementRef,
2710
2757
  externalTree: floatingTreeRoot,
2711
- isActiveTrigger: isTriggerActive
2758
+ isActiveTrigger: isTriggerActive,
2759
+ isClosing: () => store.select('transitionStatus') === 'ending'
2712
2760
  });
2713
2761
 
2714
2762
  // Whether to ignore clicks to open the menu.
@@ -2730,15 +2778,21 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2730
2778
  enabled: isInMenubar,
2731
2779
  mouseDownAction: 'open'
2732
2780
  });
2733
- const localInteractionProps = useInteractions([click, focus]);
2781
+ const localInteractionProps = React.useMemo(() => mergeProps(focus.reference, click.reference), [focus.reference, click.reference]);
2782
+ const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);
2783
+ const {
2784
+ preFocusGuardRef,
2785
+ handlePreFocusGuardFocus,
2786
+ handleFocusTargetFocus
2787
+ } = useTriggerFocusGuards(store, triggerElementRef);
2734
2788
  const state = {
2735
2789
  disabled,
2736
2790
  open: isOpenedByThisTrigger
2737
2791
  };
2738
- const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);
2739
2792
  const ref = [triggerRef, forwardedRef, buttonRef, registerTrigger, triggerElementRef];
2740
- const props = [localInteractionProps.getReferenceProps(), hoverProps ?? EMPTY_OBJECT, rootTriggerProps, {
2793
+ const props = [localInteractionProps, hoverProps ?? EMPTY_OBJECT, rootTriggerProps, {
2741
2794
  'aria-haspopup': 'menu',
2795
+ 'aria-controls': popupId,
2742
2796
  id: thisTriggerId,
2743
2797
  onMouseDown: event => {
2744
2798
  if (store.select('open')) {
@@ -2757,33 +2811,6 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2757
2811
  }, isInMenubar ? {
2758
2812
  role: 'menuitem'
2759
2813
  } : {}, mixedToggleHandlers, elementProps, getButtonProps];
2760
- const preFocusGuardRef = React.useRef(null);
2761
- const handlePreFocusGuardFocus = useStableCallback(event => {
2762
- ReactDOM.flushSync(() => {
2763
- store.setOpen(false, createChangeEventDetails(focusOut, event.nativeEvent, event.currentTarget));
2764
- });
2765
- const previousTabbable = getTabbableBeforeElement(preFocusGuardRef.current);
2766
- previousTabbable?.focus();
2767
- });
2768
- const handleFocusTargetFocus = useStableCallback(event => {
2769
- const currentPositionerElement = store.select('positionerElement');
2770
- if (currentPositionerElement && isOutsideEvent(event, currentPositionerElement)) {
2771
- store.context.beforeContentFocusGuardRef.current?.focus();
2772
- } else {
2773
- ReactDOM.flushSync(() => {
2774
- store.setOpen(false, createChangeEventDetails(focusOut, event.nativeEvent, event.currentTarget));
2775
- });
2776
- let nextTabbable = getTabbableAfterElement(store.context.triggerFocusTargetRef.current || triggerElementRef.current);
2777
- while (nextTabbable !== null && contains(currentPositionerElement, nextTabbable)) {
2778
- const prevTabbable = nextTabbable;
2779
- nextTabbable = getNextTabbable(nextTabbable);
2780
- if (nextTabbable === prevTabbable) {
2781
- break;
2782
- }
2783
- }
2784
- nextTabbable?.focus();
2785
- }
2786
- });
2787
2814
  const element = useRenderElement('button', componentProps, {
2788
2815
  enabled: !isInMenubar,
2789
2816
  stateAttributesMapping: pressableTriggerOpenStateMapping,
@@ -2796,6 +2823,7 @@ const MenuTrigger = fastComponentRef(function MenuTrigger(componentProps, forwar
2796
2823
  tag: "button",
2797
2824
  render: render,
2798
2825
  className: className,
2826
+ style: style,
2799
2827
  state: state,
2800
2828
  refs: ref,
2801
2829
  props: props,
@@ -2879,10 +2907,11 @@ function useMenuParent() {
2879
2907
  *
2880
2908
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2881
2909
  */
2882
- const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTriggerComponent(componentProps, forwardedRef) {
2910
+ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function MenuSubmenuTrigger(componentProps, forwardedRef) {
2883
2911
  const {
2884
2912
  render,
2885
2913
  className,
2914
+ style,
2886
2915
  label,
2887
2916
  id: idProp,
2888
2917
  nativeButton = false,
@@ -2892,7 +2921,9 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2892
2921
  disabled: disabledProp = false,
2893
2922
  ...elementProps
2894
2923
  } = componentProps;
2895
- const listItem = useCompositeListItem();
2924
+ const listItem = useCompositeListItem({
2925
+ label
2926
+ });
2896
2927
  const menuPositionerContext = useMenuPositionerContext();
2897
2928
  const {
2898
2929
  store
@@ -2901,6 +2932,7 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2901
2932
  const open = store.useState('open');
2902
2933
  const floatingRootContext = store.useState('floatingRootContext');
2903
2934
  const floatingTreeRoot = store.useState('floatingTreeRoot');
2935
+ const popupId = store.useState('triggerPopupId', thisTriggerId);
2904
2936
  const baseRegisterTrigger = useTriggerRegistration(thisTriggerId, store);
2905
2937
  const registerTrigger = React.useCallback(element => {
2906
2938
  const cleanup = baseRegisterTrigger(element);
@@ -2924,16 +2956,29 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2924
2956
  }
2925
2957
  store.useSyncedValue('closeDelay', closeDelay);
2926
2958
  const parentMenuStore = submenuRootContext.parentMenu;
2959
+ const rootDisabled = store.useState('disabled');
2960
+ const parentDisabled = parentMenuStore.useState('disabled');
2961
+ const disabled = disabledProp || rootDisabled || parentDisabled;
2962
+ if (process.env.NODE_ENV !== 'production') {
2963
+ // eslint-disable-next-line react-hooks/rules-of-hooks
2964
+ useIsoLayoutEffect(() => {
2965
+ const element = triggerElementRef.current;
2966
+ if (element && isElementDisabled(element) && !disabled) {
2967
+ const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
2968
+ warn(`A disabled element was detected on <Menu.SubmenuTrigger>. To properly disable the trigger, use the \`disabled\` prop on the component instead of setting it on the rendered element.${ownerStackMessage}`);
2969
+ }
2970
+ });
2971
+ }
2927
2972
  const itemProps = parentMenuStore.useState('itemProps');
2928
2973
  const highlighted = parentMenuStore.useState('isActive', listItem.index);
2929
2974
  const itemMetadata = React.useMemo(() => ({
2930
2975
  type: 'submenu-trigger',
2931
2976
  setActive() {
2932
- parentMenuStore.set('activeIndex', listItem.index);
2977
+ if (parentMenuStore.select('highlightItemOnHover')) {
2978
+ parentMenuStore.set('activeIndex', listItem.index);
2979
+ }
2933
2980
  }
2934
2981
  }), [parentMenuStore, listItem.index]);
2935
- const rootDisabled = store.useState('disabled');
2936
- const disabled = disabledProp || rootDisabled;
2937
2982
  const {
2938
2983
  getItemProps,
2939
2984
  itemRef
@@ -2946,10 +2991,9 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2946
2991
  typingRef: parentMenuStore.context.typingRef,
2947
2992
  nativeButton,
2948
2993
  itemMetadata,
2949
- nodeId: menuPositionerContext?.nodeId
2994
+ nodeId: menuPositionerContext?.context.nodeId
2950
2995
  });
2951
2996
  const hoverEnabled = store.useState('hoverEnabled');
2952
- const allowMouseEnter = parentMenuStore.useState('allowMouseEnter');
2953
2997
  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {
2954
2998
  enabled: hoverEnabled && openOnHover && !disabled,
2955
2999
  handleClose: safePolygon({
@@ -2958,12 +3002,14 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2958
3002
  mouseOnly: true,
2959
3003
  move: true,
2960
3004
  restMs: delay,
2961
- delay: allowMouseEnter ? {
3005
+ delay: {
2962
3006
  open: delay,
2963
3007
  close: closeDelay
2964
- } : 0,
3008
+ },
3009
+ shouldOpen: delay > 0 ? () => parentMenuStore.select('allowMouseEnter') : undefined,
2965
3010
  triggerElementRef,
2966
- externalTree: floatingTreeRoot
3011
+ externalTree: floatingTreeRoot,
3012
+ isClosing: () => store.select('transitionStatus') === 'ending'
2967
3013
  });
2968
3014
  const click = useClick(floatingRootContext, {
2969
3015
  enabled: !disabled,
@@ -2972,7 +3018,7 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2972
3018
  ignoreMouse: openOnHover,
2973
3019
  stickIfOpen: false
2974
3020
  });
2975
- const localInteractionProps = useInteractions([click]);
3021
+ const localInteractionProps = click.reference ?? EMPTY_OBJECT;
2976
3022
  const rootTriggerProps = store.useState('triggerProps', true);
2977
3023
  delete rootTriggerProps.id;
2978
3024
  const state = {
@@ -2983,7 +3029,8 @@ const MenuSubmenuTrigger = /*#__PURE__*/React.forwardRef(function SubmenuTrigger
2983
3029
  const element = useRenderElement('div', componentProps, {
2984
3030
  state,
2985
3031
  stateAttributesMapping: triggerOpenStateMapping,
2986
- props: [localInteractionProps.getReferenceProps(), hoverProps, rootTriggerProps, itemProps, {
3032
+ props: [localInteractionProps, hoverProps, rootTriggerProps, itemProps, {
3033
+ 'aria-controls': popupId,
2987
3034
  tabIndex: open || highlighted ? 0 : -1,
2988
3035
  onBlur() {
2989
3036
  if (highlighted) {