@readme/markdown 15.4.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.
package/dist/main.js CHANGED
@@ -11423,7 +11423,7 @@ module.exports = function () {
11423
11423
 
11424
11424
  /***/ },
11425
11425
 
11426
- /***/ 5168
11426
+ /***/ 912
11427
11427
  (module, __webpack_exports__, __webpack_require__) {
11428
11428
 
11429
11429
  "use strict";
@@ -11564,6 +11564,63 @@ __webpack_require__.d(util_types_namespaceObject, {
11564
11564
  // EXTERNAL MODULE: external {"amd":"react","commonjs":"react","commonjs2":"react","root":"React","umd":"react"}
11565
11565
  var external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_ = __webpack_require__(1307);
11566
11566
  var external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default = /*#__PURE__*/__webpack_require__.n(external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_);
11567
+ ;// ./hooks/useRestartAnimatedImages/index.tsx
11568
+
11569
+ /**
11570
+ * GIF is the one extension that implies animation. `.webp` and `.png` (the extension an APNG
11571
+ * ships under) are overwhelmingly static, so matching them would reload far more images than
11572
+ * it rewinds; telling those apart needs the bytes, not the URL.
11573
+ */
11574
+ const ANIMATED_IMAGE_SRC = /\.gif(\?|#|$)/i;
11575
+ const restartAnimatedImages = (root) => {
11576
+ root?.querySelectorAll('img').forEach(img => {
11577
+ if (!ANIMATED_IMAGE_SRC.test(img.src))
11578
+ return;
11579
+ const { src } = img;
11580
+ // There's no seek API for animated images; reassigning `src` is the only reset
11581
+ img.src = '';
11582
+ img.src = src;
11583
+ });
11584
+ };
11585
+ const useRestartAfterFirstPaint = (revealKey, restart) => {
11586
+ const isFirstRender = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useRef)(true);
11587
+ const latestRestart = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useRef)(restart);
11588
+ latestRestart.current = restart;
11589
+ (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useEffect)(() => {
11590
+ if (isFirstRender.current) {
11591
+ isFirstRender.current = false;
11592
+ return;
11593
+ }
11594
+ latestRestart.current();
11595
+ }, [revealKey]);
11596
+ };
11597
+ /**
11598
+ * Restarts GIF playback inside the panel that just became active.
11599
+ *
11600
+ * Tabs keep every panel mounted, so an <img> is never re-created and a GIF is
11601
+ * shown mid-loop (or frozen on its last frame) when its tab is re-selected.
11602
+ *
11603
+ * @returns a ref to attach to each panel element, indexed by tab.
11604
+ */
11605
+ function useRestartAnimatedImages(activeIndex) {
11606
+ const panelRefs = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useRef)([]);
11607
+ useRestartAfterFirstPaint(activeIndex, () => restartAnimatedImages(panelRefs.current[activeIndex]));
11608
+ return panelRefs;
11609
+ }
11610
+ /**
11611
+ * Restarts GIF playback inside an element that has just been revealed after being hidden —
11612
+ * a collapsed <details>, whose images have been animating out of sight since page load.
11613
+ */
11614
+ function useRestartAnimatedImagesOnReveal(isRevealed) {
11615
+ const contentRef = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useRef)(null);
11616
+ useRestartAfterFirstPaint(isRevealed, () => {
11617
+ // Rewinding content the reader just hid would be wasted work
11618
+ if (isRevealed)
11619
+ restartAnimatedImages(contentRef.current);
11620
+ });
11621
+ return contentRef;
11622
+ }
11623
+
11567
11624
  ;// ./components/Icon/index.tsx
11568
11625
 
11569
11626
  /** @see https://docs-v5.fontawesome.com/web/reference-icons */
@@ -11588,14 +11645,16 @@ const Icon = ({ className, faClassName, icon, iconColor }) => {
11588
11645
 
11589
11646
 
11590
11647
 
11648
+
11591
11649
  const Accordion = ({ children, icon, iconColor, title }) => {
11592
11650
  const [isOpen, setIsOpen] = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useState)(false);
11651
+ const contentRef = useRestartAnimatedImagesOnReveal(isOpen);
11593
11652
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("details", { className: "Accordion", onToggle: () => setIsOpen(!isOpen) },
11594
11653
  external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("summary", { className: "Accordion-title" },
11595
11654
  external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("i", { className: `Accordion-toggleIcon${isOpen ? '_opened' : ''} fa fa-regular fa-chevron-right` }),
11596
11655
  icon && external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(components_Icon, { className: "Accordion-icon", icon: icon, iconColor: iconColor }),
11597
11656
  title),
11598
- external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "Accordion-content" }, children)));
11657
+ external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { ref: contentRef, className: "Accordion-content" }, children)));
11599
11658
  };
11600
11659
  /* harmony default export */ const components_Accordion = (Accordion);
11601
11660
 
@@ -12495,11 +12554,13 @@ function TableOfContents({ children }) {
12495
12554
 
12496
12555
 
12497
12556
 
12557
+
12498
12558
  const Tab = ({ children }) => {
12499
12559
  return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "TabContent" }, children);
12500
12560
  };
12501
12561
  const Tabs = ({ children }) => {
12502
12562
  const [activeTab, setActiveTab] = (0,external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_.useState)(0);
12563
+ const panelRefs = useRestartAnimatedImages(activeTab);
12503
12564
  // React passes `children` as a single element when there's only one child, so normalize.
12504
12565
  const tabs = external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().Children.toArray(children);
12505
12566
  return (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { className: "TabGroup" },
@@ -12507,7 +12568,9 @@ const Tabs = ({ children }) => {
12507
12568
  external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("nav", { className: "TabGroup-nav" }, tabs.map((tab, index) => (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("button", { key: tab.key, className: `TabGroup-tab${activeTab === index ? '_active' : ''}`, onClick: () => setActiveTab(index) },
12508
12569
  tab.props.icon && (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(components_Icon, { className: "TabGroup-icon", icon: tab.props.icon, iconColor: tab.props.iconColor })),
12509
12570
  tab.props.title))))),
12510
- external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("section", null, tabs.map((tab, index) => (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { key: tab.key, hidden: index !== activeTab }, tab))))));
12571
+ external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("section", null, tabs.map((tab, index) => (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement("div", { key: tab.key, ref: el => {
12572
+ panelRefs.current[index] = el;
12573
+ }, hidden: index !== activeTab }, tab))))));
12511
12574
  };
12512
12575
  /* harmony default export */ const components_Tabs = (Tabs);
12513
12576
 
@@ -76390,6 +76453,10 @@ function syntax_createTokenize(mode) {
76390
76453
  effects.consume(code);
76391
76454
  return inBraceExpr;
76392
76455
  }
76456
+ // A raw `<` can't sit in an opening tag (quotes/braces are handled above);
76457
+ // bailing bounds each attempt to the next `<` instead of rescanning the line.
76458
+ if (code === codes.lessThan)
76459
+ return nok(code);
76393
76460
  effects.consume(code);
76394
76461
  return afterOpenTagName;
76395
76462
  }
@@ -101596,7 +101663,7 @@ const listMarkerRegex = /^(?:[*+-]|\d+[.)])(?:([\r\n]| {1,3})|$)/;
101596
101663
  * with their checkbox intact (for example, `- [ ]`) instead of dropping it
101597
101664
  * We can add more adjustments if needed
101598
101665
  */
101599
- const compile_list_item_listItem = (node, parent, state, info) => {
101666
+ const compile_list_item_listItem = ((node, parent, state, info) => {
101600
101667
  const head = node.children[0];
101601
101668
  const isCheckbox = typeof node.checked === 'boolean' && head && head.type === 'paragraph';
101602
101669
  if (!isCheckbox) {
@@ -101620,15 +101687,47 @@ const compile_list_item_listItem = (node, parent, state, info) => {
101620
101687
  return `${marker}${actualSeparator}${checkbox}`;
101621
101688
  });
101622
101689
  return value;
101623
- };
101690
+ });
101624
101691
  /* harmony default export */ const list_item = (compile_list_item_listItem);
101625
101692
 
101626
101693
  ;// ./processor/compile/plain.ts
101627
101694
  const plain_plain = (node) => node.value;
101628
101695
  /* harmony default export */ const compile_plain = (plain_plain);
101629
101696
 
