1 |
|
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 |
|
174 |
|
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 |
|
245 |
|
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 |
|
274 |
|
275 | public nextSibling(): Node | null {
|
276 |
|
277 |
|
278 |
|
279 |
|
280 | return null;
|
281 | }
|
282 |
|
283 | |
284 |
|
285 |
|
286 | public previousSibling(): Node | null {
|
287 |
|
288 |
|
289 |
|
290 |
|
291 | return null;
|
292 | }
|
293 |
|
294 | |
295 |
|
296 |
|
297 | public parentNode(): Node | null {
|
298 |
|
299 |
|
300 |
|
301 |
|
302 | return null;
|
303 | }
|
304 | }
|
305 |
|
306 |
|
307 |
|
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 | }
|