@astryxdesign/core 0.1.5 → 0.1.6-canary.02f2602
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 +24 -0
- package/README.md +2 -0
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +21 -18
- package/dist/AlertDialog/useImperativeAlertDialog.d.ts +0 -2
- package/dist/AlertDialog/useImperativeAlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/useImperativeAlertDialog.js +3 -5
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +55 -53
- package/dist/AspectRatio/AspectRatio.js +5 -5
- package/dist/Avatar/Avatar.js +19 -19
- package/dist/Avatar/AvatarStatusDot.js +5 -5
- package/dist/AvatarGroup/AvatarGroup.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +9 -6
- package/dist/AvatarGroup/AvatarGroupOverflow.js +5 -5
- package/dist/Badge/Badge.js +3 -3
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +32 -30
- package/dist/Blockquote/Blockquote.js +7 -7
- package/dist/Breadcrumbs/BreadcrumbItem.js +17 -17
- package/dist/Breadcrumbs/Breadcrumbs.d.ts +1 -1
- package/dist/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/Breadcrumbs/Breadcrumbs.js +11 -8
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +53 -23
- package/dist/ButtonGroup/ButtonGroup.d.ts +20 -5
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +34 -18
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +51 -48
- package/dist/Card/Card.js +3 -3
- package/dist/Carousel/Carousel.d.ts +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +27 -24
- package/dist/Center/Center.js +3 -3
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +27 -24
- package/dist/Chat/ChatComposerDrawer.d.ts +1 -1
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +23 -16
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +20 -16
- package/dist/Chat/ChatDictationButton.js +10 -10
- package/dist/Chat/ChatLayout.d.ts +1 -11
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +23 -21
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +13 -11
- package/dist/Chat/ChatMessage.d.ts +1 -1
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +20 -14
- package/dist/Chat/ChatMessageBubble.d.ts +1 -1
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +10 -8
- package/dist/Chat/ChatMessageList.d.ts +1 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +17 -15
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatMessageMetadata.js +24 -19
- package/dist/Chat/ChatPastedTextToken.d.ts.map +1 -1
- package/dist/Chat/ChatPastedTextToken.js +16 -14
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.js +6 -4
- package/dist/Chat/ChatSystemMessage.js +11 -11
- package/dist/Chat/ChatTokenizedText.js +8 -8
- package/dist/Chat/ChatToolCalls.js +73 -73
- package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
- package/dist/Chat/useSpeechRecognition.js +4 -2
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +19 -17
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +26 -24
- package/dist/CheckboxList/CheckboxList.d.ts +1 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +12 -10
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +8 -6
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +20 -12
- package/dist/ClickableCard/ClickableCard.js +6 -6
- package/dist/Code/Code.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +114 -96
- package/dist/Collapsible/Collapsible.js +11 -11
- package/dist/Collapsible/CollapsibleGroup.js +7 -7
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +39 -34
- package/dist/CommandPalette/CommandPaletteEmpty.js +3 -3
- package/dist/CommandPalette/CommandPaletteFooter.js +16 -16
- package/dist/CommandPalette/CommandPaletteGroup.js +5 -5
- package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +17 -14
- package/dist/CommandPalette/CommandPaletteItem.js +3 -3
- package/dist/CommandPalette/CommandPaletteList.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteList.js +7 -4
- package/dist/ContextMenu/ContextMenu.d.ts +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +14 -11
- package/dist/DateInput/DateInput.d.ts +1 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +28 -23
- package/dist/DateRangeInput/DateRangeInput.d.ts +1 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +32 -27
- package/dist/DateTimeInput/DateTimeInput.d.ts +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +41 -33
- package/dist/Dialog/Dialog.js +9 -9
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +20 -18
- package/dist/Dialog/useImperativeDialog.js +3 -3
- package/dist/Divider/Divider.js +7 -7
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +21 -15
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -3
- package/dist/DropdownMenu/renderDropdownItems.js +10 -10
- package/dist/EmptyState/EmptyState.d.ts +4 -1
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +9 -9
- package/dist/Field/Field.js +17 -17
- package/dist/Field/FieldLabel.js +15 -15
- package/dist/Field/InputClearButton.js +5 -5
- package/dist/FieldStatus/FieldStatus.js +3 -3
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +34 -30
- package/dist/FormLayout/FormLayout.js +5 -5
- package/dist/Grid/Grid.js +3 -3
- package/dist/Grid/GridSpan.js +3 -3
- package/dist/HStack/HStack.js +3 -3
- package/dist/Heading/Heading.js +10 -10
- package/dist/HoverCard/HoverCard.js +9 -9
- package/dist/HoverCard/useHoverCard.js +3 -3
- package/dist/Icon/Icon.js +11 -11
- package/dist/Icon/defaultIcons.js +116 -116
- package/dist/IconButton/IconButton.js +3 -3
- package/dist/InputGroup/InputGroup.js +9 -9
- package/dist/InputGroup/InputGroupText.js +3 -3
- package/dist/Item/Item.js +17 -17
- package/dist/Kbd/Kbd.js +5 -5
- package/dist/Layer/LayerProvider.js +7 -7
- package/dist/Layer/useLayer.js +5 -5
- package/dist/Layout/Layout.js +21 -21
- package/dist/Layout/LayoutContent.js +3 -3
- package/dist/Layout/LayoutFooter.js +5 -5
- package/dist/Layout/LayoutHeader.js +5 -5
- package/dist/Layout/LayoutPanel.js +3 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +35 -33
- package/dist/Lightbox/useLightbox.d.ts +0 -1
- package/dist/Lightbox/useLightbox.d.ts.map +1 -1
- package/dist/Lightbox/useLightbox.js +3 -4
- package/dist/Link/Link.d.ts +5 -3
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +47 -18
- package/dist/Link/LinkProvider.js +3 -3
- package/dist/Link/useLinkify.js +3 -3
- package/dist/List/List.js +11 -11
- package/dist/List/ListItem.js +11 -11
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +111 -109
- package/dist/MetadataList/MetadataList.js +10 -10
- package/dist/MetadataList/MetadataListItem.js +15 -15
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +19 -17
- package/dist/MobileNav/MobileNavToggle.d.ts +1 -1
- package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
- package/dist/MobileNav/MobileNavToggle.js +9 -6
- package/dist/MoreMenu/MoreMenu.d.ts +1 -1
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +7 -4
- package/dist/MultiSelector/MultiSelector.d.ts +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +69 -62
- package/dist/NavIcon/NavIcon.js +3 -3
- package/dist/NavMenu/NavHeadingMenu.js +5 -5
- package/dist/NavMenu/NavHeadingMenuItem.js +8 -8
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +21 -17
- package/dist/Outline/Outline.d.ts +1 -1
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +19 -16
- package/dist/OverflowList/OverflowList.js +8 -8
- package/dist/Overlay/Overlay.js +3 -3
- package/dist/Overlay/OverlayScrim.js +5 -5
- package/dist/Overlay/useOverlay.d.ts +0 -3
- package/dist/Overlay/useOverlay.d.ts.map +1 -1
- package/dist/Overlay/useOverlay.js +3 -6
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +73 -46
- package/dist/Popover/Popover.js +16 -16
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +11 -8
- package/dist/PowerSearch/PowerSearch.d.ts +1 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +95 -85
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +109 -103
- package/dist/PowerSearch/PowerSearchFilterEditor.js +3 -3
- package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchToken.js +12 -8
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +88 -75
- package/dist/PowerSearch/formatFilterValue.d.ts +2 -1
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +19 -9
- package/dist/PowerSearch/index.d.ts +2 -1
- package/dist/PowerSearch/index.d.ts.map +1 -1
- package/dist/PowerSearch/index.js +1 -0
- package/dist/PowerSearch/resolveOperatorLabel.d.ts +23 -0
- package/dist/PowerSearch/resolveOperatorLabel.d.ts.map +1 -0
- package/dist/PowerSearch/resolveOperatorLabel.js +28 -0
- package/dist/PowerSearch/types.d.ts +28 -2
- package/dist/PowerSearch/types.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchConfig.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchConfig.js +27 -27
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchSource.js +15 -8
- package/dist/ProgressBar/ProgressBar.js +13 -13
- package/dist/RadioList/RadioList.js +7 -7
- package/dist/RadioList/RadioListItem.js +18 -18
- package/dist/Resizable/ResizeHandle.d.ts +1 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +10 -7
- package/dist/Section/Section.js +5 -5
- package/dist/SegmentedControl/SegmentedControl.js +5 -5
- package/dist/SegmentedControl/SegmentedControlItem.js +7 -7
- package/dist/SelectableCard/SelectableCard.js +5 -5
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +57 -51
- package/dist/Selector/SelectorOption.js +3 -3
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +38 -36
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +8 -6
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +92 -90
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +49 -43
- package/dist/SideNav/SideNavSection.js +14 -14
- package/dist/Skeleton/Skeleton.js +3 -3
- package/dist/Slider/Slider.js +23 -23
- package/dist/Spinner/Spinner.js +9 -9
- package/dist/StatusDot/StatusDot.js +5 -5
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +29 -27
- package/dist/TabList/Tab.js +18 -18
- package/dist/TabList/TabList.d.ts +1 -1
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +9 -6
- package/dist/TabList/TabMenu.js +23 -23
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +39 -37
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +10 -8
- package/dist/Table/TableBody.js +3 -3
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableFooter.js +3 -3
- package/dist/Table/TableHeader.js +3 -3
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/TableRow.js +5 -5
- package/dist/Table/index.d.ts +4 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -0
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -9
- package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
- package/dist/Table/plugins/filtering/useTableFiltering.js +127 -90
- package/dist/Table/plugins/groupedRows/index.d.ts +3 -0
- package/dist/Table/plugins/groupedRows/index.d.ts.map +1 -0
- package/dist/Table/plugins/groupedRows/index.js +3 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +78 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +257 -0
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +12 -9
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +30 -28
- package/dist/Table/plugins/rowIndex/index.d.ts +3 -0
- package/dist/Table/plugins/rowIndex/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowIndex/index.js +3 -0
- package/dist/Table/plugins/rowIndex/useTableRowIndex.d.ts +50 -0
- package/dist/Table/plugins/rowIndex/useTableRowIndex.d.ts.map +1 -0
- package/dist/Table/plugins/rowIndex/useTableRowIndex.js +91 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +23 -20
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +23 -21
- package/dist/Table/tableContextMenu.js +9 -9
- package/dist/Text/Text.js +10 -10
- package/dist/TextArea/TextArea.js +14 -14
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +20 -16
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +37 -31
- package/dist/TimeInput/TimeInput.d.ts +1 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +28 -23
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +29 -14
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +17 -15
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +14 -12
- package/dist/Toast/useToast.js +4 -4
- package/dist/ToggleButton/ToggleButton.js +12 -12
- package/dist/ToggleButton/ToggleButtonGroup.js +5 -5
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +29 -23
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +33 -31
- package/dist/Toolbar/Toolbar.js +18 -18
- package/dist/Tooltip/Tooltip.js +11 -11
- package/dist/Tooltip/useTooltip.js +3 -3
- package/dist/TopNav/TopNav.d.ts +2 -1
- package/dist/TopNav/TopNav.d.ts.map +1 -1
- package/dist/TopNav/TopNav.js +36 -33
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +72 -70
- package/dist/TopNav/TopNavItem.js +5 -5
- package/dist/TopNav/TopNavMegaMenu.js +30 -30
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +10 -10
- package/dist/TopNav/TopNavMegaMenuItem.js +16 -16
- package/dist/TopNav/TopNavMenu.js +32 -32
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +13 -9
- package/dist/TreeList/TreeListBranches.js +11 -11
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +37 -31
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +26 -22
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +15 -13
- package/dist/Typeahead/TypeaheadItem.js +10 -10
- package/dist/VStack/VStack.js +3 -3
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +1 -3
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
- package/dist/VisuallyHidden/VisuallyHidden.js +1 -3
- package/dist/astryx.css +20 -2
- package/dist/astryx.umd.js +66 -64
- package/dist/astryx.umd.js.map +4 -4
- package/dist/authoring/doc.d.ts +145 -0
- package/dist/authoring/doc.d.ts.map +1 -0
- package/dist/authoring/doc.js +100 -0
- package/dist/authoring/index.d.ts +29 -0
- package/dist/authoring/index.d.ts.map +1 -0
- package/dist/authoring/index.js +28 -0
- package/dist/authoring/integration.d.ts +35 -0
- package/dist/authoring/integration.d.ts.map +1 -0
- package/dist/authoring/integration.js +30 -0
- package/dist/authoring/template.d.ts +66 -0
- package/dist/authoring/template.d.ts.map +1 -0
- package/dist/authoring/template.js +55 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +40 -0
- package/dist/hooks/useKeyboardHint.js +9 -9
- package/dist/i18n/InternationalizationContext.d.ts +12 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -0
- package/dist/i18n/InternationalizationContext.js +29 -0
- package/dist/i18n/InternationalizationProvider.d.ts +62 -0
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -0
- package/dist/i18n/InternationalizationProvider.js +44 -0
- package/dist/i18n/index.d.ts +24 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +21 -0
- package/dist/i18n/resolve.d.ts +22 -0
- package/dist/i18n/resolve.d.ts.map +1 -0
- package/dist/i18n/resolve.js +141 -0
- package/dist/i18n/translator.d.ts +25 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +1 -0
- package/dist/i18n/types.d.ts +47 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +1 -0
- package/dist/i18n/useTranslator.d.ts +15 -0
- package/dist/i18n/useTranslator.d.ts.map +1 -0
- package/dist/i18n/useTranslator.js +39 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/theme/MediaTheme.js +3 -3
- package/dist/theme/Theme.js +7 -7
- package/dist/theme/syntax/SyntaxTheme.js +5 -5
- package/locales/en.json +746 -0
- package/locales/pseudo.json +560 -0
- package/package.json +24 -3
- package/src/AlertDialog/AlertDialog.doc.mjs +5 -0
- package/src/AlertDialog/AlertDialog.tsx +4 -1
- package/src/AlertDialog/useImperativeAlertDialog.tsx +0 -2
- package/src/AppShell/AppShell.doc.mjs +8 -2
- package/src/AppShell/AppShell.tsx +3 -1
- package/src/AvatarGroup/AvatarGroup.tsx +4 -1
- package/src/Banner/Banner.tsx +10 -5
- package/src/Breadcrumbs/Breadcrumbs.tsx +4 -1
- package/src/Button/Button.tsx +34 -4
- package/src/ButtonGroup/ButtonGroup.test.tsx +386 -2
- package/src/ButtonGroup/ButtonGroup.tsx +27 -11
- package/src/Calendar/Calendar.tsx +8 -5
- package/src/Card/Card.doc.mjs +3 -3
- package/src/Carousel/Carousel.tsx +10 -7
- package/src/Chat/ChatComposer.tsx +5 -1
- package/src/Chat/ChatComposerDrawer.tsx +9 -2
- package/src/Chat/ChatComposerInput.tsx +10 -4
- package/src/Chat/ChatLayout.tsx +5 -12
- package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
- package/src/Chat/ChatMessage.tsx +5 -1
- package/src/Chat/ChatMessageBubble.tsx +2 -0
- package/src/Chat/ChatMessageList.doc.mjs +1 -1
- package/src/Chat/ChatMessageList.tsx +2 -0
- package/src/Chat/ChatMessageMetadata.tsx +15 -9
- package/src/Chat/ChatPastedTextToken.tsx +3 -1
- package/src/Chat/ChatSendButton.tsx +3 -1
- package/src/Chat/ChatToolCalls.doc.mjs +1 -1
- package/src/Chat/useSpeechRecognition.ts +11 -2
- package/src/Chat/useTriggerMenu.tsx +4 -2
- package/src/CheckboxInput/CheckboxInput.test.tsx +16 -0
- package/src/CheckboxInput/CheckboxInput.tsx +4 -2
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -1
- package/src/CheckboxList/CheckboxList.tsx +2 -0
- package/src/CheckboxList/CheckboxListItem.tsx +7 -1
- package/src/Citation/Citation.tsx +4 -2
- package/src/ClickableCard/ClickableCard.doc.mjs +1 -1
- package/src/CodeBlock/CodeBlock.tsx +91 -40
- package/src/Collapsible/Collapsible.doc.mjs +3 -3
- package/src/CommandPalette/CommandPalette.tsx +10 -3
- package/src/CommandPalette/CommandPaletteInput.test.tsx +1 -1
- package/src/CommandPalette/CommandPaletteInput.tsx +5 -1
- package/src/CommandPalette/CommandPaletteList.tsx +4 -1
- package/src/ContextMenu/ContextMenu.tsx +4 -1
- package/src/DateInput/DateInput.doc.mjs +2 -2
- package/src/DateInput/DateInput.tsx +13 -5
- package/src/DateRangeInput/DateRangeInput.doc.mjs +7 -7
- package/src/DateRangeInput/DateRangeInput.tsx +14 -6
- package/src/DateTimeInput/DateTimeInput.doc.mjs +2 -2
- package/src/DateTimeInput/DateTimeInput.tsx +17 -7
- package/src/Dialog/DialogHeader.tsx +4 -2
- package/src/DropdownMenu/DropdownMenu.tsx +12 -8
- package/src/EmptyState/EmptyState.doc.mjs +3 -3
- package/src/EmptyState/EmptyState.tsx +4 -1
- package/src/Field/Field.doc.mjs +2 -2
- package/src/FileInput/FileInput.tsx +3 -1
- package/src/Grid/Grid.doc.mjs +5 -5
- package/src/InputGroup/InputGroup.doc.mjs +1 -1
- package/src/Layer/useLayer.doc.mjs +2 -2
- package/src/Lightbox/Lightbox.doc.mjs +7 -2
- package/src/Lightbox/Lightbox.tsx +6 -4
- package/src/Lightbox/useLightbox.tsx +0 -1
- package/src/Link/Link.test.tsx +74 -2
- package/src/Link/Link.tsx +48 -3
- package/src/Markdown/Markdown.tsx +12 -4
- package/src/MobileNav/MobileNav.tsx +4 -2
- package/src/MobileNav/MobileNavToggle.tsx +5 -3
- package/src/MoreMenu/MoreMenu.tsx +4 -1
- package/src/MultiSelector/MultiSelector.tsx +14 -5
- package/src/NumberInput/NumberInput.tsx +3 -1
- package/src/Outline/Outline.tsx +4 -1
- package/src/OverflowList/OverflowList.doc.mjs +2 -2
- package/src/Overlay/useOverlay.tsx +0 -3
- package/src/Pagination/Pagination.tsx +35 -12
- package/src/Popover/usePopover.tsx +6 -1
- package/src/PowerSearch/PowerSearch.doc.mjs +9 -4
- package/src/PowerSearch/PowerSearch.tsx +22 -10
- package/src/PowerSearch/PowerSearchEditPopover.tsx +27 -20
- package/src/PowerSearch/PowerSearchToken.tsx +17 -6
- package/src/PowerSearch/PowerSearchValueEditor.tsx +34 -21
- package/src/PowerSearch/formatFilterValue.test.ts +282 -0
- package/src/PowerSearch/formatFilterValue.ts +21 -8
- package/src/PowerSearch/index.ts +5 -0
- package/src/PowerSearch/resolveOperatorLabel.ts +34 -0
- package/src/PowerSearch/types.ts +33 -9
- package/src/PowerSearch/usePowerSearchConfig.ts +59 -27
- package/src/PowerSearch/usePowerSearchSource.ts +17 -8
- package/src/RadioList/RadioList.doc.mjs +1 -1
- package/src/Resizable/ResizeHandle.tsx +4 -1
- package/src/Section/Section.doc.mjs +4 -4
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.tsx +11 -6
- package/src/SideNav/SideNav.tsx +4 -2
- package/src/SideNav/SideNavCollapseButton.tsx +3 -1
- package/src/SideNav/SideNavHeading.tsx +5 -3
- package/src/SideNav/SideNavItem.tsx +3 -1
- package/src/Slider/Slider.doc.mjs +2 -2
- package/src/Stack/Stack.doc.mjs +10 -10
- package/src/Switch/Switch.doc.mjs +2 -2
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +4 -2
- package/src/TabList/TabList.tsx +4 -1
- package/src/Table/BaseTable.tsx +8 -1
- package/src/Table/Table.doc.mjs +1 -1
- package/src/Table/Table.tsx +3 -1
- package/src/Table/index.ts +7 -0
- package/src/Table/plugins/filtering/useTableFiltering.tsx +31 -30
- package/src/Table/plugins/groupedRows/index.ts +7 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +231 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +351 -0
- package/src/Table/plugins/pagination/useTablePagination.tsx +4 -1
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +8 -4
- package/src/Table/plugins/rowIndex/index.ts +4 -0
- package/src/Table/plugins/rowIndex/useTableRowIndex.test.tsx +132 -0
- package/src/Table/plugins/rowIndex/useTableRowIndex.tsx +114 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +6 -7
- package/src/Table/plugins/sortable/useTableSortable.tsx +8 -5
- package/src/Table/useTableFiltering.doc.mjs +2 -2
- package/src/Table/useTableGroupedRows.doc.mjs +71 -0
- package/src/Table/useTableRowIndex.doc.mjs +50 -0
- package/src/Table/useTableSortable.doc.mjs +11 -11
- package/src/Text/Text.doc.mjs +2 -2
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.doc.mjs +2 -2
- package/src/TimeInput/TimeInput.tsx +6 -2
- package/src/Timestamp/Timestamp.doc.mjs +4 -4
- package/src/Timestamp/Timestamp.test.tsx +120 -7
- package/src/Timestamp/Timestamp.tsx +21 -1
- package/src/Toast/Toast.tsx +3 -1
- package/src/Toast/ToastViewport.tsx +3 -1
- package/src/Token/Token.tsx +4 -2
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.tsx +4 -4
- package/src/Toolbar/Toolbar.doc.mjs +1 -1
- package/src/TopNav/TopNav.doc.mjs +1 -0
- package/src/TopNav/TopNav.test.tsx +30 -0
- package/src/TopNav/TopNav.tsx +6 -4
- package/src/TopNav/TopNavHeading.tsx +5 -3
- package/src/TreeList/TreeList.tsx +5 -1
- package/src/TreeList/TreeListItem.tsx +6 -1
- package/src/Typeahead/BaseTypeahead.tsx +11 -4
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.tsx +3 -1
- package/src/VisuallyHidden/VisuallyHidden.tsx +1 -3
- package/src/authoring/doc.ts +166 -0
- package/src/authoring/index.test.ts +52 -0
- package/src/authoring/index.ts +50 -0
- package/src/authoring/integration.ts +42 -0
- package/src/authoring/template.ts +74 -0
- package/src/config.test.ts +16 -0
- package/src/config.ts +92 -0
- package/src/docPropLiterals.test.ts +902 -0
- package/src/hooks/useHotkeys.doc.mjs +5 -5
- package/src/i18n/InternationalizationContext.ts +39 -0
- package/src/i18n/InternationalizationProvider.doc.mjs +72 -0
- package/src/i18n/InternationalizationProvider.tsx +83 -0
- package/src/i18n/__tests__/e2e-pagination.test.tsx +172 -0
- package/src/i18n/__tests__/e2e-powersearch.test.tsx +130 -0
- package/src/i18n/__tests__/resolve.test.ts +284 -0
- package/src/i18n/index.ts +32 -0
- package/src/i18n/resolve.ts +165 -0
- package/src/i18n/translator.ts +31 -0
- package/src/i18n/types.ts +53 -0
- package/src/i18n/useTranslator.doc.mjs +46 -0
- package/src/i18n/useTranslator.ts +50 -0
- package/src/index.ts +3 -0
- package/dist/NavItem/index.d.ts +0 -9
- package/dist/NavItem/index.d.ts.map +0 -1
- package/dist/NavItem/index.js +0 -10
- package/src/NavItem/index.ts +0 -11
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
|
+
/**
|
|
5
|
+
* @file Guards doc prop types against opaque named unions (#1645).
|
|
6
|
+
* @input Every {Name}.doc.mjs plus the TypeScript sources they describe.
|
|
7
|
+
* @output Fails when a documented prop type hides its legal literal values.
|
|
8
|
+
* @position Repo-wide doc-shape guard, sibling of docPropReferences.test.ts.
|
|
9
|
+
*
|
|
10
|
+
* A prop type of `SpacingStep` or `TableSortState<TSortKey>` names a type
|
|
11
|
+
* without revealing what may be written into it. Readers without an IDE —
|
|
12
|
+
* agents especially — then guess: `gap={16}` (pixels, not a scale step),
|
|
13
|
+
* `direction: 'desc'` (the type says `'descending'`). Both fail to compile.
|
|
14
|
+
* `PropDoc.type` in docs-types.ts already asks for inlined unions
|
|
15
|
+
* (`"'primary' | 'secondary' | 'ghost'"`); this test holds docs to it.
|
|
16
|
+
*
|
|
17
|
+
* Two ways a doc prop can hide values, checked at different strictness:
|
|
18
|
+
*
|
|
19
|
+
* 1. DIRECT — the prop type *is* (or contains) a literal-union type name.
|
|
20
|
+
* `gap: SpacingStep` must read `gap: 0 | 0.5 | 1 | …`. The name never
|
|
21
|
+
* buys the reader anything the literals don't, so inlining is required;
|
|
22
|
+
* prose is not an escape (see ENUMERATED_IN_PROSE for the one exception).
|
|
23
|
+
*
|
|
24
|
+
* 2. INDIRECT — the prop type names a composite whose own definition
|
|
25
|
+
* carries a literal union: `status: InputStatus` hides
|
|
26
|
+
* `'warning' | 'error' | 'success'`, `sort: TableSortState` hides
|
|
27
|
+
* `direction: 'ascending' | 'descending'`. Here the values may be
|
|
28
|
+
* surfaced in the type (spell the shape out — preferred when the object
|
|
29
|
+
* is small) *or* in the description, because forcing a twelve-field
|
|
30
|
+
* object into the type column would make the docs worse, not better.
|
|
31
|
+
*
|
|
32
|
+
* Both registries are derived from the TypeScript sources at test time, so a
|
|
33
|
+
* new member on a union (or a renamed one) fails here until docs catch up.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import {describe, it, expect} from 'vitest';
|
|
37
|
+
import {readdirSync, readFileSync} from 'node:fs';
|
|
38
|
+
import {join, relative} from 'node:path';
|
|
39
|
+
|
|
40
|
+
const SRC_DIR = __dirname;
|
|
41
|
+
|
|
42
|
+
type DocExport = {
|
|
43
|
+
name?: string;
|
|
44
|
+
props?: PropEntry[];
|
|
45
|
+
components?: {name?: string; props?: PropEntry[]}[];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type DocModule = {
|
|
49
|
+
docs?: DocExport;
|
|
50
|
+
docsZh?: DocExport;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type PropEntry = {name: string; type?: string; description?: string};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Unions too large to read inside a prop-type cell. Exempt from inlining,
|
|
57
|
+
* but only while the prop's description enumerates every member — the
|
|
58
|
+
* values still have to be discoverable from the docs alone.
|
|
59
|
+
*/
|
|
60
|
+
const ENUMERATED_IN_PROSE = new Set(['IconName']);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generic wrappers whose type argument is still a value the consumer writes,
|
|
64
|
+
* so the walk descends through them. Every other generic (`ReactElement<P>`,
|
|
65
|
+
* `Record<K, V>`, `Ref<T>`) wraps a type the consumer never spells out by
|
|
66
|
+
* hand, and descending into it would demand, say, all 26 icon names in
|
|
67
|
+
* Button's `endContent` docs.
|
|
68
|
+
*/
|
|
69
|
+
const TRANSPARENT_WRAPPERS = new Set(['Array', 'ReadonlyArray']);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A union of nothing but string/number literals (plus null/undefined).
|
|
73
|
+
*
|
|
74
|
+
* Built so each separator has exactly ONE way to be parsed. The obvious
|
|
75
|
+
* spelling — `(?:\s*\|?\s*LITERAL\s*(?:\|\s*)?)+` — lets a repetition's
|
|
76
|
+
* trailing `(?:\|\s*)?` and the next one's leading `\|?` both claim the same
|
|
77
|
+
* pipe, so every separator doubles the parse tree. On a union that *fails* to
|
|
78
|
+
* match (a literal prefix ending in a named type, e.g. `'a' | 'b' | … |
|
|
79
|
+
* ComponentType` — an ordinary TS pattern) the engine explores all 2^n of them:
|
|
80
|
+
* measured at 1.2s for 10 members and 7.8s for 12. This form is linear, and
|
|
81
|
+
* agrees with the old one on every type alias in the package.
|
|
82
|
+
*/
|
|
83
|
+
const LITERAL = String.raw`(?:'[^']*'|-?\d+(?:\.\d+)?|null|undefined)`;
|
|
84
|
+
const LITERAL_UNION = new RegExp(
|
|
85
|
+
String.raw`^\s*\|?\s*${LITERAL}(?:\s*\|\s*${LITERAL})*\s*$`,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
function findFiles(dir: string, suffixes: string[]): string[] {
|
|
89
|
+
const out: string[] = [];
|
|
90
|
+
for (const entry of readdirSync(dir, {withFileTypes: true})) {
|
|
91
|
+
const full = join(dir, entry.name);
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
out.push(...findFiles(full, suffixes));
|
|
94
|
+
} else if (suffixes.some(s => entry.name.endsWith(s))) {
|
|
95
|
+
out.push(full);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Strip comments so a doc block's prose never reads as type syntax. */
|
|
102
|
+
function stripComments(src: string): string {
|
|
103
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Names a consumer would have to write values for, given a type expression.
|
|
108
|
+
* Skips string literals, generic parameters, and the type arguments of any
|
|
109
|
+
* wrapper that isn't transparent.
|
|
110
|
+
*/
|
|
111
|
+
function authoredTypeNames(expr: string): string[] {
|
|
112
|
+
const names: string[] = [];
|
|
113
|
+
const text = expr.replace(/'[^']*'/g, "''");
|
|
114
|
+
let i = 0;
|
|
115
|
+
while (i < text.length) {
|
|
116
|
+
const match = /[A-Za-z_$][A-Za-z0-9_$]*/.exec(text.slice(i));
|
|
117
|
+
if (!match) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const start = i + match.index;
|
|
121
|
+
const name = match[0];
|
|
122
|
+
let end = start + name.length;
|
|
123
|
+
if (/^[A-Z]/.test(name)) {
|
|
124
|
+
names.push(name);
|
|
125
|
+
}
|
|
126
|
+
// A '<' directly after the name opens that name's type arguments.
|
|
127
|
+
const rest = text.slice(end);
|
|
128
|
+
const open = /^\s*</.exec(rest);
|
|
129
|
+
if (open) {
|
|
130
|
+
let depth = 0;
|
|
131
|
+
let j = end + open[0].length - 1;
|
|
132
|
+
for (; j < text.length; j++) {
|
|
133
|
+
if (text[j] === '<') {
|
|
134
|
+
depth++;
|
|
135
|
+
} else if (text[j] === '>') {
|
|
136
|
+
depth--;
|
|
137
|
+
if (depth === 0) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const args = text.slice(end + open[0].length, j);
|
|
143
|
+
if (TRANSPARENT_WRAPPERS.has(name)) {
|
|
144
|
+
names.push(...authoredTypeNames(args));
|
|
145
|
+
}
|
|
146
|
+
end = j + 1;
|
|
147
|
+
}
|
|
148
|
+
i = end;
|
|
149
|
+
}
|
|
150
|
+
return names;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- Registries, read straight from the TypeScript sources -----------------
|
|
154
|
+
|
|
155
|
+
/** Literal-union type name → its members, e.g. "'start' | 'center'". */
|
|
156
|
+
const literalUnions = new Map<string, string>();
|
|
157
|
+
/** Every other exported type/interface name → its definition body. */
|
|
158
|
+
const namedBodies = new Map<string, string>();
|
|
159
|
+
|
|
160
|
+
for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
|
|
161
|
+
if (file.includes('.test.')) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const src = stripComments(readFileSync(file, 'utf8'));
|
|
165
|
+
for (const m of src.matchAll(
|
|
166
|
+
/export type ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
|
|
167
|
+
)) {
|
|
168
|
+
const [, name, rhsRaw] = m;
|
|
169
|
+
const rhs = rhsRaw.replace(/\s+/g, ' ').trim();
|
|
170
|
+
if (LITERAL_UNION.test(rhs)) {
|
|
171
|
+
literalUnions.set(
|
|
172
|
+
name,
|
|
173
|
+
rhs
|
|
174
|
+
.replace(/^\|\s*/, '')
|
|
175
|
+
.split('|')
|
|
176
|
+
.map(s => s.trim())
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.join(' | '),
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
namedBodies.set(name, rhs);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const m of src.matchAll(
|
|
185
|
+
/export interface ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*(?:extends [^{]+)?\{/g,
|
|
186
|
+
)) {
|
|
187
|
+
const open = src.indexOf('{', m.index);
|
|
188
|
+
let depth = 0;
|
|
189
|
+
let close = open;
|
|
190
|
+
for (; close < src.length; close++) {
|
|
191
|
+
if (src[close] === '{') {
|
|
192
|
+
depth++;
|
|
193
|
+
} else if (src[close] === '}') {
|
|
194
|
+
depth--;
|
|
195
|
+
if (depth === 0) {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
namedBodies.set(m[1], src.slice(open, close + 1).replace(/\s+/g, ' '));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Literal unions a consumer must know to author a value of `name`. */
|
|
205
|
+
function requiredUnions(
|
|
206
|
+
name: string,
|
|
207
|
+
seen = new Set<string>(),
|
|
208
|
+
): Map<string, string> {
|
|
209
|
+
const found = new Map<string, string>();
|
|
210
|
+
if (seen.has(name)) {
|
|
211
|
+
return found;
|
|
212
|
+
}
|
|
213
|
+
seen.add(name);
|
|
214
|
+
const literals = literalUnions.get(name);
|
|
215
|
+
if (literals) {
|
|
216
|
+
found.set(name, literals);
|
|
217
|
+
return found;
|
|
218
|
+
}
|
|
219
|
+
const body = namedBodies.get(name);
|
|
220
|
+
if (!body) {
|
|
221
|
+
return found;
|
|
222
|
+
}
|
|
223
|
+
for (const inner of authoredTypeNames(body)) {
|
|
224
|
+
for (const [k, v] of requiredUnions(inner, seen)) {
|
|
225
|
+
found.set(k, v);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return found;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// --- Source prop declarations, per component directory ---------------------
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* "{dir}::{Component}Props" → prop name → the type expression source declares.
|
|
235
|
+
*
|
|
236
|
+
* Keyed by the exact prop bag, never by directory: Chat/ alone holds
|
|
237
|
+
* ChatComposer, ChatMessage and ChatToolCalls, each with a differently-typed
|
|
238
|
+
* `status`, and judging one component's docs against a sibling's declaration
|
|
239
|
+
* is a false accusation. A component with no `{Name}Props` bag here (Heading
|
|
240
|
+
* re-exports its props from elsewhere) is simply not checked by this pass —
|
|
241
|
+
* a missed prop costs nothing, a wrong failure costs the guard its
|
|
242
|
+
* credibility.
|
|
243
|
+
*/
|
|
244
|
+
const sourcePropTypes = new Map<string, Map<string, string>>();
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Bag name → its members, for bags whose name is unique across the package;
|
|
248
|
+
* `null` marks a name declared in more than one place, which is never
|
|
249
|
+
* resolved rather than resolved wrongly.
|
|
250
|
+
*
|
|
251
|
+
* A hook's config bag does not live in the directory its doc does:
|
|
252
|
+
* `useTableSortable.doc.mjs` sits in `Table/`, but `UseTableSortableConfig`
|
|
253
|
+
* is declared in `Table/plugins/sortable/`. Directory-keying alone therefore
|
|
254
|
+
* cannot see it — which is why `sort`, the very prop #1645 was filed about,
|
|
255
|
+
* had no source-side guard at all until this index existed.
|
|
256
|
+
*/
|
|
257
|
+
const bagsByName = new Map<string, Map<string, string> | null>();
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Bags a hook's config may be called. Astryx spells it `Use{Name}Config` or
|
|
261
|
+
* `Use{Name}Options` (see UseTableSortableConfig, UsePopoverOptions); the
|
|
262
|
+
* other two are cheap insurance and cost nothing when absent.
|
|
263
|
+
*/
|
|
264
|
+
const HOOK_BAG_SUFFIXES = ['Config', 'Options', 'Props', 'Params'];
|
|
265
|
+
|
|
266
|
+
for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
|
|
267
|
+
if (file.includes('.test.')) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const dir = file.slice(0, file.lastIndexOf('/'));
|
|
271
|
+
const src = stripComments(readFileSync(file, 'utf8'));
|
|
272
|
+
for (const m of src.matchAll(
|
|
273
|
+
/export (?:interface|type) ([A-Z][A-Za-z0-9]*(?:Props|Config|Options|Params))(?:<[^>]*>)?\s*(?:extends [^{]+|=\s*)?\{/g,
|
|
274
|
+
)) {
|
|
275
|
+
const open = src.indexOf('{', m.index);
|
|
276
|
+
let depth = 0;
|
|
277
|
+
let close = open;
|
|
278
|
+
for (; close < src.length; close++) {
|
|
279
|
+
if (src[close] === '{') {
|
|
280
|
+
depth++;
|
|
281
|
+
} else if (src[close] === '}') {
|
|
282
|
+
depth--;
|
|
283
|
+
if (depth === 0) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const body = src.slice(open + 1, close);
|
|
289
|
+
const byProp = new Map<string, string>();
|
|
290
|
+
// Top-level `name?: Type;` members only — nested object literals would
|
|
291
|
+
// need a real parser, and a missed prop costs nothing here.
|
|
292
|
+
let depthInBody = 0;
|
|
293
|
+
for (const rawLine of body.split(/[;\n]/)) {
|
|
294
|
+
// `=>` is an arrow, not a closing angle bracket: counting it as one
|
|
295
|
+
// drives the depth negative and silently swallows every prop below a
|
|
296
|
+
// callback member. (It did, until this line.)
|
|
297
|
+
const line = rawLine.replace(/=>/g, '');
|
|
298
|
+
const opens = (line.match(/[{(<]/g) || []).length;
|
|
299
|
+
const closes = (line.match(/[})>]/g) || []).length;
|
|
300
|
+
const member = /^\s*([a-z][A-Za-z0-9]*)\??\s*:\s*(.+?)\s*$/.exec(rawLine);
|
|
301
|
+
if (depthInBody === 0 && member) {
|
|
302
|
+
byProp.set(member[1], member[2]);
|
|
303
|
+
}
|
|
304
|
+
depthInBody = Math.max(0, depthInBody + opens - closes);
|
|
305
|
+
}
|
|
306
|
+
sourcePropTypes.set(`${dir}::${m[1]}`, byProp);
|
|
307
|
+
// Second index, keyed by bag name alone, for the cross-directory hook
|
|
308
|
+
// lookup below. A duplicate name poisons the entry: guessing between two
|
|
309
|
+
// declarations is how a guard earns a false accusation.
|
|
310
|
+
bagsByName.set(m[1], bagsByName.has(m[1]) ? null : byProp);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The type source declares for `Component.prop`, if it can be found.
|
|
316
|
+
*
|
|
317
|
+
* Two lookups, most-specific first:
|
|
318
|
+
* 1. The component's own `{Name}Props` bag in its own directory. Exact, and
|
|
319
|
+
* the only pass that runs for components — Chat/ holds three differently
|
|
320
|
+
* typed `status` props, so a directory-wide search would misjudge them.
|
|
321
|
+
* 2. For hooks only (`useThing`), the uniquely-named `UseThingConfig` /
|
|
322
|
+
* `UseThingOptions` bag, wherever in the package it is declared.
|
|
323
|
+
*
|
|
324
|
+
* Anything else stays undefined and is judged on the doc alone: a missed prop
|
|
325
|
+
* costs nothing, a wrong failure costs the guard its credibility. (Heading
|
|
326
|
+
* re-exports its props and so has no bag here — deliberately unresolved.)
|
|
327
|
+
*/
|
|
328
|
+
function declaredType(
|
|
329
|
+
dir: string,
|
|
330
|
+
component: string,
|
|
331
|
+
prop: string,
|
|
332
|
+
): string | undefined {
|
|
333
|
+
const own = sourcePropTypes.get(`${dir}::${component}Props`)?.get(prop);
|
|
334
|
+
if (own !== undefined) {
|
|
335
|
+
return own;
|
|
336
|
+
}
|
|
337
|
+
if (!/^use[A-Z]/.test(component)) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
const pascal = component[0].toUpperCase() + component.slice(1);
|
|
341
|
+
for (const suffix of HOOK_BAG_SUFFIXES) {
|
|
342
|
+
const declared = bagsByName.get(`${pascal}${suffix}`)?.get(prop);
|
|
343
|
+
if (declared !== undefined) {
|
|
344
|
+
return declared;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// --- Doc props -------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
const docFiles = findFiles(SRC_DIR, ['.doc.mjs']);
|
|
353
|
+
|
|
354
|
+
interface DocProp {
|
|
355
|
+
file: string;
|
|
356
|
+
dir: string;
|
|
357
|
+
component: string;
|
|
358
|
+
prop: string;
|
|
359
|
+
type: string;
|
|
360
|
+
description: string;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const docProps: DocProp[] = [];
|
|
364
|
+
for (const file of docFiles) {
|
|
365
|
+
const mod = require(file) as DocModule;
|
|
366
|
+
// A type is language-neutral: the translated docs carry their own props[]
|
|
367
|
+
// (the `--zh` CLI surface renders them), so they are held to the same bar.
|
|
368
|
+
for (const key of ['docs', 'docsZh'] as const) {
|
|
369
|
+
const docs = mod[key];
|
|
370
|
+
if (!docs) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const entries = [
|
|
374
|
+
{name: docs.name, props: docs.props},
|
|
375
|
+
...(docs.components || []),
|
|
376
|
+
];
|
|
377
|
+
for (const {name, props} of entries) {
|
|
378
|
+
if (!name || !Array.isArray(props)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
for (const prop of props) {
|
|
382
|
+
if (typeof prop.type !== 'string') {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
docProps.push({
|
|
386
|
+
file: `${relative(SRC_DIR, file)}${key === 'docsZh' ? ' (zh)' : ''}`,
|
|
387
|
+
dir: file.slice(0, file.lastIndexOf('/')),
|
|
388
|
+
component: name,
|
|
389
|
+
prop: prop.name,
|
|
390
|
+
type: prop.type,
|
|
391
|
+
description: prop.description || '',
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function escapeRe(s: string): string {
|
|
399
|
+
return s.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** The value members of a union — `null`/`undefined` carry nothing to spell. */
|
|
403
|
+
function literalMembers(literals: string): string[] {
|
|
404
|
+
return literals
|
|
405
|
+
.split('|')
|
|
406
|
+
.map(s => s.trim())
|
|
407
|
+
.filter(s => s.startsWith("'") || /^-?\d/.test(s));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Where a literal has to be written to count as surfaced.
|
|
412
|
+
*
|
|
413
|
+
* `code` — a type column. The literal must appear *as a literal*: quotes
|
|
414
|
+
* intact for strings, digit-boundary-anchored for numbers.
|
|
415
|
+
* `prose` — a free-text description. Quotes are optional (prose may say
|
|
416
|
+
* `direction is ascending or descending`), but the boundary still holds.
|
|
417
|
+
*
|
|
418
|
+
* The boundary is the whole point. A bare `text.includes('sm')` is satisfied
|
|
419
|
+
* by `'xsm'`, and `text.includes('1')` by `1.5` or `10` — so a doc could drop
|
|
420
|
+
* a member and stay "green" purely because a longer sibling contains its
|
|
421
|
+
* characters. Every union whose members share substrings (TextSize, TextType,
|
|
422
|
+
* SpacingStep, IconName) is exposed to that, which is most of them.
|
|
423
|
+
*/
|
|
424
|
+
type Surface = 'code' | 'prose';
|
|
425
|
+
|
|
426
|
+
function isSurfaced(text: string, literal: string, surface: Surface): boolean {
|
|
427
|
+
const isString = literal.startsWith("'");
|
|
428
|
+
const inner = isString ? literal.slice(1, -1) : literal;
|
|
429
|
+
if (isString) {
|
|
430
|
+
// In code, the quotes are part of the literal: 'xsm' does not contain
|
|
431
|
+
// the *literal* 'sm', only the characters. In prose they are optional.
|
|
432
|
+
const body = escapeRe(inner);
|
|
433
|
+
return surface === 'code'
|
|
434
|
+
? new RegExp(`['"\`]${body}['"\`]`).test(text)
|
|
435
|
+
: new RegExp(`(?<![\\w-])${body}(?![\\w-])`).test(text);
|
|
436
|
+
}
|
|
437
|
+
// Numbers: reject a match that is really part of a longer number — `1` must
|
|
438
|
+
// not be found inside `1.5`, `10` or `-1`. A trailing dot is allowed only
|
|
439
|
+
// when it is sentence punctuation ("steps go 0, 0.5, 1."), not a decimal.
|
|
440
|
+
return new RegExp(`(?<![\\w.-])${escapeRe(inner)}(?![\\w-])(?!\\.\\d)`).test(
|
|
441
|
+
text,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Members of `literals` not written out in `text`. */
|
|
446
|
+
function missingFrom(
|
|
447
|
+
text: string,
|
|
448
|
+
literals: string,
|
|
449
|
+
surface: Surface = 'code',
|
|
450
|
+
): string[] {
|
|
451
|
+
return literalMembers(literals).filter(
|
|
452
|
+
literal => !isSurfaced(text, literal, surface),
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
interface Violation {
|
|
457
|
+
where: string;
|
|
458
|
+
detail: string;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Reasons this one prop hides values a reader would have to guess at.
|
|
463
|
+
*
|
|
464
|
+
* `resolveFrom` is the type expression whose unions the reader must know;
|
|
465
|
+
* `type`/`description` are what the docs actually say. They are the same
|
|
466
|
+
* expression for the doc-side pass, and differ for the source-side pass,
|
|
467
|
+
* where the component's own `.tsx` declaration is the authority and the docs
|
|
468
|
+
* are the thing on trial. That second pass is what makes the guard
|
|
469
|
+
* drift-proof: inlined literals go stale the moment the union in source
|
|
470
|
+
* gains a member, and only the source knows that happened.
|
|
471
|
+
*
|
|
472
|
+
* Pure — the synthetic cases below exercise it directly, so the guard is
|
|
473
|
+
* proven to bite without dirtying a real doc.
|
|
474
|
+
*/
|
|
475
|
+
function hiddenValueReasons(
|
|
476
|
+
type: string,
|
|
477
|
+
description: string,
|
|
478
|
+
resolveFrom: string = type,
|
|
479
|
+
): string[] {
|
|
480
|
+
const reasons: string[] = [];
|
|
481
|
+
for (const named of authoredTypeNames(resolveFrom)) {
|
|
482
|
+
for (const [union, literals] of requiredUnions(named)) {
|
|
483
|
+
if (ENUMERATED_IN_PROSE.has(union)) {
|
|
484
|
+
const missing = missingFrom(description, literals, 'prose');
|
|
485
|
+
if (missing.length > 0) {
|
|
486
|
+
reasons.push(
|
|
487
|
+
`${union} is exempt from inlining only while its description ` +
|
|
488
|
+
`enumerates every value; these are missing from the ` +
|
|
489
|
+
`description: ${missing.join(', ')}.`,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
} else if (named === union) {
|
|
493
|
+
// DIRECT: the name is the union. Inlining is always affordable, so
|
|
494
|
+
// the type column is the only surface that counts.
|
|
495
|
+
if (missingFrom(type, literals, 'code').length > 0) {
|
|
496
|
+
reasons.push(
|
|
497
|
+
`names the union ${union} instead of inlining it. ` +
|
|
498
|
+
`Write: "${literals}".`,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
} else if (
|
|
502
|
+
// INDIRECT: the union hides inside a composite. Spell the shape out
|
|
503
|
+
// in the type, or name the values in the description. Each surface is
|
|
504
|
+
// read on its own terms — the type as code, the description as prose —
|
|
505
|
+
// rather than concatenated, so a value half-spelled in one and
|
|
506
|
+
// half-mentioned in the other cannot pass for a value that is written
|
|
507
|
+
// down anywhere.
|
|
508
|
+
literalMembers(literals).some(
|
|
509
|
+
literal =>
|
|
510
|
+
!isSurfaced(type, literal, 'code') &&
|
|
511
|
+
!isSurfaced(description, literal, 'prose'),
|
|
512
|
+
)
|
|
513
|
+
) {
|
|
514
|
+
reasons.push(
|
|
515
|
+
`${named} hides ${union} = ${literals}, and neither the type nor ` +
|
|
516
|
+
`the description says so. Spell the shape out in the type ` +
|
|
517
|
+
`(preferred for small objects) or name the legal values in the ` +
|
|
518
|
+
`description, so a reader without an IDE can see what is legal.`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return reasons;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function findViolations(): Violation[] {
|
|
527
|
+
const violations: Violation[] = [];
|
|
528
|
+
for (const {file, dir, component, prop, type, description} of docProps) {
|
|
529
|
+
const where = `${file} — ${component}.${prop}: "${type}"`;
|
|
530
|
+
const seen = new Set<string>();
|
|
531
|
+
const report = (detail: string) => {
|
|
532
|
+
if (!seen.has(detail)) {
|
|
533
|
+
seen.add(detail);
|
|
534
|
+
violations.push({where, detail});
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
// Pass 1: what the docs themselves name.
|
|
538
|
+
for (const detail of hiddenValueReasons(type, description)) {
|
|
539
|
+
report(detail);
|
|
540
|
+
}
|
|
541
|
+
// Pass 2: what the component actually declares in source.
|
|
542
|
+
const declared = declaredType(dir, component, prop);
|
|
543
|
+
if (declared) {
|
|
544
|
+
for (const detail of hiddenValueReasons(type, description, declared)) {
|
|
545
|
+
report(`source declares \`${prop}: ${declared}\` — ${detail}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return violations;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
describe('doc prop types surface their literal values (#1645)', () => {
|
|
553
|
+
it('sanity: finds doc props and literal-union types in source', () => {
|
|
554
|
+
expect(docProps.length).toBeGreaterThan(0);
|
|
555
|
+
expect(literalUnions.size).toBeGreaterThan(0);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('sanity: the registries read the real source definitions', () => {
|
|
559
|
+
expect(literalUnions.get('SpacingStep')).toBe(
|
|
560
|
+
'0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
561
|
+
);
|
|
562
|
+
expect(literalUnions.get('TableSortDirection')).toBe(
|
|
563
|
+
"'ascending' | 'descending'",
|
|
564
|
+
);
|
|
565
|
+
expect([...requiredUnions('InputStatus').keys()]).toContain(
|
|
566
|
+
'InputStatusType',
|
|
567
|
+
);
|
|
568
|
+
expect([...requiredUnions('TableSortState').keys()]).toContain(
|
|
569
|
+
'TableSortDirection',
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('sanity: does not descend into opaque generic wrappers', () => {
|
|
574
|
+
// Button's endContent is ReactElement<IconProps>; the consumer writes
|
|
575
|
+
// <Icon icon="check" />, not an IconProps object, so IconName's 26
|
|
576
|
+
// values are Icon's business, not Button's.
|
|
577
|
+
expect(authoredTypeNames('ReactElement<IconProps>')).toEqual([
|
|
578
|
+
'ReactElement',
|
|
579
|
+
]);
|
|
580
|
+
expect(authoredTypeNames('Record<string, MarkdownSource>')).toEqual([
|
|
581
|
+
'Record',
|
|
582
|
+
]);
|
|
583
|
+
expect(
|
|
584
|
+
authoredTypeNames('ReadonlyArray<TableSortEntry<TSortKey>>'),
|
|
585
|
+
).toEqual(['ReadonlyArray', 'TableSortEntry']);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('LITERAL_UNION rejects a long near-miss union in linear time', () => {
|
|
589
|
+
// Every `export type` in the package is fed to this regex, so it must stay
|
|
590
|
+
// cheap on the ones that DON'T match. The dangerous shape is a long literal
|
|
591
|
+
// prefix ending in something that isn't a literal — `'a' | 'b' | … |
|
|
592
|
+
// ComponentType`, the autocomplete escape hatch — which the regex can only
|
|
593
|
+
// reject after trying every way to parse it. An ambiguous pattern has 2^n
|
|
594
|
+
// of those. This union is 24 members long: linear, it is instant; ambiguous,
|
|
595
|
+
// it would outlive the CI job.
|
|
596
|
+
const nearMiss =
|
|
597
|
+
Array.from({length: 24}, (_, i) => `'v${i}'`).join(' | ') +
|
|
598
|
+
' | ComponentType';
|
|
599
|
+
const start = performance.now();
|
|
600
|
+
expect(LITERAL_UNION.test(nearMiss)).toBe(false);
|
|
601
|
+
expect(performance.now() - start).toBeLessThan(100);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('no documented prop type hides its legal values', () => {
|
|
605
|
+
const violations = findViolations();
|
|
606
|
+
expect(
|
|
607
|
+
violations,
|
|
608
|
+
violations.map(v => `${v.where}\n ${v.detail}`).join('\n'),
|
|
609
|
+
).toEqual([]);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
describe('the guard bites (synthetic props, not real docs)', () => {
|
|
613
|
+
it('flags a bare literal-union name and accepts the inlined form', () => {
|
|
614
|
+
expect(hiddenValueReasons('SpacingStep', 'Gap between items.')).toEqual([
|
|
615
|
+
expect.stringContaining('names the union SpacingStep'),
|
|
616
|
+
]);
|
|
617
|
+
expect(
|
|
618
|
+
hiddenValueReasons(
|
|
619
|
+
'0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
620
|
+
'Gap between items.',
|
|
621
|
+
),
|
|
622
|
+
).toEqual([]);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('is not fooled by prose alone for a direct union', () => {
|
|
626
|
+
// The #1645 failure mode: prose hints at the scale, the type column
|
|
627
|
+
// still says nothing, and the CLI signature builder (which splits the
|
|
628
|
+
// type on `|`) still has no values to print.
|
|
629
|
+
expect(
|
|
630
|
+
hiddenValueReasons('SpacingStep', 'Steps: 0, 0.5, 1, 1.5, 2, 3, 4.'),
|
|
631
|
+
).not.toEqual([]);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it('flags a partially inlined union — one forgotten member is enough', () => {
|
|
635
|
+
// Judged against what source declares, so a doc that drops 'stretch'
|
|
636
|
+
// (or an inlined SpacingStep that misses a step the scale later gains)
|
|
637
|
+
// is caught even though the doc no longer names the type at all.
|
|
638
|
+
expect(
|
|
639
|
+
hiddenValueReasons(
|
|
640
|
+
"'start' | 'center' | 'end'",
|
|
641
|
+
'Alignment.',
|
|
642
|
+
'GridAlignment',
|
|
643
|
+
),
|
|
644
|
+
).toEqual([expect.stringContaining('GridAlignment')]);
|
|
645
|
+
expect(
|
|
646
|
+
hiddenValueReasons(
|
|
647
|
+
"'start' | 'center' | 'end' | 'stretch'",
|
|
648
|
+
'Alignment.',
|
|
649
|
+
'GridAlignment',
|
|
650
|
+
),
|
|
651
|
+
).toEqual([]);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('the source-side pass indexes the real component prop bags', () => {
|
|
655
|
+
const gridDir = docProps.find(p => p.component === 'Grid')!.dir;
|
|
656
|
+
expect(declaredType(gridDir, 'Grid', 'gap')).toBe('SpacingStep');
|
|
657
|
+
expect(declaredType(gridDir, 'Grid', 'align')).toBe('GridAlignment');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('never judges a component against a sibling in the same directory', () => {
|
|
661
|
+
// Chat/ declares three differently-typed `status` props. Reading
|
|
662
|
+
// ChatComposer's docs against ChatMessageMetadata's declaration would
|
|
663
|
+
// be a false accusation, so the index is keyed by prop bag, not folder.
|
|
664
|
+
const chatDir = docProps.find(p => p.component === 'ChatComposer')!.dir;
|
|
665
|
+
expect(declaredType(chatDir, 'ChatComposer', 'status')).toBe(
|
|
666
|
+
'ChatComposerStatus',
|
|
667
|
+
);
|
|
668
|
+
expect(declaredType(chatDir, 'ChatMessageMetadata', 'status')).toBe(
|
|
669
|
+
'ChatMessageStatus',
|
|
670
|
+
);
|
|
671
|
+
// Heading re-exports its props, so there is no HeadingProps bag here:
|
|
672
|
+
// the pass must stay silent rather than borrow TextProps.
|
|
673
|
+
const textDir = docProps.find(p => p.component === 'Text')!.dir;
|
|
674
|
+
expect(declaredType(textDir, 'Heading', 'type')).toBeUndefined();
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('flags a composite that hides a union, and takes either surface', () => {
|
|
678
|
+
expect(hiddenValueReasons('InputStatus', 'Validation status.')).toEqual([
|
|
679
|
+
expect.stringContaining('InputStatus hides InputStatusType'),
|
|
680
|
+
]);
|
|
681
|
+
// Spelled out in the type…
|
|
682
|
+
expect(
|
|
683
|
+
hiddenValueReasons(
|
|
684
|
+
"{type: 'warning' | 'error' | 'success', message?: string}",
|
|
685
|
+
'Validation status.',
|
|
686
|
+
),
|
|
687
|
+
).toEqual([]);
|
|
688
|
+
// …or named in the description.
|
|
689
|
+
expect(
|
|
690
|
+
hiddenValueReasons(
|
|
691
|
+
'InputStatus',
|
|
692
|
+
"Validation status. type is 'warning', 'error', or 'success'.",
|
|
693
|
+
),
|
|
694
|
+
).toEqual([]);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it("catches the issue's headline: sort direction is not 'asc'/'desc'", () => {
|
|
698
|
+
expect(
|
|
699
|
+
hiddenValueReasons(
|
|
700
|
+
'TableSortState<TSortKey>',
|
|
701
|
+
'Ordered array of {sortKey, direction} entries.',
|
|
702
|
+
),
|
|
703
|
+
).toEqual([expect.stringContaining("'ascending' | 'descending'")]);
|
|
704
|
+
// The word "descending" contains "ascending"'s literal only as a
|
|
705
|
+
// substring of itself, so a doc naming just one is still flagged.
|
|
706
|
+
expect(
|
|
707
|
+
hiddenValueReasons(
|
|
708
|
+
'TableSortState<TSortKey>',
|
|
709
|
+
"Entries sort 'ascending' by default.",
|
|
710
|
+
),
|
|
711
|
+
).toEqual([expect.stringContaining("'ascending' | 'descending'")]);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('lets a value-free type through untouched', () => {
|
|
715
|
+
expect(hiddenValueReasons('boolean', 'Disables the control.')).toEqual(
|
|
716
|
+
[],
|
|
717
|
+
);
|
|
718
|
+
expect(hiddenValueReasons('ReactNode', 'Slot content.')).toEqual([]);
|
|
719
|
+
expect(hiddenValueReasons('(value: string) => void', 'Called.')).toEqual(
|
|
720
|
+
[],
|
|
721
|
+
);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('a longer sibling literal does not mask a shorter one', () => {
|
|
725
|
+
// A substring match lies: 'sm' looks "present" inside 'xsm', and the
|
|
726
|
+
// spacing step 1 looks "present" inside 1.5 and 10. Both unions below
|
|
727
|
+
// are complete EXCEPT for the masked member — so the *only* thing that
|
|
728
|
+
// can fail them is the guard noticing the masked one is really gone.
|
|
729
|
+
// (Every other member is still listed. A test that drops extra members
|
|
730
|
+
// too would pass on the substring match alone and prove nothing —
|
|
731
|
+
// which is precisely why GridAlignment, whose members share no
|
|
732
|
+
// substrings, cannot exercise this class at all.)
|
|
733
|
+
expect(
|
|
734
|
+
hiddenValueReasons(
|
|
735
|
+
// TextSize without 'sm'. 'xsm' is still there, masking it.
|
|
736
|
+
"'4xs' | '3xs' | '2xs' | 'xsm' | 'base' | 'lg' | 'xl' | '2xl' | " +
|
|
737
|
+
"'3xl' | '4xl'",
|
|
738
|
+
'Size.',
|
|
739
|
+
'TextSize',
|
|
740
|
+
),
|
|
741
|
+
).toEqual([expect.stringContaining('TextSize')]);
|
|
742
|
+
expect(
|
|
743
|
+
hiddenValueReasons(
|
|
744
|
+
// SpacingStep without the step 1. 1.5 and 10 still mask it.
|
|
745
|
+
'0 | 0.5 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
746
|
+
'Gap.',
|
|
747
|
+
'SpacingStep',
|
|
748
|
+
),
|
|
749
|
+
).toEqual([expect.stringContaining('SpacingStep')]);
|
|
750
|
+
// …and the complete unions still pass, so the boundary match is not
|
|
751
|
+
// simply rejecting everything.
|
|
752
|
+
expect(
|
|
753
|
+
hiddenValueReasons(
|
|
754
|
+
"'4xs' | '3xs' | '2xs' | 'xsm' | 'sm' | 'base' | 'lg' | 'xl' | " +
|
|
755
|
+
"'2xl' | '3xl' | '4xl'",
|
|
756
|
+
'Size.',
|
|
757
|
+
'TextSize',
|
|
758
|
+
),
|
|
759
|
+
).toEqual([]);
|
|
760
|
+
expect(
|
|
761
|
+
hiddenValueReasons(
|
|
762
|
+
'0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
763
|
+
'Gap.',
|
|
764
|
+
'SpacingStep',
|
|
765
|
+
),
|
|
766
|
+
).toEqual([]);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it('holds the prose exemption to its bargain', () => {
|
|
770
|
+
// IconName may stay named — but only while every value is in the prose.
|
|
771
|
+
const enumerated = docProps.find(
|
|
772
|
+
p => p.component === 'Icon' && p.prop === 'icon',
|
|
773
|
+
);
|
|
774
|
+
expect(enumerated?.type).toContain('IconName');
|
|
775
|
+
expect(
|
|
776
|
+
hiddenValueReasons(enumerated!.type, enumerated!.description),
|
|
777
|
+
).toEqual([]);
|
|
778
|
+
// Drop the enumeration and the exemption stops covering it.
|
|
779
|
+
expect(
|
|
780
|
+
hiddenValueReasons('IconName | ComponentType<SVGProps>', 'An icon.'),
|
|
781
|
+
).toEqual([expect.stringContaining('exempt from inlining only while')]);
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* The synthetic cases above prove the predicate bites. These prove the
|
|
787
|
+
* *shipped docs are actually wired to it* — each one takes a real doc
|
|
788
|
+
* entry, stages the exact drift the PR exists to prevent, and asserts the
|
|
789
|
+
* guard fails. A guard that green-lights the regression it was built to
|
|
790
|
+
* catch is decoration; these are the tests that stop it becoming one.
|
|
791
|
+
*/
|
|
792
|
+
describe('the guard bites the real docs when they drift', () => {
|
|
793
|
+
const realProp = (component: string, prop: string): DocProp => {
|
|
794
|
+
const found = docProps.find(
|
|
795
|
+
p =>
|
|
796
|
+
p.component === component &&
|
|
797
|
+
p.prop === prop &&
|
|
798
|
+
!p.file.includes('(zh)'),
|
|
799
|
+
);
|
|
800
|
+
if (!found) {
|
|
801
|
+
throw new Error(`no doc prop ${component}.${prop}`);
|
|
802
|
+
}
|
|
803
|
+
return found;
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
/** What source says this doc prop is — the authority drift is judged against. */
|
|
807
|
+
const authority = (p: DocProp): string => {
|
|
808
|
+
const declared = declaredType(p.dir, p.component, p.prop);
|
|
809
|
+
if (!declared) {
|
|
810
|
+
throw new Error(
|
|
811
|
+
`${p.component}.${p.prop} has no source declaration, so nothing ` +
|
|
812
|
+
`holds its inlined literals to the union in source.`,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return declared;
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
it("#1645's headline prop is guarded: useTableSortable.sort", () => {
|
|
819
|
+
// The hook's config bag is UseTableSortableConfig, and it lives in
|
|
820
|
+
// Table/plugins/sortable/ while the doc sits in Table/ — so this only
|
|
821
|
+
// resolves because the source pass looks past `{Name}Props` *and* past
|
|
822
|
+
// the doc's own directory.
|
|
823
|
+
const sort = realProp('useTableSortable', 'sort');
|
|
824
|
+
expect(authority(sort)).toBe('TableSortState<TSortKey>');
|
|
825
|
+
// The shipped doc is honest today…
|
|
826
|
+
expect(
|
|
827
|
+
hiddenValueReasons(sort.type, sort.description, authority(sort)),
|
|
828
|
+
).toEqual([]);
|
|
829
|
+
// …and the moment it stops naming 'descending', the guard fails. This
|
|
830
|
+
// is the doc lying about the legal sort values all over again — the
|
|
831
|
+
// literal bug #1645 was filed for.
|
|
832
|
+
const stale = (s: string) => s.replace(/'?descending'?/g, '');
|
|
833
|
+
expect(stale(sort.type)).not.toBe(sort.type);
|
|
834
|
+
expect(
|
|
835
|
+
hiddenValueReasons(
|
|
836
|
+
stale(sort.type),
|
|
837
|
+
stale(sort.description),
|
|
838
|
+
authority(sort),
|
|
839
|
+
),
|
|
840
|
+
).toEqual([expect.stringContaining("'ascending' | 'descending'")]);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it('Timestamp.size cannot quietly lose a step masked by a sibling', () => {
|
|
844
|
+
const size = realProp('Timestamp', 'size');
|
|
845
|
+
expect(authority(size)).toBe('TextSize');
|
|
846
|
+
expect(
|
|
847
|
+
hiddenValueReasons(size.type, size.description, authority(size)),
|
|
848
|
+
).toEqual([]);
|
|
849
|
+
// Delete 'sm'. A substring match calls it present — 'xsm' contains it.
|
|
850
|
+
const stale = size.type.replace("'sm' | ", '');
|
|
851
|
+
expect(stale).not.toBe(size.type);
|
|
852
|
+
expect(stale).toContain("'xsm'");
|
|
853
|
+
expect(
|
|
854
|
+
hiddenValueReasons(stale, size.description, authority(size)),
|
|
855
|
+
).toEqual([expect.stringContaining('TextSize')]);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* The two props that must NOT be inlined. Everything else in this file
|
|
860
|
+
* pushes toward spelling unions out; these two push back, because the
|
|
861
|
+
* docsite playground reads `type` to choose a control. What each one is
|
|
862
|
+
* really protecting differs, and the specifics live next to the playground
|
|
863
|
+
* itself — see apps/docsite/src/__tests__/component-prop-controls.test.ts,
|
|
864
|
+
* which exercises the actual gate. These only pin the doc's shape, so the
|
|
865
|
+
* next person to "finish the job" trips here first.
|
|
866
|
+
*/
|
|
867
|
+
it('AppShell.mobileNav stays a bare ReactNode (the editor hides on an exact string)', () => {
|
|
868
|
+
const nav = realProp('AppShell', 'mobileNav');
|
|
869
|
+
// PropertyEditor's UNSUPPORTED_PROP_TYPES is a Set of exact type strings.
|
|
870
|
+
// Widening drops that match, and the prop would then be hidden only by
|
|
871
|
+
// the second, incidental gate (its slotElements). Keep the exact string.
|
|
872
|
+
expect(nav.type).toBe('ReactNode');
|
|
873
|
+
// The values still have to be discoverable — they live in the prose.
|
|
874
|
+
expect(nav.description).toContain('breakpoint');
|
|
875
|
+
expect(nav.description).toContain("'md'");
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it('Lightbox.media keeps naming its type (spelling it out yields a string control)', () => {
|
|
879
|
+
const media = realProp('Lightbox', 'media');
|
|
880
|
+
expect(media.type).toBe('LightboxMedia | LightboxMedia[]');
|
|
881
|
+
// Same bargain as IconName: named is allowed only while the prose
|
|
882
|
+
// carries the values a consumer must write.
|
|
883
|
+
expect(media.description).toContain("'image'");
|
|
884
|
+
expect(media.description).toContain("'video'");
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('Grid.gap cannot quietly lose the step 1, masked by 1.5 and 10', () => {
|
|
888
|
+
const gap = realProp('Grid', 'gap');
|
|
889
|
+
expect(authority(gap)).toBe('SpacingStep');
|
|
890
|
+
expect(
|
|
891
|
+
hiddenValueReasons(gap.type, gap.description, authority(gap)),
|
|
892
|
+
).toEqual([]);
|
|
893
|
+
const stale = gap.type.replace('1 | 1.5', '1.5');
|
|
894
|
+
expect(stale).not.toBe(gap.type);
|
|
895
|
+
expect(stale).toContain('1.5');
|
|
896
|
+
expect(stale).toContain('10');
|
|
897
|
+
expect(
|
|
898
|
+
hiddenValueReasons(stale, gap.description, authority(gap)),
|
|
899
|
+
).toEqual([expect.stringContaining('SpacingStep')]);
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
});
|