@readme/markdown 14.10.2 → 14.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -12060,10 +12060,55 @@ const Columns = ({ children, layout = 'auto' }) => {
12060
12060
  };
12061
12061
  /* harmony default export */ const components_Columns = (Columns);
12062
12062
 
12063
+ ;// ./components/Embed/normalizeYouTubeUrl.ts
12064
+ // YouTube watch/share URLs (youtube.com/watch?v=ID, youtu.be/ID) set X-Frame-Options
12065
+ // and refuse to be framed. The player only allows embedding via youtube.com/embed/ID.
12066
+ const YOUTUBE_HOSTS = new Set(['youtube.com', 'www.youtube.com', 'm.youtube.com', 'youtu.be', 'www.youtu.be', 'm.youtu.be']);
12067
+ // /embed/ accepts `start` (integer seconds), not the watch-page `t` value which may carry units (e.g. 596s, 1h2m3s).
12068
+ const toStartSeconds = (timestamp) => {
12069
+ const [, hours, minutes, seconds] = timestamp.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/) ?? [];
12070
+ const total = (Number(hours) || 0) * 3600 + (Number(minutes) || 0) * 60 + (Number(seconds) || 0);
12071
+ return total > 0 ? String(total) : null;
12072
+ };
12073
+ const extractVideoId = (parsed) => {
12074
+ if (parsed.hostname.endsWith('youtu.be'))
12075
+ return parsed.pathname.slice(1).split('/')[0] || null;
12076
+ if (parsed.pathname.startsWith('/watch'))
12077
+ return parsed.searchParams.get('v');
12078
+ if (parsed.pathname.startsWith('/shorts/'))
12079
+ return parsed.pathname.slice('/shorts/'.length).split('/')[0] || null;
12080
+ return null;
12081
+ };
12082
+ // Most youtube links we get from the browser are watch URLs, like https://www.youtube.com/watch?v=dQw4w9WgXcQ
12083
+ // These /watch URLs won't work in an iframe, so we need to convert them to the embed URL if iframe is used
12084
+ const normalizeYouTubeUrlToEmbedUrl = (url) => {
12085
+ let parsed;
12086
+ try {
12087
+ parsed = new URL(url);
12088
+ }
12089
+ catch {
12090
+ return url;
12091
+ }
12092
+ if (!YOUTUBE_HOSTS.has(parsed.hostname))
12093
+ return url;
12094
+ if (parsed.pathname.startsWith('/embed/'))
12095
+ return url;
12096
+ const videoId = extractVideoId(parsed);
12097
+ if (!videoId)
12098
+ return url;
12099
+ const embed = new URL(`https://www.youtube.com/embed/${videoId}`);
12100
+ const timestamp = parsed.searchParams.get('start') || parsed.searchParams.get('t');
12101
+ const start = timestamp ? toStartSeconds(timestamp) : null;
12102
+ if (start)
12103
+ embed.searchParams.set('start', start);
12104
+ return embed.toString();
12105
+ };
12106
+
12063
12107
  ;// ./components/Embed/index.tsx
12064
12108
  /* eslint-disable no-param-reassign */
12065
12109
  /* eslint-disable react/jsx-props-no-spreading */
12066
12110
 
12111
+
12067
12112
  const Favicon = ({ src, alt = 'favicon', ...attr }) => (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("img", { ...attr, alt: alt, height: "14", src: src, width: "14" }));
12068
12113
  const IFRAME_DERIVABLE_TYPES = new Set(['youtube', 'jsfiddle', 'pdf']);
12069
12114
  // HTML width/height attrs accept bare numbers (interpreted as px), but CSS does not, convert.
@@ -12097,7 +12142,8 @@ const Embed = ({ lazy = true, url, html, providerName, providerUrl, title, ifram
12097
12142
  // Fall back to a direct iframe for URL-derivable embed types when html is missing.
12098
12143
  const renderTypeAsIframe = !html && !explicitOptOut && url && typeOfEmbed && IFRAME_DERIVABLE_TYPES.has(typeOfEmbed);
12099
12144
  if (iframe || renderTypeAsIframe) {
12100
- return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("iframe", { ...spreadAttrs, src: url, style: iframeStyle, title: title });
12145
+ const src = normalizeYouTubeUrlToEmbedUrl(url);
12146
+ return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("iframe", { ...spreadAttrs, src: src, style: iframeStyle, title: title });
12101
12147
  }
12102
12148
  if (!providerUrl && url)
12103
12149
  providerUrl = new URL(url).hostname
@@ -100750,6 +100796,12 @@ const types_types = /** @type {const} */ ({
100750
100796
 
100751
100797
 
100752
100798
 
100799
+
100800
+ // Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
100801
+ // section, …) always start a block, so they stay flow even with trailing
100802
+ // content. Other lowercase tags (i, span, …) follow the type-7 rule and only
100803
+ // stay flow when nothing trails the close tag.
100804
+ const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
100753
100805
  const syntax_nonLazyContinuationStart = {
100754
100806
  tokenize: syntax_tokenizeNonLazyContinuationStart,
100755
100807
  partial: true,
@@ -100786,11 +100838,12 @@ const mdxComponentTextConstruct = {
100786
100838
  * lowercase HTML tags that carry at least one `{…}` attribute expression.
100787
100839
  * Multi-line, concrete, `afterClose` consumes the rest of the line.
100788
100840
  *
100789
- * **Text** — runs inside paragraphs / inline context. Claims *only* lowercase
100790
- * tags with brace attributes (PascalCase is intentionally flow-only, matching
100791
- * how ReadMe's custom components are authored). Aborts on line endings (inline
100792
- * constructs don't span lines) and exits immediately after `</tag>` so the
100793
- * paragraph's inline parser picks up the trailing text.
100841
+ * **Text** — runs inside paragraphs / inline context. Claims lowercase tags and
100842
+ * inline PascalCase components (`INLINE_COMPONENT_TAGS` Anchor, Glossary), both
100843
+ * gated on at least one `{…}` brace attribute. All other PascalCase stays
100844
+ * flow-only, matching how ReadMe's custom components are authored. Aborts on line
100845
+ * endings (inline constructs don't span lines) and exits immediately after
100846
+ * `</tag>` so the paragraph's inline parser picks up the trailing text.
100794
100847
  */
100795
100848
  function createTokenize(mode) {
100796
100849
  const isFlow = mode === 'flow';
@@ -100802,8 +100855,9 @@ function createTokenize(mode) {
100802
100855
  let closingTagName = '';
100803
100856
  // For lowercase tags we only want to claim the block if it uses JSX
100804
100857
  // attribute expression syntax (`attr={...}`). Plain HTML should fall
100805
- // through to CommonMark html-flow. Uppercase tags are always claimed
100806
- // (flow only PascalCase is not accepted in text mode).
100858
+ // through to CommonMark html-flow. Flow mode claims any PascalCase block
100859
+ // component; text mode claims only inline PascalCase components
100860
+ // (INLINE_COMPONENT_TAGS — Anchor, Glossary), also brace-gated.
100807
100861
  let isLowercaseTag = false;
100808
100862
  let sawBraceAttr = false;
100809
100863
  // Code span tracking
@@ -100814,6 +100868,9 @@ function createTokenize(mode) {
100814
100868
  let fenceLength = 0;
100815
100869
  let fenceCloseLength = 0;
100816
100870
  let atLineStart = false;
100871
+ // True once this construct consumes any line ending; lets `afterClose`
100872
+ // treat only single-line lowercase tags as inline candidates.
100873
+ let sawLineEnding = false;
100817
100874
  // Bail when the opener line has unmatched tag-like tokens in its body.
100818
100875
  // `<Foo>_<Bar>.csv` leaves opens > closes; matched shapes like
100819
100876
  // `<Callout>x <strong>y</strong>` balance to 0. Without this,
@@ -100964,13 +101021,13 @@ function createTokenize(mode) {
100964
101021
  }
100965
101022
  // ── Tag name parsing ───────────────────────────────────────────────────
100966
101023
  function tagNameFirst(code) {
100967
- // Uppercase A-Z → PascalCase MDX component. Flow-only PascalCase
100968
- // components are block elements in ReadMe's authoring model.
101024
+ // Uppercase A-Z → PascalCase MDX component. Flow mode claims block
101025
+ // components; text mode only claims inline components (Anchor, Glossary),
101026
+ // which is enforced once the full name is known in `tagNameRest`.
100969
101027
  if (code !== null && code >= codes.uppercaseA && code <= codes.uppercaseZ) {
100970
- if (!isFlow)
100971
- return nok(code);
100972
101028
  tagName = String.fromCharCode(code);
100973
101029
  isLowercaseTag = false;
101030
+ sawBraceAttr = false;
100974
101031
  effects.consume(code);
100975
101032
  return tagNameRest;
100976
101033
  }
@@ -100998,10 +101055,18 @@ function createTokenize(mode) {
100998
101055
  effects.consume(code);
100999
101056
  return tagNameRest;
101000
101057
  }
101001
- // Tag name complete — check exclusions
101002
- if (TOKENIZER_MDX_COMPONENT_EXCLUDED_TAGS.has(tagName)) {
101058
+ // Tag name complete — decide whether this tokenizer claims the tag.
101059
+ // Three cases: lowercase tags are always candidates (brace-gated later in
101060
+ // `afterOpenTagName`); flow-mode PascalCase claims any block component
101061
+ // except those with a dedicated tokenizer; text-mode PascalCase claims
101062
+ // only inline components (Anchor, Glossary).
101063
+ const claimable = isLowercaseTag
101064
+ ? true
101065
+ : isFlow
101066
+ ? !TOKENIZER_MDX_COMPONENT_EXCLUDED_TAGS.has(tagName)
101067
+ : INLINE_COMPONENT_TAGS.has(tagName);
101068
+ if (!claimable)
101003
101069
  return nok(code);
101004
- }
101005
101070
  depth = 1;
101006
101071
  return afterOpenTagName(code);
101007
101072
  }
@@ -101009,6 +101074,10 @@ function createTokenize(mode) {
101009
101074
  function afterOpenTagName(code) {
101010
101075
  if (code === null)
101011
101076
  return nok(code);
101077
+ // Everything except a flow-mode PascalCase block component must carry a
101078
+ // `{…}` brace attribute to be claimed; plain HTML falls through to
101079
+ // CommonMark.
101080
+ const requiresBraceAttr = isLowercaseTag || !isFlow;
101012
101081
  if (markdownLineEnding(code)) {
101013
101082
  if (!isFlow)
101014
101083
  return nok(code);
@@ -101017,14 +101086,14 @@ function createTokenize(mode) {
101017
101086
  }
101018
101087
  // Self-closing />
101019
101088
  if (code === codes.slash) {
101020
- if (isLowercaseTag && !sawBraceAttr)
101089
+ if (requiresBraceAttr && !sawBraceAttr)
101021
101090
  return nok(code);
101022
101091
  effects.consume(code);
101023
101092
  return selfCloseGt;
101024
101093
  }
101025
101094
  // End of opening tag
101026
101095
  if (code === codes.greaterThan) {
101027
- if (isLowercaseTag && !sawBraceAttr)
101096
+ if (requiresBraceAttr && !sawBraceAttr)
101028
101097
  return nok(code);
101029
101098
  effects.consume(code);
101030
101099
  onOpenerLine = isFlow;
@@ -101088,6 +101157,7 @@ function createTokenize(mode) {
101088
101157
  return effects.check(syntax_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
101089
101158
  }
101090
101159
  function openTagContinuationNonLazy(code) {
101160
+ sawLineEnding = true;
101091
101161
  effects.enter(types_types.lineEnding);
101092
101162
  effects.consume(code);
101093
101163
  effects.exit(types_types.lineEnding);
@@ -101211,6 +101281,7 @@ function createTokenize(mode) {
101211
101281
  return effects.check(syntax_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
101212
101282
  }
101213
101283
  function fencedCodeContinuationNonLazy(code) {
101284
+ sawLineEnding = true;
101214
101285
  effects.enter(types_types.lineEnding);
101215
101286
  effects.consume(code);
101216
101287
  effects.exit(types_types.lineEnding);
@@ -101367,6 +101438,12 @@ function createTokenize(mode) {
101367
101438
  effects.exit('mdxComponent');
101368
101439
  return ok(code);
101369
101440
  }
101441
+ // A single-line type-7 lowercase tag with content trailing its close is
101442
+ // inline, so `<i …></i> <a>…</a>` stays on one line instead of splitting
101443
+ // into separate blocks. Raw/block tags (pre, div, …) stay flow.
101444
+ if (isLowercaseTag && !sawLineEnding && !htmlFlowTagNames.has(tagName)) {
101445
+ return afterCloseInlineCandidate(code);
101446
+ }
101370
101447
  if (code === null || markdownLineEnding(code)) {
101371
101448
  effects.exit('mdxComponentData');
101372
101449
  effects.exit('mdxComponent');
@@ -101377,11 +101454,26 @@ function createTokenize(mode) {
101377
101454
  effects.consume(code);
101378
101455
  return afterClose;
101379
101456
  }
101457
+ // Whitespace-only to the line ending keeps the flow block; any other
101458
+ // trailing char means inline content, so refuse and let inline parsing run.
101459
+ function afterCloseInlineCandidate(code) {
101460
+ if (code === null || markdownLineEnding(code)) {
101461
+ effects.exit('mdxComponentData');
101462
+ effects.exit('mdxComponent');
101463
+ return ok(code);
101464
+ }
101465
+ if (markdownSpace(code)) {
101466
+ effects.consume(code);
101467
+ return afterCloseInlineCandidate;
101468
+ }
101469
+ return nok(code);
101470
+ }
101380
101471
  // ── Body continuation (line endings) ───────────────────────────────────
101381
101472
  function bodyContinuationStart(code) {
101382
101473
  return effects.check(syntax_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
101383
101474
  }
101384
101475
  function bodyContinuationNonLazy(code) {
101476
+ sawLineEnding = true;
101385
101477
  effects.enter(types_types.lineEnding);
101386
101478
  effects.consume(code);
101387
101479
  effects.exit(types_types.lineEnding);
@@ -101451,12 +101543,13 @@ function syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
101451
101543
  * self-closing `<Component />`). Prevents CommonMark from fragmenting them
101452
101544
  * across multiple HTML / paragraph nodes.
101453
101545
  *
101454
- * **Text (inline)** — registers only for lowercase tags with brace attrs
101455
- * (e.g. `Start <a href={url}>here</a> end`). Picks them up during inline
101456
- * parsing so they render inline inside their paragraph, then are rewritten
101457
- * to `mdxJsxTextElement` by the `components/inline-html` transformer.
101458
- * PascalCase is intentionally flow-only; ReadMe's custom components are
101459
- * authored as block-level elements.
101546
+ * **Text (inline)** — registers for lowercase tags and inline PascalCase
101547
+ * components (Anchor, Glossary) that carry brace attrs (e.g.
101548
+ * `Start <a href={url}>here</a> end`, `<Anchor href={url}>x</Anchor>`). Picks
101549
+ * them up during inline parsing so they render inline inside their paragraph,
101550
+ * then are rewritten to `mdxJsxTextElement` by the `components/inline-html`
101551
+ * transformer. All other PascalCase is flow-only; ReadMe's custom components
101552
+ * are authored as block-level elements.
101460
101553
  *
101461
101554
  * Excludes tags handled by dedicated tokenizers: Table, HTMLBlock, Glossary,
101462
101555
  * Anchor.
@@ -101575,12 +101668,12 @@ const parsePhrasingChildren = (value, safeMode) => {
101575
101668
  return first?.type === 'paragraph' ? first.children : [];
101576
101669
  };
101577
101670
  /**
101578
- * Attempt to promote a lowercase inline `html` node to an
101579
- * `mdxJsxTextElement`. Scope is deliberately narrow: only lowercase tags
101580
- * carrying `{…}` attribute expressions (what the `mdxComponent` text
101581
- * tokenizer claims). PascalCase components even when inline — stay with
101582
- * `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`, matching
101583
- * ReadMe's flow-level component authoring model.
101671
+ * Attempt to promote an inline `html` node to an `mdxJsxTextElement`. Scope
101672
+ * mirrors what the `mdxComponent` text tokenizer claims: lowercase tags and
101673
+ * inline PascalCase components (`INLINE_COMPONENT_TAGS` — Anchor, Glossary),
101674
+ * both carrying `{…}` attribute expressions. All other PascalCase components
101675
+ * stay with `mdxishComponentBlocks` so they remain `mdxJsxFlowElement`,
101676
+ * matching ReadMe's flow-level component authoring model.
101584
101677
  *
101585
101678
  * Returns the original node unchanged when it doesn't qualify.
101586
101679
  */
@@ -101589,12 +101682,11 @@ const promoteInlineHtml = (node, parseOpts, safeMode) => {
101589
101682
  if (!parsed)
101590
101683
  return node;
101591
101684
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
101592
- // PascalCase stays flow-level; handled by mdxishComponentBlocks.
101593
- if (isPascalCase(tag))
101594
- return node;
101595
- // Dedicated tokenizers (Table, HTMLBlock, Glossary, Anchor) handle their
101596
- // own tags — don't hijack them here.
101597
- if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
101685
+ // Non-inline PascalCase stays flow-level (handled by mdxishComponentBlocks)
101686
+ // and dedicated tokenizers (Table, HTMLBlock) own their tags. Inline
101687
+ // components (Anchor, Glossary) with expression attrs are promoted here —
101688
+ // the text tokenizer captures them as a single html node.
101689
+ if (isPascalCase(tag) && !INLINE_COMPONENT_TAGS.has(tag))
101598
101690
  return node;
101599
101691
  // Plain inline HTML (no expressions) stays as an html node so rehype-raw
101600
101692
  // parses it with parse5 as normal.
@@ -101618,14 +101710,15 @@ const promoteInlineHtml = (node, parseOpts, safeMode) => {
101618
101710
  * Runs after `mdxishComponentBlocks`, which skips paragraph-parented html
101619
101711
  * nodes and leaves them for this pass. Two producers put html nodes inside
101620
101712
  * paragraphs:
101621
- * 1. The `mdxComponent` text tokenizer, for lowercase tags with `{…}`
101622
- * attribute expressions (e.g. `<a href={url}>here</a>`).
101713
+ * 1. The `mdxComponent` text tokenizer, for lowercase tags and inline
101714
+ * PascalCase components (Anchor, Glossary) with `{}` attribute
101715
+ * expressions (e.g. `<a href={url}>here</a>`, `<Anchor href={url}>x</Anchor>`).
101623
101716
  * 2. CommonMark's built-in html-text tokenizer, for PascalCase components
101624
101717
  * (e.g. a single html node for self-closing `<C />`).
101625
101718
  *
101626
- * Eligibility mirrors `mdxishComponentBlocks`: lowercase tags only promote
101627
- * when they carry an expression attribute; plain inline HTML like
101628
- * `<a href="x">` stays as an html node for rehype-raw.
101719
+ * Eligibility mirrors `mdxishComponentBlocks`: a tag only promotes when it
101720
+ * carries an expression attribute and isn't a non-inline PascalCase component;
101721
+ * plain inline HTML like `<a href="x">` stays as an html node for rehype-raw.
101629
101722
  */
101630
101723
  const mdxishInlineMdxHtmlBlocks = (opts = {}) => tree => {
101631
101724
  const safeMode = !!opts.safeMode;
@@ -102073,12 +102166,60 @@ function restoreSnakeCase(placeholderName, mapping) {
102073
102166
  return matchingKey ? mapping[matchingKey] : placeholderName;
102074
102167
  }
102075
102168
 
102169
+ ;// ./processor/transform/mdxish/resolve-esm-imports.ts
102170
+
102171
+ // We provide React as a default module so that components can use hooks
102172
+ // and it's a common use case. Add other common libraries here.
102173
+ const defaultModuleRegistry = { react: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()) };
102174
+ const isRecord = (value) => typeof value === 'object' && value !== null;
102175
+ // Get the value of a named export from a module
102176
+ const getModuleExport = (module, importedName) => isRecord(module) && importedName in module ? module[importedName] : undefined;
102177
+ /**
102178
+ * Turn `import` declarations into a flat map of binding → value.
102179
+ *
102180
+ * A "binding" is a mapping of a local name to the value of it in the module exports
102181
+ * Example: `import { useState } from 'react'` yields `{ useState: React.useState }`.
102182
+ *
102183
+ * Unsupported specifiers are skipped with a warning rather than throwing, so a
102184
+ * single unknown import can't break the rest of the document.
102185
+ */
102186
+ const collectImportValues = (importDeclarations, registry = defaultModuleRegistry) => {
102187
+ const importValues = {};
102188
+ importDeclarations.forEach(declaration => {
102189
+ const libraryName = declaration.source.value;
102190
+ if (typeof libraryName !== 'string')
102191
+ return;
102192
+ if (!(libraryName in registry)) {
102193
+ // eslint-disable-next-line no-console
102194
+ console.warn(`[WARNING] Cannot resolve import "${libraryName}"; it is not a supported library.`);
102195
+ return;
102196
+ }
102197
+ const module = registry[libraryName];
102198
+ declaration.specifiers.forEach(spec => {
102199
+ // Default (`import React`) and namespace (`import * as React`) both bind
102200
+ // the whole module under the local name.
102201
+ if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
102202
+ importValues[spec.local.name] = module;
102203
+ }
102204
+ else if (spec.type === 'ImportSpecifier') {
102205
+ // Named imports (e.g. `import { useState } from 'react'`, useState is the import name)
102206
+ const importName = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value;
102207
+ if (typeof importName === 'string') {
102208
+ importValues[spec.local.name] = getModuleExport(module, importName);
102209
+ }
102210
+ }
102211
+ });
102212
+ });
102213
+ return importValues;
102214
+ };
102215
+
102076
102216
  ;// ./processor/transform/mdxish/evaluate-exports.ts
102077
102217
 
102078
102218
 
102079
102219
 
102080
102220
 
102081
102221
 
102222
+
102082
102223
  /**
102083
102224
  * Recursively extract all identifier names introduced by a binding pattern.
102084
102225
  * Handles destructuring (`{ a, b }`, `[x, y]`), rest elements (`...rest`),
@@ -102126,6 +102267,7 @@ const collectExportNames = (declaration) => {
102126
102267
  const evaluateExports = () => (tree, file) => {
102127
102268
  const programBody = [];
102128
102269
  const exportNames = [];
102270
+ const importDeclarations = [];
102129
102271
  const nodesToRemove = [];
102130
102272
  visit(tree, isMDXEsm, (node, index, parent) => {
102131
102273
  if (parent && typeof index === 'number') {
@@ -102139,6 +102281,10 @@ const evaluateExports = () => (tree, file) => {
102139
102281
  // handled the same as a named export — the inner declaration carries the
102140
102282
  // binding name
102141
102283
  estreeBody.forEach(statement => {
102284
+ if (statement.type === 'ImportDeclaration') {
102285
+ importDeclarations.push(statement);
102286
+ return;
102287
+ }
102142
102288
  let declaration = null;
102143
102289
  if (statement.type === 'ExportNamedDeclaration' && statement.declaration) {
102144
102290
  declaration = statement.declaration;
@@ -102166,9 +102312,11 @@ const evaluateExports = () => (tree, file) => {
102166
102312
  const program = { type: 'Program', sourceType: 'module', body: programBody };
102167
102313
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
102168
102314
  const { value: source } = toJs(program);
102315
+ // Make sure import values are available in the scope
102316
+ const importValues = collectImportValues(importDeclarations);
102169
102317
  // Evaluate and build on the expression source
102170
102318
  // Use react to compile JSX codes
102171
- const evaluatedExports = evaluate(`(() => { ${source}\nreturn { ${exportNames.join(', ')} }; })()`, { React: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()) });
102319
+ const evaluatedExports = evaluate(`(() => { ${source}\nreturn { ${exportNames.join(', ')} }; })()`, { React: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()), ...importValues });
102172
102320
  Object.assign(scope, evaluatedExports);
102173
102321
  }
102174
102322
  catch (error) {
@@ -104122,214 +104270,21 @@ const normalizeMdxJsxNodes = () => tree => {
104122
104270
  */
104123
104271
  const JSX_COMMENT_REGEX = /\{\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\}/g;
104124
104272
 
104125
- ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
104126
-
104273
+ ;// ./processor/transform/mdxish/remove-jsx-comments.ts
104127
104274
 
104128
104275
  /**
104129
104276
  * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
104130
104277
  *
104131
- * @param content
104132
- * @returns Content with JSX comments removed
104133
- * @example
104278
+ * `@param` content
104279
+ * `@returns` Content with JSX comments removed
104280
+ * `@example`
104134
104281
  * ```typescript
104135
- * removeJSXComments('Text { /* comment *\/ } more text')
104136
- * // Returns: 'Text more text'
104282
+ * removeJSXComments('Text {/* comment *\/} more text') // => 'Text more text'
104137
104283
  * ```
104138
104284
  */
104139
104285
  function removeJSXComments(content) {
104140
104286
  return content.replace(JSX_COMMENT_REGEX, '');
104141
104287
  }
104142
- const HTML_ELEM_PLACEHOLDER_PREFIX = '___MDXISH_HTML_ELEM_';
104143
- const HTML_ELEM_PLACEHOLDER = new RegExp(`${HTML_ELEM_PLACEHOLDER_PREFIX}(\\d+)___`, 'g');
104144
- // Matches an HTML element that starts at a line boundary and ends at a line boundary.
104145
- // Allows optional leading indentation and lazily matches until the same closing tag.
104146
- const BLOCK_HTML_RE = /(?<=^|\n)[ \t]*<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>[ \t]*(?=\n|$)/g;
104147
- /**
104148
- * Hides line-anchored HTML elements from the brace-escaping pass so we don't leak `\{`
104149
- * into rendered output (rehypeRaw renders the `\` literally, e.g. `<div>{foo</div>`).
104150
- *
104151
- * One carve-out: if an interior line at column 0 has bare text containing `{`, mdxish
104152
- * parses that line as a paragraph and the mdxExpression step would throw without an
104153
- * escape — so we leave that case to the brace balancer.
104154
- */
104155
- function protectHTMLElements(content) {
104156
- const htmlElements = [];
104157
- const protectedContent = content.replace(BLOCK_HTML_RE, match => {
104158
- // Look at the lines between the open and close tags. If any of them starts
104159
- // at column 0 with bare text (not whitespace, not another tag) and contains
104160
- // `{`, mdxish will parse that line as a paragraph and the brace as an MDX
104161
- // expression, which would throw an error. So we let the brace balancer escape it.
104162
- // Otherwise, we need to extract the sequence to protect it from the brace escaping.
104163
- const interior = match.split('\n').slice(1, -1);
104164
- const hazard = interior.some(line => line.length > 0 && line[0] !== ' ' && line[0] !== '\t' && line[0] !== '<' && line.includes('{'));
104165
- if (hazard)
104166
- return match;
104167
- htmlElements.push(match);
104168
- return `${HTML_ELEM_PLACEHOLDER_PREFIX}${htmlElements.length - 1}___`;
104169
- });
104170
- return { htmlElements, protectedContent };
104171
- }
104172
- function restoreHTMLElements(content, htmlElements) {
104173
- if (htmlElements.length === 0)
104174
- return content;
104175
- return content.replace(HTML_ELEM_PLACEHOLDER, (_m, idx) => htmlElements[parseInt(idx, 10)]);
104176
- }
104177
- const ESM_DECLARATION_START = /^(?:export|import)\b/;
104178
- /**
104179
- * Whether a line begins a top-level `export`/`import` declaration. Its braces
104180
- * are valid JS owned by the mdxjsEsm tokenizer (which tolerates blank lines),
104181
- * so escaping them would corrupt the source and break acorn parsing.
104182
- */
104183
- function startsEsmDeclaration(chars, lineStart) {
104184
- return ESM_DECLARATION_START.test(chars.slice(lineStart, lineStart + 7).join(''));
104185
- }
104186
- function isBlankLine(chars, lineStart) {
104187
- for (let i = lineStart; i < chars.length; i += 1) {
104188
- if (chars[i] === '\n')
104189
- return true;
104190
- if (chars[i] !== ' ' && chars[i] !== '\t')
104191
- return false;
104192
- }
104193
- return true;
104194
- }
104195
- /**
104196
- * Escapes unbalanced and paragraph-spanning braces so MDX doesn't trip on them.
104197
- */
104198
- function escapeProblematicBraces(content) {
104199
- const { htmlElements, protectedContent } = protectHTMLElements(content);
104200
- let strDelim = null;
104201
- let strEscaped = false;
104202
- // Track position of last newline (outside strings) to detect blank lines
104203
- // -2 means no recent newline
104204
- let lastNewlinePos = -2;
104205
- // Character state machine trackers
104206
- const toEscape = new Set();
104207
- // Convert to array of Unicode code points so that emojis and multi-byte characters are correctly tracked
104208
- const chars = Array.from(protectedContent);
104209
- const openStack = [];
104210
- // Whether the current top-level statement is an `export`/`import` declaration.
104211
- let insideEsmDeclaration = false;
104212
- for (let i = 0; i < chars.length; i += 1) {
104213
- const ch = chars[i];
104214
- // At a top-level (brace depth 0) line start, decide whether we're inside an
104215
- // ESM declaration. The flag persists across the declaration's own lines.
104216
- if (openStack.length === 0 && (i === 0 || chars[i - 1] === '\n')) {
104217
- if (startsEsmDeclaration(chars, i))
104218
- insideEsmDeclaration = true;
104219
- else if (isBlankLine(chars, i))
104220
- insideEsmDeclaration = false;
104221
- }
104222
- // Track string delimiters inside expressions to ignore braces within them
104223
- if (openStack.length > 0) {
104224
- if (strDelim) {
104225
- if (strEscaped)
104226
- strEscaped = false;
104227
- else if (ch === '\\')
104228
- strEscaped = true;
104229
- else if (ch === strDelim)
104230
- strDelim = null;
104231
- // eslint-disable-next-line no-continue
104232
- continue;
104233
- }
104234
- if (ch === '"' || ch === "'" || ch === '`') {
104235
- strDelim = ch;
104236
- // eslint-disable-next-line no-continue
104237
- continue;
104238
- }
104239
- if (ch === '\n') {
104240
- if (lastNewlinePos >= 0) {
104241
- const between = chars.slice(lastNewlinePos + 1, i).join('');
104242
- if (/^[ \t]*$/.test(between)) {
104243
- openStack.forEach(entry => { entry.hasBlankLine = true; });
104244
- }
104245
- }
104246
- lastNewlinePos = i;
104247
- }
104248
- }
104249
- // Skip already-escaped braces (odd run of preceding backslashes).
104250
- if (ch === '{' || ch === '}') {
104251
- let bs = 0;
104252
- for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
104253
- bs += 1;
104254
- if (bs % 2 === 1) {
104255
- // eslint-disable-next-line no-continue
104256
- continue;
104257
- }
104258
- }
104259
- if (ch === '{') {
104260
- // `=` (after whitespace) before `{` ⇒ JSX attribute expression. The
104261
- // mdxComponent tokenizer captures the whole component, so blank lines
104262
- // inside attribute values are harmless. Nested `{` inherits the flag.
104263
- let isAttrExpr = false;
104264
- for (let j = i - 1; j >= 0; j -= 1) {
104265
- const pc = chars[j];
104266
- if (pc === '=') {
104267
- isAttrExpr = true;
104268
- break;
104269
- }
104270
- if (pc !== ' ' && pc !== '\t')
104271
- break;
104272
- }
104273
- // Nested `{ ... }` inside an attribute value (e.g. `data={[{ ... }]}` or
104274
- // `data={{ a: { b: 1 } }}`) must inherit the same exemption; only the
104275
- // outer `{` is directly after `=`.
104276
- if (!isAttrExpr && openStack.length > 0 && openStack[openStack.length - 1].isAttrExpr) {
104277
- isAttrExpr = true;
104278
- }
104279
- openStack.push({ pos: i, hasBlankLine: false, isAttrExpr, isEsmDeclaration: insideEsmDeclaration });
104280
- lastNewlinePos = -2;
104281
- }
104282
- else if (ch === '}') {
104283
- if (openStack.length > 0) {
104284
- const entry = openStack.pop();
104285
- // The declaration's braces are closed; later top-level braces are not ESM.
104286
- if (openStack.length === 0)
104287
- insideEsmDeclaration = false;
104288
- // Pure `{/* ... */}` comments are handled downstream by the jsxComment
104289
- // tokenizer — escaping their braces would prevent it from running.
104290
- const isPureJsxComment = chars[entry.pos + 1] === '/' &&
104291
- chars[entry.pos + 2] === '*' &&
104292
- chars[i - 1] === '/' &&
104293
- chars[i - 2] === '*';
104294
- if (entry.hasBlankLine && !isPureJsxComment && !entry.isAttrExpr && !entry.isEsmDeclaration) {
104295
- toEscape.add(entry.pos);
104296
- toEscape.add(i);
104297
- }
104298
- }
104299
- else {
104300
- toEscape.add(i);
104301
- }
104302
- }
104303
- else if (ch === ';' && openStack.length === 0) {
104304
- // A top-level `;` ends the current statement, ESM or otherwise.
104305
- insideEsmDeclaration = false;
104306
- }
104307
- }
104308
- // Anything still open is unbalanced.
104309
- openStack.forEach(entry => {
104310
- if (!entry.isEsmDeclaration)
104311
- toEscape.add(entry.pos);
104312
- });
104313
- // Reconstruct the content with the escaped braces.
104314
- const escapedContent = toEscape.size === 0 ? protectedContent : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
104315
- return restoreHTMLElements(escapedContent, htmlElements);
104316
- }
104317
- /**
104318
- * Preprocesses JSX-like markdown content before parsing.
104319
- *
104320
- * JSX attribute expressions (`href={baseUrl}`) are no longer rewritten here —
104321
- * they flow through the tokenizer as `mdxJsxAttributeValueExpression` nodes
104322
- * and are evaluated at the hast handler step.
104323
- *
104324
- * @param content
104325
- * @returns Preprocessed content ready for markdown parsing
104326
- */
104327
- function preprocessJSXExpressions(content) {
104328
- const { protectedCode, protectedContent } = protectCodeBlocks(content);
104329
- let processed = escapeProblematicBraces(protectedContent);
104330
- processed = restoreCodeBlocks(processed, protectedCode);
104331
- return processed;
104332
- }
104333
104288
 
104334
104289
  ;// ./processor/transform/mdxish/resolve-deferred-attribute-expression-props.ts
104335
104290
 
@@ -105304,6 +105259,85 @@ function jsxTable() {
105304
105259
  ;// ./lib/micromark/jsx-table/index.ts
105305
105260
 
105306
105261
 
105262
+ ;// ./lib/micromark/mdx-expression-lenient/syntax.ts
105263
+
105264
+
105265
+ /**
105266
+ * Lenient MDX text-expression tokenizer (agnostic / no acorn).
105267
+ *
105268
+ * Matches a balanced `{ ... }` run — tracking nested braces and spanning soft
105269
+ * line breaks — and emits the standard `mdxTextExpression*` tokens so the
105270
+ * upstream `mdxExpressionFromMarkdown()` builds the node.
105271
+ *
105272
+ * Reimplements the official micromark mdxExpression, but an unbalanced brace that
105273
+ * reaches end of input returns `nok` instead of throwing: micromark rolls back
105274
+ * and the `{` renders as literal text, making the pipeline forgiving of stray
105275
+ * braces that upstream would hard-error on.
105276
+ */
105277
+ function tokenizeTextExpression(effects, ok, nok) {
105278
+ let depth = 0;
105279
+ return start;
105280
+ function start(code) {
105281
+ if (code !== codes.leftCurlyBrace)
105282
+ return nok(code);
105283
+ effects.enter('mdxTextExpression');
105284
+ effects.enter('mdxTextExpressionMarker');
105285
+ effects.consume(code);
105286
+ effects.exit('mdxTextExpressionMarker');
105287
+ return before;
105288
+ }
105289
+ function before(code) {
105290
+ if (code === codes.eof) {
105291
+ effects.exit('mdxTextExpression');
105292
+ return nok(code);
105293
+ }
105294
+ if (markdownLineEnding(code)) {
105295
+ effects.enter('lineEnding');
105296
+ effects.consume(code);
105297
+ effects.exit('lineEnding');
105298
+ return before;
105299
+ }
105300
+ if (code === codes.rightCurlyBrace && depth === 0) {
105301
+ return close(code);
105302
+ }
105303
+ effects.enter('mdxTextExpressionChunk');
105304
+ return inside(code);
105305
+ }
105306
+ function inside(code) {
105307
+ if (code === codes.eof || markdownLineEnding(code)) {
105308
+ effects.exit('mdxTextExpressionChunk');
105309
+ return before(code);
105310
+ }
105311
+ if (code === codes.rightCurlyBrace && depth === 0) {
105312
+ effects.exit('mdxTextExpressionChunk');
105313
+ return close(code);
105314
+ }
105315
+ if (code === codes.leftCurlyBrace)
105316
+ depth += 1;
105317
+ else if (code === codes.rightCurlyBrace)
105318
+ depth -= 1;
105319
+ effects.consume(code);
105320
+ return inside;
105321
+ }
105322
+ function close(code) {
105323
+ effects.enter('mdxTextExpressionMarker');
105324
+ effects.consume(code);
105325
+ effects.exit('mdxTextExpressionMarker');
105326
+ effects.exit('mdxTextExpression');
105327
+ return ok;
105328
+ }
105329
+ }
105330
+ function mdxExpressionLenient() {
105331
+ return {
105332
+ text: {
105333
+ [codes.leftCurlyBrace]: {
105334
+ name: 'mdxTextExpression',
105335
+ tokenize: tokenizeTextExpression,
105336
+ },
105337
+ },
105338
+ };
105339
+ }
105340
+
105307
105341
  ;// ./lib/utils/mdxish/mdxish-load-components.ts
105308
105342
 
105309
105343
 
@@ -105416,7 +105450,7 @@ const defaultTransformers = [
105416
105450
  * 1. Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
105417
105451
  * 2. Terminate HTML flow blocks so subsequent content isn't swallowed
105418
105452
  * 3. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
105419
- * 4. Escape problematic braces so MDX expression parsing doesn't choke
105453
+ * 4. Normalize compact ATX headings (e.g., `#Heading` `# Heading`)
105420
105454
  * 5. Replace snake_case component names with parser-safe placeholders
105421
105455
  */
105422
105456
  function preprocessContent(content, opts) {
@@ -105425,7 +105459,6 @@ function preprocessContent(content, opts) {
105425
105459
  result = terminateHtmlFlowBlocks(result);
105426
105460
  result = closeSelfClosingHtmlTags(result);
105427
105461
  result = normalizeCompactHeadings(result);
105428
- result = preprocessJSXExpressions(result);
105429
105462
  return processSnakeCaseComponent(result, { knownComponents });
105430
105463
  }
105431
105464
  function mdxishAstProcessor(mdContent, opts = {}) {
@@ -105442,12 +105475,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
105442
105475
  acc[key] = String(value);
105443
105476
  return acc;
105444
105477
  }, {});
105445
- // Get mdxExpression extension and remove its flow construct to prevent
105446
- // `{...}` from interrupting paragraphs (which breaks multiline magic blocks)
105447
- const mdxExprExt = mdxExpression({ allowEmpty: true });
105448
- const mdxExprTextOnly = {
105449
- text: mdxExprExt.text,
105450
- };
105478
+ // Parser extension for MDX expressions {}
105479
+ const mdxExprTextOnly = mdxExpressionLenient();
105451
105480
  const micromarkExts = [
105452
105481
  jsxTable(),
105453
105482
  magicBlock(),