From 884df66c0eea17d63e1d5adecce8cf57595a84e5 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Sat, 20 Jun 2026 07:45:45 -0700 Subject: [PATCH] flicker subgrid drag issue * subgrid make sure to use entire parent grid-item (not smaller obsetted grid) for event handling to make sure we don't get bogus leave/enter from the gab between sugrids (see nested.html for demo) * this removes the flicker bug seen in demo (opus 4.8 high could not figure that one and went down a rabbit hole) --- doc/API.md | 21 +++++++++++---------- doc/CHANGES.md | 1 + src/dd-droppable.ts | 23 ++++++++++++++--------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/doc/API.md b/doc/API.md index 3a059a010..7e8e40271 100644 --- a/doc/API.md +++ b/doc/API.md @@ -4316,7 +4316,7 @@ Note: Use enable()/disable() methods to change state as other operations need to new DDDroppable(el, option): DDDroppable; ``` -Defined in: [dd-droppable.ts:27](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L27) +Defined in: [dd-droppable.ts:29](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L29) ###### Parameters @@ -4341,7 +4341,7 @@ Defined in: [dd-droppable.ts:27](https://github.com/adumesny/gridstack.js/blob/m destroy(): void; ``` -Defined in: [dd-droppable.ts:70](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L70) +Defined in: [dd-droppable.ts:76](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L76) Destroy this drag & drop implementation and clean up resources. Removes all event handlers and clears internal state. @@ -4360,7 +4360,7 @@ Removes all event handlers and clears internal state. disable(forDestroy): void; ``` -Defined in: [dd-droppable.ts:57](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L57) +Defined in: [dd-droppable.ts:63](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L63) Disable this drag & drop implementation. Subclasses should override to perform additional cleanup. @@ -4385,7 +4385,7 @@ Subclasses should override to perform additional cleanup. drop(e): void; ``` -Defined in: [dd-droppable.ts:146](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L146) +Defined in: [dd-droppable.ts:152](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L152) item is being dropped on us - called by the drag mouseup handler - this calls the client drop event @@ -4405,7 +4405,7 @@ item is being dropped on us - called by the drag mouseup handler - this calls th enable(): void; ``` -Defined in: [dd-droppable.ts:44](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L44) +Defined in: [dd-droppable.ts:50](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L50) Enable this drag & drop implementation. Subclasses should override to perform additional setup. @@ -4424,7 +4424,7 @@ Subclasses should override to perform additional setup. off(event): void; ``` -Defined in: [dd-droppable.ts:40](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L40) +Defined in: [dd-droppable.ts:46](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L46) Unregister an event callback for the specified event. @@ -4448,7 +4448,7 @@ Unregister an event callback for the specified event. on(event, callback): void; ``` -Defined in: [dd-droppable.ts:36](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L36) +Defined in: [dd-droppable.ts:42](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L42) Register an event callback for the specified event. @@ -4500,7 +4500,7 @@ Result from the callback function, if any updateOption(opts): DDDroppable; ``` -Defined in: [dd-droppable.ts:77](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L77) +Defined in: [dd-droppable.ts:83](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L83) Method to update the options and return the DD implementation @@ -4523,8 +4523,9 @@ Method to update the options and return the DD implementation | Property | Modifier | Type | Default value | Description | Defined in | | ------ | ------ | ------ | ------ | ------ | ------ | | `accept` | `public` | (`el`) => `boolean` | `undefined` | - | [dd-droppable.ts:25](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L25) | -| `el` | `public` | `HTMLElement` | `undefined` | The HTML element being extended | [dd-droppable.ts:27](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L27) | -| `option` | `public` | [`DDDroppableOpt`](#dddroppableopt) | `{}` | The drag & drop options/configuration | [dd-droppable.ts:27](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L27) | +| `el` | `public` | `HTMLElement` | `undefined` | The HTML element being extended | [dd-droppable.ts:29](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L29) | +| `eventEl` | `protected` | `HTMLElement` | `undefined` | - | [dd-droppable.ts:27](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L27) | +| `option` | `public` | [`DDDroppableOpt`](#dddroppableopt) | `{}` | The drag & drop options/configuration | [dd-droppable.ts:29](https://github.com/adumesny/gridstack.js/blob/master/src/dd-droppable.ts#L29) | *** diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 33c37927f..29b267334 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -144,6 +144,7 @@ Change log * NEW: vue wrapper: brand new wrapper follow same pattern as well * fix: [#3154](https://github.com/gridstack/gridstack.js/issues/3154) Esc key doesn't restore subgrid * fix: [#3231](https://github.com/gridstack/gridstack.js/issues/3231) drag broken in Firefox 147.0.4+ and Chrome 144+ due to `navigator.maxTouchPoints > 0` now returning true on desktop (macOS trackpad) +* fix: [#3291](https://github.com/gridstack/gridstack.js/pull/3291) flicker nested grid demo when dragging between sub-grids ## 12.6.0 (2026-04-08) * feat: [#3250](https://github.com/gridstack/gridstack.js/pull/3250) full RTL support - thank you [Daniel Cohen Gindi](https://github.com/danielgindi) diff --git a/src/dd-droppable.ts b/src/dd-droppable.ts index 4ea7d4f95..a79369031 100644 --- a/src/dd-droppable.ts +++ b/src/dd-droppable.ts @@ -24,11 +24,17 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt public accept: (el: HTMLElement) => boolean; + protected eventEl: HTMLElement; + constructor(public el: HTMLElement, public option: DDDroppableOpt = {}) { super(); // create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions) this._mouseEnter = this._mouseEnter.bind(this); this._mouseLeave = this._mouseLeave.bind(this); + // For sub-grids the .grid-stack element is inset from the .grid-stack-item cell by the item + // margins (--gs-item-margin-*). Register on the outer .grid-stack-item instead so adjacent + // cells are gapless — moving from one sub-grid to a sibling never passes through dead space. + this.eventEl = (this.el.closest('.grid-stack-item') as HTMLElement) || this.el; this.enable(); this._setupAccept(); } @@ -46,11 +52,11 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt super.enable(); this.el.classList.add('ui-droppable'); this.el.classList.remove('ui-droppable-disabled'); - this.el.addEventListener('mouseenter', this._mouseEnter); - this.el.addEventListener('mouseleave', this._mouseLeave); + this.eventEl.addEventListener('mouseenter', this._mouseEnter); + this.eventEl.addEventListener('mouseleave', this._mouseLeave); if (isTouch) { - this.el.addEventListener('pointerenter', pointerenter); - this.el.addEventListener('pointerleave', pointerleave); + this.eventEl.addEventListener('pointerenter', pointerenter); + this.eventEl.addEventListener('pointerleave', pointerleave); } } @@ -59,11 +65,11 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt super.disable(); this.el.classList.remove('ui-droppable'); if (!forDestroy) this.el.classList.add('ui-droppable-disabled'); - this.el.removeEventListener('mouseenter', this._mouseEnter); - this.el.removeEventListener('mouseleave', this._mouseLeave); + this.eventEl.removeEventListener('mouseenter', this._mouseEnter); + this.eventEl.removeEventListener('mouseleave', this._mouseLeave); if (isTouch) { - this.el.removeEventListener('pointerenter', pointerenter); - this.el.removeEventListener('pointerleave', pointerleave); + this.eventEl.removeEventListener('pointerenter', pointerenter); + this.eventEl.removeEventListener('pointerleave', pointerleave); } } @@ -176,4 +182,3 @@ export class DDDroppable extends DDBaseImplement implements HTMLElementExtendOpt }; } } -