@astryxdesign/core 0.7.7-canary.c0929c8 → 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 (472) 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/AppShell/useCloseMobileDrawerOnActivate.d.ts +13 -0
  5. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
  6. package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
  7. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheet.js +79 -9
  9. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  10. package/dist/BottomSheet/BottomSheetPanel.js +23 -4
  11. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  12. package/dist/BottomSheet/bottomSheetStack.js +5 -4
  13. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  14. package/dist/BottomSheet/useSheetGestures.js +3 -1
  15. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
  16. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  17. package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
  18. package/dist/Button/Button.d.ts.map +1 -1
  19. package/dist/Button/Button.js +47 -3
  20. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  21. package/dist/CheckboxInput/CheckboxInput.js +13 -2
  22. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  23. package/dist/CheckboxList/CheckboxList.js +7 -1
  24. package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
  25. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  26. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  27. package/dist/CheckboxList/CheckboxListItem.js +10 -2
  28. package/dist/ComplexSelector/ComplexSelector.d.ts +4 -1
  29. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  30. package/dist/ComplexSelector/ComplexSelector.js +14 -4
  31. package/dist/ContextMenu/ContextMenu.js +2 -2
  32. package/dist/DateInput/DateInput.d.ts.map +1 -1
  33. package/dist/DateInput/DateInput.js +5 -2
  34. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  35. package/dist/DateInput/MonthScroller.js +13 -27
  36. package/dist/DateInput/Wheel.d.ts.map +1 -1
  37. package/dist/DateInput/Wheel.js +22 -41
  38. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  39. package/dist/DateTimeInput/DateTimeInput.js +5 -2
  40. package/dist/Dialog/Dialog.d.ts.map +1 -1
  41. package/dist/Dialog/Dialog.js +23 -45
  42. package/dist/Drawer/Drawer.d.ts.map +1 -1
  43. package/dist/Drawer/Drawer.js +15 -11
  44. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  45. package/dist/Drawer/useDrawerDialogPresence.js +5 -1
  46. package/dist/Field/Field.js +2 -2
  47. package/dist/Field/PanelSearchInput.d.ts +9 -1
  48. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  49. package/dist/Field/PanelSearchInput.js +3 -1
  50. package/dist/HoverCard/HoverCard.d.ts +4 -3
  51. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  52. package/dist/HoverCard/HoverCard.js +27 -2
  53. package/dist/HoverCard/useHoverCard.d.ts +5 -4
  54. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  55. package/dist/HoverCard/useHoverCard.js +17 -31
  56. package/dist/Icon/Icon.d.ts +5 -1
  57. package/dist/Icon/Icon.d.ts.map +1 -1
  58. package/dist/Icon/Icon.js +5 -2
  59. package/dist/InfoTip/InfoTip.d.ts +63 -0
  60. package/dist/InfoTip/InfoTip.d.ts.map +1 -0
  61. package/dist/InfoTip/InfoTip.js +126 -0
  62. package/dist/InfoTip/index.d.ts +10 -0
  63. package/dist/InfoTip/index.d.ts.map +1 -0
  64. package/dist/InfoTip/index.js +12 -0
  65. package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
  66. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  67. package/dist/Layer/layerAnimations.stylex.js +25 -20
  68. package/dist/Layer/useBackdropPress.d.ts +27 -0
  69. package/dist/Layer/useBackdropPress.d.ts.map +1 -0
  70. package/dist/Layer/useBackdropPress.js +53 -0
  71. package/dist/Layer/usePressPin.d.ts +20 -10
  72. package/dist/Layer/usePressPin.d.ts.map +1 -1
  73. package/dist/Layer/usePressPin.js +66 -18
  74. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  75. package/dist/Lightbox/Lightbox.js +18 -0
  76. package/dist/MobileNav/MobileNav.js +0 -1
  77. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  78. package/dist/MultiSelector/hooks.js +2 -4
  79. package/dist/Outline/Outline.d.ts +2 -4
  80. package/dist/Outline/Outline.d.ts.map +1 -1
  81. package/dist/Outline/Outline.js +4 -52
  82. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  83. package/dist/Outline/useOutlineFromMarkdown.js +1 -0
  84. package/dist/Outline/useScrollSpy.js +3 -3
  85. package/dist/PowerSearch/PowerSearch.d.ts +36 -3
  86. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  87. package/dist/PowerSearch/PowerSearch.js +571 -302
  88. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +100 -0
  89. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -0
  90. package/dist/PowerSearch/PowerSearchBottomSheet.js +1913 -0
  91. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +32 -1
  92. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  93. package/dist/PowerSearch/PowerSearchEditPopover.js +17 -6
  94. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +38 -3
  95. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  96. package/dist/PowerSearch/PowerSearchValueEditor.js +170 -61
  97. package/dist/PowerSearch/formatFilterValue.d.ts +7 -0
  98. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  99. package/dist/PowerSearch/formatFilterValue.js +11 -1
  100. package/dist/PowerSearch/formatTokenValue.d.ts +28 -0
  101. package/dist/PowerSearch/formatTokenValue.d.ts.map +1 -0
  102. package/dist/PowerSearch/formatTokenValue.js +160 -0
  103. package/dist/PowerSearch/powerSearchSheetUtils.d.ts +65 -0
  104. package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +1 -0
  105. package/dist/PowerSearch/powerSearchSheetUtils.js +168 -0
  106. package/dist/RadioList/RadioList.d.ts.map +1 -1
  107. package/dist/RadioList/RadioList.js +8 -2
  108. package/dist/RadioList/RadioListItem.d.ts +1 -1
  109. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  110. package/dist/RadioList/RadioListItem.js +5 -3
  111. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  112. package/dist/Resizable/ResizeHandle.js +118 -260
  113. package/dist/Resizable/resizeGesture.d.ts +91 -0
  114. package/dist/Resizable/resizeGesture.d.ts.map +1 -0
  115. package/dist/Resizable/resizeGesture.js +251 -0
  116. package/dist/SegmentedControl/SegmentedControl.d.ts +3 -1
  117. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  118. package/dist/SegmentedControl/SegmentedControl.js +115 -8
  119. package/dist/SegmentedControl/SegmentedControlContext.d.ts +6 -0
  120. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  121. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -0
  122. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  123. package/dist/SegmentedControl/SegmentedControlItem.js +8 -1
  124. package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts +357 -0
  125. package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -0
  126. package/dist/SegmentedControl/useSegmentedSelectionMotion.js +1349 -0
  127. package/dist/SegmentedControl/useSegmentedTouchTracking.d.ts +70 -0
  128. package/dist/SegmentedControl/useSegmentedTouchTracking.d.ts.map +1 -0
  129. package/dist/SegmentedControl/useSegmentedTouchTracking.js +264 -0
  130. package/dist/Selector/Selector.d.ts.map +1 -1
  131. package/dist/Selector/Selector.js +96 -67
  132. package/dist/Selector/optionShape.d.ts +24 -0
  133. package/dist/Selector/optionShape.d.ts.map +1 -0
  134. package/dist/Selector/optionShape.js +48 -0
  135. package/dist/Selector/selectableOptionEntries.d.ts +20 -0
  136. package/dist/Selector/selectableOptionEntries.d.ts.map +1 -0
  137. package/dist/Selector/selectableOptionEntries.js +38 -0
  138. package/dist/Selector/utils.d.ts +2 -17
  139. package/dist/Selector/utils.d.ts.map +1 -1
  140. package/dist/Selector/utils.js +3 -51
  141. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  142. package/dist/SideNav/SideNavItem.js +3 -9
  143. package/dist/Slider/sliderTouchController.js +2 -2
  144. package/dist/Spinner/Spinner.d.ts +1 -1
  145. package/dist/Spinner/Spinner.d.ts.map +1 -1
  146. package/dist/Spinner/Spinner.js +60 -58
  147. package/dist/Switch/switchTouch.d.ts +0 -7
  148. package/dist/Switch/switchTouch.d.ts.map +1 -1
  149. package/dist/Switch/switchTouch.js +2 -10
  150. package/dist/Switch/useSwitchTouch.d.ts +2 -11
  151. package/dist/Switch/useSwitchTouch.d.ts.map +1 -1
  152. package/dist/Switch/useSwitchTouch.js +35 -93
  153. package/dist/Table/BaseTable.d.ts.map +1 -1
  154. package/dist/Table/BaseTable.js +29 -8
  155. package/dist/TextInput/TextInput.d.ts +2 -2
  156. package/dist/TextInput/TextInput.d.ts.map +1 -1
  157. package/dist/TextInput/TextInput.js +10 -7
  158. package/dist/Toast/Toast.d.ts +11 -1
  159. package/dist/Toast/Toast.d.ts.map +1 -1
  160. package/dist/Toast/Toast.js +30 -15
  161. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  162. package/dist/Toast/ToastViewport.js +94 -34
  163. package/dist/Toast/types.d.ts +9 -3
  164. package/dist/Toast/types.d.ts.map +1 -1
  165. package/dist/Token/Token.d.ts +2 -2
  166. package/dist/Token/Token.d.ts.map +1 -1
  167. package/dist/Token/Token.js +13 -4
  168. package/dist/Token/TokenNamingContext.d.ts +14 -0
  169. package/dist/Token/TokenNamingContext.d.ts.map +1 -0
  170. package/dist/Token/TokenNamingContext.js +23 -0
  171. package/dist/Tokenizer/Tokenizer.d.ts +46 -59
  172. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  173. package/dist/Tokenizer/Tokenizer.js +40 -57
  174. package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
  175. package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
  176. package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
  177. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts +3 -0
  178. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -0
  179. package/dist/Tokenizer/TokenizerItemRemovableContext.js +19 -0
  180. package/dist/Tooltip/Tooltip.d.ts +25 -30
  181. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  182. package/dist/Tooltip/Tooltip.js +24 -5
  183. package/dist/Tooltip/index.d.ts +1 -1
  184. package/dist/Tooltip/index.d.ts.map +1 -1
  185. package/dist/Tooltip/useTooltip.d.ts +45 -28
  186. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  187. package/dist/Tooltip/useTooltip.js +52 -59
  188. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  189. package/dist/TopNav/TopNavItem.js +4 -7
  190. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  191. package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
  192. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  193. package/dist/TopNav/TopNavMenu.js +4 -7
  194. package/dist/Typeahead/TypeaheadItem.d.ts +2 -1
  195. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  196. package/dist/animator/createAnimator.d.ts +98 -0
  197. package/dist/animator/createAnimator.d.ts.map +1 -0
  198. package/dist/animator/createAnimator.js +259 -0
  199. package/dist/astryx.css +96 -7
  200. package/dist/gestures/GestureHost.d.ts +27 -1
  201. package/dist/gestures/GestureHost.d.ts.map +1 -1
  202. package/dist/gestures/GestureHost.js +157 -21
  203. package/dist/gestures/PressTracker.d.ts.map +1 -1
  204. package/dist/gestures/PressTracker.js +4 -3
  205. package/dist/hooks/actionGroupTracker.d.ts +7 -4
  206. package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
  207. package/dist/hooks/actionGroupTracker.js +19 -56
  208. package/dist/hooks/index.d.ts +1 -0
  209. package/dist/hooks/index.d.ts.map +1 -1
  210. package/dist/hooks/index.js +1 -0
  211. package/dist/hooks/pressGroupContext.d.ts +6 -0
  212. package/dist/hooks/pressGroupContext.d.ts.map +1 -0
  213. package/dist/hooks/pressGroupContext.js +20 -0
  214. package/dist/hooks/useActionGroupTracking.d.ts +53 -0
  215. package/dist/hooks/useActionGroupTracking.d.ts.map +1 -0
  216. package/dist/hooks/useActionGroupTracking.js +174 -0
  217. package/dist/hooks/useAnimator.d.ts +41 -0
  218. package/dist/hooks/useAnimator.d.ts.map +1 -0
  219. package/dist/hooks/useAnimator.js +70 -0
  220. package/dist/hooks/useAttachment.d.ts +1 -1
  221. package/dist/hooks/useAttachment.js +1 -1
  222. package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
  223. package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
  224. package/dist/hooks/useHighlightedOptionScroll.js +112 -11
  225. package/dist/hooks/useInputStatusIcon.d.ts +1 -1
  226. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  227. package/dist/hooks/useInputStatusIcon.js +2 -2
  228. package/dist/hooks/useLongPress.d.ts +6 -2
  229. package/dist/hooks/useLongPress.d.ts.map +1 -1
  230. package/dist/hooks/useLongPress.js +95 -15
  231. package/dist/hooks/useMediaQuery.d.ts +2 -1
  232. package/dist/hooks/useMediaQuery.d.ts.map +1 -1
  233. package/dist/hooks/useMediaQuery.js +7 -4
  234. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  235. package/dist/hooks/useMenuPress.js +4 -2
  236. package/dist/i18n/generated-english/InfoTip/InfoTip.generated.d.ts +8 -0
  237. package/dist/i18n/generated-english/InfoTip/InfoTip.generated.d.ts.map +1 -0
  238. package/dist/i18n/generated-english/InfoTip/InfoTip.generated.js +12 -0
  239. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
  240. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
  241. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +8 -0
  242. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -0
  243. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +42 -0
  244. package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
  245. package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +6 -0
  246. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +8 -0
  247. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +1 -0
  248. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +14 -0
  249. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
  250. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
  251. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  252. package/dist/i18n/generated-locales/en.generated.js +33 -1
  253. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  254. package/dist/i18n/generated-locales/pseudo.generated.js +33 -1
  255. package/dist/index.d.ts +2 -0
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +5 -0
  258. package/dist/internal/SheetConfirmation.d.ts +11 -0
  259. package/dist/internal/SheetConfirmation.d.ts.map +1 -0
  260. package/dist/internal/SheetConfirmation.js +92 -0
  261. package/dist/internal/indicatorPress.stylex.d.ts +20 -0
  262. package/dist/internal/indicatorPress.stylex.d.ts.map +1 -0
  263. package/dist/internal/indicatorPress.stylex.js +26 -0
  264. package/dist/internal/sheetDraft.d.ts +35 -0
  265. package/dist/internal/sheetDraft.d.ts.map +1 -0
  266. package/dist/internal/sheetDraft.js +65 -0
  267. package/dist/internal/touch-sheet.d.ts +21 -0
  268. package/dist/internal/touch-sheet.d.ts.map +1 -0
  269. package/dist/internal/touch-sheet.js +19 -0
  270. package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
  271. package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
  272. package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
  273. package/dist/motion/animator.d.ts +14 -14
  274. package/dist/motion/animator.d.ts.map +1 -1
  275. package/dist/motion/animator.js +41 -18
  276. package/dist/motion/reducedMotion.d.ts +24 -0
  277. package/dist/motion/reducedMotion.d.ts.map +1 -1
  278. package/dist/motion/reducedMotion.js +78 -1
  279. package/dist/motion/roles.d.ts +7 -1
  280. package/dist/motion/roles.d.ts.map +1 -1
  281. package/dist/motion/roles.js +20 -1
  282. package/dist/motion/uikit/alert.d.ts +41 -0
  283. package/dist/motion/uikit/alert.d.ts.map +1 -0
  284. package/dist/motion/uikit/alert.js +43 -0
  285. package/dist/motion/uikit/index.d.ts +6 -5
  286. package/dist/motion/uikit/index.d.ts.map +1 -1
  287. package/dist/motion/uikit/index.js +6 -5
  288. package/dist/motion/uikit/segmented.d.ts +55 -0
  289. package/dist/motion/uikit/segmented.d.ts.map +1 -0
  290. package/dist/motion/uikit/segmented.js +77 -0
  291. package/dist/motion/uikit/springs.d.ts +0 -18
  292. package/dist/motion/uikit/springs.d.ts.map +1 -1
  293. package/dist/motion/uikit/springs.js +5 -21
  294. package/dist/privateAttributes/pressGroupItem.stylex.d.ts +15 -0
  295. package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +1 -0
  296. package/dist/privateAttributes/pressGroupItem.stylex.js +23 -0
  297. package/dist/privateAttributes/segmentTitle.stylex.d.ts +5 -0
  298. package/dist/privateAttributes/segmentTitle.stylex.d.ts.map +1 -0
  299. package/dist/privateAttributes/segmentTitle.stylex.js +22 -0
  300. package/dist/utils/browserClickWait.d.ts +35 -0
  301. package/dist/utils/browserClickWait.d.ts.map +1 -0
  302. package/dist/utils/browserClickWait.js +123 -0
  303. package/dist/utils/clickGuard.d.ts +14 -1
  304. package/dist/utils/clickGuard.d.ts.map +1 -1
  305. package/dist/utils/clickGuard.js +46 -1
  306. package/dist/utils/dateParser.d.ts.map +1 -1
  307. package/dist/utils/dateParser.js +12 -0
  308. package/dist/utils/focusReturn.d.ts +15 -0
  309. package/dist/utils/focusReturn.d.ts.map +1 -1
  310. package/dist/utils/focusReturn.js +28 -0
  311. package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
  312. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  313. package/dist/utils/interactionOverlay.stylex.js +3 -2
  314. package/dist/utils/pressFeedback.d.ts.map +1 -1
  315. package/dist/utils/pressFeedback.js +19 -6
  316. package/dist/utils/triggerFocus.d.ts +36 -0
  317. package/dist/utils/triggerFocus.d.ts.map +1 -0
  318. package/dist/utils/triggerFocus.js +65 -0
  319. package/locales/en.json +128 -0
  320. package/locales/pseudo.json +96 -0
  321. package/package.json +18 -8
  322. package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
  323. package/src/AlertDialog/AlertDialog.tsx +37 -5
  324. package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
  325. package/src/BottomSheet/BottomSheet.tsx +95 -10
  326. package/src/BottomSheet/BottomSheetPanel.tsx +40 -5
  327. package/src/BottomSheet/bottomSheetStack.ts +8 -4
  328. package/src/BottomSheet/useSheetGestures.ts +3 -1
  329. package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
  330. package/src/Button/Button.tsx +63 -5
  331. package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
  332. package/src/CheckboxInput/CheckboxInput.tsx +36 -9
  333. package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
  334. package/src/CheckboxList/CheckboxList.tsx +6 -0
  335. package/src/CheckboxList/CheckboxListContext.tsx +6 -0
  336. package/src/CheckboxList/CheckboxListItem.tsx +11 -2
  337. package/src/ComplexSelector/ComplexSelector.tsx +20 -5
  338. package/src/ContextMenu/ContextMenu.tsx +2 -2
  339. package/src/DateInput/DateInput.tsx +8 -2
  340. package/src/DateInput/MonthScroller.tsx +18 -22
  341. package/src/DateInput/Wheel.tsx +30 -32
  342. package/src/DateTimeInput/DateTimeInput.tsx +8 -2
  343. package/src/Dialog/Dialog.tsx +54 -45
  344. package/src/Drawer/Drawer.tsx +28 -11
  345. package/src/Drawer/useDrawerDialogPresence.ts +5 -1
  346. package/src/Field/Field.tsx +2 -2
  347. package/src/Field/PanelSearchInput.tsx +11 -0
  348. package/src/HoverCard/HoverCard.doc.mjs +4 -2
  349. package/src/HoverCard/HoverCard.tsx +35 -4
  350. package/src/HoverCard/useHoverCard.doc.mjs +178 -36
  351. package/src/HoverCard/useHoverCard.tsx +46 -52
  352. package/src/Icon/Icon.doc.mjs +49 -8
  353. package/src/Icon/Icon.tsx +15 -7
  354. package/src/InfoTip/InfoTip.doc.mjs +192 -0
  355. package/src/InfoTip/InfoTip.tsx +186 -0
  356. package/src/InfoTip/index.ts +14 -0
  357. package/src/Layer/layerAnimations.stylex.ts +24 -16
  358. package/src/Layer/useBackdropPress.ts +81 -0
  359. package/src/Layer/usePressPin.ts +86 -26
  360. package/src/Lightbox/Lightbox.tsx +18 -0
  361. package/src/MobileNav/MobileNav.tsx +1 -1
  362. package/src/MultiSelector/hooks.ts +2 -4
  363. package/src/Outline/Outline.doc.mjs +177 -51
  364. package/src/Outline/Outline.tsx +5 -57
  365. package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
  366. package/src/Outline/useOutlineFromMarkdown.ts +1 -0
  367. package/src/Outline/useScrollSpy.ts +3 -3
  368. package/src/PowerSearch/PowerSearch.doc.mjs +55 -6
  369. package/src/PowerSearch/PowerSearch.tsx +798 -324
  370. package/src/PowerSearch/PowerSearchBottomSheet.tsx +2515 -0
  371. package/src/PowerSearch/PowerSearchEditPopover.tsx +28 -6
  372. package/src/PowerSearch/PowerSearchValueEditor.tsx +203 -38
  373. package/src/PowerSearch/formatFilterValue.ts +18 -1
  374. package/src/PowerSearch/formatTokenValue.ts +215 -0
  375. package/src/PowerSearch/powerSearchSheetUtils.ts +224 -0
  376. package/src/RadioList/RadioList.doc.mjs +4 -2
  377. package/src/RadioList/RadioList.tsx +7 -2
  378. package/src/RadioList/RadioListItem.tsx +5 -3
  379. package/src/Resizable/ResizeHandle.tsx +133 -328
  380. package/src/Resizable/resizeGesture.ts +368 -0
  381. package/src/SegmentedControl/SegmentedControl.doc.mjs +3 -3
  382. package/src/SegmentedControl/SegmentedControl.tsx +179 -5
  383. package/src/SegmentedControl/SegmentedControlContext.ts +6 -0
  384. package/src/SegmentedControl/SegmentedControlItem.tsx +12 -1
  385. package/src/SegmentedControl/useSegmentedSelectionMotion.ts +1640 -0
  386. package/src/SegmentedControl/useSegmentedTouchTracking.ts +308 -0
  387. package/src/Selector/Selector.tsx +136 -89
  388. package/src/Selector/optionShape.ts +64 -0
  389. package/src/Selector/selectableOptionEntries.ts +39 -0
  390. package/src/Selector/utils.ts +9 -68
  391. package/src/SideNav/SideNavItem.tsx +3 -7
  392. package/src/Slider/sliderTouchController.ts +2 -2
  393. package/src/Spinner/Spinner.tsx +77 -63
  394. package/src/Switch/switchTouch.ts +2 -10
  395. package/src/Switch/useSwitchTouch.ts +38 -104
  396. package/src/Table/BaseTable.tsx +46 -12
  397. package/src/TextInput/TextInput.tsx +11 -10
  398. package/src/Toast/Toast.doc.mjs +4 -3
  399. package/src/Toast/Toast.tsx +57 -32
  400. package/src/Toast/ToastViewport.tsx +121 -35
  401. package/src/Toast/types.ts +9 -3
  402. package/src/Token/Token.tsx +15 -4
  403. package/src/Token/TokenNamingContext.ts +28 -0
  404. package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
  405. package/src/Tokenizer/Tokenizer.tsx +89 -85
  406. package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
  407. package/src/Tokenizer/TokenizerItemRemovableContext.ts +24 -0
  408. package/src/Tooltip/Tooltip.doc.mjs +123 -54
  409. package/src/Tooltip/Tooltip.tsx +36 -39
  410. package/src/Tooltip/index.ts +0 -1
  411. package/src/Tooltip/useTooltip.doc.mjs +171 -33
  412. package/src/Tooltip/useTooltip.tsx +98 -86
  413. package/src/TopNav/TopNavItem.tsx +6 -5
  414. package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
  415. package/src/TopNav/TopNavMenu.tsx +6 -5
  416. package/src/Typeahead/TypeaheadItem.doc.mjs +6 -3
  417. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  418. package/src/animator/createAnimator.doc.mjs +153 -0
  419. package/src/animator/createAnimator.ts +324 -0
  420. package/src/gestures/GestureHost.ts +198 -20
  421. package/src/gestures/PressTracker.ts +4 -3
  422. package/src/gestures/README.md +7 -1
  423. package/src/hooks/actionGroupTracker.ts +32 -61
  424. package/src/hooks/index.ts +2 -0
  425. package/src/hooks/pressGroupContext.ts +21 -0
  426. package/src/hooks/useActionGroupTracking.ts +224 -0
  427. package/src/hooks/useAnimator.doc.mjs +109 -0
  428. package/src/hooks/useAnimator.ts +67 -0
  429. package/src/hooks/useAttachment.ts +1 -1
  430. package/src/hooks/useHighlightedOptionScroll.ts +123 -15
  431. package/src/hooks/useInputStatusIcon.tsx +3 -2
  432. package/src/hooks/useLongPress.doc.mjs +2 -2
  433. package/src/hooks/useLongPress.ts +136 -15
  434. package/src/hooks/useMediaQuery.doc.mjs +2 -2
  435. package/src/hooks/useMediaQuery.ts +13 -4
  436. package/src/hooks/useMenuPress.ts +8 -2
  437. package/src/i18n/generated-english/InfoTip/InfoTip.generated.ts +13 -0
  438. package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
  439. package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +43 -0
  440. package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +6 -0
  441. package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +15 -0
  442. package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
  443. package/src/i18n/generated-locales/en.generated.ts +43 -0
  444. package/src/i18n/generated-locales/pseudo.generated.ts +44 -0
  445. package/src/index.ts +14 -0
  446. package/src/internal/SheetConfirmation.tsx +101 -0
  447. package/src/internal/indicatorPress.stylex.ts +24 -0
  448. package/src/internal/sheetDraft.ts +80 -0
  449. package/src/internal/touch-sheet.ts +21 -0
  450. package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
  451. package/src/motion/MEASUREMENTS.md +7 -0
  452. package/src/motion/README.md +9 -5
  453. package/src/motion/animator.ts +61 -29
  454. package/src/motion/reducedMotion.ts +79 -1
  455. package/src/motion/roles.ts +20 -1
  456. package/src/motion/uikit/alert.ts +43 -0
  457. package/src/motion/uikit/index.ts +5 -5
  458. package/src/motion/uikit/segmented.ts +74 -0
  459. package/src/motion/uikit/springs.ts +5 -21
  460. package/src/privateAttributes/pressGroupItem.stylex.ts +23 -0
  461. package/src/privateAttributes/segmentTitle.stylex.ts +22 -0
  462. package/src/utils/browserClickWait.ts +133 -0
  463. package/src/utils/clickGuard.ts +64 -1
  464. package/src/utils/dateParser.ts +16 -0
  465. package/src/utils/focusReturn.ts +30 -0
  466. package/src/utils/interactionOverlay.stylex.ts +3 -2
  467. package/src/utils/pressFeedback.ts +19 -6
  468. package/src/utils/triggerFocus.ts +80 -0
  469. package/dist/internal/tokenizer-presentation.d.ts +0 -21
  470. package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
  471. package/dist/internal/tokenizer-presentation.js +0 -21
  472. package/src/internal/tokenizer-presentation.ts +0 -25
@@ -0,0 +1,1349 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useSegmentedSelectionMotion.ts
7
+ * @input The radiogroup, the selected value, the presentation and tracking
8
+ * flags; the animator, the UIKit table and the touch tracking
9
+ * @output Exports `useSegmentedSelectionMotion`, `SegmentedSelectionMotion`,
10
+ * the layer custom properties and the segment and title selectors
11
+ * @position SegmentedControl's touch layer
12
+ *
13
+ * SYNC: When modified, update:
14
+ * - /packages/core/src/SegmentedControl/SegmentedControl.tsx (the pseudo-elements)
15
+ * - /packages/core/src/SegmentedControl/SegmentedControlItem.tsx (the title marker)
16
+ * - /packages/core/src/SegmentedControl/useSegmentedTouchTracking.ts
17
+ */
18
+ import { useCallback, useEffect, useRef, useState } from 'react';
19
+ import { createMotionAnimator } from "../motion/animator.js";
20
+ import { registerSegmentedTable, segmentedControl } from "../motion/uikit/segmented.js";
21
+ import { registerSpringsTable, springs } from "../motion/uikit/springs.js";
22
+ import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
23
+ import { observeResize } from "../utils/sharedResizeObserver.js";
24
+ import { titleOfSegment } from "../privateAttributes/segmentTitle.stylex.js";
25
+ import { cubicBezier } from "../motion/bezier.js";
26
+ import { parseCssEasing, parseMotionRoleValue } from "../motion/roles.js";
27
+ import { useSegmentedTouchTracking } from "./useSegmentedTouchTracking.js";
28
+
29
+ /** The segments, in DOM order. */
30
+ export const SEGMENT_SELECTOR = '[role="radio"][data-value]';
31
+ /**
32
+ * The selected face moves to another segment only when the lens's center is
33
+ * this far past the boundary. The distance is a quarter of that segment's
34
+ * width, at least 8 px.
35
+ */
36
+ const FACE_HYSTERESIS_RATIO = 0.25;
37
+ const FACE_HYSTERESIS_MIN_PX = 8;
38
+
39
+ /** The selected surface's paint, copied into a layer's custom properties. */
40
+ export const SURFACE_PROPERTIES = ['background-color', 'background-image', 'background-position', 'background-size', 'background-repeat', 'background-origin', 'background-clip', 'box-shadow', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius', 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width', 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style', 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color', 'backdrop-filter', '-webkit-backdrop-filter'];
41
+
42
+ /**
43
+ * The radiogroup's two pseudo-element layers: the lens (::before) and the
44
+ * ::after, which under Reduced Motion shows the surface that fades out where
45
+ * it is.
46
+ */
47
+
48
+ /** Box and visibility of a layer, besides its paint. */
49
+ export const LAYER_GEOMETRY = ['left', 'top', 'width', 'height', 'visibility'];
50
+
51
+ /** The custom property on the radiogroup that sets `property` of `layer`. */
52
+ export function layerVariable(layer, property) {
53
+ return `--_segmented-${layer}-${property.replace(/^-+/, '')}`;
54
+ }
55
+
56
+ /**
57
+ * What the destination segment shows while the lens carries its surface.
58
+ * Written inline with priority so a theme's rule for the selected segment
59
+ * cannot paint a second surface; borders keep their width so nothing moves.
60
+ */
61
+ const HIDDEN_SURFACE = [['background-color', 'transparent'], ['background-image', 'none'], ['box-shadow', 'none'], ['border-top-color', 'transparent'], ['border-right-color', 'transparent'], ['border-bottom-color', 'transparent'], ['border-left-color', 'transparent'], ['backdrop-filter', 'none'], ['-webkit-backdrop-filter', 'none']];
62
+
63
+ /** A title's face: what a dragging finger moves between titles. */
64
+
65
+ /** A box in the track's padding-box coordinates, in CSS px. */
66
+
67
+ /** A finger's touch, from landing to letting go. */
68
+
69
+ /**
70
+ * The motion a release starts after its commit's click runs. It aims at the
71
+ * destination's box when the new value shows, because the titles' new weights
72
+ * move the segments. Until the value comes back, it aims at the box measured
73
+ * at landing.
74
+ */
75
+
76
+ function faceOf(element) {
77
+ const style = getComputedStyle(element);
78
+ return {
79
+ color: style.color,
80
+ fontWeight: style.fontWeight
81
+ };
82
+ }
83
+
84
+ /**
85
+ * A hidden segment's own inline declarations (its `style`) for the properties
86
+ * `hideSurface` overrides: value and priority, restored by `showSurface`.
87
+ */
88
+ const ownSurface = new WeakMap();
89
+ function hideSurface(segment) {
90
+ if (!ownSurface.has(segment)) {
91
+ ownSurface.set(segment, HIDDEN_SURFACE.map(([property]) => [property, segment.style.getPropertyValue(property), segment.style.getPropertyPriority(property)]));
92
+ }
93
+ for (const [property, value] of HIDDEN_SURFACE) {
94
+ segment.style.setProperty(property, value, 'important');
95
+ }
96
+ }
97
+
98
+ /** Gives a hidden segment its surface back, its own inline values included. */
99
+ function showSurface(segment) {
100
+ const own = ownSurface.get(segment);
101
+ if (own == null) {
102
+ return;
103
+ }
104
+ ownSurface.delete(segment);
105
+ for (const [property, value, priority] of own) {
106
+ if (value === '') {
107
+ segment.style.removeProperty(property);
108
+ } else {
109
+ segment.style.setProperty(property, value, priority);
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Paints `layer` with `segment`'s computed surface. All values are read
116
+ * before any is written. A write to the track's style makes the next
117
+ * computed-style read recompute. Interleaved reads and writes restyle the
118
+ * control once per property.
119
+ */
120
+ function paintLayer(track, layer, segment) {
121
+ const style = getComputedStyle(segment);
122
+ const values = SURFACE_PROPERTIES.map(property => style.getPropertyValue(property));
123
+ SURFACE_PROPERTIES.forEach((property, index) => {
124
+ track.style.setProperty(layerVariable(layer, property), values[index]);
125
+ });
126
+ }
127
+
128
+ /** Copies the lens's paint onto the ::after. */
129
+ function copyLensPaint(track) {
130
+ for (const property of SURFACE_PROPERTIES) {
131
+ track.style.setProperty(layerVariable('after', property), track.style.getPropertyValue(layerVariable('lens', property)));
132
+ }
133
+ }
134
+
135
+ /** Lays `layer` out on `box` and shows it. */
136
+ function placeLayer(track, layer, box) {
137
+ track.style.setProperty(layerVariable(layer, 'left'), `${box.x}px`);
138
+ track.style.setProperty(layerVariable(layer, 'top'), `${box.y}px`);
139
+ track.style.setProperty(layerVariable(layer, 'width'), `${box.width}px`);
140
+ track.style.setProperty(layerVariable(layer, 'height'), `${box.height}px`);
141
+ track.style.setProperty(layerVariable(layer, 'visibility'), 'visible');
142
+ }
143
+
144
+ /** Hides `layer` and drops its custom properties. */
145
+ function clearLayer(track, layer) {
146
+ for (const property of [...LAYER_GEOMETRY, ...SURFACE_PROPERTIES]) {
147
+ track.style.removeProperty(layerVariable(layer, property));
148
+ }
149
+ }
150
+
151
+ /**
152
+ * The scale an ancestor transform applies to the track's client rects. The
153
+ * lens lays out untransformed. `offsetWidth` rounds to whole pixels, so a
154
+ * fractional track width reads as a scale slightly off 1. Only a real
155
+ * transform moves it further than that rounding.
156
+ */
157
+ function scaleOf(track, rect) {
158
+ if (track.offsetWidth <= 0 || rect.width <= 0) {
159
+ return 1;
160
+ }
161
+ return Math.abs(rect.width - track.offsetWidth) <= 1 ? 1 : rect.width / track.offsetWidth;
162
+ }
163
+
164
+ /** Boxes relative to the track's padding box. */
165
+ function boxesIn(track) {
166
+ const trackRect = track.getBoundingClientRect();
167
+ const scale = scaleOf(track, trackRect);
168
+ return element => {
169
+ const rect = element.getBoundingClientRect();
170
+ return {
171
+ x: (rect.left - trackRect.left) / scale - track.clientLeft,
172
+ y: (rect.top - trackRect.top) / scale - track.clientTop,
173
+ width: rect.width / scale,
174
+ height: rect.height / scale
175
+ };
176
+ };
177
+ }
178
+
179
+ /** Maps a client x to the track's padding-box x, read once per touch. */
180
+ function trackXIn(track) {
181
+ const rect = track.getBoundingClientRect();
182
+ const scale = scaleOf(track, rect);
183
+ return clientX => (clientX - rect.left) / scale - track.clientLeft;
184
+ }
185
+ function segmentsOf(track) {
186
+ return Array.from(track.querySelectorAll(SEGMENT_SELECTOR));
187
+ }
188
+ function isLaidOut(box) {
189
+ return box.width > 0 && box.height > 0;
190
+ }
191
+ function center(box) {
192
+ return box.x + box.width / 2;
193
+ }
194
+
195
+ /** Whether two boxes are the same to a quarter of a pixel. */
196
+ function isSameBox(a, b) {
197
+ return Math.abs(a.x - b.x) < 0.25 && Math.abs(a.y - b.y) < 0.25 && Math.abs(a.width - b.width) < 0.25 && Math.abs(a.height - b.height) < 0.25;
198
+ }
199
+ function isEnabledSegment(segment) {
200
+ return !segment.hasAttribute('disabled') && segment.getAttribute('aria-disabled') !== 'true';
201
+ }
202
+ const titleOf = titleOfSegment;
203
+
204
+ /** The theme's timing: a duration in seconds and its easing. */
205
+
206
+ /**
207
+ * The timing of `segment`'s own transition, which reads the theme's duration
208
+ * and easing tokens. The values stay readable while a carry turns the
209
+ * transition's property off. Null when the theme gives no duration.
210
+ */
211
+ function themeTimingOf(segment) {
212
+ const view = segment.ownerDocument.defaultView;
213
+ if (view == null) {
214
+ return null;
215
+ }
216
+ const style = view.getComputedStyle(segment);
217
+ const ms = parseMotionRoleValue('duration', style.transitionDuration.split(',')[0] ?? '');
218
+ const points = parseCssEasing(style.transitionTimingFunction.split(/,(?![^(]*\))/)[0] ?? '');
219
+ if (ms == null || ms <= 0 || points == null) {
220
+ return null;
221
+ }
222
+ return {
223
+ seconds: ms / 1000,
224
+ ease: cubicBezier(points)
225
+ };
226
+ }
227
+
228
+ /** A theme that gives the segments no transition: the lens lands at once. */
229
+ const INSTANT = {
230
+ seconds: 0.001,
231
+ ease: progress => progress
232
+ };
233
+
234
+ /** A curve from `from` to `to` on `timing`. */
235
+ function timedCurve(from, to, timing) {
236
+ const {
237
+ seconds,
238
+ ease
239
+ } = timing;
240
+ const at = t => from + (to - from) * ease(Math.min(1, Math.max(0, t / seconds)));
241
+ const step = Math.min(seconds / 100, 0.001);
242
+ return {
243
+ duration: seconds,
244
+ at,
245
+ velocity: t => {
246
+ const before = Math.max(0, t - step);
247
+ const after = Math.min(seconds, t + step);
248
+ return (at(after) - at(before)) / (after - before);
249
+ }
250
+ };
251
+ }
252
+
253
+ /** What the control reads of its selection motion. */
254
+
255
+ function defaultNow() {
256
+ return typeof performance === 'undefined' ? Date.now() : performance.now();
257
+ }
258
+
259
+ /**
260
+ * The selection motion of one SegmentedControl, as UIKit's segmented control
261
+ * moves it. It lives outside React's render cycle. Nothing here sets state,
262
+ * so a slide never re-renders anything.
263
+ *
264
+ * At rest, the selected segment paints its own surface, so themes style it
265
+ * as before. For a slide, the lens takes over. The lens is the radiogroup's
266
+ * ::before, painted from custom properties copied from the segment's
267
+ * computed surface. The segment hides its own surface inline, so a theme
268
+ * rule cannot keep it painted. The lens moves with `translate` and `scale`
269
+ * on the compositor animator. Nothing lays out or paints per frame, and the
270
+ * slide keeps moving while JavaScript is busy. The lens lays out on the
271
+ * destination's box. It lands at translate 0 and scale 1 and hands the
272
+ * surface back pixel for pixel. Its position and its size run on separate
273
+ * springs, as UIKit's do. A change mid-flight starts from the presented
274
+ * geometry and its velocities. The titles change with the selection, at
275
+ * once.
276
+ *
277
+ * Under a finger:
278
+ * - A theme role owns a pressed thumb's lift, so a press on the selected
279
+ * segment lifts nothing here.
280
+ * - A drag from the selected segment carries the lens at its resting size.
281
+ * Its center chases the finger, at most `dragOvershoot` past the end
282
+ * segments.
283
+ * - The selected title's color follows the lens's center. It moves to a
284
+ * neighbor only once the center is a quarter of that segment past the
285
+ * boundary. Every title keeps its weight until the lift.
286
+ * - The lift commits the segment nearest the finger, through that segment's
287
+ * click. A lift outside the control commits nothing, and the lens settles
288
+ * back.
289
+ * - A press on another segment leaves the lens in place until the lift.
290
+ * Meanwhile that segment shows the shared pressed overlay.
291
+ * - A click that the item cancels sends the lens back at once. Otherwise the
292
+ * lens waits for the new value, which a parent can adopt later. It goes
293
+ * back only if the slide settles without the new value.
294
+ * - A cancelled touch puts the lens back.
295
+ *
296
+ * Under a mouse or a pen, the same press commits the same segment, but the
297
+ * theme owns the timing. A click, a key or a new value slides the lens on the
298
+ * theme's duration and easing, as the segments' own transition does. A drag
299
+ * from the selected segment carries the lens on the pointer itself.
300
+ *
301
+ * Under Reduced Motion, a held lens still follows the pointer: that is direct
302
+ * movement, not a transition. Nothing else chases, travels or scales. The lens
303
+ * fades in at the new segment while ::after, painted with the old surface,
304
+ * fades out in place. With a fine pointer under Reduce Motion, a change shows
305
+ * through the theme's transition.
306
+ */
307
+ export class SegmentedSelectionMotion {
308
+ track = null;
309
+ carried = null;
310
+ /**
311
+ * Segments whose surface the lens carried until a change turned it
312
+ * elsewhere. They keep their surface hidden until the lens lands. A tap's
313
+ * press paint can still fade on such a segment. That paint would cover the
314
+ * lens, which sits under the segments' backgrounds, while the lens crosses
315
+ * it.
316
+ */
317
+ abandoned = new Set();
318
+ /** The segment showing the selected face under a dragging finger. */
319
+ facedSegment = null;
320
+ /** The frame request that keeps the face on the lens while it is carried. */
321
+ lensFrame = null;
322
+ /** Titles wearing the other face's color during a drag. */
323
+ faceOverrides = new Set();
324
+ /** The motions the hand-back waits for. */
325
+ running = new Set();
326
+ /** A finger holds the lens: a drag from the selected segment carries it. */
327
+ isHeld = false;
328
+ /**
329
+ * The touch presentation applies (a coarse primary pointer), so every
330
+ * change slides on UIKit's springs. When it is off, a change slides on the
331
+ * theme's duration and easing, as the segments' transition does. A drag from
332
+ * the selected segment carries the lens on the pointer itself.
333
+ */
334
+ isPresenting = true;
335
+ /**
336
+ * Segments whose own transitions are off while a fine pointer's carry is
337
+ * on screen, with their own inline values to give back.
338
+ */
339
+ stilled = new Map();
340
+ /** The frame request that gives the stilled segments their transitions back. */
341
+ thawFrame = null;
342
+ touch = null;
343
+ /**
344
+ * The value a touch already moved the lens to: the selection change that
345
+ * follows (the controlled value coming back) re-aims the lens.
346
+ */
347
+ committed = null;
348
+ /**
349
+ * The commit's click ran without a veto: the lens waits for the value
350
+ * until the slide settles.
351
+ */
352
+ isAwaitingValue = false;
353
+ /** The motion a release starts once its commit's click has run. */
354
+ pending = null;
355
+ /** The last release was a finger's (decline and re-aim follow it). */
356
+ releasedByFinger = true;
357
+ /** The ::after shows the leaving surface (Reduced Motion). */
358
+ isLeavingShown = false;
359
+ /** Bumped by every leaving fade; a finished fade clears only its own. */
360
+ leaving = 0;
361
+
362
+ /** The ::after: the surface fading out under Reduced Motion. */
363
+
364
+ constructor(animator = createMotionAnimator(null, {
365
+ pseudoElement: '::before'
366
+ }), options = {}) {
367
+ this.animator = animator;
368
+ // The chase plays the shared `springs.fingerTracking` role, and the slide
369
+ // plays the segmented role. Both register before the first animation,
370
+ // because nothing registers on import. A second call does nothing.
371
+ registerSpringsTable();
372
+ registerSegmentedTable();
373
+ this.now = options.now ?? defaultNow;
374
+ this.after = options.afterAnimator ?? createMotionAnimator(null, {
375
+ pseudoElement: '::after',
376
+ initial: {
377
+ opacity: 0
378
+ }
379
+ });
380
+ }
381
+
382
+ /** Attaches the radiogroup whose pseudo-elements are the lens (null detaches). */
383
+ attachTrack(element) {
384
+ if (element !== this.track) {
385
+ this.settle();
386
+ }
387
+ this.track = element;
388
+ this.animator.attach(element);
389
+ this.after.attach(element);
390
+ }
391
+
392
+ /** True while the lens carries the selected surface. */
393
+ get isSliding() {
394
+ return this.carried != null;
395
+ }
396
+
397
+ /** A press (any pointer's) is down on the control, from landing to letting go. */
398
+ get isPressed() {
399
+ return this.touch != null;
400
+ }
401
+
402
+ /**
403
+ * Whether the touch presentation applies. Off, under a fine primary
404
+ * pointer, a change slides on the theme's timing, and a drag from the
405
+ * selected segment still carries the lens. A change of presentation hands
406
+ * anything in flight back.
407
+ */
408
+ present(isPresenting) {
409
+ if (this.isPresenting === isPresenting) {
410
+ return;
411
+ }
412
+ this.isPresenting = isPresenting;
413
+ this.settle();
414
+ }
415
+
416
+ /** The segment that commits `value`, if the track has one. */
417
+ segmentFor(track, value) {
418
+ return segmentsOf(track).find(segment => segment.dataset.value === value) ?? null;
419
+ }
420
+
421
+ /** The selection moved from `from` to `to`; the DOM already shows `to`. */
422
+ select(track, from, to) {
423
+ if (this.track !== track) {
424
+ return;
425
+ }
426
+ const origin = this.segmentFor(track, from);
427
+ const destination = this.segmentFor(track, to);
428
+ const isTouchCommit = this.committed === to;
429
+ // The commit's own styles take over from a drag's borrowed faces.
430
+ this.clearFaces();
431
+ this.committed = null;
432
+ this.isAwaitingValue = false;
433
+ if (isTouchCommit) {
434
+ if (this.pending?.destination === destination && destination != null) {
435
+ // Adopted during the click: the lens moves now, on the box the
436
+ // destination has with the new value shown.
437
+ this.startPending(destination, boxesIn(track)(destination));
438
+ }
439
+ // The new selection now paints the surface the lens carries, and its
440
+ // titles' weights may have moved the segments since the lens set off.
441
+ const carried = this.carried;
442
+ if (carried != null && carried.segment === destination) {
443
+ if (carried.hidden !== destination) {
444
+ showSurface(carried.hidden);
445
+ paintLayer(track, 'lens', destination);
446
+ hideSurface(destination);
447
+ carried.hidden = destination;
448
+ }
449
+ this.reaim(track, destination);
450
+ }
451
+ return;
452
+ }
453
+ if (!this.slidesChanges()) {
454
+ // A fine pointer under Reduce Motion: the theme's transition shows the
455
+ // change.
456
+ this.settle();
457
+ return;
458
+ }
459
+ if (destination == null) {
460
+ // Nothing is selected any more: no surface to carry.
461
+ this.settle();
462
+ return;
463
+ }
464
+ if (this.carried == null && origin == null) {
465
+ // Nothing was selected: there is no surface to slide from.
466
+ return;
467
+ }
468
+ const boxOf = boxesIn(track);
469
+ const to_ = boxOf(destination);
470
+ if (!isLaidOut(to_)) {
471
+ this.settle();
472
+ return;
473
+ }
474
+ const from_ = this.carried == null ? boxOf(origin) : null;
475
+ if (this.animator.reducedMotion() !== 'off') {
476
+ this.crossFadeTo(destination, to_, from_, destination, false);
477
+ return;
478
+ }
479
+ const at = this.carry(destination, to_, from_, destination);
480
+ // A programmatic or keyboard change: the whole box slides.
481
+ this.settleLens(destination, {
482
+ at
483
+ });
484
+ }
485
+
486
+ /**
487
+ * Whether a change of value slides the lens. A fine pointer under Reduce
488
+ * Motion leaves the change to the theme's transition.
489
+ */
490
+ slidesChanges() {
491
+ return this.isPresenting || this.animator.reducedMotion() === 'off';
492
+ }
493
+
494
+ /**
495
+ * A press landed on `origin`, the segment the action-group tracker resolved
496
+ * for it (null: none). It reads the track's geometry and title faces once.
497
+ * Under Reduced Motion a finger's release cross-fades.
498
+ */
499
+ begin(track, origin, isFinger = true) {
500
+ const segments = segmentsOf(track);
501
+ const boxOf = boxesIn(track);
502
+ const boxes = new Map(segments.map(segment => [segment, boxOf(segment)]));
503
+ const toTrackX = trackXIn(track);
504
+ const selected = segments.find(segment => segment.getAttribute('aria-checked') === 'true') ?? null;
505
+ const carriesLens = origin != null && origin === selected;
506
+ const selectedTitle = selected != null ? titleOf(selected) : null;
507
+ // An enabled segment's unselected face: a disabled title has its own.
508
+ const other = segments.find(segment => segment !== selected && isEnabledSegment(segment));
509
+ const otherTitle = other != null ? titleOf(other) : null;
510
+ const faces = selectedTitle != null && otherTitle != null ? {
511
+ selected: faceOf(selectedTitle),
512
+ unselected: faceOf(otherTitle)
513
+ } : null;
514
+ this.touch = {
515
+ toTrackX,
516
+ boxes,
517
+ origin,
518
+ carriesLens,
519
+ isFinger,
520
+ faces
521
+ };
522
+ }
523
+
524
+ /**
525
+ * The finger moved to `clientX`. A drag from the selected segment carries
526
+ * the lens at its resting size. A touch elsewhere leaves the lens alone,
527
+ * and that segment shows the shared pressed overlay.
528
+ */
529
+ drag(clientX) {
530
+ const touch = this.touch;
531
+ if (touch == null || !touch.carriesLens) {
532
+ return;
533
+ }
534
+ this.hold();
535
+ if (!this.isHeld) {
536
+ return;
537
+ }
538
+ this.chase(clientX);
539
+ this.followLens();
540
+ }
541
+
542
+ /**
543
+ * The first move of a drag from the selected segment binds the lens there,
544
+ * at its resting size. Under Reduced Motion too: the lens follows the hand,
545
+ * and only the release cross-fades.
546
+ */
547
+ hold() {
548
+ const touch = this.touch;
549
+ if (touch == null || touch.origin == null || this.isHeld) {
550
+ return;
551
+ }
552
+ const box = touch.boxes.get(touch.origin);
553
+ if (!isLaidOut(box)) {
554
+ return;
555
+ }
556
+ this.carry(touch.origin, box, null, touch.origin);
557
+ this.isHeld = true;
558
+ }
559
+
560
+ /**
561
+ * The selected face follows the held lens: it goes to the segment under the
562
+ * lens's presented center. Hysteresis around each boundary keeps a finger
563
+ * that rests on a boundary from flipping it. This runs every frame while
564
+ * the lens is held, so the face keeps up as the lens settles under a still
565
+ * finger.
566
+ */
567
+ followLens() {
568
+ const touch = this.touch;
569
+ const carried = this.carried;
570
+ if (touch == null || !touch.carriesLens || !this.isHeld || carried == null || touch.origin == null) {
571
+ this.lensFrame = null;
572
+ return;
573
+ }
574
+ const lensCenter = this.presented(carried.box, this.now()).center;
575
+ const spans = [...touch.boxes.entries()].filter(([segment]) => isEnabledSegment(segment)).sort((a, b) => a[1].x - b[1].x);
576
+ const current = this.facedSegment ?? touch.origin;
577
+ const at = spans.findIndex(([segment]) => segment === current);
578
+ if (at >= 0) {
579
+ const edge = i => (spans[i][1].x + spans[i][1].width + spans[i + 1][1].x) / 2;
580
+ let next = at;
581
+ while (next + 1 < spans.length && lensCenter > edge(next)) {
582
+ next += 1;
583
+ }
584
+ while (next > 0 && lensCenter < edge(next - 1)) {
585
+ next -= 1;
586
+ }
587
+ if (next !== at) {
588
+ const boundary = next > at ? edge(at) : edge(at - 1);
589
+ const hysteresis = Math.max(FACE_HYSTERESIS_MIN_PX, FACE_HYSTERESIS_RATIO * spans[next][1].width);
590
+ if (Math.abs(lensCenter - boundary) >= hysteresis) {
591
+ const segment = spans[next][0];
592
+ this.showFaceAt(segment === touch.origin ? null : segment);
593
+ }
594
+ }
595
+ }
596
+ const view = this.track?.ownerDocument.defaultView;
597
+ if (this.lensFrame == null && view != null) {
598
+ this.lensFrame = view.requestAnimationFrame(() => {
599
+ this.lensFrame = null;
600
+ this.followLens();
601
+ });
602
+ }
603
+ }
604
+
605
+ /** The held lens's center chases the finger at `clientX`. */
606
+ chase(clientX) {
607
+ const touch = this.touch;
608
+ const carried = this.carried;
609
+ if (touch == null || carried == null) {
610
+ return;
611
+ }
612
+ const centers = [...touch.boxes.values()].map(center);
613
+ const reach = segmentedControl.dragOvershoot;
614
+ const target = Math.min(Math.max(touch.toTrackX(clientX), Math.min(...centers) - reach), Math.max(...centers) + reach);
615
+ if (!this.isPresenting || this.animator.reducedMotion() !== 'off') {
616
+ // A mouse or a pen holds the lens directly, as a finger does under
617
+ // Reduce Motion. Each move writes the pointer's position.
618
+ this.animator.set({
619
+ x: target - center(carried.box)
620
+ });
621
+ return;
622
+ }
623
+ // Start each chase at the call. A chase from rest waits for the browser
624
+ // to start it. Android Chrome sends the next move first, so the lens
625
+ // would stick.
626
+ const at = this.now();
627
+ this.springLens({
628
+ x: target - center(carried.box)
629
+ }, this.springFrom({
630
+ x: target - center(carried.box)
631
+ }, 'tracking'), {
632
+ at
633
+ });
634
+ }
635
+
636
+ /**
637
+ * The finger let go on `target`, the segment the tracker resolved for it
638
+ * (null: none, or a disabled one). Moves the lens there and returns its
639
+ * value when it is not the selected one (the tracker's lift commits it
640
+ * through its click), else null.
641
+ */
642
+ release(target) {
643
+ const touch = this.touch;
644
+ this.touch = null;
645
+ if (touch == null) {
646
+ return null;
647
+ }
648
+ const selected = [...touch.boxes.keys()].find(segment => segment.getAttribute('aria-checked') === 'true') ?? null;
649
+ // A disabled segment never commits: the lens settles back on the
650
+ // selection.
651
+ const destination = (target != null && isEnabledSegment(target) ? target : null) ?? selected;
652
+ if (destination == null) {
653
+ this.clearFaces();
654
+ if (this.isHeld) {
655
+ this.isHeld = false;
656
+ this.settle();
657
+ }
658
+ return null;
659
+ }
660
+ const box = touch.boxes.get(destination);
661
+ const value = destination !== selected ? destination.dataset.value ?? null : null;
662
+ // A drag's borrowed face stays only if the lift commits (the commit
663
+ // takes it over).
664
+ if (value == null) {
665
+ this.clearFaces();
666
+ }
667
+ const isHeld = touch.carriesLens && this.isHeld;
668
+ this.committed = value;
669
+ this.isAwaitingValue = false;
670
+ this.releasedByFinger = touch.isFinger;
671
+ if (value == null) {
672
+ if (isHeld) {
673
+ // The held lens settles back on the selection.
674
+ this.moveLensTo(destination, box, selected ?? destination, touch.isFinger);
675
+ }
676
+ return null;
677
+ }
678
+ // The commit follows at once, through the segment's click; the lens
679
+ // moves once it has run, on the box the destination has then. A held
680
+ // lens settles there; a tap (or a drag that started elsewhere) slides
681
+ // there from the selected segment.
682
+ // A fine pointer under Reduce Motion moves only a held lens.
683
+ if (isHeld || selected != null && this.slidesChanges()) {
684
+ this.pending = {
685
+ destination,
686
+ box,
687
+ isHeld,
688
+ from: !isHeld && selected != null ? touch.boxes.get(selected) : null,
689
+ touchRelease: touch.isFinger
690
+ };
691
+ }
692
+ return value;
693
+ }
694
+
695
+ /**
696
+ * The commit's click for `value` has run. A click that the item's
697
+ * `onClick` cancels sends the lens back at once. Otherwise the lens waits
698
+ * for the value, which a parent can adopt later.
699
+ */
700
+ clicked(track, value, isCancelled) {
701
+ if (this.committed !== value) {
702
+ // Adopted during the click, or superseded.
703
+ return;
704
+ }
705
+ if (isCancelled) {
706
+ this.pending = null;
707
+ this.decline(track, value);
708
+ return;
709
+ }
710
+ this.isAwaitingValue = true;
711
+ // React can render the click's change only after the event that
712
+ // dispatched it ends: a click inside a touch handler is batched with it.
713
+ // Wait one turn. If the value is adopted by then, the lens already moves
714
+ // on the new box. Otherwise it aims at the box measured at landing and
715
+ // re-aims when the value comes back.
716
+ queueMicrotask(() => {
717
+ if (this.committed === value && this.pending != null) {
718
+ this.startPending(this.selectedIn(track));
719
+ }
720
+ });
721
+ }
722
+
723
+ /**
724
+ * The value the finger let go on was not adopted. The item's `onClick`
725
+ * cancelled it, or a controlled parent kept its value. The lens goes back
726
+ * to the selected segment. The next change to that value slides as any
727
+ * change does.
728
+ */
729
+ decline(track, value) {
730
+ if (this.committed !== value) {
731
+ return;
732
+ }
733
+ this.committed = null;
734
+ this.isAwaitingValue = false;
735
+ this.pending = null;
736
+ this.clearFaces();
737
+ const selected = segmentsOf(track).find(segment => segment.getAttribute('aria-checked') === 'true') ?? null;
738
+ if (selected == null || this.carried == null) {
739
+ this.settle();
740
+ return;
741
+ }
742
+ const box = boxesIn(track)(selected);
743
+ if (!isLaidOut(box)) {
744
+ this.settle();
745
+ return;
746
+ }
747
+ this.moveLensTo(selected, box, selected, this.releasedByFinger);
748
+ }
749
+
750
+ /** The touch was taken away (the page scrolled): the lens goes back. */
751
+ cancel() {
752
+ const touch = this.touch;
753
+ this.touch = null;
754
+ this.clearFaces();
755
+ if (touch == null || !touch.carriesLens || touch.origin == null || !this.isHeld) {
756
+ // Nothing was held: nothing to put back.
757
+ return;
758
+ }
759
+ const box = touch.boxes.get(touch.origin);
760
+ this.moveLensTo(touch.origin, box, touch.origin, touch.isFinger);
761
+ }
762
+
763
+ /** Hands the surface back to its segment now and hides the lens. */
764
+ settle() {
765
+ this.endLeaving();
766
+ this.isHeld = false;
767
+ this.pending = null;
768
+ const carried = this.carried;
769
+ if (carried != null) {
770
+ this.carried = null;
771
+ this.running.clear();
772
+ this.animator.stop();
773
+ this.after.stop();
774
+ showSurface(carried.hidden);
775
+ for (const segment of this.abandoned) {
776
+ showSurface(segment);
777
+ }
778
+ this.abandoned.clear();
779
+ if (this.track != null) {
780
+ clearLayer(this.track, 'lens');
781
+ clearLayer(this.track, 'after');
782
+ }
783
+ this.isLeavingShown = false;
784
+ }
785
+ // Once the surfaces are back on screen, so nothing fades in.
786
+ this.thawTransitions();
787
+ }
788
+
789
+ /**
790
+ * The control resized. The lens aims at boxes measured when its motion
791
+ * began, so it hands the surface back rather than land somewhere stale.
792
+ * Two cases keep the motion. In the first, the track keeps its measured
793
+ * size: a new title weight resized a content-sized control before the box
794
+ * was measured. In the second, a finger holds the lens.
795
+ */
796
+ resized(track) {
797
+ const carried = this.carried;
798
+ // A finger that holds the lens reflows the titles itself, because the
799
+ // selected face moves to the title under it. The lens follows the finger,
800
+ // and the commit measures the destination again.
801
+ if (carried == null || this.touch != null) {
802
+ return;
803
+ }
804
+ if (Math.abs(track.offsetWidth - carried.trackSize.width) < 0.5 && Math.abs(track.offsetHeight - carried.trackSize.height) < 0.5) {
805
+ return;
806
+ }
807
+ this.settle();
808
+ }
809
+
810
+ /**
811
+ * Ends everything in flight (unmount). The controller stays usable, so an
812
+ * effect cleanup followed by a setup (StrictMode's extra mount) loses
813
+ * nothing.
814
+ */
815
+ stop() {
816
+ this.touch = null;
817
+ if (this.lensFrame != null) {
818
+ this.track?.ownerDocument.defaultView?.cancelAnimationFrame(this.lensFrame);
819
+ this.lensFrame = null;
820
+ }
821
+ this.committed = null;
822
+ this.isAwaitingValue = false;
823
+ this.clearFaces();
824
+ this.settle();
825
+ this.cancelThaw();
826
+ this.restoreTransitions();
827
+ }
828
+
829
+ /**
830
+ * A fine pointer's carry. The segments' own transitions go off while the
831
+ * lens is on screen, so the borrowed surface does not fade out and back in.
832
+ * Under a finger, the touch presentation already turns them off.
833
+ */
834
+ stillTransitions() {
835
+ const track = this.track;
836
+ if (track == null) {
837
+ return;
838
+ }
839
+ this.cancelThaw();
840
+ for (const segment of segmentsOf(track)) {
841
+ if (!this.stilled.has(segment)) {
842
+ this.stilled.set(segment, [segment.style.getPropertyValue('transition-property'), segment.style.getPropertyPriority('transition-property')]);
843
+ }
844
+ segment.style.setProperty('transition-property', 'none', 'important');
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Gives the stilled segments their transitions back two frames after the
850
+ * hand-back, once the surfaces it gave back have been on screen.
851
+ */
852
+ thawTransitions() {
853
+ if (this.stilled.size === 0 || this.thawFrame != null) {
854
+ return;
855
+ }
856
+ const view = this.track?.ownerDocument.defaultView;
857
+ if (view == null) {
858
+ this.restoreTransitions();
859
+ return;
860
+ }
861
+ this.thawFrame = view.requestAnimationFrame(() => {
862
+ this.thawFrame = view.requestAnimationFrame(() => {
863
+ this.thawFrame = null;
864
+ this.restoreTransitions();
865
+ });
866
+ });
867
+ }
868
+ cancelThaw() {
869
+ if (this.thawFrame != null) {
870
+ this.track?.ownerDocument.defaultView?.cancelAnimationFrame(this.thawFrame);
871
+ this.thawFrame = null;
872
+ }
873
+ }
874
+ restoreTransitions() {
875
+ for (const [segment, [value, priority]] of this.stilled) {
876
+ if (value === '') {
877
+ segment.style.removeProperty('transition-property');
878
+ } else {
879
+ segment.style.setProperty('transition-property', value, priority);
880
+ }
881
+ }
882
+ this.stilled.clear();
883
+ }
884
+
885
+ /** The selected segment, as the DOM shows it now. */
886
+ selectedIn(track) {
887
+ return segmentsOf(track).find(segment => segment.getAttribute('aria-checked') === 'true') ?? null;
888
+ }
889
+
890
+ /**
891
+ * Starts the motion the release left pending: on `box` (the destination's
892
+ * box now) or the box measured at landing, showing `paint`'s surface (the
893
+ * selected segment's).
894
+ */
895
+ startPending(paint, box) {
896
+ const pending = this.pending;
897
+ this.pending = null;
898
+ if (pending == null) {
899
+ return;
900
+ }
901
+ const to = box ?? pending.box;
902
+ const surface = this.carried?.hidden ?? paint ?? pending.destination;
903
+ if (pending.isHeld) {
904
+ this.moveLensTo(pending.destination, to, surface, pending.touchRelease);
905
+ } else if (pending.from != null) {
906
+ this.slideTo(pending.destination, to, pending.from, surface, pending.touchRelease);
907
+ }
908
+ }
909
+
910
+ /**
911
+ * Aims the lens at `destination`'s box as it is now: a value adopted
912
+ * after its click (mid-flight) moves the segments with its titles' new
913
+ * weights.
914
+ */
915
+ reaim(track, destination) {
916
+ const carried = this.carried;
917
+ if (carried == null) {
918
+ return;
919
+ }
920
+ const box = boxesIn(track)(destination);
921
+ if (!isLaidOut(box) || isSameBox(box, carried.box)) {
922
+ return;
923
+ }
924
+ this.moveLensTo(destination, box, carried.hidden, this.releasedByFinger);
925
+ }
926
+
927
+ /**
928
+ * Binds the lens for `segment`, with `box` as its layout box and `paint`'s
929
+ * surface, the selected segment's. It keeps what is on screen. With nothing
930
+ * carried, it starts from `from`, a segment's resting box. Otherwise it
931
+ * starts from the presented geometry, re-based onto the new box with its
932
+ * velocities.
933
+ */
934
+ carry(segment, box, from, paint) {
935
+ const track = this.track;
936
+ if (track == null) {
937
+ return undefined;
938
+ }
939
+ if (!this.isPresenting) {
940
+ // Before the surface is borrowed: it must not fade out.
941
+ this.stillTransitions();
942
+ }
943
+ const previous = this.carried;
944
+ let start;
945
+ // Motion already on screen continues from this instant; a start from
946
+ // rest waits for the browser to commit it (the animator's rule).
947
+ let at;
948
+ this.velocity = {};
949
+ if (previous != null) {
950
+ at = this.now();
951
+ const presented = this.presented(previous.box, at);
952
+ start = {
953
+ x: presented.center - center(box),
954
+ scaleX: presented.width / box.width,
955
+ scaleY: presented.height / box.height
956
+ };
957
+ this.velocity = {
958
+ x: this.animator.getVelocity('x', at),
959
+ scaleX: this.animator.getVelocity('scaleX', at) * previous.box.width / box.width,
960
+ scaleY: this.animator.getVelocity('scaleY', at) * previous.box.height / box.height
961
+ };
962
+ } else {
963
+ const origin = from ?? box;
964
+ start = {
965
+ x: center(origin) - center(box),
966
+ scaleX: origin.width / box.width,
967
+ scaleY: origin.height / box.height
968
+ };
969
+ }
970
+ if (previous?.hidden !== paint) {
971
+ if (previous != null) {
972
+ this.abandoned.add(previous.hidden);
973
+ }
974
+ // A segment abandoned mid-flight still has its surface hidden: read
975
+ // its paint with it shown.
976
+ if (this.abandoned.has(paint)) {
977
+ showSurface(paint);
978
+ }
979
+ paintLayer(track, 'lens', paint);
980
+ hideSurface(paint);
981
+ this.abandoned.delete(paint);
982
+ }
983
+ placeLayer(track, 'lens', box);
984
+ this.carried = {
985
+ segment,
986
+ box,
987
+ hidden: paint,
988
+ trackSize: {
989
+ width: track.offsetWidth,
990
+ height: track.offsetHeight
991
+ }
992
+ };
993
+ // Opaque, whatever a Reduce Motion fade left.
994
+ this.animator.set({
995
+ ...start,
996
+ opacity: 1
997
+ });
998
+ return at;
999
+ }
1000
+
1001
+ /** Velocities captured by the last `carry`, in the new box's units. */
1002
+ velocity = {};
1003
+
1004
+ /** A spring transition that keeps the captured velocities of `values`. */
1005
+ springFrom(values, kind) {
1006
+ const spring = kind === 'selection' ? segmentedControl.selectionSpring : springs.fingerTracking;
1007
+ const velocity = {};
1008
+ for (const key of Object.keys(values)) {
1009
+ const v = this.velocity[key];
1010
+ if (v != null && v !== 0) {
1011
+ velocity[key] = v;
1012
+ }
1013
+ }
1014
+ // A captured velocity is spent by the first call that drives its channel.
1015
+ this.velocity = Object.fromEntries(Object.entries(this.velocity).filter(([key]) => !(key in values)));
1016
+ return Object.keys(velocity).length > 0 ? {
1017
+ spring,
1018
+ velocity
1019
+ } : {
1020
+ spring
1021
+ };
1022
+ }
1023
+
1024
+ /**
1025
+ * The lens settles at its resting place on `segment`. Under a finger it
1026
+ * springs on the slide spring. Under a fine pointer it takes the theme's
1027
+ * duration and easing, as the segments' own transition does.
1028
+ */
1029
+ settleLens(segment, options) {
1030
+ const rest = {
1031
+ x: 0,
1032
+ scaleX: 1,
1033
+ scaleY: 1
1034
+ };
1035
+ if (this.isPresenting) {
1036
+ this.springLens(rest, this.springFrom(rest, 'selection'), options);
1037
+ return;
1038
+ }
1039
+ this.velocity = {};
1040
+ const timing = themeTimingOf(segment) ?? INSTANT;
1041
+ const at = options.at;
1042
+ this.trackHandle(this.animator.play({
1043
+ x: timedCurve(this.animator.get('x', at), 0, timing),
1044
+ scaleX: timedCurve(this.animator.get('scaleX', at), 1, timing),
1045
+ scaleY: timedCurve(this.animator.get('scaleY', at), 1, timing)
1046
+ }, options));
1047
+ }
1048
+
1049
+ /** Springs the lens toward `values`. */
1050
+ springLens(values, transition, options) {
1051
+ this.trackHandle(this.animator.animate(values, transition, options));
1052
+ }
1053
+
1054
+ /** The lens's presented center and size, from the animator. */
1055
+ presented(box, at) {
1056
+ return {
1057
+ center: center(box) + this.animator.get('x', at),
1058
+ width: box.width * this.animator.get('scaleX', at),
1059
+ height: box.height * this.animator.get('scaleY', at)
1060
+ };
1061
+ }
1062
+
1063
+ /**
1064
+ * The lens settles on `segment`, at its resting size. Under Reduced Motion
1065
+ * a finger's release (`touchRelease`) cross-fades on any primary pointer. A
1066
+ * mouse's or a pen's lands at once under a fine one.
1067
+ */
1068
+ moveLensTo(segment, box, paint, touchRelease) {
1069
+ this.isHeld = false;
1070
+ if (!isLaidOut(box)) {
1071
+ this.settle();
1072
+ return;
1073
+ }
1074
+ if (this.animator.reducedMotion({
1075
+ touchRelease
1076
+ }) !== 'off') {
1077
+ this.crossFadeTo(segment, box, null, this.carried?.hidden ?? paint, touchRelease);
1078
+ return;
1079
+ }
1080
+ const at = this.carry(segment, box, null, this.carried?.hidden ?? paint);
1081
+ this.settleLens(segment, {
1082
+ at
1083
+ });
1084
+ }
1085
+
1086
+ /**
1087
+ * A tap's slide: from `from` to `segment` on the slide spring. Under Reduced
1088
+ * Motion there is no travel, and a finger's tap (`touchRelease`) fades.
1089
+ */
1090
+ slideTo(segment, box, from, paint, touchRelease) {
1091
+ if (!isLaidOut(box)) {
1092
+ this.settle();
1093
+ return;
1094
+ }
1095
+ if (this.animator.reducedMotion({
1096
+ touchRelease
1097
+ }) !== 'off') {
1098
+ this.crossFadeTo(segment, box, this.carried == null ? from : null, this.carried?.hidden ?? paint, touchRelease);
1099
+ return;
1100
+ }
1101
+ const at = this.carry(segment, box, this.carried == null ? from : null, this.carried?.hidden ?? paint);
1102
+ this.settleLens(segment, {
1103
+ at
1104
+ });
1105
+ }
1106
+
1107
+ /**
1108
+ * Under Reduced Motion, the lens lands on `segment` and fades in there, as
1109
+ * an animator entrance. The surface on screen fades out in place on the
1110
+ * same clock. That surface is the lens when it was carried, else `from`'s
1111
+ * resting surface. The animator decides whether anything fades. On a fine
1112
+ * pointer, both change at once unless a touch released the change
1113
+ * (`touchRelease`).
1114
+ */
1115
+ crossFadeTo(segment, box, from, paint, touchRelease) {
1116
+ const track = this.track;
1117
+ if (track == null) {
1118
+ return;
1119
+ }
1120
+ // What is on screen before the lens is rebound.
1121
+ let leaving = null;
1122
+ let opacity = 1;
1123
+ const previous = this.carried;
1124
+ if (previous != null) {
1125
+ const now = this.now();
1126
+ const presented = this.presented(previous.box, now);
1127
+ leaving = {
1128
+ x: presented.center - presented.width / 2,
1129
+ y: previous.box.y,
1130
+ width: presented.width,
1131
+ height: previous.box.height
1132
+ };
1133
+ opacity = this.animator.get('opacity', now);
1134
+ } else if (from != null) {
1135
+ leaving = from;
1136
+ }
1137
+ this.isHeld = false;
1138
+ this.endLeaving();
1139
+ this.after.stop();
1140
+ clearLayer(track, 'after');
1141
+ this.isLeavingShown = false;
1142
+ const fades = leaving != null && opacity > 0 && this.animator.reducedMotion({
1143
+ touchRelease
1144
+ }) === 'crossfade';
1145
+ if (fades) {
1146
+ this.leave(track, previous != null, paint, leaving, opacity, box);
1147
+ }
1148
+ const at = this.carry(segment, box, from, paint);
1149
+ this.velocity = {};
1150
+ this.trackHandle(this.animator.animate({
1151
+ x: 0,
1152
+ scaleX: 1,
1153
+ scaleY: 1
1154
+ }, {
1155
+ spring: segmentedControl.selectionSpring
1156
+ }, {
1157
+ at,
1158
+ crossfadeAs: 'entrance',
1159
+ touchRelease
1160
+ }));
1161
+ if (fades) {
1162
+ this.fadeLeaving(box, leaving, at, touchRelease);
1163
+ }
1164
+ }
1165
+
1166
+ /**
1167
+ * Paints the ::after with the surface on screen (the lens's paint, or
1168
+ * `paint`'s resting surface) where it is (`from`, at `opacity`).
1169
+ */
1170
+ leave(track, fromLens, paint, from, opacity, box) {
1171
+ if (fromLens) {
1172
+ copyLensPaint(track);
1173
+ } else {
1174
+ paintLayer(track, 'after', paint);
1175
+ }
1176
+ placeLayer(track, 'after', {
1177
+ ...from,
1178
+ y: box.y,
1179
+ height: box.height
1180
+ });
1181
+ this.isLeavingShown = true;
1182
+ this.after.set({
1183
+ x: 0,
1184
+ scaleX: 1,
1185
+ scaleY: 1,
1186
+ opacity
1187
+ });
1188
+ }
1189
+
1190
+ /**
1191
+ * The ::after fades out in place while the lens fades in on `box`. As a
1192
+ * leaving travel, it fades on the animator's Reduce Motion clock. It lands
1193
+ * only once it is invisible.
1194
+ */
1195
+ fadeLeaving(box, from, at, touchRelease) {
1196
+ const id = ++this.leaving;
1197
+ const handle = this.after.animate({
1198
+ x: center(box) - center(from),
1199
+ opacity: 0
1200
+ }, {
1201
+ spring: segmentedControl.selectionSpring
1202
+ }, {
1203
+ at,
1204
+ touchRelease
1205
+ });
1206
+ void handle.finished.then(() => {
1207
+ if (this.leaving === id) {
1208
+ this.endLeaving();
1209
+ }
1210
+ });
1211
+ }
1212
+ endLeaving() {
1213
+ if (!this.isLeavingShown) {
1214
+ return;
1215
+ }
1216
+ this.isLeavingShown = false;
1217
+ this.leaving++;
1218
+ this.after.stop();
1219
+ if (this.track != null) {
1220
+ clearLayer(this.track, 'after');
1221
+ }
1222
+ }
1223
+
1224
+ /**
1225
+ * Waits for `handle`; the surface goes back when nothing is left. A
1226
+ * touch's value still waiting to come back when the motion settles goes
1227
+ * back instead: the settled slide bounds the wait.
1228
+ */
1229
+ trackHandle(handle) {
1230
+ this.running.add(handle);
1231
+ void handle.finished.then(() => {
1232
+ this.running.delete(handle);
1233
+ if (this.running.size > 0 || this.isHeld || this.touch != null) {
1234
+ return;
1235
+ }
1236
+ const committed = this.committed;
1237
+ if (this.isAwaitingValue && committed != null && this.track != null) {
1238
+ this.decline(this.track, committed);
1239
+ return;
1240
+ }
1241
+ if (this.committed == null) {
1242
+ this.settle();
1243
+ }
1244
+ });
1245
+ }
1246
+
1247
+ /**
1248
+ * Gives `segment`'s title the selected face and the selected segment's
1249
+ * title an unselected one (none for null or the selected segment), at once.
1250
+ */
1251
+ showFaceAt(segment) {
1252
+ const touch = this.touch;
1253
+ const selected = touch?.origin ?? null;
1254
+ if (segment === this.facedSegment) {
1255
+ return;
1256
+ }
1257
+ this.clearFaces();
1258
+ if (touch?.faces == null || selected == null || segment == null || segment === selected) {
1259
+ return;
1260
+ }
1261
+ const title = titleOf(segment);
1262
+ const selectedTitle = titleOf(selected);
1263
+ if (title == null || selectedTitle == null) {
1264
+ return;
1265
+ }
1266
+ this.wearFace(title, touch.faces.selected);
1267
+ this.wearFace(selectedTitle, touch.faces.unselected);
1268
+ this.facedSegment = segment;
1269
+ }
1270
+
1271
+ /**
1272
+ * Mid-drag, the face is the color alone. A weight swap changes the titles'
1273
+ * widths, which moves the segments under the finger and shakes the
1274
+ * boundary. The commit's own styles give the new weight at the lift.
1275
+ */
1276
+ wearFace(title, face) {
1277
+ title.style.color = face.color;
1278
+ this.faceOverrides.add(title);
1279
+ }
1280
+
1281
+ /** Every title back to its own face. */
1282
+ clearFaces() {
1283
+ for (const title of this.faceOverrides) {
1284
+ title.style.removeProperty('color');
1285
+ }
1286
+ this.faceOverrides.clear();
1287
+ this.facedSegment = null;
1288
+ }
1289
+ }
1290
+
1291
+ /**
1292
+ * Wires `SegmentedSelectionMotion` to a SegmentedControl. While enabled,
1293
+ * every change of `value` slides the selection in the commit that shows the
1294
+ * new value, so no frame shows it unanimated. A press drives the lens while
1295
+ * `isTracking`.
1296
+ */
1297
+ export function useSegmentedSelectionMotion(trackRef, value, isPresenting, isTracking) {
1298
+ const [motion] = useState(() => new SegmentedSelectionMotion());
1299
+ const previousValueRef = useRef(value);
1300
+ // The lens is the radiogroup's own pseudo-elements. The animators attach to
1301
+ // it while the touch presentation applies or while a press can drag it,
1302
+ // which any pointer can.
1303
+ const isEnabled = isPresenting || isTracking;
1304
+
1305
+ // Syncs the lens animators with the pointer presentation: a flip hands any
1306
+ // motion in flight back.
1307
+ useIsomorphicLayoutEffect(() => {
1308
+ motion.present(isPresenting);
1309
+ }, [isPresenting, motion]);
1310
+ useIsomorphicLayoutEffect(() => {
1311
+ const from = previousValueRef.current;
1312
+ previousValueRef.current = value;
1313
+ const track = trackRef.current;
1314
+ if (!isEnabled || track == null || from === value) {
1315
+ return;
1316
+ }
1317
+ motion.select(track, from, value);
1318
+ }, [isEnabled, motion, trackRef, value]);
1319
+
1320
+ // Stops the animators that drive the radiogroup's pseudo-elements.
1321
+ useEffect(() => () => motion.stop(), [motion]);
1322
+
1323
+ // Every pointer's press is tracked. The lift commits the segment nearest
1324
+ // the pointer, through that segment's click.
1325
+ const clickWait = useSegmentedTouchTracking(trackRef, motion, isTracking);
1326
+
1327
+ // The animators attach to the radiogroup while enabled. The lens aims at
1328
+ // boxes measured when its motion began, so a resize hands the surface back.
1329
+ const attachTrack = useCallback(track => {
1330
+ if (track == null || !isEnabled) {
1331
+ return undefined;
1332
+ }
1333
+ motion.attachTrack(track);
1334
+ const unobserve = observeResize(track, () => motion.resized(track));
1335
+ return () => {
1336
+ unobserve();
1337
+ motion.settle();
1338
+ motion.attachTrack(null);
1339
+ };
1340
+ }, [isEnabled, motion]);
1341
+ const [selection] = useState(() => ({
1342
+ isPressing: () => motion.isPressed || clickWait.isAwaiting(),
1343
+ keyPressed: clickWait.end
1344
+ }));
1345
+ return {
1346
+ selection,
1347
+ attachTrack
1348
+ };
1349
+ }