@stream-mdx/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -24,6 +24,9 @@ function normalizeNewlines(input) {
24
24
  function isFenceLine(line) {
25
25
  return /^```/.test(line.trim());
26
26
  }
27
+ function isTrailingPartialFenceLine(line) {
28
+ return /^[\t ]*`{1,2}[\t ]*$/.test(line);
29
+ }
27
30
  function stripCodeFence(raw) {
28
31
  if (!raw) {
29
32
  return { code: "", info: "", hadFence: false };
@@ -47,6 +50,9 @@ function stripCodeFence(raw) {
47
50
  return { code: codeLines2.join("\n"), info, hadFence: true };
48
51
  }
49
52
  const codeLines = lines.slice(1);
53
+ if (!normalized.endsWith("\n") && codeLines.length > 0 && isTrailingPartialFenceLine(codeLines[codeLines.length - 1] ?? "")) {
54
+ codeLines.pop();
55
+ }
50
56
  return { code: codeLines.join("\n"), info, hadFence: true };
51
57
  }
52
58
  function getDomParser() {
@@ -429,12 +435,12 @@ var InlineParser = class {
429
435
  this.registerPlugin({
430
436
  id: "strong-emphasis",
431
437
  priority: 6,
432
- re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*/g,
438
+ re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*|(?<![\w\\])__([^_\n]+?)__(?!\w)/g,
433
439
  toNode: (match) => ({
434
440
  kind: "strong",
435
- children: [{ kind: "text", text: match[1] || match[2] }]
441
+ children: [{ kind: "text", text: match[1] || match[2] || match[3] }]
436
442
  }),
437
- fastCheck: (text) => text.indexOf("**") !== -1
443
+ fastCheck: (text) => text.indexOf("**") !== -1 || text.indexOf("__") !== -1
438
444
  });
439
445
  this.registerPlugin({
440
446
  id: "strikethrough",
@@ -449,12 +455,12 @@ var InlineParser = class {
449
455
  this.registerPlugin({
450
456
  id: "emphasis",
451
457
  priority: 8,
452
- re: /\*([^*\n]+?)\*/g,
458
+ re: /\*([^*\n]+?)\*|(?<![\w\\])_([^_\n]+?)_(?!\w)/g,
453
459
  toNode: (match) => ({
454
460
  kind: "em",
455
- children: [{ kind: "text", text: match[1] }]
461
+ children: [{ kind: "text", text: match[1] || match[2] }]
456
462
  }),
457
- fastCheck: (text) => text.indexOf("*") !== -1
463
+ fastCheck: (text) => text.indexOf("*") !== -1 || text.indexOf("_") !== -1
458
464
  });
459
465
  this.registerPlugin({
460
466
  id: "citations",
@@ -605,6 +611,13 @@ function prepareInlineStreamingContent(content, options) {
605
611
  };
606
612
  let mathDisplayOpen = false;
607
613
  let mathDisplayCrossedNewline = false;
614
+ const shouldOpenInlineMath = (index) => {
615
+ const next = content[index + 1] ?? "";
616
+ if (/\d/.test(next)) {
617
+ return false;
618
+ }
619
+ return true;
620
+ };
608
621
  for (let i = 0; i < content.length; i++) {
609
622
  const code = content.charCodeAt(i);
610
623
  if (code === 10 || code === 13) {
@@ -643,7 +656,10 @@ function prepareInlineStreamingContent(content, options) {
643
656
  }
644
657
  i += 1;
645
658
  } else {
646
- toggleToken("math-inline");
659
+ const mathInlineOpen = stack.includes("math-inline");
660
+ if (mathInlineOpen || shouldOpenInlineMath(i)) {
661
+ toggleToken("math-inline");
662
+ }
647
663
  }
648
664
  }
649
665
  }
@@ -655,7 +671,7 @@ function prepareInlineStreamingContent(content, options) {
655
671
  if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
656
672
  return { kind: "raw", status: "raw", reason: "incomplete-math" };
657
673
  }
658
- if (hasIncompleteMathDisplay && (!enableMathBlockAnticipation || mathDisplayCrossedNewline)) {
674
+ if (hasIncompleteMathDisplay && !enableMathBlockAnticipation) {
659
675
  return { kind: "raw", status: "raw", reason: "incomplete-math" };
660
676
  }
661
677
  }
@@ -678,7 +694,10 @@ function prepareInlineStreamingContent(content, options) {
678
694
  case "math-inline":
679
695
  return "$";
680
696
  case "math-display":
681
- return "$$";
697
+ if (!mathDisplayCrossedNewline) {
698
+ return "$$";
699
+ }
700
+ return content.endsWith("\n") || content.endsWith("\r") ? "$$" : "\n$$";
682
701
  default:
683
702
  return "";
684
703
  }
@@ -896,9 +915,25 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
896
915
  continue;
897
916
  }
898
917
  }
899
- if (!isSelfClosing && !mdxAllowed) {
900
- const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
901
- if (closingIndex === -1) {
918
+ if (!isSelfClosing) {
919
+ const closingIndex = findClosingHtmlTag(lowerSource, tagNameLower, end);
920
+ if (closingIndex !== -1) {
921
+ end = closingIndex;
922
+ } else if (mdxAllowed) {
923
+ if (mdxAutoClose) {
924
+ const tail = source.slice(end);
925
+ const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
926
+ if (newlineCount > mdxMaxNewlines) {
927
+ tagPattern.lastIndex = start + 1;
928
+ match = tagPattern.exec(source);
929
+ continue;
930
+ }
931
+ } else {
932
+ tagPattern.lastIndex = start + 1;
933
+ match = tagPattern.exec(source);
934
+ continue;
935
+ }
936
+ } else {
902
937
  if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
903
938
  const tail = source.slice(end);
904
939
  const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
@@ -925,7 +960,6 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
925
960
  match = tagPattern.exec(source);
926
961
  continue;
927
962
  }
928
- end = closingIndex;
929
963
  }
930
964
  if (start > cursor) {
931
965
  const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
@@ -949,7 +983,9 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
949
983
  match = tagPattern.exec(source);
950
984
  continue;
951
985
  }
952
- if (mdxAutoClose && !rawSegment.endsWith("/>")) {
986
+ const closingTagPattern = new RegExp(`</\\s*${escapeRegExp(tagName)}\\s*>\\s*$`, "i");
987
+ const hasExplicitClose = closingTagPattern.test(rawSegment);
988
+ if (mdxAutoClose && !hasExplicitClose && !rawSegment.endsWith("/>")) {
953
989
  rawSegment = selfCloseTag(rawSegment);
954
990
  segment.value = rawSegment;
955
991
  }
@@ -1119,6 +1155,9 @@ function selfCloseTag(rawTag) {
1119
1155
  if (closeIndex === -1) return rawTag;
1120
1156
  return `${rawTag.slice(0, closeIndex)}/>`;
1121
1157
  }
1158
+ function escapeRegExp(value) {
1159
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1160
+ }
1122
1161
  function isLikelyMdxComponent(tagName) {
1123
1162
  const first = tagName.charAt(0);
1124
1163
  return first.toUpperCase() === first && first.toLowerCase() !== first;
@@ -1399,6 +1438,12 @@ function createBlockSnapshot(block) {
1399
1438
  }
1400
1439
  }
1401
1440
  var listInlineParser = new InlineParser();
1441
+ var LIST_STREAMING_ANTICIPATION = {
1442
+ inline: true,
1443
+ mathInline: true,
1444
+ mathBlock: true,
1445
+ regex: true
1446
+ };
1402
1447
  function enrichListSnapshot(block, snapshot) {
1403
1448
  const raw = block.payload.raw ?? "";
1404
1449
  const baseOffset = block.payload.range?.from ?? 0;
@@ -1505,7 +1550,7 @@ function buildListItemSnapshot(block, listItemNode, ordered, index, id, baseOffs
1505
1550
  const nestedId = `${id}::list:${subListIndex++}`;
1506
1551
  const nestedOrdered = name === "OrderedList";
1507
1552
  const nestedSnapshot = buildListNodeSnapshot(block, cursor.node, nestedOrdered, nestedId, baseOffset, raw);
1508
- if (Array.isArray(nestedSnapshot.children) && nestedSnapshot.children.length > 0) {
1553
+ if (nestedSnapshot && Array.isArray(nestedSnapshot.children) && nestedSnapshot.children.length > 0) {
1509
1554
  childSnapshots.push(nestedSnapshot);
1510
1555
  }
1511
1556
  } else if (name === "Blockquote") {
@@ -1544,21 +1589,20 @@ function buildListItemSnapshot(block, listItemNode, ordered, index, id, baseOffs
1544
1589
  return itemSnapshot;
1545
1590
  }
1546
1591
  function parseListInline(raw, options) {
1547
- if (!options?.streaming || !options.formatAnticipation) {
1592
+ if (!options?.streaming) {
1548
1593
  return listInlineParser.parse(raw);
1549
1594
  }
1550
- const prepared = prepareInlineStreamingContent(raw, { formatAnticipation: options.formatAnticipation, math: options.math });
1595
+ const effectiveFormatAnticipation = options.formatAnticipation ?? LIST_STREAMING_ANTICIPATION;
1596
+ const prepared = prepareInlineStreamingContent(raw, { formatAnticipation: effectiveFormatAnticipation, math: options.math ?? true });
1551
1597
  if (prepared.kind === "raw") {
1552
1598
  return [{ kind: "text", text: raw }];
1553
1599
  }
1554
1600
  let preparedContent = prepared.content;
1555
- let appended = prepared.appended;
1556
- const normalized = normalizeFormatAnticipation(options.formatAnticipation);
1601
+ const normalized = normalizeFormatAnticipation(effectiveFormatAnticipation);
1557
1602
  if (normalized.regex) {
1558
1603
  const regexAppend = listInlineParser.getRegexAnticipationAppend(raw);
1559
1604
  if (regexAppend) {
1560
1605
  preparedContent += regexAppend;
1561
- appended += regexAppend;
1562
1606
  }
1563
1607
  }
1564
1608
  return listInlineParser.parse(preparedContent, { cache: false });
@@ -1611,7 +1655,7 @@ function buildCodeBlockSnapshot(block, codeNode, id, baseOffset, raw, _isFenced)
1611
1655
  const segment = raw.slice(codeNode.from, codeNode.to);
1612
1656
  const normalized = stripListIndentation(segment);
1613
1657
  const { code, info: infoString, hadFence } = stripCodeFence(normalized);
1614
- const body = hadFence ? code : dedentIndentedCode2(normalized);
1658
+ const body = hadFence ? dedentFencedCodeByClosingIndent(normalized, code) : dedentIndentedCode2(normalized);
1615
1659
  const { lang, meta } = parseCodeFenceInfo(infoString);
1616
1660
  const codeBlock = {
1617
1661
  id,
@@ -1656,6 +1700,10 @@ function buildHeadingSnapshot(block, headingNode, id, baseOffset, raw) {
1656
1700
  return createBlockSnapshot(headingBlock);
1657
1701
  }
1658
1702
  function buildListNodeSnapshot(block, listNode, ordered, id, baseOffset, raw) {
1703
+ const children = buildListItemSnapshots(block, listNode, ordered, id, baseOffset, raw);
1704
+ if (children.length === 0) {
1705
+ return null;
1706
+ }
1659
1707
  return {
1660
1708
  id,
1661
1709
  type: "list",
@@ -1663,7 +1711,7 @@ function buildListNodeSnapshot(block, listNode, ordered, id, baseOffset, raw) {
1663
1711
  ordered
1664
1712
  },
1665
1713
  range: createRange(baseOffset + listNode.from, baseOffset + listNode.to),
1666
- children: buildListItemSnapshots(block, listNode, ordered, id, baseOffset, raw)
1714
+ children
1667
1715
  };
1668
1716
  }
1669
1717
  function enrichParagraphSnapshot(block, snapshot) {
@@ -1790,6 +1838,73 @@ function dedentIndentedCode2(input) {
1790
1838
  }
1791
1839
  return lines.map((line) => line.length >= minIndent ? line.slice(minIndent) : "").join("\n").trimEnd();
1792
1840
  }
1841
+ function dedentFencedCodeByClosingIndent(fencedRaw, extractedCode) {
1842
+ if (!fencedRaw || !extractedCode) {
1843
+ return extractedCode;
1844
+ }
1845
+ const lines = fencedRaw.replace(/\r\n?/g, "\n").split("\n");
1846
+ if (lines.length < 2) {
1847
+ return extractedCode;
1848
+ }
1849
+ let closingIndex = lines.length - 1;
1850
+ while (closingIndex > 0 && lines[closingIndex].trim().length === 0) {
1851
+ closingIndex -= 1;
1852
+ }
1853
+ if (closingIndex <= 0) {
1854
+ return extractedCode;
1855
+ }
1856
+ const closingMatch = lines[closingIndex].match(/^([ \t]*)```/);
1857
+ const closingIndent = closingMatch?.[1] ?? "";
1858
+ if (closingIndent.length === 0) {
1859
+ return extractedCode;
1860
+ }
1861
+ return extractedCode.split("\n").map((line) => stripIndentPrefix(line, closingIndent)).join("\n");
1862
+ }
1863
+ function stripIndentPrefix(line, prefix) {
1864
+ if (!line || !prefix) {
1865
+ return line;
1866
+ }
1867
+ if (line.startsWith(prefix)) {
1868
+ return line.slice(prefix.length);
1869
+ }
1870
+ const targetColumns = measureIndentColumns(prefix);
1871
+ if (targetColumns <= 0) {
1872
+ return line;
1873
+ }
1874
+ let index = 0;
1875
+ let columns = 0;
1876
+ while (index < line.length && columns < targetColumns) {
1877
+ const ch = line[index];
1878
+ if (ch === " ") {
1879
+ columns += 1;
1880
+ index += 1;
1881
+ continue;
1882
+ }
1883
+ if (ch === " ") {
1884
+ columns += 4;
1885
+ index += 1;
1886
+ continue;
1887
+ }
1888
+ break;
1889
+ }
1890
+ return columns >= targetColumns ? line.slice(index) : line;
1891
+ }
1892
+ function measureIndentColumns(value) {
1893
+ let columns = 0;
1894
+ for (let i = 0; i < value.length; i += 1) {
1895
+ const ch = value[i];
1896
+ if (ch === " ") {
1897
+ columns += 1;
1898
+ continue;
1899
+ }
1900
+ if (ch === " ") {
1901
+ columns += 4;
1902
+ continue;
1903
+ }
1904
+ break;
1905
+ }
1906
+ return columns;
1907
+ }
1793
1908
  function removeHeadingMarkers2(input) {
1794
1909
  return input.replace(/^#{1,6}\s+/, "").replace(/\s+={2,}\s*$|\s+-{2,}\s*$/m, "").trim();
1795
1910
  }
@@ -1850,8 +1965,9 @@ function enrichTableSnapshot(block, snapshot) {
1850
1965
  return snapshot;
1851
1966
  }
1852
1967
  function enrichCodeSnapshot(block, snapshot) {
1853
- const source = typeof block.payload.meta?.code === "string" ? block.payload.meta?.code : block.payload.raw ?? "";
1854
- const lines = extractCodeLines(source);
1968
+ const metaCode = typeof block.payload.meta?.code === "string" ? block.payload.meta?.code : null;
1969
+ const source = metaCode ?? (block.payload.raw ?? "");
1970
+ const lines = metaCode !== null ? source.length > 0 ? source.replace(/\r\n?/g, "\n").split("\n") : [] : extractCodeLines(source);
1855
1971
  const meta = block.payload.meta;
1856
1972
  const highlightedHtml = block.payload.highlightedHtml ?? "";
1857
1973
  const hasBlockHighlight = typeof block.payload.highlightedHtml === "string" && block.payload.highlightedHtml.length > 0;
@@ -2962,7 +3078,8 @@ function collectSetProps(window2, startIndex) {
2962
3078
  }
2963
3079
  entries.push({
2964
3080
  at: cloneNodePath(current.at),
2965
- props: mergedProps
3081
+ props: mergedProps,
3082
+ meta: current.meta ? { ...current.meta } : void 0
2966
3083
  });
2967
3084
  j = k;
2968
3085
  }
@@ -2980,7 +3097,8 @@ function collectSetProps(window2, startIndex) {
2980
3097
  }
2981
3098
  const batchPatch = {
2982
3099
  op: "setPropsBatch",
2983
- entries
3100
+ entries,
3101
+ meta: first.meta ? { ...first.meta } : void 0
2984
3102
  };
2985
3103
  return { patches: [batchPatch], nextIndex: j };
2986
3104
  }
@@ -3075,7 +3193,8 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
3075
3193
  const batchEntries = [
3076
3194
  {
3077
3195
  at: cloneNodePath(current.at),
3078
- props: mergedProps
3196
+ props: mergedProps,
3197
+ meta: current.meta ? { ...current.meta } : void 0
3079
3198
  }
3080
3199
  ];
3081
3200
  let k = j;
@@ -3099,14 +3218,16 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
3099
3218
  }
3100
3219
  batchEntries.push({
3101
3220
  at: cloneNodePath(candidate.at),
3102
- props: candidateMergedProps
3221
+ props: candidateMergedProps,
3222
+ meta: candidate.meta ? { ...candidate.meta } : void 0
3103
3223
  });
3104
3224
  k = m;
3105
3225
  }
3106
3226
  if (batchEntries.length > 1) {
3107
3227
  coalesced.push({
3108
3228
  op: "setPropsBatch",
3109
- entries: batchEntries
3229
+ entries: batchEntries,
3230
+ meta: current.meta ? { ...current.meta } : void 0
3110
3231
  });
3111
3232
  i = k;
3112
3233
  continue;
@@ -236,12 +236,12 @@ var InlineParser = class {
236
236
  this.registerPlugin({
237
237
  id: "strong-emphasis",
238
238
  priority: 6,
239
- re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*/g,
239
+ re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*|(?<![\w\\])__([^_\n]+?)__(?!\w)/g,
240
240
  toNode: (match) => ({
241
241
  kind: "strong",
242
- children: [{ kind: "text", text: match[1] || match[2] }]
242
+ children: [{ kind: "text", text: match[1] || match[2] || match[3] }]
243
243
  }),
244
- fastCheck: (text) => text.indexOf("**") !== -1
244
+ fastCheck: (text) => text.indexOf("**") !== -1 || text.indexOf("__") !== -1
245
245
  });
246
246
  this.registerPlugin({
247
247
  id: "strikethrough",
@@ -256,12 +256,12 @@ var InlineParser = class {
256
256
  this.registerPlugin({
257
257
  id: "emphasis",
258
258
  priority: 8,
259
- re: /\*([^*\n]+?)\*/g,
259
+ re: /\*([^*\n]+?)\*|(?<![\w\\])_([^_\n]+?)_(?!\w)/g,
260
260
  toNode: (match) => ({
261
261
  kind: "em",
262
- children: [{ kind: "text", text: match[1] }]
262
+ children: [{ kind: "text", text: match[1] || match[2] }]
263
263
  }),
264
- fastCheck: (text) => text.indexOf("*") !== -1
264
+ fastCheck: (text) => text.indexOf("*") !== -1 || text.indexOf("_") !== -1
265
265
  });
266
266
  this.registerPlugin({
267
267
  id: "citations",
@@ -210,12 +210,12 @@ var InlineParser = class {
210
210
  this.registerPlugin({
211
211
  id: "strong-emphasis",
212
212
  priority: 6,
213
- re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*/g,
213
+ re: /\*\*\*([^*\n]+?)\*\*\*|\*\*([^*\n]+?)\*\*|(?<![\w\\])__([^_\n]+?)__(?!\w)/g,
214
214
  toNode: (match) => ({
215
215
  kind: "strong",
216
- children: [{ kind: "text", text: match[1] || match[2] }]
216
+ children: [{ kind: "text", text: match[1] || match[2] || match[3] }]
217
217
  }),
218
- fastCheck: (text) => text.indexOf("**") !== -1
218
+ fastCheck: (text) => text.indexOf("**") !== -1 || text.indexOf("__") !== -1
219
219
  });
220
220
  this.registerPlugin({
221
221
  id: "strikethrough",
@@ -230,12 +230,12 @@ var InlineParser = class {
230
230
  this.registerPlugin({
231
231
  id: "emphasis",
232
232
  priority: 8,
233
- re: /\*([^*\n]+?)\*/g,
233
+ re: /\*([^*\n]+?)\*|(?<![\w\\])_([^_\n]+?)_(?!\w)/g,
234
234
  toNode: (match) => ({
235
235
  kind: "em",
236
- children: [{ kind: "text", text: match[1] }]
236
+ children: [{ kind: "text", text: match[1] || match[2] }]
237
237
  }),
238
- fastCheck: (text) => text.indexOf("*") !== -1
238
+ fastCheck: (text) => text.indexOf("*") !== -1 || text.indexOf("_") !== -1
239
239
  });
240
240
  this.registerPlugin({
241
241
  id: "citations",
@@ -245,9 +245,25 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
245
245
  continue;
246
246
  }
247
247
  }
248
- if (!isSelfClosing && !mdxAllowed) {
249
- const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
250
- if (closingIndex === -1) {
248
+ if (!isSelfClosing) {
249
+ const closingIndex = findClosingHtmlTag(lowerSource, tagNameLower, end);
250
+ if (closingIndex !== -1) {
251
+ end = closingIndex;
252
+ } else if (mdxAllowed) {
253
+ if (mdxAutoClose) {
254
+ const tail = source.slice(end);
255
+ const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
256
+ if (newlineCount > mdxMaxNewlines) {
257
+ tagPattern.lastIndex = start + 1;
258
+ match = tagPattern.exec(source);
259
+ continue;
260
+ }
261
+ } else {
262
+ tagPattern.lastIndex = start + 1;
263
+ match = tagPattern.exec(source);
264
+ continue;
265
+ }
266
+ } else {
251
267
  if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
252
268
  const tail = source.slice(end);
253
269
  const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
@@ -274,7 +290,6 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
274
290
  match = tagPattern.exec(source);
275
291
  continue;
276
292
  }
277
- end = closingIndex;
278
293
  }
279
294
  if (start > cursor) {
280
295
  const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
@@ -298,7 +313,9 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
298
313
  match = tagPattern.exec(source);
299
314
  continue;
300
315
  }
301
- if (mdxAutoClose && !rawSegment.endsWith("/>")) {
316
+ const closingTagPattern = new RegExp(`</\\s*${escapeRegExp(tagName)}\\s*>\\s*$`, "i");
317
+ const hasExplicitClose = closingTagPattern.test(rawSegment);
318
+ if (mdxAutoClose && !hasExplicitClose && !rawSegment.endsWith("/>")) {
302
319
  rawSegment = selfCloseTag(rawSegment);
303
320
  segment.value = rawSegment;
304
321
  }
@@ -468,6 +485,9 @@ function selfCloseTag(rawTag) {
468
485
  if (closeIndex === -1) return rawTag;
469
486
  return `${rawTag.slice(0, closeIndex)}/>`;
470
487
  }
488
+ function escapeRegExp(value) {
489
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
490
+ }
471
491
  function isLikelyMdxComponent(tagName) {
472
492
  const first = tagName.charAt(0);
473
493
  return first.toUpperCase() === first && first.toLowerCase() !== first;
@@ -207,9 +207,25 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
207
207
  continue;
208
208
  }
209
209
  }
210
- if (!isSelfClosing && !mdxAllowed) {
211
- const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
212
- if (closingIndex === -1) {
210
+ if (!isSelfClosing) {
211
+ const closingIndex = findClosingHtmlTag(lowerSource, tagNameLower, end);
212
+ if (closingIndex !== -1) {
213
+ end = closingIndex;
214
+ } else if (mdxAllowed) {
215
+ if (mdxAutoClose) {
216
+ const tail = source.slice(end);
217
+ const newlineCount = countNewlines(tail, mdxMaxNewlines + 1);
218
+ if (newlineCount > mdxMaxNewlines) {
219
+ tagPattern.lastIndex = start + 1;
220
+ match = tagPattern.exec(source);
221
+ continue;
222
+ }
223
+ } else {
224
+ tagPattern.lastIndex = start + 1;
225
+ match = tagPattern.exec(source);
226
+ continue;
227
+ }
228
+ } else {
213
229
  if (htmlAutoClose && htmlAllowTags.has(tagNameLower)) {
214
230
  const tail = source.slice(end);
215
231
  const newlineCount = countNewlines(tail, htmlMaxNewlines + 1);
@@ -236,7 +252,6 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
236
252
  match = tagPattern.exec(source);
237
253
  continue;
238
254
  }
239
- end = closingIndex;
240
255
  }
241
256
  if (start > cursor) {
242
257
  const absoluteFrom = baseIsFinite ? baseOffset + cursor : void 0;
@@ -260,7 +275,9 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
260
275
  match = tagPattern.exec(source);
261
276
  continue;
262
277
  }
263
- if (mdxAutoClose && !rawSegment.endsWith("/>")) {
278
+ const closingTagPattern = new RegExp(`</\\s*${escapeRegExp(tagName)}\\s*>\\s*$`, "i");
279
+ const hasExplicitClose = closingTagPattern.test(rawSegment);
280
+ if (mdxAutoClose && !hasExplicitClose && !rawSegment.endsWith("/>")) {
264
281
  rawSegment = selfCloseTag(rawSegment);
265
282
  segment.value = rawSegment;
266
283
  }
@@ -430,6 +447,9 @@ function selfCloseTag(rawTag) {
430
447
  if (closeIndex === -1) return rawTag;
431
448
  return `${rawTag.slice(0, closeIndex)}/>`;
432
449
  }
450
+ function escapeRegExp(value) {
451
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
452
+ }
433
453
  function isLikelyMdxComponent(tagName) {
434
454
  const first = tagName.charAt(0);
435
455
  return first.toUpperCase() === first && first.toLowerCase() !== first;
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/perf/patch-batching.ts
21
21
  var patch_batching_exports = {};
22
22
  __export(patch_batching_exports, {
23
+ getPatchKind: () => getPatchKind,
23
24
  isHeavyPatch: () => isHeavyPatch,
24
25
  splitPatchBatch: () => splitPatchBatch
25
26
  });
@@ -105,23 +106,59 @@ function isHeavyPatch(patch) {
105
106
  return false;
106
107
  }
107
108
  }
109
+ function getPatchKind(patch) {
110
+ const explicitKind = patch.op === "setHTML" ? patch.patchMeta?.kind : patch.meta?.kind;
111
+ if (explicitKind === "semantic" || explicitKind === "enrichment") {
112
+ return explicitKind;
113
+ }
114
+ switch (patch.op) {
115
+ case "insertChild":
116
+ case "deleteChild":
117
+ case "replaceChild":
118
+ case "finalize":
119
+ case "reorder":
120
+ case "appendLines":
121
+ case "setHTML":
122
+ return "semantic";
123
+ case "setProps":
124
+ case "setPropsBatch":
125
+ return "semantic";
126
+ default:
127
+ return "semantic";
128
+ }
129
+ }
108
130
  function splitPatchBatch(patches, maxLightChunk = DEFAULT_MAX_LIGHT_PATCHES_PER_CHUNK) {
109
131
  if (patches.length === 0) return [];
110
132
  const groups = [];
111
133
  let current = [];
134
+ let currentMode = null;
112
135
  const flush = () => {
113
136
  if (current.length > 0) {
114
137
  groups.push(current);
115
138
  current = [];
139
+ currentMode = null;
116
140
  }
117
141
  };
118
142
  for (const patch of patches) {
143
+ const kind = getPatchKind(patch);
119
144
  const heavy = isHeavyPatch(patch);
145
+ if (kind === "semantic") {
146
+ if (currentMode !== "semantic") {
147
+ flush();
148
+ currentMode = "semantic";
149
+ }
150
+ current.push(patch);
151
+ continue;
152
+ }
120
153
  if (heavy) {
121
154
  flush();
122
155
  groups.push([patch]);
123
156
  continue;
124
157
  }
158
+ if (currentMode !== "enrichment") {
159
+ flush();
160
+ currentMode = "enrichment";
161
+ }
125
162
  current.push(patch);
126
163
  if (current.length >= maxLightChunk) {
127
164
  flush();
@@ -132,6 +169,7 @@ function splitPatchBatch(patches, maxLightChunk = DEFAULT_MAX_LIGHT_PATCHES_PER_
132
169
  }
133
170
  // Annotate the CommonJS export names for ESM import in node:
134
171
  0 && (module.exports = {
172
+ getPatchKind,
135
173
  isHeavyPatch,
136
174
  splitPatchBatch
137
175
  });
@@ -1,6 +1,7 @@
1
- import { Patch } from '../types.cjs';
1
+ import { Patch, PatchKind } from '../types.cjs';
2
2
 
3
3
  declare function isHeavyPatch(patch: Patch): boolean;
4
+ declare function getPatchKind(patch: Patch): PatchKind;
4
5
  declare function splitPatchBatch(patches: Patch[], maxLightChunk?: number): Patch[][];
5
6
 
6
- export { isHeavyPatch, splitPatchBatch };
7
+ export { getPatchKind, isHeavyPatch, splitPatchBatch };
@@ -1,6 +1,7 @@
1
- import { Patch } from '../types.js';
1
+ import { Patch, PatchKind } from '../types.js';
2
2
 
3
3
  declare function isHeavyPatch(patch: Patch): boolean;
4
+ declare function getPatchKind(patch: Patch): PatchKind;
4
5
  declare function splitPatchBatch(patches: Patch[], maxLightChunk?: number): Patch[][];
5
6
 
6
- export { isHeavyPatch, splitPatchBatch };
7
+ export { getPatchKind, isHeavyPatch, splitPatchBatch };