diff --git a/.eslintrc.js b/.eslintrc.js index 76b691f34..96d1e574f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { 'indent': ['error', 2], 'max-len': ['error', 180], 'no-trailing-spaces': 'error', - 'prefer-const': 0 + 'prefer-const': 0, + '@typescript-eslint/explicit-function-return-type': 0 } }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e9faea3d..d22d5f620 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "dragstop", "dropover", "droppable", + "gridster", "gsresize", "gsresizestop", "jqueryui", diff --git a/karma.conf.js b/karma.conf.js index 1f54dfaa8..dd02711d6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -29,8 +29,11 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - 'src/gridstack.js': ['coverage'], - 'src/gridstack.jQueryUI.js': ['coverage'] + 'src/gridstack-dragdrop-plugin.ts': ['coverage'], + 'src/gridstack.engine.ts': ['coverage'], + 'src/gridstack.ts': ['coverage'], + 'src/jqueryui-gridstack-dragdrop-plugin.ts': ['coverage'], + 'src/utils.ts': ['coverage'] }, diff --git a/package.json b/package.json index eb214ae3c..e0ccecc16 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "scripts": { "build": "yarn --no-progress && rm -rf dist/* && webpack && grunt && doctoc ./README.md && doctoc ./doc/README.md && doctoc ./doc/CHANGES.md", "web": "rm -rf dist/* && webpack", - "test": "yarn lint && karma start karma.conf.js", + "test": "karma start karma.conf.js", + "test2": "yarn lint && karma start karma.conf.js", "lint": "tsc --noEmit && eslint src/*.ts", "reset": "rm -rf dist node_modules", "prepublishOnly": "yarn build" @@ -43,7 +44,7 @@ "homepage": "http://gridstack.github.io/gridstack.js/", "dependencies": {}, "devDependencies": { - "@types/jquery": "^3.3.32", + "@types/jquery": "^3.3.33", "@types/jqueryui": "^1.12.10", "@typescript-eslint/eslint-plugin": "^2.23.0", "@typescript-eslint/parser": "^2.23.0", diff --git a/spec/gridstack-engine-spec.js b/spec/gridstack-engine-spec.js index 25c598d86..95c402169 100644 --- a/spec/gridstack-engine-spec.js +++ b/spec/gridstack-engine-spec.js @@ -24,7 +24,7 @@ describe('gridstack engine', function() { }); }); - describe('test _prepareNode', function() { + describe('test prepareNode', function() { var engine; beforeAll(function() { @@ -32,20 +32,20 @@ describe('gridstack engine', function() { }); it('should prepare a node', function() { - expect(engine._prepareNode({}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({x: 10}, false)).toEqual(jasmine.objectContaining({x: 10, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({x: -10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({y: 10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 10, width: 1, height: 1})); - expect(engine._prepareNode({y: -10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({width: 3}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 3, height: 1})); - expect(engine._prepareNode({width: 100}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 12, height: 1})); - expect(engine._prepareNode({width: 0}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({width: -190}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({height: 3}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 3})); - expect(engine._prepareNode({height: 0}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({height: -10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); - expect(engine._prepareNode({x: 4, width: 10}, false)).toEqual(jasmine.objectContaining({x: 2, y: 0, width: 10, height: 1})); - expect(engine._prepareNode({x: 4, width: 10}, true)).toEqual(jasmine.objectContaining({x: 4, y: 0, width: 8, height: 1})); + expect(engine.prepareNode({}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({x: 10}, false)).toEqual(jasmine.objectContaining({x: 10, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({x: -10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({y: 10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 10, width: 1, height: 1})); + expect(engine.prepareNode({y: -10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({width: 3}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 3, height: 1})); + expect(engine.prepareNode({width: 100}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 12, height: 1})); + expect(engine.prepareNode({width: 0}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({width: -190}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({height: 3}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 3})); + expect(engine.prepareNode({height: 0}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({height: -10}, false)).toEqual(jasmine.objectContaining({x: 0, y: 0, width: 1, height: 1})); + expect(engine.prepareNode({x: 4, width: 10}, false)).toEqual(jasmine.objectContaining({x: 2, y: 0, width: 10, height: 1})); + expect(engine.prepareNode({x: 4, width: 10}, true)).toEqual(jasmine.objectContaining({x: 4, y: 0, width: 8, height: 1})); }); }); @@ -55,7 +55,7 @@ describe('gridstack engine', function() { beforeAll(function() { engine = new GridStack.Engine(12, null, true); engine.nodes = [ - engine._prepareNode({x: 3, y: 2, width: 3, height: 2}) + engine.prepareNode({x: 3, y: 2, width: 3, height: 2}) ]; }); @@ -76,9 +76,9 @@ describe('gridstack engine', function() { beforeAll(function() { engine = new GridStack.Engine(12, null, true); engine.nodes = [ - engine._prepareNode({x: 0, y: 0, width: 1, height: 1, idx: 1, _dirty: true}), - engine._prepareNode({x: 3, y: 2, width: 3, height: 2, idx: 2, _dirty: true}), - engine._prepareNode({x: 3, y: 7, width: 3, height: 2, idx: 3}) + engine.prepareNode({x: 0, y: 0, width: 1, height: 1, idx: 1, _dirty: true}), + engine.prepareNode({x: 3, y: 2, width: 3, height: 2, idx: 2, _dirty: true}), + engine.prepareNode({x: 3, y: 7, width: 3, height: 2, idx: 3}) ]; }); @@ -167,9 +167,9 @@ describe('gridstack engine', function() { engine = new GridStack.Engine(12, spy.callback, true); engine.nodes = [ - engine._prepareNode({x: 0, y: 0, width: 1, height: 1, idx: 1, _dirty: true}), - engine._prepareNode({x: 3, y: 2, width: 3, height: 2, idx: 2, _dirty: true}), - engine._prepareNode({x: 3, y: 7, width: 3, height: 2, idx: 3}) + engine.prepareNode({x: 0, y: 0, width: 1, height: 1, idx: 1, _dirty: true}), + engine.prepareNode({x: 3, y: 2, width: 3, height: 2, idx: 2, _dirty: true}), + engine.prepareNode({x: 3, y: 7, width: 3, height: 2, idx: 3}) ]; }); diff --git a/src/gridstack-dragdrop-plugin.ts b/src/gridstack-dragdrop-plugin.ts index 35eab889d..24c949682 100644 --- a/src/gridstack-dragdrop-plugin.ts +++ b/src/gridstack-dragdrop-plugin.ts @@ -5,11 +5,13 @@ * (c) 2014-2020 Alain Dumesny, Dylan Weiss, Pavel Reznikov * gridstack.js may be freely distributed under the MIT license. */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { GridStack } from './gridstack'; import { GridItemHTMLElement } from './types'; /** drag&drop options currently called from the main code, but others can be passed in grid options */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type DDOpts = 'enable' | 'disable' | 'destroy' | 'option' | {} | any; export type DDKey = 'minWidth' | 'minHeight' | string; @@ -18,9 +20,9 @@ export type DDKey = 'minWidth' | 'minHeight' | string; */ export class GridStackDragDropPlugin { protected grid: GridStack; - static registeredPlugins = []; - - static registerPlugin(pluginClass) { + static registeredPlugins: typeof GridStackDragDropPlugin[] = []; + + static registerPlugin(pluginClass: typeof GridStackDragDropPlugin) { GridStackDragDropPlugin.registeredPlugins.push(pluginClass); } diff --git a/src/gridstack-engine.ts b/src/gridstack-engine.ts index 3ec6e79ef..294e77932 100644 --- a/src/gridstack-engine.ts +++ b/src/gridstack-engine.ts @@ -11,18 +11,10 @@ import { GridStackNode } from './types'; export type onChangeCB = (nodes: GridStackNode[], detachNode?: boolean) => void; -/** @internal class to store per column layout bare minimal info (subset of GridstackWidget) */ -interface layout { - x: number; - y: number; - width: number; - _id: number; // so we can find full node back -} - /** * Defines the GridStack engine that does most no DOM grid manipulation. * See GridStack methods and vars for descriptions. - * + * * NOTE: values should not be modified directly - call the main GridStack API instead */ export class GridStackEngine { @@ -38,18 +30,18 @@ export class GridStackEngine { /** @internal */ private _prevFloat: boolean; /** @internal */ - private _layouts?: layout[][]; // maps column # to array of values nodes + private _layouts?: Layout[][]; // maps column # to array of values nodes /** @internal */ private _ignoreLayoutsNodeChange: boolean; /** @internal */ private static _idSeq = 1; - public constructor(column?: number, onchange?: onChangeCB, float?: boolean, maxRow?: number, nodes?: GridStackNode[]) { - this.column = column || 12; - this._float = float || false; - this.maxRow = maxRow || 0; - this.nodes = nodes || []; - this.onchange = onchange || function () {}; + public constructor(column = 12, onchange?: onChangeCB, float = false, maxRow = 0, nodes: GridStackNode[] = []) { + this.column = column; + this.onchange = onchange; + this._float = float; + this.maxRow = maxRow; + this.nodes = nodes; } public batchUpdate() { @@ -57,7 +49,7 @@ export class GridStackEngine { this.batchMode = true; this._prevFloat = this._float; this._float = true; // let things go anywhere for now... commit() will restore and possibly reposition - }; + } public commit() { if (!this.batchMode) return; @@ -66,34 +58,31 @@ export class GridStackEngine { delete this._prevFloat; this._packNodes(); this._notify(); - }; + } private _fixCollisions(node: GridStackNode) { this._sortNodes(-1); let nn = node; - let hasLocked = Boolean(this.nodes.find(function(n) { return n.locked; })); + let hasLocked = Boolean(this.nodes.find(n => n.locked)); if (!this.float && !hasLocked) { nn = {x: 0, y: node.y, width: this.column, height: node.height}; } while (true) { - let array1 = [5, 12, 8, 130, 44]; - let found = array1.find(element => element > 10); - let collisionNode = this.nodes.find( n => n !== node && Utils.isIntercepted(n, nn), {node: node, nn: nn}); if (!collisionNode) { return; } this.moveNode(collisionNode, collisionNode.x, node.y + node.height, collisionNode.width, collisionNode.height, true); } - }; + } public isAreaEmpty(x: number, y: number, width: number, height: number) { let nn = {x: x || 0, y: y || 0, width: width || 1, height: height || 1}; - let collisionNode = this.nodes.find(function(n) { + let collisionNode = this.nodes.find(n => { return Utils.isIntercepted(n, nn); }); return !collisionNode; - }; + } /** re-layout grid items to reclaim any empty space */ public compact() { @@ -112,9 +101,7 @@ export class GridStackEngine { this.commit(); } - /** - * enable/disable floating widgets (default: `false`) See [example](http://gridstackjs.com/demo/float.html) - */ + /** enable/disable floating widgets (default: `false`) See [example](http://gridstackjs.com/demo/float.html) */ public set float(val: boolean) { if (this._float === val) { return; } this._float = val || false; @@ -124,12 +111,12 @@ export class GridStackEngine { } } - // getter method + /** float getter method */ public get float(): boolean { return this._float; } - private _sortNodes(dir?: number) { + private _sortNodes(dir?: -1 | 1) { this.nodes = Utils.sort(this.nodes, dir, this.column); - }; + } private _packNodes() { this._sortNodes(); @@ -175,9 +162,14 @@ export class GridStackEngine { } }); } - }; + } - public _prepareNode(node: GridStackNode, resizing?: boolean) { + /** + * given a random node, makes sure it's coordinates/values are valid in the current grid + * @param node to adjust + * @param resizing if out of bound, resize down or move into the grid to fit ? + */ + public prepareNode(node: GridStackNode, resizing?: boolean) { node = node || {}; // if we're missing position, have the grid position us automatically (before we set them to 0,0) if (node.x === undefined || node.y === undefined || node.x === null || node.y === null) { @@ -232,13 +224,13 @@ export class GridStackEngine { } return node; - }; + } public getDirtyNodes(verify?: boolean) { // compare original X,Y,W,H (or entire node?) instead as _dirty can be a temporary state if (verify) { let dirtNodes = []; - this.nodes.forEach(function (n) { + this.nodes.forEach(n => { if (n._dirty) { if (n.y === n._origY && n.x === n._origX && n.width === n._origW && n.height === n._origH) { delete n._dirty; @@ -250,24 +242,26 @@ export class GridStackEngine { return dirtNodes; } - return this.nodes.filter(function(n) { return n._dirty; }); - }; + return this.nodes.filter(n => n._dirty); + } private _notify(nodes?: GridStackNode | GridStackNode[], detachNode?: boolean) { if (this.batchMode) { return; } detachNode = (detachNode === undefined ? true : detachNode); nodes = (nodes === undefined ? [] : (Array.isArray(nodes) ? nodes : [nodes]) ); let dirtyNodes = nodes.concat(this.getDirtyNodes()); - this.onchange(dirtyNodes, detachNode); - }; + if (this.onchange) { + this.onchange(dirtyNodes, detachNode); + } + } public cleanNodes() { if (this.batchMode) { return; } - this.nodes.forEach(function(n) { delete n._dirty; }); - }; + this.nodes.forEach(n => { delete n._dirty; }); + } public addNode(node: GridStackNode, triggerAddEvent?: boolean) { - node = this._prepareNode(node); + node = this.prepareNode(node); if (node.maxWidth) { node.width = Math.min(node.width, node.maxWidth); } if (node.maxHeight) { node.height = Math.min(node.height, node.maxHeight); } @@ -279,7 +273,7 @@ export class GridStackEngine { if (node.autoPosition) { this._sortNodes(); - for (var i = 0;; ++i) { + for (let i = 0;; ++i) { let x = i % this.column; let y = Math.floor(i / this.column); if (x + node.width > this.column) { @@ -304,32 +298,32 @@ export class GridStackEngine { this._packNodes(); this._notify(); return node; - }; + } public removeNode(node: GridStackNode, detachNode?: boolean) { detachNode = (detachNode === undefined ? true : detachNode); this.removedNodes.push(node); node._id = null; // hint that node is being removed - this.nodes = Utils.without(this.nodes, node); + this.nodes = this.nodes.filter(n => n !== node); this._packNodes(); this._notify(node, detachNode); - }; + } public removeAll(detachNode?: boolean) { delete this._layouts; if (this.nodes.length === 0) { return; } detachNode = (detachNode === undefined ? true : detachNode); - this.nodes.forEach(function(n) { n._id = null; }); // hint that node is being removed + this.nodes.forEach(n => { n._id = null; }); // hint that node is being removed this.removedNodes = this.nodes; this.nodes = []; this._notify(this.removedNodes, detachNode); - }; + } public canMoveNode(node: GridStackNode, x: number, y: number, width?: number, height?: number): boolean { if (!this.isNodeChangedPosition(node, x, y, width, height)) { return false; } - let hasLocked = Boolean(this.nodes.find(function(n) { return n.locked; })); + let hasLocked = Boolean(this.nodes.find(n => n.locked)); if (!this.maxRow && !hasLocked) { return true; @@ -341,7 +335,7 @@ export class GridStackEngine { null, this.float, 0, - this.nodes.map(function(n) { + this.nodes.map(n => { if (n === node) { clonedNode = Utils.clone(n); return clonedNode; @@ -355,7 +349,7 @@ export class GridStackEngine { let canMove = true; if (hasLocked) { - canMove = canMove && !Boolean(clone.nodes.find(function(n) { + canMove = canMove && !Boolean(clone.nodes.find(n => { return n !== clonedNode && Boolean(n.locked) && Boolean(n._dirty); })); } @@ -364,7 +358,7 @@ export class GridStackEngine { } return canMove; - }; + } public canBePlacedWithRespectToHeight(node: GridStackNode) { if (!this.maxRow) { @@ -376,10 +370,10 @@ export class GridStackEngine { null, this.float, 0, - this.nodes.map(function(n) { return Utils.clone(n) })); + this.nodes.map(n => Utils.clone(n))); clone.addNode(node); return clone.getRow() <= this.maxRow; - }; + } public isNodeChangedPosition(node: GridStackNode, x: number, y: number, width: number, height: number) { if (typeof x !== 'number') { x = node.x; } @@ -396,7 +390,7 @@ export class GridStackEngine { return false; } return true; - }; + } public moveNode(node: GridStackNode, x: number, y: number, width?: number, height?: number, noPack?: boolean): GridStackNode { if (typeof x !== 'number') { x = node.x; } @@ -426,7 +420,7 @@ export class GridStackEngine { node._lastTriedWidth = width; node._lastTriedHeight = height; - node = this._prepareNode(node, resizing); + node = this.prepareNode(node, resizing); this._fixCollisions(node); if (!noPack) { @@ -434,31 +428,31 @@ export class GridStackEngine { this._notify(); } return node; - }; + } public getRow(): number { - return this.nodes.reduce(function(memo, n) { return Math.max(memo, n.y + n.height); }, 0); - }; + return this.nodes.reduce((memo, n) => Math.max(memo, n.y + n.height), 0); + } public beginUpdate(node: GridStackNode) { if (node._updating) return; node._updating = true; - this.nodes.forEach(function(n) { n._packY = n.y; }); - }; + this.nodes.forEach(n => { n._packY = n.y; }); + } public endUpdate() { - let n = this.nodes.find(function(n) { return n._updating; }); + let n = this.nodes.find(n => n._updating); if (n) { n._updating = false; - this.nodes.forEach(function(n) { delete n._packY; }); + this.nodes.forEach(n => { delete n._packY; }); } - }; + } - /** called whenever a node is added or moved - updates the cached layouts */ - public _layoutsNodesChange(nodes: GridStackNode[]) { + /** @internal called whenever a node is added or moved - updates the cached layouts */ + public layoutsNodesChange(nodes: GridStackNode[]) { if (!this._layouts || this._ignoreLayoutsNodeChange) return; // remove smaller layouts - we will re-generate those on the fly... larger ones need to update - this._layouts.forEach(function(layout, column) { + this._layouts.forEach((layout, column) => { if (!layout || column === this.column) return; if (column < this.column) { this._layouts[column] = undefined; @@ -466,8 +460,8 @@ export class GridStackEngine { else { // we save the original x,y,w (h isn't cached) to see what actually changed to propagate better. // Note: we don't need to check against out of bound scaling/moving as that will be done when using those cache values. - nodes.forEach(function(node) { - let n = layout.find(function(l) { return l._id === node._id }); + nodes.forEach(node => { + let n = layout.find(l => l._id === node._id); if (!n) return; // no cache for new nodes. Will use those values. let ratio = column / this.column; // Y changed, push down same amount @@ -484,33 +478,33 @@ export class GridStackEngine { n.width = Math.round(node.width * ratio); } // ...height always carries over from cache - }, this); + }); } - }, this); + }); } /** - * Called to scale the widget width & position up/down based on the column change. + * @internal Called to scale the widget width & position up/down based on the column change. * Note we store previous layouts (especially original ones) to make it possible to go * from say 12 -> 1 -> 12 and get back to where we were. * - * oldColumn: previous number of columns - * column: new column number - * nodes?: different sorted list (ex: DOM order) instead of current list + * @param oldColumn previous number of columns + * @param column new column number + * @param nodes different sorted list (ex: DOM order) instead of current list */ - public _updateNodeWidths(oldColumn: number, column: number, nodes: GridStackNode[]) { + public updateNodeWidths(oldColumn: number, column: number, nodes: GridStackNode[]) { if (!this.nodes.length || oldColumn === column) { return; } // cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data - let copy: layout[] = []; - this.nodes.forEach(function(n, i) { copy[i] = {x: n.x, y: n.y, width: n.width, _id: n._id} }); // only thing we change is x,y,w and id to find it back + let copy: Layout[] = []; + this.nodes.forEach((n, i) => { copy[i] = {x: n.x, y: n.y, width: n.width, _id: n._id} }); // only thing we change is x,y,w and id to find it back this._layouts = this._layouts || []; // use array to find larger quick this._layouts[oldColumn] = copy; // if we're going to 1 column and using DOM order rather than default sorting, then generate that layout if (column === 1 && nodes && nodes.length) { let top = 0; - nodes.forEach(function(n) { + nodes.forEach(n => { n.x = 0; n.width = 1; n.y = Math.max(n.y, top); @@ -529,8 +523,8 @@ export class GridStackEngine { if (cacheNodes.length) { // pretend we came from that larger column by assigning those values as starting point oldColumn = lastIndex; - cacheNodes.forEach(function(cacheNode) { - let j = nodes.findIndex(function(n) {return n && n._id === cacheNode._id}); + cacheNodes.forEach(cacheNode => { + let j = nodes.findIndex(n => n._id === cacheNode._id); if (j !== -1) { // still current, use cache info positions nodes[j].x = cacheNode.x; @@ -543,9 +537,9 @@ export class GridStackEngine { } // if we found cache re-use those nodes that are still current - let newNodes = []; - cacheNodes.forEach(function(cacheNode) { - let j = nodes.findIndex(function(n) {return n && n._id === cacheNode._id}); + let newNodes: GridStackNode[] = []; + cacheNodes.forEach(cacheNode => { + let j = nodes.findIndex(n => n && n._id === cacheNode._id); if (j !== -1) { // still current, use cache info positions nodes[j].x = cacheNode.x; @@ -557,7 +551,7 @@ export class GridStackEngine { }); // ...and add any extra non-cached ones let ratio = column / oldColumn; - nodes.forEach(function(node) { + nodes.forEach(node => { if (!node) return; node.x = (column === 1 ? 0 : Math.round(node.x * ratio)); node.width = ((column === 1 || oldColumn === 1) ? 1 : (Math.round(node.width * ratio) || 1)); @@ -569,7 +563,7 @@ export class GridStackEngine { this._ignoreLayoutsNodeChange = true; this.batchUpdate(); this.nodes = []; // pretend we have no nodes to start with (we use same structures) to simplify layout - newNodes.forEach(function(node) { + newNodes.forEach(node => { this.addNode(node, false); // 'false' for add event trigger node._dirty = true; // force attr update }, this); @@ -577,9 +571,9 @@ export class GridStackEngine { delete this._ignoreLayoutsNodeChange; } - /** called to save initial position/size */ - public _saveInitial() { - this.nodes.forEach(function(n) { + /** @internal called to save initial position/size */ + public saveInitial() { + this.nodes.forEach(n => { n._origX = n.x; n._origY = n.y; n._origW = n.width; @@ -591,3 +585,11 @@ export class GridStackEngine { // legacy method renames private getGridHeight = obsolete(GridStackEngine.prototype.getRow, 'getGridHeight', 'getRow', 'v1.0.0'); } + +/** @internal class to store per column layout bare minimal info (subset of GridstackWidget) */ +interface Layout { + x: number; + y: number; + width: number; + _id: number; // so we can find full node back +} diff --git a/src/gridstack.ts b/src/gridstack.ts index 1590382ae..2ed8395be 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -6,7 +6,7 @@ * gridstack.js may be freely distributed under the MIT license. */ -import { GridStackEngine } from './gridstack-engine' +import { GridStackEngine } from './gridstack-engine'; import { obsoleteOpts, obsoleteOptsDel, obsoleteAttr, obsolete, Utils } from './utils'; import { GridItemHTMLElement, GridstackWidget, GridStackNode, GridstackOptions, numberOrString } from './types'; import { GridStackDragDropPlugin } from './gridstack-dragdrop-plugin'; @@ -40,22 +40,22 @@ interface GridCSSStyleSheet extends CSSStyleSheet { } function getElement(els: GridStackElement): GridItemHTMLElement { - return (typeof els === 'string' ? + return (typeof els === 'string' ? (document.querySelector(els) || document.querySelector('#' + els) || document.querySelector('.' + els)) : els); } function getElements(els: GridStackElement): GridItemHTMLElement[] { return (typeof els === 'string' ? Array.from( document.querySelectorAll(els) || document.querySelectorAll('#' + els) || document.querySelectorAll('.' + els) - ) : [els]); + ) : [els]); } function getGridElement(els: string | HTMLElement): GridHTMLElement { - return (typeof els === 'string' ? + return (typeof els === 'string' ? (document.querySelector(els) || document.querySelector('#' + els) || document.querySelector('.' + els)) : els); } function getGridElements(els: string | HTMLElement): GridHTMLElement[] { return (typeof els === 'string' ? Array.from( document.querySelectorAll(els) || document.querySelectorAll('#' + els) || document.querySelectorAll('.' + els) - ) : [els]); + ) : [els]); } /** @@ -73,6 +73,9 @@ export class GridStack { /** scoping so users can call GridStack.Utils.sort() for example */ public static Utils = Utils; + /** scoping so users can call new GridStack.Engine(12) for example */ + public static Engine = GridStackEngine; + /** * 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 @@ -86,7 +89,7 @@ export class GridStack { * Note: the HTMLElement (of type GridHTMLElement) will store a `gridstack: GridStack` value that can be retrieve later * let grid = document.querySelector('.grid-stack').gridstack; */ - public static init(options?: GridstackOptions, elOrString: GridStackElement = '.grid-stack'): GridStack { + public static init(options: GridstackOptions = {}, elOrString: GridStackElement = '.grid-stack'): GridStack { let el = getGridElement(elOrString); if (!el) { if (typeof elOrString === 'string') { @@ -112,7 +115,7 @@ export class GridStack { * let grids = GridStack.initAll(); * grids.forEach(...) */ - public static initAll(options?: GridstackOptions, selector = '.grid-stack'): GridStack[] { + public static initAll(options: GridstackOptions = {}, selector = '.grid-stack'): GridStack[] { let grids: GridStack[] = []; getGridElements(selector).forEach(el => { if (!el.gridstack) { @@ -139,20 +142,20 @@ export class GridStack { /** @internal */ private placeholder: HTMLElement; private $el: JQuery; // TODO: legacy code - private oneColumnMode: boolean; private dd: GridStackDragDropPlugin; + private _oneColumnMode: boolean; private _prevColumn: number; private _stylesId: string; private _gsEventHandler: {}; private _styles: GridCSSStyleSheet; - private isAutoCellHeight: boolean; + private _isAutoCellHeight: boolean; /** * Construct a grid item from the given element and options * @param el * @param opts */ - public constructor(el: GridHTMLElement, opts?: GridstackOptions) { + public constructor(el: GridHTMLElement, opts: GridstackOptions = {}) { this.el = el; // exposed HTML element to the user this.$el = $(el); // legacy code opts = opts || {}; // handles null/undefined/0 @@ -216,19 +219,17 @@ export class GridStack { verticalMarginUnit: 'px', cellHeightUnit: 'px', disableOneColumnMode: opts.disableOneColumnMode || false, - oneColumnModeDomSort: opts.oneColumnModeDomSort, - ddPlugin: null + oneColumnModeDomSort: opts.oneColumnModeDomSort }; this.opts = Utils.defaults(opts, defaults); if (this.opts.ddPlugin === false) { this.opts.ddPlugin = GridStackDragDropPlugin; - } else if (this.opts.ddPlugin === null) { + } else if (this.opts.ddPlugin === undefined) { this.opts.ddPlugin = GridStackDragDropPlugin.registeredPlugins[0] || GridStackDragDropPlugin; } - - this.dd = new this.opts.ddPlugin(this); + this.dd = new (this.opts.ddPlugin as typeof GridStackDragDropPlugin)(this); if (this.opts.rtl === 'auto') { this.opts.rtl = el.style.direction === 'rtl'; @@ -243,8 +244,8 @@ export class GridStack { this.el.classList.add('grid-stack-nested'); } - this.isAutoCellHeight = (this.opts.cellHeight === 'auto'); - if (this.isAutoCellHeight) { + this._isAutoCellHeight = (this.opts.cellHeight === 'auto'); + if (this._isAutoCellHeight) { // make the cell square initially this.cellHeight(this.cellWidth(), true); } else { @@ -277,7 +278,6 @@ export class GridStack { if (this.opts.auto) { let elements = []; - let _this = this; this.$el.children('.' + this.opts.itemClass + ':not(.' + this.opts.placeholderClass + ')') .each((index, el) => { let x = parseInt(el.getAttribute('data-gs-x')); @@ -285,12 +285,12 @@ export class GridStack { elements.push({ el: 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.opts.column + i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * this.opts.column }); }); - Utils.sortBy(elements, x => x.i).forEach( item => { this._prepareElement(item.el) }); + elements.sort(x => x.i).forEach( item => { this._prepareElement(item.el) }); } - this.engine._saveInitial(); // initial start of items + this.engine.saveInitial(); // initial start of items this.setAnimation(this.opts.animate); @@ -303,8 +303,8 @@ export class GridStack { this._updateContainerHeight(); - $(window).resize(this.onResizeHandler.bind(this)); - this.onResizeHandler(); + $(window).resize(this._onResizeHandler.bind(this)); + this._onResizeHandler(); if (!this.opts.staticGrid && typeof this.opts.removable === 'string') { let trashZone = $(this.opts.removable); @@ -332,7 +332,7 @@ export class GridStack { }); } - this.setupAcceptWidget(); + this._setupAcceptWidget(); }; @@ -378,7 +378,7 @@ export class GridStack { if (x === undefined || typeof x === 'object') { // 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.attr('data-gs-xyz) before adding the node. - // opt = this.engine._prepareNode(opt); + // opt = this.engine.prepareNode(opt); x = (x || {}) as GridstackWidget; } else { // old legacy way of calling with items spelled out - call us back with single object instead (so we can properly initialized values) @@ -400,7 +400,7 @@ export class GridStack { /** * Initializes batch updates. You will see no changes until `commit()` method is called. */ - public batchUpdate(): void { + public batchUpdate() { this.engine.batchUpdate(); } @@ -430,7 +430,7 @@ export class GridStack { * @example * grid.cellHeight(grid.cellWidth() * 1.2); */ - public cellHeight(val: numberOrString, noUpdate?: boolean): void { + public cellHeight(val: numberOrString, noUpdate?: boolean) { let heightData = Utils.parseHeight(val); if (this.opts.cellHeightUnit === heightData.unit && this.opts.cellHeight === heightData.height) { return ; @@ -454,7 +454,7 @@ export class GridStack { /** * Finishes batch updates. Updates DOM nodes. You must call it after batchUpdate. */ - public commit(): void { + public commit() { this.engine.commit(); this._triggerRemoveEvent(); this._triggerAddEvent(); @@ -462,7 +462,7 @@ export class GridStack { }; /** re-layout grid items to reclaim any empty space */ - public compact(): void { + public compact() { this.engine.compact(); this._triggerChangeEvent(); } @@ -494,8 +494,8 @@ export class GridStack { if (doNotPropagate === true) { return; } // update the items now - see if the dom order nodes should be passed instead (else default to current list) - let domNodes; - if (this.opts.oneColumnModeDomSort && column === 1) { + let domNodes: GridStackNode[]; + if (column === 1 && this.opts.oneColumnModeDomSort) { domNodes = []; this.$el.children('.' + this.opts.itemClass).each((index, el: GridItemHTMLElement) => { let node = el.gridstackNode; @@ -503,7 +503,7 @@ export class GridStack { }); if (!domNodes.length) { domNodes = undefined; } } - this.engine._updateNodeWidths(oldColumn, column, domNodes); + this.engine.updateNodeWidths(oldColumn, column, domNodes); // and trigger our event last... this._triggerChangeEvent(true); // skip layout update @@ -520,8 +520,8 @@ export class GridStack { * Destroys a grid instance. * @param detachGrid if false nodes and grid will not be removed from the DOM (Optional. Default true). */ - public destroy(detachGrid = true): void { - $(window).off('resize', this.onResizeHandler); + public destroy(detachGrid = true) { + $(window).off('resize', this._onResizeHandler); this.disable(); if (!detachGrid) { this.removeAll(false); @@ -542,7 +542,7 @@ export class GridStack { * grid.enableMove(false); * grid.enableResize(false); */ - public disable(): void { + public disable() { this.enableMove(false); this.enableResize(false); this._triggerEvent('disable'); @@ -554,7 +554,7 @@ export class GridStack { * grid.enableMove(true); * grid.enableResize(true); */ - public enable(): void { + public enable() { this.enableMove(true); this.enableResize(true); this._triggerEvent('enable'); @@ -567,7 +567,7 @@ export class GridStack { * @param includeNewWidgets will force new widgets to be draggable as per * doEnable`s value by changing the disableDrag grid option (default: true). */ - public enableMove(doEnable: boolean, includeNewWidgets = true): void { + public enableMove(doEnable: boolean, includeNewWidgets = true) { this.$el.children('.' + this.opts.itemClass).each((index, el) => { this.movable(el, doEnable); }); @@ -651,7 +651,7 @@ export class GridStack { public locked(els: GridStackElement, val: boolean): GridStack { getElements(els).forEach(el => { let node = el.gridstackNode; - if (node!) return; + if (!node) return; node.locked = (val || false); if (node.locked) { el.setAttribute('data-gs-locked', 'yes'); @@ -787,7 +787,7 @@ export class GridStack { * @param x new position x. If value is null or undefined it will be ignored. * @param y new position y. If value is null or undefined it will be ignored. */ - public move(els: GridStackElement, x?: number, y?: number): void { + public move(els: GridStackElement, x?: number, y?: number) { this._updateElement(els, (el, node) => { x = (x !== undefined) ? x : node.x; y = (y !== undefined) ? y : node.y; @@ -812,10 +812,11 @@ export class GridStack { * grid.el.addEventListener('added', function(event) { log('added ', event.detail)} ); * */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any public on(eventName: GridStackEvent, callback: (event: CustomEvent, arg2?: GridStackNode[] | Record, arg3?: Record) => void) { // check for array of names being passed instead if (eventName.indexOf(' ') !== -1) { - var names = eventName.split(' ') as GridStackEvent[]; + let names = eventName.split(' ') as GridStackEvent[]; names.forEach(name => this.on(name, callback)); return; } @@ -831,8 +832,8 @@ export class GridStack { } this.el.addEventListener(eventName, this._gsEventHandler[eventName]); } else { - // still JQuery events - this.$el.on(eventName as any, callback); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.$el.on(eventName as any, callback); // still JQuery events } } @@ -843,7 +844,7 @@ export class GridStack { public off(eventName: GridStackEvent) { // check for array of names being passed instead if (eventName.indexOf(' ') !== -1) { - var names = eventName.split(' ') as GridStackEvent[]; + let names = eventName.split(' ') as GridStackEvent[]; names.forEach(name => this.off(name)); return; } @@ -904,7 +905,7 @@ export class GridStack { * @param width new dimensions width. If value is null or undefined it will be ignored. * @param height new dimensions height. If value is null or undefined it will be ignored. */ - public resize(els: GridStackElement, width?: number, height?: number): void { + public resize(els: GridStackElement, width?: number, height?: number) { this._updateElement(els, (el, node) => { width = (width || node.width); height = (height || node.height); @@ -936,7 +937,7 @@ export class GridStack { * Toggle the grid animation state. Toggles the `grid-stack-animate` class. * @param doAnimate if true the grid will animate. */ - public setAnimation(doAnimate: boolean): void { + public setAnimation(doAnimate: boolean) { if (doAnimate) { this.el.classList.add('grid-stack-animate'); } else { @@ -948,7 +949,7 @@ export class GridStack { * Toggle the grid static state. Also toggle the grid-stack-static class. * @param staticValue if true the grid become static. */ - public setStatic(staticValue: boolean): void { + public setStatic(staticValue: boolean) { this.opts.staticGrid = (staticValue === true); this.enableMove(!staticValue); this.enableResize(!staticValue); @@ -963,7 +964,7 @@ export class GridStack { * @param width new dimensions width. If value is null or undefined it will be ignored. * @param height new dimensions height. If value is null or undefined it will be ignored. */ - public update(els: GridStackElement, x?: number, y?: number, width?: number, height?: number): void { + public update(els: GridStackElement, x?: number, y?: number, width?: number, height?: number) { this._updateElement(els, (el, node) => { x = (x !== undefined) ? x : node.x; y = (y !== undefined) ? y : node.y; @@ -980,7 +981,7 @@ export class GridStack { * @param value new vertical margin value * @param noUpdate (optional) if true, styles will not be updated */ - public verticalMargin(value: numberOrString, noUpdate?: boolean): void { + public verticalMargin(value: numberOrString, noUpdate?: boolean) { let heightData = Utils.parseHeight(value); if (this.opts.verticalMarginUnit === heightData.unit && this.opts.maxRow === heightData.height) { @@ -994,9 +995,7 @@ export class GridStack { } } - /** - * returns current vertical margin value - */ + /** returns current vertical margin value */ public getVerticalMargin(): number { return this.opts.verticalMargin as number; } /** @@ -1017,26 +1016,25 @@ export class GridStack { * } */ public willItFit(x: number, y: number, width: number, height: number, autoPosition: boolean): boolean { - let node = {x: x, y: y, width: width, height: height, autoPosition: autoPosition}; - return this.engine.canBePlacedWithRespectToHeight(node); + return this.engine.canBePlacedWithRespectToHeight({x, y, width, height, autoPosition}); } - private _triggerChangeEvent(skipLayoutChange?: boolean): void { + private _triggerChangeEvent(skipLayoutChange?: boolean) { if (this.engine.batchMode) { return; } let elements = this.engine.getDirtyNodes(true); // verify they really changed if (elements && elements.length) { if (!skipLayoutChange) { - this.engine._layoutsNodesChange(elements); + this.engine.layoutsNodesChange(elements); } this._triggerEvent('change', elements); } - this.engine._saveInitial(); // we called, now reset initial values & dirty flags + this.engine.saveInitial(); // we called, now reset initial values & dirty flags } private _triggerAddEvent() { if (this.engine.batchMode) { return; } if (this.engine.addedNodes && this.engine.addedNodes.length > 0) { - this.engine._layoutsNodesChange(this.engine.addedNodes); + this.engine.layoutsNodesChange(this.engine.addedNodes); // prevent added nodes from also triggering 'change' event (which is called next) this.engine.addedNodes.forEach(n => { delete n._dirty; }); this._triggerEvent('added', this.engine.addedNodes); @@ -1052,6 +1050,7 @@ export class GridStack { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private _triggerEvent(name: string, data?: any) { let event = data ? new CustomEvent(name, {bubbles: false, detail: data}) : new Event(name); this.el.dispatchEvent(event); @@ -1189,9 +1188,7 @@ export class GridStack { node._isAboutToRemove = false; } - /** - * prepares the element for drag&drop - **/ + /** prepares the element for drag&drop **/ private _prepareElementsByNode(el: GridItemHTMLElement, node: GridStackNode) { // variables used/cashed between the 3 start/move/end methods, in addition to node passed above let cellWidth; @@ -1245,7 +1242,7 @@ export class GridStack { if (event.type === 'drag') { let distance = ui.position.top - node._prevYPix; node._prevYPix = ui.position.top; - Utils.updateScrollPosition(el, ui, distance); + Utils.updateScrollPosition(el, ui.position, distance); if (el.dataset.inTrashZone || x < 0 || x >= this.engine.column || y < 0 || (!this.engine.float && y > this.engine.getRow())) { if (!node._temporaryRemoved) { @@ -1301,7 +1298,7 @@ export class GridStack { } /** called when the item stops moving/resizing */ - let onEndMoving = (event): void => { + let onEndMoving = (event) => { let { target } = event; if (!target.gridstackNode) { return; } @@ -1338,7 +1335,7 @@ export class GridStack { let nestedGrids = target.querySelectorAll('.grid-stack'); if (nestedGrids.length && event.type === 'resizestop') { nestedGrids.each((index, el: GridHTMLElement) => { - el.gridstack.onResizeHandler(); + el.gridstack._onResizeHandler(); }); this._triggerNativeEvent(target, '.grid-stack-item', 'resizestop'); @@ -1374,7 +1371,7 @@ export class GridStack { this._writeAttr(el, node); } - _triggerNativeEvent(el: HTMLElement, selector: string, eventName: string): void { + private _triggerNativeEvent(el: HTMLElement, selector: string, eventName: string) { let elements = el.querySelectorAll(selector); if (elements.length) { let event = document.createEvent('HTMLEvents'); @@ -1384,7 +1381,7 @@ export class GridStack { } } - private _prepareElement(el: GridItemHTMLElement, triggerAddEvent?: boolean): void { + private _prepareElement(el: GridItemHTMLElement, triggerAddEvent?: boolean) { triggerAddEvent = (triggerAddEvent !== undefined ? triggerAddEvent : false); el.classList.add(this.opts.itemClass); @@ -1396,7 +1393,7 @@ export class GridStack { } /** call to write x,y,w,h attributes back to element */ - private _writeAttrs(el: HTMLElement, x?: number, y?: number, width?: number, height?: number): void { + private _writeAttrs(el: HTMLElement, x?: number, y?: number, width?: number, height?: number) { if (x !== undefined && x !== null) { el.setAttribute('data-gs-x', String(x)); } if (y !== undefined && y !== null) { el.setAttribute('data-gs-y', String(y)); } if (width) { el.setAttribute('data-gs-width', String(width)); } @@ -1404,7 +1401,7 @@ export class GridStack { } /** call to write any default attributes back to element */ - private _writeAttr(el: HTMLElement, node: GridstackWidget = {}): void { + private _writeAttr(el: HTMLElement, node: GridstackWidget = {}) { this._writeAttrs(el, node.x, node.y, node.width, node.height); if (node.autoPosition) { @@ -1459,7 +1456,7 @@ export class GridStack { return node; } - private _updateElement(els: GridStackElement, callback: (el:GridItemHTMLElement, node: GridStackNode) => void): void { + private _updateElement(els: GridStackElement, callback: (el: GridItemHTMLElement, node: GridStackNode) => void) { let el = getElement(els); if (!el) { return; } let node = el.gridstackNode; @@ -1490,8 +1487,8 @@ export class GridStack { * called when we are being resized - check if the one Column Mode needs to be turned on/off * and remember the prev columns we used. */ - public onResizeHandler() { - if (this.isAutoCellHeight) { + private _onResizeHandler() { + if (this._isAutoCellHeight) { Utils.throttle(() => { this.cellHeight(this.cellWidth(), false)}, 100); } @@ -1499,24 +1496,24 @@ export class GridStack { if (!this.opts.disableOneColumnMode && (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) <= this.opts.minWidth) { - if (this.oneColumnMode) { return; } - this.oneColumnMode = true; + if (this._oneColumnMode) { return; } + this._oneColumnMode = true; this.column(1); } else { - if (!this.oneColumnMode) { return; } - this.oneColumnMode = false; + if (!this._oneColumnMode) { return; } + this._oneColumnMode = false; this.column(this._prevColumn); } } /** called to add drag over support to support widgets */ - private setupAcceptWidget() { + private _setupAcceptWidget() { if (this.opts.staticGrid || !this.opts.acceptWidgets) return; // vars used by the function callback let draggingElement: GridItemHTMLElement; - let onDrag = (event, ui) => { + let onDrag = event => { let el = draggingElement; let node = el.gridstackNode; let pos = this.getCellFromPixel({left: event.pageX, top: event.pageY}, true); @@ -1586,7 +1583,7 @@ export class GridStack { draggingElement = el; - let node = this.engine._prepareNode({width: width, height: height, _added: false, _temporary: true}); + let node = this.engine.prepareNode({width, height, _added: false, _temporary: true}); node._isOutOfGrid = true; el.gridstackNode = node; el._gridstackNodeOrig = origNode; diff --git a/src/jqueryui-gridstack-dragdrop-plugin.ts b/src/jqueryui-gridstack-dragdrop-plugin.ts index 84bd49f17..0426ccf96 100644 --- a/src/jqueryui-gridstack-dragdrop-plugin.ts +++ b/src/jqueryui-gridstack-dragdrop-plugin.ts @@ -24,51 +24,54 @@ export class JQueryUIGridStackDragDropPlugin extends GridStackDragDropPlugin { } public resizable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?): GridStackDragDropPlugin { - let $el = $(el); + let $el: JQuery = $(el); if (opts === 'disable' || opts === 'enable' || opts === 'destroy') { - $el.resizable(opts); + $el.resizable(opts); } else if (opts === 'option') { $el.resizable(opts, key, value); } else { let handles = $el.data('gs-resize-handles') ? $el.data('gs-resize-handles') : this.grid.opts.resizable.handles; $el.resizable({...this.grid.opts.resizable, ...{handles: handles}, ...{ // was using $.extend() - start: opts.start || function() {}, - stop: opts.stop || function() {}, - resize: opts.resize || function() {} + start: opts.start, // || function() {}, + stop: opts.stop, // || function() {}, + resize: opts.resize // || function() {} }}); } return this; } public draggable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?): GridStackDragDropPlugin { - let $el = $(el); + let $el: JQuery = $(el); if (opts === 'disable' || opts === 'enable' || opts === 'destroy') { $el.draggable(opts); + } else if (opts === 'option') { + $el.draggable(opts, key, value); } else { $el.draggable({...this.grid.opts.draggable, ...{ // was using $.extend() containment: (this.grid.opts._isNested && !this.grid.opts.dragOut) ? $(this.grid.el).parent() : (this.grid.opts.draggable.containment || null), - start: opts.start || function() {}, - stop: opts.stop || function() {}, - drag: opts.drag || function() {} + start: opts.start, // || function() {}, + stop: opts.stop, // || function() {}, + drag: opts.drag // || function() {} }}); } return this; } public droppable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?): GridStackDragDropPlugin { - let $el = $(el); - $el.droppable(opts); + let $el: JQuery = $(el); + $el.droppable(opts, key, value); return this; } public isDroppable(el: GridItemHTMLElement): boolean { - let $el = $(el); + let $el: JQuery = $(el); return Boolean($el.data('droppable')); } public on(el: GridItemHTMLElement, eventName: string, callback): GridStackDragDropPlugin { - $(el).on(eventName, callback); + let $el: JQuery = $(el); + $el.on(eventName, callback); return this; } } @@ -76,7 +79,7 @@ export class JQueryUIGridStackDragDropPlugin extends GridStackDragDropPlugin { // finally register ourself GridStackDragDropPlugin.registerPlugin(JQueryUIGridStackDragDropPlugin); -/* OLD code for reference +/* OLD code for reference function JQueryUIGridStackDragDropPlugin(grid) { GridStack.DragDropPlugin.call(this, grid); } diff --git a/src/types.ts b/src/types.ts index 019dc1743..578175d9f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,6 +7,7 @@ */ import { GridStack } from './gridstack'; +import { GridStackDragDropPlugin } from './gridstack-dragdrop-plugin'; export type numberOrString = number | string; export interface GridItemHTMLElement extends HTMLElement { @@ -52,9 +53,9 @@ export interface GridstackOptions { column?: number; /** class that implement drag'n'drop functionality for gridstack. If false grid will be static. - * (default?: null - first available plugin will be used) + * (default?: undefined - first available plugin will be used) */ - ddPlugin?: false | null | any; + ddPlugin?: false | typeof GridStackDragDropPlugin; /** disallows dragging of widgets (default?: false) */ disableDrag?: boolean; @@ -66,7 +67,7 @@ export interface GridstackOptions { disableResize?: boolean; /** allows to override UI draggable options. (default?: { handle?: '.grid-stack-item-content', scroll?: true, appendTo?: 'body', containment: null }) */ - draggable?: {} | any; + draggable?: DDDragOpt; /** let user drag nested grid items out of a parent or not (default false) */ dragOut?: boolean; @@ -95,7 +96,7 @@ export interface GridstackOptions { minWidth?: number; /** - * set to true if you want oneColumnMode to use the DOM order and ignore x,y from normal multi column + * set to true if you want oneColumnMode to use the DOM order and ignore x,y from normal multi column * layouts during sorting. This enables you to have custom 1 column layout that differ from the rest. (default?: false) */ oneColumnModeDomSort?: boolean; @@ -106,8 +107,8 @@ export interface GridstackOptions { /** placeholder default content (default?: '') */ placeholderText?: string; - /** allows to override UI resizable options. (default?: { autoHide?: true, handles?: 'se' }) */ - resizable?: {} | any; + /** allows to override UI resizable options. (default?: { autoHide: true, handles: 'se' }) */ + resizable?: DDResizeOpt; /** * if true widgets could be removed by dragging outside of the grid. It could also be a selector string, @@ -117,7 +118,7 @@ export interface GridstackOptions { removable?: boolean | string; /** allows to override UI removable options. (default?: { accept: '.' + opts.itemClass }) */ - removableOptions?: {}; + removableOptions?: DDRemoveOpt; /** time in milliseconds before widget is being removed while dragging outside of the grid. (default?: 2000) */ removeTimeout?: number; @@ -189,6 +190,35 @@ export interface GridstackWidget { id?: numberOrString; } +/** Drag&Drop resize options */ +export interface DDResizeOpt { + /** do resize handle hide by default until mouse over ? - default: true */ + autoHide?: boolean; + /** + * sides where you can resize from (ex: 'e, se, s, sw, w') - default 'se' (south-east) + * Note: it is not recommended to resize from the top sides as weird side effect may occur. + */ + handles?: string; +} + +/** Drag&Drop remove options */ +export interface DDRemoveOpt { + /** class that be removed default?: '.' + opts.itemClass */ + accept?: string; +} + +/** Drag&Drop dragging options */ +export interface DDDragOpt { + /** class selector of items that can be dragged. default to '.grid-stack-item-content' */ + handle?: string; + /** default to `true` */ + scroll?: boolean; + /** default to 'body' */ + appendTo?: string; + /** parent constraining where item can be dragged out from (default: null = no constrain) */ + containment?: string; +} + /** * internal descriptions describing the items in the grid */ diff --git a/src/utils.ts b/src/utils.ts index 8bb3b7b2c..94d10b013 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,10 +10,10 @@ import { GridstackWidget, GridStackNode, GridstackOptions, numberOrString } from /** checks for obsolete method names */ export function obsolete(f, oldName: string, newName: string, rev: string) { - let wrapper = function() { + let wrapper = (...args) => { console.warn('gridstack.js: Function `' + oldName + '` is deprecated in ' + rev + ' and has been replaced ' + 'with `' + newName + '`. It will be **completely** removed in v1.0'); - return f.apply(this, arguments); + return f.apply(args); } wrapper.prototype = f.prototype; return wrapper; @@ -45,7 +45,7 @@ export function obsoleteAttr(el: HTMLElement, oldName: string, newName: string, } } - /** +/** * Utility methods */ export class Utils { @@ -61,38 +61,53 @@ export class Utils { * @param dir 1 for asc, -1 for desc (optional) * @param width width of the grid. If undefined the width will be calculated automatically (optional). **/ - static sort(nodes: GridStackNode[], dir?: number, column?: number): GridStackNode[] { + static sort(nodes: GridStackNode[], dir?: -1 | 1, column?: number): GridStackNode[] { if (!column) { - let widths = nodes.map(function (node) { return node.x + node.width; }); - column = Math.max.apply(Math, widths); + let widths = nodes.map(n => n.x + n.width); + column = Math.max(...widths); } if (dir === -1) - return this.sortBy(nodes, (n) => -(n.x + n.y * column)); + return nodes.sort((a, b) => (b.x + b.y * column)-(a.x + a.y * column)); else - return this.sortBy(nodes, (n) => (n.x + n.y * column)); + return nodes.sort((b, a) => (b.x + b.y * column)-(a.x + a.y * column)); } + /** + * creates a style sheet with style id under given parent + * @param id will set the 'data-gs-style-id' attribute to that id + * @param parent to insert the stylesheet as first child, + * if none supplied it will be appended to the document head instead. + */ static createStylesheet(id: string, parent?: HTMLElement): CSSStyleSheet { let style: HTMLStyleElement = document.createElement('style'); style.setAttribute('type', 'text/css'); style.setAttribute('data-gs-style-id', id); - if ((style as any).styleSheet) { // ??? only CSSImportRule have that and different beast... + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((style as any).styleSheet) { // TODO: only CSSImportRule have that and different beast ?? + // eslint-disable-next-line @typescript-eslint/no-explicit-any (style as any).styleSheet.cssText = ''; } else { style.appendChild(document.createTextNode('')); // WebKit hack } - if (!parent) { parent = document.getElementsByTagName('head')[0]; } // default to head - parent.insertBefore(style, parent.firstChild); + if (!parent) { + // default to head + parent = document.getElementsByTagName('head')[0]; + parent.appendChild(style); + } else { + parent.insertBefore(style, parent.firstChild); + } return style.sheet as CSSStyleSheet; } + /** removed the given stylesheet id */ static removeStylesheet(id: string) { let el = document.querySelector('STYLE[data-gs-style-id=' + id + ']'); - if (!el) return; + if (!el || !el.parentNode) return; el.parentNode.removeChild(el); } + /** inserts a CSS rule */ static insertCSSRule(sheet: CSSStyleSheet, selector: string, rules: string, index: number) { if (typeof sheet.insertRule === 'function') { sheet.insertRule(selector + '{' + rules + '}', index); @@ -101,6 +116,7 @@ export class Utils { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any static toBool(v: any): boolean { if (typeof v === 'boolean') { return v; @@ -113,9 +129,7 @@ export class Utils { } static toNumber(value: null | string): number | null { - return value === null || value.length === 0 - ? null - : Number(value); + return (value === null || value.length === 0) ? null : Number(value); } static parseHeight(val: numberOrString) { @@ -134,32 +148,7 @@ export class Utils { return { height: height, unit: heightUnit } } - static without(array, item) { - let index = array.indexOf(item); - - if (index !== -1) { - array = array.slice(0); - array.splice(index, 1); - } - - return array; - } - - static sortBy(array, getter) { - return array.slice(0).sort(function (left, right) { - let valueLeft = getter(left); - let valueRight = getter(right); - - if (valueRight === valueLeft) { - return 0; - } - - return valueLeft > valueRight ? 1 : -1; - }); - } - - static defaults(target, arg1) { - let sources = Array.prototype.slice.call(arguments, 1); + static defaults(target, ...sources) { sources.forEach(function (source) { for (let prop in source) { @@ -172,23 +161,23 @@ export class Utils { return target; } - static clone(target) { + static clone(target: {}) { return {...target}; // was $.extend({}, target) } static throttle(callback, delay) { let isWaiting = false; - return function () { + return function (...args) { if (!isWaiting) { - callback.apply(this, arguments); + callback.apply(this, args); isWaiting = true; setTimeout(function () { isWaiting = false; }, delay); } } } - static removePositioningStyles(el) { + static removePositioningStyles(el: HTMLElement) { let style = el.style; if (style.position) { style.removeProperty('position'); @@ -207,19 +196,20 @@ export class Utils { } } - static getScrollParent(el) { + static getScrollParent(el: HTMLElement) { let returnEl; if (el === null) { returnEl = null; } else if (el.scrollHeight > el.clientHeight) { returnEl = el; } else { - returnEl = this.getScrollParent(el.parentNode); + returnEl = this.getScrollParent(el.parentElement); } return returnEl; } - static updateScrollPosition(el, ui, distance) { + /** @private */ + static updateScrollPosition(el: HTMLElement, position: {top: number}, distance: number) { // is widget in view? let rect = el.getBoundingClientRect(); let innerHeightOrClientHeight = (window.innerHeight || document.documentElement.clientHeight); @@ -250,7 +240,7 @@ export class Utils { } } // move widget y by amount scrolled - ui.position.top += scrollEl.scrollTop - prevScroll; + position.top += scrollEl.scrollTop - prevScroll; } } } diff --git a/yarn.lock b/yarn.lock index da9a7d9b0..a64e7ead3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -112,7 +112,7 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/jquery@*", "@types/jquery@^3.3.32": +"@types/jquery@*", "@types/jquery@^3.3.33": version "3.3.33" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.33.tgz#61d9cbd4004ffcdf6cf7e34720a87a5625a7d8e9" integrity sha512-U6IdXYGkfUI42SR79vB2Spj+h1Ly3J3UZjpd8mi943lh126TK7CB+HZOxGh2nM3IySor7wqVQdemD/xtydsBKA==