diff --git a/doc/CHANGES.md b/doc/CHANGES.md index d2516c89d..84fe375d4 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -50,9 +50,9 @@ Change log ## 3.3.0-dev -- fix [#149](https://github.com/gridstack/gridstack.js/issues/149) [#1094](https://github.com/gridstack/gridstack.js/issues/1094) [#1605](https://github.com/gridstack/gridstack.js/issues/1605) re-write of the **collision code**! you can now swap items of the same size (vertical/horizontal) when grid is full, and is the default in `float:false` (top gravity) as it feels more natural. Could add Alt key for swap vs push behavior later.
-Dragging up and down now behave the same (used to require push WAY down past to swap/append). Also much more efficient collision code.
-Still TODO: handle mid point of dragged over items rather 50% of row/column and check for the most covered when multiple items collide. +- fix [#149](https://github.com/gridstack/gridstack.js/issues/149) [#1094](https://github.com/gridstack/gridstack.js/issues/1094) [#1605](https://github.com/gridstack/gridstack.js/issues/1605) re-write of the **collision code**! you can now swap items of the same size (vertical/horizontal) when grid is full, and is the default in `float:false` (top gravity) as it feels more natural. Could add Alt key for swap vs push behavior later. +- Dragging up and down now behave the same (used to require push WAY down past to swap/append). Also much more efficient collision code. +- handle mid point of dragged over items (>50%) rather than a new row/column and check for the most covered when multiple items collide. - fix [1617](https://github.com/gridstack/gridstack.js/issues/1617) FireFox DOM order issue. Thanks [@marcel-necker](https://github.com/marcel-necker) ## 3.3.0 (2021-2-2) diff --git a/spec/e2e/html/141_1534_swap.html b/spec/e2e/html/141_1534_swap.html index 63412c8eb..8c83e232b 100644 --- a/spec/e2e/html/141_1534_swap.html +++ b/spec/e2e/html/141_1534_swap.html @@ -34,7 +34,7 @@

Swap collision demo

let count = 0; let items = [ - {x:0, y:0}, {x:0, y:1}, {x:0, y:2},{x:1, y:0}, {x:1, y:1}, {x:1, y:2}, + {x:0, y:0}, {x:0, y:1}, {x:0, y:2},{x:1, y:0}, {x:1, y:1}, {x:1, y:2, h:2, w:2}, {x:5, y:0}, {x:4, y:1, w:3, locked: false, _content: 'locked'}, {x:5, y:2}, {x:7, y:0}, {x:8, y:0}, {x:9, y:0}, {x:7, y:1, w:3}, {x:8, y:2}, {x:11, y:0}, {x:11, y:1, h:2}, diff --git a/spec/gridstack-engine-spec.ts b/spec/gridstack-engine-spec.ts index 1066c5195..8574dd398 100644 --- a/spec/gridstack-engine-spec.ts +++ b/spec/gridstack-engine-spec.ts @@ -333,23 +333,23 @@ describe('gridstack engine', function() { }); it('should return true for changed x', function() { let widget = { x: 1, y: 2, w: 3, h: 4 }; - expect(engine.changedPos(widget, 2, 2)).toEqual(true); + expect(engine.changedPosConstrain(widget, {x:2, y:2})).toEqual(true); }); it('should return true for changed y', function() { let widget = { x: 1, y: 2, w: 3, h: 4 }; - expect(engine.changedPos(widget, 1, 1)).toEqual(true); + expect(engine.changedPosConstrain(widget, {x:1, y:1})).toEqual(true); }); it('should return true for changed width', function() { let widget = { x: 1, y: 2, w: 3, h: 4 }; - expect(engine.changedPos(widget, 2, 2, 4, 4)).toEqual(true); + expect(engine.changedPosConstrain(widget, {x:2, y:2, w:4, h:4})).toEqual(true); }); it('should return true for changed height', function() { let widget = { x: 1, y: 2, w: 3, h: 4 }; - expect(engine.changedPos(widget, 1, 2, 3, 3)).toEqual(true); + expect(engine.changedPosConstrain(widget, {x:1, y:2, w:3, h:3})).toEqual(true); }); it('should return false for unchanged position', function() { let widget = { x: 1, y: 2, w: 3, h: 4 }; - expect(engine.changedPos(widget, 1, 2, 3, 4)).toEqual(false); + expect(engine.changedPosConstrain(widget, {x:1, y:2, w:3, h:4})).toEqual(false); }); }); @@ -368,17 +368,17 @@ describe('gridstack engine', function() { engine.addNode(nodes[1]) // add item that moves past locked one expect(findNode(engine, 1)).toEqual(jasmine.objectContaining({x: 0, y: 1, w: 12, h: 1, locked: true})); - expect(findNode(engine, 2)).toEqual(jasmine.objectContaining({x: 1, y: 2})); + expect(findNode(engine, 2)).toEqual(jasmine.objectContaining({x: 1, y: 2, h: 3, id: 2})); // prevents moving locked item let node1 = findNode(engine, 1); - expect(engine.moveNode(node1, 6, 6)).toEqual(false); + expect(engine.moveNode(node1, {x:6, y:6})).toEqual(false); // but moves regular one (gravity ON) let node2 = findNode(engine, 2); - expect(engine.moveNode(node2, 6, 6)).toEqual(true); + expect(engine.moveNode(node2, {x:6, y:6})).toEqual(true); expect(node2).toEqual(jasmine.objectContaining({x: 6, y: 2, w: 2, h: 3})); // but moves regular one (gravity OFF) engine.float = true; - expect(engine.moveNode(node2, 7, 6)).toEqual(true); + expect(engine.moveNode(node2, {x:7, y:6})).toEqual(true); expect(node2).toEqual(jasmine.objectContaining({x: 7, y: 6, w: 2, h: 3})); }); }); diff --git a/src/gridstack-dd.ts b/src/gridstack-dd.ts index bfbf7d1b5..fae5fdd20 100644 --- a/src/gridstack-dd.ts +++ b/src/gridstack-dd.ts @@ -8,7 +8,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { GridStackDDI } from './gridstack-ddi'; -import { GridItemHTMLElement, GridStackNode, GridStackElement, DDUIData, DDDragInOpt } from './types'; +import { GridItemHTMLElement, GridStackNode, GridStackElement, DDUIData, DDDragInOpt, GridStackPosition } from './types'; import { GridStack } from './gridstack'; import { Utils } from './utils'; @@ -115,7 +115,7 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack { node.el = this.placeholder; // dom we update while dragging... this._updateContainerHeight(); - } else if (this.engine.moveNodeCheck(node, x, y)) { + } else if (this.engine.moveNodeCheck(node, {x, y})) { this._updateContainerHeight(); } }; @@ -381,6 +381,7 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid // set the min/max resize info cellWidth = this.cellWidth(); cellHeight = this.getCellHeight(true); // force pixels for calculations + this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft); let dd = GridStackDD.get() .resizable(el, 'option', 'minWidth', cellWidth * (node.minW || 1)) .resizable(el, 'option', 'minHeight', cellHeight * (node.minH || 1)); @@ -395,8 +396,8 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid let top = ui.position.top + (ui.position.top > node._lastUiPosition.top ? -this.opts.marginBottom : this.opts.marginTop); let x = Math.round(left / cellWidth); let y = Math.round(top / cellHeight); - let w: number; - let h: number; + let w = node.w; + let h = node.h; let resizing: boolean; if (event.type === 'drag') { @@ -433,7 +434,8 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid } } if (node.x === x && node.y === y) return; // skip same - if (node._lastTried && node._lastTried.x === x && node._lastTried.y === y) return; // skip one we tried (but failed) + // DON'T skip one we tried as we might have failed because of coverage <50% before + // if (node._lastTried && node._lastTried.x === x && node._lastTried.y === y) return; } else if (event.type === 'resize') { if (x < 0) return; // Scrolling page if needed @@ -446,8 +448,15 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid } node._lastTried = {x, y, w, h}; // set as last tried (will nuke if we go there) - if (this.engine.moveNodeCheck(node, x, y, w, h)) { + let rect: GridStackPosition = { // screen pix of the dragged box + x: ui.position.left + this.opts.marginLeft, + y: ui.position.top + this.opts.marginTop, + w: (ui.size ? ui.size.width : node.w * cellWidth) - this.opts.marginLeft - this.opts.marginRight, + h: (ui.size ? ui.size.height : node.h * cellHeight) - this.opts.marginTop - this.opts.marginBottom + }; + if (this.engine.moveNodeCheck(node, {x, y, w, h, cellWidth, cellHeight, rect})) { node._lastUiPosition = ui.position; + this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft); delete node._skipDown; if (resizing && node.subGrid) { (node.subGrid as GridStack).onParentResize(); } this._updateContainerHeight(); diff --git a/src/gridstack-engine.ts b/src/gridstack-engine.ts index da5cce245..3672dc0c1 100644 --- a/src/gridstack-engine.ts +++ b/src/gridstack-engine.ts @@ -7,7 +7,7 @@ */ import { Utils, obsolete } from './utils'; -import { GridStackNode, ColumnOptions } from './types'; +import { GridStackNode, ColumnOptions, GridStackPosition, GridStackMoveOpts } from './types'; export type onChangeCB = (nodes: GridStackNode[], removeDOM?: boolean) => void; @@ -74,7 +74,7 @@ export class GridStackEngine { * return true if we moved. */ private _fixCollisions(node: GridStackNode, nn = node, collide?: GridStackNode, disableSwap?: boolean): boolean { // this._sortNodes(-1); collision doesn't care about sorting - collide = collide || this.collision(node, nn); + collide = collide || this.collide(node, nn); if (!collide) return false; // swap check: if we're actively moving in gravity mode, see if we collide with an object the same size @@ -83,20 +83,21 @@ export class GridStackEngine { } let didMove = false; - while (collide = collide || this.collision(node, nn)) { // we could start colliding more than 1 item... so repeat for each + let opts: GridStackMoveOpts = {disableSwap: true, disableCoverage: true, pack: false, sanitize: false}; + while (collide = collide || this.collide(node, nn)) { // we could start colliding more than 1 item... so repeat for each let moved: boolean; // if colliding with locked item, OR moving down to a different sized item (not handle with swap) that could take our place, move ourself past instead if (collide.locked || (node._moving && !node._skipDown && nn.y > node.y && - !this.float && !this.collision(collide, {...collide, y: node.y}, node)) && + !this.float && !this.collide(collide, {...collide, y: node.y}, node)) && Utils.isIntercepted(collide, {x: node.x-0.5, y: node.y-0.5, w: node.w+1, h: node.h+1})) { node._skipDown = (node._skipDown || nn.y > node.y); - moved = this.moveNode(node, nn.x, collide.y + collide.h, nn.w, nn.h, false, false, true); // pack & sanitize = false, disableSwap = true + moved = this.moveNode(node, {...nn, y: collide.y + collide.h, ...opts}); nn = moved ? {x: node.x, y: node.y, w: node.w, h: node.h} : nn; didMove = didMove || moved; } else { // move collide down *after* us - moved = this.moveNode(collide, collide.x, nn.y + nn.h, collide.w, collide.h, false, false, true); // pack & sanitize = false, disableSwap = true + moved = this.moveNode(collide, {...collide, y: nn.y + nn.h, ...opts}); } if (!moved) { return didMove; } // break inf loop if we couldn't move after all (ex: maxRow, fixed) collide = undefined; @@ -104,14 +105,72 @@ export class GridStackEngine { return didMove; } - /** return the first node that intercept the given node. Optionally a different area can be used, as well as a second node to skip */ - public collision(skip: GridStackNode, area = skip, skip2?: GridStackNode): GridStackNode { - return this.nodes.find(n => n !== skip && n !== skip2 && Utils.isIntercepted(n, area)) + /** return the nodes that intercept the given node. Optionally a different area can be used, as well as a second node to skip */ + public collide(skip: GridStackNode, area = skip, skip2?: GridStackNode): GridStackNode { + return this.nodes.find(n => n !== skip && n !== skip2 && Utils.isIntercepted(n, area)); } + public collideAll(skip: GridStackNode, area = skip): GridStackNode[] { + return this.nodes.filter(n => n !== skip && Utils.isIntercepted(n, area)); + } + + /** does a pixel coverage collision, returning the node that has the most coverage that is >50% mid line */ + public collideCoverage(node: GridStackNode, o: GridStackMoveOpts, collides: GridStackNode[]): GridStackNode { + if (!o.rect || !node._rect) return; + let r0 = node._rect; // where started + let r = {...o.rect}; // where we are + + // update dragged rect to show where it's coming from (above or below, etc...) + if (r.y > r0.y) { + r.h += r.y - r0.y; + r.y = r0.y; + } else { + r.h += r0.y - r.y; + } + if (r.x > r0.x) { + r.w += r.x - r0.x; + r.x = r0.x; + } else { + r.w += r0.x - r.x; + } - /** return any intercepted node with the given area, skipping the passed in node (usually self) */ - public collide(node: GridStackNode, area: GridStackNode = node): GridStackNode { - return this.nodes.find(n => n !== node && Utils.isIntercepted(n, area)); + let collide: GridStackNode; + collides.forEach(n => { + if (n.locked || !n._rect) return; + let r2 = n._rect; // overlapping target + let yOver = Number.MAX_VALUE, xOver = Number.MAX_VALUE, overMax = 0.5; // need >50% + // depending on which side we started from, compute the overlap % of coverage + // (ex: from above/below we only compute the max horizontal line coverage) + if (r0.y < r2.y) { // from above + yOver = ((r.y + r.h) - r2.y) / r2.h; + } else if (r0.y+r0.h > r2.y+r2.h) { // from below + yOver = ((r2.y + r2.h) - r.y) / r2.h; + } + if (r0.x < r2.x) { // from the left + xOver = ((r.x + r.w) - r2.x) / r2.w; + } else if (r0.x+r0.w > r2.x+r2.w) { // from the right + xOver = ((r2.x + r2.w) - r.x) / r2.w; + } + let over = Math.min(xOver, yOver); + if (over > overMax) { + overMax = over; + collide = n; + } + }); + return collide; + } + + /** called to cache the nodes pixel rectangles used for collision detection during drag */ + public cacheRects(w: number, h: number, top: number, right: number, bottom: number, left: number): GridStackEngine + { + this.nodes.forEach(n => + n._rect = { + y: n.y * h + top, + x: n.x * w + left, + w: n.w * w - left - right, + h: n.h * h - top - bottom + } + ); + return this; } /** called to possibly swap between 2 nodes (same size, not locked, touching), returning true if successful */ @@ -130,8 +189,8 @@ export class GridStackEngine { return _doSwap(); /* different X will be weird (expect vertical swap) and different height overlap, so too complex. user regular layout instead // else check if swapping would not collide with anything else (requiring a re-layout) - if (!this.collision(a, {x: a.x, y: a.y, w: b.w, h: b.h}, b) && - !this.collision(a, {x: b.x, y: b.y, w: a.w, h: a.h}, b)) + if (!this.collide(a, {x: a.x, y: a.y, w: b.w, h: b.h}, b) && + !this.collide(a, {x: b.x, y: b.y, w: a.w, h: a.h}, b)) return _doSwap(); */ return false; } @@ -185,7 +244,7 @@ export class GridStackEngine { if (n._updating || n._packY === undefined || n.y === n._packY) return; let newY = n.y; while (newY >= n._packY) { - let collide = this.collision(n, {x: n.x, y: newY, w: n.w, h: n.h}) + let collide = this.collide(n, {x: n.x, y: newY, w: n.w, h: n.h}); if (!collide) { n._dirty = true; n.y = newY; @@ -198,7 +257,7 @@ export class GridStackEngine { if (n.locked) return; while (n.y > 0) { let newY = i === 0 ? 0 : n.y - 1; - let canBeMoved = i === 0 || !this.collision(n, {x: n.x, y: newY, w: n.w, h: n.h}); + let canBeMoved = i === 0 || !this.collide(n, {x: n.x, y: newY, w: n.w, h: n.h}); if (!canBeMoved) break; // Note: must be dirty (from last position) for GridStack::OnChange CB to update positions // and move items back. The user 'change' CB should detect changes from the original @@ -375,13 +434,14 @@ export class GridStackEngine { /** checks if item can be moved (layout constrain) vs moveNode(), returning true if was able to move. * In more complicated cases (locked items, or maxRow) it will attempt at moving the item and fixing * others in a clone first, then apply those changes if within specs. */ - public moveNodeCheck(node: GridStackNode, x: number, y: number, w = node.w, h = node.h): boolean { + public moveNodeCheck(node: GridStackNode, o: GridStackMoveOpts): boolean { if (node.locked) return false; - if (!this.changedPos(node, x, y, w, h)) return false; + if (!this.changedPosConstrain(node, o)) return false; + o.pack = true; // no sanitize // simpler case: move item directly... if (!this.maxRow && !this.nodes.some(n => n.locked)) { - return this.moveNode(node, x, y, w, h, true, false); // pack=true, sanitize=false + return this.moveNode(node, o); } // complex case: create a clone with NO maxRow (will check for out of bounds at the end) @@ -399,13 +459,13 @@ export class GridStackEngine { }); if (!clonedNode) return false; - let canMove = clone.moveNode(clonedNode, x, y, w, h, true, false); // pack=true, sanitize=false + let canMove = clone.moveNode(clonedNode, o); // if maxRow make sure we are still valid size if (this.maxRow && canMove) { canMove = (clone.getRow() <= this.maxRow); // turns out we can't grow, then see if we can swap instead (ex: full grid) if (!canMove) { - let collide = this.collision(node, {x, y, w, h}); + let collide = this.collide(node, o); if (collide && this.swap(node, collide)) { this._notify(); return true; @@ -419,10 +479,7 @@ export class GridStackEngine { clone.nodes.filter(n => n._dirty).forEach(c => { let n = this.nodes.find(a => a._id === c._id); if (!n) return; - n.x = c.x; - n.y = c.y; - n.w = c.w; - n.h = c.h; + Utils.copyPos(n, c); n._dirty = true; }); this._notify(); @@ -468,49 +525,60 @@ export class GridStackEngine { } /** true if x,y or w,h are different after clamping to min/max */ - public changedPos(node: GridStackNode, x: number, y: number, w = node.w, h = node.h): boolean { - if (node.x !== x || node.y !== y) return true; + public changedPosConstrain(node: GridStackNode, p: GridStackPosition): boolean { + if (node.x !== p.x || node.y !== p.y) return true; // check constrained w,h - if (node.maxW) { w = Math.min(w, node.maxW); } - if (node.maxH) { h = Math.min(h, node.maxH); } - if (node.minW) { w = Math.max(w, node.minW); } - if (node.minH) { h = Math.max(h, node.minH); } - return (node.w !== w || node.h !== h); + p.w = p.w || node.w; + p.h = p.h || node.h; + if (node.maxW) { p.w = Math.min(p.w, node.maxW); } + if (node.maxH) { p.h = Math.min(p.h, node.maxH); } + if (node.minW) { p.w = Math.max(p.w, node.minW); } + if (node.minH) { p.h = Math.max(p.h, node.minH); } + return (node.w !== p.w || node.h !== p.h); } /** return true if the passed in node was actually moved (checks for no-op and locked) */ - public moveNode(node: GridStackNode, x: number, y: number, w = node.w, h = node.h, pack = true, sanitize = true, disableSwap = false): boolean { + public moveNode(node: GridStackNode, o: GridStackMoveOpts): boolean { if (!node || node.locked) return false; + if (o.pack === undefined) o.pack = true; + if (o.sanitize === undefined) o.sanitize = true; let nn: GridStackNode; - if (sanitize) { - if (typeof x !== 'number') { x = node.x; } - if (typeof y !== 'number') { y = node.y; } + if (o.sanitize) { + if (typeof o.x !== 'number') { o.x = node.x; } + if (typeof o.y !== 'number') { o.y = node.y; } + if (typeof o.w !== 'number') { o.w = node.w; } + if (typeof o.h !== 'number') { o.h = node.h; } // constrain the passed in values and check if we're still changing our node - let resizing = (node.w !== w || node.h !== h); - nn = { x, y, w, h, maxW: node.maxW, maxH: node.maxH, minW: node.minW, minH: node.minH}; + let resizing = (node.w !== o.w || node.h !== o.h); + nn = {maxW: node.maxW, maxH: node.maxH, minW: node.minW, minH: node.minH}; + Utils.copyPos(nn, o); nn = this.prepareNode(nn, resizing); + Utils.copyPos(o, nn); } - nn = nn || {x, y, w, h} + nn = nn || o; if (Utils.samePos(node, nn)) return false; - let prevPos: GridStackNode = {...node}; + let prevPos: GridStackPosition = Utils.copyPos({}, node); // check if we will need to fix collision at our new location - let collide = this.collision(node, nn); + let collides = this.collideAll(node, nn); let moved = false; - if (collide) { - moved = this._fixCollisions(node, nn, collide, disableSwap); + if (collides.length) { + // now check to make sure we actually collided over 50% surface area while dragging + let collide = node._moving && !o.disableCoverage ? this.collideCoverage(node, o, collides) : collides[0]; + if (collide) { + moved = this._fixCollisions(node, nn, collide, o.disableSwap); + } else { + moved = true; // prevent actual moving since we didn't cover >50% for a move + } } // now move (to the original ask vs the collision version which might differ) and repack things if (!moved) { node._dirty = true; - node.x = nn.x; - node.y = nn.y; - node.w = nn.w; - node.h = nn.h; + Utils.copyPos(node, nn); } - if (pack) { + if (o.pack) { this._packNodes() ._notify(); } diff --git a/src/gridstack.ts b/src/gridstack.ts index 1390793a4..f030af160 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -1014,7 +1014,7 @@ export class GridStack { if (m) { this.engine.cleanNodes() .beginUpdate(n) - .moveNode(n, m.x, m.y, m.w, m.h); + .moveNode(n, m); this._updateContainerHeight(); this._triggerChangeEvent(); this.engine.endUpdate(); diff --git a/src/types.ts b/src/types.ts index 7f77c8f90..81d404b82 100644 --- a/src/types.ts +++ b/src/types.ts @@ -194,6 +194,27 @@ export interface GridStackOptions { _styleSheetClass?: string; } +/** options used during GridStackEngine.moveNode() */ +export interface GridStackMoveOpts extends GridStackPosition { + /** do we pack (default true) */ + pack?: boolean; + /** do we verify for bad or > max/min values (default true) */ + sanitize?: boolean; + /** true if we just want to push down (skip swapping) - default false*/ + disableSwap?: boolean; + /** true if we just want simple row/column overlap vs checking >50% coverage - default false*/ + disableCoverage?: boolean; + /* vars to calculate other cells coordinates */ + cellWidth?: number; + cellHeight?: number; + marginTop?: number; + marginBottom?: number; + marginLeft?: number; + marginRight?: number; + /** position in pixels of the currently dragged items (for overlap check) */ + rect?: GridStackPosition; +} + export interface GridStackPosition { /** widget position x (default?: 0) */ x?: number; @@ -322,6 +343,8 @@ export interface GridStackNode extends GridStackWidget { _orig?: GridStackPosition; /** @internal set on the item being dragged/resized to save initial values TODO: vs _orig ? */ _beforeDrag?: GridStackPosition; + /** @internal position in pixels used during collision check */ + _rect?: GridStackPosition; /** @internal top/left pixel location before a drag so we can detect direction of move from last position*/ _lastUiPosition?: Position; /** @internal set on the item being dragged/resized remember the last positions we've tried (but failed) so we don't try again during drag/resize */ diff --git a/src/utils.ts b/src/utils.ts index fd77eda0e..7eb9875b9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,7 +6,7 @@ * gridstack.js may be freely distributed under the MIT license. */ -import { GridStackElement, GridStackWidget, GridStackNode, GridStackOptions, numberOrString, GridStackPosition } from './types'; +import { GridStackElement, GridStackNode, GridStackOptions, numberOrString, GridStackPosition } from './types'; export interface HeightData { h: number; @@ -95,7 +95,7 @@ export class Utils { } /** returns true if a and b overlap */ - static isIntercepted(a: GridStackWidget, b: GridStackWidget): boolean { + static isIntercepted(a: GridStackPosition, b: GridStackPosition): boolean { return !(a.y >= b.y + b.h || a.y + a.h <= b.y || a.x + a.w <= b.x || a.x >= b.x + b.w); } @@ -218,6 +218,15 @@ export class Utils { return true; } + /* copies over b size & position */ + static copyPos(a: GridStackPosition, b: GridStackPosition): GridStackPosition { + a.x = b.x; + a.y = b.y; + a.w = b.w; + a.h = b.h; + return a; + } + /* true if a and b has same size & position */ static samePos(a: GridStackPosition, b: GridStackPosition): boolean { return a && b && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;