@mark-sorcery/vue 0.1.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/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @mark-3/vue
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/dist/index.cjs ADDED
@@ -0,0 +1,98 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let vue = require("vue");
3
+ let _mark_sorcery_markdown_parser = require("@mark-sorcery/markdown-parser");
4
+
5
+ //#region src/hast-to-vnodes.ts
6
+ /**
7
+ * Convert HAST node properties to Vue-compatible props.
8
+ * - `className` array → `class` string
9
+ * - `htmlFor` → `for`
10
+ * - All other properties pass through as-is
11
+ */
12
+ function convertProps(properties) {
13
+ const props = {};
14
+ for (const [key, value] of Object.entries(properties)) if (key === "className" && Array.isArray(value)) props["class"] = value.join(" ");
15
+ else if (key === "htmlFor") props["for"] = value;
16
+ else props[key] = value;
17
+ return props;
18
+ }
19
+ /** Resolve the tag/component for an element node from the Components option. */
20
+ function resolveTag(node, components) {
21
+ return (typeof components === "function" ? components(node) : components[node.tagName]) ?? node.tagName;
22
+ }
23
+ /**
24
+ * Internal recursive converter. `path` is a dot-separated string identifying
25
+ * the node's position in the tree (e.g. `"0"`, `"0.1"`, `"0.1.2"`).
26
+ */
27
+ function toVNodes(node, components, transition, path) {
28
+ if (Array.isArray(node)) return node.flatMap((n, i) => toVNodes(n, components, transition, `${path}.${i}`));
29
+ switch (node.type) {
30
+ case "root": return node.children.flatMap((child, i) => toVNodes(child, components, transition, String(i)));
31
+ case "element": {
32
+ const { properties = {}, children } = node;
33
+ const tag = resolveTag(node, components);
34
+ const props = convertProps(properties);
35
+ const childVNodes = children.flatMap((child, i) => toVNodes(child, components, transition, `${path}.${i}`));
36
+ const el = typeof tag === "string" ? (0, vue.h)(tag, props, childVNodes) : (0, vue.h)(tag, {
37
+ ...props,
38
+ node
39
+ }, { default: () => childVNodes });
40
+ if (transition !== void 0) return [(0, vue.h)(vue.Transition, {
41
+ key: path,
42
+ ...transition
43
+ }, { default: () => el })];
44
+ return [el];
45
+ }
46
+ case "text": return [node.value];
47
+ case "comment": return [(0, vue.createCommentVNode)(node.value)];
48
+ default: return [];
49
+ }
50
+ }
51
+ /**
52
+ * Recursively convert a HAST node (or array of nodes) into Vue VNodeArrayChildren.
53
+ *
54
+ * @param node - Root HAST node or array of nodes to convert.
55
+ * @param components - Custom component map or resolver function.
56
+ * @param transition - Optional `<Transition>` config applied to every element node.
57
+ */
58
+ function hastToVNodes(node, components, transition) {
59
+ return toVNodes(node, components, transition, "");
60
+ }
61
+
62
+ //#endregion
63
+ //#region src/Markdown.ts
64
+ const Markdown = (0, vue.defineComponent)({
65
+ name: "Markdown",
66
+ props: {
67
+ markdown: {
68
+ type: String,
69
+ required: true
70
+ },
71
+ options: {
72
+ type: Object,
73
+ default: void 0
74
+ },
75
+ components: {
76
+ type: [Object, Function],
77
+ default: () => ({})
78
+ },
79
+ transition: {
80
+ type: [Boolean, Object],
81
+ default: false
82
+ }
83
+ },
84
+ setup(props) {
85
+ const hast = (0, vue.computed)(() => (0, _mark_sorcery_markdown_parser.parse)(props.markdown, props.options));
86
+ return () => {
87
+ const raw = props.components ?? {};
88
+ const components = typeof raw === "function" ? raw : Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, typeof v === "string" || v == null ? v : (0, vue.markRaw)(v)]));
89
+ const transitionConfig = props.transition === true ? {} : props.transition || void 0;
90
+ return (0, vue.h)(vue.Fragment, hastToVNodes(hast.value, components, transitionConfig));
91
+ };
92
+ }
93
+ });
94
+
95
+ //#endregion
96
+ exports.Markdown = Markdown;
97
+ exports.hastToVNodes = hastToVNodes;
98
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["Transition","Fragment"],"sources":["../src/hast-to-vnodes.ts","../src/Markdown.ts"],"sourcesContent":["import { h, createCommentVNode, Transition } from 'vue';\r\nimport type { Element, Nodes } from 'hast';\r\nimport type { VNodeArrayChildren } from 'vue';\r\nimport type { ComponentResolution, Components, TransitionConfig } from './types.ts';\r\n\r\n/**\r\n * Convert HAST node properties to Vue-compatible props.\r\n * - `className` array → `class` string\r\n * - `htmlFor` → `for`\r\n * - All other properties pass through as-is\r\n */\r\nfunction convertProps(properties: Record<string, unknown>): Record<string, unknown> {\r\n const props: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(properties)) {\r\n if (key === 'className' && Array.isArray(value)) {\r\n props['class'] = value.join(' ');\r\n } else if (key === 'htmlFor') {\r\n props['for'] = value;\r\n } else {\r\n props[key] = value;\r\n }\r\n }\r\n\r\n return props;\r\n}\r\n\r\n/** Resolve the tag/component for an element node from the Components option. */\r\nfunction resolveTag(node: Element, components: Components): NonNullable<ComponentResolution> {\r\n const resolved = typeof components === 'function'\r\n ? components(node)\r\n : components[node.tagName];\r\n return resolved ?? node.tagName;\r\n}\r\n\r\n/**\r\n * Internal recursive converter. `path` is a dot-separated string identifying\r\n * the node's position in the tree (e.g. `\"0\"`, `\"0.1\"`, `\"0.1.2\"`).\r\n */\r\nfunction toVNodes(\r\n node: Nodes | Nodes[],\r\n components: Components,\r\n transition: TransitionConfig | undefined,\r\n path: string,\r\n): VNodeArrayChildren {\r\n if (Array.isArray(node)) {\r\n return node.flatMap((n, i) => toVNodes(n, components, transition, `${path}.${i}`));\r\n }\r\n\r\n switch (node.type) {\r\n case 'root':\r\n return node.children.flatMap((child, i) =>\r\n toVNodes(child, components, transition, String(i)),\r\n );\r\n\r\n case 'element': {\r\n const { properties = {}, children } = node;\r\n const tag = resolveTag(node, components);\r\n const props = convertProps(properties as Record<string, unknown>);\r\n const childVNodes: VNodeArrayChildren = children.flatMap((child, i) =>\r\n toVNodes(child, components, transition, `${path}.${i}`),\r\n );\r\n\r\n // Build the element VNode\r\n // Custom Vue components also receive the raw HAST `node` prop so they\r\n // can access the original element (e.g. to extract text content for\r\n // syntax highlighting or diagram rendering).\r\n const el = typeof tag === 'string'\r\n ? h(tag, props, childVNodes)\r\n : h(tag, { ...props, node }, { default: () => childVNodes });\r\n\r\n // Wrap in <Transition> if requested, using the tree path as a stable key\r\n if (transition !== undefined) {\r\n return [h(Transition, { key: path, ...transition }, { default: () => el })];\r\n }\r\n return [el];\r\n }\r\n\r\n case 'text':\r\n return [node.value];\r\n\r\n case 'comment':\r\n return [createCommentVNode(node.value)];\r\n\r\n default:\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Recursively convert a HAST node (or array of nodes) into Vue VNodeArrayChildren.\r\n *\r\n * @param node - Root HAST node or array of nodes to convert.\r\n * @param components - Custom component map or resolver function.\r\n * @param transition - Optional `<Transition>` config applied to every element node.\r\n */\r\nexport function hastToVNodes(\r\n node: Nodes | Nodes[],\r\n components: Components,\r\n transition?: TransitionConfig,\r\n): VNodeArrayChildren {\r\n return toVNodes(node, components, transition, '');\r\n}\r\n","import { computed, defineComponent, Fragment, h, markRaw } from 'vue';\r\nimport { parse } from '@mark-sorcery/markdown-parser';\r\nimport { hastToVNodes } from './hast-to-vnodes.ts';\r\nimport type { Components, MarkdownProps, ParseOptions, TransitionConfig } from './types.ts';\r\n\r\nexport const Markdown = defineComponent({\r\n name: 'Markdown',\r\n\r\n props: {\r\n markdown: {\r\n type: String,\r\n required: true,\r\n },\r\n options: {\r\n type: Object as () => ParseOptions,\r\n default: undefined,\r\n },\r\n components: {\r\n type: [Object, Function] as unknown as () => Components,\r\n default: () => ({}),\r\n },\r\n transition: {\r\n type: [Boolean, Object] as unknown as () => boolean | TransitionConfig,\r\n default: false,\r\n },\r\n } satisfies {\r\n [K in keyof MarkdownProps]-?: unknown;\r\n },\r\n\r\n setup(props) {\r\n const hast = computed(() => parse(props.markdown, props.options));\r\n\r\n return () => {\r\n const raw = props.components ?? {};\r\n // Function resolvers pass through; record values are wrapped in markRaw\r\n // so Vue doesn't make component objects reactive (perf warning prevention)\r\n const components: Components = typeof raw === 'function'\r\n ? raw\r\n : Object.fromEntries(\r\n Object.entries(raw).map(([k, v]) =>\r\n [k, typeof v === 'string' || v == null ? v : markRaw(v)],\r\n ),\r\n );\r\n\r\n const transitionConfig: TransitionConfig | undefined =\r\n props.transition === true\r\n ? {}\r\n : props.transition || undefined;\r\n\r\n const vnodes = hastToVNodes(hast.value, components, transitionConfig);\r\n return h(Fragment, vnodes);\r\n };\r\n },\r\n});\r\n"],"mappings":";;;;;;;;;;;AAWA,SAAS,aAAa,YAA8D;CAClF,MAAM,QAAiC,EAAE;AAEzC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,CACnD,KAAI,QAAQ,eAAe,MAAM,QAAQ,MAAM,CAC7C,OAAM,WAAW,MAAM,KAAK,IAAI;UACvB,QAAQ,UACjB,OAAM,SAAS;KAEf,OAAM,OAAO;AAIjB,QAAO;;;AAIT,SAAS,WAAW,MAAe,YAA0D;AAI3F,SAHiB,OAAO,eAAe,aACnC,WAAW,KAAK,GAChB,WAAW,KAAK,aACD,KAAK;;;;;;AAO1B,SAAS,SACP,MACA,YACA,YACA,MACoB;AACpB,KAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,KAAK,SAAS,GAAG,MAAM,SAAS,GAAG,YAAY,YAAY,GAAG,KAAK,GAAG,IAAI,CAAC;AAGpF,SAAQ,KAAK,MAAb;EACE,KAAK,OACH,QAAO,KAAK,SAAS,SAAS,OAAO,MACnC,SAAS,OAAO,YAAY,YAAY,OAAO,EAAE,CAAC,CACnD;EAEH,KAAK,WAAW;GACd,MAAM,EAAE,aAAa,EAAE,EAAE,aAAa;GACtC,MAAM,MAAM,WAAW,MAAM,WAAW;GACxC,MAAM,QAAQ,aAAa,WAAsC;GACjE,MAAM,cAAkC,SAAS,SAAS,OAAO,MAC/D,SAAS,OAAO,YAAY,YAAY,GAAG,KAAK,GAAG,IAAI,CACxD;GAMD,MAAM,KAAK,OAAO,QAAQ,sBACpB,KAAK,OAAO,YAAY,cACxB,KAAK;IAAE,GAAG;IAAO;IAAM,EAAE,EAAE,eAAe,aAAa,CAAC;AAG9D,OAAI,eAAe,OACjB,QAAO,YAAGA,gBAAY;IAAE,KAAK;IAAM,GAAG;IAAY,EAAE,EAAE,eAAe,IAAI,CAAC,CAAC;AAE7E,UAAO,CAAC,GAAG;;EAGb,KAAK,OACH,QAAO,CAAC,KAAK,MAAM;EAErB,KAAK,UACH,QAAO,6BAAoB,KAAK,MAAM,CAAC;EAEzC,QACE,QAAO,EAAE;;;;;;;;;;AAWf,SAAgB,aACd,MACA,YACA,YACoB;AACpB,QAAO,SAAS,MAAM,YAAY,YAAY,GAAG;;;;;AChGnD,MAAa,oCAA2B;CACtC,MAAM;CAEN,OAAO;EACL,UAAU;GACR,MAAM;GACN,UAAU;GACX;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,YAAY;GACV,MAAM,CAAC,QAAQ,SAAS;GACxB,gBAAgB,EAAE;GACnB;EACD,YAAY;GACV,MAAM,CAAC,SAAS,OAAO;GACvB,SAAS;GACV;EACF;CAID,MAAM,OAAO;EACX,MAAM,wEAA4B,MAAM,UAAU,MAAM,QAAQ,CAAC;AAEjE,eAAa;GACX,MAAM,MAAM,MAAM,cAAc,EAAE;GAGlC,MAAM,aAAyB,OAAO,QAAQ,aAC1C,MACA,OAAO,YACL,OAAO,QAAQ,IAAI,CAAC,KAAK,CAAC,GAAG,OAC3B,CAAC,GAAG,OAAO,MAAM,YAAY,KAAK,OAAO,qBAAY,EAAE,CAAC,CACzD,CACF;GAEL,MAAM,mBACJ,MAAM,eAAe,OACjB,EAAE,GACF,MAAM,cAAc;AAG1B,qBAASC,cADM,aAAa,KAAK,OAAO,YAAY,iBAAiB,CAC3C;;;CAG/B,CAAC"}
@@ -0,0 +1,457 @@
1
+ import * as vue from "vue";
2
+ import { BaseTransitionProps, Component, VNodeArrayChildren } from "vue";
3
+ import { ParseOptions } from "@mark-sorcery/markdown-parser";
4
+
5
+ //#region ../../node_modules/.bun/@types+unist@3.0.3/node_modules/@types/unist/index.d.ts
6
+ // ## Interfaces
7
+ /**
8
+ * Info associated with nodes by the ecosystem.
9
+ *
10
+ * This space is guaranteed to never be specified by unist or specifications
11
+ * implementing unist.
12
+ * But you can use it in utilities and plugins to store data.
13
+ *
14
+ * This type can be augmented to register custom data.
15
+ * For example:
16
+ *
17
+ * ```ts
18
+ * declare module 'unist' {
19
+ * interface Data {
20
+ * // `someNode.data.myId` is typed as `number | undefined`
21
+ * myId?: number | undefined
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ interface Data$1 {}
27
+ /**
28
+ * One place in a source file.
29
+ */
30
+ interface Point {
31
+ /**
32
+ * Line in a source file (1-indexed integer).
33
+ */
34
+ line: number;
35
+ /**
36
+ * Column in a source file (1-indexed integer).
37
+ */
38
+ column: number;
39
+ /**
40
+ * Character in a source file (0-indexed integer).
41
+ */
42
+ offset?: number | undefined;
43
+ }
44
+ /**
45
+ * Position of a node in a source document.
46
+ *
47
+ * A position is a range between two points.
48
+ */
49
+ interface Position {
50
+ /**
51
+ * Place of the first character of the parsed source region.
52
+ */
53
+ start: Point;
54
+ /**
55
+ * Place of the first character after the parsed source region.
56
+ */
57
+ end: Point;
58
+ }
59
+ /**
60
+ * Abstract unist node.
61
+ *
62
+ * The syntactic unit in unist syntax trees are called nodes.
63
+ *
64
+ * This interface is supposed to be extended.
65
+ * If you can use {@link Literal} or {@link Parent}, you should.
66
+ * But for example in markdown, a `thematicBreak` (`***`), is neither literal
67
+ * nor parent, but still a node.
68
+ */
69
+ interface Node$1 {
70
+ /**
71
+ * Node type.
72
+ */
73
+ type: string;
74
+ /**
75
+ * Info from the ecosystem.
76
+ */
77
+ data?: Data$1 | undefined;
78
+ /**
79
+ * Position of a node in a source document.
80
+ *
81
+ * Nodes that are generated (not in the original source document) must not
82
+ * have a position.
83
+ */
84
+ position?: Position | undefined;
85
+ }
86
+ //#endregion
87
+ //#region ../../node_modules/.bun/@types+hast@3.0.4/node_modules/@types/hast/index.d.ts
88
+ // ## Interfaces
89
+ /**
90
+ * Info associated with hast nodes by the ecosystem.
91
+ *
92
+ * This space is guaranteed to never be specified by unist or hast.
93
+ * But you can use it in utilities and plugins to store data.
94
+ *
95
+ * This type can be augmented to register custom data.
96
+ * For example:
97
+ *
98
+ * ```ts
99
+ * declare module 'hast' {
100
+ * interface Data {
101
+ * // `someNode.data.myId` is typed as `number | undefined`
102
+ * myId?: number | undefined
103
+ * }
104
+ * }
105
+ * ```
106
+ */
107
+ interface Data extends Data$1 {}
108
+ /**
109
+ * Info associated with an element.
110
+ */
111
+ interface Properties {
112
+ [PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>;
113
+ }
114
+ // ## Content maps
115
+ /**
116
+ * Union of registered hast nodes that can occur in {@link Element}.
117
+ *
118
+ * To register mote custom hast nodes, add them to {@link ElementContentMap}.
119
+ * They will be automatically added here.
120
+ */
121
+ type ElementContent = ElementContentMap[keyof ElementContentMap];
122
+ /**
123
+ * Registry of all hast nodes that can occur as children of {@link Element}.
124
+ *
125
+ * For a union of all {@link Element} children, see {@link ElementContent}.
126
+ */
127
+ interface ElementContentMap {
128
+ comment: Comment;
129
+ element: Element;
130
+ text: Text;
131
+ }
132
+ /**
133
+ * Union of registered hast nodes that can occur in {@link Root}.
134
+ *
135
+ * To register custom hast nodes, add them to {@link RootContentMap}.
136
+ * They will be automatically added here.
137
+ */
138
+ type RootContent = RootContentMap[keyof RootContentMap];
139
+ /**
140
+ * Registry of all hast nodes that can occur as children of {@link Root}.
141
+ *
142
+ * > 👉 **Note**: {@link Root} does not need to be an entire document.
143
+ * > it can also be a fragment.
144
+ *
145
+ * For a union of all {@link Root} children, see {@link RootContent}.
146
+ */
147
+ interface RootContentMap {
148
+ comment: Comment;
149
+ doctype: Doctype;
150
+ element: Element;
151
+ text: Text;
152
+ }
153
+ /**
154
+ * Union of registered hast nodes.
155
+ *
156
+ * To register custom hast nodes, add them to {@link RootContentMap} and other
157
+ * places where relevant.
158
+ * They will be automatically added here.
159
+ */
160
+ type Nodes = Root | RootContent;
161
+ // ## Abstract nodes
162
+ /**
163
+ * Abstract hast node.
164
+ *
165
+ * This interface is supposed to be extended.
166
+ * If you can use {@link Literal} or {@link Parent}, you should.
167
+ * But for example in HTML, a `Doctype` is neither literal nor parent, but
168
+ * still a node.
169
+ *
170
+ * To register custom hast nodes, add them to {@link RootContentMap} and other
171
+ * places where relevant (such as {@link ElementContentMap}).
172
+ *
173
+ * For a union of all registered hast nodes, see {@link Nodes}.
174
+ */
175
+ interface Node extends Node$1 {
176
+ /**
177
+ * Info from the ecosystem.
178
+ */
179
+ data?: Data | undefined;
180
+ }
181
+ /**
182
+ * Abstract hast node that contains the smallest possible value.
183
+ *
184
+ * This interface is supposed to be extended if you make custom hast nodes.
185
+ *
186
+ * For a union of all registered hast literals, see {@link Literals}.
187
+ */
188
+ interface Literal extends Node {
189
+ /**
190
+ * Plain-text value.
191
+ */
192
+ value: string;
193
+ }
194
+ /**
195
+ * Abstract hast node that contains other hast nodes (*children*).
196
+ *
197
+ * This interface is supposed to be extended if you make custom hast nodes.
198
+ *
199
+ * For a union of all registered hast parents, see {@link Parents}.
200
+ */
201
+ interface Parent extends Node {
202
+ /**
203
+ * List of children.
204
+ */
205
+ children: RootContent[];
206
+ }
207
+ // ## Concrete nodes
208
+ /**
209
+ * HTML comment.
210
+ */
211
+ interface Comment extends Literal {
212
+ /**
213
+ * Node type of HTML comments in hast.
214
+ */
215
+ type: "comment";
216
+ /**
217
+ * Data associated with the comment.
218
+ */
219
+ data?: CommentData | undefined;
220
+ }
221
+ /**
222
+ * Info associated with hast comments by the ecosystem.
223
+ */
224
+ interface CommentData extends Data {}
225
+ /**
226
+ * HTML document type.
227
+ */
228
+ interface Doctype extends Node$1 {
229
+ /**
230
+ * Node type of HTML document types in hast.
231
+ */
232
+ type: "doctype";
233
+ /**
234
+ * Data associated with the doctype.
235
+ */
236
+ data?: DoctypeData | undefined;
237
+ }
238
+ /**
239
+ * Info associated with hast doctypes by the ecosystem.
240
+ */
241
+ interface DoctypeData extends Data {}
242
+ /**
243
+ * HTML element.
244
+ */
245
+ interface Element extends Parent {
246
+ /**
247
+ * Node type of elements.
248
+ */
249
+ type: "element";
250
+ /**
251
+ * Tag name (such as `'body'`) of the element.
252
+ */
253
+ tagName: string;
254
+ /**
255
+ * Info associated with the element.
256
+ */
257
+ properties: Properties;
258
+ /**
259
+ * Children of element.
260
+ */
261
+ children: ElementContent[];
262
+ /**
263
+ * When the `tagName` field is `'template'`, a `content` field can be
264
+ * present.
265
+ */
266
+ content?: Root | undefined;
267
+ /**
268
+ * Data associated with the element.
269
+ */
270
+ data?: ElementData | undefined;
271
+ }
272
+ /**
273
+ * Info associated with hast elements by the ecosystem.
274
+ */
275
+ interface ElementData extends Data {}
276
+ /**
277
+ * Document fragment or a whole document.
278
+ *
279
+ * Should be used as the root of a tree and must not be used as a child.
280
+ *
281
+ * Can also be used as the value for the content field on a `'template'` element.
282
+ */
283
+ interface Root extends Parent {
284
+ /**
285
+ * Node type of hast root.
286
+ */
287
+ type: "root";
288
+ /**
289
+ * Children of root.
290
+ */
291
+ children: RootContent[];
292
+ /**
293
+ * Data associated with the hast root.
294
+ */
295
+ data?: RootData | undefined;
296
+ }
297
+ /**
298
+ * Info associated with hast root nodes by the ecosystem.
299
+ */
300
+ interface RootData extends Data {}
301
+ /**
302
+ * HTML character data (plain text).
303
+ */
304
+ interface Text extends Literal {
305
+ /**
306
+ * Node type of HTML character data (plain text) in hast.
307
+ */
308
+ type: "text";
309
+ /**
310
+ * Data associated with the text.
311
+ */
312
+ data?: TextData | undefined;
313
+ }
314
+ /**
315
+ * Info associated with hast texts by the ecosystem.
316
+ */
317
+ interface TextData extends Data {}
318
+ //#endregion
319
+ //#region src/types.d.ts
320
+ /**
321
+ * Configuration forwarded to Vue's `<Transition>` component.
322
+ * All props are optional; when `transition: true` is used on `<Markdown>`,
323
+ * defaults to `{}` which activates Vue's default `v-enter-*` classes.
324
+ *
325
+ * @see https://vuejs.org/api/built-in-components.html#transition
326
+ */
327
+ interface TransitionConfig {
328
+ name?: string;
329
+ css?: boolean;
330
+ appear?: boolean;
331
+ mode?: 'in-out' | 'out-in' | 'default';
332
+ enterFromClass?: string;
333
+ enterActiveClass?: string;
334
+ enterToClass?: string;
335
+ leaveFromClass?: string;
336
+ leaveActiveClass?: string;
337
+ leaveToClass?: string;
338
+ appearFromClass?: string;
339
+ appearActiveClass?: string;
340
+ appearToClass?: string;
341
+ onBeforeEnter?: BaseTransitionProps['onBeforeEnter'];
342
+ onEnter?: BaseTransitionProps['onEnter'];
343
+ onAfterEnter?: BaseTransitionProps['onAfterEnter'];
344
+ onEnterCancelled?: BaseTransitionProps['onEnterCancelled'];
345
+ onBeforeLeave?: BaseTransitionProps['onBeforeLeave'];
346
+ onLeave?: BaseTransitionProps['onLeave'];
347
+ onAfterLeave?: BaseTransitionProps['onAfterLeave'];
348
+ onLeaveCancelled?: BaseTransitionProps['onLeaveCancelled'];
349
+ }
350
+ /** A resolved component value: a Vue component, a tag string, or null/undefined to fall back to the default element. */
351
+ type ComponentResolution = Component | string | null | undefined;
352
+ /**
353
+ * Resolves which Vue component or tag to render for a given HAST element node.
354
+ *
355
+ * Can be:
356
+ * - A **record** mapping tag names to components or tag strings (e.g. `{ h1: MyHeading }`)
357
+ * - A **function** `(node: Element) => ComponentResolution` — called for every element;
358
+ * return `null` or `undefined` to fall back to the default HTML element.
359
+ *
360
+ * @example — record form
361
+ * const components: Components = { h1: MyHeading, code: MyCode }
362
+ *
363
+ * @example — function form
364
+ * const components: Components = (node) => {
365
+ * if (node.tagName === 'h1') return MyHeading
366
+ * if (node.properties?.className?.includes('warning')) return MyWarning
367
+ * }
368
+ */
369
+ type Components = Partial<Record<string, ComponentResolution>> | ((node: Element) => ComponentResolution);
370
+ /**
371
+ * Props automatically injected into every custom Vue component rendered by
372
+ * `<Markdown>`. Use this to type your custom component's `node` prop.
373
+ *
374
+ * @example
375
+ * import type { NodeProps } from '@mark-sorcery/vue'
376
+ * const props = defineProps<NodeProps>()
377
+ * // props.node is the raw HAST Element
378
+ */
379
+ interface NodeProps {
380
+ node: Element;
381
+ }
382
+ interface MarkdownProps {
383
+ /** The markdown string to render. May be a partial/streaming string. */
384
+ markdown: string;
385
+ /** Options forwarded to the underlying markdown parser. */
386
+ options?: ParseOptions;
387
+ /** Custom Vue components or tags to use in place of default HTML elements. */
388
+ components?: Components;
389
+ /**
390
+ * Optional Vue `<Transition>` configuration applied to every rendered element node.
391
+ *
392
+ * - `true` — enables transitions with Vue's default classes (`v-enter-*`).
393
+ * - `TransitionConfig` object — forwarded as props to each `<Transition>` wrapper.
394
+ * - `false` / `undefined` — no transitions (default).
395
+ *
396
+ * @example
397
+ * // Basic fade (add CSS: .fade-enter-active { transition: opacity 0.3s } .fade-enter-from { opacity: 0 })
398
+ * :transition="{ name: 'fade', appear: true }"
399
+ */
400
+ transition?: boolean | TransitionConfig;
401
+ }
402
+ //#endregion
403
+ //#region src/Markdown.d.ts
404
+ declare const Markdown: vue.DefineComponent<vue.ExtractPropTypes<{
405
+ markdown: {
406
+ type: StringConstructor;
407
+ required: boolean;
408
+ };
409
+ options: {
410
+ type: () => ParseOptions;
411
+ default: undefined;
412
+ };
413
+ components: {
414
+ type: () => Components;
415
+ default: () => {};
416
+ };
417
+ transition: {
418
+ type: () => boolean | TransitionConfig;
419
+ default: boolean;
420
+ };
421
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
422
+ [key: string]: any;
423
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
424
+ markdown: {
425
+ type: StringConstructor;
426
+ required: boolean;
427
+ };
428
+ options: {
429
+ type: () => ParseOptions;
430
+ default: undefined;
431
+ };
432
+ components: {
433
+ type: () => Components;
434
+ default: () => {};
435
+ };
436
+ transition: {
437
+ type: () => boolean | TransitionConfig;
438
+ default: boolean;
439
+ };
440
+ }>> & Readonly<{}>, {
441
+ options: ParseOptions;
442
+ components: Components;
443
+ transition: boolean | TransitionConfig;
444
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
445
+ //#endregion
446
+ //#region src/hast-to-vnodes.d.ts
447
+ /**
448
+ * Recursively convert a HAST node (or array of nodes) into Vue VNodeArrayChildren.
449
+ *
450
+ * @param node - Root HAST node or array of nodes to convert.
451
+ * @param components - Custom component map or resolver function.
452
+ * @param transition - Optional `<Transition>` config applied to every element node.
453
+ */
454
+ declare function hastToVNodes(node: Nodes | Nodes[], components: Components, transition?: TransitionConfig): VNodeArrayChildren;
455
+ //#endregion
456
+ export { type Components, Markdown, type MarkdownProps, type NodeProps, type ParseOptions, type TransitionConfig, hastToVNodes };
457
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,457 @@
1
+ import * as vue from "vue";
2
+ import { BaseTransitionProps, Component, VNodeArrayChildren } from "vue";
3
+ import { ParseOptions } from "@mark-sorcery/markdown-parser";
4
+
5
+ //#region ../../node_modules/.bun/@types+unist@3.0.3/node_modules/@types/unist/index.d.ts
6
+ // ## Interfaces
7
+ /**
8
+ * Info associated with nodes by the ecosystem.
9
+ *
10
+ * This space is guaranteed to never be specified by unist or specifications
11
+ * implementing unist.
12
+ * But you can use it in utilities and plugins to store data.
13
+ *
14
+ * This type can be augmented to register custom data.
15
+ * For example:
16
+ *
17
+ * ```ts
18
+ * declare module 'unist' {
19
+ * interface Data {
20
+ * // `someNode.data.myId` is typed as `number | undefined`
21
+ * myId?: number | undefined
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ interface Data$1 {}
27
+ /**
28
+ * One place in a source file.
29
+ */
30
+ interface Point {
31
+ /**
32
+ * Line in a source file (1-indexed integer).
33
+ */
34
+ line: number;
35
+ /**
36
+ * Column in a source file (1-indexed integer).
37
+ */
38
+ column: number;
39
+ /**
40
+ * Character in a source file (0-indexed integer).
41
+ */
42
+ offset?: number | undefined;
43
+ }
44
+ /**
45
+ * Position of a node in a source document.
46
+ *
47
+ * A position is a range between two points.
48
+ */
49
+ interface Position {
50
+ /**
51
+ * Place of the first character of the parsed source region.
52
+ */
53
+ start: Point;
54
+ /**
55
+ * Place of the first character after the parsed source region.
56
+ */
57
+ end: Point;
58
+ }
59
+ /**
60
+ * Abstract unist node.
61
+ *
62
+ * The syntactic unit in unist syntax trees are called nodes.
63
+ *
64
+ * This interface is supposed to be extended.
65
+ * If you can use {@link Literal} or {@link Parent}, you should.
66
+ * But for example in markdown, a `thematicBreak` (`***`), is neither literal
67
+ * nor parent, but still a node.
68
+ */
69
+ interface Node$1 {
70
+ /**
71
+ * Node type.
72
+ */
73
+ type: string;
74
+ /**
75
+ * Info from the ecosystem.
76
+ */
77
+ data?: Data$1 | undefined;
78
+ /**
79
+ * Position of a node in a source document.
80
+ *
81
+ * Nodes that are generated (not in the original source document) must not
82
+ * have a position.
83
+ */
84
+ position?: Position | undefined;
85
+ }
86
+ //#endregion
87
+ //#region ../../node_modules/.bun/@types+hast@3.0.4/node_modules/@types/hast/index.d.ts
88
+ // ## Interfaces
89
+ /**
90
+ * Info associated with hast nodes by the ecosystem.
91
+ *
92
+ * This space is guaranteed to never be specified by unist or hast.
93
+ * But you can use it in utilities and plugins to store data.
94
+ *
95
+ * This type can be augmented to register custom data.
96
+ * For example:
97
+ *
98
+ * ```ts
99
+ * declare module 'hast' {
100
+ * interface Data {
101
+ * // `someNode.data.myId` is typed as `number | undefined`
102
+ * myId?: number | undefined
103
+ * }
104
+ * }
105
+ * ```
106
+ */
107
+ interface Data extends Data$1 {}
108
+ /**
109
+ * Info associated with an element.
110
+ */
111
+ interface Properties {
112
+ [PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>;
113
+ }
114
+ // ## Content maps
115
+ /**
116
+ * Union of registered hast nodes that can occur in {@link Element}.
117
+ *
118
+ * To register mote custom hast nodes, add them to {@link ElementContentMap}.
119
+ * They will be automatically added here.
120
+ */
121
+ type ElementContent = ElementContentMap[keyof ElementContentMap];
122
+ /**
123
+ * Registry of all hast nodes that can occur as children of {@link Element}.
124
+ *
125
+ * For a union of all {@link Element} children, see {@link ElementContent}.
126
+ */
127
+ interface ElementContentMap {
128
+ comment: Comment;
129
+ element: Element;
130
+ text: Text;
131
+ }
132
+ /**
133
+ * Union of registered hast nodes that can occur in {@link Root}.
134
+ *
135
+ * To register custom hast nodes, add them to {@link RootContentMap}.
136
+ * They will be automatically added here.
137
+ */
138
+ type RootContent = RootContentMap[keyof RootContentMap];
139
+ /**
140
+ * Registry of all hast nodes that can occur as children of {@link Root}.
141
+ *
142
+ * > 👉 **Note**: {@link Root} does not need to be an entire document.
143
+ * > it can also be a fragment.
144
+ *
145
+ * For a union of all {@link Root} children, see {@link RootContent}.
146
+ */
147
+ interface RootContentMap {
148
+ comment: Comment;
149
+ doctype: Doctype;
150
+ element: Element;
151
+ text: Text;
152
+ }
153
+ /**
154
+ * Union of registered hast nodes.
155
+ *
156
+ * To register custom hast nodes, add them to {@link RootContentMap} and other
157
+ * places where relevant.
158
+ * They will be automatically added here.
159
+ */
160
+ type Nodes = Root | RootContent;
161
+ // ## Abstract nodes
162
+ /**
163
+ * Abstract hast node.
164
+ *
165
+ * This interface is supposed to be extended.
166
+ * If you can use {@link Literal} or {@link Parent}, you should.
167
+ * But for example in HTML, a `Doctype` is neither literal nor parent, but
168
+ * still a node.
169
+ *
170
+ * To register custom hast nodes, add them to {@link RootContentMap} and other
171
+ * places where relevant (such as {@link ElementContentMap}).
172
+ *
173
+ * For a union of all registered hast nodes, see {@link Nodes}.
174
+ */
175
+ interface Node extends Node$1 {
176
+ /**
177
+ * Info from the ecosystem.
178
+ */
179
+ data?: Data | undefined;
180
+ }
181
+ /**
182
+ * Abstract hast node that contains the smallest possible value.
183
+ *
184
+ * This interface is supposed to be extended if you make custom hast nodes.
185
+ *
186
+ * For a union of all registered hast literals, see {@link Literals}.
187
+ */
188
+ interface Literal extends Node {
189
+ /**
190
+ * Plain-text value.
191
+ */
192
+ value: string;
193
+ }
194
+ /**
195
+ * Abstract hast node that contains other hast nodes (*children*).
196
+ *
197
+ * This interface is supposed to be extended if you make custom hast nodes.
198
+ *
199
+ * For a union of all registered hast parents, see {@link Parents}.
200
+ */
201
+ interface Parent extends Node {
202
+ /**
203
+ * List of children.
204
+ */
205
+ children: RootContent[];
206
+ }
207
+ // ## Concrete nodes
208
+ /**
209
+ * HTML comment.
210
+ */
211
+ interface Comment extends Literal {
212
+ /**
213
+ * Node type of HTML comments in hast.
214
+ */
215
+ type: "comment";
216
+ /**
217
+ * Data associated with the comment.
218
+ */
219
+ data?: CommentData | undefined;
220
+ }
221
+ /**
222
+ * Info associated with hast comments by the ecosystem.
223
+ */
224
+ interface CommentData extends Data {}
225
+ /**
226
+ * HTML document type.
227
+ */
228
+ interface Doctype extends Node$1 {
229
+ /**
230
+ * Node type of HTML document types in hast.
231
+ */
232
+ type: "doctype";
233
+ /**
234
+ * Data associated with the doctype.
235
+ */
236
+ data?: DoctypeData | undefined;
237
+ }
238
+ /**
239
+ * Info associated with hast doctypes by the ecosystem.
240
+ */
241
+ interface DoctypeData extends Data {}
242
+ /**
243
+ * HTML element.
244
+ */
245
+ interface Element extends Parent {
246
+ /**
247
+ * Node type of elements.
248
+ */
249
+ type: "element";
250
+ /**
251
+ * Tag name (such as `'body'`) of the element.
252
+ */
253
+ tagName: string;
254
+ /**
255
+ * Info associated with the element.
256
+ */
257
+ properties: Properties;
258
+ /**
259
+ * Children of element.
260
+ */
261
+ children: ElementContent[];
262
+ /**
263
+ * When the `tagName` field is `'template'`, a `content` field can be
264
+ * present.
265
+ */
266
+ content?: Root | undefined;
267
+ /**
268
+ * Data associated with the element.
269
+ */
270
+ data?: ElementData | undefined;
271
+ }
272
+ /**
273
+ * Info associated with hast elements by the ecosystem.
274
+ */
275
+ interface ElementData extends Data {}
276
+ /**
277
+ * Document fragment or a whole document.
278
+ *
279
+ * Should be used as the root of a tree and must not be used as a child.
280
+ *
281
+ * Can also be used as the value for the content field on a `'template'` element.
282
+ */
283
+ interface Root extends Parent {
284
+ /**
285
+ * Node type of hast root.
286
+ */
287
+ type: "root";
288
+ /**
289
+ * Children of root.
290
+ */
291
+ children: RootContent[];
292
+ /**
293
+ * Data associated with the hast root.
294
+ */
295
+ data?: RootData | undefined;
296
+ }
297
+ /**
298
+ * Info associated with hast root nodes by the ecosystem.
299
+ */
300
+ interface RootData extends Data {}
301
+ /**
302
+ * HTML character data (plain text).
303
+ */
304
+ interface Text extends Literal {
305
+ /**
306
+ * Node type of HTML character data (plain text) in hast.
307
+ */
308
+ type: "text";
309
+ /**
310
+ * Data associated with the text.
311
+ */
312
+ data?: TextData | undefined;
313
+ }
314
+ /**
315
+ * Info associated with hast texts by the ecosystem.
316
+ */
317
+ interface TextData extends Data {}
318
+ //#endregion
319
+ //#region src/types.d.ts
320
+ /**
321
+ * Configuration forwarded to Vue's `<Transition>` component.
322
+ * All props are optional; when `transition: true` is used on `<Markdown>`,
323
+ * defaults to `{}` which activates Vue's default `v-enter-*` classes.
324
+ *
325
+ * @see https://vuejs.org/api/built-in-components.html#transition
326
+ */
327
+ interface TransitionConfig {
328
+ name?: string;
329
+ css?: boolean;
330
+ appear?: boolean;
331
+ mode?: 'in-out' | 'out-in' | 'default';
332
+ enterFromClass?: string;
333
+ enterActiveClass?: string;
334
+ enterToClass?: string;
335
+ leaveFromClass?: string;
336
+ leaveActiveClass?: string;
337
+ leaveToClass?: string;
338
+ appearFromClass?: string;
339
+ appearActiveClass?: string;
340
+ appearToClass?: string;
341
+ onBeforeEnter?: BaseTransitionProps['onBeforeEnter'];
342
+ onEnter?: BaseTransitionProps['onEnter'];
343
+ onAfterEnter?: BaseTransitionProps['onAfterEnter'];
344
+ onEnterCancelled?: BaseTransitionProps['onEnterCancelled'];
345
+ onBeforeLeave?: BaseTransitionProps['onBeforeLeave'];
346
+ onLeave?: BaseTransitionProps['onLeave'];
347
+ onAfterLeave?: BaseTransitionProps['onAfterLeave'];
348
+ onLeaveCancelled?: BaseTransitionProps['onLeaveCancelled'];
349
+ }
350
+ /** A resolved component value: a Vue component, a tag string, or null/undefined to fall back to the default element. */
351
+ type ComponentResolution = Component | string | null | undefined;
352
+ /**
353
+ * Resolves which Vue component or tag to render for a given HAST element node.
354
+ *
355
+ * Can be:
356
+ * - A **record** mapping tag names to components or tag strings (e.g. `{ h1: MyHeading }`)
357
+ * - A **function** `(node: Element) => ComponentResolution` — called for every element;
358
+ * return `null` or `undefined` to fall back to the default HTML element.
359
+ *
360
+ * @example — record form
361
+ * const components: Components = { h1: MyHeading, code: MyCode }
362
+ *
363
+ * @example — function form
364
+ * const components: Components = (node) => {
365
+ * if (node.tagName === 'h1') return MyHeading
366
+ * if (node.properties?.className?.includes('warning')) return MyWarning
367
+ * }
368
+ */
369
+ type Components = Partial<Record<string, ComponentResolution>> | ((node: Element) => ComponentResolution);
370
+ /**
371
+ * Props automatically injected into every custom Vue component rendered by
372
+ * `<Markdown>`. Use this to type your custom component's `node` prop.
373
+ *
374
+ * @example
375
+ * import type { NodeProps } from '@mark-sorcery/vue'
376
+ * const props = defineProps<NodeProps>()
377
+ * // props.node is the raw HAST Element
378
+ */
379
+ interface NodeProps {
380
+ node: Element;
381
+ }
382
+ interface MarkdownProps {
383
+ /** The markdown string to render. May be a partial/streaming string. */
384
+ markdown: string;
385
+ /** Options forwarded to the underlying markdown parser. */
386
+ options?: ParseOptions;
387
+ /** Custom Vue components or tags to use in place of default HTML elements. */
388
+ components?: Components;
389
+ /**
390
+ * Optional Vue `<Transition>` configuration applied to every rendered element node.
391
+ *
392
+ * - `true` — enables transitions with Vue's default classes (`v-enter-*`).
393
+ * - `TransitionConfig` object — forwarded as props to each `<Transition>` wrapper.
394
+ * - `false` / `undefined` — no transitions (default).
395
+ *
396
+ * @example
397
+ * // Basic fade (add CSS: .fade-enter-active { transition: opacity 0.3s } .fade-enter-from { opacity: 0 })
398
+ * :transition="{ name: 'fade', appear: true }"
399
+ */
400
+ transition?: boolean | TransitionConfig;
401
+ }
402
+ //#endregion
403
+ //#region src/Markdown.d.ts
404
+ declare const Markdown: vue.DefineComponent<vue.ExtractPropTypes<{
405
+ markdown: {
406
+ type: StringConstructor;
407
+ required: boolean;
408
+ };
409
+ options: {
410
+ type: () => ParseOptions;
411
+ default: undefined;
412
+ };
413
+ components: {
414
+ type: () => Components;
415
+ default: () => {};
416
+ };
417
+ transition: {
418
+ type: () => boolean | TransitionConfig;
419
+ default: boolean;
420
+ };
421
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
422
+ [key: string]: any;
423
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
424
+ markdown: {
425
+ type: StringConstructor;
426
+ required: boolean;
427
+ };
428
+ options: {
429
+ type: () => ParseOptions;
430
+ default: undefined;
431
+ };
432
+ components: {
433
+ type: () => Components;
434
+ default: () => {};
435
+ };
436
+ transition: {
437
+ type: () => boolean | TransitionConfig;
438
+ default: boolean;
439
+ };
440
+ }>> & Readonly<{}>, {
441
+ options: ParseOptions;
442
+ components: Components;
443
+ transition: boolean | TransitionConfig;
444
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
445
+ //#endregion
446
+ //#region src/hast-to-vnodes.d.ts
447
+ /**
448
+ * Recursively convert a HAST node (or array of nodes) into Vue VNodeArrayChildren.
449
+ *
450
+ * @param node - Root HAST node or array of nodes to convert.
451
+ * @param components - Custom component map or resolver function.
452
+ * @param transition - Optional `<Transition>` config applied to every element node.
453
+ */
454
+ declare function hastToVNodes(node: Nodes | Nodes[], components: Components, transition?: TransitionConfig): VNodeArrayChildren;
455
+ //#endregion
456
+ export { type Components, Markdown, type MarkdownProps, type NodeProps, type ParseOptions, type TransitionConfig, hastToVNodes };
457
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,96 @@
1
+ import { Fragment, Transition, computed, createCommentVNode, defineComponent, h, markRaw } from "vue";
2
+ import { parse } from "@mark-sorcery/markdown-parser";
3
+
4
+ //#region src/hast-to-vnodes.ts
5
+ /**
6
+ * Convert HAST node properties to Vue-compatible props.
7
+ * - `className` array → `class` string
8
+ * - `htmlFor` → `for`
9
+ * - All other properties pass through as-is
10
+ */
11
+ function convertProps(properties) {
12
+ const props = {};
13
+ for (const [key, value] of Object.entries(properties)) if (key === "className" && Array.isArray(value)) props["class"] = value.join(" ");
14
+ else if (key === "htmlFor") props["for"] = value;
15
+ else props[key] = value;
16
+ return props;
17
+ }
18
+ /** Resolve the tag/component for an element node from the Components option. */
19
+ function resolveTag(node, components) {
20
+ return (typeof components === "function" ? components(node) : components[node.tagName]) ?? node.tagName;
21
+ }
22
+ /**
23
+ * Internal recursive converter. `path` is a dot-separated string identifying
24
+ * the node's position in the tree (e.g. `"0"`, `"0.1"`, `"0.1.2"`).
25
+ */
26
+ function toVNodes(node, components, transition, path) {
27
+ if (Array.isArray(node)) return node.flatMap((n, i) => toVNodes(n, components, transition, `${path}.${i}`));
28
+ switch (node.type) {
29
+ case "root": return node.children.flatMap((child, i) => toVNodes(child, components, transition, String(i)));
30
+ case "element": {
31
+ const { properties = {}, children } = node;
32
+ const tag = resolveTag(node, components);
33
+ const props = convertProps(properties);
34
+ const childVNodes = children.flatMap((child, i) => toVNodes(child, components, transition, `${path}.${i}`));
35
+ const el = typeof tag === "string" ? h(tag, props, childVNodes) : h(tag, {
36
+ ...props,
37
+ node
38
+ }, { default: () => childVNodes });
39
+ if (transition !== void 0) return [h(Transition, {
40
+ key: path,
41
+ ...transition
42
+ }, { default: () => el })];
43
+ return [el];
44
+ }
45
+ case "text": return [node.value];
46
+ case "comment": return [createCommentVNode(node.value)];
47
+ default: return [];
48
+ }
49
+ }
50
+ /**
51
+ * Recursively convert a HAST node (or array of nodes) into Vue VNodeArrayChildren.
52
+ *
53
+ * @param node - Root HAST node or array of nodes to convert.
54
+ * @param components - Custom component map or resolver function.
55
+ * @param transition - Optional `<Transition>` config applied to every element node.
56
+ */
57
+ function hastToVNodes(node, components, transition) {
58
+ return toVNodes(node, components, transition, "");
59
+ }
60
+
61
+ //#endregion
62
+ //#region src/Markdown.ts
63
+ const Markdown = defineComponent({
64
+ name: "Markdown",
65
+ props: {
66
+ markdown: {
67
+ type: String,
68
+ required: true
69
+ },
70
+ options: {
71
+ type: Object,
72
+ default: void 0
73
+ },
74
+ components: {
75
+ type: [Object, Function],
76
+ default: () => ({})
77
+ },
78
+ transition: {
79
+ type: [Boolean, Object],
80
+ default: false
81
+ }
82
+ },
83
+ setup(props) {
84
+ const hast = computed(() => parse(props.markdown, props.options));
85
+ return () => {
86
+ const raw = props.components ?? {};
87
+ const components = typeof raw === "function" ? raw : Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, typeof v === "string" || v == null ? v : markRaw(v)]));
88
+ const transitionConfig = props.transition === true ? {} : props.transition || void 0;
89
+ return h(Fragment, hastToVNodes(hast.value, components, transitionConfig));
90
+ };
91
+ }
92
+ });
93
+
94
+ //#endregion
95
+ export { Markdown, hastToVNodes };
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/hast-to-vnodes.ts","../src/Markdown.ts"],"sourcesContent":["import { h, createCommentVNode, Transition } from 'vue';\r\nimport type { Element, Nodes } from 'hast';\r\nimport type { VNodeArrayChildren } from 'vue';\r\nimport type { ComponentResolution, Components, TransitionConfig } from './types.ts';\r\n\r\n/**\r\n * Convert HAST node properties to Vue-compatible props.\r\n * - `className` array → `class` string\r\n * - `htmlFor` → `for`\r\n * - All other properties pass through as-is\r\n */\r\nfunction convertProps(properties: Record<string, unknown>): Record<string, unknown> {\r\n const props: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(properties)) {\r\n if (key === 'className' && Array.isArray(value)) {\r\n props['class'] = value.join(' ');\r\n } else if (key === 'htmlFor') {\r\n props['for'] = value;\r\n } else {\r\n props[key] = value;\r\n }\r\n }\r\n\r\n return props;\r\n}\r\n\r\n/** Resolve the tag/component for an element node from the Components option. */\r\nfunction resolveTag(node: Element, components: Components): NonNullable<ComponentResolution> {\r\n const resolved = typeof components === 'function'\r\n ? components(node)\r\n : components[node.tagName];\r\n return resolved ?? node.tagName;\r\n}\r\n\r\n/**\r\n * Internal recursive converter. `path` is a dot-separated string identifying\r\n * the node's position in the tree (e.g. `\"0\"`, `\"0.1\"`, `\"0.1.2\"`).\r\n */\r\nfunction toVNodes(\r\n node: Nodes | Nodes[],\r\n components: Components,\r\n transition: TransitionConfig | undefined,\r\n path: string,\r\n): VNodeArrayChildren {\r\n if (Array.isArray(node)) {\r\n return node.flatMap((n, i) => toVNodes(n, components, transition, `${path}.${i}`));\r\n }\r\n\r\n switch (node.type) {\r\n case 'root':\r\n return node.children.flatMap((child, i) =>\r\n toVNodes(child, components, transition, String(i)),\r\n );\r\n\r\n case 'element': {\r\n const { properties = {}, children } = node;\r\n const tag = resolveTag(node, components);\r\n const props = convertProps(properties as Record<string, unknown>);\r\n const childVNodes: VNodeArrayChildren = children.flatMap((child, i) =>\r\n toVNodes(child, components, transition, `${path}.${i}`),\r\n );\r\n\r\n // Build the element VNode\r\n // Custom Vue components also receive the raw HAST `node` prop so they\r\n // can access the original element (e.g. to extract text content for\r\n // syntax highlighting or diagram rendering).\r\n const el = typeof tag === 'string'\r\n ? h(tag, props, childVNodes)\r\n : h(tag, { ...props, node }, { default: () => childVNodes });\r\n\r\n // Wrap in <Transition> if requested, using the tree path as a stable key\r\n if (transition !== undefined) {\r\n return [h(Transition, { key: path, ...transition }, { default: () => el })];\r\n }\r\n return [el];\r\n }\r\n\r\n case 'text':\r\n return [node.value];\r\n\r\n case 'comment':\r\n return [createCommentVNode(node.value)];\r\n\r\n default:\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Recursively convert a HAST node (or array of nodes) into Vue VNodeArrayChildren.\r\n *\r\n * @param node - Root HAST node or array of nodes to convert.\r\n * @param components - Custom component map or resolver function.\r\n * @param transition - Optional `<Transition>` config applied to every element node.\r\n */\r\nexport function hastToVNodes(\r\n node: Nodes | Nodes[],\r\n components: Components,\r\n transition?: TransitionConfig,\r\n): VNodeArrayChildren {\r\n return toVNodes(node, components, transition, '');\r\n}\r\n","import { computed, defineComponent, Fragment, h, markRaw } from 'vue';\r\nimport { parse } from '@mark-sorcery/markdown-parser';\r\nimport { hastToVNodes } from './hast-to-vnodes.ts';\r\nimport type { Components, MarkdownProps, ParseOptions, TransitionConfig } from './types.ts';\r\n\r\nexport const Markdown = defineComponent({\r\n name: 'Markdown',\r\n\r\n props: {\r\n markdown: {\r\n type: String,\r\n required: true,\r\n },\r\n options: {\r\n type: Object as () => ParseOptions,\r\n default: undefined,\r\n },\r\n components: {\r\n type: [Object, Function] as unknown as () => Components,\r\n default: () => ({}),\r\n },\r\n transition: {\r\n type: [Boolean, Object] as unknown as () => boolean | TransitionConfig,\r\n default: false,\r\n },\r\n } satisfies {\r\n [K in keyof MarkdownProps]-?: unknown;\r\n },\r\n\r\n setup(props) {\r\n const hast = computed(() => parse(props.markdown, props.options));\r\n\r\n return () => {\r\n const raw = props.components ?? {};\r\n // Function resolvers pass through; record values are wrapped in markRaw\r\n // so Vue doesn't make component objects reactive (perf warning prevention)\r\n const components: Components = typeof raw === 'function'\r\n ? raw\r\n : Object.fromEntries(\r\n Object.entries(raw).map(([k, v]) =>\r\n [k, typeof v === 'string' || v == null ? v : markRaw(v)],\r\n ),\r\n );\r\n\r\n const transitionConfig: TransitionConfig | undefined =\r\n props.transition === true\r\n ? {}\r\n : props.transition || undefined;\r\n\r\n const vnodes = hastToVNodes(hast.value, components, transitionConfig);\r\n return h(Fragment, vnodes);\r\n };\r\n },\r\n});\r\n"],"mappings":";;;;;;;;;;AAWA,SAAS,aAAa,YAA8D;CAClF,MAAM,QAAiC,EAAE;AAEzC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,CACnD,KAAI,QAAQ,eAAe,MAAM,QAAQ,MAAM,CAC7C,OAAM,WAAW,MAAM,KAAK,IAAI;UACvB,QAAQ,UACjB,OAAM,SAAS;KAEf,OAAM,OAAO;AAIjB,QAAO;;;AAIT,SAAS,WAAW,MAAe,YAA0D;AAI3F,SAHiB,OAAO,eAAe,aACnC,WAAW,KAAK,GAChB,WAAW,KAAK,aACD,KAAK;;;;;;AAO1B,SAAS,SACP,MACA,YACA,YACA,MACoB;AACpB,KAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,KAAK,SAAS,GAAG,MAAM,SAAS,GAAG,YAAY,YAAY,GAAG,KAAK,GAAG,IAAI,CAAC;AAGpF,SAAQ,KAAK,MAAb;EACE,KAAK,OACH,QAAO,KAAK,SAAS,SAAS,OAAO,MACnC,SAAS,OAAO,YAAY,YAAY,OAAO,EAAE,CAAC,CACnD;EAEH,KAAK,WAAW;GACd,MAAM,EAAE,aAAa,EAAE,EAAE,aAAa;GACtC,MAAM,MAAM,WAAW,MAAM,WAAW;GACxC,MAAM,QAAQ,aAAa,WAAsC;GACjE,MAAM,cAAkC,SAAS,SAAS,OAAO,MAC/D,SAAS,OAAO,YAAY,YAAY,GAAG,KAAK,GAAG,IAAI,CACxD;GAMD,MAAM,KAAK,OAAO,QAAQ,WACtB,EAAE,KAAK,OAAO,YAAY,GAC1B,EAAE,KAAK;IAAE,GAAG;IAAO;IAAM,EAAE,EAAE,eAAe,aAAa,CAAC;AAG9D,OAAI,eAAe,OACjB,QAAO,CAAC,EAAE,YAAY;IAAE,KAAK;IAAM,GAAG;IAAY,EAAE,EAAE,eAAe,IAAI,CAAC,CAAC;AAE7E,UAAO,CAAC,GAAG;;EAGb,KAAK,OACH,QAAO,CAAC,KAAK,MAAM;EAErB,KAAK,UACH,QAAO,CAAC,mBAAmB,KAAK,MAAM,CAAC;EAEzC,QACE,QAAO,EAAE;;;;;;;;;;AAWf,SAAgB,aACd,MACA,YACA,YACoB;AACpB,QAAO,SAAS,MAAM,YAAY,YAAY,GAAG;;;;;AChGnD,MAAa,WAAW,gBAAgB;CACtC,MAAM;CAEN,OAAO;EACL,UAAU;GACR,MAAM;GACN,UAAU;GACX;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,YAAY;GACV,MAAM,CAAC,QAAQ,SAAS;GACxB,gBAAgB,EAAE;GACnB;EACD,YAAY;GACV,MAAM,CAAC,SAAS,OAAO;GACvB,SAAS;GACV;EACF;CAID,MAAM,OAAO;EACX,MAAM,OAAO,eAAe,MAAM,MAAM,UAAU,MAAM,QAAQ,CAAC;AAEjE,eAAa;GACX,MAAM,MAAM,MAAM,cAAc,EAAE;GAGlC,MAAM,aAAyB,OAAO,QAAQ,aAC1C,MACA,OAAO,YACL,OAAO,QAAQ,IAAI,CAAC,KAAK,CAAC,GAAG,OAC3B,CAAC,GAAG,OAAO,MAAM,YAAY,KAAK,OAAO,IAAI,QAAQ,EAAE,CAAC,CACzD,CACF;GAEL,MAAM,mBACJ,MAAM,eAAe,OACjB,EAAE,GACF,MAAM,cAAc;AAG1B,UAAO,EAAE,UADM,aAAa,KAAK,OAAO,YAAY,iBAAiB,CAC3C;;;CAG/B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@mark-sorcery/vue",
3
+ "version": "0.1.0",
4
+ "description": "Vue renderer for Mark Sorcery markdown.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist",
8
+ "README.md"
9
+ ],
10
+ "type": "module",
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "require": "./dist/index.cjs"
19
+ }
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "tsdown",
26
+ "prepublishOnly": "bun run build && bun run test",
27
+ "test": "vitest run"
28
+ },
29
+ "dependencies": {
30
+ "@mark-sorcery/markdown-parser": "^0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/hast": "^3.0.4",
34
+ "@vue/test-utils": "^2.4.6",
35
+ "happy-dom": "^17.4.4",
36
+ "tsdown": "^0.21.0",
37
+ "vue": "^3.5.25"
38
+ },
39
+ "peerDependencies": {
40
+ "typescript": "^5",
41
+ "vue": "^3.5"
42
+ }
43
+ }