@hkdigital/lib-sveltekit 0.2.7 → 0.2.9
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/drag-drop/DragDropContext.svelte +4 -2
- package/dist/components/drag-drop/Draggable.svelte +27 -41
- package/dist/components/drag-drop/actions.d.ts +6 -0
- package/dist/components/drag-drop/actions.js +18 -0
- package/dist/components/drag-drop/drag-state.svelte.js +1 -1
- package/dist/features/presenter/Presenter.svelte +2 -2
- package/package.json +1 -1
- package/dist/components/drag-drop/drag-state.svelte.js__ +0 -323
@@ -1,6 +1,8 @@
|
|
1
1
|
<script>
|
2
2
|
import { createDragState } from './drag-state.svelte.js';
|
3
3
|
|
4
|
+
import { activeDragOver, activeTouchMove } from './actions.js';
|
5
|
+
|
4
6
|
/**
|
5
7
|
* @type {{
|
6
8
|
* contextKey?: import('../../typedef').ContextKey,
|
@@ -93,11 +95,11 @@
|
|
93
95
|
<div
|
94
96
|
data-component="drag-drop-context"
|
95
97
|
ondragenter={onDragEnter}
|
96
|
-
|
98
|
+
use:activeDragOver={onDragOver}
|
97
99
|
ondragleave={onDragLeave}
|
98
100
|
ondrop={onDrop}
|
99
101
|
ondragend={onDragEnd}
|
100
|
-
|
102
|
+
use:activeTouchMove={(e) => {
|
101
103
|
// Prevent scrolling if we're dragging
|
102
104
|
if (dragState.isDragging()) {
|
103
105
|
e.preventDefault();
|
@@ -1,4 +1,6 @@
|
|
1
1
|
<script>
|
2
|
+
import { browser } from '$app/environment';
|
3
|
+
|
2
4
|
import { toStateClasses } from '../../util/design-system/index.js';
|
3
5
|
import { createOrGetDragState } from './drag-state.svelte.js';
|
4
6
|
import { DragController } from './DragController.js';
|
@@ -177,11 +179,23 @@ let stateObject = $derived({
|
|
177
179
|
startDrag(event);
|
178
180
|
}
|
179
181
|
|
182
|
+
let transparentPixel;
|
183
|
+
|
184
|
+
if( browser )
|
185
|
+
{
|
186
|
+
transparentPixel = new Image();
|
187
|
+
transparentPixel.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
188
|
+
}
|
189
|
+
|
180
190
|
/**
|
181
191
|
* Start the drag operation
|
182
192
|
* @param {DragEvent} event - The drag event
|
183
193
|
*/
|
184
194
|
function startDrag(event) {
|
195
|
+
|
196
|
+
// Set a transparent 1x1 pixel image to hide browser's default preview
|
197
|
+
event.dataTransfer.setDragImage(transparentPixel, 0, 0);
|
198
|
+
|
185
199
|
// Get the element's bounding rectangle
|
186
200
|
const rect = draggableElement.getBoundingClientRect();
|
187
201
|
|
@@ -221,51 +235,23 @@ let stateObject = $derived({
|
|
221
235
|
// Call onDragStart with the getController function
|
222
236
|
onDragStart?.({ event, item, source, group, getController });
|
223
237
|
|
224
|
-
//
|
225
|
-
|
226
|
-
// try {
|
227
|
-
// Store rectangle information for the snippet
|
228
|
-
elementRect = rect;
|
229
|
-
|
230
|
-
// These offsets represent where the user grabbed the element relative to its top-left corner
|
231
|
-
dragOffsetX = event.clientX - rect.left;
|
232
|
-
dragOffsetY = event.clientY - rect.top;
|
238
|
+
// Store rectangle information for the snippet
|
239
|
+
elementRect = rect;
|
233
240
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
// Set a transparent 1x1 pixel image to hide browser's
|
239
|
-
// default preview
|
240
|
-
const emptyImg = new Image();
|
241
|
-
emptyImg.src =
|
242
|
-
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
243
|
-
|
244
|
-
// Chrome needs the image to be loaded before setting it
|
245
|
-
emptyImg.onload = () => {
|
246
|
-
if (event.dataTransfer) {
|
247
|
-
event.dataTransfer.setDragImage(emptyImg, 0, 0);
|
248
|
-
}
|
249
|
-
};
|
241
|
+
// These offsets represent where the user grabbed the element relative to its top-left corner
|
242
|
+
dragOffsetX = event.clientX - rect.left;
|
243
|
+
dragOffsetY = event.clientY - rect.top;
|
250
244
|
|
251
|
-
|
252
|
-
|
245
|
+
// Set initial position - this places the preview at the element's original position
|
246
|
+
previewX = rect.left;
|
247
|
+
previewY = rect.top;
|
253
248
|
|
254
|
-
|
255
|
-
|
249
|
+
// Add document level event listener to track mouse movement
|
250
|
+
document.addEventListener('dragover', handleDocumentDragOver);
|
256
251
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
// } catch (err) {
|
261
|
-
// console.error('Error setting up custom preview:', err);
|
262
|
-
// // Fallback to default preview
|
263
|
-
// previewController.applyDefaultPreview();
|
264
|
-
// }
|
265
|
-
// } else {
|
266
|
-
// // Apply default preview if no custom preview was set
|
267
|
-
// previewController.applyDefaultPreview();
|
268
|
-
// }
|
252
|
+
// Show custom preview
|
253
|
+
showPreview = true;
|
254
|
+
customPreviewSet = true;
|
269
255
|
}
|
270
256
|
|
271
257
|
/**
|
@@ -0,0 +1,18 @@
|
|
1
|
+
export function activeDragOver(node, handler) {
|
2
|
+
node.addEventListener('dragover', handler, { passive: false });
|
3
|
+
|
4
|
+
return {
|
5
|
+
destroy() {
|
6
|
+
node.removeEventListener('dragover', handler, { passive: false });
|
7
|
+
}
|
8
|
+
};
|
9
|
+
}
|
10
|
+
|
11
|
+
export function activeTouchMove(node, handler) {
|
12
|
+
node.addEventListener('touchmove', handler, { passive: false });
|
13
|
+
return {
|
14
|
+
destroy() {
|
15
|
+
node.removeEventListener('touchmove', handler, { passive: false });
|
16
|
+
}
|
17
|
+
};
|
18
|
+
}
|
@@ -270,7 +270,7 @@ class DragState {
|
|
270
270
|
|
271
271
|
// For dragover events, we can't read dataTransfer.getData in Chrome
|
272
272
|
// Instead, check if we have an active drag operation
|
273
|
-
if (event.type === 'dragover') {
|
273
|
+
if (event.type === 'dragover'|| event.type === 'dragenter') {
|
274
274
|
if (this.draggables.size > 0) {
|
275
275
|
// Return the most recent drag operation
|
276
276
|
return this.current;
|
@@ -117,7 +117,7 @@
|
|
117
117
|
inert={presenter.busy}
|
118
118
|
class="justify-self-stretch self-stretch overflow-hidden"
|
119
119
|
>
|
120
|
-
<div class="{classesA} h-full w-full" style={stylesA}>
|
120
|
+
<div class="{classesA} h-full w-full grid" style={stylesA}>
|
121
121
|
{@render layoutSnippet(presenter.slideA, presenter.layerA)}
|
122
122
|
</div>
|
123
123
|
</div>
|
@@ -129,7 +129,7 @@
|
|
129
129
|
inert={presenter.busy}
|
130
130
|
class="justify-self-stretch self-stretch overflow-hidden"
|
131
131
|
>
|
132
|
-
<div class="{classesB} h-full w-full" style={stylesB}>
|
132
|
+
<div class="{classesB} h-full w-full grid" style={stylesB}>
|
133
133
|
{@render layoutSnippet(presenter.slideB, presenter.layerB)}
|
134
134
|
</div>
|
135
135
|
</div>
|
package/package.json
CHANGED
@@ -1,323 +0,0 @@
|
|
1
|
-
// drag-state.svelte.js
|
2
|
-
import { defineStateContext } from '$lib/util/svelte/state-context/index.js';
|
3
|
-
|
4
|
-
/** @typedef {import('$lib/typedef').SimulatedDragEvent} SimulatedDragEvent */
|
5
|
-
|
6
|
-
class DragState {
|
7
|
-
// Existing draggables map
|
8
|
-
draggables = $state(new Map());
|
9
|
-
|
10
|
-
// New: Registry for dropzones
|
11
|
-
dropZones = $state(new Map());
|
12
|
-
|
13
|
-
// Track which dropzone is currently active
|
14
|
-
activeDropZone = $state(null);
|
15
|
-
|
16
|
-
// Track the last active drop zone
|
17
|
-
// - activeDropZone gets cleared by dragLeavr
|
18
|
-
// - but we need it in 'end'
|
19
|
-
lastActiveDropZone = null;
|
20
|
-
|
21
|
-
/**
|
22
|
-
* Register a dropzone
|
23
|
-
* @param {string} zoneId
|
24
|
-
* @param {Object} config
|
25
|
-
* @param {string} config.zone
|
26
|
-
* @param {string} config.group
|
27
|
-
* @param {Function} config.accepts
|
28
|
-
* @param {Function} config.onDragEnter
|
29
|
-
* @param {Function} config.onDragOver
|
30
|
-
* @param {Function} config.onDragLeave
|
31
|
-
* @param {(DropData) => void} config.onDrop
|
32
|
-
* @param {HTMLElement} config.element
|
33
|
-
*/
|
34
|
-
registerDropZone(zoneId, config) {
|
35
|
-
if (this.dropZones.has(zoneId)) {
|
36
|
-
throw new Error(`Zone [${zoneId}] is already registered`);
|
37
|
-
}
|
38
|
-
|
39
|
-
this.dropZones.set(zoneId, {
|
40
|
-
...config,
|
41
|
-
isOver: false,
|
42
|
-
canDrop: false
|
43
|
-
});
|
44
|
-
}
|
45
|
-
|
46
|
-
/**
|
47
|
-
* Unregister a dropzone
|
48
|
-
* @param {string} zoneId
|
49
|
-
*/
|
50
|
-
unregisterDropZone(zoneId) {
|
51
|
-
if (this.activeDropZone === zoneId) {
|
52
|
-
this.activeDropZone = null;
|
53
|
-
}
|
54
|
-
this.dropZones.delete(zoneId);
|
55
|
-
}
|
56
|
-
|
57
|
-
/**
|
58
|
-
* Get dropzone at coordinates
|
59
|
-
* @param {number} x
|
60
|
-
* @param {number} y
|
61
|
-
* @returns {Object|null}
|
62
|
-
*/
|
63
|
-
getDropZoneAtPoint(x, y) {
|
64
|
-
// Check all registered dropzones
|
65
|
-
for (const [zoneId, config] of this.dropZones) {
|
66
|
-
const rect = config.element.getBoundingClientRect();
|
67
|
-
|
68
|
-
if (
|
69
|
-
x >= rect.left &&
|
70
|
-
x <= rect.right &&
|
71
|
-
y >= rect.top &&
|
72
|
-
y <= rect.bottom
|
73
|
-
) {
|
74
|
-
// Found a dropzone at this point
|
75
|
-
// Check if it's the deepest one (for nested zones)
|
76
|
-
let deepestZone = { zoneId, config, depth: 0 };
|
77
|
-
|
78
|
-
// Check for nested dropzones
|
79
|
-
for (const [otherId, otherConfig] of this.dropZones) {
|
80
|
-
if (otherId === zoneId) continue;
|
81
|
-
|
82
|
-
const otherRect = otherConfig.element.getBoundingClientRect();
|
83
|
-
if (
|
84
|
-
x >= otherRect.left &&
|
85
|
-
x <= otherRect.right &&
|
86
|
-
y >= otherRect.top &&
|
87
|
-
y <= otherRect.bottom
|
88
|
-
) {
|
89
|
-
// Check if this zone is nested inside our current zone
|
90
|
-
if (config.element.contains(otherConfig.element)) {
|
91
|
-
deepestZone = {
|
92
|
-
zoneId: otherId,
|
93
|
-
config: otherConfig,
|
94
|
-
depth: deepestZone.depth + 1
|
95
|
-
};
|
96
|
-
}
|
97
|
-
}
|
98
|
-
}
|
99
|
-
|
100
|
-
return { zoneId: deepestZone.zoneId, config: deepestZone.config };
|
101
|
-
}
|
102
|
-
}
|
103
|
-
|
104
|
-
return null;
|
105
|
-
}
|
106
|
-
|
107
|
-
/**
|
108
|
-
* Update active dropzone based on coordinates
|
109
|
-
*
|
110
|
-
* @param {number} x
|
111
|
-
* @param {number} y
|
112
|
-
* @param {DragEvent|SimulatedDragEvent} event
|
113
|
-
*/
|
114
|
-
updateActiveDropZone(x, y, event) {
|
115
|
-
const dropZone = this.getDropZoneAtPoint(x, y);
|
116
|
-
const newActiveId = dropZone?.zoneId || null;
|
117
|
-
|
118
|
-
// Handle leave/enter transitions
|
119
|
-
if (this.activeDropZone !== newActiveId) {
|
120
|
-
// Leave previous zone
|
121
|
-
if (this.activeDropZone) {
|
122
|
-
this.lastActiveDropZone = this.activeDropZone;
|
123
|
-
|
124
|
-
const prevConfig = this.dropZones.get(this.activeDropZone);
|
125
|
-
if (prevConfig) {
|
126
|
-
prevConfig.isOver = false;
|
127
|
-
prevConfig.canDrop = false;
|
128
|
-
prevConfig.onDragLeave?.({ event, zone: prevConfig.zone });
|
129
|
-
}
|
130
|
-
}
|
131
|
-
|
132
|
-
// Enter new zone
|
133
|
-
if (newActiveId && dropZone) {
|
134
|
-
const dragData = this.getDraggable(event);
|
135
|
-
const canDrop = dragData && dropZone.config.accepts(dragData);
|
136
|
-
|
137
|
-
dropZone.config.isOver = true;
|
138
|
-
dropZone.config.canDrop = canDrop;
|
139
|
-
dropZone.config.onDragEnter?.({
|
140
|
-
event,
|
141
|
-
zone: dropZone.config.zone,
|
142
|
-
canDrop
|
143
|
-
});
|
144
|
-
}
|
145
|
-
|
146
|
-
this.activeDropZone = newActiveId;
|
147
|
-
} else if (newActiveId) {
|
148
|
-
// Still in the same zone, just send dragOver
|
149
|
-
dropZone.config.onDragOver?.({ event, zone: dropZone.config.zone });
|
150
|
-
}
|
151
|
-
}
|
152
|
-
|
153
|
-
/**
|
154
|
-
* Handle drop at coordinates
|
155
|
-
* @param {number} x
|
156
|
-
* @param {number} y
|
157
|
-
* @param {DragEvent|SimulatedDragEvent} event
|
158
|
-
*/
|
159
|
-
handleDropAtPoint(x, y, event) {
|
160
|
-
const dropZone = this.getDropZoneAtPoint(x, y);
|
161
|
-
|
162
|
-
if (dropZone && dropZone.config.canDrop) {
|
163
|
-
const dragData = this.getDraggable(event);
|
164
|
-
|
165
|
-
if (dragData && dropZone.config.element) {
|
166
|
-
// Calculate drop position relative to dropzone
|
167
|
-
const rect = dropZone.config.element.getBoundingClientRect();
|
168
|
-
|
169
|
-
const style = window.getComputedStyle(dropZone.config.element);
|
170
|
-
|
171
|
-
const borderLeftWidth = parseInt(style.borderLeftWidth, 10) || 0;
|
172
|
-
const borderTopWidth = parseInt(style.borderTopWidth, 10) || 0;
|
173
|
-
|
174
|
-
const dropOffsetX = x - rect.left - borderLeftWidth;
|
175
|
-
const dropOffsetY = y - rect.top - borderTopWidth;
|
176
|
-
|
177
|
-
const dropX = dropOffsetX - (dragData.offsetX ?? 0);
|
178
|
-
const dropY = dropOffsetY - (dragData.offsetY ?? 0);
|
179
|
-
|
180
|
-
// Call the dropzone's drop handler
|
181
|
-
dropZone.config.onDrop?.({
|
182
|
-
zone: dropZone.config.zone,
|
183
|
-
source: dragData.source,
|
184
|
-
item: dragData.item,
|
185
|
-
x: dropX,
|
186
|
-
y: dropY,
|
187
|
-
drag: dragData,
|
188
|
-
drop: {
|
189
|
-
offsetX: dropOffsetX,
|
190
|
-
offsetY: dropOffsetY,
|
191
|
-
target: dropZone.config.element
|
192
|
-
}
|
193
|
-
});
|
194
|
-
}
|
195
|
-
}
|
196
|
-
|
197
|
-
// Ensure we notify the active dropzone that drag ended
|
198
|
-
if (this.activeDropZone) {
|
199
|
-
const config = this.dropZones.get(this.activeDropZone);
|
200
|
-
if (config) {
|
201
|
-
config.isOver = false;
|
202
|
-
config.canDrop = false;
|
203
|
-
config.onDragLeave?.({ event, zone: config.zone });
|
204
|
-
}
|
205
|
-
}
|
206
|
-
|
207
|
-
// Reset active dropzone
|
208
|
-
this.activeDropZone = null;
|
209
|
-
}
|
210
|
-
|
211
|
-
/**
|
212
|
-
* @param {string} draggableId
|
213
|
-
* @param {import('$lib/typedef/drag.js').DragData} dragData
|
214
|
-
*/
|
215
|
-
start(draggableId, dragData) {
|
216
|
-
this.draggables.set(draggableId, dragData);
|
217
|
-
}
|
218
|
-
|
219
|
-
/**
|
220
|
-
* @param {string} draggableId
|
221
|
-
*/
|
222
|
-
end(draggableId) {
|
223
|
-
this.draggables.delete(draggableId);
|
224
|
-
|
225
|
-
// Check both current AND last active dropzone
|
226
|
-
const zoneToNotify = this.activeDropZone || this.lastActiveDropZone;
|
227
|
-
|
228
|
-
if (zoneToNotify) {
|
229
|
-
const config = this.dropZones.get(zoneToNotify);
|
230
|
-
if (config && (config.isOver || config.canDrop)) {
|
231
|
-
config.isOver = false;
|
232
|
-
config.canDrop = false;
|
233
|
-
config.onDragLeave?.({
|
234
|
-
event: new DragEvent('dragend'),
|
235
|
-
zone: config.zone
|
236
|
-
});
|
237
|
-
}
|
238
|
-
}
|
239
|
-
|
240
|
-
this.activeDropZone = null;
|
241
|
-
this.lastActiveDropZone = null;
|
242
|
-
}
|
243
|
-
|
244
|
-
/**
|
245
|
-
* Get a drag data by draggable id
|
246
|
-
*
|
247
|
-
* @param {string} draggableId
|
248
|
-
* @returns {import('$lib/typedef/drag.js').DragData|undefined}
|
249
|
-
*/
|
250
|
-
getDraggableById(draggableId) {
|
251
|
-
return this.draggables.get(draggableId);
|
252
|
-
}
|
253
|
-
|
254
|
-
/**
|
255
|
-
* Get a drag data. Extracts draggable id from the supplied DragEvent
|
256
|
-
*
|
257
|
-
* @param {DragEvent|SimulatedDragEvent} event
|
258
|
-
*
|
259
|
-
* @returns {Object|null} The drag data, or null for file drops
|
260
|
-
*/
|
261
|
-
getDraggable(event) {
|
262
|
-
// Check if this is a touch-simulated event
|
263
|
-
if (event.dataTransfer && !event.dataTransfer.files) {
|
264
|
-
try {
|
265
|
-
const jsonData = event.dataTransfer.getData('application/json');
|
266
|
-
if (jsonData) {
|
267
|
-
const transferData = JSON.parse(jsonData);
|
268
|
-
const draggableId = transferData.draggableId;
|
269
|
-
|
270
|
-
if (draggableId) {
|
271
|
-
return this.getDraggableById(draggableId);
|
272
|
-
}
|
273
|
-
}
|
274
|
-
} catch (error) {
|
275
|
-
console.error('Error getting drag data:', error);
|
276
|
-
}
|
277
|
-
}
|
278
|
-
|
279
|
-
// Check if this is a file drop
|
280
|
-
if (event.dataTransfer.types.includes('Files')) {
|
281
|
-
return null;
|
282
|
-
}
|
283
|
-
|
284
|
-
// Handle internal drag operations
|
285
|
-
try {
|
286
|
-
const jsonData = event.dataTransfer.getData('application/json');
|
287
|
-
if (jsonData) {
|
288
|
-
const transferData = JSON.parse(jsonData);
|
289
|
-
const draggableId = transferData.draggableId;
|
290
|
-
|
291
|
-
if (draggableId) {
|
292
|
-
const dragData = this.getDraggableById(draggableId);
|
293
|
-
if (dragData) {
|
294
|
-
return dragData;
|
295
|
-
}
|
296
|
-
}
|
297
|
-
}
|
298
|
-
} catch (error) {
|
299
|
-
console.error('Error getting drag data:', error);
|
300
|
-
}
|
301
|
-
|
302
|
-
return null;
|
303
|
-
}
|
304
|
-
|
305
|
-
/**
|
306
|
-
* Get the most recently started drag operation (convenience method)
|
307
|
-
* @returns {import('$lib/typedef/drag.js').DragData|undefined}
|
308
|
-
*/
|
309
|
-
get current() {
|
310
|
-
const entries = Array.from(this.draggables.entries());
|
311
|
-
return entries.length > 0 ? entries[entries.length - 1][1] : undefined;
|
312
|
-
}
|
313
|
-
|
314
|
-
/**
|
315
|
-
* @returns {boolean}
|
316
|
-
*/
|
317
|
-
isDragging() {
|
318
|
-
return this.draggables.size > 0;
|
319
|
-
}
|
320
|
-
}
|
321
|
-
|
322
|
-
export const [createOrGetDragState, createDragState, getDragState] =
|
323
|
-
defineStateContext(DragState);
|