From 60485647259789f5c59138a304232f5e0baaffae Mon Sep 17 00:00:00 2001 From: rhlin Date: Thu, 29 Oct 2020 11:52:45 -0700 Subject: [PATCH 01/17] feat(h5dd): h5dd plugin performance optimize draggable 1. use requireAnimateFrame 2. set up will change 3. use passive event if possible 4. change helper position from 'fixed' to 'absolute' to avoide flicker 5. dragend event binding after dragstart 6. fix removing ui-draggable-dragging class for drag-in element droppable 1. pre transform accept to function on init from https://github.com/rhlin/gridstack.js feat-h5dd-optimize --- src/dragdrop/dd-draggable.ts | 315 +++++++++++++++++++++++++++++++++++ src/dragdrop/dd-droppable.ts | 154 +++++++++++++++++ src/dragdrop/dd-utils.ts | 102 ++++++++++++ 3 files changed, 571 insertions(+) create mode 100644 src/dragdrop/dd-draggable.ts create mode 100644 src/dragdrop/dd-droppable.ts create mode 100644 src/dragdrop/dd-utils.ts diff --git a/src/dragdrop/dd-draggable.ts b/src/dragdrop/dd-draggable.ts new file mode 100644 index 000000000..50c703217 --- /dev/null +++ b/src/dragdrop/dd-draggable.ts @@ -0,0 +1,315 @@ +// dd-draggable.ts 2.0.2-dev @preserve + +/** + * https://gridstackjs.com/ + * (c) 2020 Alain Dumesny, rhlin + * gridstack.js may be freely distributed under the MIT license. +*/ +import { DDManager } from './dd-manager'; +import { DDUtils } from './dd-utils'; +import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; + +export interface DDDraggbleOpt { + appendTo?: string | HTMLElement; + containment?: string | HTMLElement; + handle?: string; + revert?: string | boolean | unknown; // TODO: not impleament yet + scroll?: boolean; // nature support by HTML5 drag drop, can't be switch to off actually + helper?: string | ((event: Event) => HTMLElement); + basePosision?: 'fixed' | 'absolute'; + start?: (event?, ui?) => void; + stop?: (event?, ui?) => void; + drag?: (event?, ui?) => void; +}; +export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt { + static basePosition: 'fixed'| 'absolute'= 'absolute'; + static dragEventListinerOption = DDUtils.isEventSupportPassiveOption ? { capture: true, passive: true } : true; + static originStyleProp = ['transition', 'pointerEvents', 'position', + 'left', 'top', 'opacity', 'zIndex', 'width', 'height', 'willChange']; + el: HTMLElement; + helper: HTMLElement; + option: DDDraggbleOpt; + dragOffset: { + left: number; + top: number; + width: number; + height: number; + offsetLeft: number; + offsetTop: number; + }; + dragElementOriginStyle: Array; + dragFollowTimer: number; + mouseDownElement: HTMLElement; + dragging = false; + paintTimer: number; + parentOriginStylePosition: string; + helperContainment: HTMLElement; + + constructor(el: HTMLElement, option: DDDraggbleOpt) { + super(); + this.el = el; + this.option = option || {}; + this.init(); + } + + on(event: 'drag' | 'dragstart' | 'dragstop', callback: (event: DragEvent) => void): void { + super.on(event, callback); + } + + off(event: 'drag' | 'dragstart' | 'dragstop') { + super.off(event); + } + + enable() { + super.enable(); + this.el.draggable = true; + this.el.classList.remove('ui-draggable-disabled'); + } + + disable() { + super.disable(); + this.el.draggable = false; + this.el.classList.add('ui-draggable-disabled'); + } + + updateOption(opts) { + Object.keys(opts).forEach(key => { + const value = opts[key]; + this.option[key] = value; + }); + } + + protected init() { + this.el.draggable = true; + this.el.classList.add('ui-draggable'); + this.el.addEventListener('mousedown', this.mouseDown); + this.el.addEventListener('dragstart', this.dragStart); + this.dragThrottle = DDUtils.throttle(this.drag, 100); + } + + protected mouseDown = (event: MouseEvent) => { + this.mouseDownElement = event.target as HTMLElement; + } + + protected dragStart = (event: DragEvent) => { + if (this.option.handle && !( + this.mouseDownElement + && this.mouseDownElement.matches( + `${this.option.handle}, ${this.option.handle} > *` + ) + )) { + event.preventDefault(); + return; + } + DDManager.dragElement = this; + this.helper = this.createHelper(event); + this.setupHelperContainmentStyle(); + this.dragOffset = this.getDragOffset(event, this.el, this.helperContainment); + const ev = DDUtils.initEvent(event, { target: this.el, type: 'dragstart' }); + if (this.helper !== this.el) { + this.setupDragFollowNodeNNotifyStart(ev); + } else { + this.dragFollowTimer = setTimeout(() => { + this.dragFollowTimer = undefined; + this.setupDragFollowNodeNNotifyStart(ev); + }, 0); + } + this.cancelDragGhost(event); + } + + protected setupDragFollowNodeNNotifyStart(ev) { + this.setupHelperStyle(); + document.addEventListener('dragover', this.dragThrottle, DDDraggble.dragEventListinerOption); + this.el.addEventListener('dragend', this.dragEnd); + if (this.option.start) { + this.option.start(ev, this.ui()); + } + this.triggerEvent('dragstart', ev); + this.dragging = true; + this.helper.classList.add('ui-draggable-dragging'); + } + + protected dragThrottle: (event: DragEvent) => void; + protected drag = (event: DragEvent) => { + this.dragFollow(event); + const ev = DDUtils.initEvent(event, { target: this.el, type: 'drag' }); + if (this.option.drag) { + this.option.drag(ev, this.ui()); + } + this.triggerEvent('drag', ev); + } + + protected dragEnd = (event: DragEvent) => { + if (this.dragFollowTimer) { + clearTimeout(this.dragFollowTimer); + this.dragFollowTimer = undefined; + return; + } else { + document.removeEventListener('dragover', this.dragThrottle, DDDraggble.dragEventListinerOption); + this.el.removeEventListener('dragend', this.dragEnd); + } + this.dragging = false; + this.helper.classList.remove('ui-draggable-dragging'); + this.helperContainment.style.position = this.parentOriginStylePosition || null; + if (this.helper === this.el) { + this.removeHelperStyle(); + } else { + this.helper.remove(); + } + const ev = DDUtils.initEvent(event, { target: this.el, type: 'dragstop' }); + if (this.option.stop) { + this.option.stop(ev, this.ui()); + } + this.triggerEvent('dragstop', ev); + DDManager.dragElement = undefined; + this.helper = undefined; + this.mouseDownElement = undefined; + } + + private createHelper(event: DragEvent) { + const helperIsFunction = (typeof this.option.helper) === 'function'; + const helper = (helperIsFunction + ? (this.option.helper as ((event: Event) => HTMLElement)).apply(this.el, [event]) + : (this.option.helper === "clone" ? DDUtils.clone(this.el) : this.el) + ) as HTMLElement; + if (!document.body.contains(helper)) { + DDUtils.appendTo(helper, (this.option.appendTo === "parent" + ? this.el.parentNode + : this.option.appendTo)); + } + if (helper === this.el) { + this.dragElementOriginStyle = DDDraggble.originStyleProp.map(prop => this.el.style[prop]); + } + return helper; + } + + private setupHelperStyle() { + this.helper.style.pointerEvents = 'none'; + this.helper.style.width = this.dragOffset.width + 'px'; + this.helper.style.height = this.dragOffset.height + 'px'; + this.helper.style['willChange'] = 'left, top'; + this.helper.style.transition = 'none'; // show up instancely + this.helper.style.position = this.option.basePosision || DDDraggble.basePosition; + this.helper.style.zIndex = '1000'; + setTimeout(() => { + this.helper.style.transition = null; // recover animation + }, 100); + } + + private removeHelperStyle() { + DDDraggble.originStyleProp.forEach(prop => { + this.helper.style[prop] = this.dragElementOriginStyle[prop] || null; + }); + this.dragElementOriginStyle = undefined; + } + + private dragFollow = (event: DragEvent) => { + if (this.paintTimer) { + cancelAnimationFrame(this.paintTimer); + } + this.paintTimer = requestAnimationFrame(() => { + this.paintTimer = undefined; + const offset = this.dragOffset; + this.helper.style.left = event.clientX + offset.offsetLeft + 'px'; + this.helper.style.top = event.clientY + offset.offsetTop + 'px'; + }); + } + + private setupHelperContainmentStyle() { + this.helperContainment = this.helper.parentElement; + if (this.option.basePosision !== 'fixed') { + this.parentOriginStylePosition = this.helperContainment.style.position; + if (window.getComputedStyle(this.helperContainment).position.match(/static/)) { + this.helperContainment.style.position = 'relative'; + } + } + } + + private cancelDragGhost(e: DragEvent) { + if (e.dataTransfer != null) { + e.dataTransfer.setData('text', ''); + } + e.dataTransfer.effectAllowed = 'move'; + if ('function' === typeof DataTransfer.prototype.setDragImage) { + e.dataTransfer.setDragImage(new Image(), 0, 0); + } else { + // ie + (e.target as HTMLElement).style.display = 'none'; + setTimeout(() => { + (e.target as HTMLElement).style.display = ''; + }); + e.stopPropagation(); + return; + } + e.stopPropagation(); + } + + private getDragOffset(event: DragEvent, el: HTMLElement, attachedParent: HTMLElement) { + // in case ancestor has transform/perspective css properies that change the viewpoint + const getViewPointFromParent = (parent) => { + if (!parent) { return null; } + const testEl = document.createElement('div'); + DDUtils.addElStyles(testEl, { + opacity: '0', + position: this.option.basePosision || DDDraggble.basePosition, + top: 0 + 'px', + left: 0 + 'px', + width: '1px', + height: '1px', + zIndex: '-999999', + }); + parent.appendChild(testEl); + const testElPosition = testEl.getBoundingClientRect(); + parent.removeChild(testEl); + return { + offsetX: testElPosition.left, + offsetY: testElPosition.top + }; + } + const targetOffset = el.getBoundingClientRect(); + const mousePositionXY = { + x: event.clientX, + y: event.clientY + }; + const transformOffset = getViewPointFromParent(attachedParent); + return { + left: targetOffset.left, + top: targetOffset.top, + offsetLeft: - mousePositionXY.x + targetOffset.left - transformOffset.offsetX, + offsetTop: - mousePositionXY.y + targetOffset.top - transformOffset.offsetY, + width: targetOffset.width, + height: targetOffset.height + }; + } + destroy() { + if (this.dragging) { + // Destroy while draggging should remove dragend listener and manally trigger + // dragend, otherwise dragEnd can't perform dragstop becasue eventResistry is + // destoryed. + this.dragEnd({} as DragEvent); + } + this.el.draggable = false; + this.el.classList.remove('ui-draggable'); + this.el.removeEventListener('dragstart', this.dragStart); + this.el = undefined; + this.helper = undefined; + this.option = undefined; + super.destroy(); + } + + ui = () => { + const containmentEl = this.el.parentElement; + const containmentRect = containmentEl.getBoundingClientRect(); + const offset = this.helper.getBoundingClientRect(); + return { + helper: [this.helper], //The object arr representing the helper that's being dragged. + position: { + top: offset.top - containmentRect.top, + left: offset.left - containmentRect.left + }, //Current CSS position of the helper as { top, left } object + offset: { top: offset.top, left: offset.left }// Current offset position of the helper as { top, left } object. + }; + } +} + + diff --git a/src/dragdrop/dd-droppable.ts b/src/dragdrop/dd-droppable.ts new file mode 100644 index 000000000..fbfeed7dd --- /dev/null +++ b/src/dragdrop/dd-droppable.ts @@ -0,0 +1,154 @@ +// dd-droppable.ts 2.0.2-dev @preserve + +/** + * https://gridstackjs.com/ + * (c) 2020 Alain Dumesny, rhlin + * gridstack.js may be freely distributed under the MIT license. +*/ +import { DDDraggble } from './dd-draggable'; +import { DDManager } from './dd-manager'; +import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; +import { DDUtils } from './dd-utils'; + +export interface DDDropableOpt { + accept?: string | ((el: HTMLElement) => boolean); + drop?: (event: DragEvent, ui) => void; + over?: (event: DragEvent, ui) => void; + out?: (event: DragEvent, ui) => void; +}; +export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt { + accept: (el: HTMLElement) => boolean; + el: HTMLElement; + option: DDDropableOpt; + private count = 0; + private dragEl: HTMLElement; + constructor(el: HTMLElement, opts: DDDropableOpt) { + super(); + this.el = el; + this.option = opts || {}; + this.init(); + } + on(event: 'drop' | 'dropover' | 'dropout', callback: (event: DragEvent) => void): void { + super.on(event, callback); + } + off(event: 'drop' | 'dropover' | 'dropout') { + super.off(event); + } + enable() { + if (!this.disabled) { return; } + super.enable(); + this.el.classList.remove('ui-droppable-disabled'); + this.el.addEventListener('dragenter', this.dragEnter); + this.el.addEventListener('dragover', this.dragOver); + this.el.addEventListener('drop', this.drop); + this.el.addEventListener('dragleave', this.dragLeave); + } + disable() { + if (this.disabled) { return; } + super.disable(); + this.el.classList.add('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); + } + updateOption(opts) { + Object.keys(opts).forEach(key => { + const value = opts[key]; + this.option[key] = value; + }); + this.setupAccept(); + } + + protected init() { + this.el.classList.add('ui-droppable'); + this.el.addEventListener('dragenter', this.dragEnter); + this.el.addEventListener('dragover', this.dragOver); + this.el.addEventListener('drop', this.drop); + this.el.addEventListener('dragleave', this.dragLeave); + this.setupAccept(); + } + + protected dragEnter = (event: DragEvent) => { + if (this.canDrop()) { + if (0 === this.count) { + this.dragEl = DDManager.dragElement.el; + this.dragEl.addEventListener('dragend', this.resetCount); + const ev = DDUtils.initEvent(event, { target: this.el, type: 'dropover' }); + if (this.option.over) { + this.option.over(ev, this.ui(DDManager.dragElement)) + } + this.triggerEvent('dropover', ev); + event.preventDefault(); + } + } + this.count++; + } + protected dragOver = (event: DragEvent) => { + if (this.canDrop()) { + event.preventDefault(); + event.stopPropagation(); + } + } + protected dragLeave = (event: DragEvent) => { + this.count--; + if (this.canDrop()) { + if (0 === this.count) { + const ev = DDUtils.initEvent(event, { target: this.el, type: 'dropout' }); + if (this.option.out) { + this.option.out(ev, this.ui(DDManager.dragElement)) + } + this.triggerEvent('dropout', ev); + } + event.preventDefault(); + } + } + protected drop = (event: DragEvent) => { + if (this.canDrop()) { + const ev = DDUtils.initEvent(event, { target: this.el, type: 'drop' }); + if (this.option.drop) { + this.option.drop(ev, this.ui(DDManager.dragElement)) + } + this.triggerEvent('drop', ev); + event.preventDefault(); + this.count = 0; + } + } + private resetCount = () => { + this.count = 0; + this.dragEl.removeEventListener('dragend', this.resetCount); + this.dragEl = undefined; + } + private canDrop() { + return DDManager.dragElement && (!this.accept || this.accept(DDManager.dragElement.el)); + } + private setupAccept() { + if (this.option.accept && typeof this.option.accept === 'string') { + this.accept = (el: HTMLElement) => { + return el.matches(this.option.accept as string) + } + } else { + this.accept = this.option.accept as ((el: HTMLElement) => boolean); + } + } + + destroy() { + 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); + } + super.destroy(); + } + + ui(ddDraggble: DDDraggble) { + return { + draggable: ddDraggble.el, + ...ddDraggble.ui() + }; + } +} + diff --git a/src/dragdrop/dd-utils.ts b/src/dragdrop/dd-utils.ts new file mode 100644 index 000000000..e4e8f7215 --- /dev/null +++ b/src/dragdrop/dd-utils.ts @@ -0,0 +1,102 @@ +// dd-utils.ts 2.0.2-dev @preserve + +/** + * https://gridstackjs.com/ + * (c) 2020 Alain Dumesny, rhlin + * gridstack.js may be freely distributed under the MIT license. +*/ +export class DDUtils { + static isEventSupportPassiveOption = ((()=>{ + let supportsPassive = false; + let passiveTest = () => { + // do nothing + }; + document.addEventListener('test', passiveTest, { + get passive() { + supportsPassive = true; + return true; + } + }); + document.removeEventListener('test', passiveTest); + return supportsPassive; + })()); + + static clone(el: HTMLElement): HTMLElement { + const node = el.cloneNode(true) as HTMLElement; + node.removeAttribute('id'); + return node; + } + + static appendTo(el: HTMLElement, parent: string | HTMLElement | Node) { + let parentNode: HTMLElement; + if (typeof parent === 'string') { + parentNode = document.querySelector(parent as string); + } else { + parentNode = parent as HTMLElement; + } + if (parentNode) { + parentNode.append(el); + } + } + static setPositionRelative(el) { + if (!(/^(?:r|a|f)/).test(window.getComputedStyle(el).position)) { + el.style.position = "relative"; + } + } + + static throttle(callback: (...args) => void, delay: number) { + let isWaiting = false; + + return (...args) => { + if (!isWaiting) { + callback(...args); + isWaiting = true; + setTimeout(() => isWaiting = false, delay); + } + } + } + static addElStyles(el: HTMLElement, styles: { [prop: string]: string | string[] }) { + if (styles instanceof Object) { + for (const s in styles) { + if (styles.hasOwnProperty(s)) { + if (Array.isArray(styles[s])) { + // support fallback value + (styles[s] as string[]).forEach(val => { + el.style[s] = val; + }); + } else { + el.style[s] = styles[s]; + } + } + } + } + } + static copyProps(dst, src, props) { + for (let i = 0; i < props.length; i++) { + const p = props[i]; + dst[p] = src[p]; + } + } + + static initEvent(e: DragEvent|MouseEvent, info: {type: string; target?: EventTarget}) { + const kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(','); + const ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY'.split(','); + const evt = {type: info.type}; + const obj = { + button: 0, + which: 0, + buttons: 1, + bubbles: true, + cancelable: true, + originEvent: e, + target: info.target? info.target : e.target + } + if (e instanceof DragEvent) { + Object.assign(obj, {dataTransfer: e.dataTransfer}); + } + DDUtils.copyProps(evt, e, kbdProps); + DDUtils.copyProps(evt, e, ptProps); + DDUtils.copyProps(evt, obj, Object.keys(obj)); + return evt as unknown as T; + } +} From 95db9d30028dced228d67c1b9e5d18623f576d46 Mon Sep 17 00:00:00 2001 From: rhlin Date: Thu, 29 Oct 2020 11:53:35 -0700 Subject: [PATCH 02/17] fix(h5dd): fix animation not cancel after dragend from https://github.com/rhlin/gridstack.js feat-h5dd-optimize --- src/dragdrop/dd-draggable.ts | 14 +++++++++++--- src/dragdrop/dd-droppable.ts | 24 +++++++++++------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/dragdrop/dd-draggable.ts b/src/dragdrop/dd-draggable.ts index 50c703217..6793d2ab7 100644 --- a/src/dragdrop/dd-draggable.ts +++ b/src/dragdrop/dd-draggable.ts @@ -145,6 +145,9 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< this.dragFollowTimer = undefined; return; } else { + if (this.paintTimer) { + cancelAnimationFrame(this.paintTimer); + } document.removeEventListener('dragover', this.dragThrottle, DDDraggble.dragEventListinerOption); this.el.removeEventListener('dragend', this.dragEnd); } @@ -210,8 +213,13 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< this.paintTimer = requestAnimationFrame(() => { this.paintTimer = undefined; const offset = this.dragOffset; - this.helper.style.left = event.clientX + offset.offsetLeft + 'px'; - this.helper.style.top = event.clientY + offset.offsetTop + 'px'; + let containmentRect = {left: 0, top: 0}; + if (this.helper.style.position === 'absolute') { + const {left, top} = this.helperContainment.getBoundingClientRect(); + containmentRect = {left, top}; + } + this.helper.style.left = event.clientX + offset.offsetLeft - containmentRect.left + 'px'; + this.helper.style.top = event.clientY + offset.offsetTop - containmentRect.top + 'px'; }); } @@ -251,7 +259,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< const testEl = document.createElement('div'); DDUtils.addElStyles(testEl, { opacity: '0', - position: this.option.basePosision || DDDraggble.basePosition, + position: 'fixed', top: 0 + 'px', left: 0 + 'px', width: '1px', diff --git a/src/dragdrop/dd-droppable.ts b/src/dragdrop/dd-droppable.ts index fbfeed7dd..6bc63a506 100644 --- a/src/dragdrop/dd-droppable.ts +++ b/src/dragdrop/dd-droppable.ts @@ -39,18 +39,12 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< super.enable(); this.el.classList.remove('ui-droppable-disabled'); this.el.addEventListener('dragenter', this.dragEnter); - this.el.addEventListener('dragover', this.dragOver); - this.el.addEventListener('drop', this.drop); - this.el.addEventListener('dragleave', this.dragLeave); } disable() { if (this.disabled) { return; } super.disable(); this.el.classList.add('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); } updateOption(opts) { Object.keys(opts).forEach(key => { @@ -63,9 +57,9 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< protected init() { this.el.classList.add('ui-droppable'); this.el.addEventListener('dragenter', this.dragEnter); - this.el.addEventListener('dragover', this.dragOver); - this.el.addEventListener('drop', this.drop); - this.el.addEventListener('dragleave', this.dragLeave); + // this.el.addEventListener('dragover', this.dragOver); + // this.el.addEventListener('drop', this.drop); + // this.el.addEventListener('dragleave', this.dragLeave); this.setupAccept(); } @@ -80,15 +74,16 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< } this.triggerEvent('dropover', ev); event.preventDefault(); + this.el.addEventListener('dragover', this.dragOver); + this.el.addEventListener('drop', this.drop); + this.el.addEventListener('dragleave', this.dragLeave); } } this.count++; } protected dragOver = (event: DragEvent) => { - if (this.canDrop()) { - event.preventDefault(); - event.stopPropagation(); - } + event.preventDefault(); + event.stopPropagation(); } protected dragLeave = (event: DragEvent) => { this.count--; @@ -99,6 +94,9 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< this.option.out(ev, this.ui(DDManager.dragElement)) } this.triggerEvent('dropout', ev); + this.el.removeEventListener('dragover', this.dragOver); + this.el.removeEventListener('drop', this.drop); + this.el.removeEventListener('dragleave', this.dragLeave); } event.preventDefault(); } From facb6acef1fbf50f59f12c8e727e34323f3f0fc9 Mon Sep 17 00:00:00 2001 From: rhlin Date: Thu, 29 Oct 2020 11:54:02 -0700 Subject: [PATCH 03/17] fix(h5dd): dragenter dragleave count nont right from https://github.com/rhlin/gridstack.js feat-h5dd-optimize --- src/dragdrop/dd-droppable.ts | 47 ++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/dragdrop/dd-droppable.ts b/src/dragdrop/dd-droppable.ts index 6bc63a506..57fd5b71c 100644 --- a/src/dragdrop/dd-droppable.ts +++ b/src/dragdrop/dd-droppable.ts @@ -39,12 +39,16 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< super.enable(); this.el.classList.remove('ui-droppable-disabled'); this.el.addEventListener('dragenter', this.dragEnter); + this.el.addEventListener('drop', this.drop); + this.el.addEventListener('dragleave', this.dragLeave); } disable() { if (this.disabled) { return; } super.disable(); this.el.classList.add('ui-droppable-disabled'); this.el.removeEventListener('dragenter', this.dragEnter); + this.el.removeEventListener('drop', this.drop); + this.el.removeEventListener('dragleave', this.dragLeave); } updateOption(opts) { Object.keys(opts).forEach(key => { @@ -57,27 +61,22 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< protected init() { this.el.classList.add('ui-droppable'); this.el.addEventListener('dragenter', this.dragEnter); - // this.el.addEventListener('dragover', this.dragOver); - // this.el.addEventListener('drop', this.drop); - // this.el.addEventListener('dragleave', this.dragLeave); + this.el.addEventListener('drop', this.drop); + this.el.addEventListener('dragleave', this.dragLeave); this.setupAccept(); } protected dragEnter = (event: DragEvent) => { - if (this.canDrop()) { - if (0 === this.count) { - this.dragEl = DDManager.dragElement.el; - this.dragEl.addEventListener('dragend', this.resetCount); - const ev = DDUtils.initEvent(event, { target: this.el, type: 'dropover' }); - if (this.option.over) { - this.option.over(ev, this.ui(DDManager.dragElement)) - } - this.triggerEvent('dropover', ev); - event.preventDefault(); - this.el.addEventListener('dragover', this.dragOver); - this.el.addEventListener('drop', this.drop); - this.el.addEventListener('dragleave', this.dragLeave); + if (0 === this.count && this.canDrop()) { + this.dragEl = DDManager.dragElement.el; + this.dragEl.addEventListener('dragend', this.resetCount); + const ev = DDUtils.initEvent(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); + event.preventDefault(); } this.count++; } @@ -87,17 +86,13 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< } protected dragLeave = (event: DragEvent) => { this.count--; - if (this.canDrop()) { - if (0 === this.count) { - const ev = DDUtils.initEvent(event, { target: this.el, type: 'dropout' }); - if (this.option.out) { - this.option.out(ev, this.ui(DDManager.dragElement)) - } - this.triggerEvent('dropout', ev); - this.el.removeEventListener('dragover', this.dragOver); - this.el.removeEventListener('drop', this.drop); - this.el.removeEventListener('dragleave', this.dragLeave); + if (0 === this.count && this.canDrop()) { + this.el.removeEventListener('dragover', this.dragOver); + const ev = DDUtils.initEvent(event, { target: this.el, type: 'dropout' }); + if (this.option.out) { + this.option.out(ev, this.ui(DDManager.dragElement)) } + this.triggerEvent('dropout', ev); event.preventDefault(); } } From d4d6845fd59689696dcb4913f9532a0c2eb7f82c Mon Sep 17 00:00:00 2001 From: rhlin Date: Thu, 29 Oct 2020 11:54:27 -0700 Subject: [PATCH 04/17] feat(h5dd): optimization for dragenter dragleave, mouseover mouseout from https://github.com/rhlin/gridstack.js feat-h5dd-optimize --- src/dragdrop/dd-draggable.ts | 16 +- src/dragdrop/dd-droppable.ts | 63 +++++--- src/dragdrop/dd-resizable.ts | 281 +++++++++++++++++++++++++++++++++++ src/dragdrop/dd-utils.ts | 10 +- 4 files changed, 334 insertions(+), 36 deletions(-) create mode 100644 src/dragdrop/dd-resizable.ts diff --git a/src/dragdrop/dd-draggable.ts b/src/dragdrop/dd-draggable.ts index 6793d2ab7..689224a77 100644 --- a/src/dragdrop/dd-draggable.ts +++ b/src/dragdrop/dd-draggable.ts @@ -11,7 +11,7 @@ import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; export interface DDDraggbleOpt { appendTo?: string | HTMLElement; - containment?: string | HTMLElement; + containment?: string | HTMLElement; // TODO: not impleament yet handle?: string; revert?: string | boolean | unknown; // TODO: not impleament yet scroll?: boolean; // nature support by HTML5 drag drop, can't be switch to off actually @@ -22,7 +22,7 @@ export interface DDDraggbleOpt { drag?: (event?, ui?) => void; }; export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt { - static basePosition: 'fixed'| 'absolute'= 'absolute'; + static basePosition: 'fixed' | 'absolute' = 'absolute'; static dragEventListinerOption = DDUtils.isEventSupportPassiveOption ? { capture: true, passive: true } : true; static originStyleProp = ['transition', 'pointerEvents', 'position', 'left', 'top', 'opacity', 'zIndex', 'width', 'height', 'willChange']; @@ -195,8 +195,10 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< this.helper.style.position = this.option.basePosision || DDDraggble.basePosition; this.helper.style.zIndex = '1000'; setTimeout(() => { - this.helper.style.transition = null; // recover animation - }, 100); + if (this.helper) { + this.helper.style.transition = null; // recover animation + } + }, 0); } private removeHelperStyle() { @@ -213,10 +215,10 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< this.paintTimer = requestAnimationFrame(() => { this.paintTimer = undefined; const offset = this.dragOffset; - let containmentRect = {left: 0, top: 0}; + let containmentRect = { left: 0, top: 0 }; if (this.helper.style.position === 'absolute') { - const {left, top} = this.helperContainment.getBoundingClientRect(); - containmentRect = {left, top}; + const { left, top } = this.helperContainment.getBoundingClientRect(); + containmentRect = { left, top }; } this.helper.style.left = event.clientX + offset.offsetLeft - containmentRect.left + 'px'; this.helper.style.top = event.clientY + offset.offsetTop - containmentRect.top + 'px'; diff --git a/src/dragdrop/dd-droppable.ts b/src/dragdrop/dd-droppable.ts index 57fd5b71c..dcf536335 100644 --- a/src/dragdrop/dd-droppable.ts +++ b/src/dragdrop/dd-droppable.ts @@ -20,8 +20,8 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< accept: (el: HTMLElement) => boolean; el: HTMLElement; option: DDDropableOpt; - private count = 0; - private dragEl: HTMLElement; + private acceptable: boolean = null; + private style; constructor(el: HTMLElement, opts: DDDropableOpt) { super(); this.el = el; @@ -39,16 +39,12 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< super.enable(); this.el.classList.remove('ui-droppable-disabled'); this.el.addEventListener('dragenter', this.dragEnter); - this.el.addEventListener('drop', this.drop); - this.el.addEventListener('dragleave', this.dragLeave); } disable() { if (this.disabled) { return; } super.disable(); this.el.classList.add('ui-droppable-disabled'); this.el.removeEventListener('dragenter', this.dragEnter); - this.el.removeEventListener('drop', this.drop); - this.el.removeEventListener('dragleave', this.dragLeave); } updateOption(opts) { Object.keys(opts).forEach(key => { @@ -61,57 +57,66 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< protected init() { this.el.classList.add('ui-droppable'); this.el.addEventListener('dragenter', this.dragEnter); - this.el.addEventListener('drop', this.drop); - this.el.addEventListener('dragleave', this.dragLeave); + this.setupAccept(); + this.createStyleSheet(); } protected dragEnter = (event: DragEvent) => { - if (0 === this.count && this.canDrop()) { - this.dragEl = DDManager.dragElement.el; - this.dragEl.addEventListener('dragend', this.resetCount); + this.el.removeEventListener('dragenter', this.dragEnter); + this.acceptable = this.canDrop(); + if (this.acceptable) { + event.preventDefault(); const ev = DDUtils.initEvent(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); - event.preventDefault(); + this.el.addEventListener('drop', this.drop); } - this.count++; + this.el.classList.add('ui-droppable-over'); + this.el.addEventListener('dragleave', this.dragLeave); + } protected dragOver = (event: DragEvent) => { event.preventDefault(); event.stopPropagation(); } protected dragLeave = (event: DragEvent) => { - this.count--; - if (0 === this.count && this.canDrop()) { + if (this.el.contains(event.relatedTarget as HTMLElement)) { return; }; + this.el.removeEventListener('dragleave', this.dragLeave); + this.el.classList.remove('ui-droppable-over'); + if (this.acceptable) { + event.preventDefault(); this.el.removeEventListener('dragover', this.dragOver); + this.el.removeEventListener('drop', this.drop); const ev = DDUtils.initEvent(event, { target: this.el, type: 'dropout' }); if (this.option.out) { this.option.out(ev, this.ui(DDManager.dragElement)) } this.triggerEvent('dropout', ev); - event.preventDefault(); } + this.el.addEventListener('dragenter', this.dragEnter); } + protected drop = (event: DragEvent) => { - if (this.canDrop()) { + if (this.acceptable) { + event.preventDefault(); const ev = DDUtils.initEvent(event, { target: this.el, type: 'drop' }); if (this.option.drop) { this.option.drop(ev, this.ui(DDManager.dragElement)) } this.triggerEvent('drop', ev); - event.preventDefault(); - this.count = 0; + this.dragLeave({ + ...ev, + relatedTarget: null, + preventDefault: () => { + // do nothing + } + }); } } - private resetCount = () => { - this.count = 0; - this.dragEl.removeEventListener('dragend', this.resetCount); - this.dragEl = undefined; - } private canDrop() { return DDManager.dragElement && (!this.accept || this.accept(DDManager.dragElement.el)); } @@ -125,6 +130,16 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< } } + private createStyleSheet() { + const content = `.ui-droppable.ui-droppable-over > *:not(.ui-droppable) {pointer-events: none;}`; + this.style = document.createElement('style'); + this.style.innerText = content; + this.el.appendChild(this.style); + } + private removeStyleSheet() { + this.el.removeChild(this.style); + } + destroy() { this.el.classList.remove('ui-droppable'); if (this.disabled) { diff --git a/src/dragdrop/dd-resizable.ts b/src/dragdrop/dd-resizable.ts new file mode 100644 index 000000000..30d726d02 --- /dev/null +++ b/src/dragdrop/dd-resizable.ts @@ -0,0 +1,281 @@ +// dd-resizable.ts 2.0.2-dev @preserve + +/** + * https://gridstackjs.com/ + * (c) 2020 Alain Dumesny, rhlin + * gridstack.js may be freely distributed under the MIT license. +*/ +import { DDResizableHandle } from './dd-resizable-handle'; +import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; +import { DDUtils } from './dd-utils'; +export interface DDResizableOpt { + autoHide?: boolean; + handles?: string; + maxHeight?: number; + maxWidth?: number; + minHeight?: number; + minWidth?: number; + basePosision?: 'fixed' | 'absolute'; + start?: (event: MouseEvent, ui) => void; + stop?: (event: MouseEvent, ui) => void; + resize?: (event: MouseEvent, ui) => void; +} +export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt { + static originStyleProp = ['width', 'height', 'position', 'left', 'top', 'opacity', 'zIndex']; + el: HTMLElement; + option: DDResizableOpt; + handlers: DDResizableHandle[]; + helper: HTMLElement; + originalRect; + temporalRect; + private startEvent: MouseEvent; + private elOriginStyle; + private parentOriginStylePosition; + constructor(el: HTMLElement, opts: DDResizableOpt) { + super(); + this.el = el; + this.option = opts || {}; + this.init(); + } + on(event: 'resizestart' | 'resize' | 'resizestop', callback: (event: DragEvent) => void): void { + super.on(event, callback); + } + off(event: 'resizestart' | 'resize' | 'resizestop') { + super.off(event); + } + enable() { + if (!this.disabled) { return; } + super.enable(); + this.el.classList.remove('ui-resizable-disabled'); + } + disable() { + if (this.disabled) { return; } + super.disable(); + this.el.classList.add('ui-resizable-disabled'); + } + updateOption(opts: DDResizableOpt) { + let updateHandles = false; + let updateAutoHide = false; + if (opts.handles !== this.option.handles) { + updateHandles = true; + } + if (opts.autoHide !== this.option.autoHide) { + updateAutoHide = true; + } + Object.keys(opts).forEach(key => { + const value = opts[key]; + this.option[key] = value; + }); + if (updateHandles) { + this.removeHandlers(); + this.setupHandlers(); + } + if (updateAutoHide) { + this.setupAutoHide(); + } + } + + protected init() { + this.el.classList.add('ui-resizable'); + this.setupAutoHide(); + this.setupHandlers(); + } + + protected setupAutoHide() { + if (this.option.autoHide) { + this.el.classList.add('ui-resizable-autohide'); + // use mouseover/mouseout instead of mouseenter mouseleave to get better performance; + this.el.addEventListener('mouseover', this.showHandlers); + this.el.addEventListener('mouseout', this.hideHandlers); + } else { + this.el.classList.remove('ui-resizable-autohide'); + this.el.removeEventListener('mouseover', this.showHandlers); + this.el.removeEventListener('mouseout', this.hideHandlers); + } + } + + protected showHandlers = () => { + this.el.classList.remove('ui-resizable-autohide'); + } + + protected hideHandlers = () => { + this.el.classList.add('ui-resizable-autohide'); + } + + protected setupHandlers() { + let handlerDirection = this.option.handles || 'e,s,se'; + if (handlerDirection === 'all') { + handlerDirection = 'n,e,s,w,se,sw,ne,nw'; + } + this.handlers = handlerDirection.split(',') + .map(dir => dir.trim()) + .map(dir => new DDResizableHandle(this.el, dir, { + start: (event: MouseEvent) => { + this.resizeStart(event); + }, + stop: (event: MouseEvent) => { + this.resizeStop(event); + }, + move: (event: MouseEvent) => { + this.resizing(event, dir); + } + })); + } + + protected resizeStart(event: MouseEvent) { + this.originalRect = this.el.getBoundingClientRect(); + this.startEvent = event; + this.setupHelper(); + this.applyChange(); + const ev = DDUtils.initEvent(event, { type: 'resizestart', target: this.el }); + if (this.option.start) { + this.option.start(ev, this.ui()); + } + this.triggerEvent('resizestart', ev); + } + + protected resizing(event: MouseEvent, dir: string) { + this.temporalRect = this.getChange(event, dir); + this.applyChange(); + const ev = DDUtils.initEvent(event, { type: 'resize', target: this.el }); + if (this.option.resize) { + this.option.resize(ev, this.ui()); + } + this.triggerEvent('resize', ev); + } + + protected resizeStop(event: MouseEvent) { + const ev = DDUtils.initEvent(event, { type: 'resizestop', target: this.el }); + if (this.option.stop) { + this.option.stop(ev, this.ui()); + } + this.triggerEvent('resizestop', ev); + this.cleanHelper(); + this.startEvent = undefined; + this.originalRect = undefined; + this.temporalRect = undefined; + } + + private setupHelper() { + this.elOriginStyle = DDResizable.originStyleProp.map(prop => this.el.style[prop]); + this.parentOriginStylePosition = this.el.parentElement.style.position; + if (window.getComputedStyle(this.el.parentElement).position.match(/static/)) { + this.el.parentElement.style.position = 'relative'; + } + this.el.style.position = this.option.basePosision || 'absolute'; // or 'fixed' + this.el.style.opacity = '0.8'; + this.el.style.zIndex = '1000'; + } + private cleanHelper() { + DDResizable.originStyleProp.forEach(prop => { + this.el.style[prop] = this.elOriginStyle[prop] || null; + }); + this.el.parentElement.style.position = this.parentOriginStylePosition || null; + } + private getChange(event: MouseEvent, dir: string) { + const oEvent = this.startEvent; + const newRect = { + width: this.originalRect.width, + height: this.originalRect.height, + left: this.originalRect.left, + top: this.originalRect.top + }; + const offsetH = event.clientX - oEvent.clientX; + const offsetV = event.clientY - oEvent.clientY; + + if (dir.indexOf('e') > -1) { + newRect.width += event.clientX - oEvent.clientX; + } + if (dir.indexOf('s') > -1) { + newRect.height += event.clientY - oEvent.clientY; + } + if (dir.indexOf('w') > -1) { + newRect.width -= offsetH; + newRect.left += offsetH; + } + if (dir.indexOf('n') > -1) { + newRect.height -= offsetV; + newRect.top += offsetV + } + const reshape = this.getReShapeSize(newRect.width, newRect.height); + if (newRect.width !== reshape.width) { + if (dir.indexOf('w') > -1) { + newRect.left += reshape.width - newRect.width; + } + newRect.width = reshape.width; + } + if (newRect.height !== reshape.height) { + if (dir.indexOf('n') > -1) { + newRect.top += reshape.height - newRect.height; + } + newRect.height = reshape.height; + } + return newRect; + } + + private getReShapeSize(oWidth, oHeight) { + const maxWidth = this.option.maxWidth || oWidth; + const minWidth = this.option.minWidth || oWidth; + const maxHeight = this.option.maxHeight || oHeight; + const minHeight = this.option.minHeight || oHeight; + const width = Math.min(maxWidth, Math.max(minWidth, oWidth)); + const height = Math.min(maxHeight, Math.max(minHeight, oHeight)); + return { width, height }; + } + + private applyChange() { + let containmentRect = { left: 0, top: 0, width: 0, height: 0 }; + if (this.el.style.position === 'absolute') { + const containmentEl = this.el.parentElement; + const { left, top } = containmentEl.getBoundingClientRect(); + containmentRect = { left, top, width: 0, height: 0 }; + } + Object.keys(this.temporalRect || this.originalRect).forEach(key => { + const value = this.temporalRect[key]; + this.el.style[key] = value - containmentRect[key] + 'px'; + }); + } + + protected removeHandlers() { + this.handlers.forEach(handle => handle.destory()); + this.handlers = undefined; + } + + destory() { + this.removeHandlers(); + if (this.option.autoHide) { + this.el.removeEventListener('mouseover', this.showHandlers); + this.el.removeEventListener('mouseout', this.hideHandlers); + } + this.el.classList.remove('ui-resizable'); + this.el = undefined; + super.destroy(); + } + + ui = () => { + const containmentEl = this.el.parentElement; + const containmentRect = containmentEl.getBoundingClientRect(); + const rect = this.temporalRect || this.originalRect; + return { + element: [this.el], // The object representing the element to be resized + helper: [], // TODO: not support yet // The object representing the helper that's being resized + originalElement: [this.el],// we dont wrap here, so simplify as this.el //The object representing the original element before it is wrapped + originalPosition: { + left: this.originalRect.left - containmentRect.left, + top: this.originalRect.top - containmentRect.top + }, // The position represented as { left, top } before the resizable is resized + originalSize: { + width: this.originalRect.width, + height: this.originalRect.height + },// The size represented as { width, height } before the resizable is resized + position: { + left: rect.left - containmentRect.left, + top: rect.top - containmentRect.top + }, // The current position represented as { left, top } + size: { + width: rect.width, + height: rect.height + } // The current size represented as { width, height } + }; + } +} diff --git a/src/dragdrop/dd-utils.ts b/src/dragdrop/dd-utils.ts index e4e8f7215..afc9c7bff 100644 --- a/src/dragdrop/dd-utils.ts +++ b/src/dragdrop/dd-utils.ts @@ -6,7 +6,7 @@ * gridstack.js may be freely distributed under the MIT license. */ export class DDUtils { - static isEventSupportPassiveOption = ((()=>{ + static isEventSupportPassiveOption = ((() => { let supportsPassive = false; let passiveTest = () => { // do nothing @@ -78,10 +78,10 @@ export class DDUtils { } } - static initEvent(e: DragEvent|MouseEvent, info: {type: string; target?: EventTarget}) { + static initEvent(e: DragEvent | MouseEvent, info: { type: string; target?: EventTarget }) { const kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(','); const ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY'.split(','); - const evt = {type: info.type}; + const evt = { type: info.type }; const obj = { button: 0, which: 0, @@ -89,10 +89,10 @@ export class DDUtils { bubbles: true, cancelable: true, originEvent: e, - target: info.target? info.target : e.target + target: info.target ? info.target : e.target } if (e instanceof DragEvent) { - Object.assign(obj, {dataTransfer: e.dataTransfer}); + Object.assign(obj, { dataTransfer: e.dataTransfer }); } DDUtils.copyProps(evt, e, kbdProps); DDUtils.copyProps(evt, e, ptProps); From d3785f336b8c1955700d782fa2ff395102e69bdc Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Thu, 29 Oct 2020 12:06:34 -0700 Subject: [PATCH 05/17] sample h5 demos still way too slow for html5 vs mouse event JQ. Will have to roll our own. --- demo/two-h5.html | 124 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 demo/two-h5.html diff --git a/demo/two-h5.html b/demo/two-h5.html new file mode 100644 index 000000000..25a259473 --- /dev/null +++ b/demo/two-h5.html @@ -0,0 +1,124 @@ + + + + + + + Two grids demo + + + + + + + + + + +
+

