@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1
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 +116 -0
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +5 -2
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +191 -32
- package/dist/Chat/ChatComposerInput.d.ts +9 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +5 -0
- package/dist/Chat/ChatMessage.d.ts +11 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +18 -9
- package/dist/Chat/useTriggerMenu.d.ts +9 -2
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +222 -50
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +158 -38
- package/dist/CodeBlock/tokenizer.d.ts +39 -0
- package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
- package/dist/CodeBlock/tokenizer.js +108 -0
- package/dist/CommandPalette/CommandPalette.d.ts +9 -5
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +38 -20
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
- package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +35 -18
- package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +8 -9
- package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
- package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteList.js +10 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +12 -3
- package/dist/ContextMenu/ContextMenu.d.ts +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +62 -48
- package/dist/DateInput/DateInput.js +1 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +12 -4
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +12 -2
- package/dist/Dialog/Dialog.d.ts +1 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +16 -5
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +7 -7
- package/dist/Divider/Divider.d.ts +1 -0
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +5 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +161 -65
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
- package/dist/Field/InputClearButton.d.ts +8 -4
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +23 -7
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -0
- package/dist/Grid/Grid.d.ts +13 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +7 -1
- package/dist/HoverCard/useHoverCard.d.ts +1 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +67 -49
- package/dist/InputGroup/InputGroup.d.ts +2 -4
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -4
- package/dist/InputGroup/groupStyles.d.ts +10 -2
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +19 -2
- package/dist/Layer/useLayerDismissal.d.ts +9 -9
- package/dist/Layer/useLayerDismissal.js +9 -9
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +25 -4
- package/dist/MobileNav/MobileNav.d.ts +7 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +45 -2
- package/dist/MultiSelector/MultiSelector.d.ts +7 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +24 -3
- package/dist/NumberInput/NumberInput.js +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
- package/dist/Resizable/ResizeHandle.d.ts +1 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +83 -15
- package/dist/Resizable/useResizable.d.ts +23 -0
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +78 -0
- package/dist/Selector/Selector.d.ts +6 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +28 -6
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +10 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +18 -7
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts +2 -1
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +3 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +6 -3
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +7 -1
- package/dist/astryx.css +22 -0
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +55 -12
- package/dist/hooks/useTreeFocus.d.ts +21 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +40 -2
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +3 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
- package/dist/reset.a11y.chromium.spec.js +6 -1
- package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
- package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
- package/dist/reset.until-found.a11y.browser.spec.js +71 -0
- package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +43 -2
- package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
- package/dist/theme/syntax/index.d.ts +1 -1
- package/dist/theme/syntax/index.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.d.ts +12 -2
- package/dist/theme/syntax/tokens.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.js +25 -4
- package/dist/utils/plainDate.d.ts +12 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +63 -10
- package/locales/en.json +13 -1
- package/locales/pseudo.json +9 -0
- package/package.json +4 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
- package/src/AlertDialog/AlertDialog.spec.md +205 -0
- package/src/AlertDialog/AlertDialog.test.tsx +3 -14
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
- package/src/Button/Button.test.tsx +37 -0
- package/src/Button/Button.tsx +5 -1
- package/src/Calendar/Calendar.doc.mjs +234 -60
- package/src/Calendar/Calendar.spec.md +209 -0
- package/src/Calendar/Calendar.test.tsx +559 -20
- package/src/Calendar/Calendar.tsx +258 -41
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
- package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
- package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
- package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
- package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
- package/src/Carousel/Carousel.test.tsx +54 -71
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
- package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
- package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
- package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
- package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.spec.md +94 -55
- package/src/Chat/ChatComposerInput.test.tsx +202 -1
- package/src/Chat/ChatComposerInput.tsx +13 -0
- package/src/Chat/ChatMessage.doc.mjs +31 -16
- package/src/Chat/ChatMessage.spec.md +265 -0
- package/src/Chat/ChatMessage.test.tsx +246 -0
- package/src/Chat/ChatMessage.tsx +30 -7
- package/src/Chat/ChatSystemMessage.spec.md +36 -37
- package/src/Chat/ChatSystemMessage.test.tsx +4 -4
- package/src/Chat/useTriggerMenu.test.tsx +994 -0
- package/src/Chat/useTriggerMenu.tsx +278 -46
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
- package/src/CodeBlock/CodeBlock.spec.md +40 -41
- package/src/CodeBlock/CodeBlock.test.tsx +89 -9
- package/src/CodeBlock/CodeBlock.tsx +205 -25
- package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
- package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
- package/src/CodeBlock/tokenizer.test.ts +112 -0
- package/src/CodeBlock/tokenizer.ts +106 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
- package/src/CommandPalette/CommandPalette.spec.md +100 -49
- package/src/CommandPalette/CommandPalette.test.tsx +241 -2
- package/src/CommandPalette/CommandPalette.tsx +47 -21
- package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
- package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
- package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
- package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
- package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
- package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
- package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
- package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
- package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
- package/src/CommandPalette/CommandPaletteList.tsx +10 -5
- package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
- package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
- package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
- package/src/ComplexSelector/ComplexSelector.tsx +37 -8
- package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.spec.md +51 -53
- package/src/ContextMenu/ContextMenu.test.tsx +132 -1
- package/src/ContextMenu/ContextMenu.tsx +78 -48
- package/src/DateInput/DateInput.doc.mjs +52 -0
- package/src/DateInput/DateInput.spec.md +27 -15
- package/src/DateInput/DateInput.test.tsx +38 -0
- package/src/DateInput/DateInput.tsx +1 -1
- package/src/DateInput/DateInputTouch.test.tsx +22 -0
- package/src/DateInput/MonthScroller.tsx +12 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
- package/src/DateRangeInput/DateRangeInput.tsx +20 -2
- package/src/Dialog/Dialog.test.tsx +27 -1
- package/src/Dialog/Dialog.tsx +16 -5
- package/src/Dialog/DialogHeader.test.tsx +27 -2
- package/src/Dialog/DialogHeader.tsx +7 -8
- package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
- package/src/Divider/Divider.doc.mjs +5 -3
- package/src/Divider/Divider.spec.md +140 -54
- package/src/Divider/Divider.test.tsx +15 -0
- package/src/Divider/Divider.tsx +6 -4
- package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
- package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
- package/src/DropdownMenu/DropdownMenu.tsx +212 -81
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
- package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
- package/src/Field/InputClearButton.test.tsx +29 -4
- package/src/Field/InputClearButton.tsx +26 -8
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
- package/src/FormLayout/FormLayout.tsx +1 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
- package/src/Grid/Grid.doc.mjs +14 -11
- package/src/Grid/Grid.spec.md +50 -32
- package/src/Grid/Grid.tsx +13 -4
- package/src/HoverCard/HoverCard.test.tsx +46 -1
- package/src/HoverCard/useHoverCard.tsx +68 -48
- package/src/InputGroup/InputGroup.doc.mjs +11 -1
- package/src/InputGroup/InputGroup.tsx +2 -4
- package/src/InputGroup/groupStyles.ts +40 -2
- package/src/Layer/useLayerDismissal.ts +9 -9
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
- package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
- package/src/Link/__tests__/Link.a11y.states.ts +104 -0
- package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
- package/src/Markdown/Markdown.doc.mjs +1 -1
- package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
- package/src/Markdown/Markdown.spec.md +45 -23
- package/src/Markdown/Markdown.test.tsx +38 -0
- package/src/Markdown/Markdown.tsx +24 -2
- package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
- package/src/MobileNav/MobileNav.tsx +50 -2
- package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
- package/src/MoreMenu/MoreMenu.spec.md +18 -7
- package/src/MoreMenu/MoreMenu.test.tsx +38 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
- package/src/MultiSelector/MultiSelector.spec.md +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +188 -21
- package/src/MultiSelector/MultiSelector.tsx +50 -11
- package/src/NumberInput/NumberInput.test.tsx +24 -0
- package/src/NumberInput/NumberInput.tsx +1 -1
- package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
- package/src/Resizable/Resizable.spec.md +41 -9
- package/src/Resizable/ResizeHandle.test.tsx +565 -2
- package/src/Resizable/ResizeHandle.tsx +126 -21
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
- package/src/Resizable/useResizable.ts +95 -0
- package/src/Selector/Selector.doc.mjs +7 -6
- package/src/Selector/Selector.spec.md +37 -24
- package/src/Selector/Selector.test.tsx +176 -10
- package/src/Selector/Selector.tsx +59 -14
- package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
- package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/SideNav/SideNav.tsx +16 -2
- package/src/Slider/Slider.spec.md +43 -17
- package/src/Slider/Slider.test.tsx +134 -0
- package/src/Slider/Slider.tsx +29 -6
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
- package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
- package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
- package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
- package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
- package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
- package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
- package/src/Tokenizer/Tokenizer.test.tsx +87 -0
- package/src/Tokenizer/Tokenizer.tsx +31 -3
- package/src/Toolbar/Toolbar.test.tsx +131 -4
- package/src/Toolbar/Toolbar.tsx +3 -1
- package/src/TreeList/TreeList.spec.md +33 -8
- package/src/TreeList/TreeList.test.tsx +72 -0
- package/src/TreeList/TreeList.tsx +10 -6
- package/src/TreeList/TreeListItem.tsx +5 -0
- package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
- package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
- package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
- package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
- package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
- package/src/hooks/useScrollOverflow.test.tsx +222 -0
- package/src/hooks/useScrollOverflow.ts +55 -13
- package/src/hooks/useTreeFocus.test.tsx +37 -0
- package/src/hooks/useTreeFocus.ts +55 -2
- package/src/i18n/generated-locales/en.generated.ts +3 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
- package/src/reset.a11y.chromium.spec.ts +9 -1
- package/src/reset.css +8 -3
- package/src/reset.until-found.a11y.browser.spec.ts +75 -0
- package/src/reset.until-found.test.ts +32 -0
- package/src/stylexValidation.source-build.test.mjs +42 -0
- package/src/theme/syntax/SyntaxTheme.tsx +46 -5
- package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
- package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
- package/src/theme/syntax/index.ts +5 -1
- package/src/theme/syntax/tokens.ts +32 -2
- package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
- package/src/utils/plainDate.test.ts +147 -0
- package/src/utils/plainDate.ts +75 -10
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
ChatComposerInputHandle,
|
|
12
12
|
} from './ChatComposerInput';
|
|
13
13
|
import {createStaticSource} from '../Typeahead/createStaticSource';
|
|
14
|
-
import type {SearchableItem} from '../Typeahead/types';
|
|
14
|
+
import type {SearchableItem, SearchSource} from '../Typeahead/types';
|
|
15
15
|
|
|
16
16
|
// =============================================================================
|
|
17
17
|
// Helpers
|
|
@@ -1370,6 +1370,207 @@ describe('ChatComposerInput', () => {
|
|
|
1370
1370
|
});
|
|
1371
1371
|
});
|
|
1372
1372
|
|
|
1373
|
+
describe('trigger query (component:ChatComposerInput FR13, AR5)', () => {
|
|
1374
|
+
function typeAtEnd(editable: HTMLElement, text: string) {
|
|
1375
|
+
editable.textContent = text;
|
|
1376
|
+
const node = editable.firstChild!;
|
|
1377
|
+
const range = document.createRange();
|
|
1378
|
+
range.setStart(node, text.length);
|
|
1379
|
+
range.collapse(true);
|
|
1380
|
+
const sel = window.getSelection()!;
|
|
1381
|
+
sel.removeAllRanges();
|
|
1382
|
+
sel.addRange(range);
|
|
1383
|
+
fireEvent.input(editable);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
it('Enter while a newer trigger search is pending sends the typed text, picks no row, and no menu reopens after the send', async () => {
|
|
1387
|
+
const onSubmit = vi.fn();
|
|
1388
|
+
let resolveSlow: (items: SearchableItem[]) => void = () => {};
|
|
1389
|
+
const source: SearchSource = {
|
|
1390
|
+
bootstrap: async () => [],
|
|
1391
|
+
async search(query: string) {
|
|
1392
|
+
if (query === 'ci') {
|
|
1393
|
+
return new Promise(resolve => {
|
|
1394
|
+
resolveSlow = resolve;
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
return Promise.resolve(
|
|
1398
|
+
USERS.filter(u => u.label.toLowerCase().includes(query)),
|
|
1399
|
+
);
|
|
1400
|
+
},
|
|
1401
|
+
};
|
|
1402
|
+
render(
|
|
1403
|
+
<ChatComposerInput
|
|
1404
|
+
triggers={[createMentionTrigger({searchSource: source})]}
|
|
1405
|
+
debounceMs={0}
|
|
1406
|
+
onSubmit={onSubmit}
|
|
1407
|
+
/>,
|
|
1408
|
+
);
|
|
1409
|
+
const editable = screen.getByRole('combobox');
|
|
1410
|
+
editable.focus();
|
|
1411
|
+
|
|
1412
|
+
typeAtEnd(editable, '@c');
|
|
1413
|
+
await waitFor(() => {
|
|
1414
|
+
expect(editable.getAttribute('aria-activedescendant')).toBeTruthy();
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
// A newer search is pending: Enter sends the draft exactly as typed.
|
|
1418
|
+
typeAtEnd(editable, '@ci');
|
|
1419
|
+
fireEvent.keyDown(editable, {key: 'Enter'});
|
|
1420
|
+
expect(onSubmit).toHaveBeenCalledWith('@ci');
|
|
1421
|
+
expect(editable.querySelector('[data-astryx-token]')).toBeNull();
|
|
1422
|
+
expect(editable.textContent).toBe('');
|
|
1423
|
+
|
|
1424
|
+
// The late result for the sent query must not reopen the menu.
|
|
1425
|
+
await act(async () => {
|
|
1426
|
+
resolveSlow([{id: 'cindy', label: 'Cindy Zhang'}]);
|
|
1427
|
+
});
|
|
1428
|
+
expect(editable).toHaveAttribute('aria-expanded', 'false');
|
|
1429
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
1430
|
+
expect(screen.queryByRole('option')).not.toBeInTheDocument();
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it('forgets an Escape-dismissed mention when the message is sent, so the next message can mention', async () => {
|
|
1434
|
+
const onSubmit = vi.fn();
|
|
1435
|
+
render(
|
|
1436
|
+
<ChatComposerInput
|
|
1437
|
+
triggers={[createMentionTrigger()]}
|
|
1438
|
+
onSubmit={onSubmit}
|
|
1439
|
+
/>,
|
|
1440
|
+
);
|
|
1441
|
+
const editable = screen.getByRole('combobox');
|
|
1442
|
+
editable.focus();
|
|
1443
|
+
|
|
1444
|
+
typeAtEnd(editable, '@');
|
|
1445
|
+
await waitFor(() => {
|
|
1446
|
+
expect(editable).toHaveAttribute('aria-expanded', 'true');
|
|
1447
|
+
});
|
|
1448
|
+
fireEvent.keyDown(editable, {key: 'Escape'});
|
|
1449
|
+
await waitFor(() => {
|
|
1450
|
+
expect(editable).toHaveAttribute('aria-expanded', 'false');
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
fireEvent.keyDown(editable, {key: 'Enter'});
|
|
1454
|
+
expect(onSubmit).toHaveBeenCalledWith('@');
|
|
1455
|
+
|
|
1456
|
+
typeAtEnd(editable, '@ci');
|
|
1457
|
+
await waitFor(() => {
|
|
1458
|
+
expect(editable).toHaveAttribute('aria-expanded', 'true');
|
|
1459
|
+
});
|
|
1460
|
+
expect(editable.getAttribute('aria-activedescendant')).toBeTruthy();
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
it('forgets an Escape-dismissed trigger when the send button sends the draft', async () => {
|
|
1464
|
+
const onSubmit = vi.fn();
|
|
1465
|
+
render(
|
|
1466
|
+
<ChatComposer
|
|
1467
|
+
onSubmit={onSubmit}
|
|
1468
|
+
input={<ChatComposerInput triggers={[createCommandTrigger()]} />}
|
|
1469
|
+
/>,
|
|
1470
|
+
);
|
|
1471
|
+
const editable = screen.getByRole('combobox');
|
|
1472
|
+
editable.focus();
|
|
1473
|
+
|
|
1474
|
+
typeAtEnd(editable, '/');
|
|
1475
|
+
await waitFor(() => {
|
|
1476
|
+
expect(editable).toHaveAttribute('aria-expanded', 'true');
|
|
1477
|
+
});
|
|
1478
|
+
fireEvent.keyDown(editable, {key: 'Escape'});
|
|
1479
|
+
await waitFor(() => {
|
|
1480
|
+
expect(editable).toHaveAttribute('aria-expanded', 'false');
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
fireEvent.click(screen.getByRole('button', {name: /send/i}));
|
|
1484
|
+
expect(onSubmit).toHaveBeenCalledWith('/');
|
|
1485
|
+
await waitFor(() => {
|
|
1486
|
+
expect(editable.textContent).toBe('');
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
typeAtEnd(editable, '/s');
|
|
1490
|
+
await waitFor(() => {
|
|
1491
|
+
expect(editable).toHaveAttribute('aria-expanded', 'true');
|
|
1492
|
+
});
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
it('drops a late trigger result after the send button sends the draft', async () => {
|
|
1496
|
+
const onSubmit = vi.fn();
|
|
1497
|
+
let resolveSlow: (items: SearchableItem[]) => void = () => {};
|
|
1498
|
+
const source: SearchSource = {
|
|
1499
|
+
bootstrap: async () => [],
|
|
1500
|
+
async search(query: string) {
|
|
1501
|
+
if (query === 'ci') {
|
|
1502
|
+
return new Promise(resolve => {
|
|
1503
|
+
resolveSlow = resolve;
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
return Promise.resolve(
|
|
1507
|
+
USERS.filter(u => u.label.toLowerCase().includes(query)),
|
|
1508
|
+
);
|
|
1509
|
+
},
|
|
1510
|
+
};
|
|
1511
|
+
render(
|
|
1512
|
+
<ChatComposer
|
|
1513
|
+
onSubmit={onSubmit}
|
|
1514
|
+
input={
|
|
1515
|
+
<ChatComposerInput
|
|
1516
|
+
triggers={[createMentionTrigger({searchSource: source})]}
|
|
1517
|
+
debounceMs={0}
|
|
1518
|
+
/>
|
|
1519
|
+
}
|
|
1520
|
+
/>,
|
|
1521
|
+
);
|
|
1522
|
+
const editable = screen.getByRole('combobox');
|
|
1523
|
+
editable.focus();
|
|
1524
|
+
|
|
1525
|
+
typeAtEnd(editable, '@c');
|
|
1526
|
+
await waitFor(() => {
|
|
1527
|
+
expect(editable.getAttribute('aria-activedescendant')).toBeTruthy();
|
|
1528
|
+
});
|
|
1529
|
+
typeAtEnd(editable, '@ci');
|
|
1530
|
+
|
|
1531
|
+
fireEvent.click(screen.getByRole('button', {name: /send/i}));
|
|
1532
|
+
expect(onSubmit).toHaveBeenCalledWith('@ci');
|
|
1533
|
+
await waitFor(() => {
|
|
1534
|
+
expect(editable.textContent).toBe('');
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
await act(async () => {
|
|
1538
|
+
resolveSlow([{id: 'cindy', label: 'Cindy Zhang'}]);
|
|
1539
|
+
});
|
|
1540
|
+
expect(editable).toHaveAttribute('aria-expanded', 'false');
|
|
1541
|
+
expect(screen.queryByRole('option')).not.toBeInTheDocument();
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
it('lets Escape reach the consumer while an unmatched multi-word query hides the menu', async () => {
|
|
1545
|
+
const onKeyDown = vi.fn();
|
|
1546
|
+
render(
|
|
1547
|
+
<ChatComposerInput
|
|
1548
|
+
triggers={[createMentionTrigger({hasMultiWordQuery: true})]}
|
|
1549
|
+
onKeyDown={onKeyDown}
|
|
1550
|
+
/>,
|
|
1551
|
+
);
|
|
1552
|
+
const editable = screen.getByRole('combobox');
|
|
1553
|
+
editable.focus();
|
|
1554
|
+
|
|
1555
|
+
typeAtEnd(editable, '@team please');
|
|
1556
|
+
await waitFor(() => {
|
|
1557
|
+
expect(editable).toHaveAttribute('aria-expanded', 'false');
|
|
1558
|
+
});
|
|
1559
|
+
// The hidden menu's results commit outside act, so the popover's Escape
|
|
1560
|
+
// registration is released by a passive effect that can still be pending
|
|
1561
|
+
// when the attribute changes. Flush it before pressing Escape.
|
|
1562
|
+
await act(async () => {});
|
|
1563
|
+
|
|
1564
|
+
// Nothing cancels the key: the closed menu holds no Escape handling,
|
|
1565
|
+
// so a surrounding dialog or drawer still receives it.
|
|
1566
|
+
const notCancelled = fireEvent.keyDown(editable, {key: 'Escape'});
|
|
1567
|
+
expect(notCancelled).toBe(true);
|
|
1568
|
+
expect(onKeyDown).toHaveBeenCalledWith(
|
|
1569
|
+
expect.objectContaining({key: 'Escape'}),
|
|
1570
|
+
);
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1373
1574
|
describe('trigger menu cursor anchor', () => {
|
|
1374
1575
|
// The trigger menu anchors its popover to the cursor position, not the
|
|
1375
1576
|
// entire input element. In real browsers this creates a fixed-position
|
|
@@ -170,6 +170,15 @@ export type ChatComposerTrigger = {
|
|
|
170
170
|
loadingText?: string;
|
|
171
171
|
/** Accessible label for the menu. @default 'Suggestions' */
|
|
172
172
|
menuLabel?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Whether the query continues past spaces, for suggestions whose names have
|
|
175
|
+
* spaces (e.g. "@María José García", "@Design Review: Q4"). The query then
|
|
176
|
+
* runs until a line break or about 64 characters, and when nothing matches
|
|
177
|
+
* the menu hides unless `emptySearchText` is set. When false, the query ends
|
|
178
|
+
* at the first space.
|
|
179
|
+
* @default false
|
|
180
|
+
*/
|
|
181
|
+
hasMultiWordQuery?: boolean;
|
|
173
182
|
};
|
|
174
183
|
|
|
175
184
|
export interface ChatComposerInputProps extends Omit<
|
|
@@ -685,6 +694,10 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
|
|
|
685
694
|
currentDraftRef.current = '';
|
|
686
695
|
}
|
|
687
696
|
|
|
697
|
+
// Submitting ends any trigger query, including one dismissed with
|
|
698
|
+
// Escape: no menu stays open, and no dismissal carries into the next
|
|
699
|
+
// message.
|
|
700
|
+
triggerMenu.reset();
|
|
688
701
|
onSubmit?.(text);
|
|
689
702
|
editableRef.current.textContent = '';
|
|
690
703
|
setIsEmpty(true);
|
|
@@ -6,7 +6,8 @@ export const docs = {
|
|
|
6
6
|
name: 'ChatMessage',
|
|
7
7
|
subComponentOf: 'Chat',
|
|
8
8
|
displayName: 'Chat Message',
|
|
9
|
-
description:
|
|
9
|
+
description:
|
|
10
|
+
'Sender context wrapper: handles avatar, name, metadata, and alignment based on sender role.',
|
|
10
11
|
playground: {
|
|
11
12
|
defaults: {
|
|
12
13
|
sender: 'assistant',
|
|
@@ -28,7 +29,8 @@ export const docs = {
|
|
|
28
29
|
{
|
|
29
30
|
name: 'children',
|
|
30
31
|
type: 'ReactNode',
|
|
31
|
-
description:
|
|
32
|
+
description:
|
|
33
|
+
'Free-form content: bubbles, asset lists, tool calls, images. Custom (non-bubble) children render flush with the message edge; wrap them in a ghost ChatMessageBubble to align them with the bubble text column.',
|
|
32
34
|
required: true,
|
|
33
35
|
slotElements: [
|
|
34
36
|
{
|
|
@@ -40,7 +42,8 @@ export const docs = {
|
|
|
40
42
|
{
|
|
41
43
|
name: 'avatar',
|
|
42
44
|
type: 'ReactNode',
|
|
43
|
-
description:
|
|
45
|
+
description:
|
|
46
|
+
'Avatar element rendered beside the message. Typically Avatar. `null`, booleans, and empty strings create no slot; any other content that renders nothing collapses its slot.',
|
|
44
47
|
slotElements: [
|
|
45
48
|
{
|
|
46
49
|
__element: 'Avatar',
|
|
@@ -54,12 +57,14 @@ export const docs = {
|
|
|
54
57
|
{
|
|
55
58
|
name: 'name',
|
|
56
59
|
type: 'ReactNode',
|
|
57
|
-
description:
|
|
60
|
+
description:
|
|
61
|
+
"Sender name rendered above the message body; it labels the message. `null`, booleans, and empty strings create no slot, and any other content that renders nothing collapses its slot; either way the message is labelled by the translated sender fallback. Use when the first child is raw content (not a bubble). If the first child is a bubble, put the name on the bubble's `name` prop instead.",
|
|
58
62
|
},
|
|
59
63
|
{
|
|
60
64
|
name: 'metadata',
|
|
61
65
|
type: 'ReactNode',
|
|
62
|
-
description:
|
|
66
|
+
description:
|
|
67
|
+
"Metadata rendered below the message body. `null`, booleans, and empty strings create no slot; any other content that renders nothing collapses its slot. Use when the last child is raw content (not a bubble). If the last child is a bubble, put metadata on the bubble's `metadata` prop instead.",
|
|
63
68
|
slotElements: [
|
|
64
69
|
{
|
|
65
70
|
__element: 'Text',
|
|
@@ -74,6 +79,7 @@ export const docs = {
|
|
|
74
79
|
name: 'density',
|
|
75
80
|
type: "'compact' | 'balanced' | 'spacious'",
|
|
76
81
|
description: 'Visual density. Inherited from list context if not set.',
|
|
82
|
+
default: "'balanced'",
|
|
77
83
|
},
|
|
78
84
|
],
|
|
79
85
|
};
|
|
@@ -81,13 +87,17 @@ export const docs = {
|
|
|
81
87
|
export const docsZh = {
|
|
82
88
|
name: 'ChatMessage',
|
|
83
89
|
displayName: 'Chat Message',
|
|
84
|
-
description:
|
|
90
|
+
description:
|
|
91
|
+
'发送者上下文包装器,根据发送者角色处理头像、名称、元数据和对齐方式。',
|
|
85
92
|
propDescriptions: {
|
|
86
93
|
sender: '消息发送者,控制对齐和布局。',
|
|
87
|
-
children:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
children:
|
|
95
|
+
'自由内容:气泡、资源列表、工具调用、图片。非气泡的自定义子内容默认与消息边缘齐平,可用 ghost 变体的 ChatMessageBubble 包裹使其与气泡文本列对齐。',
|
|
96
|
+
avatar:
|
|
97
|
+
'消息旁边渲染的头像元素。通常是 Avatar。`null`、布尔值和空字符串不会创建槽位;其他不渲染任何内容的值会让槽位折叠。',
|
|
98
|
+
name: '消息正文上方渲染的发送者名称,并作为消息的标签。`null`、布尔值和空字符串不会创建槽位,其他不渲染任何内容的值会让槽位折叠;两种情况都改用本地化发送者标签。当第一个子元素不是气泡时使用。',
|
|
99
|
+
metadata:
|
|
100
|
+
'消息正文下方渲染的元数据。`null`、布尔值和空字符串不会创建槽位;其他不渲染任何内容的值会让槽位折叠。当最后一个子元素不是气泡时使用。',
|
|
91
101
|
density: '视觉密度。未设置时从列表上下文继承。',
|
|
92
102
|
},
|
|
93
103
|
};
|
|
@@ -95,14 +105,19 @@ export const docsZh = {
|
|
|
95
105
|
export const docsDense = {
|
|
96
106
|
name: 'ChatMessage',
|
|
97
107
|
displayName: 'Chat Message',
|
|
98
|
-
description:
|
|
108
|
+
description:
|
|
109
|
+
'sender context wrapper; handles avatar+name+metadata+alignment by sender role',
|
|
99
110
|
propDescriptions: {
|
|
100
111
|
sender: 'who sent; controls alignment+layout',
|
|
101
|
-
children:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
children:
|
|
113
|
+
'free-form: bubbles, assets, tool calls, images; wrap non-bubble content in a ghost bubble to align with bubble text column',
|
|
114
|
+
avatar:
|
|
115
|
+
'avatar beside msg; null/boolean/"" omit slot; content rendering nothing collapses slot',
|
|
116
|
+
name: 'sender name above body; labels msg; null/boolean/"" omit slot, content rendering nothing collapses it; both fall back to translated sender label; use when first child is raw',
|
|
117
|
+
displayName:
|
|
118
|
+
'sender name above body; use when first child is raw (not bubble)',
|
|
119
|
+
metadata:
|
|
120
|
+
'metadata below body; null/boolean/"" omit slot; content rendering nothing collapses slot; use when last child is raw',
|
|
106
121
|
density: 'visual density; inherited from list context if unset',
|
|
107
122
|
},
|
|
108
123
|
};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 6
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:ChatMessage
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [public-api, behavior, layout, theming, accessibility, testing]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/Chat/ChatMessage.test.tsx,
|
|
16
|
+
packages/core/src/theme/themingTargets.test.ts,
|
|
17
|
+
apps/storybook/stories/ChatMessage.stories.tsx,
|
|
18
|
+
]
|
|
19
|
+
modules: []
|
|
20
|
+
families: []
|
|
21
|
+
design_specs: []
|
|
22
|
+
architecture:
|
|
23
|
+
[
|
|
24
|
+
architecture:public-component-api,
|
|
25
|
+
architecture:component-style-authoring,
|
|
26
|
+
architecture:component-theming-surface,
|
|
27
|
+
architecture:theme-tokens,
|
|
28
|
+
architecture:react-component-runtime,
|
|
29
|
+
architecture:component-test-sufficiency,
|
|
30
|
+
architecture:knowledge-contracts,
|
|
31
|
+
]
|
|
32
|
+
contributing: []
|
|
33
|
+
system_specs: [spec:AST-020, spec:AST-029]
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# ChatMessage component contract
|
|
37
|
+
|
|
38
|
+
This record is an **observational backfill** written during the 2026-09-24
|
|
39
|
+
component audit under `spec:AST-029/FR3–FR4`. Rows marked `verify` describe the
|
|
40
|
+
audit base's released contract. Rows marked `proposed` describe the empty-slot
|
|
41
|
+
behavior (FR3, FR6, ORD2, AR1–AR2, DEC-1) and await owner approval: `null`,
|
|
42
|
+
booleans, and empty strings create no slot; any other slot content that renders
|
|
43
|
+
nothing collapses its wrapper through CSS; and a name that renders no text falls
|
|
44
|
+
back to the translated sender label. Slot content is never inspected to decide
|
|
45
|
+
any of this. Unresolved questions remain open rather than becoming policy.
|
|
46
|
+
|
|
47
|
+
## Contract at a glance
|
|
48
|
+
|
|
49
|
+
| Area | Contract |
|
|
50
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| Public contract | `sender`, `children`, `avatar`, `name`, `metadata`, `density`, plus `BaseProps<HTMLElement>` and `ref` on the message article. |
|
|
52
|
+
| Behavior | Sender controls logical alignment and system-message suppression; density resolves explicit prop → message-list context → `balanced`; descendants receive the resolved context. |
|
|
53
|
+
| End-user impact | People reading a conversation can distinguish user, assistant, and system messages while names and translated fallback labels preserve each article's sender identity. |
|
|
54
|
+
| Builder impact | No new caller choice. Slot content that renders nothing leaves no empty space and no unnamed article. |
|
|
55
|
+
| Compatibility/readiness | Draft. No public API, default, or theming target changes; the empty-slot rows are a patch fix. Authority remains `draft` until exact-head owner approval. |
|
|
56
|
+
| Review checks | Reject a row that states unshipped behavior, changes sender or density meaning, treats caller content as component-owned, or turns the unresolved theming question into new API. |
|
|
57
|
+
| Governing rules | `architecture:public-component-api/INV1, INV3, INV5–INV6, INV8–INV9`; `architecture:component-theming-surface/INV3–INV6`; `spec:AST-029/FR3–FR6`. |
|
|
58
|
+
|
|
59
|
+
This table is a review projection; the body below is authoritative.
|
|
60
|
+
|
|
61
|
+
## Intent
|
|
62
|
+
|
|
63
|
+
ChatMessage is the sender-aware article wrapper for one message in a
|
|
64
|
+
conversation. It owns logical alignment, optional sender identity slots, the
|
|
65
|
+
resolved density context supplied to message descendants, and the article's
|
|
66
|
+
accessible name. Message content and its own interactions remain caller-owned.
|
|
67
|
+
|
|
68
|
+
Consumer syntax and examples belong in `ChatMessage.doc.mjs` and the Chat family
|
|
69
|
+
document.
|
|
70
|
+
|
|
71
|
+
## Compatibility and migration
|
|
72
|
+
|
|
73
|
+
- Released default preserved: yes — the published `./Chat` subpath exports
|
|
74
|
+
`ChatMessage`, `ChatMessageProps`, `ChatMessageSender`, and `ChatDensity`.
|
|
75
|
+
- Compatibility class: patch fix. Slot content that renders nothing creates no
|
|
76
|
+
visible slot, and a name that renders no text leaves the translated fallback
|
|
77
|
+
label; a name that renders text labels the article.
|
|
78
|
+
- Controlled/uncontrolled behavior: not applicable; the component owns no
|
|
79
|
+
controllable value.
|
|
80
|
+
- Migration decision: none.
|
|
81
|
+
|
|
82
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
83
|
+
|
|
84
|
+
## Ownership boundary
|
|
85
|
+
|
|
86
|
+
**Owns**
|
|
87
|
+
|
|
88
|
+
- One semantic `<article>` root and its public ref, DOM, data, ARIA, class, and
|
|
89
|
+
style passthrough.
|
|
90
|
+
- Logical start, end, or centered alignment selected by `sender`.
|
|
91
|
+
- Optional avatar, name, and metadata placement for non-system messages,
|
|
92
|
+
including collapsing a slot whose content renders nothing (DEC-1).
|
|
93
|
+
- Density resolution and the `ChatMessageContext` value supplied to descendants.
|
|
94
|
+
- The `chat-message` target with its `sender` and resolved `density` axes.
|
|
95
|
+
- The article's visible-name or translated fallback accessible label.
|
|
96
|
+
|
|
97
|
+
**Does not own / non-goals**
|
|
98
|
+
|
|
99
|
+
- Bubble presentation, grouping, width, name, or metadata — owned by
|
|
100
|
+
`component:ChatMessageBubble` when a bubble supplies them.
|
|
101
|
+
- Avatar appearance — owned by the caller's supplied node, commonly
|
|
102
|
+
`component:Avatar`.
|
|
103
|
+
- Metadata content, timestamp formatting, status, or footer actions — owned by
|
|
104
|
+
the supplied node and its components.
|
|
105
|
+
- Message-list ordering, spacing between messages, streaming announcements, or
|
|
106
|
+
scroll behavior — owned by `component:ChatMessageList` and
|
|
107
|
+
`component:ChatLayout`.
|
|
108
|
+
- Interactions inside `children`; descendants retain their own semantic and
|
|
109
|
+
accessibility contracts.
|
|
110
|
+
|
|
111
|
+
## Public concepts
|
|
112
|
+
|
|
113
|
+
| Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
|
|
114
|
+
| ------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------- | --------- | ---------------------- | --------- | ------------------------------- |
|
|
115
|
+
| `sender` | `assistant`; `user`; `system` | Selects logical alignment, article identity, and whether auxiliary identity slots show. | required in every message | required | component | stable | closed union; rejected by types |
|
|
116
|
+
| `children` | any `ReactNode` | Message body passed through inside the aligned content column. | every sender | required | caller | stable | n/a — required |
|
|
117
|
+
| `avatar` | renders content; renders nothing; skipped scalar; omitted | Optional identity node beside a non-system message. | assistant and user; suppressed for system | omitted | caller | stable | renders nothing: slot collapses |
|
|
118
|
+
| `name` | renders text; renders nothing; skipped scalar; omitted | Optional visible sender name and article label source. | assistant and user; suppressed for system | omitted | caller/component | stable | renders no text: fallback label |
|
|
119
|
+
| `metadata` | renders content; renders nothing; skipped scalar; omitted | Optional content below the message body. | assistant and user; suppressed for system | omitted | caller | stable | renders nothing: slot collapses |
|
|
120
|
+
| `density` | `compact`; `balanced`; `spacious`; omitted | Selects gaps and becomes descendant message context. | every sender | inherited | component/list context | stable | closed union; rejected by types |
|
|
121
|
+
| root surface | `ref`; DOM/data/ARIA props; `className`/`style`/`xstyle` | Extends the message article. | every sender | omitted | public component API | stable | n/a |
|
|
122
|
+
|
|
123
|
+
## Behavioral and layout contract
|
|
124
|
+
|
|
125
|
+
Draft requirements identify their basis so observed code is not mistaken for an
|
|
126
|
+
intentional decision.
|
|
127
|
+
|
|
128
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
129
|
+
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------ |
|
|
130
|
+
| FR1 | `assistant` MUST align to inline start, `user` to inline end, and `system` to the centre; the root MUST reflect the sender on the `chat-message` target. | shipped implementation; consumer docs; focused tests | verify |
|
|
131
|
+
| FR2 | `system` MUST suppress the supplied avatar, name, and metadata while continuing to render `children`. | shipped implementation; Chat family anatomy; focused tests | verify |
|
|
132
|
+
| FR3 | A non-system message MUST render supplied avatar, name, children, and metadata in that visual order. `null`, boolean, and empty-string auxiliary values MUST NOT create wrappers; a wrapper whose content renders nothing MUST collapse (DEC-1). | owner direction; focused tests | proposed |
|
|
133
|
+
| FR4 | Density MUST resolve explicit `density` → nearest `ChatMessageList` density → `balanced`, and the resolved value MUST reflect on the root target. | shipped implementation; consumer docs; focused tests; theming metadata | verify |
|
|
134
|
+
| FR5 | Descendants MUST receive the resolved `{sender, density}` value through `ChatMessageContext`. | shipped implementation; descendant composition | verify |
|
|
135
|
+
| FR6 | The article MUST carry the translated sender fallback as `aria-label`, and a non-system `name` that is not a skipped scalar MUST also label it through `aria-labelledby`. A name that renders text names the article; one that renders none leaves the fallback (accessible name computation 1.2, step 2B). | owner direction; accessible name computation 1.2; focused tests | proposed |
|
|
136
|
+
| FR7 | Accepted `ref`, DOM/data/ARIA props, `className`, `style`, and `xstyle` MUST reach and compose on the same article that carries the `chat-message` target. | `architecture:public-component-api/INV5–INV6, INV8`; focused tests | settled |
|
|
137
|
+
|
|
138
|
+
### Allowed variation
|
|
139
|
+
|
|
140
|
+
- **AV1 — Message content.** `children` may be bubbles, rich content, tool output,
|
|
141
|
+
media, or any other caller-owned React node.
|
|
142
|
+
- **AV2 — Identity content.** Avatar, name, and metadata are caller-owned nodes
|
|
143
|
+
passed to React unchanged; the wrapper controls placement, system suppression,
|
|
144
|
+
and collapse of a slot whose content renders nothing.
|
|
145
|
+
- **AV3 — Density geometry.** Resolved gaps follow the active theme's spacing
|
|
146
|
+
scale; the compact → balanced → spacious ordering is fixed.
|
|
147
|
+
|
|
148
|
+
### Representative states
|
|
149
|
+
|
|
150
|
+
| State | Required invariant | Allowed variation |
|
|
151
|
+
| ------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------- |
|
|
152
|
+
| Assistant with avatar, name, and metadata | Inline-start layout; visible name labels the article | Any caller-owned node may fill the slots |
|
|
153
|
+
| Skipped scalar auxiliary values | No slot or naming reference is created | `null`, booleans, empty strings |
|
|
154
|
+
| Auxiliary content that renders nothing | The slot collapses; a name of this kind leaves the fallback label | Empty array or Fragment, component returning `null` |
|
|
155
|
+
| Numeric auxiliary values | `0` remains visible and a numeric name labels the article | Any non-system sender |
|
|
156
|
+
| User without a name | Inline-end layout; translated sender fallback labels the article | Avatar and metadata may be present or omitted |
|
|
157
|
+
| System with auxiliary props supplied | Centre layout; avatar, name, and metadata remain suppressed | Any children continue to render |
|
|
158
|
+
| Standalone message | Resolved density is `balanced` | Any sender and content |
|
|
159
|
+
| Message inside a density-providing list | Inherits the list density | Any sender and content |
|
|
160
|
+
| Message with explicit density inside a list | Explicit density wins | Any supported density |
|
|
161
|
+
| Narrow container with long content | Root stays within the container and content remains available | Descendant content chooses its own wrap policy |
|
|
162
|
+
| RTL container | Logical user/assistant alignment mirrors with document direction | System remains centred |
|
|
163
|
+
|
|
164
|
+
### Transformation and precedence order
|
|
165
|
+
|
|
166
|
+
- **ORD1 — Density.** Resolve the explicit message prop first, then the nearest
|
|
167
|
+
message-list context, then `balanced`; use that single result for layout,
|
|
168
|
+
target reflection, and descendant context.
|
|
169
|
+
- **ORD2 — Accessible name.** A system `name` or a skipped scalar creates no
|
|
170
|
+
name wrapper. Otherwise the article references the name wrapper's generated
|
|
171
|
+
id; the rendered text there names the article, and empty text yields to the
|
|
172
|
+
translated fallback carried as `aria-label`.
|
|
173
|
+
|
|
174
|
+
### Performance and resources
|
|
175
|
+
|
|
176
|
+
- **PR1 — No external resources.** ChatMessage installs no listener, timer,
|
|
177
|
+
observer, request, or Effect.
|
|
178
|
+
- **PR2 — Stable broadcast.** The descendant context value changes only when the
|
|
179
|
+
resolved sender or density changes.
|
|
180
|
+
|
|
181
|
+
## Accessibility contract
|
|
182
|
+
|
|
183
|
+
- **AR1 — Article naming mechanism.** Every article carries the translated
|
|
184
|
+
sender fallback as `aria-label`. A name wrapper, when present, is referenced
|
|
185
|
+
through `aria-labelledby`; its rendered text names the article, and when it
|
|
186
|
+
renders no text the accessible name falls back to `aria-label`.
|
|
187
|
+
- **AR2 — No inspection.** Slot content is never inspected, cloned, or
|
|
188
|
+
executed to decide whether it renders; collapse and the naming fallback follow
|
|
189
|
+
the rendered DOM.
|
|
190
|
+
- **AR3 — Descendant ownership.** ChatMessage is non-interactive and introduces no
|
|
191
|
+
keyboard model. Interactive descendants remain responsible for their own roles,
|
|
192
|
+
names, states, focus, and activation.
|
|
193
|
+
|
|
194
|
+
## Design relationships
|
|
195
|
+
|
|
196
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
197
|
+
| ---------------- | ------------------------------------------------------- | ------------------------ | -------------- | ------------------ |
|
|
198
|
+
| Assistant | Align the message column to logical start | observed | content | FR1 |
|
|
199
|
+
| User | Align the message column to logical end | observed | content | FR1 |
|
|
200
|
+
| System | Centre the content column and suppress identity slots | observed | supporting | FR1–FR2 |
|
|
201
|
+
| Name | Use supporting type and secondary text colour tokens | observed | supporting | FR3 |
|
|
202
|
+
| Density | Increase root and child gaps across the three-step axis | observed | structure | FR4 |
|
|
203
|
+
|
|
204
|
+
No component-local design decision is settled by this draft.
|
|
205
|
+
|
|
206
|
+
### Theming reachability (observed)
|
|
207
|
+
|
|
208
|
+
| Anatomy part | Disposition |
|
|
209
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| Message root | `target` — the `chat-message` target sits on the article and reflects `sender` and resolved `density`; the Chat family document owns its consumer metadata. |
|
|
211
|
+
| Avatar | `delegatesTo` the supplied node, commonly the `avatar` target owned by `component:Avatar`. |
|
|
212
|
+
| Name | `none` — reachability-gap: the built-in name paints explicit supporting type and secondary colour on an untargeted descendant; see OQ1. |
|
|
213
|
+
| Content | `delegatesTo` caller-owned children and their targets; custom content has no component-owned target. |
|
|
214
|
+
| Metadata | `none` — intentional current boundary: the caller supplies the complete rendered node, while the wrapper contributes placement only. |
|
|
215
|
+
|
|
216
|
+
## Family and system relationships
|
|
217
|
+
|
|
218
|
+
- There is no current `family:chat` record. `Chat.doc.mjs` is the consumer-facing
|
|
219
|
+
family document and owns the shared target inventory; it is not product authority.
|
|
220
|
+
- `component:ChatMessageList` supplies optional density context and owns list
|
|
221
|
+
spacing, log semantics, streaming state, and message ordering.
|
|
222
|
+
- `component:ChatMessageBubble` consumes sender and density context when present
|
|
223
|
+
and owns bubble presentation.
|
|
224
|
+
- There is no current direct owner for ChatMessage's
|
|
225
|
+
`compact | balanced | spacious` density vocabulary or ordering. FR4–FR5 record
|
|
226
|
+
the shipped axis and precedence observationally; routed global layout claims do
|
|
227
|
+
not settle that component-specific vocabulary.
|
|
228
|
+
- `spec:AST-029` permits this observational draft and keeps the closed audit
|
|
229
|
+
inventory outside the component contract.
|
|
230
|
+
|
|
231
|
+
## Verification map
|
|
232
|
+
|
|
233
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
234
|
+
| ------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | ---------------------- |
|
|
235
|
+
| FR1–FR3 | `ChatMessage.test.tsx`; `ChatMessage.stories.tsx` | all senders; skipped scalar, renders-nothing, numeric, content, and suppressed slot states | Removing the scalar skip or the CSS collapse leaves an empty slot or a gap | `audit:ChatMessage/§4` |
|
|
236
|
+
| FR4–FR5 | `ChatMessage.test.tsx`; `themingTargets.test.ts` | standalone; inherited density; explicit override | Changing precedence or reflection gives descendants and themes a different density | `audit:ChatMessage/§2` |
|
|
237
|
+
| FR6, AR1–AR2 | `ChatMessage.test.tsx`; Chromium accessibility tree | visible, omitted, skipped-scalar, renders-nothing, and system names | Dropping the fallback `aria-label` or the name reference leaves an article unnamed | `audit:ChatMessage/§1` |
|
|
238
|
+
| FR7 | `ChatMessage.test.tsx`; public export and theming-target checks | ref; data prop; class; style | Moving passthrough or ref off the target article breaks the public root contract | `audit:ChatMessage/§3` |
|
|
239
|
+
| AR3 | Source review plus component-scoped accessibility audit | rich and interactive descendants | The wrapper introduces an interactive role or takes ownership away from descendants | `audit:ChatMessage/§1` |
|
|
240
|
+
| RTL | `ChatMessage.stories.tsx`; component-scoped RTL audit | all senders; narrow long content | Physical direction keeps user or assistant on the same side in both directions | `audit:ChatMessage/§9` |
|
|
241
|
+
|
|
242
|
+
## Decision log
|
|
243
|
+
|
|
244
|
+
- **DEC-1 — Slot emptiness follows the rendered DOM.** (`proposed`, awaiting
|
|
245
|
+
owner approval) ChatMessage skips only `null`, boolean, and empty-string
|
|
246
|
+
slot values. Any other content is passed to React unchanged; its wrapper
|
|
247
|
+
collapses with `:empty` when it renders nothing, and the article's naming
|
|
248
|
+
falls back to `aria-label` when the referenced name renders no text.
|
|
249
|
+
Rejected: inspecting arrays, Fragments, or iterables, which cannot see
|
|
250
|
+
component output and consumes one-shot iterators.
|
|
251
|
+
|
|
252
|
+
## Open questions
|
|
253
|
+
|
|
254
|
+
- **OQ1 — Should the built-in name become a separately themeable anatomy part?**
|
|
255
|
+
(`human-api`) The released family inventory exposes only the aggregate
|
|
256
|
+
`chat-message` target, while the name's explicit type and colour prevent a root
|
|
257
|
+
override from reaching it by inheritance. Adding or splitting a target changes
|
|
258
|
+
public theming API, so this draft records the reachability gap without choosing a
|
|
259
|
+
remedy.
|
|
260
|
+
|
|
261
|
+
## Content boundary
|
|
262
|
+
|
|
263
|
+
This file does not duplicate consumer prop tables or examples, current audit
|
|
264
|
+
scores, screenshot matrices, or shared-system rules. The audit PR owns the closed
|
|
265
|
+
run evidence required by `spec:AST-029/FR5`.
|