Skip to content

fix(DateInput, DateTimeInput): the month/year wheels move the date like UIDatePicker's - #8209

Draft
vjeux wants to merge 2189 commits into
meta:mainfrom
vjeux:uikit/c22-month-year-wheels
Draft

vjeux wants to merge 2189 commits into
meta:mainfrom
vjeux:uikit/c22-month-year-wheels

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8206 and #8162. Review only this PR's change: vjeux/astryx@c4d5c04...46e8eae

User need

Under a finger, the DateInput and DateTimeInput sheets' calendar title opens month and year wheels. Today they only move the calendar: after turning to May the user still has to tap a day. UIDatePicker's inline calendar moves the date itself (measured for #8178). One touch model (spec:AST-094 FR13); a mouse keeps today's wheels.

Evidence

Recorded on the iOS 26.2 simulator and the Android emulator (Chromium 158) from these heads; Overlays: JS clock, Reduce Motion, finger. No real device was available to the test rig. Before: #8206 + #8162.

Path before this PR
Month rows 4, then 3, below
… on Android (the line beside April: emulator compositing, no element; iOS has none)
Year 2025 with min November 1, 2025

Before: the month list ends at December, so both taps land past it; the year moves only the calendar, to November 2025, the field at October 15. This PR: October to February across the December/January loop, then May, the field reading May 15, 2026; the year moves the date to November 15, 2025, the month wheel turning onto November.

Problem and solution fit

  • The date moves. A chosen date moves to the wheels' month and keeps its day: October 15 becomes May 15. A day the month lacks becomes its last day (January 31 → February 28) within min and max; DateTimeInput keeps the time. With no date chosen, or one dateConstraints refuses, the wheels only move the calendar.
  • Looping. The month wheel loops, December to January, without changing the year (UIKit's measured loop).
  • Bounds. A year past the range goes on to its nearest reachable month, the month wheel turning onto it (0.3 s sinusoid; a cross-fade under Reduce Motion). Opening the wheels never changes the value.

Current specification

Settled (cixzhang, Oct 25): spec:AST-094 DEC-1, with FR11 and FR14 amending DR1, and spec:AST-025 FR14 (the wheels commit live). spec:AST-094 FR1, FR2 source the rules (datePicker.calendar.monthYearWheels, wheels.dayClampsToMonth).

Public delta and caller burden

Design and alternatives

dateInMonth (monthGeometry.ts) holds the rule as a pure function, shared by both sheets.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

Stack (merge in this order): … → #8178 → #8179 → #8146 → #8496 → #8200 → #8206 → #8162 → 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 30, 2026 12:49am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 1:09 AM UTC

@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

DateInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 266B 266B 0B
Lines of Code 3532 6837 -
Complexity Very High Very High (617) -
DateTimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 286B 286B 0B
Lines of Code 3609 3792 -
Complexity Very High Very High (570) -
TimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 411B 411B 0B
Lines of Code 1245 1728 -
Complexity Very High Very High (204) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/AlertDialog 56.9KB 57.2KB +294B 12.4KB 12.4KB 0B
core/AppShell 57.9KB 58.1KB +242B 11.6KB 11.6KB 0B
core/AvatarGroupOverflow 39.9KB 40.2KB +303B 6.4KB 6.4KB 0B
core/Banner 47.8KB 48.1KB +289B 9.9KB 9.9KB 0B
core/BaseTypeahead 54.3KB 54.6KB +251B 9.9KB 9.9KB 0B
core/BottomSheet 34.0KB 33.9KB -9B 7.9KB 7.9KB -22B
core/BottomSheetSwitcher 14.8KB 14.8KB 0B 3.3KB 3.3KB 0B
core/BreadcrumbItem 66.4KB 66.6KB +297B 11.0KB 11.0KB 0B
core/Button 43.3KB 43.5KB +273B 9.2KB 9.2KB 0B
core/Calendar 54.9KB 55.2KB +290B 10.0KB 10.0KB 0B
core/Carousel 50.2KB 50.5KB +316B 10.2KB 10.2KB 0B
core/ChatComposer 60.6KB 60.9KB +270B 10.8KB 10.8KB 0B
core/ChatComposerInput 55.7KB 56.0KB +264B 10.1KB 10.1KB 0B
core/ChatComposerTokenElement 55.7KB 56.0KB +264B 10.1KB 10.1KB 0B
core/ChatDictationButton 46.5KB 46.8KB +264B 9.5KB 9.5KB 0B
core/ChatLayout 48.8KB 49.1KB +273B 9.9KB 9.9KB 0B
core/ChatLayoutScrollButton 46.4KB 46.6KB +255B 9.6KB 9.6KB 0B
core/ChatSendButton 45.8KB 46.1KB +265B 9.2KB 9.2KB 0B
core/CheckboxInput 49.9KB 50.2KB +273B 9.3KB 9.3KB 0B
core/CheckboxListItem 54.0KB 54.3KB +244B 10.5KB 10.5KB 0B
core/ClickableCard 12.8KB 13.1KB +289B 6.9KB 6.9KB 0B
core/CodeBlock 60.5KB 60.8KB +278B 10.7KB 10.7KB 0B
core/Collapsible 11.7KB 12.0KB +291B 4.2KB 4.2KB 0B
core/CommandPalette 50.9KB 51.2KB +281B 11.8KB 11.8KB 0B
core/CommandPaletteItem 8.4KB 8.7KB +280B 3.5KB 3.5KB 0B
core/ComplexSelector 55.8KB 56.1KB +318B 10.6KB 10.6KB 0B
core/ContextMenu 77.7KB 78.0KB +275B 14.6KB 14.6KB -13B
core/ContextMenuItem 16.6KB 16.8KB +280B 5.2KB 5.2KB 0B
core/DateInput 99.9KB 146.8KB +46.9KB ⚠️ 14.8KB 15.1KB +355B
core/DateRangeInput 69.4KB 69.6KB +266B 11.2KB 11.2KB 0B
core/DateTimeInput 110.1KB 159.3KB +49.2KB ⚠️ 15.2KB 15.7KB +444B
core/DialogHeader 48.8KB 49.1KB +280B 9.8KB 9.8KB 0B
core/DrawerHeader 48.9KB 49.2KB +249B 11.2KB 11.2KB 0B
core/DropdownMenu 72.7KB 73.0KB +277B 14.7KB 14.7KB -12B
core/DropdownMenuCheckboxItem 16.1KB 16.4KB +284B 5.6KB 5.6KB 0B
core/DropdownMenuItem 15.4KB 15.7KB +287B 5.2KB 5.2KB 0B
core/DropdownMenuRadioItem 16.2KB 16.5KB +275B 5.6KB 5.6KB 0B
core/DropdownMenuSubMenu 58.1KB 58.3KB +270B 9.5KB 9.5KB 0B
core/FileInput 54.1KB 54.4KB +262B 10.3KB 10.3KB 0B
core/IconButton 43.3KB 43.6KB +273B 9.2KB 9.2KB 0B
core/Item 11.4KB 11.6KB +273B 4.7KB 4.7KB 0B
core/LayerProvider 63.3KB 63.6KB +288B 10.1KB 10.1KB 0B
core/Lightbox 51.2KB 51.4KB +249B 9.8KB 9.8KB 0B
core/Link 43.5KB 43.8KB +288B 7.6KB 7.6KB 0B
core/ListItem 53.5KB 53.7KB +268B 10.5KB 10.5KB 0B
core/Markdown 144.5KB 144.7KB +247B 17.4KB 17.3KB -14B
core/MobileNav 51.0KB 51.3KB +264B 10.1KB 10.1KB 0B
core/MobileNavToggle 46.2KB 46.5KB +259B 9.4KB 9.4KB 0B
core/MoreMenu 72.9KB 73.2KB +278B 14.7KB 14.7KB -12B
core/MultiSelector 76.2KB 76.4KB +270B 14.6KB 14.6KB -11B
core/NumberInput 55.7KB 56.0KB +278B 10.7KB 10.7KB 0B
core/Outline 28.6KB 28.9KB +290B 4.0KB 4.0KB 0B
core/Pagination 87.7KB 88.0KB +263B 15.3KB 15.3KB -17B
core/Popover 49.8KB 50.1KB +238B 12.1KB 12.1KB -9B
core/PowerSearch 155.1KB 203.6KB +48.5KB ⚠️ 18.6KB 19.0KB +384B
core/RadioListItem 52.1KB 52.4KB +286B 9.7KB 9.7KB 0B
core/SegmentedControlItem 8.6KB 8.9KB +285B 3.9KB 3.9KB 0B
core/SelectableCard 12.8KB 13.1KB +294B 7.1KB 7.1KB 0B
core/Selector 79.1KB 79.4KB +267B 15.0KB 15.0KB -14B
core/SelectorOption 14.5KB 14.8KB +251B 5.1KB 5.1KB 0B
core/SideNav 58.0KB 58.3KB +272B 10.6KB 10.6KB 0B
core/SideNavCollapseButton 46.6KB 46.9KB +242B 9.5KB 9.5KB 0B
core/SideNavHeading 54.7KB 55.0KB +248B 10.1KB 10.1KB 0B
core/SideNavItem 54.5KB 54.8KB +266B 10.1KB 10.1KB 0B
core/Step 32.4KB 32.7KB +279B 5.3KB 5.3KB 0B
core/Stepper 47.4KB 47.7KB +265B 9.6KB 9.6KB 0B
core/Switch 48.4KB 48.7KB +285B 9.3KB 9.3KB 0B
core/Tab 9.7KB 10.0KB +294B 4.1KB 4.1KB 0B
core/TabMenu 51.6KB 51.9KB +276B 10.0KB 10.0KB 0B
core/Table 84.0KB 84.2KB +253B 15.1KB 15.1KB -12B
core/TableCell 78.8KB 79.1KB +267B 14.8KB 14.8KB -13B
core/TableHeaderCell 78.6KB 78.8KB +272B 14.8KB 14.8KB -13B
core/TableSelectionToolbar 52.4KB 52.6KB +278B 11.2KB 11.2KB 0B
core/TextInput 53.5KB 53.8KB +281B 10.5KB 10.5KB 0B
core/Thumbnail 50.7KB 50.9KB +274B 11.0KB 11.0KB 0B
core/TimeInput 86.8KB 116.5KB +29.7KB ⚠️ 13.8KB 13.8KB +19B
core/Timestamp 27.5KB 27.5KB -1B 9.5KB 9.5KB 0B
core/Toast 59.8KB 60.1KB +258B 9.7KB 9.7KB 0B
core/ToggleButton 44.1KB 44.4KB +243B 9.3KB 9.3KB 0B
core/Token 29.6KB 29.9KB +289B 4.5KB 4.5KB 0B
core/Tokenizer 64.8KB 65.1KB +306B 11.7KB 11.7KB 0B
core/TopNav 51.9KB 52.1KB +277B 10.4KB 10.4KB 0B
core/TopNavHeading 54.2KB 54.4KB +231B 9.9KB 9.9KB 0B
core/TopNavItem 9.0KB 9.3KB +283B 3.8KB 3.8KB 0B
core/TopNavMegaMenu 54.2KB 54.4KB +268B 10.8KB 10.8KB 0B
core/TopNavMegaMenuItem 9.1KB 9.4KB +281B 4.1KB 4.1KB 0B
core/TopNavMenu 53.6KB 53.8KB +258B 10.1KB 10.1KB 0B
core/TreeList 32.5KB 32.8KB +274B 4.7KB 4.7KB 0B
core/Typeahead 61.5KB 61.7KB +268B 11.3KB 11.3KB 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 +46.9KB ⚠️ +355B
core/DateTimeInput +49.2KB ⚠️ +444B
core/PowerSearch +48.5KB ⚠️ +384B
core/TimeInput +29.7KB ⚠️ +19B

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 34.6KB 34.9KB +337B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

55 of 502 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
RichTextEditor Markdown parity: side by side probe dark 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232

and 35 more.


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

@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.

This review is on hold for a spec decision: whether the month and year wheels in the DateInput and DateTimeInput bottom sheets should change the chosen date, as iOS's inline date picker does, has no approved spec yet (component:DateInput and component:DateTimeInput are draft, and the pull request marks it as pending owner review), and the new draft spec:AST-094 would also amend the current spec:AST-025 FR14 and design:user-states DR1. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision, these changes are needed before approval:

  • Reduce this pull request's diff to its own commit: it currently carries the whole stack, about 36,700 changed lines, so land the predecessors first or base the branch on #8206.
  • In DateTimeInput, the stepped time wheels round 23:59 to 12:00 AM without moving the date, so a turn of the minute wheel commits a time about 23 hours earlier on the same day.
  • spec:AST-094 FR7 cites spec:AST-090, which does not exist in the repository, and the new dateConstraints refusal on the wheel path has no test.

[Automated review]

}
const minutes = time.hour * 60 + time.minute + time.second / 60;
const rounded = Math.round(minutes / interval) * interval;
return partsOf(rounded * 60);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In DateTimeInput this wrap drops a day: with timeIncrement={15}, a value of 2026-03-21T23:59 shows 12:00 AM, and turning the minute wheel to :45 commits 2026-03-21T00:45, because commitWheelTime keeps the date. Rounding down near midnight in the DateTimeInput sheet, or carrying the rounding into the date, avoids it; a test at 23:59 with a 15-minute step would pin 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.

Fixed in #8179: the rounding stays inside the day (23:58 shows 23:45). Head 0c6ff10.

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.

spec:AST-090 does not exist at this head or on main, so the departure this requirement asks owners to accept cannot be checked. Drop the reference, or cite the record where it lives.

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.

This branch no longer edits the AST-094 record (#8378 on main carries it).

min != null ? plainDateFromISO(min) : null,
max != null ? plainDateFromISO(max) : null,
);
if (!plainDateIsEqual(moved, selectedDate) && !isDateDisabled(moved)) {

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 refusal branch has no test in either sheet (TouchDateTimeField.tsx:834 too). A test where dateConstraints refuses the moved date, asserting that onChange is not called while the calendar still moves, would protect 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.

Added: both sheets test a refused date (May 21); head 0c6ff10.

@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.

This review is on hold for a spec decision: whether the month and year wheels in the DateInput and DateTimeInput bottom sheets should change the chosen date, as iOS's inline date picker does, has no approved spec yet (component:DateInput and component:DateTimeInput are draft), and the new draft spec:AST-094 would also amend the current spec:AST-025 FR14 and design:user-states DR1. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision, these changes are needed before approval:

  • Reduce this pull request's diff to its own commit: it still carries the whole stack, about 36,500 changed lines, so land the predecessors first or base the branch on #8206.
  • In DateTimeInput, the stepped time wheels round 23:59 to 12:00 AM without moving the date, so a turn of the minute wheel commits a time about 23 hours earlier on the same day.
  • spec:AST-094 FR7 cites spec:AST-090, which does not exist in the repository, and the new dateConstraints refusal on the wheel path has no test.

[Automated review]

}
const minutes = time.hour * 60 + time.minute + time.second / 60;
const rounded = Math.round(minutes / interval) * interval;
return partsOf(rounded * 60);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In DateTimeInput this wrap drops a day: with timeIncrement={15}, a value of 2026-03-21T23:59 shows 12:00 AM, and turning the minute wheel to :45 commits 2026-03-21T00:45, because commitWheelTime keeps the date. Rounding down near midnight in the DateTimeInput sheet, or carrying the rounding into the date, avoids it; a test at 23:59 with a 15-minute step would pin 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.

Fixed in #8179; head 0c6ff10.

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.

spec:AST-090 does not exist at this head or on main, so the departure this requirement asks owners to accept cannot be checked. Drop the reference, or cite the record where it lives.

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.

This branch no longer edits the AST-094 record.

min != null ? plainDateFromISO(min) : null,
max != null ? plainDateFromISO(max) : null,
);
if (!plainDateIsEqual(moved, selectedDate) && !isDateDisabled(moved)) {

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 refusal branch has no test in either sheet (TouchDateTimeField.tsx:834 too). A test where dateConstraints refuses the moved date, asserting that onChange is not called while the calendar still moves, would protect 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.

Added: both sheets test a refused date; head 0c6ff10.


useEffect(() => {
if (index != null && index !== pager.index) {
pager.goTo(index, {reason: 'controlled'});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

goTo reports synchronously through the caller's onIndexChange, so this Effect echoes a controlled move back to the owner, the Effect-driven callback the React runtime rules forbid, and its comment names no system it synchronizes. No caller passes index yet; skipping the report for a controlled move, or dropping the unused path, keeps it from biting later.

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.

hooks/usePager.ts is #8146's own here, not a copy; that Effect belongs to #8146's review.

@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.

This review is on hold for a spec decision: whether the month and year wheels in the DateInput and DateTimeInput bottom sheets should change the chosen date, as iOS's inline date picker does, has no approved spec yet (component:DateInput and component:DateTimeInput are draft), and draft spec:AST-094 in this range would also amend the current spec:AST-025 FR14 and design:user-states DR1. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision, these changes are needed before approval:

  • Reduce this pull request's diff to its own commit: it still carries the whole stack, about 37,200 changed lines, so land the predecessors first or base the branch on #8206.
  • In DateTimeInput, the stepped time wheels round 23:59 to 12:00 AM without moving the date, so a turn of the minute wheel commits a time about 23 hours earlier on the same day.
  • spec:AST-094 FR7 cites spec:AST-090, which does not exist in the repository, and the dateConstraints refusal on the new wheel path has no test in either sheet.

[Automated review]

return hasSeconds ? time : {...time, second: 0};
}
const minutes = time.hour * 60 + time.minute + time.second / 60;
const rounded = Math.round(minutes / interval) * interval;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In DateTimeInput this wrap drops a day: with timeIncrement={15}, a value of 2026-03-21T23:59 shows 12:00 AM, and turning the minute wheel to :45 commits 2026-03-21T00:45, because restOn turns from the shown time and commitWheelTime keeps the date (TouchDateTimeField.tsx:832-844). Rounding down at the end of the day, or carrying the rounding into the date, avoids it; a test at 23:59 with a 15-minute step would pin 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.

Fixed in #8179's 662b5c5: displayedTime keeps the rounding inside the day, so 23:59 with timeIncrement={15} shows 23:45 and a minute turn stays on the same date.

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.

spec:AST-090 does not exist at this head or on main, so the departure this requirement asks owners to accept cannot be checked. Drop the reference, or cite the record where it lives once it lands (the current-state impact at lines 234-236 names it too).

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 278a61a this branch no longer edits docs/specs/AST-094; that record is #8316's.

min != null ? plainDateFromISO(min) : null,
max != null ? plainDateFromISO(max) : null,
);
if (!plainDateIsEqual(moved, selectedDate) && !isDateDisabled(moved)) {

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 refusal branch has no test in either sheet (TouchDateTimeField.tsx:825 too). A test where dateConstraints refuses the moved date, asserting that onChange is not called while the calendar still moves, would cover the promise that a refused date never reaches the caller.

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.

Added in 278a61a: both sheets have a test where dateConstraints refuses the moved date (May 21). The date stays unchosen while the calendar moves.

Comment thread packages/core/src/hooks/usePager.ts Outdated
Comment on lines +698 to +702
useEffect(() => {
if (index != null && index !== pager.index) {
pager.goTo(index, {reason: 'controlled'});
}
}, [pager, index]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

goTo reports synchronously through the caller's onIndexChange, so this Effect echoes a controlled move back to the owner, the Effect-driven callback the React component runtime contract rules out, and its comment names no external system. Naming the pager engine and skipping the report for a controlled move, or dropping the unused controlled path, would fix 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 278a61a hooks/usePager.ts is #8146's own (49564e7), not a copy, so that Effect is #8146's.

@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.

This pull request is on hold for a spec decision: no approved spec says whether the month and year wheels in the DateInput and DateTimeInput bottom sheets should change the chosen date (component:DateInput and component:DateTimeInput are draft), and the draft spec:AST-094 carried in this range would also amend current scrolling and press-state specs. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision, these changes are needed before approval:

  • Reduce the diff to this pull request's own commit: it still carries the whole stack, about 37,500 changed lines, so land the predecessors first or base the branch on #8206.
  • In the DateTimeInput sheet, a stepped minute wheel rounds 23:59 to 12:00 AM without moving the date, so turning it commits a time about 23 hours earlier on the same day (timeWheelRules.ts, TouchDateTimeField.tsx).
  • spec:AST-094 FR7 cites spec:AST-090, which does not exist; the dateConstraints refusal on the new wheel path has no test in either sheet; and usePager's controlled-index Effect calls onIndexChange from an Effect.

[Automated review]

vjeux added 19 commits October 29, 2026 13:45
…o their own changesets

In the same release meta#8179's time columns loop, so "no shipped column loops
yet" would contradict it; the columns that loop say so themselves.
…heirs) into uikit/c21-wheel-rest-and-spin
… while the toggle fades

The header's arrows and Reset, and DateInput's footer actions, sit outside the shield that covers the panels during the month/year cross-fade, and their `inert` followed the panels' (deferred to the fade's end). During the opening fade a tap on Done fell through the still-inert Done to Save beneath it and closed the DateInput sheet, and DateTimeInput's fading arrows and Reset still paged the month or cleared the pick. These few controls now leave with the toggle and arrive with the fade's end; only the panels' own `inert` waits for the fade, since flipping it restyles a panel's whole subtree.
… the glyph is

Under Reduce Motion on a touch screen, a second title tap within the 200 ms dip restarted it from opacity 1, flashing the chevron back to opaque. The new dip now starts from the running dip's presented opacity, read before it is cancelled (a style read only in that case).
…llows the toggle during render

`useFadeInert` mirrored the toggle into state from a layout effect when the swap is not deferred, costing a second commit per toggle (architecture:react-component-runtime INV1, INV3). `settled` now follows `isWheelOpen` during render in that case; the effect keeps only the branch where the committed panel shows no exit can run (no Web Animations), with a reason naming that read.
…fect synchronizes with

The effect waits on the browser's clock and idle time (a timer for the sheet's present motion, then an idle callback), and its cleanup cools the wheels for the next opening (architecture:react-component-runtime INV3).
…ged, and where it was measured

The panels' `inert` swaps when the fade ends (not a frame later), the commit's restyle is described without claiming it changes no inherited style, the old chevron and the 0.01 s fade are DateInput's, and the iPhone and Android claims are narrowed to the iOS simulator measurement.
…s own spin

meta#8527's `spinTo` no longer takes a duration or a curve, so a column the time wheels move by themselves turns on the wheel's own spin: an animated `setContentOffset`, 0.3 s on Core Animation's ease-in-ease-out (`picker.tapSpin`, the role `timing.curves.move`), resolved under the column when the turn starts (spec:AST-094 FR4). TimeWheels no longer plays the literal `datePicker.wheels.columnTurn`, and the browser proof samples the AM/PM column's turn against that spin.
…ay round its seam

Under min 13:00 and max 23:00 the hours 0 to 12 are disabled; one ArrowDown from 23 crosses the seam onto 0, and the nearest enabled hour around the loop is 23 itself, so nothing moves or commits. It rested on 13 before meta#8527's seam-aware rest (f4386c5).
…he wheel's own spin

meta#8527's `spinTo` no longer takes a duration or a curve: the month wheel turns onto the nearest reachable month on the wheel's own spin (`picker.tapSpin` on the role `timing.curves.move`, UIKit's animated `setContentOffset`), resolved when the turn starts (spec:AST-094 FR4), not on the literal `datePicker.wheels.columnTurn`.
…he nearest enabled month around the seam

With min 2025-11-01, max 2026-02-28 and January 25, 2026 chosen, one ArrowUp from January crosses the seam onto December 2026, past max; around the loop the nearest enabled month is January itself, so nothing moves or commits. It went to February 25 before meta#8527's seam-aware rest and key step (f4386c5).
@vjeux
vjeux marked this pull request as draft October 29, 2026 22:33
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 30, 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.

This pull request is on hold for an engineering owner's decision on four draft spec rules it writes, which no current spec or owner comment states: component:DateInput PR2's month pager reporting the visible month once per half-month crossed, AR8's Shift+PageUp/Shift+PageDown moving the calendar by a year, FR13's rule that a wheel resting back on its committed out-of-range row commits nothing, and FR13's (and component:DateTimeInput FR8's) rule that a shown wheel turns onto a value changed from outside instead of jumping. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner, or the text can leave with the pull requests it comes from. Independently, the diff against main is 50,072 authored lines around this pull request's own 634-line change, so the predecessor pull requests need to land first and the branch rebased before it can be approved. The month and year wheels' date change itself is settled under spec:AST-094 DEC-1, and the looping month wheel now keeps the nearest enabled month across its seam when min and max cut the year.

