@particle-academy/react-fancy 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +120 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +120 -79
- package/dist/index.js.map +1 -1
- package/docs/Canvas.md +5 -2
- package/docs/TreeNav.md +27 -2
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2231,7 +2231,12 @@ interface CanvasContextValue {
|
|
|
2231
2231
|
nodeRects: Map<string, NodeRect>;
|
|
2232
2232
|
registryVersion: number;
|
|
2233
2233
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
2234
|
+
/** Grid spacing in canvas-space pixels (unaffected by zoom). */
|
|
2235
|
+
gridSize: number;
|
|
2236
|
+
/** When true, dragged nodes snap their top-left corner to the grid. */
|
|
2237
|
+
snapToGrid: boolean;
|
|
2234
2238
|
}
|
|
2239
|
+
type GridStyle = "dots" | "lines" | "none";
|
|
2235
2240
|
interface CanvasProps {
|
|
2236
2241
|
children: ReactNode;
|
|
2237
2242
|
viewport?: ViewportState;
|
|
@@ -2241,8 +2246,17 @@ interface CanvasProps {
|
|
|
2241
2246
|
maxZoom?: number;
|
|
2242
2247
|
pannable?: boolean;
|
|
2243
2248
|
zoomable?: boolean;
|
|
2249
|
+
/** Grid spacing in canvas-space pixels. Defaults to 20. */
|
|
2244
2250
|
gridSize?: number;
|
|
2251
|
+
/** Show/hide the canvas grid. Defaults to false. */
|
|
2245
2252
|
showGrid?: boolean;
|
|
2253
|
+
/** Grid pattern when shown — dots (default), lines, or none. Setting this
|
|
2254
|
+
* to "none" hides the grid even when `showGrid` is true. */
|
|
2255
|
+
gridStyle?: GridStyle;
|
|
2256
|
+
/** Grid color (any CSS color). Defaults to a faint zinc. */
|
|
2257
|
+
gridColor?: string;
|
|
2258
|
+
/** Snap dragged nodes to the grid. Defaults to false. */
|
|
2259
|
+
snapToGrid?: boolean;
|
|
2246
2260
|
/** Automatically fit all nodes into view on initial mount */
|
|
2247
2261
|
fitOnMount?: boolean;
|
|
2248
2262
|
className?: string;
|
|
@@ -2308,7 +2322,7 @@ declare namespace CanvasControls {
|
|
|
2308
2322
|
var displayName: string;
|
|
2309
2323
|
}
|
|
2310
2324
|
|
|
2311
|
-
declare function CanvasRoot({ children, viewport: controlledViewport, defaultViewport, onViewportChange, minZoom, maxZoom, pannable, zoomable, showGrid, fitOnMount, className, style, }: CanvasProps): react_jsx_runtime.JSX.Element;
|
|
2325
|
+
declare function CanvasRoot({ children, viewport: controlledViewport, defaultViewport, onViewportChange, minZoom, maxZoom, pannable, zoomable, showGrid, gridStyle, gridSize, gridColor, snapToGrid, fitOnMount, className, style, }: CanvasProps): react_jsx_runtime.JSX.Element;
|
|
2312
2326
|
declare const Canvas: typeof CanvasRoot & {
|
|
2313
2327
|
Node: typeof CanvasNode;
|
|
2314
2328
|
Edge: typeof CanvasEdge;
|
|
@@ -2474,6 +2488,15 @@ interface TreeNavProps {
|
|
|
2474
2488
|
draggable?: boolean;
|
|
2475
2489
|
/** Callback when a node is moved via drag and drop */
|
|
2476
2490
|
onNodeMove?: (sourceId: string, targetId: string, position: DropPosition) => void;
|
|
2491
|
+
/** Accept drops from outside the tree — OS files, items from other
|
|
2492
|
+
* components, etc. When true, drag-over is allowed for any data source
|
|
2493
|
+
* and `onExternalDrop` fires on drop. (default: false) */
|
|
2494
|
+
acceptExternalDrops?: boolean;
|
|
2495
|
+
/** Called when an external drag (file or cross-component) is dropped on
|
|
2496
|
+
* a node. The raw event lets you read `event.dataTransfer.files` for OS
|
|
2497
|
+
* file drops or `event.dataTransfer.getData(type)` for custom MIME
|
|
2498
|
+
* payloads from other components. */
|
|
2499
|
+
onExternalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;
|
|
2477
2500
|
/** Controlled expanded node IDs */
|
|
2478
2501
|
expandedIds?: string[];
|
|
2479
2502
|
/** Default expanded node IDs (uncontrolled) */
|
|
@@ -2501,6 +2524,8 @@ interface TreeNavContextValue {
|
|
|
2501
2524
|
dragState: DragState;
|
|
2502
2525
|
setDragState: (state: DragState) => void;
|
|
2503
2526
|
onNodeMove?: (sourceId: string, targetId: string, position: DropPosition) => void;
|
|
2527
|
+
acceptExternalDrops: boolean;
|
|
2528
|
+
onExternalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;
|
|
2504
2529
|
nodes: TreeNodeData[];
|
|
2505
2530
|
expandNode: (id: string) => void;
|
|
2506
2531
|
}
|
|
@@ -2515,7 +2540,7 @@ declare namespace TreeNode {
|
|
|
2515
2540
|
var displayName: string;
|
|
2516
2541
|
}
|
|
2517
2542
|
|
|
2518
|
-
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, className, }: TreeNavProps): react_jsx_runtime.JSX.Element;
|
|
2543
|
+
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, acceptExternalDrops, onExternalDrop, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, className, }: TreeNavProps): react_jsx_runtime.JSX.Element;
|
|
2519
2544
|
declare namespace TreeNavRoot {
|
|
2520
2545
|
var displayName: string;
|
|
2521
2546
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2231,7 +2231,12 @@ interface CanvasContextValue {
|
|
|
2231
2231
|
nodeRects: Map<string, NodeRect>;
|
|
2232
2232
|
registryVersion: number;
|
|
2233
2233
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
2234
|
+
/** Grid spacing in canvas-space pixels (unaffected by zoom). */
|
|
2235
|
+
gridSize: number;
|
|
2236
|
+
/** When true, dragged nodes snap their top-left corner to the grid. */
|
|
2237
|
+
snapToGrid: boolean;
|
|
2234
2238
|
}
|
|
2239
|
+
type GridStyle = "dots" | "lines" | "none";
|
|
2235
2240
|
interface CanvasProps {
|
|
2236
2241
|
children: ReactNode;
|
|
2237
2242
|
viewport?: ViewportState;
|
|
@@ -2241,8 +2246,17 @@ interface CanvasProps {
|
|
|
2241
2246
|
maxZoom?: number;
|
|
2242
2247
|
pannable?: boolean;
|
|
2243
2248
|
zoomable?: boolean;
|
|
2249
|
+
/** Grid spacing in canvas-space pixels. Defaults to 20. */
|
|
2244
2250
|
gridSize?: number;
|
|
2251
|
+
/** Show/hide the canvas grid. Defaults to false. */
|
|
2245
2252
|
showGrid?: boolean;
|
|
2253
|
+
/** Grid pattern when shown — dots (default), lines, or none. Setting this
|
|
2254
|
+
* to "none" hides the grid even when `showGrid` is true. */
|
|
2255
|
+
gridStyle?: GridStyle;
|
|
2256
|
+
/** Grid color (any CSS color). Defaults to a faint zinc. */
|
|
2257
|
+
gridColor?: string;
|
|
2258
|
+
/** Snap dragged nodes to the grid. Defaults to false. */
|
|
2259
|
+
snapToGrid?: boolean;
|
|
2246
2260
|
/** Automatically fit all nodes into view on initial mount */
|
|
2247
2261
|
fitOnMount?: boolean;
|
|
2248
2262
|
className?: string;
|
|
@@ -2308,7 +2322,7 @@ declare namespace CanvasControls {
|
|
|
2308
2322
|
var displayName: string;
|
|
2309
2323
|
}
|
|
2310
2324
|
|
|
2311
|
-
declare function CanvasRoot({ children, viewport: controlledViewport, defaultViewport, onViewportChange, minZoom, maxZoom, pannable, zoomable, showGrid, fitOnMount, className, style, }: CanvasProps): react_jsx_runtime.JSX.Element;
|
|
2325
|
+
declare function CanvasRoot({ children, viewport: controlledViewport, defaultViewport, onViewportChange, minZoom, maxZoom, pannable, zoomable, showGrid, gridStyle, gridSize, gridColor, snapToGrid, fitOnMount, className, style, }: CanvasProps): react_jsx_runtime.JSX.Element;
|
|
2312
2326
|
declare const Canvas: typeof CanvasRoot & {
|
|
2313
2327
|
Node: typeof CanvasNode;
|
|
2314
2328
|
Edge: typeof CanvasEdge;
|
|
@@ -2474,6 +2488,15 @@ interface TreeNavProps {
|
|
|
2474
2488
|
draggable?: boolean;
|
|
2475
2489
|
/** Callback when a node is moved via drag and drop */
|
|
2476
2490
|
onNodeMove?: (sourceId: string, targetId: string, position: DropPosition) => void;
|
|
2491
|
+
/** Accept drops from outside the tree — OS files, items from other
|
|
2492
|
+
* components, etc. When true, drag-over is allowed for any data source
|
|
2493
|
+
* and `onExternalDrop` fires on drop. (default: false) */
|
|
2494
|
+
acceptExternalDrops?: boolean;
|
|
2495
|
+
/** Called when an external drag (file or cross-component) is dropped on
|
|
2496
|
+
* a node. The raw event lets you read `event.dataTransfer.files` for OS
|
|
2497
|
+
* file drops or `event.dataTransfer.getData(type)` for custom MIME
|
|
2498
|
+
* payloads from other components. */
|
|
2499
|
+
onExternalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;
|
|
2477
2500
|
/** Controlled expanded node IDs */
|
|
2478
2501
|
expandedIds?: string[];
|
|
2479
2502
|
/** Default expanded node IDs (uncontrolled) */
|
|
@@ -2501,6 +2524,8 @@ interface TreeNavContextValue {
|
|
|
2501
2524
|
dragState: DragState;
|
|
2502
2525
|
setDragState: (state: DragState) => void;
|
|
2503
2526
|
onNodeMove?: (sourceId: string, targetId: string, position: DropPosition) => void;
|
|
2527
|
+
acceptExternalDrops: boolean;
|
|
2528
|
+
onExternalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;
|
|
2504
2529
|
nodes: TreeNodeData[];
|
|
2505
2530
|
expandNode: (id: string) => void;
|
|
2506
2531
|
}
|
|
@@ -2515,7 +2540,7 @@ declare namespace TreeNode {
|
|
|
2515
2540
|
var displayName: string;
|
|
2516
2541
|
}
|
|
2517
2542
|
|
|
2518
|
-
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, className, }: TreeNavProps): react_jsx_runtime.JSX.Element;
|
|
2543
|
+
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, acceptExternalDrops, onExternalDrop, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, className, }: TreeNavProps): react_jsx_runtime.JSX.Element;
|
|
2519
2544
|
declare namespace TreeNavRoot {
|
|
2520
2545
|
var displayName: string;
|
|
2521
2546
|
}
|
package/dist/index.js
CHANGED
|
@@ -11152,12 +11152,30 @@ function countCardChildren(children) {
|
|
|
11152
11152
|
n += 1;
|
|
11153
11153
|
return;
|
|
11154
11154
|
}
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
}
|
|
11155
|
+
const inner = child.props.children;
|
|
11156
|
+
if (inner !== void 0) n += countCardChildren(inner);
|
|
11158
11157
|
});
|
|
11159
11158
|
return n;
|
|
11160
11159
|
}
|
|
11160
|
+
function findCardIndex(children, cardId) {
|
|
11161
|
+
let idx = -1;
|
|
11162
|
+
let i = 0;
|
|
11163
|
+
function walk2(nodes) {
|
|
11164
|
+
Children.forEach(nodes, (child) => {
|
|
11165
|
+
if (idx !== -1) return;
|
|
11166
|
+
if (!isValidElement(child)) return;
|
|
11167
|
+
if (child.type === KanbanCard) {
|
|
11168
|
+
if (child.props.id === cardId) idx = i;
|
|
11169
|
+
i += 1;
|
|
11170
|
+
return;
|
|
11171
|
+
}
|
|
11172
|
+
const inner = child.props.children;
|
|
11173
|
+
if (inner !== void 0) walk2(inner);
|
|
11174
|
+
});
|
|
11175
|
+
}
|
|
11176
|
+
walk2(children);
|
|
11177
|
+
return idx;
|
|
11178
|
+
}
|
|
11161
11179
|
function KanbanColumn({
|
|
11162
11180
|
children,
|
|
11163
11181
|
id,
|
|
@@ -11170,26 +11188,46 @@ function KanbanColumn({
|
|
|
11170
11188
|
const { onCardMove, draggedCard, dragSource, registerColumn } = useKanban();
|
|
11171
11189
|
const [dragOver, setDragOver] = useState(false);
|
|
11172
11190
|
const [dropIndex, setDropIndex] = useState(null);
|
|
11191
|
+
const [dropY, setDropY] = useState(null);
|
|
11173
11192
|
const cardsRef = useRef(null);
|
|
11174
11193
|
useEffect(() => registerColumn(id), [id, registerColumn]);
|
|
11175
|
-
const
|
|
11194
|
+
const updateDrop = useCallback((clientY) => {
|
|
11176
11195
|
const container = cardsRef.current;
|
|
11177
11196
|
if (!container) {
|
|
11178
11197
|
setDropIndex(null);
|
|
11198
|
+
setDropY(null);
|
|
11179
11199
|
return;
|
|
11180
11200
|
}
|
|
11181
|
-
const
|
|
11182
|
-
|
|
11201
|
+
const all = Array.from(
|
|
11202
|
+
container.querySelectorAll(
|
|
11203
|
+
"[data-react-fancy-kanban-card]"
|
|
11204
|
+
)
|
|
11205
|
+
);
|
|
11206
|
+
const cards = all.filter(
|
|
11207
|
+
(el) => el.closest("[data-react-fancy-kanban-column]") === cardsRef.current?.closest("[data-react-fancy-kanban-column]")
|
|
11183
11208
|
);
|
|
11209
|
+
const containerRect = container.getBoundingClientRect();
|
|
11210
|
+
if (cards.length === 0) {
|
|
11211
|
+
setDropIndex(0);
|
|
11212
|
+
setDropY(0);
|
|
11213
|
+
return;
|
|
11214
|
+
}
|
|
11184
11215
|
let idx = cards.length;
|
|
11216
|
+
let yRel = 0;
|
|
11185
11217
|
for (let i = 0; i < cards.length; i++) {
|
|
11186
11218
|
const rect = cards[i].getBoundingClientRect();
|
|
11187
11219
|
if (clientY < rect.top + rect.height / 2) {
|
|
11188
11220
|
idx = i;
|
|
11221
|
+
yRel = rect.top - containerRect.top;
|
|
11189
11222
|
break;
|
|
11190
11223
|
}
|
|
11191
11224
|
}
|
|
11225
|
+
if (idx === cards.length) {
|
|
11226
|
+
const last = cards[cards.length - 1].getBoundingClientRect();
|
|
11227
|
+
yRel = last.bottom - containerRect.top;
|
|
11228
|
+
}
|
|
11192
11229
|
setDropIndex(idx);
|
|
11230
|
+
setDropY(yRel);
|
|
11193
11231
|
}, []);
|
|
11194
11232
|
const handleDragOver = useCallback(
|
|
11195
11233
|
(e) => {
|
|
@@ -11197,14 +11235,15 @@ function KanbanColumn({
|
|
|
11197
11235
|
e.preventDefault();
|
|
11198
11236
|
e.stopPropagation();
|
|
11199
11237
|
setDragOver(true);
|
|
11200
|
-
|
|
11238
|
+
updateDrop(e.clientY);
|
|
11201
11239
|
},
|
|
11202
|
-
[draggedCard,
|
|
11240
|
+
[draggedCard, updateDrop]
|
|
11203
11241
|
);
|
|
11204
11242
|
const handleDragLeave = useCallback((e) => {
|
|
11205
11243
|
if (e.currentTarget.contains(e.relatedTarget)) return;
|
|
11206
11244
|
setDragOver(false);
|
|
11207
11245
|
setDropIndex(null);
|
|
11246
|
+
setDropY(null);
|
|
11208
11247
|
}, []);
|
|
11209
11248
|
const handleDrop = useCallback(
|
|
11210
11249
|
(e) => {
|
|
@@ -11212,16 +11251,15 @@ function KanbanColumn({
|
|
|
11212
11251
|
e.preventDefault();
|
|
11213
11252
|
e.stopPropagation();
|
|
11214
11253
|
const target = dropIndex ?? 0;
|
|
11215
|
-
if (dragSource
|
|
11254
|
+
if (dragSource) {
|
|
11216
11255
|
let finalIdx = target;
|
|
11217
11256
|
if (dragSource === id) {
|
|
11218
11257
|
const srcIdx = findCardIndex(children, draggedCard);
|
|
11219
|
-
if (srcIdx !== -1 && target > srcIdx)
|
|
11220
|
-
finalIdx = target - 1;
|
|
11221
|
-
}
|
|
11258
|
+
if (srcIdx !== -1 && target > srcIdx) finalIdx = target - 1;
|
|
11222
11259
|
if (srcIdx === finalIdx) {
|
|
11223
11260
|
setDragOver(false);
|
|
11224
11261
|
setDropIndex(null);
|
|
11262
|
+
setDropY(null);
|
|
11225
11263
|
return;
|
|
11226
11264
|
}
|
|
11227
11265
|
}
|
|
@@ -11229,27 +11267,13 @@ function KanbanColumn({
|
|
|
11229
11267
|
}
|
|
11230
11268
|
setDragOver(false);
|
|
11231
11269
|
setDropIndex(null);
|
|
11270
|
+
setDropY(null);
|
|
11232
11271
|
},
|
|
11233
11272
|
[draggedCard, dragSource, dropIndex, id, onCardMove, children]
|
|
11234
11273
|
);
|
|
11235
11274
|
const cardCount = countCardChildren(children);
|
|
11236
|
-
if (hideWhenEmpty && cardCount === 0 && !draggedCard)
|
|
11237
|
-
|
|
11238
|
-
}
|
|
11239
|
-
let cardSeen = 0;
|
|
11240
|
-
const showIndicator = draggedCard !== null && dropIndex !== null && dragOver;
|
|
11241
|
-
const renderedChildren = Children.toArray(children).map((child, i) => {
|
|
11242
|
-
const isCard = isValidElement(child) && child.type === KanbanCard;
|
|
11243
|
-
const indicator = showIndicator && isCard && cardSeen === dropIndex ? /* @__PURE__ */ jsx(DropIndicator, {}, `drop-${i}`) : null;
|
|
11244
|
-
if (isCard) cardSeen += 1;
|
|
11245
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
11246
|
-
indicator,
|
|
11247
|
-
child
|
|
11248
|
-
] }, i);
|
|
11249
|
-
});
|
|
11250
|
-
if (showIndicator && dropIndex === cardCount) {
|
|
11251
|
-
renderedChildren.push(/* @__PURE__ */ jsx(DropIndicator, {}, "drop-end"));
|
|
11252
|
-
}
|
|
11275
|
+
if (hideWhenEmpty && cardCount === 0 && !draggedCard) return null;
|
|
11276
|
+
const showIndicator = draggedCard !== null && dropIndex !== null && dropY !== null && dragOver;
|
|
11253
11277
|
const overWip = wipLimit !== void 0 && cardCount > wipLimit;
|
|
11254
11278
|
return /* @__PURE__ */ jsx(KanbanColumnContext.Provider, { value: id, children: /* @__PURE__ */ jsxs(
|
|
11255
11279
|
"div",
|
|
@@ -11284,36 +11308,22 @@ function KanbanColumn({
|
|
|
11284
11308
|
}
|
|
11285
11309
|
)
|
|
11286
11310
|
] }),
|
|
11287
|
-
/* @__PURE__ */
|
|
11311
|
+
/* @__PURE__ */ jsxs("div", { ref: cardsRef, className: "relative flex flex-1 flex-col gap-2", children: [
|
|
11312
|
+
children,
|
|
11313
|
+
showIndicator && /* @__PURE__ */ jsx(
|
|
11314
|
+
"div",
|
|
11315
|
+
{
|
|
11316
|
+
"data-react-fancy-kanban-drop-indicator": "",
|
|
11317
|
+
style: { top: dropY },
|
|
11318
|
+
className: "pointer-events-none absolute left-0 right-0 h-0.5 -translate-y-1/2 rounded-full bg-blue-500/80 shadow-[0_0_0_3px_rgba(59,130,246,0.15)]"
|
|
11319
|
+
}
|
|
11320
|
+
)
|
|
11321
|
+
] })
|
|
11288
11322
|
]
|
|
11289
11323
|
}
|
|
11290
11324
|
) });
|
|
11291
11325
|
}
|
|
11292
11326
|
KanbanColumn.displayName = "KanbanColumn";
|
|
11293
|
-
function DropIndicator() {
|
|
11294
|
-
return /* @__PURE__ */ jsx(
|
|
11295
|
-
"div",
|
|
11296
|
-
{
|
|
11297
|
-
"data-react-fancy-kanban-drop-indicator": "",
|
|
11298
|
-
className: "h-0.5 -my-1 rounded-full bg-blue-500/80"
|
|
11299
|
-
}
|
|
11300
|
-
);
|
|
11301
|
-
}
|
|
11302
|
-
function findCardIndex(children, cardId) {
|
|
11303
|
-
let idx = -1;
|
|
11304
|
-
let i = 0;
|
|
11305
|
-
Children.forEach(children, (child) => {
|
|
11306
|
-
if (idx !== -1) return;
|
|
11307
|
-
if (!isValidElement(child)) return;
|
|
11308
|
-
if (child.type === KanbanCard) {
|
|
11309
|
-
if (child.props.id === cardId) {
|
|
11310
|
-
idx = i;
|
|
11311
|
-
}
|
|
11312
|
-
i += 1;
|
|
11313
|
-
}
|
|
11314
|
-
});
|
|
11315
|
-
return idx;
|
|
11316
|
-
}
|
|
11317
11327
|
function KanbanColumnHandle({
|
|
11318
11328
|
children,
|
|
11319
11329
|
className
|
|
@@ -11451,7 +11461,7 @@ function useCanvas() {
|
|
|
11451
11461
|
return ctx;
|
|
11452
11462
|
}
|
|
11453
11463
|
function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
|
|
11454
|
-
const { registerNode, unregisterNode, viewport } = useCanvas();
|
|
11464
|
+
const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
|
|
11455
11465
|
const nodeRef = useRef(null);
|
|
11456
11466
|
const isDragging = useRef(false);
|
|
11457
11467
|
const dragStart = useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
|
|
@@ -11484,9 +11494,15 @@ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className
|
|
|
11484
11494
|
if (!isDragging.current) return;
|
|
11485
11495
|
const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
|
|
11486
11496
|
const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
|
|
11487
|
-
|
|
11497
|
+
let nx = dragStart.current.nodeX + dx;
|
|
11498
|
+
let ny = dragStart.current.nodeY + dy;
|
|
11499
|
+
if (snapToGrid && gridSize > 0) {
|
|
11500
|
+
nx = Math.round(nx / gridSize) * gridSize;
|
|
11501
|
+
ny = Math.round(ny / gridSize) * gridSize;
|
|
11502
|
+
}
|
|
11503
|
+
onPositionChange?.(nx, ny);
|
|
11488
11504
|
},
|
|
11489
|
-
[viewport.zoom, onPositionChange]
|
|
11505
|
+
[viewport.zoom, onPositionChange, snapToGrid, gridSize]
|
|
11490
11506
|
);
|
|
11491
11507
|
const handlePointerUp = useCallback(() => {
|
|
11492
11508
|
isDragging.current = false;
|
|
@@ -11734,6 +11750,10 @@ function CanvasRoot({
|
|
|
11734
11750
|
pannable = true,
|
|
11735
11751
|
zoomable = true,
|
|
11736
11752
|
showGrid = false,
|
|
11753
|
+
gridStyle = "dots",
|
|
11754
|
+
gridSize = 20,
|
|
11755
|
+
gridColor = "rgb(161 161 170 / 0.3)",
|
|
11756
|
+
snapToGrid = false,
|
|
11737
11757
|
fitOnMount = false,
|
|
11738
11758
|
className,
|
|
11739
11759
|
style
|
|
@@ -11751,8 +11771,8 @@ function CanvasRoot({
|
|
|
11751
11771
|
containerRef
|
|
11752
11772
|
});
|
|
11753
11773
|
const ctx = useMemo(
|
|
11754
|
-
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
|
|
11755
|
-
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
|
|
11774
|
+
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
|
|
11775
|
+
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
|
|
11756
11776
|
);
|
|
11757
11777
|
const hasFitted = useRef(false);
|
|
11758
11778
|
useEffect(() => {
|
|
@@ -11808,9 +11828,13 @@ function CanvasRoot({
|
|
|
11808
11828
|
{
|
|
11809
11829
|
"data-canvas-bg": "",
|
|
11810
11830
|
className: "absolute inset-0",
|
|
11811
|
-
style: showGrid ? {
|
|
11812
|
-
backgroundImage: `
|
|
11813
|
-
backgroundSize: `${
|
|
11831
|
+
style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
|
|
11832
|
+
backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
|
|
11833
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
11834
|
+
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
11835
|
+
} : {
|
|
11836
|
+
backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
|
|
11837
|
+
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
11814
11838
|
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
11815
11839
|
} : void 0
|
|
11816
11840
|
}
|
|
@@ -12527,6 +12551,8 @@ function TreeNode({ node, depth }) {
|
|
|
12527
12551
|
dragState,
|
|
12528
12552
|
setDragState,
|
|
12529
12553
|
onNodeMove,
|
|
12554
|
+
acceptExternalDrops,
|
|
12555
|
+
onExternalDrop,
|
|
12530
12556
|
nodes,
|
|
12531
12557
|
expandNode
|
|
12532
12558
|
} = useTreeNav();
|
|
@@ -12569,14 +12595,18 @@ function TreeNode({ node, depth }) {
|
|
|
12569
12595
|
clearAutoExpand();
|
|
12570
12596
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12571
12597
|
}, [clearAutoExpand, setDragState]);
|
|
12598
|
+
const isExternalDrag = !dragState.draggedNodeId;
|
|
12572
12599
|
const handleDragOver = useCallback((e) => {
|
|
12573
|
-
if (
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12600
|
+
if (isExternalDrag) {
|
|
12601
|
+
if (!acceptExternalDrops) return;
|
|
12602
|
+
} else {
|
|
12603
|
+
const sourceId = dragState.draggedNodeId;
|
|
12604
|
+
if (sourceId === node.id) return;
|
|
12605
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
12606
|
+
}
|
|
12577
12607
|
e.preventDefault();
|
|
12578
12608
|
e.stopPropagation();
|
|
12579
|
-
e.dataTransfer.dropEffect = "move";
|
|
12609
|
+
e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
|
|
12580
12610
|
const position = computeDropPosition(e, !!isFolder);
|
|
12581
12611
|
if (isFolder && !isExpanded && position === "inside") {
|
|
12582
12612
|
if (!autoExpandTimer.current) {
|
|
@@ -12589,9 +12619,9 @@ function TreeNode({ node, depth }) {
|
|
|
12589
12619
|
clearAutoExpand();
|
|
12590
12620
|
}
|
|
12591
12621
|
if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
|
|
12592
|
-
setDragState({ draggedNodeId:
|
|
12622
|
+
setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
|
|
12593
12623
|
}
|
|
12594
|
-
}, [dragState, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
12624
|
+
}, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
12595
12625
|
const handleDragLeave = useCallback((e) => {
|
|
12596
12626
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
12597
12627
|
clearAutoExpand();
|
|
@@ -12605,21 +12635,25 @@ function TreeNode({ node, depth }) {
|
|
|
12605
12635
|
e.stopPropagation();
|
|
12606
12636
|
clearAutoExpand();
|
|
12607
12637
|
const sourceId = dragState.draggedNodeId;
|
|
12608
|
-
const position = dragState.dropPosition;
|
|
12609
|
-
if (
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12638
|
+
const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
|
|
12639
|
+
if (sourceId) {
|
|
12640
|
+
if (sourceId === node.id) return;
|
|
12641
|
+
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
12642
|
+
onNodeMove?.(sourceId, node.id, position);
|
|
12643
|
+
} else if (acceptExternalDrops) {
|
|
12644
|
+
onExternalDrop?.(e, node, position);
|
|
12645
|
+
}
|
|
12613
12646
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
12614
|
-
}, [dragState, node
|
|
12647
|
+
}, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);
|
|
12615
12648
|
const canDrag = draggable && !node.disabled;
|
|
12649
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
12616
12650
|
return /* @__PURE__ */ jsxs(
|
|
12617
12651
|
"div",
|
|
12618
12652
|
{
|
|
12619
12653
|
"data-react-fancy-tree-node": "",
|
|
12620
|
-
onDragOver:
|
|
12621
|
-
onDragLeave:
|
|
12622
|
-
onDrop:
|
|
12654
|
+
onDragOver: dropEnabled ? handleDragOver : void 0,
|
|
12655
|
+
onDragLeave: dropEnabled ? handleDragLeave : void 0,
|
|
12656
|
+
onDrop: dropEnabled ? handleDrop : void 0,
|
|
12623
12657
|
children: [
|
|
12624
12658
|
isDropTarget && dropPosition === "before" && /* @__PURE__ */ jsx(
|
|
12625
12659
|
"div",
|
|
@@ -12692,6 +12726,8 @@ function TreeNavRoot({
|
|
|
12692
12726
|
onNodeContextMenu,
|
|
12693
12727
|
draggable = false,
|
|
12694
12728
|
onNodeMove,
|
|
12729
|
+
acceptExternalDrops = false,
|
|
12730
|
+
onExternalDrop,
|
|
12695
12731
|
expandedIds: controlledExpanded,
|
|
12696
12732
|
defaultExpandedIds,
|
|
12697
12733
|
onExpandedChange,
|
|
@@ -12745,6 +12781,8 @@ function TreeNavRoot({
|
|
|
12745
12781
|
dragState,
|
|
12746
12782
|
setDragState,
|
|
12747
12783
|
onNodeMove,
|
|
12784
|
+
acceptExternalDrops,
|
|
12785
|
+
onExternalDrop,
|
|
12748
12786
|
nodes,
|
|
12749
12787
|
expandNode
|
|
12750
12788
|
}),
|
|
@@ -12759,16 +12797,19 @@ function TreeNavRoot({
|
|
|
12759
12797
|
draggable,
|
|
12760
12798
|
dragState,
|
|
12761
12799
|
onNodeMove,
|
|
12800
|
+
acceptExternalDrops,
|
|
12801
|
+
onExternalDrop,
|
|
12762
12802
|
nodes,
|
|
12763
12803
|
expandNode
|
|
12764
12804
|
]
|
|
12765
12805
|
);
|
|
12806
|
+
const dropEnabled = draggable || acceptExternalDrops;
|
|
12766
12807
|
return /* @__PURE__ */ jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
12767
12808
|
"nav",
|
|
12768
12809
|
{
|
|
12769
12810
|
"data-react-fancy-tree-nav": "",
|
|
12770
12811
|
className: cn("flex flex-col gap-0.5 py-1 text-sm", className),
|
|
12771
|
-
onDragEnd:
|
|
12812
|
+
onDragEnd: dropEnabled ? handleDragEnd : void 0,
|
|
12772
12813
|
children: nodes.map((node) => /* @__PURE__ */ jsx(TreeNode, { node, depth: 0 }, node.id))
|
|
12773
12814
|
}
|
|
12774
12815
|
) });
|