Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsContextMenu (@astryxdesign/core) · View in Storybook
Component Size
Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.
|
| Component | JavaScript | CSS |
|---|---|---|
| core/ContextMenu | +39.7KB |
+12B |
| core/Markdown | +40.1KB |
+11B |
| core/Table | +39.9KB |
+13B |
| core/TableCell | +39.8KB |
+13B |
| core/TableHeaderCell | +39.8KB |
+19B |
Bundle Size Summary
Gzip figures measure each package's entry file, not the code an import pulls in.
| Package | Entry (ESM) | Entry gzip before | Entry gzip after | Delta |
|---|---|---|---|---|
| @astryxdesign/core | 4.9KB | 1.2KB | 1.2KB | 0B |
| Package stylesheet | Gzip before | Gzip after | Delta |
|---|---|---|---|
| @astryxdesign/core | 34.6KB | 34.6KB | 0B |
Accessibility Audit
Status: No accessibility violations detected.
Visual Regression
55 of 502 shot(s) changed.
A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.
| component | story | theme | mode | pixels |
|---|---|---|---|---|
| Selector | Indicator Space Bottom Sheet Narrow End RTL | neutral | light | 753,933 |
| Selector | Indicator Space Bottom Sheet Wide Start | neutral | light | 740,072 |
| RichTextEditor | Markdown parity: side by side | probe | dark | 558,840 |
| RichTextEditor | Markdown parity: side by side | probe | light | 540,756 |
| Chat | Full AI Chat | probe | light | 230,823 |
| Chat | Full AI Chat | probe | dark | 229,805 |
| AppShell | With Mobile Nav | probe | light | 217,010 |
| AppShell | With Mobile Nav | probe | dark | 216,960 |
| Collapsible | Dividers | probe | dark | 66,445 |
| Collapsible | Dividers | probe | light | 66,445 |
| AppShell | Playground | neutral | dark | 65,198 |
| AppShell | Playground | neutral | light | 63,788 |
| Chat | Default | neutral | dark | 36,304 |
| Chat | Default | neutral | light | 35,767 |
| Chat | Empty State | probe | light | 23,531 |
| Chat | Empty State | probe | dark | 22,622 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | dark | 15,378 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | light | 14,107 |
| Chat | With Attachments | probe | light | 13,406 |
| Chat | With Attachments | probe | dark | 11,232 |
and 35 more.
Generated by PR Enrichment workflow | Storybook | Sandbox | View full report
bf6b720 to
3acaca2
Compare
There was a problem hiding this comment.
Approval waits on an engineering owner's decision on the draft spec:AST-094 touch-presentation requirements (FR1–FR23, DEC-1, DEC-2) and on the proposed glass-morph entrance for touch-opened menus; an engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Because the draft also amends current design:user-states DR1 and DR3 and spec:AST-025 FR14, those records need updating on main before the amendments can count. Separately from that decision, please land the predecessor pull requests so this one carries only the morph commit, and fix the following:
- The morph removes
background-color,box-shadowandoverflowfrom the menu's inline style when it ends, erasing values a caller passed throughstyle(ContextMenuLift.tsxlines 231–233). - The content animation does not get the glass's start time, so it trails the glass by a frame (
ContextMenuLift.tsxline 463). - On a trigger without its own background, the original shows through the lifted copy and its label renders twice.
packages/core/src/gestures/README.mdpoints atutils/touchPresentation.ts, which does not exist.
[Automated review]
| morph.glassAnimator.dispose(); | ||
| morph.glass.remove(); | ||
| for (const property of ['background-color', 'box-shadow', 'overflow']) { | ||
| morph.menu.style.removeProperty(property); |
There was a problem hiding this comment.
This also removes a background-color, box-shadow or overflow the caller passed through style, and React does not re-apply an unchanged value. Record the previous values and priorities in morphMenu and restore them here.
| filter: `blur(${(blur * Math.max(1 - m.fade, 0)).toFixed(3)}px)`, | ||
| }; | ||
| }); | ||
| const animation = menu.animate(keyframes, {duration}); |
There was a problem hiding this comment.
The content animation gets no start time, so it begins when the browser next makes it ready; in Chromium that was 16.6 ms after the glass. Setting animation.startTime = startAt puts it on the glass's clock.
| ## Touch presentation | ||
|
|
||
| Whether a component presents itself the touch (UIKit) way is answered once, | ||
| in `../utils/touchPresentation.ts`: |
There was a problem hiding this comment.
../utils/touchPresentation.ts does not exist at this head, and neither do TOUCH_QUERY, COMPACT_QUERY or COMPACT_TOUCH_QUERY.
3acaca2 to
2ab77df
Compare
There was a problem hiding this comment.
Approval waits on an engineering owner's decision on the draft spec:AST-094 touch-presentation requirements (FR1–FR23, DEC-1, DEC-2) and on the proposed glass-morph entrance for touch-opened menus; an engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Because the draft also amends current design:user-states DR1/DR3 and spec:AST-025 FR14, those records need updating on main, or the amendments dropped. Whatever that decision, these fixes are needed:
- Land #8201, #8125, #8120 and their predecessors first, so this pull request carries only the morph commit instead of about 28,800 authored lines.
- Clear the two
@eslint-react/dom-no-flush-synclint errors inContextMenu.tsx. - Give
--color-background-backdropa name the current colour-token grammar (theme-tokens INV10) admits, or amend that grammar first, then rebase and regenerate the native Swift tokens; both checks fail in CI today. - In
ContextMenuLift.tsx, restore the caller's inlinebackground-color,box-shadowandoverflowafter the morph, start the content animation at the glass's start time, keep a transparent trigger from showing through its lifted copy, and take the lift's margins from the layer runtime's gutter.
[Automated review]
| // The menu's side depends on its height: place it below, show it, | ||
| // measure it, and move it above in the same task if it does not fit. | ||
| const below = menuPlacement(lifted.lifted, 0, lifted.viewport); | ||
| flushSync(() => { |
There was a problem hiding this comment.
@eslint-react/dom-no-flush-sync fails the lint check here and at line 784. If the synchronous commit is needed to measure the menu before choosing its side, record why in an eslint-disable-next-line @eslint-react/dom-no-flush-sync -- … comment, as Drawer/useDrawerDialogPresence.ts does; otherwise place the menu without flushSync.
| // default is the iOS dimming colour, black at 20 % in light mode and 48 % in | ||
| // dark mode. --color-overlay stays the scrim that keeps text and icons | ||
| // legible over media. | ||
| '--color-background-backdrop': |
There was a problem hiding this comment.
backdrop is not a role the current colour-token grammar admits (theme-tokens INV10: surfaces are surface, body, card, popover, muted, inverted), and main's grammar test fails on this name in CI (tokens.test.ts line 398). It needs an INV10 amendment first or a name the grammar has; once rebased, the native Tokens.swift also needs regenerating (pnpm -F @astryxdesign/native generate:swift-ios).
| ); | ||
| } | ||
|
|
||
| function viewportOf(doc: Document): LiftViewport { |
There was a problem hiding this comment.
viewportOf never fills safeTop or safeBottom, so the copy's clamp and menuPlacement see no safe-area or app-declared inset, and the 16 px and 20 px margins in contextMenu.lift act as this component's own gutter. The layer runtime owns that gutter (AST-059 FR1, layer-runtime INV11).
| }; | ||
| const viewport = viewportOf(trigger.ownerDocument); | ||
| const lifted = liftedBox(source, viewport); | ||
| const preview = clonePreview(trigger); |
There was a problem hiding this comment.
The source stays painted under the semi-transparent backdrop and this copy has no surface of its own, so a trigger without a background (the Default story's dashed area) shows through its lifted copy, border and label twice. Hide the source while it is lifted and restore it on drop, or give the copy an opaque surface.
|
Posted by Cindy's agent on her behalf. Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:
This is about where the behavior lives, not about the PR's intent. |
|
Agreed: one implementation.
|
2ab77df to
f449bb2
Compare
|
Converged in f449bb2: the morph's reduced path is the animator's ( |
|
Addressed in f449bb2:
|
There was a problem hiding this comment.
Approval waits on an engineering owner's decision on the draft spec:AST-094 touch-presentation requirements (FR1–FR23, DEC-1, DEC-2), the proposed glass-morph entrance for touch-opened menus, and this head's change to the architecture:theme-tokens INV10 colour grammar; an engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Because the draft also amends design:user-states DR1/DR3 and spec:AST-025 FR14, those records need updating on main, or the amendments dropped. Whatever that decision, these fixes are needed:
- Land #8201 and the rest of the stack first, so this pull request carries only the morph commit (about 1,100 authored lines instead of about 35,900).
- Take the lift's clamp and the menu's side from the layer runtime's gutter and declared inset (
spec:AST-059FR1), which also clears CI's gutter guard, and clear the press-coverage guard that fails onContextMenu.tsx. - Drop the
platformadaptation condition or amendspec:AST-012FR2 first, align draft FR8 with the 200 ms opacity-only rule indesign:reduced-motion, remove the citation ofspec:AST-090, which does not exist, and add the writing rubric results to the description.
[Automated review]
| probe.setAttribute('data-astryx-safe-area-probe', ''); | ||
| probe.style.cssText = | ||
| 'position:fixed;top:0;left:0;visibility:hidden;pointer-events:none;' + | ||
| 'padding-top:env(safe-area-inset-top,0px);' + |
There was a problem hiding this comment.
This probe gives the lift its own gutter: the copy's clamp and menuPlacement use 16 px and 20 px plus the safe area, and never the layer runtime's gutter or LayerProvider's declared inset. spec:AST-059 FR1 keeps the gutter in the layer runtime ("no component or family defines its own"), and CI's layerViewportInset.test.ts guard fails on this file. Read the runtime's gutter and inset instead.
| // grows it while that machine believes the press, springs back when it | ||
| // ends with no menu, and stays grown while the menu it opened is up. Not | ||
| // shown on an inline trigger. | ||
| const pressable = usePressFeedback(); |
There was a problem hiding this comment.
The trigger now carries the press marker, but ContextMenu paints no press arm (the growth plays on the animator), so pressableCoverage.test.ts ("marks nothing that does not paint a press") fails in CI. Paint the press through a [data-astryx-press arm, or change the guard with its owners in the same change.
| * is everything else. Pair `ios` with `pointer: 'coarse'` for a touch | ||
| * profile, such as touch motion tuned per platform. | ||
| */ | ||
| platform?: 'ios' | 'other'; |
There was a problem hiding this comment.
spec:AST-012 FR2 keeps adaptation conditions to width, height, pointer, contrast and motion, and says a new field needs an explicit addition to that closed vocabulary first. Drop platform here, or amend FR2 on main before adding it.
| on: a transition that travels becomes a cross-fade carrying the same change | ||
| of state. An entrance lands in place and fades in, an exit fades out in | ||
| place, and any other move fades through. A navigation push or pop | ||
| cross-fades over 0.5 s on the ease-in-out curve; a sheet, a full-screen |
There was a problem hiding this comment.
Under reduced motion, design:reduced-motion DR2 allows only an opacity change that finishes within 200 ms (at once on a fine pointer), so a 0.5 s navigation fade and spring-timed menus contradict it, and the draft declares no amendment of that record. The code already follows DR2; restate FR8 to defer to it.
| follows. | ||
| - **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the | ||
| exit its UIKit counterpart plays. **Proposed; pending owner review:** this | ||
| departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10 |
There was a problem hiding this comment.
spec:AST-090 exists neither on main nor in this pull request; drop the citation or name the record that holds these claims.
|
Posted by Cindy's agent on her behalf.
Superseded (Cindy, Oct 25): there's no glass hold. This PR adds no glass material, and the touch open is governed by |
f449bb2 to
7105e4b
Compare
… a mouse press At the document, an event from inside an open shadow root is retargeted to the shadow host, and so is a touch's target, so the host looked for participants from the shadow host and found none inside the root: the browser then sent its own mousedown and click. Both cases, a finger and a mouse, attach a tap to an element in the shadow root. Fail until the host resolves targets through the composed path.
…composed path Inside an open shadow root the document's listeners see the shadow host, in event.target and in a touch's target alike, so a recognizer on an element in the root never got its finger, and nothing claimed it. landedOn takes the composed path's first element for the event's own target and for a touch whose target is the event's (another finger keeps its own): a touch's start, a pointer's press and its pairing, the last touch a scroll reads, a cancelled pointer's touch and hover.
…esentation has run Under the touch model the menu opens with a presentation, and the layer's input hold (spec:AST-094 FR35) drops every tap until that presentation completes, as UIKit drops a touch while one runs. The check tapped outside two frames after the lift, so its tap landed on the hold's shield and the menu stayed open. It now taps once the page's animations have finished, and its assertions are unchanged.
There was a problem hiding this comment.
On hold for an engineering owner's decision: this head writes three ContextMenu springs into spec:AST-094 FR4 (and grows the menu on one where FR31 names springs.menuOpen), adds architecture:layer-runtime INV13, proposes draft ContextMenu DEC-2 and exports useLongPress stages, none of which the current specs state. An engineering owner records that decision by commenting /approve-spec followed by the full head commit SHA on this pull request, and the author may do so as an engineering owner. Whatever the decision, these still need changes:
- Rebase onto the landed predecessors so only the 8-file growth remains.
- Give a lifted trigger without its own background a surface: on the Default story its text drops to about 1.84:1 over the tint.
- Fire
onOpenChange(true)from the open handler rather than a layout effect, make the press feedback's spring a motion role, and drop thespec:AST-057motion-role sentences and the unused fine-pointer press tracker.
[Automated review]
…tation has run Under the touch model the menu opens with a presentation, and the layer's input hold (spec:AST-094 FR35) drops every tap until that presentation completes, as UIKit drops a touch while one runs. The check tapped the shadow host two frames after the lift, so its tap landed on the hold's shield and the menu stayed open. It now taps once the page's animations have finished, and its assertions are unchanged.
… the presented start A zoom stage already drives its photo with an animator (pinch, pan, settle), and the zoom's grow is a rect morph on the same element: two animators would overwrite each other's channels. createRectMorph takes that animator in `animator` and plays on it; dispose leaves it to its owner. The morph's presentedRect then reads the stage's own transform, so a dismissal from a zoomed photo starts from the shown rect. A present or a dismissal also takes the animator's `start`: with 'presented', the element holds its first values until two frames have presented it, so a surface that appears late (a dialog's first frame in WebKit) plays the whole curve. Both new cases fail before this change and pass after.
There was a problem hiding this comment.
On hold for an engineering owner's decision: this head writes three ContextMenu springs into spec:AST-094 FR4 (and grows the menu on springs.contextMenuMorph where FR31 names springs.menuOpen), adds architecture:layer-runtime INV13, proposes draft ContextMenu DEC-2 and exports useLongPress stages, none of which the current specs state. An engineering owner records that decision by commenting /approve-spec followed by the full head commit SHA on this pull request, and the author may do so as an engineering owner. Whatever the decision, these still need changes:
- Rebase onto the landed predecessors so only the 8-file growth remains; today the branch carries 37,880 authored lines.
- Give a lifted trigger without its own background a surface: on the Default story its label drops from 4.83:1 to 1.22:1 over the tint.
- Fire
onOpenChange(true)from the open handler rather than a layout effect, make the press feedback's spring a motion role, drop the unusedperEventPointerpress tracker and thespec:AST-057motion-role sentences, and add real-device and busy-main-thread evidence for the growth and an INV6 justification for ContextMenu's own touch code.
[Automated review]
| clear: springs.contextMenuDismiss, | ||
| shadowIn: springs.contextMenuMorph, | ||
| shadowOut: springs.contextMenuMorph, | ||
| surfaceIn: springs.contextMenuMorph, |
There was a problem hiding this comment.
The growth plays springs.contextMenuMorph, but current spec:AST-094 FR31 names springs.menuOpen for a menu. Whether the three ContextMenu springs join FR4 is the open owner decision; if they do not, this moves to springs.menuOpen.
| 'padding-bottom', | ||
| 'padding-left', | ||
| 'color', | ||
| 'background-color', |
There was a problem hiding this comment.
Keeping the trigger's computed background-color leaves a transparent trigger transparent in the top layer: on the Default story its label drops from 4.83:1 on the page to 1.22:1 over the tint (spec:AST-020 FR10, 1.4.3). When this is transparent, paint the lifted trigger on the surface it stood on.
| } | ||
| if (!pending.isShown) { | ||
| pending.isShown = true; | ||
| openLifted(pending.lifted); |
There was a problem hiding this comment.
openLifted runs layer.show(), whose onShow calls onOpenChange(true) synchronously, so on a lifted open the consumer callback fires from this layout effect instead of from openFromFinger (architecture:react-component-runtime INV2). Notify from the handler and keep this effect for measuring and showing.
| * ContextMenu's touch code calls it before its first motion (it is | ||
| * idempotent). | ||
| */ | ||
| export function registerContextMenuTable(): void { |
There was a problem hiding this comment.
The press feedback plays press.feedbackSpring (contextMenuPressFeedback.ts line 185), and spec:AST-094 FR4 says every spring a touch presentation plays MUST be a motion role named for its use; registering it as no role leaves it outside FR4.
| * control event, nothing kept), and a mouse or pen press is not taken. | ||
| * @default false | ||
| */ | ||
| perEventPointer?: boolean; |
There was a problem hiding this comment.
This option attaches a press tracker's touchstart and pointerdown listeners where the primary pointer is fine, which spec:AST-094 FR19 rules out ("no touch listener is registered"), and it cites an amendment the current record lacks. No component passes it; please remove it until that amendment is approved.
| }, | ||
| ); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
This new Effect hands the parent a handle through onHandleChange and names no external system (architecture:react-component-runtime INV3). Pass the handle through a ref, or name what it synchronizes.
| - **Gesture tuning.** Axis-lock distance, commit ratio, fling velocity, | ||
| resistance past the panel: behavior constants in source, not public API and | ||
| not design tokens (the same ruling the touch press model received for its | ||
| clocks). The row's slide and spring motion plays on theme motion roles |
There was a problem hiding this comment.
The row still settles on SWIPE_SETTLE_MS = 200 (Item/useSwipeAction.ts line 86), and spec:AST-094 FR4 and DEC-11 keep touch roles internal, so a theme cannot set this motion. Please drop this sentence and its twin at line 225.
…only moves Under Reduce Motion on a touch device, the morph treats a path with no opacity of its own as an entrance on a present and an exit on a dismissal, so the cross-fade lands it at rest (design:reduced-motion). An element that stays visible and only moves, such as a sub-menu's parent card, then cut out and faded in on present, and faded out and popped back on dismissal, with no way for its caller to say so: the animator's crossfadeAs names only an entrance or an exit. A present or a dismissal now takes crossfadeAs: 'move', which passes no cross-fade direction, so the animator plays its own dip: out, the travel lands, back in. The new case does not compile before this change (the option had no value for a move; an untyped 'move' reached the animator only by accident) and passes after.
…t morph's source copy AST-057's record takes main's text throughout (meta#8646 drops two-finger trackpad swipes from the non-goals); this branch keeps only its own change there: the row's slide and spring motion plays on theme motion roles (spec:AST-094 FR4), so its durations leave the behavior constants.
There was a problem hiding this comment.
On hold for an engineering owner's decision: this head writes three ContextMenu springs into spec:AST-094 FR4 (and grows the menu on springs.contextMenuMorph where FR31 names springs.menuOpen), adds architecture:layer-runtime INV13, proposes draft ContextMenu DEC-2 and exports useLongPress stages, none of which the current specs state. An engineering owner records that decision by commenting /approve-spec followed by the full head commit SHA on this pull request, and the author may do so as an engineering owner. Whatever the decision, these still need changes:
- Rebase onto the landed predecessors so only the 8-file growth remains; today the branch carries 37,964 authored lines.
- Make the gesture host attach for every pointer, as
spec:AST-094FR19, FR43 and DEC-21 now require; it still attaches only under a coarse primary pointer. - Give a lifted trigger without its own background a surface (its label falls to 1.83:1 over the tint), fire
onOpenChange(true)from the open handler rather than a layout effect, make the press feedback's spring a motion role, and drop thespec:AST-057motion-role sentences and the unused options. - Add real-device evidence and Chromium and WebKit busy-main-thread proofs for the growth, and the INV6 size split.
[Automated review]
| recognizer.view = element; | ||
| if (options.perEventPointer === true) { | ||
| this.perEventRecognizers.add(recognizer); | ||
| } else if (!this.isCoarse) { |
There was a problem hiding this comment.
Current spec:AST-094 FR19 now reads "Recognizers attach for every pointer, with FR43's exceptions", FR43 "Gestures attach for every pointer, a mouse, a pen, and a finger, not only a coarse primary pointer", and DEC-21 rejects keeping gestures touch-only. Here an attach under a fine primary pointer installs nothing and only warns, so a held finger on a touchscreen laptop falls back to the released long press with no timeline or lift. Attach for every pointer with FR43's exceptions (and FR24's fine-pointer budget).
| 'padding-bottom', | ||
| 'padding-left', | ||
| 'color', | ||
| 'background-color', |
There was a problem hiding this comment.
Keeping the trigger's computed background-color leaves a transparent trigger transparent in the top layer: on the Default story the #6b7280 label goes from 4.83:1 on a white page to 1.83:1 over the core light --color-overlay (1.69:1 on --color-background-body), under the 4.5:1 of spec:AST-020 FR10's 1.4.3 row. When the kept background is transparent, paint the lifted trigger on the surface it stood on.
| } | ||
| if (!pending.isShown) { | ||
| pending.isShown = true; | ||
| openLifted(pending.lifted); |
There was a problem hiding this comment.
openLifted runs layer.show(), whose onShow calls onOpenChange?.(true) synchronously (Layer/useLayer.tsx lines 1310–1348, this file's lines 492–495), so on a lifted open the consumer callback fires from this layout effect. architecture:react-component-runtime INV2 has it fire from the handler that caused the change; notify from openFromFinger and keep the effect for measuring, placing and showing.
| settleBack( | ||
| animator.animate( | ||
| {scale: 1}, | ||
| {spring: feedbackSpring}, |
There was a problem hiding this comment.
feedbackSpring is a table literal: registerContextMenuTable registers the table with no roles ("None is a motion role", motion/uikit/contextMenu.ts lines 246–260). Current spec:AST-094 FR4 says every spring a touch presentation plays MUST be a motion role named for its use and a component MUST NOT play a literal. Register it as a role through the component's record (DEC-5).
| }, | ||
| ); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
This new Effect hands the parent a handle through onHandleChange and names no external system, as architecture:react-component-runtime INV3 asks of a new Effect. Pass the handle through a ref (useImperativeHandle) or name what it synchronizes.
| clear: springs.contextMenuDismiss, | ||
| shadowIn: springs.contextMenuMorph, | ||
| shadowOut: springs.contextMenuMorph, | ||
| surfaceIn: springs.contextMenuMorph, |
There was a problem hiding this comment.
The growth plays springs.contextMenuMorph, while current spec:AST-094 FR31 names springs.menuOpen for a menu. Whether the three ContextMenu springs join FR4 (with FR31 amended) or the growth moves to springs.menuOpen is the open engineering-owner decision.
| - **Gesture tuning.** Axis-lock distance, commit ratio, fling velocity, | ||
| resistance past the panel: behavior constants in source, not public API and | ||
| not design tokens (the same ruling the touch press model received for its | ||
| clocks). The row's slide and spring motion plays on theme motion roles |
There was a problem hiding this comment.
No row code changes here: the row still settles on SWIPE_SETTLE_MS = 200 (Item/useSwipeAction.ts lines 86, 368), and current spec:AST-094 FR4 and DEC-11 keep touch roles internal, so "a theme cannot override them yet". Drop this sentence and the one at line 225, or move them to the pull request that moves the row onto roles.
| * control event, nothing kept), and a mouse or pen press is not taken. | ||
| * @default false | ||
| */ | ||
| perEventPointer?: boolean; |
There was a problem hiding this comment.
No component passes perEventPointer, and the host justifies it as "an FR19 exception for press feedback pending in AST-094's amendment" (GestureHost.ts lines 818–819), which current AST-094 does not contain. Move it to the pull request that uses it.
Depends on #8201 (with #8485's lift module). Through #8201, it carries the static-import offer on #8125 (16dc55e) as a dependency; if #8125 lands differently, this stack follows her decision. This PR's own change: vjeux/astryx@a0e1620...94744dc
User need
Part of the UIKit-parity program. On iOS 26 a context menu opened by a held finger grows out of the finger. With #8201 alone, Astryx's menu fades in beside the lifted trigger, so it reads as a separate popup.
Problem and solution fit
Under a finger on every touch device (
spec:AST-094FR31), inside #8201's lift, the menu starts 62 pt wide at the press point and grows into its frame on UIKit's morph spring (0.3 s), fading in on the same curve. It is the menu itself, moved by core's rect morph with transform and opacity only (FR5). A tap mid-growth is swallowed (FR35), Escape closes it, and Reduce Motion fades it in where it is.Current specification
component:ContextMenu(draft);spec:AST-094FR5, FR31 and FR35;design:reduced-motion. Owner decision, pending: a touch-opened menu grows out of the finger, as UIKit's.Public delta and caller burden
No API change. ContextMenu JS goes from 79,600 B on main to 120,303 B gzip with this stack. Public-API gaps (
spec:AST-107FR7): the lift, its springs and UIKit tables, the input hold.Design and alternatives
UIKit's glass blob, its content settling out of a blur, needs an extra element and an animated
clip-path, which runs on Chromium's main thread; FR5 rules both out. So the menu itself grows, through the lift module'spresentSurface(menu, press), the grow every lifting surface shares (architecture:layer-runtimeINV15). Still a guess: the start size for other menu heights (M-CM10).Evidence
iOS 26.2 simulator (time since touch shown; dots are fingers), then Android 15:
Scope
Testing
In the story "Touch / long press", on a phone or the iOS simulator:
pnpm test:interaction ContextMenuLift ContextMenuLongPresschecks the same in Chromium and WebKit, including that only transform and opacity move. Unrelated failures: #8638, #8671. GitHub shows a conflict with main inContextMenu.spec.md: it is resolved in #8201 and reaches this PR at its next push.