@readme/markdown 14.11.3 → 14.11.5

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/main.node.js CHANGED
@@ -25257,10 +25257,10 @@ const Tabs = ({ children }) => {
25257
25257
  const tabs = external_react_default().Children.toArray(children);
25258
25258
  return (external_react_default().createElement("div", { className: "TabGroup" },
25259
25259
  external_react_default().createElement("header", null,
25260
- external_react_default().createElement("nav", { className: "TabGroup-nav" }, tabs.map((tab, index) => (external_react_default().createElement("button", { key: tab.props.title, className: `TabGroup-tab${activeTab === index ? '_active' : ''}`, onClick: () => setActiveTab(index) },
25260
+ external_react_default().createElement("nav", { className: "TabGroup-nav" }, tabs.map((tab, index) => (external_react_default().createElement("button", { key: tab.key, className: `TabGroup-tab${activeTab === index ? '_active' : ''}`, onClick: () => setActiveTab(index) },
25261
25261
  tab.props.icon && (external_react_default().createElement(components_Icon, { className: "TabGroup-icon", icon: tab.props.icon, iconColor: tab.props.iconColor })),
25262
25262
  tab.props.title))))),
25263
- external_react_default().createElement("section", null, tabs[activeTab])));
25263
+ external_react_default().createElement("section", null, tabs.map((tab, index) => (external_react_default().createElement("div", { key: tab.key, hidden: index !== activeTab }, tab))))));
25264
25264
  };
25265
25265
  /* harmony default export */ const components_Tabs = (Tabs);
25266
25266
 
@@ -74751,6 +74751,21 @@ function evaluate(source, scope = {}) {
74751
74751
  // eslint-disable-next-line no-new-func
74752
74752
  return new Function(...names, `return (${source})`)(...values);
74753
74753
  }
74754
+ /**
74755
+ * Advance a `Point` by the `consumed` substring that follows it, returning the
74756
+ * point at the consumed string's end. Lets split-out sub-nodes carry accurate
74757
+ * document positions so downstream offset shifting stays correct.
74758
+ */
74759
+ const pointAfter = (start, consumed) => {
74760
+ const newlineIndex = consumed.lastIndexOf('\n');
74761
+ const newlineCount = newlineIndex === -1 ? 0 : consumed.split('\n').length - 1;
74762
+ return {
74763
+ line: start.line + newlineCount,
74764
+ // Same line → advance the base column; a later line → column is the run since its newline.
74765
+ column: newlineCount === 0 ? start.column + consumed.length : consumed.length - newlineIndex,
74766
+ offset: (start.offset ?? 0) + consumed.length,
74767
+ };
74768
+ };
74754
74769
  /**
74755
74770
  * Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
74756
74771
  * This currently sets all the values to a string since we process/compile the MDX on the fly
@@ -96019,6 +96034,108 @@ const applyInserts = (html, inserts) => {
96019
96034
  return { value: out + html.slice(cursor), inserts: sorted };
96020
96035
  };
96021
96036
 
96037
+ ;// ./processor/transform/mdxish/tables/escape-crossing-emphasis.ts
96038
+
96039
+
96040
+
96041
+ // Maximal run of a single emphasis delimiter (`_`, `*`, `**`, `***`, …).
96042
+ const EMPHASIS_RUN_RE = /([*_])\1*/g;
96043
+ // String bounds (undefined) count as whitespace, not punctuation, per the
96044
+ // CommonMark flanking definition.
96045
+ const escape_crossing_emphasis_isWhitespace = (ch) => ch === undefined || unicodeWhitespace(ch.charCodeAt(0));
96046
+ const isPunctuation = (ch) => ch !== undefined && unicodePunctuation(ch.charCodeAt(0));
96047
+ /**
96048
+ * Whether a delimiter run can open and/or close emphasis, per CommonMark's
96049
+ * left/right-flanking rules. The extra `_` conditions forbid intraword
96050
+ * emphasis, which keeps `snake_case` from being treated as a delimiter.
96051
+ */
96052
+ const analyzeFlanking = (source, char, start, end) => {
96053
+ const before = source[start - 1];
96054
+ const after = source[end];
96055
+ const leftFlanking = !escape_crossing_emphasis_isWhitespace(after) && (!isPunctuation(after) || escape_crossing_emphasis_isWhitespace(before) || isPunctuation(before));
96056
+ const rightFlanking = !escape_crossing_emphasis_isWhitespace(before) && (!isPunctuation(before) || escape_crossing_emphasis_isWhitespace(after) || isPunctuation(after));
96057
+ if (char === '_') {
96058
+ return {
96059
+ canOpen: leftFlanking && (!rightFlanking || isPunctuation(before)),
96060
+ canClose: rightFlanking && (!leftFlanking || isPunctuation(after)),
96061
+ };
96062
+ }
96063
+ return { canOpen: leftFlanking, canClose: rightFlanking };
96064
+ };
96065
+ /**
96066
+ * Collect HTML tag boundaries (via htmlparser2) and emphasis delimiter runs
96067
+ * (via regex over the masked source, so code spans and escaped `<` are skipped)
96068
+ * into one list ordered by position. At a shared offset a tag opener sorts
96069
+ * before a closer, so a self-closing tag brackets rather than swallows a run.
96070
+ */
96071
+ const collectEvents = (html) => {
96072
+ const masked = maskNonTagRegions(html);
96073
+ const events = [];
96074
+ walkTags(html, {
96075
+ onOpen: ({ start }) => events.push({ kind: 'tagOpen', offset: start }),
96076
+ onClose: ({ start }) => events.push({ kind: 'tagClose', offset: start }),
96077
+ });
96078
+ EMPHASIS_RUN_RE.lastIndex = 0;
96079
+ let match;
96080
+ while ((match = EMPHASIS_RUN_RE.exec(masked)) !== null) {
96081
+ const [run, char] = match;
96082
+ const start = match.index;
96083
+ // eslint-disable-next-line no-continue
96084
+ if (html[start - 1] === '\\')
96085
+ continue; // already escaped
96086
+ events.push({ kind: 'emphasis', offset: start, char, length: run.length, flanking: analyzeFlanking(html, char, start, start + run.length) });
96087
+ }
96088
+ return events.sort((a, b) => a.offset - b.offset || (a.kind === 'tagClose' ? 1 : 0) - (b.kind === 'tagClose' ? 1 : 0));
96089
+ };
96090
+ /**
96091
+ * mdxjs rejects a table when a markdown emphasis run opens at one HTML
96092
+ * tag-nesting depth and closes at another — e.g. `_<ul><li>text_</li></ul>`,
96093
+ * where the `_` opens outside the list but closes inside a `<li>`. It throws
96094
+ * "Expected a closing tag for `<li>` before the end of `emphasis`" and the
96095
+ * whole `<Table>` fails to parse.
96096
+ *
96097
+ * We walk tags and emphasis together, tracking tag depth and a stack of open
96098
+ * emphasis. A delimiter only closes an opener at the same depth; any emphasis
96099
+ * still open when its enclosing tag closes (or at end of input), and any closer
96100
+ * with no same-depth opener, is escaped so mdxjs treats it as literal text.
96101
+ * Scoped to the malformed-retry path.
96102
+ */
96103
+ const escapeCrossingEmphasis = (html) => {
96104
+ const open = [];
96105
+ const orphans = [];
96106
+ let depth = 0;
96107
+ collectEvents(html).forEach(event => {
96108
+ if (event.kind === 'tagOpen') {
96109
+ depth += 1;
96110
+ return;
96111
+ }
96112
+ if (event.kind === 'tagClose') {
96113
+ depth -= 1;
96114
+ // Emphasis opened deeper than the surviving depth was inside the tag that
96115
+ // just closed, so it crosses the boundary.
96116
+ while (open.length > 0 && open[open.length - 1].depth > depth) {
96117
+ const crossed = open.pop();
96118
+ if (crossed)
96119
+ orphans.push(crossed);
96120
+ }
96121
+ return;
96122
+ }
96123
+ const top = open[open.length - 1];
96124
+ if (event.flanking.canClose && top?.char === event.char && top.depth === depth) {
96125
+ open.pop();
96126
+ }
96127
+ else if (event.flanking.canOpen) {
96128
+ open.push({ char: event.char, depth, offset: event.offset, length: event.length });
96129
+ }
96130
+ else if (event.flanking.canClose) {
96131
+ orphans.push({ offset: event.offset, length: event.length });
96132
+ }
96133
+ });
96134
+ orphans.push(...open); // anything still open never closed
96135
+ const inserts = orphans.flatMap(({ offset, length }) => Array.from({ length }, (_unused, i) => ({ offset: offset + i, text: '\\' })));
96136
+ return applyInserts(html, inserts);
96137
+ };
96138
+
96022
96139
  ;// ./processor/transform/mdxish/tables/escape-stray-less-than.ts
96023
96140
 
96024
96141
 
@@ -96171,6 +96288,21 @@ const buildOffsetMapper = (inserts) => {
96171
96288
  return repaired - shift;
96172
96289
  };
96173
96290
  };
96291
+ /**
96292
+ * Compose the per-repair offset mappers into one that maps an offset in the
96293
+ * fully-repaired string back to the original. `layers` are in application
96294
+ * order; each maps its own output space to its input space, so we apply them
96295
+ * last-repair-first to peel every edit back to the original coordinates.
96296
+ */
96297
+ const composeOffsetMapper = (layers) => {
96298
+ const mappers = layers.map(buildOffsetMapper);
96299
+ return (repaired) => {
96300
+ let offset = repaired;
96301
+ for (let i = mappers.length - 1; i >= 0; i -= 1)
96302
+ offset = mappers[i](offset);
96303
+ return offset;
96304
+ };
96305
+ };
96174
96306
  /**
96175
96307
  * Map an offset in `source` to its 1-based `{ line, column }`. `lineStarts`
96176
96308
  * is the precomputed array of offsets where each line begins.
@@ -96198,14 +96330,11 @@ const computeLineStarts = (source) => {
96198
96330
  };
96199
96331
  /**
96200
96332
  * Walk `tree`, translating every node's position from the repaired source's
96201
- * coordinate space back to the original source. Offsets are remapped via the
96202
- * insert list; line/column are recomputed from the original source so they
96203
- * remain accurate even if repairs introduced newlines.
96333
+ * coordinate space back to the original source via `mapOffset`. Line/column
96334
+ * are recomputed from the original source so they remain accurate even if
96335
+ * repairs introduced newlines.
96204
96336
  */
96205
- const remapPositionsToOriginal = (tree, originalSource, inserts) => {
96206
- if (inserts.length === 0)
96207
- return;
96208
- const mapOffset = buildOffsetMapper(inserts);
96337
+ const remapWithMapper = (tree, originalSource, mapOffset) => {
96209
96338
  const lineStarts = computeLineStarts(originalSource);
96210
96339
  visit(tree, child => {
96211
96340
  if (child.position?.start) {
@@ -96224,6 +96353,15 @@ const remapPositionsToOriginal = (tree, originalSource, inserts) => {
96224
96353
  }
96225
96354
  });
96226
96355
  };
96356
+ /**
96357
+ * Remap positions produced after a chain of repairs (each applied to the prior
96358
+ * one's output) back to the original source coordinates.
96359
+ */
96360
+ const remapPositionsThroughLayers = (tree, originalSource, layers) => {
96361
+ if (layers.length === 0)
96362
+ return;
96363
+ remapWithMapper(tree, originalSource, composeOffsetMapper(layers));
96364
+ };
96227
96365
 
96228
96366
  ;// ./processor/transform/mdxish/tables/repair-expression-escapes.ts
96229
96367
 
@@ -96523,6 +96661,83 @@ const repairUnclosedTags = (html) => {
96523
96661
  return applyInserts(html, inserts);
96524
96662
  };
96525
96663
 
96664
+ ;// ./processor/transform/mdxish/tables/split-nested-tables.ts
96665
+
96666
+
96667
+ const TOP_LEVEL_TABLE_TAG_RE = /^<(?:table|Table)(?=[\s/>])/;
96668
+ /**
96669
+ * Find every balanced, depth-matched `<table>…</table>` range in an html string.
96670
+ * Uses `walkTags` so `<table>`s inside code spans / fenced blocks (masked away)
96671
+ * are never matched.
96672
+ *
96673
+ * Note: An implicitly-closed table (no `</table>`, so htmlparser2 synthesizes the
96674
+ * close at a later tag) is skipped: splitting there would swallow whatever
96675
+ * followed the table into the fragment and drop it, so we leave such a node
96676
+ * whole for downstream raw-HTML handling instead.
96677
+ */
96678
+ const findTableRanges = (html) => {
96679
+ const ranges = [];
96680
+ let depth = 0;
96681
+ let start = 0;
96682
+ walkTags(html, {
96683
+ onOpen: ({ name, start: openStart, isStrayCloser }) => {
96684
+ if (name.toLowerCase() !== 'table' || isStrayCloser)
96685
+ return;
96686
+ if (depth === 0)
96687
+ start = openStart;
96688
+ depth += 1;
96689
+ },
96690
+ onClose: ({ name, end, implicit }) => {
96691
+ if (implicit || name.toLowerCase() !== 'table' || depth === 0)
96692
+ return;
96693
+ depth -= 1;
96694
+ if (depth === 0)
96695
+ ranges.push({ start, end });
96696
+ },
96697
+ });
96698
+ return ranges;
96699
+ };
96700
+ /**
96701
+ * Given an HTML node that might contain table sequences inside it, split
96702
+ * the node based on the table boundaries so they become a top level node.
96703
+ *
96704
+ * The surrounding raw HTML (the wrapper's open/close tags) would be re-nested
96705
+ * around the parsed tables by rehype-raw.
96706
+ *
96707
+ * Returns null when there is no wrapped table to extract.
96708
+ */
96709
+ const splitHtmlWithNestedTables = (node) => {
96710
+ const { value } = node;
96711
+ // This is a top-level table, so we don't need to split it
96712
+ if (TOP_LEVEL_TABLE_TAG_RE.test(value))
96713
+ return null;
96714
+ // No table text anywhere in the value → skip the htmlparser2 walk entirely.
96715
+ if (!/<\/?table/i.test(value))
96716
+ return null;
96717
+ const ranges = findTableRanges(value);
96718
+ if (ranges.length === 0)
96719
+ return null;
96720
+ const base = node.position?.start;
96721
+ const sliceToHtml = (from, to) => ({
96722
+ type: 'html',
96723
+ value: value.slice(from, to),
96724
+ ...(base && {
96725
+ position: { start: pointAfter(base, value.slice(0, from)), end: pointAfter(base, value.slice(0, to)) },
96726
+ }),
96727
+ });
96728
+ const parts = [];
96729
+ let cursor = 0;
96730
+ ranges.forEach(({ start, end }) => {
96731
+ if (start > cursor)
96732
+ parts.push(sliceToHtml(cursor, start));
96733
+ parts.push(sliceToHtml(start, end)); // starts with `<table` → picked up by the main pass
96734
+ cursor = end;
96735
+ });
96736
+ if (cursor < value.length)
96737
+ parts.push(sliceToHtml(cursor, value.length));
96738
+ return parts;
96739
+ };
96740
+
96526
96741
  ;// ./processor/transform/mdxish/tables/mdxish-tables.ts
96527
96742
 
96528
96743
 
@@ -96544,6 +96759,8 @@ const repairUnclosedTags = (html) => {
96544
96759
 
96545
96760
 
96546
96761
 
96762
+
96763
+
96547
96764
 
96548
96765
 
96549
96766
 
@@ -96573,6 +96790,21 @@ const buildTableNodeProcessor = (withMdx) => unified()
96573
96790
  .use(remarkGfm);
96574
96791
  const tableNodeProcessor = buildTableNodeProcessor(true);
96575
96792
  const fallbackTableNodeProcessor = buildTableNodeProcessor(false);
96793
+ // Targeted repairs for tables mdxjs rejects, tried cumulatively (each runs on
96794
+ // the prior's output) since one table can carry independent per-cell defects.
96795
+ // Each was added after seeing real customer content fail to parse:
96796
+ // - repairUnclosedTags: unclosed/orphan HTML tags
96797
+ // - normalizeTagSpacing: a line mixing text and an opening tag
96798
+ // - repairExpressionEscapes: backslash escapes inside a `{…}` expression
96799
+ // - escapeStrayLessThan: a `<` that doesn't begin a valid tag (`word <`)
96800
+ // - escapeCrossingEmphasis: emphasis opening/closing at different tag depths
96801
+ const tableRepairs = [
96802
+ repairUnclosedTags,
96803
+ normalizeTagSpacing,
96804
+ repairExpressionEscapes,
96805
+ escapeStrayLessThan,
96806
+ escapeCrossingEmphasis,
96807
+ ];
96576
96808
  /**
96577
96809
  * Parse the HTML node that contains the full table substring
96578
96810
  * into the table parts (headers, rows, cells).
@@ -96588,10 +96820,10 @@ const parseTableNode = (processor, node, repair) => {
96588
96820
  return undefined;
96589
96821
  }
96590
96822
  // If `node.value` was repaired before parsing, first remap positions back to
96591
- // the original (unrepaired) coordinates via the insert list — otherwise the
96823
+ // the original (unrepaired) coordinates via the insert layers — otherwise the
96592
96824
  // shift would land on synthetic characters and be inaccurate
96593
96825
  if (repair) {
96594
- remapPositionsToOriginal(parsed, repair.originalSource, repair.inserts);
96826
+ remapPositionsThroughLayers(parsed, repair.originalSource, repair.layers);
96595
96827
  }
96596
96828
  // The subparser produces positions relative to `node.value`; shift them by
96597
96829
  // the outer node's offset/line so consumers can slice the full source.
@@ -96688,9 +96920,7 @@ const processTableNode = (node, index, parent, documentPosition) => {
96688
96920
  visit(node, isMDXElement, (child) => {
96689
96921
  if (child.name === 'thead')
96690
96922
  hasThead = true;
96691
- if (tableTags.has(child.name) &&
96692
- Array.isArray(child.attributes) &&
96693
- child.attributes.length > 0) {
96923
+ if (tableTags.has(child.name) && Array.isArray(child.attributes) && child.attributes.length > 0) {
96694
96924
  hasStructuralAttributes = true;
96695
96925
  }
96696
96926
  });
@@ -96801,6 +97031,26 @@ const processTableNode = (node, index, parent, documentPosition) => {
96801
97031
  };
96802
97032
  parent.children[index] = mdNode;
96803
97033
  };
97034
+ /**
97035
+ * Apply `tableRepairs` cumulatively, re-parsing after every change and stopping
97036
+ * once the accumulated result parses. Each layer's inserts are relative to the
97037
+ * string that repair received, so they stay ordered for position remapping.
97038
+ */
97039
+ const repairAndReparse = (node) => {
97040
+ let repairedValue = node.value;
97041
+ const layers = [];
97042
+ let parsed;
97043
+ tableRepairs.some(repair => {
97044
+ const { value, inserts } = repair(repairedValue);
97045
+ if (value === repairedValue)
97046
+ return false;
97047
+ repairedValue = value;
97048
+ layers.push(inserts);
97049
+ parsed = parseTableNode(tableNodeProcessor, { ...node, value: repairedValue }, { layers, originalSource: node.value });
97050
+ return Boolean(parsed);
97051
+ });
97052
+ return parsed;
97053
+ };
96804
97054
  /**
96805
97055
  * Converts JSX Table elements to markdown table nodes and re-parses markdown in cells.
96806
97056
  *
@@ -96813,42 +97063,30 @@ const processTableNode = (node, index, parent, documentPosition) => {
96813
97063
  * is kept as a JSX <Table> element so that remarkRehype can properly handle the flow content.
96814
97064
  */
96815
97065
  const mdxishTables = () => tree => {
97066
+ // Pre-pass: lift `<table>`s wrapped in a raw HTML block out into their own
97067
+ // html nodes so the main pass below treats them like top-level tables.
97068
+ visit(tree, 'html', (_node, index, parent) => {
97069
+ const node = _node;
97070
+ if (typeof index !== 'number' || !parent || !('children' in parent))
97071
+ return;
97072
+ const parts = splitHtmlWithNestedTables(node);
97073
+ if (!parts)
97074
+ return;
97075
+ // The inserted parts can't re-trigger a split (table parts start with
97076
+ // `<table`; the wrapper slices hold no table), so plain in-place splicing
97077
+ // visits each once without looping.
97078
+ parent.children.splice(index, 1, ...parts);
97079
+ });
96816
97080
  visit(tree, 'html', (_node, index, parent) => {
96817
97081
  const node = _node;
96818
97082
  if (typeof index !== 'number' || !parent || !('children' in parent))
96819
97083
  return;
96820
97084
  if (!node.value.startsWith('<Table') && !node.value.startsWith('<table'))
96821
97085
  return;
96822
- // Main logic to transform table node to its parts
96823
97086
  // Because the processor uses remarkMdx, it is stricter in what it accepts
96824
- // and only accepts valid MDX syntax. in the table node.
96825
- // To get around that, we have some fallback logics after trying to repair the table content.
96826
- let parsed = parseTableNode(tableNodeProcessor, node);
96827
- if (!parsed) {
96828
- // Try a sequence of targeted repairs and re-parse
96829
- // after each, stopping at the first that yields a parseable tree:
96830
- // - repairUnclosedTags: unclosed/orphan HTML tags
96831
- // - normalizeTagSpacing: a line mixing text and an opening tag
96832
- // (e.g. `text <div> \n <div> text`)
96833
- // - repairExpressionEscapes: backslash escapes inside a `{…}` expression
96834
- // - escapeStrayLessThan: a `<` that doesn't begin a valid tag
96835
- // (e.g. `word <`, `a <1>`)
96836
- // These repairs are created after seeing real customer content that has failed to parse
96837
- const repairs = [
96838
- repairUnclosedTags,
96839
- normalizeTagSpacing,
96840
- repairExpressionEscapes,
96841
- escapeStrayLessThan,
96842
- ];
96843
- // Stops at the first repair that yields a parseable tree
96844
- repairs.some(repair => {
96845
- const { value, inserts } = repair(node.value);
96846
- if (value !== node.value) {
96847
- parsed = parseTableNode(tableNodeProcessor, { ...node, value }, { inserts, originalSource: node.value });
96848
- }
96849
- return Boolean(parsed);
96850
- });
96851
- }
97087
+ // and only accepts valid MDX syntax in the table node. To get around that,
97088
+ // fall back to the cumulative repairs when the first parse fails.
97089
+ const parsed = parseTableNode(tableNodeProcessor, node) ?? repairAndReparse(node);
96852
97090
  if (parsed) {
96853
97091
  // If the table is parsed successfully, we can now process it further
96854
97092
  // to build on the markdown / JSX table
@@ -117308,6 +117546,19 @@ const compile_list_item_listItem = (node, parent, state, info) => {
117308
117546
  const plain_plain = (node) => node.value;
117309
117547
  /* harmony default export */ const compile_plain = (plain_plain);
117310
117548
 
117549
+ ;// ./processor/compile/text.ts
117550
+
117551
+ // A `_` flanked by word characters can never open or close emphasis under
117552
+ // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
117553
+ // intraword underscores is unnecessary and only produces noisy `\_` diffs.
117554
+ // Uses Unicode letter/number classes so non-ASCII words (e.g. `é_pay`) match.
117555
+ const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
117556
+ const compile_text_text = (node, parent, state, info) => {
117557
+ const serialized = handle.text(node, parent, state, info);
117558
+ return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
117559
+ };
117560
+ /* harmony default export */ const compile_text = (compile_text_text);
117561
+
117311
117562
  ;// ./processor/compile/index.ts
117312
117563
 
117313
117564
 
@@ -117320,11 +117571,11 @@ const plain_plain = (node) => node.value;
117320
117571
 
117321
117572
 
117322
117573
 
117574
+
117323
117575
  function compilers(mdxish = false) {
117324
117576
  const data = this.data();
117325
117577
  const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
117326
117578
  const handlers = {
117327
- ...(mdxish && { [NodeTypes.anchor]: compile_anchor }),
117328
117579
  [NodeTypes.callout]: compile_callout,
117329
117580
  [NodeTypes.codeTabs]: compile_code_tabs,
117330
117581
  [NodeTypes.embedBlock]: compile_embed,
@@ -117332,15 +117583,18 @@ function compilers(mdxish = false) {
117332
117583
  [NodeTypes.glossary]: compile_compatibility,
117333
117584
  [NodeTypes.htmlBlock]: html_block,
117334
117585
  [NodeTypes.reusableContent]: compile_compatibility,
117335
- ...(mdxish && { [NodeTypes.variable]: compile_variable }),
117336
117586
  embed: compile_compatibility,
117337
117587
  escape: compile_compatibility,
117338
117588
  figure: compile_compatibility,
117339
117589
  html: compile_compatibility,
117340
117590
  i: compile_compatibility,
117341
- ...(mdxish && { listItem: list_item }),
117342
117591
  plain: compile_plain,
117343
117592
  yaml: compile_compatibility,
117593
+ // needed only for mdxish
117594
+ ...(mdxish && { [NodeTypes.anchor]: compile_anchor }),
117595
+ ...(mdxish && { listItem: list_item }),
117596
+ ...(mdxish && { text: compile_text }),
117597
+ ...(mdxish && { [NodeTypes.variable]: compile_variable }),
117344
117598
  };
117345
117599
  toMarkdownExtensions.push({ extensions: [{ handlers }] });
117346
117600
  }
@@ -119137,11 +119391,10 @@ const rehypeMdxishComponents = ({ components, processMarkdown }) => {
119137
119391
  ;// ./processor/plugin/mdxish-handlers.ts
119138
119392
 
119139
119393
 
119140
- // Convert MDX expressions to text nodes (evaluation happens earlier in pipeline)
119141
- const mdxExpressionHandler = (_state, node) => ({
119142
- type: 'text',
119143
- value: node.value || '',
119144
- });
119394
+ // If hChildren is populated, it means the node holds a renderable value (e.g. React)
119395
+ // See evaluate-expressions.ts for more details
119396
+ // Otherwise, it's a simple value and we fall back to text
119397
+ const mdxExpressionHandler = (_state, node) => node.data?.hChildren ?? { type: 'text', value: node.value || '' };
119145
119398
  // Convert MDX JSX elements to a custom mdx-jsx hast node that bypasses rehypeRaw's
119146
119399
  // HTML serialization round-trip; downstream normalization rewrites it to `element`.
119147
119400
  const mdxJsxElementHandler = (state, node) => {
@@ -122203,81 +122456,168 @@ const mdxishInlineMdxComponents = () => tree => {
122203
122456
  };
122204
122457
  /* harmony default export */ const inline_mdx_blocks = (mdxishInlineMdxComponents);
122205
122458
 
122459
+ ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
122460
+
122461
+
122462
+
122463
+ const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
122464
+ const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
122465
+ // A line that is exactly one lowercase opening (or self-closing) tag — the shape
122466
+ // that opens a CommonMark type-6/7 HTML block and swallows the lines below it.
122467
+ const SINGLE_OPENING_TAG_REGEX = /^<[a-z][^<>]*>$/;
122468
+ // Tags whose contents must be preserved as is, inserting a blank line after the
122469
+ // opener corrupts the payload.
122470
+ // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
122471
+ const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
122472
+ // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
122473
+ const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
122474
+ open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
122475
+ close: new RegExp(`</${tag}(?=[\\s>])[^>]*>`, 'gi'),
122476
+ }));
122477
+ function isLineHtml(line) {
122478
+ return STANDALONE_HTML_LINE_REGEX.test(line) || HTML_LINE_WITH_CONTENT_REGEX.test(line);
122479
+ }
122480
+ // Per-tag {opens, closes} counts of RAW_CONTENT_TAGS on one line — feeds both the
122481
+ // per-line net-open check and the cumulative still-open depth tracking.
122482
+ function countRawContentTags(line) {
122483
+ return RAW_CONTENT_TAG_MATCHERS.map(({ open, close }) => ({
122484
+ opens: (line.match(open) ?? []).length,
122485
+ closes: (line.match(close) ?? []).length,
122486
+ }));
122487
+ }
122488
+ // Indentation width in columns, counting a tab as 4 per CommonMark.
122489
+ function indentWidth(line) {
122490
+ const leading = line.match(/^[ \t]*/)?.[0] ?? '';
122491
+ return leading.replace(/\t/g, ' ').length;
122492
+ }
122493
+ /**
122494
+ * Decides whether a blank line belongs between `line` and `next` so the HTML
122495
+ * flow block opened on `line` terminates and `next` parses as markdown.
122496
+ */
122497
+ function shouldTerminateBetween(line, next, { insideRawContent, lineLeavesRawOpen }) {
122498
+ if (next.trim().length === 0)
122499
+ return false;
122500
+ const currentIndent = indentWidth(line);
122501
+ const nextIndent = indentWidth(next);
122502
+ // 4+ columns is CommonMark indented-code territory: an inserted blank line
122503
+ // would turn the next line into a code block instead of freeing it (#1344).
122504
+ if (currentIndent > 3 || nextIndent > 3)
122505
+ return false;
122506
+ const currentTrimmed = line.trim();
122507
+ const nextTrimmed = next.trim();
122508
+ if (!isLineHtml(currentTrimmed) || isLineHtml(nextTrimmed) || lineLeavesRawOpen) {
122509
+ return false;
122510
+ }
122511
+ // Column-0 pairs keep the original (pre-relaxation) rule, deliberately ignoring
122512
+ // `insideRawContent`: one unclosed <pre>/<table> typo would otherwise suppress
122513
+ // termination for the whole rest of the document.
122514
+ if (currentIndent === 0 && nextIndent === 0)
122515
+ return true;
122516
+ // Indented shapes (either line at 1–3 columns) are stricter: only a lone opening
122517
+ // tag followed by markdown. A next line opening a tag or expression must stay
122518
+ // glued for the mdxComponent tokenizer; raw-content payloads stay byte-exact.
122519
+ return (!insideRawContent &&
122520
+ SINGLE_OPENING_TAG_REGEX.test(currentTrimmed) &&
122521
+ !nextTrimmed.startsWith('<') &&
122522
+ !nextTrimmed.startsWith('{'));
122523
+ }
122524
+ /**
122525
+ * CommonMark HTML blocks (types 6/7) end only at a blank line, so markdown on the
122526
+ * next line is swallowed as HTML (`## heading` after `<div>` renders as literal text).
122527
+ * Inserts the missing blank line; the gating rules live in `shouldTerminateBetween`.
122528
+ *
122529
+ * @link https://spec.commonmark.org/0.29/#html-blocks
122530
+ */
122531
+ function terminateHtmlFlowBlocks(content) {
122532
+ const { protectedContent, protectedCode } = protectCodeBlocks(content);
122533
+ const lines = protectedContent.split('\n');
122534
+ const result = [];
122535
+ // Per-tag count of still-open raw-content elements at the current boundary.
122536
+ const rawContentDepths = RAW_CONTENT_TAG_MATCHERS.map(() => 0);
122537
+ for (let i = 0; i < lines.length; i += 1) {
122538
+ const line = lines[i];
122539
+ result.push(line);
122540
+ const tagCounts = countRawContentTags(line);
122541
+ tagCounts.forEach(({ opens, closes }, tagIndex) => {
122542
+ rawContentDepths[tagIndex] = Math.max(0, rawContentDepths[tagIndex] + opens - closes);
122543
+ });
122544
+ const next = lines[i + 1];
122545
+ const rawContentFacts = {
122546
+ insideRawContent: rawContentDepths.some(depth => depth > 0),
122547
+ lineLeavesRawOpen: tagCounts.some(({ opens, closes }) => opens > closes),
122548
+ };
122549
+ if (next !== undefined && shouldTerminateBetween(line, next, rawContentFacts)) {
122550
+ result.push('');
122551
+ }
122552
+ }
122553
+ return restoreCodeBlocks(result.join('\n'), protectedCode);
122554
+ }
122555
+
122206
122556
  ;// ./processor/transform/mdxish/components/mdx-blocks.ts
122207
122557
 
122208
122558
 
122209
122559
 
122210
122560
 
122211
- /** Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
122561
+
122562
+
122563
+
122564
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
122212
122565
  const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
122213
- /**
122214
- * Reduce leading whitespace on all lines just enough to prevent
122215
- * remark from treating indented content as code blocks (4+ spaces).
122216
- * Preserves relative indentation so whitespace text nodes are
122217
- * maintained in the HAST output.
122566
+ // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
122567
+ // of a legacy `<<VARIABLE>>`.
122568
+ const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
122569
+ // Excludes tags with dedicated transformers (`Table`, `HTMLBlock`, inline
122570
+ // components), which expect their wrapper to stay raw.
122571
+ const hasNestedGenericComponentTag = (content) => [...content.matchAll(NESTED_COMPONENT_TAG_RE)].some(match => !GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(match[1]));
122572
+ /**
122573
+ * Strip the shared leading indentation from a component body so readability indentation
122574
+ * isn't parsed as indented code (4+ spaces), e.g. ` <p>` / ` text` -> `<p>` / ` text`.
122575
+ * Relative indentation is kept, so content genuinely 4+ columns deeper stays code. We
122576
+ * only strip when a line actually reaches 4 columns; otherwise the body is left as-is so
122577
+ * its leading whitespace survives as text nodes (mixed component + HTML content needs it).
122218
122578
  */
122219
122579
  function safeDeindent(text) {
122220
122580
  const lines = text.split('\n');
122221
122581
  const nonEmptyLines = lines.filter(line => line.trim().length > 0);
122222
122582
  if (nonEmptyLines.length === 0)
122223
122583
  return text;
122224
- const minIndent = Math.min(...nonEmptyLines.map(line => {
122225
- const match = line.match(/^(\s*)/);
122226
- return match ? match[1].length : 0;
122227
- }));
122228
- // Only strip enough indent to keep all lines below the 4-space code threshold
122229
- const stripAmount = Math.max(0, minIndent - 3);
122584
+ // Indent counts characters (tab = 1), unlike indentWidth's CommonMark columns
122585
+ // (tab = 4) in terminate-html-flow-blocks.
122586
+ const indents = nonEmptyLines.map(line => line.match(/^(\s*)/)?.[1].length ?? 0);
122587
+ const minIndent = Math.min(...indents);
122588
+ const maxIndent = Math.max(...indents);
122589
+ const stripAmount = maxIndent > 3 ? minIndent : 0;
122230
122590
  if (stripAmount === 0)
122231
122591
  return text;
122232
122592
  return lines.map(line => line.slice(stripAmount)).join('\n');
122233
122593
  }
122234
122594
  /**
122235
- * Parse markdown content into mdast children nodes.
122236
- * Dedents the content first to prevent indented component content
122237
- * (from nested components) from being treated as code blocks.
122595
+ * Parse component-body markdown into mdast children. Dedenting shifts columns and
122596
+ * stales the top-level `terminateHtmlFlowBlocks` decisions, so that one preprocessor
122597
+ * re-runs here; other column-anchored fixups (compact headings, tables) do not.
122238
122598
  */
122239
122599
  const parseMdChildren = (value, safeMode) => {
122240
- const parsed = getInlineMdProcessor({ safeMode }).parse(safeDeindent(value).trim());
122600
+ const parsed = getInlineMdProcessor({ safeMode }).parse(terminateHtmlFlowBlocks(safeDeindent(value).trim()));
122241
122601
  return parsed.children || [];
122242
122602
  };
122243
- /**
122244
- * Parse substring content of a node and update the parent's children to include the new nodes.
122245
- */
122603
+ // Parses trailing content into sibling nodes and re-queues the parent so any
122604
+ // components among them get processed.
122246
122605
  const parseSibling = (stack, parent, index, sibling, safeMode) => {
122247
122606
  const siblingNodes = parseMdChildren(sibling, safeMode);
122248
- // The new sibling nodes might contain new components to be processed
122249
122607
  if (siblingNodes.length > 0) {
122250
122608
  parent.children.splice(index + 1, 0, ...siblingNodes);
122251
122609
  stack.push(parent);
122252
122610
  }
122253
122611
  };
122254
- /**
122255
- * Advance a point by the substring of source consumed from it.
122256
- */
122257
- const pointAfter = (start, consumed) => {
122258
- const newlineIndex = consumed.lastIndexOf('\n');
122259
- const newlineCount = newlineIndex === -1 ? 0 : consumed.split('\n').length - 1;
122260
- return {
122261
- line: start.line + newlineCount,
122262
- column: newlineCount === 0 ? start.column + consumed.length : consumed.length - newlineIndex,
122263
- offset: start.offset + consumed.length,
122264
- };
122265
- };
122266
- /**
122267
- * Build a position ending at `consumedLength` into the html node's value, so the
122268
- * component doesn't claim trailing content the tokenizer swallowed into one node.
122269
- */
122612
+ // Ends the position at `consumedLength` so the component doesn't claim trailing
122613
+ // content the tokenizer swallowed into the same html node.
122270
122614
  const positionEndingAtConsumed = (nodePosition, value, consumedLength) => {
122271
122615
  if (!nodePosition?.start)
122272
122616
  return nodePosition;
122273
122617
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, value.slice(0, consumedLength)) };
122274
122618
  };
122275
- /**
122276
- * Build a position ending right after the last occurrence of `closingTag` within
122277
- * this node's span in the original source. Used in the trailing-content path so
122278
- * the offset is computed against the real source bytes (including blockquote/list
122279
- * prefixes that were stripped from the html node's value).
122280
- */
122619
+ // Like `positionEndingAtConsumed`, but measures against the original source so
122620
+ // blockquote/list prefixes stripped from the html node's value are counted.
122281
122621
  const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) => {
122282
122622
  if (!nodePosition?.start || !nodePosition.end)
122283
122623
  return nodePosition;
@@ -122288,9 +122628,6 @@ const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) =>
122288
122628
  const consumed = nodeSource.slice(0, closingTagOffset + closingTag.length);
122289
122629
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, consumed) };
122290
122630
  };
122291
- /**
122292
- * Create an MdxJsxFlowElement node from component data.
122293
- */
122294
122631
  const createComponentNode = ({ tag, attributes, children, startPosition, endPosition }) => ({
122295
122632
  type: 'mdxJsxFlowElement',
122296
122633
  name: tag,
@@ -122343,8 +122680,7 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122343
122680
  if ('children' in node && Array.isArray(node.children)) {
122344
122681
  stack.push(node);
122345
122682
  }
122346
- // Only visit HTML nodes with an actual html tag,
122347
- // which means a potential unparsed MDX component
122683
+ // Only html nodes can be an unparsed MDX component.
122348
122684
  const value = node.value;
122349
122685
  if (node.type !== 'html' || typeof value !== 'string')
122350
122686
  return;
@@ -122353,32 +122689,25 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122353
122689
  if (!parsed)
122354
122690
  return;
122355
122691
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
122356
- // Offset of `trimmed` within the (possibly whitespace-padded) html node value,
122357
- // so consumed-length math maps back onto the node's real source offsets.
122692
+ // Offsets so consumed-length math maps back onto the node's real source.
122358
122693
  const leadingWhitespace = value.length - value.trimStart().length;
122359
- // Index right after the opening tag's `>` within `trimmed`.
122360
122694
  const openingTagEnd = trimmed.length - contentAfterTag.length;
122361
- // Skip tags that have dedicated transformers
122362
122695
  if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
122363
- return;
122696
+ return; // owned by dedicated transformers
122364
122697
  const isPascal = isPascalCase(tag);
122365
- // Lowercase inline tags (inside a paragraph) with `{…}` attributes are
122366
- // promoted to `mdxJsxTextElement` by mdxishInlineComponentBlocks. Skip
122367
- // them here so they stay as html for that pass; PascalCase components
122368
- // keep going through this transformer (they stay flow-level even when
122369
- // inline, which is how ReadMe's custom components are modeled).
122698
+ // Lowercase inline tags with `{…}` attributes belong to
122699
+ // mdxishInlineComponentBlocks; leave them as html for that pass. PascalCase
122700
+ // components stay flow-level even when inline (ReadMe's component model).
122370
122701
  if (!isPascal && parent.type === 'paragraph')
122371
122702
  return;
122372
- // Lowercase HTML tags are eligible when they (or a descendant tag in their
122373
- // content, e.g. a `.map()` body returning JSX with `key={i}`) carry a
122374
- // JSX-expression attribute. Without this, a wrapper `<div>` with only plain
122375
- // attributes (or none) swallows its whole nested block — including any
122376
- // `style={{...}}`/`.map()` JSX inside it — as literal HTML text, which
122377
- // rehype-raw's parse5 pass then garbles (it has no notion of JS expressions).
122378
- // Plain HTML with no expressions anywhere stays as an html node so
122379
- // rehype-raw handles it as normal.
122703
+ // A lowercase wrapper is only promoted when it (or a descendant) carries a
122704
+ // JSX expression or nests a component; otherwise it would swallow that inner
122705
+ // JSX/component as literal text that rehype-raw's parse5 pass can't handle.
122706
+ // Table-structural wrappers are excluded `mdxishTables` re-parses those.
122380
122707
  const hasNestedExpressionAttr = !selfClosing && NESTED_ATTR_EXPRESSION_RE.test(contentAfterTag);
122381
- if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr)
122708
+ const isTableStructuralTag = tag === 'table' || tableTags.has(tag);
122709
+ const hasNestedComponentTag = !selfClosing && !isTableStructuralTag && hasNestedGenericComponentTag(contentAfterTag);
122710
+ if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr && !hasNestedComponentTag)
122382
122711
  return;
122383
122712
  const closingTagStr = `</${tag}>`;
122384
122713
  // Case 1: Self-closing tag
@@ -122392,7 +122721,6 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122392
122721
  endPosition: positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd),
122393
122722
  });
122394
122723
  substituteNodeWithMdxNode(parent, index, componentNode);
122395
- // Check and parse if there's relevant content after the current closing tag
122396
122724
  const remainingContent = contentAfterTag.trim();
122397
122725
  if (remainingContent) {
122398
122726
  parseSibling(stack, parent, index, remainingContent, safeMode);
@@ -122401,17 +122729,14 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122401
122729
  }
122402
122730
  // Case 2: Self-contained block (closing tag in content)
122403
122731
  if (contentAfterTag.includes(closingTagStr)) {
122404
- // Find the first closing tag
122405
122732
  const closingTagIndex = contentAfterTag.lastIndexOf(closingTagStr);
122406
- // Pass raw (untrimmed) content so dedent in parseMdChildren can
122407
- // normalize indentation before trimming
122733
+ // Untrimmed so parseMdChildren can dedent before trimming.
122408
122734
  const componentInnerContent = contentAfterTag.substring(0, closingTagIndex);
122409
122735
  const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
122410
122736
  let parsedChildren = componentInnerContent.trim()
122411
122737
  ? parseMdChildren(componentInnerContent, safeMode)
122412
122738
  : [];
122413
- // Lowercase HTML tags are usually inline (e.g. <a>, <span>). Remark wraps
122414
- // bare text in a paragraph; unwrap when there's exactly one paragraph so
122739
+ // Lowercase tags are usually inline; unwrap a sole paragraph so their
122415
122740
  // phrasing content isn't spuriously block-wrapped.
122416
122741
  if (!isPascal && parsedChildren.length === 1 && parsedChildren[0].type === 'paragraph') {
122417
122742
  parsedChildren = parsedChildren[0].children;
@@ -122421,12 +122746,9 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122421
122746
  attributes,
122422
122747
  children: parsedChildren,
122423
122748
  startPosition: node.position,
122424
- // When trailing content follows the closing tag, compute the end position precisely
122425
- // within the html node's value so the component doesn't claim that content.
122426
- // Prefer source-based positioning when the original source is available: the html
122427
- // node's value has '> '/space prefixes stripped for blockquotes/list items, so
122428
- // positionEndingAtConsumed would undercount source offsets. When the entire node
122429
- // is consumed, use the original node position directly.
122749
+ // With trailing content, end precisely at the closing tag. Prefer source
122750
+ // offsets when available (the node's value strips blockquote/list
122751
+ // prefixes); otherwise fall back to the whole node position.
122430
122752
  endPosition: contentAfterClose
122431
122753
  ? source
122432
122754
  ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
@@ -122434,17 +122756,16 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122434
122756
  : node.position,
122435
122757
  });
122436
122758
  substituteNodeWithMdxNode(parent, index, componentNode);
122437
- // After the closing tag, there might be more content to be processed
122759
+ // Re-queue whichever side may hold further components.
122438
122760
  if (contentAfterClose) {
122439
122761
  parseSibling(stack, parent, index, contentAfterClose, safeMode);
122440
122762
  }
122441
122763
  else if (componentNode.children.length > 0) {
122442
- // The content inside the component block might contain new components to be processed
122443
122764
  stack.push(componentNode);
122444
122765
  }
122445
122766
  }
122446
122767
  };
122447
- // Process the nodes with the components depth-first to maintain the correct order of the nodes
122768
+ // Depth-first so nodes keep their source order.
122448
122769
  while (stack.length) {
122449
122770
  const parent = stack.pop();
122450
122771
  if (parent?.children) {
@@ -122765,8 +123086,6 @@ const evaluateExports = () => (tree, file) => {
122765
123086
  };
122766
123087
  /* harmony default export */ const evaluate_exports = (evaluateExports);
122767
123088
 
122768
- // EXTERNAL MODULE: ./node_modules/react-dom/server.node.js
122769
- var server_node = __webpack_require__(4362);
122770
123089
  ;// ./lib/utils/mdxish/mdxish-expression.ts
122771
123090
 
122772
123091
 
@@ -122812,26 +123131,185 @@ const evalExpression = (expression, scope) => {
122812
123131
  return evalJsxProgram(program, scope);
122813
123132
  };
122814
123133
 
122815
- ;// ./processor/transform/mdxish/evaluate-expressions.ts
123134
+ // EXTERNAL MODULE: ./node_modules/react-dom/server.node.js
123135
+ var server_node = __webpack_require__(4362);
123136
+ ;// ./processor/transform/mdxish/style-object-to-css.ts
123137
+
123138
+ /**
123139
+ * CSS properties React treats as unitless — a bare number stays as-is instead of
123140
+ * getting `px` appended. Mirrors React DOM's `isUnitlessNumber` whitelist.
123141
+ * @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/CSSProperty.js
123142
+ */
123143
+ const UNITLESS_CSS_PROPERTIES = new Set([
123144
+ 'animationIterationCount',
123145
+ 'aspectRatio',
123146
+ 'borderImageOutset',
123147
+ 'borderImageSlice',
123148
+ 'borderImageWidth',
123149
+ 'boxFlex',
123150
+ 'boxFlexGroup',
123151
+ 'boxOrdinalGroup',
123152
+ 'columnCount',
123153
+ 'columns',
123154
+ 'flex',
123155
+ 'flexGrow',
123156
+ 'flexPositive',
123157
+ 'flexShrink',
123158
+ 'flexNegative',
123159
+ 'flexOrder',
123160
+ 'gridArea',
123161
+ 'gridRow',
123162
+ 'gridRowEnd',
123163
+ 'gridRowSpan',
123164
+ 'gridRowStart',
123165
+ 'gridColumn',
123166
+ 'gridColumnEnd',
123167
+ 'gridColumnSpan',
123168
+ 'gridColumnStart',
123169
+ 'fontWeight',
123170
+ 'lineClamp',
123171
+ 'lineHeight',
123172
+ 'opacity',
123173
+ 'order',
123174
+ 'orphans',
123175
+ 'tabSize',
123176
+ 'widows',
123177
+ 'zIndex',
123178
+ 'zoom',
123179
+ 'fillOpacity',
123180
+ 'floodOpacity',
123181
+ 'stopOpacity',
123182
+ 'strokeDasharray',
123183
+ 'strokeDashoffset',
123184
+ 'strokeMiterlimit',
123185
+ 'strokeOpacity',
123186
+ 'strokeWidth',
123187
+ ]);
123188
+ /** Convert a camelCase (or CSS custom property) key into its kebab-case CSS name. */
123189
+ const cssPropertyName = (key) => (key.startsWith('--') ? key : key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`));
123190
+ /** React appends `px` to non-zero numeric values, except unitless and custom properties. */
123191
+ const cssPropertyValue = (key, value) => typeof value === 'number' && value !== 0 && !key.startsWith('--') && !UNITLESS_CSS_PROPERTIES.has(key)
123192
+ ? `${value}px`
123193
+ : `${value}`;
123194
+ /**
123195
+ * True for a value that should be serialized as a style object rather than passed through
123196
+ * as an already-CSS string. React elements are excluded since they're valid objects too.
123197
+ */
123198
+ const style_object_to_css_isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !external_react_default().isValidElement(value);
123199
+ /**
123200
+ * Serialize a React-style inline style object (`{ color: "red", fontSize: 12 }`) into a
123201
+ * CSS declaration string (`color:red;font-size:12px`), the shape hast/HTML expects for a
123202
+ * `style` attribute. Empty (`undefined`/`null`/`''`) entries are dropped.
123203
+ */
123204
+ const styleObjectToCssText = (style) => Object.entries(style)
123205
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
123206
+ .map(([key, value]) => `${cssPropertyName(key)}:${cssPropertyValue(key, value)}`)
123207
+ .join(';');
122816
123208
 
123209
+ ;// ./processor/transform/mdxish/react-element-to-hast.ts
122817
123210
 
122818
123211
 
122819
123212
 
122820
- /** True for an array containing at least one React element, e.g. the result of `.map()` returning JSX. */
122821
- const isRenderableElementArray = (value) => Array.isArray(value) && value.some((external_react_default()).isValidElement);
122822
- /** Given the type of the expression result, create the corresponding mdast node. */
122823
- const createEvaluatedNode = (result, position) => {
122824
- if (result === null || result === undefined) {
122825
- return { type: 'text', value: '', position };
123213
+ // React props that never map to an HTML/hast attribute.
123214
+ const RESERVED_PROPS = new Set(['children', 'key', 'ref']);
123215
+ /**
123216
+ * Translate React props into hast `properties`: reserved and function props (event handlers)
123217
+ * are dropped (known gap), `style` objects flatten to CSS text. Names stay as authored (`className`); the
123218
+ * hast HTML boundary maps them to `class`/`for` downstream.
123219
+ */
123220
+ function propsToHastProperties(props) {
123221
+ const properties = {};
123222
+ Object.entries(props).forEach(([key, value]) => {
123223
+ if (RESERVED_PROPS.has(key) || typeof value === 'function' || value === undefined)
123224
+ return;
123225
+ properties[key] = key === 'style' && style_object_to_css_isPlainObject(value) ? styleObjectToCssText(value) : value;
123226
+ });
123227
+ // Values are resolved React props, wider than hast's HTML-attribute `PropertyValue` union.
123228
+ return properties;
123229
+ }
123230
+ /**
123231
+ * Render an element with React's own renderer as a last resort, wrapped as a hast `raw` node —
123232
+ * the same node type markdown's literal HTML blocks produce, so it re-enters rehypeRaw's
123233
+ * parse5 pass normally. Used for element types this converter can't resolve on its own (wrapped
123234
+ * component types, or a function component that throws when called outside React, e.g. one
123235
+ * using hooks). It's not immune to the invalid-HTML-nesting this module otherwise avoids, but
123236
+ * that's a fair trade against silently dropping the content.
123237
+ */
123238
+ function renderFallbackHtml(element) {
123239
+ try {
123240
+ const rawNode = { type: 'raw', value: (0,server_node/* renderToStaticMarkup */.qV)(element) };
123241
+ return [rawNode];
122826
123242
  }
122827
- else if (external_react_default().isValidElement(result) || isRenderableElementArray(result)) {
122828
- // Convert react elements (or arrays of them, e.g. `.map()` returning JSX) to their HTML
122829
- // representation. This must come before the object check as both are a subset of it.
122830
- return { type: 'html', value: (0,server_node/* renderToStaticMarkup */.qV)(external_react_default().createElement((external_react_default()).Fragment, null, result)), position };
123243
+ catch {
123244
+ return [];
122831
123245
  }
122832
- else if (typeof result === 'object') {
122833
- return { type: 'text', value: JSON.stringify(result), position };
123246
+ }
123247
+ /**
123248
+ * Convert a React element tree into hast. Emitting `mdx-jsx` nodes (rehypeRaw passthrough, later normalized to `element`)
123249
+ * preserves nesting that is valid JSX but not valid HTML, which parse5 would otherwise
123250
+ * restructure — e.g. an `<a>` wrapping another `<a>`.
123251
+ * Recursively converts the tree into an array of hast nodes, dealing with arrays and null/undefined/boolean values.
123252
+ */
123253
+ function reactElementToHast(node) {
123254
+ if (Array.isArray(node))
123255
+ return node.flatMap(reactElementToHast);
123256
+ if (node === null || node === undefined || typeof node === 'boolean')
123257
+ return [];
123258
+ if (typeof node === 'string' || typeof node === 'number') {
123259
+ const textNode = { type: 'text', value: String(node) };
123260
+ return [textNode];
122834
123261
  }
123262
+ if (!external_react_default().isValidElement(node))
123263
+ return [];
123264
+ const { type, props } = node;
123265
+ // Fragments contribute their children with no wrapper element.
123266
+ if (type === (external_react_default()).Fragment)
123267
+ return reactElementToHast(props.children);
123268
+ // Resolve function components to their rendered output so the tree is plain intrinsic
123269
+ // elements. If invoking it directly throws (e.g. it uses hooks, which need React's own
123270
+ // render context), fall back to React's renderer for just this subtree.
123271
+ if (typeof type === 'function') {
123272
+ try {
123273
+ return reactElementToHast(type(props));
123274
+ }
123275
+ catch {
123276
+ return renderFallbackHtml(node);
123277
+ }
123278
+ }
123279
+ // Non-intrinsic, non-callable element types — `React.memo`, `React.forwardRef`,
123280
+ // `Context.Provider`/`Consumer`, `React.lazy` — have no `type` we can resolve ourselves.
123281
+ if (typeof type !== 'string')
123282
+ return renderFallbackHtml(node);
123283
+ const mdxJsxNode = {
123284
+ type: 'mdx-jsx',
123285
+ tagName: type,
123286
+ properties: propsToHastProperties(props),
123287
+ children: reactElementToHast(props.children),
123288
+ };
123289
+ return [mdxJsxNode];
123290
+ }
123291
+
123292
+ ;// ./processor/transform/mdxish/evaluate-expressions.ts
123293
+
123294
+
123295
+
123296
+
123297
+ /**
123298
+ * We divide the result of an expression into two categories:
123299
+ * 1. Renderable values: HTML, JSX, e.g. .map() returning JSX
123300
+ * 2. Non-renderable values: a string, number, or object, regular JS values
123301
+ */
123302
+ const isRenderable = (value) => {
123303
+ if (external_react_default().isValidElement(value))
123304
+ return true;
123305
+ return Array.isArray(value) && value.some(isRenderable);
123306
+ };
123307
+ /** Turn a non-renderable evaluation result into a text node. */
123308
+ const createTextNode = (result, position) => {
123309
+ if (result === null || result === undefined)
123310
+ return { type: 'text', value: '', position };
123311
+ if (typeof result === 'object')
123312
+ return { type: 'text', value: JSON.stringify(result), position };
122835
123313
  return { type: 'text', value: String(result), position };
122836
123314
  };
122837
123315
  /**
@@ -122847,13 +123325,23 @@ const evaluateExpressions = () => (tree, file) => {
122847
123325
  visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => {
122848
123326
  if (!parent || index === null || index === undefined)
122849
123327
  return;
122850
- const { value, position } = node;
123328
+ const expressionNode = node;
123329
+ const { value, position } = expressionNode;
122851
123330
  const expression = value?.trim();
122852
123331
  if (!expression)
122853
123332
  return;
122854
123333
  try {
122855
123334
  const result = evalExpression(expression, scope);
122856
- parent.children.splice(index, 1, createEvaluatedNode(result, position));
123335
+ if (isRenderable(result)) {
123336
+ // Stash hast built straight from the React tree; `mdxExpressionHandler` emits it and it
123337
+ // passes through rehypeRaw/parse5 step later in the pipeline. This ensures that the
123338
+ // expression result is not parsed by parse5 and fragmenting the nesting that is valid JSX
123339
+ // but invalid HTML — e.g. an `<a>` wrapping `<a>`.
123340
+ expressionNode.data = { ...expressionNode.data, hChildren: reactElementToHast(result) };
123341
+ }
123342
+ else {
123343
+ parent.children.splice(index, 1, createTextNode(result, position));
123344
+ }
122857
123345
  }
122858
123346
  catch (_error) {
122859
123347
  // Evaluation failed — fall back to literal `{...}` text. The expression
@@ -124764,79 +125252,6 @@ function removeJSXComments(content) {
124764
125252
  return content.replace(JSX_COMMENT_REGEX, '');
124765
125253
  }
124766
125254
 
124767
- ;// ./processor/transform/mdxish/style-object-to-css.ts
124768
-
124769
- /**
124770
- * CSS properties React treats as unitless — a bare number stays as-is instead of
124771
- * getting `px` appended. Mirrors React DOM's `isUnitlessNumber` whitelist.
124772
- * @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/CSSProperty.js
124773
- */
124774
- const UNITLESS_CSS_PROPERTIES = new Set([
124775
- 'animationIterationCount',
124776
- 'aspectRatio',
124777
- 'borderImageOutset',
124778
- 'borderImageSlice',
124779
- 'borderImageWidth',
124780
- 'boxFlex',
124781
- 'boxFlexGroup',
124782
- 'boxOrdinalGroup',
124783
- 'columnCount',
124784
- 'columns',
124785
- 'flex',
124786
- 'flexGrow',
124787
- 'flexPositive',
124788
- 'flexShrink',
124789
- 'flexNegative',
124790
- 'flexOrder',
124791
- 'gridArea',
124792
- 'gridRow',
124793
- 'gridRowEnd',
124794
- 'gridRowSpan',
124795
- 'gridRowStart',
124796
- 'gridColumn',
124797
- 'gridColumnEnd',
124798
- 'gridColumnSpan',
124799
- 'gridColumnStart',
124800
- 'fontWeight',
124801
- 'lineClamp',
124802
- 'lineHeight',
124803
- 'opacity',
124804
- 'order',
124805
- 'orphans',
124806
- 'tabSize',
124807
- 'widows',
124808
- 'zIndex',
124809
- 'zoom',
124810
- 'fillOpacity',
124811
- 'floodOpacity',
124812
- 'stopOpacity',
124813
- 'strokeDasharray',
124814
- 'strokeDashoffset',
124815
- 'strokeMiterlimit',
124816
- 'strokeOpacity',
124817
- 'strokeWidth',
124818
- ]);
124819
- /** Convert a camelCase (or CSS custom property) key into its kebab-case CSS name. */
124820
- const cssPropertyName = (key) => (key.startsWith('--') ? key : key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`));
124821
- /** React appends `px` to non-zero numeric values, except unitless and custom properties. */
124822
- const cssPropertyValue = (key, value) => typeof value === 'number' && value !== 0 && !key.startsWith('--') && !UNITLESS_CSS_PROPERTIES.has(key)
124823
- ? `${value}px`
124824
- : `${value}`;
124825
- /**
124826
- * True for a value that should be serialized as a style object rather than passed through
124827
- * as an already-CSS string. React elements are excluded since they're valid objects too.
124828
- */
124829
- const style_object_to_css_isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !external_react_default().isValidElement(value);
124830
- /**
124831
- * Serialize a React-style inline style object (`{ color: "red", fontSize: 12 }`) into a
124832
- * CSS declaration string (`color:red;font-size:12px`), the shape hast/HTML expects for a
124833
- * `style` attribute. Empty (`undefined`/`null`/`''`) entries are dropped.
124834
- */
124835
- const styleObjectToCssText = (style) => Object.entries(style)
124836
- .filter(([, value]) => value !== undefined && value !== null && value !== '')
124837
- .map(([key, value]) => `${cssPropertyName(key)}:${cssPropertyValue(key, value)}`)
124838
- .join(';');
124839
-
124840
125255
  ;// ./processor/transform/mdxish/resolve-deferred-attribute-expression-props.ts
124841
125256
 
124842
125257
 
@@ -125183,73 +125598,6 @@ function normalizeTableSeparator(content) {
125183
125598
  }
125184
125599
  /* harmony default export */ const normalize_table_separator = ((/* unused pure expression or super */ null && (normalizeTableSeparator)));
125185
125600
 
125186
- ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
125187
-
125188
-
125189
-
125190
- const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
125191
- const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
125192
- // Tags whose contents must be preserved as is, inserting a blank line after the
125193
- // opener corrupts the payload.
125194
- // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
125195
- const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
125196
- // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
125197
- const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
125198
- open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
125199
- close: new RegExp(`</${tag}(?=[\\s>])[^>]*>`, 'gi'),
125200
- }));
125201
- function isLineHtml(line) {
125202
- return STANDALONE_HTML_LINE_REGEX.test(line) || HTML_LINE_WITH_CONTENT_REGEX.test(line);
125203
- }
125204
- // True if any RAW_CONTENT_TAGS opener on this line is not closed on the same line.
125205
- function hasUnclosedRawContentOpener(line) {
125206
- return RAW_CONTENT_TAG_MATCHERS.some(({ open, close }) => {
125207
- const opens = (line.match(open) ?? []).length;
125208
- const closes = (line.match(close) ?? []).length;
125209
- return opens > closes;
125210
- });
125211
- }
125212
- /**
125213
- * Preprocessor to terminate HTML flow blocks.
125214
- *
125215
- * In CommonMark, HTML blocks (types 6 and 7) only terminate on a blank line.
125216
- * Without one, any content on the next line is consumed as part of the HTML block
125217
- * and never parsed as its own construct. For example, a `[block:callout]` immediately
125218
- * following `<div><p></p></div>` gets swallowed into the HTML flow token.
125219
- *
125220
- * @link https://spec.commonmark.org/0.29/#html-blocks
125221
- *
125222
- * This preprocessor inserts a blank line after standalone HTML lines when the next
125223
- * line is non-blank and not an HTML construct, ensuring micromark's HTML flow
125224
- * tokenizer terminates and subsequent content is parsed independently.
125225
- *
125226
- * Conditions:
125227
- * 1. Only non-indented lines with lowercase tag names are considered. Uppercase tags
125228
- * (e.g. `<Table>`, `<MyComponent>`) are JSX custom components and don't trigger
125229
- * CommonMark HTML blocks.
125230
- * 2. Lines inside protected blocks (e.g. fenced code) are left untouched.
125231
- * 3. Lines with an unclosed RAW_CONTENT_TAGS opener are exempted.
125232
- */
125233
- function terminateHtmlFlowBlocks(content) {
125234
- const { protectedContent, protectedCode } = protectCodeBlocks(content);
125235
- const lines = protectedContent.split('\n');
125236
- const result = [];
125237
- for (let i = 0; i < lines.length; i += 1) {
125238
- result.push(lines[i]);
125239
- // Skip blank & indented lines
125240
- if (i >= lines.length - 1 || lines[i + 1].trim().length === 0 || lines[i + 1].startsWith(' ') || lines[i + 1].startsWith('\t')) {
125241
- // eslint-disable-next-line no-continue
125242
- continue;
125243
- }
125244
- const isCurrentLineHtml = isLineHtml(lines[i]);
125245
- const isNextLineHtml = isLineHtml(lines[i + 1]);
125246
- if (isCurrentLineHtml && !isNextLineHtml && !hasUnclosedRawContentOpener(lines[i])) {
125247
- result.push('');
125248
- }
125249
- }
125250
- return restoreCodeBlocks(result.join('\n'), protectedCode);
125251
- }
125252
-
125253
125601
  ;// ./processor/transform/mdxish/variables-code.ts
125254
125602
 
125255
125603
 
@@ -126117,6 +126465,12 @@ function mdxishMdastToMd(mdast) {
126117
126465
  .use(remarkStringify, {
126118
126466
  bullet: '-',
126119
126467
  emphasis: '_',
126468
+ // Escape literal braces in text so they don't parse as (often
126469
+ // unterminated) MDX expressions on the next round trip.
126470
+ unsafe: [
126471
+ { character: '{', inConstruct: 'phrasing' },
126472
+ { character: '}', inConstruct: 'phrasing' },
126473
+ ],
126120
126474
  });
126121
126475
  return processor.stringify(processor.runSync(mdast));
126122
126476
  }