@astryxdesign/core 0.7.7-canary.bf14580 → 0.7.7-canary.c19ca12

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 (266) hide show
  1. package/dist/AlertDialog/AlertDialog.d.ts +4 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/dist/AlertDialog/AlertDialog.js +36 -6
  4. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheet.js +76 -6
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +23 -4
  8. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  9. package/dist/BottomSheet/bottomSheetStack.js +5 -4
  10. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  11. package/dist/BottomSheet/useSheetGestures.js +3 -1
  12. package/dist/Button/Button.d.ts.map +1 -1
  13. package/dist/Button/Button.js +47 -3
  14. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  15. package/dist/CheckboxInput/CheckboxInput.js +13 -2
  16. package/dist/ComplexSelector/ComplexSelector.d.ts +4 -1
  17. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  18. package/dist/ComplexSelector/ComplexSelector.js +14 -4
  19. package/dist/ContextMenu/ContextMenu.js +2 -2
  20. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  21. package/dist/DateInput/MonthScroller.js +13 -27
  22. package/dist/DateInput/Wheel.d.ts.map +1 -1
  23. package/dist/DateInput/Wheel.js +22 -41
  24. package/dist/Dialog/Dialog.d.ts.map +1 -1
  25. package/dist/Dialog/Dialog.js +7 -31
  26. package/dist/Field/Field.js +2 -2
  27. package/dist/HoverCard/HoverCard.d.ts +4 -3
  28. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  29. package/dist/HoverCard/HoverCard.js +27 -2
  30. package/dist/HoverCard/useHoverCard.d.ts +5 -4
  31. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  32. package/dist/HoverCard/useHoverCard.js +17 -31
  33. package/dist/Icon/Icon.d.ts +5 -1
  34. package/dist/Icon/Icon.d.ts.map +1 -1
  35. package/dist/Icon/Icon.js +5 -2
  36. package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
  37. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  38. package/dist/Layer/layerAnimations.stylex.js +25 -20
  39. package/dist/Layer/usePressPin.d.ts +17 -5
  40. package/dist/Layer/usePressPin.d.ts.map +1 -1
  41. package/dist/Layer/usePressPin.js +62 -12
  42. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  43. package/dist/Lightbox/Lightbox.js +18 -0
  44. package/dist/PowerSearch/PowerSearch.d.ts +36 -3
  45. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  46. package/dist/PowerSearch/PowerSearch.js +522 -269
  47. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +100 -0
  48. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -0
  49. package/dist/PowerSearch/PowerSearchBottomSheet.js +1913 -0
  50. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +32 -1
  51. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  52. package/dist/PowerSearch/PowerSearchEditPopover.js +17 -6
  53. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +38 -3
  54. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  55. package/dist/PowerSearch/PowerSearchValueEditor.js +170 -61
  56. package/dist/PowerSearch/formatFilterValue.d.ts +7 -0
  57. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  58. package/dist/PowerSearch/formatFilterValue.js +11 -1
  59. package/dist/PowerSearch/formatTokenValue.d.ts +28 -0
  60. package/dist/PowerSearch/formatTokenValue.d.ts.map +1 -0
  61. package/dist/PowerSearch/formatTokenValue.js +160 -0
  62. package/dist/PowerSearch/powerSearchSheetUtils.d.ts +65 -0
  63. package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +1 -0
  64. package/dist/PowerSearch/powerSearchSheetUtils.js +168 -0
  65. package/dist/RadioList/RadioList.d.ts.map +1 -1
  66. package/dist/RadioList/RadioList.js +3 -2
  67. package/dist/RadioList/RadioListItem.d.ts +1 -1
  68. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  69. package/dist/RadioList/RadioListItem.js +5 -3
  70. package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -1
  71. package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
  72. package/dist/Slider/sliderTouchController.js +2 -2
  73. package/dist/Spinner/Spinner.d.ts +1 -1
  74. package/dist/Spinner/Spinner.d.ts.map +1 -1
  75. package/dist/Spinner/Spinner.js +60 -58
  76. package/dist/Switch/useSwitchTouch.js +4 -4
  77. package/dist/Table/BaseTable.d.ts.map +1 -1
  78. package/dist/Table/BaseTable.js +29 -8
  79. package/dist/TextInput/TextInput.d.ts +2 -2
  80. package/dist/TextInput/TextInput.d.ts.map +1 -1
  81. package/dist/TextInput/TextInput.js +10 -7
  82. package/dist/Toast/Toast.d.ts +11 -1
  83. package/dist/Toast/Toast.d.ts.map +1 -1
  84. package/dist/Toast/Toast.js +25 -15
  85. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  86. package/dist/Toast/ToastViewport.js +94 -34
  87. package/dist/Toast/types.d.ts +9 -3
  88. package/dist/Toast/types.d.ts.map +1 -1
  89. package/dist/Token/Token.d.ts +2 -2
  90. package/dist/Token/Token.d.ts.map +1 -1
  91. package/dist/Token/Token.js +12 -4
  92. package/dist/Token/TokenNamingContext.d.ts +14 -0
  93. package/dist/Token/TokenNamingContext.d.ts.map +1 -0
  94. package/dist/Token/TokenNamingContext.js +23 -0
  95. package/dist/Tooltip/useTooltip.d.ts +19 -1
  96. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  97. package/dist/Tooltip/useTooltip.js +34 -53
  98. package/dist/Typeahead/TypeaheadItem.d.ts +2 -1
  99. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  100. package/dist/animator/createAnimator.d.ts +98 -0
  101. package/dist/animator/createAnimator.d.ts.map +1 -0
  102. package/dist/animator/createAnimator.js +259 -0
  103. package/dist/astryx.css +22 -6
  104. package/dist/gestures/GestureHost.d.ts +27 -1
  105. package/dist/gestures/GestureHost.d.ts.map +1 -1
  106. package/dist/gestures/GestureHost.js +90 -8
  107. package/dist/gestures/PressTracker.d.ts.map +1 -1
  108. package/dist/gestures/PressTracker.js +4 -3
  109. package/dist/hooks/actionGroupTracker.d.ts +4 -1
  110. package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
  111. package/dist/hooks/actionGroupTracker.js +13 -3
  112. package/dist/hooks/index.d.ts +1 -0
  113. package/dist/hooks/index.d.ts.map +1 -1
  114. package/dist/hooks/index.js +1 -0
  115. package/dist/hooks/pressGroupContext.d.ts +6 -0
  116. package/dist/hooks/pressGroupContext.d.ts.map +1 -0
  117. package/dist/hooks/pressGroupContext.js +20 -0
  118. package/dist/hooks/useActionGroupTracking.d.ts +53 -0
  119. package/dist/hooks/useActionGroupTracking.d.ts.map +1 -0
  120. package/dist/hooks/useActionGroupTracking.js +174 -0
  121. package/dist/hooks/useAnimator.d.ts +41 -0
  122. package/dist/hooks/useAnimator.d.ts.map +1 -0
  123. package/dist/hooks/useAnimator.js +70 -0
  124. package/dist/hooks/useAttachment.d.ts +1 -1
  125. package/dist/hooks/useAttachment.js +1 -1
  126. package/dist/hooks/useInputStatusIcon.d.ts +1 -1
  127. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  128. package/dist/hooks/useInputStatusIcon.js +2 -2
  129. package/dist/hooks/useLongPress.d.ts +6 -2
  130. package/dist/hooks/useLongPress.d.ts.map +1 -1
  131. package/dist/hooks/useLongPress.js +95 -15
  132. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  133. package/dist/hooks/useMenuPress.js +4 -2
  134. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +8 -0
  135. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -0
  136. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +42 -0
  137. package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
  138. package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +6 -0
  139. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +8 -0
  140. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +1 -0
  141. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +14 -0
  142. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  143. package/dist/i18n/generated-locales/en.generated.js +30 -0
  144. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  145. package/dist/i18n/generated-locales/pseudo.generated.js +30 -0
  146. package/dist/index.d.ts +1 -0
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +4 -0
  149. package/dist/internal/indicatorPress.stylex.d.ts +20 -0
  150. package/dist/internal/indicatorPress.stylex.d.ts.map +1 -0
  151. package/dist/internal/indicatorPress.stylex.js +26 -0
  152. package/dist/motion/animator.d.ts +14 -14
  153. package/dist/motion/animator.d.ts.map +1 -1
  154. package/dist/motion/animator.js +41 -18
  155. package/dist/motion/reducedMotion.d.ts +24 -0
  156. package/dist/motion/reducedMotion.d.ts.map +1 -1
  157. package/dist/motion/reducedMotion.js +78 -1
  158. package/dist/motion/uikit/alert.d.ts +41 -0
  159. package/dist/motion/uikit/alert.d.ts.map +1 -0
  160. package/dist/motion/uikit/alert.js +43 -0
  161. package/dist/privateAttributes/pressGroupItem.stylex.d.ts +15 -0
  162. package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +1 -0
  163. package/dist/privateAttributes/pressGroupItem.stylex.js +23 -0
  164. package/dist/utils/clickGuard.d.ts +8 -1
  165. package/dist/utils/clickGuard.d.ts.map +1 -1
  166. package/dist/utils/clickGuard.js +24 -1
  167. package/dist/utils/dateParser.d.ts.map +1 -1
  168. package/dist/utils/dateParser.js +12 -0
  169. package/dist/utils/focusReturn.d.ts +15 -0
  170. package/dist/utils/focusReturn.d.ts.map +1 -1
  171. package/dist/utils/focusReturn.js +28 -0
  172. package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
  173. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  174. package/dist/utils/interactionOverlay.stylex.js +3 -2
  175. package/dist/utils/pressFeedback.d.ts.map +1 -1
  176. package/dist/utils/pressFeedback.js +19 -6
  177. package/dist/utils/triggerFocus.d.ts +36 -0
  178. package/dist/utils/triggerFocus.d.ts.map +1 -0
  179. package/dist/utils/triggerFocus.js +65 -0
  180. package/locales/en.json +120 -0
  181. package/locales/pseudo.json +90 -0
  182. package/package.json +9 -4
  183. package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
  184. package/src/AlertDialog/AlertDialog.tsx +37 -5
  185. package/src/BottomSheet/BottomSheet.tsx +85 -5
  186. package/src/BottomSheet/BottomSheetPanel.tsx +40 -5
  187. package/src/BottomSheet/bottomSheetStack.ts +8 -4
  188. package/src/BottomSheet/useSheetGestures.ts +3 -1
  189. package/src/Button/Button.tsx +63 -5
  190. package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
  191. package/src/CheckboxInput/CheckboxInput.tsx +36 -9
  192. package/src/ComplexSelector/ComplexSelector.tsx +20 -5
  193. package/src/ContextMenu/ContextMenu.tsx +2 -2
  194. package/src/DateInput/MonthScroller.tsx +18 -22
  195. package/src/DateInput/Wheel.tsx +30 -32
  196. package/src/Dialog/Dialog.tsx +10 -31
  197. package/src/Field/Field.tsx +2 -2
  198. package/src/HoverCard/HoverCard.doc.mjs +4 -2
  199. package/src/HoverCard/HoverCard.tsx +35 -4
  200. package/src/HoverCard/useHoverCard.doc.mjs +178 -36
  201. package/src/HoverCard/useHoverCard.tsx +46 -52
  202. package/src/Icon/Icon.doc.mjs +49 -8
  203. package/src/Icon/Icon.tsx +15 -7
  204. package/src/Layer/layerAnimations.stylex.ts +24 -16
  205. package/src/Layer/usePressPin.ts +81 -15
  206. package/src/Lightbox/Lightbox.tsx +18 -0
  207. package/src/PowerSearch/PowerSearch.doc.mjs +54 -5
  208. package/src/PowerSearch/PowerSearch.tsx +734 -288
  209. package/src/PowerSearch/PowerSearchBottomSheet.tsx +2515 -0
  210. package/src/PowerSearch/PowerSearchEditPopover.tsx +28 -6
  211. package/src/PowerSearch/PowerSearchValueEditor.tsx +203 -38
  212. package/src/PowerSearch/formatFilterValue.ts +18 -1
  213. package/src/PowerSearch/formatTokenValue.ts +215 -0
  214. package/src/PowerSearch/powerSearchSheetUtils.ts +224 -0
  215. package/src/RadioList/RadioList.tsx +3 -2
  216. package/src/RadioList/RadioListItem.tsx +5 -3
  217. package/src/SegmentedControl/useSegmentedSelectionMotion.ts +6 -3
  218. package/src/Slider/sliderTouchController.ts +2 -2
  219. package/src/Spinner/Spinner.tsx +77 -63
  220. package/src/Switch/useSwitchTouch.ts +4 -4
  221. package/src/Table/BaseTable.tsx +46 -12
  222. package/src/TextInput/TextInput.tsx +11 -10
  223. package/src/Toast/Toast.doc.mjs +4 -3
  224. package/src/Toast/Toast.tsx +52 -32
  225. package/src/Toast/ToastViewport.tsx +121 -35
  226. package/src/Toast/types.ts +9 -3
  227. package/src/Token/Token.tsx +13 -4
  228. package/src/Token/TokenNamingContext.ts +28 -0
  229. package/src/Tooltip/useTooltip.doc.mjs +24 -0
  230. package/src/Tooltip/useTooltip.tsx +61 -56
  231. package/src/Typeahead/TypeaheadItem.doc.mjs +6 -3
  232. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  233. package/src/animator/createAnimator.doc.mjs +153 -0
  234. package/src/animator/createAnimator.ts +324 -0
  235. package/src/gestures/GestureHost.ts +105 -10
  236. package/src/gestures/PressTracker.ts +4 -3
  237. package/src/gestures/README.md +7 -1
  238. package/src/hooks/actionGroupTracker.ts +20 -5
  239. package/src/hooks/index.ts +2 -0
  240. package/src/hooks/pressGroupContext.ts +21 -0
  241. package/src/hooks/useActionGroupTracking.ts +224 -0
  242. package/src/hooks/useAnimator.doc.mjs +109 -0
  243. package/src/hooks/useAnimator.ts +67 -0
  244. package/src/hooks/useAttachment.ts +1 -1
  245. package/src/hooks/useInputStatusIcon.tsx +3 -2
  246. package/src/hooks/useLongPress.doc.mjs +2 -2
  247. package/src/hooks/useLongPress.ts +136 -15
  248. package/src/hooks/useMenuPress.ts +8 -2
  249. package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +43 -0
  250. package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +6 -0
  251. package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +15 -0
  252. package/src/i18n/generated-locales/en.generated.ts +41 -0
  253. package/src/i18n/generated-locales/pseudo.generated.ts +42 -0
  254. package/src/index.ts +13 -0
  255. package/src/internal/indicatorPress.stylex.ts +24 -0
  256. package/src/motion/README.md +8 -5
  257. package/src/motion/animator.ts +61 -29
  258. package/src/motion/reducedMotion.ts +79 -1
  259. package/src/motion/uikit/alert.ts +43 -0
  260. package/src/privateAttributes/pressGroupItem.stylex.ts +23 -0
  261. package/src/utils/clickGuard.ts +37 -1
  262. package/src/utils/dateParser.ts +16 -0
  263. package/src/utils/focusReturn.ts +30 -0
  264. package/src/utils/interactionOverlay.stylex.ts +3 -2
  265. package/src/utils/pressFeedback.ts +19 -6
  266. package/src/utils/triggerFocus.ts +80 -0
