Skip to content

fix(react): resize handles in an overlay layer — grab the divider from either side under sticky headers#86

Merged
zvndev merged 2 commits into
mainfrom
feat/resize-handle-overlay
Jul 9, 2026
Merged

fix(react): resize handles in an overlay layer — grab the divider from either side under sticky headers#86
zvndev merged 2 commits into
mainfrom
feat/resize-handle-overlay

Conversation

@zvndev

@zvndev zvndev commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

The bug (Kirby: "fix once and for all")

Under sticky headers — the stickyHeader prop and the row-virtualization surface (which pins th unconditionally) — every header cell gets position: sticky; z-index: var(--yable-z-header). Equal-z sibling ths each form their own stacking context, so a later cell paints over the previous cell's overhanging resize handle. The pointer hit zone and the col-resize cursor only worked when grabbing from the inner (left) side of the divider; the outer half was covered. All Bevrly tables run sticky + virtualization, so this is exactly the drift Kirby kept feeling. No z-index on an in-th handle can escape its own cell's stacking context — hence the overlay.

The fix

ResizeHandleOverlay renders all handles in one layer (.yable-resize-overlay), a child of .yable-main outside every th, painting above all header cells with a single z-index (--yable-z-resize-overlay: 35, above sticky+pinned header = 30, below content overlays = 40). Each hit zone straddles its divider symmetrically — the grab works identically from either side of the line, cursor shown across the full zone, visible bar exactly on the divider.

Handle positions are measured from the real header rects (relative to the layer) and refreshed on scroll (rAF-throttled, capture-phase for nested scrollers), container resize (ResizeObserver), and column size/order changes (a signature that also tracks a live onChange drag). Because it mirrors what the browser actually painted, one code path is correct across: sticky/non-sticky headers, pinned columns, row + column virtualization, horizontal scroll, and RTL. The grid-edge handle stays flush inside to avoid phantom horizontal scrollWidth.

Compatibility

The React table no longer renders the in-cell .yable-resize-handle; handles are .yable-resize-overlay-handle inside .yable-resize-overlay. Consumers styling the old class for the React grid should move to the overlay classes. @zvndev/yable-vanilla still renders the in-cell handle — its themes CSS is retained unchanged.

Tests

  • New regression guard: drag-resize from BOTH sides of the divider under a sticky virtualized header (grid-virtual). The OUTER-side grab now resizes — previously it hit the neighbouring th and did nothing.
  • All handle-alignment (center == boundary ±1px, straddles) and live-resize tests migrated to the overlay handle and pass, including the pinned-column case.

Verification

core 583 passed · react 328 passed · e2e 55 passed · typecheck clean · lint 0 errors.

Bumps: @zvndev/yable-react minor (0.19.0), @zvndev/yable-themes patch (0.6.3). No majors; core/vanilla untouched.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

…from either side

Under sticky headers (the stickyHeader prop and the row-virtualization surface)
every header th forms an equal-z stacking context, so a neighbouring cell paints
over the previous cell's overhanging resize handle. The hit zone — and the
col-resize cursor — only worked when grabbing from the inner side of the
divider; the outer half was covered. No z-index on an in-th handle can escape
its own cell's stacking context.

Lift the handles into one overlay layer (ResizeHandleOverlay), a child of
.yable-main outside every th, so they paint above all header cells with a single
z-index and each hit zone straddles its divider symmetrically. Handle positions
are MEASURED from the real header rects (relative to the layer) and refreshed on
scroll, container resize, and column size/order changes — so alignment holds
across sticky/non-sticky headers, pinned columns, row/column virtualization,
horizontal scroll, and RTL. The visible bar stays exactly on the divider; the
outer (grid-edge) handle stays flush inside to avoid phantom horizontal scroll.

The React table no longer renders the in-cell .yable-resize-handle; handles are
.yable-resize-overlay-handle in .yable-resize-overlay. The vanilla package still
renders the in-cell handle (its themes CSS is retained).

e2e: new both-sides drag test under a sticky virtualized header (grabbing the
OUTER side of the divider now resizes — the regression Kirby kept hitting); all
handle-alignment and live-resize tests migrated to the overlay handle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
table Ready Ready Preview, Comment Jul 9, 2026 1:00am
yable-demo Ready Ready Preview, Comment Jul 9, 2026 1:00am

Request Review

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zvndev
zvndev merged commit 5656f4f into main Jul 9, 2026
14 checks passed
@zvndev
zvndev deleted the feat/resize-handle-overlay branch July 9, 2026 01:04
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.

1 participant