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;