@astryxdesign/core 0.6.2-canary.bfee8a4 → 0.6.2-canary.d4cc232

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 (103) hide show
  1. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  2. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  3. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  4. package/dist/Collapsible/Collapsible.js +6 -1
  5. package/dist/Kbd/Kbd.d.ts +5 -3
  6. package/dist/Kbd/Kbd.d.ts.map +1 -1
  7. package/dist/Kbd/Kbd.js +36 -42
  8. package/dist/Link/Link.d.ts.map +1 -1
  9. package/dist/Link/Link.js +6 -2
  10. package/dist/Markdown/Markdown.d.ts.map +1 -1
  11. package/dist/Markdown/Markdown.js +63 -59
  12. package/dist/Markdown/ast.d.ts +124 -0
  13. package/dist/Markdown/ast.d.ts.map +1 -0
  14. package/dist/Markdown/ast.js +38 -0
  15. package/dist/Markdown/parser.d.ts +14 -8
  16. package/dist/Markdown/parser.d.ts.map +1 -1
  17. package/dist/Markdown/parser.js +352 -97
  18. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  19. package/dist/Outline/parseOutlineFromMarkdown.js +7 -7
  20. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  21. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  22. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  23. package/dist/RadioList/RadioListItem.js +13 -1
  24. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  25. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  26. package/dist/SideNav/SideNav.d.ts +2 -1
  27. package/dist/SideNav/SideNav.d.ts.map +1 -1
  28. package/dist/SideNav/SideNav.js +9 -3
  29. package/dist/Slider/Slider.d.ts.map +1 -1
  30. package/dist/Slider/Slider.js +14 -8
  31. package/dist/Switch/Switch.d.ts.map +1 -1
  32. package/dist/Switch/Switch.js +11 -0
  33. package/dist/TabList/Tab.d.ts +1 -1
  34. package/dist/TabList/Tab.d.ts.map +1 -1
  35. package/dist/TabList/Tab.js +20 -7
  36. package/dist/ToggleButton/ToggleButton.d.ts +2 -1
  37. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  38. package/dist/ToggleButton/ToggleButton.js +7 -1
  39. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  40. package/dist/Typeahead/BaseTypeahead.js +15 -6
  41. package/dist/astryx.css +8 -0
  42. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  43. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  44. package/dist/utils/interactionOverlay.stylex.js +9 -0
  45. package/package.json +3 -3
  46. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  47. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  48. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  49. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  50. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  51. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  52. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  53. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  54. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  55. package/src/Collapsible/Collapsible.test.tsx +21 -0
  56. package/src/Collapsible/Collapsible.tsx +5 -0
  57. package/src/Kbd/Kbd.doc.mjs +3 -3
  58. package/src/Kbd/Kbd.test.tsx +57 -1
  59. package/src/Kbd/Kbd.tsx +57 -37
  60. package/src/Link/Link.doc.mjs +11 -0
  61. package/src/Link/Link.test.tsx +24 -0
  62. package/src/Link/Link.tsx +5 -0
  63. package/src/Markdown/Markdown.spec.md +159 -43
  64. package/src/Markdown/Markdown.test.tsx +11 -0
  65. package/src/Markdown/Markdown.tsx +106 -96
  66. package/src/Markdown/ast.test.ts +169 -0
  67. package/src/Markdown/ast.ts +231 -0
  68. package/src/Markdown/incremental.test.ts +12 -0
  69. package/src/Markdown/parser.ts +427 -118
  70. package/src/Outline/Outline.spec.md +1 -1
  71. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  72. package/src/Outline/parseOutlineFromMarkdown.ts +8 -13
  73. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  74. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  75. package/src/RadioList/RadioList.doc.mjs +11 -0
  76. package/src/RadioList/RadioList.test.tsx +32 -0
  77. package/src/RadioList/RadioListItem.tsx +25 -1
  78. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  79. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  80. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  81. package/src/SideNav/SideNav.doc.mjs +1 -1
  82. package/src/SideNav/SideNav.test.tsx +10 -0
  83. package/src/SideNav/SideNav.tsx +14 -2
  84. package/src/Slider/Slider.doc.mjs +11 -0
  85. package/src/Slider/Slider.test.tsx +128 -0
  86. package/src/Slider/Slider.tsx +25 -7
  87. package/src/Switch/Switch.doc.mjs +11 -0
  88. package/src/Switch/Switch.test.tsx +16 -0
  89. package/src/Switch/Switch.tsx +28 -0
  90. package/src/TabList/Tab.tsx +35 -7
  91. package/src/TabList/TabList.doc.mjs +11 -0
  92. package/src/TabList/TabList.test.tsx +66 -0
  93. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  94. package/src/ToggleButton/ToggleButton.test.tsx +133 -0
  95. package/src/ToggleButton/ToggleButton.tsx +9 -2
  96. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  97. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  98. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  99. package/src/Typeahead/BaseTypeahead.tsx +15 -6
  100. package/src/Typeahead/Typeahead.test.tsx +53 -0
  101. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  102. package/src/__tests__/pressState.ts +93 -0
  103. package/src/utils/interactionOverlay.stylex.ts +20 -0
