Repository navigation
Conversation
The public animator (spec:AST-107 FR3, FR6): createAnimator(element) over the internal animator, with animate, decay, set, get, getVelocity, stop and destroy, from the package root; and useAnimator(ref), which takes the element's ref like the gesture hooks, so a gesture hook can share the element, and returns the animator. After every commit the hook follows the element behind the ref, through the attach lifecycle the gesture hooks share (useAttachment). The internal factory is renamed createMotionAnimator, along with its callers in the Slider's and the Switch's touch controllers and the pager's tests, so no internal factory shares a public factory's name. Docs, a changeset, and playing-cards templates in React and plain JavaScript, with their registrations. In both templates the card is an image named by its rank and suit, renamed on each deal; one card leaves at a time (a Discard press while a card leaves deals no second card), a leaving card takes no press, and a press on a card fading in moves only its position; and a drag the browser cancels springs back. The plain-JavaScript example mounts its deck from a callback ref on its wrapper, whose cleanup takes it down. The core API report gains the animator's rows. When the element behind useAnimator's ref unmounts, or a node replaces it, the animator's values return to rest, so a remount or a new node starts from the element's resting values. AST-107 records the owner's decision on four edge behaviors of the public animator and pan gesture (DEC-4): FR1 and FR6 state that a cancelled pan reports zero velocity, how the pan factory's destroy and the hook's unmount report, and that an opacity decay stops at 0 or 1; FR3 states the remount.
…s (spec:AST-107 FR1, FR6)
The public pan: `createPanGesture(element, {axis?, onPan}) → {destroy}` and
`usePanGesture(ref, {axis?, onPan})` over it, reporting
`{state, translation, velocity}` as a drag begins, changes, ends or is
cancelled, under a finger, a mouse and a pen; the translation leaves out the
travel the drag needed to begin, so an element that follows it does not
jump. Docs pages for both, with a playing-card example in plain JavaScript
and in React, the route lock and the sandbox's routes.
…ce and text colors Both playing-card pan blocks painted the card white and left its text color to the page, so in a dark theme the card's face read light on white. They now use the theme's surface background and primary text color. A case per block failed before.
usePanGesture kept its own node, axis and pan bookkeeping across three layout effects. It now attaches through useAttachment, Core's one owner of a node's attachment: one pan for the node behind ref with this axis, released when the node or the axis changes and at unmount. Its release still silences the pan first, so an effect never calls onPan. No behavior changes; the hook's cases pass as before.
… sentences Main's no-comment-provenance rule refuses a record or claim citation in a new source comment, and source comments use short, one-topic sentences. The pan factory's and the hook's headers and the package entry's gesture line cited the record they follow; the record's hooks path and the core architecture records still link those files. The factory's header and two test comments are split into short sentences. Comments only.
In both playing-card blocks, a drag that began during the 250 ms throw wrote the card's transition, which cancels the throw's transitions. They then end in transitioncancel, so the next card was never dealt: after a fast release the card stayed invisible and Discard did nothing. A drag that outlived the throw, or one a Discard interrupted, then moved the next card by its whole travel, a jump, and a fast release discarded a card the person never touched. The blocks now ignore a drag that meets a throw, to its end, and follow the next one. Cases per block, for a drag that begins during a throw and one a throw interrupts, failed before.
Both pan pages said a scroll that starts on the element moves it when no axis is set. That holds for a finger only: the pan takes no trackpad scroll, so a two-finger trackpad scroll over it still scrolls the page. They now say so. The factory test's header uses short sentences.
… types The new public exports, createPanGesture, usePanGesture and the PanGesture, PanGestureEvent, PanGestureOptions and PanGestureState types, get their rows in the core API report. Only these rows are added; rows that other changes left stale stay as they are.
…ack ref The example mounted its deck from an Effect after the table and button attached. A callback ref on the wrapper now owns that, as the animator's plain-JavaScript example does, and its cleanup takes the deck down. A case checks that the deck mounts with the wrapper and comes down, pan included, when it unmounts.
The comment restated the export below it. Comments only.
Both pan examples named their card "Top card" with an aria-label on a div with no role, which a screen reader may drop or read in place of the rank. The card is now an image named by its rank and suit, as the animator's examples name theirs, and each discard is announced by that name. A case per example failed before.
The header ran to 15 lines. The claim at 10 pt and the touch-action fact move into the factory's JSDoc, and the header keeps its input, output, position and SYNC lines. Comments only.
…element on snap points
`createDragToSnapPoints(element, {axis, snapPoints, flickThreshold?,
onSettle}) → {settleTo, destroy}` and `useDragToSnapPoints(ref, …)` over
it (spec:AST-107 FR2, FR6): a drag moves the element along its axis
under every pointer, and the release settles it where Core's one release
policy says (the nearest point; the next one past the flick speed, 1008
px/s by default; back to where the drag started when the release moves
back toward it), on the settle spring from the release velocity. Under
Reduce Motion the release cross-fades after a touch drag or on a coarse
primary pointer, and lands at once elsewhere. Docs pages and
playing-card examples for both, the route lock and the sandbox's routes,
and the core API report's rows for both. The plain-JavaScript example
mounts its card from a callback ref on its wrapper, whose cleanup takes
it down.
A caller handling an input event (a key, a click, a pointer, a touch) runs in that event's own task, never in a frame callback, so the first frame whose callbacks run after its call is the one that commits the call's animations, however long the task ran. The animator cannot tell that from a call a page's own frame callback makes before a busy task, whose browser start is right, so the caller says so with the internal option `start: 'next-frame'`. A call from rest made with it starts at that frame, and a later listener of the same event that holds the main thread delays the motion instead of letting it skip into its curve. - Chromium tells the page the start a frame or more after the compositor takes it. Such a call starts at its first frame, as a call whose first frame comes within LATE_START_MS already does, and also when an unrelated task delayed that frame. - WebKit dates the start at its last frame time before the task. The task's end is read at the call's first read, not at its first microtask, which runs before a later listener's block, so the dated start moves past the block. The first read stands for the task's end, so a first frame that merely comes late (more than LATE_START_MS after the call, with no block) moves the dated start to that frame too. Nothing was painted before it, so the motion waits for that slow frame instead of skipping into its curve. A call without the option, a call with an explicit start, and a call that continues motion keep the rule they had. Internal only: the public animator does not take it.
…al options) The public pan and snap-point drag keep their behavior (spec:AST-107 FR1, FR2) and now run on internal factories a Core component can use for a job the public layer does not expose (spec:AST-107 FR7): - createPan, under createPanGesture: the recognizer's own options (which pointers and scrolls start the drag, its claim distance, a begin gate, a selection lock), and what drove the drag and its travel from where the touch began in each report. - createSnapDrag, under createDragToSnapPoints: a model of where a drag draws its element (one for one, a spring, or nothing yet inside a dead zone, the first target catching a settle in flight) and where a release or a cancel lands; a custom property to draw into; settleTo with a spring, a velocity and a touch release, returning the settle's motion, and an onPass callback on the first frame the settle's curve passes an offset (no timer). - The animator draws x into a registered custom property (a length or a number), on the main thread, for a component whose parts all read one value, with an optional companion property drawn from x in the same keyframes. architecture:react-component-runtime INV13 names these jobs among the known public-API gaps, and spec:AST-107's path applicability links the two internal factories.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
|
/approve-spec 91d713b |
There was a problem hiding this comment.
Requesting changes before this can merge.
- The diff against
mainhas 7,917 authored lines because it also carries #8653, #8655, #8658 and #9261; please land those first, or rebase once they land, so this pull request reviews as its own 1,363-line commit.
The internal options themselves have spec cover: AST-107 governs the public layer, and an engineering owner approved the AST-107 and INV13 edits for this head. One question about the onPass contract and three small comment fixes are inline. None of them blocks.
[Automated review]
| * and -1 means at or below it. If the element is past it already, the | ||
| * call comes on the next frame. It never comes for a settle that lands | ||
| * short, that another settle or a drag replaces first, or that a destroy |
There was a problem hiding this comment.
Non-blocking: these two sentences conflict for a settle that starts past the offset and lands short. A settle from -200 to -100 with offset: -150, direction: -1 calls back once, on its first frame, and lands at -100. Either check the direction of travel, or drop "lands short" from the contract.
| /** | ||
| * The recognizer's own options: which pointers and scrolls start the drag, | ||
| * its claim distance, how many touches, a gate on its begin. Defaults are | ||
| * the recognizer's (every pointer, a trackpad's scroll). |
There was a problem hiding this comment.
Non-blocking: PanGestureRecognizer's allowedScrollTypes defaults to none, so by default the pan takes no trackpad scroll. Suggested wording: Defaults are the recognizer's: every pointer, and no scroll.
| * `translation` leaves out the travel the drag needed to begin. | ||
| * `totalTranslation` counts from where the touch began. `input` says what | ||
| * drove the drag, such as a trackpad's scroll. |
There was a problem hiding this comment.
Non-blocking: these lines repeat PanEvent's field docs below, so the header can drop them.
| options: PanGestureOptions, | ||
| ): PanGesture { | ||
| const {axis, onPan} = options; | ||
| // The public event carries only these three values. |
There was a problem hiding this comment.
Non-blocking: this restates the destructuring below. Keep only the reason, for example // totalTranslation and input stay internal.
Depends on #9261 (on #8658, #8655 and #8653). This pull request's own commit: vjeux/astryx@bb0ea84...91d713b
Status: draft. Already passed locally at this head: typecheck, lint (strict), the unit tests of both test projects, the accessibility contracts this change selects (Chromium), and the snap-point drag, gesture and animator interaction proofs in Chromium (pinned to two cores at their default of one worker, every case).
User need
A Core component whose drag needs more than the public layer offers should still run the shared drag path, not a private copy of it. Item's swipe row is the first such component. It chooses which pointers start its drag, models a finger with UIKit's dead zone and tracking spring, and draws its travel into one custom property. The public pan and snap-point drag expose none of that, so today such a component builds its own pan, frame loop and springs.
Problem and solution fit
createPanGestureandcreateDragToSnapPointsnow run on internal factories,createPanandcreateSnapDrag. Their behavior and tests do not change.architecture:react-component-runtimeINV13 lists these jobs among the known public-API gaps, asspec:AST-107FR7 requires.Current specification
spec:AST-107FR1 and FR2: the public pan reports a drag as values, and the public snap-point drag settles on snap points. Both keep their behavior.spec:AST-107FR7: Core uses the public layer, except for the known public-API gaps thatarchitecture:react-component-runtimeINV13 lists. This pull request adds the drag jobs there.Public delta and caller burden
packages/core/api-report.mddoes not change.Design and alternatives
Internal options:
createPan, undercreatePanGesture: which pointers and scrolls start a drag, its claim distance, a begin gate and the selection lock. Each report says what drove the drag, and its travel from where the touch began.createSnapDrag, undercreateDragToSnapPoints:settleTowith a spring, a velocity and a touch release, returning the settle's motion;settleTo({onPass}), a callback on the first animation frame whose presented value has passed an offset, read on the settle's own curve. It uses no timer, and never fires for a settle that lands short, is replaced, or is destroyed.xinto one registered custom property, on the main thread, with an optional companion. The companion is a second custom property drawn fromxby a pure mapping in the same keyframes, so it cannot drift from it.Alternative: make these options public. Not now:
spec:AST-107decides the public layer, and each option serves one Core job. INV13 records them as known gaps, so a later record can promote them.Evidence
createSnapDrag.test.ts, each internal option red first:createDragToSnapPoints.test.ts,createDragToSnapPoints.reducedMotion.test.tsandcreatePanGesture.test.tsx. The Reduce Motion test's stand-in pan now names the input that drives each event, since the pan reports it.Scope
Testing
pnpm exec vitest run packages/core/src/dragToSnapPoints packages/core/src/gestures packages/core/src/motion packages/core/src/hookspnpm check:repoandpnpm check:knowledge;ASTRYX_STRICT_LINT=1 pnpm lint: the source comments this change adds cite no record, claim, pull request or date.Writing rubric results
Records edited:
architecture:react-component-runtimeINV13: the known public-API gaps gain a drag's own pointer and scroll filter, claim distance and begin gate, a snap-point drag's own model of where a drag draws its element and where a release lands, and a drag drawn into one custom property, for spec-owner review;spec:AST-107's path applicability lists the two internal factories,dragToSnapPoints/createSnapDrag.tsandgestures/createPan.ts, sopnpm specs:forlists the record for them.R1 narration: none. The edits state what the system does, in the present tense.
R2 research: none.
R3 evidence: INV13's addition cites
spec:AST-057AR2, FR2 and FR5 for the row's jobs; no evidence cell names a pull request. The path-applicability list is paths only.R4 rejected alternatives: none added.
R5 change references: none; the records name no pull request or issue.
R6 open questions: none.