@astryxdesign/core 0.4.5 → 0.4.6-canary.cea9ecd
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/CHANGELOG.md +134 -0
- package/dist/Avatar/Avatar.d.ts +10 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +139 -30
- package/dist/Avatar/AvatarStatusDot.d.ts +2 -1
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/Avatar/AvatarStatusDot.js +30 -4
- package/dist/Avatar/AvatarStatusLabelContext.d.ts +39 -0
- package/dist/Avatar/AvatarStatusLabelContext.d.ts.map +1 -0
- package/dist/Avatar/AvatarStatusLabelContext.js +41 -0
- package/dist/AvatarGroup/AvatarGroup.d.ts +3 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +15 -9
- package/dist/Blockquote/Blockquote.d.ts +4 -3
- package/dist/Blockquote/Blockquote.d.ts.map +1 -1
- package/dist/Blockquote/Blockquote.js +9 -10
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +21 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +47 -34
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +7 -7
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +21 -12
- package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +5 -5
- package/dist/Center/Center.d.ts +23 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +12 -6
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +3 -3
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +2 -2
- package/dist/Chat/useChatDictation.d.ts +1 -1
- package/dist/Chat/useChatDictation.d.ts.map +1 -1
- package/dist/Chat/useSpeechRecognition.d.ts +1 -1
- package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
- package/dist/Chat/useSpeechRecognition.js +4 -3
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +2 -2
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +5 -5
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +4 -4
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +1 -1
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +2 -2
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +3 -3
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +6 -6
- package/dist/DateInput/DateInput.d.ts +31 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +90 -22
- package/dist/DateInput/MonthScroller.d.ts +35 -0
- package/dist/DateInput/MonthScroller.d.ts.map +1 -0
- package/dist/DateInput/MonthScroller.js +658 -0
- package/dist/DateInput/MonthYearWheels.d.ts +24 -0
- package/dist/DateInput/MonthYearWheels.d.ts.map +1 -0
- package/dist/DateInput/MonthYearWheels.js +114 -0
- package/dist/DateInput/TouchDateField.d.ts +10 -0
- package/dist/DateInput/TouchDateField.d.ts.map +1 -0
- package/dist/DateInput/TouchDateField.js +810 -0
- package/dist/DateInput/Wheel.d.ts +41 -0
- package/dist/DateInput/Wheel.d.ts.map +1 -0
- package/dist/DateInput/Wheel.js +315 -0
- package/dist/DateInput/monthGeometry.d.ts +65 -0
- package/dist/DateInput/monthGeometry.d.ts.map +1 -0
- package/dist/DateInput/monthGeometry.js +98 -0
- package/dist/DateInput/tokens.stylex.d.ts +63 -0
- package/dist/DateInput/tokens.stylex.d.ts.map +1 -0
- package/dist/DateInput/tokens.stylex.js +74 -0
- package/dist/DateInput/useOwnScrollGesture.d.ts +41 -0
- package/dist/DateInput/useOwnScrollGesture.d.ts.map +1 -0
- package/dist/DateInput/useOwnScrollGesture.js +202 -0
- package/dist/DateInput/usePointerDragScroll.d.ts +15 -0
- package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -0
- package/dist/DateInput/usePointerDragScroll.js +216 -0
- package/dist/DateInput/useScrollSettle.d.ts +26 -0
- package/dist/DateInput/useScrollSettle.d.ts.map +1 -0
- package/dist/DateInput/useScrollSettle.js +149 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +17 -14
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +17 -14
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +3 -3
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +2 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +2 -2
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +2 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +4 -4
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +6 -0
- package/dist/Field/inputStyles.stylex.d.ts +1 -1
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +5 -5
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +6 -6
- package/dist/HoverCard/HoverCard.d.ts +13 -3
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -0
- package/dist/HoverCard/index.d.ts +1 -1
- package/dist/HoverCard/index.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.d.ts +17 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +78 -9
- package/dist/InputGroup/InputGroup.js +1 -1
- package/dist/Item/Item.d.ts +9 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +52 -14
- package/dist/Layer/index.d.ts +4 -2
- package/dist/Layer/index.d.ts.map +1 -1
- package/dist/Layer/index.js +2 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +3 -2
- package/dist/Layer/useTouchTrigger.d.ts +113 -0
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
- package/dist/Layer/useTouchTrigger.js +241 -0
- package/dist/Layout/container.stylex.d.ts +4 -2
- package/dist/Layout/container.stylex.d.ts.map +1 -1
- package/dist/Layout/container.stylex.js +19 -11
- package/dist/Layout/index.d.ts +1 -0
- package/dist/Layout/index.d.ts.map +1 -1
- package/dist/Layout/index.js +4 -0
- package/dist/Layout/padding.stylex.d.ts +315 -12
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +389 -12
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +5 -4
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -6
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +1 -1
- package/dist/MobileNav/MobileNav.d.ts +1 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +24 -6
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +8 -8
- package/dist/NavItem/navItemStyles.stylex.d.ts +4 -4
- package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
- package/dist/NavItem/navItemStyles.stylex.js +4 -4
- package/dist/NavMenu/NavHeadingMenuItem.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenuItem.js +3 -3
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +36 -8
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +3 -3
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +2 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +11 -12
- package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchToken.js +3 -1
- package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +23 -8
- package/dist/RadioList/RadioListItem.d.ts +2 -2
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +70 -40
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +7 -7
- package/dist/Section/Section.d.ts +36 -1
- package/dist/Section/Section.d.ts.map +1 -1
- package/dist/Section/Section.js +12 -2
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +7 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -3
- package/dist/Selector/Selector.d.ts +26 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +93 -20
- package/dist/Selector/SelectorOption.d.ts +14 -1
- package/dist/Selector/SelectorOption.d.ts.map +1 -1
- package/dist/Selector/SelectorOption.js +6 -0
- package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
- package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
- package/dist/Selector/SelectorRowLayoutContext.js +27 -0
- package/dist/Selector/types.d.ts +1 -0
- package/dist/Selector/types.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +4 -4
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +3 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +7 -7
- package/dist/Stack/Stack.d.ts +23 -1
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +12 -6
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +2 -2
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +1 -1
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +3 -3
- package/dist/Table/TableRow.js +4 -4
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
- package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
- package/dist/Table/plugins/filtering/useTableFiltering.js +2 -2
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +2 -2
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -2
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +3 -3
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.js +1 -1
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +3 -3
- package/dist/TimeInput/TimeInput.js +2 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +6 -4
- package/dist/Timestamp/formatInstant.d.ts +3 -2
- package/dist/Timestamp/formatInstant.d.ts.map +1 -1
- package/dist/Timestamp/formatInstant.js +8 -8
- package/dist/Timestamp/tooltipEntries.d.ts +2 -1
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.js +5 -3
- package/dist/Toast/Toast.d.ts +4 -3
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +10 -7
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +6 -6
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +1 -1
- package/dist/Tooltip/Tooltip.d.ts +16 -3
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +2 -0
- package/dist/Tooltip/index.d.ts +1 -1
- package/dist/Tooltip/index.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +21 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +71 -13
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +4 -4
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +3 -3
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +2 -2
- package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +2 -2
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +4 -4
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +7 -7
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +2 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +1 -1
- package/dist/astryx.css +124 -62
- package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
- package/dist/hooks/containerReveal.stylex.js +18 -18
- package/dist/hooks/scrollbarGutter.d.ts +28 -0
- package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
- package/dist/hooks/scrollbarGutter.js +103 -0
- package/dist/hooks/useAutoMediaMode.d.ts +31 -0
- package/dist/hooks/useAutoMediaMode.d.ts.map +1 -0
- package/dist/hooks/useAutoMediaMode.js +201 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -1
- package/dist/hooks/useListFocus.d.ts +5 -2
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +12 -6
- package/dist/hooks/useScrollLock.d.ts +4 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.js +13 -1
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +2 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +6 -0
- package/dist/i18n/useCollator.d.ts +12 -0
- package/dist/i18n/useCollator.d.ts.map +1 -0
- package/dist/i18n/useCollator.js +40 -0
- package/dist/i18n/useLocale.d.ts +3 -0
- package/dist/i18n/useLocale.d.ts.map +1 -0
- package/dist/i18n/useLocale.js +31 -0
- package/dist/theme/MediaTheme.d.ts +25 -5
- package/dist/theme/MediaTheme.d.ts.map +1 -1
- package/dist/theme/MediaTheme.js +31 -6
- package/dist/theme/defineTheme.d.ts +5 -0
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +5 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +7 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +66 -5
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/syntax/index.d.ts +6 -0
- package/dist/theme/syntax/index.d.ts.map +1 -1
- package/dist/theme/syntax/index.js +7 -2
- package/dist/utils/dateParser.d.ts +4 -3
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +8 -8
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/plainDate.d.ts +5 -2
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +20 -4
- package/locales/en.json +28 -0
- package/locales/pseudo.json +21 -0
- package/package.json +3 -3
- package/src/Avatar/Avatar.doc.mjs +10 -6
- package/src/Avatar/Avatar.test.tsx +219 -1
- package/src/Avatar/Avatar.tsx +179 -37
- package/src/Avatar/AvatarStatusDot.tsx +30 -4
- package/src/Avatar/AvatarStatusLabelContext.ts +47 -0
- package/src/AvatarGroup/AvatarGroup.doc.mjs +10 -6
- package/src/AvatarGroup/AvatarGroup.test.tsx +62 -0
- package/src/AvatarGroup/AvatarGroup.tsx +3 -1
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +31 -8
- package/src/Banner/Banner.doc.mjs +2 -0
- package/src/Blockquote/Blockquote.doc.mjs +30 -7
- package/src/Blockquote/Blockquote.test.tsx +34 -3
- package/src/Blockquote/Blockquote.tsx +11 -9
- package/src/BottomSheet/BottomSheet.doc.mjs +3 -3
- package/src/BottomSheet/BottomSheet.test.tsx +24 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +58 -3
- package/src/Breadcrumbs/BreadcrumbItem.tsx +36 -23
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +26 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -0
- package/src/Button/Button.tsx +12 -9
- package/src/Calendar/Calendar.tsx +15 -9
- package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
- package/src/Calendar/styles.ts +8 -5
- package/src/Center/Center.doc.mjs +48 -0
- package/src/Center/Center.test.tsx +158 -0
- package/src/Center/Center.tsx +50 -9
- package/src/Chat/ChatComposer.tsx +11 -5
- package/src/Chat/ChatComposerDrawer.tsx +4 -1
- package/src/Chat/ChatToolCalls.tsx +13 -4
- package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
- package/src/Chat/useChatDictation.ts +1 -1
- package/src/Chat/useSpeechRecognition.ts +5 -3
- package/src/Chat/useTriggerMenu.tsx +4 -1
- package/src/CheckboxInput/CheckboxInput.tsx +5 -2
- package/src/Citation/Citation.test.tsx +6 -1
- package/src/Citation/Citation.tsx +11 -5
- package/src/ClickableCard/ClickableCard.tsx +7 -4
- package/src/CodeBlock/CodeBlock.tsx +4 -1
- package/src/Collapsible/Collapsible.tsx +5 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +6 -3
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
- package/src/ComplexSelector/ComplexSelector.tsx +14 -7
- package/src/DateInput/DateInput.test.tsx +17 -1
- package/src/DateInput/DateInput.tsx +93 -21
- package/src/DateInput/DateInputTouch.test.tsx +2582 -0
- package/src/DateInput/MonthScroller.tsx +878 -0
- package/src/DateInput/MonthYearWheels.tsx +147 -0
- package/src/DateInput/TouchDateField.tsx +1223 -0
- package/src/DateInput/Wheel.tsx +473 -0
- package/src/DateInput/monthGeometry.ts +118 -0
- package/src/DateInput/tokens.stylex.ts +99 -0
- package/src/DateInput/useOwnScrollGesture.ts +227 -0
- package/src/DateInput/usePointerDragScroll.ts +220 -0
- package/src/DateInput/useScrollSettle.ts +164 -0
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.tsx +31 -13
- package/src/DateTimeInput/DateTimeInput.tsx +25 -9
- package/src/Dialog/Dialog.test.tsx +24 -0
- package/src/Dialog/Dialog.tsx +3 -0
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +5 -2
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -2
- package/src/Field/Field.doc.mjs +2 -0
- package/src/Field/FieldLabel.tsx +9 -3
- package/src/Field/InputClearButton.test.tsx +110 -3
- package/src/Field/InputClearButton.tsx +38 -0
- package/src/Field/inputStyles.stylex.ts +17 -13
- package/src/FileInput/FileInput.tsx +15 -8
- package/src/FormLayout/FormLayout.doc.mjs +1 -1
- package/src/HoverCard/HoverCard.doc.mjs +18 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -0
- package/src/HoverCard/HoverCard.tsx +22 -2
- package/src/HoverCard/index.ts +1 -0
- package/src/HoverCard/useHoverCard.doc.mjs +2 -0
- package/src/HoverCard/useHoverCard.tsx +103 -7
- package/src/InputGroup/InputGroup.tsx +1 -1
- package/src/Item/Item.doc.mjs +1 -0
- package/src/Item/Item.test.tsx +50 -0
- package/src/Item/Item.tsx +54 -6
- package/src/Layer/index.ts +10 -0
- package/src/Layer/useLayer.tsx +13 -2
- package/src/Layer/useTouchTrigger.test.ts +70 -0
- package/src/Layer/useTouchTrigger.ts +366 -0
- package/src/Layout/Layout.doc.mjs +18 -0
- package/src/Layout/container.stylex.ts +11 -3
- package/src/Layout/index.ts +4 -0
- package/src/Layout/overlayPaddingReset.test.tsx +222 -0
- package/src/Layout/padding.stylex.ts +192 -12
- package/src/Lightbox/Lightbox.tsx +11 -3
- package/src/Link/Link.tsx +9 -6
- package/src/MetadataList/MetadataList.tsx +4 -1
- package/src/MobileNav/MobileNav.tsx +61 -5
- package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
- package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
- package/src/MultiSelector/MultiSelector.test.tsx +1 -1
- package/src/MultiSelector/MultiSelector.tsx +26 -10
- package/src/NavItem/navItemStyles.stylex.ts +7 -4
- package/src/NavMenu/NavHeadingMenuItem.tsx +6 -3
- package/src/NumberInput/NumberInput.doc.mjs +24 -0
- package/src/NumberInput/NumberInput.test.tsx +138 -0
- package/src/NumberInput/NumberInput.tsx +47 -8
- package/src/Outline/Outline.tsx +5 -2
- package/src/Pagination/Pagination.tsx +5 -2
- package/src/Popover/Popover.test.tsx +27 -1
- package/src/PowerSearch/PowerSearch.tsx +17 -8
- package/src/PowerSearch/PowerSearchToken.tsx +3 -0
- package/src/PowerSearch/formatFilterValue.test.ts +22 -2
- package/src/PowerSearch/formatFilterValue.ts +31 -6
- package/src/RadioList/RadioList.doc.mjs +1 -1
- package/src/RadioList/RadioList.test.tsx +170 -2
- package/src/RadioList/RadioListItem.tsx +88 -53
- package/src/Resizable/ResizeHandle.test.tsx +22 -0
- package/src/Resizable/ResizeHandle.tsx +25 -7
- package/src/Section/Section.doc.mjs +55 -0
- package/src/Section/Section.test.tsx +201 -4
- package/src/Section/Section.tsx +69 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +15 -3
- package/src/SelectableCard/SelectableCard.tsx +6 -3
- package/src/Selector/Selector.doc.mjs +16 -2
- package/src/Selector/Selector.test.tsx +525 -1
- package/src/Selector/Selector.tsx +180 -25
- package/src/Selector/SelectorOption.doc.mjs +7 -0
- package/src/Selector/SelectorOption.tsx +21 -0
- package/src/Selector/SelectorRowLayoutContext.ts +32 -0
- package/src/Selector/types.ts +4 -4
- package/src/SideNav/SideNavHeading.tsx +14 -5
- package/src/SideNav/SideNavItem.tsx +9 -3
- package/src/Slider/Slider.tsx +25 -6
- package/src/Stack/Stack.doc.mjs +72 -0
- package/src/Stack/Stack.test.tsx +156 -0
- package/src/Stack/Stack.tsx +50 -8
- package/src/Switch/Switch.tsx +5 -2
- package/src/TabList/Tab.tsx +4 -1
- package/src/TabList/TabMenu.tsx +9 -3
- package/src/Table/TableRow.tsx +4 -4
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +6 -2
- package/src/Table/plugins/filtering/useTableFiltering.tsx +4 -1
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +10 -3
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +6 -3
- package/src/Table/plugins/sortable/useTableSortable.tsx +4 -1
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
- package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
- package/src/Table/plugins/tree/useTableTreeData.tsx +10 -4
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.tsx +1 -1
- package/src/TextInput/TextInput.tsx +1 -1
- package/src/Thumbnail/Thumbnail.tsx +9 -3
- package/src/TimeInput/TimeInput.tsx +1 -1
- package/src/Timestamp/Timestamp.test.tsx +27 -7
- package/src/Timestamp/Timestamp.tsx +8 -4
- package/src/Timestamp/formatInstant.ts +9 -7
- package/src/Timestamp/tooltipEntries.test.ts +11 -6
- package/src/Timestamp/tooltipEntries.ts +6 -2
- package/src/Toast/Toast.tsx +9 -7
- package/src/Token/Token.tsx +14 -5
- package/src/Tokenizer/Tokenizer.tsx +4 -1
- package/src/Tooltip/Tooltip.doc.mjs +18 -0
- package/src/Tooltip/Tooltip.test.tsx +254 -1
- package/src/Tooltip/Tooltip.tsx +22 -2
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/useTooltip.doc.mjs +2 -0
- package/src/Tooltip/useTooltip.tsx +99 -21
- package/src/TopNav/TopNavHeading.tsx +13 -4
- package/src/TopNav/TopNavItem.tsx +6 -3
- package/src/TopNav/TopNavMegaMenu.tsx +5 -2
- package/src/TopNav/TopNavMegaMenuItem.tsx +5 -2
- package/src/TopNav/TopNavMenu.tsx +10 -4
- package/src/TreeList/TreeListItem.tsx +22 -7
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.tsx +4 -1
- package/src/hooks/containerReveal.stylex.ts +54 -18
- package/src/hooks/scrollbarGutter.test.ts +167 -0
- package/src/hooks/scrollbarGutter.ts +121 -0
- package/src/hooks/useAutoMediaMode.test.ts +109 -0
- package/src/hooks/useAutoMediaMode.ts +233 -0
- package/src/hooks/useInputStatusIcon.tsx +4 -1
- package/src/hooks/useListFocus.doc.mjs +2 -2
- package/src/hooks/useListFocus.test.tsx +65 -3
- package/src/hooks/useListFocus.ts +15 -7
- package/src/hooks/useScrollLock.doc.mjs +4 -4
- package/src/hooks/useScrollLock.test.ts +73 -0
- package/src/hooks/useScrollLock.ts +14 -0
- package/src/i18n/InternationalizationContext.ts +2 -0
- package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
- package/src/i18n/__tests__/useCollator.test.tsx +115 -0
- package/src/i18n/__tests__/useLocale.test.tsx +52 -0
- package/src/i18n/index.ts +6 -0
- package/src/i18n/useCollator.doc.mjs +84 -0
- package/src/i18n/useCollator.ts +44 -0
- package/src/i18n/useLocale.doc.mjs +70 -0
- package/src/i18n/useLocale.ts +34 -0
- package/src/i18n/useTranslator.doc.mjs +1 -1
- package/src/reset.css +9 -2
- package/src/reset.test.ts +22 -0
- package/src/theme/MediaTheme.doc.mjs +23 -4
- package/src/theme/MediaTheme.dom.test.tsx +97 -0
- package/src/theme/MediaTheme.tsx +42 -7
- package/src/theme/defineTheme.test.ts +9 -3
- package/src/theme/defineTheme.ts +5 -0
- package/src/theme/derivedVarRegistry.test.ts +19 -5
- package/src/theme/derivedVarRegistry.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +118 -0
- package/src/theme/generateThemeRules.ts +83 -5
- package/src/theme/index.ts +1 -1
- package/src/theme/syntax/index.ts +6 -2
- package/src/theme/syntax/serverSafeSyntax.test.ts +68 -0
- package/src/utils/dateParser.test.ts +20 -0
- package/src/utils/dateParser.ts +14 -7
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +6 -0
- package/src/utils/plainDate.ts +21 -4
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file scrollbarGutter.ts
|
|
5
|
+
* @input The element a scroll lock pins, plus live layout measurements
|
|
6
|
+
* @output Exports holdScrollbarGutter and the ScrollbarGutterHold it returns
|
|
7
|
+
* @position Internal hook utility; used by useScrollLock and MobileNav so that
|
|
8
|
+
* locking background scroll does not resize the layout viewport.
|
|
9
|
+
*
|
|
10
|
+
* Locking scroll hides the document's scrollbar. Where that scrollbar is a
|
|
11
|
+
* classic (space-taking) one — Windows/Linux desktop, and macOS set to
|
|
12
|
+
* "Always" show scroll bars — hiding it widens the layout viewport by the
|
|
13
|
+
* scrollbar's width and the whole page jumps sideways behind the overlay.
|
|
14
|
+
*
|
|
15
|
+
* The fix is `scrollbar-gutter: stable`, which holds the gutter open once the
|
|
16
|
+
* scrollbar goes away. Being a real layout change it holds `position: fixed`
|
|
17
|
+
* chrome (sticky headers, toast viewports) as well as in-flow content, which
|
|
18
|
+
* padding fundamentally cannot: fixed elements resolve against the viewport,
|
|
19
|
+
* not against the padded element.
|
|
20
|
+
*
|
|
21
|
+
* Padding is kept only as a fallback for engines without `scrollbar-gutter`
|
|
22
|
+
* (Safari shipped it in 18.2), and it is applied by measuring whether the
|
|
23
|
+
* element actually moved rather than by assuming it did.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const NOOP: ScrollbarGutterHold = {
|
|
27
|
+
settle() {},
|
|
28
|
+
release() {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface ScrollbarGutterHold {
|
|
32
|
+
/**
|
|
33
|
+
* Call once the lock's own styles are applied: measures whether the element
|
|
34
|
+
* grew anyway and, only then, pads the difference away.
|
|
35
|
+
*/
|
|
36
|
+
settle(): void;
|
|
37
|
+
/** Call on unlock: restores everything this hold changed. */
|
|
38
|
+
release(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Keeps `element`'s content box the width it is right now, across a scroll
|
|
43
|
+
* lock that is about to hide the document's scrollbar.
|
|
44
|
+
*
|
|
45
|
+
* Call it *before* applying the lock's styles, then {@link
|
|
46
|
+
* ScrollbarGutterHold.settle} immediately after, and {@link
|
|
47
|
+
* ScrollbarGutterHold.release} on unlock:
|
|
48
|
+
*
|
|
49
|
+
* ```
|
|
50
|
+
* const hold = holdScrollbarGutter(document.body);
|
|
51
|
+
* body.style.position = 'fixed';
|
|
52
|
+
* hold.settle();
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Overlay scrollbars (mobile, default macOS) take no layout space, so there is
|
|
56
|
+
* nothing to hold and nothing is touched.
|
|
57
|
+
*/
|
|
58
|
+
export function holdScrollbarGutter(element: HTMLElement): ScrollbarGutterHold {
|
|
59
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
60
|
+
return NOOP;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const root = document.documentElement;
|
|
64
|
+
const viewportWidth = root.clientWidth;
|
|
65
|
+
|
|
66
|
+
// 0 means "no layout to measure" (jsdom, and any non-visual environment),
|
|
67
|
+
// not "the scrollbar is as wide as the window".
|
|
68
|
+
if (viewportWidth === 0) {
|
|
69
|
+
return NOOP;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const previousGutter = root.style.scrollbarGutter;
|
|
73
|
+
const previousPadding = element.style.paddingRight;
|
|
74
|
+
const widthBefore = element.getBoundingClientRect().width;
|
|
75
|
+
|
|
76
|
+
let heldGutter = false;
|
|
77
|
+
let padded = false;
|
|
78
|
+
let settled = false;
|
|
79
|
+
|
|
80
|
+
// Only when a space-taking scrollbar is actually there. Holding the gutter
|
|
81
|
+
// open on a page that never had one would carve out 15px that was never
|
|
82
|
+
// reserved — the same shift, in the other direction.
|
|
83
|
+
if (window.innerWidth > viewportWidth) {
|
|
84
|
+
root.style.scrollbarGutter = 'stable';
|
|
85
|
+
heldGutter = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
settle() {
|
|
90
|
+
if (settled) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
settled = true;
|
|
94
|
+
|
|
95
|
+
// What the lock actually did to this element, rather than what we
|
|
96
|
+
// assumed it would do. 0 on any engine where the gutter held — and on a
|
|
97
|
+
// page that already sets `scrollbar-gutter: stable` itself, where the
|
|
98
|
+
// scrollbar was never taking the space back.
|
|
99
|
+
const grew = element.getBoundingClientRect().width - widthBefore;
|
|
100
|
+
if (grew <= 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const existing =
|
|
105
|
+
Number.parseFloat(window.getComputedStyle(element).paddingRight) || 0;
|
|
106
|
+
element.style.paddingRight = `${existing + grew}px`;
|
|
107
|
+
padded = true;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
release() {
|
|
111
|
+
if (padded) {
|
|
112
|
+
element.style.paddingRight = previousPadding;
|
|
113
|
+
padded = false;
|
|
114
|
+
}
|
|
115
|
+
if (heldGutter) {
|
|
116
|
+
root.style.scrollbarGutter = previousGutter;
|
|
117
|
+
heldGutter = false;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
import {pickMediaMode} from './useAutoMediaMode';
|
|
5
|
+
import {parseColor} from '../utils/color';
|
|
6
|
+
|
|
7
|
+
const rgb = (value: string) => {
|
|
8
|
+
const parsed = parseColor(value);
|
|
9
|
+
if (parsed === null) {
|
|
10
|
+
throw new Error(`bad test color ${value}`);
|
|
11
|
+
}
|
|
12
|
+
return parsed;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const onDark = rgb('#FFFFFF');
|
|
16
|
+
const onLight = rgb('#171717');
|
|
17
|
+
// The theme's --color-text-primary, per color mode.
|
|
18
|
+
const ambientLight = rgb('#171717');
|
|
19
|
+
const ambientDark = rgb('#FAFAFA');
|
|
20
|
+
|
|
21
|
+
describe('pickMediaMode', () => {
|
|
22
|
+
it('picks dark for a dark surface under a light page', () => {
|
|
23
|
+
expect(pickMediaMode(rgb('#0A1317'), ambientLight, onDark, onLight)).toBe(
|
|
24
|
+
'dark',
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('picks light for a light surface under a dark page', () => {
|
|
29
|
+
expect(pickMediaMode(rgb('#FFFFFF'), ambientDark, onDark, onLight)).toBe(
|
|
30
|
+
'light',
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// The bug this exists for: a theme whose "inverted" background is a pale
|
|
35
|
+
// grey, where a hardcoded mode="dark" paints white text at 1.25:1.
|
|
36
|
+
it('answers off for a surface a theme merely calls inverted', () => {
|
|
37
|
+
// Ambient already reads at 14.36; no media side improves on it, so a
|
|
38
|
+
// media context would churn accent and overlays for nothing.
|
|
39
|
+
expect(pickMediaMode(rgb('#E4E6EB'), ambientLight, onDark, onLight)).toBe(
|
|
40
|
+
'off',
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('answers off for a chromatic surface its ambient text already reads on', () => {
|
|
45
|
+
// The dark-mode error toast: ambient 4.50 clears the bar. Verified in
|
|
46
|
+
// Chromium that this renders identically to mode="dark" — a dark page
|
|
47
|
+
// already resolves those tokens to what the media context would install.
|
|
48
|
+
expect(pickMediaMode(rgb('#E3193B'), ambientDark, onDark, onLight)).toBe(
|
|
49
|
+
'off',
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('picks dark for a saturated surface under a light page', () => {
|
|
54
|
+
// The light-mode error toast: a deeper red, where the page's near-black
|
|
55
|
+
// text only manages 2.35 and the surface genuinely needs inverting.
|
|
56
|
+
expect(pickMediaMode(rgb('#AA071E'), ambientLight, onDark, onLight)).toBe(
|
|
57
|
+
'dark',
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('respects a theme that overrides its on-colors', () => {
|
|
62
|
+
// The decision follows the theme's own values, not an assumption that
|
|
63
|
+
// on-dark is white.
|
|
64
|
+
expect(
|
|
65
|
+
pickMediaMode(
|
|
66
|
+
rgb('#0A1317'),
|
|
67
|
+
ambientLight,
|
|
68
|
+
rgb('#101010'),
|
|
69
|
+
rgb('#F0F0F0'),
|
|
70
|
+
),
|
|
71
|
+
).toBe('light');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('composites a translucent on-color before judging it', () => {
|
|
75
|
+
// Ambient (1.44) does not read, so a side must be chosen. Raw white would
|
|
76
|
+
// score 4.54 here and win; composited at 10% alpha it is 1.21 and loses.
|
|
77
|
+
expect(
|
|
78
|
+
pickMediaMode(
|
|
79
|
+
rgb('#767676'),
|
|
80
|
+
rgb('#8A8A8A'),
|
|
81
|
+
rgb('rgba(255,255,255,0.10)'),
|
|
82
|
+
rgb('#171717'),
|
|
83
|
+
),
|
|
84
|
+
).toBe('light');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('picks a side when the ambient color cannot be read at all', () => {
|
|
88
|
+
// Unparseable token: it cannot conclude "no media wanted", so it inverts.
|
|
89
|
+
expect(pickMediaMode(rgb('#0A1317'), null, onDark, onLight)).toBe('dark');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('treats the bar as a floor, not a target', () => {
|
|
93
|
+
// 3.45:1 is poor for body text but clears the bar: the theme is rendering
|
|
94
|
+
// this successfully and a media context would not make it more legible.
|
|
95
|
+
// Deliberately NOT corrected — this is not a WCAG enforcer.
|
|
96
|
+
expect(pickMediaMode(rgb('#8A8A8A'), rgb('#FFFFFF'), onDark, onLight)).toBe(
|
|
97
|
+
'off',
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('gives up when an on-color is unreadable', () => {
|
|
102
|
+
expect(
|
|
103
|
+
pickMediaMode(rgb('#0A1317'), ambientLight, null, onLight),
|
|
104
|
+
).toBeNull();
|
|
105
|
+
expect(
|
|
106
|
+
pickMediaMode(rgb('#0A1317'), ambientLight, onDark, null),
|
|
107
|
+
).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useAutoMediaMode.ts
|
|
7
|
+
* @input Ref to the element whose backdrop should be measured
|
|
8
|
+
* @output 'dark' | 'light' | null (not yet measurable)
|
|
9
|
+
* @position INTERNAL — powers `MediaTheme mode="auto"`. Not exported from the
|
|
10
|
+
* package: MediaTheme is the supported surface for this behavior.
|
|
11
|
+
* Sibling to useImageMode, which answers the same question for images.
|
|
12
|
+
*
|
|
13
|
+
* Picks the media context a surface wants by measuring what the browser
|
|
14
|
+
* actually painted, rather than trusting a token's name.
|
|
15
|
+
*
|
|
16
|
+
* The bug it exists for: `--color-background-inverted` is not required to be
|
|
17
|
+
* inverted. A theme can define it as a pale grey, and a hardcoded
|
|
18
|
+
* `mode="dark"` then paints white text on it at 1.25:1. Because the surface
|
|
19
|
+
* colour is a runtime value and the mode is a compile-time guess, no amount
|
|
20
|
+
* of care in the component can catch that.
|
|
21
|
+
*
|
|
22
|
+
* The answer can also be `'off'`: when the surface's own ambient text already
|
|
23
|
+
* reads on it, the surface is not effectively inverted and does not want a
|
|
24
|
+
* media context. Verified in Chromium that this loses nothing on a surface
|
|
25
|
+
* that is chromatic but legible — the dark-mode error toast renders
|
|
26
|
+
* identically either way, because a dark page already resolves those tokens
|
|
27
|
+
* to the values the media context would install.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```
|
|
31
|
+
* // Inside MediaTheme; see its `mode="auto"`.
|
|
32
|
+
* const detected = useAutoMediaMode(ref, mode === 'auto');
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import {useRef, useState} from 'react';
|
|
37
|
+
import type {RefObject} from 'react';
|
|
38
|
+
import type {RGBA} from '../utils/color';
|
|
39
|
+
import {parseColor, formatColor} from '../utils/color';
|
|
40
|
+
import {contrastRatio, compositeOver} from '../theme/contrast';
|
|
41
|
+
import {useTheme} from '../theme/useTheme';
|
|
42
|
+
import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
|
|
43
|
+
|
|
44
|
+
/** A surface luminance context, or `"off"` when no media context is wanted. */
|
|
45
|
+
export type DetectedMediaMode = 'dark' | 'light' | 'off';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve token references to the colors they actually paint, by reading them
|
|
49
|
+
* back off a hidden probe.
|
|
50
|
+
*
|
|
51
|
+
* A custom property cannot simply be read: `getPropertyValue` returns the
|
|
52
|
+
* token's *specified* text, and Astryx tokens are `light-dark(a, b)` pairs
|
|
53
|
+
* that resolve only once used in a real property. The probe assigns each
|
|
54
|
+
* token to `color` and reads the computed value back, which the browser has
|
|
55
|
+
* resolved against the host's own color-scheme and any overrides.
|
|
56
|
+
*
|
|
57
|
+
* `display: none` keeps the probe out of layout — computed color still
|
|
58
|
+
* resolves — so this costs a style recalc and no reflow.
|
|
59
|
+
*/
|
|
60
|
+
function resolvePaintedColors(
|
|
61
|
+
host: HTMLElement,
|
|
62
|
+
tokens: ReadonlyArray<string>,
|
|
63
|
+
): (RGBA | null)[] {
|
|
64
|
+
const probe = document.createElement('span');
|
|
65
|
+
probe.style.display = 'none';
|
|
66
|
+
host.appendChild(probe);
|
|
67
|
+
try {
|
|
68
|
+
return tokens.map(token => {
|
|
69
|
+
probe.style.color = `var(${token})`;
|
|
70
|
+
return parseColor(getComputedStyle(probe).color);
|
|
71
|
+
});
|
|
72
|
+
} finally {
|
|
73
|
+
probe.remove();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The color actually behind an element: its own background if opaque,
|
|
79
|
+
* otherwise its translucent layers composited down onto the first opaque
|
|
80
|
+
* ancestor.
|
|
81
|
+
*
|
|
82
|
+
* Returns null when the paint is not knowable from CSS:
|
|
83
|
+
* - a `background-image` (gradient, photo, video poster) anywhere in the
|
|
84
|
+
* chain — its pixels need sampling, which is `useImageMode`'s job and
|
|
85
|
+
* needs CORS access the stylesheet cannot give us;
|
|
86
|
+
* - no opaque layer at all, where the paint is the browser canvas.
|
|
87
|
+
*/
|
|
88
|
+
function resolveBackdrop(element: HTMLElement): RGBA | null {
|
|
89
|
+
const layers: RGBA[] = [];
|
|
90
|
+
let node: HTMLElement | null = element;
|
|
91
|
+
|
|
92
|
+
while (node !== null) {
|
|
93
|
+
const style = getComputedStyle(node);
|
|
94
|
+
if (style.backgroundImage !== 'none') {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const color = parseColor(style.backgroundColor);
|
|
98
|
+
if (color !== null && color.a > 0) {
|
|
99
|
+
layers.push(color);
|
|
100
|
+
if (color.a >= 1) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
node = node.parentElement;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const base = layers.pop();
|
|
108
|
+
if (base === undefined || base.a < 1) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
// layers is now top-first; composite downward so the topmost lands last.
|
|
112
|
+
return layers.reduceRight(
|
|
113
|
+
(backdrop, layer) => compositeOver(layer, backdrop),
|
|
114
|
+
base,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Contrast at which a surface's own ambient text counts as reading well
|
|
120
|
+
* enough that no media context is wanted.
|
|
121
|
+
*
|
|
122
|
+
* WCAG's non-text / large-text line. It is deliberately low: this decides
|
|
123
|
+
* whether a surface is *effectively inverted*, not whether its text meets a
|
|
124
|
+
* standard. A pairing that clears 3:1 is one the theme is rendering
|
|
125
|
+
* successfully, and swapping in a media context there changes accent,
|
|
126
|
+
* overlays and borders for no legibility gain.
|
|
127
|
+
*/
|
|
128
|
+
const AMBIENT_READS_AT = 3;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Which media context a surface wants: `'off'` when its ambient text already
|
|
132
|
+
* reads on the surface, otherwise whichever on-color reads better. Pure — the
|
|
133
|
+
* DOM read is the caller's. Ties between the two sides go to dark, matching
|
|
134
|
+
* the convention that an inverted surface is usually the dark one.
|
|
135
|
+
*
|
|
136
|
+
* The `'off'` answer is the point of measuring at all. A surface that a theme
|
|
137
|
+
* calls inverted but paints pale does not want an inverted context; its own
|
|
138
|
+
* text is already correct for it, and forcing one is how white-on-pale-grey
|
|
139
|
+
* happens.
|
|
140
|
+
*/
|
|
141
|
+
export function pickMediaMode(
|
|
142
|
+
background: RGBA,
|
|
143
|
+
ambient: RGBA | null,
|
|
144
|
+
onDark: RGBA | null,
|
|
145
|
+
onLight: RGBA | null,
|
|
146
|
+
): DetectedMediaMode | null {
|
|
147
|
+
if (onDark === null || onLight === null) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
ambient !== null &&
|
|
153
|
+
contrastRatio(ambient, background) >= AMBIENT_READS_AT
|
|
154
|
+
) {
|
|
155
|
+
return 'off';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return contrastRatio(onDark, background) >= contrastRatio(onLight, background)
|
|
159
|
+
? 'dark'
|
|
160
|
+
: 'light';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Detect the media context for the element's surroundings.
|
|
165
|
+
*
|
|
166
|
+
* Measures `ref`'s PARENT, never `ref` itself: MediaTheme sets its media
|
|
167
|
+
* attribute on its own element, so measuring that would feed the decision
|
|
168
|
+
* back into its own input and let it oscillate. The parent owns the
|
|
169
|
+
* background and sits outside the media context, so both inputs stay
|
|
170
|
+
* independent of the result.
|
|
171
|
+
*
|
|
172
|
+
* Returns null until a measurement succeeds — on the server, before the
|
|
173
|
+
* layout effect runs, and whenever the backdrop is not knowable from CSS.
|
|
174
|
+
* The caller renders its fallback for those.
|
|
175
|
+
*/
|
|
176
|
+
export function useAutoMediaMode(
|
|
177
|
+
ref: RefObject<HTMLElement | null>,
|
|
178
|
+
isEnabled: boolean,
|
|
179
|
+
): DetectedMediaMode | null {
|
|
180
|
+
const [detected, setDetected] = useState<DetectedMediaMode | null>(null);
|
|
181
|
+
// `tokens` is a stable reference unless the theme or color mode changes.
|
|
182
|
+
const {tokens} = useTheme();
|
|
183
|
+
const lastRef = useRef<{background: string; tokens: unknown} | null>(null);
|
|
184
|
+
|
|
185
|
+
// Deliberately no dependency array. A surface's color is a painted value:
|
|
186
|
+
// it changes when a prop on some ancestor changes, and no dependency this
|
|
187
|
+
// hook could name would catch that (an earlier revision took a `watch`
|
|
188
|
+
// array and silently went stale whenever a caller forgot an entry). So the
|
|
189
|
+
// measurement re-runs whenever anything re-renders, and skips its own
|
|
190
|
+
// expensive half when nothing moved — see the early return below.
|
|
191
|
+
useIsomorphicLayoutEffect(() => {
|
|
192
|
+
const surface = ref.current?.parentElement ?? null;
|
|
193
|
+
if (!isEnabled || surface === null) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const background = resolveBackdrop(surface);
|
|
198
|
+
if (background === null) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The backdrop walk above is a couple of getComputedStyle reads; the
|
|
203
|
+
// probe below appends a node and forces another style resolution. Skip
|
|
204
|
+
// it when neither the painted backdrop nor the theme has moved, which is
|
|
205
|
+
// the overwhelmingly common render.
|
|
206
|
+
const backgroundKey = formatColor(background);
|
|
207
|
+
const last = lastRef.current;
|
|
208
|
+
if (
|
|
209
|
+
last !== null &&
|
|
210
|
+
last.background === backgroundKey &&
|
|
211
|
+
last.tokens === tokens
|
|
212
|
+
) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
lastRef.current = {background: backgroundKey, tokens};
|
|
216
|
+
|
|
217
|
+
const [ambient, onDark, onLight] = resolvePaintedColors(surface, [
|
|
218
|
+
'--color-text-primary',
|
|
219
|
+
'--color-on-dark',
|
|
220
|
+
'--color-on-light',
|
|
221
|
+
]);
|
|
222
|
+
const next = pickMediaMode(background, ambient, onDark, onLight);
|
|
223
|
+
if (next === null) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect -- the mode is derived from painted DOM colors
|
|
228
|
+
setDetected(current => (current === next ? current : next));
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// A disabled hook must not answer with a value measured while it was on.
|
|
232
|
+
return isEnabled ? detected : null;
|
|
233
|
+
}
|
|
@@ -81,7 +81,10 @@ const styles = stylex.create({
|
|
|
81
81
|
border: 'none',
|
|
82
82
|
background: 'none',
|
|
83
83
|
color: 'inherit',
|
|
84
|
-
cursor:
|
|
84
|
+
cursor: {
|
|
85
|
+
default: 'pointer',
|
|
86
|
+
':is(:disabled,[aria-disabled="true"])': 'default',
|
|
87
|
+
},
|
|
85
88
|
// Own the interactivity so the tooltip opens on hover even when the field
|
|
86
89
|
// renders this affordance inside a non-interactive trailing slot. TextArea
|
|
87
90
|
// positions its end slot as an absolute overlay with `pointer-events: none`
|
|
@@ -36,7 +36,7 @@ export const docs = {
|
|
|
36
36
|
{
|
|
37
37
|
name: 'options.onEscape',
|
|
38
38
|
type: '() => void',
|
|
39
|
-
description: 'Callback when Escape key is pressed (e.g., close menu).',
|
|
39
|
+
description: 'Callback when Escape key is pressed (e.g., close menu). Supplying it also consumes the key (preventDefault); without it Escape passes through to the surrounding layer.',
|
|
40
40
|
required: false,
|
|
41
41
|
},
|
|
42
42
|
{
|
|
@@ -144,7 +144,7 @@ export const docsDense = {
|
|
|
144
144
|
'options.itemSelector': 'selector for focusable items in list.',
|
|
145
145
|
'options.boundarySelector': "boundary selector for lists that contain nested lists (e.g. submenu flyouts); scopes items + key handling to this level.",
|
|
146
146
|
'options.wrap': 'whether arrow navigation wraps around at ends.',
|
|
147
|
-
'options.onEscape': 'callback when Escape key pressed (e.g. close menu).',
|
|
147
|
+
'options.onEscape': 'callback when Escape key pressed (e.g. close menu). Also consumes the key; without it Escape passes through to the surrounding layer.',
|
|
148
148
|
'options.orientation': "navigation orientation. 'horizontal' uses ArrowLeft/ArrowRight, 'vertical' uses ArrowUp/ArrowDown, 'both' accepts all four arrows.",
|
|
149
149
|
'options.hasHomeEnd': 'whether Home/End jump to first/last enabled item.',
|
|
150
150
|
'options.isRtl': 'ArrowLeft/ArrowRight swap for horizontal nav (RTL). default: auto-detect from container computed direction; explicit boolean wins.',
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file useListFocus.test.tsx
|
|
5
5
|
* @input Uses vitest, @testing-library/react, useListFocus hook
|
|
6
|
-
* @output Unit tests for useListFocus disabled-item skipping, navigation,
|
|
7
|
-
* RTL auto-detection
|
|
6
|
+
* @output Unit tests for useListFocus disabled-item skipping, navigation,
|
|
7
|
+
* Escape consumption, and RTL auto-detection
|
|
8
8
|
* @position Testing; validates useListFocus.ts keyboard navigation
|
|
9
9
|
*
|
|
10
10
|
* SYNC: When useListFocus.ts changes, update tests to match new behavior
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
14
14
|
import type {KeyboardEvent as ReactKeyboardEvent} from 'react';
|
|
15
15
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
16
16
|
import {useListFocus} from './useListFocus';
|
|
@@ -525,3 +525,65 @@ describe('useListFocus boundarySelector (nested lists)', () => {
|
|
|
525
525
|
expect(innerProbe).toHaveAttribute('data-owns', 'false');
|
|
526
526
|
});
|
|
527
527
|
});
|
|
528
|
+
|
|
529
|
+
// A list inside a host that dismisses on Escape. The host's guard mirrors
|
|
530
|
+
// `useFocusTrap`: it acts only on a key no inner handler has consumed.
|
|
531
|
+
function EscapeHost({
|
|
532
|
+
onEscape,
|
|
533
|
+
onHostEscape,
|
|
534
|
+
}: {
|
|
535
|
+
onEscape?: () => void;
|
|
536
|
+
onHostEscape: () => void;
|
|
537
|
+
}) {
|
|
538
|
+
const {listRef, handleKeyDown} = useListFocus<HTMLDivElement>({onEscape});
|
|
539
|
+
return (
|
|
540
|
+
<div
|
|
541
|
+
data-testid="host"
|
|
542
|
+
onKeyDown={e => {
|
|
543
|
+
if (e.key === 'Escape' && !e.defaultPrevented) {
|
|
544
|
+
onHostEscape();
|
|
545
|
+
}
|
|
546
|
+
}}>
|
|
547
|
+
<div ref={listRef} role="menu" onKeyDown={handleKeyDown}>
|
|
548
|
+
<div role="menuitem" tabIndex={-1} data-testid="One">
|
|
549
|
+
One
|
|
550
|
+
</div>
|
|
551
|
+
<div role="menuitem" tabIndex={-1} data-testid="Two">
|
|
552
|
+
Two
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
describe('useListFocus Escape', () => {
|
|
560
|
+
it('leaves Escape to the host when no onEscape is supplied', () => {
|
|
561
|
+
const onHostEscape = vi.fn();
|
|
562
|
+
render(<EscapeHost onHostEscape={onHostEscape} />);
|
|
563
|
+
|
|
564
|
+
fireEvent.keyDown(screen.getByRole('menu'), {key: 'Escape'});
|
|
565
|
+
expect(onHostEscape).toHaveBeenCalledTimes(1);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('consumes Escape and runs onEscape when one is supplied', () => {
|
|
569
|
+
const onEscape = vi.fn();
|
|
570
|
+
const onHostEscape = vi.fn();
|
|
571
|
+
render(<EscapeHost onEscape={onEscape} onHostEscape={onHostEscape} />);
|
|
572
|
+
|
|
573
|
+
fireEvent.keyDown(screen.getByRole('menu'), {key: 'Escape'});
|
|
574
|
+
expect(onEscape).toHaveBeenCalledTimes(1);
|
|
575
|
+
expect(onHostEscape).not.toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('still consumes arrow keys with no onEscape (page-scroll suppression)', () => {
|
|
579
|
+
render(<EscapeHost onHostEscape={() => {}} />);
|
|
580
|
+
screen.getByTestId('One').focus();
|
|
581
|
+
|
|
582
|
+
// fireEvent returns false when a handler cancelled the event.
|
|
583
|
+
const wasCancelled = !fireEvent.keyDown(screen.getByRole('menu'), {
|
|
584
|
+
key: 'ArrowDown',
|
|
585
|
+
});
|
|
586
|
+
expect(wasCancelled).toBe(true);
|
|
587
|
+
expect(screen.getByTestId('Two')).toHaveFocus();
|
|
588
|
+
});
|
|
589
|
+
});
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*
|
|
15
15
|
* SYNC: When modified, update:
|
|
16
16
|
* - /packages/core/src/hooks/index.ts
|
|
17
|
+
* - /packages/core/src/hooks/useListFocus.doc.mjs
|
|
18
|
+
* - /packages/core/src/hooks/useListFocus.test.tsx
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import {useCallback, useRef} from 'react';
|
|
@@ -64,7 +66,9 @@ export interface UseListFocusOptions {
|
|
|
64
66
|
wrap?: boolean;
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
|
-
* Callback when Escape key is pressed.
|
|
69
|
+
* Callback when Escape key is pressed. Supplying it also makes the list
|
|
70
|
+
* consume the key (`preventDefault`); without it Escape passes through to
|
|
71
|
+
* the surrounding layer.
|
|
68
72
|
*/
|
|
69
73
|
onEscape?: () => void;
|
|
70
74
|
|
|
@@ -277,7 +281,8 @@ function shouldDeferToCaret(target: EventTarget | null, key: string): boolean {
|
|
|
277
281
|
* - ArrowUp/ArrowLeft: Move to previous item (wraps to last)
|
|
278
282
|
* - Home: Move to first item
|
|
279
283
|
* - End: Move to last item
|
|
280
|
-
* - Escape:
|
|
284
|
+
* - Escape: runs `onEscape` and consumes the key. With no `onEscape` the key
|
|
285
|
+
* is left alone, so a surrounding layer can still dismiss on it.
|
|
281
286
|
*
|
|
282
287
|
* By default the hook only *moves* focus and leaves `tabindex` management to
|
|
283
288
|
* the caller. Opt into {@link UseListFocusOptions.hasRovingTabIndex} for a hook
|
|
@@ -557,12 +562,15 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
557
562
|
return;
|
|
558
563
|
}
|
|
559
564
|
|
|
560
|
-
// Escape is handled regardless of orientation
|
|
561
|
-
//
|
|
562
|
-
//
|
|
565
|
+
// Escape is handled regardless of orientation, but only *consumed* when
|
|
566
|
+
// a handler asked for it: a list with no dismissal to perform must leave
|
|
567
|
+
// the key to whatever host layer does have one, and those defer to
|
|
568
|
+
// `defaultPrevented` (see `useFocusTrap`) or to the native popover.
|
|
563
569
|
if (e.key === 'Escape') {
|
|
564
|
-
|
|
565
|
-
|
|
570
|
+
if (onEscape) {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
onEscape();
|
|
573
|
+
}
|
|
566
574
|
return;
|
|
567
575
|
}
|
|
568
576
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export const docs = {
|
|
5
5
|
name: 'useScrollLock',
|
|
6
6
|
displayName: 'useScrollLock',
|
|
7
|
-
keywords: ['scroll', 'lock', 'modal', 'dialog', 'body', 'prevent', 'background', 'ios', 'safari', 'fixed'],
|
|
7
|
+
keywords: ['scroll', 'lock', 'modal', 'dialog', 'body', 'prevent', 'background', 'ios', 'safari', 'fixed', 'scrollbar', 'gutter', 'layout shift', 'reflow'],
|
|
8
8
|
params: [
|
|
9
9
|
{
|
|
10
10
|
name: 'isLocked',
|
|
@@ -16,7 +16,7 @@ export const docs = {
|
|
|
16
16
|
returns: [],
|
|
17
17
|
usage: {
|
|
18
18
|
description:
|
|
19
|
-
'Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked.',
|
|
19
|
+
'Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked. Pinning hides the document scrollbar, so where that scrollbar takes layout space (desktop) the hook holds its gutter open with scrollbar-gutter: stable for the duration of the lock. The page, including any position: fixed chrome, does not shift sideways.',
|
|
20
20
|
bestPractices: [
|
|
21
21
|
{ guidance: true, description: 'Use when opening full-screen modals or dialogs to prevent background content from scrolling.' },
|
|
22
22
|
{ guidance: true, description: 'Pass the same boolean that controls dialog visibility (e.g., isOpen) as the isLocked parameter.' },
|
|
@@ -32,13 +32,13 @@ export const docs = {
|
|
|
32
32
|
/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
|
|
33
33
|
export const docsDense = {
|
|
34
34
|
description:
|
|
35
|
-
'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked.',
|
|
35
|
+
'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked. Holds the hidden scrollbar\'s gutter open (scrollbar-gutter: stable) so the page (incl. position: fixed chrome) does not shift sideways.',
|
|
36
36
|
paramDescriptions: {
|
|
37
37
|
isLocked: 'whether body scroll should be locked.',
|
|
38
38
|
},
|
|
39
39
|
usage: {
|
|
40
40
|
description:
|
|
41
|
-
'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked.',
|
|
41
|
+
'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked. Holds the hidden scrollbar\'s gutter open (scrollbar-gutter: stable) so the page (incl. position: fixed chrome) does not shift sideways.',
|
|
42
42
|
bestPractices: [
|
|
43
43
|
{ guidance: true, description: 'Use when opening full-screen modals / dialogs to prevent background content from scrolling.' },
|
|
44
44
|
{ guidance: true, description: 'Pass same boolean that controls dialog visibility (e.g. isOpen) as isLocked parameter.' },
|