@particle-academy/fancy-cms-ui 0.0.1-dev.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.
@@ -0,0 +1,701 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/editor/Editor.tsx
7
+
8
+ // src/document/reduce.ts
9
+ function childrenOf(doc, parent) {
10
+ if (parent === null) {
11
+ return doc.sections.map((id) => doc.nodes[id]).filter((n) => Boolean(n));
12
+ }
13
+ const out = [];
14
+ for (const id of Object.keys(doc.nodes)) {
15
+ const n = doc.nodes[id];
16
+ if (n.parent === parent) out.push(n);
17
+ }
18
+ out.sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0);
19
+ return out;
20
+ }
21
+ function descendantIds(doc, id) {
22
+ const out = [];
23
+ const stack = [id];
24
+ while (stack.length) {
25
+ const current = stack.pop();
26
+ for (const childId of Object.keys(doc.nodes)) {
27
+ const n = doc.nodes[childId];
28
+ if (n.parent === current) {
29
+ out.push(childId);
30
+ stack.push(childId);
31
+ }
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ function patchNode(doc, id, patch) {
37
+ const node = doc.nodes[id];
38
+ return {
39
+ ...doc,
40
+ nodes: { ...doc.nodes, [id]: { ...node, ...patch } },
41
+ seq: doc.seq + 1
42
+ };
43
+ }
44
+ function reduce(doc, op, opts = {}) {
45
+ const invalid = (reason) => {
46
+ opts.onInvalid?.(op, reason);
47
+ return doc;
48
+ };
49
+ switch (op.t) {
50
+ case "insert_node": {
51
+ if (doc.nodes[op.node.id]) return invalid("node id already exists");
52
+ if (op.node.parent !== null && !doc.nodes[op.node.parent]) return invalid("parent not found");
53
+ return {
54
+ ...doc,
55
+ nodes: { ...doc.nodes, [op.node.id]: op.node },
56
+ sections: op.node.parent === null ? [...doc.sections, op.node.id] : doc.sections,
57
+ seq: doc.seq + 1
58
+ };
59
+ }
60
+ case "remove_node": {
61
+ if (!doc.nodes[op.id]) return invalid("node not found");
62
+ const dead = /* @__PURE__ */ new Set([op.id, ...descendantIds(doc, op.id)]);
63
+ const nodes = {};
64
+ for (const id of Object.keys(doc.nodes)) {
65
+ if (!dead.has(id)) nodes[id] = doc.nodes[id];
66
+ }
67
+ return {
68
+ ...doc,
69
+ nodes,
70
+ sections: doc.sections.filter((s) => !dead.has(s)),
71
+ seq: doc.seq + 1
72
+ };
73
+ }
74
+ case "move_node": {
75
+ const node = doc.nodes[op.id];
76
+ if (!node) return invalid("node not found");
77
+ if (op.parent === op.id) return invalid("cannot parent a node to itself");
78
+ if (op.parent !== null && !doc.nodes[op.parent]) return invalid("new parent not found");
79
+ if (op.parent !== null && descendantIds(doc, op.id).includes(op.parent)) {
80
+ return invalid("move would create a cycle");
81
+ }
82
+ const wasTop = node.parent === null;
83
+ const nowTop = op.parent === null;
84
+ let sections = doc.sections;
85
+ if (wasTop && !nowTop) sections = sections.filter((s) => s !== op.id);
86
+ else if (!wasTop && nowTop) sections = [...sections, op.id];
87
+ return {
88
+ ...doc,
89
+ nodes: { ...doc.nodes, [op.id]: { ...node, parent: op.parent, order: op.order } },
90
+ sections,
91
+ seq: doc.seq + 1
92
+ };
93
+ }
94
+ case "set_props": {
95
+ if (!doc.nodes[op.id]) return invalid("node not found");
96
+ return patchNode(doc, op.id, {
97
+ props: { ...doc.nodes[op.id].props, ...op.patch }
98
+ });
99
+ }
100
+ case "set_style": {
101
+ const node = doc.nodes[op.id];
102
+ if (!node) return invalid("node not found");
103
+ const prev = node.style[op.breakpoint] ?? {};
104
+ return patchNode(doc, op.id, {
105
+ style: { ...node.style, [op.breakpoint]: { ...prev, ...op.patch } }
106
+ });
107
+ }
108
+ case "set_constraints": {
109
+ const node = doc.nodes[op.id];
110
+ if (!node) return invalid("node not found");
111
+ const base = node.constraints ?? { base: { width: "hug", height: "hug" } };
112
+ const prev = base[op.breakpoint] ?? {};
113
+ return patchNode(doc, op.id, {
114
+ constraints: { ...base, [op.breakpoint]: { ...prev, ...op.patch } }
115
+ });
116
+ }
117
+ case "set_animation": {
118
+ const node = doc.nodes[op.id];
119
+ if (!node) return invalid("node not found");
120
+ const list = node.animations ?? [];
121
+ const idx = list.findIndex((a) => a.id === op.anim.id);
122
+ return patchNode(doc, op.id, {
123
+ animations: idx >= 0 ? list.map((a, i) => i === idx ? op.anim : a) : [...list, op.anim]
124
+ });
125
+ }
126
+ case "remove_animation": {
127
+ const node = doc.nodes[op.id];
128
+ if (!node) return invalid("node not found");
129
+ return patchNode(doc, op.id, {
130
+ animations: (node.animations ?? []).filter((a) => a.id !== op.animId)
131
+ });
132
+ }
133
+ case "set_action": {
134
+ const node = doc.nodes[op.id];
135
+ if (!node) return invalid("node not found");
136
+ const list = node.actions ?? [];
137
+ let actions;
138
+ if (op.action === null) actions = list.filter((_, i) => i !== op.index);
139
+ else if (op.index >= list.length) actions = [...list, op.action];
140
+ else actions = list.map((a, i) => i === op.index ? op.action : a);
141
+ return patchNode(doc, op.id, { actions });
142
+ }
143
+ case "set_meta":
144
+ return { ...doc, meta: { ...doc.meta, ...op.patch }, seq: doc.seq + 1 };
145
+ case "set_theme":
146
+ return { ...doc, theme: { ...doc.theme, ...op.patch }, seq: doc.seq + 1 };
147
+ case "reorder_sections": {
148
+ const current = new Set(doc.sections);
149
+ const same = op.order.length === doc.sections.length && op.order.every((id) => current.has(id));
150
+ if (!same) return invalid("reorder must be a permutation of current sections");
151
+ return { ...doc, sections: [...op.order], seq: doc.seq + 1 };
152
+ }
153
+ default:
154
+ return invalid(`unknown op "${op.t}"`);
155
+ }
156
+ }
157
+ function initEditor(doc) {
158
+ return { doc, past: [], future: [], selection: null };
159
+ }
160
+ function editorReduce(state, action) {
161
+ switch (action.type) {
162
+ case "apply": {
163
+ const next = reduce(state.doc, action.op);
164
+ if (next === state.doc) return state;
165
+ return {
166
+ ...state,
167
+ doc: next,
168
+ past: [...state.past, state.doc].slice(-100),
169
+ future: []
170
+ };
171
+ }
172
+ case "undo": {
173
+ if (state.past.length === 0) return state;
174
+ const prev = state.past[state.past.length - 1];
175
+ return {
176
+ ...state,
177
+ doc: prev,
178
+ past: state.past.slice(0, -1),
179
+ future: [state.doc, ...state.future]
180
+ };
181
+ }
182
+ case "redo": {
183
+ if (state.future.length === 0) return state;
184
+ const next = state.future[0];
185
+ return {
186
+ ...state,
187
+ doc: next,
188
+ past: [...state.past, state.doc],
189
+ future: state.future.slice(1)
190
+ };
191
+ }
192
+ case "select":
193
+ return state.selection === action.id ? state : { ...state, selection: action.id };
194
+ case "replace":
195
+ return state.doc === action.doc ? state : { ...state, doc: action.doc, future: [] };
196
+ default:
197
+ return state;
198
+ }
199
+ }
200
+
201
+ // src/editor/useEditor.ts
202
+ function useEditor(initialDoc) {
203
+ const [state, dispatch] = react.useReducer(editorReduce, initialDoc, initEditor);
204
+ const apply = react.useCallback((op) => dispatch({ type: "apply", op }), []);
205
+ const undo = react.useCallback(() => dispatch({ type: "undo" }), []);
206
+ const redo = react.useCallback(() => dispatch({ type: "redo" }), []);
207
+ const select = react.useCallback((id) => dispatch({ type: "select", id }), []);
208
+ const replace = react.useCallback((doc) => dispatch({ type: "replace", doc }), []);
209
+ return react.useMemo(
210
+ () => ({
211
+ state,
212
+ apply,
213
+ undo,
214
+ redo,
215
+ select,
216
+ replace,
217
+ canUndo: state.past.length > 0,
218
+ canRedo: state.future.length > 0
219
+ }),
220
+ [state, apply, undo, redo, select, replace]
221
+ );
222
+ }
223
+
224
+ // src/document/types.ts
225
+ function isBinding(v) {
226
+ return typeof v === "object" && v !== null && typeof v.$bind === "string";
227
+ }
228
+ var DEFAULT_BREAKPOINTS = {
229
+ base: 0,
230
+ md: 768,
231
+ lg: 1024
232
+ };
233
+
234
+ // src/render/css.ts
235
+ var selectorFor = (id) => `[data-cms="${id}"]`;
236
+ function lengthToCss(len) {
237
+ return `${len.value}${len.unit}`;
238
+ }
239
+ function sizeToCss(size) {
240
+ if (size === "fill") return "100%";
241
+ if (size === "hug") return "auto";
242
+ return lengthToCss(size);
243
+ }
244
+ var ALIGN = {
245
+ start: "flex-start",
246
+ center: "center",
247
+ end: "flex-end",
248
+ stretch: "stretch"
249
+ };
250
+ var JUSTIFY = {
251
+ start: "flex-start",
252
+ center: "center",
253
+ end: "flex-end",
254
+ between: "space-between",
255
+ around: "space-around"
256
+ };
257
+ function styleDecls(style) {
258
+ const d = {};
259
+ if (style.padding) d["padding"] = lengthToCss(style.padding);
260
+ if (style.margin) d["margin"] = lengthToCss(style.margin);
261
+ if (style.radius) d["border-radius"] = lengthToCss(style.radius);
262
+ if (style.background !== void 0) d["background"] = style.background;
263
+ if (style.color !== void 0) d["color"] = style.color;
264
+ if (style.opacity !== void 0) d["opacity"] = String(style.opacity);
265
+ if (style.fontFamily !== void 0) d["font-family"] = style.fontFamily;
266
+ if (style.fontSize) d["font-size"] = lengthToCss(style.fontSize);
267
+ if (style.fontWeight !== void 0) d["font-weight"] = String(style.fontWeight);
268
+ if (style.lineHeight !== void 0) d["line-height"] = String(style.lineHeight);
269
+ if (style.textAlign !== void 0) d["text-align"] = style.textAlign;
270
+ if (style.letterSpacing) d["letter-spacing"] = lengthToCss(style.letterSpacing);
271
+ if (style.border !== void 0) d["border"] = style.border;
272
+ if (style.boxShadow !== void 0) d["box-shadow"] = style.boxShadow;
273
+ if (style.transform !== void 0) d["transform"] = style.transform;
274
+ if (style.filter !== void 0) d["filter"] = style.filter;
275
+ if (style.gap) d["gap"] = lengthToCss(style.gap);
276
+ if (style.align !== void 0) d["align-items"] = ALIGN[style.align] ?? style.align;
277
+ if (style.justify !== void 0) d["justify-content"] = JUSTIFY[style.justify] ?? style.justify;
278
+ return d;
279
+ }
280
+ function layoutDecls(node) {
281
+ const d = {};
282
+ if (node.layout === "stack") {
283
+ d["display"] = "flex";
284
+ d["flex-direction"] = node.style.base.direction ?? "column";
285
+ } else if (node.layout === "grid") {
286
+ d["display"] = "grid";
287
+ d["grid-template-columns"] = `repeat(${node.style.base.columns ?? 1}, 1fr)`;
288
+ } else if (node.layout === "free") {
289
+ d["position"] = "relative";
290
+ }
291
+ return d;
292
+ }
293
+ function constraintDecls(c, parentFree) {
294
+ const d = {};
295
+ const transforms = [];
296
+ if (parentFree) {
297
+ d["position"] = "absolute";
298
+ if (c.left) d["left"] = lengthToCss(c.left);
299
+ if (c.right) d["right"] = lengthToCss(c.right);
300
+ if (c.top) d["top"] = lengthToCss(c.top);
301
+ if (c.bottom) d["bottom"] = lengthToCss(c.bottom);
302
+ if (c.centerX) {
303
+ d["left"] = "50%";
304
+ transforms.push("translateX(-50%)");
305
+ }
306
+ if (c.centerY) {
307
+ d["top"] = "50%";
308
+ transforms.push("translateY(-50%)");
309
+ }
310
+ if (transforms.length) d["transform"] = transforms.join(" ");
311
+ }
312
+ if (c.width) d["width"] = sizeToCss(c.width);
313
+ if (c.height) d["height"] = sizeToCss(c.height);
314
+ return d;
315
+ }
316
+ function isParentFree(doc, node) {
317
+ if (node.parent === null) return false;
318
+ const parent = doc.nodes[node.parent];
319
+ return !parent || parent.layout === void 0 || parent.layout === "free";
320
+ }
321
+ function serializeRule(selector, decls, indent) {
322
+ const props = Object.keys(decls).sort();
323
+ if (props.length === 0) return "";
324
+ const body = props.map((p) => `${indent} ${p}: ${decls[p]};`).join("\n");
325
+ return `${indent}${selector} {
326
+ ${body}
327
+ ${indent}}`;
328
+ }
329
+ function emitDocCss(doc) {
330
+ const breakpointPx = DEFAULT_BREAKPOINTS;
331
+ const blocks = [];
332
+ for (const id of Object.keys(doc.nodes).sort()) {
333
+ const node = doc.nodes[id];
334
+ const selector = selectorFor(id);
335
+ const parentFree = isParentFree(doc, node);
336
+ const base = {
337
+ ...layoutDecls(node),
338
+ ...node.constraints ? constraintDecls(node.constraints.base, parentFree) : {},
339
+ ...styleDecls(node.style.base)
340
+ };
341
+ const baseRule = serializeRule(selector, base, "");
342
+ if (baseRule) blocks.push(baseRule);
343
+ for (const bp of doc.breakpoints) {
344
+ if (bp === "base") continue;
345
+ const px = breakpointPx[bp];
346
+ if (px === void 0) continue;
347
+ const decls = {
348
+ ...node.constraints?.[bp] ? constraintDecls(node.constraints[bp], parentFree) : {},
349
+ ...node.style[bp] ? styleDecls(node.style[bp]) : {}
350
+ };
351
+ const inner = serializeRule(selector, decls, " ");
352
+ if (inner) blocks.push(`@media (min-width: ${px}px) {
353
+ ${inner}
354
+ }`);
355
+ }
356
+ }
357
+ return blocks.join("\n\n") + (blocks.length ? "\n" : "");
358
+ }
359
+ function literal(v) {
360
+ if (v === void 0 || v === null) return "";
361
+ if (isBinding(v)) return "";
362
+ return String(v);
363
+ }
364
+ var defaultRegistry = {
365
+ section: ({ children }) => children,
366
+ stack: ({ children }) => children,
367
+ grid: ({ children }) => children,
368
+ frame: ({ children }) => children,
369
+ shape: ({ children }) => children,
370
+ text: ({ node }) => literal(node.props.content),
371
+ image: ({ node }) => /* @__PURE__ */ jsxRuntime.jsx(
372
+ "img",
373
+ {
374
+ src: literal(node.props.src),
375
+ alt: literal(node.props.alt),
376
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
377
+ }
378
+ )
379
+ };
380
+ function RenderNode({ doc, id, registry = defaultRegistry }) {
381
+ const node = doc.nodes[id];
382
+ if (!node) return null;
383
+ const kids = childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id: child.id, registry }, child.id));
384
+ const renderer = registry[node.type];
385
+ const inner = renderer ? renderer({ node, doc, children: kids }) : node.island ? null : kids;
386
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, children: inner });
387
+ }
388
+ function CmsPage({
389
+ doc,
390
+ registry = defaultRegistry,
391
+ includeStyles = true
392
+ }) {
393
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
394
+ includeStyles ? /* @__PURE__ */ jsxRuntime.jsx("style", { "data-cms-styles": "", children: emitDocCss(doc) }) : null,
395
+ doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(RenderNode, { doc, id, registry }, id))
396
+ ] });
397
+ }
398
+ function Canvas({ doc, selection, onSelect, apply }) {
399
+ const ref = react.useRef(null);
400
+ const [box, setBox] = react.useState(null);
401
+ const drag = react.useRef(null);
402
+ react.useLayoutEffect(() => {
403
+ const root = ref.current;
404
+ if (!root || !selection) {
405
+ setBox(null);
406
+ return;
407
+ }
408
+ const el = root.querySelector(`[data-cms="${cssEscape(selection)}"]`);
409
+ if (!el) {
410
+ setBox(null);
411
+ return;
412
+ }
413
+ const r = el.getBoundingClientRect();
414
+ const rootRect = root.getBoundingClientRect();
415
+ setBox({
416
+ x: r.left - rootRect.left + root.scrollLeft,
417
+ y: r.top - rootRect.top + root.scrollTop,
418
+ w: r.width,
419
+ h: r.height
420
+ });
421
+ }, [selection, doc]);
422
+ const handleClick = (e) => {
423
+ const target = e.target.closest("[data-cms]");
424
+ onSelect(target ? target.getAttribute("data-cms") : null);
425
+ };
426
+ const handlePointerDown = (e) => {
427
+ if (!selection) return;
428
+ const c = doc.nodes[selection]?.constraints?.base;
429
+ const left = c && c.left && typeof c.left === "object" ? c.left.value : 0;
430
+ const top = c && c.top && typeof c.top === "object" ? c.top.value : 0;
431
+ drag.current = { x: e.clientX, y: e.clientY, left, top };
432
+ e.currentTarget.setPointerCapture(e.pointerId);
433
+ e.stopPropagation();
434
+ };
435
+ const handlePointerMove = (e) => {
436
+ const d = drag.current;
437
+ if (!d || !selection) return;
438
+ apply({
439
+ t: "set_constraints",
440
+ id: selection,
441
+ breakpoint: "base",
442
+ patch: {
443
+ left: { value: Math.round(d.left + (e.clientX - d.x)), unit: "px" },
444
+ top: { value: Math.round(d.top + (e.clientY - d.y)), unit: "px" }
445
+ }
446
+ });
447
+ };
448
+ const handlePointerUp = () => {
449
+ drag.current = null;
450
+ };
451
+ const surface = {
452
+ position: "relative",
453
+ overflow: "auto",
454
+ height: "100%",
455
+ background: "#f8fafc"
456
+ };
457
+ const overlay = box ? {
458
+ position: "absolute",
459
+ left: box.x,
460
+ top: box.y,
461
+ width: box.w,
462
+ height: box.h,
463
+ outline: "2px solid #8b5cf6",
464
+ outlineOffset: -1,
465
+ cursor: "move",
466
+ touchAction: "none"
467
+ } : { display: "none" };
468
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, onClick: handleClick, style: surface, children: [
469
+ /* @__PURE__ */ jsxRuntime.jsx(CmsPage, { doc }),
470
+ /* @__PURE__ */ jsxRuntime.jsx(
471
+ "div",
472
+ {
473
+ onPointerDown: handlePointerDown,
474
+ onPointerMove: handlePointerMove,
475
+ onPointerUp: handlePointerUp,
476
+ style: overlay
477
+ }
478
+ )
479
+ ] });
480
+ }
481
+ function cssEscape(value) {
482
+ return value.replace(/["\\]/g, "\\$&");
483
+ }
484
+ function LayersPanel({ doc, selection, onSelect }) {
485
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsxRuntime.jsx(LayerRow, { doc, id, depth: 0, selection, onSelect }, id)) });
486
+ }
487
+ function LayerRow({ doc, id, depth, selection, onSelect }) {
488
+ const node = doc.nodes[id];
489
+ if (!node) return null;
490
+ const selected = selection === id;
491
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
492
+ /* @__PURE__ */ jsxRuntime.jsxs(
493
+ "button",
494
+ {
495
+ type: "button",
496
+ onClick: () => onSelect(id),
497
+ style: {
498
+ display: "block",
499
+ width: "100%",
500
+ textAlign: "left",
501
+ border: "none",
502
+ borderRadius: 6,
503
+ cursor: "pointer",
504
+ padding: "4px 8px",
505
+ paddingLeft: 8 + depth * 14,
506
+ background: selected ? "#ede9fe" : "transparent",
507
+ color: selected ? "#5b21b6" : "#334155",
508
+ font: "inherit"
509
+ },
510
+ children: [
511
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.6 }, children: node.type }),
512
+ " \xB7 ",
513
+ id
514
+ ]
515
+ }
516
+ ),
517
+ childrenOf(doc, id).map((child) => /* @__PURE__ */ jsxRuntime.jsx(
518
+ LayerRow,
519
+ {
520
+ doc,
521
+ id: child.id,
522
+ depth: depth + 1,
523
+ selection,
524
+ onSelect
525
+ },
526
+ child.id
527
+ ))
528
+ ] });
529
+ }
530
+ var label = {
531
+ display: "block",
532
+ fontSize: 11,
533
+ textTransform: "uppercase",
534
+ letterSpacing: "0.04em",
535
+ color: "#94a3b8",
536
+ margin: "10px 0 4px"
537
+ };
538
+ var input = {
539
+ width: "100%",
540
+ boxSizing: "border-box",
541
+ padding: "6px 8px",
542
+ borderRadius: 6,
543
+ border: "1px solid #e2e8f0",
544
+ font: "inherit",
545
+ fontSize: 13
546
+ };
547
+ function Inspector({ doc, selection, apply }) {
548
+ const node = selection ? doc.nodes[selection] : null;
549
+ if (!node) {
550
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, color: "#94a3b8", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
551
+ }
552
+ const s = node.style.base;
553
+ const setStyle = (patch) => apply({ t: "set_style", id: node.id, breakpoint: "base", patch });
554
+ const setProp = (key, value) => apply({ t: "set_props", id: node.id, patch: { [key]: value } });
555
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", padding: 16, fontFamily: "system-ui, sans-serif", height: "100%", boxSizing: "border-box" }, children: [
556
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "#0f172a" }, children: node.type }),
557
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#94a3b8", marginBottom: 8 }, children: node.id }),
558
+ node.type === "text" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Content" }),
560
+ /* @__PURE__ */ jsxRuntime.jsx(
561
+ "textarea",
562
+ {
563
+ style: { ...input, minHeight: 64, resize: "vertical" },
564
+ value: typeof node.props.content === "string" ? node.props.content : "",
565
+ onChange: (e) => setProp("content", e.target.value)
566
+ }
567
+ )
568
+ ] }) : null,
569
+ node.type === "image" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
570
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Source" }),
571
+ /* @__PURE__ */ jsxRuntime.jsx(
572
+ "input",
573
+ {
574
+ style: input,
575
+ value: typeof node.props.src === "string" ? node.props.src : "",
576
+ onChange: (e) => setProp("src", e.target.value)
577
+ }
578
+ )
579
+ ] }) : null,
580
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Background" }),
581
+ /* @__PURE__ */ jsxRuntime.jsx(
582
+ "input",
583
+ {
584
+ style: input,
585
+ placeholder: "#ffffff | gradient | url(\u2026)",
586
+ value: s.background ?? "",
587
+ onChange: (e) => setStyle({ background: e.target.value })
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Text color" }),
591
+ /* @__PURE__ */ jsxRuntime.jsx(
592
+ "input",
593
+ {
594
+ style: input,
595
+ placeholder: "#0f172a",
596
+ value: s.color ?? "",
597
+ onChange: (e) => setStyle({ color: e.target.value })
598
+ }
599
+ ),
600
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Font size (px)" }),
601
+ /* @__PURE__ */ jsxRuntime.jsx(
602
+ "input",
603
+ {
604
+ type: "number",
605
+ style: input,
606
+ value: s.fontSize?.value ?? "",
607
+ onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Padding (px)" }),
611
+ /* @__PURE__ */ jsxRuntime.jsx(
612
+ "input",
613
+ {
614
+ type: "number",
615
+ style: input,
616
+ value: s.padding?.value ?? "",
617
+ onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
618
+ }
619
+ ),
620
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Opacity" }),
621
+ /* @__PURE__ */ jsxRuntime.jsx(
622
+ "input",
623
+ {
624
+ type: "number",
625
+ step: "0.1",
626
+ min: "0",
627
+ max: "1",
628
+ style: input,
629
+ value: s.opacity ?? "",
630
+ onChange: (e) => setStyle({ opacity: Number(e.target.value) })
631
+ }
632
+ )
633
+ ] });
634
+ }
635
+ function Editor({ defaultValue, onChange }) {
636
+ const ed = useEditor(defaultValue);
637
+ const first = react.useRef(true);
638
+ react.useEffect(() => {
639
+ if (first.current) {
640
+ first.current = false;
641
+ return;
642
+ }
643
+ onChange?.(ed.state.doc);
644
+ }, [ed.state.doc, onChange]);
645
+ const shell = {
646
+ display: "grid",
647
+ gridTemplateColumns: "220px 1fr 300px",
648
+ height: "100%",
649
+ minHeight: 0,
650
+ background: "#fff",
651
+ color: "#0f172a",
652
+ border: "1px solid #e2e8f0",
653
+ borderRadius: 12,
654
+ overflow: "hidden"
655
+ };
656
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shell, children: [
657
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
658
+ /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { ed }),
659
+ /* @__PURE__ */ jsxRuntime.jsx(LayersPanel, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select })
660
+ ] }),
661
+ /* @__PURE__ */ jsxRuntime.jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
662
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderLeft: "1px solid #e2e8f0", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply }) })
663
+ ] });
664
+ }
665
+ function Toolbar({ ed }) {
666
+ const btn = {
667
+ font: "inherit",
668
+ fontSize: 12,
669
+ padding: "4px 10px",
670
+ borderRadius: 6,
671
+ border: "1px solid #e2e8f0",
672
+ background: "#fff",
673
+ cursor: "pointer"
674
+ };
675
+ return /* @__PURE__ */ jsxRuntime.jsxs(
676
+ "div",
677
+ {
678
+ style: {
679
+ display: "flex",
680
+ gap: 6,
681
+ padding: 8,
682
+ borderBottom: "1px solid #e2e8f0",
683
+ fontFamily: "system-ui, sans-serif"
684
+ },
685
+ children: [
686
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canUndo ? 1 : 0.4 }, disabled: !ed.canUndo, onClick: ed.undo, children: "Undo" }),
687
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" })
688
+ ]
689
+ }
690
+ );
691
+ }
692
+
693
+ exports.Canvas = Canvas;
694
+ exports.Editor = Editor;
695
+ exports.Inspector = Inspector;
696
+ exports.LayersPanel = LayersPanel;
697
+ exports.editorReduce = editorReduce;
698
+ exports.initEditor = initEditor;
699
+ exports.useEditor = useEditor;
700
+ //# sourceMappingURL=editor.cjs.map
701
+ //# sourceMappingURL=editor.cjs.map