@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.
@@ -68490,6 +68490,21 @@ function evaluate(source, scope = {}) {
68490
68490
  // eslint-disable-next-line no-new-func
68491
68491
  return new Function(...names, `return (${source})`)(...values);
68492
68492
  }
68493
+ /**
68494
+ * Advance a `Point` by the `consumed` substring that follows it, returning the
68495
+ * point at the consumed string's end. Lets split-out sub-nodes carry accurate
68496
+ * document positions so downstream offset shifting stays correct.
68497
+ */
68498
+ const pointAfter = (start, consumed) => {
68499
+ const newlineIndex = consumed.lastIndexOf('\n');
68500
+ const newlineCount = newlineIndex === -1 ? 0 : consumed.split('\n').length - 1;
68501
+ return {
68502
+ line: start.line + newlineCount,
68503
+ // Same line → advance the base column; a later line → column is the run since its newline.
68504
+ column: newlineCount === 0 ? start.column + consumed.length : consumed.length - newlineIndex,
68505
+ offset: (start.offset ?? 0) + consumed.length,
68506
+ };
68507
+ };
68493
68508
  /**
68494
68509
  * Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
68495
68510
  * This currently sets all the values to a string since we process/compile the MDX on the fly
@@ -92168,10 +92183,10 @@ const Tabs = ({ children }) => {
92168
92183
  const tabs = external_react_default().Children.toArray(children);
92169
92184
  return (external_react_default().createElement("div", { className: "TabGroup" },
92170
92185
  external_react_default().createElement("header", null,
92171
- 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) },
92186
+ 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) },
92172
92187
  tab.props.icon && (external_react_default().createElement(components_Icon, { className: "TabGroup-icon", icon: tab.props.icon, iconColor: tab.props.iconColor })),
92173
92188
  tab.props.title))))),
92174
- external_react_default().createElement("section", null, tabs[activeTab])));
92189
+ external_react_default().createElement("section", null, tabs.map((tab, index) => (external_react_default().createElement("div", { key: tab.key, hidden: index !== activeTab }, tab))))));
92175
92190
  };
92176
92191
  /* harmony default export */ const components_Tabs = (Tabs);
92177
92192
 
@@ -95970,6 +95985,108 @@ const applyInserts = (html, inserts) => {
95970
95985
  return { value: out + html.slice(cursor), inserts: sorted };
95971
95986
  };
95972
95987
 
95988
+ ;// ./processor/transform/mdxish/tables/escape-crossing-emphasis.ts
95989
+
95990
+
95991
+
95992
+ // Maximal run of a single emphasis delimiter (`_`, `*`, `**`, `***`, …).
95993
+ const EMPHASIS_RUN_RE = /([*_])\1*/g;
95994
+ // String bounds (undefined) count as whitespace, not punctuation, per the
95995
+ // CommonMark flanking definition.
95996
+ const escape_crossing_emphasis_isWhitespace = (ch) => ch === undefined || unicodeWhitespace(ch.charCodeAt(0));
95997
+ const isPunctuation = (ch) => ch !== undefined && unicodePunctuation(ch.charCodeAt(0));
95998
+ /**
95999
+ * Whether a delimiter run can open and/or close emphasis, per CommonMark's
96000
+ * left/right-flanking rules. The extra `_` conditions forbid intraword
96001
+ * emphasis, which keeps `snake_case` from being treated as a delimiter.
96002
+ */
96003
+ const analyzeFlanking = (source, char, start, end) => {
96004
+ const before = source[start - 1];
96005
+ const after = source[end];
96006
+ const leftFlanking = !escape_crossing_emphasis_isWhitespace(after) && (!isPunctuation(after) || escape_crossing_emphasis_isWhitespace(before) || isPunctuation(before));
96007
+ const rightFlanking = !escape_crossing_emphasis_isWhitespace(before) && (!isPunctuation(before) || escape_crossing_emphasis_isWhitespace(after) || isPunctuation(after));
96008
+ if (char === '_') {
96009
+ return {
96010
+ canOpen: leftFlanking && (!rightFlanking || isPunctuation(before)),
96011
+ canClose: rightFlanking && (!leftFlanking || isPunctuation(after)),
96012
+ };
96013
+ }
96014
+ return { canOpen: leftFlanking, canClose: rightFlanking };
96015
+ };
96016
+ /**
96017
+ * Collect HTML tag boundaries (via htmlparser2) and emphasis delimiter runs
96018
+ * (via regex over the masked source, so code spans and escaped `<` are skipped)
96019
+ * into one list ordered by position. At a shared offset a tag opener sorts
96020
+ * before a closer, so a self-closing tag brackets rather than swallows a run.
96021
+ */
96022
+ const collectEvents = (html) => {
96023
+ const masked = maskNonTagRegions(html);
96024
+ const events = [];
96025
+ walkTags(html, {
96026
+ onOpen: ({ start }) => events.push({ kind: 'tagOpen', offset: start }),
96027
+ onClose: ({ start }) => events.push({ kind: 'tagClose', offset: start }),
96028
+ });
96029
+ EMPHASIS_RUN_RE.lastIndex = 0;
96030
+ let match;
96031
+ while ((match = EMPHASIS_RUN_RE.exec(masked)) !== null) {
96032
+ const [run, char] = match;
96033
+ const start = match.index;
96034
+ // eslint-disable-next-line no-continue
96035
+ if (html[start - 1] === '\\')
96036
+ continue; // already escaped
96037
+ events.push({ kind: 'emphasis', offset: start, char, length: run.length, flanking: analyzeFlanking(html, char, start, start + run.length) });
96038
+ }
96039
+ return events.sort((a, b) => a.offset - b.offset || (a.kind === 'tagClose' ? 1 : 0) - (b.kind === 'tagClose' ? 1 : 0));
96040
+ };
96041
+ /**
96042
+ * mdxjs rejects a table when a markdown emphasis run opens at one HTML
96043
+ * tag-nesting depth and closes at another — e.g. `_<ul><li>text_</li></ul>`,
96044
+ * where the `_` opens outside the list but closes inside a `<li>`. It throws
96045
+ * "Expected a closing tag for `<li>` before the end of `emphasis`" and the
96046
+ * whole `<Table>` fails to parse.
96047
+ *
96048
+ * We walk tags and emphasis together, tracking tag depth and a stack of open
96049
+ * emphasis. A delimiter only closes an opener at the same depth; any emphasis
96050
+ * still open when its enclosing tag closes (or at end of input), and any closer
96051
+ * with no same-depth opener, is escaped so mdxjs treats it as literal text.
96052
+ * Scoped to the malformed-retry path.
96053
+ */
96054
+ const escapeCrossingEmphasis = (html) => {
96055
+ const open = [];
96056
+ const orphans = [];
96057
+ let depth = 0;
96058
+ collectEvents(html).forEach(event => {
96059
+ if (event.kind === 'tagOpen') {
96060
+ depth += 1;
96061
+ return;
96062
+ }
96063
+ if (event.kind === 'tagClose') {
96064
+ depth -= 1;
96065
+ // Emphasis opened deeper than the surviving depth was inside the tag that
96066
+ // just closed, so it crosses the boundary.
96067
+ while (open.length > 0 && open[open.length - 1].depth > depth) {
96068
+ const crossed = open.pop();
96069
+ if (crossed)
96070
+ orphans.push(crossed);
96071
+ }
96072
+ return;
96073
+ }
96074
+ const top = open[open.length - 1];
96075
+ if (event.flanking.canClose && top?.char === event.char && top.depth === depth) {
96076
+ open.pop();
96077
+ }
96078
+ else if (event.flanking.canOpen) {
96079
+ open.push({ char: event.char, depth, offset: event.offset, length: event.length });
96080
+ }
96081
+ else if (event.flanking.canClose) {
96082
+ orphans.push({ offset: event.offset, length: event.length });
96083
+ }
96084
+ });
96085
+ orphans.push(...open); // anything still open never closed
96086
+ const inserts = orphans.flatMap(({ offset, length }) => Array.from({ length }, (_unused, i) => ({ offset: offset + i, text: '\\' })));
96087
+ return applyInserts(html, inserts);
96088
+ };
96089
+
95973
96090
  ;// ./processor/transform/mdxish/tables/escape-stray-less-than.ts
