@jobber/components 9.5.3 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/Combobox.d.ts +1 -0
  2. package/Combobox.js +17 -0
  3. package/dist/Autocomplete/index.cjs +0 -2
  4. package/dist/Autocomplete/index.mjs +0 -2
  5. package/dist/Autocomplete-cjs.js +4 -5
  6. package/dist/Autocomplete-es.js +1 -2
  7. package/dist/Card/index.cjs +2 -2
  8. package/dist/Card/index.mjs +2 -2
  9. package/dist/Chip/index.cjs +0 -2
  10. package/dist/Chip/index.mjs +0 -2
  11. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
  12. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
  13. package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
  14. package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
  15. package/dist/Chips/index.cjs +0 -2
  16. package/dist/Chips/index.mjs +0 -2
  17. package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
  18. package/dist/Combobox/Combobox.context.d.ts +34 -0
  19. package/dist/Combobox/Combobox.d.ts +69 -0
  20. package/dist/Combobox/Combobox.footer.d.ts +7 -0
  21. package/dist/Combobox/Combobox.items.d.ts +25 -0
  22. package/dist/Combobox/Combobox.responsive.d.ts +54 -0
  23. package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
  24. package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
  25. package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
  26. package/dist/Combobox/Combobox.types.d.ts +166 -0
  27. package/dist/Combobox/index.cjs +35 -0
  28. package/dist/Combobox/index.d.ts +2 -0
  29. package/dist/Combobox/index.mjs +29 -0
  30. package/dist/Combobox-cjs.js +675 -0
  31. package/dist/Combobox-es.js +673 -0
  32. package/dist/ComboboxPrimitive-cjs.js +88 -47
  33. package/dist/ComboboxPrimitive-es.js +39 -23
  34. package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
  35. package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
  36. package/dist/ConfirmationModal/index.cjs +0 -2
  37. package/dist/ConfirmationModal/index.mjs +0 -2
  38. package/dist/ContentBlock/index.cjs +1 -0
  39. package/dist/ContentBlock/index.mjs +1 -0
  40. package/dist/ContentBlock-cjs.js +2 -18
  41. package/dist/ContentBlock-es.js +1 -17
  42. package/dist/DataDump/index.cjs +2 -2
  43. package/dist/DataDump/index.mjs +2 -2
  44. package/dist/DataList/components/DataListActions/index.cjs +0 -2
  45. package/dist/DataList/components/DataListActions/index.mjs +0 -2
  46. package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
  47. package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
  48. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
  49. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
  50. package/dist/DataList/components/DataListFilters/index.cjs +0 -2
  51. package/dist/DataList/components/DataListFilters/index.mjs +0 -2
  52. package/dist/DataList/components/DataListHeader/index.cjs +0 -2
  53. package/dist/DataList/components/DataListHeader/index.mjs +0 -2
  54. package/dist/DataList/components/DataListItem/index.cjs +0 -2
  55. package/dist/DataList/components/DataListItem/index.mjs +0 -2
  56. package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
  57. package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
  58. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
  59. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
  60. package/dist/DataList/components/DataListItems/index.cjs +0 -2
  61. package/dist/DataList/components/DataListItems/index.mjs +0 -2
  62. package/dist/DataList/components/DataListLayout/index.cjs +0 -2
  63. package/dist/DataList/components/DataListLayout/index.mjs +0 -2
  64. package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
  65. package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
  66. package/dist/DataList/index.cjs +0 -2
  67. package/dist/DataList/index.mjs +0 -2
  68. package/dist/DatePicker/index.cjs +0 -2
  69. package/dist/DatePicker/index.mjs +0 -2
  70. package/dist/DatePicker-cjs.js +4 -5
  71. package/dist/DatePicker-es.js +1 -2
  72. package/dist/Dialog/index.cjs +2 -2
  73. package/dist/Dialog/index.mjs +2 -2
  74. package/dist/DrawerDescription-cjs.js +268 -99
  75. package/dist/DrawerDescription-es.js +270 -103
  76. package/dist/DrawerTitle-cjs.js +2 -6
  77. package/dist/DrawerTitle-es.js +3 -7
  78. package/dist/FieldDescription-cjs.js +184 -181
  79. package/dist/FieldDescription-es.js +187 -184
  80. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
  81. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
  82. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
  83. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
  84. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
  85. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
  86. package/dist/FilterPicker/index.cjs +0 -2
  87. package/dist/FilterPicker/index.mjs +0 -2
  88. package/dist/FilterPickerContent-cjs.js +4 -5
  89. package/dist/FilterPickerContent-es.js +1 -2
  90. package/dist/FormatFile/index.cjs +0 -2
  91. package/dist/FormatFile/index.mjs +0 -2
  92. package/dist/Gallery/index.cjs +0 -2
  93. package/dist/Gallery/index.mjs +0 -2
  94. package/dist/InputDate/index.cjs +0 -2
  95. package/dist/InputDate/index.mjs +0 -2
  96. package/dist/InputNumber/index.cjs +2 -2
  97. package/dist/InputNumber/index.mjs +2 -2
  98. package/dist/InputNumber/types.d.ts +5 -0
  99. package/dist/InputNumber-cjs.js +2 -2
  100. package/dist/InputNumber-es.js +2 -2
  101. package/dist/LightBox/index.cjs +0 -2
  102. package/dist/LightBox/index.mjs +0 -2
  103. package/dist/Menu/index.cjs +2 -2
  104. package/dist/Menu/index.mjs +2 -2
  105. package/dist/Menu-cjs.js +4 -5
  106. package/dist/Menu-es.js +1 -2
  107. package/dist/MenuSubmenuTrigger-cjs.js +206 -164
  108. package/dist/MenuSubmenuTrigger-es.js +194 -152
  109. package/dist/Modal/index.cjs +0 -2
  110. package/dist/Modal/index.mjs +1 -3
  111. package/dist/NumberFieldInput-cjs.js +187 -308
  112. package/dist/NumberFieldInput-es.js +187 -308
  113. package/dist/Page/index.cjs +2 -2
  114. package/dist/Page/index.mjs +2 -2
  115. package/dist/Popover/index.cjs +0 -2
  116. package/dist/Popover/index.mjs +0 -2
  117. package/dist/Popover-cjs.js +7 -8
  118. package/dist/Popover-es.js +1 -2
  119. package/dist/ScrollAreaViewport-cjs.js +747 -1093
  120. package/dist/ScrollAreaViewport-es.js +734 -1081
  121. package/dist/Select/index.cjs +2 -2
  122. package/dist/Select/index.mjs +2 -2
  123. package/dist/SelectGroupLabel-cjs.js +207 -306
  124. package/dist/SelectGroupLabel-es.js +203 -302
  125. package/dist/SelectPrimitive-cjs.js +11 -17
  126. package/dist/SelectPrimitive-es.js +1 -7
  127. package/dist/Tooltip/index.cjs +0 -2
  128. package/dist/Tooltip/index.mjs +0 -2
  129. package/dist/Tooltip-cjs.js +6 -7
  130. package/dist/Tooltip-es.js +1 -2
  131. package/dist/buttonRenderAdapter-cjs.js +2 -1
  132. package/dist/buttonRenderAdapter-es.js +2 -1
  133. package/dist/dialogReturnFocus-cjs.js +1 -1
  134. package/dist/dialogReturnFocus-es.js +2 -2
  135. package/dist/docs/InputNumber/InputNumber.md +2 -2
  136. package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
  137. package/dist/floating-ui.react-cjs.js +2557 -185
  138. package/dist/floating-ui.react-es.js +2512 -149
  139. package/dist/getMappedBreakpointWidth-cjs.js +21 -0
  140. package/dist/getMappedBreakpointWidth-es.js +18 -0
  141. package/dist/index.cjs +15 -10
  142. package/dist/index.d.mts +1 -0
  143. package/dist/index.d.ts +1 -0
  144. package/dist/index.mjs +14 -10
  145. package/dist/mergeClassName-cjs.js +12 -0
  146. package/dist/mergeClassName-es.js +10 -0
  147. package/dist/primitives/BottomSheet/index.cjs +1 -1
  148. package/dist/primitives/BottomSheet/index.mjs +1 -1
  149. package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
  150. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
  151. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
  152. package/dist/primitives/SelectPrimitive/index.cjs +1 -2
  153. package/dist/primitives/SelectPrimitive/index.mjs +1 -2
  154. package/dist/primitives/index.cjs +3 -3
  155. package/dist/primitives/index.mjs +3 -3
  156. package/dist/scrollEdges-cjs.js +1 -11
  157. package/dist/scrollEdges-es.js +1 -11
  158. package/dist/scrollable-cjs.js +58 -0
  159. package/dist/scrollable-es.js +54 -0
  160. package/dist/styles.css +759 -42
  161. package/dist/unstyledPrimitives/index.cjs +720 -478
  162. package/dist/unstyledPrimitives/index.mjs +663 -421
  163. package/dist/useCompositeListItem-cjs.js +2108 -361
  164. package/dist/useCompositeListItem-es.js +2076 -331
  165. package/dist/useLabel-cjs.js +2 -3
  166. package/dist/useLabel-es.js +2 -3
  167. package/dist/useRenderElement-cjs.js +9 -7
  168. package/dist/useRenderElement-es.js +9 -7
  169. package/dist/useScrollLock-cjs.js +819 -344
  170. package/dist/useScrollLock-es.js +732 -296
  171. package/dist/useScrollToActive-cjs.js +4 -5
  172. package/dist/useScrollToActive-es.js +1 -2
  173. package/dist/utils/meta/meta.json +43 -1
  174. package/package.json +8 -3
  175. package/dist/floating-ui.react-dom-cjs.js +0 -2103
  176. package/dist/floating-ui.react-dom-es.js +0 -2072
  177. package/dist/floating-ui.utils-cjs.js +0 -342
  178. package/dist/floating-ui.utils-es.js +0 -300
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var React = require('react');
4
- var floatingUi_utils = require('./floating-ui.utils-cjs.js');
5
4
  var useRenderElement = require('./useRenderElement-cjs.js');
6
5
  var jsxRuntime = require('react/jsx-runtime');
7
6
  var ReactDOM = require('react-dom');
