Repository navigation
Conversation
Touch presentations get one native-feeling behavior by default on every touch device, iOS and Android alike: measured values, compositor motion a finger can catch, recognizer-tracked gestures with native arbitration, two press profiles, and a Reduce Motion cross-fade. Touch motion is themeable, with the measured values as theme defaults; gesture thresholds stay internal. Size keys on the spec:AST-012 FR9 compact definition, a moved default is restored by the caller's presentation choice (INV16, DEC-23), and the record names the current claims it amends. Platform look and feel (OQ3) and module-level experimental subpaths (OQ4) stay open. Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
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.
Approval of spec:AST-094 waits on an engineering owner's decision, because DEC-1, DEC-2 and FR1–FR24 add touch requirements that no current spec states and the record amends current claims in five records (spec:AST-057 FR2, spec:AST-025 FR14, design:user-states DR1 and DR3, module:DropdownMenu/useMenuPress FR6 and spec:AST-013 FR3). An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner; the decision should also settle whether FR4 may make touch motion roles public before the record lists their names and value shapes. Whatever that decision, these points need fixing:
- FR7 sets aside "a rule that turns exits off by default" without naming it; no current record has one and the rule it answers is the open draft
spec:AST-090FR1, so drop the sentence and let whichever record lands second state the exception. - FR11 should say which sentence of
useMenuPressFR6 it replaces and keep "At most one row of a menu is lit", because the cell profile ends its highlight only when a scroll wins. - FR13 cites
architecture:public-component-apiINV14 for the compact definition, but the compact claim is INV17. - #8052 is still open and adds the same file, so close it or mark it superseded.
[Automated review]
| follows. | ||
| - **FR7 — Exits animate as the native counterpart's do.** A touch | ||
| presentation MUST play the exit its UIKit counterpart plays, on every touch | ||
| platform. A rule that turns exits off by default does not apply to touch presentations; |
There was a problem hiding this comment.
This sentence sets aside a rule no current record states. The rule it answers is the open draft spec:AST-090 FR1 ("Exits are off by default"), which covers Dialog and the anchored layers and says nothing about touch presentations; if both land as written they contradict each other with no precedence (architecture:knowledge-contracts INV9). The first sentence of FR7 already requires the UIKit exit, so dropping this one lets whichever record lands second state the exception.
| `design:user-states` DR1 (the pressed overlay during a hold) and DR3 (the | ||
| selected SegmentedControl item's held surface) for touch presentations, the | ||
| press treatment of `family:buttons`, and `module:DropdownMenu/useMenuPress` | ||
| FR6, whose rule that menu rows show no pressed paint under a finger gives way |
There was a problem hiding this comment.
useMenuPress FR6 has three sentences: "At most one row of a menu is lit", the press-start row losing its look once the pointer leaves it, and the :active-under-(hover: hover) rule. The cell profile above ends its highlight only when a scroll wins, so if all of FR6 gives way, a finger that slides from one row to another (FR1, FR2) leaves two rows lit. Naming the :active clause as the replaced sentence, and keeping the one-lit-row rule, removes the ambiguity.
| its touch presentation MUST follow the same conditions in every component. | ||
| Touch behaviors key on a coarse primary pointer and are on by default there; layout keys on the available size through the one compact definition the | ||
| theme exports, the same on every platform (`spec:AST-012` FR9, | ||
| `architecture:public-component-api` INV14), and a component never restates |
There was a problem hiding this comment.
INV14 is "Shared breakpoint names keep one meaning". The compact-definition claim in architecture:public-component-api is INV17 ("A component that chooses a presentation by screen size uses the theme's compact definition").
The maintainers' ruling (`spec:AST-094`, meta#8316): touch behaviour is one model on every touch device, iOS and Android alike, and nothing chooses a platform. SearchField no longer gates its motion or its glass on the iOS idiom: under a finger (a coarse primary pointer, `useTouchPresentation()` in script and TOUCH_QUERY in CSS) it plays UIKit's activation and shows the glass close button; under a mouse or a trackpad the parts take their places at once and the button is plain, as before. Reduced motion stays the animator's. Proofs: an Android finger (Chromium with no iOS signals) plays the 383 ms activation with the glass close button (it changed at once with a plain button before); a mouse still changes at once. The iOS-idiom emulation is gone from the proofs, and the jsdom role test's no-activation case is now a mouse.
…ed Motion On meta#8076 @ 1203525 (AST-094 as meta#8316 states it): the back swipes are touch behaviour under any finger, Android and iOS alike, keyed on a coarse primary pointer (useTouchPresentation); a mouse goes back with the bar's button. Reduced Motion is the animator's default (design:reduced-motion): under a finger the screens fade in place in 200 ms with no dimming, under a mouse they change at once, and what a finger's release starts fades on any pointer (touchRelease). navigation.backChevronReducedMotionDelayMs retires with its option; the motion roles and the docs golden are regenerated.
|
#8336 makes the gesture host the document's one touch owner. Against this record at 58cb828, some of its behaviors are forbidden by the current text and some aren't covered. Here are proposed amendments, each with the case that needs it. Accept, change or reject each one, and #8336 will follow.
FR10 needs no change. #8336 now follows it: a second finger fails only a one-finger pan that has not claimed, and a claimed drag keeps its finger. |
| - **FR11 — Two press profiles.** Press feedback MUST follow one of two native | ||
| profiles. A control (UIControl) highlights on touch-down outside scroll | ||
| views, keeps tracking within its expanded bounds (70 pt), turns its highlight | ||
| off and on as the finger drags out and back, and activates on a lift inside. |
There was a problem hiding this comment.
FR11's control profile can't be met on the web as written, and meeting it changes how a control is activated, so this needs a ruling on the spec before any code.
Measured in Chromium with touch emulation, a held finger on a button, on a page with nothing to scroll:
- Default
touch-action: by 20 px of drag the browser sendspointercancel. It claims the pan even with nothing to scroll (FR20), so the press is cancelled long before 70 pt, and a lift back on the button fires no click. touch-action: pinch-zoomon the button: the pointer survives the drag, but once the finger has moved the browser fires no click, even for a lift inside the button.
So tracking out to 70 pt would only show a press the browser won't activate, unless two things change. Proposal, scoped to where UIKit tracks (a control with no scroller to take the touch: one in a fixed or sticky bar, as FR29 uses the term), under a coarse pointer only:
- A control in a bar gets
touch-action: pinch-zoom, from static CSS decided before the touch. That matches UIKit, where a drag that starts on a toolbar button never scrolls the content, and it keeps page zoom (FR18). - After a drag, a lift inside the 70 pt band activates the control from the press controller, and the browser's click for that gesture, if one fires, acts on nothing (the rule FR21 and FR26 already set for gestures and menu rows). React Aria's
usePressactivates this way.
Controls in scrolling content keep today's model: cancelled when the scroll takes the touch, as UIKit's are. A fine pointer is unchanged, and activation with detail === 0 always passes (FR21).
Question: should FR11 require this for controls in a bar? Or should it say that on the web a control's press ends when the browser takes the touch, and activation stays the browser's click?
The maintainer ruled one touch model on every touch device, iOS and Android alike, with no platform idiom (meta#8316); a supports query keyed to iOS is allowed only for styles WebKit itself provides or requires. Dynamic Type is such a capability, so it is now gated by the capability alone: the Dynamic Type CSS sits inside a supports query for font: -apple-system-body, and the root Theme probes wherever that keyword exists, with no touch-callout or pointer check. Where the keyword exists but follows no reader setting, nothing changes: macOS Safari's Body is a fixed 13px that names no category, so no category is written and every scale stays 1. Chromium and Playwright's Linux WebKit do not support the keyword. The two tests that checked the iOS idiom now check the capability: an engine with a touch callout but no font: -apple-system-body writes nothing, one with the keyword and no touch callout follows the reader's text size, and a fixed 13px Body writes no category. Docs, the story readout and the changeset say where it applies.
The maintainers ruled one touch model on every touch device, iOS and Android alike, with no platform idiom (the touch resolver in meta#8062, now in meta#8076; AST-094 in meta#8316): touch behaviors key on a coarse primary pointer, and the resolver no longer reports an idiom. The slider's touch presentation (the thumbs' touch targets, UISlider's tracking and coast, the track's tap, the lift) now applies under any finger; a mouse or a trackpad keeps the pointer handlers. The docs, story and changeset say "under a finger" instead of "on iOS". Tests: the Android cases that kept today's slider are flipped. The unit case gives a coarse pointer with no iOS engine (no -webkit-touch-callout) the touch targets and leaves a finger on the track to the touch presentation; it fails on the previous head. The interaction cases run with no iOS-engine emulation, and the Android case now checks the same model there: the lift, the travel past the threshold and a track tap that sets the value on its lift (the Android Reduce Motion case was the iOS one's duplicate and goes). The fine-pointer cases stay as they were: no touch targets, and the desktop geometry case's pointer styles.
Touch presentations get one native-feeling behavior by default on every touch device: measured values, compositor motion a finger can catch, recognizer-tracked gestures, two press profiles, a Reduce Motion cross-fade (design:reduced-motion), menus and popovers under a finger (FR26-FR35), and one release decision for every drag that commits (FR36-FR40). Touch motion is themeable through purpose-named roles; thresholds stay internal. useMenuPress, DropdownMenu, ContextMenu and Popover point to the new requirements. Retries meta#8316, whose review rounds were spent, carrying its last revision plus the review fixes: the record is current with its deciders named, FR34 follows DropdownMenu's drill-in (a parent-visible sub-menu is OQ10), the role list covers row swipes, Popover and sub-menus with purpose-led names, FR2 admits an owner decision as a source, and the quick-tap and longPressDelayMs wording is settled. Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
…th the menu beside it The lift stays inside the layer runtime's viewport gutter (spec:AST-059), read through a probe and carrying the LayerProvider's inset, instead of UIKit's own margins. The copy grows out of the trigger's rect and back on core's rect morph (motion/rectMorph.ts), laid out at the trigger's size so its content scales. The tint is the theme's modal overlay (--color-overlay), with no token of its own. The lift follows spec:AST-094 FR33 (meta#8316) and only applies with the touch model: presentation="popover" keeps the menu where it opens.
…y touch device One touch model (AST-094, meta#8316): the spring presentation keys on a coarse primary pointer (`useTouchPresentation().isTouch`) instead of iOS's idiom, so a finger on Android opens and closes the sheet on UIKit's spring, holding input while it presents, as on iOS; a mouse keeps today's transitions. The Android cases flip, and the reduced-motion fade runs for both.
Supersedes #8052 (vjeux's record, credited as co-author). It carries his record plus the owner rulings recorded since, and fixes the two review findings left at #8052's last round. The record itself contains no pull request references.
Exact decision needed
Approve
spec:AST-094: touch presentations get one native-feeling behavior by default on every touch device, iOS and Android alike. Open questions: OQ5 (the menu's drift allowance), OQ6–OQ8 (opening a sub-menu without a lift, the opening haptic, a withdrawn tentative open), and OQ9 (where a menu sits on a compact touch screen). Module-level experimental subpaths are closed: motion and gestures stay internal (FR4). Platform look and feel is a non-goal tracked separately. Release velocity is governed byspec:AST-100.Existing authority searched
component:SelectorDEC-1 (theadaptivequery);spec:AST-013FR3 (an Android Chrome support row);spec:AST-025FR14 (nested scrolling);spec:AST-057(Item's swipe constants and FR2);design:user-statesDR1/DR3;family:buttons;module:DropdownMenu/useMenuPressFR6.module:DropdownMenu/useMenuPressFR4;design:user-statesDR4;spec:AST-043FR1;spec:AST-012FR9 (compact);design:reduced-motion;architecture:public-component-apiINV14, INV16;spec:AST-017FR3, DEC-23;spec:AST-002FR3;architecture:react-component-runtimeINV5/INV7;family:overlay-dismissalAV6.Decision
design:reduced-motionDR2 and DR4.spec:AST-012FR9 compact definition where a presentation depends on size. The touch presentation applies after hydration.presentationchoice. Touch work costs nothing under a fine pointer.spec:AST-100.Rejected alternatives
presentationis the way back, INV16 and DEC-23).Claim scope and non-goals
Evidence and implementation relationship
The values come from UIKit read on the simulator, Apple documentation, WWDC sessions and WebKit's source. Implementation stacks on this record. Implementations: #8078 (menu press and handoff), #8080 (menu grow and collapse), #8187 and #8198 (Popover grow and pan-outside dismissal), #8201 (ContextMenu lift), #7308 (ContextMenu under a finger), #8341 (the release decision), #8220 (sub-menus in place), #8223 (the presentation hold), #8195 (the menu cell profile); #8089 (menu placement on a phone) is OQ9.
Writing rubric results
approved_by.Scope
architecture:knowledge-contractsINV18).Validation
pnpm check:knowledge: aligned.git diff --check: clean.