fix: preserve position during SE/S/E resize (prevent rounding drift)#3241
Merged
adumesny merged 1 commit intogridstack:masterfrom Mar 19, 2026
Merged
Conversation
During resize events, _dragOrResize unconditionally recalculates x/y
from pixel positions for all handle directions. For SE/S/E handles,
the top-left corner is anchored and should never move. The pixel→grid
round-trip through Math.round() introduces rounding errors that cause
position drift, especially on fine grids with small cell sizes (e.g.
column:480, cellHeight:'0.38vh' ≈ 3.5px per cell).
This fix:
- Passes the resize handle direction (dir) from DDResizable._resizing()
through the event object as event.resizeDir
- In _dragOrResize, only recalculates x/y when the handle moves the
top-left corner (contains 'n' or 'w')
- For SE/S/E handles, preserves the original position from node._orig
Since p is initialized from { ...node._orig }, x/y naturally keep their
original values when not overwritten.
Fixes gridstack#385, relates to gridstack#811, gridstack#1356
adumesny
approved these changes
Mar 19, 2026
Member
adumesny
left a comment
There was a problem hiding this comment.
thanks for the workaround. Still not sold why there are expected rounding errors, which would affect draggin from N/W then...
| this.temporalRect = this._getChange(event, dir); | ||
| this._applyChange(); | ||
| const ev = Utils.initEvent<MouseEvent>(event, { type: 'resize', target: this.el }); | ||
| (ev as any).resizeDir = dir; // expose handle direction so _dragOrResize can avoid position drift |
Member
There was a problem hiding this comment.
I'll add a custom type for proper TS support.
adumesny
added a commit
to adumesny/gridstack.js
that referenced
this pull request
Mar 19, 2026
* added proper type checking to gridstack#3241
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
During resize events,
_dragOrResizeunconditionally recalculatesxandyfrom pixel positions for all resize handle directions. For SE/S/E handles, the top-left corner is anchored and should never move. The pixel → grid round-trip throughMath.round()introduces rounding errors that cause position drift.History
This was introduced in commit a13fad9 (April 18, 2021, v4.2.1) — "fix sizing from top/left" (#1728, #1731). The previous code only handled left-side resizing with a targeted comparison. The fix replaced it with an unconditional
Math.round(left / cellWidth)andMath.round(top / cellHeight)for all handles — correctly fixing N/W resize but introducing a rounding drift regression for SE/S/E handles.Root Cause
The comment is correct — this is needed for N/W/NW/NE/SW handles. But for SE/S/E handles the top-left is anchored and should remain
node._orig.x/node._orig.y.Why it drifts
The pixel → grid → pixel round-trip is lossy:
This is visible on any grid where
cellHeightorcellWidthis small enough forMath.round()to tip over — not just extreme fine grids. Fractional cellHeight from vh/rem units also triggers it on standard column counts.Reproduction
Fix
Two small changes:
dd-resizable.ts: Expose the resize handle direction by attachingresizeDirto the synthesized event in_resizing()gridstack.ts: In_dragOrResize, only recalculatex/ywhen the handle actually moves the top-left corner (direction contains'n'or'w'). For SE/S/E handles,palready has the correct values from{ ...node._orig }.Testing
cellHeight: '0.38vh'): SE resize no longer drifts positionRelated Issues