@@ -5,7 +5,8 @@
5
5
  /**
6
6
  * @file PowerSearchEditPopover.tsx
7
7
  * @input InternalConfig, PartialFilter
8
- * @output Edit popover content with field/operator selectors and value editor
8
+ * @output Edit popover content with field/operator selectors and value editor;
9
+ * the built-in NestedEditor the sheet presentation's value step shares
9
10
  * @position Sub-component; consumed by PowerSearch
10
11
  *
11
12
  * SYNC: When modified, update:
@@ -384,7 +385,7 @@ function NestedSubFilterRow({
384
385
  // Nested editor
385
386
  // =============================================================================
386
387
 
387
- interface NestedEditorProps {
388
+ export interface NestedEditorProps {
388
389
  config: InternalConfig;
389
390
  partialFilter: PartialFilter;
390
391
  operatorOptions: {value: string; label: string}[];
@@ -392,9 +393,25 @@ interface NestedEditorProps {
392
393
  onPartialFilterChange: (filter: PartialFilter) => void;
393
394
  maxMenuItems?: number;
394
395
  isReadOnly: boolean;
396
+ /** Styles for the editor's tree root (the sheet makes it a query container). */
397
+ xstyle?: stylex.StyleXStyles;
398
+ /**
399
+ * Whether the tree root offers the group-operator picker when the field has
400
+ * several operators. The popover shows it; the sheet presentation's value
401
+ * step owns the operator choice in its own Operator group and hides it.
402
+ * @default true
403
+ */
404
+ hasRootOperatorPicker?: boolean;
395
405
  }
