diff --git a/doc/CHANGES.md b/doc/CHANGES.md index ab255a6ef..63b997553 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -120,7 +120,8 @@ Change log ## 11.1.2-dev (TBD) -* fix: [#2852](https://github.com/gridstack/gridstack.js/pull/2852) better React example. Thank you [CNine](https://github.com/Aysnine) +* feat: [#2695](https://github.com/gridstack/gridstack.js/issues/2695) 'Esc' to cancel now works on sidebar external items, also works dragging over trash. +* feat: [#2852](https://github.com/gridstack/gridstack.js/pull/2852) better React example. Thank you [CNine](https://github.com/Aysnine) * fix: [#2852](https://github.com/gridstack/gridstack.js/pull/2852) grid in tabs correctly handles CSS. Thank you [Luciano Martorella](https://github.com/lmartorella) * fix: [#2900](https://github.com/gridstack/gridstack.js/issues/2900) use attr `data-gs-widget` instead of `gridstacknode` (supported as well for backward compatibility) diff --git a/src/dd-draggable.ts b/src/dd-draggable.ts index 5a0d3b1cf..955a48093 100644 --- a/src/dd-draggable.ts +++ b/src/dd-draggable.ts @@ -9,6 +9,7 @@ import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; import { GridItemHTMLElement, DDUIData, GridStackNode, GridStackPosition, DDDragOpt } from './types'; import { DDElementHost } from './dd-element'; import { isTouch, touchend, touchmove, touchstart, pointerdown } from './dd-touch'; +import { GridHTMLElement } from './gridstack'; interface DragOffset { left: number; @@ -264,17 +265,16 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt /** @internal call when keys are being pressed - use Esc to cancel, R to rotate */ protected _keyEvent(e: KeyboardEvent): void { const n = this.el.gridstackNode as GridStackNodeRotate; - if (!n?.grid) return; - const grid = n.grid; + const grid = n?.grid || (DDManager.dropElement?.el as GridHTMLElement)?.gridstack; if (e.key === 'Escape') { - if (n._origRotate) { + if (n && n._origRotate) { n._orig = n._origRotate; delete n._origRotate; } - grid.engine.restoreInitial(); + grid?.cancelDrag(); this._mouseUp(this.mouseDownEvent); - } else if (e.key === 'r' || e.key === 'R') { + } else if (n && grid && (e.key === 'r' || e.key === 'R')) { if (!Utils.canBeRotated(n)) return; n._origRotate = n._origRotate || { ...n._orig }; // store the real orig size in case we Esc after doing rotation delete n._moving; // force rotate to happen (move waits for >50% coverage otherwise) diff --git a/src/gridstack-engine.ts b/src/gridstack-engine.ts index 4b6bed9a2..c0243054c 100644 --- a/src/gridstack-engine.ts +++ b/src/gridstack-engine.ts @@ -506,7 +506,7 @@ export class GridStackEngine { /** @internal restore all the nodes back to initial values (called when we leave) */ public restoreInitial(): GridStackEngine { this.nodes.forEach(n => { - if (Utils.samePos(n, n._orig)) return; + if (!n._orig || Utils.samePos(n, n._orig)) return; Utils.copyPos(n, n._orig); n._dirty = true; }); diff --git a/src/gridstack.ts b/src/gridstack.ts index 79b0f4d2d..8a223dc67 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -233,19 +233,16 @@ export class GridStack { /** @internal create placeholder DIV as needed */ public get placeholder(): GridItemHTMLElement { if (!this._placeholder) { - const placeholderChild = document.createElement('div'); // child so padding match item-content - placeholderChild.className = 'placeholder-content'; + this._placeholder = Utils.createDiv([this.opts.placeholderClass, gridDefaults.itemClass, this.opts.itemClass]); + const placeholderChild = Utils.createDiv(['placeholder-content'], this._placeholder); if (this.opts.placeholderText) { placeholderChild.textContent = this.opts.placeholderText; } - this._placeholder = document.createElement('div'); - this._placeholder.classList.add(this.opts.placeholderClass, gridDefaults.itemClass, this.opts.itemClass); - this.placeholder.appendChild(placeholderChild); } return this._placeholder; } /** @internal */ - protected _placeholder: HTMLElement; + protected _placeholder: GridItemHTMLElement; /** @internal prevent cached layouts from being updated when loading into small column layouts */ protected _ignoreLayoutsNodeChange: boolean; /** @internal */ @@ -998,6 +995,7 @@ export class GridStack { if (this.parentGridNode) delete this.parentGridNode.subGrid; delete this.parentGridNode; delete this.opts; + delete this._placeholder.gridstackNode; delete this._placeholder; delete this.engine; delete this.el.gridstack; // remove circular dependency that would prevent a freeing @@ -2074,6 +2072,22 @@ export class GridStack { return this; } + /** @internal call when drag (and drop) needs to be cancelled (Esc key) */ + public cancelDrag() { + const n = this._placeholder?.gridstackNode; + if (!n) return; + if (n._isExternal) { + // remove any newly inserted nodes (from outside) + n._isAboutToRemove = true; + this.engine.removeNode(n); + } else if (n._isAboutToRemove) { + // restore any temp removed (dragged over trash) + GridStack._itemRemoving(n.el, false); + } + + this.engine.restoreInitial(); + } + /** @internal removes any drag&drop present (called during destroy) */ protected _removeDD(el: DDElementHost): GridStack { dd.draggable(el, 'destroy').resizable(el, 'destroy'); @@ -2279,6 +2293,7 @@ export class GridStack { const wasAdded = !!this.placeholder.parentElement; // skip items not actually added to us because of constrains, but do cleanup #1419 const wasSidebar = el !== helper; this.placeholder.remove(); + delete this.placeholder.gridstackNode; // disable animation when replacing a placeholder (already positioned) with actual content const noAnim = wasAdded && this.opts.animate; @@ -2420,6 +2435,7 @@ export class GridStack { /** called when the item stops moving/resizing */ const onEndMoving = (event: Event) => { this.placeholder.remove(); + delete this.placeholder.gridstackNode; delete node._moving; delete node._event; delete node._lastTried;