@@ -106,10 +106,67 @@ function walkDescendants({
106106 }
107107 }
108108
109- // Flatten all descendant arrays into a single flat array
110109 return childIndex ;
111110}
112111
112+ /**
113+ * Perform a change on the given node and all its descendants
114+ */
115+ function mapDescendants ( {
116+ callback,
117+ getNodeKey,
118+ ignoreCollapsed,
119+ isPseudoRoot = false ,
120+ node,
121+ currentIndex,
122+ path = [ ] ,
123+ lowerSiblingCounts = [ ] ,
124+ } ) {
125+ // The pseudo-root is not considered in the path
126+ const selfPath = ! isPseudoRoot ? [ ...path , getNodeKey ( { node, treeIndex : currentIndex } ) ] : [ ] ;
127+ const selfInfo = ! isPseudoRoot ? { node, path : selfPath , lowerSiblingCounts, treeIndex : currentIndex } : null ;
128+
129+ // Return self on nodes with no children or hidden children
130+ if ( ! node . children || ( node . expanded !== true && ignoreCollapsed && ! isPseudoRoot ) ) {
131+ return {
132+ treeIndex : currentIndex ,
133+ node : callback ( selfInfo ) ,
134+ } ;
135+ }
136+
137+ // Get all descendants
138+ let childIndex = currentIndex ;
139+ const childCount = node . children . length ;
140+ let newChildren = node . children ;
141+ if ( typeof newChildren !== 'function' ) {
142+ newChildren = newChildren . map ( ( child , i ) => {
143+ const mapResult = mapDescendants ( {
144+ callback,
145+ getNodeKey,
146+ ignoreCollapsed,
147+ node : child ,
148+ currentIndex : childIndex + 1 ,
149+ lowerSiblingCounts : [ ...lowerSiblingCounts , childCount - i - 1 ] ,
150+ path : selfPath ,
151+ } ) ;
152+ childIndex = mapResult . treeIndex ;
153+
154+ return mapResult . node ;
155+ } ) ;
156+ }
157+
158+ return {
159+ node : callback ( {
160+ ...selfInfo ,
161+ node : {
162+ ...node ,
163+ children : newChildren ,
164+ } ,
165+ } ) ,
166+ treeIndex : childIndex ,
167+ } ;
168+ }
169+
113170/**
114171 * Count all the visible (expanded) descendants in the tree data.
115172 *
@@ -134,7 +191,7 @@ export function getVisibleNodeCount({ treeData }) {
134191 *
135192 * @param {!Object[] } treeData - Tree data
136193 * @param {!number } targetIndex - The index of the node to search for
137- * @param {function } getNodeKey - Function to get the key from the nodeData and tree index
194+ * @param {! function } getNodeKey - Function to get the key from the nodeData and tree index
138195 *
139196 * @return {{
140197 * node: Object,
@@ -172,7 +229,7 @@ export function getVisibleNodeInfoAtIndex({ treeData, index: targetIndex, getNod
172229 * Walk descendants depth-first and call a callback on each
173230 *
174231 * @param {!Object[] } treeData - Tree data
175- * @param {function } getNodeKey - Function to get the key from the nodeData and tree index
232+ * @param {! function } getNodeKey - Function to get the key from the nodeData and tree index
176233 * @param {function } callback - Function to call on each node
177234 * @param {boolean= } ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
178235 */
@@ -193,11 +250,37 @@ export function walk({ treeData, getNodeKey, callback, ignoreCollapsed = true })
193250 } ) ;
194251}
195252
253+ /**
254+ * Perform a depth-first transversal of the descendants and
255+ * make a change to every node in the tree
256+ *
257+ * @param {!Object[] } treeData - Tree data
258+ * @param {!function } getNodeKey - Function to get the key from the nodeData and tree index
259+ * @param {function } callback - Function to call on each node
260+ * @param {boolean= } ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
261+ */
262+ export function map ( { treeData, getNodeKey, callback, ignoreCollapsed = false } ) {
263+ if ( ! treeData || treeData . length < 1 ) {
264+ return [ ] ;
265+ }
266+
267+ return mapDescendants ( {
268+ callback,
269+ getNodeKey,
270+ ignoreCollapsed,
271+ isPseudoRoot : true ,
272+ node : { children : treeData } ,
273+ currentIndex : - 1 ,
274+ path : [ ] ,
275+ lowerSiblingCounts : [ ] ,
276+ } ) . node . children ;
277+ }
278+
196279/**
197280 * Get visible node data flattened.
198281 *
199282 * @param {!Object[] } treeData - Tree data
200- * @param {function } getNodeKey - Function to get the key from the nodeData and tree index
283+ * @param {! function } getNodeKey - Function to get the key from the nodeData and tree index
201284 *
202285 * @return {{
203286 * node: Object,
@@ -229,7 +312,7 @@ export function getVisibleNodeInfoFlattened({ treeData, getNodeKey }) {
229312 * @param {!Object[] } treeData
230313 * @param {number[]|string[] } path - Array of keys leading up to node to be changed
231314 * @param {function|any } newNode - Node to replace the node at the path with, or a function producing the new node
232- * @param {function } getNodeKey - Function to get the key from the nodeData and tree index
315+ * @param {! function } getNodeKey - Function to get the key from the nodeData and tree index
233316 * @param {boolean= } ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
234317 *
235318 * @return {Object } changedTreeData - The updated tree data
@@ -321,7 +404,7 @@ export function changeNodeAtPath({ treeData, path, newNode, getNodeKey, ignoreCo
321404 *
322405 * @param {!Object[] } treeData
323406 * @param {number[]|string[] } path - Array of keys leading up to node to be deleted
324- * @param {function } getNodeKey - Function to get the key from the nodeData and tree index
407+ * @param {! function } getNodeKey - Function to get the key from the nodeData and tree index
325408 * @param {boolean= } ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
326409 *
327410 * @return {Object } changedTreeData - The updated tree data
@@ -342,7 +425,7 @@ export function removeNodeAtPath({ treeData, path, getNodeKey, ignoreCollapsed =
342425 * @param {!Object[] } treeData
343426 * @param {!Object } newNode - The node to insert
344427 * @param {number[]|string[] } path - Array of keys leading up to node to be deleted
345- * @param {function } getNodeKey - Function to get the key from the nodeData and tree index
428+ * @param {! function } getNodeKey - Function to get the key from the nodeData and tree index
346429 * @param {boolean= } ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
347430 *
348431 * @return {Object } changedTreeData - The updated tree data
@@ -355,13 +438,75 @@ export function addNodeUnderParentPath({
355438 getNodeKey,
356439 ignoreCollapsed = true
357440} ) {
358- // return changeNodeAtPath({
359- // treeData,
360- // path: newParentPath,
361- // getNodeKey,
362- // ignoreCollapsed,
363- // newNode: (node) => typeof node.children === 'object' ? , // Delete the node
364- // });
441+ return changeNodeAtPath ( {
442+ treeData,
443+ getNodeKey,
444+ ignoreCollapsed,
445+ path : newParentPath ,
446+ newNode : ( { node : parentNode } ) => {
447+ if ( typeof parentNode . children === 'function' ) {
448+ throw new Error ( 'Cannot add to children defined by a function' ) ;
449+ }
450+
451+ return {
452+ ...parentNode ,
453+ children : ! parentNode . children ? [ newNode ] : [
454+ ...parentNode . children . slice ( 0 , newChildIndex ) ,
455+ newNode ,
456+ ...parentNode . children . slice ( newChildIndex + 1 ) ,
457+ ] ,
458+ } ;
459+ } ,
460+ } ) ;
461+ }
462+
463+ /**
464+ * Generate a tree structure from flat data.
465+ *
466+ * @param {!Object[] } flatData
467+ * @param {!function } getKey - Function to get the key from the nodeData
468+ * @param {!function } getParentKey - Function to get the parent key from the nodeData
469+ *
470+ * @return {Object[] } treeData - The flat data represented as a tree
471+ */
472+ export function getTreeFromFlatData ( {
473+ flatData,
474+ getKey,
475+ getParentKey,
476+ rootKey,
477+ } ) {
478+ if ( ! flatData ) {
479+ return [ ] ;
480+ }
481+
482+ const childrenToParents = { } ;
483+ flatData . forEach ( child => {
484+ const parentKey = getParentKey ( child ) ;
485+
486+ if ( parentKey in childrenToParents ) {
487+ childrenToParents [ parentKey ] . push ( child ) ;
488+ } else {
489+ childrenToParents [ parentKey ] = [ child ] ;
490+ }
491+ } ) ;
492+
493+ if ( ! ( rootKey in childrenToParents ) ) {
494+ return [ ] ;
495+ }
496+
497+ const trav = ( parent ) => {
498+ const parentKey = getKey ( parent ) ;
499+ if ( parentKey in childrenToParents ) {
500+ return {
501+ ...parent ,
502+ children : childrenToParents [ parentKey ] . map ( child => trav ( child ) ) ,
503+ } ;
504+ }
505+
506+ return { ...parent } ;
507+ } ;
508+
509+ return childrenToParents [ rootKey ] . map ( child => trav ( child ) ) ;
365510}
366511
367512// Performs change to every node in the tree
0 commit comments