@readme/markdown 11.15.0 → 12.0.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
@@ -52943,12 +52943,40 @@ const plain = (node, opts = {}) => {
52943
52943
  };
52944
52944
  /* harmony default export */ const lib_plain = (plain);
52945
52945
 
52946
+ ;// ./processor/transform/extract-text.ts
52947
+ /**
52948
+ * Extracts text content from a single AST node recursively.
52949
+ * Works with both MDAST and HAST-like node structures.
52950
+ *
52951
+ * Placed this outside of the utils.ts file to avoid circular dependencies.
52952
+ *
52953
+ * @param node - The node to extract text from (can be MDAST Node or HAST-like structure)
52954
+ * @returns The concatenated text content
52955
+ */
52956
+ const extractText = (node) => {
52957
+ if (node.type === 'text' && typeof node.value === 'string') {
52958
+ return node.value;
52959
+ }
52960
+ if (node.children && Array.isArray(node.children)) {
52961
+ return node.children
52962
+ .map(child => {
52963
+ if (child && typeof child === 'object' && 'type' in child) {
52964
+ return extractText(child);
52965
+ }
52966
+ return '';
52967
+ })
52968
+ .join('');
52969
+ }
52970
+ return '';
52971
+ };
52972
+
52946
52973
  ;// ./processor/transform/callouts.ts
52947
52974
 
52948
52975
 
52949
52976
 
52950
52977
 
52951
52978
 
52979
+
52952
52980
  const callouts_regex = `^(${emoji_regex().source}|⚠)(\\s+|$)`;
52953
52981
  const findFirst = (node) => {
52954
52982
  if ('children' in node)
@@ -52969,42 +52997,76 @@ const wrapHeading = (node) => {
52969
52997
  },
52970
52998
  };
52971
52999
  };
53000
+ /**
53001
+ * Checks if a blockquote matches the expected callout structure:
53002
+ * blockquote > paragraph > text node
53003
+ */
53004
+ const isCalloutStructure = (node) => {
53005
+ const firstChild = node.children?.[0];
53006
+ if (!firstChild || firstChild.type !== 'paragraph')
53007
+ return false;
53008
+ if (!('children' in firstChild))
53009
+ return false;
53010
+ const firstTextChild = firstChild.children?.[0];
53011
+ return firstTextChild?.type === 'text';
53012
+ };
53013
+ const processBlockquote = (node, index, parent) => {
53014
+ if (!isCalloutStructure(node)) {
53015
+ // Only stringify empty blockquotes (no extractable text content)
53016
+ // Preserve blockquotes with actual content (e.g., headings, lists, etc.)
53017
+ const content = extractText(node);
53018
+ const isEmpty = !content || content.trim() === '';
53019
+ if (isEmpty && index !== undefined && parent) {
53020
+ const textNode = {
53021
+ type: 'text',
53022
+ value: '>',
53023
+ };
53024
+ const paragraphNode = {
53025
+ type: 'paragraph',
53026
+ children: [textNode],
53027
+ position: node.position,
53028
+ };
53029
+ parent.children.splice(index, 1, paragraphNode);
53030
+ }
53031
+ return;
53032
+ }
53033
+ // isCalloutStructure ensures node.children[0] is a Paragraph with children
53034
+ const firstParagraph = node.children[0];
53035
+ const startText = lib_plain(firstParagraph).toString();
53036
+ const [match, icon] = startText.match(callouts_regex) || [];
53037
+ if (icon && match) {
53038
+ const heading = startText.slice(match.length);
53039
+ const empty = !heading.length && firstParagraph.children.length === 1;
53040
+ const theme = themes[icon] || 'default';
53041
+ const firstChild = findFirst(node.children[0]);
53042
+ if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
53043
+ firstChild.value = firstChild.value.slice(match.length);
53044
+ }
53045
+ if (heading) {
53046
+ node.children[0] = wrapHeading(node);
53047
+ // @note: We add to the offset/column the length of the unicode
53048
+ // character that was stripped off, so that the start position of the
53049
+ // heading/text matches where it actually starts.
53050
+ node.children[0].position.start.offset += match.length;
53051
+ node.children[0].position.start.column += match.length;
53052
+ }
53053
+ Object.assign(node, {
53054
+ type: NodeTypes.callout,
53055
+ data: {
53056
+ hName: 'Callout',
53057
+ hProperties: {
53058
+ icon,
53059
+ ...(empty && { empty }),
53060
+ theme,
53061
+ },
53062
+ },
53063
+ });
53064
+ }
53065
+ };
52972
53066
  const calloutTransformer = () => {
52973
53067
  return (tree) => {
52974
- visit(tree, 'blockquote', (node) => {
52975
- if (!(node.children[0].type === 'paragraph' && node.children[0].children[0].type === 'text'))
52976
- return;
52977
- // @ts-expect-error -- @todo: update plain to accept mdast
52978
- const startText = lib_plain(node.children[0]).toString();
52979
- const [match, icon] = startText.match(callouts_regex) || [];
52980
- if (icon && match) {
52981
- const heading = startText.slice(match.length);
52982
- const empty = !heading.length && node.children[0].children.length === 1;
52983
- const theme = themes[icon] || 'default';
52984
- const firstChild = findFirst(node.children[0]);
52985
- if (firstChild && 'value' in firstChild && typeof firstChild.value === 'string') {
52986
- firstChild.value = firstChild.value.slice(match.length);
52987
- }
52988
- if (heading) {
52989
- node.children[0] = wrapHeading(node);
52990
- // @note: We add to the offset/column the length of the unicode
52991
- // character that was stripped off, so that the start position of the
52992
- // heading/text matches where it actually starts.
52993
- node.children[0].position.start.offset += match.length;
52994
- node.children[0].position.start.column += match.length;
52995
- }
52996
- Object.assign(node, {
52997
- type: NodeTypes.callout,
52998
- data: {
52999
- hName: 'Callout',
53000
- hProperties: {
53001
- icon,
53002
- ...(empty && { empty }),
53003
- theme,
53004
- },
53005
- },
53006
- });
53007
- }
53068
+ visit(tree, 'blockquote', (node, index, parent) => {
53069
+ processBlockquote(node, index, parent);
53008
53070
  });
53009
53071
  };
53010
53072
  };
@@ -70670,6 +70732,17 @@ const parseTag = (value) => {
70670
70732
  contentAfterTag,
70671
70733
  };
70672
70734
  };
70735
+ /**
70736
+ * Parse substring content of a node and update the parent's children to include the new nodes.
70737
+ */
70738
+ const parseSibling = (stack, parent, index, sibling) => {
70739
+ const siblingNodes = parseMdChildren(sibling);
70740
+ // The new sibling nodes might contain new components to be processed
70741
+ if (siblingNodes.length > 0) {
70742
+ parent.children.splice(index + 1, 0, ...siblingNodes);
70743
+ stack.push(parent);
70744
+ }
70745
+ };
70673
70746
  /**
70674
70747
  * Create an MdxJsxFlowElement node from component data.
70675
70748
  */
@@ -70812,11 +70885,12 @@ const mdxishComponentBlocks = () => tree => {
70812
70885
  if ('children' in node && Array.isArray(node.children)) {
70813
70886
  stack.push(node);
70814
70887
  }
70815
- // Only visit HTML nodes with an actual html tag
70888
+ // Only visit HTML nodes with an actual html tag,
70889
+ // which means a potential unparsed MDX component
70816
70890
  const value = node.value;
70817
70891
  if (node.type !== 'html' || typeof value !== 'string')
70818
70892
  return;
70819
- const parsed = parseTag(value);
70893
+ const parsed = parseTag(value.trim());
70820
70894
  if (!parsed)
70821
70895
  return;
70822
70896
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
@@ -70833,11 +70907,19 @@ const mdxishComponentBlocks = () => tree => {
70833
70907
  startPosition: node.position,
70834
70908
  });
70835
70909
  substituteNodeWithMdxNode(parent, index, componentNode);
70910
+ // Check and parse if there's relevant content after the current closing tag
70911
+ const remainingContent = contentAfterTag.trim();
70912
+ if (remainingContent) {
70913
+ parseSibling(stack, parent, index, remainingContent);
70914
+ }
70836
70915
  return;
70837
70916
  }
70838
70917
  // Case 2: Self-contained block (closing tag in content)