[Automated review]

| FR10 | Standalone mode MUST compose Field; admitted InputGroup mode MUST remove competing Field chrome while preserving name, description, value, keyboard, focus, and editing semantics. | `family:input-fields/FR3`; current source and InputGroup tests | Settled shared rule and verified adoption |
| FR11 | Clear MUST emit `undefined` through the ordinary change pipeline. The pointer and bottom-sheet surfaces restore field focus using their shipped timing; the native surface MUST NOT reclaim focus because doing so reopens the OS picker. Implementation gap: when the field becomes disabled or busy while its clear control holds focus, the control unmounts and focus falls to the document body; `spec:AST-085/FR4` requires focus to stay or move to a destination this record names. | Current source and focused clear tests; `spec:AST-085/FR4` | Verified shipped behavior |
| FR12 | No surface renders native `disabled`. A disabled field MUST stay focusable with `aria-disabled`, block typing, picking, clearing, and opening, describe its disabled reason when one is given, and take disabled paint. While busy (`isLoading` or a pending Action) the pointer and bottom-sheet surfaces expose `aria-disabled` and `aria-busy`, stay focusable, and block new commits; the native surface does the same for `isLoading`, and during a pending Action stays editable so the OS picker remains attached but rejects additional commits until the Action settles. A busy field that is not disabled keeps its ordinary paint and Spinner, shows no hover ring while commits are blocked, and keeps its keyboard focus ring. | `spec:AST-085/FR1–FR6`; `family:input-fields/FR4–FR6`; `family:buttons/FR4`; current source and focused tests | Settled shared rule and verified adoption |
| FR13 | A bottom-sheet picker wheel that comes to rest on a disabled row (outside `min`/`max`) MUST continue to the nearest enabled row, a tie going toward the committed row, and select it there. A wheel resting on its committed row MUST NOT select or commit, even when that row is outside the bounds, so opening a bounded sheet never changes an untouched value: a committed value outside the bounds stays, and an empty field stays empty. A shown wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it. | DEC-1; UIDatePicker on the iOS 26.2 simulator; wheel unit and real-browser tests | Settled by DEC-1 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two clauses here go beyond the rest decision recorded for the picker wheels (a wheel at rest on a disabled row continues to the nearest allowed row; opening a bounded sheet never changes an untouched value): a committed out-of-range row that a drag rests back on commits nothing, and a shown wheel turns onto a value changed from outside instead of jumping. In this draft record they are proposals, so an engineering owner needs to approve or reject them, for example with /approve-spec <full head SHA>.

