@jobber/components 8.23.0 → 8.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/Autocomplete/index.cjs +1 -1
  2. package/dist/Autocomplete/index.mjs +1 -1
  3. package/dist/Card/index.cjs +4 -4
  4. package/dist/Card/index.mjs +4 -4
  5. package/dist/Chip/index.cjs +1 -1
  6. package/dist/Chip/index.mjs +1 -1
  7. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -1
  8. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -1
  9. package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
  10. package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
  11. package/dist/Chips/index.cjs +1 -1
  12. package/dist/Chips/index.mjs +1 -1
  13. package/dist/ComboboxChipRemove-cjs.js +668 -379
  14. package/dist/ComboboxChipRemove-es.js +508 -219
  15. package/dist/ConfirmationModal/index.cjs +1 -1
  16. package/dist/ConfirmationModal/index.mjs +1 -1
  17. package/dist/DataDump/index.cjs +4 -4
  18. package/dist/DataDump/index.mjs +4 -4
  19. package/dist/DataList/components/DataListActions/index.cjs +1 -1
  20. package/dist/DataList/components/DataListActions/index.mjs +1 -1
  21. package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
  22. package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
  23. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -1
  24. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -1
  25. package/dist/DataList/components/DataListFilters/index.cjs +1 -1
  26. package/dist/DataList/components/DataListFilters/index.mjs +1 -1
  27. package/dist/DataList/components/DataListHeader/index.cjs +1 -1
  28. package/dist/DataList/components/DataListHeader/index.mjs +1 -1
  29. package/dist/DataList/components/DataListItem/index.cjs +1 -1
  30. package/dist/DataList/components/DataListItem/index.mjs +1 -1
  31. package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
  32. package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
  33. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
  34. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
  35. package/dist/DataList/components/DataListItems/index.cjs +1 -1
  36. package/dist/DataList/components/DataListItems/index.mjs +1 -1
  37. package/dist/DataList/components/DataListLayout/index.cjs +1 -1
  38. package/dist/DataList/components/DataListLayout/index.mjs +1 -1
  39. package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
  40. package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
  41. package/dist/DataList/index.cjs +1 -1
  42. package/dist/DataList/index.mjs +1 -1
  43. package/dist/DatePicker/index.cjs +1 -1
  44. package/dist/DatePicker/index.mjs +1 -1
  45. package/dist/Dialog/index.cjs +3 -3
  46. package/dist/Dialog/index.mjs +3 -3
  47. package/dist/DrawerDescription-cjs.js +32 -33
  48. package/dist/DrawerDescription-es.js +19 -20
  49. package/dist/DrawerTitle-cjs.js +7 -5
  50. package/dist/DrawerTitle-es.js +7 -5
  51. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +1 -1
  52. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +1 -1
  53. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +1 -1
  54. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +1 -1
  55. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +1 -1
  56. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +1 -1
  57. package/dist/FilterPicker/index.cjs +1 -1
  58. package/dist/FilterPicker/index.mjs +1 -1
  59. package/dist/FormFieldWrapper-cjs.js +2 -2
  60. package/dist/FormFieldWrapper-es.js +2 -2
  61. package/dist/FormatFile/index.cjs +1 -1
  62. package/dist/FormatFile/index.mjs +1 -1
  63. package/dist/Gallery/index.cjs +1 -1
  64. package/dist/Gallery/index.mjs +1 -1
  65. package/dist/InputDate/index.cjs +1 -1
  66. package/dist/InputDate/index.mjs +1 -1
  67. package/dist/InputNumberExperimental-cjs.js +319 -102
  68. package/dist/InputNumberExperimental-es.js +321 -104
  69. package/dist/LightBox/index.cjs +1 -1
  70. package/dist/LightBox/index.mjs +1 -1
  71. package/dist/Menu/index.cjs +4 -4
  72. package/dist/Menu/index.mjs +4 -4
  73. package/dist/MenuSubmenuTrigger-cjs.js +545 -498
  74. package/dist/MenuSubmenuTrigger-es.js +490 -443
  75. package/dist/Modal/index.cjs +1 -1
  76. package/dist/Modal/index.mjs +1 -1
  77. package/dist/NumberFieldInput-cjs.js +609 -496
  78. package/dist/NumberFieldInput-es.js +612 -498
  79. package/dist/Page/index.cjs +4 -4
  80. package/dist/Page/index.mjs +4 -4
  81. package/dist/Popover/index.cjs +1 -1
  82. package/dist/Popover/index.mjs +1 -1
  83. package/dist/ScrollAreaViewport-cjs.js +326 -601
  84. package/dist/ScrollAreaViewport-es.js +278 -548
  85. package/dist/SelectGroupLabel-cjs.js +494 -528
  86. package/dist/SelectGroupLabel-es.js +411 -445
  87. package/dist/Separator-cjs.js +1 -0
  88. package/dist/Separator-es.js +1 -0
  89. package/dist/Tooltip/index.cjs +1 -1
  90. package/dist/Tooltip/index.mjs +1 -1
  91. package/dist/docs/InputTime/InputTime.md +8 -0
  92. package/dist/docs/SelectPrimitive/SelectPrimitive.md +38 -25
  93. package/dist/floating-ui.react-cjs.js +586 -10
  94. package/dist/floating-ui.react-dom-cjs.js +92 -92
  95. package/dist/floating-ui.react-dom-es.js +1 -1
  96. package/dist/floating-ui.react-es.js +580 -4
  97. package/dist/floating-ui.utils-cjs.js +159 -0
  98. package/dist/floating-ui.utils-es.js +136 -0
  99. package/dist/index.cjs +4 -4
  100. package/dist/index.mjs +4 -4
  101. package/dist/{ToolbarRootContext-cjs.js → isElementDisabled-cjs.js} +5 -0
  102. package/dist/{ToolbarRootContext-es.js → isElementDisabled-es.js} +5 -1
  103. package/dist/primitives/BottomSheet/index.cjs +3 -3
  104. package/dist/primitives/BottomSheet/index.mjs +3 -3
  105. package/dist/primitives/ComboboxPrimitive/index.cjs +2 -2
  106. package/dist/primitives/ComboboxPrimitive/index.mjs +2 -2
  107. package/dist/primitives/InputNumberExperimental/index.cjs +1 -1
  108. package/dist/primitives/InputNumberExperimental/index.mjs +1 -1
  109. package/dist/primitives/SelectPrimitive/index.cjs +5 -5
  110. package/dist/primitives/SelectPrimitive/index.mjs +5 -5
  111. package/dist/primitives/index.cjs +4 -4
  112. package/dist/primitives/index.mjs +4 -4
  113. package/dist/resolveAriaLabelledBy-cjs.js +5 -0
  114. package/dist/resolveAriaLabelledBy-es.js +5 -1
  115. package/dist/{CSPContext-cjs.js → scrollEdges-cjs.js} +30 -2
  116. package/dist/{CSPContext-es.js → scrollEdges-es.js} +28 -3
  117. package/dist/styles.css +9 -3
  118. package/dist/unstyledPrimitives/index.cjs +1217 -639
  119. package/dist/unstyledPrimitives/index.mjs +1163 -585
  120. package/dist/useButton-cjs.js +283 -317
  121. package/dist/useButton-es.js +277 -298
  122. package/dist/useCompositeListItem-cjs.js +328 -279
  123. package/dist/useCompositeListItem-es.js +293 -246
  124. package/dist/useLabel-cjs.js +11 -1
  125. package/dist/useLabel-es.js +11 -2
  126. package/dist/useLabelableId-cjs.js +37 -91
  127. package/dist/useLabelableId-es.js +37 -90
  128. package/dist/useRenderElement-cjs.js +81 -26
  129. package/dist/useRenderElement-es.js +81 -27
  130. package/dist/{InternalBackdrop-cjs.js → useScrollLock-cjs.js} +1286 -1220
  131. package/dist/{InternalBackdrop-es.js → useScrollLock-es.js} +1250 -1207
  132. package/package.json +3 -3
  133. package/dist/index.esm-cjs.js +0 -738
  134. package/dist/index.esm-es.js +0 -712
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import React__default from 'react';
3
- import { f as floor, t as tabbable, i as isTabbable, a as focusable } from './index.esm-es.js';
4
- import { f as getComputedStyle, i as isShadowRoot, g as getNodeName, c as isNode, a as isHTMLElement, o as getWindow, j as isWebKit$1, b as isElement, d as isLastTraversableNode, e as getParentNode, m as isOverflowElement } from './floating-ui.utils.dom-es.js';
5
- import { f as formatErrorMessage, a as useRefWithInit, i as isReactVersionAtLeast, N as NOOP, u as useRenderElement, E as EMPTY_OBJECT, b as useMergedRefs } from './useRenderElement-es.js';
6
- import { a as useIsoLayoutEffect, b as useStableCallback, C as ARROW_UP, A as ARROW_DOWN, s as stopEvent, w as ARROW_LEFT, x as ARROW_RIGHT, o as ownerDocument, r as contains, q as activeElement, a6 as isSafari, I as visuallyHidden, d as createChangeEventDetails, y as focusOut, l as useId, p as isTypeableCombobox, n as useValueAsRef, h as useTimeout, W as useAnimationFrame, m as getFloatingFocusElement, ai as resolveRef, aa as triggerHover, U as outsidePress, z as isVirtualClick, B as isVirtualPointerEvent, X as isWebKit, a1 as isTypeableElement, g as getTarget, a0 as isMouseLikePointerType, aj as isClickLikeEvent, a7 as triggerPress, ak as isReactEvent, a8 as escapeKey, T as Timeout, al as isEventTargetWithin, am as isRootElement, an as FOCUSABLE_ATTRIBUTE, ao as ACTIVE_KEY, ap as SELECTED_KEY, F as isIOS, Q as useValueChanged, aq as TransitionStatusDataAttributes, ar as AnimationFrame } from './useButton-es.js';
3
+ import { a as isHTMLElement, b as isElement, f as getComputedStyle, g as getNodeName, i as isShadowRoot, c as isNode, o as getWindow, d as isLastTraversableNode, e as getParentNode, m as isOverflowElement } from './floating-ui.utils.dom-es.js';
4
+ import { a as useIsoLayoutEffect, b as useStableCallback, a8 as lowerUserAgent, a9 as apple, q as contains, y as jsdom, E as visuallyHidden, s as stopEvent, o as ownerDocument, p as activeElement, B as addEventListener, d as createChangeEventDetails, v as focusOut, l as useId, m as useValueAsRef, h as useTimeout, n as useAnimationFrame, aa as resolveRef, a1 as triggerHover, Q as outsidePress, w as isVirtualClick, x as isVirtualPointerEvent, g as getTarget, Y as isMouseLikePointerType, $ as triggerPress, ab as isReactEvent, a0 as escapeKey, T as Timeout, ac as isClickLikeEvent, C as none, k as useTransitionStatus, f as useOpenChangeComplete, A as ios, O as useValueChanged, ad as TransitionStatusDataAttributes, ae as AnimationFrame } from './useButton-es.js';
5
+ import { f as formatErrorMessage, a as useRefWithInit, i as isReactVersionAtLeast, N as NOOP, u as useRenderElement, E as EMPTY_OBJECT, c as useMergedRefs } from './useRenderElement-es.js';
7
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { f as floor } from './floating-ui.utils-es.js';
8
8
  import * as ReactDOM from 'react-dom';
9
9
 
10
10
  function useOnFirstRender(fn) {
@@ -15,449 +15,24 @@ function useOnFirstRender(fn) {
15
15
  }
16
16
  }
17
17
 
18
- // src/devModeChecks/identityFunctionCheck.ts
19
- var runIdentityFunctionCheck = (resultFunc, inputSelectorsResults, outputSelectorResult) => {
20
- if (inputSelectorsResults.length === 1 && inputSelectorsResults[0] === outputSelectorResult) {
21
- let isInputSameAsOutput = false;
22
- try {
23
- const emptyObject = {};
24
- if (resultFunc(emptyObject) === emptyObject)
25
- isInputSameAsOutput = true;
26
- } catch {
27
- }
28
- if (isInputSameAsOutput) {
29
- let stack = void 0;
30
- try {
31
- throw new Error();
32
- } catch (e) {
33
- ({ stack } = e);
34
- }
35
- console.warn(
36
- "The result function returned its own inputs without modification. e.g\n`createSelector([state => state.todos], todos => todos)`\nThis could lead to inefficient memoization and unnecessary re-renders.\nEnsure transformation logic is in the result function, and extraction logic is in the input selectors.",
37
- { stack }
38
- );
39
- }
40
- }
41
- };
42
-
43
- // src/devModeChecks/inputStabilityCheck.ts
44
- var runInputStabilityCheck = (inputSelectorResultsObject, options, inputSelectorArgs) => {
45
- const { memoize, memoizeOptions } = options;
46
- const { inputSelectorResults, inputSelectorResultsCopy } = inputSelectorResultsObject;
47
- const createAnEmptyObject = memoize(() => ({}), ...memoizeOptions);
48
- const areInputSelectorResultsEqual = createAnEmptyObject.apply(null, inputSelectorResults) === createAnEmptyObject.apply(null, inputSelectorResultsCopy);
49
- if (!areInputSelectorResultsEqual) {
50
- let stack = void 0;
51
- try {
52
- throw new Error();
53
- } catch (e) {
54
- ({ stack } = e);
55
- }
56
- console.warn(
57
- "An input selector returned a different result when passed same arguments.\nThis means your output selector will likely run more frequently than intended.\nAvoid returning a new reference inside your input selector, e.g.\n`createSelector([state => state.todos.map(todo => todo.id)], todoIds => todoIds.length)`",
58
- {
59
- arguments: inputSelectorArgs,
60
- firstInputs: inputSelectorResults,
61
- secondInputs: inputSelectorResultsCopy,
62
- stack
63
- }
64
- );
65
- }
66
- };
67
-
68
- // src/devModeChecks/setGlobalDevModeChecks.ts
69
- var globalDevModeChecks = {
70
- inputStabilityCheck: "once",
71
- identityFunctionCheck: "once"
72
- };
73
-
74
- // src/utils.ts
75
- var NOT_FOUND = /* @__PURE__ */ Symbol("NOT_FOUND");
76
- function assertIsFunction(func, errorMessage = `expected a function, instead received ${typeof func}`) {
77
- if (typeof func !== "function") {
78
- throw new TypeError(errorMessage);
79
- }
80
- }
81
- function assertIsObject(object, errorMessage = `expected an object, instead received ${typeof object}`) {
82
- if (typeof object !== "object") {
83
- throw new TypeError(errorMessage);
84
- }
85
- }
86
- function assertIsArrayOfFunctions(array, errorMessage = `expected all items to be functions, instead received the following types: `) {
87
- if (!array.every((item) => typeof item === "function")) {
88
- const itemTypes = array.map(
89
- (item) => typeof item === "function" ? `function ${item.name || "unnamed"}()` : typeof item
90
- ).join(", ");
91
- throw new TypeError(`${errorMessage}[${itemTypes}]`);
92
- }
93
- }
94
- var ensureIsArray = (item) => {
95
- return Array.isArray(item) ? item : [item];
96
- };
97
- function getDependencies(createSelectorArgs) {
98
- const dependencies = Array.isArray(createSelectorArgs[0]) ? createSelectorArgs[0] : createSelectorArgs;
99
- assertIsArrayOfFunctions(
100
- dependencies,
101
- `createSelector expects all input-selectors to be functions, but received the following types: `
102
- );
103
- return dependencies;
104
- }
105
- function collectInputSelectorResults(dependencies, inputSelectorArgs) {
106
- const inputSelectorResults = [];
107
- const { length } = dependencies;
108
- for (let i = 0; i < length; i++) {
109
- inputSelectorResults.push(dependencies[i].apply(null, inputSelectorArgs));
110
- }
111
- return inputSelectorResults;
112
- }
113
- var getDevModeChecksExecutionInfo = (firstRun, devModeChecks) => {
114
- const { identityFunctionCheck, inputStabilityCheck } = {
115
- ...globalDevModeChecks,
116
- ...devModeChecks
117
- };
118
- return {
119
- identityFunctionCheck: {
120
- shouldRun: identityFunctionCheck === "always" || identityFunctionCheck === "once" && firstRun,
121
- run: runIdentityFunctionCheck
122
- },
123
- inputStabilityCheck: {
124
- shouldRun: inputStabilityCheck === "always" || inputStabilityCheck === "once" && firstRun,
125
- run: runInputStabilityCheck
126
- }
127
- };
128
- };
129
-
130
- // src/lruMemoize.ts
131
- function createSingletonCache(equals) {
132
- let entry;
133
- return {
134
- get(key) {
135
- if (entry && equals(entry.key, key)) {
136
- return entry.value;
137
- }
138
- return NOT_FOUND;
139
- },
140
- put(key, value) {
141
- entry = { key, value };
142
- },
143
- getEntries() {
144
- return entry ? [entry] : [];
145
- },
146
- clear() {
147
- entry = void 0;
148
- }
149
- };
150
- }
151
- function createLruCache(maxSize, equals) {
152
- let entries = [];
153
- function get(key) {
154
- const cacheIndex = entries.findIndex((entry) => equals(key, entry.key));
155
- if (cacheIndex > -1) {
156
- const entry = entries[cacheIndex];
157
- if (cacheIndex > 0) {
158
- entries.splice(cacheIndex, 1);
159
- entries.unshift(entry);
160
- }
161
- return entry.value;
162
- }
163
- return NOT_FOUND;
164
- }
165
- function put(key, value) {
166
- if (get(key) === NOT_FOUND) {
167
- entries.unshift({ key, value });
168
- if (entries.length > maxSize) {
169
- entries.pop();
170
- }
171
- }
172
- }
173
- function getEntries() {
174
- return entries;
175
- }
176
- function clear() {
177
- entries = [];
178
- }
179
- return { get, put, getEntries, clear };
180
- }
181
- var referenceEqualityCheck = (a, b) => a === b;
182
- function createCacheKeyComparator(equalityCheck) {
183
- return function areArgumentsShallowlyEqual(prev, next) {
184
- if (prev === null || next === null || prev.length !== next.length) {
185
- return false;
186
- }
187
- const { length } = prev;
188
- for (let i = 0; i < length; i++) {
189
- if (!equalityCheck(prev[i], next[i])) {
190
- return false;
191
- }
192
- }
193
- return true;
194
- };
195
- }
196
- function lruMemoize(func, equalityCheckOrOptions) {
197
- const providedOptions = typeof equalityCheckOrOptions === "object" ? equalityCheckOrOptions : { equalityCheck: equalityCheckOrOptions };
198
- const {
199
- equalityCheck = referenceEqualityCheck,
200
- maxSize = 1,
201
- resultEqualityCheck
202
- } = providedOptions;
203
- const comparator = createCacheKeyComparator(equalityCheck);
204
- let resultsCount = 0;
205
- const cache = maxSize <= 1 ? createSingletonCache(comparator) : createLruCache(maxSize, comparator);
206
- function memoized() {
207
- let value = cache.get(arguments);
208
- if (value === NOT_FOUND) {
209
- value = func.apply(null, arguments);
210
- resultsCount++;
211
- if (resultEqualityCheck) {
212
- const entries = cache.getEntries();
213
- const matchingEntry = entries.find(
214
- (entry) => resultEqualityCheck(entry.value, value)
215
- );
216
- if (matchingEntry) {
217
- value = matchingEntry.value;
218
- resultsCount !== 0 && resultsCount--;
219
- }
220
- }
221
- cache.put(arguments, value);
222
- }
223
- return value;
224
- }
225
- memoized.clearCache = () => {
226
- cache.clear();
227
- memoized.resetResultsCount();
228
- };
229
- memoized.resultsCount = () => resultsCount;
230
- memoized.resetResultsCount = () => {
231
- resultsCount = 0;
232
- };
233
- return memoized;
234
- }
235
-
236
- // src/weakMapMemoize.ts
237
- var StrongRef = class {
238
- constructor(value) {
239
- this.value = value;
240
- }
241
- deref() {
242
- return this.value;
243
- }
244
- };
245
- var Ref = typeof WeakRef !== "undefined" ? WeakRef : StrongRef;
246
- var UNTERMINATED = 0;
247
- var TERMINATED = 1;
248
- function createCacheNode() {
249
- return {
250
- s: UNTERMINATED,
251
- v: void 0,
252
- o: null,
253
- p: null
254
- };
255
- }
256
- function weakMapMemoize(func, options = {}) {
257
- let fnNode = createCacheNode();
258
- const { resultEqualityCheck } = options;
259
- let lastResult;
260
- let resultsCount = 0;
261
- function memoized() {
262
- let cacheNode = fnNode;
263
- const { length } = arguments;
264
- for (let i = 0, l = length; i < l; i++) {
265
- const arg = arguments[i];
266
- if (typeof arg === "function" || typeof arg === "object" && arg !== null) {
267
- let objectCache = cacheNode.o;
268
- if (objectCache === null) {
269
- cacheNode.o = objectCache = /* @__PURE__ */ new WeakMap();
270
- }
271
- const objectNode = objectCache.get(arg);
272
- if (objectNode === void 0) {
273
- cacheNode = createCacheNode();
274
- objectCache.set(arg, cacheNode);
275
- } else {
276
- cacheNode = objectNode;
277
- }
278
- } else {
279
- let primitiveCache = cacheNode.p;
280
- if (primitiveCache === null) {
281
- cacheNode.p = primitiveCache = /* @__PURE__ */ new Map();
282
- }
283
- const primitiveNode = primitiveCache.get(arg);
284
- if (primitiveNode === void 0) {
285
- cacheNode = createCacheNode();
286
- primitiveCache.set(arg, cacheNode);
287
- } else {
288
- cacheNode = primitiveNode;
289
- }
290
- }
291
- }
292
- const terminatedNode = cacheNode;
293
- let result;
294
- if (cacheNode.s === TERMINATED) {
295
- result = cacheNode.v;
296
- } else {
297
- result = func.apply(null, arguments);
298
- resultsCount++;
299
- if (resultEqualityCheck) {
300
- const lastResultValue = lastResult?.deref?.() ?? lastResult;
301
- if (lastResultValue != null && resultEqualityCheck(lastResultValue, result)) {
302
- result = lastResultValue;
303
- resultsCount !== 0 && resultsCount--;
304
- }
305
- const needsWeakRef = typeof result === "object" && result !== null || typeof result === "function";
306
- lastResult = needsWeakRef ? new Ref(result) : result;
307
- }
308
- }
309
- terminatedNode.s = TERMINATED;
310
- terminatedNode.v = result;
311
- return result;
312
- }
313
- memoized.clearCache = () => {
314
- fnNode = createCacheNode();
315
- memoized.resetResultsCount();
316
- };
317
- memoized.resultsCount = () => resultsCount;
318
- memoized.resetResultsCount = () => {
319
- resultsCount = 0;
320
- };
321
- return memoized;
322
- }
323
-
324
- // src/createSelectorCreator.ts
325
- function createSelectorCreator(memoizeOrOptions, ...memoizeOptionsFromArgs) {
326
- const createSelectorCreatorOptions = typeof memoizeOrOptions === "function" ? {
327
- memoize: memoizeOrOptions,
328
- memoizeOptions: memoizeOptionsFromArgs
329
- } : memoizeOrOptions;
330
- const createSelector2 = (...createSelectorArgs) => {
331
- let recomputations = 0;
332
- let dependencyRecomputations = 0;
333
- let lastResult;
334
- let directlyPassedOptions = {};
335
- let resultFunc = createSelectorArgs.pop();
336
- if (typeof resultFunc === "object") {
337
- directlyPassedOptions = resultFunc;
338
- resultFunc = createSelectorArgs.pop();
339
- }
340
- assertIsFunction(
341
- resultFunc,
342
- `createSelector expects an output function after the inputs, but received: [${typeof resultFunc}]`
343
- );
344
- const combinedOptions = {
345
- ...createSelectorCreatorOptions,
346
- ...directlyPassedOptions
347
- };
348
- const {
349
- memoize,
350
- memoizeOptions = [],
351
- argsMemoize = weakMapMemoize,
352
- argsMemoizeOptions = [],
353
- devModeChecks = {}
354
- } = combinedOptions;
355
- const finalMemoizeOptions = ensureIsArray(memoizeOptions);
356
- const finalArgsMemoizeOptions = ensureIsArray(argsMemoizeOptions);
357
- const dependencies = getDependencies(createSelectorArgs);
358
- const memoizedResultFunc = memoize(function recomputationWrapper() {
359
- recomputations++;
360
- return resultFunc.apply(
361
- null,
362
- arguments
363
- );
364
- }, ...finalMemoizeOptions);
365
- let firstRun = true;
366
- const selector = argsMemoize(function dependenciesChecker() {
367
- dependencyRecomputations++;
368
- const inputSelectorResults = collectInputSelectorResults(
369
- dependencies,
370
- arguments
371
- );
372
- lastResult = memoizedResultFunc.apply(null, inputSelectorResults);
373
- if (process.env.NODE_ENV !== "production") {
374
- const { identityFunctionCheck, inputStabilityCheck } = getDevModeChecksExecutionInfo(firstRun, devModeChecks);
375
- if (identityFunctionCheck.shouldRun) {
376
- identityFunctionCheck.run(
377
- resultFunc,
378
- inputSelectorResults,
379
- lastResult
380
- );
381
- }
382
- if (inputStabilityCheck.shouldRun) {
383
- const inputSelectorResultsCopy = collectInputSelectorResults(
384
- dependencies,
385
- arguments
386
- );
387
- inputStabilityCheck.run(
388
- { inputSelectorResults, inputSelectorResultsCopy },
389
- { memoize, memoizeOptions: finalMemoizeOptions },
390
- arguments
391
- );
392
- }
393
- if (firstRun)
394
- firstRun = false;
395
- }
396
- return lastResult;
397
- }, ...finalArgsMemoizeOptions);
398
- return Object.assign(selector, {
399
- resultFunc,
400
- memoizedResultFunc,
401
- dependencies,
402
- dependencyRecomputations: () => dependencyRecomputations,
403
- resetDependencyRecomputations: () => {
404
- dependencyRecomputations = 0;
405
- },
406
- lastResult: () => lastResult,
407
- recomputations: () => recomputations,
408
- resetRecomputations: () => {
409
- recomputations = 0;
410
- },
411
- memoize,
412
- argsMemoize
413
- });
414
- };
415
- Object.assign(createSelector2, {
416
- withTypes: () => createSelector2
417
- });
418
- return createSelector2;
419
- }
420
- var createSelector$1 = /* @__PURE__ */ createSelectorCreator(weakMapMemoize);
421
-
422
- // src/createStructuredSelector.ts
423
- var createStructuredSelector = Object.assign(
424
- (inputSelectorsObject, selectorCreator = createSelector$1) => {
425
- assertIsObject(
426
- inputSelectorsObject,
427
- `createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof inputSelectorsObject}`
428
- );
429
- const inputSelectorKeys = Object.keys(inputSelectorsObject);
430
- const dependencies = inputSelectorKeys.map(
431
- (key) => inputSelectorsObject[key]
432
- );
433
- const structuredSelector = selectorCreator(
434
- dependencies,
435
- (...inputSelectorResults) => {
436
- return inputSelectorResults.reduce((composition, value, index) => {
437
- composition[inputSelectorKeys[index]] = value;
438
- return composition;
439
- }, {});
440
- }
441
- );
442
- return structuredSelector;
443
- },
444
- { withTypes: () => createStructuredSelector }
445
- );
446
-
447
- /* eslint-disable no-underscore-dangle */ // __cacheKey__
18
+ /**
19
+ * The NoOptionalParams type is a utility type that checks if a function has optional or default parameters.
20
+ * If the function has optional or default parameters, it returns a string literal type with an error message.
21
+ * Otherwise, it returns the original function type.
22
+ *
23
+ * This is used to enforce that the combiner function passed to createSelector does not have optional or default parameters,
24
+ * as memoization relies on the Function.length property, which does not account for optional or default parameters.
25
+ */
448
26
 
449
- createSelectorCreator({
450
- memoize: lruMemoize,
451
- memoizeOptions: {
452
- maxSize: 1,
453
- equalityCheck: Object.is
454
- }
455
- });
456
27
  /**
457
28
  * Creates a selector function that can be used to derive values from the store's state.
458
- * The selector can take up to three additional arguments that can be used in the selector logic.
29
+ *
30
+ * The combiner function can have up to three additional parameters, but it **cannot have optional or default parameters**.
31
+ *
459
32
  * This function accepts up to six functions and combines them into a single selector function.
460
- * The last parameter is the combiner function that combines the results of the previous selectors.
33
+ * The resulting selector will take the state from the combined selectors and any additional parameters required by the combiner.
34
+ *
35
+ * The return type of the resulting selector is determined by the return type of the combiner function.
461
36
  *
462
37
  * @example
463
38
  * const selector = createSelector(
@@ -470,7 +45,6 @@ createSelectorCreator({
470
45
  * (state) => state.open,
471
46
  * (disabled, open) => ({ disabled, open })
472
47
  * );
473
- *
474
48
  */
475
49
  /* eslint-disable id-denylist */
476
50
  const createSelector = (a, b, c, d, e, f, ...other) => {
@@ -520,6 +94,7 @@ const createSelector = (a, b, c, d, e, f, ...other) => {
520
94
  }
521
95
  return selector;
522
96
  };
97
+ /* eslint-enable id-denylist */
523
98
 
524
99
  var shim = {exports: {}};
525
100
 
@@ -982,10 +557,9 @@ register({
982
557
  for (let i = 0; i < instance.syncHooks.length; i += 1) {
983
558
  const hook = instance.syncHooks[i];
984
559
  const value = hook.selector(hook.store.state, hook.a1, hook.a2, hook.a3);
985
- if (hook.didChange || !Object.is(hook.value, value)) {
560
+ if (!Object.is(hook.value, value)) {
986
561
  didChange = true;
987
562
  hook.value = value;
988
- hook.didChange = false;
989
563
  }
990
564
  }
991
565
  if (didChange) {
@@ -1036,8 +610,7 @@ function useStoreFast(store, selector, a1, a2, a3) {
1036
610
  a1,
1037
611
  a2,
1038
612
  a3,
1039
- value: selector(store.getSnapshot(), a1, a2, a3),
1040
- didChange: false
613
+ value: selector(store.getSnapshot(), a1, a2, a3)
1041
614
  };
1042
615
  instance.syncHooks.push(hook);
1043
616
  } else {
@@ -1051,7 +624,7 @@ function useStoreFast(store, selector, a1, a2, a3) {
1051
624
  hook.a1 = a1;
1052
625
  hook.a2 = a2;
1053
626
  hook.a3 = a3;
1054
- hook.didChange = true;
627
+ hook.value = selector(store.getSnapshot(), a1, a2, a3);
1055
628
  }
1056
629
  }
1057
630
  return hook.value;
@@ -1203,11 +776,13 @@ class ReactStore extends Store {
1203
776
  */
1204
777
  useSyncedValue(key, value) {
1205
778
  React.useDebugValue(key);
779
+ // eslint-disable-next-line consistent-this
780
+ const store = this;
1206
781
  useIsoLayoutEffect(() => {
1207
- if (this.state[key] !== value) {
1208
- this.set(key, value);
782
+ if (store.state[key] !== value) {
783
+ store.set(key, value);
1209
784
  }
1210
- }, [key, value]);
785
+ }, [store, key, value]);
1211
786
  }
1212
787
 
1213
788
  /**
@@ -1261,16 +836,18 @@ class ReactStore extends Store {
1261
836
  */
1262
837
  useControlledProp(key, controlled) {
1263
838
  React.useDebugValue(key);
839
+ // eslint-disable-next-line consistent-this
840
+ const store = this;
1264
841
  const isControlled = controlled !== undefined;
1265
842
  useIsoLayoutEffect(() => {
1266
- if (isControlled && !Object.is(this.state[key], controlled)) {
843
+ if (isControlled && !Object.is(store.state[key], controlled)) {
1267
844
  // Set the internal state to match the controlled value.
1268
- super.setState({
1269
- ...this.state,
845
+ store.setState({
846
+ ...store.state,
1270
847
  [key]: controlled
1271
848
  });
1272
849
  }
1273
- }, [key, controlled, isControlled]);
850
+ }, [store, key, controlled, isControlled]);
1274
851
  if (process.env.NODE_ENV !== 'production') {
1275
852
  // eslint-disable-next-line
1276
853
  const cache = this.controlledValues ??= new Map();
@@ -1363,44 +940,169 @@ class ReactStore extends Store {
1363
940
  }
1364
941
  }
1365
942
 
1366
- /* eslint-disable @typescript-eslint/no-loop-func */
1367
-
1368
- function getNodeChildren(nodes, id, onlyOpenChildren = true) {
1369
- const directChildren = nodes.filter(node => node.parentId === id && (!onlyOpenChildren || node.context?.open));
1370
- return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
943
+ /**
944
+ * Combines multiple cleanup functions into a single cleanup function.
945
+ */
946
+ function mergeCleanups(...cleanups) {
947
+ return () => {
948
+ for (let i = 0; i < cleanups.length; i += 1) {
949
+ const cleanup = cleanups[i];
950
+ if (cleanup) {
951
+ cleanup();
952
+ }
953
+ }
954
+ };
1371
955
  }
1372
- function getNodeAncestors(nodes, id) {
1373
- let allAncestors = [];
1374
- let currentParentId = nodes.find(node => node.id === id)?.parentId;
1375
- while (currentParentId) {
1376
- const currentNode = nodes.find(node => node.id === currentParentId);
1377
- currentParentId = currentNode?.parentId;
1378
- if (currentNode) {
1379
- allAncestors = allAncestors.concat(currentNode);
956
+
957
+ // WebKit (Safari, all iOS browsers, GNOME Web). Distinguished from Blink by
958
+ // the legacy `-webkit-backdrop-filter` name Blink forked from WebKit in 2013
959
+ // and only ships the unprefixed `backdrop-filter`.
960
+ /** WebKit: Safari, all iOS browsers, GNOME Web. Excludes Blink. */
961
+ const webkit = typeof CSS !== 'undefined' && !!CSS.supports?.('-webkit-backdrop-filter:none');
962
+
963
+ // Anchored to `!webkit` so engines are mutually exclusive by construction.
964
+ // Firefox-on-iOS uses WebKit (its UA marker is `FxiOS/`, not `Firefox/`); the
965
+ // `!webkit` prefix also defends against future iOS-browser UA changes that
966
+ // might inject `Firefox` into a WebKit-based UA.
967
+ /** Gecko: Firefox. */
968
+ const gecko = !webkit && lowerUserAgent.includes('firefox');
969
+
970
+ // All Chromium-based browsers ship `Chrome/` or `Chromium/` in their UA — both
971
+ // share the `chrom` prefix, so a single substring check covers Chrome, Edge,
972
+ // Opera, Brave, etc. Chrome-on-iOS uses `CriOS/` and stays WebKit. The positive
973
+ // UA check also makes this SSR-safe — an empty UA matches nothing.
974
+ /** Blink: Chrome, Edge, Opera, Brave, and other Chromium-based browsers. */
975
+ !webkit && lowerUserAgent.includes('chrom');
976
+
977
+ // Whether a screen reader is *actually* running cannot be detected. These flags
978
+ // identify platforms where a specific screen reader could be active. VoiceOver
979
+ // is the system screen reader on Apple platforms and works with every browser
980
+ // there, so the flag is purely an OS check; engine-specific quirks (e.g. the
981
+ // NSAccessibility virtual-cursor focus path) should be gated at the call site.
982
+ /**
983
+ * The user *may* be using VoiceOver — actual activation is not detectable.
984
+ * True on any Apple platform (macOS, iOS, iPadOS).
985
+ */
986
+ const voiceOver = apple;
987
+
988
+ const FOCUSABLE_ATTRIBUTE = 'data-base-ui-focusable';
989
+ const TYPEABLE_SELECTOR = "input:not([type='hidden']):not([disabled])," + "[contenteditable]:not([contenteditable='false']),textarea:not([disabled])";
990
+ const ARROW_LEFT = 'ArrowLeft';
991
+ const ARROW_RIGHT = 'ArrowRight';
992
+ const ARROW_UP = 'ArrowUp';
993
+ const ARROW_DOWN = 'ArrowDown';
994
+
995
+ function isTargetInsideEnabledTrigger(target, triggerElements) {
996
+ if (!isElement(target)) {
997
+ return false;
998
+ }
999
+ const targetElement = target;
1000
+ if (triggerElements.hasElement(targetElement)) {
1001
+ return !targetElement.hasAttribute('data-trigger-disabled');
1002
+ }
1003
+ for (const [, trigger] of triggerElements.entries()) {
1004
+ if (contains(trigger, targetElement)) {
1005
+ return !trigger.hasAttribute('data-trigger-disabled');
1380
1006
  }
1381
1007
  }
1382
- return allAncestors;
1008
+ return false;
1009
+ }
1010
+ function isEventTargetWithin(event, node) {
1011
+ if (node == null) {
1012
+ return false;
1013
+ }
1014
+ if ('composedPath' in event) {
1015
+ return event.composedPath().includes(node);
1016
+ }
1017
+
1018
+ // TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't
1019
+ const eventAgain = event;
1020
+ return eventAgain.target != null && node.contains(eventAgain.target);
1021
+ }
1022
+ function isRootElement(element) {
1023
+ return element.matches('html,body');
1024
+ }
1025
+ function isTypeableElement(element) {
1026
+ return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
1027
+ }
1028
+ function isInteractiveElement(element) {
1029
+ return element?.closest(`button,a[href],[role="button"],select,[tabindex]:not([tabindex="-1"]),${TYPEABLE_SELECTOR}`) != null;
1030
+ }
1031
+ function isTypeableCombobox(element) {
1032
+ if (!element) {
1033
+ return false;
1034
+ }
1035
+ return element.getAttribute('role') === 'combobox' && isTypeableElement(element);
1036
+ }
1037
+ function matchesFocusVisible(element) {
1038
+ // We don't want to block focus from working with `visibleOnly`
1039
+ // (JSDOM doesn't match `:focus-visible` when the element has `:focus`)
1040
+ if (!element || jsdom) {
1041
+ return true;
1042
+ }
1043
+ try {
1044
+ return element.matches(':focus-visible');
1045
+ } catch (_e) {
1046
+ return true;
1047
+ }
1048
+ }
1049
+ function getFloatingFocusElement(floatingElement) {
1050
+ if (!floatingElement) {
1051
+ return null;
1052
+ }
1053
+ // Try to find the element that has `{...getFloatingProps()}` spread on it.
1054
+ // This indicates the floating element is acting as a positioning wrapper, and
1055
+ // so focus should be managed on the child element with the event handlers and
1056
+ // aria props.
1057
+ return floatingElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? floatingElement : floatingElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`) || floatingElement;
1383
1058
  }
1384
1059
 
1060
+ const FocusGuard = /*#__PURE__*/React.forwardRef(function FocusGuard(props, ref) {
1061
+ const [role, setRole] = React.useState();
1062
+ useIsoLayoutEffect(() => {
1063
+ // Unlike NVDA and JAWS, VoiceOver's virtual cursor triggers `onFocus` as
1064
+ // it moves — but only on focusable/role-button elements through WebKit's
1065
+ // NSAccessibility path. Setting `role="button"` lets the focus trap catch
1066
+ // the cursor.
1067
+ if (voiceOver && webkit) {
1068
+ setRole('button');
1069
+ }
1070
+ }, []);
1071
+ const restProps = {
1072
+ tabIndex: 0,
1073
+ // Role is only for VoiceOver
1074
+ role
1075
+ };
1076
+ return /*#__PURE__*/jsx("span", {
1077
+ ...props,
1078
+ ref: ref,
1079
+ style: visuallyHidden,
1080
+ "aria-hidden": role ? undefined : true,
1081
+ ...restProps,
1082
+ "data-base-ui-focus-guard": ""
1083
+ });
1084
+ });
1085
+ if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1086
+
1385
1087
  function isDifferentGridRow(index, cols, prevRow) {
1386
1088
  return Math.floor(index / cols) !== prevRow;
1387
1089
  }
1388
- function isIndexOutOfListBounds(listRef, index) {
1389
- return index < 0 || index >= listRef.current.length;
1090
+ function isIndexOutOfListBounds(list, index) {
1091
+ return index < 0 || index >= list.length;
1390
1092
  }
1391
1093
  function getMinListIndex(listRef, disabledIndices) {
1392
- return findNonDisabledListIndex(listRef, {
1094
+ return findNonDisabledListIndex(listRef.current, {
1393
1095
  disabledIndices
1394
1096
  });
1395
1097
  }
1396
1098
  function getMaxListIndex(listRef, disabledIndices) {
1397
- return findNonDisabledListIndex(listRef, {
1099
+ return findNonDisabledListIndex(listRef.current, {
1398
1100
  decrement: true,
1399
1101
  startingIndex: listRef.current.length,
1400
1102
  disabledIndices
1401
1103
  });
1402
1104
  }
1403
- function findNonDisabledListIndex(listRef, {
1105
+ function findNonDisabledListIndex(list, {
1404
1106
  startingIndex = -1,
1405
1107
  decrement = false,
1406
1108
  disabledIndices,
@@ -1409,13 +1111,14 @@ function findNonDisabledListIndex(listRef, {
1409
1111
  let index = startingIndex;
1410
1112
  do {
1411
1113
  index += decrement ? -amount : amount;
1412
- } while (index >= 0 && index <= listRef.current.length - 1 && isListIndexDisabled(listRef, index, disabledIndices));
1114
+ } while (index >= 0 && index <= list.length - 1 && isListIndexDisabled(list, index, disabledIndices));
1413
1115
  return index;
1414
1116
  }
1415
- function getGridNavigatedIndex(listRef, {
1117
+ function getGridNavigatedIndex(list, {
1416
1118
  event,
1417
1119
  orientation,
1418
1120
  loopFocus,
1121
+ onLoop,
1419
1122
  rtl,
1420
1123
  cols,
1421
1124
  disabledIndices,
@@ -1443,7 +1146,7 @@ function getGridNavigatedIndex(listRef, {
1443
1146
  {
1444
1147
  let currentRowEl = null;
1445
1148
  let currentRowIndex = -1;
1446
- listRef.current.forEach((el, idx) => {
1149
+ list.forEach((el, idx) => {
1447
1150
  if (el == null) {
1448
1151
  return;
1449
1152
  }
@@ -1474,7 +1177,7 @@ function getGridNavigatedIndex(listRef, {
1474
1177
  }
1475
1178
  }
1476
1179
  }
1477
- const hasVirtualizedGaps = hasDomRows && visibleItemCount < listRef.current.length;
1180
+ const hasVirtualizedGaps = hasDomRows && visibleItemCount < list.length;
1478
1181
  const verticalCols = inferredDomCols || cols;
1479
1182
  const navigateVertically = direction => {
1480
1183
  if (!hasDomRows || prevIndex === -1) {
@@ -1492,11 +1195,17 @@ function getGridNavigatedIndex(listRef, {
1492
1195
  return undefined;
1493
1196
  }
1494
1197
  nextRow = nextRow < 0 ? rows.length - 1 : 0;
1198
+ if (onLoop) {
1199
+ const clampedCol = Math.min(colInRow, rows[nextRow].length - 1);
1200
+ const targetItemIndex = rows[nextRow][clampedCol] ?? rows[nextRow][0];
1201
+ const returnedItemIndex = onLoop(event, prevIndex, targetItemIndex);
1202
+ nextRow = rowIndexMap[returnedItemIndex] ?? nextRow;
1203
+ }
1495
1204
  }
1496
1205
  const targetRow = rows[nextRow];
1497
1206
  for (let col = Math.min(colInRow, targetRow.length - 1); col >= 0; col -= 1) {
1498
1207
  const candidate = targetRow[col];
1499
- if (!isListIndexDisabled(listRef, candidate, disabledIndices)) {
1208
+ if (!isListIndexDisabled(list, candidate, disabledIndices)) {
1500
1209
  return candidate;
1501
1210
  }
1502
1211
  }
@@ -1520,7 +1229,7 @@ function getGridNavigatedIndex(listRef, {
1520
1229
  }
1521
1230
  const rowEnd = Math.min(rowStart + verticalCols - 1, maxIndex);
1522
1231
  for (let candidate = Math.min(rowStart + colInRow, rowEnd); candidate >= rowStart; candidate -= 1) {
1523
- if (!isListIndexDisabled(listRef, candidate, disabledIndices)) {
1232
+ if (!isListIndexDisabled(list, candidate, disabledIndices)) {
1524
1233
  return candidate;
1525
1234
  }
1526
1235
  }
@@ -1536,7 +1245,7 @@ function getGridNavigatedIndex(listRef, {
1536
1245
  } else if (prevIndex === -1) {
1537
1246
  nextIndex = verticalDirection === 'up' ? maxIndex : minIndex;
1538
1247
  } else {
1539
- nextIndex = findNonDisabledListIndex(listRef, {
1248
+ nextIndex = findNonDisabledListIndex(list, {
1540
1249
  startingIndex: prevIndex,
1541
1250
  amount: verticalCols,
1542
1251
  decrement: verticalDirection === 'up',
@@ -1552,17 +1261,23 @@ function getGridNavigatedIndex(listRef, {
1552
1261
  } else {
1553
1262
  nextIndex = maxCol > col ? offset : offset - verticalCols;
1554
1263
  }
1264
+ if (onLoop) {
1265
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1266
+ }
1555
1267
  }
1556
1268
  if (verticalDirection === 'down' && prevIndex + verticalCols > maxIndex) {
1557
- nextIndex = findNonDisabledListIndex(listRef, {
1269
+ nextIndex = findNonDisabledListIndex(list, {
1558
1270
  startingIndex: prevIndex % verticalCols - verticalCols,
1559
1271
  amount: verticalCols,
1560
1272
  disabledIndices
1561
1273
  });
1274
+ if (onLoop) {
1275
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1276
+ }
1562
1277
  }
1563
1278
  }
1564
1279
  }
1565
- if (isIndexOutOfListBounds(listRef, nextIndex)) {
1280
+ if (isIndexOutOfListBounds(list, nextIndex)) {
1566
1281
  nextIndex = prevIndex;
1567
1282
  }
1568
1283
  }
@@ -1575,21 +1290,27 @@ function getGridNavigatedIndex(listRef, {
1575
1290
  stopEvent(event);
1576
1291
  }
1577
1292
  if (prevIndex % cols !== cols - 1) {
1578
- nextIndex = findNonDisabledListIndex(listRef, {
1293
+ nextIndex = findNonDisabledListIndex(list, {
1579
1294
  startingIndex: prevIndex,
1580
1295
  disabledIndices
1581
1296
  });
1582
1297
  if (loopFocus && isDifferentGridRow(nextIndex, cols, prevRow)) {
1583
- nextIndex = findNonDisabledListIndex(listRef, {
1298
+ nextIndex = findNonDisabledListIndex(list, {
1584
1299
  startingIndex: prevIndex - prevIndex % cols - 1,
1585
1300
  disabledIndices
1586
1301
  });
1302
+ if (onLoop) {
1303
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1304
+ }
1587
1305
  }
1588
1306
  } else if (loopFocus) {
1589
- nextIndex = findNonDisabledListIndex(listRef, {
1307
+ nextIndex = findNonDisabledListIndex(list, {
1590
1308
  startingIndex: prevIndex - prevIndex % cols - 1,
1591
1309
  disabledIndices
1592
1310
  });
1311
+ if (onLoop) {
1312
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1313
+ }
1593
1314
  }
1594
1315
  if (isDifferentGridRow(nextIndex, cols, prevRow)) {
1595
1316
  nextIndex = prevIndex;
@@ -1600,36 +1321,45 @@ function getGridNavigatedIndex(listRef, {
1600
1321
  stopEvent(event);
1601
1322
  }
1602
1323
  if (prevIndex % cols !== 0) {
1603
- nextIndex = findNonDisabledListIndex(listRef, {
1324
+ nextIndex = findNonDisabledListIndex(list, {
1604
1325
  startingIndex: prevIndex,
1605
1326
  decrement: true,
1606
1327
  disabledIndices
1607
1328
  });
1608
1329
  if (loopFocus && isDifferentGridRow(nextIndex, cols, prevRow)) {
1609
- nextIndex = findNonDisabledListIndex(listRef, {
1330
+ nextIndex = findNonDisabledListIndex(list, {
1610
1331
  startingIndex: prevIndex + (cols - prevIndex % cols),
1611
1332
  decrement: true,
1612
1333
  disabledIndices
1613
1334
  });
1335
+ if (onLoop) {
1336
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1337
+ }
1614
1338
  }
1615
1339
  } else if (loopFocus) {
1616
- nextIndex = findNonDisabledListIndex(listRef, {
1340
+ nextIndex = findNonDisabledListIndex(list, {
1617
1341
  startingIndex: prevIndex + (cols - prevIndex % cols),
1618
1342
  decrement: true,
1619
1343
  disabledIndices
1620
1344
  });
1345
+ if (onLoop) {
1346
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1347
+ }
1621
1348
  }
1622
1349
  if (isDifferentGridRow(nextIndex, cols, prevRow)) {
1623
1350
  nextIndex = prevIndex;
1624
1351
  }
1625
1352
  }
1626
1353
  const lastRow = floor(maxIndex / cols) === prevRow;
1627
- if (isIndexOutOfListBounds(listRef, nextIndex)) {
1354
+ if (isIndexOutOfListBounds(list, nextIndex)) {
1628
1355
  if (loopFocus && lastRow) {
1629
- nextIndex = event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT) ? maxIndex : findNonDisabledListIndex(listRef, {
1356
+ nextIndex = event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT) ? maxIndex : findNonDisabledListIndex(list, {
1630
1357
  startingIndex: prevIndex - prevIndex % cols - 1,
1631
1358
  disabledIndices
1632
1359
  });
1360
+ if (onLoop) {
1361
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1362
+ }
1633
1363
  } else {
1634
1364
  nextIndex = prevIndex;
1635
1365
  }
@@ -1637,80 +1367,12 @@ function getGridNavigatedIndex(listRef, {
1637
1367
  }
1638
1368
  return nextIndex;
1639
1369
  }
1640
-
1641
- /** For each cell index, gets the item index that occupies that cell */
1642
- function createGridCellMap(sizes, cols, dense) {
1643
- const cellMap = [];
1644
- let startIndex = 0;
1645
- sizes.forEach(({
1646
- width,
1647
- height
1648
- }, index) => {
1649
- if (width > cols) {
1650
- if (process.env.NODE_ENV !== 'production') {
1651
- throw new Error(`[Floating UI]: Invalid grid - item width at index ${index} is greater than grid columns`);
1652
- }
1653
- }
1654
- let itemPlaced = false;
1655
- while (!itemPlaced) {
1656
- const targetCells = [];
1657
- for (let i = 0; i < width; i += 1) {
1658
- for (let j = 0; j < height; j += 1) {
1659
- targetCells.push(startIndex + i + j * cols);
1660
- }
1661
- }
1662
- if (startIndex % cols + width <= cols && targetCells.every(cell => cellMap[cell] == null)) {
1663
- targetCells.forEach(cell => {
1664
- cellMap[cell] = index;
1665
- });
1666
- itemPlaced = true;
1667
- } else {
1668
- startIndex += 1;
1669
- }
1670
- }
1671
- });
1672
-
1673
- // convert into a non-sparse array
1674
- return [...cellMap];
1675
- }
1676
-
1677
- /** Gets cell index of an item's corner or -1 when index is -1. */
1678
- function getGridCellIndexOfCorner(index, sizes, cellMap, cols, corner) {
1679
- if (index === -1) {
1680
- return -1;
1681
- }
1682
- const firstCellIndex = cellMap.indexOf(index);
1683
- const sizeItem = sizes[index];
1684
- switch (corner) {
1685
- case 'tl':
1686
- return firstCellIndex;
1687
- case 'tr':
1688
- if (!sizeItem) {
1689
- return firstCellIndex;
1690
- }
1691
- return firstCellIndex + sizeItem.width - 1;
1692
- case 'bl':
1693
- if (!sizeItem) {
1694
- return firstCellIndex;
1695
- }
1696
- return firstCellIndex + (sizeItem.height - 1) * cols;
1697
- case 'br':
1698
- return cellMap.lastIndexOf(index);
1699
- default:
1700
- return -1;
1701
- }
1702
- }
1703
-
1704
- /** Gets all cell indices that correspond to the specified indices */
1705
- function getGridCellIndices(indices, cellMap) {
1706
- return cellMap.flatMap((index, cellIndex) => indices.includes(index) ? [cellIndex] : []);
1707
- }
1708
- function isListIndexDisabled(listRef, index, disabledIndices) {
1370
+ function isListIndexDisabled(list, index, disabledIndices) {
1709
1371
  const isExplicitlyDisabled = typeof disabledIndices === 'function' ? disabledIndices(index) : disabledIndices?.includes(index) ?? false;
1710
1372
  if (isExplicitlyDisabled) {
1711
1373
  return true;
1712
1374
  }
1713
- const element = listRef.current[index];
1375
+ const element = list[index];
1714
1376
  if (!element) {
1715
1377
  return false;
1716
1378
  }
@@ -1719,47 +1381,175 @@ function isListIndexDisabled(listRef, index, disabledIndices) {
1719
1381
  }
1720
1382
  return !disabledIndices && (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true');
1721
1383
  }
1722
- function isElementVisible(element) {
1723
- return getComputedStyle(element).display !== 'none';
1384
+ function isHiddenByStyles(styles) {
1385
+ return styles.visibility === 'hidden' || styles.visibility === 'collapse';
1386
+ }
1387
+ function isElementVisible(element, styles = element ? getComputedStyle(element) : null) {
1388
+ if (!element || !element.isConnected || !styles || isHiddenByStyles(styles)) {
1389
+ return false;
1390
+ }
1391
+ if (typeof element.checkVisibility === 'function') {
1392
+ return element.checkVisibility();
1393
+ }
1394
+ return styles.display !== 'none' && styles.display !== 'contents';
1724
1395
  }
1725
1396
 
1726
- const getTabbableOptions = () => ({
1727
- getShadowRoot: true,
1728
- displayCheck:
1729
- // JSDOM does not support the `tabbable` library. To solve this we can
1730
- // check if `ResizeObserver` is a real function (not polyfilled), which
1731
- // determines if the current environment is JSDOM-like.
1732
- typeof ResizeObserver === 'function' && ResizeObserver.toString().includes('[native code]') ? 'full' : 'none'
1733
- });
1734
- function getTabbableIn(container, dir) {
1735
- const list = tabbable(container, getTabbableOptions());
1736
- const len = list.length;
1737
- if (len === 0) {
1738
- return undefined;
1397
+ const CANDIDATE_SELECTOR = 'a[href],button,input,select,textarea,summary,details,iframe,object,embed,[tabindex],[contenteditable]:not([contenteditable="false"]),audio[controls],video[controls]';
1398
+ function getParentElement(element) {
1399
+ const assignedSlot = element.assignedSlot;
1400
+ if (assignedSlot) {
1401
+ return assignedSlot;
1739
1402
  }
1740
- const active = activeElement(ownerDocument(container));
1741
- const index = list.indexOf(active);
1742
- // eslint-disable-next-line no-nested-ternary
1743
- const nextIndex = index === -1 ? dir === 1 ? 0 : len - 1 : index + dir;
1744
- return list[nextIndex];
1403
+ if (element.parentElement) {
1404
+ return element.parentElement;
1405
+ }
1406
+ const rootNode = element.getRootNode();
1407
+ return isShadowRoot(rootNode) ? rootNode.host : null;
1745
1408
  }
1746
- function getNextTabbable(referenceElement) {
1747
- return getTabbableIn(ownerDocument(referenceElement).body, 1) || referenceElement;
1409
+ function getDetailsSummary(details) {
1410
+ for (const child of Array.from(details.children)) {
1411
+ if (getNodeName(child) === 'summary') {
1412
+ return child;
1413
+ }
1414
+ }
1415
+ return null;
1748
1416
  }
1749
- function getPreviousTabbable(referenceElement) {
1750
- return getTabbableIn(ownerDocument(referenceElement).body, -1) || referenceElement;
1417
+ function isWithinOpenDetailsSummary(element, details) {
1418
+ const summary = getDetailsSummary(details);
1419
+ return !!summary && (element === summary || contains(summary, element));
1751
1420
  }
1752
- function getTabbableNearElement(referenceElement, dir) {
1753
- if (!referenceElement) {
1754
- return null;
1755
- }
1756
- const list = tabbable(ownerDocument(referenceElement).body, getTabbableOptions());
1757
- const elementCount = list.length;
1758
- if (elementCount === 0) {
1759
- return null;
1421
+ function isFocusableCandidate(element) {
1422
+ const nodeName = element ? getNodeName(element) : '';
1423
+ 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');
1424
+ }
1425
+ function isFocusableElement(element) {
1426
+ if (!isFocusableCandidate(element) || !element.isConnected || element.matches(':disabled')) {
1427
+ return false;
1760
1428
  }
1761
- const index = list.indexOf(referenceElement);
1762
- if (index === -1) {
1429
+ for (let current = element; current; current = getParentElement(current)) {
1430
+ const isAncestor = current !== element;
1431
+ const isSlot = getNodeName(current) === 'slot';
1432
+ if (current.hasAttribute('inert')) {
1433
+ return false;
1434
+ }
1435
+ if (isAncestor && getNodeName(current) === 'details' && !current.open && !isWithinOpenDetailsSummary(element, current) || current.hasAttribute('hidden') || !isSlot && !isVisibleInTabbableTree(current, isAncestor)) {
1436
+ return false;
1437
+ }
1438
+ }
1439
+ return true;
1440
+ }
1441
+ function isVisibleInTabbableTree(element, isAncestor) {
1442
+ const styles = getComputedStyle(element);
1443
+ if (!isAncestor) {
1444
+ return isElementVisible(element, styles);
1445
+ }
1446
+ return styles.display !== 'none';
1447
+ }
1448
+ function getTabIndex(element) {
1449
+ const tabIndex = element.tabIndex;
1450
+ if (tabIndex < 0) {
1451
+ const nodeName = getNodeName(element);
1452
+ if (nodeName === 'details' || nodeName === 'audio' || nodeName === 'video' || isHTMLElement(element) && element.isContentEditable) {
1453
+ return 0;
1454
+ }
1455
+ }
1456
+ return tabIndex;
1457
+ }
1458
+ function getNamedRadioInput(element) {
1459
+ if (getNodeName(element) !== 'input') {
1460
+ return null;
1461
+ }
1462
+ const input = element;
1463
+ return input.type === 'radio' && input.name !== '' ? input : null;
1464
+ }
1465
+ function isTabbableRadio(element, candidates) {
1466
+ const input = getNamedRadioInput(element);
1467
+ if (!input) {
1468
+ return true;
1469
+ }
1470
+ const checkedRadio = candidates.find(candidate => {
1471
+ const radio = getNamedRadioInput(candidate);
1472
+ return radio?.name === input.name && radio.form === input.form && radio.checked;
1473
+ });
1474
+ if (checkedRadio) {
1475
+ return checkedRadio === input;
1476
+ }
1477
+ return candidates.find(candidate => {
1478
+ const radio = getNamedRadioInput(candidate);
1479
+ return radio?.name === input.name && radio.form === input.form;
1480
+ }) === input;
1481
+ }
1482
+ function getComposedChildren(container) {
1483
+ if (isHTMLElement(container) && getNodeName(container) === 'slot') {
1484
+ const assignedElements = container.assignedElements({
1485
+ flatten: true
1486
+ });
1487
+ if (assignedElements.length > 0) {
1488
+ return assignedElements;
1489
+ }
1490
+ }
1491
+ if (isHTMLElement(container) && container.shadowRoot) {
1492
+ return Array.from(container.shadowRoot.children);
1493
+ }
1494
+ return Array.from(container.children);
1495
+ }
1496
+ function appendCandidates(container, list) {
1497
+ getComposedChildren(container).forEach(child => {
1498
+ if (isFocusableCandidate(child)) {
1499
+ list.push(child);
1500
+ }
1501
+ appendCandidates(child, list);
1502
+ });
1503
+ }
1504
+ function appendMatchingElements(container, selector, list) {
1505
+ getComposedChildren(container).forEach(child => {
1506
+ if (isHTMLElement(child) && child.matches(selector)) {
1507
+ list.push(child);
1508
+ }
1509
+ appendMatchingElements(child, selector, list);
1510
+ });
1511
+ }
1512
+ function isTabbable(element) {
1513
+ return isFocusableElement(element) && getTabIndex(element) >= 0;
1514
+ }
1515
+ function focusable(container) {
1516
+ const candidates = [];
1517
+ appendCandidates(container, candidates);
1518
+ return candidates.filter(isFocusableElement);
1519
+ }
1520
+ function tabbable(container) {
1521
+ const candidates = focusable(container);
1522
+ return candidates.filter(element => getTabIndex(element) >= 0 && isTabbableRadio(element, candidates));
1523
+ }
1524
+ function getTabbableIn(container, dir) {
1525
+ const list = tabbable(container);
1526
+ const len = list.length;
1527
+ if (len === 0) {
1528
+ return undefined;
1529
+ }
1530
+ const active = activeElement(ownerDocument(container));
1531
+ const index = list.indexOf(active);
1532
+ // eslint-disable-next-line no-nested-ternary
1533
+ const nextIndex = index === -1 ? dir === 1 ? 0 : len - 1 : index + dir;
1534
+ return list[nextIndex];
1535
+ }
1536
+ function getNextTabbable(referenceElement) {
1537
+ return getTabbableIn(ownerDocument(referenceElement).body, 1) || referenceElement;
1538
+ }
1539
+ function getPreviousTabbable(referenceElement) {
1540
+ return getTabbableIn(ownerDocument(referenceElement).body, -1) || referenceElement;
1541
+ }
1542
+ function getTabbableNearElement(referenceElement, dir) {
1543
+ if (!referenceElement) {
1544
+ return null;
1545
+ }
1546
+ const list = tabbable(ownerDocument(referenceElement).body);
1547
+ const elementCount = list.length;
1548
+ if (elementCount === 0) {
1549
+ return null;
1550
+ }
1551
+ const index = list.indexOf(referenceElement);
1552
+ if (index === -1) {
1763
1553
  return null;
1764
1554
  }
1765
1555
  const nextIndex = (index + dir + elementCount) % elementCount;
@@ -1777,14 +1567,15 @@ function isOutsideEvent(event, container) {
1777
1567
  return !relatedTarget || !contains(containerElement, relatedTarget);
1778
1568
  }
1779
1569
  function disableFocusInside(container) {
1780
- const tabbableElements = tabbable(container, getTabbableOptions());
1570
+ const tabbableElements = tabbable(container);
1781
1571
  tabbableElements.forEach(element => {
1782
1572
  element.dataset.tabindex = element.getAttribute('tabindex') || '';
1783
1573
  element.setAttribute('tabindex', '-1');
1784
1574
  });
1785
1575
  }
1786
1576
  function enableFocusInside(container) {
1787
- const elements = container.querySelectorAll('[data-tabindex]');
1577
+ const elements = [];
1578
+ appendMatchingElements(container, '[data-tabindex]', elements);
1788
1579
  elements.forEach(element => {
1789
1580
  const tabindex = element.dataset.tabindex;
1790
1581
  delete element.dataset.tabindex;
@@ -1796,71 +1587,24 @@ function enableFocusInside(container) {
1796
1587
  });
1797
1588
  }
1798
1589
 
1799
- const TYPEAHEAD_RESET_MS = 500;
1800
- const PATIENT_CLICK_THRESHOLD = 500;
1801
- const DISABLED_TRANSITIONS_STYLE = {
1802
- style: {
1803
- transition: 'none'
1804
- }
1805
- };
1806
- const CLICK_TRIGGER_IDENTIFIER = 'data-base-ui-click-trigger';
1807
- const BASE_UI_SWIPE_IGNORE_ATTRIBUTE = 'data-base-ui-swipe-ignore';
1808
- const BASE_UI_SWIPE_IGNORE_SELECTOR = `[${BASE_UI_SWIPE_IGNORE_ATTRIBUTE}]`;
1809
-
1810
- /**
1811
- * Used for dropdowns that usually strictly prefer top/bottom placements and
1812
- * use `var(--available-height)` to limit their height.
1813
- */
1814
- const DROPDOWN_COLLISION_AVOIDANCE = {
1815
- fallbackAxisSide: 'none'
1816
- };
1817
-
1818
- /**
1819
- * Used by regular popups that usually aren't scrollable and are allowed to
1820
- * freely flip to any axis of placement.
1821
- */
1822
- const POPUP_COLLISION_AVOIDANCE = {
1823
- fallbackAxisSide: 'end'
1824
- };
1825
-
1826
- /**
1827
- * Special visually hidden styles for the aria-owns owner element to ensure owned element
1828
- * accessibility in iOS/Safari/VoiceControl.
1829
- * The owner element is an empty span, so most of the common visually hidden styles are not needed.
1830
- * @see https://github.com/floating-ui/floating-ui/issues/3403
1831
- */
1832
- const ownerVisuallyHidden = {
1833
- clipPath: 'inset(50%)',
1834
- position: 'fixed',
1835
- top: 0,
1836
- left: 0
1837
- };
1590
+ /* eslint-disable @typescript-eslint/no-loop-func */
1838
1591
 
1839
- const FocusGuard = /*#__PURE__*/React.forwardRef(function FocusGuard(props, ref) {
1840
- const [role, setRole] = React.useState();
1841
- useIsoLayoutEffect(() => {
1842
- if (isSafari) {
1843
- // Unlike other screen readers such as NVDA and JAWS, the virtual cursor
1844
- // on VoiceOver does trigger the onFocus event, so we can use the focus
1845
- // trap element. On Safari, only buttons trigger the onFocus event.
1846
- setRole('button');
1592
+ function getNodeChildren(nodes, id, onlyOpenChildren = true) {
1593
+ const directChildren = nodes.filter(node => node.parentId === id);
1594
+ return directChildren.flatMap(child => [...(!onlyOpenChildren || child.context?.open ? [child] : []), ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
1595
+ }
1596
+ function getNodeAncestors(nodes, id) {
1597
+ let allAncestors = [];
1598
+ let currentParentId = nodes.find(node => node.id === id)?.parentId;
1599
+ while (currentParentId) {
1600
+ const currentNode = nodes.find(node => node.id === currentParentId);
1601
+ currentParentId = currentNode?.parentId;
1602
+ if (currentNode) {
1603
+ allAncestors = allAncestors.concat(currentNode);
1847
1604
  }
1848
- }, []);
1849
- const restProps = {
1850
- tabIndex: 0,
1851
- // Role is only for VoiceOver
1852
- role
1853
- };
1854
- return /*#__PURE__*/jsx("span", {
1855
- ...props,
1856
- ref: ref,
1857
- style: visuallyHidden,
1858
- "aria-hidden": role ? undefined : true,
1859
- ...restProps,
1860
- "data-base-ui-focus-guard": ""
1861
- });
1862
- });
1863
- if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1605
+ }
1606
+ return allAncestors;
1607
+ }
1864
1608
 
1865
1609
  function createAttribute(name) {
1866
1610
  return `data-base-ui-${name}`;
@@ -1870,20 +1614,30 @@ let rafId = 0;
1870
1614
  function enqueueFocus(el, options = {}) {
1871
1615
  const {
1872
1616
  preventScroll = false,
1873
- cancelPrevious = true,
1874
- sync = false
1617
+ sync = false,
1618
+ shouldFocus
1875
1619
  } = options;
1876
- if (cancelPrevious) {
1877
- cancelAnimationFrame(rafId);
1620
+ cancelAnimationFrame(rafId);
1621
+ function exec() {
1622
+ if (shouldFocus && !shouldFocus()) {
1623
+ return;
1624
+ }
1625
+ el?.focus({
1626
+ preventScroll
1627
+ });
1878
1628
  }
1879
- const exec = () => el?.focus({
1880
- preventScroll
1881
- });
1882
1629
  if (sync) {
1883
1630
  exec();
1884
- } else {
1885
- rafId = requestAnimationFrame(exec);
1631
+ return NOOP;
1886
1632
  }
1633
+ const currentRafId = requestAnimationFrame(exec);
1634
+ rafId = currentRafId;
1635
+ return () => {
1636
+ if (rafId === currentRafId) {
1637
+ cancelAnimationFrame(currentRafId);
1638
+ rafId = 0;
1639
+ }
1640
+ };
1887
1641
  }
1888
1642
 
1889
1643
  // Modified to add conditional `aria-hidden` support:
@@ -1950,17 +1704,18 @@ const collectOutsideElements = (root, keepElements, stopElements) => {
1950
1704
  return outside;
1951
1705
  };
1952
1706
  function applyAttributeToOthers(uncorrectedAvoidElements, body, ariaHidden, inert, {
1953
- mark = true,
1954
- markerIgnoreElements = []
1707
+ mark = true
1955
1708
  }) {
1956
- // eslint-disable-next-line no-nested-ternary
1957
- const controlAttribute = inert ? 'inert' : ariaHidden ? 'aria-hidden' : null;
1709
+ let controlAttribute = null;
1710
+ if (inert) {
1711
+ controlAttribute = 'inert';
1712
+ } else if (ariaHidden) {
1713
+ controlAttribute = 'aria-hidden';
1714
+ }
1958
1715
  let counterMap = null;
1959
1716
  let uncontrolledElementsSet = null;
1960
1717
  const avoidElements = correctElements(body, uncorrectedAvoidElements);
1961
- const markerIgnoreTargets = mark ? correctElements(body, markerIgnoreElements) : [];
1962
- const markerIgnoreSet = new Set(markerIgnoreTargets);
1963
- const markerTargets = mark ? collectOutsideElements(body, buildKeepSet(avoidElements), new Set(avoidElements)).filter(target => !markerIgnoreSet.has(target)) : [];
1718
+ const markerTargets = mark ? collectOutsideElements(body, buildKeepSet(avoidElements), new Set(avoidElements)) : [];
1964
1719
  const hiddenElements = [];
1965
1720
  const markedElements = [];
1966
1721
  if (controlAttribute) {
@@ -2033,16 +1788,54 @@ function markOthers(avoidElements, options = {}) {
2033
1788
  const {
2034
1789
  ariaHidden = false,
2035
1790
  inert = false,
2036
- mark = true,
2037
- markerIgnoreElements = []
1791
+ mark = true
2038
1792
  } = options;
2039
1793
  const body = ownerDocument(avoidElements[0]).body;
2040
1794
  return applyAttributeToOthers(avoidElements, body, ariaHidden, inert, {
2041
- mark,
2042
- markerIgnoreElements
1795
+ mark
2043
1796
  });
2044
1797
  }
2045
1798
 
1799
+ const TYPEAHEAD_RESET_MS = 500;
1800
+ const PATIENT_CLICK_THRESHOLD = 500;
1801
+ const DISABLED_TRANSITIONS_STYLE = {
1802
+ style: {
1803
+ transition: 'none'
1804
+ }
1805
+ };
1806
+ const CLICK_TRIGGER_IDENTIFIER = 'data-base-ui-click-trigger';
1807
+ const BASE_UI_SWIPE_IGNORE_ATTRIBUTE = 'data-base-ui-swipe-ignore';
1808
+ const BASE_UI_SWIPE_IGNORE_SELECTOR = `[${BASE_UI_SWIPE_IGNORE_ATTRIBUTE}]`;
1809
+
1810
+ /**
1811
+ * Used for dropdowns that usually strictly prefer top/bottom placements and
1812
+ * use `var(--available-height)` to limit their height.
1813
+ */
1814
+ const DROPDOWN_COLLISION_AVOIDANCE = {
1815
+ fallbackAxisSide: 'none'
1816
+ };
1817
+
1818
+ /**
1819
+ * Used by regular popups that usually aren't scrollable and are allowed to
1820
+ * freely flip to any axis of placement.
1821
+ */
1822
+ const POPUP_COLLISION_AVOIDANCE = {
1823
+ fallbackAxisSide: 'end'
1824
+ };
1825
+
1826
+ /**
1827
+ * Special visually hidden styles for the aria-owns owner element to ensure owned element
1828
+ * accessibility in iOS/Safari/VoiceControl.
1829
+ * The owner element is an empty span, so most of the common visually hidden styles are not needed.
1830
+ * @see https://github.com/floating-ui/floating-ui/issues/3403
1831
+ */
1832
+ const ownerVisuallyHidden = {
1833
+ clipPath: 'inset(50%)',
1834
+ position: 'fixed',
1835
+ top: 0,
1836
+ left: 0
1837
+ };
1838
+
2046
1839
  const PortalContext = /*#__PURE__*/React.createContext(null);
2047
1840
  if (process.env.NODE_ENV !== "production") PortalContext.displayName = "PortalContext";
2048
1841
  const usePortalContext = () => React.useContext(PortalContext);
@@ -2126,10 +1919,11 @@ function useFloatingPortalNode(props = {}) {
2126
1919
  */
2127
1920
  const FloatingPortal = /*#__PURE__*/React.forwardRef(function FloatingPortal(componentProps, forwardedRef) {
2128
1921
  const {
1922
+ render,
1923
+ className,
1924
+ style,
2129
1925
  children,
2130
1926
  container,
2131
- className,
2132
- render,
2133
1927
  renderGuards,
2134
1928
  ...elementProps
2135
1929
  } = componentProps;
@@ -2147,6 +1941,7 @@ const FloatingPortal = /*#__PURE__*/React.forwardRef(function FloatingPortal(com
2147
1941
  const beforeInsideRef = React.useRef(null);
2148
1942
  const afterInsideRef = React.useRef(null);
2149
1943
  const [focusManagerState, setFocusManagerState] = React.useState(null);
1944
+ const focusInsideDisabledRef = React.useRef(false);
2150
1945
  const modal = focusManagerState?.modal;
2151
1946
  const open = focusManagerState?.open;
2152
1947
  const shouldRenderGuards = typeof renderGuards === 'boolean' ? renderGuards : !!focusManagerState && !focusManagerState.modal && focusManagerState.open && !!portalNode;
@@ -2162,26 +1957,30 @@ const FloatingPortal = /*#__PURE__*/React.forwardRef(function FloatingPortal(com
2162
1957
  // element outside or using the mouse.
2163
1958
  function onFocus(event) {
2164
1959
  if (portalNode && event.relatedTarget && isOutsideEvent(event)) {
2165
- const focusing = event.type === 'focusin';
2166
- const manageFocus = focusing ? enableFocusInside : disableFocusInside;
2167
- manageFocus(portalNode);
1960
+ if (event.type === 'focusin') {
1961
+ if (focusInsideDisabledRef.current) {
1962
+ enableFocusInside(portalNode);
1963
+ focusInsideDisabledRef.current = false;
1964
+ }
1965
+ } else {
1966
+ disableFocusInside(portalNode);
1967
+ focusInsideDisabledRef.current = true;
1968
+ }
2168
1969
  }
2169
1970
  }
2170
1971
 
2171
1972
  // Listen to the event on the capture phase so they run before the focus
2172
1973
  // trap elements onFocus prop is called.
2173
- portalNode.addEventListener('focusin', onFocus, true);
2174
- portalNode.addEventListener('focusout', onFocus, true);
2175
- return () => {
2176
- portalNode.removeEventListener('focusin', onFocus, true);
2177
- portalNode.removeEventListener('focusout', onFocus, true);
2178
- };
1974
+ return mergeCleanups(addEventListener(portalNode, 'focusin', onFocus, true), addEventListener(portalNode, 'focusout', onFocus, true));
2179
1975
  }, [portalNode, modal]);
2180
- React.useEffect(() => {
2181
- if (!portalNode || open) {
1976
+ useIsoLayoutEffect(() => {
1977
+ if (!portalNode || open !== true || !focusInsideDisabledRef.current) {
2182
1978
  return;
2183
1979
  }
1980
+
1981
+ // Restore tabbability before the focus manager's queued focus-on-open step runs.
2184
1982
  enableFocusInside(portalNode);
1983
+ focusInsideDisabledRef.current = false;
2185
1984
  }, [open, portalNode]);
2186
1985
  const portalContextValue = React.useMemo(() => ({
2187
1986
  beforeOutsideRef,
@@ -2353,7 +2152,7 @@ function FloatingTree(props) {
2353
2152
  }
2354
2153
 
2355
2154
  function getEventType(event, lastInteractionType) {
2356
- const win = getWindow(event.target);
2155
+ const win = getWindow(getTarget(event));
2357
2156
  if (event instanceof win.KeyboardEvent) {
2358
2157
  return 'keyboard';
2359
2158
  }
@@ -2398,35 +2197,30 @@ function getFirstTabbableElement(container) {
2398
2197
  if (!container) {
2399
2198
  return null;
2400
2199
  }
2401
- const tabbableOptions = getTabbableOptions();
2402
- if (isTabbable(container, tabbableOptions)) {
2200
+ if (isTabbable(container)) {
2403
2201
  return container;
2404
2202
  }
2405
- return tabbable(container, tabbableOptions)[0] || container;
2203
+ return tabbable(container)[0] || container;
2406
2204
  }
2407
- function isFocusable(element) {
2408
- if (!element || !element.isConnected) {
2409
- return false;
2410
- }
2411
- if (typeof element.checkVisibility === 'function') {
2412
- return element.checkVisibility();
2205
+ function handleTabIndex(floatingFocusElement) {
2206
+ if (floatingFocusElement.hasAttribute('tabindex') && !floatingFocusElement.hasAttribute('data-tabindex')) {
2207
+ return;
2413
2208
  }
2414
- return isElementVisible(element);
2415
- }
2416
- function handleTabIndex(floatingFocusElement, orderRef) {
2417
- if (!orderRef.current.includes('floating') && !floatingFocusElement.getAttribute('role')?.includes('dialog')) {
2209
+ if (!floatingFocusElement.getAttribute('role')?.includes('dialog')) {
2418
2210
  return;
2419
2211
  }
2420
- const options = getTabbableOptions();
2421
- const focusableElements = focusable(floatingFocusElement, options);
2212
+ const focusableElements = focusable(floatingFocusElement);
2422
2213
  const tabbableContent = focusableElements.filter(element => {
2423
2214
  const dataTabIndex = element.getAttribute('data-tabindex') || '';
2424
- return isTabbable(element, options) || element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-');
2215
+ return isTabbable(element) || element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-');
2425
2216
  });
2426
2217
  const tabIndex = floatingFocusElement.getAttribute('tabindex');
2427
- if (orderRef.current.includes('floating') || tabbableContent.length === 0) {
2218
+ if (tabbableContent.length === 0) {
2428
2219
  if (tabIndex !== '0') {
2429
2220
  floatingFocusElement.setAttribute('tabindex', '0');
2221
+ // Mark our own write so the externally-managed early-return above doesn't
2222
+ // mistake it for a user-authored `tabindex` and freeze management.
2223
+ floatingFocusElement.setAttribute('data-tabindex', '0');
2430
2224
  }
2431
2225
  } else if (tabIndex !== '-1' || floatingFocusElement.hasAttribute('data-tabindex') && floatingFocusElement.getAttribute('data-tabindex') !== '-1') {
2432
2226
  floatingFocusElement.setAttribute('tabindex', '-1');
@@ -2471,16 +2265,16 @@ function FloatingFocusManager(props) {
2471
2265
  // hidden dismiss button should only appear at the end of the list, not the
2472
2266
  // start.
2473
2267
  const isUntrappedTypeableCombobox = isTypeableCombobox(domReference) && ignoreInitialFocus;
2474
- const orderRef = React.useRef(['content']);
2475
2268
  const initialFocusRef = useValueAsRef(initialFocus);
2476
2269
  const returnFocusRef = useValueAsRef(returnFocus);
2477
2270
  const openInteractionTypeRef = useValueAsRef(openInteractionType);
2271
+ const openRef = useValueAsRef(open);
2478
2272
  const tree = useFloatingTree(externalTree);
2479
2273
  const portalContext = usePortalContext();
2480
2274
  const preventReturnFocusRef = React.useRef(false);
2481
2275
  const isPointerDownRef = React.useRef(false);
2482
2276
  const pointerDownOutsideRef = React.useRef(false);
2483
- const tabbableIndexRef = React.useRef(-1);
2277
+ const lastFocusedTabbableRef = React.useRef(null);
2484
2278
  const closeTypeRef = React.useRef('');
2485
2279
  const lastInteractionTypeRef = React.useRef('');
2486
2280
  const beforeGuardRef = React.useRef(null);
@@ -2493,13 +2287,9 @@ function FloatingFocusManager(props) {
2493
2287
  const isInsidePortal = portalContext != null;
2494
2288
  const floatingFocusElement = getFloatingFocusElement(floating);
2495
2289
  const getTabbableContent = useStableCallback((container = floatingFocusElement) => {
2496
- return container ? tabbable(container, getTabbableOptions()) : [];
2290
+ return container ? tabbable(container) : [];
2497
2291
  });
2498
2292
  const getResolvedInsideElements = useStableCallback(() => getInsideElements?.().filter(element => element != null) ?? []);
2499
- const getTabbableElements = useStableCallback(container => {
2500
- const content = getTabbableContent(container);
2501
- return orderRef.current.map(() => content).filter(Boolean).flat();
2502
- });
2503
2293
 
2504
2294
  // Prevent Tab from escaping the modal when there are no tabbable elements.
2505
2295
  React.useEffect(() => {
@@ -2515,11 +2305,8 @@ function FloatingFocusManager(props) {
2515
2305
  }
2516
2306
  }
2517
2307
  const doc = ownerDocument(floatingFocusElement);
2518
- doc.addEventListener('keydown', onKeyDown);
2519
- return () => {
2520
- doc.removeEventListener('keydown', onKeyDown);
2521
- };
2522
- }, [disabled, domReference, floatingFocusElement, modal, orderRef, isUntrappedTypeableCombobox, getTabbableContent, getTabbableElements]);
2308
+ return addEventListener(doc, 'keydown', onKeyDown);
2309
+ }, [disabled, floatingFocusElement, modal, isUntrappedTypeableCombobox, getTabbableContent]);
2523
2310
 
2524
2311
  // Track pointer/keyboard interactions to disambiguate focus and outside presses.
2525
2312
  React.useEffect(() => {
@@ -2538,22 +2325,21 @@ function FloatingFocusManager(props) {
2538
2325
  lastInteractionTypeRef.current = event.pointerType || 'keyboard';
2539
2326
  if (target?.closest(`[${CLICK_TRIGGER_IDENTIFIER}]`)) {
2540
2327
  isPointerDownRef.current = true;
2328
+ // Reset on the next tick so a single click on a click-trigger doesn't
2329
+ // permanently suppress focus-out closing for the lifetime of the instance.
2330
+ pointerDownTimeout.start(0, () => {
2331
+ isPointerDownRef.current = false;
2332
+ });
2541
2333
  }
2542
2334
  }
2543
2335
  function onKeyDown() {
2544
2336
  lastInteractionTypeRef.current = 'keyboard';
2545
2337
  }
2546
- doc.addEventListener('pointerdown', onPointerDown, true);
2547
- doc.addEventListener('pointerup', clearPointerDownOutside, true);
2548
- doc.addEventListener('pointercancel', clearPointerDownOutside, true);
2549
- doc.addEventListener('keydown', onKeyDown, true);
2550
- return () => {
2551
- doc.removeEventListener('pointerdown', onPointerDown, true);
2552
- doc.removeEventListener('pointerup', clearPointerDownOutside, true);
2553
- doc.removeEventListener('pointercancel', clearPointerDownOutside, true);
2554
- doc.removeEventListener('keydown', onKeyDown, true);
2555
- };
2556
- }, [disabled, floating, domReference, floatingFocusElement, open, portalContext, getResolvedInsideElements]);
2338
+ return mergeCleanups(addEventListener(doc, 'pointerdown', onPointerDown, true), addEventListener(doc, 'pointerup', clearPointerDownOutside, true), addEventListener(doc, 'pointercancel', clearPointerDownOutside, true), addEventListener(doc, 'keydown', onKeyDown, true),
2339
+ // Avoid a stale `true` leaking into the next open (e.g. keep-mounted popups)
2340
+ // if the popup dismissed between pointerdown and pointerup.
2341
+ clearPointerDownOutside);
2342
+ }, [disabled, floating, domReference, floatingFocusElement, open, portalContext, pointerDownTimeout, getResolvedInsideElements]);
2557
2343
 
2558
2344
  // Close on focus out and restore focus within the floating tree when needed.
2559
2345
  React.useEffect(() => {
@@ -2571,16 +2357,21 @@ function FloatingFocusManager(props) {
2571
2357
  }
2572
2358
  function handleFocusIn(event) {
2573
2359
  const target = getTarget(event);
2574
- const tabbableContent = getTabbableContent();
2575
- const tabbableIndex = tabbableContent.indexOf(target);
2576
- if (tabbableIndex !== -1) {
2577
- tabbableIndexRef.current = tabbableIndex;
2360
+ if (isTabbable(target)) {
2361
+ lastFocusedTabbableRef.current = target;
2578
2362
  }
2579
2363
  }
2580
2364
  function handleFocusOutside(event) {
2581
2365
  const relatedTarget = event.relatedTarget;
2582
2366
  const currentTarget = event.currentTarget;
2583
2367
  const target = getTarget(event);
2368
+
2369
+ // When focus is lost to the body (e.g. on a backdrop press), record the element that
2370
+ // had focus so a confirmation dialog opened while the body is focused can return focus
2371
+ // to it. Scoped to `modal` to avoid non-modal popups polluting the shared stack.
2372
+ if (modal && relatedTarget == null && target != null && contains(floating, target)) {
2373
+ addPreviouslyFocusedElement(target);
2374
+ }
2584
2375
  queueMicrotask(() => {
2585
2376
  const nodeId = getNodeId();
2586
2377
  const triggers = store.context.triggerElements;
@@ -2588,12 +2379,12 @@ function FloatingFocusManager(props) {
2588
2379
  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);
2589
2380
  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)));
2590
2381
  if (currentTarget === domReference && floatingFocusElement) {
2591
- handleTabIndex(floatingFocusElement, orderRef);
2382
+ handleTabIndex(floatingFocusElement);
2592
2383
  }
2593
2384
 
2594
2385
  // Restore focus to the previous tabbable element index to prevent
2595
2386
  // focus from being lost outside the floating tree.
2596
- if (restoreFocus && currentTarget !== domReference && !isFocusable(target) && activeElement(doc) === doc.body) {
2387
+ if (restoreFocus && currentTarget !== domReference && !isElementVisible(target) && activeElement(doc) === doc.body) {
2597
2388
  // Let `FloatingPortal` effect knows that focus is still inside the
2598
2389
  // floating tree.
2599
2390
  if (isHTMLElement(floatingFocusElement)) {
@@ -2611,9 +2402,9 @@ function FloatingFocusManager(props) {
2611
2402
  return;
2612
2403
  }
2613
2404
  }
2614
- const prevTabbableIndex = tabbableIndexRef.current;
2615
2405
  const tabbableContent = getTabbableContent();
2616
- const nodeToFocus = tabbableContent[prevTabbableIndex] || tabbableContent[tabbableContent.length - 1] || floatingFocusElement;
2406
+ const prevTabbable = lastFocusedTabbableRef.current;
2407
+ const nodeToFocus = (prevTabbable && tabbableContent.includes(prevTabbable) ? prevTabbable : null) || tabbableContent[tabbableContent.length - 1] || floatingFocusElement;
2617
2408
  if (isHTMLElement(nodeToFocus)) {
2618
2409
  nodeToFocus.focus();
2619
2410
  }
@@ -2651,38 +2442,11 @@ function FloatingFocusManager(props) {
2651
2442
  });
2652
2443
  }
2653
2444
  const domReferenceElement = isHTMLElement(domReference) ? domReference : null;
2654
- const cleanups = [];
2655
2445
  if (!floating && !domReferenceElement) {
2656
2446
  return undefined;
2657
2447
  }
2658
- if (domReferenceElement) {
2659
- domReferenceElement.addEventListener('focusout', handleFocusOutside);
2660
- domReferenceElement.addEventListener('pointerdown', handlePointerDown);
2661
- cleanups.push(() => {
2662
- domReferenceElement.removeEventListener('focusout', handleFocusOutside);
2663
- domReferenceElement.removeEventListener('pointerdown', handlePointerDown);
2664
- });
2665
- }
2666
- if (floating) {
2667
- floating.addEventListener('focusin', handleFocusIn);
2668
- floating.addEventListener('focusout', handleFocusOutside);
2669
- if (portalContext) {
2670
- floating.addEventListener('focusout', markInsideReactTree, true);
2671
- cleanups.push(() => {
2672
- floating.removeEventListener('focusout', markInsideReactTree, true);
2673
- });
2674
- }
2675
- cleanups.push(() => {
2676
- floating.removeEventListener('focusin', handleFocusIn);
2677
- floating.removeEventListener('focusout', handleFocusOutside);
2678
- });
2679
- }
2680
- return () => {
2681
- cleanups.forEach(cleanup => {
2682
- cleanup();
2683
- });
2684
- };
2685
- }, [disabled, domReference, floating, floatingFocusElement, modal, tree, portalContext, store, closeOnFocusOut, restoreFocus, getTabbableContent, isUntrappedTypeableCombobox, getNodeId, orderRef, dataRef, blurTimeout, pointerDownTimeout, restoreFocusFrame, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2448
+ return mergeCleanups(domReferenceElement && addEventListener(domReferenceElement, 'focusout', handleFocusOutside), domReferenceElement && addEventListener(domReferenceElement, 'pointerdown', handlePointerDown), floating && addEventListener(floating, 'focusin', handleFocusIn), floating && addEventListener(floating, 'focusout', handleFocusOutside), floating && portalContext && addEventListener(floating, 'focusout', markInsideReactTree, true));
2449
+ }, [disabled, domReference, floating, floatingFocusElement, modal, tree, portalContext, store, closeOnFocusOut, restoreFocus, getTabbableContent, isUntrappedTypeableCombobox, getNodeId, dataRef, blurTimeout, pointerDownTimeout, restoreFocusFrame, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2686
2450
 
2687
2451
  // Hide everything outside the floating tree from assistive tech while open.
2688
2452
  React.useEffect(() => {
@@ -2706,7 +2470,7 @@ function FloatingFocusManager(props) {
2706
2470
  markerCleanup();
2707
2471
  ariaHiddenCleanup();
2708
2472
  };
2709
- }, [open, disabled, domReference, floating, modal, orderRef, portalContext, isUntrappedTypeableCombobox, tree, getNodeId, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2473
+ }, [open, disabled, domReference, floating, modal, portalContext, isUntrappedTypeableCombobox, tree, getNodeId, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2710
2474
 
2711
2475
  // Focus the initial element when the floating element opens.
2712
2476
  useIsoLayoutEffect(() => {
@@ -2718,7 +2482,6 @@ function FloatingFocusManager(props) {
2718
2482
 
2719
2483
  // Wait for any layout effect state setters to execute to set `tabIndex`.
2720
2484
  queueMicrotask(() => {
2721
- const focusableElements = getTabbableElements(floatingFocusElement);
2722
2485
  const initialFocusValueOrFn = initialFocusRef.current;
2723
2486
  const resolvedInitialFocus = typeof initialFocusValueOrFn === 'function' ? initialFocusValueOrFn(openInteractionTypeRef.current || '') : initialFocusValueOrFn;
2724
2487
 
@@ -2726,22 +2489,44 @@ function FloatingFocusManager(props) {
2726
2489
  if (resolvedInitialFocus === undefined || resolvedInitialFocus === false) {
2727
2490
  return;
2728
2491
  }
2492
+ const focusAlreadyInsideFloatingEl = contains(floatingFocusElement, previouslyFocusedElement);
2493
+ if (focusAlreadyInsideFloatingEl) {
2494
+ return;
2495
+ }
2496
+ let focusableElements = null;
2497
+ const getDefaultFocusElement = () => {
2498
+ if (focusableElements == null) {
2499
+ focusableElements = getTabbableContent(floatingFocusElement);
2500
+ }
2501
+ return focusableElements[0] || floatingFocusElement;
2502
+ };
2729
2503
  let elToFocus;
2730
2504
  if (resolvedInitialFocus === true || resolvedInitialFocus === null) {
2731
- elToFocus = focusableElements[0] || floatingFocusElement;
2505
+ elToFocus = getDefaultFocusElement();
2732
2506
  } else {
2733
2507
  elToFocus = resolveRef(resolvedInitialFocus);
2734
2508
  }
2735
- elToFocus = elToFocus || focusableElements[0] || floatingFocusElement;
2736
- const focusAlreadyInsideFloatingEl = contains(floatingFocusElement, previouslyFocusedElement);
2737
- if (focusAlreadyInsideFloatingEl) {
2738
- return;
2739
- }
2509
+ elToFocus = elToFocus || getDefaultFocusElement();
2510
+ const hadFocusInside = contains(floatingFocusElement, activeElement(doc));
2740
2511
  enqueueFocus(elToFocus, {
2741
- preventScroll: elToFocus === floatingFocusElement
2512
+ preventScroll: elToFocus === floatingFocusElement,
2513
+ shouldFocus() {
2514
+ // This focus is queued on the next animation frame. If the floating element has closed
2515
+ // before it runs — e.g. tabbing out of a kept-mounted popup — don't pull focus back
2516
+ // onto the initial element after it has legitimately moved elsewhere.
2517
+ if (!openRef.current) {
2518
+ return false;
2519
+ }
2520
+ if (hadFocusInside) {
2521
+ return true;
2522
+ }
2523
+ const currentActiveElement = activeElement(doc);
2524
+ const focusMovedInside = currentActiveElement !== elToFocus && contains(floatingFocusElement, currentActiveElement);
2525
+ return !focusMovedInside;
2526
+ }
2742
2527
  });
2743
2528
  });
2744
- }, [disabled, open, floatingFocusElement, ignoreInitialFocus, getTabbableElements, initialFocusRef, openInteractionTypeRef]);
2529
+ }, [disabled, open, floatingFocusElement, getTabbableContent, initialFocusRef, openInteractionTypeRef, openRef]);
2745
2530
 
2746
2531
  // Track return focus targets and restore focus on unmount/close.
2747
2532
  useIsoLayoutEffect(() => {
@@ -2749,8 +2534,11 @@ function FloatingFocusManager(props) {
2749
2534
  return undefined;
2750
2535
  }
2751
2536
  const doc = ownerDocument(floatingFocusElement);
2752
- const previouslyFocusedElement = activeElement(doc);
2753
- addPreviouslyFocusedElement(previouslyFocusedElement);
2537
+ const elementFocusedBeforeOpen = activeElement(doc);
2538
+ // Only nullish interaction types represent programmatic opens. The empty
2539
+ // string default is intentionally not treated as programmatic.
2540
+ const preferPreviousFocus = openInteractionTypeRef.current == null;
2541
+ addPreviouslyFocusedElement(elementFocusedBeforeOpen);
2754
2542
 
2755
2543
  // Dismissing via outside press should always ignore `returnFocus` to
2756
2544
  // prevent unwanted scrolling.
@@ -2770,7 +2558,7 @@ function FloatingFocusManager(props) {
2770
2558
  preventReturnFocusRef.current = false;
2771
2559
  } else {
2772
2560
  let isPreventScrollSupported = false;
2773
- document.createElement('div').focus({
2561
+ ownerDocument(floatingFocusElement).createElement('div').focus({
2774
2562
  get preventScroll() {
2775
2563
  isPreventScrollSupported = true;
2776
2564
  return false;
@@ -2795,26 +2583,31 @@ function FloatingFocusManager(props) {
2795
2583
  if (resolvedReturnFocusValue === null) {
2796
2584
  resolvedReturnFocusValue = true;
2797
2585
  }
2586
+ const referenceReturnElement = domReference?.isConnected ? domReference : null;
2587
+ const previousReturnElement = elementFocusedBeforeOpen?.isConnected && getNodeName(elementFocusedBeforeOpen) !== 'body' ? elementFocusedBeforeOpen : null;
2588
+ let defaultReturnElement = preferPreviousFocus ? previousReturnElement || referenceReturnElement : referenceReturnElement || previousReturnElement;
2589
+ if (!defaultReturnElement) {
2590
+ defaultReturnElement = getPreviouslyFocusedElement() || null;
2591
+ }
2798
2592
  if (typeof resolvedReturnFocusValue === 'boolean') {
2799
- const el = domReference || getPreviouslyFocusedElement();
2800
- return el && el.isConnected ? el : null;
2593
+ return defaultReturnElement;
2801
2594
  }
2802
- const fallback = domReference || getPreviouslyFocusedElement();
2803
- return resolveRef(resolvedReturnFocusValue) || fallback || null;
2595
+ return resolveRef(resolvedReturnFocusValue) || defaultReturnElement || null;
2804
2596
  }
2805
2597
  return () => {
2806
2598
  events.off('openchange', onOpenChangeLocal);
2807
2599
  const activeEl = activeElement(doc);
2808
2600
  const insideElements = getResolvedInsideElements();
2809
2601
  const isFocusInsideFloatingTree = contains(floating, activeEl) || insideElements.some(element => element === activeEl || contains(element, activeEl)) || tree && getNodeChildren(tree.nodesRef.current, getNodeId(), false).some(node => contains(node.context?.elements.floating, activeEl));
2602
+
2603
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2604
+ const returnFocusValueOrFn = returnFocusRef.current;
2810
2605
  const returnElement = getReturnElement();
2811
2606
  queueMicrotask(() => {
2812
2607
  // This is `returnElement`, if it's tabbable, or its first tabbable child.
2813
2608
  const tabbableReturnElement = getFirstTabbableElement(returnElement);
2814
- const hasExplicitReturnFocus = typeof returnFocusRef.current !== 'boolean';
2815
- if (
2816
- // eslint-disable-next-line react-hooks/exhaustive-deps
2817
- returnFocusRef.current && !preventReturnFocusRef.current && isHTMLElement(tabbableReturnElement) && (
2609
+ const hasExplicitReturnFocus = typeof returnFocusValueOrFn !== 'boolean';
2610
+ if (returnFocusValueOrFn && !preventReturnFocusRef.current && isHTMLElement(tabbableReturnElement) && (
2818
2611
  // If the focus moved somewhere else after mount, avoid returning focus
2819
2612
  // since it likely entered a different element which should be
2820
2613
  // respected: https://github.com/floating-ui/floating-ui/issues/2607
@@ -2826,13 +2619,13 @@ function FloatingFocusManager(props) {
2826
2619
  preventReturnFocusRef.current = false;
2827
2620
  });
2828
2621
  };
2829
- }, [disabled, floating, floatingFocusElement, returnFocusRef, dataRef, events, tree, domReference, getNodeId, getResolvedInsideElements]);
2622
+ }, [disabled, floating, floatingFocusElement, returnFocusRef, openInteractionTypeRef, events, tree, domReference, getNodeId, getResolvedInsideElements]);
2830
2623
 
2831
2624
  // Safari may randomly scroll to the bottom of the page if an input inside a popup has focus
2832
2625
  // when the popup unmounts from the DOM.
2833
2626
  // By blurring it before the popup unmounts, we can prevent this behavior.
2834
2627
  useIsoLayoutEffect(() => {
2835
- if (!isWebKit || open || !floating) {
2628
+ if (!webkit || open || !floating) {
2836
2629
  return;
2837
2630
  }
2838
2631
  const activeEl = activeElement(ownerDocument(floating));
@@ -2843,6 +2636,7 @@ function FloatingFocusManager(props) {
2843
2636
  activeEl.blur();
2844
2637
  }
2845
2638
  }, [open, floating]);
2639
+
2846
2640
  // Synchronize the `context` & `modal` value to the FloatingPortal context.
2847
2641
  // It will decide whether or not it needs to render its own guards.
2848
2642
  useIsoLayoutEffect(() => {
@@ -2866,11 +2660,11 @@ function FloatingFocusManager(props) {
2866
2660
  if (disabled || !floatingFocusElement) {
2867
2661
  return undefined;
2868
2662
  }
2869
- handleTabIndex(floatingFocusElement, orderRef);
2663
+ handleTabIndex(floatingFocusElement);
2870
2664
  return () => {
2871
2665
  queueMicrotask(clearDisconnectedPreviouslyFocusedElements);
2872
2666
  };
2873
- }, [disabled, floatingFocusElement, orderRef]);
2667
+ }, [disabled, floatingFocusElement]);
2874
2668
  const shouldRenderGuards = !disabled && (modal ? !isUntrappedTypeableCombobox : true) && (isInsidePortal || modal);
2875
2669
  return /*#__PURE__*/jsxs(React.Fragment, {
2876
2670
  children: [shouldRenderGuards && /*#__PURE__*/jsx(FocusGuard, {
@@ -2878,7 +2672,7 @@ function FloatingFocusManager(props) {
2878
2672
  ref: mergedBeforeGuardRef,
2879
2673
  onFocus: event => {
2880
2674
  if (modal) {
2881
- const els = getTabbableElements();
2675
+ const els = getTabbableContent();
2882
2676
  enqueueFocus(els[els.length - 1]);
2883
2677
  } else if (portalContext?.portalNode) {
2884
2678
  preventReturnFocusRef.current = false;
@@ -2895,7 +2689,7 @@ function FloatingFocusManager(props) {
2895
2689
  ref: mergedAfterGuardRef,
2896
2690
  onFocus: event => {
2897
2691
  if (modal) {
2898
- enqueueFocus(getTabbableElements()[0]);
2692
+ enqueueFocus(getTabbableContent()[0]);
2899
2693
  } else if (portalContext?.portalNode) {
2900
2694
  if (closeOnFocusOut) {
2901
2695
  preventReturnFocusRef.current = true;
@@ -2917,8 +2711,6 @@ function FloatingFocusManager(props) {
2917
2711
  * @see https://floating-ui.com/docs/useClick
2918
2712
  */
2919
2713
  function useClick(context, props = {}) {
2920
- const store = 'rootStore' in context ? context.rootStore : context;
2921
- const dataRef = store.context.dataRef;
2922
2714
  const {
2923
2715
  enabled = true,
2924
2716
  event: eventOption = 'click',
@@ -2928,97 +2720,105 @@ function useClick(context, props = {}) {
2928
2720
  touchOpenDelay = 0,
2929
2721
  reason = triggerPress
2930
2722
  } = props;
2723
+ const store = 'rootStore' in context ? context.rootStore : context;
2724
+ const dataRef = store.context.dataRef;
2931
2725
  const pointerTypeRef = React.useRef(undefined);
2932
2726
  const frame = useAnimationFrame();
2933
2727
  const touchOpenTimeout = useTimeout();
2934
- const reference = React.useMemo(() => ({
2935
- onPointerDown(event) {
2936
- pointerTypeRef.current = event.pointerType;
2937
- },
2938
- onMouseDown(event) {
2939
- const pointerType = pointerTypeRef.current;
2940
- const nativeEvent = event.nativeEvent;
2941
- const open = store.select('open');
2942
-
2943
- // Ignore all buttons except for the "main" button.
2944
- // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
2945
- if (event.button !== 0 || eventOption === 'click' || isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2946
- return;
2728
+ const reference = React.useMemo(() => {
2729
+ function setOpenWithTouchDelay(nextOpen, nativeEvent, target, pointerType) {
2730
+ const details = createChangeEventDetails(reason, nativeEvent, target);
2731
+ if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
2732
+ touchOpenTimeout.start(touchOpenDelay, () => {
2733
+ store.setOpen(true, details);
2734
+ });
2735
+ } else {
2736
+ store.setOpen(nextOpen, details);
2947
2737
  }
2738
+ }
2739
+ function getNextOpen(open, currentTarget, isClickLikeOpenEvent) {
2948
2740
  const openEvent = dataRef.current.openEvent;
2949
- const openEventType = openEvent?.type;
2950
- const hasClickedOnInactiveTrigger = store.select('domReferenceElement') !== event.currentTarget;
2951
- const nextOpen = open && hasClickedOnInactiveTrigger || !(open && toggle && (openEvent && stickIfOpen ? openEventType === 'click' || openEventType === 'mousedown' : true));
2952
-
2953
- // Animations sometimes won't run on a typeable element if using a rAF.
2954
- // Focus is always set on these elements. For touch, we may delay opening.
2955
- if (isTypeableElement(nativeEvent.target)) {
2956
- const details = createChangeEventDetails(reason, nativeEvent, nativeEvent.target);
2957
- if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
2958
- touchOpenTimeout.start(touchOpenDelay, () => {
2959
- store.setOpen(true, details);
2960
- });
2961
- } else {
2962
- store.setOpen(nextOpen, details);
2963
- }
2964
- return;
2741
+ const hasClickedOnInactiveTrigger = store.select('domReferenceElement') !== currentTarget;
2742
+ if (open && hasClickedOnInactiveTrigger) {
2743
+ // Moving between triggers should always open the newly active one.
2744
+ return true;
2965
2745
  }
2966
-
2967
- // Capture the currentTarget before the rAF.
2968
- // as React sets it to null after the event handler completes.
2969
- const eventCurrentTarget = event.currentTarget;
2970
-
2971
- // Wait until focus is set on the element. This is an alternative to
2972
- // `event.preventDefault()` to avoid :focus-visible from appearing when using a pointer.
2973
- frame.request(() => {
2974
- const details = createChangeEventDetails(reason, nativeEvent, eventCurrentTarget);
2975
- if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
2976
- touchOpenTimeout.start(touchOpenDelay, () => {
2977
- store.setOpen(true, details);
2978
- });
2979
- } else {
2980
- store.setOpen(nextOpen, details);
2981
- }
2982
- });
2983
- },
2984
- onClick(event) {
2985
- if (eventOption === 'mousedown-only') {
2986
- return;
2746
+ if (!open) {
2747
+ // A closed popup should open on the next press.
2748
+ return true;
2987
2749
  }
2988
- const pointerType = pointerTypeRef.current;
2989
- if (eventOption === 'mousedown' && pointerType) {
2990
- pointerTypeRef.current = undefined;
2991
- return;
2750
+ if (!toggle) {
2751
+ // Non-toggle mode never closes on a repeated trigger press.
2752
+ return true;
2992
2753
  }
2993
- if (isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2994
- return;
2754
+ if (openEvent && stickIfOpen) {
2755
+ // Preserve hover/focus-opened popups until the matching click-like event closes them.
2756
+ return !isClickLikeOpenEvent(openEvent.type);
2995
2757
  }
2996
- const open = store.select('open');
2997
- const openEvent = dataRef.current.openEvent;
2998
- const hasClickedOnInactiveTrigger = store.select('domReferenceElement') !== event.currentTarget;
2999
- const nextOpen = open && hasClickedOnInactiveTrigger || !(open && toggle && (openEvent && stickIfOpen ? isClickLikeEvent(openEvent) : true));
3000
- const details = createChangeEventDetails(reason, event.nativeEvent, event.currentTarget);
3001
- if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
3002
- touchOpenTimeout.start(touchOpenDelay, () => {
3003
- store.setOpen(true, details);
2758
+
2759
+ // Otherwise, a repeated click toggles the popup closed.
2760
+ return false;
2761
+ }
2762
+ return {
2763
+ onPointerDown(event) {
2764
+ pointerTypeRef.current = event.pointerType;
2765
+ },
2766
+ onMouseDown(event) {
2767
+ const pointerType = pointerTypeRef.current;
2768
+ const nativeEvent = event.nativeEvent;
2769
+ const open = store.select('open');
2770
+
2771
+ // Ignore all buttons except for the "main" button.
2772
+ // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
2773
+ if (event.button !== 0 || eventOption === 'click' || isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2774
+ return;
2775
+ }
2776
+ const nextOpen = getNextOpen(open, event.currentTarget, openEventType => openEventType === 'click' || openEventType === 'mousedown');
2777
+
2778
+ // Animations sometimes won't run on a typeable element if using a rAF.
2779
+ // Focus is always set on these elements. For touch, we may delay opening.
2780
+ const target = getTarget(nativeEvent);
2781
+ if (isTypeableElement(target)) {
2782
+ setOpenWithTouchDelay(nextOpen, nativeEvent, target, pointerType);
2783
+ return;
2784
+ }
2785
+
2786
+ // Capture the currentTarget before the rAF.
2787
+ // as React sets it to null after the event handler completes.
2788
+ const eventCurrentTarget = event.currentTarget;
2789
+
2790
+ // Wait until focus is set on the element. This is an alternative to
2791
+ // `event.preventDefault()` to avoid :focus-visible from appearing when using a pointer.
2792
+ frame.request(() => {
2793
+ setOpenWithTouchDelay(nextOpen, nativeEvent, eventCurrentTarget, pointerType);
3004
2794
  });
3005
- } else {
3006
- store.setOpen(nextOpen, details);
2795
+ },
2796
+ onClick(event) {
2797
+ if (eventOption === 'mousedown-only') {
2798
+ return;
2799
+ }
2800
+ const pointerType = pointerTypeRef.current;
2801
+ if (eventOption === 'mousedown' && pointerType) {
2802
+ pointerTypeRef.current = undefined;
2803
+ return;
2804
+ }
2805
+ if (isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2806
+ return;
2807
+ }
2808
+ const open = store.select('open');
2809
+ const nextOpen = getNextOpen(open, event.currentTarget, openEventType => openEventType === 'click' || openEventType === 'mousedown' || openEventType === 'keydown' || openEventType === 'keyup');
2810
+ setOpenWithTouchDelay(nextOpen, event.nativeEvent, event.currentTarget, pointerType);
2811
+ },
2812
+ onKeyDown() {
2813
+ pointerTypeRef.current = undefined;
3007
2814
  }
3008
- },
3009
- onKeyDown() {
3010
- pointerTypeRef.current = undefined;
3011
- }
3012
- }), [dataRef, eventOption, ignoreMouse, store, stickIfOpen, toggle, frame, touchOpenTimeout, touchOpenDelay, reason]);
2815
+ };
2816
+ }, [dataRef, eventOption, ignoreMouse, reason, store, stickIfOpen, toggle, frame, touchOpenTimeout, touchOpenDelay]);
3013
2817
  return React.useMemo(() => enabled ? {
3014
2818
  reference
3015
2819
  } : EMPTY_OBJECT, [enabled, reference]);
3016
2820
  }
3017
2821
 
3018
- const bubbleHandlerKeys = {
3019
- intentional: 'onClick',
3020
- sloppy: 'onPointerDown'
3021
- };
3022
2822
  function alwaysFalse() {
3023
2823
  return false;
3024
2824
  }
@@ -3034,35 +2834,36 @@ function normalizeProp(normalizable) {
3034
2834
  * @see https://floating-ui.com/docs/useDismiss
3035
2835
  */
3036
2836
  function useDismiss(context, props = {}) {
3037
- const store = 'rootStore' in context ? context.rootStore : context;
3038
- const open = store.useState('open');
3039
- const floatingElement = store.useState('floatingElement');
3040
- const {
3041
- dataRef
3042
- } = store.context;
3043
2837
  const {
3044
2838
  enabled = true,
3045
2839
  escapeKey: escapeKey$1 = true,
3046
2840
  outsidePress: outsidePressProp = true,
3047
2841
  outsidePressEvent = 'sloppy',
3048
2842
  referencePress = alwaysFalse,
3049
- referencePressEvent = 'sloppy',
3050
2843
  bubbles,
3051
2844
  externalTree
3052
2845
  } = props;
2846
+ const store = 'rootStore' in context ? context.rootStore : context;
2847
+ const open = store.useState('open');
2848
+ const floatingElement = store.useState('floatingElement');
2849
+ const {
2850
+ dataRef
2851
+ } = store.context;
3053
2852
  const tree = useFloatingTree(externalTree);
3054
2853
  const outsidePressFn = useStableCallback(typeof outsidePressProp === 'function' ? outsidePressProp : () => false);
3055
2854
  const outsidePress$1 = typeof outsidePressProp === 'function' ? outsidePressFn : outsidePressProp;
3056
2855
  const outsidePressEnabled = outsidePress$1 !== false;
3057
2856
  const getOutsidePressEventProp = useStableCallback(() => outsidePressEvent);
3058
- const pressStartedInsideRef = React.useRef(false);
3059
- const pressStartPreventedRef = React.useRef(false);
3060
- // Ignore only the very next outside click after dragging from inside to outside.
3061
- const suppressNextOutsideClickRef = React.useRef(false);
3062
2857
  const {
3063
2858
  escapeKey: escapeKeyBubbles,
3064
2859
  outsidePress: outsidePressBubbles
3065
2860
  } = normalizeProp(bubbles);
2861
+ const pressStartedInsideRef = React.useRef(false);
2862
+ const pressStartPreventedRef = React.useRef(false);
2863
+ // Ignore only the very next outside click after dragging from inside to outside.
2864
+ const suppressNextOutsideClickRef = React.useRef(false);
2865
+ const isComposingRef = React.useRef(false);
2866
+ const currentPointerTypeRef = React.useRef('');
3066
2867
  const touchStateRef = React.useRef(null);
3067
2868
  const cancelDismissOnEndTimeout = useTimeout();
3068
2869
  const clearInsideReactTreeTimeout = useTimeout();
@@ -3070,9 +2871,20 @@ function useDismiss(context, props = {}) {
3070
2871
  clearInsideReactTreeTimeout.clear();
3071
2872
  dataRef.current.insideReactTree = false;
3072
2873
  });
3073
- const isComposingRef = React.useRef(false);
3074
- const currentPointerTypeRef = React.useRef('');
3075
- const isReferencePressEnabled = useStableCallback(referencePress);
2874
+ const hasBlockingChild = useStableCallback(bubbleKey => {
2875
+ const nodeId = dataRef.current.floatingContext?.nodeId;
2876
+ const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
2877
+ return children.some(child => child.context?.open && !child.context.dataRef.current[bubbleKey]);
2878
+ });
2879
+ const isEventWithinOwnElements = useStableCallback(event => {
2880
+ return isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement'));
2881
+ });
2882
+ const closeOnReferencePress = useStableCallback(event => {
2883
+ if (!referencePress()) {
2884
+ return;
2885
+ }
2886
+ store.setOpen(false, createChangeEventDetails(triggerPress, event.nativeEvent));
2887
+ });
3076
2888
  const closeOnEscapeKeyDown = useStableCallback(event => {
3077
2889
  if (!open || !enabled || !escapeKey$1 || event.key !== 'Escape') {
3078
2890
  return;
@@ -3083,24 +2895,15 @@ function useDismiss(context, props = {}) {
3083
2895
  if (isComposingRef.current) {
3084
2896
  return;
3085
2897
  }
3086
- const nodeId = dataRef.current.floatingContext?.nodeId;
3087
- const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
3088
- if (!escapeKeyBubbles) {
3089
- if (children.length > 0) {
3090
- let shouldDismiss = true;
3091
- children.forEach(child => {
3092
- if (child.context?.open && !child.context.dataRef.current.__escapeKeyBubbles) {
3093
- shouldDismiss = false;
3094
- }
3095
- });
3096
- if (!shouldDismiss) {
3097
- return;
3098
- }
3099
- }
2898
+ if (!escapeKeyBubbles && hasBlockingChild('__escapeKeyBubbles')) {
2899
+ return;
3100
2900
  }
3101
2901
  const native = isReactEvent(event) ? event.nativeEvent : event;
3102
2902
  const eventDetails = createChangeEventDetails(escapeKey, native);
3103
2903
  store.setOpen(false, eventDetails);
2904
+ if (!eventDetails.isCanceled) {
2905
+ event.preventDefault();
2906
+ }
3104
2907
  if (!escapeKeyBubbles && !eventDetails.isPropagationAllowed) {
3105
2908
  event.stopPropagation();
3106
2909
  }
@@ -3109,6 +2912,33 @@ function useDismiss(context, props = {}) {
3109
2912
  dataRef.current.insideReactTree = true;
3110
2913
  clearInsideReactTreeTimeout.start(0, clearInsideReactTree);
3111
2914
  });
2915
+ const markPressStartedInsideReactTree = useStableCallback(event => {
2916
+ if (!open || !enabled || event.button !== 0) {
2917
+ return;
2918
+ }
2919
+ const target = getTarget(event.nativeEvent);
2920
+
2921
+ // Only treat presses that start within the floating DOM subtree as inside.
2922
+ // This avoids suppressing parent dismissal when interacting with nested portals.
2923
+ if (!contains(store.select('floatingElement'), target)) {
2924
+ return;
2925
+ }
2926
+ if (!pressStartedInsideRef.current) {
2927
+ pressStartedInsideRef.current = true;
2928
+ pressStartPreventedRef.current = false;
2929
+ }
2930
+ });
2931
+ const markInsidePressStartPrevented = useStableCallback(event => {
2932
+ if (!open || !enabled) {
2933
+ return;
2934
+ }
2935
+ if (!(event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
2936
+ return;
2937
+ }
2938
+ if (pressStartedInsideRef.current) {
2939
+ pressStartPreventedRef.current = true;
2940
+ }
2941
+ });
3112
2942
  React.useEffect(() => {
3113
2943
  if (!open || !enabled) {
3114
2944
  return undefined;
@@ -3128,7 +2958,7 @@ function useDismiss(context, props = {}) {
3128
2958
  compositionTimeout.start(
3129
2959
  // 0ms or 1ms don't work in Safari. 5ms appears to consistently work.
3130
2960
  // Only apply to WebKit for the test to remain 0ms.
3131
- isWebKit$1() ? 5 : 0, () => {
2961
+ webkit ? 5 : 0, () => {
3132
2962
  isComposingRef.current = false;
3133
2963
  });
3134
2964
  }
@@ -3161,10 +2991,16 @@ function useDismiss(context, props = {}) {
3161
2991
  function isEventWithinFloatingTree(event) {
3162
2992
  const nodeId = dataRef.current.floatingContext?.nodeId;
3163
2993
  const targetIsInsideChildren = tree && getNodeChildren(tree.nodesRef.current, nodeId).some(node => isEventTargetWithin(event, node.context?.elements.floating));
3164
- return isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement')) || targetIsInsideChildren;
2994
+ return isEventWithinOwnElements(event) || targetIsInsideChildren;
3165
2995
  }
3166
2996
  function closeOnPressOutside(event) {
3167
2997
  if (shouldIgnoreEvent(event)) {
2998
+ // A new press began outside the floating element and its trigger. Clear any
2999
+ // leftover drag-out suppression so this press's eventual click can dismiss.
3000
+ if (event.type !== 'click' && !isEventWithinOwnElements(event)) {
3001
+ preventedPressSuppressionTimeout.clear();
3002
+ suppressNextOutsideClickRef.current = false;
3003
+ }
3168
3004
  clearInsideReactTree();
3169
3005
  return;
3170
3006
  }
@@ -3174,11 +3010,8 @@ function useDismiss(context, props = {}) {
3174
3010
  }
3175
3011
  const target = getTarget(event);
3176
3012
  const inertSelector = `[${createAttribute('inert')}]`;
3177
- let markers = Array.from(ownerDocument(store.select('floatingElement')).querySelectorAll(inertSelector));
3178
3013
  const targetRoot = isElement(target) ? target.getRootNode() : null;
3179
- if (isShadowRoot(targetRoot)) {
3180
- markers = markers.concat(Array.from(targetRoot.querySelectorAll(inertSelector)));
3181
- }
3014
+ const markers = Array.from((isShadowRoot(targetRoot) ? targetRoot : ownerDocument(store.select('floatingElement'))).querySelectorAll(inertSelector));
3182
3015
  const triggers = store.context.triggerElements;
3183
3016
 
3184
3017
  // If another trigger is clicked, don't close the floating element.
@@ -3243,30 +3076,20 @@ function useDismiss(context, props = {}) {
3243
3076
  if (typeof outsidePress$1 === 'function' && !outsidePress$1(event)) {
3244
3077
  return;
3245
3078
  }
3246
- const nodeId = dataRef.current.floatingContext?.nodeId;
3247
- const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
3248
- if (children.length > 0) {
3249
- let shouldDismiss = true;
3250
- children.forEach(child => {
3251
- if (child.context?.open && !child.context.dataRef.current.__outsidePressBubbles) {
3252
- shouldDismiss = false;
3253
- }
3254
- });
3255
- if (!shouldDismiss) {
3256
- return;
3257
- }
3079
+ if (hasBlockingChild('__outsidePressBubbles')) {
3080
+ return;
3258
3081
  }
3259
3082
  store.setOpen(false, createChangeEventDetails(outsidePress, event));
3260
3083
  clearInsideReactTree();
3261
3084
  }
3262
3085
  function handlePointerDown(event) {
3263
- if (getOutsidePressEvent() !== 'sloppy' || event.pointerType === 'touch' || !store.select('open') || !enabled || isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement'))) {
3086
+ if (getOutsidePressEvent() !== 'sloppy' || event.pointerType === 'touch' || !store.select('open') || !enabled || isEventWithinOwnElements(event)) {
3264
3087
  return;
3265
3088
  }
3266
3089
  closeOnPressOutside(event);
3267
3090
  }
3268
3091
  function handleTouchStart(event) {
3269
- if (getOutsidePressEvent() !== 'sloppy' || !store.select('open') || !enabled || isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement'))) {
3092
+ if (getOutsidePressEvent() !== 'sloppy' || !store.select('open') || !enabled || isEventWithinOwnElements(event)) {
3270
3093
  return;
3271
3094
  }
3272
3095
  const touch = event.touches[0];
@@ -3286,14 +3109,19 @@ function useDismiss(context, props = {}) {
3286
3109
  });
3287
3110
  }
3288
3111
  }
3289
- function handleTouchStartCapture(event) {
3290
- currentPointerTypeRef.current = 'touch';
3112
+ function addTargetEventListenerOnce(event, listener) {
3291
3113
  const target = getTarget(event);
3292
- function callback() {
3293
- handleTouchStart(event);
3294
- target?.removeEventListener(event.type, callback);
3114
+ if (!target) {
3115
+ return;
3295
3116
  }
3296
- target?.addEventListener(event.type, callback);
3117
+ const unsubscribe = addEventListener(target, event.type, () => {
3118
+ listener(event);
3119
+ unsubscribe();
3120
+ });
3121
+ }
3122
+ function handleTouchStartCapture(event) {
3123
+ currentPointerTypeRef.current = 'touch';
3124
+ addTargetEventListenerOnce(event, handleTouchStart);
3297
3125
  }
3298
3126
  function closeOnPressOutsideCapture(event) {
3299
3127
  cancelDismissOnEndTimeout.clear();
@@ -3303,16 +3131,13 @@ function useDismiss(context, props = {}) {
3303
3131
  if (event.type === 'mousedown' && touchStateRef.current && !touchStateRef.current.dismissOnMouseDown) {
3304
3132
  return;
3305
3133
  }
3306
- const target = getTarget(event);
3307
- function callback() {
3308
- if (event.type === 'pointerdown') {
3309
- handlePointerDown(event);
3134
+ addTargetEventListenerOnce(event, targetEvent => {
3135
+ if (targetEvent.type === 'pointerdown') {
3136
+ handlePointerDown(targetEvent);
3310
3137
  } else {
3311
- closeOnPressOutside(event);
3138
+ closeOnPressOutside(targetEvent);
3312
3139
  }
3313
- target?.removeEventListener(event.type, callback);
3314
- }
3315
- target?.addEventListener(event.type, callback);
3140
+ });
3316
3141
  }
3317
3142
  function handlePressEndCapture(event) {
3318
3143
  if (!pressStartedInsideRef.current) {
@@ -3351,7 +3176,7 @@ function useDismiss(context, props = {}) {
3351
3176
  clearInsideReactTree();
3352
3177
  }
3353
3178
  function handleTouchMove(event) {
3354
- if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement'))) {
3179
+ if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || isEventWithinOwnElements(event)) {
3355
3180
  return;
3356
3181
  }
3357
3182
  const touch = event.touches[0];
@@ -3371,15 +3196,10 @@ function useDismiss(context, props = {}) {
3371
3196
  }
3372
3197
  }
3373
3198
  function handleTouchMoveCapture(event) {
3374
- const target = getTarget(event);
3375
- function callback() {
3376
- handleTouchMove(event);
3377
- target?.removeEventListener(event.type, callback);
3378
- }
3379
- target?.addEventListener(event.type, callback);
3199
+ addTargetEventListenerOnce(event, handleTouchMove);
3380
3200
  }
3381
3201
  function handleTouchEnd(event) {
3382
- if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement'))) {
3202
+ if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || isEventWithinOwnElements(event)) {
3383
3203
  return;
3384
3204
  }
3385
3205
  if (touchStateRef.current.dismissOnTouchEnd) {
@@ -3389,97 +3209,24 @@ function useDismiss(context, props = {}) {
3389
3209
  touchStateRef.current = null;
3390
3210
  }
3391
3211
  function handleTouchEndCapture(event) {
3392
- const target = getTarget(event);
3393
- function callback() {
3394
- handleTouchEnd(event);
3395
- target?.removeEventListener(event.type, callback);
3396
- }
3397
- target?.addEventListener(event.type, callback);
3212
+ addTargetEventListenerOnce(event, handleTouchEnd);
3398
3213
  }
3399
3214
  const doc = ownerDocument(floatingElement);
3400
- if (escapeKey$1) {
3401
- doc.addEventListener('keydown', closeOnEscapeKeyDown);
3402
- doc.addEventListener('compositionstart', handleCompositionStart);
3403
- doc.addEventListener('compositionend', handleCompositionEnd);
3404
- }
3405
- if (outsidePressEnabled) {
3406
- doc.addEventListener('click', closeOnPressOutsideCapture, true);
3407
- doc.addEventListener('pointerdown', closeOnPressOutsideCapture, true);
3408
- doc.addEventListener('pointerup', handlePressEndCapture, true);
3409
- doc.addEventListener('pointercancel', handlePressEndCapture, true);
3410
- doc.addEventListener('mousedown', closeOnPressOutsideCapture, true);
3411
- doc.addEventListener('mouseup', handlePressEndCapture, true);
3412
- doc.addEventListener('touchstart', handleTouchStartCapture, true);
3413
- doc.addEventListener('touchmove', handleTouchMoveCapture, true);
3414
- doc.addEventListener('touchend', handleTouchEndCapture, true);
3415
- }
3215
+ const unsubscribe = mergeCleanups(escapeKey$1 && mergeCleanups(addEventListener(doc, 'keydown', closeOnEscapeKeyDown), addEventListener(doc, 'compositionstart', handleCompositionStart), addEventListener(doc, 'compositionend', handleCompositionEnd)), outsidePressEnabled && mergeCleanups(addEventListener(doc, 'click', closeOnPressOutsideCapture, true), addEventListener(doc, 'pointerdown', closeOnPressOutsideCapture, true), addEventListener(doc, 'pointerup', handlePressEndCapture, true), addEventListener(doc, 'pointercancel', handlePressEndCapture, true), addEventListener(doc, 'mousedown', closeOnPressOutsideCapture, true), addEventListener(doc, 'mouseup', handlePressEndCapture, true), addEventListener(doc, 'touchstart', handleTouchStartCapture, true), addEventListener(doc, 'touchmove', handleTouchMoveCapture, true), addEventListener(doc, 'touchend', handleTouchEndCapture, true)));
3416
3216
  return () => {
3417
- if (escapeKey$1) {
3418
- doc.removeEventListener('keydown', closeOnEscapeKeyDown);
3419
- doc.removeEventListener('compositionstart', handleCompositionStart);
3420
- doc.removeEventListener('compositionend', handleCompositionEnd);
3421
- }
3422
- if (outsidePressEnabled) {
3423
- doc.removeEventListener('click', closeOnPressOutsideCapture, true);
3424
- doc.removeEventListener('pointerdown', closeOnPressOutsideCapture, true);
3425
- doc.removeEventListener('pointerup', handlePressEndCapture, true);
3426
- doc.removeEventListener('pointercancel', handlePressEndCapture, true);
3427
- doc.removeEventListener('mousedown', closeOnPressOutsideCapture, true);
3428
- doc.removeEventListener('mouseup', handlePressEndCapture, true);
3429
- doc.removeEventListener('touchstart', handleTouchStartCapture, true);
3430
- doc.removeEventListener('touchmove', handleTouchMoveCapture, true);
3431
- doc.removeEventListener('touchend', handleTouchEndCapture, true);
3432
- }
3217
+ unsubscribe();
3433
3218
  compositionTimeout.clear();
3434
3219
  preventedPressSuppressionTimeout.clear();
3435
3220
  resetPressStartState();
3436
3221
  suppressNextOutsideClickRef.current = false;
3437
3222
  };
3438
- }, [dataRef, floatingElement, escapeKey$1, outsidePressEnabled, outsidePress$1, open, enabled, escapeKeyBubbles, outsidePressBubbles, closeOnEscapeKeyDown, clearInsideReactTree, getOutsidePressEventProp, tree, store, cancelDismissOnEndTimeout]);
3223
+ }, [dataRef, floatingElement, escapeKey$1, outsidePressEnabled, outsidePress$1, open, enabled, escapeKeyBubbles, outsidePressBubbles, closeOnEscapeKeyDown, clearInsideReactTree, getOutsidePressEventProp, hasBlockingChild, isEventWithinOwnElements, tree, store, cancelDismissOnEndTimeout]);
3439
3224
  React.useEffect(clearInsideReactTree, [outsidePress$1, clearInsideReactTree]);
3440
3225
  const reference = React.useMemo(() => ({
3441
3226
  onKeyDown: closeOnEscapeKeyDown,
3442
- [bubbleHandlerKeys[referencePressEvent]]: event => {
3443
- if (!isReferencePressEnabled()) {
3444
- return;
3445
- }
3446
- store.setOpen(false, createChangeEventDetails(triggerPress, event.nativeEvent));
3447
- },
3448
- ...(referencePressEvent !== 'intentional' && {
3449
- onClick(event) {
3450
- if (!isReferencePressEnabled()) {
3451
- return;
3452
- }
3453
- store.setOpen(false, createChangeEventDetails(triggerPress, event.nativeEvent));
3454
- }
3455
- })
3456
- }), [closeOnEscapeKeyDown, store, referencePressEvent, isReferencePressEnabled]);
3457
- const markPressStartedInsideReactTree = useStableCallback(event => {
3458
- if (!open || !enabled || event.button !== 0) {
3459
- return;
3460
- }
3461
- const target = getTarget(event.nativeEvent);
3462
- // Only treat presses that start within the floating DOM subtree as inside.
3463
- // This avoids suppressing parent dismissal when interacting with nested portals.
3464
- if (!contains(store.select('floatingElement'), target)) {
3465
- return;
3466
- }
3467
- if (!pressStartedInsideRef.current) {
3468
- pressStartedInsideRef.current = true;
3469
- pressStartPreventedRef.current = false;
3470
- }
3471
- });
3472
- const markInsidePressStartPrevented = useStableCallback(event => {
3473
- if (!open || !enabled) {
3474
- return;
3475
- }
3476
- if (!(event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
3477
- return;
3478
- }
3479
- if (pressStartedInsideRef.current) {
3480
- pressStartPreventedRef.current = true;
3481
- }
3482
- });
3227
+ onPointerDown: closeOnReferencePress,
3228
+ onClick: closeOnReferencePress
3229
+ }), [closeOnEscapeKeyDown, closeOnReferencePress]);
3483
3230
  const floating = React.useMemo(() => ({
3484
3231
  onKeyDown: closeOnEscapeKeyDown,
3485
3232
  // `onMouseDown` may be blocked if `event.preventDefault()` is called in
@@ -3509,6 +3256,7 @@ function useDismiss(context, props = {}) {
3509
3256
 
3510
3257
  const selectors = {
3511
3258
  open: createSelector(state => state.open),
3259
+ transitionStatus: createSelector(state => state.transitionStatus),
3512
3260
  domReferenceElement: createSelector(state => state.domReferenceElement),
3513
3261
  referenceElement: createSelector(state => state.positionReference ?? state.referenceElement),
3514
3262
  floatingElement: createSelector(state => state.floatingElement),
@@ -3517,8 +3265,8 @@ const selectors = {
3517
3265
  class FloatingRootStore extends ReactStore {
3518
3266
  constructor(options) {
3519
3267
  const {
3268
+ syncOnly,
3520
3269
  nested,
3521
- noEmit,
3522
3270
  onOpenChange,
3523
3271
  triggerElements,
3524
3272
  ...initialState
@@ -3534,11 +3282,38 @@ class FloatingRootStore extends ReactStore {
3534
3282
  },
3535
3283
  events: createEventEmitter(),
3536
3284
  nested,
3537
- noEmit,
3538
3285
  triggerElements
3539
3286
  }, selectors);
3287
+ this.syncOnly = syncOnly;
3540
3288
  }
3541
3289
 
3290
+ /**
3291
+ * Syncs the event used by hover logic to distinguish hover-open from click-like interaction.
3292
+ */
3293
+ syncOpenEvent = (newOpen, event) => {
3294
+ if (!newOpen || !this.state.open ||
3295
+ // Prevent a pending hover-open from overwriting a click-open event, while allowing
3296
+ // click events to upgrade a hover-open.
3297
+ event != null && isClickLikeEvent(event)) {
3298
+ this.context.dataRef.current.openEvent = newOpen ? event : undefined;
3299
+ }
3300
+ };
3301
+
3302
+ /**
3303
+ * Runs the root-owned side effects for an open state change.
3304
+ */
3305
+ dispatchOpenChange = (newOpen, eventDetails) => {
3306
+ this.syncOpenEvent(newOpen, eventDetails.event);
3307
+ const details = {
3308
+ open: newOpen,
3309
+ reason: eventDetails.reason,
3310
+ nativeEvent: eventDetails.event,
3311
+ nested: this.context.nested,
3312
+ triggerElement: eventDetails.trigger
3313
+ };
3314
+ this.context.events.emit('openchange', details);
3315
+ };
3316
+
3542
3317
  /**
3543
3318
  * Emits the `openchange` event through the internal event emitter and calls the `onOpenChange` handler with the provided arguments.
3544
3319
  *
@@ -3546,26 +3321,385 @@ class FloatingRootStore extends ReactStore {
3546
3321
  * @param eventDetails Details about the event that triggered the open state change.
3547
3322
  */
3548
3323
  setOpen = (newOpen, eventDetails) => {
3549
- if (!newOpen || !this.state.open ||
3550
- // Prevent a pending hover-open from overwriting a click-open event, while allowing
3551
- // click events to upgrade a hover-open.
3552
- isClickLikeEvent(eventDetails.event)) {
3553
- this.context.dataRef.current.openEvent = newOpen ? eventDetails.event : undefined;
3554
- }
3555
- if (!this.context.noEmit) {
3556
- const details = {
3557
- open: newOpen,
3558
- reason: eventDetails.reason,
3559
- nativeEvent: eventDetails.event,
3560
- nested: this.context.nested,
3561
- triggerElement: eventDetails.trigger
3562
- };
3563
- this.context.events.emit('openchange', details);
3324
+ if (this.syncOnly) {
3325
+ this.context.onOpenChange?.(newOpen, eventDetails);
3326
+ return;
3564
3327
  }
3328
+ this.dispatchOpenChange(newOpen, eventDetails);
3565
3329
  this.context.onOpenChange?.(newOpen, eventDetails);
3566
3330
  };
3567
3331
  }
3568
3332
 
3333
+ /**
3334
+ * Keeps a FloatingRootStore in sync with the provided PopupStore.
3335
+ * Uses the provided FloatingRootStore when one exists, otherwise creates one once and updates it on every render.
3336
+ */
3337
+ function useSyncedFloatingRootContext(options) {
3338
+ const {
3339
+ popupStore,
3340
+ treatPopupAsFloatingElement = false,
3341
+ floatingRootContext: floatingRootContextProp,
3342
+ floatingId,
3343
+ nested,
3344
+ onOpenChange
3345
+ } = options;
3346
+ const open = popupStore.useState('open');
3347
+ const referenceElement = popupStore.useState('activeTriggerElement');
3348
+ const floatingElement = popupStore.useState(treatPopupAsFloatingElement ? 'popupElement' : 'positionerElement');
3349
+ const triggerElements = popupStore.context.triggerElements;
3350
+ const handleOpenChange = onOpenChange;
3351
+ const internalStoreRef = React.useRef(null);
3352
+ if (floatingRootContextProp === undefined && internalStoreRef.current === null) {
3353
+ internalStoreRef.current = new FloatingRootStore({
3354
+ open,
3355
+ transitionStatus: undefined,
3356
+ referenceElement,
3357
+ floatingElement,
3358
+ triggerElements,
3359
+ onOpenChange: handleOpenChange,
3360
+ floatingId,
3361
+ syncOnly: true,
3362
+ nested
3363
+ });
3364
+ }
3365
+ const store = floatingRootContextProp ?? internalStoreRef.current;
3366
+ popupStore.useSyncedValue('floatingId', floatingId);
3367
+ useIsoLayoutEffect(() => {
3368
+ const valuesToSync = {
3369
+ open,
3370
+ floatingId,
3371
+ referenceElement,
3372
+ floatingElement
3373
+ };
3374
+ if (isElement(referenceElement)) {
3375
+ valuesToSync.domReferenceElement = referenceElement;
3376
+ }
3377
+ if (store.state.positionReference === store.state.referenceElement) {
3378
+ valuesToSync.positionReference = referenceElement;
3379
+ }
3380
+ store.update(valuesToSync);
3381
+ }, [open, floatingId, referenceElement, floatingElement, store]);
3382
+
3383
+ // Keep non-reactive context values fresh for interactions that call `store.setOpen`.
3384
+ store.context.onOpenChange = handleOpenChange;
3385
+ store.context.nested = nested;
3386
+ return store;
3387
+ }
3388
+
3389
+ const FOCUSABLE_POPUP_PROPS = {
3390
+ tabIndex: -1,
3391
+ [FOCUSABLE_ATTRIBUTE]: ''
3392
+ };
3393
+
3394
+ /**
3395
+ * Returns the default `initialFocus` resolver for a popup. When opened by touch it focuses the
3396
+ * popup element itself to prevent the virtual keyboard from opening (required for Android
3397
+ * specifically; iOS handles this automatically). Otherwise it falls back to the default behavior.
3398
+ */
3399
+ function createDefaultInitialFocus(popupRef) {
3400
+ return interactionType => interactionType === 'touch' ? popupRef.current : true;
3401
+ }
3402
+ function usePopupStore(externalStore, createStore, treatPopupAsFloatingElement = false) {
3403
+ const floatingId = useId();
3404
+ const nested = useFloatingParentNodeId() != null;
3405
+ const internalStoreRef = React.useRef(null);
3406
+ if (externalStore === undefined && internalStoreRef.current === null) {
3407
+ internalStoreRef.current = createStore(floatingId, nested);
3408
+ }
3409
+ const store = externalStore ?? internalStoreRef.current;
3410
+ useSyncedFloatingRootContext({
3411
+ popupStore: store,
3412
+ treatPopupAsFloatingElement,
3413
+ floatingRootContext: store.state.floatingRootContext,
3414
+ floatingId,
3415
+ nested,
3416
+ onOpenChange: store.setOpen
3417
+ });
3418
+ return {
3419
+ store,
3420
+ internalStore: internalStoreRef.current
3421
+ };
3422
+ }
3423
+
3424
+ /**
3425
+ * Returns a callback ref that registers/unregisters the trigger element in the store.
3426
+ *
3427
+ * @param store The Store instance where the trigger should be registered.
3428
+ */
3429
+ function useTriggerRegistration(id, store) {
3430
+ // Keep track of the currently registered element to unregister it on unmount or id change.
3431
+ const registeredElementIdRef = React.useRef(null);
3432
+ const registeredElementRef = React.useRef(null);
3433
+ return React.useCallback(element => {
3434
+ if (id === undefined) {
3435
+ return;
3436
+ }
3437
+ let shouldSyncTriggerCount = false;
3438
+ if (registeredElementIdRef.current !== null) {
3439
+ const registeredId = registeredElementIdRef.current;
3440
+ const registeredElement = registeredElementRef.current;
3441
+ const currentElement = store.context.triggerElements.getById(registeredId);
3442
+ if (registeredElement && currentElement === registeredElement) {
3443
+ store.context.triggerElements.delete(registeredId);
3444
+ shouldSyncTriggerCount = true;
3445
+ }
3446
+ registeredElementIdRef.current = null;
3447
+ registeredElementRef.current = null;
3448
+ }
3449
+ if (element !== null) {
3450
+ registeredElementIdRef.current = id;
3451
+ registeredElementRef.current = element;
3452
+ store.context.triggerElements.add(id, element);
3453
+ shouldSyncTriggerCount = true;
3454
+ }
3455
+ if (shouldSyncTriggerCount) {
3456
+ const triggerCount = store.context.triggerElements.size;
3457
+ if (store.select('open') && store.state.triggerCount !== triggerCount) {
3458
+ store.set('triggerCount', triggerCount);
3459
+ }
3460
+ }
3461
+ }, [store, id]);
3462
+ }
3463
+ function setPopupOpenState(state, open, trigger, preventUnmountOnClose = false) {
3464
+ if (open) {
3465
+ // Opening starts a new close cycle, so clear any previous request to keep the popup mounted.
3466
+ state.preventUnmountingOnClose = false;
3467
+ } else if (preventUnmountOnClose) {
3468
+ state.preventUnmountingOnClose = true;
3469
+ }
3470
+ const triggerId = trigger?.id ?? null;
3471
+
3472
+ // If a popup is closing, the `trigger` may be undefined.
3473
+ // We want to keep the previous value so that exit animations are played and focus is returned correctly.
3474
+ if (triggerId || open) {
3475
+ state.activeTriggerId = triggerId;
3476
+ state.activeTriggerElement = trigger ?? null;
3477
+ }
3478
+ }
3479
+ function attachPreventUnmountOnClose(eventDetails) {
3480
+ let preventUnmountOnClose = false;
3481
+ eventDetails.preventUnmountOnClose = () => {
3482
+ preventUnmountOnClose = true;
3483
+ };
3484
+ return () => preventUnmountOnClose;
3485
+ }
3486
+ function useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerId) {
3487
+ useOnFirstRender(() => {
3488
+ if (openProp === undefined && store.state.open === false && defaultOpen) {
3489
+ // Avoid notifying detached trigger subscribers while the Root is rendering.
3490
+ store.state = {
3491
+ ...store.state,
3492
+ open: true,
3493
+ activeTriggerId: defaultTriggerId,
3494
+ preventUnmountingOnClose: false
3495
+ };
3496
+ }
3497
+ });
3498
+ }
3499
+
3500
+ /**
3501
+ * Sets up trigger data forwarding to the store.
3502
+ *
3503
+ * @param triggerId Id of the trigger.
3504
+ * @param triggerElementRef Ref for the trigger DOM element.
3505
+ * @param store The Store instance managing the popup state.
3506
+ * @param stateUpdates An object with state updates to apply when the trigger is active.
3507
+ */
3508
+ function useTriggerDataForwarding(triggerId, triggerElementRef, store, stateUpdates) {
3509
+ const isMountedByThisTrigger = store.useState('isMountedByTrigger', triggerId);
3510
+ const baseRegisterTrigger = useTriggerRegistration(triggerId, store);
3511
+ const registerTrigger = useStableCallback(element => {
3512
+ baseRegisterTrigger(element);
3513
+ if (!element) {
3514
+ return;
3515
+ }
3516
+ const open = store.select('open');
3517
+ const activeTriggerId = store.select('activeTriggerId');
3518
+ if (activeTriggerId === triggerId) {
3519
+ store.update({
3520
+ activeTriggerElement: element,
3521
+ ...(open ? stateUpdates : null)
3522
+ });
3523
+ return;
3524
+ }
3525
+ if (activeTriggerId == null && open) {
3526
+ // If a popup is already open, a detached trigger can mount before any active trigger
3527
+ // has been established. Claim the first registered trigger so trigger-owned focus
3528
+ // management and ARIA relationships work.
3529
+ store.update({
3530
+ activeTriggerId: triggerId,
3531
+ activeTriggerElement: element,
3532
+ ...stateUpdates
3533
+ });
3534
+ }
3535
+ });
3536
+ useIsoLayoutEffect(() => {
3537
+ if (isMountedByThisTrigger) {
3538
+ store.update({
3539
+ activeTriggerElement: triggerElementRef.current,
3540
+ ...stateUpdates
3541
+ });
3542
+ }
3543
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3544
+ }, [isMountedByThisTrigger, store, triggerElementRef, ...Object.values(stateUpdates)]);
3545
+ return {
3546
+ registerTrigger,
3547
+ isMountedByThisTrigger
3548
+ };
3549
+ }
3550
+ /**
3551
+ * Keeps trigger registration state synchronized while the popup is open.
3552
+ *
3553
+ * When a popup opens without an explicit trigger id and exactly one trigger is registered, that
3554
+ * trigger is claimed as the active trigger. When the active trigger id is still registered but its
3555
+ * element changed, the active element is refreshed. When the active trigger unregisters, the
3556
+ * default path preserves existing ownership so non-closing popup families do not silently claim a
3557
+ * different trigger while staying open.
3558
+ *
3559
+ * If `closeOnActiveTriggerUnmount` is enabled, unregistering the active trigger requests a close
3560
+ * after a microtask so a same-tick replacement trigger with the same id can register first.
3561
+ *
3562
+ * This should be called on the Root part.
3563
+ *
3564
+ * @param store The Store instance managing the popup state.
3565
+ * @param options Options for active trigger unmount behavior.
3566
+ */
3567
+ function useImplicitActiveTrigger(store, options = {}) {
3568
+ const {
3569
+ closeOnActiveTriggerUnmount = false
3570
+ } = options;
3571
+ const open = store.useState('open');
3572
+ const reactiveTriggerCount = store.useState('triggerCount');
3573
+ useIsoLayoutEffect(() => {
3574
+ if (!open) {
3575
+ if (store.state.triggerCount !== 0) {
3576
+ store.set('triggerCount', 0);
3577
+ }
3578
+ return;
3579
+ }
3580
+ const triggerCount = store.context.triggerElements.size;
3581
+ const stateUpdates = {};
3582
+ if (store.state.triggerCount !== triggerCount) {
3583
+ stateUpdates.triggerCount = triggerCount;
3584
+ }
3585
+ const activeTriggerId = store.select('activeTriggerId');
3586
+ let lostActiveTriggerId = null;
3587
+ if (activeTriggerId) {
3588
+ const activeTriggerElement = store.context.triggerElements.getById(activeTriggerId);
3589
+ if (!activeTriggerElement) {
3590
+ lostActiveTriggerId = activeTriggerId;
3591
+ } else if (activeTriggerElement !== store.state.activeTriggerElement) {
3592
+ stateUpdates.activeTriggerElement = activeTriggerElement;
3593
+ }
3594
+ }
3595
+ if (!lostActiveTriggerId && !activeTriggerId && triggerCount === 1) {
3596
+ const iteratorResult = store.context.triggerElements.entries().next();
3597
+ if (!iteratorResult.done) {
3598
+ const [implicitTriggerId, implicitTriggerElement] = iteratorResult.value;
3599
+ stateUpdates.activeTriggerId = implicitTriggerId;
3600
+ stateUpdates.activeTriggerElement = implicitTriggerElement;
3601
+ }
3602
+ }
3603
+ if (stateUpdates.triggerCount !== undefined || stateUpdates.activeTriggerId !== undefined || stateUpdates.activeTriggerElement !== undefined) {
3604
+ store.update(stateUpdates);
3605
+ }
3606
+ if (lostActiveTriggerId) {
3607
+ if (closeOnActiveTriggerUnmount) {
3608
+ // Defer so a same-tick replacement trigger with the same id can register first.
3609
+ queueMicrotask(() => {
3610
+ if (store.select('open') && store.select('activeTriggerId') === lostActiveTriggerId && !store.context.triggerElements.getById(lostActiveTriggerId)) {
3611
+ const eventDetails = createChangeEventDetails(none);
3612
+ store.setOpen(false, eventDetails);
3613
+ // If closing is canceled, keep the previous active trigger ownership for the
3614
+ // still-open popup instead of claiming another trigger implicitly.
3615
+ if (!eventDetails.isCanceled) {
3616
+ store.update({
3617
+ activeTriggerId: null,
3618
+ activeTriggerElement: null
3619
+ });
3620
+ }
3621
+ }
3622
+ });
3623
+ }
3624
+ }
3625
+ }, [open, store, reactiveTriggerCount, closeOnActiveTriggerUnmount]);
3626
+ }
3627
+
3628
+ /**
3629
+ * Manages the mounted state of the popup.
3630
+ * Sets up the transition status listeners and handles unmounting when needed.
3631
+ * Updates the `mounted`, `transitionStatus`, and `preventUnmountingOnClose` states in the store.
3632
+ *
3633
+ * @param open Whether the popup is open.
3634
+ * @param store The Store instance managing the popup state.
3635
+ * @param onUnmount Optional callback to be called when the popup is unmounted.
3636
+ *
3637
+ * @returns A function to forcibly unmount the popup.
3638
+ */
3639
+ function useOpenStateTransitions(open, store, onUnmount) {
3640
+ const {
3641
+ mounted,
3642
+ setMounted,
3643
+ transitionStatus
3644
+ } = useTransitionStatus(open);
3645
+ const preventUnmountingOnClose = store.useState('preventUnmountingOnClose');
3646
+ // Opening starts a new close cycle. Clear during render so the close-completion hook below
3647
+ // reads the synchronized value on the same pass.
3648
+ const syncedPreventUnmountingOnClose = open ? false : preventUnmountingOnClose;
3649
+ store.useSyncedValues({
3650
+ mounted,
3651
+ transitionStatus,
3652
+ preventUnmountingOnClose: syncedPreventUnmountingOnClose
3653
+ });
3654
+ const forceUnmount = useStableCallback(() => {
3655
+ setMounted(false);
3656
+ store.update({
3657
+ activeTriggerId: null,
3658
+ activeTriggerElement: null,
3659
+ mounted: false,
3660
+ preventUnmountingOnClose: false
3661
+ });
3662
+ onUnmount?.();
3663
+ store.context.onOpenChangeComplete?.(false);
3664
+ });
3665
+ useOpenChangeComplete({
3666
+ enabled: mounted && !open && !syncedPreventUnmountingOnClose,
3667
+ open,
3668
+ ref: store.context.popupRef,
3669
+ onComplete() {
3670
+ if (!open) {
3671
+ forceUnmount();
3672
+ }
3673
+ }
3674
+ });
3675
+ return {
3676
+ forceUnmount,
3677
+ transitionStatus
3678
+ };
3679
+ }
3680
+ function usePopupInteractionProps(store, statePart) {
3681
+ store.useSyncedValues(statePart);
3682
+ useIsoLayoutEffect(() => () => {
3683
+ store.update({
3684
+ activeTriggerProps: EMPTY_OBJECT,
3685
+ inactiveTriggerProps: EMPTY_OBJECT,
3686
+ popupProps: EMPTY_OBJECT
3687
+ });
3688
+ }, [store]);
3689
+ }
3690
+ function usePopupRootSync(store, open) {
3691
+ useIsoLayoutEffect(() => {
3692
+ if (!open && store.state.openMethod !== null) {
3693
+ store.set('openMethod', null);
3694
+ }
3695
+ }, [open, store]);
3696
+ useIsoLayoutEffect(() => () => {
3697
+ if (store.state.openMethod !== null) {
3698
+ store.set('openMethod', null);
3699
+ }
3700
+ }, [store]);
3701
+ }
3702
+
3569
3703
  /**
3570
3704
  * Data structure to keep track of popup trigger elements by their IDs.
3571
3705
  * Uses both a set of Elements and a map of IDs to Elements for efficient lookups.
@@ -3595,6 +3729,8 @@ class PopupTriggerMap {
3595
3729
  this.idMap.set(id, element);
3596
3730
  if (process.env.NODE_ENV !== 'production') {
3597
3731
  if (this.elementsSet.size !== this.idMap.size) {
3732
+ // TODO: fix mui/no-guarded-throw
3733
+ // eslint-disable-next-line mui/no-guarded-throw
3598
3734
  throw new Error('Base UI: A trigger element cannot be registered under multiple IDs in PopupTriggerMap.');
3599
3735
  }
3600
3736
  }
@@ -3659,93 +3795,6 @@ class PopupTriggerMap {
3659
3795
  }
3660
3796
  }
3661
3797
 
3662
- /**
3663
- * Merges an array of interaction hooks' props into prop getters, allowing
3664
- * event handler functions to be composed together without overwriting one
3665
- * another.
3666
- * @see https://floating-ui.com/docs/useInteractions
3667
- */
3668
- function useInteractions(propsList = []) {
3669
- const referenceDeps = propsList.map(key => key?.reference);
3670
- const floatingDeps = propsList.map(key => key?.floating);
3671
- const itemDeps = propsList.map(key => key?.item);
3672
- const triggerDeps = propsList.map(key => key?.trigger);
3673
- const getReferenceProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'reference'),
3674
- // eslint-disable-next-line react-hooks/exhaustive-deps
3675
- referenceDeps);
3676
- const getFloatingProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'floating'),
3677
- // eslint-disable-next-line react-hooks/exhaustive-deps
3678
- floatingDeps);
3679
- const getItemProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'item'),
3680
- // eslint-disable-next-line react-hooks/exhaustive-deps
3681
- itemDeps);
3682
- const getTriggerProps = React.useCallback(userProps => mergeProps(userProps, propsList, 'trigger'),
3683
- // eslint-disable-next-line react-hooks/exhaustive-deps
3684
- triggerDeps);
3685
- return React.useMemo(() => ({
3686
- getReferenceProps,
3687
- getFloatingProps,
3688
- getItemProps,
3689
- getTriggerProps
3690
- }), [getReferenceProps, getFloatingProps, getItemProps, getTriggerProps]);
3691
- }
3692
-
3693
- /* eslint-disable guard-for-in */
3694
-
3695
- function mergeProps(userProps, propsList, elementKey) {
3696
- const eventHandlers = new Map();
3697
- const isItem = elementKey === 'item';
3698
- const outputProps = {};
3699
- if (elementKey === 'floating') {
3700
- outputProps.tabIndex = -1;
3701
- outputProps[FOCUSABLE_ATTRIBUTE] = '';
3702
- }
3703
- for (const key in userProps) {
3704
- if (isItem && userProps) {
3705
- if (key === ACTIVE_KEY || key === SELECTED_KEY) {
3706
- continue;
3707
- }
3708
- }
3709
- outputProps[key] = userProps[key];
3710
- }
3711
- for (let i = 0; i < propsList.length; i += 1) {
3712
- let props;
3713
- const propsOrGetProps = propsList[i]?.[elementKey];
3714
- if (typeof propsOrGetProps === 'function') {
3715
- props = userProps ? propsOrGetProps(userProps) : null;
3716
- } else {
3717
- props = propsOrGetProps;
3718
- }
3719
- if (!props) {
3720
- continue;
3721
- }
3722
- mutablyMergeProps(outputProps, props, isItem, eventHandlers);
3723
- }
3724
- mutablyMergeProps(outputProps, userProps, isItem, eventHandlers);
3725
- return outputProps;
3726
- }
3727
- function mutablyMergeProps(outputProps, props, isItem, eventHandlers) {
3728
- for (const key in props) {
3729
- const value = props[key];
3730
- if (isItem && (key === ACTIVE_KEY || key === SELECTED_KEY)) {
3731
- continue;
3732
- }
3733
- if (!key.startsWith('on')) {
3734
- outputProps[key] = value;
3735
- } else {
3736
- if (!eventHandlers.has(key)) {
3737
- eventHandlers.set(key, []);
3738
- }
3739
- if (typeof value === 'function') {
3740
- eventHandlers.get(key)?.push(value);
3741
- outputProps[key] = (...args) => {
3742
- return eventHandlers.get(key)?.map(fn => fn(...args)).find(val => val !== undefined);
3743
- };
3744
- }
3745
- }
3746
- }
3747
- }
3748
-
3749
3798
  /**
3750
3799
  * Provides a cross-browser way to determine the type of the pointer used to click.
3751
3800
  * Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.
@@ -3782,38 +3831,54 @@ function useEnhancedClickHandler(handler) {
3782
3831
  };
3783
3832
  }
3784
3833
 
3785
- /**
3786
- * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.
3787
- *
3788
- * @param open The open state of the component.
3789
- */
3790
- function useOpenInteractionType(open) {
3791
- const [openMethod, setOpenMethod] = React.useState(null);
3834
+ function useOpenMethodTriggerProps(open, setOpenMethod) {
3792
3835
  const handleTriggerClick = useStableCallback((_, interactionType) => {
3793
- if (!open) {
3836
+ const isOpen = typeof open === 'function' ? open() : open;
3837
+ if (!isOpen) {
3794
3838
  setOpenMethod(interactionType || (
3795
3839
  // On iOS Safari, the hitslop around touch targets means tapping outside an element's
3796
3840
  // bounds does not fire `pointerdown` but does fire `mousedown`. The `interactionType`
3797
3841
  // will be "" in that case.
3798
- isIOS ? 'touch' : ''));
3842
+ ios ? 'touch' : ''));
3799
3843
  }
3800
3844
  });
3845
+ const {
3846
+ onClick,
3847
+ onPointerDown
3848
+ } = useEnhancedClickHandler(handleTriggerClick);
3849
+ return React.useMemo(() => ({
3850
+ onClick,
3851
+ onPointerDown
3852
+ }), [onClick, onPointerDown]);
3853
+ }
3854
+
3855
+ /**
3856
+ * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.
3857
+ *
3858
+ * @param open The open state of the component.
3859
+ */
3860
+ function useOpenInteractionType(open) {
3861
+ const [openMethod, setOpenMethod] = React.useState(null);
3862
+ const triggerProps = useOpenMethodTriggerProps(open, setOpenMethod);
3801
3863
  useValueChanged(open, previousOpen => {
3802
3864
  if (previousOpen && !open) {
3803
3865
  setOpenMethod(null);
3804
3866
  }
3805
3867
  });
3806
- const {
3807
- onClick,
3808
- onPointerDown
3809
- } = useEnhancedClickHandler(handleTriggerClick);
3810
3868
  return React.useMemo(() => ({
3811
3869
  openMethod,
3812
- triggerProps: {
3813
- onClick,
3814
- onPointerDown
3815
- }
3816
- }), [openMethod, onClick, onPointerDown]);
3870
+ triggerProps
3871
+ }), [openMethod, triggerProps]);
3872
+ }
3873
+
3874
+ /**
3875
+ * @internal
3876
+ */
3877
+ const DirectionContext = /*#__PURE__*/React.createContext(undefined);
3878
+ if (process.env.NODE_ENV !== "production") DirectionContext.displayName = "DirectionContext";
3879
+ function useDirection() {
3880
+ const context = React.useContext(DirectionContext);
3881
+ return context?.direction ?? 'ltr';
3817
3882
  }
3818
3883
 
3819
3884
  let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
@@ -3907,16 +3972,6 @@ const popupStateMapping = {
3907
3972
  }
3908
3973
  };
3909
3974
 
3910
- /**
3911
- * @internal
3912
- */
3913
- const DirectionContext = /*#__PURE__*/React.createContext(undefined);
3914
- if (process.env.NODE_ENV !== "production") DirectionContext.displayName = "DirectionContext";
3915
- function useDirection() {
3916
- const context = React.useContext(DirectionContext);
3917
- return context?.direction ?? 'ltr';
3918
- }
3919
-
3920
3975
  function inertValue(value) {
3921
3976
  if (isReactVersionAtLeast(19)) {
3922
3977
  return value;
@@ -3925,6 +3980,35 @@ function inertValue(value) {
3925
3980
  return value ? 'true' : undefined;
3926
3981
  }
3927
3982
 
3983
+ const InternalBackdrop = /*#__PURE__*/React.forwardRef(function InternalBackdrop(props, ref) {
3984
+ const {
3985
+ cutout,
3986
+ ...otherProps
3987
+ } = props;
3988
+ let clipPath;
3989
+ if (cutout) {
3990
+ const rect = cutout.getBoundingClientRect();
3991
+ clipPath = `polygon(0% 0%,100% 0%,100% 100%,0% 100%,0% 0%,${rect.left}px ${rect.top}px,${rect.left}px ${rect.bottom}px,${rect.right}px ${rect.bottom}px,${rect.right}px ${rect.top}px,${rect.left}px ${rect.top}px)`;
3992
+ }
3993
+ return /*#__PURE__*/jsx("div", {
3994
+ ref: ref,
3995
+ role: "presentation"
3996
+ // Ensures Floating UI's outside press detection runs, as it considers
3997
+ // it an element that existed when the popup rendered.
3998
+ ,
3999
+ "data-base-ui-inert": "",
4000
+ ...otherProps,
4001
+ style: {
4002
+ position: 'fixed',
4003
+ inset: 0,
4004
+ userSelect: 'none',
4005
+ WebkitUserSelect: 'none',
4006
+ clipPath
4007
+ }
4008
+ });
4009
+ });
4010
+ if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "InternalBackdrop";
4011
+
3928
4012
  let originalHtmlStyles = {};
3929
4013
  let originalBodyStyles = {};
3930
4014
  let originalHtmlScrollBehavior = '';
@@ -3989,7 +4073,7 @@ function preventScrollInsetScrollbars(referenceElement) {
3989
4073
  const resizeFrame = AnimationFrame.create();
3990
4074
 
3991
4075
  // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom.
3992
- if (isWebKit && (win.visualViewport?.scale ?? 1) !== 1) {
4076
+ if (webkit && (win.visualViewport?.scale ?? 1) !== 1) {
3993
4077
  return () => {};
3994
4078
  }
3995
4079
  function lockScroll() {
@@ -4083,16 +4167,15 @@ function preventScrollInsetScrollbars(referenceElement) {
4083
4167
  resizeFrame.request(lockScroll);
4084
4168
  }
4085
4169
  lockScroll();
4086
- win.addEventListener('resize', handleResize);
4170
+ const unsubscribeResize = addEventListener(win, 'resize', handleResize);
4087
4171
  return () => {
4088
4172
  resizeFrame.cancel();
4089
4173
  cleanup();
4090
- // Sometimes this cleanup can be run after test teardown
4091
- // because it is called in a `setTimeout(fn, 0)`,
4092
- // in which case `removeEventListener` wouldn't be available,
4093
- // so we check for it to avoid test failures.
4174
+ // Sometimes this cleanup can run after test teardown because it is called
4175
+ // in a `setTimeout(fn, 0)`. Guard the returned cleanup to avoid calling
4176
+ // `removeEventListener` when it is no longer available in tests.
4094
4177
  if (typeof win.removeEventListener === 'function') {
4095
- win.removeEventListener('resize', handleResize);
4178
+ unsubscribeResize();
4096
4179
  }
4097
4180
  };
4098
4181
  }
@@ -4133,7 +4216,7 @@ class ScrollLocker {
4133
4216
  this.restore = NOOP;
4134
4217
  return;
4135
4218
  }
4136
- const hasOverlayScrollbars = isIOS || !hasInsetScrollbars(referenceElement);
4219
+ const hasOverlayScrollbars = ios || !hasInsetScrollbars(referenceElement);
4137
4220
 
4138
4221
  // On iOS, scroll locking does not work if the navbar is collapsed. Due to numerous
4139
4222
  // side effects and bugs that arise on iOS, it must be researched extensively before
@@ -4161,44 +4244,4 @@ function useScrollLock(enabled = true, referenceElement = null) {
4161
4244
  }, [enabled, referenceElement]);
4162
4245
  }
4163
4246
 
4164
- const InternalBackdrop = /*#__PURE__*/React.forwardRef(function InternalBackdrop(props, ref) {
4165
- const {
4166
- cutout,
4167
- ...otherProps
4168
- } = props;
4169
- let clipPath;
4170
- if (cutout) {
4171
- const rect = cutout?.getBoundingClientRect();
4172
- clipPath = `polygon(
4173
- 0% 0%,
4174
- 100% 0%,
4175
- 100% 100%,
4176
- 0% 100%,
4177
- 0% 0%,
4178
- ${rect.left}px ${rect.top}px,
4179
- ${rect.left}px ${rect.bottom}px,
4180
- ${rect.right}px ${rect.bottom}px,
4181
- ${rect.right}px ${rect.top}px,
4182
- ${rect.left}px ${rect.top}px
4183
- )`;
4184
- }
4185
- return /*#__PURE__*/jsx("div", {
4186
- ref: ref,
4187
- role: "presentation"
4188
- // Ensures Floating UI's outside press detection runs, as it considers
4189
- // it an element that existed when the popup rendered.
4190
- ,
4191
- "data-base-ui-inert": "",
4192
- ...otherProps,
4193
- style: {
4194
- position: 'fixed',
4195
- inset: 0,
4196
- userSelect: 'none',
4197
- WebkitUserSelect: 'none',
4198
- clipPath
4199
- }
4200
- });
4201
- });
4202
- if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "InternalBackdrop";
4203
-
4204
- export { BASE_UI_SWIPE_IGNORE_SELECTOR as $, inertValue as A, DROPDOWN_COLLISION_AVOIDANCE as B, CommonPopupDataAttributes as C, DISABLED_TRANSITIONS_STYLE as D, CLICK_TRIGGER_IDENTIFIER as E, FloatingFocusManager as F, triggerOpenStateMapping as G, createAttribute as H, InternalBackdrop as I, getNodeChildren as J, POPUP_COLLISION_AVOIDANCE as K, FloatingNode as L, FloatingTreeStore as M, fastComponent as N, useFloatingNodeId as O, PopupTriggerMap as P, FloatingTree as Q, ReactStore as R, Store as S, TYPEAHEAD_RESET_MS as T, fastComponentRef as U, getTabbableBeforeElement as V, isOutsideEvent as W, getTabbableAfterElement as X, getNextTabbable as Y, FocusGuard as Z, PATIENT_CLICK_THRESHOLD as _, FloatingRootStore as a, useFloatingTree as b, getMaxListIndex as c, createGridCellMap as d, isListIndexDisabled as e, getGridNavigatedIndex as f, getMinListIndex as g, getGridCellIndices as h, isIndexOutOfListBounds as i, getGridCellIndexOfCorner as j, findNonDisabledListIndex as k, enqueueFocus as l, isElementVisible as m, useDirection as n, createSelector as o, popupStateMapping as p, useOpenInteractionType as q, useStore as r, useClick as s, useDismiss as t, useFloatingParentNodeId as u, useInteractions as v, useOnFirstRender as w, pressableTriggerOpenStateMapping as x, FloatingPortal as y, useScrollLock as z };
4247
+ export { createAttribute as $, ARROW_UP as A, pressableTriggerOpenStateMapping as B, CommonPopupDataAttributes as C, DISABLED_TRANSITIONS_STYLE as D, FloatingPortal as E, FOCUSABLE_POPUP_PROPS as F, inertValue as G, DROPDOWN_COLLISION_AVOIDANCE as H, InternalBackdrop as I, webkit as J, usePopupRootSync as K, useImplicitActiveTrigger as L, useOpenStateTransitions as M, usePopupInteractionProps as N, setPopupOpenState as O, PopupTriggerMap as P, usePopupStore as Q, ReactStore as R, Store as S, useTriggerDataForwarding as T, useOpenMethodTriggerProps as U, CLICK_TRIGGER_IDENTIFIER as V, triggerOpenStateMapping as W, mergeCleanups as X, isTypeableElement as Y, matchesFocusVisible as Z, isTargetInsideEnabledTrigger as _, FloatingFocusManager as a, isInteractiveElement as a0, getNodeChildren as a1, POPUP_COLLISION_AVOIDANCE as a2, FloatingNode as a3, FloatingTreeStore as a4, fastComponent as a5, useInitialOpenSync as a6, useFloatingNodeId as a7, attachPreventUnmountOnClose as a8, useSyncedFloatingRootContext as a9, TYPEAHEAD_RESET_MS as aa, FloatingTree as ab, getTabbableBeforeElement as ac, isOutsideEvent as ad, getTabbableAfterElement as ae, getNextTabbable as af, fastComponentRef as ag, FocusGuard as ah, PATIENT_CLICK_THRESHOLD as ai, useTriggerRegistration as aj, getGridNavigatedIndex as ak, gecko as al, BASE_UI_SWIPE_IGNORE_SELECTOR as am, FloatingRootStore as b, createDefaultInitialFocus as c, useFloatingTree as d, isIndexOutOfListBounds as e, getMinListIndex as f, getFloatingFocusElement as g, getMaxListIndex as h, isTypeableCombobox as i, findNonDisabledListIndex as j, enqueueFocus as k, ARROW_DOWN as l, ARROW_LEFT as m, ARROW_RIGHT as n, isListIndexDisabled as o, popupStateMapping as p, isElementVisible as q, useDirection as r, useScrollLock as s, createSelector as t, useFloatingParentNodeId as u, useOpenInteractionType as v, useStore as w, useClick as x, useDismiss as y, useOnFirstRender as z };