@@ -63,11 +63,6 @@ export const IS_FIREFOX: boolean = IS_FIREFOX_;
63
63
export const IS_IOS : boolean = IS_IOS_ ;
64
64
export const IS_SAFARI : boolean = IS_SAFARI_ ;
65
65
66
- export type DFSNode = Readonly < {
67
- depth : number ;
68
- node : LexicalNode ;
69
- } > ;
70
-
71
66
/**
72
67
* Takes an HTML element and adds the classNames passed within an array,
73
68
* ignoring any non-string types. A space can be used to add multiple classes
@@ -166,59 +161,129 @@ export function mediaFileReader(
166
161
} ) ;
167
162
}
168
163
164
+ export type DFSNode = Readonly < {
165
+ depth : number ;
166
+ node : LexicalNode ;
167
+ } > ;
168
+
169
169
/**
170
170
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
171
171
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
172
172
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
173
173
* It will then return all the nodes found in the search in an array of objects.
174
- * @param startingNode - The node to start the search, if ommitted , it will start at the root node.
175
- * @param endingNode - The node to end the search, if ommitted , it will find all descendants of the startingNode.
174
+ * @param startNode - The node to start the search, if omitted , it will start at the root node.
175
+ * @param endNode - The node to end the search, if omitted , it will find all descendants of the startingNode.
176
176
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
177
- * \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the ending node) so long as it exists
177
+ * \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
178
178
*/
179
179
export function $dfs (
180
- startingNode ?: LexicalNode ,
181
- endingNode ?: LexicalNode ,
180
+ startNode ?: LexicalNode ,
181
+ endNode ?: LexicalNode ,
182
182
) : Array < DFSNode > {
183
- const nodes = [ ] ;
184
- const start = ( startingNode || $getRoot ( ) ) . getLatest ( ) ;
185
- const end =
186
- endingNode ||
187
- ( $isElementNode ( start ) ? start . getLastDescendant ( ) || start : start ) ;
188
- let node : LexicalNode | null = start ;
189
- let depth = $getDepth ( node ) ;
190
-
191
- while ( node !== null && ! node . is ( end ) ) {
192
- nodes . push ( { depth, node} ) ;
193
-
194
- if ( $isElementNode ( node ) && node . getChildrenSize ( ) > 0 ) {
195
- node = node . getFirstChild ( ) ;
196
- depth ++ ;
197
- } else {
198
- // Find immediate sibling or nearest parent sibling
199
- let sibling = null ;
183
+ return Array . from ( $dfsIterator ( startNode , endNode ) ) ;
184
+ }
185
+
186
+ type DFSIterator = {
187
+ next : ( ) => IteratorResult < DFSNode , void > ;
188
+ [ Symbol . iterator ] : ( ) => DFSIterator ;
189
+ } ;
200
190
201
- while ( sibling === null && node !== null ) {
202
- sibling = node . getNextSibling ( ) ;
191
+ const iteratorDone : Readonly < { done : true ; value : void } > = {
192
+ done : true ,
193
+ value : undefined ,
194
+ } ;
195
+ const iteratorNotDone : < T > ( value : T ) => Readonly < { done : false ; value : T } > = < T > (
196
+ value : T ,
197
+ ) => ( { done : false , value} ) ;
203
198
204
- if ( sibling === null ) {
205
- node = node . getParent ( ) ;
206
- depth -- ;
207
- } else {
208
- node = sibling ;
199
+ /**
200
+ * $dfs iterator. Tree traversal is done on the fly as new values are requested with O(1) memory.
201
+ * @param startNode - The node to start the search, if omitted, it will start at the root node.
202
+ * @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
203
+ * @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
204
+ */
205
+ export function $dfsIterator (
206
+ startNode ?: LexicalNode ,
207
+ endNode ?: LexicalNode ,
208
+ ) : DFSIterator {
209
+ const start = ( startNode || $getRoot ( ) ) . getLatest ( ) ;
210
+ const startDepth = $getDepth ( start ) ;
211
+ const end = endNode ;
212
+ let node : null | LexicalNode = start ;
213
+ let depth = startDepth ;
214
+ let isFirstNext = true ;
215
+
216
+ const iterator : DFSIterator = {
217
+ next ( ) : IteratorResult < DFSNode , void > {
218
+ if ( node === null ) {
219
+ return iteratorDone ;
220
+ }
221
+ if ( isFirstNext ) {
222
+ isFirstNext = false ;
223
+ return iteratorNotDone ( { depth, node} ) ;
224
+ }
225
+ if ( node === end ) {
226
+ return iteratorDone ;
227
+ }
228
+
229
+ if ( $isElementNode ( node ) && node . getChildrenSize ( ) > 0 ) {
230
+ node = node . getFirstChild ( ) ;
231
+ depth ++ ;
232
+ } else {
233
+ let depthDiff ;
234
+ [ node , depthDiff ] = $getNextSiblingOrParentSibling ( node ) || [ null , 0 ] ;
235
+ depth += depthDiff ;
236
+ if ( end == null && depth <= startDepth ) {
237
+ node = null ;
209
238
}
210
239
}
240
+
241
+ if ( node === null ) {
242
+ return iteratorDone ;
243
+ }
244
+ return iteratorNotDone ( { depth, node} ) ;
245
+ } ,
246
+ [ Symbol . iterator ] ( ) : DFSIterator {
247
+ return iterator ;
248
+ } ,
249
+ } ;
250
+ return iterator ;
251
+ }
252
+
253
+ /**
254
+ * Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
255
+ * R -> P -> T1, T2
256
+ * -> P2
257
+ * returns T2 for node T1, P2 for node T2, and null for node P2.
258
+ * @param node LexicalNode.
259
+ * @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
260
+ */
261
+ export function $getNextSiblingOrParentSibling (
262
+ node : LexicalNode ,
263
+ ) : null | [ LexicalNode , number ] {
264
+ let node_ : null | LexicalNode = node ;
265
+ // Find immediate sibling or nearest parent sibling
266
+ let sibling = null ;
267
+ let depthDiff = 0 ;
268
+
269
+ while ( sibling === null && node_ !== null ) {
270
+ sibling = node_ . getNextSibling ( ) ;
271
+
272
+ if ( sibling === null ) {
273
+ node_ = node_ . getParent ( ) ;
274
+ depthDiff -- ;
275
+ } else {
276
+ node_ = sibling ;
211
277
}
212
278
}
213
279
214
- if ( node !== null && node . is ( end ) ) {
215
- nodes . push ( { depth , node } ) ;
280
+ if ( node_ === null ) {
281
+ return null ;
216
282
}
217
-
218
- return nodes ;
283
+ return [ node_ , depthDiff ] ;
219
284
}
220
285
221
- function $getDepth ( node : LexicalNode ) : number {
286
+ export function $getDepth ( node : LexicalNode ) : number {
222
287
let innerNode : LexicalNode | null = node ;
223
288
let depth = 0 ;
224
289
0 commit comments