comark-solid 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.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # comark-solid
2
+
3
+ Solid renderer for [Comark](https://comark.dev).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install comark-solid comark
9
+ ```
10
+
11
+ ## `<Markdown>`
12
+
13
+ Parses Markdown strings and renders them directly.
14
+
15
+ ```tsx
16
+ import { Markdown } from "comark-solid";
17
+
18
+ const markdown = "# Hello World";
19
+
20
+ export default function App() {
21
+ return <Markdown value={markdown} />;
22
+ }
23
+ ```
24
+
25
+ ### Props
26
+
27
+ | Prop | Type | Default | Description |
28
+ | :----------- | :------------------------------- | :----------- | :--------------------------------------------------------------------------------- |
29
+ | `value` | `string \| MarkdownDocument` | _(Required)_ | Markdown string or pre-parsed document. |
30
+ | `options` | `ParserOptions` | `undefined` | Options passed to `parseMarkdown` (ignored if `value` is a `MarkdownDocument`). |
31
+ | `components` | `Record<string, ValidComponent>` | `{}` | Map of AST node names to Solid components. |
32
+ | `data` | `Record<string, unknown>` | `{}` | Runtime values referenced from markdown via `:prop="data.path"`. |
33
+ | `class` | `string` | `undefined` | Additional class name for the wrapper `div` (`comark-content` is always included). |
34
+
35
+ ## `<MarkdownDocument>`
36
+
37
+ Renders a pre-parsed `MarkdownDocument` AST without invoking the parser. Ideal for server-side rendering (SSR), static site generation, or pre-compiled content.
38
+
39
+ ```tsx
40
+ import { parseMarkdown } from "comark";
41
+ import { MarkdownDocument } from "comark-solid";
42
+
43
+ const document = await parseMarkdown("# Hello World");
44
+
45
+ export default function App() {
46
+ return <MarkdownDocument value={document} />;
47
+ }
48
+ ```
49
+
50
+ ### Props
51
+
52
+ | Prop | Type | Default | Description |
53
+ | :----------- | :------------------------------- | :----------- | :--------------------------------------------------------------------------------- |
54
+ | `value` | `MarkdownDocument` | _(Required)_ | Pre-parsed Comark document AST. |
55
+ | `components` | `Record<string, ValidComponent>` | `{}` | Map of AST node names to Solid components. |
56
+ | `data` | `Record<string, unknown>` | `{}` | Runtime values referenced from markdown via `:prop="data.path"`. |
57
+ | `class` | `string` | `undefined` | Additional class name for the wrapper `div` (`comark-content` is always included). |
58
+
59
+ ## Custom Components
60
+
61
+ Map tag to Solid components via the `components` prop:
62
+
63
+ ```tsx
64
+ import { Markdown } from "comark-solid";
65
+ import { Card } from "./Card";
66
+ import { CustomHeading } from "./CustomHeading";
67
+
68
+ const components = {
69
+ h1: CustomHeading,
70
+ card: Card,
71
+ };
72
+
73
+ export default function App() {
74
+ return <Markdown value={markdown} components={components} />;
75
+ }
76
+ ```
77
+
78
+ Components can also be loaded asynchronously using Solid's `lazy()`:
79
+
80
+ ```tsx
81
+ import { lazy } from "solid-js";
82
+
83
+ const components = {
84
+ chart: lazy(() => import("./Chart")),
85
+ };
86
+ ```
87
+
88
+ ## Component bindings
89
+
90
+ ### Props
91
+
92
+ Attributes in Comark syntax are passed as props to your component.
93
+ Use the `:` prefix to pass typed values:
94
+
95
+ | Markdown | Prop value |
96
+ | :-------------------------- | :------------------------ |
97
+ | `{type="warning"}` | `"warning"` (string) |
98
+ | `{:count="5"}` | `5` (number) |
99
+ | `{:active=true}` | `true` (boolean) |
100
+ | `{:config='{"key":"val"}'}` | `{ key: "val" }` (object) |
101
+
102
+ ### Slots
103
+
104
+ The default slot is passed as `children`.
105
+ Named slots (e.g. `#header`) are passed as camelCase props prefixed with `slot` (`slotHeader`).
106
+
107
+ ```markdown
108
+ ::card
109
+ #header
110
+ Header content
111
+
112
+ #default
113
+ Body content
114
+ ::
115
+ ```
116
+
117
+ ```tsx
118
+ interface CardProps {
119
+ slotHeader?: JSX.Element;
120
+ children?: JSX.Element;
121
+ }
122
+
123
+ export default function Card(props: CardProps) {
124
+ return (
125
+ <div>
126
+ <Show when={props.slotHeader}>{(header) => <div>{header}</div>}</Show>
127
+ <div>{props.children}</div>
128
+ </div>
129
+ );
130
+ }
131
+ ```
132
+
133
+ ## Acknowledgements
134
+
135
+ This project is heavily based on the official [Comark React renderer](https://github.com/ComarkDev/comark/tree/main/packages/react),
136
+ adapted for Solid's component model and APIs.
137
+
138
+ The original implementation is licensed under the MIT License.
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1,128 @@
1
+ import { MarkdownDocument as MarkdownDocument$1, ParserOptions } from "comark";
2
+ import { Component, ValidComponent } from "solid-js";
3
+ //#region src/components/MarkdownDocument.d.ts
4
+ interface MarkdownDocumentProps {
5
+ /**
6
+ * The parsed Comark document to render.
7
+ */
8
+ value: MarkdownDocument$1;
9
+ /**
10
+ * Maps element tags to Solid components.
11
+ *
12
+ * When an element matches a key in this object, the corresponding
13
+ * component is rendered instead of the native element.
14
+ *
15
+ * @default `{}`
16
+ */
17
+ components?: Record<string, ValidComponent>;
18
+ /**
19
+ * Runtime values referenced from markdown via `:prop="data.path"`.
20
+ *
21
+ * @default `{}`
22
+ */
23
+ data?: Record<string, unknown>;
24
+ /**
25
+ * Additional class for the wrapper div.
26
+ *
27
+ * The `comark-content` class is always included.
28
+ *
29
+ * @default undefined
30
+ */
31
+ class?: string;
32
+ }
33
+ /**
34
+ * Renders a parsed Markdown document using Solid components.
35
+ *
36
+ * Supports custom components mapping for element tags.
37
+ *
38
+ * The wrapper element is assigned the `comark-content` class by default.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * import { MarkdownDocument } from "comark-solid";
43
+ * import { lazy } from "solid-js";
44
+ * import { CustomHeading } from "./CustomHeading";
45
+ *
46
+ * const components = {
47
+ * h1: CustomHeading,
48
+ * lazyCard: lazy(() => import("./Card")),
49
+ * }
50
+ *
51
+ * export default function App() {
52
+ * return <MarkdownDocument value={document} components={components} />;
53
+ * }
54
+ * ```
55
+ */
56
+ declare const MarkdownDocument: Component<MarkdownDocumentProps>;
57
+ //#endregion
58
+ //#region src/components/Markdown.d.ts
59
+ interface MarkdownProps extends Omit<MarkdownDocumentProps, "value"> {
60
+ /**
61
+ * Markdown source or an already-parsed Comark document.
62
+ */
63
+ value: string | MarkdownDocument$1;
64
+ /**
65
+ * Options passed to the Comark Markdown parser.
66
+ *
67
+ * Ignored when `value` is an already-parsed document.
68
+ *
69
+ * @default undefined
70
+ */
71
+ options?: ParserOptions;
72
+ /**
73
+ * Maps element tags to Solid components.
74
+ *
75
+ * When an element matches a key in this object, the corresponding
76
+ * component is rendered instead of the native element.
77
+ *
78
+ * @default `{}`
79
+ */
80
+ components?: Record<string, ValidComponent>;
81
+ /**
82
+ * Runtime values referenced from markdown via `:prop="data.path"`.
83
+ *
84
+ * @default `{}`
85
+ */
86
+ data?: Record<string, unknown>;
87
+ /**
88
+ * Additional class for the wrapper div.
89
+ *
90
+ * The `comark-content` class is always included.
91
+ *
92
+ * @default undefined
93
+ */
94
+ class?: string;
95
+ }
96
+ /**
97
+ * Markdown component
98
+ *
99
+ * Renders a Markdown string or an already-parsed Comark document using Solid components.
100
+ * Markdown strings are parsed on the client before rendering.
101
+ *
102
+ * Supports custom component mapping for element tags.
103
+ *
104
+ * @example
105
+ * ```tsx
106
+ * import { Markdown } from "comark-solid";
107
+ * import { Loading } from "solid-js";
108
+ * import { CustomHeading } from "./CustomHeading";
109
+ *
110
+ * const components = {
111
+ * h1: CustomHeading,
112
+ * };
113
+ *
114
+ * export default function App() {
115
+ * return (
116
+ * <Loading fallback={<div>Loading...</div>}>
117
+ * <Markdown
118
+ * value={source}
119
+ * components={components}
120
+ * />
121
+ * </Loading>
122
+ * );
123
+ * }
124
+ * ```
125
+ */
126
+ declare const Markdown: Component<MarkdownProps>;
127
+ //#endregion
128
+ export { Markdown, MarkdownDocument, type MarkdownDocumentProps, type MarkdownProps };
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ import { className, createComponent, dynamic, effect, insert, mergeProps, template } from "@solidjs/web";
2
+ import { parseMarkdown } from "comark";
3
+ import { isMarkdownDocument, resolveAttributes } from "comark/utils";
4
+ import { Loading, createMemo } from "solid-js";
5
+ //#region src/utils/ast.ts
6
+ function getTag(node) {
7
+ if (Array.isArray(node) && node.length >= 1 && typeof node[0] === "string") return node[0];
8
+ return null;
9
+ }
10
+ function getAttributes(node) {
11
+ if (Array.isArray(node) && node.length >= 2) return node[1] || {};
12
+ return {};
13
+ }
14
+ function getChildren(node) {
15
+ if (Array.isArray(node) && node.length > 2) return node.slice(2);
16
+ return [];
17
+ }
18
+ //#endregion
19
+ //#region src/components/MarkdownDocument.tsx
20
+ var _tmpl$ = /*#__PURE__*/ template(`<div>`);
21
+ const DEFAULT_LOCAL_CONTEXT = {
22
+ parent: void 0,
23
+ renderData: {
24
+ frontmatter: {},
25
+ meta: {},
26
+ data: {},
27
+ props: {}
28
+ }
29
+ };
30
+ function toElementNode(node) {
31
+ if (typeof node === "string") return null;
32
+ const tag = getTag(node);
33
+ if (!tag) return null;
34
+ return {
35
+ tag,
36
+ node
37
+ };
38
+ }
39
+ const VOID_ELEMENTS = /* @__PURE__ */ new Set([
40
+ "hr",
41
+ "br",
42
+ "img"
43
+ ]);
44
+ function resolveProps(node, renderData) {
45
+ return resolveAttributes(getAttributes(node), renderData, { parseJson: true });
46
+ }
47
+ function getSlotName(node) {
48
+ if (getTag(node) !== "template") return null;
49
+ const props = getAttributes(node);
50
+ if (typeof props.name !== "string" || props.name.trim() === "") return null;
51
+ return props.name.trim();
52
+ }
53
+ function getSlotPropName(slotName) {
54
+ if (slotName === "default") return "children";
55
+ return `slot${slotName.charAt(0).toUpperCase() + slotName.slice(1)}`;
56
+ }
57
+ function resolveComponent(element, components) {
58
+ const component = components[element.tag];
59
+ if (!component) return null;
60
+ return {
61
+ ...element,
62
+ component
63
+ };
64
+ }
65
+ function createNodeRenderer(components) {
66
+ function renderNode(node, local = {}) {
67
+ if (typeof node === "string") return node;
68
+ const element = toElementNode(node);
69
+ if (!element) return null;
70
+ const context = {
71
+ ...DEFAULT_LOCAL_CONTEXT,
72
+ ...local
73
+ };
74
+ const component = context.parent !== void 0 && toElementNode(context.parent)?.tag === "pre" ? null : resolveComponent(element, components);
75
+ return component ? renderCustomComponent(component, context) : renderNativeElement(element, context);
76
+ }
77
+ function renderCustomComponent(element, context) {
78
+ const { component, node } = element;
79
+ const props = resolveProps(node, context.renderData);
80
+ const slots = partitionIntoSlots(node, Object.keys(props).length > 0 ? {
81
+ ...context.renderData,
82
+ props
83
+ } : context.renderData);
84
+ const CustomComponent = dynamic(() => component);
85
+ return createComponent(Loading, {
86
+ fallback: null,
87
+ get children() {
88
+ return createComponent(CustomComponent, mergeProps(props, slots));
89
+ }
90
+ });
91
+ }
92
+ function renderNativeElement(element, context) {
93
+ const { tag, node } = element;
94
+ const props = resolveProps(node, context.renderData);
95
+ const Component = dynamic(() => tag);
96
+ if (VOID_ELEMENTS.has(tag)) return createComponent(Component, props);
97
+ const children = getChildren(node).map((child) => renderNode(child, {
98
+ ...context,
99
+ parent: node
100
+ })).filter(Boolean);
101
+ return createComponent(Component, mergeProps(props, { children }));
102
+ }
103
+ function partitionIntoSlots(node, childRenderData) {
104
+ const defaultSlot = [];
105
+ const namedSlots = {};
106
+ for (const child of getChildren(node)) {
107
+ if (child === void 0 || child === null) continue;
108
+ const slotName = getSlotName(child);
109
+ if (slotName) {
110
+ const propName = getSlotPropName(slotName);
111
+ namedSlots[propName] = getChildren(child).map((slotChild) => renderNode(slotChild, {
112
+ parent: child,
113
+ renderData: childRenderData
114
+ })).filter(Boolean);
115
+ continue;
116
+ }
117
+ const rendered = renderNode(child, {
118
+ parent: node,
119
+ renderData: childRenderData
120
+ });
121
+ if (rendered) defaultSlot.push(rendered);
122
+ }
123
+ return defaultSlot.length > 0 ? {
124
+ ...namedSlots,
125
+ children: defaultSlot
126
+ } : namedSlots;
127
+ }
128
+ return renderNode;
129
+ }
130
+ /**
131
+ * Renders a parsed Markdown document using Solid components.
132
+
133
+ *
134
+ * Supports custom components mapping for element tags.
135
+ *
136
+ * The wrapper element is assigned the `comark-content` class by default.
137
+ *
138
+ * @example
139
+ * ```tsx
140
+ * import { MarkdownDocument } from "comark-solid";
141
+ * import { lazy } from "solid-js";
142
+ * import { CustomHeading } from "./CustomHeading";
143
+ *
144
+ * const components = {
145
+ * h1: CustomHeading,
146
+ * lazyCard: lazy(() => import("./Card")),
147
+ * }
148
+ *
149
+ * export default function App() {
150
+ * return <MarkdownDocument value={document} components={components} />;
151
+ * }
152
+ * ```
153
+ */
154
+ const MarkdownDocument = (_props) => {
155
+ const props = mergeProps({
156
+ components: {},
157
+ data: {},
158
+ class: void 0
159
+ }, _props);
160
+ const renderedNodes = createMemo(() => {
161
+ const renderNode = createNodeRenderer(props.components);
162
+ const document = props.value;
163
+ const renderData = {
164
+ frontmatter: document.frontmatter,
165
+ meta: document.meta,
166
+ data: props.data,
167
+ props: {}
168
+ };
169
+ return document.nodes.map((node) => renderNode(node, {
170
+ parent: void 0,
171
+ renderData
172
+ })).filter(Boolean);
173
+ });
174
+ const wrapperClass = createMemo(() => ["comark-content", props.class].map((cls) => cls?.trim()).filter(Boolean).join(" "));
175
+ var _el$ = _tmpl$();
176
+ insert(_el$, renderedNodes);
177
+ effect(() => wrapperClass(), (_v$, _$p) => {
178
+ className(_el$, _v$, _$p);
179
+ });
180
+ return _el$;
181
+ };
182
+ //#endregion
183
+ //#region src/components/Markdown.tsx
184
+ /**
185
+ * Markdown component
186
+ *
187
+ * Renders a Markdown string or an already-parsed Comark document using Solid components.
188
+ * Markdown strings are parsed on the client before rendering.
189
+ *
190
+ * Supports custom component mapping for element tags.
191
+ *
192
+ * @example
193
+ * ```tsx
194
+ * import { Markdown } from "comark-solid";
195
+ * import { Loading } from "solid-js";
196
+ * import { CustomHeading } from "./CustomHeading";
197
+ *
198
+ * const components = {
199
+ * h1: CustomHeading,
200
+ * };
201
+ *
202
+ * export default function App() {
203
+ * return (
204
+ * <Loading fallback={<div>Loading...</div>}>
205
+ * <Markdown
206
+ * value={source}
207
+ * components={components}
208
+ * />
209
+ * </Loading>
210
+ * );
211
+ * }
212
+ * ```
213
+ */
214
+ const Markdown = (_props) => {
215
+ const props = mergeProps({
216
+ components: {},
217
+ data: {}
218
+ }, _props);
219
+ const document = createMemo(() => {
220
+ const value = props.value;
221
+ return isMarkdownDocument(value) ? value : parseMarkdown(value, props.options);
222
+ });
223
+ return createComponent(MarkdownDocument, {
224
+ get value() {
225
+ return document();
226
+ },
227
+ get components() {
228
+ return props.components;
229
+ },
230
+ get data() {
231
+ return props.data;
232
+ },
233
+ get ["class"]() {
234
+ return props.class;
235
+ }
236
+ });
237
+ };
238
+ //#endregion
239
+ export { Markdown, MarkdownDocument };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "comark-solid",
3
+ "version": "0.0.1",
4
+ "description": "Solid renderer for Comark.",
5
+ "keywords": [
6
+ "comark",
7
+ "markdown",
8
+ "solid",
9
+ "solidjs"
10
+ ],
11
+ "homepage": "https://github.com/shiguri-01/my-comark#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/shiguri-01/my-comark/issues"
14
+ },
15
+ "license": "MIT",
16
+ "author": {
17
+ "name": "shiguri",
18
+ "url": "https://github.com/shiguri-01"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "type": "module",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": "./dist/index.js",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "vp pack",
34
+ "dev": "vp pack --watch",
35
+ "test": "vp test",
36
+ "check": "vp check",
37
+ "prepublishOnly": "vp run build"
38
+ },
39
+ "devDependencies": {
40
+ "@solidjs/testing-library": "catalog:",
41
+ "@solidjs/web": "catalog:",
42
+ "@testing-library/jest-dom": "catalog:",
43
+ "@types/node": "^26.1.1",
44
+ "bumpp": "^11.1.0",
45
+ "comark": "catalog:",
46
+ "jsdom": "catalog:",
47
+ "solid-js": "catalog:",
48
+ "typescript": "^7.0.2",
49
+ "unplugin-solid": "catalog:",
50
+ "vite": "catalog:",
51
+ "vite-plus": "catalog:"
52
+ },
53
+ "peerDependencies": {
54
+ "@solidjs/web": "catalog:",
55
+ "comark": "catalog:",
56
+ "solid-js": "catalog:"
57
+ }
58
+ }