@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.
Files changed (189) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/dist/BottomSheet/BottomSheetPanel.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheetPanel.js +30 -14
  5. package/dist/BottomSheet/bottomSheetStack.js +1 -1
  6. package/dist/BottomSheet/useSheetGestures.d.ts +3 -1
  7. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  8. package/dist/BottomSheet/useSheetGestures.js +4 -0
  9. package/dist/CheckboxList/CheckboxList.d.ts +13 -2
  10. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  11. package/dist/CheckboxList/CheckboxList.js +10 -4
  12. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  13. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  14. package/dist/CheckboxList/CheckboxListContext.js +2 -1
  15. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  16. package/dist/CheckboxList/CheckboxListItem.js +14 -2
  17. package/dist/CheckboxList/requiredGroup.d.ts +22 -0
  18. package/dist/CheckboxList/requiredGroup.d.ts.map +1 -0
  19. package/dist/CheckboxList/requiredGroup.js +52 -0
  20. package/dist/DateInput/PickerWheel.d.ts +1 -1
  21. package/dist/DateInput/PickerWheel.d.ts.map +1 -1
  22. package/dist/DateInput/PickerWheel.js +27 -6
  23. package/dist/DateInput/Wheel.d.ts +2 -2
  24. package/dist/DateInput/Wheel.d.ts.map +1 -1
  25. package/dist/DateInput/Wheel.js +20 -6
  26. package/dist/DateInput/datePickerTouch.d.ts +13 -0
  27. package/dist/DateInput/datePickerTouch.d.ts.map +1 -0
  28. package/dist/DateInput/datePickerTouch.js +14 -0
  29. package/dist/DateInput/wheelOptions.d.ts +22 -2
  30. package/dist/DateInput/wheelOptions.d.ts.map +1 -1
  31. package/dist/DateInput/wheelOptions.js +14 -2
  32. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  33. package/dist/DateTimeInput/TouchDateTimeField.js +62 -119
  34. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  35. package/dist/HoverCard/useHoverCard.js +6 -1
  36. package/dist/Overlay/Overlay.d.ts.map +1 -1
  37. package/dist/Overlay/Overlay.js +7 -2
  38. package/dist/PowerSearch/PowerSearch.d.ts +4 -2
  39. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  40. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +24 -1
  41. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -1
  42. package/dist/PowerSearch/PowerSearchBottomSheet.js +172 -50
  43. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  44. package/dist/Resizable/ResizeHandle.js +15 -15
  45. package/dist/Resizable/createResizable.d.ts +70 -16
  46. package/dist/Resizable/createResizable.d.ts.map +1 -1
  47. package/dist/Resizable/createResizable.js +180 -22
  48. package/dist/Resizable/separatorAttributes.d.ts +45 -0
  49. package/dist/Resizable/separatorAttributes.d.ts.map +1 -0
  50. package/dist/Resizable/separatorAttributes.js +35 -0
  51. package/dist/Resizable/useResizable.js +2 -2
  52. package/dist/TimeInput/TimeWheels.d.ts +32 -0
  53. package/dist/TimeInput/TimeWheels.d.ts.map +1 -0
  54. package/dist/TimeInput/TimeWheels.js +144 -0
  55. package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
  56. package/dist/TimeInput/TouchTimeField.js +63 -118
  57. package/dist/TimeInput/timeWheelRules.d.ts +124 -0
  58. package/dist/TimeInput/timeWheelRules.d.ts.map +1 -0
  59. package/dist/TimeInput/timeWheelRules.js +277 -0
  60. package/dist/Tokenizer/TokenizerBottomSheet.js +8 -12
  61. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  62. package/dist/Tooltip/useTooltip.js +6 -0
  63. package/dist/astryx.css +3 -1
  64. package/dist/gestures/GestureHost.d.ts.map +1 -1
  65. package/dist/gestures/GestureHost.js +3 -0
  66. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -1
  67. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +5 -2
  68. package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts +8 -0
  69. package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts.map +1 -0
  70. package/dist/i18n/generated-english/Resizable/createResizable.generated.js +13 -0
  71. package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts +8 -0
  72. package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts.map +1 -0
  73. package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.js +17 -0
  74. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  75. package/dist/i18n/generated-locales/en.generated.js +5 -2
  76. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  77. package/dist/i18n/generated-locales/pseudo.generated.js +5 -2
  78. package/dist/keyboard/index.d.ts +17 -0
  79. package/dist/keyboard/index.d.ts.map +1 -0
  80. package/dist/keyboard/index.js +19 -0
  81. package/dist/keyboard/keyboardHeight.d.ts +149 -0
  82. package/dist/keyboard/keyboardHeight.d.ts.map +1 -0
  83. package/dist/keyboard/keyboardHeight.js +432 -0
  84. package/dist/keyboard/keyboardLayoutGuide.d.ts +59 -0
  85. package/dist/keyboard/keyboardLayoutGuide.d.ts.map +1 -0
  86. package/dist/keyboard/keyboardLayoutGuide.js +207 -0
  87. package/dist/keyboard/keyboardModel.d.ts +93 -0
  88. package/dist/keyboard/keyboardModel.d.ts.map +1 -0
  89. package/dist/keyboard/keyboardModel.js +220 -0
  90. package/dist/keyboard/rideKeyboard.d.ts +42 -0
  91. package/dist/keyboard/rideKeyboard.d.ts.map +1 -0
  92. package/dist/keyboard/rideKeyboard.js +77 -0
  93. package/dist/keyboard/useMobileKeyboard.d.ts +61 -0
  94. package/dist/keyboard/useMobileKeyboard.d.ts.map +1 -0
  95. package/dist/keyboard/useMobileKeyboard.js +699 -0
  96. package/dist/motion/android/index.d.ts +16 -0
  97. package/dist/motion/android/index.d.ts.map +1 -0
  98. package/dist/motion/android/index.js +18 -0
  99. package/dist/motion/android/keyboard.d.ts +83 -0
  100. package/dist/motion/android/keyboard.d.ts.map +1 -0
  101. package/dist/motion/android/keyboard.js +96 -0
  102. package/dist/motion/uikit/datePicker.d.ts +58 -0
  103. package/dist/motion/uikit/datePicker.d.ts.map +1 -0
  104. package/dist/motion/uikit/datePicker.js +60 -0
  105. package/dist/motion/uikit/index.d.ts +4 -3
  106. package/dist/motion/uikit/index.d.ts.map +1 -1
  107. package/dist/motion/uikit/index.js +4 -3
  108. package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
  109. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  110. package/dist/utils/interactionOverlay.stylex.js +3 -2
  111. package/dist/utils/layoutViewport.d.ts +25 -0
  112. package/dist/utils/layoutViewport.d.ts.map +1 -0
  113. package/dist/utils/layoutViewport.js +102 -0
  114. package/dist/utils/pressFeedback.d.ts.map +1 -1
  115. package/dist/utils/pressFeedback.js +74 -9
  116. package/dist/utils/pressGesture.d.ts +4 -2
  117. package/dist/utils/pressGesture.d.ts.map +1 -1
  118. package/dist/utils/pressGesture.js +3 -1
  119. package/dist/utils/selectionLock.d.ts +10 -1
  120. package/dist/utils/selectionLock.d.ts.map +1 -1
  121. package/dist/utils/selectionLock.js +31 -5
  122. package/dist/utils/sharedMutationObserver.d.ts +2 -1
  123. package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
  124. package/dist/utils/sharedMutationObserver.js +2 -1
  125. package/dist/utils/timeParser.d.ts +2 -1
  126. package/dist/utils/timeParser.d.ts.map +1 -1
  127. package/dist/utils/timeParser.js +29 -3
  128. package/locales/en.json +21 -9
  129. package/locales/pseudo.json +15 -6
  130. package/package.json +5 -5
  131. package/src/BottomSheet/BottomSheet.doc.mjs +1 -1
  132. package/src/BottomSheet/BottomSheetPanel.tsx +41 -13
  133. package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +5 -0
  134. package/src/BottomSheet/bottomSheetStack.ts +1 -1
  135. package/src/BottomSheet/useSheetGestures.ts +6 -0
  136. package/src/CheckboxList/CheckboxList.doc.mjs +7 -0
  137. package/src/CheckboxList/CheckboxList.tsx +34 -2
  138. package/src/CheckboxList/CheckboxListContext.tsx +9 -1
  139. package/src/CheckboxList/CheckboxListItem.tsx +23 -2
  140. package/src/CheckboxList/requiredGroup.ts +77 -0
  141. package/src/DateInput/PickerWheel.tsx +37 -4
  142. package/src/DateInput/Wheel.tsx +30 -3
  143. package/src/DateInput/datePickerTouch.ts +15 -0
  144. package/src/DateInput/wheelOptions.ts +31 -2
  145. package/src/DateTimeInput/TouchDateTimeField.tsx +94 -178
  146. package/src/HoverCard/useHoverCard.tsx +6 -1
  147. package/src/Overlay/Overlay.tsx +7 -2
  148. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  149. package/src/PowerSearch/PowerSearch.tsx +4 -2
  150. package/src/PowerSearch/PowerSearchBottomSheet.tsx +237 -62
  151. package/src/Resizable/ResizeHandle.tsx +18 -26
  152. package/src/Resizable/createResizable.ts +242 -27
  153. package/src/Resizable/separatorAttributes.ts +73 -0
  154. package/src/Resizable/useResizable.ts +2 -2
  155. package/src/TimeInput/TimeWheels.tsx +218 -0
  156. package/src/TimeInput/TouchTimeField.tsx +93 -157
  157. package/src/TimeInput/timeWheelRules.ts +352 -0
  158. package/src/Tokenizer/TokenizerBottomSheet.tsx +8 -12
  159. package/src/Tooltip/useTooltip.tsx +6 -0
  160. package/src/gestures/GestureHost.ts +5 -4
  161. package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +5 -2
  162. package/src/i18n/generated-english/Resizable/createResizable.generated.ts +14 -0
  163. package/src/i18n/generated-english/TimeInput/TimeWheels.generated.ts +18 -0
  164. package/src/i18n/generated-locales/en.generated.ts +6 -4
  165. package/src/i18n/generated-locales/pseudo.generated.ts +6 -4
  166. package/src/keyboard/README.md +197 -0
  167. package/src/keyboard/index.ts +48 -0
  168. package/src/keyboard/keyboardHeight.ts +543 -0
  169. package/src/keyboard/keyboardLayoutGuide.ts +266 -0
  170. package/src/keyboard/keyboardModel.ts +301 -0
  171. package/src/keyboard/rideKeyboard.ts +100 -0
  172. package/src/keyboard/useMobileKeyboard.ts +866 -0
  173. package/src/motion/MEASUREMENTS.md +10 -0
  174. package/src/motion/README.md +2 -0
  175. package/src/motion/android/index.ts +18 -0
  176. package/src/motion/android/keyboard.ts +96 -0
  177. package/src/motion/uikit/datePicker.ts +60 -0
  178. package/src/motion/uikit/index.ts +4 -3
  179. package/src/utils/interactionOverlay.stylex.ts +3 -2
  180. package/src/utils/layoutViewport.ts +104 -0
  181. package/src/utils/pressFeedback.ts +99 -21
  182. package/src/utils/pressGesture.ts +4 -2
  183. package/src/utils/selectionLock.ts +40 -6
  184. package/src/utils/sharedMutationObserver.ts +2 -1
  185. package/src/utils/timeParser.ts +40 -3
  186. package/dist/BottomSheet/useMobileKeyboard.d.ts +0 -21
  187. package/dist/BottomSheet/useMobileKeyboard.d.ts.map +0 -1
  188. package/dist/BottomSheet/useMobileKeyboard.js +0 -413
  189. package/src/BottomSheet/useMobileKeyboard.ts +0 -542
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file TouchDateTimeField.tsx
7
- * @input Uses React, Field, BottomSheet, SegmentedControl, DateInput month picker primitives, Wheel, StyleX intrinsic flex layout
7
+ * @input Uses React, Field, BottomSheet, SegmentedControl, DateInput month picker primitives, Wheel and the pickers' touch layer (TimeWheels), the touch presentation, StyleX intrinsic flex layout
8
8
  * @output Exports TouchDateTimeField — the coarse-pointer surface behind DateTimeInput
9
9
  * @position Internal component; consumed by DateTimeInput.tsx
10
10
  *
@@ -75,7 +75,6 @@ import {
75
75
  formatISOTime,
76
76
  isImeKeyEvent,
77
77
  isRenderable,
78
- isTimeInRange,
79
78
  clampTime,
80
79
  mergeProps,
81
80
  parseISOTime,
@@ -97,6 +96,17 @@ import {
97
96
  } from '../DateInput/MonthScroller';
98
97
  import {MonthYearWheels} from '../DateInput/MonthYearWheels';
99
98
  import {Wheel, type WheelOption} from '../DateInput/Wheel';
99
+ import {TimeWheels} from '../DateInput/datePickerTouch';
100
+ import {
101
+ hour12From24,
102
+ hour24From12,
103
+ hourRowsInHalf,
104
+ meridiemRows,
105
+ minuteRows,
106
+ secondRows,
107
+ wheelBounds,
108
+ } from '../TimeInput/timeWheelRules';
109
+ import {useTouchPresentation} from '../utils/touchPresentation';
100
110
  import {
101
111
  dateInputTouchGeometry,
102
112
  dateInputTouchSizes,
@@ -406,45 +416,6 @@ function normalizeISOTime(
406
416
  return parsed ? formatISOTime(parsed, hasSeconds) : undefined;
407
417
  }
408
418
 
409
- function timeToSeconds(time: ISOTimeString | undefined): number | null {
410
- if (time === undefined) {
411
- return null;
412
- }
413
- const parsed = parseISOTime(time);
414
- return parsed == null
415
- ? null
416
- : parsed.hour * 3600 + parsed.minute * 60 + parsed.second;
417
- }
418
-
419
- function twoDigits(value: number): string {
420
- return String(value).padStart(2, '0');
421
- }
422
-
423
- function hour12From24(hour: number): number {
424
- return hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
425
- }
426
-
427
- function hour24From12(hour: number, meridiem: number): number {
428
- if (meridiem === 0) {
429
- return hour === 12 ? 0 : hour;
430
- }
431
- return hour === 12 ? 12 : hour + 12;
432
- }
433
-
434
- function rangeOverlaps(
435
- startSecond: number,
436
- endSecond: number,
437
- min: ISOTimeString | undefined,
438
- max: ISOTimeString | undefined,
439
- ): boolean {
440
- const minSecond = timeToSeconds(min);
441
- const maxSecond = timeToSeconds(max);
442
- return (
443
- (minSecond == null || endSecond >= minSecond) &&
444
- (maxSecond == null || startSecond <= maxSecond)
445
- );
446
- }
447
-
448
419
  type TouchDateTimePanel = 'date' | 'time';
449
420
 
450
421
  export function TouchDateTimeField({
@@ -584,6 +555,7 @@ export function TouchDateTimeField({
584
555
  );
585
556
  const [activePanel, setActivePanel] = useState<TouchDateTimePanel>('date');
586
557
  const [isWheelOpen, setIsWheelOpen] = useState(false);
558
+ const {isTouch} = useTouchPresentation();
587
559
  const [draftTime, setDraftTime] = useState<ISOTimeString | undefined>(() =>
588
560
  normalizeISOTime(splitDateTime(value).time, hasSeconds),
589
561
  );
@@ -852,108 +824,32 @@ export function TouchDateTimeField({
852
824
  );
853
825
 
854
826
  const timeBounds = timeBoundsForDate(valueParts.date);
855
- const hourOptions: WheelOption[] = useMemo(() => {
856
- if (hourFormat === '24h') {
857
- return Array.from({length: 24}, (_, hour) => ({
858
- value: hour,
859
- label: twoDigits(hour),
860
- isDisabled: !rangeOverlaps(
861
- hour * 3600,
862
- hour * 3600 + 3599,
863
- timeBounds.min,
864
- timeBounds.max,
865
- ),
866
- }));
867
- }
868
- const meridiem = parsedWheelTime.hour < 12 ? 0 : 1;
869
- // In 12-hour mode the hour column is interpreted inside the active AM/PM
870
- // half, so disabled rows describe reachability within that half only.
871
- return Array.from({length: 12}, (_, index) => {
872
- const hour = index + 1;
873
- const hour24 = hour24From12(hour, meridiem);
874
- return {
875
- value: hour,
876
- label: String(hour),
877
- // Keys step in time order: 12 PM comes before 1 PM.
878
- order: hour24,
879
- isDisabled: !rangeOverlaps(
880
- hour24 * 3600,
881
- hour24 * 3600 + 3599,
882
- timeBounds.min,
883
- timeBounds.max,
884
- ),
885
- };
886
- });
887
- }, [hourFormat, parsedWheelTime.hour, timeBounds.min, timeBounds.max]);
888
-
827
+ const bounds = useMemo(
828
+ () => wheelBounds(timeBounds.min, timeBounds.max),
829
+ [timeBounds.min, timeBounds.max],
830
+ );
831
+ const hourOptions: WheelOption[] = useMemo(
832
+ () => hourRowsInHalf(parsedWheelTime.hour, hourFormat, bounds),
833
+ [bounds, hourFormat, parsedWheelTime.hour],
834
+ );
889
835
  const minuteOptions: WheelOption[] = useMemo(
890
- () =>
891
- Array.from({length: 60}, (_, minute) => {
892
- const start = parsedWheelTime.hour * 3600 + minute * 60;
893
- return {
894
- value: minute,
895
- label: twoDigits(minute),
896
- isDisabled: !rangeOverlaps(
897
- start,
898
- start + (hasSeconds ? 59 : 0),
899
- timeBounds.min,
900
- timeBounds.max,
901
- ),
902
- };
903
- }),
904
- [hasSeconds, parsedWheelTime.hour, timeBounds.min, timeBounds.max],
836
+ () => minuteRows(parsedWheelTime.hour, hasSeconds, bounds),
837
+ [bounds, hasSeconds, parsedWheelTime.hour],
905
838
  );
906
-
907
839
  const secondOptions: WheelOption[] = useMemo(
908
- () =>
909
- Array.from({length: 60}, (_, second) => ({
910
- value: second,
911
- label: twoDigits(second),
912
- isDisabled: !isTimeInRange(
913
- formatISOTime(
914
- {
915
- hour: parsedWheelTime.hour,
916
- minute: parsedWheelTime.minute,
917
- second,
918
- },
919
- true,
920
- ),
921
- timeBounds.min,
922
- timeBounds.max,
923
- ),
924
- })),
925
- [
926
- parsedWheelTime.hour,
927
- parsedWheelTime.minute,
928
- timeBounds.min,
929
- timeBounds.max,
930
- ],
840
+ () => secondRows(parsedWheelTime.hour, parsedWheelTime.minute, bounds),
841
+ [bounds, parsedWheelTime.hour, parsedWheelTime.minute],
931
842
  );
932
-
933
843
  const meridiemOptions: WheelOption[] = useMemo(
934
- () => [
935
- {
936
- value: 0,
937
- label: t('@astryx.dateTimeInput.meridiemAM'),
938
- isDisabled: !rangeOverlaps(
939
- 0,
940
- 11 * 3600 + 3599,
941
- timeBounds.min,
942
- timeBounds.max,
943
- ),
944
- },
945
- {
946
- value: 1,
947
- label: t('@astryx.dateTimeInput.meridiemPM'),
948
- isDisabled: !rangeOverlaps(
949
- 12 * 3600,
950
- 23 * 3600 + 3599,
951
- timeBounds.min,
952
- timeBounds.max,
953
- ),
954
- },
955
- ],
956
- [t, timeBounds.min, timeBounds.max],
844
+ () =>
845
+ meridiemRows(
846
+ {
847
+ am: t('@astryx.dateTimeInput.meridiemAM'),
848
+ pm: t('@astryx.dateTimeInput.meridiemPM'),
849
+ },
850
+ bounds,
851
+ ),
852
+ [bounds, t],
957
853
  );
958
854
 
959
855
  const handleVisibleMonthChange = useCallback(
@@ -1225,49 +1121,69 @@ export function TouchDateTimeField({
1225
1121
  activePanel !== 'time' && styles.touchPanelHidden,
1226
1122
  )}>
1227
1123
  <div {...stylex.props(styles.touchTimeWheels)}>
1228
- <Wheel
1229
- label={t('@astryx.dateTimeInput.hourWheel')}
1230
- options={hourOptions}
1231
- value={
1232
- hourFormat === '24h'
1233
- ? parsedWheelTime.hour
1234
- : hour12From24(parsedWheelTime.hour)
1235
- }
1236
- isActive={activePanel === 'time'}
1237
- onChange={hour =>
1238
- commitWheelTime(
1239
- hourFormat === '24h'
1240
- ? composeWheelTime({hour24: hour})
1241
- : composeWheelTime({hour12: hour}),
1242
- )
1243
- }
1244
- />
1245
- <Wheel
1246
- label={t('@astryx.dateTimeInput.minuteWheel')}
1247
- options={minuteOptions}
1248
- value={parsedWheelTime.minute}
1249
- isActive={activePanel === 'time'}
1250
- onChange={minute => commitWheelTime(composeWheelTime({minute}))}
1251
- />
1252
- {hasSeconds && (
1253
- <Wheel
1254
- label={t('@astryx.dateTimeInput.secondWheel')}
1255
- options={secondOptions}
1256
- value={parsedWheelTime.second}
1124
+ {isTouch ? (
1125
+ <TimeWheels
1126
+ time={parsedWheelTime}
1127
+ hourFormat={hourFormat}
1128
+ hasSeconds={hasSeconds}
1129
+ min={timeBounds.min}
1130
+ max={timeBounds.max}
1257
1131
  isActive={activePanel === 'time'}
1258
- onChange={second => commitWheelTime(composeWheelTime({second}))}
1259
- />
1260
- )}
1261
- {hourFormat === '12h' && (
1262
- <Wheel
1263
- label={t('@astryx.dateTimeInput.meridiemWheel')}
1264
- options={meridiemOptions}
1265
- value={parsedWheelTime.hour < 12 ? 0 : 1}
1266
- isActive={activePanel === 'time'}
1267
- onChange={meridiem =>
1268
- commitWheelTime(composeWheelTime({meridiem}))
1132
+ onCommit={time =>
1133
+ commitWheelTime(formatISOTime(time, hasSeconds))
1269
1134
  }
1270
1135
  />
1136
+ ) : (
1137
+ <>
1138
+ <Wheel
1139
+ label={t('@astryx.dateTimeInput.hourWheel')}
1140
+ options={hourOptions}
1141
+ value={
1142
+ hourFormat === '24h'
1143
+ ? parsedWheelTime.hour
1144
+ : hour12From24(parsedWheelTime.hour)
1145
+ }
1146
+ isActive={activePanel === 'time'}
1147
+ onChange={hour =>
1148
+ commitWheelTime(
1149
+ hourFormat === '24h'
1150
+ ? composeWheelTime({hour24: hour})
1151
+ : composeWheelTime({hour12: hour}),
1152
+ )
1153
+ }
1154
+ />
1155
+ <Wheel
1156
+ label={t('@astryx.dateTimeInput.minuteWheel')}
1157
+ options={minuteOptions}
1158
+ value={parsedWheelTime.minute}
1159
+ isActive={activePanel === 'time'}
1160
+ onChange={minute =>
1161
+ commitWheelTime(composeWheelTime({minute}))
1162
+ }
1163
+ />
1164
+ {hasSeconds && (
1165
+ <Wheel
1166
+ label={t('@astryx.dateTimeInput.secondWheel')}
1167
+ options={secondOptions}
1168
+ value={parsedWheelTime.second}
1169
+ isActive={activePanel === 'time'}
1170
+ onChange={second =>
1171
+ commitWheelTime(composeWheelTime({second}))
1172
+ }
1173
+ />
1174
+ )}
1175
+ {hourFormat === '12h' && (
1176
+ <Wheel
1177
+ label={t('@astryx.dateTimeInput.meridiemWheel')}
1178
+ options={meridiemOptions}
1179
+ value={parsedWheelTime.hour < 12 ? 0 : 1}
1180
+ isActive={activePanel === 'time'}
1181
+ onChange={meridiem =>
1182
+ commitWheelTime(composeWheelTime({meridiem}))
1183
+ }
1184
+ />
1185
+ )}
1186
+ </>
1271
1187
  )}
1272
1188
  </div>
1273
1189
  <div {...stylex.props(styles.touchFooter)}>
@@ -401,7 +401,12 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
401
401
  // Tap-to-open on touch; on a mouse this does nothing and hover still rules.
402
402
  const handlePointerDown = useCallback(
403
403
  (event: PointerEvent) => {
404
- handleTouchPointerDown(event);
404
+ // A pen in range starts the hover show before it lands. Left pending,
405
+ // that show opens the card during the press. The tap then closes it.
406
+ if (handleTouchPointerDown(event) && showTimeoutRef.current) {
407
+ clearTimeout(showTimeoutRef.current);
408
+ showTimeoutRef.current = null;
409
+ }
405
410
  },
406
411
  [handleTouchPointerDown],
407
412
  );
@@ -94,8 +94,13 @@ export function Overlay({
94
94
  align,
95
95
  });
96
96
 
97
- // Border radius: mirror first child's radius onto the wrapper.
98
- // Only the component needs this — hook consumers have their own radius.
97
+ // Copies the first child's radius onto the wrapper, so the scrim clips to
98
+ // the media's rounded corners. This is a layout read of the mounted DOM, and
99
+ // no user interaction causes it. It runs again when a hidden Activity shows
100
+ // the wrapper, because the child can change while it is hidden. A ref
101
+ // callback runs again when a caller's ref changes identity, so it cannot
102
+ // keep this once-per-show timing. A useOverlay consumer sets the radius on
103
+ // its own element.
99
104
  useIsomorphicLayoutEffect(() => {
100
105
  const el = overlay.containerRef.current;
101
106
  if (!el) {
@@ -211,7 +211,7 @@ export const docs = {
211
211
  name: 'presentation',
212
212
  type: "'popover' | 'bottom-sheet' | 'adaptive'",
213
213
  description:
214
- 'How the field presents filter editing. adaptive (the default) uses the bottom sheet on compact touch screens and the popover otherwise, resolved the same way Selector resolves it; the server render and the hydrating render are the popover. popover is the tokenizer field with its typeahead and the editor popover on every device. bottom-sheet makes the whole field, on every device, one trigger showing display-only filter tokens: it opens a Filters sheet listing the filters as rows that edit or remove a filter, with Add filter, Done, and Clear all (which asks first and keeps read-only filters); an Add filter sheet with the fields (or the content search with inline suggestions) opens above it, and a value step that stages operator → value and confirms from its footer opens above that. Dismissing a step reveals the one beneath. List values edit through the Tokenizer\'s own sheet. In the sheet presentation menuWidth and tokenOverflowBehavior have no surface to act on and warn in development. Pass presentation="popover" to keep the tokenizer field and the editor popover on every device.',
214
+ 'How the field presents filter editing. adaptive (the default) uses the bottom sheet on compact touch screens and the popover otherwise, resolved the same way Selector resolves it; the server render and the hydrating render are the popover. popover is the tokenizer field with its typeahead and the editor popover on every device. bottom-sheet makes the whole field, on every device, one trigger showing display-only filter tokens: it opens a Filters sheet listing the filters as rows that edit or remove a filter, with Add filter, Done, and Clear all (which asks first and keeps read-only filters); an Add filter sheet with the fields (or the content search with inline suggestions) opens above it, and a value step that stages operator → value and confirms from its footer opens above that. Dismissing a step reveals the one beneath. The flow edits a draft: adding, editing and removing filters and Clear all change the draft, and onChange is called when Done applies it, once per edit in order; dismissing the flow with a changed draft asks before discarding it. List values edit through the Tokenizer\'s own sheet. In the sheet presentation menuWidth and tokenOverflowBehavior have no surface to act on and warn in development. Pass presentation="popover" to keep the tokenizer field and the editor popover on every device.',
215
215
  default: "'adaptive'",
216
216
  },
217
217
  ],
@@ -484,7 +484,7 @@ export const docsZh = {
484
484
  name: 'presentation',
485
485
  type: "'popover' | 'bottom-sheet' | 'adaptive'",
486
486
  description:
487
- '字段如何呈现筛选编辑。adaptive(默认)在紧凑触摸屏上使用底部面板,其他情况使用 popover,解析方式与 Selector 相同;服务端渲染和水合期间的渲染均为 popover。popover 在所有设备上为带有预输入和编辑弹出层的标记字段。bottom-sheet 在所有设备上将整个字段变为一个显示只读筛选标记的触发按钮——点按后打开列出筛选项的「筛选」面板,每行可编辑或移除筛选(含添加筛选、完成、全部清除;全部清除会先确认,并保留只读筛选);列出字段(或带内联建议的内容搜索)的「添加筛选」面板在其上方打开,按运算符 → 值暂存并在底部确认的取值步骤再在其上方打开。关闭某一步会露出下方的那一步。列表值通过 Tokenizer 自身的面板编辑。在底部面板呈现中,menuWidth 和 tokenOverflowBehavior 没有作用对象,并会在开发环境中发出警告。传入 presentation="popover" 可在所有设备上保留标记字段和编辑弹出层。',
487
+ '字段如何呈现筛选编辑。adaptive(默认)在紧凑触摸屏上使用底部面板,其他情况使用 popover,解析方式与 Selector 相同;服务端渲染和水合期间的渲染均为 popover。popover 在所有设备上为带有预输入和编辑弹出层的标记字段。bottom-sheet 在所有设备上将整个字段变为一个显示只读筛选标记的触发按钮——点按后打开列出筛选项的「筛选」面板,每行可编辑或移除筛选(含添加筛选、完成、全部清除;全部清除会先确认,并保留只读筛选);列出字段(或带内联建议的内容搜索)的「添加筛选」面板在其上方打开,按运算符 → 值暂存并在底部确认的取值步骤再在其上方打开。关闭某一步会露出下方的那一步。整个流程编辑的是草稿:添加、编辑、移除筛选和全部清除都只改动草稿,点按「完成」应用草稿时才按编辑顺序逐次触发 onChange;草稿有改动时关闭流程会先询问是否放弃。列表值通过 Tokenizer 自身的面板编辑。在底部面板呈现中,menuWidth 和 tokenOverflowBehavior 没有作用对象,并会在开发环境中发出警告。传入 presentation="popover" 可在所有设备上保留标记字段和编辑弹出层。',
488
488
  default: "'adaptive'",
489
489
  },
490
490
  ],
@@ -430,8 +430,10 @@ export interface PowerSearchProps extends Omit<
430
430
  * above it with the fields (or the content search with inline
431
431
  * suggestions), and a value step opens above that, stages operator →
432
432
  * value, and confirms from its footer. Dismissing a step reveals the
433
- * one beneath. Suited to compact touch screens, where the editor
434
- * popover competes with the software keyboard.
433
+ * one beneath. The flow edits a draft: `onChange` is called when Done
434
+ * applies it, once per edit in order, and a dismissal with a changed
435
+ * draft asks before discarding it. Suited to compact touch screens,
436
+ * where the editor popover competes with the software keyboard.
435
437
  * - `'adaptive'`: the bottom sheet on compact coarse-pointer screens,
436
438
  * otherwise the popover — the same resolution Selector uses. The server
437
439
  * render and the hydrating render are the popover.