@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
|
@@ -13,7 +13,14 @@ import {useState, type SyntheticEvent} from 'react';
|
|
|
13
13
|
import {readFileSync} from 'node:fs';
|
|
14
14
|
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
15
15
|
import type {MockInstance} from 'vitest';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
render,
|
|
18
|
+
screen,
|
|
19
|
+
fireEvent,
|
|
20
|
+
within,
|
|
21
|
+
cleanup,
|
|
22
|
+
} from '@testing-library/react';
|
|
23
|
+
import {createPortal} from 'react-dom';
|
|
17
24
|
import {
|
|
18
25
|
Dialog,
|
|
19
26
|
dialogFullscreenSafeAreaPaddingContract,
|
|
@@ -21,7 +28,10 @@ import {
|
|
|
21
28
|
} from './Dialog';
|
|
22
29
|
import {DialogHeader} from './DialogHeader';
|
|
23
30
|
import {defineTheme, generateThemeCSS} from '../theme';
|
|
24
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
__resetInteractionModalityForTest,
|
|
33
|
+
getInteractionModality,
|
|
34
|
+
} from '../utils/interactionModality';
|
|
25
35
|
|
|
26
36
|
function generateThemeTestCSS(
|
|
27
37
|
theme: Parameters<typeof generateThemeCSS>[0],
|
|
@@ -555,6 +565,210 @@ describe('Dialog', () => {
|
|
|
555
565
|
}).not.toThrow();
|
|
556
566
|
expect(dialog).not.toHaveAttribute('open');
|
|
557
567
|
});
|
|
568
|
+
|
|
569
|
+
describe('in a second document', () => {
|
|
570
|
+
// An app can render a Dialog into a same-origin iframe or a Document
|
|
571
|
+
// Picture-in-Picture window through a portal. The control that opens it
|
|
572
|
+
// is focused in that document; the page's document reports only the
|
|
573
|
+
// iframe element.
|
|
574
|
+
let frame: HTMLIFrameElement;
|
|
575
|
+
let secondDocument: Document;
|
|
576
|
+
|
|
577
|
+
beforeEach(() => {
|
|
578
|
+
frame = document.createElement('iframe');
|
|
579
|
+
document.body.appendChild(frame);
|
|
580
|
+
secondDocument = frame.contentDocument!;
|
|
581
|
+
// The showModal/close stand-ins above, on the second window's own
|
|
582
|
+
// prototype.
|
|
583
|
+
const {prototype} = secondDocument.defaultView!.HTMLDialogElement;
|
|
584
|
+
prototype.showModal = HTMLDialogElement.prototype.showModal;
|
|
585
|
+
prototype.close = HTMLDialogElement.prototype.close;
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
afterEach(() => {
|
|
589
|
+
// Unmount before the frame goes, or React removes nodes from a
|
|
590
|
+
// document that no longer exists.
|
|
591
|
+
cleanup();
|
|
592
|
+
frame.remove();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
function SecondDocumentDialog({isOpen}: {isOpen: boolean}) {
|
|
596
|
+
return createPortal(
|
|
597
|
+
<>
|
|
598
|
+
<button type="button">Open dialog</button>
|
|
599
|
+
<Dialog isOpen={isOpen} onOpenChange={() => {}} aria-label="Review">
|
|
600
|
+
<button type="button">Inside</button>
|
|
601
|
+
</Dialog>
|
|
602
|
+
</>,
|
|
603
|
+
secondDocument.body,
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function PageOpensSecondDocumentDialog({isOpen}: {isOpen: boolean}) {
|
|
608
|
+
return (
|
|
609
|
+
<>
|
|
610
|
+
<button type="button">Open from the page</button>
|
|
611
|
+
{createPortal(
|
|
612
|
+
<Dialog
|
|
613
|
+
isOpen={isOpen}
|
|
614
|
+
onOpenChange={() => {}}
|
|
615
|
+
aria-label="Review">
|
|
616
|
+
<button type="button">Inside</button>
|
|
617
|
+
</Dialog>,
|
|
618
|
+
secondDocument.body,
|
|
619
|
+
)}
|
|
620
|
+
</>
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function SecondDocumentFieldDialog({isOpen}: {isOpen: boolean}) {
|
|
625
|
+
return createPortal(
|
|
626
|
+
<>
|
|
627
|
+
<input aria-label="Search" />
|
|
628
|
+
<Dialog isOpen={isOpen} onOpenChange={() => {}} aria-label="Review">
|
|
629
|
+
<button type="button">Inside</button>
|
|
630
|
+
</Dialog>
|
|
631
|
+
</>,
|
|
632
|
+
secondDocument.body,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function SecondDocumentDialogs({count}: {count: number}) {
|
|
637
|
+
return createPortal(
|
|
638
|
+
<>
|
|
639
|
+
{count > 0 && (
|
|
640
|
+
<Dialog isOpen={false} onOpenChange={() => {}} aria-label="First">
|
|
641
|
+
First
|
|
642
|
+
</Dialog>
|
|
643
|
+
)}
|
|
644
|
+
{count > 1 && (
|
|
645
|
+
<Dialog
|
|
646
|
+
isOpen={false}
|
|
647
|
+
onOpenChange={() => {}}
|
|
648
|
+
aria-label="Second">
|
|
649
|
+
Second
|
|
650
|
+
</Dialog>
|
|
651
|
+
)}
|
|
652
|
+
</>,
|
|
653
|
+
secondDocument.body,
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
it('returns focus to the control that opened it, in its own document', () => {
|
|
658
|
+
const {rerender} = render(<SecondDocumentDialog isOpen={false} />);
|
|
659
|
+
const view = within(secondDocument.body);
|
|
660
|
+
const opener = view.getByRole('button', {name: 'Open dialog'});
|
|
661
|
+
opener.focus();
|
|
662
|
+
expect(document.activeElement).toBe(frame);
|
|
663
|
+
|
|
664
|
+
rerender(<SecondDocumentDialog isOpen={true} />);
|
|
665
|
+
view.getByRole('button', {name: 'Inside', hidden: true}).focus();
|
|
666
|
+
|
|
667
|
+
rerender(<SecondDocumentDialog isOpen={false} />);
|
|
668
|
+
expect(opener).toHaveFocus();
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('returns focus to a control in the page that opened it', () => {
|
|
672
|
+
const {rerender} = render(
|
|
673
|
+
<PageOpensSecondDocumentDialog isOpen={false} />,
|
|
674
|
+
);
|
|
675
|
+
const opener = screen.getByRole('button', {name: 'Open from the page'});
|
|
676
|
+
opener.focus();
|
|
677
|
+
|
|
678
|
+
rerender(<PageOpensSecondDocumentDialog isOpen={true} />);
|
|
679
|
+
within(secondDocument.body)
|
|
680
|
+
.getByRole('button', {name: 'Inside', hidden: true})
|
|
681
|
+
.focus();
|
|
682
|
+
|
|
683
|
+
rerender(<PageOpensSecondDocumentDialog isOpen={false} />);
|
|
684
|
+
expect(opener).toHaveFocus();
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('takes the ring from the last input in its own document', () => {
|
|
688
|
+
__resetInteractionModalityForTest();
|
|
689
|
+
const {rerender} = render(<SecondDocumentDialog isOpen={false} />);
|
|
690
|
+
const opener = within(secondDocument.body).getByRole('button', {
|
|
691
|
+
name: 'Open dialog',
|
|
692
|
+
});
|
|
693
|
+
// The page's last input was a pointer press (say, on the control that
|
|
694
|
+
// popped the window out); the dialog then closes from the keyboard in
|
|
695
|
+
// its own document.
|
|
696
|
+
fireEvent.pointerDown(document.body);
|
|
697
|
+
opener.focus();
|
|
698
|
+
rerender(<SecondDocumentDialog isOpen={true} />);
|
|
699
|
+
fireEvent.keyDown(secondDocument.body, {key: 'Escape'});
|
|
700
|
+
const focus = vi.spyOn(
|
|
701
|
+
secondDocument.defaultView!.HTMLElement.prototype,
|
|
702
|
+
'focus',
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
rerender(<SecondDocumentDialog isOpen={false} />);
|
|
706
|
+
expect(opener).toHaveFocus();
|
|
707
|
+
expect(
|
|
708
|
+
focus.mock.calls.filter(
|
|
709
|
+
(_call, index) => focus.mock.contexts[index] === opener,
|
|
710
|
+
),
|
|
711
|
+
).toEqual([[]]);
|
|
712
|
+
focus.mockRestore();
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('keeps plain focus on a text field that opened it after a pointer close', () => {
|
|
716
|
+
const {rerender} = render(<SecondDocumentFieldDialog isOpen={false} />);
|
|
717
|
+
const field = within(secondDocument.body).getByRole('textbox', {
|
|
718
|
+
name: 'Search',
|
|
719
|
+
});
|
|
720
|
+
field.focus();
|
|
721
|
+
rerender(<SecondDocumentFieldDialog isOpen={true} />);
|
|
722
|
+
const inside = within(secondDocument.body).getByRole('button', {
|
|
723
|
+
name: 'Inside',
|
|
724
|
+
hidden: true,
|
|
725
|
+
});
|
|
726
|
+
inside.focus();
|
|
727
|
+
fireEvent.pointerDown(inside);
|
|
728
|
+
// The field's own window's prototype: a field there is never an
|
|
729
|
+
// instance of the page's constructors.
|
|
730
|
+
const {prototype} = secondDocument.defaultView!.HTMLElement;
|
|
731
|
+
const focus = vi.spyOn(prototype, 'focus');
|
|
732
|
+
const blur = vi.spyOn(prototype, 'blur');
|
|
733
|
+
|
|
734
|
+
rerender(<SecondDocumentFieldDialog isOpen={false} />);
|
|
735
|
+
expect(field).toHaveFocus();
|
|
736
|
+
expect(
|
|
737
|
+
focus.mock.calls.filter(
|
|
738
|
+
(_call, index) => focus.mock.contexts[index] === field,
|
|
739
|
+
),
|
|
740
|
+
).toEqual([[]]);
|
|
741
|
+
expect(blur.mock.contexts).not.toContain(field);
|
|
742
|
+
focus.mockRestore();
|
|
743
|
+
blur.mockRestore();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it('tracks its document while any Dialog there is mounted, and releases it after the last', () => {
|
|
747
|
+
const removeEventListener = vi.spyOn(
|
|
748
|
+
secondDocument,
|
|
749
|
+
'removeEventListener',
|
|
750
|
+
);
|
|
751
|
+
const removedModalityListeners = () =>
|
|
752
|
+
removeEventListener.mock.calls
|
|
753
|
+
.map(([type]) => type)
|
|
754
|
+
.filter(type => type === 'pointerdown' || type === 'keydown')
|
|
755
|
+
.sort();
|
|
756
|
+
const {rerender} = render(<SecondDocumentDialogs count={2} />);
|
|
757
|
+
fireEvent.pointerDown(secondDocument.body);
|
|
758
|
+
expect(getInteractionModality(secondDocument)).toBe('pointer');
|
|
759
|
+
|
|
760
|
+
rerender(<SecondDocumentDialogs count={1} />);
|
|
761
|
+
fireEvent.keyDown(secondDocument.body, {key: 'Tab'});
|
|
762
|
+
expect(getInteractionModality(secondDocument)).toBe('keyboard');
|
|
763
|
+
expect(removedModalityListeners()).toEqual([]);
|
|
764
|
+
|
|
765
|
+
rerender(<SecondDocumentDialogs count={0} />);
|
|
766
|
+
expect(removedModalityListeners()).toEqual(['keydown', 'pointerdown']);
|
|
767
|
+
fireEvent.pointerDown(secondDocument.body);
|
|
768
|
+
expect(getInteractionModality(secondDocument)).toBe('keyboard');
|
|
769
|
+
removeEventListener.mockRestore();
|
|
770
|
+
});
|
|
771
|
+
});
|
|
558
772
|
});
|
|
559
773
|
|
|
560
774
|
// jsdom draws no focus ring, so these prove only which focus call Dialog
|
package/src/Dialog/Dialog.tsx
CHANGED
|
@@ -69,14 +69,18 @@ import {
|
|
|
69
69
|
overlayPaddingReset,
|
|
70
70
|
} from '../Layout/padding.stylex';
|
|
71
71
|
import type {SpacingStep} from '../utils/types';
|
|
72
|
-
import {mergeProps} from '../utils';
|
|
72
|
+
import {mergeProps} from '../utils/mergeProps';
|
|
73
73
|
import {chainEventHandlers} from '../utils/chainEventHandlers';
|
|
74
74
|
import {devWarn} from '../utils/devWarning';
|
|
75
75
|
import {DialogContext} from './DialogContext';
|
|
76
76
|
import {themeProps} from '../utils/themeProps';
|
|
77
77
|
import type {DialogVariantMap} from './index';
|
|
78
78
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
79
|
-
import {
|
|
79
|
+
import {
|
|
80
|
+
getInteractionModality,
|
|
81
|
+
trackInteractionModalityIn,
|
|
82
|
+
useInteractionModalityTracking,
|
|
83
|
+
} from '../utils/interactionModality';
|
|
80
84
|
import {focusForLastInput} from '../utils/focusReturn';
|
|
81
85
|
import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
|
|
82
86
|
|
|
@@ -91,8 +95,11 @@ function getDialogDirection(
|
|
|
91
95
|
distance = 16,
|
|
92
96
|
): {x: number; y: number} {
|
|
93
97
|
const rect = triggerEl.getBoundingClientRect();
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
// The trigger's own window: its rect is measured in that window's viewport,
|
|
99
|
+
// which is not the page's when the dialog renders into a second document.
|
|
100
|
+
const view = triggerEl.ownerDocument.defaultView ?? window;
|
|
101
|
+
const dx = rect.left + rect.width / 2 - view.innerWidth / 2;
|
|
102
|
+
const dy = rect.top + rect.height / 2 - view.innerHeight / 2;
|
|
96
103
|
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
97
104
|
return {
|
|
98
105
|
x: Math.round((dx / dist) * distance),
|
|
@@ -534,6 +541,18 @@ export function Dialog({
|
|
|
534
541
|
|
|
535
542
|
useInteractionModalityTracking();
|
|
536
543
|
|
|
544
|
+
// Input in a second document (a same-origin iframe, a Document
|
|
545
|
+
// Picture-in-Picture window) never reaches the page's listeners, so the
|
|
546
|
+
// dialog's own document keeps its own last input for the focus return while
|
|
547
|
+
// this Dialog is mounted, from before it opens until it unmounts.
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
const ownerDocument = dialogRef.current?.ownerDocument;
|
|
550
|
+
if (ownerDocument == null || ownerDocument === document) {
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
553
|
+
return trackInteractionModalityIn(ownerDocument);
|
|
554
|
+
}, [isInline]);
|
|
555
|
+
|
|
537
556
|
// This runs in a layout effect so the capture lands before any descendant's
|
|
538
557
|
// passive mount effect: all layout effects run before any passive effect,
|
|
539
558
|
// while a child's passive effect runs before this parent's passive effect.
|
|
@@ -545,7 +564,15 @@ export function Dialog({
|
|
|
545
564
|
if (isInline || !isOpen) {
|
|
546
565
|
return;
|
|
547
566
|
}
|
|
548
|
-
|
|
567
|
+
// The dialog's own document while it holds focus: an app can render a
|
|
568
|
+
// dialog into a second document (a same-origin iframe, a Document
|
|
569
|
+
// Picture-in-Picture window) through a portal, and a control there that
|
|
570
|
+
// opens it is focused in that document, which the page's document reports
|
|
571
|
+
// only as the iframe element, or not at all. A control in the page that
|
|
572
|
+
// opens such a dialog leaves focus in the page.
|
|
573
|
+
const dialogDocument = dialogRef.current?.ownerDocument ?? document;
|
|
574
|
+
const doc = dialogDocument.hasFocus() ? dialogDocument : document;
|
|
575
|
+
triggerElementRef.current = doc.activeElement as HTMLElement | null;
|
|
549
576
|
}, [isOpen, isInline]);
|
|
550
577
|
|
|
551
578
|
// Derive dismissal behavior from purpose
|
|
@@ -567,7 +594,7 @@ export function Dialog({
|
|
|
567
594
|
// captured by the layout effect above, before any descendant effect
|
|
568
595
|
// could move focus.
|
|
569
596
|
const trigger = triggerElementRef.current;
|
|
570
|
-
if (trigger && trigger !==
|
|
597
|
+
if (trigger && trigger !== trigger.ownerDocument.body) {
|
|
571
598
|
const dir = getDialogDirection(trigger);
|
|
572
599
|
dialog.style.setProperty('--dialog-dir-x', `${dir.x}px`);
|
|
573
600
|
dialog.style.setProperty('--dialog-dir-y', `${dir.y}px`);
|
|
@@ -591,9 +618,15 @@ export function Dialog({
|
|
|
591
618
|
if (dialog.open) {
|
|
592
619
|
dialog.close();
|
|
593
620
|
}
|
|
594
|
-
// Return focus to the element that opened the dialog
|
|
595
|
-
|
|
596
|
-
|
|
621
|
+
// Return focus to the element that opened the dialog, with the ring
|
|
622
|
+
// the last input in that element's own document calls for.
|
|
623
|
+
const trigger = triggerElementRef.current;
|
|
624
|
+
if (trigger) {
|
|
625
|
+
focusForLastInput(
|
|
626
|
+
trigger,
|
|
627
|
+
undefined,
|
|
628
|
+
getInteractionModality(trigger.ownerDocument),
|
|
629
|
+
);
|
|
597
630
|
}
|
|
598
631
|
triggerElementRef.current = null;
|
|
599
632
|
}
|
|
@@ -20,15 +20,16 @@ import {useEffect, useRef, type ReactNode} from 'react';
|
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
21
|
import {spacingVars, sizeVars, typeScaleVars} from '../theme/tokens.stylex';
|
|
22
22
|
import {LayoutHeader} from '../Layout/LayoutHeader';
|
|
23
|
-
import {Button} from '../Button';
|
|
24
|
-
import {Icon} from '../Icon';
|
|
23
|
+
import {Button} from '../Button/Button';
|
|
24
|
+
import {Icon} from '../Icon/Icon';
|
|
25
25
|
import {Heading} from '../Heading/Heading';
|
|
26
26
|
import {Text} from '../Text/Text';
|
|
27
27
|
import type {BaseProps} from '../BaseProps';
|
|
28
|
-
import {isRenderable
|
|
28
|
+
import {isRenderable} from '../utils/isRenderable';
|
|
29
|
+
import {mergeProps} from '../utils/mergeProps';
|
|
29
30
|
import {themeProps} from '../utils/themeProps';
|
|
30
31
|
import {useDialogContext} from './DialogContext';
|
|
31
|
-
import {useTranslator} from '../i18n';
|
|
32
|
+
import {useTranslator} from '../i18n/useTranslator';
|
|
32
33
|
|
|
33
34
|
const styles = stylex.create({
|
|
34
35
|
container: {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// @pr-a11y scope ^(packages/core/src/Dialog/|packages/core/src/utils/(focusReturn|interactionModality)\.ts|apps/storybook/stories/DialogSecondDocumentA11y)
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file DialogSecondDocument.a11y.browser.spec.ts
|
|
6
|
+
* @input Uses @playwright/test, the a11y-spec static Storybook server, and the
|
|
7
|
+
* checked-in DialogSecondDocumentA11y story
|
|
8
|
+
* @output Real-browser evidence that a Dialog rendered into a second document
|
|
9
|
+
* (a same-origin iframe, a Document Picture-in-Picture window) returns focus
|
|
10
|
+
* to the control that opened it when it closes, by Escape or by a control
|
|
11
|
+
* inside it, exactly as the same Dialog does in the page, with the focus
|
|
12
|
+
* ring that close's input calls for, a text field that opened it keeping
|
|
13
|
+
* its ring with no extra blur; and that a Dialog in an iframe opened by a
|
|
14
|
+
* control in the page returns focus to that control.
|
|
15
|
+
* @position The half of Dialog's focus return jsdom cannot reach: which
|
|
16
|
+
* document holds focus, and the browser's own focus restore when a modal
|
|
17
|
+
* dialog closes. The Vitest case in `Dialog/Dialog.test.tsx` models the
|
|
18
|
+
* same rule with an iframe. Document Picture-in-Picture is Chromium's
|
|
19
|
+
* alone, so the window's cases skip in other engines; the page and the
|
|
20
|
+
* iframe run everywhere.
|
|
21
|
+
*
|
|
22
|
+
* SYNC: Fixture lives in
|
|
23
|
+
* /apps/storybook/stories/DialogSecondDocumentA11y.stories.tsx.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
expect,
|
|
28
|
+
test,
|
|
29
|
+
type Frame,
|
|
30
|
+
type Keyboard,
|
|
31
|
+
type Locator,
|
|
32
|
+
type Page,
|
|
33
|
+
} from '@playwright/test';
|
|
34
|
+
import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
|
|
35
|
+
import {
|
|
36
|
+
DEFAULT_STORYBOOK_DIR,
|
|
37
|
+
serveStorybook,
|
|
38
|
+
type StaticServer,
|
|
39
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
40
|
+
|
|
41
|
+
const STORY_ID = 'a11y-dialog-in-a-second-document--second-documents';
|
|
42
|
+
|
|
43
|
+
let storybook: StaticServer;
|
|
44
|
+
|
|
45
|
+
test.beforeAll(async () => {
|
|
46
|
+
storybook = await serveStorybook(
|
|
47
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.afterAll(async () => {
|
|
52
|
+
await storybook?.close();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
type Where = 'page' | 'iframe' | 'Picture-in-Picture window';
|
|
56
|
+
|
|
57
|
+
/** One document of the story: where its controls live and where keys go. */
|
|
58
|
+
interface Surface {
|
|
59
|
+
frame: Frame;
|
|
60
|
+
keyboard: Keyboard;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Load the story and return the document the test drives. */
|
|
64
|
+
async function mountStory(page: Page, where: Where): Promise<Surface> {
|
|
65
|
+
await page.goto(
|
|
66
|
+
`${storybook.origin}/iframe.html?id=${STORY_ID}&viewMode=story`,
|
|
67
|
+
{waitUntil: 'load'},
|
|
68
|
+
);
|
|
69
|
+
await holdMotionStill(page);
|
|
70
|
+
const popOut = page.getByRole('button', {name: 'Pop out'});
|
|
71
|
+
await popOut.waitFor();
|
|
72
|
+
|
|
73
|
+
if (where === 'page') {
|
|
74
|
+
return {frame: page.mainFrame(), keyboard: page.keyboard};
|
|
75
|
+
}
|
|
76
|
+
if (where === 'iframe') {
|
|
77
|
+
const element = await page
|
|
78
|
+
.locator('iframe[title="Second document"]')
|
|
79
|
+
.elementHandle();
|
|
80
|
+
const frame = await element?.contentFrame();
|
|
81
|
+
if (frame == null) {
|
|
82
|
+
throw new Error('the story renders no iframe');
|
|
83
|
+
}
|
|
84
|
+
await frame
|
|
85
|
+
.getByRole('button', {name: 'Open dialog', exact: true})
|
|
86
|
+
.waitFor();
|
|
87
|
+
// Key presses go to whichever frame holds focus; the clicks below put it
|
|
88
|
+
// in this one.
|
|
89
|
+
return {frame, keyboard: page.keyboard};
|
|
90
|
+
}
|
|
91
|
+
// Opening the window needs a user gesture; the click is one.
|
|
92
|
+
const [pictureInPicture] = await Promise.all([
|
|
93
|
+
page.context().waitForEvent('page'),
|
|
94
|
+
popOut.click(),
|
|
95
|
+
]);
|
|
96
|
+
await pictureInPicture
|
|
97
|
+
.getByRole('button', {
|
|
98
|
+
name: 'Open dialog',
|
|
99
|
+
exact: true,
|
|
100
|
+
})
|
|
101
|
+
.waitFor();
|
|
102
|
+
return {
|
|
103
|
+
frame: pictureInPicture.mainFrame(),
|
|
104
|
+
keyboard: pictureInPicture.keyboard,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Whether the element matches `:focus-visible`, i.e. would show a ring. */
|
|
109
|
+
async function showsRing(element: Locator): Promise<boolean> {
|
|
110
|
+
return element.evaluate(node => node.matches(':focus-visible'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Open the dialog from its control with the pointer; returns the control. */
|
|
114
|
+
async function open(frame: Frame): Promise<{opener: Locator; dialog: Locator}> {
|
|
115
|
+
const opener = frame.getByRole('button', {name: 'Open dialog', exact: true});
|
|
116
|
+
await opener.click();
|
|
117
|
+
const dialog = frame.getByRole('dialog', {name: 'Review changes'});
|
|
118
|
+
await expect(dialog).toBeVisible();
|
|
119
|
+
return {opener, dialog};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const where of [
|
|
123
|
+
'page',
|
|
124
|
+
'iframe',
|
|
125
|
+
'Picture-in-Picture window',
|
|
126
|
+
] as const satisfies ReadonlyArray<Where>) {
|
|
127
|
+
test.describe(`a Dialog in the ${where}`, () => {
|
|
128
|
+
test.skip(
|
|
129
|
+
({browserName}) =>
|
|
130
|
+
where === 'Picture-in-Picture window' && browserName !== 'chromium',
|
|
131
|
+
'Document Picture-in-Picture is a Chromium API',
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
test('closing with Escape returns focus to the control that opened it', async ({
|
|
135
|
+
page,
|
|
136
|
+
}) => {
|
|
137
|
+
const {frame, keyboard} = await mountStory(page, where);
|
|
138
|
+
const {opener, dialog} = await open(frame);
|
|
139
|
+
|
|
140
|
+
await keyboard.press('Escape');
|
|
141
|
+
await expect(dialog).toBeHidden();
|
|
142
|
+
await expect(opener).toBeFocused();
|
|
143
|
+
// A key closed it, so the ring shows (`architecture:interaction-modality`
|
|
144
|
+
// INV1), whatever the page's own last input was (a pointer press on Pop
|
|
145
|
+
// out, for the Picture-in-Picture window).
|
|
146
|
+
await expect.poll(async () => showsRing(opener)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('closing from a control inside returns focus to the control that opened it', async ({
|
|
150
|
+
page,
|
|
151
|
+
}) => {
|
|
152
|
+
const {frame} = await mountStory(page, where);
|
|
153
|
+
const {opener, dialog} = await open(frame);
|
|
154
|
+
|
|
155
|
+
await frame.getByRole('button', {name: 'Close dialog'}).click();
|
|
156
|
+
await expect(dialog).toBeHidden();
|
|
157
|
+
await expect(opener).toBeFocused();
|
|
158
|
+
// A pointer closed it, so no ring.
|
|
159
|
+
await expect.poll(async () => showsRing(opener)).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('a text field that opened it keeps focus and its ring after a pointer close', async ({
|
|
163
|
+
page,
|
|
164
|
+
}) => {
|
|
165
|
+
const {frame} = await mountStory(page, where);
|
|
166
|
+
const field = frame.getByRole('textbox', {name: 'Search'});
|
|
167
|
+
await field.click();
|
|
168
|
+
await field.press('Enter');
|
|
169
|
+
const dialog = frame.getByRole('dialog', {name: 'Review changes'});
|
|
170
|
+
await expect(dialog).toBeVisible();
|
|
171
|
+
// Count the field's blurs from here on: the open already moved focus
|
|
172
|
+
// away, so any blur now would be the return's own.
|
|
173
|
+
await field.evaluate(node => {
|
|
174
|
+
node.dataset.blurs = '0';
|
|
175
|
+
node.addEventListener('blur', () => {
|
|
176
|
+
node.dataset.blurs = String(Number(node.dataset.blurs) + 1);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await frame.getByRole('button', {name: 'Close dialog'}).click();
|
|
181
|
+
await expect(dialog).toBeHidden();
|
|
182
|
+
await expect(field).toBeFocused();
|
|
183
|
+
// A text field shows where typing goes whatever moved focus there.
|
|
184
|
+
await expect.poll(async () => showsRing(field)).toBe(true);
|
|
185
|
+
await expect(field).toHaveAttribute('data-blurs', '0');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
test.describe('a Dialog in the iframe that a control in the page opens', () => {
|
|
191
|
+
test('closing with Escape returns focus to that control', async ({page}) => {
|
|
192
|
+
const {frame, keyboard} = await mountStory(page, 'iframe');
|
|
193
|
+
const opener = page.getByRole('button', {name: 'Open dialog in the frame'});
|
|
194
|
+
await opener.click();
|
|
195
|
+
const dialog = frame.getByRole('dialog', {name: 'Opened from the page'});
|
|
196
|
+
await expect(dialog).toBeVisible();
|
|
197
|
+
|
|
198
|
+
await keyboard.press('Escape');
|
|
199
|
+
await expect(dialog).toBeHidden();
|
|
200
|
+
await expect(opener).toBeFocused();
|
|
201
|
+
});
|
|
202
|
+
});
|
package/src/Divider/Divider.tsx
CHANGED
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
typeScaleVars,
|
|
26
26
|
borderVars,
|
|
27
27
|
} from '../theme/tokens.stylex';
|
|
28
|
-
import {isRenderable
|
|
28
|
+
import {isRenderable} from '../utils/isRenderable';
|
|
29
|
+
import {mergeProps} from '../utils/mergeProps';
|
|
29
30
|
import {themeProps} from '../utils/themeProps';
|
|
30
31
|
import type {DividerVariantMap} from './index';
|
|
31
32
|
|