@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,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var React = require('react');
4
- var index_esm = require('./index.esm-cjs.js');
5
4
  var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
6
- var useRenderElement = require('./useRenderElement-cjs.js');
7
5
  var useButton = require('./useButton-cjs.js');
6
+ var useRenderElement = require('./useRenderElement-cjs.js');
8
7
  var jsxRuntime = require('react/jsx-runtime');
8
+ var floatingUi_utils = require('./floating-ui.utils-cjs.js');
9
9
  var ReactDOM = require('react-dom');
10
10
 
11
11
  function _interopNamespaceDefault(e) {
@@ -36,449 +36,24 @@ function useOnFirstRender(fn) {
36
36
  }
37
37
  }
38
38
 
39
- // src/devModeChecks/identityFunctionCheck.ts
40
- var runIdentityFunctionCheck = (resultFunc, inputSelectorsResults, outputSelectorResult) => {
41
- if (inputSelectorsResults.length === 1 && inputSelectorsResults[0] === outputSelectorResult) {
42
- let isInputSameAsOutput = false;
43
- try {
44
- const emptyObject = {};
45
- if (resultFunc(emptyObject) === emptyObject)
46
- isInputSameAsOutput = true;
47
- } catch {
48
- }
49
- if (isInputSameAsOutput) {
50
- let stack = void 0;
51
- try {
52
- throw new Error();
53
- } catch (e) {
54
- ({ stack } = e);
55
- }
56
- console.warn(
57
- "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.",
58
- { stack }
59
- );
60
- }
61
- }
62
- };
63
-
64
- // src/devModeChecks/inputStabilityCheck.ts
65
- var runInputStabilityCheck = (inputSelectorResultsObject, options, inputSelectorArgs) => {
66
- const { memoize, memoizeOptions } = options;
67
- const { inputSelectorResults, inputSelectorResultsCopy } = inputSelectorResultsObject;
68
- const createAnEmptyObject = memoize(() => ({}), ...memoizeOptions);
69
- const areInputSelectorResultsEqual = createAnEmptyObject.apply(null, inputSelectorResults) === createAnEmptyObject.apply(null, inputSelectorResultsCopy);
70
- if (!areInputSelectorResultsEqual) {
71
- let stack = void 0;
72
- try {
73
- throw new Error();
74
- } catch (e) {
75
- ({ stack } = e);
76
- }
77
- console.warn(
78
- "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)`",
79
- {
80
- arguments: inputSelectorArgs,
81
- firstInputs: inputSelectorResults,
82
- secondInputs: inputSelectorResultsCopy,
83
- stack
84
- }
85
- );
86
- }
87
- };
88
-
89
- // src/devModeChecks/setGlobalDevModeChecks.ts
90
- var globalDevModeChecks = {
91
- inputStabilityCheck: "once",
92
- identityFunctionCheck: "once"
93
- };
94
-
95
- // src/utils.ts
96
- var NOT_FOUND = /* @__PURE__ */ Symbol("NOT_FOUND");
97
- function assertIsFunction(func, errorMessage = `expected a function, instead received ${typeof func}`) {
98
- if (typeof func !== "function") {
99
- throw new TypeError(errorMessage);
100
- }
101
- }
102
- function assertIsObject(object, errorMessage = `expected an object, instead received ${typeof object}`) {
103
- if (typeof object !== "object") {
104
- throw new TypeError(errorMessage);
105
- }
106
- }
107
- function assertIsArrayOfFunctions(array, errorMessage = `expected all items to be functions, instead received the following types: `) {
108
- if (!array.every((item) => typeof item === "function")) {
109
- const itemTypes = array.map(
110
- (item) => typeof item === "function" ? `function ${item.name || "unnamed"}()` : typeof item
111
- ).join(", ");
112
- throw new TypeError(`${errorMessage}[${itemTypes}]`);
113
- }
114
- }
115
- var ensureIsArray = (item) => {
116
- return Array.isArray(item) ? item : [item];
117
- };
118
- function getDependencies(createSelectorArgs) {
119
- const dependencies = Array.isArray(createSelectorArgs[0]) ? createSelectorArgs[0] : createSelectorArgs;
120
- assertIsArrayOfFunctions(
121
- dependencies,
122
- `createSelector expects all input-selectors to be functions, but received the following types: `
123
- );
124
- return dependencies;
125
- }
126
- function collectInputSelectorResults(dependencies, inputSelectorArgs) {
127
- const inputSelectorResults = [];
128
- const { length } = dependencies;
129
- for (let i = 0; i < length; i++) {
130
- inputSelectorResults.push(dependencies[i].apply(null, inputSelectorArgs));
131
- }
132
- return inputSelectorResults;
133
- }
134
- var getDevModeChecksExecutionInfo = (firstRun, devModeChecks) => {
135
- const { identityFunctionCheck, inputStabilityCheck } = {
136
- ...globalDevModeChecks,
137
- ...devModeChecks
138
- };
139
- return {
140
- identityFunctionCheck: {
141
- shouldRun: identityFunctionCheck === "always" || identityFunctionCheck === "once" && firstRun,
142
- run: runIdentityFunctionCheck
143
- },
144
- inputStabilityCheck: {
145
- shouldRun: inputStabilityCheck === "always" || inputStabilityCheck === "once" && firstRun,
146
- run: runInputStabilityCheck
147
- }
148
- };
149
- };
150
-
151
- // src/lruMemoize.ts
152
- function createSingletonCache(equals) {
153
- let entry;
154
- return {
155
- get(key) {
156
- if (entry && equals(entry.key, key)) {
157
- return entry.value;
158
- }
159
- return NOT_FOUND;
160
- },
161
- put(key, value) {
162
- entry = { key, value };
163
- },
164
- getEntries() {
165
- return entry ? [entry] : [];
166
- },
167
- clear() {
168
- entry = void 0;
169
- }
170
- };
171
- }
172
- function createLruCache(maxSize, equals) {
173
- let entries = [];
174
- function get(key) {
175
- const cacheIndex = entries.findIndex((entry) => equals(key, entry.key));
176
- if (cacheIndex > -1) {
177
- const entry = entries[cacheIndex];
178
- if (cacheIndex > 0) {
179
- entries.splice(cacheIndex, 1);
180
- entries.unshift(entry);
181
- }
182
- return entry.value;
183
- }
184
- return NOT_FOUND;
185
- }
186
- function put(key, value) {
187
- if (get(key) === NOT_FOUND) {
188
- entries.unshift({ key, value });
189
- if (entries.length > maxSize) {
190
- entries.pop();
191
- }
192
- }
193
- }
194
- function getEntries() {
195
- return entries;
196
- }
197
- function clear() {
198
- entries = [];
199
- }
200
- return { get, put, getEntries, clear };
201
- }
202
- var referenceEqualityCheck = (a, b) => a === b;
203
- function createCacheKeyComparator(equalityCheck) {
204
- return function areArgumentsShallowlyEqual(prev, next) {
205
- if (prev === null || next === null || prev.length !== next.length) {
206
- return false;
207
- }
208
- const { length } = prev;
209
- for (let i = 0; i < length; i++) {
210
- if (!equalityCheck(prev[i], next[i])) {
211
- return false;
212
- }
213
- }
214
- return true;
215
- };
216
- }
217
- function lruMemoize(func, equalityCheckOrOptions) {
218
- const providedOptions = typeof equalityCheckOrOptions === "object" ? equalityCheckOrOptions : { equalityCheck: equalityCheckOrOptions };
219
- const {
220
- equalityCheck = referenceEqualityCheck,
221
- maxSize = 1,
222
- resultEqualityCheck
223
- } = providedOptions;
224
- const comparator = createCacheKeyComparator(equalityCheck);
225
- let resultsCount = 0;
226
- const cache = maxSize <= 1 ? createSingletonCache(comparator) : createLruCache(maxSize, comparator);
227
- function memoized() {
228
- let value = cache.get(arguments);
229
- if (value === NOT_FOUND) {
230
- value = func.apply(null, arguments);
231
- resultsCount++;
232
- if (resultEqualityCheck) {
233
- const entries = cache.getEntries();
234
- const matchingEntry = entries.find(
235
- (entry) => resultEqualityCheck(entry.value, value)
236
- );
237
- if (matchingEntry) {
238
- value = matchingEntry.value;
239
- resultsCount !== 0 && resultsCount--;
240
- }
241
- }
242
- cache.put(arguments, value);
243
- }
244
- return value;
245
- }
246
- memoized.clearCache = () => {
247
- cache.clear();
248
- memoized.resetResultsCount();
249
- };
250
- memoized.resultsCount = () => resultsCount;
251
- memoized.resetResultsCount = () => {
252
- resultsCount = 0;
253
- };
254
- return memoized;
255
- }
256
-
257
- // src/weakMapMemoize.ts
258
- var StrongRef = class {
259
- constructor(value) {
260
- this.value = value;
261
- }
262
- deref() {
263
- return this.value;
264
- }
265
- };
266
- var Ref = typeof WeakRef !== "undefined" ? WeakRef : StrongRef;
267
- var UNTERMINATED = 0;
268
- var TERMINATED = 1;
269
- function createCacheNode() {
270
- return {
271
- s: UNTERMINATED,
272
- v: void 0,
273
- o: null,
274
- p: null
275
- };
276
- }
277
- function weakMapMemoize(func, options = {}) {
278
- let fnNode = createCacheNode();
279
- const { resultEqualityCheck } = options;
280
- let lastResult;
281
- let resultsCount = 0;
282
- function memoized() {
283
- let cacheNode = fnNode;
284
- const { length } = arguments;
285
- for (let i = 0, l = length; i < l; i++) {
286
- const arg = arguments[i];
287
- if (typeof arg === "function" || typeof arg === "object" && arg !== null) {
288
- let objectCache = cacheNode.o;
289
- if (objectCache === null) {
290
- cacheNode.o = objectCache = /* @__PURE__ */ new WeakMap();
291
- }
292
- const objectNode = objectCache.get(arg);
293
- if (objectNode === void 0) {
294
- cacheNode = createCacheNode();
295
- objectCache.set(arg, cacheNode);
296
- } else {
297
- cacheNode = objectNode;
298
- }
299
- } else {
300
- let primitiveCache = cacheNode.p;
301
- if (primitiveCache === null) {
302
- cacheNode.p = primitiveCache = /* @__PURE__ */ new Map();
303
- }
304
- const primitiveNode = primitiveCache.get(arg);
305
- if (primitiveNode === void 0) {
306
- cacheNode = createCacheNode();
307
- primitiveCache.set(arg, cacheNode);
308
- } else {
309
- cacheNode = primitiveNode;
310
- }
311
- }
312
- }
313
- const terminatedNode = cacheNode;
314
- let result;
315
- if (cacheNode.s === TERMINATED) {
316
- result = cacheNode.v;
317
- } else {
318
- result = func.apply(null, arguments);
319
- resultsCount++;
320
- if (resultEqualityCheck) {
321
- const lastResultValue = lastResult?.deref?.() ?? lastResult;
322
- if (lastResultValue != null && resultEqualityCheck(lastResultValue, result)) {
323
- result = lastResultValue;
324
- resultsCount !== 0 && resultsCount--;
325
- }
326
- const needsWeakRef = typeof result === "object" && result !== null || typeof result === "function";
327
- lastResult = needsWeakRef ? new Ref(result) : result;
328
- }
329
- }
330
- terminatedNode.s = TERMINATED;
331
- terminatedNode.v = result;
332
- return result;
333
- }
334
- memoized.clearCache = () => {
335
- fnNode = createCacheNode();
336
- memoized.resetResultsCount();
337
- };
338
- memoized.resultsCount = () => resultsCount;
339
- memoized.resetResultsCount = () => {
340
- resultsCount = 0;
341
- };
342
- return memoized;
343
- }
344
-
345
- // src/createSelectorCreator.ts
346
- function createSelectorCreator(memoizeOrOptions, ...memoizeOptionsFromArgs) {
347
- const createSelectorCreatorOptions = typeof memoizeOrOptions === "function" ? {
348
- memoize: memoizeOrOptions,
349
- memoizeOptions: memoizeOptionsFromArgs
350
- } : memoizeOrOptions;
351
- const createSelector2 = (...createSelectorArgs) => {
352
- let recomputations = 0;
353
- let dependencyRecomputations = 0;
354
- let lastResult;
355
- let directlyPassedOptions = {};
356
- let resultFunc = createSelectorArgs.pop();
357
- if (typeof resultFunc === "object") {
358
- directlyPassedOptions = resultFunc;
359
- resultFunc = createSelectorArgs.pop();
360
- }
361
- assertIsFunction(
362
- resultFunc,
363
- `createSelector expects an output function after the inputs, but received: [${typeof resultFunc}]`
364
- );
365
- const combinedOptions = {
366
- ...createSelectorCreatorOptions,
367
- ...directlyPassedOptions
368
- };
369
- const {
370
- memoize,
371
- memoizeOptions = [],
372
- argsMemoize = weakMapMemoize,
373
- argsMemoizeOptions = [],
374
- devModeChecks = {}
375
- } = combinedOptions;
376
- const finalMemoizeOptions = ensureIsArray(memoizeOptions);
377
- const finalArgsMemoizeOptions = ensureIsArray(argsMemoizeOptions);
378
- const dependencies = getDependencies(createSelectorArgs);
379
- const memoizedResultFunc = memoize(function recomputationWrapper() {
380
- recomputations++;
381
- return resultFunc.apply(
382
- null,
383
- arguments
384
- );
385
- }, ...finalMemoizeOptions);
386
- let firstRun = true;
387
- const selector = argsMemoize(function dependenciesChecker() {
388
- dependencyRecomputations++;
389
- const inputSelectorResults = collectInputSelectorResults(
390
- dependencies,
391
- arguments
392
- );
393
- lastResult = memoizedResultFunc.apply(null, inputSelectorResults);
394
- if (process.env.NODE_ENV !== "production") {
395
- const { identityFunctionCheck, inputStabilityCheck } = getDevModeChecksExecutionInfo(firstRun, devModeChecks);
396
- if (identityFunctionCheck.shouldRun) {
397
- identityFunctionCheck.run(
398
- resultFunc,
399
- inputSelectorResults,
400
- lastResult
401
- );
402
- }
403
- if (inputStabilityCheck.shouldRun) {
404
- const inputSelectorResultsCopy = collectInputSelectorResults(
405
- dependencies,
406
- arguments
407
- );
408
- inputStabilityCheck.run(
409
- { inputSelectorResults, inputSelectorResultsCopy },
410
- { memoize, memoizeOptions: finalMemoizeOptions },
411
- arguments
412
- );
413
- }
414
- if (firstRun)
415
- firstRun = false;
416
- }
417
- return lastResult;
418
- }, ...finalArgsMemoizeOptions);
419
- return Object.assign(selector, {
420
- resultFunc,
421
- memoizedResultFunc,
422
- dependencies,
423
- dependencyRecomputations: () => dependencyRecomputations,
424
- resetDependencyRecomputations: () => {
425
- dependencyRecomputations = 0;
426
- },
427
- lastResult: () => lastResult,
428
- recomputations: () => recomputations,
429
- resetRecomputations: () => {
430
- recomputations = 0;
431
- },
432
- memoize,
433
- argsMemoize
434
- });
435
- };
436
- Object.assign(createSelector2, {
437
- withTypes: () => createSelector2
438
- });
439
- return createSelector2;
440
- }
441
- var createSelector$1 = /* @__PURE__ */ createSelectorCreator(weakMapMemoize);
442
-
443
- // src/createStructuredSelector.ts
444
- var createStructuredSelector = Object.assign(
445
- (inputSelectorsObject, selectorCreator = createSelector$1) => {
446
- assertIsObject(
447
- inputSelectorsObject,
448
- `createStructuredSelector expects first argument to be an object where each property is a selector, instead received a ${typeof inputSelectorsObject}`
449
- );
450
- const inputSelectorKeys = Object.keys(inputSelectorsObject);
451
- const dependencies = inputSelectorKeys.map(
452
- (key) => inputSelectorsObject[key]
453
- );
454
- const structuredSelector = selectorCreator(
455
- dependencies,
456
- (...inputSelectorResults) => {
457
- return inputSelectorResults.reduce((composition, value, index) => {
458
- composition[inputSelectorKeys[index]] = value;
459
- return composition;
460
- }, {});
461
- }
462
- );
463
- return structuredSelector;
464
- },
465
- { withTypes: () => createStructuredSelector }
466
- );
467
-
468
- /* eslint-disable no-underscore-dangle */ // __cacheKey__
39
+ /**
40
+ * The NoOptionalParams type is a utility type that checks if a function has optional or default parameters.
41
+ * If the function has optional or default parameters, it returns a string literal type with an error message.
42
+ * Otherwise, it returns the original function type.
43
+ *
44
+ * This is used to enforce that the combiner function passed to createSelector does not have optional or default parameters,
45
+ * as memoization relies on the Function.length property, which does not account for optional or default parameters.
46
+ */
469
47
 
470
- createSelectorCreator({
471
- memoize: lruMemoize,
472
- memoizeOptions: {
473
- maxSize: 1,
474
- equalityCheck: Object.is
475
- }
476
- });
477
48
  /**
478
49
  * Creates a selector function that can be used to derive values from the store's state.
479
- * The selector can take up to three additional arguments that can be used in the selector logic.
50
+ *
51
+ * The combiner function can have up to three additional parameters, but it **cannot have optional or default parameters**.
52
+ *
480
53
  * This function accepts up to six functions and combines them into a single selector function.
481
- * The last parameter is the combiner function that combines the results of the previous selectors.
54
+ * The resulting selector will take the state from the combined selectors and any additional parameters required by the combiner.
55
+ *
56
+ * The return type of the resulting selector is determined by the return type of the combiner function.
482
57
  *
483
58
  * @example
484
59
  * const selector = createSelector(
@@ -491,7 +66,6 @@ createSelectorCreator({
491
66
  * (state) => state.open,
492
67
  * (disabled, open) => ({ disabled, open })
493
68
  * );
494
- *
495
69
  */
496
70
  /* eslint-disable id-denylist */
497
71
  const createSelector = (a, b, c, d, e, f, ...other) => {
@@ -541,6 +115,7 @@ const createSelector = (a, b, c, d, e, f, ...other) => {
541
115
  }
542
116
  return selector;
543
117
  };
118
+ /* eslint-enable id-denylist */
544
119
 
545
120
  var shim = {exports: {}};
546
121
 
