Skip to content

Commit 8fe1714

Browse files
authored
Merge pull request #3398 from adumesny/3012-resizeUpdate
* fix #3012 keep node._orig during a gesture so the stop event can't crash
2 parents 1afbfe5 + 5d7c51d commit 8fe1714

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

doc/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ Change log
150150
## 14.0.0 (TBD)
151151
* feat: [#754](https://github.com/gridstack/gridstack.js/issues/754) new `mode?: 'top' | 'float' | 'list' | 'compact'` - items are continuously re-flowed in sequential (row-major) order, like a re-orderable list: dragging, resizing, adding or removing an item re-flows everyone else instead of pushing them down, and dropping an item on another takes its place. See new
152152
[list.html](https://gridstackjs.com/demo/list.html) demo.
153+
* fix: [#3012](https://github.com/gridstack/gridstack.js/issues/3012) prevent crash when calling update() during change event
153154

154155
## 13.3.0 (2026-09-11)
155156
* feat: [#2781](https://github.com/gridstack/gridstack.js/issues/3177) [#2781](https://github.com/gridstack/gridstack.js/issues/3177) mobile: pause to drag/reszie vs scroll behavior

spec/regression-spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,41 @@ describe('regression >', () => {
256256
grid.engine.endUpdate();
257257
});
258258
});
259+
260+
describe('3012 update() from inside a drag/resize handler >', () => {
261+
beforeEach(() => {
262+
document.body.insertAdjacentHTML('afterbegin', gridstackEmptyHTML);
263+
});
264+
afterEach(() => {
265+
document.body.removeChild(document.getElementById('gs-cont'));
266+
});
267+
268+
it('keeps node._orig alive so the stop event does not crash', () => {
269+
grid = GridStack.init({cellHeight: 50, children: [{id: 'A', x: 0, y: 0, w: 3, h: 3}]});
270+
const A = grid.engine.nodes.find(n => n.id === 'A')!;
271+
272+
grid.engine.cleanNodes().beginUpdate(A); // resizestart
273+
expect(A._updating).toBe(true);
274+
expect(A._orig).toEqual({x: 0, y: 0, w: 3, h: 3});
275+
276+
// the aspect-ratio handler: update() from inside the 'resize' event
277+
grid.update(A.el!, {h: 4});
278+
expect(A.h).toBe(4);
279+
// used to be deleted here, then onEndMoving did `node._orig!.w` -> TypeError
280+
expect(A._orig).toEqual({x: 0, y: 0, w: 3, h: 3});
281+
expect(() => A.w !== A._orig!.w).not.toThrow();
282+
283+
grid.engine.endUpdate();
284+
});
285+
286+
it('still clears _orig for a plain update() outside a gesture (#2669)', () => {
287+
grid = GridStack.init({cellHeight: 50, children: [{id: 'A', x: 0, y: 0, w: 3, h: 3}]});
288+
const A = grid.engine.nodes.find(n => n.id === 'A')!;
289+
grid.engine.saveInitial();
290+
expect(A._orig).toBeDefined();
291+
grid.update(A.el!, {h: 4});
292+
expect(A._updating).toBeFalsy();
293+
expect(A._orig).toBeUndefined();
294+
});
295+
});
259296
});

src/gridstack.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,7 +1653,10 @@ export class GridStack {
16531653
} else {
16541654
this.resizeToContentCheck(widthChanged, n);
16551655
}
1656-
delete n._orig; // clear out original position now that we moved #2669
1656+
// clear out original position now that we moved #2669 - but NOT mid drag/resize, where _orig is
1657+
// the gesture's baseline that _dragOrResize/onEndMoving still need. A user calling update() from
1658+
// a 'drag'/'resize' handler used to delete it and crash on the following stop event (#3012).
1659+
if (!n._updating) delete n._orig;
16571660
}
16581661
if (m || changed) {
16591662
this._writeAttr(el, n);
@@ -2919,7 +2922,7 @@ export class GridStack {
29192922
delete node._resizing;
29202923
delete node._event;
29212924
delete node._lastTried;
2922-
const widthChanged = node.w !== node._orig!.w;
2925+
const widthChanged = !!node._orig && node.w !== node._orig.w;
29232926

29242927
// if the item has moved to another grid, we're done here
29252928
const target: GridItemHTMLElement = event.target as GridItemHTMLElement;

0 commit comments

Comments
 (0)