@readme/markdown 15.4.0 → 15.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.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
  }
@@ -81562,6 +81629,17 @@ const tablesToJsx = () => tree => {
81562
81629
  ;// ./processor/transform/tailwind.tsx
81563
81630
 
81564
81631
 
81632
+
81633
+
81634
+ /** Whitespace-only text is layout between blocks, not inline flow. */
81635
+ const isInlineContent = (node) => phrasing(node) && !(node.type === 'text' && node.value.trim() === '');
81636
+ /**
81637
+ * Whether the child at `index` sits in inline flow: its parent only allows
81638
+ * phrasing content (e.g. a paragraph), or it is flanked by phrasing siblings
81639
+ * (e.g. `<div>text <Image /> text</div>`, whose children skip the paragraph).
81640
+ */
81641
+ const isInlineInContext = (index, parent) => INLINE_ONLY_PARENT_TYPES.has(parent.type) ||
81642
+ parent.children.some((sibling, siblingIndex) => siblingIndex !== index && isInlineContent(sibling));
81565
81643
  const injectTailwindRoot = ({ components = {} }) => (node, index, parent) => {
81566
81644
  if (!('name' in node))
81567
81645
  return;
@@ -81570,7 +81648,7 @@ const injectTailwindRoot = ({ components = {} }) => (node, index, parent) => {
81570
81648
  if (!('children' in parent))
81571
81649
  return;
81572
81650
  const attrs = {
81573
- flow: node.type === 'mdxJsxFlowElement',
81651
+ flow: node.type === 'mdxJsxFlowElement' && !isInlineInContext(index, parent),
81574
81652
  };
81575
81653
  const wrapper = {
81576
81654
  type: node.type,
@@ -101596,7 +101674,7 @@ const listMarkerRegex = /^(?:[*+-]|\d+[.)])(?:([\r\n]| {1,3})|$)/;
101596
101674
  * with their checkbox intact (for example, `- [ ]`) instead of dropping it
101597
101675
  * We can add more adjustments if needed
101598
101676
  */
101599
- const compile_list_item_listItem = (node, parent, state, info) => {
101677
+ const compile_list_item_listItem = ((node, parent, state, info) => {
101600
101678
  const head = node.children[0];
101601
101679
  const isCheckbox = typeof node.checked === 'boolean' && head && head.type === 'paragraph';
101602
101680
  if (!isCheckbox) {
@@ -101620,15 +101698,47 @@ const compile_list_item_listItem = (node, parent, state, info) => {
101620
101698
  return `${marker}${actualSeparator}${checkbox}`;
101621
101699
  });
101622
101700
  return value;
101623
- };
101701
+ });
101624
101702
  /* harmony default export */ const list_item = (compile_list_item_listItem);
101625
101703
 
101626
101704
  ;// ./processor/compile/plain.ts
101627
101705
  const plain_plain = (node) => node.value;
101628
101706
  /* harmony default export */ const compile_plain = (plain_plain);
101629
101707
 
101708
+ ;// ./processor/compile/table-cell-block-markers.ts
101709
+ const CELL_START_BLOCK_MARKER = /^(?:[-+](?=\s|$)|#{1,6}(?=\s|$)|>)/;
101710
+ const BR_HTML = /^\s*<br\s*\/?>\s*$/i;
101711
+ const isLineBreak = (node) => {
101712
+ if (node.type === 'break')
101713
+ return true;
101714
+ if (node.type === 'html')
101715
+ return BR_HTML.test(node.value);
101716
+ return node.type === 'mdxJsxTextElement' && 'name' in node && node.name?.toLowerCase() === 'br';
101717
+ };
101718
+ const followsLineBreak = (node, parent) => {
101719
+ const siblings = (parent?.children ?? []);
101720
+ const previous = siblings[siblings.indexOf(node) - 1];
101721
+ return !!previous && isLineBreak(previous);
101722
+ };
101723
+ /**
101724
+ * Re-escapes a leading block marker (`-`, `+`, `#`, `>`) at the start of a table cell, or
101725
+ * after a `<br />` within one.
101726
+ *
101727
+ * A cell is serialized mid-line, so the `atBreak` patterns that escape these characters
101728
+ * never fire and an author's `| \- one |` round trips to `| - one |` (RM-17203). Cells are
101729
+ * serialized as `containerPhrasing(cell, { before: '|' })`, so `|` marks the cell start.
101730
+ */
101731
+ const escapeCellStartBlockMarker = (serialized, node, parent, state, info) => {
101732
+ if (serialized.startsWith('\\') || !CELL_START_BLOCK_MARKER.test(serialized))
101733
+ return serialized;
101734
+ if (!state.stack.includes('tableCell') || !(info.before === '|' || followsLineBreak(node, parent)))
101735
+ return serialized;
101736
+ return `\\${serialized}`;
101737
+ };
101738
+
101630
101739
  ;// ./processor/compile/text.ts
101631
101740
 
101741
+
101632
101742
  // A `_` flanked by word characters can never open or close emphasis under
101633
101743
  // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
101634
101744
  // intraword underscores is unnecessary and only produces noisy `\_` diffs.
@@ -101636,9 +101746,10 @@ const plain_plain = (node) => node.value;
101636
101746
  const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
101637
101747
  const compile_text_text = (node, parent, state, info) => {
101638
101748
  const serialized = handle.text(node, parent, state, info);
101639
- return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
101749
+ return escapeCellStartBlockMarker(serialized, node, parent, state, info);
101640
101750
  };
101641
- /* harmony default export */ const compile_text = (compile_text_text);
101751
+ const mdxishText = (node, parent, state, info) => compile_text_text(node, parent, state, info).replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
101752
+ /* harmony default export */ const compile_text = (mdxishText);
101642
101753
 
101643
101754
  ;// ./processor/compile/index.ts
101644
101755
 
@@ -101670,6 +101781,7 @@ function compilers(mdxish = false) {
101670
101781
  html: compile_compatibility,
101671
101782
  i: compile_compatibility,
101672
101783
  plain: compile_plain,
101784
+ text: compile_text_text,
101673
101785
  yaml: compile_compatibility,
101674
101786
  // needed only for mdxish
101675
101787
  ...(mdxish && { list: compile_list }),
@@ -103738,13 +103850,14 @@ function restoreCodeBlocks(content, protectedCode) {
103738
103850
  *
103739
103851
  * The attribute portion skips over quoted strings (`"..."` and `'...'`) so that
103740
103852
  * a `/>` inside an attribute value (e.g. `title="use /> here"`) does not cause
103741
- * a premature match.
103853
+ * a premature match, and stops at an unquoted `<` so a stray `<x` in prose
103854
+ * doesn't scan to the end of the document (quadratic).
103742
103855
  *
103743
103856
  * Only matches lowercase tag names to avoid interfering with PascalCase
103744
103857
  * JSX custom components (e.g. `<MyComponent />`), which are handled
103745
103858
  * separately by components/mdx-blocks.
103746
103859
  */
103747
- const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)?)?\s*\/>/g;
103860
+ const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^<>"']*(?:"[^"]*"|'[^']*'))*[^<>"']*)?)?\s*\/>/g;
103748
103861
  /**
103749
103862
  * String-level preprocessor that converts self-closing non-void HTML tags
103750
103863
  * into explicitly closed tags.
@@ -104409,8 +104522,9 @@ function terminateHtmlFlowBlocks(content) {
104409
104522
 
104410
104523
 
104411
104524
 
104412
- // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
104413
- const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
104525
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. One name
104526
+ // char suffices for existence — `[\w-]+` backtracked quadratically over huge attributes.
104527
+ const NESTED_ATTR_EXPRESSION_RE = /[\w-]\s*=\s*\{/;
104414
104528
  // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
104415
104529
  // of a legacy `<<VARIABLE>>`.
104416
104530
  const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
@@ -104709,150 +104823,6 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
104709
104823
  };
104710
104824
  /* harmony default export */ const mdx_blocks = (mdxishMdxComponentBlocks);
104711
104825
 
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
104826
  ;// ./processor/transform/mdxish/resolve-esm-imports.ts
104857
104827
 
104858
104828
  // We provide React as a default module so that components can use hooks
@@ -105037,6 +105007,54 @@ const containsJsxNode = (value) => {
105037
105007
  return true;
105038
105008
  return Object.values(value).some(containsJsxNode);
105039
105009
  };
105010
+ /** Read the component name off a JSX element name node (`Foo`, `Foo.Bar`, `foo:Bar`). */
105011
+ const jsxElementName = (name) => {
105012
+ if (name === null || typeof name !== 'object')
105013
+ return undefined;
105014
+ const node = name;
105015
+ if (node.type === 'JSXIdentifier')
105016
+ return typeof node.name === 'string' ? node.name : undefined;
105017
+ // `<Foo.Bar/>` and `<foo:Bar/>` resolve through their leftmost part.
105018
+ if (node.type === 'JSXMemberExpression')
105019
+ return jsxElementName(node.object);
105020
+ if (node.type === 'JSXNamespacedName')
105021
+ return jsxElementName(node.namespace);
105022
+ return undefined;
105023
+ };
105024
+ /**
105025
+ * Collect the capitalized names an expression uses as JSX tags. Parsed rather than pattern
105026
+ * matched: `{count < Max ? <Foo/> : <Bar/>}` puts a capitalized name straight after a `<` without
105027
+ * it being a tag, and only the parser can tell the two apart. Unparseable input yields nothing —
105028
+ * evaluation is about to throw on it anyway.
105029
+ */
105030
+ const jsxComponentNames = (expression) => {
105031
+ let program;
105032
+ try {
105033
+ program = parseExpression(expression);
105034
+ }
105035
+ catch {
105036
+ return [];
105037
+ }
105038
+ const names = new Set();
105039
+ const walk = (value) => {
105040
+ if (Array.isArray(value)) {
105041
+ value.forEach(walk);
105042
+ return;
105043
+ }
105044
+ if (value === null || typeof value !== 'object')
105045
+ return;
105046
+ const node = value;
105047
+ if (node.type === 'JSXOpeningElement') {
105048
+ const name = jsxElementName(node.name);
105049
+ // Lowercase tags compile to a string type, never a variable reference.
105050
+ if (name && /^[A-Z]/.test(name))
105051
+ names.add(name);
105052
+ }
105053
+ Object.values(node).forEach(walk);
105054
+ };
105055
+ walk(program);
105056
+ return Array.from(names);
105057
+ };
105040
105058
  /** Convert a program's JSX into `React.createElement` calls and evaluate it. `scope` must provide `React`. */
105041
105059
  const evalJsxProgram = (program, scope) => {
105042
105060
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
@@ -105242,6 +105260,28 @@ function reactElementToHast(node) {
105242
105260
 
105243
105261
 
105244
105262
 
105263
+
105264
+
105265
+
105266
+
105267
+ /**
105268
+ * Bind the components a given expression actually uses as tags. Scoped per expression on purpose:
105269
+ * binding the whole hash would shadow same-named globals (a `math` component would break
105270
+ * `{Math.max(1, 2)}`) and pad `evaluate`'s `new Function` parameter list. Resolution defers to
105271
+ * `getComponentName` so an expression and a plain tag always reach the same component.
105272
+ */
105273
+ const componentScope = (expression, components) => {
105274
+ const scope = {};
105275
+ jsxComponentNames(expression).forEach(name => {
105276
+ // `getComponentName` normalizes the tag, never the key, so it can't match `<MyBlock/>` to a
105277
+ // `my_block` entry; compare the key's PascalCase form for that direction.
105278
+ const tagName = getComponentName(name, components) ?? Object.keys(components).find(k => toPascalCase(k) === name);
105279
+ if (!tagName)
105280
+ return;
105281
+ scope[name] = (props) => external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(tagName, props);
105282
+ });
105283
+ return scope;
105284
+ };
105245
105285
  /**
105246
105286
  * We divide the result of an expression into two categories:
105247
105287
  * 1. Renderable values: HTML, JSX, e.g. .map() returning JSX
@@ -105252,6 +105292,17 @@ const isRenderable = (value) => {
105252
105292
  return true;
105253
105293
  return Array.isArray(value) && value.some(isRenderable);
105254
105294
  };
105295
+ /**
105296
+ * Whether an expression evaluated to block-level content. Components count as block-level
105297
+ * unless they're on the inline list, matching the assumption the rest of the component
105298
+ * pipeline makes.
105299
+ */
105300
+ const isBlockResult = (children) => children.some(child => {
105301
+ if (child.type !== 'element' && child.type !== 'mdx-jsx')
105302
+ return false;
105303
+ const { tagName } = child;
105304
+ return !STANDARD_HTML_TAGS.has(tagName.toLowerCase()) && !INLINE_COMPONENT_TAGS.has(tagName);
105305
+ });
105255
105306
  /** Turn a non-renderable evaluation result into a text node. */
105256
105307
  const createTextNode = (result, position) => {
105257
105308
  if (result === null || result === undefined)
@@ -105263,13 +105314,22 @@ const createTextNode = (result, position) => {
105263
105314
  /**
105264
105315
  * AST transformer to evaluate MDX expressions.
105265
105316
  * 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()) };
105317
+ * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that reference
105318
+ * identifiers can resolve if those identifiers are a custom component, the `user` variables
105319
+ * object, or were introduced by an earlier `export const/function` (collected onto
105320
+ * `file.data.mdxishScope`). Anything else falls through to the error branch and is kept as
105321
+ * literal `{...}` text.
105322
+ */
105323
+ const evaluateExpressions = ({ components, variables } = {}) => (tree, file) => {
105324
+ const baseScope = {
105325
+ // `User` matches the fallback the MDX path binds in `run.tsx`. Only when variables were
105326
+ // supplied: the proxy never throws, so an unconditional bind would resolve `user.*` on
105327
+ // surfaces that render without them instead of leaving literal text.
105328
+ ...(variables ? { user: user(variables) } : {}),
105329
+ // In-document exports win, matching `renderMdxish`.
105330
+ ...file.data.mdxishScope,
105331
+ React: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()),
105332
+ };
105273
105333
  visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => {
105274
105334
  if (!parent || index === null || index === undefined)
105275
105335
  return;
@@ -105279,6 +105339,7 @@ const evaluateExpressions = () => (tree, file) => {
105279
105339
  if (!expression)
105280
105340
  return;
105281
105341
  try {
105342
+ const scope = { ...componentScope(expression, components ?? {}), ...baseScope };
105282
105343
  const result = evalExpression(expression, scope);
105283
105344
  if (isRenderable(result)) {
105284
105345
  // Stash hast built straight from the React tree; `mdxExpressionHandler` emits it and it
@@ -105299,6 +105360,22 @@ const evaluateExpressions = () => (tree, file) => {
105299
105360
  parent.children.splice(index, 1, { type: 'text', value: `{${processed}}`, position });
105300
105361
  }
105301
105362
  });
105363
+ // A text expression is parsed inside a paragraph, but its result can be block content: a
105364
+ // `<Tabs>` renders a `<div>`, and a browser closes the `<p>` before it, so the DOM it builds
105365
+ // no longer matches what was rendered and hydration fails. Lift the expression out when
105366
+ // that's all the paragraph holds.
105367
+ visit(tree, 'paragraph', (node, index, parent) => {
105368
+ if (!parent || index === null || index === undefined)
105369
+ return;
105370
+ const meaningful = node.children.filter(child => !(child.type === 'text' && !child.value.trim()));
105371
+ const [only] = meaningful;
105372
+ if (meaningful.length !== 1 || only.type !== 'mdxTextExpression')
105373
+ return;
105374
+ const hChildren = only.data?.hChildren;
105375
+ if (!hChildren || !isBlockResult(hChildren))
105376
+ return;
105377
+ parent.children.splice(index, 1, only);
105378
+ });
105302
105379
  return tree;
105303
105380
  };
105304
105381
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
@@ -106993,12 +107070,20 @@ function removeJSXComments(content) {
106993
107070
  const OPAQUE_CONTAINERS = new Set(['pre', 'textarea', 'htmlblock']);
106994
107071
  /** The missing-slash typo shape: an attribute-less, non-self-closing opener. */
106995
107072
  const BARE_OPENER_SOURCE_RE = /^<[A-Za-z]+\s*>$/;
106996
- /** The opener shares its line with nothing but whitespace. */
106997
- const isAloneOnLine = (content, start, end) => {
106998
- const lineStart = content.lastIndexOf('\n', start - 1) + 1;
107073
+ /** The opener is immediately preceded by a complete tag, so the prefix is markup and not prose. */
107074
+ const PREFIX_ENDS_WITH_TAG_RE = /<[^<>]*>\s*$/;
107075
+ /**
107076
+ * The opener finishes its line, preceded by nothing but whitespace or by markup.
107077
+ * Requiring markup (not merely "something") keeps a markdown block prefix such as
107078
+ * a `>` quote marker from qualifying, while allowing the closer to trail a cell or
107079
+ * `</thead>` on a single-line table.
107080
+ */
107081
+ const endsLineAfterMarkup = (content, start, end) => {
106999
107082
  const lineEnd = content.indexOf('\n', end);
107000
- return (content.slice(lineStart, start).trim() === '' &&
107001
- content.slice(end, lineEnd === -1 ? content.length : lineEnd).trim() === '');
107083
+ if (content.slice(end, lineEnd === -1 ? content.length : lineEnd).trim() !== '')
107084
+ return false;
107085
+ const prefix = content.slice(content.lastIndexOf('\n', start - 1) + 1, start);
107086
+ return prefix.trim() === '' || PREFIX_ENDS_WITH_TAG_RE.test(prefix);
107002
107087
  };
107003
107088
  /**
107004
107089
  * Rewrites a bare `<table>` that is almost certainly a missing-slash closer
@@ -107006,7 +107091,7 @@ const isAloneOnLine = (content, start, end) => {
107006
107091
  * Customer docs (CX-3850) close tables with a second bare opener; without this,
107007
107092
  * the never-terminated HTML flow block swallows the markdown that follows.
107008
107093
  *
107009
- * A candidate is a bare opener alone on its line while a table is already open.
107094
+ * A candidate is a bare opener that finishes its line while a table is already open.
107010
107095
  * It is judged a typo only when its element ends implicitly (no explicit
107011
107096
  * `</table>`) without ever acquiring table-structure children — so genuine
107012
107097
  * nested tables, whatever precedes their rows, are left alone.
@@ -107035,7 +107120,7 @@ function repairMistakenTableClosers(content) {
107035
107120
  start,
107036
107121
  end,
107037
107122
  hasStructureChild: false,
107038
- isCandidate: stack.length > 0 && BARE_OPENER_SOURCE_RE.test(source) && isAloneOnLine(content, start, end),
107123
+ isCandidate: stack.length > 0 && BARE_OPENER_SOURCE_RE.test(source) && endsLineAfterMarkup(content, start, end),
107039
107124
  });
107040
107125
  }
107041
107126
  else if (opaqueDepth === 0 && tableTags.has(lower) && stack.length > 0) {
@@ -107106,45 +107191,6 @@ const resolveDeferredAttributeExpressionProps = () => (tree, file) => {
107106
107191
  };
107107
107192
  /* harmony default export */ const resolve_deferred_attribute_expression_props = (resolveDeferredAttributeExpressionProps);
107108
107193
 
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
107194
  ;// ./processor/transform/mdxish/retain-boolean-attributes.ts
107149
107195
 
107150
107196
  // Private Use Area character (U+E000) which is extremely unlikely to appear in real content.
@@ -107686,9 +107732,6 @@ function loadComponents() {
107686
107732
 
107687
107733
 
107688
107734
 
107689
-
107690
-
107691
-
107692
107735
 
107693
107736
 
107694
107737
 
@@ -107710,10 +107753,8 @@ const defaultTransformers = [
107710
107753
  * 5. Terminate HTML flow blocks so subsequent content isn't swallowed
107711
107754
  * 6. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
107712
107755
  * 7. Normalize compact ATX headings (e.g., `#Heading` → `# Heading`)
107713
- * 8. Replace snake_case component names with parser-safe placeholders
107714
107756
  */
107715
- function preprocessContent(content, opts) {
107716
- const { knownComponents } = opts;
107757
+ function preprocessContent(content) {
107717
107758
  // Runs first so `jsxTable` sees a literal `</table>` (and the HTML-line
107718
107759
  // classification in `terminateHtmlFlowBlocks` is accurate)
107719
107760
  let result = normalizeClosingTagWhitespace(content);
@@ -107726,7 +107767,7 @@ function preprocessContent(content, opts) {
107726
107767
  result = terminateHtmlFlowBlocks(result);
107727
107768
  result = closeSelfClosingHtmlTags(result);
107728
107769
  result = normalizeCompactHeadings(result);
107729
- return processSnakeCaseComponent(result, { knownComponents });
107770
+ return result;
107730
107771
  }
107731
107772
  function mdxishAstProcessor(mdContent, opts = {}) {
107732
107773
  const { components: userComponents = {}, hardBreaks: enableHardBreaks = true, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
@@ -107734,9 +107775,7 @@ function mdxishAstProcessor(mdContent, opts = {}) {
107734
107775
  ...loadComponents(),
107735
107776
  ...userComponents,
107736
107777
  };
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 });
107778
+ const parserReadyContent = preprocessContent(mdContent);
107740
107779
  // Create string map for tailwind transformer
107741
107780
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
107742
107781
  acc[key] = String(value);
@@ -107749,10 +107788,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
107749
107788
  .use(remarkParse)
107750
107789
  .use(remarkFrontmatter)
107751
107790
  .use(normalize_malformed_md_syntax)
107752
- .use(self_closing_blocks)
107753
107791
  .use(mdx_blocks, { safeMode })
107754
107792
  .use(inline_html, { safeMode })
107755
- .use(restore_snake_case_component_name, { mapping: snakeCaseMapping })
107756
107793
  .use(mdxish_tables)
107757
107794
  .use(mdxish_html_blocks) // Convert every <HTMLBlock> shape → html-block
107758
107795
  // The next few transformers must appear after mdxishMdxComponentBlocks
@@ -107828,7 +107865,7 @@ function mdxish(mdContent, opts = {}) {
107828
107865
  processor
107829
107866
  .use(safeMode ? undefined : evaluate_exports) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
107830
107867
  .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}`)
107868
+ .use(safeMode ? undefined : evaluate_expressions, { components, variables }) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
107832
107869
  .use(safeMode ? undefined : evaluate_style_block_expressions) // Evaluate `<style>{`...`}</style>` template literals into plain CSS
107833
107870
  .use(variables_code, { variables }) // Resolve <<...>> and {user.*} inside code and inline code nodes
107834
107871
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
@@ -108815,7 +108852,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"*":["about","acceptCharset","accessK
108815
108852
  /******/ // startup
108816
108853
  /******/ // Load entry module and return exports
108817
108854
  /******/ // This entry module used 'module' so it can't be inlined
108818
- /******/ let __webpack_exports__ = __webpack_require__(5168);
108855
+ /******/ let __webpack_exports__ = __webpack_require__(912);
108819
108856
  /******/
108820
108857
  /******/ return __webpack_exports__;
108821
108858
  /******/ })()