UNPKG

7.72 kBPlain TextView Raw
1// https://github.com/microsoft/tabster/blob/a89fc5d7e332d48f68d03b1ca6e344489d1c3898/src/Shadowdomize/ShadowTreeWalker.ts
2
3import {nodeContains} from './DOMFunctions';
4import {shadowDOM} from '@react-stately/flags';
5
6export 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 */
309export 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}