@astryxdesign/core 0.7.5 → 0.7.6-canary.0ef60dd
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/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +5 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +7 -1
- package/dist/BottomSheet/BottomSheet.js +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +2 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +30 -3
- package/dist/Button/Button.js +4 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -5
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +37 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -4
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +101 -15
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +8 -3
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -12
- package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
- package/dist/DateTimeInput/NativeTimeSegment.js +5 -4
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -3
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +34 -9
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +5 -4
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +2 -1
- package/dist/Drawer/Drawer.d.ts +1 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +7 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +7 -3
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +20 -2
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +31 -3
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -16
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +2 -1
- package/dist/Field/FieldLabel.d.ts +1 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +4 -4
- package/dist/Field/InputClearButton.js +3 -3
- package/dist/Field/PanelSearchInput.js +2 -2
- package/dist/FieldStatus/FieldStatus.js +2 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +7 -1
- package/dist/Heading/Heading.js +1 -1
- package/dist/Icon/Icon.js +1 -1
- package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckIndicator.js +2 -1
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckboxIndicator.js +3 -1
- package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
- package/dist/Indicator/RadioIndicator.js +3 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +2 -1
- package/dist/Item/ItemSwipeLayer.js +1 -1
- package/dist/Layer/popoverOperations.d.ts +27 -0
- package/dist/Layer/popoverOperations.d.ts.map +1 -0
- package/dist/Layer/popoverOperations.js +159 -0
- package/dist/Layer/useLayer.d.ts +1 -1
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +93 -59
- package/dist/Layout/Layout.js +1 -1
- package/dist/Layout/LayoutContent.js +1 -1
- package/dist/Layout/LayoutFooter.js +1 -1
- package/dist/Layout/LayoutHeader.js +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +5 -0
- package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
- package/dist/List/List.js +1 -1
- package/dist/List/ListItem.js +3 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +7 -2
- package/dist/MultiSelector/CreateRow.a11y.chromium.spec.js +7 -6
- package/dist/MultiSelector/MultiSelector.d.ts +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +26 -10
- package/dist/NumberInput/NumberInput.d.ts +3 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +3 -3
- package/dist/NumberInput/numberParser.d.ts +3 -0
- package/dist/NumberInput/numberParser.d.ts.map +1 -1
- package/dist/NumberInput/numberParser.js +7 -44
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +16 -12
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +28 -2
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
- package/dist/Selector/useSelectorPresentation.d.ts +8 -1
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
- package/dist/Selector/useSelectorPresentation.js +20 -4
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +10 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +3 -3
- package/dist/Stack/Stack.js +1 -1
- package/dist/Stack/StackItem.js +1 -1
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +16 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
- package/dist/Text/Text.js +1 -1
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +23 -5
- package/dist/TextArea/TextArea.d.ts +1 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +13 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +3 -3
- package/dist/TimeInput/TimeInput.d.ts +6 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +21 -14
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +5 -4
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +2 -2
- package/dist/Timestamp/TimestampHoverCard.js +4 -4
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +60 -20
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +82 -51
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +13 -2
- package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +2 -1
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -0
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +1 -0
- package/dist/Typeahead/Typeahead.d.ts +1 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +9 -1
- package/dist/astryx.css +20 -4
- package/dist/gestures/GestureArena.d.ts +6 -0
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +39 -0
- package/dist/gestures/GestureHost.d.ts +352 -0
- package/dist/gestures/GestureHost.d.ts.map +1 -0
- package/dist/gestures/GestureHost.js +1773 -0
- package/dist/gestures/GestureRecognizer.d.ts +10 -0
- package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/GestureRecognizer.js +16 -0
- package/dist/gestures/LongPressGestureRecognizer.d.ts +1 -0
- package/dist/gestures/LongPressGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/LongPressGestureRecognizer.js +3 -0
- package/dist/gestures/PanGestureRecognizer.d.ts +1 -0
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PanGestureRecognizer.js +3 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts +31 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/PinchGestureRecognizer.js +128 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +71 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.js +223 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts +37 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/SwipeGestureRecognizer.js +148 -0
- package/dist/gestures/TapGestureRecognizer.d.ts +1 -0
- package/dist/gestures/TapGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/TapGestureRecognizer.js +3 -0
- package/dist/gestures/VelocityTracker.d.ts +6 -2
- package/dist/gestures/VelocityTracker.d.ts.map +1 -1
- package/dist/gestures/VelocityTracker.js +7 -3
- package/dist/gestures/haptics.d.ts +65 -0
- package/dist/gestures/haptics.d.ts.map +1 -0
- package/dist/gestures/haptics.js +182 -0
- package/dist/gestures/nativeScroll.d.ts +42 -0
- package/dist/gestures/nativeScroll.d.ts.map +1 -0
- package/dist/gestures/nativeScroll.js +228 -0
- package/dist/gestures/touchAction.d.ts +60 -0
- package/dist/gestures/touchAction.d.ts.map +1 -0
- package/dist/gestures/touchAction.js +157 -0
- package/dist/gestures/touchActionRule.d.ts +34 -0
- package/dist/gestures/touchActionRule.d.ts.map +1 -0
- package/dist/gestures/touchActionRule.js +140 -0
- package/dist/gestures/types.d.ts +18 -7
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +22 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/modalAnnounce.d.ts.map +1 -1
- package/dist/hooks/modalAnnounce.js +107 -61
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
- package/dist/hooks/useAdaptivePresentation.js +4 -1
- package/dist/hooks/useAnnounce.d.ts +101 -42
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +161 -75
- package/dist/hooks/useAnnounceRenderedText.d.ts +11 -3
- package/dist/hooks/useAnnounceRenderedText.d.ts.map +1 -1
- package/dist/hooks/useAnnounceRenderedText.js +5 -3
- package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -1
- package/dist/hooks/useBusyAnnouncement.js +6 -12
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +12 -1
- 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/motion/animator.d.ts +7 -1
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +119 -27
- package/dist/theme/defineTheme.d.ts +19 -5
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +64 -4
- package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
- package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
- package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
- package/dist/theme/domainTokens/index.d.ts +5 -4
- package/dist/theme/domainTokens/index.d.ts.map +1 -1
- package/dist/theme/domainTokens/index.js +5 -9
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/motionKnobs.d.ts +56 -0
- package/dist/theme/motionKnobs.d.ts.map +1 -0
- package/dist/theme/motionKnobs.js +48 -0
- package/dist/theme/resolveThemeValues.d.ts +11 -4
- package/dist/theme/resolveThemeValues.d.ts.map +1 -1
- package/dist/theme/resolveThemeValues.js +40 -4
- package/dist/theme/themeAdaptations.d.ts +7 -3
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +36 -7
- package/dist/theme/themeMotion.d.ts +96 -0
- package/dist/theme/themeMotion.d.ts.map +1 -0
- package/dist/theme/themeMotion.js +177 -0
- package/dist/theme/tokenReferences.d.ts +31 -0
- package/dist/theme/tokenReferences.d.ts.map +1 -0
- package/dist/theme/tokenReferences.js +132 -0
- package/dist/theme/tokens.d.ts +1 -0
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +9 -129
- package/dist/theme/tokens.stylex.d.ts +12 -0
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +12 -0
- package/dist/utils/focusReturn.d.ts +17 -1
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +11 -4
- package/dist/utils/getNodeText.d.ts +8 -0
- package/dist/utils/getNodeText.d.ts.map +1 -0
- package/dist/utils/getNodeText.js +37 -0
- package/dist/utils/interactionModality.d.ts +11 -2
- package/dist/utils/interactionModality.d.ts.map +1 -1
- package/dist/utils/interactionModality.js +58 -5
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +17 -8
- package/dist/utils/plainDate.d.ts +2 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +46 -3
- package/dist/utils/presentation.d.ts +50 -0
- package/dist/utils/presentation.d.ts.map +1 -0
- package/dist/utils/presentation.js +125 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +20 -1
- package/dist/utils/primaryClick.d.ts +11 -0
- package/dist/utils/primaryClick.d.ts.map +1 -0
- package/dist/utils/primaryClick.js +25 -0
- package/dist/utils/timeFormat.d.ts +47 -0
- package/dist/utils/timeFormat.d.ts.map +1 -0
- package/dist/utils/timeFormat.js +240 -0
- package/dist/utils/touchPresentation.d.ts.map +1 -1
- package/dist/utils/touchPresentation.js +5 -3
- package/dist/utils/touchPresentation.stylex.d.ts +15 -0
- package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
- package/dist/utils/touchPresentation.stylex.js +33 -0
- package/dist/utils/unicodeDigits.d.ts +20 -0
- package/dist/utils/unicodeDigits.d.ts.map +1 -0
- package/dist/utils/unicodeDigits.js +70 -0
- package/locales/en.json +23 -3
- package/locales/pseudo.json +15 -0
- package/package.json +5 -4
- package/src/AlertDialog/AlertDialog.tsx +6 -5
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
- package/src/Avatar/Avatar.spec.md +22 -22
- package/src/Avatar/Avatar.tsx +18 -1
- package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
- package/src/Badge/Badge.doc.mjs +1 -1
- package/src/Badge/Badge.tsx +19 -2
- package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +260 -0
- package/src/BottomSheet/BottomSheet.test.tsx +46 -0
- package/src/BottomSheet/BottomSheet.tsx +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
- package/src/BottomSheet/useSheetGestures.test.ts +52 -0
- package/src/BottomSheet/useSheetGestures.ts +37 -6
- package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
- package/src/Button/Button.tsx +4 -4
- package/src/Calendar/Calendar.spec.md +87 -48
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
- package/src/CheckboxInput/CheckboxInput.tsx +6 -5
- package/src/ClickableCard/ClickableCard.test.tsx +96 -0
- package/src/ClickableCard/ClickableCard.tsx +48 -4
- package/src/Collapsible/Collapsible.spec.md +2 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
- package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +37 -0
- package/src/ContextMenu/ContextMenu.test.tsx +35 -0
- package/src/DateInput/DateInput.test.tsx +115 -3
- package/src/DateInput/DateInput.tsx +29 -14
- package/src/DateInput/DateInputTouch.test.tsx +76 -0
- package/src/DateInput/MonthScroller.tsx +106 -20
- package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
- package/src/DateInput/TouchDateField.tsx +14 -3
- package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
- package/src/DateRangeInput/DateRangeInput.tsx +16 -12
- package/src/DateTimeInput/DateTimeInput.doc.mjs +10 -8
- package/src/DateTimeInput/DateTimeInput.spec.md +75 -51
- package/src/DateTimeInput/DateTimeInput.test.tsx +81 -0
- package/src/DateTimeInput/DateTimeInput.tsx +46 -24
- package/src/DateTimeInput/NativeTimeSegment.tsx +4 -6
- package/src/DateTimeInput/TouchDateTimeField.tsx +3 -7
- package/src/Dialog/Dialog.test.tsx +216 -2
- package/src/Dialog/Dialog.tsx +42 -9
- package/src/Dialog/DialogHeader.tsx +5 -4
- package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
- package/src/Divider/Divider.tsx +2 -1
- package/src/Drawer/Drawer.spec.md +111 -80
- package/src/Drawer/Drawer.test.tsx +35 -0
- package/src/Drawer/Drawer.tsx +13 -2
- package/src/Drawer/DrawerHeader.spec.md +181 -0
- package/src/Drawer/__tests__/DrawerNonModalPageScroll.a11y.chromium.spec.ts +267 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
- package/src/DropdownMenu/DropdownMenu.tsx +10 -3
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
- package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
- package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +60 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +36 -7
- package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
- package/src/EmptyState/EmptyState.tsx +2 -1
- package/src/Field/FieldLabel.tsx +5 -4
- package/src/Field/InputClearButton.tsx +3 -3
- package/src/Field/PanelSearchInput.tsx +2 -2
- package/src/FieldStatus/FieldStatus.tsx +2 -2
- package/src/FileInput/FileInput.test.tsx +21 -0
- package/src/FileInput/FileInput.tsx +4 -1
- package/src/FormLayout/FormLayout.spec.md +7 -8
- package/src/Heading/Heading.tsx +1 -1
- package/src/Icon/Icon.tsx +1 -1
- package/src/Indicator/CheckIndicator.spec.md +47 -42
- package/src/Indicator/CheckIndicator.tsx +2 -1
- package/src/Indicator/CheckboxIndicator.spec.md +40 -37
- package/src/Indicator/CheckboxIndicator.tsx +3 -1
- package/src/Indicator/Indicator.spec.md +188 -0
- package/src/Indicator/RadioIndicator.tsx +3 -1
- package/src/Item/Item.tsx +2 -1
- package/src/Item/ItemSwipeLayer.tsx +1 -1
- package/src/Kbd/Kbd.spec.md +81 -38
- package/src/Layer/__tests__/LayerBrowserHide.a11y.chromium.spec.ts +126 -0
- package/src/Layer/popoverOperations.test.ts +205 -0
- package/src/Layer/popoverOperations.ts +172 -0
- package/src/Layer/useLayer.test.tsx +348 -1
- package/src/Layer/useLayer.tsx +118 -71
- package/src/Layout/Layout.spec.md +127 -131
- package/src/Layout/Layout.tsx +1 -1
- package/src/Layout/LayoutContent.spec.md +205 -0
- package/src/Layout/LayoutContent.tsx +1 -1
- package/src/Layout/LayoutFooter.spec.md +190 -0
- package/src/Layout/LayoutFooter.tsx +1 -1
- package/src/Layout/LayoutHeader.spec.md +193 -0
- package/src/Layout/LayoutHeader.tsx +1 -1
- package/src/Layout/LayoutPanel.spec.md +194 -0
- package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
- package/src/Lightbox/Lightbox.test.tsx +18 -0
- package/src/Lightbox/Lightbox.tsx +5 -0
- package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
- package/src/List/List.tsx +1 -1
- package/src/List/ListItem.spec.md +205 -0
- package/src/List/ListItem.tsx +3 -3
- package/src/MetadataList/MetadataList.spec.md +187 -0
- package/src/MetadataList/MetadataListItem.spec.md +165 -0
- package/src/MobileNav/MobileNav.test.tsx +15 -0
- package/src/MobileNav/MobileNav.tsx +9 -1
- package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
- package/src/MoreMenu/MoreMenu.test.tsx +11 -0
- package/src/MultiSelector/CreateRow.a11y.chromium.spec.ts +7 -8
- package/src/MultiSelector/MultiSelector.spec.md +7 -4
- package/src/MultiSelector/MultiSelector.test.tsx +270 -1
- package/src/MultiSelector/MultiSelector.tsx +25 -10
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
- package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
- package/src/NumberInput/NumberInput.doc.mjs +13 -7
- package/src/NumberInput/NumberInput.spec.md +59 -37
- package/src/NumberInput/NumberInput.tsx +13 -3
- package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
- package/src/NumberInput/numberParser.ts +7 -45
- package/src/ProgressBar/ProgressBar.test.tsx +108 -32
- package/src/ProgressBar/ProgressBar.tsx +58 -10
- package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
- package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
- package/src/Resizable/useResizable.test.ts +43 -0
- package/src/Resizable/useResizable.ts +32 -9
- package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
- package/src/Selector/Selector.test.tsx +90 -1
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
- package/src/Selector/useSelectorPresentation.ts +31 -3
- package/src/SideNav/SideNav.test.tsx +27 -0
- package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +176 -51
- package/src/Skeleton/Skeleton.tsx +3 -1
- package/src/Slider/Slider.test.tsx +31 -0
- package/src/Slider/Slider.tsx +8 -3
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
- package/src/Spinner/Spinner.tsx +5 -3
- package/src/Stack/Stack.tsx +1 -1
- package/src/Stack/StackItem.tsx +1 -1
- package/src/TabList/TabList.test.tsx +58 -4
- package/src/TabList/TabList.tsx +16 -1
- package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
- package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
- package/src/Text/Text.tsx +1 -1
- package/src/Text/useTruncation.test.tsx +128 -1
- package/src/Text/useTruncation.ts +23 -5
- package/src/TextArea/TextArea.spec.md +10 -13
- package/src/TextArea/TextArea.test.tsx +60 -0
- package/src/TextArea/TextArea.tsx +15 -1
- package/src/TextInput/TextInput.test.tsx +21 -0
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/TimeInput/NativeTimeInput.test.tsx +17 -0
- package/src/TimeInput/Presentation.test.tsx +21 -0
- package/src/TimeInput/TimeInput.doc.mjs +8 -8
- package/src/TimeInput/TimeInput.spec.md +45 -48
- package/src/TimeInput/TimeInput.test.tsx +76 -0
- package/src/TimeInput/TimeInput.tsx +36 -18
- package/src/TimeInput/TouchTimeField.tsx +4 -6
- package/src/Timestamp/Timestamp.spec.md +6 -12
- package/src/Timestamp/Timestamp.test.tsx +30 -5
- package/src/Timestamp/Timestamp.tsx +6 -2
- package/src/Timestamp/TimestampHoverCard.tsx +4 -4
- package/src/Toast/Toast.spec.md +14 -20
- package/src/Toast/Toast.tsx +104 -36
- package/src/Toast/ToastViewport.test.tsx +719 -1
- package/src/Toast/ToastViewport.tsx +100 -50
- package/src/Tokenizer/Tokenizer.spec.md +5 -3
- package/src/Tokenizer/Tokenizer.test.tsx +68 -0
- package/src/Tokenizer/Tokenizer.tsx +14 -2
- package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
- package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
- package/src/Toolbar/Toolbar.spec.md +5 -6
- package/src/Toolbar/Toolbar.test.tsx +30 -0
- package/src/Toolbar/Toolbar.tsx +2 -1
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
- package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
- package/src/TopNav/TopNav.test.tsx +27 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +21 -0
- package/src/TopNav/TopNavMegaMenu.tsx +4 -1
- package/src/TopNav/TopNavMenu.test.tsx +17 -0
- package/src/TopNav/TopNavMenu.tsx +1 -0
- package/src/Typeahead/Typeahead.test.tsx +40 -0
- package/src/Typeahead/Typeahead.tsx +12 -1
- package/src/__tests__/pressState.ts +8 -4
- package/src/gestures/GestureArena.ts +35 -0
- package/src/gestures/GestureHost.ts +2065 -0
- package/src/gestures/GestureRecognizer.ts +18 -0
- package/src/gestures/LongPressGestureRecognizer.ts +4 -0
- package/src/gestures/PanGestureRecognizer.ts +4 -0
- package/src/gestures/PinchGestureRecognizer.ts +148 -0
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +256 -0
- package/src/gestures/SwipeGestureRecognizer.ts +197 -0
- package/src/gestures/TapGestureRecognizer.ts +4 -0
- package/src/gestures/VelocityTracker.ts +10 -3
- package/src/gestures/__tests__/GestureHost.test.ts +2087 -0
- package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
- package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
- package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
- package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
- package/src/gestures/__tests__/arena.test.ts +5 -0
- package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +193 -0
- package/src/gestures/__tests__/browser/build-fixture.mjs +46 -0
- package/src/gestures/__tests__/browser/fixture.ts +307 -0
- package/src/gestures/__tests__/browser/gestures.interaction.browser.spec.ts +1137 -0
- package/src/gestures/__tests__/browserNegotiation.test.ts +293 -0
- package/src/gestures/__tests__/haptics.test.ts +187 -0
- package/src/gestures/__tests__/harness.ts +21 -2
- package/src/gestures/__tests__/recognizerKind.test.ts +78 -0
- package/src/gestures/__tests__/touchActionRule.test.ts +54 -0
- package/src/gestures/haptics.ts +203 -0
- package/src/gestures/nativeScroll.ts +277 -0
- package/src/gestures/touchAction.ts +191 -0
- package/src/gestures/touchActionRule.ts +157 -0
- package/src/gestures/types.ts +30 -9
- package/src/hooks/index.ts +5 -1
- package/src/hooks/modalAnnounce.ts +129 -72
- package/src/hooks/useAdaptivePresentation.ts +4 -1
- package/src/hooks/useAnnounce.doc.mjs +35 -3
- package/src/hooks/useAnnounce.replace.test.tsx +516 -0
- package/src/hooks/useAnnounce.ts +209 -102
- package/src/hooks/useAnnounceRenderedText.adopters.test.tsx +10 -1
- package/src/hooks/useAnnounceRenderedText.ts +17 -3
- package/src/hooks/useBusyAnnouncement.ts +7 -10
- package/src/hooks/useMenuHover.ts +15 -1
- package/src/i18n/generated-locales/en.generated.ts +6 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +6 -0
- package/src/motion/README.md +11 -1
- package/src/motion/animator.test.ts +151 -3
- package/src/motion/animator.ts +149 -33
- package/src/theme/Theme.doc.mjs +1 -1
- package/src/theme/defineTheme.ts +117 -8
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
- package/src/theme/domainTokens/index.ts +6 -10
- package/src/theme/expandMotionScale.test.ts +1 -1
- package/src/theme/index.ts +5 -0
- package/src/theme/motionKnobs.ts +82 -0
- package/src/theme/resolveThemeValues.ts +68 -6
- package/src/theme/themeAdaptations.ts +52 -12
- package/src/theme/themeMotion.test.ts +620 -0
- package/src/theme/themeMotion.ts +221 -0
- package/src/theme/tokenReferences.ts +149 -0
- package/src/theme/tokens.stylex.ts +10 -0
- package/src/theme/tokens.ts +15 -158
- package/src/utils/focusReturn.ts +17 -6
- package/src/utils/getNodeText.test.ts +24 -0
- package/src/utils/getNodeText.ts +37 -0
- package/src/utils/interactionModality.test.ts +45 -1
- package/src/utils/interactionModality.ts +64 -5
- package/src/utils/interactionOverlay.stylex.ts +12 -3
- package/src/utils/plainDate.test.ts +90 -0
- package/src/utils/plainDate.ts +58 -3
- package/src/utils/presentation.test.ts +380 -0
- package/src/utils/presentation.ts +180 -0
- package/src/utils/pressFeedback.test.ts +138 -0
- package/src/utils/pressFeedback.ts +34 -11
- package/src/utils/pressPaintPriority.test.ts +80 -0
- package/src/utils/primaryClick.test.ts +38 -0
- package/src/utils/primaryClick.ts +28 -0
- package/src/utils/timeFormat.test.ts +204 -0
- package/src/utils/timeFormat.ts +314 -0
- package/src/utils/touchPresentation.stylex.ts +43 -0
- package/src/utils/touchPresentation.test.ts +140 -1
- package/src/utils/touchPresentation.ts +5 -3
- package/src/utils/unicodeDigits.test.ts +44 -0
- package/src/utils/unicodeDigits.ts +71 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file timeFormat.ts
|
|
5
|
+
* @input An ISO time or typed text, plus the provider's BCP 47 locale
|
|
6
|
+
* @output Exports resolveHourFormat, formatLocaleTime, and parseLocaleTime
|
|
7
|
+
* @position Internal shared helper for TimeInput and DateTimeInput; not part
|
|
8
|
+
* of the public surface
|
|
9
|
+
*
|
|
10
|
+
* Time of day as the locale writes it. The hour cycle, digits, separator, and
|
|
11
|
+
* day-period words all come from Intl rather than a table, so a field reads
|
|
12
|
+
* the same way `Timestamp` does beside it.
|
|
13
|
+
*
|
|
14
|
+
* The hour cycle is the locale's own: `en-US` and `ko-KR` read twelve-hour,
|
|
15
|
+
* `en-GB`, `de-DE`, and `vi-VN` read twenty-four-hour. A `-u-hc-` extension
|
|
16
|
+
* on the provider locale (`en-US-u-hc-h23`) carries an app's per-user
|
|
17
|
+
* preference through the same path, so every Astryx time honors it.
|
|
18
|
+
*
|
|
19
|
+
* The parser reads back what the formatter writes: the locale's digits, its
|
|
20
|
+
* hour-minute separator, and its day-period words, on either side of the time.
|
|
21
|
+
* Whatever is left goes through the shared English-tolerant parser, so "2:30
|
|
22
|
+
* PM" and "14:30" keep working in every locale.
|
|
23
|
+
*
|
|
24
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
25
|
+
* - /packages/core/src/utils/timeFormat.test.ts
|
|
26
|
+
* - /packages/core/src/TimeInput/TimeInput.tsx
|
|
27
|
+
* - /packages/core/src/DateTimeInput/DateTimeInput.tsx
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type {Locale} from '../i18n/types';
|
|
31
|
+
import {parseISOTime, parseTimeInput, type ISOTimeString} from './timeParser';
|
|
32
|
+
import {INVISIBLE_CHARS, toAsciiDigits} from './unicodeDigits';
|
|
33
|
+
|
|
34
|
+
export type HourFormat = '12h' | '24h';
|
|
35
|
+
|
|
36
|
+
interface LocaleTimeSymbols {
|
|
37
|
+
hourFormat: HourFormat;
|
|
38
|
+
/**
|
|
39
|
+
* The text between hour and minute and between minute and second, trimmed:
|
|
40
|
+
* ":" nearly everywhere, "." in fi-FI, "h" and "min" in fr-CA. Read from
|
|
41
|
+
* both readings, because some locales write them differently: `as` writes
|
|
42
|
+
* "১৪:৩০" but "অপৰাহ্ন ২.৩০".
|
|
43
|
+
*/
|
|
44
|
+
separators: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Words the locale writes before or after the digits other than the day
|
|
47
|
+
* period, trimmed: "s" after fr-CA seconds, "kl." before nds-DE times, "ga"
|
|
48
|
+
* between an ee day period and its hour. Longest first.
|
|
49
|
+
*/
|
|
50
|
+
edgeWords: string[];
|
|
51
|
+
/** Every day-period word the locale writes, longest first. */
|
|
52
|
+
dayPeriods: {word: string; isPM: boolean}[];
|
|
53
|
+
/**
|
|
54
|
+
* The locale's twelve-hour reading writes 02:00 and 14:00 the same (fr-CM
|
|
55
|
+
* writes no day period), so its twelve-hour text is English instead.
|
|
56
|
+
*/
|
|
57
|
+
twelveHourIsAmbiguous: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const symbolsByLocale = new Map<string, LocaleTimeSymbols>();
|
|
61
|
+
|
|
62
|
+
// The space family Intl writes between a time and its day period ("2:30 PM"
|
|
63
|
+
// uses U+202F). A typed field shows a plain space so people can edit it.
|
|
64
|
+
const SPACES = /[\u00A0\u2007\u202F]/g;
|
|
65
|
+
// What may sit on a peeled word's inner side: a digit (digits are already
|
|
66
|
+
// ASCII here), whitespace, or nothing.
|
|
67
|
+
const WORD_BOUNDARY = /^[\d\s]?$/;
|
|
68
|
+
|
|
69
|
+
function utcDate(hour: number, minute: number, second: number): Date {
|
|
70
|
+
return new Date(Date.UTC(2000, 0, 1, hour, minute, second));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function timeFormatter(
|
|
74
|
+
locale: Locale,
|
|
75
|
+
hourFormat: HourFormat,
|
|
76
|
+
includeSeconds: boolean,
|
|
77
|
+
): Intl.DateTimeFormat {
|
|
78
|
+
const options: Intl.DateTimeFormatOptions = {
|
|
79
|
+
hour: hourFormat === '12h' ? 'numeric' : '2-digit',
|
|
80
|
+
minute: '2-digit',
|
|
81
|
+
second: includeSeconds ? '2-digit' : undefined,
|
|
82
|
+
// `hourCycle`, not `hour12`: `hour12: true` resolves to h11 in ja-JP and
|
|
83
|
+
// writes noon as "午後0:30".
|
|
84
|
+
hourCycle: hourFormat === '12h' ? 'h12' : 'h23',
|
|
85
|
+
timeZone: 'UTC',
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
return new Intl.DateTimeFormat(locale, options);
|
|
89
|
+
} catch {
|
|
90
|
+
return new Intl.DateTimeFormat('en', options);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getLocaleTimeSymbols(locale: Locale): LocaleTimeSymbols {
|
|
95
|
+
let symbols = symbolsByLocale.get(locale);
|
|
96
|
+
if (symbols !== undefined) {
|
|
97
|
+
return symbols;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let hourFormat: HourFormat;
|
|
101
|
+
let resolved: Intl.ResolvedDateTimeFormatOptions;
|
|
102
|
+
try {
|
|
103
|
+
resolved = new Intl.DateTimeFormat(locale, {
|
|
104
|
+
hour: 'numeric',
|
|
105
|
+
}).resolvedOptions();
|
|
106
|
+
} catch {
|
|
107
|
+
// A malformed tag formats as English everywhere else in this module.
|
|
108
|
+
resolved = new Intl.DateTimeFormat('en', {
|
|
109
|
+
hour: 'numeric',
|
|
110
|
+
}).resolvedOptions();
|
|
111
|
+
}
|
|
112
|
+
if (resolved.hourCycle !== undefined) {
|
|
113
|
+
hourFormat =
|
|
114
|
+
resolved.hourCycle === 'h11' || resolved.hourCycle === 'h12'
|
|
115
|
+
? '12h'
|
|
116
|
+
: '24h';
|
|
117
|
+
} else {
|
|
118
|
+
// Engines that leave hourCycle unresolved still write a day period for a
|
|
119
|
+
// twelve-hour locale when no cycle is forced.
|
|
120
|
+
let ambient: Intl.DateTimeFormat;
|
|
121
|
+
try {
|
|
122
|
+
ambient = new Intl.DateTimeFormat(locale, {hour: 'numeric'});
|
|
123
|
+
} catch {
|
|
124
|
+
ambient = new Intl.DateTimeFormat('en', {hour: 'numeric'});
|
|
125
|
+
}
|
|
126
|
+
hourFormat = ambient
|
|
127
|
+
.formatToParts(utcDate(13, 0, 0))
|
|
128
|
+
.some(part => part.type === 'dayPeriod')
|
|
129
|
+
? '12h'
|
|
130
|
+
: '24h';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Literals of every reading this module writes: twelve- and twenty-four-hour,
|
|
134
|
+
// with and without seconds. A literal between two numbers is a separator;
|
|
135
|
+
// one outside them is an edge word.
|
|
136
|
+
const separatorSet = new Set<string>();
|
|
137
|
+
const edgeSet = new Set<string>();
|
|
138
|
+
for (const cycle of ['12h', '24h'] as const) {
|
|
139
|
+
for (const withSeconds of [false, true]) {
|
|
140
|
+
const parts = timeFormatter(locale, cycle, withSeconds).formatToParts(
|
|
141
|
+
utcDate(13, 30, 5),
|
|
142
|
+
);
|
|
143
|
+
const isNumber = (part: Intl.DateTimeFormatPart) =>
|
|
144
|
+
part.type === 'hour' ||
|
|
145
|
+
part.type === 'minute' ||
|
|
146
|
+
part.type === 'second';
|
|
147
|
+
const first = parts.findIndex(isNumber);
|
|
148
|
+
const last = parts.length - 1 - [...parts].reverse().findIndex(isNumber);
|
|
149
|
+
parts.forEach((part, index) => {
|
|
150
|
+
if (part.type !== 'literal') {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const text = normalizeText(part.value).trim();
|
|
154
|
+
if (text === '' || text === ':') {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
(index > first && index < last ? separatorSet : edgeSet).add(text);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const separators = [...separatorSet];
|
|
162
|
+
const edgeWords = [...edgeSet].sort((a, b) => b.length - a.length);
|
|
163
|
+
|
|
164
|
+
// Every hour, because some locales name several periods across the day
|
|
165
|
+
// (flexible day periods). A word that appears on both sides of noon cannot
|
|
166
|
+
// say which half it means, so it is left out.
|
|
167
|
+
const twelveHour = timeFormatter(locale, '12h', false);
|
|
168
|
+
const sides = new Map<string, Set<boolean>>();
|
|
169
|
+
for (let hour = 0; hour < 24; hour++) {
|
|
170
|
+
const word = twelveHour
|
|
171
|
+
.formatToParts(utcDate(hour, 0, 0))
|
|
172
|
+
.find(part => part.type === 'dayPeriod')?.value;
|
|
173
|
+
if (word === undefined) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const key = normalizeText(word).trim();
|
|
177
|
+
if (key === '') {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const seen = sides.get(key) ?? new Set<boolean>();
|
|
181
|
+
seen.add(hour >= 12);
|
|
182
|
+
sides.set(key, seen);
|
|
183
|
+
}
|
|
184
|
+
const dayPeriods = [...sides]
|
|
185
|
+
.filter(([, seen]) => seen.size === 1)
|
|
186
|
+
.map(([word, seen]) => ({word, isPM: seen.has(true)}))
|
|
187
|
+
.sort((a, b) => b.word.length - a.word.length);
|
|
188
|
+
|
|
189
|
+
const twelveHourIsAmbiguous =
|
|
190
|
+
normalizeText(twelveHour.format(utcDate(2, 0, 0))) ===
|
|
191
|
+
normalizeText(twelveHour.format(utcDate(14, 0, 0)));
|
|
192
|
+
|
|
193
|
+
symbols = {
|
|
194
|
+
hourFormat,
|
|
195
|
+
separators,
|
|
196
|
+
edgeWords,
|
|
197
|
+
dayPeriods,
|
|
198
|
+
twelveHourIsAmbiguous,
|
|
199
|
+
};
|
|
200
|
+
symbolsByLocale.set(locale, symbols);
|
|
201
|
+
return symbols;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function normalizeText(text: string): string {
|
|
205
|
+
const folded = text
|
|
206
|
+
.normalize('NFKC')
|
|
207
|
+
.replace(INVISIBLE_CHARS, '')
|
|
208
|
+
.replace(SPACES, ' ');
|
|
209
|
+
return (toAsciiDigits(folded) ?? folded).toLowerCase();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function escapeRegExp(text: string): string {
|
|
213
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The hour format a field shows: the caller's explicit choice, else the
|
|
218
|
+
* locale's hour cycle (including a `-u-hc-` extension on the tag).
|
|
219
|
+
*/
|
|
220
|
+
export function resolveHourFormat(
|
|
221
|
+
locale: Locale,
|
|
222
|
+
hourFormat?: HourFormat,
|
|
223
|
+
): HourFormat {
|
|
224
|
+
return hourFormat ?? getLocaleTimeSymbols(locale).hourFormat;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Formats an ISO time as the locale writes it, in the given hour format.
|
|
229
|
+
* en-US: "14:30" → "2:30 PM"; de-DE 24h: "14:30"; ko-KR 12h: "오후 2:30".
|
|
230
|
+
* Returns '' for an empty or malformed time.
|
|
231
|
+
*/
|
|
232
|
+
export function formatLocaleTime(
|
|
233
|
+
time: ISOTimeString | undefined,
|
|
234
|
+
locale: Locale,
|
|
235
|
+
hourFormat: HourFormat,
|
|
236
|
+
includeSeconds: boolean = false,
|
|
237
|
+
): string {
|
|
238
|
+
if (!time) {
|
|
239
|
+
return '';
|
|
240
|
+
}
|
|
241
|
+
const parsed = parseISOTime(time);
|
|
242
|
+
if (!parsed) {
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
const formatLocale =
|
|
246
|
+
hourFormat === '12h' && getLocaleTimeSymbols(locale).twelveHourIsAmbiguous
|
|
247
|
+
? 'en'
|
|
248
|
+
: locale;
|
|
249
|
+
return timeFormatter(formatLocale, hourFormat, includeSeconds)
|
|
250
|
+
.format(utcDate(parsed.hour, parsed.minute, parsed.second))
|
|
251
|
+
.replace(SPACES, ' ');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Parses typed text into an ISO time, accepting the locale's digits,
|
|
256
|
+
* separator, and day-period words as well as everything `parseTimeInput`
|
|
257
|
+
* accepts. Returns null when no reading fits.
|
|
258
|
+
*/
|
|
259
|
+
export function parseLocaleTime(
|
|
260
|
+
input: string,
|
|
261
|
+
locale: Locale,
|
|
262
|
+
includeSeconds: boolean = false,
|
|
263
|
+
): ISOTimeString | null {
|
|
264
|
+
if (!input) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const {separators, edgeWords, dayPeriods} = getLocaleTimeSymbols(locale);
|
|
268
|
+
let text = normalizeText(input).trim();
|
|
269
|
+
|
|
270
|
+
// Peel the day period and edge words off both ends, in whatever order the
|
|
271
|
+
// locale writes them ("ŋdi ga 2:30", "kl. 2.30 pm").
|
|
272
|
+
let isPM: boolean | null = null;
|
|
273
|
+
// A word counts only when a digit, whitespace, or nothing sits on its inner
|
|
274
|
+
// side, so gd's "m" (morning) is not read out of an English "pm" or "p.m".
|
|
275
|
+
const strip = (word: string) => {
|
|
276
|
+
if (text.startsWith(word) && WORD_BOUNDARY.test(text.charAt(word.length))) {
|
|
277
|
+
text = text.slice(word.length).trim();
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
if (
|
|
281
|
+
text.endsWith(word) &&
|
|
282
|
+
WORD_BOUNDARY.test(text.charAt(text.length - word.length - 1))
|
|
283
|
+
) {
|
|
284
|
+
text = text.slice(0, -word.length).trim();
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
};
|
|
289
|
+
for (let peeled = true; peeled;) {
|
|
290
|
+
peeled = false;
|
|
291
|
+
if (isPM === null) {
|
|
292
|
+
const period = dayPeriods.find(({word}) => strip(word));
|
|
293
|
+
if (period !== undefined) {
|
|
294
|
+
isPM = period.isPM;
|
|
295
|
+
peeled = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (edgeWords.some(strip)) {
|
|
299
|
+
peeled = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const separator of separators) {
|
|
304
|
+
text = text.replace(
|
|
305
|
+
new RegExp(`(\\d)\\s*${escapeRegExp(separator)}\\s*(?=\\d)`, 'g'),
|
|
306
|
+
'$1:',
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (isPM !== null) {
|
|
311
|
+
text = `${text} ${isPM ? 'pm' : 'am'}`;
|
|
312
|
+
}
|
|
313
|
+
return parseTimeInput(text, includeSeconds);
|
|
314
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file touchPresentation.stylex.ts
|
|
5
|
+
* @input Uses StyleX defineConsts
|
|
6
|
+
* @output Exports touchKeys: the touch presentation resolver's queries as
|
|
7
|
+
* StyleX at-rule keys
|
|
8
|
+
* @position The reference spellings of the touch, compact and iOS-gate
|
|
9
|
+
* conditions. Core styles write these as literal keys wherever they need
|
|
10
|
+
* them (`spec:AST-094` FR13, `spec:AST-012` FR9), and a test pins each
|
|
11
|
+
* literal to these values; utils/touchPresentation.ts holds the same
|
|
12
|
+
* queries for script (matchMedia)
|
|
13
|
+
*
|
|
14
|
+
* These are the spellings to match. A style that changes with these
|
|
15
|
+
* conditions writes the same literal key (`'@media (pointer: coarse)'`, the
|
|
16
|
+
* iOS gate as that media key outside `'@supports (-webkit-touch-callout:
|
|
17
|
+
* none)'`), and a test pins every literal in core to these values. Do not use
|
|
18
|
+
* an imported key as a computed StyleX key: it compiles to a different
|
|
19
|
+
* priority than the literal (6000 against 3200) and reorders the cascade.
|
|
20
|
+
* The iOS supports gate is only for a style iOS WebKit itself requires (the
|
|
21
|
+
* 16px input floor), never a behaviour.
|
|
22
|
+
*
|
|
23
|
+
* SYNC: When modified, update touchPresentation.ts and
|
|
24
|
+
* touchPresentation.test.ts.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as stylex from '@stylexjs/stylex';
|
|
28
|
+
|
|
29
|
+
export const touchKeys = stylex.defineConsts({
|
|
30
|
+
/** The primary pointer is coarse (TOUCH_QUERY). */
|
|
31
|
+
touch: '@media (pointer: coarse)',
|
|
32
|
+
/** The viewport is compact (COMPACT_QUERY, `spec:AST-012` FR9). */
|
|
33
|
+
compact: '@media (width < 768px), (height < 500px)',
|
|
34
|
+
/** Both: a presentation such as a sheet in place of a popover. */
|
|
35
|
+
compactTouch:
|
|
36
|
+
'@media (width < 768px) and (pointer: coarse), (height < 500px) and (pointer: coarse)',
|
|
37
|
+
/**
|
|
38
|
+
* The iOS WebKit gate, inside `touch` (IOS_SUPPORTS_QUERY): only for a
|
|
39
|
+
* style iOS WebKit itself requires (the 16px input floor), never a
|
|
40
|
+
* behaviour; touch behaviour is one model on every touch device.
|
|
41
|
+
*/
|
|
42
|
+
iosSupports: '@supports (-webkit-touch-callout: none)',
|
|
43
|
+
});
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
COMPACT_TOUCH_PRESENTATION_QUERY,
|
|
23
23
|
useAdaptivePresentation,
|
|
24
24
|
} from '../hooks/useAdaptivePresentation';
|
|
25
|
+
import {reducedMotionKeys} from '../motion/reducedMotion.stylex';
|
|
25
26
|
import {DEFAULT_COMPACT_DEFINITION} from '../theme/themeAdaptations';
|
|
26
27
|
import {Theme, defineTheme} from '../theme';
|
|
27
28
|
import {
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
readTouchPresentation,
|
|
34
35
|
useTouchPresentation,
|
|
35
36
|
} from './touchPresentation';
|
|
37
|
+
import {touchKeys} from './touchPresentation.stylex';
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* A device: what its primary pointer is, how big its viewport is, and
|
|
@@ -90,6 +92,32 @@ afterEach(() => {
|
|
|
90
92
|
vi.unstubAllGlobals();
|
|
91
93
|
});
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The Levenshtein distance between two strings, or more than `limit` as soon
|
|
97
|
+
* as it must exceed it: how many characters a spelling is off another.
|
|
98
|
+
*/
|
|
99
|
+
function editDistance(a: string, b: string, limit: number): number {
|
|
100
|
+
if (Math.abs(a.length - b.length) > limit) {
|
|
101
|
+
return limit + 1;
|
|
102
|
+
}
|
|
103
|
+
let previous = Array.from({length: b.length + 1}, (_, j) => j);
|
|
104
|
+
for (let i = 1; i <= a.length; i++) {
|
|
105
|
+
const current = [i];
|
|
106
|
+
for (let j = 1; j <= b.length; j++) {
|
|
107
|
+
current[j] = Math.min(
|
|
108
|
+
previous[j] + 1,
|
|
109
|
+
current[j - 1] + 1,
|
|
110
|
+
previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (Math.min(...current) > limit) {
|
|
114
|
+
return limit + 1;
|
|
115
|
+
}
|
|
116
|
+
previous = current;
|
|
117
|
+
}
|
|
118
|
+
return previous[b.length];
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
describe('touch presentation', () => {
|
|
94
122
|
it('keys touch on the primary pointer and compact on the width', () => {
|
|
95
123
|
const {result} = renderHook(() => useTouchPresentation());
|
|
@@ -181,6 +209,116 @@ describe('touch presentation', () => {
|
|
|
181
209
|
});
|
|
182
210
|
});
|
|
183
211
|
|
|
212
|
+
it('holds the same keys the resolver defines', () => {
|
|
213
|
+
expect(touchKeys.touch).toBe(`@media ${TOUCH_QUERY}`);
|
|
214
|
+
expect(touchKeys.compact).toBe(`@media ${COMPACT_QUERY}`);
|
|
215
|
+
expect(touchKeys.compactTouch).toBe(`@media ${COMPACT_TOUCH_QUERY}`);
|
|
216
|
+
expect(touchKeys.iosSupports).toBe(`@supports ${IOS_SUPPORTS_QUERY}`);
|
|
217
|
+
expect(IOS_TOUCH_STYLE_KEYS).toEqual([
|
|
218
|
+
touchKeys.touch,
|
|
219
|
+
touchKeys.iosSupports,
|
|
220
|
+
]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('keeps every touch and Reduce Motion key literal in core: no computed StyleX key', () => {
|
|
224
|
+
// An imported defineConsts at-rule key compiles to a different StyleX
|
|
225
|
+
// priority than the same literal (6000 vs 3200), which reorders the
|
|
226
|
+
// cascade; core spells the keys and matches them to the constants.
|
|
227
|
+
const root = path.resolve(__dirname, '..');
|
|
228
|
+
const computed: string[] = [];
|
|
229
|
+
const walk = (dir: string): void => {
|
|
230
|
+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
|
|
231
|
+
const full = path.join(dir, entry.name);
|
|
232
|
+
if (entry.isDirectory()) {
|
|
233
|
+
if (entry.name !== '__tests__' && entry.name !== 'node_modules') {
|
|
234
|
+
walk(full);
|
|
235
|
+
}
|
|
236
|
+
} else if (
|
|
237
|
+
/\.(ts|tsx)$/.test(entry.name) &&
|
|
238
|
+
!/\.test\.|\.spec\./.test(entry.name)
|
|
239
|
+
) {
|
|
240
|
+
fs.readFileSync(full, 'utf8')
|
|
241
|
+
.split('\n')
|
|
242
|
+
.forEach((line, index) => {
|
|
243
|
+
if (
|
|
244
|
+
!/^\s*(\*|\/\/)/.test(line) &&
|
|
245
|
+
/\[(touchKeys|reducedMotionKeys)\.[a-zA-Z]+\]\s*:/.test(line)
|
|
246
|
+
) {
|
|
247
|
+
computed.push(`${path.relative(root, full)}:${index + 1}`);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
walk(root);
|
|
254
|
+
expect(computed).toEqual([]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('keeps every quoted at-rule key in core that names the pointer, the touch callout or a compact size, or that is a few characters off a reference spelling, one of the reference spellings', () => {
|
|
258
|
+
// Every quoted string that starts with `@` is read, whatever its at-rule
|
|
259
|
+
// is spelled. Two rules, so no drift depends on which character moved:
|
|
260
|
+
// - one that names the pointer or the touch callout (any word of either
|
|
261
|
+
// query, any case), or a compact size with its breakpoint, is one of
|
|
262
|
+
// the resolver's own spellings or a query the presentation does not
|
|
263
|
+
// decide (a second pointer, a fine pointer, Reduce Motion split by
|
|
264
|
+
// pointer);
|
|
265
|
+
// - one within three characters of any of those spellings, case aside,
|
|
266
|
+
// is that spelling exactly: a capital, a digit or a typo anywhere, in
|
|
267
|
+
// the at-rule, a feature or a value, fails.
|
|
268
|
+
const pointerKeys = new Set<string>([
|
|
269
|
+
touchKeys.touch,
|
|
270
|
+
touchKeys.compactTouch,
|
|
271
|
+
touchKeys.iosSupports,
|
|
272
|
+
'@media (any-pointer: coarse)',
|
|
273
|
+
'@media (pointer: fine)',
|
|
274
|
+
reducedMotionKeys.fade,
|
|
275
|
+
reducedMotionKeys.instant,
|
|
276
|
+
]);
|
|
277
|
+
const references = new Set<string>([...pointerKeys, touchKeys.compact]);
|
|
278
|
+
const lowered = [...references].map(reference => reference.toLowerCase());
|
|
279
|
+
const root = path.resolve(__dirname, '..');
|
|
280
|
+
const drifted: string[] = [];
|
|
281
|
+
let pointerLiterals = 0;
|
|
282
|
+
const walk = (dir: string): void => {
|
|
283
|
+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
|
|
284
|
+
const full = path.join(dir, entry.name);
|
|
285
|
+
if (entry.isDirectory()) {
|
|
286
|
+
if (entry.name !== '__tests__' && entry.name !== 'node_modules') {
|
|
287
|
+
walk(full);
|
|
288
|
+
}
|
|
289
|
+
} else if (
|
|
290
|
+
/\.(ts|tsx)$/.test(entry.name) &&
|
|
291
|
+
!/\.(test|spec|stories)\./.test(entry.name)
|
|
292
|
+
) {
|
|
293
|
+
const source = fs.readFileSync(full, 'utf8');
|
|
294
|
+
for (const match of source.matchAll(/'(@[^'\n]*)'/g)) {
|
|
295
|
+
const key = match[1];
|
|
296
|
+
if (references.has(key)) {
|
|
297
|
+
if (pointerKeys.has(key)) {
|
|
298
|
+
pointerLiterals++;
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const names =
|
|
303
|
+
/pointer|coarse|fine|touch|callout/i.test(key) ||
|
|
304
|
+
(/\b(width|height)\b/i.test(key) && /\b(768|500)px\b/.test(key));
|
|
305
|
+
const lower = key.toLowerCase();
|
|
306
|
+
const isNear = lowered.some(
|
|
307
|
+
reference => editDistance(lower, reference, 3) <= 3,
|
|
308
|
+
);
|
|
309
|
+
if (names || isNear) {
|
|
310
|
+
drifted.push(`${path.relative(root, full)}: ${key}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
walk(root);
|
|
317
|
+
expect(drifted).toEqual([]);
|
|
318
|
+
// Core writes the touch key as a literal in many places.
|
|
319
|
+
expect(pointerLiterals).toBeGreaterThan(50);
|
|
320
|
+
});
|
|
321
|
+
|
|
184
322
|
it('keeps every iOS gate in core spelled and nested as the resolver', () => {
|
|
185
323
|
// The gate is the media query outside and the supports query inside; a
|
|
186
324
|
// single supports query that names the pointer never matches anywhere.
|
|
@@ -201,7 +339,8 @@ describe('touch presentation', () => {
|
|
|
201
339
|
} else if (
|
|
202
340
|
/\.(ts|tsx)$/.test(entry.name) &&
|
|
203
341
|
!/\.test\.|\.spec\./.test(entry.name) &&
|
|
204
|
-
entry.name !== 'touchPresentation.ts'
|
|
342
|
+
entry.name !== 'touchPresentation.ts' &&
|
|
343
|
+
entry.name !== 'touchPresentation.stylex.ts'
|
|
205
344
|
) {
|
|
206
345
|
const lines = fs.readFileSync(full, 'utf8').split('\n');
|
|
207
346
|
lines.forEach((line, index) => {
|
|
@@ -46,9 +46,11 @@
|
|
|
46
46
|
* test keeps core's compact-touch keys identical to COMPACT_TOUCH_QUERY and
|
|
47
47
|
* every touch-callout key spelled and nested as above. The iOS style gate
|
|
48
48
|
* is only for a style iOS WebKit itself requires (core's 16px input floor);
|
|
49
|
-
* it never selects a behaviour.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* it never selects a behaviour. Core spells the keys literally: an imported
|
|
50
|
+
* `defineConsts` key compiles to a different StyleX priority than the same
|
|
51
|
+
* literal, which would reorder the cascade. `touchKeys`
|
|
52
|
+
* (touchPresentation.stylex.ts, internal to core) holds the same keys, and a
|
|
53
|
+
* test keeps every literal equal to them. The hook is for behaviour. It answers the server default
|
|
52
54
|
* (not touch, not compact) until hydrated unless the caller passes a server
|
|
53
55
|
* hint, and follows the pointer and size when they change (a tablet docking
|
|
54
56
|
* a mouse).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file unicodeDigits.test.ts
|
|
5
|
+
* @input Uses vitest and the unicodeDigits helpers
|
|
6
|
+
* @output Unit tests for digit values across scripts
|
|
7
|
+
* @position Testing; validates utils/unicodeDigits.ts
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When unicodeDigits.ts changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, expect, it} from 'vitest';
|
|
13
|
+
import {INVISIBLE_CHARS, digitValue, toAsciiDigits} from './unicodeDigits';
|
|
14
|
+
|
|
15
|
+
describe('digitValue', () => {
|
|
16
|
+
it.each([
|
|
17
|
+
['7', 7],
|
|
18
|
+
['٧', 7], // Arabic-Indic
|
|
19
|
+
['۷', 7], // Extended Arabic-Indic
|
|
20
|
+
['७', 7], // Devanagari
|
|
21
|
+
['৭', 7], // Bengali
|
|
22
|
+
['7', 7], // full-width
|
|
23
|
+
])('reads %s as %i', (char, value) => {
|
|
24
|
+
expect(digitValue(char)).toBe(value);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns null for a non-digit', () => {
|
|
28
|
+
expect(digitValue('a')).toBeNull();
|
|
29
|
+
expect(digitValue('')).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('toAsciiDigits', () => {
|
|
34
|
+
it('rewrites digits and leaves everything else', () => {
|
|
35
|
+
expect(toAsciiDigits('٢:٣٠ م')).toBe('2:30 م');
|
|
36
|
+
expect(toAsciiDigits('१२,३४५.६')).toBe('12,345.6');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('INVISIBLE_CHARS', () => {
|
|
41
|
+
it('strips bidi marks and zero-width characters', () => {
|
|
42
|
+
expect('\u200F١٢\u061C\uFEFF'.replace(INVISIBLE_CHARS, '')).toBe('١٢');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file unicodeDigits.ts
|
|
5
|
+
* @input Text in any digit script
|
|
6
|
+
* @output Exports INVISIBLE_CHARS, digitValue, and toAsciiDigits
|
|
7
|
+
* @position Internal shared utility for the locale-aware parsers
|
|
8
|
+
* (NumberInput/numberParser.ts, utils/timeFormat.ts); not part of the
|
|
9
|
+
* public surface
|
|
10
|
+
*
|
|
11
|
+
* Locale-independent: a digit's value is a Unicode property, not a locale
|
|
12
|
+
* preference, so this needs no Intl and no locale.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
15
|
+
* - /packages/core/src/utils/unicodeDigits.test.ts
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Built at runtime rather than written as a literal: Babel's
|
|
19
|
+
// transform-unicode-property-regex rewrites a `\p{...}` regex LITERAL into an
|
|
20
|
+
// enumerated character class, and the copy bundled with Next rejects the
|
|
21
|
+
// General_Category short names, failing the sandbox build. Property escapes are
|
|
22
|
+
// ES2018 and need no transform in any browser this package supports.
|
|
23
|
+
const DECIMAL_DIGIT = new RegExp('\\p{Nd}', 'u');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Zero-width joiners, bidi marks and isolates (which Intl writes around RTL
|
|
27
|
+
* numbers and day periods), and the BOM Excel prepends.
|
|
28
|
+
*/
|
|
29
|
+
export const INVISIBLE_CHARS =
|
|
30
|
+
/[\u200B-\u200D\u200E\u200F\u061C\u2066-\u2069\uFEFF]/g;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Value of any Unicode decimal digit. Each digit script lays its ten digits
|
|
34
|
+
* out consecutively, so walking back to the script's zero derives the value
|
|
35
|
+
* for Arabic-Indic, Devanagari, full-width and the rest without shipping a
|
|
36
|
+
* table of them. Returns null for a non-digit, and in the one shape the walk
|
|
37
|
+
* cannot resolve: two digit blocks adjacent in code space (the mathematical
|
|
38
|
+
* alphanumerics).
|
|
39
|
+
*/
|
|
40
|
+
export function digitValue(char: string): number | null {
|
|
41
|
+
const codePoint = char.codePointAt(0);
|
|
42
|
+
if (codePoint == null || !DECIMAL_DIGIT.test(char)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
for (let zero = codePoint; codePoint - zero < 10; zero--) {
|
|
46
|
+
if (!DECIMAL_DIGIT.test(String.fromCodePoint(zero - 1))) {
|
|
47
|
+
return codePoint - zero;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Rewrites every decimal digit as its ASCII digit, leaving other characters
|
|
55
|
+
* alone. Returns null when a digit's value cannot be resolved.
|
|
56
|
+
*/
|
|
57
|
+
export function toAsciiDigits(text: string): string | null {
|
|
58
|
+
let out = '';
|
|
59
|
+
for (const char of text) {
|
|
60
|
+
if (!DECIMAL_DIGIT.test(char)) {
|
|
61
|
+
out += char;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const value = digitValue(char);
|
|
65
|
+
if (value == null) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
out += String(value);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|