@tanstack/markdown 0.0.13 → 0.0.15

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/react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Fragment, createElement } from 'react';
2
2
  import { parseMarkdown } from './parser.js';
3
+ import { footnoteReferenceId } from './utils.js';
3
4
  export function Markdown({ children, ...options }) {
4
5
  return createElement(Fragment, null, renderMarkdownReact(children, options));
5
6
  }
@@ -10,14 +11,14 @@ export function renderMarkdownReact(input, options = {}) {
10
11
  export function renderBlockReact(node, options = {}, key) {
11
12
  switch (node.type) {
12
13
  case 'heading':
13
- return h(options, `h${node.depth}`, { key, ...(node.id ? { id: node.id } : {}), ...(node.framework ? { 'data-framework': node.framework } : {}) }, renderInlines(node.children, options), renderHeadingAnchorReact(node.id, options));
14
+ return h(options, `h${node.depth}`, { key, ...(node.id && { id: node.id }), ...(node.framework && { 'data-framework': node.framework }) }, renderInlines(node.children, options), renderHeadingAnchorReact(node.id, options));
14
15
  case 'paragraph':
15
16
  return h(options, 'p', { key }, renderInlines(node.children, options));
16
17
  case 'code':
17
18
  return renderCodeBlockReact(node, options, key);
18
19
  case 'list': {
19
20
  const tag = node.ordered ? 'ol' : 'ul';
20
- return h(options, tag, { key, ...(node.ordered && node.start && node.start !== 1 ? { start: node.start } : {}) }, node.items.map((item, index) => h(options, 'li', { key: index }, renderListItemChildrenReact(item.children, item.checked, node.loose, options, `${index}`))));
21
+ return h(options, tag, { key, ...(node.ordered && node.start !== undefined && node.start !== 1 && { start: node.start }) }, node.items.map((item, index) => h(options, 'li', { key: index }, renderListItemChildrenReact(item.children, item.checked, node.loose, options, `${index}`))));
21
22
  }
22
23
  case 'blockquote':
23
24
  return h(options, 'blockquote', { key }, node.children.map((child, index) => renderBlockReact(child, options, `${key}:${index}`)));
@@ -53,21 +54,23 @@ export function renderInlineReact(node, options = {}, key) {
53
54
  return h(options, 'del', { key }, renderInlines(node.children, options));
54
55
  case 'footnoteReference':
55
56
  return h(options, 'sup', { key }, h(options, 'a', {
56
- id: `user-content-fnref-${footnoteReferenceId(node)}`,
57
+ id: `user-content-fnref-${footnoteReferenceId(node.id, node.referenceIndex)}`,
57
58
  'data-footnote-ref': '',
58
59
  'aria-describedby': 'footnote-label',
59
60
  href: `#user-content-fn-${node.id}`,
60
61
  }, node.number));
61
62
  case 'link':
62
- return h(options, 'a', { key, href: node.href, ...(node.title ? { title: node.title } : {}) }, renderInlines(node.children, options));
63
+ return h(options, 'a', { key, href: node.href, ...(node.title && { title: node.title }) }, renderInlines(node.children, options));
63
64
  case 'image':
64
- return h(options, 'img', { key, src: node.src, alt: node.alt, ...(node.title ? { title: node.title } : {}) });
65
+ return h(options, 'img', { key, src: node.src, alt: node.alt, ...(node.title && { title: node.title }) });
65
66
  case 'break':
66
67
  return h(options, 'br', { key });
67
68
  case 'inlineHtml':
68
69
  return options.allowHtml
69
70
  ? h(options, 'span', { key, dangerouslySetInnerHTML: { __html: node.value } })
70
71
  : node.value;
72
+ case 'inlineComponent':
73
+ return renderComponentReact(node, options, key);
71
74
  }
72
75
  }
73
76
  function renderInlines(nodes, options) {
@@ -76,14 +79,17 @@ function renderInlines(nodes, options) {
76
79
  function renderCodeBlockReact(node, options, key) {
77
80
  const lang = node.lang ?? 'plaintext';
78
81
  const highlighter = options.highlighter;
79
- const codeProps = {
80
- className: `language-${lang}`,
81
- };
82
- const content = highlighter ? undefined : node.value;
82
+ // Keep completed groups of lines in stable text nodes as code grows. Grouping
83
+ // bounds React's sibling work without relaying out the entire code block.
84
+ // Static rendering and custom code components retain string children.
85
+ const content = highlighter ? undefined : !options.components?.code && options.extensions?.some(extension => extension.name === 'streaming')
86
+ ? node.value.match(/(?:[^\n]*\n){1,4}|[^\n]+$/g)
87
+ : node.value;
83
88
  const highlighted = highlighter
84
89
  ? {
85
90
  dangerouslySetInnerHTML: {
86
91
  __html: highlighter(node.value, lang, {
92
+ ...(node.meta && { meta: node.meta }),
87
93
  ...(node.highlightLines && { highlightLines: node.highlightLines }),
88
94
  ...(options.codeLineNumbers !== undefined && { lineNumbers: options.codeLineNumbers }),
89
95
  }),
@@ -91,19 +97,20 @@ function renderCodeBlockReact(node, options, key) {
91
97
  }
92
98
  : undefined;
93
99
  const pre = h(options, 'pre', {
100
+ key,
94
101
  className: `tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}`,
95
102
  'data-lang': lang,
96
- ...(node.title ? { 'data-code-title': node.title } : {}),
97
- ...(node.file ? { 'data-filename': node.file } : {}),
98
- ...(node.framework ? { 'data-framework': node.framework } : {}),
99
- }, h(options, 'code', { ...codeProps, ...highlighted }, content));
103
+ ...(node.meta && { 'data-meta': node.meta }),
104
+ ...(node.title && { 'data-code-title': node.title }),
105
+ ...(node.file && { 'data-filename': node.file }),
106
+ ...(node.framework && { 'data-framework': node.framework }),
107
+ }, h(options, 'code', { className: `language-${lang}`, ...highlighted }, content));
100
108
  if (!node.title)
101
- return h(options, Fragment, { key }, pre);
109
+ return pre;
102
110
  return h(options, 'figure', { key, className: 'tm-code-frame', 'data-lang': lang }, h(options, 'figcaption', null, node.title), pre);
103
111
  }
104
112
  function renderListItemChildrenReact(children, checked, loose, options, key) {
105
- const [first, ...rest] = children;
106
- const task = checked === undefined
113
+ let result = checked === undefined
107
114
  ? []
108
115
  : [
109
116
  h(options, 'input', {
@@ -115,20 +122,21 @@ function renderListItemChildrenReact(children, checked, loose, options, key) {
115
122
  }),
116
123
  ' ',
117
124
  ];
118
- if (first?.type === 'paragraph') {
119
- const content = [...task, ...renderInlines(first.children, options)];
120
- return [
121
- ...(loose ? [h(options, 'p', { key: `${key}:paragraph` }, content)] : content),
122
- ...rest.flatMap((child, childIndex) => renderListChildReact(child, loose, options, `${key}:${childIndex + 1}`)),
123
- ];
125
+ for (let index = 0; index < children.length; index++) {
126
+ const child = children[index];
127
+ if (child.type === 'paragraph' && (!loose || index === 0)) {
128
+ for (const inline of renderInlines(child.children, options))
129
+ result.push(inline);
130
+ if (loose)
131
+ result = [h(options, 'p', { key: `${key}:paragraph` }, result)];
132
+ }
133
+ else
134
+ result.push(renderBlockReact(child, options, `${key}:${index}`));
124
135
  }
125
- return [...task, ...children.flatMap((child, childIndex) => renderListChildReact(child, loose, options, `${key}:${childIndex}`))];
126
- }
127
- function renderListChildReact(child, loose, options, key) {
128
- return !loose && child.type === 'paragraph' ? renderInlines(child.children, options) : [renderBlockReact(child, options, key)];
136
+ return result;
129
137
  }
130
138
  function renderTableCellReact(tag, cell, align, options, key) {
131
- return h(options, tag, { key, ...(align ? { style: { textAlign: align } } : {}) }, renderInlines(cell.children, options));
139
+ return h(options, tag, { key, ...(align && { style: { textAlign: align } }) }, renderInlines(cell.children, options));
132
140
  }
133
141
  function renderFootnotesReact(items, options, key) {
134
142
  return h(options, 'section', { key, 'data-footnotes': '', className: 'footnotes' }, h(options, 'h2', { id: 'footnote-label', className: 'sr-only' }, 'Footnotes', renderHeadingAnchorReact('footnote-label', options)), h(options, 'ol', null, items.map(item => h(options, 'li', { key: item.id, id: `user-content-fn-${item.id}` }, renderFootnoteItemReact(item, options)))));
@@ -136,19 +144,20 @@ function renderFootnotesReact(items, options, key) {
136
144
  function renderFootnoteItemReact(item, options) {
137
145
  const lastIndex = item.children.length - 1;
138
146
  const backrefs = renderFootnoteBackrefsReact(item, options);
139
- if (lastIndex < 0)
140
- return [h(options, 'p', { key: 'backref-wrapper' }, backrefs.slice(1))];
141
- return item.children.map((child, index) => {
147
+ const result = item.children.map((child, index) => {
142
148
  if (index === lastIndex && child.type === 'paragraph') {
143
149
  return h(options, 'p', { key: index }, renderInlines(child.children, options), backrefs);
144
150
  }
145
151
  return renderBlockReact(child, options, `${index}`);
146
152
  });
153
+ if (item.children[lastIndex]?.type !== 'paragraph')
154
+ result.push(h(options, 'p', { key: 'backref-wrapper' }, backrefs.slice(1)));
155
+ return result;
147
156
  }
148
157
  function renderFootnoteBackrefsReact(item, options) {
149
158
  const result = [];
150
159
  for (let index = 1; index <= (item.referenceCount ?? 1); index++) {
151
- const referenceId = index === 1 ? item.id : `${item.id}-${index}`;
160
+ const referenceId = footnoteReferenceId(item.id, index);
152
161
  const label = index === 1 ? `${item.number}` : `${item.number}-${index}`;
153
162
  result.push(' ', h(options, 'a', {
154
163
  key: index,
@@ -160,24 +169,23 @@ function renderFootnoteBackrefsReact(item, options) {
160
169
  }
161
170
  return result;
162
171
  }
163
- function footnoteReferenceId(node) {
164
- return node.referenceIndex && node.referenceIndex > 1 ? `${node.id}-${node.referenceIndex}` : node.id;
165
- }
166
172
  function h(options, tag, props, ...children) {
167
173
  const component = typeof tag === 'string' ? options.components?.[tag] ?? tag : tag;
168
174
  return createElement(component, props, ...children);
169
175
  }
170
176
  function renderComponentReact(node, options, key) {
171
- const tag = node.tagName ?? 'md-comment-component';
177
+ const tag = node.tagName ?? (node.type === 'inlineComponent' ? 'span' : 'md-comment-component');
172
178
  const props = {
173
- ...(node.properties ?? {}),
179
+ ...node.properties,
174
180
  };
175
181
  if (!node.tagName) {
176
182
  props['data-component'] = node.name;
177
183
  if (!props['data-attributes'])
178
184
  props['data-attributes'] = JSON.stringify(node.attributes);
179
185
  }
180
- return h(options, tag, { key, ...props }, node.children.map((child, index) => renderBlockReact(child, options, `${key}:${index}`)));
186
+ return h(options, tag, { key, ...props }, node.type === 'inlineComponent'
187
+ ? renderInlines(node.children, options)
188
+ : node.children.map((child, index) => renderBlockReact(child, options, `${key}:${index}`)));
181
189
  }
182
190
  function renderHeadingAnchorReact(id, options) {
183
191
  if (!id || !options.headingAnchors)
package/dist/types.d.ts CHANGED
@@ -84,7 +84,11 @@ export interface ComponentNode {
84
84
  tagName?: string;
85
85
  properties?: Record<string, string>;
86
86
  }
87
- export type InlineNode = TextNode | CodeSpanNode | StrongNode | EmphasisNode | StrikeNode | FootnoteReferenceNode | LinkNode | ImageNode | BreakNode | HtmlInlineNode;
87
+ export interface InlineComponentNode extends Omit<ComponentNode, 'type' | 'children'> {
88
+ type: 'inlineComponent';
89
+ children: InlineNode[];
90
+ }
91
+ export type InlineNode = TextNode | CodeSpanNode | StrongNode | EmphasisNode | StrikeNode | FootnoteReferenceNode | LinkNode | ImageNode | BreakNode | HtmlInlineNode | InlineComponentNode;
88
92
  export interface TextNode {
89
93
  type: 'text';
90
94
  value: string;
@@ -158,6 +162,7 @@ export interface HtmlRenderContext {
158
162
  }
159
163
  export interface ParseOptions {
160
164
  allowHtml?: boolean;
165
+ urlTransform?: UrlTransform;
161
166
  frontmatter?: boolean;
162
167
  headingIds?: boolean | ((text: string, index: number) => string);
163
168
  extensions?: MarkdownExtension[];
@@ -166,6 +171,9 @@ export interface ParseOptions {
166
171
  footnoteOrder?: string[];
167
172
  footnoteCounts?: Record<string, number>;
168
173
  }
174
+ export interface UrlTransform {
175
+ (url: string, kind: 'link' | 'image', defaultUrl: string): string | null;
176
+ }
169
177
  export interface RenderOptions extends ParseOptions {
170
178
  highlighter?: CodeHighlighter;
171
179
  codeLineNumbers?: boolean;
@@ -175,6 +183,7 @@ export interface CodeHighlighter {
175
183
  (code: string, lang?: string, options?: CodeHighlightOptions): string;
176
184
  }
177
185
  export interface CodeHighlightOptions {
186
+ meta?: string;
178
187
  highlightLines?: number[];
179
188
  lineNumbers?: boolean;
180
189
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.ts CHANGED
@@ -1,13 +1,17 @@
1
- import type { InlineNode } from './types.js';
1
+ import type { InlineNode, LinkReferenceDefinition } from './types.js';
2
2
  export declare function normalizeInput(value: string): string;
3
3
  export declare function escapeHtml(value: string): string;
4
4
  export declare function escapeAttr(value: string): string;
5
5
  export declare function isBlank(value: string): boolean;
6
6
  export declare function stripIndent(value: string, size: number): string;
7
7
  export declare function plainText(nodes: InlineNode[]): string;
8
- export declare function createSlugger(): (value: string) => string;
8
+ export declare function createSlugger(normalize?: typeof headingSlug): (value: string) => string;
9
+ declare function headingSlug(value: string): string;
9
10
  export declare function sanitizeUrl(value: string): string;
11
+ export declare function parseDestination(value: string): LinkReferenceDefinition | undefined;
10
12
  export declare function normalizeReferenceLabel(value: string): string;
11
13
  export declare function footnoteId(label: string): string;
14
+ export declare function footnoteReferenceId(id: string, index?: number): string;
12
15
  export declare function splitLines(value: string): string[];
16
+ export {};
13
17
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAU5C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAQrD;AAED,wBAAgB,aAAa,KAGnB,OAAO,MAAM,YActB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAIlD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAarE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAQrD;AAED,wBAAgB,aAAa,CAAC,SAAS,qBAAc,IAG3C,OAAO,MAAM,YAStB;AAED,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ1C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAMnF;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,CAEjE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAIlD"}
package/dist/utils.js CHANGED
@@ -4,15 +4,17 @@ const htmlEscapes = {
4
4
  '>': '&gt;',
5
5
  '"': '&quot;',
6
6
  "'": '&#39;',
7
+ '`': '&#96;',
7
8
  };
9
+ const escapeCharacter = (char) => htmlEscapes[char];
8
10
  export function normalizeInput(value) {
9
11
  return value.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
10
12
  }
11
13
  export function escapeHtml(value) {
12
- return value.replace(/[&<>"']/g, char => htmlEscapes[char]);
14
+ return value.replace(/[&<>"']/g, escapeCharacter);
13
15
  }
14
16
  export function escapeAttr(value) {
15
- return escapeHtml(value).replace(/`/g, '&#96;');
17
+ return value.replace(/[&<>"'`]/g, escapeCharacter);
16
18
  }
17
19
  export function isBlank(value) {
18
20
  return /^\s*$/.test(value);
@@ -35,40 +37,50 @@ export function plainText(nodes) {
35
37
  }
36
38
  return value;
37
39
  }
38
- export function createSlugger() {
40
+ export function createSlugger(normalize = headingSlug) {
39
41
  const seen = new Map();
40
42
  return (value) => {
41
- const base = value
42
- .toLowerCase()
43
- .normalize('NFKD')
44
- .replace(/[\u0300-\u036f]/g, '')
45
- .replace(/&[a-z0-9#]+;/gi, '')
46
- .replace(/[^a-z0-9]+/g, '-')
47
- .replace(/^-+|-+$/g, '') || 'section';
48
- const count = seen.get(base) ?? 0;
49
- seen.set(base, count + 1);
50
- return count === 0 ? base : `${base}-${count + 1}`;
43
+ const base = normalize(value);
44
+ let count = seen.get(base) ?? 1;
45
+ let id = base;
46
+ while (seen.has(id))
47
+ id = `${base}-${++count}`;
48
+ seen.set(base, count);
49
+ seen.set(id, 1);
50
+ return id;
51
51
  };
52
52
  }
53
+ function headingSlug(value) {
54
+ return value
55
+ .toLowerCase()
56
+ .normalize('NFKD')
57
+ .replace(/[\u0300-\u036f]/g, '')
58
+ .replace(/&[a-z0-9#]+;/gi, '')
59
+ .replace(/[^a-z0-9]+/g, '-')
60
+ .replace(/^-+|-+$/g, '') || 'section';
61
+ }
53
62
  export function sanitizeUrl(value) {
54
- const trimmed = value.trim().replace(/[\u0000-\u001F\u007F\s]+/g, '');
55
- if (!trimmed)
56
- return '';
57
- if (/^(#|\/|\.\/|\.\.\/)/.test(trimmed))
58
- return trimmed;
59
- if (/^(https?:|mailto:|tel:)/i.test(trimmed))
60
- return trimmed;
61
- if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed))
62
- return '';
63
- return trimmed;
63
+ const trimmed = value.replace(/[\u0000-\u001F\u007F\s]+/g, '');
64
+ return /^(?!https?:|mailto:|tel:)[a-z][a-z0-9+.-]*:/i.test(trimmed) ? '' : trimmed;
65
+ }
66
+ export function parseDestination(value) {
67
+ const match = value.match(/^(?:<([^<>\n]*)>|([^<>\s]*?))(?:\s+(?:"([^"]*)"|'([^']*)'|\(([^)]*)\)))?$/);
68
+ if (!match)
69
+ return undefined;
70
+ const href = match[1] ?? match[2];
71
+ const title = match[3] ?? match[4] ?? match[5];
72
+ return { href: href.replace(/\\([!-/:-@\[-`{-~])/g, '$1'), ...(title ? { title } : {}) };
64
73
  }
65
74
  export function normalizeReferenceLabel(value) {
66
- return value.trim().toLowerCase();
75
+ return value.trim().replace(/\s+/g, ' ').toLowerCase();
67
76
  }
68
77
  export function footnoteId(label) {
69
78
  return normalizeReferenceLabel(label)
70
79
  .replace(/[^a-z0-9_-]+/g, '-')
71
- .replace(/^-+|-+$/g, '');
80
+ .replace(/^-+|-+$/g, '') || 'footnote';
81
+ }
82
+ export function footnoteReferenceId(id, index = 1) {
83
+ return index > 1 ? `${id}-${index}` : id;
72
84
  }
73
85
  export function splitLines(value) {
74
86
  const lines = value.split('\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/markdown",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "description": "A tiny, fast, deterministic Markdown renderer for blogs and documentation.",
6
6
  "license": "MIT",
@@ -86,6 +86,7 @@
86
86
  }
87
87
  },
88
88
  "devDependencies": {
89
+ "@changesets/cli": "3.0.2",
89
90
  "@tanstack/highlight": "0.0.6",
90
91
  "@tanstack/intent": "^0.3.6",
91
92
  "@types/node": "^24.0.0",
@@ -99,11 +100,14 @@
99
100
  "marked": "^18.0.5",
100
101
  "micromark": "^4.0.2",
101
102
  "octane": "0.1.12",
103
+ "playwright": "1.63.0",
102
104
  "react": "^19.0.0",
103
105
  "react-dom": "^19.0.0",
104
106
  "rehype-stringify": "^10.0.1",
105
107
  "remark-parse": "^11.0.0",
106
108
  "remark-rehype": "^11.1.2",
109
+ "streamdown": "2.6.0",
110
+ "streaming-markdown": "0.2.15",
107
111
  "tsx": "^4.20.0",
108
112
  "typescript": "^5.8.0",
109
113
  "unified": "^11.0.5",
@@ -116,6 +120,7 @@
116
120
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
117
121
  "typecheck": "tsc --noEmit",
118
122
  "test": "vitest run",
123
+ "test:streaming-browser": "tsx scripts/verify-streaming-browser.ts",
119
124
  "test:corpus": "vitest run tests/corpus.test.tsx",
120
125
  "corpus:audit": "tsx scripts/audit-corpus.ts",
121
126
  "corpus:audit:tanstack": "tsx scripts/audit-tanstack-corpus.ts",
@@ -125,8 +130,13 @@
125
130
  "skills:check-version": "node scripts/sync-skill-version.mjs --check",
126
131
  "skills:sync-version": "node scripts/sync-skill-version.mjs",
127
132
  "skills:stale": "intent stale",
133
+ "changeset": "changeset",
134
+ "changeset:status": "changeset status",
135
+ "release:version": "changeset version && pnpm run skills:sync-version",
136
+ "release:publish": "pnpm run verify && changeset publish",
128
137
  "conformance": "tsx scripts/conformance.ts",
129
138
  "bench": "tsx scripts/bench.ts",
139
+ "bench:streaming": "tsx scripts/bench-streaming-browser.ts",
130
140
  "size": "tsx scripts/measure-size.ts",
131
141
  "research": "pnpm run conformance && pnpm run size && pnpm run bench",
132
142
  "verify": "pnpm test && pnpm run typecheck && pnpm run build && pnpm run docs:verify && pnpm run test:skills && pnpm run research && pnpm pack --dry-run",
@@ -2,12 +2,12 @@
2
2
  name: 'custom-extensions'
3
3
  description: >
4
4
  Implement MarkdownExtension block parsers, inline and document transforms,
5
- HTML hooks, and portable ComponentNode output. Load when adding deterministic
5
+ HTML hooks, and portable block and inline component output. Load when adding deterministic
6
6
  custom syntax or rendering behavior across HTML, React, and Octane.
7
7
  metadata:
8
8
  type: core
9
9
  library: '@tanstack/markdown'
10
- library_version: '0.0.13'
10
+ library_version: '0.0.15'
11
11
  requires:
12
12
  - 'render-markdown'
13
13
  sources:
@@ -111,6 +111,13 @@ console.log(html)
111
111
 
112
112
  Transforms receive built-in inline nodes and must return a deterministic replacement array.
113
113
 
114
+ For custom inline UI, return an `InlineComponentNode` with `type: 'inlineComponent'`,
115
+ `name`, `attributes`, inline `children`, and optional `tagName` and string `properties`.
116
+ It uses the same emitted-tag component maps as a block `ComponentNode`, but defaults
117
+ to `<span>` instead of `<md-comment-component>`. Keep tag and property names under
118
+ extension control, use phrasing content, and recurse through inline `children` when
119
+ transforming text nested inside links or emphasis. This does not require `allowHtml`.
120
+
114
121
  ### Derive document metadata after parsing
115
122
 
116
123
  ```ts
@@ -370,7 +377,7 @@ export function Article() {
370
377
  }
371
378
  ```
372
379
 
373
- `renderHtml` hooks do not run in React or Octane; a `ComponentNode` and emitted-tag component mapping is the portable path.
380
+ `renderHtml` hooks do not run in React or Octane; a `ComponentNode` or `InlineComponentNode` and emitted-tag component mapping is the portable path.
374
381
 
375
382
  Source: `docs/guides/extensions.md`
376
383
 
@@ -425,7 +432,7 @@ Source: `docs/guides/extensions.md`
425
432
 
426
433
  ### HIGH Rich output versus untrusted-content safety
427
434
 
428
- Prefer `ComponentNode` plus application components for rich output. Treat `allowHtml`, extension HTML strings, and highlighter markup as explicit trusted boundaries; see `production-pipelines`.
435
+ Prefer block or inline component nodes plus application components for rich output. Treat `allowHtml`, extension HTML strings, and highlighter markup as explicit trusted boundaries; see `production-pipelines`.
429
436
 
430
437
  ### MEDIUM Parse-ahead performance versus option timing
431
438
 
@@ -9,7 +9,7 @@ description: >
9
9
  metadata:
10
10
  type: core
11
11
  library: '@tanstack/markdown'
12
- library_version: '0.0.13'
12
+ library_version: '0.0.15'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -317,4 +317,4 @@ interface CodeBlockNode {
317
317
  - `{2,4-6}` and `lines=2,4-6` produce sorted, unique positive line numbers.
318
318
  - Invalid ranges are ignored, and each individual range expands to at most 1,000 lines.
319
319
 
320
- HTML code blocks expose `data-lang`, `data-code-title`, `data-filename`, and `data-framework` when the corresponding values exist. Highlight lines are passed to a configured highlighter; metadata alone does not create token or line markup.
320
+ HTML, React, and Octane code blocks expose `data-lang`, `data-meta`, `data-code-title`, `data-filename`, and `data-framework` on `<pre>` when the corresponding values exist. Framework `pre` replacements can read `props['data-meta']`; highlighters receive the raw metadata in `options.meta` alongside highlight lines. Metadata alone does not create token or line markup.
@@ -9,7 +9,7 @@ metadata:
9
9
  type: framework
10
10
  library: '@tanstack/markdown'
11
11
  framework: 'octane'
12
- library_version: '0.0.13'
12
+ library_version: '0.0.15'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -8,7 +8,7 @@ description: >
8
8
  metadata:
9
9
  type: lifecycle
10
10
  library: '@tanstack/markdown'
11
- library_version: '0.0.13'
11
+ library_version: '0.0.15'
12
12
  requires:
13
13
  - 'render-markdown'
14
14
  sources:
@@ -127,7 +127,7 @@ import { parseMarkdown } from '@tanstack/markdown/parser'
127
127
  const cache = new Map<string, MarkdownDocument>()
128
128
 
129
129
  export function renderCachedArticle(key: string, source: string): string {
130
- const cacheKey = `markdown-0.0.13:${key}`
130
+ const cacheKey = `markdown-0.0.15:${key}`
131
131
  let document = cache.get(cacheKey)
132
132
  if (!document) {
133
133
  document = parseMarkdown(source, {
@@ -331,6 +331,14 @@ export function renderWithPolicy(
331
331
 
332
332
  Core escaping and protocol filtering do not enforce application-specific outbound-link, image, or final-HTML policy.
333
333
 
334
+ For parsed Markdown destinations, `urlTransform(url, kind, defaultUrl)` can return
335
+ the screened default, a trusted replacement, or `null` to remove the link or image
336
+ while retaining its label content. Only allow data images after application validation
337
+ of their content, MIME type, and size. Keep the default policy for other destinations.
338
+ Callback results are not screened again, and the callback does not apply to raw HTML,
339
+ extension-created URLs, or ASTs supplied directly to renderers. Do not enable raw HTML
340
+ to allow images. Include URL-policy changes in persisted AST cache invalidation.
341
+
334
342
  Source: `docs/core-concepts/security.md`
335
343
 
336
344
  ### HIGH Assuming complete CommonMark behavior
@@ -9,7 +9,7 @@ metadata:
9
9
  type: framework
10
10
  library: '@tanstack/markdown'
11
11
  framework: 'react'
12
- library_version: '0.0.13'
12
+ library_version: '0.0.15'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -9,7 +9,7 @@ description: >
9
9
  metadata:
10
10
  type: core
11
11
  library: '@tanstack/markdown'
12
- library_version: '0.0.13'
12
+ library_version: '0.0.15'
13
13
  sources:
14
14
  - 'TanStack/markdown:docs/quick-start.md'
15
15
  - 'TanStack/markdown:docs/core-concepts/document-model.md'
@@ -1,6 +1,6 @@
1
1
  # AST and Options Reference
2
2
 
3
- This reference targets `@tanstack/markdown@0.0.13`. Shared public types are
3
+ This reference targets `@tanstack/markdown@0.0.15`. Shared public types are
4
4
  exported from `@tanstack/markdown`.
5
5
 
6
6
  ## Entry Points
@@ -190,10 +190,11 @@ Important rendering invariants:
190
190
  | `emphasis` | `EmphasisNode` | inline `children` |
191
191
  | `strike` | `StrikeNode` | inline `children` |
192
192
  | `footnoteReference` | `FootnoteReferenceNode` | normalized `id`, display `number`, optional `referenceIndex` |
193
- | `link` | `LinkNode` | sanitized `href`, optional `title`, inline `children` |
194
- | `image` | `ImageNode` | sanitized `src`, text `alt`, optional `title` |
193
+ | `link` | `LinkNode` | policy-processed `href`, optional `title`, inline `children` |
194
+ | `image` | `ImageNode` | policy-processed `src`, text `alt`, optional `title` |
195
195
  | `break` | `BreakNode` | no additional fields |
196
196
  | `inlineHtml` | `HtmlInlineNode` | raw `value` |
197
+ | `inlineComponent` | `InlineComponentNode` | `name`, `attributes`, inline `children`, optional `tagName`, optional string `properties` |
197
198
 
198
199
  `HtmlInlineNode` is created only when parsing with `allowHtml: true`.
199
200
  Repeated footnote references use `referenceIndex` to produce unique source
@@ -206,10 +207,12 @@ import type {
206
207
  FootnoteDefinition,
207
208
  LinkReferenceDefinition,
208
209
  MarkdownExtension,
210
+ UrlTransform,
209
211
  } from '@tanstack/markdown'
210
212
 
211
213
  interface ParseOptions {
212
214
  allowHtml?: boolean
215
+ urlTransform?: UrlTransform
213
216
  frontmatter?: boolean
214
217
  headingIds?: boolean | ((text: string, index: number) => string)
215
218
  extensions?: MarkdownExtension[]
@@ -223,6 +226,7 @@ interface ParseOptions {
223
226
  | Option | Default | Contract |
224
227
  | --- | --- | --- |
225
228
  | `allowHtml` | `false` | Recognize raw block and inline HTML nodes. Rendering also requires this option to emit their raw values. |
229
+ | `urlTransform` | built-in policy | Synchronous `(url, kind, defaultUrl) => string \| null`; return the screened default, a trusted replacement, or `null` to keep only label content. Applies to Markdown destinations, not raw HTML or supplied ASTs. |
226
230
  | `frontmatter` | `true` | Extract one leading `---` block into `document.frontmatter`. The library does not parse YAML. |
227
231
  | `headingIds` | `true` | Generate duplicate-safe IDs, disable IDs with `false`, or return an ID from `(text, normalizedLineIndex)`. |
228
232
  | `extensions` | `[]` | Run block parsers and inline/document transforms in array order. |
@@ -259,6 +263,7 @@ interface HeadingAnchorOptions {
259
263
  }
260
264
 
261
265
  interface CodeHighlightOptions {
266
+ meta?: string
262
267
  highlightLines?: number[]
263
268
  lineNumbers?: boolean
264
269
  }