Skip to content

Commit 7b05db1

Browse files
authored
Merge pull request #1575 from adumesny/develop
don't allow drop when grid is full
2 parents 3fada5a + 905d5d7 commit 7b05db1

File tree

4 files changed

+154
-34
lines changed

4 files changed

+154
-34
lines changed

doc/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Change log
4848
## 3.1.4-dev
4949

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

5253
## 3.1.4 (2021-1-11)
5354

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<title>drop onto full</title>
8+
9+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
10+
<link rel="stylesheet" href="../../../demo/demo.css"/>
11+
<script src="../../../dist/gridstack-h5.js"></script>
12+
<link rel="stylesheet" href="../../../dist/gridstack-extra.css"/>
13+
14+
<style type="text/css">
15+
.grid-stack-item-removing {
16+
opacity: 0.5;
17+
}
18+
.trash {
19+
height: 150px;
20+
margin-bottom: 20px;
21+
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;
22+
}
23+
.sidebar {
24+
background: rgba(0, 255, 0, 0.1);
25+
height: 150px;
26+
padding: 25px 0;
27+
text-align: center;
28+
}
29+
.sidebar .grid-stack-item {
30+
width: 120px;
31+
height: 50px;
32+
border: 2px dashed green;
33+
text-align: center;
34+
line-height: 35px;
35+
z-index: 10;
36+
background: rgba(0, 255, 0, 0.1);
37+
cursor: default;
38+
display: inline-block;
39+
}
40+
.sidebar .grid-stack-item .grid-stack-item-content {
41+
background: none;
42+
}
43+
</style>
44+
</head>
45+
<body>
46+
<div class="container-fluid">
47+
<h1>drop onto full</h1>
48+
49+
<div class="row">
50+
<div class="col-md-3">
51+
<div class="sidebar">
52+
<!-- will size to match content -->
53+
<div class="grid-stack-item">
54+
<div class="grid-stack-item-content">Drag me</div>
55+
</div>
56+
<!-- manually force a drop size of 2x1 -->
57+
<div class="grid-stack-item" gs-w="2" gs-h="1" gs-max-w="3">
58+
<div class="grid-stack-item-content">2x1, max=3</div>
59+
</div>
60+
</div>
61+
</div>
62+
<div class="col-md-9">
63+
<div class="trash">
64+
</div>
65+
</div>
66+
</div>
67+
68+
<div class="row">
69+
<div class="col-md-6">
70+
<a onClick="toggleFloat(this, 0)" class="btn btn-primary" href="#">float: false</a>
71+
<a onClick="compact(0)" class="btn btn-primary" href="#">Compact</a>
72+
<div class="grid-stack grid-stack-6"></div>
73+
</div>
74+
<div class="col-md-6">
75+
<a onClick="toggleFloat(this, 1)" class="btn btn-primary" href="#">float: false</a>
76+
<a onClick="compact(1)" class="btn btn-primary" href="#">Compact</a>
77+
<div class="grid-stack grid-stack-6"></div>
78+
</div>
79+
</div>
80+
</div>
81+
<script src="../../../demo/events.js"></script>
82+
<script type="text/javascript">
83+
let options = {
84+
column: 6,
85+
minRow: 1, // don't collapse when empty
86+
maxRow: 3, // make it full
87+
cellHeight: 70,
88+
disableOneColumnMode: true,
89+
float: false,
90+
dragIn: '.sidebar .grid-stack-item', // class that can be dragged from outside
91+
dragInOptions: { revert: 'invalid', scroll: false, appendTo: 'body', helper: 'clone' }, // clone
92+
removable: '.trash', // drag-out delete class
93+
removeTimeout: 100,
94+
acceptWidgets: function(el) { return true; } // function example, else can be simple: true | false | '.someClass' value
95+
};
96+
let grids = GridStack.initAll(options);
97+
grids[0].load([{x: 4, y: 0, w: 1, h: 1}])
98+
grids[1].load([{x: 0, y: 0, w: 6, h: 3}])
99+
100+
grids.forEach(function (grid, i) {
101+
addEvents(grid, i);
102+
});
103+
104+
function toggleFloat(button, i) {
105+
grids[i].float(! grids[i].getFloat());
106+
button.innerHTML = 'float: ' + grids[i].getFloat();
107+
}
108+
109+
function compact(i) {
110+
grids[i].compact();
111+
}
112+
</script>
113+
</body>
114+
</html>

