@@ -65,8 +65,17 @@ export class CollectionNode<T> implements Node<T> {
65
65
node . render = this . render ;
66
66
node . colSpan = this . colSpan ;
67
67
node . colIndex = this . colIndex ;
68
+ node . filter = this . filter ;
68
69
return node ;
69
70
}
71
+
72
+ filter ( collection : BaseCollection < T > , newCollection : BaseCollection < T > , filterFn : ( textValue : string ) => boolean ) : CollectionNode < T > | null {
73
+ let [ firstKey , lastKey ] = filterChildren ( collection , newCollection , this . firstChildKey , filterFn ) ;
74
+ let newNode : Mutable < CollectionNode < T > > = this . clone ( ) ;
75
+ newNode . firstChildKey = firstKey ;
76
+ newNode . lastChildKey = lastKey ;
77
+ return newNode ;
78
+ }
70
79
}
71
80
72
81
/**
@@ -213,141 +222,61 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
213
222
this . frozen = ! isSSR ;
214
223
}
215
224
216
- // TODO: this is pretty specific to menu, will need to check if it is generic enough
217
- // Will need to handle varying levels I assume but will revisit after I get searchable menu working for base menu
218
- // TODO: an alternative is to simply walk the collection and add all item nodes that match the filter and any sections/separators we encounter
219
- // to an array, then walk that new array and fix all the next/Prev keys while adding them to the new collection
220
- UNSTABLE_filter ( filterFn : ( nodeValue : string ) => boolean ) : BaseCollection < T > {
225
+ UNSTABLE_filter ( filterFn : ( textValue : string ) => boolean ) : BaseCollection < T > {
221
226
let newCollection = new BaseCollection < T > ( ) ;
222
- // This tracks the absolute last node we've visited in the collection when filtering, used for setting up the filteredCollection's lastKey and
223
- // for updating the next/prevKey for every non-filtered node.
224
- let lastNode : Mutable < CollectionNode < T > > | null = null ;
225
-
226
- for ( let node of this ) {
227
- if ( node . type === 'section' && node . hasChildNodes ) {
228
- let clonedSection : Mutable < CollectionNode < T > > = ( node as CollectionNode < T > ) . clone ( ) ;
229
- let lastChildInSection : Mutable < CollectionNode < T > > | null = null ;
230
- for ( let child of this . getChildren ( node . key ) ) {
231
- if ( shouldKeepNode ( child , filterFn , this , newCollection ) ) {
232
- let clonedChild : Mutable < CollectionNode < T > > = ( child as CollectionNode < T > ) . clone ( ) ;
233
- // eslint-disable-next-line max-depth
234
- if ( lastChildInSection == null ) {
235
- clonedSection . firstChildKey = clonedChild . key ;
236
- }
237
-
238
- // eslint-disable-next-line max-depth
239
- if ( newCollection . firstKey == null ) {
240
- newCollection . firstKey = clonedSection . key ;
241
- }
242
-
243
- // eslint-disable-next-line max-depth
244
- if ( lastChildInSection && lastChildInSection . parentKey === clonedChild . parentKey ) {
245
- lastChildInSection . nextKey = clonedChild . key ;
246
- clonedChild . prevKey = lastChildInSection . key ;
247
- } else {
248
- clonedChild . prevKey = null ;
249
- }
250
-
251
- clonedChild . nextKey = null ;
252
- newCollection . addNode ( clonedChild ) ;
253
- lastChildInSection = clonedChild ;
254
- }
255
- }
227
+ let [ firstKey , lastKey ] = filterChildren ( this , newCollection , this . firstKey , filterFn ) ;
228
+ newCollection . firstKey = firstKey ;
229
+ newCollection . lastKey = lastKey ;
230
+ return newCollection ;
231
+ }
232
+ }
256
233
257
- // Add newly filtered section to collection if it has any valid child nodes, otherwise remove it and its header if any
258
- if ( lastChildInSection ) {
259
- if ( lastChildInSection . type !== 'header' ) {
260
- clonedSection . lastChildKey = lastChildInSection . key ;
261
-
262
- // If the old prev section was filtered out, will need to attach to whatever came before
263
- // eslint-disable-next-line max-depth
264
- if ( lastNode == null ) {
265
- clonedSection . prevKey = null ;
266
- } else if ( lastNode . type === 'section' || lastNode . type === 'separator' ) {
267
- lastNode . nextKey = clonedSection . key ;
268
- clonedSection . prevKey = lastNode . key ;
269
- }
270
- clonedSection . nextKey = null ;
271
- lastNode = clonedSection ;
272
- newCollection . addNode ( clonedSection ) ;
273
- } else {
274
- if ( newCollection . firstKey === clonedSection . key ) {
275
- newCollection . firstKey = null ;
276
- }
277
- newCollection . removeNode ( lastChildInSection . key ) ;
278
- }
279
- }
280
- } else if ( node . type === 'separator' ) {
281
- // will need to check if previous section key exists, if it does then we add the separator to the collection.
282
- // After the full collection is created we'll need to remove it it is the last node in the section (aka no following section after the separator)
283
- let clonedSeparator : Mutable < CollectionNode < T > > = ( node as CollectionNode < T > ) . clone ( ) ;
284
- clonedSeparator . nextKey = null ;
285
- if ( lastNode ?. type === 'section' ) {
286
- lastNode . nextKey = clonedSeparator . key ;
287
- clonedSeparator . prevKey = lastNode . key ;
288
- lastNode = clonedSeparator ;
289
- newCollection . addNode ( clonedSeparator ) ;
290
- }
291
- } else {
292
- // At this point, the node is either a subdialogtrigger node or a standard row/item
293
- let clonedNode : Mutable < CollectionNode < T > > = ( node as CollectionNode < T > ) . clone ( ) ;
294
- if ( shouldKeepNode ( clonedNode , filterFn , this , newCollection ) ) {
295
- if ( newCollection . firstKey == null ) {
296
- newCollection . firstKey = clonedNode . key ;
297
- }
298
-
299
- if ( lastNode != null ) {
300
- if (
301
- ( lastNode . type !== 'section' && lastNode . type !== 'separator' && lastNode . parentKey === clonedNode . parentKey ) ||
302
- ( clonedNode . type === 'loader' )
303
- ) {
304
- lastNode . nextKey = clonedNode . key ;
305
- clonedNode . prevKey = lastNode . key ;
306
- } else {
307
- clonedNode . prevKey = null ;
308
- }
309
- }
310
-
311
- clonedNode . nextKey = null ;
312
- newCollection . addNode ( clonedNode ) ;
313
- lastNode = clonedNode ;
314
- }
234
+ function filterChildren < T > ( collection : BaseCollection < T > , newCollection : BaseCollection < T > , firstChildKey : Key | null , filterFn : ( textValue : string ) => boolean ) : [ Key | null , Key | null ] {
235
+ // loop over the siblings for firstChildKey
236
+ // create new nodes based on calling node.filter for each child
237
+ // if it returns null then don't include it, otherwise update its prev/next keys
238
+ // add them to the newCollection
239
+ if ( firstChildKey == null ) {
240
+ return [ null , null ] ;
241
+ }
242
+
243
+ let firstNode : Node < T > | null = null ;
244
+ let lastNode : Node < T > | null = null ;
245
+ let currentNode = collection . getItem ( firstChildKey ) ;
246
+
247
+ while ( currentNode != null ) {
248
+ let newNode : Mutable < CollectionNode < T > > | null = ( currentNode as CollectionNode < T > ) . filter ( collection , newCollection , filterFn ) ;
249
+ if ( newNode != null ) {
250
+ if ( lastNode ) {
251
+ newNode . prevKey = lastNode . key ;
252
+ lastNode . nextKey = newNode . key ;
315
253
}
316
- }
317
254
318
- if ( lastNode ?. type === 'separator' && lastNode . nextKey === null ) {
319
- let lastSection ;
320
- if ( lastNode . prevKey != null ) {
321
- lastSection = newCollection . getItem ( lastNode . prevKey ) as Mutable < CollectionNode < T > > ;
322
- lastSection . nextKey = null ;
255
+ if ( firstNode == null ) {
256
+ firstNode = newNode ;
323
257
}
324
- newCollection . removeNode ( lastNode . key ) ;
325
- lastNode = lastSection ;
326
- }
327
258
328
- newCollection . lastKey = lastNode ?. key || null ;
259
+ newCollection . addNode ( newNode ) ;
260
+ lastNode = newNode ;
261
+ }
329
262
330
- return newCollection ;
263
+ currentNode = currentNode . nextKey ? collection . getItem ( currentNode . nextKey ) : null ;
331
264
}
332
- }
333
265
334
- function shouldKeepNode < T > ( node : Node < T > , filterFn : ( nodeValue : string ) => boolean , oldCollection : BaseCollection < T > , newCollection : BaseCollection < T > ) : boolean {
335
- if ( node . type === 'subdialogtrigger' || node . type === 'submenutrigger' ) {
336
- // Subdialog wrapper should only have one child, if it passes the filter add it to the new collection since we don't need to
337
- // do any extra handling for its first/next key
338
- let triggerChild = [ ...oldCollection . getChildren ( node . key ) ] [ 0 ] ;
339
- if ( triggerChild && filterFn ( triggerChild . textValue ) ) {
340
- let clonedChild : Mutable < CollectionNode < T > > = ( triggerChild as CollectionNode < T > ) . clone ( ) ;
341
- newCollection . addNode ( clonedChild ) ;
342
- return true ;
266
+ // TODO: this is pretty specific to dividers but doesn't feel like there is a good way to get around it since we only can know
267
+ // to filter the last separator in a collection only after performing a filter for the rest of the contents after it
268
+ // Its gross that it needs to live here, might be nice if somehow we could have this live in the separator code
269
+ if ( lastNode && lastNode . type === 'separator' ) {
270
+ let prevKey = lastNode . prevKey ;
271
+ newCollection . removeNode ( lastNode . key ) ;
272
+
273
+ if ( prevKey ) {
274
+ lastNode = newCollection . getItem ( prevKey ) as Mutable < CollectionNode < T > > ;
275
+ lastNode . nextKey = null ;
343
276
} else {
344
- return false ;
277
+ lastNode = null ;
345
278
}
346
- } else if ( node . type === 'header' || node . type === 'loader' ) {
347
- // TODO what about tree multiple loaders? Should a loader still be preserved if its parent row is filtered out
348
- // Actually how should a tree structure be filtered? Do levels no longer matter or does it filter at each level?
349
- return true ;
350
- } else {
351
- return filterFn ( node . textValue ) ;
352
279
}
280
+
281
+ return [ firstNode ?. key ?? null , lastNode ?. key ?? null ] ;
353
282
}
0 commit comments