Skip to content

Commit d8bff3f

Browse files
committed
more fixes
1 parent e5b8021 commit d8bff3f

3 files changed

Lines changed: 25 additions & 37 deletions

File tree

src/components/pg/nodeResize/THINKING.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ anchor. The preview CSS vars must then represent the **remaining sub-grid offset
77
overlay tracks the cursor smoothly. If the formula is wrong, the overlay jumps by ~gridSize
88
pixels at each snap boundary.
99

10-
## dragUtil snap algorithm (asymmetric)
10+
## dragUtil snap algorithm (asymmetric — the root bug)
1111

1212
```
1313
snapDx = (lastDx + half < 0)
@@ -16,8 +16,18 @@ snapDx = (lastDx + half < 0)
1616
```
1717

1818
Because of ceil vs floor the first snap threshold is **asymmetric**:
19-
- Positive drag: first snap at +8 px (half a grid unit)
20-
- Negative drag: first snap at -24 px (1.5 grid units)
19+
- Positive drag: first snap at +8 px (half grid unit) ← works correctly (south)
20+
- Negative drag: first snap at -24 px (1.5 grid units) ← broken (north)
21+
22+
Negative example step-by-step with gridSize=16, half=8:
23+
dy=-8: (-8+8)/16 = 0/16 = 0, ceil(0) = 0 → no snap yet
24+
dy=-16: (-16+8)/16 = -8/16 = -0.5, ceil(-0.5) = 0 → still no snap
25+
dy=-24: (-24+8)/16 = -16/16 = -1, ceil(-1) = -1 → FIRST snap (at 1.5 grid units!)
26+
27+
**Fix: use Math.floor for ALL values** — this rounds toward −∞ symmetrically:
28+
dy=+8: floor((8+8)/16) = floor(1) = 1 → snap ✓
29+
dy=-8: floor((-8+8)/16) = floor(0) = 0 → no snap yet
30+
dy=-9: floor((-9+8)/16) = floor(-0.0625)= -1 → snap (≈half grid unit) ✓
2131

2232
## Correct sub-grid formula
2333

src/components/pg/nodeResize/dragUtil.ts

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ interface DragConfig {
77
end?: (dx: number, dy: number, complete: boolean) => void;
88
}
99

10+
function snapUnits(delta: number, gridSize: number): number {
11+
return Math.floor((delta + gridSize / 2) / gridSize);
12+
}
13+
1014
export function drag({ source, gridSize, start, move, snap, end }: DragConfig): () => void {
1115
let startX = 0;
1216
let startY = 0;
@@ -15,7 +19,6 @@ export function drag({ source, gridSize, start, move, snap, end }: DragConfig):
1519
let lastSnapDx = 0;
1620
let lastSnapDy = 0;
1721
let activePointerId: number | null = null;
18-
const halfGridSize = gridSize / 2;
1922

2023
const onPointerDown = (e: PointerEvent) => {
2124
e.preventDefault();
@@ -38,12 +41,8 @@ export function drag({ source, gridSize, start, move, snap, end }: DragConfig):
3841
lastDx = e.clientX - startX;
3942
lastDy = e.clientY - startY;
4043
move?.(lastDx, lastDy);
41-
const snapDx = lastDx + halfGridSize < 0
42-
? Math.ceil((lastDx + halfGridSize) / gridSize)
43-
: Math.floor((lastDx + halfGridSize) / gridSize);
44-
const snapDy = lastDy + halfGridSize < 0
45-
? Math.ceil((lastDy + halfGridSize) / gridSize)
46-
: Math.floor((lastDy + halfGridSize) / gridSize);
44+
const snapDx = snapUnits(lastDx, gridSize);
45+
const snapDy = snapUnits(lastDy, gridSize);
4746
if (snapDx !== lastSnapDx || snapDy !== lastSnapDy) {
4847
lastSnapDx = snapDx;
4948
lastSnapDy = snapDy;
@@ -52,40 +51,24 @@ export function drag({ source, gridSize, start, move, snap, end }: DragConfig):
5251
};
5352

5453
const onPointerUp = (e: PointerEvent) => {
55-
const dx = e.clientX + halfGridSize - startX < 0
56-
? Math.ceil((e.clientX + halfGridSize - startX) / gridSize)
57-
: Math.floor((e.clientX + halfGridSize - startX) / gridSize);
58-
const dy = e.clientY + halfGridSize - startY < 0
59-
? Math.ceil((e.clientY + halfGridSize - startY) / gridSize)
60-
: Math.floor((e.clientY + halfGridSize - startY) / gridSize);
54+
const dx = snapUnits(e.clientX - startX, gridSize);
55+
const dy = snapUnits(e.clientY - startY, gridSize);
6156
stopDrag();
6257
end?.(dx, dy, true);
6358
};
6459

6560
const onPointerCancel = () => {
66-
const dx = lastDx < 0
67-
? Math.ceil((lastDx + halfGridSize) / gridSize)
68-
: Math.floor((lastDx + halfGridSize) / gridSize);
69-
const dy = lastDy < 0
70-
? Math.ceil((lastDy + halfGridSize) / gridSize)
71-
: Math.floor((lastDy + halfGridSize) / gridSize);
7261
stopDrag();
73-
end?.(dx, dy, false);
62+
end?.(snapUnits(lastDx, gridSize), snapUnits(lastDy, gridSize), false);
7463
};
7564

7665
const onKeyDown = (e: KeyboardEvent) => {
7766
if (e.key !== 'Escape') { return; }
78-
const dx = lastDx < 0
79-
? Math.ceil((lastDx + halfGridSize) / gridSize)
80-
: Math.floor((lastDx + halfGridSize) / gridSize);
81-
const dy = lastDy < 0
82-
? Math.ceil((lastDy + halfGridSize) / gridSize)
83-
: Math.floor((lastDy + halfGridSize) / gridSize);
8467
if (activePointerId !== null) {
8568
source.releasePointerCapture(activePointerId);
8669
}
8770
stopDrag();
88-
end?.(dx, dy, false);
71+
end?.(snapUnits(lastDx, gridSize), snapUnits(lastDy, gridSize), false);
8972
};
9073

9174
const stopDrag = () => {

src/components/pg/nodeResize/nodeResize.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,10 @@ export default class PgNodeResize extends HTMLElement {
9191

9292
render(_changes: any) {}
9393

94-
// Mirrors dragUtil's snap: floor for positive deltas, ceil for negative.
95-
// Returns pixel residual past the last snap boundary so CSS preview tracks smoothly.
94+
// Returns the pixel residual past the last snap boundary (mirrors dragUtil's snapUnits).
9695
#subGrid(delta: number): number {
9796
const { gridSize } = this;
98-
const half = gridSize / 2;
99-
const snapped = delta + half < 0
100-
? Math.ceil((delta + half) / gridSize) * gridSize
101-
: Math.floor((delta + half) / gridSize) * gridSize;
102-
return delta - snapped;
97+
return delta - Math.floor((delta + gridSize / 2) / gridSize) * gridSize;
10398
}
10499

105100
#compute(

0 commit comments

Comments
 (0)