Comment on lines +198 to +200
On the month pager under a finger, a drag moves the month grid without a render, and the
visible month is reported once per half-month crossed, during the drag and the settle
that ends it.

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 adds a requirement no current record states: on the month pager under a finger, the visible month is reported once per half-month crossed, during the drag and the settle that ends it. component:DateInput is draft, so an engineering owner needs to approve it (for example with /approve-spec <full head SHA>), or it can leave with the calendar-pager pull request it comes from.

Comment on lines +234 to +235
picks the focused day. Named exception: Shift+PageUp and Shift+PageDown move
by a year, as in the APG date picker dialog.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shift+PageUp and Shift+PageDown by a year is an extra beyond the APG grid adoption rule, which spec:AST-020 FR15 asks the owning record to name as a named exception. While this record is draft the exception is proposed, not adopted, so it needs an engineering owner's approval.

| FR5 | The two pointer segments share one row while each retains its intrinsic basis; when the container is narrower, flex wrapping moves them into separate full-width rows. | Public docs, current source, focused layout tests | Verify rendered geometry; class-level behavior covered |
| FR6 | Date-time bounds and date constraints MUST prevent invalid commits. Native controls MAY receive platform hints, but JavaScript validation remains the final commit boundary; calendar-only options MAY be unavailable on native surfaces. | Public docs, current source, focused presentation tests | Verified observation; owner approval pending |
| FR7 | A preset-time listbox MUST remain optional, preserve typed entry, expose one selected option independently from keyboard highlight, and limit offered values to the selected date's effective minimum and maximum. | Public docs, current source, focused interaction tests | Verified observation; owner approval pending |
| FR8 | Its bottom-sheet date and time wheels rest as `component:DateInput/FR13` requires: a wheel stopping on a disabled row continues to the nearest enabled row and selects it, opening a bounded sheet never changes an untouched value, and a shown wheel turns onto a value a bound clamps. | `component:DateInput/DEC-1`; wheel unit tests | Settled by `component:DateInput/DEC-1` |

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 last clause (a shown wheel turns onto a value a bound clamps) repeats component:DateInput FR13's turn rule, which no owner decision or current record states yet; it waits on the same engineering-owner decision.

vjeux added 4 commits October 29, 2026 17:34
…names what it synchronizes

The un-keyed layout effect that lands a far jump synchronizes the pager's position with the DOM: it can move only once the render that mounts the target's pane has committed it, and moves before paint (architecture:react-component-runtime INV3).
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 30, 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.

This pull request stays on hold for an engineering owner's decision on four draft spec rules it writes that no current spec or owner decision states: component:DateInput PR2 (the month pager reports the visible month once per half-month crossed), AR8's Shift+PageUp/Shift+PageDown year step, and FR13's clauses that a rest back on a committed out-of-range row commits nothing and that a shown wheel turns onto a value changed from outside (repeated in component:DateTimeInput FR8). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner, or the text can leave with the pull requests it comes from. Independently, the diff against main is 50,075 authored lines around this pull request's own 634-line change, so the predecessor pull requests need to land and the branch be rebased before it can be approved. The latest commit only documents what the month pager's layout Effect synchronizes and raises no new issue.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — 46e8eae0 Deployed Oct 30, 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 needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants