@particle-academy/fancy-cms-ui 0.0.1-dev.13 → 0.0.1-dev.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-IXPYY4S2.js +93 -0
- package/dist/chunk-IXPYY4S2.js.map +1 -0
- package/dist/editor.cjs +75 -39
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +4 -2
- package/dist/editor.d.ts +4 -2
- package/dist/editor.js +40 -11
- package/dist/editor.js.map +1 -1
- package/dist/react.cjs +37 -30
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +12 -4
- package/dist/react.d.ts +12 -4
- package/dist/react.js +2 -2
- package/dist/registry-C3PioMnL.d.ts +27 -0
- package/dist/registry-ni9y0dSw.d.cts +27 -0
- package/package.json +1 -1
- package/dist/chunk-E5AAQX7P.js +0 -86
- package/dist/chunk-E5AAQX7P.js.map +0 -1
- package/dist/registry-Bk7GN1rA.d.cts +0 -19
- package/dist/registry-Dht9JBLU.d.ts +0 -19
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { childrenOf, emitDocCss, isBinding } from './chunk-3YZMELJJ.js';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
|
|
5
|
+
function getPath(data, path) {
|
|
6
|
+
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], data);
|
|
7
|
+
}
|
|
8
|
+
function resolveValue(v, data) {
|
|
9
|
+
if (v === void 0 || v === null) return v;
|
|
10
|
+
if (isBinding(v)) return getPath(data, v.$bind);
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
var defaultRegistry = {
|
|
14
|
+
section: ({ children }) => children,
|
|
15
|
+
stack: ({ children }) => children,
|
|
16
|
+
grid: ({ children }) => children,
|
|
17
|
+
frame: ({ children }) => children,
|
|
18
|
+
shape: ({ children }) => children,
|
|
19
|
+
card: ({ children }) => children,
|
|
20
|
+
text: ({ node, text }) => text(node.props.content),
|
|
21
|
+
heading: ({ node, text }) => text(node.props.content) || "Heading",
|
|
22
|
+
link: ({ node, text }) => /* @__PURE__ */ jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
|
|
23
|
+
divider: () => /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
|
|
24
|
+
callout: ({ node, text }) => {
|
|
25
|
+
const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[text(node.props.variant)] ?? "#3b82f6";
|
|
26
|
+
return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: text(node.props.content) || "Callout" }) });
|
|
27
|
+
},
|
|
28
|
+
code: ({ node, text }) => /* @__PURE__ */ jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsx("code", { children: text(node.props.content) }) }),
|
|
29
|
+
button: ({ node, text }) => {
|
|
30
|
+
const label = text(node.props.label) || text(node.props.content) || "Button";
|
|
31
|
+
const variant = text(node.props.variant) || "primary";
|
|
32
|
+
return /* @__PURE__ */ jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label });
|
|
33
|
+
},
|
|
34
|
+
image: ({ node, text }) => /* @__PURE__ */ jsx(
|
|
35
|
+
"img",
|
|
36
|
+
{
|
|
37
|
+
src: text(node.props.src),
|
|
38
|
+
alt: text(node.props.alt),
|
|
39
|
+
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
};
|
|
43
|
+
function buttonStyle(variant) {
|
|
44
|
+
const base = {
|
|
45
|
+
display: "inline-flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
gap: 6,
|
|
48
|
+
padding: "9px 16px",
|
|
49
|
+
borderRadius: 8,
|
|
50
|
+
fontWeight: 600,
|
|
51
|
+
fontSize: 14,
|
|
52
|
+
lineHeight: 1,
|
|
53
|
+
cursor: "pointer",
|
|
54
|
+
userSelect: "none"
|
|
55
|
+
};
|
|
56
|
+
if (variant === "ghost") return { ...base, background: "transparent", color: "inherit", border: "1px solid currentColor" };
|
|
57
|
+
if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
|
|
58
|
+
return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
|
|
59
|
+
}
|
|
60
|
+
function RenderNode({ doc, id, registry = defaultRegistry, data }) {
|
|
61
|
+
const node = doc.nodes[id];
|
|
62
|
+
if (!node) return null;
|
|
63
|
+
if (node.type === "repeater") {
|
|
64
|
+
const arr = resolveValue(node.props.items, data);
|
|
65
|
+
const items = Array.isArray(arr) ? arr : [];
|
|
66
|
+
const template = childrenOf(doc, id)[0];
|
|
67
|
+
return /* @__PURE__ */ jsx("div", { "data-cms": node.id, className: node.className, children: template ? items.map((item, index) => /* @__PURE__ */ jsx(RenderNode, { doc, id: template.id, registry, data: { ...data, item, index } }, index)) : null });
|
|
68
|
+
}
|
|
69
|
+
const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(RenderNode, { doc, id: child.id, registry, data }, child.id));
|
|
70
|
+
const resolve = (v) => resolveValue(v, data);
|
|
71
|
+
const text = (v) => {
|
|
72
|
+
const r = resolve(v);
|
|
73
|
+
return r === void 0 || r === null ? "" : String(r);
|
|
74
|
+
};
|
|
75
|
+
const renderer = registry[node.type];
|
|
76
|
+
const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;
|
|
77
|
+
return /* @__PURE__ */ jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, className: node.className, children: inner });
|
|
78
|
+
}
|
|
79
|
+
function CmsPage({
|
|
80
|
+
doc,
|
|
81
|
+
registry = defaultRegistry,
|
|
82
|
+
data,
|
|
83
|
+
includeStyles = true
|
|
84
|
+
}) {
|
|
85
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
86
|
+
includeStyles ? /* @__PURE__ */ jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
|
|
87
|
+
doc.sections.map((id) => /* @__PURE__ */ jsx(RenderNode, { doc, id, registry, data }, id))
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { CmsPage, RenderNode, defaultRegistry };
|
|
92
|
+
//# sourceMappingURL=chunk-IXPYY4S2.js.map
|
|
93
|
+
//# sourceMappingURL=chunk-IXPYY4S2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/registry.tsx","../src/react/RenderNode.tsx","../src/react/CmsPage.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-IXPYY4S2.js","sourcesContent":["import type { ReactNode } from \"react\";\nimport { isBinding, type Bound, type Json, type Node, type PageDoc } from \"../document/types\";\n\n/** The page data context bindings resolve against (e.g. server props, repeater `item`). */\nexport type DataContext = Record<string, unknown>;\n\nexport interface ElementContext {\n node: Node;\n doc: PageDoc;\n /** Pre-rendered child nodes, in order. */\n children: ReactNode;\n /** The data context this node renders in (page data + any repeater scope). */\n data?: DataContext;\n /** Resolve a (possibly bound) value to its raw resolved value. */\n resolve: (v: Bound<Json> | undefined) => unknown;\n /** Resolve a (possibly bound) value to a display string. */\n text: (v: Bound<Json> | undefined) => string;\n}\n\n/**\n * Renders the INNER content of a node. The outer `[data-cms]` wrapper is added\n * by {@link RenderNode}, so a renderer only returns what goes inside.\n */\nexport type ElementRenderer = (ctx: ElementContext) => ReactNode;\n\nexport type ElementRegistry = Record<string, ElementRenderer>;\n\n/** Read a dotted path (`a.b.c`) out of the data context. */\nexport function getPath(data: DataContext | undefined, path: string): unknown {\n return path.split(\".\").reduce<unknown>((o, k) => (o == null ? undefined : (o as Record<string, unknown>)[k]), data);\n}\n\n/** Resolve a value: a `{ $bind }` reads from the data context; anything else is literal. */\nexport function resolveValue(v: Bound<Json> | undefined, data?: DataContext): unknown {\n if (v === undefined || v === null) return v;\n if (isBinding(v)) return getPath(data, v.$bind);\n return v;\n}\n\n/** Built-in native elements. Containers render their children; leaves render content. */\nexport const defaultRegistry: ElementRegistry = {\n section: ({ children }) => children,\n stack: ({ children }) => children,\n grid: ({ children }) => children,\n frame: ({ children }) => children,\n shape: ({ children }) => children,\n card: ({ children }) => children,\n text: ({ node, text }) => text(node.props.content),\n heading: ({ node, text }) => text(node.props.content) || \"Heading\",\n link: ({ node, text }) => (\n <a href={text(node.props.href) || \"#\"} style={{ color: \"inherit\", textDecoration: \"underline\" }}>\n {text(node.props.content) || \"link\"}\n </a>\n ),\n divider: () => <hr style={{ border: \"none\", borderTop: \"1px solid #e2e8f0\", margin: 0 }} />,\n callout: ({ node, text }) => {\n const tone = ({ info: \"#3b82f6\", success: \"#10b981\", warning: \"#f59e0b\", danger: \"#ef4444\" } as Record<string, string>)[text(node.props.variant)] ?? \"#3b82f6\";\n return (\n <div style={{ display: \"flex\", gap: 10, padding: \"12px 14px\", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }}>\n <span style={{ fontSize: 13 }}>{text(node.props.content) || \"Callout\"}</span>\n </div>\n );\n },\n code: ({ node, text }) => (\n <pre style={{ margin: 0, padding: \"12px 14px\", borderRadius: 10, background: \"#0b1220\", color: \"#e2e8f0\", fontFamily: \"ui-monospace, monospace\", fontSize: 13, overflowX: \"auto\" }}>\n <code>{text(node.props.content)}</code>\n </pre>\n ),\n button: ({ node, text }) => {\n const label = text(node.props.label) || text(node.props.content) || \"Button\";\n const variant = text(node.props.variant) || \"primary\";\n return <span data-cms-button={variant} style={buttonStyle(variant)}>{label}</span>;\n },\n image: ({ node, text }) => (\n <img\n src={text(node.props.src)}\n alt={text(node.props.alt)}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }}\n />\n ),\n};\n\n/**\n * A self-contained button look so the `button` primitive renders sensibly with\n * zero host CSS. Hosts that want their own button classes override the `button`\n * renderer in their registry (e.g. the sandbox maps it to `.btn .btn-primary`).\n */\nfunction buttonStyle(variant: string): import(\"react\").CSSProperties {\n const base: import(\"react\").CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"9px 16px\",\n borderRadius: 8,\n fontWeight: 600,\n fontSize: 14,\n lineHeight: 1,\n cursor: \"pointer\",\n userSelect: \"none\",\n };\n if (variant === \"ghost\") return { ...base, background: \"transparent\", color: \"inherit\", border: \"1px solid currentColor\" };\n if (variant === \"outline\") return { ...base, background: \"transparent\", color: \"#7c3aed\", border: \"1px solid #7c3aed\" };\n return { ...base, background: \"#7c3aed\", color: \"#fff\", border: \"1px solid #7c3aed\" };\n}\n","import type { ReactElement } from \"react\";\nimport type { Bound, Json, PageDoc } from \"../document/types\";\nimport { childrenOf } from \"../document/reduce\";\nimport { defaultRegistry, resolveValue, type DataContext, type ElementRegistry } from \"./registry\";\n\nexport interface RenderNodeProps {\n doc: PageDoc;\n id: string;\n registry?: ElementRegistry;\n /** Data context bindings resolve against (page data + any repeater scope). */\n data?: DataContext;\n}\n\n/**\n * Render one node (and its subtree) to a `[data-cms]`-tagged element. The CSS\n * emitted by `emitDocCss` targets these `data-cms` handles. Interactive /\n * 3rd-party **islands** with no registered renderer become an empty, sized\n * placeholder the host hydrates client-side — mirroring the PHP renderer.\n *\n * `repeater` nodes are special-cased: their first child is a template rendered\n * once per item of the bound `items` array, each in a scope that exposes `item`\n * and `index` to nested `{ $bind: \"item.…\" }` bindings.\n */\nexport function RenderNode({ doc, id, registry = defaultRegistry, data }: RenderNodeProps): ReactElement | null {\n const node = doc.nodes[id];\n if (!node) return null;\n\n if (node.type === \"repeater\") {\n const arr = resolveValue(node.props.items as Bound<Json> | undefined, data);\n const items = Array.isArray(arr) ? arr : [];\n const template = childrenOf(doc, id)[0];\n return (\n <div data-cms={node.id} className={node.className}>\n {template\n ? items.map((item, index) => (\n <RenderNode key={index} doc={doc} id={template.id} registry={registry} data={{ ...data, item, index }} />\n ))\n : null}\n </div>\n );\n }\n\n const kids = childrenOf(doc, id).map((child) => (\n <RenderNode key={child.id} doc={doc} id={child.id} registry={registry} data={data} />\n ));\n\n const resolve = (v: Bound<Json> | undefined) => resolveValue(v, data);\n const text = (v: Bound<Json> | undefined): string => {\n const r = resolve(v);\n return r === undefined || r === null ? \"\" : String(r);\n };\n\n const renderer = registry[node.type];\n const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;\n\n return (\n <div data-cms={node.id} data-cms-island={node.island ? node.type : undefined} className={node.className}>\n {inner}\n </div>\n );\n}\n","import { Fragment, type ReactElement } from \"react\";\nimport type { PageDoc } from \"../document/types\";\nimport { emitDocCss } from \"../render/css\";\nimport { RenderNode } from \"./RenderNode\";\nimport { defaultRegistry, type DataContext, type ElementRegistry } from \"./registry\";\n\nexport interface CmsPageProps {\n doc: PageDoc;\n registry?: ElementRegistry;\n /** Data context that `{ $bind: \"…\" }` props + repeaters resolve against. */\n data?: DataContext;\n /** Inject the compiled stylesheet. Set false when the host emits CSS itself. */\n includeStyles?: boolean;\n}\n\n/** Render a full page document: the compiled stylesheet + every section. */\nexport function CmsPage({\n doc,\n registry = defaultRegistry,\n data,\n includeStyles = true,\n}: CmsPageProps): ReactElement {\n return (\n <Fragment>\n {includeStyles ? <style data-cms-styles=\"\">{emitDocCss(doc)}</style> : null}\n {doc.sections.map((id) => (\n <RenderNode key={id} doc={doc} id={id} registry={registry} data={data} />\n ))}\n </Fragment>\n );\n}\n"]}
|
package/dist/editor.cjs
CHANGED
|
@@ -361,10 +361,13 @@ ${inner}
|
|
|
361
361
|
}
|
|
362
362
|
return blocks.join("\n\n") + (blocks.length ? "\n" : "");
|
|
363
363
|
}
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
364
|
+
function getPath(data, path) {
|
|
365
|
+
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], data);
|
|
366
|
+
}
|
|
367
|
+
function resolveValue(v, data) {
|
|
368
|
+
if (v === void 0 || v === null) return v;
|
|
369
|
+
if (isBinding(v)) return getPath(data, v.$bind);
|
|
370
|
+
return v;
|
|
368
371
|
}
|
|
369
372
|
var defaultRegistry = {
|
|
370
373
|
section: ({ children }) => children,
|
|
@@ -373,25 +376,25 @@ var defaultRegistry = {
|
|
|
373
376
|
frame: ({ children }) => children,
|
|
374
377
|
shape: ({ children }) => children,
|
|
375
378
|
card: ({ children }) => children,
|
|
376
|
-
text: ({ node }) =>
|
|
377
|
-
heading: ({ node }) =>
|
|
378
|
-
link: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href:
|
|
379
|
+
text: ({ node, text }) => text(node.props.content),
|
|
380
|
+
heading: ({ node, text }) => text(node.props.content) || "Heading",
|
|
381
|
+
link: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
|
|
379
382
|
divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
|
|
380
|
-
callout: ({ node }) => {
|
|
381
|
-
const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[
|
|
382
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children:
|
|
383
|
+
callout: ({ node, text }) => {
|
|
384
|
+
const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[text(node.props.variant)] ?? "#3b82f6";
|
|
385
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 10, padding: "12px 14px", borderRadius: 10, background: `${tone}14`, borderLeft: `3px solid ${tone}` }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: text(node.props.content) || "Callout" }) });
|
|
383
386
|
},
|
|
384
|
-
code: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children:
|
|
385
|
-
button: ({ node }) => {
|
|
386
|
-
const label2 =
|
|
387
|
-
const variant =
|
|
387
|
+
code: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { margin: 0, padding: "12px 14px", borderRadius: 10, background: "#0b1220", color: "#e2e8f0", fontFamily: "ui-monospace, monospace", fontSize: 13, overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: text(node.props.content) }) }),
|
|
388
|
+
button: ({ node, text }) => {
|
|
389
|
+
const label2 = text(node.props.label) || text(node.props.content) || "Button";
|
|
390
|
+
const variant = text(node.props.variant) || "primary";
|
|
388
391
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label2 });
|
|
389
392
|
},
|
|
390
|
-
image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
393
|
+
image: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
391
394
|
"img",
|
|
392
395
|
{
|
|
393
|
-
src:
|
|
394
|
-
alt:
|
|
396
|
+
src: text(node.props.src),
|
|
397
|
+
alt: text(node.props.alt),
|
|
395
398
|
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
|
|
396
399
|
}
|
|
397
400
|
)
|
|
@@ -413,30 +416,34 @@ function buttonStyle(variant) {
|
|
|
413
416
|
if (variant === "outline") return { ...base, background: "transparent", color: "#7c3aed", border: "1px solid #7c3aed" };
|
|
414
417
|
return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
|
|
415
418
|
}
|
|
416
|
-
function RenderNode({ doc, id, registry = defaultRegistry }) {
|
|
419
|
+
function RenderNode({ doc, id, registry = defaultRegistry, data }) {
|
|
417
420
|
const node = doc.nodes[id];
|
|
418
421
|
if (!node) return null;
|
|
419
|
-
|
|
422
|
+
if (node.type === "repeater") {
|
|
423
|
+
const arr = resolveValue(node.props.items, data);
|
|
424
|
+
const items = Array.isArray(arr) ? arr : [];
|
|
425
|
+
const template = childrenOf(doc, id)[0];
|
|
426
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, className: node.className, children: template ? items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: template.id, registry, data: { ...data, item, index } }, index)) : null });
|
|
427
|
+
}
|
|
428
|
+
const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: child.id, registry, data }, child.id));
|
|
429
|
+
const resolve = (v) => resolveValue(v, data);
|
|
430
|
+
const text = (v) => {
|
|
431
|
+
const r = resolve(v);
|
|
432
|
+
return r === void 0 || r === null ? "" : String(r);
|
|
433
|
+
};
|
|
420
434
|
const renderer = registry[node.type];
|
|
421
|
-
const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
|
|
422
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
423
|
-
"div",
|
|
424
|
-
{
|
|
425
|
-
"data-cms": node.id,
|
|
426
|
-
"data-cms-island": node.island ? node.type : void 0,
|
|
427
|
-
className: node.className,
|
|
428
|
-
children: inner
|
|
429
|
-
}
|
|
430
|
-
);
|
|
435
|
+
const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;
|
|
436
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, className: node.className, children: inner });
|
|
431
437
|
}
|
|
432
438
|
function CmsPage({
|
|
433
439
|
doc,
|
|
434
440
|
registry = defaultRegistry,
|
|
441
|
+
data,
|
|
435
442
|
includeStyles = true
|
|
436
443
|
}) {
|
|
437
444
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
438
445
|
includeStyles ? /* @__PURE__ */ jsxRuntime.jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
|
|
439
|
-
doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry }, id))
|
|
446
|
+
doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry, data }, id))
|
|
440
447
|
] });
|
|
441
448
|
}
|
|
442
449
|
function Canvas({ doc, selection, onSelect, apply }) {
|
|
@@ -826,7 +833,7 @@ function NodeInspector({
|
|
|
826
833
|
const setT = (patch) => onTransform({ ...transform, ...patch });
|
|
827
834
|
const handled = /* @__PURE__ */ new Set(["content", "label"]);
|
|
828
835
|
const extraKeys = Object.keys(node.props).filter(
|
|
829
|
-
(k) => !handled.has(k) && ["string", "number", "boolean"].includes(typeof node.props[k])
|
|
836
|
+
(k) => !handled.has(k) && (["string", "number", "boolean"].includes(typeof node.props[k]) || isBinding(node.props[k]))
|
|
830
837
|
);
|
|
831
838
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panel, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
832
839
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headRow, children: [
|
|
@@ -951,24 +958,50 @@ function Row({ children }) {
|
|
|
951
958
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children });
|
|
952
959
|
}
|
|
953
960
|
function PropField({ name, value, onChange }) {
|
|
961
|
+
const bound = isBinding(value);
|
|
962
|
+
const link = /* @__PURE__ */ jsxRuntime.jsx(
|
|
963
|
+
"button",
|
|
964
|
+
{
|
|
965
|
+
type: "button",
|
|
966
|
+
title: bound ? "Unbind (use a literal value)" : "Bind to data (e.g. item.name)",
|
|
967
|
+
onClick: () => onChange(bound ? "" : { $bind: "" }),
|
|
968
|
+
style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 11, color: bound ? "#a78bfa" : "#64748b" },
|
|
969
|
+
children: "\u{1F517}"
|
|
970
|
+
}
|
|
971
|
+
);
|
|
972
|
+
if (bound) {
|
|
973
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
974
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
975
|
+
name,
|
|
976
|
+
" ",
|
|
977
|
+
link
|
|
978
|
+
] }),
|
|
979
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, borderColor: "#7c3aed" }, placeholder: "data path \xB7 e.g. item.name", value: value.$bind, onChange: (e) => onChange({ $bind: e.target.value }) })
|
|
980
|
+
] });
|
|
981
|
+
}
|
|
954
982
|
if (typeof value === "boolean") {
|
|
955
983
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12 }, children: [
|
|
956
984
|
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }),
|
|
957
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize" }, children: name })
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textTransform: "capitalize", flex: 1 }, children: name }),
|
|
986
|
+
link
|
|
958
987
|
] });
|
|
959
988
|
}
|
|
960
989
|
if (name === "variant") {
|
|
961
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
990
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Sel, { label: name, value: String(value ?? ""), options: [["primary", "Primary"], ["ghost", "Ghost"], ["outline", "Outline"]], onChange });
|
|
962
991
|
}
|
|
963
992
|
const isNum = typeof value === "number";
|
|
964
993
|
return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: field, children: [
|
|
965
|
-
/* @__PURE__ */ jsxRuntime.
|
|
994
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...fieldLabel, display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
995
|
+
name,
|
|
996
|
+
" ",
|
|
997
|
+
link
|
|
998
|
+
] }),
|
|
966
999
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
967
1000
|
"input",
|
|
968
1001
|
{
|
|
969
1002
|
style: input2,
|
|
970
1003
|
type: isNum ? "number" : "text",
|
|
971
|
-
value: String(value),
|
|
1004
|
+
value: String(value ?? ""),
|
|
972
1005
|
onChange: (e) => onChange(isNum ? Number(e.target.value) : e.target.value)
|
|
973
1006
|
}
|
|
974
1007
|
)
|
|
@@ -1156,6 +1189,7 @@ var SNAP = 6;
|
|
|
1156
1189
|
function EditablePage({
|
|
1157
1190
|
doc,
|
|
1158
1191
|
registry = defaultRegistry,
|
|
1192
|
+
data,
|
|
1159
1193
|
timelineDock,
|
|
1160
1194
|
transforms,
|
|
1161
1195
|
onNodeTransform,
|
|
@@ -1375,7 +1409,7 @@ function EditablePage({
|
|
|
1375
1409
|
onContextMenu: onContextMenuSelect,
|
|
1376
1410
|
onDragOver: editing ? (e) => e.preventDefault() : void 0,
|
|
1377
1411
|
onDrop: editing ? onPageDrop : void 0,
|
|
1378
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry })
|
|
1412
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc: ed.state.doc, registry, data })
|
|
1379
1413
|
}
|
|
1380
1414
|
);
|
|
1381
1415
|
const pageShell = pinned ? /* @__PURE__ */ jsxRuntime.jsx("div", { ref: spacerRef, style: { height: `${Math.max(1, frames) * 100}vh` }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "sticky", top: 0, height: "100vh", overflow: "hidden" }, children: page }) }) : frames > 1 ? (
|
|
@@ -1510,7 +1544,7 @@ function ElementPalette({ open, onDragChange }) {
|
|
|
1510
1544
|
}
|
|
1511
1545
|
);
|
|
1512
1546
|
}
|
|
1513
|
-
var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
|
|
1547
|
+
var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device", "repeater"]);
|
|
1514
1548
|
var ADD_DEFAULTS = {
|
|
1515
1549
|
text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
|
|
1516
1550
|
heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
|
|
@@ -1523,7 +1557,8 @@ var ADD_DEFAULTS = {
|
|
|
1523
1557
|
card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
|
|
1524
1558
|
callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
|
|
1525
1559
|
divider: { type: "divider", props: {}, style: {} },
|
|
1526
|
-
code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} }
|
|
1560
|
+
code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
|
|
1561
|
+
repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
|
|
1527
1562
|
};
|
|
1528
1563
|
var GRIPS = [
|
|
1529
1564
|
{ key: "nw", north: true, west: true, cursor: "nwse-resize" },
|
|
@@ -1734,7 +1769,8 @@ var ADD_MENU = [
|
|
|
1734
1769
|
{ kind: "grid", label: "Grid" },
|
|
1735
1770
|
{ kind: "box", label: "Box" },
|
|
1736
1771
|
{ kind: "divider", label: "Divider" },
|
|
1737
|
-
{ kind: "code", label: "Code" }
|
|
1772
|
+
{ kind: "code", label: "Code" },
|
|
1773
|
+
{ kind: "repeater", label: "Repeater" }
|
|
1738
1774
|
];
|
|
1739
1775
|
function EditBar({
|
|
1740
1776
|
editing,
|