@basic-ui/core 0.0.53 → 0.0.54

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 (400) hide show
  1. package/README.md +3 -3
  2. package/build/cjs/index.js +88 -88
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/Accordion/Accordion.d.ts +9 -9
  5. package/build/esm/Accordion/Accordion.js.map +1 -1
  6. package/build/esm/Accordion/AccordionBody.d.ts +6 -6
  7. package/build/esm/Accordion/AccordionBody.js.map +1 -1
  8. package/build/esm/Accordion/AccordionHeader.d.ts +7 -7
  9. package/build/esm/Accordion/AccordionHeader.js.map +1 -1
  10. package/build/esm/Accordion/AccordionItem.d.ts +9 -9
  11. package/build/esm/Accordion/AccordionItem.js.map +1 -1
  12. package/build/esm/Accordion/context.d.ts +19 -19
  13. package/build/esm/Accordion/context.js.map +1 -1
  14. package/build/esm/Accordion/index.d.ts +4 -4
  15. package/build/esm/Accordion/index.js.map +1 -1
  16. package/build/esm/Accordion/scopeQuery.d.ts +2 -2
  17. package/build/esm/Accordion/scopeQuery.js.map +1 -1
  18. package/build/esm/CheckBox/CheckBox.d.ts +7 -7
  19. package/build/esm/CheckBox/CheckBox.js.map +1 -1
  20. package/build/esm/CheckBox/index.d.ts +1 -1
  21. package/build/esm/CheckBox/index.js.map +1 -1
  22. package/build/esm/ComboBox/Combobox.d.ts +18 -18
  23. package/build/esm/ComboBox/Combobox.js.map +1 -1
  24. package/build/esm/ComboBox/ComboboxButton.d.ts +9 -9
  25. package/build/esm/ComboBox/ComboboxButton.js.map +1 -1
  26. package/build/esm/ComboBox/ComboboxInput.d.ts +17 -17
  27. package/build/esm/ComboBox/ComboboxInput.js.map +1 -1
  28. package/build/esm/ComboBox/ComboboxLabel.d.ts +7 -7
  29. package/build/esm/ComboBox/ComboboxLabel.js.map +1 -1
  30. package/build/esm/ComboBox/ComboboxList.d.ts +8 -8
  31. package/build/esm/ComboBox/ComboboxList.js.map +1 -1
  32. package/build/esm/ComboBox/ComboboxOption.d.ts +11 -11
  33. package/build/esm/ComboBox/ComboboxOption.js.map +1 -1
  34. package/build/esm/ComboBox/ComboboxPopover.d.ts +9 -9
  35. package/build/esm/ComboBox/ComboboxPopover.js.map +1 -1
  36. package/build/esm/ComboBox/cities.d.ts +5 -5
  37. package/build/esm/ComboBox/cities.js.map +1 -1
  38. package/build/esm/ComboBox/context.d.ts +30 -30
  39. package/build/esm/ComboBox/context.js.map +1 -1
  40. package/build/esm/ComboBox/hooks.d.ts +37 -37
  41. package/build/esm/ComboBox/hooks.js.map +1 -1
  42. package/build/esm/ComboBox/index.d.ts +8 -8
  43. package/build/esm/ComboBox/index.js.map +1 -1
  44. package/build/esm/ComboBox/makeHash.d.ts +1 -1
  45. package/build/esm/ComboBox/makeHash.js.map +1 -1
  46. package/build/esm/ComboBox/scopeQuery.d.ts +1 -1
  47. package/build/esm/ComboBox/scopeQuery.js.map +1 -1
  48. package/build/esm/FocusLock/FocusLock.d.ts +9 -9
  49. package/build/esm/FocusLock/FocusLock.js.map +1 -1
  50. package/build/esm/FocusLock/index.d.ts +1 -1
  51. package/build/esm/FocusLock/index.js.map +1 -1
  52. package/build/esm/FocusLock/tabUtils.d.ts +3 -3
  53. package/build/esm/FocusLock/tabUtils.js.map +1 -1
  54. package/build/esm/FocusLock/useFocusLock.d.ts +7 -7
  55. package/build/esm/FocusLock/useFocusLock.js.map +1 -1
  56. package/build/esm/List/List.d.ts +7 -7
  57. package/build/esm/List/List.js.map +1 -1
  58. package/build/esm/List/ListItem.d.ts +7 -7
  59. package/build/esm/List/ListItem.js.map +1 -1
  60. package/build/esm/List/context.d.ts +4 -4
  61. package/build/esm/List/context.js.map +1 -1
  62. package/build/esm/List/index.d.ts +2 -2
  63. package/build/esm/List/index.js.map +1 -1
  64. package/build/esm/Menu/ContextMenuTrigger.d.ts +11 -11
  65. package/build/esm/Menu/ContextMenuTrigger.js.map +1 -1
  66. package/build/esm/Menu/Menu.d.ts +10 -10
  67. package/build/esm/Menu/Menu.js.map +1 -1
  68. package/build/esm/Menu/MenuButton.d.ts +11 -11
  69. package/build/esm/Menu/MenuButton.js.map +1 -1
  70. package/build/esm/Menu/MenuItem.d.ts +8 -8
  71. package/build/esm/Menu/MenuItem.js.map +1 -1
  72. package/build/esm/Menu/MenuList.d.ts +7 -7
  73. package/build/esm/Menu/MenuList.js.map +1 -1
  74. package/build/esm/Menu/MenuPopover.d.ts +8 -8
  75. package/build/esm/Menu/MenuPopover.js.map +1 -1
  76. package/build/esm/Menu/context.d.ts +25 -25
  77. package/build/esm/Menu/context.js.map +1 -1
  78. package/build/esm/Menu/fixtures/countryList.d.ts +1 -1
  79. package/build/esm/Menu/fixtures/countryList.js.map +1 -1
  80. package/build/esm/Menu/index.d.ts +6 -6
  81. package/build/esm/Menu/index.js.map +1 -1
  82. package/build/esm/Menu/scope.d.ts +1 -1
  83. package/build/esm/Menu/scope.js.map +1 -1
  84. package/build/esm/Modal/Modal.d.ts +9 -9
  85. package/build/esm/Modal/Modal.js.map +1 -1
  86. package/build/esm/Modal/ModalBackdrop.d.ts +10 -10
  87. package/build/esm/Modal/ModalBackdrop.js.map +1 -1
  88. package/build/esm/Modal/index.d.ts +2 -2
  89. package/build/esm/Modal/index.js.map +1 -1
  90. package/build/esm/Popper/Popper.d.ts +35 -35
  91. package/build/esm/Popper/Popper.js +1 -2
  92. package/build/esm/Popper/Popper.js.map +1 -1
  93. package/build/esm/Popper/PopperArrow.d.ts +6 -6
  94. package/build/esm/Popper/PopperArrow.js.map +1 -1
  95. package/build/esm/Popper/context.d.ts +6 -6
  96. package/build/esm/Popper/context.js.map +1 -1
  97. package/build/esm/Popper/index.d.ts +3 -3
  98. package/build/esm/Popper/index.js.map +1 -1
  99. package/build/esm/Portal/Portal.d.ts +7 -7
  100. package/build/esm/Portal/PortalSelectorProvider.d.ts +8 -8
  101. package/build/esm/Portal/index.d.ts +2 -2
  102. package/build/esm/RadioButton/RadioButton.d.ts +10 -10
  103. package/build/esm/RadioButton/RadioButton.js.map +1 -1
  104. package/build/esm/RadioButton/RadioGroup.d.ts +12 -12
  105. package/build/esm/RadioButton/RadioGroup.js.map +1 -1
  106. package/build/esm/RadioButton/context.d.ts +9 -9
  107. package/build/esm/RadioButton/context.js.map +1 -1
  108. package/build/esm/RadioButton/index.d.ts +2 -2
  109. package/build/esm/RadioButton/index.js.map +1 -1
  110. package/build/esm/SkipNav/SkipNav.d.ts +7 -7
  111. package/build/esm/SkipNav/SkipNav.js.map +1 -1
  112. package/build/esm/SkipNav/index.d.ts +1 -1
  113. package/build/esm/SkipNav/index.js.map +1 -1
  114. package/build/esm/Slider/Slider.d.ts +197 -197
  115. package/build/esm/Slider/Slider.js +82 -82
  116. package/build/esm/Slider/Slider.js.map +1 -1
  117. package/build/esm/Slider/index.d.ts +1 -1
  118. package/build/esm/Slider/index.js.map +1 -1
  119. package/build/esm/Spinner/Spinner.d.ts +12 -12
  120. package/build/esm/Spinner/Spinner.js.map +1 -1
  121. package/build/esm/Spinner/SpinnerButton.d.ts +8 -8
  122. package/build/esm/Spinner/SpinnerButton.js.map +1 -1
  123. package/build/esm/Spinner/context.d.ts +12 -12
  124. package/build/esm/Spinner/context.js.map +1 -1
  125. package/build/esm/Spinner/index.d.ts +2 -2
  126. package/build/esm/Spinner/index.js.map +1 -1
  127. package/build/esm/Tabs/Tab.d.ts +7 -7
  128. package/build/esm/Tabs/Tab.js.map +1 -1
  129. package/build/esm/Tabs/TabList.d.ts +9 -9
  130. package/build/esm/Tabs/TabList.js.map +1 -1
  131. package/build/esm/Tabs/TabPanel.d.ts +8 -8
  132. package/build/esm/Tabs/TabPanel.js.map +1 -1
  133. package/build/esm/Tabs/TabPanels.d.ts +8 -8
  134. package/build/esm/Tabs/TabPanels.js.map +1 -1
  135. package/build/esm/Tabs/Tabs.d.ts +10 -10
  136. package/build/esm/Tabs/Tabs.js.map +1 -1
  137. package/build/esm/Tabs/context.d.ts +17 -17
  138. package/build/esm/Tabs/context.js.map +1 -1
  139. package/build/esm/Tabs/index.d.ts +5 -5
  140. package/build/esm/Tabs/index.js.map +1 -1
  141. package/build/esm/Tabs/scopeQuery.d.ts +1 -1
  142. package/build/esm/Tabs/scopeQuery.js.map +1 -1
  143. package/build/esm/Tooltip/Tooltip.d.ts +10 -10
  144. package/build/esm/Tooltip/Tooltip.js.map +1 -1
  145. package/build/esm/Tooltip/index.d.ts +1 -1
  146. package/build/esm/Tooltip/index.js.map +1 -1
  147. package/build/esm/Tooltip/stateMachine.d.ts +28 -28
  148. package/build/esm/Tooltip/stateMachine.js.map +1 -1
  149. package/build/esm/Tooltip/useTooltip.d.ts +10 -10
  150. package/build/esm/Tooltip/useTooltip.js.map +1 -1
  151. package/build/esm/hooks/index.d.ts +13 -13
  152. package/build/esm/hooks/index.js.map +1 -1
  153. package/build/esm/hooks/useAutoFocus.d.ts +2 -2
  154. package/build/esm/hooks/useAutoFocus.js.map +1 -1
  155. package/build/esm/hooks/useChildrenCounter.d.ts +7 -7
  156. package/build/esm/hooks/useChildrenCounter.js.map +1 -1
  157. package/build/esm/hooks/useControlledState.d.ts +3 -3
  158. package/build/esm/hooks/useFocusReturn.d.ts +2 -2
  159. package/build/esm/hooks/useFocusReturn.js.map +1 -1
  160. package/build/esm/hooks/useFocusState.d.ts +11 -11
  161. package/build/esm/hooks/useFocusState.js.map +1 -1
  162. package/build/esm/hooks/useGestureHandlers.d.ts +52 -52
  163. package/build/esm/hooks/useGestureHandlers.js.map +1 -1
  164. package/build/esm/hooks/useMeasure.d.ts +7 -7
  165. package/build/esm/hooks/useMeasure.js.map +1 -1
  166. package/build/esm/hooks/useOnClickOutside.d.ts +2 -2
  167. package/build/esm/hooks/useOnClickOutside.js.map +1 -1
  168. package/build/esm/hooks/useOnKeyDown.d.ts +1 -1
  169. package/build/esm/hooks/useOnKeyDown.js.map +1 -1
  170. package/build/esm/hooks/useReducerMachine.d.ts +24 -24
  171. package/build/esm/hooks/useReducerMachine.js.map +1 -1
  172. package/build/esm/hooks/useRemoveBodyScroll.d.ts +2 -2
  173. package/build/esm/hooks/useRemoveBodyScroll.js.map +1 -1
  174. package/build/esm/hooks/useScope.d.ts +11 -11
  175. package/build/esm/hooks/useScope.js.map +1 -1
  176. package/build/esm/hooks/useThrottle.d.ts +1 -1
  177. package/build/esm/hooks/useThrottle.js.map +1 -1
  178. package/build/esm/index.d.ts +15 -15
  179. package/build/esm/index.js.map +1 -1
  180. package/build/esm/utils/assign-ref.d.ts +3 -3
  181. package/build/esm/utils/assign-ref.js.map +1 -1
  182. package/build/esm/utils/can-use-dom.d.ts +1 -1
  183. package/build/esm/utils/can-use-dom.js.map +1 -1
  184. package/build/esm/utils/clamp.d.ts +1 -1
  185. package/build/esm/utils/clamp.js.map +1 -1
  186. package/build/esm/utils/context.d.ts +7 -7
  187. package/build/esm/utils/context.js.map +1 -1
  188. package/build/esm/utils/create-subscription.d.ts +4 -4
  189. package/build/esm/utils/create-subscription.js.map +1 -1
  190. package/build/esm/utils/get-circular-index.d.ts +1 -1
  191. package/build/esm/utils/get-circular-index.js.map +1 -1
  192. package/build/esm/utils/index.d.ts +10 -10
  193. package/build/esm/utils/index.js.map +1 -1
  194. package/build/esm/utils/is-right-click.d.ts +6 -6
  195. package/build/esm/utils/is-right-click.js +4 -4
  196. package/build/esm/utils/is-right-click.js.map +1 -1
  197. package/build/esm/utils/owner-document.d.ts +7 -7
  198. package/build/esm/utils/owner-document.js +5 -5
  199. package/build/esm/utils/owner-document.js.map +1 -1
  200. package/build/esm/utils/polymorphic.d.ts +39 -39
  201. package/build/esm/utils/polymorphic.js.map +1 -1
  202. package/build/esm/utils/rubber-band-clamp.d.ts +2 -2
  203. package/build/esm/utils/rubber-band-clamp.js.map +1 -1
  204. package/build/esm/utils/use-stable-callback.d.ts +16 -16
  205. package/build/esm/utils/use-stable-callback.js +16 -16
  206. package/build/esm/utils/use-stable-callback.js.map +1 -1
  207. package/build/esm/utils/wrap-event.d.ts +3 -3
  208. package/build/esm/utils/wrap-event.js.map +1 -1
  209. package/build/tsconfig-build.tsbuildinfo +1 -1
  210. package/package.json +2 -2
  211. package/src/Accordion/Accordion.story.tsx +74 -74
  212. package/src/Accordion/Accordion.tsx +59 -59
  213. package/src/Accordion/AccordionBody.tsx +52 -52
  214. package/src/Accordion/AccordionHeader.tsx +167 -167
  215. package/src/Accordion/AccordionItem.tsx +50 -50
  216. package/src/Accordion/context.ts +37 -37
  217. package/src/Accordion/index.ts +4 -4
  218. package/src/Accordion/scopeQuery.ts +7 -7
  219. package/src/Accordion/styles.css +21 -21
  220. package/src/CheckBox/CheckBox.tsx +41 -41
  221. package/src/CheckBox/index.ts +1 -1
  222. package/src/ComboBox/ComboBox.story.tsx +120 -120
  223. package/src/ComboBox/Combobox.tsx +148 -148
  224. package/src/ComboBox/ComboboxButton.tsx +61 -61
  225. package/src/ComboBox/ComboboxInput.tsx +187 -187
  226. package/src/ComboBox/ComboboxLabel.tsx +33 -33
  227. package/src/ComboBox/ComboboxList.tsx +47 -47
  228. package/src/ComboBox/ComboboxOption.tsx +111 -111
  229. package/src/ComboBox/ComboboxPopover.tsx +64 -64
  230. package/src/ComboBox/cities.ts +23194 -23194
  231. package/src/ComboBox/context.ts +35 -35
  232. package/src/ComboBox/hooks.tsx +451 -451
  233. package/src/ComboBox/index.ts +8 -8
  234. package/src/ComboBox/makeHash.ts +19 -19
  235. package/src/ComboBox/scopeQuery.ts +6 -6
  236. package/src/ComboBox/styles.css +32 -32
  237. package/src/FocusLock/FocusLock.tsx +66 -66
  238. package/src/FocusLock/index.ts +1 -1
  239. package/src/FocusLock/tabUtils.ts +40 -40
  240. package/src/FocusLock/useFocusLock.ts +56 -56
  241. package/src/List/List.story.tsx +18 -18
  242. package/src/List/List.tsx +17 -17
  243. package/src/List/ListItem.tsx +23 -23
  244. package/src/List/context.ts +19 -19
  245. package/src/List/index.ts +2 -2
  246. package/src/Menu/ContextMenu.story.tsx +73 -73
  247. package/src/Menu/ContextMenuTrigger.tsx +76 -76
  248. package/src/Menu/Menu.story.tsx +160 -160
  249. package/src/Menu/Menu.tsx +83 -83
  250. package/src/Menu/MenuButton.tsx +83 -83
  251. package/src/Menu/MenuComplex.story.tsx +58 -58
  252. package/src/Menu/MenuItem.tsx +88 -88
  253. package/src/Menu/MenuList.tsx +254 -254
  254. package/src/Menu/MenuPopover.tsx +35 -35
  255. package/src/Menu/context.ts +44 -44
  256. package/src/Menu/fixtures/countryList.ts +198 -198
  257. package/src/Menu/index.ts +6 -6
  258. package/src/Menu/scope.ts +7 -7
  259. package/src/Menu/styles.css +42 -42
  260. package/src/Modal/Modal.story.tsx +258 -258
  261. package/src/Modal/Modal.tsx +48 -48
  262. package/src/Modal/ModalBackdrop.tsx +78 -78
  263. package/src/Modal/NavDrawer.story.tsx +158 -158
  264. package/src/Modal/index.ts +2 -2
  265. package/src/Modal/styles.css +46 -46
  266. package/src/Popper/Popper.story.tsx +263 -263
  267. package/src/Popper/Popper.tsx +1 -1
  268. package/src/Popper/PopperArrow.tsx +35 -35
  269. package/src/Popper/context.ts +10 -10
  270. package/src/Popper/index.ts +3 -3
  271. package/src/Popper/styles.css +60 -60
  272. package/src/RadioButton/RadioButton.story.tsx +77 -77
  273. package/src/RadioButton/RadioButton.tsx +55 -55
  274. package/src/RadioButton/RadioGroup.tsx +60 -60
  275. package/src/RadioButton/context.ts +17 -17
  276. package/src/RadioButton/index.ts +2 -2
  277. package/src/SkipNav/SkipNav.tsx +16 -16
  278. package/src/SkipNav/index.tsx +1 -1
  279. package/src/Slider/Slider.story.tsx +45 -45
  280. package/src/Slider/Slider.tsx +1120 -1120
  281. package/src/Slider/index.ts +1 -1
  282. package/src/Slider/styles.css +131 -131
  283. package/src/Spinner/Spinner.story.tsx +31 -31
  284. package/src/Spinner/Spinner.tsx +117 -117
  285. package/src/Spinner/SpinnerButton.tsx +54 -54
  286. package/src/Spinner/context.ts +20 -20
  287. package/src/Spinner/index.ts +2 -2
  288. package/src/Spinner/styles.css +23 -23
  289. package/src/Tabs/Tab.story.tsx +80 -80
  290. package/src/Tabs/Tab.tsx +136 -136
  291. package/src/Tabs/TabList.tsx +71 -71
  292. package/src/Tabs/TabPanel.tsx +53 -53
  293. package/src/Tabs/TabPanels.tsx +30 -30
  294. package/src/Tabs/Tabs.tsx +46 -46
  295. package/src/Tabs/context.ts +30 -30
  296. package/src/Tabs/index.tsx +5 -5
  297. package/src/Tabs/scopeQuery.ts +6 -6
  298. package/src/Tooltip/Tooltip.story.tsx +61 -61
  299. package/src/Tooltip/Tooltip.tsx +50 -50
  300. package/src/Tooltip/index.ts +1 -1
  301. package/src/Tooltip/stateMachine.ts +192 -192
  302. package/src/Tooltip/styles.css +17 -17
  303. package/src/Tooltip/useTooltip.ts +136 -136
  304. package/src/hooks/index.ts +13 -13
  305. package/src/hooks/useAutoFocus.ts +22 -22
  306. package/src/hooks/useChildrenCounter.ts +51 -51
  307. package/src/hooks/useFocusReturn.ts +43 -43
  308. package/src/hooks/useFocusState.ts +30 -30
  309. package/src/hooks/useGestureHandlers.ts +286 -286
  310. package/src/hooks/useMeasure.ts +33 -33
  311. package/src/hooks/useOnClickOutside.ts +32 -32
  312. package/src/hooks/useOnKeyDown.ts +19 -19
  313. package/src/hooks/useReducerMachine.ts +60 -60
  314. package/src/hooks/useRemoveBodyScroll.ts +39 -39
  315. package/src/hooks/useScope.ts +52 -52
  316. package/src/hooks/useThrottle.ts +19 -19
  317. package/src/index.ts +20 -20
  318. package/src/utils/assign-ref.ts +27 -27
  319. package/src/utils/can-use-dom.ts +7 -7
  320. package/src/utils/clamp.ts +3 -3
  321. package/src/utils/context.tsx +48 -48
  322. package/src/utils/create-subscription.ts +16 -16
  323. package/src/utils/get-circular-index.ts +7 -7
  324. package/src/utils/index.ts +10 -10
  325. package/src/utils/is-right-click.ts +14 -14
  326. package/src/utils/owner-document.ts +13 -13
  327. package/src/utils/polymorphic.ts +78 -78
  328. package/src/utils/rubber-band-clamp.ts +25 -25
  329. package/src/utils/use-stable-callback.ts +58 -58
  330. package/src/utils/wrap-event.ts +22 -22
  331. package/build/esm/Carousel/Carousel.d.ts +0 -9
  332. package/build/esm/Carousel/Carousel.js +0 -38
  333. package/build/esm/Carousel/Carousel.js.map +0 -1
  334. package/build/esm/Carousel/Fader.d.ts +0 -14
  335. package/build/esm/Carousel/Fader.js +0 -76
  336. package/build/esm/Carousel/Fader.js.map +0 -1
  337. package/build/esm/Carousel/FaderItem.d.ts +0 -5
  338. package/build/esm/Carousel/FaderItem.js +0 -16
  339. package/build/esm/Carousel/FaderItem.js.map +0 -1
  340. package/build/esm/Carousel/Preloader.d.ts +0 -7
  341. package/build/esm/Carousel/Preloader.js +0 -70
  342. package/build/esm/Carousel/Preloader.js.map +0 -1
  343. package/build/esm/Carousel/Slider.d.ts +0 -14
  344. package/build/esm/Carousel/Slider.js +0 -212
  345. package/build/esm/Carousel/Slider.js.map +0 -1
  346. package/build/esm/Carousel/SliderItem.d.ts +0 -12
  347. package/build/esm/Carousel/SliderItem.js +0 -41
  348. package/build/esm/Carousel/SliderItem.js.map +0 -1
  349. package/build/esm/Carousel/context.d.ts +0 -10
  350. package/build/esm/Carousel/context.js +0 -8
  351. package/build/esm/Carousel/context.js.map +0 -1
  352. package/build/esm/Carousel/getSliderParams.d.ts +0 -9
  353. package/build/esm/Carousel/getSliderParams.js +0 -85
  354. package/build/esm/Carousel/getSliderParams.js.map +0 -1
  355. package/build/esm/Carousel/index.d.ts +0 -7
  356. package/build/esm/Carousel/index.js +0 -8
  357. package/build/esm/Carousel/index.js.map +0 -1
  358. package/build/esm/Carousel/useCarouselGestures.d.ts +0 -30
  359. package/build/esm/Carousel/useCarouselGestures.js +0 -33
  360. package/build/esm/Carousel/useCarouselGestures.js.map +0 -1
  361. package/build/esm/DatePicker/DatePicker.d.ts +0 -24
  362. package/build/esm/DatePicker/DatePicker.js +0 -101
  363. package/build/esm/DatePicker/DatePicker.js.map +0 -1
  364. package/build/esm/DatePicker/DatePickerSelect.d.ts +0 -8
  365. package/build/esm/DatePicker/DatePickerSelect.js +0 -201
  366. package/build/esm/DatePicker/DatePickerSelect.js.map +0 -1
  367. package/build/esm/DatePicker/RangeDatePicker.d.ts +0 -28
  368. package/build/esm/DatePicker/RangeDatePicker.js +0 -94
  369. package/build/esm/DatePicker/RangeDatePicker.js.map +0 -1
  370. package/build/esm/DatePicker/adjustDates.d.ts +0 -4
  371. package/build/esm/DatePicker/adjustDates.js +0 -18
  372. package/build/esm/DatePicker/adjustDates.js.map +0 -1
  373. package/build/esm/DatePicker/contexts.d.ts +0 -31
  374. package/build/esm/DatePicker/contexts.js +0 -15
  375. package/build/esm/DatePicker/contexts.js.map +0 -1
  376. package/build/esm/DatePicker/dateTypes.d.ts +0 -2
  377. package/build/esm/DatePicker/dateTypes.js +0 -2
  378. package/build/esm/DatePicker/dateTypes.js.map +0 -1
  379. package/build/esm/DatePicker/hooks.d.ts +0 -36
  380. package/build/esm/DatePicker/hooks.js +0 -98
  381. package/build/esm/DatePicker/hooks.js.map +0 -1
  382. package/build/esm/DatePicker/index.d.ts +0 -5
  383. package/build/esm/DatePicker/index.js +0 -6
  384. package/build/esm/DatePicker/index.js.map +0 -1
  385. package/build/esm/hooks/useId.d.ts +0 -1
  386. package/build/esm/hooks/useId.js +0 -25
  387. package/build/esm/hooks/useId.js.map +0 -1
  388. package/build/esm/utils/assignRef.d.ts +0 -3
  389. package/build/esm/utils/assignRef.js +0 -25
  390. package/build/esm/utils/assignRef.js.map +0 -1
  391. package/build/esm/utils/getCircularIndex.d.ts +0 -1
  392. package/build/esm/utils/getCircularIndex.js +0 -8
  393. package/build/esm/utils/getCircularIndex.js.map +0 -1
  394. package/build/esm/utils/rubberBandClamp.d.ts +0 -2
  395. package/build/esm/utils/rubberBandClamp.js +0 -20
  396. package/build/esm/utils/rubberBandClamp.js.map +0 -1
  397. package/build/esm/utils/wrapEvent.d.ts +0 -3
  398. package/build/esm/utils/wrapEvent.js +0 -16
  399. package/build/esm/utils/wrapEvent.js.map +0 -1
  400. package/build/tsconfig.tsbuildinfo +0 -7270
@@ -1,1120 +1,1120 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment */
2
-
3
- /**
4
- * Welcome to @reach/slider!
5
- *
6
- * A UI input component where the user selects a value from within a given
7
- * range. A Slider has a handle that can be moved along a track to change its
8
- * value. When the user's mouse or focus is on the Slider's handle, the value
9
- * can be incremented with keyboard controls.
10
- *
11
- * Random thoughts/notes:
12
- * - Currently testing this against the behavior of the native input range
13
- * element to get our slider on par. We'll explore animated and multi-handle
14
- * sliders next.
15
- * - We may want to research some use cases for reversed sliders in RTL
16
- * languages if that's a thing
17
- *
18
- * @see Docs https://reach.tech/slider
19
- * @see Source https://github.com/reach/reach-ui/tree/main/packages/slider
20
- * @see WAI-ARIA https://www.w3.org/TR/wai-aria-practices-1.2/#slider
21
- * @see Example https://github.com/Stanko/aria-progress-range-slider
22
- * @see Example http://www.oaa-accessibility.org/examplep/slider1/
23
- */
24
-
25
- import {
26
- forwardRef,
27
- memo,
28
- useRef,
29
- useState,
30
- useCallback,
31
- useEffect,
32
- useLayoutEffect,
33
- useId,
34
- } from 'react';
35
- import type { ReactNode, KeyboardEvent } from 'react';
36
-
37
- import { createContext } from '../utils/context';
38
- import type * as Polymorphic from '../utils/polymorphic';
39
- import { isRightClick } from '../utils/is-right-click';
40
- import { getOwnerDocument } from '../utils/owner-document';
41
- import { useStableLayoutCallback } from '../utils/use-stable-callback';
42
- import { assignMultipleRefs, wrapEvent } from '../utils';
43
- import { useControlledState } from '../hooks';
44
-
45
- const noop = () => {
46
- /* noop */
47
- };
48
-
49
- type SliderOrientation = 'horizontal' | 'vertical';
50
- type SliderHandleAlignment = 'center' | 'contain';
51
-
52
- const [SliderProvider, useSliderContext] =
53
- createContext<ISliderContext>('Slider');
54
-
55
- ////////////////////////////////////////////////////////////////////////////////
56
-
57
- /**
58
- * Slider
59
- *
60
- * @see Docs https://reach.tech/slider#slider
61
- */
62
- const Slider = forwardRef(function Slider(
63
- { children, ...props },
64
- forwardedRef
65
- ) {
66
- return (
67
- <SliderInput {...props} ref={forwardedRef} data-reach-slider="">
68
- <SliderTrack>
69
- <SliderRange />
70
- <SliderHandle />
71
- {children}
72
- </SliderTrack>
73
- </SliderInput>
74
- );
75
- }) as Polymorphic.ForwardRefComponent<'div', SliderProps>;
76
-
77
- /**
78
- * @see Docs https://reach.tech/slider#slider-props
79
- */
80
- interface SliderProps {
81
- /**
82
- * `Slider` can accept `SliderMarker` children to enhance display of specific
83
- * values along the track.
84
- *
85
- * @see Docs https://reach.tech/slider#slider-children
86
- */
87
- children?: ReactNode;
88
- /**
89
- * The defaultValue is used to set an initial value for an uncontrolled
90
- * Slider.
91
- *
92
- * @see Docs https://reach.tech/slider#slider-defaultvalue
93
- */
94
- defaultValue?: number;
95
- /**
96
- * @see Docs https://reach.tech/slider#slider-disabled
97
- */
98
- disabled?: boolean;
99
- /**
100
- * Whether or not the slider should be disabled from user interaction.
101
- *
102
- * @see Docs https://reach.tech/slider#slider-value
103
- */
104
- value?: number;
105
- /**
106
- * A function used to set a human-readable name for the slider.
107
- *
108
- * @see Docs https://reach.tech/slider#slider-getarialabel
109
- */
110
- getAriaLabel?(value: number): string;
111
- /**
112
- * A function used to set a human-readable value text based on the slider's
113
- * current value.
114
- *
115
- * @see Docs https://reach.tech/slider#slider-getariavaluetext
116
- */
117
- getAriaValueText?(value: number): string;
118
- /**
119
- * When set to `center`, the slider's handle will be positioned directly
120
- * centered over the slider's curremt value on the track. This means that when
121
- * the slider is at its min or max value, a visiable slider handle will extend
122
- * beyond the width (or height in vertical mode) of the slider track. When set
123
- * to `contain`, the slider handle will always be contained within the bounds
124
- * of the track, meaning its position will be slightly offset from the actual
125
- * value depending on where it sits on the track.
126
- *
127
- * @see Docs https://reach.tech/slider#slider-handlealignment
128
- */
129
- handleAlignment?: SliderHandleAlignment;
130
- /**
131
- * The maximum value of the slider. Defaults to `100`.
132
- *
133
- * @see Docs https://reach.tech/slider#slider-max
134
- */
135
- max?: number;
136
- /**
137
- * The minimum value of the slider. Defaults to `0`.
138
- *
139
- * @see Docs https://reach.tech/slider#slider-min
140
- */
141
- min?: number;
142
- /**
143
- * If the slider is used as a form input, it should accept a `name` prop to
144
- * identify its value in context of the form.
145
- *
146
- * @see Docs https://reach.tech/slider#slider-name
147
- */
148
- name?: string;
149
- /**
150
- * Callback that fires when the slider value changes. When the `value` prop is
151
- * set, the Slider state becomes controlled and `onChange` must be used to
152
- * update the value in response to user interaction.
153
- *
154
- * @see Docs https://reach.tech/slider#slider-onchange
155
- */
156
- onChange?(
157
- e: SomePointerEvent | KeyboardEvent,
158
- newValue: number,
159
- props?: {
160
- min?: number;
161
- max?: number;
162
- handlePosition?: string;
163
- }
164
- ): void;
165
-
166
- // We use native DOM events for the slider since they are global
167
- onMouseDown?(event: MouseEvent): void;
168
- onMouseMove?(event: MouseEvent): void;
169
- onMouseUp?(event: MouseEvent): void;
170
- onPointerDown?(event: PointerEvent): void;
171
- onPointerUp?(event: PointerEvent): void;
172
- onTouchEnd?(event: TouchEvent): void;
173
- onTouchMove?(event: TouchEvent): void;
174
- onTouchStart?(event: TouchEvent): void;
175
-
176
- /**
177
- * Sets the slider to horizontal or vertical mode.
178
- *
179
- * @see Docs https://reach.tech/slider#slider-orientation
180
- */
181
- orientation?: SliderOrientation;
182
- /**
183
- * The step attribute is a number that specifies the granularity that the
184
- * value must adhere to as it changes. Step sets minimum intervals of change,
185
- * creating a "snap" effect when the handle is moved along the track.
186
- *
187
- * @see Docs https://reach.tech/slider#slider-step
188
- */
189
- step?: number;
190
- }
191
-
192
- ////////////////////////////////////////////////////////////////////////////////
193
-
194
- /**
195
- * SliderInput
196
- *
197
- * The parent component of the slider interface. This is a lower level component
198
- * if you need more control over styles or rendering the slider's inner
199
- * components.
200
- *
201
- * @see Docs https://reach.tech/slider#sliderinput
202
- */
203
- const SliderInput = forwardRef(function SliderInput(
204
- {
205
- 'aria-label': ariaLabel,
206
- 'aria-labelledby': ariaLabelledBy,
207
- 'aria-valuetext': ariaValueTextProp,
208
- as: Comp = 'div',
209
- innerAs,
210
- defaultValue,
211
- disabled = false,
212
- value: controlledValue,
213
- getAriaLabel,
214
- getAriaValueText,
215
- handleAlignment = 'center',
216
- max = 100,
217
- min = 0,
218
- name,
219
- onChange: onChangeProp,
220
- onKeyDown,
221
- onMouseDown,
222
- onMouseMove,
223
- onMouseUp,
224
- onPointerDown,
225
- onPointerUp,
226
- onTouchEnd,
227
- onTouchMove,
228
- onTouchStart,
229
- orientation = 'horizontal',
230
- step = 1,
231
- children,
232
- ...rest
233
- },
234
- forwardedRef
235
- ) {
236
- const touchId: TouchIdRef = useRef();
237
-
238
- const fallbackId = useId();
239
- const id = rest.id || fallbackId;
240
-
241
- // Track whether or not the pointer is down without updating the component
242
- const pointerDownRef = useRef(false);
243
-
244
- const trackRef: TrackRef = useRef(null);
245
- const handleRef: HandleRef = useRef(null);
246
- const sliderRef: SliderRef = useRef(null);
247
-
248
- const [hasFocus, setHasFocus] = useState(false);
249
-
250
- const { ref: x, ...handleDimensions } = useDimensions(handleRef);
251
-
252
- const [_value, onChange] = useControlledState(
253
- controlledValue,
254
- onChangeProp,
255
- defaultValue || min,
256
- (setValue) => (e, v) => setValue(v)
257
- );
258
- const value = clamp(_value, min, max);
259
- const trackPercent = valueToPercent(value, min, max);
260
- const isVertical = orientation === 'vertical';
261
-
262
- const handleSize = isVertical
263
- ? handleDimensions.height
264
- : handleDimensions.width;
265
-
266
- // TODO: Consider removing the `handleAlignment` prop
267
- // We may want to use accept a `handlePosition` prop instead and let apps
268
- // define their own positioning logic, similar to how we do for popovers.
269
- const handlePosition = `calc(${trackPercent}% - ${
270
- handleAlignment === 'center'
271
- ? `${handleSize}px / 2`
272
- : `${handleSize}px * ${trackPercent * 0.01}`
273
- })`;
274
- const handlePositionRef = useRef(handlePosition);
275
- useLayoutEffect(() => {
276
- handlePositionRef.current = handlePosition;
277
- }, [handlePosition]);
278
-
279
- const getNewValueFromEvent = useCallback(
280
- (event: SomePointerEvent) => {
281
- return getNewValue(getPointerPosition(event, touchId), trackRef.current, {
282
- step,
283
- orientation,
284
- min,
285
- max,
286
- });
287
- },
288
- [max, min, orientation, step]
289
- );
290
-
291
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_kbd_interaction
292
- const handleKeyDown = useStableLayoutCallback((event: KeyboardEvent) => {
293
- if (disabled) {
294
- return;
295
- }
296
-
297
- let newValue: number;
298
- const tenSteps = (max - min) / 10;
299
- const keyStep = step || (max - min) / 100;
300
-
301
- switch (event.key) {
302
- // Decrease the value of the slider by one step.
303
- case 'ArrowLeft':
304
- case 'ArrowDown':
305
- newValue = value - keyStep;
306
- break;
307
- // Increase the value of the slider by one step
308
- case 'ArrowRight':
309
- case 'ArrowUp':
310
- newValue = value + keyStep;
311
- break;
312
- // Decrement the slider by an amount larger than the step change made by
313
- // `ArrowDown`.
314
- case 'PageDown':
315
- newValue = value - tenSteps;
316
- break;
317
- // Increment the slider by an amount larger than the step change made by
318
- // `ArrowUp`.
319
- case 'PageUp':
320
- newValue = value + tenSteps;
321
- break;
322
- // Set the slider to the first allowed value in its range.
323
- case 'Home':
324
- newValue = min;
325
- break;
326
- // Set the slider to the last allowed value in its range.
327
- case 'End':
328
- newValue = max;
329
- break;
330
- default:
331
- return;
332
- }
333
-
334
- newValue = clamp(
335
- step ? roundValueToStep(newValue, step, min) : newValue,
336
- min,
337
- max
338
- );
339
- onChange(event, newValue);
340
- });
341
-
342
- const ariaValueText = getAriaValueText
343
- ? getAriaValueText(value)
344
- : ariaValueTextProp;
345
-
346
- const rangeStyle = { [isVertical ? 'height' : 'width']: `${trackPercent}%` };
347
-
348
- // Slide events!
349
- // We will try to use pointer events if they are supported to leverage
350
- // setPointerCapture and releasePointerCapture. We'll fall back to separate
351
- // mouse and touch events.
352
- // TODO: This could be more concise
353
- const removeMoveEvents = useRef<() => void>(noop);
354
- const removeStartEvents = useRef<() => void>(noop);
355
- const removeEndEvents = useRef<() => void>(noop);
356
-
357
- // Store our event handlers in refs so we aren't attaching/detaching events
358
- // on every render if the user doesn't useCallback
359
- const appEvents = useRef({
360
- onMouseMove,
361
- onMouseDown,
362
- onMouseUp,
363
- onTouchStart,
364
- onTouchEnd,
365
- onTouchMove,
366
- onPointerDown,
367
- onPointerUp,
368
- });
369
- useLayoutEffect(() => {
370
- appEvents.current.onMouseMove = onMouseMove;
371
- appEvents.current.onMouseDown = onMouseDown;
372
- appEvents.current.onMouseUp = onMouseUp;
373
- appEvents.current.onTouchStart = onTouchStart;
374
- appEvents.current.onTouchEnd = onTouchEnd;
375
- appEvents.current.onTouchMove = onTouchMove;
376
- appEvents.current.onPointerDown = onPointerDown;
377
- appEvents.current.onPointerUp = onPointerUp;
378
- }, [
379
- onMouseMove,
380
- onMouseDown,
381
- onMouseUp,
382
- onTouchStart,
383
- onTouchEnd,
384
- onTouchMove,
385
- onPointerDown,
386
- onPointerUp,
387
- ]);
388
-
389
- const handleSlideStart = useStableLayoutCallback(
390
- (event: SomePointerEvent) => {
391
- if (isRightClick(event)) return;
392
-
393
- if (disabled) {
394
- pointerDownRef.current = false;
395
- return;
396
- }
397
-
398
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
399
- const ownerDocument = getOwnerDocument(sliderRef.current)!;
400
- const ownerWindow = ownerDocument.defaultView || window;
401
- pointerDownRef.current = true;
402
-
403
- if ((event as TouchEvent).changedTouches) {
404
- // Prevent scrolling for touch events
405
- event.preventDefault();
406
- const touch = (event as TouchEvent).changedTouches?.[0];
407
- if (touch != null) {
408
- touchId.current = touch.identifier;
409
- }
410
- }
411
-
412
- const newValue = getNewValueFromEvent(event);
413
- if (newValue == null) {
414
- return;
415
- }
416
- ownerWindow.requestAnimationFrame(() => handleRef.current?.focus());
417
- onChange(event, newValue);
418
-
419
- removeMoveEvents.current = addMoveListener();
420
- removeEndEvents.current = addEndListener();
421
- }
422
- );
423
-
424
- const setPointerCapture = useStableLayoutCallback((event: PointerEvent) => {
425
- if (isRightClick(event)) return;
426
- if (disabled) {
427
- pointerDownRef.current = false;
428
- return;
429
- }
430
- pointerDownRef.current = true;
431
- sliderRef.current?.setPointerCapture(event.pointerId);
432
- });
433
-
434
- const releasePointerCapture = useStableLayoutCallback(
435
- (event: PointerEvent) => {
436
- if (isRightClick(event)) return;
437
- sliderRef.current?.releasePointerCapture(event.pointerId);
438
- pointerDownRef.current = false;
439
- }
440
- );
441
-
442
- const handlePointerMove = useStableLayoutCallback(
443
- (event: SomePointerEvent) => {
444
- if (disabled || !pointerDownRef.current) {
445
- pointerDownRef.current = false;
446
- return;
447
- }
448
-
449
- const newValue = getNewValueFromEvent(event);
450
- if (newValue == null) {
451
- return;
452
- }
453
- onChange?.(event, newValue);
454
- }
455
- );
456
-
457
- const handleSlideStop = useStableLayoutCallback((event: SomePointerEvent) => {
458
- if (isRightClick(event)) return;
459
-
460
- pointerDownRef.current = false;
461
-
462
- const newValue = getNewValueFromEvent(event);
463
- if (newValue == null) {
464
- return;
465
- }
466
-
467
- touchId.current = undefined;
468
-
469
- removeMoveEvents.current();
470
- removeEndEvents.current();
471
- });
472
-
473
- const addMoveListener = useCallback(() => {
474
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
475
- const ownerDocument = getOwnerDocument(sliderRef.current)!;
476
- const touchListener = wrapEvent(
477
- appEvents.current.onTouchMove,
478
- handlePointerMove
479
- );
480
- const mouseListener = wrapEvent(
481
- appEvents.current.onMouseMove,
482
- handlePointerMove
483
- );
484
- ownerDocument.addEventListener('touchmove', touchListener);
485
- ownerDocument.addEventListener('mousemove', mouseListener);
486
- return () => {
487
- ownerDocument.removeEventListener('touchmove', touchListener);
488
- ownerDocument.removeEventListener('mousemove', mouseListener);
489
- };
490
- }, [handlePointerMove]);
491
-
492
- const addEndListener = useCallback(() => {
493
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
494
- const ownerDocument = getOwnerDocument(sliderRef.current)!;
495
- const ownerWindow = ownerDocument.defaultView || window;
496
- const pointerListener = wrapEvent(
497
- appEvents.current.onPointerUp,
498
- releasePointerCapture
499
- );
500
- const touchListener = wrapEvent(
501
- appEvents.current.onTouchEnd,
502
- handleSlideStop
503
- );
504
- const mouseListener = wrapEvent(
505
- appEvents.current.onMouseUp,
506
- handleSlideStop
507
- );
508
- if ('PointerEvent' in ownerWindow) {
509
- ownerDocument.addEventListener('pointerup', pointerListener);
510
- }
511
- ownerDocument.addEventListener('touchend', touchListener);
512
- ownerDocument.addEventListener('mouseup', mouseListener);
513
- return () => {
514
- if ('PointerEvent' in ownerWindow) {
515
- ownerDocument.removeEventListener('pointerup', pointerListener);
516
- }
517
- ownerDocument.removeEventListener('touchend', touchListener);
518
- ownerDocument.removeEventListener('mouseup', mouseListener);
519
- };
520
- }, [handleSlideStop, releasePointerCapture]);
521
-
522
- const addStartListener = useCallback(() => {
523
- // e.preventDefault is ignored by React's synthetic touchStart event, so
524
- // we attach the listener directly to the DOM node
525
- // https://github.com/facebook/react/issues/9809#issuecomment-413978405
526
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
527
- const sliderElement = sliderRef.current!;
528
- if (!sliderElement) {
529
- return noop;
530
- }
531
-
532
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
533
- const ownerDocument = getOwnerDocument(sliderElement)!;
534
- const ownerWindow = ownerDocument.defaultView || window;
535
- const touchListener = wrapEvent(
536
- appEvents.current.onTouchStart,
537
- handleSlideStart
538
- );
539
- const mouseListener = wrapEvent(
540
- appEvents.current.onMouseDown,
541
- handleSlideStart
542
- );
543
- const pointerListener = wrapEvent(
544
- appEvents.current.onPointerDown,
545
- setPointerCapture
546
- );
547
- sliderElement.addEventListener('touchstart', touchListener);
548
- sliderElement.addEventListener('mousedown', mouseListener);
549
- if ('PointerEvent' in ownerWindow) {
550
- sliderElement.addEventListener('pointerdown', pointerListener);
551
- }
552
- return () => {
553
- sliderElement.removeEventListener('touchstart', touchListener);
554
- sliderElement.removeEventListener('mousedown', mouseListener);
555
- if ('PointerEvent' in ownerWindow) {
556
- sliderElement.removeEventListener('pointerdown', pointerListener);
557
- }
558
- };
559
- }, [setPointerCapture, handleSlideStart]);
560
-
561
- useEffect(() => {
562
- removeStartEvents.current = addStartListener();
563
-
564
- return () => {
565
- removeStartEvents.current();
566
- removeEndEvents.current();
567
- removeMoveEvents.current();
568
- };
569
- }, [addStartListener]);
570
-
571
- const inputFallbackId = useId();
572
- const inputId = id || inputFallbackId;
573
-
574
- return (
575
- <SliderProvider
576
- ariaLabel={getAriaLabel ? getAriaLabel(value) : ariaLabel}
577
- ariaLabelledBy={ariaLabelledBy}
578
- ariaValueText={ariaValueText}
579
- handleDimensions={handleDimensions}
580
- handleKeyDown={handleKeyDown}
581
- handlePosition={handlePosition}
582
- handleRef={handleRef}
583
- hasFocus={hasFocus}
584
- onKeyDown={onKeyDown}
585
- setHasFocus={setHasFocus}
586
- sliderId={id}
587
- sliderMax={max}
588
- sliderMin={min}
589
- value={value}
590
- disabled={!!disabled}
591
- isVertical={isVertical}
592
- orientation={orientation}
593
- trackPercent={trackPercent}
594
- trackRef={trackRef}
595
- rangeStyle={rangeStyle}
596
- updateValue={onChange}
597
- >
598
- <Comp
599
- {...rest}
600
- // @ts-ignore
601
- as={innerAs}
602
- ref={assignMultipleRefs(sliderRef, forwardedRef)}
603
- data-reach-slider-input=""
604
- data-disabled={disabled ? '' : undefined}
605
- data-orientation={orientation}
606
- tabIndex={-1}
607
- >
608
- {typeof children === 'function'
609
- ? children({
610
- hasFocus,
611
- id,
612
- max,
613
- min,
614
- value,
615
- ariaValueText,
616
- })
617
- : children}
618
- {name && (
619
- // If the slider is used in a form we'll need an input field to
620
- // capture the value. We'll assume this when the component is given a
621
- // form field name (A `name` prop doesn't really make sense in any
622
- // other context).
623
- <input type="hidden" value={value} name={name} id={inputId} />
624
- )}
625
- </Comp>
626
- </SliderProvider>
627
- );
628
- }) as Polymorphic.ForwardRefComponent<'div', SliderInputProps>;
629
-
630
- /**
631
- * @see Docs https://reach.tech/slider#sliderinput-props
632
- */
633
- type SliderInputProps = Omit<SliderProps, 'children'> & {
634
- /**
635
- * Slider expects `<SliderTrack>` as its child; The track will accept all
636
- * additional slider sub-components as children. It can also accept a
637
- * function/render prop as its child to expose some of its internal state
638
- * variables.
639
- *
640
- * @see Docs https://reach.tech/slider#sliderinput-children
641
- */
642
- children: ReactNode | SliderChildrenRender;
643
- };
644
-
645
- ////////////////////////////////////////////////////////////////////////////////
646
-
647
- /**
648
- * SliderTrack
649
- *
650
- * @see Docs https://reach.tech/slider#slidertrack
651
- */
652
- const SliderTrackImpl = forwardRef(function SliderTrack(
653
- { as: Comp = 'div', innerAs, children, style = {}, ...props },
654
- forwardedRef
655
- ) {
656
- const { disabled, orientation, trackRef } = useSliderContext('SliderTrack');
657
-
658
- return (
659
- <Comp
660
- ref={assignMultipleRefs(trackRef, forwardedRef)}
661
- // @ts-ignore
662
- as={innerAs}
663
- style={{ ...style, position: 'relative' }}
664
- {...props}
665
- data-reach-slider-track=""
666
- data-disabled={disabled ? '' : undefined}
667
- data-orientation={orientation}
668
- >
669
- {children}
670
- </Comp>
671
- );
672
- }) as Polymorphic.ForwardRefComponent<'div', SliderTrackProps>;
673
-
674
- const SliderTrack = memo(SliderTrackImpl) as Polymorphic.MemoComponent<
675
- 'div',
676
- SliderTrackProps
677
- >;
678
-
679
- /**
680
- * @see Docs https://reach.tech/slider#slidertrack-props
681
- */
682
- interface SliderTrackProps {
683
- /**
684
- * `SliderTrack` expects `<SliderHandle>`, at minimum, for the Slider to
685
- * function. All other Slider subcomponents should be passed as children
686
- * inside the `SliderTrack`.
687
- *
688
- * @see Docs https://reach.tech/slider#slidertrack-children
689
- */
690
- children: ReactNode;
691
- }
692
-
693
- ////////////////////////////////////////////////////////////////////////////////
694
-
695
- /**
696
- * SliderRange
697
- *
698
- * The (typically) highlighted portion of the track that represents the space
699
- * between the slider's `min` value and its current value.
700
- *
701
- * @see Docs https://reach.tech/slider#sliderrange
702
- */
703
- const SliderRangeImpl = forwardRef(function SliderRange(
704
- { as: Comp = 'div', innerAs, children, style = {}, ...props },
705
- forwardedRef
706
- ) {
707
- const { disabled, orientation, rangeStyle } = useSliderContext('SliderRange');
708
- return (
709
- <Comp
710
- ref={forwardedRef}
711
- // @ts-ignore
712
- as={innerAs}
713
- style={{ position: 'absolute', ...rangeStyle, ...style }}
714
- {...props}
715
- data-reach-slider-range=""
716
- data-disabled={disabled ? '' : undefined}
717
- data-orientation={orientation}
718
- />
719
- );
720
- }) as Polymorphic.ForwardRefComponent<'div', SliderRangeProps>;
721
-
722
- const SliderRange = memo(SliderRangeImpl) as Polymorphic.MemoComponent<
723
- 'div',
724
- SliderRangeProps
725
- >;
726
-
727
- /**
728
- * `SliderRange` accepts any props that a HTML div component accepts.
729
- * `SliderRange` will not accept or render any children.
730
- *
731
- * @see Docs https://reach.tech/slider#sliderrange-props
732
- */
733
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
734
- interface SliderRangeProps {}
735
-
736
- ////////////////////////////////////////////////////////////////////////////////
737
-
738
- /**
739
- * SliderHandle
740
- *
741
- * The handle that the user drags along the track to set the slider value.
742
- *
743
- * @see Docs https://reach.tech/slider#sliderhandle
744
- */
745
- const SliderHandleImpl = forwardRef(function SliderHandle(
746
- {
747
- // min,
748
- // max,
749
- as: Comp = 'div',
750
- innerAs,
751
- onBlur,
752
- onFocus,
753
- style = {},
754
- onKeyDown,
755
- ...props
756
- },
757
- forwardedRef
758
- ) {
759
- const {
760
- ariaLabel,
761
- ariaLabelledBy,
762
- ariaValueText,
763
- disabled,
764
- handlePosition,
765
- handleRef,
766
- isVertical,
767
- handleKeyDown,
768
- orientation,
769
- setHasFocus,
770
- sliderMin,
771
- sliderMax,
772
- value,
773
- } = useSliderContext('SliderHandle');
774
-
775
- return (
776
- <Comp
777
- // @ts-ignore
778
- as={innerAs}
779
- aria-disabled={disabled || undefined}
780
- // If the slider has a visible label, it is referenced by
781
- // `aria-labelledby` on the slider element. Otherwise, the slider
782
- // element has a label provided by `aria-label`.
783
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
784
- aria-label={ariaLabel}
785
- aria-labelledby={ariaLabel ? undefined : ariaLabelledBy}
786
- // If the slider is vertically oriented, it has `aria-orientation` set
787
- // to vertical. The default value of `aria-orientation` for a slider is
788
- // horizontal.
789
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
790
- aria-orientation={orientation}
791
- // The slider element has the `aria-valuemax` property set to a decimal
792
- // value representing the maximum allowed value of the slider.
793
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
794
- aria-valuemax={sliderMax}
795
- // The slider element has the `aria-valuemin` property set to a decimal
796
- // value representing the minimum allowed value of the slider.
797
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
798
- aria-valuemin={sliderMin}
799
- // The slider element has the `aria-valuenow` property set to a decimal
800
- // value representing the current value of the slider.
801
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
802
- aria-valuenow={value}
803
- // If the value of `aria-valuenow` is not user-friendly, e.g., the day
804
- // of the week is represented by a number, the `aria-valuetext` property
805
- // is set to a string that makes the slider value understandable, e.g.,
806
- // "Monday".
807
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
808
- aria-valuetext={ariaValueText}
809
- // The element serving as the focusable slider control has role
810
- // `slider`.
811
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
812
- role="slider"
813
- tabIndex={disabled ? -1 : 0}
814
- {...props}
815
- data-reach-slider-handle=""
816
- ref={assignMultipleRefs(handleRef, forwardedRef)}
817
- onBlur={wrapEvent(onBlur, () => {
818
- setHasFocus(false);
819
- })}
820
- onFocus={wrapEvent(onFocus, () => {
821
- setHasFocus(true);
822
- })}
823
- onKeyDown={wrapEvent(onKeyDown, handleKeyDown)}
824
- style={{
825
- position: 'absolute',
826
- ...(isVertical ? { bottom: handlePosition } : { left: handlePosition }),
827
- ...style,
828
- }}
829
- />
830
- );
831
- }) as Polymorphic.ForwardRefComponent<'div', SliderHandleProps>;
832
-
833
- const SliderHandle = memo(SliderHandleImpl) as Polymorphic.MemoComponent<
834
- 'div',
835
- SliderHandleProps
836
- >;
837
-
838
- /**
839
- * `SliderRange` accepts any props that a HTML div component accepts.
840
- *
841
- * @see Docs https://reach.tech/slider#sliderhandle-props
842
- */
843
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
844
- interface SliderHandleProps {}
845
-
846
- ////////////////////////////////////////////////////////////////////////////////
847
-
848
- /**
849
- * SliderMarker
850
- *
851
- * A fixed value marker. These can be used to illustrate a range of steps or
852
- * highlight important points along the slider track.
853
- *
854
- * @see Docs https://reach.tech/slider#slidermarker
855
- */
856
- const SliderMarkerImpl = forwardRef(function SliderMarker(
857
- { as: Comp = 'div', innerAs, children, style = {}, value, ...props },
858
- forwardedRef
859
- ) {
860
- const {
861
- disabled,
862
- isVertical,
863
- orientation,
864
- sliderMin,
865
- sliderMax,
866
- value: sliderValue,
867
- } = useSliderContext('SliderMarker');
868
-
869
- const inRange = value >= sliderMin && value <= sliderMax;
870
- const absoluteStartPosition = `${valueToPercent(
871
- value,
872
- sliderMin,
873
- sliderMax
874
- )}%`;
875
-
876
- const state =
877
- value < sliderValue
878
- ? 'under-value'
879
- : value === sliderValue
880
- ? 'at-value'
881
- : 'over-value';
882
-
883
- return inRange ? (
884
- <Comp
885
- ref={forwardedRef}
886
- // @ts-ignore
887
- as={innerAs}
888
- style={{
889
- position: 'absolute',
890
- ...(isVertical
891
- ? { bottom: absoluteStartPosition }
892
- : { left: absoluteStartPosition }),
893
- ...style,
894
- }}
895
- {...props}
896
- data-reach-slider-marker=""
897
- data-disabled={disabled ? '' : undefined}
898
- data-orientation={orientation}
899
- data-state={state}
900
- data-value={value}
901
- >
902
- {children}
903
- </Comp>
904
- ) : null;
905
- }) as Polymorphic.ForwardRefComponent<'div', SliderMarkerProps>;
906
-
907
- const SliderMarker = memo(SliderMarkerImpl) as Polymorphic.MemoComponent<
908
- 'div',
909
- SliderMarkerProps
910
- >;
911
-
912
- /**
913
- * @see Docs https://reach.tech/slider#slidermarker-props
914
- */
915
- interface SliderMarkerProps {
916
- /**
917
- * The value to denote where the marker should appear along the track.
918
- *
919
- * @see Docs https://reach.tech/slider#slidermarker-value
920
- */
921
- value: number;
922
- }
923
-
924
- ////////////////////////////////////////////////////////////////////////////////
925
-
926
- function clamp(val: number, min: number, max: number) {
927
- return val > max ? max : val < min ? min : val;
928
- }
929
-
930
- /**
931
- * This handles the case when num is very small (0.00000001), js will turn
932
- * this into 1e-8. When num is bigger than 1 or less than -1 it won't get
933
- * converted to this notation so it's fine.
934
- *
935
- * @param num
936
- * @see https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Slider/Slider.js#L69
937
- */
938
- function getDecimalPrecision(num: number) {
939
- if (Math.abs(num) < 1) {
940
- const parts = num.toExponential().split('e-');
941
- const matissaDecimalPart = parts[0].split('.')[1];
942
- return (
943
- (matissaDecimalPart ? matissaDecimalPart.length : 0) +
944
- parseInt(parts[1], 10)
945
- );
946
- }
947
-
948
- const decimalPart = num.toString().split('.')[1];
949
- return decimalPart ? decimalPart.length : 0;
950
- }
951
-
952
- function percentToValue(percent: number, min: number, max: number) {
953
- return (max - min) * percent + min;
954
- }
955
-
956
- function roundValueToStep(value: number, step: number, min: number) {
957
- const nearest = Math.round((value - min) / step) * step + min;
958
- return Number(nearest.toFixed(getDecimalPrecision(step)));
959
- }
960
-
961
- function getPointerPosition(event: SomePointerEvent, touchId: TouchIdRef) {
962
- if (touchId.current !== undefined && (event as TouchEvent).changedTouches) {
963
- for (let i = 0; i < (event as TouchEvent).changedTouches.length; i += 1) {
964
- const touch = (event as TouchEvent).changedTouches[i];
965
- if (touch.identifier === touchId.current) {
966
- return {
967
- x: touch.clientX,
968
- y: touch.clientY,
969
- };
970
- }
971
- }
972
-
973
- return false;
974
- }
975
-
976
- return {
977
- x: (event as PointerEvent | MouseEvent).clientX,
978
- y: (event as PointerEvent | MouseEvent).clientY,
979
- };
980
- }
981
-
982
- function getNewValue(
983
- handlePosition:
984
- | {
985
- x: number;
986
- y: number;
987
- }
988
- | false,
989
- track: HTMLElement | null,
990
- props: {
991
- orientation: SliderOrientation;
992
- min: number;
993
- max: number;
994
- step?: number;
995
- }
996
- ) {
997
- const { orientation, min, max, step } = props;
998
-
999
- if (!track || !handlePosition) {
1000
- return null;
1001
- }
1002
-
1003
- const { left, width, bottom, height } = track.getBoundingClientRect();
1004
- const isVertical = orientation === 'vertical';
1005
- const diff = isVertical ? bottom - handlePosition.y : handlePosition.x - left;
1006
- const percent = diff / (isVertical ? height : width);
1007
- const newValue = percentToValue(percent, min, max);
1008
-
1009
- return clamp(
1010
- step ? roundValueToStep(newValue, step, min) : newValue,
1011
- min,
1012
- max
1013
- );
1014
- }
1015
-
1016
- function useDimensions(ref: React.RefObject<HTMLElement | null>) {
1017
- const [{ width, height }, setDimensions] = useState({
1018
- width: 0,
1019
- height: 0,
1020
- });
1021
- // Many existing `useDimensions` type hooks will use `getBoundingClientRect`
1022
- // getBoundingClientRect does not work here when borders are applied.
1023
- // getComputedStyle is not as performant so we may want to create a utility to
1024
- // check for any conflicts with box sizing first and only use
1025
- // `getComputedStyle` if neccessary.
1026
- /* const { width, height } = ref.current
1027
- ? ref.current.getBoundingClientRect()
1028
- : 0; */
1029
-
1030
- useLayoutEffect(() => {
1031
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1032
- const ownerDocument = getOwnerDocument(ref.current)!;
1033
- const ownerWindow = ownerDocument.defaultView || window;
1034
- if (ref.current) {
1035
- const { height: _newHeight, width: _newWidth } =
1036
- ownerWindow.getComputedStyle(ref.current);
1037
- const newHeight = parseFloat(_newHeight);
1038
- const newWidth = parseFloat(_newWidth);
1039
-
1040
- if (newHeight !== height || newWidth !== width) {
1041
- setDimensions({ height: newHeight, width: newWidth });
1042
- }
1043
- }
1044
- }, [ref, width, height]);
1045
- return { ref, width, height };
1046
- }
1047
-
1048
- function valueToPercent(value: number, min: number, max: number) {
1049
- return ((value - min) * 100) / (max - min);
1050
- }
1051
-
1052
- ////////////////////////////////////////////////////////////////////////////////
1053
- // Types
1054
-
1055
- type TrackRef = React.RefObject<HTMLDivElement | null>;
1056
- type HandleRef = React.RefObject<HTMLDivElement | null>;
1057
- type SliderRef = React.RefObject<HTMLDivElement | null>;
1058
- type TouchIdRef = React.MutableRefObject<number | undefined>;
1059
-
1060
- type SomePointerEvent = TouchEvent | MouseEvent;
1061
-
1062
- interface ISliderContext {
1063
- ariaLabel: string | undefined;
1064
- ariaLabelledBy: string | undefined;
1065
- ariaValueText: string | undefined;
1066
- handleDimensions: {
1067
- width: number;
1068
- height: number;
1069
- };
1070
- handlePosition: string;
1071
- handleRef: HandleRef;
1072
- hasFocus: boolean;
1073
- onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
1074
- handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
1075
- setHasFocus: React.Dispatch<React.SetStateAction<boolean>>;
1076
- sliderId: string | undefined;
1077
- sliderMax: number;
1078
- sliderMin: number;
1079
- value: number;
1080
- disabled: boolean;
1081
- isVertical: boolean;
1082
- orientation: SliderOrientation;
1083
- trackPercent: number;
1084
- trackRef: TrackRef;
1085
- rangeStyle: React.CSSProperties;
1086
- updateValue: (e: SomePointerEvent | KeyboardEvent, newValue: any) => void;
1087
- }
1088
-
1089
- type SliderChildrenRender = (props: {
1090
- ariaValueText?: string | undefined;
1091
- hasFocus?: boolean;
1092
- id?: string | undefined;
1093
- sliderId?: string | undefined;
1094
- max?: number;
1095
- min?: number;
1096
- value?: number;
1097
- }) => JSX.Element;
1098
-
1099
- ////////////////////////////////////////////////////////////////////////////////
1100
- // Exports
1101
-
1102
- export default Slider;
1103
- export type {
1104
- SliderHandleProps,
1105
- SliderInputProps,
1106
- SliderMarkerProps,
1107
- SliderProps,
1108
- SliderRangeProps,
1109
- SliderTrackProps,
1110
- SliderHandleAlignment,
1111
- SliderOrientation,
1112
- };
1113
- export {
1114
- Slider,
1115
- SliderHandle,
1116
- SliderInput,
1117
- SliderMarker,
1118
- SliderTrack,
1119
- SliderRange,
1120
- };
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+
3
+ /**
4
+ * Welcome to @reach/slider!
5
+ *
6
+ * A UI input component where the user selects a value from within a given
7
+ * range. A Slider has a handle that can be moved along a track to change its
8
+ * value. When the user's mouse or focus is on the Slider's handle, the value
9
+ * can be incremented with keyboard controls.
10
+ *
11
+ * Random thoughts/notes:
12
+ * - Currently testing this against the behavior of the native input range
13
+ * element to get our slider on par. We'll explore animated and multi-handle
14
+ * sliders next.
15
+ * - We may want to research some use cases for reversed sliders in RTL
16
+ * languages if that's a thing
17
+ *
18
+ * @see Docs https://reach.tech/slider
19
+ * @see Source https://github.com/reach/reach-ui/tree/main/packages/slider
20
+ * @see WAI-ARIA https://www.w3.org/TR/wai-aria-practices-1.2/#slider
21
+ * @see Example https://github.com/Stanko/aria-progress-range-slider
22
+ * @see Example http://www.oaa-accessibility.org/examplep/slider1/
23
+ */
24
+
25
+ import {
26
+ forwardRef,
27
+ memo,
28
+ useRef,
29
+ useState,
30
+ useCallback,
31
+ useEffect,
32
+ useLayoutEffect,
33
+ useId,
34
+ } from 'react';
35
+ import type { ReactNode, KeyboardEvent } from 'react';
36
+
37
+ import { createContext } from '../utils/context';
38
+ import type * as Polymorphic from '../utils/polymorphic';
39
+ import { isRightClick } from '../utils/is-right-click';
40
+ import { getOwnerDocument } from '../utils/owner-document';
41
+ import { useStableLayoutCallback } from '../utils/use-stable-callback';
42
+ import { assignMultipleRefs, wrapEvent } from '../utils';
43
+ import { useControlledState } from '../hooks';
44
+
45
+ const noop = () => {
46
+ /* noop */
47
+ };
48
+
49
+ type SliderOrientation = 'horizontal' | 'vertical';
50
+ type SliderHandleAlignment = 'center' | 'contain';
51
+
52
+ const [SliderProvider, useSliderContext] =
53
+ createContext<ISliderContext>('Slider');
54
+
55
+ ////////////////////////////////////////////////////////////////////////////////
56
+
57
+ /**
58
+ * Slider
59
+ *
60
+ * @see Docs https://reach.tech/slider#slider
61
+ */
62
+ const Slider = forwardRef(function Slider(
63
+ { children, ...props },
64
+ forwardedRef
65
+ ) {
66
+ return (
67
+ <SliderInput {...props} ref={forwardedRef} data-reach-slider="">
68
+ <SliderTrack>
69
+ <SliderRange />
70
+ <SliderHandle />
71
+ {children}
72
+ </SliderTrack>
73
+ </SliderInput>
74
+ );
75
+ }) as Polymorphic.ForwardRefComponent<'div', SliderProps>;
76
+
77
+ /**
78
+ * @see Docs https://reach.tech/slider#slider-props
79
+ */
80
+ interface SliderProps {
81
+ /**
82
+ * `Slider` can accept `SliderMarker` children to enhance display of specific
83
+ * values along the track.
84
+ *
85
+ * @see Docs https://reach.tech/slider#slider-children
86
+ */
87
+ children?: ReactNode;
88
+ /**
89
+ * The defaultValue is used to set an initial value for an uncontrolled
90
+ * Slider.
91
+ *
92
+ * @see Docs https://reach.tech/slider#slider-defaultvalue
93
+ */
94
+ defaultValue?: number;
95
+ /**
96
+ * @see Docs https://reach.tech/slider#slider-disabled
97
+ */
98
+ disabled?: boolean;
99
+ /**
100
+ * Whether or not the slider should be disabled from user interaction.
101
+ *
102
+ * @see Docs https://reach.tech/slider#slider-value
103
+ */
104
+ value?: number;
105
+ /**
106
+ * A function used to set a human-readable name for the slider.
107
+ *
108
+ * @see Docs https://reach.tech/slider#slider-getarialabel
109
+ */
110
+ getAriaLabel?(value: number): string;
111
+ /**
112
+ * A function used to set a human-readable value text based on the slider's
113
+ * current value.
114
+ *
115
+ * @see Docs https://reach.tech/slider#slider-getariavaluetext
116
+ */
117
+ getAriaValueText?(value: number): string;
118
+ /**
119
+ * When set to `center`, the slider's handle will be positioned directly
120
+ * centered over the slider's curremt value on the track. This means that when
121
+ * the slider is at its min or max value, a visiable slider handle will extend
122
+ * beyond the width (or height in vertical mode) of the slider track. When set
123
+ * to `contain`, the slider handle will always be contained within the bounds
124
+ * of the track, meaning its position will be slightly offset from the actual
125
+ * value depending on where it sits on the track.
126
+ *
127
+ * @see Docs https://reach.tech/slider#slider-handlealignment
128
+ */
129
+ handleAlignment?: SliderHandleAlignment;
130
+ /**
131
+ * The maximum value of the slider. Defaults to `100`.
132
+ *
133
+ * @see Docs https://reach.tech/slider#slider-max
134
+ */
135
+ max?: number;
136
+ /**
137
+ * The minimum value of the slider. Defaults to `0`.
138
+ *
139
+ * @see Docs https://reach.tech/slider#slider-min
140
+ */
141
+ min?: number;
142
+ /**
143
+ * If the slider is used as a form input, it should accept a `name` prop to
144
+ * identify its value in context of the form.
145
+ *
146
+ * @see Docs https://reach.tech/slider#slider-name
147
+ */
148
+ name?: string;
149
+ /**
150
+ * Callback that fires when the slider value changes. When the `value` prop is
151
+ * set, the Slider state becomes controlled and `onChange` must be used to
152
+ * update the value in response to user interaction.
153
+ *
154
+ * @see Docs https://reach.tech/slider#slider-onchange
155
+ */
156
+ onChange?(
157
+ e: SomePointerEvent | KeyboardEvent,
158
+ newValue: number,
159
+ props?: {
160
+ min?: number;
161
+ max?: number;
162
+ handlePosition?: string;
163
+ }
164
+ ): void;
165
+
166
+ // We use native DOM events for the slider since they are global
167
+ onMouseDown?(event: MouseEvent): void;
168
+ onMouseMove?(event: MouseEvent): void;
169
+ onMouseUp?(event: MouseEvent): void;
170
+ onPointerDown?(event: PointerEvent): void;
171
+ onPointerUp?(event: PointerEvent): void;
172
+ onTouchEnd?(event: TouchEvent): void;
173
+ onTouchMove?(event: TouchEvent): void;
174
+ onTouchStart?(event: TouchEvent): void;
175
+
176
+ /**
177
+ * Sets the slider to horizontal or vertical mode.
178
+ *
179
+ * @see Docs https://reach.tech/slider#slider-orientation
180
+ */
181
+ orientation?: SliderOrientation;
182
+ /**
183
+ * The step attribute is a number that specifies the granularity that the
184
+ * value must adhere to as it changes. Step sets minimum intervals of change,
185
+ * creating a "snap" effect when the handle is moved along the track.
186
+ *
187
+ * @see Docs https://reach.tech/slider#slider-step
188
+ */
189
+ step?: number;
190
+ }
191
+
192
+ ////////////////////////////////////////////////////////////////////////////////
193
+
194
+ /**
195
+ * SliderInput
196
+ *
197
+ * The parent component of the slider interface. This is a lower level component
198
+ * if you need more control over styles or rendering the slider's inner
199
+ * components.
200
+ *
201
+ * @see Docs https://reach.tech/slider#sliderinput
202
+ */
203
+ const SliderInput = forwardRef(function SliderInput(
204
+ {
205
+ 'aria-label': ariaLabel,
206
+ 'aria-labelledby': ariaLabelledBy,
207
+ 'aria-valuetext': ariaValueTextProp,
208
+ as: Comp = 'div',
209
+ innerAs,
210
+ defaultValue,
211
+ disabled = false,
212
+ value: controlledValue,
213
+ getAriaLabel,
214
+ getAriaValueText,
215
+ handleAlignment = 'center',
216
+ max = 100,
217
+ min = 0,
218
+ name,
219
+ onChange: onChangeProp,
220
+ onKeyDown,
221
+ onMouseDown,
222
+ onMouseMove,
223
+ onMouseUp,
224
+ onPointerDown,
225
+ onPointerUp,
226
+ onTouchEnd,
227
+ onTouchMove,
228
+ onTouchStart,
229
+ orientation = 'horizontal',
230
+ step = 1,
231
+ children,
232
+ ...rest
233
+ },
234
+ forwardedRef
235
+ ) {
236
+ const touchId: TouchIdRef = useRef();
237
+
238
+ const fallbackId = useId();
239
+ const id = rest.id || fallbackId;
240
+
241
+ // Track whether or not the pointer is down without updating the component
242
+ const pointerDownRef = useRef(false);
243
+
244
+ const trackRef: TrackRef = useRef(null);
245
+ const handleRef: HandleRef = useRef(null);
246
+ const sliderRef: SliderRef = useRef(null);
247
+
248
+ const [hasFocus, setHasFocus] = useState(false);
249
+
250
+ const { ref: x, ...handleDimensions } = useDimensions(handleRef);
251
+
252
+ const [_value, onChange] = useControlledState(
253
+ controlledValue,
254
+ onChangeProp,
255
+ defaultValue || min,
256
+ (setValue) => (e, v) => setValue(v)
257
+ );
258
+ const value = clamp(_value, min, max);
259
+ const trackPercent = valueToPercent(value, min, max);
260
+ const isVertical = orientation === 'vertical';
261
+
262
+ const handleSize = isVertical
263
+ ? handleDimensions.height
264
+ : handleDimensions.width;
265
+
266
+ // TODO: Consider removing the `handleAlignment` prop
267
+ // We may want to use accept a `handlePosition` prop instead and let apps
268
+ // define their own positioning logic, similar to how we do for popovers.
269
+ const handlePosition = `calc(${trackPercent}% - ${
270
+ handleAlignment === 'center'
271
+ ? `${handleSize}px / 2`
272
+ : `${handleSize}px * ${trackPercent * 0.01}`
273
+ })`;
274
+ const handlePositionRef = useRef(handlePosition);
275
+ useLayoutEffect(() => {
276
+ handlePositionRef.current = handlePosition;
277
+ }, [handlePosition]);
278
+
279
+ const getNewValueFromEvent = useCallback(
280
+ (event: SomePointerEvent) => {
281
+ return getNewValue(getPointerPosition(event, touchId), trackRef.current, {
282
+ step,
283
+ orientation,
284
+ min,
285
+ max,
286
+ });
287
+ },
288
+ [max, min, orientation, step]
289
+ );
290
+
291
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_kbd_interaction
292
+ const handleKeyDown = useStableLayoutCallback((event: KeyboardEvent) => {
293
+ if (disabled) {
294
+ return;
295
+ }
296
+
297
+ let newValue: number;
298
+ const tenSteps = (max - min) / 10;
299
+ const keyStep = step || (max - min) / 100;
300
+
301
+ switch (event.key) {
302
+ // Decrease the value of the slider by one step.
303
+ case 'ArrowLeft':
304
+ case 'ArrowDown':
305
+ newValue = value - keyStep;
306
+ break;
307
+ // Increase the value of the slider by one step
308
+ case 'ArrowRight':
309
+ case 'ArrowUp':
310
+ newValue = value + keyStep;
311
+ break;
312
+ // Decrement the slider by an amount larger than the step change made by
313
+ // `ArrowDown`.
314
+ case 'PageDown':
315
+ newValue = value - tenSteps;
316
+ break;
317
+ // Increment the slider by an amount larger than the step change made by
318
+ // `ArrowUp`.
319
+ case 'PageUp':
320
+ newValue = value + tenSteps;
321
+ break;
322
+ // Set the slider to the first allowed value in its range.
323
+ case 'Home':
324
+ newValue = min;
325
+ break;
326
+ // Set the slider to the last allowed value in its range.
327
+ case 'End':
328
+ newValue = max;
329
+ break;
330
+ default:
331
+ return;
332
+ }
333
+
334
+ newValue = clamp(
335
+ step ? roundValueToStep(newValue, step, min) : newValue,
336
+ min,
337
+ max
338
+ );
339
+ onChange(event, newValue);
340
+ });
341
+
342
+ const ariaValueText = getAriaValueText
343
+ ? getAriaValueText(value)
344
+ : ariaValueTextProp;
345
+
346
+ const rangeStyle = { [isVertical ? 'height' : 'width']: `${trackPercent}%` };
347
+
348
+ // Slide events!
349
+ // We will try to use pointer events if they are supported to leverage
350
+ // setPointerCapture and releasePointerCapture. We'll fall back to separate
351
+ // mouse and touch events.
352
+ // TODO: This could be more concise
353
+ const removeMoveEvents = useRef<() => void>(noop);
354
+ const removeStartEvents = useRef<() => void>(noop);
355
+ const removeEndEvents = useRef<() => void>(noop);
356
+
357
+ // Store our event handlers in refs so we aren't attaching/detaching events
358
+ // on every render if the user doesn't useCallback
359
+ const appEvents = useRef({
360
+ onMouseMove,
361
+ onMouseDown,
362
+ onMouseUp,
363
+ onTouchStart,
364
+ onTouchEnd,
365
+ onTouchMove,
366
+ onPointerDown,
367
+ onPointerUp,
368
+ });
369
+ useLayoutEffect(() => {
370
+ appEvents.current.onMouseMove = onMouseMove;
371
+ appEvents.current.onMouseDown = onMouseDown;
372
+ appEvents.current.onMouseUp = onMouseUp;
373
+ appEvents.current.onTouchStart = onTouchStart;
374
+ appEvents.current.onTouchEnd = onTouchEnd;
375
+ appEvents.current.onTouchMove = onTouchMove;
376
+ appEvents.current.onPointerDown = onPointerDown;
377
+ appEvents.current.onPointerUp = onPointerUp;
378
+ }, [
379
+ onMouseMove,
380
+ onMouseDown,
381
+ onMouseUp,
382
+ onTouchStart,
383
+ onTouchEnd,
384
+ onTouchMove,
385
+ onPointerDown,
386
+ onPointerUp,
387
+ ]);
388
+
389
+ const handleSlideStart = useStableLayoutCallback(
390
+ (event: SomePointerEvent) => {
391
+ if (isRightClick(event)) return;
392
+
393
+ if (disabled) {
394
+ pointerDownRef.current = false;
395
+ return;
396
+ }
397
+
398
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
399
+ const ownerDocument = getOwnerDocument(sliderRef.current)!;
400
+ const ownerWindow = ownerDocument.defaultView || window;
401
+ pointerDownRef.current = true;
402
+
403
+ if ((event as TouchEvent).changedTouches) {
404
+ // Prevent scrolling for touch events
405
+ event.preventDefault();
406
+ const touch = (event as TouchEvent).changedTouches?.[0];
407
+ if (touch != null) {
408
+ touchId.current = touch.identifier;
409
+ }
410
+ }
411
+
412
+ const newValue = getNewValueFromEvent(event);
413
+ if (newValue == null) {
414
+ return;
415
+ }
416
+ ownerWindow.requestAnimationFrame(() => handleRef.current?.focus());
417
+ onChange(event, newValue);
418
+
419
+ removeMoveEvents.current = addMoveListener();
420
+ removeEndEvents.current = addEndListener();
421
+ }
422
+ );
423
+
424
+ const setPointerCapture = useStableLayoutCallback((event: PointerEvent) => {
425
+ if (isRightClick(event)) return;
426
+ if (disabled) {
427
+ pointerDownRef.current = false;
428
+ return;
429
+ }
430
+ pointerDownRef.current = true;
431
+ sliderRef.current?.setPointerCapture(event.pointerId);
432
+ });
433
+
434
+ const releasePointerCapture = useStableLayoutCallback(
435
+ (event: PointerEvent) => {
436
+ if (isRightClick(event)) return;
437
+ sliderRef.current?.releasePointerCapture(event.pointerId);
438
+ pointerDownRef.current = false;
439
+ }
440
+ );
441
+
442
+ const handlePointerMove = useStableLayoutCallback(
443
+ (event: SomePointerEvent) => {
444
+ if (disabled || !pointerDownRef.current) {
445
+ pointerDownRef.current = false;
446
+ return;
447
+ }
448
+
449
+ const newValue = getNewValueFromEvent(event);
450
+ if (newValue == null) {
451
+ return;
452
+ }
453
+ onChange?.(event, newValue);
454
+ }
455
+ );
456
+
457
+ const handleSlideStop = useStableLayoutCallback((event: SomePointerEvent) => {
458
+ if (isRightClick(event)) return;
459
+
460
+ pointerDownRef.current = false;
461
+
462
+ const newValue = getNewValueFromEvent(event);
463
+ if (newValue == null) {
464
+ return;
465
+ }
466
+
467
+ touchId.current = undefined;
468
+
469
+ removeMoveEvents.current();
470
+ removeEndEvents.current();
471
+ });
472
+
473
+ const addMoveListener = useCallback(() => {
474
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
475
+ const ownerDocument = getOwnerDocument(sliderRef.current)!;
476
+ const touchListener = wrapEvent(
477
+ appEvents.current.onTouchMove,
478
+ handlePointerMove
479
+ );
480
+ const mouseListener = wrapEvent(
481
+ appEvents.current.onMouseMove,
482
+ handlePointerMove
483
+ );
484
+ ownerDocument.addEventListener('touchmove', touchListener);
485
+ ownerDocument.addEventListener('mousemove', mouseListener);
486
+ return () => {
487
+ ownerDocument.removeEventListener('touchmove', touchListener);
488
+ ownerDocument.removeEventListener('mousemove', mouseListener);
489
+ };
490
+ }, [handlePointerMove]);
491
+
492
+ const addEndListener = useCallback(() => {
493
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
494
+ const ownerDocument = getOwnerDocument(sliderRef.current)!;
495
+ const ownerWindow = ownerDocument.defaultView || window;
496
+ const pointerListener = wrapEvent(
497
+ appEvents.current.onPointerUp,
498
+ releasePointerCapture
499
+ );
500
+ const touchListener = wrapEvent(
501
+ appEvents.current.onTouchEnd,
502
+ handleSlideStop
503
+ );
504
+ const mouseListener = wrapEvent(
505
+ appEvents.current.onMouseUp,
506
+ handleSlideStop
507
+ );
508
+ if ('PointerEvent' in ownerWindow) {
509
+ ownerDocument.addEventListener('pointerup', pointerListener);
510
+ }
511
+ ownerDocument.addEventListener('touchend', touchListener);
512
+ ownerDocument.addEventListener('mouseup', mouseListener);
513
+ return () => {
514
+ if ('PointerEvent' in ownerWindow) {
515
+ ownerDocument.removeEventListener('pointerup', pointerListener);
516
+ }
517
+ ownerDocument.removeEventListener('touchend', touchListener);
518
+ ownerDocument.removeEventListener('mouseup', mouseListener);
519
+ };
520
+ }, [handleSlideStop, releasePointerCapture]);
521
+
522
+ const addStartListener = useCallback(() => {
523
+ // e.preventDefault is ignored by React's synthetic touchStart event, so
524
+ // we attach the listener directly to the DOM node
525
+ // https://github.com/facebook/react/issues/9809#issuecomment-413978405
526
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
527
+ const sliderElement = sliderRef.current!;
528
+ if (!sliderElement) {
529
+ return noop;
530
+ }
531
+
532
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
533
+ const ownerDocument = getOwnerDocument(sliderElement)!;
534
+ const ownerWindow = ownerDocument.defaultView || window;
535
+ const touchListener = wrapEvent(
536
+ appEvents.current.onTouchStart,
537
+ handleSlideStart
538
+ );
539
+ const mouseListener = wrapEvent(
540
+ appEvents.current.onMouseDown,
541
+ handleSlideStart
542
+ );
543
+ const pointerListener = wrapEvent(
544
+ appEvents.current.onPointerDown,
545
+ setPointerCapture
546
+ );
547
+ sliderElement.addEventListener('touchstart', touchListener);
548
+ sliderElement.addEventListener('mousedown', mouseListener);
549
+ if ('PointerEvent' in ownerWindow) {
550
+ sliderElement.addEventListener('pointerdown', pointerListener);
551
+ }
552
+ return () => {
553
+ sliderElement.removeEventListener('touchstart', touchListener);
554
+ sliderElement.removeEventListener('mousedown', mouseListener);
555
+ if ('PointerEvent' in ownerWindow) {
556
+ sliderElement.removeEventListener('pointerdown', pointerListener);
557
+ }
558
+ };
559
+ }, [setPointerCapture, handleSlideStart]);
560
+
561
+ useEffect(() => {
562
+ removeStartEvents.current = addStartListener();
563
+
564
+ return () => {
565
+ removeStartEvents.current();
566
+ removeEndEvents.current();
567
+ removeMoveEvents.current();
568
+ };
569
+ }, [addStartListener]);
570
+
571
+ const inputFallbackId = useId();
572
+ const inputId = id || inputFallbackId;
573
+
574
+ return (
575
+ <SliderProvider
576
+ ariaLabel={getAriaLabel ? getAriaLabel(value) : ariaLabel}
577
+ ariaLabelledBy={ariaLabelledBy}
578
+ ariaValueText={ariaValueText}
579
+ handleDimensions={handleDimensions}
580
+ handleKeyDown={handleKeyDown}
581
+ handlePosition={handlePosition}
582
+ handleRef={handleRef}
583
+ hasFocus={hasFocus}
584
+ onKeyDown={onKeyDown}
585
+ setHasFocus={setHasFocus}
586
+ sliderId={id}
587
+ sliderMax={max}
588
+ sliderMin={min}
589
+ value={value}
590
+ disabled={!!disabled}
591
+ isVertical={isVertical}
592
+ orientation={orientation}
593
+ trackPercent={trackPercent}
594
+ trackRef={trackRef}
595
+ rangeStyle={rangeStyle}
596
+ updateValue={onChange}
597
+ >
598
+ <Comp
599
+ {...rest}
600
+ // @ts-ignore
601
+ as={innerAs}
602
+ ref={assignMultipleRefs(sliderRef, forwardedRef)}
603
+ data-reach-slider-input=""
604
+ data-disabled={disabled ? '' : undefined}
605
+ data-orientation={orientation}
606
+ tabIndex={-1}
607
+ >
608
+ {typeof children === 'function'
609
+ ? children({
610
+ hasFocus,
611
+ id,
612
+ max,
613
+ min,
614
+ value,
615
+ ariaValueText,
616
+ })
617
+ : children}
618
+ {name && (
619
+ // If the slider is used in a form we'll need an input field to
620
+ // capture the value. We'll assume this when the component is given a
621
+ // form field name (A `name` prop doesn't really make sense in any
622
+ // other context).
623
+ <input type="hidden" value={value} name={name} id={inputId} />
624
+ )}
625
+ </Comp>
626
+ </SliderProvider>
627
+ );
628
+ }) as Polymorphic.ForwardRefComponent<'div', SliderInputProps>;
629
+
630
+ /**
631
+ * @see Docs https://reach.tech/slider#sliderinput-props
632
+ */
633
+ type SliderInputProps = Omit<SliderProps, 'children'> & {
634
+ /**
635
+ * Slider expects `<SliderTrack>` as its child; The track will accept all
636
+ * additional slider sub-components as children. It can also accept a
637
+ * function/render prop as its child to expose some of its internal state
638
+ * variables.
639
+ *
640
+ * @see Docs https://reach.tech/slider#sliderinput-children
641
+ */
642
+ children: ReactNode | SliderChildrenRender;
643
+ };
644
+
645
+ ////////////////////////////////////////////////////////////////////////////////
646
+
647
+ /**
648
+ * SliderTrack
649
+ *
650
+ * @see Docs https://reach.tech/slider#slidertrack
651
+ */
652
+ const SliderTrackImpl = forwardRef(function SliderTrack(
653
+ { as: Comp = 'div', innerAs, children, style = {}, ...props },
654
+ forwardedRef
655
+ ) {
656
+ const { disabled, orientation, trackRef } = useSliderContext('SliderTrack');
657
+
658
+ return (
659
+ <Comp
660
+ ref={assignMultipleRefs(trackRef, forwardedRef)}
661
+ // @ts-ignore
662
+ as={innerAs}
663
+ style={{ ...style, position: 'relative' }}
664
+ {...props}
665
+ data-reach-slider-track=""
666
+ data-disabled={disabled ? '' : undefined}
667
+ data-orientation={orientation}
668
+ >
669
+ {children}
670
+ </Comp>
671
+ );
672
+ }) as Polymorphic.ForwardRefComponent<'div', SliderTrackProps>;
673
+
674
+ const SliderTrack = memo(SliderTrackImpl) as Polymorphic.MemoComponent<
675
+ 'div',
676
+ SliderTrackProps
677
+ >;
678
+
679
+ /**
680
+ * @see Docs https://reach.tech/slider#slidertrack-props
681
+ */
682
+ interface SliderTrackProps {
683
+ /**
684
+ * `SliderTrack` expects `<SliderHandle>`, at minimum, for the Slider to
685
+ * function. All other Slider subcomponents should be passed as children
686
+ * inside the `SliderTrack`.
687
+ *
688
+ * @see Docs https://reach.tech/slider#slidertrack-children
689
+ */
690
+ children: ReactNode;
691
+ }
692
+
693
+ ////////////////////////////////////////////////////////////////////////////////
694
+
695
+ /**
696
+ * SliderRange
697
+ *
698
+ * The (typically) highlighted portion of the track that represents the space
699
+ * between the slider's `min` value and its current value.
700
+ *
701
+ * @see Docs https://reach.tech/slider#sliderrange
702
+ */
703
+ const SliderRangeImpl = forwardRef(function SliderRange(
704
+ { as: Comp = 'div', innerAs, children, style = {}, ...props },
705
+ forwardedRef
706
+ ) {
707
+ const { disabled, orientation, rangeStyle } = useSliderContext('SliderRange');
708
+ return (
709
+ <Comp
710
+ ref={forwardedRef}
711
+ // @ts-ignore
712
+ as={innerAs}
713
+ style={{ position: 'absolute', ...rangeStyle, ...style }}
714
+ {...props}
715
+ data-reach-slider-range=""
716
+ data-disabled={disabled ? '' : undefined}
717
+ data-orientation={orientation}
718
+ />
719
+ );
720
+ }) as Polymorphic.ForwardRefComponent<'div', SliderRangeProps>;
721
+
722
+ const SliderRange = memo(SliderRangeImpl) as Polymorphic.MemoComponent<
723
+ 'div',
724
+ SliderRangeProps
725
+ >;
726
+
727
+ /**
728
+ * `SliderRange` accepts any props that a HTML div component accepts.
729
+ * `SliderRange` will not accept or render any children.
730
+ *
731
+ * @see Docs https://reach.tech/slider#sliderrange-props
732
+ */
733
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
734
+ interface SliderRangeProps {}
735
+
736
+ ////////////////////////////////////////////////////////////////////////////////
737
+
738
+ /**
739
+ * SliderHandle
740
+ *
741
+ * The handle that the user drags along the track to set the slider value.
742
+ *
743
+ * @see Docs https://reach.tech/slider#sliderhandle
744
+ */
745
+ const SliderHandleImpl = forwardRef(function SliderHandle(
746
+ {
747
+ // min,
748
+ // max,
749
+ as: Comp = 'div',
750
+ innerAs,
751
+ onBlur,
752
+ onFocus,
753
+ style = {},
754
+ onKeyDown,
755
+ ...props
756
+ },
757
+ forwardedRef
758
+ ) {
759
+ const {
760
+ ariaLabel,
761
+ ariaLabelledBy,
762
+ ariaValueText,
763
+ disabled,
764
+ handlePosition,
765
+ handleRef,
766
+ isVertical,
767
+ handleKeyDown,
768
+ orientation,
769
+ setHasFocus,
770
+ sliderMin,
771
+ sliderMax,
772
+ value,
773
+ } = useSliderContext('SliderHandle');
774
+
775
+ return (
776
+ <Comp
777
+ // @ts-ignore
778
+ as={innerAs}
779
+ aria-disabled={disabled || undefined}
780
+ // If the slider has a visible label, it is referenced by
781
+ // `aria-labelledby` on the slider element. Otherwise, the slider
782
+ // element has a label provided by `aria-label`.
783
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
784
+ aria-label={ariaLabel}
785
+ aria-labelledby={ariaLabel ? undefined : ariaLabelledBy}
786
+ // If the slider is vertically oriented, it has `aria-orientation` set
787
+ // to vertical. The default value of `aria-orientation` for a slider is
788
+ // horizontal.
789
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
790
+ aria-orientation={orientation}
791
+ // The slider element has the `aria-valuemax` property set to a decimal
792
+ // value representing the maximum allowed value of the slider.
793
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
794
+ aria-valuemax={sliderMax}
795
+ // The slider element has the `aria-valuemin` property set to a decimal
796
+ // value representing the minimum allowed value of the slider.
797
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
798
+ aria-valuemin={sliderMin}
799
+ // The slider element has the `aria-valuenow` property set to a decimal
800
+ // value representing the current value of the slider.
801
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
802
+ aria-valuenow={value}
803
+ // If the value of `aria-valuenow` is not user-friendly, e.g., the day
804
+ // of the week is represented by a number, the `aria-valuetext` property
805
+ // is set to a string that makes the slider value understandable, e.g.,
806
+ // "Monday".
807
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
808
+ aria-valuetext={ariaValueText}
809
+ // The element serving as the focusable slider control has role
810
+ // `slider`.
811
+ // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
812
+ role="slider"
813
+ tabIndex={disabled ? -1 : 0}
814
+ {...props}
815
+ data-reach-slider-handle=""
816
+ ref={assignMultipleRefs(handleRef, forwardedRef)}
817
+ onBlur={wrapEvent(onBlur, () => {
818
+ setHasFocus(false);
819
+ })}
820
+ onFocus={wrapEvent(onFocus, () => {
821
+ setHasFocus(true);
822
+ })}
823
+ onKeyDown={wrapEvent(onKeyDown, handleKeyDown)}
824
+ style={{
825
+ position: 'absolute',
826
+ ...(isVertical ? { bottom: handlePosition } : { left: handlePosition }),
827
+ ...style,
828
+ }}
829
+ />
830
+ );
831
+ }) as Polymorphic.ForwardRefComponent<'div', SliderHandleProps>;
832
+
833
+ const SliderHandle = memo(SliderHandleImpl) as Polymorphic.MemoComponent<
834
+ 'div',
835
+ SliderHandleProps
836
+ >;
837
+
838
+ /**
839
+ * `SliderRange` accepts any props that a HTML div component accepts.
840
+ *
841
+ * @see Docs https://reach.tech/slider#sliderhandle-props
842
+ */
843
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
844
+ interface SliderHandleProps {}
845
+
846
+ ////////////////////////////////////////////////////////////////////////////////
847
+
848
+ /**
849
+ * SliderMarker
850
+ *
851
+ * A fixed value marker. These can be used to illustrate a range of steps or
852
+ * highlight important points along the slider track.
853
+ *
854
+ * @see Docs https://reach.tech/slider#slidermarker
855
+ */
856
+ const SliderMarkerImpl = forwardRef(function SliderMarker(
857
+ { as: Comp = 'div', innerAs, children, style = {}, value, ...props },
858
+ forwardedRef
859
+ ) {
860
+ const {
861
+ disabled,
862
+ isVertical,
863
+ orientation,
864
+ sliderMin,
865
+ sliderMax,
866
+ value: sliderValue,
867
+ } = useSliderContext('SliderMarker');
868
+
869
+ const inRange = value >= sliderMin && value <= sliderMax;
870
+ const absoluteStartPosition = `${valueToPercent(
871
+ value,
872
+ sliderMin,
873
+ sliderMax
874
+ )}%`;
875
+
876
+ const state =
877
+ value < sliderValue
878
+ ? 'under-value'
879
+ : value === sliderValue
880
+ ? 'at-value'
881
+ : 'over-value';
882
+
883
+ return inRange ? (
884
+ <Comp
885
+ ref={forwardedRef}
886
+ // @ts-ignore
887
+ as={innerAs}
888
+ style={{
889
+ position: 'absolute',
890
+ ...(isVertical
891
+ ? { bottom: absoluteStartPosition }
892
+ : { left: absoluteStartPosition }),
893
+ ...style,
894
+ }}
895
+ {...props}
896
+ data-reach-slider-marker=""
897
+ data-disabled={disabled ? '' : undefined}
898
+ data-orientation={orientation}
899
+ data-state={state}
900
+ data-value={value}
901
+ >
902
+ {children}
903
+ </Comp>
904
+ ) : null;
905
+ }) as Polymorphic.ForwardRefComponent<'div', SliderMarkerProps>;
906
+
907
+ const SliderMarker = memo(SliderMarkerImpl) as Polymorphic.MemoComponent<
908
+ 'div',
909
+ SliderMarkerProps
910
+ >;
911
+
912
+ /**
913
+ * @see Docs https://reach.tech/slider#slidermarker-props
914
+ */
915
+ interface SliderMarkerProps {
916
+ /**
917
+ * The value to denote where the marker should appear along the track.
918
+ *
919
+ * @see Docs https://reach.tech/slider#slidermarker-value
920
+ */
921
+ value: number;
922
+ }
923
+
924
+ ////////////////////////////////////////////////////////////////////////////////
925
+
926
+ function clamp(val: number, min: number, max: number) {
927
+ return val > max ? max : val < min ? min : val;
928
+ }
929
+
930
+ /**
931
+ * This handles the case when num is very small (0.00000001), js will turn
932
+ * this into 1e-8. When num is bigger than 1 or less than -1 it won't get
933
+ * converted to this notation so it's fine.
934
+ *
935
+ * @param num
936
+ * @see https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Slider/Slider.js#L69
937
+ */
938
+ function getDecimalPrecision(num: number) {
939
+ if (Math.abs(num) < 1) {
940
+ const parts = num.toExponential().split('e-');
941
+ const matissaDecimalPart = parts[0].split('.')[1];
942
+ return (
943
+ (matissaDecimalPart ? matissaDecimalPart.length : 0) +
944
+ parseInt(parts[1], 10)
945
+ );
946
+ }
947
+
948
+ const decimalPart = num.toString().split('.')[1];
949
+ return decimalPart ? decimalPart.length : 0;
950
+ }
951
+
952
+ function percentToValue(percent: number, min: number, max: number) {
953
+ return (max - min) * percent + min;
954
+ }
955
+
956
+ function roundValueToStep(value: number, step: number, min: number) {
957
+ const nearest = Math.round((value - min) / step) * step + min;
958
+ return Number(nearest.toFixed(getDecimalPrecision(step)));
959
+ }
960
+
961
+ function getPointerPosition(event: SomePointerEvent, touchId: TouchIdRef) {
962
+ if (touchId.current !== undefined && (event as TouchEvent).changedTouches) {
963
+ for (let i = 0; i < (event as TouchEvent).changedTouches.length; i += 1) {
964
+ const touch = (event as TouchEvent).changedTouches[i];
965
+ if (touch.identifier === touchId.current) {
966
+ return {
967
+ x: touch.clientX,
968
+ y: touch.clientY,
969
+ };
970
+ }
971
+ }
972
+
973
+ return false;
974
+ }
975
+
976
+ return {
977
+ x: (event as PointerEvent | MouseEvent).clientX,
978
+ y: (event as PointerEvent | MouseEvent).clientY,
979
+ };
980
+ }
981
+
982
+ function getNewValue(
983
+ handlePosition:
984
+ | {
985
+ x: number;
986
+ y: number;
987
+ }
988
+ | false,
989
+ track: HTMLElement | null,
990
+ props: {
991
+ orientation: SliderOrientation;
992
+ min: number;
993
+ max: number;
994
+ step?: number;
995
+ }
996
+ ) {
997
+ const { orientation, min, max, step } = props;
998
+
999
+ if (!track || !handlePosition) {
1000
+ return null;
1001
+ }
1002
+
1003
+ const { left, width, bottom, height } = track.getBoundingClientRect();
1004
+ const isVertical = orientation === 'vertical';
1005
+ const diff = isVertical ? bottom - handlePosition.y : handlePosition.x - left;
1006
+ const percent = diff / (isVertical ? height : width);
1007
+ const newValue = percentToValue(percent, min, max);
1008
+
1009
+ return clamp(
1010
+ step ? roundValueToStep(newValue, step, min) : newValue,
1011
+ min,
1012
+ max
1013
+ );
1014
+ }
1015
+
1016
+ function useDimensions(ref: React.RefObject<HTMLElement | null>) {
1017
+ const [{ width, height }, setDimensions] = useState({
1018
+ width: 0,
1019
+ height: 0,
1020
+ });
1021
+ // Many existing `useDimensions` type hooks will use `getBoundingClientRect`
1022
+ // getBoundingClientRect does not work here when borders are applied.
1023
+ // getComputedStyle is not as performant so we may want to create a utility to
1024
+ // check for any conflicts with box sizing first and only use
1025
+ // `getComputedStyle` if neccessary.
1026
+ /* const { width, height } = ref.current
1027
+ ? ref.current.getBoundingClientRect()
1028
+ : 0; */
1029
+
1030
+ useLayoutEffect(() => {
1031
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1032
+ const ownerDocument = getOwnerDocument(ref.current)!;
1033
+ const ownerWindow = ownerDocument.defaultView || window;
1034
+ if (ref.current) {
1035
+ const { height: _newHeight, width: _newWidth } =
1036
+ ownerWindow.getComputedStyle(ref.current);
1037
+ const newHeight = parseFloat(_newHeight);
1038
+ const newWidth = parseFloat(_newWidth);
1039
+
1040
+ if (newHeight !== height || newWidth !== width) {
1041
+ setDimensions({ height: newHeight, width: newWidth });
1042
+ }
1043
+ }
1044
+ }, [ref, width, height]);
1045
+ return { ref, width, height };
1046
+ }
1047
+
1048
+ function valueToPercent(value: number, min: number, max: number) {
1049
+ return ((value - min) * 100) / (max - min);
1050
+ }
1051
+
1052
+ ////////////////////////////////////////////////////////////////////////////////
1053
+ // Types
1054
+
1055
+ type TrackRef = React.RefObject<HTMLDivElement | null>;
1056
+ type HandleRef = React.RefObject<HTMLDivElement | null>;
1057
+ type SliderRef = React.RefObject<HTMLDivElement | null>;
1058
+ type TouchIdRef = React.MutableRefObject<number | undefined>;
1059
+
1060
+ type SomePointerEvent = TouchEvent | MouseEvent;
1061
+
1062
+ interface ISliderContext {
1063
+ ariaLabel: string | undefined;
1064
+ ariaLabelledBy: string | undefined;
1065
+ ariaValueText: string | undefined;
1066
+ handleDimensions: {
1067
+ width: number;
1068
+ height: number;
1069
+ };
1070
+ handlePosition: string;
1071
+ handleRef: HandleRef;
1072
+ hasFocus: boolean;
1073
+ onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
1074
+ handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
1075
+ setHasFocus: React.Dispatch<React.SetStateAction<boolean>>;
1076
+ sliderId: string | undefined;
1077
+ sliderMax: number;
1078
+ sliderMin: number;
1079
+ value: number;
1080
+ disabled: boolean;
1081
+ isVertical: boolean;
1082
+ orientation: SliderOrientation;
1083
+ trackPercent: number;
1084
+ trackRef: TrackRef;
1085
+ rangeStyle: React.CSSProperties;
1086
+ updateValue: (e: SomePointerEvent | KeyboardEvent, newValue: any) => void;
1087
+ }
1088
+
1089
+ type SliderChildrenRender = (props: {
1090
+ ariaValueText?: string | undefined;
1091
+ hasFocus?: boolean;
1092
+ id?: string | undefined;
1093
+ sliderId?: string | undefined;
1094
+ max?: number;
1095
+ min?: number;
1096
+ value?: number;
1097
+ }) => JSX.Element;
1098
+
1099
+ ////////////////////////////////////////////////////////////////////////////////
1100
+ // Exports
1101
+
1102
+ export default Slider;
1103
+ export type {
1104
+ SliderHandleProps,
1105
+ SliderInputProps,
1106
+ SliderMarkerProps,
1107
+ SliderProps,
1108
+ SliderRangeProps,
1109
+ SliderTrackProps,
1110
+ SliderHandleAlignment,
1111
+ SliderOrientation,
1112
+ };
1113
+ export {
1114
+ Slider,
1115
+ SliderHandle,
1116
+ SliderInput,
1117
+ SliderMarker,
1118
+ SliderTrack,
1119
+ SliderRange,
1120
+ };