@astryxdesign/core 0.6.1 → 0.6.2-canary.176dfa6

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 (104) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
  9. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  10. package/dist/DateRangeInput/DateRangeInput.js +16 -9
  11. package/dist/Dialog/DialogHeader.d.ts +1 -1
  12. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  13. package/dist/Dialog/DialogHeader.js +10 -7
  14. package/dist/FileInput/FileInput.d.ts.map +1 -1
  15. package/dist/FileInput/FileInput.js +9 -3
  16. package/dist/Markdown/Markdown.d.ts +10 -2
  17. package/dist/Markdown/Markdown.d.ts.map +1 -1
  18. package/dist/Markdown/Markdown.js +58 -14
  19. package/dist/Markdown/index.d.ts +1 -1
  20. package/dist/Markdown/index.d.ts.map +1 -1
  21. package/dist/Markdown/parser.d.ts +126 -12
  22. package/dist/Markdown/parser.d.ts.map +1 -1
  23. package/dist/Markdown/parser.js +369 -34
  24. package/dist/Markdown/utils.d.ts +1 -1
  25. package/dist/Markdown/utils.d.ts.map +1 -1
  26. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  27. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  28. package/dist/Slider/Slider.d.ts.map +1 -1
  29. package/dist/Slider/Slider.js +5 -2
  30. package/dist/Spinner/Spinner.d.ts +1 -1
  31. package/dist/Spinner/Spinner.d.ts.map +1 -1
  32. package/dist/Spinner/Spinner.js +23 -15
  33. package/dist/ToggleButton/ToggleButton.d.ts +2 -1
  34. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  35. package/dist/ToggleButton/ToggleButton.js +7 -1
  36. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  37. package/dist/Typeahead/BaseTypeahead.js +15 -6
  38. package/dist/astryx.css +5 -2
  39. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  40. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  41. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  42. package/dist/hooks/useScrollableArea.d.ts +6 -2
  43. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  44. package/dist/hooks/useScrollableArea.js +17 -5
  45. package/locales/en.json +16 -0
  46. package/locales/pseudo.json +12 -0
  47. package/package.json +7 -5
  48. package/scripts/agent-doc-state.mjs +1 -1
  49. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  50. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  51. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  52. package/src/BottomSheet/BottomSheet.tsx +3 -1
  53. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  54. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  55. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  56. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  57. package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
  58. package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
  59. package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
  60. package/src/DateRangeInput/DateRangeInput.tsx +29 -20
  61. package/src/Dialog/Dialog.doc.mjs +3 -0
  62. package/src/Dialog/Dialog.spec.md +1 -1
  63. package/src/Dialog/DialogHeader.doc.mjs +38 -0
  64. package/src/Dialog/DialogHeader.test.tsx +49 -0
  65. package/src/Dialog/DialogHeader.tsx +23 -4
  66. package/src/Dialog/modules/DialogHeader.spec.md +152 -0
  67. package/src/FileInput/FileInput.doc.mjs +2 -0
  68. package/src/FileInput/FileInput.spec.md +199 -0
  69. package/src/FileInput/FileInput.test.tsx +14 -0
  70. package/src/FileInput/FileInput.tsx +13 -3
  71. package/src/Markdown/Markdown.doc.mjs +167 -42
  72. package/src/Markdown/Markdown.public.test.ts +157 -0
  73. package/src/Markdown/Markdown.spec.md +149 -70
  74. package/src/Markdown/Markdown.test.tsx +107 -3
  75. package/src/Markdown/Markdown.tsx +116 -35
  76. package/src/Markdown/incremental.test.ts +175 -7
  77. package/src/Markdown/index.ts +6 -0
  78. package/src/Markdown/parser.perf.test.ts +3 -1
  79. package/src/Markdown/parser.test.ts +122 -0
  80. package/src/Markdown/parser.ts +609 -81
  81. package/src/Markdown/utils.ts +6 -0
  82. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  83. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  84. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  85. package/src/Slider/Slider.doc.mjs +16 -0
  86. package/src/Slider/Slider.spec.md +61 -47
  87. package/src/Slider/Slider.test.tsx +18 -0
  88. package/src/Slider/Slider.tsx +12 -6
  89. package/src/Spinner/Spinner.doc.mjs +6 -3
  90. package/src/Spinner/Spinner.test.tsx +37 -0
  91. package/src/Spinner/Spinner.tsx +31 -14
  92. package/src/ToggleButton/ToggleButton.test.tsx +133 -0
  93. package/src/ToggleButton/ToggleButton.tsx +9 -2
  94. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  95. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  96. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  97. package/src/Typeahead/BaseTypeahead.tsx +15 -6
  98. package/src/Typeahead/Typeahead.test.tsx +53 -0
  99. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  100. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  101. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  102. package/src/hooks/useScrollableArea.test.tsx +59 -1
  103. package/src/hooks/useScrollableArea.ts +34 -10
  104. package/src/theme/derivedVarRegistry.test.ts +6 -4