101697
+ ;// ./processor/compile/table-cell-block-markers.ts
101698
+ const CELL_START_BLOCK_MARKER = /^(?:[-+](?=\s|$)|#{1,6}(?=\s|$)|>)/;
101699
+ const BR_HTML = /^\s*<br\s*\/?>\s*$/i;
101700
+ const isLineBreak = (node) => {
101701
+ if (node.type === 'break')
101702
+ return true;
101703
+ if (node.type === 'html')
101704
+ return BR_HTML.test(node.value);
101705
+ return node.type === 'mdxJsxTextElement' && 'name' in node && node.name?.toLowerCase() === 'br';
101706
+ };
101707
+ const followsLineBreak = (node, parent) => {
101708
+ const siblings = (parent?.children ?? []);
101709
+ const previous = siblings[siblings.indexOf(node) - 1];
101710
+ return !!previous && isLineBreak(previous);
101711
+ };
101712
+ /**
101713
+ * Re-escapes a leading block marker (`-`, `+`, `#`, `>`) at the start of a table cell, or
101714
+ * after a `<br />` within one.
101715
+ *
101716
+ * A cell is serialized mid-line, so the `atBreak` patterns that escape these characters
101717
+ * never fire and an author's `| \- one |` round trips to `| - one |` (RM-17203). Cells are
101718
+ * serialized as `containerPhrasing(cell, { before: '|' })`, so `|` marks the cell start.
101719
+ */
101720
+ const escapeCellStartBlockMarker = (serialized, node, parent, state, info) => {
101721
+ if (serialized.startsWith('\\') || !CELL_START_BLOCK_MARKER.test(serialized))
101722
+ return serialized;
101723
+ if (!state.stack.includes('tableCell') || !(info.before === '|' || followsLineBreak(node, parent)))
101724
+ return serialized;
101725
+ return `\\${serialized}`;
101726
+ };
101727
+
101630
101728
  ;// ./processor/compile/text.ts
101631
101729
 
101730
+
101632
101731
  // A `_` flanked by word characters can never open or close emphasis under
101633
101732
  // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
101634
101733
  // intraword underscores is unnecessary and only produces noisy `\_` diffs.
@@ -101636,9 +101735,10 @@ const plain_plain = (node) => node.value;
101636
101735
  const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
101637
101736
  const compile_text_text = (node, parent, state, info) => {
101638
101737
  const serialized = handle.text(node, parent, state, info);
101639
- return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
101738
+ return escapeCellStartBlockMarker(serialized, node, parent, state, info);
101640
101739
  };
101641
- /* harmony default export */ const compile_text = (compile_text_text);
101740
+ const mdxishText = (node, parent, state, info) => compile_text_text(node, parent, state, info).replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
101741
+ /* harmony default export */ const compile_text = (mdxishText);
101642
101742
 
101643
101743
  ;// ./processor/compile/index.ts
101644
101744
 
@@ -101670,6 +101770,7 @@ function compilers(mdxish = false) {
101670
101770
  html: compile_compatibility,
101671
101771
  i: compile_compatibility,
101672
101772
  plain: compile_plain,
101773
+ text: compile_text_text,
101673
101774
  yaml: compile_compatibility,
101674
101775
  // needed only for mdxish
101675
101776
  ...(mdxish && { list: compile_list }),
@@ -103738,13 +103839,14 @@ function restoreCodeBlocks(content, protectedCode) {
103738
103839
  *
103739
103840
  * The attribute portion skips over quoted strings (`"..."` and `'...'`) so that
103740
103841
  * a `/>` inside an attribute value (e.g. `title="use /> here"`) does not cause
103741
- * a premature match.
103842
+ * a premature match, and stops at an unquoted `<` so a stray `<x` in prose
103843
+ * doesn't scan to the end of the document (quadratic).
103742
103844
  *
103743
103845
  * Only matches lowercase tag names to avoid interfering with PascalCase
103744
103846
  * JSX custom components (e.g. `<MyComponent />`), which are handled
103745
103847
  * separately by components/mdx-blocks.
103746
103848
  */
103747
- const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)?)?\s*\/>/g;
103849
+ const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^<>"']*(?:"[^"]*"|'[^']*'))*[^<>"']*)?)?\s*\/>/g;
103748
103850
  /**
103749
103851
  * String-level preprocessor that converts self-closing non-void HTML tags
103750
103852
  * into explicitly closed tags.
@@ -104409,8 +104511,9 @@ function terminateHtmlFlowBlocks(content) {
104409
104511
 
104410
104512
 
104411
104513
 
104412
- // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
104413
- const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
104514
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. One name
104515
+ // char suffices for existence — `[\w-]+` backtracked quadratically over huge attributes.
104516
+ const NESTED_ATTR_EXPRESSION_RE = /[\w-]\s*=\s*\{/;
104414
104517
  // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
104415
104518
  // of a legacy `<<VARIABLE>>`.
104416
104519
  const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
@@ -104709,150 +104812,6 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
104709
104812
  };
104710
104813
  /* harmony default export */ const mdx_blocks = (mdxishMdxComponentBlocks);
104711
104814
 
104712
- ;// ./processor/transform/mdxish/components/self-closing-blocks.ts
104713
-
104714
-
104715
- /**
104716
- * Tags to process as self-closing blocks.
104717
- * These components use simple string attributes (no JSX expressions like `data={[...]}`).
104718
- * Components with JSX expression attributes should NOT be added here as parseAttributes
104719
- * cannot handle them correctly.
104720
- */
104721
- const SELF_CLOSING_BLOCK_TAGS = new Set(['Embed', 'Recipe']);
104722
- // Regex to match self-closing PascalCase tags (handles multi-line)
104723
- const selfClosingTagPattern = /^<([A-Z][A-Za-z0-9_]*)([\s\S]*?)\/>$/;
104724
- /**
104725
- * Try to convert a paragraph node containing a self-closing JSX component into an mdxJsxFlowElement.
104726
- * Returns the new node if conversion succeeded, or null if the node doesn't match.
104727
- */
104728
- const tryConvertToMdxNode = (node) => {
104729
- if (node.children.length !== 1)
104730
- return null;
104731
- const child = node.children[0];
104732
- if (child.type !== 'html')
104733
- return null;
104734
- const value = child.value?.trim();
104735
- if (!value)
104736
- return null;
104737
- const match = value.match(selfClosingTagPattern);
104738
- if (!match)
104739
- return null;
104740
- const [, tag, attrString] = match;
104741
- if (!SELF_CLOSING_BLOCK_TAGS.has(tag))
104742
- return null;
104743
- return {
104744
- type: 'mdxJsxFlowElement',
104745
- name: tag,
104746
- attributes: parseAttributes(attrString),
104747
- children: [],
104748
- position: node.position,
104749
- };
104750
- };
104751
- /**
104752
- * Transform paragraph-wrapped self-closing JSX components into mdxJsxFlowElement nodes.
104753
- *
104754
- * CommonMark wraps multi-line JSX in paragraphs when the opening tag isn't complete
104755
- * on one line. This plugin detects these structures and unwraps them for components
104756
- * in the SELF_CLOSING_BLOCK_TAGS allowlist.
104757
- *
104758
- * Input structure:
104759
- * ```
104760
- * paragraph > html: "<Embed\n typeOfEmbed=\"youtube\"\n/>"
104761
- * ```
104762
- *
104763
- * Output structure:
104764
- * ```
104765
- * mdxJsxFlowElement: { name: "Embed", attributes: [...], children: [] }
104766
- * ```
104767
- */
104768
- const mdxishSelfClosingBlocks = () => tree => {
104769
- visit(tree, 'paragraph', (node, index, parent) => {
104770
- if (index === undefined || !parent)
104771
- return;
104772
- const mdxNode = tryConvertToMdxNode(node);
104773
- if (mdxNode) {
104774
- parent.children.splice(index, 1, mdxNode);
104775
- }
104776
- });
104777
- };
104778
- /* harmony default export */ const self_closing_blocks = (mdxishSelfClosingBlocks);
104779
-
104780
- ;// ./processor/transform/mdxish/components/snake-case-components.ts
104781
-
104782
-
104783
- /**
104784
- * Replaces snake_case component names with valid HTML placeholders.
104785
- * Required because remark-parse rejects tags with underscores.
104786
- * Example: `<Snake_case />` → `<MDXishSnakeCase0 />`
104787
- *
104788
- * Code blocks and inline code are protected and will not be transformed.
104789
- *
104790
- * @param content - The markdown content to process
104791
- * @param options - Options including knownComponents to filter by
104792
- */
104793
- function processSnakeCaseComponent(content, options = {}) {
104794
- const { knownComponents } = options;
104795
- // Early exit if no potential snake_case components
104796
- if (!/[A-Z][A-Za-z0-9]*_[A-Za-z0-9_]*/.test(content)) {
104797
- return { content, mapping: {} };
104798
- }
104799
- // Step 1: Extract code blocks to protect them from transformation
104800
- const { protectedCode, protectedContent } = protectCodeBlocks(content);
104801
- // Find the highest existing placeholder number to avoid collisions
104802
- // e.g., if content has <MDXishSnakeCase0 />, start counter from 1
104803
- const placeholderPattern = /MDXishSnakeCase(\d+)/g;
104804
- let startCounter = 0;
104805
- let placeholderMatch;
104806
- while ((placeholderMatch = placeholderPattern.exec(content)) !== null) {
104807
- const num = parseInt(placeholderMatch[1], 10);
104808
- if (num >= startCounter) {
104809
- startCounter = num + 1;
104810
- }
104811
- }
104812
- const mapping = {};
104813
- const reverseMap = new Map();
104814
- let counter = startCounter;
104815
- // Step 2: Transform snake_case components in non-code content
104816
- const processedContent = protectedContent.replace(componentTagPattern, (match, tagName, attrs, selfClosing) => {
104817
- if (!tagName.includes('_')) {
104818
- return match;
104819
- }
104820
- const isClosing = tagName.startsWith('/');
104821
- const cleanTagName = isClosing ? tagName.slice(1) : tagName;
104822
- // Only transform if it's a known component (or if no filter is provided)
104823
- if (knownComponents && !knownComponents.has(cleanTagName)) {
104824
- return match;
104825
- }
104826
- let placeholder = reverseMap.get(cleanTagName);
104827
- if (!placeholder) {
104828
- // eslint-disable-next-line no-plusplus
104829
- placeholder = `MDXishSnakeCase${counter++}`;
104830
- mapping[placeholder] = cleanTagName;
104831
- reverseMap.set(cleanTagName, placeholder);
104832
- }
104833
- const processedTagName = isClosing ? `/${placeholder}` : placeholder;
104834
- return `<${processedTagName}${attrs}${selfClosing}>`;
104835
- });
104836
- // Step 3: Restore code blocks (untouched)
104837
- const finalContent = restoreCodeBlocks(processedContent, protectedCode);
104838
- return {
104839
- content: finalContent,
104840
- mapping,
104841
- };
104842
- }
104843
- /**
104844
- * Restores placeholder name to original snake_case name.
104845
- * Uses case-insensitive matching since HTML parsers normalize to lowercase.
104846
- */
104847
- function restoreSnakeCase(placeholderName, mapping) {
104848
- if (mapping[placeholderName]) {
104849
- return mapping[placeholderName];
104850
- }
104851
- const lowerName = placeholderName.toLowerCase();
104852
- const matchingKey = Object.keys(mapping).find(key => key.toLowerCase() === lowerName);
104853
- return matchingKey ? mapping[matchingKey] : placeholderName;
104854
- }
104855
-
104856
104815
  ;// ./processor/transform/mdxish/resolve-esm-imports.ts
104857
104816
 
104858
104817
  // We provide React as a default module so that components can use hooks
@@ -105037,6 +104996,54 @@ const containsJsxNode = (value) => {
105037
104996
  return true;
105038
104997
  return Object.values(value).some(containsJsxNode);
105039
104998
  };
104999
+ /** Read the component name off a JSX element name node (`Foo`, `Foo.Bar`, `foo:Bar`). */
105000
+ const jsxElementName = (name) => {
105001
+ if (name === null || typeof name !== 'object')
105002
+ return undefined;
105003
+ const node = name;
105004
+ if (node.type === 'JSXIdentifier')
105005
+ return typeof node.name === 'string' ? node.name : undefined;
105006
+ // `<Foo.Bar/>` and `<foo:Bar/>` resolve through their leftmost part.
105007
+ if (node.type === 'JSXMemberExpression')
105008
+ return jsxElementName(node.object);
105009
+ if (node.type === 'JSXNamespacedName')
105010
+ return jsxElementName(node.namespace);
105011
+ return undefined;
105012
+ };
105013
+ /**
105014
+ * Collect the capitalized names an expression uses as JSX tags. Parsed rather than pattern
105015
+ * matched: `{count < Max ? <Foo/> : <Bar/>}` puts a capitalized name straight after a `<` without
105016
+ * it being a tag, and only the parser can tell the two apart. Unparseable input yields nothing —
105017
+ * evaluation is about to throw on it anyway.
105018
+ */
105019
+ const jsxComponentNames = (expression) => {
105020
+ let program;
105021
+ try {
105022
+ program = parseExpression(expression);
105023
+ }
105024
+ catch {
105025
+ return [];
105026
+ }
105027
+ const names = new Set();
105028
+ const walk = (value) => {
105029
+ if (Array.isArray(value)) {
105030
+ value.forEach(walk);
105031
+ return;
105032
+ }
105033
+ if (value === null || typeof value !== 'object')
105034
+ return;
105035
+ const node = value;
105036
+ if (node.type === 'JSXOpeningElement') {
105037
+ const name = jsxElementName(node.name);
105038
+ // Lowercase tags compile to a string type, never a variable reference.
105039
+ if (name && /^[A-Z]/.test(name))
105040
+ names.add(name);
105041
+ }
105042
+ Object.values(node).forEach(walk);
105043
+ };
105044
+ walk(program);
105045
+ return Array.from(names);
105046
+ };
105040
105047
  /** Convert a program's JSX into `React.createElement` calls and evaluate it. `scope` must provide `React`. */