src/gridstack-dd.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,22 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
127127
.droppable(this.el, {
128128
accept: (el: GridItemHTMLElement) => {
129129
let node: GridStackNode = el.gridstackNode;
130-
if (node && node.grid === this) {
131-
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
132-
}
130+
// set accept drop to true on ourself (which we ignore) so we don't get "can't drop" icon in HTML5 mode while moving
131+
if (node && node.grid === this) return true;
132+
// check for accept method or class matching
133+
let canAccept = true;
133134
if (typeof this.opts.acceptWidgets === 'function') {
134-
return this.opts.acceptWidgets(el);
135+
canAccept = this.opts.acceptWidgets(el);
136+
} else {
137+
let selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets as string);
138+
canAccept = el.matches(selector);
139+
}
140+
// finally check to make sure we actually have space left #1571
141+
if (canAccept && node && this.opts.maxRow) {
142+
let n = {w: node.w, h: node.h, minW: node.minW, minH: node.minH}; // only width/height matters
143+
canAccept = this.engine.willItFit(n);
135144
}
136-
let selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets as string);
137-
return el.matches(selector);
145+
return canAccept;
138146
}
139147
})
140148
.on(this.el, 'dropover', (event, el: GridItemHTMLElement) => {

src/h5/dd-droppable.ts

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt
2424
public option: DDDroppableOpt;
2525

2626
/** @internal */
27-
private acceptable: boolean = null;
27+
private moving: boolean;
2828

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

59-
public disable(): void {
59+
public disable(forDestroy=false): void {
6060
if (this.disabled) return;
6161
super.disable();
62-
this.el.classList.add('ui-droppable-disabled');
62+
if (!forDestroy) this.el.classList.add('ui-droppable-disabled');
6363
this.el.removeEventListener('dragenter', this._dragEnter);
6464
}
6565

6666
public destroy(): void {
67+
if (this.moving) {
68+
this._removeLeaveCallbacks();
69+
}
70+
this.disable(true);
6771
this.el.classList.remove('ui-droppable');
68-
if (this.disabled) {
69-
this.el.classList.remove('ui-droppable-disabled');
70-
this.el.removeEventListener('dragenter', this._dragEnter);
71-
this.el.removeEventListener('dragover', this._dragOver);
72-
this.el.removeEventListener('drop', this._drop);
73-
this.el.removeEventListener('dragleave', this._dragLeave);
74-
}
72+
this.el.classList.remove('ui-droppable-disabled');
73+
delete this.moving;
7574
super.destroy();
7675
}
7776

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

8483
/** @internal called when the cursor enters our area - prepare for a possible drop and track leaving */
8584
private _dragEnter(event: DragEvent): void {
86-
this.el.removeEventListener('dragenter', this._dragEnter);
87-
this.acceptable = this._canDrop();
88-
if (this.acceptable) {
89-
event.preventDefault();
90-
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'dropover' });
91-
if (this.option.over) {
92-
this.option.over(ev, this._ui(DDManager.dragElement))
93-
}
94-
this.triggerEvent('dropover', ev);
95-
this.el.addEventListener('dragover', this._dragOver);
96-
this.el.addEventListener('drop', this._drop);
85+
if (!this._canDrop()) return;
86+
this.moving = true;
87+
88+
event.preventDefault();
89+
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'dropover' });
90+
if (this.option.over) {
91+
this.option.over(ev, this._ui(DDManager.dragElement))
9792
}
98-
this.el.classList.add('ui-droppable-over');
93+
this.triggerEvent('dropover', ev);
94+
this.el.addEventListener('dragover', this._dragOver);
95+
this.el.addEventListener('drop', this._drop);
9996
this.el.addEventListener('dragleave', this._dragLeave);
97+
this.el.classList.add('ui-droppable-over');
10098
}
10199

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

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

122120
/** @internal item is being dropped on us - call the client drop event */
123121
private _drop(event: DragEvent): void {
124-
if (!this.acceptable) return; // should not have received event...
122+
if (!this.moving) return; // should not have received event...
125123
event.preventDefault();
126124
const ev = DDUtils.initEvent<DragEvent>(event, { target: this.el, type: 'drop' });
127125
if (this.option.drop) {
@@ -135,11 +133,10 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt
135133
private _removeLeaveCallbacks() {
136134
this.el.removeEventListener('dragleave', this._dragLeave);
137135
this.el.classList.remove('ui-droppable-over');
138-
if (this.acceptable) {
136+
if (this.moving) {
139137
this.el.removeEventListener('dragover', this._dragOver);
140138
this.el.removeEventListener('drop', this._drop);
141139
}
142-
this.el.addEventListener('dragenter', this._dragEnter);
143140
}
144141

145142
/** @internal */

0 commit comments

Comments
 (0)