Skip to content

feat(core): createDragToSnapPoints and useDragToSnapPoints settle an element on snap points - #8658

Merged
cixzhang merged 12 commits into
meta:mainfrom
vjeux:uikit/f1-drag-to-snap-points
Nov 6, 2026
Merged

cixzhang merged 12 commits into
meta:mainfrom
vjeux:uikit/f1-drag-to-snap-points

Conversation

@vjeux

@vjeux vjeux commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

#8655 has merged.

The merge worker merged main in after #8655's squash (vjeux@f127a5587c), so the diff against main is only this pull request's own commits: the snap drag (vjeux@83c69ca2f2), the shared Playwright test import (vjeux@a00b8a8240), a committed onSettle and styled example buttons (vjeux@a42cd3f3b2), and the motion engine's new name (vjeux@e33f7e7a1d).

Why

A product's drawer, card or panel that rests on a few positions wants iOS's release behavior: the nearest position after a slow release, the next one after a flick, and back where it started when the finger moves back. Without a public API, each product rebuilds this from raw pointer events.

What led to it

This comes from the work to make Astryx's touch components behave as native iOS ones do. spec:AST-107 FR2 asks for it over the public pan (#8655) and animator (#8653), with a thin hook (FR6) and Core's release policy (resolveSnapPoint), so drawers and BottomSheet release alike.

Options and tradeoffs

  • Viewport fractions, as BottomSheet's snapPoints are, or pixel offsets from the resting position: fractions suit sheets, offsets suit any element.
  • New options mid-use: create the drag again where the element is, or hand them to the running drag.

Why this choice

The spec picks pixel offsets and a public flickThreshold (default 1008 px/s, UIKit's measured switch). New options create the drag again where the element is, which stops a settle in flight there: the hook adds no behavior of its own (FR6). settleTo(index) gives keyboard users the same positions.

What changes

  • createDragToSnapPoints(element, {axis, snapPoints, flickThreshold?, onSettle}) from the package root only, and useDragToSnapPoints(ref, options) from @astryxdesign/core/hooks. The hook keeps one drag for the element behind its ref through Core's shared useAttachment hook (hooks/useAttachment), keyed on the axis, the snap points and the flick speed, so a node mounted or replaced later is driven too and new options create the drag again.
  • A finger, mouse or pen drags (spec:AST-094 FR43). The settle plays springs.settle from the release velocity (FR38); under Reduce Motion the release cross-fades after a touch drag or on a coarse primary pointer, and lands at once elsewhere.
  • Bad input warns in development; destroy removes every listener.
  • The core API report gains the rows of both and their types; main's unrelated drift in it is left out.
  • Both examples run their places in reading order, right to left too. The plain-JavaScript example mounts its card from a callback ref on its wrapper, whose cleanup takes it down.

Behavior module: createDragToSnapPoints with its hook useDragToSnapPoints.

Pull request size. This pull request's own commit is 2,453 authored lines with tests, and no part of it can stack apart. spec:AST-107 FR5 reads "Each hook ships with docs and a playing-cards example", so the two examples, their test and their registrations (390 lines) ship with the hook. The rest is the factory and the hook with their docs, changeset and API report rows (664 lines), and the tests that cover them (1,410 lines), which cannot land apart from the code they test.

How to check it

  1. In the useDragToSnapPoints example (a card on Hand, Table and Discard pile), drag past halfway toward Table and release: it settles there. Flick: it carries one place further. Drag toward Table and back: it returns to Hand.
  2. A mouse drag does the same.
  3. Right to left, press "Table": the card moves left.

Evidence

  • Tests at 83c69ca: factory (releases, FR38, and a non-default flickThreshold through the factory), fine pointer, hook (late element, options mid-use, and its imports: createDragToSnapPoints and ./useAttachment, nothing from motion/ or gestures/), examples (right to left), entry.
  • Real touches in Chromium and WebKit settle on 240, back on 0, and on 480 by settleTo. The browser spec now reads the settles once the panel is at rest, not after a fixed time, so a second settle would show: with every animation at a quarter speed, the old 1,200 ms wait fails 8 of 8 and the new read passes 12 of 12 (not-for-landing arms fceac0b and 2bc739b).
  • At e33f7e7, on main 8c0e95b: build, docsite generate (route lock unchanged), type check, CI=true lint and Prettier pass; the drag, hook and gesture unit tests pass (868), and so do the doc inputs and touch bundle tests (18); the snap spec's 4 tests pass in Chromium and in the WebKit container; changeset coverage passes. useDragToSnapPoints writes its latest onSettle in a layout effect, and a new test shows a render React throws away leaves the committed one in place (red before the fix). Core has no shared latest-ref helper. useGestureRecognizer, PickerWheel and Markdown each write their listener's ref in a layout Effect, and this follows that pattern. review:pre flags such an Effect as a copy into a ref (INV3); here the copy at commit is the fix. useEffectEvent needs React 19.2, and Core supports 19.0. The plain-JavaScript example's place buttons take the secondary button's tokens, and a new test checks them (red before the change).
  • Gates at an earlier head (on feat(core): createPanGesture and usePanGesture report a drag as values (spec:AST-107 FR1, FR6) #8655's 24c5d6f, on main 859df5f): install, build, docsite generate (route lock unchanged), Storybook, type check, CI=true lint, test:ui (19,744), the node suites (16,263), the docsite tests, knowledge, changesets and coverage pass. The snap spec passes 10 of 10 in Chromium and in the CI WebKit container, and interaction passes 541 in Chromium and 480 in WebKit (at d6efeb3, which differs only in the hook's ref write and two comments and test lines). api:report --check asks only for rows main lacks (--duration-exit, CheckboxList isOptional, CodeBlock startLineNumber), in the same 8 reports as at feat(core): createPanGesture and usePanGesture report a drag as values (spec:AST-107 FR1, FR6) #8655's head.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 6, 2026 6:14am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 6, 2026, 6:25 AM UTC

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Thanks for the public snap-points hook. One structure ask: this PR has its own release rule (snapPointAfterRelease plus a private nearestIndex), which duplicates Core's shared release-policy helper, resolveSnapPoint (#8663). That helper also handles the projection, tolerance and step limits the sheet and pager need. Please have createDragToSnapPoints call resolveSnapPoint, so there's one release rule. If this stays idle, we'll make the change after about 30 minutes, keeping your authorship.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 27, 2026
@vjeux

vjeux commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Done in a671989: createDragToSnapPoints now calls resolveSnapPoint (#8663, merged in as a dependency) with the snap points, the release position and velocity, flickThreshold and the drag's start. Its own snapPointAfterRelease and nearestIndex are gone, and the starting snap point comes from the same call. The release tests now check FR2's and FR37's cases through resolveSnapPoint; the touch and browser tests are unchanged and pass.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This snap-point drag can't be approved yet: createDragToSnapPoints and useDragToSnapPoints rely on spec:AST-107, which isn't on main (it is proposed in #8600), and the current specs still keep the gesture and motion layer and release thresholds internal. An engineering owner needs to provide or identify the governing spec for these exports, for example by landing #8600, and record that decision here by commenting /approve-spec followed by the full head SHA; the author may do so as an engineering owner. Whatever that decision, these changes are still needed:

  • Rebase once the stacked pull requests land, so this diff holds only the snap-point change (about 1,500 changed lines instead of 20,493).
  • Make useDragToSnapPoints attach to an element that mounts or is replaced after the first render, as usePanGesture does.
  • Start a re-created factory from the element's current offset, so changing snapPoints, axis or flickThreshold doesn't make the next drag jump.
  • Reject an empty snapPoints in the hook as the factory does, and add a test that fails if the settle ignores the release velocity.

[Automated review]

Comment on lines +65 to +83
useEffect(() => {
const element = ref.current;
if (element == null) {
return undefined;
}
const created = createDragToSnapPoints(element, {
axis,
snapPoints: points.split(',').map(Number),
flickThreshold,
onSettle: index => onSettleRef.current(index),
});
snapRef.current = created;
return () => {
created.destroy();
if (snapRef.current === created) {
snapRef.current = null;
}
};
}, [ref, axis, points, flickThreshold]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect only re-runs when ref, axis, points or flickThreshold change, so an element that mounts after the first commit (for example {open && <div ref={ref} />}) or a replaced node never gets the drag, and settleTo stays a no-op. usePanGesture in this stack re-checks the node on every commit; doing the same here, or taking a callback ref as useAnimator does, would handle attachment and replacement as architecture:react-component-runtime INV6 asks.

}
const created = createDragToSnapPoints(element, {
axis,
snapPoints: points.split(',').map(Number),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[].join(',') is '', and ''.split(',').map(Number) is [0], so an empty snapPoints passed to the hook becomes one snap point at 0 instead of throwing as createDragToSnapPoints does.

Comment on lines +121 to +128
const animator = createMotionAnimator(element);
// The snap point the element rests on, or is settling toward.
let index = resolveSnapPoint({
points: snapPoints,
position: 0,
velocity: 0,
flickThreshold,
}).index;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the hook re-creates the factory after snapPoints, axis or flickThreshold change, this starts from offset 0 and the snap point nearest 0, but the previous animator's inline translate stays on the element, so the next drag jumps from where the element is drawn to the finger's travel from 0. Seeding the animator's initial and the starting index from the element's current offset would keep them in step.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This snap-point drag can't be approved yet: createDragToSnapPoints and useDragToSnapPoints rely on spec:AST-107, which isn't on main (it is proposed in #8600), and the current specs still keep the gesture and motion layer and release thresholds internal. An engineering owner needs to provide or identify the governing spec for these exports, for example by landing #8600, and record that decision here by commenting /approve-spec followed by the full head SHA; the author may do so as an engineering owner. Whatever that decision, these changes are still needed:

  • Rebase once the stacked pull requests land, so this diff holds only the snap-point change (roughly 1,550 changed lines instead of 20,574).
  • Make useDragToSnapPoints attach to an element that mounts or is replaced after the first render, as usePanGesture does.
  • Start a re-created factory from the element's current offset, so changing snapPoints, axis or flickThreshold doesn't send the next drag back toward the first snap point.
  • Mirror the templates' snap points in right-to-left layouts, where the card now moves away from its next place and off-screen.
  • Add a test that fails if the settle ignores the release velocity.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner deciding which spec governs the new public createDragToSnapPoints and useDragToSnapPoints (with flickThreshold): spec:AST-107 exists only in open #8600, while current spec:AST-017 FR21, architecture:react-component-runtime INV13 and spec:AST-100 FR3 keep gesture modules and release thresholds internal. Any engineering owner, the author included if they are one, can record that decision by commenting /approve-spec 21f071817cf2ef629e0e2cfa8713f02377a0a57e on this pull request, or can point to another current record that governs these exports. Whatever the decision, these still need changes:

  • The diff carries 20,168 authored lines from eleven other open pull requests, so rebase onto the landed dependencies until it holds only the snap-point drag.
  • A mouse or pen drag never starts where the primary pointer is fine (GestureHost.ts:580-583), though both doc pages and the templates' grab cursor promise one.
  • useDragToSnapPoints never attaches to an element that mounts after its first render, and after snapPoints change the next drag starts from 0 (useDragToSnapPoints.ts:67-85, createDragToSnapPoints.ts:124-129).
  • The templates' card moves the wrong way in right-to-left layouts (DragToSnapPointsCard.tsx:15-24), and no test fails when the settle ignores the release velocity (spec:AST-094 FR38).

[Automated review]

});
};

const pan = createPanGesture(element, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a desktop whose primary pointer is fine, the document's gesture host installs no listener (GestureHost.ts:580-583), so no mouse or pen drag reaches this pan and only settleTo moves the element, while both doc pages describe the settle "after a mouse or pen drag" and the templates show a grab cursor. Either give the public pan a mouse and pen path on its own element under a fine pointer (the host already does this for action groups, GestureHost.ts:476-487), or document that the drag needs a coarse primary pointer and drop the grab cursor.


const PLACES = ['Hand', 'Table', 'Discard pile'];
/** Where each place sits, in px from the card's resting position. */
const SNAP_POINTS = [0, 110, 220];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These offsets are physical, but the card and the place buttons sit in rows that mirror: in a right-to-left layout Table is to the card's left, a drag toward it settles back on Hand, and Table's button slides the card 110 px right, past the inline-start edge (design:bidirectionality DR13). Mirror the offsets when the layout is right-to-left, mapping onSettle's sorted index back to its place; DragToSnapPointsCardVanilla.tsx needs the same change.

snap.destroy();
});

it('a quick flick carries to the next snap point', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flick test asserts only onSettle, so it still passes when the settle drops the release velocity (createDragToSnapPoints.ts:147); spec:AST-094 FR38's verification row needs a test that fails then. Assert the recorded settle's opening keyframes, or the animator's velocity right after the release, against the release velocity.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · c133eb9

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC
DateInput 167.0 kB (+18 B) · 16.1 kB CSS (0) —
PowerSearch 263.9 kB (+17 B) · 22.9 kB CSS (0) —
richtext/RichTextView 270.9 kB (+5 B) · 2.3 kB CSS (0) —
21 more components
Component Size LoC
lab/NavigationScreen 75.0 kB (+2 B) · 1.1 kB CSS (0) —
charts/ChartTooltip 151.0 kB (+1 B) · 423 B CSS (0) —
lab/NavigationStack 75.0 kB (+1 B) · 1.1 kB CSS (0) —
Calendar 62.4 kB (0) · 9.9 kB CSS (0) —
DateRangeInput 81.3 kB (0) · 11.8 kB CSS (0) —
DateTimeInput 193.3 kB (0) · 18.0 kB CSS (0) —
lab/ChatEmojiPicker 74.6 kB (0) · 773 B CSS (0) —
lab/ChatReactionBar 75.1 kB (0) · 1.1 kB CSS (0) —
lab/ChatTypingIndicator 25.1 kB (0) · 612 B CSS (0) —
lab/CommentComposer 115.3 kB (0) · 1.9 kB CSS (0) —
lab/CommentPin 52.6 kB (0) · 1.2 kB CSS (0) —
lab/DiffViewer 77.9 kB (0) · 2.1 kB CSS (0) —
lab/EmojiPicker 77.5 kB (0) · 751 B CSS (0) —
lab/ListInput 90.9 kB (0) · 1.6 kB CSS (0) —
lab/ThreeDChart 5.0 kB (0) · 20 B CSS (0) —
lab/TransferList 85.3 kB (0) · 1.7 kB CSS (0) —
lab/TransferListSelector 92.3 kB (0) · 1.9 kB CSS (0) —
richtext/RichTextEditor 290.6 kB (0) · 2.6 kB CSS (0) —
charts/Chart 173.6 kB (−9 B) · 494 B CSS (0) —
lab/Comments 281.6 kB (−41 B) · 2.5 kB CSS (0) —
lab/CommentThread 275.8 kB (−49 B) · 2.4 kB CSS (0) —

Package size:

  • @astryxdesign/cli 3.39 MB packed (+2.3 kB); no new dependencies
  • @astryxdesign/core 6.69 MB packed (+9.7 kB); no new dependencies

API changes: core +9 · ~0 · −0

Committed API report behind this build: cli 19 rows, core 21 rows; run pnpm api:report.

9 API changes (advisory)
Package Where Signature Usage
+ core . · Hooks · hook useDragToSnapPoints hook useDragToSnapPoints: (ref: RefObject<HTMLElement | null>, options: DragToSnapPointsOptions) => UseDragToSnapPointsResult import {useDragToSnapPoints} from '@astryxdesign/core'; useDragToSnapPoints(…)
+ core . · Functions · function createDragToSnapPoints function createDragToSnapPoints: (element: HTMLElement, options: DragToSnapPointsOptions) => DragToSnapPoints import {createDragToSnapPoints} from '@astryxdesign/core'; createDragToSnapPoints(…)
+ core . · Types · interface DragToSnapPoints interface DragToSnapPoints import type {DragToSnapPoints} from '@astryxdesign/core'
+ core . · Types · interface DragToSnapPointsOptions interface DragToSnapPointsOptions import type {DragToSnapPointsOptions} from '@astryxdesign/core'
+ core . · Types · interface UseDragToSnapPointsResult interface UseDragToSnapPointsResult import type {UseDragToSnapPointsResult} from '@astryxdesign/core'
+ core ./hooks · Also exported from an earlier entry point · DragToSnapPoints DragToSnapPoints (.) import {DragToSnapPoints} from '@astryxdesign/core/hooks'
+ core ./hooks · Also exported from an earlier entry point · DragToSnapPointsOptions DragToSnapPointsOptions (.) import {DragToSnapPointsOptions} from '@astryxdesign/core/hooks'
+ core ./hooks · Also exported from an earlier entry point · UseDragToSnapPointsResult UseDragToSnapPointsResult (.) import {UseDragToSnapPointsResult} from '@astryxdesign/core/hooks'
+ core ./hooks · Also exported from an earlier entry point · useDragToSnapPoints useDragToSnapPoints (.) import {useDragToSnapPoints} from '@astryxdesign/core/hooks'

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
meta#8653 now carries main dcdb356 and its review fixes. The only
conflict, internal/shadcn-registry/routes.lock.json, is regenerated with
UPDATE_SHADCN_ROUTE_LOCK=1 node apps/docsite/scripts/generate-data.mjs:
meta#8653's lock plus this pull request's five routes.
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 27, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner deciding the architecture:theme-authoring-contract INV15 that this branch carries from #8661: its clause that "an explicit token anywhere in the lineage still wins" over a child theme's tempo and character is not stated by spec:AST-108 FR5 and changes the record's own base-then-generated precedence. Any engineering owner, the author included if they are one, can record that decision by commenting /approve-spec d504154424d4488fde7f96499f099505c5a2ab66 on this pull request or ask for the clause to change, and rebasing the theme work out of this branch also removes it. Whatever the decision, these still need changes:

  • A mouse or pen drag never starts where the primary pointer is fine (GestureHost.ts:580-583), though spec:AST-107 FR1 and FR2 require every pointer and both doc pages and templates promise one.
  • The diff carries 22,498 authored lines from thirteen other open pull requests, so rebase onto the landed dependencies until it holds only the snap-point drag.
  • useDragToSnapPoints never attaches to an element that mounts after its first render, and after an option change the next drag starts from 0 (useDragToSnapPoints.ts:67-85, createDragToSnapPoints.ts:122-129).
  • The templates' card moves the wrong way in right-to-left layouts (DragToSnapPointsCard.tsx:15), and no test fails when the settle ignores the release velocity (spec:AST-094 FR38).

[Automated review]

are the defaults. A duration scale's durations win over `tempo`, and
explicit tokens win over both. An extending theme inherits motion field by
field: a duration-scale field or knob the child leaves unset keeps its
base's value, and an explicit token anywhere in the lineage still wins. Each duration's

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clause makes a base theme's explicit motion token outrank a child theme's tempo and character. spec:AST-108 FR5 says explicit tokens win over the knobs, but not that a base theme's do, and this record's own order (resolved base, then generated values, then explicit tokens) lets a child's generated durations replace its base's tokens. Either an engineering owner approves this as the reading, or the clause follows the existing order.

…test

Main's lint asks every browser spec to import test from
@astryxdesign/a11y-spec/playwright, whose contexts stay off remote hosts.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before this can merge, though the snap-point drag itself follows spec:AST-107 FR2, FR5 and FR6, and its unit and browser tests pass on this head.

  • Regression: the Stack: #8655 → #8658, #9928 line no longer holds because #9928's head does not contain #8655's current head, so this branch is judged on its own and carries #8655's 1,799-line public pan beside the drag (4,232 authored lines), which the behavior-module declaration does not cover; bring #9928 onto #8655's current head (or list only the pull requests that contain the one below them), or land #8655 first.

Non-blocking: the plain-JavaScript example's place buttons render unstyled under the Astryx reset, both hooks write their latest callback into a ref during render, and the description's notes on the stack (no merge commits, #8655's head) are out of date.

[Automated review]

const {axis, snapPoints, flickThreshold} = options;
const onSettleRef = useRef(options.onSettle);
// The drag calls the latest onSettle.
onSettleRef.current = options.onSettle;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: this writes the latest onSettle into a ref during render. A render React discards can then leave a callback from a render that never committed. Writing it in a layout Effect keeps it to committed renders.

@vjeux vjeux Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a42cd3f: the hook writes onSettle into the ref in a layout Effect, so a render React throws away leaves the committed one in place, and a new test shows it. Core has no shared latest-ref helper, and useGestureRecognizer writes its listener's ref the same way.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before this can merge, though the snap-point drag itself follows spec:AST-107 FR2, FR5 and FR6 and needs only a rename.

  • Regression: packages/core/src/dragToSnapPoints/createDragToSnapPoints.ts (line 24) still imports createMotionAnimator from ../motion/animator, which main has since renamed to createMotionEngine, so at this head Core's type check and build fail and every createDragToSnapPoints call throws TypeError: createMotionAnimator is not a function (33 of the drag's 43 unit tests fail); rename the import and the call at line 135, and the factory test's mock at lines 36-39, to createMotionEngine, after which all 43 tests and the type check pass.

Non-blocking: the plain-JavaScript example's place buttons render unstyled under the Astryx reset, and useDragToSnapPoints writes its latest onSettle into a ref during render, as usePanGesture does on main.

[Automated review]

createPanGesture,
type PanGestureEvent,
} from '../gestures/createPanGesture';
import {createMotionAnimator} from '../motion/animator';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regression: main has renamed this export to createMotionEngine, so this import no longer resolves. Core's type check fails here (TS2305), the core build stops on it, and the call at line 135 throws TypeError: createMotionAnimator is not a function for every caller. Rename it here and at line 135, and in the factory test's mock (createDragToSnapPoints.test.ts lines 36-39).

@vjeux vjeux Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e33f7e7: the import, the call and the factory test's mock now use createMotionEngine.

vjeux added 2 commits November 5, 2026 07:36
useDragToSnapPoints wrote the latest onSettle into a ref during render,
so a render that React throws away could leave its callback behind. It
now writes it in a layout effect, as the other hooks do.

The plain-JavaScript example's place buttons rendered unstyled under the
Astryx reset. They now take the theme's secondary button look from its
tokens.
Main renamed createMotionAnimator to createMotionEngine.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending approval. Approved pending CI: CI has not run on this draft head yet. Once the pull request is marked ready for review, a maintainer adds the ci:run label so CI runs on this fork branch.

createDragToSnapPoints and useDragToSnapPoints match the public gesture-hooks spec (spec:AST-107 FR2, FR5 and FR6). The createMotionEngine rename fixes the build break, the hook now hands the drag only a committed onSettle, and the plain-JavaScript example's buttons take the secondary button look; the new tests for both changes fail on the previous head and pass here.

[Automated review]

…ntries plus this PR's four, in the generator's path order)

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending approval. Approved pending CI: CI has not run on this draft head. Once the pull request is marked ready for review, a maintainer adds the ci:run label so CI runs.

createDragToSnapPoints and useDragToSnapPoints match spec:AST-107 FR2, FR5 and FR6, and their unit tests, template tests and Chromium browser spec pass on this head.

One optional suggestion:

  • Regression: main now lists createPanGesture, usePanGesture, createAnimator and useAnimator under the "Motion & gestures" hook category, while both new docs still say category: 'interaction' (createDragToSnapPoints.doc.mjs:112, useDragToSnapPoints.doc.mjs:101); 'motion & gestures' would list the drag beside the hooks it builds on.

[Automated review]

relatedComponents: ['BottomSheet', 'Carousel'],
relatedHooks: ['useDragToSnapPoints', 'createPanGesture', 'createAnimator'],
importPath: '@astryxdesign/core',
category: 'interaction',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (Regression): main now files createPanGesture and createAnimator under category: 'motion & gestures'. Using the same category here keeps the drag listed beside the pan and animator it builds on.

  category: 'motion & gestures',

relatedComponents: ['BottomSheet', 'Carousel'],
relatedHooks: ['usePanGesture', 'useAnimator'],
importPath: '@astryxdesign/core/hooks',
category: 'interaction',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (Regression): usePanGesture and useAnimator are now under category: 'motion & gestures' on main, so astryx hook --list shows this hook apart from them under "Interaction".

  category: 'motion & gestures',

@vjeux
vjeux marked this pull request as ready for review November 5, 2026 19:30
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 5, 2026
@cixzhang
cixzhang marked this pull request as draft November 5, 2026 19:36
@cixzhang
cixzhang marked this pull request as ready for review November 5, 2026 19:36

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending approval. Approved pending CI: on this head CI's build and test jobs stopped with "CI is full" before running, so a maintainer needs to re-run all jobs before merge.

createDragToSnapPoints and useDragToSnapPoints match spec:AST-107 FR2, FR5 and FR6, and their unit tests, template tests and Chromium browser spec pass on this head.

One optional suggestion:

  • Regression: main lists createPanGesture, usePanGesture, createAnimator and useAnimator under the "Motion & gestures" hook category, while both new docs still say category: 'interaction' (createDragToSnapPoints.doc.mjs:112, useDragToSnapPoints.doc.mjs:101); 'motion & gestures' would list the drag beside the hooks it builds on.

[Automated review]

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending approval. Approved pending CI: on this head CI's build and test jobs stopped with "CI is full" before running, so all jobs need a re-run.

createDragToSnapPoints and useDragToSnapPoints follow spec:AST-107 (FR2, FR5, FR6): the release settles on the nearest snap point, carries one further past flickThreshold, and returns to its start when the release moves back; settleTo gives keyboard users the same positions. The drag's unit, hook and template tests and its browser spec pass on this head with main's latest motion changes merged in.

  • Optional: both new docs use category: 'interaction' (createDragToSnapPoints.doc.mjs:112, useDragToSnapPoints.doc.mjs:101), while the pan and animator they build on are now under 'motion & gestures'; matching them keeps the three listed together.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending approval.

createDragToSnapPoints and useDragToSnapPoints follow spec:AST-107 (FR2, FR5, FR6): the release settles on the nearest snap point, carries one further past flickThreshold, and returns to its start when the release moves back; settleTo gives keyboard users the same positions. The drag's unit, hook and template tests and its browser spec pass on this head with main's latest changes merged in, and CI is green.

  • Optional: both new docs use category: 'interaction' (createDragToSnapPoints.doc.mjs:112, useDragToSnapPoints.doc.mjs:101), while the pan and animator they build on are under 'motion & gestures'; matching them keeps the three listed together.

[Automated review]

@cixzhang
cixzhang merged commit a789c8c into meta:main Nov 6, 2026
66 checks passed
@vjeux
vjeux deleted the uikit/f1-drag-to-snap-points branch November 6, 2026 06:36

This branch was successfully deployed

1 active deployment
Preview — c133eb98 Deployed Nov 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants