UNPKG

@darksnow-ui/node-tree-react

Version:
549 lines (542 loc) 21.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ContextMenu: () => ContextMenu, NodeTree: () => NodeTree, NodeWrapper: () => NodeWrapper, TreeNode: () => TreeNode }); module.exports = __toCommonJS(index_exports); // src/components/NodeTree.tsx var import_node_tree_headless4 = require("@darksnow-ui/node-tree-headless"); // src/components/TreeNode.tsx var import_node_tree_headless2 = require("@darksnow-ui/node-tree-headless"); var import_lucide_react = require("lucide-react"); var import_clsx2 = require("clsx"); // src/components/NodeWrapper.tsx var import_node_tree_headless = require("@darksnow-ui/node-tree-headless"); var import_react = require("react"); var import_clsx = require("clsx"); var import_jsx_runtime = require("react/jsx-runtime"); var NodeWrapper = (0, import_react.memo)( ({ id, children, className }) => { const { handlers } = (0, import_node_tree_headless.useNodeTree)(); const wrapperProps = (0, import_react.useMemo)( () => handlers.wrapperProps(id), [handlers, id] ); const isSelected = (0, import_node_tree_headless.useNodeTreeState)((s) => s.selectedNodes.has(id)); const isFocused = (0, import_node_tree_headless.useNodeTreeState)((s) => s.focusId === id); const isCursor = (0, import_node_tree_headless.useNodeTreeState)((s) => s.cursorId === id); const isDragOver = (0, import_node_tree_headless.useNodeTreeState)((s) => s.dragOverId === id); const isDragging = (0, import_node_tree_headless.useNodeTreeState)((s) => s.dragStartId === id); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { ...wrapperProps, "data-node-id": id, className: (0, import_clsx.clsx)( "node-wrapper", className, isSelected && "selected", isFocused && "focused", isCursor && "cursor", isDragOver && "drag-over", isDragging && "dragging" ), children } ); } ); NodeWrapper.displayName = "NodeWrapper"; // src/components/TreeNode.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); function TreeNode({ row, renderLabel, resolveIcon, indentSize = 20 }) { const { node } = row; const isExpanded = (0, import_node_tree_headless2.useNodeTreeState)((s) => s.expandedNodes.has(node.id)); const isSelected = (0, import_node_tree_headless2.useNodeTreeState)((s) => s.selectedNodes.has(node.id)); const isCursor = (0, import_node_tree_headless2.useNodeTreeState)((s) => s.cursorId === node.id); const isFocus = (0, import_node_tree_headless2.useNodeTreeState)((s) => s.focusId === node.id); const isDragOver = (0, import_node_tree_headless2.useNodeTreeState)((s) => s.dragOverId === node.id); const defaultIconResolver = (node2, isExpanded2) => { if (!node2.expandable) { const ext = node2.label.split(".").pop()?.toLowerCase(); switch (ext) { case "ts": return "typescript"; case "tsx": return "react_ts"; case "js": return "javascript"; case "jsx": return "react"; case "json": return "json"; case "md": return "markdown"; default: return "file"; } } return isExpanded2 ? "folder-open" : "folder"; }; const iconName = resolveIcon ? resolveIcon(node, isExpanded) : defaultIconResolver(node, isExpanded); return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( NodeWrapper, { id: node.id, className: (0, import_clsx2.clsx)( "group flex items-center px-2 cursor-pointer relative", "hover:bg-vscode-list-hoverBackground", isSelected && "bg-vscode-list-activeSelectionBackground", isCursor && "outline outline-1 outline-vscode-focusBorder", isDragOver && "bg-vscode-list-dropBackground", node.expandable && "expandable" ), children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: `${row.level * indentSize}px` } }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center justify-center w-5 h-5", children: node.expandable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "span", { className: (0, import_clsx2.clsx)( "flex items-center justify-center w-full h-full", "hover:bg-vscode-border/50 rounded transition-colors" ), "aria-expanded": isExpanded, children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ChevronDown, { className: "w-3 h-3" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ChevronRight, { className: "w-3 h-3" }) } ) : null }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-shrink-0 mr-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", { className: `icon icon-${iconName}` }) }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "span", { className: (0, import_clsx2.clsx)( "flex-1 text-sm truncate", isSelected ? "text-vscode-list-activeSelectionForeground" : "text-vscode-foreground" // isFocus && "font-semibold", ), "aria-selected": isSelected, children: renderLabel ? renderLabel(node) : node.label } ) ] } ); } // src/components/ContextMenu.tsx var import_node_tree_headless3 = require("@darksnow-ui/node-tree-headless"); var import_react2 = require("react"); var import_lucide_react2 = require("lucide-react"); var import_jsx_runtime3 = require("react/jsx-runtime"); function ContextMenu({ x, y, nodeId, containerElementRef, onClose, className, onAction }) { const { services, controller } = (0, import_node_tree_headless3.useNodeTree)(); const menuRef = (0, import_react2.useRef)(null); const nodes = services.store.getState().nodes; const isRoot = nodeId === "root" || !nodeId; const node = isRoot ? null : nodes.get(nodeId); const isExpanded = !isRoot && services.store.getState().expandedNodes.has(nodeId); (0, import_react2.useEffect)(() => { menuRef.current?.focus(); return () => { containerElementRef?.current?.focus(); }; }, [containerElementRef]); (0, import_react2.useEffect)(() => { const handleKeyDown = (e) => { if (e.key === "Escape") { onClose(); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [onClose]); const handleAction = async (action, actionName) => { await action(); if (actionName && onAction) { onAction(actionName, nodeId); } onClose(); }; if (isRoot) { return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { ref: menuRef, className: className || "context-menu", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), tabIndex: -1, children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { const name = prompt("New folder name:"); if (name) { await controller.createNode(void 0, name); } }, "create-folder"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.FolderPlus, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "New Folder" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { const name = prompt("New file name:"); if (name) { await controller.createNode(void 0, name); } }, "create-file"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.FilePlus, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "New File" }) ] } ), services.store.getState().clipboardNodes?.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "context-menu-divider" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { await controller.pasteNodes(void 0); }, "paste"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Clipboard, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Paste" }) ] } ) ] }) ] } ); } if (!node) return null; return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "div", { ref: menuRef, className: className || "context-menu", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), tabIndex: -1, children: [ node.expandable && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( "button", { className: "context-menu-item", onClick: () => handleAction( () => { if (isExpanded) { services.navigationService.collapseNode(nodeId); } else { services.navigationService.expandNode(nodeId); } }, isExpanded ? "collapse" : "expand" ), children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.FolderClosed, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Collapse" }) ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.FolderOpen, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Expand" }) ] }) } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "context-menu-divider" }) ] }), node.expandable && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { const name = prompt("New folder name:"); if (name) { await controller.createNode(nodeId, name); } }, "create-folder"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.FolderPlus, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "New Folder" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { const name = prompt("New file name:"); if (name) { await controller.createNode(nodeId, name); } }, "create-file"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.FilePlus, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "New File" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "context-menu-divider" }) ] }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(() => { controller.copyNodes([nodeId]); }, "copy"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Copy, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Copy" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(() => { controller.cutNodes([nodeId]); }, "cut"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Scissors, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Cut" }) ] } ), node.expandable && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { await controller.pasteNodes(nodeId); }, "paste"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Clipboard, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Paste" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "context-menu-divider" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item", onClick: () => handleAction(async () => { await controller.renameNode(nodeId); }, "rename"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Edit2, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Rename" }) ] } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( "button", { className: "context-menu-item context-menu-item-danger", onClick: () => handleAction(async () => { await controller.deleteNodes([nodeId]); }, "delete"), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Trash2, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Delete" }) ] } ) ] } ); } // src/components/NodeTree.tsx var import_react3 = require("react"); var import_clsx3 = require("clsx"); var import_jsx_runtime4 = require("react/jsx-runtime"); function NodeTree({ className, style, renderNode, renderEmptyState, onContextMenuAction, indentSize = 20, showContextMenu = true, contextMenuClassName, emptyStateClassName, showRootDropArea = true, rootDropAreaClassName, renderRootDropArea, resolveIcon }) { const context = (0, import_node_tree_headless4.useNodeTree)(); const indexedNodes = (0, import_node_tree_headless4.useNodeTreeState)((state) => state.indexedNodes); const nodes = (0, import_node_tree_headless4.useNodeTreeState)((state) => state.nodes); const dragOverId = (0, import_node_tree_headless4.useNodeTreeState)((state) => state.dragOverId); const dragStartId = (0, import_node_tree_headless4.useNodeTreeState)((state) => state.dragStartId); const { contextMenu, closeContextMenu } = (0, import_node_tree_headless4.useContextMenu)(); const [hasFocus, setHasFocus] = (0, import_react3.useState)(false); const defaultRenderNode = (0, import_react3.useCallback)( (row) => { const node = nodes.get(row.id); if (!node) return null; const state = context.services.store.getState(); const enhancedRow = { ...row, node, selected: state.selectedNodes.has(row.id), expanded: state.expandedNodes.has(row.id), visible: true, isCursor: state.cursorId === row.id, isFocus: state.focusId === row.id }; return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( TreeNode, { row: enhancedRow, indentSize, resolveIcon }, row.id ); }, [nodes, context.services.store, indentSize] ); const defaultRenderEmptyState = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "div", { className: emptyStateClassName || "text-center py-8 text-gray-500 dark:text-gray-400", children: "No nodes to display" } ); const defaultRenderRootDropArea = (isDragOver) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "div", { className: (0, import_clsx3.clsx)( rootDropAreaClassName || "node-tree-root-drop-area", isDragOver && "drag-over" ), style: { flex: 1, minHeight: "20px", display: "flex", alignItems: "center", justifyContent: "center", transition: "all 0.2s ease", cursor: "pointer" } } ); return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)( "div", { ref: context.elementRef, tabIndex: 0, className: (0, import_clsx3.clsx)( "node-tree-container", hasFocus && "has-focus", className ), style: { ...style, display: "flex", flexDirection: "column", height: "100%" }, onFocus: () => setHasFocus(true), onBlur: () => setHasFocus(false), children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "node-tree-content", style: { flexShrink: 0 }, children: indexedNodes && indexedNodes.length > 0 ? indexedNodes.map((row) => { if (renderNode) { const node = nodes.get(row.id); if (!node) return null; const state = context.services.store.getState(); const enhancedRow = { ...row, node, selected: state.selectedNodes.has(row.id), expanded: state.expandedNodes.has(row.id), visible: true, isCursor: state.cursorId === row.id, isFocus: state.focusId === row.id }; return renderNode({ row: enhancedRow }); } return defaultRenderNode(row); }) : renderEmptyState ? renderEmptyState() : defaultRenderEmptyState() }), showRootDropArea && indexedNodes && indexedNodes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "div", { ...context.handlers.rootDropProps, style: { flex: 1, display: "flex" }, children: renderRootDropArea ? renderRootDropArea(dragOverId === "root") : defaultRenderRootDropArea(dragOverId === "root") } ) ] } ), showContextMenu && contextMenu && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( ContextMenu, { x: contextMenu.x, y: contextMenu.y, nodeId: contextMenu.nodeId, containerElementRef: contextMenu.containerElementRef, onClose: closeContextMenu, className: contextMenuClassName, onAction: onContextMenuAction } ) ] }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ContextMenu, NodeTree, NodeWrapper, TreeNode }); //# sourceMappingURL=index.js.map