@@ -104,14 +103,6 @@ function serializeToDevModeString(input) {
104
103
  const noop = () => {};
105
104
  const useIsoLayoutEffect = typeof document !== 'undefined' ? React__namespace.useLayoutEffect : noop;
106
105
 
107
- function useOnFirstRender(fn) {
108
- const ref = React__namespace.useRef(true);
109
- if (ref.current) {
110
- ref.current = false;
111
- fn();
112
- }
113
- }
114
-
115
106
  /**
116
107
  * A clone of the React namespace for reading APIs that may be missing in older
117
108
  * supported React versions. Bundlers can rewrite direct `React.someNewApi`
@@ -210,87 +201,6 @@ const visuallyHiddenInput = {
210
201
  position: 'absolute'
211
202
  };
212
203
 
213
- /**
214
- * The NoOptionalParams type is a utility type that checks if a function has optional or default parameters.
215
- * If the function has optional or default parameters, it returns a string literal type with an error message.
216
- * Otherwise, it returns the original function type.
217
- *
218
- * This is used to enforce that the combiner function passed to createSelector does not have optional or default parameters,
219
- * as memoization relies on the Function.length property, which does not account for optional or default parameters.
220
- */
221
-
222
- /**
223
- * Creates a selector function that can be used to derive values from the store's state.
224
- *
225
- * The combiner function can have up to three additional parameters, but it **cannot have optional or default parameters**.
226
- *
227
- * This function accepts up to six functions and combines them into a single selector function.
228
- * The resulting selector will take the state from the combined selectors and any additional parameters required by the combiner.
229
- *
230
- * The return type of the resulting selector is determined by the return type of the combiner function.
231
- *
232
- * @example
233
- * const selector = createSelector(
234
- * (state) => state.disabled
235
- * );
236
- *
237
- * @example
238
- * const selector = createSelector(
239
- * (state) => state.disabled,
240
- * (state) => state.open,
241
- * (disabled, open) => ({ disabled, open })
242
- * );
243
- */
244
- /* eslint-disable id-denylist */
245
- const createSelector = (a, b, c, d, e, f, ...other) => {
246
- if (other.length > 0) {
247
- throw new Error(process.env.NODE_ENV !== "production" ? 'Unsupported number of selectors' : useRenderElement.formatErrorMessage(1));
248
- }
249
- let selector;
250
- if (a && b && c && d && e && f) {
251
- selector = (state, a1, a2, a3) => {
252
- const va = a(state, a1, a2, a3);
253
- const vb = b(state, a1, a2, a3);
254
- const vc = c(state, a1, a2, a3);
255
- const vd = d(state, a1, a2, a3);
256
- const ve = e(state, a1, a2, a3);
257
- return f(va, vb, vc, vd, ve, a1, a2, a3);
258
- };
259
- } else if (a && b && c && d && e) {
260
- selector = (state, a1, a2, a3) => {
261
- const va = a(state, a1, a2, a3);
262
- const vb = b(state, a1, a2, a3);
263
- const vc = c(state, a1, a2, a3);
264
- const vd = d(state, a1, a2, a3);
265
- return e(va, vb, vc, vd, a1, a2, a3);
266
- };
267
- } else if (a && b && c && d) {
268
- selector = (state, a1, a2, a3) => {
269
- const va = a(state, a1, a2, a3);
270
- const vb = b(state, a1, a2, a3);
271
- const vc = c(state, a1, a2, a3);
272
- return d(va, vb, vc, a1, a2, a3);
273
- };
274
- } else if (a && b && c) {
275
- selector = (state, a1, a2, a3) => {
276
- const va = a(state, a1, a2, a3);
277
- const vb = b(state, a1, a2, a3);
278
- return c(va, vb, a1, a2, a3);
279
- };
280
- } else if (a && b) {
281
- selector = (state, a1, a2, a3) => {
282
- const va = a(state, a1, a2, a3);
283
- return b(va, a1, a2, a3);
284
- };
285
- } else if (a) {
286
- selector = a;
287
- } else {
288
- throw /* minify-error-disabled */new Error('Missing arguments');
289
- }
290
- return selector;
291
- };
292
- /* eslint-enable id-denylist */
293
-
294
204
  var shim = {exports: {}};
295
205
 
296
206
  var useSyncExternalStoreShim_production = {};
@@ -698,6 +608,41 @@ function getInstance() {
698
608
  function register(hook) {
699
609
  hooks.push(hook);
700
610
  }
611
+
612
+ /**
613
+ * Wraps a component function to enable performance optimizations for internal hooks.
614
+ *
615
+ * **Performance Optimization:**
616
+ * Components wrapped with `fastComponent` have access to a shared "instance" context that enables
617
+ * specialized hook implementations to batch operations and reduce overhead. The wrapper creates a
618
+ * stable instance object that persists across renders, sets it as the current context, calls
619
+ * registered hooks before and after rendering, then clears the context. The primary benefit is
620
+ * with `useStore`, where multiple store subscriptions within the same component are collapsed into
621
+ * a single `useSyncExternalStore` subscription per store, significantly reducing re-render overhead.
622
+ * This optimization is only active on React 19+; on earlier versions `useStore` falls back to a
623
+ * separate subscription per call.
624
+ *
625
+ * **Requirements:**
626
+ * - The component function should follow standard React component patterns
627
+ * - `useStore` calls must keep a stable order and count across renders, as batched hooks are
628
+ * matched by call index
629
+ * - Do not rely on the instance context outside of specialized hooks
630
+ *
631
+ * @param fn - The component function to wrap
632
+ * @returns A wrapped component with the same signature as the input function
633
+ *
634
+ * @example
635
+ * ```tsx
636
+ * // Wrapping a component to enable optimized useStore batching
637
+ * export const TooltipRoot = fastComponent(function TooltipRoot(props) {
638
+ * // These useStore calls share a single subscription
639
+ * const open = useStore(store, (state) => state.open);
640
+ * const disabled = useStore(store, (state) => state.disabled);
641
+ * const value = useStore(store, (state) => state.value);
642
+ * // ...
643
+ * });
644
+ * ```
645
+ */
701
646
  function fastComponent(fn) {
702
647
  const FastComponent = (props, forwardedRef) => {
703
648
  const instance = useRenderElement.useRefWithInit(createInstance).current;
@@ -720,6 +665,31 @@ function fastComponent(fn) {
720
665
  FastComponent.displayName = fn.displayName || fn.name;
721
666
  return FastComponent;
722
667
  }
668
+
669
+ /**
670
+ * Wraps a component function with ref forwarding to enable performance optimizations for internal hooks.
671
+ *
672
+ * This is a convenience wrapper that combines `fastComponent` with `React.forwardRef`, enabling
673
+ * both performance optimizations and proper ref forwarding. See `fastComponent` for details on
674
+ * the performance benefits.
675
+ *
676
+ * @param fn - The component function that accepts props and a forwarded ref
677
+ * @returns A wrapped component with ref forwarding enabled
678
+ *
679
+ * @example
680
+ * ```tsx
681
+ * // Wrapping a component with ref forwarding and optimized hooks
682
+ * export const TooltipTrigger = fastComponentRef(function TooltipTrigger(
683
+ * props,
684
+ * forwardedRef
685
+ * ) {
686
+ * const store = useContext(TooltipContext);
687
+ * const open = useStore(store, (state) => state.open);
688
+ * // ... component logic with ref
689
+ * return <button ref={forwardedRef} {...props} />;
690
+ * });
691
+ * ```
692
+ */
723
693
  function fastComponentRef(fn) {
724
694
  return /*#__PURE__*/React__namespace.forwardRef(fastComponent(fn));
725
695
  }
@@ -1135,6 +1105,170 @@ class ReactStore extends Store {
1135
1105
  }
1136
1106
  }
1137
1107
 
1108
+ function hasWindow() {
1109
+ return typeof window !== 'undefined';
1110
+ }
1111
+ function getNodeName(node) {
1112
+ if (isNode(node)) {
1113
+ return (node.nodeName || '').toLowerCase();
1114
+ }
1115
+ // Mocked nodes in testing environments may not be instances of Node. By
1116
+ // returning `#document` an infinite loop won't occur.
1117
+ // https://github.com/floating-ui/floating-ui/issues/2317
1118
+ return '#document';
1119
+ }
1120
+ function getWindow(node) {
1121
+ var _node$ownerDocument;
1122
+ return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
1123
+ }
1124
+ function getDocumentElement(node) {
1125
+ var _ref;
1126
+ return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement;
1127
+ }
1128
+ function isNode(value) {
1129
+ if (!hasWindow()) {
1130
+ return false;
1131
+ }
1132
+ return value instanceof Node || value instanceof getWindow(value).Node;
1133
+ }
1134
+ function isElement(value) {
1135
+ if (!hasWindow()) {
1136
+ return false;
1137
+ }
1138
+ return value instanceof Element || value instanceof getWindow(value).Element;
1139
+ }
1140
+ function isHTMLElement(value) {
1141
+ if (!hasWindow()) {
1142
+ return false;
1143
+ }
1144
+ return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement;
1145
+ }
1146
+ function isShadowRoot(value) {
1147
+ if (!hasWindow() || typeof ShadowRoot === 'undefined') {
1148
+ return false;
1149
+ }
1150
+ return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot;
1151
+ }
1152
+ function isOverflowElement(element) {
1153
+ const {
1154
+ overflow,
1155
+ overflowX,
1156
+ overflowY,
1157
+ display
1158
+ } = getComputedStyle(element);
1159
+ return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && display !== 'inline' && display !== 'contents';
1160
+ }
1161
+ function isTableElement(element) {
1162
+ return /^(table|td|th)$/.test(getNodeName(element));
1163
+ }
1164
+ function isTopLayer(element) {
1165
+ try {
1166
+ if (element.matches(':popover-open')) {
1167
+ return true;
1168
+ }
1169
+ } catch (_e) {
1170
+ // no-op
1171
+ }
1172
+ try {
1173
+ return element.matches(':modal');
1174
+ } catch (_e) {
1175
+ return false;
1176
+ }
1177
+ }
1178
+ const willChangeRe = /transform|translate|scale|rotate|perspective|filter/;
1179
+ const containRe = /paint|layout|strict|content/;
1180
+ const isNotNone = value => !!value && value !== 'none';
1181
+ let isWebKitValue;
1182
+ function isContainingBlock(elementOrCss) {
1183
+ const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
1184
+
1185
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
1186
+ // https://drafts.csswg.org/css-transforms-2/#individual-transforms
1187
+ return isNotNone(css.transform) || isNotNone(css.translate) || isNotNone(css.scale) || isNotNone(css.rotate) || isNotNone(css.perspective) || !isWebKit() && (isNotNone(css.backdropFilter) || isNotNone(css.filter)) || willChangeRe.test(css.willChange || '') || containRe.test(css.contain || '');
1188
+ }
1189
+ function getContainingBlock(element) {
1190
+ let currentNode = getParentNode(element);
1191
+ while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
1192
+ if (isContainingBlock(currentNode)) {
1193
+ return currentNode;
1194
+ } else if (isTopLayer(currentNode)) {
1195
+ return null;
1196
+ }
1197
+ currentNode = getParentNode(currentNode);
1198
+ }
1199
+ return null;
1200
+ }
1201
+ function isWebKit() {
1202
+ if (isWebKitValue == null) {
1203
+ isWebKitValue = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('-webkit-backdrop-filter', 'none');
1204
+ }
1205
+ return isWebKitValue;
1206
+ }
1207
+ function isLastTraversableNode(node) {
1208
+ return /^(html|body|#document)$/.test(getNodeName(node));
1209
+ }
1210
+ function getComputedStyle(element) {
1211
+ return getWindow(element).getComputedStyle(element);
1212
+ }
1213
+ function getNodeScroll(element) {
1214
+ if (isElement(element)) {
1215
+ return {
1216
+ scrollLeft: element.scrollLeft,
1217
+ scrollTop: element.scrollTop
1218
+ };
1219
+ }
1220
+ return {
1221
+ scrollLeft: element.scrollX,
1222
+ scrollTop: element.scrollY
1223
+ };
1224
+ }
1225
+ function getParentNode(node) {
1226
+ if (getNodeName(node) === 'html') {
1227
+ return node;
1228
+ }
1229
+ const result =
1230
+ // Step into the shadow DOM of the parent of a slotted node.
1231
+ node.assignedSlot ||
1232
+ // DOM Element detected.
1233
+ node.parentNode ||
1234
+ // ShadowRoot detected.
1235
+ isShadowRoot(node) && node.host ||
1236
+ // Fallback.
1237
+ getDocumentElement(node);
1238
+ return isShadowRoot(result) ? result.host : result;
1239
+ }
1240
+ function getNearestOverflowAncestor(node) {
1241
+ const parentNode = getParentNode(node);
1242
+ if (isLastTraversableNode(parentNode)) {
1243
+ return (node.ownerDocument || node).body;
1244
+ }
1245
+ if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
1246
+ return parentNode;
1247
+ }
1248
+ return getNearestOverflowAncestor(parentNode);
1249
+ }
1250
+ function getOverflowAncestors(node, list, traverseIframes) {
1251
+ var _node$ownerDocument2;
1252
+ if (list === void 0) {
1253
+ list = [];
1254
+ }
1255
+ if (traverseIframes === void 0) {
1256
+ traverseIframes = true;
1257
+ }
1258
+ const scrollableAncestor = getNearestOverflowAncestor(node);
1259
+ const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body);
1260
+ const win = getWindow(scrollableAncestor);
1261
+ if (isBody) {
1262
+ const frameElement = getFrameElement(win);
1263
+ return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []);
1264
+ } else {
1265
+ return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
1266
+ }
1267
+ }
1268
+ function getFrameElement(win) {
1269
+ return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
1270
+ }
1271
+
1138
1272
  /**
1139
1273
  * Combines multiple cleanup functions into a single cleanup function.
1140
1274
  */
@@ -1164,15 +1298,13 @@ function addEventListener(target, type, listener, options) {
1164
1298
  };
1165
1299
  }
1166
1300
 
1167
- const EMPTY$2 = [];
1168
-
1169
1301
  /**
1170
1302
  * A React.useEffect equivalent that runs once, when the component is mounted.
1171
1303
  */
1172
1304
  function useOnMount(fn) {
1173
1305
  // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- no need to put `fn` in the dependency array
1174
1306
  /* eslint-disable react-hooks/exhaustive-deps */
1175
- React__namespace.useEffect(fn, EMPTY$2);
1307
+ React__namespace.useEffect(fn, useRenderElement.EMPTY_ARRAY);
1176
1308
  /* eslint-enable react-hooks/exhaustive-deps */
1177
1309
  }
1178
1310
 
@@ -1236,7 +1368,7 @@ class Scheduler {
1236
1368
  this.callbacksCount -= 1;
1237
1369
  }
1238
1370
  }
