diff --git a/demo/two.html b/demo/two.html
index 5315e892f..5f9e53089 100644
--- a/demo/two.html
+++ b/demo/two.html
@@ -73,11 +73,13 @@
Two grids demo
let items = [
{x: 0, y: 0, w: 2, h: 2},
- {x: 3, y: 1, h: 2},
- {x: 4, y: 1},
- {x: 2, y: 3, w: 3, maxW: 3, id: 'special', content: 'has maxW=3'},
+ {x: 1, y: 1, h: 2}, // intentional overlap to test collision on load
+ {x: 1, y: 1}, // intentional overlap to test collision on load
+ {x: 3, y: 1},
+ {x: 2, y: 3, w: 3, maxW: 3, content: 'has maxW=3'},
{x: 2, y: 5}
];
+ items.forEach((item, i) => item.content = item.content || String(i));
grids.forEach(function (grid, i) {
addEvents(grid, i);
diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index 2814ca3a1..ba54b9656 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -5,6 +5,7 @@ Change log
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
+- [10.2.1-dev (TBD)](#1021-dev-tbd)
- [10.2.1 (2024-06-23)](#1021-2024-06-23)
- [10.2.0 (2024-06-02)](#1020-2024-06-02)
- [10.1.2 (2024-03-30)](#1012-2024-03-30)
@@ -111,6 +112,9 @@ Change log
+## 10.2.1-dev (TBD)
+* fix: [#2717](https://github.com/gridstack/gridstack.js/pull/2717) load() now creates in order
+
## 10.2.1 (2024-06-23)
* fix: [#2683](https://github.com/gridstack/gridstack.js/issues/2683) check for fixed grid maxRow during resize
* fix: [#2694](https://github.com/gridstack/gridstack.js/issues/2694) prevent 'r' rotation to items that can't resize (locked, noResize, fixed sizes)
diff --git a/src/gridstack-engine.ts b/src/gridstack-engine.ts
index cdf6e9658..a5565402b 100644
--- a/src/gridstack-engine.ts
+++ b/src/gridstack-engine.ts
@@ -39,6 +39,8 @@ export class GridStackEngine {
protected _prevFloat: boolean;
/** @internal cached layouts of difference column count so we can restore back (eg 12 -> 1 -> 12) */
protected _layouts?: GridStackNode[][]; // maps column # to array of values nodes
+ /** @internal set during loading (which is sorted) so item gets added AFTER collision nodes */
+ public _loading?: boolean
/** @internal true while we are resizing widgets during column resize to skip certain parts */
protected _inColumnResize?: boolean;
/** @internal true if we have some items locked */
@@ -91,7 +93,7 @@ export class GridStackEngine {
// during while() collisions MAKE SURE to check entire row so larger items don't leap frog small ones (push them all down starting last in grid)
let area = nn;
- if (this._useEntireRowArea(node, nn)) {
+ if (!this._loading && this._useEntireRowArea(node, nn)) {
area = {x: 0, w: this.column, y: nn.y, h: nn.h};
collide = this.collide(node, area, opt.skip); // force new hit
}
@@ -100,14 +102,14 @@ export class GridStackEngine {
let newOpt: GridStackMoveOpts = {nested: true, pack: false};
while (collide = collide || this.collide(node, area, opt.skip)) { // could collide with more than 1 item... so repeat for each
let moved: boolean;
- // if colliding with a locked item OR moving down with top gravity (and collide could move up) -> skip past the collide,
+ // if colliding with a locked item OR loading (move after) OR moving down with top gravity (and collide could move up) -> skip past the collide,
// but remember that skip down so we only do this once (and push others otherwise).
- if (collide.locked || node._moving && !node._skipDown && nn.y > node.y && !this.float &&
+ if (collide.locked || this._loading || node._moving && !node._skipDown && nn.y > node.y && !this.float &&
// can take space we had, or before where we're going
(!this.collide(collide, {...collide, y: node.y}, node) || !this.collide(collide, {...collide, y: nn.y - collide.h}, node))) {
node._skipDown = (node._skipDown || nn.y > node.y);
moved = this.moveNode(node, {...nn, y: collide.y + collide.h, ...newOpt});
- if (collide.locked && moved) {
+ if ((collide.locked || this._loading) && moved) {
Utils.copyPos(nn, node); // moving after lock become our new desired location
} else if (!collide.locked && moved && opt.pack) {
// we moved after and will pack: do it now and keep the original drop location, but past the old collide to see what else we might push way
diff --git a/src/gridstack.ts b/src/gridstack.ts
index 2be126bd3..7a825a2f9 100644
--- a/src/gridstack.ts
+++ b/src/gridstack.ts
@@ -252,8 +252,6 @@ export class GridStack {
protected _sizeThrottle: () => void;
/** @internal limit auto cell resizing method */
protected prevWidth: number;
- /** @internal true when loading items to insert first rather than append */
- protected _insertNotAppend: boolean;
/** @internal extra row added when dragging at the bottom of the grid */
protected _extraDragRow = 0;
/** @internal true if nested grid should get column count from our width */
@@ -421,7 +419,7 @@ export class GridStack {
// load any passed in children as well, which overrides any DOM layout done above
if (opts.children) {
- let children = opts.children;
+ const children = opts.children;
delete opts.children;
if (children.length) this.load(children); // don't load empty
}
@@ -495,11 +493,7 @@ export class GridStack {
node = this.engine.prepareNode(options);
this._writeAttr(el, options);
- if (this._insertNotAppend) {
- this.el.prepend(el);
- } else {
- this.el.appendChild(el);
- }
+ this.el.appendChild(el);
this.makeWidget(el, options);
@@ -703,22 +697,16 @@ export class GridStack {
// make sure size 1x1 (default) is present as it may need to override current sizes
items.forEach(n => { n.w = n.w || 1; n.h = n.h || 1 });
- // if we have a mix of new items without coordinates and existing items, separate them out so they can be added after #2639
- let addAfter = items.filter(n => (n.x === undefined || n.y === undefined) && !Utils.find(this.engine.nodes, n.id));
- if (addAfter.length && addAfter.length !== items.length) {
- items = items.filter(n => !Utils.find(addAfter, n.id));
- } else addAfter = [];
-
- // if passed list has coordinates, use them (insert from end to beginning for conflict resolution) else keep widget order
- const haveCoord = items.some(w => w.x !== undefined || w.y !== undefined);
- if (haveCoord) items = Utils.sort(items, -1);
- this._insertNotAppend = haveCoord; // if we create in reverse order...
+ // sort items. those without coord will be appended last
+ items = Utils.sort(items);
// if we're loading a layout into for example 1 column and items don't fit, make sure to save
// the original wanted layout so we can scale back up correctly #1471
- if (items.some(n => ((n.x || 0) + (n.w || 1)) > column)) {
+ let maxColumn = 0;
+ items.forEach(n => { maxColumn = Math.max(maxColumn, (n.x || 0) + n.w) });
+ if (maxColumn > column) {
this._ignoreLayoutsNodeChange = true; // skip layout update
- this.engine.cacheLayout(items, 12, true); // TODO: 12 is arbitrary. use max value in layout ?
+ this.engine.cacheLayout(items, maxColumn, true);
}
// if given a different callback, temporally set it as global option so creating will use it
@@ -728,19 +716,18 @@ export class GridStack {
let removed: GridStackNode[] = [];
this.batchUpdate();
- // if we are blank (loading into empty like startup) temp remove animation
- const noAnim = !this.engine.nodes.length;
- if (noAnim) this.setAnimation(false);
+ // if we are loading from empty temporarily remove animation
+ const blank = !this.engine.nodes.length;
+ if (blank) this.setAnimation(false);
// see if any items are missing from new layout and need to be removed first
- if (addRemove) {
+ if (!blank && addRemove) {
let copyNodes = [...this.engine.nodes]; // don't loop through array you modify
copyNodes.forEach(n => {
if (!n.id) return;
let item = Utils.find(items, n.id);
if (!item) {
- if (GridStack.addRemoveCB)
- GridStack.addRemoveCB(this.el, n, false, false);
+ if (GridStack.addRemoveCB) GridStack.addRemoveCB(this.el, n, false, false);
removed.push(n); // batch keep track
this.removeWidget(n.el, true, false);
}
@@ -749,6 +736,7 @@ export class GridStack {
// now add/update the widgets - starting with removing items in the new layout we will reposition
// to reduce collision and add no-coord ones at next available spot
+ this.engine._loading = true; // help with collision
let updateNodes: GridStackWidget[] = [];
this.engine.nodes = this.engine.nodes.filter(n => {
if (Utils.find(items, n.id)) { updateNodes.push(n); return false; } // remove if found from list
@@ -778,7 +766,6 @@ export class GridStack {
let sub = item.el.querySelector('.grid-stack') as GridHTMLElement;
if (sub && sub.gridstack) {
sub.gridstack.load(w.subGridOpts.children); // TODO: support updating grid options ?
- this._insertNotAppend = true; // got reset by above call
}
}
} else if (addRemove) {
@@ -786,20 +773,15 @@ export class GridStack {
}
});
- // finally append any separate ones that didn't have explicit coordinates last so they can find next empty spot
- if (addRemove) {
- addAfter.forEach(w => this.addWidget(w))
- }
-
+ delete this.engine._loading; // done loading
this.engine.removedNodes = removed;
this.batchUpdate(false);
// after commit, clear that flag
delete this._ignoreLayoutsNodeChange;
- delete this._insertNotAppend;
prevCB ? GridStack.addRemoveCB = prevCB : delete GridStack.addRemoveCB;
// delay adding animation back
- if (noAnim && this.opts?.animate) this.setAnimation(this.opts.animate, true);
+ if (blank && this.opts?.animate) this.setAnimation(this.opts.animate, true);
return this;
}