@readme/markdown 15.3.0 → 15.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19045,7 +19045,7 @@ module.exports = function () {
19045
19045
 
19046
19046
  /***/ },
19047
19047
 
19048
- /***/ 345
19048
+ /***/ 8691
19049
19049
  (module, __webpack_exports__, __webpack_require__) {
19050
19050
 
19051
19051
  "use strict";
@@ -86684,18 +86684,77 @@ const gemojiTransformer = () => (tree) => {
86684
86684
  };
86685
86685
  /* harmony default export */ const gemoji_ = (gemojiTransformer);
86686
86686
 
86687
+ ;// ./hooks/useRestartAnimatedImages/index.tsx
86688
+
86689
+ /**
86690
+ * GIF is the one extension that implies animation. `.webp` and `.png` (the extension an APNG
86691
+ * ships under) are overwhelmingly static, so matching them would reload far more images than
86692
+ * it rewinds; telling those apart needs the bytes, not the URL.
86693
+ */
86694
+ const ANIMATED_IMAGE_SRC = /\.gif(\?|#|$)/i;
86695
+ const restartAnimatedImages = (root) => {
86696
+ root?.querySelectorAll('img').forEach(img => {
86697
+ if (!ANIMATED_IMAGE_SRC.test(img.src))
86698
+ return;
86699
+ const { src } = img;
86700
+ // There's no seek API for animated images; reassigning `src` is the only reset
86701
+ img.src = '';
86702
+ img.src = src;
86703
+ });
86704
+ };
86705
+ const useRestartAfterFirstPaint = (revealKey, restart) => {
86706
+ const isFirstRender = (0,external_react_.useRef)(true);
86707
+ const latestRestart = (0,external_react_.useRef)(restart);
86708
+ latestRestart.current = restart;
86709
+ (0,external_react_.useEffect)(() => {
86710
+ if (isFirstRender.current) {
86711
+ isFirstRender.current = false;
86712
+ return;
86713
+ }
86714
+ latestRestart.current();
86715
+ }, [revealKey]);
86716
+ };
86717
+ /**
86718
+ * Restarts GIF playback inside the panel that just became active.
86719
+ *
86720
+ * Tabs keep every panel mounted, so an <img> is never re-created and a GIF is
86721
+ * shown mid-loop (or frozen on its last frame) when its tab is re-selected.
86722
+ *
86723
+ * @returns a ref to attach to each panel element, indexed by tab.
86724
+ */
86725
+ function useRestartAnimatedImages(activeIndex) {
86726
+ const panelRefs = (0,external_react_.useRef)([]);
86727
+ useRestartAfterFirstPaint(activeIndex, () => restartAnimatedImages(panelRefs.current[activeIndex]));
86728
+ return panelRefs;
86729
+ }
86730
+ /**
86731
+ * Restarts GIF playback inside an element that has just been revealed after being hidden —
86732
+ * a collapsed <details>, whose images have been animating out of sight since page load.
86733
+ */
86734
+ function useRestartAnimatedImagesOnReveal(isRevealed) {
86735
+ const contentRef = (0,external_react_.useRef)(null);
86736
+ useRestartAfterFirstPaint(isRevealed, () => {
86737
+ // Rewinding content the reader just hid would be wasted work
86738
+ if (isRevealed)
86739
+ restartAnimatedImages(contentRef.current);
86740
+ });
86741
+ return contentRef;
86742
+ }
86743
+
86687
86744
  ;// ./components/Accordion/index.tsx
86688
86745
 
86689
86746
 
86690
86747
 
86748
+
86691
86749
  const Accordion = ({ children, icon, iconColor, title }) => {
86692
86750
  const [isOpen, setIsOpen] = (0,external_react_.useState)(false);
86751
+ const contentRef = useRestartAnimatedImagesOnReveal(isOpen);
86693
86752
  return (external_react_default().createElement("details", { className: "Accordion", onToggle: () => setIsOpen(!isOpen) },
86694
86753
  external_react_default().createElement("summary", { className: "Accordion-title" },
86695
86754
  external_react_default().createElement("i", { className: `Accordion-toggleIcon${isOpen ? '_opened' : ''} fa fa-regular fa-chevron-right` }),
86696
86755
  icon && external_react_default().createElement(components_Icon, { className: "Accordion-icon", icon: icon, iconColor: iconColor }),
86697
86756
  title),
86698
- external_react_default().createElement("div", { className: "Accordion-content" }, children)));
86757
+ external_react_default().createElement("div", { ref: contentRef, className: "Accordion-content" }, children)));
86699
86758
  };
86700
86759
  /* harmony default export */ const components_Accordion = (Accordion);
86701
86760
 
@@ -92513,11 +92572,13 @@ function TableOfContents({ children }) {
92513
92572
 
92514
92573
 
92515
92574
 
92575
+
92516
92576
  const Tab = ({ children }) => {
92517
92577
  return external_react_default().createElement("div", { className: "TabContent" }, children);
92518
92578
  };
92519
92579
  const Tabs = ({ children }) => {
92520
92580
  const [activeTab, setActiveTab] = (0,external_react_.useState)(0);
92581
+ const panelRefs = useRestartAnimatedImages(activeTab);
92521
92582
  // React passes `children` as a single element when there's only one child, so normalize.
92522
92583
  const tabs = external_react_default().Children.toArray(children);
92523
92584
  return (external_react_default().createElement("div", { className: "TabGroup" },
@@ -92525,7 +92586,9 @@ const Tabs = ({ children }) => {
92525
92586
  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) },
92526
92587
  tab.props.icon && (external_react_default().createElement(components_Icon, { className: "TabGroup-icon", icon: tab.props.icon, iconColor: tab.props.iconColor })),
92527
92588
  tab.props.title))))),
92528
- external_react_default().createElement("section", null, tabs.map((tab, index) => (external_react_default().createElement("div", { key: tab.key, hidden: index !== activeTab }, tab))))));
92589
+ external_react_default().createElement("section", null, tabs.map((tab, index) => (external_react_default().createElement("div", { key: tab.key, ref: el => {
92590
+ panelRefs.current[index] = el;
92591
+ }, hidden: index !== activeTab }, tab))))));
92529
92592
  };
92530
92593
  /* harmony default export */ const components_Tabs = (Tabs);
92531
92594
 
@@ -92693,7 +92756,7 @@ async function loadStylesheet(id, base) {
92693
92756
  async function loadModule() {
92694
92757
  throw new Error('The browser build does not support plugins or config files.');
92695
92758
  }
92696
- async function createCompiler({ darkModeDataAttribute }) {
92759
+ async function createCompiler({ darkModeDataAttribute, darkModeRootSelector, }) {
92697
92760
  let css = `
92698
92761
  @layer theme, base, components, utilities;
92699
92762
 
@@ -92701,9 +92764,12 @@ async function createCompiler({ darkModeDataAttribute }) {
92701
92764
  @import "tailwindcss/utilities.css" layer(utilities);
92702
92765
  `;
92703
92766
  if (darkModeDataAttribute) {
92767
+ // Anchors `dark:` to `darkModeRootSelector`'s own attribute instead of any ancestor's,
92768
+ // when supplied — see the `darkModeRootSelector` param doc below for why there's no default.
92769
+ const root = darkModeRootSelector || '';
92704
92770
  css += `
92705
92771
 
92706
- @custom-variant dark (&:where([${darkModeDataAttribute}=dark], [${darkModeDataAttribute}=dark] *));`;
92772
+ @custom-variant dark (&:where(${root}[${darkModeDataAttribute}=dark], ${root}[${darkModeDataAttribute}=dark] *));`;
92707
92773
  }
92708
92774
  return Hu(css, {
92709
92775
  base: '/',
@@ -92711,8 +92777,8 @@ async function createCompiler({ darkModeDataAttribute }) {
92711
92777
  loadModule,
92712
92778
  });
92713
92779
  }
92714
- async function tailwindCompiler(classes, { prefix, darkModeDataAttribute }) {
92715
- const compiler = await createCompiler({ darkModeDataAttribute });
92780
+ async function tailwindCompiler(classes, { prefix, darkModeDataAttribute, darkModeRootSelector, }) {
92781
+ const compiler = await createCompiler({ darkModeDataAttribute, darkModeRootSelector });
92716
92782
  const css = compiler.build(Array.from(classes));
92717
92783
  return lib_postcss([postcss_prefix_selector_default()({ prefix })]).process(css, { from: undefined });
92718
92784
  }
@@ -92727,7 +92793,7 @@ const traverse = (node, callback) => {
92727
92793
  traverse(child, callback);
92728
92794
  });
92729
92795
  };
92730
- const TailwindStyle = ({ children, darkModeDataAttribute }) => {
92796
+ const TailwindStyle = ({ children, darkModeDataAttribute, darkModeRootSelector }) => {
92731
92797
  const [stylesheet, setStylesheet] = (0,external_react_.useState)('');
92732
92798
  const classesSet = (0,external_react_.useRef)(new Set());
92733
92799
  const ref = (0,external_react_.useRef)(null);
@@ -92751,6 +92817,7 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
92751
92817
  const sheet = await tailwindCompiler(classes, {
92752
92818
  prefix: `.${tailwindPrefix}`,
92753
92819
  darkModeDataAttribute,
92820
+ darkModeRootSelector,
92754
92821
  });
92755
92822
  /* @note: don't insert an empty stylesheet */
92756
92823
  if (sheet.css.match(/^@layer utilities;/m))
@@ -92758,7 +92825,7 @@ const TailwindStyle = ({ children, darkModeDataAttribute }) => {
92758
92825
  setStylesheet(sheet.css);
92759
92826
  };
92760
92827
  run();
92761
- }, [classes, darkModeDataAttribute]);
92828
+ }, [classes, darkModeDataAttribute, darkModeRootSelector]);
92762
92829
  /*
92763
92830
  * @note: execute once on load
92764
92831
  */
@@ -96515,6 +96582,10 @@ function syntax_createTokenize(mode) {
96515
96582
  effects.consume(code);
96516
96583
  return inBraceExpr;
96517
96584
  }
96585
+ // A raw `<` can't sit in an opening tag (quotes/braces are handled above);
96586
+ // bailing bounds each attempt to the next `<` instead of rescanning the line.
96587
+ if (code === codes.lessThan)
96588
+ return nok(code);
96518
96589
  effects.consume(code);
96519
96590
  return afterOpenTagName;
96520
96591
  }
@@ -121549,7 +121620,7 @@ const listMarkerRegex = /^(?:[*+-]|\d+[.)])(?:([\r\n]| {1,3})|$)/;
121549
121620
  * with their checkbox intact (for example, `- [ ]`) instead of dropping it
121550
121621
  * We can add more adjustments if needed
121551
121622
  */
121552
- const compile_list_item_listItem = (node, parent, state, info) => {
121623
+ const compile_list_item_listItem = ((node, parent, state, info) => {
121553
121624
  const head = node.children[0];
121554
121625
  const isCheckbox = typeof node.checked === 'boolean' && head && head.type === 'paragraph';
121555
121626
  if (!isCheckbox) {
@@ -121573,15 +121644,47 @@ const compile_list_item_listItem = (node, parent, state, info) => {
121573
121644
  return `${marker}${actualSeparator}${checkbox}`;
121574
121645
  });
121575
121646
  return value;
121576
- };
121647
+ });
121577
121648
  /* harmony default export */ const list_item = (compile_list_item_listItem);
121578
121649
 
121579
121650
  ;// ./processor/compile/plain.ts
121580
121651
  const plain_plain = (node) => node.value;
121581
121652
  /* harmony default export */ const compile_plain = (plain_plain);
121582
121653
 
121654
+ ;// ./processor/compile/table-cell-block-markers.ts
121655
+ const CELL_START_BLOCK_MARKER = /^(?:[-+](?=\s|$)|#{1,6}(?=\s|$)|>)/;
121656
+ const BR_HTML = /^\s*<br\s*\/?>\s*$/i;
121657
+ const isLineBreak = (node) => {
121658
+ if (node.type === 'break')
121659
+ return true;
121660
+ if (node.type === 'html')
121661
+ return BR_HTML.test(node.value);
121662
+ return node.type === 'mdxJsxTextElement' && 'name' in node && node.name?.toLowerCase() === 'br';
121663
+ };
121664
+ const followsLineBreak = (node, parent) => {
121665
+ const siblings = (parent?.children ?? []);
121666
+ const previous = siblings[siblings.indexOf(node) - 1];
121667
+ return !!previous && isLineBreak(previous);
121668
+ };
121669
+ /**
121670
+ * Re-escapes a leading block marker (`-`, `+`, `#`, `>`) at the start of a table cell, or
121671
+ * after a `<br />` within one.
121672
+ *
121673
+ * A cell is serialized mid-line, so the `atBreak` patterns that escape these characters
121674
+ * never fire and an author's `| \- one |` round trips to `| - one |` (RM-17203). Cells are
121675
+ * serialized as `containerPhrasing(cell, { before: '|' })`, so `|` marks the cell start.
121676
+ */
121677
+ const escapeCellStartBlockMarker = (serialized, node, parent, state, info) => {
121678
+ if (serialized.startsWith('\\') || !CELL_START_BLOCK_MARKER.test(serialized))
121679
+ return serialized;
121680
+ if (!state.stack.includes('tableCell') || !(info.before === '|' || followsLineBreak(node, parent)))
121681
+ return serialized;
121682
+ return `\\${serialized}`;
121683
+ };
121684
+
121583
121685
  ;// ./processor/compile/text.ts
121584
121686
 
121687
+
121585
121688
  // A `_` flanked by word characters can never open or close emphasis under
121586
121689
  // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
121587
121690
  // intraword underscores is unnecessary and only produces noisy `\_` diffs.
@@ -121589,9 +121692,10 @@ const plain_plain = (node) => node.value;
121589
121692
  const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
121590
121693
  const compile_text_text = (node, parent, state, info) => {
121591
121694
  const serialized = handle.text(node, parent, state, info);
121592
- return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
121695
+ return escapeCellStartBlockMarker(serialized, node, parent, state, info);
121593
121696
  };
121594
- /* harmony default export */ const compile_text = (compile_text_text);
121697
+ const mdxishText = (node, parent, state, info) => compile_text_text(node, parent, state, info).replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
121698
+ /* harmony default export */ const compile_text = (mdxishText);
121595
121699
 
121596
121700
  ;// ./processor/compile/index.ts
121597
121701
 
@@ -121623,6 +121727,7 @@ function compilers(mdxish = false) {
121623
121727
  html: compile_compatibility,
121624
121728
  i: compile_compatibility,
121625
121729
  plain: compile_plain,
121730
+ text: compile_text_text,
121626
121731
  yaml: compile_compatibility,
121627
121732
  // needed only for mdxish
121628
121733
  ...(mdxish && { list: compile_list }),
@@ -122280,13 +122385,14 @@ function restoreCodeBlocks(content, protectedCode) {
122280
122385
  *
122281
122386
  * The attribute portion skips over quoted strings (`"..."` and `'...'`) so that
122282
122387
  * a `/>` inside an attribute value (e.g. `title="use /> here"`) does not cause
122283
- * a premature match.
122388
+ * a premature match, and stops at an unquoted `<` so a stray `<x` in prose
122389
+ * doesn't scan to the end of the document (quadratic).
122284
122390
  *
122285
122391
  * Only matches lowercase tag names to avoid interfering with PascalCase
122286
122392
  * JSX custom components (e.g. `<MyComponent />`), which are handled
122287
122393
  * separately by components/mdx-blocks.
122288
122394
  */
122289
- const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)?)?\s*\/>/g;
122395
+ const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^<>"']*(?:"[^"]*"|'[^']*'))*[^<>"']*)?)?\s*\/>/g;
122290
122396
  /**
122291
122397
  * String-level preprocessor that converts self-closing non-void HTML tags
122292
122398
  * into explicitly closed tags.
@@ -122951,8 +123057,9 @@ function terminateHtmlFlowBlocks(content) {
122951
123057
 
122952
123058
 
122953
123059
 
122954
- // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
122955
- const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
123060
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. One name
123061
+ // char suffices for existence — `[\w-]+` backtracked quadratically over huge attributes.
123062
+ const NESTED_ATTR_EXPRESSION_RE = /[\w-]\s*=\s*\{/;
122956
123063
  // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
122957
123064
  // of a legacy `<<VARIABLE>>`.
122958
123065
  const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
@@ -123251,150 +123358,6 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
123251
123358
  };
123252
123359
  /* harmony default export */ const mdx_blocks = (mdxishMdxComponentBlocks);
123253
123360
 
123254
- ;// ./processor/transform/mdxish/components/self-closing-blocks.ts
123255
-
123256
-
123257
- /**
123258
- * Tags to process as self-closing blocks.
123259
- * These components use simple string attributes (no JSX expressions like `data={[...]}`).
123260
- * Components with JSX expression attributes should NOT be added here as parseAttributes
123261
- * cannot handle them correctly.
123262
- */
123263
- const SELF_CLOSING_BLOCK_TAGS = new Set(['Embed', 'Recipe']);
123264
- // Regex to match self-closing PascalCase tags (handles multi-line)
123265
- const selfClosingTagPattern = /^<([A-Z][A-Za-z0-9_]*)([\s\S]*?)\/>$/;
123266
- /**
123267
- * Try to convert a paragraph node containing a self-closing JSX component into an mdxJsxFlowElement.
123268
- * Returns the new node if conversion succeeded, or null if the node doesn't match.
123269
- */
123270
- const tryConvertToMdxNode = (node) => {
123271
- if (node.children.length !== 1)
123272
- return null;
123273
- const child = node.children[0];
123274
- if (child.type !== 'html')
123275
- return null;
123276
- const value = child.value?.trim();
123277
- if (!value)
123278
- return null;
123279
- const match = value.match(selfClosingTagPattern);
123280
- if (!match)
123281
- return null;
123282
- const [, tag, attrString] = match;
123283
- if (!SELF_CLOSING_BLOCK_TAGS.has(tag))
123284
- return null;
123285
- return {
123286
- type: 'mdxJsxFlowElement',
123287
- name: tag,
123288
- attributes: parseAttributes(attrString),
123289
- children: [],
123290
- position: node.position,
123291
- };
123292
- };
123293
- /**
123294
- * Transform paragraph-wrapped self-closing JSX components into mdxJsxFlowElement nodes.
123295
- *
123296
- * CommonMark wraps multi-line JSX in paragraphs when the opening tag isn't complete
123297
- * on one line. This plugin detects these structures and unwraps them for components
123298
- * in the SELF_CLOSING_BLOCK_TAGS allowlist.
123299
- *
123300
- * Input structure:
123301
- * ```
123302
- * paragraph > html: "<Embed\n typeOfEmbed=\"youtube\"\n/>"
123303
- * ```
123304
- *
123305
- * Output structure:
123306
- * ```
123307
- * mdxJsxFlowElement: { name: "Embed", attributes: [...], children: [] }
123308
- * ```
123309
- */
123310
- const mdxishSelfClosingBlocks = () => tree => {
123311
- visit(tree, 'paragraph', (node, index, parent) => {
123312
- if (index === undefined || !parent)
123313
- return;
123314
- const mdxNode = tryConvertToMdxNode(node);
123315
- if (mdxNode) {
123316
- parent.children.splice(index, 1, mdxNode);
123317
- }
123318
- });
123319
- };
123320
- /* harmony default export */ const self_closing_blocks = (mdxishSelfClosingBlocks);
123321
-
123322
- ;// ./processor/transform/mdxish/components/snake-case-components.ts
123323
-
123324
-
123325
- /**
123326
- * Replaces snake_case component names with valid HTML placeholders.
123327
- * Required because remark-parse rejects tags with underscores.
123328
- * Example: `<Snake_case />` → `<MDXishSnakeCase0 />`
123329
- *
123330
- * Code blocks and inline code are protected and will not be transformed.
123331
- *
123332
- * @param content - The markdown content to process
123333
- * @param options - Options including knownComponents to filter by
123334
- */
123335
- function processSnakeCaseComponent(content, options = {}) {
123336
- const { knownComponents } = options;
123337
- // Early exit if no potential snake_case components
123338
- if (!/[A-Z][A-Za-z0-9]*_[A-Za-z0-9_]*/.test(content)) {
123339
- return { content, mapping: {} };
123340
- }
123341
- // Step 1: Extract code blocks to protect them from transformation
123342
- const { protectedCode, protectedContent } = protectCodeBlocks(content);
123343
- // Find the highest existing placeholder number to avoid collisions
123344
- // e.g., if content has <MDXishSnakeCase0 />, start counter from 1
123345
- const placeholderPattern = /MDXishSnakeCase(\d+)/g;
123346
- let startCounter = 0;
123347
- let placeholderMatch;
123348
- while ((placeholderMatch = placeholderPattern.exec(content)) !== null) {
123349
- const num = parseInt(placeholderMatch[1], 10);
123350
- if (num >= startCounter) {
123351
- startCounter = num + 1;
123352
- }
123353
- }
123354
- const mapping = {};
123355
- const reverseMap = new Map();
123356
- let counter = startCounter;
123357
- // Step 2: Transform snake_case components in non-code content
123358
- const processedContent = protectedContent.replace(componentTagPattern, (match, tagName, attrs, selfClosing) => {
123359
- if (!tagName.includes('_')) {
123360
- return match;
123361
- }
123362
- const isClosing = tagName.startsWith('/');
123363
- const cleanTagName = isClosing ? tagName.slice(1) : tagName;
123364
- // Only transform if it's a known component (or if no filter is provided)
123365
- if (knownComponents && !knownComponents.has(cleanTagName)) {
123366
- return match;
123367
- }
123368
- let placeholder = reverseMap.get(cleanTagName);
123369
- if (!placeholder) {
123370
- // eslint-disable-next-line no-plusplus
123371
- placeholder = `MDXishSnakeCase${counter++}`;
123372
- mapping[placeholder] = cleanTagName;
123373
- reverseMap.set(cleanTagName, placeholder);
123374
- }
123375
- const processedTagName = isClosing ? `/${placeholder}` : placeholder;
123376
- return `<${processedTagName}${attrs}${selfClosing}>`;
123377
- });
123378
- // Step 3: Restore code blocks (untouched)
123379
- const finalContent = restoreCodeBlocks(processedContent, protectedCode);
123380
- return {
123381
- content: finalContent,
123382
- mapping,
123383
- };
123384
- }
123385
- /**
123386
- * Restores placeholder name to original snake_case name.
123387
- * Uses case-insensitive matching since HTML parsers normalize to lowercase.
123388
- */
123389
- function restoreSnakeCase(placeholderName, mapping) {
123390
- if (mapping[placeholderName]) {
123391
- return mapping[placeholderName];
123392
- }
123393
- const lowerName = placeholderName.toLowerCase();
123394
- const matchingKey = Object.keys(mapping).find(key => key.toLowerCase() === lowerName);
123395
- return matchingKey ? mapping[matchingKey] : placeholderName;
123396
- }
123397
-
123398
123361
  ;// ./processor/transform/mdxish/resolve-esm-imports.ts
123399
123362
 
123400
123363
  // We provide React as a default module so that components can use hooks
@@ -123579,6 +123542,54 @@ const containsJsxNode = (value) => {
123579
123542
  return true;
123580
123543
  return Object.values(value).some(containsJsxNode);
123581
123544
  };
123545
+ /** Read the component name off a JSX element name node (`Foo`, `Foo.Bar`, `foo:Bar`). */
123546
+ const jsxElementName = (name) => {
123547
+ if (name === null || typeof name !== 'object')
123548
+ return undefined;
123549
+ const node = name;
123550
+ if (node.type === 'JSXIdentifier')
123551
+ return typeof node.name === 'string' ? node.name : undefined;
123552
+ // `<Foo.Bar/>` and `<foo:Bar/>` resolve through their leftmost part.
123553
+ if (node.type === 'JSXMemberExpression')
123554
+ return jsxElementName(node.object);
123555
+ if (node.type === 'JSXNamespacedName')
123556
+ return jsxElementName(node.namespace);
123557
+ return undefined;
123558
+ };
123559
+ /**
123560
+ * Collect the capitalized names an expression uses as JSX tags. Parsed rather than pattern
123561
+ * matched: `{count < Max ? <Foo/> : <Bar/>}` puts a capitalized name straight after a `<` without
123562
+ * it being a tag, and only the parser can tell the two apart. Unparseable input yields nothing —
123563
+ * evaluation is about to throw on it anyway.
123564
+ */
123565
+ const jsxComponentNames = (expression) => {
123566
+ let program;
123567
+ try {
123568
+ program = parseExpression(expression);
123569
+ }
123570
+ catch {
123571
+ return [];
123572
+ }
123573
+ const names = new Set();
123574
+ const walk = (value) => {
123575
+ if (Array.isArray(value)) {
123576
+ value.forEach(walk);
123577
+ return;
123578
+ }
123579
+ if (value === null || typeof value !== 'object')
123580
+ return;
123581
+ const node = value;
123582
+ if (node.type === 'JSXOpeningElement') {
123583
+ const name = jsxElementName(node.name);
123584
+ // Lowercase tags compile to a string type, never a variable reference.
123585
+ if (name && /^[A-Z]/.test(name))
123586
+ names.add(name);
123587
+ }
123588
+ Object.values(node).forEach(walk);
123589
+ };
123590
+ walk(program);
123591
+ return Array.from(names);
123592
+ };
123582
123593
  /** Convert a program's JSX into `React.createElement` calls and evaluate it. `scope` must provide `React`. */
123583
123594
  const evalJsxProgram = (program, scope) => {
123584
123595
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
@@ -123765,6 +123776,28 @@ function reactElementToHast(node) {
123765
123776
 
123766
123777
 
123767
123778
 
123779
+
123780
+
123781
+
123782
+
123783
+ /**
123784
+ * Bind the components a given expression actually uses as tags. Scoped per expression on purpose:
123785
+ * binding the whole hash would shadow same-named globals (a `math` component would break
123786
+ * `{Math.max(1, 2)}`) and pad `evaluate`'s `new Function` parameter list. Resolution defers to
123787
+ * `getComponentName` so an expression and a plain tag always reach the same component.
123788
+ */
123789
+ const componentScope = (expression, components) => {
123790
+ const scope = {};
123791
+ jsxComponentNames(expression).forEach(name => {
123792
+ // `getComponentName` normalizes the tag, never the key, so it can't match `<MyBlock/>` to a
123793
+ // `my_block` entry; compare the key's PascalCase form for that direction.
123794
+ const tagName = getComponentName(name, components) ?? Object.keys(components).find(k => toPascalCase(k) === name);
123795
+ if (!tagName)
123796
+ return;
123797
+ scope[name] = (props) => external_react_default().createElement(tagName, props);
123798
+ });
123799
+ return scope;
123800
+ };
123768
123801
  /**
123769
123802
  * We divide the result of an expression into two categories:
123770
123803
  * 1. Renderable values: HTML, JSX, e.g. .map() returning JSX
@@ -123775,6 +123808,17 @@ const isRenderable = (value) => {
123775
123808
  return true;
123776
123809
  return Array.isArray(value) && value.some(isRenderable);
123777
123810
  };
123811
+ /**
123812
+ * Whether an expression evaluated to block-level content. Components count as block-level
123813
+ * unless they're on the inline list, matching the assumption the rest of the component
123814
+ * pipeline makes.
123815
+ */
123816
+ const isBlockResult = (children) => children.some(child => {
123817
+ if (child.type !== 'element' && child.type !== 'mdx-jsx')
123818
+ return false;
123819
+ const { tagName } = child;
123820
+ return !STANDARD_HTML_TAGS.has(tagName.toLowerCase()) && !INLINE_COMPONENT_TAGS.has(tagName);
123821
+ });
123778
123822
  /** Turn a non-renderable evaluation result into a text node. */
123779
123823
  const createTextNode = (result, position) => {
123780
123824
  if (result === null || result === undefined)
@@ -123786,13 +123830,22 @@ const createTextNode = (result, position) => {
123786
123830
  /**
123787
123831
  * AST transformer to evaluate MDX expressions.
123788
123832
  * Replaces mdxFlowExpression and mdxTextExpression nodes with their evaluated values.
123789
- * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that
123790
- * reference identifiers can resolve if those identifiers were introduced by an
123791
- * earlier `export const/function` (collected onto `file.data.mdxishScope`).
123792
- * Anything else falls through to the error branch and is kept as literal `{...}` text.
123793
- */
123794
- const evaluateExpressions = () => (tree, file) => {
123795
- const scope = { ...(file.data.mdxishScope), React: (external_react_default()) };
123833
+ * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that reference
123834
+ * identifiers can resolve if those identifiers are a custom component, the `user` variables
123835
+ * object, or were introduced by an earlier `export const/function` (collected onto
123836
+ * `file.data.mdxishScope`). Anything else falls through to the error branch and is kept as
123837
+ * literal `{...}` text.
123838
+ */
123839
+ const evaluateExpressions = ({ components, variables } = {}) => (tree, file) => {
123840
+ const baseScope = {
123841
+ // `User` matches the fallback the MDX path binds in `run.tsx`. Only when variables were
123842
+ // supplied: the proxy never throws, so an unconditional bind would resolve `user.*` on
123843
+ // surfaces that render without them instead of leaving literal text.
123844
+ ...(variables ? { user: user(variables) } : {}),
123845
+ // In-document exports win, matching `renderMdxish`.
123846
+ ...file.data.mdxishScope,
123847
+ React: (external_react_default()),
123848
+ };
123796
123849
  visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => {
123797
123850
  if (!parent || index === null || index === undefined)
123798
123851
  return;
@@ -123802,6 +123855,7 @@ const evaluateExpressions = () => (tree, file) => {
123802
123855
  if (!expression)
123803
123856
  return;
123804
123857
  try {
123858
+ const scope = { ...componentScope(expression, components ?? {}), ...baseScope };
123805
123859
  const result = evalExpression(expression, scope);
123806
123860
  if (isRenderable(result)) {
123807
123861
  // Stash hast built straight from the React tree; `mdxExpressionHandler` emits it and it
@@ -123822,6 +123876,22 @@ const evaluateExpressions = () => (tree, file) => {
123822
123876
  parent.children.splice(index, 1, { type: 'text', value: `{${processed}}`, position });
123823
123877
  }
123824
123878
  });
123879
+ // A text expression is parsed inside a paragraph, but its result can be block content: a
123880
+ // `<Tabs>` renders a `<div>`, and a browser closes the `<p>` before it, so the DOM it builds
123881
+ // no longer matches what was rendered and hydration fails. Lift the expression out when
123882
+ // that's all the paragraph holds.
123883
+ visit(tree, 'paragraph', (node, index, parent) => {
123884
+ if (!parent || index === null || index === undefined)
123885
+ return;
123886
+ const meaningful = node.children.filter(child => !(child.type === 'text' && !child.value.trim()));
123887
+ const [only] = meaningful;
123888
+ if (meaningful.length !== 1 || only.type !== 'mdxTextExpression')
123889
+ return;
123890
+ const hChildren = only.data?.hChildren;
123891
+ if (!hChildren || !isBlockResult(hChildren))
123892
+ return;
123893
+ parent.children.splice(index, 1, only);
123894
+ });
123825
123895
  return tree;
123826
123896
  };
123827
123897
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
@@ -127049,45 +127119,6 @@ const resolveDeferredAttributeExpressionProps = () => (tree, file) => {
127049
127119
  };
127050
127120
  /* harmony default export */ const resolve_deferred_attribute_expression_props = (resolveDeferredAttributeExpressionProps);
127051
127121
 
127052
- ;// ./processor/transform/mdxish/restore-snake-case-component-name.ts
127053
-
127054
-
127055
- /**
127056
- * Restores snake_case component names from placeholders after parsing.
127057
- * Runs after mdxishComponentBlocks converts HTML nodes to mdxJsxFlowElement.
127058
- */
127059
- const restoreSnakeCaseComponentNames = (options) => {
127060
- const { mapping } = options;
127061
- return tree => {
127062
- if (!mapping || Object.keys(mapping).length === 0) {
127063
- return tree;
127064
- }
127065
- visit(tree, 'mdxJsxFlowElement', (node) => {
127066
- if (node.name) {
127067
- node.name = restoreSnakeCase(node.name, mapping);
127068
- }
127069
- });
127070
- // Pre-compile regex patterns for better performance
127071
- const regexPatterns = Object.entries(mapping).map(([placeholder, original]) => ({
127072
- regex: new RegExp(`(<\\/?)(${placeholder})(\\s|\\/?>)`, 'gi'),
127073
- original,
127074
- }));
127075
- visit(tree, 'html', (node) => {
127076
- if (node.value) {
127077
- let newValue = node.value;
127078
- regexPatterns.forEach(({ regex, original }) => {
127079
- newValue = newValue.replace(regex, `$1${original}$3`);
127080
- });
127081
- if (newValue !== node.value) {
127082
- node.value = newValue;
127083
- }
127084
- }
127085
- });
127086
- return tree;
127087
- };
127088
- };
127089
- /* harmony default export */ const restore_snake_case_component_name = (restoreSnakeCaseComponentNames);
127090
-
127091
127122
  ;// ./processor/transform/mdxish/retain-boolean-attributes.ts
127092
127123
 
127093
127124
  // Private Use Area character (U+E000) which is extremely unlikely to appear in real content.
@@ -127610,9 +127641,6 @@ function loadComponents() {
127610
127641
 
127611
127642
 
127612
127643
 
127613
-
127614
-
127615
-
127616
127644
 
127617
127645
 
127618
127646
 
@@ -127634,10 +127662,8 @@ const defaultTransformers = [
127634
127662
  * 5. Terminate HTML flow blocks so subsequent content isn't swallowed
127635
127663
  * 6. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
127636
127664
  * 7. Normalize compact ATX headings (e.g., `#Heading` → `# Heading`)
127637
- * 8. Replace snake_case component names with parser-safe placeholders
127638
127665
  */
127639
- function preprocessContent(content, opts) {
127640
- const { knownComponents } = opts;
127666
+ function preprocessContent(content) {
127641
127667
  // Runs first so `jsxTable` sees a literal `</table>` (and the HTML-line
127642
127668
  // classification in `terminateHtmlFlowBlocks` is accurate)
127643
127669
  let result = normalizeClosingTagWhitespace(content);
@@ -127650,7 +127676,7 @@ function preprocessContent(content, opts) {
127650
127676
  result = terminateHtmlFlowBlocks(result);
127651
127677
  result = closeSelfClosingHtmlTags(result);
127652
127678
  result = normalizeCompactHeadings(result);
127653
- return processSnakeCaseComponent(result, { knownComponents });
127679
+ return result;
127654
127680
  }
127655
127681
  function mdxishAstProcessor(mdContent, opts = {}) {
127656
127682
  const { components: userComponents = {}, hardBreaks: enableHardBreaks = true, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
@@ -127658,9 +127684,7 @@ function mdxishAstProcessor(mdContent, opts = {}) {
127658
127684
  ...loadComponents(),
127659
127685
  ...userComponents,
127660
127686
  };
127661
- // Build set of known component names for snake_case filtering
127662
- const knownComponents = new Set(Object.keys(components));
127663
- const { content: parserReadyContent, mapping: snakeCaseMapping } = preprocessContent(mdContent, { knownComponents });
127687
+ const parserReadyContent = preprocessContent(mdContent);
127664
127688
  // Create string map for tailwind transformer
127665
127689
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
127666
127690
  acc[key] = String(value);
@@ -127673,10 +127697,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
127673
127697
  .use(remarkParse)
127674
127698
  .use(remarkFrontmatter)
127675
127699
  .use(normalize_malformed_md_syntax)
127676
- .use(self_closing_blocks)
127677
127700
  .use(mdx_blocks, { safeMode })
127678
127701
  .use(inline_html, { safeMode })
127679
- .use(restore_snake_case_component_name, { mapping: snakeCaseMapping })
127680
127702
  .use(mdxish_tables)
127681
127703
  .use(mdxish_html_blocks) // Convert every <HTMLBlock> shape → html-block
127682
127704
  // The next few transformers must appear after mdxishMdxComponentBlocks
@@ -127752,7 +127774,7 @@ function mdxish(mdContent, opts = {}) {
127752
127774
  processor
127753
127775
  .use(safeMode ? undefined : evaluate_exports) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
127754
127776
  .use(enableHardBreaks ? hard_breaks : undefined) // Must precede evaluateExpressions to avoid splitting the \n in an evaluated template literal into a <br> node
127755
- .use(safeMode ? undefined : evaluate_expressions) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
127777
+ .use(safeMode ? undefined : evaluate_expressions, { components, variables }) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
127756
127778
  .use(safeMode ? undefined : evaluate_style_block_expressions) // Evaluate `<style>{`...`}</style>` template literals into plain CSS
127757
127779
  .use(variables_code, { variables }) // Resolve <<...>> and {user.*} inside code and inline code nodes
127758
127780
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
@@ -128843,7 +128865,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"*":["about","acceptCharset","accessK
128843
128865
  /******/ // startup
128844
128866
  /******/ // Load entry module and return exports
128845
128867
  /******/ // This entry module used 'module' so it can't be inlined
128846
- /******/ let __webpack_exports__ = __webpack_require__(345);
128868
+ /******/ let __webpack_exports__ = __webpack_require__(8691);
128847
128869
  /******/ module.exports = __webpack_exports__;
128848
128870
  /******/
128849
128871
  /******/ })()