@pixerate/editor-svelte 0.15.3 → 0.16.1

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 (150) hide show
  1. package/README.md +83 -0
  2. package/dist/BubbleMenu.svelte.d.ts +14 -0
  3. package/dist/BubbleMenu.svelte.d.ts.map +1 -0
  4. package/{src → dist}/EditableTextNodeEditor.svelte +1 -1
  5. package/dist/EditableTextNodeEditor.svelte.d.ts +26 -0
  6. package/dist/EditableTextNodeEditor.svelte.d.ts.map +1 -0
  7. package/dist/TemplateRenderer.svelte.d.ts +14 -0
  8. package/dist/TemplateRenderer.svelte.d.ts.map +1 -0
  9. package/dist/canvas/AgentPresenceLayer.svelte.d.ts +9 -0
  10. package/dist/canvas/AgentPresenceLayer.svelte.d.ts.map +1 -0
  11. package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts +9 -0
  12. package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts.map +1 -0
  13. package/dist/canvas/createCanvasClipboard.svelte.d.ts +32 -0
  14. package/dist/canvas/createCanvasClipboard.svelte.d.ts.map +1 -0
  15. package/dist/canvas/createCanvasClipboard.svelte.js +171 -0
  16. package/dist/canvas/createCanvasDisplacement.d.ts +32 -0
  17. package/dist/canvas/createCanvasDisplacement.d.ts.map +1 -0
  18. package/dist/canvas/createCanvasDisplacement.js +170 -0
  19. package/dist/canvas/createCanvasDocking.svelte.d.ts +56 -0
  20. package/dist/canvas/createCanvasDocking.svelte.d.ts.map +1 -0
  21. package/dist/canvas/createCanvasDocking.svelte.js +227 -0
  22. package/dist/canvas/createCanvasExplosion.svelte.d.ts +31 -0
  23. package/dist/canvas/createCanvasExplosion.svelte.d.ts.map +1 -0
  24. package/dist/canvas/createCanvasExplosion.svelte.js +346 -0
  25. package/dist/canvas/createCanvasGraph.svelte.d.ts +62 -0
  26. package/dist/canvas/createCanvasGraph.svelte.d.ts.map +1 -0
  27. package/dist/canvas/createCanvasGraph.svelte.js +223 -0
  28. package/dist/canvas/createCanvasInteractions.svelte.d.ts +28 -0
  29. package/dist/canvas/createCanvasInteractions.svelte.d.ts.map +1 -0
  30. package/dist/canvas/createCanvasInteractions.svelte.js +95 -0
  31. package/dist/canvas/createCanvasLayout.d.ts +22 -0
  32. package/dist/canvas/createCanvasLayout.d.ts.map +1 -0
  33. package/dist/canvas/createCanvasLayout.js +76 -0
  34. package/dist/canvas/createCanvasMultiDrag.svelte.d.ts +61 -0
  35. package/dist/canvas/createCanvasMultiDrag.svelte.d.ts.map +1 -0
  36. package/dist/canvas/createCanvasMultiDrag.svelte.js +105 -0
  37. package/dist/canvas/createCanvasNodeSync.svelte.d.ts +58 -0
  38. package/dist/canvas/createCanvasNodeSync.svelte.d.ts.map +1 -0
  39. package/dist/canvas/createCanvasNodeSync.svelte.js +82 -0
  40. package/dist/canvas/createCanvasSelection.svelte.d.ts +26 -0
  41. package/dist/canvas/createCanvasSelection.svelte.d.ts.map +1 -0
  42. package/dist/canvas/createCanvasSelection.svelte.js +26 -0
  43. package/dist/canvas/createCanvasShortcuts.d.ts +9 -0
  44. package/dist/canvas/createCanvasShortcuts.d.ts.map +1 -0
  45. package/dist/canvas/createCanvasShortcuts.js +97 -0
  46. package/dist/canvas/createCanvasTrajectory.d.ts +31 -0
  47. package/dist/canvas/createCanvasTrajectory.d.ts.map +1 -0
  48. package/dist/canvas/createCanvasTrajectory.js +141 -0
  49. package/dist/canvas/createStreamingCanvas.svelte.d.ts +19 -0
  50. package/dist/canvas/createStreamingCanvas.svelte.d.ts.map +1 -0
  51. package/dist/canvas/createStreamingCanvas.svelte.js +32 -0
  52. package/dist/canvas/index.d.ts +18 -688
  53. package/dist/canvas/index.d.ts.map +1 -0
  54. package/dist/canvas/index.js +34 -1839
  55. package/dist/canvas/types.d.ts +183 -0
  56. package/dist/canvas/types.d.ts.map +1 -0
  57. package/dist/canvas/types.js +1 -0
  58. package/dist/createNavigationGuard.svelte.d.ts +24 -0
  59. package/dist/createNavigationGuard.svelte.d.ts.map +1 -0
  60. package/dist/createNavigationGuard.svelte.js +65 -0
  61. package/dist/editor.svelte.d.ts +23 -0
  62. package/dist/editor.svelte.d.ts.map +1 -0
  63. package/dist/editor.svelte.js +41 -0
  64. package/dist/history/createHistoryShortcuts.d.ts +24 -0
  65. package/dist/history/createHistoryShortcuts.d.ts.map +1 -0
  66. package/dist/history/createHistoryShortcuts.js +57 -0
  67. package/dist/history/history.svelte.d.ts +18 -0
  68. package/dist/history/history.svelte.d.ts.map +1 -0
  69. package/dist/history/history.svelte.js +43 -0
  70. package/dist/history/index.d.ts +3 -0
  71. package/dist/history/index.d.ts.map +1 -0
  72. package/dist/history/index.js +2 -0
  73. package/{src → dist}/image-editor/ImageEditor.svelte +1 -1
  74. package/dist/image-editor/ImageEditor.svelte.d.ts +17 -0
  75. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -0
  76. package/dist/image-editor/imageEditorState.svelte.d.ts +50 -0
  77. package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -0
  78. package/dist/image-editor/imageEditorState.svelte.js +132 -0
  79. package/dist/image-editor/index.d.ts +6 -16
  80. package/dist/image-editor/index.d.ts.map +1 -0
  81. package/dist/image-editor/index.js +2 -9
  82. package/dist/index.d.ts +20 -249
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +10 -1119
  85. package/dist/navigationGuard.d.ts +21 -0
  86. package/dist/navigationGuard.d.ts.map +1 -0
  87. package/dist/navigationGuard.js +59 -0
  88. package/{src → dist}/spreadsheet/FormulaBar.svelte +1 -1
  89. package/dist/spreadsheet/FormulaBar.svelte.d.ts +17 -0
  90. package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -0
  91. package/{src → dist}/spreadsheet/SpreadsheetEditor.svelte +1 -1
  92. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +22 -0
  93. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -0
  94. package/dist/spreadsheet/index.d.ts +9 -0
  95. package/dist/spreadsheet/index.d.ts.map +1 -0
  96. package/{src/spreadsheet/index.ts → dist/spreadsheet/index.js} +1 -1
  97. package/dist/spreadsheet/spreadsheet.svelte.d.ts +33 -0
  98. package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -0
  99. package/dist/spreadsheet/spreadsheet.svelte.js +160 -0
  100. package/package.json +14 -16
  101. package/dist/canvas/index.js.map +0 -1
  102. package/dist/chunk-HEOI4UNF.js +0 -1191
  103. package/dist/chunk-HEOI4UNF.js.map +0 -1
  104. package/dist/image-editor/index.js.map +0 -1
  105. package/dist/index.css +0 -50
  106. package/dist/index.css.map +0 -1
  107. package/dist/index.js.map +0 -1
  108. package/src/canvas/__tests__/canvas-clipboard.test.ts +0 -154
  109. package/src/canvas/__tests__/canvas-docking.test.ts +0 -288
  110. package/src/canvas/__tests__/canvas-explosion.test.ts +0 -634
  111. package/src/canvas/__tests__/canvas-graph.test.ts +0 -159
  112. package/src/canvas/__tests__/canvas-interactions.test.ts +0 -86
  113. package/src/canvas/__tests__/canvas-layout.test.ts +0 -75
  114. package/src/canvas/__tests__/canvas-multi-drag.test.ts +0 -112
  115. package/src/canvas/__tests__/canvas-node-sync.svelte.test.ts +0 -104
  116. package/src/canvas/__tests__/canvas-shortcuts.test.ts +0 -59
  117. package/src/canvas/createCanvasClipboard.svelte.ts +0 -219
  118. package/src/canvas/createCanvasDisplacement.ts +0 -253
  119. package/src/canvas/createCanvasDocking.svelte.ts +0 -355
  120. package/src/canvas/createCanvasExplosion.svelte.ts +0 -457
  121. package/src/canvas/createCanvasGraph.svelte.ts +0 -330
  122. package/src/canvas/createCanvasInteractions.svelte.ts +0 -112
  123. package/src/canvas/createCanvasLayout.ts +0 -129
  124. package/src/canvas/createCanvasMultiDrag.svelte.ts +0 -175
  125. package/src/canvas/createCanvasNodeSync.svelte.ts +0 -150
  126. package/src/canvas/createCanvasSelection.svelte.ts +0 -51
  127. package/src/canvas/createCanvasShortcuts.ts +0 -99
  128. package/src/canvas/createCanvasTrajectory.ts +0 -207
  129. package/src/canvas/createStreamingCanvas.svelte.ts +0 -56
  130. package/src/canvas/index.d.ts +0 -688
  131. package/src/canvas/index.ts +0 -126
  132. package/src/canvas/types.ts +0 -223
  133. package/src/createNavigationGuard.svelte.ts +0 -97
  134. package/src/editor.svelte.ts +0 -65
  135. package/src/history/createHistoryShortcuts.ts +0 -95
  136. package/src/history/history.svelte.ts +0 -68
  137. package/src/history/index.ts +0 -2
  138. package/src/image-editor/imageEditorState.svelte.ts +0 -151
  139. package/src/image-editor/index.d.ts +0 -16
  140. package/src/image-editor/index.ts +0 -2
  141. package/src/index.d.ts +0 -249
  142. package/src/index.ts +0 -89
  143. package/src/navigationGuard.ts +0 -82
  144. package/src/spreadsheet/spreadsheet.svelte.ts +0 -176
  145. package/src/svelte.d.ts +0 -5
  146. /package/{src → dist}/BubbleMenu.svelte +0 -0
  147. /package/{src → dist}/TemplateRenderer.svelte +0 -0
  148. /package/{src → dist}/canvas/AgentPresenceLayer.svelte +0 -0
  149. /package/{src → dist}/canvas/FloatingHorizontalScrollbar.svelte +0 -0
  150. /package/{src → dist}/canvas/README.md +0 -0
