Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Change log
## 3.1.4-dev

- fix [1572](https://github.com/gridstack/gridstack.js/issues/1572) `column: N` option now sets CSS class
- fix [1571](https://github.com/gridstack/gridstack.js/issues/1571) don't allow drop when grid is full

## 3.1.4 (2021-1-11)

Expand Down
114 changes: 114 additions & 0 deletions spec/e2e/html/1571_drop_onto_full.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>drop onto full</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="../../../demo/demo.css"/>
<script src="../../../dist/gridstack-h5.js"></script>
<link rel="stylesheet" href="../../../dist/gridstack-extra.css"/>

<style type="text/css">
.grid-stack-item-removing {
opacity: 0.5;
}
.trash {
height: 150px;
margin-bottom: 20px;
background: rgba(255, 0, 0, 0.1) center center url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDQzOC41MjkgNDM4LjUyOSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDM4LjUyOSA0MzguNTI5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTQxNy42ODksNzUuNjU0Yy0xLjcxMS0xLjcwOS0zLjkwMS0yLjU2OC02LjU2My0yLjU2OGgtODguMjI0TDMwMi45MTcsMjUuNDFjLTIuODU0LTcuMDQ0LTcuOTk0LTEzLjA0LTE1LjQxMy0xNy45ODkgICAgQzI4MC4wNzgsMi40NzMsMjcyLjU1NiwwLDI2NC45NDUsMGgtOTEuMzYzYy03LjYxMSwwLTE1LjEzMSwyLjQ3My0yMi41NTQsNy40MjFjLTcuNDI0LDQuOTQ5LTEyLjU2MywxMC45NDQtMTUuNDE5LDE3Ljk4OSAgICBsLTE5Ljk4NSw0Ny42NzZoLTg4LjIyYy0yLjY2NywwLTQuODUzLDAuODU5LTYuNTY3LDIuNTY4Yy0xLjcwOSwxLjcxMy0yLjU2OCwzLjkwMy0yLjU2OCw2LjU2N3YxOC4yNzQgICAgYzAsMi42NjQsMC44NTUsNC44NTQsMi41NjgsNi41NjRjMS43MTQsMS43MTIsMy45MDQsMi41NjgsNi41NjcsMi41NjhoMjcuNDA2djI3MS44YzAsMTUuODAzLDQuNDczLDI5LjI2NiwxMy40MTgsNDAuMzk4ICAgIGM4Ljk0NywxMS4xMzksMTkuNzAxLDE2LjcwMywzMi4yNjQsMTYuNzAzaDIzNy41NDJjMTIuNTY2LDAsMjMuMzE5LTUuNzU2LDMyLjI2NS0xNy4yNjhjOC45NDUtMTEuNTIsMTMuNDE1LTI1LjE3NCwxMy40MTUtNDAuOTcxICAgIFYxMDkuNjI3aDI3LjQxMWMyLjY2MiwwLDQuODUzLTAuODU2LDYuNTYzLTIuNTY4YzEuNzA4LTEuNzA5LDIuNTctMy45LDIuNTctNi41NjRWODIuMjIxICAgIEM0MjAuMjYsNzkuNTU3LDQxOS4zOTcsNzcuMzY3LDQxNy42ODksNzUuNjU0eiBNMTY5LjMwMSwzOS42NzhjMS4zMzEtMS43MTIsMi45NS0yLjc2Miw0Ljg1My0zLjE0aDkwLjUwNCAgICBjMS45MDMsMC4zODEsMy41MjUsMS40Myw0Ljg1NCwzLjE0bDEzLjcwOSwzMy40MDRIMTU1LjMxMUwxNjkuMzAxLDM5LjY3OHogTTM0Ny4xNzMsMzgwLjI5MWMwLDQuMTg2LTAuNjY0LDguMDQyLTEuOTk5LDExLjU2MSAgICBjLTEuMzM0LDMuNTE4LTIuNzE3LDYuMDg4LTQuMTQxLDcuNzA2Yy0xLjQzMSwxLjYyMi0yLjQyMywyLjQyNy0yLjk5OCwyLjQyN0gxMDAuNDkzYy0wLjU3MSwwLTEuNTY1LTAuODA1LTIuOTk2LTIuNDI3ICAgIGMtMS40MjktMS42MTgtMi44MS00LjE4OC00LjE0My03LjcwNmMtMS4zMzEtMy41MTktMS45OTctNy4zNzktMS45OTctMTEuNTYxVjEwOS42MjdoMjU1LjgxNVYzODAuMjkxeiIgZmlsbD0iI2ZmOWNhZSIvPgoJCTxwYXRoIGQ9Ik0xMzcuMDQsMzQ3LjE3MmgxOC4yNzFjMi42NjcsMCw0Ljg1OC0wLjg1NSw2LjU2Ny0yLjU2N2MxLjcwOS0xLjcxOCwyLjU2OC0zLjkwMSwyLjU2OC02LjU3VjE3My41ODEgICAgYzAtMi42NjMtMC44NTktNC44NTMtMi41NjgtNi41NjdjLTEuNzE0LTEuNzA5LTMuODk5LTIuNTY1LTYuNTY3LTIuNTY1SDEzNy4wNGMtMi42NjcsMC00Ljg1NCwwLjg1NS02LjU2NywyLjU2NSAgICBjLTEuNzExLDEuNzE0LTIuNTY4LDMuOTA0LTIuNTY4LDYuNTY3djE2NC40NTRjMCwyLjY2OSwwLjg1NCw0Ljg1MywyLjU2OCw2LjU3QzEzMi4xODYsMzQ2LjMxNiwxMzQuMzczLDM0Ny4xNzIsMTM3LjA0LDM0Ny4xNzJ6IiBmaWxsPSIjZmY5Y2FlIi8+CgkJPHBhdGggZD0iTTIxMC4xMjksMzQ3LjE3MmgxOC4yNzFjMi42NjYsMCw0Ljg1Ni0wLjg1NSw2LjU2NC0yLjU2N2MxLjcxOC0xLjcxOCwyLjU2OS0zLjkwMSwyLjU2OS02LjU3VjE3My41ODEgICAgYzAtMi42NjMtMC44NTItNC44NTMtMi41NjktNi41NjdjLTEuNzA4LTEuNzA5LTMuODk4LTIuNTY1LTYuNTY0LTIuNTY1aC0xOC4yNzFjLTIuNjY0LDAtNC44NTQsMC44NTUtNi41NjcsMi41NjUgICAgYy0xLjcxNCwxLjcxNC0yLjU2OCwzLjkwNC0yLjU2OCw2LjU2N3YxNjQuNDU0YzAsMi42NjksMC44NTQsNC44NTMsMi41NjgsNi41N0MyMDUuMjc0LDM0Ni4zMTYsMjA3LjQ2NSwzNDcuMTcyLDIxMC4xMjksMzQ3LjE3MnogICAgIiBmaWxsPSIjZmY5Y2FlIi8+CgkJPHBhdGggZD0iTTI4My4yMiwzNDcuMTcyaDE4LjI2OGMyLjY2OSwwLDQuODU5LTAuODU1LDYuNTctMi41NjdjMS43MTEtMS43MTgsMi41NjItMy45MDEsMi41NjItNi41N1YxNzMuNTgxICAgIGMwLTIuNjYzLTAuODUyLTQuODUzLTIuNTYyLTYuNTY3Yy0xLjcxMS0xLjcwOS0zLjkwMS0yLjU2NS02LjU3LTIuNTY1SDI4My4yMmMtMi42NywwLTQuODUzLDAuODU1LTYuNTcxLDIuNTY1ICAgIGMtMS43MTEsMS43MTQtMi41NjYsMy45MDQtMi41NjYsNi41Njd2MTY0LjQ1NGMwLDIuNjY5LDAuODU1LDQuODUzLDIuNTY2LDYuNTdDMjc4LjM2NywzNDYuMzE2LDI4MC41NSwzNDcuMTcyLDI4My4yMiwzNDcuMTcyeiIgZmlsbD0iI2ZmOWNhZSIvPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=) no-repeat;
}
.sidebar {
background: rgba(0, 255, 0, 0.1);
height: 150px;
padding: 25px 0;
text-align: center;
}
.sidebar .grid-stack-item {
width: 120px;
height: 50px;
border: 2px dashed green;
text-align: center;
line-height: 35px;
z-index: 10;
background: rgba(0, 255, 0, 0.1);
cursor: default;
display: inline-block;
}
.sidebar .grid-stack-item .grid-stack-item-content {
background: none;
}
</style>
</head>
<body>
<div class="container-fluid">
<h1>drop onto full</h1>

<div class="row">
<div class="col-md-3">
<div class="sidebar">
<!-- will size to match content -->
<div class="grid-stack-item">
<div class="grid-stack-item-content">Drag me</div>
</div>
<!-- manually force a drop size of 2x1 -->
<div class="grid-stack-item" gs-w="2" gs-h="1" gs-max-w="3">
<div class="grid-stack-item-content">2x1, max=3</div>
</div>
</div>
</div>
<div class="col-md-9">
<div class="trash">
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<a onClick="toggleFloat(this, 0)" class="btn btn-primary" href="#">float: false</a>
<a onClick="compact(0)" class="btn btn-primary" href="#">Compact</a>
<div class="grid-stack grid-stack-6"></div>
</div>
<div class="col-md-6">
<a onClick="toggleFloat(this, 1)" class="btn btn-primary" href="#">float: false</a>
<a onClick="compact(1)" class="btn btn-primary" href="#">Compact</a>
<div class="grid-stack grid-stack-6"></div>
</div>
</div>
</div>
<script src="../../../demo/events.js"></script>
<script type="text/javascript">
let options = {
column: 6,
minRow: 1, // don't collapse when empty
maxRow: 3, // make it full
cellHeight: 70,
disableOneColumnMode: true,
float: false,
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);
grids[0].load([{x: 4, y: 0, w: 1, h: 1}])
grids[1].load([{x: 0, y: 0, w: 6, h: 3}])

grids.forEach(function (grid, i) {
addEvents(grid, i);
});

function toggleFloat(button, i) {
grids[i].float(! grids[i].getFloat());
button.innerHTML = 'float: ' + grids[i].getFloat();
}

function compact(i) {
grids[i].compact();
}
</script>
</body>
</html>
20 changes: 14 additions & 6 deletions src/gridstack-dd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,22 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
.droppable(this.el, {
accept: (el: GridItemHTMLElement) => {
let node: GridStackNode = el.gridstackNode;
if (node && node.grid === this) {
return true; // set accept drop to true on ourself (which we ignore) so we don't get "can't drop" icon in HTML5 mode while moving
}
// set accept drop to true on ourself (which we ignore) so we don't get "can't drop" icon in HTML5 mode while moving
if (node && node.grid === this) return true;
// check for accept method or class matching
let canAccept = true;
if (typeof this.opts.acceptWidgets === 'function') {
return this.opts.acceptWidgets(el);
canAccept = this.opts.acceptWidgets(el);
} else {
let selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets as string);
canAccept = el.matches(selector);
}
// finally check to make sure we actually have space left #1571
if (canAccept && node && this.opts.maxRow) {
let n = {w: node.w, h: node.h, minW: node.minW, minH: node.minH}; // only width/height matters
canAccept = this.engine.willItFit(n);
}
let selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets as string);
return el.matches(selector);
return canAccept;
}
})
.on(this.el, 'dropover', (event, el: GridItemHTMLElement) => {
Expand Down
53 changes: 25 additions & 28 deletions src/h5/dd-droppable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt
public option: DDDroppableOpt;

/** @internal */
private acceptable: boolean = null;
private moving: boolean;

constructor(el: HTMLElement, opts: DDDroppableOpt = {}) {
super();
Expand Down Expand Up @@ -56,22 +56,21 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt
this.el.addEventListener('dragenter', this._dragEnter);
}

public disable(): void {
public disable(forDestroy=false): void {
if (this.disabled) return;
super.disable();
this.el.classList.add('ui-droppable-disabled');
if (!forDestroy) this.el.classList.add('ui-droppable-disabled');
this.el.removeEventListener('dragenter', this._dragEnter);
}

public destroy(): void {
if (this.moving) {
this._removeLeaveCallbacks();
}
this.disable(true);
this.el.classList.remove('ui-droppable');
if (this.disabled) {
this.el.classList.remove('ui-droppable-disabled');
this.el.removeEventListener('dragenter', this._dragEnter);
this.el.removeEventListener('dragover', this._dragOver);
this.el.removeEventListener('drop', this._drop);
this.el.removeEventListener('dragleave', this._dragLeave);
}
this.el.classList.remove('ui-droppable-disabled');
delete this.moving;
super.destroy();
}

Expand All @@ -83,33 +82,32 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt

/** @internal called when the cursor enters our area - prepare for a possible drop and track leaving */
private _dragEnter(event: DragEvent): void {
this.el.removeEventListener('dragenter', this._dragEnter);
this.acceptable = this._canDrop();
if (this.acceptable) {
event.preventDefault();
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'dropover' });
if (this.option.over) {
this.option.over(ev, this._ui(DDManager.dragElement))
}
this.triggerEvent('dropover', ev);
this.el.addEventListener('dragover', this._dragOver);
this.el.addEventListener('drop', this._drop);
if (!this._canDrop()) return;
this.moving = true;

event.preventDefault();
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'dropover' });
if (this.option.over) {
this.option.over(ev, this._ui(DDManager.dragElement))
}
this.el.classList.add('ui-droppable-over');
this.triggerEvent('dropover', ev);
this.el.addEventListener('dragover', this._dragOver);
this.el.addEventListener('drop', this._drop);
this.el.addEventListener('dragleave', this._dragLeave);
this.el.classList.add('ui-droppable-over');
}