70839
70918
  if (contentAfterTag.includes(closingTagStr)) {
70840
- const componentInnerContent = contentAfterTag.substring(0, contentAfterTag.lastIndexOf(closingTagStr)).trim();
70919
+ // Find the first closing tag
70920
+ const closingTagIndex = contentAfterTag.indexOf(closingTagStr);
70921
+ const componentInnerContent = contentAfterTag.substring(0, closingTagIndex).trim();
70922
+ const contentAfterClose = contentAfterTag.substring(closingTagIndex + closingTagStr.length).trim();
70841
70923
  const componentNode = createComponentNode({
70842
70924
  tag,
70843
70925
  attributes,
@@ -70845,6 +70927,14 @@ const mdxishComponentBlocks = () => tree => {
70845
70927
  startPosition: node.position,
70846
70928
  });
70847
70929
  substituteNodeWithMdxNode(parent, index, componentNode);
70930
+ // After the closing tag, there might be more content to be processed
70931
+ if (contentAfterClose) {
70932
+ parseSibling(stack, parent, index, contentAfterClose);
70933
+ }
70934
+ else if (componentNode.children.length > 0) {
70935
+ // The content inside the component block might contain new components to be processed
70936
+ stack.push(componentNode);
70937
+ }
70848
70938
  return;
70849
70939
  }
70850
70940
  // Case 3: Multi-sibling component (closing tag in a following sibling)
@@ -70870,8 +70960,13 @@ const mdxishComponentBlocks = () => tree => {
70870
70960
  });
70871
70961
  // Remove all nodes from opening tag to closing tag (inclusive) and replace with component node
70872
70962
  parent.children.splice(index, closingIndex - index + 1, componentNode);
70963
+ // Since we might be merging sibling nodes together and combining content,
70964
+ // there might be new components to process
70965
+ if (componentNode.children.length > 0) {
70966
+ stack.push(componentNode);
70967
+ }
70873
70968
  };