95974
96091
 
95975
96092
 
@@ -96122,6 +96239,21 @@ const buildOffsetMapper = (inserts) => {
96122
96239
  return repaired - shift;
96123
96240
  };
96124
96241
  };
96242
+ /**
96243
+ * Compose the per-repair offset mappers into one that maps an offset in the
96244
+ * fully-repaired string back to the original. `layers` are in application
96245
+ * order; each maps its own output space to its input space, so we apply them
96246
+ * last-repair-first to peel every edit back to the original coordinates.
96247
+ */
96248
+ const composeOffsetMapper = (layers) => {
96249
+ const mappers = layers.map(buildOffsetMapper);
96250
+ return (repaired) => {
96251
+ let offset = repaired;
96252
+ for (let i = mappers.length - 1; i >= 0; i -= 1)
96253
+ offset = mappers[i](offset);
96254
+ return offset;
96255
+ };
96256
+ };
96125
96257
  /**
96126
96258
  * Map an offset in `source` to its 1-based `{ line, column }`. `lineStarts`
96127
96259
  * is the precomputed array of offsets where each line begins.
@@ -96149,14 +96281,11 @@ const computeLineStarts = (source) => {
96149
96281
  };
96150
96282
  /**
96151
96283
  * Walk `tree`, translating every node's position from the repaired source's
96152
- * coordinate space back to the original source. Offsets are remapped via the
96153
- * insert list; line/column are recomputed from the original source so they
96154
- * remain accurate even if repairs introduced newlines.
96284
+ * coordinate space back to the original source via `mapOffset`. Line/column
96285
+ * are recomputed from the original source so they remain accurate even if
96286
+ * repairs introduced newlines.
96155
96287
  */
