Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 4 #235

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9d759e2
New Nodes Code
jameskerr Mar 13, 2024
1f8a343
Update Tree Struct
jameskerr Mar 13, 2024
d8a9dea
Remove .yarn
jameskerr Mar 13, 2024
192367f
Merge remote-tracking branch 'origin/main' into use-nodes
jameskerr Mar 13, 2024
626e6d6
Back Out Experiments
jameskerr Mar 13, 2024
89311a2
Implementing the Terms Doc
jameskerr Mar 13, 2024
a3fbf69
Finish tree-manager
jameskerr Mar 14, 2024
84854e7
Node Rows Being Rendered
jameskerr Mar 14, 2024
c9ead60
Virtualized List Works
jameskerr Mar 14, 2024
4efe870
Plumbing for Row and Node Renderers Done
jameskerr Mar 14, 2024
728186a
Open State Works
jameskerr Mar 15, 2024
dbc9eb6
Edit Node Works
jameskerr Mar 15, 2024
42e7e3f
Multi Selection Working
jameskerr Mar 18, 2024
0a21adc
Dragging In Progress
jameskerr Mar 19, 2024
276a5cb
Drag and Drop Works
jameskerr Mar 19, 2024
55279a0
Default Props Works
jameskerr Mar 19, 2024
8ba482f
Disable Drop Works
jameskerr Mar 19, 2024
f717e78
Node sorter
jameskerr Mar 28, 2024
8a51691
Add Multiple Sort Fields
jameskerr Mar 28, 2024
0df3e6f
Visiblity working
jameskerr Mar 29, 2024
6cd3cb6
Focus and Scroll Working
jameskerr Apr 2, 2024
f034806
Focus when selecting
jameskerr Apr 2, 2024
7e8e44d
Tab Shortcuts Work
jameskerr Apr 2, 2024
e317edc
Page Up and Down
jameskerr Apr 2, 2024
613039c
Add destroy command
jameskerr Apr 2, 2024
2b485dd
Added create leaf and create internal
jameskerr Apr 3, 2024
eb0978e
Add edit command
jameskerr Apr 3, 2024
f89834a
Move Selection Commands
jameskerr Apr 3, 2024
67270b8
Finished shortcuts
jameskerr Apr 3, 2024
2a822f2
Refactor Nodes Folder
jameskerr Apr 3, 2024
b85e8d2
Tree CRUD working
jameskerr Apr 3, 2024
c7428db
Move tree.drop to onDrop instead of onDragEnd
jameskerr Apr 4, 2024
700d6cb
Right to Left Mode Works
jameskerr Apr 4, 2024
7a10e29
Open folder when hovering over it
jameskerr Apr 4, 2024
2d4b3c4
Open folder after dropping
jameskerr Apr 4, 2024
9179af3
use filter working great
jameskerr Apr 4, 2024
fd6a30a
Add on scroll handler
jameskerr Apr 4, 2024
736cf98
Add open siblings command
jameskerr Apr 4, 2024
1dc1cfc
Deleted Components
jameskerr Apr 4, 2024
961d9df
Use when clause from npm
jameskerr Apr 16, 2024
87a6bb2
Build a valid esmodule
jameskerr Apr 16, 2024
b0ec88d
Fix Types
jameskerr Apr 16, 2024
61318f6
Allow for custom renderers
jameskerr Apr 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
23 changes: 10 additions & 13 deletions modules/docs/content/terms.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@

This is the list of the common domain models found in react-arborist.

* **Tree View**: The main component that renders the UI.
* **Source Data**: Any data you bring from the outside world.
* **Node Object**: An interface that the TreeManager and TreeController expects
* **Source Data Proxy**: A wrapper around SourceData conforming to the NodeObject with methods to mutating SourceData.
* **Tree Manager:** Responsible for responding to change events from the TreeView
* **Tree Controller**: The programming API for developers to interact with the tree.
* **Node Controller**: The programming API for developers to interact with the node.
* **Partial Controller**: An object with the properties `value` and `onChange`, used for managing a slice of the component's state.
- **Tree View**: The main component that renders the UI.
- **Source Data**: Any data you bring from the outside world.
- **Node Object**: An interface that the TreeManager and TreeController expects
- **Source Data Proxy**: A wrapper around SourceData conforming to the NodeObject with methods to mutating SourceData.
- **Tree Manager:** Responsible for responding to change events from the TreeView
- **Tree Controller**: The programming API for developers to interact with the tree.
- **Node Controller**: The programming API for developers to interact with the node.
- **Partial Controller**: An object with the properties `value` and `onChange`, used for managing a slice of the component's state.

