Skip to content

Commit

Permalink
Drag and Drop Works
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskerr committed Mar 19, 2024
1 parent 0a21adc commit 276a5cb
Show file tree
Hide file tree
Showing 13 changed files with 1,693 additions and 56 deletions.
1 change: 1 addition & 0 deletions modules/react-arborist/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"filterable"
],
"dependencies": {
"react-aria": "^3.32.1",
"react-dnd": "^14.0.3",
"react-dnd-html5-backend": "^14.0.3",
"react-window": "^1.8.10",
Expand Down
90 changes: 53 additions & 37 deletions modules/react-arborist/src/components/tree-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@ import React, {
ReactElement,
forwardRef,
useContext,
useMemo,
useRef,
} from "react";
import { TreeController } from "../controllers/tree-controller";
import { TreeViewProps } from "../types/tree-view-props";

import { FixedSizeList, FixedSizeListProps } from "react-window";
import { useDrag, useDrop } from "react-aria";
import { FixedSizeList } from "react-window";
import { NodeController } from "../controllers/node-controller";
import { createRowAttributes } from "../row/attributes";
import { useRowDragAndDrop } from "../row/drag-and-drop";
import { HTML5Backend } from "react-dnd-html5-backend";
import { DndProvider } from "react-dnd";
import { DefaultCursor } from "./default-cursor";
import { useCursorProps } from "../cursor/use-cursor-props";
import { useCursorContainerStyle } from "../cursor/use-cursor-container-style";
import { useOuterDrop } from "../dnd/outer-drop-hook";
import { useListInnerStyle } from "../list/use-list-inner-style";
import { computeDrop } from "../dnd/compute-drop";
import { useNodeDrag } from "../dnd/use-node-drag";
import { useNodeDrop } from "../dnd/use-node-drop";

export function TreeView<T>(props: TreeViewProps<T>) {
console.log("<TreeView/>");
const tree = new TreeController<T>(props);
return (
<TreeViewProvider tree={tree}>
Expand All @@ -37,7 +40,7 @@ function TreeViewProvider<T>(props: {
}) {
return (
<Context.Provider value={props.tree as TreeController<T>}>
<DndProvider backend={HTML5Backend}>{props.children}</DndProvider>
{props.children}
</Context.Provider>
);
}
Expand All @@ -51,12 +54,12 @@ export function useTree<T>(): TreeController<T> {
function TreeViewContainer() {
const tree = useTree();
const outerRef = useRef();
useOuterDrop(outerRef);
// useOuterDrop(outerRef);
return (
<div role="tree">
{/* @ts-ignore */}
<FixedSizeList
// className={tree.props.className}
className={tree.props.className}
itemCount={tree.rows.length}
height={tree.height}
width={tree.width}
Expand All @@ -65,12 +68,12 @@ function TreeViewContainer() {
itemKey={(index) => tree.rows[index]?.id || index}
outerElementType={ListOuter as any}
outerRef={outerRef}
// innerElementType={ListInnerElement}
innerElementType={ListInner as any}
// onScroll={tree.props.onScroll}
// onItemsRendered={tree.onItemsRendered.bind(tree)}
// ref={tree.list}
>
{RowContainer}
{RowContainer as any}
</FixedSizeList>
</div>
);
Expand All @@ -88,6 +91,19 @@ const ListOuter = forwardRef(function ListOuter(
);
});

const ListInner = forwardRef(function ListInner(
{ children, ...rest }: any,
ref,
) {
const tree = useTree();
const style = useListInnerStyle(tree, rest.style);
return (
<div {...rest} ref={ref} style={style}>
{children}
</div>
);
});

function CursorContainer() {
const tree = useTree();
const style = useCursorContainerStyle(tree);
Expand All @@ -103,54 +119,54 @@ function CursorContainer() {
function RowContainer<T>(props: { style: React.CSSProperties; index: number }) {
const tree = useTree<T>();
const node = tree.rows[props.index];
const indent = tree.indent * node.level;
const nodeStyle = { paddingLeft: indent };
const attrs = createRowAttributes(tree, node, props.style);
const { dragRef, innerRef } = useRowDragAndDrop(node);
const ref = useRef<any>();
const dropProps = useNodeDrop(node, ref);

return (
<RowRenderer node={node} attrs={attrs} innerRef={innerRef}>
<NodeRenderer
style={nodeStyle}
dragHandle={dragRef}
node={node}
tree={tree}
/>
<RowRenderer node={node} attrs={{ ...attrs, ...dropProps }} innerRef={ref}>
<NodeContainer node={node} />
</RowRenderer>
);
}

export function RowRenderer<T>(props: {
node: NodeController<T>;
attrs: HTMLAttributes<any>;
innerRef: (el: HTMLDivElement | null) => void;
children: ReactElement;
innerRef: any;
}) {
return (
<div
{...props.attrs}
ref={props.innerRef}
onFocus={(e) => e.stopPropagation()}
// onClick={commands.bind("row-click")}
ref={props.innerRef}
>
{props.children}
</div>
);
}

function NodeContainer<T>(props: { node: NodeController<T> }) {
const { node } = props;
const indent = node.tree.indent * node.level;
const style = { paddingLeft: indent };
const dragProps = useNodeDrag(node);

return <NodeRenderer attrs={{ style, ...dragProps }} node={node} />;
}

function NodeRenderer<T>(props: {
style: CSSProperties;
attrs: HTMLAttributes<any>;
node: NodeController<T>;
tree: TreeController<T>;
dragHandle?: (el: HTMLDivElement | null) => void;
preview?: boolean;
}) {
const { node } = props;
const { node, attrs } = props;

function onSubmit(e: any) {
e.preventDefault();
const data = new FormData(e.currentTarget);
const changes = Object.fromEntries(data.entries());
props.node.submit(changes as Partial<T>);
node.submit(changes as Partial<T>);
}

function onClick(e: any) {
Expand All @@ -166,33 +182,33 @@ function NodeRenderer<T>(props: {
}

return (
<div ref={props.dragHandle} style={props.style}>
<div {...attrs}>
<span
onClick={(e) => {
e.stopPropagation();
props.node.toggle();
node.toggle();
}}
>
{props.node.isLeaf ? "·" : props.node.isOpen ? "📂" : "📁"}
{node.isLeaf ? "·" : node.isOpen ? "📂" : "📁"}
</span>{" "}
{props.node.isEditing ? (
{node.isEditing ? (
<form onSubmit={onSubmit} style={{ display: "contents" }}>
<input
type="text"
name="name"
defaultValue={
/* @ts-ignore */
props.node.data.name
node.data.name
}
/>
</form>
) : (
<span style={{ color: props.node.isSelected ? "red" : "inherit" }}>
<span onClick={onClick}>{props.node.id}</span>
<span style={{ color: node.isSelected ? "red" : "inherit" }}>
<span onClick={onClick}>{node.id}</span>
<span>
{
/* @ts-ignore */
props.node.data.name
node.data.name
}
</span>
</span>
Expand Down
1 change: 0 additions & 1 deletion modules/react-arborist/src/dnd/drag-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export function useDragHook<T>(node: NodeController<T>): ConnectDragSource {
item: () => {
// This is fired once at the begging of a drag operation
tree.dragStart(node.id);
console.log("item", node.id);
return { id: node.id };
},
end: () => {
Expand Down
1 change: 0 additions & 1 deletion modules/react-arborist/src/dnd/drop-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { RefObject } from "react";
import { ConnectDropTarget, useDrop } from "react-dnd";
import { DragItem } from "../types/dnd";
import { computeDrop } from "./compute-drop";
import { actions as dnd } from "../state/dnd-slice";
import { NodeController } from "../controllers/node-controller";

export type DropResult = {
Expand Down
6 changes: 3 additions & 3 deletions modules/react-arborist/src/dnd/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type DndState = {
destinationIndex: number | null;
};

export type DndOnChangeEvent<T> =
export type DndOnChangeEvent =
| {
type: "drag-start";
dragIds: string[];
Expand All @@ -20,7 +20,7 @@ export type DndOnChangeEvent<T> =
type: "drag-end";
};

export type DndPartialController<T> = PartialController<
export type DndPartialController = PartialController<
DndState,
DndOnChangeEvent<T>
DndOnChangeEvent
>;
30 changes: 27 additions & 3 deletions modules/react-arborist/src/dnd/use-dnd.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
export function useDnd() {
import { DndPartialController, DndState } from "./types";
import { useState } from "react";

function getInitialState(): DndState {
return { dragIds: [], destinationParentId: null, destinationIndex: null };
}

export function useDnd(): DndPartialController {
const [value, setValue] = useState(getInitialState);
return {
value: {},
onChange: () => {},
value: value,
onChange: (e) => {
switch (e.type) {
case "drag-start":
setValue((prev) => ({ ...prev, dragIds: e.dragIds }));
break;
case "dragging-over":
setValue((prev) => ({
...prev,
destinationParentId: e.destinationParentId,
destinationIndex: e.destinationIndex,
}));
break;
case "drag-end":
setValue(getInitialState());
break;
}
},
};
}
19 changes: 19 additions & 0 deletions modules/react-arborist/src/dnd/use-node-drag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useDrag } from "react-aria";
import { NodeController } from "../controllers/node-controller";

export function useNodeDrag<T>(node: NodeController<T>) {
const { tree } = node;
const { dragProps } = useDrag({
getItems() {
return [{ nodeId: node.id }];
},
onDragStart(e) {
tree.dragStart(node.id);
},
onDragEnd(e) {
if (tree.canDrop()) tree.drop();
tree.dragEnd();
},
});
return dragProps;
}
28 changes: 28 additions & 0 deletions modules/react-arborist/src/dnd/use-node-drop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useDrop } from "react-aria";
import { NodeController } from "../controllers/node-controller";
import { computeDrop } from "./compute-drop";

export function useNodeDrop<T>(node: NodeController<T>, ref: any) {
const { dropProps } = useDrop({
ref,
onDropMove(e) {
const { drop, cursor } = computeDrop({
element: ref.current,
offset: { x: e.x, y: e.y },
indent: node.tree.indent,
node: node,
nextNode: node.next,
prevNode: node.prev,
});
if (drop) node.tree.draggingOver(drop.parentId, drop.index!);

if (true /* canDrop? */) {
if (cursor) node.tree.showCursor(cursor);
} else {
node.tree.hideCursor();
}
},
});

return dropProps;
}
8 changes: 8 additions & 0 deletions modules/react-arborist/src/list/use-list-inner-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { TreeController } from "../controllers/tree-controller";

export function useListInnerStyle(tree: TreeController<any>, style: any) {
return {
...style,
height: `${parseFloat(style.height) + tree.paddingTop + tree.paddingBottom}px`,
};
}
11 changes: 4 additions & 7 deletions modules/react-arborist/src/nodes/tree-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class TreeManager<T> {
accessor: SourceDataAccessor<T>;

constructor(
sourceData: T[],
public sourceData: T[],
public accessors: Partial<SourceDataAccessors<T>>,
) {
this.accessor = new SourceDataAccessor(accessors);
Expand All @@ -18,10 +18,6 @@ export class TreeManager<T> {
});
}

get sourceData() {
return this.nodes.map((node) => node.sourceData);
}

create(args: { parentId: string | null; index: number; data: T }) {
const { parentId, index, data } = args;
if (!parentId) {
Expand All @@ -44,13 +40,13 @@ export class TreeManager<T> {
const { dragIds, parentId, index } = args;
const draggedData = this.findAll(dragIds).map((d) => d.sourceData);
if (!parentId) {
this.insertChildren(index, ...draggedData);
this.destroy({ ids: dragIds });
this.insertChildren(index, ...draggedData);
} else {
const parent = this.find(parentId);
if (parent) {
parent.insertChildren(index, ...draggedData);
this.destroy({ ids: dragIds });
parent.insertChildren(index, ...draggedData);
}
}
}
Expand Down Expand Up @@ -100,6 +96,7 @@ export class TreeManager<T> {
}

insertChildren(index: number, ...data: T[]) {
console.log(this.sourceData, index, data);
this.sourceData.splice(index, 0, ...data);
}

Expand Down
1 change: 1 addition & 0 deletions modules/react-arborist/src/nodes/use-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function useNodes<T>(
treeManager.destroy(event);
break;
}
console.log([...treeManager.sourceData]);
setSourceData([...treeManager.sourceData]);
},
};
Expand Down
Loading

0 comments on commit 276a5cb

Please sign in to comment.