96156
- const remapPositionsToOriginal = (tree, originalSource, inserts) => {
96157
- if (inserts.length === 0)
96158
- return;
96159
- const mapOffset = buildOffsetMapper(inserts);
96288
+ const remapWithMapper = (tree, originalSource, mapOffset) => {
96160
96289
  const lineStarts = computeLineStarts(originalSource);
96161
96290
  lib_visit(tree, child => {
96162
96291
  if (child.position?.start) {
@@ -96175,6 +96304,15 @@ const remapPositionsToOriginal = (tree, originalSource, inserts) => {
96175
96304
  }
96176
96305
  });
96177
96306
  };
96307
+ /**
96308
+ * Remap positions produced after a chain of repairs (each applied to the prior
96309
+ * one's output) back to the original source coordinates.
96310
+ */
96311
+ const remapPositionsThroughLayers = (tree, originalSource, layers) => {
96312
+ if (layers.length === 0)
96313
+ return;
96314
+ remapWithMapper(tree, originalSource, composeOffsetMapper(layers));
96315
+ };
96178
96316
 
96179
96317
  ;// ./processor/transform/mdxish/tables/repair-expression-escapes.ts
96180
96318
 
@@ -96474,6 +96612,83 @@ const repairUnclosedTags = (html) => {
96474
96612
  return applyInserts(html, inserts);
96475
96613
  };
96476
96614
 
96615
+ ;// ./processor/transform/mdxish/tables/split-nested-tables.ts
96616
+
96617
+
96618
+ const TOP_LEVEL_TABLE_TAG_RE = /^<(?:table|Table)(?=[\s/>])/;
96619
+ /**
96620
+ * Find every balanced, depth-matched `<table>…</table>` range in an html string.
96621
+ * Uses `walkTags` so `<table>`s inside code spans / fenced blocks (masked away)
96622
+ * are never matched.
96623
+ *
96624
+ * Note: An implicitly-closed table (no `</table>`, so htmlparser2 synthesizes the
96625
+ * close at a later tag) is skipped: splitting there would swallow whatever
96626
+ * followed the table into the fragment and drop it, so we leave such a node
96627
+ * whole for downstream raw-HTML handling instead.
96628
+ */
96629
+ const findTableRanges = (html) => {
96630
+ const ranges = [];
96631
+ let depth = 0;
96632
+ let start = 0;
96633
+ walkTags(html, {
96634
+ onOpen: ({ name, start: openStart, isStrayCloser }) => {
96635
+ if (name.toLowerCase() !== 'table' || isStrayCloser)
96636
+ return;
96637
+ if (depth === 0)
96638
+ start = openStart;
96639
+ depth += 1;
96640
+ },
96641
+ onClose: ({ name, end, implicit }) => {
96642
+ if (implicit || name.toLowerCase() !== 'table' || depth === 0)
96643
+ return;
96644
+ depth -= 1;
96645
+ if (depth === 0)
96646
+ ranges.push({ start, end });
96647
+ },
96648
+ });
96649
+ return ranges;
96650
+ };
96651
+ /**
96652
+ * Given an HTML node that might contain table sequences inside it, split
96653
+ * the node based on the table boundaries so they become a top level node.
96654
+ *
96655
+ * The surrounding raw HTML (the wrapper's open/close tags) would be re-nested
96656
+ * around the parsed tables by rehype-raw.
96657
+ *
96658
+ * Returns null when there is no wrapped table to extract.
96659
+ */
96660
+ const splitHtmlWithNestedTables = (node) => {
96661
+ const { value } = node;
96662
+ // This is a top-level table, so we don't need to split it
96663
+ if (TOP_LEVEL_TABLE_TAG_RE.test(value))
96664
+ return null;
96665
+ // No table text anywhere in the value → skip the htmlparser2 walk entirely.
96666
+ if (!/<\/?table/i.test(value))
96667
+ return null;
96668
+ const ranges = findTableRanges(value);
96669
+ if (ranges.length === 0)
96670
+ return null;
96671
+ const base = node.position?.start;
96672
+ const sliceToHtml = (from, to) => ({
96673
+ type: 'html',
96674
+ value: value.slice(from, to),
96675
+ ...(base && {
96676
+ position: { start: pointAfter(base, value.slice(0, from)), end: pointAfter(base, value.slice(0, to)) },
96677
+ }),
96678
+ });
96679
+ const parts = [];
96680
+ let cursor = 0;
96681
+ ranges.forEach(({ start, end }) => {
96682
+ if (start > cursor)
96683
+ parts.push(sliceToHtml(cursor, start));
96684
+ parts.push(sliceToHtml(start, end)); // starts with `<table` → picked up by the main pass
96685
+ cursor = end;
96686
+ });
96687
+ if (cursor < value.length)
96688
+ parts.push(sliceToHtml(cursor, value.length));
96689
+ return parts;
96690
+ };
96691
+
96477
96692
  ;// ./processor/transform/mdxish/tables/mdxish-tables.ts
96478
96693
 
96479
96694
 
@@ -96495,6 +96710,8 @@ const repairUnclosedTags = (html) => {
96495
96710
 
96496
96711
 
96497
96712
 
96713
+
96714
+
96498
96715
 
96499
96716
 
96500
96717
 
@@ -96524,6 +96741,21 @@ const buildTableNodeProcessor = (withMdx) => lib_unified()
96524
96741
  .use(lib_remarkGfm);
96525
96742
  const tableNodeProcessor = buildTableNodeProcessor(true);
96526
96743
  const fallbackTableNodeProcessor = buildTableNodeProcessor(false);
96744
+ // Targeted repairs for tables mdxjs rejects, tried cumulatively (each runs on
96745
+ // the prior's output) since one table can carry independent per-cell defects.
96746
+ // Each was added after seeing real customer content fail to parse:
96747
+ // - repairUnclosedTags: unclosed/orphan HTML tags
96748
+ // - normalizeTagSpacing: a line mixing text and an opening tag
96749
+ // - repairExpressionEscapes: backslash escapes inside a `{…}` expression
96750
+ // - escapeStrayLessThan: a `<` that doesn't begin a valid tag (`word <`)
96751
+ // - escapeCrossingEmphasis: emphasis opening/closing at different tag depths
96752
+ const tableRepairs = [
96753
+ repairUnclosedTags,
96754
+ normalizeTagSpacing,
96755
+ repairExpressionEscapes,
96756
+ escapeStrayLessThan,
96757
+ escapeCrossingEmphasis,
96758
+ ];
96527
96759
  /**
96528
96760
  * Parse the HTML node that contains the full table substring
96529
96761
  * into the table parts (headers, rows, cells).
@@ -96539,10 +96771,10 @@ const parseTableNode = (processor, node, repair) => {
96539
96771
  return undefined;
96540
96772
  }
96541
96773
  // If `node.value` was repaired before parsing, first remap positions back to
96542
- // the original (unrepaired) coordinates via the insert list — otherwise the
96774
+ // the original (unrepaired) coordinates via the insert layers — otherwise the
96543
96775
  // shift would land on synthetic characters and be inaccurate
96544
96776
  if (repair) {
96545
- remapPositionsToOriginal(parsed, repair.originalSource, repair.inserts);
96777
+ remapPositionsThroughLayers(parsed, repair.originalSource, repair.layers);
96546
96778
  }
96547
96779
  // The subparser produces positions relative to `node.value`; shift them by
96548
96780
  // the outer node's offset/line so consumers can slice the full source.
@@ -96639,9 +96871,7 @@ const processTableNode = (node, index, parent, documentPosition) => {
96639
96871
  lib_visit(node, utils_isMDXElement, (child) => {
96640
96872
  if (child.name === 'thead')
96641
96873
  hasThead = true;
96642
- if (tableTags.has(child.name) &&
96643
- Array.isArray(child.attributes) &&
96644
- child.attributes.length > 0) {
96874
+ if (tableTags.has(child.name) && Array.isArray(child.attributes) && child.attributes.length > 0) {
96645
96875
  hasStructuralAttributes = true;
96646
96876
  }
96647
96877
  });
@@ -96752,6 +96982,26 @@ const processTableNode = (node, index, parent, documentPosition) => {
96752
96982
  };
96753
96983
  parent.children[index] = mdNode;
96754
96984
  };
96985
+ /**
96986
+ * Apply `tableRepairs` cumulatively, re-parsing after every change and stopping
96987
+ * once the accumulated result parses. Each layer's inserts are relative to the
96988
+ * string that repair received, so they stay ordered for position remapping.
96989
+ */
96990
+ const repairAndReparse = (node) => {
96991
+ let repairedValue = node.value;
96992
+ const layers = [];
96993
+ let parsed;
96994
+ tableRepairs.some(repair => {
96995
+ const { value, inserts } = repair(repairedValue);
96996
+ if (value === repairedValue)
96997
+ return false;
96998
+ repairedValue = value;
96999
+ layers.push(inserts);
97000
+ parsed = parseTableNode(tableNodeProcessor, { ...node, value: repairedValue }, { layers, originalSource: node.value });
97001
+ return Boolean(parsed);
97002
+ });
97003
+ return parsed;
97004
+ };
96755
97005
  /**
96756
97006
  * Converts JSX Table elements to markdown table nodes and re-parses markdown in cells.
96757
97007
  *
@@ -96764,42 +97014,30 @@ const processTableNode = (node, index, parent, documentPosition) => {
96764
97014
  * is kept as a JSX <Table> element so that remarkRehype can properly handle the flow content.
96765
97015
  */
96766
97016
  const mdxish_tables_mdxishTables = () => tree => {
97017
+ // Pre-pass: lift `<table>`s wrapped in a raw HTML block out into their own
97018
+ // html nodes so the main pass below treats them like top-level tables.
97019
+ lib_visit(tree, 'html', (_node, index, parent) => {
97020
+ const node = _node;
97021
+ if (typeof index !== 'number' || !parent || !('children' in parent))
97022
+ return;
97023
+ const parts = splitHtmlWithNestedTables(node);
97024
+ if (!parts)
97025
+ return;
97026
+ // The inserted parts can't re-trigger a split (table parts start with
97027
+ // `<table`; the wrapper slices hold no table), so plain in-place splicing
97028
+ // visits each once without looping.
97029
+ parent.children.splice(index, 1, ...parts);
97030
+ });
96767
97031
  lib_visit(tree, 'html', (_node, index, parent) => {
96768
97032
  const node = _node;
96769
97033
  if (typeof index !== 'number' || !parent || !('children' in parent))
96770
97034
  return;
96771
97035
  if (!node.value.startsWith('<Table') && !node.value.startsWith('<table'))
96772
97036
  return;
96773
- // Main logic to transform table node to its parts
96774
97037
  // Because the processor uses remarkMdx, it is stricter in what it accepts
96775
- // and only accepts valid MDX syntax. in the table node.
96776
- // To get around that, we have some fallback logics after trying to repair the table content.
96777
- let parsed = parseTableNode(tableNodeProcessor, node);
96778
- if (!parsed) {
96779
- // Try a sequence of targeted repairs and re-parse
96780
- // after each, stopping at the first that yields a parseable tree:
96781
- // - repairUnclosedTags: unclosed/orphan HTML tags
96782
- // - normalizeTagSpacing: a line mixing text and an opening tag
96783
- // (e.g. `text <div> \n <div> text`)
96784
- // - repairExpressionEscapes: backslash escapes inside a `{…}` expression
96785
- // - escapeStrayLessThan: a `<` that doesn't begin a valid tag
96786
- // (e.g. `word <`, `a <1>`)
96787
- // These repairs are created after seeing real customer content that has failed to parse
96788
- const repairs = [
96789
- repairUnclosedTags,
96790
- normalizeTagSpacing,
96791
- repairExpressionEscapes,
96792
- escapeStrayLessThan,
96793
- ];
96794
- // Stops at the first repair that yields a parseable tree
96795
- repairs.some(repair => {
96796
- const { value, inserts } = repair(node.value);
96797
- if (value !== node.value) {
96798
- parsed = parseTableNode(tableNodeProcessor, { ...node, value }, { inserts, originalSource: node.value });
96799
- }
96800
- return Boolean(parsed);
96801
- });
96802
- }
97038
+ // and only accepts valid MDX syntax in the table node. To get around that,
97039
+ // fall back to the cumulative repairs when the first parse fails.
97040
+ const parsed = parseTableNode(tableNodeProcessor, node) ?? repairAndReparse(node);
96803
97041
  if (parsed) {
96804
97042
  // If the table is parsed successfully, we can now process it further
96805
97043
  // to build on the markdown / JSX table
@@ -117259,6 +117497,19 @@ const compile_list_item_listItem = (node, parent, state, info) => {
117259
117497
  const plain_plain = (node) => node.value;
117260
117498
  /* harmony default export */ const compile_plain = (plain_plain);
117261
117499
 
117500
+ ;// ./processor/compile/text.ts
117501
+
117502
+ // A `_` flanked by word characters can never open or close emphasis under
117503
+ // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
117504
+ // intraword underscores is unnecessary and only produces noisy `\_` diffs.
117505
+ // Uses Unicode letter/number classes so non-ASCII words (e.g. `é_pay`) match.
117506
+ const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
117507
+ const compile_text_text = (node, parent, state, info) => {
117508
+ const serialized = handle.text(node, parent, state, info);
117509
+ return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
117510
+ };
117511
+ /* harmony default export */ const compile_text = (compile_text_text);
117512
+
117262
117513
  ;// ./processor/compile/index.ts
117263
117514
 
117264
117515
 
@@ -117271,11 +117522,11 @@ const plain_plain = (node) => node.value;
117271
117522
 
117272
117523
 
117273
117524
 
117525
+
117274
117526
  function compilers(mdxish = false) {
117275
117527
  const data = this.data();
117276
117528
  const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
117277
117529
  const handlers = {
117278
- ...(mdxish && { [enums_NodeTypes.anchor]: compile_anchor }),
117279
117530
  [enums_NodeTypes.callout]: compile_callout,
117280
117531
  [enums_NodeTypes.codeTabs]: compile_code_tabs,
117281
117532
  [enums_NodeTypes.embedBlock]: compile_embed,
@@ -117283,15 +117534,18 @@ function compilers(mdxish = false) {
117283
117534
  [enums_NodeTypes.glossary]: compile_compatibility,
117284
117535
  [enums_NodeTypes.htmlBlock]: html_block,
117285
117536
  [enums_NodeTypes.reusableContent]: compile_compatibility,
117286
- ...(mdxish && { [enums_NodeTypes.variable]: compile_variable }),
117287
117537
  embed: compile_compatibility,
117288
117538
  escape: compile_compatibility,
117289
117539
  figure: compile_compatibility,
117290
117540
  html: compile_compatibility,
117291
117541
  i: compile_compatibility,
117292
- ...(mdxish && { listItem: list_item }),
117293
117542
  plain: compile_plain,
117294
117543
  yaml: compile_compatibility,
117544
+ // needed only for mdxish
117545
+ ...(mdxish && { [enums_NodeTypes.anchor]: compile_anchor }),
117546
+ ...(mdxish && { listItem: list_item }),
117547
+ ...(mdxish && { text: compile_text }),
117548
+ ...(mdxish && { [enums_NodeTypes.variable]: compile_variable }),
117295
117549
  };
117296
117550
  toMarkdownExtensions.push({ extensions: [{ handlers }] });
117297
117551
  }
@@ -117668,11 +117922,10 @@ const rehypeMdxishComponents = ({ components, processMarkdown }) => {
117668
117922
  ;// ./processor/plugin/mdxish-handlers.ts
117669
117923
 
117670
117924
 
117671
- // Convert MDX expressions to text nodes (evaluation happens earlier in pipeline)
117672
- const mdxExpressionHandler = (_state, node) => ({
117673
- type: 'text',
117674
- value: node.value || '',
117675
- });
117925
+ // If hChildren is populated, it means the node holds a renderable value (e.g. React)
117926
+ // See evaluate-expressions.ts for more details
117927
+ // Otherwise, it's a simple value and we fall back to text
117928
+ const mdxExpressionHandler = (_state, node) => node.data?.hChildren ?? { type: 'text', value: node.value || '' };
117676
117929
  // Convert MDX JSX elements to a custom mdx-jsx hast node that bypasses rehypeRaw's
117677
117930
  // HTML serialization round-trip; downstream normalization rewrites it to `element`.
117678
117931
  const mdxJsxElementHandler = (state, node) => {
@@ -120734,81 +120987,168 @@ const mdxishInlineMdxComponents = () => tree => {
120734
120987
  };
120735
120988
  /* harmony default export */ const inline_mdx_blocks = (mdxishInlineMdxComponents);
120736
120989
 
120990
+ ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
120991
+
120992
+
120993
+
120994
+ const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
120995
+ const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
120996
+ // A line that is exactly one lowercase opening (or self-closing) tag — the shape
120997
+ // that opens a CommonMark type-6/7 HTML block and swallows the lines below it.
120998
+ const SINGLE_OPENING_TAG_REGEX = /^<[a-z][^<>]*>$/;
120999
+ // Tags whose contents must be preserved as is, inserting a blank line after the
121000
+ // opener corrupts the payload.
121001
+ // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
121002
+ const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
121003
+ // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
121004
+ const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
121005
+ open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
121006
+ close: new RegExp(`</${tag}(?=[\\s>])[^>]*>`, 'gi'),
121007
+ }));
121008
+ function isLineHtml(line) {
121009
+ return STANDALONE_HTML_LINE_REGEX.test(line) || HTML_LINE_WITH_CONTENT_REGEX.test(line);
121010
+ }
121011
+ // Per-tag {opens, closes} counts of RAW_CONTENT_TAGS on one line — feeds both the
121012
+ // per-line net-open check and the cumulative still-open depth tracking.
121013
+ function countRawContentTags(line) {
121014
+ return RAW_CONTENT_TAG_MATCHERS.map(({ open, close }) => ({
121015
+ opens: (line.match(open) ?? []).length,
121016
+ closes: (line.match(close) ?? []).length,
121017
+ }));
121018
+ }
121019
+ // Indentation width in columns, counting a tab as 4 per CommonMark.
121020
+ function indentWidth(line) {
121021
+ const leading = line.match(/^[ \t]*/)?.[0] ?? '';
121022
+ return leading.replace(/\t/g, ' ').length;
121023
+ }
121024
+ /**
121025
+ * Decides whether a blank line belongs between `line` and `next` so the HTML
121026
+ * flow block opened on `line` terminates and `next` parses as markdown.
121027
+ */
121028
+ function shouldTerminateBetween(line, next, { insideRawContent, lineLeavesRawOpen }) {
121029
+ if (next.trim().length === 0)
121030
+ return false;
121031
+ const currentIndent = indentWidth(line);
121032
+ const nextIndent = indentWidth(next);
121033
+ // 4+ columns is CommonMark indented-code territory: an inserted blank line
121034
+ // would turn the next line into a code block instead of freeing it (#1344).
121035
+ if (currentIndent > 3 || nextIndent > 3)
121036
+ return false;
121037
+ const currentTrimmed = line.trim();
121038
+ const nextTrimmed = next.trim();
121039
+ if (!isLineHtml(currentTrimmed) || isLineHtml(nextTrimmed) || lineLeavesRawOpen) {
121040
+ return false;
121041
+ }
121042
+ // Column-0 pairs keep the original (pre-relaxation) rule, deliberately ignoring
121043
+ // `insideRawContent`: one unclosed <pre>/<table> typo would otherwise suppress
121044
+ // termination for the whole rest of the document.
121045
+ if (currentIndent === 0 && nextIndent === 0)
121046
+ return true;
121047
+ // Indented shapes (either line at 1–3 columns) are stricter: only a lone opening
121048
+ // tag followed by markdown. A next line opening a tag or expression must stay
121049
+ // glued for the mdxComponent tokenizer; raw-content payloads stay byte-exact.
121050
+ return (!insideRawContent &&
121051
+ SINGLE_OPENING_TAG_REGEX.test(currentTrimmed) &&
121052
+ !nextTrimmed.startsWith('<') &&
121053
+ !nextTrimmed.startsWith('{'));
121054
+ }
121055
+ /**
121056
+ * CommonMark HTML blocks (types 6/7) end only at a blank line, so markdown on the
121057
+ * next line is swallowed as HTML (`## heading` after `<div>` renders as literal text).
121058
+ * Inserts the missing blank line; the gating rules live in `shouldTerminateBetween`.
121059
+ *
121060
+ * @link https://spec.commonmark.org/0.29/#html-blocks
121061
+ */
121062
+ function terminateHtmlFlowBlocks(content) {
121063
+ const { protectedContent, protectedCode } = protectCodeBlocks(content);
121064
+ const lines = protectedContent.split('\n');
121065
+ const result = [];
121066
+ // Per-tag count of still-open raw-content elements at the current boundary.
121067
+ const rawContentDepths = RAW_CONTENT_TAG_MATCHERS.map(() => 0);
121068
+ for (let i = 0; i < lines.length; i += 1) {
121069
+ const line = lines[i];
121070
+ result.push(line);
121071
+ const tagCounts = countRawContentTags(line);
121072
+ tagCounts.forEach(({ opens, closes }, tagIndex) => {
121073
+ rawContentDepths[tagIndex] = Math.max(0, rawContentDepths[tagIndex] + opens - closes);
121074
+ });
121075
+ const next = lines[i + 1];
121076
+ const rawContentFacts = {
121077
+ insideRawContent: rawContentDepths.some(depth => depth > 0),
121078
+ lineLeavesRawOpen: tagCounts.some(({ opens, closes }) => opens > closes),
121079
+ };
121080
+ if (next !== undefined && shouldTerminateBetween(line, next, rawContentFacts)) {
121081
+ result.push('');
121082
+ }
121083
+ }
121084
+ return restoreCodeBlocks(result.join('\n'), protectedCode);
121085
+ }
121086
+
120737
121087
  ;// ./processor/transform/mdxish/components/mdx-blocks.ts
120738
121088
 
120739
121089
 
120740
121090
 
120741
121091
 
120742
- /** Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
121092
+
121093
+
121094
+
121095
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
120743
121096
  const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
120744
- /**
120745
- * Reduce leading whitespace on all lines just enough to prevent
120746
- * remark from treating indented content as code blocks (4+ spaces).
120747
- * Preserves relative indentation so whitespace text nodes are
120748
- * maintained in the HAST output.
121097
+ // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
121098
+ // of a legacy `<<VARIABLE>>`.
121099
+ const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
121100
+ // Excludes tags with dedicated transformers (`Table`, `HTMLBlock`, inline
121101
+ // components), which expect their wrapper to stay raw.
121102
+ const hasNestedGenericComponentTag = (content) => [...content.matchAll(NESTED_COMPONENT_TAG_RE)].some(match => !GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(match[1]));
121103
+ /**
121104
+ * Strip the shared leading indentation from a component body so readability indentation
121105
+ * isn't parsed as indented code (4+ spaces), e.g. ` <p>` / ` text` -> `<p>` / ` text`.
121106
+ * Relative indentation is kept, so content genuinely 4+ columns deeper stays code. We
121107
+ * only strip when a line actually reaches 4 columns; otherwise the body is left as-is so
121108
+ * its leading whitespace survives as text nodes (mixed component + HTML content needs it).
120749
121109
  */
120750
121110
  function safeDeindent(text) {
120751
121111
  const lines = text.split('\n');
120752
121112
  const nonEmptyLines = lines.filter(line => line.trim().length > 0);
120753
121113
  if (nonEmptyLines.length === 0)
120754
121114
  return text;
120755
- const minIndent = Math.min(...nonEmptyLines.map(line => {
120756
- const match = line.match(/^(\s*)/);
120757
- return match ? match[1].length : 0;
120758
- }));
120759
- // Only strip enough indent to keep all lines below the 4-space code threshold
120760
- const stripAmount = Math.max(0, minIndent - 3);
121115
+ // Indent counts characters (tab = 1), unlike indentWidth's CommonMark columns
121116
+ // (tab = 4) in terminate-html-flow-blocks.
121117
+ const indents = nonEmptyLines.map(line => line.match(/^(\s*)/)?.[1].length ?? 0);
121118
+ const minIndent = Math.min(...indents);
121119
+ const maxIndent = Math.max(...indents);
121120
+ const stripAmount = maxIndent > 3 ? minIndent : 0;
120761
121121
  if (stripAmount === 0)
120762
121122
  return text;
120763
121123
  return lines.map(line => line.slice(stripAmount)).join('\n');
120764
121124
  }
120765
121125
  /**
120766
- * Parse markdown content into mdast children nodes.
120767
- * Dedents the content first to prevent indented component content
120768
- * (from nested components) from being treated as code blocks.
121126
+ * Parse component-body markdown into mdast children. Dedenting shifts columns and
121127
+ * stales the top-level `terminateHtmlFlowBlocks` decisions, so that one preprocessor
121128
+ * re-runs here; other column-anchored fixups (compact headings, tables) do not.
120769
121129
  */
120770
121130
  const parseMdChildren = (value, safeMode) => {
120771
- const parsed = getInlineMdProcessor({ safeMode }).parse(safeDeindent(value).trim());
121131
+ const parsed = getInlineMdProcessor({ safeMode }).parse(terminateHtmlFlowBlocks(safeDeindent(value).trim()));
120772
121132
  return parsed.children || [];
120773
121133
  };
120774
- /**
120775
- * Parse substring content of a node and update the parent's children to include the new nodes.
120776
- */
121134
+ // Parses trailing content into sibling nodes and re-queues the parent so any
121135
+ // components among them get processed.
120777
121136
  const parseSibling = (stack, parent, index, sibling, safeMode) => {
120778
121137
  const siblingNodes = parseMdChildren(sibling, safeMode);
120779
- // The new sibling nodes might contain new components to be processed
120780
121138
  if (siblingNodes.length > 0) {
120781
121139
  parent.children.splice(index + 1, 0, ...siblingNodes);
120782
121140
  stack.push(parent);
120783
121141
  }
120784
121142
  };
120785
- /**
120786
- * Advance a point by the substring of source consumed from it.
120787
- */
120788
- const pointAfter = (start, consumed) => {
120789
- const newlineIndex = consumed.lastIndexOf('\n');
120790
- const newlineCount = newlineIndex === -1 ? 0 : consumed.split('\n').length - 1;
120791
- return {
120792
- line: start.line + newlineCount,
120793
- column: newlineCount === 0 ? start.column + consumed.length : consumed.length - newlineIndex,
120794
- offset: start.offset + consumed.length,
120795
- };
120796
- };
120797
- /**
120798
- * Build a position ending at `consumedLength` into the html node's value, so the
120799
- * component doesn't claim trailing content the tokenizer swallowed into one node.
120800
- */
121143
+ // Ends the position at `consumedLength` so the component doesn't claim trailing
121144
+ // content the tokenizer swallowed into the same html node.
120801
121145
  const positionEndingAtConsumed = (nodePosition, value, consumedLength) => {
120802
121146
  if (!nodePosition?.start)
120803
121147
  return nodePosition;
120804
121148
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, value.slice(0, consumedLength)) };
120805
121149
  };
120806
- /**
120807
- * Build a position ending right after the last occurrence of `closingTag` within
120808
- * this node's span in the original source. Used in the trailing-content path so
120809
- * the offset is computed against the real source bytes (including blockquote/list
120810
- * prefixes that were stripped from the html node's value).
120811
- */
121150
+ // Like `positionEndingAtConsumed`, but measures against the original source so
121151
+ // blockquote/list prefixes stripped from the html node's value are counted.
120812
121152
  const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) => {
120813
121153
  if (!nodePosition?.start || !nodePosition.end)
120814
121154
  return nodePosition;
@@ -120819,9 +121159,6 @@ const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) =>
120819
121159
  const consumed = nodeSource.slice(0, closingTagOffset + closingTag.length);
120820
121160
  return { start: nodePosition.start, end: pointAfter(nodePosition.start, consumed) };
120821
121161
  };
120822
- /**
120823
- * Create an MdxJsxFlowElement node from component data.
120824
- */
120825
121162
  const createComponentNode = ({ tag, attributes, children, startPosition, endPosition }) => ({
120826
121163
  type: 'mdxJsxFlowElement',
120827
121164
  name: tag,
@@ -120874,8 +121211,7 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
120874
121211
  if ('children' in node && Array.isArray(node.children)) {
120875
121212
  stack.push(node);
120876
121213
  }
120877
- // Only visit HTML nodes with an actual html tag,
120878
- // which means a potential unparsed MDX component
121214
+ // Only html nodes can be an unparsed MDX component.
120879
121215
  const value = node.value;
120880
121216
  if (node.type !== 'html' || typeof value !== 'string')
120881
121217
  return;
@@ -120884,32 +121220,25 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
120884
121220
  if (!parsed)
120885
121221
  return;
120886
121222
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
120887
- // Offset of `trimmed` within the (possibly whitespace-padded) html node value,
120888
- // so consumed-length math maps back onto the node's real source offsets.
121223
+ // Offsets so consumed-length math maps back onto the node's real source.
120889
121224
  const leadingWhitespace = value.length - value.trimStart().length;
120890
- // Index right after the opening tag's `>` within `trimmed`.
120891
121225
  const openingTagEnd = trimmed.length - contentAfterTag.length;
120892
- // Skip tags that have dedicated transformers
120893
121226
  if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
120894
- return;
121227
+ return; // owned by dedicated transformers
120895
121228
  const isPascal = isPascalCase(tag);
120896
- // Lowercase inline tags (inside a paragraph) with `{…}` attributes are
120897
- // promoted to `mdxJsxTextElement` by mdxishInlineComponentBlocks. Skip
120898
- // them here so they stay as html for that pass; PascalCase components
120899
- // keep going through this transformer (they stay flow-level even when
120900
- // inline, which is how ReadMe's custom components are modeled).
121229
+ // Lowercase inline tags with `{…}` attributes belong to
121230
+ // mdxishInlineComponentBlocks; leave them as html for that pass. PascalCase
121231
+ // components stay flow-level even when inline (ReadMe's component model).
120901
121232
  if (!isPascal && parent.type === 'paragraph')
120902
121233
  return;
120903
- // Lowercase HTML tags are eligible when they (or a descendant tag in their
120904
- // content, e.g. a `.map()` body returning JSX with `key={i}`) carry a
120905
- // JSX-expression attribute. Without this, a wrapper `<div>` with only plain
120906
- // attributes (or none) swallows its whole nested block — including any
120907
- // `style={{...}}`/`.map()` JSX inside it — as literal HTML text, which
120908
- // rehype-raw's parse5 pass then garbles (it has no notion of JS expressions).
120909
- // Plain HTML with no expressions anywhere stays as an html node so
120910
- // rehype-raw handles it as normal.
121234
+ // A lowercase wrapper is only promoted when it (or a descendant) carries a
121235
+ // JSX expression or nests a component; otherwise it would swallow that inner
121236
+ // JSX/component as literal text that rehype-raw's parse5 pass can't handle.
121237
+ // Table-structural wrappers are excluded `mdxishTables` re-parses those.
120911
121238
  const hasNestedExpressionAttr = !selfClosing && NESTED_ATTR_EXPRESSION_RE.test(contentAfterTag);
120912
- if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr)
121239
+ const isTableStructuralTag = tag === 'table' || tableTags.has(tag);
121240
+ const hasNestedComponentTag = !selfClosing && !isTableStructuralTag && hasNestedGenericComponentTag(contentAfterTag);
121241
+ if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr && !hasNestedComponentTag)
120913
121242
  return;
120914
121243
  const closingTagStr = `</${tag}>`;
120915
121244
  // Case 1: Self-closing tag
@@ -120923,7 +121252,6 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
120923
121252
  endPosition: positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd),
120924
121253
  });
120925
121254
  substituteNodeWithMdxNode(parent, index, componentNode);
120926
- // Check and parse if there's relevant content after the current closing tag
120927
121255
  const remainingContent = contentAfterTag.trim();
120928
121256
  if (remainingContent) {
120929
121257
  parseSibling(stack, parent, index, remainingContent, safeMode);
@@ -120932,17 +121260,14 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
120932
121260
  }
120933
121261
  // Case 2: Self-contained block (closing tag in content)
120934
121262
  if (contentAfterTag.includes(closingTagStr)) {
120935
- // Find the first closing tag
120936
121263
  const closingTagIndex = contentAfterTag.lastIndexOf(closingTagStr);
120937
- // Pass raw (untrimmed) content so dedent in parseMdChildren can
120938
- // normalize indentation before trimming
121264
+ // Untrimmed so parseMdChildren can dedent before trimming.
120939
121265
  const componentInnerContent = contentAfterTag.substring(0, closingTagIndex);
120940
121266
  const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
120941
121267
  let parsedChildren = componentInnerContent.trim()
120942
121268
  ? parseMdChildren(componentInnerContent, safeMode)
120943
121269
  : [];
120944
- // Lowercase HTML tags are usually inline (e.g. <a>, <span>). Remark wraps
120945
- // bare text in a paragraph; unwrap when there's exactly one paragraph so
121270
+ // Lowercase tags are usually inline; unwrap a sole paragraph so their
120946
121271
  // phrasing content isn't spuriously block-wrapped.
120947
121272
  if (!isPascal && parsedChildren.length === 1 && parsedChildren[0].type === 'paragraph') {
120948
121273
  parsedChildren = parsedChildren[0].children;
@@ -120952,12 +121277,9 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
120952
121277
  attributes,
120953
121278
  children: parsedChildren,
120954
121279
  startPosition: node.position,
120955
- // When trailing content follows the closing tag, compute the end position precisely
120956
- // within the html node's value so the component doesn't claim that content.
120957
- // Prefer source-based positioning when the original source is available: the html
120958
- // node's value has '> '/space prefixes stripped for blockquotes/list items, so
120959
- // positionEndingAtConsumed would undercount source offsets. When the entire node
120960
- // is consumed, use the original node position directly.
121280
+ // With trailing content, end precisely at the closing tag. Prefer source
121281
+ // offsets when available (the node's value strips blockquote/list
121282
+ // prefixes); otherwise fall back to the whole node position.
120961
121283
  endPosition: contentAfterClose
120962
121284
  ? source
120963
121285
  ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
@@ -120965,17 +121287,16 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
120965
121287
  : node.position,
120966
121288
  });
120967
121289
  substituteNodeWithMdxNode(parent, index, componentNode);
120968
- // After the closing tag, there might be more content to be processed
121290
+ // Re-queue whichever side may hold further components.
120969
121291
  if (contentAfterClose) {
120970
121292
  parseSibling(stack, parent, index, contentAfterClose, safeMode);
120971
121293
  }
120972
121294
  else if (componentNode.children.length > 0) {
120973
- // The content inside the component block might contain new components to be processed
120974
121295
  stack.push(componentNode);
120975
121296
  }
120976
121297
  }
120977
121298
  };
120978
- // Process the nodes with the components depth-first to maintain the correct order of the nodes
121299
+ // Depth-first so nodes keep their source order.
120979
121300
  while (stack.length) {
120980
121301
  const parent = stack.pop();
120981
121302
  if (parent?.children) {
@@ -121341,26 +121662,183 @@ const evalExpression = (expression, scope) => {
121341
121662
  return evalJsxProgram(program, scope);
121342
121663
  };
121343
121664
 
121344
- ;// ./processor/transform/mdxish/evaluate-expressions.ts
121665
+ ;// ./processor/transform/mdxish/style-object-to-css.ts
121666
+
121667
+ /**
121668
+ * CSS properties React treats as unitless — a bare number stays as-is instead of
121669
+ * getting `px` appended. Mirrors React DOM's `isUnitlessNumber` whitelist.
121670
+ * @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/CSSProperty.js
121671
+ */
121672
+ const UNITLESS_CSS_PROPERTIES = new Set([
121673
+ 'animationIterationCount',
121674
+ 'aspectRatio',
121675
+ 'borderImageOutset',
121676
+ 'borderImageSlice',
121677
+ 'borderImageWidth',
121678
+ 'boxFlex',
121679
+ 'boxFlexGroup',
121680
+ 'boxOrdinalGroup',
121681
+ 'columnCount',
121682
+ 'columns',
121683
+ 'flex',
121684
+ 'flexGrow',
121685
+ 'flexPositive',
121686
+ 'flexShrink',
121687
+ 'flexNegative',
121688
+ 'flexOrder',
121689
+ 'gridArea',
121690
+ 'gridRow',
121691
+ 'gridRowEnd',
121692
+ 'gridRowSpan',
121693
+ 'gridRowStart',
121694
+ 'gridColumn',
121695
+ 'gridColumnEnd',
121696
+ 'gridColumnSpan',
121697
+ 'gridColumnStart',
121698
+ 'fontWeight',
121699
+ 'lineClamp',
121700
+ 'lineHeight',
121701
+ 'opacity',
121702
+ 'order',
121703
+ 'orphans',
121704
+ 'tabSize',
121705
+ 'widows',
121706
+ 'zIndex',
121707
+ 'zoom',
121708
+ 'fillOpacity',
121709
+ 'floodOpacity',
121710
+ 'stopOpacity',
121711
+ 'strokeDasharray',
121712
+ 'strokeDashoffset',
121713
+ 'strokeMiterlimit',
121714
+ 'strokeOpacity',
121715
+ 'strokeWidth',
121716
+ ]);
121717
+ /** Convert a camelCase (or CSS custom property) key into its kebab-case CSS name. */
121718
+ const cssPropertyName = (key) => (key.startsWith('--') ? key : key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`));
121719
+ /** React appends `px` to non-zero numeric values, except unitless and custom properties. */
121720
+ const cssPropertyValue = (key, value) => typeof value === 'number' && value !== 0 && !key.startsWith('--') && !UNITLESS_CSS_PROPERTIES.has(key)
121721
+ ? `${value}px`
121722
+ : `${value}`;
121723
+ /**
121724
+ * True for a value that should be serialized as a style object rather than passed through
121725
+ * as an already-CSS string. React elements are excluded since they're valid objects too.
121726
+ */
121727
+ const style_object_to_css_isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !external_react_default().isValidElement(value);
121728
+ /**
121729
+ * Serialize a React-style inline style object (`{ color: "red", fontSize: 12 }`) into a
121730
+ * CSS declaration string (`color:red;font-size:12px`), the shape hast/HTML expects for a
121731
+ * `style` attribute. Empty (`undefined`/`null`/`''`) entries are dropped.
121732
+ */
121733
+ const styleObjectToCssText = (style) => Object.entries(style)
121734
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
121735
+ .map(([key, value]) => `${cssPropertyName(key)}:${cssPropertyValue(key, value)}`)
121736
+ .join(';');
121345
121737
 
121738
+ ;// ./processor/transform/mdxish/react-element-to-hast.ts
121346
121739
 
121347
121740
 
121348
121741
 
121349
- /** True for an array containing at least one React element, e.g. the result of `.map()` returning JSX. */
121350
- const isRenderableElementArray = (value) => Array.isArray(value) && value.some((external_react_default()).isValidElement);
121351
- /** Given the type of the expression result, create the corresponding mdast node. */
121352
- const createEvaluatedNode = (result, position) => {
121353
- if (result === null || result === undefined) {
121354
- return { type: 'text', value: '', position };
121742
+ // React props that never map to an HTML/hast attribute.
121743
+ const RESERVED_PROPS = new Set(['children', 'key', 'ref']);
121744
+ /**
121745
+ * Translate React props into hast `properties`: reserved and function props (event handlers)
121746
+ * are dropped (known gap), `style` objects flatten to CSS text. Names stay as authored (`className`); the
121747
+ * hast HTML boundary maps them to `class`/`for` downstream.
121748
+ */
121749
+ function propsToHastProperties(props) {
121750
+ const properties = {};
121751
+ Object.entries(props).forEach(([key, value]) => {
121752
+ if (RESERVED_PROPS.has(key) || typeof value === 'function' || value === undefined)
121753
+ return;
121754
+ properties[key] = key === 'style' && style_object_to_css_isPlainObject(value) ? styleObjectToCssText(value) : value;
121755
+ });
121756
+ // Values are resolved React props, wider than hast's HTML-attribute `PropertyValue` union.
121757
+ return properties;
121758
+ }
121759
+ /**
121760
+ * Render an element with React's own renderer as a last resort, wrapped as a hast `raw` node —
121761
+ * the same node type markdown's literal HTML blocks produce, so it re-enters rehypeRaw's
121762
+ * parse5 pass normally. Used for element types this converter can't resolve on its own (wrapped
121763
+ * component types, or a function component that throws when called outside React, e.g. one
121764
+ * using hooks). It's not immune to the invalid-HTML-nesting this module otherwise avoids, but
121765
+ * that's a fair trade against silently dropping the content.
121766
+ */
121767
+ function renderFallbackHtml(element) {
121768
+ try {
121769
+ const rawNode = { type: 'raw', value: (0,server_node/* renderToStaticMarkup */.qV)(element) };
121770
+ return [rawNode];
121355
121771
  }
121356
- else if (external_react_default().isValidElement(result) || isRenderableElementArray(result)) {
121357
- // Convert react elements (or arrays of them, e.g. `.map()` returning JSX) to their HTML
121358
- // representation. This must come before the object check as both are a subset of it.
121359
- return { type: 'html', value: (0,server_node/* renderToStaticMarkup */.qV)(external_react_default().createElement((external_react_default()).Fragment, null, result)), position };
121772
+ catch {
121773
+ return [];
121360
121774
  }
121361
- else if (typeof result === 'object') {
121362
- return { type: 'text', value: JSON.stringify(result), position };
121775
+ }
121776
+ /**
121777
+ * Convert a React element tree into hast. Emitting `mdx-jsx` nodes (rehypeRaw passthrough, later normalized to `element`)
121778
+ * preserves nesting that is valid JSX but not valid HTML, which parse5 would otherwise
121779
+ * restructure — e.g. an `<a>` wrapping another `<a>`.
121780
+ * Recursively converts the tree into an array of hast nodes, dealing with arrays and null/undefined/boolean values.
121781
+ */
121782
+ function reactElementToHast(node) {
121783
+ if (Array.isArray(node))
121784
+ return node.flatMap(reactElementToHast);
121785
+ if (node === null || node === undefined || typeof node === 'boolean')
121786
+ return [];
121787
+ if (typeof node === 'string' || typeof node === 'number') {
121788
+ const textNode = { type: 'text', value: String(node) };
121789
+ return [textNode];
121363
121790
  }
121791
+ if (!external_react_default().isValidElement(node))
121792
+ return [];
121793
+ const { type, props } = node;
121794
+ // Fragments contribute their children with no wrapper element.
121795
+ if (type === (external_react_default()).Fragment)
121796
+ return reactElementToHast(props.children);
121797
+ // Resolve function components to their rendered output so the tree is plain intrinsic
121798
+ // elements. If invoking it directly throws (e.g. it uses hooks, which need React's own
121799
+ // render context), fall back to React's renderer for just this subtree.
121800
+ if (typeof type === 'function') {
121801
+ try {
121802
+ return reactElementToHast(type(props));
121803
+ }
121804
+ catch {
121805
+ return renderFallbackHtml(node);
121806
+ }
121807
+ }
121808
+ // Non-intrinsic, non-callable element types — `React.memo`, `React.forwardRef`,
121809
+ // `Context.Provider`/`Consumer`, `React.lazy` — have no `type` we can resolve ourselves.
121810
+ if (typeof type !== 'string')
121811
+ return renderFallbackHtml(node);
121812
+ const mdxJsxNode = {
121813
+ type: 'mdx-jsx',
121814
+ tagName: type,
121815
+ properties: propsToHastProperties(props),
121816
+ children: reactElementToHast(props.children),
121817
+ };
121818
+ return [mdxJsxNode];
121819
+ }
121820
+
121821
+ ;// ./processor/transform/mdxish/evaluate-expressions.ts
121822
+
121823
+
121824
+
121825
+
121826
+ /**
121827
+ * We divide the result of an expression into two categories:
121828
+ * 1. Renderable values: HTML, JSX, e.g. .map() returning JSX
121829
+ * 2. Non-renderable values: a string, number, or object, regular JS values
121830
+ */
121831
+ const isRenderable = (value) => {
121832
+ if (external_react_default().isValidElement(value))
121833
+ return true;
121834
+ return Array.isArray(value) && value.some(isRenderable);
121835
+ };
121836
+ /** Turn a non-renderable evaluation result into a text node. */
121837
+ const createTextNode = (result, position) => {
121838
+ if (result === null || result === undefined)
121839
+ return { type: 'text', value: '', position };
121840
+ if (typeof result === 'object')
121841
+ return { type: 'text', value: JSON.stringify(result), position };
121364
121842
  return { type: 'text', value: String(result), position };
121365
121843
  };
121366
121844
  /**
@@ -121376,13 +121854,23 @@ const evaluateExpressions = () => (tree, file) => {
121376
121854
  lib_visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => {
121377
121855
  if (!parent || index === null || index === undefined)
121378
121856
  return;
121379
- const { value, position } = node;
121857
+ const expressionNode = node;
121858
+ const { value, position } = expressionNode;
121380
121859
  const expression = value?.trim();
121381
121860
  if (!expression)
121382
121861
  return;
121383
121862
  try {
121384
121863
  const result = evalExpression(expression, scope);
121385
- parent.children.splice(index, 1, createEvaluatedNode(result, position));
121864
+ if (isRenderable(result)) {
121865
+ // Stash hast built straight from the React tree; `mdxExpressionHandler` emits it and it
121866
+ // passes through rehypeRaw/parse5 step later in the pipeline. This ensures that the
121867
+ // expression result is not parsed by parse5 and fragmenting the nesting that is valid JSX
121868
+ // but invalid HTML — e.g. an `<a>` wrapping `<a>`.
121869
+ expressionNode.data = { ...expressionNode.data, hChildren: reactElementToHast(result) };
121870
+ }
121871
+ else {
121872
+ parent.children.splice(index, 1, createTextNode(result, position));
121873
+ }
121386
121874
  }
121387
121875
  catch (_error) {
121388
121876
  // Evaluation failed — fall back to literal `{...}` text. The expression
@@ -124713,79 +125201,6 @@ function removeJSXComments(content) {
124713
125201
  return content.replace(JSX_COMMENT_REGEX, '');
124714
125202
  }
124715
125203
 
124716
- ;// ./processor/transform/mdxish/style-object-to-css.ts
124717
-
124718
- /**
124719
- * CSS properties React treats as unitless — a bare number stays as-is instead of
124720
- * getting `px` appended. Mirrors React DOM's `isUnitlessNumber` whitelist.
124721
- * @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/CSSProperty.js
124722
- */
124723
- const UNITLESS_CSS_PROPERTIES = new Set([
124724
- 'animationIterationCount',
124725
- 'aspectRatio',
124726
- 'borderImageOutset',
124727
- 'borderImageSlice',
124728
- 'borderImageWidth',
124729
- 'boxFlex',
124730
- 'boxFlexGroup',
124731
- 'boxOrdinalGroup',
124732
- 'columnCount',
124733
- 'columns',
124734
- 'flex',
124735
- 'flexGrow',
124736
- 'flexPositive',
124737
- 'flexShrink',
124738
- 'flexNegative',
124739
- 'flexOrder',
124740
- 'gridArea',
124741
- 'gridRow',
124742
- 'gridRowEnd',
124743
- 'gridRowSpan',
124744
- 'gridRowStart',
124745
- 'gridColumn',
124746
- 'gridColumnEnd',
124747
- 'gridColumnSpan',
124748
- 'gridColumnStart',
124749
- 'fontWeight',
124750
- 'lineClamp',
124751
- 'lineHeight',
124752
- 'opacity',
124753
- 'order',
124754
- 'orphans',
124755
- 'tabSize',
124756
- 'widows',
124757
- 'zIndex',
124758
- 'zoom',
124759
- 'fillOpacity',
124760
- 'floodOpacity',
124761
- 'stopOpacity',
124762
- 'strokeDasharray',
124763
- 'strokeDashoffset',
124764
- 'strokeMiterlimit',
124765
- 'strokeOpacity',
124766
- 'strokeWidth',
124767
- ]);
124768
- /** Convert a camelCase (or CSS custom property) key into its kebab-case CSS name. */
124769
- const cssPropertyName = (key) => (key.startsWith('--') ? key : key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`));
124770
- /** React appends `px` to non-zero numeric values, except unitless and custom properties. */
124771
- const cssPropertyValue = (key, value) => typeof value === 'number' && value !== 0 && !key.startsWith('--') && !UNITLESS_CSS_PROPERTIES.has(key)
124772
- ? `${value}px`
124773
- : `${value}`;
124774
- /**
124775
- * True for a value that should be serialized as a style object rather than passed through
124776
- * as an already-CSS string. React elements are excluded since they're valid objects too.
124777
- */
124778
- const style_object_to_css_isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !external_react_default().isValidElement(value);
124779
- /**
124780
- * Serialize a React-style inline style object (`{ color: "red", fontSize: 12 }`) into a
124781
- * CSS declaration string (`color:red;font-size:12px`), the shape hast/HTML expects for a
124782
- * `style` attribute. Empty (`undefined`/`null`/`''`) entries are dropped.
124783
- */
124784
- const styleObjectToCssText = (style) => Object.entries(style)
124785
- .filter(([, value]) => value !== undefined && value !== null && value !== '')
124786
- .map(([key, value]) => `${cssPropertyName(key)}:${cssPropertyValue(key, value)}`)
124787
- .join(';');
124788
-
124789
125204
  ;// ./processor/transform/mdxish/resolve-deferred-attribute-expression-props.ts
124790
125205
 
124791
125206
 
@@ -125132,73 +125547,6 @@ function normalizeTableSeparator(content) {
125132
125547
  }
125133
125548
  /* harmony default export */ const normalize_table_separator = ((/* unused pure expression or super */ null && (normalizeTableSeparator)));
125134
125549
 
125135
- ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
125136
-
125137
-
125138
-
125139
- const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
125140
- const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
125141
- // Tags whose contents must be preserved as is, inserting a blank line after the
125142
- // opener corrupts the payload.
125143
- // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
125144
- const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
125145
- // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
125146
- const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
125147
- open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
125148
- close: new RegExp(`</${tag}(?=[\\s>])[^>]*>`, 'gi'),
125149
- }));
125150
- function isLineHtml(line) {
125151
- return STANDALONE_HTML_LINE_REGEX.test(line) || HTML_LINE_WITH_CONTENT_REGEX.test(line);
125152
- }
125153
- // True if any RAW_CONTENT_TAGS opener on this line is not closed on the same line.
125154
- function hasUnclosedRawContentOpener(line) {
125155
- return RAW_CONTENT_TAG_MATCHERS.some(({ open, close }) => {
125156
- const opens = (line.match(open) ?? []).length;
125157
- const closes = (line.match(close) ?? []).length;
125158
- return opens > closes;
125159
- });
125160
- }
125161
- /**
125162
- * Preprocessor to terminate HTML flow blocks.
125163
- *
125164
- * In CommonMark, HTML blocks (types 6 and 7) only terminate on a blank line.
125165
- * Without one, any content on the next line is consumed as part of the HTML block
125166
- * and never parsed as its own construct. For example, a `[block:callout]` immediately
125167
- * following `<div><p></p></div>` gets swallowed into the HTML flow token.
125168
- *
125169
- * @link https://spec.commonmark.org/0.29/#html-blocks
125170
- *
125171
- * This preprocessor inserts a blank line after standalone HTML lines when the next
125172
- * line is non-blank and not an HTML construct, ensuring micromark's HTML flow
125173
- * tokenizer terminates and subsequent content is parsed independently.
125174
- *
125175
- * Conditions:
125176
- * 1. Only non-indented lines with lowercase tag names are considered. Uppercase tags
125177
- * (e.g. `<Table>`, `<MyComponent>`) are JSX custom components and don't trigger
125178
- * CommonMark HTML blocks.
125179
- * 2. Lines inside protected blocks (e.g. fenced code) are left untouched.
125180
- * 3. Lines with an unclosed RAW_CONTENT_TAGS opener are exempted.
125181
- */
125182
- function terminateHtmlFlowBlocks(content) {
125183
- const { protectedContent, protectedCode } = protectCodeBlocks(content);
125184
- const lines = protectedContent.split('\n');
125185
- const result = [];
125186
- for (let i = 0; i < lines.length; i += 1) {
125187
- result.push(lines[i]);
125188
- // Skip blank & indented lines
125189
- if (i >= lines.length - 1 || lines[i + 1].trim().length === 0 || lines[i + 1].startsWith(' ') || lines[i + 1].startsWith('\t')) {
125190
- // eslint-disable-next-line no-continue
125191
- continue;
125192
- }
125193
- const isCurrentLineHtml = isLineHtml(lines[i]);
125194
- const isNextLineHtml = isLineHtml(lines[i + 1]);
125195
- if (isCurrentLineHtml && !isNextLineHtml && !hasUnclosedRawContentOpener(lines[i])) {
125196
- result.push('');
125197
- }
125198
- }
125199
- return restoreCodeBlocks(result.join('\n'), protectedCode);
125200
- }
125201
-
125202
125550
  ;// ./processor/transform/mdxish/variables-code.ts
125203
125551
 
125204
125552
 
@@ -126066,6 +126414,12 @@ function mdxishMdastToMd(mdast) {
126066
126414
  .use(remarkStringify, {
126067
126415
  bullet: '-',
126068
126416
  emphasis: '_',
126417
+ // Escape literal braces in text so they don't parse as (often
126418
+ // unterminated) MDX expressions on the next round trip.
126419
+ unsafe: [
126420
+ { character: '{', inConstruct: 'phrasing' },
126421
+ { character: '}', inConstruct: 'phrasing' },
126422
+ ],
126069
126423
  });
126070
126424
  return processor.stringify(processor.runSync(mdast));
126071
126425
  }