@@ -1,154 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import {
3
- createCanvasClipboard,
4
- isValidCanvasNode,
5
- isValidUrl,
6
- isEventFromTextInput
7
- } from '../createCanvasClipboard.svelte.js';
8
- import type { CanvasNode, CanvasEdge } from '../types.js';
9
-
10
- describe('createCanvasClipboard & Clipboard Utilities', () => {
11
- it('validates canvas nodes with isValidCanvasNode', () => {
12
- expect(isValidCanvasNode({ id: '1', position: { x: 10, y: 20 } })).toBe(true);
13
- expect(isValidCanvasNode({ id: 123, position: { x: 10, y: 20 } })).toBe(false);
14
- expect(isValidCanvasNode({ id: '1' })).toBe(false);
15
- expect(isValidCanvasNode(null)).toBe(false);
16
- expect(isValidCanvasNode('not a node')).toBe(false);
17
- });
18
-
19
- it('validates URLs with isValidUrl', () => {
20
- expect(isValidUrl('https://example.com')).toBe(true);
21
- expect(isValidUrl('http://localhost:5173/canvas')).toBe(true);
22
- expect(isValidUrl('not a url')).toBe(false);
23
- expect(isValidUrl('ftp://example.com')).toBe(false);
24
- });
25
-
26
- it('detects text input targets with isEventFromTextInput', () => {
27
- const inputEl = { tagName: 'INPUT' } as unknown as HTMLElement;
28
- const textareaEl = { tagName: 'TEXTAREA' } as unknown as HTMLElement;
29
- const contentEditableEl = { isContentEditable: true, tagName: 'DIV' } as unknown as HTMLElement;
30
- const canvasEl = { tagName: 'DIV', isContentEditable: false } as unknown as HTMLElement;
31
-
32
- expect(isEventFromTextInput({ target: inputEl })).toBe(true);
33
- expect(isEventFromTextInput({ target: textareaEl })).toBe(true);
34
- expect(isEventFromTextInput({ target: contentEditableEl })).toBe(true);
35
- expect(isEventFromTextInput({ target: canvasEl })).toBe(false);
36
- expect(isEventFromTextInput({ target: null })).toBe(false);
37
- });
38
-
39
- it('copies selected nodes into clipboardData JSON payload', () => {
40
- const clipboard = createCanvasClipboard();
41
- const mockClipboardData = {
42
- setData: vi.fn()
43
- };
44
- const mockEvent = {
45
- target: { tagName: 'DIV' },
46
- clipboardData: mockClipboardData,
47
- preventDefault: vi.fn()
48
- } as unknown as ClipboardEvent;
49
-
50
- const selectedNodes: CanvasNode[] = [{ id: 'n1', position: { x: 10, y: 20 }, data: {} }];
51
- const connectedEdges: CanvasEdge[] = [{ id: 'e1', source: 'n1', target: 'n2' }];
52
-
53
- const copied = clipboard.copySelectedNodes(selectedNodes, connectedEdges, mockEvent);
54
- expect(copied).toBe(true);
55
- expect(mockClipboardData.setData).toHaveBeenCalledWith(
56
- 'application/json',
57
- expect.stringContaining('"id":"n1"')
58
- );
59
- expect(mockEvent.preventDefault).toHaveBeenCalled();
60
- });
61
-
62
- it('cuts selected nodes by calling onRemove callback', () => {
63
- const clipboard = createCanvasClipboard();
64
- const onRemove = vi.fn();
65
- const mockEvent = {
66
- target: { tagName: 'DIV' },
67
- clipboardData: { setData: vi.fn() },
68
- preventDefault: vi.fn()
69
- } as unknown as ClipboardEvent;
70
-
71
- const selectedNodes: CanvasNode[] = [{ id: 'n1', position: { x: 0, y: 0 }, data: {} }];
72
-
73
- const cut = clipboard.cutSelectedNodes(selectedNodes, undefined, onRemove, mockEvent);
74
- expect(cut).toBe(true);
75
- expect(onRemove).toHaveBeenCalledWith(selectedNodes);
76
- });
77
-
78
- it('handles paste of serialized node JSON with UUID remapping and offset', async () => {
79
- const onPasteNodes = vi.fn();
80
- const clipboard = createCanvasClipboard({ onPasteNodes });
81
-
82
- const originalNodes: CanvasNode[] = [
83
- { id: 'orig-1', position: { x: 100, y: 100 }, data: { label: 'Node 1' } },
84
- { id: 'orig-2', position: { x: 200, y: 200 }, data: { label: 'Node 2' } }
85
- ];
86
- const originalEdges: CanvasEdge[] = [{ id: 'e-orig', source: 'orig-1', target: 'orig-2' }];
87
-
88
- const serializedPayload = JSON.stringify({
89
- version: 1,
90
- nodes: originalNodes,
91
- edges: originalEdges
92
- });
93
-
94
- const mockEvent = {
95
- target: { tagName: 'DIV' },
96
- clipboardData: {
97
- getData: (format: string) => (format === 'application/json' ? serializedPayload : ''),
98
- files: []
99
- },
100
- preventDefault: vi.fn()
101
- } as unknown as ClipboardEvent;
102
-
103
- const handled = await clipboard.handlePasteEvent(mockEvent);
104
- expect(handled).toBe(true);
105
- expect(onPasteNodes).toHaveBeenCalledTimes(1);
106
-
107
- const [pastedNodes, pastedEdges] = onPasteNodes.mock.calls[0];
108
- expect(pastedNodes).toHaveLength(2);
109
- expect(pastedNodes[0].id).not.toBe('orig-1');
110
- expect(pastedNodes[0].position.x).toBe(140); // 100 + 40
111
- expect(pastedNodes[0].position.y).toBe(140); // 100 + 40
112
-
113
- // Check edge remapping
114
- expect(pastedEdges).toHaveLength(1);
115
- expect(pastedEdges[0].source).toBe(pastedNodes[0].id);
116
- expect(pastedEdges[0].target).toBe(pastedNodes[1].id);
117
- });
118
-
119
- it('handles paste of plain text URLs', async () => {
120
- const onPasteUrl = vi.fn();
121
- const clipboard = createCanvasClipboard({ onPasteUrl });
122
-
123
- const mockEvent = {
124
- target: { tagName: 'DIV' },
125
- clipboardData: {
126
- getData: (format: string) => (format === 'text/plain' ? 'https://example.com/asset' : ''),
127
- files: []
128
- },
129
- preventDefault: vi.fn()
130
- } as unknown as ClipboardEvent;
131
-
132
- const handled = await clipboard.handlePasteEvent(mockEvent);
133
- expect(handled).toBe(true);
134
- expect(onPasteUrl).toHaveBeenCalledWith('https://example.com/asset');
135
- });
136
-
137
- it('handles paste of plain text', async () => {
138
- const onPasteText = vi.fn();
139
- const clipboard = createCanvasClipboard({ onPasteText });
140
-
141
- const mockEvent = {
142
- target: { tagName: 'DIV' },
143
- clipboardData: {
144
- getData: (format: string) => (format === 'text/plain' ? 'Sample text content' : ''),
145
- files: []
146
- },
147
- preventDefault: vi.fn()
148
- } as unknown as ClipboardEvent;
149
-
150
- const handled = await clipboard.handlePasteEvent(mockEvent);
151
- expect(handled).toBe(true);
152
- expect(onPasteText).toHaveBeenCalledWith('Sample text content');
153
- });
154
- });
@@ -1,288 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { createCanvasDocking } from '../createCanvasDocking.svelte.js';
3
- import type { CanvasNode } from '../types.js';
4
-
5
- describe('createCanvasDocking', () => {
6
- const createSampleNode = (id: string, type = 'default', x = 0, y = 0): CanvasNode => ({
7
- id,
8
- type,
9
- position: { x, y },
10
- data: {}
11
- });
12
-
13
- it('initializes with null drag and connect states', () => {
14
- const docking = createCanvasDocking();
15
-
16
- expect(docking.isDragging).toBe(false);
17
- expect(docking.draggedNode).toBeNull();
18
- expect(docking.draggedInitialPosition).toBeNull();
19
- expect(docking.connectingHandle).toBe('');
20
- expect(docking.connectingNode).toBeNull();
21
- });
22
-
23
- it('tracks drag start, records initial position, and sets isDragging', () => {
24
- const docking = createCanvasDocking();
25
- const node = createSampleNode('n1', 'default', 100, 200);
26
-
27
- docking.handleNodeDragStart({ targetNode: node });
28
-
29
- expect(docking.isDragging).toBe(true);
30
- expect(docking.draggedNode).toBe(node);
31
- expect(docking.draggedInitialPosition).toEqual({ x: 100, y: 200 });
32
- });
33
-
34
- it('fires onDockHover and onDockLeave as intersecting dock targets change', () => {
35
- const onDockHover = vi.fn();
36
- const onDockLeave = vi.fn();
37
- const docking = createCanvasDocking({
38
- isDockTarget: (n) => n.type === 'dockArea',
39
- onDockHover,
40
- onDockLeave
41
- });
42
-
43
- const draggedNode = createSampleNode('dragged', 'item', 50, 50);
44
- const dockNode1 = createSampleNode('dock-1', 'dockArea', 0, 0);
45
- const dockNode2 = createSampleNode('dock-2', 'dockArea', 200, 200);
46
- const normalNode = createSampleNode('normal', 'item', 50, 50);
47
-
48
- docking.handleNodeDragStart({ targetNode: draggedNode });
49
-
50
- // Drag intersecting with dockNode1 and a non-dock normalNode
51
- docking.handleNodeDrag(
52
- { targetNode: draggedNode },
53
- () => [dockNode1, normalNode]
54
- );
55
-
56
- expect(onDockHover).toHaveBeenCalledTimes(1);
57
- expect(onDockHover).toHaveBeenCalledWith(dockNode1, draggedNode);
58
- expect(onDockLeave).not.toHaveBeenCalled();
59
-
60
- // Drag moves over dockNode2, leaving dockNode1
61
- docking.handleNodeDrag(
62
- { targetNode: draggedNode },
63
- () => [dockNode2]
64
- );
65
-
66
- expect(onDockLeave).toHaveBeenCalledTimes(1);
67
- expect(onDockLeave).toHaveBeenCalledWith(expect.objectContaining({ id: 'dock-1' }), draggedNode);
68
- expect(onDockHover).toHaveBeenCalledTimes(2);
69
- expect(onDockHover).toHaveBeenLastCalledWith(dockNode2, draggedNode);
70
- });
71
-
72
- it('fires onDockDrop and restores position on dock stop', () => {
73
- const onDockDrop = vi.fn();
74
- const onSave = vi.fn();
75
- const updateNodePosition = vi.fn();
76
-
77
- const docking = createCanvasDocking({
78
- isDockTarget: (n) => n.type === 'dockArea',
79
- restorePositionOnDock: true,
80
- onDockDrop,
81
- onSave
82
- });
83
-
84
- const draggedNode = createSampleNode('dragged', 'item', 100, 100);
85
- const dockNode = createSampleNode('dock', 'dockArea', 0, 0);
86
-
87
- docking.handleNodeDragStart({ targetNode: draggedNode });
88
-
89
- // Node was dragged to new position
90
- draggedNode.position = { x: 300, y: 300 };
91
-
92
- docking.handleNodeDragStop(
93
- { targetNode: draggedNode },
94
- () => [dockNode],
95
- updateNodePosition
96
- );
97
-
98
- expect(onDockDrop).toHaveBeenCalledWith(dockNode, draggedNode);
99
- expect(updateNodePosition).toHaveBeenCalledWith('dragged', { x: 100, y: 100 });
100
- expect(onSave).toHaveBeenCalled();
101
- expect(docking.isDragging).toBe(false);
102
- expect(docking.draggedNode).toBeNull();
103
- });
104
-
105
- it('tracks handle connect start and end', () => {
106
- const docking = createCanvasDocking();
107
- const node = createSampleNode('source-node');
108
- const getNode = (id: string) => (id === 'source-node' ? node : undefined);
109
-
110
- docking.handleConnectStart(
111
- { nodeId: 'source-node', handleId: 'h1', handleType: 'source' },
112
- getNode
113
- );
114
-
115
- expect(docking.connectingHandle).toBe('source');
116
- expect(docking.connectingNode).toBe(node);
117
-
118
- docking.handleConnectEnd();
119
-
120
- expect(docking.connectingHandle).toBe('');
121
- expect(docking.connectingNode).toBeNull();
122
- });
123
-
124
- it('resets all state cleanly', () => {
125
- const docking = createCanvasDocking();
126
- const node = createSampleNode('test');
127
-
128
- docking.handleNodeDragStart({ targetNode: node });
129
- expect(docking.isDragging).toBe(true);
130
-
131
- docking.reset();
132
- expect(docking.isDragging).toBe(false);
133
- expect(docking.draggedNode).toBeNull();
134
- expect(docking.draggedInitialPosition).toBeNull();
135
- });
136
-
137
- describe('Dock Target Disambiguation & Exclusive Hover', () => {
138
- it('disambiguates adjacent targets using default center-point strategy and exclusive hover', () => {
139
- const onDockHover = vi.fn();
140
- const onDockLeave = vi.fn();
141
- const onDockDrop = vi.fn();
142
-
143
- const docking = createCanvasDocking({
144
- isDockTarget: (n) => n.type === 'dockArea',
145
- onDockHover,
146
- onDockLeave,
147
- onDockDrop
148
- });
149
-
150
- // Adjacent columns: dockA [0, 200] and dockB [200, 400]
151
- const dockA = {
152
- ...createSampleNode('dock-a', 'dockArea', 0, 0),
153
- measured: { width: 200, height: 400 }
154
- };
155
- const dockB = {
156
- ...createSampleNode('dock-b', 'dockArea', 200, 0),
157
- measured: { width: 200, height: 400 }
158
- };
159
-
160
- // Dragged card with width 100, positioned at x: 50 -> center is x: 100 (inside dockA)
161
- const draggedNode = {
162
- ...createSampleNode('dragged', 'item', 50, 50),
163
- measured: { width: 100, height: 50 }
164
- };
165
-
166
- docking.handleNodeDragStart({ targetNode: draggedNode });
167
-
168
- // Even if xyflow returns both intersecting targets
169
- docking.handleNodeDrag(
170
- { targetNode: draggedNode },
171
- () => [dockA, dockB]
172
- );
173
-
174
- // Only dockA receives hover
175
- expect(onDockHover).toHaveBeenCalledTimes(1);
176
- expect(onDockHover).toHaveBeenCalledWith(dockA, draggedNode);
177
- expect(onDockLeave).not.toHaveBeenCalled();
178
-
179
- // Card dragged across boundary: x: 160 -> center is x: 210 (inside dockB)
180
- draggedNode.position = { x: 160, y: 50 };
181
- docking.handleNodeDrag(
182
- { targetNode: draggedNode },
183
- () => [dockA, dockB]
184
- );
185
-
186
- // dockA is left, dockB is entered
187
- expect(onDockLeave).toHaveBeenCalledTimes(1);
188
- expect(onDockLeave).toHaveBeenCalledWith(expect.objectContaining({ id: 'dock-a' }), draggedNode);
189
- expect(onDockHover).toHaveBeenCalledTimes(2);
190
- expect(onDockHover).toHaveBeenLastCalledWith(dockB, draggedNode);
191
-
192
- // Dropping selects dockB, not dockA
193
- docking.handleNodeDragStop(
194
- { targetNode: draggedNode },
195
- () => [dockA, dockB]
196
- );
197
-
198
- expect(onDockDrop).toHaveBeenCalledTimes(1);
199
- expect(onDockDrop).toHaveBeenCalledWith(dockB, draggedNode);
200
- });
201
-
202
- it('disambiguates candidate targets using max-overlap strategy', () => {
203
- const onDockHover = vi.fn();
204
- const onDockDrop = vi.fn();
205
-
206
- const docking = createCanvasDocking({
207
- isDockTarget: (n) => n.type === 'dockArea',
208
- dockStrategy: 'max-overlap',
209
- onDockHover,
210
- onDockDrop
211
- });
212
-
213
- // dockA: [0, 100] (area overlap will be 20 * 50 = 1000)
214
- const dockA = {
215
- ...createSampleNode('dock-a', 'dockArea', 0, 0),
216
- measured: { width: 100, height: 200 }
217
- };
218
- // dockB: [100, 300] (area overlap will be 80 * 50 = 4000)
219
- const dockB = {
220
- ...createSampleNode('dock-b', 'dockArea', 100, 0),
221
- measured: { width: 200, height: 200 }
222
- };
223
-
224
- // Dragged card spanning x: 80 to 180 (width 100)
225
- const draggedNode = {
226
- ...createSampleNode('dragged', 'item', 80, 50),
227
- measured: { width: 100, height: 50 }
228
- };
229
-
230
- docking.handleNodeDragStart({ targetNode: draggedNode });
231
- docking.handleNodeDrag({ targetNode: draggedNode }, () => [dockA, dockB]);
232
-
233
- // dockB has the larger overlap area (80px vs 20px width)
234
- expect(onDockHover).toHaveBeenCalledWith(dockB, draggedNode);
235
-
236
- docking.handleNodeDragStop({ targetNode: draggedNode }, () => [dockA, dockB]);
237
- expect(onDockDrop).toHaveBeenCalledWith(dockB, draggedNode);
238
- });
239
-
240
- it('allows custom resolvePrimaryDockTarget callback to take precedence', () => {
241
- const onDockHover = vi.fn();
242
- const onDockDrop = vi.fn();
243
-
244
- const dockA = createSampleNode('dock-a', 'dockArea', 0, 0);
245
- const dockB = createSampleNode('dock-b', 'dockArea', 100, 0);
246
- const draggedNode = createSampleNode('dragged', 'item', 0, 0);
247
-
248
- const docking = createCanvasDocking({
249
- isDockTarget: (n) => n.type === 'dockArea',
250
- resolvePrimaryDockTarget: (targets) => targets.find((t) => t.id === 'dock-b') ?? null,
251
- onDockHover,
252
- onDockDrop
253
- });
254
-
255
- docking.handleNodeDragStart({ targetNode: draggedNode });
256
- docking.handleNodeDrag({ targetNode: draggedNode }, () => [dockA, dockB]);
257
-
258
- expect(onDockHover).toHaveBeenCalledWith(dockB, draggedNode);
259
-
260
- docking.handleNodeDragStop({ targetNode: draggedNode }, () => [dockA, dockB]);
261
- expect(onDockDrop).toHaveBeenCalledWith(dockB, draggedNode);
262
- });
263
-
264
- it('supports non-exclusive hover mode when exclusiveHover is false', () => {
265
- const onDockHover = vi.fn();
266
- const onDockLeave = vi.fn();
267
-
268
- const docking = createCanvasDocking({
269
- isDockTarget: (n) => n.type === 'dockArea',
270
- exclusiveHover: false,
271
- onDockHover,
272
- onDockLeave
273
- });
274
-
275
- const dockA = createSampleNode('dock-a', 'dockArea', 0, 0);
276
- const dockB = createSampleNode('dock-b', 'dockArea', 100, 0);
277
- const draggedNode = createSampleNode('dragged', 'item', 50, 0);
278
-
279
- docking.handleNodeDragStart({ targetNode: draggedNode });
280
- docking.handleNodeDrag({ targetNode: draggedNode }, () => [dockA, dockB]);
281
-
282
- // Both targets should receive hover in non-exclusive mode
283
- expect(onDockHover).toHaveBeenCalledTimes(2);
284
- expect(onDockHover).toHaveBeenCalledWith(dockA, draggedNode);
285
- expect(onDockHover).toHaveBeenCalledWith(dockB, draggedNode);
286
- });
287
- });
288
- });