396
406
 
397
- function NestedEditor({
407
+ /**
408
+ * The built-in editor for a `nested` value: a tree of sub-filters with Add,
409
+ * field/operator/value rows and removal. The popover renders it below its
410
+ * field selector; the sheet presentation's value step renders the same editor
411
+ * in its body, so a `nested` filter is created
412
+ * and edited the same way in both presentations.
413
+ */
414
+ export function NestedEditor({
398
415
  config,
399
416
  partialFilter,
400
417
  operatorOptions,
@@ -402,6 +419,8 @@ function NestedEditor({
402
419
  onPartialFilterChange,
403
420
  maxMenuItems,
404
421
  isReadOnly,
422
+ xstyle,
423
+ hasRootOperatorPicker = true,
405
424
  }: NestedEditorProps) {
406
425
  const t = useComponentTranslator();
407
426
  const [subFilters, setSubFilters] = useState<EditablePartialFilter[]>(() => {
@@ -587,7 +606,7 @@ function NestedEditor({
587
606
 
588
607
  const rootLabel = (
589
608
  <div {...stylex.props(styles.nestedRootLabel)}>
590
- {operatorOptions.length > 1 ? (
609
+ {hasRootOperatorPicker && operatorOptions.length > 1 ? (
591
610
  <div {...stylex.props(styles.operatorSelector)}>
592
611
  <Selector
593
612
  label={t('@astryx.powersearch.editor.groupOperator')}
@@ -600,7 +619,10 @@ function NestedEditor({
600
619
  />
601
620
  </div>
602
621
  ) : (
603
- (operatorOptions[0]?.label ?? t('@astryx.powersearch.editor.group'))
622
+ (operatorOptions.find(option => option.value === partialFilter.operator)
623
+ ?.label ??
624
+ operatorOptions[0]?.label ??
625
+ t('@astryx.powersearch.editor.group'))
604
626
  )}
605
627
  </div>
606
628
  );
@@ -614,7 +636,7 @@ function NestedEditor({
614
636
  },
615
637
  ];
616
638
 
617
- return <TreeList items={items} density="balanced" />;
639
+ return <TreeList items={items} density="balanced" xstyle={xstyle} />;
618
640
  }
619
641
 
620
642
  // =============================================================================
@@ -4,9 +4,12 @@
4
4
 
5
5
  /**
6
6
  * @file PowerSearchValueEditor.tsx
7
- * @input OperatorValue, FilterValue, onChange callback
7
+ * @input OperatorValue, FilterValue, onChange callback, and the presentation
8
+ * the editor renders in
8
9
  * @output Renders the appropriate editor for a filter value type
9
- * @position Sub-component; consumed by PowerSearchEditPopover
10
+ * @position Sub-component; consumed by PowerSearchEditPopover (popover
11
+ * presentation) and PowerSearchBottomSheet (sheet presentation, which
12
+ * renders the closed vocabularies as rows itself)
10
13
  *
11
14
  * SYNC: When modified, update:
12
15
  * - /packages/core/src/PowerSearch/index.ts
@@ -43,9 +46,10 @@ import {DateRangeInput, type DateRange} from '../DateRangeInput';
43
46
  import {resolveDateTimeRangePart} from './resolveDateTimeRangePart';
44
47
  import {TimeInput} from '../TimeInput';
45
48
  import {Selector} from '../Selector';
46
- import {Tokenizer} from '../Tokenizer';
49
+ import {Tokenizer} from '../Tokenizer/Tokenizer';
47
50
  import {Typeahead} from '../Typeahead';
48
51
  import {useLocale} from '../i18n';
52
+ import type {TranslatorFn} from '../i18n';
49
53
  import {
50
54
  registerEnglishFallback,
51
55
  useComponentTranslator,
@@ -64,8 +68,30 @@ export interface PowerSearchValueEditorProps {
64
68
  maxMenuItems?: number;
65
69
  isDisabled?: boolean;
66
70
  timezoneID?: string;
71
+ /**
72
+ * Called when a staged value no longer stands — the number input's draft is
73
+ * empty or does not parse — so the sheet presentation's confirm action
74
+ * disables again. Only the `bottom-sheet` presentation stages as typed; the
75
+ * popover commits on blur or Enter and never calls this.
76
+ */
77
+ onUnstage?: () => void;
78
+ /**
79
+ * Where the editor renders. In the `bottom-sheet` presentation the inputs
80
+ * fill the sheet's width, number inputs stage every parseable keystroke
81
+ * (the sheet's footer confirms; there is no Enter-to-save), the date range
82
+ * shows one month, and list values open the Tokenizer's own sheet titled
83
+ * with `fieldLabel`.
84
+ * @default 'popover'
85
+ */
86
+ presentation?: 'popover' | 'bottom-sheet';
87
+ /** The field's label; names the Tokenizer sheet in the sheet presentation. */
88
+ fieldLabel?: string;
67
89
  }
68
90
 
91
+ type EditorPresentation = NonNullable<
92
+ PowerSearchValueEditorProps['presentation']
93
+ >;
94
+
69
95
  // =============================================================================
70
96
  // Helpers
71
97
  // =============================================================================
@@ -113,6 +139,7 @@ function StringEditor({
113
139
  onEnter: _onEnter,
114
140
  maxMenuItems,
115
141
  isDisabled,
142
+ presentation,
116
143
  }: {
117
144
  operatorValue: OperatorValue & {type: 'string'};
118
145
  filterValue: FilterValue | undefined;
@@ -120,8 +147,10 @@ function StringEditor({
120
147
  onEnter?: () => void;
121
148
  maxMenuItems?: number;
122
149
  isDisabled?: boolean;
150
+ presentation: EditorPresentation;
123
151
  }) {
124
152
  const t = useComponentTranslator();
153
+ const width = presentation === 'bottom-sheet' ? '100%' : undefined;
125
154
  const currentValue = filterValue?.type === 'string' ? filterValue.value : '';
126
155
 
127
156
  // When a searchSource is provided, render a typeahead instead of a plain
@@ -148,6 +177,7 @@ function StringEditor({
148
177
  placeholder={t('@astryx.powersearch.valueEditor.searchPlaceholder')}
149
178
  debounceMs={150}
150
179
  maxMenuItems={maxMenuItems}
180
+ width={width}
151
181
  />
152
182
  );
153
183
  }
@@ -162,6 +192,7 @@ function StringEditor({
162
192
  onChange={(value: string) => {
163
193
  onChange({type: 'string', value});
164
194
  }}
195
+ width={width}
165
196
  />
166
197
  );
167
198
  }
@@ -172,14 +203,19 @@ function StringListEditor({
172
203
  onChange,
173
204
  maxMenuItems,
174
205
  isDisabled,
206
+ presentation,
207
+ fieldLabel,
175
208
  }: {
176
209
  operatorValue: OperatorValue & {type: 'string_list'};
177
210
  filterValue: FilterValue | undefined;
178
211
  onChange: (value: FilterValue) => void;
179
212
  maxMenuItems?: number;
180
213
  isDisabled?: boolean;
214
+ presentation: EditorPresentation;
215
+ fieldLabel?: string;
181
216
  }) {
182
217
  const t = useComponentTranslator();
218
+ const isSheet = presentation === 'bottom-sheet';
183
219
  const currentValue: SearchableItem[] = useMemo(() => {
184
220
  if (filterValue?.type !== 'string_list') {
185
221
  return [];
@@ -203,9 +239,16 @@ function StringListEditor({
203
239
  const hasCreate =
204
240
  operatorValue.isArbitraryStringAllowed || !operatorValue.searchSource;
205
241
 
242
+ // In the sheet presentation the list value edits through the Tokenizer's
243
+ // own sheet, titled with the field's label; a popover menu inside a sheet
244
+ // would stack a second anchored layer on a surface the person is already in.
206
245
  return (
207
246
  <Tokenizer
208
- label={t('@astryx.powersearch.valueEditor.values')}
247
+ label={
248
+ isSheet && fieldLabel != null
249
+ ? fieldLabel
250
+ : t('@astryx.powersearch.valueEditor.values')
251
+ }
209
252
  isLabelHidden
210
253
  isDisabled={isDisabled}
211
254
  searchSource={source}
@@ -220,6 +263,8 @@ function StringListEditor({
220
263
  debounceMs={operatorValue.searchSource ? 150 : 0}
221
264
  hasCreate={hasCreate}
222
265
  maxMenuItems={maxMenuItems}
266
+ width={isSheet ? '100%' : undefined}
267
+ experimental={isSheet ? {presentation: 'bottom-sheet'} : undefined}
223
268
  />
224
269
  );
225
270
  }
@@ -231,6 +276,8 @@ function useNumberEditorHandlers({
231
276
  isIntegerOnly,
232
277
  onChange,
233
278
  onEnter,
279
+ onUnstage,
280
+ stagesAsTyped,
234
281
  }: {
235
282
  valueType: 'integer' | 'float';
236
283
  min?: number;
@@ -238,6 +285,14 @@ function useNumberEditorHandlers({
238
285
  isIntegerOnly: boolean;
239
286
  onChange: (value: FilterValue, shouldSave?: boolean) => void;
240
287
  onEnter?: () => void;
288
+ onUnstage?: () => void;
289
+ /**
290
+ * Stage every keystroke the input would commit, and unstage when the draft
291
+ * stops parsing. The sheet presentation confirms from its footer, so the
292
+ * confirm action must enable while the person is still typing and disable
293
+ * again over an empty field; the popover commits on blur or Enter.
294
+ */
295
+ stagesAsTyped: boolean;
241
296
  }) {
242
297
  const locale = useLocale();
243
298
  const pendingDecisionRef = useRef<NumberInputCommitDecision | null>(null);
@@ -259,12 +314,30 @@ function useNumberEditorHandlers({
259
314
  }, []);
260
315
  const handleInput = useCallback(
261
316
  (event: FormEvent<HTMLElement>) => {
262
- pendingDecisionRef.current = resolveNumberInputCommit(
317
+ const decision = resolveNumberInputCommit(
263
318
  (event.currentTarget as HTMLInputElement).value,
264
319
  {min, max, isIntegerOnly, locale, hasClear: false},
265
320
  );
321
+ pendingDecisionRef.current = decision;
322
+ if (stagesAsTyped) {
323
+ if (decision.type === 'commit') {
324
+ onChange(toFilterValue(decision.value));
325
+ } else {
326
+ // Cleared, or not a number: nothing stands to confirm.
327
+ onUnstage?.();
328
+ }
329
+ }
266
330
  },
267
- [isIntegerOnly, locale, max, min],
331
+ [
332
+ isIntegerOnly,
333
+ locale,
334
+ max,
335
+ min,
336
+ onChange,
337
+ onUnstage,
338
+ stagesAsTyped,
339
+ toFilterValue,
340
+ ],
268
341
  );
269
342
  const handleEnter = useCallback(() => {
270
343
  const decision = pendingDecisionRef.current;
@@ -292,13 +365,17 @@ function IntegerEditor({
292
365
  filterValue,
293
366
  onChange,
294
367
  onEnter,
368
+ onUnstage,
295
369
  isDisabled,
370
+ presentation,
296
371
  }: {
297
372
  operatorValue: OperatorValue & {type: 'integer'};
298
373
  filterValue: FilterValue | undefined;
299
374
  onChange: (value: FilterValue, shouldSave?: boolean) => void;
300
375
  onEnter?: () => void;
376
+ onUnstage?: () => void;
301
377
  isDisabled?: boolean;
378
+ presentation: EditorPresentation;
302
379
  }) {
303
380
  const t = useComponentTranslator();
304
381
  const currentValue =
@@ -310,6 +387,8 @@ function IntegerEditor({
310
387
  isIntegerOnly: true,
311
388
  onChange,
312
389
  onEnter,
390
+ onUnstage,
391
+ stagesAsTyped: presentation === 'bottom-sheet',
313
392
  });
314
393
 
315
394
  return (
@@ -328,6 +407,7 @@ function IntegerEditor({
328
407
  units={operatorValue.units}
329
408
  isIntegerOnly
330
409
  placeholder={t('@astryx.powersearch.valueEditor.enterNumberPlaceholder')}
410
+ width={presentation === 'bottom-sheet' ? '100%' : undefined}
331
411
  />
332
412
  );
333
413
  }
@@ -337,13 +417,17 @@ function FloatEditor({
337
417
  filterValue,
338
418
  onChange,
339
419
  onEnter,
420
+ onUnstage,
340
421
  isDisabled,
422
+ presentation,
341
423
  }: {
342
424
  operatorValue: OperatorValue & {type: 'float'};
343
425
  filterValue: FilterValue | undefined;
344
426
  onChange: (value: FilterValue, shouldSave?: boolean) => void;
345
427
  onEnter?: () => void;
428
+ onUnstage?: () => void;
346
429
  isDisabled?: boolean;
430
+ presentation: EditorPresentation;
347
431
  }) {
348
432
  const t = useComponentTranslator();
349
433
  const currentValue =
@@ -355,6 +439,8 @@ function FloatEditor({
355
439
  isIntegerOnly: false,
356
440
  onChange,
357
441
  onEnter,
442
+ onUnstage,
443
+ stagesAsTyped: presentation === 'bottom-sheet',
358
444
  });
359
445
 
360
446
  return (
@@ -372,6 +458,7 @@ function FloatEditor({
372
458
  max={operatorValue.maxValue}
373
459
  units={operatorValue.units}
374
460
  placeholder={t('@astryx.powersearch.valueEditor.enterNumberPlaceholder')}
461
+ width={presentation === 'bottom-sheet' ? '100%' : undefined}
375
462
  />
376
463
  );
377
464
  }
@@ -381,11 +468,13 @@ function TimeEditor({
381
468
  filterValue,
382
469
  onChange,
383
470
  isDisabled,
471
+ presentation,
384
472
  }: {
385
473
  operatorValue: OperatorValue & {type: 'time'};
386
474
  filterValue: FilterValue | undefined;
387
475
  onChange: (value: FilterValue) => void;
388
476
  isDisabled?: boolean;
477
+ presentation: EditorPresentation;
389
478
  }) {
390
479
  const t = useComponentTranslator();
391
480
  const currentValue =
@@ -406,6 +495,7 @@ function TimeEditor({
406
495
  }}
407
496
  min={operatorValue.minValue as ISOTimeString | undefined}
408
497
  max={operatorValue.maxValue as ISOTimeString | undefined}
498
+ width={presentation === 'bottom-sheet' ? '100%' : undefined}
409
499
  />
410
500
  );
411
501
  }
@@ -414,11 +504,13 @@ function DateAbsoluteEditor({
414
504
  filterValue,
415
505
  onChange,
416
506
  isDisabled,
507
+ presentation,
417
508
  }: {
418
509
  operatorValue: OperatorValue & {type: 'date_absolute'};
419
510
  filterValue: FilterValue | undefined;
420
511
  onChange: (value: FilterValue) => void;
421
512
  isDisabled?: boolean;
513
+ presentation: EditorPresentation;
422
514
  }) {
423
515
  const t = useComponentTranslator();
424
516
  // Convert unixSeconds to ISO date string for the date input
@@ -442,10 +534,69 @@ function DateAbsoluteEditor({
442
534
  onChange({type: 'date_absolute', unixSeconds});
443
535
  }
444
536
  }}
537
+ width={presentation === 'bottom-sheet' ? '100%' : undefined}
445
538
  />
446
539
  );
447
540
  }
448
541
 
542
+ const RELATIVE_DATE_UNITS = [
543
+ {
544
+ unit: 'day',
545
+ plural: 'days',
546
+ amounts: [1, 3, 7, 14, 30],
547
+ ago: '@astryx.powersearch.sheet.relativeDaysAgo',
548
+ fromNow: '@astryx.powersearch.sheet.relativeDaysFromNow',
549
+ },
550
+ {
551
+ unit: 'week',
552
+ plural: 'weeks',
553
+ amounts: [1, 2, 4],
554
+ ago: '@astryx.powersearch.sheet.relativeWeeksAgo',
555
+ fromNow: '@astryx.powersearch.sheet.relativeWeeksFromNow',
556
+ },
557
+ {
558
+ unit: 'month',
559
+ plural: 'months',
560
+ amounts: [1, 3, 6, 12],
561
+ ago: '@astryx.powersearch.sheet.relativeMonthsAgo',
562
+ fromNow: '@astryx.powersearch.sheet.relativeMonthsFromNow',
563
+ },
564
+ ] as const;
565
+
566
+ /**
567
+ * The relative-date choices an operator offers, in display order. Shared by
568
+ * the popover's Selector and the sheet presentation's rows so both list the
569
+ * same values. With `t` the labels resolve through the catalog's ICU plural
570
+ * messages (the sheet presentation's rows); without it they are the popover
571
+ * editor's own literals. In English the two read identically.
572
+ */
573
+ export function buildRelativeDateOptions(
574
+ operatorValue: OperatorValue & {type: 'date_relative'},
575
+ t?: TranslatorFn,
576
+ ): {value: string; label: string}[] {
577
+ const result: {value: string; label: string}[] = [];
578
+ for (const {unit, plural, amounts, ago, fromNow} of RELATIVE_DATE_UNITS) {
579
+ for (const amount of amounts) {
580
+ const unitWord = amount === 1 ? unit : plural;
581
+ if (operatorValue.isPastAllowed !== false) {
582
+ result.push({
583
+ value: `${amount}${unit[0]}_ago`,
584
+ label: t ? t(ago, {count: amount}) : `${amount} ${unitWord} ago`,
585
+ });
586
+ }
587
+ if (operatorValue.isFutureAllowed !== false) {
588
+ result.push({
589
+ value: `${amount}${unit[0]}_from_now`,
590
+ label: t
591
+ ? t(fromNow, {count: amount})
592
+ : `${amount} ${unitWord} from now`,
593
+ });
594
+ }
595
+ }
596
+ }
597
+ return result;
598
+ }
599
+
449
600
  function DateRelativeEditor({
450
601
  operatorValue,
451
602
  filterValue,
@@ -461,37 +612,18 @@ function DateRelativeEditor({
461
612
  const currentValue =
462
613
  filterValue?.type === 'date_relative' ? filterValue.value : undefined;
463
614
 
464
- const options = useMemo(() => {
465
- const result: {value: string; label: string}[] = [];
466
- const units = [
467
- {unit: 'day', plural: 'days'},
468
- {unit: 'week', plural: 'weeks'},
469
- {unit: 'month', plural: 'months'},
470
- ];
471
- for (const {unit, plural} of units) {
472
- const amounts =
473
- unit === 'day'
474
- ? [1, 3, 7, 14, 30]
475
- : unit === 'week'
476
- ? [1, 2, 4]
477
- : [1, 3, 6, 12];
478
- for (const amount of amounts) {
479
- if (operatorValue.isPastAllowed !== false) {
480
- result.push({
481
- value: `${amount}${unit[0]}_ago`,
482
- label: `${amount} ${amount === 1 ? unit : plural} ago`,
483
- });
484
- }
485
- if (operatorValue.isFutureAllowed !== false) {
486
- result.push({
487
- value: `${amount}${unit[0]}_from_now`,
488
- label: `${amount} ${amount === 1 ? unit : plural} from now`,
489
- });
490
- }
491
- }
492
- }
493
- return result;
494
- }, [operatorValue.isPastAllowed, operatorValue.isFutureAllowed]);
615
+ // The choices depend on the two flags only; a config that rebuilds its
616
+ // operator objects per render keeps the same memoized list.
617
+ const {isPastAllowed, isFutureAllowed} = operatorValue;
618
+ const options = useMemo(
619
+ () =>
620
+ buildRelativeDateOptions({
621
+ type: 'date_relative',
622
+ isPastAllowed,
623
+ isFutureAllowed,
624
+ }),
625
+ [isPastAllowed, isFutureAllowed],
626
+ );
495
627
 
496
628
  return (
497
629
  <Selector
@@ -511,11 +643,13 @@ function DateRangeEditor({
511
643
  filterValue,
512
644
  onChange,
513
645
  isDisabled,
646
+ presentation,
514
647
  }: {
515
648
  operatorValue: OperatorValue & {type: 'date_range'};
516
649
  filterValue: FilterValue | undefined;
517
650
  onChange: (value: FilterValue) => void;
518
651
  isDisabled?: boolean;
652
+ presentation: EditorPresentation;
519
653
  }) {
520
654
  const t = useComponentTranslator();
521
655
  const currentValue = useMemo<DateRange | null>(() => {
@@ -569,6 +703,9 @@ function DateRangeEditor({
569
703
  value={currentValue}
570
704
  onChange={handleChange}
571
705
  hasClear={false}
706
+ // A phone-wide sheet fits one month; the popover keeps the default.
707
+ numberOfMonths={presentation === 'bottom-sheet' ? 1 : undefined}
708
+ width={presentation === 'bottom-sheet' ? '100%' : undefined}
572
709
  />
573
710
  </div>
574
711
  );
@@ -671,14 +808,19 @@ function EntityListEditor({
671
808
  onChange,
672
809
  maxMenuItems,
673
810
  isDisabled,
811
+ presentation,
812
+ fieldLabel,
674
813
  }: {
675
814
  operatorValue: OperatorValue & {type: 'entity_list'};
676
815
  filterValue: FilterValue | undefined;
677
816
  onChange: (value: FilterValue) => void;
678
817
  maxMenuItems?: number;
679
818
  isDisabled?: boolean;
819
+ presentation: EditorPresentation;
820
+ fieldLabel?: string;
680
821
  }) {
681
822
  const t = useComponentTranslator();
823
+ const isSheet = presentation === 'bottom-sheet';
682
824
  const source = useMemo<SearchSource<SearchableItem>>(() => {
683
825
  if (operatorValue.searchSource) {
684
826
  return operatorValue.searchSource;
@@ -701,9 +843,15 @@ function EntityListEditor({
701
843
  }));
702
844
  }, [filterValue]);
703
845
 
846
+ // Sheet presentation: the Tokenizer's own sheet, titled with the field
847
+ // label, as for string_list.
704
848
  return (
705
849
  <Tokenizer
706
- label={t('@astryx.powersearch.valueEditor.entities')}
850
+ label={
851
+ isSheet && fieldLabel != null
852
+ ? fieldLabel
853
+ : t('@astryx.powersearch.valueEditor.entities')
854
+ }
707
855
  isLabelHidden
708
856
  isDisabled={isDisabled}
709
857
  searchSource={source}
@@ -726,6 +874,8 @@ function EntityListEditor({
726
874
  placeholder={t('@astryx.powersearch.valueEditor.searchPlaceholder')}
727
875
  debounceMs={operatorValue.searchSource ? 150 : 0}
728
876
  maxMenuItems={maxMenuItems}
877
+ width={isSheet ? '100%' : undefined}
878
+ experimental={isSheet ? {presentation: 'bottom-sheet'} : undefined}
729
879
  />
730
880
  );
731
881
  }
@@ -771,6 +921,9 @@ export function PowerSearchValueEditor({
771
921
  onEnter,
772
922
  maxMenuItems,
773
923
  isDisabled,
924
+ onUnstage,
925
+ presentation = 'popover',
926
+ fieldLabel,
774
927
  }: PowerSearchValueEditorProps) {
775
928
  switch (operatorValue.type) {
776
929
  case 'empty':
@@ -780,6 +933,7 @@ export function PowerSearchValueEditor({
780
933
  return (
781
934
  <StringEditor
782
935
  isDisabled={isDisabled}
936
+ presentation={presentation}
783
937
  operatorValue={operatorValue}
784
938
  filterValue={filterValue}
785
939
  onChange={onChange}
@@ -792,6 +946,8 @@ export function PowerSearchValueEditor({
792
946
  return (
793
947
  <StringListEditor
794
948
  isDisabled={isDisabled}
949
+ presentation={presentation}
950
+ fieldLabel={fieldLabel}
795
951
  operatorValue={operatorValue}
796
952
  filterValue={filterValue}
797
953
  onChange={onChange}
@@ -803,6 +959,8 @@ export function PowerSearchValueEditor({
803
959
  return (
804
960
  <IntegerEditor
805
961
  isDisabled={isDisabled}
962
+ presentation={presentation}
963
+ onUnstage={onUnstage}
806
964
  operatorValue={operatorValue}
807
965
  filterValue={filterValue}
808
966
  onChange={onChange}
@@ -814,6 +972,8 @@ export function PowerSearchValueEditor({
814
972
  return (
815
973
  <FloatEditor
816
974
  isDisabled={isDisabled}
975
+ presentation={presentation}
976
+ onUnstage={onUnstage}
817
977
  operatorValue={operatorValue}
818
978
  filterValue={filterValue}
819
979
  onChange={onChange}
@@ -825,6 +985,7 @@ export function PowerSearchValueEditor({
825
985
  return (
826
986
  <TimeEditor
827
987
  isDisabled={isDisabled}
988
+ presentation={presentation}
828
989
  operatorValue={operatorValue}
829
990
  filterValue={filterValue}
830
991
  onChange={onChange}
@@ -835,6 +996,7 @@ export function PowerSearchValueEditor({
835
996
  return (
836
997
  <DateAbsoluteEditor
837
998
  isDisabled={isDisabled}
999
+ presentation={presentation}
838
1000
  operatorValue={operatorValue}
839
1001
  filterValue={filterValue}
840
1002
  onChange={onChange}
@@ -855,6 +1017,7 @@ export function PowerSearchValueEditor({
855
1017
  return (
856
1018
  <DateRangeEditor
857
1019
  isDisabled={isDisabled}
1020
+ presentation={presentation}
858
1021
  operatorValue={operatorValue}
859
1022
  filterValue={filterValue}
860
1023
  onChange={onChange}
@@ -885,6 +1048,8 @@ export function PowerSearchValueEditor({
885
1048
  return (
886
1049
  <EntityListEditor
887
1050
  isDisabled={isDisabled}
1051
+ presentation={presentation}
1052
+ fieldLabel={fieldLabel}
888
1053
  operatorValue={operatorValue}
889
1054
  filterValue={filterValue}
890
1055
  onChange={onChange}
@@ -32,10 +32,27 @@ function formatEnumLabel(
32
32
  }
33
33
 
34
34
  function formatNumber(value: number, locale: Locale, units?: string): string {
35
- const formatted = new Intl.NumberFormat(locale).format(value);
35
+ const formatted = formatSharedNumber(value, locale);
36
36
  return units ? `${formatted} ${units}` : formatted;
37
37
  }
38
38
 
39
+ /**
40
+ * A number in the provider locale's digits, decimal mark, and — unless the
41
+ * options say otherwise — grouping. The shared number helper for
42
+ * PowerSearch's own readings of a value — this module owns the raw `Intl`
43
+ * access.
44
+ */
45
+ export function formatSharedNumber(
46
+ value: number,
47
+ locale: Locale,
48
+ options?: Pick<
49
+ Intl.NumberFormatOptions,
50
+ 'useGrouping' | 'maximumFractionDigits'
51
+ >,
52
+ ): string {
53
+ return new Intl.NumberFormat(locale, options).format(value);
54
+ }
55
+
39
56
  export function formatDateAbsolute(
40
57
  unixSeconds: number,
41
58
  locale: Locale,