@readme/markdown 14.10.0 → 14.10.2

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.node.js CHANGED
@@ -97007,6 +97007,7 @@ const readmeComponents = (opts) => () => tree => {
97007
97007
 
97008
97008
 
97009
97009
 
97010
+
97010
97011
  const imageAttrs = ['align', 'alt', 'caption', 'border', 'height', 'src', 'title', 'width', 'lazy', 'className'];
97011
97012
  const readmeToMdx = () => tree => {
97012
97013
  // Unwrap pinned nodes, replace rdme-pin with its child node
@@ -97097,7 +97098,11 @@ const readmeToMdx = () => tree => {
97097
97098
  }
97098
97099
  });
97099
97100
  visit(tree, NodeTypes.imageBlock, (image, index, parent) => {
97100
- const attributes = toAttributes({ ...image, ...image.data.hProperties }, imageAttrs);
97101
+ // Special case for caption: Captions are parsed as children of the image node, so we need to extract them explicitly
97102
+ const captionChildren = Array.isArray(image.children) ? image.children : [];
97103
+ // We want to add it back to the attributes, so re-serialize it to string
97104
+ const caption = captionChildren.length ? toMarkdown({ type: 'root', children: captionChildren }).trim() : '';
97105
+ const attributes = toAttributes({ ...image, ...image.data.hProperties, ...(caption && { caption }) }, imageAttrs);
97101
97106
  if (hasExtra(attributes)) {
97102
97107
  parent.children.splice(index, 1, {
97103
97108
  type: 'mdxJsxFlowElement',
@@ -118835,6 +118840,11 @@ function smartCamelCase(str) {
118835
118840
  if (str.includes('-')) {
118836
118841
  return str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
118837
118842
  }
118843
+ // Keys that already carry casing (e.g. `cardWidth`) are correct as-is; running
118844
+ // boundary matching over them mangles inner matches (e.g. `id` inside `Width`).
118845
+ if (/[A-Z]/.test(str)) {
118846
+ return str;
118847
+ }
118838
118848
  const allBoundaries = [...REACT_HTML_PROP_BOUNDARIES, ...CSS_STYLE_PROP_BOUNDARIES, ...CUSTOM_PROP_BOUNDARIES];
118839
118849
  // Return as-is if already a boundary word to avoid incorrect splitting
118840
118850
  if (allBoundaries.includes(str.toLowerCase())) {
@@ -121943,6 +121953,43 @@ const parseSibling = (stack, parent, index, sibling, safeMode) => {
121943
121953
  stack.push(parent);
121944
121954
  }
121945
121955
  };
121956
+ /**
121957
+ * Advance a point by the substring of source consumed from it.
121958
+ */
121959
+ const pointAfter = (start, consumed) => {
121960
+ const newlineIndex = consumed.lastIndexOf('\n');
121961
+ const newlineCount = newlineIndex === -1 ? 0 : consumed.split('\n').length - 1;
121962
+ return {
121963
+ line: start.line + newlineCount,
121964
+ column: newlineCount === 0 ? start.column + consumed.length : consumed.length - newlineIndex,
121965
+ offset: start.offset + consumed.length,
121966
+ };
121967
+ };
121968
+ /**
121969
+ * Build a position ending at `consumedLength` into the html node's value, so the
121970
+ * component doesn't claim trailing content the tokenizer swallowed into one node.
121971
+ */
121972
+ const positionEndingAtConsumed = (nodePosition, value, consumedLength) => {
121973
+ if (!nodePosition?.start)
121974
+ return nodePosition;
121975
+ return { start: nodePosition.start, end: pointAfter(nodePosition.start, value.slice(0, consumedLength)) };
121976
+ };
121977
+ /**
121978
+ * Build a position ending right after the last occurrence of `closingTag` within
121979
+ * this node's span in the original source. Used in the trailing-content path so
121980
+ * the offset is computed against the real source bytes (including blockquote/list
121981
+ * prefixes that were stripped from the html node's value).
121982
+ */
121983
+ const positionEndingAtClosingTagInSource = (nodePosition, closingTag, source) => {
121984
+ if (!nodePosition?.start || !nodePosition.end)
121985
+ return nodePosition;
121986
+ const nodeSource = source.slice(nodePosition.start.offset, nodePosition.end.offset);
121987
+ const closingTagOffset = nodeSource.lastIndexOf(closingTag);
121988
+ if (closingTagOffset === -1)
121989
+ return nodePosition;
121990
+ const consumed = nodeSource.slice(0, closingTagOffset + closingTag.length);
121991
+ return { start: nodePosition.start, end: pointAfter(nodePosition.start, consumed) };
121992
+ };
121946
121993
  /**
121947
121994
  * Create an MdxJsxFlowElement node from component data.
121948
121995
  */
@@ -121986,9 +122033,10 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
121986
122033
  * The opening tag, content, and closing tag are all captured in one HTML node
121987
122034
  * (guaranteed by the mdx-component tokenizer).
121988
122035
  */
121989
- const mdxishMdxComponentBlocks = (opts = {}) => tree => {
122036
+ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121990
122037
  const stack = [tree];
121991
122038
  const safeMode = !!opts.safeMode;
122039
+ const source = file?.value ? String(file.value) : null;
121992
122040
  const parseOpts = { preserveExpressionsAsText: safeMode };
121993
122041
  const processChildNode = (parent, index) => {
121994
122042
  const node = parent.children[index];
@@ -122002,10 +122050,16 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
122002
122050
  const value = node.value;
122003
122051
  if (node.type !== 'html' || typeof value !== 'string')
122004
122052
  return;
122005
- const parsed = parseTag(value.trim(), parseOpts);
122053
+ const trimmed = value.trim();
122054
+ const parsed = parseTag(trimmed, parseOpts);
122006
122055
  if (!parsed)
122007
122056
  return;
122008
122057
  const { tag, attributes, selfClosing, contentAfterTag = '' } = parsed;
122058
+ // Offset of `trimmed` within the (possibly whitespace-padded) html node value,
122059
+ // so consumed-length math maps back onto the node's real source offsets.
122060
+ const leadingWhitespace = value.length - value.trimStart().length;
122061
+ // Index right after the opening tag's `>` within `trimmed`.
122062
+ const openingTagEnd = trimmed.length - contentAfterTag.length;
122009
122063
  // Skip tags that have dedicated transformers
122010
122064
  if (GENERIC_MDX_COMPONENT_EXCLUDED_TAGS.has(tag))
122011
122065
  return;
@@ -122030,6 +122084,8 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
122030
122084
  attributes,
122031
122085
  children: [],
122032
122086
  startPosition: node.position,
122087
+ // End at the self-closing tag, not at any trailing content.
122088
+ endPosition: positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd),
122033
122089
  });
122034
122090
  substituteNodeWithMdxNode(parent, index, componentNode);
122035
122091
  // Check and parse if there's relevant content after the current closing tag
@@ -122061,6 +122117,17 @@ const mdxishMdxComponentBlocks = (opts = {}) => tree => {
122061
122117
  attributes,
122062
122118
  children: parsedChildren,
122063
122119
  startPosition: node.position,
122120
+ // When trailing content follows the closing tag, compute the end position precisely
122121
+ // within the html node's value so the component doesn't claim that content.
122122
+ // Prefer source-based positioning when the original source is available: the html
122123
+ // node's value has '> '/space prefixes stripped for blockquotes/list items, so
122124
+ // positionEndingAtConsumed would undercount source offsets. When the entire node
122125
+ // is consumed, use the original node position directly.
122126
+ endPosition: contentAfterClose
122127
+ ? source
122128
+ ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
122129
+ : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length)
122130
+ : node.position,
122064
122131
  });
122065
122132
  substituteNodeWithMdxNode(parent, index, componentNode);
122066
122133
  // After the closing tag, there might be more content to be processed
@@ -124331,6 +124398,24 @@ function restoreHTMLElements(content, htmlElements) {
124331
124398
  return content;
124332
124399
  return content.replace(HTML_ELEM_PLACEHOLDER, (_m, idx) => htmlElements[parseInt(idx, 10)]);
124333
124400
  }
124401
+ const ESM_DECLARATION_START = /^(?:export|import)\b/;
124402
+ /**
124403
+ * Whether a line begins a top-level `export`/`import` declaration. Its braces
124404
+ * are valid JS owned by the mdxjsEsm tokenizer (which tolerates blank lines),
124405
+ * so escaping them would corrupt the source and break acorn parsing.
124406
+ */
124407
+ function startsEsmDeclaration(chars, lineStart) {
124408
+ return ESM_DECLARATION_START.test(chars.slice(lineStart, lineStart + 7).join(''));
124409
+ }
124410
+ function isBlankLine(chars, lineStart) {
124411
+ for (let i = lineStart; i < chars.length; i += 1) {
124412
+ if (chars[i] === '\n')
124413
+ return true;
124414
+ if (chars[i] !== ' ' && chars[i] !== '\t')
124415
+ return false;
124416
+ }
124417
+ return true;
124418
+ }
124334
124419
  /**
124335
124420
  * Escapes unbalanced and paragraph-spanning braces so MDX doesn't trip on them.
124336
124421
  */
@@ -124346,8 +124431,18 @@ function escapeProblematicBraces(content) {
124346
124431
  // Convert to array of Unicode code points so that emojis and multi-byte characters are correctly tracked
124347
124432
  const chars = Array.from(protectedContent);
124348
124433
  const openStack = [];
124434
+ // Whether the current top-level statement is an `export`/`import` declaration.
124435
+ let insideEsmDeclaration = false;
124349
124436
  for (let i = 0; i < chars.length; i += 1) {
124350
124437
  const ch = chars[i];
124438
+ // At a top-level (brace depth 0) line start, decide whether we're inside an
124439
+ // ESM declaration. The flag persists across the declaration's own lines.
124440
+ if (openStack.length === 0 && (i === 0 || chars[i - 1] === '\n')) {
124441
+ if (startsEsmDeclaration(chars, i))
124442
+ insideEsmDeclaration = true;
124443
+ else if (isBlankLine(chars, i))
124444
+ insideEsmDeclaration = false;
124445
+ }
124351
124446
  // Track string delimiters inside expressions to ignore braces within them
124352
124447
  if (openStack.length > 0) {
124353
124448
  if (strDelim) {
@@ -124405,19 +124500,22 @@ function escapeProblematicBraces(content) {
124405
124500
  if (!isAttrExpr && openStack.length > 0 && openStack[openStack.length - 1].isAttrExpr) {
124406
124501
  isAttrExpr = true;
124407
124502
  }
124408
- openStack.push({ pos: i, hasBlankLine: false, isAttrExpr });
124503
+ openStack.push({ pos: i, hasBlankLine: false, isAttrExpr, isEsmDeclaration: insideEsmDeclaration });
124409
124504
  lastNewlinePos = -2;
124410
124505
  }
124411
124506
  else if (ch === '}') {
124412
124507
  if (openStack.length > 0) {
124413
124508
  const entry = openStack.pop();
124509
+ // The declaration's braces are closed; later top-level braces are not ESM.
124510
+ if (openStack.length === 0)
124511
+ insideEsmDeclaration = false;
124414
124512
  // Pure `{/* ... */}` comments are handled downstream by the jsxComment
124415
124513
  // tokenizer — escaping their braces would prevent it from running.
124416
124514
  const isPureJsxComment = chars[entry.pos + 1] === '/' &&
124417
124515
  chars[entry.pos + 2] === '*' &&
124418
124516
  chars[i - 1] === '/' &&
124419
124517
  chars[i - 2] === '*';
124420
- if (entry.hasBlankLine && !isPureJsxComment && !entry.isAttrExpr) {
124518
+ if (entry.hasBlankLine && !isPureJsxComment && !entry.isAttrExpr && !entry.isEsmDeclaration) {
124421
124519
  toEscape.add(entry.pos);
124422
124520
  toEscape.add(i);
124423
124521
  }
@@ -124426,9 +124524,16 @@ function escapeProblematicBraces(content) {
124426
124524
  toEscape.add(i);
124427
124525
  }
124428
124526
  }
124527
+ else if (ch === ';' && openStack.length === 0) {
124528
+ // A top-level `;` ends the current statement, ESM or otherwise.
124529
+ insideEsmDeclaration = false;
124530
+ }
124429
124531
  }
124430
124532
  // Anything still open is unbalanced.
124431
- openStack.forEach(entry => toEscape.add(entry.pos));
124533
+ openStack.forEach(entry => {
124534
+ if (!entry.isEsmDeclaration)
124535
+ toEscape.add(entry.pos);
124536
+ });
124432
124537
  // Reconstruct the content with the escaped braces.
124433
124538
  const escapedContent = toEscape.size === 0 ? protectedContent : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
124434
124539
  return restoreHTMLElements(escapedContent, htmlElements);
@@ -125927,11 +126032,18 @@ function exportComponentsForRehype(components) {
125927
126032
  * semantics). That breaks any component prop that is genuinely an array. With
125928
126033
  * `passNode: true` we get the original hast node in `props.node` for components
125929
126034
  * and can read the raw properties directly.
126035
+ *
126036
+ * Only arrays win over `rest`, overwriting other shapes would undo correct
126037
+ * React conversions like `style="color:red"` → `{ color: 'red' }`.
125930
126038
  */
125931
126039
  function createElementPreservingHastProps(type, props, ...children) {
125932
126040
  if (props?.node?.properties) {
125933
126041
  const { node, ...rest } = props;
125934
- const mergedProps = { ...rest, ...node.properties };
126042
+ const mergedProps = { ...rest };
126043
+ Object.entries(node.properties).forEach(([key, value]) => {
126044
+ if (Array.isArray(value))
126045
+ mergedProps[key] = value;
126046
+ });
125935
126047
  // Strip undefined so positional args don't shadow node.properties.children
125936
126048
  const definedChildren = children.filter(c => c !== undefined);
125937
126049
  return external_react_default().createElement(type, mergedProps, ...definedChildren);