Repository navigation
Conversation
The motion README's uikit/search.ts row goes back to meta#8224's text, which an earlier merge here had reworded.
The merge of the heads meta#8129 builds on: the gesture line (meta#8320 @ ca71fce) and the animator line (meta#8340 @ 9718a3d), both on the touch foundation meta#8076 @ 3fb31dd, and Core's internal entries (meta#8562 @ 598be90). With it as an ancestor, GitHub's compare from it shows exactly meta#8129's own files.
… @ 9718a3d), into the TabBar stack
…changed) c996c19 is the merge of the heads this pull request builds on. With it as an ancestor, GitHub's compare from it shows exactly this pull request's own files.
…ebKit too (spec:AST-094 FR43, FR41) WebKit extends a drag's selection over text outside the element that refuses it: at 80 px past a card, a claimed mouse pan selected the text around it, where Chromium selects nothing. While a mouse or pen pan's claim is live, selection stays off document-wide (a selectstart anywhere is refused, a selection the drag extends goes) until the press's own mouseup and click have passed. Fails in WebKit and in the unit case until the host holds the document.
…ntries and internal/motion Main's resolveSnapPoint test (meta#8663) keeps the release policy internal: not on the package root, not in a barrel, and no package export naming motion. The last check also caught ./internal/motion, Core's internal entry for Astryx's own packages (architecture:react-component-runtime INV13), which re-exports no release policy. The export check now covers the public keys only, and a new assertion keeps the claim as strong: internal/motion must not re-export resolveSnapPoint either. On this branch with main the test failed on ./internal/motion; it now passes, and it fails when internal/motion re-exports resolveSnapPoint.
The gesture README promises that usePressTracker rebuilds its tracker when trackingExpansion, highlightDelay, cancelTravel or quickTapFlash change, from the next touch. That fix (c248459) was lost in a later merge of the press-tracking line: the tracker was built once, so a changed option never applied. It is memoized on those four again; the node attachment, keyed on the tracker, moves to the new one and disposes the old. The callbacks keep being read through the ref on every touch. The new test fails before this change (the same tracker after each option change) and passes after it.
…nt-wide until its press is over (spec:AST-094 FR43, FR41) The drag lock covered only the claiming recognizers' elements, and WebKit extends a drag's selection over text outside them. lockDragSelection now holds the document too, from the claim: a selectstart anywhere is refused and a selection the drag extends is cleared (a focused text field keeps its own). The press's mouseup and click come after its pointerup in the same task, and WebKit settles the drag's selection there, so the document's hold ends a task after the release, clearing once more. Page listeners only during a press (FR24).
The merge of the heads meta#8129 builds on: the gesture line (meta#8320 @ ca71fce) and the animator line (meta#8340 @ 9718a3d), both on the touch foundation meta#8076 @ 3fb31dd, and Core's internal entries (meta#8562 @ cd3ad72). With it as an ancestor, GitHub's compare from it shows exactly meta#8129's own files.
…changed) 3901418 is the merge of the heads this pull request builds on. With it as an ancestor, GitHub's compare from it shows exactly this pull request's own files.
…arch field covers it Under a finger the active field spans the bar's row over the leading circle, which kept its tab stop and its name. It is inert while the field is active there, with a browser test (red first: the covered circle took focus).
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about what a mouse or pen drag shows on TabBar. spec:AST-094 FR43 covers attaching the lens's gesture for every pointer, but no current record states this presentation (the lens's resting place following the pointer with no lift or copy, the selected look held until the release), and spec:AST-017 FR59 asks for an owner decision on a new fine-pointer look or feel. An engineering owner can provide or identify the governing record and record the decision by commenting /approve-spec 0e93121143a0cfc3d876fa72c962a347681c837c on this pull request; the author is an engineering owner and may do so. Whatever the decision, these changes are needed:
- TabBar joins
family:action-groupsby that family's rule, so its press tracking should run through core'sactionGroupTracker(architecture:interaction-modalityINV13) instead of the lens's own copy. - After a mouse or pen slide, focus should follow the chosen tab as it does after a click (family FR1); today it stays on the tab the press landed on.
- While the pointer is off the bar or over a disabled tab, the lens should not show a tab the release will not choose (family FR2).
- The diff still carries about 46,000 lines from the open stack below it, so land or retarget that stack first and leave this pull request with only its own change.
[Automated review]
| !press.isOff && | ||
| isDisabledRef.current?.(nearest) !== true | ||
| ) { | ||
| flushSync(() => onLiftRef.current(nearest)); |
There was a problem hiding this comment.
After a slide this release selects another tab, but focus stays on the tab the press landed on: the browser focused it at pointerdown, and that tab is now tabindex="-1". family:action-groups FR1 says "After a slide, focus does what a click on the lifted action does". Moving focus to the chosen tab when it differs from the pressed one would match a click.
| press.isOff = | ||
| location.x < press.box.left || | ||
| location.x > press.box.right || | ||
| location.y < press.box.top || | ||
| location.y > press.box.bottom; | ||
| // A fine pointer's drag carries the lens's resting place, held on an | ||
| // enabled tab like a finger's chase; from a disabled tab it counts | ||
| // from here. | ||
| if (!press.isDragging) { | ||
| return; | ||
| } | ||
| press.isIgnored = false; | ||
| if (press.hasLens && selected >= 0) { | ||
| placeLens(chaseTarget(x, press.centers) - press.centers[0]); | ||
| } |
There was a problem hiding this comment.
Off the capsule's box this marks the press as cancelling, but it still places the lens from the pointer's x. The lens then shows a tab the release will not choose, and chaseTarget does the same over a disabled tab. family:action-groups FR2 wants nothing highlighted outside the group or over a disabled action. Returning the lens to its resting place while press.isOff, or over a disabled tab, would fix both.
| useGestureRecognizer( | ||
| capsuleRef, | ||
| () => | ||
| new LongPressGestureRecognizer({ | ||
| name: 'tab-bar-selection', | ||
| minimumPressDuration: 0, | ||
| // Every pointer (`spec:AST-094` FR43): a finger in the touch | ||
| // presentation, a mouse or a pen dragging in the mouse one. | ||
| allowedPointerTypes: ['touch', 'pen', 'mouse'], | ||
| // The hold lifts the lens and drags it, so no selection or callout | ||
| // starts under the finger; a press at 0 ms brings no lock of its | ||
| // own (`spec:AST-094` FR41). | ||
| selectionLock: lockSelection, | ||
| // Only the capsule's own box: the search tab beside it is a button. | ||
| shouldReceiveTouch: (_recognizer, touch) => { | ||
| const box = capsuleRef.current?.getBoundingClientRect(); | ||
| return box != null && touch.x >= box.left && touch.x <= box.right; | ||
| }, | ||
| }), |
There was a problem hiding this comment.
This recognizer tracks the held pointer across the tabs with its own nearest-tab and off-box logic. TabBar joins family:action-groups by its membership rule ("a tab bar"), and architecture:interaction-modality INV13 gives that tracking to core's actionGroupTracker: "No other member keeps its own copy of the tracking." The tracker's resolveTarget and onMove already serve a row of segments and a lens that chases the pointer.
…ar the lens shows the selection After a mouse or pen slide the chosen tab takes the focus the press gave the tab it landed on, which had left the tab order (family:action-groups FR1). Dragged off the bar, where the release chooses nothing, the lens shows the selected tab again. Browser tests, red first in Chromium and WebKit.
The Effect that ends a press when the recognizer turns off, and the one that ends it when the tab list changes under a finger, now say what they synchronize, as the selection Effect does.
While the bar is collapsed its tabs are inert, so focus never lands on a tab: keyboard focus reaches the circle, whose activation expands the bar. The prop doc, the component doc and the hook said focus on a tab expands it.
…tree (AST-085 FR5) Reads Chromium's accessibility tree: the disabled tab keeps its name and is announced disabled on the tab itself; no other tab is.
The component doc said the search circle becomes the cancel button; the circle stays a tab, out of reach while the field is active, and a separate Cancel button lies over it.
There was a problem hiding this comment.
Approval waits on two engineering-owner decisions: what a mouse or pen drag shows on TabBar (no current record states it, and spec:AST-017 FR59 asks for an owner decision), and the new spec:AST-094 FR41 text this branch carries for mouse and pen drags a recognizer claims. An engineering owner records them by commenting /approve-spec followed by the full head SHA on this pull request, and the author may do so if they are an engineering owner. Independently of that decision, these still need changes:
- After a slide, move focus to the chosen tab as a click does (
family:action-groupsFR1). - Off the bar or over a disabled tab, stop showing the lens on a tab the release will not choose (FR2).
- Track the press through the shared
actionGroupTrackerinstead of TabBar's own copy (architecture:interaction-modalityINV13). - Land or retarget the stack so the diff against
mainholds only this pull request's own files.
[Automated review]
There was a problem hiding this comment.
Approval still waits on two engineering-owner decisions: what a mouse or pen drag shows on TabBar (no current record states it, and spec:AST-017 FR59 asks for an owner decision) and the new spec:AST-094 FR41 text this branch carries for mouse and pen drags a recognizer claims. An engineering owner records them by commenting /approve-spec followed by the full head SHA on this pull request, and the author may do so if they are an engineering owner. Focus now follows the chosen tab after a slide and a drag off the bar returns the lens to the selected tab, but these still need changes whatever the decision:
- Over a disabled tab, return the lens to the selected tab instead of showing its enabled neighbour, since the release chooses nothing there (
family:action-groupsFR2), and add a mouse case that reads it. - Track the press through the shared
actionGroupTrackerinstead of TabBar's own copy (architecture:interaction-modalityINV13). - Land or retarget the stack so the diff against
mainholds only this pull request's own files.
[Automated review]
| if (press.hasLens && selected >= 0) { | ||
| placeLens( | ||
| press.isOff ? null : chaseTarget(x, press.centers) - press.centers[0], | ||
| ); |
There was a problem hiding this comment.
Over a disabled tab chaseTarget still puts the lens on an enabled neighbour (Profile in the Disabled Tab story) while the release at :538-542 chooses nothing. family:action-groups FR2 says nothing is highlighted over a disabled action; returning the lens to its resting place here, as the off-bar branch now does, would meet it and keep spec:AST-085 FR6's no-drag-affordance rule.
| useGestureRecognizer( | ||
| capsuleRef, | ||
| () => | ||
| new LongPressGestureRecognizer({ | ||
| name: 'tab-bar-selection', | ||
| minimumPressDuration: 0, | ||
| // Every pointer (`spec:AST-094` FR43): a finger in the touch | ||
| // presentation, a mouse or a pen dragging in the mouse one. | ||
| allowedPointerTypes: ['touch', 'pen', 'mouse'], |
There was a problem hiding this comment.
This recognizer, with begin, move and end, is TabBar's own copy of the group tracking, now extended to a mouse and a pen. architecture:interaction-modality INV13 gives that job to Core's actionGroupTracker, and nothing in packages/lab imports it; the recognizer and the lens's look can stay while the choice, the off-group cancel and the highlight go through the shared tracker.
Depends on #8248 and #8665, with #8666's web input drivers.
Review only this PR's change: vjeux/astryx@c804efa...6dcca1e
User need
Part of making Astryx's gestures work for every pointer on the web (
spec:AST-094FR43, now current). On a desktop, TabBar's lens answers only a click; a mouse or pen can't drag it across the tabs as a finger can. This adds that drag.Problem and solution fit
Owner decisions (pending owner review)
Current specification
spec:AST-094FR43, FR24 (the click guard counts once per page), FR41, FR13;design:reduced-motionDR3.Public delta and caller burden
No new prop; a mouse or pen drag across the bar now chooses a tab; fingers are unchanged.
Size, gzip against #8248 with #8665: TabBar's import 71.1 → 71.2 KB JavaScript, 1,980 → 1,980 B CSS.
Design and alternatives
Evidence
Chromium, mouse: a drag from Home to Browse, each frame labelled with the lens's offset.
Scope
Stack: #8076 → … → #8320 → #8129 → … → #8248 → this PR, with #8665 merged in.
Testing
Try it:
pnpm storybook, Lab › TabBar › Default, with a mouse.Browser tests, red first: five new cases (a mouse drag, a pen drag, a held press, the bar's listeners, Reduce Motion) failed before the change and pass after; a press leaves only the shared click guard's page listener.
CI's
pr-interactionfails one SearchField case, also at this PR's base: #8665's click guard takes the click #8224's SearchField waits for. 720d246 (in #8689) fixes it; this PR takes it from its base or main.Passing:
pnpm test:interaction TabBar,pnpm vitest run packages/lab/src/TabBar,pnpm typecheck, lint.