@astryxdesign/core 0.7.8 → 0.7.9-canary.0200dfe

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 (585) hide show
  1. package/dist/AppShell/observeMainOverflow.d.ts.map +1 -1
  2. package/dist/AppShell/observeMainOverflow.js +9 -6
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +37 -3
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetPanel.js +40 -8
  7. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  8. package/dist/BottomSheet/bottomSheetStack.js +4 -3
  9. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  10. package/dist/BottomSheet/useSheetGestures.js +61 -21
  11. package/dist/Calendar/Calendar.d.ts +14 -2
  12. package/dist/Calendar/Calendar.d.ts.map +1 -1
  13. package/dist/Calendar/Calendar.js +63 -74
  14. package/dist/Calendar/getInitialFocusDate.d.ts +4 -1
  15. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -1
  16. package/dist/Calendar/getInitialFocusDate.js +7 -5
  17. package/dist/Calendar/hooks/useCalendarDays.d.ts +19 -2
  18. package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
  19. package/dist/Calendar/hooks/useCalendarDays.js +57 -64
  20. package/dist/Calendar/hooks/useCalendarNavigation.d.ts +28 -3
  21. package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
  22. package/dist/Calendar/hooks/useCalendarNavigation.js +33 -22
  23. package/dist/Calendar/styles.d.ts +1 -1
  24. package/dist/CheckboxInput/CheckboxInput.d.ts +2 -1
  25. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  26. package/dist/CheckboxInput/CheckboxInput.js +74 -18
  27. package/dist/CheckboxInput/CheckboxRowPressContext.d.ts +2 -0
  28. package/dist/CheckboxInput/CheckboxRowPressContext.d.ts.map +1 -0
  29. package/dist/CheckboxInput/CheckboxRowPressContext.js +20 -0
  30. package/dist/CheckboxList/CheckboxListItem.d.ts +2 -1
  31. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  32. package/dist/CheckboxList/CheckboxListItem.js +40 -14
  33. package/dist/ClickableCard/ClickableCard.d.ts +1 -1
  34. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  35. package/dist/ClickableCard/ClickableCard.js +5 -1
  36. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  37. package/dist/ContextMenu/ContextMenu.js +2 -0
  38. package/dist/DateInput/DateInput.d.ts +37 -6
  39. package/dist/DateInput/DateInput.d.ts.map +1 -1
  40. package/dist/DateInput/DateInput.js +97 -124
  41. package/dist/DateInput/MonthScroller.d.ts +5 -2
  42. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  43. package/dist/DateInput/MonthScroller.js +26 -29
  44. package/dist/DateInput/MonthYearWheels.d.ts +10 -1
  45. package/dist/DateInput/MonthYearWheels.d.ts.map +1 -1
  46. package/dist/DateInput/MonthYearWheels.js +54 -42
  47. package/dist/DateInput/PickerWheel.d.ts.map +1 -1
  48. package/dist/DateInput/PickerWheel.js +7 -1
  49. package/dist/DateInput/TouchDateField.d.ts +1 -1
  50. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  51. package/dist/DateInput/TouchDateField.js +26 -21
  52. package/dist/DateInput/monthGeometry.d.ts +42 -8
  53. package/dist/DateInput/monthGeometry.d.ts.map +1 -1
  54. package/dist/DateInput/monthGeometry.js +81 -7
  55. package/dist/DateInput/useCalendarPresentation.d.ts +10 -0
  56. package/dist/DateInput/useCalendarPresentation.d.ts.map +1 -0
  57. package/dist/DateInput/useCalendarPresentation.js +32 -0
  58. package/dist/DateInput/useTypedDate.d.ts +92 -0
  59. package/dist/DateInput/useTypedDate.d.ts.map +1 -0
  60. package/dist/DateInput/useTypedDate.js +269 -0
  61. package/dist/DateInput/wheelStrip.js +2 -2
  62. package/dist/DateRangeInput/DateRangeInput.d.ts +17 -1
  63. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  64. package/dist/DateRangeInput/DateRangeInput.js +15 -8
  65. package/dist/DateTimeInput/DateTimeInput.d.ts +26 -3
  66. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  67. package/dist/DateTimeInput/DateTimeInput.js +92 -88
  68. package/dist/DateTimeInput/TouchDateTimeField.d.ts +1 -1
  69. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  70. package/dist/DateTimeInput/TouchDateTimeField.js +26 -20
  71. package/dist/Dialog/Dialog.d.ts +2 -1
  72. package/dist/Dialog/Dialog.d.ts.map +1 -1
  73. package/dist/Dialog/Dialog.js +14 -3
  74. package/dist/Drawer/useDrawerDialogPresence.d.ts +2 -1
  75. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  76. package/dist/Drawer/useDrawerDialogPresence.js +11 -2
  77. package/dist/DropdownMenu/DropdownMenu.d.ts +9 -1
  78. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  79. package/dist/DropdownMenu/DropdownMenu.js +11 -8
  80. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  81. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +3 -2
  82. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  83. package/dist/DropdownMenu/DropdownMenuItem.js +3 -2
  84. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  85. package/dist/DropdownMenu/DropdownMenuRadioItem.js +3 -2
  86. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  87. package/dist/DropdownMenu/DropdownMenuSubMenu.js +3 -2
  88. package/dist/Field/inputDisabledFocus.d.ts +4 -4
  89. package/dist/Field/inputStyles.stylex.d.ts +1 -1
  90. package/dist/FileInput/FileInput.d.ts.map +1 -1
  91. package/dist/FileInput/FileInput.js +16 -2
  92. package/dist/HoverCard/useHoverCard.d.ts +3 -1
  93. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  94. package/dist/Icon/Icon.d.ts +1 -1
  95. package/dist/InfoTip/InfoTip.d.ts +1 -0
  96. package/dist/InfoTip/InfoTip.d.ts.map +1 -1
  97. package/dist/InfoTip/InfoTip.js +6 -18
  98. package/dist/InputGroup/InputGroup.d.ts +4 -3
  99. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  100. package/dist/InputGroup/InputGroup.js +2 -2
  101. package/dist/InputGroup/InputGroupText.d.ts +2 -1
  102. package/dist/InputGroup/InputGroupText.d.ts.map +1 -1
  103. package/dist/InputGroup/InputGroupText.js +2 -1
  104. package/dist/InputGroup/groupStyles.d.ts +1 -1
  105. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  106. package/dist/InputGroup/groupStyles.js +1 -1
  107. package/dist/Item/Item.d.ts +20 -1
  108. package/dist/Item/Item.d.ts.map +1 -1
  109. package/dist/Item/Item.js +97 -29
  110. package/dist/Item/ItemListRowContext.d.ts +2 -0
  111. package/dist/Item/ItemListRowContext.d.ts.map +1 -0
  112. package/dist/Item/ItemListRowContext.js +16 -0
  113. package/dist/Layer/holdEntranceInput.d.ts +24 -0
  114. package/dist/Layer/holdEntranceInput.d.ts.map +1 -0
  115. package/dist/Layer/holdEntranceInput.js +65 -0
  116. package/dist/Layer/inputHold.d.ts +49 -0
  117. package/dist/Layer/inputHold.d.ts.map +1 -0
  118. package/dist/Layer/inputHold.js +636 -0
  119. package/dist/Layer/outsidePress.d.ts +4 -0
  120. package/dist/Layer/outsidePress.d.ts.map +1 -1
  121. package/dist/Layer/outsidePress.js +18 -1
  122. package/dist/Layer/preserveAnimations.d.ts +1 -5
  123. package/dist/Layer/preserveAnimations.d.ts.map +1 -1
  124. package/dist/Layer/preserveAnimations.js +12 -214
  125. package/dist/Layer/useBackdropPress.d.ts +8 -0
  126. package/dist/Layer/useBackdropPress.d.ts.map +1 -1
  127. package/dist/Layer/useBackdropPress.js +31 -2
  128. package/dist/Layer/useLayer.d.ts +3 -1
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +64 -4
  131. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  132. package/dist/Layer/useTouchTrigger.js +4 -0
  133. package/dist/Layout/LayoutContent.d.ts +3 -1
  134. package/dist/Layout/LayoutContent.d.ts.map +1 -1
  135. package/dist/Layout/LayoutContent.js +12 -4
  136. package/dist/Layout/LayoutPanel.d.ts +13 -10
  137. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  138. package/dist/Layout/LayoutPanel.js +81 -11
  139. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  140. package/dist/Lightbox/Lightbox.js +4 -0
  141. package/dist/List/List.d.ts.map +1 -1
  142. package/dist/List/List.js +1 -0
  143. package/dist/List/ListItem.d.ts +5 -4
  144. package/dist/List/ListItem.d.ts.map +1 -1
  145. package/dist/List/ListItem.js +45 -9
  146. package/dist/Markdown/parser.d.ts.map +1 -1
  147. package/dist/Markdown/parser.js +56 -39
  148. package/dist/MetadataList/MetadataList.js +1 -1
  149. package/dist/MobileNav/MobileNav.d.ts +11 -1
  150. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  151. package/dist/MobileNav/MobileNav.js +39 -1
  152. package/dist/MoreMenu/MoreMenu.d.ts +5 -3
  153. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  154. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  155. package/dist/MultiSelector/MultiSelector.js +6 -9
  156. package/dist/NumberInput/NumberInput.d.ts +4 -2
  157. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  158. package/dist/NumberInput/NumberInput.js +244 -11
  159. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  160. package/dist/OverflowList/OverflowList.js +7 -2
  161. package/dist/Popover/usePopover.d.ts +1 -0
  162. package/dist/Popover/usePopover.d.ts.map +1 -1
  163. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  164. package/dist/PowerSearch/PowerSearch.js +12 -5
  165. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -1
  166. package/dist/PowerSearch/PowerSearchBottomSheet.js +4 -2
  167. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchToken.js +3 -1
  169. package/dist/PowerSearch/formatFilterValue.d.ts +9 -3
  170. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  171. package/dist/PowerSearch/formatFilterValue.js +14 -11
  172. package/dist/PowerSearch/formatTokenValue.d.ts +6 -2
  173. package/dist/PowerSearch/formatTokenValue.d.ts.map +1 -1
  174. package/dist/PowerSearch/formatTokenValue.js +6 -3
  175. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  176. package/dist/RadioList/RadioListItem.js +16 -3
  177. package/dist/Resizable/ResizeHandle.d.ts +5 -5
  178. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  179. package/dist/Resizable/ResizeHandle.js +44 -5
  180. package/dist/Resizable/resizablePane.d.ts +58 -0
  181. package/dist/Resizable/resizablePane.d.ts.map +1 -0
  182. package/dist/Resizable/resizablePane.js +137 -0
  183. package/dist/Resizable/useResizable.d.ts +7 -0
  184. package/dist/Resizable/useResizable.d.ts.map +1 -1
  185. package/dist/Resizable/useResizable.js +13 -0
  186. package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
  187. package/dist/SelectableCard/SelectableCard.d.ts +1 -1
  188. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  189. package/dist/SelectableCard/SelectableCard.js +5 -1
  190. package/dist/Selector/Selector.d.ts.map +1 -1
  191. package/dist/Selector/Selector.js +51 -10
  192. package/dist/SideNav/SideNav.d.ts +9 -3
  193. package/dist/SideNav/SideNav.d.ts.map +1 -1
  194. package/dist/SideNav/SideNav.js +45 -10
  195. package/dist/Slider/sliderTouchController.js +2 -2
  196. package/dist/Spinner/Spinner.d.ts.map +1 -1
  197. package/dist/Spinner/Spinner.js +14 -71
  198. package/dist/Spinner/index.d.ts +1 -1
  199. package/dist/Spinner/index.d.ts.map +1 -1
  200. package/dist/Spinner/spinnerRotor.d.ts +28 -0
  201. package/dist/Spinner/spinnerRotor.d.ts.map +1 -0
  202. package/dist/Spinner/spinnerRotor.js +98 -0
  203. package/dist/Stack/StackItem.d.ts +8 -4
  204. package/dist/Stack/StackItem.d.ts.map +1 -1
  205. package/dist/Stack/StackItem.js +17 -4
  206. package/dist/Switch/Switch.d.ts.map +1 -1
  207. package/dist/Switch/Switch.js +13 -10
  208. package/dist/Switch/useSwitchTouch.js +4 -4
  209. package/dist/TabList/Tab.d.ts.map +1 -1
  210. package/dist/TabList/Tab.js +5 -1
  211. package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
  212. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  213. package/dist/Timestamp/Timestamp.js +14 -8
  214. package/dist/Timestamp/formatInstant.d.ts +10 -1
  215. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  216. package/dist/Timestamp/formatInstant.js +13 -27
  217. package/dist/Timestamp/tooltipEntries.d.ts +5 -3
  218. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
  219. package/dist/Timestamp/tooltipEntries.js +6 -4
  220. package/dist/Token/Token.d.ts.map +1 -1
  221. package/dist/Token/Token.js +9 -0
  222. package/dist/Token/TokenLink.d.ts +1 -1
  223. package/dist/Token/TokenLink.d.ts.map +1 -1
  224. package/dist/Token/TokenLink.js +4 -1
  225. package/dist/Tokenizer/Tokenizer.d.ts +14 -10
  226. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  227. package/dist/Tokenizer/Tokenizer.js +189 -89
  228. package/dist/Tooltip/useTooltip.d.ts +3 -1
  229. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  230. package/dist/Tooltip/useTooltip.js +164 -59
  231. package/dist/TreeList/TreeListItem.d.ts +2 -1
  232. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  233. package/dist/TreeList/TreeListItem.js +27 -3
  234. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  235. package/dist/Typeahead/BaseTypeahead.js +2 -1
  236. package/dist/animator/createAnimator.js +2 -2
  237. package/dist/astryx.css +362 -325
  238. package/dist/gestures/GestureHost.d.ts +2 -2
  239. package/dist/gestures/GestureHost.d.ts.map +1 -1
  240. package/dist/gestures/GestureHost.js +21 -6
  241. package/dist/gestures/PanGestureRecognizer.d.ts +11 -1
  242. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  243. package/dist/gestures/PanGestureRecognizer.js +15 -1
  244. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +6 -1
  245. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
  246. package/dist/gestures/nativeScroll.d.ts +3 -6
  247. package/dist/gestures/nativeScroll.d.ts.map +1 -1
  248. package/dist/gestures/nativeScroll.js +7 -52
  249. package/dist/gestures/wheelSegmenter.d.ts +68 -1
  250. package/dist/gestures/wheelSegmenter.d.ts.map +1 -1
  251. package/dist/gestures/wheelSegmenter.js +199 -10
  252. package/dist/hooks/StackItemScrollRegion.d.ts +31 -0
  253. package/dist/hooks/StackItemScrollRegion.d.ts.map +1 -0
  254. package/dist/hooks/StackItemScrollRegion.js +40 -0
  255. package/dist/hooks/computeOverflow.d.ts +2 -0
  256. package/dist/hooks/computeOverflow.d.ts.map +1 -1
  257. package/dist/hooks/computeOverflow.js +1 -1
  258. package/dist/hooks/menuPressGesture.d.ts +133 -21
  259. package/dist/hooks/menuPressGesture.d.ts.map +1 -1
  260. package/dist/hooks/menuPressGesture.js +155 -30
  261. package/dist/hooks/menuPressValues.d.ts +16 -0
  262. package/dist/hooks/menuPressValues.d.ts.map +1 -0
  263. package/dist/hooks/menuPressValues.js +18 -0
  264. package/dist/hooks/useClickableContainer.d.ts +36 -7
  265. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  266. package/dist/hooks/useClickableContainer.js +63 -14
  267. package/dist/hooks/useFocusTrap.d.ts +10 -0
  268. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  269. package/dist/hooks/useFocusTrap.js +225 -132
  270. package/dist/hooks/useKeyboardHighlight.d.ts +17 -0
  271. package/dist/hooks/useKeyboardHighlight.d.ts.map +1 -0
  272. package/dist/hooks/useKeyboardHighlight.js +38 -0
  273. package/dist/hooks/useMediaQuery.d.ts +2 -1
  274. package/dist/hooks/useMediaQuery.d.ts.map +1 -1
  275. package/dist/hooks/useMediaQuery.js +37 -12
  276. package/dist/hooks/useMenuPress.d.ts +34 -1
  277. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  278. package/dist/hooks/useMenuPress.js +93 -5
  279. package/dist/hooks/useOverflow.d.ts +10 -0
  280. package/dist/hooks/useOverflow.d.ts.map +1 -1
  281. package/dist/hooks/useOverflow.js +214 -23
  282. package/dist/hooks/usePressFeedback.d.ts +6 -6
  283. package/dist/hooks/usePressFeedback.js +6 -6
  284. package/dist/hooks/useScrollRegionProps.d.ts +35 -0
  285. package/dist/hooks/useScrollRegionProps.d.ts.map +1 -0
  286. package/dist/hooks/useScrollRegionProps.js +51 -0
  287. package/dist/i18n/InternationalizationContext.d.ts +6 -0
  288. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  289. package/dist/i18n/InternationalizationProvider.d.ts +23 -3
  290. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  291. package/dist/i18n/InternationalizationProvider.js +8 -3
  292. package/dist/i18n/generated-english/DateInput/DateInput.generated.d.ts.map +1 -1
  293. package/dist/i18n/generated-english/DateInput/DateInput.generated.js +0 -1
  294. package/dist/i18n/generated-english/DateInput/useTypedDate.generated.d.ts +8 -0
  295. package/dist/i18n/generated-english/DateInput/useTypedDate.generated.d.ts.map +1 -0
  296. package/dist/i18n/generated-english/DateInput/useTypedDate.generated.js +16 -0
  297. package/dist/i18n/generated-english/DateTimeInput/DateTimeInput.generated.d.ts.map +1 -1
  298. package/dist/i18n/generated-english/DateTimeInput/DateTimeInput.generated.js +0 -1
  299. package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts +8 -0
  300. package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts.map +1 -0
  301. package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.js +12 -0
  302. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  303. package/dist/i18n/generated-locales/en.generated.js +5 -0
  304. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  305. package/dist/i18n/generated-locales/pseudo.generated.js +5 -0
  306. package/dist/i18n/index.d.ts +2 -0
  307. package/dist/i18n/index.d.ts.map +1 -1
  308. package/dist/i18n/index.js +1 -0
  309. package/dist/i18n/useCalendarSystem.d.ts +19 -0
  310. package/dist/i18n/useCalendarSystem.d.ts.map +1 -0
  311. package/dist/i18n/useCalendarSystem.js +61 -0
  312. package/dist/internal/calendar-system.d.ts +18 -0
  313. package/dist/internal/calendar-system.d.ts.map +1 -0
  314. package/dist/internal/calendar-system.js +19 -0
  315. package/dist/internal/spinner-rotor.d.ts +16 -0
  316. package/dist/internal/spinner-rotor.d.ts.map +1 -0
  317. package/dist/internal/spinner-rotor.js +16 -0
  318. package/dist/internal/touch.d.ts +20 -2
  319. package/dist/internal/touch.d.ts.map +1 -1
  320. package/dist/internal/touch.js +29 -3
  321. package/dist/motion/animator.d.ts +5 -5
  322. package/dist/motion/animator.d.ts.map +1 -1
  323. package/dist/motion/animator.js +7 -6
  324. package/dist/motion/carryAnimations.d.ts +58 -0
  325. package/dist/motion/carryAnimations.d.ts.map +1 -0
  326. package/dist/motion/carryAnimations.js +305 -0
  327. package/dist/motion/transientCopy.d.ts +4 -2
  328. package/dist/motion/transientCopy.d.ts.map +1 -1
  329. package/dist/motion/transientCopy.js +34 -5
  330. package/dist/motion/uikit/gestures.d.ts +41 -0
  331. package/dist/motion/uikit/gestures.d.ts.map +1 -1
  332. package/dist/motion/uikit/gestures.js +25 -0
  333. package/dist/motion/uikit/index.d.ts +1 -1
  334. package/dist/motion/uikit/index.d.ts.map +1 -1
  335. package/dist/motion/uikit/index.js +1 -1
  336. package/dist/motion/uikit/timing.d.ts +14 -10
  337. package/dist/motion/uikit/timing.d.ts.map +1 -1
  338. package/dist/motion/uikit/timing.js +7 -5
  339. package/dist/privateAttributes/menuPressLit.stylex.d.ts +52 -0
  340. package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +1 -0
  341. package/dist/privateAttributes/menuPressLit.stylex.js +99 -0
  342. package/dist/privateAttributes/press.stylex.d.ts +142 -0
  343. package/dist/privateAttributes/press.stylex.d.ts.map +1 -0
  344. package/dist/privateAttributes/press.stylex.js +101 -0
  345. package/dist/theme/themeMotion.d.ts +3 -2
  346. package/dist/theme/themeMotion.d.ts.map +1 -1
  347. package/dist/theme/themeMotion.js +3 -2
  348. package/dist/theme/tokens.stylex.d.ts +2 -2
  349. package/dist/theme/tokens.stylex.js +1 -1
  350. package/dist/utils/calendarDateParser.d.ts +110 -0
  351. package/dist/utils/calendarDateParser.d.ts.map +1 -0
  352. package/dist/utils/calendarDateParser.js +1225 -0
  353. package/dist/utils/calendarSystem.d.ts +176 -0
  354. package/dist/utils/calendarSystem.d.ts.map +1 -0
  355. package/dist/utils/calendarSystem.js +569 -0
  356. package/dist/utils/coarsePointer.d.ts +5 -0
  357. package/dist/utils/coarsePointer.d.ts.map +1 -1
  358. package/dist/utils/coarsePointer.js +22 -13
  359. package/dist/utils/flatTree.d.ts +6 -3
  360. package/dist/utils/flatTree.d.ts.map +1 -1
  361. package/dist/utils/flatTree.js +13 -3
  362. package/dist/utils/focusReturn.d.ts +34 -13
  363. package/dist/utils/focusReturn.d.ts.map +1 -1
  364. package/dist/utils/focusReturn.js +64 -17
  365. package/dist/utils/hitArea.d.ts +8 -1
  366. package/dist/utils/hitArea.d.ts.map +1 -1
  367. package/dist/utils/hitArea.js +99 -9
  368. package/dist/utils/inputPresentation.d.ts +9 -1
  369. package/dist/utils/inputPresentation.d.ts.map +1 -1
  370. package/dist/utils/inputPresentation.js +15 -1
  371. package/dist/utils/inputStream.d.ts +14 -1
  372. package/dist/utils/inputStream.d.ts.map +1 -1
  373. package/dist/utils/inputStream.js +40 -9
  374. package/dist/utils/interactionOverlay.stylex.d.ts +5 -9
  375. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  376. package/dist/utils/interactionOverlay.stylex.js +29 -31
  377. package/dist/utils/plainDate.d.ts +38 -7
  378. package/dist/utils/plainDate.d.ts.map +1 -1
  379. package/dist/utils/plainDate.js +92 -18
  380. package/dist/utils/pressFeedback.d.ts +10 -2
  381. package/dist/utils/pressFeedback.d.ts.map +1 -1
  382. package/dist/utils/pressFeedback.js +40 -15
  383. package/dist/utils/pressGesture.d.ts +12 -6
  384. package/dist/utils/pressGesture.d.ts.map +1 -1
  385. package/dist/utils/pressGesture.js +26 -8
  386. package/dist/utils/scrollView.d.ts +25 -0
  387. package/dist/utils/scrollView.d.ts.map +1 -0
  388. package/dist/utils/scrollView.js +108 -0
  389. package/dist/utils/selectionLock.d.ts.map +1 -1
  390. package/dist/utils/selectionLock.js +1 -10
  391. package/dist/utils/touchHitArea.d.ts +30 -0
  392. package/dist/utils/touchHitArea.d.ts.map +1 -0
  393. package/dist/utils/touchHitArea.js +167 -0
  394. package/dist/utils/touchHitArea.stylex.d.ts +38 -0
  395. package/dist/utils/touchHitArea.stylex.d.ts.map +1 -0
  396. package/dist/utils/touchHitArea.stylex.js +48 -0
  397. package/dist/utils/triggerFocus.d.ts +5 -0
  398. package/dist/utils/triggerFocus.d.ts.map +1 -1
  399. package/dist/utils/triggerFocus.js +9 -2
  400. package/locales/en.json +21 -1
  401. package/locales/pseudo.json +15 -0
  402. package/package.json +15 -4
  403. package/src/AppShell/observeMainOverflow.ts +9 -6
  404. package/src/BottomSheet/BottomSheet.tsx +45 -3
  405. package/src/BottomSheet/BottomSheetPanel.tsx +64 -7
  406. package/src/BottomSheet/bottomSheetStack.ts +4 -3
  407. package/src/BottomSheet/useSheetGestures.ts +83 -20
  408. package/src/Calendar/Calendar.doc.mjs +76 -6
  409. package/src/Calendar/Calendar.tsx +123 -104
  410. package/src/Calendar/getInitialFocusDate.ts +22 -8
  411. package/src/Calendar/hooks/useCalendarDays.ts +89 -63
  412. package/src/Calendar/hooks/useCalendarNavigation.ts +68 -26
  413. package/src/CheckboxInput/CheckboxInput.doc.mjs +5 -4
  414. package/src/CheckboxInput/CheckboxInput.tsx +105 -20
  415. package/src/CheckboxInput/CheckboxRowPressContext.ts +22 -0
  416. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  417. package/src/CheckboxList/CheckboxListItem.doc.mjs +3 -3
  418. package/src/CheckboxList/CheckboxListItem.tsx +35 -14
  419. package/src/ClickableCard/ClickableCard.tsx +8 -1
  420. package/src/ContextMenu/ContextMenu.tsx +2 -0
  421. package/src/DateInput/DateInput.doc.mjs +45 -12
  422. package/src/DateInput/DateInput.tsx +165 -179
  423. package/src/DateInput/MonthScroller.tsx +39 -37
  424. package/src/DateInput/MonthYearWheels.tsx +68 -47
  425. package/src/DateInput/PickerWheel.tsx +7 -2
  426. package/src/DateInput/TouchDateField.tsx +45 -17
  427. package/src/DateInput/monthGeometry.ts +110 -8
  428. package/src/DateInput/useCalendarPresentation.ts +64 -0
  429. package/src/DateInput/useTypedDate.ts +469 -0
  430. package/src/DateInput/wheelStrip.ts +2 -2
  431. package/src/DateRangeInput/DateRangeInput.doc.mjs +9 -0
  432. package/src/DateRangeInput/DateRangeInput.tsx +43 -9
  433. package/src/DateTimeInput/DateTimeInput.doc.mjs +42 -9
  434. package/src/DateTimeInput/DateTimeInput.tsx +138 -148
  435. package/src/DateTimeInput/TouchDateTimeField.tsx +31 -16
  436. package/src/Dialog/Dialog.doc.mjs +1 -1
  437. package/src/Dialog/Dialog.tsx +14 -2
  438. package/src/Drawer/Drawer.doc.mjs +1 -1
  439. package/src/Drawer/useDrawerDialogPresence.ts +9 -2
  440. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -2
  441. package/src/DropdownMenu/DropdownMenu.tsx +31 -7
  442. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -1
  443. package/src/DropdownMenu/DropdownMenuItem.tsx +8 -4
  444. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -1
  445. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -1
  446. package/src/FileInput/FileInput.tsx +16 -2
  447. package/src/HoverCard/useHoverCard.tsx +3 -1
  448. package/src/Icon/useIcon.doc.mjs +94 -0
  449. package/src/InfoTip/InfoTip.doc.mjs +26 -2
  450. package/src/InfoTip/InfoTip.tsx +6 -27
  451. package/src/InputGroup/InputGroup.doc.mjs +4 -3
  452. package/src/InputGroup/InputGroup.tsx +4 -3
  453. package/src/InputGroup/InputGroupText.doc.mjs +45 -3
  454. package/src/InputGroup/InputGroupText.tsx +2 -1
  455. package/src/InputGroup/groupStyles.ts +3 -1
  456. package/src/Item/Item.tsx +139 -35
  457. package/src/Item/ItemListRowContext.tsx +18 -0
  458. package/src/Layer/holdEntranceInput.ts +84 -0
  459. package/src/Layer/inputHold.ts +734 -0
  460. package/src/Layer/outsidePress.ts +18 -1
  461. package/src/Layer/preserveAnimations.ts +12 -275
  462. package/src/Layer/useBackdropPress.ts +41 -1
  463. package/src/Layer/useLayer.tsx +77 -4
  464. package/src/Layer/useTouchTrigger.ts +4 -0
  465. package/src/Layout/LayoutContent.doc.mjs +6 -3
  466. package/src/Layout/LayoutContent.tsx +10 -4
  467. package/src/Layout/LayoutPanel.doc.mjs +14 -11
  468. package/src/Layout/LayoutPanel.tsx +113 -16
  469. package/src/Lightbox/Lightbox.tsx +4 -0
  470. package/src/List/List.doc.mjs +10 -1
  471. package/src/List/List.tsx +5 -0
  472. package/src/List/ListItem.tsx +63 -8
  473. package/src/Markdown/parser.ts +65 -42
  474. package/src/MetadataList/MetadataList.tsx +1 -1
  475. package/src/MobileNav/MobileNav.doc.mjs +3 -3
  476. package/src/MobileNav/MobileNav.tsx +39 -1
  477. package/src/MoreMenu/MoreMenu.doc.mjs +2 -4
  478. package/src/MoreMenu/MoreMenu.tsx +5 -3
  479. package/src/MultiSelector/MultiSelector.tsx +7 -17
  480. package/src/NumberInput/NumberInput.doc.mjs +1 -1
  481. package/src/NumberInput/NumberInput.tsx +259 -11
  482. package/src/OverflowList/OverflowList.tsx +15 -9
  483. package/src/Popover/usePopover.tsx +1 -0
  484. package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
  485. package/src/PowerSearch/PowerSearch.tsx +20 -3
  486. package/src/PowerSearch/PowerSearchBottomSheet.tsx +4 -1
  487. package/src/PowerSearch/PowerSearchToken.tsx +3 -0
  488. package/src/PowerSearch/formatFilterValue.ts +21 -11
  489. package/src/PowerSearch/formatTokenValue.ts +7 -1
  490. package/src/RadioList/RadioListItem.doc.mjs +3 -2
  491. package/src/RadioList/RadioListItem.tsx +16 -6
  492. package/src/Resizable/Resizable.doc.mjs +40 -12
  493. package/src/Resizable/ResizeHandle.tsx +68 -6
  494. package/src/Resizable/resizablePane.ts +179 -0
  495. package/src/Resizable/useResizable.ts +15 -0
  496. package/src/SegmentedControl/useSegmentedSelectionMotion.ts +3 -3
  497. package/src/SelectableCard/SelectableCard.tsx +8 -1
  498. package/src/Selector/Selector.tsx +83 -8
  499. package/src/SideNav/SideNav.doc.mjs +2 -2
  500. package/src/SideNav/SideNav.tsx +87 -12
  501. package/src/Slider/sliderTouchController.ts +2 -2
  502. package/src/Spinner/Spinner.tsx +7 -89
  503. package/src/Spinner/index.ts +1 -5
  504. package/src/Spinner/spinnerRotor.ts +113 -0
  505. package/src/Stack/Stack.doc.mjs +1 -1
  506. package/src/Stack/StackItem.tsx +27 -13
  507. package/src/Switch/Switch.tsx +7 -6
  508. package/src/Switch/useSwitchTouch.ts +4 -4
  509. package/src/TabList/Tab.tsx +5 -1
  510. package/src/Table/plugins/sortable/useTableSortable.tsx +1 -1
  511. package/src/Timestamp/Timestamp.doc.mjs +3 -3
  512. package/src/Timestamp/Timestamp.tsx +13 -8
  513. package/src/Timestamp/formatInstant.ts +49 -28
  514. package/src/Timestamp/tooltipEntries.ts +7 -3
  515. package/src/Token/Token.tsx +18 -1
  516. package/src/Token/TokenLink.tsx +7 -1
  517. package/src/Tokenizer/Tokenizer.doc.mjs +3 -3
  518. package/src/Tokenizer/Tokenizer.tsx +252 -115
  519. package/src/Tooltip/useTooltip.tsx +180 -80
  520. package/src/TreeList/TreeListItem.tsx +36 -2
  521. package/src/Typeahead/BaseTypeahead.tsx +2 -0
  522. package/src/animator/createAnimator.ts +3 -3
  523. package/src/gestures/GestureHost.ts +21 -5
  524. package/src/gestures/PanGestureRecognizer.ts +19 -2
  525. package/src/gestures/README.md +14 -5
  526. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +6 -1
  527. package/src/gestures/nativeScroll.ts +12 -68
  528. package/src/gestures/wheelSegmenter.ts +243 -10
  529. package/src/hooks/StackItemScrollRegion.tsx +57 -0
  530. package/src/hooks/computeOverflow.ts +1 -1
  531. package/src/hooks/menuPressGesture.ts +244 -38
  532. package/src/hooks/menuPressValues.ts +18 -0
  533. package/src/hooks/useClickableContainer.doc.mjs +8 -2
  534. package/src/hooks/useClickableContainer.ts +93 -15
  535. package/src/hooks/useFocusTrap.ts +268 -151
  536. package/src/hooks/useKeyboardHighlight.ts +71 -0
  537. package/src/hooks/useMediaQuery.ts +41 -12
  538. package/src/hooks/useMenuPress.ts +118 -11
  539. package/src/hooks/useOverflow.ts +321 -75
  540. package/src/hooks/usePressFeedback.doc.mjs +3 -3
  541. package/src/hooks/usePressFeedback.ts +6 -6
  542. package/src/hooks/useScrollRegionProps.ts +76 -0
  543. package/src/i18n/InternationalizationContext.ts +6 -0
  544. package/src/i18n/InternationalizationProvider.doc.mjs +29 -1
  545. package/src/i18n/InternationalizationProvider.tsx +35 -3
  546. package/src/i18n/generated-english/DateInput/DateInput.generated.ts +0 -1
  547. package/src/i18n/generated-english/DateInput/useTypedDate.generated.ts +17 -0
  548. package/src/i18n/generated-english/DateTimeInput/DateTimeInput.generated.ts +0 -1
  549. package/src/i18n/generated-english/hooks/useScrollRegionProps.generated.ts +13 -0
  550. package/src/i18n/generated-locales/en.generated.ts +7 -0
  551. package/src/i18n/generated-locales/pseudo.generated.ts +7 -0
  552. package/src/i18n/index.ts +2 -0
  553. package/src/i18n/useCalendarSystem.ts +95 -0
  554. package/src/internal/calendar-system.ts +26 -0
  555. package/src/internal/spinner-rotor.ts +20 -0
  556. package/src/internal/touch.ts +43 -2
  557. package/src/motion/MEASUREMENTS.md +11 -0
  558. package/src/motion/README.md +3 -3
  559. package/src/motion/animator.ts +7 -7
  560. package/src/motion/carryAnimations.ts +389 -0
  561. package/src/motion/transientCopy.ts +45 -5
  562. package/src/motion/uikit/gestures.ts +22 -0
  563. package/src/motion/uikit/index.ts +1 -0
  564. package/src/motion/uikit/timing.ts +7 -5
  565. package/src/privateAttributes/menuPressLit.stylex.ts +104 -0
  566. package/src/privateAttributes/press.stylex.ts +261 -0
  567. package/src/theme/themeMotion.ts +3 -2
  568. package/src/theme/tokens.stylex.ts +1 -1
  569. package/src/utils/calendarDateParser.ts +1385 -0
  570. package/src/utils/calendarSystem.ts +832 -0
  571. package/src/utils/coarsePointer.ts +27 -14
  572. package/src/utils/flatTree.ts +15 -3
  573. package/src/utils/focusReturn.ts +78 -18
  574. package/src/utils/hitArea.ts +138 -9
  575. package/src/utils/inputPresentation.ts +21 -1
  576. package/src/utils/inputStream.ts +47 -10
  577. package/src/utils/interactionOverlay.stylex.ts +19 -22
  578. package/src/utils/plainDate.ts +130 -19
  579. package/src/utils/pressFeedback.ts +46 -14
  580. package/src/utils/pressGesture.ts +35 -9
  581. package/src/utils/scrollView.ts +135 -0
  582. package/src/utils/selectionLock.ts +5 -10
  583. package/src/utils/touchHitArea.stylex.ts +56 -0
  584. package/src/utils/touchHitArea.ts +177 -0
  585. package/src/utils/triggerFocus.ts +17 -4
@@ -0,0 +1,1385 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file calendarDateParser.ts
5
+ * @input Date text, locale, calendar; Intl, calendarSystem, unicodeDigits
6
+ * @output Cached per-locale lexicons, a typed-date parser with distinct rejection
7
+ * kinds, committed display text, the format hint, and calendar display names
8
+ * @position Core utility; used by DateInput and DateTimeInput through useTypedDate
9
+ *
10
+ * SYNC: When modified, update:
11
+ * - /packages/core/src/utils/calendarDateParser.test.ts
12
+ * - /packages/core/src/utils/calendarDateParser.roundtrip.test.ts
13
+ */
14
+
15
+ import type {PlainDate} from './dateTypes';
16
+ import type {Locale} from '../i18n/types';
17
+ import {
18
+ type CalendarSystem,
19
+ type CalendarDateFields,
20
+ calendarDateFromFields,
21
+ calendarMonthOf,
22
+ calendarMonthsInYear,
23
+ formatCalendarDate,
24
+ isCalendarSystemAvailable,
25
+ isGregorianAligned,
26
+ } from './calendarSystem';
27
+ import {
28
+ type SharedDateFormat,
29
+ SHARED_DATE_FORMAT_OPTIONS,
30
+ formatSharedDate,
31
+ getDaysInMonth,
32
+ plainDateAddDays,
33
+ plainDateDiffDays,
34
+ plainDateFormat,
35
+ plainDateFormatToParts,
36
+ plainDateToday,
37
+ } from './plainDate';
38
+ import {INVISIBLE_CHARS, toAsciiDigits} from './unicodeDigits';
39
+
40
+ /** How a typed text reads in a calendar. */
41
+ export type CalendarDateParseResult =
42
+ | {readonly kind: 'empty'}
43
+ /** Part of a date: nothing to report yet. */
44
+ | {readonly kind: 'incomplete'}
45
+ /** Text that cannot become a date. */
46
+ | {readonly kind: 'unreadable'}
47
+ /** A readable date that the calendar does not have. */
48
+ | {readonly kind: 'nonexistent'; readonly isComplete: boolean}
49
+ /** A date that the constraints reject. */
50
+ | {
51
+ readonly kind: 'outOfRange';
52
+ readonly date: PlainDate;
53
+ readonly isComplete: boolean;
54
+ }
55
+ /**
56
+ * A date. `isComplete` is true when more typing cannot change its meaning,
57
+ * so a field may report it while the person types.
58
+ */
59
+ | {
60
+ readonly kind: 'value';
61
+ readonly date: PlainDate;
62
+ readonly isComplete: boolean;
63
+ };
64
+
65
+ export interface CalendarDateParseOptions {
66
+ /** The Gregorian date that two-digit and omitted years resolve against. */
67
+ readonly today?: PlainDate;
68
+ /** Reads ISO-shaped text as a Gregorian ISO date, as `system_date` shows. */
69
+ readonly isoIsGregorian?: boolean;
70
+ /** Returns true for a date that may not be chosen. */
71
+ readonly isDateDisabled?: (date: PlainDate) => boolean;
72
+ }
73
+
74
+ type DateField = 'year' | 'month' | 'day';
75
+
76
+ interface EraArithmetic {
77
+ /** The Gregorian year before era year 1, or after it for a backward era. */
78
+ readonly base: number;
79
+ readonly isForward: boolean;
80
+ }
81
+
82
+ /** What a locale writes for one calendar, learned from Intl. */
83
+ export interface CalendarDateLexicon {
84
+ readonly locale: Locale;
85
+ readonly calendar: CalendarSystem;
86
+ readonly order: ReadonlyArray<DateField>;
87
+ readonly nameOrder: ReadonlyArray<DateField>;
88
+ readonly months: ReadonlyMap<string, ReadonlySet<string>>;
89
+ readonly monthTemplates: ReadonlyArray<MonthTemplate>;
90
+ readonly leapMarkers: ReadonlySet<string>;
91
+ readonly eras: ReadonlyMap<string, string>;
92
+ readonly yearAliases: ReadonlyMap<string, string>;
93
+ readonly trailers: ReadonlySet<string>;
94
+ readonly yearMarkers: ReadonlySet<string>;
95
+ readonly separators: ReadonlySet<string>;
96
+ readonly weekdays: ReadonlySet<string>;
97
+ readonly vocabulary: ReadonlyArray<string>;
98
+ readonly vocabularySet: ReadonlySet<string>;
99
+ readonly isNumericMonthOrdinal: boolean;
100
+ readonly eraArithmetic: ReadonlyMap<string, EraArithmetic>;
101
+ }
102
+
103
+ interface MonthTemplate {
104
+ readonly pre: string;
105
+ readonly post: string;
106
+ readonly isLeap: boolean;
107
+ }
108
+
109
+ const NUMERIC: Intl.DateTimeFormatOptions = {
110
+ year: 'numeric',
111
+ month: 'numeric',
112
+ day: 'numeric',
113
+ };
114
+ const LONG = SHARED_DATE_FORMAT_OPTIONS.date_long;
115
+ const YEAR_ONLY: Intl.DateTimeFormatOptions = {year: 'numeric'};
116
+ const ERA_AND_YEAR: Intl.DateTimeFormatOptions = {
117
+ era: 'short',
118
+ year: 'numeric',
119
+ };
120
+ const LONG_WITH_ERA: Intl.DateTimeFormatOptions = {...LONG, era: 'long'};
121
+ const ERA_WIDTHS: Readonly<Record<string, Intl.DateTimeFormatOptions>> = {
122
+ long: {era: 'long', year: 'numeric'},
123
+ short: ERA_AND_YEAR,
124
+ narrow: {era: 'narrow', year: 'numeric'},
125
+ };
126
+ const WEEKDAY_FORMATS: ReadonlyArray<Intl.DateTimeFormatOptions> = [
127
+ {weekday: 'long'},
128
+ {weekday: 'short'},
129
+ {...LONG, weekday: 'long'},
130
+ {...LONG, weekday: 'short'},
131
+ ];
132
+ const LONG_WITH_WEEKDAY: Intl.DateTimeFormatOptions = {
133
+ ...LONG,
134
+ weekday: 'short',
135
+ };
136
+ // Built at runtime: the Babel copy in a Next.js source build rejects a
137
+ // `\p{...}` regex literal.
138
+ const LETTER = new RegExp('[\\p{L}\\p{M}]', 'u');
139
+ const SEPARATOR = new RegExp('[\\p{P}\\p{S}]', 'u');
140
+ const ONLY_SEPARATORS = new RegExp('^[\\p{P}\\p{S}]+$', 'u');
141
+ const NOT_WORDS = new RegExp('[\\d\\p{P}\\p{S}]+', 'gu');
142
+ const LATIN_MARKS = new RegExp('(\\p{Script=Latin})\\p{Mn}+', 'gu');
143
+ // Scripts written without spaces between words: a name may run into the
144
+ // next word, so a name match there needs no word boundary.
145
+ const SPACELESS = new RegExp(
146
+ '[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}\\p{Script=Thai}\\p{Script=Lao}\\p{Script=Khmer}\\p{Script=Myanmar}]',
147
+ 'u',
148
+ );
149
+ const LUNISOLAR: ReadonlySet<CalendarSystem> = new Set(['chinese', 'dangi']);
150
+ const WITHOUT_ERA: ReadonlySet<CalendarSystem> = new Set([
151
+ 'gregory',
152
+ 'iso8601',
153
+ 'chinese',
154
+ 'dangi',
155
+ ]);
156
+
157
+ /**
158
+ * One ISO date inside each era of a calendar. Intl supplies the names; the
159
+ * era codes are the Temporal and ECMA-402 identifiers.
160
+ */
161
+ const ERA_SAMPLES: Readonly<Record<string, Readonly<Record<string, string>>>> =
162
+ {
163
+ gregory: {ce: '2000-06-15', bce: '-000100-06-15'},
164
+ japanese: {
165
+ meiji: '1900-06-15',
166
+ taisho: '1920-06-15',
167
+ showa: '1950-06-15',
168
+ heisei: '2000-06-15',
169
+ reiwa: '2026-06-15',
170
+ },
171
+ roc: {roc: '2026-06-15', broc: '1900-06-15'},
172
+ buddhist: {be: '2026-06-15'},
173
+ ethiopic: {am: '2026-06-15', aa: '-005600-06-15'},
174
+ ethioaa: {aa: '2026-06-15'},
175
+ coptic: {am: '2026-06-15'},
176
+ persian: {ap: '2026-06-15'},
177
+ hebrew: {am: '2026-06-15'},
178
+ indian: {shaka: '2026-06-15'},
179
+ 'islamic-umalqura': {ah: '2026-06-15'},
180
+ 'islamic-civil': {ah: '2026-06-15'},
181
+ 'islamic-tbla': {ah: '2026-06-15'},
182
+ };
183
+
184
+ /** The calendar a text field reads and writes. `iso8601` writes as Gregorian. */
185
+ function textCalendar(calendar: CalendarSystem): CalendarSystem {
186
+ if (calendar === 'iso8601' || !isCalendarSystemAvailable(calendar)) {
187
+ return 'gregory';
188
+ }
189
+ return calendar;
190
+ }
191
+
192
+ // =============================================================================
193
+ // Intl access
194
+ // =============================================================================
195
+
196
+ /** Part types such as `relatedYear` and `yearName` are absent from older TS libs. */
197
+ function partType(part: Intl.DateTimeFormatPart): string {
198
+ return part.type;
199
+ }
200
+
201
+ function isoToPlainDate(iso: string): PlainDate {
202
+ const match = /^(-?\d+)-(\d+)-(\d+)$/.exec(iso);
203
+ if (match == null) {
204
+ throw new RangeError(`Bad sample date ${iso}`);
205
+ }
206
+ return {year: +match[1], month: +match[2], day: +match[3]};
207
+ }
208
+
209
+ function partsOf(
210
+ locale: Locale,
211
+ calendar: CalendarSystem,
212
+ options: Intl.DateTimeFormatOptions,
213
+ pd: PlainDate,
214
+ ): Intl.DateTimeFormatPart[] {
215
+ return plainDateFormatToParts(pd, {...options, calendar}, locale);
216
+ }
217
+
218
+ // =============================================================================
219
+ // Normalization
220
+ // =============================================================================
221
+
222
+ /** Text in one comparable form: ASCII digits, lower case, no Latin accents. */
223
+ export function normalizeDateText(text: string, locale: Locale): string {
224
+ // Most typed text is printable ASCII; skip the Unicode passes it does not need.
225
+ if (/^[\u0020-\u007e]*$/.test(text)) {
226
+ const ascii = text.replaceAll('`', "'");
227
+ return /[A-Z]/.test(ascii) ? ascii.toLocaleLowerCase(locale) : ascii;
228
+ }
229
+ const folded = text
230
+ .normalize('NFKC')
231
+ .replace(INVISIBLE_CHARS, '')
232
+ // Pasted text can carry bidi embeddings.
233
+ .replace(/[\u202a-\u202e]/g, '')
234
+ // People type an ASCII apostrophe or quote for Hebrew geresh and
235
+ // gershayim, and for the ʻ of Islamic month names such as Dhuʻl-Hijjah.
236
+ .replace(/[\u05f3\u2018\u2019\u02bb\u02bc\u02bf`]/g, "'")
237
+ .replace(/[\u05f4\u201c\u201d]/g, '"');
238
+ let result = (toAsciiDigits(folded) ?? folded).toLocaleLowerCase(locale);
239
+ if (/[\u00c0-\u024f\u1e00-\u1eff]/.test(result)) {
240
+ // Fold Latin accents only: in other scripts a mark is part of the letter.
241
+ result = result
242
+ .normalize('NFD')
243
+ .replace(LATIN_MARKS, '$1')
244
+ .normalize('NFC');
245
+ }
246
+ return result;
247
+ }
248
+
249
+ // =============================================================================
250
+ // Lexicon
251
+ // =============================================================================
252
+
253
+ const lexicons = new Map<string, CalendarDateLexicon>();
254
+
255
+ /**
256
+ * The lexicon for a locale and calendar, built on first use and cached.
257
+ * Building one formats a few hundred dates; call it when a field gains focus.
258
+ */
259
+ export function getCalendarDateLexicon(
260
+ locale: Locale,
261
+ calendar: CalendarSystem,
262
+ ): CalendarDateLexicon {
263
+ const resolved = textCalendar(calendar);
264
+ const key = `${locale}|${resolved}`;
265
+ let lexicon = lexicons.get(key);
266
+ if (lexicon == null) {
267
+ lexicon = buildLexicon(locale, resolved);
268
+ lexicons.set(key, lexicon);
269
+ }
270
+ return lexicon;
271
+ }
272
+
273
+ interface SampledMonth {
274
+ readonly code: string;
275
+ readonly ordinal: number;
276
+ readonly mid: PlainDate;
277
+ readonly monthsInYear: number;
278
+ }
279
+
280
+ /** Months to learn names from: a few calendar years, so leap months appear. */
281
+ function sampleMonths(calendar: CalendarSystem): SampledMonth[] {
282
+ if (isGregorianAligned(calendar)) {
283
+ return Array.from({length: 12}, (_, i) => ({
284
+ code: `M${String(i + 1).padStart(2, '0')}`,
285
+ ordinal: i + 1,
286
+ mid: {year: 2026, month: i + 1, day: 15},
287
+ monthsInYear: 12,
288
+ }));
289
+ }
290
+ const reference = calendarYearOf(calendar, {year: 2026, month: 6, day: 15});
291
+ const sampled: SampledMonth[] = [];
292
+ for (let year = reference - 1; year <= reference + 3; year++) {
293
+ const starts: {code: string; start: PlainDate}[] = [];
294
+ for (let n = 1; n <= 13; n++) {
295
+ for (const leap of ['', 'L']) {
296
+ const code = `M${String(n).padStart(2, '0')}${leap}`;
297
+ const start = calendarDateFromFields(calendar, {
298
+ year,
299
+ monthCode: code,
300
+ day: 1,
301
+ });
302
+ if (start != null) {
303
+ starts.push({code, start});
304
+ }
305
+ }
306
+ }
307
+ starts.sort((a, b) => plainDateDiffDays(b.start, a.start));
308
+ starts.forEach(({code, start}, i) => {
309
+ const month = calendarMonthOf(calendar, start);
310
+ sampled.push({
311
+ code,
312
+ ordinal: i + 1,
313
+ mid: plainDateAddDays(month.start, Math.floor(month.length / 2)),
314
+ monthsInYear: starts.length,
315
+ });
316
+ });
317
+ }
318
+ return sampled;
319
+ }
320
+
321
+ /** The calendar's year number of a Gregorian date, read from Intl. */
322
+ function calendarYearOf(calendar: CalendarSystem, pd: PlainDate): number {
323
+ const parts = partsOf('en', calendar, YEAR_ONLY, pd);
324
+ const year = parts.find(
325
+ p => partType(p) === 'relatedYear' || p.type === 'year',
326
+ );
327
+ return Number(normalizeDateText(year?.value ?? '', 'en'));
328
+ }
329
+
330
+ function fieldOrder(parts: Intl.DateTimeFormatPart[]): DateField[] {
331
+ const order: DateField[] = [];
332
+ for (const part of parts) {
333
+ const type = partType(part) === 'relatedYear' ? 'year' : part.type;
334
+ if (
335
+ (type === 'year' || type === 'month' || type === 'day') &&
336
+ !order.includes(type)
337
+ ) {
338
+ order.push(type);
339
+ }
340
+ }
341
+ return order;
342
+ }
343
+
344
+ function buildLexicon(
345
+ locale: Locale,
346
+ calendar: CalendarSystem,
347
+ ): CalendarDateLexicon {
348
+ const locales: Locale[] = locale === 'en' ? ['en'] : [locale, 'en'];
349
+ const norm = (text: string) => normalizeDateText(text, locale).trim();
350
+ const months = new Map<string, Set<string>>();
351
+ const templates = new Map<string, MonthTemplate>();
352
+ const leapMarkers = new Set<string>();
353
+ const eras = new Map<string, string>();
354
+ const yearAliases = new Map<string, string>();
355
+ const skip = new Set<string>();
356
+ const trailers = new Set<string>();
357
+ const yearMarkers = new Set<string>();
358
+ const separators = new Set<string>();
359
+ const weekdays = new Set<string>();
360
+
361
+ const addSkip = (text: string, words = skip) => {
362
+ for (const word of norm(text).replace(NOT_WORDS, ' ').split(/\s+/)) {
363
+ if (word) {
364
+ words.add(word);
365
+ }
366
+ }
367
+ };
368
+ const addMonth = (text: string, {code, ordinal}: SampledMonth) => {
369
+ const name = norm(text);
370
+ // A month number with affixes: "11月", "9:e mån", "윤6", "6bis". A name
371
+ // can also hold another number: Hungarian "Réb. 1" is the third month.
372
+ const match = /^(\D*)(\d+)(\D*)$/.exec(name);
373
+ const isOwnNumber = [ordinal, Number(code.slice(1, 3))].includes(
374
+ Number(match?.[2]),
375
+ );
376
+ if (!name || (/\d/.test(name) && (!LETTER.test(name) || isOwnNumber))) {
377
+ if (match != null && (match[1] || match[3])) {
378
+ templates.set(`${match[1]}#${match[3]}`, {
379
+ pre: match[1],
380
+ post: match[3],
381
+ isLeap: code.endsWith('L'),
382
+ });
383
+ }
384
+ return;
385
+ }
386
+ let codes = months.get(name);
387
+ if (codes == null) {
388
+ codes = new Set();
389
+ months.set(name, codes);
390
+ }
391
+ codes.add(code);
392
+ };
393
+
394
+ const sampled = sampleMonths(calendar);
395
+ const monthWidths: Intl.DateTimeFormatOptions[] = [
396
+ LONG,
397
+ {year: 'numeric', month: 'short', day: 'numeric'},
398
+ NUMERIC,
399
+ {month: 'long'},
400
+ {month: 'short'},
401
+ ];
402
+ const baseText = new Map<string, string>();
403
+ for (const month of sampled) {
404
+ for (const loc of locales) {
405
+ monthWidths.forEach((options, width) => {
406
+ const text = partsOf(loc, calendar, options, month.mid).find(
407
+ p => p.type === 'month',
408
+ )?.value;
409
+ if (text == null) {
410
+ return;
411
+ }
412
+ const baseKey = `${loc}|${width}|${month.code.slice(0, 3)}`;
413
+ if (month.code.endsWith('L')) {
414
+ // A leap month written as its common month plus a marker ("闰六月").
415
+ const base = baseText.get(baseKey);
416
+ if (base != null && text.includes(base) && !/\d/.test(base)) {
417
+ const marker = norm(text.replace(base, ''));
418
+ if (marker) {
419
+ leapMarkers.add(marker);
420
+ return;
421
+ }
422
+ }
423
+ } else {
424
+ baseText.set(baseKey, text);
425
+ }
426
+ addMonth(text, month);
427
+ });
428
+ }
429
+ }
430
+
431
+ const literalFormats: Intl.DateTimeFormatOptions[] = [];
432
+ for (const options of [
433
+ NUMERIC,
434
+ LONG,
435
+ SHARED_DATE_FORMAT_OPTIONS.date,
436
+ SHARED_DATE_FORMAT_OPTIONS.date_weekday,
437
+ LONG_WITH_WEEKDAY,
438
+ ]) {
439
+ literalFormats.push(options, displayOptions(locale, calendar, options));
440
+ }
441
+ const literalDates = sampled.slice(0, 14).map(m => m.mid);
442
+ for (const loc of locales) {
443
+ for (const options of literalFormats) {
444
+ for (const pd of literalDates) {
445
+ const parts = partsOf(loc, calendar, options, pd);
446
+ for (const [k, part] of parts.entries()) {
447
+ if (part.type === 'literal' || partType(part) === 'yearName') {
448
+ addSkip(part.value);
449
+ }
450
+ if (part.type === 'literal' && parts[k - 1]?.type === 'year') {
451
+ addSkip(part.value, yearMarkers);
452
+ }
453
+ }
454
+ if (loc === locale) {
455
+ const tail = parts.at(-1);
456
+ if (tail?.type === 'literal') {
457
+ const trailer = norm(tail.value).replace(/\s+/g, '');
458
+ if (ONLY_SEPARATORS.test(trailer)) {
459
+ trailers.add(trailer);
460
+ }
461
+ }
462
+ }
463
+ }
464
+ }
465
+ for (let day = 0; day < 7; day++) {
466
+ const pd = {year: 2026, month: 11, day: 1 + day};
467
+ for (const options of WEEKDAY_FORMATS) {
468
+ const text = partsOf(loc, calendar, options, pd).find(
469
+ p => p.type === 'weekday',
470
+ )?.value;
471
+ if (text != null) {
472
+ addSkip(text);
473
+ addSkip(text, weekdays);
474
+ // Vietnamese writes a weekday as a word and a number: "Thứ 2".
475
+ if (/\d/.test(norm(text))) {
476
+ skip.add(norm(text));
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+ const hint = displayOptions(locale, calendar, NUMERIC);
483
+ for (const part of partsOf(locale, calendar, hint, literalDates[0])) {
484
+ const separator = norm(part.value).replace(/\s+/g, '');
485
+ if (part.type === 'literal' && separator) {
486
+ separators.add(separator);
487
+ }
488
+ }
489
+ if (LUNISOLAR.has(calendar)) {
490
+ // Year names cycle every 60 years.
491
+ for (let year = 1996; year < 2057; year++) {
492
+ for (const loc of locales) {
493
+ for (const part of partsOf(loc, calendar, LONG, {
494
+ year,
495
+ month: 6,
496
+ day: 15,
497
+ })) {
498
+ if (partType(part) === 'yearName') {
499
+ addSkip(part.value);
500
+ }
501
+ }
502
+ }
503
+ }
504
+ }
505
+
506
+ const eraArithmetic = new Map<string, EraArithmetic>();
507
+ const eraWidths: ReadonlyArray<'long' | 'short' | 'narrow'> =
508
+ calendar === 'japanese' ? ['long', 'short', 'narrow'] : ['long', 'short'];
509
+ for (const [code, iso] of Object.entries(ERA_SAMPLES[calendar] ?? {})) {
510
+ const sample = isoToPlainDate(iso);
511
+ for (const loc of locales) {
512
+ // A full date can spell the era differently, such as French "EB".
513
+ for (const options of [
514
+ ...eraWidths.map(width => ERA_WIDTHS[width]),
515
+ ...literalFormats,
516
+ ]) {
517
+ const text = partsOf(loc, calendar, options, sample).find(
518
+ p => p.type === 'era',
519
+ )?.value;
520
+ if (text != null && norm(text)) {
521
+ eras.set(norm(text), code);
522
+ }
523
+ }
524
+ }
525
+ const eraYear = (pd: PlainDate) => calendarEraYear('en', calendar, pd).year;
526
+ const year = eraYear(sample);
527
+ const isForward = eraYear({...sample, year: sample.year + 1}) > year;
528
+ const base = isForward ? sample.year - year : sample.year + year;
529
+ eraArithmetic.set(code, {base, isForward});
530
+ const firstYear = {
531
+ year: isForward ? base + 1 : base - 1,
532
+ month: 12,
533
+ day: 31,
534
+ };
535
+ // Some locales write era year 1 as a word, such as Japanese 元.
536
+ if (isGregorianAligned(calendar) && firstYear.year > 0) {
537
+ const text = partsOf(locale, calendar, LONG_WITH_ERA, firstYear).find(
538
+ p => p.type === 'year',
539
+ )?.value;
540
+ if (text != null && !/\d/.test(norm(text))) {
541
+ yearAliases.set(norm(text), '1');
542
+ }
543
+ }
544
+ }
545
+ if (calendar === 'japanese') {
546
+ eraArithmetic.set('ce', {base: 0, isForward: true});
547
+ }
548
+ for (const marker of leapMarkers) {
549
+ skip.delete(marker);
550
+ }
551
+ for (const word of [
552
+ ...months.keys(),
553
+ ...eras.keys(),
554
+ ...yearAliases.keys(),
555
+ ]) {
556
+ skip.delete(word);
557
+ }
558
+
559
+ // Whether a typed month number is the month's position in its year (Hebrew)
560
+ // or its code number (Chinese). Probed with a locale that writes numbers.
561
+ let isNumericMonthOrdinal = false;
562
+ const shifted = sampled.find(m => m.ordinal !== Number(m.code.slice(1, 3)));
563
+ if (shifted != null) {
564
+ const text = partsOf('sv', calendar, NUMERIC, shifted.mid).find(
565
+ p => p.type === 'month',
566
+ )?.value;
567
+ isNumericMonthOrdinal =
568
+ Number(normalizeDateText(text ?? '', 'sv').replace(/\D+/g, '')) ===
569
+ shifted.ordinal;
570
+ }
571
+
572
+ const vocabularySet = new Set([
573
+ ...months.keys(),
574
+ ...eras.keys(),
575
+ ...skip,
576
+ ...leapMarkers,
577
+ ...yearAliases.keys(),
578
+ ]);
579
+ const sample = {year: 2026, month: 11, day: 2};
580
+ return {
581
+ locale,
582
+ calendar,
583
+ order: fieldOrder(partsOf(locale, calendar, NUMERIC, sample)),
584
+ nameOrder: fieldOrder(partsOf(locale, calendar, LONG, sample)),
585
+ months,
586
+ monthTemplates: [...templates.values()].sort(
587
+ (a, b) => b.pre.length + b.post.length - (a.pre.length + a.post.length),
588
+ ),
589
+ leapMarkers,
590
+ eras,
591
+ yearAliases,
592
+ trailers,
593
+ yearMarkers,
594
+ separators,
595
+ weekdays,
596
+ vocabulary: [...vocabularySet].sort((a, b) => b.length - a.length),
597
+ vocabularySet,
598
+ isNumericMonthOrdinal,
599
+ eraArithmetic,
600
+ };
601
+ }
602
+
603
+ /** The era name and era year Intl writes for a date. */
604
+ function calendarEraYear(
605
+ locale: Locale,
606
+ calendar: CalendarSystem,
607
+ pd: PlainDate,
608
+ ): {era: string; year: number} {
609
+ const parts = partsOf(locale, calendar, ERA_AND_YEAR, pd);
610
+ return {
611
+ era: parts.find(p => p.type === 'era')?.value ?? '',
612
+ year: Number(
613
+ normalizeDateText(
614
+ parts.find(p => p.type === 'year' || partType(p) === 'relatedYear')
615
+ ?.value ?? '',
616
+ locale,
617
+ ),
618
+ ),
619
+ };
620
+ }
621
+
622
+ // =============================================================================
623
+ // Tokens
624
+ // =============================================================================
625
+
626
+ type Token =
627
+ | {readonly t: 'num'; readonly v: string; readonly isAlias?: boolean}
628
+ | {
629
+ readonly t: 'month';
630
+ readonly codes: ReadonlySet<string>;
631
+ readonly isWeekday?: boolean;
632
+ }
633
+ | {readonly t: 'monthNum'; readonly v: string; readonly isLeap: boolean}
634
+ | {readonly t: 'era'; readonly code: string; readonly isWeekday?: boolean}
635
+ | {readonly t: 'leap'}
636
+ | {readonly t: 'word'; readonly isYearMarker?: boolean}
637
+ | {readonly t: 'sep'; readonly v: string};
638
+
639
+ type Tokens = Token[] | 'incomplete' | 'unreadable';
640
+
641
+ function tokenize(
642
+ s: string,
643
+ lex: CalendarDateLexicon,
644
+ isDigitNameRead = true,
645
+ ): Tokens {
646
+ const out: Token[] = [];
647
+ let hasDigitName = false;
648
+ let i = 0;
649
+ const end = s.trimEnd().length;
650
+ while (i < end) {
651
+ const ch = s[i];
652
+ if (/\s/.test(ch)) {
653
+ i++;
654
+ continue;
655
+ }
656
+ const template = matchTemplate(s, i, lex);
657
+ if (template != null) {
658
+ out.push(template.token);
659
+ i = template.end;
660
+ continue;
661
+ }
662
+ const digitName = isDigitNameRead && /\d/.test(ch) && matchWord(s, i, lex);
663
+ if (digitName) {
664
+ out.push(wordToken(digitName, lex));
665
+ hasDigitName = true;
666
+ i += digitName.length;
667
+ continue;
668
+ }
669
+ if (/\d/.test(ch)) {
670
+ let j = i;
671
+ while (j < s.length && /\d/.test(s[j])) {
672
+ j++;
673
+ }
674
+ out.push({t: 'num', v: s.slice(i, j)});
675
+ i = j;
676
+ continue;
677
+ }
678
+ const word = matchWord(s, i, lex);
679
+ if (word != null) {
680
+ out.push(wordToken(word, lex));
681
+ i += word.length;
682
+ continue;
683
+ }
684
+ if (SEPARATOR.test(ch)) {
685
+ let j = i;
686
+ while (j < end && (SEPARATOR.test(s[j]) || /\s/.test(s[j]))) {
687
+ j++;
688
+ }
689
+ out.push({t: 'sep', v: s.slice(i, j).replace(/\s+/g, '')});
690
+ i = j;
691
+ continue;
692
+ }
693
+ let j = i;
694
+ while (j < end && LETTER.test(s[j])) {
695
+ j++;
696
+ }
697
+ const run = s.slice(i, j);
698
+ const joined = matchJoinedWord(s, i, lex);
699
+ if (joined != null) {
700
+ out.push(wordToken(joined, lex));
701
+ i += joined.length;
702
+ continue;
703
+ }
704
+ const monthCodes = monthByPrefix(run, lex);
705
+ if (monthCodes != null) {
706
+ out.push({t: 'month', codes: monthCodes});
707
+ i = j;
708
+ continue;
709
+ }
710
+ if (j >= end && run && lex.vocabulary.some(w => w.startsWith(run))) {
711
+ return 'incomplete';
712
+ }
713
+ return 'unreadable';
714
+ }
715
+ // Latvian "1. adars" is a month, and also day 1 of "adars". It is the day
716
+ // when no other number can be the day. A number before the year word, as in
717
+ // "26. g.", is the year.
718
+ const canBeDay = (token: Token, k: number) => {
719
+ const next = out.slice(k + 1).find(t => t.t !== 'sep');
720
+ const isYear = next?.t === 'word' && next.isYearMarker === true;
721
+ return token.t === 'num' && Number(token.v) <= 31 && !isYear;
722
+ };
723
+ if (hasDigitName && !out.some(canBeDay)) {
724
+ return tokenize(s, lex, false);
725
+ }
726
+ return out;
727
+ }
728
+
729
+ function matchTemplate(
730
+ s: string,
731
+ i: number,
732
+ lex: CalendarDateLexicon,
733
+ ): {token: Token; end: number} | null {
734
+ for (const template of lex.monthTemplates) {
735
+ if (!s.startsWith(template.pre, i)) {
736
+ continue;
737
+ }
738
+ const digits = /^\d+/.exec(s.slice(i + template.pre.length));
739
+ if (digits == null) {
740
+ continue;
741
+ }
742
+ const after = i + template.pre.length + digits[0].length;
743
+ if (template.post && !s.startsWith(template.post, after)) {
744
+ continue;
745
+ }
746
+ if (!template.post && !template.pre) {
747
+ continue;
748
+ }
749
+ return {
750
+ token: {t: 'monthNum', v: digits[0], isLeap: template.isLeap},
751
+ end: after + template.post.length,
752
+ };
753
+ }
754
+ return null;
755
+ }
756
+
757
+ /** The longest known word at `i` that ends where a word may end. */
758
+ function matchWord(
759
+ s: string,
760
+ i: number,
761
+ lex: CalendarDateLexicon,
762
+ ): string | null {
763
+ for (const word of lex.vocabulary) {
764
+ if (!s.startsWith(word, i)) {
765
+ continue;
766
+ }
767
+ const next = s[i + word.length] ?? '';
768
+ const last = word[word.length - 1];
769
+ if (
770
+ !LETTER.test(next) ||
771
+ SPACELESS.test(next) ||
772
+ SPACELESS.test(last) ||
773
+ !LETTER.test(last)
774
+ ) {
775
+ return word;
776
+ }
777
+ }
778
+ return null;
779
+ }
780
+
781
+ /**
782
+ * A known word joined to the next one with no space, such as Hebrew ב before
783
+ * a month name or a leap marker after it ("Eighth Monthbis").
784
+ */
785
+ function matchJoinedWord(
786
+ s: string,
787
+ i: number,
788
+ lex: CalendarDateLexicon,
789
+ ): string | null {
790
+ for (const word of lex.vocabulary) {
791
+ if (!s.startsWith(word, i)) {
792
+ continue;
793
+ }
794
+ const next = i + word.length;
795
+ if (lex.vocabulary.some(w => s.startsWith(w, next))) {
796
+ return word;
797
+ }
798
+ }
799
+ return null;
800
+ }
801
+
802
+ function wordToken(word: string, lex: CalendarDateLexicon): Token {
803
+ const isWeekday = lex.weekdays.has(word.replace(NOT_WORDS, ''));
804
+ const codes = lex.months.get(word);
805
+ if (codes != null) {
806
+ return {t: 'month', codes, isWeekday};
807
+ }
808
+ const era = lex.eras.get(word);
809
+ if (era != null) {
810
+ return {t: 'era', code: era, isWeekday};
811
+ }
812
+ if (lex.leapMarkers.has(word)) {
813
+ return {t: 'leap'};
814
+ }
815
+ const alias = lex.yearAliases.get(word);
816
+ if (alias != null) {
817
+ return {t: 'num', v: alias, isAlias: true};
818
+ }
819
+ return {t: 'word', isYearMarker: lex.yearMarkers.has(word)};
820
+ }
821
+
822
+ /** The month a typed abbreviation names when it starts only one month name. */
823
+ function monthByPrefix(
824
+ run: string,
825
+ lex: CalendarDateLexicon,
826
+ ): ReadonlySet<string> | null {
827
+ if (run.length < 3) {
828
+ return null;
829
+ }
830
+ let found: ReadonlySet<string> | null = null;
831
+ for (const [name, codes] of lex.months) {
832
+ if (name.startsWith(run)) {
833
+ if (found != null && !sameCodes(found, codes)) {
834
+ return null;
835
+ }
836
+ found = codes;
837
+ }
838
+ }
839
+ return found;
840
+ }
841
+
842
+ /**
843
+ * A weekday word can also be a month or era name, such as French "mar.". When
844
+ * the text has another month or era, the first such word is the weekday.
845
+ */
846
+ function weekdaysAsWords(tokens: Token[]): Token[] {
847
+ let out = tokens;
848
+ for (const kinds of [['month', 'monthNum'], ['era']]) {
849
+ const named = out.filter(t => kinds.includes(t.t));
850
+ const weekday = named.find(t => 'isWeekday' in t && t.isWeekday);
851
+ if (named.length > 1 && weekday != null) {
852
+ out = out.map(t => (t === weekday ? {t: 'word'} : t));
853
+ }
854
+ }
855
+ return out;
856
+ }
857
+
858
+ function sameCodes(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
859
+ return a.size === b.size && [...a].every(code => b.has(code));
860
+ }
861
+
862
+ // =============================================================================
863
+ // Parsing
864
+ // =============================================================================
865
+
866
+ const INCOMPLETE: CalendarDateParseResult = {kind: 'incomplete'};
867
+ const UNREADABLE: CalendarDateParseResult = {kind: 'unreadable'};
868
+ const EMPTY: CalendarDateParseResult = {kind: 'empty'};
869
+
870
+ type DateFields = {
871
+ -readonly [K in keyof CalendarDateFields]: CalendarDateFields[K];
872
+ };
873
+
874
+ /**
875
+ * Reads typed text as a date in the lexicon's calendar. Values are Gregorian.
876
+ * The result tells an empty field, part of a date, unreadable text, a date the
877
+ * calendar does not have, a date out of range, and a date apart.
878
+ */
879
+ export function parseCalendarDate(
880
+ text: string,
881
+ lex: CalendarDateLexicon,
882
+ options: CalendarDateParseOptions = {},
883
+ ): CalendarDateParseResult {
884
+ const s = normalizeDateText(text, lex.locale).trim();
885
+ if (!s) {
886
+ return EMPTY;
887
+ }
888
+ const {isDateDisabled, isoIsGregorian = false} = options;
889
+ if (isoIsGregorian) {
890
+ const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(s);
891
+ if (iso != null) {
892
+ return finish(
893
+ gregorianDate(+iso[1], +iso[2], +iso[3]),
894
+ true,
895
+ isDateDisabled,
896
+ );
897
+ }
898
+ }
899
+ const read = tokenize(s, lex);
900
+ if (read === 'incomplete' || read === 'unreadable') {
901
+ return read === 'incomplete' ? INCOMPLETE : UNREADABLE;
902
+ }
903
+ const tokens = weekdaysAsWords(read);
904
+ const nums = tokens.filter(
905
+ (token): token is Extract<Token, {t: 'num'}> => token.t === 'num',
906
+ );
907
+ const monthTokens = tokens.filter(
908
+ token => token.t === 'month' || token.t === 'monthNum',
909
+ );
910
+ const eraCodes = new Set(
911
+ tokens.flatMap(token => (token.t === 'era' ? [token.code] : [])),
912
+ );
913
+ const isLeap = tokens.some(
914
+ token => token.t === 'leap' || (token.t === 'monthNum' && token.isLeap),
915
+ );
916
+ if (monthTokens.length > 1 || eraCodes.size > 1) {
917
+ return UNREADABLE;
918
+ }
919
+ if (nums.length === 0 && monthTokens.length === 0) {
920
+ return eraCodes.size > 0 ? INCOMPLETE : UNREADABLE;
921
+ }
922
+ const typedEra = eraCodes.size === 1 ? [...eraCodes][0] : undefined;
923
+
924
+ const assigned = new Map<Token, DateField>();
925
+ let monthCode: string | undefined;
926
+ let monthOrdinal: number | undefined;
927
+ const monthToken = monthTokens[0];
928
+ if (monthToken != null) {
929
+ if (monthToken.t === 'month') {
930
+ if (monthToken.codes.size !== 1) {
931
+ return UNREADABLE;
932
+ }
933
+ monthCode = [...monthToken.codes][0];
934
+ } else if (lex.isNumericMonthOrdinal) {
935
+ monthOrdinal = Number(monthToken.v);
936
+ } else {
937
+ monthCode = `M${monthToken.v.padStart(2, '0')}`;
938
+ }
939
+ if (nums.length === 0 || nums.length > 2) {
940
+ return nums.length === 0 ? INCOMPLETE : UNREADABLE;
941
+ }
942
+ let rest = lex.nameOrder.filter(field => field !== 'month');
943
+ const long = nums.filter(n => n.v.length >= 3 && !n.isAlias);
944
+ if (nums.length === 1) {
945
+ rest = long.length === 1 ? ['year'] : ['day'];
946
+ } else if (long.length === 1) {
947
+ // With a month name, a number of three or more digits is the year.
948
+ rest = nums[0] === long[0] ? ['year', 'day'] : ['day', 'year'];
949
+ }
950
+ nums.forEach((n, k) => assigned.set(n, rest[k]));
951
+ } else {
952
+ if (nums.length < 2) {
953
+ return INCOMPLETE;
954
+ }
955
+ if (nums.length > 3 || !sameSeparators(tokens, lex)) {
956
+ return UNREADABLE;
957
+ }
958
+ let order: ReadonlyArray<DateField> =
959
+ nums.length === 2
960
+ ? lex.order.filter(field => field !== 'year')
961
+ : lex.order;
962
+ if (nums.length === 3 && order[0] !== 'year' && nums[0].v.length >= 3) {
963
+ // ISO-shaped text: a leading long number is the year.
964
+ order = ['year', 'month', 'day'];
965
+ }
966
+ if (nums.length === 2 && nums.some(n => n.v.length >= 3)) {
967
+ return INCOMPLETE;
968
+ }
969
+ nums.forEach((n, k) => assigned.set(n, order[k]));
970
+ }
971
+ const tokenOf = (field: DateField) =>
972
+ nums.find(n => assigned.get(n) === field);
973
+ const dayToken = tokenOf('day');
974
+ const yearToken = tokenOf('year');
975
+ const monthNumToken = tokenOf('month');
976
+ if (dayToken == null) {
977
+ return INCOMPLETE;
978
+ }
979
+ if (monthNumToken != null) {
980
+ if (lex.isNumericMonthOrdinal) {
981
+ monthOrdinal = Number(monthNumToken.v);
982
+ } else {
983
+ monthCode = `M${String(Number(monthNumToken.v)).padStart(2, '0')}`;
984
+ }
985
+ }
986
+ if (monthCode != null && isLeap && !monthCode.endsWith('L')) {
987
+ monthCode += 'L';
988
+ }
989
+ const day = Number(dayToken.v);
990
+ const monthNumber = monthOrdinal ?? Number(monthCode?.slice(1, 3) ?? 0);
991
+ if (day < 1 || day > 31 || monthNumber < 1 || monthNumber > 13) {
992
+ // A lone 0 starts a two-digit day or month such as 07.
993
+ const startsField = [dayToken, monthNumToken].some(t => t?.v === '0');
994
+ return startsField ? INCOMPLETE : UNREADABLE;
995
+ }
996
+
997
+ const today = options.today ?? plainDateToday();
998
+ const dateFields: DateFields = {day};
999
+ if (monthCode != null) {
1000
+ dateFields.monthCode = monthCode;
1001
+ } else {
1002
+ dateFields.month = monthOrdinal;
1003
+ }
1004
+ let isYearComplete = false;
1005
+ if (yearToken == null) {
1006
+ applyDefaultYear(dateFields, lex, today);
1007
+ } else {
1008
+ const digits = yearToken.isAlias ? 1 : yearToken.v.length;
1009
+ const year = Number(yearToken.v);
1010
+ const isYearLast = lastNumber(tokens) === yearToken;
1011
+ const {calendar} = lex;
1012
+ if (typedEra != null) {
1013
+ dateFields.era = typedEra;
1014
+ dateFields.eraYear = year;
1015
+ isYearComplete = true;
1016
+ } else if (calendar === 'japanese' && digits <= 2) {
1017
+ const era = currentEra(lex, today);
1018
+ if (era == null) {
1019
+ return UNREADABLE;
1020
+ }
1021
+ dateFields.era = era;
1022
+ dateFields.eraYear = year;
1023
+ isYearComplete = !isYearLast;
1024
+ } else if (calendar === 'japanese' && digits === 4) {
1025
+ dateFields.era = 'ce';
1026
+ dateFields.eraYear = year;
1027
+ isYearComplete = true;
1028
+ } else if (calendar === 'roc') {
1029
+ dateFields.era = 'roc';
1030
+ dateFields.eraYear = year;
1031
+ isYearComplete = digits >= 4 || !isYearLast;
1032
+ } else if (digits === 2) {
1033
+ dateFields.year = windowedYear(year, calendarYearOf(calendar, today));
1034
+ } else if (digits >= 4) {
1035
+ dateFields.year = year;
1036
+ isYearComplete = true;
1037
+ } else {
1038
+ return INCOMPLETE;
1039
+ }
1040
+ }
1041
+
1042
+ const date = resolveDate(lex, dateFields);
1043
+ const isComplete =
1044
+ yearToken != null &&
1045
+ isYearComplete &&
1046
+ isLastFieldClosed(tokens, lex, assigned, date);
1047
+ return finish(date, isComplete, isDateDisabled);
1048
+ }
1049
+
1050
+ function finish(
1051
+ date: PlainDate | null,
1052
+ isComplete: boolean,
1053
+ isDateDisabled: ((date: PlainDate) => boolean) | undefined,
1054
+ ): CalendarDateParseResult {
1055
+ if (date == null) {
1056
+ return {kind: 'nonexistent', isComplete};
1057
+ }
1058
+ if (isDateDisabled?.(date)) {
1059
+ return {kind: 'outOfRange', date, isComplete};
1060
+ }
1061
+ return {kind: 'value', date, isComplete};
1062
+ }
1063
+
1064
+ /**
1065
+ * Whether the numbers share one separator. Two separators of the locale's own
1066
+ * numeric pattern also match, such as Hindi Buddhist "1/1`/2443".
1067
+ */
1068
+ function sameSeparators(tokens: Token[], lex: CalendarDateLexicon): boolean {
1069
+ let previous: string | null = null;
1070
+ let seenNumber = false;
1071
+ let pending: string | null = null;
1072
+ for (const token of tokens) {
1073
+ if (token.t === 'num') {
1074
+ if (seenNumber) {
1075
+ const separator = pending ?? '';
1076
+ if (
1077
+ previous != null &&
1078
+ separator !== previous &&
1079
+ !(lex.separators.has(separator) && lex.separators.has(previous))
1080
+ ) {
1081
+ return false;
1082
+ }
1083
+ previous = separator;
1084
+ }
1085
+ seenNumber = true;
1086
+ pending = null;
1087
+ } else if (token.t === 'sep') {
1088
+ pending = token.v;
1089
+ } else {
1090
+ pending = null;
1091
+ seenNumber = false;
1092
+ previous = null;
1093
+ }
1094
+ }
1095
+ return true;
1096
+ }
1097
+
1098
+ function lastNumber(tokens: Token[]): Token | undefined {
1099
+ const last = tokens.filter(token => token.t !== 'sep').at(-1);
1100
+ return last?.t === 'num' ? last : undefined;
1101
+ }
1102
+
1103
+ /**
1104
+ * Whether the text has stopped growing: no separator trails it, and its last
1105
+ * field cannot take another digit.
1106
+ */
1107
+ function isLastFieldClosed(
1108
+ tokens: Token[],
1109
+ lex: CalendarDateLexicon,
1110
+ assigned: ReadonlyMap<Token, DateField>,
1111
+ date: PlainDate | null,
1112
+ ): boolean {
1113
+ const last = tokens.at(-1);
1114
+ if (last == null) {
1115
+ return false;
1116
+ }
1117
+ if (last.t === 'sep') {
1118
+ return lex.trailers.has(last.v);
1119
+ }
1120
+ if (last.t !== 'num') {
1121
+ return true;
1122
+ }
1123
+ const field = assigned.get(last);
1124
+ if (field === 'year' || last.v.length >= 2) {
1125
+ return true;
1126
+ }
1127
+ const value = Number(last.v);
1128
+ if (field === 'day') {
1129
+ // Not the month's own length: a 3 can start a 30 that the month lacks,
1130
+ // and the field must then report that day, not the 3rd.
1131
+ return value * 10 > 31;
1132
+ }
1133
+ const monthsInYear =
1134
+ date == null ? 13 : calendarMonthsInYear(lex.calendar, date);
1135
+ return value * 10 > monthsInYear;
1136
+ }
1137
+
1138
+ /**
1139
+ * Resolves a two-digit year to the century that puts it at most 80 years
1140
+ * before and 19 years after the current year.
1141
+ */
1142
+ function windowedYear(twoDigits: number, current: number): number {
1143
+ let year = current - (current % 100) + twoDigits;
1144
+ if (year > current + 19) {
1145
+ year -= 100;
1146
+ } else if (year < current - 80) {
1147
+ year += 100;
1148
+ }
1149
+ return year;
1150
+ }
1151
+
1152
+ function currentEra(
1153
+ lex: CalendarDateLexicon,
1154
+ today: PlainDate,
1155
+ ): string | undefined {
1156
+ const {era} = calendarEraYear('en', lex.calendar, today);
1157
+ return lex.eras.get(normalizeDateText(era, lex.locale).trim());
1158
+ }
1159
+
1160
+ function applyDefaultYear(
1161
+ fields: DateFields,
1162
+ lex: CalendarDateLexicon,
1163
+ today: PlainDate,
1164
+ ): void {
1165
+ if (lex.eraArithmetic.size > 0 && !WITHOUT_ERA.has(lex.calendar)) {
1166
+ const {era, year} = calendarEraYear('en', lex.calendar, today);
1167
+ const code = lex.eras.get(normalizeDateText(era, lex.locale).trim());
1168
+ if (code != null) {
1169
+ fields.era = code;
1170
+ fields.eraYear = year;
1171
+ return;
1172
+ }
1173
+ }
1174
+ fields.year = calendarYearOf(lex.calendar, today);
1175
+ }
1176
+
1177
+ function gregorianDate(
1178
+ year: number,
1179
+ month: number,
1180
+ day: number,
1181
+ ): PlainDate | null {
1182
+ if (
1183
+ year < 1 ||
1184
+ year > 9999 ||
1185
+ month < 1 ||
1186
+ month > 12 ||
1187
+ day < 1 ||
1188
+ day > getDaysInMonth(year, month)
1189
+ ) {
1190
+ return null;
1191
+ }
1192
+ return {year, month, day};
1193
+ }
1194
+
1195
+ /** The Gregorian date for calendar fields, or null when it does not exist. */
1196
+ function resolveDate(
1197
+ lex: CalendarDateLexicon,
1198
+ fields: DateFields,
1199
+ ): PlainDate | null {
1200
+ const {calendar} = lex;
1201
+ if (!isGregorianAligned(calendar)) {
1202
+ const defined = Object.fromEntries(
1203
+ Object.entries(fields).filter(([, value]) => value !== undefined),
1204
+ ) as DateFields;
1205
+ const date = calendarDateFromFields(calendar, defined);
1206
+ return date != null && date.year >= 1 && date.year <= 9999 ? date : null;
1207
+ }
1208
+ if (fields.monthCode?.endsWith('L')) {
1209
+ return null;
1210
+ }
1211
+ const month = fields.month ?? Number(fields.monthCode?.slice(1, 3));
1212
+ let gregorianYear: number;
1213
+ if (fields.era != null) {
1214
+ const arithmetic = lex.eraArithmetic.get(fields.era);
1215
+ if (arithmetic == null || fields.eraYear == null || fields.eraYear < 1) {
1216
+ return null;
1217
+ }
1218
+ gregorianYear = arithmetic.isForward
1219
+ ? arithmetic.base + fields.eraYear
1220
+ : arithmetic.base - fields.eraYear;
1221
+ } else {
1222
+ const defaultEra = calendar === 'buddhist' ? 'be' : 'ce';
1223
+ const arithmetic = lex.eraArithmetic.get(defaultEra);
1224
+ gregorianYear = (arithmetic?.base ?? 0) + (fields.year ?? 0);
1225
+ }
1226
+ const date = gregorianDate(gregorianYear, month, fields.day);
1227
+ if (date == null || calendar !== 'japanese' || fields.era === 'ce') {
1228
+ return date;
1229
+ }
1230
+ // Each Japanese era ends on a set day; a year past it is not in that era.
1231
+ const written = calendarEraYear('en', calendar, date);
1232
+ const era = lex.eras.get(normalizeDateText(written.era, lex.locale).trim());
1233
+ return era === fields.era && written.year === fields.eraYear ? date : null;
1234
+ }
1235
+
1236
+ // =============================================================================
1237
+ // Display
1238
+ // =============================================================================
1239
+
1240
+ const displayOptionCache = new WeakMap<
1241
+ Intl.DateTimeFormatOptions,
1242
+ Map<string, Intl.DateTimeFormatOptions>
1243
+ >();
1244
+
1245
+ const eraChangeCache = new Map<CalendarSystem, boolean>();
1246
+
1247
+ /**
1248
+ * Whether the era changes between 1900 and 2100. Only then can a year without
1249
+ * its era read as two dates.
1250
+ */
1251
+ function hasEraChange(calendar: CalendarSystem): boolean {
1252
+ let result = eraChangeCache.get(calendar);
1253
+ if (result == null) {
1254
+ const eraOf = (date: PlainDate) =>
1255
+ calendarEraYear('en', calendar, date).era;
1256
+ result =
1257
+ eraOf({year: 1900, month: 1, day: 1}) !==
1258
+ eraOf({year: 2100, month: 12, day: 31});
1259
+ eraChangeCache.set(calendar, result);
1260
+ }
1261
+ return result;
1262
+ }
1263
+
1264
+ /**
1265
+ * Adds the era to a format that omits it, so a year never reads as a
1266
+ * Gregorian year. Keeps the locale's own pattern when the era would reorder
1267
+ * the fields. Where the era changes, it uses a narrow era that keeps the
1268
+ * order instead, such as Amharic Japanese "1/1/8 R".
1269
+ */
1270
+ function displayOptions(
1271
+ locale: Locale,
1272
+ calendar: CalendarSystem,
1273
+ options: Intl.DateTimeFormatOptions,
1274
+ ): Intl.DateTimeFormatOptions {
1275
+ if (WITHOUT_ERA.has(calendar)) {
1276
+ return options;
1277
+ }
1278
+ let byLocale = displayOptionCache.get(options);
1279
+ if (byLocale == null) {
1280
+ byLocale = new Map();
1281
+ displayOptionCache.set(options, byLocale);
1282
+ }
1283
+ const key = `${locale}|${calendar}`;
1284
+ let result = byLocale.get(key);
1285
+ if (result == null) {
1286
+ const sample = {year: 2026, month: 11, day: 2};
1287
+ const parts = partsOf(locale, calendar, options, sample);
1288
+ result = options;
1289
+ const order = fieldOrder(parts).join();
1290
+ if (!parts.some(p => p.type === 'era')) {
1291
+ const widths = hasEraChange(calendar)
1292
+ ? (['short', 'narrow'] as const)
1293
+ : (['short'] as const);
1294
+ for (const era of widths) {
1295
+ const withEra = {...options, era};
1296
+ const eraParts = partsOf(locale, calendar, withEra, sample);
1297
+ if (fieldOrder(eraParts).join() === order) {
1298
+ result = withEra;
1299
+ break;
1300
+ }
1301
+ }
1302
+ }
1303
+ byLocale.set(key, result);
1304
+ }
1305
+ return result;
1306
+ }
1307
+
1308
+ function formatInCalendar(
1309
+ pd: PlainDate,
1310
+ options: Intl.DateTimeFormatOptions,
1311
+ locale: Locale,
1312
+ calendar: CalendarSystem,
1313
+ ): string {
1314
+ if (LUNISOLAR.has(calendar)) {
1315
+ return formatCalendarDate(calendar, pd, options, locale, {
1316
+ relatedYear: true,
1317
+ });
1318
+ }
1319
+ const display = displayOptions(locale, calendar, options);
1320
+ const text = plainDateFormat(pd, {...display, calendar}, locale);
1321
+ // When every era width reorders the fields, such as the Amharic short-month
1322
+ // date, the text has no era. Text without an era reads in the latest era.
1323
+ // So a date of an earlier era adds the era. It goes at the start or the
1324
+ // end, as in the era format.
1325
+ const eraOf = (date: PlainDate) => calendarEraYear('en', calendar, date).era;
1326
+ if (
1327
+ !hasEraChange(calendar) ||
1328
+ eraOf(pd) === eraOf({year: 9999, month: 12, day: 31}) ||
1329
+ partsOf(locale, calendar, display, pd).some(p => p.type === 'era')
1330
+ ) {
1331
+ return text;
1332
+ }
1333
+ const withEra = partsOf(locale, calendar, {...options, era: 'short'}, pd);
1334
+ const at = withEra.findIndex(p => p.type === 'era');
1335
+ const era = withEra
1336
+ .slice(at === 0 ? 0 : at - 1, at === 0 ? 2 : at + 1)
1337
+ .map(p => p.value)
1338
+ .join('');
1339
+ return at === 0 ? era + text : text + era;
1340
+ }
1341
+
1342
+ /**
1343
+ * The committed text of a date field in a calendar. Gregorian text is the
1344
+ * shared Timestamp date text; `system_date` is always the ISO date.
1345
+ */
1346
+ export function formatCalendarDateText(
1347
+ pd: PlainDate,
1348
+ format: SharedDateFormat,
1349
+ locale: Locale,
1350
+ calendar: CalendarSystem,
1351
+ ): string {
1352
+ const resolved = textCalendar(calendar);
1353
+ if (format === 'system_date' || resolved === 'gregory') {
1354
+ return formatSharedDate(pd, format, locale);
1355
+ }
1356
+ return formatInCalendar(
1357
+ pd,
1358
+ SHARED_DATE_FORMAT_OPTIONS[format],
1359
+ locale,
1360
+ resolved,
1361
+ );
1362
+ }
1363
+
1364
+ /** An example date in the calendar's numeric pattern, with its era. */
1365
+ export function formatCalendarDateHint(
1366
+ pd: PlainDate,
1367
+ locale: Locale,
1368
+ calendar: CalendarSystem,
1369
+ ): string {
1370
+ return formatInCalendar(pd, NUMERIC, locale, textCalendar(calendar));
1371
+ }
1372
+
1373
+ /** The calendar's name in the locale, such as "Persian Calendar". */
1374
+ export function calendarDisplayName(
1375
+ calendar: CalendarSystem,
1376
+ locale: Locale,
1377
+ ): string {
1378
+ try {
1379
+ return (
1380
+ new Intl.DisplayNames(locale, {type: 'calendar'}).of(calendar) ?? calendar
1381
+ );
1382
+ } catch {
1383
+ return calendar;
1384
+ }
1385
+ }