@allxsmith/bestax-bulma 5.27.0 → 5.27.2

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 (348) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/columns/Column.js +48 -0
  4. package/dist/esm/columns/Column.js.map +1 -0
  5. package/dist/esm/columns/Columns.js +53 -0
  6. package/dist/esm/columns/Columns.js.map +1 -0
  7. package/dist/esm/components/Avatar.js +299 -0
  8. package/dist/esm/components/Avatar.js.map +1 -0
  9. package/dist/esm/components/Avatars.js +81 -0
  10. package/dist/esm/components/Avatars.js.map +1 -0
  11. package/dist/esm/components/Badge.js +105 -0
  12. package/dist/esm/components/Badge.js.map +1 -0
  13. package/dist/esm/components/Breadcrumb.js +36 -0
  14. package/dist/esm/components/Breadcrumb.js.map +1 -0
  15. package/dist/esm/components/Card.js +252 -0
  16. package/dist/esm/components/Card.js.map +1 -0
  17. package/dist/esm/components/Carousel.js +351 -0
  18. package/dist/esm/components/Carousel.js.map +1 -0
  19. package/dist/esm/components/Collapse.js +193 -0
  20. package/dist/esm/components/Collapse.js.map +1 -0
  21. package/dist/esm/components/Collapses.js +99 -0
  22. package/dist/esm/components/Collapses.js.map +1 -0
  23. package/dist/esm/components/Dialog.js +230 -0
  24. package/dist/esm/components/Dialog.js.map +1 -0
  25. package/dist/esm/components/Dropdown.js +362 -0
  26. package/dist/esm/components/Dropdown.js.map +1 -0
  27. package/dist/esm/components/Loading.js +87 -0
  28. package/dist/esm/components/Loading.js.map +1 -0
  29. package/dist/esm/components/Menu.js +117 -0
  30. package/dist/esm/components/Menu.js.map +1 -0
  31. package/dist/esm/components/Message.js +56 -0
  32. package/dist/esm/components/Message.js.map +1 -0
  33. package/dist/esm/components/Modal.js +354 -0
  34. package/dist/esm/components/Modal.js.map +1 -0
  35. package/dist/esm/components/Navbar.js +287 -0
  36. package/dist/esm/components/Navbar.js.map +1 -0
  37. package/dist/esm/components/Pagination.js +191 -0
  38. package/dist/esm/components/Pagination.js.map +1 -0
  39. package/dist/esm/components/Panel.js +100 -0
  40. package/dist/esm/components/Panel.js.map +1 -0
  41. package/dist/esm/components/Popover.js +298 -0
  42. package/dist/esm/components/Popover.js.map +1 -0
  43. package/dist/esm/components/Reveal.js +157 -0
  44. package/dist/esm/components/Reveal.js.map +1 -0
  45. package/dist/esm/components/Sidebar.js +209 -0
  46. package/dist/esm/components/Sidebar.js.map +1 -0
  47. package/dist/esm/components/Steps.js +128 -0
  48. package/dist/esm/components/Steps.js.map +1 -0
  49. package/dist/esm/components/Tabs.js +335 -0
  50. package/dist/esm/components/Tabs.js.map +1 -0
  51. package/dist/esm/components/Toast.js +322 -0
  52. package/dist/esm/components/Toast.js.map +1 -0
  53. package/dist/esm/components/Tooltip.js +202 -0
  54. package/dist/esm/components/Tooltip.js.map +1 -0
  55. package/dist/esm/components/collapsesContext.js +12 -0
  56. package/dist/esm/components/collapsesContext.js.map +1 -0
  57. package/dist/esm/elements/Block.js +29 -0
  58. package/dist/esm/elements/Block.js.map +1 -0
  59. package/dist/esm/elements/Box.js +32 -0
  60. package/dist/esm/elements/Box.js.map +1 -0
  61. package/dist/esm/elements/Button.js +139 -0
  62. package/dist/esm/elements/Button.js.map +1 -0
  63. package/dist/esm/elements/Buttons.js +42 -0
  64. package/dist/esm/elements/Buttons.js.map +1 -0
  65. package/dist/esm/elements/Code.js +25 -0
  66. package/dist/esm/elements/Code.js.map +1 -0
  67. package/dist/esm/elements/Content.js +33 -0
  68. package/dist/esm/elements/Content.js.map +1 -0
  69. package/dist/esm/elements/Delete.js +35 -0
  70. package/dist/esm/elements/Delete.js.map +1 -0
  71. package/dist/esm/elements/Divider.js +27 -0
  72. package/dist/esm/elements/Divider.js.map +1 -0
  73. package/dist/esm/elements/Emphasis.js +25 -0
  74. package/dist/esm/elements/Emphasis.js.map +1 -0
  75. package/dist/esm/elements/Figure.js +58 -0
  76. package/dist/esm/elements/Figure.js.map +1 -0
  77. package/dist/esm/elements/Icon.js +256 -0
  78. package/dist/esm/elements/Icon.js.map +1 -0
  79. package/dist/esm/elements/IconText.js +36 -0
  80. package/dist/esm/elements/IconText.js.map +1 -0
  81. package/dist/esm/elements/Image.js +46 -0
  82. package/dist/esm/elements/Image.js.map +1 -0
  83. package/dist/esm/elements/Link.js +36 -0
  84. package/dist/esm/elements/Link.js.map +1 -0
  85. package/dist/esm/elements/LinkButton.js +37 -0
  86. package/dist/esm/elements/LinkButton.js.map +1 -0
  87. package/dist/esm/elements/ListItem.js +28 -0
  88. package/dist/esm/elements/ListItem.js.map +1 -0
  89. package/dist/esm/elements/Loader.js +37 -0
  90. package/dist/esm/elements/Loader.js.map +1 -0
  91. package/dist/esm/elements/Notification.js +316 -0
  92. package/dist/esm/elements/Notification.js.map +1 -0
  93. package/dist/esm/elements/OrderedList.js +31 -0
  94. package/dist/esm/elements/OrderedList.js.map +1 -0
  95. package/dist/esm/elements/Paragraph.js +28 -0
  96. package/dist/esm/elements/Paragraph.js.map +1 -0
  97. package/dist/esm/elements/Pre.js +25 -0
  98. package/dist/esm/elements/Pre.js.map +1 -0
  99. package/dist/esm/elements/Progress.js +33 -0
  100. package/dist/esm/elements/Progress.js.map +1 -0
  101. package/dist/esm/elements/Skeleton.js +30 -0
  102. package/dist/esm/elements/Skeleton.js.map +1 -0
  103. package/dist/esm/elements/Span.js +28 -0
  104. package/dist/esm/elements/Span.js.map +1 -0
  105. package/dist/esm/elements/Strong.js +25 -0
  106. package/dist/esm/elements/Strong.js.map +1 -0
  107. package/dist/esm/elements/SubTitle.js +51 -0
  108. package/dist/esm/elements/SubTitle.js.map +1 -0
  109. package/dist/esm/elements/Table.js +51 -0
  110. package/dist/esm/elements/Table.js.map +1 -0
  111. package/dist/esm/elements/Tag.js +49 -0
  112. package/dist/esm/elements/Tag.js.map +1 -0
  113. package/dist/esm/elements/Tags.js +34 -0
  114. package/dist/esm/elements/Tags.js.map +1 -0
  115. package/dist/esm/elements/Tbody.js +26 -0
  116. package/dist/esm/elements/Tbody.js.map +1 -0
  117. package/dist/esm/elements/Td.js +61 -0
  118. package/dist/esm/elements/Td.js.map +1 -0
  119. package/dist/esm/elements/Tfoot.js +26 -0
  120. package/dist/esm/elements/Tfoot.js.map +1 -0
  121. package/dist/esm/elements/Th.js +34 -0
  122. package/dist/esm/elements/Th.js.map +1 -0
  123. package/dist/esm/elements/Thead.js +26 -0
  124. package/dist/esm/elements/Thead.js.map +1 -0
  125. package/dist/esm/elements/Title.js +44 -0
  126. package/dist/esm/elements/Title.js.map +1 -0
  127. package/dist/esm/elements/Tr.js +31 -0
  128. package/dist/esm/elements/Tr.js.map +1 -0
  129. package/dist/esm/elements/UnorderedList.js +31 -0
  130. package/dist/esm/elements/UnorderedList.js.map +1 -0
  131. package/dist/esm/elements/iconProps.js +49 -0
  132. package/dist/esm/elements/iconProps.js.map +1 -0
  133. package/dist/esm/form/Autocomplete.js +279 -0
  134. package/dist/esm/form/Autocomplete.js.map +1 -0
  135. package/dist/esm/form/Checkbox.js +111 -0
  136. package/dist/esm/form/Checkbox.js.map +1 -0
  137. package/dist/esm/form/Checkboxes.js +90 -0
  138. package/dist/esm/form/Checkboxes.js.map +1 -0
  139. package/dist/esm/form/Control.js +79 -0
  140. package/dist/esm/form/Control.js.map +1 -0
  141. package/dist/esm/form/DateInput.js +88 -0
  142. package/dist/esm/form/DateInput.js.map +1 -0
  143. package/dist/esm/form/DateInputBase.js +263 -0
  144. package/dist/esm/form/DateInputBase.js.map +1 -0
  145. package/dist/esm/form/DateRangeInput.js +77 -0
  146. package/dist/esm/form/DateRangeInput.js.map +1 -0
  147. package/dist/esm/form/DateRangeInputBase.js +301 -0
  148. package/dist/esm/form/DateRangeInputBase.js.map +1 -0
  149. package/dist/esm/form/DateTimeInput.js +88 -0
  150. package/dist/esm/form/DateTimeInput.js.map +1 -0
  151. package/dist/esm/form/DateTimeInputBase.js +333 -0
  152. package/dist/esm/form/DateTimeInputBase.js.map +1 -0
  153. package/dist/esm/form/Field.js +167 -0
  154. package/dist/esm/form/Field.js.map +1 -0
  155. package/dist/esm/form/File.js +112 -0
  156. package/dist/esm/form/File.js.map +1 -0
  157. package/dist/esm/form/FormContext.js +194 -0
  158. package/dist/esm/form/FormContext.js.map +1 -0
  159. package/dist/esm/form/Input.js +88 -0
  160. package/dist/esm/form/Input.js.map +1 -0
  161. package/dist/esm/form/InputBase.js +40 -0
  162. package/dist/esm/form/InputBase.js.map +1 -0
  163. package/dist/esm/form/Numberinput.js +201 -0
  164. package/dist/esm/form/Numberinput.js.map +1 -0
  165. package/dist/esm/form/Radio.js +108 -0
  166. package/dist/esm/form/Radio.js.map +1 -0
  167. package/dist/esm/form/Radios.js +92 -0
  168. package/dist/esm/form/Radios.js.map +1 -0
  169. package/dist/esm/form/Rate.js +280 -0
  170. package/dist/esm/form/Rate.js.map +1 -0
  171. package/dist/esm/form/Select.js +81 -0
  172. package/dist/esm/form/Select.js.map +1 -0
  173. package/dist/esm/form/SelectBase.js +54 -0
  174. package/dist/esm/form/SelectBase.js.map +1 -0
  175. package/dist/esm/form/Slider.js +461 -0
  176. package/dist/esm/form/Slider.js.map +1 -0
  177. package/dist/esm/form/Switch.js +96 -0
  178. package/dist/esm/form/Switch.js.map +1 -0
  179. package/dist/esm/form/Taginput.js +351 -0
  180. package/dist/esm/form/Taginput.js.map +1 -0
  181. package/dist/esm/form/TextArea.js +77 -0
  182. package/dist/esm/form/TextArea.js.map +1 -0
  183. package/dist/esm/form/TextAreaBase.js +45 -0
  184. package/dist/esm/form/TextAreaBase.js.map +1 -0
  185. package/dist/esm/form/TimeInput.js +84 -0
  186. package/dist/esm/form/TimeInput.js.map +1 -0
  187. package/dist/esm/form/TimeInputBase.js +246 -0
  188. package/dist/esm/form/TimeInputBase.js.map +1 -0
  189. package/dist/esm/form/_pickerInternals/Calendar.js +760 -0
  190. package/dist/esm/form/_pickerInternals/Calendar.js.map +1 -0
  191. package/dist/esm/form/_pickerInternals/PickerPopover.js +95 -0
  192. package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -0
  193. package/dist/esm/form/_pickerInternals/TimeWheels.js +721 -0
  194. package/dist/esm/form/_pickerInternals/TimeWheels.js.map +1 -0
  195. package/dist/esm/form/_pickerInternals/audioTick.js +109 -0
  196. package/dist/esm/form/_pickerInternals/audioTick.js.map +1 -0
  197. package/dist/esm/form/_pickerInternals/dateUtils.js +267 -0
  198. package/dist/esm/form/_pickerInternals/dateUtils.js.map +1 -0
  199. package/dist/esm/form/_pickerInternals/formatters.js +322 -0
  200. package/dist/esm/form/_pickerInternals/formatters.js.map +1 -0
  201. package/dist/esm/form/_pickerInternals/haptics.js +38 -0
  202. package/dist/esm/form/_pickerInternals/haptics.js.map +1 -0
  203. package/dist/esm/form/_pickerInternals/nativeInputSupport.js +25 -0
  204. package/dist/esm/form/_pickerInternals/nativeInputSupport.js.map +1 -0
  205. package/dist/esm/form/_pickerInternals/pickerTypes.js +50 -0
  206. package/dist/esm/form/_pickerInternals/pickerTypes.js.map +1 -0
  207. package/dist/esm/form/_pickerInternals/segmentMap.js +315 -0
  208. package/dist/esm/form/_pickerInternals/segmentMap.js.map +1 -0
  209. package/dist/esm/form/_pickerInternals/useNativeMobilePicker.js +49 -0
  210. package/dist/esm/form/_pickerInternals/useNativeMobilePicker.js.map +1 -0
  211. package/dist/esm/form/_pickerInternals/useSegmentedEntry.js +427 -0
  212. package/dist/esm/form/_pickerInternals/useSegmentedEntry.js.map +1 -0
  213. package/dist/esm/form/controlLoading.js +18 -0
  214. package/dist/esm/form/controlLoading.js.map +1 -0
  215. package/dist/esm/form/useAutoLabelId.js +88 -0
  216. package/dist/esm/form/useAutoLabelId.js.map +1 -0
  217. package/dist/esm/grid/Cell.js +35 -0
  218. package/dist/esm/grid/Cell.js.map +1 -0
  219. package/dist/esm/grid/Grid.js +51 -0
  220. package/dist/esm/grid/Grid.js.map +1 -0
  221. package/dist/esm/helpers/ClientOnly.js +33 -0
  222. package/dist/esm/helpers/ClientOnly.js.map +1 -0
  223. package/dist/esm/helpers/Config.js +60 -0
  224. package/dist/esm/helpers/Config.js.map +1 -0
  225. package/dist/esm/helpers/Theme.js +947 -0
  226. package/dist/esm/helpers/Theme.js.map +1 -0
  227. package/dist/esm/helpers/anchorAttrs.js +72 -0
  228. package/dist/esm/helpers/anchorAttrs.js.map +1 -0
  229. package/dist/esm/helpers/bulmaClassHelpers.js +415 -0
  230. package/dist/esm/helpers/bulmaClassHelpers.js.map +1 -0
  231. package/dist/esm/helpers/buttonType.js +24 -0
  232. package/dist/esm/helpers/buttonType.js.map +1 -0
  233. package/dist/esm/helpers/classNames.js +137 -0
  234. package/dist/esm/helpers/classNames.js.map +1 -0
  235. package/dist/esm/helpers/colorDeprecations.js +50 -0
  236. package/dist/esm/helpers/colorDeprecations.js.map +1 -0
  237. package/dist/esm/helpers/devWarnings.js +27 -0
  238. package/dist/esm/helpers/devWarnings.js.map +1 -0
  239. package/dist/esm/helpers/inertProps.js +27 -0
  240. package/dist/esm/helpers/inertProps.js.map +1 -0
  241. package/dist/esm/helpers/mergeBulmaStyles.js +20 -0
  242. package/dist/esm/helpers/mergeBulmaStyles.js.map +1 -0
  243. package/dist/esm/helpers/polymorphic.js +18 -0
  244. package/dist/esm/helpers/polymorphic.js.map +1 -0
  245. package/dist/esm/helpers/portal.js +76 -0
  246. package/dist/esm/helpers/portal.js.map +1 -0
  247. package/dist/esm/helpers/positionStacks.js +45 -0
  248. package/dist/esm/helpers/positionStacks.js.map +1 -0
  249. package/dist/esm/helpers/scrollLock.js +38 -0
  250. package/dist/esm/helpers/scrollLock.js.map +1 -0
  251. package/dist/esm/helpers/shadowDom.js +71 -0
  252. package/dist/esm/helpers/shadowDom.js.map +1 -0
  253. package/dist/esm/helpers/statusRegion.js +282 -0
  254. package/dist/esm/helpers/statusRegion.js.map +1 -0
  255. package/dist/esm/helpers/useAnchoredPosition.js +86 -0
  256. package/dist/esm/helpers/useAnchoredPosition.js.map +1 -0
  257. package/dist/esm/helpers/useBulmaClasses.js +158 -0
  258. package/dist/esm/helpers/useBulmaClasses.js.map +1 -0
  259. package/dist/esm/helpers/useClientLayoutEffect.js +13 -0
  260. package/dist/esm/helpers/useClientLayoutEffect.js.map +1 -0
  261. package/dist/esm/helpers/useColorClasses.js +84 -0
  262. package/dist/esm/helpers/useColorClasses.js.map +1 -0
  263. package/dist/esm/helpers/useFlexboxClasses.js +92 -0
  264. package/dist/esm/helpers/useFlexboxClasses.js.map +1 -0
  265. package/dist/esm/helpers/useFocusTrap.js +470 -0
  266. package/dist/esm/helpers/useFocusTrap.js.map +1 -0
  267. package/dist/esm/helpers/useIsHydrated.js +31 -0
  268. package/dist/esm/helpers/useIsHydrated.js.map +1 -0
  269. package/dist/esm/helpers/useOtherClasses.js +115 -0
  270. package/dist/esm/helpers/useOtherClasses.js.map +1 -0
  271. package/dist/esm/helpers/useSpacingClasses.js +82 -0
  272. package/dist/esm/helpers/useSpacingClasses.js.map +1 -0
  273. package/dist/esm/helpers/useTypographyClasses.js +93 -0
  274. package/dist/esm/helpers/useTypographyClasses.js.map +1 -0
  275. package/dist/esm/helpers/useVisibilityClasses.js +134 -0
  276. package/dist/esm/helpers/useVisibilityClasses.js.map +1 -0
  277. package/dist/esm/helpers/withSubComponents.js +24 -0
  278. package/dist/esm/helpers/withSubComponents.js.map +1 -0
  279. package/dist/esm/layout/Container.js +48 -0
  280. package/dist/esm/layout/Container.js.map +1 -0
  281. package/dist/esm/layout/Footer.js +33 -0
  282. package/dist/esm/layout/Footer.js.map +1 -0
  283. package/dist/esm/layout/Hero.js +90 -0
  284. package/dist/esm/layout/Hero.js.map +1 -0
  285. package/dist/esm/layout/Level.js +110 -0
  286. package/dist/esm/layout/Level.js.map +1 -0
  287. package/dist/esm/layout/Media.js +86 -0
  288. package/dist/esm/layout/Media.js.map +1 -0
  289. package/dist/esm/layout/Section.js +30 -0
  290. package/dist/esm/layout/Section.js.map +1 -0
  291. package/dist/extras.css +1 -1
  292. package/dist/extras.css.map +1 -1
  293. package/dist/index.cjs +187 -57
  294. package/dist/index.cjs.map +1 -1
  295. package/dist/index.esm.js +115 -17516
  296. package/dist/index.esm.js.map +1 -1
  297. package/dist/types/elements/Icon.d.ts +10 -7
  298. package/dist/types/form/Autocomplete.d.ts +1 -1
  299. package/dist/types/form/Checkboxes.d.ts +1 -1
  300. package/dist/types/form/DateInput.d.ts +1 -1
  301. package/dist/types/form/DateInputBase.d.ts +3 -4
  302. package/dist/types/form/DateRangeInput.d.ts +1 -1
  303. package/dist/types/form/DateTimeInput.d.ts +1 -1
  304. package/dist/types/form/DateTimeInputBase.d.ts +3 -4
  305. package/dist/types/form/Field.d.ts +2 -2
  306. package/dist/types/form/File.d.ts +4 -4
  307. package/dist/types/form/FormContext.d.ts +24 -2
  308. package/dist/types/form/Numberinput.d.ts +1 -1
  309. package/dist/types/form/Radios.d.ts +1 -1
  310. package/dist/types/form/Rate.d.ts +1 -1
  311. package/dist/types/form/Slider.d.ts +1 -1
  312. package/dist/types/form/Taginput.d.ts +1 -1
  313. package/dist/types/form/TimeInput.d.ts +1 -1
  314. package/dist/types/form/useAutoLabelId.d.ts +25 -4
  315. package/dist/types/helpers/Config.d.ts +3 -4
  316. package/dist/types/helpers/Theme.d.ts +44 -40
  317. package/dist/types-cjs/elements/Icon.d.ts +10 -7
  318. package/dist/types-cjs/form/Autocomplete.d.ts +1 -1
  319. package/dist/types-cjs/form/Checkboxes.d.ts +1 -1
  320. package/dist/types-cjs/form/DateInput.d.ts +1 -1
  321. package/dist/types-cjs/form/DateInputBase.d.ts +3 -4
  322. package/dist/types-cjs/form/DateRangeInput.d.ts +1 -1
  323. package/dist/types-cjs/form/DateTimeInput.d.ts +1 -1
  324. package/dist/types-cjs/form/DateTimeInputBase.d.ts +3 -4
  325. package/dist/types-cjs/form/Field.d.ts +2 -2
  326. package/dist/types-cjs/form/File.d.ts +4 -4
  327. package/dist/types-cjs/form/FormContext.d.ts +24 -2
  328. package/dist/types-cjs/form/Numberinput.d.ts +1 -1
  329. package/dist/types-cjs/form/Radios.d.ts +1 -1
  330. package/dist/types-cjs/form/Rate.d.ts +1 -1
  331. package/dist/types-cjs/form/Slider.d.ts +1 -1
  332. package/dist/types-cjs/form/Taginput.d.ts +1 -1
  333. package/dist/types-cjs/form/TimeInput.d.ts +1 -1
  334. package/dist/types-cjs/form/useAutoLabelId.d.ts +25 -4
  335. package/dist/types-cjs/helpers/Config.d.ts +3 -4
  336. package/dist/types-cjs/helpers/Theme.d.ts +44 -40
  337. package/dist/versions/bestax-no-dark-mode.css +1 -1
  338. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  339. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  340. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  341. package/dist/versions/bestax-no-helpers.css +1 -1
  342. package/dist/versions/bestax-no-helpers.css.map +1 -1
  343. package/dist/versions/bestax-prefixed.css +1 -1
  344. package/dist/versions/bestax-prefixed.css.map +1 -1
  345. package/package.json +1 -1
  346. package/src/scss/form/_dateinput.scss +37 -6
  347. package/src/scss/form/_datetimeinput.scss +7 -2
  348. package/src/scss/form/_picker-popover.scss +1 -4
