@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,352 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file timeWheelRules.ts
|
|
5
|
+
* @input A time of day, the hour format and the bounds
|
|
6
|
+
* @output Pure rules of UIDatePicker's time wheels: the columns' rows, the
|
|
7
|
+
* time a turned column makes, and which other columns turn with it
|
|
8
|
+
* @position Internal helper of the TimeInput and DateTimeInput sheets: the
|
|
9
|
+
* rows of both their finger and their mouse wheels
|
|
10
|
+
*
|
|
11
|
+
* The columns do not loop. Each rule follows a leaf of motion/uikit/
|
|
12
|
+
* datePicker.ts `wheels`. Pure functions keep the rules testable without a
|
|
13
|
+
* layout engine.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: When modified, update timeWheelRules.test.ts and TimeWheels.tsx.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
clampTime,
|
|
20
|
+
formatISOTime,
|
|
21
|
+
parseISOTime,
|
|
22
|
+
type ISOTimeString,
|
|
23
|
+
} from '../utils/timeParser';
|
|
24
|
+
|
|
25
|
+
/** A time of day, 24-hour. */
|
|
26
|
+
export interface TimeParts {
|
|
27
|
+
hour: number;
|
|
28
|
+
minute: number;
|
|
29
|
+
second: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A time wheel column. */
|
|
33
|
+
export type TimeColumn = 'hour' | 'minute' | 'second' | 'meridiem';
|
|
34
|
+
|
|
35
|
+
/** One row of a column: the value it stands for and its label. */
|
|
36
|
+
export interface TimeRow {
|
|
37
|
+
value: number;
|
|
38
|
+
label: string;
|
|
39
|
+
isDisabled: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Where the row falls when a key steps the column. An hour row holds the hour
|
|
42
|
+
* it stands for, so a key follows the full time: 12 PM comes before 1 PM.
|
|
43
|
+
*/
|
|
44
|
+
order?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Inclusive bounds in seconds since midnight; `null` is unbounded. */
|
|
48
|
+
export interface TimeBounds {
|
|
49
|
+
min: number | null;
|
|
50
|
+
max: number | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SECONDS_PER_DAY = 24 * 3600;
|
|
54
|
+
|
|
55
|
+
function secondsOfISO(time: ISOTimeString | undefined): number | null {
|
|
56
|
+
if (time == null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseISOTime(time);
|
|
60
|
+
return parsed == null
|
|
61
|
+
? null
|
|
62
|
+
: parsed.hour * 3600 + parsed.minute * 60 + parsed.second;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The bounds of a field's `min` and `max` times. */
|
|
66
|
+
export function wheelBounds(
|
|
67
|
+
min: ISOTimeString | undefined,
|
|
68
|
+
max: ISOTimeString | undefined,
|
|
69
|
+
): TimeBounds {
|
|
70
|
+
return {min: secondsOfISO(min), max: secondsOfISO(max)};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Whether the hour falls in the PM half (1) or the AM half (0). */
|
|
74
|
+
export function meridiemOf(hour: number): 0 | 1 {
|
|
75
|
+
return hour < 12 ? 0 : 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The 12-hour clock's label for a 24-hour hour: 0 → 12, 13 → 1. */
|
|
79
|
+
export function hour12From24(hour: number): number {
|
|
80
|
+
const h = hour % 12;
|
|
81
|
+
return h === 0 ? 12 : h;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The 24-hour hour for a 12-hour hour in a half of the day: 12 AM → 0. */
|
|
85
|
+
export function hour24From12(hour: number, meridiem: number): number {
|
|
86
|
+
return (hour % 12) + (meridiem === 1 ? 12 : 0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function partsOf(seconds: number): TimeParts {
|
|
90
|
+
const s = ((seconds % SECONDS_PER_DAY) + SECONDS_PER_DAY) % SECONDS_PER_DAY;
|
|
91
|
+
return {
|
|
92
|
+
hour: Math.floor(s / 3600),
|
|
93
|
+
minute: Math.floor((s % 3600) / 60),
|
|
94
|
+
second: s % 60,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Whether [start, end] (seconds) overlaps the bounds. */
|
|
99
|
+
function overlaps(start: number, end: number, bounds: TimeBounds): boolean {
|
|
100
|
+
return (
|
|
101
|
+
(bounds.min == null || end >= bounds.min) &&
|
|
102
|
+
(bounds.max == null || start <= bounds.max)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The time the wheels show for `time`: its seconds kept when the picker has a
|
|
108
|
+
* seconds column and dropped when it has none. A time outside the bounds is
|
|
109
|
+
* shown as it is, on its disabled rows: opening the sheet never moves an
|
|
110
|
+
* untouched value.
|
|
111
|
+
*/
|
|
112
|
+
export function displayedTime(time: TimeParts, hasSeconds: boolean): TimeParts {
|
|
113
|
+
return {...time, second: hasSeconds ? time.second : 0};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The time a column's rest commits when it would make `time`: the nearest
|
|
118
|
+
* time inside the bounds that the columns have a row for. Past a bound, that
|
|
119
|
+
* is the bound itself, or, with no seconds column, the nearest whole minute
|
|
120
|
+
* inside it (a 16:55:30 maximum: 16:55). Inside the bounds, `time` itself.
|
|
121
|
+
*/
|
|
122
|
+
export function restingTime(
|
|
123
|
+
time: TimeParts,
|
|
124
|
+
hasSeconds: boolean,
|
|
125
|
+
bounds: TimeBounds,
|
|
126
|
+
): TimeParts {
|
|
127
|
+
const boundAt = (seconds: number | null) =>
|
|
128
|
+
seconds == null ? undefined : formatISOTime(partsOf(seconds), true);
|
|
129
|
+
const resting = clampTime(
|
|
130
|
+
formatISOTime(time, hasSeconds),
|
|
131
|
+
boundAt(bounds.min),
|
|
132
|
+
boundAt(bounds.max),
|
|
133
|
+
hasSeconds,
|
|
134
|
+
);
|
|
135
|
+
return parseISOTime(resting) ?? time;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The hour the hour column makes when it comes to rest on `row` from the row
|
|
140
|
+
* that showed `from` (24-hour). In 24-hour time the row is the hour. In 12-hour
|
|
141
|
+
* time the half of the day changes only when the move passes between 11 and 12.
|
|
142
|
+
* The column (1 … 12) does not loop, so 12 counts as next to 1. A move between
|
|
143
|
+
* 12 and 1 to 6 keeps the half of the day. A move between 12 and 7 to 11
|
|
144
|
+
* changes it.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```
|
|
148
|
+
* hourAfterTurn(11, 12, '12h'); // 12: 11 AM → 12 PM
|
|
149
|
+
* hourAfterTurn(12, 1, '12h'); // 13: 12 PM → 1 PM
|
|
150
|
+
* hourAfterTurn(13, 9, '12h'); // 21: 1 PM → 9 PM
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export function hourAfterTurn(
|
|
154
|
+
from: number,
|
|
155
|
+
row: number,
|
|
156
|
+
hourFormat: '12h' | '24h',
|
|
157
|
+
): number {
|
|
158
|
+
if (hourFormat === '24h') {
|
|
159
|
+
return row;
|
|
160
|
+
}
|
|
161
|
+
const shown = hour12From24(from);
|
|
162
|
+
if (row === shown) {
|
|
163
|
+
return from;
|
|
164
|
+
}
|
|
165
|
+
const half = meridiemOf(from);
|
|
166
|
+
const passes11And12 =
|
|
167
|
+
(shown === 12 && row >= 7) || (row === 12 && shown >= 7);
|
|
168
|
+
return hour24From12(row, passes11And12 ? 1 - half : half);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function hourRowsFor(
|
|
172
|
+
hourFormat: '12h' | '24h',
|
|
173
|
+
hourOf: (row: number) => number,
|
|
174
|
+
bounds: TimeBounds,
|
|
175
|
+
): TimeRow[] {
|
|
176
|
+
const rows =
|
|
177
|
+
hourFormat === '24h'
|
|
178
|
+
? Array.from({length: 24}, (_, hour) => hour)
|
|
179
|
+
: Array.from({length: 12}, (_, index) => index + 1);
|
|
180
|
+
return rows.map(row => {
|
|
181
|
+
const hour = hourOf(row);
|
|
182
|
+
return {
|
|
183
|
+
value: row,
|
|
184
|
+
label: hourFormat === '24h' ? String(row).padStart(2, '0') : String(row),
|
|
185
|
+
isDisabled: !overlaps(hour * 3600, hour * 3600 + 3599, bounds),
|
|
186
|
+
order: hour,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The hour column under a finger. In 24-hour time its rows are 00 … 23. In
|
|
193
|
+
* 12-hour time they are 1 … 12, each row standing for the hour a turn onto it
|
|
194
|
+
* from `from` makes (`hourAfterTurn`). A row is disabled when no minute of that
|
|
195
|
+
* hour is inside the bounds.
|
|
196
|
+
*/
|
|
197
|
+
export function hourRows(
|
|
198
|
+
from: number,
|
|
199
|
+
hourFormat: '12h' | '24h',
|
|
200
|
+
bounds: TimeBounds,
|
|
201
|
+
): TimeRow[] {
|
|
202
|
+
return hourRowsFor(
|
|
203
|
+
hourFormat,
|
|
204
|
+
row => hourAfterTurn(from, row, hourFormat),
|
|
205
|
+
bounds,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The hour column under a mouse, whose columns turn one at a time. A 12-hour
|
|
211
|
+
* row stands for its hour in the half of the day that `from` is in.
|
|
212
|
+
*/
|
|
213
|
+
export function hourRowsInHalf(
|
|
214
|
+
from: number,
|
|
215
|
+
hourFormat: '12h' | '24h',
|
|
216
|
+
bounds: TimeBounds,
|
|
217
|
+
): TimeRow[] {
|
|
218
|
+
const half = meridiemOf(from);
|
|
219
|
+
return hourRowsFor(
|
|
220
|
+
hourFormat,
|
|
221
|
+
row => (hourFormat === '24h' ? row : hour24From12(row, half)),
|
|
222
|
+
bounds,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The minute column for `hour`: every minute. A row is disabled when it is
|
|
228
|
+
* outside the bounds (its whole minute, when the picker shows seconds).
|
|
229
|
+
*/
|
|
230
|
+
export function minuteRows(
|
|
231
|
+
hour: number,
|
|
232
|
+
hasSeconds: boolean,
|
|
233
|
+
bounds: TimeBounds,
|
|
234
|
+
): TimeRow[] {
|
|
235
|
+
return Array.from({length: 60}, (_, minute): TimeRow => {
|
|
236
|
+
const start = hour * 3600 + minute * 60;
|
|
237
|
+
return {
|
|
238
|
+
value: minute,
|
|
239
|
+
label: String(minute).padStart(2, '0'),
|
|
240
|
+
isDisabled: !overlaps(start, start + (hasSeconds ? 59 : 0), bounds),
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The second column for `hour`:`minute`. */
|
|
246
|
+
export function secondRows(
|
|
247
|
+
hour: number,
|
|
248
|
+
minute: number,
|
|
249
|
+
bounds: TimeBounds,
|
|
250
|
+
): TimeRow[] {
|
|
251
|
+
return Array.from({length: 60}, (_, second): TimeRow => {
|
|
252
|
+
const at = hour * 3600 + minute * 60 + second;
|
|
253
|
+
return {
|
|
254
|
+
value: second,
|
|
255
|
+
label: String(second).padStart(2, '0'),
|
|
256
|
+
isDisabled: !overlaps(at, at, bounds),
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The AM/PM column: a half is disabled when none of its hours is in bounds. */
|
|
262
|
+
export function meridiemRows(
|
|
263
|
+
labels: {am: string; pm: string},
|
|
264
|
+
bounds: TimeBounds,
|
|
265
|
+
): TimeRow[] {
|
|
266
|
+
return [
|
|
267
|
+
{
|
|
268
|
+
value: 0,
|
|
269
|
+
label: labels.am,
|
|
270
|
+
isDisabled: !overlaps(0, 12 * 3600 - 1, bounds),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
value: 1,
|
|
274
|
+
label: labels.pm,
|
|
275
|
+
isDisabled: !overlaps(12 * 3600, SECONDS_PER_DAY - 1, bounds),
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The time made by turning `column` to `value` (its row's value) from `from`
|
|
282
|
+
* (the time the wheels show). Only that column changes, and the AM/PM column
|
|
283
|
+
* moves the hour by twelve.
|
|
284
|
+
*/
|
|
285
|
+
export function turnColumn(
|
|
286
|
+
from: TimeParts,
|
|
287
|
+
column: TimeColumn,
|
|
288
|
+
value: number,
|
|
289
|
+
hourFormat: '12h' | '24h',
|
|
290
|
+
): TimeParts {
|
|
291
|
+
switch (column) {
|
|
292
|
+
case 'hour':
|
|
293
|
+
return {...from, hour: hourAfterTurn(from.hour, value, hourFormat)};
|
|
294
|
+
case 'minute':
|
|
295
|
+
return {...from, minute: value};
|
|
296
|
+
case 'second':
|
|
297
|
+
return {...from, second: value};
|
|
298
|
+
case 'meridiem':
|
|
299
|
+
return {...from, hour: hour24From12(hour12From24(from.hour), value)};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** What a column shows for a time: its row's value. */
|
|
304
|
+
export function columnValue(
|
|
305
|
+
time: TimeParts,
|
|
306
|
+
column: TimeColumn,
|
|
307
|
+
hourFormat: '12h' | '24h',
|
|
308
|
+
): number {
|
|
309
|
+
switch (column) {
|
|
310
|
+
case 'hour':
|
|
311
|
+
return hourFormat === '24h' ? time.hour : hour12From24(time.hour);
|
|
312
|
+
case 'minute':
|
|
313
|
+
return time.minute;
|
|
314
|
+
case 'second':
|
|
315
|
+
return time.second;
|
|
316
|
+
case 'meridiem':
|
|
317
|
+
return meridiemOf(time.hour);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The columns that must turn after `column` came to rest on `value`. That is
|
|
323
|
+
* every column whose row for the committed time differs from the row it shows
|
|
324
|
+
* now. The column that moved shows `value`; the others show `from`. An hour
|
|
325
|
+
* that crossed 11 ↔ 12 turns the AM/PM column; a time brought back onto a bound
|
|
326
|
+
* turns every column whose row changed.
|
|
327
|
+
*/
|
|
328
|
+
export function columnsToTurn(
|
|
329
|
+
from: TimeParts,
|
|
330
|
+
column: TimeColumn,
|
|
331
|
+
value: number,
|
|
332
|
+
committed: TimeParts,
|
|
333
|
+
options: {hourFormat: '12h' | '24h'; hasSeconds: boolean},
|
|
334
|
+
): {column: TimeColumn; value: number}[] {
|
|
335
|
+
const columns: TimeColumn[] = ['hour', 'minute'];
|
|
336
|
+
if (options.hasSeconds) {
|
|
337
|
+
columns.push('second');
|
|
338
|
+
}
|
|
339
|
+
if (options.hourFormat === '12h') {
|
|
340
|
+
columns.push('meridiem');
|
|
341
|
+
}
|
|
342
|
+
const turns: {column: TimeColumn; value: number}[] = [];
|
|
343
|
+
for (const each of columns) {
|
|
344
|
+
const shown =
|
|
345
|
+
each === column ? value : columnValue(from, each, options.hourFormat);
|
|
346
|
+
const target = columnValue(committed, each, options.hourFormat);
|
|
347
|
+
if (shown !== target) {
|
|
348
|
+
turns.push({column: each, value: target});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return turns;
|
|
352
|
+
}
|
|
@@ -354,10 +354,10 @@ function TokenizerSheetContent<T extends SearchableItem>({
|
|
|
354
354
|
// has an entrance animation and no exit, so a close is its unmount; the
|
|
355
355
|
// close handler commits that unmount at once and then places focus itself,
|
|
356
356
|
// now that the modal is gone and the sheet's content can take focus again
|
|
357
|
-
// (`architecture:react-component-runtime` INV2)
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
//
|
|
357
|
+
// (`architecture:react-component-runtime` INV2). After a clear, Clear all
|
|
358
|
+
// is aria-disabled but stays focusable, so it keeps focus (FR24,
|
|
359
|
+
// `spec:AST-085` FR4). Focus on the search field opens the on-screen
|
|
360
|
+
// keyboard over the cleared rows. A confirmed clear is announced too — a
|
|
361
361
|
// change the person hears about, as an add or a remove is
|
|
362
362
|
// (`component:Tokenizer` AR6) — after the confirmation's dialog has gone,
|
|
363
363
|
// so the sheet is the modal on top and the words are delivered inside it
|
|
@@ -372,11 +372,9 @@ function TokenizerSheetContent<T extends SearchableItem>({
|
|
|
372
372
|
const closeClearConfirmation = (confirmed: boolean) => {
|
|
373
373
|
// eslint-disable-next-line @eslint-react/dom-no-flush-sync -- the confirmation must be gone before focus moves into the sheet and the words are routed into it
|
|
374
374
|
flushSync(() => setIsClearConfirmationOpen(false));
|
|
375
|
+
clearAllRef.current?.focus();
|
|
375
376
|
if (confirmed) {
|
|
376
|
-
searchRef.current?.focus();
|
|
377
377
|
announce(t('@astryx.tokenizer.allRemoved'));
|
|
378
|
-
} else {
|
|
379
|
-
clearAllRef.current?.focus();
|
|
380
378
|
}
|
|
381
379
|
};
|
|
382
380
|
// Keep editing: the confirmation is gone, and focus returns to the search
|
|
@@ -1000,11 +998,9 @@ function TokenizerSheetContent<T extends SearchableItem>({
|
|
|
1000
998
|
variant="secondary"
|
|
1001
999
|
size="lg"
|
|
1002
1000
|
isDisabled={draftValue.length === 0}
|
|
1003
|
-
// With a tooltip, Button
|
|
1004
|
-
//
|
|
1005
|
-
// (`spec:AST-085` FR1)
|
|
1006
|
-
// natively disabled button does — a confirmed Clear all returns
|
|
1007
|
-
// focus here before the search field takes it (FR24).
|
|
1001
|
+
// With a tooltip, Button uses aria-disabled, so Clear all stays
|
|
1002
|
+
// focusable after a clear and says why it does nothing
|
|
1003
|
+
// (`spec:AST-085` FR1; FR24).
|
|
1008
1004
|
tooltip={
|
|
1009
1005
|
draftValue.length === 0
|
|
1010
1006
|
? t('@astryx.tokenizer.nothingToClear')
|
|
@@ -633,6 +633,12 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
|
633
633
|
return;
|
|
634
634
|
}
|
|
635
635
|
if (handleTouchPointerDown(event)) {
|
|
636
|
+
// A pen in range starts the hover show before it lands. Left pending,
|
|
637
|
+
// that show opens the layer during the press. The tap then closes it.
|
|
638
|
+
if (showTimeoutRef.current) {
|
|
639
|
+
clearTimeout(showTimeoutRef.current);
|
|
640
|
+
showTimeoutRef.current = null;
|
|
641
|
+
}
|
|
636
642
|
return;
|
|
637
643
|
}
|
|
638
644
|
if (isOpen !== undefined) {
|
|
@@ -179,6 +179,7 @@ import {
|
|
|
179
179
|
import {subscribeInput} from '../utils/inputStream';
|
|
180
180
|
import {isPrimaryClick} from '../utils/primaryClick';
|
|
181
181
|
import {
|
|
182
|
+
type DragSelectionHold,
|
|
182
183
|
type DragSelectionReleaseOptions,
|
|
183
184
|
lockDragSelection,
|
|
184
185
|
locksSelectionFor,
|
|
@@ -710,10 +711,7 @@ export class GestureHost {
|
|
|
710
711
|
(options?: DragSelectionReleaseOptions) => void
|
|
711
712
|
>();
|
|
712
713
|
/** Each claimed contact's drag hold, until its lift or a give-back. */
|
|
713
|
-
private readonly claimHolds = new Map<
|
|
714
|
-
number,
|
|
715
|
-
(options?: DragSelectionReleaseOptions) => void
|
|
716
|
-
>();
|
|
714
|
+
private readonly claimHolds = new Map<number, DragSelectionHold>();
|
|
717
715
|
private readonly presentedHitTesting: 'auto' | 'always' | 'never';
|
|
718
716
|
/**
|
|
719
717
|
* Contacts sent to what is drawn instead of what the engine hit, by host
|
|
@@ -3747,10 +3745,13 @@ export class GestureHost {
|
|
|
3747
3745
|
}
|
|
3748
3746
|
this.pointerIds.delete(event.pointerId);
|
|
3749
3747
|
this.pointerKinds.delete(event.pointerId);
|
|
3748
|
+
const hold = this.claimHolds.get(id);
|
|
3750
3749
|
this.unlockSelection(id);
|
|
3751
3750
|
const dispositions = this.arena.ended([
|
|
3752
3751
|
{id, x: event.clientX, y: event.clientY, time: eventTime(event)},
|
|
3753
3752
|
]);
|
|
3753
|
+
// A selection the lift's own handlers made stays, as a click's does.
|
|
3754
|
+
hold?.keepLiftSelection();
|
|
3754
3755
|
// A retargeted tap's own click arms the click guard for the engine's.
|
|
3755
3756
|
const landed = this.retargeted.get(id);
|
|
3756
3757
|
this.retargeted.delete(id);
|
|
@@ -18,15 +18,18 @@ const english: Readonly<Record<string, string>> = {
|
|
|
18
18
|
"@astryx.powersearch.sheet.clearAllConfirm": "Clear filters",
|
|
19
19
|
"@astryx.powersearch.sheet.clearAllDescription": "This removes all editable filters from this search.",
|
|
20
20
|
"@astryx.powersearch.sheet.clearAllTitle": "Clear all filters?",
|
|
21
|
+
"@astryx.powersearch.sheet.discardConfirm": "Discard changes",
|
|
22
|
+
"@astryx.powersearch.sheet.discardDescription": "Your changes to the filters haven't been applied.",
|
|
23
|
+
"@astryx.powersearch.sheet.discardTitle": "Discard changes?",
|
|
21
24
|
"@astryx.powersearch.sheet.done": "Done",
|
|
22
|
-
"@astryx.powersearch.sheet.editableFiltersClearedWithCount": "Editable filters removed. {resultCount}",
|
|
23
25
|
"@astryx.powersearch.sheet.fieldOperator": "{field} {operator}",
|
|
24
26
|
"@astryx.powersearch.sheet.filterUnavailable": "This filter is no longer available",
|
|
25
|
-
"@astryx.powersearch.sheet.filtersClearedWithCount": "All filters removed. {resultCount}",
|
|
26
27
|
"@astryx.powersearch.sheet.filtersTitle": "Filters",
|
|
28
|
+
"@astryx.powersearch.sheet.keepEditing": "Keep editing",
|
|
27
29
|
"@astryx.powersearch.sheet.labelValue": "{label} {value}",
|
|
28
30
|
"@astryx.powersearch.sheet.noFilters": "No filters applied",
|
|
29
31
|
"@astryx.powersearch.sheet.noFiltersDescription": "Add a filter to narrow your results and find what you need faster.",
|
|
32
|
+
"@astryx.powersearch.sheet.noFiltersInDraft": "No filters",
|
|
30
33
|
"@astryx.powersearch.sheet.noMatchingFields": "No filters match your search",
|
|
31
34
|
"@astryx.powersearch.sheet.noResults": "No results found",
|
|
32
35
|
"@astryx.powersearch.sheet.removeFilter": "Remove {field} filter",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated by scripts/generate-i18n-runtime.mjs from packages/core/locales/en.json,
|
|
5
|
+
* holding the English for the catalog keys named in packages/core/src/Resizable/createResizable.ts.
|
|
6
|
+
* Do not edit by hand. Run `pnpm generate:i18n-runtime` instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const english: Readonly<Record<string, string>> = {
|
|
10
|
+
"@astryx.resizable.collapsed": "Collapsed",
|
|
11
|
+
"@astryx.resizable.handle.label": "Resize handle"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default english;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated by scripts/generate-i18n-runtime.mjs from packages/core/locales/en.json,
|
|
5
|
+
* holding the English for the catalog keys named in packages/core/src/TimeInput/TimeWheels.tsx.
|
|
6
|
+
* Do not edit by hand. Run `pnpm generate:i18n-runtime` instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const english: Readonly<Record<string, string>> = {
|
|
10
|
+
"@astryx.dateTimeInput.hourWheel": "Hour",
|
|
11
|
+
"@astryx.dateTimeInput.meridiemAM": "AM",
|
|
12
|
+
"@astryx.dateTimeInput.meridiemPM": "PM",
|
|
13
|
+
"@astryx.dateTimeInput.meridiemWheel": "AM/PM",
|
|
14
|
+
"@astryx.dateTimeInput.minuteWheel": "Minute",
|
|
15
|
+
"@astryx.dateTimeInput.secondWheel": "Second"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default english;
|
|
@@ -81,6 +81,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
81
81
|
'@astryx.powersearch.sheet.filtersTitle': 'Filters',
|
|
82
82
|
'@astryx.powersearch.sheet.addFilter': 'Add filter',
|
|
83
83
|
'@astryx.powersearch.sheet.noFilters': 'No filters applied',
|
|
84
|
+
'@astryx.powersearch.sheet.noFiltersInDraft': 'No filters',
|
|
84
85
|
'@astryx.powersearch.sheet.noFiltersDescription':
|
|
85
86
|
'Add a filter to narrow your results and find what you need faster.',
|
|
86
87
|
'@astryx.powersearch.sheet.clearAll': 'Clear all',
|
|
@@ -90,6 +91,11 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
90
91
|
'@astryx.powersearch.sheet.clearAllConfirm': 'Clear filters',
|
|
91
92
|
'@astryx.powersearch.sheet.clearAllCancel': 'Keep filters',
|
|
92
93
|
'@astryx.powersearch.sheet.done': 'Done',
|
|
94
|
+
'@astryx.powersearch.sheet.discardTitle': 'Discard changes?',
|
|
95
|
+
'@astryx.powersearch.sheet.discardDescription':
|
|
96
|
+
"Your changes to the filters haven't been applied.",
|
|
97
|
+
'@astryx.powersearch.sheet.keepEditing': 'Keep editing',
|
|
98
|
+
'@astryx.powersearch.sheet.discardConfirm': 'Discard changes',
|
|
93
99
|
'@astryx.powersearch.sheet.search': 'Search',
|
|
94
100
|
'@astryx.powersearch.sheet.searchResults': 'Search results',
|
|
95
101
|
'@astryx.powersearch.sheet.searchFields': 'Search filters',
|
|
@@ -115,10 +121,6 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
115
121
|
'{count, number} {count, plural, one {month} other {months}} from now',
|
|
116
122
|
'@astryx.powersearch.sheet.fieldOperator': '{field} {operator}',
|
|
117
123
|
'@astryx.powersearch.sheet.labelValue': '{label} {value}',
|
|
118
|
-
'@astryx.powersearch.sheet.filtersClearedWithCount':
|
|
119
|
-
'All filters removed. {resultCount}',
|
|
120
|
-
'@astryx.powersearch.sheet.editableFiltersClearedWithCount':
|
|
121
|
-
'Editable filters removed. {resultCount}',
|
|
122
124
|
'@astryx.alertDialog.cancel': 'Cancel',
|
|
123
125
|
'@astryx.appShell.mobileNavigation': 'Mobile navigation',
|
|
124
126
|
'@astryx.appShell.skipToContent': 'Skip to content',
|
|
@@ -83,6 +83,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
83
83
|
'@astryx.powersearch.sheet.filtersTitle': '⟦Ƒíłţéřš⟧',
|
|
84
84
|
'@astryx.powersearch.sheet.addFilter': '⟦Àðð ƒíłţéř⟧',
|
|
85
85
|
'@astryx.powersearch.sheet.noFilters': '⟦Ñó ƒíłţéřš àþþłíéð⟧',
|
|
86
|
+
'@astryx.powersearch.sheet.noFiltersInDraft': '⟦Ñó ƒíłţéřš⟧',
|
|
86
87
|
'@astryx.powersearch.sheet.noFiltersDescription':
|
|
87
88
|
'⟦Àðð à ƒíłţéř ţó ñàřřóŵ ýóúř řéšúłţš àñð ƒíñð ŵĥàţ ýóú ñééð ƒàšţéř.⟧',
|
|
88
89
|
'@astryx.powersearch.sheet.clearAll': '⟦Çłéàř àłł⟧',
|
|
@@ -92,6 +93,11 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
92
93
|
'@astryx.powersearch.sheet.clearAllConfirm': '⟦Çłéàř ƒíłţéřš⟧',
|
|
93
94
|
'@astryx.powersearch.sheet.clearAllCancel': '⟦Ķééþ ƒíłţéřš⟧',
|
|
94
95
|
'@astryx.powersearch.sheet.done': '⟦Ðóñé⟧',
|
|
96
|
+
'@astryx.powersearch.sheet.discardTitle': '⟦Ðíšçàřð çĥàñĝéš?⟧',
|
|
97
|
+
'@astryx.powersearch.sheet.discardDescription':
|
|
98
|
+
"⟦Ýóúř çĥàñĝéš ţó ţĥé ƒíłţéřš ĥàṽéñ'ţ ƀééñ àþþłíéð.⟧",
|
|
99
|
+
'@astryx.powersearch.sheet.keepEditing': '⟦Ķééþ éðíţíñĝ⟧',
|
|
100
|
+
'@astryx.powersearch.sheet.discardConfirm': '⟦Ðíšçàřð çĥàñĝéš⟧',
|
|
95
101
|
'@astryx.powersearch.sheet.search': '⟦Šéàřçĥ⟧',
|
|
96
102
|
'@astryx.powersearch.sheet.searchResults': '⟦Šéàřçĥ řéšúłţš⟧',
|
|
97
103
|
'@astryx.powersearch.sheet.searchFields': '⟦Šéàřçĥ ƒíłţéřš⟧',
|
|
@@ -118,10 +124,6 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
118
124
|
'⟦{count, number} {count, plural, one {month} other {months}} ƒřóɱ ñóŵ⟧',
|
|
119
125
|
'@astryx.powersearch.sheet.fieldOperator': '⟦{field} {operator}⟧',
|
|
120
126
|
'@astryx.powersearch.sheet.labelValue': '⟦{label} {value}⟧',
|
|
121
|
-
'@astryx.powersearch.sheet.filtersClearedWithCount':
|
|
122
|
-
'⟦Àłł ƒíłţéřš řéɱóṽéð. {resultCount}⟧',
|
|
123
|
-
'@astryx.powersearch.sheet.editableFiltersClearedWithCount':
|
|
124
|
-
'⟦Éðíţàƀłé ƒíłţéřš řéɱóṽéð. {resultCount}⟧',
|
|
125
127
|
'@astryx.alertDialog.cancel': '⟦Çàñçéł⟧',
|
|
126
128
|
'@astryx.appShell.mobileNavigation': '⟦Ṁóƀíłé ñàṽíĝàţíóñ⟧',
|
|
127
129
|
'@astryx.appShell.skipToContent': '⟦Šķíþ ţó çóñţéñţ⟧',
|