@mhamz.01/easyflow-whiteboard 2.178.0 → 2.181.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.
Files changed (95) hide show
  1. package/README.md +429 -0
  2. package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -1
  3. package/dist/components/node/custom-node-overlay-layer.js +1 -0
  4. package/dist/components/node/custom-node.d.ts.map +1 -1
  5. package/dist/components/node/custom-node.js +1 -0
  6. package/dist/components/node/document-node.d.ts.map +1 -1
  7. package/dist/components/node/document-node.js +1 -0
  8. package/dist/components/node/hooks/useKeyboardShortcuts.d.ts.map +1 -1
  9. package/dist/components/node/hooks/useKeyboardShortcuts.js +5 -1
  10. package/dist/components/node/hooks/useNodeDrag.d.ts.map +1 -1
  11. package/dist/components/node/hooks/useNodeDrag.js +5 -1
  12. package/dist/components/node/hooks/useNodeState.d.ts.map +1 -1
  13. package/dist/components/node/hooks/useNodeState.js +1 -0
  14. package/dist/components/node/types/overlay-types.d.ts.map +1 -1
  15. package/dist/components/node/types/overlay-types.js +1 -0
  16. package/dist/components/toolbar/document-dropdown.d.ts.map +1 -1
  17. package/dist/components/toolbar/document-dropdown.js +1 -0
  18. package/dist/components/toolbar/layers-control.d.ts.map +1 -1
  19. package/dist/components/toolbar/layers-control.js +1 -0
  20. package/dist/components/toolbar/options/arrow-options.d.ts.map +1 -1
  21. package/dist/components/toolbar/options/arrow-options.js +1 -0
  22. package/dist/components/toolbar/options/erase-option.d.ts.map +1 -1
  23. package/dist/components/toolbar/options/erase-option.js +1 -0
  24. package/dist/components/toolbar/options/image-options.d.ts.map +1 -1
  25. package/dist/components/toolbar/options/image-options.js +1 -0
  26. package/dist/components/toolbar/options/line-options.d.ts.map +1 -1
  27. package/dist/components/toolbar/options/line-options.js +1 -0
  28. package/dist/components/toolbar/options/pen-option.d.ts.map +1 -1
  29. package/dist/components/toolbar/options/pen-option.js +1 -0
  30. package/dist/components/toolbar/options/shape-option.d.ts.map +1 -1
  31. package/dist/components/toolbar/options/shape-option.js +1 -0
  32. package/dist/components/toolbar/options/text-option.d.ts.map +1 -1
  33. package/dist/components/toolbar/options/text-option.js +1 -0
  34. package/dist/components/toolbar/task-dropdown.d.ts.map +1 -1
  35. package/dist/components/toolbar/task-dropdown.js +1 -0
  36. package/dist/components/toolbar/toolbar-button.d.ts.map +1 -1
  37. package/dist/components/toolbar/toolbar-seperator.d.ts.map +1 -1
  38. package/dist/components/toolbar/toolbar-skeleton/toolbar-skeleton.d.ts.map +1 -1
  39. package/dist/components/toolbar/toolbar-skeleton/toolbar-skeleton.js +1 -0
  40. package/dist/components/toolbar/tooloptions-panel.d.ts.map +1 -1
  41. package/dist/components/toolbar/tooloptions-panel.js +1 -0
  42. package/dist/components/toolbar/whiteboard-toolbar.d.ts.map +1 -1
  43. package/dist/components/toolbar/whiteboard-toolbar.js +13 -5
  44. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  45. package/dist/components/ui/label.d.ts.map +1 -1
  46. package/dist/components/ui/slider.d.ts.map +1 -1
  47. package/dist/components/ui/slider.js +1 -0
  48. package/dist/components/whiteboard/whiteboard-test.d.ts +5 -1
  49. package/dist/components/whiteboard/whiteboard-test.d.ts.map +1 -1
  50. package/dist/components/whiteboard/whiteboard-test.js +25 -4
  51. package/dist/components/zoomcontrol/zoom-control.d.ts.map +1 -1
  52. package/dist/components/zoomcontrol/zoom-control.js +1 -0
  53. package/dist/hooks/useCanvasInit.d.ts.map +1 -1
  54. package/dist/hooks/useCanvasInit.js +7 -1
  55. package/dist/hooks/useCopyPaste.d.ts.map +1 -1
  56. package/dist/hooks/useCopyPaste.js +6 -1
  57. package/dist/hooks/useDrawing.d.ts.map +1 -1
  58. package/dist/hooks/useDrawing.js +1 -0
  59. package/dist/hooks/useEraser.d.ts.map +1 -1
  60. package/dist/hooks/useEraser.js +1 -0
  61. package/dist/hooks/useLiveUpdate.d.ts.map +1 -1
  62. package/dist/hooks/useLiveUpdate.js +1 -0
  63. package/dist/hooks/useMouseHandlers.d.ts.map +1 -1
  64. package/dist/hooks/useMouseHandlers.js +1 -0
  65. package/dist/hooks/usePan.d.ts.map +1 -1
  66. package/dist/hooks/usePan.js +1 -0
  67. package/dist/hooks/usePersistance.d.ts.map +1 -1
  68. package/dist/hooks/usePersistance.js +1 -0
  69. package/dist/hooks/useSelection.d.ts.map +1 -1
  70. package/dist/hooks/useSelection.js +1 -1
  71. package/dist/hooks/useTextStyle.d.ts.map +1 -1
  72. package/dist/hooks/useTextStyle.js +1 -0
  73. package/dist/hooks/useToolManager.d.ts.map +1 -1
  74. package/dist/hooks/useToolManager.js +19 -0
  75. package/dist/hooks/useZoom.d.ts.map +1 -1
  76. package/dist/hooks/useZoom.js +1 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1 -0
  79. package/dist/lib/fabric-arrow.d.ts.map +1 -1
  80. package/dist/lib/fabric-arrow.js +1 -0
  81. package/dist/lib/fabric-bidirectional-arrow.d.ts.map +1 -1
  82. package/dist/lib/fabric-bidirectional-arrow.js +1 -0
  83. package/dist/lib/fabric-frame.d.ts.map +1 -1
  84. package/dist/lib/fabric-frame.js +1 -1
  85. package/dist/lib/fabric-utils.d.ts.map +1 -1
  86. package/dist/lib/fabric-utils.js +1 -0
  87. package/dist/lib/utils.d.ts.map +1 -1
  88. package/dist/lib/utils.js +1 -0
  89. package/dist/store/whiteboard-store.d.ts +2 -0
  90. package/dist/store/whiteboard-store.d.ts.map +1 -1
  91. package/dist/store/whiteboard-store.js +4 -0
  92. package/dist/styles.css +6 -0
  93. package/dist/types/canvas-node.d.ts.map +1 -1
  94. package/dist/types/canvas-node.js +1 -0
  95. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,429 @@
