@astryxdesign/core 0.6.1 → 0.6.2

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
  3. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  4. package/dist/DateRangeInput/DateRangeInput.js +16 -9
  5. package/dist/Dialog/DialogHeader.d.ts +1 -1
  6. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  7. package/dist/Dialog/DialogHeader.js +10 -7
  8. package/dist/FileInput/FileInput.d.ts.map +1 -1
  9. package/dist/FileInput/FileInput.js +9 -3
  10. package/dist/Markdown/Markdown.d.ts +10 -2
  11. package/dist/Markdown/Markdown.d.ts.map +1 -1
  12. package/dist/Markdown/Markdown.js +58 -14
  13. package/dist/Markdown/index.d.ts +1 -1
  14. package/dist/Markdown/index.d.ts.map +1 -1
  15. package/dist/Markdown/parser.d.ts +126 -12
  16. package/dist/Markdown/parser.d.ts.map +1 -1
  17. package/dist/Markdown/parser.js +369 -34
  18. package/dist/Markdown/utils.d.ts +1 -1
  19. package/dist/Markdown/utils.d.ts.map +1 -1
  20. package/dist/Slider/Slider.d.ts.map +1 -1
  21. package/dist/Slider/Slider.js +5 -2
  22. package/dist/Spinner/Spinner.d.ts +1 -1
  23. package/dist/Spinner/Spinner.d.ts.map +1 -1
  24. package/dist/Spinner/Spinner.js +23 -15
  25. package/dist/astryx.css +2 -1
  26. package/locales/en.json +16 -0
  27. package/locales/pseudo.json +12 -0
  28. package/package.json +6 -4
  29. package/scripts/agent-doc-state.mjs +1 -1
  30. package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
  31. package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
  32. package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
  33. package/src/DateRangeInput/DateRangeInput.tsx +29 -20
  34. package/src/Dialog/Dialog.doc.mjs +3 -0
  35. package/src/Dialog/Dialog.spec.md +1 -1
  36. package/src/Dialog/DialogHeader.doc.mjs +38 -0
  37. package/src/Dialog/DialogHeader.test.tsx +49 -0
  38. package/src/Dialog/DialogHeader.tsx +23 -4
  39. package/src/Dialog/modules/DialogHeader.spec.md +152 -0
  40. package/src/FileInput/FileInput.doc.mjs +2 -0
  41. package/src/FileInput/FileInput.spec.md +199 -0
  42. package/src/FileInput/FileInput.test.tsx +14 -0
  43. package/src/FileInput/FileInput.tsx +13 -3
  44. package/src/Markdown/Markdown.doc.mjs +167 -42
  45. package/src/Markdown/Markdown.public.test.ts +157 -0
  46. package/src/Markdown/Markdown.spec.md +149 -70
  47. package/src/Markdown/Markdown.test.tsx +107 -3
  48. package/src/Markdown/Markdown.tsx +116 -35
  49. package/src/Markdown/incremental.test.ts +175 -7
  50. package/src/Markdown/index.ts +6 -0
  51. package/src/Markdown/parser.perf.test.ts +3 -1
  52. package/src/Markdown/parser.test.ts +122 -0
  53. package/src/Markdown/parser.ts +609 -81
  54. package/src/Markdown/utils.ts +6 -0
  55. package/src/Slider/Slider.doc.mjs +16 -0
  56. package/src/Slider/Slider.spec.md +61 -47
  57. package/src/Slider/Slider.test.tsx +18 -0
  58. package/src/Slider/Slider.tsx +12 -6
  59. package/src/Spinner/Spinner.doc.mjs +6 -3
  60. package/src/Spinner/Spinner.test.tsx +37 -0
  61. package/src/Spinner/Spinner.tsx +31 -14
  62. package/src/theme/derivedVarRegistry.test.ts +6 -4
@@ -0,0 +1,199 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 4
4
+ kind: component
5
+ id: component:FileInput
6
+ authority: current
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-09-14
11
+ owners: [cixzhang, imdreamrunner]
12
+ review_triggers: [theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/FileInput/FileInput.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ scripts/check-knowledge.mjs,
18
+ ]
19
+ modules: []
20
+ families: [family:input-fields]
21
+ design_specs: []
22
+ architecture: [architecture:component-theming-surface]
23
+ contributing: []
24
+ system_specs: []
25
+ ---
26
+
27
+ # FileInput component contract
28
+
29
+ ## Intent
30
+
31
+ FileInput presents a labelled file-selection field in compact input or dropzone
32
+ form. This contract records its current consumer anatomy and approves separate theme
33
+ ownership for the upload affordance that FileInput paints through Icon.
34
+
35
+ ## Compatibility and migration
36
+
37
+ - Released default preserved: `yes`
38
+ - Compatibility class: additive public theming target; no existing target,
39
+ runtime default, DOM, prop, interaction, or accessibility behavior changes
40
+ - Controlled/uncontrolled behavior: unchanged; FileInput remains controlled
41
+ - Migration decision: `component:FileInput/DEC-1`
42
+
43
+ Consumer migration instructions belong in consumer docs and release notes.
44
+
45
+ ## Ownership boundary
46
+
47
+ **Owns**
48
+
49
+ - The visible file-selection surface and its input/dropzone mode.
50
+ - Whether, where, and at what default size the upload affordance appears.
51
+ - Reflecting FileInput's mode on its locally owned theme targets.
52
+
53
+ **Does not own / non-goals**
54
+
55
+ - The upload artwork or Icon's base color, size, and accessibility semantics —
56
+ owned by `component:Icon`.
57
+ - Label, description, clear-control, and validation-message presentation — owned
58
+ by `component:Field` and `component:FieldStatus`.
59
+ - Loading-indicator presentation — owned by `component:Spinner`.
60
+ - A new prop, variant, icon slot, or custom property.
61
+
62
+ ## Public concepts
63
+
64
+ No consumer prop changes. The `file-input-icon` target gives themes a
65
+ same-element seam for the Upload icon and reflects the existing `mode` axis. The
66
+ existing `file-input` target remains on the visible selection surface and keeps
67
+ its `mode` and `status` axes.
68
+
69
+ ## Behavioral and layout contract
70
+
71
+ | ID | Candidate invariant | Basis | Review state |
72
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------- |
73
+ | FR1 | The visible selection surface carries `file-input` and reflects the existing `mode` and resolved status. | Current source, public docs, and focused tests | Verified current behavior |
74
+ | FR2 | When not loading, input mode renders an upload affordance at the small Icon size. Dropzone mode renders it at the medium Icon size only while no file is selected. | Current source and focused tests | Verified current behavior |
75
+ | FR3 | Icon owns the rendered glyph's base size, color, and accessibility semantics; FileInput owns the affordance's mode-dependent placement and default size. | Current composition and component boundaries | Verified current composition |
76
+ | FR4 | The rendered upload affordance MUST carry `file-input-icon` with the existing `mode` reflected, so a theme can restyle the glyph box without structural selectors or changing every Icon that uses the same artwork. | Owner-approved target contract | Approved additive contract |
77
+ | FR5 | Adding the target MUST NOT change the default artwork, computed layout, interaction, file-selection behavior, accessible name, or decorative Icon semantics. | Compatibility policy and focused regression tests | Required compatibility behavior |
78
+
79
+ ### Allowed variation
80
+
81
+ - **AV1 — Theme paint.** A theme may change standard visual
82
+ properties such as the upload glyph's size or color through
83
+ `file-input-icon`; FileInput still owns whether and where the affordance renders.
84
+ - **AV2 — Artwork.** Icon registry and future icon-slot decisions may change the
85
+ artwork without changing this CSS target's ownership of the painted glyph box.
86
+
87
+ ### Representative states
88
+
89
+ | State | Required invariant | Allowed variation |
90
+ | ------------------------ | ------------------------------------------------------------------------ | --------------------------------------- |
91
+ | Input, empty or selected | Small upload affordance renders on the input surface when not loading. | Files, placeholder, status, theme paint |
92
+ | Dropzone, empty | Medium upload affordance renders above the placeholder when not loading. | Drag state, placeholder, theme paint |
93
+ | Dropzone, selected | File names replace the upload affordance. | File names and status |
94
+ | Loading | Spinner replaces the upload affordance. | Mode and loading presentation |
95
+
96
+ ### Transformation and precedence order
97
+
98
+ - **ORD1 — Content selection.** Resolve loading and selected-file state, choose
99
+ input or dropzone content, then render the mode-sized upload affordance only in
100
+ the states recorded by FR2.
101
+ - **ORD2 — Theme composition.** Icon applies its base size and color, then the
102
+ same-element FileInput target participates in the existing theme layer and
103
+ standard Icon styling merge order.
104
+
105
+ ### Performance and resources
106
+
107
+ - **PR1 — No new work.** The additive target performs no measurement, listener,
108
+ observer, state update, or additional render pass.
109
+
110
+ ## Accessibility contract
111
+
112
+ The upload affordance remains decorative. The existing focusable file-selection
113
+ trigger, label, description, required/invalid state, disabled explanation, and
114
+ selection announcements remain unchanged.
115
+
116
+ ## Design relationships
117
+
118
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
119
+ | ---------------- | ----------------------------------------------------------------------------------- | ---------------------------------------- | -------------- | ------------------ |
120
+ | Drop zone | Presents the visible file-selection surface in input or dropzone form. | Current source and public docs | Prominent | FR1 |
121
+ | Upload icon | Hints at the upload action and changes default size with the selected mode. | Current source and owner-approved target | Supporting | FR2, FR3, FR4 |
122
+ | Shared feedback | Uses Field, FieldStatus, and Spinner for labels, validation, and loading treatment. | Current shared composition | Supporting | FR5 |
123
+
124
+ ### Theming anatomy
125
+
126
+ <!-- anatomy-theming:v1 -->
127
+
128
+ ```json
129
+ {
130
+ "Label": {
131
+ "delegatesTo": {"owner": "component:Field", "target": "field-label"}
132
+ },
133
+ "Description": {
134
+ "none": {
135
+ "reason": "unsettled: No current public target reaches the stable Description; future exposure still needs an owner decision"
136
+ }
137
+ },
138
+ "Drop zone": {"target": "file-input"},
139
+ "Upload icon": {"target": "file-input-icon"},
140
+ "Placeholder": {"inherits": "file-input"},
141
+ "File name display": {"inherits": "file-input"},
142
+ "Clear button": {
143
+ "delegatesTo": {
144
+ "owner": "component:Field",
145
+ "target": "input-clear-button"
146
+ }
147
+ },
148
+ "Spinner": {
149
+ "delegatesTo": {"owner": "component:Spinner", "target": "spinner"}
150
+ },
151
+ "Status message": {
152
+ "delegatesTo": {
153
+ "owner": "component:FieldStatus",
154
+ "target": "field-status"
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ The `file-input-icon` disposition records the approved target state. Icon still
161
+ owns the general `icon` target and base glyph semantics; FileInput's narrower
162
+ target owns only this stable upload position and its existing mode distinction.
163
+
164
+ ## Family and system relationships
165
+
166
+ - `family:input-fields` owns shared labelled-field and validation behavior.
167
+ - `architecture:component-theming-surface` owns target qualification, anatomy
168
+ mapping, and the requirement that public targets sit on stable painted parts.
169
+ - Field, FieldStatus, Icon, and Spinner retain their existing public target
170
+ contracts when composed by FileInput.
171
+
172
+ ## Verification map
173
+
174
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
175
+ | ------------------- | ----------------------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------- |
176
+ | FR1, FR2 | `FileInput.test.tsx` rendering and target suites | Input/dropzone; empty/selected/loading | Moving the root target or changing when/at what size the affordance renders breaks focused assertions. | `audit:FileInput/theming` |
177
+ | FR3, FR4, FR5 | `FileInput.test.tsx`, `themingTargets.test.ts`, probe-theme check | Both modes and same-element Icon target | Missing the target, reflecting the wrong mode, or moving it off the glyph fails source/docs/probe coverage. | `audit:FileInput/theming` |
178
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Nine anatomy entries and two locally owned targets | Missing, extra, prefixed, stale, or unclaimed current mappings fail validation. | `audit:FileInput/anatomy` |
179
+
180
+ ## Decision log
181
+
182
+ ### DEC-1 — Upload icon is stable FileInput theme anatomy
183
+
184
+ **Reference:** `component:FileInput/DEC-1`
185
+ **Decider:** cixzhang, 2026-09-14
186
+
187
+ The upload icon is a stable, consumer-recognizable FileInput affordance whose
188
+ mode-dependent placement and default size belong to FileInput. It receives the
189
+ `file-input-icon` target on the same Icon element that paints the glyph, while
190
+ Icon retains its general target and base glyph semantics.
191
+
192
+ ## Open questions
193
+
194
+ None.
195
+
196
+ ## Content boundary
197
+
198
+ This file does not duplicate consumer prop tables, examples, implementation
199
+ steps, or shared-component contracts. It links to their owners.
@@ -119,6 +119,20 @@ describe('FileInput', () => {
119
119
  expect(screen.getByText('Drop here')).toBeInTheDocument();
120
120
  });
121
121
 
122
+ it.each([
123
+ {mode: 'input' as const, size: 'sm'},
124
+ {mode: 'dropzone' as const, size: 'md'},
125
+ ])('exposes the upload icon as a $mode theme target', ({mode, size}) => {
126
+ render(
127
+ <FileInput label="Upload" mode={mode} value={null} onChange={() => {}} />,
128
+ );
129
+
130
+ const icon = document.querySelector('.astryx-file-input-icon');
131
+ expect(icon).toHaveClass('astryx-icon');
132
+ expect(icon).toHaveAttribute('data-mode', mode);
133
+ expect(icon).toHaveAttribute('data-size', size);
134
+ });
135
+
122
136
  it('displays selected file name', () => {
123
137
  const file = createFile('report.pdf', 1024, 'application/pdf');
124
138
  render(<FileInput label="Document" value={file} onChange={() => {}} />);
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @file FileInput.tsx
7
7
  * @input Uses React, useId, Field, Icon, Spinner, VisuallyHidden
8
- * @output Exports FileInput component, FileInputProps, FileInputStatus
8
+ * @output Exports FileInput component, public types, and its root/icon theme targets
9
9
  * @position Core implementation; consumed by index.ts, tested by FileInput.test.tsx
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
@@ -707,7 +707,12 @@ export function FileInput({
707
707
  }
708
708
  return (
709
709
  <>
710
- <Icon icon="arrowUp" size="md" color="secondary" />
710
+ <Icon
711
+ icon="arrowUp"
712
+ size="md"
713
+ color="secondary"
714
+ {...themeProps('file-input-icon', {mode})}
715
+ />
711
716
  <span {...stylex.props(styles.placeholderText)}>
712
717
  {isDragOver ? t('@astryx.fileInput.dropHint') : displayPlaceholder}
713
718
  </span>
@@ -728,7 +733,12 @@ export function FileInput({
728
733
  }
729
734
  return (
730
735
  <>
731
- <Icon icon="arrowUp" size="sm" color="secondary" />
736
+ <Icon
737
+ icon="arrowUp"
738
+ size="sm"
739
+ color="secondary"
740
+ {...themeProps('file-input-icon', {mode})}
741
+ />
732
742
  <span
733
743
  {...stylex.props(
734
744
  hasFiles ? styles.fileNameText : styles.placeholderText,
@@ -5,8 +5,7 @@ const anatomy = [
5
5
  {
6
6
  name: 'Document',
7
7
  required: true,
8
- description:
9
- 'Root container for block or inline Markdown content.',
8
+ description: 'Root container for block or inline Markdown content.',
10
9
  },
11
10
  {
12
11
  name: 'Heading',
@@ -41,7 +40,8 @@ const anatomy = [
41
40
  {
42
41
  name: 'Table',
43
42
  required: false,
44
- description: 'Scrollable table block rendered from Markdown rows and columns.',
43
+ description:
44
+ 'Scrollable table block rendered from Markdown rows and columns.',
45
45
  },
46
46
  {
47
47
  name: 'Divider',
@@ -138,14 +138,14 @@ export const docs = {
138
138
  name: 'contentAlign',
139
139
  type: "'start' | 'center'",
140
140
  description:
141
- "Alignment of prose content within the container when contentWidth is narrower than the available space.",
141
+ 'Alignment of prose content within the container when contentWidth is narrower than the available space.',
142
142
  default: "'start'",
143
143
  },
144
144
  {
145
145
  name: 'inlinePlugins',
146
146
  type: 'MarkdownInlinePlugin[]',
147
147
  description:
148
- 'Transforms regex matches in parsed text nodes into custom inline React elements. Use for issue refs, diff refs, mentions, and other shorthand patterns. Inline code and fenced code blocks are unaffected.',
148
+ 'Transforms regex matches in parsed text nodes into custom inline React elements. Use for prefixed identifiers, mentions, and other shorthand patterns. Inline code, fenced code blocks, and math are unaffected.',
149
149
  },
150
150
  {
151
151
  name: 'autolink',
@@ -156,7 +156,8 @@ export const docs = {
156
156
  {
157
157
  name: 'components',
158
158
  type: 'MarkdownComponents',
159
- description: 'Custom React component overrides for rendered Markdown elements (code, inlineCode, link, heading, paragraph, image, blockquote, hr, citation).',
159
+ description:
160
+ 'Custom React component overrides for rendered Markdown elements (code, inlineCode, math, link, heading, paragraph, image, blockquote, hr, citation). Providing math enables `$…$` inline and `$$…$$` display parsing and receives `{value, display}`; omit it when dollar text should stay literal.',
160
161
  },
161
162
  {
162
163
  name: 'xstyle',
@@ -184,7 +185,8 @@ export const docs = {
184
185
  ],
185
186
  playground: {
186
187
  defaults: {
187
- children: '## Getting Started\n\nInstall the package:\n\n```bash\nnpm install @astryxdesign/core\n```\n\nThen import and use any component:\n\n```tsx\nimport {Button} from \'@astryxdesign/core/Button\';\n```\n\n**Bold**, *italic*, and `inline code` all work.',
188
+ children:
189
+ "## Getting Started\n\nInstall the package:\n\n```bash\nnpm install @astryxdesign/core\n```\n\nThen import and use any component:\n\n```tsx\nimport {Button} from '@astryxdesign/core/Button';\n```\n\n**Bold**, *italic*, and `inline code` all work.",
188
190
  },