@@ -0,0 +1,760 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { useRef, useState, useMemo, useCallback, useId, useEffect } from 'react';
4
+ import { usePrefixedClassNames, prefixedClassNames, classNames } from '../../helpers/classNames.js';
5
+ import { useConfig } from '../../helpers/Config.js';
6
+ import { getActiveElementInTree } from '../../helpers/shadowDom.js';
7
+ import { visuallyHidden } from '../../helpers/statusRegion.js';
8
+ import { mergeLabels, isPopoverToggleKey } from './pickerTypes.js';
9
+ import { floorMin, buildMonthGrid, isDayUnselectable, isPeriodUnselectable, makeDate, canCloseRange, startOfDay, addDays, isSameDay, addYears, addMonths, startOfMonth, clampDate, FIRST_YEAR, isSameMonth } from './dateUtils.js';
10
+ import { getDayNames, getMonthNames, formatDate } from './formatters.js';
11
+
12
+ /**
13
+ * Selects the calendar's focused cell, the one tab stop of whichever grid or
14
+ * year list is on show, for a popover to put focus on as it opens. It runs
15
+ * against the whole panel, so it matches only inside the grid or the list.
16
+ */
17
+ const CALENDAR_FOCUSED_CELL = '[role="grid"] [data-focused="true"], [role="listbox"] [data-focused-year="true"]';
18
+ const BASE_VIEW = {
19
+ day: 'days',
20
+ month: 'months',
21
+ year: 'years',
22
+ };
23
+ // Months in rows of three, so the grid is four rows deep.
24
+ const MONTH_ROWS = [
25
+ [0, 1, 2],
26
+ [3, 4, 5],
27
+ [6, 7, 8],
28
+ [9, 10, 11],
29
+ ];
30
+ const MONTHS_PER_ROW = 3;
31
+ // The year list's CSS lays it out four to a row.
32
+ const YEARS_PER_ROW = 4;
33
+ /**
34
+ * The month grid's keyboard, after the day grid's: arrows move a month or a
35
+ * row, Home/End go to the row's ends, PageUp/PageDown move a year. Returns
36
+ * the months to move by and the direction to search past disabled months, or
37
+ * `null` for a key the grid doesn't handle. `col` is the focused month's
38
+ * column.
39
+ */
40
+ function monthStep(key, col) {
41
+ switch (key) {
42
+ case 'ArrowLeft':
43
+ return [-1, -1];
44
+ case 'ArrowRight':
45
+ return [1, 1];
46
+ case 'ArrowUp':
47
+ return [-MONTHS_PER_ROW, -1];
48
+ case 'ArrowDown':
49
+ return [MONTHS_PER_ROW, 1];
50
+ case 'PageUp':
51
+ return [-12, -1];
52
+ case 'PageDown':
53
+ return [12, 1];
54
+ case 'Home':
55
+ return [-col, 1];
56
+ case 'End':
57
+ return [MONTHS_PER_ROW - 1 - col, -1];
58
+ default:
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * Whether DOM focus should follow a grid's tab stop as it moves: focus is in
64
+ * the grid, or fell to <body> while the grid held it last, as it does when
65
+ * the focused cell leaves the page or becomes disabled. Focus is read in the
66
+ * grid's own tree, so this holds inside a shadow root too.
67
+ */
68
+ function focusFollows(grid, heldLast) {
69
+ const active = getActiveElementInTree(grid);
70
+ if (grid.contains(active))
71
+ return true;
72
+ return heldLast && active === grid.ownerDocument.body;
73
+ }
74
+ /**
75
+ * The cell nearest index `i` that `disabled` leaves enabled, `i` itself when
76
+ * it is. At equal distance the later cell wins. With every cell disabled it
77
+ * stays `i`, as there is nothing to reach.
78
+ */
79
+ function nearestEnabled(i, disabled) {
80
+ if (!disabled[i])
81
+ return i;
82
+ for (let d = 1; d < disabled.length; d++) {
83
+ if (i + d < disabled.length && !disabled[i + d])
84
+ return i + d;
85
+ if (i - d >= 0 && !disabled[i - d])
86
+ return i - d;
87
+ }
88
+ return i;
89
+ }
90
+ /** The long, spoken form of a date in a range's announcement. */
91
+ const LONG_DATE = {
92
+ year: 'numeric',
93
+ month: 'long',
94
+ day: 'numeric',
95
+ };
96
+ /** A range's pending start: its start, when it has no end. */
97
+ const pendingStartOf = (range) => range?.[0] && !range[1] ? startOfDay(range[0]) : null;
98
+ /**
99
+ * The day grid's cells in rows of a week. A grid owns rows and rows own
100
+ * cells, so assistive technology can walk the grid, and the selection it
101
+ * reports, row by row.
102
+ */
103
+ const inWeeks = (days, className) => {
104
+ const rows = [];
105
+ for (let i = 0; i < days.length; i += 7) {
106
+ rows.push(jsx("div", { role: "row", className: className, children: days.slice(i, i + 7) }, i));
107
+ }
108
+ return rows;
109
+ };
110
+ /** A date's time, or `null` for none, for comparing ranges by value. */
111
+ const timeOf = (d) => d ? d.getTime() : null;
112
+ /**
113
+ * The year list's keyboard, as the selection surface and as navigation:
114
+ * arrows move a year or a row, Home/End go to the list's ends. Returns the
115
+ * year to start from and the direction to search past disabled years, or
116
+ * `null` for a key the list doesn't handle. As navigation no year is
117
+ * disabled, so only the year is used.
118
+ */
119
+ function yearStep(key, focusedYear, years) {
120
+ switch (key) {
121
+ case 'ArrowLeft':
122
+ return [focusedYear - 1, -1];
123
+ case 'ArrowRight':
124
+ return [focusedYear + 1, 1];
125
+ case 'ArrowUp':
126
+ return [focusedYear - YEARS_PER_ROW, -1];
127
+ case 'ArrowDown':
128
+ return [focusedYear + YEARS_PER_ROW, 1];
129
+ case 'Home':
130
+ return [years[0], 1];
131
+ case 'End':
132
+ return [years[years.length - 1], -1];
133
+ default:
134
+ return null;
135
+ }
136
+ }
137
+ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minProp, max, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, locale, dayNames, monthNames, nearbyMonthDays = true, color, size, className, id, autoFocusCell = false, labels, yearsRange, granularity = 'day', range, onRangeSelect, allowDisabledInRange = false, }) => {
138
+ const gridRef = useRef(null);
139
+ const monthGridRef = useRef(null);
140
+ const yearGridRef = useRef(null);
141
+ const { classPrefix } = useConfig();
142
+ const t = mergeLabels(labels);
143
+ const baseView = BASE_VIEW[granularity];
144
+ const isDayGranularity = granularity === 'day';
145
+ const isYearGranularity = granularity === 'year';
146
+ const [view, setView] = useState(baseView);
147
+ // Nothing before year 1 can be picked or reached, as in HTML's date inputs.
148
+ const min = useMemo(() => floorMin(minProp), [minProp]);
149
+ const computedDayNames = useMemo(() => {
150
+ if (dayNames && dayNames.length === 7)
151
+ return dayNames;
152
+ const sundayFirst = getDayNames(locale, 'short');
153
+ return [
154
+ ...sundayFirst.slice(firstDayOfWeek),
155
+ ...sundayFirst.slice(0, firstDayOfWeek),
156
+ ];
157
+ }, [dayNames, locale, firstDayOfWeek]);
158
+ const computedMonthNames = useMemo(() => {
159
+ if (monthNames && monthNames.length === 12)
160
+ return monthNames;
161
+ return getMonthNames(locale, 'long');
162
+ }, [monthNames, locale]);
163
+ // The month grid's cells are too narrow for long names, so they show the
164
+ // short form unless the caller supplied names of their own.
165
+ const monthCellNames = useMemo(() => {
166
+ if (monthNames && monthNames.length === 12)
167
+ return monthNames;
168
+ return getMonthNames(locale, 'short');
169
+ }, [monthNames, locale]);
170
+ const cells = useMemo(() => buildMonthGrid(focusedDate, firstDayOfWeek), [focusedDate, firstDayOfWeek]);
171
+ const isDateUnselectable = useCallback((d) => isDayUnselectable(d, { min, max, shouldDisableDate, unselectableDates }), [min, max, shouldDisableDate, unselectableDates]);
172
+ // A month or year is out of reach only when every day in it is.
173
+ const isMonthUnselectable = useCallback((d) => isPeriodUnselectable(d, 'month', {
174
+ min,
175
+ max,
176
+ shouldDisableDate,
177
+ unselectableDates,
178
+ }), [min, max, shouldDisableDate, unselectableDates]);
179
+ const isYearUnselectable = useCallback((year) => isPeriodUnselectable(makeDate(year), 'year', {
180
+ min,
181
+ max,
182
+ shouldDisableDate,
183
+ unselectableDates,
184
+ }), [min, max, shouldDisableDate, unselectableDates]);
185
+ // The day grid's one tab stop. A disabled button can't take focus, so when
186
+ // the focused day is disabled the stop moves to the nearest enabled day of
187
+ // the month on show, and focusing it makes it the focused day. A nearby
188
+ // month's day is left out, as focusing it would turn the grid to its month.
189
+ const tabStopDay = useMemo(() => {
190
+ const days = cells.filter(c => c.inCurrentMonth).map(c => c.date);
191
+ const i = nearestEnabled(focusedDate.getDate() - 1, days.map(isDateUnselectable));
192
+ return days[i];
193
+ }, [cells, focusedDate, isDateUnselectable]);
194
+ // ----- Range mode -----
195
+ // Range props come only with the day grid.
196
+ const rangeMode = !!range;
197
+ const valuePending = pendingStartOf(range);
198
+ // The start of a range being picked, which nothing outside the calendar
199
+ // sees until its end is picked too. It starts as the range's own pending
200
+ // start, and starts there again whenever the range changes from outside.
201
+ const [anchor, setAnchor] = useState(valuePending);
202
+ const rangeKey = `${timeOf(range?.[0])}|${timeOf(range?.[1])}`;
203
+ const [anchorRangeKey, setAnchorRangeKey] = useState(rangeKey);
204
+ if (anchorRangeKey !== rangeKey) {
205
+ setAnchorRangeKey(rangeKey);
206
+ setAnchor(valuePending);
207
+ }
208
+ // The day under the pointer while a start is pending, which the preview
209
+ // runs to ahead of the focused day.
210
+ const [hoverDate, setHoverDate] = useState(null);
211
+ // Whether `day` can end a range that starts on `start`. The start is
212
+ // checked as well as the end: one the range brought, or one picked before
213
+ // the bounds or the disabled days changed, can rule itself out.
214
+ const canEndAt = useCallback((start, day) => !isDateUnselectable(start) &&
215
+ !isDateUnselectable(day) &&
216
+ canCloseRange(start, day, { shouldDisableDate, unselectableDates }, allowDisabledInRange), [
217
+ isDateUnselectable,
218
+ shouldDisableDate,
219
+ unselectableDates,
220
+ allowDisabledInRange,
221
+ ]);
222
+ // A pick in range mode: the end of the pending range when it can be one,
223
+ // and otherwise the start of a new one.
224
+ const pickRangeDay = useCallback((picked) => {
225
+ const day = startOfDay(picked);
226
+ // The pointer is on the picked day, which the focused day now is too.
227
+ setHoverDate(null);
228
+ if (anchor && canEndAt(anchor, day)) {
229
+ setAnchor(null);
230
+ onRangeSelect?.(anchor, day);
231
+ }
232
+ else {
233
+ setAnchor(day);
234
+ }
235
+ }, [anchor, canEndAt, onRangeSelect]);
236
+ // What the grid shows: while a start is pending, the range from it to the
237
+ // hovered or focused day, when that day can end it; otherwise the
238
+ // committed range.
239
+ let shownStart;
240
+ let shownEnd;
241
+ if (anchor) {
242
+ shownStart = anchor;
243
+ const previewEnd = startOfDay(hoverDate ?? focusedDate);
244
+ shownEnd = canEndAt(anchor, previewEnd) ? previewEnd : null;
245
+ }
246
+ else {
247
+ shownStart = range?.[0] ? startOfDay(range[0]) : null;
248
+ shownEnd = range?.[1] ? startOfDay(range[1]) : null;
249
+ }
250
+ const previewing = !!anchor;
251
+ // Ids for the words that name a range's ends and state the whole range.
252
+ const reactId = useId();
253
+ const rangeIdBase = id ?? `calendar-${reactId}`;
254
+ const rangeStartId = `${rangeIdBase}-range-start`;
255
+ const rangeEndId = `${rangeIdBase}-range-end`;
256
+ const rangePreviewEndId = `${rangeIdBase}-range-preview-end`;
257
+ const rangeSummaryId = `${rangeIdBase}-range-summary`;
258
+ // The selection in words: the pending start alone, or the committed ends.
259
+ const selectedEnd = previewing ? null : shownEnd;
260
+ const rangeSummary = [
261
+ shownStart &&
262
+ `${t.rangeStart}: ${formatDate(shownStart, LONG_DATE, locale)}`,
263
+ selectedEnd &&
264
+ `${t.rangeEnd}: ${formatDate(selectedEnd, LONG_DATE, locale)}`,
265
+ ]
266
+ .filter(Boolean)
267
+ .join(', ');
268
+ // Whether the grid on show holds focus, or held it last before focus went
269
+ // nowhere: the cell that had it left the page or became disabled. A blur
270
+ // that takes focus out of the grid, to nowhere included, is the user
271
+ // leaving, so it clears this. A render that removes or disables the focused
272
+ // cell sends no blur here: browsers fire none, or fire it inside React's
273
+ // commit, where React delivers no events.
274
+ const gridHeldFocusRef = useRef(false);
275
+ const gridFocusHandlers = {
276
+ onFocus: () => {
277
+ gridHeldFocusRef.current = true;
278
+ },
279
+ onBlur: (e) => {
280
+ const next = e.relatedTarget;
281
+ if (!next || !e.currentTarget.contains(next)) {
282
+ gridHeldFocusRef.current = false;
283
+ }
284
+ },
285
+ };
286
+ // Step from `next` by `direction` days until we hit a selectable date,
287
+ // skipping past dates blocked by min/max, shouldDisableDate, or
288
+ // unselectableDates. If the whole searched range is disabled, focus stays
289
+ // put. Cap iterations to guard against pathologically empty ranges.
290
+ const moveFocus = useCallback((next, direction) => {
291
+ let candidate = next;
292
+ for (let i = 0; i < 366; i++) {
293
+ if (candidate.getTime() < startOfDay(min).getTime())
294
+ return;
295
+ if (max && candidate.getTime() > startOfDay(max).getTime())
296
+ return;
297
+ if (!isDateUnselectable(candidate)) {
298
+ onFocusedDateChange(candidate);
299
+ return;
300
+ }
301
+ candidate = addDays(candidate, direction);
302
+ }
303
+ }, [min, max, isDateUnselectable, onFocusedDateChange]);
304
+ const handleKeyDown = useCallback((e) => {
305
+ if (isPopoverToggleKey(e))
306
+ return;
307
+ // A key takes the range preview back from the pointer.
308
+ setHoverDate(null);
309
+ switch (e.key) {
310
+ case 'ArrowLeft':
311
+ e.preventDefault();
312
+ moveFocus(addDays(focusedDate, -1), -1);
313
+ break;
314
+ case 'ArrowRight':
315
+ e.preventDefault();
316
+ moveFocus(addDays(focusedDate, 1), 1);
317
+ break;
318
+ case 'ArrowUp':
319
+ e.preventDefault();
320
+ moveFocus(addDays(focusedDate, -7), -1);
321
+ break;
322
+ case 'ArrowDown':
323
+ e.preventDefault();
324
+ moveFocus(addDays(focusedDate, 7), 1);
325
+ break;
326
+ case 'PageUp':
327
+ e.preventDefault();
328
+ moveFocus(e.shiftKey ? addYears(focusedDate, -1) : addMonths(focusedDate, -1), -1);
329
+ break;
330
+ case 'PageDown':
331
+ e.preventDefault();
332
+ moveFocus(e.shiftKey ? addYears(focusedDate, 1) : addMonths(focusedDate, 1), 1);
333
+ break;
334
+ case 'Home': {
335
+ e.preventDefault();
336
+ const dayOfWeek = focusedDate.getDay();
337
+ const offset = (dayOfWeek - firstDayOfWeek + 7) % 7;
338
+ moveFocus(addDays(focusedDate, -offset), 1);
339
+ break;
340
+ }
341
+ case 'End': {
342
+ e.preventDefault();
343
+ const dayOfWeek = focusedDate.getDay();
344
+ const offset = (dayOfWeek - firstDayOfWeek + 7) % 7;
345
+ moveFocus(addDays(focusedDate, 6 - offset), -1);
346
+ break;
347
+ }
348
+ case 'Enter':
349
+ case ' ':
350
+ e.preventDefault();
351
+ if (isDateUnselectable(focusedDate))
352
+ break;
353
+ if (rangeMode)
354
+ pickRangeDay(focusedDate);
355
+ else
356
+ onSelect?.(focusedDate);
357
+ break;
358
+ case 'Escape':
359
+ // A pending pick is taken back, and the key stops there so a
360
+ // popover around the calendar stays open. The range's own pending
361
+ // start is no pick, so Escape goes on to close the popover.
362
+ if (anchor && !(valuePending && isSameDay(anchor, valuePending))) {
363
+ e.preventDefault();
364
+ e.stopPropagation();
365
+ setAnchor(valuePending);
366
+ }
367
+ break;
368
+ }
369
+ }, [
370
+ focusedDate,
371
+ firstDayOfWeek,
372
+ isDateUnselectable,
373
+ moveFocus,
374
+ onSelect,
375
+ rangeMode,
376
+ pickRangeDay,
377
+ anchor,
378
+ valuePending,
379
+ ]);
380
+ // The month grid's counterpart to `moveFocus`: step by `direction` months
381
+ // until a month with a selectable day turns up, stopping at the min/max
382
+ // months. The cap guards against a range with nothing selectable.
383
+ const moveMonthFocus = useCallback((next, direction) => {
384
+ let candidate = next;
385
+ for (let i = 0; i < 120; i++) {
386
+ const month = startOfMonth(candidate).getTime();
387
+ if (month < startOfMonth(min).getTime())
388
+ return;
389
+ if (max && month > startOfMonth(max).getTime())
390
+ return;
391
+ if (!isMonthUnselectable(candidate)) {
392
+ onFocusedDateChange(candidate);
393
+ return;
394
+ }
395
+ candidate = addMonths(candidate, direction);
396
+ }
397
+ }, [min, max, isMonthUnselectable, onFocusedDateChange]);
398
+ const handleMonthKeyDown = useCallback((e) => {
399
+ if (isPopoverToggleKey(e))
400
+ return;
401
+ if (e.key === 'Enter' || e.key === ' ') {
402
+ e.preventDefault();
403
+ if (!isMonthUnselectable(focusedDate)) {
404
+ onSelect?.(startOfMonth(focusedDate));
405
+ }
406
+ return;
407
+ }
408
+ const step = monthStep(e.key, focusedDate.getMonth() % MONTHS_PER_ROW);
409
+ if (!step)
410
+ return;
411
+ e.preventDefault();
412
+ moveMonthFocus(addMonths(focusedDate, step[0]), step[1]);
413
+ }, [focusedDate, isMonthUnselectable, moveMonthFocus, onSelect]);
414
+ const calendarClass = usePrefixedClassNames('dateinput', {
415
+ [`is-${color}`]: !!color,
416
+ [`is-${size}`]: !!size,
417
+ });
418
+ const headerClass = usePrefixedClassNames('dateinput-header');
419
+ const monthTriggerClass = usePrefixedClassNames('dateinput-month-trigger', {
420
+ 'is-active': view === 'years',
421
+ });
422
+ const monthCaretClass = usePrefixedClassNames('dateinput-month-caret');
423
+ const navGroupClass = usePrefixedClassNames('dateinput-nav-group');
424
+ const navPrevClass = usePrefixedClassNames('dateinput-nav-prev');
425
+ const navNextClass = usePrefixedClassNames('dateinput-nav-next');
426
+ const monthLabelClass = usePrefixedClassNames('dateinput-month-label');
427
+ const dayNamesRowClass = usePrefixedClassNames('dateinput-day-names');
428
+ const dayNameClass = usePrefixedClassNames('dateinput-day-name');
429
+ const gridClass = usePrefixedClassNames('dateinput-grid');
430
+ const weekClass = usePrefixedClassNames('dateinput-week');
431
+ const monthsGridClass = usePrefixedClassNames('dateinput-months-grid');
432
+ const monthsRowClass = usePrefixedClassNames('dateinput-months-row');
433
+ const yearsGridClass = usePrefixedClassNames('dateinput-years-grid');
434
+ const focusedYear = focusedDate.getFullYear();
435
+ const today = new Date();
436
+ const todayYear = today.getFullYear();
437
+ // The header steps a month at a time over the day grid and a year at a
438
+ // time over the month grid, kept inside min/max. A step is off once the
439
+ // month or year on show holds the bound or lies past it, as picking a year
440
+ // from the list can leave it.
441
+ const prevAnchor = isDayGranularity
442
+ ? addMonths(focusedDate, -1)
443
+ : clampDate(addYears(focusedDate, -1), min, max);
444
+ const nextAnchor = isDayGranularity
445
+ ? addMonths(focusedDate, 1)
446
+ : clampDate(addYears(focusedDate, 1), min, max);
447
+ const focusedMonth = startOfMonth(focusedDate).getTime();
448
+ const prevDisabled = isDayGranularity
449
+ ? focusedMonth <= startOfMonth(min).getTime()
450
+ : focusedYear <= min.getFullYear();
451
+ const nextDisabled = isDayGranularity
452
+ ? !!max && focusedMonth >= startOfMonth(max).getTime()
453
+ : !!max && focusedYear >= max.getFullYear();
454
+ const labelId = id ? `${id}-label` : undefined;
455
+ const monthLabel = isDayGranularity
456
+ ? `${computedMonthNames[focusedDate.getMonth()]} ${focusedYear}`
457
+ : String(focusedYear);
458
+ // As the selection surface the year list is where focus moves, so its
459
+ // window stays centred on the year focused at mount: re-centring on every
460
+ // move would reflow the list under the keyboard.
461
+ const [mountYear] = useState(focusedYear);
462
+ const windowYear = isYearGranularity ? mountYear : focusedYear;
463
+ const yearList = useMemo(() => {
464
+ const minYear = minProp
465
+ ? min.getFullYear()
466
+ : Math.max(FIRST_YEAR, windowYear - 100);
467
+ const maxYear = max ? max.getFullYear() : windowYear + 100;
468
+ const [lo, hi] = yearsRange ?? [minYear, maxYear];
469
+ const start = Math.max(lo, minYear);
470
+ const end = Math.min(hi, maxYear);
471
+ const out = [];
472
+ for (let y = start; y <= end; y++)
473
+ out.push(y);
474
+ return out;
475
+ }, [windowYear, minProp, min, max, yearsRange]);
476
+ // Only the selection surface disables years; as navigation every listed
477
+ // year can be visited.
478
+ const disabledYears = useMemo(() => new Set(isYearGranularity ? yearList.filter(y => isYearUnselectable(y)) : []), [isYearGranularity, yearList, isYearUnselectable]);
479
+ // The year list's one tab stop, after the month grid's: when the focused
480
+ // year is disabled the stop moves to the nearest enabled year in the list,
481
+ // and focusing it makes it the focused year. As the selection surface a
482
+ // year outside a non-empty list starts from the end it fell past, so the
483
+ // list keeps a stop. As navigation it marks the current year, so a year
484
+ // outside the list marks no option, and the stop itself follows focus.
485
+ const tabStopYear = useMemo(() => {
486
+ let i = yearList.indexOf(focusedYear);
487
+ if (i < 0) {
488
+ if (!isYearGranularity || yearList.length === 0)
489
+ return focusedYear;
490
+ i = focusedYear < yearList[0] ? 0 : yearList.length - 1;
491
+ }
492
+ return yearList[nearestEnabled(i, yearList.map(y => disabledYears.has(y)))];
493
+ }, [yearList, focusedYear, disabledYears, isYearGranularity]);
494
+ // As navigation the keys move focus and leave the focused year alone, so
495
+ // the tab stop goes to the year that last had focus while it is listed,
496
+ // and Tab leaves the list rather than landing on the current year. It
497
+ // starts over each time the list opens.
498
+ const [navYear, setNavYear] = useState(null);
499
+ const listTabStop = navYear !== null && yearList.includes(navYear) ? navYear : tabStopYear;
500
+ // Which of the focused year's months have no selectable day.
501
+ const disabledMonths = useMemo(() => granularity === 'month'
502
+ ? Array.from({ length: 12 }, (_, m) => isMonthUnselectable(makeDate(focusedYear, m)))
503
+ : [], [granularity, focusedYear, isMonthUnselectable]);
504
+ // The month grid's one tab stop. A disabled button can't take focus, so
505
+ // when the focused month is disabled the stop moves to the nearest enabled
506
+ // month, and focusing it makes it the focused month.
507
+ const tabStopMonth = nearestEnabled(focusedDate.getMonth(), disabledMonths);
508
+ const handleYearSelect = useCallback((year) => {
509
+ const next = new Date(focusedDate);
510
+ next.setFullYear(year);
511
+ // Keep month/day. The month grid also clamps into [min, max] so its
512
+ // focused month is never one the bounds rule out.
513
+ onFocusedDateChange(isDayGranularity ? next : clampDate(next, min, max));
514
+ setView(baseView);
515
+ }, [focusedDate, onFocusedDateChange, isDayGranularity, min, max, baseView]);
516
+ // Picking from the year list as the selection surface: focus follows the
517
+ // pick and `onSelect` gets the year's first day.
518
+ const handleYearPick = useCallback((year) => {
519
+ onFocusedDateChange(clampDate(makeDate(year), min, max));
520
+ onSelect?.(makeDate(year));
521
+ }, [min, max, onFocusedDateChange, onSelect]);
522
+ // Step through the listed years from `year` by `direction`, skipping
523
+ // disabled ones, and focus the first selectable year found.
524
+ const moveYearFocus = useCallback((year, direction) => {
525
+ const first = yearList[0];
526
+ const last = yearList[yearList.length - 1];
527
+ for (let y = year; y >= first && y <= last; y += direction) {
528
+ if (!disabledYears.has(y)) {
529
+ onFocusedDateChange(clampDate(makeDate(y), min, max));
530
+ return;
531
+ }
532
+ }
533
+ }, [yearList, disabledYears, onFocusedDateChange, min, max]);
534
+ const handleYearListKeyDown = useCallback((e) => {
535
+ if (isPopoverToggleKey(e))
536
+ return;
537
+ if (isYearGranularity) {
538
+ const step = yearStep(e.key, focusedYear, yearList);
539
+ if (!step)
540
+ return;
541
+ e.preventDefault();
542
+ moveYearFocus(step[0], step[1]);
543
+ return;
544
+ }
545
+ // As navigation, Escape returns to the grid the list was opened from.
546
+ // It stops there, so a popover around the calendar stays open and only
547
+ // a second Escape closes it.
548
+ if (e.key === 'Escape') {
549
+ e.preventDefault();
550
+ e.stopPropagation();
551
+ setView(baseView);
552
+ return;
553
+ }
554
+ // The other keys move DOM focus from the year that has it and leave
555
+ // the focused date alone. The list is centred on that date, so moving
556
+ // it would reflow the list under the keyboard, and it would turn the
557
+ // grid behind the list before anything is picked. Enter or Space
558
+ // clicks the year with focus, which picks it.
559
+ const options = Array.from(e.currentTarget.querySelectorAll('[role="option"]'));
560
+ const from = options.indexOf(e.target);
561
+ if (from < 0)
562
+ return;
563
+ const step = yearStep(e.key, yearList[from], yearList);
564
+ if (!step)
565
+ return;
566
+ e.preventDefault();
567
+ // The listed years run one apart, so a year's index is its distance
568
+ // from the first. A step past either end finds no option.
569
+ options[step[0] - yearList[0]]?.focus();
570
+ }, [isYearGranularity, baseView, focusedYear, yearList, moveYearFocus]);
571
+ // The day or month grid takes focus as the popover opens on it, or comes
572
+ // back to it from the year list.
573
+ useEffect(() => {
574
+ if (!autoFocusCell)
575
+ return;
576
+ const grid = view === 'days'
577
+ ? gridRef.current
578
+ : view === 'months'
579
+ ? monthGridRef.current
580
+ : null;
581
+ grid?.querySelector('[data-focused="true"]')?.focus();
582
+ }, [autoFocusCell, view]);
583
+ // From then on focus follows the tab stop only while the grid has it, or
584
+ // has just lost it, so focus on a header button or a time wheel stays put.
585
+ const tabStopDayTime = tabStopDay.getTime();
586
+ useEffect(() => {
587
+ const grid = gridRef.current;
588
+ if (view !== 'days' || !grid)
589
+ return;
590
+ if (!focusFollows(grid, gridHeldFocusRef.current))
591
+ return;
592
+ grid.querySelector('[data-focused="true"]')?.focus();
593
+ }, [tabStopDayTime, view]);
594
+ useEffect(() => {
595
+ const grid = monthGridRef.current;
596
+ if (view !== 'months' || !grid)
597
+ return;
598
+ if (!focusFollows(grid, gridHeldFocusRef.current))
599
+ return;
600
+ grid.querySelector('[data-focused="true"]')?.focus();
601
+ }, [focusedYear, tabStopMonth, view]);
602
+ // When the year view opens as navigation, scroll the focused year into
603
+ // view.
604
+ useEffect(() => {
605
+ if (view !== 'years' || isYearGranularity || !yearGridRef.current)
606
+ return;
607
+ const sel = yearGridRef.current.querySelector('[data-focused-year="true"]');
608
+ sel?.scrollIntoView({ block: 'center' });
609
+ sel?.focus();
610
+ }, [view, isYearGranularity]);
611
+ // As the selection surface the year list opens with the focused year in
612
+ // its middle. It scrolls the list itself rather than calling
613
+ // `scrollIntoView`, which would also scroll the page to an inline calendar
614
+ // on load, and focuses that year only when the popover asks.
615
+ useEffect(() => {
616
+ const list = yearGridRef.current;
617
+ if (!isYearGranularity || !list)
618
+ return;
619
+ const target = list.querySelector('[data-focused-year="true"]');
620
+ if (!target)
621
+ return;
622
+ const rect = target.getBoundingClientRect();
623
+ list.scrollTop +=
624
+ rect.top -
625
+ list.getBoundingClientRect().top -
626
+ (list.clientHeight - rect.height) / 2;
627
+ if (autoFocusCell)
628
+ target.focus({ preventScroll: true });
629
+ }, [isYearGranularity, autoFocusCell]);
630
+ // Keyboard moves keep DOM focus on the focused year while the list has it.
631
+ useEffect(() => {
632
+ const list = yearGridRef.current;
633
+ if (!isYearGranularity || !list)
634
+ return;
635
+ if (!list.contains(getActiveElementInTree(list)))
636
+ return;
637
+ list.querySelector('[data-focused-year="true"]')?.focus();
638
+ }, [isYearGranularity, focusedYear]);
639
+ const navArrow = (d) => (jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }));
640
+ return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => {
641
+ setNavYear(null);
642
+ setView(v => (v === 'years' ? baseView : 'years'));
643
+ }, children: [jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxs("div", { className: navGroupClass, children: [jsx("button", { type: "button", className: navPrevClass, "aria-label": isDayGranularity ? t.prevMonth : t.prevYear, disabled: prevDisabled || view !== baseView, onClick: () => onFocusedDateChange(prevAnchor), children: navArrow('M10 3l-5 5 5 5') }), jsx("button", { type: "button", className: navNextClass, "aria-label": isDayGranularity ? t.nextMonth : t.nextYear, disabled: nextDisabled || view !== baseView, onClick: () => onFocusedDateChange(nextAnchor), children: navArrow('M6 3l5 5-5 5') })] }))] }), view === 'days' && (jsxs(Fragment, { children: [jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsx("div", { className: dayNameClass, children: name }, i))) }), jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, "aria-multiselectable": rangeMode || undefined, "aria-describedby": rangeMode ? rangeSummaryId : undefined, className: gridClass, onKeyDown: handleKeyDown, onMouseLeave: anchor ? () => setHoverDate(null) : undefined, ...gridFocusHandlers, children: inWeeks(cells.map(cell => {
644
+ const disabled = isDateUnselectable(cell.date);
645
+ const time = cell.date.getTime();
646
+ // Range mode. The range on show has its ends filled. Every day
647
+ // of a committed range is selected, and of a preview only the
648
+ // start.
649
+ const isStart = time === timeOf(shownStart);
650
+ const isEnd = time === timeOf(shownEnd);
651
+ const inRange = !!shownStart &&
652
+ !!shownEnd &&
653
+ time > shownStart.getTime() &&
654
+ time < shownEnd.getTime();
655
+ // The day a pending range would end on, which isn't picked
656
+ // yet, so its description says what picking it does.
657
+ const isPreviewEnd = previewing && isEnd && !isStart;
658
+ const isPreview = (previewing && inRange) || isPreviewEnd;
659
+ const isFilled = rangeMode
660
+ ? isStart || (isEnd && !previewing)
661
+ : !!value && isSameDay(value, cell.date);
662
+ const isSelected = isFilled || (rangeMode && !previewing && inRange);
663
+ const describedBy = rangeMode
664
+ ? [
665
+ isStart && rangeStartId,
666
+ isEnd && !previewing && rangeEndId,
667
+ isPreviewEnd && rangePreviewEndId,
668
+ ]
669
+ .filter(Boolean)
670
+ .join(' ') || undefined
671
+ : undefined;
672
+ const isFocused = isSameDay(cell.date, tabStopDay);
673
+ const otherMonth = !cell.inCurrentMonth;
674
+ const cellClass = prefixedClassNames(classPrefix, 'dateinput-cell', {
675
+ 'is-selected': isFilled,
676
+ 'is-range-start': isStart,
677
+ 'is-range-end': isEnd,
678
+ 'is-in-range': inRange,
679
+ 'is-preview': isPreview,
680
+ 'is-today': cell.isToday,
681
+ 'is-disabled': disabled,
682
+ 'is-other-month': otherMonth,
683
+ });
684
+ const display = !otherMonth || nearbyMonthDays;
685
+ return (jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": cell.isToday ? 'date' : undefined, "aria-describedby": describedBy, "data-focused": isFocused ? 'true' : undefined, disabled: disabled || !display, className: cellClass, onFocus: () => {
686
+ // Reached by Tab while the focused day is disabled, or
687
+ // by pointer: keys move on from here. A nearby month's
688
+ // day waits for its click, which turns the grid.
689
+ if (!otherMonth && !isSameDay(cell.date, focusedDate)) {
690
+ onFocusedDateChange(cell.date);
691
+ }
692
+ },
693
+ // Mouseover rather than mouseenter: browsers send it to a
694
+ // disabled day as well, where React holds mouseenter back,
695
+ // so the preview leaves the last enabled day behind.
696
+ onMouseOver: anchor ? () => setHoverDate(cell.date) : undefined, onClick: () => {
697
+ if (disabled)
698
+ return;
699
+ onFocusedDateChange(cell.date);
700
+ if (rangeMode)
701
+ pickRangeDay(cell.date);
702
+ else
703
+ onSelect?.(cell.date);
704
+ }, style: !display ? { visibility: 'hidden' } : undefined, children: cell.date.getDate() }, cell.date.toISOString()));
705
+ }), weekClass) })] })), view === 'months' && (jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, ...gridFocusHandlers, children: MONTH_ROWS.map(row => (jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
706
+ const date = makeDate(focusedYear, month);
707
+ const disabled = disabledMonths[month];
708
+ const isSelected = !!value && isSameMonth(value, date);
709
+ const isFocused = month === tabStopMonth;
710
+ const isCurrent = isSameMonth(date, today);
711
+ const cellClass = prefixedClassNames(classPrefix, 'dateinput-month-cell', {
712
+ 'is-selected': isSelected,
713
+ 'is-today': isCurrent,
714
+ 'is-disabled': disabled,
715
+ });
716
+ return (jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": isCurrent ? 'date' : undefined, "aria-label": computedMonthNames[month], "data-focused": isFocused ? 'true' : undefined, disabled: disabled, className: cellClass, onFocus: () => {
717
+ // Reached by Tab while the focused month is disabled,
718
+ // or by pointer: keys move on from here.
719
+ if (month !== focusedDate.getMonth()) {
720
+ onFocusedDateChange(clampDate(date, min, max));
721
+ }
722
+ }, onClick: () => {
723
+ onFocusedDateChange(clampDate(date, min, max));
724
+ onSelect?.(date);
725
+ }, children: monthCellNames[month] }, month));
726
+ }) }, row[0]))) })), view === 'years' && (jsx("div", { ref: yearGridRef, className: yearsGridClass, role: "listbox", "aria-label": isYearGranularity ? t.chooseYear : monthLabel, onKeyDown: handleYearListKeyDown, children: yearList.map(year => {
727
+ const isFocused = year === tabStopYear;
728
+ const isToday = year === todayYear;
729
+ // As navigation the focused year is the current one. As the
730
+ // selection surface the value's year is selected and focus roams.
731
+ const isSelected = isYearGranularity
732
+ ? !!value && value.getFullYear() === year
733
+ : isFocused;
734
+ const disabled = disabledYears.has(year);
735
+ const cellCls = prefixedClassNames(classPrefix, 'dateinput-year-cell', {
736
+ 'is-selected': isSelected,
737
+ 'is-today': isToday,
738
+ 'is-disabled': disabled,
739
+ });
740
+ return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex: year === listTabStop ? 0 : -1, disabled: disabled, className: cellCls, onFocus: () => {
741
+ // As navigation, focusing a year by key or pointer only
742
+ // moves the tab stop: nothing is committed until a year is
743
+ // picked. As the selection surface it is reached by Tab
744
+ // while the focused year is disabled, or by pointer, and
745
+ // keys move on from here.
746
+ if (!isYearGranularity) {
747
+ setNavYear(year);
748
+ }
749
+ else if (year !== focusedYear) {
750
+ onFocusedDateChange(clampDate(makeDate(year), min, max));
751
+ }
752
+ }, onClick: () => isYearGranularity
753
+ ? handleYearPick(year)
754
+ : handleYearSelect(year), children: year }, year));
755
+ }) })), rangeMode && (jsxs(Fragment, { children: [jsx("span", { id: rangeStartId, hidden: true, children: t.rangeStart }), jsx("span", { id: rangeEndId, hidden: true, children: t.rangeEnd }), jsx("span", { id: rangePreviewEndId, hidden: true, children: t.rangePreviewEnd }), jsx("div", { id: rangeSummaryId, "aria-live": "polite", style: visuallyHidden, children: rangeSummary })] }))] }));
756
+ };
757
+ Calendar.displayName = 'Calendar';
758
+
759
+ export { CALENDAR_FOCUSED_CELL, Calendar };
760
+ //# sourceMappingURL=Calendar.js.map