Skip to content

Commit d6b3bbc

Browse files
authored
Merge pull request #1634 from adumesny/develop
Collision: part3 making drop-in rewrite
2 parents 5042030 + ebf9e22 commit d6b3bbc

File tree

6 files changed

+183
-136
lines changed

6 files changed

+183
-136
lines changed

src/gridstack-dd.ts

Lines changed: 162 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { GridStackDDI } from './gridstack-ddi';
1111
import { GridItemHTMLElement, GridStackNode, GridStackElement, DDUIData, DDDragInOpt, GridStackPosition } from './types';
12-
import { GridStack } from './gridstack';
12+
import { GridStack, MousePosition } from './gridstack';
1313
import { Utils } from './utils';
1414

1515
/** Drag&Drop drop options */
@@ -88,35 +88,40 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
8888
return this;
8989
}
9090

91-
let onDrag = (event, el: GridItemHTMLElement) => {
91+
// vars shared across all methods
92+
let gridPos: MousePosition;
93+
let cellHeight: number, cellWidth: number;
94+
95+
let onDrag = (event: DragEvent, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
96+
9297
let node = el.gridstackNode;
93-
let pos = this.getCellFromPixel({left: event.pageX, top: event.pageY}, true);
94-
let x = Math.max(0, pos.x);
95-
let y = Math.max(0, pos.y);
98+
helper = helper || el;
99+
// let left = event.pageX - gridPos.left;
100+
// let top = event.pageY - gridPos.top;
101+
let rec = helper.getBoundingClientRect();
102+
let left = rec.left - gridPos.left;
103+
let top = rec.top - gridPos.top;
104+
let ui: DDUIData = {position: {top, left}};
105+
96106
if (!node._added) {
97-
node.x = x;
98-
node.y = y;
107+
node.x = Math.max(0, Math.round(left / cellWidth));
108+
node.y = Math.max(0, Math.round(top / cellHeight));
99109
delete node.autoPosition;
100110

101111
// don't accept *initial* location if doesn't fit #1419 (locked drop region, or can't grow), but maybe try if it will go somewhere
102112
if (!this.engine.willItFit(node)) {
103113
node.autoPosition = true; // ignore x,y and try for any slot...
104-
if (!this.engine.willItFit(node)) return; // full grid or can't grow
114+
if (!this.engine.willItFit(node)) {
115+
GridStackDD.get().off(el, 'drag'); // stop calling us
116+
return; // full grid or can't grow
117+
}
105118
}
106-
node._added = true;
107-
108-
node.el = el;
109-
this.engine.cleanNodes()
110-
.beginUpdate(node)
111-
.addNode(node);
112119

113-
this._writePosAttr(this.placeholder, node.x, node.y, node.w, node.h);
114-
this.el.appendChild(this.placeholder);
115-
node.el = this.placeholder; // dom we update while dragging...
116-
117-
this._updateContainerHeight();
118-
} else if (this.engine.moveNodeCheck(node, {x, y})) {
119-
this._updateContainerHeight();
120+
// re-use the existing node dragging method
121+
this._onStartMoving(event, ui, node, cellWidth, cellHeight);
122+
} else {
123+
// re-use the existing node dragging that does so much of the collision detection
124+
this._dragOrResize(event, ui, node, cellWidth, cellHeight);
120125
}
121126
};
122127

@@ -142,14 +147,21 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
142147
return canAccept;
143148
}
144149
})
145-
.on(this.el, 'dropover', (event, el: GridItemHTMLElement) => {
150+
.on(this.el, 'dropover', (event: Event, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
151+
146152
// ignore drop enter on ourself, and prevent parent from receiving event
147153
let node = el.gridstackNode;
148154
if (node && node.grid === this) {
149155
delete node._added; // reset this to track placeholder again in case we were over other grid #1484 (dropout doesn't always clear)
150156
return false;
151157
}
152158

159+
// get grid screen coordinates and cell dimensions
160+
let box = this.el.getBoundingClientRect();
161+
gridPos = {top: box.top + document.documentElement.scrollTop, left: box.left};
162+
cellWidth = this.cellWidth();
163+
cellHeight = this.getCellHeight(true);
164+
153165
// load any element attributes if we don't have a node
154166
if (!node) {
155167
node = this._readAttr(el);
@@ -161,14 +173,16 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
161173
}
162174

163175
// if not calculate the grid size based on element outer size
164-
let w = node.w || Math.round(el.offsetWidth / this.cellWidth()) || 1;
165-
let h = node.h || Math.round(el.offsetHeight / this.getCellHeight(true)) || 1;
176+
helper = helper || el;
177+
let w = node.w || Math.round(helper.offsetWidth / cellWidth) || 1;
178+
let h = node.h || Math.round(helper.offsetHeight / cellHeight) || 1;
166179

167-
// copy the node original values (min/max/id/etc...) but override width/height/other flags which are this grid specific
180+
// COPY the node original values (min/max/id/etc...) but override width/height/other flags which are this grid specific
168181
let newNode = this.engine.prepareNode({...node, ...{w, h, _added: false, _temporary: true, _isOutOfGrid: true}});
169182
el.gridstackNode = newNode;
170183
el._gridstackNodeOrig = node;
171184

185+
onDrag(event as DragEvent, el, helper); // make sure this is called at least once when going fast #1578
172186
GridStackDD.get().on(el, 'drag', onDrag);
173187
return false; // prevent parent from receiving msg (which may be grid as well)
174188
})
@@ -358,114 +372,19 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
358372

359373
/** called when item starts moving/resizing */
360374
let onStartMoving = (event: Event, ui: DDUIData): void => {
361-
let target = event.target as HTMLElement;
362-
363375
// trigger any 'dragstart' / 'resizestart' manually
364376
if (this._gsEventHandler[event.type]) {
365-
this._gsEventHandler[event.type](event, target);
377+
this._gsEventHandler[event.type](event, event.target);
366378
}
367-
368-
this.engine.cleanNodes()
369-
.beginUpdate(node);
370-
371-
this._writePosAttr(this.placeholder, node.x, node.y, node.w, node.h)
372-
this.el.append(this.placeholder);
373-
374-
node.el = this.placeholder;
375-
node._lastUiPosition = ui.position;
376-
node._prevYPix = ui.position.top;
377-
node._moving = (event.type === 'dragstart');
378-
delete node._lastTried;
379-
380-
// set the min/max resize info
381379
cellWidth = this.cellWidth();
382380
cellHeight = this.getCellHeight(true); // force pixels for calculations
383-
this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft);
384-
let dd = GridStackDD.get()
385-
.resizable(el, 'option', 'minWidth', cellWidth * (node.minW || 1))
386-
.resizable(el, 'option', 'minHeight', cellHeight * (node.minH || 1));
387-
if (node.maxW) { dd.resizable(el, 'option', 'maxWidth', cellWidth * node.maxW); }
388-
if (node.maxH) { dd.resizable(el, 'option', 'maxHeight', cellHeight * node.maxH); }
381+
382+
this._onStartMoving(event, ui, node, cellWidth, cellHeight);
389383
}
390384

391385
/** called when item is being dragged/resized */
392386
let dragOrResize = (event: Event, ui: DDUIData): void => {
393-
// calculate the place where we're landing by offsetting margin so actual edge crosses mid point
394-
let left = ui.position.left + (ui.position.left > node._lastUiPosition.left ? -this.opts.marginRight : this.opts.marginLeft);
395-
let top = ui.position.top + (ui.position.top > node._lastUiPosition.top ? -this.opts.marginBottom : this.opts.marginTop);
396-
let x = Math.round(left / cellWidth);
397-
let y = Math.round(top / cellHeight);
398-
let w = node.w;
399-
let h = node.h;
400-
let resizing: boolean;
401-
402-
if (event.type === 'drag') {
403-
let distance = ui.position.top - node._prevYPix;
404-
node._prevYPix = ui.position.top;
405-
Utils.updateScrollPosition(el, ui.position, distance);
406-
// if inTrash, outside of the bounds or added to another grid (#393) temporarily remove it from us
407-
if (el.dataset.inTrashZone || node._added || this.engine.isOutside(x, y, node)) {
408-
if (node._temporaryRemoved) return;
409-
if (this.opts.removable === true) {
410-
this._setupRemovingTimeout(el);
411-
}
412-
413-
x = node._beforeDrag.x;
414-
y = node._beforeDrag.y;
415-
416-
if (this.placeholder.parentNode === this.el) {
417-
this.placeholder.remove();
418-
}
419-
this.engine.removeNode(node);
420-
this._updateContainerHeight();
421-
422-
node._temporaryRemoved = true;
423-
delete node._added; // no need for this now
424-
} else {
425-
if (node._removeTimeout) this._clearRemovingTimeout(el);
426-
427-
if (node._temporaryRemoved) {
428-
this.engine.addNode(node);
429-
this._writePosAttr(this.placeholder, x, y, w, h);
430-
this.el.appendChild(this.placeholder);
431-
node.el = this.placeholder;
432-
delete node._temporaryRemoved;
433-
}
434-
}
435-
if (node.x === x && node.y === y) return; // skip same
436-
// DON'T skip one we tried as we might have failed because of coverage <50% before
437-
// if (node._lastTried && node._lastTried.x === x && node._lastTried.y === y) return;
438-
} else if (event.type === 'resize') {
439-
if (x < 0) return;
440-
// Scrolling page if needed
441-
Utils.updateScrollResize(event as MouseEvent, el, cellHeight);
442-
w = Math.round(ui.size.width / cellWidth);
443-
h = Math.round(ui.size.height / cellHeight);
444-
if (node.w === w && node.h === h) return;
445-
if (node._lastTried && node._lastTried.w === w && node._lastTried.h === h) return; // skip one we tried (but failed)
446-
resizing = true;
447-
}
448-
449-
node._lastTried = {x, y, w, h}; // set as last tried (will nuke if we go there)
450-
let rect: GridStackPosition = { // screen pix of the dragged box
451-
x: ui.position.left + this.opts.marginLeft,
452-
y: ui.position.top + this.opts.marginTop,
453-
w: (ui.size ? ui.size.width : node.w * cellWidth) - this.opts.marginLeft - this.opts.marginRight,
454-
h: (ui.size ? ui.size.height : node.h * cellHeight) - this.opts.marginTop - this.opts.marginBottom
455-
};
456-
if (this.engine.moveNodeCheck(node, {x, y, w, h, cellWidth, cellHeight, rect})) {
457-
node._lastUiPosition = ui.position;
458-
this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft);
459-
delete node._skipDown;
460-
if (resizing && node.subGrid) { (node.subGrid as GridStack).onParentResize(); }
461-
this._updateContainerHeight();
462-
463-
let target = event.target as GridItemHTMLElement;
464-
this._writePosAttr(target, node.x, node.y, node.w, node.h);
465-
if (this._gsEventHandler[event.type]) {
466-
this._gsEventHandler[event.type](event, target);
467-
}
468-
}
387+
this._dragOrResize(event, ui, node, cellWidth, cellHeight);
469388
}
470389

