From 0df3e6f2c2efc54a52bda997f1cb7aa8ab179021 Mon Sep 17 00:00:00 2001 From: James Kerr Date: Fri, 29 Mar 2024 09:58:22 -0700 Subject: [PATCH] Visiblity working --- modules/docs/content/_index.md | 9 ++++ .../src/controllers/node-controller.ts | 9 ++-- .../src/controllers/tree-controller.ts | 10 +++++ modules/react-arborist/src/index.ts | 3 ++ .../src/props/use-default-props.ts | 1 + .../src/types/tree-view-props.ts | 5 ++- modules/react-arborist/src/visible/types.ts | 12 +++++ .../react-arborist/src/visible/use-filter.ts | 44 +++++++++++++++++++ modules/showcase/pages/version4.tsx | 19 +++++++- package.json | 1 + 10 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 modules/react-arborist/src/visible/types.ts create mode 100644 modules/react-arborist/src/visible/use-filter.ts diff --git a/modules/docs/content/_index.md b/modules/docs/content/_index.md index 543a222..772c854 100644 --- a/modules/docs/content/_index.md +++ b/modules/docs/content/_index.md @@ -330,3 +330,12 @@ return ( /> ); ``` + + +## Filtering + +I need to decide where to do the filtering on the dones. Some things to consider are this. When the tree is filtered, we probably want have different open state. For example, if everything is closed and we type a search, we want all the folders to suddenly be open. But if they clear out their search, it would be good to leave all the folders closed again. + +So the tree needs to know if it is filtered or not. So the filtering should happen in the tree. + +this.rows = ConstructNodes diff --git a/modules/react-arborist/src/controllers/node-controller.ts b/modules/react-arborist/src/controllers/node-controller.ts index da50e75..d076052 100644 --- a/modules/react-arborist/src/controllers/node-controller.ts +++ b/modules/react-arborist/src/controllers/node-controller.ts @@ -1,5 +1,4 @@ import { NodeObject } from "../nodes/node-object"; -import { NodeState } from "../types/state"; import { TreeController } from "./tree-controller"; export class NodeController { @@ -9,9 +8,11 @@ export class NodeController { let index = 0; while (queue.length > 0) { const object = queue.shift()!; - const node = new NodeController(tree, object, index++); - rows.push(node); - if (node.isOpen) queue.unshift(...node.object.children!); + if (tree.isVisible(object.id)) { + const node = new NodeController(tree, object, index++); + rows.push(node); + if (node.isOpen) queue.unshift(...node.object.children!); + } } return rows; } diff --git a/modules/react-arborist/src/controllers/tree-controller.ts b/modules/react-arborist/src/controllers/tree-controller.ts index ba8cad9..3eeca26 100644 --- a/modules/react-arborist/src/controllers/tree-controller.ts +++ b/modules/react-arborist/src/controllers/tree-controller.ts @@ -272,4 +272,14 @@ export class TreeController { get hasFocus() { return this.props.focus.value.isWithinTree; } + + /* Visibility */ + + isVisible(id: string) { + if (id in this.props.visible.value) { + return this.props.visible.value[id] === true; + } else { + return true; // default visible state + } + } } diff --git a/modules/react-arborist/src/index.ts b/modules/react-arborist/src/index.ts index a1d5217..c27a86c 100644 --- a/modules/react-arborist/src/index.ts +++ b/modules/react-arborist/src/index.ts @@ -12,3 +12,6 @@ export * from "./nodes/tree-manager"; export * from "./nodes/use-nodes"; export * from "./selection/use-multi-selection"; export * from "./dnd/use-dnd"; + +/* Partial Controllers */ +export * from "./visible/use-filter"; diff --git a/modules/react-arborist/src/props/use-default-props.ts b/modules/react-arborist/src/props/use-default-props.ts index a835132..fb3ca58 100644 --- a/modules/react-arborist/src/props/use-default-props.ts +++ b/modules/react-arborist/src/props/use-default-props.ts @@ -19,6 +19,7 @@ export function useDefaultProps( dnd: props.dnd ?? useDnd(), cursor: props.cursor ?? useCursor(), focus: props.focus ?? useFocus(), + visible: props.visible ?? { value: {}, onChange: () => {} }, width: props.width ?? 300, height: props.height ?? 500, diff --git a/modules/react-arborist/src/types/tree-view-props.ts b/modules/react-arborist/src/types/tree-view-props.ts index 36bc0a9..0f86f16 100644 --- a/modules/react-arborist/src/types/tree-view-props.ts +++ b/modules/react-arborist/src/types/tree-view-props.ts @@ -1,11 +1,11 @@ import { NodesPartialController } from "../nodes/types"; import { OpensPartialController } from "../opens/types"; -import { PartialController } from "./utils"; -import { EditOnChangeEvent, EditPartialController } from "../edit/types"; +import { EditPartialController } from "../edit/types"; import { SelectionPartialController } from "../selection/types"; import { DisableDropCheck, DndPartialController } from "../dnd/types"; import { CursorPartialController } from "../cursor/types"; import { FocusPartialController } from "../focus/types"; +import { VisiblePartialController } from "../visible/types"; export type TreeViewProps = { /* Partial Controllers */ @@ -16,6 +16,7 @@ export type TreeViewProps = { dnd: DndPartialController; cursor: CursorPartialController; focus: FocusPartialController; + visible: VisiblePartialController; /* Dimensions and Sizes */ width: number | string; diff --git a/modules/react-arborist/src/visible/types.ts b/modules/react-arborist/src/visible/types.ts new file mode 100644 index 0000000..35dba22 --- /dev/null +++ b/modules/react-arborist/src/visible/types.ts @@ -0,0 +1,12 @@ +import { PartialController } from "../types/utils"; + +export type VisibleState = Record; + +export type VisibleOnChangeEvent = { + value: VisibleState; +}; + +export type VisiblePartialController = PartialController< + VisibleState, + VisibleOnChangeEvent +>; diff --git a/modules/react-arborist/src/visible/use-filter.ts b/modules/react-arborist/src/visible/use-filter.ts new file mode 100644 index 0000000..2f13189 --- /dev/null +++ b/modules/react-arborist/src/visible/use-filter.ts @@ -0,0 +1,44 @@ +import { VisibleState } from "./types"; +import { NodeObject } from "../nodes/node-object"; + +export function useFilter(nodeObjects: NodeObject[], searchTerm: string) { + const term = searchTerm.trim(); + const value: VisibleState = {}; + + function checkVisibility(nodeObjects: NodeObject[] | null) { + if (!nodeObjects) return; + for (const nodeObject of nodeObjects) { + if (isMatch(nodeObject.sourceData, term)) { + value[nodeObject.id] = true; + for (const id of getAncestorIds(nodeObject)) value[id] = true; + } else { + value[nodeObject.id] = false; + } + checkVisibility(nodeObject.children); + } + } + + if (term.length > 0) checkVisibility(nodeObjects); + + return { value, onChange: () => {} }; +} + +function isMatch(sourceData: T, searchTerm: string) { + const haystack = Array.from(Object.values(sourceData as any)) + .filter((value) => typeof value === "string") + .join(" ") + .toLocaleLowerCase(); + const needle = searchTerm.toLocaleLowerCase(); + + return haystack.includes(needle); +} + +function getAncestorIds(nodeObject: NodeObject) { + const ids = []; + let parent = nodeObject.parent; + while (parent) { + ids.push(parent.id); + parent = parent.parent; + } + return ids; +} diff --git a/modules/showcase/pages/version4.tsx b/modules/showcase/pages/version4.tsx index e011af8..b14a0a7 100644 --- a/modules/showcase/pages/version4.tsx +++ b/modules/showcase/pages/version4.tsx @@ -1,5 +1,20 @@ -import { TreeView } from "react-arborist"; +import { TreeView, useNodes, useFilter } from "react-arborist"; +import { gmailData } from "../data/gmail"; +import { useState } from "react"; export default function Version4() { - return
{}
; + const [searchTerm, setSearchTerm] = useState(""); + const nodes = useNodes(gmailData); + const visible = useFilter(nodes.value, searchTerm); + console.log(visible); + return ( +
+ + setSearchTerm(e.target.value)} + /> +
+ ); } diff --git a/package.json b/package.json index b030376..927bf64 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ ], "scripts": { "build": "yarn workspaces foreach --all run build", + "build:lib": "yarn workspace react-arborist build", "test": "yarn workspaces foreach --all run test", "bump": "yarn workspace react-arborist version", "publish": "sh bin/publish",