/** @internal called when an acceptable to drop item is being dragged over - do nothing but eat the event */
/** @internal called when an moving to drop item is being dragged over - do nothing but eat the event */
private _dragOver(event: DragEvent): void {
event.preventDefault();
event.stopPropagation();
}

/** @internal called when the item is leaving our area, stop tracking if we had acceptable item */
/** @internal called when the item is leaving our area, stop tracking if we had moving item */
private _dragLeave(event: DragEvent): void {
if (this.el.contains(event.relatedTarget as HTMLElement)) return;
this._removeLeaveCallbacks();
if (this.acceptable) {
if (this.moving) {
event.preventDefault();
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'dropout' });
if (this.option.out) {
Expand All @@ -121,7 +119,7 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt

/** @internal item is being dropped on us - call the client drop event */
private _drop(event: DragEvent): void {
if (!this.acceptable) return; // should not have received event...
if (!this.moving) return; // should not have received event...
event.preventDefault();
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'drop' });
if (this.option.drop) {
Expand All @@ -135,11 +133,10 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt
private _removeLeaveCallbacks() {
this.el.removeEventListener('dragleave', this._dragLeave);
this.el.classList.remove('ui-droppable-over');
if (this.acceptable) {
if (this.moving) {
this.el.removeEventListener('dragover', this._dragOver);
this.el.removeEventListener('drop', this._drop);
}
this.el.addEventListener('dragenter', this._dragEnter);
}

/** @internal */
Expand Down