@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
@@ -2,12 +2,12 @@
2
2
 
3
3
  var React = require('react');
4
4
  var useRenderElement = require('./useRenderElement-cjs.js');
5
- var InternalBackdrop = require('./InternalBackdrop-cjs.js');
5
+ var useScrollLock = require('./useScrollLock-cjs.js');
6
6
  var useButton = require('./useButton-cjs.js');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var useCompositeListItem = require('./useCompositeListItem-cjs.js');
9
- var ToolbarRootContext = require('./ToolbarRootContext-cjs.js');
10
- var CSPContext = require('./CSPContext-cjs.js');
9
+ var isElementDisabled = require('./isElementDisabled-cjs.js');
10
+ var scrollEdges = require('./scrollEdges-cjs.js');
11
11
  var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
12
12
  var ScrollAreaViewport = require('./ScrollAreaViewport-cjs.js');
13
13
  var ReactDOM = require('react-dom');
@@ -57,28 +57,31 @@ function getRestMs(value) {
57
57
  function isClickLikeOpenEvent(openEventType, interactedInside) {
58
58
  return interactedInside || openEventType === 'click' || openEventType === 'mousedown';
59
59
  }
60
+ function isHoverOpenEvent(openEventType) {
61
+ return openEventType?.includes('mouse') && openEventType !== 'mousedown';
62
+ }
60
63
 
61
- const isMacSafari = useButton.isMac && useButton.isSafari;
64
+ const isMacSafari = useButton.mac && useScrollLock.webkit;
62
65
  /**
63
66
  * Opens the floating element while the reference element has focus, like CSS
64
67
  * `:focus`.
65
68
  * @see https://floating-ui.com/docs/useFocus
66
69
  */
67
70
  function useFocus(context, props = {}) {
71
+ const {
72
+ enabled = true,
73
+ delay
74
+ } = props;
68
75
  const store = 'rootStore' in context ? context.rootStore : context;
69
76
  const {
70
77
  events,
71
78
  dataRef
72
79
  } = store.context;
73
- const {
74
- enabled = true,
75
- delay
76
- } = props;
77
80
  const blockFocusRef = React__namespace.useRef(false);
78
81
  // Track which reference should be blocked from re-opening after Escape/press dismissal.
79
82
  const blockedReferenceRef = React__namespace.useRef(null);
80
- const timeout = useButton.useTimeout();
81
83
  const keyboardModalityRef = React__namespace.useRef(true);
84
+ const timeout = useButton.useTimeout();
82
85
  React__namespace.useEffect(() => {
83
86
  const domReference = store.select('domReferenceElement');
84
87
  if (!enabled) {
@@ -101,18 +104,7 @@ function useFocus(context, props = {}) {
101
104
  function onPointerDown() {
102
105
  keyboardModalityRef.current = false;
103
106
  }
104
- win.addEventListener('blur', onBlur);
105
- if (isMacSafari) {
106
- win.addEventListener('keydown', onKeyDown, true);
107
- win.addEventListener('pointerdown', onPointerDown, true);
108
- }
109
- return () => {
110
- win.removeEventListener('blur', onBlur);
111
- if (isMacSafari) {
112
- win.removeEventListener('keydown', onKeyDown, true);
113
- win.removeEventListener('pointerdown', onPointerDown, true);
114
- }
115
- };
107
+ return useScrollLock.mergeCleanups(useButton.addEventListener(win, 'blur', onBlur), isMacSafari && useButton.addEventListener(win, 'keydown', onKeyDown, true), isMacSafari && useButton.addEventListener(win, 'pointerdown', onPointerDown, true));
116
108
  }, [store, enabled]);
117
109
  React__namespace.useEffect(() => {
118
110
  if (!enabled) {
@@ -132,101 +124,100 @@ function useFocus(context, props = {}) {
132
124
  events.off('openchange', onOpenChangeLocal);
133
125
  };
134
126
  }, [events, enabled, store]);
135
- const reference = React__namespace.useMemo(() => ({
136
- onMouseLeave() {
127
+ const reference = React__namespace.useMemo(() => {
128
+ function resetBlockedFocus() {
137
129
  blockFocusRef.current = false;
138
130
  blockedReferenceRef.current = null;
139
- },
140
- onFocus(event) {
141
- const focusTarget = event.currentTarget;
142
- if (blockFocusRef.current) {
143
- if (blockedReferenceRef.current === focusTarget) {
144
- return;
145
- }
146
- blockFocusRef.current = false;
147
- blockedReferenceRef.current = null;
148
- }
149
- const target = useButton.getTarget(event.nativeEvent);
150
- if (floatingUi_utils_dom.isElement(target)) {
151
- // Safari fails to match `:focus-visible` if focus was initially
152
- // outside the document.
153
- if (isMacSafari && !event.relatedTarget) {
154
- if (!keyboardModalityRef.current && !useButton.isTypeableElement(target)) {
131
+ }
132
+ return {
133
+ onMouseLeave() {
134
+ resetBlockedFocus();
135
+ },
136
+ onFocus(event) {
137
+ const focusTarget = event.currentTarget;
138
+ if (blockFocusRef.current) {
139
+ if (blockedReferenceRef.current === focusTarget) {
155
140
  return;
156
141
  }
157
- } else if (!useButton.matchesFocusVisible(target)) {
158
- return;
142
+ resetBlockedFocus();
159
143
  }
160
- }
161
- const movedFromOtherEnabledTrigger = useButton.isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements);
162
- const {
163
- nativeEvent,
164
- currentTarget
165
- } = event;
166
- const delayValue = typeof delay === 'function' ? delay() : delay;
167
- if (store.select('open') && movedFromOtherEnabledTrigger || delayValue === 0 || delayValue === undefined) {
168
- store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerFocus, nativeEvent, currentTarget));
169
- return;
170
- }
171
- timeout.start(delayValue, () => {
172
- if (blockFocusRef.current) {
173
- return;
144
+ const target = useButton.getTarget(event.nativeEvent);
145
+ if (floatingUi_utils_dom.isElement(target)) {
146
+ // Safari fails to match `:focus-visible` if focus was initially
147
+ // outside the document.
148
+ if (isMacSafari && !event.relatedTarget) {
149
+ if (!keyboardModalityRef.current && !useScrollLock.isTypeableElement(target)) {
150
+ return;
151
+ }
152
+ } else if (!useScrollLock.matchesFocusVisible(target)) {
153
+ return;
154
+ }
174
155
  }
175
- store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerFocus, nativeEvent, currentTarget));
176
- });
177
- },
178
- onBlur(event) {
179
- blockFocusRef.current = false;
180
- blockedReferenceRef.current = null;
181
- const relatedTarget = event.relatedTarget;
182
- const nativeEvent = event.nativeEvent;
183
-
184
- // Hit the non-modal focus management portal guard. Focus will be
185
- // moved into the floating element immediately after.
186
- const movedToFocusGuard = floatingUi_utils_dom.isElement(relatedTarget) && relatedTarget.hasAttribute(InternalBackdrop.createAttribute('focus-guard')) && relatedTarget.getAttribute('data-type') === 'outside';
187
-
188
- // Wait for the window blur listener to fire.
189
- timeout.start(0, () => {
190
- const domReference = store.select('domReferenceElement');
191
- const activeEl = useButton.activeElement(domReference ? domReference.ownerDocument : document);
192
-
193
- // Focus left the page, keep it open.
194
- if (!relatedTarget && activeEl === domReference) {
156
+ const movedFromOtherEnabledTrigger = useScrollLock.isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements);
157
+ const {
158
+ nativeEvent,
159
+ currentTarget
160
+ } = event;
161
+ const delayValue = typeof delay === 'function' ? delay() : delay;
162
+ if (store.select('open') && movedFromOtherEnabledTrigger || delayValue === 0 || delayValue === undefined) {
163
+ store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerFocus, nativeEvent, currentTarget));
195
164
  return;
196
165
  }
166
+ timeout.start(delayValue, () => {
167
+ if (blockFocusRef.current) {
168
+ return;
169
+ }
170
+ store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerFocus, nativeEvent, currentTarget));
171
+ });
172
+ },
173
+ onBlur(event) {
174
+ resetBlockedFocus();
175
+ const relatedTarget = event.relatedTarget;
176
+ const nativeEvent = event.nativeEvent;
177
+
178
+ // Hit the non-modal focus management portal guard. Focus will be
179
+ // moved into the floating element immediately after.
180
+ const movedToFocusGuard = floatingUi_utils_dom.isElement(relatedTarget) && relatedTarget.hasAttribute(useScrollLock.createAttribute('focus-guard')) && relatedTarget.getAttribute('data-type') === 'outside';
181
+
182
+ // Wait for the window blur listener to fire.
183
+ timeout.start(0, () => {
184
+ const domReference = store.select('domReferenceElement');
185
+ const activeEl = useButton.activeElement(useButton.ownerDocument(domReference));
186
+
187
+ // Focus left the page, keep it open.
188
+ if (!relatedTarget && activeEl === domReference) {
189
+ return;
190
+ }
197
191
 
198
- // When focusing the reference element (e.g. regular click), then
199
- // clicking into the floating element, prevent it from hiding.
200
- // Note: it must be focusable, e.g. `tabindex="-1"`.
201
- // We can not rely on relatedTarget to point to the correct element
202
- // as it will only point to the shadow host of the newly focused element
203
- // and not the element that actually has received focus if it is located
204
- // inside a shadow root.
205
- if (useButton.contains(dataRef.current.floatingContext?.refs.floating.current, activeEl) || useButton.contains(domReference, activeEl) || movedToFocusGuard) {
206
- return;
207
- }
192
+ // When focusing the reference element (e.g. regular click), then
193
+ // clicking into the floating element, prevent it from hiding.
194
+ // Note: it must be focusable, e.g. `tabindex="-1"`.
195
+ // We can not rely on relatedTarget to point to the correct element
196
+ // as it will only point to the shadow host of the newly focused element
197
+ // and not the element that actually has received focus if it is located
198
+ // inside a shadow root.
199
+ if (useButton.contains(dataRef.current.floatingContext?.refs.floating.current, activeEl) || useButton.contains(domReference, activeEl) || movedToFocusGuard) {
200
+ return;
201
+ }
208
202
 
209
- // If the next focused element is one of the triggers, do not close
210
- // the floating element. The focus handler of that trigger will
211
- // handle the open state.
212
- const nextFocusedElement = relatedTarget ?? activeEl;
213
- if (useButton.isTargetInsideEnabledTrigger(nextFocusedElement, store.context.triggerElements)) {
214
- return;
215
- }
216
- store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerFocus, nativeEvent));
217
- });
218
- }
219
- }), [dataRef, store, timeout, delay]);
203
+ // If the next focused element is one of the triggers, do not close
204
+ // the floating element. The focus handler of that trigger will
205
+ // handle the open state.
206
+ const nextFocusedElement = relatedTarget ?? activeEl;
207
+ if (useScrollLock.isTargetInsideEnabledTrigger(nextFocusedElement, store.context.triggerElements)) {
208
+ return;
209
+ }
210
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerFocus, nativeEvent));
211
+ });
212
+ }
213
+ };
214
+ }, [dataRef, delay, store, timeout]);
220
215
  return React__namespace.useMemo(() => enabled ? {
221
216
  reference,
222
217
  trigger: reference
223
218
  } : {}, [enabled, reference]);
224
219
  }
225
220
 
