@astryxdesign/core 0.7.5 → 0.7.6-canary.5cd7a15
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/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 +4 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +10 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +16 -2
- 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/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/MultiSelector.d.ts +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +5 -2
- 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/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +7 -1
- package/dist/Spinner/Spinner.js +2 -2
- 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/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 +15 -1
- 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/useAdaptivePresentation.d.ts.map +1 -1
- package/dist/hooks/useAdaptivePresentation.js +4 -1
- 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 +3 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +3 -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.js +1 -1
- 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/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/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/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 +15 -3
- package/locales/pseudo.json +9 -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/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 +7 -1
- 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 +53 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +23 -6
- 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/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/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/MultiSelector.spec.md +7 -4
- package/src/MultiSelector/MultiSelector.test.tsx +118 -1
- package/src/MultiSelector/MultiSelector.tsx +6 -2
- 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/Slider/Slider.test.tsx +12 -0
- package/src/Slider/Slider.tsx +4 -1
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
- package/src/Spinner/Spinner.tsx +2 -2
- 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/Typeahead/Typeahead.test.tsx +40 -0
- package/src/Typeahead/Typeahead.tsx +12 -1
- 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/useAdaptivePresentation.ts +4 -1
- package/src/hooks/useMenuHover.ts +15 -1
- package/src/i18n/generated-locales/en.generated.ts +4 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +4 -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/defineTheme.ts +1 -1
- 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/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/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/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
|
@@ -680,7 +680,7 @@ function PointerDateField({
|
|
|
680
680
|
// Unified change handler that fires both onChange and changeAction
|
|
681
681
|
const fireChange = useCallback(
|
|
682
682
|
(newValue: ISODateString | undefined) => {
|
|
683
|
-
if (
|
|
683
|
+
if (isEffectivelyDisabled) {
|
|
684
684
|
return;
|
|
685
685
|
}
|
|
686
686
|
onChange?.(newValue);
|
|
@@ -691,7 +691,13 @@ function PointerDateField({
|
|
|
691
691
|
});
|
|
692
692
|
}
|
|
693
693
|
},
|
|
694
|
-
[
|
|
694
|
+
[
|
|
695
|
+
isEffectivelyDisabled,
|
|
696
|
+
onChange,
|
|
697
|
+
changeAction,
|
|
698
|
+
startTransition,
|
|
699
|
+
setOptimisticValue,
|
|
700
|
+
],
|
|
695
701
|
);
|
|
696
702
|
|
|
697
703
|
// Handle clear button click
|
|
@@ -714,11 +720,16 @@ function PointerDateField({
|
|
|
714
720
|
// Handle date selection from calendar
|
|
715
721
|
const handleDateSelect = useCallback(
|
|
716
722
|
(selectedDate: ISODateString) => {
|
|
723
|
+
// An already-open calendar can outlive an enabled owner. Reject the
|
|
724
|
+
// whole selection, including dismissal, so its focused day stays put.
|
|
725
|
+
if (isDisabled) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
717
728
|
fireChange(selectedDate);
|
|
718
729
|
setPendingInput(null);
|
|
719
730
|
popover.hide();
|
|
720
731
|
},
|
|
721
|
-
[fireChange, popover],
|
|
732
|
+
[isDisabled, fireChange, popover],
|
|
722
733
|
);
|
|
723
734
|
|
|
724
735
|
// Handle input text change - update immediately if valid and allowed
|
|
@@ -948,17 +959,21 @@ function PointerDateField({
|
|
|
948
959
|
{isBusy && <Spinner size="sm" aria-hidden="true" />}
|
|
949
960
|
{statusIcon}
|
|
950
961
|
{popover.render(
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
+
// The layer renders its children open or closed, so mount the
|
|
963
|
+
// calendar only while it is open; it opens on the current value.
|
|
964
|
+
!popover.isOpen ? null : (
|
|
965
|
+
<Calendar
|
|
966
|
+
handleRef={calendarRef}
|
|
967
|
+
mode="single"
|
|
968
|
+
value={optimisticValue ?? null}
|
|
969
|
+
onChange={handleDateSelect}
|
|
970
|
+
min={min}
|
|
971
|
+
max={max}
|
|
972
|
+
dateConstraints={dateConstraints}
|
|
973
|
+
numberOfMonths={numberOfMonths}
|
|
974
|
+
weekStartsOn={weekStartsOn}
|
|
975
|
+
/>
|
|
976
|
+
),
|
|
962
977
|
{placement: 'below', alignment: 'start'},
|
|
963
978
|
)}
|
|
964
979
|
{showsDisabledMessage &&
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
afterEach,
|
|
30
30
|
} from 'vitest';
|
|
31
31
|
import {
|
|
32
|
+
act,
|
|
32
33
|
render,
|
|
33
34
|
screen,
|
|
34
35
|
fireEvent,
|
|
@@ -276,6 +277,81 @@ function weekdayRow(): HTMLElement {
|
|
|
276
277
|
return row as HTMLElement;
|
|
277
278
|
}
|
|
278
279
|
|
|
280
|
+
describe('DateInput — open-sheet disablement', () => {
|
|
281
|
+
it('blocks selection after the open sheet becomes disabled, retaining value and focus', () => {
|
|
282
|
+
const onChange = vi.fn();
|
|
283
|
+
const changeAction = vi.fn();
|
|
284
|
+
const props = {initial: '2026-02-21' as const, onChange, changeAction};
|
|
285
|
+
withLayout(() => {
|
|
286
|
+
const {rerender} = render(<Controlled {...props} />);
|
|
287
|
+
fireEvent.click(field());
|
|
288
|
+
const day = within(pane('February 2026')).getByRole('button', {
|
|
289
|
+
name: /February 22, 2026/,
|
|
290
|
+
});
|
|
291
|
+
act(() => day.focus());
|
|
292
|
+
rerender(<Controlled {...props} isDisabled />);
|
|
293
|
+
expect(day).toHaveFocus();
|
|
294
|
+
fireEvent.click(day);
|
|
295
|
+
|
|
296
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
297
|
+
expect(changeAction).not.toHaveBeenCalled();
|
|
298
|
+
expect(field()).toHaveValue('February 21, 2026');
|
|
299
|
+
expect(field()).toHaveAttribute('aria-expanded', 'true');
|
|
300
|
+
expect(day).toHaveFocus();
|
|
301
|
+
expect(
|
|
302
|
+
document.querySelector('[data-title="month-year"]'),
|
|
303
|
+
).toHaveTextContent('February 2026');
|
|
304
|
+
|
|
305
|
+
rerender(<Controlled {...props} />);
|
|
306
|
+
fireEvent.click(day);
|
|
307
|
+
expect(onChange).toHaveBeenCalledExactlyOnceWith('2026-02-22');
|
|
308
|
+
expect(changeAction).toHaveBeenCalledExactlyOnceWith('2026-02-22');
|
|
309
|
+
expect(field()).toHaveAttribute('aria-expanded', 'true');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('blocks disabled reset but preserves busy-only month navigation', () => {
|
|
314
|
+
const onChange = vi.fn();
|
|
315
|
+
const changeAction = vi.fn();
|
|
316
|
+
const props = {initial: '2026-02-21' as const, onChange, changeAction};
|
|
317
|
+
withLayout(() => {
|
|
318
|
+
const {rerender} = render(<Controlled {...props} />);
|
|
319
|
+
fireEvent.click(field());
|
|
320
|
+
const reset = screen.getByRole('button', {name: 'Reset'});
|
|
321
|
+
act(() => reset.focus());
|
|
322
|
+
rerender(<Controlled {...props} isDisabled />);
|
|
323
|
+
expect(reset).toHaveFocus();
|
|
324
|
+
fireEvent.click(reset);
|
|
325
|
+
|
|
326
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
327
|
+
expect(changeAction).not.toHaveBeenCalled();
|
|
328
|
+
expect(field()).toHaveValue('February 21, 2026');
|
|
329
|
+
expect(field()).toHaveAttribute('aria-expanded', 'true');
|
|
330
|
+
expect(reset).toHaveFocus();
|
|
331
|
+
expect(
|
|
332
|
+
document.querySelector('[data-title="month-year"]'),
|
|
333
|
+
).toHaveTextContent('February 2026');
|
|
334
|
+
|
|
335
|
+
rerender(<Controlled {...props} isLoading />);
|
|
336
|
+
fireEvent.click(reset);
|
|
337
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
338
|
+
expect(changeAction).not.toHaveBeenCalled();
|
|
339
|
+
expect(field()).toHaveValue('February 21, 2026');
|
|
340
|
+
expect(field()).toHaveAttribute('aria-expanded', 'true');
|
|
341
|
+
expect(reset).toHaveFocus();
|
|
342
|
+
expect(
|
|
343
|
+
document.querySelector('[data-title="month-year"]'),
|
|
344
|
+
).toHaveTextContent('March 2026');
|
|
345
|
+
|
|
346
|
+
rerender(<Controlled {...props} />);
|
|
347
|
+
fireEvent.click(reset);
|
|
348
|
+
expect(onChange).toHaveBeenCalledExactlyOnceWith(undefined);
|
|
349
|
+
expect(changeAction).toHaveBeenCalledExactlyOnceWith(undefined);
|
|
350
|
+
expect(field()).toHaveAttribute('aria-expanded', 'true');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
279
355
|
// ---------------------------------------------------------------------------
|
|
280
356
|
// Which surface, and why
|
|
281
357
|
// ---------------------------------------------------------------------------
|
|
@@ -85,6 +85,14 @@ const DAY_SIZE = dateInputTouchSizes.daySize;
|
|
|
85
85
|
*/
|
|
86
86
|
const OVERSCAN = 3;
|
|
87
87
|
|
|
88
|
+
/** Input on the scroller that ends a steer: the user's from here. */
|
|
89
|
+
const USER_INPUT_EVENTS = [
|
|
90
|
+
'touchstart',
|
|
91
|
+
'wheel',
|
|
92
|
+
'pointerdown',
|
|
93
|
+
'keydown',
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
88
96
|
const styles = stylex.create({
|
|
89
97
|
scroller: {
|
|
90
98
|
position: 'relative',
|
|
@@ -326,13 +334,59 @@ export function MonthScroller({
|
|
|
326
334
|
// updater — React runs updaters during render, where another component's
|
|
327
335
|
// setState is illegal.
|
|
328
336
|
const centerRowRef = useRef(centerRow);
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Scroll snapping is off from a re-center until the frame after its
|
|
340
|
+
* commit. WebKit keeps the position it snapped to as an index into the
|
|
341
|
+
* container's snap offsets, and re-snaps to that index after a layout: a
|
|
342
|
+
* re-center that unmounts panes before the snapped one shifts the index,
|
|
343
|
+
* so the re-snap lands a month further on, and the scroll that makes
|
|
344
|
+
* re-centers again. One stalled frame right after an arrow (300 ms,
|
|
345
|
+
* measured) was enough to set the calendar paging on by itself, a month
|
|
346
|
+
* every few frames. With snapping off across the commit nothing re-snaps,
|
|
347
|
+
* and switching it back on a frame later snaps to the pane in place.
|
|
348
|
+
*
|
|
349
|
+
* Each pause counts itself, so a batch of re-centers that ends on the
|
|
350
|
+
* center it began from, which commits no new center, still restores
|
|
351
|
+
* snapping.
|
|
352
|
+
*
|
|
353
|
+
* Not while a finger drives the scroll, from its touch until the scroll it
|
|
354
|
+
* started comes to rest: iOS tracks a snap target for the gesture, and
|
|
355
|
+
* turning snapping off drops it, so the swipe coasted past the month for
|
|
356
|
+
* about two seconds and was corrected late (iOS 26.2 simulator, where the
|
|
357
|
+
* same swipe snaps in about 0.6 s with snapping left on).
|
|
358
|
+
*/
|
|
359
|
+
const [snapPauses, setSnapPauses] = useState(0);
|
|
360
|
+
const isFingerScrollRef = useRef(false);
|
|
361
|
+
const recenter = useCallback((scroller: HTMLElement, row: number) => {
|
|
362
|
+
if (!isFingerScrollRef.current) {
|
|
363
|
+
scroller.style.scrollSnapType = 'none';
|
|
364
|
+
setSnapPauses(count => count + 1);
|
|
365
|
+
}
|
|
366
|
+
setCenterRow(row);
|
|
367
|
+
}, []);
|
|
368
|
+
// Synchronizes the scroller's snapping with the browser's frame loop: it
|
|
369
|
+
// comes back on in the frame after the pause's commit, and a restore still
|
|
370
|
+
// pending at the next pause or at unmount is dropped.
|
|
371
|
+
useLayoutEffect(() => {
|
|
372
|
+
const scroller = scrollerRef.current;
|
|
373
|
+
if (snapPauses === 0 || scroller == null) {
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
const frame = requestAnimationFrame(() => {
|
|
377
|
+
scroller.style.scrollSnapType = '';
|
|
378
|
+
});
|
|
379
|
+
return () => cancelAnimationFrame(frame);
|
|
380
|
+
}, [snapPauses]);
|
|
329
381
|
// A row to scroll to once its pane is mounted; see scrollToMonth.
|
|
330
382
|
const pendingScrollRef = useRef<number | null>(null);
|
|
331
383
|
|
|
332
384
|
/**
|
|
333
385
|
* The row a programmatic scroll is heading for, so its own scroll events
|
|
334
|
-
* are not reported back as if the user had gone there
|
|
335
|
-
*
|
|
386
|
+
* are not reported back as if the user had gone there, and so the settle
|
|
387
|
+
* correction finishes the steer instead of rounding it back. Null once the
|
|
388
|
+
* user's own input reaches the scroller (a finger, a wheel or trackpad, a
|
|
389
|
+
* press, a key), or once the steered scroll has arrived.
|
|
336
390
|
*/
|
|
337
391
|
const steeredRowRef = useRef<number | null>(null);
|
|
338
392
|
|
|
@@ -401,7 +455,12 @@ export function MonthScroller({
|
|
|
401
455
|
// below); a jump that far is never worth animating either.
|
|
402
456
|
pendingScrollRef.current = row;
|
|
403
457
|
centerRowRef.current = row;
|
|
404
|
-
|
|
458
|
+
// The jump is a steer too. Recording it replaces a steer left behind
|
|
459
|
+
// by a scroll that moved nothing (the re-assert when the month and
|
|
460
|
+
// year wheels close), which the settle would otherwise finish,
|
|
461
|
+
// pulling the scroller back off the jump's month.
|
|
462
|
+
steeredRowRef.current = row;
|
|
463
|
+
recenter(scroller, row);
|
|
405
464
|
return;
|
|
406
465
|
}
|
|
407
466
|
steeredRowRef.current = row;
|
|
@@ -410,7 +469,7 @@ export function MonthScroller({
|
|
|
410
469
|
behavior,
|
|
411
470
|
});
|
|
412
471
|
},
|
|
413
|
-
[paneSize, minMonthIndex, rowCount, isRTL],
|
|
472
|
+
[paneSize, minMonthIndex, rowCount, isRTL, recenter],
|
|
414
473
|
);
|
|
415
474
|
|
|
416
475
|
// Deliberately un-keyed: it runs after every render, costs a null check, and
|
|
@@ -476,8 +535,11 @@ export function MonthScroller({
|
|
|
476
535
|
* A tail crawling slower than half a pixel a frame has effectively arrived,
|
|
477
536
|
* so treating it as arrived is right anyway.
|
|
478
537
|
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
538
|
+
* After the user's own scroll the correction goes to the nearest boundary,
|
|
539
|
+
* at most half a pane away. After a steer it goes on to the steered month,
|
|
540
|
+
* however early the browser cut the steer short. Either way its own scroll
|
|
541
|
+
* settles onto the boundary it aimed at, where a steer counts as arrived, so
|
|
542
|
+
* it cannot oscillate.
|
|
481
543
|
*/
|
|
482
544
|
const settleFrameRef = useRef<number | undefined>(undefined);
|
|
483
545
|
useEffect(
|
|
@@ -490,6 +552,8 @@ export function MonthScroller({
|
|
|
490
552
|
);
|
|
491
553
|
|
|
492
554
|
useScrollSettle(scrollerRef, scroller => {
|
|
555
|
+
// At rest, momentum included: the finger no longer drives the scroll.
|
|
556
|
+
isFingerScrollRef.current = false;
|
|
493
557
|
if (paneSize === 0) {
|
|
494
558
|
return;
|
|
495
559
|
}
|
|
@@ -505,15 +569,22 @@ export function MonthScroller({
|
|
|
505
569
|
if (Math.abs(scroller.scrollLeft - offsetBefore) >= 0.5) {
|
|
506
570
|
return;
|
|
507
571
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
572
|
+
// A steer the browser cut short is finished, not rounded back. WebKit
|
|
573
|
+
// ends a smooth scroll that a second one retargeted once the first
|
|
574
|
+
// one's time is up, firing `scrollend` part-way: an arrow tapped late
|
|
575
|
+
// in the previous arrow's scroll stopped near that month, and rounding
|
|
576
|
+
// to the nearest pane put the scroller back on it while the title named
|
|
577
|
+
// the new one.
|
|
578
|
+
const steered = steeredRowRef.current;
|
|
579
|
+
const row =
|
|
580
|
+
steered ??
|
|
581
|
+
rowAtScrollOffset(scroller.scrollLeft, paneSize, rowCount, isRTL);
|
|
514
582
|
const target = scrollOffsetForRow(row, paneSize, isRTL);
|
|
515
583
|
// Sub-pixel drift is the browser's own rounding, not a failed snap.
|
|
516
584
|
if (Math.abs(scroller.scrollLeft - target) < 1) {
|
|
585
|
+
// At rest on its target, a steer has arrived even if no scroll
|
|
586
|
+
// crossed into its month.
|
|
587
|
+
steeredRowRef.current = null;
|
|
517
588
|
return;
|
|
518
589
|
}
|
|
519
590
|
scroller.scrollTo({left: target, behavior: 'smooth'});
|
|
@@ -554,11 +625,20 @@ export function MonthScroller({
|
|
|
554
625
|
if (scroller == null || paneSize === 0) {
|
|
555
626
|
return;
|
|
556
627
|
}
|
|
557
|
-
// A finger
|
|
558
|
-
// passes are the user's doing
|
|
559
|
-
|
|
628
|
+
// A finger, a wheel or trackpad, a press or a key on the scroller ends any
|
|
629
|
+
// steering: from here the months it passes are the user's doing, every
|
|
630
|
+
// one of them is worth reporting, and the settle rounds to the nearest
|
|
631
|
+
// month instead of finishing a steer. A steer that moved nothing (the
|
|
632
|
+
// re-assert when the month and year wheels close on the month shown)
|
|
633
|
+
// would otherwise pull the user's next scroll back to its month.
|
|
634
|
+
const endSteer = () => {
|
|
560
635
|
steeredRowRef.current = null;
|
|
561
636
|
};
|
|
637
|
+
// From a touch until its scroll comes to rest (the settle above), the
|
|
638
|
+
// finger drives the scroll and a re-center leaves snapping on.
|
|
639
|
+
const onTouchStart = () => {
|
|
640
|
+
isFingerScrollRef.current = true;
|
|
641
|
+
};
|
|
562
642
|
|
|
563
643
|
const onScroll = () => {
|
|
564
644
|
if (frameRef.current != null) {
|
|
@@ -576,7 +656,7 @@ export function MonthScroller({
|
|
|
576
656
|
return;
|
|
577
657
|
}
|
|
578
658
|
centerRowRef.current = row;
|
|
579
|
-
|
|
659
|
+
recenter(scroller, row);
|
|
580
660
|
// Report nothing while a steer is in flight. `scrollToMonth` is only
|
|
581
661
|
// ever called by something that already knows the month — a wheel
|
|
582
662
|
// commit, a header arrow, the re-assert when the wheels close — so
|
|
@@ -590,9 +670,9 @@ export function MonthScroller({
|
|
|
590
670
|
// the exact moment reports start being trusted again. The month drifted
|
|
591
671
|
// on the way back to the calendar.
|
|
592
672
|
//
|
|
593
|
-
// Cleared on arrival, or by
|
|
594
|
-
//
|
|
595
|
-
// reporting.
|
|
673
|
+
// Cleared on arrival, or by the user's input below (a finger, a wheel
|
|
674
|
+
// or trackpad, a press, a key) — from then the user owns the scroller,
|
|
675
|
+
// and every month it passes is worth reporting.
|
|
596
676
|
if (steeredRowRef.current != null) {
|
|
597
677
|
if (steeredRowRef.current === row) {
|
|
598
678
|
steeredRowRef.current = null;
|
|
@@ -603,16 +683,22 @@ export function MonthScroller({
|
|
|
603
683
|
});
|
|
604
684
|
};
|
|
605
685
|
scroller.addEventListener('scroll', onScroll, {passive: true});
|
|
686
|
+
for (const type of USER_INPUT_EVENTS) {
|
|
687
|
+
scroller.addEventListener(type, endSteer, {passive: true});
|
|
688
|
+
}
|
|
606
689
|
scroller.addEventListener('touchstart', onTouchStart, {passive: true});
|
|
607
690
|
return () => {
|
|
608
691
|
scroller.removeEventListener('scroll', onScroll);
|
|
692
|
+
for (const type of USER_INPUT_EVENTS) {
|
|
693
|
+
scroller.removeEventListener(type, endSteer);
|
|
694
|
+
}
|
|
609
695
|
scroller.removeEventListener('touchstart', onTouchStart);
|
|
610
696
|
if (frameRef.current != null) {
|
|
611
697
|
cancelAnimationFrame(frameRef.current);
|
|
612
698
|
frameRef.current = undefined;
|
|
613
699
|
}
|
|
614
700
|
};
|
|
615
|
-
}, [paneSize, rowCount, minMonthIndex, isRTL]);
|
|
701
|
+
}, [paneSize, rowCount, minMonthIndex, isRTL, recenter]);
|
|
616
702
|
|
|
617
703
|
// Move the keyboard focus by whole days and let the scroller follow. Paging
|
|
618
704
|
// by month is PageUp/PageDown; everything else is the APG grid vocabulary.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {act, render} from '@testing-library/react';
|
|
4
|
+
import {createRef} from 'react';
|
|
5
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
6
|
+
import {MonthScroller, type MonthScrollerHandle} from './MonthScroller';
|
|
7
|
+
import {SCROLL_QUIET_MS} from './useScrollSettle';
|
|
8
|
+
|
|
9
|
+
class FakeResizeObserver {
|
|
10
|
+
constructor(private callback: ResizeObserverCallback) {}
|
|
11
|
+
observe() {
|
|
12
|
+
this.callback([], this);
|
|
13
|
+
}
|
|
14
|
+
unobserve() {}
|
|
15
|
+
disconnect() {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PANE = 300;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The month scroller's settle, with the browser's scrolling played by hand:
|
|
22
|
+
* `scrollTo` is recorded, and the offset is whatever the test sets.
|
|
23
|
+
*/
|
|
24
|
+
describe('MonthScroller settle', () => {
|
|
25
|
+
const scrollTo = vi.fn();
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.useFakeTimers({
|
|
28
|
+
toFake: [
|
|
29
|
+
'setTimeout',
|
|
30
|
+
'clearTimeout',
|
|
31
|
+
'requestAnimationFrame',
|
|
32
|
+
'cancelAnimationFrame',
|
|
33
|
+
'Date',
|
|
34
|
+
'performance',
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
vi.stubGlobal('ResizeObserver', FakeResizeObserver);
|
|
38
|
+
vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(PANE);
|
|
39
|
+
vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockReturnValue(
|
|
40
|
+
PANE,
|
|
41
|
+
);
|
|
42
|
+
Element.prototype.scrollTo = scrollTo;
|
|
43
|
+
scrollTo.mockClear();
|
|
44
|
+
});
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
vi.unstubAllGlobals();
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function renderScroller() {
|
|
52
|
+
const handleRef = createRef<MonthScrollerHandle>();
|
|
53
|
+
const view = render(
|
|
54
|
+
<MonthScroller
|
|
55
|
+
handleRef={handleRef}
|
|
56
|
+
minMonthIndex={0}
|
|
57
|
+
maxMonthIndex={23}
|
|
58
|
+
initialMonthIndex={9}
|
|
59
|
+
onVisibleMonthChange={() => {}}
|
|
60
|
+
selectedDate={null}
|
|
61
|
+
today={{year: 2026, month: 10, day: 15}}
|
|
62
|
+
isDateDisabled={() => false}
|
|
63
|
+
weekStartsOn={0}
|
|
64
|
+
onSelect={() => {}}
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
const scroller = view.container.querySelector<HTMLElement>(
|
|
68
|
+
'[data-scroller="months"]',
|
|
69
|
+
);
|
|
70
|
+
if (scroller == null) {
|
|
71
|
+
throw new Error('no month scroller');
|
|
72
|
+
}
|
|
73
|
+
scrollTo.mockClear();
|
|
74
|
+
return {handleRef, scroller};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The browser moving the scroller to `left`, as a scroll event reports. */
|
|
78
|
+
/**
|
|
79
|
+
* A touch event carrying the lists a real one always has: jsdom has no
|
|
80
|
+
* constructible TouchEvent, and the gesture hook reads `touches[0]`.
|
|
81
|
+
*/
|
|
82
|
+
function touchEvent(type: 'touchstart' | 'touchend') {
|
|
83
|
+
const event = new Event(type, {bubbles: true});
|
|
84
|
+
const point = {identifier: 1, clientX: 100, clientY: 100};
|
|
85
|
+
Object.defineProperties(event, {
|
|
86
|
+
touches: {value: type === 'touchend' ? [] : [point]},
|
|
87
|
+
targetTouches: {value: type === 'touchend' ? [] : [point]},
|
|
88
|
+
changedTouches: {value: [point]},
|
|
89
|
+
});
|
|
90
|
+
return event;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function scrollBy(scroller: HTMLElement, left: number) {
|
|
94
|
+
scroller.scrollLeft = left;
|
|
95
|
+
scroller.dispatchEvent(new Event('scroll'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
it('turns scroll snapping back on after re-centers that end on the month they began on', () => {
|
|
99
|
+
const {handleRef, scroller} = renderScroller();
|
|
100
|
+
// Two far jumps in one batch, out and back: each re-centers the mounted
|
|
101
|
+
// months, pausing scroll snapping, and the second puts the center back
|
|
102
|
+
// where it was, so the render that follows changes no center.
|
|
103
|
+
act(() => {
|
|
104
|
+
handleRef.current?.scrollToMonth(20);
|
|
105
|
+
handleRef.current?.scrollToMonth(9);
|
|
106
|
+
});
|
|
107
|
+
act(() => {
|
|
108
|
+
vi.advanceTimersByTime(50);
|
|
109
|
+
});
|
|
110
|
+
expect(scroller.style.scrollSnapType).toBe('');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('leaves snapping on for a re-center while a finger drives the scroll', () => {
|
|
114
|
+
const {scroller} = renderScroller();
|
|
115
|
+
// A finger drags the next month in: the scroller crosses the middle of
|
|
116
|
+
// October's pane and re-centers its months with the finger still down.
|
|
117
|
+
act(() => {
|
|
118
|
+
scroller.dispatchEvent(touchEvent('touchstart'));
|
|
119
|
+
});
|
|
120
|
+
act(() => scrollBy(scroller, 9 * PANE + 0.6 * PANE));
|
|
121
|
+
act(() => {
|
|
122
|
+
vi.advanceTimersByTime(16);
|
|
123
|
+
});
|
|
124
|
+
// Snapping stays on: iOS tracks a snap target for the gesture.
|
|
125
|
+
expect(scroller.style.scrollSnapType).toBe('');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('pauses snapping for a re-center again once the finger’s scroll has come to rest', () => {
|
|
129
|
+
const {handleRef, scroller} = renderScroller();
|
|
130
|
+
act(() => {
|
|
131
|
+
scroller.dispatchEvent(touchEvent('touchstart'));
|
|
132
|
+
scroller.dispatchEvent(touchEvent('touchend'));
|
|
133
|
+
});
|
|
134
|
+
act(() => {
|
|
135
|
+
vi.advanceTimersByTime(SCROLL_QUIET_MS + 50);
|
|
136
|
+
});
|
|
137
|
+
// An arrow's far jump after the swipe re-centers with snapping paused.
|
|
138
|
+
act(() => handleRef.current?.scrollToMonth(20));
|
|
139
|
+
expect(scroller.style.scrollSnapType).toBe('none');
|
|
140
|
+
act(() => {
|
|
141
|
+
vi.advanceTimersByTime(50);
|
|
142
|
+
});
|
|
143
|
+
expect(scroller.style.scrollSnapType).toBe('');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('finishes a steer the browser cut short instead of rounding it back', () => {
|
|
147
|
+
const {handleRef, scroller} = renderScroller();
|
|
148
|
+
// An arrow toward month 10, its scroll 95 % of the way there.
|
|
149
|
+
act(() => handleRef.current?.scrollToMonth(10));
|
|
150
|
+
act(() => scrollBy(scroller, 9 * PANE + 0.95 * PANE));
|
|
151
|
+
// The other arrow, late in that scroll: back toward month 9.
|
|
152
|
+
act(() => handleRef.current?.scrollToMonth(9));
|
|
153
|
+
scrollTo.mockClear();
|
|
154
|
+
// WebKit ends the retargeted scroll where the first one's time ran out,
|
|
155
|
+
// near month 10, and the scroller goes quiet there.
|
|
156
|
+
act(() => scrollBy(scroller, 9 * PANE + 0.95 * PANE));
|
|
157
|
+
act(() => {
|
|
158
|
+
vi.advanceTimersByTime(SCROLL_QUIET_MS + 50);
|
|
159
|
+
});
|
|
160
|
+
// The settle finishes the steer onto month 9, not the nearest pane (10).
|
|
161
|
+
expect(
|
|
162
|
+
scrollTo.mock.calls.map(([options]) => (options as ScrollToOptions).left),
|
|
163
|
+
).toEqual([9 * PANE]);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -772,7 +772,7 @@ export function TouchDateField({
|
|
|
772
772
|
|
|
773
773
|
const fireChange = useCallback(
|
|
774
774
|
(newValue: ISODateString | undefined) => {
|
|
775
|
-
if (
|
|
775
|
+
if (isEffectivelyDisabled) {
|
|
776
776
|
return;
|
|
777
777
|
}
|
|
778
778
|
onChange?.(newValue);
|
|
@@ -783,7 +783,13 @@ export function TouchDateField({
|
|
|
783
783
|
});
|
|
784
784
|
}
|
|
785
785
|
},
|
|
786
|
-
[
|
|
786
|
+
[
|
|
787
|
+
isEffectivelyDisabled,
|
|
788
|
+
onChange,
|
|
789
|
+
changeAction,
|
|
790
|
+
startTransition,
|
|
791
|
+
setOptimisticValue,
|
|
792
|
+
],
|
|
787
793
|
);
|
|
788
794
|
|
|
789
795
|
const openSheet = useCallback(() => {
|
|
@@ -841,6 +847,11 @@ export function TouchDateField({
|
|
|
841
847
|
* value is still cleared either way — that half never depends on the range.
|
|
842
848
|
*/
|
|
843
849
|
const handleResetInSheet = useCallback(() => {
|
|
850
|
+
// Disabled: reject the entire reset without closing or moving focus.
|
|
851
|
+
// Busy-only fields keep month navigation; fireChange blocks the clear.
|
|
852
|
+
if (isDisabled) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
844
855
|
fireChange(undefined);
|
|
845
856
|
const currentMonth = monthIndexOf(today);
|
|
846
857
|
if (currentMonth < minMonthIndex || currentMonth > maxMonthIndex) {
|
|
@@ -851,7 +862,7 @@ export function TouchDateField({
|
|
|
851
862
|
}
|
|
852
863
|
setMonthIndex(currentMonth);
|
|
853
864
|
scrollerHandleRef.current?.scrollToMonth(currentMonth, 'smooth');
|
|
854
|
-
}, [fireChange, today, monthIndex, minMonthIndex, maxMonthIndex]);
|
|
865
|
+
}, [isDisabled, fireChange, today, monthIndex, minMonthIndex, maxMonthIndex]);
|
|
855
866
|
|
|
856
867
|
// Selection commits on the tap and leaves the sheet up, so a mistake can be
|
|
857
868
|
// corrected in place and a nearby date reconsidered without reopening.
|