Repository navigation
Conversation
…e roles yet) into the role-reading animator The motion README's introduction keeps the animator and adds #8257's sentence that no shipped component reads the roles yet.
…t, with no theme surface Touch motion stays internal (the maintainer's ruling; the base keeps only spec:AST-094 FR4's roles, internal too). segmentedControl.selectionSpring, UISegmentedControl's measured spring, is a plain constant: no registerSegmentedControlTable, no role, no theme variable. - The record's FR6, the changeset and the component doc say so; the doc loses "a theme can retune the slide". - The browser proof that a theme retunes the slide becomes one that the measured spring plays whatever a theme sets (the old role variable, a slow duration scale); the Reduce Motion DR4 proof slows the theme's durations instead of a role variable. - The table test holds the spring to no role and to its measured value under the old variable.
…ty back on attach Under Reduce Motion on a touch screen, a dip over the stylesheet's opacity that ended while the animator was detached wrote the borrowed value inline on the next attach, so the element stopped following its stylesheet. attach now hands a finished or interrupted borrow back instead.
…xport reverted; overlap and borrow fixes) Conflicts in animator.ts and the motion README: #8056's Reduce Motion plan wins, which already carries a waiting move to the next call's invisible moment and keeps a running dip's borrow. #8055's new tests run against the plan, the re-timing one advancing the clock to the landing and the preference change written through the media stub.
…hey hold - dispose() settles a move still waiting to land, and the calls it carries, as interrupted, where it left their finished pending for good. - stop() and a node replacement honour an exit by role's borrowed restore: the stylesheet's opacity goes back to the stylesheet instead of staying inline. - releaseIdentity's doc comment sits on releaseIdentity again.
…es only the springs it plays The springs table was one object, so a component that played one sheet spring bundled every spring (architecture:component-style-authoring INV6). Each row is now its own export (SHEET_PRESENT_SPRING, SETTLE_SPRING, ...), a literal of its own so a bundler can drop the rows nothing imports, and springs composes them by row name for the existing readers; a row export is the same object as its row. The registry takes only the springs spec:AST-094 FR4 names, so registering the roles ships no other spring. The table test reads a row's source from the JSDoc above its export, and checks that rows measured on one spring stay equal now that they no longer spread a shared base. A bundle test imports one row, and registers the roles, and finds no other spring; both fail on the previous code.
Under the plan a detach lands the dip's move at once, handing the stylesheet's opacity back in the model, but the fade's end value written inline stayed on the element, so re-attaching it showed opacity 0. The detach now removes it with the borrow (#8055's late-attach test).
…ng animator The table test keeps both rules: a row's source sits in the JSDoc above its export, and a value bound from an exported group is checked where the group declares it.
…dip's opacity back Under Reduce Motion on a touch screen, attach(null), attaching another element or dispose() during a dip over the stylesheet's opacity left that opacity inline on the outgoing element. All three now end the borrow on the element it ran on, which replaces the late-attach hand-back (a borrow no longer outlives its element).
…y pointer is coarse and a recognizer is enabled From CodeBunny on #8073 (spec:AST-094 FR19), fixed once here: the host wrote its managed touch-action on every device. It now writes it only while the primary pointer is coarse and an attached recognizer is enabled, and restores the element's own value when either stops being true; with the default coarse-pointer read it follows the media query's change too. The test observes no touch-action write under a fine pointer, the write once coarse, its removal while the recognizer is disabled and again when the pointer turns fine.
…ated press fold into utils/pressFeedback.ts The touch press controller is the one writer of data-astryx-press: paintPress and readPressPaint move from gestures/pressPaint.ts into utils/pressFeedback.ts, and the controller writes through the same function, so a fade's clock and the attribute never drift apart and a press never overwrites a held row. gestures/delegatedPress.ts, a second press machine no component installed, is removed with its tests and its exports from the internal gestures index, so no surface changes. paintPress drops its attribute option, which only the delegated press used.
# Conflicts: # packages/cli/assets/docs/motion.doc.mjs # packages/cli/test/__golden__/no-icon-set/docs-topics.json # packages/core/src/motion/roleKinds.generated.ts
…stants, with no theme surface Touch motion stays internal (the maintainer's ruling; the base registers only spec:AST-094 FR4's roles, internally). The table's values (the selection, lift and tracking springs, the lift's growth, the tap's hang, the drag's reach and the pressed title's clocks, curves and opacity) are plain constants: no registerSegmentedControlTable, and the touch module reads them directly instead of resolving them as roles. - The component doc loses "a theme can retune the slide and the lift". - The browser proof that a theme retunes the slide becomes one that the measured spring plays whatever a theme sets (the old role variable, a slow duration scale); the Reduce Motion DR4 proof slows the theme's durations instead of a role variable. - The table test holds every value to no role, and the springs to their measured values under the old role variables. The look stays the theme's: the lift's size follows its spacing, the shadow is --shadow-high, and the dim is the item target's pressed-state opacity.
#8073 moved paintPress into utils/pressFeedback.ts and removed the delegated press, which nothing installed; the README still described installPressAttribute and usePressAttribute and listed their test. The rows section now names the document press controller, and paintPress its module.
# Conflicts: # packages/core/src/SegmentedControl/SegmentedControl.doc.mjs # packages/core/src/SegmentedControl/SegmentedControl.spec.md # packages/core/src/SegmentedControl/__tests__/SegmentedControl.interaction.browser.spec.ts # packages/core/src/SegmentedControl/useSegmentedSelectionMotion.ts # packages/core/src/motion/uikit/segmented.test.ts # packages/core/src/motion/uikit/segmented.ts
…ites its record - Pinch, rotation, screen-edge pan and swipe import their per-group table exports (UIKIT_PINCH, UIKIT_ROTATION, UIKIT_SCREEN_EDGE_PAN, UIKIT_SWIPE) instead of the UIKIT_GESTURES union, so a bundle that uses one recognizer carries only its group (INV6). - HoverGestureRecognizer's header names its governing record: architecture:interaction-modality (hover only enhances). The one-edge ScreenEdgePan touch-action stays interim per current FR18; D13a is pending.
… gesture host uses off iOS The gesture line's selection lock (`spec:AST-094` FR41, #8270) now refuses a held finger's selection by cancelling `selectstart` wherever iOS WebKit does not run, and writes `user-select` only where it does (iOS WebKit fires no `selectstart` for a long press). The proof read the control's computed `user-select`, so it failed in Chromium (`auto`) and WebKit (`text`) on the new line although the lock held. It now asks whether a selection starting on the held segment is refused: a cancelable `selectstart` dispatched there is cancelled while the finger is down and not after the lift, and `user-select` stays as at rest throughout (no style write, nothing repaints). With the pan's `selectionLock` removed the proof fails in both engines; iOS WebKit's style path stays covered by the simulator recordings.
There was a problem hiding this comment.
This pull request is on hold until an engineering owner approves or rejects the draft SegmentedControl record's FR5–FR10 (the touch slide, the pressed dim, the lift, the label colour follow and drag-to-select), including whether design:user-states DR1/DR3 and spec:AST-094 FR11, which still require the pressed overlay and an unchanged held surface under a finger, are amended. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, which the author may do if they are an engineering owner. The re-entry dim is fixed at this head; whatever the decision, these still need changes:
- UISegmentedControl's spring, lift and dim clocks are played as constants (
motion/uikit/segmented.ts), although AST-094 FR4 requires them to be motion roles in the generated role table. - The
pressedstate is set outsidethemeProps()and is missing from the item target'sstatesinSegmentedControl.doc.mjs, against the theming-surface architecture's INV6. - Each segment keeps its first pressed-opacity reading until the control unmounts, so after a theme changes the
pressedopacity a segment pressed before still dims to the old value; forgetting the reading once its return finishes would fix it. - The compare against
mainis about 29,000 changed lines, with SegmentedControl's touch layer imported statically and its size unmeasured againstmain, because #8127's stack is in it; landing #8127 first leaves this change at its four files.
[Automated review]
| */ | ||
| private pressedOpacity(segment: HTMLElement): number { | ||
| const kept = this.pressedOpacities.get(segment); | ||
| if (this.dimmedTitles.has(segment) && kept !== undefined) { |
There was a problem hiding this comment.
This returns the kept reading whenever the segment is in dimmedTitles, and dimmedTitles is only cleared by stop(), which runs on unmount. So once a segment has been pressed, a later change to the pressed state's opacity (a theme switch) never reaches it: with the pressed opacity changed to 0.5, a segment pressed for the first time dims to 0.5 while one pressed before still dims to 0.2. Forgetting the reading when the segment's return to 1 finishes would keep the re-entry fix and pick up theme changes; a test that changes the state's opacity between two presses would cover it.
| * | ||
| * Every leaf carries one or more `@source`; uikit.test.ts enforces it. | ||
| * Durations are in ms; curves are cubic-bézier control points | ||
| * [x1, y1, x2, y2]. The values are internal constants: `spec:AST-094` FR4 |
There was a problem hiding this comment.
AST-094 FR4 says every spring, duration and curve a touch presentation plays MUST be a motion role, with a component's measured roles joining the generated motion role table (DEC-5); DEC-11 keeps roles internal but does not exempt them. Registering these values as internal component roles (as the navigation and scroll tables do), or an owner amending FR4, would resolve it.
| clock: 'fade' | 'lift', | ||
| ): void { | ||
| if (isPressed) { | ||
| segment.setAttribute(PRESSED_STATE, 'pressed'); |
There was a problem hiding this comment.
The pressed state is reflected here with setAttribute, outside themeProps(), and SegmentedControl.doc.mjs still lists only ['selected', 'disabled'] as the item target's states, while the docs tell theme authors to set the dim on pressed. The theming-surface architecture's INV6 has interaction state reflected as a target capability; declaring pressed in the target's states, and either reflecting it through themeProps() or recording this imperative reflection as an exception, would resolve it.
…e pressed state again The kept pressed-state reading lasted until unmount, so a theme change to the pressed opacity never reached a segment pressed before. The reading is now kept only while the segment's dim is still moving (pressDim's new shownPressDim reads what it shows); once the return finishes, the next press reads the state again. A new test retunes the state between two presses and fails on the old rule.
There was a problem hiding this comment.
This pull request is on hold until an engineering owner approves or rejects the draft SegmentedControl record's FR5–FR10 (the touch slide, the pressed dim, the lift, the label colour follow and drag-to-select), including whether design:user-states DR1/DR3 and spec:AST-094 FR11, which still require the pressed overlay and an unchanged held surface under a finger, are amended. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, which the author may do if they are an engineering owner. The theme-change reading is fixed at this head; whatever the decision, these still need changes:
- UISegmentedControl's spring, lift and dim clocks are played as constants (
motion/uikit/segmented.ts), although AST-094 FR4 requires them to be motion roles in the generated role table. - The
pressedstate is set outsidethemeProps()and is missing from the item target'sstatesinSegmentedControl.doc.mjs, against the theming-surface architecture's INV6. pressedOpacityre-reads computed style as soon as the animator's model reports 1, but the return's inlineopacity: 1and its held animation stay until the animator's retire timer runs, so a press in that gap dims to 1 instead of 0.2.- The compare against
mainis about 29,000 changed lines, with SegmentedControl's touch layer imported statically and its size unreported againstmain, because #8127's stack is in it; landing #8127 first leaves this change at its four files.
[Automated review]
| */ | ||
| private pressedOpacity(segment: HTMLElement): number { | ||
| const kept = this.pressedOpacities.get(segment); | ||
| if (kept !== undefined && shownPressDim(segment) < 1) { |
There was a problem hiding this comment.
shownPressDim is the animator's model value, which reads exactly 1 as soon as the return curve's time has passed, but the return's inline opacity: 1 (animator.ts rebuild) and its fill: 'both' hold stay until the retire setTimeout runs. A press in that gap re-reads getComputedStyle(segment).opacity and gets 1 instead of the pressed state's 0.2, so that press shows no dim and keeps 1. Treating the dim as at rest only once the animator has released the opacity (no inline value, nothing running on the channel), and a test that presses between the curve's end and the retire timer, would cover it.
…face before writing it paintLayer copied the selected segment's computed surface onto the radiogroup's layer variables one property at a time: read a value from the segment's computed style, write it to the track, read the next. A write to the track's style makes the next read from a computed style recompute it, so every paint restyled the control once per surface property. A Chromium trace of an Android lift (Reduce Motion, where a change paints both layers) put about 60 ms of the touchend handler there: 63 forced restyles of 11 elements, `getPropertyValue` taking most of its samples. It now reads every value first, then writes them. A unit proof records the style traffic of a change (each computed declaration taken, each value read from one, each write to the track) and asks that no read follow a write made after its declaration was taken: 50 such reads before this change, none after.
…or has let go of the segment A return's curve can end before the animator retires its inline opacity and fill hold; a press in that gap re-read getComputedStyle and got 1. pressDim's isPressDimReleased (replacing shownPressDim) now says the dim has let go only when the animator shows 1 and holds no inline opacity and no running opacity animation on the element; until then the segment keeps its reading. A new test presses in that gap and fails on the old check.
# Conflicts: # packages/core/src/SegmentedControl/useSegmentedSelectionMotion.test.ts
…hey write them The style-traffic proof #8094 adds for a change also runs through the touch path, the one a Chromium trace of an Android lift measured: a press, a drag across two segments and the lift's commit, with and without Reduce Motion. No read from a computed style follows a write to the track made after it was taken; on the interleaved paint this path made 50 such reads.
…re from it shows only this PR's change
There was a problem hiding this comment.
This pull request is on hold until an engineering owner approves or rejects the draft SegmentedControl record's FR5–FR10 (the touch slide, the pressed dim, the lift, the label colour follow and drag-to-select), including whether design:user-states DR1/DR3 and spec:AST-094 FR11, which still require the pressed overlay and an unchanged held surface under a finger, are amended. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, which the author may do if they are an engineering owner. The dim after a return now waits for the animator to let go, which fixes the earlier no-dim press; whatever the decision, these still need changes:
- UISegmentedControl's spring, lift and dim clocks are played as constants (
motion/uikit/segmented.ts), although AST-094 FR4 requires motion roles in the generated role table. - The
pressedstate is set outsidethemeProps()and is missing from the item target'sstatesinSegmentedControl.doc.mjs, against the theming-surface architecture's INV6. - A screen-edge pan given two horizontal edges sets
touch-action: pan-y pinch-zoomon the whole element, which AST-094 FR18 (as amended by DEC-13) rules out outside the edge bands. - The size report against
mainneeds SegmentedControl's CSS delta and a justification for the statically imported touch cost, and the compare is about 29,000 changed lines because #8127's stack is in it; landing #8127 first leaves this change at its four files.
[Automated review]
| * [x1, y1, x2, y2]. The values are internal constants: `spec:AST-094` FR4 | ||
| * names no segmented-control motion role, so no theme retunes them. The lens |
There was a problem hiding this comment.
AST-094 FR4 makes every spring, duration and curve a touch presentation plays a motion role, with a component's measured roles joining the generated motion role table (DEC-5), and says a component "MUST NOT play a literal". DEC-11 keeps the roles internal; it does not exempt these values from being roles. Registering selectionSpring, liftSpring and the pressedTitle clocks there (still internal) and reading them through the table would conform, as the navigation and scroll domains already do.
| clock: 'fade' | 'lift', | ||
| ): void { | ||
| if (isPressed) { | ||
| segment.setAttribute(PRESSED_STATE, 'pressed'); |
There was a problem hiding this comment.
The pressed state is reflected here with setAttribute, outside themeProps(), and SegmentedControl.doc.mjs still declares the item target's states as ['selected', 'disabled'], while its description tells theme authors to set the dim on the pressed state. The component-theming-surface architecture's INV6 asks for interaction state to be reflected as a target capability through themeProps; declaring pressed in the target's states, and reflecting it there or recording the exception, would close that.
| override get nativeTouchBehavior(): NativeTouchBehavior { | ||
| if (this.edges.length === 1) { | ||
| return ALL_NATIVE_TOUCH; | ||
| } | ||
| const horizontal = this.edges.some( | ||
| edge => edge !== 'top' && edge !== 'bottom', | ||
| ); | ||
| const vertical = this.edges.some( | ||
| edge => edge === 'top' || edge === 'bottom', | ||
| ); | ||
| return { | ||
| panX: !horizontal, | ||
| panY: !vertical, | ||
| pinchZoom: true, | ||
| doubleTapZoom: false, | ||
| }; | ||
| } |
There was a problem hiding this comment.
With two horizontal edges this reports panX: false, so the host writes touch-action: pan-y pinch-zoom on the whole element and the author's sideways scrolling stops everywhere, not only in the 40.2 px bands. AST-094 FR18, as amended on main by DEC-13, says a screen-edge pan claims pan-x only for a touch that lands in the edge band and leaves the author's scrolling untouched outside it. Keeping every native behaviour for several edges too, and claiming per touch inside each band, would match the one-edge path.
… through internal segmented motion roles spec:AST-094 FR4 has every spring, duration and curve a touch presentation plays be a motion role; SegmentedControl played its table's values as constants. registerSegmentedTable registers selectionSpring, liftSpring, tapLiftHangMs and the pressed title's clocks and curves as internal roles (DEC-11: no theme surface), FR4's role table names them with their measured values, and the motion registers the table and reads the durations and curves through resolveMotion. A new test checks each role. Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
There was a problem hiding this comment.
This pull request is on hold until an engineering owner approves or rejects the draft SegmentedControl record's FR5–FR10 (the touch slide, the pressed dim, the lift, the label colour follow and drag-to-select) and the seven segmented.* motion roles this head adds to spec:AST-094 FR4, including whether design:user-states DR1/DR3 and AST-094 FR11, which still require the pressed overlay and an unchanged held surface under a finger, are amended. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, which the author may do if they are an engineering owner. Whatever the decision, these still need changes:
- The
pressedstate is set outsidethemeProps()and is missing from the item target'sstatesinSegmentedControl.doc.mjs, against the theming-surface architecture's INV6. - A screen-edge pan given two horizontal edges sets
touch-action: pan-y pinch-zoomon the whole element, which AST-094 FR18 (as amended by DEC-13) rules out outside the edge bands. - The stylesheet growth the size report measures (+1,152 B gzip against
main), almost all of it touch-only CSS, is over the style-authoring architecture's 1 KB flag (INV6) and needs a justification. - The compare is about 29,000 changed lines because #8127's stack is in it; landing #8127 first leaves this change at its four files.
[Automated review]
| | `segmented.selectionSpring` | response 0.4 s, damping ratio 0.85 | measured: UISegmentedControl's selection slide, iOS 26.2 simulator, 2026-10-23 | | ||
| | `segmented.liftSpring` | response 0.25 s, damping ratio 1 | measured: UISegmentedControl's lens lift, iOS 26.2 simulator, 2026-10-23 | | ||
| | `segmented.tapLiftHangMs` | 230 ms | measured: UISegmentedControl's lens lift held through a tap's slide, iOS 26.2 simulator, 2026-10-23 | | ||
| | `segmented.pressedTitle.fadeMs` | 470 ms | measured: UISegmentedControl's pressed title dimming, iOS 26.2 simulator, 2026-10-24 | | ||
| | `segmented.pressedTitle.fadeCurve` | cubic-bezier(0.25, 0.1, 0.25, 1) | measured: UISegmentedControl's pressed title dimming, iOS 26.2 simulator, 2026-10-24 | | ||
| | `segmented.pressedTitle.liftMs` | 180 ms | measured: UISegmentedControl's pressed title returning at the lift, iOS 26.2 simulator, 2026-10-24 | | ||
| | `segmented.pressedTitle.liftCurve` | cubic-bezier(0.25, 0.1, 0.25, 1) | measured: UISegmentedControl's pressed title returning at the lift, iOS 26.2 simulator, 2026-10-24 | |
There was a problem hiding this comment.
These seven segmented.* rows add roles to a current record's FR4 table, so they need an engineering owner's decision (/approve-spec <full head SHA>), together with the draft SegmentedControl FR5–FR10 they time. FR4 already lets a component's own record add measured roles through the generated motion role table (DEC-5), which registerSegmentedTable does in code.
| clock: 'fade' | 'lift', | ||
| ): void { | ||
| if (isPressed) { | ||
| segment.setAttribute(PRESSED_STATE, 'pressed'); |
There was a problem hiding this comment.
The pressed state is reflected here with setAttribute, outside themeProps(), and SegmentedControl.doc.mjs:89 still declares the item target's states as ['selected', 'disabled'], while its description tells theme authors to set the dim on the pressed state. The component-theming-surface architecture's INV6 asks for interaction state to be reflected as a target capability through themeProps; declaring pressed in the target's states, and reflecting it there or recording the exception, would close that.
| edge => edge === 'top' || edge === 'bottom', | ||
| ); | ||
| return { | ||
| panX: !horizontal, |
There was a problem hiding this comment.
With two horizontal edges this reports panX: false, so the host writes touch-action: pan-y pinch-zoom on the whole element and sideways scrolling stops everywhere, not only in the 40.2 px bands. AST-094 FR18, as amended on main by DEC-13, says a screen-edge pan claims pan-x only for a touch that lands in the edge band and leaves the author's scrolling untouched outside it; keeping every native behaviour for several edges too, and claiming per touch inside each band, would match the one-edge path.
…passes the late-click guard; the pressed dim is internal, not a theming state - The late-click guard lets a click with detail 0 through (a screen reader's or switch access's activation), as AST-094 FR21 and the shared tracker's swallower do; isLateClick holds the rule, with tests. - The pressed dim stays UIKit's internal 0.2: the item comment and the usage doc no longer tell themes to retune it on a pressed state the theming catalog does not declare (component-theming-surface INV6, INV7). - segmented.test.ts asserts the registered segmented roles, and the motion header says the values play through those internal roles. Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…etune the internal dim Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…alls the internal dim a theme value
There was a problem hiding this comment.
This pull request is on hold until an engineering owner approves or rejects the draft SegmentedControl record's FR5–FR10 (the touch slide, the pressed dim, the lift, the label colour follow and drag-to-select) and the seven segmented.* motion roles this head adds to spec:AST-094 FR4, including whether design:user-states DR1/DR3 and AST-094 FR11, which still require the pressed overlay and an unchanged held surface under a finger, are amended. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, which the author may do if they are an engineering owner. Whatever the decision, these still need changes:
- The press dim is now described as internal, but it is still written as a
data-pressedstate that a theme'spressedkey reaches, outsidethemeProps()and undeclared, while FR7 and the changeset still promise a themed 0.2 default; one contract has to win (theming-surface architecture INV6). - A screen-edge pan given two horizontal edges sets
touch-action: pan-y pinch-zoomon the whole element, which AST-094 FR18 (DEC-13) rules out outside the edge bands. - The touch-only stylesheet growth against
main(+1,152 B gzip by the description's figures) needs a justification under the style-authoring architecture's INV6; #8127's description covers only +56 B. - The compare is about 29,500 changed lines because #8127's stack is in it; landing #8127 first leaves this change at its four files.
[Automated review]
| clock: 'fade' | 'lift', | ||
| ): void { | ||
| if (isPressed) { | ||
| segment.setAttribute(PRESSED_STATE, 'pressed'); |
There was a problem hiding this comment.
The dim is written here as data-pressed="pressed", outside themeProps(). The item's style now calls it internal, but a theme's pressed key still compiles to exactly [data-pressed="pressed"] (the theme compiler emits a rule for every state key), and pressedOpacityOf reads the result back from computed style, so a theme can still retune the dim while the target's states don't declare it. If it is internal, playing the dim to segmentedControl.pressedTitle.opacity with an attribute no theme key compiles to would make it so; if it is theme-facing, declaring pressed and reflecting it through themeProps() would match the theming-surface architecture's INV6.
| | FR4 | Selected and disabled remain reflected states on `segmented-control-item`, not standalone anatomy or targets. | Current source, docs, and tests | Verified current behavior; no target change | | ||
| | FR5 | Under a finger (`spec:AST-094` FR13), a change of selection slides the selected surface from the old segment to the new one as one surface, the radiogroup's `::before`, animating transform and opacity only; under a mouse the theme's colour transition applies. | `spec:AST-094` FR13; current source and tests | Proposed; pending owner review | | ||
| | FR6 | The slide runs on UISegmentedControl's measured spring (response 0.40 s, damping ratio 0.85), an internal constant no theme retunes; under Reduced Motion the old surface fades out in place as the new one fades in (`design:reduced-motion`). | `spec:AST-094` FR4, DEC-11; UISegmentedControl, iOS 26.2 simulator | Proposed; pending owner review | | ||
| | FR7 | Under a finger, `segmented-control-item` reflects a `pressed` state on the unselected segment a finger holds; its `opacity` is that segment's dim (0.2 by default), faded on the compositor. | Owner direction, 2026-10-25 | Proposed; pending owner review | |
There was a problem hiding this comment.
FR7 (with the Intent above and the changeset) still says segmented-control-item reflects a pressed state whose opacity is the dim, "0.2 by default", while SegmentedControlItem.tsx now calls the dim "internal, not a theming state". Rewording FR7 to the contract the code keeps would let an owner approve what actually ships.
| if (this.edges.length === 1) { | ||
| return ALL_NATIVE_TOUCH; | ||
| } | ||
| const horizontal = this.edges.some( | ||
| edge => edge !== 'top' && edge !== 'bottom', | ||
| ); | ||
| const vertical = this.edges.some( | ||
| edge => edge === 'top' || edge === 'bottom', | ||
| ); | ||
| return { | ||
| panX: !horizontal, | ||
| panY: !vertical, | ||
| pinchZoom: true, | ||
| doubleTapZoom: false, | ||
| }; | ||
| } |
There was a problem hiding this comment.
With two horizontal edges this reports panX: false, so the host writes touch-action: pan-y pinch-zoom on the whole element and sideways scrolling stops everywhere, not only in the 40.2 px bands. AST-094 FR18 on main (DEC-13) has a screen-edge pan claim pan-x only for a touch that lands in the edge band and leave the author's scrolling untouched outside it; keeping every native touch behaviour here and claiming per touch inside each band, as the one-edge path does, would match it (and the test at ScreenEdgePanGestureRecognizer.test.ts:61-68 would change with it).
Stacked on #8127 (SegmentedControl's touch press), and lands after it. #8127's branch is on a fork, so this PR's base has to be
main, and GitHub's file count includes #8127's stack. This PR's own change is 4 files, +370/−141: compare against #8127's head.User impact
None. A finger on an unselected segment dims its title and brings it back exactly as before: UIKit's 470 ms fade and 180 ms lift on CSS ease, the 200 ms Reduce Motion fade (or an instant change on a fine pointer), and the hand-back to the segment's own style at full opacity.
What changes
SegmentedControl's pressed-title dim moves into one internal function with one job,
utils/pressDim.ts, which sits beside the press machine. The press machine and SegmentedControl decide when a title dims;pressDimplays the fade:fadePressDim(element, to, {durationMs, curve}, {touchRelease})plays one opacity curve, from the opacity on screen toto, through the element's shared animator (animatorFor(element)). The fade runs on the compositor, shares the element's one animator, and takes the animator's Reduce Motion policy (design:reduced-motionDR2–DR4).stopPressDim(element)hands the element back to its own style at once.isPressDimReleased(element)says whether the dim has let go entirely: the animator shows 1 and holds no inline opacity or running opacity animation on the element.It takes plain inputs and names no component. It isn't exported from any entry point and isn't listed in core's
exports(architecture:public-component-apiINV12). This is the press-feedback move of the shared touch-module plan. No other component duplicates this dim today.Size (gzipped, minified esbuild bundle of
SegmentedControlfrompackages/core/dist,sideEffectshonoured)main| Package stylesheet (
astryx.css, gzip) | 33,181 B | 34,333 B | 34,333 B (byte-identical) |main→ #8127 is #8127's stack (SegmentedControl's touch press, its motion and the shared touch runtime it imports). This PR's own change is +0.05 KB of JavaScript and no CSS (the stylesheet is byte-identical to #8127's): the dim moves intopressDim, which adds the released check below. The touch-only CSS behindmain→ #8127 is #8127's and is justified there.Touch cost (
architecture:component-style-authoringINV6): SegmentedControl's touch code is imported statically, one copy per page shared by every touch component, under the shared-module rule proposed in #8464 (AST-080 DEC-5), so it isn't loaded lazily. This PR moves code into that shared runtime rather than adding to it: +0.05 KB.Desktop and phone are the same because SegmentedControl's touch code is imported statically. The dim moves rather than disappears, so the saving comes when the next owner reuses
fadePressDiminstead of carrying its own copy.Testing
pressDim.test.ts: one opacity curve through the animator, from the opacity on screen to the target on the clock and curve; the touch release is passed on;stopPressDimhands back at once.tsc, eslint,check:sync: clean.