226
- const interactiveSelector = `button,a,[role="button"],select,[tabindex]:not([tabindex="-1"]),${useButton.TYPEABLE_SELECTOR}`;
227
- function isInteractiveElement(element) {
228
- return element ? Boolean(element.closest(interactiveSelector)) : false;
229
- }
230
221
  class HoverInteraction {
231
222
  constructor() {
232
223
  this.pointerType = undefined;
@@ -253,13 +244,18 @@ class HoverInteraction {
253
244
  return this.dispose;
254
245
  };
255
246
  }
247
+ const pointerEventsMutationOwnerByScopeElement = new WeakMap();
256
248
  function clearSafePolygonPointerEventsMutation(instance) {
257
249
  if (!instance.performedPointerEventsMutation) {
258
250
  return;
259
251
  }
260
- instance.pointerEventsScopeElement?.style.removeProperty('pointer-events');
261
- instance.pointerEventsReferenceElement?.style.removeProperty('pointer-events');
262
- instance.pointerEventsFloatingElement?.style.removeProperty('pointer-events');
252
+ const scopeElement = instance.pointerEventsScopeElement;
253
+ if (scopeElement && pointerEventsMutationOwnerByScopeElement.get(scopeElement) === instance) {
254
+ instance.pointerEventsScopeElement?.style.removeProperty('pointer-events');
255
+ instance.pointerEventsReferenceElement?.style.removeProperty('pointer-events');
256
+ instance.pointerEventsFloatingElement?.style.removeProperty('pointer-events');
257
+ pointerEventsMutationOwnerByScopeElement.delete(scopeElement);
258
+ }
263
259
  instance.performedPointerEventsMutation = false;
264
260
  instance.pointerEventsScopeElement = null;
265
261
  instance.pointerEventsReferenceElement = null;
@@ -271,18 +267,23 @@ function applySafePolygonPointerEventsMutation(instance, options) {
271
267
  referenceElement,
272
268
  floatingElement
273
269
  } = options;
270
+ const existingOwner = pointerEventsMutationOwnerByScopeElement.get(scopeElement);
271
+ if (existingOwner && existingOwner !== instance) {
272
+ clearSafePolygonPointerEventsMutation(existingOwner);
273
+ }
274
274
  clearSafePolygonPointerEventsMutation(instance);
275
275
  instance.performedPointerEventsMutation = true;
276
276
  instance.pointerEventsScopeElement = scopeElement;
277
277
  instance.pointerEventsReferenceElement = referenceElement;
278
278
  instance.pointerEventsFloatingElement = floatingElement;
279
+ pointerEventsMutationOwnerByScopeElement.set(scopeElement, instance);
279
280
  scopeElement.style.pointerEvents = 'none';
280
281
  referenceElement.style.pointerEvents = 'auto';
281
282
  floatingElement.style.pointerEvents = 'auto';
282
283
  }
283
284
  function useHoverInteractionSharedState(store) {
284
- const instance = useRenderElement.useRefWithInit(HoverInteraction.create).current;
285
285
  const data = store.context.dataRef.current;
286
+ const instance = useRenderElement.useRefWithInit(() => data.hoverInteractionState ?? HoverInteraction.create()).current;
286
287
  if (!data.hoverInteractionState) {
287
288
  data.hoverInteractionState = instance;
288
289
  }
@@ -294,6 +295,11 @@ function useHoverInteractionSharedState(store) {
294
295
  * Provides hover interactions that should be attached to the floating element.
295
296
  */
296
297
  function useHoverFloatingInteraction(context, parameters = {}) {
298
+ const {
299
+ enabled = true,
300
+ closeDelay: closeDelayProp = 0,
301
+ nodeId: nodeIdProp
302
+ } = parameters;
297
303
  const store = 'rootStore' in context ? context.rootStore : context;
298
304
  const open = store.useState('open');
299
305
  const floatingElement = store.useState('floatingElement');
@@ -301,47 +307,19 @@ function useHoverFloatingInteraction(context, parameters = {}) {
301
307
  const {
302
308
  dataRef
303
309
  } = store.context;
304
- const {
305
- enabled = true,
306
- closeDelay: closeDelayProp = 0
307
- } = parameters;
310
+ const tree = useScrollLock.useFloatingTree();
311
+ const parentId = useScrollLock.useFloatingParentNodeId();
308
312
  const instance = useHoverInteractionSharedState(store);
309
- const tree = InternalBackdrop.useFloatingTree();
310
- const parentId = InternalBackdrop.useFloatingParentNodeId();
313
+ const childClosedTimeout = useButton.useTimeout();
311
314
  const isClickLikeOpenEvent$1 = useButton.useStableCallback(() => {
312
315
  return isClickLikeOpenEvent(dataRef.current.openEvent?.type, instance.interactedInside);
313
316
  });
314
317
  const isHoverOpen = useButton.useStableCallback(() => {
315
- const type = dataRef.current.openEvent?.type;
316
- return type?.includes('mouse') && type !== 'mousedown';
317
- });
318
- const isRelatedTargetInsideEnabledTrigger = useButton.useStableCallback(target => {
319
- return useButton.isTargetInsideEnabledTrigger(target, store.context.triggerElements);
318
+ return isHoverOpenEvent(dataRef.current.openEvent?.type);
320
319
  });
321
- const closeWithDelay = React__namespace.useCallback(event => {
322
- const closeDelay = getDelay(closeDelayProp, 'close', instance.pointerType);
323
- const close = () => {
324
- store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerHover, event));
325
- tree?.events.emit('floating.closed', event);
326
- };
327
- if (closeDelay) {
328
- instance.openChangeTimeout.start(closeDelay, close);
329
- } else {
330
- instance.openChangeTimeout.clear();
331
- close();
332
- }
333
- }, [closeDelayProp, store, instance, tree]);
334
320
  const clearPointerEvents = useButton.useStableCallback(() => {
335
321
  clearSafePolygonPointerEventsMutation(instance);
336
322
  });
337
- const handleInteractInside = useButton.useStableCallback(event => {
338
- const target = useButton.getTarget(event);
339
- if (!isInteractiveElement(target)) {
340
- instance.interactedInside = false;
341
- return;
342
- }
343
- instance.interactedInside = target?.closest('[aria-haspopup]') != null;
344
- });
345
323
  useButton.useIsoLayoutEffect(() => {
346
324
  if (!open) {
347
325
  instance.pointerType = undefined;
@@ -365,7 +343,13 @@ function useHoverFloatingInteraction(context, parameters = {}) {
365
343
  if (parentFloating) {
366
344
  parentFloating.style.pointerEvents = '';
367
345
  }
368
- const scopeElement = instance.handleCloseOptions?.getScope?.() ?? instance.pointerEventsScopeElement ?? parentFloating ?? ref.closest('[data-rootownerid]') ?? doc.body;
346
+
347
+ // A keep-mounted submenu can appear in the tree before it opens, so a
348
+ // cached scope or parent lookup may resolve to the submenu itself. That
349
+ // would not shield sibling items in the parent menu.
350
+ const cachedScopeElement = instance.pointerEventsScopeElement !== floatingEl ? instance.pointerEventsScopeElement : null;
351
+ const parentScopeElement = parentFloating !== floatingEl ? parentFloating : null;
352
+ const scopeElement = instance.handleCloseOptions?.getScope?.() ?? cachedScopeElement ?? parentScopeElement ?? ref.closest('[data-rootownerid]') ?? doc.body;
369
353
  applySafePolygonPointerEventsMutation(instance, {
370
354
  scopeElement,
371
355
  referenceElement: ref,
@@ -377,11 +361,34 @@ function useHoverFloatingInteraction(context, parameters = {}) {
377
361
  }
378
362
  return undefined;
379
363
  }, [enabled, open, domReferenceElement, floatingElement, instance, isHoverOpen, tree, parentId, clearPointerEvents]);
380
- const childClosedTimeout = useButton.useTimeout();
381
364
  React__namespace.useEffect(() => {
382
365
  if (!enabled) {
383
366
  return undefined;
384
367
  }
368
+ function hasParentChildren() {
369
+ return !!(tree && parentId && useScrollLock.getNodeChildren(tree.nodesRef.current, parentId).length > 0);
370
+ }
371
+ function closeWithDelay(event) {
372
+ const closeDelay = getDelay(closeDelayProp, 'close', instance.pointerType);
373
+ const close = () => {
374
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerHover, event));
375
+ tree?.events.emit('floating.closed', event);
376
+ };
377
+ if (closeDelay) {
378
+ instance.openChangeTimeout.start(closeDelay, close);
379
+ } else {
380
+ instance.openChangeTimeout.clear();
381
+ close();
382
+ }
383
+ }
384
+ function handleInteractInside(event) {
385
+ const target = useButton.getTarget(event);
386
+ if (!useScrollLock.isInteractiveElement(target)) {
387
+ instance.interactedInside = false;
388
+ return;
389
+ }
390
+ instance.interactedInside = target?.closest('[aria-haspopup]') != null;
391
+ }
385
392
  function onFloatingMouseEnter() {
386
393
  instance.openChangeTimeout.clear();
387
394
  childClosedTimeout.clear();
@@ -389,15 +396,21 @@ function useHoverFloatingInteraction(context, parameters = {}) {
389
396
  clearPointerEvents();
390
397
  }
391
398
  function onFloatingMouseLeave(event) {
392
- if (tree && parentId && InternalBackdrop.getNodeChildren(tree.nodesRef.current, parentId).length > 0) {
399
+ if (hasParentChildren() && tree) {
393
400
  tree.events.on('floating.closed', onNodeClosed);
394
401
  return;
395
402
  }
396
- if (isRelatedTargetInsideEnabledTrigger(event.relatedTarget)) {
403
+ if (useScrollLock.isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements)) {
397
404
  // If the mouse is leaving the reference element to another trigger, don't explicitly close the popup
398
405
  // as it will be moved.
399
406
  return;
400
407
  }
408
+ const currentNodeId = dataRef.current.floatingContext?.nodeId ?? nodeIdProp;
409
+ const relatedTarget = event.relatedTarget;
410
+ const isMovingIntoDescendantFloating = tree && currentNodeId && floatingUi_utils_dom.isElement(relatedTarget) && useScrollLock.getNodeChildren(tree.nodesRef.current, currentNodeId, false).some(node => useButton.contains(node.context?.elements.floating, relatedTarget));
411
+ if (isMovingIntoDescendantFloating) {
412
+ return;
413
+ }
401
414
 
402
415
  // If the safePolygon handler is active, let it handle the close logic.
403
416
  if (instance.handler) {
@@ -405,12 +418,12 @@ function useHoverFloatingInteraction(context, parameters = {}) {
405
418
  return;
406
419
  }
407
420
  clearPointerEvents();
408
- if (!isClickLikeOpenEvent$1()) {
421
+ if (isHoverOpen() && !isClickLikeOpenEvent$1()) {
409
422
  closeWithDelay(event);
410
423
  }
411
424
  }
412
425
  function onNodeClosed(event) {
413
- if (!tree || !parentId || InternalBackdrop.getNodeChildren(tree.nodesRef.current, parentId).length > 0) {
426
+ if (!tree || !parentId || hasParentChildren()) {
414
427
  return;
415
428
  }
416
429
  // Allow the mouseenter event to fire in case child was closed because mouse moved into parent.
@@ -421,20 +434,10 @@ function useHoverFloatingInteraction(context, parameters = {}) {
421
434
  });
422
435
  }
423
436
  const floating = floatingElement;
424
- if (floating) {
425
- floating.addEventListener('mouseenter', onFloatingMouseEnter);
426
- floating.addEventListener('mouseleave', onFloatingMouseLeave);
427
- floating.addEventListener('pointerdown', handleInteractInside, true);
428
- }
429
- return () => {
430
- if (floating) {
431
- floating.removeEventListener('mouseenter', onFloatingMouseEnter);
432
- floating.removeEventListener('mouseleave', onFloatingMouseLeave);
433
- floating.removeEventListener('pointerdown', handleInteractInside, true);
434
- }
437
+ return useScrollLock.mergeCleanups(floating && useButton.addEventListener(floating, 'mouseenter', onFloatingMouseEnter), floating && useButton.addEventListener(floating, 'mouseleave', onFloatingMouseLeave), floating && useButton.addEventListener(floating, 'pointerdown', handleInteractInside, true), () => {
435
438
  tree?.events.off('floating.closed', onNodeClosed);
436
- };
437
- }, [enabled, floatingElement, store, dataRef, isClickLikeOpenEvent$1, isRelatedTargetInsideEnabledTrigger, closeWithDelay, clearPointerEvents, handleInteractInside, instance, tree, parentId, childClosedTimeout]);
439
+ });
440
+ }, [enabled, floatingElement, store, dataRef, closeDelayProp, nodeIdProp, isHoverOpen, isClickLikeOpenEvent$1, clearPointerEvents, instance, tree, parentId, childClosedTimeout]);
438
441
  }
439
442
 
440
443
  const EMPTY_REF = {
@@ -446,11 +449,6 @@ const EMPTY_REF = {
446
449
  * elements.
447
450
  */
448
451
  function useHoverReferenceInteraction(context, props = {}) {
449
- const store = 'rootStore' in context ? context.rootStore : context;
450
- const {
451
- dataRef,
452
- events
453
- } = store.context;
454
452
  const {
455
453
  enabled = true,
456
454
  delay = 0,
@@ -461,23 +459,29 @@ function useHoverReferenceInteraction(context, props = {}) {
461
459
  triggerElementRef = EMPTY_REF,
462
460
  externalTree,
463
461
  isActiveTrigger = true,
464
- getHandleCloseContext
462
+ getHandleCloseContext,
463
+ isClosing,
464
+ shouldOpen: shouldOpenProp
465
465
  } = props;
466
- const tree = InternalBackdrop.useFloatingTree(externalTree);
466
+ const store = 'rootStore' in context ? context.rootStore : context;
467
+ const {
468
+ dataRef,
469
+ events
470
+ } = store.context;
471
+ const tree = useScrollLock.useFloatingTree(externalTree);
467
472
  const instance = useHoverInteractionSharedState(store);
473
+ const isHoverCloseActiveRef = React__namespace.useRef(false);
468
474
  const handleCloseRef = useButton.useValueAsRef(handleClose);
469
475
  const delayRef = useButton.useValueAsRef(delay);
470
476
  const restMsRef = useButton.useValueAsRef(restMs);
471
477
  const enabledRef = useButton.useValueAsRef(enabled);
472
- if (isActiveTrigger) {
473
- // eslint-disable-next-line no-underscore-dangle
474
- instance.handleCloseOptions = handleCloseRef.current?.__options;
475
- }
478
+ const shouldOpenRef = useButton.useValueAsRef(shouldOpenProp);
479
+ const isClosingRef = useButton.useValueAsRef(isClosing);
476
480
  const isClickLikeOpenEvent$1 = useButton.useStableCallback(() => {
477
481
  return isClickLikeOpenEvent(dataRef.current.openEvent?.type, instance.interactedInside);
478
482
  });
479
- const isRelatedTargetInsideEnabledTrigger = useButton.useStableCallback(target => {
480
- return useButton.isTargetInsideEnabledTrigger(target, store.context.triggerElements);
483
+ const checkShouldOpen = useButton.useStableCallback(() => {
484
+ return shouldOpenRef.current?.() !== false;
481
485
  });
482
486
  const isOverInactiveTrigger = useButton.useStableCallback((currentDomReference, currentTarget, target) => {
483
487
  const allTriggers = store.context.triggerElements;
@@ -494,19 +498,6 @@ function useHoverReferenceInteraction(context, props = {}) {
494
498
  const targetElement = target;
495
499
  return allTriggers.hasMatchingElement(trigger => useButton.contains(trigger, targetElement)) && (!currentDomReference || !useButton.contains(currentDomReference, targetElement));
496
500
  });
497
- const closeWithDelay = React__namespace.useCallback((event, runElseBranch = true) => {
498
- const closeDelay = getDelay(delayRef.current, 'close', instance.pointerType);
499
- if (closeDelay) {
500
- instance.openChangeTimeout.start(closeDelay, () => {
501
- store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerHover, event));
502
- tree?.events.emit('floating.closed', event);
503
- });
504
- } else if (runElseBranch) {
505
- instance.openChangeTimeout.clear();
506
- store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerHover, event));
507
- tree?.events.emit('floating.closed', event);
508
- }
509
- }, [delayRef, store, instance, tree]);
510
501
  const cleanupMouseMoveHandler = useButton.useStableCallback(() => {
511
502
  if (!instance.handler) {
512
503
  return;
@@ -515,10 +506,14 @@ function useHoverReferenceInteraction(context, props = {}) {
515
506
  doc.removeEventListener('mousemove', instance.handler);
516
507
  instance.handler = undefined;
517
508
  });
518
- React__namespace.useEffect(() => cleanupMouseMoveHandler, [cleanupMouseMoveHandler]);
519
509
  const clearPointerEvents = useButton.useStableCallback(() => {
520
510
  clearSafePolygonPointerEventsMutation(instance);
521
511
  });
512
+ if (isActiveTrigger) {
513
+ // eslint-disable-next-line no-underscore-dangle
514
+ instance.handleCloseOptions = handleCloseRef.current?.__options;
515
+ }
516
+ React__namespace.useEffect(() => cleanupMouseMoveHandler, [cleanupMouseMoveHandler]);
522
517
 
523
518
  // When closing before opening, clear the delay timeouts to cancel it
524
519
  // from showing.
@@ -528,11 +523,14 @@ function useHoverReferenceInteraction(context, props = {}) {
528
523
  }
529
524
  function onOpenChangeLocal(details) {
530
525
  if (!details.open) {
526
+ isHoverCloseActiveRef.current = details.reason === useButton.triggerHover;
531
527
  cleanupMouseMoveHandler();
532
528
  instance.openChangeTimeout.clear();
533
529
  instance.restTimeout.clear();
534
530
  instance.blockMouseMove = true;
535
531
  instance.restTimeoutPending = false;
532
+ } else {
533
+ isHoverCloseActiveRef.current = false;
536
534
  }
537
535
  }
538
536
  events.on('openchange', onOpenChangeLocal);
@@ -544,6 +542,19 @@ function useHoverReferenceInteraction(context, props = {}) {
544
542
  if (!enabled) {
545
543
  return undefined;
546
544
  }
545
+ function closeWithDelay(event, runElseBranch = true) {
546
+ const closeDelay = getDelay(delayRef.current, 'close', instance.pointerType);
547
+ if (closeDelay) {
548
+ instance.openChangeTimeout.start(closeDelay, () => {
549
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerHover, event));
550
+ tree?.events.emit('floating.closed', event);
551
+ });
552
+ } else if (runElseBranch) {
553
+ instance.openChangeTimeout.clear();
554
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerHover, event));
555
+ tree?.events.emit('floating.closed', event);
556
+ }
557
+ }
547
558
  const trigger = triggerElementRef.current ?? (isActiveTrigger ? store.select('domReferenceElement') : null);
548
559
  if (!floatingUi_utils_dom.isElement(trigger)) {
549
560
  return undefined;
@@ -558,27 +569,57 @@ function useHoverReferenceInteraction(context, props = {}) {
558
569
  // Only rest delay is set; there's no fallback delay.
559
570
  // This will be handled by `onMouseMove`.
560
571
  const restMsValue = getRestMs(restMsRef.current);
561
- if (restMsValue > 0 && !getDelay(delayRef.current, 'open')) {
562
- return;
563
- }
564
572
  const openDelay = getDelay(delayRef.current, 'open', instance.pointerType);
565
- const triggerNode = event.currentTarget ?? null;
573
+ const eventTarget = useButton.getTarget(event);
574
+ const currentTarget = event.currentTarget ?? null;
566
575
  const currentDomReference = store.select('domReferenceElement');
567
- const isOverInactive = triggerNode == null ? false : isOverInactiveTrigger(currentDomReference, triggerNode, event.target);
576
+ let triggerNode = currentTarget;
577
+
578
+ // Wrapper/delegated mode: resolve the actual trigger from the event target.
579
+ if (floatingUi_utils_dom.isElement(eventTarget) && !store.context.triggerElements.hasElement(eventTarget)) {
580
+ for (const triggerElement of store.context.triggerElements.elements()) {
581
+ if (useButton.contains(triggerElement, eventTarget)) {
582
+ triggerNode = triggerElement;
583
+ break;
584
+ }
585
+ }
586
+ }
587
+
588
+ // Wrapper/delegated mode fallback: if the wrapper contains the active trigger,
589
+ // treat this as re-entering that active trigger.
590
+ if (floatingUi_utils_dom.isElement(currentTarget) && floatingUi_utils_dom.isElement(currentDomReference) && !store.context.triggerElements.hasElement(currentTarget) && useButton.contains(currentTarget, currentDomReference)) {
591
+ triggerNode = currentDomReference;
592
+ }
593
+ const isOverInactive = triggerNode == null ? false : isOverInactiveTrigger(currentDomReference, triggerNode, eventTarget);
568
594
  const isOpen = store.select('open');
595
+ const isInClosingTransition = isClosingRef.current?.() ?? store.select('transitionStatus') === 'ending';
596
+ const isHoverCloseTransition = !isOpen && isInClosingTransition && isHoverCloseActiveRef.current;
597
+ const isReenteringSameTriggerDuringCloseTransition = !isOverInactive && floatingUi_utils_dom.isElement(triggerNode) && floatingUi_utils_dom.isElement(currentDomReference) && useButton.contains(currentDomReference, triggerNode) && isHoverCloseTransition;
598
+ const isRestOnlyDelay = restMsValue > 0 && !openDelay;
599
+ const shouldOpenImmediately = isOverInactive && (isOpen || isHoverCloseTransition) || isReenteringSameTriggerDuringCloseTransition;
569
600
  const shouldOpen = !isOpen || isOverInactive;
570
601
 
571
- // When moving between triggers while already open, open immediately without delay
572
- if (isOverInactive && isOpen) {
573
- store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerHover, event, triggerNode));
574
- } else if (openDelay) {
602
+ // Open immediately when moving between triggers while open, or during
603
+ // a hover-driven close transition (including same-trigger re-entry).
604
+ if (shouldOpenImmediately) {
605
+ if (checkShouldOpen()) {
606
+ store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerHover, event, triggerNode));
607
+ }
608
+ return;
609
+ }
610
+ if (isRestOnlyDelay) {
611
+ return;
612
+ }
613
+ if (openDelay) {
575
614
  instance.openChangeTimeout.start(openDelay, () => {
576
- if (shouldOpen) {
615
+ if (shouldOpen && checkShouldOpen()) {
577
616
  store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerHover, event, triggerNode));
578
617
  }
579
618
  });
580
619
  } else if (shouldOpen) {
581
- store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerHover, event, triggerNode));
620
+ if (checkShouldOpen()) {
621
+ store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerHover, event, triggerNode));
622
+ }
582
623
  }
583
624
  }
584
625
  function onMouseLeave(event) {
@@ -592,8 +633,7 @@ function useHoverReferenceInteraction(context, props = {}) {
592
633
  instance.restTimeout.clear();
593
634
  instance.restTimeoutPending = false;
594
635
  const handleCloseContextBase = dataRef.current.floatingContext ?? getHandleCloseContext?.();
595
- const ignoreRelatedTargetTrigger = isRelatedTargetInsideEnabledTrigger(event.relatedTarget);
596
- if (ignoreRelatedTargetTrigger) {
636
+ if (useScrollLock.isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements)) {
597
637
  return;
598
638
  }
599
639
  if (handleCloseRef.current && handleCloseContextBase) {
@@ -624,20 +664,12 @@ function useHoverReferenceInteraction(context, props = {}) {
624
664
  }
625
665
  }
626
666
  if (move) {
627
- trigger.addEventListener('mousemove', onMouseEnter, {
667
+ return useScrollLock.mergeCleanups(useButton.addEventListener(trigger, 'mousemove', onMouseEnter, {
628
668
  once: true
629
- });
669
+ }), useButton.addEventListener(trigger, 'mouseenter', onMouseEnter), useButton.addEventListener(trigger, 'mouseleave', onMouseLeave));
630
670
  }
631
- trigger.addEventListener('mouseenter', onMouseEnter);
632
- trigger.addEventListener('mouseleave', onMouseLeave);
633
- return () => {
634
- if (move) {
635
- trigger.removeEventListener('mousemove', onMouseEnter);
636
- }
637
- trigger.removeEventListener('mouseenter', onMouseEnter);
638
- trigger.removeEventListener('mouseleave', onMouseLeave);
639
- };
640
- }, [cleanupMouseMoveHandler, clearPointerEvents, dataRef, delayRef, closeWithDelay, store, enabled, handleCloseRef, instance, isActiveTrigger, isOverInactiveTrigger, isClickLikeOpenEvent$1, isRelatedTargetInsideEnabledTrigger, mouseOnly, move, restMsRef, triggerElementRef, tree, enabledRef, getHandleCloseContext]);
671
+ return useScrollLock.mergeCleanups(useButton.addEventListener(trigger, 'mouseenter', onMouseEnter), useButton.addEventListener(trigger, 'mouseleave', onMouseLeave));
672
+ }, [cleanupMouseMoveHandler, clearPointerEvents, dataRef, delayRef, store, enabled, handleCloseRef, instance, isActiveTrigger, isOverInactiveTrigger, isClickLikeOpenEvent$1, mouseOnly, move, restMsRef, triggerElementRef, tree, enabledRef, getHandleCloseContext, isClosingRef, checkShouldOpen]);
641
673
  return React__namespace.useMemo(() => {
642
674
  if (!enabled) {
643
675
  return undefined;
@@ -659,6 +691,17 @@ function useHoverReferenceInteraction(context, props = {}) {
659
691
  if (mouseOnly && !useButton.isMouseLikePointerType(instance.pointerType)) {
660
692
  return;
661
693
  }
694
+ if (currentOpen && isOverInactive && instance.handleCloseOptions?.blockPointerEvents) {
695
+ const floatingElement = store.select('floatingElement');
696
+ if (floatingElement) {
697
+ const scopeElement = instance.handleCloseOptions?.getScope?.() ?? trigger.ownerDocument.body;
698
+ applySafePolygonPointerEventsMutation(instance, {
699
+ scopeElement,
700
+ referenceElement: trigger,
701
+ floatingElement
702
+ });
703
+ }
704
+ }
662
705
  const restMsValue = getRestMs(restMsRef.current);
663
706
  if (currentOpen && !isOverInactive || restMsValue === 0) {
664
707
  return;
@@ -676,7 +719,7 @@ function useHoverReferenceInteraction(context, props = {}) {
676
719
  return;
677
720
  }
678
721
  const latestOpen = store.select('open');
679
- if (!instance.blockMouseMove && (!latestOpen || isOverInactive)) {
722
+ if (!instance.blockMouseMove && (!latestOpen || isOverInactive) && checkShouldOpen()) {
680
723
  store.setOpen(true, useButton.createChangeEventDetails(useButton.triggerHover, nativeEvent, trigger));
681
724
  }
682
725
  }
@@ -692,7 +735,7 @@ function useHoverReferenceInteraction(context, props = {}) {
692
735
  }
693
736
  }
694
737
  };
695
- }, [enabled, instance, isClickLikeOpenEvent$1, isOverInactiveTrigger, mouseOnly, store, restMsRef]);
738
+ }, [enabled, instance, isClickLikeOpenEvent$1, isOverInactiveTrigger, mouseOnly, store, restMsRef, checkShouldOpen]);
696
739
  }
697
740
 
698
741
  /* eslint-disable no-nested-ternary */
@@ -810,7 +853,7 @@ function safePolygon(options = {}) {
810
853
  return undefined;
811
854
  }
812
855
  function hasOpenChildNode() {
813
- return Boolean(tree && InternalBackdrop.getNodeChildren(tree.nodesRef.current, nodeId).length > 0);
856
+ return Boolean(tree && useScrollLock.getNodeChildren(tree.nodesRef.current, nodeId).length > 0);
814
857
  }
815
858
  function closeIfNoOpenChild() {
816
859
  if (!hasOpenChildNode()) {
@@ -931,6 +974,7 @@ function safePolygon(options = {}) {
931
974
 
932
975
  // eslint-disable-next-line no-underscore-dangle
933
976
  fn.__options = {
977
+ ...options,
934
978
  blockPointerEvents
935
979
  };
936
980
  return fn;
@@ -967,7 +1011,7 @@ function useContextMenuRootContext(optional = true) {
967
1011
  }
968
1012
 
969
1013
  const stateAttributesMapping$1 = {
970
- ...InternalBackdrop.popupStateMapping,
1014
+ ...useScrollLock.popupStateMapping,
971
1015
  ...useButton.transitionStatusMapping
972
1016
  };
973
1017
 
@@ -979,8 +1023,9 @@ const stateAttributesMapping$1 = {
979
1023
  */
980
1024
  const MenuBackdrop = /*#__PURE__*/React__namespace.forwardRef(function MenuBackdrop(componentProps, forwardedRef) {
981
1025
  const {
982
- className,
983
1026
  render,
1027
+ className,
1028
+ style,
984
1029
  ...elementProps
985
1030
  } = componentProps;
986
1031
  const {
@@ -1031,12 +1076,13 @@ function useMenuItemCommonProps(params) {
1031
1076
  const {
1032
1077
  events: menuEvents
1033
1078
  } = store.useState('floatingTreeRoot');
1079
+ const open = store.useState('open');
1034
1080
  const contextMenuContext = useContextMenuRootContext(true);
1035
1081
  const isContextMenu = contextMenuContext !== undefined;
1036
1082
  return React__namespace.useMemo(() => ({
1037
1083
  id,
1038
1084
  role: 'menuitem',
1039
- tabIndex: highlighted ? 0 : -1,
1085
+ tabIndex: open && highlighted ? 0 : -1,
1040
1086
  onKeyDown(event) {
1041
1087
  if (event.key === ' ' && typingRef?.current) {
1042
1088
  event.preventDefault();
@@ -1072,7 +1118,7 @@ function useMenuItemCommonProps(params) {
1072
1118
 
1073
1119
  // On non-macOS platforms, this mouseup belongs to the right-click gesture
1074
1120
  // that opened the context menu, so it must not activate an item.
1075
- if (isContextMenu && !useButton.isMac && event.button === 2) {
1121
+ if (isContextMenu && !useButton.mac && event.button === 2) {
1076
1122
  return;
1077
1123
  }
1078
1124
  }
@@ -1084,7 +1130,7 @@ function useMenuItemCommonProps(params) {
1084
1130
  }
1085
1131
  }
1086
1132
  }
1087
- }), [closeOnClick, highlighted, id, menuEvents, nodeId, store, typingRef, itemRef, contextMenuContext, isContextMenu, itemMetadata]);
1133
+ }), [closeOnClick, highlighted, id, menuEvents, nodeId, open, store, typingRef, itemRef, contextMenuContext, isContextMenu, itemMetadata]);
1088
1134
  }
1089
1135
 
1090
1136
  const REGULAR_ITEM = {
@@ -1093,7 +1139,7 @@ const REGULAR_ITEM = {
1093
1139
  function useMenuItem(params) {
1094
1140
  const {
1095
1141
  closeOnClick,
1096
- disabled = false,
1142
+ disabled: disabledProp = false,
1097
1143
  highlighted,
1098
1144
  id,
1099
1145
  store,
@@ -1102,6 +1148,8 @@ function useMenuItem(params) {
1102
1148
  itemMetadata,
1103
1149
  nodeId
1104
1150
  } = params;
1151
+ const rootDisabled = store.useState('disabled');
1152
+ const disabled = disabledProp || rootDisabled;
1105
1153
  const itemRef = React__namespace.useRef(null);
1106
1154
  const {
1107
1155
  getButtonProps,
@@ -1178,7 +1226,7 @@ if (process.env.NODE_ENV !== "production") MenuGroupContext.displayName = "MenuG
1178
1226
  function useMenuGroupRootContext() {
1179
1227
  const context = React__namespace.useContext(MenuGroupContext);
1180
1228
  if (context === undefined) {
1181
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.' : useRenderElement.formatErrorMessage(31));
1229
+ 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>.' : useRenderElement.formatErrorMessage(31));
1182
1230
  }
1183
1231
  return context;
1184
1232
  }
@@ -1187,12 +1235,10 @@ const MenuGroup = /*#__PURE__*/React__namespace.forwardRef(function MenuGroup(co
1187
1235
  const {
1188
1236
  render,
1189
1237
  className,
1238
+ style,
1190
1239
  ...elementProps
1191
1240
  } = componentProps;
1192
1241
  const [labelId, setLabelId] = React__namespace.useState(undefined);
1193
- const context = React__namespace.useMemo(() => ({
1194
- setLabelId
1195
- }), [setLabelId]);
1196
1242
  const element = useRenderElement.useRenderElement('div', componentProps, {
1197
1243
  ref: forwardedRef,
1198
1244
  props: {
@@ -1202,7 +1248,7 @@ const MenuGroup = /*#__PURE__*/React__namespace.forwardRef(function MenuGroup(co
1202
1248
  }
1203
1249
  });
1204
1250
  return /*#__PURE__*/jsxRuntime.jsx(MenuGroupContext.Provider, {
1205
- value: context,
1251
+ value: setLabelId,
1206
1252
  children: element
1207
1253
  });
1208
1254
  });
@@ -1214,17 +1260,16 @@ if (process.env.NODE_ENV !== "production") MenuGroup.displayName = "MenuGroup";
1214
1260
  *
1215
1261
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
1216
1262
  */
1217
- const MenuGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function MenuGroupLabelComponent(componentProps, forwardedRef) {
1263
+ const MenuGroupLabel = /*#__PURE__*/React__namespace.forwardRef(function MenuGroupLabel(componentProps, forwardedRef) {
1218
1264
  const {
1219
- className,
1220
1265
  render,
1266
+ className,
1267
+ style,
1221
1268
  id: idProp,
1222
1269
  ...elementProps
1223
1270
  } = componentProps;
1224
1271
  const id = useButton.useBaseUiId(idProp);
1225
- const {
1226
- setLabelId
1227
- } = useMenuGroupRootContext();
1272
+ const setLabelId = useMenuGroupRootContext();
1228
1273
  useButton.useIsoLayoutEffect(() => {
1229
1274
  setLabelId(id);
1230
1275
  return () => {
@@ -1257,6 +1302,7 @@ const MenuItem = /*#__PURE__*/React__namespace.forwardRef(function MenuItem(comp
1257
1302
  nativeButton = false,
1258
1303
  disabled = false,
1259
1304
  closeOnClick = true,
1305
+ style,
1260
1306
  ...elementProps
1261
1307
  } = componentProps;
1262
1308
  const listItem = useCompositeListItem.useCompositeListItem({
@@ -1279,7 +1325,7 @@ const MenuItem = /*#__PURE__*/React__namespace.forwardRef(function MenuItem(comp
1279
1325
  id,
1280
1326
  store,
1281
1327
  nativeButton,
1282
- nodeId: menuPositionerContext?.nodeId,
1328
+ nodeId: menuPositionerContext?.context.nodeId,
1283
1329
  itemMetadata: REGULAR_ITEM
1284
1330
  });
1285
1331
  const state = {
@@ -1307,6 +1353,7 @@ const MenuLinkItem = /*#__PURE__*/React__namespace.forwardRef(function MenuLinkI
1307
1353
  id: idProp,
1308
1354
  label,
1309
1355
  closeOnClick = false,
1356
+ style,
1310
1357
  ...elementProps
1311
1358
  } = componentProps;
1312
1359
  const linkRef = React__namespace.useRef(null);
@@ -1314,7 +1361,7 @@ const MenuLinkItem = /*#__PURE__*/React__namespace.forwardRef(function MenuLinkI
1314
1361
  label
1315
1362
  });
1316
1363
  const menuPositionerContext = useMenuPositionerContext(true);
1317
- const nodeId = menuPositionerContext?.nodeId;
1364
+ const nodeId = menuPositionerContext?.context.nodeId;
1318
1365
  const id = useButton.useBaseUiId(idProp);
1319
1366
  const {
1320
1367
  store
@@ -1341,9 +1388,9 @@ const MenuLinkItem = /*#__PURE__*/React__namespace.forwardRef(function MenuLinkI
1341
1388
  function getItemProps(externalProps) {
1342
1389
  return useRenderElement.mergeProps(commonProps, externalProps, getButtonProps);
1343
1390
  }
1344
- const state = React__namespace.useMemo(() => ({
1391
+ const state = {
1345
1392
  highlighted
1346
- }), [highlighted]);
1393
+ };
1347
1394
  return useRenderElement.useRenderElement('a', componentProps, {
1348
1395
  state,
1349
1396
  props: [itemProps, elementProps, getItemProps],
@@ -1353,7 +1400,7 @@ const MenuLinkItem = /*#__PURE__*/React__namespace.forwardRef(function MenuLinkI
1353
1400
  if (process.env.NODE_ENV !== "production") MenuLinkItem.displayName = "MenuLinkItem";
1354
1401
 
1355
1402
  const stateAttributesMapping = {
1356
- ...InternalBackdrop.popupStateMapping,
1403
+ ...useScrollLock.popupStateMapping,
1357
1404
  ...useButton.transitionStatusMapping
1358
1405
  };
1359
1406
 
@@ -1367,6 +1414,7 @@ const MenuPopup = /*#__PURE__*/React__namespace.forwardRef(function MenuPopup(co
1367
1414
  const {
1368
1415
  render,
1369
1416
  className,
1417
+ style,
1370
1418
  finalFocus,
1371
1419
  ...elementProps
1372
1420
  } = componentProps;
@@ -1377,7 +1425,7 @@ const MenuPopup = /*#__PURE__*/React__namespace.forwardRef(function MenuPopup(co
1377
1425
  side,
1378
1426
  align
1379
1427
  } = useMenuPositionerContext();
1380
- const insideToolbar = ToolbarRootContext.useToolbarRootContext(true) != null;
1428
+ const insideToolbar = isElementDisabled.useToolbarRootContext(true) != null;
1381
1429
  const open = store.useState('open');
1382
1430
  const transitionStatus = store.useState('transitionStatus');
1383
1431
  const popupProps = store.useState('popupProps');
@@ -1391,6 +1439,9 @@ const MenuPopup = /*#__PURE__*/React__namespace.forwardRef(function MenuPopup(co
1391
1439
  const floatingTreeRoot = store.useState('floatingTreeRoot');
1392
1440
  const closeDelay = store.useState('closeDelay');
1393
1441
  const activeTriggerElement = store.useState('activeTriggerElement');
1442
+ const hoverEnabled = store.useState('hoverEnabled');
1443
+ const disabled = store.useState('disabled');
1444
+ const openMethod = store.useState('openMethod');
1394
1445
  const isContextMenu = parent.type === 'context-menu';
1395
1446
  useButton.useOpenChangeComplete({
1396
1447
  open,
@@ -1410,12 +1461,13 @@ const MenuPopup = /*#__PURE__*/React__namespace.forwardRef(function MenuPopup(co
1410
1461
  floatingTreeRoot.events.off('close', handleClose);
1411
1462
  };
1412
1463
  }, [floatingTreeRoot.events, store]);
1413
- const hoverEnabled = store.useState('hoverEnabled');
1414
- const disabled = store.useState('disabled');
1415
1464
  useHoverFloatingInteraction(floatingContext, {
1416
1465
  enabled: hoverEnabled && !disabled && !isContextMenu && parent.type !== 'menubar',
1417
1466
  closeDelay
1418
1467
  });
1468
+ const setPopupElement = React__namespace.useCallback(element => {
1469
+ store.set('popupElement', element);
1470
+ }, [store]);
1419
1471
  const state = {
1420
1472
  transitionStatus,
1421
1473
  side,
@@ -1424,16 +1476,13 @@ const MenuPopup = /*#__PURE__*/React__namespace.forwardRef(function MenuPopup(co
1424
1476
  nested: parent.type === 'menu',
1425
1477
  instant: instantType
1426
1478
  };
1427
- const setPopupElement = React__namespace.useCallback(element => {
1428
- store.set('popupElement', element);
1429
- }, [store]);
1430
1479
  const element = useRenderElement.useRenderElement('div', componentProps, {
1431
1480
  state,
1432
1481
  ref: [forwardedRef, store.context.popupRef, setPopupElement],
1433
1482
  stateAttributesMapping,
1434
1483
  props: [popupProps, {
1435
1484
  onKeyDown(event) {
1436
- if (insideToolbar && CSPContext.COMPOSITE_KEYS.has(event.key)) {
1485
+ if (insideToolbar && scrollEdges.COMPOSITE_KEYS.has(event.key)) {
1437
1486
  event.stopPropagation();
1438
1487
  }
1439
1488
  }
@@ -1445,8 +1494,9 @@ const MenuPopup = /*#__PURE__*/React__namespace.forwardRef(function MenuPopup(co
1445
1494
  if (triggerElement || parent.type === 'menubar' && lastOpenChangeReason !== useButton.outsidePress) {
1446
1495
  returnFocus = true;
1447
1496
  }
1448
- return /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingFocusManager, {
1497
+ return /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingFocusManager, {
1449
1498
  context: floatingContext,
1499
+ openInteractionType: openMethod,
1450
1500
  modal: isContextMenu,
1451
1501
  disabled: !mounted,
1452
1502
  returnFocus: finalFocus === undefined ? returnFocus : finalFocus,
@@ -1486,7 +1536,7 @@ const MenuPortal = /*#__PURE__*/React__namespace.forwardRef(function MenuPortal(
1486
1536
  }
1487
1537
  return /*#__PURE__*/jsxRuntime.jsx(MenuPortalContext.Provider, {
1488
1538
  value: keepMounted,
1489
- children: /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingPortal, {
1539
+ children: /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingPortal, {
1490
1540
  ref: forwardedRef,
1491
1541
  ...portalProps
1492
1542
  })
@@ -1509,7 +1559,8 @@ const MenuPositioner = /*#__PURE__*/React__namespace.forwardRef(function MenuPos
1509
1559
  arrowPadding = 5,
1510
1560
  sticky = false,
1511
1561
  disableAnchorTracking = false,
1512
- collisionAvoidance: collisionAvoidanceProp = InternalBackdrop.DROPDOWN_COLLISION_AVOIDANCE,
1562
+ collisionAvoidance: collisionAvoidanceProp = useScrollLock.DROPDOWN_COLLISION_AVOIDANCE,
1563
+ style,
1513
1564
  ...elementProps
1514
1565
  } = componentProps;
1515
1566
  const {
@@ -1523,6 +1574,7 @@ const MenuPositioner = /*#__PURE__*/React__namespace.forwardRef(function MenuPos
1523
1574
  const mounted = store.useState('mounted');
1524
1575
  const open = store.useState('open');
1525
1576
  const modal = store.useState('modal');
1577
+ const openMethod = store.useState('openMethod');
1526
1578
  const triggerElement = store.useState('activeTriggerElement');
1527
1579
  const transitionStatus = store.useState('transitionStatus');
1528
1580
  const positionerElement = store.useState('positionerElement');
@@ -1552,9 +1604,9 @@ const MenuPositioner = /*#__PURE__*/React__namespace.forwardRef(function MenuPos
1552
1604
  if (parent.type === 'menu') {
1553
1605
  computedSide = computedSide ?? 'inline-end';
1554
1606
  computedAlign = computedAlign ?? 'start';
1555
- collisionAvoidance = componentProps.collisionAvoidance ?? InternalBackdrop.POPUP_COLLISION_AVOIDANCE;
1607
+ collisionAvoidance = componentProps.collisionAvoidance ?? useScrollLock.POPUP_COLLISION_AVOIDANCE;
1556
1608
  } else if (parent.type === 'menubar') {
1557
- computedSide = computedSide ?? 'bottom';
1609
+ computedSide = computedSide ?? (parent.context.orientation === 'vertical' ? 'inline-end' : 'bottom');
1558
1610
  computedAlign = computedAlign ?? 'start';
1559
1611
  }
1560
1612
  const contextMenu = parent.type === 'context-menu';
@@ -1579,20 +1631,6 @@ const MenuPositioner = /*#__PURE__*/React__namespace.forwardRef(function MenuPos
1579
1631
  externalTree: floatingTreeRoot,
1580
1632
  adaptiveOrigin: hasViewport ? useCompositeListItem.adaptiveOrigin : undefined
1581
1633
  });
1582
- const positionerProps = React__namespace.useMemo(() => {
1583
- const hiddenStyles = {};
1584
- if (!open) {
1585
- hiddenStyles.pointerEvents = 'none';
1586
- }
1587
- return {
1588
- role: 'presentation',
1589
- hidden: !mounted,
1590
- style: {
1591
- ...positioner.positionerStyles,
1592
- ...hiddenStyles
1593
- }
1594
- };
1595
- }, [open, mounted, positioner.positionerStyles]);
1596
1634
  React__namespace.useEffect(() => {
1597
1635
  function onMenuOpenChange(details) {
1598
1636
  if (details.open) {
@@ -1700,19 +1738,16 @@ const MenuPositioner = /*#__PURE__*/React__namespace.forwardRef(function MenuPos
1700
1738
  nested: parent.type === 'menu',
1701
1739
  instant: instantType
1702
1740
  };
1703
- const contextValue = React__namespace.useMemo(() => ({
1704
- side: positioner.side,
1705
- align: positioner.align,
1706
- arrowRef: positioner.arrowRef,
1707
- arrowUncentered: positioner.arrowUncentered,
1708
- arrowStyles: positioner.arrowStyles,
1709
- nodeId: positioner.context.nodeId
1710
- }), [positioner.side, positioner.align, positioner.arrowRef, positioner.arrowUncentered, positioner.arrowStyles, positioner.context.nodeId]);
1711
- const element = useRenderElement.useRenderElement('div', componentProps, {
1712
- state,
1713
- stateAttributesMapping: InternalBackdrop.popupStateMapping,
1714
- ref: [forwardedRef, store.useStateSetter('positionerElement')],
1715
- props: [positionerProps, useCompositeListItem.getDisabledMountTransitionStyles(transitionStatus), elementProps]
1741
+ const menubarModal = parent.type === 'menubar' && parent.context.modal;
1742
+ const popupModal = modal && lastOpenChangeReason !== useButton.triggerHover;
1743
+ useCompositeListItem.useAnchoredPopupScrollLock(open && (menubarModal || popupModal), openMethod === 'touch', positionerElement, triggerElement);
1744
+ const element = useCompositeListItem.usePositioner(componentProps, state, {
1745
+ styles: positioner.positionerStyles,
1746
+ transitionStatus,
1747
+ props: elementProps,
1748
+ refs: [forwardedRef, store.useStateSetter('positionerElement')],
1749
+ hidden: !mounted,
1750
+ inert: !open
1716
1751
  });
1717
1752
  const shouldRenderBackdrop = mounted && parent.type !== 'menu' && (parent.type !== 'menubar' && modal && lastOpenChangeReason !== useButton.triggerHover || parent.type === 'menubar' && parent.context.modal);
1718
1753
 
@@ -1724,12 +1759,12 @@ const MenuPositioner = /*#__PURE__*/React__namespace.forwardRef(function MenuPos
1724
1759
  backdropCutout = triggerElement;
1725
1760
  }
1726
1761
  return /*#__PURE__*/jsxRuntime.jsxs(MenuPositionerContext.Provider, {
1727
- value: contextValue,
1728
- children: [shouldRenderBackdrop && /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.InternalBackdrop, {
1762
+ value: positioner,
1763
+ children: [shouldRenderBackdrop && /*#__PURE__*/jsxRuntime.jsx(useScrollLock.InternalBackdrop, {
1729
1764
  ref: parent.type === 'context-menu' || parent.type === 'nested-context-menu' ? parent.context.internalBackdropRef : null,
1730
- inert: InternalBackdrop.inertValue(!open),
1765
+ inert: useScrollLock.inertValue(!open),
1731
1766
  cutout: backdropCutout
1732
- }), /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingNode, {
1767
+ }), /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingNode, {
1733
1768
  id: floatingNodeId,
1734
1769
  children: /*#__PURE__*/jsxRuntime.jsx(useCompositeListItem.CompositeList, {
1735
1770
  elementsRef: store.context.itemDomElements,
@@ -1765,16 +1800,18 @@ const MenuRadioGroup = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__na
1765
1800
  defaultValue,
1766
1801
  onValueChange: onValueChangeProp,
1767
1802
  disabled = false,
1803
+ style,
1804
+ 'aria-labelledby': ariaLabelledByProp,
1768
1805
  ...elementProps
1769
1806
  } = componentProps;
1807
+ const [labelId, setLabelId] = React__namespace.useState(undefined);
1770
1808
  const [value, setValueUnwrapped] = useButton.useControlled({
1771
1809
  controlled: valueProp,
1772
1810
  default: defaultValue,
1773
1811
  name: 'MenuRadioGroup'
1774
1812
  });
1775
- const onValueChange = useButton.useStableCallback(onValueChangeProp);
1776
1813
  const setValue = useButton.useStableCallback((newValue, eventDetails) => {
1777
- onValueChange?.(newValue, eventDetails);
1814
+ onValueChangeProp?.(newValue, eventDetails);
1778
1815
  if (eventDetails.isCanceled) {
1779
1816
  return;
1780
1817
  }
@@ -1788,6 +1825,7 @@ const MenuRadioGroup = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__na
1788
1825
  ref: forwardedRef,
1789
1826
  props: {
1790
1827
  role: 'group',
1828
+ 'aria-labelledby': ariaLabelledByProp ?? labelId,
1791
1829
  'aria-disabled': disabled || undefined,
1792
1830
  ...elementProps
1793
1831
  }
@@ -1797,9 +1835,12 @@ const MenuRadioGroup = /*#__PURE__*/React__namespace.memo(/*#__PURE__*/React__na
1797
1835
  setValue,
1798
1836
  disabled
1799
1837
  }), [value, setValue, disabled]);
1800
- return /*#__PURE__*/jsxRuntime.jsx(MenuRadioGroupContext.Provider, {
1801
- value: context,
1802
- children: element
1838
+ return /*#__PURE__*/jsxRuntime.jsx(MenuGroupContext.Provider, {
1839
+ value: setLabelId,
1840
+ children: /*#__PURE__*/jsxRuntime.jsx(MenuRadioGroupContext.Provider, {
1841
+ value: context,
1842
+ children: element
1843
+ })
1803
1844
  });
1804
1845
  }));
1805
1846
  if (process.env.NODE_ENV !== "production") MenuRadioGroup.displayName = "MenuRadioGroup";
@@ -1824,6 +1865,7 @@ const MenuRadioItem = /*#__PURE__*/React__namespace.forwardRef(function MenuRadi
1824
1865
  disabled: disabledProp = false,
1825
1866
  closeOnClick = false,
1826
1867
  value,
1868
+ style,
1827
1869
  ...elementProps
1828
1870
  } = componentProps;
1829
1871
  const listItem = useCompositeListItem.useCompositeListItem({
@@ -1853,7 +1895,7 @@ const MenuRadioItem = /*#__PURE__*/React__namespace.forwardRef(function MenuRadi
1853
1895
  id,
1854
1896
  store,
1855
1897
  nativeButton,
1856
- nodeId: menuPositionerContext?.nodeId,
1898
+ nodeId: menuPositionerContext?.context.nodeId,
1857
1899
  itemMetadata: REGULAR_ITEM
1858
1900
  });
1859
1901
  const state = React__namespace.useMemo(() => ({
@@ -1861,13 +1903,12 @@ const MenuRadioItem = /*#__PURE__*/React__namespace.forwardRef(function MenuRadi
1861
1903
  highlighted,
1862
1904
  checked
1863
1905
  }), [disabled, highlighted, checked]);
1864
- const handleClick = useButton.useStableCallback(event => {
1865
- const details = {
1866
- ...useButton.createChangeEventDetails(useButton.itemPress, event.nativeEvent),
1867
- preventUnmountOnClose: () => {}
1868
- };
1906
+ function handleClick(event) {
1907
+ const details = useButton.createChangeEventDetails(useButton.itemPress, event.nativeEvent, undefined, {
1908
+ preventUnmountOnClose() {}
1909
+ });
1869
1910
  setSelectedValue(value, details);
1870
- });
1911
+ }
1871
1912
  const element = useRenderElement.useRenderElement('div', componentProps, {
1872
1913
  state,
1873
1914
  stateAttributesMapping: itemMapping,
@@ -1895,6 +1936,7 @@ const MenuRadioItemIndicator = /*#__PURE__*/React__namespace.forwardRef(function
1895
1936
  const {
1896
1937
  render,
1897
1938
  className,
1939
+ style,
1898
1940
  keepMounted = false,
1899
1941
  ...elementProps
1900
1942
  } = componentProps;
@@ -1945,34 +1987,36 @@ function useMenubarContext(optional) {
1945
1987
 
1946
1988
  const selectors = {
1947
1989
  ...ScrollAreaViewport.popupStoreSelectors,
1948
- disabled: InternalBackdrop.createSelector(state => state.parent.type === 'menubar' ? state.parent.context.disabled || state.disabled : state.disabled),
1949
- modal: InternalBackdrop.createSelector(state => (state.parent.type === undefined || state.parent.type === 'context-menu') && (state.modal ?? true)),
1950
- allowMouseEnter: InternalBackdrop.createSelector(state => state.allowMouseEnter),
1951
- stickIfOpen: InternalBackdrop.createSelector(state => state.stickIfOpen),
1952
- parent: InternalBackdrop.createSelector(state => state.parent),
1953
- rootId: InternalBackdrop.createSelector(state => {
1990
+ disabled: useScrollLock.createSelector(state => state.parent.type === 'menubar' ? state.parent.context.disabled || state.disabled : state.disabled),
1991
+ modal: useScrollLock.createSelector(state => (state.parent.type === undefined || state.parent.type === 'context-menu') && (state.modal ?? true)),
1992
+ openMethod: useScrollLock.createSelector(state => state.openMethod),
1993
+ allowMouseEnter: useScrollLock.createSelector(state => state.allowMouseEnter),
1994
+ highlightItemOnHover: useScrollLock.createSelector(state => state.highlightItemOnHover),
1995
+ stickIfOpen: useScrollLock.createSelector(state => state.stickIfOpen),
1996
+ parent: useScrollLock.createSelector(state => state.parent),
1997
+ rootId: useScrollLock.createSelector(state => {
1954
1998
  if (state.parent.type === 'menu') {
1955
1999
  return state.parent.store.select('rootId');
1956
2000
  }
1957
2001
  return state.parent.type !== undefined ? state.parent.context.rootId : state.rootId;
1958
2002
  }),
1959
- activeIndex: InternalBackdrop.createSelector(state => state.activeIndex),
1960
- isActive: InternalBackdrop.createSelector((state, itemIndex) => state.activeIndex === itemIndex),
1961
- hoverEnabled: InternalBackdrop.createSelector(state => state.hoverEnabled),
1962
- instantType: InternalBackdrop.createSelector(state => state.instantType),
1963
- lastOpenChangeReason: InternalBackdrop.createSelector(state => state.openChangeReason),
1964
- floatingTreeRoot: InternalBackdrop.createSelector(state => {
2003
+ activeIndex: useScrollLock.createSelector(state => state.activeIndex),
2004
+ isActive: useScrollLock.createSelector((state, itemIndex) => state.activeIndex === itemIndex),
2005
+ hoverEnabled: useScrollLock.createSelector(state => state.hoverEnabled),
2006
+ instantType: useScrollLock.createSelector(state => state.instantType),
2007
+ lastOpenChangeReason: useScrollLock.createSelector(state => state.openChangeReason),
2008
+ floatingTreeRoot: useScrollLock.createSelector(state => {
1965
2009
  if (state.parent.type === 'menu') {
1966
2010
  return state.parent.store.select('floatingTreeRoot');
1967
2011
  }
1968
2012
  return state.floatingTreeRoot;
1969
2013
  }),
1970
- floatingNodeId: InternalBackdrop.createSelector(state => state.floatingNodeId),
1971
- floatingParentNodeId: InternalBackdrop.createSelector(state => state.floatingParentNodeId),
1972
- itemProps: InternalBackdrop.createSelector(state => state.itemProps),
1973
- closeDelay: InternalBackdrop.createSelector(state => state.closeDelay),
1974
- hasViewport: InternalBackdrop.createSelector(state => state.hasViewport),
1975
- keyboardEventRelay: InternalBackdrop.createSelector(state => {
2014
+ floatingNodeId: useScrollLock.createSelector(state => state.floatingNodeId),
2015
+ floatingParentNodeId: useScrollLock.createSelector(state => state.floatingParentNodeId),
2016
+ itemProps: useScrollLock.createSelector(state => state.itemProps),
2017
+ closeDelay: useScrollLock.createSelector(state => state.closeDelay),
2018
+ hasViewport: useScrollLock.createSelector(state => state.hasViewport),
2019
+ keyboardEventRelay: useScrollLock.createSelector(state => {
1976
2020
  if (state.keyboardEventRelay) {
1977
2021
  return state.keyboardEventRelay;
1978
2022
  }
@@ -1982,7 +2026,7 @@ const selectors = {
1982
2026
  return undefined;
1983
2027
  })
1984
2028
  };
1985
- class MenuStore extends InternalBackdrop.ReactStore {
2029
+ class MenuStore extends useScrollLock.ReactStore {
1986
2030
  constructor(initialState) {
1987
2031
  super({
1988
2032
  ...createInitialState(),
@@ -2005,7 +2049,7 @@ class MenuStore extends InternalBackdrop.ReactStore {
2005
2049
  triggerFocusTargetRef: /*#__PURE__*/React__namespace.createRef(),
2006
2050
  beforeContentFocusGuardRef: /*#__PURE__*/React__namespace.createRef(),
2007
2051
  onOpenChangeComplete: undefined,
2008
- triggerElements: new InternalBackdrop.PopupTriggerMap()
2052
+ triggerElements: new useScrollLock.PopupTriggerMap()
2009
2053
  }, selectors);
2010
2054
 
2011
2055
  // Set up propagation of state from parent menu if applicable.
@@ -2056,7 +2100,9 @@ function createInitialState() {
2056
2100
  ...ScrollAreaViewport.createInitialPopupStoreState(),
2057
2101
  disabled: false,
2058
2102
  modal: true,
2103
+ openMethod: null,
2059
2104
  allowMouseEnter: false,
2105
+ highlightItemOnHover: true,
2060
2106
  stickIfOpen: true,
2061
2107
  parent: {
2062
2108
  type: undefined
@@ -2066,7 +2112,7 @@ function createInitialState() {
2066
2112
  hoverEnabled: true,
2067
2113
  instantType: undefined,
2068
2114
  openChangeReason: null,
2069
- floatingTreeRoot: new InternalBackdrop.FloatingTreeStore(),
2115
+ floatingTreeRoot: new useScrollLock.FloatingTreeStore(),
2070
2116
  floatingNodeId: undefined,
2071
2117
  floatingParentNodeId: null,
2072
2118
  itemProps: useRenderElement.EMPTY_OBJECT,
@@ -2082,7 +2128,7 @@ function useMenuSubmenuRootContext() {
2082
2128
  return React__namespace.useContext(MenuSubmenuRootContext);
2083
2129
  }
2084
2130
 
2085
- const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2131
+ const MenuRoot = useScrollLock.fastComponent(function MenuRoot(props) {
2086
2132
  const {
2087
2133
  children,
2088
2134
  open: openProp,
@@ -2138,46 +2184,19 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2138
2184
  triggerIdProp,
2139
2185
  parent: parentFromContext
2140
2186
  });
2141
-
2142
- // Support initially open state when uncontrolled
2143
- InternalBackdrop.useOnFirstRender(() => {
2144
- if (openProp === undefined && store.state.open === false && defaultOpen === true) {
2145
- store.update({
2146
- open: true,
2147
- activeTriggerId: defaultTriggerIdProp
2148
- });
2149
- }
2150
- });
2187
+ useScrollLock.useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerIdProp);
2151
2188
  store.useControlledProp('openProp', openProp);
2152
2189
  store.useControlledProp('triggerIdProp', triggerIdProp);
2153
2190
  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
2191
+ const rootId = useButton.useId();
2192
+ const floatingId = useButton.useId();
2154
2193
  const floatingTreeRoot = store.useState('floatingTreeRoot');
2155
- const floatingNodeIdFromContext = InternalBackdrop.useFloatingNodeId(floatingTreeRoot);
2156
- const floatingParentNodeIdFromContext = InternalBackdrop.useFloatingParentNodeId();
2157
- useButton.useIsoLayoutEffect(() => {
2158
- if (contextMenuContext && !parentMenuRootContext) {
2159
- // This is a context menu root.
2160
- // It doesn't support detached triggers yet, so we have to sync the parent context manually.
2161
- store.update({
2162
- parent: {
2163
- type: 'context-menu',
2164
- context: contextMenuContext
2165
- },
2166
- floatingNodeId: floatingNodeIdFromContext,
2167
- floatingParentNodeId: floatingParentNodeIdFromContext
2168
- });
2169
- } else if (parentMenuRootContext) {
2170
- store.update({
2171
- floatingNodeId: floatingNodeIdFromContext,
2172
- floatingParentNodeId: floatingParentNodeIdFromContext
2173
- });
2174
- }
2175
- }, [contextMenuContext, parentMenuRootContext, floatingNodeIdFromContext, floatingParentNodeIdFromContext, store]);
2194
+ const floatingNodeIdFromContext = useScrollLock.useFloatingNodeId(floatingTreeRoot);
2195
+ const floatingParentNodeIdFromContext = useScrollLock.useFloatingParentNodeId();
2176
2196
  const open = store.useState('open');
2177
2197
  const activeTriggerElement = store.useState('activeTriggerElement');
2178
2198
  const positionerElement = store.useState('positionerElement');
2179
2199
  const hoverEnabled = store.useState('hoverEnabled');
2180
- const modal = store.useState('modal');
2181
2200
  const disabled = store.useState('disabled');
2182
2201
  const lastOpenChangeReason = store.useState('lastOpenChangeReason');
2183
2202
  const parent = store.useState('parent');
@@ -2185,33 +2204,55 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2185
2204
  const payload = store.useState('payload');
2186
2205
  const floatingParentNodeId = store.useState('floatingParentNodeId');
2187
2206
  const openEventRef = React__namespace.useRef(null);
2207
+ const allowOutsidePressDismissalRef = React__namespace.useRef(parent.type !== 'context-menu');
2208
+ const allowOutsidePressDismissalTimeout = useButton.useTimeout();
2209
+ const allowTouchToCloseRef = React__namespace.useRef(true);
2210
+ const allowTouchToCloseTimeout = useButton.useTimeout();
2188
2211
  const nested = floatingParentNodeId != null;
2189
- let floatingEvents;
2190
2212
  if (process.env.NODE_ENV !== 'production') {
2191
2213
  if (parent.type !== undefined && modalProp !== undefined) {
2192
2214
  console.warn('Base UI: The `modal` prop is not supported on nested menus. It will be ignored.');
2193
2215
  }
2194
2216
  }
2217
+ const {
2218
+ openMethod,
2219
+ triggerProps: interactionTypeProps
2220
+ } = useScrollLock.useOpenInteractionType(open);
2195
2221
  store.useSyncedValues({
2196
2222
  disabled: disabledProp,
2223
+ highlightItemOnHover,
2197
2224
  modal: parent.type === undefined ? modalProp : undefined,
2198
- rootId: useButton.useId()
2199
- });
2200
- const {
2201
2225
  openMethod,
2202
- triggerProps: interactionTypeProps
2203
- } = InternalBackdrop.useOpenInteractionType(open);
2204
- ScrollAreaViewport.useImplicitActiveTrigger(store);
2226
+ rootId
2227
+ });
2228
+ useScrollLock.useImplicitActiveTrigger(store);
2205
2229
  const {
2206
2230
  forceUnmount
2207
- } = ScrollAreaViewport.useOpenStateTransitions(open, store, () => {
2231
+ } = useScrollLock.useOpenStateTransitions(open, store, () => {
2208
2232
  store.update({
2209
2233
  allowMouseEnter: false,
2210
2234
  stickIfOpen: true
2211
2235
  });
2212
2236
  });
2213
- const allowOutsidePressDismissalRef = React__namespace.useRef(parent.type !== 'context-menu');
2214
- const allowOutsidePressDismissalTimeout = useButton.useTimeout();
2237
+ useButton.useIsoLayoutEffect(() => {
2238
+ if (contextMenuContext && !parentMenuRootContext) {
2239
+ // This is a context menu root.
2240
+ // It doesn't support detached triggers yet, so we have to sync the parent context manually.
2241
+ store.update({
2242
+ parent: {
2243
+ type: 'context-menu',
2244
+ context: contextMenuContext
2245
+ },
2246
+ floatingNodeId: floatingNodeIdFromContext,
2247
+ floatingParentNodeId: floatingParentNodeIdFromContext
2248
+ });
2249
+ } else if (parentMenuRootContext) {
2250
+ store.update({
2251
+ floatingNodeId: floatingNodeIdFromContext,
2252
+ floatingParentNodeId: floatingParentNodeIdFromContext
2253
+ });
2254
+ }
2255
+ }, [contextMenuContext, parentMenuRootContext, floatingNodeIdFromContext, floatingParentNodeIdFromContext, store]);
2215
2256
  React__namespace.useEffect(() => {
2216
2257
  if (!open) {
2217
2258
  openEventRef.current = null;
@@ -2232,22 +2273,17 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2232
2273
  allowOutsidePressDismissalRef.current = true;
2233
2274
  });
2234
2275
  }, [allowOutsidePressDismissalTimeout, open, parent.type]);
2235
- InternalBackdrop.useScrollLock(open && modal && lastOpenChangeReason !== useButton.triggerHover && openMethod !== 'touch', positionerElement);
2236
2276
  useButton.useIsoLayoutEffect(() => {
2237
2277
  if (!open && !hoverEnabled) {
2238
2278
  store.set('hoverEnabled', true);
2239
2279
  }
2240
2280
  }, [open, hoverEnabled, store]);
2241
- const allowTouchToCloseRef = React__namespace.useRef(true);
2242
- const allowTouchToCloseTimeout = useButton.useTimeout();
2243
2281
  const setOpen = useButton.useStableCallback((nextOpen, eventDetails) => {
2244
2282
  const reason = eventDetails.reason;
2245
2283
  if (open === nextOpen && eventDetails.trigger === activeTriggerElement && lastOpenChangeReason === reason) {
2246
2284
  return;
2247
2285
  }
2248
- eventDetails.preventUnmountOnClose = () => {
2249
- store.set('preventUnmountingOnClose', true);
2250
- };
2286
+ const shouldPreventUnmountOnClose = useScrollLock.attachPreventUnmountOnClose(eventDetails);
2251
2287
 
2252
2288
  // Do not immediately reset the activeTriggerId to allow
2253
2289
  // exit animations to play and focus to be returned correctly.
@@ -2258,31 +2294,12 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2258
2294
  if (eventDetails.isCanceled) {
2259
2295
  return;
2260
2296
  }
2261
- const details = {
2262
- open: nextOpen,
2263
- nativeEvent: eventDetails.event,
2264
- reason: eventDetails.reason,
2265
- nested
2266
- };
2267
- floatingEvents?.emit('openchange', details);
2297
+ store.state.floatingRootContext.dispatchOpenChange(nextOpen, eventDetails);
2268
2298
  const nativeEvent = eventDetails.event;
2269
2299
  if (nextOpen === false && nativeEvent?.type === 'click' && nativeEvent.pointerType === 'touch' && !allowTouchToCloseRef.current) {
2270
2300
  return;
2271
2301
  }
2272
2302
 
2273
- // Workaround `enableFocusInside` in Floating UI setting `tabindex=0` of a non-highlighted
2274
- // option upon close when tabbing out due to `keepMounted=true`:
2275
- // https://github.com/floating-ui/floating-ui/pull/3004/files#diff-962a7439cdeb09ea98d4b622a45d517bce07ad8c3f866e089bda05f4b0bbd875R194-R199
2276
- // This otherwise causes options to retain `tabindex=0` incorrectly when the popup is closed
2277
- // when tabbing outside.
2278
- if (!nextOpen && activeIndex !== null) {
2279
- const activeOption = store.context.itemDomElements.current[activeIndex];
2280
- // Wait for Floating UI's focus effect to have fired
2281
- queueMicrotask(() => {
2282
- activeOption?.setAttribute('tabindex', '-1');
2283
- });
2284
- }
2285
-
2286
2303
  // Prevent the menu from closing on mobile devices that have a delayed click event.
2287
2304
  // In some cases the menu, when tapped, will fire the focus event first and then the click event.
2288
2305
  // Without this guard, the menu will close immediately after opening.
@@ -2302,14 +2319,7 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2302
2319
  openChangeReason: reason
2303
2320
  };
2304
2321
  openEventRef.current = eventDetails.event ?? null;
2305
-
2306
- // If a popup is closing, the `trigger` may be null.
2307
- // We want to keep the previous value so that exit animations are played and focus is returned correctly.
2308
- const newTriggerId = eventDetails.trigger?.id ?? null;
2309
- if (newTriggerId || nextOpen) {
2310
- updatedState.activeTriggerId = newTriggerId;
2311
- updatedState.activeTriggerElement = eventDetails.trigger ?? null;
2312
- }
2322
+ useScrollLock.setPopupOpenState(updatedState, nextOpen, eventDetails.trigger, shouldPreventUnmountOnClose());
2313
2323
  store.update(updatedState);
2314
2324
  if (parent.type === 'menubar' && (reason === useButton.triggerFocus || reason === useButton.focusOut || reason === useButton.triggerHover || reason === useButton.listNavigation || reason === useButton.siblingOpen)) {
2315
2325
  store.set('instantType', 'group');
@@ -2319,16 +2329,26 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2319
2329
  store.set('instantType', undefined);
2320
2330
  }
2321
2331
  });
2322
- const createMenuEventDetails = React__namespace.useCallback(reason => {
2323
- const details = useButton.createChangeEventDetails(reason);
2324
- details.preventUnmountOnClose = () => {
2325
- store.set('preventUnmountingOnClose', true);
2332
+ const floatingRootContext = useScrollLock.useSyncedFloatingRootContext({
2333
+ popupStore: store,
2334
+ floatingId,
2335
+ nested: floatingParentNodeIdFromContext != null,
2336
+ onOpenChange: setOpen
2337
+ });
2338
+ const floatingEvents = floatingRootContext.context.events;
2339
+ React__namespace.useEffect(() => {
2340
+ const handleSetOpenEvent = ({
2341
+ open: nextOpen,
2342
+ eventDetails
2343
+ }) => setOpen(nextOpen, eventDetails);
2344
+ floatingEvents.on('setOpen', handleSetOpenEvent);
2345
+ return () => {
2346
+ floatingEvents?.off('setOpen', handleSetOpenEvent);
2326
2347
  };
2327
- return details;
2328
- }, [store]);
2348
+ }, [floatingEvents, setOpen]);
2329
2349
  const handleImperativeClose = React__namespace.useCallback(() => {
2330
- store.setOpen(false, createMenuEventDetails(useButton.imperativeAction));
2331
- }, [store, createMenuEventDetails]);
2350
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.imperativeAction));
2351
+ }, [store]);
2332
2352
  React__namespace.useImperativeHandle(actionsRef, () => ({
2333
2353
  unmount: forceUnmount,
2334
2354
  close: handleImperativeClose
@@ -2341,22 +2361,7 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2341
2361
  React__namespace.useImperativeHandle(ctx?.actionsRef, () => ({
2342
2362
  setOpen
2343
2363
  }), [setOpen]);
2344
- const floatingRootContext = ScrollAreaViewport.useSyncedFloatingRootContext({
2345
- popupStore: store,
2346
- onOpenChange: setOpen
2347
- });
2348
- floatingEvents = floatingRootContext.context.events;
2349
- React__namespace.useEffect(() => {
2350
- const handleSetOpenEvent = ({
2351
- open: nextOpen,
2352
- eventDetails
2353
- }) => setOpen(nextOpen, eventDetails);
2354
- floatingEvents.on('setOpen', handleSetOpenEvent);
2355
- return () => {
2356
- floatingEvents?.off('setOpen', handleSetOpenEvent);
2357
- };
2358
- }, [floatingEvents, setOpen]);
2359
- const dismiss = InternalBackdrop.useDismiss(floatingRootContext, {
2364
+ const dismiss = useScrollLock.useDismiss(floatingRootContext, {
2360
2365
  enabled: !disabled,
2361
2366
  bubbles: {
2362
2367
  escapeKey: closeParentOnEsc && parent.type === 'menu'
@@ -2369,10 +2374,7 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2369
2374
  },
2370
2375
  externalTree: nested ? floatingTreeRoot : undefined
2371
2376
  });
2372
- const role = ScrollAreaViewport.useRole(floatingRootContext, {
2373
- role: 'menu'
2374
- });
2375
- const direction = InternalBackdrop.useDirection();
2377
+ const direction = useScrollLock.useDirection();
2376
2378
  const setActiveIndex = React__namespace.useCallback(index => {
2377
2379
  if (store.select('activeIndex') === index) {
2378
2380
  return;
@@ -2394,47 +2396,42 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2394
2396
  externalTree: nested ? floatingTreeRoot : undefined,
2395
2397
  focusItemOnHover: highlightItemOnHover
2396
2398
  });
2397
- const onTypingChange = React__namespace.useCallback(nextTyping => {
2399
+ const onTyping = React__namespace.useCallback(nextTyping => {
2398
2400
  store.context.typingRef.current = nextTyping;
2399
2401
  }, [store]);
2400
2402
  const typeahead = useCompositeListItem.useTypeahead(floatingRootContext, {
2403
+ enabled: !disabled,
2401
2404
  listRef: store.context.itemLabels,
2402
2405
  elementsRef: store.context.itemDomElements,
2403
2406
  activeIndex,
2404
- resetMs: InternalBackdrop.TYPEAHEAD_RESET_MS,
2407
+ resetMs: useScrollLock.TYPEAHEAD_RESET_MS,
2405
2408
  onMatch: index => {
2406
2409
  if (open && index !== activeIndex) {
2407
2410
  store.set('activeIndex', index);
2408
2411
  }
2409
2412
  },
2410
- onTypingChange
2413
+ onTyping
2411
2414
  });
2412
- const {
2413
- getReferenceProps,
2414
- getFloatingProps,
2415
- getItemProps,
2416
- getTriggerProps
2417
- } = InternalBackdrop.useInteractions([dismiss, role, listNavigation, typeahead]);
2418
2415
  const activeTriggerProps = React__namespace.useMemo(() => {
2419
- const mergedProps = useRenderElement.mergeProps(getReferenceProps(), {
2416
+ const mergedProps = useRenderElement.mergeProps(typeahead.reference, listNavigation.reference, dismiss.reference, {
2420
2417
  onMouseMove() {
2421
2418
  store.set('allowMouseEnter', true);
2422
2419
  }
2423
2420
  }, interactionTypeProps);
2424
- delete mergedProps.role;
2421
+ mergedProps['aria-haspopup'] = 'menu';
2422
+ mergedProps['aria-expanded'] = open;
2425
2423
  return mergedProps;
2426
- }, [getReferenceProps, store, interactionTypeProps]);
2424
+ }, [store, typeahead.reference, listNavigation.reference, dismiss.reference, interactionTypeProps, open]);
2427
2425
  const inactiveTriggerProps = React__namespace.useMemo(() => {
2428
- const triggerProps = getTriggerProps();
2429
- if (!triggerProps) {
2430
- return triggerProps;
2431
- }
2432
- const mergedProps = useRenderElement.mergeProps(triggerProps, interactionTypeProps);
2433
- delete mergedProps.role;
2434
- delete mergedProps['aria-controls'];
2426
+ const mergedProps = useRenderElement.mergeProps(listNavigation.trigger, dismiss.trigger, interactionTypeProps);
2427
+ mergedProps['aria-haspopup'] = 'menu';
2428
+ mergedProps['aria-expanded'] = false;
2435
2429
  return mergedProps;
2436
- }, [getTriggerProps, interactionTypeProps]);
2437
- const popupProps = React__namespace.useMemo(() => getFloatingProps({
2430
+ }, [listNavigation.trigger, dismiss.trigger, interactionTypeProps]);
2431
+ const popupProps = React__namespace.useMemo(() => useRenderElement.mergeProps(useScrollLock.FOCUSABLE_POPUP_PROPS, {
2432
+ id: floatingId,
2433
+ role: 'menu',
2434
+ 'aria-labelledby': activeTriggerElement?.id,
2438
2435
  onMouseMove() {
2439
2436
  store.set('allowMouseEnter', true);
2440
2437
  if (parent.type === 'menu') {
@@ -2455,9 +2452,9 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2455
2452
  relay(event);
2456
2453
  }
2457
2454
  }
2458
- }), [getFloatingProps, parent.type, store]);
2459
- const itemProps = React__namespace.useMemo(() => getItemProps(), [getItemProps]);
2460
- store.useSyncedValues({
2455
+ }, typeahead.floating, listNavigation.floating, dismiss.floating), [activeTriggerElement, floatingId, parent.type, store, typeahead.floating, listNavigation.floating, dismiss.floating]);
2456
+ const itemProps = listNavigation.item ?? useRenderElement.EMPTY_OBJECT;
2457
+ useScrollLock.usePopupInteractionProps(store, {
2461
2458
  floatingRootContext,
2462
2459
  activeTriggerProps,
2463
2460
  inactiveTriggerProps,
@@ -2476,7 +2473,7 @@ const MenuRoot = InternalBackdrop.fastComponent(function MenuRoot(props) {
2476
2473
  });
2477
2474
  if (parent.type === undefined || parent.type === 'context-menu') {
2478
2475
  // set up a FloatingTree to provide the context to nested menus
2479
- return /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FloatingTree, {
2476
+ return /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FloatingTree, {
2480
2477
  externalTree: floatingTreeRoot,
2481
2478
  children: content
2482
2479
  });
@@ -2487,7 +2484,7 @@ if (process.env.NODE_ENV !== "production") MenuRoot.displayName = "MenuRoot";
2487
2484
 
2488
2485
  /**
2489
2486
  * Groups all parts of a submenu.
2490
- * Doesnt render its own HTML element.
2487
+ * Doesn't render its own HTML element.
2491
2488
  *
2492
2489
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2493
2490
  */
@@ -2517,7 +2514,7 @@ function useCompositeItem(params = {}) {
2517
2514
  const isHighlighted = highlightedIndex === index;
2518
2515
  const itemRef = React__namespace.useRef(null);
2519
2516
  const mergedRef = useRenderElement.useMergedRefs(ref, itemRef);
2520
- const compositeProps = React__namespace.useMemo(() => ({
2517
+ const compositeProps = {
2521
2518
  tabIndex: isHighlighted ? 0 : -1,
2522
2519
  onFocus() {
2523
2520
  onHighlightedIndexChange(index);
@@ -2532,7 +2529,7 @@ function useCompositeItem(params = {}) {
2532
2529
  item.focus();
2533
2530
  }
2534
2531
  }
2535
- }), [isHighlighted, onHighlightedIndexChange, index, highlightItemOnHover]);
2532
+ };
2536
2533
  return {
2537
2534
  compositeProps,
2538
2535
  compositeRef: mergedRef,
@@ -2547,6 +2544,7 @@ function CompositeItem(componentProps) {
2547
2544
  const {
2548
2545
  render,
2549
2546
  className,
2547
+ style,
2550
2548
  state = useRenderElement.EMPTY_OBJECT,
2551
2549
  props = useRenderElement.EMPTY_ARRAY,
2552
2550
  refs = useRenderElement.EMPTY_ARRAY,
@@ -2579,6 +2577,53 @@ function findRootOwnerId(node) {
2579
2577
  return findRootOwnerId(floatingUi_utils_dom.getParentNode(node));
2580
2578
  }
2581
2579
 
2580
+ /**
2581
+ * Minimal store interface required by the focus guard hook.
2582
+ * Both PopoverStore and MenuStore satisfy this interface.
2583
+ */
2584
+
2585
+ /**
2586
+ * Provides focus guard handlers for popup triggers (Popover, Menu).
2587
+ *
2588
+ * When the popup is open, invisible focus guard elements are placed before and after
2589
+ * the trigger. These handlers close the popup and move focus to the appropriate
2590
+ * tabbable element when the guards receive focus (i.e. when the user tabs out).
2591
+ */
2592
+ function useTriggerFocusGuards(store, triggerElementRef) {
2593
+ const preFocusGuardRef = React__namespace.useRef(null);
2594
+ function handlePreFocusGuardFocus(event) {
2595
+ ReactDOM__namespace.flushSync(() => {
2596
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.focusOut, event.nativeEvent, event.currentTarget));
2597
+ });
2598
+ const previousTabbable = useScrollLock.getTabbableBeforeElement(preFocusGuardRef.current);
2599
+ previousTabbable?.focus();
2600
+ }
2601
+ function handleFocusTargetFocus(event) {
2602
+ const positionerElement = store.select('positionerElement');
2603
+ if (positionerElement && useScrollLock.isOutsideEvent(event, positionerElement)) {
2604
+ store.context.beforeContentFocusGuardRef.current?.focus();
2605
+ } else {
2606
+ ReactDOM__namespace.flushSync(() => {
2607
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.focusOut, event.nativeEvent, event.currentTarget));
2608
+ });
2609
+ let nextTabbable = useScrollLock.getTabbableAfterElement(store.context.triggerFocusTargetRef.current || triggerElementRef.current);
2610
+ while (nextTabbable !== null && useButton.contains(positionerElement, nextTabbable)) {
2611
+ const prevTabbable = nextTabbable;
2612
+ nextTabbable = useScrollLock.getNextTabbable(nextTabbable);
2613
+ if (nextTabbable === prevTabbable) {
2614
+ break;
2615
+ }
2616
+ }
2617
+ nextTabbable?.focus();
2618
+ }
2619
+ }
2620
+ return {
2621
+ preFocusGuardRef,
2622
+ handlePreFocusGuardFocus,
2623
+ handleFocusTargetFocus
2624
+ };
2625
+ }
2626
+
2582
2627
  /**
2583
2628
  * Returns `click` and `mousedown` handlers that fix the behavior of triggers of popups that are toggled by different events.
2584
2629
  * For example, a button that opens a popup on mousedown and closes it on click.
@@ -2624,10 +2669,11 @@ const BOUNDARY_OFFSET = 2;
2624
2669
  *
2625
2670
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2626
2671
  */
2627
- const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(componentProps, forwardedRef) {
2672
+ const MenuTrigger = useScrollLock.fastComponentRef(function MenuTrigger(componentProps, forwardedRef) {
2628
2673
  const {
2629
2674
  render,
2630
2675
  className,
2676
+ style,
2631
2677
  disabled: disabledProp = false,
2632
2678
  nativeButton = true,
2633
2679
  id: idProp,
@@ -2647,19 +2693,20 @@ const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(compo
2647
2693
  const isTriggerActive = store.useState('isTriggerActive', thisTriggerId);
2648
2694
  const floatingRootContext = store.useState('floatingRootContext');
2649
2695
  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);
2696
+ const popupId = store.useState('triggerPopupId', thisTriggerId);
2650
2697
  const triggerElementRef = React__namespace.useRef(null);
2651
2698
  const parent = useMenuParent();
2652
2699
  const compositeRootContext = useButton.useCompositeRootContext(true);
2653
- const floatingTreeRootFromContext = InternalBackdrop.useFloatingTree();
2700
+ const floatingTreeRootFromContext = useScrollLock.useFloatingTree();
2654
2701
  const floatingTreeRoot = React__namespace.useMemo(() => {
2655
- return floatingTreeRootFromContext ?? new InternalBackdrop.FloatingTreeStore();
2702
+ return floatingTreeRootFromContext ?? new useScrollLock.FloatingTreeStore();
2656
2703
  }, [floatingTreeRootFromContext]);
2657
- const floatingNodeId = InternalBackdrop.useFloatingNodeId(floatingTreeRoot);
2658
- const floatingParentNodeId = InternalBackdrop.useFloatingParentNodeId();
2704
+ const floatingNodeId = useScrollLock.useFloatingNodeId(floatingTreeRoot);
2705
+ const floatingParentNodeId = useScrollLock.useFloatingParentNodeId();
2659
2706
  const {
2660
2707
  registerTrigger,
2661
2708
  isMountedByThisTrigger
2662
- } = ScrollAreaViewport.useTriggerDataForwarding(thisTriggerId, triggerElementRef, store, {
2709
+ } = useScrollLock.useTriggerDataForwarding(thisTriggerId, triggerElementRef, store, {
2663
2710
  payload,
2664
2711
  closeDelay,
2665
2712
  parent,
@@ -2730,14 +2777,15 @@ const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(compo
2730
2777
  },
2731
2778
  triggerElementRef,
2732
2779
  externalTree: floatingTreeRoot,
2733
- isActiveTrigger: isTriggerActive
2780
+ isActiveTrigger: isTriggerActive,
2781
+ isClosing: () => store.select('transitionStatus') === 'ending'
2734
2782
  });
2735
2783
 
2736
2784
  // Whether to ignore clicks to open the menu.
2737
2785
  // `lastOpenChangeReason` doesn't need to be reactive here, as we need to run this
2738
2786
  // only when `isOpenedByThisTrigger` changes.
2739
2787
  const stickIfOpen = useStickIfOpen(isOpenedByThisTrigger, store.select('lastOpenChangeReason'));
2740
- const click = InternalBackdrop.useClick(floatingRootContext, {
2788
+ const click = useScrollLock.useClick(floatingRootContext, {
2741
2789
  enabled: !disabled && parent.type !== 'context-menu',
2742
2790
  event: isOpenedByThisTrigger && isInMenubar ? 'click' : 'mousedown',
2743
2791
  toggle: true,
@@ -2752,15 +2800,21 @@ const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(compo
2752
2800
  enabled: isInMenubar,
2753
2801
  mouseDownAction: 'open'
2754
2802
  });
2755
- const localInteractionProps = InternalBackdrop.useInteractions([click, focus]);
2803
+ const localInteractionProps = React__namespace.useMemo(() => useRenderElement.mergeProps(focus.reference, click.reference), [focus.reference, click.reference]);
2804
+ const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);
2805
+ const {
2806
+ preFocusGuardRef,
2807
+ handlePreFocusGuardFocus,
2808
+ handleFocusTargetFocus
2809
+ } = useTriggerFocusGuards(store, triggerElementRef);
2756
2810
  const state = {
2757
2811
  disabled,
2758
2812
  open: isOpenedByThisTrigger
2759
2813
  };
2760
- const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);
2761
2814
  const ref = [triggerRef, forwardedRef, buttonRef, registerTrigger, triggerElementRef];
2762
- const props = [localInteractionProps.getReferenceProps(), hoverProps ?? useRenderElement.EMPTY_OBJECT, rootTriggerProps, {
2815
+ const props = [localInteractionProps, hoverProps ?? useRenderElement.EMPTY_OBJECT, rootTriggerProps, {
2763
2816
  'aria-haspopup': 'menu',
2817
+ 'aria-controls': popupId,
2764
2818
  id: thisTriggerId,
2765
2819
  onMouseDown: event => {
2766
2820
  if (store.select('open')) {
@@ -2779,36 +2833,9 @@ const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(compo
2779
2833
  }, isInMenubar ? {
2780
2834
  role: 'menuitem'
2781
2835
  } : {}, mixedToggleHandlers, elementProps, getButtonProps];
2782
- const preFocusGuardRef = React__namespace.useRef(null);
2783
- const handlePreFocusGuardFocus = useButton.useStableCallback(event => {
2784
- ReactDOM__namespace.flushSync(() => {
2785
- store.setOpen(false, useButton.createChangeEventDetails(useButton.focusOut, event.nativeEvent, event.currentTarget));
2786
- });
2787
- const previousTabbable = InternalBackdrop.getTabbableBeforeElement(preFocusGuardRef.current);
2788
- previousTabbable?.focus();
2789
- });
2790
- const handleFocusTargetFocus = useButton.useStableCallback(event => {
2791
- const currentPositionerElement = store.select('positionerElement');
2792
- if (currentPositionerElement && InternalBackdrop.isOutsideEvent(event, currentPositionerElement)) {
2793
- store.context.beforeContentFocusGuardRef.current?.focus();
2794
- } else {
2795
- ReactDOM__namespace.flushSync(() => {
2796
- store.setOpen(false, useButton.createChangeEventDetails(useButton.focusOut, event.nativeEvent, event.currentTarget));
2797
- });
2798
- let nextTabbable = InternalBackdrop.getTabbableAfterElement(store.context.triggerFocusTargetRef.current || triggerElementRef.current);
2799
- while (nextTabbable !== null && useButton.contains(currentPositionerElement, nextTabbable)) {
2800
- const prevTabbable = nextTabbable;
2801
- nextTabbable = InternalBackdrop.getNextTabbable(nextTabbable);
2802
- if (nextTabbable === prevTabbable) {
2803
- break;
2804
- }
2805
- }
2806
- nextTabbable?.focus();
2807
- }
2808
- });
2809
2836
  const element = useRenderElement.useRenderElement('button', componentProps, {
2810
2837
  enabled: !isInMenubar,
2811
- stateAttributesMapping: InternalBackdrop.pressableTriggerOpenStateMapping,
2838
+ stateAttributesMapping: useScrollLock.pressableTriggerOpenStateMapping,
2812
2839
  state,
2813
2840
  ref,
2814
2841
  props
@@ -2818,10 +2845,11 @@ const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(compo
2818
2845
  tag: "button",
2819
2846
  render: render,
2820
2847
  className: className,
2848
+ style: style,
2821
2849
  state: state,
2822
2850
  refs: ref,
2823
2851
  props: props,
2824
- stateAttributesMapping: InternalBackdrop.pressableTriggerOpenStateMapping
2852
+ stateAttributesMapping: useScrollLock.pressableTriggerOpenStateMapping
2825
2853
  });
2826
2854
  }
2827
2855
 
@@ -2830,12 +2858,12 @@ const MenuTrigger = InternalBackdrop.fastComponentRef(function MenuTrigger(compo
2830
2858
 
2831
2859
  if (isOpenedByThisTrigger) {
2832
2860
  return /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
2833
- children: [/*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FocusGuard, {
2861
+ children: [/*#__PURE__*/jsxRuntime.jsx(useScrollLock.FocusGuard, {
2834
2862
  ref: preFocusGuardRef,
2835
2863
  onFocus: handlePreFocusGuardFocus
2836
2864
  }, `${thisTriggerId}-pre-focus-guard`), /*#__PURE__*/jsxRuntime.jsx(React__namespace.Fragment, {
2837
2865
  children: element
2838
- }, thisTriggerId), /*#__PURE__*/jsxRuntime.jsx(InternalBackdrop.FocusGuard, {
2866
+ }, thisTriggerId), /*#__PURE__*/jsxRuntime.jsx(useScrollLock.FocusGuard, {
2839
2867
  ref: store.context.triggerFocusTargetRef,
2840
2868
  onFocus: handleFocusTargetFocus
2841
2869
  }, `${thisTriggerId}-post-focus-guard`)]
@@ -2857,7 +2885,7 @@ function useStickIfOpen(open, openReason) {
2857
2885
  // Only allow "patient" clicks to close the menu if it's open.
2858
2886
  // If they clicked within 500ms of the menu opening, keep it open.
2859
2887
  setStickIfOpen(true);
2860
- stickIfOpenTimeout.start(InternalBackdrop.PATIENT_CLICK_THRESHOLD, () => {
2888
+ stickIfOpenTimeout.start(useScrollLock.PATIENT_CLICK_THRESHOLD, () => {
2861
2889
  setStickIfOpen(false);
2862
2890
  });
2863
2891
  } else if (!open) {
@@ -2901,10 +2929,11 @@ function useMenuParent() {
2901
2929
  *
2902
2930
  * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
2903
2931
  */
2904
- const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function SubmenuTriggerComponent(componentProps, forwardedRef) {
2932
+ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function MenuSubmenuTrigger(componentProps, forwardedRef) {
2905
2933
  const {
2906
2934
  render,
2907
2935
  className,
2936
+ style,
2908
2937
  label,
2909
2938
  id: idProp,
2910
2939
  nativeButton = false,
@@ -2914,7 +2943,9 @@ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function Sub
2914
2943
  disabled: disabledProp = false,
2915
2944
  ...elementProps
2916
2945
  } = componentProps;
2917
- const listItem = useCompositeListItem.useCompositeListItem();
2946
+ const listItem = useCompositeListItem.useCompositeListItem({
2947
+ label
2948
+ });
2918
2949
  const menuPositionerContext = useMenuPositionerContext();
2919
2950
  const {
2920
2951
  store
@@ -2923,7 +2954,8 @@ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function Sub
2923
2954
  const open = store.useState('open');
2924
2955
  const floatingRootContext = store.useState('floatingRootContext');
2925
2956
  const floatingTreeRoot = store.useState('floatingTreeRoot');
2926
- const baseRegisterTrigger = ScrollAreaViewport.useTriggerRegistration(thisTriggerId, store);
2957
+ const popupId = store.useState('triggerPopupId', thisTriggerId);
2958
+ const baseRegisterTrigger = useScrollLock.useTriggerRegistration(thisTriggerId, store);
2927
2959
  const registerTrigger = React__namespace.useCallback(element => {
2928
2960
  const cleanup = baseRegisterTrigger(element);
2929
2961
  if (element !== null && store.select('open') && store.select('activeTriggerId') == null) {
@@ -2946,16 +2978,29 @@ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function Sub
2946
2978
  }
2947
2979
  store.useSyncedValue('closeDelay', closeDelay);
2948
2980
  const parentMenuStore = submenuRootContext.parentMenu;
2981
+ const rootDisabled = store.useState('disabled');
2982
+ const parentDisabled = parentMenuStore.useState('disabled');
2983
+ const disabled = disabledProp || rootDisabled || parentDisabled;
2984
+ if (process.env.NODE_ENV !== 'production') {
2985
+ // eslint-disable-next-line react-hooks/rules-of-hooks
2986
+ useButton.useIsoLayoutEffect(() => {
2987
+ const element = triggerElementRef.current;
2988
+ if (element && isElementDisabled.isElementDisabled(element) && !disabled) {
2989
+ const ownerStackMessage = useButton.SafeReact.captureOwnerStack?.() || '';
2990
+ useRenderElement.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}`);
2991
+ }
2992
+ });
2993
+ }
2949
2994
  const itemProps = parentMenuStore.useState('itemProps');
2950
2995
  const highlighted = parentMenuStore.useState('isActive', listItem.index);
2951
2996
  const itemMetadata = React__namespace.useMemo(() => ({
2952
2997
  type: 'submenu-trigger',
2953
2998
  setActive() {
2954
- parentMenuStore.set('activeIndex', listItem.index);
2999
+ if (parentMenuStore.select('highlightItemOnHover')) {
3000
+ parentMenuStore.set('activeIndex', listItem.index);
3001
+ }
2955
3002
  }
2956
3003
  }), [parentMenuStore, listItem.index]);
2957
- const rootDisabled = store.useState('disabled');
2958
- const disabled = disabledProp || rootDisabled;
2959
3004
  const {
2960
3005
  getItemProps,
2961
3006
  itemRef
@@ -2968,10 +3013,9 @@ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function Sub
2968
3013
  typingRef: parentMenuStore.context.typingRef,
2969
3014
  nativeButton,
2970
3015
  itemMetadata,
2971
- nodeId: menuPositionerContext?.nodeId
3016
+ nodeId: menuPositionerContext?.context.nodeId
2972
3017
  });
2973
3018
  const hoverEnabled = store.useState('hoverEnabled');
2974
- const allowMouseEnter = parentMenuStore.useState('allowMouseEnter');
2975
3019
  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {
2976
3020
  enabled: hoverEnabled && openOnHover && !disabled,
2977
3021
  handleClose: safePolygon({
@@ -2980,21 +3024,23 @@ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function Sub
2980
3024
  mouseOnly: true,
2981
3025
  move: true,
2982
3026
  restMs: delay,
2983
- delay: allowMouseEnter ? {
3027
+ delay: {
2984
3028
  open: delay,
2985
3029
  close: closeDelay
2986
- } : 0,
3030
+ },
3031
+ shouldOpen: delay > 0 ? () => parentMenuStore.select('allowMouseEnter') : undefined,
2987
3032
  triggerElementRef,
2988
- externalTree: floatingTreeRoot
3033
+ externalTree: floatingTreeRoot,
3034
+ isClosing: () => store.select('transitionStatus') === 'ending'
2989
3035
  });
2990
- const click = InternalBackdrop.useClick(floatingRootContext, {
3036
+ const click = useScrollLock.useClick(floatingRootContext, {
2991
3037
  enabled: !disabled,
2992
3038
  event: 'mousedown',
2993
3039
  toggle: !openOnHover,
2994
3040
  ignoreMouse: openOnHover,
2995
3041
  stickIfOpen: false
2996
3042
  });
2997
- const localInteractionProps = InternalBackdrop.useInteractions([click]);
3043
+ const localInteractionProps = click.reference ?? useRenderElement.EMPTY_OBJECT;
2998
3044
  const rootTriggerProps = store.useState('triggerProps', true);
2999
3045
  delete rootTriggerProps.id;
3000
3046
  const state = {
@@ -3004,8 +3050,9 @@ const MenuSubmenuTrigger = /*#__PURE__*/React__namespace.forwardRef(function Sub
3004
3050
  };
3005
3051
  const element = useRenderElement.useRenderElement('div', componentProps, {
3006
3052
  state,
3007
- stateAttributesMapping: InternalBackdrop.triggerOpenStateMapping,
3008
- props: [localInteractionProps.getReferenceProps(), hoverProps, rootTriggerProps, itemProps, {
3053
+ stateAttributesMapping: useScrollLock.triggerOpenStateMapping,
3054
+ props: [localInteractionProps, hoverProps, rootTriggerProps, itemProps, {
3055
+ 'aria-controls': popupId,
3009
3056
  tabIndex: open || highlighted ? 0 : -1,
3010
3057
  onBlur() {
3011
3058
  if (highlighted) {