Skip to content

fix(drag): continue autoscroll when widget dragged past root container edge - #3311

Closed
developerVibhay wants to merge 1 commit into
gridstack:masterfrom
developerVibhay:fix/autoscroll-parent-grid
Closed

fix(drag): continue autoscroll when widget dragged past root container edge#3311
developerVibhay wants to merge 1 commit into
gridstack:masterfrom
developerVibhay:fix/autoscroll-parent-grid

Conversation

@developerVibhay

Copy link
Copy Markdown

Description

When a gridstack container is scrollable and you drag a widget aggressively toward the top of the browser window — pulling it fully above the container's visible area — autoscroll stops instead of continuing
to scroll up (or down when dragged below).

Root cause: _getClipping() in dd-draggable.ts had an early return for the "fully outside" case:

if (elRect.bottom < scrollRect.top || elRect.top > scrollRect.bottom) return 0;

This is correct for nested grid containers (the widget has left that grid, so the parent should take over). But when the scroll container is the document root, there is no parent to hand off to — scrolling
should continue at full speed.

Fix: Added an isRoot check so the early return only applies to nested containers:

const isRoot = scrollEl === (document.scrollingElement || document.documentElement);
if (!isRoot && (elRect.bottom < scrollRect.top || elRect.top > scrollRect.bottom)) return 0;

Example: Create a gridstack grid taller than the viewport (so the page scrolls). Grab the last widget and quickly drag it toward the top of the browser window past the visible area. Before this fix, autoscroll
would halt as soon as the widget left the visible bounds. After this fix, the container continues scrolling up until it reaches the top.

…r edge

When the scroll container is the document root, dragging a widget fully
above or below the visible area should keep scrolling. Previously the
'fully outside' early return in _getClipping stopped scrolling in that
case, making it impossible to scroll up by aggressively dragging near
the top edge.

Fix: skip the early-return only for nested (non-root) scroll containers.
Add 6 unit tests covering both the nested-stop and root-continue cases.
@developerVibhay
developerVibhay marked this pull request as draft July 17, 2026 06:00
@adumesny

adumesny commented Jul 18, 2026

Copy link
Copy Markdown
Member

shouldn't scrolling stop when we have nothing else to scroll instead of where item is relative to scrollRect ? I mean the original scrollElement shoud continue reguadless if we enter a parent grid or not. Also your check for main view but could be a regular parent div that still above all grid, so your logic isn't matching to wording anyway...

@adumesny adumesny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment

@adumesny

adumesny commented Aug 3, 2026

Copy link
Copy Markdown
Member

@developerVibhay
| pulling it fully above the container's visible area — autoscroll stops instead of continuing
to scroll up (or down when dragged below).

I don't agree with that. if I drag an item out of a grid, either to drop into another grid or trash can to delete, I surely wouldn't expect the old scrolled area to continue scrolling.... I think you code would possibly break nested grid gesture as well. closing.

@adumesny adumesny closed this Aug 3, 2026
@adumesny

adumesny commented Aug 3, 2026

Copy link
Copy Markdown
Member

from gemini 3.1 pro, wiht my feedback above

That is an excellent point and a very common UX concern. If you drag an element clearly out of a container to drop it somewhere else (like a sidebar trash can or an adjacent grid), it feels wrong if the original container keeps uncontrollably scrolling just because your mouse is vertically above or below it.

You are right that stopping the scroll when you "leave" the area is important. But we still need to solve the edge-case from PR 3311, where an aggressive flick upward to scroll the current grid temporarily pushes the dragged widget fully above the grid, halting the scroll by mistake.

The Solution: Horizontal Bounds
What if we distinguish between "dragging to scroll" and "leaving the grid" by looking at the horizontal boundaries?

When you flick a widget up or down to scroll, you usually stay roughly in the same column (horizontally aligned with the grid). But when you drag to a trash can or another grid, you move completely out of the grid's left/right boundaries.

We can update the clipping function to stop scrolling only if the widget is completely outside the grid horizontally:

/** @internal compute how many pixels the element is clipped */
protected _getClipping(el: HTMLElement, scrollEl: HTMLElement): number {
  const elRect = el.getBoundingClientRect();
  const scrollRect = scrollEl.getBoundingClientRect();
  const viewportH = window.innerHeight || document.documentElement.clientHeight;
  // 💡 NEW: If completely horizontally outside, assume the user left the area (e.g. to a sidebar/trash) and stop scrolling!
  if (elRect.right < scrollRect.left || elRect.left > scrollRect.right) {
    return 0;
  }
  const clippedBelow = elRect.bottom - Math.min(scrollRect.bottom, viewportH);
  const clippedAbove = elRect.top - Math.max(scrollRect.top, 0);
  if (clippedAbove < 0) return clippedAbove;
  if (clippedBelow > 0) return clippedBelow;
  return 0;
}

Why this addresses both concerns:
Dragging to Trash/Sidebar: If you grab a widget and drag it outside the grid horizontally, _getClipping returns 0, and the original grid safely stops scrolling exactly like you expect.
Aggressive scrolling: If you grab a widget and flick it fast towards the top of the grid to scroll up, it continues scrolling perfectly, even if the widget briefly escapes the grid vertically.
Leaving to a new grid: GridStack already has a separate safeguard. When you enter a new GridStack drop area, it explicitly calls DDManager.dragElement._stopScrolling() to detach from the old grid anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants