@astryxdesign/core 0.7.6 → 0.7.7-canary.0eb44a2

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 (272) hide show
  1. package/dist/AppShell/AppShell.d.ts.map +1 -1
  2. package/dist/AppShell/AppShell.js +23 -3
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +1 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetPanel.js +16 -9
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +2 -1
  9. package/dist/Button/Button.d.ts.map +1 -1
  10. package/dist/Button/Button.js +3 -2
  11. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  12. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  13. package/dist/Chat/ChatComposerInput.js +2 -0
  14. package/dist/Chat/ChatMessage.js +2 -2
  15. package/dist/Chat/chatMessage.vars.stylex.d.ts +8 -8
  16. package/dist/Chat/chatMessage.vars.stylex.js +8 -8
  17. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteInput.js +8 -1
  19. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  20. package/dist/DateInput/MonthScroller.js +72 -2
  21. package/dist/DateInput/Wheel.d.ts +17 -1
  22. package/dist/DateInput/Wheel.d.ts.map +1 -1
  23. package/dist/DateInput/Wheel.js +179 -19
  24. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  25. package/dist/DateRangeInput/DateRangeInput.js +63 -49
  26. package/dist/Dialog/Dialog.d.ts.map +1 -1
  27. package/dist/Dialog/Dialog.js +37 -1
  28. package/dist/Drawer/Drawer.d.ts.map +1 -1
  29. package/dist/Drawer/Drawer.js +6 -0
  30. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  31. package/dist/Drawer/useDrawerDialogPresence.js +4 -0
  32. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  33. package/dist/DropdownMenu/DropdownMenu.js +20 -4
  34. package/dist/DropdownMenu/useMenuOverflow.d.ts +13 -3
  35. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -1
  36. package/dist/DropdownMenu/useMenuOverflow.js +31 -8
  37. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  38. package/dist/Field/PanelSearchInput.js +8 -1
  39. package/dist/Field/textEntryTraits.d.ts +6 -6
  40. package/dist/Field/textEntryTraits.js +6 -6
  41. package/dist/Layer/layerClickBoundary.d.ts +7 -1
  42. package/dist/Layer/layerClickBoundary.d.ts.map +1 -1
  43. package/dist/Layer/layerClickBoundary.js +2 -2
  44. package/dist/Layer/usePressPin.d.ts +95 -0
  45. package/dist/Layer/usePressPin.d.ts.map +1 -0
  46. package/dist/Layer/usePressPin.js +365 -0
  47. package/dist/Layer/useTouchTrigger.d.ts +23 -1
  48. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  49. package/dist/Layer/useTouchTrigger.js +45 -7
  50. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  51. package/dist/Lightbox/Lightbox.js +2 -1
  52. package/dist/Link/Link.d.ts.map +1 -1
  53. package/dist/Link/Link.js +1 -0
  54. package/dist/Popover/usePopover.d.ts.map +1 -1
  55. package/dist/Popover/usePopover.js +56 -7
  56. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  57. package/dist/PowerSearch/PowerSearch.js +38 -30
  58. package/dist/ProgressBar/ProgressBar.d.ts +1 -1
  59. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  60. package/dist/ProgressBar/ProgressBar.js +52 -14
  61. package/dist/ProgressBar/ProgressBarTouch.d.ts +29 -0
  62. package/dist/ProgressBar/ProgressBarTouch.d.ts.map +1 -0
  63. package/dist/ProgressBar/ProgressBarTouch.js +91 -0
  64. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  65. package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
  66. package/dist/Selector/Selector.d.ts.map +1 -1
  67. package/dist/Selector/Selector.js +25 -13
  68. package/dist/Slider/Slider.d.ts.map +1 -1
  69. package/dist/Slider/Slider.js +404 -131
  70. package/dist/Slider/sliderTouchController.d.ts +189 -0
  71. package/dist/Slider/sliderTouchController.d.ts.map +1 -0
  72. package/dist/Slider/sliderTouchController.js +1017 -0
  73. package/dist/Slider/useSliderTouch.d.ts +22 -0
  74. package/dist/Slider/useSliderTouch.d.ts.map +1 -0
  75. package/dist/Slider/useSliderTouch.js +56 -0
  76. package/dist/Switch/Switch.d.ts +1 -1
  77. package/dist/Switch/Switch.d.ts.map +1 -1
  78. package/dist/Switch/Switch.js +200 -46
  79. package/dist/Switch/switchFill.d.ts +31 -0
  80. package/dist/Switch/switchFill.d.ts.map +1 -0
  81. package/dist/Switch/switchFill.js +88 -0
  82. package/dist/Switch/switchTouch.d.ts +81 -0
  83. package/dist/Switch/switchTouch.d.ts.map +1 -0
  84. package/dist/Switch/switchTouch.js +133 -0
  85. package/dist/Switch/useSwitchTouch.d.ts +285 -0
  86. package/dist/Switch/useSwitchTouch.d.ts.map +1 -0
  87. package/dist/Switch/useSwitchTouch.js +998 -0
  88. package/dist/TabList/Tab.d.ts.map +1 -1
  89. package/dist/TabList/Tab.js +4 -3
  90. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  91. package/dist/Tokenizer/Tokenizer.js +12 -5
  92. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts +3 -0
  93. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -0
  94. package/dist/Tokenizer/TokenizerItemRemovableContext.js +17 -0
  95. package/dist/Tooltip/Tooltip.d.ts +30 -6
  96. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  97. package/dist/Tooltip/Tooltip.js +5 -0
  98. package/dist/Tooltip/index.d.ts +1 -1
  99. package/dist/Tooltip/index.d.ts.map +1 -1
  100. package/dist/Tooltip/useTooltip.d.ts +31 -5
  101. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  102. package/dist/Tooltip/useTooltip.js +59 -18
  103. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  104. package/dist/Typeahead/BaseTypeahead.js +19 -9
  105. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  106. package/dist/Typeahead/Typeahead.js +26 -15
  107. package/dist/astryx.css +91 -8
  108. package/dist/gestures/GestureArena.d.ts +21 -2
  109. package/dist/gestures/GestureArena.d.ts.map +1 -1
  110. package/dist/gestures/GestureArena.js +23 -3
  111. package/dist/gestures/GestureHost.d.ts +123 -3
  112. package/dist/gestures/GestureHost.d.ts.map +1 -1
  113. package/dist/gestures/GestureHost.js +797 -15
  114. package/dist/gestures/GestureRecognizer.d.ts +15 -1
  115. package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
  116. package/dist/gestures/GestureRecognizer.js +12 -0
  117. package/dist/gestures/PanGestureRecognizer.d.ts +8 -0
  118. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  119. package/dist/gestures/PinchGestureRecognizer.d.ts +5 -1
  120. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -1
  121. package/dist/gestures/PinchGestureRecognizer.js +10 -0
  122. package/dist/gestures/PressTracker.d.ts +6 -0
  123. package/dist/gestures/PressTracker.d.ts.map +1 -1
  124. package/dist/gestures/PressTracker.js +3 -0
  125. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +3 -3
  126. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
  127. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +7 -5
  128. package/dist/gestures/nativeScroll.d.ts +16 -2
  129. package/dist/gestures/nativeScroll.d.ts.map +1 -1
  130. package/dist/gestures/nativeScroll.js +39 -8
  131. package/dist/gestures/recognizerInternals.d.ts +4 -2
  132. package/dist/gestures/recognizerInternals.d.ts.map +1 -1
  133. package/dist/gestures/types.d.ts +17 -0
  134. package/dist/gestures/types.d.ts.map +1 -1
  135. package/dist/gestures/types.js +7 -0
  136. package/dist/gestures/usePressTracker.d.ts.map +1 -1
  137. package/dist/gestures/usePressTracker.js +1 -0
  138. package/dist/gestures/wheelSegmenter.d.ts +56 -0
  139. package/dist/gestures/wheelSegmenter.d.ts.map +1 -0
  140. package/dist/gestures/wheelSegmenter.js +122 -0
  141. package/dist/hooks/useAttachment.d.ts +16 -8
  142. package/dist/hooks/useAttachment.d.ts.map +1 -1
  143. package/dist/hooks/useAttachment.js +35 -15
  144. package/dist/hooks/useFocusTrap.d.ts +15 -0
  145. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  146. package/dist/hooks/useFocusTrap.js +13 -2
  147. package/dist/hooks/usePager.d.ts +261 -0
  148. package/dist/hooks/usePager.d.ts.map +1 -0
  149. package/dist/hooks/usePager.js +1169 -0
  150. package/dist/motion/animator.d.ts.map +1 -1
  151. package/dist/motion/animator.js +140 -19
  152. package/dist/motion/paging.d.ts +69 -0
  153. package/dist/motion/paging.d.ts.map +1 -0
  154. package/dist/motion/paging.js +148 -0
  155. package/dist/motion/uikit/index.d.ts +8 -4
  156. package/dist/motion/uikit/index.d.ts.map +1 -1
  157. package/dist/motion/uikit/index.js +8 -4
  158. package/dist/motion/uikit/paging.d.ts +171 -0
  159. package/dist/motion/uikit/paging.d.ts.map +1 -0
  160. package/dist/motion/uikit/paging.js +192 -0
  161. package/dist/motion/uikit/progressBar.d.ts +79 -0
  162. package/dist/motion/uikit/progressBar.d.ts.map +1 -0
  163. package/dist/motion/uikit/progressBar.js +102 -0
  164. package/dist/motion/uikit/scroll.d.ts +0 -19
  165. package/dist/motion/uikit/scroll.d.ts.map +1 -1
  166. package/dist/motion/uikit/scroll.js +2 -21
  167. package/dist/motion/uikit/slider.d.ts +97 -0
  168. package/dist/motion/uikit/slider.d.ts.map +1 -0
  169. package/dist/motion/uikit/slider.js +111 -0
  170. package/dist/motion/uikit/springs.d.ts +59 -30
  171. package/dist/motion/uikit/springs.d.ts.map +1 -1
  172. package/dist/motion/uikit/springs.js +64 -34
  173. package/dist/motion/uikit/switch.d.ts +166 -0
  174. package/dist/motion/uikit/switch.d.ts.map +1 -0
  175. package/dist/motion/uikit/switch.js +186 -0
  176. package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts +18 -0
  177. package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts.map +1 -0
  178. package/dist/privateAttributes/pressHoldsDrag.stylex.js +28 -0
  179. package/dist/utils/pressFeedback.d.ts.map +1 -1
  180. package/dist/utils/pressFeedback.js +5 -0
  181. package/dist/utils/selectionLock.d.ts +43 -0
  182. package/dist/utils/selectionLock.d.ts.map +1 -1
  183. package/dist/utils/selectionLock.js +394 -6
  184. package/package.json +5 -5
  185. package/src/AppShell/AppShell.doc.mjs +10 -0
  186. package/src/AppShell/AppShell.tsx +48 -2
  187. package/src/Avatar/Avatar.tsx +3 -1
  188. package/src/BottomSheet/BottomSheetPanel.tsx +16 -9
  189. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  190. package/src/Button/Button.tsx +3 -2
  191. package/src/Chat/Chat.doc.mjs +1 -0
  192. package/src/Chat/ChatComposerInput.doc.mjs +11 -0
  193. package/src/Chat/ChatComposerInput.tsx +11 -0
  194. package/src/Chat/ChatMessage.tsx +2 -2
  195. package/src/Chat/chatMessage.vars.stylex.ts +8 -8
  196. package/src/CommandPalette/CommandPaletteInput.tsx +6 -0
  197. package/src/DateInput/MonthScroller.tsx +63 -5
  198. package/src/DateInput/Wheel.tsx +174 -14
  199. package/src/DateRangeInput/DateRangeInput.doc.mjs +3 -2
  200. package/src/DateRangeInput/DateRangeInput.tsx +117 -71
  201. package/src/Dialog/Dialog.tsx +37 -1
  202. package/src/Drawer/Drawer.tsx +6 -0
  203. package/src/Drawer/useDrawerDialogPresence.ts +4 -0
  204. package/src/DropdownMenu/DropdownMenu.tsx +28 -6
  205. package/src/DropdownMenu/useMenuOverflow.ts +44 -8
  206. package/src/Field/PanelSearchInput.tsx +6 -0
  207. package/src/Field/textEntryTraits.ts +6 -6
  208. package/src/Layer/layerClickBoundary.ts +2 -2
  209. package/src/Layer/usePressPin.ts +516 -0
  210. package/src/Layer/useTouchTrigger.ts +56 -6
  211. package/src/Lightbox/Lightbox.tsx +2 -1
  212. package/src/Link/Link.tsx +3 -0
  213. package/src/Popover/usePopover.tsx +71 -9
  214. package/src/PowerSearch/PowerSearch.tsx +38 -31
  215. package/src/ProgressBar/ProgressBar.doc.mjs +7 -5
  216. package/src/ProgressBar/ProgressBar.tsx +147 -16
  217. package/src/ProgressBar/ProgressBarTouch.ts +109 -0
  218. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -1
  219. package/src/Selector/Selector.tsx +29 -2
  220. package/src/Slider/Slider.doc.mjs +11 -7
  221. package/src/Slider/Slider.tsx +539 -132
  222. package/src/Slider/sliderTouchController.ts +1209 -0
  223. package/src/Slider/useSliderTouch.ts +78 -0
  224. package/src/Switch/Switch.doc.mjs +3 -3
  225. package/src/Switch/Switch.tsx +412 -86
  226. package/src/Switch/switchFill.ts +106 -0
  227. package/src/Switch/switchTouch.ts +194 -0
  228. package/src/Switch/useSwitchTouch.ts +1121 -0
  229. package/src/TabList/Tab.tsx +5 -3
  230. package/src/Tokenizer/Tokenizer.tsx +15 -5
  231. package/src/Tokenizer/TokenizerItemRemovableContext.ts +22 -0
  232. package/src/Tooltip/Tooltip.doc.mjs +21 -6
  233. package/src/Tooltip/Tooltip.tsx +42 -5
  234. package/src/Tooltip/index.ts +1 -0
  235. package/src/Tooltip/useTooltip.doc.mjs +4 -2
  236. package/src/Tooltip/useTooltip.tsx +84 -21
  237. package/src/Typeahead/BaseTypeahead.tsx +16 -8
  238. package/src/Typeahead/Typeahead.tsx +29 -15
  239. package/src/gestures/GestureArena.ts +45 -4
  240. package/src/gestures/GestureHost.ts +958 -15
  241. package/src/gestures/GestureRecognizer.ts +28 -0
  242. package/src/gestures/PanGestureRecognizer.ts +8 -0
  243. package/src/gestures/PinchGestureRecognizer.ts +16 -1
  244. package/src/gestures/PressTracker.ts +10 -0
  245. package/src/gestures/README.md +82 -3
  246. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +7 -5
  247. package/src/gestures/nativeScroll.ts +49 -9
  248. package/src/gestures/recognizerInternals.ts +9 -2
  249. package/src/gestures/types.ts +18 -0
  250. package/src/gestures/usePressTracker.ts +2 -0
  251. package/src/gestures/wheelSegmenter.ts +143 -0
  252. package/src/hooks/useAttachment.ts +56 -14
  253. package/src/hooks/useFocusTrap.ts +26 -2
  254. package/src/hooks/usePager.ts +1446 -0
  255. package/src/motion/MEASUREMENTS.md +36 -27
  256. package/src/motion/README.md +7 -3
  257. package/src/motion/animator.ts +193 -25
  258. package/src/motion/paging.ts +161 -0
  259. package/src/motion/uikit/index.ts +7 -4
  260. package/src/motion/uikit/paging.ts +193 -0
  261. package/src/motion/uikit/progressBar.ts +102 -0
  262. package/src/motion/uikit/scroll.ts +2 -18
  263. package/src/motion/uikit/slider.ts +105 -0
  264. package/src/motion/uikit/springs.ts +53 -32
  265. package/src/motion/uikit/switch.ts +174 -0
  266. package/src/privateAttributes/pressHoldsDrag.stylex.ts +28 -0
  267. package/src/utils/pressFeedback.ts +5 -0
  268. package/src/utils/selectionLock.ts +501 -6
  269. package/dist/motion/selectPage.d.ts +0 -26
  270. package/dist/motion/selectPage.d.ts.map +0 -1
  271. package/dist/motion/selectPage.js +0 -55
  272. package/src/motion/selectPage.ts +0 -55
