@seed-ship/mcp-ui-solid 2.0.1 → 2.1.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 (136) hide show
  1. package/dist/components/AutocompleteDropdown.cjs +201 -0
  2. package/dist/components/AutocompleteDropdown.cjs.map +1 -0
  3. package/dist/components/AutocompleteDropdown.d.ts +71 -0
  4. package/dist/components/AutocompleteDropdown.d.ts.map +1 -0
  5. package/dist/components/AutocompleteDropdown.js +201 -0
  6. package/dist/components/AutocompleteDropdown.js.map +1 -0
  7. package/dist/components/AutocompleteFormField.cjs +289 -0
  8. package/dist/components/AutocompleteFormField.cjs.map +1 -0
  9. package/dist/components/AutocompleteFormField.d.ts +52 -0
  10. package/dist/components/AutocompleteFormField.d.ts.map +1 -0
  11. package/dist/components/AutocompleteFormField.js +289 -0
  12. package/dist/components/AutocompleteFormField.js.map +1 -0
  13. package/dist/components/DraggableGridItem.cjs +133 -0
  14. package/dist/components/DraggableGridItem.cjs.map +1 -0
  15. package/dist/components/DraggableGridItem.d.ts +95 -0
  16. package/dist/components/DraggableGridItem.d.ts.map +1 -0
  17. package/dist/components/DraggableGridItem.js +133 -0
  18. package/dist/components/DraggableGridItem.js.map +1 -0
  19. package/dist/components/EditableUIResourceRenderer.cjs +199 -0
  20. package/dist/components/EditableUIResourceRenderer.cjs.map +1 -0
  21. package/dist/components/EditableUIResourceRenderer.d.ts +43 -0
  22. package/dist/components/EditableUIResourceRenderer.d.ts.map +1 -0
  23. package/dist/components/EditableUIResourceRenderer.js +199 -0
  24. package/dist/components/EditableUIResourceRenderer.js.map +1 -0
  25. package/dist/components/GhostText.cjs +105 -0
  26. package/dist/components/GhostText.cjs.map +1 -0
  27. package/dist/components/GhostText.d.ts +113 -0
  28. package/dist/components/GhostText.d.ts.map +1 -0
  29. package/dist/components/GhostText.js +105 -0
  30. package/dist/components/GhostText.js.map +1 -0
  31. package/dist/components/ResizeHandle.cjs +177 -0
  32. package/dist/components/ResizeHandle.cjs.map +1 -0
  33. package/dist/components/ResizeHandle.d.ts +50 -0
  34. package/dist/components/ResizeHandle.d.ts.map +1 -0
  35. package/dist/components/ResizeHandle.js +177 -0
  36. package/dist/components/ResizeHandle.js.map +1 -0
  37. package/dist/context/AutocompleteContext.cjs +158 -0
  38. package/dist/context/AutocompleteContext.cjs.map +1 -0
  39. package/dist/context/AutocompleteContext.d.ts +77 -0
  40. package/dist/context/AutocompleteContext.d.ts.map +1 -0
  41. package/dist/context/AutocompleteContext.js +158 -0
  42. package/dist/context/AutocompleteContext.js.map +1 -0
  43. package/dist/hooks/index.d.ts +6 -0
  44. package/dist/hooks/index.d.ts.map +1 -1
  45. package/dist/hooks/useAutocomplete.cjs +242 -0
  46. package/dist/hooks/useAutocomplete.cjs.map +1 -0
  47. package/dist/hooks/useAutocomplete.d.ts +119 -0
  48. package/dist/hooks/useAutocomplete.d.ts.map +1 -0
  49. package/dist/hooks/useAutocomplete.js +242 -0
  50. package/dist/hooks/useAutocomplete.js.map +1 -0
  51. package/dist/hooks/useDragDrop.cjs +170 -0
  52. package/dist/hooks/useDragDrop.cjs.map +1 -0
  53. package/dist/hooks/useDragDrop.d.ts +100 -0
  54. package/dist/hooks/useDragDrop.d.ts.map +1 -0
  55. package/dist/hooks/useDragDrop.js +170 -0
  56. package/dist/hooks/useDragDrop.js.map +1 -0
  57. package/dist/hooks/useResize.cjs +209 -0
  58. package/dist/hooks/useResize.cjs.map +1 -0
  59. package/dist/hooks/useResize.d.ts +87 -0
  60. package/dist/hooks/useResize.d.ts.map +1 -0
  61. package/dist/hooks/useResize.js +209 -0
  62. package/dist/hooks/useResize.js.map +1 -0
  63. package/dist/hooks.cjs +6 -0
  64. package/dist/hooks.cjs.map +1 -1
  65. package/dist/hooks.d.cts +6 -0
  66. package/dist/hooks.d.ts +6 -0
  67. package/dist/hooks.js +6 -0
  68. package/dist/hooks.js.map +1 -1
  69. package/dist/index.cjs +29 -0
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +18 -3
  72. package/dist/index.d.ts +18 -3
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +29 -0
  75. package/dist/index.js.map +1 -1
  76. package/dist/plugins/duckdb.cjs +192 -0
  77. package/dist/plugins/duckdb.cjs.map +1 -0
  78. package/dist/plugins/duckdb.d.ts +20 -0
  79. package/dist/plugins/duckdb.d.ts.map +1 -0
  80. package/dist/plugins/duckdb.js +170 -0
  81. package/dist/plugins/duckdb.js.map +1 -0
  82. package/dist/plugins/groq.cjs +97 -0
  83. package/dist/plugins/groq.cjs.map +1 -0
  84. package/dist/plugins/groq.d.ts +13 -0
  85. package/dist/plugins/groq.d.ts.map +1 -0
  86. package/dist/plugins/groq.js +97 -0
  87. package/dist/plugins/groq.js.map +1 -0
  88. package/dist/plugins/index.d.ts +10 -0
  89. package/dist/plugins/index.d.ts.map +1 -0
  90. package/dist/plugins/rest.cjs +92 -0
  91. package/dist/plugins/rest.cjs.map +1 -0
  92. package/dist/plugins/rest.d.ts +13 -0
  93. package/dist/plugins/rest.d.ts.map +1 -0
  94. package/dist/plugins/rest.js +92 -0
  95. package/dist/plugins/rest.js.map +1 -0
  96. package/dist/plugins/supabase.cjs +79 -0
  97. package/dist/plugins/supabase.cjs.map +1 -0
  98. package/dist/plugins/supabase.d.ts +13 -0
  99. package/dist/plugins/supabase.d.ts.map +1 -0
  100. package/dist/plugins/supabase.js +79 -0
  101. package/dist/plugins/supabase.js.map +1 -0
  102. package/dist/types/index.d.ts +430 -0
  103. package/dist/types/index.d.ts.map +1 -1
  104. package/dist/types.d.cts +430 -0
  105. package/dist/types.d.ts +430 -0
  106. package/esbuild-why-Full bundle (with deps).html +51 -0
  107. package/esbuild-why-Hooks only.html +51 -0
  108. package/esbuild-why-Streaming renderer.html +51 -0
  109. package/package.json +16 -1
  110. package/src/components/AutocompleteDropdown.tsx +329 -0
  111. package/src/components/AutocompleteFormField.tsx +288 -0
  112. package/src/components/DraggableGridItem.tsx +274 -0
  113. package/src/components/EditableUIResourceRenderer.tsx +268 -0
  114. package/src/components/GhostText.tsx +262 -0
  115. package/src/components/ResizeHandle.tsx +267 -0
  116. package/src/context/AutocompleteContext.tsx +317 -0
  117. package/src/hooks/index.ts +23 -0
  118. package/src/hooks/useAutocomplete.test.ts +334 -0
  119. package/src/hooks/useAutocomplete.ts +482 -0
  120. package/src/hooks/useDragDrop.test.ts +355 -0
  121. package/src/hooks/useDragDrop.ts +379 -0
  122. package/src/hooks/useResize.test.ts +313 -0
  123. package/src/hooks/useResize.ts +372 -0
  124. package/src/index.ts +71 -0
  125. package/src/plugins/duckdb.ts +269 -0
  126. package/src/plugins/groq.ts +137 -0
  127. package/src/plugins/index.ts +14 -0
  128. package/src/plugins/rest.ts +147 -0
  129. package/src/plugins/supabase.ts +120 -0
  130. package/src/styles/autocomplete.css +356 -0
  131. package/src/styles/drag-drop.css +297 -0
  132. package/src/styles/index.css +7 -0
  133. package/src/types/index.ts +529 -0
  134. package/src/vite-env.d.ts +18 -0
  135. package/tsconfig.tsbuildinfo +1 -1
  136. package/vite.config.ts +2 -0
