@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.
- package/dist/components/AutocompleteDropdown.cjs +201 -0
- package/dist/components/AutocompleteDropdown.cjs.map +1 -0
- package/dist/components/AutocompleteDropdown.d.ts +71 -0
- package/dist/components/AutocompleteDropdown.d.ts.map +1 -0
- package/dist/components/AutocompleteDropdown.js +201 -0
- package/dist/components/AutocompleteDropdown.js.map +1 -0
- package/dist/components/AutocompleteFormField.cjs +289 -0
- package/dist/components/AutocompleteFormField.cjs.map +1 -0
- package/dist/components/AutocompleteFormField.d.ts +52 -0
- package/dist/components/AutocompleteFormField.d.ts.map +1 -0
- package/dist/components/AutocompleteFormField.js +289 -0
- package/dist/components/AutocompleteFormField.js.map +1 -0
- package/dist/components/DraggableGridItem.cjs +133 -0
- package/dist/components/DraggableGridItem.cjs.map +1 -0
- package/dist/components/DraggableGridItem.d.ts +95 -0
- package/dist/components/DraggableGridItem.d.ts.map +1 -0
- package/dist/components/DraggableGridItem.js +133 -0
- package/dist/components/DraggableGridItem.js.map +1 -0
- package/dist/components/EditableUIResourceRenderer.cjs +199 -0
- package/dist/components/EditableUIResourceRenderer.cjs.map +1 -0
- package/dist/components/EditableUIResourceRenderer.d.ts +43 -0
- package/dist/components/EditableUIResourceRenderer.d.ts.map +1 -0
- package/dist/components/EditableUIResourceRenderer.js +199 -0
- package/dist/components/EditableUIResourceRenderer.js.map +1 -0
- package/dist/components/GhostText.cjs +105 -0
- package/dist/components/GhostText.cjs.map +1 -0
- package/dist/components/GhostText.d.ts +113 -0
- package/dist/components/GhostText.d.ts.map +1 -0
- package/dist/components/GhostText.js +105 -0
- package/dist/components/GhostText.js.map +1 -0
- package/dist/components/ResizeHandle.cjs +177 -0
- package/dist/components/ResizeHandle.cjs.map +1 -0
- package/dist/components/ResizeHandle.d.ts +50 -0
- package/dist/components/ResizeHandle.d.ts.map +1 -0
- package/dist/components/ResizeHandle.js +177 -0
- package/dist/components/ResizeHandle.js.map +1 -0
- package/dist/context/AutocompleteContext.cjs +158 -0
- package/dist/context/AutocompleteContext.cjs.map +1 -0
- package/dist/context/AutocompleteContext.d.ts +77 -0
- package/dist/context/AutocompleteContext.d.ts.map +1 -0
- package/dist/context/AutocompleteContext.js +158 -0
- package/dist/context/AutocompleteContext.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useAutocomplete.cjs +242 -0
- package/dist/hooks/useAutocomplete.cjs.map +1 -0
- package/dist/hooks/useAutocomplete.d.ts +119 -0
- package/dist/hooks/useAutocomplete.d.ts.map +1 -0
- package/dist/hooks/useAutocomplete.js +242 -0
- package/dist/hooks/useAutocomplete.js.map +1 -0
- package/dist/hooks/useDragDrop.cjs +170 -0
- package/dist/hooks/useDragDrop.cjs.map +1 -0
- package/dist/hooks/useDragDrop.d.ts +100 -0
- package/dist/hooks/useDragDrop.d.ts.map +1 -0
- package/dist/hooks/useDragDrop.js +170 -0
- package/dist/hooks/useDragDrop.js.map +1 -0
- package/dist/hooks/useResize.cjs +209 -0
- package/dist/hooks/useResize.cjs.map +1 -0
- package/dist/hooks/useResize.d.ts +87 -0
- package/dist/hooks/useResize.d.ts.map +1 -0
- package/dist/hooks/useResize.js +209 -0
- package/dist/hooks/useResize.js.map +1 -0
- package/dist/hooks.cjs +6 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +6 -0
- package/dist/hooks.d.ts +6 -0
- package/dist/hooks.js +6 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +29 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/duckdb.cjs +192 -0
- package/dist/plugins/duckdb.cjs.map +1 -0
- package/dist/plugins/duckdb.d.ts +20 -0
- package/dist/plugins/duckdb.d.ts.map +1 -0
- package/dist/plugins/duckdb.js +170 -0
- package/dist/plugins/duckdb.js.map +1 -0
- package/dist/plugins/groq.cjs +97 -0
- package/dist/plugins/groq.cjs.map +1 -0
- package/dist/plugins/groq.d.ts +13 -0
- package/dist/plugins/groq.d.ts.map +1 -0
- package/dist/plugins/groq.js +97 -0
- package/dist/plugins/groq.js.map +1 -0
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/rest.cjs +92 -0
- package/dist/plugins/rest.cjs.map +1 -0
- package/dist/plugins/rest.d.ts +13 -0
- package/dist/plugins/rest.d.ts.map +1 -0
- package/dist/plugins/rest.js +92 -0
- package/dist/plugins/rest.js.map +1 -0
- package/dist/plugins/supabase.cjs +79 -0
- package/dist/plugins/supabase.cjs.map +1 -0
- package/dist/plugins/supabase.d.ts +13 -0
- package/dist/plugins/supabase.d.ts.map +1 -0
- package/dist/plugins/supabase.js +79 -0
- package/dist/plugins/supabase.js.map +1 -0
- package/dist/types/index.d.ts +430 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +430 -0
- package/dist/types.d.ts +430 -0
- package/esbuild-why-Full bundle (with deps).html +51 -0
- package/esbuild-why-Hooks only.html +51 -0
- package/esbuild-why-Streaming renderer.html +51 -0
- package/package.json +16 -1
- package/src/components/AutocompleteDropdown.tsx +329 -0
- package/src/components/AutocompleteFormField.tsx +288 -0
- package/src/components/DraggableGridItem.tsx +274 -0
- package/src/components/EditableUIResourceRenderer.tsx +268 -0
- package/src/components/GhostText.tsx +262 -0
- package/src/components/ResizeHandle.tsx +267 -0
- package/src/context/AutocompleteContext.tsx +317 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/useAutocomplete.test.ts +334 -0
- package/src/hooks/useAutocomplete.ts +482 -0
- package/src/hooks/useDragDrop.test.ts +355 -0
- package/src/hooks/useDragDrop.ts +379 -0
- package/src/hooks/useResize.test.ts +313 -0
- package/src/hooks/useResize.ts +372 -0
- package/src/index.ts +71 -0
- package/src/plugins/duckdb.ts +269 -0
- package/src/plugins/groq.ts +137 -0
- package/src/plugins/index.ts +14 -0
- package/src/plugins/rest.ts +147 -0
- package/src/plugins/supabase.ts +120 -0
- package/src/styles/autocomplete.css +356 -0
- package/src/styles/drag-drop.css +297 -0
- package/src/styles/index.css +7 -0
- package/src/types/index.ts +529 -0
- package/src/vite-env.d.ts +18 -0
- package/tsconfig.tsbuildinfo +1 -1
- 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
|
+
})
|