@tanstack/markdown 0.0.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.
Files changed (44) hide show
  1. package/README.md +36 -0
  2. package/dist/extensions/callouts.d.ts +4 -0
  3. package/dist/extensions/callouts.d.ts.map +1 -0
  4. package/dist/extensions/callouts.js +39 -0
  5. package/dist/extensions/comment-components.d.ts +14 -0
  6. package/dist/extensions/comment-components.d.ts.map +1 -0
  7. package/dist/extensions/comment-components.js +68 -0
  8. package/dist/extensions/docs.d.ts +8 -0
  9. package/dist/extensions/docs.d.ts.map +1 -0
  10. package/dist/extensions/docs.js +25 -0
  11. package/dist/extensions/framework.d.ts +3 -0
  12. package/dist/extensions/framework.d.ts.map +1 -0
  13. package/dist/extensions/framework.js +40 -0
  14. package/dist/extensions/headings.d.ts +7 -0
  15. package/dist/extensions/headings.d.ts.map +1 -0
  16. package/dist/extensions/headings.js +51 -0
  17. package/dist/extensions/shared.d.ts +12 -0
  18. package/dist/extensions/shared.d.ts.map +1 -0
  19. package/dist/extensions/shared.js +92 -0
  20. package/dist/extensions/tabs.d.ts +7 -0
  21. package/dist/extensions/tabs.d.ts.map +1 -0
  22. package/dist/extensions/tabs.js +145 -0
  23. package/dist/html.d.ts +6 -0
  24. package/dist/html.d.ts.map +1 -0
  25. package/dist/html.js +157 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +3 -0
  29. package/dist/inline.d.ts +3 -0
  30. package/dist/inline.d.ts.map +1 -0
  31. package/dist/inline.js +227 -0
  32. package/dist/parser.d.ts +3 -0
  33. package/dist/parser.d.ts.map +1 -0
  34. package/dist/parser.js +349 -0
  35. package/dist/react.d.ts +15 -0
  36. package/dist/react.d.ts.map +1 -0
  37. package/dist/react.js +129 -0
  38. package/dist/types.d.ts +172 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +1 -0
  41. package/dist/utils.d.ts +11 -0
  42. package/dist/utils.d.ts.map +1 -0
  43. package/dist/utils.js +70 -0
  44. package/package.json +94 -0
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # TanStack Markdown
2
+
3
+ A tiny deterministic TanStack-flavored Markdown renderer.
4
+
5
+ ```bash
6
+ pnpm add @tanstack/markdown
7
+ ```
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ pnpm test
13
+ pnpm run typecheck
14
+ pnpm run build
15
+ pnpm run research
16
+ ```
17
+
18
+ ## Entry Points
19
+
20
+ - `src/parser.ts`: Markdown subset parser to a serializable AST
21
+ - `src/html.ts`: HTML string renderer
22
+ - `src/react.ts`: React adapter
23
+ - `src/extensions/*`: optional docs-site extension functions
24
+ - `scripts/measure-size.ts`: bundle size matrix
25
+ - `scripts/bench.ts`: CPU benchmark matrix
26
+
27
+ ## Reports
28
+
29
+ - [Bundle sizes](./reports/sizes.md)
30
+ - [Benchmarks](./reports/benchmarks.md)
31
+ - [Audit](./docs/audit.md)
32
+ - [Spec and API](./docs/spec.md)
33
+ - [Goal and coverage map](./docs/goal.md)
34
+ - [Bundle strategy](./docs/bundle-strategy.md)
35
+ - [Extensions](./docs/extensions.md)
36
+ - [Recommendation](./docs/recommendation.md)
@@ -0,0 +1,4 @@
1
+ import type { CalloutNode, MarkdownExtension } from '../types.js';
2
+ export declare function calloutsExtension(): MarkdownExtension;
3
+ export declare function parseCalloutBlock(context: Parameters<NonNullable<MarkdownExtension['parseBlock']>>[0]): CalloutNode | undefined;
4
+ //# sourceMappingURL=callouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callouts.d.ts","sourceRoot":"","sources":["../../src/extensions/callouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEjE,wBAAgB,iBAAiB,IAAI,iBAAiB,CAKrD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,GAAG,SAAS,CA+B/H"}
@@ -0,0 +1,39 @@
1
+ export function calloutsExtension() {
2
+ return {
3
+ name: 'callouts',
4
+ parseBlock: parseCalloutBlock,
5
+ };
6
+ }
7
+ export function parseCalloutBlock(context) {
8
+ const first = context.lines[context.index] ?? '';
9
+ const match = first.match(/^ {0,3}>\s*\[!([A-Za-z]+)\](?:\s+(.*))?$/);
10
+ if (!match)
11
+ return undefined;
12
+ const kind = match[1].toLowerCase();
13
+ const title = match[2]?.trim() || titleCase(kind);
14
+ const body = [];
15
+ let cursor = context.index + 1;
16
+ while (cursor < context.lines.length) {
17
+ const line = context.lines[cursor] ?? '';
18
+ if (/^\s*$/.test(line)) {
19
+ body.push('');
20
+ cursor++;
21
+ continue;
22
+ }
23
+ const quoted = line.match(/^ {0,3}>\s?(.*)$/);
24
+ if (!quoted)
25
+ break;
26
+ body.push(quoted[1]);
27
+ cursor++;
28
+ }
29
+ context.consume(cursor - context.index);
30
+ return {
31
+ type: 'callout',
32
+ kind,
33
+ title,
34
+ children: context.parseBlocks(body.join('\n')),
35
+ };
36
+ }
37
+ function titleCase(value) {
38
+ return value.slice(0, 1).toUpperCase() + value.slice(1).toLowerCase();
39
+ }
@@ -0,0 +1,14 @@
1
+ import type { ComponentNode, MarkdownExtension } from '../types.js';
2
+ export interface CommentComponentOptions {
3
+ transformComponent?: (node: ComponentNode) => ComponentNode;
4
+ }
5
+ export interface ComponentComment {
6
+ block: boolean;
7
+ name: string;
8
+ attributes: Record<string, string>;
9
+ }
10
+ export declare function commentComponentsExtension(options?: CommentComponentOptions): MarkdownExtension;
11
+ export declare function parseCommentComponentBlock(context: Parameters<NonNullable<MarkdownExtension['parseBlock']>>[0], options?: CommentComponentOptions): ComponentNode | undefined;
12
+ export declare function parseComponentComment(line: string): ComponentComment | undefined;
13
+ export declare function parseAttributes(value: string): Record<string, string>;
14
+ //# sourceMappingURL=comment-components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-components.d.ts","sourceRoot":"","sources":["../../src/extensions/comment-components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEnE,MAAM,WAAW,uBAAuB;IACtC,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,aAAa,CAAA;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,uBAA4B,GAAG,iBAAiB,CAKnG;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACpE,OAAO,GAAE,uBAA4B,GACpC,aAAa,GAAG,SAAS,CA2C3B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAQhF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrE"}
@@ -0,0 +1,68 @@
1
+ export function commentComponentsExtension(options = {}) {
2
+ return {
3
+ name: 'comment-components',
4
+ parseBlock: context => parseCommentComponentBlock(context, options),
5
+ };
6
+ }
7
+ export function parseCommentComponentBlock(context, options = {}) {
8
+ const line = context.lines[context.index] ?? '';
9
+ const start = parseComponentComment(line);
10
+ if (!start)
11
+ return undefined;
12
+ if (!start.block) {
13
+ context.consume(1);
14
+ return transform({
15
+ type: 'component',
16
+ name: start.name,
17
+ attributes: start.attributes,
18
+ children: [],
19
+ }, options);
20
+ }
21
+ const body = [];
22
+ let cursor = context.index + 1;
23
+ let foundEnd = false;
24
+ while (cursor < context.lines.length) {
25
+ const candidate = context.lines[cursor] ?? '';
26
+ if (isEndComment(candidate, start.name)) {
27
+ foundEnd = true;
28
+ cursor++;
29
+ break;
30
+ }
31
+ body.push(candidate);
32
+ cursor++;
33
+ }
34
+ context.consume(foundEnd ? cursor - context.index : 1);
35
+ return transform({
36
+ type: 'component',
37
+ name: start.name,
38
+ attributes: start.attributes,
39
+ children: foundEnd ? context.parseBlocks(body.join('\n')) : [],
40
+ }, options);
41
+ }
42
+ export function parseComponentComment(line) {
43
+ const match = line.match(/^ {0,3}<!--\s*::(start:)?([A-Za-z][\w-]*)(.*?)\s*-->\s*$/);
44
+ if (!match)
45
+ return undefined;
46
+ return {
47
+ block: Boolean(match[1]),
48
+ name: match[2].toLowerCase(),
49
+ attributes: parseAttributes(match[3] ?? ''),
50
+ };
51
+ }
52
+ export function parseAttributes(value) {
53
+ const attrs = {};
54
+ const regex = /([A-Za-z_][\w:-]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
55
+ for (const match of value.matchAll(regex)) {
56
+ attrs[match[1]] = match[2] ?? match[3] ?? match[4] ?? 'true';
57
+ }
58
+ return attrs;
59
+ }
60
+ function transform(node, options) {
61
+ return options.transformComponent?.(node) ?? node;
62
+ }
63
+ function isEndComment(line, name) {
64
+ return new RegExp(`^ {0,3}<!--\\s*::end:${escapeRegex(name)}\\s*-->\\s*$`, 'i').test(line);
65
+ }
66
+ function escapeRegex(value) {
67
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
68
+ }
@@ -0,0 +1,8 @@
1
+ import type { ComponentNode, MarkdownExtension } from '../types.js';
2
+ import { type HeadingCollectionOptions } from './headings.js';
3
+ export interface DocsMarkdownOptions {
4
+ collectHeadings?: boolean | HeadingCollectionOptions;
5
+ }
6
+ export declare function docsMarkdownExtensions(options?: DocsMarkdownOptions): MarkdownExtension[];
7
+ export declare function transformDocsComponent(node: ComponentNode): ComponentNode;
8
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/extensions/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAInE,OAAO,EAA8B,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAGzF,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAA;CACrD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,mBAAwB,GAAG,iBAAiB,EAAE,CAiB7F;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAKzE"}
@@ -0,0 +1,25 @@
1
+ import { calloutsExtension } from './callouts.js';
2
+ import { commentComponentsExtension } from './comment-components.js';
3
+ import { transformFrameworkComponent } from './framework.js';
4
+ import { headingCollectionExtension } from './headings.js';
5
+ import { transformTabsComponent } from './tabs.js';
6
+ export function docsMarkdownExtensions(options = {}) {
7
+ const extensions = [
8
+ calloutsExtension(),
9
+ commentComponentsExtension({
10
+ transformComponent: transformDocsComponent,
11
+ }),
12
+ ];
13
+ if (options.collectHeadings !== false) {
14
+ extensions.push(headingCollectionExtension(typeof options.collectHeadings === 'object' ? options.collectHeadings : {}));
15
+ }
16
+ return extensions;
17
+ }
18
+ export function transformDocsComponent(node) {
19
+ const name = node.name.toLowerCase();
20
+ if (name === 'tabs')
21
+ return transformTabsComponent(node);
22
+ if (name === 'framework')
23
+ return transformFrameworkComponent(node);
24
+ return node;
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { ComponentNode } from '../types.js';
2
+ export declare function transformFrameworkComponent(node: ComponentNode): ComponentNode;
3
+ //# sourceMappingURL=framework.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../src/extensions/framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3D,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAyC9E"}
@@ -0,0 +1,40 @@
1
+ import { markFrameworkHeadings, splitByHeading } from './shared.js';
2
+ export function transformFrameworkComponent(node) {
3
+ const sections = splitByHeading(node.children, 1);
4
+ if (!sections.length)
5
+ return node;
6
+ const frameworks = sections.map(section => section.name.toLowerCase());
7
+ const panels = sections.map((section) => {
8
+ const framework = section.name.toLowerCase();
9
+ return {
10
+ type: 'component',
11
+ name: 'framework-panel',
12
+ tagName: 'md-framework-panel',
13
+ attributes: {},
14
+ properties: {
15
+ 'data-framework': framework,
16
+ },
17
+ children: markFrameworkHeadings(section.children, framework),
18
+ };
19
+ });
20
+ return {
21
+ ...node,
22
+ properties: {
23
+ ...(node.properties ?? {}),
24
+ 'data-available-frameworks': JSON.stringify(frameworks),
25
+ 'data-framework-meta': JSON.stringify({
26
+ codeBlocksByFramework: Object.fromEntries(sections.map(section => [
27
+ section.name.toLowerCase(),
28
+ section.children
29
+ .filter((child) => child.type === 'code')
30
+ .map(child => ({
31
+ title: child.title ?? '',
32
+ code: child.value,
33
+ language: child.lang ?? 'plaintext',
34
+ })),
35
+ ])),
36
+ }),
37
+ },
38
+ children: panels,
39
+ };
40
+ }
@@ -0,0 +1,7 @@
1
+ import type { MarkdownDocument, MarkdownExtension, MarkdownHeading } from '../types.js';
2
+ export interface HeadingCollectionOptions {
3
+ skipComponentNames?: ReadonlySet<string> | string[];
4
+ }
5
+ export declare function headingCollectionExtension(options?: HeadingCollectionOptions): MarkdownExtension;
6
+ export declare function collectMarkdownHeadings(document: MarkdownDocument, options?: HeadingCollectionOptions): MarkdownHeading[];
7
+ //# sourceMappingURL=headings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.d.ts","sourceRoot":"","sources":["../../src/extensions/headings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAGlG,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;CACpD;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,wBAA6B,GAAG,iBAAiB,CAUpG;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE,wBAA6B,GAAG,eAAe,EAAE,CAK7H"}
@@ -0,0 +1,51 @@
1
+ import { plainText } from '../utils.js';
2
+ export function headingCollectionExtension(options = {}) {
3
+ return {
4
+ name: 'heading-collection',
5
+ transformDocument(document) {
6
+ return {
7
+ ...document,
8
+ headings: collectMarkdownHeadings(document, options),
9
+ };
10
+ },
11
+ };
12
+ }
13
+ export function collectMarkdownHeadings(document, options = {}) {
14
+ const headings = [];
15
+ const skip = new Set(options.skipComponentNames ?? ['tabs']);
16
+ collectFromBlocks(document.children, headings, undefined, false, skip);
17
+ return headings;
18
+ }
19
+ function collectFromBlocks(blocks, headings, framework, insideSkippedComponent, skipComponentNames) {
20
+ for (const block of blocks) {
21
+ if (block.type === 'heading') {
22
+ if (!insideSkippedComponent && block.id) {
23
+ const heading = {
24
+ id: block.id,
25
+ text: plainText(block.children),
26
+ level: block.depth,
27
+ };
28
+ const headingFramework = block.framework ?? framework;
29
+ if (headingFramework)
30
+ heading.framework = headingFramework;
31
+ headings.push(heading);
32
+ }
33
+ continue;
34
+ }
35
+ if (block.type === 'list') {
36
+ for (const item of block.items)
37
+ collectFromBlocks(item.children, headings, framework, insideSkippedComponent, skipComponentNames);
38
+ continue;
39
+ }
40
+ if (block.type === 'blockquote' || block.type === 'callout') {
41
+ collectFromBlocks(block.children, headings, framework, insideSkippedComponent, skipComponentNames);
42
+ continue;
43
+ }
44
+ if (block.type === 'component') {
45
+ const name = block.name.toLowerCase();
46
+ const nextInsideSkippedComponent = insideSkippedComponent || skipComponentNames.has(name);
47
+ const nextFramework = block.tagName === 'md-framework-panel' ? block.properties?.['data-framework'] ?? framework : framework;
48
+ collectFromBlocks(block.children, headings, nextFramework, nextInsideSkippedComponent, skipComponentNames);
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,12 @@
1
+ import type { BlockNode, ComponentNode } from '../types.js';
2
+ export interface HeadingSection {
3
+ id?: string;
4
+ name: string;
5
+ children: BlockNode[];
6
+ }
7
+ export declare function blocksToText(blocks: BlockNode[]): string;
8
+ export declare function splitByHeading(children: BlockNode[], forcedDepth?: number): HeadingSection[];
9
+ export declare function slugify(value: string, fallback: string): string;
10
+ export declare function markFrameworkHeadings(blocks: BlockNode[], framework: string): BlockNode[];
11
+ export declare function getComponentName(node: ComponentNode): string;
12
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/extensions/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAe,MAAM,aAAa,CAAA;AAGxE,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,SAAS,EAAE,CAAA;CACtB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAsBxD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAsB5F;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAWtD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAmCzF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,UAEnD"}
@@ -0,0 +1,92 @@
1
+ import { plainText } from '../utils.js';
2
+ export function blocksToText(blocks) {
3
+ return blocks
4
+ .map(block => {
5
+ switch (block.type) {
6
+ case 'paragraph':
7
+ case 'heading':
8
+ return plainText(block.children);
9
+ case 'code':
10
+ return block.value;
11
+ case 'list':
12
+ return block.items.map(item => blocksToText(item.children)).join('\n');
13
+ case 'blockquote':
14
+ case 'callout':
15
+ case 'component':
16
+ return blocksToText(block.children);
17
+ case 'table':
18
+ return [block.header, ...block.rows].map(row => row.map(cell => plainText(cell.children)).join(' ')).join('\n');
19
+ default:
20
+ return '';
21
+ }
22
+ })
23
+ .join('\n');
24
+ }
25
+ export function splitByHeading(children, forcedDepth) {
26
+ const headings = children.filter((child) => child.type === 'heading');
27
+ const depth = forcedDepth ?? Math.min(...headings.map(heading => heading.depth));
28
+ if (!Number.isFinite(depth))
29
+ return [];
30
+ const sections = [];
31
+ let current;
32
+ for (const child of children) {
33
+ if (child.type === 'heading' && child.depth === depth) {
34
+ current = {
35
+ name: plainText(child.children),
36
+ children: [],
37
+ };
38
+ if (child.id)
39
+ current.id = child.id;
40
+ sections.push(current);
41
+ continue;
42
+ }
43
+ if (current)
44
+ current.children.push(child);
45
+ }
46
+ return sections;
47
+ }
48
+ export function slugify(value, fallback) {
49
+ return (value
50
+ .trim()
51
+ .toLowerCase()
52
+ .replace(/[^a-z0-9\s-]/g, '')
53
+ .replace(/\s+/g, '-')
54
+ .replace(/-+/g, '-')
55
+ .replace(/^-|-$/g, '')
56
+ .slice(0, 64) || fallback);
57
+ }
58
+ export function markFrameworkHeadings(blocks, framework) {
59
+ return blocks.map(block => {
60
+ if (block.type === 'heading' && block.depth > 1) {
61
+ return {
62
+ ...block,
63
+ framework,
64
+ };
65
+ }
66
+ if (block.type === 'blockquote' || block.type === 'callout') {
67
+ return {
68
+ ...block,
69
+ children: markFrameworkHeadings(block.children, framework),
70
+ };
71
+ }
72
+ if (block.type === 'list') {
73
+ return {
74
+ ...block,
75
+ items: block.items.map(item => ({
76
+ ...item,
77
+ children: markFrameworkHeadings(item.children, framework),
78
+ })),
79
+ };
80
+ }
81
+ if (block.type === 'component') {
82
+ return {
83
+ ...block,
84
+ children: markFrameworkHeadings(block.children, framework),
85
+ };
86
+ }
87
+ return block;
88
+ });
89
+ }
90
+ export function getComponentName(node) {
91
+ return node.name.toLowerCase();
92
+ }
@@ -0,0 +1,7 @@
1
+ import type { ComponentNode } from '../types.js';
2
+ export declare function transformTabsComponent(node: ComponentNode): ComponentNode;
3
+ export declare function transformFileTabs(node: ComponentNode): ComponentNode;
4
+ export declare function transformPackageManagerTabs(node: ComponentNode): ComponentNode;
5
+ export declare function transformBundlerTabs(node: ComponentNode): ComponentNode;
6
+ export declare function transformHeadingTabs(node: ComponentNode): ComponentNode;
7
+ //# sourceMappingURL=tabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/extensions/tabs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,aAAa,CAAA;AAK3D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAQzE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAkCpE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CA4B9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAiCvE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CA2BvE"}
@@ -0,0 +1,145 @@
1
+ import { blocksToText, slugify, splitByHeading } from './shared.js';
2
+ const bundlers = ['vite', 'rsbuild'];
3
+ export function transformTabsComponent(node) {
4
+ const variant = node.attributes.variant?.toLowerCase();
5
+ if (variant === 'files')
6
+ return transformFileTabs(node);
7
+ if (variant === 'package-manager' || variant === 'package-managers')
8
+ return transformPackageManagerTabs(node);
9
+ if (variant === 'bundler')
10
+ return transformBundlerTabs(node);
11
+ return transformHeadingTabs(node);
12
+ }
13
+ export function transformFileTabs(node) {
14
+ const files = node.children.filter((child) => child.type === 'code');
15
+ if (!files.length)
16
+ return node;
17
+ const tabs = files.map((file, index) => ({
18
+ slug: `file-${index}`,
19
+ name: file.title || file.file || 'Untitled',
20
+ }));
21
+ return {
22
+ ...node,
23
+ properties: {
24
+ ...(node.properties ?? {}),
25
+ 'data-attributes': JSON.stringify({ tabs }),
26
+ 'data-files-meta': JSON.stringify({
27
+ files: files.map(file => ({
28
+ title: file.title || file.file || 'Untitled',
29
+ code: file.value,
30
+ language: file.lang ?? 'plaintext',
31
+ })),
32
+ }),
33
+ },
34
+ children: files.map((file, index) => ({
35
+ type: 'component',
36
+ name: 'tab-panel',
37
+ tagName: 'md-tab-panel',
38
+ attributes: {},
39
+ properties: {
40
+ 'data-tab-slug': `file-${index}`,
41
+ 'data-tab-index': String(index),
42
+ },
43
+ children: [file],
44
+ })),
45
+ };
46
+ }
47
+ export function transformPackageManagerTabs(node) {
48
+ const packagesByFramework = {};
49
+ for (const line of blocksToText(node.children).split('\n')) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed)
52
+ continue;
53
+ const colon = trimmed.indexOf(':');
54
+ if (colon === -1)
55
+ continue;
56
+ const framework = trimmed.slice(0, colon).trim().toLowerCase();
57
+ const packages = trimmed.slice(colon + 1).trim().split(/\s+/).filter(Boolean);
58
+ if (!framework || packages.length === 0)
59
+ continue;
60
+ packagesByFramework[framework] ??= [];
61
+ packagesByFramework[framework].push(packages);
62
+ }
63
+ if (!Object.keys(packagesByFramework).length)
64
+ return node;
65
+ return {
66
+ ...node,
67
+ properties: {
68
+ ...(node.properties ?? {}),
69
+ 'data-package-manager-meta': JSON.stringify({
70
+ packagesByFramework,
71
+ mode: resolveInstallMode(node.attributes.mode),
72
+ }),
73
+ },
74
+ children: [],
75
+ };
76
+ }
77
+ export function transformBundlerTabs(node) {
78
+ const sections = splitByHeading(node.children);
79
+ const selected = sections.filter(section => isBundler(section.name.toLowerCase()));
80
+ if (!selected.length)
81
+ return node;
82
+ const tabs = bundlers
83
+ .map(bundler => selected.find(section => section.name.toLowerCase() === bundler))
84
+ .filter((section) => Boolean(section))
85
+ .map(section => ({ slug: section.name.toLowerCase(), name: section.name.toLowerCase() }));
86
+ return {
87
+ ...node,
88
+ properties: {
89
+ ...(node.properties ?? {}),
90
+ 'data-attributes': JSON.stringify({ tabs }),
91
+ 'data-bundler-meta': JSON.stringify({ bundlers: tabs.map(tab => tab.slug) }),
92
+ },
93
+ children: tabs.map((tab, index) => {
94
+ const section = selected.find(section => section.name.toLowerCase() === tab.slug);
95
+ return {
96
+ type: 'component',
97
+ name: 'tab-panel',
98
+ tagName: 'md-tab-panel',
99
+ attributes: {},
100
+ properties: {
101
+ 'data-tab-slug': tab.slug,
102
+ 'data-tab-index': String(index),
103
+ 'data-content': section.children.length === 1 && section.children[0]?.type === 'code' ? 'code-only' : 'mixed',
104
+ },
105
+ children: section.children,
106
+ };
107
+ }),
108
+ };
109
+ }
110
+ export function transformHeadingTabs(node) {
111
+ const sections = splitByHeading(node.children);
112
+ if (!sections.length)
113
+ return node;
114
+ const tabs = sections.map((section, index) => ({
115
+ slug: section.id || slugify(section.name, `tab-${index + 1}`),
116
+ name: section.name,
117
+ }));
118
+ return {
119
+ ...node,
120
+ properties: {
121
+ ...(node.properties ?? {}),
122
+ 'data-attributes': JSON.stringify({ tabs }),
123
+ },
124
+ children: sections.map((section, index) => ({
125
+ type: 'component',
126
+ name: 'tab-panel',
127
+ tagName: 'md-tab-panel',
128
+ attributes: {},
129
+ properties: {
130
+ 'data-tab-slug': tabs[index]?.slug ?? `tab-${index + 1}`,
131
+ 'data-tab-index': String(index),
132
+ },
133
+ children: section.children,
134
+ })),
135
+ };
136
+ }
137
+ function resolveInstallMode(value) {
138
+ const mode = value?.toLowerCase();
139
+ if (mode === 'dev-install' || mode === 'local-install')
140
+ return mode;
141
+ return 'install';
142
+ }
143
+ function isBundler(value) {
144
+ return bundlers.includes(value);
145
+ }
package/dist/html.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { BlockNode, InlineNode, MarkdownDocument, MarkdownInput, RenderOptions } from './types.js';
2
+ export declare function renderHtml(input: MarkdownInput, options?: RenderOptions): string;
3
+ export declare function renderBlock(node: BlockNode, options?: RenderOptions): string;
4
+ export declare function renderInline(node: InlineNode, options?: RenderOptions): string;
5
+ export declare function renderDocument(document: MarkdownDocument, options?: RenderOptions): string;
6
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAuC,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAG3J,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAGpF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CA8ChF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAwBlF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE9F"}