@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
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* @file interactionModality.ts
|
|
7
7
|
* @input Global pointerdown/keydown events
|
|
8
8
|
* @output `useInteractionModalityTracking()` activates document-lifetime tracking;
|
|
9
|
-
* `getInteractionModality()` reads how the user last interacted
|
|
9
|
+
* `getInteractionModality()` reads how the user last interacted;
|
|
10
|
+
* `trackInteractionModalityIn(doc)` and `getInteractionModality(doc)` do the
|
|
11
|
+
* same for a second document
|
|
10
12
|
* @position Internal document-wide singleton; used where `:focus-visible` alone
|
|
11
13
|
* is too broad
|
|
12
14
|
*
|
|
@@ -26,8 +28,12 @@
|
|
|
26
28
|
* The store lives on `document`, so duplicate Astryx bundles share one state
|
|
27
29
|
* value and one listener pair. The first mounted consumer activates tracking;
|
|
28
30
|
* the listeners then remain for the document lifetime so input during a gap
|
|
29
|
-
* between consumers is not lost.
|
|
30
|
-
*
|
|
31
|
+
* between consumers is not lost. A second document (a same-origin iframe, a
|
|
32
|
+
* Document Picture-in-Picture window) gets its own store when a component
|
|
33
|
+
* tracks it, since input there never reaches the page's listeners; its
|
|
34
|
+
* listeners stay only while a consumer there is mounted. Importing this module
|
|
35
|
+
* has no DOM side effects, and server rendering keeps the keyboard-safe
|
|
36
|
+
* default.
|
|
31
37
|
*/
|
|
32
38
|
|
|
33
39
|
import {useEffect} from 'react';
|
|
@@ -37,6 +43,8 @@ export type InteractionModality = 'keyboard' | 'pointer';
|
|
|
37
43
|
type InteractionModalityStore = {
|
|
38
44
|
modality: InteractionModality;
|
|
39
45
|
isListening: boolean;
|
|
46
|
+
/** Mounted consumers holding a second document's listeners. */
|
|
47
|
+
consumers?: number;
|
|
40
48
|
onPointerDown: () => void;
|
|
41
49
|
onKeyDown: (event: KeyboardEvent) => void;
|
|
42
50
|
};
|
|
@@ -107,8 +115,59 @@ export function useInteractionModalityTracking(): void {
|
|
|
107
115
|
useEffect(() => trackInteractionModality(), []);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
/**
|
|
111
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Track a second document while a consumer there is mounted: a component an
|
|
120
|
+
* app renders into a same-origin iframe or a Document Picture-in-Picture
|
|
121
|
+
* window gets input there that never reaches the page's listeners. The first
|
|
122
|
+
* consumer installs the document's listener pair and the last one's cleanup
|
|
123
|
+
* removes it. Read the value with `getInteractionModality(doc)`. The page's
|
|
124
|
+
* own document keeps its document-lifetime listeners.
|
|
125
|
+
*/
|
|
126
|
+
export function trackInteractionModalityIn(
|
|
127
|
+
targetDocument: Document,
|
|
128
|
+
): () => void {
|
|
129
|
+
if (typeof document !== 'undefined' && targetDocument === document) {
|
|
130
|
+
return trackInteractionModality();
|
|
131
|
+
}
|
|
132
|
+
const store = getStore(targetDocument);
|
|
133
|
+
store.consumers = (store.consumers ?? 0) + 1;
|
|
134
|
+
if (store.consumers === 1) {
|
|
135
|
+
store.isListening = true;
|
|
136
|
+
targetDocument.addEventListener('pointerdown', store.onPointerDown, {
|
|
137
|
+
capture: true,
|
|
138
|
+
passive: true,
|
|
139
|
+
});
|
|
140
|
+
targetDocument.addEventListener('keydown', store.onKeyDown, {
|
|
141
|
+
capture: true,
|
|
142
|
+
passive: true,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
let isReleased = false;
|
|
146
|
+
return () => {
|
|
147
|
+
if (isReleased) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
isReleased = true;
|
|
151
|
+
store.consumers = (store.consumers ?? 1) - 1;
|
|
152
|
+
if (store.consumers === 0) {
|
|
153
|
+
store.isListening = false;
|
|
154
|
+
targetDocument.removeEventListener('pointerdown', store.onPointerDown, {
|
|
155
|
+
capture: true,
|
|
156
|
+
});
|
|
157
|
+
targetDocument.removeEventListener('keydown', store.onKeyDown, {
|
|
158
|
+
capture: true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** How the user last interacted with the page, or with `targetDocument`. */
|
|
165
|
+
export function getInteractionModality(
|
|
166
|
+
targetDocument?: Document,
|
|
167
|
+
): InteractionModality {
|
|
168
|
+
if (targetDocument != null) {
|
|
169
|
+
return getStore(targetDocument).modality;
|
|
170
|
+
}
|
|
112
171
|
return typeof document === 'undefined'
|
|
113
172
|
? 'keyboard'
|
|
114
173
|
: getStore(document).modality;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
plainDateFromISO,
|
|
8
8
|
plainDateToISO,
|
|
9
9
|
plainDateToDate,
|
|
10
|
+
dateTimeFormatFor,
|
|
10
11
|
plainDateFromDate,
|
|
11
12
|
plainDateToday,
|
|
12
13
|
getDaysInMonth,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
plainDateGetWeekNumber,
|
|
29
30
|
plainDateFormat,
|
|
30
31
|
formatSharedDate,
|
|
32
|
+
DATE_FORMAT_LONG,
|
|
31
33
|
DATE_FORMAT_WITH_WEEKDAY,
|
|
32
34
|
DATE_FORMAT_MONTH_YEAR,
|
|
33
35
|
plainDateWeekFieldName,
|
|
@@ -754,6 +756,94 @@ describe('plainDateGetWeekNumber', () => {
|
|
|
754
756
|
});
|
|
755
757
|
|
|
756
758
|
describe('plainDateFormat', () => {
|
|
759
|
+
it('keeps one formatter per locale and options, whatever order the options are in', () => {
|
|
760
|
+
// A month of day labels, as a calendar renders them, reuses one formatter.
|
|
761
|
+
const first = dateTimeFormatFor('en-NZ', DATE_FORMAT_WITH_WEEKDAY);
|
|
762
|
+
expect(dateTimeFormatFor('en-NZ', DATE_FORMAT_WITH_WEEKDAY)).toBe(first);
|
|
763
|
+
expect(
|
|
764
|
+
dateTimeFormatFor('en-NZ', {
|
|
765
|
+
year: 'numeric',
|
|
766
|
+
day: 'numeric',
|
|
767
|
+
month: 'long',
|
|
768
|
+
weekday: 'long',
|
|
769
|
+
}),
|
|
770
|
+
).toBe(first);
|
|
771
|
+
// Other options, or another locale, get their own.
|
|
772
|
+
expect(dateTimeFormatFor('en-NZ', DATE_FORMAT_MONTH_YEAR)).not.toBe(first);
|
|
773
|
+
expect(dateTimeFormatFor('en-ZA', DATE_FORMAT_WITH_WEEKDAY)).not.toBe(
|
|
774
|
+
first,
|
|
775
|
+
);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('formats the same date after the host zone moves', () => {
|
|
779
|
+
// The page stays open while the device travels east: a formatter cached
|
|
780
|
+
// in the first zone must not move the date.
|
|
781
|
+
const zone = process.env.TZ;
|
|
782
|
+
try {
|
|
783
|
+
process.env.TZ = 'America/Los_Angeles';
|
|
784
|
+
expect(
|
|
785
|
+
plainDateFormat(
|
|
786
|
+
{year: 2026, month: 2, day: 1},
|
|
787
|
+
DATE_FORMAT_MONTH_YEAR,
|
|
788
|
+
'en-IE',
|
|
789
|
+
),
|
|
790
|
+
).toBe('February 2026');
|
|
791
|
+
process.env.TZ = 'America/New_York';
|
|
792
|
+
expect(
|
|
793
|
+
plainDateFormat(
|
|
794
|
+
{year: 2026, month: 2, day: 1},
|
|
795
|
+
DATE_FORMAT_MONTH_YEAR,
|
|
796
|
+
'en-IE',
|
|
797
|
+
),
|
|
798
|
+
).toBe('February 2026');
|
|
799
|
+
process.env.TZ = 'Pacific/Kiritimati';
|
|
800
|
+
expect(
|
|
801
|
+
plainDateFormat(
|
|
802
|
+
{year: 2026, month: 2, day: 1},
|
|
803
|
+
DATE_FORMAT_WITH_WEEKDAY,
|
|
804
|
+
'en-IE',
|
|
805
|
+
),
|
|
806
|
+
).toBe('Sunday 1 February 2026');
|
|
807
|
+
} finally {
|
|
808
|
+
if (zone === undefined) {
|
|
809
|
+
delete process.env.TZ;
|
|
810
|
+
} else {
|
|
811
|
+
process.env.TZ = zone;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it('prints a zone name or a time of day in the host zone, as before', () => {
|
|
817
|
+
// A PlainDate has neither: such fields read local midnight in the zone
|
|
818
|
+
// current at the call, not the UTC the date-only path formats in.
|
|
819
|
+
const zone = process.env.TZ;
|
|
820
|
+
try {
|
|
821
|
+
process.env.TZ = 'America/Los_Angeles';
|
|
822
|
+
const withZone = plainDateFormat(
|
|
823
|
+
{year: 2026, month: 1, day: 25},
|
|
824
|
+
{...DATE_FORMAT_LONG, timeZoneName: 'short'},
|
|
825
|
+
'en-US',
|
|
826
|
+
);
|
|
827
|
+
expect(withZone).toContain('January 25, 2026');
|
|
828
|
+
expect(withZone).toContain('PST');
|
|
829
|
+
expect(withZone).not.toContain('UTC');
|
|
830
|
+
process.env.TZ = 'America/New_York';
|
|
831
|
+
expect(
|
|
832
|
+
plainDateFormat(
|
|
833
|
+
{year: 2026, month: 1, day: 25},
|
|
834
|
+
{...DATE_FORMAT_LONG, timeZoneName: 'short'},
|
|
835
|
+
'en-US',
|
|
836
|
+
),
|
|
837
|
+
).toContain('EST');
|
|
838
|
+
} finally {
|
|
839
|
+
if (zone === undefined) {
|
|
840
|
+
delete process.env.TZ;
|
|
841
|
+
} else {
|
|
842
|
+
process.env.TZ = zone;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
757
847
|
it('returns a human-readable date string with weekday', () => {
|
|
758
848
|
const result = plainDateFormat(
|
|
759
849
|
{year: 2026, month: 1, day: 25},
|
package/src/utils/plainDate.ts
CHANGED
|
@@ -374,10 +374,65 @@ export function plainDateFormat(
|
|
|
374
374
|
options: Intl.DateTimeFormatOptions,
|
|
375
375
|
locale: Locale = 'en',
|
|
376
376
|
): string {
|
|
377
|
-
|
|
377
|
+
const calendar = options.calendar ?? 'gregory';
|
|
378
|
+
if (options.timeZone != null) {
|
|
379
|
+
return dateTimeFormatFor(locale, {...options, calendar}).format(
|
|
380
|
+
plainDateToDate(pd),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
if (TIME_OF_DAY_FIELDS.some(field => options[field] != null)) {
|
|
384
|
+
// A zone name or a time of day reads local midnight in the zone current
|
|
385
|
+
// at the call. A cached formatter would keep the zone it was built in,
|
|
386
|
+
// so this one is built for the call.
|
|
387
|
+
return new Intl.DateTimeFormat(locale, {...options, calendar}).format(
|
|
388
|
+
plainDateToDate(pd),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
// Date fields alone: UTC midnight formatted in UTC, so neither the cached
|
|
392
|
+
// formatter nor the date depends on the host's zone, which can change while
|
|
393
|
+
// a page is open.
|
|
394
|
+
return dateTimeFormatFor(locale, {
|
|
378
395
|
...options,
|
|
379
|
-
calendar
|
|
380
|
-
|
|
396
|
+
calendar,
|
|
397
|
+
timeZone: 'UTC',
|
|
398
|
+
}).format(utcTime(pd.year, pd.month - 1, pd.day));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Options that print a time of day or a zone, which a PlainDate lacks. */
|
|
402
|
+
const TIME_OF_DAY_FIELDS = [
|
|
403
|
+
'timeZoneName',
|
|
404
|
+
'hour',
|
|
405
|
+
'minute',
|
|
406
|
+
'second',
|
|
407
|
+
'fractionalSecondDigits',
|
|
408
|
+
'dayPeriod',
|
|
409
|
+
'timeStyle',
|
|
410
|
+
] as const satisfies ReadonlyArray<keyof Intl.DateTimeFormatOptions>;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* One `Intl.DateTimeFormat` per locale and options, built on first use.
|
|
414
|
+
* Building a formatter costs far more than formatting with it, and a calendar
|
|
415
|
+
* formats each of a month's 42 day labels with the same options on every
|
|
416
|
+
* render. The options are keyed in a fixed order, so the same options written
|
|
417
|
+
* in another order share a formatter.
|
|
418
|
+
*/
|
|
419
|
+
const dateTimeFormats = new Map<string, Intl.DateTimeFormat>();
|
|
420
|
+
|
|
421
|
+
/** @internal plainDateFormat's formatter cache; not part of the public API. */
|
|
422
|
+
export function dateTimeFormatFor(
|
|
423
|
+
locale: Locale,
|
|
424
|
+
options: Intl.DateTimeFormatOptions,
|
|
425
|
+
): Intl.DateTimeFormat {
|
|
426
|
+
const key = JSON.stringify([
|
|
427
|
+
locale,
|
|
428
|
+
Object.entries(options).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
429
|
+
]);
|
|
430
|
+
let format = dateTimeFormats.get(key);
|
|
431
|
+
if (format == null) {
|
|
432
|
+
format = new Intl.DateTimeFormat(locale, options);
|
|
433
|
+
dateTimeFormats.set(key, format);
|
|
434
|
+
}
|
|
435
|
+
return format;
|
|
381
436
|
}
|
|
382
437
|
|
|
383
438
|
/**
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file presentation.test.ts
|
|
5
|
+
* @input Uses vitest, React Testing Library, react-dom/server and a
|
|
6
|
+
* query-aware matchMedia and CSS.supports stub
|
|
7
|
+
* @output A caller's explicit presentation wins: `'popover'` pins the fine
|
|
8
|
+
* surface and input modes on an iPhone; an omitted value follows the
|
|
9
|
+
* component's default (`'popover'` or `'touch'`), distinguishable from an
|
|
10
|
+
* explicit `'popover'`; `'adaptive'` and `'bottom-sheet'` keep their sheet
|
|
11
|
+
* meaning with touch modes under a finger, the same on every touch device;
|
|
12
|
+
* the surface equals useAdaptivePresentation's for every
|
|
13
|
+
* value on every device; live pointer changes; the server and hydrating
|
|
14
|
+
* render, which no hint can put into touch modes; the one-moment read
|
|
15
|
+
* @position Unit tests of packages/core/src/utils/presentation.ts
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {act, renderHook} from '@testing-library/react';
|
|
19
|
+
import {createElement, type ComponentProps, type ReactNode} from 'react';
|
|
20
|
+
import {hydrateRoot, type Root} from 'react-dom/client';
|
|
21
|
+
import {renderToString} from 'react-dom/server';
|
|
22
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
23
|
+
import {
|
|
24
|
+
useAdaptivePresentation,
|
|
25
|
+
type AdaptivePresentation,
|
|
26
|
+
} from '../hooks/useAdaptivePresentation';
|
|
27
|
+
import {
|
|
28
|
+
readPresentation,
|
|
29
|
+
usePresentation,
|
|
30
|
+
type PresentationDefault,
|
|
31
|
+
type UsePresentationOptions,
|
|
32
|
+
} from './presentation';
|
|
33
|
+
import {Theme, defineTheme} from '../theme';
|
|
34
|
+
import {IOS_SUPPORTS_QUERY} from './touchPresentation';
|
|
35
|
+
import {
|
|
36
|
+
compactDefinition,
|
|
37
|
+
DEFAULT_COMPACT_DEFINITION,
|
|
38
|
+
} from '../theme/themeAdaptations';
|
|
39
|
+
|
|
40
|
+
const device = {coarse: false, width: 1280, height: 800, iosWebKit: false};
|
|
41
|
+
const listeners = new Set<() => void>();
|
|
42
|
+
|
|
43
|
+
/** Answers a media query list: `,` is or, `and` is and. */
|
|
44
|
+
function answer(query: string): boolean {
|
|
45
|
+
return query.split(', ').some(alternative =>
|
|
46
|
+
alternative.split(' and ').every(part => {
|
|
47
|
+
if (part === '(pointer: coarse)') {
|
|
48
|
+
return device.coarse;
|
|
49
|
+
}
|
|
50
|
+
const range = /^\((width|height) < (\d+)px\)$/.exec(part);
|
|
51
|
+
if (range != null) {
|
|
52
|
+
return device[range[1] as 'width' | 'height'] < Number(range[2]);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`unexpected query ${query}`);
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setDevice(next: Partial<typeof device>): void {
|
|
60
|
+
Object.assign(device, next);
|
|
61
|
+
for (const listener of [...listeners]) {
|
|
62
|
+
listener();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const IPHONE = {coarse: true, width: 393, height: 852, iosWebKit: true};
|
|
67
|
+
const IPAD = {coarse: true, width: 820, height: 1180, iosWebKit: true};
|
|
68
|
+
const ANDROID = {coarse: true, width: 412, height: 915, iosWebKit: false};
|
|
69
|
+
const DESKTOP = {coarse: false, width: 1280, height: 800, iosWebKit: false};
|
|
70
|
+
const MAC_SAFARI = {coarse: false, width: 1440, height: 900, iosWebKit: false};
|
|
71
|
+
/** A narrow window under a fine pointer: compact by size, never by touch. */
|
|
72
|
+
const FINE_NARROW = {coarse: false, width: 600, height: 800, iosWebKit: false};
|
|
73
|
+
/** A phone in landscape: compact by its height alone. */
|
|
74
|
+
const PHONE_LANDSCAPE = {
|
|
75
|
+
coarse: true,
|
|
76
|
+
width: 852,
|
|
77
|
+
height: 393,
|
|
78
|
+
iosWebKit: true,
|
|
79
|
+
};
|
|
80
|
+
/** The compact edges at the default md point (768 wide, 500 high). */
|
|
81
|
+
const COMPACT_EDGES = [
|
|
82
|
+
{coarse: true, width: 767, height: 800, iosWebKit: false},
|
|
83
|
+
{coarse: true, width: 768, height: 800, iosWebKit: false},
|
|
84
|
+
{coarse: true, width: 1024, height: 499, iosWebKit: false},
|
|
85
|
+
{coarse: true, width: 1024, height: 500, iosWebKit: false},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
setDevice(DESKTOP);
|
|
90
|
+
vi.stubGlobal('CSS', {
|
|
91
|
+
supports: (condition: string) =>
|
|
92
|
+
condition === IOS_SUPPORTS_QUERY && device.iosWebKit,
|
|
93
|
+
});
|
|
94
|
+
vi.stubGlobal(
|
|
95
|
+
'matchMedia',
|
|
96
|
+
vi.fn((query: string) => ({
|
|
97
|
+
get matches() {
|
|
98
|
+
return answer(query);
|
|
99
|
+
},
|
|
100
|
+
media: query,
|
|
101
|
+
addEventListener: (_type: string, listener: () => void) =>
|
|
102
|
+
listeners.add(listener),
|
|
103
|
+
removeEventListener: (_type: string, listener: () => void) =>
|
|
104
|
+
listeners.delete(listener),
|
|
105
|
+
})),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
listeners.clear();
|
|
111
|
+
vi.unstubAllGlobals();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const resolve = (
|
|
115
|
+
presentation: AdaptivePresentation | undefined,
|
|
116
|
+
whenOmitted?: PresentationDefault,
|
|
117
|
+
) =>
|
|
118
|
+
renderHook(() => usePresentation(presentation, {whenOmitted})).result.current;
|
|
119
|
+
|
|
120
|
+
/** A component whose omitted presentation is the touch default. */
|
|
121
|
+
const TOUCH_WHEN_OMITTED: UsePresentationOptions = {whenOmitted: 'touch'};
|
|
122
|
+
|
|
123
|
+
describe('presentation: a caller choice wins over touch defaults', () => {
|
|
124
|
+
it('pins the fine surface and input modes with an explicit popover, even on an iPhone', () => {
|
|
125
|
+
setDevice(IPHONE);
|
|
126
|
+
for (const whenOmitted of ['popover', 'touch'] as const) {
|
|
127
|
+
expect(resolve('popover', whenOmitted)).toEqual({
|
|
128
|
+
surface: 'popover',
|
|
129
|
+
touchModes: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("tells an omitted value from an explicit popover: the component's touch default applies only when omitted", () => {
|
|
135
|
+
setDevice(IPHONE);
|
|
136
|
+
expect(resolve(undefined, 'touch')).toEqual({
|
|
137
|
+
surface: 'popover',
|
|
138
|
+
touchModes: true,
|
|
139
|
+
});
|
|
140
|
+
// Today's components: omitted is popover.
|
|
141
|
+
expect(resolve(undefined)).toEqual(resolve('popover'));
|
|
142
|
+
expect(resolve(undefined, 'popover')).toEqual(resolve('popover'));
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('turns touch modes on under a finger only, the same on every touch device', () => {
|
|
146
|
+
setDevice(ANDROID);
|
|
147
|
+
expect(resolve(undefined, 'touch')).toEqual({
|
|
148
|
+
surface: 'popover',
|
|
149
|
+
touchModes: true,
|
|
150
|
+
});
|
|
151
|
+
for (const desk of [DESKTOP, MAC_SAFARI]) {
|
|
152
|
+
setDevice(desk);
|
|
153
|
+
expect(resolve(undefined, 'touch')).toMatchObject({
|
|
154
|
+
touchModes: false,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('keeps the sheet meaning of adaptive and bottom-sheet, with touch modes under a finger', () => {
|
|
160
|
+
setDevice(IPHONE);
|
|
161
|
+
expect(resolve('adaptive')).toEqual({
|
|
162
|
+
surface: 'bottom-sheet',
|
|
163
|
+
touchModes: true,
|
|
164
|
+
});
|
|
165
|
+
setDevice(IPAD);
|
|
166
|
+
expect(resolve('adaptive')).toMatchObject({
|
|
167
|
+
surface: 'popover',
|
|
168
|
+
touchModes: true,
|
|
169
|
+
});
|
|
170
|
+
setDevice(DESKTOP);
|
|
171
|
+
expect(resolve('adaptive')).toMatchObject({
|
|
172
|
+
surface: 'popover',
|
|
173
|
+
touchModes: false,
|
|
174
|
+
});
|
|
175
|
+
expect(resolve('bottom-sheet')).toMatchObject({
|
|
176
|
+
surface: 'bottom-sheet',
|
|
177
|
+
touchModes: false,
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("renders useAdaptivePresentation's surface for every value on every device", () => {
|
|
182
|
+
for (const next of [
|
|
183
|
+
IPHONE,
|
|
184
|
+
IPAD,
|
|
185
|
+
ANDROID,
|
|
186
|
+
DESKTOP,
|
|
187
|
+
MAC_SAFARI,
|
|
188
|
+
FINE_NARROW,
|
|
189
|
+
PHONE_LANDSCAPE,
|
|
190
|
+
...COMPACT_EDGES,
|
|
191
|
+
]) {
|
|
192
|
+
setDevice(next);
|
|
193
|
+
for (const value of ['popover', 'bottom-sheet', 'adaptive'] as const) {
|
|
194
|
+
const {result} = renderHook(() => ({
|
|
195
|
+
surface: usePresentation(value).surface,
|
|
196
|
+
adaptive: useAdaptivePresentation(value),
|
|
197
|
+
}));
|
|
198
|
+
expect(result.current.surface).toBe(result.current.adaptive);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("answers 'adaptive' with the sheet only on a compact touch screen: a fine narrow window, a phone in landscape and each compact edge (spec:AST-094 FR13, spec:AST-012 FR9)", () => {
|
|
204
|
+
for (const [next, surface] of [
|
|
205
|
+
[FINE_NARROW, 'popover'],
|
|
206
|
+
[PHONE_LANDSCAPE, 'bottom-sheet'],
|
|
207
|
+
[COMPACT_EDGES[0], 'bottom-sheet'],
|
|
208
|
+
[COMPACT_EDGES[1], 'popover'],
|
|
209
|
+
[COMPACT_EDGES[2], 'bottom-sheet'],
|
|
210
|
+
[COMPACT_EDGES[3], 'popover'],
|
|
211
|
+
] as const) {
|
|
212
|
+
setDevice(next);
|
|
213
|
+
expect({...next, surface: resolve('adaptive').surface}).toEqual({
|
|
214
|
+
...next,
|
|
215
|
+
surface,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("follows the active theme's md point for 'adaptive' (spec:AST-012 FR9)", () => {
|
|
221
|
+
const wideMd = defineTheme({
|
|
222
|
+
name: 'wide-md-presentation',
|
|
223
|
+
adaptations: {widthBreakpoints: {md: 1000, lg: 1100}},
|
|
224
|
+
});
|
|
225
|
+
const wrapper = ({children}: {children: ReactNode}) =>
|
|
226
|
+
createElement(
|
|
227
|
+
Theme,
|
|
228
|
+
{theme: wideMd} as ComponentProps<typeof Theme>,
|
|
229
|
+
children,
|
|
230
|
+
);
|
|
231
|
+
setDevice({coarse: true, width: 900, height: 1024});
|
|
232
|
+
// Under the default md point (768) this screen is not compact.
|
|
233
|
+
const plain = renderHook(() => usePresentation('adaptive'));
|
|
234
|
+
expect(plain.result.current.surface).toBe('popover');
|
|
235
|
+
plain.unmount();
|
|
236
|
+
// Under a theme whose md is 1000, the same screen is compact.
|
|
237
|
+
const themed = renderHook(() => usePresentation('adaptive'), {wrapper});
|
|
238
|
+
expect(themed.result.current).toEqual({
|
|
239
|
+
surface: 'bottom-sheet',
|
|
240
|
+
touchModes: true,
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('follows the pointer when it changes (an iPad docking a trackpad)', () => {
|
|
245
|
+
setDevice(IPAD);
|
|
246
|
+
const {result} = renderHook(() =>
|
|
247
|
+
usePresentation(undefined, {whenOmitted: 'touch'}),
|
|
248
|
+
);
|
|
249
|
+
expect(result.current.touchModes).toBe(true);
|
|
250
|
+
act(() => setDevice({coarse: false}));
|
|
251
|
+
// A fine pointer in this context, checked before the claim.
|
|
252
|
+
expect(window.matchMedia('(pointer: coarse)').matches).toBe(false);
|
|
253
|
+
expect(result.current).toMatchObject({touchModes: false});
|
|
254
|
+
act(() => setDevice({coarse: true}));
|
|
255
|
+
expect(result.current).toMatchObject({touchModes: true});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('renders no touch surface or input mode on the server or while hydrating, and takes no hint that would (spec:AST-094 FR13)', () => {
|
|
259
|
+
// On a phone, where a live read would answer touch modes.
|
|
260
|
+
setDevice(IPHONE);
|
|
261
|
+
function Probe({
|
|
262
|
+
presentation,
|
|
263
|
+
options = TOUCH_WHEN_OMITTED,
|
|
264
|
+
}: {
|
|
265
|
+
presentation?: AdaptivePresentation;
|
|
266
|
+
options?: UsePresentationOptions;
|
|
267
|
+
}) {
|
|
268
|
+
const resolved = usePresentation(presentation, options);
|
|
269
|
+
return `${resolved.surface}/${String(resolved.touchModes)}`;
|
|
270
|
+
}
|
|
271
|
+
expect(renderToString(createElement(Probe))).toContain('popover/false');
|
|
272
|
+
expect(
|
|
273
|
+
renderToString(createElement(Probe, {presentation: 'adaptive'})),
|
|
274
|
+
).toContain('popover/false');
|
|
275
|
+
expect(
|
|
276
|
+
renderToString(createElement(Probe, {presentation: 'bottom-sheet'})),
|
|
277
|
+
).toContain('bottom-sheet/false');
|
|
278
|
+
// A server hint would put touch modes into the server render, which
|
|
279
|
+
// hydration then contradicts: usePresentation takes none.
|
|
280
|
+
const hinted = {
|
|
281
|
+
whenOmitted: 'touch',
|
|
282
|
+
// @ts-expect-error usePresentation takes no serverDefault.
|
|
283
|
+
serverDefault: {isTouch: true, isCompact: true},
|
|
284
|
+
} satisfies UsePresentationOptions;
|
|
285
|
+
expect(
|
|
286
|
+
renderToString(
|
|
287
|
+
createElement(Probe, {presentation: 'adaptive', options: hinted}),
|
|
288
|
+
),
|
|
289
|
+
).toContain('popover/false');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('hydrates on a phone from the server render, then follows the device (spec:AST-094 FR13)', async () => {
|
|
293
|
+
setDevice(IPHONE);
|
|
294
|
+
const renders: string[] = [];
|
|
295
|
+
function Probe({presentation}: {presentation: AdaptivePresentation}) {
|
|
296
|
+
const resolved = usePresentation(presentation, TOUCH_WHEN_OMITTED);
|
|
297
|
+
const text = `${resolved.surface}/${String(resolved.touchModes)}`;
|
|
298
|
+
renders.push(text);
|
|
299
|
+
return text;
|
|
300
|
+
}
|
|
301
|
+
const html = renderToString(
|
|
302
|
+
createElement(Probe, {presentation: 'adaptive'}),
|
|
303
|
+
);
|
|
304
|
+
expect(html).toContain('popover/false');
|
|
305
|
+
const container = document.createElement('div');
|
|
306
|
+
container.innerHTML = html;
|
|
307
|
+
document.body.append(container);
|
|
308
|
+
renders.length = 0;
|
|
309
|
+
const recoverable = vi.fn();
|
|
310
|
+
let root: Root | null = null;
|
|
311
|
+
await act(async () => {
|
|
312
|
+
root = hydrateRoot(
|
|
313
|
+
container,
|
|
314
|
+
createElement(Probe, {presentation: 'adaptive'}),
|
|
315
|
+
{onRecoverableError: recoverable},
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
// The hydrating render is the server's; the phone's answer follows it.
|
|
319
|
+
expect(renders[0]).toBe('popover/false');
|
|
320
|
+
expect(renders.at(-1)).toBe('bottom-sheet/true');
|
|
321
|
+
expect(recoverable).not.toHaveBeenCalled();
|
|
322
|
+
act(() => root?.unmount());
|
|
323
|
+
container.remove();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('subscribes only to the queries a request reads (architecture:react-component-runtime INV5)', () => {
|
|
327
|
+
setDevice(IPHONE);
|
|
328
|
+
for (const [presentation, whenOmitted, subscriptions] of [
|
|
329
|
+
['popover', 'touch', 0],
|
|
330
|
+
[undefined, 'popover', 0],
|
|
331
|
+
['bottom-sheet', 'popover', 1],
|
|
332
|
+
[undefined, 'touch', 1],
|
|
333
|
+
['adaptive', 'popover', 2],
|
|
334
|
+
] as const) {
|
|
335
|
+
listeners.clear();
|
|
336
|
+
const {unmount} = renderHook(() =>
|
|
337
|
+
usePresentation(presentation, {whenOmitted}),
|
|
338
|
+
);
|
|
339
|
+
expect({
|
|
340
|
+
presentation,
|
|
341
|
+
whenOmitted,
|
|
342
|
+
subscriptions: listeners.size,
|
|
343
|
+
}).toEqual({presentation, whenOmitted, subscriptions});
|
|
344
|
+
unmount();
|
|
345
|
+
expect(listeners.size).toBe(0);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('answers at one moment as the hook does, and as the server would with no window', () => {
|
|
350
|
+
setDevice(IPHONE);
|
|
351
|
+
const compact = DEFAULT_COMPACT_DEFINITION;
|
|
352
|
+
expect(
|
|
353
|
+
readPresentation(undefined, {whenOmitted: 'touch', compact}),
|
|
354
|
+
).toEqual(resolve(undefined, 'touch'));
|
|
355
|
+
expect(
|
|
356
|
+
readPresentation('popover', {whenOmitted: 'touch', compact}),
|
|
357
|
+
).toEqual(resolve('popover', 'touch'));
|
|
358
|
+
expect(readPresentation('adaptive', {compact})).toEqual(
|
|
359
|
+
resolve('adaptive'),
|
|
360
|
+
);
|
|
361
|
+
expect(
|
|
362
|
+
readPresentation(undefined, {whenOmitted: 'touch', view: null, compact}),
|
|
363
|
+
).toEqual({
|
|
364
|
+
surface: 'popover',
|
|
365
|
+
touchModes: false,
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('reads the compact definition it is given, so a theme that moves md moves the sheet (spec:AST-012 FR9)', () => {
|
|
370
|
+
setDevice({...IPHONE, width: 900, height: 1024});
|
|
371
|
+
expect(
|
|
372
|
+
readPresentation('adaptive', {compact: DEFAULT_COMPACT_DEFINITION})
|
|
373
|
+
.surface,
|
|
374
|
+
).toBe('popover');
|
|
375
|
+
expect(
|
|
376
|
+
readPresentation('adaptive', {compact: compactDefinition({md: 1000})})
|
|
377
|
+
.surface,
|
|
378
|
+
).toBe('bottom-sheet');
|
|
379
|
+
});
|
|
380
|
+
});
|