Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the governing spec: spec:AST-107 FR3–FR6, which defines createAnimator, the @astryxdesign/core/animator entry and useAnimator, is only in open #8600. On main, architecture:react-component-runtime INV13 and spec:AST-017 FR21 still keep motion internal, and architecture:public-component-api INV21 has no carve-out yet for a hook that wraps a factory. Any engineering owner can settle it by landing #8600 or by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these need fixing:
- Land #8076 first and rebase: this diff carries 17,491 authored lines, and that stack alone differs from
mainby 15,672. scripts/motion-node-load.test.mjsstill imports the renamedcreateAnimatorfrommotion/animatorand fails.- The two playing-card templates need a Changeset naming
@astryxdesign/cli, a refreshed ShadCN route lock and the Sandbox's static route list; lint, docsite-data and build-sandbox fail without them. - The templates should read zero release velocity after a still pause and deal the next card once the old one fades, and the docs should call
animate's motion Astryx's state-change spring rather than the theme's, since roles stay internal underspec:AST-094DEC-11.
[Automated review]
| * the press paint's styles). | ||
| */ | ||
| const ENTRY = ` | ||
| export {createAnimator, animatorFor, SETTLE_THRESHOLD} from './motion/animator'; |
There was a problem hiding this comment.
This still re-exports createAnimator from ./motion/animator, which this PR renames to createMotionAnimator. esbuild leaves the binding undefined, and the child-process test fails with TypeError: m.createAnimator is not a function. Lines 103, 106 and 115 need the same rename.
| /** The release velocity in px/s, over the drag's last 80 ms. */ | ||
| function releaseVelocity(samples: Sample[]): {x: number; y: number} { | ||
| const last = samples.at(-1); | ||
| const first = samples.find(s => last != null && last.t - s.t <= 80); |
There was a problem hiding this comment.
The release velocity comes from the last 80 ms of moves and ignores how long the pointer rested before pointerup. A drag that stops and holds still for 600 ms before release still throws the card away (Chromium), although the template says a gentle release springs it back. spec:AST-100 FR2 reads zero after a still pause. Passing the release's timeStamp and returning zero when the newest sample is older than about 80 ms would fix it, here and in the plain-JavaScript template's onUp.
| const speed = Math.hypot(velocity.x, velocity.y); | ||
| const boost = speed < MIN_THROW ? MIN_THROW / Math.max(speed, 1) : 1; | ||
| const throwX = speed < 1 ? MIN_THROW : velocity.x * boost; | ||
| await Promise.all([ |
There was a problem hiding this comment.
This waits for the whole coast, about 800 px at 1,600 px/s. After Discard, the table stays empty and the live region silent for 4.55 s before the next card is dealt. Dealing and announcing once animate({opacity: 0}) resolves, then calling stop(), keeps the keyboard path responsive.
| name: 'animate', | ||
| type: '(target: {x?, y?, scale?, rotate?, opacity?}, options?: {velocity?}) => Promise<void>', | ||
| description: | ||
| "Springs the given channels to `target` from wherever they are on screen, on the theme's state-change motion, and resolves when they arrive (or as soon as a later call takes them over). `x` and `y` are px, `scale` is 1 at the element's size, `rotate` is degrees and `opacity` runs from 0 to 1. Pass `velocity` (units per second, such as a drag's release velocity) to start with it; without it a channel keeps the velocity it has on screen, so interrupting a motion never jumps.", |
There was a problem hiding this comment.
"On the theme's state-change motion": the animator plays springs.stateChange's table value, and no theme can retune it while spec:AST-094 DEC-11 keeps the roles internal (the motion README says the same). "Astryx's state-change spring" would be accurate; the same wording is at line 77, in useAnimator.doc.mjs lines 33 and 38, in the changeset and in the createAnimator JSDoc.
| @@ -0,0 +1,7 @@ | |||
| --- | |||
| '@astryxdesign/core': patch | |||
There was a problem hiding this comment.
This names only @astryxdesign/core, but the two new playing-card templates ship as @astryxdesign/cli output, and CI lint's changeset coverage fails on that. A separate [feat] changeset naming @astryxdesign/cli would clear it (spec:AST-017 FR7).
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the governing spec: spec:AST-107 FR3–FR6, which defines createAnimator, the @astryxdesign/core/animator entry and useAnimator, is only in open #8600, while on main, architecture:react-component-runtime INV13 and spec:AST-017 FR21 keep the animator internal and architecture:public-component-api INV21 has no exception yet for a hook over a factory. Any engineering owner can settle it by landing #8600 or by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these need fixing:
- Land #8076 first and rebase: this diff carries 17,530 authored lines, 15,672 of them from that stack.
scripts/motion-node-load.test.mjsstill imports the renamedcreateAnimatorfrommotion/animator(lines 31, 103, 106 and 115), so its child-process test fails.useAnimator.doc.mjsline 33 still says calls work before the ref attaches, which this head no longer does.- The templates should read zero release velocity after a still pause and deal the next card once the old one fades, and the docs and JSDoc should drop
usePanGestureanduseAnimatedLayout, which the package does not have yet, and call the motion Astryx's state-change spring rather than the theme's.
[Automated review]
| * the press paint's styles). | ||
| */ | ||
| const ENTRY = ` | ||
| export {createAnimator, animatorFor, SETTLE_THRESHOLD} from './motion/animator'; |
There was a problem hiding this comment.
./motion/animator now exports createMotionAnimator, not createAnimator, so this re-export finds nothing and m.createAnimator(null) at line 103 throws in the child process: the second test fails whether the bundler stops on the missing name or leaves it undefined. Lines 103, 106 and 115 need the same rename.
| name: 'animator', | ||
| type: 'AnimatorHandle', | ||
| description: | ||
| "The element's animator, the same object on every render: `animate(target, {velocity?})` springs channels to a target on the theme's state-change motion, `decay({x?, y?})` coasts them from a release velocity, `set(values)` jumps them, `get()` and `getVelocity()` read them, and `stop()` halts them. The channels are `x` and `y` (px), `scale` (1 at the element's size), `rotate` (degrees) and `opacity` (0 to 1). `animate` and `decay` return a promise that resolves when the motion ends. Calling it never re-renders the component, and it works before the ref attaches: the element shows its values when it mounts.", |
There was a problem hiding this comment.
"It works before the ref attaches: the element shows its values when it mounts" no longer holds: before the ref attaches every call does nothing, as line 27 and the hook's test an animator used before its ref attaches does nothing and resolves at once say.
| function releaseVelocity(samples: Sample[]): {x: number; y: number} { | ||
| const last = samples.at(-1); | ||
| const first = samples.find(s => last != null && last.t - s.t <= 80); | ||
| if (last == null || first == null || last.t === first.t) { | ||
| return {x: 0, y: 0}; | ||
| } | ||
| const seconds = (last.t - first.t) / 1000; | ||
| return {x: (last.x - first.x) / seconds, y: (last.y - first.y) / seconds}; | ||
| } |
There was a problem hiding this comment.
The 80 ms window is measured back from the newest move, not from the release. After a fast drag that stops and holds still, no more moves arrive, so the release still reads the earlier speed and throws the card, although the copy says a gentle release springs back. Passing the release event's timeStamp and returning zero when the newest sample is older than the window would fix it, here and in the plain-JavaScript template's onUp.
| await Promise.all([ | ||
| animator.decay({x: throwX, y: velocity.y * boost}), | ||
| animator.animate({opacity: 0}), | ||
| ]); |
There was a problem hiding this comment.
This waits for the whole coast: from the Discard button, 1,600 px/s travels about 800 px and settles about 4.5 s later, so the table stays empty and the live region silent long after the card has faded. Dealing and announcing once animate({opacity: 0}) resolves, then calling stop(), keeps the keyboard path responsive.
| export interface AnimatorAnimateOptions { | ||
| /** | ||
| * Each channel's velocity at the start, such as a drag's release velocity | ||
| * (`usePanGesture` reports it at `ended`). A channel without one keeps the |
There was a problem hiding this comment.
usePanGesture is not in the package yet, so this public JSDoc points callers to a hook they cannot import.
| ], | ||
| usage: { | ||
| description: | ||
| "Animates one element with the motion Astryx's own components use, without React. `animate` springs an element to new values on the theme's state-change motion, `decay` flings it with the momentum of a released scroll view, and `set` moves it directly, so a card can follow a finger, snap back and be thrown away. Use the `useAnimator` hook in React. To animate an area's layout as its items change, use `useAnimatedLayout`; for what a component already animates, such as a sheet's detents or a carousel's snapping, use the component's own props.", |
There was a problem hiding this comment.
Two claims here go beyond the package: no theme can retune springs.stateChange while motion roles stay internal (spec:AST-094 DEC-11), so "Astryx's state-change spring" would be accurate, and useAnimatedLayout does not exist in the package yet. The same wording is at line 38, in useAnimator.doc.mjs lines 33 and 38, in the changeset and in the createAnimator JSDoc.
…oduction registry checks The same two checks meta#8653's fix meets, for this pull request's own docs: - createDragToSnapPoints' doc is a core doc that component <Name> reads but the component list leaves out (a vanilla factory), so the CLI's doc inputs test pins it. - The useDragToSnapPoints example imports the hook from the released hooks subpath, so the hidden production registry built it against the published core, which has no such hook yet. Its doc now names useDragToSnapPoints among the components it uses, so production skips it until the hook is released. A latest-target generate shows the item without this line and drops it with it.
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the governing spec: spec:AST-107 FR3–FR6, which defines createAnimator, the @astryxdesign/core/animator entry and useAnimator, is only in open #8600, while on main, architecture:react-component-runtime INV13 and spec:AST-017 FR21 keep the animator internal and architecture:public-component-api INV21 has no exception yet for a hook over a factory. Any engineering owner can settle it by landing #8600 or by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these need fixing:
- Land #8076 first and rebase: this diff carries 17,538 authored lines, 15,669 of them from that stack.
useAnimator.doc.mjsline 33 still says calls work before the ref attaches, which this head no longer does.- Both templates should read zero release velocity after a still pause, and deal and announce the next card once the old one fades rather than after the whole 4.5 s coast.
- The docs and JSDoc should drop
usePanGestureanduseAnimatedLayout, which the package does not have yet, and call the motion Astryx's state-change spring rather than the theme's.
[Automated review]
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the governing spec: spec:AST-107 FR3–FR6, which defines createAnimator, the @astryxdesign/core/animator entry and useAnimator, is only in open #8600, while on main, architecture:react-component-runtime INV13 and spec:AST-017 FR21 keep the animator internal and architecture:public-component-api INV21 has no exception yet for a hook over a factory. Any engineering owner can settle it by landing #8600 or by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these need fixing:
- Land #8076 first and rebase: this diff carries 17,663 authored lines, 15,794 of them from that stack.
useAnimator.doc.mjsline 33 still says calls work before the ref attaches, which the hook does not do.- Both templates should read zero release velocity after a still pause, and deal and announce the next card once the old one fades rather than after the whole 4.5 s coast.
- The docs and JSDoc should drop
usePanGestureanduseAnimatedLayout, which the package does not have yet, and call the motion Astryx's state-change spring rather than the theme's.
[Automated review]
PR Analysis Report
The exact-head preview is not available yet. Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
Requesting changes: the public createAnimator and useAnimator match spec:AST-107 FR3–FR6, which is now on main, but this branch also carries the open #8076 and #8677 stacks (19,819 authored lines in all), so please land those first and rebase onto main. These fixes are needed in this pull request's own files:
useAnimator.doc.mjsline 33 says calls work before the ref attaches; they do nothing until it does.- Both playing-card templates should read zero release velocity after a still pause (a 150 px drag held still for 600 ms is still thrown away).
- Both templates should deal and announce the next card once the discarded one fades, not after the whole 4.5 s coast.
- The docs and the
velocityJSDoc should dropuseAnimatedLayoutandusePanGesture, which the package does not export yet.
[Automated review]
| name: 'animator', | ||
| type: 'AnimatorHandle', | ||
| description: | ||
| "The element's animator, the same object on every render: `animate(target, {velocity?})` springs channels to a target on the theme's state-change motion, at its tempo and character, `decay(velocity)` coasts them from a release velocity, `set(values)` jumps them, `get()` and `getVelocity()` read them, and `stop()` halts them. The channels are `x` and `y` (px), `scale` (1 at the element's size), `rotate` (degrees) and `opacity` (0 to 1). `animate` and `decay` return a promise that resolves when the motion ends. Calling it never re-renders the component, and it works before the ref attaches: the element shows its values when it mounts.", |
There was a problem hiding this comment.
"It works before the ref attaches: the element shows its values when it mounts" no longer holds: before the ref attaches every call does nothing and resolves at once, as line 27, createAnimator.ts (inactive()) and the hook's test say.
| } | ||
| origin = null; | ||
| const last = samples.at(-1); | ||
| const first = samples.find(s => last != null && last.t - s.t <= 80); |
There was a problem hiding this comment.
The window is measured back from the newest move, not from the release, so a drag that stops and rests before pointerup is still thrown: a 150 px drag held still for 600 ms lands at about 828 px in Chromium. Passing the release event's timeStamp to onUp and returning zero when the newest sample is older than 80 ms would fix it.
| async function discard(vx: number, vy: number) { | ||
| const speed = Math.hypot(vx, vy); | ||
| const boost = speed < MIN_THROW ? MIN_THROW / Math.max(speed, 1) : 1; | ||
| await Promise.all([ |
There was a problem hiding this comment.
This waits for the whole coast, so the next card comes about 4.5 s after Discard although the card is transparent long before. Dealing once animator.animate({opacity: 0}) resolves, then calling stop(), keeps the keyboard path responsive.
Takes meta#8360's head, which merges first in her order and is the reconciled gesture foundation (meta#8076, meta#8073, meta#8446 and meta#8074). Two of its test files, the presented hit-testing fixture and the animator browser spec, name the internal animator createAnimator; this pull request renamed it createMotionAnimator, so they take that name here. Without it, main would stop type-checking when this lands after meta#8360.
There was a problem hiding this comment.
Requesting changes: the public createAnimator (package root only) and useAnimator (@astryxdesign/core/hooks) match spec:AST-107 FR3–FR6, and the @astryxdesign/core/animator subpath is gone as an engineering owner asked. The branch still carries the open #8076, the #8360 chain, #8661 and #8677, which make up 29,064 of its 31,203 authored lines, so please land those first and rebase onto main so this diff holds only the animator's change. After the rebase the remaining change is still about 2,140 lines, half of them tests, so the description should say why the factory, the hook, both docs pages, both examples and their tests need to ship together.
[Automated review]
|
Thanks. Those pull requests land first in the maintainer's merge order (#8360, #8665, #8076, then this one), and #8677 with #8661 is the theme line it builds on. This one merges |
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved, and it needs changes either way. The new INV15 in docs/architecture/theme-authoring-contract.md, merged here from #8661, says only motion-keyed adaptation rules read reducedMotion: 'always', which is narrower than current spec:AST-108 FR6; an engineering owner records the decision by commenting /approve-spec with this head's full SHA, and the author may do so as an engineering owner. Independently of that decision:
- 29,211 of the 31,350 authored lines come from the open #8076, #8360, #8661 and #8677; land them and rebase so this pull request holds only the animator.
- The theme docs and changeset from the latest push say springs don't read
tempoorcharacter(motion.doc.mjs,.changeset/theme-motion-knobs.md), while this branch's animator and its own docs say they do; make them describe the branch. - The Vercel preview deployment fails on this head after passing on the previous one; check its log after the rebase.
[Automated review]
| `tempo`. `reducedMotion: 'always'` marks the theme's root, which only | ||
| motion-keyed adaptation rules read. An adaptation rule may set `tempo` and |
There was a problem hiding this comment.
This sentence narrows current spec:AST-108 FR6, which has every component style keyed on prefers-reduced-motion, the animator and every script reader of prefers-reduced-motion treat 'always' as set, not only motion-keyed adaptation rules. On this branch the animator and the layout glide already read the mark (packages/core/src/motion/animator.ts:871, animatedLayout.ts:145). Either an engineering owner approves the narrower reading by commenting /approve-spec with this head's full SHA, or INV15 restates FR6.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: "A theme sets how motion feels with three knobs in `defineTheme`'s `motion`. They drive the CSS motion tokens; the springs of touch motion don't read them yet.", |
There was a problem hiding this comment.
On this branch the springs do read the knobs: the animator resolves every motion-role spring at the theme's tempo and character (packages/core/src/motion/resolveMotion.ts, animator.ts:2251), and the createAnimator page says animate plays "at its tempo and character". The same wording is in author-a-theme.doc.mjs:44, theme.template.ts:184, defineTheme.ts:296, themeMotion.ts:16 and the record's owning-code note; make them say what this branch ships.
| '@astryxdesign/cli': patch | ||
| --- | ||
|
|
||
| [feat] A theme's `motion` takes three knobs beside the duration scale: `tempo` (0.75–1.5) scales every motion duration, `character` (`'crisp'`, `'standard'` or `'expressive'`) sets the new `--ease-entrance` and `--ease-move` tokens, and `reducedMotion: 'always'` marks the theme's root so its motion-keyed adaptation rules apply as under Reduce Motion, for everyone. A theme without them renders exactly as before; a duration scale and explicit tokens win over the knobs, Spinner's and Skeleton's loops keep their timing under every tempo, and the press release fade follows tempo up to the 200 ms Reduce Motion cap. Springs don't read the knobs yet. The motion, token and theme-authoring docs and the theme template describe the knobs. |
There was a problem hiding this comment.
"Springs don't read the knobs yet" contradicts .changeset/public-animator.md in the same release, which says animate springs "at its tempo and character", and the code on this branch, where they do.
There was a problem hiding this comment.
Requesting changes: the public createAnimator (package root) and useAnimator (@astryxdesign/core/hooks) match spec:AST-107 FR3-FR6, and INV15 of the theme-authoring contract now restates spec:AST-108 FR6, but the branch still carries four other open pull requests and theme docs that contradict its own code. To move forward:
- Land #8076, #8360, #8661 and #8677 (or drop them from this branch) and rebase onto
main, so the diff holds only the animator; today 29,354 of its 31,493 authored lines come from them. - Make the motion and theme-authoring docs, the theme template, the
theme-motion-knobschangeset and the record's two unshipped notes say what this branch does: the animator's springs followtempoandcharacter, and the animator treatsreducedMotion: 'always'as Reduce Motion.
[Automated review]
| The reach of `reducedMotion: 'always'` is accepted but partly unshipped | ||
| (`spec:AST-108` FR6, INV15). Motion-keyed adaptation rules and the press | ||
| release fade key reduced motion on the mark; components' other Reduce Motion | ||
| styles, the animator, and other script readers of `prefers-reduced-motion` do | ||
| not yet. Until they do, that part of INV15 and FR6 is current authority but not | ||
| enforcement. |
There was a problem hiding this comment.
On this branch the animator already keys reduced motion on the mark: createMotionAnimator counts themeReducesMotion(target) as the person's setting (packages/core/src/motion/animator.ts:866-871), and reducedMotionFade does the same (packages/core/src/motion/reducedMotion.ts). As written, this note tells readers the animator ignores 'always'. Name the animator and reducedMotionFade among what follows the mark here, or keep this wording on #8661 alone and update it when #8677 lands.
| `resolveThemeMotion`'s role tables are accepted but unshipped (`spec:AST-108` | ||
| FR6): no spring reads the motion knobs yet. Until they land, that part of FR6 | ||
| is current authority but not enforcement. |
There was a problem hiding this comment.
On this branch springs do read the knobs: resolveMotion resolves every motion role at the theme's tempo and character (packages/core/src/motion/resolveMotion.ts, used at packages/core/src/motion/animator.ts:2251 and :2279), and the public animate follows them (packages/core/src/animator/createAnimator.test.ts:211). This note understates what ships with it.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: "A theme sets how motion feels with three knobs in `defineTheme`'s `motion`. They drive the CSS motion tokens; the springs of touch motion don't read them yet.", |
There was a problem hiding this comment.
On this branch the springs do read the knobs, and the public animator's own page says animate plays "at its tempo and character" (packages/core/src/animator/createAnimator.doc.mjs:38). A theme author who sets tempo: 1.5 here also slows the animator's springs. Say that the knobs drive the CSS motion tokens and the animator's springs.
| '@astryxdesign/cli': patch | ||
| --- | ||
|
|
||
| [feat] A theme's `motion` takes three knobs beside the duration scale: `tempo` (0.75–1.5) scales every motion duration, `character` (`'crisp'`, `'standard'` or `'expressive'`) sets the new `--ease-entrance` and `--ease-move` tokens, and `reducedMotion: 'always'` reduces motion for everyone; so far the theme's motion-keyed adaptation rules and the press release fade follow it, not yet components' other Reduce Motion styles. A theme without them renders exactly as before; a duration scale and explicit tokens win over the knobs, Spinner's and Skeleton's loops keep their timing under every tempo, and the press release fade follows tempo up to the 200 ms Reduce Motion cap, under the person's setting or the theme's `'always'`. Springs don't read the knobs yet. The motion, token and theme-authoring docs and the theme template describe the knobs. |
There was a problem hiding this comment.
"Springs don't read the knobs yet" contradicts .changeset/public-animator.md in the same release, which says animate springs "at its tempo and character", and this branch's animator does follow them.
Takes meta#8677's head, which carries meta#8661's head: the theme's tempo and character attributes now come with meta#8677, their reader, and the knobs' docs and record say the springs follow them and the animator follows reducedMotion: 'always', as this pull request's tree does. The no-icon-set search goldens are regenerated with experimental-isolation's --update.
There was a problem hiding this comment.
Requesting changes: the public createAnimator (package root) and useAnimator (@astryxdesign/core/hooks) match spec:AST-107 FR3-FR6, and the theme docs and record now describe what the springs and the animator do, but 29,379 of this branch's 31,518 authored lines still come from the open #8076, #8360, #8661 and #8677. Please land those first and rebase onto main so the diff holds only the animator, and fix these two issues the branch carries:
- Inside a nested
Themethat sets no motion knobs, the springs fall back to Astryx's own tempo and character while the CSS motion tokens inherit the outer theme's (packages/core/src/motion/themeKnobs.ts:56-79); resolving each knob from the nearest scope that sets it would keep them in step. - The plain-JavaScript playing-cards card hard-codes a white background with no text colour, so its rank drops to about 1.3:1 in a dark theme (
AnimatorPlayingCardsVanilla.tsx:28-31); styling it from theme tokens fixes that.
[Automated review]
| export function themeMotionKnobs( | ||
| element: Element | null | undefined, | ||
| ): ThemeMotionKnobsAt { | ||
| const scope = element?.closest?.(THEME_SCOPE); |
There was a problem hiding this comment.
This reads the knobs from the nearest theme scope only, so inside a nested Theme that sets none the springs play at tempo 1 and 'standard', while that theme's CSS durations and curves inherit the outer theme's (it declares no --duration-* of its own: themeMotion.ts writes them only when tempo is set, and theme tokens sit on :scope). Resolving each knob from the nearest scope that sets it, as themeReducesMotion already does for the mark, keeps the springs and the CSS in step (spec:AST-108 Intent, FR3).
| placeItems: 'center', | ||
| font: 'bold 40px system-ui, sans-serif', | ||
| borderRadius: '12px', | ||
| background: 'white', |
There was a problem hiding this comment.
The card is white but sets no text colour, so its rank inherits the theme's: in a dark theme that is about 1.3:1 on this background. var(--color-background-card) with var(--color-text-primary) (and a shadow token) would follow the theme, as the React example's Card does.
|
Thanks.
|
There was a problem hiding this comment.
Requesting changes: the public createAnimator (package root) and useAnimator (@astryxdesign/core/hooks) match spec:AST-107 FR3–FR6, and the nested-theme springs and the plain-JavaScript card are fixed, but 29,619 of this branch's 31,770 authored lines still come from the open #8076, #8360, #8661 and #8677. Please land those first (or drop them from this branch) and merge main, so the diff holds only the animator's 20 files and its rename edits, which spec:AST-107 FR5 and FR6 then cover on their own. One small fix for the animator itself: the createAnimator page says it writes only translate, scale, rotate and opacity, but it also sets an inline will-change while it moves (restored 500 ms after), which the element description should mention.
[Automated review]
|
Thanks.
|
There was a problem hiding this comment.
Requesting changes: the public createAnimator (package root) and useAnimator (@astryxdesign/core/hooks) match spec:AST-107 FR3–FR6, and the createAnimator page now describes the temporary will-change, but 29,619 of this branch's 31,770 authored lines still come from the open #8076, #8360, #8661 and #8677. Please land those first (or drop them from this branch) and merge main, so the diff holds only the animator's 20 files and its rename edits, which spec:AST-107 FR5 and FR6 then cover on their own.
[Automated review]
… the gesture host The browser negotiation and the gesture host on top of the recognizers: touch-action derived from what is attached and never wider than the element's own (spec:AST-094 FR18, DEC-8), a native scroll probe that follows a slotted node into its shadow tree, the tap haptic, and the host that claims a finger, suppresses the click a claimed touch would send, and hands a touch to the browser when it takes over. The host serves a mouse, a pen and a finger alike (FR19, FR43). Where the primary pointer is fine, an element that uses a gesture keeps one passive pointerdown listener and the page has none until a press, whose listeners go once its contacts end (FR24); a right click, a pen's barrel button and a ctrl-click on macOS start no press. A detach re-syncs the host even when the element keeps another attachment, so one left disabled keeps no listener or observer. It gives a screen-edge pan its view's box and, through one ResizeObserver and no listener, reports a resize of that box or of the viewport, so the pan's edge-zone answer and the touch-action derived from it follow the layout (FR18, FR19, FR24, DEC-22). Recognizers report their kind, which the host and the touch-action rules read instead of checking a class (architecture:react-component-runtime INV13). The gesture system's real-touch browser proof runs with it, under playwright.interaction.config.ts on a phone in Chromium and WebKit: touch-action per element, claims and cancelled moves, click suppression, pinch, a second finger, a long press under a blocked main thread, the 10 px take, the screen-edge band in both writing directions, a scroll's take of a held press only once the finger is 10 px from where it landed, and the browser taking a passive control's press by its scroll or, where nothing can scroll, its pointercancel (FR18-FR22); and, under a fine pointer, a mouse and a pen and the page listeners a press adds and removes (FR43, FR24). meta#8071's and meta#8072's changes, with meta#8665's serving of every pointer, rebased onto main after meta#8061 as one commit; the host's hover routing lands with the hover recognizer's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
…mator Core's internal touch foundation on the gesture recognizers (meta#8061) and the gesture host (meta#8071 + meta#8072): UIControl press tracking with its press profiles and hooks, built by createPressTracker; and the animator, which starts a motion from rest at its commit, holds the value on screen inline while a curve runs, and lets a call wait for a presented frame. It absorbs meta#8073 (closed: the press tracking this rests on) and meta#8340 (the animator's start and hold fixes), with the held dip's load fixes, and keeps meta#8641's rule: a gesture that cancels itself while its finger moves keeps that finger until it lifts. A gesture that ends on an unmount or a replaced node calls no listener from the effect. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
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. The internal factory is renamed createMotionAnimator, 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.
8df516e to
587445a
Compare
There was a problem hiding this comment.
Requesting changes: the public createAnimator (package root) and useAnimator(ref) (@astryxdesign/core/hooks) match spec:AST-107 FR3–FR6, but 13,339 of this branch's 15,546 authored lines still sit in files the open #8071 and #8076 change. Please land those (or drop them) and merge main so the diff holds only the animator, and fix two issues in its own files:
- Both playing-cards examples' Discard button calls
decay, which keeps its momentum under Reduce Motion, so on a touch screen a tapped card slides about 260 px while it fades; keepdecayfor a released flick, have the button callanimate({x: 400, opacity: 0}), and make the docs' keyboard guidance say a control callsanimate. - A
decaythat mixesxorywithscale,rotateoropacityawaits two internal handles, so a later call that takes overxleaves its promise pending until the other coast ends; resolve it as soon as either handle is interrupted, asanimatedoes.
[Automated review]
| <Button | ||
| label={`Discard ${card.name}`} | ||
| variant="secondary" | ||
| onClick={() => void discard({x: MIN_THROW, y: 0})} |
There was a problem hiding this comment.
Under Reduce Motion on a touch screen, this tap coasts the card about 260 px during its 200 ms fade. discard starts decay({x: 1600}), and the animator keeps a coast's momentum because it treats it as a person's throw (design:reduced-motion DR2). Calling animator.animate({x: 400, opacity: 0}) here, and keeping decay for a released flick, gives an in-place fade under Reduce Motion and the same exit otherwise.
| ); | ||
| } | ||
| } | ||
| const onDiscard = () => void discard(MIN_THROW, 0); |
There was a problem hiding this comment.
Same as the React example: the button reaches decay({x: 1600}), so under Reduce Motion on a touch screen the card slides about 260 px while it fades. animator.animate({x: 400, opacity: 0}) for the button path would fade it in place there.
| name: 'Keyboard and assistive technology', | ||
| category: 'Keyboard', | ||
| description: | ||
| 'An animator moves an element; it adds no way to act. Give every action an animation stands for a control a keyboard and a screen reader can reach, such as a button that discards a card, and call the same `animate` or `decay` from it. Keep focus on an element that stays on screen: move it before you fling its element away.', |
There was a problem hiding this comment.
"Call the same animate or decay from it": a decay from a button keeps its momentum under Reduce Motion, because the animator treats every coast as a throw. Saying a control calls animate (and that decay follows a person's throw) would keep products inside DR2. The same sentence is at useAnimator.doc.mjs:68.
| if (Object.keys(others).length > 0) { | ||
| moving.push(motion.play(others).finished); | ||
| } | ||
| await Promise.all(moving); |
There was a problem hiding this comment.
After decay({x: 1200, rotate: 90}), a later set({x: 0}) resolves the translate handle at once, but this Promise.all waits for the rotate coast (about 4 s). animate resolves as soon as any of its channels is taken over (spec:AST-107 FR3). Resolving when either handle finishes false, or when both finish, would match.
Stacked on #8071 and #8076, one commit each on
main, with no merge commits: review only this pull request's commit, vjeux@587445a8b5.Why
A product that drags, flicks or throws an element has no public way to give it the motion Astryx's own components use: today it copies springs and decay curves by hand and loses Reduce Motion handling.
What led to it
spec:AST-107FR3 opens the touch components' animator as a public handle, with a non-React factory under the hook (FR6).Options and tradeoffs
Why this choice
The spec picks the small handle, so a product's drag stays in step with Core's components. Following the maintainers' API decision,
useAnimator(ref)takes the element's ref, as the gesture hooks do, so a drag and an animation can share one element. The factory, the hook, both docs pages, both examples and their tests ship together: FR5 asks each hook to ship with its docs and a playing-cards example, and FR6 builds the hook on the factory.What changes
createAnimator(element), from the package root only, returnsanimate,decay,set,get,getVelocity,stopanddestroyoverx,y,scale,rotateandopacity.useAnimator(ref), from@astryxdesign/core/hooks, takes the element's ref and returns the animator. After every render it follows the element behind the ref: it attaches a node that mounts, moves to one that replaces it, and lets an unmounted one go, keeping its values for a remount.decaycoasts any channel; opacity stops at 0 or 1. Under Reduce Motion a thrownxorykeeps its momentum; anything else fades on a touch screen or lands at once with a mouse.createMotionAnimator, so no internal factory shares a public factory's name.Left out
animatefollowing the theme'stempoandcharacter(spec:AST-108FR3) arrives with feat(core): the animator's springs follow the theme's tempo and character #8677, which makes the springs read the theme.usePanGesture, a drag and an animation sharing the card's one ref, lands right after feat(core): createPanGesture and usePanGesture report a drag as values (spec:AST-107 FR1, FR6) #8655.How to check it
useAnimatorplaying-cards example, fling the top card: it coasts away, and the next card is dealt once it has faded.Evidence
pnpm test:ui(16,742), the node suites, lint, Storybook, the docs data, the registry's production check, and the animator, motion and gesture interaction specs in Chromium (109) and WebKit (102).useAnimator.test.tsxchecks the ref signature, a replaced node, an unmount and a remount, and a second hook sharing the ref; it failed against the previoususeAnimator().createAnimator.test.tschecks each method against the spring and decay math,destroyand Reduce Motion.Scope