@dogsbay/markdown-it-mdx-jsx 0.2.0-beta.13 → 0.2.0-beta.130

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/index.d.ts CHANGED
@@ -16,6 +16,13 @@ export interface MdxJsxOptions {
16
16
  components?: string[];
17
17
  /** Tags that are always self-closing even without `/>`. */
18
18
  selfClosing?: string[];
19
+ /**
20
+ * A self-closing tag followed by TEXT on its line opens a paragraph (the
21
+ * tag inline), as MDX reads it, instead of a block that dropped the rest of
22
+ * the line. Opt-in: a host must then handle (or report) inline components,
23
+ * or the component is lost instead of the text.
24
+ */
25
+ inlineWhenFollowedByText?: boolean;
19
26
  }
20
27
  /**
21
28
  * Parse JSX-style attributes from a string.
package/dist/index.js CHANGED
@@ -271,7 +271,10 @@ function parseBlockTag(state, startLine, endLine, offset = 0) {
271
271
  let combined = remaining;
272
272
  let currentLine = startLine + 1;
273
273
  while (currentLine < endLine) {
274
- const nextLine = getLine(state, currentLine);
274
+ // The whole line, indentation included: a multi-line attribute VALUE
275
+ // (`<Code code={`…`} />`) is content, and trimming each line flattened
276
+ // every code sample written that way.
277
+ const nextLine = state.src.slice(state.bMarks[currentLine], state.eMarks[currentLine]);
275
278
  combined += "\n" + nextLine;
276
279
  const parsed = parseTag(combined, 0);
277
280
  if (parsed) {
@@ -300,10 +303,21 @@ function createJsxBlockRule(options) {
300
303
  return false;
301
304
  if (componentSet && !componentSet.has(parsed.tag))
302
305
  return false;
303
- if (silent)
304
- return true;
305
306
  const lineEnd = state.eMarks[startLine];
306
307
  const lineText = state.src.slice(pos, lineEnd);
308
+ // A self-closing tag followed by TEXT on its line opens a paragraph, as in
309
+ // MDX (`<Badge text="Beta" /> Connected meetings let you…`): taken as a
310
+ // block, the rest of the line was dropped. Another tag after it is still
311
+ // flow (`<Tabs> <TabItem>`), handled below.
312
+ if (options.inlineWhenFollowedByText && parsed.endLine === startLine && parsed.selfClosing) {
313
+ const rest = lineText.slice(parsed.length).trim();
314
+ // Another tag or a trailing `{…}` expression (`{/* c */}`, `{" "}`) is
315
+ // still flow; only prose makes it a paragraph.
316
+ if (rest && !rest.startsWith("<") && !rest.startsWith("{"))
317
+ return false;
318
+ }
319
+ if (silent)
320
+ return true;
307
321
  // Process this tag and any subsequent tags on the same line
308
322
  return processFlowTags(state, startLine, endLine, pos, lineText, componentSet, selfClosingSet);
309
323
  };
@@ -406,6 +420,7 @@ function processFlowTags(state, startLine, endLine, linePos, lineText, component
406
420
  return true;
407
421
  }
408
422
  // ── Inline rule ────────────────────────────────────────────
423
+ const openInline = new WeakMap();
409
424
  function createJsxInlineRule(options) {
410
425
  const componentSet = options.components ? new Set(options.components) : null;
411
426
  const selfClosingSet = new Set(options.selfClosing || []);
@@ -418,11 +433,23 @@ function createJsxInlineRule(options) {
418
433
  return false;
419
434
  if (componentSet && !componentSet.has(parsed.tag))
420
435
  return false;
421
- if (silent)
436
+ // In silent mode (link-label scanning) the rule must still CONSUME the
437
+ // tag: returning true without moving pos threw "inline rule didn't
438
+ // increment state.pos" for any JSX inside `[label](url)`.
439
+ if (silent) {
440
+ state.pos += parsed.length;
422
441
  return true;
442
+ }
423
443
  const isSelfClosing = parsed.selfClosing || selfClosingSet.has(parsed.tag);
444
+ // Open inline tags in THIS inline run. A closer with no opener here (its
445
+ // opener sat in another paragraph) must not be nesting -1: markdown-it
446
+ // pops its delimiter stack on every -1, and an unmatched pop left it
447
+ // undefined, crashing the parse in balance_pairs.
448
+ const open = openInline.get(state) ?? 0;
424
449
  if (parsed.isClose) {
425
- const token = state.push("jsx_inline_close", parsed.tag, -1);
450
+ const token = state.push("jsx_inline_close", parsed.tag, open > 0 ? -1 : 0);
451
+ if (open > 0)
452
+ openInline.set(state, open - 1);
426
453
  token.markup = `</${parsed.tag}>`;
427
454
  }
428
455
  else if (isSelfClosing) {
@@ -431,6 +458,7 @@ function createJsxInlineRule(options) {
431
458
  }
432
459
  else {
433
460
  const token = state.push("jsx_inline_open", parsed.tag, 1);
461
+ openInline.set(state, open + 1);
434
462
  token.meta = { attrs: parsed.attrs };
435
463
  token.markup = `<${parsed.tag}>`;
436
464
  }
@@ -480,6 +508,13 @@ function createImportStripRule() {
480
508
  state.line = startLine + 1;
481
509
  return true;
482
510
  }
511
+ // Side-effect import (no binding): `import "./styles.module.css";`
512
+ if (text.match(/^import\s+["'][^"']*["']\s*;?\s*$/)) {
513
+ if (silent)
514
+ return true;
515
+ state.line = startLine + 1;
516
+ return true;
517
+ }
483
518
  if (text.match(/^import\s*\{/)) {
484
519
  if (silent)
485
520
  return true;
@@ -514,11 +549,34 @@ function createImportStripRule() {
514
549
  * Exported so consumers can call it before md.parse() if needed.
515
550
  */
516
551
  export function normalizeJsxLines(source) {
517
- // Split close tag + open/close tag: </X> <Y or </X> </Y
518
- let result = source.replace(/(<\/[A-Z]\w*\s*>)([ \t]+)(<\/?[A-Z])/g, "$1\n$3");
519
- // Split open tag end + next tag: ...> <Y (for <Tabs> <TabItem> pattern)
520
- result = result.replace(/(>)([ \t]+)(<\/?[A-Z]\w*[\s>])/g, "$1\n$3");
521
- return result;
552
+ // Line by line, and never inside a fence or a table row: a row is ONE
553
+ // line by definition, and splitting `</p> <Example>` out of a cell broke
554
+ // the table in two; a fence shows its JSX as code.
555
+ let fence = null;
556
+ return source
557
+ .split("\n")
558
+ .map((line) => {
559
+ const m = /^[ \t]*(`{3,}|~{3,})/.exec(line);
560
+ if (fence) {
561
+ if (m && m[1][0] === fence[0] && m[1].length >= fence.length && line.trim() === m[1])
562
+ fence = null;
563
+ return line;
564
+ }
565
+ // A backtick fence's info string cannot hold a backtick: "```x@y```"
566
+ // is an inline code span, and taking it for a fence switched this off
567
+ // for the rest of the document.
568
+ if (m && !(m[1][0] === "`" && line.slice(line.indexOf(m[1]) + m[1].length).includes("`"))) {
569
+ fence = m[1];
570
+ return line;
571
+ }
572
+ if (/^[ \t]*\|/.test(line))
573
+ return line;
574
+ // Split close tag + open/close tag: </X> <Y or </X> </Y
575
+ const once = line.replace(/(<\/[A-Z]\w*\s*>)([ \t]+)(<\/?[A-Z])/g, "$1\n$3");
576
+ // Split open tag end + next tag: ...> <Y (for <Tabs> <TabItem> pattern)
577
+ return once.replace(/(>)([ \t]+)(<\/?[A-Z]\w*[\s>])/g, "$1\n$3");
578
+ })
579
+ .join("\n");
522
580
  }
523
581
  // ── Plugin entry point ─────────────────────────────────────
524
582
  export default function mdxJsxPlugin(md, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/markdown-it-mdx-jsx",
3
- "version": "0.2.0-beta.13",
3
+ "version": "0.2.0-beta.130",
4
4
  "description": "markdown-it plugin that tokenizes JSX-style component tags inside Markdown — used by @dogsbay/format-starlight",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,13 +11,13 @@
11
11
  "README.md"
12
12
  ],
13
13
  "dependencies": {
14
- "markdown-it": "^14.1.0"
14
+ "markdown-it": "^14.3.0"
15
15
  },
16
16
  "devDependencies": {
17
- "@types/node": "^22.0.0",
18
17
  "@types/markdown-it": "^14.1.2",
19
- "typescript": "^5.8.0",
20
- "vitest": "^3.2.0"
18
+ "@types/node": "^22.20.1",
19
+ "typescript": "^5.9.3",
20
+ "vitest": "^4.1.10"
21
21
  },
22
22
  "license": "MIT",
23
23
  "repository": {