1239
- const scheduler = new Scheduler();
1371
+ let scheduler = new Scheduler();
1240
1372
  class AnimationFrame {
1241
1373
  static create() {
1242
1374
  return new AnimationFrame();
@@ -1483,7 +1615,7 @@ function contains(parent, child) {
1483
1615
  }
1484
1616
 
1485
1617
  // Then fall back to traversing out of shadow roots when needed.
1486
- if (rootNode && floatingUi_utils.isShadowRoot(rootNode)) {
1618
+ if (rootNode && isShadowRoot(rootNode)) {
1487
1619
  let next = child;
1488
1620
  while (next) {
1489
1621
  if (parent === next) {
@@ -1505,7 +1637,7 @@ function getTarget(event) {
1505
1637
  }
1506
1638
 
1507
1639
  function isTargetInsideEnabledTrigger(target, triggerElements) {
1508
- if (!floatingUi_utils.isElement(target)) {
1640
+ if (!isElement(target)) {
1509
1641
  return false;
1510
1642
  }
1511
1643
  const targetElement = target;
@@ -1535,7 +1667,7 @@ function isRootElement(element) {
1535
1667
  return element.matches('html,body');
1536
1668
  }
1537
1669
  function isTypeableElement(element) {
1538
- return floatingUi_utils.isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
1670
+ return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
1539
1671
  }
1540
1672
  function isInteractiveElement(element) {
1541
1673
  return element?.closest(`button,a[href],[role="button"],select,[tabindex]:not([tabindex="-1"]),${TYPEABLE_SELECTOR}`) != null;
@@ -1676,6 +1808,138 @@ const FocusGuard = /*#__PURE__*/React__namespace.forwardRef(function FocusGuard(
1676
1808
  });
1677
1809
  if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1678
1810
 
1811
+ /**
1812
+ * Custom positioning reference element.
1813
+ * @see https://floating-ui.com/docs/virtual-elements
1814
+ */
1815
+
1816
+ const min = Math.min;
1817
+ const max = Math.max;
1818
+ const round = Math.round;
1819
+ const floor = Math.floor;
1820
+ const createCoords = v => ({
1821
+ x: v,
1822
+ y: v
1823
+ });
1824
+ const oppositeSideMap = {
1825
+ left: 'right',
1826
+ right: 'left',
1827
+ bottom: 'top',
1828
+ top: 'bottom'
1829
+ };
1830
+ function clamp(start, value, end) {
1831
+ return max(start, min(value, end));
1832
+ }
1833
+ function evaluate(value, param) {
1834
+ return typeof value === 'function' ? value(param) : value;
1835
+ }
1836
+ function getSide(placement) {
1837
+ return placement.split('-')[0];
1838
+ }
1839
+ function getAlignment(placement) {
1840
+ return placement.split('-')[1];
1841
+ }
1842
+ function getOppositeAxis(axis) {
1843
+ return axis === 'x' ? 'y' : 'x';
1844
+ }
1845
+ function getAxisLength(axis) {
1846
+ return axis === 'y' ? 'height' : 'width';
1847
+ }
1848
+ function getSideAxis(placement) {
1849
+ const firstChar = placement[0];
1850
+ return firstChar === 't' || firstChar === 'b' ? 'y' : 'x';
1851
+ }
1852
+ function getAlignmentAxis(placement) {
1853
+ return getOppositeAxis(getSideAxis(placement));
1854
+ }
1855
+ function getAlignmentSides(placement, rects, rtl) {
1856
+ if (rtl === void 0) {
1857
+ rtl = false;
1858
+ }
1859
+ const alignment = getAlignment(placement);
1860
+ const alignmentAxis = getAlignmentAxis(placement);
1861
+ const length = getAxisLength(alignmentAxis);
1862
+ let mainAlignmentSide = alignmentAxis === 'x' ? alignment === (rtl ? 'end' : 'start') ? 'right' : 'left' : alignment === 'start' ? 'bottom' : 'top';
1863
+ if (rects.reference[length] > rects.floating[length]) {
1864
+ mainAlignmentSide = getOppositePlacement(mainAlignmentSide);
1865
+ }
1866
+ return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)];
1867
+ }
1868
+ function getExpandedPlacements(placement) {
1869
+ const oppositePlacement = getOppositePlacement(placement);
1870
+ return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)];
1871
+ }
1872
+ function getOppositeAlignmentPlacement(placement) {
1873
+ return placement.includes('start') ? placement.replace('start', 'end') : placement.replace('end', 'start');
1874
+ }
1875
+ const lrPlacement = ['left', 'right'];
1876
+ const rlPlacement = ['right', 'left'];
1877
+ const tbPlacement = ['top', 'bottom'];
1878
+ const btPlacement = ['bottom', 'top'];
1879
+ function getSideList(side, isStart, rtl) {
1880
+ switch (side) {
1881
+ case 'top':
1882
+ case 'bottom':
1883
+ if (rtl) return isStart ? rlPlacement : lrPlacement;
1884
+ return isStart ? lrPlacement : rlPlacement;
1885
+ case 'left':
1886
+ case 'right':
1887
+ return isStart ? tbPlacement : btPlacement;
1888
+ default:
1889
+ return [];
1890
+ }
1891
+ }
1892
+ function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) {
1893
+ const alignment = getAlignment(placement);
1894
+ let list = getSideList(getSide(placement), direction === 'start', rtl);
1895
+ if (alignment) {
1896
+ list = list.map(side => side + "-" + alignment);
1897
+ if (flipAlignment) {
1898
+ list = list.concat(list.map(getOppositeAlignmentPlacement));
1899
+ }
1900
+ }
1901
+ return list;
1902
+ }
1903
+ function getOppositePlacement(placement) {
1904
+ const side = getSide(placement);
1905
+ return oppositeSideMap[side] + placement.slice(side.length);
1906
+ }
1907
+ function expandPaddingObject(padding) {
1908
+ var _padding$top, _padding$right, _padding$bottom, _padding$left;
1909
+ return {
1910
+ top: (_padding$top = padding.top) != null ? _padding$top : 0,
1911
+ right: (_padding$right = padding.right) != null ? _padding$right : 0,
1912
+ bottom: (_padding$bottom = padding.bottom) != null ? _padding$bottom : 0,
1913
+ left: (_padding$left = padding.left) != null ? _padding$left : 0
1914
+ };
1915
+ }
1916
+ function getPaddingObject(padding) {
1917
+ return typeof padding !== 'number' ? expandPaddingObject(padding) : {
1918
+ top: padding,
1919
+ right: padding,
1920
+ bottom: padding,
1921
+ left: padding
1922
+ };
1923
+ }
1924
+ function rectToClientRect(rect) {
1925
+ const {
1926
+ x,
1927
+ y,
1928
+ width,
1929
+ height
1930
+ } = rect;
1931
+ return {
1932
+ width,
1933
+ height,
1934
+ top: y,
1935
+ left: x,
1936
+ right: x + width,
1937
+ bottom: y + height,
1938
+ x,
1939
+ y
1940
+ };
1941
+ }
1942
+
1679
1943
  function isDifferentGridRow(index, cols, prevRow) {
1680
1944
  return Math.floor(index / cols) !== prevRow;
1681
1945
  }
@@ -1811,7 +2075,7 @@ function getGridNavigatedIndex(list, {
1811
2075
  const colInRow = prevIndex % verticalCols;
1812
2076
  const rowStep = direction === 'up' ? -verticalCols : verticalCols;
1813
2077
  const lastRowStart = maxIndex - maxIndex % verticalCols;
1814
- const rowCount = floatingUi_utils.floor(maxIndex / verticalCols) + 1;
2078
+ const rowCount = floor(maxIndex / verticalCols) + 1;
1815
2079
  for (let rowStart = prevIndex - colInRow + rowStep, i = 0; i < rowCount; i += 1, rowStart += rowStep) {
1816
2080
  if (rowStart < 0 || rowStart > maxIndex) {
1817
2081
  if (!loopFocus) {
@@ -1876,7 +2140,7 @@ function getGridNavigatedIndex(list, {
1876
2140
 
1877
2141
  // Remains on the same row/column.
1878
2142
  if (orientation === 'both') {
1879
- const prevRow = floatingUi_utils.floor(prevIndex / cols);
2143
+ const prevRow = floor(prevIndex / cols);
1880
2144
  if (event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)) {
1881
2145
  if (stop) {
1882
2146
  stopEvent(event);
@@ -1942,7 +2206,7 @@ function getGridNavigatedIndex(list, {
1942
2206
  nextIndex = prevIndex;
1943
2207
  }
1944
2208
  }
1945
- const lastRow = floatingUi_utils.floor(maxIndex / cols) === prevRow;
2209
+ const lastRow = floor(maxIndex / cols) === prevRow;
1946
2210
  if (isIndexOutOfListBounds(list, nextIndex)) {
1947
2211
  if (loopFocus && lastRow) {
1948
2212
  nextIndex = event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT) ? maxIndex : findNonDisabledListIndex(list, {
@@ -1971,12 +2235,19 @@ function isListIndexDisabled(list, index, disabledIndices) {
1971
2235
  if (!isElementVisible(element)) {
1972
2236
  return true;
1973
2237
  }
2238
+
2239
+ // A natively disabled element can never receive focus, so it must always be
2240
+ // skipped, even when `disabledIndices` marks it as enabled. Only
2241
+ // `aria-disabled` items can be focusable-while-disabled.
2242
+ if (element.matches(':disabled')) {
2243
+ return true;
2244
+ }
1974
2245
  return !disabledIndices && (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true');
1975
2246
  }
1976
2247
  function isHiddenByStyles(styles) {
1977
2248
  return styles.visibility === 'hidden' || styles.visibility === 'collapse';
1978
2249
  }
1979
- function isElementVisible(element, styles = element ? floatingUi_utils.getComputedStyle(element) : null) {
2250
+ function isElementVisible(element, styles = element ? getComputedStyle(element) : null) {
1980
2251
  if (!element || !element.isConnected || !styles || isHiddenByStyles(styles)) {
1981
2252
  return false;
1982
2253
  }
@@ -1996,11 +2267,11 @@ function getParentElement(element) {
1996
2267
  return element.parentElement;
1997
2268
  }
1998
2269
  const rootNode = element.getRootNode();
1999
- return floatingUi_utils.isShadowRoot(rootNode) ? rootNode.host : null;
2270
+ return isShadowRoot(rootNode) ? rootNode.host : null;
2000
2271
  }
2001
2272
  function getDetailsSummary(details) {
2002
2273
  for (const child of Array.from(details.children)) {
2003
- if (floatingUi_utils.getNodeName(child) === 'summary') {
2274
+ if (getNodeName(child) === 'summary') {
2004
2275
  return child;
2005
2276
  }
2006
2277
  }
@@ -2011,8 +2282,8 @@ function isWithinOpenDetailsSummary(element, details) {
2011
2282
  return !!summary && (element === summary || contains(summary, element));
2012
2283
  }
2013
2284
  function isFocusableCandidate(element) {
2014
- const nodeName = element ? floatingUi_utils.getNodeName(element) : '';
2015
- return element != null && element.matches(CANDIDATE_SELECTOR) && (nodeName !== 'summary' || element.parentElement != null && floatingUi_utils.getNodeName(element.parentElement) === 'details' && getDetailsSummary(element.parentElement) === element) && (nodeName !== 'details' || getDetailsSummary(element) == null) && (nodeName !== 'input' || element.type !== 'hidden');
2285
+ const nodeName = element ? getNodeName(element) : '';
2286
+ return element != null && element.matches(CANDIDATE_SELECTOR) && (nodeName !== 'summary' || element.parentElement != null && getNodeName(element.parentElement) === 'details' && getDetailsSummary(element.parentElement) === element) && (nodeName !== 'details' || getDetailsSummary(element) == null) && (nodeName !== 'input' || element.type !== 'hidden');
2016
2287
  }
2017
2288
  function isFocusableElement(element) {
2018
2289
  if (!isFocusableCandidate(element) || !element.isConnected || element.matches(':disabled')) {
@@ -2020,18 +2291,18 @@ function isFocusableElement(element) {
2020
2291
  }
2021
2292
  for (let current = element; current; current = getParentElement(current)) {
2022
2293
  const isAncestor = current !== element;
2023
- const isSlot = floatingUi_utils.getNodeName(current) === 'slot';
2294
+ const isSlot = getNodeName(current) === 'slot';
2024
2295
  if (current.hasAttribute('inert')) {
2025
2296
  return false;
2026
2297
  }
2027
- if (isAncestor && floatingUi_utils.getNodeName(current) === 'details' && !current.open && !isWithinOpenDetailsSummary(element, current) || current.hasAttribute('hidden') || !isSlot && !isVisibleInTabbableTree(current, isAncestor)) {
2298
+ if (isAncestor && getNodeName(current) === 'details' && !current.open && !isWithinOpenDetailsSummary(element, current) || current.hasAttribute('hidden') || !isSlot && !isVisibleInTabbableTree(current, isAncestor)) {
2028
2299
  return false;
2029
2300
  }
2030
2301
  }
2031
2302
  return true;
2032
2303
  }
2033
2304
  function isVisibleInTabbableTree(element, isAncestor) {
2034
- const styles = floatingUi_utils.getComputedStyle(element);
2305
+ const styles = getComputedStyle(element);
2035
2306
  if (!isAncestor) {
2036
2307
  return isElementVisible(element, styles);
2037
2308
  }
@@ -2040,15 +2311,15 @@ function isVisibleInTabbableTree(element, isAncestor) {
2040
2311
  function getTabIndex(element) {
2041
2312
  const tabIndex = element.tabIndex;
2042
2313
  if (tabIndex < 0) {
2043
- const nodeName = floatingUi_utils.getNodeName(element);
2044
- if (nodeName === 'details' || nodeName === 'audio' || nodeName === 'video' || floatingUi_utils.isHTMLElement(element) && element.isContentEditable) {
2314
+ const nodeName = getNodeName(element);
2315
+ if (nodeName === 'details' || nodeName === 'audio' || nodeName === 'video' || isHTMLElement(element) && element.isContentEditable) {
2045
2316
  return 0;
2046
2317
  }
2047
2318
  }
2048
2319
  return tabIndex;
2049
2320
  }
2050
2321
  function getNamedRadioInput(element) {
2051
- if (floatingUi_utils.getNodeName(element) !== 'input') {
2322
+ if (getNodeName(element) !== 'input') {
2052
2323
  return null;
2053
2324
  }
2054
2325
  const input = element;
@@ -2072,7 +2343,7 @@ function isTabbableRadio(element, candidates) {
2072
2343
  }) === input;
2073
2344
  }
2074
2345
  function getComposedChildren(container) {
2075
- if (floatingUi_utils.isHTMLElement(container) && floatingUi_utils.getNodeName(container) === 'slot') {
2346
+ if (isHTMLElement(container) && getNodeName(container) === 'slot') {
2076
2347
  const assignedElements = container.assignedElements({
2077
2348
  flatten: true
2078
2349
  });
@@ -2080,7 +2351,7 @@ function getComposedChildren(container) {
2080
2351
  return assignedElements;
2081
2352
  }
2082
2353
  }
2083
- if (floatingUi_utils.isHTMLElement(container) && container.shadowRoot) {
2354
+ if (isHTMLElement(container) && container.shadowRoot) {
2084
2355
  return Array.from(container.shadowRoot.children);
2085
2356
  }
2086
2357
  return Array.from(container.children);
@@ -2095,7 +2366,7 @@ function appendCandidates(container, list) {
2095
2366
  }
2096
2367
  function appendMatchingElements(container, selector, list) {
2097
2368
  getComposedChildren(container).forEach(child => {
2098
- if (floatingUi_utils.isHTMLElement(child) && child.matches(selector)) {
2369
+ if (isHTMLElement(child) && child.matches(selector)) {
2099
2370
  list.push(child);
2100
2371
  }
2101
2372
  appendMatchingElements(child, selector, list);
@@ -2252,7 +2523,7 @@ function unwrapHost(node) {
2252
2523
  if (!node) {
2253
2524
  return null;
2254
2525
  }
2255
- return floatingUi_utils.isShadowRoot(node) ? node.host : unwrapHost(node.parentNode);
2526
+ return isShadowRoot(node) ? node.host : unwrapHost(node.parentNode);
2256
2527
  }
2257
2528
  const correctElements = (parent, targets) => targets.map(target => {
2258
2529
  if (parent.contains(target)) {
@@ -2282,7 +2553,7 @@ const collectOutsideElements = (root, keepElements, stopElements) => {
2282
2553
  return;
2283
2554
  }
2284
2555
  Array.from(parent.children).forEach(node => {
2285
- if (floatingUi_utils.getNodeName(node) === 'script') {
2556
+ if (getNodeName(node) === 'script') {
2286
2557
  return;
2287
2558
  }
2288
2559
  if (keepElements.has(node)) {
@@ -2501,12 +2772,7 @@ function useFloatingPortalNode(props = {}) {
2501
2772
  }
2502
2773
  return;
2503
2774
  }
2504
-
2505
- // React 17 does not use React.useId().
2506
- if (uniqueId == null) {
2507
- return;
2508
- }
2509
- const resolvedContainer = (containerProp && (floatingUi_utils.isNode(containerProp) ? containerProp : containerProp.current)) ?? parentPortalNode ?? document.body;
2775
+ const resolvedContainer = (containerProp && (isNode(containerProp) ? containerProp : containerProp.current)) ?? parentPortalNode ?? document.body;
2510
2776
  if (resolvedContainer == null) {
2511
2777
  if (containerRef.current) {
2512
2778
  containerRef.current = null;
@@ -2520,7 +2786,7 @@ function useFloatingPortalNode(props = {}) {
2520
2786
  setPortalNode(null);
2521
2787
  setContainerElement(resolvedContainer);
2522
2788
  }
2523
- }, [containerProp, parentPortalNode, uniqueId]);
2789
+ }, [containerProp, parentPortalNode]);
2524
2790
  const portalElement = useRenderElement.useRenderElement('div', componentProps, {
2525
2791
  ref: [ref, setPortalNodeRef],
2526
2792
  props: [{
@@ -2533,8 +2799,11 @@ function useFloatingPortalNode(props = {}) {
2533
2799
  // Another call inside `FloatingPortal`/`FloatingPortalLite` then injects the children into `portalElement`.
2534
2800
  const portalSubtree = containerElement && portalElement ? /*#__PURE__*/ReactDOM__namespace.createPortal(portalElement, containerElement) : null;
2535
2801
  return {
2536
- portalNode,
2537
- portalSubtree
2802
+ node: portalNode,
2803
+ // `id` and `render` props can override or remove the generated ID. Use the exact
2804
+ // rendered value so `aria-owns` never points at an ID absent from the DOM.
2805
+ nodeId: /*#__PURE__*/React__namespace.isValidElement(portalElement) ? portalElement.props.id : undefined,
2806
+ subtree: portalSubtree
2538
2807
  };
2539
2808
  }
2540
2809
 
@@ -2554,12 +2823,12 @@ const FloatingPortal = /*#__PURE__*/React__namespace.forwardRef(function Floatin
2554
2823
  style,
2555
2824
  children,
2556
2825
  container,
2557
- renderGuards,
2558
2826
  ...elementProps
2559
2827
  } = componentProps;
2560
2828
  const {
2561
- portalNode,
2562
- portalSubtree
2829
+ node: portalNode,
2830
+ nodeId: portalNodeId,
2831
+ subtree: portalSubtree
2563
2832
  } = useFloatingPortalNode({
2564
2833
  container,
2565
2834
  ref: forwardedRef,
@@ -2574,7 +2843,7 @@ const FloatingPortal = /*#__PURE__*/React__namespace.forwardRef(function Floatin
2574
2843
  const focusInsideDisabledRef = React__namespace.useRef(false);
2575
2844
  const modal = focusManagerState?.modal;
2576
2845
  const open = focusManagerState?.open;
2577
- const shouldRenderGuards = typeof renderGuards === 'boolean' ? renderGuards : !!focusManagerState && !focusManagerState.modal && focusManagerState.open && !!portalNode;
2846
+ const shouldRenderGuards = !!focusManagerState && !focusManagerState.modal && focusManagerState.open && !!portalNode;
2578
2847
 
2579
2848
  // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx
2580
2849
  React__namespace.useEffect(() => {
@@ -2636,7 +2905,7 @@ const FloatingPortal = /*#__PURE__*/React__namespace.forwardRef(function Floatin
2636
2905
  }
2637
2906
  }
2638
2907
  }), shouldRenderGuards && portalNode && /*#__PURE__*/jsxRuntime.jsx("span", {
2639
- "aria-owns": portalNode.id,
2908
+ "aria-owns": portalNodeId,
2640
2909
  style: ownerVisuallyHidden
2641
2910
  }), portalNode && /*#__PURE__*/ReactDOM__namespace.createPortal(children, portalNode), shouldRenderGuards && portalNode && /*#__PURE__*/jsxRuntime.jsx(FocusGuard, {
2642
2911
  "data-type": "outside",
@@ -2793,7 +3062,7 @@ function resolveRef(maybeRef) {
2793
3062
  }
2794
3063
 
2795
3064
  function getEventType(event, lastInteractionType) {
2796
- const win = floatingUi_utils.getWindow(getTarget(event));
3065
+ const win = getWindow(getTarget(event));
2797
3066
  if (event instanceof win.KeyboardEvent) {
2798
3067
  return 'keyboard';
2799
3068
  }
@@ -2823,7 +3092,7 @@ function clearDisconnectedPreviouslyFocusedElements() {
2823
3092
  }
2824
3093
  function addPreviouslyFocusedElement(element) {
2825
3094
  clearDisconnectedPreviouslyFocusedElements();
2826
- if (element && floatingUi_utils.getNodeName(element) !== 'body') {
3095
+ if (element && getNodeName(element) !== 'body') {
2827
3096
  previouslyFocusedElements.push(new WeakRef(element));
2828
3097
  if (previouslyFocusedElements.length > LIST_LIMIT) {
2829
3098
  previouslyFocusedElements = previouslyFocusedElements.slice(-LIST_LIMIT);
@@ -2900,11 +3169,10 @@ function FloatingFocusManager(props) {
2900
3169
  } = store.context;
2901
3170
  const getNodeId = useStableCallback(() => dataRef.current.floatingContext?.nodeId);
2902
3171
  const ignoreInitialFocus = initialFocus === false;
2903
- // If the reference is a combobox and is typeable (e.g. input/textarea),
2904
- // there are different focus semantics. The guards should not be rendered, but
2905
- // aria-hidden should be applied to all nodes still. Further, the visually
2906
- // hidden dismiss button should only appear at the end of the list, not the
2907
- // start.
3172
+ // A typeable combobox reference (e.g. input/textarea) with `initialFocus={false}`
3173
+ // has different focus semantics: focus is not trapped inside the floating element,
3174
+ // so in the modal case the guards are not rendered, but `aria-hidden` is still
3175
+ // applied to the outside nodes.
2908
3176
  const isUntrappedTypeableCombobox = isTypeableCombobox(domReference) && ignoreInitialFocus;
2909
3177
  const initialFocusRef = useValueAsRef(initialFocus);
2910
3178
  const returnFocusRef = useValueAsRef(returnFocus);
@@ -3018,25 +3286,25 @@ function FloatingFocusManager(props) {
3018
3286
  const triggers = store.context.triggerElements;
3019
3287
  const insideElements = getResolvedInsideElements();
3020
3288
  const isRelatedFocusGuard = relatedTarget?.hasAttribute(createAttribute('focus-guard')) && [beforeGuardRef.current, afterGuardRef.current, portalContext?.beforeInsideRef.current, portalContext?.afterInsideRef.current, portalContext?.beforeOutsideRef.current, portalContext?.afterOutsideRef.current, resolveRef(previousFocusableElement), resolveRef(nextFocusableElement)].includes(relatedTarget);
3021
- const movedToUnrelatedNode = !(contains(domReference, relatedTarget) || contains(floating, relatedTarget) || contains(relatedTarget, floating) || contains(portalContext?.portalNode, relatedTarget) || insideElements.some(element => element === relatedTarget || contains(element, relatedTarget)) || relatedTarget != null && triggers.hasElement(relatedTarget) || triggers.hasMatchingElement(trigger => contains(trigger, relatedTarget)) || isRelatedFocusGuard || tree && (getNodeChildren(tree.nodesRef.current, nodeId).find(node => contains(node.context?.elements.floating, relatedTarget) || contains(node.context?.elements.domReference, relatedTarget)) || getNodeAncestors(tree.nodesRef.current, nodeId).find(node => [node.context?.elements.floating, getFloatingFocusElement(node.context?.elements.floating)].includes(relatedTarget) || node.context?.elements.domReference === relatedTarget)));
3289
+ const movedToUnrelatedNode = !(contains(domReference, relatedTarget) || contains(floating, relatedTarget) || contains(relatedTarget, floating) || contains(portalContext?.portalNode, relatedTarget) || insideElements.some(element => element === relatedTarget || contains(element, relatedTarget)) || triggers.hasMatchingElement(trigger => contains(trigger, relatedTarget)) || isRelatedFocusGuard || tree && (getNodeChildren(tree.nodesRef.current, nodeId).find(node => contains(node.context?.elements.floating, relatedTarget) || contains(node.context?.elements.domReference, relatedTarget)) || getNodeAncestors(tree.nodesRef.current, nodeId).find(node => [node.context?.elements.floating, getFloatingFocusElement(node.context?.elements.floating)].includes(relatedTarget) || node.context?.elements.domReference === relatedTarget)));
3022
3290
  if (currentTarget === domReference && floatingFocusElement) {
3023
3291
  handleTabIndex(floatingFocusElement);
3024
3292
  }
3025
3293
 
3026
- // Restore focus to the previous tabbable element index to prevent
3294
+ // Restore focus to the previously focused tabbable element to prevent
3027
3295
  // focus from being lost outside the floating tree.
3028
3296
  if (restoreFocus && currentTarget !== domReference && !isElementVisible(target) && activeElement(doc) === doc.body) {
3029
3297
  // Let `FloatingPortal` effect knows that focus is still inside the
3030
3298
  // floating tree.
3031
- if (floatingUi_utils.isHTMLElement(floatingFocusElement)) {
3299
+ if (isHTMLElement(floatingFocusElement)) {
3032
3300
  floatingFocusElement.focus();
3033
3301
  // If explicitly requested to restore focus to the popup container, do not search
3034
3302
  // for the next/previous tabbable element.
3035
3303
  if (restoreFocus === 'popup') {
3036
- // If the element is removed on pointerdown, focus tries to move it,
3037
- // but since it's removed at the same time, focus gets lost as it
3038
- // happens after the .focus() call above.
3039
- // In this case, focus needs to be moved asynchronously.
3304
+ // If the focused element is removed on pointerdown, the browser
3305
+ // tries to move focus to it right after the `.focus()` call above,
3306
+ // but because it's removed in the same tick, focus is lost instead.
3307
+ // Re-focusing asynchronously (next frame) wins that race.
3040
3308
  restoreFocusFrame.request(() => {
3041
3309
  floatingFocusElement.focus();
3042
3310
  });
@@ -3046,7 +3314,7 @@ function FloatingFocusManager(props) {
3046
3314
  const tabbableContent = getTabbableContent();
3047
3315
  const prevTabbable = lastFocusedTabbableRef.current;
3048
3316
  const nodeToFocus = (prevTabbable && tabbableContent.includes(prevTabbable) ? prevTabbable : null) || tabbableContent[tabbableContent.length - 1] || floatingFocusElement;
3049
- if (floatingUi_utils.isHTMLElement(nodeToFocus)) {
3317
+ if (isHTMLElement(nodeToFocus)) {
3050
3318
  nodeToFocus.focus();
3051
3319
  }
3052
3320
  }
@@ -3082,7 +3350,7 @@ function FloatingFocusManager(props) {
3082
3350
  dataRef.current.insideReactTree = false;
3083
3351
  });
3084
3352
  }
3085
- const domReferenceElement = floatingUi_utils.isHTMLElement(domReference) ? domReference : null;
3353
+ const domReferenceElement = isHTMLElement(domReference) ? domReference : null;
3086
3354
  if (!floating && !domReferenceElement) {
3087
3355
  return undefined;
3088
3356
  }
@@ -3115,9 +3383,11 @@ function FloatingFocusManager(props) {
3115
3383
 
3116
3384
  // Focus the initial element when the floating element opens.
3117
3385
  useIsoLayoutEffect(() => {
3118
- if (!open || disabled || !floatingUi_utils.isHTMLElement(floatingFocusElement)) {
3386
+ if (!open || disabled || !isHTMLElement(floatingFocusElement)) {
3119
3387
  return;
3120
3388
  }
3389
+ closeTypeRef.current = '';
3390
+ lastInteractionTypeRef.current = '';
3121
3391
  const doc = ownerDocument(floatingFocusElement);
3122
3392
  const previouslyFocusedElement = activeElement(doc);
3123
3393
 
@@ -3149,7 +3419,9 @@ function FloatingFocusManager(props) {
3149
3419
  }
3150
3420
  elToFocus = elToFocus || getDefaultFocusElement();
3151
3421
  const hadFocusInside = contains(floatingFocusElement, activeElement(doc));
3152
- enqueueFocus(elToFocus, {
3422
+
3423
+ // enqueueFocus returns a rAF-cancel function; we intentionally don't cancel this focus.
3424
+ void enqueueFocus(elToFocus, {
3153
3425
  preventScroll: elToFocus === floatingFocusElement,
3154
3426
  shouldFocus() {
3155
3427
  // This focus is queued on the next animation frame. If the floating element has closed
@@ -3176,13 +3448,11 @@ function FloatingFocusManager(props) {
3176
3448
  }
3177
3449
  const doc = ownerDocument(floatingFocusElement);
3178
3450
  const elementFocusedBeforeOpen = activeElement(doc);
3179
- // Only nullish interaction types represent programmatic opens. The empty
3180
- // string default is intentionally not treated as programmatic.
3451
+ // Only an explicit `null` interaction type represents a programmatic open.
3452
+ // `undefined` is normalized to `''` by the prop default, so it never reaches
3453
+ // here as nullish and is intentionally not treated as programmatic.
3181
3454
  const preferPreviousFocus = openInteractionTypeRef.current == null;
3182
3455
  addPreviouslyFocusedElement(elementFocusedBeforeOpen);
3183
-
3184
- // Dismissing via outside press should always ignore `returnFocus` to
3185
- // prevent unwanted scrolling.
3186
3456
  function onOpenChangeLocal(details) {
3187
3457
  if (!details.open) {
3188
3458
  closeTypeRef.current = getEventType(details.nativeEvent, lastInteractionTypeRef.current);
@@ -3198,6 +3468,11 @@ function FloatingFocusManager(props) {
3198
3468
  } else if (isVirtualClick(details.nativeEvent) || isVirtualPointerEvent(details.nativeEvent)) {
3199
3469
  preventReturnFocusRef.current = false;
3200
3470
  } else {
3471
+ // On outside press, only return focus to the reference when the browser supports the
3472
+ // `focus({ preventScroll })` option; without it, restoring focus scrolls the page.
3473
+ // Chrome on Android and Samsung Internet still don't support `preventScroll`
3474
+ // (https://issues.chromium.org/issues/41453122), so the runtime check keeps return
3475
+ // focus disabled there to avoid the scroll jump.
3201
3476
  let isPreventScrollSupported = false;
3202
3477
  ownerDocument(floatingFocusElement).createElement('div').focus({
3203
3478
  get preventScroll() {
@@ -3213,9 +3488,9 @@ function FloatingFocusManager(props) {
3213
3488
  }
3214
3489
  }
3215
3490
  events.on('openchange', onOpenChangeLocal);
3216
- function getReturnElement() {
3491
+ function getReturnElement(closeType) {
3217
3492
  const returnFocusValueOrFn = returnFocusRef.current;
3218
- let resolvedReturnFocusValue = typeof returnFocusValueOrFn === 'function' ? returnFocusValueOrFn(closeTypeRef.current) : returnFocusValueOrFn;
3493
+ let resolvedReturnFocusValue = typeof returnFocusValueOrFn === 'function' ? returnFocusValueOrFn(closeType) : returnFocusValueOrFn;
3219
3494
 
3220
3495
  // `null` should fallback to default behavior in case of an empty ref.
3221
3496
  if (resolvedReturnFocusValue === undefined || resolvedReturnFocusValue === false) {
@@ -3225,7 +3500,7 @@ function FloatingFocusManager(props) {
3225
3500
  resolvedReturnFocusValue = true;
3226
3501
  }
3227
3502
  const referenceReturnElement = domReference?.isConnected ? domReference : null;
3228
- const previousReturnElement = elementFocusedBeforeOpen?.isConnected && floatingUi_utils.getNodeName(elementFocusedBeforeOpen) !== 'body' ? elementFocusedBeforeOpen : null;
3503
+ const previousReturnElement = elementFocusedBeforeOpen?.isConnected && getNodeName(elementFocusedBeforeOpen) !== 'body' ? elementFocusedBeforeOpen : null;
3229
3504
  let defaultReturnElement = preferPreviousFocus ? previousReturnElement || referenceReturnElement : referenceReturnElement || previousReturnElement;
3230
3505
  if (!defaultReturnElement) {
3231
3506
  defaultReturnElement = getPreviouslyFocusedElement() || null;
@@ -3243,19 +3518,25 @@ function FloatingFocusManager(props) {
3243
3518
 
3244
3519
  // eslint-disable-next-line react-hooks/exhaustive-deps
3245
3520
  const returnFocusValueOrFn = returnFocusRef.current;
3246
- const returnElement = getReturnElement();
3521
+ const closeType = closeTypeRef.current;
3522
+ const returnElement = getReturnElement(closeType);
3247
3523
  queueMicrotask(() => {
3248
- // This is `returnElement`, if it's tabbable, or its first tabbable child.
3524
+ // `returnElement` if it is tabbable, otherwise its first tabbable child,
3525
+ // otherwise `returnElement` itself (which may not be tabbable at all).
3249
3526
  const tabbableReturnElement = getFirstTabbableElement(returnElement);
3250
3527
  const hasExplicitReturnFocus = typeof returnFocusValueOrFn !== 'boolean';
3251
- if (returnFocusValueOrFn && !preventReturnFocusRef.current && floatingUi_utils.isHTMLElement(tabbableReturnElement) && (
3528
+ if (returnFocusValueOrFn && !preventReturnFocusRef.current && isHTMLElement(tabbableReturnElement) && (
3252
3529
  // If the focus moved somewhere else after mount, avoid returning focus
3253
3530
  // since it likely entered a different element which should be
3254
3531
  // respected: https://github.com/floating-ui/floating-ui/issues/2607
3255
3532
  !hasExplicitReturnFocus && tabbableReturnElement !== activeEl && activeEl !== doc.body ? isFocusInsideFloatingTree : true)) {
3256
- tabbableReturnElement.focus({
3533
+ const focusOptions = {
3257
3534
  preventScroll: true
3258
- });
3535
+ };
3536
+ if (closeType === 'keyboard') {
3537
+ focusOptions.focusVisible = true;
3538
+ }
3539
+ tabbableReturnElement.focus(focusOptions);
3259
3540
  }
3260
3541
  preventReturnFocusRef.current = false;
3261
3542
  });
@@ -3270,7 +3551,7 @@ function FloatingFocusManager(props) {
3270
3551
  return;
3271
3552
  }
3272
3553
  const activeEl = activeElement(ownerDocument(floating));
3273
- if (!floatingUi_utils.isHTMLElement(activeEl) || !isTypeableElement(activeEl)) {
3554
+ if (!isHTMLElement(activeEl) || !isTypeableElement(activeEl)) {
3274
3555
  return;
3275
3556
  }
3276
3557
  if (contains(floating, activeEl)) {
@@ -3278,8 +3559,8 @@ function FloatingFocusManager(props) {
3278
3559
  }
3279
3560
  }, [open, floating]);
3280
3561
 
3281
- // Synchronize the `context` & `modal` value to the FloatingPortal context.
3282
- // It will decide whether or not it needs to render its own guards.
3562
+ // Synchronize the focus manager state (modal, closeOnFocusOut, open, etc.) to the
3563
+ // FloatingPortal context, which uses it to decide whether to render its own guards.
3283
3564
  useIsoLayoutEffect(() => {
3284
3565
  if (disabled || !portalContext) {
3285
3566
  return undefined;
@@ -3314,7 +3595,8 @@ function FloatingFocusManager(props) {
3314
3595
  onFocus: event => {
3315
3596
  if (modal) {
3316
3597
  const els = getTabbableContent();
3317
- enqueueFocus(els[els.length - 1]);
3598
+ // enqueueFocus returns a rAF-cancel function we don't need here.
3599
+ void enqueueFocus(els[els.length - 1]);
3318
3600
  } else if (portalContext?.portalNode) {
3319
3601
  preventReturnFocusRef.current = false;
3320
3602
  if (isOutsideEvent(event, portalContext.portalNode)) {
@@ -3330,7 +3612,8 @@ function FloatingFocusManager(props) {
3330
3612
  ref: mergedAfterGuardRef,
3331
3613
  onFocus: event => {
3332
3614
  if (modal) {
3333
- enqueueFocus(getTabbableContent()[0]);
3615
+ // enqueueFocus returns a rAF-cancel function we don't need here.
3616
+ void enqueueFocus(getTabbableContent()[0]);
3334
3617
  } else if (portalContext?.portalNode) {
3335
3618
  if (closeOnFocusOut) {
3336
3619
  preventReturnFocusRef.current = true;
@@ -3402,7 +3685,11 @@ function useClick(context, props = {}) {
3402
3685
  }
3403
3686
  return {
3404
3687
  onPointerDown(event) {
3405
- pointerTypeRef.current = event.pointerType;
3688
+ // Screen reader activations (Android TalkBack, desktop screen readers) report a
3689
+ // mouse-like `pointerType`, but `ignoreMouse` must not drop them: hover logic cannot
3690
+ // open for a virtual press since there is no real pointer movement to wait for.
3691
+ // Virtual `touch` presses (iOS VoiceOver) keep their type so `touchOpenDelay` applies.
3692
+ pointerTypeRef.current = isMouseLikePointerType(event.pointerType, true) && isVirtualPointerEvent(event.nativeEvent) ? 'virtual' : event.pointerType;
3406
3693
  },
3407
3694
  onMouseDown(event) {
3408
3695
  const pointerType = pointerTypeRef.current;
@@ -3582,7 +3869,7 @@ function useDismiss(context, props = {}) {
3582
3869
  });
3583
3870
  React__namespace.useEffect(() => {
3584
3871
  if (!open || !enabled) {
3585
- return undefined;
3872
+ return clearInsideReactTree;
3586
3873
  }
3587
3874
  dataRef.current.__escapeKeyBubbles = escapeKeyBubbles;
3588
3875
  dataRef.current.__outsidePressBubbles = outsidePressBubbles;
@@ -3651,18 +3938,18 @@ function useDismiss(context, props = {}) {
3651
3938
  }
3652
3939
  const target = getTarget(event);
3653
3940
  const inertSelector = `[${createAttribute('inert')}]`;
3654
- const targetRoot = floatingUi_utils.isElement(target) ? target.getRootNode() : null;
3655
- const markers = Array.from((floatingUi_utils.isShadowRoot(targetRoot) ? targetRoot : ownerDocument(store.select('floatingElement'))).querySelectorAll(inertSelector));
3941
+ const targetRoot = isElement(target) ? target.getRootNode() : null;
3942
+ const markers = Array.from((isShadowRoot(targetRoot) ? targetRoot : ownerDocument(store.select('floatingElement'))).querySelectorAll(inertSelector));
3656
3943
  const triggers = store.context.triggerElements;
3657
3944
 
3658
3945
  // If another trigger is clicked, don't close the floating element.
3659
3946
  if (target && (triggers.hasElement(target) || triggers.hasMatchingElement(trigger => contains(trigger, target)))) {
3660
3947
  return;
3661
3948
  }
3662
- let targetRootAncestor = floatingUi_utils.isElement(target) ? target : null;
3663
- while (targetRootAncestor && !floatingUi_utils.isLastTraversableNode(targetRootAncestor)) {
3664
- const nextParent = floatingUi_utils.getParentNode(targetRootAncestor);
3665
- if (floatingUi_utils.isLastTraversableNode(nextParent) || !floatingUi_utils.isElement(nextParent)) {
3949
+ let targetRootAncestor = isElement(target) ? target : null;
3950
+ while (targetRootAncestor && !isLastTraversableNode(targetRootAncestor)) {
3951
+ const nextParent = getParentNode(targetRootAncestor);
3952
+ if (isLastTraversableNode(nextParent) || !isElement(nextParent)) {
3666
3953
  break;
3667
3954
  }
3668
3955
  targetRootAncestor = nextParent;
@@ -3670,7 +3957,7 @@ function useDismiss(context, props = {}) {
3670
3957
 
3671
3958
  // Check if the click occurred on a third-party element injected after the
3672
3959
  // floating element rendered.
3673
- if (markers.length && floatingUi_utils.isElement(target) && !isRootElement(target) &&
3960
+ if (markers.length && isElement(target) && !isRootElement(target) &&
3674
3961
  // Clicked on a direct ancestor (e.g. FloatingOverlay).
3675
3962
  !contains(target, store.select('floatingElement')) &&
3676
3963
  // If the target root element contains none of the markers, then the
@@ -3681,9 +3968,9 @@ function useDismiss(context, props = {}) {
3681
3968
 
3682
3969
  // Check if the click occurred on the scrollbar
3683
3970
  // Skip for touch events: scrollbars don't receive touch events on most platforms
3684
- if (floatingUi_utils.isHTMLElement(target) && !('touches' in event)) {
3685
- const lastTraversableNode = floatingUi_utils.isLastTraversableNode(target);
3686
- const style = floatingUi_utils.getComputedStyle(target);
3971
+ if (isHTMLElement(target) && !('touches' in event)) {
3972
+ const lastTraversableNode = isLastTraversableNode(target);
3973
+ const style = getComputedStyle(target);
3687
3974
  const scrollRe = /auto|scroll/;
3688
3975
  const isScrollableX = lastTraversableNode || scrollRe.test(style.overflowX);
3689
3976
  const isScrollableY = lastTraversableNode || scrollRe.test(style.overflowY);
@@ -3860,9 +4147,9 @@ function useDismiss(context, props = {}) {
3860
4147
  preventedPressSuppressionTimeout.clear();
3861
4148
  resetPressStartState();
3862
4149
  suppressNextOutsideClickRef.current = false;
4150
+ clearInsideReactTree();
3863
4151
  };
3864
4152
  }, [dataRef, floatingElement, escapeKey$1, outsidePressEnabled, outsidePress$1, open, enabled, escapeKeyBubbles, outsidePressBubbles, closeOnEscapeKeyDown, clearInsideReactTree, getOutsidePressEventProp, hasBlockingChild, isEventWithinOwnElements, tree, store, cancelDismissOnEndTimeout]);
3865
- React__namespace.useEffect(clearInsideReactTree, [outsidePress$1, clearInsideReactTree]);
3866
4153
  const reference = React__namespace.useMemo(() => ({
3867
4154
  onKeyDown: closeOnEscapeKeyDown,
3868
4155
  onPointerDown: closeOnReferencePress,
@@ -3896,12 +4183,12 @@ function useDismiss(context, props = {}) {
3896
4183
  }
3897
4184
 
3898
4185
  const selectors = {
3899
- open: createSelector(state => state.open),
3900
- transitionStatus: createSelector(state => state.transitionStatus),
3901
- domReferenceElement: createSelector(state => state.domReferenceElement),
3902
- referenceElement: createSelector(state => state.positionReference ?? state.referenceElement),
3903
- floatingElement: createSelector(state => state.floatingElement),
3904
- floatingId: createSelector(state => state.floatingId)
4186
+ open: state => state.open,
4187
+ transitionStatus: state => state.transitionStatus,
4188
+ domReferenceElement: state => state.domReferenceElement,
4189
+ referenceElement: state => state.positionReference ?? state.referenceElement,
4190
+ floatingElement: state => state.floatingElement,
4191
+ floatingId: state => state.floatingId
3905
4192
  };
3906
4193
  class FloatingRootStore extends ReactStore {
3907
4194
  constructor(options) {
@@ -4012,7 +4299,7 @@ function useSyncedFloatingRootContext(options) {
4012
4299
  referenceElement,
4013
4300
  floatingElement
4014
4301
  };
4015
- if (floatingUi_utils.isElement(referenceElement)) {
4302
+ if (isElement(referenceElement)) {
4016
4303
  valuesToSync.domReferenceElement = referenceElement;
4017
4304
  }
4018
4305
  if (store.state.positionReference === store.state.referenceElement) {
@@ -4090,44 +4377,14 @@ function useTransitionStatus(open, enableIdleState = false, deferEndingState = f
4090
4377
  };
4091
4378
  }
4092
4379
 
4093
- let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
4094
- /**
4095
- * Present when the component is animating in.
4096
- */
4097
- TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
4098
- /**
4099
- * Present when the component is animating out.
4100
- */
4101
- TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
4102
- return TransitionStatusDataAttributes;
4103
- }({});
4104
- const STARTING_HOOK = {
4105
- [TransitionStatusDataAttributes.startingStyle]: ''
4106
- };
4107
- const ENDING_HOOK = {
4108
- [TransitionStatusDataAttributes.endingStyle]: ''
4109
- };
4110
- const transitionStatusMapping = {
4111
- transitionStatus(value) {
4112
- if (value === 'starting') {
4113
- return STARTING_HOOK;
4114
- }
4115
- if (value === 'ending') {
4116
- return ENDING_HOOK;
4117
- }
4118
- return null;
4119
- }
4120
- };
4121
-
4122
4380
  /**
4123
4381
  * Executes a function once all animations have finished on the provided element.
4382
+ * If an animation is canceled, waits for any replacement animations before executing.
4124
4383
  * @param elementOrRef - The element to watch for animations.
4125
4384
  * @param waitForStartingStyleRemoved - Whether to wait for [data-starting-style] to be removed before checking for animations.
4126
- * @param treatAbortedAsFinished - Whether to treat aborted animations as finished. If `false`, and there are aborted animations,
4127
- * the function will check again if any new animations have started and wait for them to finish.
4128
4385
  * @returns A function that takes a callback to execute once all animations have finished, and an optional AbortSignal to abort the callback
4129
4386
  */
4130
- function useAnimationsFinished(elementOrRef, waitForStartingStyleRemoved = false, treatAbortedAsFinished = true) {
4387
+ function useAnimationsFinished(elementOrRef, waitForStartingStyleRemoved = false) {
4131
4388
  const frame = useAnimationFrame();
4132
4389
  return useStableCallback((fnToExecute,
4133
4390
  /**
@@ -4156,23 +4413,22 @@ function useAnimationsFinished(elementOrRef, waitForStartingStyleRemoved = false
4156
4413
  if (!signal?.aborted) {
4157
4414
  done();
4158
4415
  }
4159
- }).catch(() => {
4160
- if (treatAbortedAsFinished) {
4161
- if (!signal?.aborted) {
4162
- done();
4163
- }
4416
+ }, () => {
4417
+ if (signal?.aborted) {
4164
4418
  return;
4165
4419
  }
4166
4420
  const currentAnimations = resolvedElement.getAnimations();
4167
- if (!signal?.aborted && currentAnimations.length > 0 && currentAnimations.some(animation => animation.pending || animation.playState !== 'finished')) {
4421
+ if (currentAnimations.some(animation => animation.pending || animation.playState !== 'finished')) {
4168
4422
  // Sometimes animations can be aborted because a property they depend on changes while the animation plays.
4169
4423
  // In such cases, we need to re-check if any new animations have started.
4170
4424
  exec();
4425
+ return;
4171
4426
  }
4427
+ done();
4172
4428
  });
4173
4429
  }
4174
4430
  if (waitForStartingStyleRemoved) {
4175
- const startingStyleAttribute = TransitionStatusDataAttributes.startingStyle;
4431
+ const startingStyleAttribute = 'data-starting-style';
4176
4432
 
4177
4433
  // If `[data-starting-style]` isn't present, fall back to waiting one more frame
4178
4434
  // to give "open" animations a chance to be registered.
@@ -4212,7 +4468,7 @@ function useOpenChangeComplete(parameters) {
4212
4468
  onComplete: onCompleteParam
4213
4469
  } = parameters;
4214
4470
  const onComplete = useStableCallback(onCompleteParam);
4215
- const runOnceAnimationsFinish = useAnimationsFinished(ref, open, false);
4471
+ const runOnceAnimationsFinish = useAnimationsFinished(ref, open);
4216
4472
  React__namespace.useEffect(() => {
4217
4473
  if (!enabled) {
4218
4474
  return undefined;
@@ -4238,14 +4494,26 @@ const FOCUSABLE_POPUP_PROPS = {
4238
4494
  function createDefaultInitialFocus(popupRef) {
4239
4495
  return interactionType => interactionType === 'touch' ? popupRef.current : true;
4240
4496
  }
4241
- function usePopupStore(externalStore, createStore, treatPopupAsFloatingElement = false) {
4497
+
4498
+ /**
4499
+ * The subset of a popup handle that a Root needs to bind its store to. Both the real handle classes
4500
+ * and any test double satisfy it.
4501
+ */
4502
+
4503
+ /**
4504
+ * Creates and owns a popup store on behalf of a Root part. The store is created exactly once, with
4505
+ * controlled props and root state synced separately after creation. Sets up the synced floating
4506
+ * root context and returns the store.
4507
+ *
4508
+ * @param createStore Factory that builds the store. Called exactly once, receiving the floating id
4509
+ * and whether the popup is nested inside another floating element, both resolved on the first render.
4510
+ * @param treatPopupAsFloatingElement Whether the popup element is passed to Floating UI as the
4511
+ * floating element instead of the default positioner.
4512
+ */
4513
+ function usePopupRootStore(createStore, treatPopupAsFloatingElement = false) {
4242
4514
  const floatingId = useId();
4243
4515
  const nested = useFloatingParentNodeId() != null;
4244
- const internalStoreRef = React__namespace.useRef(null);
4245
- if (externalStore === undefined && internalStoreRef.current === null) {
4246
- internalStoreRef.current = createStore(floatingId, nested);
4247
- }
4248
- const store = externalStore ?? internalStoreRef.current;
4516
+ const store = useRenderElement.useRefWithInit(() => createStore(floatingId, nested)).current;
4249
4517
  useSyncedFloatingRootContext({
4250
4518
  popupStore: store,
4251
4519
  treatPopupAsFloatingElement,
@@ -4254,10 +4522,27 @@ function usePopupStore(externalStore, createStore, treatPopupAsFloatingElement =
4254
4522
  nested,
4255
4523
  onOpenChange: store.setOpen
4256
4524
  });
4257
- return {
4258
- store,
4259
- internalStore: internalStoreRef.current
4260
- };
4525
+ return store;
4526
+ }
4527
+
4528
+ /**
4529
+ * Attaches a Root's store to a handle for this component's committed lifetime. Popup Roots render
4530
+ * it before their interactions and user children so its layout effect runs before descendant layout
4531
+ * effects. This lets descendants call the handle during the Root's initial commit without attaching
4532
+ * during render, which would leak suspended or abandoned stores. Store subscribers are notified by
4533
+ * `attachStore` in this ordinary layout phase, where React permits synchronous updates.
4534
+ *
4535
+ * Popup Roots must render this component only when a handle is present so handle-less Roots avoid
4536
+ * mounting an extra fiber and layout effect.
4537
+ */
4538
+ function PopupHandleAttachment({
4539
+ handle,
4540
+ store
4541
+ }) {
4542
+ useIsoLayoutEffect(() => {
4543
+ return handle.attachStore(store);
4544
+ }, [handle, store]);
4545
+ return null;
4261
4546
  }
4262
4547
 
4263
4548
  /**
@@ -4322,19 +4607,6 @@ function attachPreventUnmountOnClose(eventDetails) {
4322
4607
  };
4323
4608
  return () => preventUnmountOnClose;
4324
4609
  }
4325
- function useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerId) {
4326
- useOnFirstRender(() => {
4327
- if (openProp === undefined && store.state.open === false && defaultOpen) {
4328
- // Avoid notifying detached trigger subscribers while the Root is rendering.
4329
- store.state = {
4330
- ...store.state,
4331
- open: true,
4332
- activeTriggerId: defaultTriggerId,
4333
- preventUnmountingOnClose: false
4334
- };
4335
- }
4336
- });
4337
- }
4338
4610
 
4339
4611
  /**
4340
4612
  * Sets up trigger data forwarding to the store.
@@ -4347,11 +4619,11 @@ function useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerId) {
4347
4619
  function useTriggerDataForwarding(triggerId, triggerElementRef, store, stateUpdates) {
4348
4620
  const isMountedByThisTrigger = store.useState('isMountedByTrigger', triggerId);
4349
4621
  const baseRegisterTrigger = useTriggerRegistration(triggerId, store);
4350
- const registerTrigger = useStableCallback(element => {
4351
- baseRegisterTrigger(element);
4352
- if (!element) {
4353
- return;
4354
- }
4622
+
4623
+ // Applies trigger-owned state (active-trigger ownership and payload) when the trigger registers.
4624
+ // Stable so payload/`stateUpdates` changes do not change the ref identity (which would needlessly
4625
+ // churn registration); it reads the latest closure values when invoked.
4626
+ const applyTriggerData = useStableCallback(element => {
4355
4627
  const open = store.select('open');
4356
4628
  const activeTriggerId = store.select('activeTriggerId');
4357
4629
  if (activeTriggerId === triggerId) {
@@ -4372,6 +4644,18 @@ function useTriggerDataForwarding(triggerId, triggerElementRef, store, stateUpda
4372
4644
  });
4373
4645
  }
4374
4646
  });
4647
+
4648
+ // Intentionally NOT stable. Its identity is derived from `baseRegisterTrigger`, which is keyed on
4649
+ // `[store, id]`, so when a handle-backed trigger's store pointer swaps the merged ref re-fires —
4650
+ // unregistering from the previous store and registering into the new one. This lets a detached
4651
+ // trigger follow its handle's currently-attached store across attach/detach/remount. (A stable
4652
+ // callback would keep its identity and never re-fire on a store swap.)
4653
+ const registerTrigger = React__namespace.useCallback(element => {
4654
+ baseRegisterTrigger(element);
4655
+ if (element) {
4656
+ applyTriggerData(element);
4657
+ }
4658
+ }, [baseRegisterTrigger, applyTriggerData]);
4375
4659
  useIsoLayoutEffect(() => {
4376
4660
  if (isMountedByThisTrigger) {
4377
4661
  store.update({
@@ -4391,12 +4675,17 @@ function useTriggerDataForwarding(triggerId, triggerElementRef, store, stateUpda
4391
4675
  *
4392
4676
  * When a popup opens without an explicit trigger id and exactly one trigger is registered, that
4393
4677
  * trigger is claimed as the active trigger. When the active trigger id is still registered but its
4394
- * element changed, the active element is refreshed. When the active trigger unregisters, the
4395
- * default path preserves existing ownership so non-closing popup families do not silently claim a
4396
- * different trigger while staying open.
4678
+ * element changed, the active element is refreshed. When the active trigger id is missing from the
4679
+ * registry but the same element is still registered under a different id (e.g. the rendered trigger
4680
+ * carries its own DOM `id` that differs from Base UI's internal trigger id), the active id is
4681
+ * reassociated to the registered id instead of being treated as lost. When the active trigger
4682
+ * unregisters, the default path preserves existing ownership so non-closing popup families do not
4683
+ * silently claim a different trigger while staying open.
4397
4684
  *
4398
- * If `closeOnActiveTriggerUnmount` is enabled, unregistering the active trigger requests a close
4399
- * after a microtask so a same-tick replacement trigger with the same id can register first.
4685
+ * If `closeOnActiveTriggerUnmount` is enabled, unregistering a previously resolved active trigger
4686
+ * requests a close after a microtask so a same-tick replacement trigger with the same id can
4687
+ * register first. An active trigger id that has not matched a registered trigger yet is treated as
4688
+ * pending and does not request a close.
4400
4689
  *
4401
4690
  * This should be called on the Root part.
4402
4691
  *
@@ -4407,10 +4696,22 @@ function useImplicitActiveTrigger(store, options = {}) {
4407
4696
  const {
4408
4697
  closeOnActiveTriggerUnmount = false
4409
4698
  } = options;
4699
+ // Distinguishes a trigger that unmounted from a new active trigger that has not hydrated yet.
4700
+ const resolvedActiveTriggerIdRef = React__namespace.useRef(null);
4410
4701
  const open = store.useState('open');
4411
4702
  const reactiveTriggerCount = store.useState('triggerCount');
4703
+ // Subscribe to the active trigger id so the reconciliation below reruns when ownership moves to
4704
+ // another trigger while the popup stays open (e.g. a focus/hover handoff between triggers).
4705
+ const activeTriggerId = store.useState('activeTriggerId');
4706
+ // Subscribe to the active trigger element so the reconciliation reruns when a pending active
4707
+ // trigger registers in a commit where the trigger count nets out unchanged (registration
4708
+ // forwards the element to the store when the registering trigger matches the active id).
4709
+ // Without this, the id would never be marked resolved and a later genuine unmount would be
4710
+ // misclassified as pending, disabling `closeOnActiveTriggerUnmount`.
4711
+ const reactiveActiveTriggerElement = store.useState('activeTriggerElement');
4412
4712
  useIsoLayoutEffect(() => {
4413
4713
  if (!open) {
4714
+ resolvedActiveTriggerIdRef.current = null;
4414
4715
  if (store.state.triggerCount !== 0) {
4415
4716
  store.set('triggerCount', 0);
4416
4717
  }
@@ -4421,22 +4722,42 @@ function useImplicitActiveTrigger(store, options = {}) {
4421
4722
  if (store.state.triggerCount !== triggerCount) {
4422
4723
  stateUpdates.triggerCount = triggerCount;
4423
4724
  }
4424
- const activeTriggerId = store.select('activeTriggerId');
4725
+ const currentActiveTriggerId = store.select('activeTriggerId');
4425
4726
  let lostActiveTriggerId = null;
4426
- if (activeTriggerId) {
4427
- const activeTriggerElement = store.context.triggerElements.getById(activeTriggerId);
4727
+ if (currentActiveTriggerId) {
4728
+ const activeTriggerElement = store.context.triggerElements.getById(currentActiveTriggerId);
4428
4729
  if (!activeTriggerElement) {
4429
- lostActiveTriggerId = activeTriggerId;
4430
- } else if (activeTriggerElement !== store.state.activeTriggerElement) {
4431
- stateUpdates.activeTriggerElement = activeTriggerElement;
4730
+ for (const [triggerId, triggerElement] of store.context.triggerElements.entries()) {
4731
+ if (triggerElement === store.state.activeTriggerElement) {
4732
+ stateUpdates.activeTriggerId = triggerId;
4733
+ stateUpdates.activeTriggerElement = triggerElement;
4734
+ resolvedActiveTriggerIdRef.current = triggerId;
4735
+ break;
4736
+ }
4737
+ }
4738
+ if (stateUpdates.activeTriggerId === undefined) {
4739
+ if (resolvedActiveTriggerIdRef.current === currentActiveTriggerId) {
4740
+ lostActiveTriggerId = currentActiveTriggerId;
4741
+ } else {
4742
+ resolvedActiveTriggerIdRef.current = null;
4743
+ }
4744
+ }
4745
+ } else {
4746
+ resolvedActiveTriggerIdRef.current = currentActiveTriggerId;
4747
+ if (activeTriggerElement !== store.state.activeTriggerElement) {
4748
+ stateUpdates.activeTriggerElement = activeTriggerElement;
4749
+ }
4432
4750
  }
4751
+ } else {
4752
+ resolvedActiveTriggerIdRef.current = null;
4433
4753
  }
4434
- if (!lostActiveTriggerId && !activeTriggerId && triggerCount === 1) {
4754
+ if (!lostActiveTriggerId && !currentActiveTriggerId && triggerCount === 1) {
4435
4755
  const iteratorResult = store.context.triggerElements.entries().next();
4436
4756
  if (!iteratorResult.done) {
4437
4757
  const [implicitTriggerId, implicitTriggerElement] = iteratorResult.value;
4438
4758
  stateUpdates.activeTriggerId = implicitTriggerId;
4439
4759
  stateUpdates.activeTriggerElement = implicitTriggerElement;
4760
+ resolvedActiveTriggerIdRef.current = implicitTriggerId;
4440
4761
  }
4441
4762
  }
4442
4763
  if (stateUpdates.triggerCount !== undefined || stateUpdates.activeTriggerId !== undefined || stateUpdates.activeTriggerElement !== undefined) {
@@ -4461,7 +4782,7 @@ function useImplicitActiveTrigger(store, options = {}) {
4461
4782
  });
4462
4783
  }
4463
4784
  }
4464
- }, [open, store, reactiveTriggerCount, closeOnActiveTriggerUnmount]);
4785
+ }, [open, store, reactiveTriggerCount, activeTriggerId, reactiveActiveTriggerElement, closeOnActiveTriggerUnmount]);
4465
4786
  }
4466
4787
 
4467
4788
  /**
@@ -4539,13 +4860,33 @@ function usePopupRootSync(store, open) {
4539
4860
  }, [store]);
4540
4861
  }
4541
4862
 
4863
+ /**
4864
+ * Development-only reverse index of element to registered id, keyed by the owning map.
4865
+ *
4866
+ * Registration would otherwise have to scan every entry to detect an element claimed by two ids,
4867
+ * making the mount of many triggers sharing one handle quadratic. Kept module-scoped, lazily
4868
+ * initialized, and read only from `process.env.NODE_ENV` guards so production builds drop it along
4869
+ * with the checks.
4870
+ */
4871
+ let devElementIdsByMap;
4872
+ function getDevElementIds(map) {
4873
+ devElementIdsByMap ??= new WeakMap();
4874
+ let elementIds = devElementIdsByMap.get(map);
4875
+ if (!elementIds) {
4876
+ elementIds = new WeakMap();
4877
+ devElementIdsByMap.set(map, elementIds);
4878
+ }
4879
+ return elementIds;
4880
+ }
4881
+
4542
4882
  /**
4543
4883
  * Data structure to keep track of popup trigger elements by their IDs.
4544
- * Uses both a set of Elements and a map of IDs to Elements for efficient lookups.
4884
+ *
4885
+ * Element lookups iterate the id map rather than maintaining a parallel Set. Registration is O(1),
4886
+ * while `hasElement` and `hasMatchingElement` are linear in the number of triggers.
4545
4887
  */
4546
4888
  class PopupTriggerMap {
4547
4889
  constructor() {
4548
- this.elementsSet = new Set();
4549
4890
  this.idMap = new Map();
4550
4891
  }
4551
4892
 
@@ -4555,49 +4896,56 @@ class PopupTriggerMap {
4555
4896
  * Note: The provided element is assumed to not be registered under multiple IDs.
4556
4897
  */
4557
4898
  add(id, element) {
4558
- const existingElement = this.idMap.get(id);
4559
- if (existingElement === element) {
4560
- return;
4561
- }
4562
- if (existingElement !== undefined) {
4563
- // We assume that the same element won't be registered under multiple ids.
4564
- // This is safe considering how useTriggerRegistration is implemented.
4565
- this.elementsSet.delete(existingElement);
4566
- }
4567
- this.elementsSet.add(element);
4568
- this.idMap.set(id, element);
4569
4899
  if (process.env.NODE_ENV !== 'production') {
4570
- if (this.elementsSet.size !== this.idMap.size) {
4900
+ const elementIds = getDevElementIds(this);
4901
+ const existingId = elementIds.get(element);
4902
+ if (existingId !== undefined && existingId !== id) {
4571
4903
  // TODO: fix mui/no-guarded-throw
4572
4904
  // eslint-disable-next-line mui/no-guarded-throw
4573
4905
  throw new Error('Base UI: A trigger element cannot be registered under multiple IDs in PopupTriggerMap.');
4574
4906
  }
4907
+
4908
+ // Reusing an id for a different element evicts the previous one, so it must lose its claim
4909
+ // on the id or a later registration under a different id would be reported as a duplicate.
4910
+ const previousElement = this.idMap.get(id);
4911
+ if (previousElement !== undefined && previousElement !== element) {
4912
+ elementIds.delete(previousElement);
4913
+ }
4914
+ elementIds.set(element, id);
4575
4915
  }
4916
+ this.idMap.set(id, element);
4576
4917
  }
4577
4918
 
4578
4919
  /**
4579
4920
  * Removes the trigger element with the given ID.
4580
4921
  */
4581
4922
  delete(id) {
4582
- const element = this.idMap.get(id);
4583
- if (element) {
4584
- this.elementsSet.delete(element);
4585
- this.idMap.delete(id);
4923
+ if (process.env.NODE_ENV !== 'production') {
4924
+ const element = this.idMap.get(id);
4925
+ if (element !== undefined) {
4926
+ devElementIdsByMap?.get(this)?.delete(element);
4927
+ }
4586
4928
  }
4929
+ this.idMap.delete(id);
4587
4930
  }
4588
4931
 
4589
4932
  /**
4590
4933
  * Whether the given element is registered as a trigger.
4591
4934
  */
4592
4935
  hasElement(element) {
4593
- return this.elementsSet.has(element);
4936
+ for (const registered of this.idMap.values()) {
4937
+ if (registered === element) {
4938
+ return true;
4939
+ }
4940
+ }
4941
+ return false;
4594
4942
  }
4595
4943
 
4596
4944
  /**
4597
4945
  * Whether there is a registered trigger element matching the given predicate.
4598
4946
  */
4599
4947
  hasMatchingElement(predicate) {
4600
- for (const element of this.elementsSet) {
4948
+ for (const element of this.idMap.values()) {
4601
4949
  if (predicate(element)) {
4602
4950
  return true;
4603
4951
  }
@@ -4623,7 +4971,7 @@ class PopupTriggerMap {
4623
4971
  * Returns an iterable of all registered trigger elements.
4624
4972
  */
4625
4973
  elements() {
4626
- return this.elementsSet.values();
4974
+ return this.idMap.values();
4627
4975
  }
4628
4976
 
4629
4977
  /**
@@ -4683,14 +5031,11 @@ function useValueChanged(value, onChange) {
4683
5031
  const valueRef = React__namespace.useRef(value);
4684
5032
  const onChangeCallback = useStableCallback(onChange);
4685
5033
  useIsoLayoutEffect(() => {
4686
- if (valueRef.current === value) {
4687
- return;
5034
+ if (valueRef.current !== value) {
5035
+ onChangeCallback(valueRef.current);
4688
5036
  }
4689
- onChangeCallback(valueRef.current);
4690
- }, [value, onChangeCallback]);
4691
- useIsoLayoutEffect(() => {
4692
5037
  valueRef.current = value;
4693
- }, [value]);
5038
+ }, [value, onChangeCallback]);
4694
5039
  }
4695
5040
 
4696
5041
  function useOpenMethodTriggerProps(open, setOpenMethod) {
@@ -4733,9 +5078,6 @@ function useOpenInteractionType(open) {
4733
5078
  }), [openMethod, triggerProps]);
4734
5079
  }
4735
5080
 
4736
- /**
4737
- * @internal
4738
- */
4739
5081
  const DirectionContext = /*#__PURE__*/React__namespace.createContext(undefined);
4740
5082
  if (process.env.NODE_ENV !== "production") DirectionContext.displayName = "DirectionContext";
4741
5083
  function useDirection() {
@@ -4794,6 +5136,30 @@ function useFocusableWhenDisabled(parameters) {
4794
5136
  };
4795
5137
  }
4796
5138
 
5139
+ /**
5140
+ * Dispatches a constructed click on the target so it carries the source event's
5141
+ * modifier state, which `click()` always reports as unpressed. Like `click()`,
5142
+ * the untrusted click still runs native activation behavior (form submission,
5143
+ * link navigation).
5144
+ * `detail` defaults to 0 (the native convention for keyboard-generated clicks);
5145
+ * pass `detail: 1` when the click represents a mouse gesture so consumers keying
5146
+ * off `detail === 0` don't classify it as a keyboard activation.
5147
+ */
5148
+ function dispatchClickWithModifiers(target, sourceEvent, {
5149
+ detail = 0
5150
+ } = {}) {
5151
+ target.dispatchEvent(new (getWindow(target).PointerEvent)('click', {
5152
+ bubbles: true,
5153
+ cancelable: true,
5154
+ composed: true,
5155
+ detail,
5156
+ shiftKey: sourceEvent.shiftKey,
5157
+ ctrlKey: sourceEvent.ctrlKey,
5158
+ altKey: sourceEvent.altKey,
5159
+ metaKey: sourceEvent.metaKey
5160
+ }));
5161
+ }
5162
+
4797
5163
  function useButton(parameters = {}) {
4798
5164
  const {
4799
5165
  disabled = false,
@@ -4894,24 +5260,34 @@ function useButton(parameters = {}) {
4894
5260
  return;
4895
5261
  }
4896
5262
  event.preventDefault();
4897
- if (isLink || isNativeButton && isButton) {
4898
- currentTarget.click();
4899
- event.preventBaseUIHandler();
4900
- } else if (shouldClick) {
4901
- externalOnClick?.(event);
5263
+
5264
+ // Only a native-mode item that isn't a real <button> is excluded.
5265
+ if (!isNativeButton || isButton) {
4902
5266
  event.preventBaseUIHandler();
5267
+ dispatchClickWithModifiers(currentTarget, event);
4903
5268
  }
4904
5269
  return;
4905
5270
  }
4906
5271
 
4907
5272
  // Keyboard accessibility for native and non-native elements.
4908
- if (shouldClick) {
4909
- if (!isNativeButton && (isSpaceKey || isEnterKey)) {
5273
+ if (!shouldClick || isNativeButton || !isSpaceKey && !isEnterKey) {
5274
+ // Space activates links on keyup (`role="button"` semantics, matching the
5275
+ // composite path); prevent the page scroll Space would otherwise trigger.
5276
+ // Enter is left to the browser's native link activation.
5277
+ if (isCurrentTarget && isLink && isSpaceKey) {
4910
5278
  event.preventDefault();
4911
5279
  }
4912
- if (!isNativeButton && isEnterKey) {
4913
- externalOnClick?.(event);
4914
- }
5280
+ return;
5281
+ }
5282
+
5283
+ // Match native buttons: preventing the keydown's default cancels activation.
5284
+ if (event.defaultPrevented) {
5285
+ return;
5286
+ }
5287
+ event.preventDefault();
5288
+ if (isEnterKey) {
5289
+ event.preventBaseUIHandler();
5290
+ dispatchClickWithModifiers(currentTarget, event);
4915
5291
  }
4916
5292
  },
4917
5293
  onKeyUp(event) {
@@ -4931,9 +5307,14 @@ function useButton(parameters = {}) {
4931
5307
  return;
4932
5308
  }
4933
5309
 
4934
- // Keyboard accessibility for non interactive elements
4935
- if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && event.key === ' ') {
4936
- externalOnClick?.(event);
5310
+ // Keyboard accessibility for non interactive elements.
5311
+ // Match native buttons: preventing the keyup's default cancels Space activation.
5312
+ // Limitation: unlike a native <button>, a prevented *keydown* cannot cancel the
5313
+ // activation — no state is kept between keydown and keyup, so we can't tell
5314
+ // whether the keydown was prevented or even happened on this element.
5315
+ if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && !event.defaultPrevented && event.key === ' ') {
5316
+ event.preventBaseUIHandler();
5317
+ dispatchClickWithModifiers(event.currentTarget, event);
4937
5318
  }
4938
5319
  },
4939
5320
  onPointerDown(event) {
@@ -4959,12 +5340,41 @@ function useButton(parameters = {}) {
4959
5340
  };
4960
5341
  }
4961
5342
  function isButtonElement(elem) {
4962
- return floatingUi_utils.isHTMLElement(elem) && elem.tagName === 'BUTTON';
5343
+ return isHTMLElement(elem) && elem.tagName === 'BUTTON';
4963
5344
  }
4964
5345
  function isValidLinkElement(elem) {
4965
- return Boolean(elem?.tagName === 'A' && elem?.href);
5346
+ return isHTMLElement(elem) && elem.tagName === 'A' && Boolean(elem.href);
4966
5347
  }
4967
5348
 
5349
+ let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
5350
+ /**
5351
+ * Present when the component begins animating in.
5352
+ */
5353
+ TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
5354
+ /**
5355
+ * Present when the component is animating out.
5356
+ */
5357
+ TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
5358
+ return TransitionStatusDataAttributes;
5359
+ }({});
5360
+ const STARTING_HOOK = {
5361
+ 'data-starting-style': ''
5362
+ };
5363
+ const ENDING_HOOK = {
5364
+ 'data-ending-style': ''
5365
+ };
5366
+ const transitionStatusMapping = {
5367
+ transitionStatus(value) {
5368
+ if (value === 'starting') {
5369
+ return STARTING_HOOK;
5370
+ }
5371
+ if (value === 'ending') {
5372
+ return ENDING_HOOK;
5373
+ }
5374
+ return null;
5375
+ }
5376
+ };
5377
+
4968
5378
  let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
4969
5379
  /**
4970
5380
  * Present when the popup is open.
@@ -4975,7 +5385,7 @@ let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
4975
5385
  */
4976
5386
  CommonPopupDataAttributes["closed"] = "data-closed";
4977
5387
  /**
4978
- * Present when the popup is animating in.
5388
+ * Present when the popup begins animating in.
4979
5389
  */
4980
5390
  CommonPopupDataAttributes[CommonPopupDataAttributes["startingStyle"] = TransitionStatusDataAttributes.startingStyle] = "startingStyle";
4981
5391
  /**
@@ -4998,32 +5408,24 @@ let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
4998
5408
  CommonPopupDataAttributes["align"] = "data-align";
4999
5409
  return CommonPopupDataAttributes;
5000
5410
  }({});
5001
- let CommonTriggerDataAttributes = /*#__PURE__*/function (CommonTriggerDataAttributes) {
5002
- /**
5003
- * Present when the popup is open.
5004
- */
5005
- CommonTriggerDataAttributes["popupOpen"] = "data-popup-open";
5006
- /**
5007
- * Present when a pressable trigger is pressed.
5008
- */
5009
- CommonTriggerDataAttributes["pressed"] = "data-pressed";
5010
- return CommonTriggerDataAttributes;
5011
- }({});
5411
+
5412
+ // Literal keys (instead of enum member references) keep the docs-only enums above
5413
+ // tree-shakeable: a runtime reference would retain the whole enum IIFE in every bundle.
5012
5414
  const TRIGGER_HOOK = {
5013
- [CommonTriggerDataAttributes.popupOpen]: ''
5415
+ 'data-popup-open': ''
5014
5416
  };
5015
5417
  const PRESSABLE_TRIGGER_HOOK = {
5016
- [CommonTriggerDataAttributes.popupOpen]: '',
5017
- [CommonTriggerDataAttributes.pressed]: ''
5418
+ 'data-popup-open': '',
5419
+ 'data-pressed': ''
5018
5420
  };
5019
5421
  const POPUP_OPEN_HOOK = {
5020
- [CommonPopupDataAttributes.open]: ''
5422
+ 'data-open': ''
5021
5423
  };
5022
5424
  const POPUP_CLOSED_HOOK = {
5023
- [CommonPopupDataAttributes.closed]: ''
5425
+ 'data-closed': ''
5024
5426
  };
5025
5427
  const ANCHOR_HIDDEN_HOOK = {
5026
- [CommonPopupDataAttributes.anchorHidden]: ''
5428
+ 'data-anchor-hidden': ''
5027
5429
  };
5028
5430
  const triggerOpenStateMapping = {
5029
5431
  open(value) {
@@ -5055,6 +5457,10 @@ const popupStateMapping = {
5055
5457
  return null;
5056
5458
  }
5057
5459
  };
5460
+ const popupTransitionStateMapping = {
5461
+ ...popupStateMapping,
5462
+ ...transitionStatusMapping
5463
+ };
5058
5464
 
5059
5465
  function inertValue(value) {
5060
5466
  if (useRenderElement.isReactVersionAtLeast(19)) {
@@ -5096,12 +5502,21 @@ if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "Inter
5096
5502
  let originalHtmlStyles = {};
5097
5503
  let originalBodyStyles = {};
5098
5504
  let originalHtmlScrollBehavior = '';
5505
+
5506
+ // The viewport's overflow comes from <html> when it establishes its own scroll container, and
5507
+ // propagates from <body> otherwise. An `overflow` style on the other element doesn't lock the page.
5508
+ function getViewportScroller(html, body) {
5509
+ return isOverflowElement(html) ? html : body;
5510
+ }
5511
+ function isPageScrollLocked(win, html, body) {
5512
+ return /hidden|clip/.test(win.getComputedStyle(getViewportScroller(html, body)).overflowY);
5513
+ }
5099
5514
  function hasInsetScrollbars(referenceElement) {
5100
5515
  if (typeof document === 'undefined') {
5101
5516
  return false;
5102
5517
  }
5103
5518
  const doc = ownerDocument(referenceElement);
5104
- const win = floatingUi_utils.getWindow(doc);
5519
+ const win = getWindow(doc);
5105
5520
  return win.innerWidth - doc.documentElement.clientWidth > 0;
5106
5521
  }
5107
5522
  function supportsStableScrollbarGutter(referenceElement) {
@@ -5112,7 +5527,7 @@ function supportsStableScrollbarGutter(referenceElement) {
5112
5527
  const doc = ownerDocument(referenceElement);
5113
5528
  const html = doc.documentElement;
5114
5529
  const body = doc.body;
5115
- const scrollContainer = floatingUi_utils.isOverflowElement(html) ? html : body;
5530
+ const scrollContainer = getViewportScroller(html, body);
5116
5531
  const originalScrollContainerOverflowY = scrollContainer.style.overflowY;
5117
5532
  const originalHtmlStyleGutter = html.style.scrollbarGutter;
5118
5533
  html.style.scrollbarGutter = 'stable';
@@ -5133,7 +5548,7 @@ function preventScrollOverlayScrollbars(referenceElement) {
5133
5548
  // won't have any effect.
5134
5549
  // But if <body> has an `overflow` style (like `overflow-x: hidden`), we need to lock it
5135
5550
  // instead, as sticky elements shift otherwise.
5136
- const elementToLock = floatingUi_utils.isOverflowElement(html) ? html : body;
5551
+ const elementToLock = getViewportScroller(html, body);
5137
5552
  const originalElementToLockStyles = {
5138
5553
  overflowY: elementToLock.style.overflowY,
5139
5554
  overflowX: elementToLock.style.overflowX
@@ -5150,7 +5565,7 @@ function preventScrollInsetScrollbars(referenceElement) {
5150
5565
  const doc = ownerDocument(referenceElement);
5151
5566
  const html = doc.documentElement;
5152
5567
  const body = doc.body;
5153
- const win = floatingUi_utils.getWindow(html);
5568
+ const win = getWindow(html);
5154
5569
  let scrollTop = 0;
5155
5570
  let scrollLeft = 0;
5156
5571
  let updateGutterOnly = false;
@@ -5198,7 +5613,7 @@ function preventScrollInsetScrollbars(referenceElement) {
5198
5613
  // with whitespace. Warn if <body> has margins?
5199
5614
  const marginY = parseFloat(bodyStyles.marginTop) + parseFloat(bodyStyles.marginBottom);
5200
5615
  const marginX = parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight);
5201
- const elementToLock = floatingUi_utils.isOverflowElement(html) ? html : body;
5616
+ const elementToLock = getViewportScroller(html, body);
5202
5617
  updateGutterOnly = supportsStableScrollbarGutter(referenceElement);
5203
5618
 
5204
5619
  /*
@@ -5228,7 +5643,9 @@ function preventScrollInsetScrollbars(referenceElement) {
5228
5643
  height: marginY || scrollbarHeight ? `calc(100dvh - ${marginY + scrollbarHeight}px)` : '100dvh',
5229
5644
  width: marginX || scrollbarWidth ? `calc(100vw - ${marginX + scrollbarWidth}px)` : '100vw',
5230
5645
  boxSizing: 'border-box',
5231
- overflow: 'hidden',
5646
+ // Assign the longhands that `cleanup` restores, so nothing is left behind.
5647
+ overflowY: 'hidden',
5648
+ overflowX: 'hidden',
5232
5649
  scrollBehavior: 'unset'
5233
5650
  });
5234
5651
  body.scrollTop = scrollTop;
@@ -5293,11 +5710,30 @@ class ScrollLocker {
5293
5710
  }
5294
5711
  const doc = ownerDocument(referenceElement);
5295
5712
  const html = doc.documentElement;
5296
- const htmlOverflowY = floatingUi_utils.getWindow(html).getComputedStyle(html).overflowY;
5713
+ const body = doc.body;
5714
+ const win = getWindow(html);
5715
+
5716
+ // The page is already locked, either by the site author or by a non-Base UI overlay that
5717
+ // hasn't cleaned up yet. Leave it alone and wait for the lock to clear before taking over,
5718
+ // otherwise we'd snapshot the locked state and restore it after our own lock is released.
5719
+ if (isPageScrollLocked(win, html, body)) {
5720
+ const observer = new win.MutationObserver(() => {
5721
+ if (isPageScrollLocked(win, html, body)) {
5722
+ return;
5723
+ }
5724
+ observer.disconnect();
5725
+ this.restore = null;
5726
+ this.lock(referenceElement);
5727
+ });
5297
5728
 
5298
- // If the site author already hid overflow on <html>, respect it and bail out.
5299
- if (htmlOverflowY === 'hidden' || htmlOverflowY === 'clip') {
5300
- this.restore = useRenderElement.NOOP;
5729
+ // Watch every attribute: locks are applied through inline styles, classes, or attributes
5730
+ // paired with a stylesheet (`data-scroll-locked` in react-remove-scroll, for example).
5731
+ const options = {
5732
+ attributes: true
5733
+ };
5734
+ observer.observe(html, options);
5735
+ observer.observe(body, options);
5736
+ this.restore = () => observer.disconnect();
5301
5737
  return;
5302
5738
  }
5303
5739
  const hasOverlayScrollbars = ios || !hasInsetScrollbars(referenceElement);
@@ -5332,6 +5768,7 @@ exports.ARROW_DOWN = ARROW_DOWN;
5332
5768
  exports.ARROW_LEFT = ARROW_LEFT;
5333
5769
  exports.ARROW_RIGHT = ARROW_RIGHT;
5334
5770
  exports.ARROW_UP = ARROW_UP;
5771
+ exports.AnimationFrame = AnimationFrame;
5335
5772
  exports.BASE_UI_SWIPE_IGNORE_SELECTOR = BASE_UI_SWIPE_IGNORE_SELECTOR;
5336
5773
  exports.CLICK_TRIGGER_IDENTIFIER = CLICK_TRIGGER_IDENTIFIER;
5337
5774
  exports.CommonPopupDataAttributes = CommonPopupDataAttributes;
@@ -5348,6 +5785,7 @@ exports.FocusGuard = FocusGuard;
5348
5785
  exports.InternalBackdrop = InternalBackdrop;
5349
5786
  exports.PATIENT_CLICK_THRESHOLD = PATIENT_CLICK_THRESHOLD;
5350
5787
  exports.POPUP_COLLISION_AVOIDANCE = POPUP_COLLISION_AVOIDANCE;
5788
+ exports.PopupHandleAttachment = PopupHandleAttachment;
5351
5789
  exports.PopupTriggerMap = PopupTriggerMap;
5352
5790
  exports.ReactStore = ReactStore;
5353
5791
  exports.SafeReact = SafeReact;
@@ -5361,33 +5799,57 @@ exports.android = android;
5361
5799
  exports.attachPreventUnmountOnClose = attachPreventUnmountOnClose;
5362
5800
  exports.cancelOpen = cancelOpen;
5363
5801
  exports.chipRemovePress = chipRemovePress;
5802
+ exports.clamp = clamp;
5364
5803
  exports.clearPress = clearPress;
5365
5804
  exports.closePress = closePress;
5366
5805
  exports.closeWatcher = closeWatcher;
5367
5806
  exports.contains = contains;
5368
5807
  exports.createAttribute = createAttribute;
5369
5808
  exports.createChangeEventDetails = createChangeEventDetails;
5809
+ exports.createCoords = createCoords;
5370
5810
  exports.createDefaultInitialFocus = createDefaultInitialFocus;
5371
5811
  exports.createGenericEventDetails = createGenericEventDetails;
5372
- exports.createSelector = createSelector;
5373
5812
  exports.decrementPress = decrementPress;
5813
+ exports.dispatchClickWithModifiers = dispatchClickWithModifiers;
5374
5814
  exports.enqueueFocus = enqueueFocus;
5375
5815
  exports.error = error;
5376
5816
  exports.escapeKey = escapeKey;
5817
+ exports.evaluate = evaluate;
5377
5818
  exports.fastComponent = fastComponent;
5378
5819
  exports.fastComponentRef = fastComponentRef;
5379
5820
  exports.findNonDisabledListIndex = findNonDisabledListIndex;
5821
+ exports.floor = floor;
5380
5822
  exports.focusOut = focusOut;
5381
5823
  exports.gecko = gecko;
5824
+ exports.getAlignment = getAlignment;
5825
+ exports.getAlignmentAxis = getAlignmentAxis;
5826
+ exports.getAlignmentSides = getAlignmentSides;
5827
+ exports.getAxisLength = getAxisLength;
5828
+ exports.getComputedStyle = getComputedStyle;
5829
+ exports.getContainingBlock = getContainingBlock;
5830
+ exports.getDocumentElement = getDocumentElement;
5831
+ exports.getExpandedPlacements = getExpandedPlacements;
5382
5832
  exports.getFloatingFocusElement = getFloatingFocusElement;
5833
+ exports.getFrameElement = getFrameElement;
5383
5834
  exports.getGridNavigatedIndex = getGridNavigatedIndex;
5384
5835
  exports.getMaxListIndex = getMaxListIndex;
5385
5836
  exports.getMinListIndex = getMinListIndex;
5386
5837
  exports.getNextTabbable = getNextTabbable;
5387
5838
  exports.getNodeChildren = getNodeChildren;
5839
+ exports.getNodeName = getNodeName;
5840
+ exports.getNodeScroll = getNodeScroll;
5841
+ exports.getOppositeAxis = getOppositeAxis;
5842
+ exports.getOppositeAxisPlacements = getOppositeAxisPlacements;
5843
+ exports.getOppositePlacement = getOppositePlacement;
5844
+ exports.getOverflowAncestors = getOverflowAncestors;
5845
+ exports.getPaddingObject = getPaddingObject;
5846
+ exports.getParentNode = getParentNode;
5847
+ exports.getSide = getSide;
5848
+ exports.getSideAxis = getSideAxis;
5388
5849
  exports.getTabbableAfterElement = getTabbableAfterElement;
5389
5850
  exports.getTabbableBeforeElement = getTabbableBeforeElement;
5390
5851
  exports.getTarget = getTarget;
5852
+ exports.getWindow = getWindow;
5391
5853
  exports.imperativeAction = imperativeAction;
5392
5854
  exports.incrementPress = incrementPress;
5393
5855
  exports.inertValue = inertValue;
@@ -5397,32 +5859,46 @@ exports.inputClear = inputClear;
5397
5859
  exports.inputPaste = inputPaste;
5398
5860
  exports.inputPress = inputPress;
5399
5861
  exports.ios = ios;
5862
+ exports.isContainingBlock = isContainingBlock;
5863
+ exports.isElement = isElement;
5400
5864
  exports.isElementVisible = isElementVisible;
5865
+ exports.isHTMLElement = isHTMLElement;
5401
5866
  exports.isIndexOutOfListBounds = isIndexOutOfListBounds;
5402
5867
  exports.isInteractiveElement = isInteractiveElement;
5868
+ exports.isLastTraversableNode = isLastTraversableNode;
5403
5869
  exports.isListIndexDisabled = isListIndexDisabled;
5404
5870
  exports.isMouseLikePointerType = isMouseLikePointerType;
5405
5871
  exports.isOutsideEvent = isOutsideEvent;
5872
+ exports.isOverflowElement = isOverflowElement;
5873
+ exports.isTableElement = isTableElement;
5406
5874
  exports.isTargetInsideEnabledTrigger = isTargetInsideEnabledTrigger;
5875
+ exports.isTopLayer = isTopLayer;
5407
5876
  exports.isTypeableCombobox = isTypeableCombobox;
5408
5877
  exports.isTypeableElement = isTypeableElement;
5409
5878
  exports.isVirtualClick = isVirtualClick;
5410
5879
  exports.isVirtualPointerEvent = isVirtualPointerEvent;
5880
+ exports.isWebKit = isWebKit;
5411
5881
  exports.itemPress = itemPress;
5412
5882
  exports.jsdom = jsdom;
5413
5883
  exports.keyboard = keyboard;
5414
5884
  exports.listNavigation = listNavigation;
5415
5885
  exports.mac = mac;
5416
5886
  exports.matchesFocusVisible = matchesFocusVisible;
5887
+ exports.max = max;
5417
5888
  exports.mergeCleanups = mergeCleanups;
5889
+ exports.min = min;
5418
5890
  exports.none = none;
5419
5891
  exports.outsidePress = outsidePress;
5420
5892
  exports.ownerDocument = ownerDocument;
5421
5893
  exports.pointer = pointer;
5422
5894
  exports.popupStateMapping = popupStateMapping;
5895
+ exports.popupTransitionStateMapping = popupTransitionStateMapping;
5423
5896
  exports.pressableTriggerOpenStateMapping = pressableTriggerOpenStateMapping;
5897
+ exports.rectToClientRect = rectToClientRect;
5898
+ exports.round = round;
5424
5899
  exports.scrub = scrub;
5425
5900
  exports.setPopupOpenState = setPopupOpenState;
5901
+ exports.shimExports = shimExports;
5426
5902
  exports.siblingOpen = siblingOpen;
5427
5903
  exports.stopEvent = stopEvent;
5428
5904
  exports.swipe = swipe;
@@ -5445,17 +5921,15 @@ exports.useFloatingParentNodeId = useFloatingParentNodeId;
5445
5921
  exports.useFloatingTree = useFloatingTree;
5446
5922
  exports.useId = useId;
5447
5923
  exports.useImplicitActiveTrigger = useImplicitActiveTrigger;
5448
- exports.useInitialOpenSync = useInitialOpenSync;
5449
5924
  exports.useIsoLayoutEffect = useIsoLayoutEffect;
5450
- exports.useOnFirstRender = useOnFirstRender;
5451
5925
  exports.useOnMount = useOnMount;
5452
5926
  exports.useOpenChangeComplete = useOpenChangeComplete;
5453
5927
  exports.useOpenInteractionType = useOpenInteractionType;
5454
5928
  exports.useOpenMethodTriggerProps = useOpenMethodTriggerProps;
5455
5929
  exports.useOpenStateTransitions = useOpenStateTransitions;
5456
5930
  exports.usePopupInteractionProps = usePopupInteractionProps;
5931
+ exports.usePopupRootStore = usePopupRootStore;
5457
5932
  exports.usePopupRootSync = usePopupRootSync;
5458
- exports.usePopupStore = usePopupStore;
5459
5933
  exports.useScrollLock = useScrollLock;
5460
5934
  exports.useStableCallback = useStableCallback;
5461
5935
  exports.useStore = useStore;
@@ -5468,6 +5942,7 @@ exports.useValueAsRef = useValueAsRef;
5468
5942
  exports.useValueChanged = useValueChanged;
5469
5943
  exports.visuallyHidden = visuallyHidden;
5470
5944
  exports.visuallyHiddenInput = visuallyHiddenInput;
5945
+ exports.voiceOver = voiceOver;
5471
5946
  exports.webkit = webkit;
5472
5947
  exports.wheel = wheel;
5473
5948
  exports.windowResize = windowResize;