@astryxdesign/core 0.7.8-canary.ffd29c5 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +30 -14
- package/dist/BottomSheet/bottomSheetStack.js +1 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +3 -1
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +4 -0
- package/dist/CheckboxList/CheckboxList.d.ts +13 -2
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +10 -4
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListContext.js +2 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +14 -2
- package/dist/CheckboxList/requiredGroup.d.ts +22 -0
- package/dist/CheckboxList/requiredGroup.d.ts.map +1 -0
- package/dist/CheckboxList/requiredGroup.js +52 -0
- package/dist/DateInput/PickerWheel.d.ts +1 -1
- package/dist/DateInput/PickerWheel.d.ts.map +1 -1
- package/dist/DateInput/PickerWheel.js +27 -6
- package/dist/DateInput/Wheel.d.ts +2 -2
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +20 -6
- package/dist/DateInput/datePickerTouch.d.ts +13 -0
- package/dist/DateInput/datePickerTouch.d.ts.map +1 -0
- package/dist/DateInput/datePickerTouch.js +14 -0
- package/dist/DateInput/wheelOptions.d.ts +22 -2
- package/dist/DateInput/wheelOptions.d.ts.map +1 -1
- package/dist/DateInput/wheelOptions.js +14 -2
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +62 -119
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -1
- package/dist/Overlay/Overlay.d.ts.map +1 -1
- package/dist/Overlay/Overlay.js +7 -2
- package/dist/PowerSearch/PowerSearch.d.ts +4 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +24 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.js +172 -50
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +15 -15
- package/dist/Resizable/createResizable.d.ts +70 -16
- package/dist/Resizable/createResizable.d.ts.map +1 -1
- package/dist/Resizable/createResizable.js +180 -22
- package/dist/Resizable/separatorAttributes.d.ts +45 -0
- package/dist/Resizable/separatorAttributes.d.ts.map +1 -0
- package/dist/Resizable/separatorAttributes.js +35 -0
- package/dist/Resizable/useResizable.js +2 -2
- package/dist/TimeInput/TimeWheels.d.ts +32 -0
- package/dist/TimeInput/TimeWheels.d.ts.map +1 -0
- package/dist/TimeInput/TimeWheels.js +144 -0
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +63 -118
- package/dist/TimeInput/timeWheelRules.d.ts +124 -0
- package/dist/TimeInput/timeWheelRules.d.ts.map +1 -0
- package/dist/TimeInput/timeWheelRules.js +277 -0
- package/dist/Tokenizer/TokenizerBottomSheet.js +8 -12
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +6 -0
- package/dist/astryx.css +3 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +3 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +5 -2
- package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts +8 -0
- package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/Resizable/createResizable.generated.js +13 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts +8 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.js +17 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +5 -2
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +5 -2
- package/dist/keyboard/index.d.ts +17 -0
- package/dist/keyboard/index.d.ts.map +1 -0
- package/dist/keyboard/index.js +19 -0
- package/dist/keyboard/keyboardHeight.d.ts +149 -0
- package/dist/keyboard/keyboardHeight.d.ts.map +1 -0
- package/dist/keyboard/keyboardHeight.js +432 -0
- package/dist/keyboard/keyboardLayoutGuide.d.ts +59 -0
- package/dist/keyboard/keyboardLayoutGuide.d.ts.map +1 -0
- package/dist/keyboard/keyboardLayoutGuide.js +207 -0
- package/dist/keyboard/keyboardModel.d.ts +93 -0
- package/dist/keyboard/keyboardModel.d.ts.map +1 -0
- package/dist/keyboard/keyboardModel.js +220 -0
- package/dist/keyboard/rideKeyboard.d.ts +42 -0
- package/dist/keyboard/rideKeyboard.d.ts.map +1 -0
- package/dist/keyboard/rideKeyboard.js +77 -0
- package/dist/keyboard/useMobileKeyboard.d.ts +61 -0
- package/dist/keyboard/useMobileKeyboard.d.ts.map +1 -0
- package/dist/keyboard/useMobileKeyboard.js +699 -0
- package/dist/motion/android/index.d.ts +16 -0
- package/dist/motion/android/index.d.ts.map +1 -0
- package/dist/motion/android/index.js +18 -0
- package/dist/motion/android/keyboard.d.ts +83 -0
- package/dist/motion/android/keyboard.d.ts.map +1 -0
- package/dist/motion/android/keyboard.js +96 -0
- package/dist/motion/uikit/datePicker.d.ts +58 -0
- package/dist/motion/uikit/datePicker.d.ts.map +1 -0
- package/dist/motion/uikit/datePicker.js +60 -0
- package/dist/motion/uikit/index.d.ts +4 -3
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +4 -3
- package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +3 -2
- package/dist/utils/layoutViewport.d.ts +25 -0
- package/dist/utils/layoutViewport.d.ts.map +1 -0
- package/dist/utils/layoutViewport.js +102 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +74 -9
- package/dist/utils/pressGesture.d.ts +4 -2
- package/dist/utils/pressGesture.d.ts.map +1 -1
- package/dist/utils/pressGesture.js +3 -1
- package/dist/utils/selectionLock.d.ts +10 -1
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +31 -5
- package/dist/utils/sharedMutationObserver.d.ts +2 -1
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
- package/dist/utils/sharedMutationObserver.js +2 -1
- package/dist/utils/timeParser.d.ts +2 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +29 -3
- package/locales/en.json +21 -9
- package/locales/pseudo.json +15 -6
- package/package.json +5 -5
- package/src/BottomSheet/BottomSheet.doc.mjs +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +41 -13
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +5 -0
- package/src/BottomSheet/bottomSheetStack.ts +1 -1
- package/src/BottomSheet/useSheetGestures.ts +6 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +7 -0
- package/src/CheckboxList/CheckboxList.tsx +34 -2
- package/src/CheckboxList/CheckboxListContext.tsx +9 -1
- package/src/CheckboxList/CheckboxListItem.tsx +23 -2
- package/src/CheckboxList/requiredGroup.ts +77 -0
- package/src/DateInput/PickerWheel.tsx +37 -4
- package/src/DateInput/Wheel.tsx +30 -3
- package/src/DateInput/datePickerTouch.ts +15 -0
- package/src/DateInput/wheelOptions.ts +31 -2
- package/src/DateTimeInput/TouchDateTimeField.tsx +94 -178
- package/src/HoverCard/useHoverCard.tsx +6 -1
- package/src/Overlay/Overlay.tsx +7 -2
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.tsx +4 -2
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +237 -62
- package/src/Resizable/ResizeHandle.tsx +18 -26
- package/src/Resizable/createResizable.ts +242 -27
- package/src/Resizable/separatorAttributes.ts +73 -0
- package/src/Resizable/useResizable.ts +2 -2
- package/src/TimeInput/TimeWheels.tsx +218 -0
- package/src/TimeInput/TouchTimeField.tsx +93 -157
- package/src/TimeInput/timeWheelRules.ts +352 -0
- package/src/Tokenizer/TokenizerBottomSheet.tsx +8 -12
- package/src/Tooltip/useTooltip.tsx +6 -0
- package/src/gestures/GestureHost.ts +5 -4
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +5 -2
- package/src/i18n/generated-english/Resizable/createResizable.generated.ts +14 -0
- package/src/i18n/generated-english/TimeInput/TimeWheels.generated.ts +18 -0
- package/src/i18n/generated-locales/en.generated.ts +6 -4
- package/src/i18n/generated-locales/pseudo.generated.ts +6 -4
- package/src/keyboard/README.md +197 -0
- package/src/keyboard/index.ts +48 -0
- package/src/keyboard/keyboardHeight.ts +543 -0
- package/src/keyboard/keyboardLayoutGuide.ts +266 -0
- package/src/keyboard/keyboardModel.ts +301 -0
- package/src/keyboard/rideKeyboard.ts +100 -0
- package/src/keyboard/useMobileKeyboard.ts +866 -0
- package/src/motion/MEASUREMENTS.md +10 -0
- package/src/motion/README.md +2 -0
- package/src/motion/android/index.ts +18 -0
- package/src/motion/android/keyboard.ts +96 -0
- package/src/motion/uikit/datePicker.ts +60 -0
- package/src/motion/uikit/index.ts +4 -3
- package/src/utils/interactionOverlay.stylex.ts +3 -2
- package/src/utils/layoutViewport.ts +104 -0
- package/src/utils/pressFeedback.ts +99 -21
- package/src/utils/pressGesture.ts +4 -2
- package/src/utils/selectionLock.ts +40 -6
- package/src/utils/sharedMutationObserver.ts +2 -1
- package/src/utils/timeParser.ts +40 -3
- package/dist/BottomSheet/useMobileKeyboard.d.ts +0 -21
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +0 -1
- package/dist/BottomSheet/useMobileKeyboard.js +0 -413
- package/src/BottomSheet/useMobileKeyboard.ts +0 -542
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file TimeWheels.tsx
|
|
7
|
+
* @input A time of day, the hour format, the bounds and a commit callback
|
|
8
|
+
* @output Exports TimeWheels: UIDatePicker's `.time` wheels as one picker
|
|
9
|
+
* @position Internal component; the wheels under a finger in the TimeInput
|
|
10
|
+
* sheet and DateTimeInput's time panel. timeWheelRules.ts holds the rules.
|
|
11
|
+
*
|
|
12
|
+
* A column that moves by itself turns on the Wheel's own spin (`spinTo`). The
|
|
13
|
+
* spin starts before the commit, so the new value finds the column moving.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: When modified, update TimeWheels.test.tsx, timeWheelRules.ts,
|
|
16
|
+
* TimeInput.doc.mjs and DateTimeInput.doc.mjs.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {useMemo, useRef} from 'react';
|
|
20
|
+
import {Wheel, type WheelHandle} from '../DateInput/Wheel';
|
|
21
|
+
import type {ISOTimeString} from '../utils/timeParser';
|
|
22
|
+
import {
|
|
23
|
+
columnValue,
|
|
24
|
+
columnsToTurn,
|
|
25
|
+
displayedTime,
|
|
26
|
+
restingTime,
|
|
27
|
+
hourRows,
|
|
28
|
+
meridiemOf,
|
|
29
|
+
meridiemRows,
|
|
30
|
+
minuteRows,
|
|
31
|
+
secondRows,
|
|
32
|
+
turnColumn,
|
|
33
|
+
wheelBounds,
|
|
34
|
+
type TimeColumn,
|
|
35
|
+
type TimeParts,
|
|
36
|
+
type TimeRow,
|
|
37
|
+
} from './timeWheelRules';
|
|
38
|
+
import {
|
|
39
|
+
registerEnglishFallback,
|
|
40
|
+
useComponentTranslator,
|
|
41
|
+
} from '../i18n/useComponentTranslator';
|
|
42
|
+
import english from '../i18n/generated-english/TimeInput/TimeWheels.generated';
|
|
43
|
+
|
|
44
|
+
registerEnglishFallback(english);
|
|
45
|
+
|
|
46
|
+
export interface TimeWheelsProps {
|
|
47
|
+
/** The time the wheels show. */
|
|
48
|
+
time: TimeParts;
|
|
49
|
+
/** 12-hour (with an AM/PM column) or 24-hour time. */
|
|
50
|
+
hourFormat: '12h' | '24h';
|
|
51
|
+
/** Whether a seconds column shows. */
|
|
52
|
+
hasSeconds: boolean;
|
|
53
|
+
/** Earliest time that may commit. */
|
|
54
|
+
min?: ISOTimeString;
|
|
55
|
+
/** Latest time that may commit. */
|
|
56
|
+
max?: ISOTimeString;
|
|
57
|
+
/** False while the wheels are hidden. */
|
|
58
|
+
isActive: boolean;
|
|
59
|
+
/** Called with the committed time, already inside the bounds. */
|
|
60
|
+
onCommit: (time: TimeParts) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sameTime(a: TimeParts, b: TimeParts): boolean {
|
|
64
|
+
return a.hour === b.hour && a.minute === b.minute && a.second === b.second;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* UIDatePicker's time wheels.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```
|
|
72
|
+
* <TimeWheels time={{hour: 11, minute: 58, second: 0}} hourFormat="12h"
|
|
73
|
+
* hasSeconds={false} isActive onCommit={setTime} />
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function TimeWheels({
|
|
77
|
+
time,
|
|
78
|
+
hourFormat,
|
|
79
|
+
hasSeconds,
|
|
80
|
+
min,
|
|
81
|
+
max,
|
|
82
|
+
isActive,
|
|
83
|
+
onCommit,
|
|
84
|
+
}: TimeWheelsProps) {
|
|
85
|
+
const t = useComponentTranslator();
|
|
86
|
+
const bounds = useMemo(() => wheelBounds(min, max), [min, max]);
|
|
87
|
+
const shown = displayedTime(time, hasSeconds);
|
|
88
|
+
|
|
89
|
+
const hourOptions = useMemo(
|
|
90
|
+
() => hourRows(shown.hour, hourFormat, bounds),
|
|
91
|
+
[shown.hour, hourFormat, bounds],
|
|
92
|
+
);
|
|
93
|
+
const minuteOptions = useMemo(
|
|
94
|
+
() => minuteRows(shown.hour, hasSeconds, bounds),
|
|
95
|
+
[shown.hour, hasSeconds, bounds],
|
|
96
|
+
);
|
|
97
|
+
const secondOptions = useMemo(
|
|
98
|
+
() => secondRows(shown.hour, shown.minute, bounds),
|
|
99
|
+
[shown.hour, shown.minute, bounds],
|
|
100
|
+
);
|
|
101
|
+
const meridiemOptions = useMemo(
|
|
102
|
+
() =>
|
|
103
|
+
meridiemRows(
|
|
104
|
+
{
|
|
105
|
+
am: t('@astryx.dateTimeInput.meridiemAM'),
|
|
106
|
+
pm: t('@astryx.dateTimeInput.meridiemPM'),
|
|
107
|
+
},
|
|
108
|
+
bounds,
|
|
109
|
+
),
|
|
110
|
+
[t, bounds],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const hourRef = useRef<WheelHandle | null>(null);
|
|
114
|
+
const minuteRef = useRef<WheelHandle | null>(null);
|
|
115
|
+
const secondRef = useRef<WheelHandle | null>(null);
|
|
116
|
+
const meridiemRef = useRef<WheelHandle | null>(null);
|
|
117
|
+
|
|
118
|
+
const rows: Record<TimeColumn, TimeRow[]> = {
|
|
119
|
+
hour: hourOptions,
|
|
120
|
+
minute: minuteOptions,
|
|
121
|
+
second: secondOptions,
|
|
122
|
+
meridiem: meridiemOptions,
|
|
123
|
+
};
|
|
124
|
+
// The rows the columns turn onto by themselves (`spinTo`), each with the id of
|
|
125
|
+
// its turn. A turn's arrival is not a person's rest, so it commits nothing,
|
|
126
|
+
// even when the owner kept the old time.
|
|
127
|
+
const spinsRef = useRef<
|
|
128
|
+
Partial<Record<TimeColumn, {value: number; id: number}>>
|
|
129
|
+
>({});
|
|
130
|
+
const nextSpinIdRef = useRef(0);
|
|
131
|
+
|
|
132
|
+
const restOn = (column: TimeColumn) => (value: number) => {
|
|
133
|
+
if (spinsRef.current[column]?.value === value) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const from = shown;
|
|
137
|
+
// What commits is the row the wheels come to rest on: past a bound, the
|
|
138
|
+
// nearest time inside it that has a row (datePicker.wheels.restPastBound).
|
|
139
|
+
const next = restingTime(
|
|
140
|
+
turnColumn(from, column, value, hourFormat),
|
|
141
|
+
hasSeconds,
|
|
142
|
+
bounds,
|
|
143
|
+
);
|
|
144
|
+
const handles: Record<TimeColumn, WheelHandle | null> = {
|
|
145
|
+
hour: hourRef.current,
|
|
146
|
+
minute: minuteRef.current,
|
|
147
|
+
second: secondRef.current,
|
|
148
|
+
meridiem: meridiemRef.current,
|
|
149
|
+
};
|
|
150
|
+
// Turn the other columns first, so the commit's new value finds them
|
|
151
|
+
// already moving (a Wheel never parks a moving column).
|
|
152
|
+
for (const turn of columnsToTurn(from, column, value, next, {
|
|
153
|
+
hourFormat,
|
|
154
|
+
hasSeconds,
|
|
155
|
+
})) {
|
|
156
|
+
const index = rows[turn.column].findIndex(
|
|
157
|
+
row => row.value === turn.value,
|
|
158
|
+
);
|
|
159
|
+
const handle = handles[turn.column];
|
|
160
|
+
if (index >= 0 && handle != null) {
|
|
161
|
+
nextSpinIdRef.current += 1;
|
|
162
|
+
const id = nextSpinIdRef.current;
|
|
163
|
+
spinsRef.current[turn.column] = {value: turn.value, id};
|
|
164
|
+
void handle.spinTo(index).finally(() => {
|
|
165
|
+
if (spinsRef.current[turn.column]?.id === id) {
|
|
166
|
+
spinsRef.current[turn.column] = undefined;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!sameTime(next, from)) {
|
|
172
|
+
onCommit(next);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<>
|
|
178
|
+
<Wheel
|
|
179
|
+
ref={hourRef}
|
|
180
|
+
label={t('@astryx.dateTimeInput.hourWheel')}
|
|
181
|
+
options={hourOptions}
|
|
182
|
+
value={columnValue(shown, 'hour', hourFormat)}
|
|
183
|
+
isActive={isActive}
|
|
184
|
+
onChange={restOn('hour')}
|
|
185
|
+
/>
|
|
186
|
+
<Wheel
|
|
187
|
+
ref={minuteRef}
|
|
188
|
+
label={t('@astryx.dateTimeInput.minuteWheel')}
|
|
189
|
+
options={minuteOptions}
|
|
190
|
+
value={shown.minute}
|
|
191
|
+
isActive={isActive}
|
|
192
|
+
onChange={restOn('minute')}
|
|
193
|
+
/>
|
|
194
|
+
{hasSeconds && (
|
|
195
|
+
<Wheel
|
|
196
|
+
ref={secondRef}
|
|
197
|
+
label={t('@astryx.dateTimeInput.secondWheel')}
|
|
198
|
+
options={secondOptions}
|
|
199
|
+
value={shown.second}
|
|
200
|
+
isActive={isActive}
|
|
201
|
+
onChange={restOn('second')}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
{hourFormat === '12h' && (
|
|
205
|
+
<Wheel
|
|
206
|
+
ref={meridiemRef}
|
|
207
|
+
label={t('@astryx.dateTimeInput.meridiemWheel')}
|
|
208
|
+
options={meridiemOptions}
|
|
209
|
+
value={meridiemOf(shown.hour)}
|
|
210
|
+
isActive={isActive}
|
|
211
|
+
onChange={restOn('meridiem')}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
</>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
TimeWheels.displayName = 'TimeWheels';
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file TouchTimeField.tsx
|
|
7
|
-
* @input Uses React, Field, BottomSheet,
|
|
8
|
-
*
|
|
7
|
+
* @input Uses React, Field, BottomSheet, the wheels (TimeWheels under a finger,
|
|
8
|
+
* Wheel with a mouse), DateInput touch geometry tokens and time utilities
|
|
9
9
|
* @output Exports TouchTimeField — the bottom-sheet surface behind TimeInput
|
|
10
10
|
* @position Internal component; consumed by TimeInput.tsx for `presentation`
|
|
11
|
-
* sheet surfaces
|
|
11
|
+
* sheet surfaces
|
|
12
12
|
*
|
|
13
13
|
* SYNC: When modified, update these files to stay in sync:
|
|
14
14
|
* - /packages/core/src/TimeInput/TimeInput.tsx
|
|
@@ -54,6 +54,7 @@ import {useSize} from '../SizeContext/SizeContext';
|
|
|
54
54
|
import {Spinner} from '../Spinner';
|
|
55
55
|
import {useTooltip} from '../Tooltip';
|
|
56
56
|
import {Wheel, type WheelOption} from '../DateInput/Wheel';
|
|
57
|
+
import {useTouchPresentation} from '../utils/touchPresentation';
|
|
57
58
|
import {dateInputTouchGeometry} from '../DateInput/tokens.stylex';
|
|
58
59
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
59
60
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -68,6 +69,16 @@ import {
|
|
|
68
69
|
} from '../utils';
|
|
69
70
|
import {chainEventHandlers} from '../utils/chainEventHandlers';
|
|
70
71
|
import type {TimeInputProps} from './TimeInput';
|
|
72
|
+
import {TimeWheels} from '../DateInput/datePickerTouch';
|
|
73
|
+
import {
|
|
74
|
+
hour12From24,
|
|
75
|
+
hour24From12,
|
|
76
|
+
hourRowsInHalf,
|
|
77
|
+
meridiemRows,
|
|
78
|
+
minuteRows,
|
|
79
|
+
secondRows,
|
|
80
|
+
wheelBounds,
|
|
81
|
+
} from './timeWheelRules';
|
|
71
82
|
import {
|
|
72
83
|
registerEnglishFallback,
|
|
73
84
|
useComponentTranslator,
|
|
@@ -141,45 +152,6 @@ const styles = stylex.create({
|
|
|
141
152
|
},
|
|
142
153
|
});
|
|
143
154
|
|
|
144
|
-
function twoDigits(value: number): string {
|
|
145
|
-
return String(value).padStart(2, '0');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function hour12From24(hour: number): number {
|
|
149
|
-
return hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function hour24From12(hour: number, meridiem: number): number {
|
|
153
|
-
if (meridiem === 0) {
|
|
154
|
-
return hour === 12 ? 0 : hour;
|
|
155
|
-
}
|
|
156
|
-
return hour === 12 ? 12 : hour + 12;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function timeToSeconds(time: ISOTimeString | undefined): number | null {
|
|
160
|
-
if (time == null) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
const parsed = parseISOTime(time);
|
|
164
|
-
return parsed == null
|
|
165
|
-
? null
|
|
166
|
-
: parsed.hour * 3600 + parsed.minute * 60 + parsed.second;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function rangeOverlaps(
|
|
170
|
-
startSecond: number,
|
|
171
|
-
endSecond: number,
|
|
172
|
-
min: ISOTimeString | undefined,
|
|
173
|
-
max: ISOTimeString | undefined,
|
|
174
|
-
): boolean {
|
|
175
|
-
const minSecond = timeToSeconds(min);
|
|
176
|
-
const maxSecond = timeToSeconds(max);
|
|
177
|
-
return (
|
|
178
|
-
(minSecond == null || endSecond >= minSecond) &&
|
|
179
|
-
(maxSecond == null || startSecond <= maxSecond)
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
155
|
/**
|
|
184
156
|
* The Astryx bottom-sheet time surface: a read-only closed field that opens
|
|
185
157
|
* hour/minute(/second/meridiem) wheels in a BottomSheet. Wheels commit live,
|
|
@@ -249,6 +221,7 @@ export function TouchTimeField({
|
|
|
249
221
|
const mergedInputRef = useMergedRefs(ref, inputRef);
|
|
250
222
|
const inputGroup = useInputGroup();
|
|
251
223
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
224
|
+
const {isTouch} = useTouchPresentation();
|
|
252
225
|
const [, startTransition] = useTransition();
|
|
253
226
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
254
227
|
const isBusy = isLoading || optimisticValue !== value;
|
|
@@ -363,86 +336,29 @@ export function TouchTimeField({
|
|
|
363
336
|
[fireChange, hasSeconds, max, min, optimisticValue],
|
|
364
337
|
);
|
|
365
338
|
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
isDisabled: !rangeOverlaps(hour * 3600, hour * 3600 + 3599, min, max),
|
|
372
|
-
}));
|
|
373
|
-
}
|
|
374
|
-
const meridiem = parsedWheelTime.hour < 12 ? 0 : 1;
|
|
375
|
-
return Array.from({length: 12}, (_, index) => {
|
|
376
|
-
const hour = index + 1;
|
|
377
|
-
const hour24 = hour24From12(hour, meridiem);
|
|
378
|
-
return {
|
|
379
|
-
value: hour,
|
|
380
|
-
label: String(hour),
|
|
381
|
-
// Keys step in time order: 12 PM comes before 1 PM.
|
|
382
|
-
order: hour24,
|
|
383
|
-
isDisabled: !rangeOverlaps(
|
|
384
|
-
hour24 * 3600,
|
|
385
|
-
hour24 * 3600 + 3599,
|
|
386
|
-
min,
|
|
387
|
-
max,
|
|
388
|
-
),
|
|
389
|
-
};
|
|
390
|
-
});
|
|
391
|
-
}, [hourFormat, max, min, parsedWheelTime.hour]);
|
|
392
|
-
|
|
339
|
+
const bounds = useMemo(() => wheelBounds(min, max), [min, max]);
|
|
340
|
+
const hourOptions: WheelOption[] = useMemo(
|
|
341
|
+
() => hourRowsInHalf(parsedWheelTime.hour, hourFormat, bounds),
|
|
342
|
+
[bounds, hourFormat, parsedWheelTime.hour],
|
|
343
|
+
);
|
|
393
344
|
const minuteOptions: WheelOption[] = useMemo(
|
|
394
|
-
() =>
|
|
395
|
-
|
|
396
|
-
const start = parsedWheelTime.hour * 3600 + minute * 60;
|
|
397
|
-
return {
|
|
398
|
-
value: minute,
|
|
399
|
-
label: twoDigits(minute),
|
|
400
|
-
isDisabled: !rangeOverlaps(
|
|
401
|
-
start,
|
|
402
|
-
start + (hasSeconds ? 59 : 0),
|
|
403
|
-
min,
|
|
404
|
-
max,
|
|
405
|
-
),
|
|
406
|
-
};
|
|
407
|
-
}),
|
|
408
|
-
[hasSeconds, max, min, parsedWheelTime.hour],
|
|
345
|
+
() => minuteRows(parsedWheelTime.hour, hasSeconds, bounds),
|
|
346
|
+
[bounds, hasSeconds, parsedWheelTime.hour],
|
|
409
347
|
);
|
|
410
|
-
|
|
411
348
|
const secondOptions: WheelOption[] = useMemo(
|
|
412
|
-
() =>
|
|
413
|
-
|
|
414
|
-
value: second,
|
|
415
|
-
label: twoDigits(second),
|
|
416
|
-
isDisabled: !isTimeInRange(
|
|
417
|
-
formatISOTime(
|
|
418
|
-
{
|
|
419
|
-
hour: parsedWheelTime.hour,
|
|
420
|
-
minute: parsedWheelTime.minute,
|
|
421
|
-
second,
|
|
422
|
-
},
|
|
423
|
-
true,
|
|
424
|
-
),
|
|
425
|
-
min,
|
|
426
|
-
max,
|
|
427
|
-
),
|
|
428
|
-
})),
|
|
429
|
-
[max, min, parsedWheelTime.hour, parsedWheelTime.minute],
|
|
349
|
+
() => secondRows(parsedWheelTime.hour, parsedWheelTime.minute, bounds),
|
|
350
|
+
[bounds, parsedWheelTime.hour, parsedWheelTime.minute],
|
|
430
351
|
);
|
|
431
|
-
|
|
432
352
|
const meridiemOptions: WheelOption[] = useMemo(
|
|
433
|
-
() =>
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
isDisabled: !rangeOverlaps(12 * 3600, 23 * 3600 + 3599, min, max),
|
|
443
|
-
},
|
|
444
|
-
],
|
|
445
|
-
[max, min, t],
|
|
353
|
+
() =>
|
|
354
|
+
meridiemRows(
|
|
355
|
+
{
|
|
356
|
+
am: t('@astryx.dateTimeInput.meridiemAM'),
|
|
357
|
+
pm: t('@astryx.dateTimeInput.meridiemPM'),
|
|
358
|
+
},
|
|
359
|
+
bounds,
|
|
360
|
+
),
|
|
361
|
+
[bounds, t],
|
|
446
362
|
);
|
|
447
363
|
|
|
448
364
|
const openSheet = useCallback(() => {
|
|
@@ -541,49 +457,69 @@ export function TouchTimeField({
|
|
|
541
457
|
padding={0}>
|
|
542
458
|
<div ref={sheetBodyRef} {...stylex.props(styles.sheetBody)}>
|
|
543
459
|
<div {...stylex.props(styles.wheels)}>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
isActive={isSheetOpen}
|
|
553
|
-
onChange={hour =>
|
|
554
|
-
commitWheelTime(
|
|
555
|
-
hourFormat === '24h'
|
|
556
|
-
? composeWheelTime({hour24: hour})
|
|
557
|
-
: composeWheelTime({hour12: hour}),
|
|
558
|
-
)
|
|
559
|
-
}
|
|
560
|
-
/>
|
|
561
|
-
<Wheel
|
|
562
|
-
label={t('@astryx.dateTimeInput.minuteWheel')}
|
|
563
|
-
options={minuteOptions}
|
|
564
|
-
value={parsedWheelTime.minute}
|
|
565
|
-
isActive={isSheetOpen}
|
|
566
|
-
onChange={minute => commitWheelTime(composeWheelTime({minute}))}
|
|
567
|
-
/>
|
|
568
|
-
{hasSeconds && (
|
|
569
|
-
<Wheel
|
|
570
|
-
label={t('@astryx.dateTimeInput.secondWheel')}
|
|
571
|
-
options={secondOptions}
|
|
572
|
-
value={parsedWheelTime.second}
|
|
573
|
-
isActive={isSheetOpen}
|
|
574
|
-
onChange={second => commitWheelTime(composeWheelTime({second}))}
|
|
575
|
-
/>
|
|
576
|
-
)}
|
|
577
|
-
{hourFormat === '12h' && (
|
|
578
|
-
<Wheel
|
|
579
|
-
label={t('@astryx.dateTimeInput.meridiemWheel')}
|
|
580
|
-
options={meridiemOptions}
|
|
581
|
-
value={parsedWheelTime.hour < 12 ? 0 : 1}
|
|
460
|
+
{isTouch ? (
|
|
461
|
+
<TimeWheels
|
|
462
|
+
time={parsedWheelTime}
|
|
463
|
+
hourFormat={hourFormat}
|
|
464
|
+
hasSeconds={hasSeconds}
|
|
465
|
+
min={min}
|
|
466
|
+
max={max}
|
|
582
467
|
isActive={isSheetOpen}
|
|
583
|
-
|
|
584
|
-
commitWheelTime(
|
|
468
|
+
onCommit={time =>
|
|
469
|
+
commitWheelTime(formatISOTime(time, hasSeconds))
|
|
585
470
|
}
|
|
586
471
|
/>
|
|
472
|
+
) : (
|
|
473
|
+
<>
|
|
474
|
+
<Wheel
|
|
475
|
+
label={t('@astryx.dateTimeInput.hourWheel')}
|
|
476
|
+
options={hourOptions}
|
|
477
|
+
value={
|
|
478
|
+
hourFormat === '24h'
|
|
479
|
+
? parsedWheelTime.hour
|
|
480
|
+
: hour12From24(parsedWheelTime.hour)
|
|
481
|
+
}
|
|
482
|
+
isActive={isSheetOpen}
|
|
483
|
+
onChange={hour =>
|
|
484
|
+
commitWheelTime(
|
|
485
|
+
hourFormat === '24h'
|
|
486
|
+
? composeWheelTime({hour24: hour})
|
|
487
|
+
: composeWheelTime({hour12: hour}),
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
/>
|
|
491
|
+
<Wheel
|
|
492
|
+
label={t('@astryx.dateTimeInput.minuteWheel')}
|
|
493
|
+
options={minuteOptions}
|
|
494
|
+
value={parsedWheelTime.minute}
|
|
495
|
+
isActive={isSheetOpen}
|
|
496
|
+
onChange={minute =>
|
|
497
|
+
commitWheelTime(composeWheelTime({minute}))
|
|
498
|
+
}
|
|
499
|
+
/>
|
|
500
|
+
{hasSeconds && (
|
|
501
|
+
<Wheel
|
|
502
|
+
label={t('@astryx.dateTimeInput.secondWheel')}
|
|
503
|
+
options={secondOptions}
|
|
504
|
+
value={parsedWheelTime.second}
|
|
505
|
+
isActive={isSheetOpen}
|
|
506
|
+
onChange={second =>
|
|
507
|
+
commitWheelTime(composeWheelTime({second}))
|
|
508
|
+
}
|
|
509
|
+
/>
|
|
510
|
+
)}
|
|
511
|
+
{hourFormat === '12h' && (
|
|
512
|
+
<Wheel
|
|
513
|
+
label={t('@astryx.dateTimeInput.meridiemWheel')}
|
|
514
|
+
options={meridiemOptions}
|
|
515
|
+
value={parsedWheelTime.hour < 12 ? 0 : 1}
|
|
516
|
+
isActive={isSheetOpen}
|
|
517
|
+
onChange={meridiem =>
|
|
518
|
+
commitWheelTime(composeWheelTime({meridiem}))
|
|
519
|
+
}
|
|
520
|
+
/>
|
|
521
|
+
)}
|
|
522
|
+
</>
|
|
587
523
|
)}
|
|
588
524
|
</div>
|
|
589
525
|
<div {...stylex.props(styles.footer)}>
|