Skip to content

Commit 2d5b2a0

Browse files
committed
fix(Wheel): the touch wheel draws only the rows around its band
Under a finger, opening a DateInput or DateTimeInput bottom sheet, and its month and year wheels, held the main thread longer for each year in the range: every wheel drew every row twice (on the cylinder, each its own 3D layer, and on the band's copy). In Mobile Safari (iOS 26.2 simulator), opening the year wheel of a 200-year range held the page for 278-754 ms; it now takes 54-65 ms. UIPickerView lays out only the cells it shows. PickerWheel now draws a window of rows: on the cylinder its front half (rows within 90 degrees of the band), and on the band's copy the row under the band and one each side, each with a buffer of 2: 15 and 7 rows at most at rest, for any list. Every motion mounts the rows it will show before it plays (flushSync), so its cost follows the distance it travels; a reduced-motion cross-fade mounts its target's rows at the fade's midpoint; a drag moves the window when the finger nears its edge; the window settles back around the rest. The band copy's rows sit at their positions along the wheel, so a looping wheel needs no copies of its strip. WheelMotion attaches rows by index and carries a running motion through a re-attach: rows that mount join it in step, rows that unmount leave it, and rows that mount during a cross-fade show where it is. The listbox's options are separate, plain and complete, stacked transparent over the band: assistive technology meets every option, and both drawings carry their text as generated content. Also: WheelGroup flags when its element is attached (the element stays in a ref), so a Wheel places its vanishing point at the group's centre once the group mounts; and a test holds the gesture host's selection lock on the column under a held finger. Stories BottomSheetWideRange (200 years) and BottomSheetNarrowRange (2 years) back the browser proof. Under a mouse, its year wheel stays today's column with every year listed. Wheel.interaction.browser.spec.ts marks the row drawn on the cylinder for its busy-main-thread proof (the option it marked is transparent now).
1 parent 1cfe5aa commit 2d5b2a0

11 files changed

Lines changed: 1021 additions & 222 deletions

‎.changeset/wheel-row-window.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@astryxdesign/core': patch
3+
---
4+
5+
[fix] Under a finger, opening the bottom sheet of a DateInput or DateTimeInput, and its month and year wheels, no longer blocks the page longer for each year in the range. A picker wheel now draws only the rows around its band, as UIPickerView lays out only the rows it shows, so drawing a wheel costs the same for 12 rows or 200. Every option is still listed for assistive technology, and a fling or a drag draws the rows it reaches before they come into sight. In Mobile Safari (iOS 26.2 simulator), opening the year wheel of a 200-year range used to hold the page for 278–754 ms; it now takes 54–65 ms.
6+
7+
@vjeux

‎apps/storybook/stories/DateInput.stories.tsx‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,3 +832,39 @@ export const StylingParts: Story = {
832832
</div>
833833
),
834834
};
835+
836+
/**
837+
* The bottom sheet with two centuries of years: the year wheel has 200 rows.
838+
* Opening the month and year wheels costs the same as for two years: a wheel
839+
* draws only the rows around its band.
840+
*/
841+
export const BottomSheetWideRange: Story = {
842+
render: args => {
843+
const [value, setValue] = useState<ISODateString | undefined>(
844+
'2026-03-15' as ISODateString,
845+
);
846+
return <DateInput {...args} value={value} onChange={setValue} />;
847+
},
848+
args: {
849+
label: 'Date of birth',
850+
presentation: 'bottom-sheet',
851+
min: '1900-01-01' as ISODateString,
852+
max: '2099-12-31' as ISODateString,
853+
},
854+
};
855+
856+
/** The same sheet with two years: the year wheel has 2 rows. */
857+
export const BottomSheetNarrowRange: Story = {
858+
render: args => {
859+
const [value, setValue] = useState<ISODateString | undefined>(
860+
'2026-03-15' as ISODateString,
861+
);
862+
return <DateInput {...args} value={value} onChange={setValue} />;
863+
},
864+
args: {
865+
label: 'Date of birth',
866+
presentation: 'bottom-sheet',
867+
min: '2026-01-01' as ISODateString,
868+
max: '2027-12-31' as ISODateString,
869+
},
870+
};

0 commit comments

Comments
 (0)