@@ -1003,10 +578,9 @@ register({
1003
578
  for (let i = 0; i < instance.syncHooks.length; i += 1) {
1004
579
  const hook = instance.syncHooks[i];
1005
580
  const value = hook.selector(hook.store.state, hook.a1, hook.a2, hook.a3);
1006
- if (hook.didChange || !Object.is(hook.value, value)) {
581
+ if (!Object.is(hook.value, value)) {
1007
582
  didChange = true;
1008
583
  hook.value = value;
1009
- hook.didChange = false;
1010
584
  }
1011
585
  }
1012
586
  if (didChange) {
@@ -1057,8 +631,7 @@ function useStoreFast(store, selector, a1, a2, a3) {
1057
631
  a1,
1058
632
  a2,
1059
633
  a3,
1060
- value: selector(store.getSnapshot(), a1, a2, a3),
1061
- didChange: false
634
+ value: selector(store.getSnapshot(), a1, a2, a3)
1062
635
  };
1063
636
  instance.syncHooks.push(hook);
1064
637
  } else {
@@ -1072,7 +645,7 @@ function useStoreFast(store, selector, a1, a2, a3) {
1072
645
  hook.a1 = a1;
1073
646
  hook.a2 = a2;
1074
647
  hook.a3 = a3;
1075
- hook.didChange = true;
648
+ hook.value = selector(store.getSnapshot(), a1, a2, a3);
1076
649
  }
1077
650
  }
1078
651
  return hook.value;
@@ -1224,11 +797,13 @@ class ReactStore extends Store {
1224
797
  */
1225
798
  useSyncedValue(key, value) {
1226
799
  React__namespace.useDebugValue(key);
800
+ // eslint-disable-next-line consistent-this
801
+ const store = this;
1227
802
  useButton.useIsoLayoutEffect(() => {
1228
- if (this.state[key] !== value) {
1229
- this.set(key, value);
803
+ if (store.state[key] !== value) {
804
+ store.set(key, value);
1230
805
  }
1231
- }, [key, value]);
806
+ }, [store, key, value]);
1232
807
  }
1233
808
 
1234
809
  /**
@@ -1282,16 +857,18 @@ class ReactStore extends Store {
1282
857
  */
1283
858
  useControlledProp(key, controlled) {
1284
859
  React__namespace.useDebugValue(key);
860
+ // eslint-disable-next-line consistent-this
861
+ const store = this;
1285
862
  const isControlled = controlled !== undefined;
1286
863
  useButton.useIsoLayoutEffect(() => {
1287
- if (isControlled && !Object.is(this.state[key], controlled)) {
864
+ if (isControlled && !Object.is(store.state[key], controlled)) {
1288
865
  // Set the internal state to match the controlled value.
1289
- super.setState({
1290
- ...this.state,
866
+ store.setState({
867
+ ...store.state,
1291
868
  [key]: controlled
1292
869
  });
1293
870
  }
1294
- }, [key, controlled, isControlled]);
871
+ }, [store, key, controlled, isControlled]);
1295
872
  if (process.env.NODE_ENV !== 'production') {
1296
873
  // eslint-disable-next-line
1297
874
  const cache = this.controlledValues ??= new Map();
@@ -1384,44 +961,169 @@ class ReactStore extends Store {
1384
961
  }
1385
962
  }
1386
963
 
1387
- /* eslint-disable @typescript-eslint/no-loop-func */
1388
-
1389
- function getNodeChildren(nodes, id, onlyOpenChildren = true) {
1390
- const directChildren = nodes.filter(node => node.parentId === id && (!onlyOpenChildren || node.context?.open));
1391
- return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
964
+ /**
965
+ * Combines multiple cleanup functions into a single cleanup function.
966
+ */
967
+ function mergeCleanups(...cleanups) {
968
+ return () => {
969
+ for (let i = 0; i < cleanups.length; i += 1) {
970
+ const cleanup = cleanups[i];
971
+ if (cleanup) {
972
+ cleanup();
973
+ }
974
+ }
975
+ };
1392
976
  }
1393
- function getNodeAncestors(nodes, id) {
1394
- let allAncestors = [];
1395
- let currentParentId = nodes.find(node => node.id === id)?.parentId;
1396
- while (currentParentId) {
1397
- const currentNode = nodes.find(node => node.id === currentParentId);
1398
- currentParentId = currentNode?.parentId;
1399
- if (currentNode) {
1400
- allAncestors = allAncestors.concat(currentNode);
977
+
978
+ // WebKit (Safari, all iOS browsers, GNOME Web). Distinguished from Blink by
979
+ // the legacy `-webkit-backdrop-filter` name Blink forked from WebKit in 2013
980
+ // and only ships the unprefixed `backdrop-filter`.
981
+ /** WebKit: Safari, all iOS browsers, GNOME Web. Excludes Blink. */
982
+ const webkit = typeof CSS !== 'undefined' && !!CSS.supports?.('-webkit-backdrop-filter:none');
983
+
984
+ // Anchored to `!webkit` so engines are mutually exclusive by construction.
985
+ // Firefox-on-iOS uses WebKit (its UA marker is `FxiOS/`, not `Firefox/`); the
986
+ // `!webkit` prefix also defends against future iOS-browser UA changes that
987
+ // might inject `Firefox` into a WebKit-based UA.
988
+ /** Gecko: Firefox. */
989
+ const gecko = !webkit && useButton.lowerUserAgent.includes('firefox');
990
+
991
+ // All Chromium-based browsers ship `Chrome/` or `Chromium/` in their UA — both
992
+ // share the `chrom` prefix, so a single substring check covers Chrome, Edge,
993
+ // Opera, Brave, etc. Chrome-on-iOS uses `CriOS/` and stays WebKit. The positive
994
+ // UA check also makes this SSR-safe — an empty UA matches nothing.
995
+ /** Blink: Chrome, Edge, Opera, Brave, and other Chromium-based browsers. */
996
+ !webkit && useButton.lowerUserAgent.includes('chrom');
997
+
998
+ // Whether a screen reader is *actually* running cannot be detected. These flags
999
+ // identify platforms where a specific screen reader could be active. VoiceOver
1000
+ // is the system screen reader on Apple platforms and works with every browser
1001
+ // there, so the flag is purely an OS check; engine-specific quirks (e.g. the
1002
+ // NSAccessibility virtual-cursor focus path) should be gated at the call site.
1003
+ /**
1004
+ * The user *may* be using VoiceOver — actual activation is not detectable.
1005
+ * True on any Apple platform (macOS, iOS, iPadOS).
1006
+ */
1007
+ const voiceOver = useButton.apple;
1008
+
1009
+ const FOCUSABLE_ATTRIBUTE = 'data-base-ui-focusable';
1010
+ const TYPEABLE_SELECTOR = "input:not([type='hidden']):not([disabled])," + "[contenteditable]:not([contenteditable='false']),textarea:not([disabled])";
1011
+ const ARROW_LEFT = 'ArrowLeft';
1012
+ const ARROW_RIGHT = 'ArrowRight';
1013
+ const ARROW_UP = 'ArrowUp';
1014
+ const ARROW_DOWN = 'ArrowDown';
1015
+
1016
+ function isTargetInsideEnabledTrigger(target, triggerElements) {
1017
+ if (!floatingUi_utils_dom.isElement(target)) {
1018
+ return false;
1019
+ }
1020
+ const targetElement = target;
1021
+ if (triggerElements.hasElement(targetElement)) {
1022
+ return !targetElement.hasAttribute('data-trigger-disabled');
1023
+ }
1024
+ for (const [, trigger] of triggerElements.entries()) {
1025
+ if (useButton.contains(trigger, targetElement)) {
1026
+ return !trigger.hasAttribute('data-trigger-disabled');
1401
1027
  }
1402
1028
  }
1403
- return allAncestors;
1029
+ return false;
1030
+ }
1031
+ function isEventTargetWithin(event, node) {
1032
+ if (node == null) {
1033
+ return false;
1034
+ }
1035
+ if ('composedPath' in event) {
1036
+ return event.composedPath().includes(node);
1037
+ }
1038
+
1039
+ // TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't
1040
+ const eventAgain = event;
1041
+ return eventAgain.target != null && node.contains(eventAgain.target);
1042
+ }
1043
+ function isRootElement(element) {
1044
+ return element.matches('html,body');
1045
+ }
1046
+ function isTypeableElement(element) {
1047
+ return floatingUi_utils_dom.isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
1048
+ }
1049
+ function isInteractiveElement(element) {
1050
+ return element?.closest(`button,a[href],[role="button"],select,[tabindex]:not([tabindex="-1"]),${TYPEABLE_SELECTOR}`) != null;
1404
1051
  }
1052
+ function isTypeableCombobox(element) {
1053
+ if (!element) {
1054
+ return false;
1055
+ }
1056
+ return element.getAttribute('role') === 'combobox' && isTypeableElement(element);
1057
+ }
1058
+ function matchesFocusVisible(element) {
1059
+ // We don't want to block focus from working with `visibleOnly`
1060
+ // (JSDOM doesn't match `:focus-visible` when the element has `:focus`)
1061
+ if (!element || useButton.jsdom) {
1062
+ return true;
1063
+ }
1064
+ try {
1065
+ return element.matches(':focus-visible');
1066
+ } catch (_e) {
1067
+ return true;
1068
+ }
1069
+ }
1070
+ function getFloatingFocusElement(floatingElement) {
1071
+ if (!floatingElement) {
1072
+ return null;
1073
+ }
1074
+ // Try to find the element that has `{...getFloatingProps()}` spread on it.
1075
+ // This indicates the floating element is acting as a positioning wrapper, and
1076
+ // so focus should be managed on the child element with the event handlers and
1077
+ // aria props.
1078
+ return floatingElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? floatingElement : floatingElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`) || floatingElement;
1079
+ }
1080
+
1081
+ const FocusGuard = /*#__PURE__*/React__namespace.forwardRef(function FocusGuard(props, ref) {
1082
+ const [role, setRole] = React__namespace.useState();
1083
+ useButton.useIsoLayoutEffect(() => {
1084
+ // Unlike NVDA and JAWS, VoiceOver's virtual cursor triggers `onFocus` as
1085
+ // it moves — but only on focusable/role-button elements through WebKit's
1086
+ // NSAccessibility path. Setting `role="button"` lets the focus trap catch
1087
+ // the cursor.
1088
+ if (voiceOver && webkit) {
1089
+ setRole('button');
1090
+ }
1091
+ }, []);
1092
+ const restProps = {
1093
+ tabIndex: 0,
1094
+ // Role is only for VoiceOver
1095
+ role
1096
+ };
1097
+ return /*#__PURE__*/jsxRuntime.jsx("span", {
1098
+ ...props,
1099
+ ref: ref,
1100
+ style: useButton.visuallyHidden,
1101
+ "aria-hidden": role ? undefined : true,
1102
+ ...restProps,
1103
+ "data-base-ui-focus-guard": ""
1104
+ });
1105
+ });
1106
+ if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1405
1107
 
1406
1108
  function isDifferentGridRow(index, cols, prevRow) {
1407
1109
  return Math.floor(index / cols) !== prevRow;
1408
1110
  }
1409
- function isIndexOutOfListBounds(listRef, index) {
1410
- return index < 0 || index >= listRef.current.length;
1111
+ function isIndexOutOfListBounds(list, index) {
1112
+ return index < 0 || index >= list.length;
1411
1113
  }
1412
1114
  function getMinListIndex(listRef, disabledIndices) {
1413
- return findNonDisabledListIndex(listRef, {
1115
+ return findNonDisabledListIndex(listRef.current, {
1414
1116
  disabledIndices
1415
1117
  });
1416
1118
  }
1417
1119
  function getMaxListIndex(listRef, disabledIndices) {
1418
- return findNonDisabledListIndex(listRef, {
1120
+ return findNonDisabledListIndex(listRef.current, {
1419
1121
  decrement: true,
1420
1122
  startingIndex: listRef.current.length,
1421
1123
  disabledIndices
1422
1124
  });
1423
1125
  }
1424
- function findNonDisabledListIndex(listRef, {
1126
+ function findNonDisabledListIndex(list, {
1425
1127
  startingIndex = -1,
1426
1128
  decrement = false,
1427
1129
  disabledIndices,
@@ -1430,13 +1132,14 @@ function findNonDisabledListIndex(listRef, {
1430
1132
  let index = startingIndex;
1431
1133
  do {
1432
1134
  index += decrement ? -amount : amount;
1433
- } while (index >= 0 && index <= listRef.current.length - 1 && isListIndexDisabled(listRef, index, disabledIndices));
1135
+ } while (index >= 0 && index <= list.length - 1 && isListIndexDisabled(list, index, disabledIndices));
1434
1136
  return index;
1435
1137
  }
1436
- function getGridNavigatedIndex(listRef, {
1138
+ function getGridNavigatedIndex(list, {
1437
1139
  event,
1438
1140
  orientation,
1439
1141
  loopFocus,
1142
+ onLoop,
1440
1143
  rtl,
1441
1144
  cols,
1442
1145
  disabledIndices,
@@ -1447,9 +1150,9 @@ function getGridNavigatedIndex(listRef, {
1447
1150
  }) {
1448
1151
  let nextIndex = prevIndex;
1449
1152
  let verticalDirection;
1450
- if (event.key === useButton.ARROW_UP) {
1153
+ if (event.key === ARROW_UP) {
1451
1154
  verticalDirection = 'up';
1452
- } else if (event.key === useButton.ARROW_DOWN) {
1155
+ } else if (event.key === ARROW_DOWN) {
1453
1156
  verticalDirection = 'down';
1454
1157
  }
1455
1158
  if (verticalDirection) {
@@ -1464,7 +1167,7 @@ function getGridNavigatedIndex(listRef, {
1464
1167
  {
1465
1168
  let currentRowEl = null;
1466
1169
  let currentRowIndex = -1;
1467
- listRef.current.forEach((el, idx) => {
1170
+ list.forEach((el, idx) => {
1468
1171
  if (el == null) {
1469
1172
  return;
1470
1173
  }
@@ -1495,7 +1198,7 @@ function getGridNavigatedIndex(listRef, {
1495
1198
  }
1496
1199
  }
1497
1200
  }
1498
- const hasVirtualizedGaps = hasDomRows && visibleItemCount < listRef.current.length;
1201
+ const hasVirtualizedGaps = hasDomRows && visibleItemCount < list.length;
1499
1202
  const verticalCols = inferredDomCols || cols;
1500
1203
  const navigateVertically = direction => {
1501
1204
  if (!hasDomRows || prevIndex === -1) {
@@ -1513,11 +1216,17 @@ function getGridNavigatedIndex(listRef, {
1513
1216
  return undefined;
1514
1217
  }
1515
1218
  nextRow = nextRow < 0 ? rows.length - 1 : 0;
1219
+ if (onLoop) {
1220
+ const clampedCol = Math.min(colInRow, rows[nextRow].length - 1);
1221
+ const targetItemIndex = rows[nextRow][clampedCol] ?? rows[nextRow][0];
1222
+ const returnedItemIndex = onLoop(event, prevIndex, targetItemIndex);
1223
+ nextRow = rowIndexMap[returnedItemIndex] ?? nextRow;
1224
+ }
1516
1225
  }
1517
1226
  const targetRow = rows[nextRow];
1518
1227
  for (let col = Math.min(colInRow, targetRow.length - 1); col >= 0; col -= 1) {
1519
1228
  const candidate = targetRow[col];
1520
- if (!isListIndexDisabled(listRef, candidate, disabledIndices)) {
1229
+ if (!isListIndexDisabled(list, candidate, disabledIndices)) {
1521
1230
  return candidate;
1522
1231
  }
1523
1232
  }
@@ -1531,7 +1240,7 @@ function getGridNavigatedIndex(listRef, {
1531
1240
  const colInRow = prevIndex % verticalCols;
1532
1241
  const rowStep = direction === 'up' ? -verticalCols : verticalCols;
1533
1242
  const lastRowStart = maxIndex - maxIndex % verticalCols;
1534
- const rowCount = index_esm.floor(maxIndex / verticalCols) + 1;
1243
+ const rowCount = floatingUi_utils.floor(maxIndex / verticalCols) + 1;
1535
1244
  for (let rowStart = prevIndex - colInRow + rowStep, i = 0; i < rowCount; i += 1, rowStart += rowStep) {
1536
1245
  if (rowStart < 0 || rowStart > maxIndex) {
1537
1246
  if (!loopFocus) {
@@ -1541,7 +1250,7 @@ function getGridNavigatedIndex(listRef, {
1541
1250
  }
1542
1251
  const rowEnd = Math.min(rowStart + verticalCols - 1, maxIndex);
1543
1252
  for (let candidate = Math.min(rowStart + colInRow, rowEnd); candidate >= rowStart; candidate -= 1) {
1544
- if (!isListIndexDisabled(listRef, candidate, disabledIndices)) {
1253
+ if (!isListIndexDisabled(list, candidate, disabledIndices)) {
1545
1254
  return candidate;
1546
1255
  }
1547
1256
  }
@@ -1557,7 +1266,7 @@ function getGridNavigatedIndex(listRef, {
1557
1266
  } else if (prevIndex === -1) {
1558
1267
  nextIndex = verticalDirection === 'up' ? maxIndex : minIndex;
1559
1268
  } else {
1560
- nextIndex = findNonDisabledListIndex(listRef, {
1269
+ nextIndex = findNonDisabledListIndex(list, {
1561
1270
  startingIndex: prevIndex,
1562
1271
  amount: verticalCols,
1563
1272
  decrement: verticalDirection === 'up',
@@ -1573,84 +1282,105 @@ function getGridNavigatedIndex(listRef, {
1573
1282
  } else {
1574
1283
  nextIndex = maxCol > col ? offset : offset - verticalCols;
1575
1284
  }
1285
+ if (onLoop) {
1286
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1287
+ }
1576
1288
  }
1577
1289
  if (verticalDirection === 'down' && prevIndex + verticalCols > maxIndex) {
1578
- nextIndex = findNonDisabledListIndex(listRef, {
1290
+ nextIndex = findNonDisabledListIndex(list, {
1579
1291
  startingIndex: prevIndex % verticalCols - verticalCols,
1580
1292
  amount: verticalCols,
1581
1293
  disabledIndices
1582
1294
  });
1295
+ if (onLoop) {
1296
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1297
+ }
1583
1298
  }
1584
1299
  }
1585
1300
  }
1586
- if (isIndexOutOfListBounds(listRef, nextIndex)) {
1301
+ if (isIndexOutOfListBounds(list, nextIndex)) {
1587
1302
  nextIndex = prevIndex;
1588
1303
  }
1589
1304
  }
1590
1305
 
1591
1306
  // Remains on the same row/column.
1592
1307
  if (orientation === 'both') {
1593
- const prevRow = index_esm.floor(prevIndex / cols);
1594
- if (event.key === (rtl ? useButton.ARROW_LEFT : useButton.ARROW_RIGHT)) {
1308
+ const prevRow = floatingUi_utils.floor(prevIndex / cols);
1309
+ if (event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)) {
1595
1310
  if (stop) {
1596
1311
  useButton.stopEvent(event);
1597
1312
  }
1598
1313
  if (prevIndex % cols !== cols - 1) {
1599
- nextIndex = findNonDisabledListIndex(listRef, {
1314
+ nextIndex = findNonDisabledListIndex(list, {
1600
1315
  startingIndex: prevIndex,
1601
1316
  disabledIndices
1602
1317
  });
1603
1318
  if (loopFocus && isDifferentGridRow(nextIndex, cols, prevRow)) {
1604
- nextIndex = findNonDisabledListIndex(listRef, {
1319
+ nextIndex = findNonDisabledListIndex(list, {
1605
1320
  startingIndex: prevIndex - prevIndex % cols - 1,
1606
1321
  disabledIndices
1607
1322
  });
1323
+ if (onLoop) {
1324
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1325
+ }
1608
1326
  }
1609
1327
  } else if (loopFocus) {
1610
- nextIndex = findNonDisabledListIndex(listRef, {
1328
+ nextIndex = findNonDisabledListIndex(list, {
1611
1329
  startingIndex: prevIndex - prevIndex % cols - 1,
1612
1330
  disabledIndices
1613
1331
  });
1332
+ if (onLoop) {
1333
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1334
+ }
1614
1335
  }
1615
1336
  if (isDifferentGridRow(nextIndex, cols, prevRow)) {
1616
1337
  nextIndex = prevIndex;
1617
1338
  }
1618
1339
  }
1619
- if (event.key === (rtl ? useButton.ARROW_RIGHT : useButton.ARROW_LEFT)) {
1340
+ if (event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT)) {
1620
1341
  if (stop) {
1621
1342
  useButton.stopEvent(event);
1622
1343
  }
1623
1344
  if (prevIndex % cols !== 0) {
1624
- nextIndex = findNonDisabledListIndex(listRef, {
1345
+ nextIndex = findNonDisabledListIndex(list, {
1625
1346
  startingIndex: prevIndex,
1626
1347
  decrement: true,
1627
1348
  disabledIndices
1628
1349
  });
1629
1350
  if (loopFocus && isDifferentGridRow(nextIndex, cols, prevRow)) {
1630
- nextIndex = findNonDisabledListIndex(listRef, {
1351
+ nextIndex = findNonDisabledListIndex(list, {
1631
1352
  startingIndex: prevIndex + (cols - prevIndex % cols),
1632
1353
  decrement: true,
1633
1354
  disabledIndices
1634
1355
  });
1356
+ if (onLoop) {
1357
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1358
+ }
1635
1359
  }
1636
1360
  } else if (loopFocus) {
1637
- nextIndex = findNonDisabledListIndex(listRef, {
1361
+ nextIndex = findNonDisabledListIndex(list, {
1638
1362
  startingIndex: prevIndex + (cols - prevIndex % cols),
1639
1363
  decrement: true,
1640
1364
  disabledIndices
1641
1365
  });
1366
+ if (onLoop) {
1367
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1368
+ }
1642
1369
  }
1643
1370
  if (isDifferentGridRow(nextIndex, cols, prevRow)) {
1644
1371
  nextIndex = prevIndex;
1645
1372
  }
1646
1373
  }
1647
- const lastRow = index_esm.floor(maxIndex / cols) === prevRow;
1648
- if (isIndexOutOfListBounds(listRef, nextIndex)) {
1374
+ const lastRow = floatingUi_utils.floor(maxIndex / cols) === prevRow;
1375
+ if (isIndexOutOfListBounds(list, nextIndex)) {
1649
1376
  if (loopFocus && lastRow) {
1650
- nextIndex = event.key === (rtl ? useButton.ARROW_RIGHT : useButton.ARROW_LEFT) ? maxIndex : findNonDisabledListIndex(listRef, {
1377
+ nextIndex = event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT) ? maxIndex : findNonDisabledListIndex(list, {
1651
1378
  startingIndex: prevIndex - prevIndex % cols - 1,
1652
1379
  disabledIndices
1653
1380
  });
1381
+ if (onLoop) {
1382
+ nextIndex = onLoop(event, prevIndex, nextIndex);
1383
+ }
1654
1384
  } else {
1655
1385
  nextIndex = prevIndex;
1656
1386
  }
@@ -1658,80 +1388,12 @@ function getGridNavigatedIndex(listRef, {
1658
1388
  }
1659
1389
  return nextIndex;
1660
1390
  }
1661
-
1662
- /** For each cell index, gets the item index that occupies that cell */
1663
- function createGridCellMap(sizes, cols, dense) {
1664
- const cellMap = [];
1665
- let startIndex = 0;
1666
- sizes.forEach(({
1667
- width,
1668
- height
1669
- }, index) => {
1670
- if (width > cols) {
1671
- if (process.env.NODE_ENV !== 'production') {
1672
- throw new Error(`[Floating UI]: Invalid grid - item width at index ${index} is greater than grid columns`);
1673
- }
1674
- }
1675
- let itemPlaced = false;
1676
- while (!itemPlaced) {
1677
- const targetCells = [];
1678
- for (let i = 0; i < width; i += 1) {
1679
- for (let j = 0; j < height; j += 1) {
1680
- targetCells.push(startIndex + i + j * cols);
1681
- }
1682
- }
1683
- if (startIndex % cols + width <= cols && targetCells.every(cell => cellMap[cell] == null)) {
1684
- targetCells.forEach(cell => {
1685
- cellMap[cell] = index;
1686
- });
1687
- itemPlaced = true;
1688
- } else {
1689
- startIndex += 1;
1690
- }
1691
- }
1692
- });
1693
-
1694
- // convert into a non-sparse array
1695
- return [...cellMap];
1696
- }
1697
-
1698
- /** Gets cell index of an item's corner or -1 when index is -1. */
1699
- function getGridCellIndexOfCorner(index, sizes, cellMap, cols, corner) {
1700
- if (index === -1) {
1701
- return -1;
1702
- }
1703
- const firstCellIndex = cellMap.indexOf(index);
1704
- const sizeItem = sizes[index];
1705
- switch (corner) {
1706
- case 'tl':
1707
- return firstCellIndex;
1708
- case 'tr':
1709
- if (!sizeItem) {
1710
- return firstCellIndex;
1711
- }
1712
- return firstCellIndex + sizeItem.width - 1;
1713
- case 'bl':
1714
- if (!sizeItem) {
1715
- return firstCellIndex;
1716
- }
1717
- return firstCellIndex + (sizeItem.height - 1) * cols;
1718
- case 'br':
1719
- return cellMap.lastIndexOf(index);
1720
- default:
1721
- return -1;
1722
- }
1723
- }
1724
-
1725
- /** Gets all cell indices that correspond to the specified indices */
1726
- function getGridCellIndices(indices, cellMap) {
1727
- return cellMap.flatMap((index, cellIndex) => indices.includes(index) ? [cellIndex] : []);
1728
- }
1729
- function isListIndexDisabled(listRef, index, disabledIndices) {
1391
+ function isListIndexDisabled(list, index, disabledIndices) {
1730
1392
  const isExplicitlyDisabled = typeof disabledIndices === 'function' ? disabledIndices(index) : disabledIndices?.includes(index) ?? false;
1731
1393
  if (isExplicitlyDisabled) {
1732
1394
  return true;
1733
1395
  }
1734
- const element = listRef.current[index];
1396
+ const element = list[index];
1735
1397
  if (!element) {
1736
1398
  return false;
1737
1399
  }
@@ -1740,44 +1402,172 @@ function isListIndexDisabled(listRef, index, disabledIndices) {
1740
1402
  }
1741
1403
  return !disabledIndices && (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true');
1742
1404
  }
1743
- function isElementVisible(element) {
1744
- return floatingUi_utils_dom.getComputedStyle(element).display !== 'none';
1405
+ function isHiddenByStyles(styles) {
1406
+ return styles.visibility === 'hidden' || styles.visibility === 'collapse';
1407
+ }
1408
+ function isElementVisible(element, styles = element ? floatingUi_utils_dom.getComputedStyle(element) : null) {
1409
+ if (!element || !element.isConnected || !styles || isHiddenByStyles(styles)) {
1410
+ return false;
1411
+ }
1412
+ if (typeof element.checkVisibility === 'function') {
1413
+ return element.checkVisibility();
1414
+ }
1415
+ return styles.display !== 'none' && styles.display !== 'contents';
1745
1416
  }
1746
1417
 
1747
- const getTabbableOptions = () => ({
1748
- getShadowRoot: true,
1749
- displayCheck:
1750
- // JSDOM does not support the `tabbable` library. To solve this we can
1751
- // check if `ResizeObserver` is a real function (not polyfilled), which
1752
- // determines if the current environment is JSDOM-like.
1753
- typeof ResizeObserver === 'function' && ResizeObserver.toString().includes('[native code]') ? 'full' : 'none'
1754
- });
1755
- function getTabbableIn(container, dir) {
1756
- const list = index_esm.tabbable(container, getTabbableOptions());
1757
- const len = list.length;
1758
- if (len === 0) {
1759
- return undefined;
1418
+ const CANDIDATE_SELECTOR = 'a[href],button,input,select,textarea,summary,details,iframe,object,embed,[tabindex],[contenteditable]:not([contenteditable="false"]),audio[controls],video[controls]';
1419
+ function getParentElement(element) {
1420
+ const assignedSlot = element.assignedSlot;
1421
+ if (assignedSlot) {
1422
+ return assignedSlot;
1760
1423
  }
1761
- const active = useButton.activeElement(useButton.ownerDocument(container));
1762
- const index = list.indexOf(active);
1763
- // eslint-disable-next-line no-nested-ternary
1764
- const nextIndex = index === -1 ? dir === 1 ? 0 : len - 1 : index + dir;
1765
- return list[nextIndex];
1424
+ if (element.parentElement) {
1425
+ return element.parentElement;
1426
+ }
1427
+ const rootNode = element.getRootNode();
1428
+ return floatingUi_utils_dom.isShadowRoot(rootNode) ? rootNode.host : null;
1766
1429
  }
1767
- function getNextTabbable(referenceElement) {
1768
- return getTabbableIn(useButton.ownerDocument(referenceElement).body, 1) || referenceElement;
1430
+ function getDetailsSummary(details) {
1431
+ for (const child of Array.from(details.children)) {
1432
+ if (floatingUi_utils_dom.getNodeName(child) === 'summary') {
1433
+ return child;
1434
+ }
1435
+ }
1436
+ return null;
1769
1437
  }
1770
- function getPreviousTabbable(referenceElement) {
1771
- return getTabbableIn(useButton.ownerDocument(referenceElement).body, -1) || referenceElement;
1438
+ function isWithinOpenDetailsSummary(element, details) {
1439
+ const summary = getDetailsSummary(details);
1440
+ return !!summary && (element === summary || useButton.contains(summary, element));
1772
1441
  }
1773
- function getTabbableNearElement(referenceElement, dir) {
1774
- if (!referenceElement) {
1775
- return null;
1776
- }
1777
- const list = index_esm.tabbable(useButton.ownerDocument(referenceElement).body, getTabbableOptions());
1778
- const elementCount = list.length;
1779
- if (elementCount === 0) {
1780
- return null;
1442
+ function isFocusableCandidate(element) {
1443
+ const nodeName = element ? floatingUi_utils_dom.getNodeName(element) : '';
1444
+ return element != null && element.matches(CANDIDATE_SELECTOR) && (nodeName !== 'summary' || element.parentElement != null && floatingUi_utils_dom.getNodeName(element.parentElement) === 'details' && getDetailsSummary(element.parentElement) === element) && (nodeName !== 'details' || getDetailsSummary(element) == null) && (nodeName !== 'input' || element.type !== 'hidden');
1445
+ }
1446
+ function isFocusableElement(element) {
1447
+ if (!isFocusableCandidate(element) || !element.isConnected || element.matches(':disabled')) {
1448
+ return false;
1449
+ }
1450
+ for (let current = element; current; current = getParentElement(current)) {
1451
+ const isAncestor = current !== element;
1452
+ const isSlot = floatingUi_utils_dom.getNodeName(current) === 'slot';
1453
+ if (current.hasAttribute('inert')) {
1454
+ return false;
1455
+ }
1456
+ if (isAncestor && floatingUi_utils_dom.getNodeName(current) === 'details' && !current.open && !isWithinOpenDetailsSummary(element, current) || current.hasAttribute('hidden') || !isSlot && !isVisibleInTabbableTree(current, isAncestor)) {
1457
+ return false;
1458
+ }
1459
+ }
1460
+ return true;
1461
+ }
1462
+ function isVisibleInTabbableTree(element, isAncestor) {
1463
+ const styles = floatingUi_utils_dom.getComputedStyle(element);
1464
+ if (!isAncestor) {
1465
+ return isElementVisible(element, styles);
1466
+ }
1467
+ return styles.display !== 'none';
1468
+ }
1469
+ function getTabIndex(element) {
1470
+ const tabIndex = element.tabIndex;
1471
+ if (tabIndex < 0) {
1472
+ const nodeName = floatingUi_utils_dom.getNodeName(element);
1473
+ if (nodeName === 'details' || nodeName === 'audio' || nodeName === 'video' || floatingUi_utils_dom.isHTMLElement(element) && element.isContentEditable) {
1474
+ return 0;
1475
+ }
1476
+ }
1477
+ return tabIndex;
1478
+ }
1479
+ function getNamedRadioInput(element) {
1480
+ if (floatingUi_utils_dom.getNodeName(element) !== 'input') {
1481
+ return null;
1482
+ }
1483
+ const input = element;
1484
+ return input.type === 'radio' && input.name !== '' ? input : null;
1485
+ }
1486
+ function isTabbableRadio(element, candidates) {
1487
+ const input = getNamedRadioInput(element);
1488
+ if (!input) {
1489
+ return true;
1490
+ }
1491
+ const checkedRadio = candidates.find(candidate => {
1492
+ const radio = getNamedRadioInput(candidate);
1493
+ return radio?.name === input.name && radio.form === input.form && radio.checked;
1494
+ });
1495
+ if (checkedRadio) {
1496
+ return checkedRadio === input;
1497
+ }
1498
+ return candidates.find(candidate => {
1499
+ const radio = getNamedRadioInput(candidate);
1500
+ return radio?.name === input.name && radio.form === input.form;
1501
+ }) === input;
1502
+ }
1503
+ function getComposedChildren(container) {
1504
+ if (floatingUi_utils_dom.isHTMLElement(container) && floatingUi_utils_dom.getNodeName(container) === 'slot') {
1505
+ const assignedElements = container.assignedElements({
1506
+ flatten: true
1507
+ });
1508
+ if (assignedElements.length > 0) {
1509
+ return assignedElements;
1510
+ }
1511
+ }
1512
+ if (floatingUi_utils_dom.isHTMLElement(container) && container.shadowRoot) {
1513
+ return Array.from(container.shadowRoot.children);
1514
+ }
1515
+ return Array.from(container.children);
1516
+ }
1517
+ function appendCandidates(container, list) {
1518
+ getComposedChildren(container).forEach(child => {
1519
+ if (isFocusableCandidate(child)) {
1520
+ list.push(child);
1521
+ }
1522
+ appendCandidates(child, list);
1523
+ });
1524
+ }
1525
+ function appendMatchingElements(container, selector, list) {
1526
+ getComposedChildren(container).forEach(child => {
1527
+ if (floatingUi_utils_dom.isHTMLElement(child) && child.matches(selector)) {
1528
+ list.push(child);
1529
+ }
1530
+ appendMatchingElements(child, selector, list);
1531
+ });
1532
+ }
1533
+ function isTabbable(element) {
1534
+ return isFocusableElement(element) && getTabIndex(element) >= 0;
1535
+ }
1536
+ function focusable(container) {
1537
+ const candidates = [];
1538
+ appendCandidates(container, candidates);
1539
+ return candidates.filter(isFocusableElement);
1540
+ }
1541
+ function tabbable(container) {
1542
+ const candidates = focusable(container);
1543
+ return candidates.filter(element => getTabIndex(element) >= 0 && isTabbableRadio(element, candidates));
1544
+ }
1545
+ function getTabbableIn(container, dir) {
1546
+ const list = tabbable(container);
1547
+ const len = list.length;
1548
+ if (len === 0) {
1549
+ return undefined;
1550
+ }
1551
+ const active = useButton.activeElement(useButton.ownerDocument(container));
1552
+ const index = list.indexOf(active);
1553
+ // eslint-disable-next-line no-nested-ternary
1554
+ const nextIndex = index === -1 ? dir === 1 ? 0 : len - 1 : index + dir;
1555
+ return list[nextIndex];
1556
+ }
1557
+ function getNextTabbable(referenceElement) {
1558
+ return getTabbableIn(useButton.ownerDocument(referenceElement).body, 1) || referenceElement;
1559
+ }
1560
+ function getPreviousTabbable(referenceElement) {
1561
+ return getTabbableIn(useButton.ownerDocument(referenceElement).body, -1) || referenceElement;
1562
+ }
1563
+ function getTabbableNearElement(referenceElement, dir) {
1564
+ if (!referenceElement) {
1565
+ return null;
1566
+ }
1567
+ const list = tabbable(useButton.ownerDocument(referenceElement).body);
1568
+ const elementCount = list.length;
1569
+ if (elementCount === 0) {
1570
+ return null;
1781
1571
  }
1782
1572
  const index = list.indexOf(referenceElement);
1783
1573
  if (index === -1) {
@@ -1798,14 +1588,15 @@ function isOutsideEvent(event, container) {
1798
1588
  return !relatedTarget || !useButton.contains(containerElement, relatedTarget);
1799
1589
  }
1800
1590
  function disableFocusInside(container) {
1801
- const tabbableElements = index_esm.tabbable(container, getTabbableOptions());
1591
+ const tabbableElements = tabbable(container);
1802
1592
  tabbableElements.forEach(element => {
1803
1593
  element.dataset.tabindex = element.getAttribute('tabindex') || '';
1804
1594
  element.setAttribute('tabindex', '-1');
1805
1595
  });
1806
1596
  }
1807
1597
  function enableFocusInside(container) {
1808
- const elements = container.querySelectorAll('[data-tabindex]');
1598
+ const elements = [];
1599
+ appendMatchingElements(container, '[data-tabindex]', elements);
1809
1600
  elements.forEach(element => {
1810
1601
  const tabindex = element.dataset.tabindex;
1811
1602
  delete element.dataset.tabindex;
@@ -1817,71 +1608,24 @@ function enableFocusInside(container) {
1817
1608
  });
1818
1609
  }
1819
1610
 
1820
- const TYPEAHEAD_RESET_MS = 500;
1821
- const PATIENT_CLICK_THRESHOLD = 500;
1822
- const DISABLED_TRANSITIONS_STYLE = {
1823
- style: {
1824
- transition: 'none'
1825
- }
1826
- };
1827
- const CLICK_TRIGGER_IDENTIFIER = 'data-base-ui-click-trigger';
1828
- const BASE_UI_SWIPE_IGNORE_ATTRIBUTE = 'data-base-ui-swipe-ignore';
1829
- const BASE_UI_SWIPE_IGNORE_SELECTOR = `[${BASE_UI_SWIPE_IGNORE_ATTRIBUTE}]`;
1830
-
1831
- /**
1832
- * Used for dropdowns that usually strictly prefer top/bottom placements and
1833
- * use `var(--available-height)` to limit their height.
1834
- */
1835
- const DROPDOWN_COLLISION_AVOIDANCE = {
1836
- fallbackAxisSide: 'none'
1837
- };
1838
-
1839
- /**
1840
- * Used by regular popups that usually aren't scrollable and are allowed to
1841
- * freely flip to any axis of placement.
1842
- */
1843
- const POPUP_COLLISION_AVOIDANCE = {
1844
- fallbackAxisSide: 'end'
1845
- };
1846
-
1847
- /**
1848
- * Special visually hidden styles for the aria-owns owner element to ensure owned element
1849
- * accessibility in iOS/Safari/VoiceControl.
1850
- * The owner element is an empty span, so most of the common visually hidden styles are not needed.
1851
- * @see https://github.com/floating-ui/floating-ui/issues/3403
1852
- */
1853
- const ownerVisuallyHidden = {
1854
- clipPath: 'inset(50%)',
1855
- position: 'fixed',
1856
- top: 0,
1857
- left: 0
1858
- };
1611
+ /* eslint-disable @typescript-eslint/no-loop-func */
1859
1612
 
1860
- const FocusGuard = /*#__PURE__*/React__namespace.forwardRef(function FocusGuard(props, ref) {
1861
- const [role, setRole] = React__namespace.useState();
1862
- useButton.useIsoLayoutEffect(() => {
1863
- if (useButton.isSafari) {
1864
- // Unlike other screen readers such as NVDA and JAWS, the virtual cursor
1865
- // on VoiceOver does trigger the onFocus event, so we can use the focus
1866
- // trap element. On Safari, only buttons trigger the onFocus event.
1867
- setRole('button');
1613
+ function getNodeChildren(nodes, id, onlyOpenChildren = true) {
1614
+ const directChildren = nodes.filter(node => node.parentId === id);
1615
+ return directChildren.flatMap(child => [...(!onlyOpenChildren || child.context?.open ? [child] : []), ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
1616
+ }
1617
+ function getNodeAncestors(nodes, id) {
1618
+ let allAncestors = [];
1619
+ let currentParentId = nodes.find(node => node.id === id)?.parentId;
1620
+ while (currentParentId) {
1621
+ const currentNode = nodes.find(node => node.id === currentParentId);
1622
+ currentParentId = currentNode?.parentId;
1623
+ if (currentNode) {
1624
+ allAncestors = allAncestors.concat(currentNode);
1868
1625
  }
1869
- }, []);
1870
- const restProps = {
1871
- tabIndex: 0,
1872
- // Role is only for VoiceOver
1873
- role
1874
- };
1875
- return /*#__PURE__*/jsxRuntime.jsx("span", {
1876
- ...props,
1877
- ref: ref,
1878
- style: useButton.visuallyHidden,
1879
- "aria-hidden": role ? undefined : true,
1880
- ...restProps,
1881
- "data-base-ui-focus-guard": ""
1882
- });
1883
- });
1884
- if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1626
+ }
1627
+ return allAncestors;
1628
+ }
1885
1629
 
1886
1630
  function createAttribute(name) {
1887
1631
  return `data-base-ui-${name}`;
@@ -1891,20 +1635,30 @@ let rafId = 0;
1891
1635
  function enqueueFocus(el, options = {}) {
1892
1636
  const {
1893
1637
  preventScroll = false,
1894
- cancelPrevious = true,
1895
- sync = false
1638
+ sync = false,
1639
+ shouldFocus
1896
1640
  } = options;
1897
- if (cancelPrevious) {
1898
- cancelAnimationFrame(rafId);
1641
+ cancelAnimationFrame(rafId);
1642
+ function exec() {
1643
+ if (shouldFocus && !shouldFocus()) {
1644
+ return;
1645
+ }
1646
+ el?.focus({
1647
+ preventScroll
1648
+ });
1899
1649
  }
1900
- const exec = () => el?.focus({
1901
- preventScroll
1902
- });
1903
1650
  if (sync) {
1904
1651
  exec();
1905
- } else {
1906
- rafId = requestAnimationFrame(exec);
1652
+ return useRenderElement.NOOP;
1907
1653
  }
1654
+ const currentRafId = requestAnimationFrame(exec);
1655
+ rafId = currentRafId;
1656
+ return () => {
1657
+ if (rafId === currentRafId) {
1658
+ cancelAnimationFrame(currentRafId);
1659
+ rafId = 0;
1660
+ }
1661
+ };
1908
1662
  }
1909
1663
 
1910
1664
  // Modified to add conditional `aria-hidden` support:
@@ -1971,17 +1725,18 @@ const collectOutsideElements = (root, keepElements, stopElements) => {
1971
1725
  return outside;
1972
1726
  };
1973
1727
  function applyAttributeToOthers(uncorrectedAvoidElements, body, ariaHidden, inert, {
1974
- mark = true,
1975
- markerIgnoreElements = []
1728
+ mark = true
1976
1729
  }) {
1977
- // eslint-disable-next-line no-nested-ternary
1978
- const controlAttribute = inert ? 'inert' : ariaHidden ? 'aria-hidden' : null;
1730
+ let controlAttribute = null;
1731
+ if (inert) {
1732
+ controlAttribute = 'inert';
1733
+ } else if (ariaHidden) {
1734
+ controlAttribute = 'aria-hidden';
1735
+ }
1979
1736
  let counterMap = null;
1980
1737
  let uncontrolledElementsSet = null;
1981
1738
  const avoidElements = correctElements(body, uncorrectedAvoidElements);
1982
- const markerIgnoreTargets = mark ? correctElements(body, markerIgnoreElements) : [];
1983
- const markerIgnoreSet = new Set(markerIgnoreTargets);
1984
- const markerTargets = mark ? collectOutsideElements(body, buildKeepSet(avoidElements), new Set(avoidElements)).filter(target => !markerIgnoreSet.has(target)) : [];
1739
+ const markerTargets = mark ? collectOutsideElements(body, buildKeepSet(avoidElements), new Set(avoidElements)) : [];
1985
1740
  const hiddenElements = [];
1986
1741
  const markedElements = [];
1987
1742
  if (controlAttribute) {
@@ -2054,16 +1809,54 @@ function markOthers(avoidElements, options = {}) {
2054
1809
  const {
2055
1810
  ariaHidden = false,
2056
1811
  inert = false,
2057
- mark = true,
2058
- markerIgnoreElements = []
1812
+ mark = true
2059
1813
  } = options;
2060
1814
  const body = useButton.ownerDocument(avoidElements[0]).body;
2061
1815
  return applyAttributeToOthers(avoidElements, body, ariaHidden, inert, {
2062
- mark,
2063
- markerIgnoreElements
1816
+ mark
2064
1817
  });
2065
1818
  }
2066
1819
 
1820
+ const TYPEAHEAD_RESET_MS = 500;
1821
+ const PATIENT_CLICK_THRESHOLD = 500;
1822
+ const DISABLED_TRANSITIONS_STYLE = {
1823
+ style: {
1824
+ transition: 'none'
1825
+ }
1826
+ };
1827
+ const CLICK_TRIGGER_IDENTIFIER = 'data-base-ui-click-trigger';
1828
+ const BASE_UI_SWIPE_IGNORE_ATTRIBUTE = 'data-base-ui-swipe-ignore';
1829
+ const BASE_UI_SWIPE_IGNORE_SELECTOR = `[${BASE_UI_SWIPE_IGNORE_ATTRIBUTE}]`;
1830
+
1831
+ /**
1832
+ * Used for dropdowns that usually strictly prefer top/bottom placements and
1833
+ * use `var(--available-height)` to limit their height.
1834
+ */
1835
+ const DROPDOWN_COLLISION_AVOIDANCE = {
1836
+ fallbackAxisSide: 'none'
1837
+ };
1838
+
1839
+ /**
1840
+ * Used by regular popups that usually aren't scrollable and are allowed to
1841
+ * freely flip to any axis of placement.
1842
+ */
1843
+ const POPUP_COLLISION_AVOIDANCE = {
1844
+ fallbackAxisSide: 'end'
1845
+ };
1846
+
1847
+ /**
1848
+ * Special visually hidden styles for the aria-owns owner element to ensure owned element
1849
+ * accessibility in iOS/Safari/VoiceControl.
1850
+ * The owner element is an empty span, so most of the common visually hidden styles are not needed.
1851
+ * @see https://github.com/floating-ui/floating-ui/issues/3403
1852
+ */
1853
+ const ownerVisuallyHidden = {
1854
+ clipPath: 'inset(50%)',
1855
+ position: 'fixed',
1856
+ top: 0,
1857
+ left: 0
1858
+ };
1859
+
2067
1860
  const PortalContext = /*#__PURE__*/React__namespace.createContext(null);
2068
1861
  if (process.env.NODE_ENV !== "production") PortalContext.displayName = "PortalContext";
2069
1862
  const usePortalContext = () => React__namespace.useContext(PortalContext);
@@ -2147,10 +1940,11 @@ function useFloatingPortalNode(props = {}) {
2147
1940
  */
2148
1941
  const FloatingPortal = /*#__PURE__*/React__namespace.forwardRef(function FloatingPortal(componentProps, forwardedRef) {
2149
1942
  const {
1943
+ render,
1944
+ className,
1945
+ style,
2150
1946
  children,
2151
1947
  container,
2152
- className,
2153
- render,
2154
1948
  renderGuards,
2155
1949
  ...elementProps
2156
1950
  } = componentProps;
@@ -2168,6 +1962,7 @@ const FloatingPortal = /*#__PURE__*/React__namespace.forwardRef(function Floatin
2168
1962
  const beforeInsideRef = React__namespace.useRef(null);
2169
1963
  const afterInsideRef = React__namespace.useRef(null);
2170
1964
  const [focusManagerState, setFocusManagerState] = React__namespace.useState(null);
1965
+ const focusInsideDisabledRef = React__namespace.useRef(false);
2171
1966
  const modal = focusManagerState?.modal;
2172
1967
  const open = focusManagerState?.open;
2173
1968
  const shouldRenderGuards = typeof renderGuards === 'boolean' ? renderGuards : !!focusManagerState && !focusManagerState.modal && focusManagerState.open && !!portalNode;
@@ -2183,26 +1978,30 @@ const FloatingPortal = /*#__PURE__*/React__namespace.forwardRef(function Floatin
2183
1978
  // element outside or using the mouse.
2184
1979
  function onFocus(event) {
2185
1980
  if (portalNode && event.relatedTarget && isOutsideEvent(event)) {
2186
- const focusing = event.type === 'focusin';
2187
- const manageFocus = focusing ? enableFocusInside : disableFocusInside;
2188
- manageFocus(portalNode);
1981
+ if (event.type === 'focusin') {
1982
+ if (focusInsideDisabledRef.current) {
1983
+ enableFocusInside(portalNode);
1984
+ focusInsideDisabledRef.current = false;
1985
+ }
1986
+ } else {
1987
+ disableFocusInside(portalNode);
1988
+ focusInsideDisabledRef.current = true;
1989
+ }
2189
1990
  }
2190
1991
  }
2191
1992
 
2192
1993
  // Listen to the event on the capture phase so they run before the focus
2193
1994
  // trap elements onFocus prop is called.
2194
- portalNode.addEventListener('focusin', onFocus, true);
2195
- portalNode.addEventListener('focusout', onFocus, true);
2196
- return () => {
2197
- portalNode.removeEventListener('focusin', onFocus, true);
2198
- portalNode.removeEventListener('focusout', onFocus, true);
2199
- };
1995
+ return mergeCleanups(useButton.addEventListener(portalNode, 'focusin', onFocus, true), useButton.addEventListener(portalNode, 'focusout', onFocus, true));
2200
1996
  }, [portalNode, modal]);
2201
- React__namespace.useEffect(() => {
2202
- if (!portalNode || open) {
1997
+ useButton.useIsoLayoutEffect(() => {
1998
+ if (!portalNode || open !== true || !focusInsideDisabledRef.current) {
2203
1999
  return;
2204
2000
  }
2001
+
2002
+ // Restore tabbability before the focus manager's queued focus-on-open step runs.
2205
2003
  enableFocusInside(portalNode);
2004
+ focusInsideDisabledRef.current = false;
2206
2005
  }, [open, portalNode]);
2207
2006
  const portalContextValue = React__namespace.useMemo(() => ({
2208
2007
  beforeOutsideRef,
@@ -2374,7 +2173,7 @@ function FloatingTree(props) {
2374
2173
  }
2375
2174
 
2376
2175
  function getEventType(event, lastInteractionType) {
2377
- const win = floatingUi_utils_dom.getWindow(event.target);
2176
+ const win = floatingUi_utils_dom.getWindow(useButton.getTarget(event));
2378
2177
  if (event instanceof win.KeyboardEvent) {
2379
2178
  return 'keyboard';
2380
2179
  }
@@ -2419,35 +2218,30 @@ function getFirstTabbableElement(container) {
2419
2218
  if (!container) {
2420
2219
  return null;
2421
2220
  }
2422
- const tabbableOptions = getTabbableOptions();
2423
- if (index_esm.isTabbable(container, tabbableOptions)) {
2221
+ if (isTabbable(container)) {
2424
2222
  return container;
2425
2223
  }
2426
- return index_esm.tabbable(container, tabbableOptions)[0] || container;
2224
+ return tabbable(container)[0] || container;
2427
2225
  }
2428
- function isFocusable(element) {
2429
- if (!element || !element.isConnected) {
2430
- return false;
2431
- }
2432
- if (typeof element.checkVisibility === 'function') {
2433
- return element.checkVisibility();
2226
+ function handleTabIndex(floatingFocusElement) {
2227
+ if (floatingFocusElement.hasAttribute('tabindex') && !floatingFocusElement.hasAttribute('data-tabindex')) {
2228
+ return;
2434
2229
  }
2435
- return isElementVisible(element);
2436
- }
2437
- function handleTabIndex(floatingFocusElement, orderRef) {
2438
- if (!orderRef.current.includes('floating') && !floatingFocusElement.getAttribute('role')?.includes('dialog')) {
2230
+ if (!floatingFocusElement.getAttribute('role')?.includes('dialog')) {
2439
2231
  return;
2440
2232
  }
2441
- const options = getTabbableOptions();
2442
- const focusableElements = index_esm.focusable(floatingFocusElement, options);
2233
+ const focusableElements = focusable(floatingFocusElement);
2443
2234
  const tabbableContent = focusableElements.filter(element => {
2444
2235
  const dataTabIndex = element.getAttribute('data-tabindex') || '';
2445
- return index_esm.isTabbable(element, options) || element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-');
2236
+ return isTabbable(element) || element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-');
2446
2237
  });
2447
2238
  const tabIndex = floatingFocusElement.getAttribute('tabindex');
2448
- if (orderRef.current.includes('floating') || tabbableContent.length === 0) {
2239
+ if (tabbableContent.length === 0) {
2449
2240
  if (tabIndex !== '0') {
2450
2241
  floatingFocusElement.setAttribute('tabindex', '0');
2242
+ // Mark our own write so the externally-managed early-return above doesn't
2243
+ // mistake it for a user-authored `tabindex` and freeze management.
2244
+ floatingFocusElement.setAttribute('data-tabindex', '0');
2451
2245
  }
2452
2246
  } else if (tabIndex !== '-1' || floatingFocusElement.hasAttribute('data-tabindex') && floatingFocusElement.getAttribute('data-tabindex') !== '-1') {
2453
2247
  floatingFocusElement.setAttribute('tabindex', '-1');
@@ -2491,17 +2285,17 @@ function FloatingFocusManager(props) {
2491
2285
  // aria-hidden should be applied to all nodes still. Further, the visually
2492
2286
  // hidden dismiss button should only appear at the end of the list, not the
2493
2287
  // start.
2494
- const isUntrappedTypeableCombobox = useButton.isTypeableCombobox(domReference) && ignoreInitialFocus;
2495
- const orderRef = React__namespace.useRef(['content']);
2288
+ const isUntrappedTypeableCombobox = isTypeableCombobox(domReference) && ignoreInitialFocus;
2496
2289
  const initialFocusRef = useButton.useValueAsRef(initialFocus);
2497
2290
  const returnFocusRef = useButton.useValueAsRef(returnFocus);
2498
2291
  const openInteractionTypeRef = useButton.useValueAsRef(openInteractionType);
2292
+ const openRef = useButton.useValueAsRef(open);
2499
2293
  const tree = useFloatingTree(externalTree);
2500
2294
  const portalContext = usePortalContext();
2501
2295
  const preventReturnFocusRef = React__namespace.useRef(false);
2502
2296
  const isPointerDownRef = React__namespace.useRef(false);
2503
2297
  const pointerDownOutsideRef = React__namespace.useRef(false);
2504
- const tabbableIndexRef = React__namespace.useRef(-1);
2298
+ const lastFocusedTabbableRef = React__namespace.useRef(null);
2505
2299
  const closeTypeRef = React__namespace.useRef('');
2506
2300
  const lastInteractionTypeRef = React__namespace.useRef('');
2507
2301
  const beforeGuardRef = React__namespace.useRef(null);
@@ -2512,15 +2306,11 @@ function FloatingFocusManager(props) {
2512
2306
  const pointerDownTimeout = useButton.useTimeout();
2513
2307
  const restoreFocusFrame = useButton.useAnimationFrame();
2514
2308
  const isInsidePortal = portalContext != null;
2515
- const floatingFocusElement = useButton.getFloatingFocusElement(floating);
2309
+ const floatingFocusElement = getFloatingFocusElement(floating);
2516
2310
  const getTabbableContent = useButton.useStableCallback((container = floatingFocusElement) => {
2517
- return container ? index_esm.tabbable(container, getTabbableOptions()) : [];
2311
+ return container ? tabbable(container) : [];
2518
2312
  });
2519
2313
  const getResolvedInsideElements = useButton.useStableCallback(() => getInsideElements?.().filter(element => element != null) ?? []);
2520
- const getTabbableElements = useButton.useStableCallback(container => {
2521
- const content = getTabbableContent(container);
2522
- return orderRef.current.map(() => content).filter(Boolean).flat();
2523
- });
2524
2314
 
2525
2315
  // Prevent Tab from escaping the modal when there are no tabbable elements.
2526
2316
  React__namespace.useEffect(() => {
@@ -2536,11 +2326,8 @@ function FloatingFocusManager(props) {
2536
2326
  }
2537
2327
  }
2538
2328
  const doc = useButton.ownerDocument(floatingFocusElement);
2539
- doc.addEventListener('keydown', onKeyDown);
2540
- return () => {
2541
- doc.removeEventListener('keydown', onKeyDown);
2542
- };
2543
- }, [disabled, domReference, floatingFocusElement, modal, orderRef, isUntrappedTypeableCombobox, getTabbableContent, getTabbableElements]);
2329
+ return useButton.addEventListener(doc, 'keydown', onKeyDown);
2330
+ }, [disabled, floatingFocusElement, modal, isUntrappedTypeableCombobox, getTabbableContent]);
2544
2331
 
2545
2332
  // Track pointer/keyboard interactions to disambiguate focus and outside presses.
2546
2333
  React__namespace.useEffect(() => {
@@ -2559,22 +2346,21 @@ function FloatingFocusManager(props) {
2559
2346
  lastInteractionTypeRef.current = event.pointerType || 'keyboard';
2560
2347
  if (target?.closest(`[${CLICK_TRIGGER_IDENTIFIER}]`)) {
2561
2348
  isPointerDownRef.current = true;
2349
+ // Reset on the next tick so a single click on a click-trigger doesn't
2350
+ // permanently suppress focus-out closing for the lifetime of the instance.
2351
+ pointerDownTimeout.start(0, () => {
2352
+ isPointerDownRef.current = false;
2353
+ });
2562
2354
  }
2563
2355
  }
2564
2356
  function onKeyDown() {
2565
2357
  lastInteractionTypeRef.current = 'keyboard';
2566
2358
  }
2567
- doc.addEventListener('pointerdown', onPointerDown, true);
2568
- doc.addEventListener('pointerup', clearPointerDownOutside, true);
2569
- doc.addEventListener('pointercancel', clearPointerDownOutside, true);
2570
- doc.addEventListener('keydown', onKeyDown, true);
2571
- return () => {
2572
- doc.removeEventListener('pointerdown', onPointerDown, true);
2573
- doc.removeEventListener('pointerup', clearPointerDownOutside, true);
2574
- doc.removeEventListener('pointercancel', clearPointerDownOutside, true);
2575
- doc.removeEventListener('keydown', onKeyDown, true);
2576
- };
2577
- }, [disabled, floating, domReference, floatingFocusElement, open, portalContext, getResolvedInsideElements]);
2359
+ return mergeCleanups(useButton.addEventListener(doc, 'pointerdown', onPointerDown, true), useButton.addEventListener(doc, 'pointerup', clearPointerDownOutside, true), useButton.addEventListener(doc, 'pointercancel', clearPointerDownOutside, true), useButton.addEventListener(doc, 'keydown', onKeyDown, true),
2360
+ // Avoid a stale `true` leaking into the next open (e.g. keep-mounted popups)
2361
+ // if the popup dismissed between pointerdown and pointerup.
2362
+ clearPointerDownOutside);
2363
+ }, [disabled, floating, domReference, floatingFocusElement, open, portalContext, pointerDownTimeout, getResolvedInsideElements]);
2578
2364
 
2579
2365
  // Close on focus out and restore focus within the floating tree when needed.
2580
2366
  React__namespace.useEffect(() => {
@@ -2592,29 +2378,34 @@ function FloatingFocusManager(props) {
2592
2378
  }
2593
2379
  function handleFocusIn(event) {
2594
2380
  const target = useButton.getTarget(event);
2595
- const tabbableContent = getTabbableContent();
2596
- const tabbableIndex = tabbableContent.indexOf(target);
2597
- if (tabbableIndex !== -1) {
2598
- tabbableIndexRef.current = tabbableIndex;
2381
+ if (isTabbable(target)) {
2382
+ lastFocusedTabbableRef.current = target;
2599
2383
  }
2600
2384
  }
2601
2385
  function handleFocusOutside(event) {
2602
2386
  const relatedTarget = event.relatedTarget;
2603
2387
  const currentTarget = event.currentTarget;
2604
2388
  const target = useButton.getTarget(event);
2389
+
2390
+ // When focus is lost to the body (e.g. on a backdrop press), record the element that
2391
+ // had focus so a confirmation dialog opened while the body is focused can return focus
2392
+ // to it. Scoped to `modal` to avoid non-modal popups polluting the shared stack.
2393
+ if (modal && relatedTarget == null && target != null && useButton.contains(floating, target)) {
2394
+ addPreviouslyFocusedElement(target);
2395
+ }
2605
2396
  queueMicrotask(() => {
2606
2397
  const nodeId = getNodeId();
2607
2398
  const triggers = store.context.triggerElements;
2608
2399
  const insideElements = getResolvedInsideElements();
2609
2400
  const isRelatedFocusGuard = relatedTarget?.hasAttribute(createAttribute('focus-guard')) && [beforeGuardRef.current, afterGuardRef.current, portalContext?.beforeInsideRef.current, portalContext?.afterInsideRef.current, portalContext?.beforeOutsideRef.current, portalContext?.afterOutsideRef.current, useButton.resolveRef(previousFocusableElement), useButton.resolveRef(nextFocusableElement)].includes(relatedTarget);
2610
- const movedToUnrelatedNode = !(useButton.contains(domReference, relatedTarget) || useButton.contains(floating, relatedTarget) || useButton.contains(relatedTarget, floating) || useButton.contains(portalContext?.portalNode, relatedTarget) || insideElements.some(element => element === relatedTarget || useButton.contains(element, relatedTarget)) || relatedTarget != null && triggers.hasElement(relatedTarget) || triggers.hasMatchingElement(trigger => useButton.contains(trigger, relatedTarget)) || isRelatedFocusGuard || tree && (getNodeChildren(tree.nodesRef.current, nodeId).find(node => useButton.contains(node.context?.elements.floating, relatedTarget) || useButton.contains(node.context?.elements.domReference, relatedTarget)) || getNodeAncestors(tree.nodesRef.current, nodeId).find(node => [node.context?.elements.floating, useButton.getFloatingFocusElement(node.context?.elements.floating)].includes(relatedTarget) || node.context?.elements.domReference === relatedTarget)));
2401
+ const movedToUnrelatedNode = !(useButton.contains(domReference, relatedTarget) || useButton.contains(floating, relatedTarget) || useButton.contains(relatedTarget, floating) || useButton.contains(portalContext?.portalNode, relatedTarget) || insideElements.some(element => element === relatedTarget || useButton.contains(element, relatedTarget)) || relatedTarget != null && triggers.hasElement(relatedTarget) || triggers.hasMatchingElement(trigger => useButton.contains(trigger, relatedTarget)) || isRelatedFocusGuard || tree && (getNodeChildren(tree.nodesRef.current, nodeId).find(node => useButton.contains(node.context?.elements.floating, relatedTarget) || useButton.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)));
2611
2402
  if (currentTarget === domReference && floatingFocusElement) {
2612
- handleTabIndex(floatingFocusElement, orderRef);
2403
+ handleTabIndex(floatingFocusElement);
2613
2404
  }
2614
2405
 
2615
2406
  // Restore focus to the previous tabbable element index to prevent
2616
2407
  // focus from being lost outside the floating tree.
2617
- if (restoreFocus && currentTarget !== domReference && !isFocusable(target) && useButton.activeElement(doc) === doc.body) {
2408
+ if (restoreFocus && currentTarget !== domReference && !isElementVisible(target) && useButton.activeElement(doc) === doc.body) {
2618
2409
  // Let `FloatingPortal` effect knows that focus is still inside the
2619
2410
  // floating tree.
2620
2411
  if (floatingUi_utils_dom.isHTMLElement(floatingFocusElement)) {
@@ -2632,9 +2423,9 @@ function FloatingFocusManager(props) {
2632
2423
  return;
2633
2424
  }
2634
2425
  }
2635
- const prevTabbableIndex = tabbableIndexRef.current;
2636
2426
  const tabbableContent = getTabbableContent();
2637
- const nodeToFocus = tabbableContent[prevTabbableIndex] || tabbableContent[tabbableContent.length - 1] || floatingFocusElement;
2427
+ const prevTabbable = lastFocusedTabbableRef.current;
2428
+ const nodeToFocus = (prevTabbable && tabbableContent.includes(prevTabbable) ? prevTabbable : null) || tabbableContent[tabbableContent.length - 1] || floatingFocusElement;
2638
2429
  if (floatingUi_utils_dom.isHTMLElement(nodeToFocus)) {
2639
2430
  nodeToFocus.focus();
2640
2431
  }
@@ -2672,38 +2463,11 @@ function FloatingFocusManager(props) {
2672
2463
  });
2673
2464
  }
2674
2465
  const domReferenceElement = floatingUi_utils_dom.isHTMLElement(domReference) ? domReference : null;
2675
- const cleanups = [];
2676
2466
  if (!floating && !domReferenceElement) {
2677
2467
  return undefined;
2678
2468
  }
2679
- if (domReferenceElement) {
2680
- domReferenceElement.addEventListener('focusout', handleFocusOutside);
2681
- domReferenceElement.addEventListener('pointerdown', handlePointerDown);
2682
- cleanups.push(() => {
2683
- domReferenceElement.removeEventListener('focusout', handleFocusOutside);
2684
- domReferenceElement.removeEventListener('pointerdown', handlePointerDown);
2685
- });
2686
- }
2687
- if (floating) {
2688
- floating.addEventListener('focusin', handleFocusIn);
2689
- floating.addEventListener('focusout', handleFocusOutside);
2690
- if (portalContext) {
2691
- floating.addEventListener('focusout', markInsideReactTree, true);
2692
- cleanups.push(() => {
2693
- floating.removeEventListener('focusout', markInsideReactTree, true);
2694
- });
2695
- }
2696
- cleanups.push(() => {
2697
- floating.removeEventListener('focusin', handleFocusIn);
2698
- floating.removeEventListener('focusout', handleFocusOutside);
2699
- });
2700
- }
2701
- return () => {
2702
- cleanups.forEach(cleanup => {
2703
- cleanup();
2704
- });
2705
- };
2706
- }, [disabled, domReference, floating, floatingFocusElement, modal, tree, portalContext, store, closeOnFocusOut, restoreFocus, getTabbableContent, isUntrappedTypeableCombobox, getNodeId, orderRef, dataRef, blurTimeout, pointerDownTimeout, restoreFocusFrame, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2469
+ return mergeCleanups(domReferenceElement && useButton.addEventListener(domReferenceElement, 'focusout', handleFocusOutside), domReferenceElement && useButton.addEventListener(domReferenceElement, 'pointerdown', handlePointerDown), floating && useButton.addEventListener(floating, 'focusin', handleFocusIn), floating && useButton.addEventListener(floating, 'focusout', handleFocusOutside), floating && portalContext && useButton.addEventListener(floating, 'focusout', markInsideReactTree, true));
2470
+ }, [disabled, domReference, floating, floatingFocusElement, modal, tree, portalContext, store, closeOnFocusOut, restoreFocus, getTabbableContent, isUntrappedTypeableCombobox, getNodeId, dataRef, blurTimeout, pointerDownTimeout, restoreFocusFrame, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2707
2471
 
2708
2472
  // Hide everything outside the floating tree from assistive tech while open.
2709
2473
  React__namespace.useEffect(() => {
@@ -2714,7 +2478,7 @@ function FloatingFocusManager(props) {
2714
2478
  // Don't hide portals nested within the parent portal.
2715
2479
  const portalNodes = Array.from(portalContext?.portalNode?.querySelectorAll(`[${createAttribute('portal')}]`) || []);
2716
2480
  const ancestors = tree ? getNodeAncestors(tree.nodesRef.current, getNodeId()) : [];
2717
- const rootAncestorComboboxDomReference = ancestors.find(node => useButton.isTypeableCombobox(node.context?.elements.domReference || null))?.context?.elements.domReference;
2481
+ const rootAncestorComboboxDomReference = ancestors.find(node => isTypeableCombobox(node.context?.elements.domReference || null))?.context?.elements.domReference;
2718
2482
  const controlInsideElements = [floating, ...portalNodes, beforeGuardRef.current, afterGuardRef.current, portalContext?.beforeOutsideRef.current, portalContext?.afterOutsideRef.current, ...getResolvedInsideElements()];
2719
2483
  const insideElements = [...controlInsideElements, rootAncestorComboboxDomReference, useButton.resolveRef(previousFocusableElement), useButton.resolveRef(nextFocusableElement), isUntrappedTypeableCombobox ? domReference : null].filter(x => x != null);
2720
2484
  const ariaHiddenCleanup = markOthers(insideElements, {
@@ -2727,7 +2491,7 @@ function FloatingFocusManager(props) {
2727
2491
  markerCleanup();
2728
2492
  ariaHiddenCleanup();
2729
2493
  };
2730
- }, [open, disabled, domReference, floating, modal, orderRef, portalContext, isUntrappedTypeableCombobox, tree, getNodeId, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2494
+ }, [open, disabled, domReference, floating, modal, portalContext, isUntrappedTypeableCombobox, tree, getNodeId, nextFocusableElement, previousFocusableElement, getResolvedInsideElements]);
2731
2495
 
2732
2496
  // Focus the initial element when the floating element opens.
2733
2497
  useButton.useIsoLayoutEffect(() => {
@@ -2739,7 +2503,6 @@ function FloatingFocusManager(props) {
2739
2503
 
2740
2504
  // Wait for any layout effect state setters to execute to set `tabIndex`.
2741
2505
  queueMicrotask(() => {
2742
- const focusableElements = getTabbableElements(floatingFocusElement);
2743
2506
  const initialFocusValueOrFn = initialFocusRef.current;
2744
2507
  const resolvedInitialFocus = typeof initialFocusValueOrFn === 'function' ? initialFocusValueOrFn(openInteractionTypeRef.current || '') : initialFocusValueOrFn;
2745
2508
 
@@ -2747,22 +2510,44 @@ function FloatingFocusManager(props) {
2747
2510
  if (resolvedInitialFocus === undefined || resolvedInitialFocus === false) {
2748
2511
  return;
2749
2512
  }
2513
+ const focusAlreadyInsideFloatingEl = useButton.contains(floatingFocusElement, previouslyFocusedElement);
2514
+ if (focusAlreadyInsideFloatingEl) {
2515
+ return;
2516
+ }
2517
+ let focusableElements = null;
2518
+ const getDefaultFocusElement = () => {
2519
+ if (focusableElements == null) {
2520
+ focusableElements = getTabbableContent(floatingFocusElement);
2521
+ }
2522
+ return focusableElements[0] || floatingFocusElement;
2523
+ };
2750
2524
  let elToFocus;
2751
2525
  if (resolvedInitialFocus === true || resolvedInitialFocus === null) {
2752
- elToFocus = focusableElements[0] || floatingFocusElement;
2526
+ elToFocus = getDefaultFocusElement();
2753
2527
  } else {
2754
2528
  elToFocus = useButton.resolveRef(resolvedInitialFocus);
2755
2529
  }
2756
- elToFocus = elToFocus || focusableElements[0] || floatingFocusElement;
2757
- const focusAlreadyInsideFloatingEl = useButton.contains(floatingFocusElement, previouslyFocusedElement);
2758
- if (focusAlreadyInsideFloatingEl) {
2759
- return;
2760
- }
2530
+ elToFocus = elToFocus || getDefaultFocusElement();
2531
+ const hadFocusInside = useButton.contains(floatingFocusElement, useButton.activeElement(doc));
2761
2532
  enqueueFocus(elToFocus, {
2762
- preventScroll: elToFocus === floatingFocusElement
2533
+ preventScroll: elToFocus === floatingFocusElement,
2534
+ shouldFocus() {
2535
+ // This focus is queued on the next animation frame. If the floating element has closed
2536
+ // before it runs — e.g. tabbing out of a kept-mounted popup — don't pull focus back
2537
+ // onto the initial element after it has legitimately moved elsewhere.
2538
+ if (!openRef.current) {
2539
+ return false;
2540
+ }
2541
+ if (hadFocusInside) {
2542
+ return true;
2543
+ }
2544
+ const currentActiveElement = useButton.activeElement(doc);
2545
+ const focusMovedInside = currentActiveElement !== elToFocus && useButton.contains(floatingFocusElement, currentActiveElement);
2546
+ return !focusMovedInside;
2547
+ }
2763
2548
  });
2764
2549
  });
2765
- }, [disabled, open, floatingFocusElement, ignoreInitialFocus, getTabbableElements, initialFocusRef, openInteractionTypeRef]);
2550
+ }, [disabled, open, floatingFocusElement, getTabbableContent, initialFocusRef, openInteractionTypeRef, openRef]);
2766
2551
 
2767
2552
  // Track return focus targets and restore focus on unmount/close.
2768
2553
  useButton.useIsoLayoutEffect(() => {
@@ -2770,8 +2555,11 @@ function FloatingFocusManager(props) {
2770
2555
  return undefined;
2771
2556
  }
2772
2557
  const doc = useButton.ownerDocument(floatingFocusElement);
2773
- const previouslyFocusedElement = useButton.activeElement(doc);
2774
- addPreviouslyFocusedElement(previouslyFocusedElement);
2558
+ const elementFocusedBeforeOpen = useButton.activeElement(doc);
2559
+ // Only nullish interaction types represent programmatic opens. The empty
2560
+ // string default is intentionally not treated as programmatic.
2561
+ const preferPreviousFocus = openInteractionTypeRef.current == null;
2562
+ addPreviouslyFocusedElement(elementFocusedBeforeOpen);
2775
2563
 
2776
2564
  // Dismissing via outside press should always ignore `returnFocus` to
2777
2565
  // prevent unwanted scrolling.
@@ -2791,7 +2579,7 @@ function FloatingFocusManager(props) {
2791
2579
  preventReturnFocusRef.current = false;
2792
2580
  } else {
2793
2581
  let isPreventScrollSupported = false;
2794
- document.createElement('div').focus({
2582
+ useButton.ownerDocument(floatingFocusElement).createElement('div').focus({
2795
2583
  get preventScroll() {
2796
2584
  isPreventScrollSupported = true;
2797
2585
  return false;
@@ -2816,26 +2604,31 @@ function FloatingFocusManager(props) {
2816
2604
  if (resolvedReturnFocusValue === null) {
2817
2605
  resolvedReturnFocusValue = true;
2818
2606
  }
2607
+ const referenceReturnElement = domReference?.isConnected ? domReference : null;
2608
+ const previousReturnElement = elementFocusedBeforeOpen?.isConnected && floatingUi_utils_dom.getNodeName(elementFocusedBeforeOpen) !== 'body' ? elementFocusedBeforeOpen : null;
2609
+ let defaultReturnElement = preferPreviousFocus ? previousReturnElement || referenceReturnElement : referenceReturnElement || previousReturnElement;
2610
+ if (!defaultReturnElement) {
2611
+ defaultReturnElement = getPreviouslyFocusedElement() || null;
2612
+ }
2819
2613
  if (typeof resolvedReturnFocusValue === 'boolean') {
2820
- const el = domReference || getPreviouslyFocusedElement();
2821
- return el && el.isConnected ? el : null;
2614
+ return defaultReturnElement;
2822
2615
  }
2823
- const fallback = domReference || getPreviouslyFocusedElement();
2824
- return useButton.resolveRef(resolvedReturnFocusValue) || fallback || null;
2616
+ return useButton.resolveRef(resolvedReturnFocusValue) || defaultReturnElement || null;
2825
2617
  }
2826
2618
  return () => {
2827
2619
  events.off('openchange', onOpenChangeLocal);
2828
2620
  const activeEl = useButton.activeElement(doc);
2829
2621
  const insideElements = getResolvedInsideElements();
2830
2622
  const isFocusInsideFloatingTree = useButton.contains(floating, activeEl) || insideElements.some(element => element === activeEl || useButton.contains(element, activeEl)) || tree && getNodeChildren(tree.nodesRef.current, getNodeId(), false).some(node => useButton.contains(node.context?.elements.floating, activeEl));
2623
+
2624
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2625
+ const returnFocusValueOrFn = returnFocusRef.current;
2831
2626
  const returnElement = getReturnElement();
2832
2627
  queueMicrotask(() => {
2833
2628
  // This is `returnElement`, if it's tabbable, or its first tabbable child.
2834
2629
  const tabbableReturnElement = getFirstTabbableElement(returnElement);
2835
- const hasExplicitReturnFocus = typeof returnFocusRef.current !== 'boolean';
2836
- if (
2837
- // eslint-disable-next-line react-hooks/exhaustive-deps
2838
- returnFocusRef.current && !preventReturnFocusRef.current && floatingUi_utils_dom.isHTMLElement(tabbableReturnElement) && (
2630
+ const hasExplicitReturnFocus = typeof returnFocusValueOrFn !== 'boolean';
2631
+ if (returnFocusValueOrFn && !preventReturnFocusRef.current && floatingUi_utils_dom.isHTMLElement(tabbableReturnElement) && (
2839
2632
  // If the focus moved somewhere else after mount, avoid returning focus
2840
2633
  // since it likely entered a different element which should be
2841
2634
  // respected: https://github.com/floating-ui/floating-ui/issues/2607
@@ -2847,23 +2640,24 @@ function FloatingFocusManager(props) {
2847
2640
  preventReturnFocusRef.current = false;
2848
2641
  });
2849
2642
  };
2850
- }, [disabled, floating, floatingFocusElement, returnFocusRef, dataRef, events, tree, domReference, getNodeId, getResolvedInsideElements]);
2643
+ }, [disabled, floating, floatingFocusElement, returnFocusRef, openInteractionTypeRef, events, tree, domReference, getNodeId, getResolvedInsideElements]);
2851
2644
 
2852
2645
  // Safari may randomly scroll to the bottom of the page if an input inside a popup has focus
2853
2646
  // when the popup unmounts from the DOM.
2854
2647
  // By blurring it before the popup unmounts, we can prevent this behavior.
2855
2648
  useButton.useIsoLayoutEffect(() => {
2856
- if (!useButton.isWebKit || open || !floating) {
2649
+ if (!webkit || open || !floating) {
2857
2650
  return;
2858
2651
  }
2859
2652
  const activeEl = useButton.activeElement(useButton.ownerDocument(floating));
2860
- if (!floatingUi_utils_dom.isHTMLElement(activeEl) || !useButton.isTypeableElement(activeEl)) {
2653
+ if (!floatingUi_utils_dom.isHTMLElement(activeEl) || !isTypeableElement(activeEl)) {
2861
2654
  return;
2862
2655
  }
2863
2656
  if (useButton.contains(floating, activeEl)) {
2864
2657
  activeEl.blur();
2865
2658
  }
2866
2659
  }, [open, floating]);
2660
+
2867
2661
  // Synchronize the `context` & `modal` value to the FloatingPortal context.
2868
2662
  // It will decide whether or not it needs to render its own guards.
2869
2663
  useButton.useIsoLayoutEffect(() => {
@@ -2887,11 +2681,11 @@ function FloatingFocusManager(props) {
2887
2681
  if (disabled || !floatingFocusElement) {
2888
2682
  return undefined;
2889
2683
  }
2890
- handleTabIndex(floatingFocusElement, orderRef);
2684
+ handleTabIndex(floatingFocusElement);
2891
2685
  return () => {
2892
2686
  queueMicrotask(clearDisconnectedPreviouslyFocusedElements);
2893
2687
  };
2894
- }, [disabled, floatingFocusElement, orderRef]);
2688
+ }, [disabled, floatingFocusElement]);
2895
2689
  const shouldRenderGuards = !disabled && (modal ? !isUntrappedTypeableCombobox : true) && (isInsidePortal || modal);
2896
2690
  return /*#__PURE__*/jsxRuntime.jsxs(React__namespace.Fragment, {
2897
2691
  children: [shouldRenderGuards && /*#__PURE__*/jsxRuntime.jsx(FocusGuard, {
@@ -2899,7 +2693,7 @@ function FloatingFocusManager(props) {
2899
2693
  ref: mergedBeforeGuardRef,
2900
2694
  onFocus: event => {
2901
2695
  if (modal) {
2902
- const els = getTabbableElements();
2696
+ const els = getTabbableContent();
2903
2697
  enqueueFocus(els[els.length - 1]);
2904
2698
  } else if (portalContext?.portalNode) {
2905
2699
  preventReturnFocusRef.current = false;
@@ -2916,7 +2710,7 @@ function FloatingFocusManager(props) {
2916
2710
  ref: mergedAfterGuardRef,
2917
2711
  onFocus: event => {
2918
2712
  if (modal) {
2919
- enqueueFocus(getTabbableElements()[0]);
2713
+ enqueueFocus(getTabbableContent()[0]);
2920
2714
  } else if (portalContext?.portalNode) {
2921
2715
  if (closeOnFocusOut) {
2922
2716
  preventReturnFocusRef.current = true;
@@ -2938,8 +2732,6 @@ function FloatingFocusManager(props) {
2938
2732
  * @see https://floating-ui.com/docs/useClick
2939
2733
  */
2940
2734
  function useClick(context, props = {}) {
2941
- const store = 'rootStore' in context ? context.rootStore : context;
2942
- const dataRef = store.context.dataRef;
2943
2735
  const {
2944
2736
  enabled = true,
2945
2737
  event: eventOption = 'click',
@@ -2949,97 +2741,105 @@ function useClick(context, props = {}) {
2949
2741
  touchOpenDelay = 0,
2950
2742
  reason = useButton.triggerPress
2951
2743
  } = props;
2744
+ const store = 'rootStore' in context ? context.rootStore : context;
2745
+ const dataRef = store.context.dataRef;
2952
2746
  const pointerTypeRef = React__namespace.useRef(undefined);
2953
2747
  const frame = useButton.useAnimationFrame();
2954
2748
  const touchOpenTimeout = useButton.useTimeout();
2955
- const reference = React__namespace.useMemo(() => ({
2956
- onPointerDown(event) {
2957
- pointerTypeRef.current = event.pointerType;
2958
- },
2959
- onMouseDown(event) {
2960
- const pointerType = pointerTypeRef.current;
2961
- const nativeEvent = event.nativeEvent;
2962
- const open = store.select('open');
2963
-
2964
- // Ignore all buttons except for the "main" button.
2965
- // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
2966
- if (event.button !== 0 || eventOption === 'click' || useButton.isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2967
- return;
2749
+ const reference = React__namespace.useMemo(() => {
2750
+ function setOpenWithTouchDelay(nextOpen, nativeEvent, target, pointerType) {
2751
+ const details = useButton.createChangeEventDetails(reason, nativeEvent, target);
2752
+ if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
2753
+ touchOpenTimeout.start(touchOpenDelay, () => {
2754
+ store.setOpen(true, details);
2755
+ });
2756
+ } else {
2757
+ store.setOpen(nextOpen, details);
2968
2758
  }
2759
+ }
2760
+ function getNextOpen(open, currentTarget, isClickLikeOpenEvent) {
2969
2761
  const openEvent = dataRef.current.openEvent;
2970
- const openEventType = openEvent?.type;
2971
- const hasClickedOnInactiveTrigger = store.select('domReferenceElement') !== event.currentTarget;
2972
- const nextOpen = open && hasClickedOnInactiveTrigger || !(open && toggle && (openEvent && stickIfOpen ? openEventType === 'click' || openEventType === 'mousedown' : true));
2973
-
2974
- // Animations sometimes won't run on a typeable element if using a rAF.
2975
- // Focus is always set on these elements. For touch, we may delay opening.
2976
- if (useButton.isTypeableElement(nativeEvent.target)) {
2977
- const details = useButton.createChangeEventDetails(reason, nativeEvent, nativeEvent.target);
2978
- if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
2979
- touchOpenTimeout.start(touchOpenDelay, () => {
2980
- store.setOpen(true, details);
2981
- });
2982
- } else {
2983
- store.setOpen(nextOpen, details);
2984
- }
2985
- return;
2762
+ const hasClickedOnInactiveTrigger = store.select('domReferenceElement') !== currentTarget;
2763
+ if (open && hasClickedOnInactiveTrigger) {
2764
+ // Moving between triggers should always open the newly active one.
2765
+ return true;
2986
2766
  }
2987
-
2988
- // Capture the currentTarget before the rAF.
2989
- // as React sets it to null after the event handler completes.
2990
- const eventCurrentTarget = event.currentTarget;
2991
-
2992
- // Wait until focus is set on the element. This is an alternative to
2993
- // `event.preventDefault()` to avoid :focus-visible from appearing when using a pointer.
2994
- frame.request(() => {
2995
- const details = useButton.createChangeEventDetails(reason, nativeEvent, eventCurrentTarget);
2996
- if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
2997
- touchOpenTimeout.start(touchOpenDelay, () => {
2998
- store.setOpen(true, details);
2999
- });
3000
- } else {
3001
- store.setOpen(nextOpen, details);
3002
- }
3003
- });
3004
- },
3005
- onClick(event) {
3006
- if (eventOption === 'mousedown-only') {
3007
- return;
2767
+ if (!open) {
2768
+ // A closed popup should open on the next press.
2769
+ return true;
3008
2770
  }
3009
- const pointerType = pointerTypeRef.current;
3010
- if (eventOption === 'mousedown' && pointerType) {
3011
- pointerTypeRef.current = undefined;
3012
- return;
2771
+ if (!toggle) {
2772
+ // Non-toggle mode never closes on a repeated trigger press.
2773
+ return true;
3013
2774
  }
3014
- if (useButton.isMouseLikePointerType(pointerType, true) && ignoreMouse) {
3015
- return;
2775
+ if (openEvent && stickIfOpen) {
2776
+ // Preserve hover/focus-opened popups until the matching click-like event closes them.
2777
+ return !isClickLikeOpenEvent(openEvent.type);
3016
2778
  }
3017
- const open = store.select('open');
3018
- const openEvent = dataRef.current.openEvent;
3019
- const hasClickedOnInactiveTrigger = store.select('domReferenceElement') !== event.currentTarget;
3020
- const nextOpen = open && hasClickedOnInactiveTrigger || !(open && toggle && (openEvent && stickIfOpen ? useButton.isClickLikeEvent(openEvent) : true));
3021
- const details = useButton.createChangeEventDetails(reason, event.nativeEvent, event.currentTarget);
3022
- if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {
3023
- touchOpenTimeout.start(touchOpenDelay, () => {
3024
- store.setOpen(true, details);
2779
+
2780
+ // Otherwise, a repeated click toggles the popup closed.
2781
+ return false;
2782
+ }
2783
+ return {
2784
+ onPointerDown(event) {
2785
+ pointerTypeRef.current = event.pointerType;
2786
+ },
2787
+ onMouseDown(event) {
2788
+ const pointerType = pointerTypeRef.current;
2789
+ const nativeEvent = event.nativeEvent;
2790
+ const open = store.select('open');
2791
+
2792
+ // Ignore all buttons except for the "main" button.
2793
+ // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
2794
+ if (event.button !== 0 || eventOption === 'click' || useButton.isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2795
+ return;
2796
+ }
2797
+ const nextOpen = getNextOpen(open, event.currentTarget, openEventType => openEventType === 'click' || openEventType === 'mousedown');
2798
+
2799
+ // Animations sometimes won't run on a typeable element if using a rAF.
2800
+ // Focus is always set on these elements. For touch, we may delay opening.
2801
+ const target = useButton.getTarget(nativeEvent);
2802
+ if (isTypeableElement(target)) {
2803
+ setOpenWithTouchDelay(nextOpen, nativeEvent, target, pointerType);
2804
+ return;
2805
+ }
2806
+
2807
+ // Capture the currentTarget before the rAF.
2808
+ // as React sets it to null after the event handler completes.
2809
+ const eventCurrentTarget = event.currentTarget;
2810
+
2811
+ // Wait until focus is set on the element. This is an alternative to
2812
+ // `event.preventDefault()` to avoid :focus-visible from appearing when using a pointer.
2813
+ frame.request(() => {
2814
+ setOpenWithTouchDelay(nextOpen, nativeEvent, eventCurrentTarget, pointerType);
3025
2815
  });
3026
- } else {
3027
- store.setOpen(nextOpen, details);
2816
+ },
2817
+ onClick(event) {
2818
+ if (eventOption === 'mousedown-only') {
2819
+ return;
2820
+ }
2821
+ const pointerType = pointerTypeRef.current;
2822
+ if (eventOption === 'mousedown' && pointerType) {
2823
+ pointerTypeRef.current = undefined;
2824
+ return;
2825
+ }
2826
+ if (useButton.isMouseLikePointerType(pointerType, true) && ignoreMouse) {
2827
+ return;
2828
+ }
2829
+ const open = store.select('open');
2830
+ const nextOpen = getNextOpen(open, event.currentTarget, openEventType => openEventType === 'click' || openEventType === 'mousedown' || openEventType === 'keydown' || openEventType === 'keyup');
2831
+ setOpenWithTouchDelay(nextOpen, event.nativeEvent, event.currentTarget, pointerType);
2832
+ },
2833
+ onKeyDown() {
2834
+ pointerTypeRef.current = undefined;
3028
2835
  }
3029
- },
3030
- onKeyDown() {
3031
- pointerTypeRef.current = undefined;
3032
- }
3033
- }), [dataRef, eventOption, ignoreMouse, store, stickIfOpen, toggle, frame, touchOpenTimeout, touchOpenDelay, reason]);
2836
+ };
2837
+ }, [dataRef, eventOption, ignoreMouse, reason, store, stickIfOpen, toggle, frame, touchOpenTimeout, touchOpenDelay]);
3034
2838
  return React__namespace.useMemo(() => enabled ? {
3035
2839
  reference
3036
2840
  } : useRenderElement.EMPTY_OBJECT, [enabled, reference]);
3037
2841
  }
3038
2842
 
3039
- const bubbleHandlerKeys = {
3040
- intentional: 'onClick',
3041
- sloppy: 'onPointerDown'
3042
- };
3043
2843
  function alwaysFalse() {
3044
2844
  return false;
3045
2845
  }
@@ -3055,35 +2855,36 @@ function normalizeProp(normalizable) {
3055
2855
  * @see https://floating-ui.com/docs/useDismiss
3056
2856
  */
3057
2857
  function useDismiss(context, props = {}) {
3058
- const store = 'rootStore' in context ? context.rootStore : context;
3059
- const open = store.useState('open');
3060
- const floatingElement = store.useState('floatingElement');
3061
- const {
3062
- dataRef
3063
- } = store.context;
3064
2858
  const {
3065
2859
  enabled = true,
3066
2860
  escapeKey = true,
3067
2861
  outsidePress: outsidePressProp = true,
3068
2862
  outsidePressEvent = 'sloppy',
3069
2863
  referencePress = alwaysFalse,
3070
- referencePressEvent = 'sloppy',
3071
2864
  bubbles,
3072
2865
  externalTree
3073
2866
  } = props;
2867
+ const store = 'rootStore' in context ? context.rootStore : context;
2868
+ const open = store.useState('open');
2869
+ const floatingElement = store.useState('floatingElement');
2870
+ const {
2871
+ dataRef
2872
+ } = store.context;
3074
2873
  const tree = useFloatingTree(externalTree);
3075
2874
  const outsidePressFn = useButton.useStableCallback(typeof outsidePressProp === 'function' ? outsidePressProp : () => false);
3076
2875
  const outsidePress = typeof outsidePressProp === 'function' ? outsidePressFn : outsidePressProp;
3077
2876
  const outsidePressEnabled = outsidePress !== false;
3078
2877
  const getOutsidePressEventProp = useButton.useStableCallback(() => outsidePressEvent);
3079
- const pressStartedInsideRef = React__namespace.useRef(false);
3080
- const pressStartPreventedRef = React__namespace.useRef(false);
3081
- // Ignore only the very next outside click after dragging from inside to outside.
3082
- const suppressNextOutsideClickRef = React__namespace.useRef(false);
3083
2878
  const {
3084
2879
  escapeKey: escapeKeyBubbles,
3085
2880
  outsidePress: outsidePressBubbles
3086
2881
  } = normalizeProp(bubbles);
2882
+ const pressStartedInsideRef = React__namespace.useRef(false);
2883
+ const pressStartPreventedRef = React__namespace.useRef(false);
2884
+ // Ignore only the very next outside click after dragging from inside to outside.
2885
+ const suppressNextOutsideClickRef = React__namespace.useRef(false);
2886
+ const isComposingRef = React__namespace.useRef(false);
2887
+ const currentPointerTypeRef = React__namespace.useRef('');
3087
2888
  const touchStateRef = React__namespace.useRef(null);
3088
2889
  const cancelDismissOnEndTimeout = useButton.useTimeout();
3089
2890
  const clearInsideReactTreeTimeout = useButton.useTimeout();
@@ -3091,9 +2892,20 @@ function useDismiss(context, props = {}) {
3091
2892
  clearInsideReactTreeTimeout.clear();
3092
2893
  dataRef.current.insideReactTree = false;
3093
2894
  });
3094
- const isComposingRef = React__namespace.useRef(false);
3095
- const currentPointerTypeRef = React__namespace.useRef('');
3096
- const isReferencePressEnabled = useButton.useStableCallback(referencePress);
2895
+ const hasBlockingChild = useButton.useStableCallback(bubbleKey => {
2896
+ const nodeId = dataRef.current.floatingContext?.nodeId;
2897
+ const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
2898
+ return children.some(child => child.context?.open && !child.context.dataRef.current[bubbleKey]);
2899
+ });
2900
+ const isEventWithinOwnElements = useButton.useStableCallback(event => {
2901
+ return isEventTargetWithin(event, store.select('floatingElement')) || isEventTargetWithin(event, store.select('domReferenceElement'));
2902
+ });
2903
+ const closeOnReferencePress = useButton.useStableCallback(event => {
2904
+ if (!referencePress()) {
2905
+ return;
2906
+ }
2907
+ store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerPress, event.nativeEvent));
2908
+ });
3097
2909
  const closeOnEscapeKeyDown = useButton.useStableCallback(event => {
3098
2910
  if (!open || !enabled || !escapeKey || event.key !== 'Escape') {
3099
2911
  return;
@@ -3104,24 +2916,15 @@ function useDismiss(context, props = {}) {
3104
2916
  if (isComposingRef.current) {
3105
2917
  return;
3106
2918
  }
3107
- const nodeId = dataRef.current.floatingContext?.nodeId;
3108
- const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
3109
- if (!escapeKeyBubbles) {
3110
- if (children.length > 0) {
3111
- let shouldDismiss = true;
3112
- children.forEach(child => {
3113
- if (child.context?.open && !child.context.dataRef.current.__escapeKeyBubbles) {
3114
- shouldDismiss = false;
3115
- }
3116
- });
3117
- if (!shouldDismiss) {
3118
- return;
3119
- }
3120
- }
2919
+ if (!escapeKeyBubbles && hasBlockingChild('__escapeKeyBubbles')) {
2920
+ return;
3121
2921
  }
3122
2922
  const native = useButton.isReactEvent(event) ? event.nativeEvent : event;
3123
2923
  const eventDetails = useButton.createChangeEventDetails(useButton.escapeKey, native);
3124
2924
  store.setOpen(false, eventDetails);
2925
+ if (!eventDetails.isCanceled) {
2926
+ event.preventDefault();
2927
+ }
3125
2928
  if (!escapeKeyBubbles && !eventDetails.isPropagationAllowed) {
3126
2929
  event.stopPropagation();
3127
2930
  }
@@ -3130,6 +2933,33 @@ function useDismiss(context, props = {}) {
3130
2933
  dataRef.current.insideReactTree = true;
3131
2934
  clearInsideReactTreeTimeout.start(0, clearInsideReactTree);
3132
2935
  });
2936
+ const markPressStartedInsideReactTree = useButton.useStableCallback(event => {
2937
+ if (!open || !enabled || event.button !== 0) {
2938
+ return;
2939
+ }
2940
+ const target = useButton.getTarget(event.nativeEvent);
2941
+
2942
+ // Only treat presses that start within the floating DOM subtree as inside.
2943
+ // This avoids suppressing parent dismissal when interacting with nested portals.
2944
+ if (!useButton.contains(store.select('floatingElement'), target)) {
2945
+ return;
2946
+ }
2947
+ if (!pressStartedInsideRef.current) {
2948
+ pressStartedInsideRef.current = true;
2949
+ pressStartPreventedRef.current = false;
2950
+ }
2951
+ });
2952
+ const markInsidePressStartPrevented = useButton.useStableCallback(event => {
2953
+ if (!open || !enabled) {
2954
+ return;
2955
+ }
2956
+ if (!(event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
2957
+ return;
2958
+ }
2959
+ if (pressStartedInsideRef.current) {
2960
+ pressStartPreventedRef.current = true;
2961
+ }
2962
+ });
3133
2963
  React__namespace.useEffect(() => {
3134
2964
  if (!open || !enabled) {
3135
2965
  return undefined;
@@ -3149,7 +2979,7 @@ function useDismiss(context, props = {}) {
3149
2979
  compositionTimeout.start(
3150
2980
  // 0ms or 1ms don't work in Safari. 5ms appears to consistently work.
3151
2981
  // Only apply to WebKit for the test to remain 0ms.
3152
- floatingUi_utils_dom.isWebKit() ? 5 : 0, () => {
2982
+ webkit ? 5 : 0, () => {
3153
2983
  isComposingRef.current = false;
3154
2984
  });
3155
2985
  }
@@ -3181,11 +3011,17 @@ function useDismiss(context, props = {}) {
3181
3011
  }
3182
3012
  function isEventWithinFloatingTree(event) {
3183
3013
  const nodeId = dataRef.current.floatingContext?.nodeId;
3184
- const targetIsInsideChildren = tree && getNodeChildren(tree.nodesRef.current, nodeId).some(node => useButton.isEventTargetWithin(event, node.context?.elements.floating));
3185
- return useButton.isEventTargetWithin(event, store.select('floatingElement')) || useButton.isEventTargetWithin(event, store.select('domReferenceElement')) || targetIsInsideChildren;
3014
+ const targetIsInsideChildren = tree && getNodeChildren(tree.nodesRef.current, nodeId).some(node => isEventTargetWithin(event, node.context?.elements.floating));
3015
+ return isEventWithinOwnElements(event) || targetIsInsideChildren;
3186
3016
  }
3187
3017
  function closeOnPressOutside(event) {
3188
3018
  if (shouldIgnoreEvent(event)) {
3019
+ // A new press began outside the floating element and its trigger. Clear any
3020
+ // leftover drag-out suppression so this press's eventual click can dismiss.
3021
+ if (event.type !== 'click' && !isEventWithinOwnElements(event)) {
3022
+ preventedPressSuppressionTimeout.clear();
3023
+ suppressNextOutsideClickRef.current = false;
3024
+ }
3189
3025
  clearInsideReactTree();
3190
3026
  return;
3191
3027
  }
@@ -3195,11 +3031,8 @@ function useDismiss(context, props = {}) {
3195
3031
  }
3196
3032
  const target = useButton.getTarget(event);
3197
3033
  const inertSelector = `[${createAttribute('inert')}]`;
3198
- let markers = Array.from(useButton.ownerDocument(store.select('floatingElement')).querySelectorAll(inertSelector));
3199
3034
  const targetRoot = floatingUi_utils_dom.isElement(target) ? target.getRootNode() : null;
3200
- if (floatingUi_utils_dom.isShadowRoot(targetRoot)) {
3201
- markers = markers.concat(Array.from(targetRoot.querySelectorAll(inertSelector)));
3202
- }
3035
+ const markers = Array.from((floatingUi_utils_dom.isShadowRoot(targetRoot) ? targetRoot : useButton.ownerDocument(store.select('floatingElement'))).querySelectorAll(inertSelector));
3203
3036
  const triggers = store.context.triggerElements;
3204
3037
 
3205
3038
  // If another trigger is clicked, don't close the floating element.
@@ -3217,7 +3050,7 @@ function useDismiss(context, props = {}) {
3217
3050
 
3218
3051
  // Check if the click occurred on a third-party element injected after the
3219
3052
  // floating element rendered.
3220
- if (markers.length && floatingUi_utils_dom.isElement(target) && !useButton.isRootElement(target) &&
3053
+ if (markers.length && floatingUi_utils_dom.isElement(target) && !isRootElement(target) &&
3221
3054
  // Clicked on a direct ancestor (e.g. FloatingOverlay).
3222
3055
  !useButton.contains(target, store.select('floatingElement')) &&
3223
3056
  // If the target root element contains none of the markers, then the
@@ -3264,30 +3097,20 @@ function useDismiss(context, props = {}) {
3264
3097
  if (typeof outsidePress === 'function' && !outsidePress(event)) {
3265
3098
  return;
3266
3099
  }
3267
- const nodeId = dataRef.current.floatingContext?.nodeId;
3268
- const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
3269
- if (children.length > 0) {
3270
- let shouldDismiss = true;
3271
- children.forEach(child => {
3272
- if (child.context?.open && !child.context.dataRef.current.__outsidePressBubbles) {
3273
- shouldDismiss = false;
3274
- }
3275
- });
3276
- if (!shouldDismiss) {
3277
- return;
3278
- }
3100
+ if (hasBlockingChild('__outsidePressBubbles')) {
3101
+ return;
3279
3102
  }
3280
3103
  store.setOpen(false, useButton.createChangeEventDetails(useButton.outsidePress, event));
3281
3104
  clearInsideReactTree();
3282
3105
  }
3283
3106
  function handlePointerDown(event) {
3284
- if (getOutsidePressEvent() !== 'sloppy' || event.pointerType === 'touch' || !store.select('open') || !enabled || useButton.isEventTargetWithin(event, store.select('floatingElement')) || useButton.isEventTargetWithin(event, store.select('domReferenceElement'))) {
3107
+ if (getOutsidePressEvent() !== 'sloppy' || event.pointerType === 'touch' || !store.select('open') || !enabled || isEventWithinOwnElements(event)) {
3285
3108
  return;
3286
3109
  }
3287
3110
  closeOnPressOutside(event);
3288
3111
  }
3289
3112
  function handleTouchStart(event) {
3290
- if (getOutsidePressEvent() !== 'sloppy' || !store.select('open') || !enabled || useButton.isEventTargetWithin(event, store.select('floatingElement')) || useButton.isEventTargetWithin(event, store.select('domReferenceElement'))) {
3113
+ if (getOutsidePressEvent() !== 'sloppy' || !store.select('open') || !enabled || isEventWithinOwnElements(event)) {
3291
3114
  return;
3292
3115
  }
3293
3116
  const touch = event.touches[0];
@@ -3307,14 +3130,19 @@ function useDismiss(context, props = {}) {
3307
3130
  });
3308
3131
  }
3309
3132
  }
3310
- function handleTouchStartCapture(event) {
3311
- currentPointerTypeRef.current = 'touch';
3133
+ function addTargetEventListenerOnce(event, listener) {
3312
3134
  const target = useButton.getTarget(event);
3313
- function callback() {
3314
- handleTouchStart(event);
3315
- target?.removeEventListener(event.type, callback);
3135
+ if (!target) {
3136
+ return;
3316
3137
  }
3317
- target?.addEventListener(event.type, callback);
3138
+ const unsubscribe = useButton.addEventListener(target, event.type, () => {
3139
+ listener(event);
3140
+ unsubscribe();
3141
+ });
3142
+ }
3143
+ function handleTouchStartCapture(event) {
3144
+ currentPointerTypeRef.current = 'touch';
3145
+ addTargetEventListenerOnce(event, handleTouchStart);
3318
3146
  }
3319
3147
  function closeOnPressOutsideCapture(event) {
3320
3148
  cancelDismissOnEndTimeout.clear();
@@ -3324,16 +3152,13 @@ function useDismiss(context, props = {}) {
3324
3152
  if (event.type === 'mousedown' && touchStateRef.current && !touchStateRef.current.dismissOnMouseDown) {
3325
3153
  return;
3326
3154
  }
3327
- const target = useButton.getTarget(event);
3328
- function callback() {
3329
- if (event.type === 'pointerdown') {
3330
- handlePointerDown(event);
3155
+ addTargetEventListenerOnce(event, targetEvent => {
3156
+ if (targetEvent.type === 'pointerdown') {
3157
+ handlePointerDown(targetEvent);
3331
3158
  } else {
3332
- closeOnPressOutside(event);
3159
+ closeOnPressOutside(targetEvent);
3333
3160
  }
3334
- target?.removeEventListener(event.type, callback);
3335
- }
3336
- target?.addEventListener(event.type, callback);
3161
+ });
3337
3162
  }
3338
3163
  function handlePressEndCapture(event) {
3339
3164
  if (!pressStartedInsideRef.current) {
@@ -3372,7 +3197,7 @@ function useDismiss(context, props = {}) {
3372
3197
  clearInsideReactTree();
3373
3198
  }
3374
3199
  function handleTouchMove(event) {
3375
- if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || useButton.isEventTargetWithin(event, store.select('floatingElement')) || useButton.isEventTargetWithin(event, store.select('domReferenceElement'))) {
3200
+ if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || isEventWithinOwnElements(event)) {
3376
3201
  return;
3377
3202
  }
3378
3203
  const touch = event.touches[0];
@@ -3392,15 +3217,10 @@ function useDismiss(context, props = {}) {
3392
3217
  }
3393
3218
  }
3394
3219
  function handleTouchMoveCapture(event) {
3395
- const target = useButton.getTarget(event);
3396
- function callback() {
3397
- handleTouchMove(event);
3398
- target?.removeEventListener(event.type, callback);
3399
- }
3400
- target?.addEventListener(event.type, callback);
3220
+ addTargetEventListenerOnce(event, handleTouchMove);
3401
3221
  }
3402
3222
  function handleTouchEnd(event) {
3403
- if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || useButton.isEventTargetWithin(event, store.select('floatingElement')) || useButton.isEventTargetWithin(event, store.select('domReferenceElement'))) {
3223
+ if (getOutsidePressEvent() !== 'sloppy' || !touchStateRef.current || isEventWithinOwnElements(event)) {
3404
3224
  return;
3405
3225
  }
3406
3226
  if (touchStateRef.current.dismissOnTouchEnd) {
@@ -3410,97 +3230,24 @@ function useDismiss(context, props = {}) {
3410
3230
  touchStateRef.current = null;
3411
3231
  }
3412
3232
  function handleTouchEndCapture(event) {
3413
- const target = useButton.getTarget(event);
3414
- function callback() {
3415
- handleTouchEnd(event);
3416
- target?.removeEventListener(event.type, callback);
3417
- }
3418
- target?.addEventListener(event.type, callback);
3233
+ addTargetEventListenerOnce(event, handleTouchEnd);
3419
3234
  }
3420
3235
  const doc = useButton.ownerDocument(floatingElement);
3421
- if (escapeKey) {
3422
- doc.addEventListener('keydown', closeOnEscapeKeyDown);
3423
- doc.addEventListener('compositionstart', handleCompositionStart);
3424
- doc.addEventListener('compositionend', handleCompositionEnd);
3425
- }
3426
- if (outsidePressEnabled) {
3427
- doc.addEventListener('click', closeOnPressOutsideCapture, true);
3428
- doc.addEventListener('pointerdown', closeOnPressOutsideCapture, true);
3429
- doc.addEventListener('pointerup', handlePressEndCapture, true);
3430
- doc.addEventListener('pointercancel', handlePressEndCapture, true);
3431
- doc.addEventListener('mousedown', closeOnPressOutsideCapture, true);
3432
- doc.addEventListener('mouseup', handlePressEndCapture, true);
3433
- doc.addEventListener('touchstart', handleTouchStartCapture, true);
3434
- doc.addEventListener('touchmove', handleTouchMoveCapture, true);
3435
- doc.addEventListener('touchend', handleTouchEndCapture, true);
3436
- }
3236
+ const unsubscribe = mergeCleanups(escapeKey && mergeCleanups(useButton.addEventListener(doc, 'keydown', closeOnEscapeKeyDown), useButton.addEventListener(doc, 'compositionstart', handleCompositionStart), useButton.addEventListener(doc, 'compositionend', handleCompositionEnd)), outsidePressEnabled && mergeCleanups(useButton.addEventListener(doc, 'click', closeOnPressOutsideCapture, true), useButton.addEventListener(doc, 'pointerdown', closeOnPressOutsideCapture, true), useButton.addEventListener(doc, 'pointerup', handlePressEndCapture, true), useButton.addEventListener(doc, 'pointercancel', handlePressEndCapture, true), useButton.addEventListener(doc, 'mousedown', closeOnPressOutsideCapture, true), useButton.addEventListener(doc, 'mouseup', handlePressEndCapture, true), useButton.addEventListener(doc, 'touchstart', handleTouchStartCapture, true), useButton.addEventListener(doc, 'touchmove', handleTouchMoveCapture, true), useButton.addEventListener(doc, 'touchend', handleTouchEndCapture, true)));
3437
3237
  return () => {
3438
- if (escapeKey) {
3439
- doc.removeEventListener('keydown', closeOnEscapeKeyDown);
3440
- doc.removeEventListener('compositionstart', handleCompositionStart);
3441
- doc.removeEventListener('compositionend', handleCompositionEnd);
3442
- }
3443
- if (outsidePressEnabled) {
3444
- doc.removeEventListener('click', closeOnPressOutsideCapture, true);
3445
- doc.removeEventListener('pointerdown', closeOnPressOutsideCapture, true);
3446
- doc.removeEventListener('pointerup', handlePressEndCapture, true);
3447
- doc.removeEventListener('pointercancel', handlePressEndCapture, true);
3448
- doc.removeEventListener('mousedown', closeOnPressOutsideCapture, true);
3449
- doc.removeEventListener('mouseup', handlePressEndCapture, true);
3450
- doc.removeEventListener('touchstart', handleTouchStartCapture, true);
3451
- doc.removeEventListener('touchmove', handleTouchMoveCapture, true);
3452
- doc.removeEventListener('touchend', handleTouchEndCapture, true);
3453
- }
3238
+ unsubscribe();
3454
3239
  compositionTimeout.clear();
3455
3240
  preventedPressSuppressionTimeout.clear();
3456
3241
  resetPressStartState();
3457
3242
  suppressNextOutsideClickRef.current = false;
3458
3243
  };
3459
- }, [dataRef, floatingElement, escapeKey, outsidePressEnabled, outsidePress, open, enabled, escapeKeyBubbles, outsidePressBubbles, closeOnEscapeKeyDown, clearInsideReactTree, getOutsidePressEventProp, tree, store, cancelDismissOnEndTimeout]);
3244
+ }, [dataRef, floatingElement, escapeKey, outsidePressEnabled, outsidePress, open, enabled, escapeKeyBubbles, outsidePressBubbles, closeOnEscapeKeyDown, clearInsideReactTree, getOutsidePressEventProp, hasBlockingChild, isEventWithinOwnElements, tree, store, cancelDismissOnEndTimeout]);
3460
3245
  React__namespace.useEffect(clearInsideReactTree, [outsidePress, clearInsideReactTree]);
3461
3246
  const reference = React__namespace.useMemo(() => ({
3462
3247
  onKeyDown: closeOnEscapeKeyDown,
3463
- [bubbleHandlerKeys[referencePressEvent]]: event => {
3464
- if (!isReferencePressEnabled()) {
3465
- return;
3466
- }
3467
- store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerPress, event.nativeEvent));
3468
- },
3469
- ...(referencePressEvent !== 'intentional' && {
3470
- onClick(event) {
3471
- if (!isReferencePressEnabled()) {
3472
- return;
3473
- }
3474
- store.setOpen(false, useButton.createChangeEventDetails(useButton.triggerPress, event.nativeEvent));
3475
- }
3476
- })
3477
- }), [closeOnEscapeKeyDown, store, referencePressEvent, isReferencePressEnabled]);
3478
- const markPressStartedInsideReactTree = useButton.useStableCallback(event => {
3479
- if (!open || !enabled || event.button !== 0) {
3480
- return;
3481
- }
3482
- const target = useButton.getTarget(event.nativeEvent);
3483
- // Only treat presses that start within the floating DOM subtree as inside.
3484
- // This avoids suppressing parent dismissal when interacting with nested portals.
3485
- if (!useButton.contains(store.select('floatingElement'), target)) {
3486
- return;
3487
- }
3488
- if (!pressStartedInsideRef.current) {
3489
- pressStartedInsideRef.current = true;
3490
- pressStartPreventedRef.current = false;
3491
- }
3492
- });
3493
- const markInsidePressStartPrevented = useButton.useStableCallback(event => {
3494
- if (!open || !enabled) {
3495
- return;
3496
- }
3497
- if (!(event.defaultPrevented || event.nativeEvent.defaultPrevented)) {
3498
- return;
3499
- }
3500
- if (pressStartedInsideRef.current) {
3501
- pressStartPreventedRef.current = true;
3502
- }
3503
- });
3248
+ onPointerDown: closeOnReferencePress,
3249
+ onClick: closeOnReferencePress
3250
+ }), [closeOnEscapeKeyDown, closeOnReferencePress]);
3504
3251
  const floating = React__namespace.useMemo(() => ({
3505
3252
  onKeyDown: closeOnEscapeKeyDown,
3506
3253
  // `onMouseDown` may be blocked if `event.preventDefault()` is called in
@@ -3530,6 +3277,7 @@ function useDismiss(context, props = {}) {
3530
3277
 
3531
3278
  const selectors = {
3532
3279
  open: createSelector(state => state.open),
3280
+ transitionStatus: createSelector(state => state.transitionStatus),
3533
3281
  domReferenceElement: createSelector(state => state.domReferenceElement),
3534
3282
  referenceElement: createSelector(state => state.positionReference ?? state.referenceElement),
3535
3283
  floatingElement: createSelector(state => state.floatingElement),
@@ -3538,8 +3286,8 @@ const selectors = {
3538
3286
  class FloatingRootStore extends ReactStore {
3539
3287
  constructor(options) {
3540
3288
  const {
3289
+ syncOnly,
3541
3290
  nested,
3542
- noEmit,
3543
3291
  onOpenChange,
3544
3292
  triggerElements,
3545
3293
  ...initialState
@@ -3555,11 +3303,38 @@ class FloatingRootStore extends ReactStore {
3555
3303
  },
3556
3304
  events: createEventEmitter(),
3557
3305
  nested,
3558
- noEmit,
3559
3306
  triggerElements
3560
3307
  }, selectors);
3308
+ this.syncOnly = syncOnly;
3561
3309
  }
3562
3310
 
3311
+ /**
3312
+ * Syncs the event used by hover logic to distinguish hover-open from click-like interaction.
3313
+ */
3314
+ syncOpenEvent = (newOpen, event) => {
3315
+ if (!newOpen || !this.state.open ||
3316
+ // Prevent a pending hover-open from overwriting a click-open event, while allowing
3317
+ // click events to upgrade a hover-open.
3318
+ event != null && useButton.isClickLikeEvent(event)) {
3319
+ this.context.dataRef.current.openEvent = newOpen ? event : undefined;
3320
+ }
3321
+ };
3322
+
3323
+ /**
3324
+ * Runs the root-owned side effects for an open state change.
3325
+ */
3326
+ dispatchOpenChange = (newOpen, eventDetails) => {
3327
+ this.syncOpenEvent(newOpen, eventDetails.event);
3328
+ const details = {
3329
+ open: newOpen,
3330
+ reason: eventDetails.reason,
3331
+ nativeEvent: eventDetails.event,
3332
+ nested: this.context.nested,
3333
+ triggerElement: eventDetails.trigger
3334
+ };
3335
+ this.context.events.emit('openchange', details);
3336
+ };
3337
+
3563
3338
  /**
3564
3339
  * Emits the `openchange` event through the internal event emitter and calls the `onOpenChange` handler with the provided arguments.
3565
3340
  *
@@ -3567,26 +3342,385 @@ class FloatingRootStore extends ReactStore {
3567
3342
  * @param eventDetails Details about the event that triggered the open state change.
3568
3343
  */
3569
3344
  setOpen = (newOpen, eventDetails) => {
3570
- if (!newOpen || !this.state.open ||
3571
- // Prevent a pending hover-open from overwriting a click-open event, while allowing
3572
- // click events to upgrade a hover-open.
3573
- useButton.isClickLikeEvent(eventDetails.event)) {
3574
- this.context.dataRef.current.openEvent = newOpen ? eventDetails.event : undefined;
3575
- }
3576
- if (!this.context.noEmit) {
3577
- const details = {
3578
- open: newOpen,
3579
- reason: eventDetails.reason,
3580
- nativeEvent: eventDetails.event,
3581
- nested: this.context.nested,
3582
- triggerElement: eventDetails.trigger
3583
- };
3584
- this.context.events.emit('openchange', details);
3345
+ if (this.syncOnly) {
3346
+ this.context.onOpenChange?.(newOpen, eventDetails);
3347
+ return;
3585
3348
  }
3349
+ this.dispatchOpenChange(newOpen, eventDetails);
3586
3350
  this.context.onOpenChange?.(newOpen, eventDetails);
3587
3351
  };
3588
3352
  }
3589
3353
 
3354
+ /**
3355
+ * Keeps a FloatingRootStore in sync with the provided PopupStore.
3356
+ * Uses the provided FloatingRootStore when one exists, otherwise creates one once and updates it on every render.
3357
+ */
3358
+ function useSyncedFloatingRootContext(options) {
3359
+ const {
3360
+ popupStore,
3361
+ treatPopupAsFloatingElement = false,
3362
+ floatingRootContext: floatingRootContextProp,
3363
+ floatingId,
3364
+ nested,
3365
+ onOpenChange
3366
+ } = options;
3367
+ const open = popupStore.useState('open');
3368
+ const referenceElement = popupStore.useState('activeTriggerElement');
3369
+ const floatingElement = popupStore.useState(treatPopupAsFloatingElement ? 'popupElement' : 'positionerElement');
3370
+ const triggerElements = popupStore.context.triggerElements;
3371
+ const handleOpenChange = onOpenChange;
3372
+ const internalStoreRef = React__namespace.useRef(null);
3373
+ if (floatingRootContextProp === undefined && internalStoreRef.current === null) {
3374
+ internalStoreRef.current = new FloatingRootStore({
3375
+ open,
3376
+ transitionStatus: undefined,
3377
+ referenceElement,
3378
+ floatingElement,
3379
+ triggerElements,
3380
+ onOpenChange: handleOpenChange,
3381
+ floatingId,
3382
+ syncOnly: true,
3383
+ nested
3384
+ });
3385
+ }
3386
+ const store = floatingRootContextProp ?? internalStoreRef.current;
3387
+ popupStore.useSyncedValue('floatingId', floatingId);
3388
+ useButton.useIsoLayoutEffect(() => {
3389
+ const valuesToSync = {
3390
+ open,
3391
+ floatingId,
3392
+ referenceElement,
3393
+ floatingElement
3394
+ };
3395
+ if (floatingUi_utils_dom.isElement(referenceElement)) {
3396
+ valuesToSync.domReferenceElement = referenceElement;
3397
+ }
3398
+ if (store.state.positionReference === store.state.referenceElement) {
3399
+ valuesToSync.positionReference = referenceElement;
3400
+ }
3401
+ store.update(valuesToSync);
3402
+ }, [open, floatingId, referenceElement, floatingElement, store]);
3403
+
3404
+ // Keep non-reactive context values fresh for interactions that call `store.setOpen`.
3405
+ store.context.onOpenChange = handleOpenChange;
3406
+ store.context.nested = nested;
3407
+ return store;
3408
+ }
3409
+
3410
+ const FOCUSABLE_POPUP_PROPS = {
3411
+ tabIndex: -1,
3412
+ [FOCUSABLE_ATTRIBUTE]: ''
3413
+ };
3414
+
3415
+ /**
3416
+ * Returns the default `initialFocus` resolver for a popup. When opened by touch it focuses the
3417
+ * popup element itself to prevent the virtual keyboard from opening (required for Android
3418
+ * specifically; iOS handles this automatically). Otherwise it falls back to the default behavior.
3419
+ */
3420
+ function createDefaultInitialFocus(popupRef) {
3421
+ return interactionType => interactionType === 'touch' ? popupRef.current : true;
3422
+ }
3423
+ function usePopupStore(externalStore, createStore, treatPopupAsFloatingElement = false) {
3424
+ const floatingId = useButton.useId();
3425
+ const nested = useFloatingParentNodeId() != null;
3426
+ const internalStoreRef = React__namespace.useRef(null);
3427
+ if (externalStore === undefined && internalStoreRef.current === null) {
3428
+ internalStoreRef.current = createStore(floatingId, nested);
3429
+ }
3430
+ const store = externalStore ?? internalStoreRef.current;
3431
+ useSyncedFloatingRootContext({
3432
+ popupStore: store,
3433
+ treatPopupAsFloatingElement,
3434
+ floatingRootContext: store.state.floatingRootContext,
3435
+ floatingId,
3436
+ nested,
3437
+ onOpenChange: store.setOpen
3438
+ });
3439
+ return {
3440
+ store,
3441
+ internalStore: internalStoreRef.current
3442
+ };
3443
+ }
3444
+
3445
+ /**
3446
+ * Returns a callback ref that registers/unregisters the trigger element in the store.
3447
+ *
3448
+ * @param store The Store instance where the trigger should be registered.
3449
+ */
3450
+ function useTriggerRegistration(id, store) {
3451
+ // Keep track of the currently registered element to unregister it on unmount or id change.
3452
+ const registeredElementIdRef = React__namespace.useRef(null);
3453
+ const registeredElementRef = React__namespace.useRef(null);
3454
+ return React__namespace.useCallback(element => {
3455
+ if (id === undefined) {
3456
+ return;
3457
+ }
3458
+ let shouldSyncTriggerCount = false;
3459
+ if (registeredElementIdRef.current !== null) {
3460
+ const registeredId = registeredElementIdRef.current;
3461
+ const registeredElement = registeredElementRef.current;
3462
+ const currentElement = store.context.triggerElements.getById(registeredId);
3463
+ if (registeredElement && currentElement === registeredElement) {
3464
+ store.context.triggerElements.delete(registeredId);
3465
+ shouldSyncTriggerCount = true;
3466
+ }
3467
+ registeredElementIdRef.current = null;
3468
+ registeredElementRef.current = null;
3469
+ }
3470
+ if (element !== null) {
3471
+ registeredElementIdRef.current = id;
3472
+ registeredElementRef.current = element;
3473
+ store.context.triggerElements.add(id, element);
3474
+ shouldSyncTriggerCount = true;
3475
+ }
3476
+ if (shouldSyncTriggerCount) {
3477
+ const triggerCount = store.context.triggerElements.size;
3478
+ if (store.select('open') && store.state.triggerCount !== triggerCount) {
3479
+ store.set('triggerCount', triggerCount);
3480
+ }
3481
+ }
3482
+ }, [store, id]);
3483
+ }
3484
+ function setPopupOpenState(state, open, trigger, preventUnmountOnClose = false) {
3485
+ if (open) {
3486
+ // Opening starts a new close cycle, so clear any previous request to keep the popup mounted.
3487
+ state.preventUnmountingOnClose = false;
3488
+ } else if (preventUnmountOnClose) {
3489
+ state.preventUnmountingOnClose = true;
3490
+ }
3491
+ const triggerId = trigger?.id ?? null;
3492
+
3493
+ // If a popup is closing, the `trigger` may be undefined.
3494
+ // We want to keep the previous value so that exit animations are played and focus is returned correctly.
3495
+ if (triggerId || open) {
3496
+ state.activeTriggerId = triggerId;
3497
+ state.activeTriggerElement = trigger ?? null;
3498
+ }
3499
+ }
3500
+ function attachPreventUnmountOnClose(eventDetails) {
3501
+ let preventUnmountOnClose = false;
3502
+ eventDetails.preventUnmountOnClose = () => {
3503
+ preventUnmountOnClose = true;
3504
+ };
3505
+ return () => preventUnmountOnClose;
3506
+ }
3507
+ function useInitialOpenSync(store, openProp, defaultOpen, defaultTriggerId) {
3508
+ useOnFirstRender(() => {
3509
+ if (openProp === undefined && store.state.open === false && defaultOpen) {
3510
+ // Avoid notifying detached trigger subscribers while the Root is rendering.
3511
+ store.state = {
3512
+ ...store.state,
3513
+ open: true,
3514
+ activeTriggerId: defaultTriggerId,
3515
+ preventUnmountingOnClose: false
3516
+ };
3517
+ }
3518
+ });
3519
+ }
3520
+
3521
+ /**
3522
+ * Sets up trigger data forwarding to the store.
3523
+ *
3524
+ * @param triggerId Id of the trigger.
3525
+ * @param triggerElementRef Ref for the trigger DOM element.
3526
+ * @param store The Store instance managing the popup state.
3527
+ * @param stateUpdates An object with state updates to apply when the trigger is active.
3528
+ */
3529
+ function useTriggerDataForwarding(triggerId, triggerElementRef, store, stateUpdates) {
3530
+ const isMountedByThisTrigger = store.useState('isMountedByTrigger', triggerId);
3531
+ const baseRegisterTrigger = useTriggerRegistration(triggerId, store);
3532
+ const registerTrigger = useButton.useStableCallback(element => {
3533
+ baseRegisterTrigger(element);
3534
+ if (!element) {
3535
+ return;
3536
+ }
3537
+ const open = store.select('open');
3538
+ const activeTriggerId = store.select('activeTriggerId');
3539
+ if (activeTriggerId === triggerId) {
3540
+ store.update({
3541
+ activeTriggerElement: element,
3542
+ ...(open ? stateUpdates : null)
3543
+ });
3544
+ return;
3545
+ }
3546
+ if (activeTriggerId == null && open) {
3547
+ // If a popup is already open, a detached trigger can mount before any active trigger
3548
+ // has been established. Claim the first registered trigger so trigger-owned focus
3549
+ // management and ARIA relationships work.
3550
+ store.update({
3551
+ activeTriggerId: triggerId,
3552
+ activeTriggerElement: element,
3553
+ ...stateUpdates
3554
+ });
3555
+ }
3556
+ });
3557
+ useButton.useIsoLayoutEffect(() => {
3558
+ if (isMountedByThisTrigger) {
3559
+ store.update({
3560
+ activeTriggerElement: triggerElementRef.current,
3561
+ ...stateUpdates
3562
+ });
3563
+ }
3564
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3565
+ }, [isMountedByThisTrigger, store, triggerElementRef, ...Object.values(stateUpdates)]);
3566
+ return {
3567
+ registerTrigger,
3568
+ isMountedByThisTrigger
3569
+ };
3570
+ }
3571
+ /**
3572
+ * Keeps trigger registration state synchronized while the popup is open.
3573
+ *
3574
+ * When a popup opens without an explicit trigger id and exactly one trigger is registered, that
3575
+ * trigger is claimed as the active trigger. When the active trigger id is still registered but its
3576
+ * element changed, the active element is refreshed. When the active trigger unregisters, the
3577
+ * default path preserves existing ownership so non-closing popup families do not silently claim a
3578
+ * different trigger while staying open.
3579
+ *
3580
+ * If `closeOnActiveTriggerUnmount` is enabled, unregistering the active trigger requests a close
3581
+ * after a microtask so a same-tick replacement trigger with the same id can register first.
3582
+ *
3583
+ * This should be called on the Root part.
3584
+ *
3585
+ * @param store The Store instance managing the popup state.
3586
+ * @param options Options for active trigger unmount behavior.
3587
+ */
3588
+ function useImplicitActiveTrigger(store, options = {}) {
3589
+ const {
3590
+ closeOnActiveTriggerUnmount = false
3591
+ } = options;
3592
+ const open = store.useState('open');
3593
+ const reactiveTriggerCount = store.useState('triggerCount');
3594
+ useButton.useIsoLayoutEffect(() => {
3595
+ if (!open) {
3596
+ if (store.state.triggerCount !== 0) {
3597
+ store.set('triggerCount', 0);
3598
+ }
3599
+ return;
3600
+ }
3601
+ const triggerCount = store.context.triggerElements.size;
3602
+ const stateUpdates = {};
3603
+ if (store.state.triggerCount !== triggerCount) {
3604
+ stateUpdates.triggerCount = triggerCount;
3605
+ }
3606
+ const activeTriggerId = store.select('activeTriggerId');
3607
+ let lostActiveTriggerId = null;
3608
+ if (activeTriggerId) {
3609
+ const activeTriggerElement = store.context.triggerElements.getById(activeTriggerId);
3610
+ if (!activeTriggerElement) {
3611
+ lostActiveTriggerId = activeTriggerId;
3612
+ } else if (activeTriggerElement !== store.state.activeTriggerElement) {
3613
+ stateUpdates.activeTriggerElement = activeTriggerElement;
3614
+ }
3615
+ }
3616
+ if (!lostActiveTriggerId && !activeTriggerId && triggerCount === 1) {
3617
+ const iteratorResult = store.context.triggerElements.entries().next();
3618
+ if (!iteratorResult.done) {
3619
+ const [implicitTriggerId, implicitTriggerElement] = iteratorResult.value;
3620
+ stateUpdates.activeTriggerId = implicitTriggerId;
3621
+ stateUpdates.activeTriggerElement = implicitTriggerElement;
3622
+ }
3623
+ }
3624
+ if (stateUpdates.triggerCount !== undefined || stateUpdates.activeTriggerId !== undefined || stateUpdates.activeTriggerElement !== undefined) {
3625
+ store.update(stateUpdates);
3626
+ }
3627
+ if (lostActiveTriggerId) {
3628
+ if (closeOnActiveTriggerUnmount) {
3629
+ // Defer so a same-tick replacement trigger with the same id can register first.
3630
+ queueMicrotask(() => {
3631
+ if (store.select('open') && store.select('activeTriggerId') === lostActiveTriggerId && !store.context.triggerElements.getById(lostActiveTriggerId)) {
3632
+ const eventDetails = useButton.createChangeEventDetails(useButton.none);
3633
+ store.setOpen(false, eventDetails);
3634
+ // If closing is canceled, keep the previous active trigger ownership for the
3635
+ // still-open popup instead of claiming another trigger implicitly.
3636
+ if (!eventDetails.isCanceled) {
3637
+ store.update({
3638
+ activeTriggerId: null,
3639
+ activeTriggerElement: null
3640
+ });
3641
+ }
3642
+ }
3643
+ });
3644
+ }
3645
+ }
3646
+ }, [open, store, reactiveTriggerCount, closeOnActiveTriggerUnmount]);
3647
+ }
3648
+
3649
+ /**
3650
+ * Manages the mounted state of the popup.
3651
+ * Sets up the transition status listeners and handles unmounting when needed.
3652
+ * Updates the `mounted`, `transitionStatus`, and `preventUnmountingOnClose` states in the store.
3653
+ *
3654
+ * @param open Whether the popup is open.
3655
+ * @param store The Store instance managing the popup state.
3656
+ * @param onUnmount Optional callback to be called when the popup is unmounted.
3657
+ *
3658
+ * @returns A function to forcibly unmount the popup.
3659
+ */
3660
+ function useOpenStateTransitions(open, store, onUnmount) {
3661
+ const {
3662
+ mounted,
3663
+ setMounted,
3664
+ transitionStatus
3665
+ } = useButton.useTransitionStatus(open);
3666
+ const preventUnmountingOnClose = store.useState('preventUnmountingOnClose');
3667
+ // Opening starts a new close cycle. Clear during render so the close-completion hook below
3668
+ // reads the synchronized value on the same pass.
3669
+ const syncedPreventUnmountingOnClose = open ? false : preventUnmountingOnClose;
3670
+ store.useSyncedValues({
3671
+ mounted,
3672
+ transitionStatus,
3673
+ preventUnmountingOnClose: syncedPreventUnmountingOnClose
3674
+ });
3675
+ const forceUnmount = useButton.useStableCallback(() => {
3676
+ setMounted(false);
3677
+ store.update({
3678
+ activeTriggerId: null,
3679
+ activeTriggerElement: null,
3680
+ mounted: false,
3681
+ preventUnmountingOnClose: false
3682
+ });
3683
+ onUnmount?.();
3684
+ store.context.onOpenChangeComplete?.(false);
3685
+ });
3686
+ useButton.useOpenChangeComplete({
3687
+ enabled: mounted && !open && !syncedPreventUnmountingOnClose,
3688
+ open,
3689
+ ref: store.context.popupRef,
3690
+ onComplete() {
3691
+ if (!open) {
3692
+ forceUnmount();
3693
+ }
3694
+ }
3695
+ });
3696
+ return {
3697
+ forceUnmount,
3698
+ transitionStatus
3699
+ };
3700
+ }
3701
+ function usePopupInteractionProps(store, statePart) {
3702
+ store.useSyncedValues(statePart);
3703
+ useButton.useIsoLayoutEffect(() => () => {
3704
+ store.update({
3705
+ activeTriggerProps: useRenderElement.EMPTY_OBJECT,
3706
+ inactiveTriggerProps: useRenderElement.EMPTY_OBJECT,
3707
+ popupProps: useRenderElement.EMPTY_OBJECT
3708
+ });
3709
+ }, [store]);
3710
+ }
3711
+ function usePopupRootSync(store, open) {
3712
+ useButton.useIsoLayoutEffect(() => {
3713
+ if (!open && store.state.openMethod !== null) {
3714
+ store.set('openMethod', null);
3715
+ }
3716
+ }, [open, store]);
3717
+ useButton.useIsoLayoutEffect(() => () => {
3718
+ if (store.state.openMethod !== null) {
3719
+ store.set('openMethod', null);
3720
+ }
3721
+ }, [store]);
3722
+ }
3723
+
3590
3724
  /**
3591
3725
  * Data structure to keep track of popup trigger elements by their IDs.
3592
3726
  * Uses both a set of Elements and a map of IDs to Elements for efficient lookups.
@@ -3616,6 +3750,8 @@ class PopupTriggerMap {
3616
3750
  this.idMap.set(id, element);
3617
3751
  if (process.env.NODE_ENV !== 'production') {
3618
3752
  if (this.elementsSet.size !== this.idMap.size) {
3753
+ // TODO: fix mui/no-guarded-throw
3754
+ // eslint-disable-next-line mui/no-guarded-throw
3619
3755
  throw new Error('Base UI: A trigger element cannot be registered under multiple IDs in PopupTriggerMap.');
3620
3756
  }
3621
3757
  }
@@ -3680,93 +3816,6 @@ class PopupTriggerMap {
3680
3816
  }
3681
3817
  }
3682
3818
 
3683
- /**
3684
- * Merges an array of interaction hooks' props into prop getters, allowing
3685
- * event handler functions to be composed together without overwriting one
3686
- * another.
3687
- * @see https://floating-ui.com/docs/useInteractions
3688
- */
3689
- function useInteractions(propsList = []) {
3690
- const referenceDeps = propsList.map(key => key?.reference);
3691
- const floatingDeps = propsList.map(key => key?.floating);
3692
- const itemDeps = propsList.map(key => key?.item);
3693
- const triggerDeps = propsList.map(key => key?.trigger);
3694
- const getReferenceProps = React__namespace.useCallback(userProps => mergeProps(userProps, propsList, 'reference'),
3695
- // eslint-disable-next-line react-hooks/exhaustive-deps
3696
- referenceDeps);
3697
- const getFloatingProps = React__namespace.useCallback(userProps => mergeProps(userProps, propsList, 'floating'),
3698
- // eslint-disable-next-line react-hooks/exhaustive-deps
3699
- floatingDeps);
3700
- const getItemProps = React__namespace.useCallback(userProps => mergeProps(userProps, propsList, 'item'),
3701
- // eslint-disable-next-line react-hooks/exhaustive-deps
3702
- itemDeps);
3703
- const getTriggerProps = React__namespace.useCallback(userProps => mergeProps(userProps, propsList, 'trigger'),
3704
- // eslint-disable-next-line react-hooks/exhaustive-deps
3705
- triggerDeps);
3706
- return React__namespace.useMemo(() => ({
3707
- getReferenceProps,
3708
- getFloatingProps,
3709
- getItemProps,
3710
- getTriggerProps
3711
- }), [getReferenceProps, getFloatingProps, getItemProps, getTriggerProps]);
3712
- }
3713
-
3714
- /* eslint-disable guard-for-in */
3715
-
3716
- function mergeProps(userProps, propsList, elementKey) {
3717
- const eventHandlers = new Map();
3718
- const isItem = elementKey === 'item';
3719
- const outputProps = {};
3720
- if (elementKey === 'floating') {
3721
- outputProps.tabIndex = -1;
3722
- outputProps[useButton.FOCUSABLE_ATTRIBUTE] = '';
3723
- }
3724
- for (const key in userProps) {
3725
- if (isItem && userProps) {
3726
- if (key === useButton.ACTIVE_KEY || key === useButton.SELECTED_KEY) {
3727
- continue;
3728
- }
3729
- }
3730
- outputProps[key] = userProps[key];
3731
- }
3732
- for (let i = 0; i < propsList.length; i += 1) {
3733
- let props;
3734
- const propsOrGetProps = propsList[i]?.[elementKey];
3735
- if (typeof propsOrGetProps === 'function') {
3736
- props = userProps ? propsOrGetProps(userProps) : null;
3737
- } else {
3738
- props = propsOrGetProps;
3739
- }
3740
- if (!props) {
3741
- continue;
3742
- }
3743
- mutablyMergeProps(outputProps, props, isItem, eventHandlers);
3744
- }
3745
- mutablyMergeProps(outputProps, userProps, isItem, eventHandlers);
3746
- return outputProps;
3747
- }
3748
- function mutablyMergeProps(outputProps, props, isItem, eventHandlers) {
3749
- for (const key in props) {
3750
- const value = props[key];
3751
- if (isItem && (key === useButton.ACTIVE_KEY || key === useButton.SELECTED_KEY)) {
3752
- continue;
3753
- }
3754
- if (!key.startsWith('on')) {
3755
- outputProps[key] = value;
3756
- } else {
3757
- if (!eventHandlers.has(key)) {
3758
- eventHandlers.set(key, []);
3759
- }
3760
- if (typeof value === 'function') {
3761
- eventHandlers.get(key)?.push(value);
3762
- outputProps[key] = (...args) => {
3763
- return eventHandlers.get(key)?.map(fn => fn(...args)).find(val => val !== undefined);
3764
- };
3765
- }
3766
- }
3767
- }
3768
- }
3769
-
3770
3819
  /**
3771
3820
  * Provides a cross-browser way to determine the type of the pointer used to click.
3772
3821
  * Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.
@@ -3803,38 +3852,54 @@ function useEnhancedClickHandler(handler) {
3803
3852
  };
3804
3853
  }
3805
3854
 
3806
- /**
3807
- * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.
3808
- *
3809
- * @param open The open state of the component.
3810
- */
3811
- function useOpenInteractionType(open) {
3812
- const [openMethod, setOpenMethod] = React__namespace.useState(null);
3855
+ function useOpenMethodTriggerProps(open, setOpenMethod) {
3813
3856
  const handleTriggerClick = useButton.useStableCallback((_, interactionType) => {
3814
- if (!open) {
3857
+ const isOpen = typeof open === 'function' ? open() : open;
3858
+ if (!isOpen) {
3815
3859
  setOpenMethod(interactionType || (
3816
3860
  // On iOS Safari, the hitslop around touch targets means tapping outside an element's
3817
3861
  // bounds does not fire `pointerdown` but does fire `mousedown`. The `interactionType`
3818
3862
  // will be "" in that case.
3819
- useButton.isIOS ? 'touch' : ''));
3863
+ useButton.ios ? 'touch' : ''));
3820
3864
  }
3821
3865
  });
3866
+ const {
3867
+ onClick,
3868
+ onPointerDown
3869
+ } = useEnhancedClickHandler(handleTriggerClick);
3870
+ return React__namespace.useMemo(() => ({
3871
+ onClick,
3872
+ onPointerDown
3873
+ }), [onClick, onPointerDown]);
3874
+ }
3875
+
3876
+ /**
3877
+ * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.
3878
+ *
3879
+ * @param open The open state of the component.
3880
+ */
3881
+ function useOpenInteractionType(open) {
3882
+ const [openMethod, setOpenMethod] = React__namespace.useState(null);
3883
+ const triggerProps = useOpenMethodTriggerProps(open, setOpenMethod);
3822
3884
  useButton.useValueChanged(open, previousOpen => {
3823
3885
  if (previousOpen && !open) {
3824
3886
  setOpenMethod(null);
3825
3887
  }
3826
3888
  });
3827
- const {
3828
- onClick,
3829
- onPointerDown
3830
- } = useEnhancedClickHandler(handleTriggerClick);
3831
3889
  return React__namespace.useMemo(() => ({
3832
3890
  openMethod,
3833
- triggerProps: {
3834
- onClick,
3835
- onPointerDown
3836
- }
3837
- }), [openMethod, onClick, onPointerDown]);
3891
+ triggerProps
3892
+ }), [openMethod, triggerProps]);
3893
+ }
3894
+
3895
+ /**
3896
+ * @internal
3897
+ */
3898
+ const DirectionContext = /*#__PURE__*/React__namespace.createContext(undefined);
3899
+ if (process.env.NODE_ENV !== "production") DirectionContext.displayName = "DirectionContext";
3900
+ function useDirection() {
3901
+ const context = React__namespace.useContext(DirectionContext);
3902
+ return context?.direction ?? 'ltr';
3838
3903
  }
3839
3904
 
3840
3905
  let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
@@ -3928,16 +3993,6 @@ const popupStateMapping = {
3928
3993
  }
3929
3994
  };
3930
3995
 
3931
- /**
3932
- * @internal
3933
- */
3934
- const DirectionContext = /*#__PURE__*/React__namespace.createContext(undefined);
3935
- if (process.env.NODE_ENV !== "production") DirectionContext.displayName = "DirectionContext";
3936
- function useDirection() {
3937
- const context = React__namespace.useContext(DirectionContext);
3938
- return context?.direction ?? 'ltr';
3939
- }
3940
-
3941
3996
  function inertValue(value) {
3942
3997
  if (useRenderElement.isReactVersionAtLeast(19)) {
3943
3998
  return value;
@@ -3946,6 +4001,35 @@ function inertValue(value) {
3946
4001
  return value ? 'true' : undefined;
3947
4002
  }
3948
4003
 
4004
+ const InternalBackdrop = /*#__PURE__*/React__namespace.forwardRef(function InternalBackdrop(props, ref) {
4005
+ const {
4006
+ cutout,
4007
+ ...otherProps
4008
+ } = props;
4009
+ let clipPath;
4010
+ if (cutout) {
4011
+ const rect = cutout.getBoundingClientRect();
4012
+ 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)`;
4013
+ }
4014
+ return /*#__PURE__*/jsxRuntime.jsx("div", {
4015
+ ref: ref,
4016
+ role: "presentation"
4017
+ // Ensures Floating UI's outside press detection runs, as it considers
4018
+ // it an element that existed when the popup rendered.
4019
+ ,
4020
+ "data-base-ui-inert": "",
4021
+ ...otherProps,
4022
+ style: {
4023
+ position: 'fixed',
4024
+ inset: 0,
4025
+ userSelect: 'none',
4026
+ WebkitUserSelect: 'none',
4027
+ clipPath
4028
+ }
4029
+ });
4030
+ });
4031
+ if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "InternalBackdrop";
4032
+
3949
4033
  let originalHtmlStyles = {};
3950
4034
  let originalBodyStyles = {};
3951
4035
  let originalHtmlScrollBehavior = '';
@@ -4010,7 +4094,7 @@ function preventScrollInsetScrollbars(referenceElement) {
4010
4094
  const resizeFrame = useButton.AnimationFrame.create();
4011
4095
 
4012
4096
  // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom.
4013
- if (useButton.isWebKit && (win.visualViewport?.scale ?? 1) !== 1) {
4097
+ if (webkit && (win.visualViewport?.scale ?? 1) !== 1) {
4014
4098
  return () => {};
4015
4099
  }
4016
4100
  function lockScroll() {
@@ -4104,16 +4188,15 @@ function preventScrollInsetScrollbars(referenceElement) {
4104
4188
  resizeFrame.request(lockScroll);
4105
4189
  }
4106
4190
  lockScroll();
4107
- win.addEventListener('resize', handleResize);
4191
+ const unsubscribeResize = useButton.addEventListener(win, 'resize', handleResize);
4108
4192
  return () => {
4109
4193
  resizeFrame.cancel();
4110
4194
  cleanup();
4111
- // Sometimes this cleanup can be run after test teardown
4112
- // because it is called in a `setTimeout(fn, 0)`,
4113
- // in which case `removeEventListener` wouldn't be available,
4114
- // so we check for it to avoid test failures.
4195
+ // Sometimes this cleanup can run after test teardown because it is called
4196
+ // in a `setTimeout(fn, 0)`. Guard the returned cleanup to avoid calling
4197
+ // `removeEventListener` when it is no longer available in tests.
4115
4198
  if (typeof win.removeEventListener === 'function') {
4116
- win.removeEventListener('resize', handleResize);
4199
+ unsubscribeResize();
4117
4200
  }
4118
4201
  };
4119
4202
  }
@@ -4154,7 +4237,7 @@ class ScrollLocker {
4154
4237
  this.restore = useRenderElement.NOOP;
4155
4238
  return;
4156
4239
  }
4157
- const hasOverlayScrollbars = useButton.isIOS || !hasInsetScrollbars(referenceElement);
4240
+ const hasOverlayScrollbars = useButton.ios || !hasInsetScrollbars(referenceElement);
4158
4241
 
4159
4242
  // On iOS, scroll locking does not work if the navbar is collapsed. Due to numerous
4160
4243
  // side effects and bugs that arise on iOS, it must be researched extensively before
@@ -4182,51 +4265,16 @@ function useScrollLock(enabled = true, referenceElement = null) {
4182
4265
  }, [enabled, referenceElement]);
4183
4266
  }
4184
4267
 
4185
- const InternalBackdrop = /*#__PURE__*/React__namespace.forwardRef(function InternalBackdrop(props, ref) {
4186
- const {
4187
- cutout,
4188
- ...otherProps
4189
- } = props;
4190
- let clipPath;
4191
- if (cutout) {
4192
- const rect = cutout?.getBoundingClientRect();
4193
- clipPath = `polygon(
4194
- 0% 0%,
4195
- 100% 0%,
4196
- 100% 100%,
4197
- 0% 100%,
4198
- 0% 0%,
4199
- ${rect.left}px ${rect.top}px,
4200
- ${rect.left}px ${rect.bottom}px,
4201
- ${rect.right}px ${rect.bottom}px,
4202
- ${rect.right}px ${rect.top}px,
4203
- ${rect.left}px ${rect.top}px
4204
- )`;
4205
- }
4206
- return /*#__PURE__*/jsxRuntime.jsx("div", {
4207
- ref: ref,
4208
- role: "presentation"
4209
- // Ensures Floating UI's outside press detection runs, as it considers
4210
- // it an element that existed when the popup rendered.
4211
- ,
4212
- "data-base-ui-inert": "",
4213
- ...otherProps,
4214
- style: {
4215
- position: 'fixed',
4216
- inset: 0,
4217
- userSelect: 'none',
4218
- WebkitUserSelect: 'none',
4219
- clipPath
4220
- }
4221
- });
4222
- });
4223
- if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "InternalBackdrop";
4224
-
4268
+ exports.ARROW_DOWN = ARROW_DOWN;
4269
+ exports.ARROW_LEFT = ARROW_LEFT;
4270
+ exports.ARROW_RIGHT = ARROW_RIGHT;
4271
+ exports.ARROW_UP = ARROW_UP;
4225
4272
  exports.BASE_UI_SWIPE_IGNORE_SELECTOR = BASE_UI_SWIPE_IGNORE_SELECTOR;
4226
4273
  exports.CLICK_TRIGGER_IDENTIFIER = CLICK_TRIGGER_IDENTIFIER;
4227
4274
  exports.CommonPopupDataAttributes = CommonPopupDataAttributes;
4228
4275
  exports.DISABLED_TRANSITIONS_STYLE = DISABLED_TRANSITIONS_STYLE;
4229
4276
  exports.DROPDOWN_COLLISION_AVOIDANCE = DROPDOWN_COLLISION_AVOIDANCE;
4277
+ exports.FOCUSABLE_POPUP_PROPS = FOCUSABLE_POPUP_PROPS;
4230
4278
  exports.FloatingFocusManager = FloatingFocusManager;
4231
4279
  exports.FloatingNode = FloatingNode;
4232
4280
  exports.FloatingPortal = FloatingPortal;
@@ -4241,15 +4289,16 @@ exports.PopupTriggerMap = PopupTriggerMap;
4241
4289
  exports.ReactStore = ReactStore;
4242
4290
  exports.Store = Store;
4243
4291
  exports.TYPEAHEAD_RESET_MS = TYPEAHEAD_RESET_MS;
4292
+ exports.attachPreventUnmountOnClose = attachPreventUnmountOnClose;
4244
4293
  exports.createAttribute = createAttribute;
4245
- exports.createGridCellMap = createGridCellMap;
4294
+ exports.createDefaultInitialFocus = createDefaultInitialFocus;
4246
4295
  exports.createSelector = createSelector;
4247
4296
  exports.enqueueFocus = enqueueFocus;
4248
4297
  exports.fastComponent = fastComponent;
4249
4298
  exports.fastComponentRef = fastComponentRef;
4250
4299
  exports.findNonDisabledListIndex = findNonDisabledListIndex;
4251
- exports.getGridCellIndexOfCorner = getGridCellIndexOfCorner;
4252
- exports.getGridCellIndices = getGridCellIndices;
4300
+ exports.gecko = gecko;
4301
+ exports.getFloatingFocusElement = getFloatingFocusElement;
4253
4302
  exports.getGridNavigatedIndex = getGridNavigatedIndex;
4254
4303
  exports.getMaxListIndex = getMaxListIndex;
4255
4304
  exports.getMinListIndex = getMinListIndex;
@@ -4260,10 +4309,17 @@ exports.getTabbableBeforeElement = getTabbableBeforeElement;
4260
4309
  exports.inertValue = inertValue;
4261
4310
  exports.isElementVisible = isElementVisible;
4262
4311
  exports.isIndexOutOfListBounds = isIndexOutOfListBounds;
4312
+ exports.isInteractiveElement = isInteractiveElement;
4263
4313
  exports.isListIndexDisabled = isListIndexDisabled;
4264
4314
  exports.isOutsideEvent = isOutsideEvent;
4315
+ exports.isTargetInsideEnabledTrigger = isTargetInsideEnabledTrigger;
4316
+ exports.isTypeableCombobox = isTypeableCombobox;
4317
+ exports.isTypeableElement = isTypeableElement;
4318
+ exports.matchesFocusVisible = matchesFocusVisible;
4319
+ exports.mergeCleanups = mergeCleanups;
4265
4320
  exports.popupStateMapping = popupStateMapping;
4266
4321
  exports.pressableTriggerOpenStateMapping = pressableTriggerOpenStateMapping;
4322
+ exports.setPopupOpenState = setPopupOpenState;
4267
4323
  exports.triggerOpenStateMapping = triggerOpenStateMapping;
4268
4324
  exports.useClick = useClick;
4269
4325
  exports.useDirection = useDirection;
@@ -4271,8 +4327,18 @@ exports.useDismiss = useDismiss;
4271
4327
  exports.useFloatingNodeId = useFloatingNodeId;
4272
4328
  exports.useFloatingParentNodeId = useFloatingParentNodeId;
4273
4329
  exports.useFloatingTree = useFloatingTree;
4274
- exports.useInteractions = useInteractions;
4330
+ exports.useImplicitActiveTrigger = useImplicitActiveTrigger;
4331
+ exports.useInitialOpenSync = useInitialOpenSync;
4275
4332
  exports.useOnFirstRender = useOnFirstRender;
4276
4333
  exports.useOpenInteractionType = useOpenInteractionType;
4334
+ exports.useOpenMethodTriggerProps = useOpenMethodTriggerProps;
4335
+ exports.useOpenStateTransitions = useOpenStateTransitions;
4336
+ exports.usePopupInteractionProps = usePopupInteractionProps;
4337
+ exports.usePopupRootSync = usePopupRootSync;
4338
+ exports.usePopupStore = usePopupStore;
4277
4339
  exports.useScrollLock = useScrollLock;
4278
4340
  exports.useStore = useStore;
4341
+ exports.useSyncedFloatingRootContext = useSyncedFloatingRootContext;
4342
+ exports.useTriggerDataForwarding = useTriggerDataForwarding;
4343
+ exports.useTriggerRegistration = useTriggerRegistration;
4344
+ exports.webkit = webkit;