Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
192 commits
Select commit Hold shift + click to select a range
f1b86d7
test(interaction): real touch input for browser interaction proofs
vjeux Oct 24, 2026
4076bf3
spec(AST-094): touch presentations follow UIKit
vjeux Oct 24, 2026
28343b4
chore(core): the UIKit constants table for touch presentations
vjeux Oct 24, 2026
c88087c
chore(core): UIKit's spring, decay, rubber-band and projection math
vjeux Oct 24, 2026
081b6c5
feat(core): gesture recognizer core — UIKit's state machine and the a…
vjeux Oct 24, 2026
f423ffe
feat(core): tap and long-press recognizers with UIKit's measured thre…
vjeux Oct 24, 2026
4dc2aae
feat(core): one touch presentation resolver
vjeux Oct 24, 2026
706683b
fix(build): resolve defineConsts read across files in astryxStylex
vjeux Oct 24, 2026
2a8b491
chore(core): UIKit's spring, decay, rubber-band and projection math
vjeux Oct 24, 2026
60fc05c
chore(core): a compositor animator for touch presentations
vjeux Oct 24, 2026
968b85f
fix(interaction): release only the lifted finger on a partial lift in…
vjeux Oct 24, 2026
6f2b3e0
test(interaction): curve sampling and deterministic storyboards
vjeux Oct 24, 2026
eef784b
fix(interaction): curves end before a held end value; rest judges pre…
vjeux Oct 24, 2026
dbbdebc
test(interaction): frame evidence and the BottomSheet exemplar
vjeux Oct 24, 2026
6bd39ce
test(interaction): make the busy-main-thread verdict robust to a load…
vjeux Oct 24, 2026
5cb0543
feat(core): pan gesture recognizer with UIKit's hysteresis and velocity
vjeux Oct 24, 2026
7d58f27
feat(core): swipe, pinch, rotation, screen-edge pan and hover recogni…
vjeux Oct 24, 2026
1a76704
feat(core): touch-action derivation, native scroll probe and haptics
vjeux Oct 24, 2026
cfffebf
feat(core): gesture host — claims, click suppression and the browser'…
vjeux Oct 24, 2026
0453495
feat(core): UIControl press tracking, press profiles, the press paint…
vjeux Oct 24, 2026
398d4eb
test(core): gesture browser proof in Chromium and WebKit, and the README
vjeux Oct 24, 2026
e9f1e0a
chore(core): the animator plays any curve, drives a pseudo-element, s…
vjeux Oct 24, 2026
90c34de
Merge the interaction harness (#8045) under the animator's browser pr…
vjeux Oct 24, 2026
f328e32
chore(core): useAnimator and the animator's browser proofs on the int…
vjeux Oct 24, 2026
9d2d720
feat(core): the touch presentation resolver knows the iOS idiom
vjeux Oct 24, 2026
765a094
feat(core): themes retune touch motion through motion roles
vjeux Oct 24, 2026
f87a453
Merge the theme's motion roles into the animator branch
vjeux Oct 24, 2026
65fb64b
feat(core): the animator plays each table spring and curve as its the…
vjeux Oct 24, 2026
5f79e11
Merge the gesture system (#8074) under the experimental touch exports
vjeux Oct 24, 2026
8f0d9c7
Merge the touch presentation resolver (#8062) under the experimental …
vjeux Oct 24, 2026
eaf2189
feat(core): experimental motion and gesture subpaths for touch presen…
vjeux Oct 24, 2026
7065daf
fix(core): one touch model on every device; the adaptive change is a …
cixzhang Oct 24, 2026
dc6ae1e
test(interaction): real touch input for browser interaction proofs
vjeux Oct 24, 2026
75fc6cb
fix(interaction): release only the lifted finger on a partial lift in…
vjeux Oct 24, 2026
0a07aa1
fix(interaction): join the proofs to test, keep their setup in scope,…
vjeux Oct 24, 2026
92ef66c
Merge main into wip/uikit-pr-touch-presentation
cixzhang Oct 24, 2026
5f2ed50
fix(interaction): releaseVelocity measures from when the finger reach…
vjeux Oct 24, 2026
398c723
fix(core): the touch resolver reads the theme's compact definition
cixzhang Oct 24, 2026
33ee284
docs(MoreMenu): keep the doc's own formatting; change only the compac…
cixzhang Oct 24, 2026
d3c13cc
test(interaction): curve sampling and deterministic storyboards
vjeux Oct 24, 2026
65c017e
fix(interaction): curves end before a held end value; rest judges pre…
vjeux Oct 24, 2026
d8af73a
fix(interaction): hold only running animations at their own offsets, …
vjeux Oct 24, 2026
63e5356
fix(core): the animator's Reduced Motion follows design:reduced-motion
vjeux Oct 24, 2026
18ff410
fix(core): under Reduced Motion, travel lands with a plain write whil…
vjeux Oct 24, 2026
512b368
Merge #8062 (the touch resolver reads the theme's compact definition)…
vjeux Oct 24, 2026
1203525
Merge commit '706683bb99' into uikit/pr-export-t2
vjeux Oct 24, 2026
0268a58
test(build): the cross-file constants fixture keys on a width, not it…
vjeux Oct 24, 2026
38edf28
chore(core): the constants table names only what exists, and dates ev…
vjeux Oct 24, 2026
0783fcd
fix(core): paging past the last page lands on a real page
vjeux Oct 24, 2026
b419011
fix(core): an identity at rest leaves the element
vjeux Oct 24, 2026
1264c5b
fix(core): compact follows the active theme's md; the branch drops it…
cixzhang Oct 24, 2026
8103ede
fix(core): the animator's model holds a curve's start until the brows…
vjeux Oct 24, 2026
cea95bb
test(core): a fixed child stays fixed after a motion back to identity
vjeux Oct 24, 2026
ec08340
docs(spec): drop this branch's stacked copy of AST-094
cixzhang Oct 24, 2026
1dcf753
fix(core): theme motion role ids are checked against the generated li…
vjeux Oct 24, 2026
5aafeaf
fix(core): a motion reads a theme switched earlier in the same task
vjeux Oct 24, 2026
a5ec352
fix(core): resolveMotion observes and listens to nothing
vjeux Oct 24, 2026
6f81139
Merge #8075 (defineConsts read across files) into the experimental su…
vjeux Oct 24, 2026
9d8226d
feat(core): the Reduce Motion media keys are StyleX constants
vjeux Oct 24, 2026
ced7766
docs(ContextMenu, MoreMenu): the compact edge is the theme md breakpoint
cixzhang Oct 25, 2026
fe9fbba
fix(core): reading a motion table registers nothing, and a theme ship…
vjeux Oct 25, 2026
9fff2ac
fix(core): under Reduced Motion, two calls in one frame both land
vjeux Oct 25, 2026
14fe728
feat(core): export the Reduce Motion keys for other packages
vjeux Oct 25, 2026
7c604ea
test(lab): say what the Reduce Motion keys import test proves
vjeux Oct 25, 2026
dbd6d17
fix(core): Reduce Motion keys stay literal in a transition
vjeux Oct 25, 2026
0b81c86
fix(core): a retarget of a curve the browser has not started keeps it…
vjeux Oct 25, 2026
970c908
fix(core): a call on the clock of a waiting call from rest starts wit…
vjeux Oct 25, 2026
7091f8b
fix(core): experimental touch hooks stay out of stable search and doc…
vjeux Oct 25, 2026
4d728a7
refactor(core): drop the unused isCompactTouch from the touch present…
vjeux Oct 25, 2026
d34d800
test(core): the adaptive menu's compact edges in Chromium and WebKit
vjeux Oct 25, 2026
9960f84
refactor(core): export the animator's settle thresholds
vjeux Oct 25, 2026
65ac34a
test(core): a move during a Reduce Motion dip's fade out ends visible
vjeux Oct 25, 2026
5cb103f
fix(core): a time ahead reads a waiting curve from its call's time
vjeux Oct 25, 2026
49bb28a
fix(core): under Reduce Motion, only a call's own opacity makes it an…
vjeux Oct 25, 2026
189de88
fix(core): theme adaptations have no platform condition
vjeux Oct 25, 2026
2e170f5
fix(interaction): film from each animation's own offset, label sample…
vjeux Oct 25, 2026
601b54e
refactor(core): the animator reads a coarse pointer through TOUCH_QUERY
vjeux Oct 25, 2026
874ca93
docs(core): theme motion examples name a role that exists
vjeux Oct 25, 2026
04cbfc5
fix(interaction): waitForAnimation sees a short animation that ended …
vjeux Oct 25, 2026
d29981d
refactor(interaction): the storyboard layer moves to its own pull req…
vjeux Oct 25, 2026
2164696
fix(core): a page registers and ships only the motion tables it anima…
vjeux Oct 25, 2026
3e2d5e3
test(core): the role registry test checks every table discovery regis…
vjeux Oct 25, 2026
67f6ac1
chore(motion): ContextMenu's touch timeline and press curve, measured…
vjeux Oct 25, 2026
b496085
Merge main into the experimental subpaths (lab typechecks with its te…
vjeux Oct 25, 2026
c5efd27
Merge main into #8062 (lab typecheck fix aa3b92f320 and current main)
vjeux Oct 25, 2026
c325a79
Merge b49608584b (#8076, with main) into the context-menu timeline
vjeux Oct 25, 2026
beecc80
test(interaction): a second hold adds to the held set
vjeux Oct 25, 2026
7618bd8
test(core): the theme browser proof registers the springs table it re…
vjeux Oct 25, 2026
4a6dfbe
test(interaction): deterministic storyboards
vjeux Oct 25, 2026
ab35d4b
docs(core): state how a decay behaves under Reduce Motion
vjeux Oct 25, 2026
eec2404
test(interaction): a touch filmstrip's handler clock follows its pacing
vjeux Oct 25, 2026
0954697
test(interaction): frame evidence and the BottomSheet exemplar
vjeux Oct 24, 2026
6be18c6
test(interaction): make the busy-main-thread verdict robust to a load…
vjeux Oct 24, 2026
3f39b21
test(BottomSheet): pin the settle's easing and duration instead of re…
vjeux Oct 24, 2026
03eadf9
test(interaction): block after the frame's rendering update; rebase W…
vjeux Oct 25, 2026
9dc4ae9
docs(cli): the theme template names no platform condition
vjeux Oct 25, 2026
ffc2bd4
Merge branch 'main' into interaction-harness-2-curves
vjeux Oct 25, 2026
e918a2f
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
79a1fed
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
b8642ba
test(core): the touch presentation's browser proofs leave for the des…
vjeux Oct 25, 2026
f809dae
test(DropdownMenu): a sub-menu's drill-in follows a theme that moves md
vjeux Oct 25, 2026
4b5e872
refactor(core): readTouchPresentation takes the compact definition it…
vjeux Oct 25, 2026
8d0a829
test(interaction): a held animation the page cancels leaves the held set
vjeux Oct 25, 2026
cb37bd7
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
0b4d12b
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
b91dd6c
refactor(core): motion and gesture stay internal, with no experimenta…
cixzhang Oct 25, 2026
54a6ffe
Merge #8062 (the compact definition follows the theme's md point) int…
vjeux Oct 25, 2026
a21ddd7
fix(core): Reduced Motion takes no override
cixzhang Oct 25, 2026
5617fbb
fix(core): the Reduce Motion constants stay internal, and the touch c…
vjeux Oct 25, 2026
ba4ce66
test(core): a component reaches a motion table only by importing it
vjeux Oct 25, 2026
a71f4e5
build(lab): Lab type-checks core's internal motion and gesture modules
vjeux Oct 25, 2026
85171fd
feat(core): a held finger never starts text selection on a touch control
vjeux Oct 24, 2026
1ff0149
refactor(core): the gesture barrels no longer export VelocityStrategy
vjeux Oct 25, 2026
614b9e9
chore(core): the press feedback's selection lock is a [fix], the gest…
vjeux Oct 25, 2026
d8f7a42
chore(core): the gesture changesets here are internal, as the base's are
vjeux Oct 25, 2026
d908233
Merge a71f4e510e (#8076: motion and gesture internal, Reduced Motion …
vjeux Oct 25, 2026
cf240c9
chore(core): resolve a component's presentation so a caller's choice …
vjeux Oct 24, 2026
c81578e
refactor(core): the presentation resolver follows one touch model
vjeux Oct 25, 2026
377059e
fix(core): the presentation resolver reads the touch presentation #80…
vjeux Oct 25, 2026
9d35331
fix(core): a held finger keeps a link's native menu
vjeux Oct 25, 2026
d3b499a
fix(core): gesture host fixes from review
vjeux Oct 24, 2026
ee7bdeb
chore(core): the gesture changesets here are internal, as the base's are
vjeux Oct 25, 2026
4646969
fix(core): gesture hooks follow their element; recognizer plumbing is…
vjeux Oct 24, 2026
4278e7e
fix(core): useGestureRecognizer's isEnabled sync calls no consumer ca…
vjeux Oct 25, 2026
f491431
chore(core): the gesture changesets here are internal, as the base's are
vjeux Oct 25, 2026
4b75758
feat(core): claimsTouches: false makes a gesture watcher
vjeux Oct 24, 2026
df6a2ee
refactor(core): the gesture host and scroll probe read direction thro…
vjeux Oct 24, 2026
400b5da
feat(core): the gesture host holds the page still for a scroll lock
vjeux Oct 24, 2026
8d04846
feat(core): the gesture host tells a finger's scroll from the program's
vjeux Oct 24, 2026
00b2845
feat(core): the gesture host counts the fingers on the screen
vjeux Oct 24, 2026
bf6022d
feat(core): a scroll that ends while its finger rests stays the finge…
vjeux Oct 24, 2026
39231bc
feat(core): touch gates, and a second finger gives a one-finger pan up
vjeux Oct 25, 2026
9a94488
feat(core): a delivered press keeps its touch from the browser's pan
vjeux Oct 25, 2026
2d15d17
chore: changeset for the gesture host as the one document touch owner
vjeux Oct 25, 2026
6ff80ab
fix(core): keepsTouch is opt-in per press tracker
vjeux Oct 25, 2026
0ceed9d
fix(core): a second finger fails only a one-finger pan that has not c…
vjeux Oct 25, 2026
6ca3867
fix(core): detaching a keepsTouch press tracker removes its blocking …
vjeux Oct 25, 2026
51ab191
chore(core): the gesture changesets here are internal, as the base's are
vjeux Oct 25, 2026
058f7f6
Merge the gesture stack's host (#8336 51ab191647) into the timeline
vjeux Oct 25, 2026
374a213
Merge the presentation resolver (#8298 377059e522) into the timeline
vjeux Oct 25, 2026
a606d3f
feat(ContextMenu): open from a held finger on iOS 26's timeline
vjeux Oct 25, 2026
277fd33
feat(ContextMenu): presentation="popover" keeps today's menu under a …
vjeux Oct 25, 2026
a499860
docs(interaction): the touch filmstrip's clock limit names no missing…
vjeux Oct 25, 2026
212ec1d
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
b36bf63
docs(interaction): the touch filmstrip's clock limit names the screen…
vjeux Oct 25, 2026
91f8ddd
test(interaction): a hold stays where it was read
vjeux Oct 25, 2026
b5783ed
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
2a718ad
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
06e0f84
fix(ContextMenu): the press feedback imports the springs table from i…
vjeux Oct 25, 2026
3f44fe9
test(interaction): a block never starts on a pending animation
vjeux Oct 25, 2026
bc744cb
test(interaction): the WebKit container keeps compiled shaders betwee…
vjeux Oct 25, 2026
f898984
Merge commit 'refs/f3/main' into interaction-harness-1-touch
vjeux Oct 25, 2026
824bc84
Merge branch 'interaction-harness-1-touch' into interaction-harness-2…
vjeux Oct 25, 2026
0e3e379
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
9716595
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
4538572
ci: the harness's typecheck runs through pnpm typecheck, once
vjeux Oct 25, 2026
26f2b98
test(interaction): a watch on first contact ignores what already runs
vjeux Oct 25, 2026
d8fbea2
Merge branch 'interaction-harness-1-touch' into interaction-harness-2…
vjeux Oct 25, 2026
5850e7a
test(interaction): a later hold never moves the first hold's times
vjeux Oct 25, 2026
c6d91e8
ci: pin the interaction routing's fail-closed edges by running them
vjeux Oct 25, 2026
15a1a76
Merge branch 'interaction-harness-1-touch' into interaction-harness-2…
vjeux Oct 25, 2026
0a9e7bf
docs(interaction): seek and sample times count from the held origin
vjeux Oct 25, 2026
2438fc7
ci: a head cut before the interaction proofs passes with a notice
vjeux Oct 25, 2026
295c0da
Merge branch 'interaction-harness-1-touch' into interaction-harness-2…
vjeux Oct 25, 2026
eeef165
ci: a head cut before the interaction proofs passes with a notice
vjeux Oct 25, 2026
a9b2acf
fix(interaction): a watch keeps the start of an animation the page ca…
vjeux Oct 25, 2026
cf0ad08
chore(interaction): the WebKit container script moves up the stack
vjeux Oct 25, 2026
8afa44a
Merge branch 'interaction-harness-1-touch' into interaction-harness-2…
vjeux Oct 25, 2026
990fa36
chore(interaction): the BottomSheet detent exemplar leaves the harnes…
vjeux Oct 25, 2026
2642e06
fix(interaction): a storyboard retakes a photograph Chromium returns …
vjeux Oct 25, 2026
ca4fee0
test(interaction): pin what the long-task log sees; the cache test wa…
vjeux Oct 25, 2026
18b3bd3
docs(interaction): the storyboard retake waits two frames, three atte…
vjeux Oct 25, 2026
a68b352
test(interaction): the self-test films a cast window
vjeux Oct 25, 2026
ca46ccb
fix(ContextMenu): outside the touch model a held finger keeps today's…
vjeux Oct 25, 2026
ab5e517
test(ContextMenu): the primary pointer alone picks a finger's rules
vjeux Oct 25, 2026
7351693
Merge main (b023d1c829, AST-094 r4) into the press
vjeux Oct 25, 2026
265cec4
perf(ContextMenu): the touch layer loads only under the touch model
cixzhang Oct 25, 2026
44278bf
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
56d5de0
chore(interaction): the WebKit container script lands with the storyb…
vjeux Oct 25, 2026
cc613ef
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
618ed4e
Merge remote-tracking branch 'refs/remotes/origin/main' into interact…
vjeux Oct 25, 2026
37050d3
ci: the interaction jobs keep to the 15-minute budget and report thei…
vjeux Oct 25, 2026
cd141ab
Merge branch 'interaction-harness-1-touch' into interaction-harness-2…
vjeux Oct 25, 2026
9864ff8
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
fd1e7e1
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
b6cb3d8
Merge main (#8043 landed) into the curve sampling
cixzhang Oct 25, 2026
7ee8fea
Merge branch 'interaction-harness-2-curves' into uikit/f3-storyboards
vjeux Oct 25, 2026
b9c5d7b
Merge branch 'uikit/f3-storyboards' into interaction-harness-3-frames
vjeux Oct 25, 2026
1ad55aa
fix(ContextMenu): a finger before the touch layer attaches still open…
cixzhang Oct 25, 2026
4cca0d2
Merge #8045's interaction harness (b9c5d7b253) into the press
cixzhang Oct 25, 2026
55466e9
Merge main (106a98cfbd5) into the press
cixzhang Oct 25, 2026
bf001e6
test(ContextMenu): the selection lock test reads the host's per-press…
vjeux Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/build-stylex-cross-file-consts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/build': patch
---

[fix] `astryxStylex` resolves `defineConsts` values read from another file. With the `{stylexOptions}` form (the one Storybook uses), a file that read only constants from a `.stylex.ts` module lost that import once StyleX compiled it, so the constants never reached the CSS: a constant used as a media-query key failed the build ("Invalid empty selector") and one used as a value shipped as a custom property nothing declares. A constant defined on the library side of the layer split and read on the product side stayed unresolved in either form. A production build now also warns, naming the variable and the rule that reads it, when its stylesheet reads a StyleX-generated custom property that nothing declares; set `undefinedVariables: 'error'` to fail the build instead.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/contextmenu-presentation-popover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[feat] Under a finger, a ContextMenu whose `presentation` is omitted now opens from a held finger on iOS's timeline (the touch model). Pass `presentation="popover"` to keep today's menu on every device: the menu at the pointer, opened by a finger held for 500 ms (the long press). Where the primary pointer is fine (a mouse or trackpad), a finger keeps that same long press. The touch model's code loads only when it applies, so pages used with a mouse do not download it.

@vjeux
8 changes: 8 additions & 0 deletions .changeset/contextmenu-press-constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@astryxdesign/core': patch
'@astryxdesign/cli': patch
---

[chore] The UIKit constants table gains ContextMenu's touch timeline as measured on iOS 26.2: the press feedback starts at 150 ms, the press commits at 400 ms, and the menu displays at 735 ms. It also records the drift tolerance, and the press feedback's growth, spring and display latency. A closed-form press-feedback curve, checked against UIKit's per-frame presentation, comes with them. No component uses them yet. The feedback's spring and display latencies are motion roles a theme can retune, and `astryx docs motion` lists them. The onset, the commit and the menu's moment decide when a press counts and who gets the touch, so they stay out of the theme.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"No component uses them yet" no longer holds in this diff: .changeset/contextmenu-touch-timeline.md ships ContextMenu on these constants, so the combined release notes contradict each other. Dropping that sentence, or letting this changeset leave with its predecessor when the stack is split, fixes it.


@vjeux
7 changes: 7 additions & 0 deletions .changeset/contextmenu-touch-timeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[feat] ContextMenu opens from a held finger on iOS 26's measured timeline, on every touch device (a coarse primary pointer, iOS and Android alike). The trigger starts to grow at 150 ms (the onset of core's one press machine), following UIKit's measured press curve on the compositor, and the hold runs on useLongPress's detector with UIKit's measured moments. At 400 ms the press takes the finger: its lift no longer sends a click, and the page cannot scroll under it. The menu opens at 735 ms if the finger is still down, or as soon as it lifts after 400 ms. A finger that lifts before 400 ms, or drifts more than 11.5 px on either axis before the menu opens, gets no menu, and the trigger springs back. The page stays still while the menu is up, and the trigger returns to its size when the menu closes. A theme can retune the growth's spring and latency (motion roles). A touch open lights no row: the menu takes the focus, and a row lights only under the finger, as in UIKit (keyboard and pointer opens still focus the first item). A second finger cancels the press, and Android's own long-press menu no longer opens a second menu. A fine pointer (a mouse) keeps the right-click menu, and the touch recognizer stays off there.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-browser-negotiation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: touch-action derivation, the native scroll probe and haptics for the gesture system. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-hook-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture hooks follow their element and call nothing back after unmount, and turning `isEnabled` off mid-gesture ends the gesture without calling the listener from an Effect; `usePressTracker` takes `isBraking` and applies option changes from the next touch; the recognizer's arena plumbing is `protected`; `attachTapHaptic` gives focus straight back. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-host-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: gesture host fixes from review. A page scroll under a still finger keeps the press and its click; watchers and touches nothing can claim get passive listeners only; a claimed pen holds only its own stylus touches; an element's own `touch-action` is never widened; slotted content scrolls with its shadow-tree scroller. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-host-owner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture host becomes the one document-level touch owner: a scroll lock's page hold, finger scrolls, the finger count, touch gates, and a kept press for continuous-tracking controls. A second finger fails a one-finger pan that has not claimed, so the page can pinch. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-pan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the pan gesture recognizer, with UIKit's measured hysteresis and velocity filter. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-press-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: UIControl press tracking with control and cell profiles, the shared press paint, delegated press tracking and the gesture hooks. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's README for component authors, with its browser proof in Chromium and WebKit.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-recognizer-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture recognizer core for touch presentations: UIKit's recognizer state machine and the arena that settles which recognizer owns a touch. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-swipe-pinch-rotation-edge-hover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: swipe, pinch, rotation, screen-edge pan and hover gesture recognizers. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-tap-long-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: tap and long-press gesture recognizers with UIKit's measured thresholds. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-watcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: a recognizer created with `claimsTouches: false` is a watcher: passive listeners only, simultaneous with every recognizer, and it follows a touch the browser takes for a scroll to the lift. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/presentation-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] An internal `usePresentation` / `readPresentation` resolves a component's `presentation` prop into the surface to render and whether its touch input modes apply, keeping an omitted value distinct from an explicit `'popover'` so a caller's choice always wins over a touch default. No component reads it yet; behavior is unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"No component reads it yet; behavior is unchanged" no longer holds in this diff: ContextMenu resolves its presentation with usePresentation (ContextMenu.tsx:359) and .changeset/contextmenu-touch-timeline.md ships that change, so the combined release notes contradict each other. Drop the sentence here, or let this changeset leave with its predecessor when the stack is split.


@vjeux
7 changes: 7 additions & 0 deletions .changeset/reduced-motion-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] The experimental touch animator follows design:reduced-motion. Under Reduce Motion every `animate` and `play` (travel or opacity, on a spring or any curve) becomes an opacity-only cross-fade of 200 ms on a coarse primary pointer, or for a call marked `touchRelease`, and completes at once on any other pointer; nothing moves, scales or springs, and travel lands with a plain style write while the element is transparent, never as an animated transform. `timing.reducedMotionCrossFadeMs` is now 200 ms and is no longer a theme role. The `reducedMotion` and `crossfade` call options are removed; `crossfadeAs` stays. `animator.reducedMotion()` reports the mode for feedback with no fade of its own, `reducedMotionFade()` gives the same fade to code beside the animator, and `reducedMotion.stylex.ts` holds its constants for StyleX transitions.

@vjeux
8 changes: 8 additions & 0 deletions .changeset/touch-motion-theme-roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@astryxdesign/core': patch
'@astryxdesign/cli': patch
---

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

timing.reducedMotionCrossFadeMs is given as a role themes can retune, but motion/uikit/timing.ts:155-158 excludes it and .changeset/reduced-motion-record.md says it is no longer a theme role. Could this example name a role that ships?

[feat] Themes can retune touch motion roles. `defineTheme({motion})` takes `springs`, `durations` and `curves` keyed by motion role, the table path of a measured touch motion value (`springs.sheetPresent`, `navigation.transitionMs`), at the root or in an adaptation rule, and writes one `--motion-*` variable per role it sets. A role the theme leaves out keeps its measured default, an unknown role id is refused, and the duration scale (`fast`, `medium`, `ratio`) is optional when a theme sets only roles. No shipped component reads the roles yet. `astryx docs motion` lists every motion role with its CSS variable and default, and the theme authoring guide and template describe the new fields.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"No shipped component reads the roles yet" no longer holds in this diff: ContextMenu's press feedback reads its spring and latency roles (contextMenuPressFeedback.ts:149, :178), and the interaction proof "a theme retunes the press feedback (a motion role)" depends on it. Drop the sentence here, or let this changeset leave with its predecessor.


@vjeux
7 changes: 7 additions & 0 deletions .changeset/touch-selection-lock-gestures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture host takes the press feedback's selection lock for a press-tracked control's contact and for a recognizer created with `preventsTextSelection`, a non-text input under the finger included, and restores selection when the contact ends. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/touch-selection-lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] Under a finger or pen, holding a control or a cell that shows the touch press (the press feedback surfaces) no longer starts text selection or the iOS callout, as on UIKit controls. Selection and the callout come back when the finger lifts. Text fields keep their selection, a link keeps its native menu and preview, and a mouse keeps desktop selection.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-constants-table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the UIKit constants table for touch presentations (springs, scroll physics, timing curves and navigation values), each value with its source and a checked list of the simulator measurements still owed. Not exported from the package entry point; no component reads it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-animator-curves.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the touch presentations' animator plays any closed-form curve (such as a spring chasing a moving target), drives a pseudo-element such as a dialog's ::backdrop, follows design:reduced-motion (under Reduce Motion, an opacity-only cross-fade of 200 ms on a coarse pointer or after a touch release and an immediate change on any other pointer, with no option or theme value that keeps travel or lengthens the fade; an entrance or exit role orients a slide that changes no opacity), and starts a call from rest when the browser commits it, as Core Animation does. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-animator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: a compositor animator for touch presentations. It plays UIKit's springs, scroll coasts and any closed-form curve as Web Animations on translate, rotate, scale and opacity, so they keep running while JavaScript is busy, and reads values and velocities back from the curves for interruption, retargeting and Reduce Motion cross-fades. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-math.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the spring, decay, rubber-band and projection math of UIKit for touch presentations, as pure functions checked against an independent integration and against values read from UIKit. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-use-animator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: useAnimator, a React wrapper around the touch presentations' animator that never re-renders per frame, and browser proofs on the interaction harness that the animator's motion keeps running through a blocked main thread in Chromium and WebKit. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uselongpress-stages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[feat] useLongPress takes `stages`: later moments of the same hold, each fired once the finger has stayed that long from the start and still within `moveCancelPx`. ContextMenu claims the finger at its long press and shows its menu at a later stage. `start` also takes the press's start time, so the deadlines can count from a touch's own timestamp.

@vjeux
5 changes: 4 additions & 1 deletion .github/scripts/ci-interaction.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ describe('interaction proof routing', () => {
}
});

it('keeps the harness, components, Storybook and its own setup in scope', () => {
it('keeps the harness, components, themes, Storybook and its own setup in scope', () => {
const pattern = new RegExp(
decide().run.match(/bash "\$helper" "\$BASE_SHA" '([^']+)'/)[1],
);
Expand All @@ -242,6 +242,8 @@ describe('interaction proof routing', () => {
'internal/a11y-spec/src/storybook.ts',
'playwright.interaction.config.ts',
'packages/core/src/BottomSheet/BottomSheet.tsx',
// Themes set the motion durations components animate with.
'packages/themes/neutral/src/neutralTheme.ts',
'apps/storybook/.storybook/preview.tsx',
'.github/workflows/ci.yml',
'package.json',
Expand All @@ -253,6 +255,7 @@ describe('interaction proof routing', () => {
'docs/specs/AST-030/spec.md',
'apps/docsite/src/app/page.tsx',
'packages/core/package.json',
'packages/themes/neutral/package.json',
'.github/scripts/native-swift.sh',
]) {
expect(pattern.test(untouched), untouched).toBe(false);
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1766,7 +1766,7 @@ jobs:
&& git fetch --quiet --no-tags --depth=1 origin "$BASE_SHA" \
&& git show "$BASE_SHA:.github/scripts/pr-scope-touches.sh" > "$helper"; then
scope=0
bash "$helper" "$BASE_SHA" '^(internal/interaction-harness/|internal/a11y-spec/|playwright\.interaction\.config\.ts$|packages/[^/]+/src/|apps/storybook/|\.github/workflows/ci\.yml$|package\.json$|pnpm-lock\.yaml$)' || scope=$?
bash "$helper" "$BASE_SHA" '^(internal/interaction-harness/|internal/a11y-spec/|playwright\.interaction\.config\.ts$|packages/[^/]+/src/|packages/themes/[^/]+/src/|apps/storybook/|\.github/workflows/ci\.yml$|package\.json$|pnpm-lock\.yaml$)' || scope=$?
fi
if [ "$scope" = 1 ]; then
echo 'No harness, component, Storybook or proof-setup change; skipping the interaction proofs.'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ jobs:
- name: Typecheck every package
run: pnpm typecheck

- name: Typecheck interaction harness
run: pnpm -F @astryxdesign/interaction-harness typecheck

# Stable join for the three post-merge validation lanes.
test:
needs: [test-ui, test-node, typecheck]
Expand Down
1 change: 1 addition & 0 deletions apps/sandbox/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export default defineConfig({
unstable_moduleResolution: stylexResolution.stylexModuleResolution,
},
stylexPrefix: 'x',
undefinedVariables: 'error',
}),
react({
babel: {
Expand Down
6 changes: 6 additions & 0 deletions apps/storybook/.storybook/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const runtimeImports = [
describe('storybook main.ts workspace source aliases', () => {
let viteAliases: Record<string, string>;
let stylexAliases: Record<string, string[]>;
let undefinedVariables: unknown;
const stylexSpy = vi.spyOn(build, 'astryxStylex');

beforeAll(async () => {
Expand All @@ -69,6 +70,11 @@ describe('storybook main.ts workspace source aliases', () => {
throw new Error('Expected the Storybook StyleX options');
}
stylexAliases = options.stylexOptions!.aliases as Record<string, string[]>;
undefinedVariables = options.undefinedVariables;
});

it('fails the build on a generated variable nothing declares', () => {
expect(undefinedVariables).toBe('error');
});

afterAll(() => stylexSpy.mockRestore());
Expand Down
3 changes: 3 additions & 0 deletions apps/storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ const config: StorybookConfig = {
},
},
libraryPattern: 'packages/',
// CI catches a style that reads a generated variable nothing
// declares (a dropped declaration) instead of shipping it.
undefinedVariables: 'error',
}),
],
resolve: {
Expand Down
69 changes: 69 additions & 0 deletions apps/storybook/stories/ContextMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,3 +589,72 @@ export const InScrollContainer: Story = {
</div>
),
};

const touchStyles = stylex.create({
card: {
width: '200px',
height: '120px',
display: 'grid',
placeItems: 'center',
borderRadius: '12px',
backgroundColor: '#e5e7eb',
color: '#374151',
userSelect: 'none',
},
});

export const TouchLongPress: Story = {
name: 'Touch / long press',
parameters: {
docs: {
description: {
story:
'On a touch screen (iOS or Android), hold a finger on the card. As on iOS, it starts to grow after 150 ms, a haptic plays at 400 ms where the device can play one, and the menu opens at 735 ms, or as soon as the finger lifts after 400 ms. A finger that lifts before 400 ms, or drifts more than 11.5 px on either axis before the menu opens, gets no menu, and the card springs back. While the menu is up the page stays still, and the card returns to its size when the menu closes.',
},
},
},
render: () => (
<ContextMenu
data-testid="touch-context-menu"
label="Photo actions"
items={[
{label: 'Copy', icon: ClipboardIcon, onClick: () => {}},
{label: 'Share', icon: ShareIcon, onClick: () => {}},
{label: 'Save to Files', icon: ArrowDownTrayIcon, onClick: () => {}},
{type: 'divider'},
{
label: 'Delete',
icon: TrashIcon,
variant: 'destructive',
onClick: () => {},
},
]}>
<div {...stylex.props(touchStyles.card)}>Hold for actions</div>
</ContextMenu>
),
};

export const TouchLongPressPopover: Story = {
name: 'Touch / long press, presentation="popover"',
parameters: {
docs: {
description: {
story:
'With `presentation="popover"`, ContextMenu keeps today’s menu on every device: a finger held for 500 ms opens it at the finger, with no growth and no iOS timeline. A finger on a device whose primary pointer is fine (a mouse or a trackpad) gets the same long press.',
},
},
},
render: () => (
<ContextMenu
data-testid="touch-context-menu"
label="Photo actions"
presentation="popover"
items={[
{label: 'Copy', icon: ClipboardIcon, onClick: () => {}},
{label: 'Share', icon: ShareIcon, onClick: () => {}},
{label: 'Save to Files', icon: ArrowDownTrayIcon, onClick: () => {}},
]}>
<div {...stylex.props(touchStyles.card)}>Hold for actions</div>
</ContextMenu>
),
};
Loading
Loading