Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
ccbee43 to
0eab992
Compare
PR Analysis Report
Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
This pull request is on hold for an owner decision: no spec record yet says that a Popover presented under a finger should ignore every tap while it animates in, since component:Popover keeps native light dismiss enabled and the draft AST-094 has no rule on input during a transition. An engineering owner can settle it by writing that rule into the record, including whether page pinch-zoom stays available as AST-094 FR10 and FR18 propose, and commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, please land the dependency stack first so this diff is only the hold's own commit (it carries about 33,500 authored lines today), update the useMenuPress record's 500 ms long-press row to the new 150 ms default, and add a WebKit run and a focus check for the held tap.
[Automated review]
| border: '0', | ||
| background: 'transparent', | ||
| overflow: 'hidden', | ||
| 'touch-action': 'none', |
There was a problem hiding this comment.
touch-action: none also blocks page pinch-zoom for the length of the present spring. The draft AST-094 FR10 and FR18 keep pinch-zoom unless a pinch or rotation recognizer is attached; pinch-zoom here would still let no pan start. Worth settling with the owner decision on the hold.
There was a problem hiding this comment.
Done in 943d1b2: the hold's touch-action is now pinch-zoom. No pan starts on it, and a pinch still zooms the page, as draft AST-094 FR10 and FR18 keep it; the unit case asserts it.
| @@ -140,6 +140,7 @@ Consumer migration instructions belong in consumer docs and release notes. | |||
| | stray-click window | 400 ms, or the next `pointerdown` | how long after a tracked release the browser's click is still swallowed | `MENU_PRESS_STRAY_CLICK_MS` | `module:DropdownMenu/useMenuPress` | stable | | |||
| | settle time | 300 ms | proposed: how long an opening press must last before its release may act | `MENU_PRESS_SETTLE_MS` | `module:DropdownMenu/useMenuPress` | proposed | | |||
| | long-press delay | 500 ms | proposed: how long a finger rests on the trigger before the menu opens under it | `longPressDelayMs` | `module:DropdownMenu/useMenuPress` | proposed | | |||
There was a problem hiding this comment.
This row still says 500 ms, but useMenuPress.ts now defaults to menu.holdOpenMs (150 ms) and the hook doc says 150. Update the row with its UIKit source, or keep 500 ms in code until OQ1 is ruled on.
There was a problem hiding this comment.
This record comes from the DropdownMenu press change below this one in the stack, and that change is being brought in line with the code (the 150 ms hold-open and continueGesture). Nothing to change in this pull request.
0eab992 to
882e965
Compare
There was a problem hiding this comment.
This pull request is on hold for an owner decision: no spec record says that a Popover presented under a finger should ignore every touch while it animates in, since component:Popover keeps native light dismiss enabled and the draft AST-094 has no rule on input during a transition (its FR10 and FR18 also keep page pinch-zoom, which the hold's touch-action: none blocks). An engineering owner can settle it by writing that rule into the record and commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, please land the dependency stack first so this diff is only the hold's own commit (it carries about 34,300 authored lines today), update the useMenuPress record's 500 ms long-press row to the new 150 ms default, and add a focus check after the held tap in Chromium and in the iOS Safari run.
[Automated review]
| expect(log.closed).toEqual([]); | ||
| expect(await outsideClicks()).toBe(0); | ||
| // It was the hold that took the tap, inside the popover's own subtree. | ||
| expect(log.held).toEqual([true]); |
There was a problem hiding this comment.
Nothing here checks where focus is after this held tap. The hold is a non-focusable element outside the painted surface, and useFocusTrap leaves focus a pointer moved where it lands; in a fixture that reproduces the hold's DOM, a Chromium touch tap on it moved focus from the Popover's switch to the page body while the Popover stayed open. Assert that document.activeElement is still the Popover's first content control (component:Popover VG1 and AR3), and record the same check in the iOS Safari run.
There was a problem hiding this comment.
The tap case already asserts that the switch the popover focused keeps focus after the held tap. That assertion failed before the hold cancelled the compatibility mousedown and the touch's end. In 943d1b2 the pan case asserts it too. The iOS 26.2 Safari run logged focus on the switch after the held tap.
882e965 to
ded6a6a
Compare
ded6a6a to
943d1b2
Compare
There was a problem hiding this comment.
This pull request is on hold for an owner decision: no spec record says that a Popover presented under a finger should ignore taps and pans while it animates in, since component:Popover keeps native light dismiss enabled and the draft AST-094 has no rule on input during a transition. An engineering owner can settle it by writing that rule into the record and commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, please land the dependency stack first so this diff is only the hold's own commit (it carries about 34,700 authored lines today), and conform the stacked touch presentations to two records that landed on main since: design:reduced-motion DR2 caps a Reduce Motion touch cross-fade at 200 ms (the Popover's fade, and the hold with it, run about 0.53 s), and spec:AST-012 FR9 requires the touch resolver to use the theme's compact definition instead of its own 744 px edge.
[Automated review]
| expect(shown.size).toBe(0); | ||
| }); | ||
|
|
||
| it('holds under Reduce Motion too: the fade runs on the same spring', () => { |
There was a problem hiding this comment.
Under Reduce Motion this case keeps the hold for the whole cross-fade on springs.popoverPresent, which settles in about 532 ms by the animator's own rule. design:reduced-motion DR2, current on main, requires a touch cross-fade to change opacity only and finish in 200 ms or less, so the fade, and the hold that lives for it, should end within 200 ms.
| export const TOUCH_QUERY = '(pointer: coarse)'; | ||
|
|
||
| /** The viewport is compact: layout behaviours. */ | ||
| export const COMPACT_QUERY = '(width < 744px), (height < 500px)'; |
There was a problem hiding this comment.
spec:AST-012 FR9, current on main, defines compact once (width below the theme's md point, 768, or height below 500) and says the web touch presentation resolver uses that definition and keeps no size numbers of its own. With 744px here, coarse-pointer screens 744 to 767 px wide open the anchored popover where the bottom sheet is required; building the query from the theme's exported compact definition fixes it.
943d1b2 to
98898c3
Compare
|
Updated to 803ff4e. It sits on #9823's new head (b0a37a1) and on #8342's rebase onto |
There was a problem hiding this comment.
Changes requested for the stack. A pull request lands only when it and every pull request below it are approved.
#8186: approve. The layer runtime's exit copy and its record text carry an engineering owner's approval at its head.
#8187: changes requested.
readThemeTiming(packages/core/src/motion/themeTiming.ts:62-76) keeps a second copy of SegmentedControl's timed curve and its finite-difference velocity (timedCurve,packages/core/src/SegmentedControl/useSegmentedSelectionMotion.ts:388-406): givethemeTiming.tsone timed-progress builder, build SegmentedControl's lens curves on it throughmapProgress, and deletetimedCurve.- Waiting on an engineering owner: the growth and shrink play the theme's duration and easing tokens (
popoverSurfaceFrame.ts:28-41), which currentspec:AST-094FR4 and FR31 do not allow until #9706's FR45 is onmain, and Popover keeps its own touch-presentation latch beside the layer runtime's presentation hold (architecture:layer-runtimeINV14); an engineering owner records the decision by commenting/approve-spec <full head SHA>on #8187 (the author may, as an engineering owner), or the stack rebases onto #9706 once it lands.
#9823: changes requested. Its proofs assert #8187's token timing, so it follows #8187's decision.
#8223: changes requested.
.changeset/popover-touch-input-hold.mdsays “A mouse is unchanged.”, but on a coarse-primary device the first press of a mouse that has not moved since a finger's tap lands on the hold's shield and does nothing (packages/core/src/Layer/inputHold.ts:292-300): write “A fine primary pointer (a mouse) is unchanged; after a finger, a mouse that has not moved yet passes once it moves.”- Waiting on an engineering owner: the
component:MobileNavFR5 sentence and thespec:AST-094Current-state paragraph carried from #8342 have no approval at #8342's rebased head or at this head; an engineering owner records it by commenting/approve-spec <full head SHA>on this pull request (the author may, as an engineering owner), or #8342 lands first.
The input hold itself does what spec:AST-094 FR35, component:Popover FR9 and spec:AST-017 FR60 ask, and the ci.yml conflict with main is resolved.
Whole stack: fails. The two defects above ship in the cumulative diff. CI has not run on this head: every CI job was skipped while the stack is a draft.
[Automated review]
| return { | ||
| duration, | ||
| at, | ||
| velocity: t => { |
There was a problem hiding this comment.
This central difference is the same as timedCurve's in SegmentedControl/useSegmentedSelectionMotion.ts:396-405 (same step, same clamp). Export one timed-progress builder from here, build SegmentedControl's lens curves on it with mapProgress and mapRange([0, 1], [from, to]), and delete timedCurve, so a fix to the step or the end reaches both.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] Under a finger, a tap that lands while a Popover grows out of its trigger is ignored. The popover stays open and keeps its focus, and the tap reaches nothing under it. A pan that starts then scrolls nothing, and a pinch still zooms the page. Once the popover is in, a tap outside dismisses it as before. A mouse is unchanged. |
There was a problem hiding this comment.
“A mouse is unchanged.” is broader than the change: on a coarse-primary device, the first press of a mouse that has not moved since a finger's tap lands on the hold's shield and does nothing (Layer/inputHold.ts:292-300). Suggest: “A fine primary pointer (a mouse) is unchanged; after a finger, a mouse that has not moved yet passes once it moves.”
|
Updated to 919caa1, on #9823's new head (ae680ec), answering the 05:33Z stack review:
The gate is green at this head, and the description has the numbers. |
There was a problem hiding this comment.
Changes requested for the stack; a pull request lands only when it and every pull request below it are approved.
#8186: changes requested.
- Regression:
packages/core/src/Layer/__tests__/layerExitCopy.interaction.browser.spec.ts:22imports@playwright/test, whichmain's ESLint config now rejects in browser specs (no-restricted-imports), so the required Lint job fails on merge; importexpect,testand the types from@astryxdesign/a11y-spec/playwrightinstead.
#8187: changes requested.
- Waiting on #9706: the growth and shrink play the theme's duration and easing tokens (
packages/core/src/Popover/popoverSurfaceFrame.ts:28-41), which currentspec:AST-094FR4 and FR31 do not allow, and Popover keeps its own touch-presentation latch beside the layer runtime's presentation hold (architecture:layer-runtimeINV14); as planned, rebase onto #9706's FR45 once it lands, and an engineering owner then records the decision by commenting/approve-spec <full head SHA>on #8187 (the author may, as an engineering owner).
#9823: changes requested.
- Regression:
packages/core/src/Popover/__tests__/PopoverTouchMotion.interaction.browser.spec.ts:21imports@playwright/test, whichmain's lint now rejects; import from@astryxdesign/a11y-spec/playwright. - Its proofs assert #8187's token timing, so it follows #8187's decision.
#8223: changes requested.
- Regression: the four browser specs merged in from #8342 (
DialogTouchEntrance.interaction.browser.spec.ts:29,DrawerTouchEntrance.interaction.browser.spec.ts:28,inputHold.interaction.browser.spec.ts:31andMobileNavTouchPresentation.a11y.chromium.spec.ts:28) import@playwright/test, whichmain's lint now rejects; merge #8342's current head, which already imports from@astryxdesign/a11y-spec/playwright.
Whole stack: fails. The input hold itself does what spec:AST-094 FR35, component:Popover FR9 and spec:AST-017 FR60 ask, but the lint regression fails every merge with main and #8187 waits on #9706. CI has not run on these heads: every job was skipped while the stack is a draft.
[Automated review]
| */ | ||
|
|
||
| import {fileURLToPath} from 'node:url'; | ||
| import {expect, test, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Regression (#8186): main's ESLint config now rejects this import in browser specs (no-restricted-imports: "Import from '@astryxdesign/a11y-spec/playwright' instead"), so the required Lint job fails on merge. Import expect, test and the types from @astryxdesign/a11y-spec/playwright.
| * unit-tested in popoverSurfaceMotion.test.ts | ||
| */ | ||
|
|
||
| import {expect, test, type Locator, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Regression (#9823): main's ESLint config now rejects this import in browser specs (no-restricted-imports: "Import from '@astryxdesign/a11y-spec/playwright' instead"), so the required Lint job fails on merge. Import expect, test and the types from @astryxdesign/a11y-spec/playwright.
| * and finishing the entrance ends it at once. | ||
| */ | ||
|
|
||
| import {expect, test, type Locator, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Regression (from #8342): main's ESLint config now rejects this import in browser specs (no-restricted-imports: "Import from '@astryxdesign/a11y-spec/playwright' instead"), so the required Lint job fails on merge. Import expect, test and the types from @astryxdesign/a11y-spec/playwright. #8342's current head already does.
| * and finishing the slide-in ends it at once. | ||
| */ | ||
|
|
||
| import {expect, test, type Locator, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Regression (from #8342): main's ESLint config now rejects this import in browser specs (no-restricted-imports: "Import from '@astryxdesign/a11y-spec/playwright' instead"), so the required Lint job fails on merge. Import expect, test and the types from @astryxdesign/a11y-spec/playwright. #8342's current head already does.
| import path from 'node:path'; | ||
| import {fileURLToPath} from 'node:url'; | ||
| import {build} from 'esbuild'; | ||
| import {expect, test, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Regression (from #8342): main's ESLint config now rejects this import in browser specs (no-restricted-imports: "Import from '@astryxdesign/a11y-spec/playwright' instead"), so the required Lint job fails on merge. Import expect, test and the types from @astryxdesign/a11y-spec/playwright. #8342's current head already does.
| * timing either way. | ||
| */ | ||
|
|
||
| import {expect, test, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Regression (from #8342): main's ESLint config now rejects this import in browser specs (no-restricted-imports: "Import from '@astryxdesign/a11y-spec/playwright' instead"), so the required Lint job fails on merge. Import expect, test and the types from @astryxdesign/a11y-spec/playwright. #8342's current head already does.
…s close placeLayerExitCopy puts a static, inert copy of a closing layer where the layer was drawn, in the top layer, for a touch presentation's close to play on: the browser hides a popover the moment it closes. The copy keeps the layer's box, transform, presented look, overflow and scroll positions; has no ids, anchor names, form names or form owner; is inert and hidden from assistive technology; and its descendants' keyframe animations and transitions do not start again. A refused show during the closing toggle waits, hidden, until the toggle is over. A layer inside other layers gets its copy after the outermost one, with what it inherited there (its color scheme included), so a close that takes the layers around it too still draws the copy. That copy is shown again above the copy the same close then places for a layer around it. A layer has one copy. The layer-runtime record states it, and that the presentation plays its exit on the copy (AST-094 FR7). Core's shared inertCopy now takes every copied native control and form-associated custom element out of any form with form="", as its documentation says, so the swap's copies join no form either. It also sets anchor-name: none on each copied element that has an anchor name, so no live anchor gets a second element.
…av (spec:AST-094 FR35)
Layer/inputHold holds a layer's pointer input while its presentation
enters or leaves. holdInput(layer, {until, scope}) raises a shield:
'viewport' over the whole viewport while a modal layer enters; 'surface'
over the layer's own box while a non-modal one enters, the page around it
live; 'layer' over the viewport, its content inert, while a layer leaves.
It decides by each event's pointer (a finger's or a pen's press is held to
its end; a mouse passes once it moves with no button down, or from the
start when the page last heard a mouse, DEC-16), never cancels a touch's
start, and cancels a one-finger move so a pinch still zooms (FR10). It
hears input through Core's shared input stream before every other
subscriber, and ends the dispatch for a held event, so press feedback and the
gesture host never hear it; the stream gains first subscribers and records
the type of the latest pointer event it delivers (getLastPointerType). The gesture
host asks the hold for each touch (holdsTouch), so the hold imports nothing
from gestures/ and a component in the root entry may use it statically
(architecture:react-component-runtime INV13).
Its first users hold only under a coarse primary pointer, a touch
presentation (FR13): Dialog, Drawer and MobileNav hold a finger's tap or
drag while they enter (holdEntranceInput, which reads the entrance's own
animations; MobileNav's slide-in is capped at 550 ms), a non-modal Drawer
only on its panel. Under a fine primary pointer nothing is held and
nothing listens (FR24). Where the primary pointer is fine and the screen
also takes touch, a finger is not held yet; AST-094's current-state impact
and Drawer's record name that gap. AST-094's current-state impact also
names the first press of a still mouse after a finger, which lands on an
entering presentation's shield. AlertDialog renders Dialog, so its
entrance is held too: its record says FR7 ships in part, the leaving
alert's inert content waiting for FR6's dismissal.
Every browser proof lands its input at an exact point (an entrance held
where it is, the page's clock, or an exact step of a drag) and plays once.
The WebKit interaction lane's scope includes Layer/inputHold.ts: the
gesture host imports it, so the gesture specs' fixture bundles it.
MobileNav's record states what the rest of the slide-out after the close
does: it is inert, taking no input and hidden from assistive technology
(FR5). A browser case proves both against the open drawer.
From meta#8342 and its per-event offer, meta#8630 (Dialog and Drawer) and meta#8374
(MobileNav).
Co-authored-by: Cindy Zhang <cindyxz@meta.com>
…s back into it Under a coarse primary pointer, Popover grows out of its trigger on the theme's entrance duration and easing and shrinks back into it on the theme's exit duration and easing, on the layer runtime's exit copy. A fine primary pointer keeps the layer's own entrance.
…rink Real-browser proofs, in Chromium and WebKit, that under a finger Popover grows out of its trigger on the theme's entrance tokens and shrinks back on its exit tokens, keeps advancing through a blocked main thread, does no Layout or Paint per frame, and cross-fades under Reduce Motion, with storyboards of both.
…d, as on iOS While the popover grows out of its trigger under a coarse primary pointer, the layer runtime's input hold takes every touch. A tap outside neither dismisses it nor reaches what is under it, a pan scrolls nothing, and a pinch still zooms the page.
|
Updated to e0a9bb5, on #9823's rebased head (904c29f) and #8342's rebase onto |
…s close placeLayerExitCopy puts a static, inert copy of a closing layer where the layer was drawn, in the top layer, for a touch presentation's close to play on: the browser hides a popover the moment it closes. The copy keeps the layer's box, transform, presented look, overflow and scroll positions; has no ids, anchor names, form names or form owner; is inert and hidden from assistive technology; and its descendants' keyframe animations and transitions do not start again. A refused show during the closing toggle waits, hidden, until the toggle is over. A layer inside other layers gets its copy after the outermost one, with what it inherited there (its color scheme included), so a close that takes the layers around it too still draws the copy. That copy is shown again above the copy the same close then places for a layer around it. A layer has one copy. The layer-runtime record states it, and that the presentation plays its exit on the copy (AST-094 FR7). Core's shared inertCopy now takes every copied native control and form-associated custom element out of any form with form="", as its documentation says, so the swap's copies join no form either. It also sets anchor-name: none on each copied element that has an anchor name, so no live anchor gets a second element.
…s back into it Under a coarse primary pointer, Popover grows out of its trigger on the theme duration step nearest iOS's growth (`--duration-medium-min`) and the theme's entrance easing, and shrinks back into it on the theme's exit duration and easing, on the layer runtime's exit copy. A fine primary pointer keeps the layer's own entrance.
…rink Real-browser proofs, in Chromium and WebKit, that under a finger Popover grows out of its trigger on the theme's entrance tokens and shrinks back on its exit tokens, keeps advancing through a blocked main thread, does no Layout or Paint per frame, and cross-fades under Reduce Motion, with storyboards of both.
…matches the lane's intended resolution)
|
/approve-spec 134e7c9 |
There was a problem hiding this comment.
Approved pending CI: CI has not run on these heads yet, so once the drafts are ready a maintainer adds the ci:run label so CI runs.
#8186: approve.
#8187: approve.
#9823: approve.
#8223: approve.
Whole stack: passes. Each pull request lands only when it and every pull request below it are approved, and the record text in #8186, #8187 and #8223 carries an engineering owner's approval at its head. Two small non-blocking follow-ups are in the inline comments.
[Automated review]
| }, ATTRIBUTE); | ||
| // A press outside: the browser closes the layer by itself. | ||
| await page.mouse.click(360, 700); | ||
| const result = await page.evaluate(attribute => { |
There was a problem hiding this comment.
Non-blocking: this read runs straight after the outside click, while the other cases now await a frame first. Playwright's Chromium 149 accepts the copy's show, so it passes today; on Chromium 150 and later, where a refused show opens the copy in the next frame, it would race. Awaiting requestAnimationFrame inside this evaluate, as at line 101, keeps it stable.
| element: HTMLElement; | ||
| motion: PopoverSurfaceMotion; | ||
| } | null>(null); | ||
| const isTouchPresentationRef = useRef(false); |
There was a problem hiding this comment.
Non-blocking: motionRef already records whether the open was a touch one. A non-touch open calls release() (line 94), so motionRef.current?.motion.exit() does nothing without this check, and exit() itself does nothing without a touch presentation. Dropping this ref leaves one record of the open's presentation, the latch FR9's gap names.
…own diff unchanged)
…roofs (own diff unchanged)
…ion-hold (own diff unchanged)
|
/approve-spec 1f71ae2 |
There was a problem hiding this comment.
Approved pending CI: CI has not run on these heads yet, because the drafts come from a fork without the ci:run label.
#8186: approve. The layer runtime's exit copy follows architecture:layer-runtime INV14, and its record text carries an engineering owner's approval at this head.
#8187: changes requested.
- A tap that lands while the popover is still growing closes it (
packages/core/src/Popover/popoverSurfaceMotion.ts:174on #8187), butspec:AST-094FR35 andcomponent:PopoverFR9 require that touch to be ignored, so take the layer runtime's input hold for the growth in #8187, as #8223 does, or fold #8223 into it.
#9823: approve. The browser proofs cover the growth, the shrink and the mouse path.
#8223: approve, pending CI. The input hold makes a tap or drag during the growth do nothing, as FR35 and Popover FR9 ask, and a tap after the growth still dismisses.
Whole stack: passes. A pull request lands only when it and every pull request below it are approved, so #9823 and #8223 wait on #8187. Next: mark the drafts ready for review, and a maintainer adds the ci:run label so CI runs.
[Automated review]
Stack: #8186 → #8187 → #9823 → #8223
These four pull requests merge back to back, as one unit. This one builds on #8342, the layer runtime's input hold, which is on
main. Its head, 134e7c9, merges #9823's head (5f98e88) into this branch. Review only this pull request's change: vjeux/astryx@5f98e88...134e7c9The unit's timing comes from #8187 and follows
spec:AST-094FR45, merged in #9915 (257573b). Popover's main interaction is a tap: "A component whose main interaction is a tap plays the theme's curve tokens (duration and easing, scaled bytempo), never a spring, since a tap carries no velocity; no character or option plays a platform-measured curve for it (owner decision, 2026-11-04)." The growth follows "In a component whose main interaction is a tap, a motion a tap, key or the program starts whose record names no duration step, and for which the platform has a measured duration, plays the theme duration token whose value in the Storybook theme (neutral) is nearest that measurement, with the easing for its kind (entrance, exit or move); the measured value stays a reference in the component's motion table." The shrink follows "A motion whose record names its step keeps it: a tap-started overlay exit plays--duration-exitand--ease-exit(FR7); an in-place toggle cross-fade is not an exit, andcomponent:ProgressBarFR4's fill keeps--duration-medium."User need
Under a finger, a tap that lands while a Popover is still growing out of its trigger closes it again, and a drag scrolls the page and dismisses it. A person who taps twice quickly, or starts to scroll just after a tap, loses the popover they just opened. On iOS, UIKit ignores every touch while a popover presents, and
spec:AST-094FR35 asks the same of ours.Problem and solution fit
Current specification
spec:AST-094FR35: a touch presentation holds input while it enters, through the layer runtime's one input hold.component:PopoverFR9: under a finger, a touch during the growth is ignored. This change records FR9 as implemented, with a gap underarchitecture:layer-runtimeINV14, which gives presentation holds to the runtime: "the open-to-close latch waits for the shared presentation hold".Writing rubric results
This change edits one cell of
component:Popover(FR9's state).Public delta and caller burden
No public API change and no caller decision. The behavior changes only under a coarse primary pointer, and only while the popover grows.
Design and alternatives
holdInput(element, {until: growth, scope: 'viewport'})from the layer runtime. The hold's shield covers the viewport from inside the layer, so a tap on it is a tap inside the popover, which the browser's light dismiss leaves open.preventDefaultin capture listeners still closes it in Chromium.Evidence
At this head (134e7c9):
bank/C27/8223-red-first-e2-ryon the fork) is this head without the hold (not for landing). Its three hold tests fail, 3 of 20 in the surface-motion and token suites. This head passes 20 of 20.mainlistsLayer/inputHold.tsin that lane's paths. CI runs the full suites.maina Popover does not grow under a finger, so this change adds one sentence to the unit's feature changeset (popover-uikit-presentation.md) instead of its own[fix]: "A tap that lands while it grows is ignored, and a pan started then scrolls nothing."PopoverSecondDocumentandMobileNavTouchPresentation29/29.@astryxdesign/core/Popoverfrom each builtpackages/core/dist, minified, with React and StyleX external, gzip at the default level. test(Popover): browser proofs of the touch presentation's grow and shrink #9823's head is 102,506 B; this head is 104,537 B (+2,031 B). Most of that is the layer runtime's input hold, which Dialog, Drawer and MobileNav share and a page carries once. Without the shared modules, Popover grows 69 B.astryx.cssis unchanged.CI=true, a full build, typecheck, the Storybook build, the unit suites above, the routing test and the browser proofs: every exit code 0.Scope
Testing