From 901969c90a60634f610ce68d0b813f77d4d44a6c Mon Sep 17 00:00:00 2001 From: Aniket Bansal Date: Mon, 21 Apr 2025 18:22:23 +0530 Subject: [PATCH 1/4] Support dragging the grid item from outside the grid item. --- demo/demo.css | 10 ++++++ demo/index.html | 1 + demo/title_drag_from_outside_gs_item.html | 44 +++++++++++++++++++++++ doc/README.md | 1 + src/dd-draggable.ts | 32 ++++++++++++----- src/types.ts | 2 ++ src/utils.ts | 2 +- 7 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 demo/title_drag_from_outside_gs_item.html diff --git a/demo/demo.css b/demo/demo.css index 27f7aa2de..659cf803c 100644 --- a/demo/demo.css +++ b/demo/demo.css @@ -59,6 +59,16 @@ h1 { cursor: move; min-height: 25px; background-color: #16af91; + &.outside { + display: none; + width: fit-content; + position: absolute; + cursor: move; + z-index: 99999; + &:hover { + display: block; + } + } } .card-header:hover { background-color: #149b80; diff --git a/demo/index.html b/demo/index.html index ed3b6b780..abbc9d802 100644 --- a/demo/index.html +++ b/demo/index.html @@ -32,6 +32,7 @@

Demos

  • Size To Content
  • Static
  • Title drag
  • +
  • Title drag from outside grid stack item
  • Transform (scale+offset)
  • Two grids
  • Two grids Vertical
  • diff --git a/demo/title_drag_from_outside_gs_item.html b/demo/title_drag_from_outside_gs_item.html new file mode 100644 index 000000000..7d1276da1 --- /dev/null +++ b/demo/title_drag_from_outside_gs_item.html @@ -0,0 +1,44 @@ + + + + + + + Title area drag from outside GridStack item + + + + + +
    +

    Title area drag from outside GridStack item

    +

    +
    - Drag here -
    +
    +
    +
    Hover on the panel to toggle drag title (which is outside grid-stack-item) visibility. The rest of the panel content doesn't drag.
    +
    +
    +
    + + + + diff --git a/doc/README.md b/doc/README.md index e4c31a9ef..a4b22596e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -148,6 +148,7 @@ v10.x supports a much richer responsive behavior, you can have breakpoints of wi - `scroll`?: boolean - default to 'true', enable or disable the scroll when an element is dragged on bottom or top of the grid. - `cancel`?: string - prevents dragging from starting on specified elements, listed as comma separated selectors (eg: '.no-drag'). default built in is 'input,textarea,button,select,option' - `helper`?: 'clone' | ((el: HTMLElement) => HTMLElement) - helper function when dragging side panel items that need to be cloned before dropping (ex: 'clone' or your own method) +- `dragElements`?: HTMLElement[] - references to the elements (outside the grid item) to be used for dragging grid item ## Grid attributes diff --git a/src/dd-draggable.ts b/src/dd-draggable.ts index 8a0bdb91b..68a788076 100644 --- a/src/dd-draggable.ts +++ b/src/dd-draggable.ts @@ -40,8 +40,6 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt protected dragOffset: DragOffset; /** @internal */ protected dragElementOriginStyle: Array; - /** @internal */ - protected dragEls: HTMLElement[]; /** @internal true while we are dragging an item around */ protected dragging: boolean; /** @internal last drag event */ @@ -65,13 +63,6 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt constructor(public el: GridItemHTMLElement, public option: DDDragOpt = {}) { super(); - // get the element that is actually supposed to be dragged by - const handleName = option?.handle?.substring(1); - const n = el.gridstackNode; - this.dragEls = !handleName || el.classList.contains(handleName) ? [el] : (n?.subGrid ? [el.querySelector(option.handle) || el] : Array.from(el.querySelectorAll(option.handle))); - if (this.dragEls.length === 0) { - this.dragEls = [el]; - } // create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions) this._mouseDown = this._mouseDown.bind(this); this._mouseMove = this._mouseMove.bind(this); @@ -80,6 +71,29 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt this.enable(); } + /** @intenal */ + protected get dragEls(): HTMLElement[] { + // get the element that is actually supposed to be dragged by + const handleName = this.option?.handle?.substring(1); + let dragEls: HTMLElement[] = []; + if (!handleName || this.el.classList.contains(handleName)) { + dragEls = [this.el]; + } else { + const n = this.el.gridstackNode; + dragEls = n?.subGrid ? [this.el.querySelector(this.option.handle) || this.el] : Array.from(this.el.querySelectorAll(this.option.handle)); + } + + if (this.option.dragElements?.length) { + dragEls = this.option.dragElements; + } + + if (dragEls.length === 0) { + dragEls = [this.el]; + } + + return dragEls; + } + public on(event: DDDragEvent, callback: (event: DragEvent) => void): void { super.on(event, callback); } diff --git a/src/types.ts b/src/types.ts index 4cc7e88b5..a5754fad7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -383,6 +383,8 @@ export interface DDDragOpt { cancel?: string; /** helper function when dropping: 'clone' or your own method */ helper?: 'clone' | ((el: HTMLElement) => HTMLElement); + /** references to the elements (outside the grid item) to be used for dragging grid item */ + dragElements?: HTMLElement[]; /** callbacks */ start?: (event: Event, ui: DDUIData) => void; stop?: (event: Event) => void; diff --git a/src/utils.ts b/src/utils.ts index 6aa104a71..ab46f54cf 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -434,7 +434,7 @@ export class Utils { */ static cloneDeep(obj: T): T { // list of fields we will skip during cloneDeep (nested objects, other internal) - const skipFields = ['parentGrid', 'el', 'grid', 'subGrid', 'engine']; + const skipFields = ['parentGrid', 'el', 'grid', 'subGrid', 'engine', 'dragElements']; // return JSON.parse(JSON.stringify(obj)); // doesn't work with date format ? const ret = Utils.clone(obj); for (const key in ret) { From 978467b0bc134ff3d3811a3c191c4a205bc68f4a Mon Sep 17 00:00:00 2001 From: Aniket Bansal Date: Tue, 22 Apr 2025 11:53:23 +0530 Subject: [PATCH 2/4] -improved sample --- demo/title_drag_from_outside_gs_item.html | 53 +++++++++++++++-------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/demo/title_drag_from_outside_gs_item.html b/demo/title_drag_from_outside_gs_item.html index 7d1276da1..e13a755cc 100644 --- a/demo/title_drag_from_outside_gs_item.html +++ b/demo/title_drag_from_outside_gs_item.html @@ -1,44 +1,61 @@ + Title area drag from outside GridStack item - + +

    Title area drag from outside GridStack item



    - Drag here -
    -
    -
    Hover on the panel to toggle drag title (which is outside grid-stack-item) visibility. The rest of the panel content doesn't drag.
    -
    +
    +
    +
    Hover on the panel to toggle drag title (which is outside grid-stack-item) visibility. The + rest of the panel content doesn't drag.
    +
    +
    +
    +
    +
    - Drag here -
    +
    This panel has title inside grid item. Only title drags, rest of the panel content doesn't drag.
    +
    +
    - + + \ No newline at end of file From 6bb22b43b43565a276564e461f8c4081778e75bb Mon Sep 17 00:00:00 2001 From: Aniket Bansal Date: Tue, 22 Apr 2025 18:31:03 +0530 Subject: [PATCH 3/4] added tests --- spec/gridstack-spec.ts | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/spec/gridstack-spec.ts b/spec/gridstack-spec.ts index bfbe3668c..5330b6bab 100644 --- a/spec/gridstack-spec.ts +++ b/spec/gridstack-spec.ts @@ -47,6 +47,20 @@ describe('gridstack >', function() { ''; // generic widget with no param let widgetHTML = '
    hello
    '; + // grid with one grid item which has item title outside grid + let gridstackExternalItemTitle = ` +
    +
    - Drag Here -
    +
    +
    +
    +
    Hover on the panel to toggle drag title (which is outside grid-stack-item) visibility. The + rest of the panel content doesn't drag.
    +
    +
    +
    +
    + `; describe('grid.init() / initAll() >', function() { beforeEach(function() { @@ -1986,4 +2000,63 @@ describe('gridstack >', function() { }); }); + describe("grid.moveExternal >", function () { + beforeEach(function () { + document.body.insertAdjacentHTML("afterbegin", gridstackExternalItemTitle); + }); + afterEach(function () { + document.body.removeChild(document.getElementById("gs-cont")); + }); + it("dragstart event not triggered on external element drag >", function () { + let externalTitle = document.querySelector(".title-header")!; + grid = GridStack.init(); + let test = 0; + grid.on("dragstart", () => { + test++; + }); + let mouseDownEvt = new MouseEvent("mousedown", { + bubbles: true, + cancelable: true, + clientX: externalTitle.getBoundingClientRect().x, + clientY: externalTitle.getBoundingClientRect().y, + }); + externalTitle?.dispatchEvent(mouseDownEvt); + let mousemove_event = new MouseEvent("mousemove", { + bubbles: true, + cancelable: true, + clientX: externalTitle.getBoundingClientRect().x + 5, + clientY: externalTitle.getBoundingClientRect().y + 5, + }); + document.dispatchEvent(mousemove_event); + expect(test).toBe(0); + }); + it("dragstart event triggered on external element drag >", function () { + let externalTitle = document.querySelector(".title-header")!; + grid = GridStack.init({ + draggable: { + dragElements: [externalTitle], + }, + }); + let test = 0; + grid.on("dragstart", () => { + test++; + }); + let mouseDownEvt = new MouseEvent("mousedown", { + bubbles: true, + cancelable: true, + clientX: externalTitle.getBoundingClientRect().x, + clientY: externalTitle.getBoundingClientRect().y, + }); + externalTitle?.dispatchEvent(mouseDownEvt); + let mousemove_event = new MouseEvent("mousemove", { + bubbles: true, + cancelable: true, + clientX: externalTitle.getBoundingClientRect().x + 5, + clientY: externalTitle.getBoundingClientRect().y + 5, + }); + document.dispatchEvent(mousemove_event); + expect(test).not.toBe(0); + }); + }); + }); From 06445bd75584ed825b9989a9bd7931f782a921f4 Mon Sep 17 00:00:00 2001 From: Aniket Bansal Date: Thu, 24 Apr 2025 16:29:09 +0530 Subject: [PATCH 4/4] - minor fix --- src/dd-draggable.ts | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/dd-draggable.ts b/src/dd-draggable.ts index 68a788076..0ef14ecc1 100644 --- a/src/dd-draggable.ts +++ b/src/dd-draggable.ts @@ -40,6 +40,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt protected dragOffset: DragOffset; /** @internal */ protected dragElementOriginStyle: Array; + /** @internal */ + protected dragEls: HTMLElement[]; /** @internal true while we are dragging an item around */ protected dragging: boolean; /** @internal last drag event */ @@ -63,6 +65,16 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt constructor(public el: GridItemHTMLElement, public option: DDDragOpt = {}) { super(); + // get the element that is actually supposed to be dragged by + const handleName = option?.handle?.substring(1); + const n = el.gridstackNode; + this.dragEls = !handleName || el.classList.contains(handleName) ? [el] : (n?.subGrid ? [el.querySelector(option.handle) || el] : Array.from(el.querySelectorAll(option.handle))); + if(option?.dragElements?.length) { + this.dragEls = option.dragElements + } + if (this.dragEls.length === 0) { + this.dragEls = [el]; + } // create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions) this._mouseDown = this._mouseDown.bind(this); this._mouseMove = this._mouseMove.bind(this); @@ -71,29 +83,6 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt this.enable(); } - /** @intenal */ - protected get dragEls(): HTMLElement[] { - // get the element that is actually supposed to be dragged by - const handleName = this.option?.handle?.substring(1); - let dragEls: HTMLElement[] = []; - if (!handleName || this.el.classList.contains(handleName)) { - dragEls = [this.el]; - } else { - const n = this.el.gridstackNode; - dragEls = n?.subGrid ? [this.el.querySelector(this.option.handle) || this.el] : Array.from(this.el.querySelectorAll(this.option.handle)); - } - - if (this.option.dragElements?.length) { - dragEls = this.option.dragElements; - } - - if (dragEls.length === 0) { - dragEls = [this.el]; - } - - return dragEls; - } - public on(event: DDDragEvent, callback: (event: DragEvent) => void): void { super.on(event, callback); }