@react-aria/utils 3.27.0 → 3.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DOMFunctions.main.js +44 -0
- package/dist/DOMFunctions.main.js.map +1 -0
- package/dist/DOMFunctions.mjs +37 -0
- package/dist/DOMFunctions.module.js +37 -0
- package/dist/DOMFunctions.module.js.map +1 -0
- package/dist/ShadowTreeWalker.main.js +200 -0
- package/dist/ShadowTreeWalker.main.js.map +1 -0
- package/dist/ShadowTreeWalker.mjs +194 -0
- package/dist/ShadowTreeWalker.module.js +194 -0
- package/dist/ShadowTreeWalker.module.js.map +1 -0
- package/dist/constants.main.js +0 -2
- package/dist/constants.main.js.map +1 -1
- package/dist/constants.mjs +1 -2
- package/dist/constants.module.js +1 -2
- package/dist/constants.module.js.map +1 -1
- package/dist/domHelpers.main.js +9 -0
- package/dist/domHelpers.main.js.map +1 -1
- package/dist/domHelpers.mjs +9 -1
- package/dist/domHelpers.module.js +9 -1
- package/dist/domHelpers.module.js.map +1 -1
- package/dist/import.mjs +11 -3
- package/dist/inertValue.main.js +19 -0
- package/dist/inertValue.main.js.map +1 -0
- package/dist/inertValue.mjs +14 -0
- package/dist/inertValue.module.js +14 -0
- package/dist/inertValue.module.js.map +1 -0
- package/dist/isFocusable.main.js +34 -0
- package/dist/isFocusable.main.js.map +1 -0
- package/dist/isFocusable.mjs +28 -0
- package/dist/isFocusable.module.js +28 -0
- package/dist/isFocusable.module.js.map +1 -0
- package/dist/main.js +17 -1
- package/dist/main.js.map +1 -1
- package/dist/mergeProps.main.js.map +1 -1
- package/dist/mergeProps.module.js.map +1 -1
- package/dist/module.js +11 -3
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +50 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useGlobalListeners.main.js.map +1 -1
- package/dist/useGlobalListeners.module.js.map +1 -1
- package/dist/useId.main.js +26 -21
- package/dist/useId.main.js.map +1 -1
- package/dist/useId.mjs +28 -23
- package/dist/useId.module.js +28 -23
- package/dist/useId.module.js.map +1 -1
- package/dist/useUpdateEffect.main.js +2 -1
- package/dist/useUpdateEffect.main.js.map +1 -1
- package/dist/useUpdateEffect.mjs +2 -1
- package/dist/useUpdateEffect.module.js +2 -1
- package/dist/useUpdateEffect.module.js.map +1 -1
- package/package.json +4 -3
- package/src/constants.ts +0 -1
- package/src/domHelpers.ts +19 -0
- package/src/index.ts +6 -2
- package/src/inertValue.ts +11 -0
- package/src/isFocusable.ts +28 -0
- package/src/mergeProps.ts +1 -1
- package/src/shadowdom/DOMFunctions.ts +70 -0
- package/src/shadowdom/ShadowTreeWalker.ts +319 -0
- package/src/useGlobalListeners.ts +1 -0
- package/src/useId.ts +24 -15
- package/src/useUpdateEffect.ts +3 -2
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// https://github.com/microsoft/tabster/blob/a89fc5d7e332d48f68d03b1ca6e344489d1c3898/src/Shadowdomize/ShadowTreeWalker.ts
|
|
2
|
+
|
|
3
|
+
import {nodeContains} from './DOMFunctions';
|
|
4
|
+
import {shadowDOM} from '@react-stately/flags';
|
|
5
|
+
|
|
6
|
+
export class ShadowTreeWalker implements TreeWalker {
|
|
7
|
+
public readonly filter: NodeFilter | null;
|
|
8
|
+
public readonly root: Node;
|
|
9
|
+
public readonly whatToShow: number;
|
|
10
|
+
|
|
11
|
+
private _doc: Document;
|
|
12
|
+
private _walkerStack: Array<TreeWalker> = [];
|
|
13
|
+
private _currentNode: Node;
|
|
14
|
+
private _currentSetFor: Set<TreeWalker> = new Set();
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
doc: Document,
|
|
18
|
+
root: Node,
|
|
19
|
+
whatToShow?: number,
|
|
20
|
+
filter?: NodeFilter | null
|
|
21
|
+
) {
|
|
22
|
+
this._doc = doc;
|
|
23
|
+
this.root = root;
|
|
24
|
+
this.filter = filter ?? null;
|
|
25
|
+
this.whatToShow = whatToShow ?? NodeFilter.SHOW_ALL;
|
|
26
|
+
this._currentNode = root;
|
|
27
|
+
|
|
28
|
+
this._walkerStack.unshift(
|
|
29
|
+
doc.createTreeWalker(root, whatToShow, this._acceptNode)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const shadowRoot = (root as Element).shadowRoot;
|
|
33
|
+
|
|
34
|
+
if (shadowRoot) {
|
|
35
|
+
const walker = this._doc.createTreeWalker(
|
|
36
|
+
shadowRoot,
|
|
37
|
+
this.whatToShow,
|
|
38
|
+
{acceptNode: this._acceptNode}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
this._walkerStack.unshift(walker);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private _acceptNode = (node: Node): number => {
|
|
46
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
47
|
+
const shadowRoot = (node as Element).shadowRoot;
|
|
48
|
+
|
|
49
|
+
if (shadowRoot) {
|
|
50
|
+
const walker = this._doc.createTreeWalker(
|
|
51
|
+
shadowRoot,
|
|
52
|
+
this.whatToShow,
|
|
53
|
+
{acceptNode: this._acceptNode}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
this._walkerStack.unshift(walker);
|
|
57
|
+
|
|
58
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
59
|
+
} else {
|
|
60
|
+
if (typeof this.filter === 'function') {
|
|
61
|
+
return this.filter(node);
|
|
62
|
+
} else if (this.filter?.acceptNode) {
|
|
63
|
+
return this.filter.acceptNode(node);
|
|
64
|
+
} else if (this.filter === null) {
|
|
65
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return NodeFilter.FILTER_SKIP;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
public get currentNode(): Node {
|
|
74
|
+
return this._currentNode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public set currentNode(node: Node) {
|
|
78
|
+
if (!nodeContains(this.root, node)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Cannot set currentNode to a node that is not contained by the root node.'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const walkers: TreeWalker[] = [];
|
|
85
|
+
let curNode: Node | null | undefined = node;
|
|
86
|
+
let currentWalkerCurrentNode = node;
|
|
87
|
+
|
|
88
|
+
this._currentNode = node;
|
|
89
|
+
|
|
90
|
+
while (curNode && curNode !== this.root) {
|
|
91
|
+
if (curNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
92
|
+
const shadowRoot = curNode as ShadowRoot;
|
|
93
|
+
|
|
94
|
+
const walker = this._doc.createTreeWalker(
|
|
95
|
+
shadowRoot,
|
|
96
|
+
this.whatToShow,
|
|
97
|
+
{acceptNode: this._acceptNode}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
walkers.push(walker);
|
|
101
|
+
|
|
102
|
+
walker.currentNode = currentWalkerCurrentNode;
|
|
103
|
+
|
|
104
|
+
this._currentSetFor.add(walker);
|
|
105
|
+
|
|
106
|
+
curNode = currentWalkerCurrentNode = shadowRoot.host;
|
|
107
|
+
} else {
|
|
108
|
+
curNode = curNode.parentNode;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const walker = this._doc.createTreeWalker(
|
|
113
|
+
this.root,
|
|
114
|
+
this.whatToShow,
|
|
115
|
+
{acceptNode: this._acceptNode}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
walkers.push(walker);
|
|
119
|
+
|
|
120
|
+
walker.currentNode = currentWalkerCurrentNode;
|
|
121
|
+
|
|
122
|
+
this._currentSetFor.add(walker);
|
|
123
|
+
|
|
124
|
+
this._walkerStack = walkers;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public get doc(): Document {
|
|
128
|
+
return this._doc;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public firstChild(): Node | null {
|
|
132
|
+
let currentNode = this.currentNode;
|
|
133
|
+
let newNode = this.nextNode();
|
|
134
|
+
if (!nodeContains(currentNode, newNode)) {
|
|
135
|
+
this.currentNode = currentNode;
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (newNode) {
|
|
139
|
+
this.currentNode = newNode;
|
|
140
|
+
}
|
|
141
|
+
return newNode;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public lastChild(): Node | null {
|
|
145
|
+
let walker = this._walkerStack[0];
|
|
146
|
+
let newNode = walker.lastChild();
|
|
147
|
+
if (newNode) {
|
|
148
|
+
this.currentNode = newNode;
|
|
149
|
+
}
|
|
150
|
+
return newNode;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public nextNode(): Node | null {
|
|
154
|
+
const nextNode = this._walkerStack[0].nextNode();
|
|
155
|
+
|
|
156
|
+
if (nextNode) {
|
|
157
|
+
const shadowRoot = (nextNode as Element).shadowRoot;
|
|
158
|
+
|
|
159
|
+
if (shadowRoot) {
|
|
160
|
+
let nodeResult: number | undefined;
|
|
161
|
+
|
|
162
|
+
if (typeof this.filter === 'function') {
|
|
163
|
+
nodeResult = this.filter(nextNode);
|
|
164
|
+
} else if (this.filter?.acceptNode) {
|
|
165
|
+
nodeResult = this.filter.acceptNode(nextNode);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
|
|
169
|
+
this.currentNode = nextNode;
|
|
170
|
+
return nextNode;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// _acceptNode should have added new walker for this shadow,
|
|
174
|
+
// go in recursively.
|
|
175
|
+
let newNode = this.nextNode();
|
|
176
|
+
if (newNode) {
|
|
177
|
+
this.currentNode = newNode;
|
|
178
|
+
}
|
|
179
|
+
return newNode;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (nextNode) {
|
|
183
|
+
this.currentNode = nextNode;
|
|
184
|
+
}
|
|
185
|
+
return nextNode;
|
|
186
|
+
} else {
|
|
187
|
+
if (this._walkerStack.length > 1) {
|
|
188
|
+
this._walkerStack.shift();
|
|
189
|
+
|
|
190
|
+
let newNode = this.nextNode();
|
|
191
|
+
if (newNode) {
|
|
192
|
+
this.currentNode = newNode;
|
|
193
|
+
}
|
|
194
|
+
return newNode;
|
|
195
|
+
} else {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public previousNode(): Node | null {
|
|
202
|
+
const currentWalker = this._walkerStack[0];
|
|
203
|
+
|
|
204
|
+
if (currentWalker.currentNode === currentWalker.root) {
|
|
205
|
+
if (this._currentSetFor.has(currentWalker)) {
|
|
206
|
+
this._currentSetFor.delete(currentWalker);
|
|
207
|
+
|
|
208
|
+
if (this._walkerStack.length > 1) {
|
|
209
|
+
this._walkerStack.shift();
|
|
210
|
+
let newNode = this.previousNode();
|
|
211
|
+
if (newNode) {
|
|
212
|
+
this.currentNode = newNode;
|
|
213
|
+
}
|
|
214
|
+
return newNode;
|
|
215
|
+
} else {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const previousNode = currentWalker.previousNode();
|
|
224
|
+
|
|
225
|
+
if (previousNode) {
|
|
226
|
+
const shadowRoot = (previousNode as Element).shadowRoot;
|
|
227
|
+
|
|
228
|
+
if (shadowRoot) {
|
|
229
|
+
let nodeResult: number | undefined;
|
|
230
|
+
|
|
231
|
+
if (typeof this.filter === 'function') {
|
|
232
|
+
nodeResult = this.filter(previousNode);
|
|
233
|
+
} else if (this.filter?.acceptNode) {
|
|
234
|
+
nodeResult = this.filter.acceptNode(previousNode);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
|
|
238
|
+
if (previousNode) {
|
|
239
|
+
this.currentNode = previousNode;
|
|
240
|
+
}
|
|
241
|
+
return previousNode;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// _acceptNode should have added new walker for this shadow,
|
|
245
|
+
// go in recursively.
|
|
246
|
+
let newNode = this.lastChild();
|
|
247
|
+
if (newNode) {
|
|
248
|
+
this.currentNode = newNode;
|
|
249
|
+
}
|
|
250
|
+
return newNode;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (previousNode) {
|
|
254
|
+
this.currentNode = previousNode;
|
|
255
|
+
}
|
|
256
|
+
return previousNode;
|
|
257
|
+
} else {
|
|
258
|
+
if (this._walkerStack.length > 1) {
|
|
259
|
+
this._walkerStack.shift();
|
|
260
|
+
|
|
261
|
+
let newNode = this.previousNode();
|
|
262
|
+
if (newNode) {
|
|
263
|
+
this.currentNode = newNode;
|
|
264
|
+
}
|
|
265
|
+
return newNode;
|
|
266
|
+
} else {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @deprecated
|
|
274
|
+
*/
|
|
275
|
+
public nextSibling(): Node | null {
|
|
276
|
+
// if (__DEV__) {
|
|
277
|
+
// throw new Error("Method not implemented.");
|
|
278
|
+
// }
|
|
279
|
+
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @deprecated
|
|
285
|
+
*/
|
|
286
|
+
public previousSibling(): Node | null {
|
|
287
|
+
// if (__DEV__) {
|
|
288
|
+
// throw new Error("Method not implemented.");
|
|
289
|
+
// }
|
|
290
|
+
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @deprecated
|
|
296
|
+
*/
|
|
297
|
+
public parentNode(): Node | null {
|
|
298
|
+
// if (__DEV__) {
|
|
299
|
+
// throw new Error("Method not implemented.");
|
|
300
|
+
// }
|
|
301
|
+
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* ShadowDOM safe version of document.createTreeWalker.
|
|
308
|
+
*/
|
|
309
|
+
export function createShadowTreeWalker(
|
|
310
|
+
doc: Document,
|
|
311
|
+
root: Node,
|
|
312
|
+
whatToShow?: number,
|
|
313
|
+
filter?: NodeFilter | null
|
|
314
|
+
) {
|
|
315
|
+
if (shadowDOM()) {
|
|
316
|
+
return new ShadowTreeWalker(doc, root, whatToShow, filter);
|
|
317
|
+
}
|
|
318
|
+
return doc.createTreeWalker(root, whatToShow, filter);
|
|
319
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import {useCallback, useEffect, useRef} from 'react';
|
|
14
14
|
|
|
15
15
|
interface GlobalListeners {
|
|
16
|
+
addGlobalListener<K extends keyof WindowEventMap>(el: Window, type: K, listener: (this: Document, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void,
|
|
16
17
|
addGlobalListener<K extends keyof DocumentEventMap>(el: EventTarget, type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void,
|
|
17
18
|
addGlobalListener(el: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void,
|
|
18
19
|
removeGlobalListener<K extends keyof DocumentEventMap>(el: EventTarget, type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void,
|
package/src/useId.ts
CHANGED
|
@@ -22,7 +22,13 @@ let canUseDOM = Boolean(
|
|
|
22
22
|
window.document.createElement
|
|
23
23
|
);
|
|
24
24
|
|
|
25
|
-
let idsUpdaterMap: Map<string,
|
|
25
|
+
export let idsUpdaterMap: Map<string, { current: string | null }[]> = new Map();
|
|
26
|
+
// This allows us to clean up the idsUpdaterMap when the id is no longer used.
|
|
27
|
+
// Map is a strong reference, so unused ids wouldn't be cleaned up otherwise.
|
|
28
|
+
// This can happen in suspended components where mount/unmount is not called.
|
|
29
|
+
let registry = new FinalizationRegistry<string>((heldValue) => {
|
|
30
|
+
idsUpdaterMap.delete(heldValue);
|
|
31
|
+
});
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* If a default is not provided, generate an id.
|
|
@@ -33,35 +39,38 @@ export function useId(defaultId?: string): string {
|
|
|
33
39
|
let nextId = useRef(null);
|
|
34
40
|
|
|
35
41
|
let res = useSSRSafeId(value);
|
|
42
|
+
let cleanupRef = useRef(null);
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
nextId.current = val;
|
|
39
|
-
}, []);
|
|
44
|
+
registry.register(cleanupRef, res);
|
|
40
45
|
|
|
41
46
|
if (canUseDOM) {
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
47
|
+
const cacheIdRef = idsUpdaterMap.get(res);
|
|
48
|
+
if (cacheIdRef && !cacheIdRef.includes(nextId)) {
|
|
49
|
+
cacheIdRef.push(nextId);
|
|
45
50
|
} else {
|
|
46
|
-
idsUpdaterMap.set(res, [
|
|
51
|
+
idsUpdaterMap.set(res, [nextId]);
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
useLayoutEffect(() => {
|
|
51
56
|
let r = res;
|
|
52
57
|
return () => {
|
|
58
|
+
// In Suspense, the cleanup function may be not called
|
|
59
|
+
// when it is though, also remove it from the finalization registry.
|
|
60
|
+
registry.unregister(cleanupRef);
|
|
53
61
|
idsUpdaterMap.delete(r);
|
|
54
62
|
};
|
|
55
63
|
}, [res]);
|
|
56
64
|
|
|
57
|
-
// This cannot cause an infinite loop because the ref is
|
|
65
|
+
// This cannot cause an infinite loop because the ref is always cleaned up.
|
|
58
66
|
// eslint-disable-next-line
|
|
59
67
|
useEffect(() => {
|
|
60
68
|
let newId = nextId.current;
|
|
61
|
-
if (newId) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
if (newId) { setValue(newId); }
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
if (newId) { nextId.current = null; }
|
|
73
|
+
};
|
|
65
74
|
});
|
|
66
75
|
|
|
67
76
|
return res;
|
|
@@ -78,13 +87,13 @@ export function mergeIds(idA: string, idB: string): string {
|
|
|
78
87
|
|
|
79
88
|
let setIdsA = idsUpdaterMap.get(idA);
|
|
80
89
|
if (setIdsA) {
|
|
81
|
-
setIdsA.forEach(
|
|
90
|
+
setIdsA.forEach(ref => (ref.current = idB));
|
|
82
91
|
return idB;
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
let setIdsB = idsUpdaterMap.get(idB);
|
|
86
95
|
if (setIdsB) {
|
|
87
|
-
setIdsB.forEach(
|
|
96
|
+
setIdsB.forEach((ref) => (ref.current = idA));
|
|
88
97
|
return idA;
|
|
89
98
|
}
|
|
90
99
|
|
package/src/useUpdateEffect.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import {EffectCallback, useEffect, useRef} from 'react';
|
|
14
14
|
|
|
15
15
|
// Like useEffect, but only called for updates after the initial render.
|
|
16
|
-
export function useUpdateEffect(effect: EffectCallback, dependencies: any[]) {
|
|
16
|
+
export function useUpdateEffect(effect: EffectCallback, dependencies: any[]): void {
|
|
17
17
|
const isInitialMount = useRef(true);
|
|
18
18
|
const lastDeps = useRef<any[] | null>(null);
|
|
19
19
|
|
|
@@ -25,9 +25,10 @@ export function useUpdateEffect(effect: EffectCallback, dependencies: any[]) {
|
|
|
25
25
|
}, []);
|
|
26
26
|
|
|
27
27
|
useEffect(() => {
|
|
28
|
+
let prevDeps = lastDeps.current;
|
|
28
29
|
if (isInitialMount.current) {
|
|
29
30
|
isInitialMount.current = false;
|
|
30
|
-
} else if (!
|
|
31
|
+
} else if (!prevDeps || dependencies.some((dep, i) => !Object.is(dep, prevDeps[i]))) {
|
|
31
32
|
effect();
|
|
32
33
|
}
|
|
33
34
|
lastDeps.current = dependencies;
|