@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
|
@@ -81,17 +81,19 @@ describe('CodeBlock', () => {
|
|
|
81
81
|
expect(root).toHaveStyle({opacity: '0.75'});
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it('
|
|
84
|
+
it('names the code viewport and gives it a tab stop only while it overflows', () => {
|
|
85
|
+
// jsdom has no layout, so the viewport fits: a fitting viewport is not a
|
|
86
|
+
// tab stop (AST-025 FR12); the overflowing states are browser evidence.
|
|
85
87
|
render(<CodeBlock code="const x = 1;" language="javascript" />);
|
|
86
88
|
const region = screen.getByRole('group');
|
|
87
|
-
expect(region).toHaveAttribute('tabindex'
|
|
89
|
+
expect(region).not.toHaveAttribute('tabindex');
|
|
88
90
|
expect(region).toHaveAttribute('aria-label', 'javascript');
|
|
91
|
+
expect(region).toHaveAttribute('data-scroll-axis', 'both');
|
|
89
92
|
});
|
|
90
93
|
|
|
91
94
|
it('labels the scroll region "Code" when no language label is shown', () => {
|
|
92
95
|
render(<CodeBlock code="hello" hasLanguageLabel={false} />);
|
|
93
96
|
const region = screen.getByRole('group');
|
|
94
|
-
expect(region).toHaveAttribute('tabindex', '0');
|
|
95
97
|
expect(region).toHaveAttribute('aria-label', 'Code');
|
|
96
98
|
});
|
|
97
99
|
|
|
@@ -119,6 +121,84 @@ describe('CodeBlock', () => {
|
|
|
119
121
|
expect(screen.getByRole('group')).toHaveStyle({maxHeight: '0px'});
|
|
120
122
|
});
|
|
121
123
|
|
|
124
|
+
describe('diff layer', () => {
|
|
125
|
+
const diff =
|
|
126
|
+
'@@ -1,2 +1,2 @@\n- const a = 1;\n+ const a = 2;\n const b = 3;';
|
|
127
|
+
|
|
128
|
+
it('marks added, removed and header rows and leaves context unmarked', () => {
|
|
129
|
+
const {container} = render(
|
|
130
|
+
<CodeBlock code={diff} language="typescript" isDiff />,
|
|
131
|
+
);
|
|
132
|
+
const rows = container.querySelectorAll('[data-line]');
|
|
133
|
+
expect(rows).toHaveLength(4);
|
|
134
|
+
expect(rows[0].getAttribute('data-diff')).toBe('header');
|
|
135
|
+
expect(rows[1].getAttribute('data-diff')).toBe('removed');
|
|
136
|
+
expect(rows[2].getAttribute('data-diff')).toBe('added');
|
|
137
|
+
expect(rows[3].getAttribute('data-diff')).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('puts the glyph in its own marker column and keeps base-language tokens past it', () => {
|
|
141
|
+
const {container} = render(
|
|
142
|
+
<CodeBlock code={diff} language="typescript" isDiff />,
|
|
143
|
+
);
|
|
144
|
+
const rows = container.querySelectorAll('[data-line]');
|
|
145
|
+
const marker = (row: Element) =>
|
|
146
|
+
row.querySelector('[data-diff-marker]')?.textContent;
|
|
147
|
+
expect(marker(rows[1])).toBe('-');
|
|
148
|
+
expect(marker(rows[2])).toBe('+');
|
|
149
|
+
// The glyph is the source's own text, so assistive technology reads it.
|
|
150
|
+
expect(rows[1].querySelector('[data-diff-marker]')).not.toHaveAttribute(
|
|
151
|
+
'aria-hidden',
|
|
152
|
+
);
|
|
153
|
+
expect(marker(rows[3])).toBe(' ');
|
|
154
|
+
// The code column starts after the glyph on every line: the removed and
|
|
155
|
+
// context lines' content keeps its own two-space indentation.
|
|
156
|
+
const content = (row: Element) =>
|
|
157
|
+
row.querySelectorAll('span')[1]?.textContent;
|
|
158
|
+
expect(content(rows[1])).toBe(' const a = 1;');
|
|
159
|
+
expect(content(rows[3])).toBe(' const b = 3;');
|
|
160
|
+
// `const` is a keyword on changed and context lines alike.
|
|
161
|
+
expect(rows[1].querySelector('.astryx-token-keyword')).not.toBeNull();
|
|
162
|
+
expect(rows[3].querySelector('.astryx-token-keyword')).not.toBeNull();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('keeps the header and names the viewport diff / tsx diff', () => {
|
|
166
|
+
const {rerender} = render(<CodeBlock code={diff} isDiff />);
|
|
167
|
+
expect(screen.getByRole('group', {name: 'diff'})).toBeInTheDocument();
|
|
168
|
+
expect(screen.getByText('diff')).toBeInTheDocument();
|
|
169
|
+
rerender(<CodeBlock code={diff} language="tsx" isDiff />);
|
|
170
|
+
expect(screen.getByRole('group', {name: 'tsx diff'})).toBeInTheDocument();
|
|
171
|
+
expect(screen.getByText('tsx diff')).toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('copies the exact source, glyphs included', () => {
|
|
175
|
+
render(<CodeBlock code={diff} language="typescript" isDiff />);
|
|
176
|
+
fireEvent.click(screen.getByRole('button', {name: 'Copy code'}));
|
|
177
|
+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(diff);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('composes with highlightLines and does nothing without isDiff', () => {
|
|
181
|
+
const {container, rerender} = render(
|
|
182
|
+
<CodeBlock
|
|
183
|
+
code={diff}
|
|
184
|
+
language="typescript"
|
|
185
|
+
isDiff
|
|
186
|
+
highlightLines={[2]}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
expect(
|
|
190
|
+
container
|
|
191
|
+
.querySelector('[data-diff="removed"]')
|
|
192
|
+
?.getAttribute('data-line'),
|
|
193
|
+
).toBe('2');
|
|
194
|
+
rerender(<CodeBlock code={diff} language="typescript" />);
|
|
195
|
+
expect(container.querySelector('[data-diff]')).toBeNull();
|
|
196
|
+
expect(
|
|
197
|
+
container.querySelector('[data-line] [aria-hidden="true"]'),
|
|
198
|
+
).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
122
202
|
it('copies code when the copy button is clicked', () => {
|
|
123
203
|
render(<CodeBlock code="const x = 1;" language="javascript" />);
|
|
124
204
|
const copyButton = screen.getByRole('button', {name: 'Copy code'});
|
|
@@ -371,9 +451,9 @@ describe('CodeBlock', () => {
|
|
|
371
451
|
|
|
372
452
|
fireEvent.click(header);
|
|
373
453
|
expect(header).toHaveAttribute('aria-expanded', 'false');
|
|
374
|
-
// Collapsed: the wrapper is inert, so the
|
|
375
|
-
//
|
|
376
|
-
//
|
|
454
|
+
// Collapsed: the wrapper is inert, so the scroll container inside it
|
|
455
|
+
// drops out of the tab order and the accessibility tree instead of
|
|
456
|
+
// remaining an invisible tab stop while it overflows.
|
|
377
457
|
expect(region).toHaveAttribute('inert');
|
|
378
458
|
const scrollContainer = screen.getByRole('group');
|
|
379
459
|
expect(scrollContainer.closest('[inert]')).toBe(region);
|
|
@@ -399,12 +479,12 @@ describe('CodeBlock', () => {
|
|
|
399
479
|
expect(region).toHaveAttribute('inert');
|
|
400
480
|
fireEvent.click(header);
|
|
401
481
|
expect(header).toHaveAttribute('aria-expanded', 'true');
|
|
402
|
-
// Expanded again: inert is removed and the scroll container is a
|
|
403
|
-
//
|
|
482
|
+
// Expanded again: inert is removed and the scroll container is a named
|
|
483
|
+
// group once more (its tab stop follows overflow, see the browser spec).
|
|
404
484
|
expect(region).not.toHaveAttribute('inert');
|
|
405
485
|
const scrollContainer = screen.getByRole('group');
|
|
406
486
|
expect(scrollContainer.closest('[inert]')).toBeNull();
|
|
407
|
-
expect(scrollContainer).toHaveAttribute('
|
|
487
|
+
expect(scrollContainer).toHaveAttribute('data-scroll-axis', 'both');
|
|
408
488
|
});
|
|
409
489
|
|
|
410
490
|
it('applies a per-instance syntax theme via the syntaxTheme prop', () => {
|
|
@@ -34,11 +34,14 @@ import {
|
|
|
34
34
|
} from '../theme/tokens.stylex';
|
|
35
35
|
import {mergeProps} from '../utils';
|
|
36
36
|
import {useClipboard} from '../hooks/useClipboard';
|
|
37
|
+
import {useScrollableArea} from '../hooks/useScrollableArea';
|
|
37
38
|
import {Icon} from '../Icon';
|
|
38
39
|
import {IconButton} from '../IconButton';
|
|
40
|
+
import type {DiffLineKind} from './tokenizer';
|
|
39
41
|
import {
|
|
40
42
|
tokenize,
|
|
41
43
|
tokenizeAsync,
|
|
44
|
+
splitDiffLines,
|
|
42
45
|
flatTokensToLines,
|
|
43
46
|
SYNC_TOKENIZE_THRESHOLD,
|
|
44
47
|
} from './tokenizer';
|
|
@@ -117,6 +120,13 @@ const styles = stylex.create({
|
|
|
117
120
|
backgroundColor: 'var(--color-syntax-background)',
|
|
118
121
|
overflow: 'hidden',
|
|
119
122
|
},
|
|
123
|
+
// The viewport is a flex item of the column root. Its implied min-height
|
|
124
|
+
// would be its content while it fits (the shared capability keeps it
|
|
125
|
+
// `overflow: clip` then), so a root sized from outside could never make it
|
|
126
|
+
// shrink and scroll; 0 lets the root's bound reach it.
|
|
127
|
+
viewport: {
|
|
128
|
+
minHeight: 0,
|
|
129
|
+
},
|
|
120
130
|
headerRow: {
|
|
121
131
|
display: 'flex',
|
|
122
132
|
alignItems: 'center',
|
|
@@ -160,10 +170,6 @@ const styles = stylex.create({
|
|
|
160
170
|
margin: 0,
|
|
161
171
|
lineHeight: typeScaleVars['--text-supporting-leading'],
|
|
162
172
|
},
|
|
163
|
-
scrollContainer: {
|
|
164
|
-
overflowX: 'auto',
|
|
165
|
-
overflowY: 'auto',
|
|
166
|
-
},
|
|
167
173
|
codeWrapper: {
|
|
168
174
|
display: 'flex',
|
|
169
175
|
minWidth: 'fit-content',
|
|
@@ -228,6 +234,10 @@ const styles = stylex.create({
|
|
|
228
234
|
code: {
|
|
229
235
|
display: 'block',
|
|
230
236
|
flex: 1,
|
|
237
|
+
// The block grows to its longest line, so a row's paint (diff tint,
|
|
238
|
+
// highlightLines) spans the full scroll width instead of stopping at the
|
|
239
|
+
// viewport edge when the reader scrolls a long line.
|
|
240
|
+
minWidth: 'max-content',
|
|
231
241
|
paddingBlock: spacingVars['--spacing-3'],
|
|
232
242
|
paddingInline: spacingVars['--spacing-4'],
|
|
233
243
|
margin: 0,
|
|
@@ -239,6 +249,8 @@ const styles = stylex.create({
|
|
|
239
249
|
overflowWrap: 'normal',
|
|
240
250
|
},
|
|
241
251
|
codeWrapped: {
|
|
252
|
+
// Wrapped lines never overflow, and max-content would stop them wrapping.
|
|
253
|
+
minWidth: 'auto',
|
|
242
254
|
whiteSpace: 'pre-wrap',
|
|
243
255
|
wordBreak: 'break-all',
|
|
244
256
|
overflowWrap: 'break-word',
|
|
@@ -298,6 +310,75 @@ const styles = stylex.create({
|
|
|
298
310
|
marginInline: `calc(-1 * ${spacingVars['--spacing-4']})`,
|
|
299
311
|
paddingInline: spacingVars['--spacing-4'],
|
|
300
312
|
},
|
|
313
|
+
// Diff change rows: the whole row is tinted so added and removed lines read
|
|
314
|
+
// at a glance; the text keeps its ordinary colour, which stays readable on
|
|
315
|
+
// the tint where the semantic text colours would not, and the leading +/-
|
|
316
|
+
// glyph stays in the text as the cue that needs no colour. A caller's
|
|
317
|
+
// highlightLines row is declared after these and keeps its background.
|
|
318
|
+
// A changed row's margin reads the change: the tint runs under the gutter
|
|
319
|
+
// cell too (the row spans the full width), and the line number and the
|
|
320
|
+
// glyph take the row's marker slot. The code — tokens and plain runs alike
|
|
321
|
+
// — keeps the syntax theme's colours, so an identifier looks the same on a
|
|
322
|
+
// changed row as on a context row.
|
|
323
|
+
lineAdded: {
|
|
324
|
+
backgroundColor: 'var(--color-syntax-added)',
|
|
325
|
+
marginInline: `calc(-1 * ${spacingVars['--spacing-4']})`,
|
|
326
|
+
paddingInline: spacingVars['--spacing-4'],
|
|
327
|
+
'::before': {color: 'var(--color-syntax-added-marker)'},
|
|
328
|
+
},
|
|
329
|
+
lineRemoved: {
|
|
330
|
+
backgroundColor: 'var(--color-syntax-removed)',
|
|
331
|
+
marginInline: `calc(-1 * ${spacingVars['--spacing-4']})`,
|
|
332
|
+
paddingInline: spacingVars['--spacing-4'],
|
|
333
|
+
'::before': {color: 'var(--color-syntax-removed-marker)'},
|
|
334
|
+
},
|
|
335
|
+
// Hunk and file headers stay visible but quiet: a muted row with secondary
|
|
336
|
+
// text, never change paint.
|
|
337
|
+
lineDiffHeader: {
|
|
338
|
+
backgroundColor: 'var(--color-syntax-diff-header)',
|
|
339
|
+
// The syntax theme's own secondary colour: text-secondary in the default
|
|
340
|
+
// theme, the theme's punctuation colour in a defined one.
|
|
341
|
+
color: 'var(--color-syntax-punctuation)',
|
|
342
|
+
marginInline: `calc(-1 * ${spacingVars['--spacing-4']})`,
|
|
343
|
+
paddingInline: spacingVars['--spacing-4'],
|
|
344
|
+
},
|
|
345
|
+
// The glyph column: the +, - or space of every line sits in its own track
|
|
346
|
+
// before the code, so the code column starts at the same inline offset on
|
|
347
|
+
// every line and the content's indentation reads exactly as written. The
|
|
348
|
+
// glyph is laid out as text but not selectable; the copy button still
|
|
349
|
+
// copies the original source with its glyphs.
|
|
350
|
+
lineDiff: {
|
|
351
|
+
display: 'grid',
|
|
352
|
+
gridTemplateColumns: '1ch 1fr',
|
|
353
|
+
columnGap: spacingVars['--spacing-2'],
|
|
354
|
+
},
|
|
355
|
+
// With line numbers the gutter's gap (which the full-height divider sits
|
|
356
|
+
// in) has to stay between the number and the marker, so the grid has no
|
|
357
|
+
// gap of its own and the marker carries both gaps as margins.
|
|
358
|
+
lineDiffNumbered: {
|
|
359
|
+
// `auto`, not `1ch`: the marker's gap margins have to widen its track.
|
|
360
|
+
gridTemplateColumns: 'var(--_codeblock-gutter-width) auto 1fr',
|
|
361
|
+
columnGap: 0,
|
|
362
|
+
},
|
|
363
|
+
// The glyph is the source's own text: read by assistive technology, kept
|
|
364
|
+
// out of a drag selection (copy returns the exact source anyway).
|
|
365
|
+
diffMarker: {
|
|
366
|
+
color: colorVars['--color-text-secondary'],
|
|
367
|
+
userSelect: 'none',
|
|
368
|
+
whiteSpace: 'pre',
|
|
369
|
+
},
|
|
370
|
+
diffMarkerNumbered: {
|
|
371
|
+
marginInlineStart: `calc(${spacingVars['--spacing-3']} + ${borderVars['--border-width']} + ${spacingVars['--spacing-4']})`,
|
|
372
|
+
marginInlineEnd: spacingVars['--spacing-2'],
|
|
373
|
+
},
|
|
374
|
+
// On a tinted row the glyph is the cue a reader without colour depends on,
|
|
375
|
+
// so it takes the row's marker slot like the line number does.
|
|
376
|
+
diffMarkerAdded: {
|
|
377
|
+
color: 'var(--color-syntax-added-marker)',
|
|
378
|
+
},
|
|
379
|
+
diffMarkerRemoved: {
|
|
380
|
+
color: 'var(--color-syntax-removed-marker)',
|
|
381
|
+
},
|
|
301
382
|
sizeSm: {
|
|
302
383
|
fontSize: typeScaleVars['--text-supporting-size'],
|
|
303
384
|
},
|
|
@@ -332,12 +413,14 @@ const CodeChunk = React.memo(function CodeChunk({
|
|
|
332
413
|
lines,
|
|
333
414
|
startIndex,
|
|
334
415
|
highlightSet,
|
|
416
|
+
diffKinds,
|
|
335
417
|
renderLineContent,
|
|
336
418
|
lineNumbers,
|
|
337
419
|
}: {
|
|
338
420
|
lines: string[];
|
|
339
421
|
startIndex: number;
|
|
340
422
|
highlightSet: Set<number> | null;
|
|
423
|
+
diffKinds: ReadonlyArray<DiffLineKind> | null;
|
|
341
424
|
renderLineContent: (line: string, lineIndex: number) => React.ReactNode;
|
|
342
425
|
lineNumbers: boolean;
|
|
343
426
|
}) {
|
|
@@ -345,13 +428,22 @@ const CodeChunk = React.memo(function CodeChunk({
|
|
|
345
428
|
<>
|
|
346
429
|
{lines.map((line, j) => {
|
|
347
430
|
const i = startIndex + j;
|
|
431
|
+
const diffKind = diffKinds?.[i];
|
|
348
432
|
return (
|
|
349
433
|
<div
|
|
350
434
|
key={i}
|
|
351
435
|
data-line={i + 1}
|
|
436
|
+
data-diff={diffKind === 'context' ? undefined : diffKind}
|
|
352
437
|
{...stylex.props(
|
|
353
438
|
styles.line,
|
|
354
439
|
lineNumbers && styles.lineNumbered,
|
|
440
|
+
diffKinds != null && styles.lineDiff,
|
|
441
|
+
diffKinds != null && lineNumbers && styles.lineDiffNumbered,
|
|
442
|
+
diffKind === 'added' && styles.lineAdded,
|
|
443
|
+
diffKind === 'removed' && styles.lineRemoved,
|
|
444
|
+
diffKind === 'header' && styles.lineDiffHeader,
|
|
445
|
+
// Last on purpose: a highlight replaces a diff tint rather than
|
|
446
|
+
// stacking on it, so tokens keep their single-paint contrast.
|
|
355
447
|
(highlightSet?.has(i + 1) ?? false) && styles.lineHighlighted,
|
|
356
448
|
)}>
|
|
357
449
|
{renderLineContent(line, i)}
|
|
@@ -365,6 +457,7 @@ const CodeChunk = React.memo(function CodeChunk({
|
|
|
365
457
|
function renderLines(
|
|
366
458
|
lines: string[],
|
|
367
459
|
highlightSet: Set<number> | null,
|
|
460
|
+
diffKinds: ReadonlyArray<DiffLineKind> | null,
|
|
368
461
|
renderLineContent: (line: string, lineIndex: number) => React.ReactNode,
|
|
369
462
|
lineNumbers: boolean,
|
|
370
463
|
chunkSize: number = LINE_CHUNK_SIZE,
|
|
@@ -377,6 +470,7 @@ function renderLines(
|
|
|
377
470
|
lines={lines}
|
|
378
471
|
startIndex={0}
|
|
379
472
|
highlightSet={highlightSet}
|
|
473
|
+
diffKinds={diffKinds}
|
|
380
474
|
renderLineContent={renderLineContent}
|
|
381
475
|
lineNumbers={lineNumbers}
|
|
382
476
|
/>
|
|
@@ -399,6 +493,7 @@ function renderLines(
|
|
|
399
493
|
lines={chunkLines}
|
|
400
494
|
startIndex={start}
|
|
401
495
|
highlightSet={highlightSet}
|
|
496
|
+
diffKinds={diffKinds}
|
|
402
497
|
renderLineContent={renderLineContent}
|
|
403
498
|
lineNumbers={lineNumbers}
|
|
404
499
|
/>
|
|
@@ -420,6 +515,15 @@ export interface CodeBlockProps extends BaseProps<HTMLPreElement> {
|
|
|
420
515
|
hasLanguageLabel?: boolean;
|
|
421
516
|
hasLineNumbers?: boolean;
|
|
422
517
|
highlightLines?: number[];
|
|
518
|
+
/**
|
|
519
|
+
* Render the code as a unified diff over `language`: lines starting with
|
|
520
|
+
* `+` are added, `-` removed, `@@` is a hunk header (a `--- `/`+++ ` pair
|
|
521
|
+
* directly before one is a file header), anything else is context; the
|
|
522
|
+
* label reads `diff` or `<language> diff`. The glyph sits in its own column and the rest of every line
|
|
523
|
+
* keeps the language's syntax highlighting. Copy still returns the exact
|
|
524
|
+
* source, glyphs included.
|
|
525
|
+
*/
|
|
526
|
+
isDiff?: boolean;
|
|
423
527
|
hasCopyButton?: boolean;
|
|
424
528
|
onCopy?: () => void;
|
|
425
529
|
isWrapped?: boolean;
|
|
@@ -594,6 +698,8 @@ function SpanCodeContent({
|
|
|
594
698
|
lines,
|
|
595
699
|
tokenLines,
|
|
596
700
|
highlightSet,
|
|
701
|
+
diffKinds,
|
|
702
|
+
diff,
|
|
597
703
|
isWrapped,
|
|
598
704
|
sizeStyle,
|
|
599
705
|
hasLineNumbers,
|
|
@@ -602,6 +708,8 @@ function SpanCodeContent({
|
|
|
602
708
|
lines: string[];
|
|
603
709
|
tokenLines: TokenLine[];
|
|
604
710
|
highlightSet: Set<number> | null;
|
|
711
|
+
diffKinds: ReadonlyArray<DiffLineKind> | null;
|
|
712
|
+
diff: ReturnType<typeof splitDiffLines> | null;
|
|
605
713
|
isWrapped: boolean;
|
|
606
714
|
sizeStyle: stylex.StyleXStyles;
|
|
607
715
|
hasLineNumbers: boolean;
|
|
@@ -614,6 +722,31 @@ function SpanCodeContent({
|
|
|
614
722
|
const renderLineContent = useCallback(
|
|
615
723
|
(line: string, lineIndex: number): React.ReactNode => {
|
|
616
724
|
const tokens = tokenLines[lineIndex] ?? [];
|
|
725
|
+
if (diff) {
|
|
726
|
+
// Header rows show their raw text; every other row shows its glyph in
|
|
727
|
+
// the marker column and the base-language tokens of the rest.
|
|
728
|
+
const kind = diff.kinds[lineIndex];
|
|
729
|
+
const content = kind === 'header' ? line : diff.content[lineIndex];
|
|
730
|
+
return (
|
|
731
|
+
<>
|
|
732
|
+
<span
|
|
733
|
+
data-diff-marker=""
|
|
734
|
+
{...stylex.props(
|
|
735
|
+
styles.diffMarker,
|
|
736
|
+
hasLineNumbers && styles.diffMarkerNumbered,
|
|
737
|
+
kind === 'added' && styles.diffMarkerAdded,
|
|
738
|
+
kind === 'removed' && styles.diffMarkerRemoved,
|
|
739
|
+
)}>
|
|
740
|
+
{diff.glyphs[lineIndex] || ' '}
|
|
741
|
+
</span>
|
|
742
|
+
<span {...stylex.props(styles.lineContent)}>
|
|
743
|
+
{kind === 'header'
|
|
744
|
+
? content || '\u200b'
|
|
745
|
+
: buildSpanLine(content, tokens)}
|
|
746
|
+
</span>
|
|
747
|
+
</>
|
|
748
|
+
);
|
|
749
|
+
}
|
|
617
750
|
// Wrap tokens in a single element so they occupy one grid cell when line
|
|
618
751
|
// numbers are on (see `lineNumbered`); an inline span is a no-op when off.
|
|
619
752
|
return (
|
|
@@ -622,7 +755,7 @@ function SpanCodeContent({
|
|
|
622
755
|
</span>
|
|
623
756
|
);
|
|
624
757
|
},
|
|
625
|
-
[tokenLines],
|
|
758
|
+
[tokenLines, diff, hasLineNumbers],
|
|
626
759
|
);
|
|
627
760
|
|
|
628
761
|
return (
|
|
@@ -634,7 +767,13 @@ function SpanCodeContent({
|
|
|
634
767
|
hasLineNumbers && styles.codeNumbered,
|
|
635
768
|
hasLineNumbers && dynamicStyles.gutterWidth(maxDigits),
|
|
636
769
|
)}>
|
|
637
|
-
{renderLines(
|
|
770
|
+
{renderLines(
|
|
771
|
+
lines,
|
|
772
|
+
highlightSet,
|
|
773
|
+
diffKinds,
|
|
774
|
+
renderLineContent,
|
|
775
|
+
hasLineNumbers,
|
|
776
|
+
)}
|
|
638
777
|
</code>
|
|
639
778
|
);
|
|
640
779
|
}
|
|
@@ -647,6 +786,7 @@ function RangeCodeContent({
|
|
|
647
786
|
lines,
|
|
648
787
|
tokenLines,
|
|
649
788
|
highlightSet,
|
|
789
|
+
diffKinds,
|
|
650
790
|
isWrapped,
|
|
651
791
|
sizeStyle,
|
|
652
792
|
hasLineNumbers,
|
|
@@ -655,6 +795,7 @@ function RangeCodeContent({
|
|
|
655
795
|
lines: string[];
|
|
656
796
|
tokenLines: TokenLine[];
|
|
657
797
|
highlightSet: Set<number> | null;
|
|
798
|
+
diffKinds: ReadonlyArray<DiffLineKind> | null;
|
|
658
799
|
isWrapped: boolean;
|
|
659
800
|
sizeStyle: stylex.StyleXStyles;
|
|
660
801
|
hasLineNumbers: boolean;
|
|
@@ -694,7 +835,13 @@ function RangeCodeContent({
|
|
|
694
835
|
hasLineNumbers && styles.codeNumbered,
|
|
695
836
|
hasLineNumbers && dynamicStyles.gutterWidth(maxDigits),
|
|
696
837
|
)}>
|
|
697
|
-
{renderLines(
|
|
838
|
+
{renderLines(
|
|
839
|
+
lines,
|
|
840
|
+
highlightSet,
|
|
841
|
+
diffKinds,
|
|
842
|
+
renderLineContent,
|
|
843
|
+
hasLineNumbers,
|
|
844
|
+
)}
|
|
698
845
|
</code>
|
|
699
846
|
);
|
|
700
847
|
}
|
|
@@ -719,6 +866,7 @@ export function CodeBlock({
|
|
|
719
866
|
hasLanguageLabel = true,
|
|
720
867
|
hasLineNumbers = false,
|
|
721
868
|
highlightLines,
|
|
869
|
+
isDiff = false,
|
|
722
870
|
hasCopyButton = true,
|
|
723
871
|
onCopy,
|
|
724
872
|
isWrapped = false,
|
|
@@ -746,6 +894,7 @@ export function CodeBlock({
|
|
|
746
894
|
});
|
|
747
895
|
|
|
748
896
|
const useSpans =
|
|
897
|
+
isDiff ||
|
|
749
898
|
highlightMode === 'spans' ||
|
|
750
899
|
(highlightMode === 'auto' && !hasHighlightAPI()) ||
|
|
751
900
|
(highlightMode === 'auto' && isSafari());
|
|
@@ -758,8 +907,19 @@ export function CodeBlock({
|
|
|
758
907
|
return l;
|
|
759
908
|
}, [code]);
|
|
760
909
|
|
|
761
|
-
|
|
762
|
-
|
|
910
|
+
// In diff mode the base language sees the code with the glyph column set
|
|
911
|
+
// aside, so a `tsx` diff still highlights as `tsx` on every line; headers
|
|
912
|
+
// contribute an empty line and are rendered as their raw text.
|
|
913
|
+
const diff = useMemo(
|
|
914
|
+
() => (isDiff ? splitDiffLines(lines) : null),
|
|
915
|
+
[isDiff, lines],
|
|
916
|
+
);
|
|
917
|
+
const tokenLines = useTokenLines(
|
|
918
|
+
diff ? diff.content.join('\n') : code,
|
|
919
|
+
language,
|
|
920
|
+
customTokenizer,
|
|
921
|
+
);
|
|
922
|
+
const diffKinds = diff?.kinds ?? null;
|
|
763
923
|
const highlightSet = useMemo(
|
|
764
924
|
() => (highlightLines ? new Set(highlightLines) : null),
|
|
765
925
|
[highlightLines],
|
|
@@ -775,8 +935,18 @@ export function CodeBlock({
|
|
|
775
935
|
const sizeStyle = size === 'sm' ? styles.sizeSm : styles.sizeMd;
|
|
776
936
|
// Digits in the largest line number — sizes the gutter column width.
|
|
777
937
|
const maxLineDigits = String(lines.length).length;
|
|
778
|
-
|
|
779
|
-
|
|
938
|
+
// With the diff layer on, the label and viewport name say so: `diff` over
|
|
939
|
+
// plaintext, `tsx diff` over a language — so a diff keeps its header,
|
|
940
|
+
// collapse control, and a name that still matches a `diff` fence.
|
|
941
|
+
const languageLabel = !hasLanguageLabel
|
|
942
|
+
? null
|
|
943
|
+
: isDiff
|
|
944
|
+
? language === 'plaintext'
|
|
945
|
+
? 'diff'
|
|
946
|
+
: `${language} diff`
|
|
947
|
+
: language !== 'plaintext'
|
|
948
|
+
? language
|
|
949
|
+
: null;
|
|
780
950
|
const showHeader = title != null || languageLabel != null;
|
|
781
951
|
|
|
782
952
|
// A collapsed body must never outlive the visible header that controls it.
|
|
@@ -800,7 +970,19 @@ export function CodeBlock({
|
|
|
800
970
|
// this is always a resolvable reference — aria-controls can be unconditional.
|
|
801
971
|
const regionId = useId();
|
|
802
972
|
|
|
803
|
-
|
|
973
|
+
// The code viewport enters the Tab order only while its content actually
|
|
974
|
+
// overflows (AST-025 FR12–FR13): the shared scroll capability measures
|
|
975
|
+
// both axes, keeps focus where it is when overflow goes away, and owns the
|
|
976
|
+
// overflow declarations. role="group" (not "region") so several code
|
|
977
|
+
// blocks on a page don't create duplicate same-named landmarks.
|
|
978
|
+
const {getViewportProps, getContentProps} = useScrollableArea({
|
|
979
|
+
axis: 'both',
|
|
980
|
+
keyboardAccess: {
|
|
981
|
+
owner: 'viewport',
|
|
982
|
+
role: 'group',
|
|
983
|
+
label: languageLabel ?? t('@astryx.codeBlock.code'),
|
|
984
|
+
},
|
|
985
|
+
});
|
|
804
986
|
const scrollStyle: CSSProperties | undefined =
|
|
805
987
|
maxHeight != null
|
|
806
988
|
? {
|
|
@@ -895,27 +1077,24 @@ export function CodeBlock({
|
|
|
895
1077
|
|
|
896
1078
|
const codeBody = (
|
|
897
1079
|
<div
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
// scroll long or wide code that overflows the viewport. Uses
|
|
901
|
-
// role="group" (not "region") so multiple code blocks on a page don't
|
|
902
|
-
// create duplicate same-named landmarks (axe: landmark-unique).
|
|
903
|
-
tabIndex={0}
|
|
904
|
-
role="group"
|
|
905
|
-
aria-label={languageLabel ?? t('@astryx.codeBlock.code')}
|
|
906
|
-
{...mergeProps(stylex.props(styles.scrollContainer), {
|
|
1080
|
+
{...getViewportProps<HTMLDivElement>({
|
|
1081
|
+
...stylex.props(styles.viewport),
|
|
907
1082
|
style: scrollStyle,
|
|
908
1083
|
})}>
|
|
909
1084
|
<div
|
|
910
|
-
{...
|
|
911
|
-
|
|
912
|
-
|
|
1085
|
+
{...getContentProps<HTMLDivElement>(
|
|
1086
|
+
stylex.props(
|
|
1087
|
+
styles.codeWrapper,
|
|
1088
|
+
showHeader && !hasLineNumbers && styles.codeWrapperCompact,
|
|
1089
|
+
),
|
|
913
1090
|
)}>
|
|
914
1091
|
{useSpans ? (
|
|
915
1092
|
<SpanCodeContent
|
|
916
1093
|
lines={lines}
|
|
917
1094
|
tokenLines={tokenLines}
|
|
918
1095
|
highlightSet={highlightSet}
|
|
1096
|
+
diffKinds={diffKinds}
|
|
1097
|
+
diff={diff}
|
|
919
1098
|
isWrapped={isWrapped}
|
|
920
1099
|
sizeStyle={sizeStyle}
|
|
921
1100
|
hasLineNumbers={hasLineNumbers}
|
|
@@ -926,6 +1105,7 @@ export function CodeBlock({
|
|
|
926
1105
|
lines={lines}
|
|
927
1106
|
tokenLines={tokenLines}
|
|
928
1107
|
highlightSet={highlightSet}
|
|
1108
|
+
diffKinds={diffKinds}
|
|
929
1109
|
isWrapped={isWrapped}
|
|
930
1110
|
sizeStyle={sizeStyle}
|
|
931
1111
|
hasLineNumbers={hasLineNumbers}
|
|
@@ -958,7 +1138,7 @@ export function CodeBlock({
|
|
|
958
1138
|
// While collapsed, the region is only hidden visually (0fr grid
|
|
959
1139
|
// row); inert also removes it from the tab order and accessibility
|
|
960
1140
|
// tree so keyboard users cannot Tab into the invisible scroll
|
|
961
|
-
// container (
|
|
1141
|
+
// container (a tab stop while it overflows). aria-controls pointing at an inert
|
|
962
1142
|
// element remains a valid, resolvable reference.
|
|
963
1143
|
inert={isCollapsed ? true : undefined}
|
|
964
1144
|
{...stylex.props(
|