189
191
  },
190
192
  theming: {
@@ -229,11 +231,41 @@ export const docs = {
229
231
  description:
230
232
  'Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.',
231
233
  bestPractices: [
232
- { guidance: true, description: 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.' },
233
- { guidance: true, description: 'Use contentWidth to keep prose at a readable line length in wide layouts.' },
234
- { guidance: true, description: 'Use inlinePlugins for custom shorthand patterns like issue refs, diff refs, and mentions instead of preprocessing the markdown string.' },
235
- { guidance: true, description: 'Pair with Outline and useOutlineFromMarkdown for section navigation: headings render generated id attributes that match the outline item ids, so hash links scroll to their target.' },
236
- { guidance: false, description: 'Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.' },
234
+ {
235
+ guidance: true,
236
+ description:
237
+ 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.',
238
+ },
239
+ {
240
+ guidance: true,
241
+ description:
242
+ 'Use contentWidth to keep prose at a readable line length in wide layouts.',
243
+ },
244
+ {
245
+ guidance: true,
246
+ description:
247
+ 'Use inlinePlugins for prefixed identifiers, mentions, and other prose-only shorthand instead of preprocessing the markdown string.',
248
+ },
249
+ {
250
+ guidance: true,
251
+ description:
252
+ 'Provide components.math only for documents that use dollar-delimited math. The renderer owns typesetting and accessible output; Astryx passes the expression as text and never executes raw HTML.',
253
+ },
254
+ {
255
+ guidance: true,
256
+ description:
257
+ 'For direct parsing, use MathParseOptions and handle InlineNodeWithMath or BlockNodeWithMath. Incremental math parsing also uses createIncrementalState<true>() and IncrementalParseState<true>; default calls and ParseOptions annotations keep the legacy unions.',
258
+ },
259
+ {
260
+ guidance: true,
261
+ description:
262
+ 'Pair with Outline and useOutlineFromMarkdown for section navigation: headings render generated id attributes that match the outline item ids, so hash links scroll to their target.',
263
+ },
264
+ {
265
+ guidance: false,
266
+ description:
267
+ 'Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.',
268
+ },
237
269
  ],
238
270
  },
239
271
  examples: [
@@ -259,23 +291,38 @@ import {Text} from '@astryxdesign/core/Text';
259
291
  `,
260
292
  },
261
293
  {
262
- label: 'Inline Plugins',
294
+ label: 'Entity links',
263
295
  code: `
264
296
  import {Link} from '@astryxdesign/core/Link';
265
297
 
266
- const issuePlugins = [
298
+ const entityPlugins = [
267
299
  {
268
300
  pattern: /\\b([A-Z][A-Z0-9]+-\\d+)\\b/g,
269
301
  render: (match, key) => (
270
- <Link key={key} href={\`/issues/\${match[1]}\`}>
302
+ <Link key={key} href={\`/entities/\${match[1]}\`}>
271
303
  {match[0]}
272
304
  </Link>
273
305
  ),
274
306
  },
275
307
  ];
276
308
 
277
- <Markdown inlinePlugins={issuePlugins}>
278
- {'Fixed PROJ-123. Inline code stays plain: \`PROJ-999\`.'}
309
+ <Markdown inlinePlugins={entityPlugins}>
310
+ {'See DOC-2048. Inline code stays plain: \`DOC-9999\`.'}
311
+ </Markdown>;
312
+ `,
313
+ },
314
+ {
315
+ label: 'Math renderer',
316
+ code: `
317
+ import {BlockMath, InlineMath} from 'react-katex';
318
+
319
+ function MathExpression({value, display}) {
320
+ const Component = display === 'block' ? BlockMath : InlineMath;
321
+ return <Component math={value} />;
322
+ }
323
+
324
+ <Markdown components={{math: MathExpression}}>
325
+ {'Inline $x_1 + y$ and display math:\\n\\n$$\\n\\\\sum_i x_i\\n$$'}
279
326
  </Markdown>;
280
327
  `,
281
328
  },
@@ -315,8 +362,7 @@ export const docsZh = {
315
362
  {
316
363
  name: 'isStreaming',
317
364
  type: 'boolean',
318
- description:
319
- '启用流式模式,使用增量解析和淡入动画处理分块文本。',
365
+ description: '启用流式模式,使用增量解析和淡入动画处理分块文本。',
320
366
  default: 'false',
321
367
  },
322
368
  {
@@ -348,14 +394,14 @@ export const docsZh = {
348
394
  name: 'contentAlign',
349
395
  type: "'start' | 'center'",
350
396
  description:
351
- "当 contentWidth 小于可用空间时,正文内容在容器内的对齐方式。",
397
+ '当 contentWidth 小于可用空间时,正文内容在容器内的对齐方式。',
352
398
  default: "'start'",
353
399
  },
354
400
  {
355
401
  name: 'inlinePlugins',
356
402
  type: 'MarkdownInlinePlugin[]',
357
403
  description:
358
- '将已解析文本节点中的正则匹配转换为自定义内联 React 元素。适用于 issue 引用、diff 引用、用户提及等简写模式。内联代码和围栏代码块不受影响。',
404
+ '将已解析文本节点中的正则匹配转换为自定义内联 React 元素。适用于带前缀的标识符、用户提及等简写模式。内联代码、围栏代码块和数学表达式不受影响。',
359
405
  },
360
406
  {
361
407
  name: 'autolink',
@@ -363,6 +409,12 @@ export const docsZh = {
363
409
  description:
364
410
  "可选的裸 URL 和电子邮箱自动链接。设为 'gfm' 启用 GitHub Flavored Markdown 自动链接规则:裸 https?://、www.、<scheme:url>、<email> 以及 user@host 都会变成链接。末尾句末标点和不平衡的末尾右括号会被排除;代码块、现有链接和图片替代文本内部的匹配会被跳过。默认为关闭。",
365
411
  },
412
+ {
413
+ name: 'components',
414
+ type: 'MarkdownComponents',
415
+ description:
416
+ '用于覆盖 Markdown 渲染元素的自定义 React 组件(code、inlineCode、math、link、heading、paragraph、image、blockquote、hr、citation)。提供 math 会启用 `$…$` 行内数学和 `$$…$$` 块级数学解析,并接收 `{value, display}`;不提供时美元符号保持原样。',
417
+ },
366
418
  {
367
419
  name: 'xstyle',
368
420
  type: 'StyleXStyles',
@@ -372,12 +424,14 @@ export const docsZh = {
372
424
  {
373
425
  name: 'className',
374
426
  type: 'string',
375
- description: '根元素的 CSS 类名。建议使用 xstyle,className 适用于非 StyleX 系统集成。',
427
+ description:
428
+ '根元素的 CSS 类名。建议使用 xstyle,className 适用于非 StyleX 系统集成。',
376
429
  },
377
430
  {
378
431
  name: 'style',
379
432
  type: 'CSSProperties',
380
- description: '根元素的内联样式。建议使用 xstyle,内联样式会绕过 StyleX 优化。',
433
+ description:
434
+ '根元素的内联样式。建议使用 xstyle,内联样式会绕过 StyleX 优化。',
381
435
  },
382
436
  {
383
437
  name: 'data-testid',
@@ -443,11 +497,41 @@ export const docsZh = {
443
497
  description:
444
498
  'Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.',
445
499
  bestPractices: [
446
- { guidance: true, description: 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.' },
447
- { guidance: true, description: 'Use contentWidth to keep prose at a readable line length in wide layouts.' },
448
- { guidance: true, description: 'Use inlinePlugins for custom shorthand patterns like issue refs, diff refs, and mentions instead of preprocessing the markdown string.' },
449
- { guidance: true, description: 'Pair with Outline and useOutlineFromMarkdown for section navigation: headings render generated id attributes that match the outline item ids, so hash links scroll to their target.' },
450
- { guidance: false, description: 'Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.' },
500
+ {
501
+ guidance: true,
502
+ description:
503
+ 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.',
504
+ },
505
+ {
506
+ guidance: true,
507
+ description:
508
+ 'Use contentWidth to keep prose at a readable line length in wide layouts.',
509
+ },
510
+ {
511
+ guidance: true,
512
+ description:
513
+ 'Use inlinePlugins for prefixed identifiers, mentions, and other prose-only shorthand instead of preprocessing the markdown string.',
514
+ },
515
+ {
516
+ guidance: true,
517
+ description:
518
+ 'Provide components.math only for documents that use dollar-delimited math. The renderer owns typesetting and accessible output; Astryx passes the expression as text and never executes raw HTML.',
519
+ },
520
+ {
521
+ guidance: true,
522
+ description:
523
+ 'For direct parsing, use MathParseOptions and handle InlineNodeWithMath or BlockNodeWithMath. Incremental math parsing also uses createIncrementalState<true>() and IncrementalParseState<true>; default calls and ParseOptions annotations keep the legacy unions.',
524
+ },
525
+ {
526
+ guidance: true,
527
+ description:
528
+ 'Pair with Outline and useOutlineFromMarkdown for section navigation: headings render generated id attributes that match the outline item ids, so hash links scroll to their target.',
529
+ },
530
+ {
531
+ guidance: false,
532
+ description:
533
+ 'Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.',
534
+ },
451
535
  ],
452
536
  },
453
537
  };
@@ -460,25 +544,66 @@ export const docsDense = {
460
544
  description:
461
545
  'Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.',
462
546
  bestPractices: [
463
- { guidance: true, description: 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.' },
464
- { guidance: true, description: 'Use contentWidth to keep prose at a readable line length in wide layouts.' },
465
- { guidance: true, description: 'Use inlinePlugins for custom shorthand patterns (issue refs, diff refs, mentions) instead of preprocessing the markdown string.' },
466
- { guidance: true, description: 'Headings render id attributes matching useOutlineFromMarkdown ids; pair with Outline for hash navigation.' },
467
- { guidance: false, description: 'Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.' },
547
+ {
548
+ guidance: true,
549
+ description:
550
+ 'Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.',
551
+ },
552
+ {
553
+ guidance: true,
554
+ description:
555
+ 'Use contentWidth to keep prose at a readable line length in wide layouts.',
556
+ },
557
+ {
558
+ guidance: true,
559
+ description:
560
+ 'Use inlinePlugins for prefixed identifiers, mentions, and other prose-only shorthand instead of preprocessing the markdown string.',
561
+ },
562
+ {
563
+ guidance: true,
564
+ description:
565
+ 'Provide components.math only for documents that use dollar-delimited math; the renderer owns typesetting and accessible output.',
566
+ },
567
+ {
568
+ guidance: true,
569
+ description:
570
+ 'Direct math parser calls use MathParseOptions and the explicit WithMath node unions; incremental calls also use createIncrementalState<true>() and IncrementalParseState<true>. Default calls keep the legacy unions.',
571
+ },
572
+ {
573
+ guidance: true,
574
+ description:
575
+ 'Headings render id attributes matching useOutlineFromMarkdown ids; pair with Outline for hash navigation.',
576
+ },
577
+ {
578
+ guidance: false,
579
+ description:
580
+ 'Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.',
581
+ },
468
582
  ],
469
583
  },
470
584
  propDescriptions: {
471
585
  children: 'markdown string',
472
586
  density: "Block spacing. 'default'|'compact'. Default: 'default'.",
473
- headingLevelStart: 'Maps # to this heading level (1-6). Clamped to h6. Default: 1.',
474
- isStreaming: 'Incremental parse + fade-in for streamed chunks. Default: false.',
475
- onLinkClick: '(href, event) => void|false. Return false prevents navigation.',
476
- sources: 'Record<string, MarkdownSource>. Citation sources by ID. [id]/【id】 markers render as chips.',
477
- citationStyle: "'label'|'number'. label=chip w/ title+icon, number=compact badge. Default: 'label'.",
478
- contentWidth: 'number|string. Max width for prose (headings, paragraphs, lists). Tables/code unconstrained.',
479
- contentAlign: "'start'|'center'. Prose alignment when contentWidth < container. Default: 'start'.",
480
- inlinePlugins: 'MarkdownInlinePlugin[]. Regex matches in text nodes -> custom inline React elements. Skips inline/fenced code.',
481
- autolink: "'gfm'. Opt-in GFM autolinking: bare URLs (https?://, www.), <scheme:url>, <email>, user@host. Skips code, code blocks, existing links. Default: off.",
587
+ headingLevelStart:
588
+ 'Maps # to this heading level (1-6). Clamped to h6. Default: 1.',
589
+ isStreaming:
590
+ 'Incremental parse + fade-in for streamed chunks. Default: false.',
591
+ onLinkClick:
592
+ '(href, event) => void|false. Return false prevents navigation.',
593
+ sources:
594
+ 'Record<string, MarkdownSource>. Citation sources by ID. [id]/【id】 markers render as chips.',
595
+ citationStyle:
596
+ "'label'|'number'. label=chip w/ title+icon, number=compact badge. Default: 'label'.",
597
+ contentWidth:
598
+ 'number|string. Max width for prose (headings, paragraphs, lists). Tables/code unconstrained.',
599
+ contentAlign:
600
+ "'start'|'center'. Prose alignment when contentWidth < container. Default: 'start'.",
601
+ inlinePlugins:
602
+ 'MarkdownInlinePlugin[]. Regex matches in text nodes -> custom inline React elements. Skips inline/fenced code and math.',
603
+ autolink:
604
+ "'gfm'. Opt-in GFM autolinking: bare URLs (https?://, www.), <scheme:url>, <email>, user@host. Skips code, code blocks, existing links. Default: off.",
605
+ components:
606
+ 'MarkdownComponents. Custom renderers; math({value, display}) opts into $…$/$$…$$ parsing. Renderer owns output and accessibility.',
482
607
  xstyle: 'stylex.create() for layout (margins, sizing).',
483
608
  className: 'CSS class. Prefer xstyle.',
484
609
  style: 'Inline styles. Prefer xstyle.',