UNPKG

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
"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 () {