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