@json-render/image 0.19.0 → 0.20.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 +2 -0
- package/dist/{chunk-A67OT34V.mjs → chunk-BUDISVCZ.mjs} +5 -3
- package/dist/chunk-BUDISVCZ.mjs.map +1 -0
- package/dist/{chunk-KQSKKHJA.mjs → chunk-FPTDH3PH.mjs} +14 -6
- package/dist/chunk-FPTDH3PH.mjs.map +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/render.js +11 -5
- package/dist/render.js.map +1 -1
- package/dist/render.mjs +1 -1
- package/dist/server.d.mts +10 -1
- package/dist/server.d.ts +10 -1
- package/dist/server.js +4 -2
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-A67OT34V.mjs.map +0 -1
- package/dist/chunk-KQSKKHJA.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -156,6 +156,8 @@ const png = await renderToPng(spec, { fonts });
|
|
|
156
156
|
|
|
157
157
|
## Server-Safe Import
|
|
158
158
|
|
|
159
|
+
Nested lists can set `repeat.statePath` to `{ "$item": "field" }` to iterate an array on the enclosing repeat item.
|
|
160
|
+
|
|
159
161
|
Import schema and catalog definitions without pulling in React or Satori:
|
|
160
162
|
|
|
161
163
|
```typescript
|
|
@@ -9,7 +9,8 @@ var schema = defineSchema(
|
|
|
9
9
|
type: s.ref("catalog.components"),
|
|
10
10
|
props: s.propsOf("catalog.components"),
|
|
11
11
|
children: s.array(s.string()),
|
|
12
|
-
visible: s.any()
|
|
12
|
+
visible: { ...s.any(), ...s.optional() },
|
|
13
|
+
repeat: { ...s.any(), ...s.optional() }
|
|
13
14
|
})
|
|
14
15
|
)
|
|
15
16
|
}),
|
|
@@ -30,7 +31,8 @@ var schema = defineSchema(
|
|
|
30
31
|
"All text content must use Heading or Text components. Raw strings are not supported.",
|
|
31
32
|
"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.",
|
|
32
33
|
"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.",
|
|
33
|
-
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist."
|
|
34
|
+
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.",
|
|
35
|
+
'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.'
|
|
34
36
|
]
|
|
35
37
|
}
|
|
36
38
|
);
|
|
@@ -38,4 +40,4 @@ var schema = defineSchema(
|
|
|
38
40
|
export {
|
|
39
41
|
schema
|
|
40
42
|
};
|
|
41
|
-
//# sourceMappingURL=chunk-
|
|
43
|
+
//# sourceMappingURL=chunk-BUDISVCZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts"],"sourcesContent":["import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/image\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas\n *\n * Reuses the same { root, elements } spec format as the React and React PDF renderers.\n */\nexport const schema = defineSchema(\n (s) => ({\n spec: s.object({\n root: s.string(),\n elements: s.record(\n s.object({\n type: s.ref(\"catalog.components\"),\n props: s.propsOf(\"catalog.components\"),\n children: s.array(s.string()),\n visible: { ...s.any(), ...s.optional() },\n repeat: { ...s.any(), ...s.optional() },\n }),\n ),\n }),\n\n catalog: s.object({\n components: s.map({\n props: s.zod(),\n slots: s.array(s.string()),\n description: s.string(),\n example: s.any(),\n }),\n }),\n }),\n {\n defaultRules: [\n \"The root element MUST be a Frame component. It defines the image dimensions (width, height) and background.\",\n \"Frame width and height determine the output image size. Common sizes: 1200x630 (OG image), 1080x1080 (social square), 1920x1080 (banner).\",\n \"Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.\",\n \"All text content must use Heading or Text components. Raw strings are not supported.\",\n \"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.\",\n \"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.\",\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.\",\n 'REQUIRED FIELDS: Every element MUST include a \"children\" array. Leaf elements (text, badges, inputs, images) use an empty array: \"children\": []. Omitting \"children\" fails validation.',\n ],\n },\n);\n\nexport type ImageSchema = typeof schema;\n\nexport type ImageSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n"],"mappings":";AAAA,SAAS,oBAAoB;AAWtB,IAAM,SAAS;AAAA,EACpB,CAAC,OAAO;AAAA,IACN,MAAM,EAAE,OAAO;AAAA,MACb,MAAM,EAAE,OAAO;AAAA,MACf,UAAU,EAAE;AAAA,QACV,EAAE,OAAO;AAAA,UACP,MAAM,EAAE,IAAI,oBAAoB;AAAA,UAChC,OAAO,EAAE,QAAQ,oBAAoB;AAAA,UACrC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,UAC5B,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,SAAS,EAAE;AAAA,UACvC,QAAQ,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,SAAS,EAAE;AAAA,QACxC,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,IAED,SAAS,EAAE,OAAO;AAAA,MAChB,YAAY,EAAE,IAAI;AAAA,QAChB,OAAO,EAAE,IAAI;AAAA,QACb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,QACzB,aAAa,EAAE,OAAO;AAAA,QACtB,SAAS,EAAE,IAAI;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EACA;AAAA,IACE,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import satori from "satori";
|
|
3
3
|
import {
|
|
4
4
|
resolveElementProps,
|
|
5
|
+
resolveRepeatItemStatePath,
|
|
6
|
+
resolveRepeatStatePath,
|
|
5
7
|
evaluateVisibility,
|
|
6
8
|
getByPath
|
|
7
9
|
} from "@json-render/core";
|
|
@@ -253,12 +255,18 @@ function renderElement(elementKey, spec, registry, stateModel, repeatItem, repea
|
|
|
253
255
|
const Component = registry[resolvedElement.type];
|
|
254
256
|
if (!Component) return null;
|
|
255
257
|
if (resolvedElement.repeat) {
|
|
256
|
-
const
|
|
258
|
+
const repeat = resolvedElement.repeat;
|
|
259
|
+
const statePath = resolveRepeatStatePath(repeat.statePath, repeatBasePath);
|
|
260
|
+
if (statePath === void 0) {
|
|
261
|
+
console.warn(
|
|
262
|
+
"[json-render/image] $item in repeat.statePath used outside of a repeat scope"
|
|
263
|
+
);
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const items = getByPath(stateModel, statePath) ?? [];
|
|
257
267
|
const fragments = items.map((item, index) => {
|
|
258
|
-
const key =
|
|
259
|
-
|
|
260
|
-
) : String(index);
|
|
261
|
-
const childPath = `${resolvedElement.repeat.statePath}/${index}`;
|
|
268
|
+
const key = repeat.key && typeof item === "object" && item !== null ? String(item[repeat.key] ?? index) : String(index);
|
|
269
|
+
const childPath = resolveRepeatItemStatePath(statePath, index);
|
|
262
270
|
const children2 = resolvedElement.children?.map(
|
|
263
271
|
(childKey) => renderElement(
|
|
264
272
|
childKey,
|
|
@@ -345,4 +353,4 @@ export {
|
|
|
345
353
|
renderToSvg,
|
|
346
354
|
renderToPng
|
|
347
355
|
};
|
|
348
|
-
//# sourceMappingURL=chunk-
|
|
356
|
+
//# sourceMappingURL=chunk-FPTDH3PH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/render.tsx","../src/components/standard.tsx"],"sourcesContent":["import React from \"react\";\nimport satori, { type SatoriOptions } from \"satori\";\nimport type { Spec, UIElement } from \"@json-render/core\";\nimport {\n resolveElementProps,\n resolveRepeatItemStatePath,\n resolveRepeatStatePath,\n evaluateVisibility,\n getByPath,\n type PropResolutionContext,\n} from \"@json-render/core\";\nimport { standardComponents } from \"./components/standard\";\nimport type { ComponentRegistry } from \"./types\";\n\nexport { standardComponents };\n\nexport interface RenderOptions {\n registry?: ComponentRegistry;\n includeStandard?: boolean;\n state?: Record<string, unknown>;\n fonts?: SatoriOptions[\"fonts\"];\n /** Override the Frame width. When omitted, uses the Frame component's width prop. */\n width?: number;\n /** Override the Frame height. When omitted, uses the Frame component's height prop. */\n height?: number;\n}\n\nconst noopEmit = () => {};\n\nfunction renderElement(\n elementKey: string,\n spec: Spec,\n registry: ComponentRegistry,\n stateModel: Record<string, unknown>,\n repeatItem?: unknown,\n repeatIndex?: number,\n repeatBasePath?: string,\n): React.ReactElement | null {\n const element = spec.elements[elementKey];\n if (!element) return null;\n\n const ctx: PropResolutionContext = {\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n };\n\n if (element.visible !== undefined) {\n if (!evaluateVisibility(element.visible, ctx)) {\n return null;\n }\n }\n\n const resolvedProps = resolveElementProps(\n element.props as Record<string, unknown>,\n ctx,\n );\n const resolvedElement: UIElement = { ...element, props: resolvedProps };\n\n const Component = registry[resolvedElement.type];\n if (!Component) return null;\n\n if (resolvedElement.repeat) {\n const repeat = resolvedElement.repeat;\n const statePath = resolveRepeatStatePath(repeat.statePath, repeatBasePath);\n if (statePath === undefined) {\n console.warn(\n \"[json-render/image] $item in repeat.statePath used outside of a repeat scope\",\n );\n return null;\n }\n\n const items =\n (getByPath(stateModel, statePath) as unknown[] | undefined) ?? [];\n\n const fragments = items.map((item, index) => {\n const key =\n repeat.key && typeof item === \"object\" && item !== null\n ? String((item as Record<string, unknown>)[repeat.key!] ?? index)\n : String(index);\n\n const childPath = resolveRepeatItemStatePath(statePath, index);\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n item,\n index,\n childPath,\n ),\n );\n\n return (\n <Component key={key} element={resolvedElement} emit={noopEmit}>\n {children}\n </Component>\n );\n });\n\n return <>{fragments}</>;\n }\n\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n ),\n );\n\n return (\n <Component key={elementKey} element={resolvedElement} emit={noopEmit}>\n {children && children.length > 0 ? children : undefined}\n </Component>\n );\n}\n\ninterface ImageDimensions {\n width: number;\n height: number;\n}\n\nfunction getDimensions(\n spec: Spec,\n options: RenderOptions = {},\n): ImageDimensions {\n if (options.width && options.height) {\n return { width: options.width, height: options.height };\n }\n\n const rootElement = spec.elements[spec.root];\n const props = rootElement?.props as Record<string, unknown> | undefined;\n\n return {\n width: options.width ?? (props?.width as number) ?? 1200,\n height: options.height ?? (props?.height as number) ?? 630,\n };\n}\n\nfunction buildTree(\n spec: Spec,\n options: RenderOptions = {},\n): React.ReactElement {\n const {\n registry: customRegistry,\n includeStandard = true,\n state = {},\n } = options;\n\n const mergedState: Record<string, unknown> = {\n ...spec.state,\n ...state,\n };\n\n const registry: ComponentRegistry = {\n ...(includeStandard ? standardComponents : {}),\n ...customRegistry,\n };\n\n const root = renderElement(spec.root, spec, registry, mergedState);\n return root ?? <></>;\n}\n\n/**\n * Render a json-render spec to an SVG string.\n *\n * Uses Satori to convert the spec's component tree into SVG.\n * No additional dependencies are needed beyond satori.\n */\nexport async function renderToSvg(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<string> {\n const tree = buildTree(spec, options);\n const { width, height } = getDimensions(spec, options);\n\n return satori(tree as React.ReactNode, {\n width,\n height,\n fonts: options.fonts ?? [],\n });\n}\n\n/**\n * Render a json-render spec to a PNG buffer.\n *\n * Requires `@resvg/resvg-js` to be installed as a peer dependency.\n * The SVG is first generated via Satori, then rasterized to PNG.\n */\nexport async function renderToPng(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<Uint8Array> {\n const svg = await renderToSvg(spec, options);\n\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n const mod = await import(\"@resvg/resvg-js\");\n Resvg = mod.Resvg;\n } catch {\n throw new Error(\n \"@resvg/resvg-js is required for PNG output. Install it with: npm install @resvg/resvg-js\",\n );\n }\n\n const resvg = new Resvg(svg);\n const pngData = resvg.render();\n return pngData.asPng();\n}\n","import React from \"react\";\nimport type { ComponentRenderProps, ComponentRegistry } from \"../types\";\nimport type { StandardComponentProps } from \"../catalog\";\n\nconst headingSizes: Record<string, number> = {\n h1: 48,\n h2: 36,\n h3: 28,\n h4: 22,\n};\n\nconst headingWeights: Record<string, number> = {\n h1: 700,\n h2: 700,\n h3: 600,\n h4: 600,\n};\n\n/**\n * Satori crashes on explicit `undefined` style values (e.g. `padding: undefined`).\n * Strip them so only defined properties are passed.\n */\nfunction cleanStyle(raw: Record<string, unknown>): React.CSSProperties {\n const out: Record<string, unknown> = {};\n for (const k in raw) {\n if (raw[k] !== undefined && raw[k] !== null) {\n out[k] = raw[k];\n }\n }\n return out as React.CSSProperties;\n}\n\n// =============================================================================\n// Root\n// =============================================================================\n\nfunction FrameComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Frame\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: p.display ?? \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n width: p.width,\n height: p.height,\n backgroundColor: p.backgroundColor ?? \"white\",\n padding: p.padding,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Layout Components\n// =============================================================================\n\nfunction BoxComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Box\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n padding: p.padding,\n paddingTop: p.paddingTop,\n paddingBottom: p.paddingBottom,\n paddingLeft: p.paddingLeft,\n paddingRight: p.paddingRight,\n margin: p.margin,\n backgroundColor: p.backgroundColor,\n borderWidth: p.borderWidth,\n borderColor: p.borderColor,\n borderRadius: p.borderRadius,\n borderStyle: p.borderWidth ? \"solid\" : undefined,\n flex: p.flex,\n width: p.width,\n height: p.height,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n position: p.position,\n top: p.top,\n left: p.left,\n right: p.right,\n bottom: p.bottom,\n overflow: p.overflow,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction RowComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Row\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"row\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n flexWrap: p.wrap ? \"wrap\" : undefined,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction ColumnComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Column\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"column\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Content Components\n// =============================================================================\n\nfunction HeadingComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Heading\">>) {\n const p = element.props;\n const level = p.level ?? \"h2\";\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: headingSizes[level] ?? 36,\n fontWeight: headingWeights[level] ?? 700,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n letterSpacing: p.letterSpacing,\n lineHeight: p.lineHeight ?? 1.2,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction TextComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Text\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: p.fontSize ?? 16,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n fontWeight: p.fontWeight === \"bold\" ? 700 : 400,\n fontStyle: p.fontStyle ?? \"normal\",\n lineHeight: p.lineHeight ?? 1.4,\n letterSpacing: p.letterSpacing,\n textDecoration: p.textDecoration,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction ImageComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Image\">>) {\n const p = element.props;\n\n return (\n <img\n src={p.src}\n width={p.width ?? undefined}\n height={p.height ?? undefined}\n style={cleanStyle({\n borderRadius: p.borderRadius,\n objectFit: p.objectFit ?? \"contain\",\n })}\n />\n );\n}\n\n// =============================================================================\n// Decorative Components\n// =============================================================================\n\nfunction DividerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Divider\">>) {\n const p = element.props;\n\n return (\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n borderBottom: `${p.thickness ?? 1}px solid ${p.color ?? \"#e5e7eb\"}`,\n marginTop: p.marginTop ?? 8,\n marginBottom: p.marginBottom ?? 8,\n }}\n />\n );\n}\n\nfunction SpacerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Spacer\">>) {\n const p = element.props;\n\n return <div style={{ display: \"flex\", height: p.height ?? 20 }} />;\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\nexport const standardComponents: ComponentRegistry = {\n Frame: FrameComponent,\n Box: BoxComponent,\n Row: RowComponent,\n Column: ColumnComponent,\n Heading: HeadingComponent,\n Text: TextComponent,\n Image: ImageComponent,\n Divider: DividerComponent,\n Spacer: SpacerComponent,\n};\n"],"mappings":"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","names":["jsx","children","jsx"]}
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,8 @@ var schema = (0, import_core.defineSchema)(
|
|
|
49
49
|
type: s.ref("catalog.components"),
|
|
50
50
|
props: s.propsOf("catalog.components"),
|
|
51
51
|
children: s.array(s.string()),
|
|
52
|
-
visible: s.any()
|
|
52
|
+
visible: { ...s.any(), ...s.optional() },
|
|
53
|
+
repeat: { ...s.any(), ...s.optional() }
|
|
53
54
|
})
|
|
54
55
|
)
|
|
55
56
|
}),
|
|
@@ -70,7 +71,8 @@ var schema = (0, import_core.defineSchema)(
|
|
|
70
71
|
"All text content must use Heading or Text components. Raw strings are not supported.",
|
|
71
72
|
"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.",
|
|
72
73
|
"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.",
|
|
73
|
-
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist."
|
|
74
|
+
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.",
|
|
75
|
+
'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.'
|
|
74
76
|
]
|
|
75
77
|
}
|
|
76
78
|
);
|
|
@@ -324,12 +326,18 @@ function renderElement(elementKey, spec, registry, stateModel, repeatItem, repea
|
|
|
324
326
|
const Component = registry[resolvedElement.type];
|
|
325
327
|
if (!Component) return null;
|
|
326
328
|
if (resolvedElement.repeat) {
|
|
327
|
-
const
|
|
329
|
+
const repeat = resolvedElement.repeat;
|
|
330
|
+
const statePath = (0, import_core2.resolveRepeatStatePath)(repeat.statePath, repeatBasePath);
|
|
331
|
+
if (statePath === void 0) {
|
|
332
|
+
console.warn(
|
|
333
|
+
"[json-render/image] $item in repeat.statePath used outside of a repeat scope"
|
|
334
|
+
);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const items = (0, import_core2.getByPath)(stateModel, statePath) ?? [];
|
|
328
338
|
const fragments = items.map((item, index) => {
|
|
329
|
-
const key =
|
|
330
|
-
|
|
331
|
-
) : String(index);
|
|
332
|
-
const childPath = `${resolvedElement.repeat.statePath}/${index}`;
|
|
339
|
+
const key = repeat.key && typeof item === "object" && item !== null ? String(item[repeat.key] ?? index) : String(index);
|
|
340
|
+
const childPath = (0, import_core2.resolveRepeatItemStatePath)(statePath, index);
|
|
333
341
|
const children2 = resolvedElement.children?.map(
|
|
334
342
|
(childKey) => renderElement(
|
|
335
343
|
childKey,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/schema.ts","../src/components/standard.tsx","../src/render.tsx","../src/catalog.ts"],"sourcesContent":["// Schema\nexport { schema, type ImageSchema, type ImageSpec } from \"./schema\";\n\n// Core types (re-exported for convenience)\nexport type { Spec } from \"@json-render/core\";\n\n// Catalog-aware types\nexport type {\n SetState,\n StateModel,\n ComponentContext,\n ComponentFn,\n Components,\n} from \"./catalog-types\";\n\n// Renderer types\nexport type {\n ComponentRenderProps,\n ComponentRenderer,\n ComponentRegistry,\n} from \"./types\";\n\n// Standard components\nexport { standardComponents } from \"./components\";\n\n// Server-side render functions\nexport { renderToSvg, renderToPng, type RenderOptions } from \"./render\";\n\n// Catalog definitions\nexport {\n standardComponentDefinitions,\n type StandardComponentDefinitions,\n type StandardComponentProps,\n} from \"./catalog\";\n","import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/image\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas\n *\n * Reuses the same { root, elements } spec format as the React and React PDF renderers.\n */\nexport const schema = defineSchema(\n (s) => ({\n spec: s.object({\n root: s.string(),\n elements: s.record(\n s.object({\n type: s.ref(\"catalog.components\"),\n props: s.propsOf(\"catalog.components\"),\n children: s.array(s.string()),\n visible: s.any(),\n }),\n ),\n }),\n\n catalog: s.object({\n components: s.map({\n props: s.zod(),\n slots: s.array(s.string()),\n description: s.string(),\n example: s.any(),\n }),\n }),\n }),\n {\n defaultRules: [\n \"The root element MUST be a Frame component. It defines the image dimensions (width, height) and background.\",\n \"Frame width and height determine the output image size. Common sizes: 1200x630 (OG image), 1080x1080 (social square), 1920x1080 (banner).\",\n \"Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.\",\n \"All text content must use Heading or Text components. Raw strings are not supported.\",\n \"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.\",\n \"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.\",\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.\",\n ],\n },\n);\n\nexport type ImageSchema = typeof schema;\n\nexport type ImageSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n","import React from \"react\";\nimport type { ComponentRenderProps, ComponentRegistry } from \"../types\";\nimport type { StandardComponentProps } from \"../catalog\";\n\nconst headingSizes: Record<string, number> = {\n h1: 48,\n h2: 36,\n h3: 28,\n h4: 22,\n};\n\nconst headingWeights: Record<string, number> = {\n h1: 700,\n h2: 700,\n h3: 600,\n h4: 600,\n};\n\n/**\n * Satori crashes on explicit `undefined` style values (e.g. `padding: undefined`).\n * Strip them so only defined properties are passed.\n */\nfunction cleanStyle(raw: Record<string, unknown>): React.CSSProperties {\n const out: Record<string, unknown> = {};\n for (const k in raw) {\n if (raw[k] !== undefined && raw[k] !== null) {\n out[k] = raw[k];\n }\n }\n return out as React.CSSProperties;\n}\n\n// =============================================================================\n// Root\n// =============================================================================\n\nfunction FrameComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Frame\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: p.display ?? \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n width: p.width,\n height: p.height,\n backgroundColor: p.backgroundColor ?? \"white\",\n padding: p.padding,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Layout Components\n// =============================================================================\n\nfunction BoxComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Box\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n padding: p.padding,\n paddingTop: p.paddingTop,\n paddingBottom: p.paddingBottom,\n paddingLeft: p.paddingLeft,\n paddingRight: p.paddingRight,\n margin: p.margin,\n backgroundColor: p.backgroundColor,\n borderWidth: p.borderWidth,\n borderColor: p.borderColor,\n borderRadius: p.borderRadius,\n borderStyle: p.borderWidth ? \"solid\" : undefined,\n flex: p.flex,\n width: p.width,\n height: p.height,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n position: p.position,\n top: p.top,\n left: p.left,\n right: p.right,\n bottom: p.bottom,\n overflow: p.overflow,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction RowComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Row\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"row\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n flexWrap: p.wrap ? \"wrap\" : undefined,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction ColumnComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Column\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"column\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Content Components\n// =============================================================================\n\nfunction HeadingComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Heading\">>) {\n const p = element.props;\n const level = p.level ?? \"h2\";\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: headingSizes[level] ?? 36,\n fontWeight: headingWeights[level] ?? 700,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n letterSpacing: p.letterSpacing,\n lineHeight: p.lineHeight ?? 1.2,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction TextComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Text\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: p.fontSize ?? 16,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n fontWeight: p.fontWeight === \"bold\" ? 700 : 400,\n fontStyle: p.fontStyle ?? \"normal\",\n lineHeight: p.lineHeight ?? 1.4,\n letterSpacing: p.letterSpacing,\n textDecoration: p.textDecoration,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction ImageComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Image\">>) {\n const p = element.props;\n\n return (\n <img\n src={p.src}\n width={p.width ?? undefined}\n height={p.height ?? undefined}\n style={cleanStyle({\n borderRadius: p.borderRadius,\n objectFit: p.objectFit ?? \"contain\",\n })}\n />\n );\n}\n\n// =============================================================================\n// Decorative Components\n// =============================================================================\n\nfunction DividerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Divider\">>) {\n const p = element.props;\n\n return (\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n borderBottom: `${p.thickness ?? 1}px solid ${p.color ?? \"#e5e7eb\"}`,\n marginTop: p.marginTop ?? 8,\n marginBottom: p.marginBottom ?? 8,\n }}\n />\n );\n}\n\nfunction SpacerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Spacer\">>) {\n const p = element.props;\n\n return <div style={{ display: \"flex\", height: p.height ?? 20 }} />;\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\nexport const standardComponents: ComponentRegistry = {\n Frame: FrameComponent,\n Box: BoxComponent,\n Row: RowComponent,\n Column: ColumnComponent,\n Heading: HeadingComponent,\n Text: TextComponent,\n Image: ImageComponent,\n Divider: DividerComponent,\n Spacer: SpacerComponent,\n};\n","import React from \"react\";\nimport satori, { type SatoriOptions } from \"satori\";\nimport type { Spec, UIElement } from \"@json-render/core\";\nimport {\n resolveElementProps,\n evaluateVisibility,\n getByPath,\n type PropResolutionContext,\n} from \"@json-render/core\";\nimport { standardComponents } from \"./components/standard\";\nimport type { ComponentRegistry } from \"./types\";\n\nexport { standardComponents };\n\nexport interface RenderOptions {\n registry?: ComponentRegistry;\n includeStandard?: boolean;\n state?: Record<string, unknown>;\n fonts?: SatoriOptions[\"fonts\"];\n /** Override the Frame width. When omitted, uses the Frame component's width prop. */\n width?: number;\n /** Override the Frame height. When omitted, uses the Frame component's height prop. */\n height?: number;\n}\n\nconst noopEmit = () => {};\n\nfunction renderElement(\n elementKey: string,\n spec: Spec,\n registry: ComponentRegistry,\n stateModel: Record<string, unknown>,\n repeatItem?: unknown,\n repeatIndex?: number,\n repeatBasePath?: string,\n): React.ReactElement | null {\n const element = spec.elements[elementKey];\n if (!element) return null;\n\n const ctx: PropResolutionContext = {\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n };\n\n if (element.visible !== undefined) {\n if (!evaluateVisibility(element.visible, ctx)) {\n return null;\n }\n }\n\n const resolvedProps = resolveElementProps(\n element.props as Record<string, unknown>,\n ctx,\n );\n const resolvedElement: UIElement = { ...element, props: resolvedProps };\n\n const Component = registry[resolvedElement.type];\n if (!Component) return null;\n\n if (resolvedElement.repeat) {\n const items =\n (getByPath(stateModel, resolvedElement.repeat.statePath) as\n | unknown[]\n | undefined) ?? [];\n\n const fragments = items.map((item, index) => {\n const key =\n resolvedElement.repeat!.key && typeof item === \"object\" && item !== null\n ? String(\n (item as Record<string, unknown>)[resolvedElement.repeat!.key!] ??\n index,\n )\n : String(index);\n\n const childPath = `${resolvedElement.repeat!.statePath}/${index}`;\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n item,\n index,\n childPath,\n ),\n );\n\n return (\n <Component key={key} element={resolvedElement} emit={noopEmit}>\n {children}\n </Component>\n );\n });\n\n return <>{fragments}</>;\n }\n\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n ),\n );\n\n return (\n <Component key={elementKey} element={resolvedElement} emit={noopEmit}>\n {children && children.length > 0 ? children : undefined}\n </Component>\n );\n}\n\ninterface ImageDimensions {\n width: number;\n height: number;\n}\n\nfunction getDimensions(\n spec: Spec,\n options: RenderOptions = {},\n): ImageDimensions {\n if (options.width && options.height) {\n return { width: options.width, height: options.height };\n }\n\n const rootElement = spec.elements[spec.root];\n const props = rootElement?.props as Record<string, unknown> | undefined;\n\n return {\n width: options.width ?? (props?.width as number) ?? 1200,\n height: options.height ?? (props?.height as number) ?? 630,\n };\n}\n\nfunction buildTree(\n spec: Spec,\n options: RenderOptions = {},\n): React.ReactElement {\n const {\n registry: customRegistry,\n includeStandard = true,\n state = {},\n } = options;\n\n const mergedState: Record<string, unknown> = {\n ...spec.state,\n ...state,\n };\n\n const registry: ComponentRegistry = {\n ...(includeStandard ? standardComponents : {}),\n ...customRegistry,\n };\n\n const root = renderElement(spec.root, spec, registry, mergedState);\n return root ?? <></>;\n}\n\n/**\n * Render a json-render spec to an SVG string.\n *\n * Uses Satori to convert the spec's component tree into SVG.\n * No additional dependencies are needed beyond satori.\n */\nexport async function renderToSvg(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<string> {\n const tree = buildTree(spec, options);\n const { width, height } = getDimensions(spec, options);\n\n return satori(tree as React.ReactNode, {\n width,\n height,\n fonts: options.fonts ?? [],\n });\n}\n\n/**\n * Render a json-render spec to a PNG buffer.\n *\n * Requires `@resvg/resvg-js` to be installed as a peer dependency.\n * The SVG is first generated via Satori, then rasterized to PNG.\n */\nexport async function renderToPng(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<Uint8Array> {\n const svg = await renderToSvg(spec, options);\n\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n const mod = await import(\"@resvg/resvg-js\");\n Resvg = mod.Resvg;\n } catch {\n throw new Error(\n \"@resvg/resvg-js is required for PNG output. Install it with: npm install @resvg/resvg-js\",\n );\n }\n\n const resvg = new Resvg(svg);\n const pngData = resvg.render();\n return pngData.asPng();\n}\n","import { z } from \"zod\";\n\n/**\n * Standard component definitions for image catalogs.\n *\n * These define the available image components with their Zod prop schemas.\n * All components render to Satori-compatible JSX (HTML-like elements with\n * inline CSS flexbox styles).\n */\nexport const standardComponentDefinitions = {\n // ==========================================================================\n // Root\n // ==========================================================================\n\n Frame: {\n props: z.object({\n width: z.number(),\n height: z.number(),\n backgroundColor: z.string().nullable(),\n padding: z.number().nullable(),\n display: z.enum([\"flex\", \"none\"]).nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Root image container. Defines the output image dimensions and background. Must be the root element.\",\n example: { width: 1200, height: 630, backgroundColor: \"#ffffff\" },\n },\n\n // ==========================================================================\n // Layout Components\n // ==========================================================================\n\n Box: {\n props: z.object({\n padding: z.number().nullable(),\n paddingTop: z.number().nullable(),\n paddingBottom: z.number().nullable(),\n paddingLeft: z.number().nullable(),\n paddingRight: z.number().nullable(),\n margin: z.number().nullable(),\n backgroundColor: z.string().nullable(),\n borderWidth: z.number().nullable(),\n borderColor: z.string().nullable(),\n borderRadius: z.number().nullable(),\n flex: z.number().nullable(),\n width: z.union([z.number(), z.string()]).nullable(),\n height: z.union([z.number(), z.string()]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n position: z.enum([\"relative\", \"absolute\"]).nullable(),\n top: z.number().nullable(),\n left: z.number().nullable(),\n right: z.number().nullable(),\n bottom: z.number().nullable(),\n overflow: z.enum([\"visible\", \"hidden\"]).nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Generic container with padding, margin, background, border, and flex alignment. Supports absolute positioning.\",\n example: {\n padding: 20,\n backgroundColor: \"#f9f9f9\",\n borderRadius: 8,\n alignItems: \"center\",\n },\n },\n\n Row: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n wrap: z.boolean().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Horizontal flex layout. Use for placing elements side by side.\",\n example: { gap: 10, alignItems: \"center\" },\n },\n\n Column: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Vertical flex layout. Use for stacking elements top to bottom.\",\n example: { gap: 8, padding: 10 },\n },\n\n // ==========================================================================\n // Content Components\n // ==========================================================================\n\n Heading: {\n props: z.object({\n text: z.string(),\n level: z.enum([\"h1\", \"h2\", \"h3\", \"h4\"]).nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n lineHeight: z.number().nullable(),\n }),\n slots: [],\n description:\n \"Heading text at various levels. h1 is largest, h4 is smallest.\",\n example: { text: \"Hello World\", level: \"h1\", color: \"#000000\" },\n },\n\n Text: {\n props: z.object({\n text: z.string(),\n fontSize: z.number().nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n fontWeight: z.enum([\"normal\", \"bold\"]).nullable(),\n fontStyle: z.enum([\"normal\", \"italic\"]).nullable(),\n lineHeight: z.number().nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n textDecoration: z.enum([\"none\", \"underline\", \"line-through\"]).nullable(),\n }),\n slots: [],\n description:\n \"Body text with configurable size, color, weight, and alignment.\",\n example: { text: \"Some content here.\", fontSize: 16, color: \"#333333\" },\n },\n\n Image: {\n props: z.object({\n src: z.string(),\n width: z.number().nullable(),\n height: z.number().nullable(),\n borderRadius: z.number().nullable(),\n objectFit: z.enum([\"contain\", \"cover\", \"fill\", \"none\"]).nullable(),\n }),\n slots: [],\n description:\n \"Image from a URL. Specify width and/or height to control size. For placeholder images use https://picsum.photos/{width}/{height}?random={n}.\",\n example: {\n src: \"https://picsum.photos/400/300?random=1\",\n width: 400,\n height: 300,\n },\n },\n\n // ==========================================================================\n // Decorative Components\n // ==========================================================================\n\n Divider: {\n props: z.object({\n color: z.string().nullable(),\n thickness: z.number().nullable(),\n marginTop: z.number().nullable(),\n marginBottom: z.number().nullable(),\n }),\n slots: [],\n description: \"Horizontal line separator between content sections.\",\n example: { color: \"#e5e7eb\", thickness: 1 },\n },\n\n Spacer: {\n props: z.object({\n height: z.number().nullable(),\n }),\n slots: [],\n description: \"Empty vertical space between elements.\",\n example: { height: 20 },\n },\n};\n\nexport type StandardComponentDefinitions = typeof standardComponentDefinitions;\n\nexport type StandardComponentProps<\n K extends keyof StandardComponentDefinitions,\n> = StandardComponentDefinitions[K][\"props\"] extends { _output: infer O }\n ? O\n : z.output<StandardComponentDefinitions[K][\"props\"]>;\n"],"mappings":"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","names":["import_core","import_jsx_runtime","children","satori"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/schema.ts","../src/components/standard.tsx","../src/render.tsx","../src/catalog.ts"],"sourcesContent":["// Schema\nexport { schema, type ImageSchema, type ImageSpec } from \"./schema\";\n\n// Core types (re-exported for convenience)\nexport type { Spec } from \"@json-render/core\";\n\n// Catalog-aware types\nexport type {\n SetState,\n StateModel,\n ComponentContext,\n ComponentFn,\n Components,\n} from \"./catalog-types\";\n\n// Renderer types\nexport type {\n ComponentRenderProps,\n ComponentRenderer,\n ComponentRegistry,\n} from \"./types\";\n\n// Standard components\nexport { standardComponents } from \"./components\";\n\n// Server-side render functions\nexport { renderToSvg, renderToPng, type RenderOptions } from \"./render\";\n\n// Catalog definitions\nexport {\n standardComponentDefinitions,\n type StandardComponentDefinitions,\n type StandardComponentProps,\n} from \"./catalog\";\n","import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/image\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas\n *\n * Reuses the same { root, elements } spec format as the React and React PDF renderers.\n */\nexport const schema = defineSchema(\n (s) => ({\n spec: s.object({\n root: s.string(),\n elements: s.record(\n s.object({\n type: s.ref(\"catalog.components\"),\n props: s.propsOf(\"catalog.components\"),\n children: s.array(s.string()),\n visible: { ...s.any(), ...s.optional() },\n repeat: { ...s.any(), ...s.optional() },\n }),\n ),\n }),\n\n catalog: s.object({\n components: s.map({\n props: s.zod(),\n slots: s.array(s.string()),\n description: s.string(),\n example: s.any(),\n }),\n }),\n }),\n {\n defaultRules: [\n \"The root element MUST be a Frame component. It defines the image dimensions (width, height) and background.\",\n \"Frame width and height determine the output image size. Common sizes: 1200x630 (OG image), 1080x1080 (social square), 1920x1080 (banner).\",\n \"Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.\",\n \"All text content must use Heading or Text components. Raw strings are not supported.\",\n \"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.\",\n \"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.\",\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.\",\n 'REQUIRED FIELDS: Every element MUST include a \"children\" array. Leaf elements (text, badges, inputs, images) use an empty array: \"children\": []. Omitting \"children\" fails validation.',\n ],\n },\n);\n\nexport type ImageSchema = typeof schema;\n\nexport type ImageSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n","import React from \"react\";\nimport type { ComponentRenderProps, ComponentRegistry } from \"../types\";\nimport type { StandardComponentProps } from \"../catalog\";\n\nconst headingSizes: Record<string, number> = {\n h1: 48,\n h2: 36,\n h3: 28,\n h4: 22,\n};\n\nconst headingWeights: Record<string, number> = {\n h1: 700,\n h2: 700,\n h3: 600,\n h4: 600,\n};\n\n/**\n * Satori crashes on explicit `undefined` style values (e.g. `padding: undefined`).\n * Strip them so only defined properties are passed.\n */\nfunction cleanStyle(raw: Record<string, unknown>): React.CSSProperties {\n const out: Record<string, unknown> = {};\n for (const k in raw) {\n if (raw[k] !== undefined && raw[k] !== null) {\n out[k] = raw[k];\n }\n }\n return out as React.CSSProperties;\n}\n\n// =============================================================================\n// Root\n// =============================================================================\n\nfunction FrameComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Frame\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: p.display ?? \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n width: p.width,\n height: p.height,\n backgroundColor: p.backgroundColor ?? \"white\",\n padding: p.padding,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Layout Components\n// =============================================================================\n\nfunction BoxComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Box\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n padding: p.padding,\n paddingTop: p.paddingTop,\n paddingBottom: p.paddingBottom,\n paddingLeft: p.paddingLeft,\n paddingRight: p.paddingRight,\n margin: p.margin,\n backgroundColor: p.backgroundColor,\n borderWidth: p.borderWidth,\n borderColor: p.borderColor,\n borderRadius: p.borderRadius,\n borderStyle: p.borderWidth ? \"solid\" : undefined,\n flex: p.flex,\n width: p.width,\n height: p.height,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n position: p.position,\n top: p.top,\n left: p.left,\n right: p.right,\n bottom: p.bottom,\n overflow: p.overflow,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction RowComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Row\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"row\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n flexWrap: p.wrap ? \"wrap\" : undefined,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction ColumnComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Column\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"column\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Content Components\n// =============================================================================\n\nfunction HeadingComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Heading\">>) {\n const p = element.props;\n const level = p.level ?? \"h2\";\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: headingSizes[level] ?? 36,\n fontWeight: headingWeights[level] ?? 700,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n letterSpacing: p.letterSpacing,\n lineHeight: p.lineHeight ?? 1.2,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction TextComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Text\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: p.fontSize ?? 16,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n fontWeight: p.fontWeight === \"bold\" ? 700 : 400,\n fontStyle: p.fontStyle ?? \"normal\",\n lineHeight: p.lineHeight ?? 1.4,\n letterSpacing: p.letterSpacing,\n textDecoration: p.textDecoration,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction ImageComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Image\">>) {\n const p = element.props;\n\n return (\n <img\n src={p.src}\n width={p.width ?? undefined}\n height={p.height ?? undefined}\n style={cleanStyle({\n borderRadius: p.borderRadius,\n objectFit: p.objectFit ?? \"contain\",\n })}\n />\n );\n}\n\n// =============================================================================\n// Decorative Components\n// =============================================================================\n\nfunction DividerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Divider\">>) {\n const p = element.props;\n\n return (\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n borderBottom: `${p.thickness ?? 1}px solid ${p.color ?? \"#e5e7eb\"}`,\n marginTop: p.marginTop ?? 8,\n marginBottom: p.marginBottom ?? 8,\n }}\n />\n );\n}\n\nfunction SpacerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Spacer\">>) {\n const p = element.props;\n\n return <div style={{ display: \"flex\", height: p.height ?? 20 }} />;\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\nexport const standardComponents: ComponentRegistry = {\n Frame: FrameComponent,\n Box: BoxComponent,\n Row: RowComponent,\n Column: ColumnComponent,\n Heading: HeadingComponent,\n Text: TextComponent,\n Image: ImageComponent,\n Divider: DividerComponent,\n Spacer: SpacerComponent,\n};\n","import React from \"react\";\nimport satori, { type SatoriOptions } from \"satori\";\nimport type { Spec, UIElement } from \"@json-render/core\";\nimport {\n resolveElementProps,\n resolveRepeatItemStatePath,\n resolveRepeatStatePath,\n evaluateVisibility,\n getByPath,\n type PropResolutionContext,\n} from \"@json-render/core\";\nimport { standardComponents } from \"./components/standard\";\nimport type { ComponentRegistry } from \"./types\";\n\nexport { standardComponents };\n\nexport interface RenderOptions {\n registry?: ComponentRegistry;\n includeStandard?: boolean;\n state?: Record<string, unknown>;\n fonts?: SatoriOptions[\"fonts\"];\n /** Override the Frame width. When omitted, uses the Frame component's width prop. */\n width?: number;\n /** Override the Frame height. When omitted, uses the Frame component's height prop. */\n height?: number;\n}\n\nconst noopEmit = () => {};\n\nfunction renderElement(\n elementKey: string,\n spec: Spec,\n registry: ComponentRegistry,\n stateModel: Record<string, unknown>,\n repeatItem?: unknown,\n repeatIndex?: number,\n repeatBasePath?: string,\n): React.ReactElement | null {\n const element = spec.elements[elementKey];\n if (!element) return null;\n\n const ctx: PropResolutionContext = {\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n };\n\n if (element.visible !== undefined) {\n if (!evaluateVisibility(element.visible, ctx)) {\n return null;\n }\n }\n\n const resolvedProps = resolveElementProps(\n element.props as Record<string, unknown>,\n ctx,\n );\n const resolvedElement: UIElement = { ...element, props: resolvedProps };\n\n const Component = registry[resolvedElement.type];\n if (!Component) return null;\n\n if (resolvedElement.repeat) {\n const repeat = resolvedElement.repeat;\n const statePath = resolveRepeatStatePath(repeat.statePath, repeatBasePath);\n if (statePath === undefined) {\n console.warn(\n \"[json-render/image] $item in repeat.statePath used outside of a repeat scope\",\n );\n return null;\n }\n\n const items =\n (getByPath(stateModel, statePath) as unknown[] | undefined) ?? [];\n\n const fragments = items.map((item, index) => {\n const key =\n repeat.key && typeof item === \"object\" && item !== null\n ? String((item as Record<string, unknown>)[repeat.key!] ?? index)\n : String(index);\n\n const childPath = resolveRepeatItemStatePath(statePath, index);\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n item,\n index,\n childPath,\n ),\n );\n\n return (\n <Component key={key} element={resolvedElement} emit={noopEmit}>\n {children}\n </Component>\n );\n });\n\n return <>{fragments}</>;\n }\n\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n ),\n );\n\n return (\n <Component key={elementKey} element={resolvedElement} emit={noopEmit}>\n {children && children.length > 0 ? children : undefined}\n </Component>\n );\n}\n\ninterface ImageDimensions {\n width: number;\n height: number;\n}\n\nfunction getDimensions(\n spec: Spec,\n options: RenderOptions = {},\n): ImageDimensions {\n if (options.width && options.height) {\n return { width: options.width, height: options.height };\n }\n\n const rootElement = spec.elements[spec.root];\n const props = rootElement?.props as Record<string, unknown> | undefined;\n\n return {\n width: options.width ?? (props?.width as number) ?? 1200,\n height: options.height ?? (props?.height as number) ?? 630,\n };\n}\n\nfunction buildTree(\n spec: Spec,\n options: RenderOptions = {},\n): React.ReactElement {\n const {\n registry: customRegistry,\n includeStandard = true,\n state = {},\n } = options;\n\n const mergedState: Record<string, unknown> = {\n ...spec.state,\n ...state,\n };\n\n const registry: ComponentRegistry = {\n ...(includeStandard ? standardComponents : {}),\n ...customRegistry,\n };\n\n const root = renderElement(spec.root, spec, registry, mergedState);\n return root ?? <></>;\n}\n\n/**\n * Render a json-render spec to an SVG string.\n *\n * Uses Satori to convert the spec's component tree into SVG.\n * No additional dependencies are needed beyond satori.\n */\nexport async function renderToSvg(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<string> {\n const tree = buildTree(spec, options);\n const { width, height } = getDimensions(spec, options);\n\n return satori(tree as React.ReactNode, {\n width,\n height,\n fonts: options.fonts ?? [],\n });\n}\n\n/**\n * Render a json-render spec to a PNG buffer.\n *\n * Requires `@resvg/resvg-js` to be installed as a peer dependency.\n * The SVG is first generated via Satori, then rasterized to PNG.\n */\nexport async function renderToPng(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<Uint8Array> {\n const svg = await renderToSvg(spec, options);\n\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n const mod = await import(\"@resvg/resvg-js\");\n Resvg = mod.Resvg;\n } catch {\n throw new Error(\n \"@resvg/resvg-js is required for PNG output. Install it with: npm install @resvg/resvg-js\",\n );\n }\n\n const resvg = new Resvg(svg);\n const pngData = resvg.render();\n return pngData.asPng();\n}\n","import { z } from \"zod\";\n\n/**\n * Standard component definitions for image catalogs.\n *\n * These define the available image components with their Zod prop schemas.\n * All components render to Satori-compatible JSX (HTML-like elements with\n * inline CSS flexbox styles).\n */\nexport const standardComponentDefinitions = {\n // ==========================================================================\n // Root\n // ==========================================================================\n\n Frame: {\n props: z.object({\n width: z.number(),\n height: z.number(),\n backgroundColor: z.string().nullable(),\n padding: z.number().nullable(),\n display: z.enum([\"flex\", \"none\"]).nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Root image container. Defines the output image dimensions and background. Must be the root element.\",\n example: { width: 1200, height: 630, backgroundColor: \"#ffffff\" },\n },\n\n // ==========================================================================\n // Layout Components\n // ==========================================================================\n\n Box: {\n props: z.object({\n padding: z.number().nullable(),\n paddingTop: z.number().nullable(),\n paddingBottom: z.number().nullable(),\n paddingLeft: z.number().nullable(),\n paddingRight: z.number().nullable(),\n margin: z.number().nullable(),\n backgroundColor: z.string().nullable(),\n borderWidth: z.number().nullable(),\n borderColor: z.string().nullable(),\n borderRadius: z.number().nullable(),\n flex: z.number().nullable(),\n width: z.union([z.number(), z.string()]).nullable(),\n height: z.union([z.number(), z.string()]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n position: z.enum([\"relative\", \"absolute\"]).nullable(),\n top: z.number().nullable(),\n left: z.number().nullable(),\n right: z.number().nullable(),\n bottom: z.number().nullable(),\n overflow: z.enum([\"visible\", \"hidden\"]).nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Generic container with padding, margin, background, border, and flex alignment. Supports absolute positioning.\",\n example: {\n padding: 20,\n backgroundColor: \"#f9f9f9\",\n borderRadius: 8,\n alignItems: \"center\",\n },\n },\n\n Row: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n wrap: z.boolean().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Horizontal flex layout. Use for placing elements side by side.\",\n example: { gap: 10, alignItems: \"center\" },\n },\n\n Column: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Vertical flex layout. Use for stacking elements top to bottom.\",\n example: { gap: 8, padding: 10 },\n },\n\n // ==========================================================================\n // Content Components\n // ==========================================================================\n\n Heading: {\n props: z.object({\n text: z.string(),\n level: z.enum([\"h1\", \"h2\", \"h3\", \"h4\"]).nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n lineHeight: z.number().nullable(),\n }),\n slots: [],\n description:\n \"Heading text at various levels. h1 is largest, h4 is smallest.\",\n example: { text: \"Hello World\", level: \"h1\", color: \"#000000\" },\n },\n\n Text: {\n props: z.object({\n text: z.string(),\n fontSize: z.number().nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n fontWeight: z.enum([\"normal\", \"bold\"]).nullable(),\n fontStyle: z.enum([\"normal\", \"italic\"]).nullable(),\n lineHeight: z.number().nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n textDecoration: z.enum([\"none\", \"underline\", \"line-through\"]).nullable(),\n }),\n slots: [],\n description:\n \"Body text with configurable size, color, weight, and alignment.\",\n example: { text: \"Some content here.\", fontSize: 16, color: \"#333333\" },\n },\n\n Image: {\n props: z.object({\n src: z.string(),\n width: z.number().nullable(),\n height: z.number().nullable(),\n borderRadius: z.number().nullable(),\n objectFit: z.enum([\"contain\", \"cover\", \"fill\", \"none\"]).nullable(),\n }),\n slots: [],\n description:\n \"Image from a URL. Specify width and/or height to control size. For placeholder images use https://picsum.photos/{width}/{height}?random={n}.\",\n example: {\n src: \"https://picsum.photos/400/300?random=1\",\n width: 400,\n height: 300,\n },\n },\n\n // ==========================================================================\n // Decorative Components\n // ==========================================================================\n\n Divider: {\n props: z.object({\n color: z.string().nullable(),\n thickness: z.number().nullable(),\n marginTop: z.number().nullable(),\n marginBottom: z.number().nullable(),\n }),\n slots: [],\n description: \"Horizontal line separator between content sections.\",\n example: { color: \"#e5e7eb\", thickness: 1 },\n },\n\n Spacer: {\n props: z.object({\n height: z.number().nullable(),\n }),\n slots: [],\n description: \"Empty vertical space between elements.\",\n example: { height: 20 },\n },\n};\n\nexport type StandardComponentDefinitions = typeof standardComponentDefinitions;\n\nexport type StandardComponentProps<\n K extends keyof StandardComponentDefinitions,\n> = StandardComponentDefinitions[K][\"props\"] extends { _output: infer O }\n ? O\n : z.output<StandardComponentDefinitions[K][\"props\"]>;\n"],"mappings":"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","names":["import_core","import_jsx_runtime","children","satori"]}
|
package/dist/index.mjs
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
renderToPng,
|
|
3
3
|
renderToSvg,
|
|
4
4
|
standardComponents
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FPTDH3PH.mjs";
|
|
6
6
|
import {
|
|
7
7
|
schema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BUDISVCZ.mjs";
|
|
9
9
|
import {
|
|
10
10
|
standardComponentDefinitions
|
|
11
11
|
} from "./chunk-PMEELRVP.mjs";
|
package/dist/render.js
CHANGED
|
@@ -285,12 +285,18 @@ function renderElement(elementKey, spec, registry, stateModel, repeatItem, repea
|
|
|
285
285
|
const Component = registry[resolvedElement.type];
|
|
286
286
|
if (!Component) return null;
|
|
287
287
|
if (resolvedElement.repeat) {
|
|
288
|
-
const
|
|
288
|
+
const repeat = resolvedElement.repeat;
|
|
289
|
+
const statePath = (0, import_core.resolveRepeatStatePath)(repeat.statePath, repeatBasePath);
|
|
290
|
+
if (statePath === void 0) {
|
|
291
|
+
console.warn(
|
|
292
|
+
"[json-render/image] $item in repeat.statePath used outside of a repeat scope"
|
|
293
|
+
);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const items = (0, import_core.getByPath)(stateModel, statePath) ?? [];
|
|
289
297
|
const fragments = items.map((item, index) => {
|
|
290
|
-
const key =
|
|
291
|
-
|
|
292
|
-
) : String(index);
|
|
293
|
-
const childPath = `${resolvedElement.repeat.statePath}/${index}`;
|
|
298
|
+
const key = repeat.key && typeof item === "object" && item !== null ? String(item[repeat.key] ?? index) : String(index);
|
|
299
|
+
const childPath = (0, import_core.resolveRepeatItemStatePath)(statePath, index);
|
|
294
300
|
const children2 = resolvedElement.children?.map(
|
|
295
301
|
(childKey) => renderElement(
|
|
296
302
|
childKey,
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render.tsx","../src/components/standard.tsx"],"sourcesContent":["import React from \"react\";\nimport satori, { type SatoriOptions } from \"satori\";\nimport type { Spec, UIElement } from \"@json-render/core\";\nimport {\n resolveElementProps,\n evaluateVisibility,\n getByPath,\n type PropResolutionContext,\n} from \"@json-render/core\";\nimport { standardComponents } from \"./components/standard\";\nimport type { ComponentRegistry } from \"./types\";\n\nexport { standardComponents };\n\nexport interface RenderOptions {\n registry?: ComponentRegistry;\n includeStandard?: boolean;\n state?: Record<string, unknown>;\n fonts?: SatoriOptions[\"fonts\"];\n /** Override the Frame width. When omitted, uses the Frame component's width prop. */\n width?: number;\n /** Override the Frame height. When omitted, uses the Frame component's height prop. */\n height?: number;\n}\n\nconst noopEmit = () => {};\n\nfunction renderElement(\n elementKey: string,\n spec: Spec,\n registry: ComponentRegistry,\n stateModel: Record<string, unknown>,\n repeatItem?: unknown,\n repeatIndex?: number,\n repeatBasePath?: string,\n): React.ReactElement | null {\n const element = spec.elements[elementKey];\n if (!element) return null;\n\n const ctx: PropResolutionContext = {\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n };\n\n if (element.visible !== undefined) {\n if (!evaluateVisibility(element.visible, ctx)) {\n return null;\n }\n }\n\n const resolvedProps = resolveElementProps(\n element.props as Record<string, unknown>,\n ctx,\n );\n const resolvedElement: UIElement = { ...element, props: resolvedProps };\n\n const Component = registry[resolvedElement.type];\n if (!Component) return null;\n\n if (resolvedElement.repeat) {\n const items =\n (getByPath(stateModel, resolvedElement.repeat.statePath) as\n | unknown[]\n | undefined) ?? [];\n\n const fragments = items.map((item, index) => {\n const key =\n resolvedElement.repeat!.key && typeof item === \"object\" && item !== null\n ? String(\n (item as Record<string, unknown>)[resolvedElement.repeat!.key!] ??\n index,\n )\n : String(index);\n\n const childPath = `${resolvedElement.repeat!.statePath}/${index}`;\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n item,\n index,\n childPath,\n ),\n );\n\n return (\n <Component key={key} element={resolvedElement} emit={noopEmit}>\n {children}\n </Component>\n );\n });\n\n return <>{fragments}</>;\n }\n\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n ),\n );\n\n return (\n <Component key={elementKey} element={resolvedElement} emit={noopEmit}>\n {children && children.length > 0 ? children : undefined}\n </Component>\n );\n}\n\ninterface ImageDimensions {\n width: number;\n height: number;\n}\n\nfunction getDimensions(\n spec: Spec,\n options: RenderOptions = {},\n): ImageDimensions {\n if (options.width && options.height) {\n return { width: options.width, height: options.height };\n }\n\n const rootElement = spec.elements[spec.root];\n const props = rootElement?.props as Record<string, unknown> | undefined;\n\n return {\n width: options.width ?? (props?.width as number) ?? 1200,\n height: options.height ?? (props?.height as number) ?? 630,\n };\n}\n\nfunction buildTree(\n spec: Spec,\n options: RenderOptions = {},\n): React.ReactElement {\n const {\n registry: customRegistry,\n includeStandard = true,\n state = {},\n } = options;\n\n const mergedState: Record<string, unknown> = {\n ...spec.state,\n ...state,\n };\n\n const registry: ComponentRegistry = {\n ...(includeStandard ? standardComponents : {}),\n ...customRegistry,\n };\n\n const root = renderElement(spec.root, spec, registry, mergedState);\n return root ?? <></>;\n}\n\n/**\n * Render a json-render spec to an SVG string.\n *\n * Uses Satori to convert the spec's component tree into SVG.\n * No additional dependencies are needed beyond satori.\n */\nexport async function renderToSvg(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<string> {\n const tree = buildTree(spec, options);\n const { width, height } = getDimensions(spec, options);\n\n return satori(tree as React.ReactNode, {\n width,\n height,\n fonts: options.fonts ?? [],\n });\n}\n\n/**\n * Render a json-render spec to a PNG buffer.\n *\n * Requires `@resvg/resvg-js` to be installed as a peer dependency.\n * The SVG is first generated via Satori, then rasterized to PNG.\n */\nexport async function renderToPng(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<Uint8Array> {\n const svg = await renderToSvg(spec, options);\n\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n const mod = await import(\"@resvg/resvg-js\");\n Resvg = mod.Resvg;\n } catch {\n throw new Error(\n \"@resvg/resvg-js is required for PNG output. Install it with: npm install @resvg/resvg-js\",\n );\n }\n\n const resvg = new Resvg(svg);\n const pngData = resvg.render();\n return pngData.asPng();\n}\n","import React from \"react\";\nimport type { ComponentRenderProps, ComponentRegistry } from \"../types\";\nimport type { StandardComponentProps } from \"../catalog\";\n\nconst headingSizes: Record<string, number> = {\n h1: 48,\n h2: 36,\n h3: 28,\n h4: 22,\n};\n\nconst headingWeights: Record<string, number> = {\n h1: 700,\n h2: 700,\n h3: 600,\n h4: 600,\n};\n\n/**\n * Satori crashes on explicit `undefined` style values (e.g. `padding: undefined`).\n * Strip them so only defined properties are passed.\n */\nfunction cleanStyle(raw: Record<string, unknown>): React.CSSProperties {\n const out: Record<string, unknown> = {};\n for (const k in raw) {\n if (raw[k] !== undefined && raw[k] !== null) {\n out[k] = raw[k];\n }\n }\n return out as React.CSSProperties;\n}\n\n// =============================================================================\n// Root\n// =============================================================================\n\nfunction FrameComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Frame\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: p.display ?? \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n width: p.width,\n height: p.height,\n backgroundColor: p.backgroundColor ?? \"white\",\n padding: p.padding,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Layout Components\n// =============================================================================\n\nfunction BoxComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Box\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n padding: p.padding,\n paddingTop: p.paddingTop,\n paddingBottom: p.paddingBottom,\n paddingLeft: p.paddingLeft,\n paddingRight: p.paddingRight,\n margin: p.margin,\n backgroundColor: p.backgroundColor,\n borderWidth: p.borderWidth,\n borderColor: p.borderColor,\n borderRadius: p.borderRadius,\n borderStyle: p.borderWidth ? \"solid\" : undefined,\n flex: p.flex,\n width: p.width,\n height: p.height,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n position: p.position,\n top: p.top,\n left: p.left,\n right: p.right,\n bottom: p.bottom,\n overflow: p.overflow,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction RowComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Row\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"row\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n flexWrap: p.wrap ? \"wrap\" : undefined,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction ColumnComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Column\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"column\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Content Components\n// =============================================================================\n\nfunction HeadingComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Heading\">>) {\n const p = element.props;\n const level = p.level ?? \"h2\";\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: headingSizes[level] ?? 36,\n fontWeight: headingWeights[level] ?? 700,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n letterSpacing: p.letterSpacing,\n lineHeight: p.lineHeight ?? 1.2,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction TextComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Text\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: p.fontSize ?? 16,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n fontWeight: p.fontWeight === \"bold\" ? 700 : 400,\n fontStyle: p.fontStyle ?? \"normal\",\n lineHeight: p.lineHeight ?? 1.4,\n letterSpacing: p.letterSpacing,\n textDecoration: p.textDecoration,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction ImageComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Image\">>) {\n const p = element.props;\n\n return (\n <img\n src={p.src}\n width={p.width ?? undefined}\n height={p.height ?? undefined}\n style={cleanStyle({\n borderRadius: p.borderRadius,\n objectFit: p.objectFit ?? \"contain\",\n })}\n />\n );\n}\n\n// =============================================================================\n// Decorative Components\n// =============================================================================\n\nfunction DividerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Divider\">>) {\n const p = element.props;\n\n return (\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n borderBottom: `${p.thickness ?? 1}px solid ${p.color ?? \"#e5e7eb\"}`,\n marginTop: p.marginTop ?? 8,\n marginBottom: p.marginBottom ?? 8,\n }}\n />\n );\n}\n\nfunction SpacerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Spacer\">>) {\n const p = element.props;\n\n return <div style={{ display: \"flex\", height: p.height ?? 20 }} />;\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\nexport const standardComponents: ComponentRegistry = {\n Frame: FrameComponent,\n Box: BoxComponent,\n Row: RowComponent,\n Column: ColumnComponent,\n Heading: HeadingComponent,\n Text: TextComponent,\n Image: ImageComponent,\n Divider: DividerComponent,\n Spacer: SpacerComponent,\n};\n"],"mappings":"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","names":["import_jsx_runtime","children","satori"]}
|
|
1
|
+
{"version":3,"sources":["../src/render.tsx","../src/components/standard.tsx"],"sourcesContent":["import React from \"react\";\nimport satori, { type SatoriOptions } from \"satori\";\nimport type { Spec, UIElement } from \"@json-render/core\";\nimport {\n resolveElementProps,\n resolveRepeatItemStatePath,\n resolveRepeatStatePath,\n evaluateVisibility,\n getByPath,\n type PropResolutionContext,\n} from \"@json-render/core\";\nimport { standardComponents } from \"./components/standard\";\nimport type { ComponentRegistry } from \"./types\";\n\nexport { standardComponents };\n\nexport interface RenderOptions {\n registry?: ComponentRegistry;\n includeStandard?: boolean;\n state?: Record<string, unknown>;\n fonts?: SatoriOptions[\"fonts\"];\n /** Override the Frame width. When omitted, uses the Frame component's width prop. */\n width?: number;\n /** Override the Frame height. When omitted, uses the Frame component's height prop. */\n height?: number;\n}\n\nconst noopEmit = () => {};\n\nfunction renderElement(\n elementKey: string,\n spec: Spec,\n registry: ComponentRegistry,\n stateModel: Record<string, unknown>,\n repeatItem?: unknown,\n repeatIndex?: number,\n repeatBasePath?: string,\n): React.ReactElement | null {\n const element = spec.elements[elementKey];\n if (!element) return null;\n\n const ctx: PropResolutionContext = {\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n };\n\n if (element.visible !== undefined) {\n if (!evaluateVisibility(element.visible, ctx)) {\n return null;\n }\n }\n\n const resolvedProps = resolveElementProps(\n element.props as Record<string, unknown>,\n ctx,\n );\n const resolvedElement: UIElement = { ...element, props: resolvedProps };\n\n const Component = registry[resolvedElement.type];\n if (!Component) return null;\n\n if (resolvedElement.repeat) {\n const repeat = resolvedElement.repeat;\n const statePath = resolveRepeatStatePath(repeat.statePath, repeatBasePath);\n if (statePath === undefined) {\n console.warn(\n \"[json-render/image] $item in repeat.statePath used outside of a repeat scope\",\n );\n return null;\n }\n\n const items =\n (getByPath(stateModel, statePath) as unknown[] | undefined) ?? [];\n\n const fragments = items.map((item, index) => {\n const key =\n repeat.key && typeof item === \"object\" && item !== null\n ? String((item as Record<string, unknown>)[repeat.key!] ?? index)\n : String(index);\n\n const childPath = resolveRepeatItemStatePath(statePath, index);\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n item,\n index,\n childPath,\n ),\n );\n\n return (\n <Component key={key} element={resolvedElement} emit={noopEmit}>\n {children}\n </Component>\n );\n });\n\n return <>{fragments}</>;\n }\n\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n ),\n );\n\n return (\n <Component key={elementKey} element={resolvedElement} emit={noopEmit}>\n {children && children.length > 0 ? children : undefined}\n </Component>\n );\n}\n\ninterface ImageDimensions {\n width: number;\n height: number;\n}\n\nfunction getDimensions(\n spec: Spec,\n options: RenderOptions = {},\n): ImageDimensions {\n if (options.width && options.height) {\n return { width: options.width, height: options.height };\n }\n\n const rootElement = spec.elements[spec.root];\n const props = rootElement?.props as Record<string, unknown> | undefined;\n\n return {\n width: options.width ?? (props?.width as number) ?? 1200,\n height: options.height ?? (props?.height as number) ?? 630,\n };\n}\n\nfunction buildTree(\n spec: Spec,\n options: RenderOptions = {},\n): React.ReactElement {\n const {\n registry: customRegistry,\n includeStandard = true,\n state = {},\n } = options;\n\n const mergedState: Record<string, unknown> = {\n ...spec.state,\n ...state,\n };\n\n const registry: ComponentRegistry = {\n ...(includeStandard ? standardComponents : {}),\n ...customRegistry,\n };\n\n const root = renderElement(spec.root, spec, registry, mergedState);\n return root ?? <></>;\n}\n\n/**\n * Render a json-render spec to an SVG string.\n *\n * Uses Satori to convert the spec's component tree into SVG.\n * No additional dependencies are needed beyond satori.\n */\nexport async function renderToSvg(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<string> {\n const tree = buildTree(spec, options);\n const { width, height } = getDimensions(spec, options);\n\n return satori(tree as React.ReactNode, {\n width,\n height,\n fonts: options.fonts ?? [],\n });\n}\n\n/**\n * Render a json-render spec to a PNG buffer.\n *\n * Requires `@resvg/resvg-js` to be installed as a peer dependency.\n * The SVG is first generated via Satori, then rasterized to PNG.\n */\nexport async function renderToPng(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<Uint8Array> {\n const svg = await renderToSvg(spec, options);\n\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n const mod = await import(\"@resvg/resvg-js\");\n Resvg = mod.Resvg;\n } catch {\n throw new Error(\n \"@resvg/resvg-js is required for PNG output. Install it with: npm install @resvg/resvg-js\",\n );\n }\n\n const resvg = new Resvg(svg);\n const pngData = resvg.render();\n return pngData.asPng();\n}\n","import React from \"react\";\nimport type { ComponentRenderProps, ComponentRegistry } from \"../types\";\nimport type { StandardComponentProps } from \"../catalog\";\n\nconst headingSizes: Record<string, number> = {\n h1: 48,\n h2: 36,\n h3: 28,\n h4: 22,\n};\n\nconst headingWeights: Record<string, number> = {\n h1: 700,\n h2: 700,\n h3: 600,\n h4: 600,\n};\n\n/**\n * Satori crashes on explicit `undefined` style values (e.g. `padding: undefined`).\n * Strip them so only defined properties are passed.\n */\nfunction cleanStyle(raw: Record<string, unknown>): React.CSSProperties {\n const out: Record<string, unknown> = {};\n for (const k in raw) {\n if (raw[k] !== undefined && raw[k] !== null) {\n out[k] = raw[k];\n }\n }\n return out as React.CSSProperties;\n}\n\n// =============================================================================\n// Root\n// =============================================================================\n\nfunction FrameComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Frame\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: p.display ?? \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n width: p.width,\n height: p.height,\n backgroundColor: p.backgroundColor ?? \"white\",\n padding: p.padding,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Layout Components\n// =============================================================================\n\nfunction BoxComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Box\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n padding: p.padding,\n paddingTop: p.paddingTop,\n paddingBottom: p.paddingBottom,\n paddingLeft: p.paddingLeft,\n paddingRight: p.paddingRight,\n margin: p.margin,\n backgroundColor: p.backgroundColor,\n borderWidth: p.borderWidth,\n borderColor: p.borderColor,\n borderRadius: p.borderRadius,\n borderStyle: p.borderWidth ? \"solid\" : undefined,\n flex: p.flex,\n width: p.width,\n height: p.height,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n position: p.position,\n top: p.top,\n left: p.left,\n right: p.right,\n bottom: p.bottom,\n overflow: p.overflow,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction RowComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Row\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"row\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n flexWrap: p.wrap ? \"wrap\" : undefined,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction ColumnComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Column\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"column\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Content Components\n// =============================================================================\n\nfunction HeadingComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Heading\">>) {\n const p = element.props;\n const level = p.level ?? \"h2\";\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: headingSizes[level] ?? 36,\n fontWeight: headingWeights[level] ?? 700,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n letterSpacing: p.letterSpacing,\n lineHeight: p.lineHeight ?? 1.2,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction TextComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Text\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: p.fontSize ?? 16,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n fontWeight: p.fontWeight === \"bold\" ? 700 : 400,\n fontStyle: p.fontStyle ?? \"normal\",\n lineHeight: p.lineHeight ?? 1.4,\n letterSpacing: p.letterSpacing,\n textDecoration: p.textDecoration,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction ImageComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Image\">>) {\n const p = element.props;\n\n return (\n <img\n src={p.src}\n width={p.width ?? undefined}\n height={p.height ?? undefined}\n style={cleanStyle({\n borderRadius: p.borderRadius,\n objectFit: p.objectFit ?? \"contain\",\n })}\n />\n );\n}\n\n// =============================================================================\n// Decorative Components\n// =============================================================================\n\nfunction DividerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Divider\">>) {\n const p = element.props;\n\n return (\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n borderBottom: `${p.thickness ?? 1}px solid ${p.color ?? \"#e5e7eb\"}`,\n marginTop: p.marginTop ?? 8,\n marginBottom: p.marginBottom ?? 8,\n }}\n />\n );\n}\n\nfunction SpacerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Spacer\">>) {\n const p = element.props;\n\n return <div style={{ display: \"flex\", height: p.height ?? 20 }} />;\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\nexport const standardComponents: ComponentRegistry = {\n Frame: FrameComponent,\n Box: BoxComponent,\n Row: RowComponent,\n Column: ColumnComponent,\n Heading: HeadingComponent,\n Text: TextComponent,\n Image: ImageComponent,\n Divider: DividerComponent,\n Spacer: SpacerComponent,\n};\n"],"mappings":"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","names":["import_jsx_runtime","children","satori"]}
|
package/dist/render.mjs
CHANGED
package/dist/server.d.mts
CHANGED
|
@@ -20,7 +20,16 @@ declare const schema: _json_render_core.Schema<{
|
|
|
20
20
|
type: _json_render_core.SchemaType<"ref", string>;
|
|
21
21
|
props: _json_render_core.SchemaType<"propsOf", string>;
|
|
22
22
|
children: _json_render_core.SchemaType<"array", _json_render_core.SchemaType<"string", unknown>>;
|
|
23
|
-
visible:
|
|
23
|
+
visible: {
|
|
24
|
+
optional: true;
|
|
25
|
+
kind: "any";
|
|
26
|
+
inner?: unknown;
|
|
27
|
+
};
|
|
28
|
+
repeat: {
|
|
29
|
+
optional: true;
|
|
30
|
+
kind: "any";
|
|
31
|
+
inner?: unknown;
|
|
32
|
+
};
|
|
24
33
|
}>>;
|
|
25
34
|
}>;
|
|
26
35
|
catalog: _json_render_core.SchemaType<"object", {
|
package/dist/server.d.ts
CHANGED
|
@@ -20,7 +20,16 @@ declare const schema: _json_render_core.Schema<{
|
|
|
20
20
|
type: _json_render_core.SchemaType<"ref", string>;
|
|
21
21
|
props: _json_render_core.SchemaType<"propsOf", string>;
|
|
22
22
|
children: _json_render_core.SchemaType<"array", _json_render_core.SchemaType<"string", unknown>>;
|
|
23
|
-
visible:
|
|
23
|
+
visible: {
|
|
24
|
+
optional: true;
|
|
25
|
+
kind: "any";
|
|
26
|
+
inner?: unknown;
|
|
27
|
+
};
|
|
28
|
+
repeat: {
|
|
29
|
+
optional: true;
|
|
30
|
+
kind: "any";
|
|
31
|
+
inner?: unknown;
|
|
32
|
+
};
|
|
24
33
|
}>>;
|
|
25
34
|
}>;
|
|
26
35
|
catalog: _json_render_core.SchemaType<"object", {
|
package/dist/server.js
CHANGED
|
@@ -36,7 +36,8 @@ var schema = (0, import_core.defineSchema)(
|
|
|
36
36
|
type: s.ref("catalog.components"),
|
|
37
37
|
props: s.propsOf("catalog.components"),
|
|
38
38
|
children: s.array(s.string()),
|
|
39
|
-
visible: s.any()
|
|
39
|
+
visible: { ...s.any(), ...s.optional() },
|
|
40
|
+
repeat: { ...s.any(), ...s.optional() }
|
|
40
41
|
})
|
|
41
42
|
)
|
|
42
43
|
}),
|
|
@@ -57,7 +58,8 @@ var schema = (0, import_core.defineSchema)(
|
|
|
57
58
|
"All text content must use Heading or Text components. Raw strings are not supported.",
|
|
58
59
|
"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.",
|
|
59
60
|
"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.",
|
|
60
|
-
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist."
|
|
61
|
+
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.",
|
|
62
|
+
'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.'
|
|
61
63
|
]
|
|
62
64
|
}
|
|
63
65
|
);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/schema.ts","../src/catalog.ts"],"sourcesContent":["// Server-safe entry point: schema and catalog definitions only.\n// Does not import React or Satori.\n\nexport { schema, type ImageSchema, type ImageSpec } from \"./schema\";\n\nexport {\n standardComponentDefinitions,\n type StandardComponentDefinitions,\n type StandardComponentProps,\n} from \"./catalog\";\n\nexport type { Spec } from \"@json-render/core\";\n\nexport type {\n SetState,\n StateModel,\n ComponentContext,\n ComponentFn,\n Components,\n} from \"./catalog-types\";\n","import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/image\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas\n *\n * Reuses the same { root, elements } spec format as the React and React PDF renderers.\n */\nexport const schema = defineSchema(\n (s) => ({\n spec: s.object({\n root: s.string(),\n elements: s.record(\n s.object({\n type: s.ref(\"catalog.components\"),\n props: s.propsOf(\"catalog.components\"),\n children: s.array(s.string()),\n visible: s.any(),\n }),\n ),\n }),\n\n catalog: s.object({\n components: s.map({\n props: s.zod(),\n slots: s.array(s.string()),\n description: s.string(),\n example: s.any(),\n }),\n }),\n }),\n {\n defaultRules: [\n \"The root element MUST be a Frame component. It defines the image dimensions (width, height) and background.\",\n \"Frame width and height determine the output image size. Common sizes: 1200x630 (OG image), 1080x1080 (social square), 1920x1080 (banner).\",\n \"Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.\",\n \"All text content must use Heading or Text components. Raw strings are not supported.\",\n \"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.\",\n \"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.\",\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.\",\n ],\n },\n);\n\nexport type ImageSchema = typeof schema;\n\nexport type ImageSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n","import { z } from \"zod\";\n\n/**\n * Standard component definitions for image catalogs.\n *\n * These define the available image components with their Zod prop schemas.\n * All components render to Satori-compatible JSX (HTML-like elements with\n * inline CSS flexbox styles).\n */\nexport const standardComponentDefinitions = {\n // ==========================================================================\n // Root\n // ==========================================================================\n\n Frame: {\n props: z.object({\n width: z.number(),\n height: z.number(),\n backgroundColor: z.string().nullable(),\n padding: z.number().nullable(),\n display: z.enum([\"flex\", \"none\"]).nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Root image container. Defines the output image dimensions and background. Must be the root element.\",\n example: { width: 1200, height: 630, backgroundColor: \"#ffffff\" },\n },\n\n // ==========================================================================\n // Layout Components\n // ==========================================================================\n\n Box: {\n props: z.object({\n padding: z.number().nullable(),\n paddingTop: z.number().nullable(),\n paddingBottom: z.number().nullable(),\n paddingLeft: z.number().nullable(),\n paddingRight: z.number().nullable(),\n margin: z.number().nullable(),\n backgroundColor: z.string().nullable(),\n borderWidth: z.number().nullable(),\n borderColor: z.string().nullable(),\n borderRadius: z.number().nullable(),\n flex: z.number().nullable(),\n width: z.union([z.number(), z.string()]).nullable(),\n height: z.union([z.number(), z.string()]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n position: z.enum([\"relative\", \"absolute\"]).nullable(),\n top: z.number().nullable(),\n left: z.number().nullable(),\n right: z.number().nullable(),\n bottom: z.number().nullable(),\n overflow: z.enum([\"visible\", \"hidden\"]).nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Generic container with padding, margin, background, border, and flex alignment. Supports absolute positioning.\",\n example: {\n padding: 20,\n backgroundColor: \"#f9f9f9\",\n borderRadius: 8,\n alignItems: \"center\",\n },\n },\n\n Row: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n wrap: z.boolean().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Horizontal flex layout. Use for placing elements side by side.\",\n example: { gap: 10, alignItems: \"center\" },\n },\n\n Column: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Vertical flex layout. Use for stacking elements top to bottom.\",\n example: { gap: 8, padding: 10 },\n },\n\n // ==========================================================================\n // Content Components\n // ==========================================================================\n\n Heading: {\n props: z.object({\n text: z.string(),\n level: z.enum([\"h1\", \"h2\", \"h3\", \"h4\"]).nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n lineHeight: z.number().nullable(),\n }),\n slots: [],\n description:\n \"Heading text at various levels. h1 is largest, h4 is smallest.\",\n example: { text: \"Hello World\", level: \"h1\", color: \"#000000\" },\n },\n\n Text: {\n props: z.object({\n text: z.string(),\n fontSize: z.number().nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n fontWeight: z.enum([\"normal\", \"bold\"]).nullable(),\n fontStyle: z.enum([\"normal\", \"italic\"]).nullable(),\n lineHeight: z.number().nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n textDecoration: z.enum([\"none\", \"underline\", \"line-through\"]).nullable(),\n }),\n slots: [],\n description:\n \"Body text with configurable size, color, weight, and alignment.\",\n example: { text: \"Some content here.\", fontSize: 16, color: \"#333333\" },\n },\n\n Image: {\n props: z.object({\n src: z.string(),\n width: z.number().nullable(),\n height: z.number().nullable(),\n borderRadius: z.number().nullable(),\n objectFit: z.enum([\"contain\", \"cover\", \"fill\", \"none\"]).nullable(),\n }),\n slots: [],\n description:\n \"Image from a URL. Specify width and/or height to control size. For placeholder images use https://picsum.photos/{width}/{height}?random={n}.\",\n example: {\n src: \"https://picsum.photos/400/300?random=1\",\n width: 400,\n height: 300,\n },\n },\n\n // ==========================================================================\n // Decorative Components\n // ==========================================================================\n\n Divider: {\n props: z.object({\n color: z.string().nullable(),\n thickness: z.number().nullable(),\n marginTop: z.number().nullable(),\n marginBottom: z.number().nullable(),\n }),\n slots: [],\n description: \"Horizontal line separator between content sections.\",\n example: { color: \"#e5e7eb\", thickness: 1 },\n },\n\n Spacer: {\n props: z.object({\n height: z.number().nullable(),\n }),\n slots: [],\n description: \"Empty vertical space between elements.\",\n example: { height: 20 },\n },\n};\n\nexport type StandardComponentDefinitions = typeof standardComponentDefinitions;\n\nexport type StandardComponentProps<\n K extends keyof StandardComponentDefinitions,\n> = StandardComponentDefinitions[K][\"props\"] extends { _output: infer O }\n ? O\n : z.output<StandardComponentDefinitions[K][\"props\"]>;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA6B;AAWtB,IAAM,aAAS;AAAA,EACpB,CAAC,OAAO;AAAA,IACN,MAAM,EAAE,OAAO;AAAA,MACb,MAAM,EAAE,OAAO;AAAA,MACf,UAAU,EAAE;AAAA,QACV,EAAE,OAAO;AAAA,UACP,MAAM,EAAE,IAAI,oBAAoB;AAAA,UAChC,OAAO,EAAE,QAAQ,oBAAoB;AAAA,UACrC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,UAC5B,SAAS,EAAE,IAAI;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,IAED,SAAS,EAAE,OAAO;AAAA,MAChB,YAAY,EAAE,IAAI;AAAA,QAChB,OAAO,EAAE,IAAI;AAAA,QACb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,QACzB,aAAa,EAAE,OAAO;AAAA,QACtB,SAAS,EAAE,IAAI;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EACA;AAAA,IACE,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AC7CA,iBAAkB;AASX,IAAM,+BAA+B;AAAA;AAAA;AAAA;AAAA,EAK1C,OAAO;AAAA,IACL,OAAO,aAAE,OAAO;AAAA,MACd,OAAO,aAAE,OAAO;AAAA,MAChB,QAAQ,aAAE,OAAO;AAAA,MACjB,iBAAiB,aAAE,OAAO,EAAE,SAAS;AAAA,MACrC,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,SAAS,aAAE,KAAK,CAAC,QAAQ,MAAM,CAAC,EAAE,SAAS;AAAA,MAC3C,eAAe,aAAE,KAAK,CAAC,OAAO,QAAQ,CAAC,EAAE,SAAS;AAAA,MAClD,YAAY,aACT,KAAK,CAAC,cAAc,UAAU,YAAY,SAAS,CAAC,EACpD,SAAS;AAAA,MACZ,gBAAgB,aACb,KAAK;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC,EACA,SAAS;AAAA,IACd,CAAC;AAAA,IACD,OAAO,CAAC,SAAS;AAAA,IACjB,aACE;AAAA,IACF,SAAS,EAAE,OAAO,MAAM,QAAQ,KAAK,iBAAiB,UAAU;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA,EAMA,KAAK;AAAA,IACH,OAAO,aAAE,OAAO;AAAA,MACd,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,YAAY,aAAE,OAAO,EAAE,SAAS;AAAA,MAChC,eAAe,aAAE,OAAO,EAAE,SAAS;AAAA,MACnC,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,MACjC,cAAc,aAAE,OAAO,EAAE,SAAS;AAAA,MAClC,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,MAC5B,iBAAiB,aAAE,OAAO,EAAE,SAAS;AAAA,MACrC,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,MACjC,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,MACjC,cAAc,aAAE,OAAO,EAAE,SAAS;AAAA,MAClC,MAAM,aAAE,OAAO,EAAE,SAAS;AAAA,MAC1B,OAAO,aAAE,MAAM,CAAC,aAAE,OAAO,GAAG,aAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,MAClD,QAAQ,aAAE,MAAM,CAAC,aAAE,OAAO,GAAG,aAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,MACnD,YAAY,aACT,KAAK,CAAC,cAAc,UAAU,YAAY,SAAS,CAAC,EACpD,SAAS;AAAA,MACZ,gBAAgB,aACb,KAAK;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC,EACA,SAAS;AAAA,MACZ,eAAe,aAAE,KAAK,CAAC,OAAO,QAAQ,CAAC,EAAE,SAAS;AAAA,MAClD,UAAU,aAAE,KAAK,CAAC,YAAY,UAAU,CAAC,EAAE,SAAS;AAAA,MACpD,KAAK,aAAE,OAAO,EAAE,SAAS;AAAA,MACzB,MAAM,aAAE,OAAO,EAAE,SAAS;AAAA,MAC1B,OAAO,aAAE,OAAO,EAAE,SAAS;AAAA,MAC3B,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,MAC5B,UAAU,aAAE,KAAK,CAAC,WAAW,QAAQ,CAAC,EAAE,SAAS;AAAA,IACnD,CAAC;AAAA,IACD,OAAO,CAAC,SAAS;AAAA,IACjB,aACE;AAAA,IACF,SAAS;AAAA,MACP,SAAS;AAAA,MACT,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,YAAY;AAAA,IACd;AAAA,EACF;AAAA,EAEA,KAAK;AAAA,IACH,OAAO,aAAE,OAAO;AAAA,MACd,KAAK,aAAE,OAAO,EAAE,SAAS;AAAA,MACzB,YAAY,aACT,KAAK,CAAC,cAAc,UAAU,YAAY,SAAS,CAAC,EACpD,SAAS;AAAA,MACZ,gBAAgB,aACb,KAAK;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC,EACA,SAAS;AAAA,MACZ,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,MAAM,aAAE,OAAO,EAAE,SAAS;AAAA,MAC1B,MAAM,aAAE,QAAQ,EAAE,SAAS;AAAA,IAC7B,CAAC;AAAA,IACD,OAAO,CAAC,SAAS;AAAA,IACjB,aACE;AAAA,IACF,SAAS,EAAE,KAAK,IAAI,YAAY,SAAS;AAAA,EAC3C;AAAA,EAEA,QAAQ;AAAA,IACN,OAAO,aAAE,OAAO;AAAA,MACd,KAAK,aAAE,OAAO,EAAE,SAAS;AAAA,MACzB,YAAY,aACT,KAAK,CAAC,cAAc,UAAU,YAAY,SAAS,CAAC,EACpD,SAAS;AAAA,MACZ,gBAAgB,aACb,KAAK;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC,EACA,SAAS;AAAA,MACZ,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,MAAM,aAAE,OAAO,EAAE,SAAS;AAAA,IAC5B,CAAC;AAAA,IACD,OAAO,CAAC,SAAS;AAAA,IACjB,aACE;AAAA,IACF,SAAS,EAAE,KAAK,GAAG,SAAS,GAAG;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS;AAAA,IACP,OAAO,aAAE,OAAO;AAAA,MACd,MAAM,aAAE,OAAO;AAAA,MACf,OAAO,aAAE,KAAK,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC,EAAE,SAAS;AAAA,MACjD,OAAO,aAAE,OAAO,EAAE,SAAS;AAAA,MAC3B,OAAO,aAAE,KAAK,CAAC,QAAQ,UAAU,OAAO,CAAC,EAAE,SAAS;AAAA,MACpD,eAAe,aAAE,MAAM,CAAC,aAAE,OAAO,GAAG,aAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,MAC1D,YAAY,aAAE,OAAO,EAAE,SAAS;AAAA,IAClC,CAAC;AAAA,IACD,OAAO,CAAC;AAAA,IACR,aACE;AAAA,IACF,SAAS,EAAE,MAAM,eAAe,OAAO,MAAM,OAAO,UAAU;AAAA,EAChE;AAAA,EAEA,MAAM;AAAA,IACJ,OAAO,aAAE,OAAO;AAAA,MACd,MAAM,aAAE,OAAO;AAAA,MACf,UAAU,aAAE,OAAO,EAAE,SAAS;AAAA,MAC9B,OAAO,aAAE,OAAO,EAAE,SAAS;AAAA,MAC3B,OAAO,aAAE,KAAK,CAAC,QAAQ,UAAU,OAAO,CAAC,EAAE,SAAS;AAAA,MACpD,YAAY,aAAE,KAAK,CAAC,UAAU,MAAM,CAAC,EAAE,SAAS;AAAA,MAChD,WAAW,aAAE,KAAK,CAAC,UAAU,QAAQ,CAAC,EAAE,SAAS;AAAA,MACjD,YAAY,aAAE,OAAO,EAAE,SAAS;AAAA,MAChC,eAAe,aAAE,MAAM,CAAC,aAAE,OAAO,GAAG,aAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,MAC1D,gBAAgB,aAAE,KAAK,CAAC,QAAQ,aAAa,cAAc,CAAC,EAAE,SAAS;AAAA,IACzE,CAAC;AAAA,IACD,OAAO,CAAC;AAAA,IACR,aACE;AAAA,IACF,SAAS,EAAE,MAAM,sBAAsB,UAAU,IAAI,OAAO,UAAU;AAAA,EACxE;AAAA,EAEA,OAAO;AAAA,IACL,OAAO,aAAE,OAAO;AAAA,MACd,KAAK,aAAE,OAAO;AAAA,MACd,OAAO,aAAE,OAAO,EAAE,SAAS;AAAA,MAC3B,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,MAC5B,cAAc,aAAE,OAAO,EAAE,SAAS;AAAA,MAClC,WAAW,aAAE,KAAK,CAAC,WAAW,SAAS,QAAQ,MAAM,CAAC,EAAE,SAAS;AAAA,IACnE,CAAC;AAAA,IACD,OAAO,CAAC;AAAA,IACR,aACE;AAAA,IACF,SAAS;AAAA,MACP,KAAK;AAAA,MACL,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS;AAAA,IACP,OAAO,aAAE,OAAO;AAAA,MACd,OAAO,aAAE,OAAO,EAAE,SAAS;AAAA,MAC3B,WAAW,aAAE,OAAO,EAAE,SAAS;AAAA,MAC/B,WAAW,aAAE,OAAO,EAAE,SAAS;AAAA,MAC/B,cAAc,aAAE,OAAO,EAAE,SAAS;AAAA,IACpC,CAAC;AAAA,IACD,OAAO,CAAC;AAAA,IACR,aAAa;AAAA,IACb,SAAS,EAAE,OAAO,WAAW,WAAW,EAAE;AAAA,EAC5C;AAAA,EAEA,QAAQ;AAAA,IACN,OAAO,aAAE,OAAO;AAAA,MACd,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,IAC9B,CAAC;AAAA,IACD,OAAO,CAAC;AAAA,IACR,aAAa;AAAA,IACb,SAAS,EAAE,QAAQ,GAAG;AAAA,EACxB;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/schema.ts","../src/catalog.ts"],"sourcesContent":["// Server-safe entry point: schema and catalog definitions only.\n// Does not import React or Satori.\n\nexport { schema, type ImageSchema, type ImageSpec } from \"./schema\";\n\nexport {\n standardComponentDefinitions,\n type StandardComponentDefinitions,\n type StandardComponentProps,\n} from \"./catalog\";\n\nexport type { Spec } from \"@json-render/core\";\n\nexport type {\n SetState,\n StateModel,\n ComponentContext,\n ComponentFn,\n Components,\n} from \"./catalog-types\";\n","import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/image\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas\n *\n * Reuses the same { root, elements } spec format as the React and React PDF renderers.\n */\nexport const schema = defineSchema(\n (s) => ({\n spec: s.object({\n root: s.string(),\n elements: s.record(\n s.object({\n type: s.ref(\"catalog.components\"),\n props: s.propsOf(\"catalog.components\"),\n children: s.array(s.string()),\n visible: { ...s.any(), ...s.optional() },\n repeat: { ...s.any(), ...s.optional() },\n }),\n ),\n }),\n\n catalog: s.object({\n components: s.map({\n props: s.zod(),\n slots: s.array(s.string()),\n description: s.string(),\n example: s.any(),\n }),\n }),\n }),\n {\n defaultRules: [\n \"The root element MUST be a Frame component. It defines the image dimensions (width, height) and background.\",\n \"Frame width and height determine the output image size. Common sizes: 1200x630 (OG image), 1080x1080 (social square), 1920x1080 (banner).\",\n \"Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.\",\n \"All text content must use Heading or Text components. Raw strings are not supported.\",\n \"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.\",\n \"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.\",\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.\",\n 'REQUIRED FIELDS: Every element MUST include a \"children\" array. Leaf elements (text, badges, inputs, images) use an empty array: \"children\": []. Omitting \"children\" fails validation.',\n ],\n },\n);\n\nexport type ImageSchema = typeof schema;\n\nexport type ImageSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n","import { z } from \"zod\";\n\n/**\n * Standard component definitions for image catalogs.\n *\n * These define the available image components with their Zod prop schemas.\n * All components render to Satori-compatible JSX (HTML-like elements with\n * inline CSS flexbox styles).\n */\nexport const standardComponentDefinitions = {\n // ==========================================================================\n // Root\n // ==========================================================================\n\n Frame: {\n props: z.object({\n width: z.number(),\n height: z.number(),\n backgroundColor: z.string().nullable(),\n padding: z.number().nullable(),\n display: z.enum([\"flex\", \"none\"]).nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Root image container. Defines the output image dimensions and background. Must be the root element.\",\n example: { width: 1200, height: 630, backgroundColor: \"#ffffff\" },\n },\n\n // ==========================================================================\n // Layout Components\n // ==========================================================================\n\n Box: {\n props: z.object({\n padding: z.number().nullable(),\n paddingTop: z.number().nullable(),\n paddingBottom: z.number().nullable(),\n paddingLeft: z.number().nullable(),\n paddingRight: z.number().nullable(),\n margin: z.number().nullable(),\n backgroundColor: z.string().nullable(),\n borderWidth: z.number().nullable(),\n borderColor: z.string().nullable(),\n borderRadius: z.number().nullable(),\n flex: z.number().nullable(),\n width: z.union([z.number(), z.string()]).nullable(),\n height: z.union([z.number(), z.string()]).nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n flexDirection: z.enum([\"row\", \"column\"]).nullable(),\n position: z.enum([\"relative\", \"absolute\"]).nullable(),\n top: z.number().nullable(),\n left: z.number().nullable(),\n right: z.number().nullable(),\n bottom: z.number().nullable(),\n overflow: z.enum([\"visible\", \"hidden\"]).nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Generic container with padding, margin, background, border, and flex alignment. Supports absolute positioning.\",\n example: {\n padding: 20,\n backgroundColor: \"#f9f9f9\",\n borderRadius: 8,\n alignItems: \"center\",\n },\n },\n\n Row: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n wrap: z.boolean().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Horizontal flex layout. Use for placing elements side by side.\",\n example: { gap: 10, alignItems: \"center\" },\n },\n\n Column: {\n props: z.object({\n gap: z.number().nullable(),\n alignItems: z\n .enum([\"flex-start\", \"center\", \"flex-end\", \"stretch\"])\n .nullable(),\n justifyContent: z\n .enum([\n \"flex-start\",\n \"center\",\n \"flex-end\",\n \"space-between\",\n \"space-around\",\n ])\n .nullable(),\n padding: z.number().nullable(),\n flex: z.number().nullable(),\n }),\n slots: [\"default\"],\n description:\n \"Vertical flex layout. Use for stacking elements top to bottom.\",\n example: { gap: 8, padding: 10 },\n },\n\n // ==========================================================================\n // Content Components\n // ==========================================================================\n\n Heading: {\n props: z.object({\n text: z.string(),\n level: z.enum([\"h1\", \"h2\", \"h3\", \"h4\"]).nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n lineHeight: z.number().nullable(),\n }),\n slots: [],\n description:\n \"Heading text at various levels. h1 is largest, h4 is smallest.\",\n example: { text: \"Hello World\", level: \"h1\", color: \"#000000\" },\n },\n\n Text: {\n props: z.object({\n text: z.string(),\n fontSize: z.number().nullable(),\n color: z.string().nullable(),\n align: z.enum([\"left\", \"center\", \"right\"]).nullable(),\n fontWeight: z.enum([\"normal\", \"bold\"]).nullable(),\n fontStyle: z.enum([\"normal\", \"italic\"]).nullable(),\n lineHeight: z.number().nullable(),\n letterSpacing: z.union([z.number(), z.string()]).nullable(),\n textDecoration: z.enum([\"none\", \"underline\", \"line-through\"]).nullable(),\n }),\n slots: [],\n description:\n \"Body text with configurable size, color, weight, and alignment.\",\n example: { text: \"Some content here.\", fontSize: 16, color: \"#333333\" },\n },\n\n Image: {\n props: z.object({\n src: z.string(),\n width: z.number().nullable(),\n height: z.number().nullable(),\n borderRadius: z.number().nullable(),\n objectFit: z.enum([\"contain\", \"cover\", \"fill\", \"none\"]).nullable(),\n }),\n slots: [],\n description:\n \"Image from a URL. Specify width and/or height to control size. For placeholder images use https://picsum.photos/{width}/{height}?random={n}.\",\n example: {\n src: \"https://picsum.photos/400/300?random=1\",\n width: 400,\n height: 300,\n },\n },\n\n // ==========================================================================\n // Decorative Components\n // ==========================================================================\n\n Divider: {\n props: z.object({\n color: z.string().nullable(),\n thickness: z.number().nullable(),\n marginTop: z.number().nullable(),\n marginBottom: z.number().nullable(),\n }),\n slots: [],\n description: \"Horizontal line separator between content sections.\",\n example: { color: \"#e5e7eb\", thickness: 1 },\n },\n\n Spacer: {\n props: z.object({\n height: z.number().nullable(),\n }),\n slots: [],\n description: \"Empty vertical space between elements.\",\n example: { height: 20 },\n },\n};\n\nexport type StandardComponentDefinitions = typeof standardComponentDefinitions;\n\nexport type StandardComponentProps<\n K extends keyof StandardComponentDefinitions,\n> = StandardComponentDefinitions[K][\"props\"] extends { _output: infer O }\n ? O\n : z.output<StandardComponentDefinitions[K][\"props\"]>;\n"],"mappings":"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","names":[]}
|
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@json-render/image",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
|
|
6
6
|
"keywords": [
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
],
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"satori": "^0.19.2",
|
|
60
|
-
"@json-render/core": "0.
|
|
60
|
+
"@json-render/core": "0.20.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/react": "19.2.3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema.ts"],"sourcesContent":["import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/image\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas\n *\n * Reuses the same { root, elements } spec format as the React and React PDF renderers.\n */\nexport const schema = defineSchema(\n (s) => ({\n spec: s.object({\n root: s.string(),\n elements: s.record(\n s.object({\n type: s.ref(\"catalog.components\"),\n props: s.propsOf(\"catalog.components\"),\n children: s.array(s.string()),\n visible: s.any(),\n }),\n ),\n }),\n\n catalog: s.object({\n components: s.map({\n props: s.zod(),\n slots: s.array(s.string()),\n description: s.string(),\n example: s.any(),\n }),\n }),\n }),\n {\n defaultRules: [\n \"The root element MUST be a Frame component. It defines the image dimensions (width, height) and background.\",\n \"Frame width and height determine the output image size. Common sizes: 1200x630 (OG image), 1080x1080 (social square), 1920x1080 (banner).\",\n \"Use Row for horizontal layouts and Column for vertical layouts. Both support gap, align, and justify props.\",\n \"All text content must use Heading or Text components. Raw strings are not supported.\",\n \"Image src must be a fully qualified URL. For placeholder images, use https://picsum.photos/{width}/{height}?random={n}.\",\n \"Satori renders a subset of CSS: flexbox layout, borders, backgrounds, text styling. Absolute positioning is supported via position/top/left/right/bottom.\",\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist.\",\n ],\n },\n);\n\nexport type ImageSchema = typeof schema;\n\nexport type ImageSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n"],"mappings":";AAAA,SAAS,oBAAoB;AAWtB,IAAM,SAAS;AAAA,EACpB,CAAC,OAAO;AAAA,IACN,MAAM,EAAE,OAAO;AAAA,MACb,MAAM,EAAE,OAAO;AAAA,MACf,UAAU,EAAE;AAAA,QACV,EAAE,OAAO;AAAA,UACP,MAAM,EAAE,IAAI,oBAAoB;AAAA,UAChC,OAAO,EAAE,QAAQ,oBAAoB;AAAA,UACrC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,UAC5B,SAAS,EAAE,IAAI;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,IAED,SAAS,EAAE,OAAO;AAAA,MAChB,YAAY,EAAE,IAAI;AAAA,QAChB,OAAO,EAAE,IAAI;AAAA,QACb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,QACzB,aAAa,EAAE,OAAO;AAAA,QACtB,SAAS,EAAE,IAAI;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EACA;AAAA,IACE,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render.tsx","../src/components/standard.tsx"],"sourcesContent":["import React from \"react\";\nimport satori, { type SatoriOptions } from \"satori\";\nimport type { Spec, UIElement } from \"@json-render/core\";\nimport {\n resolveElementProps,\n evaluateVisibility,\n getByPath,\n type PropResolutionContext,\n} from \"@json-render/core\";\nimport { standardComponents } from \"./components/standard\";\nimport type { ComponentRegistry } from \"./types\";\n\nexport { standardComponents };\n\nexport interface RenderOptions {\n registry?: ComponentRegistry;\n includeStandard?: boolean;\n state?: Record<string, unknown>;\n fonts?: SatoriOptions[\"fonts\"];\n /** Override the Frame width. When omitted, uses the Frame component's width prop. */\n width?: number;\n /** Override the Frame height. When omitted, uses the Frame component's height prop. */\n height?: number;\n}\n\nconst noopEmit = () => {};\n\nfunction renderElement(\n elementKey: string,\n spec: Spec,\n registry: ComponentRegistry,\n stateModel: Record<string, unknown>,\n repeatItem?: unknown,\n repeatIndex?: number,\n repeatBasePath?: string,\n): React.ReactElement | null {\n const element = spec.elements[elementKey];\n if (!element) return null;\n\n const ctx: PropResolutionContext = {\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n };\n\n if (element.visible !== undefined) {\n if (!evaluateVisibility(element.visible, ctx)) {\n return null;\n }\n }\n\n const resolvedProps = resolveElementProps(\n element.props as Record<string, unknown>,\n ctx,\n );\n const resolvedElement: UIElement = { ...element, props: resolvedProps };\n\n const Component = registry[resolvedElement.type];\n if (!Component) return null;\n\n if (resolvedElement.repeat) {\n const items =\n (getByPath(stateModel, resolvedElement.repeat.statePath) as\n | unknown[]\n | undefined) ?? [];\n\n const fragments = items.map((item, index) => {\n const key =\n resolvedElement.repeat!.key && typeof item === \"object\" && item !== null\n ? String(\n (item as Record<string, unknown>)[resolvedElement.repeat!.key!] ??\n index,\n )\n : String(index);\n\n const childPath = `${resolvedElement.repeat!.statePath}/${index}`;\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n item,\n index,\n childPath,\n ),\n );\n\n return (\n <Component key={key} element={resolvedElement} emit={noopEmit}>\n {children}\n </Component>\n );\n });\n\n return <>{fragments}</>;\n }\n\n const children = resolvedElement.children?.map((childKey) =>\n renderElement(\n childKey,\n spec,\n registry,\n stateModel,\n repeatItem,\n repeatIndex,\n repeatBasePath,\n ),\n );\n\n return (\n <Component key={elementKey} element={resolvedElement} emit={noopEmit}>\n {children && children.length > 0 ? children : undefined}\n </Component>\n );\n}\n\ninterface ImageDimensions {\n width: number;\n height: number;\n}\n\nfunction getDimensions(\n spec: Spec,\n options: RenderOptions = {},\n): ImageDimensions {\n if (options.width && options.height) {\n return { width: options.width, height: options.height };\n }\n\n const rootElement = spec.elements[spec.root];\n const props = rootElement?.props as Record<string, unknown> | undefined;\n\n return {\n width: options.width ?? (props?.width as number) ?? 1200,\n height: options.height ?? (props?.height as number) ?? 630,\n };\n}\n\nfunction buildTree(\n spec: Spec,\n options: RenderOptions = {},\n): React.ReactElement {\n const {\n registry: customRegistry,\n includeStandard = true,\n state = {},\n } = options;\n\n const mergedState: Record<string, unknown> = {\n ...spec.state,\n ...state,\n };\n\n const registry: ComponentRegistry = {\n ...(includeStandard ? standardComponents : {}),\n ...customRegistry,\n };\n\n const root = renderElement(spec.root, spec, registry, mergedState);\n return root ?? <></>;\n}\n\n/**\n * Render a json-render spec to an SVG string.\n *\n * Uses Satori to convert the spec's component tree into SVG.\n * No additional dependencies are needed beyond satori.\n */\nexport async function renderToSvg(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<string> {\n const tree = buildTree(spec, options);\n const { width, height } = getDimensions(spec, options);\n\n return satori(tree as React.ReactNode, {\n width,\n height,\n fonts: options.fonts ?? [],\n });\n}\n\n/**\n * Render a json-render spec to a PNG buffer.\n *\n * Requires `@resvg/resvg-js` to be installed as a peer dependency.\n * The SVG is first generated via Satori, then rasterized to PNG.\n */\nexport async function renderToPng(\n spec: Spec,\n options: RenderOptions = {},\n): Promise<Uint8Array> {\n const svg = await renderToSvg(spec, options);\n\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n const mod = await import(\"@resvg/resvg-js\");\n Resvg = mod.Resvg;\n } catch {\n throw new Error(\n \"@resvg/resvg-js is required for PNG output. Install it with: npm install @resvg/resvg-js\",\n );\n }\n\n const resvg = new Resvg(svg);\n const pngData = resvg.render();\n return pngData.asPng();\n}\n","import React from \"react\";\nimport type { ComponentRenderProps, ComponentRegistry } from \"../types\";\nimport type { StandardComponentProps } from \"../catalog\";\n\nconst headingSizes: Record<string, number> = {\n h1: 48,\n h2: 36,\n h3: 28,\n h4: 22,\n};\n\nconst headingWeights: Record<string, number> = {\n h1: 700,\n h2: 700,\n h3: 600,\n h4: 600,\n};\n\n/**\n * Satori crashes on explicit `undefined` style values (e.g. `padding: undefined`).\n * Strip them so only defined properties are passed.\n */\nfunction cleanStyle(raw: Record<string, unknown>): React.CSSProperties {\n const out: Record<string, unknown> = {};\n for (const k in raw) {\n if (raw[k] !== undefined && raw[k] !== null) {\n out[k] = raw[k];\n }\n }\n return out as React.CSSProperties;\n}\n\n// =============================================================================\n// Root\n// =============================================================================\n\nfunction FrameComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Frame\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: p.display ?? \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n width: p.width,\n height: p.height,\n backgroundColor: p.backgroundColor ?? \"white\",\n padding: p.padding,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Layout Components\n// =============================================================================\n\nfunction BoxComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Box\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: p.flexDirection ?? \"column\",\n padding: p.padding,\n paddingTop: p.paddingTop,\n paddingBottom: p.paddingBottom,\n paddingLeft: p.paddingLeft,\n paddingRight: p.paddingRight,\n margin: p.margin,\n backgroundColor: p.backgroundColor,\n borderWidth: p.borderWidth,\n borderColor: p.borderColor,\n borderRadius: p.borderRadius,\n borderStyle: p.borderWidth ? \"solid\" : undefined,\n flex: p.flex,\n width: p.width,\n height: p.height,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n position: p.position,\n top: p.top,\n left: p.left,\n right: p.right,\n bottom: p.bottom,\n overflow: p.overflow,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction RowComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Row\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"row\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n flexWrap: p.wrap ? \"wrap\" : undefined,\n })}\n >\n {children}\n </div>\n );\n}\n\nfunction ColumnComponent({\n element,\n children,\n}: ComponentRenderProps<StandardComponentProps<\"Column\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n flexDirection: \"column\",\n gap: p.gap,\n alignItems: p.alignItems,\n justifyContent: p.justifyContent,\n padding: p.padding,\n flex: p.flex,\n })}\n >\n {children}\n </div>\n );\n}\n\n// =============================================================================\n// Content Components\n// =============================================================================\n\nfunction HeadingComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Heading\">>) {\n const p = element.props;\n const level = p.level ?? \"h2\";\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: headingSizes[level] ?? 36,\n fontWeight: headingWeights[level] ?? 700,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n letterSpacing: p.letterSpacing,\n lineHeight: p.lineHeight ?? 1.2,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction TextComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Text\">>) {\n const p = element.props;\n\n return (\n <div\n style={cleanStyle({\n display: \"flex\",\n fontSize: p.fontSize ?? 16,\n color: p.color ?? \"black\",\n textAlign: p.align ?? \"left\",\n fontWeight: p.fontWeight === \"bold\" ? 700 : 400,\n fontStyle: p.fontStyle ?? \"normal\",\n lineHeight: p.lineHeight ?? 1.4,\n letterSpacing: p.letterSpacing,\n textDecoration: p.textDecoration,\n })}\n >\n {p.text}\n </div>\n );\n}\n\nfunction ImageComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Image\">>) {\n const p = element.props;\n\n return (\n <img\n src={p.src}\n width={p.width ?? undefined}\n height={p.height ?? undefined}\n style={cleanStyle({\n borderRadius: p.borderRadius,\n objectFit: p.objectFit ?? \"contain\",\n })}\n />\n );\n}\n\n// =============================================================================\n// Decorative Components\n// =============================================================================\n\nfunction DividerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Divider\">>) {\n const p = element.props;\n\n return (\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n borderBottom: `${p.thickness ?? 1}px solid ${p.color ?? \"#e5e7eb\"}`,\n marginTop: p.marginTop ?? 8,\n marginBottom: p.marginBottom ?? 8,\n }}\n />\n );\n}\n\nfunction SpacerComponent({\n element,\n}: ComponentRenderProps<StandardComponentProps<\"Spacer\">>) {\n const p = element.props;\n\n return <div style={{ display: \"flex\", height: p.height ?? 20 }} />;\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\nexport const standardComponents: ComponentRegistry = {\n Frame: FrameComponent,\n Box: BoxComponent,\n Row: RowComponent,\n Column: ColumnComponent,\n Heading: HeadingComponent,\n Text: TextComponent,\n Image: ImageComponent,\n Divider: DividerComponent,\n Spacer: SpacerComponent,\n};\n"],"mappings":"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","names":["jsx","children","jsx"]}
|