gridstack
Version:
TypeScript/JS lib for dashboard layout and creation, responsive, mobile support, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)
1,012 lines (1,011 loc) • 132 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GridStack = void 0;
/*!
* GridStack 12.0.0
* https://gridstackjs.com/
*
* Copyright (c) 2021-2024 Alain Dumesny
* see root license https://github.com/gridstack/gridstack.js/tree/master/LICENSE
*/
var gridstack_engine_1 = require("./gridstack-engine");
var utils_1 = require("./utils");
var types_1 = require("./types");
/*
* and include D&D by default
* TODO: while we could generate a gridstack-static.js at smaller size - saves about 31k (41k -> 72k)
* I don't know how to generate the DD only code at the remaining 31k to delay load as code depends on Gridstack.ts
* also it caused loading issues in prod - see https://github.com/gridstack/gridstack.js/issues/2039
*/
var dd_gridstack_1 = require("./dd-gridstack");
var dd_touch_1 = require("./dd-touch");
var dd_manager_1 = require("./dd-manager");
var dd = new dd_gridstack_1.DDGridStack;
// export all dependent file as well to make it easier for users to just import the main file
__exportStar(require("./types"), exports);
__exportStar(require("./utils"), exports);
__exportStar(require("./gridstack-engine"), exports);
__exportStar(require("./dd-gridstack"), exports);
/**
* Main gridstack class - you will need to call `GridStack.init()` first to initialize your grid.
* Note: your grid elements MUST have the following classes for the CSS layout to work:
* @example
* <div class="grid-stack">
* <div class="grid-stack-item">
* <div class="grid-stack-item-content">Item 1</div>
* </div>
* </div>
*/
var GridStack = exports.GridStack = /** @class */ (function () {
/**
* Construct a grid item from the given element and options
* @param el the HTML element tied to this grid after it's been initialized
* @param opts grid options - public for classes to access, but use methods to modify!
*/
function GridStack(el, opts) {
if (opts === void 0) { opts = {}; }
var _this = this;
var _a, _b, _c;
this.el = el;
this.opts = opts;
/** time to wait for animation (if enabled) to be done so content sizing can happen */
this.animationDelay = 300 + 10;
/** @internal */
this._gsEventHandler = {};
/** @internal extra row added when dragging at the bottom of the grid */
this._extraDragRow = 0;
/** @internal meant to store the scale of the active grid */
this.dragTransform = { xScale: 1, yScale: 1, xOffset: 0, yOffset: 0 };
el.gridstack = this;
this.opts = opts = opts || {}; // handles null/undefined/0
if (!el.classList.contains('grid-stack')) {
this.el.classList.add('grid-stack');
}
// if row property exists, replace minRow and maxRow instead
if (opts.row) {
opts.minRow = opts.maxRow = opts.row;
delete opts.row;
}
var rowAttr = utils_1.Utils.toNumber(el.getAttribute('gs-row'));
// flag only valid in sub-grids (handled by parent, not here)
if (opts.column === 'auto') {
delete opts.column;
}
// save original setting so we can restore on save
if (opts.alwaysShowResizeHandle !== undefined) {
opts._alwaysShowResizeHandle = opts.alwaysShowResizeHandle;
}
var bk = (_a = opts.columnOpts) === null || _a === void 0 ? void 0 : _a.breakpoints;
// LEGACY: oneColumnMode stuff changed in v10.x - check if user explicitly set something to convert over
var oldOpts = opts;
if (oldOpts.oneColumnModeDomSort) {
delete oldOpts.oneColumnModeDomSort;
console.log('warning: Gridstack oneColumnModeDomSort no longer supported. Use GridStackOptions.columnOpts instead.');
}
if (oldOpts.oneColumnSize || oldOpts.disableOneColumnMode === false) {
var oneSize = oldOpts.oneColumnSize || 768;
delete oldOpts.oneColumnSize;
delete oldOpts.disableOneColumnMode;
opts.columnOpts = opts.columnOpts || {};
bk = opts.columnOpts.breakpoints = opts.columnOpts.breakpoints || [];
var oneColumn = bk.find(function (b) { return b.c === 1; });
if (!oneColumn) {
oneColumn = { c: 1, w: oneSize };
bk.push(oneColumn, { c: 12, w: oneSize + 1 });
}
else
oneColumn.w = oneSize;
}
//...end LEGACY
// cleanup responsive opts (must have columnWidth | breakpoints) then sort breakpoints by size (so we can match during resize)
var resp = opts.columnOpts;
if (resp) {
if (!resp.columnWidth && !((_b = resp.breakpoints) === null || _b === void 0 ? void 0 : _b.length)) {
delete opts.columnOpts;
bk = undefined;
}
else {
resp.columnMax = resp.columnMax || 12;
}
}
if ((bk === null || bk === void 0 ? void 0 : bk.length) > 1)
bk.sort(function (a, b) { return (b.w || 0) - (a.w || 0); });
// elements DOM attributes override any passed options (like CSS style) - merge the two together
var defaults = __assign(__assign({}, utils_1.Utils.cloneDeep(types_1.gridDefaults)), { column: utils_1.Utils.toNumber(el.getAttribute('gs-column')) || types_1.gridDefaults.column, minRow: rowAttr ? rowAttr : utils_1.Utils.toNumber(el.getAttribute('gs-min-row')) || types_1.gridDefaults.minRow, maxRow: rowAttr ? rowAttr : utils_1.Utils.toNumber(el.getAttribute('gs-max-row')) || types_1.gridDefaults.maxRow, staticGrid: utils_1.Utils.toBool(el.getAttribute('gs-static')) || types_1.gridDefaults.staticGrid, sizeToContent: utils_1.Utils.toBool(el.getAttribute('gs-size-to-content')) || undefined, draggable: {
handle: (opts.handleClass ? '.' + opts.handleClass : (opts.handle ? opts.handle : '')) || types_1.gridDefaults.draggable.handle,
}, removableOptions: {
accept: opts.itemClass || types_1.gridDefaults.removableOptions.accept,
decline: types_1.gridDefaults.removableOptions.decline
} });
if (el.getAttribute('gs-animate')) { // default to true, but if set to false use that instead
defaults.animate = utils_1.Utils.toBool(el.getAttribute('gs-animate'));
}
opts = utils_1.Utils.defaults(opts, defaults);
this._initMargin(); // part of settings defaults...
// Now check if we're loading into !12 column mode FIRST so we don't do un-necessary work (like cellHeight = width / 12 then go 1 column)
this.checkDynamicColumn();
this._updateColumnVar(opts);
if (opts.rtl === 'auto') {
opts.rtl = (el.style.direction === 'rtl');
}
if (opts.rtl) {
this.el.classList.add('grid-stack-rtl');
}
// check if we're been nested, and if so update our style and keep pointer around (used during save)
var parentGridItem = this.el.closest('.' + types_1.gridDefaults.itemClass);
var parentNode = parentGridItem === null || parentGridItem === void 0 ? void 0 : parentGridItem.gridstackNode;
if (parentNode) {
parentNode.subGrid = this;
this.parentGridNode = parentNode;
this.el.classList.add('grid-stack-nested');
parentNode.el.classList.add('grid-stack-sub-grid');
}
this._isAutoCellHeight = (opts.cellHeight === 'auto');
if (this._isAutoCellHeight || opts.cellHeight === 'initial') {
// make the cell content square initially (will use resize/column event to keep it square)
this.cellHeight(undefined);
}
else {
// append unit if any are set
if (typeof opts.cellHeight == 'number' && opts.cellHeightUnit && opts.cellHeightUnit !== types_1.gridDefaults.cellHeightUnit) {
opts.cellHeight = opts.cellHeight + opts.cellHeightUnit;
delete opts.cellHeightUnit;
}
var val = opts.cellHeight;
delete opts.cellHeight; // force initial cellHeight() call to set the value
this.cellHeight(val);
}
// see if we need to adjust auto-hide
if (opts.alwaysShowResizeHandle === 'mobile') {
opts.alwaysShowResizeHandle = dd_touch_1.isTouch;
}
this._setStaticClass();
var engineClass = opts.engineClass || GridStack.engineClass || gridstack_engine_1.GridStackEngine;
this.engine = new engineClass({
column: this.getColumn(),
float: opts.float,
maxRow: opts.maxRow,
onChange: function (cbNodes) {
cbNodes.forEach(function (n) {
var el = n.el;
if (!el)
return;
if (n._removeDOM) {
if (el)
el.remove();
delete n._removeDOM;
}
else {
_this._writePosAttr(el, n);
}
});
_this._updateContainerHeight();
}
});
if (opts.auto) {
this.batchUpdate(); // prevent in between re-layout #1535 TODO: this only set float=true, need to prevent collision check...
this.engine._loading = true; // loading collision check
this.getGridItems().forEach(function (el) { return _this._prepareElement(el); });
delete this.engine._loading;
this.batchUpdate(false);
}
// load any passed in children as well, which overrides any DOM layout done above
if (opts.children) {
var children = opts.children;
delete opts.children;
if (children.length)
this.load(children); // don't load empty
}
this.setAnimation();
// dynamic grids require pausing during drag to detect over to nest vs push
if (opts.subGridDynamic && !dd_manager_1.DDManager.pauseDrag)
dd_manager_1.DDManager.pauseDrag = true;
if (((_c = opts.draggable) === null || _c === void 0 ? void 0 : _c.pause) !== undefined)
dd_manager_1.DDManager.pauseDrag = opts.draggable.pause;
this._setupRemoveDrop();
this._setupAcceptWidget();
this._updateResizeEvent();
}
/**
* initializing the HTML element, or selector string, into a grid will return the grid. Calling it again will
* simply return the existing instance (ignore any passed options). There is also an initAll() version that support
* multiple grids initialization at once. Or you can use addGrid() to create the entire grid from JSON.
* @param options grid options (optional)
* @param elOrString element or CSS selector (first one used) to convert to a grid (default to '.grid-stack' class selector)
*
* @example
* const grid = GridStack.init();
*
* Note: the HTMLElement (of type GridHTMLElement) will store a `gridstack: GridStack` value that can be retrieve later
* const grid = document.querySelector('.grid-stack').gridstack;
*/
GridStack.init = function (options, elOrString) {
if (options === void 0) { options = {}; }
if (elOrString === void 0) { elOrString = '.grid-stack'; }
if (typeof document === 'undefined')
return null; // temp workaround SSR
var el = GridStack.getGridElement(elOrString);
if (!el) {
if (typeof elOrString === 'string') {
console.error('GridStack.initAll() no grid was found with selector "' + elOrString + '" - element missing or wrong selector ?' +
'\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.');
}
else {
console.error('GridStack.init() no grid element was passed.');
}
return null;
}
if (!el.gridstack) {
el.gridstack = new GridStack(el, utils_1.Utils.cloneDeep(options));
}
return el.gridstack;
};
/**
* Will initialize a list of elements (given a selector) and return an array of grids.
* @param options grid options (optional)
* @param selector elements selector to convert to grids (default to '.grid-stack' class selector)
*
* @example
* const grids = GridStack.initAll();
* grids.forEach(...)
*/
GridStack.initAll = function (options, selector) {
if (options === void 0) { options = {}; }
if (selector === void 0) { selector = '.grid-stack'; }
var grids = [];
if (typeof document === 'undefined')
return grids; // temp workaround SSR
GridStack.getGridElements(selector).forEach(function (el) {
if (!el.gridstack) {
el.gridstack = new GridStack(el, utils_1.Utils.cloneDeep(options));
}
grids.push(el.gridstack);
});
if (grids.length === 0) {
console.error('GridStack.initAll() no grid was found with selector "' + selector + '" - element missing or wrong selector ?' +
'\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.');
}
return grids;
};
/**
* call to create a grid with the given options, including loading any children from JSON structure. This will call GridStack.init(), then
* grid.load() on any passed children (recursively). Great alternative to calling init() if you want entire grid to come from
* JSON serialized data, including options.
* @param parent HTML element parent to the grid
* @param opt grids options used to initialize the grid, and list of children
*/
GridStack.addGrid = function (parent, opt) {
if (opt === void 0) { opt = {}; }
if (!parent)
return null;
var el = parent;
if (el.gridstack) {
// already a grid - set option and load data
var grid_1 = el.gridstack;
if (opt)
grid_1.opts = __assign(__assign({}, grid_1.opts), opt);
if (opt.children !== undefined)
grid_1.load(opt.children);
return grid_1;
}
// create the grid element, but check if the passed 'parent' already has grid styling and should be used instead
var parentIsGrid = parent.classList.contains('grid-stack');
if (!parentIsGrid || GridStack.addRemoveCB) {
if (GridStack.addRemoveCB) {
el = GridStack.addRemoveCB(parent, opt, true, true);
}
else {
el = utils_1.Utils.createDiv(['grid-stack', opt.class], parent);
}
}
// create grid class and load any children
var grid = GridStack.init(opt, el);
return grid;
};
/** call this method to register your engine instead of the default one.
* See instead `GridStackOptions.engineClass` if you only need to
* replace just one instance.
*/
GridStack.registerEngine = function (engineClass) {
GridStack.engineClass = engineClass;
};
Object.defineProperty(GridStack.prototype, "placeholder", {
/** @internal create placeholder DIV as needed */
get: function () {
if (!this._placeholder) {
this._placeholder = utils_1.Utils.createDiv([this.opts.placeholderClass, types_1.gridDefaults.itemClass, this.opts.itemClass]);
var placeholderChild = utils_1.Utils.createDiv(['placeholder-content'], this._placeholder);
if (this.opts.placeholderText) {
placeholderChild.textContent = this.opts.placeholderText;
}
}
return this._placeholder;
},
enumerable: false,
configurable: true
});
GridStack.prototype._updateColumnVar = function (opts) {
if (opts === void 0) { opts = this.opts; }
this.el.classList.add('gs-' + opts.column);
if (typeof opts.column === 'number')
this.el.style.setProperty('--gs-column-width', "".concat(100 / opts.column, "%"));
};
/**
* add a new widget and returns it.
*
* Widget will be always placed even if result height is more than actual grid height.
* You need to use `willItFit()` before calling addWidget for additional check.
* See also `makeWidget(el)` for DOM element.
*
* @example
* const grid = GridStack.init();
* grid.addWidget({w: 3, content: 'hello'});
*
* @param w GridStackWidget definition. used MakeWidget(el) if you have dom element instead.
*/
GridStack.prototype.addWidget = function (w) {
if (typeof w === 'string') {
console.error('V11: GridStack.addWidget() does not support string anymore. see #2736');
return;
}
if (w.ELEMENT_NODE) {
console.error('V11: GridStack.addWidget() does not support HTMLElement anymore. use makeWidget()');
return this.makeWidget(w);
}
var el;
var node = w;
node.grid = this;
if (node === null || node === void 0 ? void 0 : node.el) {
el = node.el; // re-use element stored in the node
}
else if (GridStack.addRemoveCB) {
el = GridStack.addRemoveCB(this.el, w, true, false);
}
else {
el = this.createWidgetDivs(node);
}
if (!el)
return;
// if the caller ended up initializing the widget in addRemoveCB, or we stared with one already, skip the rest
node = el.gridstackNode;
if (node && el.parentElement === this.el && this.engine.nodes.find(function (n) { return n._id === node._id; }))
return el;
// Tempting to initialize the passed in opt with default and valid values, but this break knockout demos
// as the actual value are filled in when _prepareElement() calls el.getAttribute('gs-xyz') before adding the node.
// So make sure we load any DOM attributes that are not specified in passed in options (which override)
var domAttr = this._readAttr(el);
utils_1.Utils.defaults(w, domAttr);
this.engine.prepareNode(w);
// this._writeAttr(el, w); why write possibly incorrect values back when makeWidget() will ?
this.el.appendChild(el);
this.makeWidget(el, w);
return el;
};
/** create the default grid item divs, and content (possibly lazy loaded) by using GridStack.renderCB() */
GridStack.prototype.createWidgetDivs = function (n) {
var el = utils_1.Utils.createDiv(['grid-stack-item', this.opts.itemClass]);
var cont = utils_1.Utils.createDiv(['grid-stack-item-content'], el);
if (utils_1.Utils.lazyLoad(n)) {
if (!n.visibleObservable) {
n.visibleObservable = new IntersectionObserver(function (_a) {
var _b, _c;
var entry = _a[0];
if (entry.isIntersecting) {
(_b = n.visibleObservable) === null || _b === void 0 ? void 0 : _b.disconnect();
delete n.visibleObservable;
GridStack.renderCB(cont, n);
(_c = n.grid) === null || _c === void 0 ? void 0 : _c.prepareDragDrop(n.el);
}
});
window.setTimeout(function () { var _a; return (_a = n.visibleObservable) === null || _a === void 0 ? void 0 : _a.observe(el); }); // wait until callee sets position attributes
}
}
else
GridStack.renderCB(cont, n);
return el;
};
/**
* Convert an existing gridItem element into a sub-grid with the given (optional) options, else inherit them
* from the parent's subGrid options.
* @param el gridItem element to convert
* @param ops (optional) sub-grid options, else default to node, then parent settings, else defaults
* @param nodeToAdd (optional) node to add to the newly created sub grid (used when dragging over existing regular item)
* @param saveContent if true (default) the html inside .grid-stack-content will be saved to child widget
* @returns newly created grid
*/
GridStack.prototype.makeSubGrid = function (el, ops, nodeToAdd, saveContent) {
var _a, _b, _c;
if (saveContent === void 0) { saveContent = true; }
var node = el.gridstackNode;
if (!node) {
node = this.makeWidget(el).gridstackNode;
}
if ((_a = node.subGrid) === null || _a === void 0 ? void 0 : _a.el)
return node.subGrid; // already done
// find the template subGrid stored on a parent as fallback...
var subGridTemplate; // eslint-disable-next-line @typescript-eslint/no-this-alias
var grid = this;
while (grid && !subGridTemplate) {
subGridTemplate = (_b = grid.opts) === null || _b === void 0 ? void 0 : _b.subGridOpts;
grid = (_c = grid.parentGridNode) === null || _c === void 0 ? void 0 : _c.grid;
}
//... and set the create options
ops = utils_1.Utils.cloneDeep(__assign(__assign(__assign(__assign({}, this.opts), { id: undefined, children: undefined, column: 'auto', columnOpts: undefined, layout: 'list', subGridOpts: undefined }), (subGridTemplate || {})), (ops || node.subGridOpts || {})));
node.subGridOpts = ops;
// if column special case it set, remember that flag and set default
var autoColumn;
if (ops.column === 'auto') {
autoColumn = true;
ops.column = Math.max(node.w || 1, (nodeToAdd === null || nodeToAdd === void 0 ? void 0 : nodeToAdd.w) || 1);
delete ops.columnOpts; // driven by parent
}
// if we're converting an existing full item, move over the content to be the first sub item in the new grid
var content = node.el.querySelector('.grid-stack-item-content');
var newItem;
var newItemOpt;
if (saveContent) {
this._removeDD(node.el); // remove D&D since it's set on content div
newItemOpt = __assign(__assign({}, node), { x: 0, y: 0 });
utils_1.Utils.removeInternalForSave(newItemOpt);
delete newItemOpt.subGridOpts;
if (node.content) {
newItemOpt.content = node.content;
delete node.content;
}
if (GridStack.addRemoveCB) {
newItem = GridStack.addRemoveCB(this.el, newItemOpt, true, false);
}
else {
newItem = utils_1.Utils.createDiv(['grid-stack-item']);
newItem.appendChild(content);
content = utils_1.Utils.createDiv(['grid-stack-item-content'], node.el);
}
this.prepareDragDrop(node.el); // ... and restore original D&D
}
// if we're adding an additional item, make the container large enough to have them both
if (nodeToAdd) {
var w = autoColumn ? ops.column : node.w;
var h = node.h + nodeToAdd.h;
var style_1 = node.el.style;
style_1.transition = 'none'; // show up instantly so we don't see scrollbar with nodeToAdd
this.update(node.el, { w: w, h: h });
setTimeout(function () { return style_1.transition = null; }); // recover animation
}
var subGrid = node.subGrid = GridStack.addGrid(content, ops);
if (nodeToAdd === null || nodeToAdd === void 0 ? void 0 : nodeToAdd._moving)
subGrid._isTemp = true; // prevent re-nesting as we add over
if (autoColumn)
subGrid._autoColumn = true;
// add the original content back as a child of hte newly created grid
if (saveContent) {
subGrid.makeWidget(newItem, newItemOpt);
}
// now add any additional node
if (nodeToAdd) {
if (nodeToAdd._moving) {
// create an artificial event even for the just created grid to receive this item
window.setTimeout(function () { return utils_1.Utils.simulateMouseEvent(nodeToAdd._event, 'mouseenter', subGrid.el); }, 0);
}
else {
subGrid.makeWidget(node.el, node);
}
}
// if sizedToContent, we need to re-calc the size of ourself
this.resizeToContentCheck(false, node);
return subGrid;
};
/**
* called when an item was converted into a nested grid to accommodate a dragged over item, but then item leaves - return back
* to the original grid-item. Also called to remove empty sub-grids when last item is dragged out (since re-creating is simple)
*/
GridStack.prototype.removeAsSubGrid = function (nodeThatRemoved) {
var _this = this;
var _a;
var pGrid = (_a = this.parentGridNode) === null || _a === void 0 ? void 0 : _a.grid;
if (!pGrid)
return;
pGrid.batchUpdate();
pGrid.removeWidget(this.parentGridNode.el, true, true);
this.engine.nodes.forEach(function (n) {
// migrate any children over and offsetting by our location
n.x += _this.parentGridNode.x;
n.y += _this.parentGridNode.y;
pGrid.makeWidget(n.el, n);
});
pGrid.batchUpdate(false);
if (this.parentGridNode)
delete this.parentGridNode.subGrid;
delete this.parentGridNode;
// create an artificial event for the original grid now that this one is gone (got a leave, but won't get enter)
if (nodeThatRemoved) {
window.setTimeout(function () { return utils_1.Utils.simulateMouseEvent(nodeThatRemoved._event, 'mouseenter', pGrid.el); }, 0);
}
};
/**
* saves the current layout returning a list of widgets for serialization which might include any nested grids.
* @param saveContent if true (default) the latest html inside .grid-stack-content will be saved to GridStackWidget.content field, else it will
* be removed.
* @param saveGridOpt if true (default false), save the grid options itself, so you can call the new GridStack.addGrid()
* to recreate everything from scratch. GridStackOptions.children would then contain the widget list instead.
* @param saveCB callback for each node -> widget, so application can insert additional data to be saved into the widget data structure.
* @returns list of widgets or full grid option, including .children list of widgets
*/
GridStack.prototype.save = function (saveContent, saveGridOpt, saveCB) {
if (saveContent === void 0) { saveContent = true; }
if (saveGridOpt === void 0) { saveGridOpt = false; }
if (saveCB === void 0) { saveCB = GridStack.saveCB; }
// return copied GridStackWidget (with optionally .el) we can modify at will...
var list = this.engine.save(saveContent, saveCB);
// check for HTML content and nested grids
list.forEach(function (n) {
var _a;
if (saveContent && n.el && !n.subGrid && !saveCB) { // sub-grid are saved differently, not plain content
var itemContent = n.el.querySelector('.grid-stack-item-content');
n.content = itemContent === null || itemContent === void 0 ? void 0 : itemContent.innerHTML;
if (!n.content)
delete n.content;
}
else {
if (!saveContent && !saveCB) {
delete n.content;
}
// check for nested grid
if ((_a = n.subGrid) === null || _a === void 0 ? void 0 : _a.el) {
var listOrOpt = n.subGrid.save(saveContent, saveGridOpt, saveCB);
n.subGridOpts = (saveGridOpt ? listOrOpt : { children: listOrOpt });
delete n.subGrid;
}
}
delete n.el;
});
// check if save entire grid options (needed for recursive) + children...
if (saveGridOpt) {
var o = utils_1.Utils.cloneDeep(this.opts);
// delete default values that will be recreated on launch
if (o.marginBottom === o.marginTop && o.marginRight === o.marginLeft && o.marginTop === o.marginRight) {
o.margin = o.marginTop;
delete o.marginTop;
delete o.marginRight;
delete o.marginBottom;
delete o.marginLeft;
}
if (o.rtl === (this.el.style.direction === 'rtl')) {
o.rtl = 'auto';
}
if (this._isAutoCellHeight) {
o.cellHeight = 'auto';
}
if (this._autoColumn) {
o.column = 'auto';
}
var origShow = o._alwaysShowResizeHandle;
delete o._alwaysShowResizeHandle;
if (origShow !== undefined) {
o.alwaysShowResizeHandle = origShow;
}
else {
delete o.alwaysShowResizeHandle;
}
utils_1.Utils.removeInternalAndSame(o, types_1.gridDefaults);
o.children = list;
return o;
}
return list;
};
/**
* load the widgets from a list. This will call update() on each (matching by id) or add/remove widgets that are not there.
*
* @param items list of widgets definition to update/create
* @param addRemove boolean (default true) or callback method can be passed to control if and how missing widgets can be added/removed, giving
* the user control of insertion.
*
* @example
* see http://gridstackjs.com/demo/serialization.html
*/
GridStack.prototype.load = function (items, addRemove) {
var _this = this;
var _a;
if (addRemove === void 0) { addRemove = GridStack.addRemoveCB || true; }
items = utils_1.Utils.cloneDeep(items); // so we can mod
var column = this.getColumn();
// make sure size 1x1 (default) is present as it may need to override current sizes
items.forEach(function (n) { n.w = n.w || 1; n.h = n.h || 1; });
// sort items. those without coord will be appended last
items = utils_1.Utils.sort(items);
this.engine.skipCacheUpdate = this._ignoreLayoutsNodeChange = true; // skip layout update
// 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
var maxColumn = 0;
items.forEach(function (n) { maxColumn = Math.max(maxColumn, (n.x || 0) + n.w); });
if (maxColumn > this.engine.defaultColumn)
this.engine.defaultColumn = maxColumn;
if (maxColumn > column) {
// if we're loading (from empty) into a smaller column, check for special responsive layout
if (this.engine.nodes.length === 0 && this.responseLayout) {
this.engine.nodes = items;
this.engine.columnChanged(maxColumn, column, this.responseLayout);
items = this.engine.nodes;
this.engine.nodes = [];
delete this.responseLayout;
}
else
this.engine.cacheLayout(items, maxColumn, true);
}
// if given a different callback, temporally set it as global option so creating will use it
var prevCB = GridStack.addRemoveCB;
if (typeof (addRemove) === 'function')
GridStack.addRemoveCB = addRemove;
var removed = [];
this.batchUpdate();
// if we are loading from empty temporarily remove animation
var 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 (!blank && addRemove) {
var copyNodes = __spreadArray([], this.engine.nodes, true); // don't loop through array you modify
copyNodes.forEach(function (n) {
if (!n.id)
return;
var item = utils_1.Utils.find(items, n.id);
if (!item) {
if (GridStack.addRemoveCB)
GridStack.addRemoveCB(_this.el, n, false, false);
removed.push(n); // batch keep track
_this.removeWidget(n.el, true, false);
}
});
}
// 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
var updateNodes = [];
this.engine.nodes = this.engine.nodes.filter(function (n) {
if (utils_1.Utils.find(items, n.id)) {
updateNodes.push(n);
return false;
} // remove if found from list
return true;
});
items.forEach(function (w) {
var _a;
var item = utils_1.Utils.find(updateNodes, w.id);
if (item) {
// if item sizes to content, re-use the exiting height so it's a better guess at the final size (same if width doesn't change)
if (utils_1.Utils.shouldSizeToContent(item))
w.h = item.h;
// check if missing coord, in which case find next empty slot with new (or old if missing) sizes
_this.engine.nodeBoundFix(w);
if (w.autoPosition || w.x === undefined || w.y === undefined) {
w.w = w.w || item.w;
w.h = w.h || item.h;
_this.engine.findEmptyPosition(w);
}
// add back to current list BUT force a collision check if it 'appears' we didn't change to make sure we don't overlap others now
_this.engine.nodes.push(item);
if (utils_1.Utils.samePos(item, w) && _this.engine.nodes.length > 1) {
_this.moveNode(item, __assign(__assign({}, w), { forceCollide: true }));
utils_1.Utils.copyPos(w, item); // use possily updated values before update() is called next (no-op since already moved)
}
_this.update(item.el, w);
if ((_a = w.subGridOpts) === null || _a === void 0 ? void 0 : _a.children) { // update any sub grid as well
var sub = item.el.querySelector('.grid-stack');
if (sub && sub.gridstack) {
sub.gridstack.load(w.subGridOpts.children); // TODO: support updating grid options ?
}
}
}
else if (addRemove) {
_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.engine.skipCacheUpdate;
prevCB ? GridStack.addRemoveCB = prevCB : delete GridStack.addRemoveCB;
// delay adding animation back
if (blank && ((_a = this.opts) === null || _a === void 0 ? void 0 : _a.animate))
this.setAnimation(this.opts.animate, true);
return this;
};
/**
* use before calling a bunch of `addWidget()` to prevent un-necessary relayouts in between (more efficient)
* and get a single event callback. You will see no changes until `batchUpdate(false)` is called.
*/
GridStack.prototype.batchUpdate = function (flag) {
if (flag === void 0) { flag = true; }
this.engine.batchUpdate(flag);
if (!flag) {
this._updateContainerHeight();
this._triggerRemoveEvent();
this._triggerAddEvent();
this._triggerChangeEvent();
}
return this;
};
/**
* Gets current cell height.
*/
GridStack.prototype.getCellHeight = function (forcePixel) {
if (forcePixel === void 0) { forcePixel = false; }
if (this.opts.cellHeight && this.opts.cellHeight !== 'auto' &&
(!forcePixel || !this.opts.cellHeightUnit || this.opts.cellHeightUnit === 'px')) {
return this.opts.cellHeight;
}
// do rem/em/cm/mm to px conversion
if (this.opts.cellHeightUnit === 'rem') {
return this.opts.cellHeight * parseFloat(getComputedStyle(document.documentElement).fontSize);
}
if (this.opts.cellHeightUnit === 'em') {
return this.opts.cellHeight * parseFloat(getComputedStyle(this.el).fontSize);
}
if (this.opts.cellHeightUnit === 'cm') {
// 1cm = 96px/2.54. See https://www.w3.org/TR/css-values-3/#absolute-lengths
return this.opts.cellHeight * (96 / 2.54);
}
if (this.opts.cellHeightUnit === 'mm') {
return this.opts.cellHeight * (96 / 2.54) / 10;
}
// else get first cell height
var el = this.el.querySelector('.' + this.opts.itemClass);
if (el) {
var h = utils_1.Utils.toNumber(el.getAttribute('gs-h')) || 1; // since we don't write 1 anymore
return Math.round(el.offsetHeight / h);
}
// else do entire grid and # of rows (but doesn't work if min-height is the actual constrain)
var rows = parseInt(this.el.getAttribute('gs-current-row'));
return rows ? Math.round(this.el.getBoundingClientRect().height / rows) : this.opts.cellHeight;
};
/**
* Update current cell height - see `GridStackOptions.cellHeight` for format.
* This method rebuilds an internal CSS style sheet.
* Note: You can expect performance issues if call this method too often.
*
* @param val the cell height. If not passed (undefined), cells content will be made square (match width minus margin),
* if pass 0 the CSS will be generated by the application instead.
*
* @example
* grid.cellHeight(100); // same as 100px
* grid.cellHeight('70px');
* grid.cellHeight(grid.cellWidth() * 1.2);
*/
GridStack.prototype.cellHeight = function (val) {
// if not called internally, check if we're changing mode
if (val !== undefined) {
if (this._isAutoCellHeight !== (val === 'auto')) {
this._isAutoCellHeight = (val === 'auto');
this._updateResizeEvent();
}
}
if (val === 'initial' || val === 'auto') {
val = undefined;
}
// make item content be square
if (val === undefined) {
var marginDiff = -this.opts.marginRight - this.opts.marginLeft
+ this.opts.marginTop + this.opts.marginBottom;
val = this.cellWidth() + marginDiff;
}
var data = utils_1.Utils.parseHeight(val);
if (this.opts.cellHeightUnit === data.unit && this.opts.cellHeight === data.h) {
return this;
}
this.opts.cellHeightUnit = data.unit;
this.opts.cellHeight = data.h;
// finally update var and container
this.el.style.setProperty('--gs-cell-height', "".concat(this.opts.cellHeight).concat(this.opts.cellHeightUnit));
this._updateContainerHeight();
this.resizeToContentCheck();
return this;
};
/** Gets current cell width. */
GridStack.prototype.cellWidth = function () {
return this._widthOrContainer() / this.getColumn();
};
/** return our expected width (or parent) , and optionally of window for dynamic column check */
GridStack.prototype._widthOrContainer = function (forBreakpoint) {
var _a;
if (forBreakpoint === void 0) { forBreakpoint = false; }
// use `offsetWidth` or `clientWidth` (no scrollbar) ?
// https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
return forBreakpoint && ((_a = this.opts.columnOpts) === null || _a === void 0 ? void 0 : _a.breakpointForWindow) ? window.innerWidth : (this.el.clientWidth || this.el.parentElement.clientWidth || window.innerWidth);
};
/** checks for dynamic column count for our current size, returning true if changed */
GridStack.prototype.checkDynamicColumn = function () {
var _a, _b;
var resp = this.opts.columnOpts;
if (!resp || (!resp.columnWidth && !((_a = resp.breakpoints) === null || _a === void 0 ? void 0 : _a.length)))
return false;
var column = this.getColumn();
var newColumn = column;
var w = this._widthOrContainer(true);
if (resp.columnWidth) {
newColumn = Math.min(Math.round(w / resp.columnWidth) || 1, resp.columnMax);
}
else {
// find the closest breakpoint (already sorted big to small) that matches
newColumn = resp.columnMax;
var i = 0;
while (i < resp.breakpoints.length && w <= resp.breakpoints[i].w) {
newColumn = resp.breakpoints[i++].c || column;
}
}
if (newColumn !== column) {
var bk = (_b = resp.breakpoints) === null || _b === void 0 ? void 0 : _b.find(function (b) { return b.c === newColumn; });
this.column(newColumn, (bk === null || bk === void 0 ? void 0 : bk.layout) || resp.layout);
return true;
}
return false;
};
/**
* re-layout grid items to reclaim any empty space. Options are:
* 'list' keep the widget left->right order the same, even if that means leaving an empty slot if things don't fit
* 'compact' might re-order items to fill any empty space
*
* doSort - 'false' to let you do your own sorting ahead in case you need to control a different order. (default to sort)
*/
GridStack.prototype.compact = function (layout, doSort) {
if (layout === void 0) { layout = 'compact'; }
if (doSort === void 0) { doSort = true; }
this.engine.compact(layout, doSort);
this._triggerChangeEvent();
return this;
};
/**
* set the number of columns in the grid. Will update existing widgets to conform to new number of columns,
* as well as cache the original layout so you can revert back to previous positions without loss.
* @param column - Integer > 0 (default 12).
* @param layout specify the type of re-layout that will happen (position, size, etc...).
* Note: items will never be outside of the current column boundaries. default ('moveScale'). Ignored for 1 column
*/
GridStack.prototype.column = function (column, layout) {
if (layout === void 0) { layout = 'moveScale'; }
if (!column || column < 1 || this.opts.column === column)
return this;
var oldColumn = this.getColumn();
this.opts.column = column;
if (!this.engine) {
// called in constructor, noting else to do but remember that breakpoint layout
this.responseLayout = layout;
return this;
}
this.engine.column = column;
this.el.classList.remove('gs-' + oldColumn);
this._updateColumnVar();
// update the items now
this.engine.columnChanged(oldColumn, column, layout);
if (this._isAutoCellHeight)
this.cellHeight();
this.resizeToContentCheck(true); // wait for width resizing
// and trigger our event last...
this._ignoreLayoutsNodeChange = true; // skip layout update
this._triggerChangeEvent();
delete this._ignoreLayoutsNodeChange;
return this;
};
/**
* get the number of columns in the grid (default 12)
*/
GridStack.prototype.getColumn = function () { return this.opts.column; };
/** returns an array of grid HTML elements (no placeholder) - used to iterate through our children in DOM order */
GridStack.prototype.getGridItems = function () {
var _this = this;
return Array.from(this.el.children)
.filter(function (el) { return el.matches('.' + _this.opts.itemClass) && !el.matches('.' + _this.opts.placeholderClass); });
};
/** true if changeCB should be ignored due to column change, sizeToContent, loading, etc... which caller can ignore for dirty flag case */
GridStack.prototype.isIgnoreChangeCB = function () { return this._ignoreLayoutsNodeChange; };
/**
* Destroys a grid instance. DO NOT CALL any methods or access any vars after this as it will free up members.
* @param removeDOM if `false` grid and items HTML elements will not be removed from the DOM (Optional. Default `true`).
*/
GridStack.prototype.destroy = function (removeDOM) {
var _a;
if (removeDOM === void 0) { removeDOM = true; }
if (!this.el)
return; // prevent multiple calls
this.offAll();
this._updateResizeEvent(true);
this.setStatic(true, false); // permanently removes DD but don't set CSS class (we're going away)
this.setAnimation(false);
if (!removeDOM) {
this.removeAll(removeDOM);
this.el.removeAttribute('gs-current-row');
}
else {
this.el.parentNode.removeChild(this.el);
}
if (this.parentGridNode)
delete this.parentGridNode.subGrid;
delete this.parentGridNode;
delete this.opts;
(_a = this._placeholder) === null || _a === void 0 ? true : delete _a.gridstackNode;
delete this._placeholder;
delete this.engine;
delete this.el.gridstack; // remove circular dependency that would prevent a freeing
delete this.el;
return this;
};
/**
* enable/disable floating widgets (default: `false`) See [example](http://gridstackjs.com/demo/float.html)
*/
GridStack.prototype.float = function (val) {
if (this.opts.float !== val) {
this.opts.float = this.engine.float = val;
this._triggerChangeEvent();
}
return this;
};
/**
* get the current float mode
*/
GridStack.prototype.getFloat = function () {
return this.engine.float;
};
/**
* Get the position of the cell under a pixel on screen.
* @param position the position of the pixel to resolve in
* absolute coordinates, as an object with top and left properties
* @param useDocRelative if true, value will be based on document position vs parent position (Optional. Default false).
* Useful when grid is within `position: relative` element
*
* Returns an object with properties `x` and `y` i.e. the column and row in the grid.
*/
GridStack.prototype.getCellFromPixel = function (position, useDocRelative) {
if (useDocRelative === void 0) { useDocRelative = false; }
var box = this.el.getBoundingClientRect();
// console.log(`getBoundingClientRect left: ${box.left} top: ${box.top} w: ${box.w} h: ${box.h}`)
var containerPos;
if (useDocRelative) {
containerPos = { top: box.top + document.documentElement.scrollTop, left: box.left };
// console.log(`getCellFromPixel scrollTop: ${document.documentElement.scrollTop}`)
}
else {
containerPos = { top: this.el.offsetTop, left: this.el.offsetLeft };
// console.log(`getCellFromPixel offsetTop: ${containerPos.left} offsetLeft: ${containerPos.top}`)
}
var relativeLeft = position.left - containerPos.left;
var relativeTop = position.top - containerPos.top;
var columnWidth = (box.width / this.getColumn());
var rowHeight = (box.height / parseInt(this.el.getAttribute('gs-current-row')));
return { x: Math.floor(relativeLeft / columnWidth), y: Math.floor(relativeTop / rowHeight) };
};
/** returns the current number of rows, which will be at least `minRow` if set */
GridStack.prototype.getRow = function () {