Skip to content

fix(core): re-clamp the held resizable size when its bounds move - #5256

Draft
cixzhang wants to merge 1 commit into
mainfrom
fix/resizable-reclamp-on-bounds-change
Draft

fix(core): re-clamp the held resizable size when its bounds move#5256
cixzhang wants to merge 1 commit into
mainfrom
fix/resizable-reclamp-on-bounds-change

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

The problem

useResizable applies minSizePx/maxSizePx when it initializes and on every
path that sets the size, but never to the size it is already holding. When the
band itself moves — a resizable side region whose maximum is derived from the
width available to it — the region keeps a size that has become illegal, and the
bound the hook exists to guarantee is silently violated.

Measured in a real browser on a story whose maxSizePx is half its container,
with the panel driven out to the ceiling at a 1400px viewport:

viewport ceiling (aria-valuemax) panel width, before panel width, after
1400 667 667 667
1000 467 667 467
700 317 667 317
1400 (back) 667 667 317

At a 700px viewport the panel is 350px past its own ceiling: it covers the whole
shell and the content region is gone. ResizeHandle reports it faithfully —
aria-valuenow=667 against aria-valuemax=317, a value outside its own range.

The fix, and the asymmetry that makes it safe

The held size now follows the band when the band moves under it, in one
direction only
:

  • the band narrows so the held size falls outside it → correct the size into the
    band;
  • the band widens again → leave the size alone.

Growing back is what the viewer's choice looks like once the ceiling has moved.
A symmetric re-clamp would restore the pre-clamp size and the region would
rubber-band in and out as a window is dragged; the last row of the table is the
test for that, and it is the one a naive version fails.

The correction runs during render rather than in an effect, so the out-of-band
size is never committed: React re-runs the hook before the commit, nothing
paints at the illegal size, and no observer in the same commit sees it.

The four decisions this raised

snaps — reuse clampSize, don't invent a second rounding rule. With
snap points the correction lands on the nearest snap inside the new band
([100, 300, 500], ceiling drops to 400 → the region rests at 300, not 400).
Where no snap fits under the ceiling, the ceiling wins and the region rests off
a snap — which is exactly what clampSize already does on the drag path. The
band is a hard constraint; snaps are a preference. One definition of "legal
size" for every path.

onSizeChange — fires. The name says the size changed, and it did. A
consumer mirroring the size into its own state (a stored width, a label beside
the panel) that does not hear this renders a size the region does not have.
It is delivered as an effect, so the callback is never called during render.

A collapsed region — untouched. Its zero is deliberate, not a size that
drifted out of band, and clamping zero into [min, max] would silently expand a
region the viewer closed. expand() already applies the current band when it
restores the pre-collapse size, so the collapsed path was never the leak.

autoSaveId — the clamp is persisted. The alternative is remembering the
pre-clamp size and restoring it on reload, which is the rubber-band again with a
page load in the middle: the session already settled that the widened band does
not restore the size. One notion of current size, and it is the one that
persists.

Risk

Behavior only changes for a region whose minSizePx/maxSizePx actually change
after mount. Fixed bounds — every configuration in the stories, showcases and
templates — take an identity comparison per render and nothing else.

Testing

New colocated tests for the hook cover both directions, snaps, the collapsed
path, onSizeChange, persistence and multi-region; the 20 existing
ResizeHandle tests still pass. Verified in Chromium against local Storybook
with the numbers above; screenshots at 700px show the content region restored.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 20, 2026
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview Aug 20, 2026 5:26pm

Request Review

useResizable applied minSizePx/maxSizePx at initialization and on every
set path, but never to the size it was already holding. A region whose
maximum is derived from available width kept a size that became illegal
the moment that maximum dropped.

The held size now follows the band when the band moves under it, in one
direction only: outside the band it is corrected in, and a band that
widens again leaves it alone. The correction happens during render, so
the out-of-band size is never committed, and it reports through
onSizeChange like any other size change.
@cixzhang
cixzhang force-pushed the fix/resizable-reclamp-on-bounds-change branch from a3fbf8d to 83a9eb2 Compare August 20, 2026 17:22
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

Modified Components

Resizable (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 760 -
Complexity N/A Very High (88) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.8KB 1.2KB

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

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

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant