diff --git a/README.md b/README.md
index 631eb4555..105116b1d 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@ Join us on Slack: https://gridstackjs.troolee.com
- [Migrating to v1](#migrating-to-v1)
- [Migrating to v2](#migrating-to-v2)
- [Migrating to v3](#migrating-to-v3)
+ - [Migrating to v4](#migrating-to-v4)
- [jQuery Application](#jquery-application)
- [Changes](#changes)
- [The Team](#the-team)
@@ -390,6 +391,30 @@ Breaking changes:
5. `GridStackWidget` used in most API `width|height|minWidth|minHeight|maxWidth|maxHeight` are now shorter `w|h|minW|minH|maxW|maxH` as well
+## Migrating to v4
+
+make sure to read v3 migration first!
+
+v4 is a complete re-write of the collision and drag in/out heuristics to fix some very long standing request & bugs. It also greatly improved usability. Read the release notes for more detail.
+
+**Unlikely** Breaking Changes (internal usage):
+
+1. `removeTimeout` was removed (feedback over trash will be immediate - actual removal still on mouse up)
+
+2. the following `GridStackEngine` methods changed (used internally, doesn't affect `GridStack` public API)
+
+```js
+// moved to 3 methods with new option params to support new code and pixel coverage check
+`collision()` -> `collide(), collideAll(), collideCoverage()`
+`moveNodeCheck(node, x, y, w, h)` -> `moveNodeCheck(node, opt: GridStackMoveOpts)`
+`isNodeChangedPosition(node, x, y, w, h)` -> `changedPosConstrain(node, opt: GridStackMoveOpts)`
+`moveNode(node, x, y, w, h, noPack)` -> `moveNode(node, opt: GridStackMoveOpts)`
+```
+
+3. removed old obsolete (v0.6-v1 methods/attrs) `getGridHeight()`, `verticalMargin`, `data-gs-current-height`,
+`locked()`, `maxWidth()`, `minWidth()`, `maxHeight()`, `minHeight()`, `move()`, `resize()`
+
+
# jQuery Application
We now have a native HTML5 drag'n'drop through the plugin system (default), but the jquery-ui version can be used instead. It will bundle `jquery` (3.5.1) + `jquery-ui` (1.12.1 minimal drag|drop|resize) + `jquery-ui-touch-punch` (1.0.8 for mobile support) in `gridstack-jq.js`. IFF you want to use gridstack-jq instead and your app needs to bring your own JQ version (only possible in 1.x), you should **instead** use v1.x and include `gridstack-poly.min.js` (optional IE support) + `gridstack.min.js` + `gridstack.jQueryUI.min.js` after you import your JQ libs. But note that there are issue with jQuery and ES6 import (see [1306](https://github.com/gridstack/gridstack.js/issues/1306)).
diff --git a/demo/two-jq.html b/demo/two-jq.html
index 986e708c0..69202ac49 100644
--- a/demo/two-jq.html
+++ b/demo/two-jq.html
@@ -92,7 +92,6 @@
Two grids demo (Jquery version)
dragIn: '.sidebar .grid-stack-item', // add draggable to class
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper: 'clone' }, // clone
removable: '.trash', // drag-out delete class
- removeTimeout: 100,
acceptWidgets: function(el) { return true; } // function example, else can be simple: true | false | '.someClass' value
};
grids = GridStack.initAll(options);
diff --git a/demo/two.html b/demo/two.html
index 732556a3a..8671f9095 100644
--- a/demo/two.html
+++ b/demo/two.html
@@ -90,7 +90,6 @@ Two grids demo
// dragIn: '.sidebar .grid-stack-item', // add draggable to class
// dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper: 'clone' }, // clone
removable: '.trash', // drag-out delete class
- removeTimeout: 100,
acceptWidgets: function(el) { return true; } // function example, else can be simple: true | false | '.someClass' value
};
let grids = GridStack.initAll(options);
diff --git a/demo/web2.html b/demo/web2.html
index 9534133a3..d92f6e1c7 100644
--- a/demo/web2.html
+++ b/demo/web2.html
@@ -62,7 +62,6 @@ Advanced Demo
dragIn: '.newWidget', // class that can be dragged from outside
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper: 'clone' },
removable: '#trash', // drag-out delete class
- removeTimeout: 100,
});
let items = [
diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index ebeca32fd..426b205bd 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -50,13 +50,19 @@ 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.
-- 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 [#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 - fixing 6 years old most requested request**
+1. 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.
+2. Dragging up and down now behave the same (used to require push WAY down past to swap/append). Also much more efficient collision code.
+3. handle mid point of dragged over items (>50%) rather than just a new row/column and check for the most covered item when multiple collide.
+
+- fix [#393](https://github.com/gridstack/gridstack.js/issues/393) [#1612](https://github.com/gridstack/gridstack.js/issues/1612) [#1578](https://github.com/gridstack/gridstack.js/issues/1578) re-write of the **drag in/out code - fixing 5 years old bug**
+1. we now remove item when cursor leaves (`acceptWidgets` case using `dropout` event) or shape is outside (re-using same method) and re-insert on cursor enter (since we only get `dropover` event). Should **not be possible to have 2 placeholders** which confuses the grids.
+2. major re-write and cleanup of the drag in/out. Vars have been renamed and fully documented as I couldn't understand the legacy buggy code.
+3. removed any over trash delay feedback as I don't see the point and could introduce race conditions.
- fix [1617](https://github.com/gridstack/gridstack.js/issues/1617) FireFox DOM order issue. Thanks [@marcel-necker](https://github.com/marcel-necker)
- fix changing column # `column(n)` now resizes `cellHeight:'auto'` to keep square
-- add `drag | resize` events while dragging [1616](https://github.com/gridstack/gridstack.js/pull/1616). Thanks [@MrCorba](https://github.com/MrCorba)
-- add `GridStack.setupDragIn()` so user can update external draggable after the grid has been created [1637](https://github.com/gridstack/gridstack.js/issues/1637)
+- add [1616](https://github.com/gridstack/gridstack.js/pull/1616) `drag | resize` events while dragging. Thanks [@MrCorba](https://github.com/MrCorba)
+- add [1637](https://github.com/gridstack/gridstack.js/issues/1637) `GridStack.setupDragIn()` so user can update external draggable after the grid has been created
## 3.3.0 (2021-2-2)
diff --git a/spec/e2e/html/1570_drag_bottom_max_row.html b/spec/e2e/html/1570_drag_bottom_max_row.html
index e4be4c1c8..7e1e63e1c 100644
--- a/spec/e2e/html/1570_drag_bottom_max_row.html
+++ b/spec/e2e/html/1570_drag_bottom_max_row.html
@@ -91,7 +91,6 @@
maxRow: 3, // change this to show issue
acceptWidgets: true,
removable: true,
- removeTimeout: 0,
float: true
}
diff --git a/spec/e2e/html/1571_drop_onto_full.html b/spec/e2e/html/1571_drop_onto_full.html
index 537c6cf0a..f0f417a60 100644
--- a/spec/e2e/html/1571_drop_onto_full.html
+++ b/spec/e2e/html/1571_drop_onto_full.html
@@ -89,7 +89,6 @@ drop onto full
dragIn: '.sidebar .grid-stack-item', // class that can be dragged from outside
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper: 'clone' }, // clone
removable: '.trash', // drag-out delete class
- removeTimeout: 100,
acceptWidgets: function(el) { return true; } // function example, else can be simple: true | false | '.someClass' value
};
let grids = GridStack.initAll(options);
diff --git a/src/gridstack-dd.ts b/src/gridstack-dd.ts
index 987f1af14..ae9d2f537 100644
--- a/src/gridstack-dd.ts
+++ b/src/gridstack-dd.ts
@@ -6,7 +6,6 @@
* gridstack.js may be freely distributed under the MIT license.
*/
/* eslint-disable @typescript-eslint/no-unused-vars */
-
import { GridStackDDI } from './gridstack-ddi';
import { GridItemHTMLElement, GridStackNode, GridStackElement, DDUIData, DDDragInOpt, GridStackPosition } from './types';
import { GridStack, MousePosition } from './gridstack';
@@ -93,8 +92,9 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
let cellHeight: number, cellWidth: number;
let onDrag = (event: DragEvent, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
-
let node = el.gridstackNode;
+ if (!node) return;
+
helper = helper || el;
// let left = event.pageX - gridPos.left;
// let top = event.pageY - gridPos.top;
@@ -103,7 +103,7 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
let top = rec.top - gridPos.top;
let ui: DDUIData = {position: {top, left}};
- if (!node._added) {
+ if (node._temporaryRemoved) {
node.x = Math.max(0, Math.round(left / cellWidth));
node.y = Math.max(0, Math.round(top / cellHeight));
delete node.autoPosition;
@@ -119,13 +119,12 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
}
// re-use the existing node dragging method
- delete node._updating; // make sure beginUpdate() is called cleanly on this
this._onStartMoving(event, ui, node, cellWidth, cellHeight);
} else {
// re-use the existing node dragging that does so much of the collision detection
this._dragOrResize(event, ui, node, cellWidth, cellHeight);
}
- };
+ }
GridStackDD.get()
.droppable(this.el, {
@@ -153,12 +152,18 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
* entering our grid area
*/
.on(this.el, 'dropover', (event: Event, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
-
- // ignore drop enter on ourself, and prevent parent from receiving event
let node = el.gridstackNode;
- if (node && node.grid === this) {
- delete node._added; // reset this to track placeholder again in case we were over other grid #1484 (dropout doesn't always clear)
- return false;
+ // ignore drop enter on ourself (unless we temporarily removed) which happens on a simple drag of our item
+ if (node && node.grid === this && !node._temporaryRemoved) {
+ // delete node._added; // reset this to track placeholder again in case we were over other grid #1484 (dropout doesn't always clear)
+ return false; // prevent parent from receiving msg (which may be a grid as well)
+ }
+
+ // fix #1578 when dragging fast, we may not get a leave on the previous grid so force one now
+ if (node && node.grid && node.grid !== this && !node._temporaryRemoved) {
+ // TEST console.log('dropover without leave');
+ let otherGrid = node.grid;
+ otherGrid._leave(el.gridstackNode, el, helper, true); // MATCH line 222
}
// get grid screen coordinates and cell dimensions
@@ -171,50 +176,51 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
if (!node) {
node = this._readAttr(el);
}
-
- // if the item came from another grid, let it know it was added here to removed duplicate shadow #393
- if (node.grid && node.grid !== this) {
- node._added = true;
+ if (!node.grid) {
+ node._isExternal = true;
+ el.gridstackNode = node;
}
-
- // if not calculate the grid size based on element outer size
+
+ // calculate the grid size based on element outer size
helper = helper || el;
let w = node.w || Math.round(helper.offsetWidth / cellWidth) || 1;
let h = node.h || Math.round(helper.offsetHeight / cellHeight) || 1;
- // COPY the node original values (min/max/id/etc...) but override width/height/other flags which are this grid specific
- let newNode = this.engine.prepareNode({...node, ...{w, h, _added: false, _temporary: true, _isOutOfGrid: true}});
- el.gridstackNode = newNode;
- el._gridstackNodeOrig = node;
+ // if the item came from another grid, make a copy and save the original info in case we go back there
+ if (node.grid && node.grid !== this) {
+ // copy the node original values (min/max/id/etc...) but override width/height/other flags which are this grid specific
+ // TEST console.log('dropover cloning node');
+ if (!el._gridstackNodeOrig) el._gridstackNodeOrig = node; // shouldn't have multiple nested!
+ el.gridstackNode = node = {...node, w, h, grid: this};
+ this.engine.cleanupNode(node)
+ .nodeBoundFix(node);
+ // restore some internal fields we need after clearing them all
+ node._initDD =
+ node._isExternal = // DOM needs to be re-parented on a drop
+ node._temporaryRemoved = true;
+ } else {
+ node.w = w; node.h = h;
+ node._temporaryRemoved = true; // so we can insert it
+ }
+
+ // we're entering this grid (even if we left another)
+ delete node._isCursorOutside;
- onDrag(event as DragEvent, el, helper); // make sure this is called at least once when going fast #1578
GridStackDD.get().on(el, 'drag', onDrag);
- return false; // prevent parent from receiving msg (which may be grid as well)
+ // make sure this is called at least once when going fast #1578
+ onDrag(event as DragEvent, el, helper);
+ return false; // prevent parent from receiving msg (which may be a grid as well)
})
/**
* Leaving our grid area...
*/
- .on(this.el, 'dropout', (event, el: GridItemHTMLElement) => {
+ .on(this.el, 'dropout', (event, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
let node = el.gridstackNode;
- if (!node) return;
-
- // clear any added flag now that we are leaving #1484
- delete node._added;
-
- // jquery-ui bug. Must verify widget is being dropped out
- // check node variable that gets set when widget is out of grid
- if (!node._isOutOfGrid) {
- return;
- }
-
- GridStackDD.get().off(el, 'drag');
- node.el = null;
- this.engine.removeNode(node);
- if (this.placeholder.parentNode === this.el) {
- this.placeholder.remove();
+ // fix #1578 when dragging fast, we might get leave after other grid gets enter (which calls us to clean)
+ // so skip this one if we're not the active grid really..
+ if (!node.grid || node.grid === this) {
+ this._leave(node, el, helper, true); // MATCH line 166
}
- this._updateContainerHeight();
- el.gridstackNode = el._gridstackNodeOrig;
return false; // prevent parent from receiving msg (which may be grid as well)
})
/**
@@ -222,19 +228,18 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
*/
.on(this.el, 'drop', (event, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
let node = el.gridstackNode;
- let wasAdded = !!this.placeholder.parentElement; // skip items not actually added to us because of constrains, but do cleanup #1419
- // ignore drop on ourself from ourself - dragend will handle the simple move instead
- if (node && node.grid === this) return false;
+ // ignore drop on ourself from ourself that didn't come from the outside - dragend will handle the simple move instead
+ if (node && node.grid === this && !node._isExternal) return false;
+ let wasAdded = !!this.placeholder.parentElement; // skip items not actually added to us because of constrains, but do cleanup #1419
this.placeholder.remove();
// notify previous grid of removal
+ // TEST console.log('drop delete _gridstackNodeOrig')
let origNode = el._gridstackNodeOrig;
delete el._gridstackNodeOrig;
if (wasAdded && origNode && origNode.grid && origNode.grid !== this) {
let oGrid = origNode.grid;
- oGrid.placeholder.remove();
- origNode.el = el; // was using placeholder, have it point to node we've moved instead
oGrid.engine.removedNodes.push(origNode);
oGrid._triggerRemoveEvent();
}
@@ -243,9 +248,7 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
// use existing placeholder node as it's already in our list with drop location
if (wasAdded) {
- const _id = node._id;
- this.engine.cleanupNode(node); // removes all internal _xyz values (including the _id so add that back)
- node._id = _id;
+ this.engine.cleanupNode(node); // removes all internal _xyz values
node.grid = this;
}
GridStackDD.get().off(el, 'drag');
@@ -265,6 +268,7 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
el.gridstackNode = node;
node.el = el;
+ Utils.copyPos(node, this._readAttr(this.placeholder)); // placeholder values as moving VERY fast can throw things off #1578
Utils.removePositioningStyles(el);
this._writeAttr(el, node);
this.el.appendChild(el);
@@ -279,9 +283,13 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
}
// wait till we return out of the drag callback to set the new drag&resize handler or they may get messed up
- // IFF we are still there (some application will use as placeholder and insert their real widget instead)
window.setTimeout(() => {
- if (node.el && node.el.parentElement) this._prepareDragDropByNode(node);
+ // IFF we are still there (some application will use as placeholder and insert their real widget instead and better call makeWidget())
+ if (node.el && node.el.parentElement) {
+ this._prepareDragDropByNode(node);
+ } else {
+ this.engine.removeNode(node);
+ }
});
return false; // prevent parent from receiving msg (which may be grid as well)
@@ -302,42 +310,20 @@ GridStack.prototype._setupRemoveDrop = function(): GridStack {
.on(trashEl, 'dropover', function(event, el) { // don't use => notation to avoid using 'this' as grid by mistake...
let node = el.gridstackNode;
if (!node || !node.grid) return;
- el.dataset.inTrashZone = 'true';
- node.grid._setupRemovingTimeout(el);
+ node._isAboutToRemove = true;
+ el.classList.add('grid-stack-item-removing');
})
.on(trashEl, 'dropout', function(event, el) { // same
let node = el.gridstackNode;
if (!node || !node.grid) return;
- delete el.dataset.inTrashZone;
- node.grid._clearRemovingTimeout(el);
+ delete node._isAboutToRemove;
+ el.classList.remove('grid-stack-item-removing');
});
}
}
return this;
}
-/** @internal */
-GridStack.prototype._setupRemovingTimeout = function(el: GridItemHTMLElement): GridStack {
- let node = el.gridstackNode;
- if (!node || node._removeTimeout || !this.opts.removable) return this;
- node._removeTimeout = window.setTimeout(() => {
- el.classList.add('grid-stack-item-removing');
- node._isAboutToRemove = true;
- }, this.opts.removeTimeout);
- return this;
-}
-
-/** @internal */
-GridStack.prototype._clearRemovingTimeout = function(el: GridItemHTMLElement): GridStack {
- let node = el.gridstackNode;
- if (!node || !node._removeTimeout) return this;
- clearTimeout(node._removeTimeout);
- delete node._removeTimeout;
- el.classList.remove('grid-stack-item-removing');
- delete node._isAboutToRemove;
- return this;
-}
-
/**
* call to setup dragging in from the outside (say toolbar), by specifying the class selection and options.
* Called during GridStack.init() as options, but can also be called directly (last param are cached) in case the toolbar
@@ -398,7 +384,7 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
let cellHeight: number;
/** called when item starts moving/resizing */
- let onStartMoving = (event: Event, ui: DDUIData): void => {
+ let onStartMoving = (event: Event, ui: DDUIData) => {
// trigger any 'dragstart' / 'resizestart' manually
if (this._gsEventHandler[event.type]) {
this._gsEventHandler[event.type](event, event.target);
@@ -410,15 +396,13 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
}
/** called when item is being dragged/resized */
- let dragOrResize = (event: Event, ui: DDUIData): void => {
+ let dragOrResize = (event: Event, ui: DDUIData) => {
this._dragOrResize(event, ui, node, cellWidth, cellHeight);
}
/** called when the item stops moving/resizing */
- let onEndMoving = (event: Event): void => {
- if (this.placeholder.parentNode === this.el) {
- this.placeholder.remove();
- }
+ let onEndMoving = (event: Event) => {
+ this.placeholder.remove();
delete node._moving;
delete node._lastTried;
@@ -433,24 +417,24 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
if (gridToNotify._gsEventHandler[event.type]) {
gridToNotify._gsEventHandler[event.type](event, target);
}
- gridToNotify.engine.removedNodes.push(node);
GridStackDD.get().remove(el);
- delete el.gridstackNode; // hint we're removing it next and break circular link
+ gridToNotify.engine.removedNodes.push(node);
gridToNotify._triggerRemoveEvent();
- if (el.parentElement) {
- el.remove(); // finally remove it
- }
+ // break circular links and remove DOM
+ delete el.gridstackNode;
+ delete node.el;
+ el.remove();
} else {
- this._clearRemovingTimeout(el);
if (!node._temporaryRemoved) {
+ // move to new placeholder location
Utils.removePositioningStyles(target);
this._writePosAttr(target, node);
} else {
+ // got removed - restore item back to before dragging position
Utils.removePositioningStyles(target);
- this._writePosAttr(target, {...node._beforeDrag, w: node.w, h: node.h});
- node.x = node._beforeDrag.x;
- node.y = node._beforeDrag.y;
- delete node._temporaryRemoved;
+ Utils.copyPos(node, node._beforeDrag);
+ delete node._beforeDrag;
+ this._writePosAttr(target, node);
this.engine.addNode(node);
}
if (this._gsEventHandler[event.type]) {
@@ -462,13 +446,6 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
this._triggerChangeEvent();
this.engine.endUpdate();
-
- /* doing it on live resize instead
- // if we re-sized a nested grid item, let the children resize as well
- if (event.type === 'resizestop') {
- if (target.gridstackNode.subGrid) {(target.gridstackNode.subGrid as GridStack).onParentResize()}
- }
- */
}
GridStackDD.get()
@@ -495,23 +472,24 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
}
/** @internal called when item is starting a drag/resize */
-GridStack.prototype._onStartMoving = function(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
+GridStack.prototype._onStartMoving = function(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number) {
this.engine.cleanNodes()
.beginUpdate(node);
this._writePosAttr(this.placeholder, node)
- this.el.append(this.placeholder);
+ this.el.appendChild(this.placeholder);
+ // TEST console.log('_onStartMoving placeholder')
node.el = this.placeholder;
node._lastUiPosition = ui.position;
node._prevYPix = ui.position.top;
node._moving = (event.type === 'dragstart');
delete node._lastTried;
+ delete node._isCursorOutside;
- if (event.type === 'dropover' && !node._added) {
- node._added = true;
- this.engine.addNode(node);
- this._writePosAttr(this.placeholder, node);
+ if (event.type === 'dropover' && node._temporaryRemoved) {
+ // TEST console.log('engine.addNode x=' + node.x);
+ this.engine.addNode(node); // will add, constrain, update attr and clear _temporaryRemoved
node._moving = true; // lastly mark as moving object
}
@@ -527,8 +505,39 @@ GridStack.prototype._onStartMoving = function(event: Event, ui: DDUIData, node:
}
}
+/** @internal called when item leaving our area by either cursor dropout event
+ * or shape is outside our boundaries. remove it from us, and mark temporary if this was
+ * our item to start with else restore prev node values from prev grid it came from.
+ **/
+GridStack.prototype._leave = function(node: GridStackNode, el: GridItemHTMLElement, helper?: GridItemHTMLElement, dropoutEvent = false) {
+ if (!node) return;
+
+ if (dropoutEvent) {
+ node._isCursorOutside = true;
+ GridStackDD.get().off(el, 'drag'); // no need to track while being outside
+ }
+
+ // this gets called when cursor leaves and shape is outside, so only do this once
+ if (node._temporaryRemoved) return;
+ node._temporaryRemoved = true;
+
+ this.engine.removeNode(node); // remove placeholder as well
+ node.el = node._isExternal && helper ? helper : el; // point back to real item being dragged
+
+ // finally if item originally came from another grid, but left us, restore things back to prev info
+ if (el._gridstackNodeOrig) {
+ // TEST console.log('leave delete _gridstackNodeOrig')
+ el.gridstackNode = el._gridstackNodeOrig;
+ delete el._gridstackNodeOrig;
+ } else if (node._isExternal) {
+ // item came from outside (like a toolbar) so nuke any node info
+ delete node.el;
+ delete el.gridstackNode;
+ }
+}
+
/** @internal called when item is being dragged/resized */
-GridStack.prototype._dragOrResize = function(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
+GridStack.prototype._dragOrResize = function(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number) {
let el = node.el || event.target as GridItemHTMLElement;
// calculate the place where we're landing by offsetting margin so actual edge crosses mid point
let left = ui.position.left + (ui.position.left > node._lastUiPosition.left ? -this.opts.marginRight : this.opts.marginLeft);
@@ -537,42 +546,22 @@ GridStack.prototype._dragOrResize = function(event: Event, ui: DDUIData, node: G
let y = Math.round(top / cellHeight);
let w = node.w;
let h = node.h;
- if (node._isOutOfGrid) {
- // items coming from outside are handled by 'dragout' event instead, so make coordinates fit
- let fix = this.engine.nodeBoundFix({x, y, w, h});
- x = fix.x; y = fix.y; w = fix.w; h = fix.h;
- }
let resizing: boolean;
if (event.type === 'drag') {
+ if (node._isCursorOutside) return; // handled by dropover
let distance = ui.position.top - node._prevYPix;
node._prevYPix = ui.position.top;
Utils.updateScrollPosition(el, ui.position, distance);
- // if inTrash, outside of the bounds or added to another grid (#393) temporarily remove it from us
- if (el.dataset.inTrashZone || (node._added && !node._isOutOfGrid) || this.engine.isOutside(x, y, node)) {
- if (node._temporaryRemoved) return;
- if (this.opts.removable === true) {
- this._setupRemovingTimeout(el);
- }
-
- x = node._beforeDrag.x;
- y = node._beforeDrag.y;
-
- if (this.placeholder.parentNode === this.el) {
- this.placeholder.remove();
- }
- this.engine.removeNode(node);
- this._updateContainerHeight();
-
- node._temporaryRemoved = true;
- delete node._added; // no need for this now
+ // if inTrash or outside of the bounds (but not external which is handled by 'dropout' event), temporarily remove it from us
+ if (node._isAboutToRemove || (!node._isExternal && this.engine.isOutside(x, y, node))) {
+ this._leave(node, event.target);
} else {
- if (node._removeTimeout) this._clearRemovingTimeout(el);
-
if (node._temporaryRemoved) {
node.el = this.placeholder;
this.engine.addNode(node);
this.el.appendChild(this.placeholder);
+ // TEST console.log('drag placeholder');
delete node._temporaryRemoved;
}
}
diff --git a/src/gridstack-engine.ts b/src/gridstack-engine.ts
index 07b12161b..fe0bf9ea7 100644
--- a/src/gridstack-engine.ts
+++ b/src/gridstack-engine.ts
@@ -42,7 +42,7 @@ export class GridStackEngine {
private _layouts?: Layout[][]; // maps column # to array of values nodes
/** @internal */
private _ignoreLayoutsNodeChange: boolean;
- /** @internal */
+ /** @internal unique global internal _id counter NOT starting at 0 */
private static _idSeq = 1;
public constructor(opts: GridStackEngineOptions = {}) {
@@ -73,7 +73,8 @@ export class GridStackEngine {
/** @internal fix collision on given 'node', going to given new location 'nn', with optional 'collide' node already found.
* return true if we moved. */
private _fixCollisions(node: GridStackNode, nn = node, collide?: GridStackNode, opt: GridStackMoveOpts = {}): boolean {
- // this._sortNodes(-1); collision doesn't care about sorting
+ this._sortNodes(-1); // TODO: collision should not care about sorting but it does (behaves differently second time trying to insert same spot)
+
collide = collide || this.collide(node, nn);
if (!collide) return false;
@@ -261,24 +262,26 @@ export class GridStackEngine {
return this;
}
- /** @internal called to top gravity pack the items back */
+ /** @internal called to top gravity pack the items back OR revert back to original Y positions when floating */
private _packNodes(): GridStackEngine {
this._sortNodes();
if (this.float) {
+ // restore original Y pos
this.nodes.forEach(n => {
if (n._updating || n._packY === undefined || n.y === n._packY) return;
let newY = n.y;
- while (newY >= n._packY) {
+ while (newY > n._packY) {
+ --newY;
let collide = this.collide(n, {x: n.x, y: newY, w: n.w, h: n.h});
if (!collide) {
n._dirty = true;
n.y = newY;
}
- --newY;
}
});
} else {
+ // top gravity pack
this.nodes.forEach((n, i) => {
if (n.locked) return;
while (n.y > 0) {
@@ -416,7 +419,10 @@ export class GridStackEngine {
public addNode(node: GridStackNode, triggerAddEvent = false): GridStackNode {
let dup: GridStackNode;
if (dup = this.nodes.find(n => n._id === node._id)) return dup; // prevent inserting twice! return it instead.
+
node = this.prepareNode(node);
+ delete node._temporaryRemoved;
+ delete node._removeDOM;
if (node.autoPosition) {
this._sortNodes();
@@ -447,20 +453,21 @@ export class GridStackEngine {
}
public removeNode(node: GridStackNode, removeDOM = true, triggerEvent = false): GridStackEngine {
+ if (!this.nodes.find(n => n === node)) return; // not in our list
if (triggerEvent) { // we wait until final drop to manually track removed items (rather than during drag)
this.removedNodes.push(node);
}
- node._id = null; // hint that node is being removed
+ if (removeDOM) node._removeDOM = true; // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
// don't use 'faster' .splice(findIndex(),1) in case node isn't in our list, or in multiple times.
this.nodes = this.nodes.filter(n => n !== node);
- !this.float && this._packNodes();
- return this._notify(node, removeDOM);
+ return this._packNodes()
+ ._notify(node, removeDOM);
}
public removeAll(removeDOM = true): GridStackEngine {
delete this._layouts;
if (this.nodes.length === 0) return this;
- removeDOM && this.nodes.forEach(n => n._id = null); // hint that node is being removed
+ removeDOM && this.nodes.forEach(n => n._removeDOM = true); // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
this.removedNodes = this.nodes;
this.nodes = [];
return this._notify(this.removedNodes, removeDOM);
@@ -536,7 +543,7 @@ export class GridStackEngine {
/** return true if the passed in node (x,y) is being dragged outside of the grid, and not added to bottom */
public isOutside(x: number, y: number, node: GridStackNode): boolean {
- if (node._isOutOfGrid) return false; // dragging out is handled by 'dropout' event instead
+ if (node._isCursorOutside) return false; // dragging out is handled by 'dropout' event instead
// simple outside boundaries
if (x < 0 || x >= this.column || y < 0) return true;
if (this.maxRow) return (y >= this.maxRow);
@@ -813,16 +820,13 @@ export class GridStackEngine {
}
- /** called to remove all internal values */
+ /** called to remove all internal values but the _id */
public cleanupNode(node: GridStackNode): GridStackEngine {
for (let prop in node) {
- if (prop[0] === '_') delete node[prop];
+ if (prop[0] === '_' && prop !== '_id') delete node[prop];
}
return this;
}
-
- /** @internal legacy method renames */
- private getGridHeight = obsolete(this, GridStackEngine.prototype.getRow, 'getGridHeight', 'getRow', 'v1.0.0');
}
/** @internal class to store per column layout bare minimal info (subset of GridStackWidget) */
diff --git a/src/gridstack.ts b/src/gridstack.ts
index 61192fddc..782f671a9 100644
--- a/src/gridstack.ts
+++ b/src/gridstack.ts
@@ -77,7 +77,6 @@ const GridDefaults: GridStackOptions = {
removableOptions: {
accept: '.grid-stack-item'
},
- removeTimeout: 2000,
marginUnit: 'px',
cellHeightUnit: 'px',
disableOneColumnMode: false,
@@ -165,7 +164,7 @@ export class GridStack {
let doc = document.implementation.createHTMLDocument();
doc.body.innerHTML = ``;
let el = doc.body.children[0] as HTMLElement;
- parent.append(el);
+ parent.appendChild(el);
// create grid class and load any children
let grid = GridStack.init(opt, el);
@@ -234,10 +233,6 @@ export class GridStack {
this.el = el; // exposed HTML element to the user
opts = opts || {}; // handles null/undefined/0
- obsoleteOpts(opts, 'verticalMargin', 'margin', 'v2.0');
-
- obsoleteAttr(this.el, 'data-gs-current-height', 'gs-current-row', 'v1.0.0');
-
// if row property exists, replace minRow and maxRow instead
if (opts.row) {
opts.minRow = opts.maxRow = opts.row;
@@ -317,8 +312,9 @@ export class GridStack {
this.engine.nodes.forEach(n => { maxH = Math.max(maxH, n.y + n.h) });
cbNodes.forEach(n => {
let el = n.el;
- if (removeDOM && n._id === null) {
- if (el && el.parentNode) { el.parentNode.removeChild(el) }
+ if (removeDOM && n._removeDOM) { // TODO: do we need to pass 'removeDOM' ?
+ if (el) el.remove();
+ delete n._removeDOM;
} else {
this._writePosAttr(el, n);
}
@@ -1516,30 +1512,12 @@ export class GridStack {
public _setupAcceptWidget(): GridStack { return this }
/** @internal called to setup a trash drop zone if the user specifies it */
public _setupRemoveDrop(): GridStack { return this }
- /** @internal */
- public _setupRemovingTimeout(el: GridItemHTMLElement): GridStack { return this }
- /** @internal */
- public _clearRemovingTimeout(el: GridItemHTMLElement): GridStack { return this }
/** @internal prepares the element for drag&drop **/
public _prepareDragDropByNode(node: GridStackNode): GridStack { return this }
/** @internal handles actual drag/resize start **/
public _onStartMoving(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void { return }
/** @internal handles actual drag/resize **/
public _dragOrResize(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void { return }
-
- // 2.x API that just calls the new and better update() - keep those around for backward compat only...
- /** @internal */
- public locked(els: GridStackElement, locked: boolean): GridStack { return this.update(els, {locked}) }
- /** @internal */
- public maxWidth(els: GridStackElement, maxW: number): GridStack { return this.update(els, {maxW}) }
- /** @internal */
- public minWidth(els: GridStackElement, minW: number): GridStack { return this.update(els, {minW}) }
- /** @internal */
- public maxHeight(els: GridStackElement, maxH: number): GridStack { return this.update(els, {maxH}) }
- /** @internal */
- public minHeight(els: GridStackElement, minH: number): GridStack { return this.update(els, {minH}) }
- /** @internal */
- public move(els: GridStackElement, x?: number, y?: number): GridStack { return this.update(els, {x, y}) }
- /** @internal */
- public resize(els: GridStackElement, w?: number, h?: number): GridStack { return this.update(els, {w, h}) }
+ /** @internal called when a node leaves our area (mouse out or shape outside) **/
+ public _leave(node: GridStackNode, el: GridItemHTMLElement, helper?: GridItemHTMLElement, dropoutEvent = false): void { return }
}
diff --git a/src/h5/dd-utils.ts b/src/h5/dd-utils.ts
index db96b656b..2f79c7c43 100644
--- a/src/h5/dd-utils.ts
+++ b/src/h5/dd-utils.ts
@@ -37,7 +37,7 @@ export class DDUtils {
parentNode = parent as HTMLElement;
}
if (parentNode) {
- parentNode.append(el);
+ parentNode.appendChild(el);
}
}
diff --git a/src/types.ts b/src/types.ts
index b7823912f..d5ab11acc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -165,12 +165,9 @@ export interface GridStackOptions {
*/
removable?: boolean | string;
- /** allows to override UI removable options. (default?: { accept: '.' + opts.itemClass }) */
+ /** allows to override UI removable options. (default?: { accept: '.grid-stack-item' }) */
removableOptions?: DDRemoveOpt;
- /** time in milliseconds before widget is being removed while dragging outside of the grid. (default?: 2000) */
- removeTimeout?: number;
-
/** fix grid number of rows. This is a shortcut of writing `minRow:N, maxRow:N`. (default `0` no constrain) */
row?: number;
@@ -321,18 +318,18 @@ export interface GridStackNode extends GridStackWidget {
el?: GridItemHTMLElement;
/** pointer back to Grid instance */
grid?: GridStack;
- /** @internal */
+ /** @internal internal id used to match when cloning engines or saving column layouts */
_id?: number;
/** @internal */
_dirty?: boolean;
/** @internal */
_updating?: boolean;
- /** @internal */
- _added?: boolean;
- /** @internal */
- _temporary?: boolean;
- /** @internal */
- _isOutOfGrid?: boolean;
+ /** @internal true if the cursor is outside of the grid, as we get dropout/dropover vs shape being outside */
+ _isCursorOutside?: boolean;
+ /** @internal true when over trash/another grid so we don't bother removing drag CSS style that would animate back to old position */
+ _isAboutToRemove?: boolean;
+ /** @internal true if item came from outside of the grid -> actual item need to be moved over */
+ _isExternal?: boolean;
/** @internal moving vs resizing */
_moving?: boolean;
/** @internal true if we jump down past item below (one time jump so we don't have to totally pass it) */
@@ -347,16 +344,14 @@ export interface GridStackNode extends GridStackWidget {
_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 */
_lastTried?: GridStackPosition;
- /** @internal */
+ /** @internal original Y when another item is dragged around a float=true so we can restore back as item is dragged around */
_packY?: number;
- /** @internal */
- _isAboutToRemove?: boolean;
- /** @internal */
- _removeTimeout?: number;
/** @internal last drag Y pixel position used to incrementally update V scroll bar */
_prevYPix?: number;
- /** @internal */
+ /** @internal true if we've remove the item from ourself (dragging out) but might revert it back (release on nothing -> goes back) */
_temporaryRemoved?: boolean;
+ /** @internal true if we should remove DOM element on _notify() rather than clearing _id (old way) */
+ _removeDOM?: boolean;
/** @internal */
_initDD?: boolean;
}