@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.
@@ -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
 
@@ -96519,6 +96582,10 @@ function syntax_createTokenize(mode) {
96519
96582
  effects.consume(code);
96520
96583
  return inBraceExpr;
96521
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);
96522
96589
  effects.consume(code);
96523
96590
  return afterOpenTagName;
96524
96591
  }
@@ -101691,6 +101758,17 @@ const tablesToJsx = () => tree => {
101691
101758
  ;// ./processor/transform/tailwind.tsx
101692
101759
 
101693
101760
 
101761
+
101762
+
101763
+ /** Whitespace-only text is layout between blocks, not inline flow. */
101764
+ const isInlineContent = (node) => phrasing(node) && !(node.type === 'text' && node.value.trim() === '');
101765
+ /**
101766
+ * Whether the child at `index` sits in inline flow: its parent only allows
101767
+ * phrasing content (e.g. a paragraph), or it is flanked by phrasing siblings
101768
+ * (e.g. `<div>text <Image /> text</div>`, whose children skip the paragraph).
101769
+ */
101770
+ const isInlineInContext = (index, parent) => INLINE_ONLY_PARENT_TYPES.has(parent.type) ||
101771
+ parent.children.some((sibling, siblingIndex) => siblingIndex !== index && isInlineContent(sibling));
101694
101772
  const injectTailwindRoot = ({ components = {} }) => (node, index, parent) => {
101695
101773
  if (!('name' in node))
101696
101774
  return;
@@ -101699,7 +101777,7 @@ const injectTailwindRoot = ({ components = {} }) => (node, index, parent) => {
101699
101777
  if (!('children' in parent))
101700
101778
  return;
101701
101779
  const attrs = {
101702
- flow: node.type === 'mdxJsxFlowElement',
101780
+ flow: node.type === 'mdxJsxFlowElement' && !isInlineInContext(index, parent),
101703
101781
  };
101704
101782
  const wrapper = {
101705
101783
  type: node.type,
@@ -121553,7 +121631,7 @@ const listMarkerRegex = /^(?:[*+-]|\d+[.)])(?:([\r\n]| {1,3})|$)/;
121553
121631
  * with their checkbox intact (for example, `- [ ]`) instead of dropping it
121554
121632
  * We can add more adjustments if needed
121555
121633
  */
121556
- const compile_list_item_listItem = (node, parent, state, info) => {
121634
+ const compile_list_item_listItem = ((node, parent, state, info) => {
121557
121635
  const head = node.children[0];
121558
121636
  const isCheckbox = typeof node.checked === 'boolean' && head && head.type === 'paragraph';
121559
121637
  if (!isCheckbox) {
@@ -121577,15 +121655,47 @@ const compile_list_item_listItem = (node, parent, state, info) => {
121577
121655
  return `${marker}${actualSeparator}${checkbox}`;
121578
121656
  });
121579
121657
  return value;
121580
- };
121658
+ });
121581
121659
  /* harmony default export */ const list_item = (compile_list_item_listItem);
121582
121660
 
121583
121661
  ;// ./processor/compile/plain.ts
121584
121662
  const plain_plain = (node) => node.value;
121585
121663
  /* harmony default export */ const compile_plain = (plain_plain);
121586
121664
 
121665
+ ;// ./processor/compile/table-cell-block-markers.ts
121666
+ const CELL_START_BLOCK_MARKER = /^(?:[-+](?=\s|$)|#{1,6}(?=\s|$)|>)/;
121667
+ const BR_HTML = /^\s*<br\s*\/?>\s*$/i;
121668
+ const isLineBreak = (node) => {
121669
+ if (node.type === 'break')
121670
+ return true;
121671
+ if (node.type === 'html')
121672
+ return BR_HTML.test(node.value);
121673
+ return node.type === 'mdxJsxTextElement' && 'name' in node && node.name?.toLowerCase() === 'br';
121674
+ };
121675
+ const followsLineBreak = (node, parent) => {
121676
+ const siblings = (parent?.children ?? []);
121677
+ const previous = siblings[siblings.indexOf(node) - 1];
121678
+ return !!previous && isLineBreak(previous);
121679
+ };
121680
+ /**
121681
+ * Re-escapes a leading block marker (`-`, `+`, `#`, `>`) at the start of a table cell, or
121682
+ * after a `<br />` within one.
121683
+ *
121684
+ * A cell is serialized mid-line, so the `atBreak` patterns that escape these characters
121685
+ * never fire and an author's `| \- one |` round trips to `| - one |` (RM-17203). Cells are
121686
+ * serialized as `containerPhrasing(cell, { before: '|' })`, so `|` marks the cell start.
121687
+ */
121688
+ const escapeCellStartBlockMarker = (serialized, node, parent, state, info) => {
121689
+ if (serialized.startsWith('\\') || !CELL_START_BLOCK_MARKER.test(serialized))
121690
+ return serialized;
121691
+ if (!state.stack.includes('tableCell') || !(info.before === '|' || followsLineBreak(node, parent)))
121692
+ return serialized;
121693
+ return `\\${serialized}`;
121694
+ };
121695
+
121587
121696
  ;// ./processor/compile/text.ts
121588
121697
 
121698
+
121589
121699
  // A `_` flanked by word characters can never open or close emphasis under
121590
121700
  // CommonMark's flanking rules, so the escape mdast-util-to-markdown adds to
121591
121701
  // intraword underscores is unnecessary and only produces noisy `\_` diffs.
@@ -121593,9 +121703,10 @@ const plain_plain = (node) => node.value;
121593
121703
  const INTRAWORD_UNDERSCORE_ESCAPE = /(?<=[\p{L}\p{N}_])\\_(?=[\p{L}\p{N}_]|\\_)/gu;
121594
121704
  const compile_text_text = (node, parent, state, info) => {
121595
121705
  const serialized = handle.text(node, parent, state, info);
121596
- return serialized.replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
121706
+ return escapeCellStartBlockMarker(serialized, node, parent, state, info);
121597
121707
  };
121598
- /* harmony default export */ const compile_text = (compile_text_text);
121708
+ const mdxishText = (node, parent, state, info) => compile_text_text(node, parent, state, info).replace(INTRAWORD_UNDERSCORE_ESCAPE, '_');
121709
+ /* harmony default export */ const compile_text = (mdxishText);
121599
121710
 
121600
121711
  ;// ./processor/compile/index.ts
121601
121712
 
@@ -121627,6 +121738,7 @@ function compilers(mdxish = false) {
121627
121738
  html: compile_compatibility,
121628
121739
  i: compile_compatibility,
121629
121740
  plain: compile_plain,
121741
+ text: compile_text_text,
121630
121742
  yaml: compile_compatibility,
121631
121743
  // needed only for mdxish
121632
121744
  ...(mdxish && { list: compile_list }),
@@ -122284,13 +122396,14 @@ function restoreCodeBlocks(content, protectedCode) {
122284
122396
  *
122285
122397
  * The attribute portion skips over quoted strings (`"..."` and `'...'`) so that
122286
122398
  * a `/>` inside an attribute value (e.g. `title="use /> here"`) does not cause
122287
- * a premature match.
122399
+ * a premature match, and stops at an unquoted `<` so a stray `<x` in prose
122400
+ * doesn't scan to the end of the document (quadratic).
122288
122401
  *
122289
122402
  * Only matches lowercase tag names to avoid interfering with PascalCase
122290
122403
  * JSX custom components (e.g. `<MyComponent />`), which are handled
122291
122404
  * separately by components/mdx-blocks.
122292
122405
  */
122293
- const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^>"']*(?:"[^"]*"|'[^']*'))*[^>"']*)?)?\s*\/>/g;
122406
+ const SELF_CLOSING_TAG_RE = /<([a-z][a-z0-9-]*)((?:\s+(?:[^<>"']*(?:"[^"]*"|'[^']*'))*[^<>"']*)?)?\s*\/>/g;
122294
122407
  /**
122295
122408
  * String-level preprocessor that converts self-closing non-void HTML tags
122296
122409
  * into explicitly closed tags.
@@ -122955,8 +123068,9 @@ function terminateHtmlFlowBlocks(content) {
122955
123068
 
122956
123069
 
122957
123070
 
122958
- // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. */
122959
- const NESTED_ATTR_EXPRESSION_RE = /[\w-]+\s*=\s*\{/;
123071
+ // Matches a JSX attribute expression (e.g. `key={i}`) anywhere in a string. One name
123072
+ // char suffices for existence — `[\w-]+` backtracked quadratically over huge attributes.
123073
+ const NESTED_ATTR_EXPRESSION_RE = /[\w-]\s*=\s*\{/;
122960
123074
  // Name shape mirrors `componentTagPattern`; the lookbehind skips the inner tag
122961
123075
  // of a legacy `<<VARIABLE>>`.
122962
123076
  const NESTED_COMPONENT_TAG_RE = /(?<!<)<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
@@ -123255,150 +123369,6 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
123255
123369
  };
123256
123370
  /* harmony default export */ const mdx_blocks = (mdxishMdxComponentBlocks);
123257
123371
 
123258
- ;// ./processor/transform/mdxish/components/self-closing-blocks.ts
123259
-
123260
-
123261
- /**
123262
- * Tags to process as self-closing blocks.
123263
- * These components use simple string attributes (no JSX expressions like `data={[...]}`).
123264
- * Components with JSX expression attributes should NOT be added here as parseAttributes
123265
- * cannot handle them correctly.
123266
- */
123267
- const SELF_CLOSING_BLOCK_TAGS = new Set(['Embed', 'Recipe']);
123268
- // Regex to match self-closing PascalCase tags (handles multi-line)
123269
- const selfClosingTagPattern = /^<([A-Z][A-Za-z0-9_]*)([\s\S]*?)\/>$/;
123270
- /**
123271
- * Try to convert a paragraph node containing a self-closing JSX component into an mdxJsxFlowElement.
123272
- * Returns the new node if conversion succeeded, or null if the node doesn't match.
123273
- */
123274
- const tryConvertToMdxNode = (node) => {
123275
- if (node.children.length !== 1)
123276
- return null;
123277
- const child = node.children[0];
123278
- if (child.type !== 'html')
123279
- return null;
123280
- const value = child.value?.trim();
123281
- if (!value)
123282
- return null;
123283
- const match = value.match(selfClosingTagPattern);
123284
- if (!match)
123285
- return null;
123286
- const [, tag, attrString] = match;
123287
- if (!SELF_CLOSING_BLOCK_TAGS.has(tag))
123288
- return null;
123289
- return {
123290
- type: 'mdxJsxFlowElement',
123291
- name: tag,
123292
- attributes: parseAttributes(attrString),
123293
- children: [],
123294
- position: node.position,
123295
- };
123296
- };
123297
- /**
123298
- * Transform paragraph-wrapped self-closing JSX components into mdxJsxFlowElement nodes.
123299
- *
123300
- * CommonMark wraps multi-line JSX in paragraphs when the opening tag isn't complete
123301
- * on one line. This plugin detects these structures and unwraps them for components
123302
- * in the SELF_CLOSING_BLOCK_TAGS allowlist.
123303
- *
123304
- * Input structure:
123305
- * ```
123306
- * paragraph > html: "<Embed\n typeOfEmbed=\"youtube\"\n/>"
123307
- * ```
123308
- *
123309
- * Output structure:
123310
- * ```
123311
- * mdxJsxFlowElement: { name: "Embed", attributes: [...], children: [] }
123312
- * ```
123313
- */
123314
- const mdxishSelfClosingBlocks = () => tree => {
123315
- visit(tree, 'paragraph', (node, index, parent) => {
123316
- if (index === undefined || !parent)
123317
- return;
123318
- const mdxNode = tryConvertToMdxNode(node);
123319
- if (mdxNode) {
123320
- parent.children.splice(index, 1, mdxNode);
123321
- }
123322
- });
123323
- };
123324
- /* harmony default export */ const self_closing_blocks = (mdxishSelfClosingBlocks);
123325
-
123326
- ;// ./processor/transform/mdxish/components/snake-case-components.ts
123327
-
123328
-
123329
- /**
123330
- * Replaces snake_case component names with valid HTML placeholders.
123331
- * Required because remark-parse rejects tags with underscores.
123332
- * Example: `<Snake_case />` → `<MDXishSnakeCase0 />`
123333
- *
123334
- * Code blocks and inline code are protected and will not be transformed.
123335
- *
123336
- * @param content - The markdown content to process
123337
- * @param options - Options including knownComponents to filter by
123338
- */
123339
- function processSnakeCaseComponent(content, options = {}) {
123340
- const { knownComponents } = options;
123341
- // Early exit if no potential snake_case components
123342
- if (!/[A-Z][A-Za-z0-9]*_[A-Za-z0-9_]*/.test(content)) {
123343
- return { content, mapping: {} };
123344
- }
123345
- // Step 1: Extract code blocks to protect them from transformation
123346
- const { protectedCode, protectedContent } = protectCodeBlocks(content);
123347
- // Find the highest existing placeholder number to avoid collisions
123348
- // e.g., if content has <MDXishSnakeCase0 />, start counter from 1
123349
- const placeholderPattern = /MDXishSnakeCase(\d+)/g;
123350
- let startCounter = 0;
123351
- let placeholderMatch;
123352
- while ((placeholderMatch = placeholderPattern.exec(content)) !== null) {
123353
- const num = parseInt(placeholderMatch[1], 10);
123354
- if (num >= startCounter) {
123355
- startCounter = num + 1;
123356
- }
123357
- }
123358
- const mapping = {};
123359
- const reverseMap = new Map();
123360
- let counter = startCounter;
123361
- // Step 2: Transform snake_case components in non-code content
123362
- const processedContent = protectedContent.replace(componentTagPattern, (match, tagName, attrs, selfClosing) => {
123363
- if (!tagName.includes('_')) {
123364
- return match;
123365
- }
123366
- const isClosing = tagName.startsWith('/');
123367
- const cleanTagName = isClosing ? tagName.slice(1) : tagName;
123368
- // Only transform if it's a known component (or if no filter is provided)
123369
- if (knownComponents && !knownComponents.has(cleanTagName)) {
123370
- return match;
123371
- }
123372
- let placeholder = reverseMap.get(cleanTagName);
123373
- if (!placeholder) {
123374
- // eslint-disable-next-line no-plusplus
123375
- placeholder = `MDXishSnakeCase${counter++}`;
123376
- mapping[placeholder] = cleanTagName;
123377
- reverseMap.set(cleanTagName, placeholder);
123378
- }
123379
- const processedTagName = isClosing ? `/${placeholder}` : placeholder;
123380
- return `<${processedTagName}${attrs}${selfClosing}>`;
123381
- });
123382
- // Step 3: Restore code blocks (untouched)
123383
- const finalContent = restoreCodeBlocks(processedContent, protectedCode);
123384
- return {
123385
- content: finalContent,
123386
- mapping,
123387
- };
123388
- }
123389
- /**
123390
- * Restores placeholder name to original snake_case name.
123391
- * Uses case-insensitive matching since HTML parsers normalize to lowercase.
123392
- */
123393
- function restoreSnakeCase(placeholderName, mapping) {
123394
- if (mapping[placeholderName]) {
123395
- return mapping[placeholderName];
123396
- }
123397
- const lowerName = placeholderName.toLowerCase();
123398
- const matchingKey = Object.keys(mapping).find(key => key.toLowerCase() === lowerName);
123399
- return matchingKey ? mapping[matchingKey] : placeholderName;
123400
- }
123401
-
123402
123372
  ;// ./processor/transform/mdxish/resolve-esm-imports.ts
123403
123373
 
123404
123374
  // We provide React as a default module so that components can use hooks
@@ -123583,6 +123553,54 @@ const containsJsxNode = (value) => {
123583
123553
  return true;
123584
123554
  return Object.values(value).some(containsJsxNode);
123585
123555
  };
123556
+ /** Read the component name off a JSX element name node (`Foo`, `Foo.Bar`, `foo:Bar`). */
123557
+ const jsxElementName = (name) => {
123558
+ if (name === null || typeof name !== 'object')
123559
+ return undefined;
123560
+ const node = name;
123561
+ if (node.type === 'JSXIdentifier')
123562
+ return typeof node.name === 'string' ? node.name : undefined;
123563
+ // `<Foo.Bar/>` and `<foo:Bar/>` resolve through their leftmost part.
123564
+ if (node.type === 'JSXMemberExpression')
123565
+ return jsxElementName(node.object);
123566
+ if (node.type === 'JSXNamespacedName')
123567
+ return jsxElementName(node.namespace);
123568
+ return undefined;
123569
+ };
123570
+ /**
123571
+ * Collect the capitalized names an expression uses as JSX tags. Parsed rather than pattern
123572
+ * matched: `{count < Max ? <Foo/> : <Bar/>}` puts a capitalized name straight after a `<` without
123573
+ * it being a tag, and only the parser can tell the two apart. Unparseable input yields nothing —
123574
+ * evaluation is about to throw on it anyway.
123575
+ */
123576
+ const jsxComponentNames = (expression) => {
123577
+ let program;
123578
+ try {
123579
+ program = parseExpression(expression);
123580
+ }
123581
+ catch {
123582
+ return [];
123583
+ }
123584
+ const names = new Set();
123585
+ const walk = (value) => {
123586
+ if (Array.isArray(value)) {
123587
+ value.forEach(walk);
123588
+ return;
123589
+ }
123590
+ if (value === null || typeof value !== 'object')
123591
+ return;
123592
+ const node = value;
123593
+ if (node.type === 'JSXOpeningElement') {
123594
+ const name = jsxElementName(node.name);
123595
+ // Lowercase tags compile to a string type, never a variable reference.
123596
+ if (name && /^[A-Z]/.test(name))
123597
+ names.add(name);
123598
+ }
123599
+ Object.values(node).forEach(walk);
123600
+ };
123601
+ walk(program);
123602
+ return Array.from(names);
123603
+ };
123586
123604
  /** Convert a program's JSX into `React.createElement` calls and evaluate it. `scope` must provide `React`. */
123587
123605
  const evalJsxProgram = (program, scope) => {
123588
123606
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
@@ -123769,6 +123787,28 @@ function reactElementToHast(node) {
123769
123787
 
123770
123788
 
123771
123789
 
123790
+
123791
+
123792
+
123793
+
123794
+ /**
123795
+ * Bind the components a given expression actually uses as tags. Scoped per expression on purpose:
123796
+ * binding the whole hash would shadow same-named globals (a `math` component would break
123797
+ * `{Math.max(1, 2)}`) and pad `evaluate`'s `new Function` parameter list. Resolution defers to
123798
+ * `getComponentName` so an expression and a plain tag always reach the same component.
123799
+ */
123800
+ const componentScope = (expression, components) => {
123801
+ const scope = {};
123802
+ jsxComponentNames(expression).forEach(name => {
123803
+ // `getComponentName` normalizes the tag, never the key, so it can't match `<MyBlock/>` to a
123804
+ // `my_block` entry; compare the key's PascalCase form for that direction.
123805
+ const tagName = getComponentName(name, components) ?? Object.keys(components).find(k => toPascalCase(k) === name);
123806
+ if (!tagName)
123807
+ return;
123808
+ scope[name] = (props) => external_react_default().createElement(tagName, props);
123809
+ });
123810
+ return scope;
123811
+ };
123772
123812
  /**
123773
123813
  * We divide the result of an expression into two categories:
123774
123814
  * 1. Renderable values: HTML, JSX, e.g. .map() returning JSX
@@ -123779,6 +123819,17 @@ const isRenderable = (value) => {
123779
123819
  return true;
123780
123820
  return Array.isArray(value) && value.some(isRenderable);
123781
123821
  };
123822
+ /**
123823
+ * Whether an expression evaluated to block-level content. Components count as block-level
123824
+ * unless they're on the inline list, matching the assumption the rest of the component
123825
+ * pipeline makes.
123826
+ */
123827
+ const isBlockResult = (children) => children.some(child => {
123828
+ if (child.type !== 'element' && child.type !== 'mdx-jsx')
123829
+ return false;
123830
+ const { tagName } = child;
123831
+ return !STANDARD_HTML_TAGS.has(tagName.toLowerCase()) && !INLINE_COMPONENT_TAGS.has(tagName);
123832
+ });
123782
123833
  /** Turn a non-renderable evaluation result into a text node. */
123783
123834
  const createTextNode = (result, position) => {
123784
123835
  if (result === null || result === undefined)
@@ -123790,13 +123841,22 @@ const createTextNode = (result, position) => {
123790
123841
  /**
123791
123842
  * AST transformer to evaluate MDX expressions.
123792
123843
  * Replaces mdxFlowExpression and mdxTextExpression nodes with their evaluated values.
123793
- * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that
123794
- * reference identifiers can resolve if those identifiers were introduced by an
123795
- * earlier `export const/function` (collected onto `file.data.mdxishScope`).
123796
- * Anything else falls through to the error branch and is kept as literal `{...}` text.
123797
- */
123798
- const evaluateExpressions = () => (tree, file) => {
123799
- const scope = { ...(file.data.mdxishScope), React: (external_react_default()) };
123844
+ * Self-contained expressions resolve directly (e.g. `{1+1}`); expressions that reference
123845
+ * identifiers can resolve if those identifiers are a custom component, the `user` variables
123846
+ * object, or were introduced by an earlier `export const/function` (collected onto
123847
+ * `file.data.mdxishScope`). Anything else falls through to the error branch and is kept as
123848
+ * literal `{...}` text.
123849
+ */
123850
+ const evaluateExpressions = ({ components, variables } = {}) => (tree, file) => {
123851
+ const baseScope = {
123852
+ // `User` matches the fallback the MDX path binds in `run.tsx`. Only when variables were
123853
+ // supplied: the proxy never throws, so an unconditional bind would resolve `user.*` on
123854
+ // surfaces that render without them instead of leaving literal text.
123855
+ ...(variables ? { user: user(variables) } : {}),
123856
+ // In-document exports win, matching `renderMdxish`.
123857
+ ...file.data.mdxishScope,
123858
+ React: (external_react_default()),
123859
+ };
123800
123860
  visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node, index, parent) => {
123801
123861
  if (!parent || index === null || index === undefined)
123802
123862
  return;
@@ -123806,6 +123866,7 @@ const evaluateExpressions = () => (tree, file) => {
123806
123866
  if (!expression)
123807
123867
  return;
123808
123868
  try {
123869
+ const scope = { ...componentScope(expression, components ?? {}), ...baseScope };
123809
123870
  const result = evalExpression(expression, scope);
123810
123871
  if (isRenderable(result)) {
123811
123872
  // Stash hast built straight from the React tree; `mdxExpressionHandler` emits it and it
@@ -123826,6 +123887,22 @@ const evaluateExpressions = () => (tree, file) => {
123826
123887
  parent.children.splice(index, 1, { type: 'text', value: `{${processed}}`, position });
123827
123888
  }
123828
123889
  });
123890
+ // A text expression is parsed inside a paragraph, but its result can be block content: a
123891
+ // `<Tabs>` renders a `<div>`, and a browser closes the `<p>` before it, so the DOM it builds
123892
+ // no longer matches what was rendered and hydration fails. Lift the expression out when
123893
+ // that's all the paragraph holds.
123894
+ visit(tree, 'paragraph', (node, index, parent) => {
123895
+ if (!parent || index === null || index === undefined)
123896
+ return;
123897
+ const meaningful = node.children.filter(child => !(child.type === 'text' && !child.value.trim()));
123898
+ const [only] = meaningful;
123899
+ if (meaningful.length !== 1 || only.type !== 'mdxTextExpression')
123900
+ return;
123901
+ const hChildren = only.data?.hChildren;
123902
+ if (!hChildren || !isBlockResult(hChildren))
123903
+ return;
123904
+ parent.children.splice(index, 1, only);
123905
+ });
123829
123906
  return tree;
123830
123907
  };
123831
123908
  /* harmony default export */ const evaluate_expressions = (evaluateExpressions);
@@ -126940,12 +127017,20 @@ function removeJSXComments(content) {
126940
127017
  const OPAQUE_CONTAINERS = new Set(['pre', 'textarea', 'htmlblock']);
126941
127018
  /** The missing-slash typo shape: an attribute-less, non-self-closing opener. */
126942
127019
  const BARE_OPENER_SOURCE_RE = /^<[A-Za-z]+\s*>$/;
126943
- /** The opener shares its line with nothing but whitespace. */
126944
- const isAloneOnLine = (content, start, end) => {
126945
- const lineStart = content.lastIndexOf('\n', start - 1) + 1;
127020
+ /** The opener is immediately preceded by a complete tag, so the prefix is markup and not prose. */
127021
+ const PREFIX_ENDS_WITH_TAG_RE = /<[^<>]*>\s*$/;
127022
+ /**
127023
+ * The opener finishes its line, preceded by nothing but whitespace or by markup.
127024
+ * Requiring markup (not merely "something") keeps a markdown block prefix such as
127025
+ * a `>` quote marker from qualifying, while allowing the closer to trail a cell or
127026
+ * `</thead>` on a single-line table.
127027
+ */
127028
+ const endsLineAfterMarkup = (content, start, end) => {
126946
127029
  const lineEnd = content.indexOf('\n', end);
126947
- return (content.slice(lineStart, start).trim() === '' &&
126948
- content.slice(end, lineEnd === -1 ? content.length : lineEnd).trim() === '');
127030
+ if (content.slice(end, lineEnd === -1 ? content.length : lineEnd).trim() !== '')
127031
+ return false;
127032
+ const prefix = content.slice(content.lastIndexOf('\n', start - 1) + 1, start);
127033
+ return prefix.trim() === '' || PREFIX_ENDS_WITH_TAG_RE.test(prefix);
126949
127034
  };
126950
127035
  /**
126951
127036
  * Rewrites a bare `<table>` that is almost certainly a missing-slash closer
@@ -126953,7 +127038,7 @@ const isAloneOnLine = (content, start, end) => {
126953
127038
  * Customer docs (CX-3850) close tables with a second bare opener; without this,
126954
127039
  * the never-terminated HTML flow block swallows the markdown that follows.
126955
127040
  *
126956
- * A candidate is a bare opener alone on its line while a table is already open.
127041
+ * A candidate is a bare opener that finishes its line while a table is already open.
126957
127042
  * It is judged a typo only when its element ends implicitly (no explicit
126958
127043
  * `</table>`) without ever acquiring table-structure children — so genuine
126959
127044
  * nested tables, whatever precedes their rows, are left alone.
@@ -126982,7 +127067,7 @@ function repairMistakenTableClosers(content) {
126982
127067
  start,
126983
127068
  end,
126984
127069
  hasStructureChild: false,
126985
- isCandidate: stack.length > 0 && BARE_OPENER_SOURCE_RE.test(source) && isAloneOnLine(content, start, end),
127070
+ isCandidate: stack.length > 0 && BARE_OPENER_SOURCE_RE.test(source) && endsLineAfterMarkup(content, start, end),
126986
127071
  });
126987
127072
  }
126988
127073
  else if (opaqueDepth === 0 && tableTags.has(lower) && stack.length > 0) {
@@ -127053,45 +127138,6 @@ const resolveDeferredAttributeExpressionProps = () => (tree, file) => {
127053
127138
  };
127054
127139
  /* harmony default export */ const resolve_deferred_attribute_expression_props = (resolveDeferredAttributeExpressionProps);
127055
127140
 
127056
- ;// ./processor/transform/mdxish/restore-snake-case-component-name.ts
127057
-
127058
-
127059
- /**
127060
- * Restores snake_case component names from placeholders after parsing.
127061
- * Runs after mdxishComponentBlocks converts HTML nodes to mdxJsxFlowElement.
127062
- */
127063
- const restoreSnakeCaseComponentNames = (options) => {
127064
- const { mapping } = options;
127065
- return tree => {
127066
- if (!mapping || Object.keys(mapping).length === 0) {
127067
- return tree;
127068
- }
127069
- visit(tree, 'mdxJsxFlowElement', (node) => {
127070
- if (node.name) {
127071
- node.name = restoreSnakeCase(node.name, mapping);
127072
- }
127073
- });
127074
- // Pre-compile regex patterns for better performance
127075
- const regexPatterns = Object.entries(mapping).map(([placeholder, original]) => ({
127076
- regex: new RegExp(`(<\\/?)(${placeholder})(\\s|\\/?>)`, 'gi'),
127077
- original,
127078
- }));
127079
- visit(tree, 'html', (node) => {
127080
- if (node.value) {
127081
- let newValue = node.value;
127082
- regexPatterns.forEach(({ regex, original }) => {
127083
- newValue = newValue.replace(regex, `$1${original}$3`);
127084
- });
127085
- if (newValue !== node.value) {
127086
- node.value = newValue;
127087
- }
127088
- }
127089
- });
127090
- return tree;
127091
- };
127092
- };
127093
- /* harmony default export */ const restore_snake_case_component_name = (restoreSnakeCaseComponentNames);
127094
-
127095
127141
  ;// ./processor/transform/mdxish/retain-boolean-attributes.ts
127096
127142
 
127097
127143
  // Private Use Area character (U+E000) which is extremely unlikely to appear in real content.
@@ -127614,9 +127660,6 @@ function loadComponents() {
127614
127660
 
127615
127661
 
127616
127662
 
127617
-
127618
-
127619
-
127620
127663
 
127621
127664
 
127622
127665
 
@@ -127638,10 +127681,8 @@ const defaultTransformers = [
127638
127681
  * 5. Terminate HTML flow blocks so subsequent content isn't swallowed
127639
127682
  * 6. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
127640
127683
  * 7. Normalize compact ATX headings (e.g., `#Heading` → `# Heading`)
127641
- * 8. Replace snake_case component names with parser-safe placeholders
127642
127684
  */
127643
- function preprocessContent(content, opts) {
127644
- const { knownComponents } = opts;
127685
+ function preprocessContent(content) {
127645
127686
  // Runs first so `jsxTable` sees a literal `</table>` (and the HTML-line
127646
127687
  // classification in `terminateHtmlFlowBlocks` is accurate)
127647
127688
  let result = normalizeClosingTagWhitespace(content);
@@ -127654,7 +127695,7 @@ function preprocessContent(content, opts) {
127654
127695
  result = terminateHtmlFlowBlocks(result);
127655
127696
  result = closeSelfClosingHtmlTags(result);
127656
127697
  result = normalizeCompactHeadings(result);
127657
- return processSnakeCaseComponent(result, { knownComponents });
127698
+ return result;
127658
127699
  }
127659
127700
  function mdxishAstProcessor(mdContent, opts = {}) {
127660
127701
  const { components: userComponents = {}, hardBreaks: enableHardBreaks = true, newEditorTypes = false, safeMode = false, useTailwind, } = opts;
@@ -127662,9 +127703,7 @@ function mdxishAstProcessor(mdContent, opts = {}) {
127662
127703
  ...loadComponents(),
127663
127704
  ...userComponents,
127664
127705
  };
127665
- // Build set of known component names for snake_case filtering
127666
- const knownComponents = new Set(Object.keys(components));
127667
- const { content: parserReadyContent, mapping: snakeCaseMapping } = preprocessContent(mdContent, { knownComponents });
127706
+ const parserReadyContent = preprocessContent(mdContent);
127668
127707
  // Create string map for tailwind transformer
127669
127708
  const tempComponentsMap = Object.entries(components).reduce((acc, [key, value]) => {
127670
127709
  acc[key] = String(value);
@@ -127677,10 +127716,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
127677
127716
  .use(remarkParse)
127678
127717
  .use(remarkFrontmatter)
127679
127718
  .use(normalize_malformed_md_syntax)
127680
- .use(self_closing_blocks)
127681
127719
  .use(mdx_blocks, { safeMode })
127682
127720
  .use(inline_html, { safeMode })
127683
- .use(restore_snake_case_component_name, { mapping: snakeCaseMapping })
127684
127721
  .use(mdxish_tables)
127685
127722
  .use(mdxish_html_blocks) // Convert every <HTMLBlock> shape → html-block
127686
127723
  // The next few transformers must appear after mdxishMdxComponentBlocks
@@ -127756,7 +127793,7 @@ function mdxish(mdContent, opts = {}) {
127756
127793
  processor
127757
127794
  .use(safeMode ? undefined : evaluate_exports) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
127758
127795
  .use(enableHardBreaks ? hard_breaks : undefined) // Must precede evaluateExpressions to avoid splitting the \n in an evaluated template literal into a <br> node
127759
- .use(safeMode ? undefined : evaluate_expressions) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
127796
+ .use(safeMode ? undefined : evaluate_expressions, { components, variables }) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
127760
127797
  .use(safeMode ? undefined : evaluate_style_block_expressions) // Evaluate `<style>{`...`}</style>` template literals into plain CSS
127761
127798
  .use(variables_code, { variables }) // Resolve <<...>> and {user.*} inside code and inline code nodes
127762
127799
  .use(remarkRehype, { allowDangerousHtml: true, handlers: mdxComponentHandlers })
@@ -128847,7 +128884,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"*":["about","acceptCharset","accessK
128847
128884
  /******/ // startup
128848
128885
  /******/ // Load entry module and return exports
128849
128886
  /******/ // This entry module used 'module' so it can't be inlined
128850
- /******/ let __webpack_exports__ = __webpack_require__(345);
128887
+ /******/ let __webpack_exports__ = __webpack_require__(8691);
128851
128888
  /******/ module.exports = __webpack_exports__;
128852
128889
  /******/
128853
128890
  /******/ })()