Skip to content

Commit

Permalink
Visiblity working
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskerr committed Mar 29, 2024
1 parent 8a51691 commit 0df3e6f
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 8 deletions.
9 changes: 9 additions & 0 deletions modules/docs/content/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 5 additions & 4 deletions modules/react-arborist/src/controllers/node-controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { NodeObject } from "../nodes/node-object";
import { NodeState } from "../types/state";
import { TreeController } from "./tree-controller";

export class NodeController<T> {
Expand All @@ -9,9 +8,11 @@ export class NodeController<T> {
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;
}
Expand Down
10 changes: 10 additions & 0 deletions modules/react-arborist/src/controllers/tree-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,14 @@ export class TreeController<T> {
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
}
}
}
3 changes: 3 additions & 0 deletions modules/react-arborist/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
1 change: 1 addition & 0 deletions modules/react-arborist/src/props/use-default-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function useDefaultProps<T>(
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,
Expand Down
5 changes: 3 additions & 2 deletions modules/react-arborist/src/types/tree-view-props.ts
Original file line number Diff line number Diff line change
@@ -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<T> = {
/* Partial Controllers */
Expand All @@ -16,6 +16,7 @@ export type TreeViewProps<T> = {
dnd: DndPartialController;
cursor: CursorPartialController;
focus: FocusPartialController;
visible: VisiblePartialController;

/* Dimensions and Sizes */
width: number | string;
Expand Down
12 changes: 12 additions & 0 deletions modules/react-arborist/src/visible/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PartialController } from "../types/utils";

export type VisibleState = Record<string, boolean>;

export type VisibleOnChangeEvent = {
value: VisibleState;
};

export type VisiblePartialController = PartialController<
VisibleState,
VisibleOnChangeEvent
>;
44 changes: 44 additions & 0 deletions modules/react-arborist/src/visible/use-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { VisibleState } from "./types";
import { NodeObject } from "../nodes/node-object";

export function useFilter<T>(nodeObjects: NodeObject<T>[], searchTerm: string) {
const term = searchTerm.trim();
const value: VisibleState = {};

function checkVisibility(nodeObjects: NodeObject<T>[] | 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<T>(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<any>) {
const ids = [];
let parent = nodeObject.parent;
while (parent) {
ids.push(parent.id);
parent = parent.parent;
}
return ids;
}
19 changes: 17 additions & 2 deletions modules/showcase/pages/version4.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>{<TreeView />}</div>;
const [searchTerm, setSearchTerm] = useState("");
const nodes = useNodes(gmailData);
const visible = useFilter(nodes.value, searchTerm);
console.log(visible);
return (
<div>
<TreeView nodes={nodes} visible={visible} />
<input
type="search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 0df3e6f

Please sign in to comment.