@@ -2,12 +2,10 @@
2
2
 
3
3
  /**
4
4
  * @file parser.ts
5
- * @input Markdown string
6
- * @output Array of MarkdownNode AST nodes, including opt-in math; heading slug
7
- * helpers
8
- * (inlineText, slugify, uniqueSlug) shared by Markdown rendering and
9
- * Outline's parseOutlineFromMarkdown
10
- * @position Core parser; consumed by Markdown.tsx and Outline
5
+ * @input Markdown string and released parse options
6
+ * @output Canonical MDAST-aligned nodes for internal consumers plus unchanged
7
+ * released parser-node projections; shared heading slug helpers
8
+ * @position Core parser and compatibility boundary; consumed by Markdown and Outline
11
9
  */
12
10
 
13
11
  // ---------------------------------------------------------------------------
@@ -36,6 +34,207 @@
36
34
  * optional fields without breaking anyone.
37
35
  */
38
36
 
37
+ function projectRange(position) {
38
+ const start = position?.start.offset;
39
+ const end = position?.end.offset;
40
+ return start == null || end == null ? {} : {
41
+ range: {
42
+ start,
43
+ end
44
+ }
45
+ };
46
+ }
47
+ function projectInlineNode(node, cache) {
48
+ const cached = cache.get(node);
49
+ if (cached != null) {
50
+ return cached;
51
+ }
52
+ let projected;
53
+ switch (node.type) {
54
+ case 'text':
55
+ projected = {
56
+ type: 'text',
57
+ content: node.value
58
+ };
59
+ break;
60
+ case 'strong':
61
+ projected = {
62
+ type: 'bold',
63
+ children: node.children.map(child => projectInlineNode(child, cache))
64
+ };
65
+ break;
66
+ case 'emphasis':
67
+ projected = {
68
+ type: 'italic',
69
+ children: node.children.map(child => projectInlineNode(child, cache))
70
+ };
71
+ break;
72
+ case 'delete':
73
+ projected = {
74
+ type: 'strikethrough',
75
+ children: node.children.map(child => projectInlineNode(child, cache))
76
+ };
77
+ break;
78
+ case 'inlineCode':
79
+ projected = {
80
+ type: 'code',
81
+ content: node.value
82
+ };
83
+ break;
84
+ case 'inlineMath':
85
+ projected = {
86
+ type: 'math',
87
+ value: node.value
88
+ };
89
+ break;
90
+ case 'link':
91
+ projected = {
92
+ type: 'link',
93
+ href: node.url,
94
+ children: node.children.map(child => projectInlineNode(child, cache))
95
+ };
96
+ break;
97
+ case 'image':
98
+ projected = {
99
+ type: 'image',
100
+ src: node.url,
101
+ alt: node.alt
102
+ };
103
+ break;
104
+ case 'citation':
105
+ projected = {
106
+ type: 'citation',
107
+ sourceId: node.sourceId
108
+ };
109
+ break;
110
+ case 'break':
111
+ projected = {
112
+ type: 'break'
113
+ };
114
+ break;
115
+ }
116
+ cache.set(node, projected);
117
+ return projected;
118
+ }
119
+ function projectTableCell(node, cache) {
120
+ const cached = cache.get(node);
121
+ if (cached != null) {
122
+ return cached;
123
+ }
124
+ const projected = {
125
+ children: node.children.map(child => projectInlineNode(child, cache))
126
+ };
127
+ cache.set(node, projected);
128
+ return projected;
129
+ }
130
+ function projectListItem(node, cache) {
131
+ const cached = cache.get(node);
132
+ if (cached != null) {
133
+ return cached;
134
+ }
135
+ const projected = {
136
+ checked: node.checked,
137
+ children: node.children.map(child => projectBlockNode(child, cache))
138
+ };
139
+ cache.set(node, projected);
140
+ return projected;
141
+ }
142
+ function projectBlockNode(node, cache) {
143
+ const cached = cache.get(node);
144
+ if (cached != null) {
145
+ return cached;
146
+ }
147
+ const metadata = projectRange(node.position);
148
+ let projected;
149
+ switch (node.type) {
150
+ case 'heading':
151
+ projected = {
152
+ type: 'heading',
153
+ level: node.depth,
154
+ children: node.children.map(child => projectInlineNode(child, cache)),
155
+ ...metadata
156
+ };
157
+ break;
158
+ case 'paragraph':
159
+ projected = {
160
+ type: 'paragraph',
161
+ children: node.children.map(child => projectInlineNode(child, cache)),
162
+ ...metadata
163
+ };
164
+ break;
165
+ case 'code':
166
+ projected = {
167
+ type: 'codeblock',
168
+ language: node.lang ?? 'plaintext',
169
+ content: node.value,
170
+ ...metadata
171
+ };
172
+ break;
173
+ case 'math':
174
+ projected = {
175
+ type: 'math',
176
+ value: node.value,
177
+ ...metadata
178
+ };
179
+ break;
180
+ case 'blockquote':
181
+ projected = {
182
+ type: 'blockquote',
183
+ children: node.children.map(child => projectBlockNode(child, cache)),
184
+ ...metadata
185
+ };
186
+ break;
187
+ case 'list':
188
+ projected = {
189
+ type: 'list',
190
+ ordered: node.ordered,
191
+ start: node.start,
192
+ delimiter: node.delimiter,
193
+ loose: node.spread,
194
+ items: node.children.map(item => projectListItem(item, cache)),
195
+ ...metadata
196
+ };
197
+ break;
198
+ case 'table':
199
+ {
200
+ const [header = {
201
+ type: 'tableRow',
202
+ children: []
203
+ }, ...rows] = node.children;
204
+ projected = {
205
+ type: 'table',
206
+ headers: header.children.map(cell => projectTableCell(cell, cache)),
207
+ alignments: [...node.align],
208
+ rows: rows.map(row => row.children.map(cell => projectTableCell(cell, cache))),
209
+ ...metadata
210
+ };
211
+ break;
212
+ }
213
+ case 'thematicBreak':
214
+ projected = {
215
+ type: 'hr',
216
+ ...metadata
217
+ };
218
+ break;
219
+ case 'image':
220
+ projected = {
221
+ type: 'image',
222
+ alt: node.alt,
223
+ src: node.url,
224
+ ...metadata
225
+ };
226
+ break;
227
+ }
228
+ cache.set(node, projected);
229
+ return projected;
230
+ }
231
+ function projectInlineNodes(nodes, cache = new WeakMap()) {
232
+ return nodes.map(node => projectInlineNode(node, cache));
233
+ }
234
+ function projectMarkdownRoot(root, cache = new WeakMap()) {
235
+ return root.children.map(node => projectBlockNode(node, cache));
236
+ }
237
+
39
238
  // ---------------------------------------------------------------------------
40
239
  // Parse options
41
240
  // ---------------------------------------------------------------------------
@@ -359,7 +558,7 @@ function matchReferenceLink(text, start, linkDefs, opts) {
359
558
  return {
360
559
  node: {
361
560
  type: 'link',
362
- href,
561
+ url: href,
363
562
  children: parseInlineImpl(linkText, opts)
364
563
  },
365
564
  end: labelClose + 1
@@ -380,7 +579,7 @@ function matchReferenceLink(text, start, linkDefs, opts) {
380
579
  return {
381
580
  node: {
382
581
  type: 'link',
383
- href,
582
+ url: href,
384
583
  children: parseInlineImpl(linkText, opts)
385
584
  },
386
585
  end: textClose + 1
@@ -404,7 +603,7 @@ function matchReferenceImage(text, start, linkDefs) {
404
603
  return {
405
604
  node: {
406
605
  type: 'image',
407
- src,
606
+ url: src,
408
607
  alt
409
608
  },
410
609
  end: labelClose + 1
@@ -423,7 +622,7 @@ function matchReferenceImage(text, start, linkDefs) {
423
622
  return {
424
623
  node: {
425
624
  type: 'image',
426
- src,
625
+ url: src,
427
626
  alt
428
627
  },
429
628
  end: altClose + 1
@@ -571,6 +770,11 @@ function matchBracketCitation(text, i, opts) {
571
770
  };
572
771
  }
573
772
  export function parseInline(text, arg) {
773
+ return projectInlineNodes(parseInlineAst(text, arg));
774
+ }
775
+
776
+ /** @internal Canonical inline parse used by Markdown rendering. */
777
+ export function parseInlineAst(text, arg) {
574
778
  return parseInlineEntry(text, resolveOptions(arg));
575
779
  }
576
780
 
@@ -595,7 +799,7 @@ function parseInlineImpl(text, opts) {
595
799
  if (text[i] === '\\' && i + 1 < text.length) {
596
800
  nodes.push({
597
801
  type: 'text',
598
- content: text[i + 1]
802
+ value: text[i + 1]
599
803
  });
600
804
  i += 2;
601
805
  continue;
@@ -608,8 +812,8 @@ function parseInlineImpl(text, opts) {
608
812
  const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
609
813
  if (closeIndex !== -1) {
610
814
  nodes.push({
611
- type: 'code',
612
- content: text.slice(openIndex, closeIndex)
815
+ type: 'inlineCode',
816
+ value: text.slice(openIndex, closeIndex)
613
817
  });
614
818
  i = closeIndex + tickCount;
615
819
  continue;
@@ -621,7 +825,7 @@ function parseInlineImpl(text, opts) {
621
825
  const closeIndex = findInlineMathEnd(text, i);
622
826
  if (closeIndex !== -1) {
623
827
  nodes.push({
624
- type: 'math',
828
+ type: 'inlineMath',
625
829
  value: text.slice(i + 1, closeIndex)
626
830
  });
627
831
  i = closeIndex + 1;
@@ -653,12 +857,12 @@ function parseInlineImpl(text, opts) {
653
857
  // Dangerous scheme — emit as plain text.
654
858
  nodes.push({
655
859
  type: 'text',
656
- content: text.slice(i, srcClose + 1)
860
+ value: text.slice(i, srcClose + 1)
657
861
  });
658
862
  } else {
659
863
  nodes.push({
660
864
  type: 'image',
661
- src,
865
+ url: src,
662
866
  alt: text.slice(i + 2, altClose)
663
867
  });
664
868
  }
@@ -702,12 +906,12 @@ function parseInlineImpl(text, opts) {
702
906
  // Dangerous scheme — emit as plain text instead of a link.
703
907
  nodes.push({
704
908
  type: 'text',
705
- content: text.slice(i, urlClose + 1)
909
+ value: text.slice(i, urlClose + 1)
706
910
  });
707
911
  } else {
708
912
  nodes.push({
709
913
  type: 'link',
710
- href,
914
+ url: href,
711
915
  children: parseInlineImpl(text.slice(i + 1, textClose), opts)
712
916
  });
713
917
  }
@@ -737,9 +941,9 @@ function parseInlineImpl(text, opts) {
737
941
  const closeIndex = text.indexOf(marker, i + 3);
738
942
  if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 3]))) {
739
943
  nodes.push({
740
- type: 'bold',
944
+ type: 'strong',
741
945
  children: [{
742
- type: 'italic',
946
+ type: 'emphasis',
743
947
  children: parseInlineImpl(text.slice(i + 3, closeIndex), opts)
744
948
  }]
745
949
  });
@@ -759,7 +963,7 @@ function parseInlineImpl(text, opts) {
759
963
  const closeIndex = text.indexOf(marker, i + 2);
760
964
  if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 2]))) {
761
965
  nodes.push({
762
- type: 'bold',
966
+ type: 'strong',
763
967
  children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
764
968
  });
765
969
  i = closeIndex + 2;
@@ -773,7 +977,7 @@ function parseInlineImpl(text, opts) {
773
977
  const closeIndex = text.indexOf('~~', i + 2);
774
978
  if (closeIndex !== -1) {
775
979
  nodes.push({
776
- type: 'strikethrough',
980
+ type: 'delete',
777
981
  children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
778
982
  });
779
983
  i = closeIndex + 2;
@@ -790,7 +994,7 @@ function parseInlineImpl(text, opts) {
790
994
  const closeIndex = text.indexOf(text[i], i + 1);
791
995
  if (closeIndex !== -1 && closeIndex > i + 1 && (!isUnderscore || !isWordChar(text[closeIndex + 1]))) {
792
996
  nodes.push({
793
- type: 'italic',
997
+ type: 'emphasis',
794
998
  children: parseInlineImpl(text.slice(i + 1, closeIndex), opts)
795
999
  });
796
1000
  i = closeIndex + 1;
@@ -813,11 +1017,14 @@ function parseInlineImpl(text, opts) {
813
1017
  if (trimmed.length > 0) {
814
1018
  const last = nodes[nodes.length - 1];
815
1019
  if (last?.type === 'text') {
816
- last.content += trimmed;
1020
+ nodes[nodes.length - 1] = {
1021
+ ...last,
1022
+ value: last.value + trimmed
1023
+ };
817
1024
  } else {
818
1025
  nodes.push({
819
1026
  type: 'text',
820
- content: trimmed
1027
+ value: trimmed
821
1028
  });
822
1029
  }
823
1030
  }
@@ -830,11 +1037,14 @@ function parseInlineImpl(text, opts) {
830
1037
  }
831
1038
  const last = nodes[nodes.length - 1];
832
1039
  if (last?.type === 'text') {
833
- last.content += content;
1040
+ nodes[nodes.length - 1] = {
1041
+ ...last,
1042
+ value: last.value + content
1043
+ };
834
1044
  } else {
835
1045
  nodes.push({
836
1046
  type: 'text',
837
- content
1047
+ value: content
838
1048
  });
839
1049
  }
840
1050
  i = end;
@@ -1063,7 +1273,7 @@ function splitTextOnAutolinks(content) {
1063
1273
  if (matches.length === 0) {
1064
1274
  return [{
1065
1275
  type: 'text',
1066
- content
1276
+ value: content
1067
1277
  }];
1068
1278
  }
1069
1279
  const out = [];
@@ -1072,15 +1282,15 @@ function splitTextOnAutolinks(content) {
1072
1282
  if (m.start > cursor) {
1073
1283
  out.push({
1074
1284
  type: 'text',
1075
- content: content.slice(cursor, m.start)
1285
+ value: content.slice(cursor, m.start)
1076
1286
  });
1077
1287
  }
1078
1288
  out.push({
1079
1289
  type: 'link',
1080
- href: m.href,
1290
+ url: m.href,
1081
1291
  children: [{
1082
1292
  type: 'text',
1083
- content: m.display
1293
+ value: m.display
1084
1294
  }]
1085
1295
  });
1086
1296
  cursor = m.end;
@@ -1088,7 +1298,7 @@ function splitTextOnAutolinks(content) {
1088
1298
  if (cursor < content.length) {
1089
1299
  out.push({
1090
1300
  type: 'text',
1091
- content: content.slice(cursor)
1301
+ value: content.slice(cursor)
1092
1302
  });
1093
1303
  }
1094
1304
  return out;
@@ -1106,16 +1316,19 @@ function transformAutolinks(nodes) {
1106
1316
  const out = [];
1107
1317
  for (const node of nodes) {
1108
1318
  if (node.type === 'text') {
1109
- const split = splitTextOnAutolinks(node.content);
1319
+ const split = splitTextOnAutolinks(node.value);
1110
1320
  for (const seg of split) {
1111
1321
  const last = out[out.length - 1];
1112
1322
  if (seg.type === 'text' && last?.type === 'text') {
1113
- last.content += seg.content;
1323
+ out[out.length - 1] = {
1324
+ ...last,
1325
+ value: last.value + seg.value
1326
+ };
1114
1327
  } else {
1115
1328
  out.push(seg);
1116
1329
  }
1117
1330
  }
1118
- } else if (node.type === 'bold' || node.type === 'italic' || node.type === 'strikethrough') {
1331
+ } else if (node.type === 'strong' || node.type === 'emphasis' || node.type === 'delete') {
1119
1332
  out.push({
1120
1333
  ...node,
1121
1334
  children: transformAutolinks(node.children)
@@ -1271,6 +1484,7 @@ function splitTableRow(line) {
1271
1484
  }
1272
1485
  function parseTable(lines, lineIndex, opts) {
1273
1486
  const headers = splitTableRow(lines[lineIndex]).map(cell => ({
1487
+ type: 'tableCell',
1274
1488
  children: parseInlineEntry(cell, opts)
1275
1489
  }));
1276
1490
  const alignments = splitTableRow(lines[lineIndex + 1]).map(cell => {
@@ -1282,17 +1496,23 @@ function parseTable(lines, lineIndex, opts) {
1282
1496
  const rows = [];
1283
1497
  let rowIndex = lineIndex + 2;
1284
1498
  while (rowIndex < lines.length && lines[rowIndex].includes('|') && lines[rowIndex].trim() !== '') {
1285
- rows.push(splitTableRow(lines[rowIndex]).map(cell => ({
1286
- children: parseInlineEntry(cell, opts)
1287
- })));
1499
+ rows.push({
1500
+ type: 'tableRow',
1501
+ children: splitTableRow(lines[rowIndex]).map(cell => ({
1502
+ type: 'tableCell',
1503
+ children: parseInlineEntry(cell, opts)
1504
+ }))
1505
+ });
1288
1506
  rowIndex++;
1289
1507
  }
1290
1508
  return {
1291
1509
  node: {
1292
1510
  type: 'table',
1293
- headers,
1294
- alignments,
1295
- rows
1511
+ align: alignments,
1512
+ children: [{
1513
+ type: 'tableRow',
1514
+ children: headers
1515
+ }, ...rows]
1296
1516
  },
1297
1517
  nextIndex: rowIndex
1298
1518
  };
@@ -1335,6 +1555,7 @@ function parseList(lines, startIndex, ordered, opts) {
1335
1555
  itemText += '\n' + deindented.join('\n');
1336
1556
  }
1337
1557
  items.push({
1558
+ type: 'listItem',
1338
1559
  checked,
1339
1560
  children: parseMarkdownImpl(itemText, nested(opts))
1340
1561
  });
@@ -1357,8 +1578,8 @@ function parseList(lines, startIndex, ordered, opts) {
1357
1578
  ordered,
1358
1579
  start,
1359
1580
  delimiter: ordered ? delim : undefined,
1360
- loose: loose || undefined,
1361
- items
1581
+ spread: loose || undefined,
1582
+ children: items
1362
1583
  },
1363
1584
  nextIndex: index
1364
1585
  };
@@ -1369,7 +1590,15 @@ function parseList(lines, startIndex, ordered, opts) {
1369
1590
  // ---------------------------------------------------------------------------
1370
1591
 
1371
1592
  export function parseMarkdown(input, arg) {
1372
- return parseMarkdownImpl(input, resolveOptions(arg));
1593
+ return projectMarkdownRoot(parseMarkdownAst(input, arg));
1594
+ }
1595
+
1596
+ /** @internal Canonical block parse used by Markdown and Outline rendering. */
1597
+ export function parseMarkdownAst(input, arg) {
1598
+ return {
1599
+ type: 'root',
1600
+ children: parseMarkdownImpl(input, resolveOptions(arg))
1601
+ };
1373
1602
  }
1374
1603
  function parseMarkdownImpl(input, baseOpts) {
1375
1604
  // Collect this input's link reference definitions and strip their lines,
@@ -1425,7 +1654,7 @@ function parseMarkdownImpl(input, baseOpts) {
1425
1654
  const fenceMatch = line.match(/^(`{3,}|~{3,})(\w*)/);
1426
1655
  if (fenceMatch) {
1427
1656
  const fence = fenceMatch[1];
1428
- const language = fenceMatch[2] || 'plaintext';
1657
+ const language = fenceMatch[2] || null;
1429
1658
  const codeLines = [];
1430
1659
  index++;
1431
1660
  while (index < lines.length && !lines[index].startsWith(fence)) {
@@ -1437,9 +1666,9 @@ function parseMarkdownImpl(input, baseOpts) {
1437
1666
  // can end on them, so it states its own end rather than letting the
1438
1667
  // positional derivation trim them off.
1439
1668
  pushBlock({
1440
- type: 'codeblock',
1441
- language,
1442
- content: codeLines.join('\n')
1669
+ type: 'code',
1670
+ lang: language,
1671
+ value: codeLines.join('\n')
1443
1672
  }, Math.min(index, lines.length) - 1);
1444
1673
  continue;
1445
1674
  }
@@ -1462,7 +1691,7 @@ function parseMarkdownImpl(input, baseOpts) {
1462
1691
  if (headingMatch) {
1463
1692
  pushBlock({
1464
1693
  type: 'heading',
1465
- level: headingMatch[1].length,
1694
+ depth: headingMatch[1].length,
1466
1695
  children: parseInlineEntry(headingMatch[2], opts)
1467
1696
  });
1468
1697
  index++;
@@ -1472,7 +1701,7 @@ function parseMarkdownImpl(input, baseOpts) {
1472
1701
  // --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
1473
1702
  if (isHorizontalRule(line)) {
1474
1703
  pushBlock({
1475
- type: 'hr'
1704
+ type: 'thematicBreak'
1476
1705
  });
1477
1706
  index++;
1478
1707
  continue;
@@ -1486,7 +1715,7 @@ function parseMarkdownImpl(input, baseOpts) {
1486
1715
  pushBlock({
1487
1716
  type: 'image',
1488
1717
  alt: imageMatch[1],
1489
- src: imageMatch[2]
1718
+ url: imageMatch[2]
1490
1719
  });
1491
1720
  index++;
1492
1721
  continue;
@@ -1586,9 +1815,13 @@ function stampSourceRanges(blocks, blockStartLines, blockEndLines, lines, lineMa
1586
1815
  const end = lineStart(endLine) + lines[endLine].length;
1587
1816
  blocks[i] = {
1588
1817
  ...blocks[i],
1589
- range: {
1590
- start: lineStart(startLine),
1591
- end
1818
+ position: {
1819
+ start: {
1820
+ offset: lineStart(startLine)
1821
+ },
1822
+ end: {
1823
+ offset: end
1824
+ }
1592
1825
  }
1593
1826
  };
1594
1827
  }
@@ -1609,6 +1842,11 @@ function makeIncrementalCache(state) {
1609
1842
  tailLinkDefs: new Map(),
1610
1843
  linkDefs: defs,
1611
1844
  linkDefsKey: linkDefsSignature(defs),
1845
+ settledAstBlocks: [],
1846
+ settledRevision: 0,
1847
+ projectedRevision: -1,
1848
+ projectedSettledBlocks: [],
1849
+ projectionCache: new WeakMap(),
1612
1850
  work: {
1613
1851
  splitCharacters: 0,
1614
1852
  boundaryLines: 0,
@@ -2066,14 +2304,13 @@ function mergeSettledBlocks(prev, delta) {
2066
2304
  ordered: prevLast.ordered,
2067
2305
  start: prevLast.start,
2068
2306
  delimiter: prevLast.delimiter,
2069
- loose: true,
2070
- items: [...prevLast.items, ...deltaFirst.items],
2071
- // One list now, so one range: from where the first half started to
2072
- // where the second half ended.
2073
- ...(prevLast.range != null && deltaFirst.range != null ? {
2074
- range: {
2075
- start: prevLast.range.start,
2076
- end: deltaFirst.range.end
2307
+ spread: true,
2308
+ children: [...prevLast.children, ...deltaFirst.children],
2309
+ // One list now, so one position spans both halves.
2310
+ ...(prevLast.position != null && deltaFirst.position != null ? {
2311
+ position: {
2312
+ start: prevLast.position.start,
2313
+ end: deltaFirst.position.end
2077
2314
  }
2078
2315
  } : null)
2079
2316
  };
@@ -2085,11 +2322,11 @@ function mergeSettledBlocks(prev, delta) {
2085
2322
  /** Append a newly-settled slice without copying the already-settled prefix. */
2086
2323
  function appendSettledBlocks(prev, delta) {
2087
2324
  if (delta.length === 0) {
2088
- return;
2325
+ return false;
2089
2326
  }
2090
2327
  if (prev.length === 0) {
2091
2328
  prev.push(...delta);
2092
- return;
2329
+ return false;
2093
2330
  }
2094
2331
  const prevLast = prev[prev.length - 1];
2095
2332
  const deltaFirst = delta[0];
@@ -2099,19 +2336,20 @@ function appendSettledBlocks(prev, delta) {
2099
2336
  ordered: prevLast.ordered,
2100
2337
  start: prevLast.start,
2101
2338
  delimiter: prevLast.delimiter,
2102
- loose: true,
2103
- items: [...prevLast.items, ...deltaFirst.items],
2104
- ...(prevLast.range != null && deltaFirst.range != null ? {
2105
- range: {
2106
- start: prevLast.range.start,
2107
- end: deltaFirst.range.end
2339
+ spread: true,
2340
+ children: [...prevLast.children, ...deltaFirst.children],
2341
+ ...(prevLast.position != null && deltaFirst.position != null ? {
2342
+ position: {
2343
+ start: prevLast.position.start,
2344
+ end: deltaFirst.position.end
2108
2345
  }
2109
2346
  } : null)
2110
2347
  };
2111
2348
  prev.push(...delta.slice(1));
2112
- return;
2349
+ return true;
2113
2350
  }
2114
2351
  prev.push(...delta);
2352
+ return false;
2115
2353
  }
2116
2354
  function sameUnsettledDefinitions(previous, next, settled) {
2117
2355
  for (const [label, destination] of previous) {
@@ -2138,6 +2376,11 @@ function resetIncrementalCache(state, cache) {
2138
2376
  cache.tailLinkDefs = new Map();
2139
2377
  cache.linkDefs = new Map();
2140
2378
  cache.linkDefsKey = '';
2379
+ cache.settledAstBlocks = [];
2380
+ cache.settledRevision++;
2381
+ cache.projectedRevision = -1;
2382
+ cache.projectedSettledBlocks = [];
2383
+ cache.projectionCache = new WeakMap();
2141
2384
  cache.work = {
2142
2385
  splitCharacters: 0,
2143
2386
  boundaryLines: 0,
@@ -2159,6 +2402,25 @@ function resetIncrementalCache(state, cache) {
2159
2402
  */
2160
2403
 
2161
2404
  export function parseMarkdownIncremental(input, state, arg) {
2405
+ const root = parseMarkdownAstIncremental(input, state, arg);
2406
+ const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
2407
+ const projected = projectMarkdownRoot(root, cache.projectionCache);
2408
+ if (cache.projectedRevision !== cache.settledRevision) {
2409
+ cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache));
2410
+ cache.projectedRevision = cache.settledRevision;
2411
+ }
2412
+ state.settledBlocks = cache.projectedSettledBlocks;
2413
+ return projected;
2414
+ }
2415
+
2416
+ /** @internal Canonical incremental parse used by Markdown rendering. */
2417
+ export function parseMarkdownAstIncremental(input, state, arg) {
2418
+ return {
2419
+ type: 'root',
2420
+ children: parseMarkdownIncrementalAstBlocks(input, state, arg)
2421
+ };
2422
+ }
2423
+ function parseMarkdownIncrementalAstBlocks(input, state, arg) {
2162
2424
  const opts = resolveOptions(arg);
2163
2425
  const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
2164
2426
  let reparseSettled = false;
@@ -2262,12 +2524,27 @@ export function parseMarkdownIncremental(input, state, arg) {
2262
2524
  const unsettledText = openFence ? unsettledRaw : openMath ? trimOpenDisplayMath(unsettledRaw) : trimUnsettledStructural(unsettledRaw);
2263
2525
  let parsedSettledBlocks = 0;
2264
2526
  if (reparseSettled) {
2265
- state.settledBlocks = state.settledText ? parseMarkdownImpl(state.settledText, parseOpts) : [];
2266
- parsedSettledBlocks = state.settledBlocks.length;
2527
+ cache.settledAstBlocks = state.settledText ? parseMarkdownImpl(state.settledText, parseOpts) : [];
2528
+ parsedSettledBlocks = cache.settledAstBlocks.length;
2529
+ cache.settledRevision++;
2530
+ cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache));
2531
+ cache.projectedRevision = cache.settledRevision;
2267
2532
  } else if (settledDelta !== '') {
2268
2533
  const deltaBlocks = parseMarkdownImpl(settledDelta, atOffset(parseOpts, oldSettledEnd));
2269
- appendSettledBlocks(state.settledBlocks, deltaBlocks);
2534
+ const mergeIndex = cache.settledAstBlocks.length - 1;
2535
+ const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
2270
2536
  parsedSettledBlocks = deltaBlocks.length;
2537
+ cache.settledRevision++;
2538
+ const projectedDelta = deltaBlocks.map(block => projectBlockNode(block, cache.projectionCache));
2539
+ if (mergedList) {
2540
+ const canonicalMerged = cache.settledAstBlocks[mergeIndex];
2541
+ const projectedMerged = projectBlockNode(canonicalMerged, cache.projectionCache);
2542
+ cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
2543
+ cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
2544
+ } else {
2545
+ cache.projectedSettledBlocks.push(...projectedDelta);
2546
+ }
2547
+ cache.projectedRevision = cache.settledRevision;
2271
2548
  }
2272
2549
 
2273
2550
  // The unsettled tail is trimmed before parsing, so its offset in the
@@ -2290,7 +2567,7 @@ export function parseMarkdownIncremental(input, state, arg) {
2290
2567
  definitionCharacters: settledDelta.length + unsettledInput.length,
2291
2568
  renderedBlocks: parsedSettledBlocks + unsettledBlocks.length
2292
2569
  };
2293
- return mergeSettledBlocks(state.settledBlocks, unsettledBlocks);
2570
+ return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
2294
2571
  }
2295
2572
 
2296
2573
  // ---------------------------------------------------------------------------
@@ -2301,28 +2578,6 @@ export function parseMarkdownIncremental(input, state, arg) {
2301
2578
  // derives its item ids from the same functions, so outline hash links always
2302
2579
  // resolve to a rendered heading by construction.
2303
2580
 
2304
- /** Flatten inline nodes into their plain text content. */
2305
- export function inlineText(nodes) {
2306
- return nodes.map(node => {
2307
- switch (node.type) {
2308
- case 'text':
2309
- case 'code':
2310
- case 'math':
2311
- return node.type === 'math' ? node.value : node.content;
2312
- case 'bold':
2313
- case 'italic':
2314
- case 'strikethrough':
2315
- case 'link':
2316
- return inlineText(node.children);
2317
- case 'image':
2318
- return node.alt;
2319
- case 'citation':
2320
- case 'break':
2321
- return '';
2322
- }
2323
- }).join('');
2324
- }
2325
-
2326
2581
  /** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
2327
2582
  export function slugify(value) {
2328
2583
  return value.trim().toLowerCase().replace(/['"]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');