105041
105048
  const evalJsxProgram = (program, scope) => {
105042
105049
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
@@ -105242,6 +105249,28 @@ function reactElementToHast(node) {
105242
105249
 
105243
105250
 
105244
105251
 
105252
+
105253
+
105254
+
105255
+
105256
+ /**
105257
+ * Bind the components a given expression actually uses as tags. Scoped per expression on purpose:
105258
+ * binding the whole hash would shadow same-named globals (a `math` component would break
105259
+ * `{Math.max(1, 2)}`) and pad `evaluate`'s `new Function` parameter list. Resolution defers to
105260
+ * `getComponentName` so an expression and a plain tag always reach the same component.
105261
+ */
105262
+ const componentScope = (expression, components) => {
105263
+ const scope = {};
105264
+ jsxComponentNames(expression).forEach(name => {
105265
+ // `getComponentName` normalizes the tag, never the key, so it can't match `<MyBlock/>` to a
105266
+ // `my_block` entry; compare the key's PascalCase form for that direction.
105267
+ const tagName = getComponentName(name, components) ?? Object.keys(components).find(k => toPascalCase(k) === name);
105268
+ if (!tagName)
105269
+ return;
105270
+ scope[name] = (props) => external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(tagName, props);
105271
+ });
105272
+ return scope;
105273
+ };
105245
105274
  /**
105246
105275
  * We divide the result of an expression into two categories:
105247
105276
  * 1. Renderable values: HTML, JSX, e.g. .map() returning JSX
@@ -105252,6 +105281,17 @@ const isRenderable = (value) => {
105252
105281
  return true;
105253
105282
  return Array.isArray(value) && value.some(isRenderable);
105254
105283
  };
105284
+ /**
105285
+ * Whether an expression evaluated to block-level content. Components count as block-level
105286
+ * unless they're on the inline list, matching the assumption the rest of the component
105287
+ * pipeline makes.
105288
+ */
105289
+ const isBlockResult = (children) => children.some(child => {
105290
+ if (child.type !== 'element' && child.type !== 'mdx-jsx')
105291
+ return false;
105292
+ const { tagName } = child;
105293
+ return !STANDARD_HTML_TAGS.has(tagName.toLowerCase()) && !INLINE_COMPONENT_TAGS.has(tagName);
105294
+ });
105255
105295
  /** Turn a non-renderable evaluation result into a text node. */
105256
105296
  const createTextNode = (result, position) => {
105257
105297
  if (result === null || result === undefined)
@@ -105263,13 +105303,22 @@ const createTextNode = (result, position) => {
105263
105303
  /**
105264
105304
  * AST transformer to evaluate MDX expressions.
105265
105305
  * Replaces mdxFlowExpression and mdxTextExpression nodes with their evaluated values.
105266
- * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that
105267
- * reference identifiers can resolve if those identifiers were introduced by an
105268
- * earlier `export const/function` (collected onto `file.data.mdxishScope`).
105269
- * Anything else falls through to the error branch and is kept as literal `{...}` text.
105270
- */
105271
- const evaluateExpressions = () => (tree, file) => {
105272
- const scope = { ...(file.data.mdxishScope), React: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()) };
105306
+ * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that reference
105307
+ * identifiers can resolve if those identifiers are a custom component, the `user` variables
105308
+ * object, or were introduced by an earlier `export const/function` (collected onto
105309
+ * `file.data.mdxishScope`). Anything else falls through to the error branch and is kept as
105310
+ * literal `{...}` text.
105311
+ */
105312
+ const evaluateExpressions = ({ components, variables } = {}) => (tree, file) => {
105313
+ const baseScope = {
105314
+ // `User` matches the fallback the MDX path binds in `run.tsx`. Only when variables were
105315
+ // supplied: the proxy never throws, so an unconditional bind would resolve `user.*` on
105316
+ // surfaces that render without them instead of leaving literal text.
105317
+ ...(variables ? { user: user(variables) } : {}),
105318
+ // In-document exports win, matching `renderMdxish`.
105319
+ ...file.data.mdxishScope,
105320
+ React: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()),
105321
+ };
105273
105322
  visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => {
105274
105323
  if (!parent || index === null || index === undefined)
105275
105324
  return;
@@ -105279,6 +105328,7 @@ const evaluateExpressions = () => (tree, file) => {
105279
105328
  if (!expression)
105280
105329
  return;
105281
105330
  try {
105331
+ const scope = { ...componentScope(expression, components ?? {}), ...baseScope };
105282
105332
  const result = evalExpression(expression, scope);
105283
105333
  if (isRenderable(result)) {
105284
105334
  // Stash hast built straight from the React tree; `mdxExpressionHandler` emits it and it
@@ -105299,6 +105349,22 @@ const evaluateExpressions = () => (tree, file) => {
105299
105349
  parent.children.splice(index, 1, { type: 'text', value: `{${processed}}`, position });
105300
105350
  }
105301
105351
  });
105352
+ // A text expression is parsed inside a paragraph, but its result can be block content: a
105353
+ // `<Tabs>` renders a `<div>`, and a browser closes the `<p>` before it, so the DOM it builds
105354
+ // no longer matches what was rendered and hydration fails. Lift the expression out when
105355
+ // that's all the paragraph holds.
105356
+ visit(tree, 'paragraph', (node, index, parent) => {
105357
+ if (!parent || index === null || index === undefined)
105358
+ return;
105359
+ const meaningful = node.children.filter(child => !(child.type === 'text' && !child.value.trim()));
105360
+ const [only] = meaningful;
105361
+ if (meaningful.length !== 1 || only.type !== 'mdxTextExpression')
105362
+ return;
105363
+ const hChildren = only.data?.hChildren;
105364
+ if (!hChildren || !isBlockResult(hChildren))
105365
+ return;
105366
+ parent.children.splice(index, 1, only);
105367
+ });
105302
105368
  return tree;
105303
105369
  };
105304
105370
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
@@ -107106,45 +107172,6 @@ const resolveDeferredAttributeExpressionProps = () => (tree, file) => {
107106
107172
  };
107107
107173
  /* harmony default export */ const resolve_deferred_attribute_expression_props = (resolveDeferredAttributeExpressionProps);
107108
107174
 
107109
- ;// ./processor/transform/mdxish/restore-snake-case-component-name.ts
107110
-
107111
-
107112
- /**
107113
- * Restores snake_case component names from placeholders after parsing.
107114
- * Runs after mdxishComponentBlocks converts HTML nodes to mdxJsxFlowElement.
107115
- */
107116
- const restoreSnakeCaseComponentNames = (options) => {
107117
- const { mapping } = options;
107118
- return tree => {
107119
- if (!mapping || Object.keys(mapping).length === 0) {
107120
- return tree;
107121
- }
107122
- visit(tree, 'mdxJsxFlowElement', (node) => {
107123
- if (node.name) {
107124
- node.name = restoreSnakeCase(node.name, mapping);
107125
- }
107126
- });
107127
- // Pre-compile regex patterns for better performance
107128
- const regexPatterns = Object.entries(mapping).map(([placeholder, original]) => ({
107129
- regex: new RegExp(`(<\\/?)(${placeholder})(\\s|\\/?>)`, 'gi'),
107130
- original,
107131
- }));
107132
- visit(tree, 'html', (node) => {
107133
- if (node.value) {
107134
- let newValue = node.value;
107135
- regexPatterns.forEach(({ regex, original }) => {
107136
- newValue = newValue.replace(regex, `$1${original}$3`);
107137
- });
107138
- if (newValue !== node.value) {
107139
- node.value = newValue;
107140
- }
107141
- }
107142
- });
107143
- return tree;
107144
- };
107145
- };
107146
- /* harmony default export */ const restore_snake_case_component_name = (restoreSnakeCaseComponentNames);
107147
-
107148
107175
  ;// ./processor/transform/mdxish/retain-boolean-attributes.ts
107149
107176
 
107150
107177
  // Private Use Area character (U+E000) which is extremely unlikely to appear in real content.
@@ -107686,9 +107713,6 @@ function loadComponents() {
107686
107713
 
107687
107714
 
107688
107715
 
107689
-
107690
-
107691
-
107692
107716
 
107693
107717
 
107694
107718
 
@@ -107710,10 +107734,8 @@ const defaultTransformers = [
107710
107734
  * 5. Terminate HTML flow blocks so subsequent content isn't swallowed
107711
107735
  * 6. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
107712
107736
  * 7. Normalize compact ATX headings (e.g., `#Heading` → `# Heading`)
107713
- * 8. Replace snake_case component names with parser-safe placeholders
107714
107737
  */
107715
- function preprocessContent(content, opts) {
107716
- const { knownComponents } = opts;
107738
+ function preprocessContent(content) {
107717
107739
  // Runs first so `jsxTable` sees a literal `</table>` (and the HTML-line
107718
107740
  // classification in `terminateHtmlFlowBlocks` is accurate)
107719
107741
  let result = normalizeClosingTagWhitespace(content);
@@ -107726,7 +107748,7 @@ function preprocessContent(content, opts) {
107726
107748
  result = terminateHtmlFlowBlocks(result);
107727
107749
  result = closeSelfClosingHtmlTags(result);
107728
107750
  result = normalizeCompactHeadings(result);
107729
- return processSnakeCaseComponent(result, { knownComponents });
107751
+ return result;
107730
107752
  }
107731
107753
  function mdxishAstProcessor(mdContent, opts = {}) {
107732
107754
  const { components: userComponents = {}, hardBreaks: enableHardBreaks = true, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
@@ -107734,9 +107756,7 @@ function mdxishAstProcessor(mdContent, opts = {}) {
107734
107756
  ...loadComponents(),
107735
107757
  ...userComponents,
107736
107758
  };
107737
- // Build set of known component names for snake_case filtering
107738
- const knownComponents = new Set(Object.keys(components));
107739
- const { content: parserReadyContent, mapping: snakeCaseMapping } = preprocessContent(mdContent, { knownComponents });
107759
+ const parserReadyContent = preprocessContent(mdContent);
107740
107760
  // Create string map for tailwind transformer
107741
107761
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
107742
107762
  acc[key] = String(value);
@@ -107749,10 +107769,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
107749
107769
  .use(remarkParse)
107750
107770
  .use(remarkFrontmatter)
107751
107771
  .use(normalize_malformed_md_syntax)
107752
- .use(self_closing_blocks)
107753
107772
  .use(mdx_blocks, { safeMode })
107754
107773
  .use(inline_html, { safeMode })
107755
- .use(restore_snake_case_component_name, { mapping: snakeCaseMapping })
107756
107774
  .use(mdxish_tables)
107757
107775
  .use(mdxish_html_blocks) // Convert every <HTMLBlock> shape → html-block
107758
107776
  // The next few transformers must appear after mdxishMdxComponentBlocks
@@ -107828,7 +107846,7 @@ function mdxish(mdContent, opts = {}) {
107828
107846
  processor
107829
107847
  .use(safeMode ? undefined : evaluate_exports) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
107830
107848
  .use(enableHardBreaks ? hard_breaks : undefined) // Must precede evaluateExpressions to avoid splitting the \n in an evaluated template literal into a <br> node
107831
- .use(safeMode ? undefined : evaluate_expressions) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
107849
+ .use(safeMode ? undefined : evaluate_expressions, { components, variables }) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
107832
107850
  .use(safeMode ? undefined : evaluate_style_block_expressions) // Evaluate `<style>{`...`}</style>` template literals into plain CSS
107833
107851
  .use(variables_code, { variables }) // Resolve <<...>> and {user.*} inside code and inline code nodes
107834
107852
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
@@ -108815,7 +108833,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"*":["about","acceptCharset","accessK
108815
108833
  /******/ // startup
108816
108834
  /******/ // Load entry module and return exports
108817
108835
  /******/ // This entry module used 'module' so it can't be inlined
108818
- /******/ let __webpack_exports__ = __webpack_require__(5168);
108836
+ /******/ let __webpack_exports__ = __webpack_require__(912);
108819
108837
  /******/
108820
108838
  /******/ return __webpack_exports__;
108821
108839
  /******/ })()