Two grids demo

+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+ + + + From 44fa7c127c37c79bbf4ed10a13247333cccdc9ff Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sun, 1 Nov 2020 20:58:26 -0800 Subject: [PATCH 06/17] H5: fixed resizing performance * make sure to add .ui-resizable-resizing class while resizing, else we fall into CSS 300ms animation (grid default to animate) resize is now fast like dragging * removed drag/resize delay timeout - not needed and Util has routine anyway if we decide later * fixed typos throughout H5 code * changed credit order to give credit where credit is due (rhlin) --- src/dragdrop/dd-base-impl.ts | 41 +++++++++ src/dragdrop/dd-draggable.ts | 47 +++++----- src/dragdrop/dd-droppable.ts | 19 ++-- src/dragdrop/dd-element.ts | 92 +++++++++++++++++++ src/dragdrop/dd-manager.ts | 10 ++ src/dragdrop/dd-resizable-handle.ts | 105 +++++++++++++++++++++ src/dragdrop/dd-resizable.ts | 17 ++-- src/dragdrop/dd-utils.ts | 14 +-- src/dragdrop/gridstack-dd-native.ts | 137 ++++++++++++++++++++++++++++ src/utils.ts | 2 +- 10 files changed, 427 insertions(+), 57 deletions(-) create mode 100644 src/dragdrop/dd-base-impl.ts create mode 100644 src/dragdrop/dd-element.ts create mode 100644 src/dragdrop/dd-manager.ts create mode 100644 src/dragdrop/dd-resizable-handle.ts create mode 100644 src/dragdrop/gridstack-dd-native.ts diff --git a/src/dragdrop/dd-base-impl.ts b/src/dragdrop/dd-base-impl.ts new file mode 100644 index 000000000..9dee8c590 --- /dev/null +++ b/src/dragdrop/dd-base-impl.ts @@ -0,0 +1,41 @@ +// dd-base-impl.ts 2.0.2-dev @preserve +/** + * https://gridstackjs.com/ + * (c) 2020 rhlin, Alain Dumesny + * gridstack.js may be freely distributed under the MIT license. +*/ +export type EventCallback = (event: Event) => boolean|void; +export abstract class DDBaseImplement { + disabled = false; + private eventRegister: { + [eventName: string]: EventCallback; + } = {}; + on(event: string, callback: EventCallback): void { + this.eventRegister[event] = callback; + } + off(event: string) { + delete this.eventRegister[event]; + } + enable(): void { + this.disabled = false; + } + disable(): void { + this.disabled = true; + } + destroy() { + this.eventRegister = undefined; + } + triggerEvent(eventName: string, event: Event): boolean|void { + if (this.disabled) { return; } + if (!this.eventRegister) {return; } // used when destroy before triggerEvent fire + if (this.eventRegister[eventName]) { + return this.eventRegister[eventName](event); + } + } +} + +export interface HTMLElementExtendOpt { + el: HTMLElement; + option: T; + updateOption(T): void; +} diff --git a/src/dragdrop/dd-draggable.ts b/src/dragdrop/dd-draggable.ts index 689224a77..481c50b64 100644 --- a/src/dragdrop/dd-draggable.ts +++ b/src/dragdrop/dd-draggable.ts @@ -1,34 +1,33 @@ // dd-draggable.ts 2.0.2-dev @preserve - /** * https://gridstackjs.com/ - * (c) 2020 Alain Dumesny, rhlin + * (c) 2020 rhlin, Alain Dumesny * gridstack.js may be freely distributed under the MIT license. */ import { DDManager } from './dd-manager'; import { DDUtils } from './dd-utils'; import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; -export interface DDDraggbleOpt { +export interface DDDraggableOpt { appendTo?: string | HTMLElement; - containment?: string | HTMLElement; // TODO: not impleament yet + containment?: string | HTMLElement; // TODO: not implemented yet handle?: string; - revert?: string | boolean | unknown; // TODO: not impleament yet + revert?: string | boolean | unknown; // TODO: not implemented yet scroll?: boolean; // nature support by HTML5 drag drop, can't be switch to off actually helper?: string | ((event: Event) => HTMLElement); - basePosision?: 'fixed' | 'absolute'; + basePosition?: 'fixed' | 'absolute'; start?: (event?, ui?) => void; stop?: (event?, ui?) => void; drag?: (event?, ui?) => void; }; -export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt { +export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt { static basePosition: 'fixed' | 'absolute' = 'absolute'; - static dragEventListinerOption = DDUtils.isEventSupportPassiveOption ? { capture: true, passive: true } : true; + static dragEventListenerOption = DDUtils.isEventSupportPassiveOption ? { capture: true, passive: true } : true; static originStyleProp = ['transition', 'pointerEvents', 'position', 'left', 'top', 'opacity', 'zIndex', 'width', 'height', 'willChange']; el: HTMLElement; helper: HTMLElement; - option: DDDraggbleOpt; + option: DDDraggableOpt; dragOffset: { left: number; top: number; @@ -45,7 +44,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< parentOriginStylePosition: string; helperContainment: HTMLElement; - constructor(el: HTMLElement, option: DDDraggbleOpt) { + constructor(el: HTMLElement, option: DDDraggableOpt) { super(); this.el = el; this.option = option || {}; @@ -84,7 +83,6 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< this.el.classList.add('ui-draggable'); this.el.addEventListener('mousedown', this.mouseDown); this.el.addEventListener('dragstart', this.dragStart); - this.dragThrottle = DDUtils.throttle(this.drag, 100); } protected mouseDown = (event: MouseEvent) => { @@ -119,17 +117,16 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< protected setupDragFollowNodeNNotifyStart(ev) { this.setupHelperStyle(); - document.addEventListener('dragover', this.dragThrottle, DDDraggble.dragEventListinerOption); + document.addEventListener('dragover', this.drag, DDDraggable.dragEventListenerOption); this.el.addEventListener('dragend', this.dragEnd); if (this.option.start) { this.option.start(ev, this.ui()); } - this.triggerEvent('dragstart', ev); this.dragging = true; this.helper.classList.add('ui-draggable-dragging'); + this.triggerEvent('dragstart', ev); } - protected dragThrottle: (event: DragEvent) => void; protected drag = (event: DragEvent) => { this.dragFollow(event); const ev = DDUtils.initEvent(event, { target: this.el, type: 'drag' }); @@ -148,7 +145,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< if (this.paintTimer) { cancelAnimationFrame(this.paintTimer); } - document.removeEventListener('dragover', this.dragThrottle, DDDraggble.dragEventListinerOption); + document.removeEventListener('dragover', this.drag, DDDraggable.dragEventListenerOption); this.el.removeEventListener('dragend', this.dragEnd); } this.dragging = false; @@ -181,7 +178,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< : this.option.appendTo)); } if (helper === this.el) { - this.dragElementOriginStyle = DDDraggble.originStyleProp.map(prop => this.el.style[prop]); + this.dragElementOriginStyle = DDDraggable.originStyleProp.map(prop => this.el.style[prop]); } return helper; } @@ -191,8 +188,8 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< this.helper.style.width = this.dragOffset.width + 'px'; this.helper.style.height = this.dragOffset.height + 'px'; this.helper.style['willChange'] = 'left, top'; - this.helper.style.transition = 'none'; // show up instancely - this.helper.style.position = this.option.basePosision || DDDraggble.basePosition; + this.helper.style.transition = 'none'; // show up instantly + this.helper.style.position = this.option.basePosition || DDDraggable.basePosition; this.helper.style.zIndex = '1000'; setTimeout(() => { if (this.helper) { @@ -202,7 +199,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< } private removeHelperStyle() { - DDDraggble.originStyleProp.forEach(prop => { + DDDraggable.originStyleProp.forEach(prop => { this.helper.style[prop] = this.dragElementOriginStyle[prop] || null; }); this.dragElementOriginStyle = undefined; @@ -227,7 +224,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< private setupHelperContainmentStyle() { this.helperContainment = this.helper.parentElement; - if (this.option.basePosision !== 'fixed') { + if (this.option.basePosition !== 'fixed') { this.parentOriginStylePosition = this.helperContainment.style.position; if (window.getComputedStyle(this.helperContainment).position.match(/static/)) { this.helperContainment.style.position = 'relative'; @@ -255,7 +252,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< } private getDragOffset(event: DragEvent, el: HTMLElement, attachedParent: HTMLElement) { - // in case ancestor has transform/perspective css properies that change the viewpoint + // in case ancestor has transform/perspective css properties that change the viewpoint const getViewPointFromParent = (parent) => { if (!parent) { return null; } const testEl = document.createElement('div'); @@ -293,9 +290,9 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< } destroy() { if (this.dragging) { - // Destroy while draggging should remove dragend listener and manally trigger - // dragend, otherwise dragEnd can't perform dragstop becasue eventResistry is - // destoryed. + // Destroy while dragging should remove dragend listener and manually trigger + // dragend, otherwise dragEnd can't perform dragstop because eventRegistry is + // destroyed. this.dragEnd({} as DragEvent); } this.el.draggable = false; @@ -317,7 +314,7 @@ export class DDDraggble extends DDBaseImplement implements HTMLElementExtendOpt< top: offset.top - containmentRect.top, left: offset.left - containmentRect.left }, //Current CSS position of the helper as { top, left } object - offset: { top: offset.top, left: offset.left }// Current offset position of the helper as { top, left } object. + offset: { top: offset.top, left: offset.left } // Current offset position of the helper as { top, left } object. }; } } diff --git a/src/dragdrop/dd-droppable.ts b/src/dragdrop/dd-droppable.ts index dcf536335..8cf7ba725 100644 --- a/src/dragdrop/dd-droppable.ts +++ b/src/dragdrop/dd-droppable.ts @@ -1,28 +1,27 @@ // dd-droppable.ts 2.0.2-dev @preserve - /** * https://gridstackjs.com/ - * (c) 2020 Alain Dumesny, rhlin + * (c) 2020 rhlin, Alain Dumesny * gridstack.js may be freely distributed under the MIT license. */ -import { DDDraggble } from './dd-draggable'; +import { DDDraggable } from './dd-draggable'; import { DDManager } from './dd-manager'; import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; import { DDUtils } from './dd-utils'; -export interface DDDropableOpt { +export interface DDDroppableOpt { accept?: string | ((el: HTMLElement) => boolean); drop?: (event: DragEvent, ui) => void; over?: (event: DragEvent, ui) => void; out?: (event: DragEvent, ui) => void; }; -export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt { +export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt { accept: (el: HTMLElement) => boolean; el: HTMLElement; - option: DDDropableOpt; + option: DDDroppableOpt; private acceptable: boolean = null; private style; - constructor(el: HTMLElement, opts: DDDropableOpt) { + constructor(el: HTMLElement, opts: DDDroppableOpt) { super(); this.el = el; this.option = opts || {}; @@ -152,10 +151,10 @@ export class DDDropable extends DDBaseImplement implements HTMLElementExtendOpt< super.destroy(); } - ui(ddDraggble: DDDraggble) { + ui(DDDraggable: DDDraggable) { return { - draggable: ddDraggble.el, - ...ddDraggble.ui() + draggable: DDDraggable.el, + ...DDDraggable.ui() }; } } diff --git a/src/dragdrop/dd-element.ts b/src/dragdrop/dd-element.ts new file mode 100644 index 000000000..b730b4e63 --- /dev/null +++ b/src/dragdrop/dd-element.ts @@ -0,0 +1,92 @@ +// dd-elements.ts 2.0.2-dev @preserve +/** + * https://gridstackjs.com/ + * (c) 2020 rhlin, Alain Dumesny + * gridstack.js may be freely distributed under the MIT license. +*/ +import { DDResizable, DDResizableOpt } from './dd-resizable'; +import { GridItemHTMLElement } from './../types'; +import { DDDraggable, DDDraggableOpt } from './dd-draggable'; +import { DDDroppable, DDDroppableOpt } from './dd-droppable'; +export interface DDElementHost extends GridItemHTMLElement { + ddElement?: DDElement; +} +export class DDElement { + static init(el) { + el.ddElement = new DDElement(el); + return el.ddElement; + } + el: DDElementHost; + ddDraggable?: DDDraggable; + ddDroppable?: DDDroppable; + ddResizable?: DDResizable; + constructor(el: DDElementHost) { + this.el = el; + } + on(eventName: string, callback: (event: MouseEvent) => void) { + if (this.ddDraggable && ['drag', 'dragstart', 'dragstop'].indexOf(eventName) > -1) { + this.ddDraggable.on(eventName as 'drag' | 'dragstart' | 'dragstop', callback); + return; + } + if (this.ddDroppable && ['drop', 'dropover', 'dropout'].indexOf(eventName) > -1) { + this.ddDroppable.on(eventName as 'drop' | 'dropover' | 'dropout', callback); + return; + } + if (this.ddResizable && ['resizestart', 'resize', 'resizestop'].indexOf(eventName) > -1) { + this.ddResizable.on(eventName as 'resizestart' | 'resize' | 'resizestop', callback); + return; + } + return; + } + off(eventName: string) { + if (this.ddDraggable && ['drag', 'dragstart', 'dragstop'].indexOf(eventName) > -1) { + this.ddDraggable.off(eventName as 'drag' | 'dragstart' | 'dragstop'); + return; + } + if (this.ddDroppable && ['drop', 'dropover', 'dropout'].indexOf(eventName) > -1) { + this.ddDroppable.off(eventName as 'drop' | 'dropover' | 'dropout'); + return; + } + if (this.ddResizable && ['resizestart', 'resize', 'resizestop'].indexOf(eventName) > -1) { + this.ddResizable.off(eventName as 'resizestart' | 'resize' | 'resizestop'); + return; + } + return; + } + setupDraggable(opts: DDDraggableOpt) { + if (!this.ddDraggable) { + this.ddDraggable = new DDDraggable(this.el, opts); + } else { + this.ddDraggable.updateOption(opts); + } + } + setupResizable(opts: DDResizableOpt) { + if (!this.ddResizable) { + this.ddResizable = new DDResizable(this.el, opts); + } else { + this.ddResizable.updateOption(opts); + } + } + cleanDraggable() { + if (!this.ddDraggable) { return; } + this.ddDraggable.destroy(); + this.ddDraggable = undefined; + } + setupDroppable(opts: DDDroppableOpt) { + if (!this.ddDroppable) { + this.ddDroppable = new DDDroppable(this.el, opts); + } else { + this.ddDroppable.updateOption(opts); + } + } + cleanDroppable() { + if (!this.ddDroppable) { return; } + this.ddDroppable.destroy(); + this.ddDroppable = undefined; + } + cleanResizable() { + if (!this.cleanResizable) { return; } + this.ddResizable.destroy(); + this.ddResizable = undefined; + } +} diff --git a/src/dragdrop/dd-manager.ts b/src/dragdrop/dd-manager.ts new file mode 100644 index 000000000..a97a92344 --- /dev/null +++ b/src/dragdrop/dd-manager.ts @@ -0,0 +1,10 @@ +// dd-manager.ts 2.0.2-dev @preserve +/** + * https://gridstackjs.com/ + * (c) 2020 rhlin, Alain Dumesny + * gridstack.js may be freely distributed under the MIT license. +*/ +import { DDDraggable } from './dd-draggable'; +export class DDManager { + static dragElement: DDDraggable; +} diff --git a/src/dragdrop/dd-resizable-handle.ts b/src/dragdrop/dd-resizable-handle.ts new file mode 100644 index 000000000..b99f3cc67 --- /dev/null +++ b/src/dragdrop/dd-resizable-handle.ts @@ -0,0 +1,105 @@ +// dd-resizable-handle.ts 2.0.2-dev @preserve +/** + * https://gridstackjs.com/ + * (c) 2020 rhlin, Alain Dumesny + * gridstack.js may be freely distributed under the MIT license. +*/ +export interface DDResizableHandleOpt { + start?: (event) => void; + move?: (event) => void; + stop?: (event) => void; +} +export class DDResizableHandle { + static prefix = 'ui-resizable-'; + el: HTMLElement; + host: HTMLElement; + option: DDResizableHandleOpt; + dir: string; + private mouseMoving = false; + private started = false; + private mouseDownEvent: MouseEvent; + constructor(host: HTMLElement, direction: string, option: DDResizableHandleOpt) { + this.host = host; + this.dir = direction; + this.option = option; + this.init(); + } + + init() { + const el = document.createElement('div'); + el.classList.add('ui-resizable-handle'); + el.classList.add(`${DDResizableHandle.prefix}${this.dir}`); + el.style.zIndex = '100'; + el.style.userSelect = 'none'; + this.el = el; + this.host.appendChild(this.el); + this.el.addEventListener('mousedown', this.mouseDown); + } + + protected mouseDown = (event: MouseEvent) => { + this.mouseDownEvent = event; + setTimeout(() => { + document.addEventListener('mousemove', this.mouseMove, true); + document.addEventListener('mouseup', this.mouseUp); + setTimeout(() => { + if (!this.mouseMoving) { + document.removeEventListener('mousemove', this.mouseMove, true); + document.removeEventListener('mouseup', this.mouseUp); + this.mouseDownEvent = undefined; + } + }, 300); + }, 100); + } + + protected mouseMove = (event: MouseEvent) => { + if (!this.started && !this.mouseMoving) { + if (this.hasMoved(event, this.mouseDownEvent)) { + this.mouseMoving = true; + this.triggerEvent('start', this.mouseDownEvent); + this.started = true; + } + } + if (this.started) { + this.triggerEvent('move', event); + } + } + + protected mouseUp = (event: MouseEvent) => { + if (this.mouseMoving) { + this.triggerEvent('stop', event); + } + document.removeEventListener('mousemove', this.mouseMove, true); + document.removeEventListener('mouseup', this.mouseUp); + this.mouseMoving = false; + this.started = false; + this.mouseDownEvent = undefined; + } + + private hasMoved(event: MouseEvent, oEvent: MouseEvent) { + const { clientX, clientY } = event; + const { clientX: oClientX, clientY: oClientY } = oEvent; + return ( + Math.abs(clientX - oClientX) > 1 + || Math.abs(clientY - oClientY) > 1 + ); + } + + show() { + this.el.style.display = 'block'; + } + + hide() { + this.el.style.display = 'none'; + } + + destroy() { + this.host.removeChild(this.el); + } + + triggerEvent(name: string, event: MouseEvent) { + if (this.option[name]) { + this.option[name](event); + } + } + +} diff --git a/src/dragdrop/dd-resizable.ts b/src/dragdrop/dd-resizable.ts index 30d726d02..6d29733ee 100644 --- a/src/dragdrop/dd-resizable.ts +++ b/src/dragdrop/dd-resizable.ts @@ -1,8 +1,7 @@ // dd-resizable.ts 2.0.2-dev @preserve - /** * https://gridstackjs.com/ - * (c) 2020 Alain Dumesny, rhlin + * (c) 2020 rhlin, Alain Dumesny * gridstack.js may be freely distributed under the MIT license. */ import { DDResizableHandle } from './dd-resizable-handle'; @@ -15,7 +14,7 @@ export interface DDResizableOpt { maxWidth?: number; minHeight?: number; minWidth?: number; - basePosision?: 'fixed' | 'absolute'; + basePosition?: 'fixed' | 'absolute'; start?: (event: MouseEvent, ui) => void; stop?: (event: MouseEvent, ui) => void; resize?: (event: MouseEvent, ui) => void; @@ -131,6 +130,7 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt if (this.option.start) { this.option.start(ev, this.ui()); } + this.el.classList.add('ui-resizable-resizing'); this.triggerEvent('resizestart', ev); } @@ -149,6 +149,7 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt if (this.option.stop) { this.option.stop(ev, this.ui()); } + this.el.classList.remove('ui-resizable-resizing'); this.triggerEvent('resizestop', ev); this.cleanHelper(); this.startEvent = undefined; @@ -162,7 +163,7 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt if (window.getComputedStyle(this.el.parentElement).position.match(/static/)) { this.el.parentElement.style.position = 'relative'; } - this.el.style.position = this.option.basePosision || 'absolute'; // or 'fixed' + this.el.style.position = this.option.basePosition || 'absolute'; // or 'fixed' this.el.style.opacity = '0.8'; this.el.style.zIndex = '1000'; } @@ -237,11 +238,11 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt } protected removeHandlers() { - this.handlers.forEach(handle => handle.destory()); + this.handlers.forEach(handle => handle.destroy()); this.handlers = undefined; } - destory() { + destroy() { this.removeHandlers(); if (this.option.autoHide) { this.el.removeEventListener('mouseover', this.showHandlers); @@ -258,8 +259,8 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt const rect = this.temporalRect || this.originalRect; return { element: [this.el], // The object representing the element to be resized - helper: [], // TODO: not support yet // The object representing the helper that's being resized - originalElement: [this.el],// we dont wrap here, so simplify as this.el //The object representing the original element before it is wrapped + helper: [], // TODO: not support yet - The object representing the helper that's being resized + originalElement: [this.el],// we don't wrap here, so simplify as this.el //The object representing the original element before it is wrapped originalPosition: { left: this.originalRect.left - containmentRect.left, top: this.originalRect.top - containmentRect.top diff --git a/src/dragdrop/dd-utils.ts b/src/dragdrop/dd-utils.ts index afc9c7bff..819384b8b 100644 --- a/src/dragdrop/dd-utils.ts +++ b/src/dragdrop/dd-utils.ts @@ -1,8 +1,7 @@ // dd-utils.ts 2.0.2-dev @preserve - /** * https://gridstackjs.com/ - * (c) 2020 Alain Dumesny, rhlin + * (c) 2020 rhlin, Alain Dumesny * gridstack.js may be freely distributed under the MIT license. */ export class DDUtils { @@ -44,17 +43,6 @@ export class DDUtils { } } - static throttle(callback: (...args) => void, delay: number) { - let isWaiting = false; - - return (...args) => { - if (!isWaiting) { - callback(...args); - isWaiting = true; - setTimeout(() => isWaiting = false, delay); - } - } - } static addElStyles(el: HTMLElement, styles: { [prop: string]: string | string[] }) { if (styles instanceof Object) { for (const s in styles) { diff --git a/src/dragdrop/gridstack-dd-native.ts b/src/dragdrop/gridstack-dd-native.ts new file mode 100644 index 000000000..faabe2870 --- /dev/null +++ b/src/dragdrop/gridstack-dd-native.ts @@ -0,0 +1,137 @@ +// gridstack-dd-native.ts 2.0.2-dev @preserve +/** + * https://gridstackjs.com/ + * (c) 2020 rhlin, Alain Dumesny + * gridstack.js may be freely distributed under the MIT license. +*/ +import { DDManager } from './dd-manager'; +import { DDElement } from './dd-element'; + +import { GridStack, GridStackElement } from '../gridstack'; +import { GridStackDD, DDOpts, DDKey, DDDropOpt, DDCallback, DDValue } from '../gridstack-dd'; +import { GridItemHTMLElement, DDDragInOpt } from '../types'; + +/** + * HTML 5 Native DragDrop based drag'n'drop plugin. + */ +export class GridStackDDNative extends GridStackDD { + public constructor(grid: GridStack) { + super(grid); + } + + public resizable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDDNative { + let dEl = this.getGridStackDDElement(el); + if (opts === 'disable' || opts === 'enable') { + dEl.ddResizable[opts](); + } else if (opts === 'destroy') { + if (dEl.ddResizable) { + dEl.cleanResizable(); + } + } else if (opts === 'option') { + dEl.setupResizable({ [key]: value }); + } else { + let handles = dEl.el.getAttribute('gs-resize-handles') ? dEl.el.getAttribute('gs-resize-handles') : this.grid.opts.resizable.handles; + dEl.setupResizable({ + ...this.grid.opts.resizable, + ...{ handles: handles }, + ...{ + start: opts.start, + stop: opts.stop, + resize: opts.resize + } + }); + } + return this; + } + + public draggable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDDNative { + const dEl = this.getGridStackDDElement(el); + if (opts === 'disable' || opts === 'enable') { + dEl.ddDraggable && dEl.ddDraggable[opts](); + } else if (opts === 'destroy') { + if (dEl.ddDraggable) { // error to call destroy if not there + dEl.cleanDraggable(); + } + } else if (opts === 'option') { + dEl.setupDraggable({ [key]: value }); + } else { + dEl.setupDraggable({ + ...this.grid.opts.draggable, + ...{ + containment: (this.grid.opts._isNested && !this.grid.opts.dragOut) + ? this.grid.el.parentElement + : (this.grid.opts.draggable.containment || null), + start: opts.start, + stop: opts.stop, + drag: opts.drag + } + }); + } + return this; + } + + public dragIn(el: GridStackElement, opts: DDDragInOpt): GridStackDDNative { + let dEl = this.getGridStackDDElement(el); + dEl.setupDraggable(opts); + return this; + } + + public droppable(el: GridItemHTMLElement, opts: DDOpts | DDDropOpt, key?: DDKey, value?: DDValue): GridStackDDNative { + let dEl = this.getGridStackDDElement(el); + if (typeof opts.accept === 'function' && !opts._accept) { + opts._accept = opts.accept; + opts.accept = (el) => opts._accept(el); + } + if (opts === 'disable' || opts === 'enable') { + dEl.ddDroppable && dEl.ddDroppable[opts](); + } else if (opts === 'destroy') { + if (dEl.ddDroppable) { // error to call destroy if not there + dEl.cleanDroppable(); + } + } else if (opts === 'option') { + dEl.setupDroppable({ [key]: value }); + } else { + dEl.setupDroppable(opts); + } + return this; + } + + public isDroppable(el: GridItemHTMLElement): boolean { + const dEl = this.getGridStackDDElement(el); + return !!(dEl.ddDroppable); + } + + public isDraggable(el: GridStackElement): boolean { + const dEl = this.getGridStackDDElement(el); + return !!(dEl.ddDraggable); + } + + public on(el: GridItemHTMLElement, name: string, callback: DDCallback): GridStackDDNative { + let dEl = this.getGridStackDDElement(el); + dEl.on(name, (event: Event) => { + callback( + event, + DDManager.dragElement ? DDManager.dragElement.el : event.target as GridItemHTMLElement, + DDManager.dragElement ? DDManager.dragElement.helper : null) + }); + return this; + } + + public off(el: GridItemHTMLElement, name: string): GridStackDD { + let dEl = this.getGridStackDDElement(el); + dEl.off(name); + return this; + } + private getGridStackDDElement(el: GridStackElement): DDElement { + let dEl; + if (typeof el === 'string') { + dEl = document.querySelector(el as string); + } else { + dEl = el; + } + return dEl.ddElement ? dEl.ddElement: DDElement.init(dEl); + } +} + +// finally register ourself +GridStackDD.registerPlugin(GridStackDDNative); diff --git a/src/utils.ts b/src/utils.ts index a34d2d42c..4eecb6010 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -187,7 +187,7 @@ export class Utils { return Utils.closestByClass(el, name); } - /** @internal */ + /** delay calling the given function by certain amount of time */ static throttle(callback: () => void, delay: number): () => void { let isWaiting = false; From ed058d90f88526cab77377a76c2ddca58bc03ef0 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sun, 15 Nov 2020 07:40:59 -0800 Subject: [PATCH 07/17] h5: rebase to develop 2.2.0 branch --- demo/two-h5.html | 2 +- src/dragdrop/dd-base-impl.ts | 3 ++- src/dragdrop/dd-draggable.ts | 5 +++-- src/dragdrop/dd-droppable.ts | 3 ++- src/dragdrop/dd-element.ts | 3 ++- src/dragdrop/dd-manager.ts | 3 ++- src/dragdrop/dd-resizable-handle.ts | 3 ++- src/dragdrop/dd-resizable.ts | 3 ++- src/dragdrop/dd-utils.ts | 3 ++- src/dragdrop/gridstack-dd-native.ts | 3 ++- src/gridstack.ts | 3 --- src/index-h5.ts | 13 +++++++++++++ src/index-jq.ts | 13 +++++++++++++ src/index-static.ts | 11 +++++++++++ webpack.config.js | 7 +++---- 15 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 src/index-h5.ts create mode 100644 src/index-jq.ts create mode 100644 src/index-static.ts diff --git a/demo/two-h5.html b/demo/two-h5.html index 25a259473..b4f3c8fe9 100644 --- a/demo/two-h5.html +++ b/demo/two-h5.html @@ -10,7 +10,7 @@ - + + + + +