## Details

When you reach for react-arborist you usually will bring with you some _source data_ that you with to render with the _TreeView_ component.

The _TreeView_ component receives many props, one of which is called "nodes". The "nodes" prop is a _partial controller_ object with `value` and `onChange` properties. The nodes partial controller value must be an array of _node objects_.



A _node object_ is anything with the following interface:

```ts
Expand All @@ -29,10 +27,9 @@ type NodeObject<T> = {
children: NodeObject<T>[] | null;
isLeaf: boolean;
level: number;
}
};
```


You can convert the _source data_ into _node objects_ yourself, or you can use a helper function provided by react-arborist called `createTreeManager(sourceData, options)`. It will return a _TreeManager_ instance. The _tree manager_ will have a property called "nodes" which will return an array of objects that conform to the _node object_ interface. However, they will be instances of the _SourceDataProxy_ class. These objects have all the properties required of _node objects_ along with methods to mutate the _source data_ it contains. The _TreeManager_ also has methods for mutating the _source data_ in response to change events.

### Internals
Expand All @@ -41,4 +38,4 @@ Now let's see the internals of the _TreeView_ component. All props are given to

During a render, the _tree controller_ will flatten the _node objects_ into an array of _node controllers_. This array can be accessed with the `.nodes` property.

To create a _NodeController_ instance, you will need the parent _tree controller_, the relevant _node object_ and the _row index_ where it will be rendered in UI. That class provides a bunch of convince methods for developers to use when rendering the UI.
To create a _NodeController_ instance, you will need the parent _tree controller_, the relevant _node object_ and the _row index_ where it will be rendered in UI. That class provides a bunch of convince methods for developers to use when rendering the UI.
14 changes: 5 additions & 9 deletions modules/react-arborist/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
"name": "react-arborist",
"version": "3.4.0",
"license": "MIT",
"source": "src/index.ts",
"main": "dist/main/index.js",
"module": "dist/module/index.js",
"type": "module",
"exports": "./dist/module/index.js",
"types": "dist/module/index.d.ts",
"sideEffects": false,
"scripts": {
"build:cjs": "tsc --outDir dist/main",
"build:es": "tsc --outDir dist/module --module es2022 --moduleResolution node",
"build:es": "tsc --outDir dist/module",
"build": "npm-run-all clean -p 'build:**'",
"clean": "rimraf dist",
"prepack": "yarn build",
Expand Down Expand Up @@ -38,11 +36,9 @@
"filterable"
],
"dependencies": {
"react-dnd": "^14.0.3",
"react-dnd-html5-backend": "^14.0.3",
"react-aria": "^3.32.1",
"react-window": "^1.8.10",
"redux": "^5.0.0",
"use-sync-external-store": "^1.2.0"
"when-clause": "0.0.3"
},
"peerDependencies": {
"react": ">= 16.14",
Expand Down
164 changes: 164 additions & 0 deletions modules/react-arborist/src/commands/default-commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { NodeController } from "../controllers/node-controller.js";
import { TreeController } from "../controllers/tree-controller.js";
import { NodeType } from "../nodes/types.js";
import { focusNextElement, focusPrevElement } from "../utils.js";

export type Tree = TreeController<any>;

export function focusFirst(tree: Tree) {
if (tree.firstNode) tree.focus(tree.firstNode.id);
}

export function focusLast(tree: Tree) {
if (tree.lastNode) tree.focus(tree.lastNode.id);
}

export function focusNext(tree: Tree) {
const next = tree.nextNode || tree.firstNode;
if (next) tree.focus(next.id);
}

export function focusPrev(tree: Tree) {
const prev = tree.prevNode || tree.lastNode;
if (prev) tree.focus(prev.id);
}

export function focusPrevPage(tree: Tree) {
const start = tree.visibleStartIndex;
const stop = tree.visibleStopIndex;
const page = stop - start;
let index = tree.focusedNode?.rowIndex ?? 0;
if (index > start) {
index = start;
} else {
index = Math.max(start - page, 0);
}
const node = tree.rows.at(index);
if (node) tree.focus(node.id);
}

export function focusNextPage(tree: Tree) {
const start = tree.visibleStartIndex;
const stop = tree.visibleStopIndex;
const page = stop - start;
let index = tree.focusedNode?.rowIndex ?? 0;
if (index < stop) {
index = stop;
} else {
index = Math.min(index + page, tree.rows.length - 1);
}
const node = tree.rows.at(index);
if (node) tree.focus(node.id);
}

export function focusParent(tree: Tree) {
const parentId = tree.focusedNode?.parentId;
if (parentId) tree.focus(parentId);
}

export function close(tree: Tree) {
tree.focusedNode?.close();
}

export function open(tree: Tree) {
tree.focusedNode?.open();
}

export function focusOutsideNext(tree: Tree) {
if (tree.element) focusNextElement(tree.element);
}

export function focusOutsidePrev(tree: Tree) {
if (tree.element) focusPrevElement(tree.element);
}

export function destroy(tree: Tree) {
if (confirm("Are you sure you want to delete?")) {
if (tree.selectedIds.length) {
tree.destroy(tree.selectedIds);
} else if (tree.focusedNode) {
tree.destroy([tree.focusedNode.id]);
}
}
}

function create(tree: Tree, nodeType: NodeType) {
const node = tree.focusedNode;
const parentId = getInsertParentId(node);
const index = getInsertIndex(tree, node);
const data = tree.props.nodes.initialize({ nodeType });
tree.create({ parentId, index, data });
tree.edit(data.id);
tree.focus(data.id);
}

export function createLeaf(tree: Tree) {
create(tree, "leaf");
}

export function createInternal(tree: Tree) {
create(tree, "internal");
}

function getInsertParentId(focus: NodeController<any> | null) {
if (!focus) return null;
if (focus.isOpen) return focus.id;
return focus.parentId;
}

function getInsertIndex(tree: Tree, focus: NodeController<any> | null) {
if (!focus) return tree.props.nodes.value.length;
if (focus.isOpen) return 0;
return focus.childIndex + 1;
}

export function edit(tree: Tree) {
const node = tree.focusedNode;
if (node) tree.edit(node.id);
}

export function moveSelectionStart(tree: Tree) {
const prev = tree.prevNode;
if (prev) {
tree.focus(prev.id);
tree.selectContiguous(prev.id);
}
}

export function moveSelectionEnd(tree: Tree) {
const next = tree.nextNode;
if (next) {
tree.focus(next.id);
tree.selectContiguous(next.id);
}
}

export function select(tree: Tree) {
const node = tree.focusedNode;
if (node) tree.select(node.id);
}

export function selectAll(tree: Tree) {
tree.selectAll();
}

export function toggle(tree: Tree) {
const node = tree.focusedNode;
if (node) {
node.isOpen ? tree.close(node.id) : tree.open(node.id);
}
}

export function openSiblings(tree: Tree) {
const node = tree.focusedNode;
if (!node) return;
const parent = node.parent;
if (!parent) return;

for (let sibling of parent.object.children!) {
if (!sibling.isLeaf) {
node.isOpen ? tree.close(sibling.id) : tree.open(sibling.id);
}
}
tree.scrollTo(node.rowIndex);
}
7 changes: 7 additions & 0 deletions modules/react-arborist/src/commands/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TreeController } from "../controllers/tree-controller.js";

export type CommandObject<T> = {
[name: string]: CommandBody<T>;
};

export type CommandBody<T> = (tree: TreeController<T>) => void;
15 changes: 0 additions & 15 deletions modules/react-arborist/src/components/cursor.tsx

This file was deleted.

Loading
Loading