# All files / src/tree useTreeItems.ts

100% Statements 137/137
100% Branches 12/12
100% Functions 2/2
100% Lines 137/137

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    1381x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    142x
    142x
    142x
    142x
    142x
    142x
    142x
    142x
    142x
    142x
    142x
    142x
    373x
    373x
    123x
    123x
    123x
    123x
    123x
    123x
    373x
    142x
    142x
    81x
    81x
    61x
    61x
    61x
    123x
    123x
    123x
    123x
    123x
    123x
    123x
    61x
    61x
    61x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    73x
    73x
    73x
    73x
    73x
    19x
    19x
    19x
    19x
    19x
    19x
    19x
    19x
    19x
    19x
    1x
    1x
    1x
    1x
    19x
    19x
    19x
    19x
    19x
    73x
    73x
     import { useMemo } from "react";
    import type {
      TreeItemSorter,
      TreeItemNode,
      TreeData,
      DefaultTreeItemNode,
    } from "./types.js";

    /**
     * A lookup to find all the child ids for a specific parent. This was added to
     * support the `*` keyboard behavior of opening all tree items at the current
     * level.
     *
     * @remarks \@since 6.0.0
     * @internal
     */
    export type TreeItemChildIds = Map<string | null, Set<string>>;

    /**
     * @remarks \@since 6.0.0
     * @internal
     */
    export type RenderableTreeItemNode<
      T extends TreeItemNode = DefaultTreeItemNode,
    > = T & {
      items?: readonly RenderableTreeItemNode<T>[];
    };

    /**
     * @remarks \@since 6.0.0
     * @internal
     */
    export interface BuildTreeOptions<T extends TreeItemNode> {
      sort: TreeItemSorter<T>;
      nodes: T[];
      parentId: string | null;
      treeItemChildIds: TreeItemChildIds;
    }

    /**
     * This util performantly builds a nested list of tree items from a giant flat
     * list of items by linking items together with the provided `parentId`. This
     * will also recursively build the tree and _hopefully_ all items will be added.
     *
     * @remarks \@since 6.0.0 Updated to include the {@link TreeItemChildIds}
     */
    export function buildTree<T extends TreeItemNode>(
      options: BuildTreeOptions<T>
    ): readonly RenderableTreeItemNode<T>[] | undefined {
      const { sort, nodes, parentId, treeItemChildIds } = options;
      const childIds = treeItemChildIds.get(parentId) || new Set();
      const childItems: RenderableTreeItemNode<T>[] = [];

      // doing a "reverse" order filter/move so that the items array shrinks while
      // looping. This makes it so that the entire items array doesn't need to
      // continually be looped through as more items are added to the tree, only the
      // remaining items will have to be looped
      let i = nodes.length;
      while (i > 0) {
        i -= 1;
        if (nodes[i] && nodes[i].parentId === parentId) {
          const [item] = nodes.splice(i, 1);
          childIds.add(item.itemId);
          // shallow cloning so childItems doesn't get applied to the original data
          // set
          childItems.unshift({ ...item });
        }
      }

      if (!childItems.length) {
        return undefined;
      }

      treeItemChildIds.set(parentId, childIds);
      childItems.forEach((childItem) => {
        childItem.items = buildTree({
          sort,
          nodes,
          parentId: childItem.itemId,
          treeItemChildIds,
        });
      });

      return sort(childItems);
    }

    /**
     * @remarks \@since 6.0.0
     */
    export interface TreeItemOptions<T extends TreeItemNode> {
      data: TreeData<T>;
      sort: TreeItemSorter<T>;
      rootId: string | null;
    }

    /**
     * @remarks \@since 6.0.0
     */
    export interface TreeItems<T extends TreeItemNode> {
      items: readonly RenderableTreeItemNode<T>[];
      treeItemChildIds: TreeItemChildIds;
    }

    /**
     * This is mostly an internal hook, but can be used to build tree-like
     * data structures without the need of the `Tree` component.
     *
     * @remarks \@since 6.0.0 converted to use an object argument instead of
     * multiple arguments. Also logs any orphaned items that do not have a parent
     */
    export function useTreeItems<T extends TreeItemNode>(
      options: TreeItemOptions<T>
    ): TreeItems<T> {
      const { data, sort, rootId } = options;

      return useMemo<TreeItems<T>>(() => {
        const values = Object.values(data);
        const treeItemChildIds = new Map<string, Set<string>>();
        const items = buildTree<T>({
          sort,
          nodes: values,
          parentId: rootId,
          treeItemChildIds,
        });

        if (process.env.NODE_ENV !== "production" && values.length) {
          /* eslint-disable no-console */
          console.warn("The following tree items are orphaned without a parent:");
          console.warn(values.slice());
        }

        return {
          items: items || [],
          treeItemChildIds,
        };
      }, [data, rootId, sort]);
    }