Skip to content

feat(DateInput, TimeInput, DateTimeInput): the sheets' wheels draw as one picker under a finger - #8161

Closed
vjeux wants to merge 546 commits into
meta:mainfrom
vjeux:uikit/c21-wheel-inputs
Closed

vjeux wants to merge 546 commits into
meta:mainfrom
vjeux:uikit/c21-wheel-inputs

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8159 (and its predecessors); review the last commit.

User impact

Under a finger, the TimeInput, DateTimeInput and DateInput sheets show two to four wheels side by side. UIPickerView draws such columns as one picker: one selection band spans them, and each column's dimmed rows lean toward the picker's centre. After #8159 each touch wheel still draws its own band and vanishing point. This PR wraps the sheets' wheels in WheelGroup, which draws that frame under a finger and nothing under a fine pointer: mouse sheets are unchanged.

Visual authority

  • Existing component/module DD row reused or amended.
  • Durable component-local intent added to the optional Design decisions table by a team design owner.
  • Correction already settled by current family, design, theme, or objective accessibility authority.
  • New shared or cross-component direction settled in docs/design/.

None of these settles it yet. The geometry is UIKit's, measured for #8156 (one band inset 9 pt across the columns, every column's table centred on the picker), under spec:AST-094 (#8378) FR1. The band's fill is #8159's, proposed; pending owner review.

Before and after

#8159 (columns alone) This PR UIKit (iOS 26.2)

A flick in Mobile Safari (iOS 26.2 simulator) under the shared band; the blue dot marks the finger (drawn for the recording):

Flick under one band

State matrix

Mobile Safari, iOS 26.2 simulator:

DateTimeInput time panel DateInput month and year TimeInput, RTL
DateTimeInput DateInput RTL

One band, Android Chromium

Scope

  • Appearance is the primary intent.
  • Behavior, API, interaction model, and unrelated layout changes were removed or split.
  • The evidence shows the changed pixels actively running, not only source or computed styles.
  • Public text and artifacts contain no internal Meta context.

Testing

  • jsdom, for the TimeInput sheet, the DateTimeInput time panel and the DateInput month and year wheels: one band under a finger (fails on feat(Wheel): UIPickerView's motion under a finger, in today's look #8159's consumers: one band per wheel), and none under a mouse (presentation="bottom-sheet" on a fine pointer).
  • pnpm exec vitest run packages/core/src/DateInput packages/core/src/TimeInput packages/core/src/DateTimeInput packages/core/src/motion packages/core/src/gestures packages/core/src/utils packages/core/src/BottomSheet: 95 files, 2310 tests.
  • Chromium: the time wheels draw one picker under a finger and stay separate scrolling columns under a mouse, and the DateTimeInput time wheels keep their gutter under a mouse (every gap between adjacent columns at least 8 px; 0 px on e16c969); on the stack top's build, with feat(Wheel): UIPickerView's motion under a finger, in today's look #8159's cases, 11 passed.
  • pnpm -F @astryxdesign/core typecheck, ESLint and Prettier on touched files, pnpm check:changesets.

Stack (merge in this order): … → #8156 → #8159 → this PR (the full order is in #8156).

@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 26, 2026 3:00am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
⏳ Queued (for maintainers only) Oct 26, 2026, 2:52 AM UTC

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

ContextMenu (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 1.1KB 1.1KB 0B
Lines of Code 639 639 -
Complexity Very High Very High (84) -
DateInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 266B 266B 0B
Lines of Code 3525 5287 -
Complexity Very High Very High (496) -
DateTimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 286B 286B 0B
Lines of Code 3609 3614 -
Complexity Very High Very High (559) -
MoreMenu (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 219B 219B 0B
Lines of Code 87 87 -
Complexity Low Low (4) -
MultiSelector (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 273B 273B 0B
Lines of Code 2626 2626 -
Complexity Very High Very High (338) -
TimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 411B 411B 0B
Lines of Code 1236 1241 -
Complexity Very High Very High (191) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/AlertDialog 51.3KB 51.9KB +651B 12.2KB 12.2KB 0B
core/AppShell 52.5KB 53.1KB +627B 11.4KB 11.4KB 0B
core/AvatarGroupOverflow 37.0KB 37.6KB +646B 6.2KB 6.2KB 0B
core/Banner 44.6KB 45.2KB +649B 9.5KB 9.5KB 0B
core/BaseTypeahead 50.7KB 51.3KB +643B 9.7KB 9.7KB 0B
core/BottomSheet 25.8KB 25.8KB 0B 7.5KB 7.5KB 0B
core/BottomSheetSwitcher 9.1KB 9.1KB 0B 3.2KB 3.2KB 0B
core/BreadcrumbItem 62.7KB 63.3KB +596B 10.8KB 10.8KB 0B
core/Button 40.1KB 40.8KB +649B 9.0KB 9.0KB 0B
core/Calendar 51.8KB 52.5KB +668B 9.8KB 9.8KB 0B
core/Carousel 45.8KB 46.4KB +622B 10.0KB 10.0KB 0B
core/ChatComposer 57.2KB 57.8KB +603B 10.5KB 10.5KB 0B
core/ChatComposerInput 52.3KB 53.0KB +618B 9.8KB 9.8KB 0B
core/ChatComposerTokenElement 52.3KB 53.0KB +618B 9.8KB 9.8KB 0B
core/ChatDictationButton 43.4KB 44.0KB +623B 9.2KB 9.2KB 0B
core/ChatLayout 45.8KB 46.4KB +606B 9.7KB 9.7KB 0B
core/ChatLayoutScrollButton 43.3KB 43.9KB +625B 9.3KB 9.3KB 0B
core/ChatSendButton 42.8KB 43.4KB +615B 9.0KB 9.0KB 0B
core/CheckboxInput 46.5KB 47.1KB +580B 9.2KB 9.2KB 0B
core/CheckboxListItem 50.6KB 51.2KB +641B 10.3KB 10.3KB 0B
core/ClickableCard 12.7KB 13.3KB +639B 6.8KB 6.8KB 0B
core/CodeBlock 55.5KB 56.1KB +626B 10.4KB 10.4KB 0B
core/Collapsible 11.6KB 12.2KB +652B 4.1KB 4.1KB 0B
core/CommandPalette 49.0KB 49.7KB +658B 11.6KB 11.6KB 0B
core/CommandPaletteItem 8.3KB 8.9KB +644B 3.3KB 3.3KB 0B
core/ComplexSelector 52.1KB 52.7KB +638B 10.3KB 10.3KB 0B
core/ContextMenu 73.9KB 74.8KB +922B 14.2KB 14.2KB 0B
core/ContextMenuItem 16.4KB 17.1KB +653B 5.0KB 5.0KB 0B
core/DateInput 89.6KB 116.3KB +26.7KB ⚠️ 14.3KB 14.6KB +313B
core/DateRangeInput 61.3KB 61.9KB +596B 10.9KB 10.9KB 0B
core/DateTimeInput 98.9KB 125.8KB +26.9KB ⚠️ 14.7KB 15.0KB +312B
core/DialogHeader 45.7KB 46.3KB +622B 9.5KB 9.5KB 0B
core/DrawerHeader 45.7KB 46.3KB +661B 10.9KB 10.9KB 0B
core/DropdownMenu 69.0KB 69.9KB +888B 14.4KB 14.4KB 0B
core/DropdownMenuCheckboxItem 16.0KB 16.6KB +659B 5.4KB 5.4KB 0B
core/DropdownMenuDivider 6.5KB 6.5KB 0B 3.4KB 3.4KB 0B
core/DropdownMenuGroup 5.9KB 5.9KB 0B 3.1KB 3.1KB 0B
core/DropdownMenuItem 15.3KB 15.9KB +679B 4.9KB 4.9KB 0B
core/DropdownMenuRadioGroup 5.9KB 5.9KB 0B 3.1KB 3.1KB 0B
core/DropdownMenuRadioItem 16.0KB 16.7KB +660B 5.4KB 5.4KB 0B
core/DropdownMenuSubMenu 54.5KB 55.0KB +558B 9.3KB 9.3KB 0B
core/FileInput 50.6KB 51.2KB +588B 10.1KB 10.1KB 0B
core/IconButton 40.2KB 40.8KB +632B 9.0KB 9.0KB 0B
core/Item 11.2KB 11.9KB +658B 4.5KB 4.5KB 0B
core/LayerProvider 54.4KB 55.0KB +628B 9.9KB 9.9KB 0B
core/Lightbox 46.4KB 47.0KB +621B 9.6KB 9.6KB 0B
core/Link 40.6KB 41.2KB +645B 7.4KB 7.4KB 0B
core/ListItem 50.1KB 50.7KB +643B 10.3KB 10.3KB 0B
core/Markdown 133.2KB 134.2KB +991B 16.9KB 16.9KB 0B
core/MobileNav 45.7KB 46.3KB +618B 9.8KB 9.8KB 0B
core/MobileNavToggle 43.2KB 43.8KB +628B 9.2KB 9.2KB 0B
core/MoreMenu 69.2KB 70.1KB +900B 14.4KB 14.4KB 0B
core/MultiSelector 71.9KB 72.8KB +969B 14.1KB 14.1KB 0B
core/NumberInput 52.5KB 53.0KB +577B 10.4KB 10.4KB 0B
core/Outline 28.5KB 29.1KB +588B 3.9KB 3.9KB 0B
core/Overlay 14.2KB 14.2KB 0B 3.7KB 3.7KB 0B
core/Pagination 83.6KB 84.6KB +975B 14.8KB 14.8KB 0B
core/Popover 44.1KB 44.7KB +614B 9.2KB 9.2KB 0B
core/PowerSearch 143.9KB 171.8KB +27.8KB ⚠️ 18.1KB 18.4KB +298B
core/RadioListItem 49.2KB 49.8KB +659B 9.4KB 9.4KB 0B
core/SegmentedControlItem 8.5KB 9.1KB +639B 3.7KB 3.7KB 0B
core/SelectableCard 12.6KB 13.3KB +643B 7.0KB 7.0KB 0B
core/Selector 75.1KB 76.0KB +903B 14.5KB 14.5KB 0B
core/SelectorOption 14.4KB 15.0KB +651B 4.8KB 4.8KB 0B
core/SideNav 52.8KB 53.4KB +618B 10.3KB 10.3KB 0B
core/SideNavCollapseButton 43.5KB 44.1KB +615B 9.2KB 9.2KB 0B
core/SideNavHeading 51.6KB 52.2KB +594B 9.9KB 9.9KB 0B
core/SideNavItem 51.3KB 51.8KB +577B 9.9KB 9.9KB 0B
core/Step 32.2KB 32.9KB +632B 5.1KB 5.1KB 0B
core/Stepper 44.3KB 45.0KB +621B 9.3KB 9.3KB 0B
core/Switch 45.4KB 46.0KB +603B 9.1KB 9.1KB 0B
core/Tab 9.6KB 10.2KB +645B 4.0KB 4.0KB 0B
core/TabMenu 48.5KB 49.1KB +611B 9.8KB 9.8KB 0B
core/Table 80.1KB 81.0KB +916B 14.6KB 14.6KB 0B
core/TableCell 74.9KB 75.9KB +936B 14.4KB 14.4KB 0B
core/TableHeaderCell 74.7KB 75.6KB +915B 14.3KB 14.3KB 0B
core/TableSelectionToolbar 49.1KB 49.7KB +617B 11.0KB 11.0KB 0B
core/TextInput 50.0KB 50.5KB +593B 10.2KB 10.2KB 0B
core/Thumbnail 47.0KB 47.6KB +625B 10.8KB 10.8KB 0B
core/TimeInput 76.1KB 102.5KB +26.4KB ⚠️ 13.2KB 13.6KB +363B
core/Timestamp 27.4KB 27.4KB -2B 9.3KB 9.3KB 0B
core/Toast 51.2KB 51.8KB +642B 9.5KB 9.5KB 0B
core/ToggleButton 41.0KB 41.6KB +626B 9.1KB 9.1KB 0B
core/Token 29.5KB 30.1KB +643B 4.3KB 4.3KB 0B
core/Tokenizer 61.2KB 61.8KB +640B 11.4KB 11.4KB 0B
core/TopNav 46.6KB 47.2KB +594B 10.1KB 10.1KB 0B
core/TopNavHeading 51.0KB 51.6KB +599B 9.7KB 9.7KB 0B
core/TopNavItem 8.9KB 9.5KB +638B 3.7KB 3.7KB 0B
core/TopNavMegaMenu 51.0KB 51.6KB +599B 10.5KB 10.5KB 0B
core/TopNavMegaMenuItem 9.0KB 9.6KB +645B 3.9KB 3.9KB 0B
core/TopNavMenu 50.5KB 51.0KB +605B 9.9KB 9.9KB 0B
core/TreeList 32.4KB 33.0KB +612B 4.5KB 4.5KB 0B
core/Typeahead 57.7KB 58.4KB +641B 11.0KB 11.0KB 0B

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.

⚠️ Touch-only size budget

These components grew past the touch-only budget (2 KB gzip JavaScript or 1 KB gzip CSS per component). If the growth runs only on a coarse pointer, load its JavaScript lazily so the import does not carry it, or justify the cost in the pull request description; CSS stays in the package stylesheet, so justify it.

Component JavaScript CSS
core/DateInput +26.7KB ⚠️ +313B
core/DateTimeInput +26.9KB ⚠️ +312B
core/PowerSearch +27.8KB ⚠️ +298B
core/TimeInput +26.4KB ⚠️ +363B

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 33.7KB 33.9KB +254B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

48 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 556,900
RichTextEditor Markdown parity: side by side probe light 538,816
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 28 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold until an engineering owner approves or rejects the draft requirements in AST-094 (FR1–FR23) and the iOS picker look and motion this pull request gives the TimeInput, DateInput and DateTimeInput wheels; an engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and you may do so yourself if you are an engineering owner. Independently of that decision, these changes are needed:

  • Split the 29,256 authored lines into smaller pull requests, landing the animator, projection and unused recognizers with their first consumer.
  • Keep dateInputTouchSizes.wheelItemSize and dateInputTouchGeometry.wheelEdgePadding in DateInput/tokens.stylex or ship their removal as [breaking], derive the wheel's resting row during render instead of in a layout Effect, and remove the gesture README section that documents a touchPresentation module that does not exist.

[Automated review]

Comment thread packages/core/src/DateInput/Wheel.tsx Outdated
Comment on lines +438 to +439
// eslint-disable-next-line @eslint-react/set-state-in-effect -- the active row follows the wheel's resting offset, which only exists in the motion engine
setRestIndex(selectedIndex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This layout Effect stores selectedIndex, a value derived from props, so the listbox renders once with the previous active row and is repaired afterwards; the reason names the component's own motion engine rather than an external system. Derive the resting row during render when selectedIndex changes and the wheel is idle, and keep only motion.setOffset here (architecture react-component-runtime INV1, INV3).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f), on which this branch now sits: the active row derives from the committed value during render; the effect only syncs the motion engine's offset. Test: "points the active row at a value set from outside in the same commit".

* column of values, not as a stack of buttons with air between them. At
* this height ~9 options are on screen.
*/
wheelItemSize: '28px',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateInputTouchSizes.wheelItemSize is part of the released public subpath @astryxdesign/core/DateInput/tokens.stylex; removing it breaks consumers and swizzled wheels. Keep it (and wheelEdgePadding) with its released value, or ship the removal as [breaking] with a migration to wheelGeometry (AST-017 FR7).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f): both released keys stay in tokens.stylex.ts (unchanged from main), and the touch geometry lives in the unexported wheelGeometry.stylex.ts.

## Touch presentation

Whether a component presents itself the touch (UIKit) way is answered once,
in `../utils/touchPresentation.ts`:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../utils/touchPresentation.ts, TOUCH_QUERY, COMPACT_QUERY, COMPACT_TOUCH_QUERY, useTouchPresentation(), readTouchPresentation() and useMotionPresentation() do not exist in this pull request; remove this section or land the module with it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head the module exists: the branch sits on #8076 (1203525), whose utils/touchPresentation.ts exports TOUCH_QUERY, COMPACT_QUERY, COMPACT_TOUCH_QUERY, useTouchPresentation and readTouchPresentation, and touchPresentation.test.ts checks the queries' spelling. The README section is #8076's.

Comment thread .github/workflows/ci.yml Outdated
--output tooltip-hover-bridge-output

# A picker wheel dragged onto a row outside its bounds must come to rest
# on the nearest row inside them. The stop comes from native scrolling,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wheel no longer rests through native scrolling, CSS snapping or scrollend; it rests through its recognizers and motion engine. Reword this comment so the contract step describes what it proves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded: on the touch column (a finger) the stop comes from real touch input through the gesture system; on today's column (a mouse) from native scrolling, CSS snapping and the end-of-scroll signal. The step runs both.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold until an engineering owner approves or rejects the draft spec:AST-094 requirements (FR1–FR23 and DEC-1/DEC-2, which carry the wheels' UIPickerView look and motion) by commenting /approve-spec <full head SHA> on this pull request; the author may record that decision too if they are an engineering owner. Independent of that decision, these changes are needed:

  • Retarget onto the open #8159 stack, or land it first, so this pull request merges only its own one-picker change instead of 29,324 lines.
  • Keep the released dateInputTouchSizes.wheelItemSize and dateInputTouchGeometry.wheelEdgePadding tokens, or ship their removal as [breaking] with a migration, and tag the new look [feat] (AST-017 FR3, FR7).
  • Derive the wheel's active row during render instead of repairing it in the layout effect at Wheel.tsx:430-440 (react-component-runtime INV1).
  • Remove or fix the docs that describe modules missing from the tree (gestures/README.md:335-356, motion/MEASUREMENTS.md), the stale WheelRest comment in ci.yml, and the spec:AST-090 citations.

[Automated review]

Comment thread packages/core/src/DateInput/Wheel.tsx Outdated
motion.setOffset(position * ROW_HEIGHT);
}
// eslint-disable-next-line @eslint-react/set-state-in-effect -- the active row follows the wheel's resting offset, which only exists in the motion engine
setRestIndex(selectedIndex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This layout Effect copies the prop-derived selectedIndex into restIndex, so after an outside value change the first render points aria-activedescendant (line 807) at the old row and this Effect repairs it with a second render. architecture:react-component-runtime INV1 forbids rendering a known-wrong state and repairing it in an Effect; the lint reason names the motion engine, but the stored value does not come from it. Keep the rest row together with the value it belongs to and derive the active row during render, leaving only motion.setOffset here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f): no restIndex state any more; the active descendant derives from the committed value during render.

* column of values, not as a stack of buttons with air between them. At
* this height ~9 options are on screen.
*/
wheelItemSize: '28px',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateInputTouchSizes.wheelItemSize (and dateInputTouchGeometry.wheelEdgePadding below) ship in 0.7.4 at @astryxdesign/core/DateInput/tokens.stylex, a path exported since 0.6.7 for swizzled components. Removing them under a [fix] patch Changeset contradicts spec:AST-017 FR3 and FR7: keep them as deprecated aliases, or ship the removal as [breaking] with a migration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f): tokens.stylex.ts is unchanged from main; a test keeps it to its released names.

## Touch presentation

Whether a component presents itself the touch (UIKit) way is answered once,
in `../utils/touchPresentation.ts`:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../utils/touchPresentation.ts, TOUCH_QUERY, COMPACT_QUERY, COMPACT_TOUCH_QUERY, useTouchPresentation(), readTouchPresentation() and useMotionPresentation() do not exist in this tree, and no test enforces them. Drop this section or land the module first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head the module exists: the branch sits on #8076 (1203525), whose utils/touchPresentation.ts exports TOUCH_QUERY, COMPACT_QUERY, COMPACT_TOUCH_QUERY, useTouchPresentation and readTouchPresentation, and touchPresentation.test.ts checks the queries' spelling. The README section is #8076's.

Comment thread .github/workflows/ci.yml Outdated
--output tooltip-hover-bridge-output

# A picker wheel dragged onto a row outside its bounds must come to rest
# on the nearest row inside them. The stop comes from native scrolling,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wheel no longer stops through native scrolling, CSS snapping or the end-of-scroll signal: at this head WheelMotion plays the release and snap curves. Describe the engine's stop instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded: on the touch column (a finger) the stop comes from real touch input through the gesture system; on today's column (a mouse) from native scrolling, CSS snapping and the end-of-scroll signal. The step runs both.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No spec:AST-090 exists on main or at this head, so this departure (and the amendment at line 235) points at nothing. Cite the record meant, or drop the note.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AST-094's spec.md is not in this pull request's commit: it comes from #8052 (now superseded by #8316). The missing AST-090 citation is with that record's owner.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold until an engineering owner approves or rejects the draft spec:AST-094 requirements (FR1–FR23 and DEC-1/DEC-2, which carry the wheels' UIPickerView look and motion) by commenting /approve-spec <full head SHA> on this pull request; the author may record that decision too if they are an engineering owner. Independent of that decision, these changes are needed:

  • Retarget onto the open #8159 stack, or land it first, so this pull request merges only its own one-picker change instead of 29,172 lines.
  • Keep the released dateInputTouchSizes.wheelItemSize and dateInputTouchGeometry.wheelEdgePadding tokens, or ship their removal as [breaking] with a migration, and tag the new look [feat] (AST-017 FR3, FR4, FR7).
  • Derive the wheel's active row during render instead of repairing it in the layout Effect at Wheel.tsx:430-440 (react-component-runtime INV1).
  • Remove or fix the docs that describe modules missing from the tree (gestures/README.md:335-356, motion/MEASUREMENTS.md, the sources.test.ts headers), the stale WheelRest comment in ci.yml, and the spec:AST-090 citations.

[Automated review]

Comment thread packages/core/src/DateInput/Wheel.tsx Outdated
motion.setOffset(position * ROW_HEIGHT);
}
// eslint-disable-next-line @eslint-react/set-state-in-effect -- the active row follows the wheel's resting offset, which only exists in the motion engine
setRestIndex(selectedIndex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This layout Effect copies the prop-derived selectedIndex into restIndex, so after an outside value change the first render points aria-activedescendant (line 807) at the previous row while aria-selected (line 829) already marks the new one, and a second render repairs it. architecture:react-component-runtime INV1: "A component MUST NOT render a known-wrong initial state and repair it in a follow-up Effect". The stored value comes from props, not from the motion engine the lint reason names. Keep the rest row with the value it belongs to (for example {index, value} in state), derive the active row during render when they differ, and leave only motion.setOffset here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f): the active descendant derives from the committed value during render (one commit after an outside change, checked with a React Profiler), and the effect keeps only motion.setOffset.

* column of values, not as a stack of buttons with air between them. At
* this height ~9 options are on screen.
*/
wheelItemSize: '28px',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateInputTouchSizes.wheelItemSize (and dateInputTouchGeometry.wheelEdgePadding below) ship in 0.7.4 at @astryxdesign/core/DateInput/tokens.stylex (dist/DateInput/tokens.stylex.d.ts lines 49 and 61). Removing them breaks code that reads them, which spec:AST-017 FR3, FR4 and FR7 make a [breaking] change, while the Changesets declare [fix] patch. Keep both keys with their released values beside wheelGeometry, or declare the removal [breaking] with a migration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f): wheelItemSize and wheelEdgePadding stay with their released values (today's column uses them); wheelGeometry is in an unexported module.

Comment thread .changeset/wheel-uikit-picker.md Outdated
'@astryxdesign/core': patch
---

[fix] The picker wheels in the TimeInput, DateTimeInput and DateInput bottom sheets now look and move like iOS's own picker. The rows sit on a cylinder seen in perspective, with one capsule band across every column and the row inside it magnified. A flick coasts at UIKit's deceleration onto the row UIKit's projection picks. A finger landing on a moving wheel stops it. A tap on a row turns it onto the band. Past the first or last row, a drag rubber-bands and the wheel bounces back. As on iOS, the value commits when the wheel comes to rest, and the coast keeps running while the page's main thread is busy. Arrow keys and clicks on a row still commit at once.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This describes an intentional new look and motion, which spec:AST-017 FR3 tags [feat] ("[fix] when it restores current visual authority and [feat] when it intentionally changes visual direction"); no current record states the UIPickerView look yet. The same applies to .changeset/wheel-group-inputs.md.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retagged [feat]: the touch look is a new visual direction (AST-017 FR3). .changeset/wheel-group-inputs.md is [feat] too.


## Touch presentation

Whether a component presents itself the touch (UIKit) way is answered once,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../utils/touchPresentation.ts, TOUCH_QUERY, COMPACT_QUERY, COMPACT_TOUCH_QUERY, useTouchPresentation(), readTouchPresentation() and useMotionPresentation() do not exist at this head, and no test enforces the query spelling. Drop this section until the resolver lands, or land the module with it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head the module exists: the branch sits on #8076 (1203525), whose utils/touchPresentation.ts exports TOUCH_QUERY, COMPACT_QUERY, COMPACT_TOUCH_QUERY, useTouchPresentation and readTouchPresentation, and touchPresentation.test.ts checks the queries' spelling. The README section is #8076's.

Comment thread .github/workflows/ci.yml Outdated
--output tooltip-hover-bridge-output

# A picker wheel dragged onto a row outside its bounds must come to rest
# on the nearest row inside them. The stop comes from native scrolling,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head the wheel no longer stops through native scrolling, CSS snapping or the end-of-scroll signal: WheelMotion plays the release and snap curves and reports arrival. Reword the comment to describe what the contract now exercises.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded: on the touch column (a finger) the stop comes from real touch input through the gesture system; on today's column (a mouse) from native scrolling, CSS snapping and the end-of-scroll signal. The step runs both.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No spec:AST-090 exists on main or at this head, so this departure (and the amendment listed at line 235) points at nothing. Cite the record meant, or drop the note.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AST-094's spec.md is not in this pull request's commit: it comes from #8052 (now superseded by #8316). The missing AST-090 citation is with that record's owner.

@vjeux
vjeux force-pushed the uikit/c21-wheel-inputs branch from 7e49b08 to ca05d39 Compare October 24, 2026 20:59
@vjeux vjeux changed the title feat(DateInput, TimeInput, DateTimeInput): the sheets' wheels draw as one picker feat(DateInput, TimeInput, DateTimeInput): the sheets' wheels draw as one picker in the iOS idiom Oct 24, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold until an engineering owner approves or rejects the draft spec:AST-094 requirements (FR1–FR23, DEC-1 and DEC-2, which carry the wheels' iOS look and motion) and identifies the record that governs the new motion/experimental and gestures/experimental subpaths and the theme motion roles. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may record it too as an engineering owner. Independent of that decision, these changes are needed:

  • Retarget onto the #8159 branch, or land that stack first, so this pull request merges only its 343-line one-picker change.
  • Resolve compact as width below the theme's md point or height below 500 instead of 744 px (public-component-api INV17, AST-012 FR9).
  • Remove when.platform or have it added to AST-012 FR2, and make FR4 and DEC-1 match the themable motion roles or drop the roles.
  • Derive the wheel's active row during render instead of repairing it in the layout Effect at PickerWheel.tsx:362-372 (react-component-runtime INV1).
  • Keep the experimental hooks out of stable astryx search output (AST-017 FR27), make Home and End reach the ends while the wheel moves, tag the new look [feat], and fix the references to missing files and spec:AST-090.

[Automated review]

export const TOUCH_QUERY = '(pointer: coarse)';

/** The viewport is compact: layout behaviours. */
export const COMPACT_QUERY = '(width < 744px), (height < 500px)';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This edge contradicts architecture:public-component-api INV17 ("uses the theme's compact definition: width below the effective md point or height below 500 … It keeps no compact numbers of its own") and spec:AST-012 FR9, which names the web touch presentation resolver. At 760 × 1024 with a coarse pointer, MoreMenu presentation="adaptive" now opens a popover where current spec gives a sheet. Resolve compact from the theme's effective md point (768 by default) or a height below 500, and update touchPresentation.test.ts:328, the docs and the README that repeat 744.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this pull request's commits: utils/touchPresentation.ts is #8076's. At #8076 1203525, which this branch now sits on, the resolver reads the theme's compact definition (DEFAULT_COMPACT_DEFINITION, AST-012 FR9) and keeps no numbers of its own.

* is everything else. Pair `ios` with `pointer: 'coarse'` for a touch
* profile, such as touch motion tuned per platform.
*/
platform?: 'ios' | 'other';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spec:AST-012 FR2 lists width, height, pointer, contrast and motion, and says "Future environment fields require explicit additions to this closed vocabulary." Remove platform, or have an AST-012 owner add it to FR2 before it ships on the exported ThemeAdaptationCondition.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this pull request's commits: themeAdaptations.ts and its platform field come from #8076's stack. The FR2 amendment or the removal belongs to that change.

Comment on lines +362 to +372
useIsomorphicLayoutEffect(() => {
if (!isActive || dragRef.current != null || motion.isMoving()) {
return;
}
const position = nearestPosition(selectedIndex, motion.offset(), layout);
if (Math.abs(motion.offset() - position * ROW_HEIGHT) > 0.01) {
motion.setOffset(position * ROW_HEIGHT);
}
// eslint-disable-next-line @eslint-react/set-state-in-effect -- the active row follows the wheel's resting offset, which only exists in the motion engine
setRestIndex(selectedIndex);
}, [isActive, selectedIndex, layout, motion]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After an outside value change the first render points aria-activedescendant (line 757) at the previous row while aria-selected (line 779) marks the new one, and this layout Effect repairs it with a second render; architecture:react-component-runtime INV1 forbids that. Keep the rest row with the value it belongs to (for example {index, value} in state), derive the active row during render when they differ, and leave only motion.setOffset here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f), which this branch sits on: there is no restIndex state. The active descendant derives from the committed value during render, and the effect only syncs the motion engine's offset. The case "points the active row at a value set from outside in the same commit" uses a React Profiler to check for one commit.

Comment on lines +735 to +742
case 'Home':
event.preventDefault();
moveBy(-restIndex);
break;
case 'End':
event.preventDefault();
moveBy(count - 1 - restIndex);
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moveBy steps from motion.target(), the row a running coast is heading to, so Home pressed while the wheel coasts from row 10 to row 20 lands on row 10, and End misses the same way on an upward coast. Move to the first and last enabled rows directly instead of a delta from restIndex.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #8159 (adb185f), which this branch sits on: Home and End go to the first and last enabled rows wherever the wheel is heading. The case "takes Home and End to the first and last enabled rows, even mid-coast" fails before the change.

Comment thread .changeset/wheel-uikit-picker.md Outdated
'@astryxdesign/core': patch
---

[fix] On iOS and iPadOS, under a finger, the picker wheels in the TimeInput, DateTimeInput and DateInput bottom sheets now look and move like iOS's own picker. The rows sit on a cylinder seen in perspective, and the row in the capsule band is magnified. A flick coasts at UIKit's deceleration onto the row UIKit's projection picks. A finger landing on a moving wheel stops it. A tap on a row turns it onto the band. Past the first or last row, a drag rubber-bands and the wheel bounces back. As on iOS, the value commits when the wheel comes to rest, and the coast keeps running while the page's main thread is busy. Arrow keys and clicks on a row still commit at once. A theme can retune the tap spin's duration (`picker.tapSpin.durationMs`) and curve (`timing.curves.easeInOut`) and the bounce at an end (`scroll.bounce`). On Android and with a mouse the wheels are unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an intentional new look and motion, which spec:AST-017 FR3 tags [feat] ("[fix] when it restores current visual authority and [feat] when it intentionally changes visual direction"); the same applies to .changeset/wheel-group-inputs.md.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: .changeset/wheel-uikit-picker.md and .changeset/wheel-group-inputs.md are both [feat].

Comment on lines +73 to +80
- **FR4 — Physics are not themable in touch presentations.** A touch
presentation MUST read its motion from the table on every theme. Themes keep
color, shape, type and spacing; the theme's duration and easing tokens keep
governing pointer presentations. **Proposed; pending owner review:** this
narrows `architecture:theme-tokens` (owners: cixzhang, imdreamrunner) to
pointer presentations, and departs from the draft `design:motion` DR3
(owners: ernestt, cixzhang) wherever UIKit settles with an overshoot or
stretches with a rubber band.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request also ships defineTheme({motion: {springs, durations, curves}}) and the wheel reads picker.tapSpin.durationMs, timing.curves.easeInOut and scroll.bounce through the theme, so FR4 (and DEC-1's rejection of per-theme tuning) says the opposite of the code. Make the record state the themable roles, or drop the roles.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this pull request's commits: AST-094's spec.md comes from #8052, now superseded by #8316, and these points are with that record's owner.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No spec:AST-090 exists on main or at this head, so this departure and the amendment listed at line 235 point at nothing. Cite the record meant, or drop the note.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this pull request's commits: AST-094's spec.md comes from #8052, now superseded by #8316, and these points are with that record's owner.

@@ -1,4 +1,24 @@
Results for "experimental" (3)
Results for "experimental" (7)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The four new results are experimental hooks in stable astryx search output with no opt-in. spec:AST-017 FR27 says "An omitted opt-in MUST leave stable defaults, output, routes, data, and behavior unchanged", and experimental-isolation.test.mjs calls a golden diff that names experimental the leak it exists to catch. Keep the hooks out of stable search unless a marked experimental route carries them, and restore this golden.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this pull request's commits: those search results and the golden come from #8076's stack, and that change carries the fix.

where a switch outside any scroll view is needed.

Done (iOS 26.2 simulator, Oct 23 2026; in the switch's own table,
`uikit/switch.ts`): M-SW02, M-SW03, M-SW04, M-SW05, M-SW06, M-SW09 (UISwitch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uikit/switch.ts (and uikit/contextMenu.ts at line 254) are not in this tree; the table headers in uikit/scroll.ts, springs.ts and timing.ts likewise name a sources.test.ts that does not exist (the check is uikit/uikit.test.ts). Fix the references or drop them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not this pull request's lines: the switch and context-menu references and the table headers are #8076's, and that change carries the fix. This pull request adds only the picker section.

@vjeux
vjeux force-pushed the uikit/c21-wheel-inputs branch from ca05d39 to e16c969 Compare October 25, 2026 00:39
vjeux and others added 24 commits October 25, 2026 18:03
…the next touch

The hook rebuilds its tracker when trackingExpansion, highlightDelay,
cancelTravel or quickTapFlash change, but the test that pinned it was lost
in a merge. Restored: a control rerendered with a cancel travel cancels a
later touch that moves past it. With cancelTravel left out of the
rebuild's dependencies, it fails.
Her arena work (the touch depth, a view's delivery only to involved
recognizers, the reset count, touchBeganElsewhere's delivery, the
simultaneity rule) reaches the recognizer's plumbing through the internal
view, as this PR keeps it out of the public type; the new
deliverBeganElsewhere joins that view.
…lved, main) into the gesture hook fixes

# Conflicts:
#	packages/core/src/gestures/GestureArena.ts
#	packages/core/src/gestures/GestureRecognizer.ts
Her foundation fixes for the review items this PR also answered take her
form: the swipe's segment reading with directions ruled out for good, the
tap's lift centroid, the claim release that counts a recognized discrete
gesture, Hover's restart and the idle recognizer's comment. Both sides'
tests stay: her spacing, 40-degree, re-basing and settling cases beside
this PR's 20-degree, re-based release and mid-hover cases.
…ePressTracker's restored option-change test) into meta#8315
…ades

On a touch screen, or after a touch drag, the wheel's own scrolls cross-fade
with the rows landing while they are faded out (design:reduced-motion DR2);
otherwise they land at once (DR3). "With a mouse or keyboard" read as if a
key step on a touch screen landed at once.
…uikit/c21-wheel-nearest-valid

# Conflicts:
#	.github/workflows/ci.yml
… they add

picker.tapSpin.durationMs is a role defineTheme accepts and astryx docs motion
lists, a compatible capability, so the entry is a [feat] that names it first
(spec:AST-017 FR7) instead of an internal [chore].
…uikit/c21-wheel-math

# Conflicts:
#	packages/core/src/motion/README.md
…urves.move

spec:AST-094 FR4 names the table's roles by what they do: the base now
calls Core Animation's ease-in-ease-out timing.curves.move (the same
control points, 0.42, 0, 0.58, 1). The tap spin's default curve and the
picker table's notes read that role. With the old name the tap spin baked
an undefined curve: at the merge, wheelPhysics.test.ts's tap spin and three
of wheelDrum.test.ts's looping cases failed.
…e base

The base's motion docs topic and this branch's picker role (the tap spin's
duration, picker.tapSpin.durationMs) each moved the topic's skeleton; the
merge kept the base's golden. Regenerated with
`vitest run packages/cli/test/experimental-isolation.test.mjs --update`:
only the motion topic's skeleton hash changes, and no experimental surface
is named.
…gnizer and compositor curves

The picker wheel is a native scroll-snap column. WebKit gives every
scroll-snapping scroller the fast deceleration rate (0.99/ms) while
UIPickerView decelerates at the normal rate (0.998) onto a row it picks from
its own projection, and a native scroll's momentum cannot be caught or held
to a curve. Under a finger, on every touch device (one touch model, iOS and
Android alike), the Wheel is now UIPickerView's, PickerWheel.tsx, on the
gesture system and the picker's values in the UIKit constants table:

- A ScrollViewPanGestureRecognizer, the pan a scroll view uses, drags the
  rows, on UIKit's rubber band past an end. The lift plays UIKit's release
  plan (wheelPhysics.ts) from a scroll view's release velocity (the pan's
  last two, blended as UIPickerView's); a finger landing on a moving wheel
  stops it where it is; a tap turns a row onto the band. Hidden mid-drag, the wheel
  drops the drag uncommitted and parks the committed row when shown again.
- Each motion plays through wheelMotion.ts: per-row Web Animations on the
  compositor, each row turned onto the cylinder, with a selection tick per row
  reached where the browser can vibrate.
- The tap spin's duration and curve and the bounce at an end are the theme's
  motion roles (picker.tapSpin.durationMs, timing.curves.easeInOut,
  scroll.bounce), resolved against the column as each motion starts.
- The value commits when the wheel comes to rest; keys and clicks commit at
  once and turn the wheel. Home and End go to the first and last enabled
  row wherever the wheel is heading. The listbox's active descendant is the
  committed row, derived during render.
- Under reduced motion (design:reduced-motion) every transition the wheel
  starts lands without travel: the rows cross-fade onto their row on the
  animator's Reduce Motion fade (reducedMotionFade, WheelMotion.crossFade),
  whatever curve it was asked for; the drag itself follows the finger.
- isLooping, resolveRest and a spinTo(index) handle for the date and time
  wheels.
- WheelGroup: one band and one vanishing point across picker columns. A Wheel
  on its own draws its own band. The band is decoration, so the frame's (or
  the lone column's) ::before draws it, marked data-wheel-band (INV7). The vanishing point follows the shared
  resize observer.
- A held finger never starts text selection or the callout: the pan asks the
  gesture host for it (preventsTextSelection). The column keeps the pointer
  of a finger that lands on it, as it keeps the touch: a BottomSheet would
  otherwise arm its pull-down on the pointer and follow the first 8 px of a
  downward drag.

The switch reads useTouchPresentation. A wheel shows only on a sheet, so a
caller who pins the fine surface gets no sheet and no wheel
(architecture:public-component-api INV16). Under a mouse or other fine
pointer Wheel keeps today's scrolling column, now named ScrollWheel and
otherwise unchanged (the released DateInput tokens stay as they are; the
touch geometry is in the unexported wheelGeometry.stylex.ts); there spinTo
commits at once, isLooping and resolveRest do nothing, and WheelGroup
renders its children alone. PickerWheel.tsx is the touch layer, imported by
Wheel.tsx alone. The shared types and nearestEnabledIndex move to
wheelOptions.ts.
…s coarse

The gesture host takes input only where the primary pointer is coarse
(spec:AST-094 FR19, the gesture line's coarse-pointer gate), and the touch
column draws only there. PickerWheel.test.tsx ran under jsdom's fine pointer,
so on that base every touch case saw no input: 11 of 30 failed. Each case now
runs under a coarse primary pointer, as on a phone or a tablet; the mouse
cases are a mouse on a touch device, a tablet's trackpad.
The base renamed Core Animation's ease-in-ease-out to timing.curves.move
(spec:AST-094 FR4, the same control points). The tap spin resolves that
role under the column's theme, and the docs and the release note name it.
With the old name the spin resolved an undefined curve, and 13 of
PickerWheel.test.tsx's cases that turn the wheel failed at the merge.
…nLock

The base replaced a recognizer's preventsTextSelection with the selectionLock
option (spec:AST-094 FR41): a recognizer whose hold does something passes
selectionLock: lockSelection from gestures/selectionLock.ts. The wheel's pan
is such a recognizer (a held finger turns the wheel), so it keeps the lock.
… one picker under a finger

The month and year wheels of the DateInput sheet and the time wheels of the
TimeInput and DateTimeInput sheets sit in a WheelGroup. Under a finger, on
every touch device, one selection band spans every column, and each
column's dimmed rows lean toward the picker's centre, as UIPickerView draws
them. Under a mouse or other fine pointer the group renders the columns
alone, so the sheets keep today's layout; the sheets' own styles are
unchanged (the DateTimeInput time row keeps its gutter between today's
columns: a Chromium check under a mouse holds every gap to 8 px or more).
@vjeux
vjeux force-pushed the uikit/c21-wheel-inputs branch from 17a6cee to 9a40046 Compare October 26, 2026 02:52
@vjeux

vjeux commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Nothing is left in this pull request, so it closes. Adding an extra load on mobile is extremely costly, so the touch column stays a static import behind isTouch rather than the lazy load INV6 asks for: DateInput grows by 24.2 KB gzip, about 16.7 KB of it gesture code that every touch layer shares and a page pays once (#8159's description). That departure from INV6 is the author's position as an engineering owner; the record and the merge stay yours.

Where its parts went:

@vjeux vjeux closed this Oct 26, 2026
@vjeux
vjeux deleted the uikit/c21-wheel-inputs branch October 26, 2026 02:59
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
meta#8161 is folded into meta#8159, which draws today's look under a finger:
there is no WheelGroup, so the time wheels render their columns
directly, as the sheets' other wheels do, and the browser spec drags by
`WHEEL_ROW_PITCH`.

This branch was successfully deployed

1 active deployment
Preview — 9a40046b Deployed Oct 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants