@readme/markdown 14.10.3 → 14.11.1

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
@@ -95943,6 +95943,18 @@ const tableTags = new Set([
95943
95943
  'th',
95944
95944
  'td',
95945
95945
  ]);
95946
+ /**
95947
+ * Replaces every paragraph node with its inline children. Used where paragraphs
95948
+ * are parser artifacts (remarkMdx wrapping inline JSX), not real content.
95949
+ */
95950
+ const unwrapParagraphNodes = (children) => {
95951
+ return children.flatMap(child => {
95952
+ if (child.type === 'paragraph' && 'children' in child && Array.isArray(child.children)) {
95953
+ return child.children;
95954
+ }
95955
+ return [child];
95956
+ });
95957
+ };
95946
95958
  /**
95947
95959
  * If the cell has exactly one paragraph child, unwrap it so its inline children sit
95948
95960
  * directly under the cell (matches GFM table cell shape and avoids stray `<p>` wrappers).
@@ -95954,12 +95966,7 @@ const unwrapSoleParagraph = (children) => {
95954
95966
  const paragraphCount = children.filter(c => c.type === 'paragraph').length;
95955
95967
  if (paragraphCount !== 1)
95956
95968
  return children;
95957
- return children.flatMap(child => {
95958
- if (child.type === 'paragraph' && 'children' in child && Array.isArray(child.children)) {
95959
- return child.children;
95960
- }
95961
- return [child];
95962
- });
95969
+ return unwrapParagraphNodes(children);
95963
95970
  };
95964
95971
  /**
95965
95972
  * Splice each text into `html` at its offset. Inserts at the same offset
@@ -96309,6 +96316,23 @@ const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable', 'html-block', 'r
96309
96316
  * Uses the html-tags package, converted to a Set<string> for efficient lookups.
96310
96317
  */
96311
96318
  const STANDARD_HTML_TAGS = new Set(html_tags_namespaceObject);
96319
+ /**
96320
+ * Table structural tags. Blank lines inside these carry deliberate meaning for
96321
+ * `mdxishTables` (e.g. splitting cell content into paragraphs, or deciding
96322
+ * whether a table stays plain HTML vs a JSX `<Table>`), so transforms that
96323
+ * neutralize or claim across blank lines must leave them alone.
96324
+ */
96325
+ const HTML_TABLE_STRUCTURE_TAGS = new Set([
96326
+ 'table',
96327
+ 'thead',
96328
+ 'tbody',
96329
+ 'tfoot',
96330
+ 'tr',
96331
+ 'td',
96332
+ 'th',
96333
+ 'caption',
96334
+ 'colgroup',
96335
+ ]);
96312
96336
  /**
96313
96337
  * HTML void elements — elements that have no closing tag and no children.
96314
96338
  *
@@ -96620,18 +96644,17 @@ const processTableNode = (node, index, parent, documentPosition) => {
96620
96644
  // same line as content becomes mdxJsxTextElement inside a paragraph).
96621
96645
  // Unwrap these so <td>/<th> sit directly under <tr>, and strip
96622
96646
  // whitespace-only text nodes to avoid rendering empty <p>/<br>.
96623
- const cleanChildren = (children) => children
96624
- .flatMap(child => {
96625
- if (child.type === 'paragraph' && 'children' in child && Array.isArray(child.children)) {
96626
- return child.children;
96627
- }
96628
- return [child];
96629
- })
96630
- .filter(child => !(child.type === 'text' && 'value' in child && typeof child.value === 'string' && !child.value.trim()));
96647
+ const removeWhitespaceOnlyTextNodes = (children) => children.filter(child => !(child.type === 'text' && 'value' in child && typeof child.value === 'string' && !child.value.trim()));
96631
96648
  visit(node, isMDXElement, (el) => {
96632
- if ('children' in el && Array.isArray(el.children)) {
96633
- el.children = cleanChildren(el.children);
96634
- }
96649
+ if (!('children' in el) || !Array.isArray(el.children))
96650
+ return;
96651
+ // Filtering transformers
96652
+ // A cell only unwraps a sole paragraph: multiple paragraphs are real
96653
+ // blank-line-separated content that must stay separated
96654
+ const unwrapped = isTableCell(el)
96655
+ ? unwrapSoleParagraph(el.children)
96656
+ : unwrapParagraphNodes(el.children);
96657
+ el.children = removeWhitespaceOnlyTextNodes(unwrapped);
96635
96658
  });
96636
96659
  parent.children[index] = {
96637
96660
  ...node,
@@ -121021,15 +121044,26 @@ const types_types = /** @type {const} */ ({
121021
121044
 
121022
121045
 
121023
121046
 
121047
+
121024
121048
  // Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
121025
121049
  // section, …) always start a block, so they stay flow even with trailing
121026
121050
  // content. Other lowercase tags (i, span, …) follow the type-7 rule and only
121027
121051
  // stay flow when nothing trails the close tag.
121028
121052
  const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
121053
+ // Lowercase type-6 block tags claimable in flow even without a `{…}` attribute, so
121054
+ // blank lines between nested JSX siblings don't fragment the block. Excludes table
121055
+ // tags (mdxishTables owns their blank lines) and voids (never close).
121056
+ const plainBlockClaimTagNames = new Set([...htmlBlockNames].filter(tag => !HTML_TABLE_STRUCTURE_TAGS.has(tag) && !HTML_VOID_ELEMENTS.has(tag)));
121029
121057
  const syntax_nonLazyContinuationStart = {
121030
121058
  tokenize: syntax_tokenizeNonLazyContinuationStart,
121031
121059
  partial: true,
121032
121060
  };
121061
+ // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
121062
+ // that merely starts with a tag? Run via `effects.check` so it never consumes.
121063
+ const markupOnlyContinuation = {
121064
+ tokenize: tokenizeMarkupOnlyContinuation,
121065
+ partial: true,
121066
+ };
121033
121067
  function resolveToMdxComponent(events) {
121034
121068
  let index = events.length;
121035
121069
  while (index > 0) {
@@ -121084,6 +121118,10 @@ function createTokenize(mode) {
121084
121118
  // (INLINE_COMPONENT_TAGS — Anchor, Glossary), also brace-gated.
121085
121119
  let isLowercaseTag = false;
121086
121120
  let sawBraceAttr = false;
121121
+ // A plain lowercase block tag claimed without a `{…}` attribute, gated by
121122
+ // `plainClaimLineStart`: after a blank line it may only continue on a tag line.
121123
+ let isPlainBlockClaim = false;
121124
+ let pendingBlankLine = false;
121087
121125
  // Code span tracking
121088
121126
  let codeSpanOpenSize = 0;
121089
121127
  let codeSpanCloseSize = 0;
@@ -121317,8 +121355,13 @@ function createTokenize(mode) {
121317
121355
  }
121318
121356
  // End of opening tag
121319
121357
  if (code === codes.greaterThan) {
121320
- if (requiresBraceAttr && !sawBraceAttr)
121321
- return nok(code);
121358
+ if (requiresBraceAttr && !sawBraceAttr) {
121359
+ // Plain lowercase block tags stay claimable in flow, gated per line by
121360
+ // `plainClaimLineStart`; everything else falls through to CommonMark.
121361
+ if (!isFlow || !plainBlockClaimTagNames.has(tagName))
121362
+ return nok(code);
121363
+ isPlainBlockClaim = true;
121364
+ }
121322
121365
  effects.consume(code);
121323
121366
  onOpenerLine = isFlow;
121324
121367
  return body;
@@ -121468,6 +121511,7 @@ function createTokenize(mode) {
121468
121511
  if (atLineStart && codeSpanOpenSize >= 3) {
121469
121512
  fenceChar = codes.graveAccent;
121470
121513
  fenceLength = codeSpanOpenSize;
121514
+ atLineStart = false;
121471
121515
  return inFencedCode(code);
121472
121516
  }
121473
121517
  return inCodeSpan(code);
@@ -121615,8 +121659,14 @@ function createTokenize(mode) {
121615
121659
  effects.consume(code);
121616
121660
  return nestedOpenTagName;
121617
121661
  }
121618
- // Only increment depth for same-name tags that are followed by valid tag-end chars
121619
- if (closingTagName === tagName && (code === codes.greaterThan || code === codes.slash || code === codes.space || code === codes.horizontalTab)) {
121662
+ // Same-name opener followed by a tag-end char bumps depth. A line ending
121663
+ // counts too: Prettier puts a newline right after the name (`<div\n …\n>`).
121664
+ if (closingTagName === tagName &&
121665
+ (code === codes.greaterThan ||
121666
+ code === codes.slash ||
121667
+ code === codes.space ||
121668
+ code === codes.horizontalTab ||
121669
+ markdownLineEnding(code))) {
121620
121670
  depth += 1;
121621
121671
  }
121622
121672
  atLineStart = false;
@@ -121705,6 +121755,10 @@ function createTokenize(mode) {
121705
121755
  }
121706
121756
  function bodyContinuationBefore(code) {
121707
121757
  if (code === null || markdownLineEnding(code)) {
121758
+ // Empty line: outside any `{…}` expression this is CommonMark's html-block
121759
+ // terminator, so a plain block claim must pass the guard below to continue.
121760
+ if (isPlainBlockClaim && braceDepth === 0)
121761
+ pendingBlankLine = true;
121708
121762
  return bodyContinuationStart(code);
121709
121763
  }
121710
121764
  effects.enter('mdxComponentData');
@@ -121716,19 +121770,52 @@ function createTokenize(mode) {
121716
121770
  return inBodyBraceString(code);
121717
121771
  return inBodyBraceExpr(code);
121718
121772
  }
121719
- // Detect tilde fences at line start
121720
- if (atLineStart && code === codes.tilde) {
121773
+ if (isPlainBlockClaim)
121774
+ return plainClaimLineStart(code);
121775
+ return bodyLineStart(code);
121776
+ }
121777
+ // Dispatch a non-blank continuation line: fenced code at line start, else body.
121778
+ function bodyLineStart(code) {
121779
+ if (atLineStart && code === codes.tilde)
121721
121780
  return bodyAfterLineStart(code);
121722
- }
121723
- // Detect backtick fences at line start
121724
121781
  if (atLineStart && code === codes.graveAccent) {
121725
121782
  codeSpanOpenSize = 0;
121726
- atLineStart = false;
121783
+ // Leave `atLineStart` set — `countOpenTicks` needs it to tell a fence from an
121784
+ // inline code span once the run of backticks is fully counted; it's cleared once
121785
+ // that fence-vs-span decision has actually been made.
121727
121786
  return countOpenTicks(code);
121728
121787
  }
121729
121788
  atLineStart = false;
121730
121789
  return body(code);
121731
121790
  }
121791
+ // Line-start gate for plain block claims. After a blank line the block may only
121792
+ // continue on a tag line (`<…`); any markdown island (`**bold**`, `[block:…]`, a
121793
+ // fence) refuses so CommonMark html-flow reparses it exactly as it does today.
121794
+ function plainClaimLineStart(code) {
121795
+ // Leading whitespace only → treat as a blank line, matching CommonMark.
121796
+ if (code === codes.space || code === codes.horizontalTab) {
121797
+ effects.consume(code);
121798
+ return plainClaimLineStart;
121799
+ }
121800
+ if (code === null || markdownLineEnding(code)) {
121801
+ pendingBlankLine = true;
121802
+ effects.exit('mdxComponentData');
121803
+ return bodyContinuationStart(code);
121804
+ }
121805
+ // Across a blank line the block only continues onto a markup-only line; a
121806
+ // paragraph that merely starts with a tag must fall back so its markdown
121807
+ // parses and rehype-raw re-nests it into the wrapper.
121808
+ if (pendingBlankLine) {
121809
+ if (code !== codes.lessThan)
121810
+ return nok(code);
121811
+ return effects.check(markupOnlyContinuation, plainClaimContinue, nok)(code);
121812
+ }
121813
+ return bodyLineStart(code);
121814
+ }
121815
+ function plainClaimContinue(code) {
121816
+ pendingBlankLine = false;
121817
+ return bodyLineStart(code);
121818
+ }
121732
121819
  // ── Shared lazy continuation failure ───────────────────────────────────
121733
121820
  function continuationAfter(code) {
121734
121821
  if (code === null) {
@@ -121759,6 +121846,46 @@ function syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
121759
121846
  return ok(code);
121760
121847
  }
121761
121848
  }
121849
+ // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
121850
+ // spaces) at a `>`. That distinguishes a structural continuation like
121851
+ // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
121852
+ function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
121853
+ let lastNonSpace = null;
121854
+ return start;
121855
+ function start(code) {
121856
+ // Caller guarantees we are at `<` at the (already de-indented) line start.
121857
+ effects.enter(types_types.data);
121858
+ effects.consume(code);
121859
+ return afterLessThan;
121860
+ }
121861
+ function afterLessThan(code) {
121862
+ if (code === codes.slash) {
121863
+ effects.consume(code);
121864
+ return afterSlash;
121865
+ }
121866
+ return afterSlash(code);
121867
+ }
121868
+ // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
121869
+ function afterSlash(code) {
121870
+ if (asciiAlpha(code)) {
121871
+ lastNonSpace = code;
121872
+ effects.consume(code);
121873
+ return scanToLineEnd;
121874
+ }
121875
+ effects.exit(types_types.data);
121876
+ return nok(code);
121877
+ }
121878
+ function scanToLineEnd(code) {
121879
+ if (code === null || markdownLineEnding(code)) {
121880
+ effects.exit(types_types.data);
121881
+ return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
121882
+ }
121883
+ if (!markdownSpace(code))
121884
+ lastNonSpace = code;
121885
+ effects.consume(code);
121886
+ return scanToLineEnd;
121887
+ }
121888
+ }
121762
121889
  /**
121763
121890
  * Micromark extension that tokenizes MDX-like components.
121764
121891
  *
@@ -122005,6 +122132,8 @@ const mdxishInlineMdxComponents = () => tree => {
122005
122132
 
122006
122133
 
122007
122134
 
122135
+ /** Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
122136
+ const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
122008
122137
  /**
122009
122138
  * Reduce leading whitespace on all lines just enough to prevent
122010
122139
  * remark from treating indented content as code blocks (4+ spaces).
@@ -122164,10 +122293,16 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
122164
122293
  // inline, which is how ReadMe's custom components are modeled).
122165
122294
  if (!isPascal && parent.type === 'paragraph')
122166
122295
  return;
122167
- // Lowercase HTML tags are only eligible when the tokenizer claimed them
122168
- // for JSX-expression attributes. Plain HTML should stay as html nodes so
122296
+ // Lowercase HTML tags are eligible when they (or a descendant tag in their
122297
+ // content, e.g. a `.map()` body returning JSX with `key={i}`) carry a
122298
+ // JSX-expression attribute. Without this, a wrapper `<div>` with only plain
122299
+ // attributes (or none) swallows its whole nested block — including any
122300
+ // `style={{...}}`/`.map()` JSX inside it — as literal HTML text, which
122301
+ // rehype-raw's parse5 pass then garbles (it has no notion of JS expressions).
122302
+ // Plain HTML with no expressions anywhere stays as an html node so
122169
122303
  // rehype-raw handles it as normal.
122170
- if (!isPascal && !hasExpressionAttr(attributes))
122304
+ const hasNestedExpressionAttr = !selfClosing && NESTED_ATTR_EXPRESSION_RE.test(contentAfterTag);
122305
+ if (!isPascal && !hasExpressionAttr(attributes) && !hasNestedExpressionAttr)
122171
122306
  return;
122172
122307
  const closingTagStr = `</${tag}>`;
122173
122308
  // Case 1: Self-closing tag
@@ -122390,12 +122525,60 @@ function restoreSnakeCase(placeholderName, mapping) {
122390
122525
  return matchingKey ? mapping[matchingKey] : placeholderName;
122391
122526
  }
122392
122527
 
122528
+ ;// ./processor/transform/mdxish/resolve-esm-imports.ts
122529
+
122530
+ // We provide React as a default module so that components can use hooks
122531
+ // and it's a common use case. Add other common libraries here.
122532
+ const defaultModuleRegistry = { react: (external_react_default()) };
122533
+ const isRecord = (value) => typeof value === 'object' && value !== null;
122534
+ // Get the value of a named export from a module
122535
+ const getModuleExport = (module, importedName) => isRecord(module) && importedName in module ? module[importedName] : undefined;
122536
+ /**
122537
+ * Turn `import` declarations into a flat map of binding → value.
122538
+ *
122539
+ * A "binding" is a mapping of a local name to the value of it in the module exports
122540
+ * Example: `import { useState } from 'react'` yields `{ useState: React.useState }`.
122541
+ *
122542
+ * Unsupported specifiers are skipped with a warning rather than throwing, so a
122543
+ * single unknown import can't break the rest of the document.
122544
+ */
122545
+ const collectImportValues = (importDeclarations, registry = defaultModuleRegistry) => {
122546
+ const importValues = {};
122547
+ importDeclarations.forEach(declaration => {
122548
+ const libraryName = declaration.source.value;
122549
+ if (typeof libraryName !== 'string')
122550
+ return;
122551
+ if (!(libraryName in registry)) {
122552
+ // eslint-disable-next-line no-console
122553
+ console.warn(`[WARNING] Cannot resolve import "${libraryName}"; it is not a supported library.`);
122554
+ return;
122555
+ }
122556
+ const module = registry[libraryName];
122557
+ declaration.specifiers.forEach(spec => {
122558
+ // Default (`import React`) and namespace (`import * as React`) both bind
122559
+ // the whole module under the local name.
122560
+ if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
122561
+ importValues[spec.local.name] = module;
122562
+ }
122563
+ else if (spec.type === 'ImportSpecifier') {
122564
+ // Named imports (e.g. `import { useState } from 'react'`, useState is the import name)
122565
+ const importName = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value;
122566
+ if (typeof importName === 'string') {
122567
+ importValues[spec.local.name] = getModuleExport(module, importName);
122568
+ }
122569
+ }
122570
+ });
122571
+ });
122572
+ return importValues;
122573
+ };
122574
+
122393
122575
  ;// ./processor/transform/mdxish/evaluate-exports.ts
122394
122576
 
122395
122577
 
122396
122578
 
122397
122579
 
122398
122580
 
122581
+
122399
122582
  /**
122400
122583
  * Recursively extract all identifier names introduced by a binding pattern.
122401
122584
  * Handles destructuring (`{ a, b }`, `[x, y]`), rest elements (`...rest`),
@@ -122443,6 +122626,7 @@ const collectExportNames = (declaration) => {
122443
122626
  const evaluateExports = () => (tree, file) => {
122444
122627
  const programBody = [];
122445
122628
  const exportNames = [];
122629
+ const importDeclarations = [];
122446
122630
  const nodesToRemove = [];
122447
122631
  visit(tree, isMDXEsm, (node, index, parent) => {
122448
122632
  if (parent && typeof index === 'number') {
@@ -122456,6 +122640,10 @@ const evaluateExports = () => (tree, file) => {
122456
122640
  // handled the same as a named export — the inner declaration carries the
122457
122641
  // binding name
122458
122642
  estreeBody.forEach(statement => {
122643
+ if (statement.type === 'ImportDeclaration') {
122644
+ importDeclarations.push(statement);
122645
+ return;
122646
+ }
122459
122647
  let declaration = null;
122460
122648
  if (statement.type === 'ExportNamedDeclaration' && statement.declaration) {
122461
122649
  declaration = statement.declaration;
@@ -122483,9 +122671,11 @@ const evaluateExports = () => (tree, file) => {
122483
122671
  const program = { type: 'Program', sourceType: 'module', body: programBody };
122484
122672
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
122485
122673
  const { value: source } = toJs(program);
122674
+ // Make sure import values are available in the scope
122675
+ const importValues = collectImportValues(importDeclarations);
122486
122676
  // Evaluate and build on the expression source
122487
122677
  // Use react to compile JSX codes
122488
- const evaluatedExports = evaluate(`(() => { ${source}\nreturn { ${exportNames.join(', ')} }; })()`, { React: (external_react_default()) });
122678
+ const evaluatedExports = evaluate(`(() => { ${source}\nreturn { ${exportNames.join(', ')} }; })()`, { React: (external_react_default()), ...importValues });
122489
122679
  Object.assign(scope, evaluatedExports);
122490
122680
  }
122491
122681
  catch (error) {
@@ -122551,15 +122741,17 @@ const evalExpression = (expression, scope) => {
122551
122741
 
122552
122742
 
122553
122743
 
122744
+ /** True for an array containing at least one React element, e.g. the result of `.map()` returning JSX. */
122745
+ const isRenderableElementArray = (value) => Array.isArray(value) && value.some((external_react_default()).isValidElement);
122554
122746
  /** Given the type of the expression result, create the corresponding mdast node. */
122555
122747
  const createEvaluatedNode = (result, position) => {
122556
122748
  if (result === null || result === undefined) {
122557
122749
  return { type: 'text', value: '', position };
122558
122750
  }
122559
- else if (external_react_default().isValidElement(result)) {
122560
- // Convert react elements to its HTML representation
122561
- // This must come before the object check as this is a subset of it
122562
- return { type: 'html', value: (0,server_node/* renderToStaticMarkup */.qV)(result), position };
122751
+ else if (external_react_default().isValidElement(result) || isRenderableElementArray(result)) {
122752
+ // Convert react elements (or arrays of them, e.g. `.map()` returning JSX) to their HTML
122753
+ // representation. This must come before the object check as both are a subset of it.
122754
+ return { type: 'html', value: (0,server_node/* renderToStaticMarkup */.qV)(external_react_default().createElement((external_react_default()).Fragment, null, result)), position };
122563
122755
  }
122564
122756
  else if (typeof result === 'object') {
122565
122757
  return { type: 'text', value: JSON.stringify(result), position };
@@ -122599,6 +122791,47 @@ const evaluateExpressions = () => (tree, file) => {
122599
122791
  };
122600
122792
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
122601
122793
 
122794
+ ;// ./processor/transform/mdxish/evaluate-style-block-expressions.ts
122795
+
122796
+
122797
+
122798
+ // Matches a standalone `<style ...>{ <expr> }</style>` block — the JSX shape MDX evaluates
122799
+ // (typically a template-literal-wrapped CSS string) but which mdxish otherwise leaves as
122800
+ // literal, invalid-CSS text (see CX-3646).
122801
+ const STYLE_EXPRESSION_RE = /^(<style\b[^>]*>)\s*\{([\s\S]*)\}\s*(<\/style>)$/i;
122802
+ /**
122803
+ * Evaluate the JSX expression wrapping a `<style>` tag's contents (typically a template
122804
+ * literal, e.g. `<style>{\`.foo { color: red; }\`}</style>`) so the tag ends up holding plain
122805
+ * CSS text instead of the literal `{`...`}` wrapper — which the browser treats as invalid CSS
122806
+ * and drops the entire stylesheet for.
122807
+ *
122808
+ * `<style>` is one of CommonMark's "raw text" HTML elements (along with script/pre/textarea):
122809
+ * its contents are captured verbatim as a single `html` mdast node with no inner tokenization,
122810
+ * so this expression never reaches the mdxFlowExpression tokenizer or `evaluateExpressions` —
122811
+ * it has to be matched and evaluated directly against the raw node's string value.
122812
+ *
122813
+ * Must run after `evaluateExports` (needs `file.data.mdxishScope`) and before `remarkRehype`
122814
+ * turns `html` mdast nodes into raw hast strings. Skipped in safeMode.
122815
+ */
122816
+ const evaluateStyleBlockExpressions = () => (tree, file) => {
122817
+ const scope = { ...file.data.mdxishScope, React: (external_react_default()) };
122818
+ visit(tree, 'html', (node) => {
122819
+ const match = node.value.trim().match(STYLE_EXPRESSION_RE);
122820
+ if (!match)
122821
+ return;
122822
+ const [, openTag, expression, closeTag] = match;
122823
+ try {
122824
+ const css = evalExpression(expression, scope);
122825
+ node.value = `${openTag}${String(css)}${closeTag}`;
122826
+ }
122827
+ catch {
122828
+ // Evaluation failed — leave the node untouched so it round-trips as literal text.
122829
+ }
122830
+ });
122831
+ return tree;
122832
+ };
122833
+ /* harmony default export */ const evaluate_style_block_expressions = (evaluateStyleBlockExpressions);
122834
+
122602
122835
  ;// ./processor/transform/mdxish/heading-slugs.ts
122603
122836
 
122604
122837
 
@@ -124439,219 +124672,100 @@ const normalizeMdxJsxNodes = () => tree => {
124439
124672
  */
124440
124673
  const JSX_COMMENT_REGEX = /\{\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\}/g;
124441
124674
 
124442
- ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
124443
-
124675
+ ;// ./processor/transform/mdxish/remove-jsx-comments.ts
124444
124676
 
124445
124677
  /**
124446
124678
  * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
124447
124679
  *
124448
- * @param content
124449
- * @returns Content with JSX comments removed
124450
- * @example
124680
+ * `@param` content
124681
+ * `@returns` Content with JSX comments removed
124682
+ * `@example`
124451
124683
  * ```typescript
124452
- * removeJSXComments('Text { /* comment *\/ } more text')
124453
- * // Returns: 'Text more text'
124684
+ * removeJSXComments('Text {/* comment *\/} more text') // => 'Text more text'
124454
124685
  * ```
124455
124686
  */
124456
124687
  function removeJSXComments(content) {
124457
124688
  return content.replace(JSX_COMMENT_REGEX, '');
124458
124689
  }
124459
- const HTML_ELEM_PLACEHOLDER_PREFIX = '___MDXISH_HTML_ELEM_';
124460
- const HTML_ELEM_PLACEHOLDER = new RegExp(`${HTML_ELEM_PLACEHOLDER_PREFIX}(\\d+)___`, 'g');
124461
- // Matches an HTML element that starts at a line boundary and ends at a line boundary.
124462
- // Allows optional leading indentation and lazily matches until the same closing tag.
124463
- const BLOCK_HTML_RE = /(?<=^|\n)[ \t]*<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>[ \t]*(?=\n|$)/g;
124464
- /**
124465
- * Hides line-anchored HTML elements from the brace-escaping pass so we don't leak `\{`
124466
- * into rendered output (rehypeRaw renders the `\` literally, e.g. `<div>{foo</div>`).
124467
- *
124468
- * One carve-out: if an interior line at column 0 has bare text containing `{`, mdxish
124469
- * parses that line as a paragraph and the mdxExpression step would throw without an
124470
- * escape — so we leave that case to the brace balancer.
124471
- */
124472
- function protectHTMLElements(content) {
124473
- const htmlElements = [];
124474
- const protectedContent = content.replace(BLOCK_HTML_RE, match => {
124475
- // Look at the lines between the open and close tags. If any of them starts
124476
- // at column 0 with bare text (not whitespace, not another tag) and contains
124477
- // `{`, mdxish will parse that line as a paragraph and the brace as an MDX
124478
- // expression, which would throw an error. So we let the brace balancer escape it.
124479
- // Otherwise, we need to extract the sequence to protect it from the brace escaping.
124480
- const interior = match.split('\n').slice(1, -1);
124481
- const hazard = interior.some(line => line.length > 0 && line[0] !== ' ' && line[0] !== '\t' && line[0] !== '<' && line.includes('{'));
124482
- if (hazard)
124483
- return match;
124484
- htmlElements.push(match);
124485
- return `${HTML_ELEM_PLACEHOLDER_PREFIX}${htmlElements.length - 1}___`;
124486
- });
124487
- return { htmlElements, protectedContent };
124488
- }
124489
- function restoreHTMLElements(content, htmlElements) {
124490
- if (htmlElements.length === 0)
124491
- return content;
124492
- return content.replace(HTML_ELEM_PLACEHOLDER, (_m, idx) => htmlElements[parseInt(idx, 10)]);
124493
- }
124494
- const ESM_DECLARATION_START = /^(?:export|import)\b/;
124690
+
124691
+ ;// ./processor/transform/mdxish/style-object-to-css.ts
124692
+
124495
124693
  /**
124496
- * Whether a line begins a top-level `export`/`import` declaration. Its braces
124497
- * are valid JS owned by the mdxjsEsm tokenizer (which tolerates blank lines),
124498
- * so escaping them would corrupt the source and break acorn parsing.
124694
+ * CSS properties React treats as unitless — a bare number stays as-is instead of
124695
+ * getting `px` appended. Mirrors React DOM's `isUnitlessNumber` whitelist.
124696
+ * @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/CSSProperty.js
124499
124697
  */
124500
- function startsEsmDeclaration(chars, lineStart) {
124501
- return ESM_DECLARATION_START.test(chars.slice(lineStart, lineStart + 7).join(''));
124502
- }
124503
- function isBlankLine(chars, lineStart) {
124504
- for (let i = lineStart; i < chars.length; i += 1) {
124505
- if (chars[i] === '\n')
124506
- return true;
124507
- if (chars[i] !== ' ' && chars[i] !== '\t')
124508
- return false;
124509
- }
124510
- return true;
124511
- }
124698
+ const UNITLESS_CSS_PROPERTIES = new Set([
124699
+ 'animationIterationCount',
124700
+ 'aspectRatio',
124701
+ 'borderImageOutset',
124702
+ 'borderImageSlice',
124703
+ 'borderImageWidth',
124704
+ 'boxFlex',
124705
+ 'boxFlexGroup',
124706
+ 'boxOrdinalGroup',
124707
+ 'columnCount',
124708
+ 'columns',
124709
+ 'flex',
124710
+ 'flexGrow',
124711
+ 'flexPositive',
124712
+ 'flexShrink',
124713
+ 'flexNegative',
124714
+ 'flexOrder',
124715
+ 'gridArea',
124716
+ 'gridRow',
124717
+ 'gridRowEnd',
124718
+ 'gridRowSpan',
124719
+ 'gridRowStart',
124720
+ 'gridColumn',
124721
+ 'gridColumnEnd',
124722
+ 'gridColumnSpan',
124723
+ 'gridColumnStart',
124724
+ 'fontWeight',
124725
+ 'lineClamp',
124726
+ 'lineHeight',
124727
+ 'opacity',
124728
+ 'order',
124729
+ 'orphans',
124730
+ 'tabSize',
124731
+ 'widows',
124732
+ 'zIndex',
124733
+ 'zoom',
124734
+ 'fillOpacity',
124735
+ 'floodOpacity',
124736
+ 'stopOpacity',
124737
+ 'strokeDasharray',
124738
+ 'strokeDashoffset',
124739
+ 'strokeMiterlimit',
124740
+ 'strokeOpacity',
124741
+ 'strokeWidth',
124742
+ ]);
124743
+ /** Convert a camelCase (or CSS custom property) key into its kebab-case CSS name. */
124744
+ const cssPropertyName = (key) => (key.startsWith('--') ? key : key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`));
124745
+ /** React appends `px` to non-zero numeric values, except unitless and custom properties. */
124746
+ const cssPropertyValue = (key, value) => typeof value === 'number' && value !== 0 && !key.startsWith('--') && !UNITLESS_CSS_PROPERTIES.has(key)
124747
+ ? `${value}px`
124748
+ : `${value}`;
124512
124749
  /**
124513
- * Escapes unbalanced and paragraph-spanning braces so MDX doesn't trip on them.
124750
+ * True for a value that should be serialized as a style object rather than passed through
124751
+ * as an already-CSS string. React elements are excluded since they're valid objects too.
124514
124752
  */
124515
- function escapeProblematicBraces(content) {
124516
- const { htmlElements, protectedContent } = protectHTMLElements(content);
124517
- let strDelim = null;
124518
- let strEscaped = false;
124519
- // Track position of last newline (outside strings) to detect blank lines
124520
- // -2 means no recent newline
124521
- let lastNewlinePos = -2;
124522
- // Character state machine trackers
124523
- const toEscape = new Set();
124524
- // Convert to array of Unicode code points so that emojis and multi-byte characters are correctly tracked
124525
- const chars = Array.from(protectedContent);
124526
- const openStack = [];
124527
- // Whether the current top-level statement is an `export`/`import` declaration.
124528
- let insideEsmDeclaration = false;
124529
- for (let i = 0; i < chars.length; i += 1) {
124530
- const ch = chars[i];
124531
- // At a top-level (brace depth 0) line start, decide whether we're inside an
124532
- // ESM declaration. The flag persists across the declaration's own lines.
124533
- if (openStack.length === 0 && (i === 0 || chars[i - 1] === '\n')) {
124534
- if (startsEsmDeclaration(chars, i))
124535
- insideEsmDeclaration = true;
124536
- else if (isBlankLine(chars, i))
124537
- insideEsmDeclaration = false;
124538
- }
124539
- // Track string delimiters inside expressions to ignore braces within them
124540
- if (openStack.length > 0) {
124541
- if (strDelim) {
124542
- if (strEscaped)
124543
- strEscaped = false;
124544
- else if (ch === '\\')
124545
- strEscaped = true;
124546
- else if (ch === strDelim)
124547
- strDelim = null;
124548
- // eslint-disable-next-line no-continue
124549
- continue;
124550
- }
124551
- if (ch === '"' || ch === "'" || ch === '`') {
124552
- strDelim = ch;
124553
- // eslint-disable-next-line no-continue
124554
- continue;
124555
- }
124556
- if (ch === '\n') {
124557
- if (lastNewlinePos >= 0) {
124558
- const between = chars.slice(lastNewlinePos + 1, i).join('');
124559
- if (/^[ \t]*$/.test(between)) {
124560
- openStack.forEach(entry => { entry.hasBlankLine = true; });
124561
- }
124562
- }
124563
- lastNewlinePos = i;
124564
- }
124565
- }
124566
- // Skip already-escaped braces (odd run of preceding backslashes).
124567
- if (ch === '{' || ch === '}') {
124568
- let bs = 0;
124569
- for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
124570
- bs += 1;
124571
- if (bs % 2 === 1) {
124572
- // eslint-disable-next-line no-continue
124573
- continue;
124574
- }
124575
- }
124576
- if (ch === '{') {
124577
- // `=` (after whitespace) before `{` ⇒ JSX attribute expression. The
124578
- // mdxComponent tokenizer captures the whole component, so blank lines
124579
- // inside attribute values are harmless. Nested `{` inherits the flag.
124580
- let isAttrExpr = false;
124581
- for (let j = i - 1; j >= 0; j -= 1) {
124582
- const pc = chars[j];
124583
- if (pc === '=') {
124584
- isAttrExpr = true;
124585
- break;
124586
- }
124587
- if (pc !== ' ' && pc !== '\t')
124588
- break;
124589
- }
124590
- // Nested `{ ... }` inside an attribute value (e.g. `data={[{ ... }]}` or
124591
- // `data={{ a: { b: 1 } }}`) must inherit the same exemption; only the
124592
- // outer `{` is directly after `=`.
124593
- if (!isAttrExpr && openStack.length > 0 && openStack[openStack.length - 1].isAttrExpr) {
124594
- isAttrExpr = true;
124595
- }
124596
- openStack.push({ pos: i, hasBlankLine: false, isAttrExpr, isEsmDeclaration: insideEsmDeclaration });
124597
- lastNewlinePos = -2;
124598
- }
124599
- else if (ch === '}') {
124600
- if (openStack.length > 0) {
124601
- const entry = openStack.pop();
124602
- // The declaration's braces are closed; later top-level braces are not ESM.
124603
- if (openStack.length === 0)
124604
- insideEsmDeclaration = false;
124605
- // Pure `{/* ... */}` comments are handled downstream by the jsxComment
124606
- // tokenizer — escaping their braces would prevent it from running.
124607
- const isPureJsxComment = chars[entry.pos + 1] === '/' &&
124608
- chars[entry.pos + 2] === '*' &&
124609
- chars[i - 1] === '/' &&
124610
- chars[i - 2] === '*';
124611
- if (entry.hasBlankLine && !isPureJsxComment && !entry.isAttrExpr && !entry.isEsmDeclaration) {
124612
- toEscape.add(entry.pos);
124613
- toEscape.add(i);
124614
- }
124615
- }
124616
- else {
124617
- toEscape.add(i);
124618
- }
124619
- }
124620
- else if (ch === ';' && openStack.length === 0) {
124621
- // A top-level `;` ends the current statement, ESM or otherwise.
124622
- insideEsmDeclaration = false;
124623
- }
124624
- }
124625
- // Anything still open is unbalanced.
124626
- openStack.forEach(entry => {
124627
- if (!entry.isEsmDeclaration)
124628
- toEscape.add(entry.pos);
124629
- });
124630
- // Reconstruct the content with the escaped braces.
124631
- const escapedContent = toEscape.size === 0 ? protectedContent : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
124632
- return restoreHTMLElements(escapedContent, htmlElements);
124633
- }
124753
+ const style_object_to_css_isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !external_react_default().isValidElement(value);
124634
124754
  /**
124635
- * Preprocesses JSX-like markdown content before parsing.
124636
- *
124637
- * JSX attribute expressions (`href={baseUrl}`) are no longer rewritten here —
124638
- * they flow through the tokenizer as `mdxJsxAttributeValueExpression` nodes
124639
- * and are evaluated at the hast handler step.
124640
- *
124641
- * @param content
124642
- * @returns Preprocessed content ready for markdown parsing
124755
+ * Serialize a React-style inline style object (`{ color: "red", fontSize: 12 }`) into a
124756
+ * CSS declaration string (`color:red;font-size:12px`), the shape hast/HTML expects for a
124757
+ * `style` attribute. Empty (`undefined`/`null`/`''`) entries are dropped.
124643
124758
  */
124644
- function preprocessJSXExpressions(content) {
124645
- const { protectedCode, protectedContent } = protectCodeBlocks(content);
124646
- let processed = escapeProblematicBraces(protectedContent);
124647
- processed = restoreCodeBlocks(processed, protectedCode);
124648
- return processed;
124649
- }
124759
+ const styleObjectToCssText = (style) => Object.entries(style)
124760
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
124761
+ .map(([key, value]) => `${cssPropertyName(key)}:${cssPropertyValue(key, value)}`)
124762
+ .join(';');
124650
124763
 
124651
124764
  ;// ./processor/transform/mdxish/resolve-deferred-attribute-expression-props.ts
124652
124765
 
124653
124766
 
124654
124767
 
124768
+
124655
124769
  /**
124656
124770
  * Resolve attribute expressions that `mdxJsxElementHandler` deferred.
124657
124771
  *
@@ -124673,7 +124787,10 @@ const resolveDeferredAttributeExpressionProps = () => (tree, file) => {
124673
124787
  const properties = node.properties;
124674
124788
  Object.entries(deferredExpressions).forEach(([name, source]) => {
124675
124789
  try {
124676
- properties[name] = evalExpression(source, scope);
124790
+ const result = evalExpression(source, scope);
124791
+ // hast/HTML `style` is a plain CSS string; a `style={{...}}` expression evaluates to
124792
+ // a JS object, which must be serialized or it renders as the literal "[object Object]".
124793
+ properties[name] = name === 'style' && style_object_to_css_isPlainObject(result) ? styleObjectToCssText(result) : result;
124677
124794
  }
124678
124795
  catch {
124679
124796
  // Evaluation failed — fall back to the raw expression source so the attribute
@@ -124993,13 +125110,13 @@ function normalizeTableSeparator(content) {
124993
125110
  ;// ./processor/transform/mdxish/terminate-html-flow-blocks.ts
124994
125111
 
124995
125112
 
125113
+
124996
125114
  const STANDALONE_HTML_LINE_REGEX = /^(<[a-z][^<>]*>|<\/[a-z][^<>]*>)+\s*$/;
124997
125115
  const HTML_LINE_WITH_CONTENT_REGEX = /^<[a-z][^<>]*>.*<\/[a-z][^<>]*>(?:[^<]*)$/;
124998
- const terminate_html_flow_blocks_TABLE_STRUCTURE_TAGS = ['table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'caption', 'colgroup'];
124999
125116
  // Tags whose contents must be preserved as is, inserting a blank line after the
125000
125117
  // opener corrupts the payload.
125001
125118
  // htmlRawNames here refer to <pre>, <textarea>, <script>, <style>
125002
- const RAW_CONTENT_TAGS = [...htmlRawNames, ...terminate_html_flow_blocks_TABLE_STRUCTURE_TAGS];
125119
+ const RAW_CONTENT_TAGS = [...htmlRawNames, ...HTML_TABLE_STRUCTURE_TAGS];
125003
125120
  // The `(?=[\s/>])` lookahead avoids false matches on lookalike names like `<script-foo>`.
125004
125121
  const RAW_CONTENT_TAG_MATCHERS = RAW_CONTENT_TAGS.map(tag => ({
125005
125122
  open: new RegExp(`<${tag}(?=[\\s/>])[^>]*?(?<!/)>`, 'gi'),
@@ -125621,6 +125738,85 @@ function jsxTable() {
125621
125738
  ;// ./lib/micromark/jsx-table/index.ts
125622
125739
 
125623
125740
 
125741
+ ;// ./lib/micromark/mdx-expression-lenient/syntax.ts
125742
+
125743
+
125744
+ /**
125745
+ * Lenient MDX text-expression tokenizer (agnostic / no acorn).
125746
+ *
125747
+ * Matches a balanced `{ ... }` run — tracking nested braces and spanning soft
125748
+ * line breaks — and emits the standard `mdxTextExpression*` tokens so the
125749
+ * upstream `mdxExpressionFromMarkdown()` builds the node.
125750
+ *
125751
+ * Reimplements the official micromark mdxExpression, but an unbalanced brace that
125752
+ * reaches end of input returns `nok` instead of throwing: micromark rolls back
125753
+ * and the `{` renders as literal text, making the pipeline forgiving of stray
125754
+ * braces that upstream would hard-error on.
125755
+ */
125756
+ function tokenizeTextExpression(effects, ok, nok) {
125757
+ let depth = 0;
125758
+ return start;
125759
+ function start(code) {
125760
+ if (code !== codes.leftCurlyBrace)
125761
+ return nok(code);
125762
+ effects.enter('mdxTextExpression');
125763
+ effects.enter('mdxTextExpressionMarker');
125764
+ effects.consume(code);
125765
+ effects.exit('mdxTextExpressionMarker');
125766
+ return before;
125767
+ }
125768
+ function before(code) {
125769
+ if (code === codes.eof) {
125770
+ effects.exit('mdxTextExpression');
125771
+ return nok(code);
125772
+ }
125773
+ if (markdownLineEnding(code)) {
125774
+ effects.enter('lineEnding');
125775
+ effects.consume(code);
125776
+ effects.exit('lineEnding');
125777
+ return before;
125778
+ }
125779
+ if (code === codes.rightCurlyBrace && depth === 0) {
125780
+ return close(code);
125781
+ }
125782
+ effects.enter('mdxTextExpressionChunk');
125783
+ return inside(code);
125784
+ }
125785
+ function inside(code) {
125786
+ if (code === codes.eof || markdownLineEnding(code)) {
125787
+ effects.exit('mdxTextExpressionChunk');
125788
+ return before(code);
125789
+ }
125790
+ if (code === codes.rightCurlyBrace && depth === 0) {
125791
+ effects.exit('mdxTextExpressionChunk');
125792
+ return close(code);
125793
+ }
125794
+ if (code === codes.leftCurlyBrace)
125795
+ depth += 1;
125796
+ else if (code === codes.rightCurlyBrace)
125797
+ depth -= 1;
125798
+ effects.consume(code);
125799
+ return inside;
125800
+ }
125801
+ function close(code) {
125802
+ effects.enter('mdxTextExpressionMarker');
125803
+ effects.consume(code);
125804
+ effects.exit('mdxTextExpressionMarker');
125805
+ effects.exit('mdxTextExpression');
125806
+ return ok;
125807
+ }
125808
+ }
125809
+ function mdxExpressionLenient() {
125810
+ return {
125811
+ text: {
125812
+ [codes.leftCurlyBrace]: {
125813
+ name: 'mdxTextExpression',
125814
+ tokenize: tokenizeTextExpression,
125815
+ },
125816
+ },
125817
+ };
125818
+ }
125819
+
125624
125820
  ;// ./lib/utils/mdxish/mdxish-load-components.ts
125625
125821
 
125626
125822
 
@@ -125718,6 +125914,7 @@ function loadComponents() {
125718
125914
 
125719
125915
 
125720
125916
 
125917
+
125721
125918
 
125722
125919
 
125723
125920
  const defaultTransformers = [
@@ -125733,7 +125930,7 @@ const defaultTransformers = [
125733
125930
  * 1. Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
125734
125931
  * 2. Terminate HTML flow blocks so subsequent content isn't swallowed
125735
125932
  * 3. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
125736
- * 4. Escape problematic braces so MDX expression parsing doesn't choke
125933
+ * 4. Normalize compact ATX headings (e.g., `#Heading` `# Heading`)
125737
125934
  * 5. Replace snake_case component names with parser-safe placeholders
125738
125935
  */
125739
125936
  function preprocessContent(content, opts) {
@@ -125742,7 +125939,6 @@ function preprocessContent(content, opts) {
125742
125939
  result = terminateHtmlFlowBlocks(result);
125743
125940
  result = closeSelfClosingHtmlTags(result);
125744
125941
  result = normalizeCompactHeadings(result);
125745
- result = preprocessJSXExpressions(result);
125746
125942
  return processSnakeCaseComponent(result, { knownComponents });
125747
125943
  }
125748
125944
  function mdxishAstProcessor(mdContent, opts = {}) {
@@ -125759,12 +125955,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
125759
125955
  acc[key] = String(value);
125760
125956
  return acc;
125761
125957
  }, {});
125762
- // Get mdxExpression extension and remove its flow construct to prevent
125763
- // `{...}` from interrupting paragraphs (which breaks multiline magic blocks)
125764
- const mdxExprExt = mdxExpression({ allowEmpty: true });
125765
- const mdxExprTextOnly = {
125766
- text: mdxExprExt.text,
125767
- };
125958
+ // Parser extension for MDX expressions {}
125959
+ const mdxExprTextOnly = mdxExpressionLenient();
125768
125960
  const micromarkExts = [
125769
125961
  jsxTable(),
125770
125962
  magicBlock(),
@@ -125873,6 +126065,7 @@ function mdxish(mdContent, opts = {}) {
125873
126065
  .use(safeMode ? undefined : evaluate_exports) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
125874
126066
  .use(remarkBreaks) // Must precede evaluateExpressions to avoid splitting the \n in an evaluated template literal into a <br> node
125875
126067
  .use(safeMode ? undefined : evaluate_expressions) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
126068
+ .use(safeMode ? undefined : evaluate_style_block_expressions) // Evaluate `<style>{`...`}</style>` template literals into plain CSS
125876
126069
  .use(variables_code, { variables }) // Resolve <<...>> and {user.*} inside code and inline code nodes
125877
126070
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
125878
126071
  .use(preserveBooleanProperties) // RehypeRaw converts boolean properties to empty strings