@@ -17,7 +17,9 @@
17
17
  */
18
18
 
19
19
  import {
20
+ Fragment,
20
21
  useId,
22
+ useMemo,
21
23
  useOptimistic,
22
24
  useTransition,
23
25
  useEffect,
@@ -36,6 +38,7 @@ import {
36
38
  typographyVars,
37
39
  typeScaleVars,
38
40
  focusVars,
41
+ shadowVars,
39
42
  } from '../theme/tokens.stylex';
40
43
  import {FieldLabel} from '../Field/FieldLabel';
41
44
  import {FieldStatus} from '../FieldStatus/FieldStatus';
@@ -45,7 +48,6 @@ import {Spinner} from '../Spinner';
45
48
  import {useTooltip} from '../Tooltip';
46
49
  import {mergeProps, mergeRefs, rtlStyles} from '../utils';
47
50
  import {switchFocusInput, switchScope} from './switch.markers.stylex';
48
- import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
49
51
  import type {BaseProps} from '../BaseProps';
50
52
  import type {SizeValue} from '../utils/types';
51
53
  import {themeProps} from '../utils/themeProps';
@@ -61,18 +63,53 @@ import {
61
63
  import english from '../i18n/generated-english/Switch/Switch.generated';
62
64
 
63
65
  registerEnglishFallback(english);
64
- const wrapperSizeStyles = stylex.create({
65
- sm: {
66
- width: 32,
67
- height: 20,
68
- },
69
- md: {
70
- width: 40,
71
- height: 24,
72
- },
73
- });
66
+ import {
67
+ interactionOverlayStyles,
68
+ pressConsts,
69
+ pressVars,
70
+ } from '../utils/interactionOverlay.stylex';
71
+ import {pressHoldsDragProps} from '../privateAttributes/pressHoldsDrag.stylex';
72
+ import {useSwitchTouch, type SwitchTouchParts} from './useSwitchTouch';
73
+ import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
74
+ import {watchHitArea, writeHitInsets} from '../utils/hitArea';
75
+
76
+ /**
77
+ * The track box of each size, px (width × height), for the touch
78
+ * presentation's thumb geometry.
79
+ * SYNC: wrapperSizeStyles and trackSizeStyles below.
80
+ */
81
+ const TRACK_SIZE = {sm: [32, 20], md: [40, 24]} as const;
74
82
 
75
- const inputSizeStyles = stylex.create({
83
+ /**
84
+ * A finger: utils/touchPresentation.ts's TOUCH_QUERY. The touch presentation
85
+ * is one model on every touch device: the lift, tracking, flip rule and
86
+ * springs sit under it alone.
87
+ * SYNC: utils/touchPresentation.ts TOUCH_QUERY.
88
+ */
89
+ const TOUCH = '@media (pointer: coarse)';
90
+
91
+ /** The press controller's two states on the row it writes to. */
92
+ const PRESS_ON = '[data-astryx-press="on"]';
93
+ const PRESS_FADING = '[data-astryx-press="fading"]';
94
+
95
+ /**
96
+ * The shared press release, for the press layers the switch fades on the
97
+ * compositor. The clock scales by the theme's tempo, and the curve is the
98
+ * controller's own. Switch.test.tsx compares the two.
99
+ * SYNC: utils/interactionOverlay.stylex.ts PRESS_RELEASE_DURATION,
100
+ * PRESS_RELEASE_DURATION_REDUCED and RELEASE_EASE.
101
+ */
102
+ const PRESS_RELEASE_DURATION = `calc(${pressConsts.releaseMs}ms * min(var(--_astryx-motion-tempo, 1), 1.5))`;
103
+ const PRESS_RELEASE_DURATION_REDUCED = `min(${pressConsts.releaseMs}ms, calc(${pressConsts.releaseMs}ms * var(--_astryx-motion-tempo, 1)))`;
104
+ const PRESS_RELEASE_EASE = 'cubic-bezier(0, 0, 0.2, 1)';
105
+
106
+ // The measured hit insets (utils/hitArea), 0 until measured.
107
+ const HIT_TOP = 'var(--astryx-hit-inset-top, 0px)';
108
+ const HIT_RIGHT = 'var(--astryx-hit-inset-right, 0px)';
109
+ const HIT_BOTTOM = 'var(--astryx-hit-inset-bottom, 0px)';
110
+ const HIT_LEFT = 'var(--astryx-hit-inset-left, 0px)';
111
+
112
+ const wrapperSizeStyles = stylex.create({
76
113
  sm: {
77
114
  width: 32,
78
115
  height: 20,
@@ -83,6 +120,9 @@ const inputSizeStyles = stylex.create({
83
120
  },
84
121
  });
85
122
 
123
+ // Every touch value sits in the same property as the mouse value, as a
124
+ // nested condition, never in a later style. A later style that names a
125
+ // property replaces all of it, mouse value included.
86
126
  const trackSizeStyles = stylex.create({
87
127
  sm: {
88
128
  width: 32,
@@ -96,16 +136,28 @@ const trackSizeStyles = stylex.create({
96
136
  },
97
137
  });
98
138
 
139
+ // The thumb is one circle under any pointer, at each state's size. Under a
140
+ // finger it rests by insets instead of a transform, centered in the track's
141
+ // height at each end. So the compositor moves and lifts it with translate and
142
+ // scale alone; useSwitchTouch measures the box it renders.
99
143
  const thumbOffSizeStyles = stylex.create({
100
144
  sm: {
101
145
  width: 14,
102
146
  height: 14,
103
- transform: 'translateX(0)',
147
+ insetInlineStart: {default: null, [TOUCH]: 3},
148
+ transform: {
149
+ default: 'translateX(0)',
150
+ [TOUCH]: 'none',
151
+ },
104
152
  },
105
153
  md: {
106
154
  width: 16,
107
155
  height: 16,
108
- transform: 'translateX(0)',
156
+ insetInlineStart: {default: null, [TOUCH]: 4},
157
+ transform: {
158
+ default: 'translateX(0)',
159
+ [TOUCH]: 'none',
160
+ },
109
161
  },
110
162
  });
111
163
 
@@ -113,6 +165,8 @@ const thumbOnSizeStyles = stylex.create({
113
165
  sm: {
114
166
  width: 16,
115
167
  height: 16,
168
+ insetInlineStart: {default: null, [TOUCH]: 'auto'},
169
+ insetInlineEnd: {default: null, [TOUCH]: 2},
116
170
  // The thumb rests at the inline-start edge (flex-start, which flexbox
117
171
  // already mirrors under RTL). The on-state travel toward the inline-end
118
172
  // edge is a physical translateX, so it must flip sign under RTL — right
@@ -120,15 +174,25 @@ const thumbOnSizeStyles = stylex.create({
120
174
  // iOS): off-thumb on the reading-start side, on-thumb on the reading-end.
121
175
  transform: {
122
176
  default: 'translateX(12px)',
123
- ':is([dir="rtl"] *)': 'translateX(-12px)',
177
+ ':is([dir="rtl"] *)': {
178
+ default: 'translateX(-12px)',
179
+ [TOUCH]: 'none',
180
+ },
181
+ [TOUCH]: 'none',
124
182
  },
125
183
  },
126
184
  md: {
127
185
  width: 20,
128
186
  height: 20,
187
+ insetInlineStart: {default: null, [TOUCH]: 'auto'},
188
+ insetInlineEnd: {default: null, [TOUCH]: 2},
129
189
  transform: {
130
190
  default: 'translateX(14px)',
131
- ':is([dir="rtl"] *)': 'translateX(-14px)',
191
+ ':is([dir="rtl"] *)': {
192
+ default: 'translateX(-14px)',
193
+ [TOUCH]: 'none',
194
+ },
195
+ [TOUCH]: 'none',
132
196
  },
133
197
  },
134
198
  });
@@ -141,11 +205,43 @@ const thumbOnSizeStyles = stylex.create({
141
205
  // hover tint already is: pressing the input, the track or the label all
142
206
  // activate the row.
143
207
  const pressedImage = `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`;
144
- // The touch press's paint, declared by the shared overlay styles on the
145
- // element the controller writes to (`pressedAlpha`) at the press's strength,
146
- // 1 while on and 1 → 0 over the release, and inherited resolved by the layer
147
- // that paints it. See interactionOverlay.stylex.ts.
148
- const pressedOverlayImage = 'var(--_press-paint-image)';
208
+
209
+ /** The touch press's release on the press layer: opacity 1 → 0. */
210
+ const pressLayerRelease = stylex.keyframes({
211
+ from: {opacity: 1},
212
+ to: {opacity: 0},
213
+ });
214
+
215
+ const PRESS_ALPHA = pressVars['--astryx-press-alpha'];
216
+
217
+ /**
218
+ * The shared strength's release, restated so a fine pointer keeps it: the
219
+ * same keyframes, so the same animation.
220
+ */
221
+ const pressStrengthRelease = stylex.keyframes({
222
+ from: {[PRESS_ALPHA]: 1},
223
+ to: {[PRESS_ALPHA]: 0},
224
+ });
225
+
226
+ const pressOwnerStyles = stylex.create({
227
+ /**
228
+ * Composed after `interactionOverlayStyles.pressedAlpha` on the row. Under
229
+ * a coarse primary pointer the track's and thumb's layers fade the press.
230
+ * So the strength holds through the release, not animated on the main
231
+ * thread. With any other pointer the row keeps `pressedAlpha`'s release.
232
+ */
233
+ pressedAlphaLayered: {
234
+ animationName: {
235
+ default: null,
236
+ [PRESS_FADING]: {
237
+ default: pressStrengthRelease,
238
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
239
+ 'none',
240
+ '@media (pointer: coarse)': 'none',
241
+ },
242
+ },
243
+ },
244
+ });
149
245
 
150
246
  const labelWrapperSizeStyles = stylex.create({
151
247
  sm: {
@@ -182,6 +278,13 @@ const styles = stylex.create({
182
278
  flexShrink: 0,
183
279
  isolation: 'isolate',
184
280
  },
281
+ // Horizontal touch moves are the switch's drag and vertical ones the page's
282
+ // scroll, under any primary pointer. A touchscreen laptop's finger counts
283
+ // too. It is set in CSS so it holds before hydration. A disabled or loading
284
+ // switch takes no drag, so it leaves every move to the page.
285
+ switchWrapperInteractive: {
286
+ touchAction: 'pan-y pinch-zoom',
287
+ },
185
288
  input: {
186
289
  position: 'absolute',
187
290
  top: '50%',
@@ -194,10 +297,24 @@ const styles = stylex.create({
194
297
  },
195
298
  zIndex: 1,
196
299
  },
300
+ // The invisible input covers the track. Under a coarse pointer it is the
301
+ // hit area a finger needs. It grows toward 44 × 44 px by the insets
302
+ // utils/hitArea measures, each side stopping at half the gap to a
303
+ // neighbouring target. Its center moves with any uneven side, and nothing
304
+ // visible grows. The insets are 0 until measured. Physical left and
305
+ // translate, as centerInline: the insets are physical sides.
197
306
  inputCoarse: {
198
- '@media (pointer: coarse)': {
199
- minInlineSize: 24,
200
- minBlockSize: 24,
307
+ width: {
308
+ default: '100%',
309
+ '@media (pointer: coarse)': `calc(100% + ${HIT_LEFT} + ${HIT_RIGHT})`,
310
+ },
311
+ height: {
312
+ default: '100%',
313
+ '@media (pointer: coarse)': `calc(100% + ${HIT_TOP} + ${HIT_BOTTOM})`,
314
+ },
315
+ transform: {
316
+ default: 'translate(-50%, -50%)',
317
+ '@media (pointer: coarse)': `translate(calc(-50% + (${HIT_RIGHT} - ${HIT_LEFT}) / 2), calc(-50% + (${HIT_BOTTOM} - ${HIT_TOP}) / 2))`,
201
318
  },
202
319
  },
203
320
  inputDisabled: {
@@ -209,11 +326,22 @@ const styles = stylex.create({
209
326
  track: {
210
327
  display: 'flex',
211
328
  alignItems: 'center',
329
+ // Under a finger the press layer (::before), the colour fade (::after)
330
+ // and the thumb sit in the track, in a stacking context of its own.
331
+ position: {default: null, [TOUCH]: 'relative'},
332
+ isolation: {default: null, [TOUCH]: 'isolate'},
212
333
  borderRadius: radiusVars['--radius-full'],
213
334
  transitionProperty: 'background-color',
335
+ // Under a finger the track's fill changes at once and the fade layer
336
+ // crossfades it on the compositor. Reduced motion comes last so it holds
337
+ // everywhere: under a finger the fade layer
338
+ // carries the colour, and with a mouse the change is immediate.
214
339
  transitionDuration: {
215
340
  default: durationVars['--duration-fast'],
216
- '@media (prefers-reduced-motion: reduce)': '0s',
341
+ [TOUCH]: '0s',
342
+ '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': '0s',
343
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
344
+ '0s',
217
345
  },
218
346
  transitionTimingFunction: easeVars['--ease-standard'],
219
347
  boxSizing: 'border-box',
@@ -240,28 +368,102 @@ const styles = stylex.create({
240
368
  default: null,
241
369
  '@media (forced-colors: none)': {
242
370
  default: pressedImage,
243
- // Under a coarse pointer the touch press model writes `data-astryx-press`
244
- // on the row instead; see interactionOverlay.stylex.ts.
371
+ // Under a coarse pointer the touch press controller writes
372
+ // `data-astryx-press` on the row instead, and the press layer
373
+ // (::before) paints it.
245
374
  '@media (pointer: coarse)': 'none',
246
375
  },
247
376
  },
248
- // Nested in the same media as the arm above so it outranks the drop: an
249
- // ancestor-scoped attribute selector gets no priority of its own.
250
- [stylex.when.ancestor('[data-astryx-press="on"]', switchScope)]: {
377
+ },
378
+ // The press, under a finger: a layer over the track's fill and colour
379
+ // fade and under the thumb. It exists only while the row is pressed or
380
+ // releasing. Its opacity is the press: on at once, then the shared
381
+ // release's clock and curve. The release runs on the compositor, so the
382
+ // press never sticks while the main thread is busy.
383
+ '::before': {
384
+ content: {
251
385
  default: null,
252
- '@media (forced-colors: none)': {
386
+ [stylex.when.ancestor(PRESS_ON, switchScope)]: {
253
387
  default: null,
254
- '@media (pointer: coarse)': pressedOverlayImage,
388
+ [TOUCH]: '""',
389
+ },
390
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
391
+ default: null,
392
+ [TOUCH]: '""',
255
393
  },
256
394
  },
257
- [stylex.when.ancestor('[data-astryx-press="fading"]', switchScope)]: {
395
+ position: 'absolute',
396
+ inset: 0,
397
+ zIndex: 1,
398
+ borderRadius: 'inherit',
399
+ pointerEvents: 'none',
400
+ backgroundColor: {
258
401
  default: null,
259
- '@media (forced-colors: none)': {
402
+ '@media (forced-colors: none)': colorVars['--color-overlay-pressed'],
403
+ },
404
+ opacity: {
405
+ default: null,
406
+ [stylex.when.ancestor(PRESS_ON, switchScope)]: {
407
+ default: null,
408
+ [TOUCH]: 1,
409
+ },
410
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
411
+ default: null,
412
+ [TOUCH]: 0,
413
+ },
414
+ },
415
+ animationName: {
416
+ default: null,
417
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
260
418
  default: null,
261
- '@media (pointer: coarse)': pressedOverlayImage,
419
+ [TOUCH]: pressLayerRelease,
420
+ },
421
+ },
422
+ animationDuration: {
423
+ default: null,
424
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
425
+ default: null,
426
+ [TOUCH]: {
427
+ default: PRESS_RELEASE_DURATION,
428
+ '@media (prefers-reduced-motion: reduce) and (pointer: coarse)':
429
+ PRESS_RELEASE_DURATION_REDUCED,
430
+ },
431
+ },
432
+ },
433
+ animationTimingFunction: {
434
+ default: null,
435
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
436
+ default: null,
437
+ [TOUCH]: PRESS_RELEASE_EASE,
438
+ },
439
+ },
440
+ animationFillMode: {
441
+ default: null,
442
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
443
+ default: null,
444
+ [TOUCH]: 'both',
262
445
  },
263
446
  },
264
447
  },
448
+ // The colour fade, under a finger: a layer over the track's own fill. It
449
+ // shows the fill the track is leaving, or mid-drag the one it heads to,
450
+ // and fades it on the compositor. Its colour is the theme's, read from
451
+ // the track (switchFill.ts). The animator owns its opacity, and at rest it
452
+ // is clear.
453
+ '::after': {
454
+ content: '""',
455
+ display: {default: 'none', [TOUCH]: 'block'},
456
+ position: 'absolute',
457
+ inset: 0,
458
+ zIndex: 0,
459
+ borderRadius: 'inherit',
460
+ pointerEvents: 'none',
461
+ opacity: 0,
462
+ backgroundColor: 'var(--_switch-fade-fill, transparent)',
463
+ // Its own compositing layer from the start, so its fade runs off the
464
+ // main thread (iOS Safari otherwise animates it on the main thread).
465
+ willChange: {default: null, [TOUCH]: 'opacity'},
466
+ },
265
467
  },
266
468
  // The one ring in the system not drawn by focusOutlineStyles. The focusable
267
469
  // input is the track's preceding sibling, so the condition reads that input
@@ -329,38 +531,117 @@ const styles = stylex.create({
329
531
  alignItems: 'center',
330
532
  justifyContent: 'center',
331
533
  borderRadius: radiusVars['--radius-full'],
332
- transitionProperty: 'transform, width, height',
534
+ // Under a finger it sits in the track, centered in its height, above the
535
+ // colour fade (::after) and the press layer (::before). useSwitchTouch
536
+ // moves and lifts it on the compositor: nothing of its own transitions.
537
+ position: {default: null, [TOUCH]: 'absolute'},
538
+ insetBlock: {default: null, [TOUCH]: 0},
539
+ marginBlock: {default: null, [TOUCH]: 'auto'},
540
+ zIndex: {default: null, [TOUCH]: 2},
541
+ pointerEvents: {default: null, [TOUCH]: 'none'},
542
+ transitionProperty: {
543
+ default: 'transform, width, height',
544
+ [TOUCH]: 'none',
545
+ },
546
+ // Under reduced motion nothing slides: under a finger the thumb
547
+ // cross-fades through the animator, and with a mouse it moves at once.
333
548
  transitionDuration: {
334
549
  default: durationVars['--duration-fast'],
335
- '@media (prefers-reduced-motion: reduce)': '0s',
550
+ '@media (prefers-reduced-motion: reduce) and (pointer: coarse)': '0s',
551
+ '@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
552
+ '0s',
336
553
  },
337
554
  transitionTimingFunction: easeVars['--ease-standard'],
338
- // Pressed: the same overlay as the track, so the whole control darkens
339
- // under the finger rather than the thumb standing out against a darker
340
- // track.
341
- backgroundImage: {
342
- default: null,
343
- [stylex.when.ancestor(':active', switchScope)]: {
555
+ // The lifted shadow, under a finger: the theme's elevation on a layer the
556
+ // thumb's size. It fades in with the lift on the compositor, and at rest
557
+ // it is clear. The press on the thumb too, under a finger: the same layer
558
+ // as the track's, over the thumb's fill.
559
+ '::before': {
560
+ content: {
344
561
  default: null,
345
- '@media (forced-colors: none)': {
346
- default: pressedImage,
347
- '@media (pointer: coarse)': 'none',
562
+ [stylex.when.ancestor(PRESS_ON, switchScope)]: {
563
+ default: null,
564
+ [TOUCH]: '""',
565
+ },
566
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
567
+ default: null,
568
+ [TOUCH]: '""',
348
569
  },
349
570
  },
350
- // Nested in the same media as the arm above so it outranks the drop: an
351
- // ancestor-scoped attribute selector gets no priority of its own.
352
- [stylex.when.ancestor('[data-astryx-press="on"]', switchScope)]: {
571
+ position: 'absolute',
572
+ inset: 0,
573
+ borderRadius: 'inherit',
574
+ pointerEvents: 'none',
575
+ backgroundColor: {
353
576
  default: null,
354
- '@media (forced-colors: none)': {
577
+ '@media (forced-colors: none)': colorVars['--color-overlay-pressed'],
578
+ },
579
+ opacity: {
580
+ default: null,
581
+ [stylex.when.ancestor(PRESS_ON, switchScope)]: {
582
+ default: null,
583
+ [TOUCH]: 1,
584
+ },
585
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
355
586
  default: null,
356
- '@media (pointer: coarse)': pressedOverlayImage,
587
+ [TOUCH]: 0,
357
588
  },
358
589
  },
359
- [stylex.when.ancestor('[data-astryx-press="fading"]', switchScope)]: {
590
+ animationName: {
360
591
  default: null,
361
- '@media (forced-colors: none)': {
592
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
362
593
  default: null,
363
- '@media (pointer: coarse)': pressedOverlayImage,
594
+ [TOUCH]: pressLayerRelease,
595
+ },
596
+ },
597
+ animationDuration: {
598
+ default: null,
599
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
600
+ default: null,
601
+ [TOUCH]: {
602
+ default: PRESS_RELEASE_DURATION,
603
+ '@media (prefers-reduced-motion: reduce) and (pointer: coarse)':
604
+ PRESS_RELEASE_DURATION_REDUCED,
605
+ },
606
+ },
607
+ },
608
+ animationTimingFunction: {
609
+ default: null,
610
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
611
+ default: null,
612
+ [TOUCH]: PRESS_RELEASE_EASE,
613
+ },
614
+ },
615
+ animationFillMode: {
616
+ default: null,
617
+ [stylex.when.ancestor(PRESS_FADING, switchScope)]: {
618
+ default: null,
619
+ [TOUCH]: 'both',
620
+ },
621
+ },
622
+ },
623
+ '::after': {
624
+ content: '""',
625
+ display: {default: 'none', [TOUCH]: 'block'},
626
+ position: 'absolute',
627
+ inset: 0,
628
+ borderRadius: 'inherit',
629
+ pointerEvents: 'none',
630
+ opacity: 0,
631
+ boxShadow: shadowVars['--shadow-low'],
632
+ willChange: {default: null, [TOUCH]: 'opacity'},
633
+ },
634
+ // Pressed with a mouse: the same overlay as the track, so the whole
635
+ // control darkens rather than the thumb standing out against a darker
636
+ // track. Under a finger the track's press layer paints under the thumb,
637
+ // which lifts.
638
+ backgroundImage: {
639
+ default: null,
640
+ [stylex.when.ancestor(':active', switchScope)]: {
641
+ default: null,
642
+ '@media (forced-colors: none)': {
643
+ default: pressedImage,
644
+ '@media (pointer: coarse)': 'none',
364
645
  },
365
646
  },
366
647
  },
@@ -519,7 +800,7 @@ export interface SwitchProps extends Omit<BaseProps, 'onChange'> {
519
800
  status?: InputStatus;
520
801
  /**
521
802
  * Size variant controlling track and thumb dimensions.
522
- * - 'sm': 34x20px (matches sm checkbox/radio vertical rhythm)
803
+ * - 'sm': 32x20px (matches sm checkbox/radio vertical rhythm)
523
804
  * - 'md': 40x24px (default, matches md checkbox/radio vertical rhythm)
524
805
  * @default 'md'
525
806
  */
@@ -649,10 +930,68 @@ export function Switch({
649
930
  const ariaDescribedBy =
650
931
  describedByParts.length > 0 ? describedByParts.join(' ') : undefined;
651
932
 
933
+ // The touch presentation (coarse pointer): its parts are always rendered;
934
+ // CSS shows them under a finger and the hook animates and listens there.
935
+ const surfaceRef = useRef<HTMLDivElement>(null);
936
+ const inputRef = useRef<HTMLInputElement>(null);
937
+ const thumbRef = useRef<HTMLDivElement>(null);
938
+ const trackRef = useRef<HTMLDivElement>(null);
939
+ const touchParts = useMemo<SwitchTouchParts>(
940
+ () => ({
941
+ surface: surfaceRef,
942
+ input: inputRef,
943
+ thumb: thumbRef,
944
+ track: trackRef,
945
+ }),
946
+ [],
947
+ );
948
+ // The track in a state: its theme target and fill. The other state's, for
949
+ // the fade layer's colour (switchFill.ts).
950
+ const trackPropsFor = (on: boolean) =>
951
+ mergeProps(
952
+ themeProps('switch', {
953
+ checked: on ? 'checked' : null,
954
+ disabled: isDisabled ? 'disabled' : null,
955
+ size,
956
+ }),
957
+ stylex.props(
958
+ styles.track,
959
+ trackSizeStyles[size],
960
+ on ? styles.trackOn : styles.trackOff,
961
+ styles.trackFocus,
962
+ isDisabled && styles.trackDisabled,
963
+ isDisabled && !on && styles.trackDisabledOff,
964
+ ),
965
+ );
966
+ // Syncs the hit area's insets with the layout around the switch, through
967
+ // utils/hitArea's observers. Under a coarse pointer they grow toward 44 px,
968
+ // stopping at half the gap to each neighbouring target. A disabled switch
969
+ // watches nothing.
970
+ useIsomorphicLayoutEffect(() => {
971
+ const surface = surfaceRef.current;
972
+ if (surface == null || isDisabled) {
973
+ return;
974
+ }
975
+ return watchHitArea(surface, insets => writeHitInsets(surface, insets));
976
+ }, [isDisabled]);
977
+ useSwitchTouch(touchParts, {
978
+ isOn,
979
+ isInteractive: !isDisabled && !isBusy,
980
+ isDisabled,
981
+ trackSize: TRACK_SIZE[size],
982
+ otherTrackProps: trackPropsFor(!isOn),
983
+ });
984
+
652
985
  const switchElement = (
653
- <div {...stylex.props(styles.switchWrapper, wrapperSizeStyles[size])}>
986
+ <div
987
+ ref={surfaceRef}
988
+ {...stylex.props(
989
+ styles.switchWrapper,
990
+ wrapperSizeStyles[size],
991
+ !isDisabled && !isBusy && styles.switchWrapperInteractive,
992
+ )}>
654
993
  <input
655
- ref={useMergedRefs(ref, disabledMessageTooltip.positionRef)}
994
+ ref={useMergedRefs(ref, disabledMessageTooltip.positionRef, inputRef)}
656
995
  id={id}
657
996
  type="checkbox"
658
997
  role="switch"
@@ -695,30 +1034,14 @@ export function Switch({
695
1034
  styles.input,
696
1035
  rtlStyles.centerInline('-50%'),
697
1036
  styles.inputCoarse,
698
- inputSizeStyles[size],
699
1037
  isDisabled && styles.inputDisabled,
700
1038
  isBusy && styles.inputBusy,
701
1039
  switchFocusInput,
702
1040
  )}
703
1041
  />
704
- <div
705
- aria-hidden="true"
706
- {...mergeProps(
707
- themeProps('switch', {
708
- checked: isOn ? 'checked' : null,
709
- disabled: isDisabled ? 'disabled' : null,
710
- size,
711
- }),
712
- stylex.props(
713
- styles.track,
714
- trackSizeStyles[size],
715
- isOn ? styles.trackOn : styles.trackOff,
716
- styles.trackFocus,
717
- isDisabled && styles.trackDisabled,
718
- isDisabled && !isOn && styles.trackDisabledOff,
719
- ),
720
- )}>
1042
+ <div ref={trackRef} aria-hidden="true" {...trackPropsFor(isOn)}>
721
1043
  <div
1044
+ ref={thumbRef}
722
1045
  {...mergeProps(
723
1046
  themeProps('switch-thumb', {
724
1047
  checked: isOn ? 'checked' : null,
@@ -785,6 +1108,7 @@ export function Switch({
785
1108
  disabledMessageTooltip.interactionRef(el);
786
1109
  }}
787
1110
  {...(isDisabled ? undefined : pressable)}
1111
+ {...pressHoldsDragProps(!isDisabled)}
788
1112
  {...stylex.props(
789
1113
  styles.container,
790
1114
  isLabelHidden && styles.containerLabelHidden,
@@ -793,19 +1117,21 @@ export function Switch({
793
1117
  // The touch press's strength and release live on the row the
794
1118
  // controller writes to; the track and thumb inherit and paint it.
795
1119
  !isDisabled && interactionOverlayStyles.pressedAlpha,
1120
+ !isDisabled && pressOwnerStyles.pressedAlphaLayered,
796
1121
  )}>
797
1122
  {' '}
798
- {labelPosition === 'start' ? (
799
- <>
800
- {labelElement}
801
- {switchElement}
802
- </>
803
- ) : (
804
- <>
805
- {switchElement}
806
- {labelElement}
807
- </>
808
- )}
1123
+ {/* Keyed, so a labelPosition change moves the nodes instead of
1124
+ rebuilding them: the touch controller stays on the input, track
1125
+ and thumb it attached to. */}
1126
+ {labelPosition === 'start'
1127
+ ? [
1128
+ <Fragment key="label">{labelElement}</Fragment>,
1129
+ <Fragment key="switch">{switchElement}</Fragment>,
1130
+ ]
1131
+ : [
1132
+ <Fragment key="switch">{switchElement}</Fragment>,
1133
+ <Fragment key="label">{labelElement}</Fragment>,
1134
+ ]}
809
1135
  </div>
810
1136
  {status?.message && (
811
1137
  <FieldStatus