@griddle/vue 0.1.6 → 0.1.7

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.
@@ -112,6 +112,11 @@ import {
112
112
  import type { TileLayout } from '@griddle/core';
113
113
  import type { GriddleApi } from './useGriddle.js';
114
114
  import { animateReposition, liftTransition } from './animation.js';
115
+ import {
116
+ measureInteractionScale,
117
+ toLocalInteractionDelta,
118
+ type InteractionScale,
119
+ } from './interaction.js';
115
120
 
116
121
  const props = defineProps<{
117
122
  api: GriddleApi;
@@ -208,6 +213,7 @@ interface DragVisual {
208
213
  const drag = ref<DragVisual | null>(null);
209
214
  let dragStartPointerX = 0;
210
215
  let dragStartPointerY = 0;
216
+ let interactionScale: InteractionScale = { x: 1, y: 1 };
211
217
 
212
218
  interface GroupDragVisual {
213
219
  tileIds: string[];
@@ -277,6 +283,7 @@ function beginPendingDrag(pending: PendingDragState): boolean {
277
283
 
278
284
  dragStartPointerX = pending.startPointerX;
279
285
  dragStartPointerY = pending.startPointerY;
286
+ interactionScale = measureInteractionScale(scrollEl.value);
280
287
 
281
288
  if (pending.mode === 'pin') {
282
289
  const layout = computeTileLayout({
@@ -411,6 +418,7 @@ function onBackgroundPointerDown(e: PointerEvent) {
411
418
  function onResizeHandleDown(e: PointerEvent, tile: Tile, c: Corner) {
412
419
  if (tile.resizable === false) return;
413
420
  (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
421
+ interactionScale = measureInteractionScale(scrollEl.value);
414
422
  resize.value = {
415
423
  tileId: tile.id,
416
424
  corner: c,
@@ -461,15 +469,21 @@ function onPointerMove(e: PointerEvent) {
461
469
  const d = drag.value;
462
470
  const r = resize.value;
463
471
  if (pd) {
464
- const dx = e.clientX - pd.startPointerX;
465
- const dy = e.clientY - pd.startPointerY;
472
+ const { dx, dy } = toLocalInteractionDelta(
473
+ e.clientX - pd.startPointerX,
474
+ e.clientY - pd.startPointerY,
475
+ interactionScale,
476
+ );
466
477
  const newPinPx = { x: pd.startPinPx.x + dx, y: pd.startPinPx.y + dy };
467
478
  const newPin = pixelsToPin(newPinPx, config.value);
468
479
  props.api.grid.setTilePinned(pd.tileId, newPin);
469
480
  }
470
481
  if (gd) {
471
- const dx = e.clientX - dragStartPointerX;
472
- const dy = e.clientY - dragStartPointerY;
482
+ const { dx, dy } = toLocalInteractionDelta(
483
+ e.clientX - dragStartPointerX,
484
+ e.clientY - dragStartPointerY,
485
+ interactionScale,
486
+ );
473
487
  const dcol = Math.round(dx / colSize.value);
474
488
  const drow = Math.round(dy / rowSize.value);
475
489
  const result = groupDragController.update({ dcol, drow });
@@ -483,8 +497,11 @@ function onPointerMove(e: PointerEvent) {
483
497
  if (result.changed) syncTiles();
484
498
  }
485
499
  if (d) {
486
- const dx = e.clientX - dragStartPointerX;
487
- const dy = e.clientY - dragStartPointerY;
500
+ const { dx, dy } = toLocalInteractionDelta(
501
+ e.clientX - dragStartPointerX,
502
+ e.clientY - dragStartPointerY,
503
+ interactionScale,
504
+ );
488
505
  const candidateCol = d.pickupCol + Math.round(dx / colSize.value);
489
506
  const candidateRow = d.pickupRow + Math.round(dy / rowSize.value);
490
507
  const result = dragController.update({ col: candidateCol, row: candidateRow });
@@ -501,8 +518,11 @@ function onPointerMove(e: PointerEvent) {
501
518
  if (result.changed) syncTiles();
502
519
  }
503
520
  if (r) {
504
- const dx = e.clientX - r.startPointerX;
505
- const dy = e.clientY - r.startPointerY;
521
+ const { dx, dy } = toLocalInteractionDelta(
522
+ e.clientX - r.startPointerX,
523
+ e.clientY - r.startPointerY,
524
+ interactionScale,
525
+ );
506
526
  let dw = 0, dh = 0, dcol = 0, drow = 0;
507
527
  const stepsX = Math.round(dx / colSize.value);
508
528
  const stepsY = Math.round(dy / rowSize.value);
@@ -0,0 +1,53 @@
1
+ export interface InteractionScale {
2
+ x: number;
3
+ y: number;
4
+ }
5
+
6
+ type MeasurableElement = Pick<
7
+ HTMLElement,
8
+ 'getBoundingClientRect' | 'offsetWidth' | 'offsetHeight'
9
+ >;
10
+
11
+ function validScale(value: number): number | null {
12
+ return Number.isFinite(value) && value > 0 ? value : null;
13
+ }
14
+
15
+ /**
16
+ * Measure how CSS transforms map an element's local coordinate space into
17
+ * viewport pixels. PointerEvent.clientX/clientY are viewport coordinates,
18
+ * while Griddle's cells and inline transforms use the element's local CSS
19
+ * pixels, so drag math must cross this boundary explicitly.
20
+ */
21
+ export function measureInteractionScale(
22
+ element: MeasurableElement | null,
23
+ ): InteractionScale {
24
+ if (!element) return { x: 1, y: 1 };
25
+
26
+ const rect = element.getBoundingClientRect();
27
+ const measuredX = validScale(
28
+ element.offsetWidth > 0 ? rect.width / element.offsetWidth : NaN,
29
+ );
30
+ const measuredY = validScale(
31
+ element.offsetHeight > 0 ? rect.height / element.offsetHeight : NaN,
32
+ );
33
+
34
+ // A content-sized grid can temporarily report a zero height while mounting.
35
+ // Prefer the other measured axis before falling back to an unscaled value;
36
+ // this also handles the common uniform transform: scale(...) host.
37
+ return {
38
+ x: measuredX ?? measuredY ?? 1,
39
+ y: measuredY ?? measuredX ?? 1,
40
+ };
41
+ }
42
+
43
+ /** Convert a viewport-space pointer delta into the grid's local CSS pixels. */
44
+ export function toLocalInteractionDelta(
45
+ dx: number,
46
+ dy: number,
47
+ scale: InteractionScale,
48
+ ): { dx: number; dy: number } {
49
+ return {
50
+ dx: dx / scale.x,
51
+ dy: dy / scale.y,
52
+ };
53
+ }