@astryxdesign/core 0.7.5 → 0.7.6-canary.0ef60dd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +5 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +7 -1
- package/dist/BottomSheet/BottomSheet.js +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +2 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +30 -3
- package/dist/Button/Button.js +4 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -5
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +37 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -4
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +101 -15
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +8 -3
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -12
- package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
- package/dist/DateTimeInput/NativeTimeSegment.js +5 -4
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -3
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +34 -9
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +5 -4
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +2 -1
- package/dist/Drawer/Drawer.d.ts +1 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +7 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +7 -3
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +20 -2
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +31 -3
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -16
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +2 -1
- package/dist/Field/FieldLabel.d.ts +1 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +4 -4
- package/dist/Field/InputClearButton.js +3 -3
- package/dist/Field/PanelSearchInput.js +2 -2
- package/dist/FieldStatus/FieldStatus.js +2 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +7 -1
- package/dist/Heading/Heading.js +1 -1
- package/dist/Icon/Icon.js +1 -1
- package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckIndicator.js +2 -1
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckboxIndicator.js +3 -1
- package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
- package/dist/Indicator/RadioIndicator.js +3 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +2 -1
- package/dist/Item/ItemSwipeLayer.js +1 -1
- package/dist/Layer/popoverOperations.d.ts +27 -0
- package/dist/Layer/popoverOperations.d.ts.map +1 -0
- package/dist/Layer/popoverOperations.js +159 -0
- package/dist/Layer/useLayer.d.ts +1 -1
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +93 -59
- package/dist/Layout/Layout.js +1 -1
- package/dist/Layout/LayoutContent.js +1 -1
- package/dist/Layout/LayoutFooter.js +1 -1
- package/dist/Layout/LayoutHeader.js +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +5 -0
- package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
- package/dist/List/List.js +1 -1
- package/dist/List/ListItem.js +3 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +7 -2
- package/dist/MultiSelector/CreateRow.a11y.chromium.spec.js +7 -6
- package/dist/MultiSelector/MultiSelector.d.ts +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +26 -10
- package/dist/NumberInput/NumberInput.d.ts +3 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +3 -3
- package/dist/NumberInput/numberParser.d.ts +3 -0
- package/dist/NumberInput/numberParser.d.ts.map +1 -1
- package/dist/NumberInput/numberParser.js +7 -44
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +16 -12
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +28 -2
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
- package/dist/Selector/useSelectorPresentation.d.ts +8 -1
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
- package/dist/Selector/useSelectorPresentation.js +20 -4
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +10 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +3 -3
- package/dist/Stack/Stack.js +1 -1
- package/dist/Stack/StackItem.js +1 -1
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +16 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
- package/dist/Text/Text.js +1 -1
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +23 -5
- package/dist/TextArea/TextArea.d.ts +1 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +13 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +3 -3
- package/dist/TimeInput/TimeInput.d.ts +6 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +21 -14
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +5 -4
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +2 -2
- package/dist/Timestamp/TimestampHoverCard.js +4 -4
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +60 -20
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +82 -51
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +13 -2
- package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +2 -1
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -0
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +1 -0
- package/dist/Typeahead/Typeahead.d.ts +1 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +9 -1
- package/dist/astryx.css +20 -4
- package/dist/gestures/GestureArena.d.ts +6 -0
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +39 -0
- package/dist/gestures/GestureHost.d.ts +352 -0
- package/dist/gestures/GestureHost.d.ts.map +1 -0
- package/dist/gestures/GestureHost.js +1773 -0
- package/dist/gestures/GestureRecognizer.d.ts +10 -0
- package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/GestureRecognizer.js +16 -0
- package/dist/gestures/LongPressGestureRecognizer.d.ts +1 -0
- package/dist/gestures/LongPressGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/LongPressGestureRecognizer.js +3 -0
- package/dist/gestures/PanGestureRecognizer.d.ts +1 -0
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PanGestureRecognizer.js +3 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts +31 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/PinchGestureRecognizer.js +128 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +71 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.js +223 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts +37 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/SwipeGestureRecognizer.js +148 -0
- package/dist/gestures/TapGestureRecognizer.d.ts +1 -0
- package/dist/gestures/TapGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/TapGestureRecognizer.js +3 -0
- package/dist/gestures/VelocityTracker.d.ts +6 -2
- package/dist/gestures/VelocityTracker.d.ts.map +1 -1
- package/dist/gestures/VelocityTracker.js +7 -3
- package/dist/gestures/haptics.d.ts +65 -0
- package/dist/gestures/haptics.d.ts.map +1 -0
- package/dist/gestures/haptics.js +182 -0
- package/dist/gestures/nativeScroll.d.ts +42 -0
- package/dist/gestures/nativeScroll.d.ts.map +1 -0
- package/dist/gestures/nativeScroll.js +228 -0
- package/dist/gestures/touchAction.d.ts +60 -0
- package/dist/gestures/touchAction.d.ts.map +1 -0
- package/dist/gestures/touchAction.js +157 -0
- package/dist/gestures/touchActionRule.d.ts +34 -0
- package/dist/gestures/touchActionRule.d.ts.map +1 -0
- package/dist/gestures/touchActionRule.js +140 -0
- package/dist/gestures/types.d.ts +18 -7
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +22 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/modalAnnounce.d.ts.map +1 -1
- package/dist/hooks/modalAnnounce.js +107 -61
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
- package/dist/hooks/useAdaptivePresentation.js +4 -1
- package/dist/hooks/useAnnounce.d.ts +101 -42
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +161 -75
- package/dist/hooks/useAnnounceRenderedText.d.ts +11 -3
- package/dist/hooks/useAnnounceRenderedText.d.ts.map +1 -1
- package/dist/hooks/useAnnounceRenderedText.js +5 -3
- package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -1
- package/dist/hooks/useBusyAnnouncement.js +6 -12
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +12 -1
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +5 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +5 -0
- package/dist/motion/animator.d.ts +7 -1
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +119 -27
- package/dist/theme/defineTheme.d.ts +19 -5
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +64 -4
- package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
- package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
- package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
- package/dist/theme/domainTokens/index.d.ts +5 -4
- package/dist/theme/domainTokens/index.d.ts.map +1 -1
- package/dist/theme/domainTokens/index.js +5 -9
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/motionKnobs.d.ts +56 -0
- package/dist/theme/motionKnobs.d.ts.map +1 -0
- package/dist/theme/motionKnobs.js +48 -0
- package/dist/theme/resolveThemeValues.d.ts +11 -4
- package/dist/theme/resolveThemeValues.d.ts.map +1 -1
- package/dist/theme/resolveThemeValues.js +40 -4
- package/dist/theme/themeAdaptations.d.ts +7 -3
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +36 -7
- package/dist/theme/themeMotion.d.ts +96 -0
- package/dist/theme/themeMotion.d.ts.map +1 -0
- package/dist/theme/themeMotion.js +177 -0
- package/dist/theme/tokenReferences.d.ts +31 -0
- package/dist/theme/tokenReferences.d.ts.map +1 -0
- package/dist/theme/tokenReferences.js +132 -0
- package/dist/theme/tokens.d.ts +1 -0
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +9 -129
- package/dist/theme/tokens.stylex.d.ts +12 -0
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +12 -0
- package/dist/utils/focusReturn.d.ts +17 -1
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +11 -4
- package/dist/utils/getNodeText.d.ts +8 -0
- package/dist/utils/getNodeText.d.ts.map +1 -0
- package/dist/utils/getNodeText.js +37 -0
- package/dist/utils/interactionModality.d.ts +11 -2
- package/dist/utils/interactionModality.d.ts.map +1 -1
- package/dist/utils/interactionModality.js +58 -5
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +17 -8
- package/dist/utils/plainDate.d.ts +2 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +46 -3
- package/dist/utils/presentation.d.ts +50 -0
- package/dist/utils/presentation.d.ts.map +1 -0
- package/dist/utils/presentation.js +125 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +20 -1
- package/dist/utils/primaryClick.d.ts +11 -0
- package/dist/utils/primaryClick.d.ts.map +1 -0
- package/dist/utils/primaryClick.js +25 -0
- package/dist/utils/timeFormat.d.ts +47 -0
- package/dist/utils/timeFormat.d.ts.map +1 -0
- package/dist/utils/timeFormat.js +240 -0
- package/dist/utils/touchPresentation.d.ts.map +1 -1
- package/dist/utils/touchPresentation.js +5 -3
- package/dist/utils/touchPresentation.stylex.d.ts +15 -0
- package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
- package/dist/utils/touchPresentation.stylex.js +33 -0
- package/dist/utils/unicodeDigits.d.ts +20 -0
- package/dist/utils/unicodeDigits.d.ts.map +1 -0
- package/dist/utils/unicodeDigits.js +70 -0
- package/locales/en.json +23 -3
- package/locales/pseudo.json +15 -0
- package/package.json +5 -4
- package/src/AlertDialog/AlertDialog.tsx +6 -5
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
- package/src/Avatar/Avatar.spec.md +22 -22
- package/src/Avatar/Avatar.tsx +18 -1
- package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
- package/src/Badge/Badge.doc.mjs +1 -1
- package/src/Badge/Badge.tsx +19 -2
- package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +260 -0
- package/src/BottomSheet/BottomSheet.test.tsx +46 -0
- package/src/BottomSheet/BottomSheet.tsx +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
- package/src/BottomSheet/useSheetGestures.test.ts +52 -0
- package/src/BottomSheet/useSheetGestures.ts +37 -6
- package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
- package/src/Button/Button.tsx +4 -4
- package/src/Calendar/Calendar.spec.md +87 -48
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
- package/src/CheckboxInput/CheckboxInput.tsx +6 -5
- package/src/ClickableCard/ClickableCard.test.tsx +96 -0
- package/src/ClickableCard/ClickableCard.tsx +48 -4
- package/src/Collapsible/Collapsible.spec.md +2 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
- package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +37 -0
- package/src/ContextMenu/ContextMenu.test.tsx +35 -0
- package/src/DateInput/DateInput.test.tsx +115 -3
- package/src/DateInput/DateInput.tsx +29 -14
- package/src/DateInput/DateInputTouch.test.tsx +76 -0
- package/src/DateInput/MonthScroller.tsx +106 -20
- package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
- package/src/DateInput/TouchDateField.tsx +14 -3
- package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
- package/src/DateRangeInput/DateRangeInput.tsx +16 -12
- package/src/DateTimeInput/DateTimeInput.doc.mjs +10 -8
- package/src/DateTimeInput/DateTimeInput.spec.md +75 -51
- package/src/DateTimeInput/DateTimeInput.test.tsx +81 -0
- package/src/DateTimeInput/DateTimeInput.tsx +46 -24
- package/src/DateTimeInput/NativeTimeSegment.tsx +4 -6
- package/src/DateTimeInput/TouchDateTimeField.tsx +3 -7
- package/src/Dialog/Dialog.test.tsx +216 -2
- package/src/Dialog/Dialog.tsx +42 -9
- package/src/Dialog/DialogHeader.tsx +5 -4
- package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
- package/src/Divider/Divider.tsx +2 -1
- package/src/Drawer/Drawer.spec.md +111 -80
- package/src/Drawer/Drawer.test.tsx +35 -0
- package/src/Drawer/Drawer.tsx +13 -2
- package/src/Drawer/DrawerHeader.spec.md +181 -0
- package/src/Drawer/__tests__/DrawerNonModalPageScroll.a11y.chromium.spec.ts +267 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
- package/src/DropdownMenu/DropdownMenu.tsx +10 -3
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
- package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
- package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +60 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +36 -7
- package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
- package/src/EmptyState/EmptyState.tsx +2 -1
- package/src/Field/FieldLabel.tsx +5 -4
- package/src/Field/InputClearButton.tsx +3 -3
- package/src/Field/PanelSearchInput.tsx +2 -2
- package/src/FieldStatus/FieldStatus.tsx +2 -2
- package/src/FileInput/FileInput.test.tsx +21 -0
- package/src/FileInput/FileInput.tsx +4 -1
- package/src/FormLayout/FormLayout.spec.md +7 -8
- package/src/Heading/Heading.tsx +1 -1
- package/src/Icon/Icon.tsx +1 -1
- package/src/Indicator/CheckIndicator.spec.md +47 -42
- package/src/Indicator/CheckIndicator.tsx +2 -1
- package/src/Indicator/CheckboxIndicator.spec.md +40 -37
- package/src/Indicator/CheckboxIndicator.tsx +3 -1
- package/src/Indicator/Indicator.spec.md +188 -0
- package/src/Indicator/RadioIndicator.tsx +3 -1
- package/src/Item/Item.tsx +2 -1
- package/src/Item/ItemSwipeLayer.tsx +1 -1
- package/src/Kbd/Kbd.spec.md +81 -38
- package/src/Layer/__tests__/LayerBrowserHide.a11y.chromium.spec.ts +126 -0
- package/src/Layer/popoverOperations.test.ts +205 -0
- package/src/Layer/popoverOperations.ts +172 -0
- package/src/Layer/useLayer.test.tsx +348 -1
- package/src/Layer/useLayer.tsx +118 -71
- package/src/Layout/Layout.spec.md +127 -131
- package/src/Layout/Layout.tsx +1 -1
- package/src/Layout/LayoutContent.spec.md +205 -0
- package/src/Layout/LayoutContent.tsx +1 -1
- package/src/Layout/LayoutFooter.spec.md +190 -0
- package/src/Layout/LayoutFooter.tsx +1 -1
- package/src/Layout/LayoutHeader.spec.md +193 -0
- package/src/Layout/LayoutHeader.tsx +1 -1
- package/src/Layout/LayoutPanel.spec.md +194 -0
- package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
- package/src/Lightbox/Lightbox.test.tsx +18 -0
- package/src/Lightbox/Lightbox.tsx +5 -0
- package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
- package/src/List/List.tsx +1 -1
- package/src/List/ListItem.spec.md +205 -0
- package/src/List/ListItem.tsx +3 -3
- package/src/MetadataList/MetadataList.spec.md +187 -0
- package/src/MetadataList/MetadataListItem.spec.md +165 -0
- package/src/MobileNav/MobileNav.test.tsx +15 -0
- package/src/MobileNav/MobileNav.tsx +9 -1
- package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
- package/src/MoreMenu/MoreMenu.test.tsx +11 -0
- package/src/MultiSelector/CreateRow.a11y.chromium.spec.ts +7 -8
- package/src/MultiSelector/MultiSelector.spec.md +7 -4
- package/src/MultiSelector/MultiSelector.test.tsx +270 -1
- package/src/MultiSelector/MultiSelector.tsx +25 -10
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
- package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
- package/src/NumberInput/NumberInput.doc.mjs +13 -7
- package/src/NumberInput/NumberInput.spec.md +59 -37
- package/src/NumberInput/NumberInput.tsx +13 -3
- package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
- package/src/NumberInput/numberParser.ts +7 -45
- package/src/ProgressBar/ProgressBar.test.tsx +108 -32
- package/src/ProgressBar/ProgressBar.tsx +58 -10
- package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
- package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
- package/src/Resizable/useResizable.test.ts +43 -0
- package/src/Resizable/useResizable.ts +32 -9
- package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
- package/src/Selector/Selector.test.tsx +90 -1
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
- package/src/Selector/useSelectorPresentation.ts +31 -3
- package/src/SideNav/SideNav.test.tsx +27 -0
- package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +176 -51
- package/src/Skeleton/Skeleton.tsx +3 -1
- package/src/Slider/Slider.test.tsx +31 -0
- package/src/Slider/Slider.tsx +8 -3
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
- package/src/Spinner/Spinner.tsx +5 -3
- package/src/Stack/Stack.tsx +1 -1
- package/src/Stack/StackItem.tsx +1 -1
- package/src/TabList/TabList.test.tsx +58 -4
- package/src/TabList/TabList.tsx +16 -1
- package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
- package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
- package/src/Text/Text.tsx +1 -1
- package/src/Text/useTruncation.test.tsx +128 -1
- package/src/Text/useTruncation.ts +23 -5
- package/src/TextArea/TextArea.spec.md +10 -13
- package/src/TextArea/TextArea.test.tsx +60 -0
- package/src/TextArea/TextArea.tsx +15 -1
- package/src/TextInput/TextInput.test.tsx +21 -0
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/TimeInput/NativeTimeInput.test.tsx +17 -0
- package/src/TimeInput/Presentation.test.tsx +21 -0
- package/src/TimeInput/TimeInput.doc.mjs +8 -8
- package/src/TimeInput/TimeInput.spec.md +45 -48
- package/src/TimeInput/TimeInput.test.tsx +76 -0
- package/src/TimeInput/TimeInput.tsx +36 -18
- package/src/TimeInput/TouchTimeField.tsx +4 -6
- package/src/Timestamp/Timestamp.spec.md +6 -12
- package/src/Timestamp/Timestamp.test.tsx +30 -5
- package/src/Timestamp/Timestamp.tsx +6 -2
- package/src/Timestamp/TimestampHoverCard.tsx +4 -4
- package/src/Toast/Toast.spec.md +14 -20
- package/src/Toast/Toast.tsx +104 -36
- package/src/Toast/ToastViewport.test.tsx +719 -1
- package/src/Toast/ToastViewport.tsx +100 -50
- package/src/Tokenizer/Tokenizer.spec.md +5 -3
- package/src/Tokenizer/Tokenizer.test.tsx +68 -0
- package/src/Tokenizer/Tokenizer.tsx +14 -2
- package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
- package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
- package/src/Toolbar/Toolbar.spec.md +5 -6
- package/src/Toolbar/Toolbar.test.tsx +30 -0
- package/src/Toolbar/Toolbar.tsx +2 -1
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
- package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
- package/src/TopNav/TopNav.test.tsx +27 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +21 -0
- package/src/TopNav/TopNavMegaMenu.tsx +4 -1
- package/src/TopNav/TopNavMenu.test.tsx +17 -0
- package/src/TopNav/TopNavMenu.tsx +1 -0
- package/src/Typeahead/Typeahead.test.tsx +40 -0
- package/src/Typeahead/Typeahead.tsx +12 -1
- package/src/__tests__/pressState.ts +8 -4
- package/src/gestures/GestureArena.ts +35 -0
- package/src/gestures/GestureHost.ts +2065 -0
- package/src/gestures/GestureRecognizer.ts +18 -0
- package/src/gestures/LongPressGestureRecognizer.ts +4 -0
- package/src/gestures/PanGestureRecognizer.ts +4 -0
- package/src/gestures/PinchGestureRecognizer.ts +148 -0
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +256 -0
- package/src/gestures/SwipeGestureRecognizer.ts +197 -0
- package/src/gestures/TapGestureRecognizer.ts +4 -0
- package/src/gestures/VelocityTracker.ts +10 -3
- package/src/gestures/__tests__/GestureHost.test.ts +2087 -0
- package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
- package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
- package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
- package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
- package/src/gestures/__tests__/arena.test.ts +5 -0
- package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +193 -0
- package/src/gestures/__tests__/browser/build-fixture.mjs +46 -0
- package/src/gestures/__tests__/browser/fixture.ts +307 -0
- package/src/gestures/__tests__/browser/gestures.interaction.browser.spec.ts +1137 -0
- package/src/gestures/__tests__/browserNegotiation.test.ts +293 -0
- package/src/gestures/__tests__/haptics.test.ts +187 -0
- package/src/gestures/__tests__/harness.ts +21 -2
- package/src/gestures/__tests__/recognizerKind.test.ts +78 -0
- package/src/gestures/__tests__/touchActionRule.test.ts +54 -0
- package/src/gestures/haptics.ts +203 -0
- package/src/gestures/nativeScroll.ts +277 -0
- package/src/gestures/touchAction.ts +191 -0
- package/src/gestures/touchActionRule.ts +157 -0
- package/src/gestures/types.ts +30 -9
- package/src/hooks/index.ts +5 -1
- package/src/hooks/modalAnnounce.ts +129 -72
- package/src/hooks/useAdaptivePresentation.ts +4 -1
- package/src/hooks/useAnnounce.doc.mjs +35 -3
- package/src/hooks/useAnnounce.replace.test.tsx +516 -0
- package/src/hooks/useAnnounce.ts +209 -102
- package/src/hooks/useAnnounceRenderedText.adopters.test.tsx +10 -1
- package/src/hooks/useAnnounceRenderedText.ts +17 -3
- package/src/hooks/useBusyAnnouncement.ts +7 -10
- package/src/hooks/useMenuHover.ts +15 -1
- package/src/i18n/generated-locales/en.generated.ts +6 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +6 -0
- package/src/motion/README.md +11 -1
- package/src/motion/animator.test.ts +151 -3
- package/src/motion/animator.ts +149 -33
- package/src/theme/Theme.doc.mjs +1 -1
- package/src/theme/defineTheme.ts +117 -8
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
- package/src/theme/domainTokens/index.ts +6 -10
- package/src/theme/expandMotionScale.test.ts +1 -1
- package/src/theme/index.ts +5 -0
- package/src/theme/motionKnobs.ts +82 -0
- package/src/theme/resolveThemeValues.ts +68 -6
- package/src/theme/themeAdaptations.ts +52 -12
- package/src/theme/themeMotion.test.ts +620 -0
- package/src/theme/themeMotion.ts +221 -0
- package/src/theme/tokenReferences.ts +149 -0
- package/src/theme/tokens.stylex.ts +10 -0
- package/src/theme/tokens.ts +15 -158
- package/src/utils/focusReturn.ts +17 -6
- package/src/utils/getNodeText.test.ts +24 -0
- package/src/utils/getNodeText.ts +37 -0
- package/src/utils/interactionModality.test.ts +45 -1
- package/src/utils/interactionModality.ts +64 -5
- package/src/utils/interactionOverlay.stylex.ts +12 -3
- package/src/utils/plainDate.test.ts +90 -0
- package/src/utils/plainDate.ts +58 -3
- package/src/utils/presentation.test.ts +380 -0
- package/src/utils/presentation.ts +180 -0
- package/src/utils/pressFeedback.test.ts +138 -0
- package/src/utils/pressFeedback.ts +34 -11
- package/src/utils/pressPaintPriority.test.ts +80 -0
- package/src/utils/primaryClick.test.ts +38 -0
- package/src/utils/primaryClick.ts +28 -0
- package/src/utils/timeFormat.test.ts +204 -0
- package/src/utils/timeFormat.ts +314 -0
- package/src/utils/touchPresentation.stylex.ts +43 -0
- package/src/utils/touchPresentation.test.ts +140 -1
- package/src/utils/touchPresentation.ts +5 -3
- package/src/utils/unicodeDigits.test.ts +44 -0
- package/src/utils/unicodeDigits.ts +71 -0
|
@@ -122,17 +122,17 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
122
122
|
|
|
123
123
|
## Behavioral and layout contract
|
|
124
124
|
|
|
125
|
-
| ID | Invariant
|
|
126
|
-
| --- |
|
|
127
|
-
| FR1 | The root is a relative, non-shrinking inline-flex box of the resolved size with the shape's radius; it carries the `avatar` target, which reflects `size` and `shape`. A clipped content area fills it with the same radius and suppresses text selection.
|
|
128
|
-
| FR2 | The content area shows `src` while it has not failed, then `fallbackSrc` while it has not failed, then initials when the name yields any, then a default person icon at 60% of the box. Each image renders with empty `alt` and `object-fit: cover`. A failure is tracked per source, so a changed `src` or `fallbackSrc` is tried again.
|
|
129
|
-
| FR3 | Initials are the first letter, digit, or emoji of the first and last words of the trimmed `name`, uppercased; leading punctuation in a word is skipped, words with none are dropped, one qualifying word yields one initial, and grapheme clusters are never split. A whitespace-only name counts as absent. Initials and the default icon paint on the `avatar-fallback` target (reflecting `size`) with `--color-neutral`, secondary text, body family, medium weight, and initials at 40% of the box.
|
|
130
|
-
| FR4 | A truthy `status` renders in an absolutely positioned slot outside the clipped content area. On a circle it centers on the circle's edge at the bottom inline-end 45° point; on other shapes it sits at the bottom inline-end corner, offset outward by a quarter of its size. Placement mirrors in right-to-left documents.
|
|
131
|
-
| FR5 | The resolved name is `alt`, else `name` (blank strings count as absent), composed with the status label as the translated "{name}, {status}" when both exist; a status label alone also names the avatar. The status label comes from a string `label` on the passed element, read on every render, and from a label an AvatarStatusDot reports at any wrapper depth, which wins and updates the root before paint without a second render.
|
|
132
|
-
| FR6 | `href` renders the shared link component (or `as`) with `target` and `rel`; otherwise `onClick` renders a `button type="button"`; otherwise the root is a `div`. Link and button roots carry the `data-avatar-item` marker and the shared focus-visible outline; `onClick` attaches to the link or button. In development, an interactive avatar whose only name is a status label, or that has no name and no caller `aria-label`/`aria-labelledby`, logs a warning.
|
|
133
|
-
| FR7 | The tooltip shows the trimmed custom string, else the meaningful `name`, placed above, on hover and keyboard focus; `tooltip={false}` or a blank result shows none. A static avatar with a tooltip and outside a group gets `tabIndex={0}` so keyboard users can reveal it. A custom string is added to `aria-describedby` after any caller value; the default name tooltip is not, because it repeats the name. Implementation gap: a grouped link or button avatar that is not the group's tab stop stops opening its tooltip on focus after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount (Current-state impact).
|
|
134
|
-
| FR8 | Inside an AvatarGroup in the same layer (`spec:AST-038` FR7, FR8), the group's size and shape win over the avatar's own props; the root gains a 2px surface-colored ring outside its box and the group's negative inline-start overlap; a static avatar adds no tooltip tab stop, and link and button roots keep the `data-avatar-item` marker so they join the group's roving set. An avatar in a hover card, popover, or other layer opened from a group keeps its own size and shape, gets no ring or overlap, and keeps its tooltip tab stop; a link or button avatar there still carries the marker, so the group's roving focus, which selects marked elements in its DOM subtree, still includes it.
|
|
135
|
-
| FR9 | `ref` reaches the root of every variant. Supported DOM, ARIA, data, and event props reach the root after the derived ARIA, so a caller `aria-label`, `role`, or `aria-hidden` wins (`architecture:public-component-api` INV5); `data-testid` reaches the root; `xstyle`, `className`, and `style` merge in that order after component styles. Implementation gap: on the static root the component sets `tabIndex` after caller props, so a caller `tabIndex` is replaced (Current-state impact).
|
|
125
|
+
| ID | Invariant | Basis |
|
|
126
|
+
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
|
127
|
+
| FR1 | The root is a relative, non-shrinking inline-flex box of the resolved size with the shape's radius; it carries the `avatar` target, which reflects `size` and `shape`. A clipped content area fills it with the same radius and suppresses text selection. | Current source and tests |
|
|
128
|
+
| FR2 | The content area shows `src` while it has not failed, then `fallbackSrc` while it has not failed, then initials when the name yields any, then a default person icon at 60% of the box. Each image renders with empty `alt` and `object-fit: cover`. A failure is tracked per source, so a changed `src` or `fallbackSrc` is tried again. | Current source and tests |
|
|
129
|
+
| FR3 | Initials are the first letter, digit, or emoji of the first and last words of the trimmed `name`, uppercased; leading punctuation in a word is skipped, words with none are dropped, one qualifying word yields one initial, and grapheme clusters are never split. A whitespace-only name counts as absent. Initials and the default icon paint on the `avatar-fallback` target (reflecting `size`) with `--color-neutral`, secondary text, body family, medium weight, and initials at 40% of the box. In forced colors, a lone Avatar's initials and default icon take the surrounding color (`currentColor`): the forced foreground by default, and the system color of a control that paints one around it (GrayText, HighlightText); inside an AvatarGroup they keep the forced foreground on the group's ring. | Current source and tests |
|
|
130
|
+
| FR4 | A truthy `status` renders in an absolutely positioned slot outside the clipped content area. On a circle it centers on the circle's edge at the bottom inline-end 45° point; on other shapes it sits at the bottom inline-end corner, offset outward by a quarter of its size. Placement mirrors in right-to-left documents. | Current source and tests |
|
|
131
|
+
| FR5 | The resolved name is `alt`, else `name` (blank strings count as absent), composed with the status label as the translated "{name}, {status}" when both exist; a status label alone also names the avatar. The status label comes from a string `label` on the passed element, read on every render, and from a label an AvatarStatusDot reports at any wrapper depth, which wins and updates the root before paint without a second render. | Current source and tests |
|
|
132
|
+
| FR6 | `href` renders the shared link component (or `as`) with `target` and `rel`; otherwise `onClick` renders a `button type="button"`; otherwise the root is a `div`. Link and button roots carry the `data-avatar-item` marker and the shared focus-visible outline; `onClick` attaches to the link or button. In development, an interactive avatar whose only name is a status label, or that has no name and no caller `aria-label`/`aria-labelledby`, logs a warning. | Current source and tests |
|
|
133
|
+
| FR7 | The tooltip shows the trimmed custom string, else the meaningful `name`, placed above, on hover and keyboard focus; `tooltip={false}` or a blank result shows none. A static avatar with a tooltip and outside a group gets `tabIndex={0}` so keyboard users can reveal it. A custom string is added to `aria-describedby` after any caller value; the default name tooltip is not, because it repeats the name. Implementation gap: a grouped link or button avatar that is not the group's tab stop stops opening its tooltip on focus after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount (Current-state impact). | Current source and tests |
|
|
134
|
+
| FR8 | Inside an AvatarGroup in the same layer (`spec:AST-038` FR7, FR8), the group's size and shape win over the avatar's own props; the root gains a 2px surface-colored ring outside its box and the group's negative inline-start overlap; a static avatar adds no tooltip tab stop, and link and button roots keep the `data-avatar-item` marker so they join the group's roving set. An avatar in a hover card, popover, or other layer opened from a group keeps its own size and shape, gets no ring or overlap, and keeps its tooltip tab stop; a link or button avatar there still carries the marker, so the group's roving focus, which selects marked elements in its DOM subtree, still includes it. | Current source and tests |
|
|
135
|
+
| FR9 | `ref` reaches the root of every variant. Supported DOM, ARIA, data, and event props reach the root after the derived ARIA, so a caller `aria-label`, `role`, or `aria-hidden` wins (`architecture:public-component-api` INV5); `data-testid` reaches the root; `xstyle`, `className`, and `style` merge in that order after component styles. Implementation gap: on the static root the component sets `tabIndex` after caller props, so a caller `tabIndex` is replaced (Current-state impact). | Current source and tests |
|
|
136
136
|
|
|
137
137
|
### Allowed variation
|
|
138
138
|
|
|
@@ -227,17 +227,17 @@ no root part, so no anatomy-theming block is declared.
|
|
|
227
227
|
|
|
228
228
|
## Verification map
|
|
229
229
|
|
|
230
|
-
| Contract | Verification | Representative states
|
|
231
|
-
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
232
|
-
| FR1–FR3 | `Avatar.test.tsx` fallback, initials, retry, and target tests
|
|
233
|
-
| FR4 | `Avatar.test.tsx` shape and status tests; `Avatar.stories.tsx` status fixtures | Every shape with a status; RTL
|
|
234
|
-
| FR5, AR1, AR2, PR1 | `Avatar.test.tsx` status-name and wrapper suites | Direct, wrapped, changing, removed labels
|
|
235
|
-
| FR6, AR3 | `Avatar.test.tsx` interactivity suite | Link, button, static, dev warnings
|
|
236
|
-
| FR7, AR4, AR5 | `Avatar.test.tsx` name-tooltip suite | Default, custom, disabled, blank
|
|
237
|
-
| FR8 | `Avatar.test.tsx` group shape tests; `AvatarGroup.test.tsx` size-cascade, overlap, and roving suites; `layerScopedContext.test.tsx` AvatarGroup layer tests | Inside a group; in a layer opened from a group; outside a group
|
|
238
|
-
| FR9, AR6 | `Avatar.test.tsx` consumer-ARIA override suite | Static and interactive roots
|
|
239
|
-
| AR1–AR3, AR5 | Accessibility-tree evidence (`spec:AST-009` FR3) | Named, decorative, status-composed, interactive
|
|
240
|
-
| AR7 | `Avatar.stories.tsx` interactive and size fixtures in a browser | Interactive avatars at every size
|
|
230
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
231
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
|
|
232
|
+
| FR1–FR3 | `Avatar.test.tsx` fallback, initials, retry, and target tests; `AvatarForcedColors.a11y.chromium.spec.ts` in real Chromium with forced colors emulated | Photo, failure, initials, punctuation, emoji, blank name; `lg` box; forced colors alone, inside GrayText and HighlightText, and grouped | A wrong initials-or-icon fallback, split grapheme, lost retry, missing target, wrong `lg` box size, or forced-colors initials that miss the surrounding system color (lone) or leave the forced foreground (grouped) fails. Gap: no test sets `src` and `fallbackSrc` together, so the `src`-first order is unchecked, and sizes other than `lg` and numeric sizes are visible only in `Avatar.stories.tsx`. | `audit:Avatar/behavior` |
|
|
233
|
+
| FR4 | `Avatar.test.tsx` shape and status tests; `Avatar.stories.tsx` status fixtures | Every shape with a status; RTL | A missing status slot fails. Gap: the tests assert presence only, so the slot's position, mirroring, and offset are visible only in the rendered fixtures. | `audit:Avatar/layout` |
|
|
234
|
+
| FR5, AR1, AR2, PR1 | `Avatar.test.tsx` status-name and wrapper suites | Direct, wrapped, changing, removed labels | A lost composed name, extra render, or stale label fails. | `audit:Avatar/a11y` |
|
|
235
|
+
| FR6, AR3 | `Avatar.test.tsx` interactivity suite | Link, button, static, dev warnings | A wrong root, a missing marker, or a missing warning fails. Gap: no test checks the focus-visible outline. | `audit:Avatar/behavior` |
|
|
236
|
+
| FR7, AR4, AR5 | `Avatar.test.tsx` name-tooltip suite | Default, custom, disabled, blank | A missing tab stop, a duplicated description, or a lost ref fails. Gap: no test covers a grouped avatar's tooltip after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount. | `audit:Avatar/a11y` |
|
|
237
|
+
| FR8 | `Avatar.test.tsx` group shape tests; `AvatarGroup.test.tsx` size-cascade, overlap, and roving suites; `layerScopedContext.test.tsx` AvatarGroup layer tests | Inside a group; in a layer opened from a group; outside a group | An avatar size or shape prop winning inside a group, a lost overlap, an interactive avatar missing from the roving set, or a static avatar in a layer losing its tooltip tab stop fails. Gap: no test asserts the ring, or size and shape inside a layer. | `audit:Avatar/behavior` |
|
|
238
|
+
| FR9, AR6 | `Avatar.test.tsx` consumer-ARIA override suite | Static and interactive roots | A derived value replacing a caller `aria-label`, `role`, or `aria-hidden` fails. Gap: no test covers a caller `tabIndex`. | `audit:Avatar/a11y` |
|
|
239
|
+
| AR1–AR3, AR5 | Accessibility-tree evidence (`spec:AST-009` FR3) | Named, decorative, status-composed, interactive | Gap: no accessibility-tree check proves the computed roles, names, or description; the unit tests query the DOM. | `audit:Avatar/a11y` |
|
|
240
|
+
| AR7 | `Avatar.stories.tsx` interactive and size fixtures in a browser | Interactive avatars at every size | Gap: no automated target-size check; the undersized targets are the recorded gap. | `audit:Avatar/a11y` |
|
|
241
241
|
|
|
242
242
|
## Decision log
|
|
243
243
|
|
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -193,6 +193,19 @@ const styles = stylex.create({
|
|
|
193
193
|
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
194
194
|
textTransform: 'uppercase',
|
|
195
195
|
},
|
|
196
|
+
// After `fallback` on a lone Avatar: in forced colors its initials and
|
|
197
|
+
// default icon take the color around them, which the browser forces to
|
|
198
|
+
// CanvasText, so a control that paints itself disabled (GrayText) or
|
|
199
|
+
// selected (HighlightText) carries them with it; an
|
|
200
|
+
// authored color would be forced to CanvasText whatever the control shows.
|
|
201
|
+
// A grouped face keeps it: its ring's opaque fill is forced to Canvas, so
|
|
202
|
+
// only CanvasText is sure to read on it.
|
|
203
|
+
loneFallback: {
|
|
204
|
+
color: {
|
|
205
|
+
default: colorVars['--color-text-secondary'],
|
|
206
|
+
'@media (forced-colors: active)': 'currentColor',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
196
209
|
status: {
|
|
197
210
|
position: 'absolute',
|
|
198
211
|
},
|
|
@@ -784,6 +797,7 @@ export function Avatar({
|
|
|
784
797
|
themeProps('avatar-fallback', {size: resolvedSize}),
|
|
785
798
|
stylex.props(
|
|
786
799
|
styles.fallback,
|
|
800
|
+
avatarGroup == null && styles.loneFallback,
|
|
787
801
|
dynamicStyles.fontSize(numericSize),
|
|
788
802
|
),
|
|
789
803
|
)}>
|
|
@@ -794,7 +808,10 @@ export function Avatar({
|
|
|
794
808
|
<div
|
|
795
809
|
{...mergeProps(
|
|
796
810
|
themeProps('avatar-fallback', {size: resolvedSize}),
|
|
797
|
-
stylex.props(
|
|
811
|
+
stylex.props(
|
|
812
|
+
styles.fallback,
|
|
813
|
+
avatarGroup == null && styles.loneFallback,
|
|
814
|
+
),
|
|
798
815
|
)}>
|
|
799
816
|
<DefaultIcon size={numericSize} />
|
|
800
817
|
</div>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// @pr-a11y scope ^packages/core/src/Avatar/ ^apps/storybook/stories/Avatar
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file AvatarForcedColors.a11y.chromium.spec.ts
|
|
6
|
+
* @input The built Avatar "Initials Fallback" story in real Chromium, with
|
|
7
|
+
* forced colors emulated and without
|
|
8
|
+
* @output Browser evidence that, in forced colors, a lone Avatar's initials
|
|
9
|
+
* take the color around them: CanvasText by default, GrayText inside a control
|
|
10
|
+
* that paints itself disabled, HighlightText inside a
|
|
11
|
+
* selected one, while grouped faces keep CanvasText on the group's ring;
|
|
12
|
+
* outside forced colors they keep the theme's color
|
|
13
|
+
* @position Regression proof for Avatar's fallback color (Chromium only:
|
|
14
|
+
* Playwright's WebKit matches the forced-colors query but forces nothing)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {expect, test, type Page} from '@playwright/test';
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_STORYBOOK_DIR,
|
|
20
|
+
serveStorybook,
|
|
21
|
+
type StaticServer,
|
|
22
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
23
|
+
|
|
24
|
+
let storybook: StaticServer;
|
|
25
|
+
test.beforeAll(async () => {
|
|
26
|
+
storybook = await serveStorybook(
|
|
27
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
test.afterAll(async () => {
|
|
31
|
+
await storybook?.close();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The initials' color, with `around` painted on `host`: the Avatar's own box
|
|
36
|
+
* for a lone Avatar, the group's for grouped ones.
|
|
37
|
+
*/
|
|
38
|
+
async function initialsColor(
|
|
39
|
+
page: Page,
|
|
40
|
+
forcedColors: 'active' | 'none',
|
|
41
|
+
around: string | null,
|
|
42
|
+
{
|
|
43
|
+
story = 'core-avatar--initials-fallback',
|
|
44
|
+
host = '.astryx-avatar',
|
|
45
|
+
}: {story?: string; host?: string} = {},
|
|
46
|
+
): Promise<{initials: string; reference: string}> {
|
|
47
|
+
await page.emulateMedia({forcedColors});
|
|
48
|
+
await page.goto(`${storybook.origin}/iframe.html?id=${story}&viewMode=story`);
|
|
49
|
+
await page.locator('.astryx-avatar-fallback').first().waitFor();
|
|
50
|
+
if (around != null) {
|
|
51
|
+
await page.addStyleTag({content: `${host} { ${around} }`});
|
|
52
|
+
}
|
|
53
|
+
return page.evaluate(() => {
|
|
54
|
+
const initials = document.querySelector('.astryx-avatar-fallback');
|
|
55
|
+
// A probe painted with the system color the initials should show.
|
|
56
|
+
const probe = document.createElement('span');
|
|
57
|
+
document.body.append(probe);
|
|
58
|
+
const read = (color: string) => {
|
|
59
|
+
probe.style.color = color;
|
|
60
|
+
return getComputedStyle(probe).color;
|
|
61
|
+
};
|
|
62
|
+
const result = {
|
|
63
|
+
initials: initials == null ? '' : getComputedStyle(initials).color,
|
|
64
|
+
reference: JSON.stringify({
|
|
65
|
+
CanvasText: read('CanvasText'),
|
|
66
|
+
GrayText: read('GrayText'),
|
|
67
|
+
HighlightText: read('HighlightText'),
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
probe.remove();
|
|
71
|
+
return result;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
test('in forced colors the initials are CanvasText by default', async ({
|
|
76
|
+
page,
|
|
77
|
+
}) => {
|
|
78
|
+
const {initials, reference} = await initialsColor(page, 'active', null);
|
|
79
|
+
expect(initials).toBe(JSON.parse(reference).CanvasText);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('in forced colors the initials take GrayText inside a control that paints itself disabled', async ({
|
|
83
|
+
page,
|
|
84
|
+
}) => {
|
|
85
|
+
const {initials, reference} = await initialsColor(
|
|
86
|
+
page,
|
|
87
|
+
'active',
|
|
88
|
+
'color: GrayText;',
|
|
89
|
+
);
|
|
90
|
+
expect(initials).toBe(JSON.parse(reference).GrayText);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('in forced colors the initials take HighlightText inside a selected control', async ({
|
|
94
|
+
page,
|
|
95
|
+
}) => {
|
|
96
|
+
const {initials, reference} = await initialsColor(
|
|
97
|
+
page,
|
|
98
|
+
'active',
|
|
99
|
+
'forced-color-adjust: none; background: Highlight; color: HighlightText;',
|
|
100
|
+
);
|
|
101
|
+
expect(initials).toBe(JSON.parse(reference).HighlightText);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("outside forced colors the initials keep the theme's color, whatever is around them", async ({
|
|
105
|
+
page,
|
|
106
|
+
}) => {
|
|
107
|
+
const plain = await initialsColor(page, 'none', null);
|
|
108
|
+
const around = await initialsColor(page, 'none', 'color: rgb(200, 0, 0);');
|
|
109
|
+
expect(around.initials).toBe(plain.initials);
|
|
110
|
+
expect(plain.initials).not.toBe('rgb(200, 0, 0)');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("in forced colors grouped initials keep CanvasText on the group's ring", async ({
|
|
114
|
+
page,
|
|
115
|
+
}) => {
|
|
116
|
+
const {initials, reference} = await initialsColor(
|
|
117
|
+
page,
|
|
118
|
+
'active',
|
|
119
|
+
'color: GrayText;',
|
|
120
|
+
{
|
|
121
|
+
story: 'core-avatargroup--initials-fallback',
|
|
122
|
+
host: '.astryx-avatar-group',
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
expect(initials).toBe(JSON.parse(reference).CanvasText);
|
|
126
|
+
});
|
package/src/Badge/Badge.doc.mjs
CHANGED
|
@@ -20,7 +20,7 @@ export const docs = {
|
|
|
20
20
|
type: 'ReactNode',
|
|
21
21
|
required: true,
|
|
22
22
|
description:
|
|
23
|
-
'Badge text content. A badge is one line, so a label wider than the space available is cut with an ellipsis rather than escaping its container; a string or number label is also set as the badge\'s title so the full text stays reachable on hover.',
|
|
23
|
+
'Badge text content. A badge is one line and at least as tall as that line of its label, so it grows with its text (a larger text size); a label wider than the space available is cut with an ellipsis rather than escaping its container; a string or number label is also set as the badge\'s title so the full text stays reachable on hover.',
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
name: 'icon',
|
package/src/Badge/Badge.tsx
CHANGED
|
@@ -46,7 +46,7 @@ const styles = stylex.create({
|
|
|
46
46
|
lineHeight: typeScaleVars['--text-supporting-leading'],
|
|
47
47
|
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
48
48
|
whiteSpace: 'nowrap',
|
|
49
|
-
// A badge is one line by construction —
|
|
49
|
+
// A badge is one line by construction — one line tall, `nowrap` — so a
|
|
50
50
|
// label wider than the space available has to go somewhere. Without these
|
|
51
51
|
// it went *outside* its container: `nowrap` with nothing to clip it
|
|
52
52
|
// neither wraps nor truncates, it just escapes, and lands on whatever sits
|
|
@@ -56,6 +56,16 @@ const styles = stylex.create({
|
|
|
56
56
|
maxWidth: '100%',
|
|
57
57
|
minWidth: 0,
|
|
58
58
|
},
|
|
59
|
+
// Never shorter than one line of its label: when the text grows (a larger
|
|
60
|
+
// text size), the pill grows with the label instead of the label spilling
|
|
61
|
+
// out of it. A minimum rather than a new height, so
|
|
62
|
+
// the theme's height (`--spacing-5`, or a theme's own, such as Butter's
|
|
63
|
+
// 30px) holds at the default size, where the line fits. Only with a label:
|
|
64
|
+
// a color-only badge has no text to hold and keeps the component:Badge FR1
|
|
65
|
+
// geometry at every text size.
|
|
66
|
+
grows: {
|
|
67
|
+
minHeight: '1lh',
|
|
68
|
+
},
|
|
59
69
|
// The ellipsis goes on the label rather than the badge itself, because
|
|
60
70
|
// `text-overflow` needs a block container and taking the root off
|
|
61
71
|
// `inline-flex` to get one would cost the icon its centring. The label is a
|
|
@@ -238,13 +248,20 @@ export function Badge({
|
|
|
238
248
|
: typeof label === 'string' && label.length > 0
|
|
239
249
|
? label
|
|
240
250
|
: undefined;
|
|
251
|
+
// Whether the label renders anything, which is when the badge grows with it.
|
|
252
|
+
const hasLabel = label !== '' && label != null && typeof label !== 'boolean';
|
|
241
253
|
|
|
242
254
|
return (
|
|
243
255
|
<span
|
|
244
256
|
ref={ref}
|
|
245
257
|
{...mergeProps(
|
|
246
258
|
themeProps('badge', {variant}),
|
|
247
|
-
stylex.props(
|
|
259
|
+
stylex.props(
|
|
260
|
+
styles.base,
|
|
261
|
+
hasLabel && styles.grows,
|
|
262
|
+
variants[variant],
|
|
263
|
+
xstyle,
|
|
264
|
+
),
|
|
248
265
|
className,
|
|
249
266
|
style,
|
|
250
267
|
)}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file BadgeLargerText.a11y.chromium.spec.ts
|
|
5
|
+
* @input The `Core/Badge` Larger Text story in a built Storybook, in Chromium
|
|
6
|
+
* at an iPhone's page size (402 x 714), under the neutral theme and under
|
|
7
|
+
* Butter (whose badges set their own height).
|
|
8
|
+
* @output Real-layout proof that a badge with a label is never shorter than one
|
|
9
|
+
* line of it, and that nothing else about its size changes: at the default
|
|
10
|
+
* size its geometry is exactly what it is without that minimum (the theme's
|
|
11
|
+
* height, Butter's own 30px included); when its text grows (here by raising
|
|
12
|
+
* the supporting text size on a wrapper) the pill grows with the label,
|
|
13
|
+
* contains it and shows all
|
|
14
|
+
* of it, with no width floor; a color-only badge keeps its size at every
|
|
15
|
+
* text size; and where there is too little room (a crowded row, a cell
|
|
16
|
+
* narrower than one line) the badge shrinks and stays inside
|
|
17
|
+
* (component:Badge FR3).
|
|
18
|
+
* @position Run with `pnpm test:a11y-contract`. Line boxes and box sizes need
|
|
19
|
+
* a real layout engine; jsdom has neither.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {expect, test, type Page} from '@playwright/test';
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_STORYBOOK_DIR,
|
|
25
|
+
serveStorybook,
|
|
26
|
+
type StaticServer,
|
|
27
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
28
|
+
|
|
29
|
+
// A phone's page size (an iPhone 17 Pro's): at a desktop width every row fits
|
|
30
|
+
// on one line and a squeezed, truncated label would go unseen.
|
|
31
|
+
test.use({viewport: {width: 402, height: 714}});
|
|
32
|
+
|
|
33
|
+
let storybook: StaticServer;
|
|
34
|
+
test.beforeAll(async () => {
|
|
35
|
+
storybook = await serveStorybook(
|
|
36
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
test.afterAll(async () => {
|
|
40
|
+
await storybook?.close();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
interface BadgeBox {
|
|
44
|
+
label: string;
|
|
45
|
+
/** The label is cut with an ellipsis: wider than the room it has. */
|
|
46
|
+
truncated: boolean;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
lineHeight: number;
|
|
50
|
+
labelTop: number;
|
|
51
|
+
labelBottom: number;
|
|
52
|
+
top: number;
|
|
53
|
+
bottom: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function badgesIn(page: Page, size: string): Promise<BadgeBox[]> {
|
|
57
|
+
return page.getByTestId(`badge-row-${size}`).evaluate(row =>
|
|
58
|
+
[...row.querySelectorAll<HTMLElement>('.astryx-badge')].map(badge => {
|
|
59
|
+
const box = badge.getBoundingClientRect();
|
|
60
|
+
const label = badge.lastElementChild as HTMLElement;
|
|
61
|
+
const labelBox = label.getBoundingClientRect();
|
|
62
|
+
return {
|
|
63
|
+
label: label.textContent ?? '',
|
|
64
|
+
truncated: label.scrollWidth > label.clientWidth,
|
|
65
|
+
width: box.width,
|
|
66
|
+
height: box.height,
|
|
67
|
+
lineHeight: Number.parseFloat(getComputedStyle(badge).lineHeight),
|
|
68
|
+
labelTop: labelBox.top,
|
|
69
|
+
labelBottom: labelBox.bottom,
|
|
70
|
+
top: box.top,
|
|
71
|
+
bottom: box.bottom,
|
|
72
|
+
};
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Each badge's size in the row, with and without the minimum this change adds:
|
|
79
|
+
* the "without" pass sets `min-height` back to what it was before (`auto`) on
|
|
80
|
+
* the element itself.
|
|
81
|
+
*/
|
|
82
|
+
async function withAndWithoutMinimums(page: Page, size: string) {
|
|
83
|
+
return page.getByTestId(`badge-row-${size}`).evaluate(row =>
|
|
84
|
+
[...row.querySelectorAll<HTMLElement>('.astryx-badge')].map(badge => {
|
|
85
|
+
const measure = () => {
|
|
86
|
+
const box = badge.getBoundingClientRect();
|
|
87
|
+
return {width: box.width, height: box.height};
|
|
88
|
+
};
|
|
89
|
+
const withMinimums = measure();
|
|
90
|
+
badge.style.minHeight = 'auto';
|
|
91
|
+
const without = measure();
|
|
92
|
+
badge.style.removeProperty('min-height');
|
|
93
|
+
return {label: badge.textContent ?? '', withMinimums, without};
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Each badge's natural width in the row: its content's width plus its own
|
|
100
|
+
* inline padding, measured with no minimum or maximum width and sized to its
|
|
101
|
+
* content (`max-content`). A badge with room is exactly this wide, so a width
|
|
102
|
+
* floor (or a stretch) shows up as a difference.
|
|
103
|
+
*/
|
|
104
|
+
async function naturalWidths(page: Page, size: string): Promise<number[]> {
|
|
105
|
+
return page.getByTestId(`badge-row-${size}`).evaluate(row =>
|
|
106
|
+
[...row.querySelectorAll<HTMLElement>('.astryx-badge')].map(badge => {
|
|
107
|
+
const {style} = badge;
|
|
108
|
+
style.setProperty('width', 'max-content');
|
|
109
|
+
style.setProperty('min-width', '0px');
|
|
110
|
+
style.setProperty('max-width', 'none');
|
|
111
|
+
const width = badge.getBoundingClientRect().width;
|
|
112
|
+
style.removeProperty('width');
|
|
113
|
+
style.removeProperty('min-width');
|
|
114
|
+
style.removeProperty('max-width');
|
|
115
|
+
return width;
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The neutral theme's badge height: its `--spacing-5`. */
|
|
121
|
+
async function spacing5(page: Page): Promise<number> {
|
|
122
|
+
return page.evaluate(() => {
|
|
123
|
+
const probe = document.createElement('div');
|
|
124
|
+
probe.style.height = 'var(--spacing-5)';
|
|
125
|
+
document.body.append(probe);
|
|
126
|
+
const height = probe.getBoundingClientRect().height;
|
|
127
|
+
probe.remove();
|
|
128
|
+
return height;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const THEMES = [
|
|
133
|
+
{name: 'neutral', badgeHeight: spacing5},
|
|
134
|
+
// Butter's own badge height (themes/butter/src/butterTheme.ts,
|
|
135
|
+
// `components.badge.base.height`).
|
|
136
|
+
{name: 'butter', badgeHeight: async () => 30},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
for (const {name, badgeHeight} of THEMES) {
|
|
140
|
+
test.describe(`a badge and its text, ${name} theme`, () => {
|
|
141
|
+
test.beforeEach(async ({page}) => {
|
|
142
|
+
await page.goto(
|
|
143
|
+
`${storybook.origin}/iframe.html?id=core-badge--larger-text&viewMode=story&globals=astryxTheme:${name}`,
|
|
144
|
+
);
|
|
145
|
+
await expect(page.getByTestId('badge-row-default')).toBeVisible();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('keeps its exact geometry and the theme height at the default text size', async ({
|
|
149
|
+
page,
|
|
150
|
+
}) => {
|
|
151
|
+
const height = await badgeHeight(page);
|
|
152
|
+
const natural = await naturalWidths(page, 'default');
|
|
153
|
+
for (const [index, badge] of (
|
|
154
|
+
await withAndWithoutMinimums(page, 'default')
|
|
155
|
+
).entries()) {
|
|
156
|
+
// The new minimum does not bind at the default size …
|
|
157
|
+
expect(badge.withMinimums.width, badge.label).toBeCloseTo(
|
|
158
|
+
badge.without.width,
|
|
159
|
+
2,
|
|
160
|
+
);
|
|
161
|
+
expect(badge.withMinimums.height, badge.label).toBeCloseTo(
|
|
162
|
+
badge.without.height,
|
|
163
|
+
2,
|
|
164
|
+
);
|
|
165
|
+
// … so the badge is as tall as its theme says, and as wide as its
|
|
166
|
+
// content and padding: a color-only badge is its padding alone
|
|
167
|
+
// (component:Badge FR1).
|
|
168
|
+
expect(badge.withMinimums.height, badge.label).toBeCloseTo(height, 1);
|
|
169
|
+
expect(badge.withMinimums.width, badge.label).toBeCloseTo(
|
|
170
|
+
natural[index],
|
|
171
|
+
1,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
for (const badge of await badgesIn(page, 'default')) {
|
|
175
|
+
expect(badge.truncated, badge.label).toBe(false);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('stays inside a cell narrower than one line of its text', async ({
|
|
180
|
+
page,
|
|
181
|
+
}) => {
|
|
182
|
+
const boxes = await page.getByTestId('narrow-cell').evaluateAll(cells =>
|
|
183
|
+
cells.map(cell => {
|
|
184
|
+
const badge = cell.querySelector('.astryx-badge');
|
|
185
|
+
const own = cell.getBoundingClientRect();
|
|
186
|
+
const box = badge?.getBoundingClientRect();
|
|
187
|
+
return {
|
|
188
|
+
label: cell.textContent ?? '',
|
|
189
|
+
cell: [own.left, own.right],
|
|
190
|
+
badge: box == null ? null : [box.left, box.right],
|
|
191
|
+
};
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
expect(boxes).toHaveLength(1);
|
|
195
|
+
for (const {label, cell, badge} of boxes) {
|
|
196
|
+
expect(badge, label).not.toBeNull();
|
|
197
|
+
if (badge != null) {
|
|
198
|
+
expect(badge[0], label).toBeGreaterThanOrEqual(cell[0] - 0.5);
|
|
199
|
+
expect(badge[1], label).toBeLessThanOrEqual(cell[1] + 0.5);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('stays inside a crowded row at 2.5rem text', async ({page}) => {
|
|
205
|
+
// A title that cannot shrink leaves the count less room than one line of
|
|
206
|
+
// its text is tall: the badge keeps its own width there, where a
|
|
207
|
+
// one-line minimum width would push it out of the row.
|
|
208
|
+
const box = await page.getByTestId('badge-row-crowded').evaluate(row => {
|
|
209
|
+
const badge = row.querySelector('.astryx-badge');
|
|
210
|
+
const own = row.getBoundingClientRect();
|
|
211
|
+
const rect = badge?.getBoundingClientRect();
|
|
212
|
+
return {
|
|
213
|
+
row: [own.left, own.right],
|
|
214
|
+
badge: rect == null ? null : [rect.left, rect.right],
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
expect(box.badge).not.toBeNull();
|
|
218
|
+
if (box.badge != null) {
|
|
219
|
+
expect(box.badge[1]).toBeLessThanOrEqual(box.row[1] + 0.5);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
for (const size of ['1.25rem', '2.5rem']) {
|
|
224
|
+
test(`grows with its label at ${size} text, shows all of it, and has no width floor`, async ({
|
|
225
|
+
page,
|
|
226
|
+
}) => {
|
|
227
|
+
const height = await badgeHeight(page);
|
|
228
|
+
const badges = await badgesIn(page, size);
|
|
229
|
+
const natural = await naturalWidths(page, size);
|
|
230
|
+
expect(natural).toHaveLength(badges.length);
|
|
231
|
+
for (const [index, badge] of badges.entries()) {
|
|
232
|
+
if (badge.label === '') {
|
|
233
|
+
// A color-only badge has no text to hold: it keeps its theme
|
|
234
|
+
// height and its padding-only width (component:Badge FR1).
|
|
235
|
+
expect(badge.height).toBeCloseTo(height, 1);
|
|
236
|
+
expect(badge.width).toBeCloseTo(natural[index], 1);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
// At least one line of its own text tall …
|
|
240
|
+
expect(badge.height, badge.label).toBeGreaterThanOrEqual(
|
|
241
|
+
badge.lineHeight - 0.5,
|
|
242
|
+
);
|
|
243
|
+
// … so the label stays inside the pill …
|
|
244
|
+
expect(badge.labelTop, badge.label).toBeGreaterThanOrEqual(
|
|
245
|
+
badge.top - 0.5,
|
|
246
|
+
);
|
|
247
|
+
expect(badge.labelBottom, badge.label).toBeLessThanOrEqual(
|
|
248
|
+
badge.bottom + 0.5,
|
|
249
|
+
);
|
|
250
|
+
// … whole rather than cut to an ellipsis, in rows that wrap as a
|
|
251
|
+
// layout must at these sizes …
|
|
252
|
+
expect(badge.truncated, badge.label).toBe(false);
|
|
253
|
+
// … and with no width floor: as wide as its own content and padding,
|
|
254
|
+
// a one-character count included.
|
|
255
|
+
expect(badge.width, badge.label).toBeCloseTo(natural[index], 1);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
@@ -1045,6 +1045,52 @@ describe('BottomSheet', () => {
|
|
|
1045
1045
|
});
|
|
1046
1046
|
|
|
1047
1047
|
describe('swipe to dismiss', () => {
|
|
1048
|
+
it("a sheet opened from inside another sheet's content drags to dismiss by its own handle, and the covered host neither drags nor takes the pointer (FR6, FR7)", () => {
|
|
1049
|
+
const onInnerOpenChange = vi.fn();
|
|
1050
|
+
const onOuterOpenChange = vi.fn();
|
|
1051
|
+
render(
|
|
1052
|
+
<BottomSheet isOpen onOpenChange={onOuterOpenChange} label="Outer">
|
|
1053
|
+
Outer content
|
|
1054
|
+
<BottomSheet isOpen onOpenChange={onInnerOpenChange} label="Inner">
|
|
1055
|
+
Inner content
|
|
1056
|
+
</BottomSheet>
|
|
1057
|
+
</BottomSheet>,
|
|
1058
|
+
);
|
|
1059
|
+
const dialogs = screen.getAllByRole('dialog', {hidden: true});
|
|
1060
|
+
expect(dialogs).toHaveLength(2);
|
|
1061
|
+
const [outerDialog, innerDialog] = dialogs;
|
|
1062
|
+
// The inner sheet's dialog is rendered inside the outer sheet's body,
|
|
1063
|
+
// so its events bubble through the outer body's gesture listeners.
|
|
1064
|
+
expect(outerDialog.contains(innerDialog)).toBe(true);
|
|
1065
|
+
const outerPanel = outerDialog.querySelector<HTMLElement>(
|
|
1066
|
+
':scope > .astryx-bottom-sheet',
|
|
1067
|
+
)!;
|
|
1068
|
+
const outerBody = outerPanel.querySelector<HTMLElement>(
|
|
1069
|
+
':scope > :not([aria-hidden])',
|
|
1070
|
+
)!;
|
|
1071
|
+
const innerHandle = innerDialog.querySelector<HTMLElement>(
|
|
1072
|
+
'.astryx-bottom-sheet > [aria-hidden="true"]',
|
|
1073
|
+
)!;
|
|
1074
|
+
// `setPointerCapture` is a prototype stub here, so record who captures.
|
|
1075
|
+
const captured: Element[] = [];
|
|
1076
|
+
const capture = vi
|
|
1077
|
+
.spyOn(Element.prototype, 'setPointerCapture')
|
|
1078
|
+
.mockImplementation(function (this: Element) {
|
|
1079
|
+
captured.push(this);
|
|
1080
|
+
});
|
|
1081
|
+
drag(innerHandle, [{y: 0}, {y: 40}, {y: 120}], 'touch');
|
|
1082
|
+
capture.mockRestore();
|
|
1083
|
+
// The inner sheet's own drag ran to its dismissal.
|
|
1084
|
+
expect(onInnerOpenChange).toHaveBeenCalledWith(false);
|
|
1085
|
+
// The covered host saw the same pointer bubble up and did nothing with
|
|
1086
|
+
// it: no drag of its own (its panel keeps its resting transform) and no
|
|
1087
|
+
// pointer capture, which would have cancelled the inner handle's drag.
|
|
1088
|
+
expect(onOuterOpenChange).not.toHaveBeenCalled();
|
|
1089
|
+
expect(captured).toContain(innerHandle);
|
|
1090
|
+
expect(captured).not.toContain(outerBody);
|
|
1091
|
+
expect(outerPanel.style.transform).toBe('');
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1048
1094
|
it('requests close when dragged past the dismiss threshold', () => {
|
|
1049
1095
|
const onOpenChange = vi.fn();
|
|
1050
1096
|
render(
|