@astryxdesign/core 0.1.3 → 0.1.4-canary.01e5b29
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 +51 -0
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +20 -18
- package/dist/AlertDialog/useImperativeAlertDialog.js +3 -3
- package/dist/AppShell/AppShell.d.ts +1 -1
- 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.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +27 -25
- package/dist/Avatar/AvatarStatusDot.js +5 -5
- package/dist/AvatarGroup/AvatarGroup.js +5 -5
- 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 -25
- package/dist/Blockquote/Blockquote.js +7 -7
- package/dist/Breadcrumbs/BreadcrumbItem.js +17 -17
- package/dist/Breadcrumbs/Breadcrumbs.js +7 -7
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +47 -26
- package/dist/ButtonGroup/ButtonGroup.js +7 -7
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +108 -64
- package/dist/Calendar/dayCellUtils.d.ts +67 -5
- package/dist/Calendar/dayCellUtils.d.ts.map +1 -1
- package/dist/Calendar/dayCellUtils.js +139 -18
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Card/Card.d.ts +4 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +18 -10
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +20 -19
- package/dist/Center/Center.js +3 -3
- package/dist/Chat/ChatComposer.js +23 -23
- package/dist/Chat/ChatComposerDrawer.js +14 -14
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +27 -15
- package/dist/Chat/ChatDictationButton.d.ts +1 -1
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +13 -11
- package/dist/Chat/ChatLayout.js +17 -17
- package/dist/Chat/ChatLayoutScrollButton.d.ts +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +12 -10
- package/dist/Chat/ChatMessage.js +12 -12
- package/dist/Chat/ChatMessageBubble.js +7 -7
- package/dist/Chat/ChatMessageList.js +14 -14
- package/dist/Chat/ChatMessageMetadata.d.ts +1 -1
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatMessageMetadata.js +14 -12
- package/dist/Chat/ChatPastedTextToken.js +13 -13
- package/dist/Chat/ChatSendButton.js +3 -3
- package/dist/Chat/ChatSystemMessage.d.ts +1 -1
- package/dist/Chat/ChatSystemMessage.d.ts.map +1 -1
- package/dist/Chat/ChatSystemMessage.js +17 -17
- package/dist/Chat/ChatTokenizedText.d.ts +1 -1
- package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
- package/dist/Chat/ChatTokenizedText.js +24 -15
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +81 -74
- package/dist/Chat/index.d.ts +1 -1
- package/dist/Chat/index.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +15 -15
- package/dist/CheckboxInput/CheckboxInput.d.ts +6 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +32 -24
- package/dist/CheckboxList/CheckboxList.js +9 -9
- package/dist/CheckboxList/CheckboxListItem.d.ts +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +8 -6
- package/dist/Citation/Citation.js +10 -10
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +32 -9
- package/dist/Code/Code.d.ts +15 -1
- package/dist/Code/Code.d.ts.map +1 -1
- package/dist/Code/Code.js +35 -5
- package/dist/Code/index.d.ts +1 -1
- package/dist/Code/index.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +88 -52
- package/dist/CodeBlock/index.d.ts +1 -1
- package/dist/CodeBlock/index.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.d.ts +9 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +110 -22
- package/dist/Collapsible/CollapsibleGroup.d.ts +39 -7
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +73 -12
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +31 -0
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +33 -3
- package/dist/Collapsible/index.d.ts +1 -0
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/Collapsible/useCollapsible.d.ts +4 -1
- package/dist/Collapsible/useCollapsible.d.ts.map +1 -1
- package/dist/Collapsible/useCollapsible.js +20 -6
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +36 -34
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +27 -7
- package/dist/CommandPalette/CommandPaletteFooter.js +16 -16
- package/dist/CommandPalette/CommandPaletteGroup.js +5 -5
- package/dist/CommandPalette/CommandPaletteInput.js +13 -13
- package/dist/CommandPalette/CommandPaletteItem.js +3 -3
- package/dist/CommandPalette/CommandPaletteList.js +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts +9 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +16 -11
- package/dist/DateInput/DateInput.js +18 -18
- package/dist/DateRangeInput/DateRangeInput.js +23 -23
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -7
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +30 -26
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +9 -9
- package/dist/Dialog/DialogHeader.js +16 -16
- package/dist/Dialog/useImperativeDialog.js +3 -3
- package/dist/Divider/Divider.d.ts +0 -22
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +7 -7
- package/dist/DropdownMenu/DropdownMenu.js +10 -10
- package/dist/DropdownMenu/DropdownMenuItem.d.ts +2 -17
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -3
- package/dist/DropdownMenu/renderDropdownItems.js +10 -10
- 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.js +29 -29
- package/dist/FormLayout/FormLayout.js +5 -5
- package/dist/Grid/Grid.d.ts +4 -3
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +29 -12
- 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.d.ts.map +1 -1
- package/dist/Icon/Icon.js +16 -12
- package/dist/Icon/defaultIcons.js +116 -116
- package/dist/IconButton/IconButton.js +3 -3
- package/dist/InputGroup/InputGroup.js +11 -11
- package/dist/InputGroup/InputGroupText.js +3 -3
- package/dist/Item/Item.js +18 -18
- 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 +55 -31
- package/dist/Lightbox/useLightbox.js +3 -3
- package/dist/Link/Link.js +15 -15
- 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 +102 -102
- package/dist/Markdown/parser.d.ts +7 -0
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +300 -6
- package/dist/MetadataList/MetadataList.js +10 -10
- package/dist/MetadataList/MetadataListItem.js +15 -15
- package/dist/MobileNav/MobileNav.js +15 -15
- package/dist/MobileNav/MobileNavToggle.js +5 -5
- package/dist/MoreMenu/MoreMenu.js +3 -3
- package/dist/MultiSelector/MultiSelector.d.ts +7 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +64 -55
- 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 +48 -32
- package/dist/Outline/Outline.js +15 -15
- 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.js +3 -3
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +125 -72
- package/dist/Popover/Popover.js +16 -16
- package/dist/Popover/usePopover.js +7 -7
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +110 -92
- package/dist/PowerSearch/PowerSearchEditPopover.js +84 -84
- package/dist/PowerSearch/PowerSearchFilterEditor.js +3 -3
- package/dist/PowerSearch/PowerSearchToken.js +5 -5
- package/dist/PowerSearch/PowerSearchValueEditor.js +54 -54
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +25 -18
- package/dist/RadioList/RadioList.d.ts +7 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +10 -8
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +30 -22
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +28 -10
- package/dist/Section/Section.js +5 -5
- package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +32 -10
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +18 -12
- package/dist/SelectableCard/SelectableCard.js +5 -5
- package/dist/Selector/Selector.d.ts +6 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +54 -45
- package/dist/Selector/SelectorOption.js +3 -3
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +38 -34
- package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +8 -6
- package/dist/SideNav/SideNavHeading.js +87 -87
- package/dist/SideNav/SideNavItem.js +42 -42
- package/dist/SideNav/SideNavSection.d.ts +1 -1
- package/dist/SideNav/SideNavSection.d.ts.map +1 -1
- package/dist/SideNav/SideNavSection.js +18 -16
- package/dist/Skeleton/Skeleton.js +3 -3
- package/dist/Slider/Slider.d.ts +6 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +31 -22
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +10 -10
- package/dist/StatusDot/StatusDot.js +5 -5
- package/dist/Switch/Switch.d.ts +13 -4
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +41 -30
- package/dist/TabList/Tab.js +18 -18
- package/dist/TabList/TabList.js +5 -5
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +57 -30
- package/dist/TabList/tab.markers.stylex.d.ts +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +51 -39
- package/dist/Table/Table.js +7 -7
- package/dist/Table/TableBody.d.ts +1 -1
- package/dist/Table/TableBody.d.ts.map +1 -1
- package/dist/Table/TableBody.js +9 -5
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +76 -7
- package/dist/Table/TableFooter.d.ts +1 -1
- package/dist/Table/TableFooter.d.ts.map +1 -1
- package/dist/Table/TableFooter.js +9 -5
- package/dist/Table/TableHeader.d.ts +1 -1
- package/dist/Table/TableHeader.d.ts.map +1 -1
- package/dist/Table/TableHeader.js +9 -5
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/TableRow.js +5 -5
- package/dist/Table/index.d.ts +2 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +14 -11
- package/dist/Table/plugins/filtering/useTableFiltering.js +71 -71
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +5 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +7 -7
- package/dist/Table/plugins/rowExpansion/index.d.ts +3 -0
- package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowExpansion/index.js +3 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +90 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +398 -0
- package/dist/Table/plugins/selection/useTableSelection.js +18 -18
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +17 -17
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
- package/dist/Table/tableContextMenu.d.ts +5 -1
- package/dist/Table/tableContextMenu.d.ts.map +1 -1
- package/dist/Table/tableContextMenu.js +19 -12
- package/dist/Text/Text.d.ts +1 -1
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +14 -13
- package/dist/Text/text.stylex.d.ts +35 -0
- package/dist/Text/text.stylex.d.ts.map +1 -1
- package/dist/Text/text.stylex.js +52 -1
- package/dist/TextArea/TextArea.js +14 -14
- package/dist/TextInput/TextInput.js +15 -15
- package/dist/Thumbnail/Thumbnail.d.ts +3 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +30 -29
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +26 -21
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +37 -26
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +25 -17
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +23 -11
- 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.js +21 -21
- package/dist/Tokenizer/Tokenizer.d.ts +6 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +38 -29
- 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.js +32 -32
- package/dist/TopNav/TopNavHeading.js +67 -67
- package/dist/TopNav/TopNavItem.js +5 -5
- package/dist/TopNav/TopNavMegaMenu.js +30 -30
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +25 -14
- package/dist/TopNav/TopNavMegaMenuItem.js +16 -16
- package/dist/TopNav/TopNavMenu.js +32 -32
- package/dist/TreeList/TreeList.js +8 -8
- package/dist/TreeList/TreeListBranches.js +11 -11
- package/dist/TreeList/TreeListItem.js +30 -30
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +23 -20
- package/dist/Typeahead/Typeahead.js +12 -12
- package/dist/Typeahead/TypeaheadItem.js +10 -10
- package/dist/VStack/VStack.js +3 -3
- package/dist/astryx.css +23 -0
- package/dist/astryx.umd.js +60 -58
- package/dist/astryx.umd.js.map +4 -4
- package/dist/docs-types.d.ts +13 -1
- package/dist/docs-types.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFocusTrap.d.ts +3 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +38 -1
- package/dist/hooks/useHotkeys.d.ts +51 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -0
- package/dist/hooks/useHotkeys.js +160 -0
- package/dist/hooks/useInteractiveRole.d.ts +6 -2
- package/dist/hooks/useInteractiveRole.d.ts.map +1 -1
- package/dist/hooks/useInteractiveRole.js +6 -2
- package/dist/hooks/useKeyboardHint.js +9 -9
- package/dist/hooks/useStreamingText.d.ts +3 -1
- package/dist/hooks/useStreamingText.d.ts.map +1 -1
- package/dist/hooks/useStreamingText.js +14 -5
- package/dist/theme/MediaTheme.js +3 -3
- package/dist/theme/Theme.js +7 -7
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +10 -3
- package/dist/theme/hct.d.ts +0 -15
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/syntax/SyntaxTheme.js +5 -5
- package/dist/theme/tokens.d.ts +10 -2
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +220 -4
- package/dist/utils/color.d.ts +42 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +150 -0
- package/dist/utils/composeEventHandlers.d.ts +30 -0
- package/dist/utils/composeEventHandlers.d.ts.map +1 -0
- package/dist/utils/composeEventHandlers.js +40 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +8 -5
- package/package.json +3 -3
- package/src/AlertDialog/AlertDialog.tsx +2 -0
- package/src/AppShell/AppShell.tsx +2 -0
- package/src/AspectRatio/AspectRatio.doc.mjs +1 -1
- package/src/Avatar/Avatar.doc.mjs +13 -2
- package/src/Avatar/Avatar.test.tsx +32 -1
- package/src/Avatar/Avatar.tsx +12 -20
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +38 -0
- package/src/Banner/Banner.tsx +8 -1
- package/src/Button/Button.tsx +39 -6
- package/src/ButtonGroup/ButtonGroup.doc.mjs +1 -1
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +201 -17
- package/src/Calendar/Calendar.tsx +79 -26
- package/src/Calendar/dayCellUtils.test.ts +306 -1
- package/src/Calendar/dayCellUtils.ts +206 -13
- package/src/Calendar/styles.ts +12 -2
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Card/Card.tsx +16 -6
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +13 -0
- package/src/Carousel/Carousel.tsx +1 -0
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.test.tsx +24 -0
- package/src/Chat/ChatComposerInput.tsx +17 -1
- package/src/Chat/ChatDictationButton.test.tsx +43 -0
- package/src/Chat/ChatDictationButton.tsx +4 -4
- package/src/Chat/ChatLayoutScrollButton.test.tsx +33 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
- package/src/Chat/ChatMessageMetadata.test.tsx +32 -0
- package/src/Chat/ChatMessageMetadata.tsx +4 -6
- package/src/Chat/ChatSystemMessage.test.tsx +26 -11
- package/src/Chat/ChatSystemMessage.tsx +7 -7
- package/src/Chat/ChatTokenizedText.test.tsx +29 -16
- package/src/Chat/ChatTokenizedText.tsx +15 -8
- package/src/Chat/ChatToolCalls.test.tsx +55 -0
- package/src/Chat/ChatToolCalls.tsx +10 -12
- package/src/Chat/index.ts +1 -4
- package/src/Chat/useChatNewMessages.test.tsx +4 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +12 -4
- package/src/CheckboxInput/CheckboxInput.test.tsx +103 -0
- package/src/CheckboxInput/CheckboxInput.tsx +13 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
- package/src/CheckboxList/CheckboxList.test.tsx +19 -0
- package/src/CheckboxList/CheckboxListItem.tsx +11 -2
- package/src/Citation/Citation.doc.mjs +5 -0
- package/src/ClickableCard/ClickableCard.tsx +45 -2
- package/src/Code/Code.test.tsx +54 -0
- package/src/Code/Code.tsx +48 -4
- package/src/Code/index.ts +1 -1
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
- package/src/CodeBlock/CodeBlock.test.tsx +122 -3
- package/src/CodeBlock/CodeBlock.tsx +62 -4
- package/src/CodeBlock/index.ts +1 -1
- package/src/Collapsible/Collapsible.doc.mjs +8 -4
- package/src/Collapsible/Collapsible.test.tsx +405 -0
- package/src/Collapsible/Collapsible.tsx +86 -10
- package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
- package/src/Collapsible/CollapsibleGroup.test.tsx +318 -0
- package/src/Collapsible/CollapsibleGroup.tsx +107 -9
- package/src/Collapsible/CollapsibleGroupContext.tsx +42 -2
- package/src/Collapsible/index.ts +4 -0
- package/src/Collapsible/useCollapsible.ts +20 -6
- package/src/CommandPalette/CommandPalette.tsx +5 -3
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
- package/src/CommandPalette/CommandPaletteEmpty.tsx +15 -1
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
- package/src/ContextMenu/ContextMenu.test.tsx +15 -0
- package/src/ContextMenu/ContextMenu.tsx +19 -1
- package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
- package/src/DateInput/DateInput.doc.mjs +6 -1
- package/src/DateInput/DateInput.test.tsx +11 -20
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.test.tsx +31 -44
- package/src/DateRangeInput/DateRangeInput.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.doc.mjs +4 -3
- package/src/DateTimeInput/DateTimeInput.test.tsx +118 -16
- package/src/DateTimeInput/DateTimeInput.tsx +9 -8
- package/src/DateTimeInput/index.ts +1 -0
- package/src/Dialog/DialogHeader.doc.mjs +45 -0
- package/src/Divider/Divider.tsx +1 -23
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +5 -3
- package/src/DropdownMenu/DropdownMenuItem.tsx +5 -17
- package/src/EmptyState/EmptyState.doc.mjs +1 -1
- package/src/Field/Field.doc.mjs +1 -1
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FieldStatus/FieldStatus.test.tsx +282 -0
- package/src/FileInput/FileInput.doc.mjs +1 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.doc.mjs +3 -3
- package/src/Grid/Grid.test.tsx +43 -18
- package/src/Grid/Grid.tsx +26 -9
- package/src/Icon/Icon.test.tsx +41 -29
- package/src/Icon/Icon.tsx +6 -2
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/InteractiveRoleContext/InteractiveRoleContext.test.tsx +185 -0
- package/src/Item/Item.tsx +1 -1
- package/src/Layout/Layout.test.tsx +285 -0
- package/src/Layout/LayoutContent.doc.mjs +12 -0
- package/src/Layout/LayoutPanel.doc.mjs +40 -0
- package/src/Layout/LayoutSlots.test.tsx +364 -0
- package/src/Layout/__tests__/edgeCompensation.test.tsx +72 -0
- package/src/Lightbox/Lightbox.doc.mjs +5 -0
- package/src/Lightbox/Lightbox.test.tsx +123 -2
- package/src/Lightbox/Lightbox.tsx +29 -1
- package/src/Markdown/Markdown.test.tsx +23 -0
- package/src/Markdown/Markdown.tsx +9 -7
- package/src/Markdown/incremental.test.ts +36 -0
- package/src/Markdown/parser.perf.test.ts +34 -12
- package/src/Markdown/parser.test.ts +210 -0
- package/src/Markdown/parser.ts +301 -6
- package/src/MobileNav/MobileNav.doc.mjs +20 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +14 -1
- package/src/MultiSelector/MultiSelector.test.tsx +33 -0
- package/src/MultiSelector/MultiSelector.tsx +20 -0
- package/src/NavMenu/NavMenu.doc.mjs +15 -0
- package/src/NumberInput/NumberInput.test.tsx +48 -3
- package/src/NumberInput/NumberInput.tsx +32 -15
- package/src/Outline/parseOutlineFromMarkdown.test.ts +80 -0
- package/src/OverflowList/OverflowList.test.tsx +416 -0
- package/src/Overlay/Overlay.doc.mjs +6 -0
- package/src/Overlay/Overlay.test.tsx +356 -0
- package/src/Pagination/Pagination.test.tsx +162 -0
- package/src/Pagination/Pagination.tsx +80 -26
- package/src/Popover/Popover.test.tsx +65 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +4 -4
- package/src/PowerSearch/PowerSearch.test.tsx +109 -1
- package/src/PowerSearch/PowerSearch.tsx +37 -16
- package/src/ProgressBar/ProgressBar.test.tsx +30 -0
- package/src/ProgressBar/ProgressBar.tsx +12 -5
- package/src/RadioList/RadioList.doc.mjs +7 -1
- package/src/RadioList/RadioList.test.tsx +65 -0
- package/src/RadioList/RadioList.tsx +10 -1
- package/src/RadioList/RadioListItem.tsx +13 -4
- package/src/Resizable/ResizeHandle.test.tsx +218 -0
- package/src/Resizable/ResizeHandle.tsx +22 -4
- package/src/Section/Section.test.tsx +4 -10
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +192 -0
- package/src/SegmentedControl/SegmentedControl.tsx +27 -5
- package/src/SegmentedControl/SegmentedControlItem.tsx +9 -4
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.doc.mjs +12 -1
- package/src/Selector/Selector.test.tsx +30 -0
- package/src/Selector/Selector.tsx +18 -0
- package/src/SideNav/SideNav.doc.mjs +7 -1
- package/src/SideNav/SideNav.test.tsx +66 -0
- package/src/SideNav/SideNav.tsx +6 -2
- package/src/SideNav/SideNavCollapseButton.tsx +4 -3
- package/src/SideNav/SideNavSection.tsx +6 -4
- package/src/SizeContext/SizeContext.test.tsx +226 -0
- package/src/Slider/Slider.doc.mjs +14 -2
- package/src/Slider/Slider.test.tsx +30 -0
- package/src/Slider/Slider.tsx +20 -0
- package/src/Spinner/Spinner.tsx +1 -0
- package/src/Switch/Switch.doc.mjs +21 -9
- package/src/Switch/Switch.test.tsx +137 -0
- package/src/Switch/Switch.tsx +32 -7
- package/src/TabList/TabList.doc.mjs +3 -3
- package/src/TabList/TabList.test.tsx +185 -1
- package/src/TabList/TabMenu.tsx +45 -9
- package/src/Table/BaseTable.tsx +16 -3
- package/src/Table/Table.test.tsx +87 -0
- package/src/Table/TableBody.tsx +15 -2
- package/src/Table/TableCell.tsx +87 -8
- package/src/Table/TableFooter.tsx +15 -2
- package/src/Table/TableHeader.tsx +15 -2
- package/src/Table/index.ts +5 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
- package/src/Table/plugins/pagination/paginateData.ts +5 -1
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
- package/src/Table/plugins/rowExpansion/index.ts +7 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +155 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +590 -0
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
- package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
- package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
- package/src/Table/tableContextMenu.test.tsx +27 -3
- package/src/Table/tableContextMenu.tsx +20 -2
- package/src/Table/useTablePagination.doc.mjs +4 -1
- package/src/Table/useTableRowExpansion.doc.mjs +80 -0
- package/src/Table/useTableSortable.doc.mjs +4 -1
- package/src/Text/Text.doc.mjs +2 -2
- package/src/Text/Text.test.tsx +11 -0
- package/src/Text/Text.tsx +4 -2
- package/src/Text/text.stylex.ts +41 -0
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +36 -2
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.doc.mjs +11 -6
- package/src/TimeInput/TimeInput.test.tsx +37 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Timestamp/Timestamp.test.tsx +52 -0
- package/src/Timestamp/Timestamp.tsx +30 -16
- package/src/Toast/Toast.tsx +11 -3
- package/src/Toast/ToastViewport.test.tsx +64 -0
- package/src/Toast/ToastViewport.tsx +12 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +16 -4
- package/src/Tokenizer/Tokenizer.test.tsx +33 -0
- package/src/Tokenizer/Tokenizer.tsx +19 -0
- package/src/Toolbar/Toolbar.test.tsx +1 -1
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TopNav/TopNav.test.tsx +18 -0
- package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +9 -2
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.doc.mjs +7 -2
- package/src/Typeahead/Typeahead.test.tsx +45 -1
- package/src/__tests__/TestIcon.tsx +26 -0
- package/src/__tests__/fastRoleQueries.ts +66 -0
- package/src/docPropReferences.test.ts +178 -0
- package/src/docs-types.ts +14 -0
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFocusTrap.doc.mjs +4 -2
- package/src/hooks/useFocusTrap.test.tsx +149 -1
- package/src/hooks/useFocusTrap.ts +50 -1
- package/src/hooks/useHotkeys.doc.mjs +52 -0
- package/src/hooks/useHotkeys.test.ts +245 -0
- package/src/hooks/useHotkeys.ts +203 -0
- package/src/hooks/useInteractiveRole.ts +12 -4
- package/src/hooks/useKeyboardHint.doc.mjs +6 -6
- package/src/hooks/useStreamingText.test.ts +54 -19
- package/src/hooks/useStreamingText.ts +16 -5
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +4 -13
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +29 -0
- package/src/theme/expandColorScale.ts +11 -6
- package/src/theme/hct.ts +9 -17
- package/src/theme/tokens.test.ts +182 -9
- package/src/theme/tokens.ts +239 -4
- package/src/theme/useTheme.test.tsx +20 -0
- package/src/utils/color.test.ts +133 -0
- package/src/utils/color.ts +148 -0
- package/src/utils/composeEventHandlers.test.ts +71 -0
- package/src/utils/composeEventHandlers.ts +44 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/mergeProps.test.ts +166 -0
- package/src/utils/sharedResizeObserver.test.ts +2 -2
- package/src/utils/timeParser.test.ts +13 -0
- package/src/utils/timeParser.ts +8 -5
package/src/theme/tokens.ts
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* build-time theme adapters, chart configuration, canvas/SVG rendering, tests,
|
|
11
11
|
* or plain JS theme objects for other styling libraries.
|
|
12
12
|
*
|
|
13
|
+
* Derived tokens can reference other tokens (`var(--color-accent)`) and use CSS
|
|
14
|
+
* color functions (`color-mix`). The resolver follows those references through
|
|
15
|
+
* the theme iteratively and evaluates the supported color functions so callers
|
|
16
|
+
* receive concrete raw values, not CSS expressions.
|
|
17
|
+
*
|
|
13
18
|
* SYNC: When modified, update:
|
|
14
19
|
* - /packages/core/src/theme/useTheme.ts
|
|
15
20
|
* - /packages/core/src/theme/index.ts
|
|
@@ -21,6 +26,7 @@ import {
|
|
|
21
26
|
type TokenName,
|
|
22
27
|
type TokenValue,
|
|
23
28
|
} from './defineTheme';
|
|
29
|
+
import {parseColor, formatColor} from '../utils/color';
|
|
24
30
|
|
|
25
31
|
/** Resolved color mode used when choosing the side of light/dark token values. */
|
|
26
32
|
export type ResolvedThemeMode = 'light' | 'dark';
|
|
@@ -144,14 +150,243 @@ function resolveXDSTokenValue(
|
|
|
144
150
|
return value;
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// Reference resolution — turn token expressions into concrete raw values
|
|
155
|
+
// =============================================================================
|
|
156
|
+
//
|
|
157
|
+
// Generated themes emit derived tokens as references to other tokens rather
|
|
158
|
+
// than baked-in values, so a scoped CSS override of a base token re-themes the
|
|
159
|
+
// subtree that references it. The CSS cascade resolves those references, but
|
|
160
|
+
// non-CSS consumers (canvas, SVG, data-viz) need the concrete value. These
|
|
161
|
+
// helpers replay the same resolution in JS: follow `var()` references through
|
|
162
|
+
// the resolved token map (iteratively, guarding against cycles) and evaluate
|
|
163
|
+
// the CSS color functions used by the theme generator against those values.
|
|
164
|
+
|
|
165
|
+
/** Index of the `)` matching the `(` at `openIndex`, or -1 when unbalanced. */
|
|
166
|
+
function findMatchingParen(input: string, openIndex: number): number {
|
|
167
|
+
let depth = 0;
|
|
168
|
+
for (let i = openIndex; i < input.length; i++) {
|
|
169
|
+
const char = input[i];
|
|
170
|
+
if (char === '(') {
|
|
171
|
+
depth++;
|
|
172
|
+
} else if (char === ')') {
|
|
173
|
+
depth--;
|
|
174
|
+
if (depth === 0) {
|
|
175
|
+
return i;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return -1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Split a color-mix component into its color and optional percentage. */
|
|
183
|
+
function parseMixComponent(
|
|
184
|
+
part: string,
|
|
185
|
+
): {color: string; percentage: number | null} | null {
|
|
186
|
+
const trimmed = part.trim();
|
|
187
|
+
const match = trimmed.match(/\s+([\d.]+)%$/);
|
|
188
|
+
if (match) {
|
|
189
|
+
return {
|
|
190
|
+
color: trimmed.slice(0, match.index).trim(),
|
|
191
|
+
percentage: parseFloat(match[1]),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {color: trimmed, percentage: null};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Evaluate `color-mix(in <space>, c1 [p1], c2 [p2])` to a concrete color.
|
|
199
|
+
*
|
|
200
|
+
* Supports the `srgb` color space (the one the theme generator emits) using
|
|
201
|
+
* the CSS Color 5 algorithm: normalize the percentages, interpolate the
|
|
202
|
+
* premultiplied channels, then apply the alpha multiplier when the mix weights
|
|
203
|
+
* sum to less than 100%. Returns null for anything it can't evaluate so the
|
|
204
|
+
* original expression is preserved rather than guessed.
|
|
205
|
+
*/
|
|
206
|
+
function evaluateColorMix(body: string): string | null {
|
|
207
|
+
const spaceMatch = body.match(/^in\s+([\w-]+)\s*,\s*(.+)$/s);
|
|
208
|
+
if (!spaceMatch) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const [, colorSpace, rest] = spaceMatch;
|
|
212
|
+
if (colorSpace.toLowerCase() !== 'srgb') {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const split = splitTopLevelComma(rest);
|
|
217
|
+
if (split === null) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const first = parseMixComponent(split[0]);
|
|
221
|
+
const second = parseMixComponent(split[1]);
|
|
222
|
+
if (first === null || second === null) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const c1 = parseColor(first.color);
|
|
227
|
+
const c2 = parseColor(second.color);
|
|
228
|
+
if (c1 === null || c2 === null) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Fill in omitted percentages, then normalize so they sum to 100%.
|
|
233
|
+
let p1: number;
|
|
234
|
+
let p2: number;
|
|
235
|
+
if (first.percentage !== null && second.percentage !== null) {
|
|
236
|
+
p1 = first.percentage;
|
|
237
|
+
p2 = second.percentage;
|
|
238
|
+
} else if (first.percentage !== null) {
|
|
239
|
+
p1 = first.percentage;
|
|
240
|
+
p2 = 100 - p1;
|
|
241
|
+
} else if (second.percentage !== null) {
|
|
242
|
+
p2 = second.percentage;
|
|
243
|
+
p1 = 100 - p2;
|
|
244
|
+
} else {
|
|
245
|
+
p1 = 50;
|
|
246
|
+
p2 = 50;
|
|
247
|
+
}
|
|
248
|
+
const sum = p1 + p2;
|
|
249
|
+
if (sum <= 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const w1 = p1 / sum;
|
|
253
|
+
const w2 = p2 / sum;
|
|
254
|
+
const alphaMultiplier = sum < 100 ? sum / 100 : 1;
|
|
255
|
+
|
|
256
|
+
// Interpolate in premultiplied sRGB, then un-premultiply.
|
|
257
|
+
const mixedA = w1 * c1.a + w2 * c2.a;
|
|
258
|
+
const premix = (k1: number, k2: number): number =>
|
|
259
|
+
w1 * k1 * c1.a + w2 * k2 * c2.a;
|
|
260
|
+
const rp = premix(c1.r, c2.r);
|
|
261
|
+
const gp = premix(c1.g, c2.g);
|
|
262
|
+
const bp = premix(c1.b, c2.b);
|
|
263
|
+
const rgb =
|
|
264
|
+
mixedA === 0
|
|
265
|
+
? {r: 0, g: 0, b: 0}
|
|
266
|
+
: {r: rp / mixedA, g: gp / mixedA, b: bp / mixedA};
|
|
267
|
+
|
|
268
|
+
return formatColor({...rgb, a: mixedA * alphaMultiplier});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Evaluate every supported color function in an expression, innermost first. */
|
|
272
|
+
function evaluateColorFunctions(expr: string): string {
|
|
273
|
+
const idx = expr.indexOf('color-mix(');
|
|
274
|
+
if (idx === -1) {
|
|
275
|
+
return expr;
|
|
276
|
+
}
|
|
277
|
+
const openIndex = idx + 'color-mix'.length;
|
|
278
|
+
const closeIndex = findMatchingParen(expr, openIndex);
|
|
279
|
+
if (closeIndex === -1) {
|
|
280
|
+
return expr;
|
|
281
|
+
}
|
|
282
|
+
const body = evaluateColorFunctions(expr.slice(openIndex + 1, closeIndex));
|
|
283
|
+
const evaluated = evaluateColorMix(body);
|
|
284
|
+
const replacement = evaluated ?? `color-mix(${body})`;
|
|
285
|
+
return (
|
|
286
|
+
expr.slice(0, idx) +
|
|
287
|
+
replacement +
|
|
288
|
+
evaluateColorFunctions(expr.slice(closeIndex + 1))
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Substitute `var(--name[, fallback])` references with their resolved values.
|
|
294
|
+
* `seen` tracks the reference chain so cycles resolve to the literal reference
|
|
295
|
+
* instead of recursing forever.
|
|
296
|
+
*/
|
|
297
|
+
function substituteVars(
|
|
298
|
+
expr: string,
|
|
299
|
+
raw: Record<string, string>,
|
|
300
|
+
cache: Record<string, string>,
|
|
301
|
+
seen: Set<string>,
|
|
302
|
+
): string {
|
|
303
|
+
const start = expr.indexOf('var(');
|
|
304
|
+
if (start === -1) {
|
|
305
|
+
return expr;
|
|
306
|
+
}
|
|
307
|
+
const openIndex = start + 'var'.length;
|
|
308
|
+
const closeIndex = findMatchingParen(expr, openIndex);
|
|
309
|
+
if (closeIndex === -1) {
|
|
310
|
+
return expr;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const inner = expr.slice(openIndex + 1, closeIndex);
|
|
314
|
+
const commaSplit = splitTopLevelComma(inner);
|
|
315
|
+
const name = (commaSplit ? commaSplit[0] : inner).trim();
|
|
316
|
+
const fallback = commaSplit ? commaSplit[1] : null;
|
|
317
|
+
|
|
318
|
+
let replacement: string;
|
|
319
|
+
if (seen.has(name)) {
|
|
320
|
+
replacement = expr.slice(start, closeIndex + 1);
|
|
321
|
+
} else if (name in raw) {
|
|
322
|
+
seen.add(name);
|
|
323
|
+
replacement = resolveReference(name, raw, cache, seen);
|
|
324
|
+
seen.delete(name);
|
|
325
|
+
} else if (fallback !== null) {
|
|
326
|
+
replacement = substituteVars(fallback.trim(), raw, cache, seen);
|
|
327
|
+
} else {
|
|
328
|
+
replacement = expr.slice(start, closeIndex + 1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const rest = substituteVars(expr.slice(closeIndex + 1), raw, cache, seen);
|
|
332
|
+
return expr.slice(0, start) + replacement + rest;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Fully resolve a single expression: substitute references, then evaluate colors. */
|
|
336
|
+
function resolveExpression(
|
|
337
|
+
expr: string,
|
|
338
|
+
raw: Record<string, string>,
|
|
339
|
+
cache: Record<string, string>,
|
|
340
|
+
seen: Set<string>,
|
|
341
|
+
): string {
|
|
342
|
+
if (!expr.includes('var(') && !expr.includes('color-mix(')) {
|
|
343
|
+
return expr;
|
|
344
|
+
}
|
|
345
|
+
const substituted = substituteVars(expr, raw, cache, seen);
|
|
346
|
+
return evaluateColorFunctions(substituted);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Resolve one token by name, memoizing the result in `cache`. */
|
|
350
|
+
function resolveReference(
|
|
351
|
+
name: string,
|
|
352
|
+
raw: Record<string, string>,
|
|
353
|
+
cache: Record<string, string>,
|
|
354
|
+
seen: Set<string>,
|
|
355
|
+
): string {
|
|
356
|
+
if (name in cache) {
|
|
357
|
+
return cache[name];
|
|
358
|
+
}
|
|
359
|
+
const value = raw[name];
|
|
360
|
+
if (value === undefined) {
|
|
361
|
+
return '';
|
|
362
|
+
}
|
|
363
|
+
const resolved = resolveExpression(value, raw, cache, seen);
|
|
364
|
+
cache[name] = resolved;
|
|
365
|
+
return resolved;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Resolve every reference/color function in a raw token map to concrete values. */
|
|
369
|
+
function resolveReferences(
|
|
370
|
+
raw: Record<string, string>,
|
|
371
|
+
): Record<string, string> {
|
|
372
|
+
const cache: Record<string, string> = {};
|
|
373
|
+
for (const name of Object.keys(raw)) {
|
|
374
|
+
resolveReference(name, raw, cache, new Set<string>());
|
|
375
|
+
}
|
|
376
|
+
return cache;
|
|
377
|
+
}
|
|
378
|
+
|
|
147
379
|
/**
|
|
148
380
|
* Resolve all Astryx token values for a theme and effective color mode.
|
|
149
381
|
*
|
|
150
382
|
* The result starts with `tokenDefaults`, applies `theme.tokens`, then
|
|
151
383
|
* reapplies `theme.__inputTokens` when available so explicit tuple overrides
|
|
152
384
|
* retain their original light/dark sides instead of relying on CSS parsing.
|
|
153
|
-
*
|
|
154
|
-
*
|
|
385
|
+
* A final pass resolves any `var()` references between tokens and evaluates
|
|
386
|
+
* the CSS color functions the theme generator emits (e.g. `color-mix`), so
|
|
387
|
+
* derived tokens like `--color-text-accent` return concrete values rather than
|
|
388
|
+
* `var(--color-accent)`. This mirrors the token resolution used by
|
|
389
|
+
* `useTheme()` but does not need React context or media queries.
|
|
155
390
|
*
|
|
156
391
|
* Pass `theme` as null/undefined to resolve defaults only.
|
|
157
392
|
*/
|
|
@@ -167,7 +402,7 @@ export function resolveThemeTokens(
|
|
|
167
402
|
}
|
|
168
403
|
|
|
169
404
|
if (theme == null) {
|
|
170
|
-
return resolved;
|
|
405
|
+
return resolveReferences(resolved);
|
|
171
406
|
}
|
|
172
407
|
|
|
173
408
|
for (const [key, value] of Object.entries(theme.tokens)) {
|
|
@@ -182,7 +417,7 @@ export function resolveThemeTokens(
|
|
|
182
417
|
}
|
|
183
418
|
}
|
|
184
419
|
|
|
185
|
-
return resolved;
|
|
420
|
+
return resolveReferences(resolved);
|
|
186
421
|
}
|
|
187
422
|
|
|
188
423
|
/** Resolve one Astryx token value for a theme and effective color mode. */
|
|
@@ -122,4 +122,24 @@ describe('useTheme', () => {
|
|
|
122
122
|
resolveThemeTokens(testTheme, {mode: 'dark'}),
|
|
123
123
|
);
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
it('resolves derived accent tokens to raw values, not var()/color-mix', () => {
|
|
127
|
+
const brandTheme = defineTheme({name: 'brand', color: {accent: '#0064E0'}});
|
|
128
|
+
const {result} = renderHook(() => useTheme(), {
|
|
129
|
+
wrapper: ({children}) => (
|
|
130
|
+
<Theme theme={brandTheme} mode="light">
|
|
131
|
+
{children}
|
|
132
|
+
</Theme>
|
|
133
|
+
),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const accent = result.current.token('--color-accent');
|
|
137
|
+
expect(result.current.token('--color-text-accent')).toBe(accent);
|
|
138
|
+
expect(result.current.token('--color-icon-accent')).toBe(accent);
|
|
139
|
+
expect(result.current.token('--color-accent-muted')).toMatch(/^(#|rgb)/);
|
|
140
|
+
expect(result.current.token('--color-accent-muted')).not.toContain('var(');
|
|
141
|
+
expect(result.current.token('--color-accent-muted')).not.toContain(
|
|
142
|
+
'color-mix',
|
|
143
|
+
);
|
|
144
|
+
});
|
|
125
145
|
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest';
|
|
4
|
+
import {parseHex, parseRgb, parseColor, formatHex, formatColor} from './color';
|
|
5
|
+
|
|
6
|
+
describe('parseHex', () => {
|
|
7
|
+
it('parses #rrggbb', () => {
|
|
8
|
+
expect(parseHex('#0064E0')).toEqual({r: 0, g: 100, b: 224, a: 1});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('parses without a leading #', () => {
|
|
12
|
+
expect(parseHex('0064E0')).toEqual({r: 0, g: 100, b: 224, a: 1});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('expands #rgb shorthand', () => {
|
|
16
|
+
expect(parseHex('#abc')).toEqual({r: 0xaa, g: 0xbb, b: 0xcc, a: 1});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('parses #rrggbbaa alpha', () => {
|
|
20
|
+
expect(parseHex('#00000080')).toEqual({r: 0, g: 0, b: 0, a: 128 / 255});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('expands #rgba shorthand alpha', () => {
|
|
24
|
+
expect(parseHex('#f008')).toEqual({
|
|
25
|
+
r: 0xff,
|
|
26
|
+
g: 0,
|
|
27
|
+
b: 0,
|
|
28
|
+
a: 0x88 / 255,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns null for non-hex input', () => {
|
|
33
|
+
expect(parseHex('rgb(0,0,0)')).toBeNull();
|
|
34
|
+
expect(parseHex('#12')).toBeNull();
|
|
35
|
+
expect(parseHex('#zzzzzz')).toBeNull();
|
|
36
|
+
// @ts-expect-error runtime guard for non-string input
|
|
37
|
+
expect(parseHex(null)).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('parseRgb', () => {
|
|
42
|
+
it('parses comma-separated rgb()', () => {
|
|
43
|
+
expect(parseRgb('rgb(0, 100, 224)')).toEqual({
|
|
44
|
+
r: 0,
|
|
45
|
+
g: 100,
|
|
46
|
+
b: 224,
|
|
47
|
+
a: 1,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('parses rgba() with alpha', () => {
|
|
52
|
+
expect(parseRgb('rgba(0, 0, 0, 0.5)')).toEqual({r: 0, g: 0, b: 0, a: 0.5});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('parses space-separated with slash alpha', () => {
|
|
56
|
+
expect(parseRgb('rgb(0 100 224 / 0.25)')).toEqual({
|
|
57
|
+
r: 0,
|
|
58
|
+
g: 100,
|
|
59
|
+
b: 224,
|
|
60
|
+
a: 0.25,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('parses percentage channels', () => {
|
|
65
|
+
expect(parseRgb('rgb(100%, 0%, 0%)')).toEqual({
|
|
66
|
+
r: 255,
|
|
67
|
+
g: 0,
|
|
68
|
+
b: 0,
|
|
69
|
+
a: 1,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('clamps alpha and channels into range', () => {
|
|
74
|
+
expect(parseRgb('rgba(300, -20, 10, 2)')).toEqual({
|
|
75
|
+
r: 255,
|
|
76
|
+
g: 0,
|
|
77
|
+
b: 10,
|
|
78
|
+
a: 1,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('returns null when it cannot parse', () => {
|
|
83
|
+
expect(parseRgb('rgb(0, 0)')).toBeNull();
|
|
84
|
+
expect(parseRgb('#000000')).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('parseColor', () => {
|
|
89
|
+
it('routes hex and rgb strings to the right parser', () => {
|
|
90
|
+
expect(parseColor('#FFFFFF')).toEqual({r: 255, g: 255, b: 255, a: 1});
|
|
91
|
+
expect(parseColor('rgb(1, 2, 3)')).toEqual({r: 1, g: 2, b: 3, a: 1});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('resolves the named colors used in token expressions', () => {
|
|
95
|
+
expect(parseColor('transparent')).toEqual({r: 0, g: 0, b: 0, a: 0});
|
|
96
|
+
expect(parseColor('BLACK')).toEqual({r: 0, g: 0, b: 0, a: 1});
|
|
97
|
+
expect(parseColor('white')).toEqual({r: 255, g: 255, b: 255, a: 1});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('returns null for unsupported colors so callers can preserve them', () => {
|
|
101
|
+
expect(parseColor('var(--color-accent)')).toBeNull();
|
|
102
|
+
expect(parseColor('oklch(0.5 0.1 200)')).toBeNull();
|
|
103
|
+
expect(parseColor('rebeccapurple')).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('formatHex', () => {
|
|
108
|
+
it('formats channels as uppercase #RRGGBB', () => {
|
|
109
|
+
expect(formatHex(0, 100, 224)).toBe('#0064E0');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('rounds and clamps out-of-range channels', () => {
|
|
113
|
+
expect(formatHex(-5, 300, 127.6)).toBe('#00FF80');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('formatColor', () => {
|
|
118
|
+
it('formats opaque colors as hex', () => {
|
|
119
|
+
expect(formatColor({r: 0, g: 100, b: 224, a: 1})).toBe('#0064E0');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('formats translucent colors as rgba()', () => {
|
|
123
|
+
expect(formatColor({r: 0, g: 100, b: 224, a: 0.2})).toBe(
|
|
124
|
+
'rgba(0, 100, 224, 0.2)',
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('round-trips through parseColor', () => {
|
|
129
|
+
const parsed = parseColor('rgba(10, 20, 30, 0.5)');
|
|
130
|
+
expect(parsed).not.toBeNull();
|
|
131
|
+
expect(formatColor(parsed!)).toBe('rgba(10, 20, 30, 0.5)');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file color.ts
|
|
5
|
+
* @input CSS color strings (hex, rgb()/rgba(), a small set of named colors)
|
|
6
|
+
* @output Shared color parsing/formatting primitives used across the design system
|
|
7
|
+
* @position Package utility; backs theme token resolution, HCT color math, and chart rendering
|
|
8
|
+
*
|
|
9
|
+
* A single home for the color parsers that were previously duplicated across
|
|
10
|
+
* the theme layer, charts, and lab. Consumers get one well-tested definition of
|
|
11
|
+
* "parse this color" rather than several subtly different regexes.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** A color decomposed into 0-255 RGB channels and a 0-1 alpha. */
|
|
15
|
+
export interface RGBA {
|
|
16
|
+
r: number;
|
|
17
|
+
g: number;
|
|
18
|
+
b: number;
|
|
19
|
+
a: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The named colors the design system relies on in token expressions. */
|
|
23
|
+
const NAMED_COLORS: Record<string, RGBA> = {
|
|
24
|
+
transparent: {r: 0, g: 0, b: 0, a: 0},
|
|
25
|
+
black: {r: 0, g: 0, b: 0, a: 1},
|
|
26
|
+
white: {r: 255, g: 255, b: 255, a: 1},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const clamp = (value: number, min: number, max: number): number =>
|
|
30
|
+
Math.max(min, Math.min(max, value));
|
|
31
|
+
|
|
32
|
+
/** Expand a shorthand hex body (`rgb`/`rgba`) to its full form (`rrggbb`/`rrggbbaa`). */
|
|
33
|
+
function expandShorthand(body: string): string {
|
|
34
|
+
return body
|
|
35
|
+
.split('')
|
|
36
|
+
.map(c => c + c)
|
|
37
|
+
.join('');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse a hex color into {@link RGBA}. Accepts `#rgb`, `#rgba`, `#rrggbb`, and
|
|
42
|
+
* `#rrggbbaa`, with or without the leading `#`. Returns null for anything else.
|
|
43
|
+
*/
|
|
44
|
+
export function parseHex(hex: string): RGBA | null {
|
|
45
|
+
if (typeof hex !== 'string') {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const body = hex.trim().replace(/^#/, '');
|
|
49
|
+
const normalized =
|
|
50
|
+
body.length === 3 || body.length === 4 ? expandShorthand(body) : body;
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
(normalized.length !== 6 && normalized.length !== 8) ||
|
|
54
|
+
!/^[0-9a-fA-F]+$/.test(normalized)
|
|
55
|
+
) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
r: parseInt(normalized.slice(0, 2), 16),
|
|
61
|
+
g: parseInt(normalized.slice(2, 4), 16),
|
|
62
|
+
b: parseInt(normalized.slice(4, 6), 16),
|
|
63
|
+
a: normalized.length === 8 ? parseInt(normalized.slice(6, 8), 16) / 255 : 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parse an `rgb()`/`rgba()` color into {@link RGBA}. Accepts comma- or
|
|
69
|
+
* space-separated channels, an optional `/ alpha`, and percentage channels.
|
|
70
|
+
*/
|
|
71
|
+
export function parseRgb(value: string): RGBA | null {
|
|
72
|
+
const open = value.indexOf('(');
|
|
73
|
+
if (open === -1 || !value.trim().endsWith(')')) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const body = value
|
|
77
|
+
.slice(open + 1, value.lastIndexOf(')'))
|
|
78
|
+
.replace(/\//g, ' ');
|
|
79
|
+
const parts = body
|
|
80
|
+
.split(/[\s,]+/)
|
|
81
|
+
.map(p => p.trim())
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
if (parts.length < 3) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const channel = (p: string): number => {
|
|
88
|
+
const n = p.endsWith('%') ? (parseFloat(p) / 100) * 255 : parseFloat(p);
|
|
89
|
+
return clamp(n, 0, 255);
|
|
90
|
+
};
|
|
91
|
+
const r = channel(parts[0]);
|
|
92
|
+
const g = channel(parts[1]);
|
|
93
|
+
const b = channel(parts[2]);
|
|
94
|
+
if ([r, g, b].some(Number.isNaN)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let a = 1;
|
|
99
|
+
if (parts.length >= 4) {
|
|
100
|
+
const raw = parts[3];
|
|
101
|
+
a = raw.endsWith('%') ? parseFloat(raw) / 100 : parseFloat(raw);
|
|
102
|
+
if (Number.isNaN(a)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
a = clamp(a, 0, 1);
|
|
106
|
+
}
|
|
107
|
+
return {r, g, b, a};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse a concrete CSS color string into {@link RGBA}. Supports hex,
|
|
112
|
+
* `rgb()`/`rgba()`, and the named colors used in token expressions. Returns
|
|
113
|
+
* null for anything it can't evaluate (e.g. `var()`, `oklch()`, unknown names)
|
|
114
|
+
* so callers can preserve the original expression rather than guessing.
|
|
115
|
+
*/
|
|
116
|
+
export function parseColor(value: string): RGBA | null {
|
|
117
|
+
const trimmed = value.trim();
|
|
118
|
+
const named = NAMED_COLORS[trimmed.toLowerCase()];
|
|
119
|
+
if (named) {
|
|
120
|
+
return {...named};
|
|
121
|
+
}
|
|
122
|
+
if (trimmed.startsWith('#')) {
|
|
123
|
+
return parseHex(trimmed);
|
|
124
|
+
}
|
|
125
|
+
if (/^rgba?\(/i.test(trimmed)) {
|
|
126
|
+
return parseRgb(trimmed);
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Format RGB channels (0-255) as an uppercase `#RRGGBB` string. */
|
|
132
|
+
export function formatHex(r: number, g: number, b: number): string {
|
|
133
|
+
const channel = (c: number): string =>
|
|
134
|
+
clamp(Math.round(c), 0, 255).toString(16).padStart(2, '0').toUpperCase();
|
|
135
|
+
return `#${channel(r)}${channel(g)}${channel(b)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Serialize {@link RGBA} to a compact, JS-usable CSS color: `#RRGGBB` when
|
|
140
|
+
* fully opaque, otherwise `rgba(r, g, b, a)`.
|
|
141
|
+
*/
|
|
142
|
+
export function formatColor({r, g, b, a}: RGBA): string {
|
|
143
|
+
if (a >= 1) {
|
|
144
|
+
return formatHex(r, g, b);
|
|
145
|
+
}
|
|
146
|
+
const round = (n: number): number => clamp(Math.round(n), 0, 255);
|
|
147
|
+
return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${parseFloat(a.toFixed(4))})`;
|
|
148
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
4
|
+
import type {SyntheticEvent} from 'react';
|
|
5
|
+
import {composeEventHandlers} from './composeEventHandlers';
|
|
6
|
+
|
|
7
|
+
function fakeEvent(): SyntheticEvent {
|
|
8
|
+
const event = {
|
|
9
|
+
defaultPrevented: false,
|
|
10
|
+
preventDefault() {
|
|
11
|
+
event.defaultPrevented = true;
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
return event as unknown as SyntheticEvent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('composeEventHandlers', () => {
|
|
18
|
+
it('calls every handler in argument order', () => {
|
|
19
|
+
const calls: string[] = [];
|
|
20
|
+
const composed = composeEventHandlers(
|
|
21
|
+
() => calls.push('first'),
|
|
22
|
+
() => calls.push('second'),
|
|
23
|
+
);
|
|
24
|
+
composed(fakeEvent());
|
|
25
|
+
expect(calls).toEqual(['first', 'second']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('skips undefined handlers', () => {
|
|
29
|
+
const handler = vi.fn();
|
|
30
|
+
const composed = composeEventHandlers(undefined, handler, undefined);
|
|
31
|
+
composed(fakeEvent());
|
|
32
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('passes the same event to each handler', () => {
|
|
36
|
+
const event = fakeEvent();
|
|
37
|
+
const a = vi.fn();
|
|
38
|
+
const b = vi.fn();
|
|
39
|
+
composeEventHandlers(a, b)(event);
|
|
40
|
+
expect(a).toHaveBeenCalledWith(event);
|
|
41
|
+
expect(b).toHaveBeenCalledWith(event);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('stops after a handler prevents default', () => {
|
|
45
|
+
const first = vi.fn((e: SyntheticEvent) => e.preventDefault());
|
|
46
|
+
const second = vi.fn();
|
|
47
|
+
composeEventHandlers(first, second)(fakeEvent());
|
|
48
|
+
expect(first).toHaveBeenCalledTimes(1);
|
|
49
|
+
expect(second).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('lets a consumer handler opt out of later behavior (consumer-first order)', () => {
|
|
53
|
+
const consumer = vi.fn((e: SyntheticEvent) => e.preventDefault());
|
|
54
|
+
const ownBehavior = vi.fn();
|
|
55
|
+
composeEventHandlers(consumer, ownBehavior)(fakeEvent());
|
|
56
|
+
expect(ownBehavior).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('runs later handlers when default is not prevented', () => {
|
|
60
|
+
const consumer = vi.fn();
|
|
61
|
+
const ownBehavior = vi.fn();
|
|
62
|
+
composeEventHandlers(consumer, ownBehavior)(fakeEvent());
|
|
63
|
+
expect(ownBehavior).toHaveBeenCalledTimes(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('returns a no-op-safe function when all handlers are undefined', () => {
|
|
67
|
+
expect(() =>
|
|
68
|
+
composeEventHandlers(undefined, undefined)(fakeEvent()),
|
|
69
|
+
).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file composeEventHandlers.ts
|
|
5
|
+
* @input Multiple React event handlers (or undefined)
|
|
6
|
+
* @output A single handler that calls each in order, stopping if one prevents default
|
|
7
|
+
* @position Utility; used by components that own an interaction (a click that
|
|
8
|
+
* selects, a keydown that drives arrow navigation) but also accept a consumer
|
|
9
|
+
* handler for the same event through {...rest}. Composes them instead of
|
|
10
|
+
* letting one clobber the other.
|
|
11
|
+
*
|
|
12
|
+
* Order is call order: the first argument runs first. Put the consumer handler
|
|
13
|
+
* first so it can opt out of the component's built-in behavior via
|
|
14
|
+
* `event.preventDefault()`; put the component's handler first when its behavior
|
|
15
|
+
* must always run regardless of the consumer.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type {SyntheticEvent} from 'react';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compose event handlers into one. Each handler runs in argument order; if any
|
|
22
|
+
* calls `event.preventDefault()`, the remaining handlers are skipped.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* // Consumer first: a consumer onClick can preventDefault to block selection.
|
|
27
|
+
* <button onClick={composeEventHandlers(onClickProp, handleSelect)} />
|
|
28
|
+
*
|
|
29
|
+
* // Component first: built-in keyboard nav always runs.
|
|
30
|
+
* <div onKeyDown={composeEventHandlers(handleArrowKeys, onKeyDownProp)} />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function composeEventHandlers<E extends SyntheticEvent>(
|
|
34
|
+
...handlers: (((event: E) => void) | undefined)[]
|
|
35
|
+
): (event: E) => void {
|
|
36
|
+
return (event: E) => {
|
|
37
|
+
for (const handler of handlers) {
|
|
38
|
+
handler?.(event);
|
|
39
|
+
if (event.defaultPrevented) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|