Advanced Demo

+
+
+
+
+ +
+
+ Drop here to remove! +
+
+
+
+
+ +
+
+ Drag me in the dashboard! +
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/dragdrop/gridstack-dd-native.ts b/src/dragdrop/gridstack-dd-native.ts index 4ccc97a36..26af54821 100644 --- a/src/dragdrop/gridstack-dd-native.ts +++ b/src/dragdrop/gridstack-dd-native.ts @@ -6,11 +6,12 @@ * gridstack.js may be freely distributed under the MIT license. */ import { DDManager } from './dd-manager'; -import { DDElement } from './dd-element'; +import { DDElement, DDElementHost } from './dd-element'; import { GridStackElement } from '../gridstack'; import { GridStackDD, DDOpts, DDKey, DDDropOpt, DDCallback, DDValue } from '../gridstack-dd'; import { GridItemHTMLElement, DDDragInOpt } from '../types'; +import { Utils } from '../utils'; /** * HTML 5 Native DragDrop based drag'n'drop plugin. @@ -18,119 +19,117 @@ import { GridItemHTMLElement, DDDragInOpt } from '../types'; export class GridStackDDNative extends GridStackDD { public resizable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDDNative { - let dEl = this.getGridStackDDElement(el); - if (opts === 'disable' || opts === 'enable') { - dEl.ddResizable[opts](); - } else if (opts === 'destroy') { - if (dEl.ddResizable) { - dEl.cleanResizable(); - } - } else if (opts === 'option') { - dEl.setupResizable({ [key]: value }); - } else { - const grid = el.gridstackNode.grid; - let handles = dEl.el.getAttribute('gs-resize-handles') ? dEl.el.getAttribute('gs-resize-handles') : grid.opts.resizable.handles; - dEl.setupResizable({ - ...grid.opts.resizable, - ...{ handles: handles }, - ...{ - start: opts.start, - stop: opts.stop, - resize: opts.resize + this.getDDElements(el).forEach(dEl => { + if (opts === 'disable' || opts === 'enable') { + dEl.ddResizable[opts](); + } else if (opts === 'destroy') { + if (dEl.ddResizable) { + dEl.cleanResizable(); } - }); - } + } else if (opts === 'option') { + dEl.setupResizable({ [key]: value }); + } else { + const grid = dEl.el.gridstackNode.grid; + let handles = dEl.el.getAttribute('gs-resize-handles') ? dEl.el.getAttribute('gs-resize-handles') : grid.opts.resizable.handles; + dEl.setupResizable({ + ...grid.opts.resizable, + ...{ handles: handles }, + ...{ + start: opts.start, + stop: opts.stop, + resize: opts.resize + } + }); + } + }); return this; } public draggable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDDNative { - const dEl = this.getGridStackDDElement(el); - if (opts === 'disable' || opts === 'enable') { - dEl.ddDraggable && dEl.ddDraggable[opts](); - } else if (opts === 'destroy') { - if (dEl.ddDraggable) { // error to call destroy if not there - dEl.cleanDraggable(); - } - } else if (opts === 'option') { - dEl.setupDraggable({ [key]: value }); - } else { - const grid = el.gridstackNode.grid; - dEl.setupDraggable({ - ...grid.opts.draggable, - ...{ - containment: (grid.opts._isNested && !grid.opts.dragOut) - ? grid.el.parentElement - : (grid.opts.draggable.containment || null), - start: opts.start, - stop: opts.stop, - drag: opts.drag + this.getDDElements(el).forEach(dEl => { + if (opts === 'disable' || opts === 'enable') { + dEl.ddDraggable && dEl.ddDraggable[opts](); + } else if (opts === 'destroy') { + if (dEl.ddDraggable) { // error to call destroy if not there + dEl.cleanDraggable(); } - }); - } + } else if (opts === 'option') { + dEl.setupDraggable({ [key]: value }); + } else { + const grid = dEl.el.gridstackNode.grid; + dEl.setupDraggable({ + ...grid.opts.draggable, + ...{ + containment: (grid.opts._isNested && !grid.opts.dragOut) + ? grid.el.parentElement + : (grid.opts.draggable.containment || null), + start: opts.start, + stop: opts.stop, + drag: opts.drag + } + }); + } + }); return this; } public dragIn(el: GridStackElement, opts: DDDragInOpt): GridStackDDNative { - let dEl = this.getGridStackDDElement(el); - dEl.setupDraggable(opts); + this.getDDElements(el).forEach(dEl => dEl.setupDraggable(opts)); return this; } public droppable(el: GridItemHTMLElement, opts: DDOpts | DDDropOpt, key?: DDKey, value?: DDValue): GridStackDDNative { - let dEl = this.getGridStackDDElement(el); if (typeof opts.accept === 'function' && !opts._accept) { opts._accept = opts.accept; opts.accept = (el) => opts._accept(el); } - if (opts === 'disable' || opts === 'enable') { - dEl.ddDroppable && dEl.ddDroppable[opts](); - } else if (opts === 'destroy') { - if (dEl.ddDroppable) { // error to call destroy if not there - dEl.cleanDroppable(); + this.getDDElements(el).forEach(dEl => { + if (opts === 'disable' || opts === 'enable') { + dEl.ddDroppable && dEl.ddDroppable[opts](); + } else if (opts === 'destroy') { + if (dEl.ddDroppable) { // error to call destroy if not there + dEl.cleanDroppable(); + } + } else if (opts === 'option') { + dEl.setupDroppable({ [key]: value }); + } else { + dEl.setupDroppable(opts); } - } else if (opts === 'option') { - dEl.setupDroppable({ [key]: value }); - } else { - dEl.setupDroppable(opts); - } + }); return this; } + /** true if at least one of them is droppable */ public isDroppable(el: GridItemHTMLElement): boolean { - const dEl = this.getGridStackDDElement(el); - return !!(dEl.ddDroppable); + return this.getDDElements(el).some(dEl => !!(dEl.ddDroppable)); } + /** true if at least one of them is draggable */ public isDraggable(el: GridStackElement): boolean { - const dEl = this.getGridStackDDElement(el); - return !!(dEl.ddDraggable); + return this.getDDElements(el).some(dEl => !!(dEl.ddDraggable)); } public on(el: GridItemHTMLElement, name: string, callback: DDCallback): GridStackDDNative { - let dEl = this.getGridStackDDElement(el); - dEl.on(name, (event: Event) => { - callback( - event, - DDManager.dragElement ? DDManager.dragElement.el : event.target as GridItemHTMLElement, - DDManager.dragElement ? DDManager.dragElement.helper : null) - }); + this.getDDElements(el).forEach(dEl => + dEl.on(name, (event: Event) => { + callback( + event, + DDManager.dragElement ? DDManager.dragElement.el : event.target as GridItemHTMLElement, + DDManager.dragElement ? DDManager.dragElement.helper : null) + }) + ); return this; } public off(el: GridItemHTMLElement, name: string): GridStackDD { - let dEl = this.getGridStackDDElement(el); - dEl.off(name); + this.getDDElements(el).forEach(dEl => dEl.off(name)); return this; } - private getGridStackDDElement(el: GridStackElement): DDElement { - let dEl; - if (typeof el === 'string') { - dEl = document.querySelector(el as string); - } else { - dEl = el; - } - return dEl.ddElement ? dEl.ddElement: DDElement.init(dEl); + private getDDElements(els: GridStackElement): DDElement[] { + let list = Utils.getElements(els) as DDElementHost[]; + if (!list.length) { return []; } + return list.map(e => e.ddElement || DDElement.init(e)); } } diff --git a/src/gridstack.ts b/src/gridstack.ts index e5d70b281..7120662f2 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -8,7 +8,7 @@ import { GridStackEngine } from './gridstack-engine'; import { obsoleteOpts, obsoleteOptsDel, obsoleteAttr, obsolete, Utils, HeightData } from './utils'; -import { GridItemHTMLElement, GridStackWidget, GridStackNode, GridStackOptions, numberOrString, ColumnOptions, DDUIData } from './types'; +import { GridStackElement, GridItemHTMLElement, GridStackWidget, GridStackNode, GridStackOptions, numberOrString, ColumnOptions, DDUIData } from './types'; import { GridStackDD } from './gridstack-dd'; // export all dependent file as well to make it easier for users to just import the main file @@ -17,8 +17,6 @@ export * from './utils'; export * from './gridstack-engine'; export * from './gridstack-dd'; -export type GridStackElement = string | HTMLElement | GridItemHTMLElement; - export interface GridHTMLElement extends HTMLElement { gridstack?: GridStack; // grid's parent DOM element points back to grid class } @@ -1766,48 +1764,19 @@ export class GridStack { /** @internal convert a potential selector into actual element */ private static getElement(els: GridStackElement = '.grid-stack-item'): GridItemHTMLElement { - if (typeof els === 'string') { - if (!els.length) { return null} - if (els[0] === '#') { - return document.getElementById(els.substring(1)); - } - if (els[0] === '.') { - return document.querySelector(els); - } - - // if we start with a digit, assume it's an id (error calling querySelector('#1')) as class are not valid CSS - if(!isNaN(+els[0])) { // start with digit - return document.getElementById(els); - } - - // finally try string, then id then class - let el = document.querySelector(els); - if (!el) { el = document.getElementById(els) } - if (!el) { el = document.querySelector('.' + els) } - return el as GridItemHTMLElement; - } - return els; + return Utils.getElement(els); } - - /** @internal convert a potential selector into actual list of elements */ + /** @internal */ private static getElements(els: GridStackElement = '.grid-stack-item'): GridItemHTMLElement[] { - if (typeof els === 'string') { - let list = document.querySelectorAll(els); - if (!list.length && els[0] !== '.' && els[0] !== '#') { - list = document.querySelectorAll('.' + els); - if (!list.length) { list = document.querySelectorAll('#' + els) } - } - return Array.from(list) as GridItemHTMLElement[]; - } - return [els]; + return Utils.getElements(els); } /** @internal */ - private static getGridElement(els: string | HTMLElement = '.grid-stack'): GridHTMLElement { - return GridStack.getElement(els) as GridHTMLElement; + private static getGridElement(els: GridStackElement): GridHTMLElement { + return GridStack.getElement(els); } /** @internal */ - private static getGridElements(els: string | HTMLElement = '.grid-stack'): GridHTMLElement[] { - return GridStack.getElements(els) as GridHTMLElement[]; + private static getGridElements(els: string): GridHTMLElement[] { + return Utils.getElements(els); } /** @internal initialize margin top/bottom/left/right and units */ diff --git a/src/types.ts b/src/types.ts index 6710e766d..53c329e5c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,8 @@ export interface GridItemHTMLElement extends HTMLElement { _gridstackNodeOrig?: GridStackNode; } +export type GridStackElement = string | HTMLElement | GridItemHTMLElement; + /** * Defines the options for a Grid */ diff --git a/src/utils.ts b/src/utils.ts index 4eecb6010..a96044d29 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,7 +6,7 @@ * gridstack.js may be freely distributed under the MIT license. */ -import { GridStackWidget, GridStackNode, GridStackOptions, numberOrString } from './types'; +import { GridStackElement, GridStackWidget, GridStackNode, GridStackOptions, numberOrString } from './types'; export interface HeightData { height: number; @@ -56,6 +56,44 @@ export function obsoleteAttr(el: HTMLElement, oldName: string, newName: string, */ export class Utils { + /** convert a potential selector into actual list of html elements */ + static getElements(els: GridStackElement): HTMLElement[] { + if (typeof els === 'string') { + let list = document.querySelectorAll(els); + if (!list.length && els[0] !== '.' && els[0] !== '#') { + list = document.querySelectorAll('.' + els); + if (!list.length) { list = document.querySelectorAll('#' + els) } + } + return Array.from(list) as HTMLElement[]; + } + return [els]; + } + + /** convert a potential selector into actual single element */ + static getElement(els: GridStackElement): HTMLElement { + if (typeof els === 'string') { + if (!els.length) { return null} + if (els[0] === '#') { + return document.getElementById(els.substring(1)); + } + if (els[0] === '.') { + return document.querySelector(els); + } + + // if we start with a digit, assume it's an id (error calling querySelector('#1')) as class are not valid CSS + if(!isNaN(+els[0])) { // start with digit + return document.getElementById(els); + } + + // finally try string, then id then class + let el = document.querySelector(els); + if (!el) { el = document.getElementById(els) } + if (!el) { el = document.querySelector('.' + els) } + return el as HTMLElement; + } + return els; + } + /** returns true if a and b overlap */ static isIntercepted(a: GridStackWidget, b: GridStackWidget): boolean { return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y); From fd8e3e04d9fcb05859160a2443cc86134f79d7bd Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Wed, 18 Nov 2020 07:10:19 -0800 Subject: [PATCH 14/17] h5: travis fix node_js 10.19 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e8b0fe1e0..2aba306bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: -- 8.17.0 +- 10.19.0 dist: trusty sudo: required addons: From a2013924f7d6a06273e5cb34663ea83a13606eb6 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sat, 21 Nov 2020 09:44:18 -0800 Subject: [PATCH 15/17] t5: moved native dd files --- src/{dragdrop => h5}/dd-base-impl.ts | 0 src/{dragdrop => h5}/dd-draggable.ts | 0 src/{dragdrop => h5}/dd-droppable.ts | 0 src/{dragdrop => h5}/dd-element.ts | 0 src/{dragdrop => h5}/dd-manager.ts | 0 src/{dragdrop => h5}/dd-resizable-handle.ts | 0 src/{dragdrop => h5}/dd-resizable.ts | 0 src/{dragdrop => h5}/dd-utils.ts | 0 src/{dragdrop => h5}/gridstack-dd-native.ts | 0 src/index-h5.ts | 2 +- 10 files changed, 1 insertion(+), 1 deletion(-) rename src/{dragdrop => h5}/dd-base-impl.ts (100%) rename src/{dragdrop => h5}/dd-draggable.ts (100%) rename src/{dragdrop => h5}/dd-droppable.ts (100%) rename src/{dragdrop => h5}/dd-element.ts (100%) rename src/{dragdrop => h5}/dd-manager.ts (100%) rename src/{dragdrop => h5}/dd-resizable-handle.ts (100%) rename src/{dragdrop => h5}/dd-resizable.ts (100%) rename src/{dragdrop => h5}/dd-utils.ts (100%) rename src/{dragdrop => h5}/gridstack-dd-native.ts (100%) diff --git a/src/dragdrop/dd-base-impl.ts b/src/h5/dd-base-impl.ts similarity index 100% rename from src/dragdrop/dd-base-impl.ts rename to src/h5/dd-base-impl.ts diff --git a/src/dragdrop/dd-draggable.ts b/src/h5/dd-draggable.ts similarity index 100% rename from src/dragdrop/dd-draggable.ts rename to src/h5/dd-draggable.ts diff --git a/src/dragdrop/dd-droppable.ts b/src/h5/dd-droppable.ts similarity index 100% rename from src/dragdrop/dd-droppable.ts rename to src/h5/dd-droppable.ts diff --git a/src/dragdrop/dd-element.ts b/src/h5/dd-element.ts similarity index 100% rename from src/dragdrop/dd-element.ts rename to src/h5/dd-element.ts diff --git a/src/dragdrop/dd-manager.ts b/src/h5/dd-manager.ts similarity index 100% rename from src/dragdrop/dd-manager.ts rename to src/h5/dd-manager.ts diff --git a/src/dragdrop/dd-resizable-handle.ts b/src/h5/dd-resizable-handle.ts similarity index 100% rename from src/dragdrop/dd-resizable-handle.ts rename to src/h5/dd-resizable-handle.ts diff --git a/src/dragdrop/dd-resizable.ts b/src/h5/dd-resizable.ts similarity index 100% rename from src/dragdrop/dd-resizable.ts rename to src/h5/dd-resizable.ts diff --git a/src/dragdrop/dd-utils.ts b/src/h5/dd-utils.ts similarity index 100% rename from src/dragdrop/dd-utils.ts rename to src/h5/dd-utils.ts diff --git a/src/dragdrop/gridstack-dd-native.ts b/src/h5/gridstack-dd-native.ts similarity index 100% rename from src/dragdrop/gridstack-dd-native.ts rename to src/h5/gridstack-dd-native.ts diff --git a/src/index-h5.ts b/src/index-h5.ts index 1000242c2..aed9ad2de 100644 --- a/src/index-h5.ts +++ b/src/index-h5.ts @@ -8,6 +8,6 @@ export * from './gridstack-engine'; export * from './gridstack-dd'; export * from './gridstack'; -export * from './dragdrop/gridstack-dd-native'; +export * from './h5/gridstack-dd-native'; // declare module 'gridstack'; for umd ? From 70777ded148eba97043cca986a90d61b48add4a2 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sat, 21 Nov 2020 20:19:27 -0800 Subject: [PATCH 16/17] h5: moved all DD code to gridstack-dd.ts * all drag&drop grid code has been moved to gridstack-dd.ts (base class for html5 and jquery version) * that reduces static grid requirements down from 43k to just 35k * with html5 complete is still 64k (+29k) and jq 187k (+152k) --- src/gridstack-dd.ts | 491 +++++++++++++++++++++++++++++++++++++--- src/gridstack-ddi.ts | 34 +++ src/gridstack.ts | 516 +++++-------------------------------------- src/index-h5.ts | 2 +- src/index-jq.ts | 2 +- src/index-static.ts | 2 +- 6 files changed, 548 insertions(+), 499 deletions(-) create mode 100644 src/gridstack-ddi.ts diff --git a/src/gridstack-dd.ts b/src/gridstack-dd.ts index de831c7b4..883eb5706 100644 --- a/src/gridstack-dd.ts +++ b/src/gridstack-dd.ts @@ -1,4 +1,4 @@ -// gridstack-dd.ts 2.2.0-dev @preserve +// gridstack-GridStackDD.get().ts 2.2.0-dev @preserve /** * https://gridstackjs.com/ @@ -7,8 +7,10 @@ */ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { GridStackElement } from './gridstack'; -import { GridItemHTMLElement, DDDragInOpt } from './types'; +import { GridStackDDI } from './gridstack-ddi'; +import { GridItemHTMLElement, GridStackNode, GridStackElement, DDUIData, DDDragInOpt } from './types'; +import { GridStack } from './gridstack'; +import { Utils } from './utils'; /** Drag&Drop drop options */ export type DDDropOpt = { @@ -26,19 +28,13 @@ export type DDValue = number | string; export type DDCallback = (event: Event, arg2: GridItemHTMLElement, helper?: GridItemHTMLElement) => void; /** - * Base class for drag'n'drop plugin. + * Base class implementing common Grid drag'n'drop functionality, with domain specific subclass (h5 vs jq subclasses) */ -export class GridStackDD { - static registeredPlugins: typeof GridStackDD; +export abstract class GridStackDD extends GridStackDDI { - /** call this method to register your plugin instead of the default no-op one */ - static registerPlugin(pluginClass: typeof GridStackDD): void { - GridStackDD.registeredPlugins = pluginClass; - } - - /** get the current registered plugin to use */ - static get(): typeof GridStackDD { - return GridStackDD.registeredPlugins || GridStackDD; + /** override to cast to correct type */ + static get(): GridStackDD { + return GridStackDDI.get() as GridStackDD; } /** removes any drag&drop present (called during destroy) */ @@ -50,35 +46,470 @@ export class GridStackDD { return this; } - public resizable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDD { - return this; + // APIs that must be implemented by subclasses to do actual darg/drop/resize called by GridStack code below + + public abstract resizable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDD; + + public abstract draggable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDD; + + public abstract dragIn(el: GridStackElement, opts: DDDragInOpt): GridStackDD; + + public abstract isDraggable(el: GridStackElement): boolean; + + public abstract droppable(el: GridItemHTMLElement, opts: DDOpts | DDDropOpt, key?: DDKey, value?: DDValue): GridStackDD; + + public abstract isDroppable(el: GridItemHTMLElement): boolean; + + public abstract on(el: GridItemHTMLElement, eventName: string, callback: DDCallback): GridStackDD; + + public abstract off(el: GridItemHTMLElement, eventName: string): GridStackDD; +} + +/******************************************************************************** + * GridStack code that is doing drag&drop extracted here so main class is smaller + * for static grid that don't do any of this work anyway. Saves about 10k. + ********************************************************************************/ + +/** @internal called to add drag over support to support widgets */ +GridStack.prototype._setupAcceptWidget = function(): GridStack { + if (this.opts.staticGrid || !this.opts.acceptWidgets) return this; + + let onDrag = (event, el: GridItemHTMLElement) => { + let node = el.gridstackNode; + let pos = this.getCellFromPixel({left: event.pageX, top: event.pageY}, true); + let x = Math.max(0, pos.x); + let y = Math.max(0, pos.y); + if (!node._added) { + node._added = true; + + node.el = el; + node.x = x; + node.y = y; + delete node.autoPosition; + this.engine.cleanNodes(); + this.engine.beginUpdate(node); + this.engine.addNode(node); + + this._writeAttrs(this.placeholder, node.x, node.y, node.width, node.height); + this.el.appendChild(this.placeholder); + node.el = this.placeholder; // dom we update while dragging... + node._beforeDragX = node.x; + node._beforeDragY = node.y; + + this._updateContainerHeight(); + } else if ((x !== node.x || y !== node.y) && this.engine.canMoveNode(node, x, y)) { + this.engine.moveNode(node, x, y); + this._updateContainerHeight(); + } + }; + + GridStackDD.get() + .droppable(this.el, { + accept: (el: GridItemHTMLElement) => { + let node: GridStackNode = el.gridstackNode; + if (node && node.grid === this) { + return false; + } + if (typeof this.opts.acceptWidgets === 'function') { + return this.opts.acceptWidgets(el); + } + let selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets as string); + return el.matches(selector); + } + }) + .on(this.el, 'dropover', (event, el: GridItemHTMLElement) => { + + // see if we already have a node with widget/height and check for attributes + let node = el.gridstackNode || {}; + if (!node.width || !node.height) { + let w = parseInt(el.getAttribute('data-gs-width')); + if (w > 0) { node.width = w; } + let h = parseInt(el.getAttribute('data-gs-height')); + if (h > 0) { node.height = h; } + } + + // 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 not calculate the grid size based on element outer size + let width = node.width || Math.round(el.offsetWidth / this.cellWidth()) || 1; + let height = node.height || Math.round(el.offsetHeight / this.getCellHeight(true)) || 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, ...{width, height, _added: false, _temporary: true}}); + newNode._isOutOfGrid = true; + el.gridstackNode = newNode; + el._gridstackNodeOrig = node; + + GridStackDD.get().on(el, 'drag', onDrag); + return false; // prevent parent from receiving msg (which may be grid as well) + }) + .on(this.el, 'dropout', (event, el: GridItemHTMLElement) => { + // jquery-ui bug. Must verify widget is being dropped out + // check node variable that gets set when widget is out of grid + let node = el.gridstackNode; + if (!node || !node._isOutOfGrid) { + return; + } + GridStackDD.get().off(el, 'drag'); + node.el = null; + this.engine.removeNode(node); + if (this.placeholder.parentNode === this.el) { + this.placeholder.remove(); + } + this._updateContainerHeight(); + el.gridstackNode = el._gridstackNodeOrig; + return false; // prevent parent from receiving msg (which may be grid as well) + }) + .on(this.el, 'drop', (event, el: GridItemHTMLElement, helper: GridItemHTMLElement) => { + this.placeholder.remove(); + + // notify previous grid of removal + let origNode = el._gridstackNodeOrig; + delete el._gridstackNodeOrig; + if (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(); + } + + let node = el.gridstackNode; // use existing placeholder node as it's already in our list with drop location + const _id = node._id; + this.engine.cleanupNode(node); // removes all internal _xyz values (including the _id so add that back) + node._id = _id; + node.grid = this; + GridStackDD.get().off(el, 'drag'); + // if we made a copy ('helper' which is temp) of the original node then insert a copy, else we move the original node (#1102) + // as the helper will be nuked by jqueryui otherwise + if (helper !== el) { + helper.remove(); + el.gridstackNode = origNode; // original item (left behind) is re-stored to pre dragging as the node now has drop info + el = el.cloneNode(true) as GridItemHTMLElement; + } else { + el.remove(); // reduce flicker as we change depth here, and size further down + GridStackDD.get().remove(el); + } + el.gridstackNode = node; + node.el = el; + + Utils.removePositioningStyles(el); + this._writeAttr(el, node); + this.el.appendChild(el); + this._updateContainerHeight(); + this.engine.addedNodes.push(node); + this._triggerAddEvent(); + this._triggerChangeEvent(); + + this.engine.endUpdate(); + if (this._gsEventHandler['dropped']) { + this._gsEventHandler['dropped']({type: 'dropped'}, origNode && origNode.grid ? origNode : undefined, node); + } + + // 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); + }); + + return false; // prevent parent from receiving msg (which may be grid as well) + }); + return this; +} + +/** @internal called to setup a trash drop zone if the user specifies it */ +GridStack.prototype._setupRemoveDrop = function(): GridStack { + if (!this.opts.staticGrid && typeof this.opts.removable === 'string') { + let trashZone = document.querySelector(this.opts.removable) as HTMLElement; + if (!trashZone) return this; + // only register ONE dropover/dropout callback for the 'trash', and it will + // update the passed in item and parent grid because the 'trash' is a shared resource anyway, + // and Native DD only has 1 event CB (having a list and technically a per grid removableOptions complicates things greatly) + if (!GridStackDD.get().isDroppable(trashZone)) { + GridStackDD.get().droppable(trashZone, this.opts.removableOptions) + .on(trashZone, '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); + }) + .on(trashZone, 'dropout', function(event, el) { // same + let node = el.gridstackNode; + if (!node || !node.grid) return; + delete el.dataset.inTrashZone; + node.grid._clearRemovingTimeout(el); + }); + } } + return this; +} - public draggable(el: GridItemHTMLElement, opts: DDOpts, key?: DDKey, value?: DDValue): GridStackDD { - 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; +} + +/** @internal call to setup dragging in from the outside (say toolbar), with options */ +GridStack.prototype._setupDragIn = function(): GridStack { + if (!this.opts.staticGrid && typeof this.opts.dragIn === 'string') { + if (!GridStackDD.get().isDraggable(this.opts.dragIn)) { + GridStackDD.get().dragIn(this.opts.dragIn, this.opts.dragInOptions); + } } + return this; +} - public dragIn(el: GridStackElement, opts: DDDragInOpt): GridStackDD { +/** @internal prepares the element for drag&drop **/ +GridStack.prototype._prepareDragDropByNode = function(node: GridStackNode): GridStack { + // check for disabled grid first + if (this.opts.staticGrid || node.locked || + ((node.noMove || this.opts.disableDrag) && (node.noResize || this.opts.disableResize))) { + if (node._initDD) { + GridStackDD.get().remove(node.el); // nukes everything instead of just disable, will add some styles back next + delete node._initDD; + } + node.el.classList.add('ui-draggable-disabled', 'ui-resizable-disabled'); // add styles one might depend on #1435 return this; } - - public isDraggable(el: GridStackElement): boolean { - return false; + // check if init already done + if (node._initDD) { + return this; } - public droppable(el: GridItemHTMLElement, opts: DDOpts | DDDropOpt, key?: DDKey, value?: DDValue): GridStackDD { - return this; + // remove our style that look like D&D + node.el.classList.remove('ui-draggable-disabled', 'ui-resizable-disabled'); + + // variables used/cashed between the 3 start/move/end methods, in addition to node passed above + let cellWidth: number; + let cellHeight: number; + let el = node.el; + + /** called when item starts moving/resizing */ + let onStartMoving = (event: Event, ui: DDUIData): void => { + let target = event.target as HTMLElement; + + // trigger any 'dragstart' / 'resizestart' manually + if (this._gsEventHandler[event.type]) { + this._gsEventHandler[event.type](event, target); + } + + this.engine.cleanNodes(); + this.engine.beginUpdate(node); + cellWidth = this.cellWidth(); + cellHeight = this.getCellHeight(true); // force pixels for calculations + + this.placeholder.setAttribute('data-gs-x', target.getAttribute('data-gs-x')); + this.placeholder.setAttribute('data-gs-y', target.getAttribute('data-gs-y')); + this.placeholder.setAttribute('data-gs-width', target.getAttribute('data-gs-width')); + this.placeholder.setAttribute('data-gs-height', target.getAttribute('data-gs-height')); + this.el.append(this.placeholder); + + node.el = this.placeholder; + node._beforeDragX = node.x; + node._beforeDragY = node.y; + node._prevYPix = ui.position.top; + + GridStackDD.get().resizable(el, 'option', 'minWidth', cellWidth * (node.minWidth || 1)); + GridStackDD.get().resizable(el, 'option', 'minHeight', cellHeight * (node.minHeight || 1)); } - public isDroppable(el: GridItemHTMLElement): boolean { - return false; + /** called when item is being dragged/resized */ + let dragOrResize = (event: Event, ui: DDUIData): void => { + let x = Math.round(ui.position.left / cellWidth); + let y = Math.floor((ui.position.top + cellHeight / 2) / cellHeight); + let width; + let height; + + if (event.type === 'drag') { + 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 || x < 0 || x >= this.engine.column || y < 0 || (!this.engine.float && y > this.engine.getRow()) || node._added) { + if (node._temporaryRemoved) { return; } + if (this.opts.removable === true) { + this._setupRemovingTimeout(el); + } + + x = node._beforeDragX; + y = node._beforeDragY; + + 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 + } else { + this._clearRemovingTimeout(el); + + if (node._temporaryRemoved) { + this.engine.addNode(node); + this._writeAttrs(this.placeholder, x, y, width, height); + this.el.appendChild(this.placeholder); + node.el = this.placeholder; + delete node._temporaryRemoved; + } + } + } else if (event.type === 'resize') { + if (x < 0) return; + width = Math.round(ui.size.width / cellWidth); + height = Math.round(ui.size.height / cellHeight); + } + // width and height are undefined if not resizing + let _lastTriedWidth = (width || node._lastTriedWidth); + let _lastTriedHeight = (height || node._lastTriedHeight); + if (!this.engine.canMoveNode(node, x, y, width, height) || + (node._lastTriedX === x && node._lastTriedY === y && + node._lastTriedWidth === _lastTriedWidth && node._lastTriedHeight === _lastTriedHeight)) { + return; + } + node._lastTriedX = x; + node._lastTriedY = y; + node._lastTriedWidth = width; + node._lastTriedHeight = height; + this.engine.moveNode(node, x, y, width, height); + this._updateContainerHeight(); } - public on(el: GridItemHTMLElement, eventName: string, callback: DDCallback): GridStackDD { - return this; + /** called when the item stops moving/resizing */ + let onEndMoving = (event: Event): void => { + if (this.placeholder.parentNode === this.el) { + this.placeholder.remove(); + } + + // if the item has moved to another grid, we're done here + let target: GridItemHTMLElement = event.target as GridItemHTMLElement; + if (!target.gridstackNode || target.gridstackNode.grid !== this) return; + + node.el = target; + + if (node._isAboutToRemove) { + let gridToNotify = el.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._triggerRemoveEvent(); + if (el.parentElement) { + el.remove(); // finally remove it + } + } else { + this._clearRemovingTimeout(el); + if (!node._temporaryRemoved) { + Utils.removePositioningStyles(target); + this._writeAttrs(target, node.x, node.y, node.width, node.height); + } else { + Utils.removePositioningStyles(target); + this._writeAttrs(target, node._beforeDragX, node._beforeDragY, node.width, node.height); + node.x = node._beforeDragX; + node.y = node._beforeDragY; + delete node._temporaryRemoved; + this.engine.addNode(node); + } + if (this._gsEventHandler[event.type]) { + this._gsEventHandler[event.type](event, target); + } + } + + this._updateContainerHeight(); + this._triggerChangeEvent(); + + this.engine.endUpdate(); + + // if we re-sized a nested grid item, let the children resize as well + if (event.type === 'resizestop') { + this._resizeNestedGrids(target); + } } - public off(el: GridItemHTMLElement, eventName: string): GridStackDD { - return this; + GridStackDD.get() + .draggable(el, { + start: onStartMoving, + stop: onEndMoving, + drag: dragOrResize + }) + .resizable(el, { + start: onStartMoving, + stop: onEndMoving, + resize: dragOrResize + }); + node._initDD = true; // we've set DD support now + + // finally fine tune drag vs move by disabling any part... + if (node.noMove || this.opts.disableDrag) { + GridStackDD.get().draggable(el, 'disable'); + } + if (node.noResize || this.opts.disableResize) { + GridStackDD.get().resizable(el, 'disable'); } + return this; +} + +/** + * Enables/Disables moving. + * @param els widget or selector to modify. + * @param val if true widget will be draggable. + */ +GridStack.prototype.movable = function(els: GridStackElement, val: boolean): GridStack { + if (val && this.opts.staticGrid) { return this; } // can't move a static grid! + GridStack.getElements(els).forEach(el => { + let node = el.gridstackNode; + if (!node) { return } + node.noMove = !(val || false); + if (node.noMove) { + GridStackDD.get().draggable(el, 'disable'); + el.classList.remove('ui-draggable-handle'); + } else { + this._prepareDragDropByNode(node); // init DD if need be + GridStackDD.get().draggable(el, 'enable'); + el.classList.remove('ui-draggable-handle'); + } + }); + return this; +} + +/** + * Enables/Disables resizing. + * @param els widget or selector to modify + * @param val if true widget will be resizable. + */ +GridStack.prototype.resizable = function(els: GridStackElement, val: boolean): GridStack { + if (val && this.opts.staticGrid) { return this; } // can't resize a static grid! + GridStack.getElements(els).forEach(el => { + let node = el.gridstackNode; + if (!node) { return; } + node.noResize = !(val || false); + if (node.noResize) { + GridStackDD.get().resizable(el, 'disable'); + } else { + this._prepareDragDropByNode(node); // init DD if need be + GridStackDD.get().resizable(el, 'enable'); + } + }); + return this; } diff --git a/src/gridstack-ddi.ts b/src/gridstack-ddi.ts new file mode 100644 index 000000000..6922b5998 --- /dev/null +++ b/src/gridstack-ddi.ts @@ -0,0 +1,34 @@ +// gridstack-ddi.ts 2.2.0-dev @preserve + +/** + * https://gridstackjs.com/ + * (c) 2014-2020 Alain Dumesny, Dylan Weiss, Pavel Reznikov + * gridstack.js may be freely distributed under the MIT license. +*/ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { GridItemHTMLElement } from './types'; + +/** + * Abstract Partial Interface API for drag'n'drop plugin - look at GridStackDD and HTML5 / Jquery implementation versions + */ +export class GridStackDDI { + + protected static ddi: GridStackDDI; + + /** call this method to register your plugin instead of the default no-op one */ + static registerPlugin(pluginClass: typeof GridStackDDI): void { + GridStackDDI.ddi = new pluginClass(); + } + + /** get the current registered plugin to use */ + static get(): GridStackDDI { + if (!GridStackDDI.ddi) { GridStackDDI.registerPlugin(GridStackDDI); } + return GridStackDDI.ddi; + } + + /** removes any drag&drop present (called during destroy) */ + public remove(el: GridItemHTMLElement): GridStackDDI { + return this; // no-op for static grids + } +} diff --git a/src/gridstack.ts b/src/gridstack.ts index 7120662f2..9cbb1db44 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -8,14 +8,14 @@ import { GridStackEngine } from './gridstack-engine'; import { obsoleteOpts, obsoleteOptsDel, obsoleteAttr, obsolete, Utils, HeightData } from './utils'; -import { GridStackElement, GridItemHTMLElement, GridStackWidget, GridStackNode, GridStackOptions, numberOrString, ColumnOptions, DDUIData } from './types'; -import { GridStackDD } from './gridstack-dd'; +import { GridStackElement, GridItemHTMLElement, GridStackWidget, GridStackNode, GridStackOptions, numberOrString, ColumnOptions } from './types'; +import { GridStackDDI } from './gridstack-ddi'; // export all dependent file as well to make it easier for users to just import the main file export * from './types'; export * from './utils'; export * from './gridstack-engine'; -export * from './gridstack-dd'; +export * from './gridstack-ddi'; export interface GridHTMLElement extends HTMLElement { gridstack?: GridStack; // grid's parent DOM element points back to grid class @@ -40,9 +40,6 @@ interface GridCSSStyleSheet extends CSSStyleSheet { _max?: number; // internal tracker of the max # of rows we created\ } -/** current drag&drop plugin being used - first grid will initialize */ -let dd: GridStackDD; - /** * Main gridstack class - you will need to call `GridStack.init()` first to initialize your grid. * Note: your grid elements MUST have the following classes for the CSS layout to work: @@ -125,13 +122,13 @@ export class GridStack { public opts: GridStackOptions; /** @internal */ - private placeholder: HTMLElement; + public placeholder: HTMLElement; /** @internal */ private _oneColumnMode: boolean; /** @internal */ private _prevColumn: number; /** @internal */ - private _gsEventHandler = {}; + public _gsEventHandler = {}; /** @internal */ private _styles: GridCSSStyleSheet; /** @internal flag to keep cells square during resize */ @@ -219,10 +216,9 @@ export class GridStack { } this.opts = Utils.defaults(opts, defaults); + opts = null; // make sure we use this.opts instead this.initMargin(); - dd = dd || new (GridStackDD.get())(); - if (this.opts.rtl === 'auto') { this.opts.rtl = el.style.direction === 'rtl'; } @@ -231,7 +227,7 @@ export class GridStack { this.el.classList.add('grid-stack-rtl'); } - this.opts._isNested = Utils.closestByClass(this.el, opts.itemClass) !== null; + this.opts._isNested = Utils.closestByClass(this.el, this.opts.itemClass) !== null; if (this.opts._isNested) { this.el.classList.add('grid-stack-nested'); } @@ -263,9 +259,7 @@ export class GridStack { } }); this._updateStyles(false, maxHeight); // false = don't recreate, just append if need be - }, - this.opts.float, - this.opts.maxRow); + }, this.opts.float, this.opts.maxRow); if (this.opts.auto) { let elements: {el: HTMLElement; i: number}[] = []; @@ -773,29 +767,6 @@ export class GridStack { return this._updateAttr(els, val, 'data-gs-min-height', 'minHeight'); } - /** - * Enables/Disables moving. - * @param els widget or selector to modify. - * @param val if true widget will be draggable. - */ - public movable(els: GridStackElement, val: boolean): GridStack { - if (val && this.opts.staticGrid) { return this; } // can't move a static grid! - GridStack.getElements(els).forEach(el => { - let node = el.gridstackNode; - if (!node) { return } - node.noMove = !(val || false); - if (node.noMove) { - dd.draggable(el, 'disable'); - el.classList.remove('ui-draggable-handle'); - } else { - this._prepareDragDropByNode(node); // init DD if need be - dd.draggable(el, 'enable'); - el.classList.remove('ui-draggable-handle'); - } - }); - return this; - } - /** * Changes widget position * @param els widget or singular selector to modify @@ -897,7 +868,7 @@ export class GridStack { // remove our DOM data (circular link) and drag&drop permanently delete el.gridstackNode; - dd.remove(el); + GridStackDDI.get().remove(el); this.engine.removeNode(node, removeDOM, triggerEvent); @@ -920,7 +891,7 @@ export class GridStack { // always remove our DOM data (circular link) before list gets emptied and drag&drop permanently this.engine.nodes.forEach(n => { delete n.el.gridstackNode; - dd.remove(n.el); + GridStackDDI.get().remove(n.el); }); this.engine.removeAll(removeDOM); this._triggerRemoveEvent(); @@ -943,27 +914,6 @@ export class GridStack { return this; } - /** - * Enables/Disables resizing. - * @param els widget or selector to modify - * @param val if true widget will be resizable. - */ - public resizable(els: GridStackElement, val: boolean): GridStack { - if (val && this.opts.staticGrid) { return this; } // can't resize a static grid! - GridStack.getElements(els).forEach(el => { - let node = el.gridstackNode; - if (!node) { return; } - node.noResize = !(val || false); - if (node.noResize) { - dd.resizable(el, 'disable'); - } else { - this._prepareDragDropByNode(node); // init DD if need be - dd.resizable(el, 'enable'); - } - }); - return this; - } - /** * Toggle the grid animation state. Toggles the `grid-stack-animate` class. * @param doAnimate if true the grid will animate. @@ -1083,7 +1033,7 @@ export class GridStack { } /** @internal */ - private _triggerRemoveEvent(): GridStack { + public _triggerRemoveEvent(): GridStack { if (this.engine.batchMode) { return this; } if (this.engine.removedNodes && this.engine.removedNodes.length > 0) { this._triggerEvent('removed', this.engine.removedNodes); @@ -1195,219 +1145,6 @@ export class GridStack { return this; } - /** @internal */ - private _setupRemovingTimeout(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 */ - private _clearRemovingTimeout(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; - } - - /** @internal prepares the element for drag&drop **/ - private _prepareDragDropByNode(node: GridStackNode): GridStack { - // check for disabled grid first - if (this.opts.staticGrid || node.locked || - ((node.noMove || this.opts.disableDrag) && (node.noResize || this.opts.disableResize))) { - if (node._initDD) { - dd.remove(node.el); // nukes everything instead of just disable, will add some styles back next - delete node._initDD; - } - node.el.classList.add('ui-draggable-disabled', 'ui-resizable-disabled'); // add styles one might depend on #1435 - return this; - } - // check if init already done - if (node._initDD) { - return this; - } - - // remove our style that look like D&D - node.el.classList.remove('ui-draggable-disabled', 'ui-resizable-disabled'); - - // variables used/cashed between the 3 start/move/end methods, in addition to node passed above - let cellWidth: number; - let cellHeight: number; - let el = node.el; - - /** called when item starts moving/resizing */ - let onStartMoving = (event: Event, ui: DDUIData): void => { - let target = event.target as HTMLElement; - - // trigger any 'dragstart' / 'resizestart' manually - if (this._gsEventHandler[event.type]) { - this._gsEventHandler[event.type](event, target); - } - - this.engine.cleanNodes(); - this.engine.beginUpdate(node); - cellWidth = this.cellWidth(); - cellHeight = this.getCellHeight(true); // force pixels for calculations - - this.placeholder.setAttribute('data-gs-x', target.getAttribute('data-gs-x')); - this.placeholder.setAttribute('data-gs-y', target.getAttribute('data-gs-y')); - this.placeholder.setAttribute('data-gs-width', target.getAttribute('data-gs-width')); - this.placeholder.setAttribute('data-gs-height', target.getAttribute('data-gs-height')); - this.el.append(this.placeholder); - - node.el = this.placeholder; - node._beforeDragX = node.x; - node._beforeDragY = node.y; - node._prevYPix = ui.position.top; - - dd.resizable(el, 'option', 'minWidth', cellWidth * (node.minWidth || 1)); - dd.resizable(el, 'option', 'minHeight', cellHeight * (node.minHeight || 1)); - } - - /** called when item is being dragged/resized */ - let dragOrResize = (event: Event, ui: DDUIData): void => { - let x = Math.round(ui.position.left / cellWidth); - let y = Math.floor((ui.position.top + cellHeight / 2) / cellHeight); - let width; - let height; - - if (event.type === 'drag') { - 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 || x < 0 || x >= this.engine.column || y < 0 || (!this.engine.float && y > this.engine.getRow()) || node._added) { - if (node._temporaryRemoved) { return; } - if (this.opts.removable === true) { - this._setupRemovingTimeout(el); - } - - x = node._beforeDragX; - y = node._beforeDragY; - - 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 - } else { - this._clearRemovingTimeout(el); - - if (node._temporaryRemoved) { - this.engine.addNode(node); - this._writeAttrs(this.placeholder, x, y, width, height); - this.el.appendChild(this.placeholder); - node.el = this.placeholder; - delete node._temporaryRemoved; - } - } - } else if (event.type === 'resize') { - if (x < 0) return; - width = Math.round(ui.size.width / cellWidth); - height = Math.round(ui.size.height / cellHeight); - } - // width and height are undefined if not resizing - let _lastTriedWidth = (width || node._lastTriedWidth); - let _lastTriedHeight = (height || node._lastTriedHeight); - if (!this.engine.canMoveNode(node, x, y, width, height) || - (node._lastTriedX === x && node._lastTriedY === y && - node._lastTriedWidth === _lastTriedWidth && node._lastTriedHeight === _lastTriedHeight)) { - return; - } - node._lastTriedX = x; - node._lastTriedY = y; - node._lastTriedWidth = width; - node._lastTriedHeight = height; - this.engine.moveNode(node, x, y, width, height); - this._updateContainerHeight(); - } - - /** called when the item stops moving/resizing */ - let onEndMoving = (event: Event): void => { - if (this.placeholder.parentNode === this.el) { - this.placeholder.remove(); - } - - // if the item has moved to another grid, we're done here - let target: GridItemHTMLElement = event.target as GridItemHTMLElement; - if (!target.gridstackNode || target.gridstackNode.grid !== this) return; - - node.el = target; - - if (node._isAboutToRemove) { - let gridToNotify = el.gridstackNode.grid; - if (gridToNotify._gsEventHandler[event.type]) { - gridToNotify._gsEventHandler[event.type](event, target); - } - gridToNotify.engine.removedNodes.push(node); - dd.remove(el); - delete el.gridstackNode; // hint we're removing it next and break circular link - gridToNotify._triggerRemoveEvent(); - if (el.parentElement) { - el.remove(); // finally remove it - } - } else { - this._clearRemovingTimeout(el); - if (!node._temporaryRemoved) { - Utils.removePositioningStyles(target); - this._writeAttrs(target, node.x, node.y, node.width, node.height); - } else { - Utils.removePositioningStyles(target); - this._writeAttrs(target, node._beforeDragX, node._beforeDragY, node.width, node.height); - node.x = node._beforeDragX; - node.y = node._beforeDragY; - delete node._temporaryRemoved; - this.engine.addNode(node); - } - if (this._gsEventHandler[event.type]) { - this._gsEventHandler[event.type](event, target); - } - } - - this._updateContainerHeight(); - this._triggerChangeEvent(); - - this.engine.endUpdate(); - - // if we re-sized a nested grid item, let the children resize as well - if (event.type === 'resizestop') { - this._resizeNestedGrids(target); - } - } - - dd - .draggable(el, { - start: onStartMoving, - stop: onEndMoving, - drag: dragOrResize - }) - .resizable(el, { - start: onStartMoving, - stop: onEndMoving, - resize: dragOrResize - }); - node._initDD = true; // we've set DD support now - - // finally fine tune drag vs move by disabling any part... - if (node.noMove || this.opts.disableDrag) { - dd.draggable(el, 'disable'); - } - if (node.noResize || this.opts.disableResize) { - dd.resizable(el, 'disable'); - } - return this; - } - /** called to resize children nested grids when we/item resizes */ private _resizeNestedGrids(target: HTMLElement): GridStack { target.querySelectorAll('.grid-stack').forEach((el: GridHTMLElement) => { @@ -1575,207 +1312,20 @@ export class GridStack { return this; } - /** @internal call to setup dragging in from the outside (say toolbar), with options */ - private _setupDragIn(): GridStack { - if (!this.opts.staticGrid && typeof this.opts.dragIn === 'string') { - if (!dd.isDraggable(this.opts.dragIn)) { - dd.dragIn(this.opts.dragIn, this.opts.dragInOptions); - } - } - return this; - } - - /** @internal called to setup a trash drop zone if the user specifies it */ - private _setupRemoveDrop(): GridStack { - if (!this.opts.staticGrid && typeof this.opts.removable === 'string') { - let trashZone = document.querySelector(this.opts.removable) as HTMLElement; - if (!trashZone) return this; - // only register ONE dropover/dropout callback for the 'trash', and it will - // update the passed in item and parent grid because the 'trash' is a shared resource anyway, - // and Native DD only has 1 event CB (having a list and technically a per grid removableOptions complicates things greatly) - if (!dd.isDroppable(trashZone)) { - dd.droppable(trashZone, this.opts.removableOptions) - .on(trashZone, '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); - }) - .on(trashZone, 'dropout', function(event, el) { // same - let node = el.gridstackNode; - if (!node || !node.grid) return; - delete el.dataset.inTrashZone; - node.grid._clearRemovingTimeout(el); - }); - } - } - return this; - } - - /** @internal called to add drag over support to support widgets */ - private _setupAcceptWidget(): GridStack { - if (this.opts.staticGrid || !this.opts.acceptWidgets) return this; - - let onDrag = (event, el: GridItemHTMLElement) => { - let node = el.gridstackNode; - let pos = this.getCellFromPixel({left: event.pageX, top: event.pageY}, true); - let x = Math.max(0, pos.x); - let y = Math.max(0, pos.y); - if (!node._added) { - node._added = true; - - node.el = el; - node.x = x; - node.y = y; - delete node.autoPosition; - this.engine.cleanNodes(); - this.engine.beginUpdate(node); - this.engine.addNode(node); - - this._writeAttrs(this.placeholder, node.x, node.y, node.width, node.height); - this.el.appendChild(this.placeholder); - node.el = this.placeholder; // dom we update while dragging... - node._beforeDragX = node.x; - node._beforeDragY = node.y; - - this._updateContainerHeight(); - } else if ((x !== node.x || y !== node.y) && this.engine.canMoveNode(node, x, y)) { - this.engine.moveNode(node, x, y); - this._updateContainerHeight(); - } - }; - - dd - .droppable(this.el, { - accept: (el: GridItemHTMLElement) => { - let node: GridStackNode = el.gridstackNode; - if (node && node.grid === this) { - return false; - } - if (typeof this.opts.acceptWidgets === 'function') { - return this.opts.acceptWidgets(el); - } - let selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets as string); - return el.matches(selector); - } - }) - .on(this.el, 'dropover', (event, el: GridItemHTMLElement) => { - - // see if we already have a node with widget/height and check for attributes - let node = el.gridstackNode || {}; - if (!node.width || !node.height) { - let w = parseInt(el.getAttribute('data-gs-width')); - if (w > 0) { node.width = w; } - let h = parseInt(el.getAttribute('data-gs-height')); - if (h > 0) { node.height = h; } - } - - // 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 not calculate the grid size based on element outer size - let width = node.width || Math.round(el.offsetWidth / this.cellWidth()) || 1; - let height = node.height || Math.round(el.offsetHeight / this.getCellHeight(true)) || 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, ...{width, height, _added: false, _temporary: true}}); - newNode._isOutOfGrid = true; - el.gridstackNode = newNode; - el._gridstackNodeOrig = node; - - dd.on(el, 'drag', onDrag); - return false; // prevent parent from receiving msg (which may be grid as well) - }) - .on(this.el, 'dropout', (event, el: GridItemHTMLElement) => { - // jquery-ui bug. Must verify widget is being dropped out - // check node variable that gets set when widget is out of grid - let node = el.gridstackNode; - if (!node || !node._isOutOfGrid) { - return; - } - dd.off(el, 'drag'); - node.el = null; - this.engine.removeNode(node); - if (this.placeholder.parentNode === this.el) { - this.placeholder.remove(); - } - this._updateContainerHeight(); - el.gridstackNode = el._gridstackNodeOrig; - return false; // prevent parent from receiving msg (which may be grid as well) - }) - .on(this.el, 'drop', (event, el: GridItemHTMLElement, helper: GridItemHTMLElement) => { - this.placeholder.remove(); - - // notify previous grid of removal - let origNode = el._gridstackNodeOrig; - delete el._gridstackNodeOrig; - if (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(); - } - - let node = el.gridstackNode; // use existing placeholder node as it's already in our list with drop location - const _id = node._id; - this.engine.cleanupNode(node); // removes all internal _xyz values (including the _id so add that back) - node._id = _id; - node.grid = this; - dd.off(el, 'drag'); - // if we made a copy ('helper' which is temp) of the original node then insert a copy, else we move the original node (#1102) - // as the helper will be nuked by jqueryui otherwise - if (helper !== el) { - helper.remove(); - el.gridstackNode = origNode; // original item (left behind) is re-stored to pre dragging as the node now has drop info - el = el.cloneNode(true) as GridItemHTMLElement; - } else { - el.remove(); // reduce flicker as we change depth here, and size further down - dd.remove(el); - } - el.gridstackNode = node; - node.el = el; - - Utils.removePositioningStyles(el); - this._writeAttr(el, node); - this.el.appendChild(el); - this._updateContainerHeight(); - this.engine.addedNodes.push(node); - this._triggerAddEvent(); - this._triggerChangeEvent(); - - this.engine.endUpdate(); - if (this._gsEventHandler['dropped']) { - this._gsEventHandler['dropped']({type: 'dropped'}, origNode && origNode.grid ? origNode : undefined, node); - } - - // 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); - }); - - return false; // prevent parent from receiving msg (which may be grid as well) - }); - return this; - } - /** @internal convert a potential selector into actual element */ - private static getElement(els: GridStackElement = '.grid-stack-item'): GridItemHTMLElement { + public static getElement(els: GridStackElement = '.grid-stack-item'): GridItemHTMLElement { return Utils.getElement(els); } /** @internal */ - private static getElements(els: GridStackElement = '.grid-stack-item'): GridItemHTMLElement[] { + public static getElements(els: GridStackElement = '.grid-stack-item'): GridItemHTMLElement[] { return Utils.getElements(els); } /** @internal */ - private static getGridElement(els: GridStackElement): GridHTMLElement { + public static getGridElement(els: GridStackElement): GridHTMLElement { return GridStack.getElement(els); } /** @internal */ - private static getGridElements(els: string): GridHTMLElement[] { + public static getGridElements(els: string): GridHTMLElement[] { return Utils.getElements(els); } @@ -1858,11 +1408,45 @@ export class GridStack { return this; } + /* + * drag&drop empty stubs that will be implemented in gridstack-dd.ts for non static grid + * so we don't incur the load unless needed. + * NOTE: had to make those methods public in order to define them else as + * GridStack.prototype._setupAcceptWidget = function() + * maybe there is a better way.... + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + + /** + * Enables/Disables moving. + * @param els widget or selector to modify. + * @param val if true widget will be draggable. + */ + public movable(els: GridStackElement, val: boolean): GridStack { return this; } + /** + * Enables/Disables resizing. + * @param els widget or selector to modify + * @param val if true widget will be resizable. + */ + public resizable(els: GridStackElement, val: boolean): GridStack { return this; } + /** @internal called to add drag over support to support widgets */ + 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 call to setup dragging in from the outside (say toolbar), with options */ + public _setupDragIn(): GridStack {return this; } + /** @internal prepares the element for drag&drop **/ + public _prepareDragDropByNode(node: GridStackNode): GridStack { return this; } + // legacy method renames /** @internal */ private setGridWidth = obsolete(this, GridStack.prototype.column, 'setGridWidth', 'column', 'v0.5.3'); /** @internal */ private setColumn = obsolete(this, GridStack.prototype.column, 'setColumn', 'column', 'v0.6.4'); /** @internal */ - private getGridHeight = obsolete(this, GridStackEngine.prototype.getRow, 'getGridHeight', 'getRow', 'v1.0.0'); + private getGridHeight = obsolete(this, GridStackEngine.prototype.getRow, 'getGridHeight', 'getRow', 'v1.0.0'); } diff --git a/src/index-h5.ts b/src/index-h5.ts index aed9ad2de..41f45679d 100644 --- a/src/index-h5.ts +++ b/src/index-h5.ts @@ -5,7 +5,7 @@ export * from './types'; export * from './utils'; export * from './gridstack-engine'; -export * from './gridstack-dd'; +export * from './gridstack-ddi'; export * from './gridstack'; export * from './h5/gridstack-dd-native'; diff --git a/src/index-jq.ts b/src/index-jq.ts index 83ff13e2a..58681bfa6 100644 --- a/src/index-jq.ts +++ b/src/index-jq.ts @@ -5,7 +5,7 @@ export * from './types'; export * from './utils'; export * from './gridstack-engine'; -export * from './gridstack-dd'; +export * from './gridstack-ddi'; export * from './gridstack'; export * from './jq/gridstack-dd-jqueryui'; diff --git a/src/index-static.ts b/src/index-static.ts index 322d9e1ab..7fd4aca5e 100644 --- a/src/index-static.ts +++ b/src/index-static.ts @@ -5,7 +5,7 @@ export * from './types'; export * from './utils'; export * from './gridstack-engine'; -export * from './gridstack-dd'; +export * from './gridstack-ddi'; export * from './gridstack'; // declare module 'gridstack'; for umd ? From fe1b7fec54ad707eee4ba4fd7ca49c1970e5a643 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sun, 22 Nov 2020 18:53:19 -0800 Subject: [PATCH 17/17] h5: remove jquery from tests, switch to h5 version * changed the tests to not have any $() jquery anymore and switch them to use the html5 DD instead * all demos now use html5 version, added two-jq.html for legacy and to compare * reverted back to karma-typescript 4.1.1 to get correct error line numbers * gridstack.all.js no longer exist. changed all to gridstack-h5.js and readme (more changes on how to include later) * isResizable (new) / isDraggable / isDroppable work on a single element as it was confusting to e list of items (could be miss-match) * latest tool lock file --- Gruntfile.js | 10 - README.md | 13 +- demo/advance-h5.html | 96 -- demo/advance.html | 2 +- demo/anijs.html | 2 +- demo/column.html | 2 +- demo/experiment/test.html | 86 -- demo/experiment/test2.html | 37 - demo/float.html | 2 +- demo/knockout.html | 2 +- demo/locked.html | 2 +- demo/nested.html | 2 +- demo/responsive.html | 2 +- demo/right-to-left(rtl).html | 2 +- demo/serialization.html | 2 +- demo/static.html | 2 +- demo/{two-h5.html => two-jq.html} | 2 +- demo/two.html | 2 +- demo/vue2js.html | 2 +- demo/vue3js.html | 2 +- doc/CHANGES.md | 2 +- karma.conf.js | 8 +- package.json | 2 +- .../1017-items-no-x-y-for-autoPosition.html | 2 +- spec/e2e/html/1102-button-between-grids.html | 2 +- spec/e2e/html/1142_change_event_missing.html | 2 +- .../html/1143_nested_acceptWidget_types.html | 2 +- spec/e2e/html/1155-max-row.html | 2 +- spec/e2e/html/1286-load.html | 2 +- spec/e2e/html/810-many-columns.html | 2 +- spec/gridstack-spec.ts | 524 ++++---- spec/utils-spec.ts | 10 +- src/gridstack-dd.ts | 18 +- src/h5/dd-base-impl.ts | 23 +- src/h5/gridstack-dd-native.ts | 28 +- src/jq/gridstack-dd-jqueryui.ts | 11 +- tsconfig.json | 3 +- webpack.config.js | 10 +- yarn.lock | 1060 ++++++++--------- 39 files changed, 869 insertions(+), 1116 deletions(-) delete mode 100644 demo/advance-h5.html delete mode 100644 demo/experiment/test.html delete mode 100644 demo/experiment/test2.html rename demo/{two-h5.html => two-jq.html} (99%) diff --git a/Gruntfile.js b/Gruntfile.js index b1e6813cb..052de2053 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -40,8 +40,6 @@ module.exports = function(grunt) { dist: { files: { 'dist/gridstack-poly.js': ['src/gridstack-poly.js'], - //'dist/jq/jquery.js': ['src/jq/jquery.js'], - //'dist/jq/jquery-ui.js': ['src/jq/jquery-ui.js'], 'dist/src/gridstack.scss': ['src/gridstack.scss'], 'dist/src/gridstack-extra.scss': ['src/gridstack-extra.scss'], } @@ -58,14 +56,6 @@ module.exports = function(grunt) { files: { 'dist/jq/jquery.js': 'src/jq/jquery.js', 'dist/jq/jquery-ui.js': 'src/jq/jquery-ui.js', - /* - 'dist/jq/gridstack-dd-jqueryui.min.js': 'dist/jq/gridstack-dd-jqueryui.js', - 'dist/gridstack-dd.min.js': 'dist/gridstack-dd.js', - 'dist/gridstack-engine.min.js': 'dist/gridstack-engine.js', - 'dist/gridstack-poly.min.js': 'src/gridstack-poly.js', - 'dist/types.min.js': 'dist/types.js', - 'dist/utils.min.js': 'dist/utils.js', - */ } } }, diff --git a/README.md b/README.md index 3b9d5a754..9eb02f1b3 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ alternatively in html ```html - + ``` ## Basic usage @@ -127,7 +127,7 @@ see [jsfiddle sample](https://jsfiddle.net/adumesny/jqhkry7g) as running example ## Requirements -GridStack no longer requires external dependencies as of v1.0.0 (lodash was removed in v0.5.0 and jquery API in v1.0.0). All you need to include is `gridstack.all.js` and `gridstack.min.css` (layouts are done using CSS column based %). +GridStack no longer requires external dependencies as of v1.0.0 (lodash was removed in v0.5.0 and jquery API in v1.0.0). All you need to include is `gridstack-h5.js` and `gridstack.min.css` (layouts are done using CSS column based %). ## API Documentation @@ -262,7 +262,7 @@ Please use [jQuery UI Touch Punch](https://github.com/furf/jquery-ui-touch-punch working on touch-based devices. ```html - + ``` @@ -358,12 +358,11 @@ v2.x is a Typescript rewrite of 1.x, removing all jquery events, using classes a # jQuery Application -We're working on implementing HTML5 drag'n'drop through the plugin system. Right now it is still jquery-ui based. Because of that we are still bundling `jquery` (3.5.1) + `jquery-ui` (1.12.1 minimal drag|drop|resize) internally in `gridstack.all.js`. IFF your app needs to bring your own version instead, you should **instead** 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)). +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) in `gridstack-jq.js`. IFF your app needs to bring your own version instead, you should **instead** 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)). -Note: v2.0.0 does not currently support importing GridStack Drag&Drop without also including our jquery + jqueryui. Still trying to figure how to make that bundle possible. You will have to use 1.x - -As for events, you can still use `$(".grid-stack").on(...)` while jqueryui is used internally for things we don't support, but recommended you don't as that will get dropped at some point. +NOTE: v2.x / v3.0.0 does not currently support importing GridStack Drag&Drop without also including our jquery + jquery-ui. Still trying to figure how to make that bundle possible. You will have to use 1.x for now... +As for events, you can still use `$(".grid-stack").on(...)` for the version that uses jquery-ui for things we don't support. # Changes diff --git a/demo/advance-h5.html b/demo/advance-h5.html deleted file mode 100644 index f21d5905a..000000000 --- a/demo/advance-h5.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - Advanced grid demo - - - - - - - - - - - - - -

Advanced Demo

-
-
-
-
- -
-
- Drop here to remove! -
-
-
-
-
- -
-
- Drag me in the dashboard! -
-
-
-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/demo/advance.html b/demo/advance.html index 76cde9696..3fb8f0c6e 100644 --- a/demo/advance.html +++ b/demo/advance.html @@ -13,7 +13,7 @@ - + - - - -
-

Drag demo

-

-
Manual
-
HTML5 D&D
-
- - - - - \ No newline at end of file diff --git a/demo/experiment/test2.html b/demo/experiment/test2.html deleted file mode 100644 index 7b3c95a69..000000000 --- a/demo/experiment/test2.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - -

Drag the W3Schools image into the rectangle:

- -
-
- - - - \ No newline at end of file diff --git a/demo/float.html b/demo/float.html index 69ef2eb66..64f8b887e 100644 --- a/demo/float.html +++ b/demo/float.html @@ -7,7 +7,7 @@ Float grid demo - + diff --git a/demo/knockout.html b/demo/knockout.html index 2fdbb23a3..d31591aad 100644 --- a/demo/knockout.html +++ b/demo/knockout.html @@ -9,7 +9,7 @@ - +
diff --git a/demo/locked.html b/demo/locked.html index 878555171..6af6ae0ee 100644 --- a/demo/locked.html +++ b/demo/locked.html @@ -7,7 +7,7 @@ Locked demo - + diff --git a/demo/nested.html b/demo/nested.html index b622dd3fa..2742e5ae8 100644 --- a/demo/nested.html +++ b/demo/nested.html @@ -7,7 +7,7 @@ Nested grids demo - + - +
diff --git a/demo/serialization.html b/demo/serialization.html index c27e56d4f..bffadc764 100644 --- a/demo/serialization.html +++ b/demo/serialization.html @@ -7,7 +7,7 @@ Serialization demo - +
diff --git a/demo/static.html b/demo/static.html index 52ab0f622..cac65ff44 100644 --- a/demo/static.html +++ b/demo/static.html @@ -7,7 +7,7 @@ Static Grid - + diff --git a/demo/two-h5.html b/demo/two-jq.html similarity index 99% rename from demo/two-h5.html rename to demo/two-jq.html index b4f3c8fe9..3ed4acba2 100644 --- a/demo/two-h5.html +++ b/demo/two-jq.html @@ -10,7 +10,7 @@ - +