diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index 328cd0ccc..dede6ef27 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -5,7 +5,7 @@ Change log
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
-- [7.0.2 (TBD)](#702-tbd)
+- [7.0.1-dev (TBD)](#701-dev-tbd)
- [7.0.1 (2022-10-14)](#701-2022-10-14)
- [7.0.0 (2022-10-09)](#700-2022-10-09)
- [6.0.3 (2022-10-08)](#603-2022-10-08)
@@ -75,9 +75,12 @@ Change log
-## 7.0.2 (TBD)
+## 7.0.1-dev (TBD)
+* add `GridStackEngine.findEmptyPosition()`
* fixed [#2081](https://github.com/gridstack/gridstack.js/issues/2081) removeWidget() after it's gone from DOM
-* add GridStackEngine.findEmptyPosition()
+* fixed [#1985](https://github.com/gridstack/gridstack.js/issues/1985) addWidget() or DOM read in single column mode will not adjust to multi column mode
+* fixed [#1975](https://github.com/gridstack/gridstack.js/issues/1975) oneColumnModeDomSort not respected when loading in 1 column
+
## 7.0.1 (2022-10-14)
* fixed [#2073](https://github.com/gridstack/gridstack.js/issues/2073) SSR (server side rendering) isTouch issue (introduced in v6)
* fixed - removing last item delete sub-grid that are not auto-generated (nested.html vs nested_advanced.html)
diff --git a/package.json b/package.json
index c34172767..2c62a694b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "gridstack",
"version": "7.0.1-dev",
- "description": "TypeScript/JS lib for dashboard layout and creation, mobile support, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)",
+ "description": "TypeScript/JS lib for dashboard layout and creation, responsive, mobile support, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)",
"main": "./dist/gridstack.js",
"types": "./dist/gridstack.d.ts",
"repository": {
diff --git a/spec/e2e/html/1985_read_1_column_wrong_12.html b/spec/e2e/html/1985_read_1_column_wrong_12.html
new file mode 100644
index 000000000..cf0cb5630
--- /dev/null
+++ b/spec/e2e/html/1985_read_1_column_wrong_12.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ read 1 column
+
+
+
+
+
+
+
+
read from dom into 1 column has wrong order and 12 column layout
+
+
1
+
0
+
2
+
+
+
+
+
diff --git a/src/gridstack-engine.ts b/src/gridstack-engine.ts
index 893f3a164..38afd4e01 100644
--- a/src/gridstack-engine.ts
+++ b/src/gridstack-engine.ts
@@ -37,7 +37,7 @@ export class GridStackEngine {
protected _float: boolean;
/** @internal */
protected _prevFloat: boolean;
- /** @internal cached layouts of difference column count so we can restore ack (eg 12 -> 1 -> 12) */
+ /** @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 true while we are resizing widgets during column resize to skip certain parts */
protected _inColumnResize: boolean;
@@ -362,7 +362,7 @@ export class GridStackEngine {
return this.nodeBoundFix(node, resizing);
}
- /** part2 of preparing a node to fit inside our grid - checks for x,y from grid dimensions */
+ /** part2 of preparing a node to fit inside our grid - checks for x,y,w from grid dimensions */
public nodeBoundFix(node: GridStackNode, resizing?: boolean): GridStackNode {
let before = node._orig || Utils.copyPos({}, node);
@@ -372,14 +372,18 @@ export class GridStackEngine {
if (node.minW && node.minW <= this.column) { node.w = Math.max(node.w, node.minW); }
if (node.minH) { node.h = Math.max(node.h, node.minH); }
+ // if user loaded a larger than allowed widget for current # of columns (or force 1 column mode),
+ // remember it's position & width so we can restore back (1 -> 12 column) #1655 #1985
+ // IFF we're not in the middle of column resizing!
+ const saveOrig = this.column === 1 || node.x + node.w > this.column;
+ if (saveOrig && this.column < 12 && !this._inColumnResize && !node.autoPosition && node._id && this.findCacheLayout(node, 12) === -1) {
+ let copy = {...node}; // need _id + positions
+ copy.x = Math.min(11, copy.x);
+ copy.w = Math.min(12, copy.w);
+ this.cacheOneLayout(copy, 12);
+ }
+
if (node.w > this.column) {
- // if user loaded a larger than allowed widget for current # of columns,
- // remember it's full width so we can restore back (1 -> 12 column) #1655
- // IFF we're not in the middle of column resizing!
- if (this.column < 12 && !this._inColumnResize) {
- node.w = Math.min(12, node.w);
- this.cacheOneLayout(node, 12);
- }
node.w = this.column;
} else if (node.w < 1) {
node.w = 1;
@@ -706,7 +710,7 @@ export class GridStackEngine {
return this;
}
- /** saves a copy of the largest column layout (eg 12 even when rendering oneColumnMode, so we don't loose orig layout),
+ /** saves a copy of the largest column layout (eg 12 even when rendering oneColumnMode) so we don't loose orig layout,
* returning a list of widgets for serialization */
public save(saveElement = true): GridStackNode[] {
// use the highest layout for any saved info so we can have full detail on reload #1849
@@ -852,7 +856,7 @@ export class GridStackEngine {
}
// finally re-layout them in reverse order (to get correct placement)
- newNodes = Utils.sort(newNodes, -1, column);
+ if (!domOrder) newNodes = Utils.sort(newNodes, -1, column);
this._inColumnResize = true; // prevent cache update
this.nodes = []; // pretend we have no nodes to start with (add() will use same structures) to simplify layout
newNodes.forEach(node => {
@@ -891,11 +895,18 @@ export class GridStackEngine {
let layout: GridStackNode = {x: n.x, y: n.y, w: n.w, _id: n._id}
this._layouts = this._layouts || [];
this._layouts[column] = this._layouts[column] || [];
- let index = this._layouts[column].findIndex(l => l._id === n._id);
- index === -1 ? this._layouts[column].push(layout) : this._layouts[column][index] = layout;
+ let index = this.findCacheLayout(n, column);
+ if (index === -1)
+ this._layouts[column].push(layout);
+ else
+ this._layouts[column][index] = layout;
return this;
}
+ protected findCacheLayout(n: GridStackNode, column: number): number {
+ return this._layouts?.[column]?.findIndex(l => l._id === n._id) ?? -1;
+ }
+
/** called to remove all internal values but the _id */
public cleanupNode(node: GridStackNode): GridStackEngine {
diff --git a/src/gridstack.ts b/src/gridstack.ts
index 0075f997f..da61b2aed 100644
--- a/src/gridstack.ts
+++ b/src/gridstack.ts
@@ -34,6 +34,9 @@ export interface CellPosition {
y: number;
}
+/** optional function called during load() to callback the user on new added/remove items */
+export type AddRemoveFcn = (g: GridStack, w: GridStackWidget, add: boolean) => GridItemHTMLElement;
+
interface GridCSSStyleSheet extends CSSStyleSheet {
_max?: number; // internal tracker of the max # of rows we created
}
@@ -186,7 +189,7 @@ export class GridStack {
protected _placeholder: HTMLElement;
/** @internal */
protected _prevColumn: number;
- /** @internal */
+ /** @internal prevent cached layouts from being updated when loading into small column layouts */
protected _ignoreLayoutsNodeChange: boolean;
/** @internal */
public _gsEventHandler = {};
@@ -328,16 +331,18 @@ export class GridStack {
if (this.opts.auto) {
this.batchUpdate(); // prevent in between re-layout #1535 TODO: this only set float=true, need to prevent collision check...
let elements: {el: HTMLElement; i: number}[] = [];
+ let column = this.getColumn();
+ if (column === 1 && this._prevColumn) column = this._prevColumn; // do 12 column when reading into 1 column mode
this.getGridItems().forEach(el => { // get dom elements (not nodes yet)
let x = parseInt(el.getAttribute('gs-x'));
let y = parseInt(el.getAttribute('gs-y'));
elements.push({
el,
// if x,y are missing (autoPosition) add them to end of list - but keep their respective DOM order
- i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * this.getColumn()
+ i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * column
});
});
- elements.sort((a, b) => a.i - b.i).forEach(e => this._prepareElement(e.el));
+ elements.sort((a, b) => b.i - a.i).forEach(e => this._prepareElement(e.el)); // revert sort so lowest item wins
this.batchUpdate(false);
}
@@ -421,8 +426,14 @@ export class GridStack {
this.makeSubGrid(node.el, undefined, undefined, false);
}
+ // if we're adding an item into 1 column (_prevColumn is set only when going to 1) make sure
+ // we don't override the larger 12 column layout that was already saved. #1985
+ if (this._prevColumn && this.opts.column === 1) {
+ this._ignoreLayoutsNodeChange = true;
+ }
this._triggerAddEvent();
this._triggerChangeEvent();
+ delete this._ignoreLayoutsNodeChange;
return el;
}
@@ -603,11 +614,11 @@ export class GridStack {
* @example
* see http://gridstackjs.com/demo/serialization.html
**/
- public load(layout: GridStackWidget[], addAndRemove: boolean | ((g: GridStack, w: GridStackWidget, add: boolean) => GridItemHTMLElement) = true): GridStack {
+ public load(layout: GridStackWidget[], addAndRemove: boolean | AddRemoveFcn = true): GridStack {
let items = GridStack.Utils.sort([...layout], -1, this._prevColumn || this.getColumn()); // make copy before we mod/sort
this._insertNotAppend = true; // since create in reverse order...
- // if we're loading a layout into 1 column (_prevColumn is set only when going to 1) and items don't fit, make sure to save
+ // if we're loading a layout into for example 1 column (_prevColumn is set only when going to 1) and items don't fit, make sure to save
// the original wanted layout so we can scale back up correctly #1471
if (this._prevColumn && this._prevColumn !== this.opts.column && items.some(n => (n.x + n.w) > this.opts.column)) {
this._ignoreLayoutsNodeChange = true; // skip layout update
diff --git a/src/utils.ts b/src/utils.ts
index 26dd5e3cc..06bb6d51f 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -236,7 +236,7 @@ export class Utils {
return true;
}
- /** copies over b size & position (GridStackPosition), and possibly min/max as well */
+ /** copies over b size & position (GridStackPosition), and optionally min/max as well */
static copyPos(a: GridStackWidget, b: GridStackWidget, doMinMax = false): GridStackWidget {
a.x = b.x;
a.y = b.y;