react-arborist icon indicating copy to clipboard operation
react-arborist copied to clipboard

Including all the matching node's children in the search result

Open lsfgrd opened this issue 1 year ago • 5 comments

Hey.

Currently the matching function will only add the children that also match the search criteria.

However, I wonder how hard it would be to also provide the possibility to set up the matching to show all the children of matching nodes, regardless if they also match or not.

For example, if I search for "Issue Example", I would like this whole thing to appear: image

For my use case, where the user can select both the parent and its children, the current behavior makes it impossible for them to list the children by searching for the more generic parent, which can be very useful.

I had to change the flattenAndFilterTree function and use patch-package to be able to use it that way, but I believe this use-case might not be that much specific so it might be worth adding the support to the library.

My code base also includes the changes from https://github.com/brimdata/react-arborist/pull/185 as I was having trouble with doing the matches with just the id, since my data can have repeated ids. If we can use truly unique keys, it works much better. Sharing below what I personally did in case it's useful to someone or for the feature request.

modified flattenAndFilterTree

function flattenAndFilterTree<T>(
  root: NodeApi<T>,
  isMatch: (n: NodeApi<T>) => boolean
): NodeApi<T>[] {
  const matches: Record<string, boolean> = {};
  const list: NodeApi<T>[] = [];

  function matchChildren(children: NodeApi<T>[]) {
    children?.forEach((child) => {
      matches[child.key] = true;

      if (child.children) {
        matchChildren(child.children);
      }
    });
  }

  function markMatch(node: NodeApi<T>) {
    const yes = !node.isRoot && isMatch(node);

    if (yes) {
      // match the current node
      matches[node.key] = true;
      let parent = node.parent;

      // match the parents
      while (parent) {
        matches[parent.key] = true;
        parent = parent.parent;
      }

      // match the children
      if (node.children) {
        matchChildren(node.children);
      }
    }

    if (node.children) {
      for (let child of node.children) markMatch(child);
    }
  }

  function collect(node: NodeApi<T>) {
    if (node.level >= 0 && matches[node.key]) {
      list.push(node);
    }
    if (node.isOpen) {
      node.children?.forEach(collect);
    }
  }

  markMatch(root);
  collect(root);
  list.forEach(assignRowIndex);
  return list;
}

Does this make sense? Any interest in making this work?

lsfgrd avatar Nov 01 '23 18:11 lsfgrd