Repository navigation
fix(core): gestures read another window's nodes and touches - #10224
Conversation
Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
|
/approve-spec 9680c76 |
PR Analysis Report🔎 Preview ·
|
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| LayerProvider | 4.1 kB (0) · 20 B CSS (0) | — | ✓ · ✓ · ✗ |
| lab/Schedule | 98.8 kB (+109 B) · 3.9 kB CSS (0) | — | — · — · — |
| Markdown | 161.4 kB (+107 B) · 19.5 kB CSS (0) | — | — · — · — |
138 more components
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| Toast | 89.0 kB (+100 B) · 9.6 kB CSS (0) | — | — · — · — |
| ContextMenu | 90.2 kB (+99 B) · 16.5 kB CSS (0) | — | — · — · — |
| MoreMenu | 86.1 kB (+98 B) · 16.6 kB CSS (0) | — | — · — · — |
| charts/ChartTooltip | 151.2 kB (+95 B) · 423 B CSS (0) | — | — · — · — |
| lab/ChatReactionBar | 75.2 kB (+89 B) · 1.1 kB CSS (0) | — | — · — · — |
| ContextMenuItem | 26.7 kB (+88 B) · 6.1 kB CSS (0) | — | — · — · — |
| Table | 99.6 kB (+87 B) · 17.2 kB CSS (0) | — | — · — · — |
| DropdownMenu | 85.8 kB (+85 B) · 16.6 kB CSS (0) | — | — · — · — |
| Carousel | 58.4 kB (+84 B) · 10.1 kB CSS (0) | — | — · — · — |
| richtext/RichTextEditorToolbar | 239.6 kB (+84 B) · 454 B CSS (0) | — | — · — · — |
| TableCell | 91.4 kB (+82 B) · 16.9 kB CSS (0) | — | — · — · — |
| TableHeaderCell | 91.0 kB (+82 B) · 16.7 kB CSS (0) | — | — · — · — |
| SideNav | 81.6 kB (+81 B) · 10.7 kB CSS (0) | — | — · — · — |
| MobileNav | 61.5 kB (+80 B) · 9.9 kB CSS (0) | — | — · — · — |
| lab/Comments | 281.7 kB (+80 B) · 2.5 kB CSS (0) | — | — · — · — |
| lab/ChatEmojiPicker | 74.6 kB (+76 B) · 773 B CSS (0) | — | — · — · — |
| lab/TransferList | 85.4 kB (+73 B) · 1.7 kB CSS (0) | — | — · — · — |
| charts/Chart | 173.8 kB (+71 B) · 494 B CSS (0) | — | — · — · — |
| MultiSelector | 88.2 kB (+70 B) · 16.2 kB CSS (0) | — | — · — · — |
| TimeInput | 144.0 kB (+70 B) · 15.1 kB CSS (0) | — | — · — · — |
| Pagination | 98.7 kB (+68 B) · 16.9 kB CSS (0) | — | — · — · — |
| lab/CommentThread | 275.9 kB (+67 B) · 2.4 kB CSS (0) | — | — · — · — |
| lab/NavigationScreen | 75.1 kB (+66 B) · 1.1 kB CSS (0) | — | — · — · — |
| lab/NavigationStack | 75.1 kB (+66 B) · 1.1 kB CSS (0) | — | — · — · — |
| Switch | 94.8 kB (+65 B) · 9.5 kB CSS (0) | — | — · — · — |
| AlertDialog | 95.6 kB (+64 B) · 13.7 kB CSS (0) | — | — · — · — |
| Selector | 88.7 kB (+64 B) · 16.5 kB CSS (0) | — | — · — · — |
| lab/ListInput | 91.0 kB (+64 B) · 1.6 kB CSS (0) | — | — · — · — |
| lab/TourStep | 66.9 kB (+64 B) · 590 B CSS (0) | — | — · — · — |
| Dialog | 22.5 kB (+63 B) · 6.7 kB CSS (0) | — | — · — · — |
| AppShell | 81.5 kB (+61 B) · 12.8 kB CSS (0) | — | — · — · — |
| DateRangeInput | 81.3 kB (+61 B) · 11.8 kB CSS (0) | — | — · — · — |
| SelectableCard | 16.9 kB (+57 B) · 7.3 kB CSS (0) | — | — · — · — |
| Slider | 97.0 kB (+57 B) · 8.3 kB CSS (0) | — | — · — · — |
| DateInput | 167.1 kB (+56 B) · 16.1 kB CSS (0) | — | — · — · — |
| TopNav | 63.1 kB (+56 B) · 10.2 kB CSS (0) | — | — · — · — |
| BottomSheet | 57.0 kB (+55 B) · 7.5 kB CSS (0) | — | — · — · — |
| SideNavHeading | 66.6 kB (+55 B) · 10.6 kB CSS (0) | — | — · — · — |
| lab/TransferListSelector | 92.4 kB (+54 B) · 1.9 kB CSS (0) | — | — · — · — |
| CommandPalette | 59.3 kB (+53 B) · 12.4 kB CSS (0) | — | — · — · — |
| TopNavHeading | 66.0 kB (+52 B) · 10.4 kB CSS (0) | — | — · — · — |
| SegmentedControl | 89.0 kB (+51 B) · 6.8 kB CSS (0) | — | — · — · — |
| lab/CommentComposer | 115.4 kB (+51 B) · 1.9 kB CSS (0) | — | — · — · — |
| ClickableCard | 16.7 kB (+50 B) · 7.1 kB CSS (0) | — | — · — · — |
| DateTimeInput | 193.4 kB (+50 B) · 18.0 kB CSS (0) | — | — · — · — |
| TabMenu | 63.0 kB (+48 B) · 10.4 kB CSS (0) | — | — · — · — |
| CheckboxListItem | 66.1 kB (+47 B) · 10.9 kB CSS (0) | — | — · — · — |
| Drawer | 44.5 kB (+47 B) · 8.2 kB CSS (0) | — | — · — · — |
| TopNavMegaMenuItem | 12.6 kB (+47 B) · 4.2 kB CSS (0) | — | — · — · — |
| BreadcrumbItem | 75.7 kB (+45 B) · 11.5 kB CSS (0) | — | — · — · — |
| Collapsible | 25.3 kB (+43 B) · 4.5 kB CSS (0) | — | — · — · — |
| Popover | 60.8 kB (+43 B) · 13.4 kB CSS (0) | — | — · — · — |
| TopNavMenu | 65.8 kB (+43 B) · 10.5 kB CSS (0) | — | — · — · — |
| CommandPaletteItem | 11.9 kB (+42 B) · 3.6 kB CSS (0) | — | — · — · — |
| PowerSearch | 264.1 kB (+40 B) · 22.9 kB CSS (0) | — | — · — · — |
| TopNavItem | 12.6 kB (+40 B) · 4.0 kB CSS (0) | — | — · — · — |
| Item | 16.1 kB (+39 B) · 5.3 kB CSS (0) | — | — · — · — |
| SelectorOption | 19.5 kB (+39 B) · 5.8 kB CSS (0) | — | — · — · — |
| Step | 29.8 kB (+39 B) · 5.6 kB CSS (0) | — | — · — · — |
| Outline | 23.7 kB (+38 B) · 4.1 kB CSS (0) | — | — · — · — |
| ButtonGroup | 39.9 kB (+37 B) · 3.7 kB CSS (0) | — | — · — · — |
| SegmentedControlItem | 16.0 kB (+37 B) · 4.3 kB CSS (0) | — | — · — · — |
| TreeList | 41.5 kB (+36 B) · 5.4 kB CSS (0) | — | — · — · — |
| DropdownMenuItem | 20.5 kB (+35 B) · 6.0 kB CSS (0) | — | — · — · — |
| ListItem | 65.2 kB (+35 B) · 10.9 kB CSS (0) | — | — · — · — |
| DropdownMenuRadioItem | 21.3 kB (+34 B) · 6.4 kB CSS (0) | — | — · — · — |
| RadioListItem | 62.9 kB (+34 B) · 9.7 kB CSS (0) | — | — · — · — |
| BottomSheetSwitcher | 17.5 kB (+33 B) · 3.4 kB CSS (0) | — | — · — · — |
| Tab | 13.3 kB (+33 B) · 4.2 kB CSS (0) | — | — · — · — |
| Token | 27.4 kB (+33 B) · 4.7 kB CSS (0) | — | — · — · — |
| TopNavMegaMenu | 60.9 kB (+33 B) · 11.2 kB CSS (0) | — | — · — · — |
| Lightbox | 59.7 kB (+32 B) · 9.7 kB CSS (0) | — | — · — · — |
| NumberInput | 62.8 kB (+32 B) · 10.7 kB CSS (0) | — | — · — · — |
| ComplexSelector | 62.4 kB (+31 B) · 11.0 kB CSS (0) | — | — · — · — |
| DropdownMenuCheckboxItem | 21.3 kB (+27 B) · 6.4 kB CSS (0) | — | — · — · — |
| SideNavItem | 61.6 kB (+25 B) · 10.4 kB CSS (0) | — | — · — · — |
| lab/SelectableCardGroup | 56.1 kB (+23 B) · 162 B CSS (0) | — | — · — · — |
| lab/EmojiPicker | 77.6 kB (+22 B) · 751 B CSS (0) | — | — · — · — |
| Tokenizer | 80.3 kB (+21 B) · 17.8 kB CSS (0) | — | — · — · — |
| FileInput | 60.2 kB (+18 B) · 10.3 kB CSS (0) | — | — · — · — |
| TextInput | 59.4 kB (+15 B) · 10.5 kB CSS (0) | — | — · — · — |
| FieldLabel | 42.9 kB (+14 B) · 5.2 kB CSS (0) | — | — · — · — |
| richtext/RichTextEditor | 290.6 kB (+13 B) · 2.6 kB CSS (0) | — | — · — · — |
| CheckboxInput | 59.9 kB (+11 B) · 9.0 kB CSS (0) | — | — · — · — |
| Banner | 53.5 kB (+10 B) · 9.8 kB CSS (0) | — | — · — · — |
| HoverCard | 24.3 kB (+10 B) · 4.3 kB CSS (0) | — | — · — · — |
| ChatComposerInput | 66.2 kB (+7 B) · 10.6 kB CSS (0) | — | — · — · — |
| ChatLayout | 54.9 kB (+7 B) · 9.8 kB CSS (0) | — | — · — · — |
| Link | 48.5 kB (+7 B) · 6.9 kB CSS (0) | — | — · — · — |
| Tooltip | 25.8 kB (+7 B) · 4.3 kB CSS (0) | — | — · — · — |
| ChatComposerTokenElement | 66.2 kB (+6 B) · 10.6 kB CSS (0) | — | — · — · — |
| richtext/RichTextView | 270.9 kB (+5 B) · 2.3 kB CSS (0) | — | — · — · — |
| DrawerHeader | 54.6 kB (+3 B) · 12.2 kB CSS (0) | — | — · — · — |
| DropdownMenuSubMenu | 64.5 kB (+3 B) · 9.4 kB CSS (0) | — | — · — · — |
| IconButton | 51.6 kB (+2 B) · 9.3 kB CSS (0) | — | — · — · — |
| AvatarGroupOverflow | 44.4 kB (+1 B) · 5.5 kB CSS (0) | — | — · — · — |
| ChatComposer | 68.2 kB (+1 B) · 11.2 kB CSS (0) | — | — · — · — |
| ChatSendButton | 52.0 kB (+1 B) · 9.3 kB CSS (0) | — | — · — · — |
| ProgressBar | 11.5 kB (+1 B) · 6.2 kB CSS (0) | — | — · — · — |
| Spinner | 20.3 kB (+1 B) · 6.1 kB CSS (0) | — | — · — · — |
| TableSelectionToolbar | 61.1 kB (+1 B) · 12.4 kB CSS (0) | — | — · — · — |
| Thumbnail | 55.9 kB (+1 B) · 10.9 kB CSS (0) | — | — · — · — |
| Timestamp | 23.0 kB (+1 B) · 9.6 kB CSS (0) | — | — · — · — |
| charts/ChartLegend | 35.6 kB (0) · 224 B CSS (0) | — | — · — · — |
| ChatToolCalls | 27.7 kB (0) · 8.3 kB CSS (0) | — | — · — · — |
| CommandPaletteInput | 27.9 kB (0) · 7.5 kB CSS (0) | — | — · — · — |
| Heading | 8.0 kB (0) · 5.3 kB CSS (0) | — | — · — · — |
| NavHeadingMenu | 15.5 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| Text | 7.5 kB (0) · 5.3 kB CSS (0) | — | — · — · — |
| Timer | 9.6 kB (0) · 5.9 kB CSS (0) | — | — · — · — |
| ToggleButton | 52.4 kB (0) · 9.4 kB CSS (0) | — | — · — · — |
| lab/DiffViewer | 77.9 kB (0) · 2.1 kB CSS (0) | — | — · — · — |
| Avatar | 39.4 kB (−1 B) · 5.5 kB CSS (0) | — | — · — · — |
| ChatMessageList | 23.8 kB (−1 B) · 6.9 kB CSS (0) | — | — · — · — |
| CodeBlock | 66.1 kB (−1 B) · 10.6 kB CSS (0) | — | — · — · — |
| InfoTip | 43.0 kB (−2 B) · 5.3 kB CSS (0) | — | — · — · — |
| Toolbar | 39.4 kB (−2 B) · 7.9 kB CSS (0) | — | — · — · — |
| Calendar | 62.4 kB (−3 B) · 9.9 kB CSS (0) | — | — · — · — |
| TabList | 40.1 kB (−3 B) · 6.4 kB CSS (0) | — | — · — · — |
| Typeahead | 69.9 kB (−3 B) · 11.8 kB CSS (0) | — | — · — · — |
| ChatDictationButton | 52.1 kB (−4 B) · 9.4 kB CSS (0) | — | — · — · — |
| SideNavCollapseButton | 52.4 kB (−4 B) · 9.4 kB CSS (0) | — | — · — · — |
| lab/CommentPin | 52.6 kB (−4 B) · 1.2 kB CSS (0) | — | — · — · — |
| DialogHeader | 54.4 kB (−5 B) · 10.8 kB CSS (0) | — | — · — · — |
| Field | 46.7 kB (−5 B) · 6.7 kB CSS (0) | — | — · — · — |
| ChatLayoutScrollButton | 52.0 kB (−8 B) · 9.5 kB CSS (0) | — | — · — · — |
| MobileNavToggle | 51.9 kB (−8 B) · 9.3 kB CSS (0) | — | — · — · — |
| RadioList | 48.2 kB (−8 B) · 6.8 kB CSS (0) | — | — · — · — |
| StatusDot | 27.5 kB (−9 B) · 5.3 kB CSS (0) | — | — · — · — |
| Stepper | 53.3 kB (−9 B) · 9.5 kB CSS (0) | — | — · — · — |
| AvatarGroup | 42.1 kB (−10 B) · 5.6 kB CSS (0) | — | — · — · — |
| TextArea | 53.8 kB (−10 B) · 8.9 kB CSS (0) | — | — · — · — |
| CheckboxList | 50.0 kB (−12 B) · 7.5 kB CSS (0) | — | — · — · — |
| Button | 51.5 kB (−15 B) · 9.3 kB CSS (0) | — | — · — · — |
| InputGroup | 48.5 kB (−16 B) · 7.3 kB CSS (0) | — | — · — · — |
| BaseTypeahead | 61.9 kB (−17 B) · 10.3 kB CSS (0) | — | — · — · — |
| layer | — | — | — · ✓ · — |
| Layer | — | — | ✓ · ✓ · — |
Package size: @astryxdesign/core 6.70 MB packed (+959 B); no new dependencies.
API changes: none in this pull request
Committed API report behind this build: cli 19 rows, core 21 rows; run pnpm api:report.
Touched components checked; add ci:run-visual, ci:run-a11y or ci:run-rtl to run the full suites.
CI impact: +1.6 job-min per update · pool +1.4% · time to green −8.8 min · standard runners
| Before | After | Change | |
|---|---|---|---|
| Job-minutes per PR update | 116.8 | 118.4 | +1.6 |
| Runner pool, runner-minutes a day | 53,692 | 54,424 | +732 (+1.4%) |
| Average concurrent jobs | 37.3 | 37.8 | +0.5 |
| Time to green (median → this run) | 28.3 min | 19.5 min | −8.8 min |
| Runs a day | 460 | 460 | 0 |
Changed jobs: pr-interaction-webkit changed: standard
Assumptions: before is each job's average over the last 20 pull-request runs; after keeps that for unchanged jobs and uses this run's minutes for added or changed ones, weighted by how often each changed job ran in those runs; runs a day averages the seven whole days before today; time to green compares this run with the median of those runs that ran the same jobs.
Generated by PR Enrichment workflow | View full report
There was a problem hiding this comment.
Changes requested. Gestures now begin for a component portalled into a same-origin iframe or a Picture-in-Picture window, and the new iframe tests fail on main and pass here, but two things need fixing:
isHTMLElement(packages/core/src/utils/nodeType.ts:44-52) checks an element against its document's window, so an element the page creates and then moves into an iframe or a Picture-in-Picture window fails it, and in Chromium such an element loses thetouch-actionit had on main (pinch-zoomthere,autohere, againstspec:AST-094FR18) while its switch label gains a second hidden switch; testnamespaceURI === 'http://www.w3.org/1999/xhtml'instead and add a moved-element case.isNodeTarget(packages/core/src/keyboard/keyboardHeight.ts:100) repeats the newisNode, andGestureHost.tskeeps its owninstanceof view.HTMLElementandinstanceof view.Nodechecks (lines 1286, 2096 and 2720), so a later fix to the shared guards misses them; have both modules useutils/nodeType.ts.
A non-blocking wording suggestion for the styleOf comment is inline. Required CI has not run on this head yet.
[Automated review]
| export function isHTMLElement(value: unknown): value is HTMLElement { | ||
| if (!isElement(value)) { | ||
| return false; | ||
| } | ||
| const ownClass = | ||
| value.ownerDocument.defaultView?.HTMLElement ?? | ||
| (typeof HTMLElement === 'undefined' ? undefined : HTMLElement); | ||
| return ownClass != null && value instanceof ownClass; | ||
| } |
There was a problem hiding this comment.
An element the page creates and then appends into an iframe or a Picture-in-Picture window keeps the page's prototype, so value instanceof value.ownerDocument.defaultView.HTMLElement is false for it, where main's instanceof HTMLElement was true. In Chromium a pan recognizer on such an element gets touch-action: pinch-zoom on main and auto here (GestureHost.ts:1429 skips applyStyle, against spec:AST-094 FR18), and attachTapHaptic adds a second hidden switch to its label.
The HTML namespace holds in every window:
export function isHTMLElement(value: unknown): value is HTMLElement {
return isElement(value) && value.namespaceURI === 'http://www.w3.org/1999/xhtml';
}A moved-element case beside the iframe ones would keep it fixed.
| */ | ||
|
|
||
| /** Whether `value` is a DOM node, from this window or another one. */ | ||
| export function isNode(value: unknown): value is Node { |
There was a problem hiding this comment.
isNodeTarget in keyboard/keyboardHeight.ts:100 is this same check, and GestureHost.ts keeps its own realm checks at lines 1286 (instanceof view.HTMLElement), 2096 and 2720 (instanceof view.Node). Import isNode and isHTMLElement from here in both modules and delete isNodeTarget, so a fix to these guards reaches every caller.
| * An element's inline style, when it has one (HTML and SVG elements). It is | ||
| * checked as an object, so another window's style passes too. |
There was a problem hiding this comment.
Non-blocking: "It is checked as an object" restates the code in the passive voice. Keep only the reason, for example: "Another window's style fails instanceof Object in this window."
|
/approve-spec 4d00e70 |
There was a problem hiding this comment.
Approved pending CI: required checks are still running on this head.
Gestures now begin for a component portalled into a same-origin iframe or a Document Picture-in-Picture window, and the new iframe tests fail on main and pass here. Both earlier findings are fixed: isHTMLElement reads the namespace, so an element the page moves into another window keeps its touch-action and switch label, and keyboardHeight.ts and GestureHost.ts now use the shared guards.
The earlier non-blocking wording suggestion on the styleOf comment in selectionLock.ts still applies.
[Automated review]
|
/approve-spec f92cf87 |
There was a problem hiding this comment.
Approved pending CI: required checks have not finished on this head yet.
Gestures now begin for a component portalled into a same-origin iframe or a Document Picture-in-Picture window, and the new iframe tests fail on main and pass here. The merge from main leaves the patch unchanged, and both earlier findings stay fixed.
The earlier non-blocking wording suggestion on the styleOf comment in selectionLock.ts still applies.
[Automated review]
e69dd39 merged onto main 3060510. One conflict: the WebKit lane's scope line in ci.yml, which main's meta#10224 also edits; resolved as main's line plus meta#10094's token-references path.
…s close (#8186) * chore(core): the layer runtime's exit copy, for a touch presentation'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. * Merge branch 'main' into uikit/c26-layer-exit-copy * Merge branch 'main' into uikit/c26-layer-exit-copy * fix(core): the exit copy keeps no closed layer The exit copy found the inner layers to raise through a module-level set of every copied layer. A layer React removed on close stayed reachable there until a later close showed a copy. The inner layers are inside the closing layer, so they are now found there, and nothing keeps a layer once it is gone. Chromium 150 and later refuse a show during another popover's toggle, so the copy can open in the next frame. The browser spec's light-dismiss case now reads the copy after a frame, as its nested-close cases do. The cap check's comment now names only the copy cap. * docs(core): the exit copy's JSDoc states the empty-canvas rule once The empty-canvas rule stays in the first paragraph, and the inert-copy paragraph no longer repeats it or `inertCopy`'s own contract. * Merge main into the layer exit copy branch (layer-runtime.md verification table keeps both rows) * Merge main into the layer exit copy branch (#8147 landed) * Merge main into the layer exit copy branch (placeOver's beforeNode beside keepInherited) * Merge main into the layer exit copy branch (#10224 landed) * fix(core): the exit copy reads its host's style before the insertion keepInherited read the outer layer's parent's computed style right after the copy was inserted there: a style read after a write. It reads only that host and writes only on the still-detached copy, so it now runs before the insertion. What the copy inherits is unchanged. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
…s back into it (#8187) * chore(core): the layer runtime's exit copy, for a touch presentation'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. * Merge branch 'main' into uikit/c26-layer-exit-copy * feat(Popover): under a finger, it grows out of its trigger and shrinks 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. * Merge branch 'main' into uikit/c26-layer-exit-copy * Merge the updated lower stack branch into uikit/c26-popover-present (own diff unchanged) * feat(Popover): a touch during the growth is ignored While the popover grows, the layer runtime's input hold takes every touch, as UIKit ignores touches while it presents a popover. A tap outside does not dismiss it and reaches nothing under it, and a pinch still zooms the page. This folds in the hold that #8223 carried, so the growth and its hold land together. * fix(core): the exit copy keeps no closed layer The exit copy found the inner layers to raise through a module-level set of every copied layer. A layer React removed on close stayed reachable there until a later close showed a copy. The inner layers are inside the closing layer, so they are now found there, and nothing keeps a layer once it is gone. Chromium 150 and later refuse a show during another popover's toggle, so the copy can open in the next frame. The browser spec's light-dismiss case now reads the copy after a frame, as its nested-close cases do. The cap check's comment now names only the copy cap. * docs(core): the exit copy's JSDoc states the empty-canvas rule once The empty-canvas rule stays in the first paragraph, and the inert-copy paragraph no longer repeats it or `inertCopy`'s own contract. * Merge the updated lower stack branch into uikit/c26-popover-present (own diff unchanged) * Merge main into the layer exit copy branch (layer-runtime.md verification table keeps both rows) * Merge the updated lower stack branch into uikit/c26-popover-present (own diff unchanged) * Merge main into the layer exit copy branch (#8147 landed) * Merge the updated lower stack branch into the Popover branch (#8147 landed; uikit index header lists popover and zoomMedia) * Merge the updated lower stack branch into the Popover branch * Merge main into the layer exit copy branch (placeOver's beforeNode beside keepInherited) * Merge the updated lower stack branch into the Popover branch (#10224 landed) * Merge main into the layer exit copy branch (#10224 landed) * feat(Popover): the touch presentation starts in the next frame The growth and its input hold now start on the first frame after the open, not in the task that opens the popover, so the opening tap's task lays out no more than a mouse opening does. Folded from #8662, so the group's squashes apply in order: these lines are this pull request's own. * docs(Popover): record the next-frame window with the code The input hold starts on the first frame after the open, so a touch that lands within that frame reaches the page. spec:AST-094 and Popover's record state that window here, beside the code that opens it. Moved from #8662. * fix(core): the exit copy reads its host's style before the insertion keepInherited read the outer layer's parent's computed style right after the copy was inserted there: a style read after a write. It reads only that host and writes only on the still-detached copy, so it now runs before the insertion. What the copy inherits is unchanged. * Merge the updated lower stack branch into the Popover branch * perf(Popover): the growth reads its boxes and timing before it writes The frame that starts a touch presentation wrote the CSS entrance off and the start scale, read both boxes, wrote the origin and the start frame, then read the theme's entrance tokens. Each read after a write restyled or laid the page out again. It now reads both boxes and the entrance timing in one pass, then writes, the start frame first: the motion engine may read the element's will-change as it sets it. The box needs no reset first: the opening task already took the CSS entrance off, and a dismissal leaves the element at full size. Shown again during the dismissal, the popover reads its entrance timing before its first write too. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
…rink (#9823) * chore(core): the layer runtime's exit copy, for a touch presentation'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. * Merge branch 'main' into uikit/c26-layer-exit-copy * feat(Popover): under a finger, it grows out of its trigger and shrinks 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. * test(Popover): browser proofs of the touch presentation's grow and shrink 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. * Merge branch 'main' into uikit/c26-layer-exit-copy * Merge the updated lower stack branch into uikit/c26-popover-present (own diff unchanged) * Merge the updated lower stack branch into uikit/c27-popover-present-proofs (own diff unchanged) * Merge #8187's input hold into the Popover touch proofs * feat(Popover): a touch during the growth is ignored While the popover grows, the layer runtime's input hold takes every touch, as UIKit ignores touches while it presents a popover. A tap outside does not dismiss it and reaches nothing under it, and a pinch still zooms the page. This folds in the hold that #8223 carried, so the growth and its hold land together. * test(Popover): browser proofs of the hold while the popover grows A tap outside, a tap on the trigger and a pan while the popover grows do nothing, a pinch still zooms, and a storyboard shows the growth going on under a tap outside. Folded from #8223. * fix(core): the exit copy keeps no closed layer The exit copy found the inner layers to raise through a module-level set of every copied layer. A layer React removed on close stayed reachable there until a later close showed a copy. The inner layers are inside the closing layer, so they are now found there, and nothing keeps a layer once it is gone. Chromium 150 and later refuse a show during another popover's toggle, so the copy can open in the next frame. The browser spec's light-dismiss case now reads the copy after a frame, as its nested-close cases do. The cap check's comment now names only the copy cap. * docs(core): the exit copy's JSDoc states the empty-canvas rule once The empty-canvas rule stays in the first paragraph, and the inert-copy paragraph no longer repeats it or `inertCopy`'s own contract. * Merge the updated lower stack branch into uikit/c26-popover-present (own diff unchanged) * Merge the updated lower stack branch into uikit/c27-popover-present-proofs (own diff unchanged) * test(Popover): the blocked-main-thread proof blocks from the page and asserts the verdict The page now starts the block once the growth has run a frame, and the proof asserts the harness verdict, which fails a judged part under 4 frames. Here the growth runs 600 ms at a constant speed, so each frame moves several pixels and a block that starts late on a loaded machine still lands inside it. * Merge main into the layer exit copy branch (layer-runtime.md verification table keeps both rows) * Merge the updated lower stack branch into uikit/c26-popover-present (own diff unchanged) * Merge the updated lower stack branch into uikit/c27-popover-present-proofs (own diff unchanged) * Merge main into the layer exit copy branch (#8147 landed) * Merge the updated lower stack branch into the Popover branch (#8147 landed; uikit index header lists popover and zoomMedia) * Merge the updated lower stack branch into the Popover branch (#8147 landed; uikit index header lists popover and zoomMedia) * Merge the updated lower stack branch into the Popover branch * Merge main into the layer exit copy branch (placeOver's beforeNode beside keepInherited) * Merge the updated lower stack branch into the Popover branch * Merge the updated lower stack branch into the Popover branch (#10224 landed) * Merge main into the layer exit copy branch (#10224 landed) * Merge #8187 (next-frame start folded in) into the Popover touch proofs * feat(Popover): the touch presentation starts in the next frame The growth and its input hold now start on the first frame after the open, not in the task that opens the popover, so the opening tap's task lays out no more than a mouse opening does. Folded from #8662, so the group's squashes apply in order: these lines are this pull request's own. * test(Popover): browser proofs of the next-frame start The touch-motion spec's cases for the growth that starts on the first frame after the open, folded from #8662 with the change they prove. * docs(Popover): record the next-frame window with the code The input hold starts on the first frame after the open, so a touch that lands within that frame reaches the page. spec:AST-094 and Popover's record state that window here, beside the code that opens it. Moved from #8662. * Merge #8187 (next-frame records moved in) into the Popover touch proofs * fix(core): the exit copy reads its host's style before the insertion keepInherited read the outer layer's parent's computed style right after the copy was inserted there: a style read after a write. It reads only that host and writes only on the still-detached copy, so it now runs before the insertion. What the copy inherits is unchanged. * Merge the updated lower stack branch into the Popover branch * perf(Popover): the growth reads its boxes and timing before it writes The frame that starts a touch presentation wrote the CSS entrance off and the start scale, read both boxes, wrote the origin and the start frame, then read the theme's entrance tokens. Each read after a write restyled or laid the page out again. It now reads both boxes and the entrance timing in one pass, then writes, the start frame first: the motion engine may read the element's will-change as it sets it. The box needs no reset first: the opening task already took the CSS entrance off, and a dismissal leaves the element at full size. Shown again during the dismissal, the popover reads its entrance timing before its first write too. * Merge the updated lower stack branch into the Popover touch proofs Co-authored-by: Cindy Zhang <cindyxz@meta.com>
Supersedes #10211 (co-authored by vjeux)
The change of #10211, re-landed whole on current
mainfrom a maintainer branch with the author as co-author. Its halves depend on each other: the gesture code, and thenodeTypeentry it adds to the interaction lanes' scope pattern in.github/workflows/ci.yml.Every file #10211 changes is byte-identical to its head
d78dd80d, merged withmainwith no conflict. That head:main, and the gesture host's input lock keepsmain's signature (isElement(recognizer.view) && managed(recognizer.view, recognizer));main'suseFingerScrollonEndand takesisDocumentfromutils/nodeType;A follow-up replaces the WebKit lane's hand-kept scope pattern with one derived from where the interaction specs live; this pattern entry goes away with it.
User impact
A component rendered into another window's document gets no gestures. That is a same-origin iframe or a Document Picture-in-Picture window the page's code renders or moves elements into. There its pans, taps, long presses and swipes never begin, under any pointer. Its finger scrolls read as the program's, and a held layer holds no touch. A press in a scroll box there gets the wrong delay, and on iOS a hold can start a selection. A page whose iframe loads its own Astryx is not affected.
Problem and solution fit
instanceofchecks test the page's classes, so it dropped that window's touches.Expected behavior and authority
family:overlay-dismissalFR8: trigger-anchored members render in the page, a same-origin iframe or a Document Picture-in-Picture window.spec:AST-094FR43: gestures attach for every pointer, with no limit to the page's own document.Smallest restoration
utils/nodeType.tsadds node-type guards. ItsisHTMLElementreads the namespace, so an element moved between windows' documents passes.Evidence
Scope
Testing
At this head, which merges main
62f5ebc5e4. Everything below ran on this code.pnpm test:ui: 21,569 passed in 867 files.pnpm test:node: 16,832 passed in 928 files.pnpm -F @astryxdesign/core typecheck: 0 errors.CI=true pnpm lint: 0 errors, and no warning in a changed file. Prettier andreview:pre: clean.gestures.interaction.browser.spec.tsandpresentedHitTesting.interaction.browser.spec.ts: Chromium 78 passed and 4 skipped; WebKit 70 passed and 12 skipped by design. That includes the trackpad case of fix(gestures): a pan on its own scroller takes a trackpad's scroll, as a finger's pan (spec:AST-094 FR43) #10212.isHTMLElement(touch-actionauto, notpinch-zoom). It passes 3 of 3 in both here. The two moved-element unit cases fail and pass the same way.PopoverSecondDocumentandDialogSecondDocument, in an iframe and a Picture-in-Picture window: Chromium 33 passed.