@particle-academy/fancy-cms-ui 0.0.1-dev.8 → 0.0.1-dev.9

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/editor.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
- import { P as PageDoc, N as Node, m as StyleProps } from './types-CpfPEq3V.cjs';
2
+ import { P as PageDoc, i as NodeId, N as Node, m as StyleProps, L as LayoutMode } from './types-CpfPEq3V.cjs';
3
3
  import { a as ElementRegistry } from './registry-Bk7GN1rA.cjs';
4
- import { P as PageOp } from './ops-C_s0FGeV.cjs';
4
+ import { P as PageOp } from './ops-CdBZarAT.cjs';
5
5
 
6
6
  interface EditorProps {
7
7
  /** Initial document; edits are surfaced via {@link EditorProps.onChange}. */
@@ -73,9 +73,11 @@ interface LayersPanelProps {
73
73
  doc: PageDoc;
74
74
  selection: string | null;
75
75
  onSelect: (id: string) => void;
76
+ /** Reparent/reorder a node (drag-to-reorder). Omit to disable dragging. */
77
+ onMove?: (id: NodeId, parent: NodeId | null, order: string) => void;
76
78
  }
77
- /** The section/layer tree. Click a row to select. */
78
- declare function LayersPanel({ doc, selection, onSelect }: LayersPanelProps): ReactElement;
79
+ /** The section/layer tree. Click a row to select; drag a row onto another to reorder. */
80
+ declare function LayersPanel({ doc, selection, onSelect, onMove }: LayersPanelProps): ReactElement;
79
81
 
80
82
  interface InspectorProps {
81
83
  doc: PageDoc;
@@ -98,6 +100,8 @@ interface NodeInspectorProps {
98
100
  onProps: (patch: Record<string, unknown>) => void;
99
101
  /** Patch the node's base style. */
100
102
  onStyle: (patch: Partial<StyleProps>) => void;
103
+ /** Set how a container arranges its children (free / stack / grid). */
104
+ onLayout?: (layout: LayoutMode | undefined) => void;
101
105
  /** Commit a transform (position / size / opacity / scale / rotate) into the active keyframe. */
102
106
  onTransform: (t: NodeTransform) => void;
103
107
  onRemove: () => void;
@@ -111,7 +115,7 @@ interface NodeInspectorProps {
111
115
  * box. Every control writes through the op-spine or the active keyframe, so edits
112
116
  * are undoable and animatable.
113
117
  */
114
- declare function NodeInspector({ node, transform, measured, onProps, onStyle, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
118
+ declare function NodeInspector({ node, transform, measured, onProps, onStyle, onLayout, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
115
119
 
116
120
  /**
117
121
  * Editor state engine — pure and framework-agnostic so it's unit-testable.
package/dist/editor.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
- import { P as PageDoc, N as Node, m as StyleProps } from './types-CpfPEq3V.js';
2
+ import { P as PageDoc, i as NodeId, N as Node, m as StyleProps, L as LayoutMode } from './types-CpfPEq3V.js';
3
3
  import { a as ElementRegistry } from './registry-Dht9JBLU.js';
4
- import { P as PageOp } from './ops-nevYkDzK.js';
4
+ import { P as PageOp } from './ops-BsM6kZxY.js';
5
5
 
6
6
  interface EditorProps {
7
7
  /** Initial document; edits are surfaced via {@link EditorProps.onChange}. */
@@ -73,9 +73,11 @@ interface LayersPanelProps {
73
73
  doc: PageDoc;
74
74
  selection: string | null;
75
75
  onSelect: (id: string) => void;
76
+ /** Reparent/reorder a node (drag-to-reorder). Omit to disable dragging. */
77
+ onMove?: (id: NodeId, parent: NodeId | null, order: string) => void;
76
78
  }
77
- /** The section/layer tree. Click a row to select. */
78
- declare function LayersPanel({ doc, selection, onSelect }: LayersPanelProps): ReactElement;
79
+ /** The section/layer tree. Click a row to select; drag a row onto another to reorder. */
80
+ declare function LayersPanel({ doc, selection, onSelect, onMove }: LayersPanelProps): ReactElement;
79
81
 
80
82
  interface InspectorProps {
81
83
  doc: PageDoc;
@@ -98,6 +100,8 @@ interface NodeInspectorProps {
98
100
  onProps: (patch: Record<string, unknown>) => void;
99
101
  /** Patch the node's base style. */
100
102
  onStyle: (patch: Partial<StyleProps>) => void;
103
+ /** Set how a container arranges its children (free / stack / grid). */
104
+ onLayout?: (layout: LayoutMode | undefined) => void;
101
105
  /** Commit a transform (position / size / opacity / scale / rotate) into the active keyframe. */
102
106
  onTransform: (t: NodeTransform) => void;
103
107
  onRemove: () => void;
@@ -111,7 +115,7 @@ interface NodeInspectorProps {
111
115
  * box. Every control writes through the op-spine or the active keyframe, so edits
112
116
  * are undoable and animatable.
113
117
  */
114
- declare function NodeInspector({ node, transform, measured, onProps, onStyle, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
118
+ declare function NodeInspector({ node, transform, measured, onProps, onStyle, onLayout, onTransform, onRemove, onClose, }: NodeInspectorProps): ReactElement;
115
119
 
116
120
  /**
117
121
  * Editor state engine — pure and framework-agnostic so it's unit-testable.
package/dist/editor.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { keyBetween } from './chunk-7YYLYBLI.js';
2
- import { CmsPage, defaultRegistry } from './chunk-5O6U4VIU.js';
3
- import { reduce, childrenOf } from './chunk-HYHNMW47.js';
2
+ import { CmsPage, defaultRegistry } from './chunk-ZV7CV54P.js';
3
+ import { reduce, childrenOf } from './chunk-3YZMELJJ.js';
4
4
  import { useReducer, useCallback, useMemo, useRef, useState, useLayoutEffect, Fragment, useEffect } from 'react';
5
5
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
6
6
  import { ContextMenu } from '@particle-academy/react-fancy';
@@ -157,31 +157,66 @@ function Canvas({ doc, selection, onSelect, apply }) {
157
157
  function cssEscape(value) {
158
158
  return value.replace(/["\\]/g, "\\$&");
159
159
  }
160
- function LayersPanel({ doc, selection, onSelect }) {
161
- return /* @__PURE__ */ jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsx(LayerRow, { doc, id, depth: 0, selection, onSelect }, id)) });
160
+ var MIME = "application/x-cms-node";
161
+ function isAncestorOrSelf(doc, id, maybeAncestor) {
162
+ for (let cur = id; cur; cur = doc.nodes[cur]?.parent ?? null) {
163
+ if (cur === maybeAncestor) return true;
164
+ }
165
+ return false;
166
+ }
167
+ function LayersPanel({ doc, selection, onSelect, onMove }) {
168
+ const [overId, setOverId] = useState(null);
169
+ return /* @__PURE__ */ jsx("div", { style: { overflow: "auto", padding: 8, fontSize: 13, fontFamily: "system-ui, sans-serif" }, children: doc.sections.map((id) => /* @__PURE__ */ jsx(LayerRow, { doc, id, depth: 0, selection, onSelect, onMove, overId, setOverId }, id)) });
162
170
  }
163
- function LayerRow({ doc, id, depth, selection, onSelect }) {
171
+ function LayerRow({ doc, id, depth, selection, onSelect, onMove, overId, setOverId }) {
164
172
  const node = doc.nodes[id];
165
173
  if (!node) return null;
166
174
  const selected = selection === id;
175
+ const draggable = Boolean(onMove);
176
+ const drop = (e) => {
177
+ e.preventDefault();
178
+ e.stopPropagation();
179
+ setOverId(null);
180
+ if (!onMove) return;
181
+ const dragId = e.dataTransfer.getData(MIME);
182
+ if (!dragId || dragId === id || isAncestorOrSelf(doc, id, dragId)) return;
183
+ const siblings = childrenOf(doc, node.parent);
184
+ const idx = siblings.findIndex((n) => n.id === id);
185
+ const next = siblings[idx + 1];
186
+ onMove(dragId, node.parent, keyBetween(node.order, next ? next.order : null));
187
+ };
167
188
  return /* @__PURE__ */ jsxs(Fragment, { children: [
168
189
  /* @__PURE__ */ jsxs(
169
190
  "button",
170
191
  {
171
192
  type: "button",
193
+ draggable,
172
194
  onClick: () => onSelect(id),
195
+ onDragStart: (e) => {
196
+ e.dataTransfer.setData(MIME, id);
197
+ e.dataTransfer.effectAllowed = "move";
198
+ },
199
+ onDragOver: (e) => {
200
+ if (!onMove) return;
201
+ e.preventDefault();
202
+ e.dataTransfer.dropEffect = "move";
203
+ if (overId !== id) setOverId(id);
204
+ },
205
+ onDragLeave: () => overId === id && setOverId(null),
206
+ onDrop: drop,
173
207
  style: {
174
208
  display: "block",
175
209
  width: "100%",
176
210
  textAlign: "left",
177
211
  border: "none",
178
212
  borderRadius: 6,
179
- cursor: "pointer",
213
+ cursor: draggable ? "grab" : "pointer",
180
214
  padding: "4px 8px",
181
215
  paddingLeft: 8 + depth * 14,
182
216
  background: selected ? "#ede9fe" : "transparent",
183
217
  color: selected ? "#5b21b6" : "#334155",
184
- font: "inherit"
218
+ font: "inherit",
219
+ boxShadow: overId === id ? "inset 0 -2px 0 0 #8b5cf6" : "none"
185
220
  },
186
221
  children: [
187
222
  /* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: node.type }),
@@ -190,17 +225,7 @@ function LayerRow({ doc, id, depth, selection, onSelect }) {
190
225
  ]
191
226
  }
192
227
  ),
193
- childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(
194
- LayerRow,
195
- {
196
- doc,
197
- id: child.id,
198
- depth: depth + 1,
199
- selection,
200
- onSelect
201
- },
202
- child.id
203
- ))
228
+ childrenOf(doc, id).map((child) => /* @__PURE__ */ jsx(LayerRow, { doc, id: child.id, depth: depth + 1, selection, onSelect, onMove, overId, setOverId }, child.id))
204
229
  ] });
205
230
  }
206
231
  var label = {
@@ -332,7 +357,15 @@ function Editor({ defaultValue, onChange }) {
332
357
  return /* @__PURE__ */ jsxs("div", { style: shell, children: [
333
358
  /* @__PURE__ */ jsxs("div", { style: { borderRight: "1px solid #e2e8f0", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
334
359
  /* @__PURE__ */ jsx(Toolbar, { ed }),
335
- /* @__PURE__ */ jsx(LayersPanel, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select })
360
+ /* @__PURE__ */ jsx(
361
+ LayersPanel,
362
+ {
363
+ doc: ed.state.doc,
364
+ selection: ed.state.selection,
365
+ onSelect: ed.select,
366
+ onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
367
+ }
368
+ )
336
369
  ] }),
337
370
  /* @__PURE__ */ jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply }),
338
371
  /* @__PURE__ */ jsx("div", { style: { borderLeft: "1px solid #e2e8f0", minHeight: 0 }, children: /* @__PURE__ */ jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply }) })
@@ -372,6 +405,7 @@ function NodeInspector({
372
405
  measured,
373
406
  onProps,
374
407
  onStyle,
408
+ onLayout,
375
409
  onTransform,
376
410
  onRemove,
377
411
  onClose
@@ -454,9 +488,44 @@ function NodeInspector({
454
488
  ] }),
455
489
  /* @__PURE__ */ jsxs(Row, { children: [
456
490
  /* @__PURE__ */ jsx(Num, { label: "Padding", value: base.padding?.value, placeholder: "0", onChange: (v) => onStyle({ padding: v == null ? void 0 : len(v) }) }),
491
+ /* @__PURE__ */ jsx(Num, { label: "Margin", value: base.margin?.value, placeholder: "0", onChange: (v) => onStyle({ margin: v == null ? void 0 : len(v) }) })
492
+ ] }),
493
+ /* @__PURE__ */ jsx(Row, { children: /* @__PURE__ */ jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
494
+ ] }),
495
+ CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxs(Group, { label: "Layout", children: [
496
+ /* @__PURE__ */ jsxs(Row, { children: [
497
+ /* @__PURE__ */ jsx(
498
+ Sel,
499
+ {
500
+ label: "Mode",
501
+ value: node.layout ?? "free",
502
+ options: [["free", "Free"], ["stack", "Stack"], ["grid", "Grid"]],
503
+ onChange: (v) => onLayout?.(v)
504
+ }
505
+ ),
457
506
  /* @__PURE__ */ jsx(Num, { label: "Gap", value: base.gap?.value, placeholder: "0", onChange: (v) => onStyle({ gap: v == null ? void 0 : len(v) }) })
507
+ ] }),
508
+ /* @__PURE__ */ jsxs(Row, { children: [
509
+ /* @__PURE__ */ jsx(
510
+ Sel,
511
+ {
512
+ label: "Align",
513
+ value: base.align ?? "",
514
+ options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["stretch", "Stretch"]],
515
+ onChange: (v) => onStyle({ align: v || void 0 })
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsx(
519
+ Sel,
520
+ {
521
+ label: "Justify",
522
+ value: base.justify ?? "",
523
+ options: [["", "\u2014"], ["start", "Start"], ["center", "Center"], ["end", "End"], ["between", "Between"], ["around", "Around"]],
524
+ onChange: (v) => onStyle({ justify: v || void 0 })
525
+ }
526
+ )
458
527
  ] })
459
- ] })
528
+ ] }) : null
460
529
  ] })
461
530
  ] });
462
531
  }
@@ -511,6 +580,12 @@ function Num({ label: label2, value, placeholder, onChange, step, min, max }) {
511
580
  )
512
581
  ] });
513
582
  }
583
+ function Txt({ label: label2, value, placeholder, onChange }) {
584
+ return /* @__PURE__ */ jsxs("label", { style: field, children: [
585
+ /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
586
+ /* @__PURE__ */ jsx("input", { style: input2, type: "text", value, placeholder, onChange: (e) => onChange(e.target.value) })
587
+ ] });
588
+ }
514
589
  function Color({ label: label2, value, onChange }) {
515
590
  return /* @__PURE__ */ jsxs("label", { style: field, children: [
516
591
  /* @__PURE__ */ jsx("span", { style: fieldLabel, children: label2 }),
@@ -777,6 +852,7 @@ function EditablePage({
777
852
  );
778
853
  const setStyle = (patch) => selection && ed.apply({ t: "set_style", id: selection, breakpoint: "base", patch });
779
854
  const setProps = (patch) => selection && ed.apply({ t: "set_props", id: selection, patch });
855
+ const setLayout = (layout) => selection && ed.apply({ t: "set_layout", id: selection, layout });
780
856
  const removeSelected = () => {
781
857
  if (!selection) return;
782
858
  ed.apply({ t: "remove_node", id: selection });
@@ -801,6 +877,8 @@ function EditablePage({
801
877
  ed.select(id);
802
878
  requestAnimationFrame(() => pageRef.current?.querySelector(`[data-cms="${cssEscape2(id)}"]`)?.focus());
803
879
  };
880
+ const nudgeRef = useRef({ transforms, onNodeTransform });
881
+ nudgeRef.current = { transforms, onNodeTransform };
804
882
  useEffect(() => {
805
883
  if (!editing) return;
806
884
  const onKey = (e) => {
@@ -819,6 +897,15 @@ function EditablePage({
819
897
  } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "v") {
820
898
  e.preventDefault();
821
899
  paste(selection);
900
+ } else if (selection && /^Arrow(Left|Right|Up|Down)$/.test(e.key)) {
901
+ const { transforms: tf, onNodeTransform: commit } = nudgeRef.current;
902
+ if (!commit) return;
903
+ e.preventDefault();
904
+ const step = e.shiftKey ? 10 : 1;
905
+ const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
906
+ const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
907
+ const cur = tf?.[selection] ?? {};
908
+ commit(selection, { ...cur, x: (cur.x ?? 0) + dx, y: (cur.y ?? 0) + dy });
822
909
  }
823
910
  };
824
911
  window.addEventListener("keydown", onKey);
@@ -872,6 +959,7 @@ function EditablePage({
872
959
  measured: { w: box.w, h: box.h },
873
960
  onProps: setProps,
874
961
  onStyle: setStyle,
962
+ onLayout: setLayout,
875
963
  onTransform: (t) => selection && onNodeTransform?.(selection, t),
876
964
  onRemove: removeSelected,
877
965
  onClose: () => ed.select(null)