@pixerate/editor-svelte 0.15.2 → 0.16.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 (149) hide show
  1. package/dist/BubbleMenu.svelte.d.ts +14 -0
  2. package/dist/BubbleMenu.svelte.d.ts.map +1 -0
  3. package/{src → dist}/EditableTextNodeEditor.svelte +1 -1
  4. package/dist/EditableTextNodeEditor.svelte.d.ts +26 -0
  5. package/dist/EditableTextNodeEditor.svelte.d.ts.map +1 -0
  6. package/dist/TemplateRenderer.svelte.d.ts +14 -0
  7. package/dist/TemplateRenderer.svelte.d.ts.map +1 -0
  8. package/dist/canvas/AgentPresenceLayer.svelte.d.ts +9 -0
  9. package/dist/canvas/AgentPresenceLayer.svelte.d.ts.map +1 -0
  10. package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts +9 -0
  11. package/dist/canvas/FloatingHorizontalScrollbar.svelte.d.ts.map +1 -0
  12. package/dist/canvas/createCanvasClipboard.svelte.d.ts +32 -0
  13. package/dist/canvas/createCanvasClipboard.svelte.d.ts.map +1 -0
  14. package/dist/canvas/createCanvasClipboard.svelte.js +171 -0
  15. package/dist/canvas/createCanvasDisplacement.d.ts +32 -0
  16. package/dist/canvas/createCanvasDisplacement.d.ts.map +1 -0
  17. package/dist/canvas/createCanvasDisplacement.js +170 -0
  18. package/dist/canvas/createCanvasDocking.svelte.d.ts +56 -0
  19. package/dist/canvas/createCanvasDocking.svelte.d.ts.map +1 -0
  20. package/dist/canvas/createCanvasDocking.svelte.js +227 -0
  21. package/dist/canvas/createCanvasExplosion.svelte.d.ts +31 -0
  22. package/dist/canvas/createCanvasExplosion.svelte.d.ts.map +1 -0
  23. package/dist/canvas/createCanvasExplosion.svelte.js +346 -0
  24. package/dist/canvas/createCanvasGraph.svelte.d.ts +62 -0
  25. package/dist/canvas/createCanvasGraph.svelte.d.ts.map +1 -0
  26. package/dist/canvas/createCanvasGraph.svelte.js +223 -0
  27. package/dist/canvas/createCanvasInteractions.svelte.d.ts +28 -0
  28. package/dist/canvas/createCanvasInteractions.svelte.d.ts.map +1 -0
  29. package/dist/canvas/createCanvasInteractions.svelte.js +95 -0
  30. package/dist/canvas/createCanvasLayout.d.ts +22 -0
  31. package/dist/canvas/createCanvasLayout.d.ts.map +1 -0
  32. package/dist/canvas/createCanvasLayout.js +76 -0
  33. package/dist/canvas/createCanvasMultiDrag.svelte.d.ts +61 -0
  34. package/dist/canvas/createCanvasMultiDrag.svelte.d.ts.map +1 -0
  35. package/dist/canvas/createCanvasMultiDrag.svelte.js +105 -0
  36. package/dist/canvas/createCanvasNodeSync.svelte.d.ts +58 -0
  37. package/dist/canvas/createCanvasNodeSync.svelte.d.ts.map +1 -0
  38. package/dist/canvas/createCanvasNodeSync.svelte.js +82 -0
  39. package/dist/canvas/createCanvasSelection.svelte.d.ts +26 -0
  40. package/dist/canvas/createCanvasSelection.svelte.d.ts.map +1 -0
  41. package/dist/canvas/createCanvasSelection.svelte.js +26 -0
  42. package/dist/canvas/createCanvasShortcuts.d.ts +9 -0
  43. package/dist/canvas/createCanvasShortcuts.d.ts.map +1 -0
  44. package/dist/canvas/createCanvasShortcuts.js +97 -0
  45. package/dist/canvas/createCanvasTrajectory.d.ts +31 -0
  46. package/dist/canvas/createCanvasTrajectory.d.ts.map +1 -0
  47. package/dist/canvas/createCanvasTrajectory.js +141 -0
  48. package/dist/canvas/createStreamingCanvas.svelte.d.ts +19 -0
  49. package/dist/canvas/createStreamingCanvas.svelte.d.ts.map +1 -0
  50. package/dist/canvas/createStreamingCanvas.svelte.js +32 -0
  51. package/dist/canvas/index.d.ts +18 -688
  52. package/dist/canvas/index.d.ts.map +1 -0
  53. package/dist/canvas/index.js +34 -1839
  54. package/dist/canvas/types.d.ts +183 -0
  55. package/dist/canvas/types.d.ts.map +1 -0
  56. package/dist/canvas/types.js +1 -0
  57. package/dist/createNavigationGuard.svelte.d.ts +24 -0
  58. package/dist/createNavigationGuard.svelte.d.ts.map +1 -0
  59. package/dist/createNavigationGuard.svelte.js +65 -0
  60. package/dist/editor.svelte.d.ts +23 -0
  61. package/dist/editor.svelte.d.ts.map +1 -0
  62. package/dist/editor.svelte.js +41 -0
  63. package/dist/history/createHistoryShortcuts.d.ts +24 -0
  64. package/dist/history/createHistoryShortcuts.d.ts.map +1 -0
  65. package/dist/history/createHistoryShortcuts.js +57 -0
  66. package/dist/history/history.svelte.d.ts +18 -0
  67. package/dist/history/history.svelte.d.ts.map +1 -0
  68. package/dist/history/history.svelte.js +43 -0
  69. package/dist/history/index.d.ts +3 -0
  70. package/dist/history/index.d.ts.map +1 -0
  71. package/dist/history/index.js +2 -0
  72. package/{src → dist}/image-editor/ImageEditor.svelte +1 -1
  73. package/dist/image-editor/ImageEditor.svelte.d.ts +17 -0
  74. package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -0
  75. package/dist/image-editor/imageEditorState.svelte.d.ts +50 -0
  76. package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -0
  77. package/dist/image-editor/imageEditorState.svelte.js +132 -0
  78. package/dist/image-editor/index.d.ts +6 -16
  79. package/dist/image-editor/index.d.ts.map +1 -0
  80. package/dist/image-editor/index.js +2 -9
  81. package/dist/index.d.ts +20 -249
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +10 -1119
  84. package/dist/navigationGuard.d.ts +21 -0
  85. package/dist/navigationGuard.d.ts.map +1 -0
  86. package/dist/navigationGuard.js +59 -0
  87. package/{src → dist}/spreadsheet/FormulaBar.svelte +1 -1
  88. package/dist/spreadsheet/FormulaBar.svelte.d.ts +17 -0
  89. package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -0
  90. package/{src → dist}/spreadsheet/SpreadsheetEditor.svelte +1 -1
  91. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +22 -0
  92. package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -0
  93. package/dist/spreadsheet/index.d.ts +9 -0
  94. package/dist/spreadsheet/index.d.ts.map +1 -0
  95. package/{src/spreadsheet/index.ts → dist/spreadsheet/index.js} +1 -1
  96. package/dist/spreadsheet/spreadsheet.svelte.d.ts +33 -0
  97. package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -0
  98. package/dist/spreadsheet/spreadsheet.svelte.js +160 -0
  99. package/package.json +14 -16
  100. package/dist/canvas/index.js.map +0 -1
  101. package/dist/chunk-HEOI4UNF.js +0 -1191
  102. package/dist/chunk-HEOI4UNF.js.map +0 -1
  103. package/dist/image-editor/index.js.map +0 -1
  104. package/dist/index.css +0 -50
  105. package/dist/index.css.map +0 -1
  106. package/dist/index.js.map +0 -1
  107. package/src/canvas/__tests__/canvas-clipboard.test.ts +0 -154
  108. package/src/canvas/__tests__/canvas-docking.test.ts +0 -288
  109. package/src/canvas/__tests__/canvas-explosion.test.ts +0 -634
  110. package/src/canvas/__tests__/canvas-graph.test.ts +0 -159
  111. package/src/canvas/__tests__/canvas-interactions.test.ts +0 -86
  112. package/src/canvas/__tests__/canvas-layout.test.ts +0 -75
  113. package/src/canvas/__tests__/canvas-multi-drag.test.ts +0 -112
  114. package/src/canvas/__tests__/canvas-node-sync.svelte.test.ts +0 -104
  115. package/src/canvas/__tests__/canvas-shortcuts.test.ts +0 -59
  116. package/src/canvas/createCanvasClipboard.svelte.ts +0 -219
  117. package/src/canvas/createCanvasDisplacement.ts +0 -253
  118. package/src/canvas/createCanvasDocking.svelte.ts +0 -355
  119. package/src/canvas/createCanvasExplosion.svelte.ts +0 -457
  120. package/src/canvas/createCanvasGraph.svelte.ts +0 -330
  121. package/src/canvas/createCanvasInteractions.svelte.ts +0 -112
  122. package/src/canvas/createCanvasLayout.ts +0 -129
  123. package/src/canvas/createCanvasMultiDrag.svelte.ts +0 -175
  124. package/src/canvas/createCanvasNodeSync.svelte.ts +0 -150
  125. package/src/canvas/createCanvasSelection.svelte.ts +0 -51
  126. package/src/canvas/createCanvasShortcuts.ts +0 -99
  127. package/src/canvas/createCanvasTrajectory.ts +0 -207
  128. package/src/canvas/createStreamingCanvas.svelte.ts +0 -56
  129. package/src/canvas/index.d.ts +0 -688
  130. package/src/canvas/index.ts +0 -126
  131. package/src/canvas/types.ts +0 -223
  132. package/src/createNavigationGuard.svelte.ts +0 -97
  133. package/src/editor.svelte.ts +0 -65
  134. package/src/history/createHistoryShortcuts.ts +0 -95
  135. package/src/history/history.svelte.ts +0 -68
  136. package/src/history/index.ts +0 -2
  137. package/src/image-editor/imageEditorState.svelte.ts +0 -151
  138. package/src/image-editor/index.d.ts +0 -16
  139. package/src/image-editor/index.ts +0 -2
  140. package/src/index.d.ts +0 -249
  141. package/src/index.ts +0 -89
  142. package/src/navigationGuard.ts +0 -82
  143. package/src/spreadsheet/spreadsheet.svelte.ts +0 -176
  144. package/src/svelte.d.ts +0 -5
  145. /package/{src → dist}/BubbleMenu.svelte +0 -0
  146. /package/{src → dist}/TemplateRenderer.svelte +0 -0
  147. /package/{src → dist}/canvas/AgentPresenceLayer.svelte +0 -0
  148. /package/{src → dist}/canvas/FloatingHorizontalScrollbar.svelte +0 -0
  149. /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
- });