Repository navigation
feat(Resizable): createResizable is public, and component:Resizable FR3 names it as the owner ResizeHandle and useResizable go through - #9372
Conversation
…t (spec:AST-107 FR6) Red: there is no createResizable yet. The tests drive a plain element through the factory: its role, name, value and tab stop, a pointer drag that holds its pointer, the keys, collapse and restore, a controlled collapse, disabled input, update and subscribe, a percentage basis, persistence, destroy mid-drag, a region with no handle yet, and an import that does no DOM work.
…ain element a window splitter that resizes a region, without React (spec:AST-107 FR6) createResizable(handle, options) gives the element role=separator, a tab stop and the catalog's English name when it has none of its own, its orientation and value, and answers a pointer drag that holds its pointer (so an embedded frame cannot swallow the drag), the arrow keys, Home, End, and Enter and a double-click to collapse and restore. It resolves, clamps, snaps and saves through resizableModel.ts, as useResizable does, reports the size through subscribe and onSizeChange, takes new options through update, and destroy removes every listener, observer and attribute it added. With a null handle the region still answers collapse, expand, resize and subscribe. The input lives in resizeGesture.ts. Importing either module does no DOM work. It is not exported from the package, so the changeset records it as maintenance.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR Analysis Report
Generated by PR Enrichment workflow | View current CI run |
|
AI review status for this pull request.
|
…ions update() replaces the options, as a new render does, so an option left out returns to its default; the region keeps its size and collapse state within the new options. Its JSDoc said the handle and the region keep their state, which read as a merge. A test pins the contract: a full set with one change keeps the selection, the name and the bounds, and a set that leaves options out returns them to their defaults.
…he region's state has one copy (architecture:public-component-api INV22) Red: the configuration-object form creates no factory yet. With a stand-in factory, the tests check that it creates one, with no handle, from the configuration, passes later options through update(), destroys it on unmount and returns what it reports, in its size, collapse state and props. Guards that pass on the current implementation and must keep passing on the factory: the form reports size and isCollapsed in the commit of onCollapseChange's own state, controlled or not, through collapse(), Enter, expand(), double-clicks and a drag past the threshold (component:Resizable FR8); it keeps its size and collapse state across an <Activity> that hides and shows it; and a configuration object kept across renders follows a container swapped behind containerRef.
…and ResizeHandle's `resizable` path goes through its input The configuration-object form is createResizable with no handle of its own, so the region's state has one copy, the factory's (spec:AST-107 FR6; architecture:public-component-api INV22). The props it returns carry what the factory reports, and a ResizeHandle given them drives the factory's region through its model, read at each input so a whole drag reaches the factory as it is now. ResizeHandle's input for a `resizable` prop is the factory's input (resizeGesture.ts) over the props, so the handle has one implementation of the drag, the keys and the double-click. Behavior unchanged: size and isCollapsed land in the commit of onCollapseChange's own state, a controlled isCollapsed included (FR8); the region keeps its size across an <Activity> (the hook's store remembers where it stood and seeds the next factory), and follows a container swapped behind containerRef with the same configuration. React's store subscriptions do not read the store again after an <Activity> shows its subtree, so the hook renders again from the layout phase when the factory made for the reveal reports something new. The factory stays internal, so no public API or record changes; the changeset records the move.
There was a problem hiding this comment.
createResizable, and the move of both useResizable forms and ResizeHandle onto it, fit the useResizable gap that architecture:public-component-api INV22 lists, but approval waits on an engineering-owner decision. An engineering owner needs to approve, for this exact head, the rewritten component:Resizable FR3 owner sentence and the public rows no current record names (ResizableController.update and subscribe, and the label, isReversed and isDisabled options) by commenting /approve-spec 062b726388ccc2efe77e3d943d83aef3eb846480; as an engineering owner, you can record it yourself. Two changes are still needed after that:
- A factory recreated before its container is measured (a ResizeHandle that mounts after the hook, or an
<Activity>hide and show of the configuration-object form) keeps and saves the temporary 1200px basis, so a'30%'default stays 360px instead of 240px (useResizable.ts:572-584). - The diff still carries #9297 and #9370 (5508 lines); merging those first leaves this pull request's own 1894 lines.
[Automated review]
| const previous = | ||
| entry == null | ||
| ? slot.remembered() | ||
| : readResizableSelection(entry.resizable); | ||
| entry?.resizable.destroy(); | ||
| const createdWith = | ||
| previous == null | ||
| ? optionsRef.current | ||
| : { | ||
| ...optionsRef.current, | ||
| defaultSize: previous.size, | ||
| defaultIsCollapsed: previous.isCollapsed, | ||
| }; |
There was a problem hiding this comment.
When the old factory was still on the temporary 1200px basis (a containerRef that is not laid out yet), this passes that placeholder size as a pixel defaultSize, so the new factory never resolves the percentage against the real container and saves the placeholder: defaultSize: '30%' stays 360px after the container measures 800px instead of 240px, with a ResizeHandle that mounts after the hook or an <Activity> hide and show. Carrying the previous selection only when it was made against a measured basis would keep AST-010's rule that the temporary basis is never final or persisted.
There was a problem hiding this comment.
Fixed in #9297: a factory made for a reveal or a late handle takes the region's size only when it was chosen against a measured basis, so a '30%' default resolves to 240px once the container measures. Tests cover both cases.
…ontainer after an <Activity> hid and showed the region before then (spec:AST-010) Red: while the container is not laid out, the temporary 1200px basis stands in, so a '30%' default shows 360px. A factory made for an <Activity> reveal took that stand-in as the region's selection, kept 360px after the container measured 800px, and saved it, where the default should resolve to 240px against the measured container and the temporary basis is never final or saved.
…ry basis's stand-in as the region's size (spec:AST-010) readResizableSelection reports the size only when it is a selection (saved, chosen, or the default resolved against a measured basis); while only the temporary 1200px basis stands in, it reports none, and the hook seeds the next factory with the collapse state alone, so the factory resolves its own default once the container measures. This holds for a factory made after an <Activity> hides and shows the region.
…reateResizable, and ResizeHandle renders the factory bound to its element (spec:AST-107 FR6) Red: there is no ref-taking useResizable yet. The tests check that it creates the factory on the element, passes later options through update(), destroys it on unmount and returns what it reports, in the commit that onCollapseChange's own state lands in, controlled or not (component:Resizable FR8); that a ResizeHandle given the ref renders the region and forwards its drag, keys and double-click to the factory, with its own isReversed and isDisabled, after a caller's onKeyDown (FR7); that a handle a child mounts from its own state, or hides and shows in its own <Activity>, is bound at once, and the region keeps its size across an <Activity> that hides the hook's own component; that a plain element drops its listeners when it unmounts and is named and announced through the message catalog; that the same options follow a container swapped behind containerRef; and that the hook renders its initial size on the server.
…s thin React wrapper, and ResizeHandle renders the factory bound to its element (spec:AST-107 FR6) The hook creates the factory on the element behind the ref as it mounts, passes later options through update(), destroys it as it unmounts, and returns what the factory reports, in the commit of the parent's own state, a controlled isCollapsed included (component:Resizable FR8). The factory follows the element behind the ref at every commit, and whenever a ResizeHandle given the ref attaches or lets go of its element, which a child can do without the hook's component rendering; each new factory starts where the region stood. A plain element is named and announced through the provider's catalog messages, which reach the factory through the package's internal channel. A ResizeHandle given the ref claims its element, so the factory binds to it instead of driving it: the handle renders the factory's region and forwards its pointer, keyboard and double-click events to the factory's input, after a caller's onKeyDown (FR7), and its own isReversed and isDisabled apply with the factory's options. A factory stays bound when the handle lets go of its element and claims it again. The handle follows the binding from the layout phase, so a factory bound in the commit that mounts or shows it renders before paint. A ResizeHandle with a `resizable` prop and a bound region warns once (useDevWarning).
…report rows and its changeset useResizable's page documents the handle ref passed first, with its options, and that a ResizeHandle reports its element as it mounts while a plain element is read when the hook's own component renders. UseResizableResult is exported, and the core API report gains the overload and the type (only those rows; on this base the committed report lacks rows that a later main refreshed).
… drag-across-a-frame proof and API report rows (spec:AST-107 FR6) createResizable, CreateResizableOptions and ResizableController are exported from the Resizable entry point and the root, in the pull request whose FR3 line names createResizable as the owner ResizeHandle and useResizable go through, so the public API and its record line land together. createResizable is documented as a hook beside useResizable (no category, so it is not a tile in the components gallery; the docsite's route lock names its page, and the doc compiler's pin reads it by name); its page says update() takes the full set of options, and useResizable's page names it as the factory both forms wrap. The story drives a plain divider over an embedded frame, and the browser proof drags across that frame, which the held pointer keeps from swallowing the drag. The core API report gains the export rows (only those), and the changeset announces the factory.
…and useResizable go through component:Resizable FR3 owned the drag, keys, double-click, snapping, persistence, reversal and size in ResizeHandle and useResizable. Both forms of the hook and both paths of the handle go through createResizable, now public, so FR3 names it as the owner they go through (architecture:public-component-api INV22).
062b726 to
7a6ab57
Compare
There was a problem hiding this comment.
Approval waits on an engineering-owner decision: component:Resizable FR3's reworded owner sentence and the public rows no current record names (ResizableController.update and subscribe, and the label, isReversed and isDisabled options) need a spec-owner approval for this exact head, which an engineering owner records by commenting /approve-spec 7a6ab579d185b006e69bbb75b1c656e387767184; you may record it yourself if you are an engineering owner. The earlier placeholder-basis issue is fixed, and ResizeHandle behaves as before in the stories checked, but three changes are still needed after that:
- A recreated factory drops a selection when its percentage bound's container measures 0: with
maxSize: '50%', a region resized to 300px comes back at its 200px default after an<Activity>hide and show (or a ref-form handle that attaches late), where the base keeps 300px (useResizable.ts:582-591). .changeset/create-resizable.mdsayslabelreplaces the announcement too; it names the handle only, and the collapsed state is always announced in English.- The diff still carries #9297 and #9370 (5654 lines); merging those first leaves this pull request's own 594 lines.
[Automated review]
| const createdWith = | ||
| previous == null | ||
| ? optionsRef.current | ||
| : { | ||
| ...optionsRef.current, | ||
| ...(previous.size === undefined | ||
| ? {} | ||
| : {defaultSize: previous.size}), | ||
| defaultIsCollapsed: previous.isCollapsed, | ||
| }; |
There was a problem hiding this comment.
The previous size reaches the new factory as a pixel defaultSize, and the options effect at lines 615-620 then calls update(options) with the caller's own defaultSize. When the new factory has a percentage bound and its container measures 0 at that moment, the default is not final yet, so the update replaces the carried size: with defaultSize: 200, maxSize: '50%', a region resized to 300px comes back at 200px once the container measures, after an <Activity> hide and show (the base keeps 300px) or a ref-form handle that attaches after resize(300). Passing the previous selection as the factory's selection (an internal seed for chosenSize) rather than as defaultSize would keep it through the update.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [feat] `createResizable(handle, options)` makes a plain element a window splitter that resizes a region, without React: a pointer drag that holds its pointer across embedded frames, the arrow keys, Home, End, Enter and a double-click, with bounds, snap points, collapse and saved sizes. It reports the size through `subscribe` and `onSizeChange`, and you size the panel; `destroy` removes every listener and attribute it added. The handle is named and announced with the message catalog's English messages, unless you pass `label`. `useResizable(handleRef, options)` is its React form. |
There was a problem hiding this comment.
label sets only the handle's name; a bare factory always announces its collapsed state with the English catalog's "Collapsed" (createResizable.ts:805-808). Suggested: "The handle is named with the catalog's English message unless you pass label, and announces its collapsed state in English."
The third of five stacked pull requests for createResizable. It is built on
useResizable(handleRef, options)(#9370) and merges after it; this branch carries the commits of #9297 and #9370. This head's own changes: vjeux/astryx@4905b79...7a6ab57. The LayoutPanel slide (#9300) and the deprecation of the configuration-object form (#9202) are stacked on it.User need
architecture:public-component-apiINV22 has every public interaction hook delegate to a publiccreate…(element, options)factory (spec:AST-107FR6), and lists useResizable among the hooks without one. After #9297 and #9370 both forms of useResizable and both paths of ResizeHandle go through createResizable, but the factory is internal: a page without React cannot make a divider a window splitter, andcomponent:ResizableFR3 still names ResizeHandle and useResizable as the owners of the behavior.Problem and solution fit
createResizable(handle, options) → {size, isCollapsed, collapse, expand, resize, update, subscribe, destroy}, with itsCreateResizableOptionsandResizableControllertypes, is exported from@astryxdesign/core/Resizableand the root. It makes a plain element a window splitter that resizes a region, without React: a pointer drag that holds its pointer across embedded frames, the arrow keys, Home, End, Enter and a double-click, with bounds, snap points, collapse and saved sizes. It reports the size; the caller sizes the panel.component:ResizableFR3 names createResizable as the owner ResizeHandle and useResizable go through, so the public API and its record line land together.update()takes the full set of options, as a new render does.architecture:internationalizationINV3);labelnames the handle in another language. How non-React callers localize the collapsed text is pending a system-wide decision by the owner, since no existing factory announces text and this one would set the precedent; this pull request merges after that answer. The docs state the English default and nothing beyond it.Current specification
spec:AST-107FR6architecture:public-component-apiINV22architecture:internationalizationINV3component:ResizableFR3 (edited here)Public delta and caller burden
New exports from
@astryxdesign/core/Resizableand the root:createResizable,CreateResizableOptionsandResizableController; the core API report gains their rows. Nothing existing changes.Design and alternatives
update()is synchronous, so itssizeis already current.Writing rubric results
Record touched:
component:Resizable(FR3). Only FR3's owner sentence changes; no other row changes.Rejected:line inside its decision; untouched here.Evidence
Scope
Testing
Local gates at 7a6ab57: unit tests (test:ui, 17,471 passed), check:repo, lint, root build then typecheck, the core API report regenerated from this build (it gains only this pull request's rows; on this base, 00146a7, the committed report lacks rows that #9344 later refreshed), the node project (only the known cache.test.mjs red), the docsite build and tests (732 passed), the Storybook build, accessibility in Chromium (77/77), the accessibility audit of Resizable, SideNav and Layout (0 new) and of every story (0 new), interaction Chromium (createResizable and SideNav's rail motion, 11/11), and the drag-across-a-frame proof under stress: Chromium 2-core ×24 24/24, WebKit CI lane (4 CPUs, CI, LP_NUM_THREADS=4, 1 worker) ×10 10/10, WebKit 3-CPU with 1 worker 10/10 and with 16 workers 40/40.