Skip to content

Commit 61235de

Browse files
authored
Merge pull request #1549 from adumesny/develop
H5 safari drag fix
2 parents 8500483 + 07cbbd6 commit 61235de

File tree

5 files changed

+58
-73
lines changed

5 files changed

+58
-73
lines changed

doc/CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ Change log
4343
- [v0.1.0 (2014-11-18)](#v010-2014-11-18)
4444

4545
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
46-
4746
## 3.1.2-dev
4847

4948
- fix [1535](https://github.com/gridstack/gridstack.js/issues/1535) use batchUpdate() around grid init to make sure gs-y attributes are respected.
49+
- fix [1540](https://github.com/gridstack/gridstack.js/issues/1540) Safari H5 drag&drop fixed
5050

5151
## 3.1.2 (2020-12-7)
5252

src/gridstack-dd.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ GridStack.prototype._setupAcceptWidget = function(): GridStack {
101101
this.engine.beginUpdate(node);
102102
this.engine.addNode(node);
103103

104-
this._writeAttrs(this.placeholder, node.x, node.y, node.w, node.h);
104+
this._writePosAttr(this.placeholder, node.x, node.y, node.w, node.h);
105105
this.el.appendChild(this.placeholder);
106106
node.el = this.placeholder; // dom we update while dragging...
107107
node._beforeDragX = node.x;
@@ -354,29 +354,22 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
354354

355355
this.engine.cleanNodes();
356356
this.engine.beginUpdate(node);
357-
cellWidth = this.cellWidth();
358-
cellHeight = this.getCellHeight(true); // force pixels for calculations
359357

360-
this.placeholder.setAttribute('gs-x', target.getAttribute('gs-x'));
361-
this.placeholder.setAttribute('gs-y', target.getAttribute('gs-y'));
362-
this.placeholder.setAttribute('gs-w', target.getAttribute('gs-w'));
363-
this.placeholder.setAttribute('gs-h', target.getAttribute('gs-h'));
358+
this._writePosAttr(this.placeholder, node.x, node.y, node.w, node.h)
364359
this.el.append(this.placeholder);
365360

366361
node.el = this.placeholder;
367362
node._beforeDragX = node.x;
368363
node._beforeDragY = node.y;
369364
node._prevYPix = ui.position.top;
370365

366+
// set the min/max resize info
367+
cellWidth = this.cellWidth();
368+
cellHeight = this.getCellHeight(true); // force pixels for calculations
371369
GridStackDD.get().resizable(el, 'option', 'minWidth', cellWidth * (node.minW || 1));
372370
GridStackDD.get().resizable(el, 'option', 'minHeight', cellHeight * (node.minH || 1));
373-
// also set max if set #1330
374-
if (node.maxW) {
375-
GridStackDD.get().resizable(el, 'option', 'maxWidth', cellWidth * node.maxW);
376-
}
377-
if (node.maxH) {
378-
GridStackDD.get().resizable(el, 'option', 'maxHeight', cellHeight * node.maxH);
379-
}
371+
if (node.maxW) { GridStackDD.get().resizable(el, 'option', 'maxWidth', cellWidth * node.maxW); }
372+
if (node.maxH) { GridStackDD.get().resizable(el, 'option', 'maxHeight', cellHeight * node.maxH); }
380373
}
381374

382375
/** called when item is being dragged/resized */
@@ -413,7 +406,7 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
413406

414407
if (node._temporaryRemoved) {
415408
this.engine.addNode(node);
416-
this._writeAttrs(this.placeholder, x, y, w, h);
409+
this._writePosAttr(this.placeholder, x, y, w, h);
417410
this.el.appendChild(this.placeholder);
418411
node.el = this.placeholder;
419412
delete node._temporaryRemoved;
@@ -464,10 +457,10 @@ GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): Grid
464457
this._clearRemovingTimeout(el);
465458
if (!node._temporaryRemoved) {
466459
Utils.removePositioningStyles(target);
467-
this._writeAttrs(target, node.x, node.y, node.w, node.h);
460+
this._writePosAttr(target, node.x, node.y, node.w, node.h);
468461
} else {
469462
Utils.removePositioningStyles(target);
470-
this._writeAttrs(target, node._beforeDragX, node._beforeDragY, node.w, node.h);
463+
this._writePosAttr(target, node._beforeDragX, node._beforeDragY, node.w, node.h);
471464
node.x = node._beforeDragX;
472465
node.y = node._beforeDragY;
473466
delete node._temporaryRemoved;

src/gridstack.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ export class GridStack {
304304
if (removeDOM && n._id === null) {
305305
if (el && el.parentNode) { el.parentNode.removeChild(el) }
306306
} else {
307-
this._writeAttrs(el, n.x, n.y, n.w, n.h);
307+
this._writePosAttr(el, n.x, n.y, n.w, n.h);
308308
}
309309
});
310310
this._updateStyles(false, maxH); // false = don't recreate, just append if need be
@@ -1283,7 +1283,7 @@ export class GridStack {
12831283
}
12841284

12851285
/** @internal call to write x,y,w,h attributes back to element */
1286-
private _writeAttrs(el: HTMLElement, x?: number, y?: number, w?: number, h?: number): GridStack {
1286+
private _writePosAttr(el: HTMLElement, x?: number, y?: number, w?: number, h?: number): GridStack {
12871287
if (x !== undefined && x !== null) { el.setAttribute('gs-x', String(x)); }
12881288
if (y !== undefined && y !== null) { el.setAttribute('gs-y', String(y)); }
12891289
if (w) { el.setAttribute('gs-w', String(w)); }
@@ -1294,9 +1294,9 @@ export class GridStack {
12941294
/** @internal call to write any default attributes back to element */
12951295
private _writeAttr(el: HTMLElement, node: GridStackWidget): GridStack {
12961296
if (!node) return this;
1297-
this._writeAttrs(el, node.x, node.y, node.w, node.h);
1297+
this._writePosAttr(el, node.x, node.y, node.w, node.h);
12981298

1299-
let attrs /*: like GridStackWidget but strings */ = { // remaining attributes
1299+
let attrs /*: GridStackWidget but strings */ = { // remaining attributes
13001300
autoPosition: 'gs-auto-position',
13011301
minW: 'gs-min-w',
13021302
minH: 'gs-min-h',
@@ -1309,7 +1309,7 @@ export class GridStack {
13091309
resizeHandles: 'gs-resize-handles'
13101310
};
13111311
for (const key in attrs) {
1312-
if (node[key]) { // 0 is valid for x,y only but done above already and not in list
1312+
if (node[key]) { // 0 is valid for x,y only but done above already and not in list anyway
13131313
el.setAttribute(attrs[key], String(node[key]));
13141314
} else {
13151315
el.removeAttribute(attrs[key]);

src/h5/dd-draggable.ts

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
4545
/** @internal */
4646
private dragFollowTimer: number;
4747
/** @internal */
48-
private mouseDownElement: HTMLElement;
48+
private dragEl: HTMLElement;
4949
/** @internal */
5050
private dragging = false;
5151
/** @internal */
@@ -127,12 +127,12 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
127127
let className = this.option.handle.substring(1);
128128
let el = event.target as HTMLElement;
129129
while (el && !el.classList.contains(className)) { el = el.parentElement; }
130-
this.mouseDownElement = el;
130+
this.dragEl = el;
131131
}
132132

133133
/** @internal */
134134
private _dragStart(event: DragEvent): void {
135-
if (!this.mouseDownElement) { event.preventDefault(); return; }
135+
if (!this.dragEl) { event.preventDefault(); return; }
136136
DDManager.dragElement = this;
137137
this.helper = this._createHelper(event);
138138
this._setupHelperContainmentStyle();
@@ -201,20 +201,19 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
201201
this.triggerEvent('dragstop', ev);
202202
delete DDManager.dragElement;
203203
delete this.helper;
204-
delete this.mouseDownElement;
204+
delete this.dragEl;
205205
}
206206

207-
/** @internal */
207+
/** @internal create a clone copy (or user defined method) of the original drag item if set */
208208
private _createHelper(event: DragEvent): HTMLElement {
209-
const helperIsFunction = (typeof this.option.helper) === 'function';
210-
const helper = (helperIsFunction
211-
? (this.option.helper as ((event: Event) => HTMLElement)).apply(this.el, [event])
212-
: (this.option.helper === "clone" ? DDUtils.clone(this.el) : this.el)
213-
) as HTMLElement;
209+
let helper = this.el;
210+
if (typeof this.option.helper === 'function') {
211+
helper = this.option.helper.apply(this.el, event);
212+
} else if (this.option.helper === 'clone') {
213+
helper = DDUtils.clone(this.el);
214+
}
214215
if (!document.body.contains(helper)) {
215-
DDUtils.appendTo(helper, (this.option.appendTo === "parent"
216-
? this.el.parentNode
217-
: this.option.appendTo));
216+
DDUtils.appendTo(helper, this.option.appendTo === 'parent' ? this.el.parentNode : this.option.appendTo);
218217
}
219218
if (helper === this.el) {
220219
this.dragElementOriginStyle = DDDraggable.originStyleProp.map(prop => this.el.style[prop]);
@@ -227,7 +226,7 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
227226
this.helper.style.pointerEvents = 'none';
228227
this.helper.style.width = this.dragOffset.width + 'px';
229228
this.helper.style.height = this.dragOffset.height + 'px';
230-
this.helper.style['willChange'] = 'left, top';
229+
this.helper.style.willChange = 'left, top';
231230
this.helper.style.transition = 'none'; // show up instantly
232231
this.helper.style.position = this.option.basePosition || DDDraggable.basePosition;
233232
this.helper.style.zIndex = '1000';
@@ -282,23 +281,27 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
282281
return this;
283282
}
284283

285-
/** @internal */
284+
/** @internal prevent the default gost image to be created (which has wrongas we move the helper/element instead
285+
* (legacy jquery UI code updates the top/left of the item).
286+
* TODO: maybe use mouse event instead of HTML5 drag as we have to work around it anyway, or change code to not update
287+
* the actual grid-item but move the gost image around (and special case jq version) ?
288+
**/
286289
private _cancelDragGhost(e: DragEvent): DDDraggable {
287-
if (e.dataTransfer != null) {
288-
e.dataTransfer.setData('text', '');
289-
}
290-
e.dataTransfer.effectAllowed = 'move';
291-
if ('function' === typeof DataTransfer.prototype.setDragImage) {
292-
e.dataTransfer.setDragImage(new Image(), 0, 0);
293-
} else {
294-
// ie
295-
(e.target as HTMLElement).style.display = 'none';
296-
setTimeout(() => {
297-
(e.target as HTMLElement).style.display = '';
298-
});
299-
e.stopPropagation();
300-
return;
301-
}
290+
/* doesn't seem to do anything...
291+
let t = e.dataTransfer;
292+
t.effectAllowed = 'none';
293+
t.dropEffect = 'none';
294+
t.setData('text', '');
295+
*/
296+
297+
// NOTE: according to spec (and required by Safari see #1540) the image has to be visible in the browser (in dom and not hidden) so make it a 1px div
298+
let img = document.createElement('div');
299+
img.style.width = '1px';
300+
img.style.height = '1px';
301+
document.body.appendChild(img);
302+
e.dataTransfer.setDragImage(img, 0, 0);
303+
setTimeout(() => document.body.removeChild(img)); // nuke once drag had a chance to grab this 'image'
304+
302305
e.stopPropagation();
303306
return this;
304307
}

src/h5/dd-utils.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class DDUtils {
4141
}
4242
}
4343

44-
public static setPositionRelative(el): void {
44+
public static setPositionRelative(el: HTMLElement): void {
4545
if (!(/^(?:r|a|f)/).test(window.getComputedStyle(el).position)) {
4646
el.style.position = "relative";
4747
}
@@ -65,32 +65,21 @@ export class DDUtils {
6565
}
6666

6767
public static initEvent<T>(e: DragEvent | MouseEvent, info: { type: string; target?: EventTarget }): T {
68-
const kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(',');
69-
const ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY'.split(',');
7068
const evt = { type: info.type };
7169
const obj = {
7270
button: 0,
7371
which: 0,
7472
buttons: 1,
7573
bubbles: true,
7674
cancelable: true,
77-
originEvent: e,
7875
target: info.target ? info.target : e.target
76+
};
77+
// don't check for `instanceof DragEvent` as Safari use MouseEvent #1540
78+
if ((e as DragEvent).dataTransfer) {
79+
evt['dataTransfer'] = (e as DragEvent).dataTransfer; // workaround 'readonly' field.
7980
}
80-
if (e instanceof DragEvent) {
81-
Object.assign(obj, { dataTransfer: e.dataTransfer });
82-
}
83-
DDUtils._copyProps(evt, e, kbdProps);
84-
DDUtils._copyProps(evt, e, ptProps);
85-
DDUtils._copyProps(evt, obj, Object.keys(obj));
86-
return evt as unknown as T;
87-
}
88-
89-
/** @internal */
90-
private static _copyProps(dst: unknown, src: unknown, props: string[]): void {
91-
for (let i = 0; i < props.length; i++) {
92-
const p = props[i];
93-
dst[p] = src[p];
94-
}
81+
['altKey','ctrlKey','metaKey','shiftKey'].forEach(p => evt[p] = e[p]); // keys
82+
['pageX','pageY','clientX','clientY','screenX','screenY'].forEach(p => evt[p] = e[p]); // point info
83+
return {...evt, ...obj} as unknown as T;
9584
}
96-
}
85+
}

0 commit comments

Comments
 (0)