@@ -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.',
@@ -0,0 +1,157 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Markdown.public.test.ts
5
+ * @input Imports Markdown parser functions and node types from the public barrel
6
+ * @output Compile-time compatibility coverage for legacy and math-enabled results
7
+ * @position Public API test guarding @astryxdesign/core/Markdown
8
+ */
9
+
10
+ import {describe, expectTypeOf, it} from 'vitest';
11
+ import {
12
+ createIncrementalState,
13
+ parseInline,
14
+ parseMarkdown,
15
+ parseMarkdownIncremental,
16
+ } from './index';
17
+ import type {
18
+ BlockNode,
19
+ BlockNodeWithMath,
20
+ InlineNode,
21
+ InlineNodeWithMath,
22
+ ParseOptions,
23
+ } from './index';
24
+
25
+ function assertNever(value: never): never {
26
+ throw new Error(`Unexpected node: ${JSON.stringify(value)}`);
27
+ }
28
+
29
+ // Existing exhaustive consumers must not gain a new case when they do not opt
30
+ // into math parsing. These functions fail to compile if the legacy unions widen.
31
+ function legacyInlineText(node: InlineNode): string {
32
+ switch (node.type) {
33
+ case 'text':
34
+ case 'code':
35
+ return node.content;
36
+ case 'bold':
37
+ case 'italic':
38
+ case 'strikethrough':
39
+ case 'link':
40
+ return node.children.map(legacyInlineText).join('');
41
+ case 'image':
42
+ return node.alt;
43
+ case 'citation':
44
+ return node.sourceId;
45
+ case 'break':
46
+ return '\n';
47
+ default:
48
+ return assertNever(node);
49
+ }
50
+ }
51
+
52
+ function legacyBlockText(node: BlockNode): string {
53
+ switch (node.type) {
54
+ case 'heading':
55
+ case 'paragraph':
56
+ return node.children.map(legacyInlineText).join('');
57
+ case 'codeblock':
58
+ return node.content;
59
+ case 'blockquote':
60
+ return node.children.map(legacyBlockText).join('\n');
61
+ case 'list':
62
+ return node.items
63
+ .flatMap(item => item.children.map(legacyBlockText))
64
+ .join('\n');
65
+ case 'table':
66
+ return [...node.headers, ...node.rows.flat()]
67
+ .flatMap(cell => cell.children.map(legacyInlineText))
68
+ .join(' ');
69
+ case 'image':
70
+ return node.alt;
71
+ case 'hr':
72
+ return '';
73
+ default:
74
+ return assertNever(node);
75
+ }
76
+ }
77
+
78
+ describe('Markdown public parser types', () => {
79
+ it('keeps default and legacy parser calls on the legacy unions', () => {
80
+ expectTypeOf(parseInline('plain')).toEqualTypeOf<InlineNode[]>();
81
+ expectTypeOf(parseInline('plain', new Set<string>())).toEqualTypeOf<
82
+ InlineNode[]
83
+ >();
84
+ expectTypeOf(parseMarkdown('plain')).toEqualTypeOf<BlockNode[]>();
85
+ expectTypeOf(parseMarkdown('plain', {math: false})).toEqualTypeOf<
86
+ BlockNode[]
87
+ >();
88
+ const annotatedOptions: ParseOptions = {autolink: 'gfm'};
89
+ expectTypeOf(parseInline('plain', annotatedOptions)).toEqualTypeOf<
90
+ InlineNode[]
91
+ >();
92
+ expectTypeOf(parseMarkdown('plain', annotatedOptions)).toEqualTypeOf<
93
+ BlockNode[]
94
+ >();
95
+ expectTypeOf(
96
+ parseMarkdownIncremental(
97
+ 'plain',
98
+ createIncrementalState(),
99
+ annotatedOptions,
100
+ ),
101
+ ).toEqualTypeOf<BlockNode[]>();
102
+ expectTypeOf(
103
+ parseMarkdownIncremental('plain', createIncrementalState()),
104
+ ).toEqualTypeOf<BlockNode[]>();
105
+ expectTypeOf(createIncrementalState().settledBlocks).toEqualTypeOf<
106
+ BlockNode[]
107
+ >();
108
+ expectTypeOf(legacyInlineText).returns.toBeString();
109
+ expectTypeOf(legacyBlockText).returns.toBeString();
110
+ });
111
+
112
+ it('rejects ambiguous math options and structurally forged state', () => {
113
+ function compileOnlyGuards() {
114
+ const dynamicOptions: {math: boolean} = {math: true};
115
+ // @ts-expect-error callers must narrow to ParseOptions or MathParseOptions
116
+ parseMarkdown('$$x$$', dynamicOptions);
117
+
118
+ const structuralState = {
119
+ prevInput: '',
120
+ settledText: '',
121
+ settledBlocks: [] as BlockNode[],
122
+ settledUpTo: 0,
123
+ };
124
+ // @ts-expect-error math caches must come from createIncrementalState<true>()
125
+ parseMarkdownIncremental('$$x$$', structuralState, {math: true});
126
+ }
127
+
128
+ expectTypeOf(compileOnlyGuards).toBeFunction();
129
+ });
130
+
131
+ it('types direct and incremental math opt-ins with explicit math unions', () => {
132
+ const inline = parseInline('$x$', {math: true});
133
+ const direct = parseMarkdown('$$x$$', {math: true});
134
+ const incremental = parseMarkdownIncremental(
135
+ '$$x$$',
136
+ createIncrementalState<true>(),
137
+ {math: true},
138
+ );
139
+
140
+ // A state carries the same node contract as the parser result it caches.
141
+ // @ts-expect-error math parsing requires a math-enabled incremental state
142
+ parseMarkdownIncremental('$$x$$', createIncrementalState(), {math: true});
143
+
144
+ expectTypeOf(inline).toEqualTypeOf<InlineNodeWithMath[]>();
145
+ expectTypeOf(direct).toEqualTypeOf<BlockNodeWithMath[]>();
146
+ expectTypeOf(incremental).toEqualTypeOf<BlockNodeWithMath[]>();
147
+ expectTypeOf(createIncrementalState<true>().settledBlocks).toEqualTypeOf<
148
+ BlockNodeWithMath[]
149
+ >();
150
+ expectTypeOf<
151
+ Extract<InlineNodeWithMath, {type: 'math'}>['value']
152
+ >().toBeString();
153
+ expectTypeOf<
154
+ Extract<BlockNodeWithMath, {type: 'math'}>['value']
155
+ >().toBeString();
156
+ });
157
+ });