1
+ # @mhamz.01/easyflow-whiteboard
2
+
3
+ A feature-rich, embeddable React whiteboard built on Fabric.js, Zustand, and TailwindCSS. Drop it into any React 18+ app and get a fully functional infinite canvas with drawing tools, HTML overlay nodes, and automatic persistence.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [Component API](#component-api)
12
+ - [Types](#types)
13
+ - [Store API](#store-api)
14
+ - [Available Tools](#available-tools)
15
+ - [Keyboard Shortcuts](#keyboard-shortcuts)
16
+ - [Persistence Pattern](#persistence-pattern)
17
+ - [Architecture Overview](#architecture-overview)
18
+ - [Directory Structure](#directory-structure)
19
+ - [Performance Design](#performance-design)
20
+ - [Built With](#built-with)
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @mhamz.01/easyflow-whiteboard
28
+ ```
29
+
30
+ ### Peer Dependencies
31
+
32
+ ```bash
33
+ npm install fabric@^7 react@^18 react-dom@^18 zustand@^5 \
34
+ @radix-ui/react-dropdown-menu@^2 \
35
+ @radix-ui/react-label@^2 \
36
+ @radix-ui/react-slider@^1
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ```tsx
44
+ import { EasyflowWhiteboard } from '@mhamz.01/easyflow-whiteboard';
45
+ import '@mhamz.01/easyflow-whiteboard/dist/styles.css';
46
+
47
+ export default function Page() {
48
+ return (
49
+ <div style={{ width: '100%', height: '100vh' }}>
50
+ <EasyflowWhiteboard />
51
+ </div>
52
+ );
53
+ }
54
+ ```
55
+
56
+ > **Next.js note:** The component uses `"use client"` internally. Wrap it in a dynamic import or a client boundary if you use the App Router.
57
+
58
+ ---
59
+
60
+ ## Component API
61
+
62
+ ### `<EasyflowWhiteboard />`
63
+
64
+ | Prop | Type | Default | Description |
65
+ |------|------|---------|-------------|
66
+ | `initialData` | `WhiteboardInitialData` | — | Pre-load saved canvas JSON and HTML nodes |
67
+ | `onSave` | `(payload: WhiteboardSavePayload) => void` | — | Debounced callback fired whenever the canvas changes |
68
+ | `saveDebounceMs` | `number` | `2000` | Debounce delay (ms) for `onSave` |
69
+ | `availableTasks` | `TaskTemplate[]` | `[]` | Tasks shown in the task picker dropdown |
70
+ | `availableDocuments` | `DocumentTemplate[]` | `[]` | Documents shown in the document picker dropdown |
71
+ | `isLoadingData` | `boolean` | `false` | Show toolbar skeleton while async data loads |
72
+ | `createNewTaskHref` | `string` | — | URL for the "Create new task" footer link |
73
+ | `createNewDocumentHref` | `string` | — | URL for the "Create new document" footer link |
74
+ | `editable` | `boolean` | `true` | Set `false` to make the canvas fully read-only. See [EDITABLE_MODE.md](./EDITABLE_MODE.md) |
75
+
76
+ ---
77
+
78
+ ## Types
79
+
80
+ ### `WhiteboardInitialData`
81
+
82
+ Shape of the data you pass in to restore a saved board.
83
+
84
+ ```ts
85
+ interface WhiteboardInitialData {
86
+ canvas?: string; // canvas.toJSON() string from a previous save
87
+ tasks?: TaskNodeData[];
88
+ documents?: DocumentNodeData[];
89
+ }
90
+ ```
91
+
92
+ ### `WhiteboardSavePayload`
93
+
94
+ Shape of the data the `onSave` callback receives.
95
+
96
+ ```ts
97
+ interface WhiteboardSavePayload {
98
+ canvas: string; // Serialized Fabric.js canvas state
99
+ tasks: TaskNodeData[];
100
+ documents: DocumentNodeData[];
101
+ }
102
+ ```
103
+
104
+ ### `TaskTemplate` / `TaskNodeData`
105
+
106
+ ```ts
107
+ interface TaskNodeData {
108
+ id: string;
109
+ type: "task";
110
+ title: string;
111
+ status: "todo" | "in-progress" | "done";
112
+ x: number; // World-space X position on the canvas
113
+ y: number; // World-space Y position on the canvas
114
+ assignee?: string;
115
+ project?: string;
116
+ priority?: "low" | "medium" | "high";
117
+ dueDate?: string;
118
+ }
119
+ ```
120
+
121
+ ### `DocumentTemplate` / `DocumentNodeData`
122
+
123
+ ```ts
124
+ interface DocumentNodeData {
125
+ id: string;
126
+ type: "document";
127
+ title: string;
128
+ project: string;
129
+ breadcrumb?: string[];
130
+ preview: string;
131
+ updatedAt?: string;
132
+ x: number;
133
+ y: number;
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Store API
140
+
141
+ Access and mutate whiteboard state from anywhere in your app:
142
+
143
+ ```tsx
144
+ import { useWhiteboardStore } from '@mhamz.01/easyflow-whiteboard';
145
+
146
+ function MyComponent() {
147
+ const activeTool = useWhiteboardStore((s) => s.activeTool);
148
+ const setActiveTool = useWhiteboardStore((s) => s.setActiveTool);
149
+
150
+ return <button onClick={() => setActiveTool("pen")}>Pen</button>;
151
+ }
152
+ ```
153
+
154
+ ### State Slices
155
+
156
+ | Field | Type | Description |
157
+ |-------|------|-------------|
158
+ | `activeTool` | `Tool` | Currently selected drawing tool |
159
+ | `setActiveTool` | `(tool: Tool) => void` | Switch the active tool programmatically |
160
+ | `toolOptions` | `ToolOptions` | Per-tool styling options (color, strokeWidth, etc.) |
161
+ | `setToolOption` | `(tool, option, value) => void` | Update a single tool option |
162
+ | `selectedObjects` | `FabricObject[]` | Fabric objects currently selected on canvas |
163
+ | `canUndo` / `canRedo` | `boolean` | Whether undo/redo is available |
164
+ | `zoom` | `number` | Current canvas zoom level (1 = 100%) |
165
+ | `bringToFront` | `(canvas) => void` | Move selected object to the top layer |
166
+ | `sendToBack` | `(canvas) => void` | Move selected object to the bottom layer |
167
+
168
+ ---
169
+
170
+ ## Available Tools
171
+
172
+ | Tool ID | Shortcut | Description |
173
+ |---------|----------|-------------|
174
+ | `select` | `V` | Select and move Fabric objects |
175
+ | `pan` | `H` | Click-drag to pan the viewport; two-finger pinch to zoom |
176
+ | `pen` | `P` | Freehand drawing with configurable stroke |
177
+ | `rectangle` | `R` | Draw rectangles (drag to size) |
178
+ | `circle` | `C` | Draw circles (drag to size) |
179
+ | `frame` | `F` | Draw white frames — always rendered behind other objects |
180
+ | `line` | `L` | Draw straight lines |
181
+ | `arrow` | `A` | Draw lines with a filled arrowhead |
182
+ | `text` | `T` | Place editable inline text |
183
+ | `image` | `I` | Upload and place an image from disk |
184
+ | `eraser` | `E` | Brush-erase objects by painting over them |
185
+ | `undo` | `Ctrl+Z` | Undo the last canvas change |
186
+ | `redo` | `Ctrl+Shift+Z` | Redo the last undone change |
187
+
188
+ ---
189
+
190
+ ## Keyboard Shortcuts
191
+
192
+ | Key | Action |
193
+ |-----|--------|
194
+ | `V H P R C F L A T I E` | Activate corresponding tool |
195
+ | `Delete` / `Backspace` | Delete selected objects or HTML nodes |
196
+ | `Ctrl+Z` | Undo |
197
+ | `Ctrl+Shift+Z` or `Ctrl+Y` | Redo |
198
+ | `Ctrl+C` | Copy selected Fabric objects |
199
+ | `Ctrl+V` | Paste with cascading offset |
200
+ | `Ctrl+A` | Select all HTML nodes (tasks + documents) |
201
+ | `Escape` | Clear HTML node selection |
202
+ | `Ctrl++` / `Ctrl+-` | Zoom in / out |
203
+ | `Ctrl+0` | Reset zoom to 100% |
204
+ | `Ctrl+Scroll` | Zoom to cursor |
205
+ | `Shift+Scroll` | Horizontal pan |
206
+
207
+ ---
208
+
209
+ ## Persistence Pattern
210
+
211
+ The library owns the UI and interaction. You own the data storage:
212
+
213
+ ```tsx
214
+ import { EasyflowWhiteboard, WhiteboardInitialData, WhiteboardSavePayload } from '@mhamz.01/easyflow-whiteboard';
215
+ import '@mhamz.01/easyflow-whiteboard/dist/styles.css';
216
+ import { useCallback, useEffect, useState } from 'react';
217
+
218
+ export default function BoardPage({ boardId }: { boardId: string }) {
219
+ const [initialData, setInitialData] = useState<WhiteboardInitialData>();
220
+ const [isLoading, setIsLoading] = useState(true);
221
+
222
+ // 1. Load saved state from your database on mount
223
+ useEffect(() => {
224
+ fetch(`/api/boards/${boardId}`)
225
+ .then(r => r.json())
226
+ .then(data => {
227
+ setInitialData({
228
+ canvas: data.canvasJson,
229
+ tasks: data.tasks,
230
+ documents: data.documents,
231
+ });
232
+ setIsLoading(false);
233
+ });
234
+ }, [boardId]);
235
+
236
+ // 2. Save on every debounced change
237
+ const handleSave = useCallback(async (payload: WhiteboardSavePayload) => {
238
+ await fetch(`/api/boards/${boardId}`, {
239
+ method: 'PUT',
240
+ body: JSON.stringify({
241
+ canvasJson: payload.canvas,
242
+ tasks: payload.tasks,
243
+ documents: payload.documents,
244
+ }),
245
+ });
246
+ }, [boardId]);
247
+
248
+ return (
249
+ <div style={{ width: '100%', height: '100vh' }}>
250
+ <EasyflowWhiteboard
251
+ initialData={initialData}
252
+ onSave={handleSave}
253
+ saveDebounceMs={2000}
254
+ isLoadingData={isLoading}
255
+ availableTasks={myProjectTasks}
256
+ availableDocuments={myProjectDocs}
257
+ createNewTaskHref="/tasks/new"
258
+ createNewDocumentHref="/docs/new"
259
+ />
260
+ </div>
261
+ );
262
+ }
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Architecture Overview
268
+
269
+ ```
270
+ EasyflowWhiteboard (whiteboard-test.tsx)
271
+
272
+ ├── <canvas> Fabric.js v7 rendering surface
273
+ │ Handles: shapes, paths, images, arrows, frames, text, selection
274
+
275
+ ├── CanvasOverlayLayer Absolutely-positioned HTML div
276
+ │ ├── TaskNode Rich task card (status, assignee, priority)
277
+ │ └── DocumentNode Rich document card (preview, breadcrumb)
278
+ │ Reads canvas viewport transform on every `after:render` to stay aligned
279
+
280
+ ├── WhiteboardToolbar Fixed bottom bar
281
+ │ ├── Tool buttons (V H P R C F L A T I E)
282
+ │ ├── TaskDropdown Template picker → add task to canvas
283
+ │ └── DocumentDropdown Template picker → add doc to canvas
284
+
285
+ ├── ToolOptionsPanel Left sidebar (appears for active tools)
286
+ │ └── PenOptions / ShapeOptions / TextOptions / ImageOptions / ...
287
+
288
+ └── ZoomControls Top-right zoom percentage pill
289
+ ```
290
+
291
+ ### Two-layer rendering
292
+
293
+ Fabric.js renders vector objects (shapes, paths, images) on a `<canvas>` element. An HTML `<div>` overlay, transformed with the same `viewportTransform` matrix, renders interactive React cards (tasks, documents). The two layers share a single world coordinate system — placing a node at `(x: 100, y: 200)` positions it exactly over canvas coordinates `(100, 200)`.
294
+
295
+ ### Hook composition
296
+
297
+ The root `FabricWhiteboard` component delegates every behaviour to a dedicated hook:
298
+
299
+ | Hook | Responsibility |
300
+ |------|---------------|
301
+ | `useCanvasInit` | Mount canvas, hydrate saved state, handle resize |
302
+ | `useToolManager` | Switch drawing mode and cursors on tool change |
303
+ | `useDrawing` | Mouse-drag shape creation |
304
+ | `useEraser` | Brush cursor, trail render, object deletion |
305
+ | `useMouseHandlers` | Single event router with rAF throttle |
306
+ | `usePan` | Viewport drag and pinch zoom |
307
+ | `useZoom` | Wheel zoom, keyboard shortcuts |
308
+ | `useSelection` | Fabric selection → store, rubber-band box |
309
+ | `useTextStyle` | Live font/color updates on selected text |
310
+ | `useLiveUpdate` | Live style updates on selected objects |
311
+ | `usePersistence` | Debounced auto-save + history push |
312
+ | `useCopyPaste` | Ctrl+C / Ctrl+V clipboard |
313
+
314
+ ---
315
+
316
+ ## Directory Structure
317
+
318
+ ```
319
+ src/
320
+ ├── index.ts Public API — component, store, and types
321
+ ├── styles.css Component stylesheet (TailwindCSS, scoped)
322
+
323
+ ├── store/
324
+ │ └── whiteboard-store.ts Zustand store (tools, history, zoom, layer order)
325
+
326
+ ├── types/
327
+ │ └── canvas-node.ts TaskNodeData, DocumentNodeData, CanvasNode
328
+
329
+ ├── lib/
330
+ │ ├── fabric-utils.ts Canvas setup helpers, shape updates, welcome animation
331
+ │ ├── fabric-arrow.ts Custom Arrow shape (Line + triangle arrowhead)
332
+ │ ├── fabric-bidirectional-arrow.ts Custom BidirectionalArrow shape (arrowheads both ends)
333
+ │ ├── fabric-frame.ts Custom Frame shape (white Rect, always behind)
334
+ │ └── utils.ts cn() — clsx + tailwind-merge
335
+
336
+ ├── hooks/ Canvas behaviour hooks (consumed by whiteboard-test.tsx)
337
+ │ ├── useCanvasInit.ts Fabric canvas mount, hydration, resize
338
+ │ ├── useToolManager.ts Drawing mode + cursor switch on tool change
339
+ │ ├── useDrawing.ts Mouse-drag shape creation (rect, circle, etc.)
340
+ │ ├── useEraser.ts Eraser brush, trail, object deletion
341
+ │ ├── useMouseHandlers.ts Single event router with rAF throttle on mouse:move
342
+ │ ├── usePan.ts Viewport drag and two-finger pinch zoom
343
+ │ ├── useZoom.ts Wheel zoom, toolbar buttons, keyboard shortcuts
344
+ │ ├── useSelection.ts Fabric selection events → store + rubber-band box
345
+ │ ├── useTextStyle.ts Live font/size/color sync to selected IText
346
+ │ ├── useLiveUpdate.ts Live fill/stroke sync to selected objects
347
+ │ ├── usePersistance.ts Debounced onSave callback + undo history push
348
+ │ └── useCopyPaste.ts Ctrl+C / Ctrl+V with cascading paste offset
349
+
350
+ └── components/
351
+ ├── whiteboard/
352
+ │ └── whiteboard-test.tsx Root component — composes all hooks and layers
353
+
354
+ ├── node/ HTML overlay: draggable cards synced to canvas
355
+ │ ├── custom-node-overlay-layer.tsx Overlay orchestrator (renders all nodes)
356
+ │ ├── custom-node.tsx TaskNode — task card with status toggle
357
+ │ ├── document-node.tsx DocumentNode — document card with preview
358
+ │ ├── types/
359
+ │ │ └── overlay-types.ts Shared interfaces (Task, Document, DragState, etc.)
360
+ │ └── hooks/
361
+ │ ├── useNodeState.ts Local state + parent prop sync
362
+ │ ├── useNodeSelection.ts selectedIds set + selection guard refs
363
+ │ ├── useNodeDrag.ts Full drag lifecycle with rAF throttle
364
+ │ ├── useFabricSync.ts Fabric object:moving → HTML position delta
365
+ │ ├── useWheelZoom.ts Forwards wheel events from overlay to Fabric
366
+ │ ├── useSelectionBox.ts Rubber-band hit-test for HTML nodes
367
+ │ └── useKeyboardShortcuts.ts Ctrl+A, Escape, Delete for overlay nodes
368
+
369
+ ├── toolbar/
370
+ │ ├── whiteboard-toolbar.tsx Bottom tool bar — buttons, shortcuts, dropdowns
371
+ │ ├── tooloptions-panel.tsx Left sidebar — properties for active tool or selection
372
+ │ ├── toolbar-button.tsx Reusable icon button with tooltip
373
+ │ ├── toolbar-seperator.tsx Vertical divider between toolbar groups
374
+ │ ├── task-dropdown.tsx Task template picker (opens above toolbar)
375
+ │ ├── document-dropdown.tsx Document template picker (opens above toolbar)
376
+ │ ├── layers-control.tsx Bring/send layer order controls
377
+ │ ├── options/ Per-tool options panels
378
+ │ │ ├── pen-option.tsx Stroke color, width, dash style
379
+ │ │ ├── shape-option.tsx Fill, stroke, dash style for rect/circle/frame
380
+ │ │ ├── text-option.tsx Font family, size, weight, alignment, color
381
+ │ │ ├── image-options.tsx Opacity and filter controls for images
382
+ │ │ ├── line-options.tsx Stroke color, width, dash style for lines
383
+ │ │ └── arrow-options.tsx Stroke color, width for arrows
384
+ │ └── toolbar-skeleton/
385
+ │ └── toolbar-skeleton.tsx Animated loading skeleton matching toolbar shape
386
+
387
+ ├── ui/ Low-level Radix UI wrappers (no business logic)
388
+ │ ├── dropdown-menu.tsx Radix DropdownMenu
389
+ │ ├── label.tsx Radix Label
390
+ │ └── slider.tsx Radix Slider
391
+
392
+ └── zoomcontrol/
393
+ └── zoom-control.tsx Zoom percentage pill with +/− buttons
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Performance Design
399
+
400
+ The library is built to stay smooth at 60fps even during fast draws and large canvases.
401
+
402
+ | Pattern | Where | Why |
403
+ |---------|-------|-----|
404
+ | `requestRenderAll()` everywhere | All hooks | Defers paint to Fabric's rAF loop; never blocks the main thread |
405
+ | rAF throttle on `mouse:move` | `useMouseHandlers` | Raw pointer rate can be 200Hz; throttle to one frame |
406
+ | Props in refs, effects register once | All canvas hooks | Avoids tearing down/reattaching listeners on every render |
407
+ | `React.memo` + custom comparators | Toolbar, Overlay wrappers | Pan/zoom state changes don't re-render toolbar or node cards |
408
+ | Delta accumulator in `useFabricSync` | Node drag | Never drops position updates even when RAF guard is active |
409
+ | `requestIdleCallback` for JSON serialization | `usePersistence` | Expensive `canvas.toJSON()` runs during browser idle time |
410
+ | History capped at 50 snapshots | `whiteboard-store` | Each snapshot can be 100 KB+; cap prevents memory growth |
411
+
412
+ ---
413
+
414
+ ## Built With
415
+
416
+ | Dependency | Role |
417
+ |-----------|------|
418
+ | [Fabric.js v7](https://fabricjs.com/) | Canvas rendering engine |
419
+ | [Zustand v5](https://zustand-demo.pmnd.rs/) | Lightweight state management |
420
+ | [Radix UI](https://www.radix-ui.com/) | Accessible headless UI primitives |
421
+ | [Lucide React](https://lucide.dev/) | Icon set |
422
+ | [TailwindCSS v4](https://tailwindcss.com/) | Utility-first styling |
423
+ | [clsx](https://github.com/lukeed/clsx) + [tailwind-merge](https://github.com/dcastil/tailwind-merge) | Safe class name composition |
424
+
425
+ ---
426
+
427
+ ## License
428
+
429
+ MIT © Muhammad Hamza
@@ -1 +1 @@
1
- {"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAUrE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;;AAE5D,wBA6KE"}
1
+ {"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAUrE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;;AAE5D,wBA6KE"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // HTML overlay orchestrator — renders task and document node cards positioned in canvas world-space coordinates.
3
4
  import React, { useRef, useCallback } from "react";
4
5
  import TaskNode from "./custom-node";
5
6
  import DocumentNode from "./document-node";
@@ -1 +1 @@
1
- {"version":3,"file":"custom-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,aAAc,SAAQ,aAAa;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,KAAK,IAAI,CAAC;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,IAAQ,GACT,EAAE,aAAa,2CAoIf"}
1
+ {"version":3,"file":"custom-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,aAAc,SAAQ,aAAa;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,KAAK,IAAI,CAAC;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,IAAQ,GACT,EAAE,aAAa,2CAoIf"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // TaskNode card — draggable HTML card showing task title, status, priority, assignee, and a one-click done toggle.
3
4
  import { useState, useEffect } from "react";
4
5
  import { Check, ListCheck, Calendar, User2, AlertCircle } from "lucide-react";
5
6
  export default function TaskNode({ id, title, status, assignee, project, priority, dueDate, isSelected = false, onSelect, onDragStart, onStatusChange, zoom = 1, // Default to 1:1 scale
@@ -1 +1 @@
1
- {"version":3,"file":"document-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/document-node.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,iBAAkB,SAAQ,aAAa;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,OAAO,EACP,UAAe,EACf,OAAO,EACP,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,GACZ,EAAE,iBAAiB,2CAwHnB"}
1
+ {"version":3,"file":"document-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/document-node.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,iBAAkB,SAAQ,aAAa;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,OAAO,EACP,UAAe,EACf,OAAO,EACP,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,GACZ,EAAE,iBAAiB,2CAwHnB"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // DocumentNode card — draggable HTML card showing document title, breadcrumb path, and an expandable content preview.
3
4
  import { useState, useEffect } from "react";
4
5
  import { ChevronDown, ChevronUp, Clock, ExternalLink, MoreHorizontal, Layers } from "lucide-react";
5
6
  export default function DocumentNode({ id, title, project, breadcrumb = [], preview, updatedAt, isSelected = false, onSelect, onDragStart, }) {
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE7D,UAAU,yBAAyB;IACjC,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,yBAAyB,QAwC3B"}
1
+ {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG7D,UAAU,yBAAyB;IACjC,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,yBAAyB,QA2C3B"}
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from "react";
2
+ import { useWhiteboardStore } from "../../../store/whiteboard-store";
2
3
  /**
3
4
  * SRP: handles global keyboard shortcuts for the overlay.
4
5
  *
@@ -11,6 +12,7 @@ import { useEffect } from "react";
11
12
  * Delete/Backspace — delete selected nodes
12
13
  */
13
14
  export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selectedIdsRef, setSelectedIds, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }) {
15
+ const editable = useWhiteboardStore((s) => s.editable);
14
16
  useEffect(() => {
15
17
  const handleKeyDown = (e) => {
16
18
  if (e.target instanceof HTMLInputElement ||
@@ -29,6 +31,8 @@ export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selecte
29
31
  return;
30
32
  }
31
33
  if ((e.key === "Delete" || e.key === "Backspace") && selectedIdsRef.current.size > 0) {
34
+ if (!editable)
35
+ return;
32
36
  e.preventDefault();
33
37
  const sel = selectedIdsRef.current;
34
38
  const updatedTasks = localTasksRef.current.filter((t) => !sel.has(t.id));
@@ -42,5 +46,5 @@ export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selecte
42
46
  };
43
47
  window.addEventListener("keydown", handleKeyDown);
44
48
  return () => window.removeEventListener("keydown", handleKeyDown);
45
- }, [onTasksUpdate, onDocumentsUpdate]);
49
+ }, [onTasksUpdate, onDocumentsUpdate, editable]);
46
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,UAAU,IAAI,eAAe,EAC7B,UAAU,IAAI,eAAe,EAC9B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAA4B,MAAM,wBAAwB,CAAC;AAEvF,UAAU,gBAAgB;IACxB,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,yBAAyB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrD,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB;;iBACmC,MAAM,EAAE;;0BAcrD,MAAM,KAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;8BAWzC,MAAM,KAAK,eAAe,GAAG,eAAe;EAqLxD"}
1
+ {"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,UAAU,IAAI,eAAe,EAC7B,UAAU,IAAI,eAAe,EAC9B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAA4B,MAAM,wBAAwB,CAAC;AAGvF,UAAU,gBAAgB;IACxB,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,yBAAyB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrD,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB;;iBAEmC,MAAM,EAAE;;0BAcrD,MAAM,KAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;8BAWzC,MAAM,KAAK,eAAe,GAAG,eAAe;EAsLxD"}
@@ -1,4 +1,5 @@
1
1
  import { useState, useRef, useCallback, useEffect } from "react";
2
+ import { useWhiteboardStore } from "../../../store/whiteboard-store";
2
3
  function readPointer(e) {
3
4
  if ("touches" in e && e.touches.length > 0) {
4
5
  return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };
@@ -22,6 +23,7 @@ function readPointer(e) {
22
23
  * matrix-decomposition vs matrix-multiplication divergence.
23
24
  */
24
25
  export function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, htmlNodesSelectedByBoxRef, localTasksRef, localDocumentsRef, fabricCanvas, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }) {
26
+ const editable = useWhiteboardStore((s) => s.editable);
25
27
  const [dragging, setDragging] = useState(null);
26
28
  const dragStateRef = useRef({
27
29
  isDragging: false,
@@ -42,6 +44,8 @@ export function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, htmlNodesSelec
42
44
  return undefined;
43
45
  }, [localTasksRef, localDocumentsRef]);
44
46
  const handleDragStart = useCallback((itemId, e) => {
47
+ if (!editable)
48
+ return;
45
49
  const canvas = fabricCanvas?.current;
46
50
  if (!canvas)
47
51
  return;
@@ -119,7 +123,7 @@ export function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, htmlNodesSelec
119
123
  document.body.style.cursor = "grabbing";
120
124
  document.body.style.userSelect = "none";
121
125
  document.body.style.touchAction = "none";
122
- }, [fabricCanvas, selectedIdsRef, dragSelectedIdsRef, getItemPosition]);
126
+ }, [fabricCanvas, selectedIdsRef, dragSelectedIdsRef, getItemPosition, editable]);
123
127
  // Attach move/end listeners only while a drag is active (SRP for side effects).
124
128
  useEffect(() => {
125
129
  if (!dragging)
@@ -1 +1 @@
1
- {"version":3,"file":"useNodeState.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;;;;;;;EAoBhE"}
1
+ {"version":3,"file":"useNodeState.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;;;;;;;EAoBhE"}
@@ -1,3 +1,4 @@
1
+ // Owns local task/document state and keeps it in sync with parent props; exposes stable refs for sibling hooks.
1
2
  import { useState, useEffect, useRef } from "react";
2
3
  /**
3
4
  * SRP: owns local task/document state and keeps it in sync with parent props.
@@ -1 +1 @@
1
- {"version":3,"file":"overlay-types.d.ts","sourceRoot":"","sources":["../../../../src/components/node/types/overlay-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"overlay-types.d.ts","sourceRoot":"","sources":["../../../../src/components/node/types/overlay-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1,2 @@
1
+ // Shared TypeScript interfaces for the HTML canvas overlay: node data shapes, drag state, and component prop contracts.
1
2
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"document-dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/document-dropdown.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,qBAAqB;IAC7B,aAAa,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkDD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,aAAa,EACb,kBAAuB,EACvB,aAAa,GACd,EAAE,qBAAqB,2CAoGvB"}
1
+ {"version":3,"file":"document-dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/document-dropdown.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,qBAAqB;IAC7B,aAAa,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkDD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,aAAa,EACb,kBAAuB,EACvB,aAAa,GACd,EAAE,qBAAqB,2CAoGvB"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Document picker dropdown — lists document templates passed by the consumer; clicking one places it on the canvas as a DocumentNode.
3
4
  import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu";
4
5
  import { FileText, Plus, Clock, Layers } from "lucide-react";
5
6
  import { useWhiteboardStore } from "../../store/whiteboard-store";
@@ -1 +1 @@
1
- {"version":3,"file":"layers-control.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/layers-control.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,UAAU,kBAAkB;IAC1B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,2CAyEzE"}
1
+ {"version":3,"file":"layers-control.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/layers-control.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,UAAU,kBAAkB;IAC1B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,2CAyEzE"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Layer ordering controls — bring to front/back and step forward/backward for the currently selected Fabric object.
3
4
  import { ArrowUp, ArrowDown, ChevronsUp, ChevronsDown } from "lucide-react";
4
5
  import { useWhiteboardStore } from "../../store/whiteboard-store";
5
6
  export default function LayerControls({ fabricCanvas }) {
@@ -1 +1 @@
1
- {"version":3,"file":"arrow-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/arrow-options.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAuB,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAehC,UAAU,iBAAiB;IACzB,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzC;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,iBAAiB,2CAiNvE"}
1
+ {"version":3,"file":"arrow-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/arrow-options.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAuB,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAehC,UAAU,iBAAiB;IACzB,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzC;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,iBAAiB,2CAiNvE"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Arrow tool options panel — stroke color, stroke width, and dash style for the arrow drawing tool.
3
4
  import { useWhiteboardStore } from "../../../store/whiteboard-store";
4
5
  import { cn } from "../../../lib/utils";
5
6
  import { useEffect, useState } from "react";
@@ -1 +1 @@
1
- {"version":3,"file":"erase-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/erase-option.tsx"],"names":[],"mappings":"AAMA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAgEpC"}
1
+ {"version":3,"file":"erase-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/erase-option.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAgEpC"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Eraser tool options panel — brush size control for the eraser tool.
3
4
  import { useWhiteboardStore } from "../../../store/whiteboard-store";
4
5
  const PRESET_SIZES = [10, 20, 30, 50];
5
6
  export default function EraserOptions() {
@@ -1 +1 @@
1
- {"version":3,"file":"image-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/image-options.tsx"],"names":[],"mappings":"AAMA,MAAM,CAAC,OAAO,UAAU,YAAY,4CA8CnC"}
1
+ {"version":3,"file":"image-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/image-options.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,YAAY,4CA8CnC"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Image tool options panel — opacity slider and filter controls for placed FabricImage objects.
3
4
  import { useWhiteboardStore } from "../../../store/whiteboard-store";
4
5
  import { Label } from "../../../components/ui/label";
5
6
  import { Slider } from "../../../components/ui/slider";