@astryxdesign/core 0.4.4 → 0.4.5-canary.013e728
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/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 +65 -15
- 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.map +1 -1
- package/dist/DateInput/DateInput.js +17 -9
- 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 +85 -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/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/plainDate.d.ts +9 -2
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +16 -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 +8 -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 +6 -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/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 +41 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +95 -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 +13 -9
- 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/utils/dateParser.test.ts +20 -0
- package/src/utils/dateParser.ts +14 -7
- package/src/utils/plainDate.test.ts +56 -0
- package/src/utils/plainDate.ts +17 -4
|
@@ -16,6 +16,8 @@ import {FormLayout} from './FormLayout';
|
|
|
16
16
|
import {FormLayoutContext} from './FormLayoutContext';
|
|
17
17
|
import type {FormLayoutDirection} from './FormLayoutContext';
|
|
18
18
|
import {Field} from '../Field';
|
|
19
|
+
import {TextInput} from '../TextInput';
|
|
20
|
+
import {CheckboxInput} from '../CheckboxInput';
|
|
19
21
|
|
|
20
22
|
// Helper component to read context
|
|
21
23
|
function DirectionReader() {
|
|
@@ -23,6 +25,12 @@ function DirectionReader() {
|
|
|
23
25
|
return <span data-testid="direction">{direction}</span>;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
// Helper component to read the defaultOptionality context value
|
|
29
|
+
function OptionalityReader() {
|
|
30
|
+
const {defaultOptionality} = use(FormLayoutContext);
|
|
31
|
+
return <span data-testid="optionality">{defaultOptionality ?? 'unset'}</span>;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
describe('FormLayout', () => {
|
|
27
35
|
// ─── Basic rendering ────────────────────────────────────────────────────
|
|
28
36
|
|
|
@@ -154,6 +162,178 @@ describe('FormLayout', () => {
|
|
|
154
162
|
expect(screen.getByTestId('inner-child-2')).toBeInTheDocument();
|
|
155
163
|
});
|
|
156
164
|
|
|
165
|
+
// ─── defaultOptionality context propagation ─────────────────────────────
|
|
166
|
+
|
|
167
|
+
it('leaves defaultOptionality unset by default', () => {
|
|
168
|
+
render(
|
|
169
|
+
<FormLayout>
|
|
170
|
+
<OptionalityReader />
|
|
171
|
+
</FormLayout>,
|
|
172
|
+
);
|
|
173
|
+
expect(screen.getByTestId('optionality')).toHaveTextContent('unset');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('provides defaultOptionality="optional" to children', () => {
|
|
177
|
+
render(
|
|
178
|
+
<FormLayout defaultOptionality="optional">
|
|
179
|
+
<OptionalityReader />
|
|
180
|
+
</FormLayout>,
|
|
181
|
+
);
|
|
182
|
+
expect(screen.getByTestId('optionality')).toHaveTextContent('optional');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('provides defaultOptionality="required" to children', () => {
|
|
186
|
+
render(
|
|
187
|
+
<FormLayout defaultOptionality="required">
|
|
188
|
+
<OptionalityReader />
|
|
189
|
+
</FormLayout>,
|
|
190
|
+
);
|
|
191
|
+
expect(screen.getByTestId('optionality')).toHaveTextContent('required');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('an inner layout shadows the outer defaultOptionality', () => {
|
|
195
|
+
render(
|
|
196
|
+
<FormLayout defaultOptionality="optional">
|
|
197
|
+
<FormLayout defaultOptionality="required">
|
|
198
|
+
<OptionalityReader />
|
|
199
|
+
</FormLayout>
|
|
200
|
+
</FormLayout>,
|
|
201
|
+
);
|
|
202
|
+
expect(screen.getByTestId('optionality')).toHaveTextContent('required');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ─── defaultOptionality indicator behavior (through Field) ───────────────
|
|
206
|
+
//
|
|
207
|
+
// The rule: only the *exception* is marked. A field that restates the form
|
|
208
|
+
// default shows nothing; a deviation shows its indicator.
|
|
209
|
+
|
|
210
|
+
it('optional default: only isRequired fields show an indicator', () => {
|
|
211
|
+
render(
|
|
212
|
+
<FormLayout defaultOptionality="optional">
|
|
213
|
+
<Field label="Bio" inputID="bio">
|
|
214
|
+
<input id="bio" />
|
|
215
|
+
</Field>
|
|
216
|
+
<Field label="Nickname" inputID="nick" isOptional>
|
|
217
|
+
<input id="nick" />
|
|
218
|
+
</Field>
|
|
219
|
+
<Field label="Email" inputID="email" isRequired>
|
|
220
|
+
<input id="email" />
|
|
221
|
+
</Field>
|
|
222
|
+
</FormLayout>,
|
|
223
|
+
);
|
|
224
|
+
// Plain + isOptional match the default → nothing shown.
|
|
225
|
+
expect(screen.queryByText(/Optional/)).not.toBeInTheDocument();
|
|
226
|
+
// isRequired deviates → the required indicator shows.
|
|
227
|
+
expect(screen.getByText(/Required/)).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('required default: only isOptional fields show an indicator', () => {
|
|
231
|
+
render(
|
|
232
|
+
<FormLayout defaultOptionality="required">
|
|
233
|
+
<Field label="Name" inputID="name">
|
|
234
|
+
<input id="name" />
|
|
235
|
+
</Field>
|
|
236
|
+
<Field label="Email" inputID="email" isRequired>
|
|
237
|
+
<input id="email" />
|
|
238
|
+
</Field>
|
|
239
|
+
<Field label="Nickname" inputID="nick" isOptional>
|
|
240
|
+
<input id="nick" />
|
|
241
|
+
</Field>
|
|
242
|
+
</FormLayout>,
|
|
243
|
+
);
|
|
244
|
+
// Plain + isRequired match the default → nothing shown.
|
|
245
|
+
expect(screen.queryByText(/Required/)).not.toBeInTheDocument();
|
|
246
|
+
// isOptional deviates → the optional indicator shows.
|
|
247
|
+
expect(screen.getByText(/Optional/)).toBeInTheDocument();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('unset default preserves per-field indicators (backwards compatible)', () => {
|
|
251
|
+
render(
|
|
252
|
+
<FormLayout>
|
|
253
|
+
<Field label="Email" inputID="email" isRequired>
|
|
254
|
+
<input id="email" />
|
|
255
|
+
</Field>
|
|
256
|
+
<Field label="Nickname" inputID="nick" isOptional>
|
|
257
|
+
<input id="nick" />
|
|
258
|
+
</Field>
|
|
259
|
+
</FormLayout>,
|
|
260
|
+
);
|
|
261
|
+
expect(screen.getByText(/Required/)).toBeInTheDocument();
|
|
262
|
+
expect(screen.getByText(/Optional/)).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ─── defaultOptionality aria-required resolution ─────────────────────────
|
|
266
|
+
//
|
|
267
|
+
// The indicator is suppressed for the unmarked majority, so the matching
|
|
268
|
+
// `aria-required` must still be exposed — otherwise a sighted user reads a
|
|
269
|
+
// field as required (form default, no indicator) while a screen reader hears
|
|
270
|
+
// "not required". Native `required` stays bound to the explicit prop so a
|
|
271
|
+
// layout default never switches on browser validation.
|
|
272
|
+
|
|
273
|
+
it('required default: an unmarked input still exposes aria-required', () => {
|
|
274
|
+
render(
|
|
275
|
+
<FormLayout defaultOptionality="required">
|
|
276
|
+
<TextInput label="Name" value="" onChange={() => {}} />
|
|
277
|
+
</FormLayout>,
|
|
278
|
+
);
|
|
279
|
+
expect(screen.getByLabelText('Name')).toHaveAttribute(
|
|
280
|
+
'aria-required',
|
|
281
|
+
'true',
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('required default: an isOptional input is not aria-required', () => {
|
|
286
|
+
render(
|
|
287
|
+
<FormLayout defaultOptionality="required">
|
|
288
|
+
<TextInput label="Nickname" value="" onChange={() => {}} isOptional />
|
|
289
|
+
</FormLayout>,
|
|
290
|
+
);
|
|
291
|
+
expect(screen.getByRole('textbox')).not.toHaveAttribute('aria-required');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('optional default: an unmarked input is not aria-required', () => {
|
|
295
|
+
render(
|
|
296
|
+
<FormLayout defaultOptionality="optional">
|
|
297
|
+
<TextInput label="Bio" value="" onChange={() => {}} />
|
|
298
|
+
</FormLayout>,
|
|
299
|
+
);
|
|
300
|
+
expect(screen.getByLabelText('Bio')).not.toHaveAttribute('aria-required');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('no layout: an unmarked input is not aria-required (backwards compatible)', () => {
|
|
304
|
+
render(<TextInput label="Solo" value="" onChange={() => {}} />);
|
|
305
|
+
expect(screen.getByLabelText('Solo')).not.toHaveAttribute('aria-required');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('required default resolves aria-required without native required', () => {
|
|
309
|
+
render(
|
|
310
|
+
<FormLayout defaultOptionality="required">
|
|
311
|
+
<CheckboxInput label="Terms" value={false} onChange={() => {}} />
|
|
312
|
+
</FormLayout>,
|
|
313
|
+
);
|
|
314
|
+
const checkbox = screen.getByRole('checkbox', {name: 'Terms'});
|
|
315
|
+
// Announced as required (form default)…
|
|
316
|
+
expect(checkbox).toHaveAttribute('aria-required', 'true');
|
|
317
|
+
// …but the native `required` attribute is not switched on by the layout.
|
|
318
|
+
expect(checkbox).not.toHaveAttribute('required');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('explicit isRequired still drives native required under a layout', () => {
|
|
322
|
+
render(
|
|
323
|
+
<FormLayout defaultOptionality="required">
|
|
324
|
+
<CheckboxInput
|
|
325
|
+
label="Consent"
|
|
326
|
+
value={false}
|
|
327
|
+
onChange={() => {}}
|
|
328
|
+
isRequired
|
|
329
|
+
/>
|
|
330
|
+
</FormLayout>,
|
|
331
|
+
);
|
|
332
|
+
const checkbox = screen.getByRole('checkbox', {name: 'Consent'});
|
|
333
|
+
expect(checkbox).toHaveAttribute('aria-required', 'true');
|
|
334
|
+
expect(checkbox).toHaveAttribute('required');
|
|
335
|
+
});
|
|
336
|
+
|
|
157
337
|
// ─── Snapshot tests ─────────────────────────────────────────────────────
|
|
158
338
|
|
|
159
339
|
it('matches snapshot for vertical direction', () => {
|
|
@@ -223,10 +403,7 @@ describe('FormLayout', () => {
|
|
|
223
403
|
it('horizontal-labels with Field: label and input wrapper are siblings under display:contents', () => {
|
|
224
404
|
render(
|
|
225
405
|
<FormLayout direction="horizontal-labels" data-testid="layout">
|
|
226
|
-
<Field
|
|
227
|
-
label="Username"
|
|
228
|
-
inputID="username"
|
|
229
|
-
data-testid="username-field">
|
|
406
|
+
<Field label="Username" inputID="username" data-testid="username-field">
|
|
230
407
|
<input id="username" data-testid="username-input" />
|
|
231
408
|
</Field>
|
|
232
409
|
</FormLayout>,
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
12
12
|
* - /packages/core/src/FormLayout/FormLayout.test.tsx (tests for new/changed behavior)
|
|
13
|
+
* - /packages/core/src/FormLayout/FormLayoutContext.ts (context value shape)
|
|
14
|
+
* - /packages/core/src/FormLayout/FormLayout.doc.mjs (props table)
|
|
13
15
|
* - /packages/core/src/FormLayout/index.ts (exports if types change)
|
|
16
|
+
* - /packages/core/src/Field/FieldLabel.tsx (defaultOptionality indicator resolution)
|
|
14
17
|
* - /apps/storybook/stories/FormLayout.stories.tsx (storybook stories)
|
|
15
18
|
* - /packages/cli/assets/templates/blocks/components/FormLayout/ (showcase blocks)
|
|
16
19
|
*/
|
|
@@ -19,7 +22,11 @@ import {useMemo, type ReactNode} from 'react';
|
|
|
19
22
|
import type {BaseProps} from '../BaseProps';
|
|
20
23
|
import * as stylex from '@stylexjs/stylex';
|
|
21
24
|
import {spacingVars} from '../theme/tokens.stylex';
|
|
22
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
FormLayoutContext,
|
|
27
|
+
type FormLayoutDirection,
|
|
28
|
+
type FormOptionality,
|
|
29
|
+
} from './FormLayoutContext';
|
|
23
30
|
import {mergeProps} from '../utils';
|
|
24
31
|
import {themeProps} from '../utils/themeProps';
|
|
25
32
|
|
|
@@ -82,6 +89,26 @@ export interface FormLayoutProps extends BaseProps<HTMLDivElement> {
|
|
|
82
89
|
* @default 'vertical'
|
|
83
90
|
*/
|
|
84
91
|
direction?: FormLayoutDirection;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Which state the form treats as its default, so only the *exception*
|
|
95
|
+
* carries a visible optional/required indicator. It also resolves each
|
|
96
|
+
* field's `aria-required` so the unmarked majority is still announced
|
|
97
|
+
* correctly — but only `aria-required`, never the native `required`
|
|
98
|
+
* attribute, so a layout default can't switch on browser validation.
|
|
99
|
+
*
|
|
100
|
+
* - `'optional'` — fields read as optional; only a field with `isRequired`
|
|
101
|
+
* shows an indicator (the "required" one).
|
|
102
|
+
* - `'required'` — fields read as required; only a field with `isOptional`
|
|
103
|
+
* shows an indicator (the "optional" one). Fields without `isOptional`
|
|
104
|
+
* expose `aria-required` even though they show no indicator.
|
|
105
|
+
* - unset — today's behavior: `isRequired` and `isOptional` each show their
|
|
106
|
+
* own indicator independently.
|
|
107
|
+
*
|
|
108
|
+
* A field that merely restates the default (e.g. `isOptional` under
|
|
109
|
+
* `'optional'`) shows nothing. An inner `FormLayout` shadows an outer one.
|
|
110
|
+
*/
|
|
111
|
+
defaultOptionality?: FormOptionality;
|
|
85
112
|
}
|
|
86
113
|
|
|
87
114
|
// =============================================================================
|
|
@@ -109,13 +136,17 @@ export interface FormLayoutProps extends BaseProps<HTMLDivElement> {
|
|
|
109
136
|
export function FormLayout({
|
|
110
137
|
children,
|
|
111
138
|
direction = 'vertical',
|
|
139
|
+
defaultOptionality,
|
|
112
140
|
xstyle,
|
|
113
141
|
className,
|
|
114
142
|
style,
|
|
115
143
|
ref,
|
|
116
144
|
...props
|
|
117
145
|
}: FormLayoutProps) {
|
|
118
|
-
const contextValue = useMemo(
|
|
146
|
+
const contextValue = useMemo(
|
|
147
|
+
() => ({direction, defaultOptionality}),
|
|
148
|
+
[direction, defaultOptionality],
|
|
149
|
+
);
|
|
119
150
|
|
|
120
151
|
return (
|
|
121
152
|
<FormLayoutContext value={contextValue}>
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file FormLayoutContext.ts
|
|
7
7
|
* @input Uses React createContext
|
|
8
|
-
* @output Exports FormLayoutContext and
|
|
9
|
-
* @position Context for form layout direction detection
|
|
8
|
+
* @output Exports FormLayoutContext, FormLayoutDirection, and FormOptionality types
|
|
9
|
+
* @position Context for form layout direction + default-optionality detection
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/FormLayout/FormLayout.tsx (prop + context value)
|
|
13
|
+
* - /packages/core/src/Field/FieldLabel.tsx (indicator resolution)
|
|
14
|
+
* - /packages/core/src/FormLayout/index.ts (exports if types change)
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import {createContext} from 'react';
|
|
@@ -22,15 +25,27 @@ import {createContext} from 'react';
|
|
|
22
25
|
* of their inputs (settings/admin panel pattern).
|
|
23
26
|
*/
|
|
24
27
|
export type FormLayoutDirection =
|
|
25
|
-
| '
|
|
26
|
-
| 'horizontal'
|
|
27
|
-
| 'horizontal-labels';
|
|
28
|
+
'vertical' | 'horizontal' | 'horizontal-labels';
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* Which state a form treats as its default, so only the *exception* carries a
|
|
32
|
+
* visible optional/required indicator.
|
|
33
|
+
*
|
|
34
|
+
* - `'optional'` — fields are optional unless a field opts into `isRequired`;
|
|
35
|
+
* only required fields show an indicator.
|
|
36
|
+
* - `'required'` — fields are required unless a field opts into `isOptional`;
|
|
37
|
+
* only optional fields show an indicator.
|
|
38
|
+
*/
|
|
39
|
+
export type FormOptionality = 'optional' | 'required';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Context for detecting which form layout a component is rendered in. Children
|
|
43
|
+
* can use this to adapt their rendering based on the parent layout — direction
|
|
44
|
+
* for spatial arrangement, and `defaultOptionality` so a field can suppress the
|
|
45
|
+
* indicator that merely restates the form-wide default.
|
|
32
46
|
*/
|
|
33
47
|
export const FormLayoutContext = createContext<{
|
|
34
48
|
direction: FormLayoutDirection;
|
|
49
|
+
defaultOptionality?: FormOptionality;
|
|
35
50
|
}>({direction: 'vertical'});
|
|
36
51
|
FormLayoutContext.displayName = 'FormLayoutContext';
|
package/src/FormLayout/index.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
|
|
14
14
|
export {FormLayout} from './FormLayout';
|
|
15
15
|
export type {FormLayoutProps} from './FormLayout';
|
|
16
|
-
export type {FormLayoutDirection} from './FormLayoutContext';
|
|
16
|
+
export type {FormLayoutDirection, FormOptionality} from './FormLayoutContext';
|
|
17
17
|
export {FormLayoutContext} from './FormLayoutContext';
|
|
@@ -73,6 +73,13 @@ export const docs = {
|
|
|
73
73
|
description: 'Controls when focus events trigger the hover card.',
|
|
74
74
|
default: "'auto'",
|
|
75
75
|
},
|
|
76
|
+
{
|
|
77
|
+
name: 'touchTrigger',
|
|
78
|
+
type: "'auto' | 'tap' | 'none'",
|
|
79
|
+
description:
|
|
80
|
+
'What a tap does where there is no hover. auto opens on tap unless the trigger performs an action of its own (a button, link, or form control), whose tap belongs to the control. tap always opens. none never opens on touch. A tap-opened card is dismissed by a tap outside it.',
|
|
81
|
+
default: "'auto'",
|
|
82
|
+
},
|
|
76
83
|
{
|
|
77
84
|
name: 'isEnabled',
|
|
78
85
|
type: 'boolean',
|
|
@@ -111,6 +118,7 @@ export const docs = {
|
|
|
111
118
|
{ guidance: true, description: 'Keep content supplementary; hover cards should enhance understanding without blocking the primary workflow.' },
|
|
112
119
|
{ guidance: true, description: 'Provide a dashed underline on text triggers so users know the element is hoverable.' },
|
|
113
120
|
{ guidance: true, description: 'Use the hook API (useHoverCard) when you need more control over timing or placement.' },
|
|
121
|
+
{ guidance: true, description: 'Leave touchTrigger on auto so a tap opens the card on triggers that do nothing else, and stays out of the way on triggers that perform an action.' },
|
|
114
122
|
{ guidance: false, description: 'Place critical actions or required information inside a hover card; users may miss content that only appears on hover.' },
|
|
115
123
|
{ guidance: false, description: 'Use a hover card when a simple Tooltip or Popover would suffice.' },
|
|
116
124
|
{ guidance: false, description: 'Use a HoverCard for content the user must interact with; it disappears when the cursor leaves.' },
|
|
@@ -188,6 +196,13 @@ export const docsZh = {
|
|
|
188
196
|
description: '控制焦点事件何时触发悬浮卡片。',
|
|
189
197
|
default: "'auto'",
|
|
190
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: 'touchTrigger',
|
|
201
|
+
type: "'auto' | 'tap' | 'none'",
|
|
202
|
+
description:
|
|
203
|
+
'在没有悬停的触摸设备上,轻点的行为。auto:轻点即打开,除非触发元素本身会执行操作(按钮、链接、表单控件),此时轻点归该控件所有。tap:始终轻点打开。none:触摸永不打开。轻点打开的卡片可通过点击卡片外部关闭。',
|
|
204
|
+
default: "'auto'",
|
|
205
|
+
},
|
|
191
206
|
{
|
|
192
207
|
name: 'isEnabled',
|
|
193
208
|
type: 'boolean',
|
|
@@ -226,6 +241,7 @@ export const docsZh = {
|
|
|
226
241
|
{ guidance: true, description: 'Keep content supplementary; hover cards should enhance understanding without blocking the primary workflow.' },
|
|
227
242
|
{ guidance: true, description: 'Provide a dashed underline on text triggers so users know the element is hoverable.' },
|
|
228
243
|
{ guidance: true, description: 'Use the hook API (useHoverCard) when you need more control over timing or placement.' },
|
|
244
|
+
{ guidance: true, description: 'Leave touchTrigger on auto so a tap opens the card on triggers that do nothing else, and stays out of the way on triggers that perform an action.' },
|
|
229
245
|
{ guidance: false, description: 'Place critical actions or required information inside a hover card; users may miss content that only appears on hover.' },
|
|
230
246
|
{ guidance: false, description: 'Use a hover card when a simple Tooltip or Popover would suffice.' },
|
|
231
247
|
{ guidance: false, description: 'Use a HoverCard for content the user must interact with; it disappears when the cursor leaves.' },
|
|
@@ -244,6 +260,7 @@ export const docsDense = {
|
|
|
244
260
|
{ guidance: true, description: 'Keep content supplementary; hover cards should enhance understanding without blocking the primary workflow.' },
|
|
245
261
|
{ guidance: true, description: 'Provide a dashed underline on text triggers so users know the element is hoverable.' },
|
|
246
262
|
{ guidance: true, description: 'Use the hook API (useHoverCard) when you need more control over timing or placement.' },
|
|
263
|
+
{ guidance: true, description: 'Leave touchTrigger on auto so a tap opens the card on triggers that do nothing else, and stays out of the way on triggers that perform an action.' },
|
|
247
264
|
{ guidance: false, description: 'Place critical actions or required information inside a hover card; users may miss content that only appears on hover.' },
|
|
248
265
|
{ guidance: false, description: 'Use a hover card when a simple Tooltip or Popover would suffice.' },
|
|
249
266
|
{ guidance: false, description: 'Use a HoverCard for content the user must interact with; it disappears when the cursor leaves.' },
|
|
@@ -263,6 +280,7 @@ export const docsDense = {
|
|
|
263
280
|
delay: 'Show delay in ms.',
|
|
264
281
|
hideDelay: 'Hide delay in ms.',
|
|
265
282
|
focusTrigger: 'Controls when focus events trigger hover card.',
|
|
283
|
+
touchTrigger: 'Tap behavior where there is no hover. auto = tap opens unless the trigger acts (button/link/control); tap = always opens; none = never on touch. Tap-opened cards close on a tap outside.',
|
|
266
284
|
isEnabled: 'Enable/disable hover + focus triggers.',
|
|
267
285
|
label: 'Accessible name for the popup. With label: named role="dialog"; without: role="group".',
|
|
268
286
|
onOpenChange: 'Callback when visibility changes; true=shown, false=hidden.',
|
|
@@ -25,6 +25,7 @@ import {StrictMode} from 'react';
|
|
|
25
25
|
import {Button} from '../Button/Button';
|
|
26
26
|
import {Theme, defineTheme} from '../theme';
|
|
27
27
|
import {HoverCard} from './HoverCard';
|
|
28
|
+
import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
28
29
|
|
|
29
30
|
// Store original matches to restore later
|
|
30
31
|
const originalMatches = HTMLElement.prototype.matches;
|
|
@@ -804,4 +805,192 @@ describe('HoverCard', () => {
|
|
|
804
805
|
container.remove();
|
|
805
806
|
});
|
|
806
807
|
});
|
|
808
|
+
|
|
809
|
+
describe('touch', () => {
|
|
810
|
+
// The modality is document-global; a tap in one case must not decide the
|
|
811
|
+
// next one's answer.
|
|
812
|
+
beforeEach(() => {
|
|
813
|
+
__resetInteractionModalityForTest();
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
/** A tap: the pointer sequence a finger produces before hover is faked. */
|
|
817
|
+
const tap = (element: HTMLElement) => {
|
|
818
|
+
// A finger's arrival fires pointerenter too, and that is the path a pen
|
|
819
|
+
// must not take — cover it here rather than starting at pointerdown.
|
|
820
|
+
fireEvent.pointerEnter(element, {pointerType: 'touch'});
|
|
821
|
+
fireEvent.pointerDown(element, {pointerType: 'touch'});
|
|
822
|
+
fireEvent.pointerUp(element, {pointerType: 'touch'});
|
|
823
|
+
// Touch synthesizes hover after the press; the card must not act on it.
|
|
824
|
+
fireEvent.mouseEnter(element);
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
it('opens on a tap when the trigger performs no action', async () => {
|
|
828
|
+
const onOpenChange = vi.fn();
|
|
829
|
+
render(
|
|
830
|
+
<HoverCard
|
|
831
|
+
content={<span>Card content</span>}
|
|
832
|
+
onOpenChange={onOpenChange}
|
|
833
|
+
delay={300}>
|
|
834
|
+
Ruby Cheung
|
|
835
|
+
</HoverCard>,
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
tap(screen.getByText('Ruby Cheung'));
|
|
839
|
+
|
|
840
|
+
// Immediately: a tap is a decision, not hover intent, so no delay applies.
|
|
841
|
+
await waitFor(() => {
|
|
842
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it('stays shut on a tap when the trigger performs an action', async () => {
|
|
847
|
+
const onOpenChange = vi.fn();
|
|
848
|
+
render(
|
|
849
|
+
<HoverCard
|
|
850
|
+
content={<span>Card content</span>}
|
|
851
|
+
onOpenChange={onOpenChange}
|
|
852
|
+
delay={0}>
|
|
853
|
+
<button type="button">Save</button>
|
|
854
|
+
</HoverCard>,
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
const trigger = screen.getByRole('button', {name: 'Save'});
|
|
858
|
+
tap(trigger);
|
|
859
|
+
// A tap focuses what it activates; that focus must not reopen the card.
|
|
860
|
+
fireEvent.focusIn(trigger);
|
|
861
|
+
|
|
862
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
863
|
+
expect(onOpenChange).not.toHaveBeenCalledWith(true);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it('opens on a tap of an action trigger when touchTrigger is "tap"', async () => {
|
|
867
|
+
const onOpenChange = vi.fn();
|
|
868
|
+
render(
|
|
869
|
+
<HoverCard
|
|
870
|
+
content={<span>Card content</span>}
|
|
871
|
+
onOpenChange={onOpenChange}
|
|
872
|
+
touchTrigger="tap"
|
|
873
|
+
delay={0}>
|
|
874
|
+
<button type="button">Details</button>
|
|
875
|
+
</HoverCard>,
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
tap(screen.getByRole('button', {name: 'Details'}));
|
|
879
|
+
|
|
880
|
+
await waitFor(() => {
|
|
881
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
it('survives a tap on its own content', async () => {
|
|
886
|
+
const onOpenChange = vi.fn();
|
|
887
|
+
render(
|
|
888
|
+
<HoverCard
|
|
889
|
+
content={<button type="button">Follow</button>}
|
|
890
|
+
onOpenChange={onOpenChange}
|
|
891
|
+
delay={0}
|
|
892
|
+
hideDelay={0}>
|
|
893
|
+
Ruby Cheung
|
|
894
|
+
</HoverCard>,
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
tap(screen.getByText('Ruby Cheung'));
|
|
898
|
+
await waitFor(() => {
|
|
899
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
900
|
+
});
|
|
901
|
+
onOpenChange.mockClear();
|
|
902
|
+
|
|
903
|
+
const action = await screen.findByRole('button', {
|
|
904
|
+
name: 'Follow',
|
|
905
|
+
hidden: true,
|
|
906
|
+
});
|
|
907
|
+
fireEvent.pointerDown(action, {pointerType: 'touch'});
|
|
908
|
+
|
|
909
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
910
|
+
expect(onOpenChange).not.toHaveBeenCalledWith(false);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it('closes on a tap outside', async () => {
|
|
914
|
+
const onOpenChange = vi.fn();
|
|
915
|
+
render(
|
|
916
|
+
<>
|
|
917
|
+
<HoverCard
|
|
918
|
+
content={<span>Card content</span>}
|
|
919
|
+
onOpenChange={onOpenChange}
|
|
920
|
+
delay={0}
|
|
921
|
+
hideDelay={0}>
|
|
922
|
+
Ruby Cheung
|
|
923
|
+
</HoverCard>
|
|
924
|
+
<button type="button">Elsewhere</button>
|
|
925
|
+
</>,
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
tap(screen.getByText('Ruby Cheung'));
|
|
929
|
+
await waitFor(() => {
|
|
930
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
fireEvent.pointerDown(screen.getByRole('button', {name: 'Elsewhere'}), {
|
|
934
|
+
pointerType: 'touch',
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
await waitFor(() => {
|
|
938
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it('closes on a second tap of the trigger', async () => {
|
|
943
|
+
const onOpenChange = vi.fn();
|
|
944
|
+
render(
|
|
945
|
+
<HoverCard
|
|
946
|
+
content={<span>Card content</span>}
|
|
947
|
+
onOpenChange={onOpenChange}
|
|
948
|
+
delay={0}
|
|
949
|
+
hideDelay={0}>
|
|
950
|
+
Ruby Cheung
|
|
951
|
+
</HoverCard>,
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
const trigger = screen.getByText('Ruby Cheung');
|
|
955
|
+
tap(trigger);
|
|
956
|
+
await waitFor(() => {
|
|
957
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
tap(trigger);
|
|
961
|
+
await waitFor(() => {
|
|
962
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('ignores the focus a tap leaves behind, but not keyboard focus', async () => {
|
|
967
|
+
const onOpenChange = vi.fn();
|
|
968
|
+
render(
|
|
969
|
+
<HoverCard
|
|
970
|
+
content={<span>Card content</span>}
|
|
971
|
+
onOpenChange={onOpenChange}
|
|
972
|
+
delay={0}>
|
|
973
|
+
<button type="button">Save</button>
|
|
974
|
+
</HoverCard>,
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
const trigger = screen.getByRole('button', {name: 'Save'});
|
|
978
|
+
// The tap goes to the button, as `auto` decides for an action trigger —
|
|
979
|
+
// and the focus it leaves behind must not put the card over the control
|
|
980
|
+
// the user just pressed.
|
|
981
|
+
tap(trigger);
|
|
982
|
+
fireEvent.focusIn(trigger);
|
|
983
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
984
|
+
expect(onOpenChange).not.toHaveBeenCalledWith(true);
|
|
985
|
+
|
|
986
|
+
// Reaching for the keyboard ends the touch interaction: the same trigger,
|
|
987
|
+
// focused by Tab, still opens.
|
|
988
|
+
fireEvent.keyDown(document, {key: 'Tab'});
|
|
989
|
+
fireEvent.focusIn(trigger);
|
|
990
|
+
|
|
991
|
+
await waitFor(() => {
|
|
992
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
993
|
+
});
|
|
994
|
+
});
|
|
995
|
+
});
|
|
807
996
|
});
|
|
@@ -18,12 +18,19 @@
|
|
|
18
18
|
import {useCallback, useRef, type ReactElement, type ReactNode} from 'react';
|
|
19
19
|
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
useHoverCard,
|
|
23
|
+
type HoverCardFocusTrigger,
|
|
24
|
+
type HoverCardTouchTrigger,
|
|
25
|
+
} from './useHoverCard';
|
|
22
26
|
import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
|
|
23
27
|
import type {BaseProps} from '../BaseProps';
|
|
24
28
|
import {colorVars, spacingVars} from '../theme/tokens.stylex';
|
|
25
29
|
|
|
26
|
-
export type {
|
|
30
|
+
export type {
|
|
31
|
+
HoverCardFocusTrigger,
|
|
32
|
+
HoverCardTouchTrigger,
|
|
33
|
+
} from './useHoverCard';
|
|
27
34
|
|
|
28
35
|
const styles = stylex.create({
|
|
29
36
|
wrapperContents: {
|
|
@@ -88,6 +95,17 @@ export interface HoverCardProps extends Pick<
|
|
|
88
95
|
*/
|
|
89
96
|
focusTrigger?: HoverCardFocusTrigger;
|
|
90
97
|
|
|
98
|
+
/**
|
|
99
|
+
* What a tap does on a touch pointer, where there is no hover:
|
|
100
|
+
* - `auto`: tap opens the card, unless the trigger performs an action of its
|
|
101
|
+
* own (a button, a link, a form control) — that tap belongs to the control
|
|
102
|
+
* - `tap`: tap always opens the card, even on a trigger that acts
|
|
103
|
+
* - `none`: touch never opens the card
|
|
104
|
+
*
|
|
105
|
+
* @default 'auto'
|
|
106
|
+
*/
|
|
107
|
+
touchTrigger?: HoverCardTouchTrigger;
|
|
108
|
+
|
|
91
109
|
/**
|
|
92
110
|
* Whether the hover card is enabled.
|
|
93
111
|
* When false, hover/focus triggers are disabled.
|
|
@@ -174,6 +192,7 @@ export function HoverCard({
|
|
|
174
192
|
delay = 300,
|
|
175
193
|
hideDelay = 200,
|
|
176
194
|
focusTrigger = 'auto',
|
|
195
|
+
touchTrigger = 'auto',
|
|
177
196
|
isEnabled = true,
|
|
178
197
|
label,
|
|
179
198
|
onOpenChange,
|
|
@@ -206,6 +225,7 @@ export function HoverCard({
|
|
|
206
225
|
delay,
|
|
207
226
|
hideDelay,
|
|
208
227
|
focusTrigger,
|
|
228
|
+
touchTrigger,
|
|
209
229
|
isEnabled,
|
|
210
230
|
label,
|
|
211
231
|
isOpen,
|