@react-aria/utils 3.26.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/animation.main.js +97 -0
- package/dist/animation.main.js.map +1 -0
- package/dist/animation.mjs +91 -0
- package/dist/animation.module.js +91 -0
- package/dist/animation.module.js.map +1 -0
- package/dist/constants.main.js +23 -0
- package/dist/constants.main.js.map +1 -0
- package/dist/constants.mjs +17 -0
- package/dist/constants.module.js +17 -0
- package/dist/constants.module.js.map +1 -0
- 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 +18 -2
- 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/keyboard.main.js +26 -0
- package/dist/keyboard.main.js.map +1 -0
- package/dist/keyboard.mjs +21 -0
- package/dist/keyboard.module.js +21 -0
- package/dist/keyboard.module.js.map +1 -0
- package/dist/main.js +31 -0
- 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 +18 -2
- package/dist/module.js.map +1 -1
- package/dist/scrollIntoView.main.js +14 -8
- package/dist/scrollIntoView.main.js.map +1 -1
- package/dist/scrollIntoView.mjs +14 -8
- package/dist/scrollIntoView.module.js +14 -8
- package/dist/scrollIntoView.module.js.map +1 -1
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/useGlobalListeners.main.js +1 -1
- package/dist/useGlobalListeners.main.js.map +1 -1
- package/dist/useGlobalListeners.mjs +1 -1
- package/dist/useGlobalListeners.module.js +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/dist/useUpdateLayoutEffect.main.js +40 -0
- package/dist/useUpdateLayoutEffect.main.js.map +1 -0
- package/dist/useUpdateLayoutEffect.mjs +35 -0
- package/dist/useUpdateLayoutEffect.module.js +35 -0
- package/dist/useUpdateLayoutEffect.module.js.map +1 -0
- package/package.json +6 -4
- package/src/animation.ts +103 -0
- package/src/constants.ts +15 -0
- package/src/domHelpers.ts +19 -0
- package/src/index.ts +9 -1
- package/src/inertValue.ts +11 -0
- package/src/isFocusable.ts +28 -0
- package/src/keyboard.tsx +27 -0
- package/src/mergeProps.ts +1 -1
- package/src/scrollIntoView.ts +28 -12
- package/src/shadowdom/DOMFunctions.ts +70 -0
- package/src/shadowdom/ShadowTreeWalker.ts +319 -0
- package/src/useGlobalListeners.ts +2 -1
- package/src/useId.ts +24 -15
- package/src/useUpdateEffect.ts +3 -2
- package/src/useUpdateLayoutEffect.ts +37 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const focusableElements = [
|
|
2
|
+
'input:not([disabled]):not([type=hidden])',
|
|
3
|
+
'select:not([disabled])',
|
|
4
|
+
'textarea:not([disabled])',
|
|
5
|
+
'button:not([disabled])',
|
|
6
|
+
'a[href]',
|
|
7
|
+
'area[href]',
|
|
8
|
+
'summary',
|
|
9
|
+
'iframe',
|
|
10
|
+
'object',
|
|
11
|
+
'embed',
|
|
12
|
+
'audio[controls]',
|
|
13
|
+
'video[controls]',
|
|
14
|
+
'[contenteditable]:not([contenteditable^="false"])'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const FOCUSABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]),') + ',[tabindex]:not([disabled]):not([hidden])';
|
|
18
|
+
|
|
19
|
+
focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])');
|
|
20
|
+
const TABBABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]):not([tabindex="-1"]),');
|
|
21
|
+
|
|
22
|
+
export function isFocusable(element: Element) {
|
|
23
|
+
return element.matches(FOCUSABLE_ELEMENT_SELECTOR);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isTabbable(element: Element) {
|
|
27
|
+
return element.matches(TABBABLE_ELEMENT_SELECTOR);
|
|
28
|
+
}
|
package/src/keyboard.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {isMac} from './platform';
|
|
14
|
+
|
|
15
|
+
interface Event {
|
|
16
|
+
altKey: boolean,
|
|
17
|
+
ctrlKey: boolean,
|
|
18
|
+
metaKey: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isCtrlKeyPressed(e: Event) {
|
|
22
|
+
if (isMac()) {
|
|
23
|
+
return e.metaKey;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return e.ctrlKey;
|
|
27
|
+
}
|
package/src/mergeProps.ts
CHANGED
|
@@ -23,7 +23,7 @@ type PropsArg = Props | null | undefined;
|
|
|
23
23
|
// taken from: https://stackoverflow.com/questions/51603250/typescript-3-parameter-list-intersection-type/51604379#51604379
|
|
24
24
|
type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? NullToObject<V> : never;
|
|
25
25
|
type NullToObject<T> = T extends (null | undefined) ? {} : T;
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
28
28
|
|
|
29
29
|
/**
|
package/src/scrollIntoView.ts
CHANGED
|
@@ -30,24 +30,40 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement) {
|
|
|
30
30
|
let x = scrollView.scrollLeft;
|
|
31
31
|
let y = scrollView.scrollTop;
|
|
32
32
|
|
|
33
|
-
// Account for top/left border offsetting the scroll top/Left
|
|
34
|
-
let {
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// Account for top/left border offsetting the scroll top/Left + scroll padding
|
|
34
|
+
let {
|
|
35
|
+
borderTopWidth,
|
|
36
|
+
borderLeftWidth,
|
|
37
|
+
scrollPaddingTop,
|
|
38
|
+
scrollPaddingRight,
|
|
39
|
+
scrollPaddingBottom,
|
|
40
|
+
scrollPaddingLeft
|
|
41
|
+
} = getComputedStyle(scrollView);
|
|
42
|
+
|
|
43
|
+
let borderAdjustedX = x + parseInt(borderLeftWidth, 10);
|
|
44
|
+
let borderAdjustedY = y + parseInt(borderTopWidth, 10);
|
|
37
45
|
// Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width
|
|
38
46
|
let maxX = borderAdjustedX + scrollView.clientWidth;
|
|
39
47
|
let maxY = borderAdjustedY + scrollView.clientHeight;
|
|
40
48
|
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
// Get scroll padding values as pixels - defaults to 0 if no scroll padding
|
|
50
|
+
// is used.
|
|
51
|
+
let scrollPaddingTopNumber = parseInt(scrollPaddingTop, 10) || 0;
|
|
52
|
+
let scrollPaddingBottomNumber = parseInt(scrollPaddingBottom, 10) || 0;
|
|
53
|
+
let scrollPaddingRightNumber = parseInt(scrollPaddingRight, 10) || 0;
|
|
54
|
+
let scrollPaddingLeftNumber = parseInt(scrollPaddingLeft, 10) || 0;
|
|
55
|
+
|
|
56
|
+
if (offsetX <= x + scrollPaddingLeftNumber) {
|
|
57
|
+
x = offsetX - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber;
|
|
58
|
+
} else if (offsetX + width > maxX - scrollPaddingRightNumber) {
|
|
59
|
+
x += offsetX + width - maxX + scrollPaddingRightNumber;
|
|
45
60
|
}
|
|
46
|
-
if (offsetY <= borderAdjustedY) {
|
|
47
|
-
y = offsetY - parseInt(borderTopWidth, 10);
|
|
48
|
-
} else if (offsetY + height > maxY) {
|
|
49
|
-
y += offsetY + height - maxY;
|
|
61
|
+
if (offsetY <= borderAdjustedY + scrollPaddingTopNumber) {
|
|
62
|
+
y = offsetY - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber;
|
|
63
|
+
} else if (offsetY + height > maxY - scrollPaddingBottomNumber) {
|
|
64
|
+
y += offsetY + height - maxY + scrollPaddingBottomNumber;
|
|
50
65
|
}
|
|
66
|
+
|
|
51
67
|
scrollView.scrollLeft = x;
|
|
52
68
|
scrollView.scrollTop = y;
|
|
53
69
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Source: https://github.com/microsoft/tabster/blob/a89fc5d7e332d48f68d03b1ca6e344489d1c3898/src/Shadowdomize/DOMFunctions.ts#L16
|
|
2
|
+
|
|
3
|
+
import {isShadowRoot} from '../domHelpers';
|
|
4
|
+
import {shadowDOM} from '@react-stately/flags';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ShadowDOM safe version of Node.contains.
|
|
8
|
+
*/
|
|
9
|
+
export function nodeContains(
|
|
10
|
+
node: Node | null | undefined,
|
|
11
|
+
otherNode: Node | null | undefined
|
|
12
|
+
): boolean {
|
|
13
|
+
if (!shadowDOM()) {
|
|
14
|
+
return otherNode && node ? node.contains(otherNode) : false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!node || !otherNode) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let currentNode: HTMLElement | Node | null | undefined = otherNode;
|
|
22
|
+
|
|
23
|
+
while (currentNode !== null) {
|
|
24
|
+
if (currentNode === node) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if ((currentNode as HTMLSlotElement).tagName === 'SLOT' &&
|
|
29
|
+
(currentNode as HTMLSlotElement).assignedSlot) {
|
|
30
|
+
// Element is slotted
|
|
31
|
+
currentNode = (currentNode as HTMLSlotElement).assignedSlot!.parentNode;
|
|
32
|
+
} else if (isShadowRoot(currentNode)) {
|
|
33
|
+
// Element is in shadow root
|
|
34
|
+
currentNode = currentNode.host;
|
|
35
|
+
} else {
|
|
36
|
+
currentNode = currentNode.parentNode;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ShadowDOM safe version of document.activeElement.
|
|
45
|
+
*/
|
|
46
|
+
export const getActiveElement = (doc: Document = document) => {
|
|
47
|
+
if (!shadowDOM()) {
|
|
48
|
+
return doc.activeElement;
|
|
49
|
+
}
|
|
50
|
+
let activeElement: Element | null = doc.activeElement;
|
|
51
|
+
|
|
52
|
+
while (activeElement && 'shadowRoot' in activeElement &&
|
|
53
|
+
activeElement.shadowRoot?.activeElement) {
|
|
54
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return activeElement;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* ShadowDOM safe version of event.target.
|
|
62
|
+
*/
|
|
63
|
+
export function getEventTarget(event): Element {
|
|
64
|
+
if (shadowDOM() && event.target.shadowRoot) {
|
|
65
|
+
if (event.composedPath) {
|
|
66
|
+
return event.composedPath()[0];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return event.target;
|
|
70
|
+
}
|
|
@@ -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,
|
|
@@ -29,7 +30,7 @@ export function useGlobalListeners(): GlobalListeners {
|
|
|
29
30
|
listener(...args);
|
|
30
31
|
} : listener;
|
|
31
32
|
globalListeners.current.set(listener, {type, eventTarget, fn, options});
|
|
32
|
-
eventTarget.addEventListener(type,
|
|
33
|
+
eventTarget.addEventListener(type, fn, options);
|
|
33
34
|
}, []);
|
|
34
35
|
let removeGlobalListener = useCallback((eventTarget, type, listener, options) => {
|
|
35
36
|
let fn = globalListeners.current.get(listener)?.fn || listener;
|
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;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {EffectCallback, useRef} from 'react';
|
|
14
|
+
import {useLayoutEffect} from './useLayoutEffect';
|
|
15
|
+
|
|
16
|
+
// Like useLayoutEffect, but only called for updates after the initial render.
|
|
17
|
+
export function useUpdateLayoutEffect(effect: EffectCallback, dependencies: any[]) {
|
|
18
|
+
const isInitialMount = useRef(true);
|
|
19
|
+
const lastDeps = useRef<any[] | null>(null);
|
|
20
|
+
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
isInitialMount.current = true;
|
|
23
|
+
return () => {
|
|
24
|
+
isInitialMount.current = false;
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
useLayoutEffect(() => {
|
|
29
|
+
if (isInitialMount.current) {
|
|
30
|
+
isInitialMount.current = false;
|
|
31
|
+
} else if (!lastDeps.current || dependencies.some((dep, i) => !Object.is(dep, lastDeps[i]))) {
|
|
32
|
+
effect();
|
|
33
|
+
}
|
|
34
|
+
lastDeps.current = dependencies;
|
|
35
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
+
}, dependencies);
|
|
37
|
+
}
|