@@ -0,0 +1,379 @@
1
+ /**
2
+ * useDragDrop Hook
3
+ * Handles drag-and-drop reordering of grid components using HTML5 Drag-Drop API
4
+ *
5
+ * Sprint Drag-Drop Feature
6
+ */
7
+
8
+ import { createSignal, Accessor, batch, onCleanup } from 'solid-js'
9
+ import type { UIComponent } from '../types'
10
+
11
+ /**
12
+ * Options for the useDragDrop hook
13
+ */
14
+ export interface UseDragDropOptions {
15
+ /**
16
+ * Accessor for current components
17
+ */
18
+ components: Accessor<UIComponent[]>
19
+
20
+ /**
21
+ * Callback when components are reordered
22
+ */
23
+ onReorder: (newComponents: UIComponent[]) => void
24
+
25
+ /**
26
+ * Whether drag-drop is enabled
27
+ */
28
+ enabled?: boolean
29
+
30
+ /**
31
+ * Animation duration in ms
32
+ */
33
+ animationDuration?: number
34
+ }
35
+
36
+ /**
37
+ * Props to apply to a draggable element
38
+ */
39
+ export interface DragProps {
40
+ draggable: boolean
41
+ onDragStart: (e: DragEvent) => void
42
+ onDragOver: (e: DragEvent) => void
43
+ onDragEnter: (e: DragEvent) => void
44
+ onDragLeave: (e: DragEvent) => void
45
+ onDrop: (e: DragEvent) => void
46
+ onDragEnd: (e: DragEvent) => void
47
+ 'data-component-id': string
48
+ }
49
+
50
+ /**
51
+ * Return type for the useDragDrop hook
52
+ */
53
+ export interface UseDragDropReturn {
54
+ /**
55
+ * ID of the component currently being dragged
56
+ */
57
+ draggedId: Accessor<string | null>
58
+
59
+ /**
60
+ * ID of the current drop target
61
+ */
62
+ dropTargetId: Accessor<string | null>
63
+
64
+ /**
65
+ * Whether a drag operation is in progress
66
+ */
67
+ isDragging: Accessor<boolean>
68
+
69
+ /**
70
+ * Handle drag start event
71
+ */
72
+ handleDragStart: (e: DragEvent, componentId: string) => void
73
+
74
+ /**
75
+ * Handle drag over event
76
+ */
77
+ handleDragOver: (e: DragEvent, componentId: string) => void
78
+
79
+ /**
80
+ * Handle drag enter event
81
+ */
82
+ handleDragEnter: (e: DragEvent, componentId: string) => void
83
+
84
+ /**
85
+ * Handle drag leave event
86
+ */
87
+ handleDragLeave: (e: DragEvent) => void
88
+
89
+ /**
90
+ * Handle drop event
91
+ */
92
+ handleDrop: (e: DragEvent, targetId: string) => void
93
+
94
+ /**
95
+ * Handle drag end event
96
+ */
97
+ handleDragEnd: (e: DragEvent) => void
98
+
99
+ /**
100
+ * Get drag props for a component
101
+ */
102
+ getDragProps: (componentId: string) => DragProps
103
+
104
+ /**
105
+ * Check if a component is being dragged
106
+ */
107
+ isComponentDragging: (componentId: string) => boolean
108
+
109
+ /**
110
+ * Check if a component is a drop target
111
+ */
112
+ isDropTarget: (componentId: string) => boolean
113
+ }
114
+
115
+ /**
116
+ * Calculate drop position based on mouse position relative to target
117
+ */
118
+ function getDropPosition(
119
+ e: DragEvent,
120
+ targetElement: HTMLElement
121
+ ): 'before' | 'after' {
122
+ const rect = targetElement.getBoundingClientRect()
123
+ const midY = rect.top + rect.height / 2
124
+ return e.clientY < midY ? 'before' : 'after'
125
+ }
126
+
127
+ /**
128
+ * Reorder components by inserting source at a position relative to target
129
+ */
130
+ function reorderComponents(
131
+ components: UIComponent[],
132
+ sourceId: string,
133
+ targetId: string,
134
+ position: 'before' | 'after'
135
+ ): UIComponent[] {
136
+ const sourceIndex = components.findIndex(c => c.id === sourceId)
137
+ const targetIndex = components.findIndex(c => c.id === targetId)
138
+
139
+ if (sourceIndex === -1 || targetIndex === -1 || sourceIndex === targetIndex) {
140
+ return components
141
+ }
142
+
143
+ // Create a copy and remove source
144
+ const result = [...components]
145
+ const [sourceComponent] = result.splice(sourceIndex, 1)
146
+
147
+ // Find new target index (accounting for removal)
148
+ let newTargetIndex = result.findIndex(c => c.id === targetId)
149
+ if (position === 'after') {
150
+ newTargetIndex += 1
151
+ }
152
+
153
+ // Insert at new position
154
+ result.splice(newTargetIndex, 0, sourceComponent)
155
+
156
+ // Recalculate row positions based on new order
157
+ return recalculatePositions(result)
158
+ }
159
+
160
+ /**
161
+ * Recalculate grid positions after reordering
162
+ * Assigns sequential row positions while preserving column layout
163
+ */
164
+ function recalculatePositions(components: UIComponent[]): UIComponent[] {
165
+ let currentRow = 1
166
+ const rowComponents: Map<number, UIComponent[]> = new Map()
167
+
168
+ // Group by original row
169
+ components.forEach((c, index) => {
170
+ const row = c.position.rowStart || index + 1
171
+ if (!rowComponents.has(row)) {
172
+ rowComponents.set(row, [])
173
+ }
174
+ rowComponents.get(row)!.push(c)
175
+ })
176
+
177
+ // Flatten with new row assignments
178
+ const result: UIComponent[] = []
179
+ const sortedRows = Array.from(rowComponents.keys()).sort((a, b) => a - b)
180
+
181
+ sortedRows.forEach(row => {
182
+ const rowComps = rowComponents.get(row)!
183
+ rowComps.forEach(c => {
184
+ result.push({
185
+ ...c,
186
+ position: {
187
+ ...c.position,
188
+ rowStart: currentRow
189
+ }
190
+ })
191
+ })
192
+ currentRow += Math.max(...rowComps.map(c => c.position.rowSpan || 1))
193
+ })
194
+
195
+ return result
196
+ }
197
+
198
+ /**
199
+ * Hook for handling drag-and-drop reordering of grid components
200
+ */
201
+ export function useDragDrop(options: UseDragDropOptions): UseDragDropReturn {
202
+ const {
203
+ components,
204
+ onReorder,
205
+ enabled = true
206
+ } = options
207
+
208
+ const [draggedId, setDraggedId] = createSignal<string | null>(null)
209
+ const [dropTargetId, setDropTargetId] = createSignal<string | null>(null)
210
+ const [dropPosition, setDropPosition] = createSignal<'before' | 'after'>('after')
211
+ const isDragging = () => draggedId() !== null
212
+
213
+ // Track drag counter for nested elements
214
+ let dragCounter = 0
215
+
216
+ /**
217
+ * Handle drag start
218
+ */
219
+ const handleDragStart = (e: DragEvent, componentId: string) => {
220
+ if (!enabled) {
221
+ e.preventDefault()
222
+ return
223
+ }
224
+
225
+ const target = e.currentTarget as HTMLElement
226
+
227
+ // Set drag data
228
+ e.dataTransfer!.effectAllowed = 'move'
229
+ e.dataTransfer!.setData('text/plain', componentId)
230
+
231
+ // Create drag image
232
+ if (target) {
233
+ const rect = target.getBoundingClientRect()
234
+ e.dataTransfer!.setDragImage(target, rect.width / 2, 20)
235
+ }
236
+
237
+ // Delay setting state to allow drag image to be captured
238
+ requestAnimationFrame(() => {
239
+ setDraggedId(componentId)
240
+ })
241
+ }
242
+
243
+ /**
244
+ * Handle drag over
245
+ */
246
+ const handleDragOver = (e: DragEvent, componentId: string) => {
247
+ if (!enabled || !draggedId() || draggedId() === componentId) {
248
+ return
249
+ }
250
+
251
+ e.preventDefault()
252
+ e.dataTransfer!.dropEffect = 'move'
253
+
254
+ // Calculate drop position
255
+ const target = e.currentTarget as HTMLElement
256
+ const position = getDropPosition(e, target)
257
+ setDropPosition(position)
258
+ }
259
+
260
+ /**
261
+ * Handle drag enter
262
+ */
263
+ const handleDragEnter = (e: DragEvent, componentId: string) => {
264
+ if (!enabled || !draggedId() || draggedId() === componentId) {
265
+ return
266
+ }
267
+
268
+ e.preventDefault()
269
+ dragCounter++
270
+ setDropTargetId(componentId)
271
+ }
272
+
273
+ /**
274
+ * Handle drag leave
275
+ */
276
+ const handleDragLeave = (_e: DragEvent) => {
277
+ if (!enabled) return
278
+
279
+ dragCounter--
280
+ if (dragCounter === 0) {
281
+ setDropTargetId(null)
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Handle drop
287
+ */
288
+ const handleDrop = (e: DragEvent, targetId: string) => {
289
+ if (!enabled) return
290
+
291
+ e.preventDefault()
292
+ dragCounter = 0
293
+
294
+ const sourceId = e.dataTransfer!.getData('text/plain')
295
+ if (!sourceId || sourceId === targetId) {
296
+ batch(() => {
297
+ setDraggedId(null)
298
+ setDropTargetId(null)
299
+ })
300
+ return
301
+ }
302
+
303
+ // Reorder components
304
+ const currentComponents = components()
305
+ const reordered = reorderComponents(
306
+ currentComponents,
307
+ sourceId,
308
+ targetId,
309
+ dropPosition()
310
+ )
311
+
312
+ // Apply reorder
313
+ batch(() => {
314
+ setDraggedId(null)
315
+ setDropTargetId(null)
316
+ })
317
+
318
+ onReorder(reordered)
319
+ }
320
+
321
+ /**
322
+ * Handle drag end
323
+ */
324
+ const handleDragEnd = (_e: DragEvent) => {
325
+ dragCounter = 0
326
+ batch(() => {
327
+ setDraggedId(null)
328
+ setDropTargetId(null)
329
+ })
330
+ }
331
+
332
+ /**
333
+ * Get drag props for a component
334
+ */
335
+ const getDragProps = (componentId: string): DragProps => ({
336
+ draggable: enabled,
337
+ onDragStart: (e) => handleDragStart(e, componentId),
338
+ onDragOver: (e) => handleDragOver(e, componentId),
339
+ onDragEnter: (e) => handleDragEnter(e, componentId),
340
+ onDragLeave: handleDragLeave,
341
+ onDrop: (e) => handleDrop(e, componentId),
342
+ onDragEnd: handleDragEnd,
343
+ 'data-component-id': componentId
344
+ })
345
+
346
+ /**
347
+ * Check if a component is being dragged
348
+ */
349
+ const isComponentDragging = (componentId: string): boolean => {
350
+ return draggedId() === componentId
351
+ }
352
+
353
+ /**
354
+ * Check if a component is a drop target
355
+ */
356
+ const isDropTarget = (componentId: string): boolean => {
357
+ return dropTargetId() === componentId
358
+ }
359
+
360
+ // Cleanup on unmount
361
+ onCleanup(() => {
362
+ dragCounter = 0
363
+ })
364
+
365
+ return {
366
+ draggedId,
367
+ dropTargetId,
368
+ isDragging,
369
+ handleDragStart,
370
+ handleDragOver,
371
+ handleDragEnter,
372
+ handleDragLeave,
373
+ handleDrop,
374
+ handleDragEnd,
375
+ getDragProps,
376
+ isComponentDragging,
377
+ isDropTarget
378
+ }
379
+ }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * useResize Tests
3
+ * Sprint Drag-Drop Feature
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
7
+ import { createSignal, createRoot } from 'solid-js'
8
+ import { useResize } from './useResize'
9
+ import type { GridPosition } from '../types'
10
+
11
+ // Mock pointer events
12
+ const createMockPointerEvent = (
13
+ type: string,
14
+ clientX: number = 0,
15
+ clientY: number = 0
16
+ ): PointerEvent => {
17
+ return {
18
+ type,
19
+ clientX,
20
+ clientY,
21
+ preventDefault: vi.fn(),
22
+ stopPropagation: vi.fn()
23
+ } as unknown as PointerEvent
24
+ }
25
+
26
+ describe('useResize', () => {
27
+ beforeEach(() => {
28
+ vi.useFakeTimers()
29
+ })
30
+
31
+ afterEach(() => {
32
+ vi.useRealTimers()
33
+ vi.clearAllMocks()
34
+ // Clean up any document listeners
35
+ document.body.style.cursor = ''
36
+ document.body.style.userSelect = ''
37
+ })
38
+
39
+ describe('initialization', () => {
40
+ it('initializes with correct default state', () => {
41
+ createRoot((dispose) => {
42
+ const [position] = createSignal<GridPosition>({
43
+ colStart: 1,
44
+ colSpan: 6
45
+ })
46
+
47
+ const resize = useResize({
48
+ componentId: 'test-comp',
49
+ currentPosition: position,
50
+ onResize: vi.fn(),
51
+ enabled: true
52
+ })
53
+
54
+ expect(resize.isResizing()).toBe(false)
55
+ expect(resize.resizeEdge()).toBeNull()
56
+ expect(resize.previewPosition()).toBeNull()
57
+
58
+ dispose()
59
+ })
60
+ })
61
+
62
+ it('provides getResizeHandleProps for edges', () => {
63
+ createRoot((dispose) => {
64
+ const [position] = createSignal<GridPosition>({
65
+ colStart: 1,
66
+ colSpan: 6
67
+ })
68
+
69
+ const resize = useResize({
70
+ componentId: 'test-comp',
71
+ currentPosition: position,
72
+ onResize: vi.fn(),
73
+ enabled: true
74
+ })
75
+
76
+ const rightProps = resize.getResizeHandleProps('right')
77
+ const leftProps = resize.getResizeHandleProps('left')
78
+ const topProps = resize.getResizeHandleProps('top')
79
+ const bottomProps = resize.getResizeHandleProps('bottom')
80
+
81
+ expect(rightProps['data-resize-edge']).toBe('right')
82
+ expect(leftProps['data-resize-edge']).toBe('left')
83
+ expect(topProps['data-resize-edge']).toBe('top')
84
+ expect(bottomProps['data-resize-edge']).toBe('bottom')
85
+
86
+ expect(rightProps.style.cursor).toBe('ew-resize')
87
+ expect(topProps.style.cursor).toBe('ns-resize')
88
+
89
+ dispose()
90
+ })
91
+ })
92
+ })
93
+
94
+ describe('resize operations', () => {
95
+ it('starts resize on pointer down', () => {
96
+ createRoot((dispose) => {
97
+ const [position] = createSignal<GridPosition>({
98
+ colStart: 1,
99
+ colSpan: 6
100
+ })
101
+
102
+ const resize = useResize({
103
+ componentId: 'test-comp',
104
+ currentPosition: position,
105
+ onResize: vi.fn(),
106
+ enabled: true
107
+ })
108
+
109
+ const event = createMockPointerEvent('pointerdown', 100, 50)
110
+ resize.handleResizeStart(event, 'right')
111
+
112
+ expect(resize.isResizing()).toBe(true)
113
+ expect(resize.resizeEdge()).toBe('right')
114
+ expect(resize.previewPosition()).toEqual({ colStart: 1, colSpan: 6 })
115
+ expect(event.preventDefault).toHaveBeenCalled()
116
+ expect(event.stopPropagation).toHaveBeenCalled()
117
+
118
+ dispose()
119
+ })
120
+ })
121
+
122
+ it('does not start resize when disabled', () => {
123
+ createRoot((dispose) => {
124
+ const [position] = createSignal<GridPosition>({
125
+ colStart: 1,
126
+ colSpan: 6
127
+ })
128
+
129
+ const resize = useResize({
130
+ componentId: 'test-comp',
131
+ currentPosition: position,
132
+ onResize: vi.fn(),
133
+ enabled: false
134
+ })
135
+
136
+ const event = createMockPointerEvent('pointerdown', 100, 50)
137
+ resize.handleResizeStart(event, 'right')
138
+
139
+ expect(resize.isResizing()).toBe(false)
140
+ expect(resize.resizeEdge()).toBeNull()
141
+
142
+ dispose()
143
+ })
144
+ })
145
+ })
146
+
147
+ describe('constraints', () => {
148
+ it('respects minColSpan constraint', () => {
149
+ createRoot((dispose) => {
150
+ const [position] = createSignal<GridPosition>({
151
+ colStart: 1,
152
+ colSpan: 4
153
+ })
154
+
155
+ const onResize = vi.fn()
156
+
157
+ const resize = useResize({
158
+ componentId: 'test-comp',
159
+ currentPosition: position,
160
+ onResize,
161
+ constraints: { minColSpan: 2, maxColSpan: 8 },
162
+ enabled: true
163
+ })
164
+
165
+ // Verify constraints are applied in the hook configuration
166
+ expect(resize.isResizing()).toBe(false)
167
+
168
+ dispose()
169
+ })
170
+ })
171
+
172
+ it('handles lockHorizontal constraint', () => {
173
+ createRoot((dispose) => {
174
+ const [position] = createSignal<GridPosition>({
175
+ colStart: 1,
176
+ colSpan: 6
177
+ })
178
+
179
+ const resize = useResize({
180
+ componentId: 'test-comp',
181
+ currentPosition: position,
182
+ onResize: vi.fn(),
183
+ constraints: { lockHorizontal: true },
184
+ enabled: true
185
+ })
186
+
187
+ // Start horizontal resize
188
+ const event = createMockPointerEvent('pointerdown', 100, 50)
189
+ resize.handleResizeStart(event, 'right')
190
+
191
+ // The initial state should be set correctly
192
+ expect(resize.isResizing()).toBe(true)
193
+
194
+ dispose()
195
+ })
196
+ })
197
+ })
198
+
199
+ describe('edge-specific behavior', () => {
200
+ it('handles right edge resize start', () => {
201
+ createRoot((dispose) => {
202
+ const [position] = createSignal<GridPosition>({
203
+ colStart: 1,
204
+ colSpan: 6
205
+ })
206
+
207
+ const resize = useResize({
208
+ componentId: 'test-comp',
209
+ currentPosition: position,
210
+ onResize: vi.fn(),
211
+ enabled: true
212
+ })
213
+
214
+ resize.handleResizeStart(createMockPointerEvent('pointerdown'), 'right')
215
+ expect(resize.resizeEdge()).toBe('right')
216
+
217
+ dispose()
218
+ })
219
+ })
220
+
221
+ it('handles left edge resize start', () => {
222
+ createRoot((dispose) => {
223
+ const [position] = createSignal<GridPosition>({
224
+ colStart: 3,
225
+ colSpan: 6
226
+ })
227
+
228
+ const resize = useResize({
229
+ componentId: 'test-comp',
230
+ currentPosition: position,
231
+ onResize: vi.fn(),
232
+ enabled: true
233
+ })
234
+
235
+ resize.handleResizeStart(createMockPointerEvent('pointerdown'), 'left')
236
+ expect(resize.resizeEdge()).toBe('left')
237
+
238
+ dispose()
239
+ })
240
+ })
241
+
242
+ it('handles top edge resize start', () => {
243
+ createRoot((dispose) => {
244
+ const [position] = createSignal<GridPosition>({
245
+ colStart: 1,
246
+ colSpan: 6,
247
+ rowStart: 2,
248
+ rowSpan: 2
249
+ })
250
+
251
+ const resize = useResize({
252
+ componentId: 'test-comp',
253
+ currentPosition: position,
254
+ onResize: vi.fn(),
255
+ enabled: true
256
+ })
257
+
258
+ resize.handleResizeStart(createMockPointerEvent('pointerdown'), 'top')
259
+ expect(resize.resizeEdge()).toBe('top')
260
+
261
+ dispose()
262
+ })
263
+ })
264
+
265
+ it('handles bottom edge resize start', () => {
266
+ createRoot((dispose) => {
267
+ const [position] = createSignal<GridPosition>({
268
+ colStart: 1,
269
+ colSpan: 6,
270
+ rowStart: 1,
271
+ rowSpan: 2
272
+ })
273
+
274
+ const resize = useResize({
275
+ componentId: 'test-comp',
276
+ currentPosition: position,
277
+ onResize: vi.fn(),
278
+ enabled: true
279
+ })
280
+
281
+ resize.handleResizeStart(createMockPointerEvent('pointerdown'), 'bottom')
282
+ expect(resize.resizeEdge()).toBe('bottom')
283
+
284
+ dispose()
285
+ })
286
+ })
287
+ })
288
+
289
+ describe('SSR safety', () => {
290
+ it('returns no-op functions on server', () => {
291
+ // Note: In real SSR, isServer would be true
292
+ // This test verifies the hook doesn't throw
293
+ createRoot((dispose) => {
294
+ const [position] = createSignal<GridPosition>({
295
+ colStart: 1,
296
+ colSpan: 6
297
+ })
298
+
299
+ const resize = useResize({
300
+ componentId: 'test-comp',
301
+ currentPosition: position,
302
+ onResize: vi.fn(),
303
+ enabled: true
304
+ })
305
+
306
+ // Should not throw
307
+ expect(() => resize.getResizeHandleProps('right')).not.toThrow()
308
+
309
+ dispose()
310
+ })
311
+ })
312
+ })
313
+ })