@astryxdesign/core 0.4.4 → 0.4.5-canary.0472ec1
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 +40 -0
- package/dist/AvatarGroup/AvatarGroupOverflow.js +1 -1
- 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/BottomSheet.d.ts +11 -2
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +77 -18
- package/dist/BottomSheet/index.d.ts +1 -1
- package/dist/BottomSheet/index.d.ts.map +1 -1
- package/dist/BottomSheet/snapOffsets.d.ts +57 -20
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
- package/dist/BottomSheet/snapOffsets.js +106 -30
- package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +160 -64
- package/dist/Breadcrumbs/BreadcrumbItem.js +6 -6
- package/dist/Button/Button.js +5 -5
- package/dist/Calendar/Calendar.d.ts +17 -0
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +55 -15
- package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
- package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
- package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
- package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarDays.js +14 -10
- package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
- package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
- package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
- package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
- package/dist/Calendar/styles.js +3 -3
- 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 +2 -2
- package/dist/Chat/ChatToolCalls.js +1 -1
- 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/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +9 -0
- package/dist/Citation/Citation.js +3 -3
- package/dist/ClickableCard/ClickableCard.js +2 -2
- package/dist/CommandPalette/CommandPaletteItem.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +8 -3
- package/dist/DateInput/DateInput.d.ts +31 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +92 -19
- package/dist/DateInput/MonthScroller.d.ts +35 -0
- package/dist/DateInput/MonthScroller.d.ts.map +1 -0
- package/dist/DateInput/MonthScroller.js +573 -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 +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +47 -9
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +18 -10
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +3 -3
- package/dist/Field/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +21 -3
- package/dist/Field/InputClearButton.d.ts +2 -1
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +12 -1
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +4 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +2 -2
- package/dist/FormLayout/FormLayout.d.ts +24 -2
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +7 -2
- package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
- package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
- package/dist/FormLayout/FormLayoutContext.js +19 -4
- package/dist/FormLayout/index.d.ts +1 -1
- package/dist/FormLayout/index.d.ts.map +1 -1
- 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/Item/Item.d.ts +9 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +46 -8
- 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 +2 -1
- package/dist/Link/Link.js +4 -4
- package/dist/Markdown/Markdown.d.ts +8 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +31 -3
- package/dist/Markdown/parser.d.ts +14 -2
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +50 -2
- 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 -3
- package/dist/NavItem/navItemStyles.stylex.d.ts +2 -2
- package/dist/NavItem/navItemStyles.stylex.js +2 -2
- package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +38 -5
- package/dist/Outline/Outline.js +2 -2
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
- 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/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +12 -1
- package/dist/RadioList/RadioListItem.d.ts +2 -2
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +68 -38
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +3 -3
- 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 +6 -1
- package/dist/SelectableCard/SelectableCard.js +1 -1
- package/dist/Selector/Selector.d.ts +26 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +95 -17
- 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.js +1 -1
- package/dist/SideNav/SideNavItem.js +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +3 -3
- 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/StatusDot/StatusDot.d.ts +43 -3
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +42 -5
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +9 -0
- package/dist/TabList/TabMenu.js +1 -1
- 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/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- 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.js +2 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +6 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +6 -1
- package/dist/Thumbnail/Thumbnail.js +1 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- 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/Token/Token.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.js +2 -2
- package/dist/TopNav/TopNavItem.js +2 -2
- package/dist/TopNav/TopNavMegaMenu.js +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
- package/dist/TopNav/TopNavMenu.js +2 -2
- package/dist/TreeList/TreeListItem.js +1 -1
- package/dist/astryx.css +123 -60
- 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/useResolvedRequired.d.ts +19 -0
- package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
- package/dist/hooks/useResolvedRequired.js +40 -0
- 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/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/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 +11 -2
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +32 -4
- package/locales/af-ZA.json +958 -0
- package/locales/ar-SA.json +998 -0
- package/locales/ca-ES.json +974 -0
- package/locales/cs-CZ.json +990 -0
- package/locales/da-DK.json +966 -0
- package/locales/de-DE.json +966 -0
- package/locales/el-GR.json +990 -0
- package/locales/en.json +32 -0
- package/locales/es-ES.json +978 -0
- package/locales/fi-FI.json +990 -0
- package/locales/fr-FR.json +928 -0
- package/locales/he-IL.json +990 -0
- package/locales/hu-HU.json +990 -0
- package/locales/it-IT.json +978 -0
- package/locales/ja-JP.json +998 -0
- package/locales/ko-KR.json +990 -0
- package/locales/nl-NL.json +954 -0
- package/locales/no-NO.json +978 -0
- package/locales/pl-PL.json +982 -0
- package/locales/pseudo.json +24 -0
- package/locales/pt-BR.json +982 -0
- package/locales/pt-PT.json +982 -0
- package/locales/ro-RO.json +982 -0
- package/locales/ru-RU.json +990 -0
- package/locales/sr-SP.json +990 -0
- package/locales/sv-SE.json +986 -0
- package/locales/tr-TR.json +990 -0
- package/locales/uk-UA.json +990 -0
- package/locales/vi-VN.json +986 -0
- package/locales/zh-CN.json +998 -0
- package/locales/zh-TW.json +998 -0
- package/package.json +3 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +1 -1
- 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 +36 -3
- package/src/BottomSheet/BottomSheet.test.tsx +177 -2
- package/src/BottomSheet/BottomSheet.tsx +16 -2
- package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +121 -15
- package/src/BottomSheet/index.ts +5 -1
- package/src/BottomSheet/snapOffsets.test.ts +114 -47
- package/src/BottomSheet/snapOffsets.ts +119 -33
- package/src/BottomSheet/useSheetGestures.test.ts +113 -15
- package/src/BottomSheet/useSheetGestures.ts +196 -100
- package/src/Breadcrumbs/BreadcrumbItem.tsx +1 -1
- package/src/Button/Button.tsx +7 -7
- package/src/Calendar/Calendar.doc.mjs +16 -0
- package/src/Calendar/Calendar.test.tsx +137 -0
- package/src/Calendar/Calendar.tsx +80 -11
- package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
- package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
- package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
- package/src/Calendar/hooks/useCalendarDays.ts +13 -10
- package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
- package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
- package/src/Calendar/styles.ts +3 -3
- 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 +7 -4
- package/src/Chat/ChatToolCalls.tsx +1 -1
- 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/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/Citation/Citation.tsx +3 -3
- package/src/ClickableCard/ClickableCard.tsx +2 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
- package/src/ComplexSelector/ComplexSelector.tsx +8 -5
- package/src/DateInput/DateInput.test.tsx +17 -1
- package/src/DateInput/DateInput.tsx +90 -19
- package/src/DateInput/DateInputTouch.test.tsx +2393 -0
- package/src/DateInput/MonthScroller.tsx +784 -0
- package/src/DateInput/MonthYearWheels.tsx +147 -0
- package/src/DateInput/TouchDateField.tsx +1214 -0
- package/src/DateInput/Wheel.tsx +470 -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 +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
- package/src/DateRangeInput/DateRangeInput.tsx +88 -8
- package/src/DateTimeInput/DateTimeInput.tsx +23 -8
- package/src/Dialog/Dialog.test.tsx +24 -0
- package/src/Dialog/Dialog.tsx +3 -0
- package/src/Field/Field.doc.mjs +3 -0
- package/src/Field/FieldLabel.tsx +20 -4
- package/src/Field/InputClearButton.test.tsx +134 -2
- package/src/Field/InputClearButton.tsx +42 -1
- package/src/Field/inputStyles.stylex.ts +16 -12
- package/src/FileInput/FileInput.tsx +5 -4
- package/src/FormLayout/FormLayout.doc.mjs +13 -0
- package/src/FormLayout/FormLayout.test.tsx +181 -4
- package/src/FormLayout/FormLayout.tsx +33 -2
- package/src/FormLayout/FormLayoutContext.ts +22 -7
- package/src/FormLayout/index.ts +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/Item/Item.doc.mjs +1 -0
- package/src/Item/Item.test.tsx +50 -0
- package/src/Item/Item.tsx +41 -2
- 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 +2 -1
- package/src/Link/Link.tsx +4 -4
- package/src/Markdown/Markdown.doc.mjs +3 -0
- package/src/Markdown/Markdown.test.tsx +78 -0
- package/src/Markdown/Markdown.tsx +41 -1
- package/src/Markdown/parser.ts +60 -2
- 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 +8 -5
- package/src/NavItem/navItemStyles.stylex.ts +2 -2
- package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
- package/src/NumberInput/NumberInput.doc.mjs +24 -0
- package/src/NumberInput/NumberInput.test.tsx +138 -0
- package/src/NumberInput/NumberInput.tsx +44 -6
- package/src/Outline/Outline.doc.mjs +2 -0
- package/src/Outline/Outline.tsx +1 -1
- package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
- 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/RadioList.tsx +9 -1
- package/src/RadioList/RadioListItem.tsx +83 -51
- package/src/Resizable/ResizeHandle.test.tsx +22 -0
- package/src/Resizable/ResizeHandle.tsx +9 -3
- 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 +11 -2
- package/src/SelectableCard/SelectableCard.tsx +1 -1
- package/src/Selector/Selector.doc.mjs +16 -2
- package/src/Selector/Selector.test.tsx +525 -1
- package/src/Selector/Selector.tsx +166 -21
- 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 +2 -2
- package/src/SideNav/SideNavItem.tsx +1 -1
- package/src/Slider/Slider.tsx +11 -1
- 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/StatusDot/StatusDot.doc.mjs +13 -0
- package/src/StatusDot/StatusDot.test.tsx +115 -8
- package/src/StatusDot/StatusDot.tsx +74 -9
- package/src/Switch/Switch.tsx +6 -0
- package/src/TabList/TabMenu.tsx +1 -1
- package/src/Table/TableRow.tsx +4 -4
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +2 -1
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +2 -1
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +22 -4
- 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 +2 -2
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.tsx +3 -1
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/Thumbnail/Thumbnail.tsx +1 -1
- package/src/TimeInput/TimeInput.tsx +3 -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/Token/Token.tsx +1 -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 +1 -1
- package/src/TopNav/TopNavItem.tsx +2 -2
- package/src/TopNav/TopNavMegaMenu.tsx +1 -1
- package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
- package/src/TopNav/TopNavMenu.tsx +2 -2
- package/src/TreeList/TreeListItem.tsx +1 -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/useResolvedRequired.ts +42 -0
- 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/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/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 +56 -0
- package/src/utils/plainDate.ts +33 -4
|
@@ -53,6 +53,9 @@ import {
|
|
|
53
53
|
parseMarkdownIncremental,
|
|
54
54
|
createIncrementalState,
|
|
55
55
|
trimStreamingArtifacts,
|
|
56
|
+
inlineText,
|
|
57
|
+
slugify,
|
|
58
|
+
uniqueSlug,
|
|
56
59
|
} from './parser';
|
|
57
60
|
import type {BlockNode, InlineNode, IncrementalState} from './parser';
|
|
58
61
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -108,6 +111,14 @@ export interface MarkdownComponents {
|
|
|
108
111
|
heading?: React.ComponentType<{
|
|
109
112
|
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
110
113
|
children: React.ReactNode;
|
|
114
|
+
/**
|
|
115
|
+
* Generated slug for this heading, matching the ids produced by
|
|
116
|
+
* useOutlineFromMarkdown / parseOutlineFromMarkdown. Render it as the
|
|
117
|
+
* element's `id` to keep Outline hash navigation working. Undefined for
|
|
118
|
+
* headings nested inside blockquotes or list items (the outline only
|
|
119
|
+
* lists top-level headings).
|
|
120
|
+
*/
|
|
121
|
+
id?: string;
|
|
111
122
|
}>;
|
|
112
123
|
paragraph?: React.ComponentType<{children: React.ReactNode}>;
|
|
113
124
|
image?: React.ComponentType<{src: string; alt: string}>;
|
|
@@ -1045,6 +1056,7 @@ function renderBlock(
|
|
|
1045
1056
|
inlinePlugins: MarkdownInlinePlugin[] | undefined,
|
|
1046
1057
|
components: Partial<MarkdownComponents> | undefined,
|
|
1047
1058
|
t: TranslatorFn,
|
|
1059
|
+
headingIdMap?: ReadonlyMap<BlockNode, string>,
|
|
1048
1060
|
): SyncReactNode {
|
|
1049
1061
|
const blockAlignMargin = BLOCK_ALIGN_MARGIN[contentAlign];
|
|
1050
1062
|
const blockAlignStyle =
|
|
@@ -1071,10 +1083,15 @@ function renderBlock(
|
|
|
1071
1083
|
components,
|
|
1072
1084
|
),
|
|
1073
1085
|
);
|
|
1086
|
+
// Only top-level headings get an id: the map is built from the same
|
|
1087
|
+
// traversal parseOutlineFromMarkdown uses (which skips headings nested
|
|
1088
|
+
// in blockquotes / list items), so rendered ids and outline ids stay
|
|
1089
|
+
// identical — including duplicate-slug numbering.
|
|
1090
|
+
const headingId = headingIdMap?.get(node);
|
|
1074
1091
|
const HeadingComp = components?.heading;
|
|
1075
1092
|
if (HeadingComp) {
|
|
1076
1093
|
return (
|
|
1077
|
-
<HeadingComp key={index} level={level}>
|
|
1094
|
+
<HeadingComp key={index} level={level} id={headingId}>
|
|
1078
1095
|
{headingChildren}
|
|
1079
1096
|
</HeadingComp>
|
|
1080
1097
|
);
|
|
@@ -1083,6 +1100,7 @@ function renderBlock(
|
|
|
1083
1100
|
return (
|
|
1084
1101
|
<Tag
|
|
1085
1102
|
key={index}
|
|
1103
|
+
id={headingId}
|
|
1086
1104
|
{...mergeProps(
|
|
1087
1105
|
themeProps('markdown-heading', {density, level}),
|
|
1088
1106
|
stylex.props(
|
|
@@ -1652,6 +1670,27 @@ export function Markdown({
|
|
|
1652
1670
|
return parseMarkdown(children, parseOptions);
|
|
1653
1671
|
}, [display, smoothedText, children, isStreaming, parseOptions]);
|
|
1654
1672
|
|
|
1673
|
+
// Assign each top-level heading the slug that parseOutlineFromMarkdown
|
|
1674
|
+
// would derive for it, so Outline hash links built from the same source
|
|
1675
|
+
// always find a matching DOM id. Mirrors that function's traversal exactly:
|
|
1676
|
+
// top-level blocks only, one shared duplicate-numbering sequence.
|
|
1677
|
+
// NOTE: must stay above the `display === 'inline'` early return below —
|
|
1678
|
+
// hooks cannot be conditional.
|
|
1679
|
+
const headingIdMap = useMemo(() => {
|
|
1680
|
+
if (display === 'inline' || blocks.length === 0) {
|
|
1681
|
+
return undefined;
|
|
1682
|
+
}
|
|
1683
|
+
const map = new Map<BlockNode, string>();
|
|
1684
|
+
const counts = new Map<string, number>();
|
|
1685
|
+
for (const block of blocks) {
|
|
1686
|
+
if (block.type === 'heading') {
|
|
1687
|
+
const label = inlineText(block.children).trim();
|
|
1688
|
+
map.set(block, uniqueSlug(slugify(label), counts));
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return map;
|
|
1692
|
+
}, [display, blocks]);
|
|
1693
|
+
|
|
1655
1694
|
const inlineNodes = useMemo(() => {
|
|
1656
1695
|
if (display !== 'inline') {
|
|
1657
1696
|
return [];
|
|
@@ -1765,6 +1804,7 @@ export function Markdown({
|
|
|
1765
1804
|
inlinePlugins,
|
|
1766
1805
|
components,
|
|
1767
1806
|
t,
|
|
1807
|
+
headingIdMap,
|
|
1768
1808
|
),
|
|
1769
1809
|
)}
|
|
1770
1810
|
</div>
|
package/src/Markdown/parser.ts
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file parser.ts
|
|
5
5
|
* @input Markdown string
|
|
6
|
-
* @output Array of MarkdownNode AST nodes
|
|
7
|
-
*
|
|
6
|
+
* @output Array of MarkdownNode AST nodes; heading slug helpers
|
|
7
|
+
* (inlineText, slugify, uniqueSlug) shared by Markdown rendering and
|
|
8
|
+
* Outline's parseOutlineFromMarkdown
|
|
9
|
+
* @position Core parser; consumed by Markdown.tsx and Outline
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
@@ -1815,3 +1817,59 @@ export function parseMarkdownIncremental(
|
|
|
1815
1817
|
|
|
1816
1818
|
return mergeSettledBlocks(settledBlocks, unsettledBlocks);
|
|
1817
1819
|
}
|
|
1820
|
+
|
|
1821
|
+
// ---------------------------------------------------------------------------
|
|
1822
|
+
// Heading slugs
|
|
1823
|
+
// ---------------------------------------------------------------------------
|
|
1824
|
+
// Single source of truth for the heading id contract: Markdown renders these
|
|
1825
|
+
// slugs as `id` attributes on h1–h6, and Outline's parseOutlineFromMarkdown
|
|
1826
|
+
// derives its item ids from the same functions, so outline hash links always
|
|
1827
|
+
// resolve to a rendered heading by construction.
|
|
1828
|
+
|
|
1829
|
+
/** Flatten inline nodes into their plain text content. */
|
|
1830
|
+
export function inlineText(nodes: InlineNode[]): string {
|
|
1831
|
+
return nodes
|
|
1832
|
+
.map(node => {
|
|
1833
|
+
switch (node.type) {
|
|
1834
|
+
case 'text':
|
|
1835
|
+
case 'code':
|
|
1836
|
+
return node.content;
|
|
1837
|
+
case 'bold':
|
|
1838
|
+
case 'italic':
|
|
1839
|
+
case 'strikethrough':
|
|
1840
|
+
case 'link':
|
|
1841
|
+
return inlineText(node.children);
|
|
1842
|
+
case 'image':
|
|
1843
|
+
return node.alt;
|
|
1844
|
+
case 'citation':
|
|
1845
|
+
case 'break':
|
|
1846
|
+
return '';
|
|
1847
|
+
}
|
|
1848
|
+
})
|
|
1849
|
+
.join('');
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
/** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
|
|
1853
|
+
export function slugify(value: string): string {
|
|
1854
|
+
return value
|
|
1855
|
+
.trim()
|
|
1856
|
+
.toLowerCase()
|
|
1857
|
+
.replace(/['"]/g, '')
|
|
1858
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1859
|
+
.replace(/^-+|-+$/g, '');
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
/**
|
|
1863
|
+
* Disambiguate repeated slugs with a numeric suffix (`setup`, `setup-1`, …).
|
|
1864
|
+
* Empty slugs fall back to `section`. The caller owns the counts map so one
|
|
1865
|
+
* document shares a single numbering sequence.
|
|
1866
|
+
*/
|
|
1867
|
+
export function uniqueSlug(
|
|
1868
|
+
baseSlug: string,
|
|
1869
|
+
counts: Map<string, number>,
|
|
1870
|
+
): string {
|
|
1871
|
+
const fallbackSlug = baseSlug || 'section';
|
|
1872
|
+
const count = counts.get(fallbackSlug) ?? 0;
|
|
1873
|
+
counts.set(fallbackSlug, count + 1);
|
|
1874
|
+
return count === 0 ? fallbackSlug : `${fallbackSlug}-${count}`;
|
|
1875
|
+
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* SYNC: When modified, update these files to stay in sync:
|
|
25
25
|
* - /packages/core/src/MobileNav/index.ts (exports if types change)
|
|
26
|
+
* - /packages/core/src/hooks/scrollbarGutter.ts (shared scroll-lock gutter)
|
|
26
27
|
* - /packages/cli/assets/templates/blocks/components/MobileNav/ (showcase blocks)
|
|
27
28
|
*/
|
|
28
29
|
|
|
@@ -47,7 +48,12 @@ import {Button} from '../Button';
|
|
|
47
48
|
import {Icon} from '../Icon';
|
|
48
49
|
import {Heading} from '../Heading/Heading';
|
|
49
50
|
import {useAppShellMobile} from '../AppShell/AppShellMobileContext';
|
|
51
|
+
import {
|
|
52
|
+
holdScrollbarGutter,
|
|
53
|
+
type ScrollbarGutterHold,
|
|
54
|
+
} from '../hooks/scrollbarGutter';
|
|
50
55
|
import {mergeProps, mergeRefs, composeEventHandlers} from '../utils';
|
|
56
|
+
import {overlayPaddingReset} from '../Layout/padding.stylex';
|
|
51
57
|
import type {BaseProps} from '../BaseProps';
|
|
52
58
|
import {themeProps} from '../utils/themeProps';
|
|
53
59
|
import {useTranslator} from '../i18n';
|
|
@@ -70,7 +76,16 @@ const styles = stylex.create({
|
|
|
70
76
|
width: '100vw',
|
|
71
77
|
height: '100dvh',
|
|
72
78
|
backgroundColor: 'transparent',
|
|
73
|
-
|
|
79
|
+
// `clip`, not `hidden`. Both clip the off-screen drawer, but `hidden` makes
|
|
80
|
+
// the dialog a SCROLL CONTAINER, and a scroll container in the top layer
|
|
81
|
+
// whose subtree holds another scroller (the drawer's content area) does not
|
|
82
|
+
// paint a @starting-style entry transition for its descendants in Chromium:
|
|
83
|
+
// the transition ticks in the CSSOM while every painted frame shows the
|
|
84
|
+
// end value, so the drawer appears fully open. `clip` clips without
|
|
85
|
+
// creating a scroll container and the slide-in paints normally. The dialog
|
|
86
|
+
// never scrolls anyway — its child is absolutely positioned — so nothing
|
|
87
|
+
// depended on it being a scroll container.
|
|
88
|
+
overflow: 'clip',
|
|
74
89
|
overscrollBehavior: 'contain',
|
|
75
90
|
// Prevent touch gestures (pull-to-refresh, background scroll) passing through
|
|
76
91
|
touchAction: 'none',
|
|
@@ -112,7 +127,14 @@ const styles = stylex.create({
|
|
|
112
127
|
},
|
|
113
128
|
backdropOpen: {
|
|
114
129
|
'::backdrop': {
|
|
115
|
-
|
|
130
|
+
// The ::backdrop only exists once showModal() has put the dialog in the
|
|
131
|
+
// top layer, so its first rendered frame already has the open opacity.
|
|
132
|
+
// Without a starting style there is no earlier value to transition from
|
|
133
|
+
// and the scrim snaps in — @starting-style supplies that value.
|
|
134
|
+
opacity: {
|
|
135
|
+
default: 1,
|
|
136
|
+
'@starting-style': 0,
|
|
137
|
+
},
|
|
116
138
|
},
|
|
117
139
|
},
|
|
118
140
|
drawer: {
|
|
@@ -143,7 +165,17 @@ const styles = stylex.create({
|
|
|
143
165
|
},
|
|
144
166
|
},
|
|
145
167
|
drawerStartOpen: {
|
|
146
|
-
|
|
168
|
+
// The whole dialog is `display: none` while closed, so the drawer is not
|
|
169
|
+
// rendered and the open transform is the only value it has ever had — a
|
|
170
|
+
// transition needs a previous value to run from. @starting-style gives the
|
|
171
|
+
// first rendered frame the off-screen transform, so the slide-in plays.
|
|
172
|
+
transform: {
|
|
173
|
+
default: 'translateX(0)',
|
|
174
|
+
'@starting-style': {
|
|
175
|
+
default: 'translateX(-100%)',
|
|
176
|
+
':is([dir="rtl"] *)': 'translateX(100%)',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
147
179
|
},
|
|
148
180
|
drawerEnd: {
|
|
149
181
|
insetInlineEnd: 0,
|
|
@@ -156,7 +188,14 @@ const styles = stylex.create({
|
|
|
156
188
|
},
|
|
157
189
|
},
|
|
158
190
|
drawerEndOpen: {
|
|
159
|
-
|
|
191
|
+
// See drawerStartOpen — same starting style, mirrored edge.
|
|
192
|
+
transform: {
|
|
193
|
+
default: 'translateX(0)',
|
|
194
|
+
'@starting-style': {
|
|
195
|
+
default: 'translateX(100%)',
|
|
196
|
+
':is([dir="rtl"] *)': 'translateX(-100%)',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
160
199
|
},
|
|
161
200
|
header: {
|
|
162
201
|
display: 'flex',
|
|
@@ -396,6 +435,15 @@ export function MobileNav({
|
|
|
396
435
|
|
|
397
436
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
398
437
|
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
|
438
|
+
const gutterRef = useRef<ScrollbarGutterHold | null>(null);
|
|
439
|
+
|
|
440
|
+
// Gives back the gutter held open in place of the hidden scrollbar.
|
|
441
|
+
const releaseGutter = useCallback(() => {
|
|
442
|
+
if (gutterRef.current) {
|
|
443
|
+
gutterRef.current.release();
|
|
444
|
+
gutterRef.current = null;
|
|
445
|
+
}
|
|
446
|
+
}, []);
|
|
399
447
|
// Resolved side — computed from trigger position when side='auto'
|
|
400
448
|
const [resolvedSide, setResolvedSide] = useState<'start' | 'end'>(
|
|
401
449
|
side === 'auto' ? 'end' : side,
|
|
@@ -438,6 +486,10 @@ export function MobileNav({
|
|
|
438
486
|
}
|
|
439
487
|
|
|
440
488
|
if (isOpen) {
|
|
489
|
+
// Taken first: every mutation below is one that can hide the scrollbar,
|
|
490
|
+
// and the gutter has to be measured while it is still there.
|
|
491
|
+
gutterRef.current ??= holdScrollbarGutter(document.documentElement);
|
|
492
|
+
|
|
441
493
|
if (!dialog.open) {
|
|
442
494
|
dialog.showModal();
|
|
443
495
|
}
|
|
@@ -445,8 +497,10 @@ export function MobileNav({
|
|
|
445
497
|
// overflow: clip avoids creating a scroll container (unlike hidden),
|
|
446
498
|
// so there's no scroll bounce and no need to save/restore scroll position.
|
|
447
499
|
document.documentElement.style.overflow = 'clip';
|
|
500
|
+
gutterRef.current.settle();
|
|
448
501
|
} else if (dialog.open) {
|
|
449
502
|
document.documentElement.style.overflow = '';
|
|
503
|
+
releaseGutter();
|
|
450
504
|
|
|
451
505
|
closeTimeoutRef.current = setTimeout(() => {
|
|
452
506
|
dialog.close();
|
|
@@ -459,8 +513,9 @@ export function MobileNav({
|
|
|
459
513
|
closeTimeoutRef.current = null;
|
|
460
514
|
}
|
|
461
515
|
document.documentElement.style.overflow = '';
|
|
516
|
+
releaseGutter();
|
|
462
517
|
};
|
|
463
|
-
}, [isOpen]);
|
|
518
|
+
}, [isOpen, releaseGutter]);
|
|
464
519
|
|
|
465
520
|
// Close the native dialog on unmount if it's still open. Inside AppShell the
|
|
466
521
|
// drawer is mounted in an <Activity> that switches to mode="hidden" when the
|
|
@@ -512,6 +567,7 @@ export function MobileNav({
|
|
|
512
567
|
themeProps('mobile-nav', {side: resolvedSide}),
|
|
513
568
|
stylex.props(
|
|
514
569
|
styles.dialog,
|
|
570
|
+
overlayPaddingReset.reset,
|
|
515
571
|
isOpen && styles.open,
|
|
516
572
|
styles.backdrop,
|
|
517
573
|
isOpen && styles.backdropOpen,
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file MobileNavEntryAnimation.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, the TypeScript compiler API
|
|
6
|
+
* @output Regression tests keeping the drawer's slide-in and scrim fade-in
|
|
7
|
+
* @position Testing; guards the open path of MobileNav.tsx
|
|
8
|
+
*
|
|
9
|
+
* The drawer used to open with no animation at all while closing smoothly.
|
|
10
|
+
*
|
|
11
|
+
* The dialog is `display: none` until `isOpen`, so nothing inside it is
|
|
12
|
+
* rendered while closed. React commits the open `display` and the open
|
|
13
|
+
* `transform` in the same pass, which means the first frame the drawer is ever
|
|
14
|
+
* rendered in already holds the open transform: a transition has no earlier
|
|
15
|
+
* value to run from and the drawer simply appears. Closing looked fine because
|
|
16
|
+
* both values exist by then — `display` stays in the transition with
|
|
17
|
+
* `allow-discrete`, so the element is still rendered as the transform animates
|
|
18
|
+
* out.
|
|
19
|
+
*
|
|
20
|
+
* `@starting-style` supplies that before-change style — the off-screen
|
|
21
|
+
* transform for the drawer, transparent for the `::backdrop`.
|
|
22
|
+
*
|
|
23
|
+
* That alone is NOT enough, and the second half is the part that is easy to
|
|
24
|
+
* undo by accident. The dialog used `overflow: hidden`, which makes it a
|
|
25
|
+
* SCROLL CONTAINER. A scroll container in the top layer whose subtree holds
|
|
26
|
+
* another scroller — here the drawer's own content area — does not paint a
|
|
27
|
+
* `@starting-style` entry transition for its descendants in Chromium: the
|
|
28
|
+
* transition ticks in the CSSOM (`getComputedStyle` interpolates perfectly)
|
|
29
|
+
* while every painted frame shows the end value. Measuring the CSSOM says
|
|
30
|
+
* "animating"; the screen says "snapped". `overflow: clip` clips the
|
|
31
|
+
* off-screen drawer exactly as `hidden` did without creating a scroll
|
|
32
|
+
* container, and the slide-in paints.
|
|
33
|
+
*
|
|
34
|
+
* Reproduced minimally: dialog `overflow: hidden` + a scrolling child inside
|
|
35
|
+
* the drawer snaps; either one alone animates.
|
|
36
|
+
*
|
|
37
|
+
* Note on scope: jsdom has no top layer, no transitions, no `@starting-style`
|
|
38
|
+
* evaluation and no compositor, so none of this is observable here. These
|
|
39
|
+
* tests pin the three declarations the behaviour rests on. Verified in
|
|
40
|
+
* Chromium by screencasting painted frames (not computed style) at both edges,
|
|
41
|
+
* LTR and RTL: open and close each paint ~60-100 distinct intermediate
|
|
42
|
+
* positions, and reduced motion collapses to a 10ms transition.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import {describe, it, expect, beforeAll} from 'vitest';
|
|
46
|
+
import {render, screen} from '@testing-library/react';
|
|
47
|
+
import {readFileSync} from 'node:fs';
|
|
48
|
+
import {join} from 'node:path';
|
|
49
|
+
import ts from 'typescript';
|
|
50
|
+
import {MobileNav} from './MobileNav';
|
|
51
|
+
|
|
52
|
+
// jsdom doesn't implement showModal/close on <dialog>, so we mock them
|
|
53
|
+
beforeAll(() => {
|
|
54
|
+
HTMLDialogElement.prototype.showModal =
|
|
55
|
+
HTMLDialogElement.prototype.showModal ||
|
|
56
|
+
function (this: HTMLDialogElement) {
|
|
57
|
+
this.setAttribute('open', '');
|
|
58
|
+
};
|
|
59
|
+
HTMLDialogElement.prototype.close =
|
|
60
|
+
HTMLDialogElement.prototype.close ||
|
|
61
|
+
function (this: HTMLDialogElement) {
|
|
62
|
+
this.removeAttribute('open');
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/** Every CSS rule StyleX has injected into the document, as text. */
|
|
67
|
+
function injectedRules(): string[] {
|
|
68
|
+
const rules: string[] = [];
|
|
69
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
70
|
+
let cssRules: CSSRuleList;
|
|
71
|
+
try {
|
|
72
|
+
cssRules = sheet.cssRules;
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const rule of Array.from(cssRules)) {
|
|
77
|
+
rules.push(rule.cssText);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return rules;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('MobileNav scrim fades in', () => {
|
|
84
|
+
it('gives the ::backdrop a transparent starting style', () => {
|
|
85
|
+
render(
|
|
86
|
+
<MobileNav isOpen onOpenChange={() => {}} data-testid="mobile-nav">
|
|
87
|
+
<span>Nav content</span>
|
|
88
|
+
</MobileNav>,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const classes = Array.from(screen.getByTestId('mobile-nav').classList);
|
|
92
|
+
// Without this the scrim is opaque in the frame the dialog enters the top
|
|
93
|
+
// layer, so it has nothing to fade from and snaps in behind the drawer.
|
|
94
|
+
const startingStyleForThisDialog = injectedRules().filter(
|
|
95
|
+
rule =>
|
|
96
|
+
rule.startsWith('@starting-style') &&
|
|
97
|
+
rule.includes('::backdrop') &&
|
|
98
|
+
classes.some(cls => rule.includes(`.${cls}`)),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(startingStyleForThisDialog).toHaveLength(1);
|
|
102
|
+
expect(startingStyleForThisDialog[0]).toMatch(/opacity:\s*0/);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// Drawer slide-in — asserted on the style definition
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
const SOURCE = readFileSync(join(__dirname, 'MobileNav.tsx'), 'utf8');
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The object literal a `styles.<name>` key is defined with, e.g. the value of
|
|
114
|
+
* `drawerStartOpen` inside the file's `stylex.create({...})` call.
|
|
115
|
+
*/
|
|
116
|
+
function styleDefinition(name: string): ts.ObjectLiteralExpression {
|
|
117
|
+
const file = ts.createSourceFile(
|
|
118
|
+
'MobileNav.tsx',
|
|
119
|
+
SOURCE,
|
|
120
|
+
ts.ScriptTarget.Latest,
|
|
121
|
+
true,
|
|
122
|
+
ts.ScriptKind.TSX,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
let found: ts.ObjectLiteralExpression | undefined;
|
|
126
|
+
const visit = (node: ts.Node) => {
|
|
127
|
+
if (
|
|
128
|
+
ts.isPropertyAssignment(node) &&
|
|
129
|
+
ts.isIdentifier(node.name) &&
|
|
130
|
+
node.name.text === name &&
|
|
131
|
+
ts.isObjectLiteralExpression(node.initializer)
|
|
132
|
+
) {
|
|
133
|
+
found = node.initializer;
|
|
134
|
+
}
|
|
135
|
+
ts.forEachChild(node, visit);
|
|
136
|
+
};
|
|
137
|
+
visit(file);
|
|
138
|
+
|
|
139
|
+
if (!found) {
|
|
140
|
+
throw new Error(`No style named "${name}" in MobileNav.tsx`);
|
|
141
|
+
}
|
|
142
|
+
return found;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The value of one property of a style definition, as source text. */
|
|
146
|
+
function property(style: ts.ObjectLiteralExpression, key: string): string {
|
|
147
|
+
const prop = style.properties.find(
|
|
148
|
+
p =>
|
|
149
|
+
ts.isPropertyAssignment(p) &&
|
|
150
|
+
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
151
|
+
p.name.text === key,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (!prop) {
|
|
155
|
+
throw new Error(`No "${key}" in ${style.getText().slice(0, 40)}…`);
|
|
156
|
+
}
|
|
157
|
+
return (prop as ts.PropertyAssignment).initializer.getText();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
describe('MobileNav dialog does not become a scroll container', () => {
|
|
161
|
+
it('clips the off-screen drawer with `clip`, not `hidden`', () => {
|
|
162
|
+
// This is the half of the fix with no visible declaration of its own
|
|
163
|
+
// purpose: `hidden` looks like a pure clipping choice and clips exactly as
|
|
164
|
+
// well, so it is an easy "harmless tidy-up" to make. It is not harmless —
|
|
165
|
+
// it makes the dialog a scroll container, and the entry animation then
|
|
166
|
+
// ticks in the CSSOM without ever painting. See the file header.
|
|
167
|
+
expect(property(styleDefinition('dialog'), 'overflow')).toBe("'clip'");
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe.each([
|
|
172
|
+
{
|
|
173
|
+
name: 'drawerStartOpen',
|
|
174
|
+
offscreen: 'translateX(-100%)',
|
|
175
|
+
offscreenRtl: 'translateX(100%)',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'drawerEndOpen',
|
|
179
|
+
offscreen: 'translateX(100%)',
|
|
180
|
+
offscreenRtl: 'translateX(-100%)',
|
|
181
|
+
},
|
|
182
|
+
])('MobileNav drawer slides in ($name)', ({name, offscreen, offscreenRtl}) => {
|
|
183
|
+
it('opens to the on-screen transform', () => {
|
|
184
|
+
expect(property(styleDefinition(name), 'transform')).toContain(
|
|
185
|
+
'translateX(0)',
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('starts off-screen so the open transform has something to run from', () => {
|
|
190
|
+
const transform = property(styleDefinition(name), 'transform');
|
|
191
|
+
|
|
192
|
+
// The whole point: the first rendered frame needs the closed transform.
|
|
193
|
+
// Drop this and the drawer is simply there, fully open, on frame one —
|
|
194
|
+
// while the close still animates, which is what made the bug look like a
|
|
195
|
+
// missing entry animation rather than a missing starting style.
|
|
196
|
+
expect(transform).toContain('@starting-style');
|
|
197
|
+
expect(transform).toContain(offscreen);
|
|
198
|
+
// Mirrored, like the closed styles it has to match: sliding in from the
|
|
199
|
+
// wrong edge in RTL is as broken as not sliding at all.
|
|
200
|
+
expect(transform).toContain(offscreenRtl);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('starts from the same edge the closed style parks it at', () => {
|
|
204
|
+
const closed = property(
|
|
205
|
+
styleDefinition(name.replace('Open', '')),
|
|
206
|
+
'transform',
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(closed).toContain(offscreen);
|
|
210
|
+
expect(closed).toContain(offscreenRtl);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file MobileNavScrollbarGutter.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, MobileNav, a stubbed viewport
|
|
6
|
+
* @output Unit tests for the scrollbar gutter MobileNav reserves while open
|
|
7
|
+
* @position Testing; guards MobileNav.tsx against the open-drawer page shift
|
|
8
|
+
*
|
|
9
|
+
* MobileNav clips the document to lock background scroll, which hides a
|
|
10
|
+
* classic scrollbar and widens the layout viewport by its width — the page
|
|
11
|
+
* behind the drawer jumps sideways. The clip has to be paired with holding
|
|
12
|
+
* that gutter open.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When MobileNav.tsx changes, update tests to match new behavior
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {describe, it, expect, beforeAll, afterEach} from 'vitest';
|
|
18
|
+
import {render, cleanup} from '@testing-library/react';
|
|
19
|
+
import {MobileNav} from './MobileNav';
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
// jsdom doesn't implement showModal/close on <dialog>
|
|
23
|
+
HTMLDialogElement.prototype.showModal =
|
|
24
|
+
HTMLDialogElement.prototype.showModal ||
|
|
25
|
+
function (this: HTMLDialogElement) {
|
|
26
|
+
this.setAttribute('open', '');
|
|
27
|
+
};
|
|
28
|
+
HTMLDialogElement.prototype.close =
|
|
29
|
+
HTMLDialogElement.prototype.close ||
|
|
30
|
+
function (this: HTMLDialogElement) {
|
|
31
|
+
this.removeAttribute('open');
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** jsdom does no layout, so both halves of the measurement are stubbed. */
|
|
36
|
+
function stubViewport(innerWidth: number, clientWidth: number) {
|
|
37
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
38
|
+
value: innerWidth,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(document.documentElement, 'clientWidth', {
|
|
43
|
+
value: clientWidth,
|
|
44
|
+
configurable: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rootGutter = () => document.documentElement.style.scrollbarGutter;
|
|
49
|
+
|
|
50
|
+
describe('MobileNav scrollbar gutter', () => {
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
cleanup();
|
|
53
|
+
document.documentElement.style.cssText = '';
|
|
54
|
+
// @ts-expect-error -- drop the viewport stub so jsdom's own value comes back
|
|
55
|
+
delete document.documentElement.clientWidth;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('holds the gutter open while the drawer is open', () => {
|
|
59
|
+
stubViewport(1024, 1009);
|
|
60
|
+
|
|
61
|
+
const view = render(
|
|
62
|
+
<MobileNav isOpen={true} onOpenChange={() => {}}>
|
|
63
|
+
<span>Nav content</span>
|
|
64
|
+
</MobileNav>,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(document.documentElement.style.overflow).toBe('clip');
|
|
68
|
+
expect(rootGutter()).toBe('stable');
|
|
69
|
+
|
|
70
|
+
view.unmount();
|
|
71
|
+
|
|
72
|
+
expect(document.documentElement.style.overflow).toBe('');
|
|
73
|
+
expect(rootGutter()).toBe('');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('gives the gutter back when the drawer closes', () => {
|
|
77
|
+
stubViewport(1024, 1009);
|
|
78
|
+
|
|
79
|
+
const view = render(
|
|
80
|
+
<MobileNav isOpen={true} onOpenChange={() => {}}>
|
|
81
|
+
<span>Nav content</span>
|
|
82
|
+
</MobileNav>,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(rootGutter()).toBe('stable');
|
|
86
|
+
|
|
87
|
+
view.rerender(
|
|
88
|
+
<MobileNav isOpen={false} onOpenChange={() => {}}>
|
|
89
|
+
<span>Nav content</span>
|
|
90
|
+
</MobileNav>,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(rootGutter()).toBe('');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('holds nothing when the scrollbar is an overlay one', () => {
|
|
97
|
+
stubViewport(1024, 1024);
|
|
98
|
+
|
|
99
|
+
render(
|
|
100
|
+
<MobileNav isOpen={true} onOpenChange={() => {}}>
|
|
101
|
+
<span>Nav content</span>
|
|
102
|
+
</MobileNav>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(document.documentElement.style.overflow).toBe('clip');
|
|
106
|
+
expect(rootGutter()).toBe('');
|
|
107
|
+
expect(document.documentElement.style.paddingRight).toBe('');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -1790,7 +1790,7 @@ describe('MultiSelector clear icon theme target', () => {
|
|
|
1790
1790
|
expect(css).toContain('.astryx-multi-selector-clear-icon {');
|
|
1791
1791
|
expect(css).toContain('width: 12px');
|
|
1792
1792
|
expect(css).toContain('height: 12px');
|
|
1793
|
-
expect(css).toContain('.astryx-multi-selector-clear-icon:hover
|
|
1793
|
+
expect(css).toContain('.astryx-multi-selector-clear-icon:hover');
|
|
1794
1794
|
expect(css).toContain('color: var(--color-icon-primary)');
|
|
1795
1795
|
});
|
|
1796
1796
|
});
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
import {useMultiCombobox} from './hooks';
|
|
74
74
|
import {getInputARIA, isImeKeyEvent, mergeProps} from '../utils';
|
|
75
75
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
76
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
76
77
|
import type {BaseProps} from '../BaseProps';
|
|
77
78
|
import type {SizeValue} from '../utils/types';
|
|
78
79
|
import {useSize} from '../SizeContext/SizeContext';
|
|
@@ -189,16 +190,17 @@ const styles = stylex.create({
|
|
|
189
190
|
backgroundColor: 'transparent',
|
|
190
191
|
backgroundImage: {
|
|
191
192
|
default: null,
|
|
192
|
-
':hover': {
|
|
193
|
+
':hover:where(:not(:disabled,[aria-disabled="true"]))': {
|
|
193
194
|
'@media (hover: hover)': `linear-gradient(${colorVars['--color-overlay-hover']}, ${colorVars['--color-overlay-hover']})`,
|
|
194
195
|
},
|
|
195
196
|
':active': `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`,
|
|
196
197
|
},
|
|
197
198
|
boxShadow: {
|
|
198
199
|
default: 'none',
|
|
199
|
-
':hover:not(:focus-within)':
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
|
|
201
|
+
{
|
|
202
|
+
'@media (hover: hover)': 'none',
|
|
203
|
+
},
|
|
202
204
|
':focus-within': 'none',
|
|
203
205
|
},
|
|
204
206
|
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
@@ -706,6 +708,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
706
708
|
style,
|
|
707
709
|
}: MultiSelectorProps<T>) {
|
|
708
710
|
const t = useTranslator();
|
|
711
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
709
712
|
const placeholder =
|
|
710
713
|
placeholderFromProps ?? t('@astryx.multiSelector.selectPlaceholder');
|
|
711
714
|
const selectAllLabel =
|
|
@@ -1526,7 +1529,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1526
1529
|
}
|
|
1527
1530
|
aria-describedby={ariaDescribedBy}
|
|
1528
1531
|
aria-labelledby={ariaLabelledBy}
|
|
1529
|
-
aria-required={
|
|
1532
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
1530
1533
|
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
1531
1534
|
aria-busy={isBusy || undefined}
|
|
1532
1535
|
// With a disabledMessage the trigger keeps focusability via
|