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