471390
/** called when the item stops moving/resizing */
@@ -548,6 +467,125 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
548467
return this;
549468
}
550469

470+
/** @internal called when item is starting a drag/resize */
471+
GridStack.prototype._onStartMoving = function(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
472+
this.engine.cleanNodes()
473+
.beginUpdate(node);
474+
475+
this._writePosAttr(this.placeholder, node.x, node.y, node.w, node.h)
476+
this.el.append(this.placeholder);
477+
478+
node.el = this.placeholder;
479+
node._lastUiPosition = ui.position;
480+
node._prevYPix = ui.position.top;
481+
node._moving = (event.type === 'dragstart');
482+
delete node._lastTried;
483+
484+
if (event.type === 'dropover' && !node._added) {
485+
node._added = true;
486+
this.engine.addNode(node);
487+
this._writePosAttr(this.placeholder, node.x, node.y, node.w, node.h);
488+
node._moving = true; // lastly mark as moving object
489+
}
490+
491+
// set the min/max resize info
492+
this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft);
493+
if (event.type === 'resizestart') {
494+
let el = node.el;
495+
let dd = GridStackDD.get()
496+
.resizable(el, 'option', 'minWidth', cellWidth * (node.minW || 1))
497+
.resizable(el, 'option', 'minHeight', cellHeight * (node.minH || 1));
498+
if (node.maxW) { dd.resizable(el, 'option', 'maxWidth', cellWidth * node.maxW); }
499+
if (node.maxH) { dd.resizable(el, 'option', 'maxHeight', cellHeight * node.maxH); }
500+
}
501+
}
502+
503+
/** @internal called when item is being dragged/resized */
504+
GridStack.prototype._dragOrResize = function(event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
505+
let el = node.el;
506+
// calculate the place where we're landing by offsetting margin so actual edge crosses mid point
507+
let left = ui.position.left + (ui.position.left > node._lastUiPosition.left ? -this.opts.marginRight : this.opts.marginLeft);
508+
let top = ui.position.top + (ui.position.top > node._lastUiPosition.top ? -this.opts.marginBottom : this.opts.marginTop);
509+
let x = Math.round(left / cellWidth);
510+
let y = Math.round(top / cellHeight);
511+
if (node._isOutOfGrid) {
512+
// items coming from outside are handled by 'dragout' event instead, so make coordinates fit
513+
x = Math.max(0, x);
514+
y = Math.max(0, y);
515+
}
516+
let w = node.w;
517+
let h = node.h;
518+
let resizing: boolean;
519+
520+
if (event.type === 'drag') {
521+
let distance = ui.position.top - node._prevYPix;
522+
node._prevYPix = ui.position.top;
523+
Utils.updateScrollPosition(el, ui.position, distance);
524+
// if inTrash, outside of the bounds or added to another grid (#393) temporarily remove it from us
525+
if (el.dataset.inTrashZone || (node._added && !node._isOutOfGrid) || this.engine.isOutside(x, y, node)) {
526+
if (node._temporaryRemoved) return;
527+
if (this.opts.removable === true) {
528+
this._setupRemovingTimeout(el);
529+
}
530+
531+
x = node._beforeDrag.x;
532+
y = node._beforeDrag.y;
533+
534+
if (this.placeholder.parentNode === this.el) {
535+
this.placeholder.remove();
536+
}
537+
this.engine.removeNode(node);
538+
this._updateContainerHeight();
539+
540+
node._temporaryRemoved = true;
541+
delete node._added; // no need for this now
542+
} else {
543+
if (node._removeTimeout) this._clearRemovingTimeout(el);
544+
545+
if (node._temporaryRemoved) {
546+
this.engine.addNode(node);
547+
this._writePosAttr(this.placeholder, x, y, w, h);
548+
this.el.appendChild(this.placeholder);
549+
node.el = this.placeholder;
550+
delete node._temporaryRemoved;
551+
}
552+
}
553+
if (node.x === x && node.y === y) return; // skip same
554+
// DON'T skip one we tried as we might have failed because of coverage <50% before
555+
// if (node._lastTried && node._lastTried.x === x && node._lastTried.y === y) return;
556+
} else if (event.type === 'resize') {
557+
if (x < 0) return;
558+
// Scrolling page if needed
559+
Utils.updateScrollResize(event as MouseEvent, el, cellHeight);
560+
w = Math.round(ui.size.width / cellWidth);
561+
h = Math.round(ui.size.height / cellHeight);
562+
if (node.w === w && node.h === h) return;
563+
if (node._lastTried && node._lastTried.w === w && node._lastTried.h === h) return; // skip one we tried (but failed)
564+
resizing = true;
565+
}
566+
567+
node._lastTried = {x, y, w, h}; // set as last tried (will nuke if we go there)
568+
let rect: GridStackPosition = { // screen pix of the dragged box
569+
x: ui.position.left + this.opts.marginLeft,
570+
y: ui.position.top + this.opts.marginTop,
571+
w: (ui.size ? ui.size.width : node.w * cellWidth) - this.opts.marginLeft - this.opts.marginRight,
572+
h: (ui.size ? ui.size.height : node.h * cellHeight) - this.opts.marginTop - this.opts.marginBottom
573+
};
574+
if (this.engine.moveNodeCheck(node, {x, y, w, h, cellWidth, cellHeight, rect})) {
575+
node._lastUiPosition = ui.position;
576+
this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft);
577+
delete node._skipDown;
578+
if (resizing && node.subGrid) { (node.subGrid as GridStack).onParentResize(); }
579+
this._updateContainerHeight();
580+
581+
let target = event.target as GridItemHTMLElement;
582+
this._writePosAttr(target, node.x, node.y, node.w, node.h);
583+
if (this._gsEventHandler[event.type]) {
584+
this._gsEventHandler[event.type](event, target);
585+
}
586+
}
587+
}
588+
551589
/**
552590
* Enables/Disables moving.
553591
* @param els widget or selector to modify.

src/gridstack-engine.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ export class GridStackEngine {
390390

391391
/** call to add the given node to our list, fixing collision and re-packing */
392392
public addNode(node: GridStackNode, triggerAddEvent = false): GridStackNode {
393+
if (this.nodes.find(n => n._id === node._id)) return; // prevent inserting twice!
393394
node = this.prepareNode(node);
394395

395396
if (node.autoPosition) {
@@ -511,6 +512,7 @@ export class GridStackEngine {
511512

512513
/** return true if the passed in node (x,y) is being dragged outside of the grid, and not added to bottom */
513514
public isOutside(x: number, y: number, node: GridStackNode): boolean {
515+
if (node._isOutOfGrid) return false; // dragging out is handled by 'dropout' event instead
514516
// simple outside boundaries
515517
if (x < 0 || x >= this.column || y < 0) return true;
516518
if (this.maxRow) return (y >= this.maxRow);

0 commit comments

Comments
 (0)