70874
- // Travel the tree depth-first
70969
+ // Process the nodes with the components depth-first to maintain the correct order of the nodes
70875
70970
  while (stack.length) {
70876
70971
  const parent = stack.pop();
70877
70972
  if (parent?.children) {
@@ -70892,6 +70987,7 @@ const mdxishComponentBlocks = () => tree => {
70892
70987
 
70893
70988
 
70894
70989
 
70990
+
70895
70991
  const isTableCell = (node) => isMDXElement(node) && ['th', 'td'].includes(node.name);
70896
70992
  const tableTypes = {
70897
70993
  tr: 'tableRow',
@@ -70916,18 +71012,13 @@ const isTextOnly = (children) => {
70916
71012
  });
70917
71013
  };
70918
71014
  /**
70919
- * Extract text content from children nodes
71015
+ * Convenience wrapper that extracts text content from an array of children nodes.
70920
71016
  */
70921
- const extractText = (children) => {
71017
+ const extractTextFromChildren = (children) => {
70922
71018
  return children
70923
71019
  .map(child => {
70924
71020
  if (child && typeof child === 'object' && 'type' in child) {
70925
- if (child.type === 'text' && 'value' in child && typeof child.value === 'string') {
70926
- return child.value;
70927
- }
70928
- if (child.type === 'mdxJsxTextElement' && 'children' in child && Array.isArray(child.children)) {
70929
- return extractText(child.children);
70930
- }
71021
+ return extractText(child);
70931
71022
  }
70932
71023
  return '';
70933
71024
  })
@@ -70958,7 +71049,7 @@ const processTableNode = (node, index, parent) => {
70958
71049
  let parsedChildren = cellChildren;
70959
71050
  // If cell contains only text nodes, try to re-parse as markdown
70960
71051
  if (isTextOnly(cellChildren)) {
70961
- const textContent = extractText(cellChildren);
71052
+ const textContent = extractTextFromChildren(cellChildren);
70962
71053
  if (textContent.trim()) {
70963
71054
  try {
70964
71055
  const parsed = parseMarkdown(textContent);
@@ -86373,7 +86464,7 @@ const CUSTOM_PROP_BOUNDARIES = [
86373
86464
  /**
86374
86465
  * Tags that should be passed through and handled at runtime (not by the mdxish plugin)
86375
86466
  */
86376
- const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable']);
86467
+ const RUNTIME_COMPONENT_TAGS = new Set(['Variable', 'variable', 'rdme-pin']);
86377
86468
  /**
86378
86469
  * Standard HTML tags that should never be treated as custom components.
86379
86470
  * Uses the html-tags package, converted to a Set<string> for efficient lookups.
@@ -93004,6 +93095,33 @@ const INLINE_COMPONENT_TAGS = new Set(['anchor', 'glossary']);
93004
93095
  function isElementContentNode(node) {
93005
93096
  return node.type === 'element' || node.type === 'text' || node.type === 'comment';
93006
93097
  }
93098
+ /**
93099
+ * Components are assumed to be block-level, so whitespace between them can be removed
93100
+ * We want to remove them because it can be treated as actual children of the component
93101
+ * when it doesn't need to.
93102
+ *
93103
+ * It can be a problem because it can be passed as args to the components, like the ones
93104
+ * defined in /components, which can break the type assumptions of the components and cause
93105
+ * type errors, accessing properties that don't exist.
93106
+ */
93107
+ function areAllChildrenComponents(children) {
93108
+ return children.every(child => {
93109
+ // Whitespace-only text nodes don't affect the check
93110
+ if (child.type === 'text' && !child.value.trim())
93111
+ return true;
93112
+ // Text with actual content means we have mixed content
93113
+ if (child.type === 'text')
93114
+ return false;
93115
+ // Comments don't affect the check
93116
+ if (child.type === 'comment')
93117
+ return true;
93118
+ // Standard HTML tags are not considered components
93119
+ if (child.type === 'element' && 'tagName' in child) {
93120
+ return !STANDARD_HTML_TAGS.has(child.tagName.toLowerCase());
93121
+ }
93122
+ return false;
93123
+ });
93124
+ }
93007
93125
  /** Check if nodes represent a single paragraph with only text (no markdown formatting) */
93008
93126
  function isSingleParagraphTextNode(nodes) {
93009
93127
  return (nodes.length === 1 &&
@@ -93048,6 +93166,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
93048
93166
  function parseTextChildren(node, processMarkdown) {
93049
93167
  if (!node.children?.length)
93050
93168
  return;
93169
+ // First pass: Recursively process text children as they may contain stringified markdown / mdx content
93051
93170
  node.children = node.children.flatMap(child => {
93052
93171
  if (child.type !== 'text' || !child.value.trim())
93053
93172
  return [child];
@@ -93059,6 +93178,15 @@ function parseTextChildren(node, processMarkdown) {
93059
93178
  }
93060
93179
  return children;
93061
93180
  });
93181
+ // Post-processing: remove whitespace-only text nodes if all siblings are components
93182
+ // This prevents whitespace between component children from being counted as extra children
93183
+ if (areAllChildrenComponents(node.children)) {
93184
+ node.children = node.children.filter(child => {
93185
+ if (child.type === 'text' && !child.value.trim())
93186
+ return false;
93187
+ return true;
93188
+ });
93189
+ }
93062
93190
  }
93063
93191
  /** Convert node properties from kebab-case/lowercase to camelCase */
93064
93192
  function normalizeProperties(node) {
@@ -93121,6 +93249,15 @@ const mdxExpressionHandler = (_state, node) => ({
93121
93249
  type: 'text',
93122
93250
  value: node.value || '',
93123
93251
  });
93252
+ // Since we serialize component / html tag attributes
93253
+ function decodeHtmlEntities(value) {
93254
+ return value
93255
+ .replace(/&quot;/g, '"')
93256
+ .replace(/&lt;/g, '<')
93257
+ .replace(/&gt;/g, '>')
93258
+ .replace(/&#10;/g, '\n')
93259
+ .replace(/&amp;/g, '&');
93260
+ }
93124
93261
  // Convert MDX JSX elements to HAST elements, preserving attributes and children
93125
93262
  const mdxJsxElementHandler = (state, node) => {
93126
93263
  const { attributes = [], name } = node;
@@ -93132,7 +93269,7 @@ const mdxJsxElementHandler = (state, node) => {
93132
93269
  properties[attribute.name] = true;
93133
93270
  }
93134
93271
  else if (typeof attribute.value === 'string') {
93135
- properties[attribute.name] = attribute.value;
93272
+ properties[attribute.name] = decodeHtmlEntities(attribute.value);
93136
93273
  }
93137
93274
  else {
93138
93275
  properties[attribute.name] = attribute.value.value;
@@ -93194,6 +93331,17 @@ function base64Decode(str) {
93194
93331
  }
93195
93332
  return decodeURIComponent(escape(atob(str)));
93196
93333
  }
93334
+ function escapeHtmlAttribute(value) {
93335
+ return value
93336
+ .replace(/&/g, '&amp;')
93337
+ .replace(/"/g, '&quot;')
93338
+ .replace(/</g, '&lt;')
93339
+ .replace(/>/g, '&gt;')
93340
+ .replace(/\n/g, '&#10;');
93341
+ }
93342
+ // Marker prefix for JSON-serialized complex values (arrays/objects)
93343
+ // Using a prefix that won't conflict with regular string values
93344
+ const JSON_VALUE_MARKER = '__MDXISH_JSON__';
93197
93345
  // Markers for protected HTMLBlock content (HTML comments avoid markdown parsing issues)
93198
93346
  const HTML_BLOCK_CONTENT_START = '<!--RDMX_HTMLBLOCK:';
93199
93347
  const HTML_BLOCK_CONTENT_END = ':RDMX_HTMLBLOCK-->';
@@ -93323,6 +93471,16 @@ function extractBalancedBraces(content, start) {
93323
93471
  return null;
93324
93472
  return { content: content.slice(start, pos - 1), end: pos };
93325
93473
  }
93474
+ function restoreInlineCode(content, protectedCode) {
93475
+ return content.replace(/___INLINE_CODE_(\d+)___/g, (_m, idx) => {
93476
+ return protectedCode.inlineCode[parseInt(idx, 10)];
93477
+ });
93478
+ }
93479
+ function restoreCodeBlocks(content, protectedCode) {
93480
+ return content.replace(/___CODE_BLOCK_(\d+)___/g, (_m, idx) => {
93481
+ return protectedCode.codeBlocks[parseInt(idx, 10)];
93482
+ });
93483
+ }
93326
93484
  /**
93327
93485
  * Converts JSX attribute expressions (attribute={expression}) to HTML attributes (attribute="value").
93328
93486
  * Handles style objects (camelCase → kebab-case), className → class, and JSON stringifies objects.
@@ -93338,7 +93496,7 @@ function extractBalancedBraces(content, start) {
93338
93496
  * // Returns: '<a href="https://example.com">Link</a>'
93339
93497
  * ```
93340
93498
  */
93341
- function evaluateAttributeExpressions(content, context) {
93499
+ function evaluateAttributeExpressions(content, context, protectedCode) {
93342
93500
  const attrStartRegex = /(\w+)=\{/g;
93343
93501
  let result = '';
93344
93502
  let lastEnd = 0;
@@ -93348,7 +93506,14 @@ function evaluateAttributeExpressions(content, context) {
93348
93506
  const braceStart = match.index + match[0].length;
93349
93507
  const extracted = extractBalancedBraces(content, braceStart);
93350
93508
  if (extracted) {
93351
- const expression = extracted.content;
93509
+ // The expression might contain template literals in MDX component tag props
93510
+ // E.g. <Component greeting={`Hello World!`} />
93511
+ // that is marked as inline code. So we need to restore the inline codes
93512
+ // in the expression to evaluate it
93513
+ let expression = extracted.content;
93514
+ if (protectedCode) {
93515
+ expression = restoreInlineCode(expression, protectedCode);
93516
+ }
93352
93517
  const fullMatchEnd = extracted.end;
93353
93518
  result += content.slice(lastEnd, match.index);
93354
93519
  try {
@@ -93364,14 +93529,20 @@ function evaluateAttributeExpressions(content, context) {
93364
93529
  result += `style="${cssString}"`;
93365
93530
  }
93366
93531
  else {
93367
- result += `${attributeName}='${JSON.stringify(evalResult)}'`;
93532
+ // These are arrays / objects attribute values
93533
+ // Mark JSON-serialized values with a prefix so they can be parsed back correctly
93534
+ const jsonValue = escapeHtmlAttribute(JSON_VALUE_MARKER + JSON.stringify(evalResult));
93535
+ // Use double quotes so that multi-paragraph values are not split into multiple attributes by the processors
93536
+ result += `${attributeName}="${jsonValue}"`;
93368
93537
  }
93369
93538
  }
93370
93539
  else if (attributeName === 'className') {
93371
- result += `class="${evalResult}"`;
93540
+ // Escape special characters so that it doesn't break and split the attribute value to nodes
93541
+ // This will be restored later in the pipeline
93542
+ result += `class="${escapeHtmlAttribute(String(evalResult))}"`;
93372
93543
  }
93373
93544
  else {
93374
- result += `${attributeName}="${evalResult}"`;
93545
+ result += `${attributeName}="${escapeHtmlAttribute(String(evalResult))}"`;
93375
93546
  }
93376
93547
  }
93377
93548
  catch (_error) {
@@ -93402,13 +93573,9 @@ function evaluateAttributeExpressions(content, context) {
93402
93573
  * // Returns: 'Text with `inline` and ```js\ncode\n```'
93403
93574
  * ```
93404
93575
  */
93405
- function restoreCodeBlocks(content, protectedCode) {
93406
- let restored = content.replace(/___CODE_BLOCK_(\d+)___/g, (_match, index) => {
93407
- return protectedCode.codeBlocks[parseInt(index, 10)];
93408
- });
93409
- restored = restored.replace(/___INLINE_CODE_(\d+)___/g, (_match, index) => {
93410
- return protectedCode.inlineCode[parseInt(index, 10)];
93411
- });
93576
+ function restoreProtectedCodes(content, protectedCode) {
93577
+ let restored = restoreCodeBlocks(content, protectedCode);
93578
+ restored = restoreInlineCode(restored, protectedCode);
93412
93579
  return restored;
93413
93580
  }
93414
93581
  /**
@@ -93429,9 +93596,9 @@ function preprocessJSXExpressions(content, context = {}) {
93429
93596
  // Step 3: Evaluate attribute expressions (JSX attribute syntax: href={baseUrl})
93430
93597
  // For inline expressions, we use a library to parse the expression & evaluate it later
93431
93598
  // For attribute expressions, it was difficult to use a library to parse them, so do it manually
93432
- processed = evaluateAttributeExpressions(processed, context);
93599
+ processed = evaluateAttributeExpressions(processed, context, protectedCode);
93433
93600
  // Step 4: Restore protected code blocks
93434
- processed = restoreCodeBlocks(processed, protectedCode);
93601
+ processed = restoreProtectedCodes(processed, protectedCode);
93435
93602
  return processed;
93436
93603
  }
93437
93604
 
@@ -93843,7 +94010,7 @@ const wrapPinnedBlocks = (node, json) => {
93843
94010
  return node;
93844
94011
  return {
93845
94012
  children: [node],
93846
- data: { className: 'pin', hName: 'rdme-pin' },
94013
+ data: { hName: 'rdme-pin', hProperties: { className: 'pin' } },
93847
94014
  type: 'rdme-pin',
93848
94015
  };
93849
94016
  };
@@ -93931,15 +94098,16 @@ function parseMagicBlock(raw, options = {}) {
93931
94098
  type: 'code',
93932
94099
  value: obj.code.trim(),
93933
94100
  }));
93934
- // Single code block without a tab name renders as a plain code block
94101
+ // Single code block without a tab name (meta or language) renders as a plain code block
94102
+ // Otherwise, we want to render it as a code tabs block
93935
94103
  if (children.length === 1) {
93936
94104
  if (!children[0].value)
93937
94105
  return [];
93938
- if (children[0].meta)
94106
+ if (!(children[0].meta || children[0].lang))
93939
94107
  return [wrapPinnedBlocks(children[0], json)];
93940
94108
  }
93941
- // Multiple code blocks render as tabbed code blocks
93942
- return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'code-tabs' }, type: 'code-tabs' }, json)];
94109
+ // Multiple code blocks or a single code block with a tab name (meta or language) renders as a code tabs block
94110
+ return [wrapPinnedBlocks({ children, className: 'tabs', data: { hName: 'CodeTabs' }, type: 'code-tabs' }, json)];
93943
94111
  }
93944
94112
  // API header: renders as a heading element (h1-h6)
93945
94113
  case 'api-header': {
@@ -94140,11 +94308,31 @@ function parseMagicBlock(raw, options = {}) {
94140
94308
  }
94141
94309
  }
94142
94310
  /**
94143
- * Check if a child node is a flow element that needs unwrapping (mdxJsxFlowElement, etc.)
94311
+ * Block-level node types that cannot be nested inside paragraphs.
94144
94312
  */
94145
- const needsUnwrapping = (child) => {
94146
- return child.type === 'mdxJsxFlowElement';
94147
- };
94313
+ const blockTypes = [
94314
+ 'heading',
94315
+ 'code',
94316
+ 'code-tabs',
94317
+ 'paragraph',
94318
+ 'blockquote',
94319
+ 'list',
94320
+ 'table',
94321
+ 'thematicBreak',
94322
+ 'html',
94323
+ 'yaml',
94324
+ 'toml',
94325
+ 'rdme-pin',
94326
+ 'rdme-callout',
94327
+ 'html-block',
94328
+ 'embed',
94329
+ 'figure',
94330
+ 'mdxJsxFlowElement',
94331
+ ];
94332
+ /**
94333
+ * Check if a node is a block-level node (cannot be inside a paragraph)
94334
+ */
94335
+ const isBlockNode = (node) => blockTypes.includes(node.type);
94148
94336
  /**
94149
94337
  * Unified plugin that restores magic blocks from placeholder tokens.
94150
94338
  *
@@ -94157,8 +94345,9 @@ const magicBlockRestorer = ({ blocks }) => tree => {
94157
94345
  return;
94158
94346
  // Map: key → original raw magic block content
94159
94347
  const magicBlockKeys = new Map(blocks.map(({ key, raw }) => [key, raw]));
94160
- // Find inlineCode nodes that match our placeholder tokens
94161
- const modifications = [];
94348
+ // Collect replacements to apply (we need to visit in reverse to maintain indices)
94349
+ const replacements = [];
94350
+ // First pass: collect all replacements
94162
94351
  visit(tree, 'inlineCode', (node, index, parent) => {
94163
94352
  if (!parent || index == null)
94164
94353
  return undefined;
@@ -94168,31 +94357,73 @@ const magicBlockRestorer = ({ blocks }) => tree => {
94168
94357
  const children = parseMagicBlock(raw);
94169
94358
  if (!children.length)
94170
94359
  return undefined;
94171
- if (children[0] && needsUnwrapping(children[0]) && parent.type === 'paragraph') {
94172
- // Find paragraph's parent and unwrap
94173
- let paragraphParent;
94174
- visit(tree, 'paragraph', (p, pIndex, pParent) => {
94175
- if (p === parent && pParent && 'children' in pParent) {
94176
- paragraphParent = pParent;
94177
- return false;
94360
+ // If parent is a paragraph and we're inserting block nodes (which must not be in paragraphs), lift them out
94361
+ if (parent.type === 'paragraph' && children.some(child => isBlockNode(child))) {
94362
+ const blockNodes = [];
94363
+ const inlineNodes = [];
94364
+ // Separate block and inline nodes
94365
+ children.forEach(child => {
94366
+ if (isBlockNode(child)) {
94367
+ blockNodes.push(child);
94178
94368
  }
94179
- return undefined;
94180
- });
94181
- if (paragraphParent) {
94182
- const paragraphIndex = paragraphParent.children.indexOf(parent);
94183
- if (paragraphIndex !== -1) {
94184
- modifications.push({ children, index: paragraphIndex, parent: paragraphParent });
94369
+ else {
94370
+ inlineNodes.push(child);
94185
94371
  }
94186
- }
94187
- return SKIP;
94372
+ });
94373
+ const before = parent.children.slice(0, index);
94374
+ const after = parent.children.slice(index + 1);
94375
+ replacements.push({
94376
+ parent,
94377
+ blockNodes,
94378
+ inlineNodes,
94379
+ before,
94380
+ after,
94381
+ });
94188
94382
  }
94189
- parent.children.splice(index, 1, ...children);
94190
- return [SKIP, index + children.length];
94191
- });
94192
- // Apply modifications in reverse order to avoid index shifting
94193
- modifications.reverse().forEach(({ children, index, parent }) => {
94194
- parent.children.splice(index, 1, ...children);
94383
+ else {
94384
+ // Normal case: just replace the inlineCode with the children
94385
+ parent.children.splice(index, 1, ...children);
94386
+ }
94387
+ return undefined;
94195
94388
  });
94389
+ // Second pass: apply replacements that require lifting block nodes out of paragraphs
94390
+ // Process in reverse order to maintain correct indices
94391
+ for (let i = replacements.length - 1; i >= 0; i -= 1) {
94392
+ const { after, before, blockNodes, inlineNodes, parent } = replacements[i];
94393
+ // Find the paragraph's position in the root
94394
+ const rootChildren = tree.children;
94395
+ const paraIndex = rootChildren.findIndex(child => child === parent);
94396
+ if (paraIndex === -1) {
94397
+ // Paragraph not found in root - fall back to normal replacement
94398
+ // This shouldn't happen normally, but handle it gracefully
94399
+ // Reconstruct the original index from before.length
94400
+ const originalIndex = before.length;
94401
+ parent.children.splice(originalIndex, 1, ...blockNodes, ...inlineNodes);
94402
+ // eslint-disable-next-line no-continue
94403
+ continue;
94404
+ }
94405
+ // Update or remove the paragraph
94406
+ if (inlineNodes.length > 0) {
94407
+ // Keep paragraph with inline nodes
94408
+ parent.children = [...before, ...inlineNodes, ...after];
94409
+ // Insert block nodes after the paragraph
94410
+ if (blockNodes.length > 0) {
94411
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94412
+ }
94413
+ }
94414
+ else if (before.length === 0 && after.length === 0) {
94415
+ // Remove empty paragraph and replace with block nodes
94416
+ rootChildren.splice(paraIndex, 1, ...blockNodes);
94417
+ }
94418
+ else {
94419
+ // Keep paragraph with remaining content
94420
+ parent.children = [...before, ...after];
94421
+ // Insert block nodes after the paragraph
94422
+ if (blockNodes.length > 0) {
94423
+ rootChildren.splice(paraIndex + 1, 0, ...blockNodes);
94424
+ }
94425
+ }
94426
+ }
94196
94427
  };
94197
94428
  /* harmony default export */ const mdxish_magic_blocks = (magicBlockRestorer);
94198
94429
 
@@ -95046,6 +95277,40 @@ const makeUseMDXComponents = (more = {}) => {
95046
95277
 
95047
95278
 
95048
95279
 
95280
+
95281
+ /**
95282
+ * Parse JSON-marked string values in props back to their original types.
95283
+ * This handles arrays and objects that were serialized during JSX preprocessing.
95284
+ */
95285
+ function parseJsonProps(props) {
95286
+ if (!props)
95287
+ return props;
95288
+ const parsed = {};
95289
+ Object.entries(props).forEach(([key, value]) => {
95290
+ if (typeof value === 'string' && value.startsWith(JSON_VALUE_MARKER)) {
95291
+ try {
95292
+ parsed[key] = JSON.parse(value.slice(JSON_VALUE_MARKER.length));
95293
+ }
95294
+ catch {
95295
+ // If parsing fails, use the value without the marker
95296
+ parsed[key] = value.slice(JSON_VALUE_MARKER.length);
95297
+ }
95298
+ }
95299
+ else {
95300
+ parsed[key] = value;
95301
+ }
95302
+ });
95303
+ return parsed;
95304
+ }
95305
+ /**
95306
+ * Custom createElement wrapper that parses JSON-marked string props.
95307
+ * This is needed because rehype-react converts HAST to React, but complex
95308
+ * types (arrays/objects) get serialized to strings during markdown parsing.
95309
+ */
95310
+ function createElementWithJsonProps(type, props, ...children) {
95311
+ const parsedProps = parseJsonProps(props);
95312
+ return external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default().createElement(type, parsedProps, ...children);
95313
+ }
95049
95314
  /** Flatten CustomComponents into a component map for rehype-react */
95050
95315
  function exportComponentsForRehype(components) {
95051
95316
  const exported = Object.entries(components).reduce((memo, [tag, mod]) => {
@@ -95077,7 +95342,7 @@ function exportComponentsForRehype(components) {
95077
95342
  function createRehypeReactProcessor(components) {
95078
95343
  // @ts-expect-error - rehype-react types are incompatible with React.Fragment return type
95079
95344
  return unified().use((rehype_react_default()), {
95080
- createElement: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()).createElement,
95345
+ createElement: createElementWithJsonProps,
95081
95346
  Fragment: (external_amd_react_commonjs_react_commonjs2_react_root_React_umd_react_default()).Fragment,
95082
95347
  components,
95083
95348
  });