@griddle/vue 0.1.2 → 0.1.4
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/GriddleGrid.vue.d.ts.map +1 -1
- package/dist/index.js +576 -540
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/GriddleGrid.vue +111 -45
package/src/GriddleGrid.vue
CHANGED
|
@@ -191,6 +191,7 @@ function setSelection(next: Set<string>) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
const DEFAULT_DRAG_IGNORE = 'a, button, input, textarea, select, [contenteditable]';
|
|
194
|
+
const DRAG_START_THRESHOLD_PX = 12;
|
|
194
195
|
|
|
195
196
|
const dragController = new DragController(props.api.grid);
|
|
196
197
|
const groupDragController = new GroupDragController(props.api.grid);
|
|
@@ -249,18 +250,35 @@ interface DrawState {
|
|
|
249
250
|
}
|
|
250
251
|
const drawState = ref<DrawState | null>(null);
|
|
251
252
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
interface PendingDragState {
|
|
254
|
+
pointerId: number;
|
|
255
|
+
tileId: string;
|
|
256
|
+
tileElement: HTMLDivElement;
|
|
257
|
+
startPointerX: number;
|
|
258
|
+
startPointerY: number;
|
|
259
|
+
mode: 'pin' | 'group' | 'single';
|
|
260
|
+
groupTileIds: string[];
|
|
261
|
+
}
|
|
262
|
+
let pendingDrag: PendingDragState | null = null;
|
|
263
|
+
|
|
264
|
+
function beginPendingDrag(pending: PendingDragState): boolean {
|
|
265
|
+
const tile = props.api.grid.getTile(pending.tileId);
|
|
266
|
+
if (!tile) return false;
|
|
267
|
+
|
|
268
|
+
// Capture only once movement proves this is a drag. Capturing on
|
|
269
|
+
// pointer-down retargets pointer-up away from editable descendants and turns
|
|
270
|
+
// ordinary clicks into drag start/end cycles.
|
|
271
|
+
try {
|
|
272
|
+
pending.tileElement.setPointerCapture(pending.pointerId);
|
|
273
|
+
} catch {
|
|
274
|
+
// The pointer may already have ended or the tile may have unmounted.
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
255
277
|
|
|
256
|
-
|
|
257
|
-
|
|
278
|
+
dragStartPointerX = pending.startPointerX;
|
|
279
|
+
dragStartPointerY = pending.startPointerY;
|
|
258
280
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// Out-of-flow tiles get free-pixel drag (no selection).
|
|
262
|
-
if (config.value.enablePositioning && isOutOfFlow(tile)) {
|
|
263
|
-
(e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
|
|
281
|
+
if (pending.mode === 'pin') {
|
|
264
282
|
const layout = computeTileLayout({
|
|
265
283
|
tile,
|
|
266
284
|
config: config.value,
|
|
@@ -276,10 +294,61 @@ function onTilePointerDown(e: PointerEvent, tile: Tile) {
|
|
|
276
294
|
pinDrag.value = {
|
|
277
295
|
tileId: tile.id,
|
|
278
296
|
startPinPx,
|
|
297
|
+
startPointerX: pending.startPointerX,
|
|
298
|
+
startPointerY: pending.startPointerY,
|
|
299
|
+
};
|
|
300
|
+
emit('dragStart', tile.id);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (pending.mode === 'group') {
|
|
305
|
+
if (!groupDragController.start(pending.groupTileIds)) return false;
|
|
306
|
+
groupDrag.value = {
|
|
307
|
+
tileIds: pending.groupTileIds,
|
|
308
|
+
deltaX: 0,
|
|
309
|
+
deltaY: 0,
|
|
310
|
+
committedDcol: 0,
|
|
311
|
+
committedDrow: 0,
|
|
312
|
+
};
|
|
313
|
+
emit('dragStart', tile.id);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!dragController.start(tile.id)) return false;
|
|
318
|
+
drag.value = {
|
|
319
|
+
tileId: tile.id,
|
|
320
|
+
pickupCol: tile.col,
|
|
321
|
+
pickupRow: tile.row,
|
|
322
|
+
deltaX: 0,
|
|
323
|
+
deltaY: 0,
|
|
324
|
+
indicatorCol: tile.col,
|
|
325
|
+
indicatorRow: tile.row,
|
|
326
|
+
};
|
|
327
|
+
emit('dragStart', tile.id);
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function onTilePointerDown(e: PointerEvent, tile: Tile) {
|
|
332
|
+
if (e.button !== 0) return;
|
|
333
|
+
if (tile.draggable === false) return;
|
|
334
|
+
if ((e.target as HTMLElement).dataset.griddleHandle) return;
|
|
335
|
+
|
|
336
|
+
const ignoreSelector = config.value.dragIgnoreFrom ?? DEFAULT_DRAG_IGNORE;
|
|
337
|
+
if (ignoreSelector && (e.target as HTMLElement).closest(ignoreSelector)) return;
|
|
338
|
+
|
|
339
|
+
const metaKey = e.metaKey || e.ctrlKey;
|
|
340
|
+
|
|
341
|
+
// Out-of-flow tiles get free-pixel drag (no selection).
|
|
342
|
+
if (config.value.enablePositioning && isOutOfFlow(tile)) {
|
|
343
|
+
pendingDrag = {
|
|
344
|
+
pointerId: e.pointerId,
|
|
345
|
+
tileId: tile.id,
|
|
346
|
+
tileElement: e.currentTarget as HTMLDivElement,
|
|
279
347
|
startPointerX: e.clientX,
|
|
280
348
|
startPointerY: e.clientY,
|
|
349
|
+
mode: 'pin',
|
|
350
|
+
groupTileIds: [],
|
|
281
351
|
};
|
|
282
|
-
emit('dragStart', tile.id);
|
|
283
352
|
e.stopPropagation();
|
|
284
353
|
return;
|
|
285
354
|
}
|
|
@@ -304,42 +373,20 @@ function onTilePointerDown(e: PointerEvent, tile: Tile) {
|
|
|
304
373
|
setSelection(new Set([tile.id]));
|
|
305
374
|
}
|
|
306
375
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// Group drag if 2+ tiles selected including this one.
|
|
376
|
+
// Defer capture and drag state until the pointer crosses the movement
|
|
377
|
+
// threshold. A stationary pointer-down/up remains a normal click, including
|
|
378
|
+
// when it starts inside contenteditable tile content.
|
|
311
379
|
const effectiveSelection = tileIsSelected ? selection.value : new Set([tile.id]);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
dragStartPointerX = e.clientX;
|
|
316
|
-
dragStartPointerY = e.clientY;
|
|
317
|
-
groupDrag.value = {
|
|
318
|
-
tileIds: ids,
|
|
319
|
-
deltaX: 0,
|
|
320
|
-
deltaY: 0,
|
|
321
|
-
committedDcol: 0,
|
|
322
|
-
committedDrow: 0,
|
|
323
|
-
};
|
|
324
|
-
emit('dragStart', tile.id);
|
|
325
|
-
e.stopPropagation();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Single tile drag.
|
|
330
|
-
if (!dragController.start(tile.id)) return;
|
|
331
|
-
dragStartPointerX = e.clientX;
|
|
332
|
-
dragStartPointerY = e.clientY;
|
|
333
|
-
drag.value = {
|
|
380
|
+
const groupTileIds = Array.from(effectiveSelection);
|
|
381
|
+
pendingDrag = {
|
|
382
|
+
pointerId: e.pointerId,
|
|
334
383
|
tileId: tile.id,
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
indicatorRow: tile.row,
|
|
384
|
+
tileElement: e.currentTarget as HTMLDivElement,
|
|
385
|
+
startPointerX: e.clientX,
|
|
386
|
+
startPointerY: e.clientY,
|
|
387
|
+
mode: groupTileIds.length > 1 ? 'group' : 'single',
|
|
388
|
+
groupTileIds,
|
|
341
389
|
};
|
|
342
|
-
emit('dragStart', tile.id);
|
|
343
390
|
e.stopPropagation();
|
|
344
391
|
}
|
|
345
392
|
|
|
@@ -395,6 +442,20 @@ function onPointerMove(e: PointerEvent) {
|
|
|
395
442
|
}
|
|
396
443
|
return;
|
|
397
444
|
}
|
|
445
|
+
|
|
446
|
+
const pending = pendingDrag;
|
|
447
|
+
if (pending && e.pointerId === pending.pointerId) {
|
|
448
|
+
const distance = Math.hypot(
|
|
449
|
+
e.clientX - pending.startPointerX,
|
|
450
|
+
e.clientY - pending.startPointerY,
|
|
451
|
+
);
|
|
452
|
+
if (distance <= DRAG_START_THRESHOLD_PX) return;
|
|
453
|
+
|
|
454
|
+
pendingDrag = null;
|
|
455
|
+
if (!beginPendingDrag(pending)) return;
|
|
456
|
+
e.preventDefault();
|
|
457
|
+
}
|
|
458
|
+
|
|
398
459
|
const pd = pinDrag.value;
|
|
399
460
|
const gd = groupDrag.value;
|
|
400
461
|
const d = drag.value;
|
|
@@ -472,7 +533,11 @@ function syncTiles() {
|
|
|
472
533
|
props.api.version.value++;
|
|
473
534
|
}
|
|
474
535
|
|
|
475
|
-
function onPointerUp() {
|
|
536
|
+
function onPointerUp(e: PointerEvent) {
|
|
537
|
+
if (pendingDrag?.pointerId === e.pointerId) {
|
|
538
|
+
pendingDrag = null;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
476
541
|
if (drawState.value) {
|
|
477
542
|
const ds = drawState.value;
|
|
478
543
|
const col = Math.min(ds.anchorCol, ds.currentCol);
|
|
@@ -554,6 +619,7 @@ onMounted(() => {
|
|
|
554
619
|
window.addEventListener('keydown', onKeyDown);
|
|
555
620
|
});
|
|
556
621
|
onBeforeUnmount(() => {
|
|
622
|
+
pendingDrag = null;
|
|
557
623
|
const el = scrollEl.value;
|
|
558
624
|
if (el) el.removeEventListener('scroll', updateViewport);
|
|
559
625
|
ro?.disconnect();
|