@particle-academy/fancy-cms-ui 0.7.0 → 0.8.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/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { B as Binding, P as PageDoc, m as NodeId, N as Node, J as Json, q as StyleProps, L as LayoutMode } from './registry-CiPir27w.cjs';
2
- export { A as Action, a as ActionEvent, b as ActionHandler, c as Anim, d as AnimDriver, e as Bound, f as Breakpoint, C as Constraints, D as DEFAULT_BREAKPOINTS, g as DataContext, E as ElementContext, h as ElementRegistry, i as ElementRenderer, K as Keyframe, j as LegacyPageDoc, k as Length, l as LengthUnit, M as MigrateResult, n as PageMeta, o as PerBreakpoint, S as ScrollMode, p as SizeMode, T as ThemeTokens, r as defaultRegistry, s as emptyDoc, t as getPath, u as isBinding, v as migrateDoc, w as migrateDocVerbose, x as needsMigration, y as resolveValue } from './registry-CiPir27w.cjs';
3
- import { P as PageOp } from './useEditor-CoxNeC53.cjs';
4
- export { A as Actor, C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, O as OpEnvelope, h as PageOpType, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-CoxNeC53.cjs';
1
+ import { B as Binding, P as PageDoc, m as NodeId, N as Node, J as Json, q as StyleProps, L as LayoutMode } from './registry-B4FaQDip.cjs';
2
+ export { A as Action, a as ActionEvent, b as ActionHandler, c as Anim, d as AnimDriver, e as Bound, f as Breakpoint, C as Constraints, D as DEFAULT_BREAKPOINTS, g as DataContext, E as ElementContext, h as ElementRegistry, i as ElementRenderer, K as Keyframe, j as LegacyPageDoc, k as Length, l as LengthUnit, M as MigrateResult, n as PageMeta, o as PerBreakpoint, S as ScrollMode, p as SizeMode, T as ThemeTokens, r as defaultRegistry, s as emptyDoc, t as getPath, u as hasNodeMap, v as isBinding, w as migrateDoc, x as migrateDocVerbose, y as needsMigration, z as resolveValue } from './registry-B4FaQDip.cjs';
3
+ import { P as PageOp } from './useEditor-CIRm2oi-.cjs';
4
+ export { A as Actor, C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, O as OpEnvelope, h as PageOpType, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-CIRm2oi-.cjs';
5
5
  export { fractionalKey, fractionalKey as keyBetween } from '@particle-academy/fancy-doc-commons';
6
6
  export { CmsPage, CmsPageProps, CmsRegion, CmsRegionProps, RenderNode, RenderNodeProps } from './react.cjs';
7
7
  import 'react';
@@ -120,7 +120,7 @@ declare function emitDocCss(doc: PageDoc): string;
120
120
  */
121
121
  /** Node types that accept children, so an insert lands *inside* them. */
122
122
  declare const CONTAINER_TYPES: Set<string>;
123
- type AddKind = "text" | "heading" | "button" | "link" | "image" | "box" | "stack" | "grid" | "card" | "callout" | "divider" | "code" | "richtext" | "repeater";
123
+ type AddKind = "text" | "heading" | "button" | "link" | "image" | "box" | "stack" | "grid" | "card" | "callout" | "divider" | "code" | "richtext" | "repeater" | "list";
124
124
  declare const ADD_DEFAULTS: Record<AddKind, {
125
125
  type: string;
126
126
  props: Record<string, Json>;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { B as Binding, P as PageDoc, m as NodeId, N as Node, J as Json, q as StyleProps, L as LayoutMode } from './registry-CiPir27w.js';
2
- export { A as Action, a as ActionEvent, b as ActionHandler, c as Anim, d as AnimDriver, e as Bound, f as Breakpoint, C as Constraints, D as DEFAULT_BREAKPOINTS, g as DataContext, E as ElementContext, h as ElementRegistry, i as ElementRenderer, K as Keyframe, j as LegacyPageDoc, k as Length, l as LengthUnit, M as MigrateResult, n as PageMeta, o as PerBreakpoint, S as ScrollMode, p as SizeMode, T as ThemeTokens, r as defaultRegistry, s as emptyDoc, t as getPath, u as isBinding, v as migrateDoc, w as migrateDocVerbose, x as needsMigration, y as resolveValue } from './registry-CiPir27w.js';
3
- import { P as PageOp } from './useEditor-DAE36OYU.js';
4
- export { A as Actor, C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, O as OpEnvelope, h as PageOpType, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-DAE36OYU.js';
1
+ import { B as Binding, P as PageDoc, m as NodeId, N as Node, J as Json, q as StyleProps, L as LayoutMode } from './registry-B4FaQDip.js';
2
+ export { A as Action, a as ActionEvent, b as ActionHandler, c as Anim, d as AnimDriver, e as Bound, f as Breakpoint, C as Constraints, D as DEFAULT_BREAKPOINTS, g as DataContext, E as ElementContext, h as ElementRegistry, i as ElementRenderer, K as Keyframe, j as LegacyPageDoc, k as Length, l as LengthUnit, M as MigrateResult, n as PageMeta, o as PerBreakpoint, S as ScrollMode, p as SizeMode, T as ThemeTokens, r as defaultRegistry, s as emptyDoc, t as getPath, u as hasNodeMap, v as isBinding, w as migrateDoc, x as migrateDocVerbose, y as needsMigration, z as resolveValue } from './registry-B4FaQDip.js';
3
+ import { P as PageOp } from './useEditor-DZDD9qpS.js';
4
+ export { A as Actor, C as Canvas, a as CanvasProps, E as Editor, b as EditorAction, c as EditorApi, d as EditorProps, e as EditorState, I as Inspector, f as InspectorProps, L as LayersPanel, g as LayersPanelProps, O as OpEnvelope, h as PageOpType, i as editorReduce, j as initEditor, u as useEditor } from './useEditor-DZDD9qpS.js';
5
5
  export { fractionalKey, fractionalKey as keyBetween } from '@particle-academy/fancy-doc-commons';
6
6
  export { CmsPage, CmsPageProps, CmsRegion, CmsRegionProps, RenderNode, RenderNodeProps } from './react.js';
7
7
  import 'react';
@@ -120,7 +120,7 @@ declare function emitDocCss(doc: PageDoc): string;
120
120
  */
121
121
  /** Node types that accept children, so an insert lands *inside* them. */
122
122
  declare const CONTAINER_TYPES: Set<string>;
123
- type AddKind = "text" | "heading" | "button" | "link" | "image" | "box" | "stack" | "grid" | "card" | "callout" | "divider" | "code" | "richtext" | "repeater";
123
+ type AddKind = "text" | "heading" | "button" | "link" | "image" | "box" | "stack" | "grid" | "card" | "callout" | "divider" | "code" | "richtext" | "repeater" | "list";
124
124
  declare const ADD_DEFAULTS: Record<AddKind, {
125
125
  type: string;
126
126
  props: Record<string, Json>;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- export { CmsRegion } from './chunk-BWGAUTKG.js';
2
- export { ADD_DEFAULTS, ADD_MENU, CONTAINER_TYPES, Canvas, Editor, Inspector, LayersPanel, buildInsertOp, editorReduce, initEditor, resolveInsertParent, useEditor } from './chunk-YOAZRQKN.js';
3
- import { isBinding } from './chunk-IWQQINCF.js';
4
- export { CmsPage, DEFAULT_BREAKPOINTS, RenderNode, childrenOf, defaultRegistry, descendantIds, emitDocCss, emptyDoc, getPath, invert, isBinding, lastRootId, migrateDoc, migrateDocVerbose, needsMigration, reduce, reduceAll, resolveValue, rootIds } from './chunk-IWQQINCF.js';
1
+ export { CmsRegion } from './chunk-V6QIOJI7.js';
2
+ export { ADD_DEFAULTS, ADD_MENU, CONTAINER_TYPES, Canvas, Editor, Inspector, LayersPanel, buildInsertOp, editorReduce, initEditor, resolveInsertParent, useEditor } from './chunk-IOMIYI45.js';
3
+ import { isBinding } from './chunk-SJYKJ6SG.js';
4
+ export { CmsPage, DEFAULT_BREAKPOINTS, RenderNode, childrenOf, defaultRegistry, descendantIds, emitDocCss, emptyDoc, getPath, hasNodeMap, invert, isBinding, lastRootId, migrateDoc, migrateDocVerbose, needsMigration, reduce, reduceAll, resolveValue, rootIds } from './chunk-SJYKJ6SG.js';
5
5
  export { fractionalKey, fractionalKey as keyBetween } from '@particle-academy/fancy-doc-commons';
6
6
 
7
7
  // src/document/bindings.ts
package/dist/react.cjs CHANGED
@@ -1,26 +1,53 @@
1
1
  'use strict';
2
2
 
3
+ var react = require('react');
3
4
  var fancyDocCommons = require('@particle-academy/fancy-doc-commons');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
- var react = require('react');
6
6
 
7
- // src/document/reduce.ts
8
- function childrenOf(doc, parent) {
9
- return fancyDocCommons.childrenOf(doc, parent);
10
- }
11
- function rootIds(doc) {
12
- return childrenOf(doc, null).map((n) => n.id);
13
- }
7
+ // src/react/RenderNode.tsx
14
8
 
15
9
  // src/document/types.ts
16
10
  function isBinding(v) {
17
11
  return typeof v === "object" && v !== null && typeof v.$bind === "string";
18
12
  }
13
+ function hasNodeMap(value) {
14
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
15
+ const nodes = value.nodes;
16
+ return typeof nodes === "object" && nodes !== null && !Array.isArray(nodes);
17
+ }
19
18
  var DEFAULT_BREAKPOINTS = {
20
19
  base: 0,
21
20
  md: 768,
22
21
  lg: 1024
23
22
  };
23
+ function emptyDoc(id, slug = "/") {
24
+ return {
25
+ id,
26
+ seq: 0,
27
+ meta: { title: "Untitled", slug, scrollMode: "smooth" },
28
+ theme: { name: "default" },
29
+ breakpoints: ["base", "md", "lg"],
30
+ nodes: {}
31
+ };
32
+ }
33
+
34
+ // src/document/own.ts
35
+ var has = Object.prototype.hasOwnProperty;
36
+ function own(map, key) {
37
+ return map != null && has.call(map, key) ? map[key] : void 0;
38
+ }
39
+ function setOwn(map, key, value) {
40
+ Object.defineProperty(map, key, { value, writable: true, enumerable: true, configurable: true });
41
+ }
42
+
43
+ // src/document/reduce.ts
44
+ function childrenOf(doc, parent) {
45
+ return fancyDocCommons.childrenOf(doc, parent);
46
+ }
47
+ function rootIds(doc) {
48
+ if (!hasNodeMap(doc)) return [];
49
+ return childrenOf(doc, null).map((n) => n.id);
50
+ }
24
51
  function getPath(data, path) {
25
52
  return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], data);
26
53
  }
@@ -29,6 +56,48 @@ function resolveValue(v, data) {
29
56
  if (isBinding(v)) return getPath(data, v.$bind);
30
57
  return v;
31
58
  }
59
+ var LANDMARK_TAGS = ["section", "header", "nav", "main", "footer", "article", "aside", "div"];
60
+ var LANDMARKS = new Set(LANDMARK_TAGS);
61
+ var CONTAINERS = /* @__PURE__ */ new Set(["stack", "grid", "frame", "shape", "card"]);
62
+ var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
63
+ function safeHref(raw) {
64
+ const href = raw.trim();
65
+ if (!href) return null;
66
+ const probe = href.replace(/[\u0000- \u007f]+/g, "");
67
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(probe);
68
+ if (scheme && !SAFE_SCHEMES.has(scheme[1].toLowerCase())) return null;
69
+ return href;
70
+ }
71
+ var ANCHOR_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
72
+ function anchorFor(node, text) {
73
+ const anchor = text(node.props.anchor);
74
+ return ANCHOR_PATTERN.test(anchor) ? anchor : null;
75
+ }
76
+ function elementFor(node, text) {
77
+ const as = text(node.props.as);
78
+ switch (node.type) {
79
+ case "heading": {
80
+ const level = Number(text(node.props.level) || 2);
81
+ return { tag: Number.isInteger(level) && level >= 1 && level <= 6 ? `h${level}` : "h2" };
82
+ }
83
+ case "text":
84
+ return { tag: "p" };
85
+ case "section":
86
+ return { tag: LANDMARKS.has(as) ? as : "section" };
87
+ case "list":
88
+ return { tag: text(node.props.ordered) === "true" ? "ol" : "ul" };
89
+ case "button":
90
+ case "link": {
91
+ const href = safeHref(text(node.props.href));
92
+ if (href === null) return { tag: node.type === "link" ? "span" : "div" };
93
+ const attrs = { href };
94
+ if (/^https?:\/\//i.test(href)) attrs.rel = "noopener noreferrer";
95
+ return { tag: "a", attrs };
96
+ }
97
+ default:
98
+ return { tag: CONTAINERS.has(node.type) && LANDMARKS.has(as) ? as : "div" };
99
+ }
100
+ }
32
101
  var defaultRegistry = {
33
102
  section: ({ children }) => children,
34
103
  stack: ({ children }) => children,
@@ -41,7 +110,12 @@ var defaultRegistry = {
41
110
  // Rich text — inline-formatted HTML (gradient spans, links, <code>, …) that
42
111
  // plain `text` can't express. The author owns the markup (it's their content).
43
112
  richtext: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: text(node.props.html) } }),
44
- link: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: text(node.props.href) || "#", style: { color: "inherit", textDecoration: "underline" }, children: text(node.props.content) || "link" }),
113
+ // The [data-cms] element is already the <a> (see elementFor), so a link renders its label only.
114
+ link: ({ node, text }) => text(node.props.content) || "link",
115
+ list: ({ node, resolve }) => {
116
+ const items = resolve(node.props.items);
117
+ return Array.isArray(items) ? items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: item === null || item === void 0 ? "" : String(item) }, i)) : null;
118
+ },
45
119
  divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
46
120
  callout: ({ node, text }) => {
47
121
  const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[text(node.props.variant)] ?? "#3b82f6";
@@ -53,15 +127,20 @@ var defaultRegistry = {
53
127
  const variant = text(node.props.variant) || "primary";
54
128
  return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label });
55
129
  },
56
- image: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx(
57
- "img",
58
- {
59
- src: text(node.props.src),
60
- alt: text(node.props.alt),
61
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
62
- }
63
- )
130
+ image: ({ node, text }) => {
131
+ const fit = text(node.props.fit);
132
+ return /* @__PURE__ */ jsxRuntime.jsx(
133
+ "img",
134
+ {
135
+ src: text(node.props.src),
136
+ alt: text(node.props.alt),
137
+ loading: text(node.props.loading) === "eager" ? "eager" : "lazy",
138
+ style: { width: "100%", height: "100%", objectFit: IMAGE_FITS.has(fit) ? fit : "cover", display: "block" }
139
+ }
140
+ );
141
+ }
64
142
  };
143
+ var IMAGE_FITS = /* @__PURE__ */ new Set(["cover", "contain", "fill", "none", "scale-down"]);
65
144
  function buttonStyle(variant) {
66
145
  const base = {
67
146
  display: "inline-flex",
@@ -80,7 +159,7 @@ function buttonStyle(variant) {
80
159
  return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
81
160
  }
82
161
  function RenderNode({ doc, id, registry = defaultRegistry, data }) {
83
- const node = doc.nodes[id];
162
+ const node = own(doc.nodes, id);
84
163
  if (!node) return null;
85
164
  if (node.type === "repeater") {
86
165
  const arr = resolveValue(node.props.items, data);
@@ -96,19 +175,37 @@ function RenderNode({ doc, id, registry = defaultRegistry, data }) {
96
175
  };
97
176
  const renderer = registry[node.type];
98
177
  const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;
99
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cms": node.id, "data-cms-island": node.island ? node.type : void 0, className: node.className, children: inner });
178
+ const { tag, attrs } = elementFor(node, text);
179
+ const anchor = anchorFor(node, text);
180
+ return react.createElement(
181
+ tag,
182
+ {
183
+ "data-cms": node.id,
184
+ "data-cms-island": node.island ? node.type : void 0,
185
+ className: node.className,
186
+ ...anchor ? { id: anchor } : {},
187
+ ...attrs
188
+ },
189
+ inner
190
+ );
191
+ }
192
+ function normalizeDoc(input) {
193
+ if (hasNodeMap(input)) return input;
194
+ const id = typeof input === "object" && input !== null && !Array.isArray(input) && typeof input.id === "string" ? input.id : "malformed";
195
+ return emptyDoc(id);
100
196
  }
101
197
  function migrateDocVerbose(input) {
102
- const legacy = input;
198
+ const normalized = normalizeDoc(input);
199
+ const legacy = normalized;
103
200
  const sections = legacy.sections;
104
201
  if (!Array.isArray(sections)) {
105
- return { doc: input, migrated: false, danglingIds: [], unlistedRootIds: [] };
202
+ return { doc: normalized, migrated: false, danglingIds: [], unlistedRootIds: [] };
106
203
  }
107
204
  const danglingIds = [];
108
205
  const ordered = [];
109
206
  const seen = /* @__PURE__ */ new Set();
110
207
  for (const id of sections) {
111
- if (!input.nodes[id]) {
208
+ if (!own(normalized.nodes, id)) {
112
209
  danglingIds.push(id);
113
210
  continue;
114
211
  }
@@ -116,13 +213,13 @@ function migrateDocVerbose(input) {
116
213
  seen.add(id);
117
214
  ordered.push(id);
118
215
  }
119
- const unlistedRootIds = Object.values(input.nodes).filter((n) => n.parent === null && !seen.has(n.id)).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0).map((n) => n.id);
216
+ const unlistedRootIds = Object.values(normalized.nodes).filter((n) => n.parent === null && !seen.has(n.id)).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0).map((n) => n.id);
120
217
  const finalOrder = [...ordered, ...unlistedRootIds];
121
- const nodes = { ...input.nodes };
218
+ const nodes = { ...normalized.nodes };
122
219
  let prev = null;
123
220
  for (const id of finalOrder) {
124
221
  const key = fancyDocCommons.fractionalKey(prev, null);
125
- nodes[id] = { ...nodes[id], order: key };
222
+ setOwn(nodes, id, { ...own(nodes, id), order: key });
126
223
  prev = key;
127
224
  }
128
225
  const { sections: _dropped, ...rest } = legacy;
@@ -138,15 +235,36 @@ function migrateDoc(input) {
138
235
  }
139
236
 
140
237
  // src/render/css.ts
141
- var selectorFor = (id) => `[data-cms="${id}"]`;
238
+ function cssString(value) {
239
+ let out = "";
240
+ for (const ch of value) {
241
+ const code = ch.codePointAt(0);
242
+ if (ch === "\\" || ch === '"') out += `\\${ch}`;
243
+ else if (code < 32 || code === 127 || ch === "<") out += `\\${code.toString(16)} `;
244
+ else out += ch;
245
+ }
246
+ return out;
247
+ }
248
+ var selectorFor = (id) => `[data-cms="${cssString(id)}"]`;
249
+ var UNITS = /* @__PURE__ */ new Set(["px", "%", "vw", "vh", "rem", "fr"]);
142
250
  function lengthToCss(len) {
143
- return `${len.value}${len.unit}`;
251
+ if (typeof len !== "object" || len === null) return null;
252
+ const { value, unit } = len;
253
+ if (typeof value !== "number" || !Number.isFinite(value) || typeof unit !== "string" || !UNITS.has(unit)) return null;
254
+ return `${value}${unit}`;
144
255
  }
145
256
  function sizeToCss(size) {
146
257
  if (size === "fill") return "100%";
147
258
  if (size === "hug") return "auto";
148
259
  return lengthToCss(size);
149
260
  }
261
+ function numberToCss(value) {
262
+ return typeof value === "number" && Number.isFinite(value) ? String(value) : null;
263
+ }
264
+ function putLen(d, props, len) {
265
+ const css = lengthToCss(len);
266
+ if (css !== null) for (const p of props) d[p] = css;
267
+ }
150
268
  var ALIGN = {
151
269
  start: "flex-start",
152
270
  center: "center",
@@ -160,51 +278,150 @@ var JUSTIFY = {
160
278
  between: "space-between",
161
279
  around: "space-around"
162
280
  };
281
+ function safeCssValue(value) {
282
+ if (/[{}<]/.test(value)) return null;
283
+ let depth = 0;
284
+ let quote = null;
285
+ for (const ch of value) {
286
+ if (quote) {
287
+ if (ch === quote) quote = null;
288
+ } else if (ch === '"' || ch === "'") {
289
+ quote = ch;
290
+ } else if (ch === "(") {
291
+ depth++;
292
+ } else if (ch === ")") {
293
+ depth = Math.max(0, depth - 1);
294
+ } else if (ch === ";" && depth === 0) {
295
+ return null;
296
+ }
297
+ }
298
+ return value;
299
+ }
300
+ function put(d, prop, value) {
301
+ if (value === void 0) return;
302
+ const safe = safeCssValue(value);
303
+ if (safe !== null) d[prop] = safe;
304
+ }
305
+ var OVERFLOW = /* @__PURE__ */ new Set(["visible", "hidden", "auto"]);
306
+ var TEXT_TRANSFORM = /* @__PURE__ */ new Set(["none", "uppercase", "lowercase", "capitalize"]);
307
+ var BACKGROUND_SIZE = /* @__PURE__ */ new Set(["cover", "contain", "auto"]);
308
+ var TEXT_ALIGN = /* @__PURE__ */ new Set(["left", "center", "right", "justify"]);
309
+ var DIRECTIONS = /* @__PURE__ */ new Set(["row", "column"]);
310
+ function keyword(map, value) {
311
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(map, value) ? map[value] : null;
312
+ }
313
+ function inSet(set, value) {
314
+ return typeof value === "string" && set.has(value) ? value : null;
315
+ }
163
316
  function styleDecls(style) {
164
317
  const d = {};
165
- if (style.padding) d["padding"] = lengthToCss(style.padding);
166
- if (style.margin) d["margin"] = lengthToCss(style.margin);
167
- if (style.radius) d["border-radius"] = lengthToCss(style.radius);
168
- if (style.background !== void 0) d["background"] = style.background;
169
- if (style.color !== void 0) d["color"] = style.color;
170
- if (style.opacity !== void 0) d["opacity"] = String(style.opacity);
171
- if (style.fontFamily !== void 0) d["font-family"] = style.fontFamily;
172
- if (style.fontSize) d["font-size"] = lengthToCss(style.fontSize);
173
- if (style.fontWeight !== void 0) d["font-weight"] = String(style.fontWeight);
174
- if (style.lineHeight !== void 0) d["line-height"] = String(style.lineHeight);
175
- if (style.textAlign !== void 0) d["text-align"] = style.textAlign;
176
- if (style.letterSpacing) d["letter-spacing"] = lengthToCss(style.letterSpacing);
177
- if (style.border !== void 0) d["border"] = style.border;
178
- if (style.boxShadow !== void 0) d["box-shadow"] = style.boxShadow;
179
- if (style.transform !== void 0) d["transform"] = style.transform;
180
- if (style.filter !== void 0) d["filter"] = style.filter;
181
- if (style.gap) d["gap"] = lengthToCss(style.gap);
182
- if (style.align !== void 0) d["align-items"] = ALIGN[style.align] ?? style.align;
183
- if (style.justify !== void 0) d["justify-content"] = JUSTIFY[style.justify] ?? style.justify;
318
+ putLen(d, ["padding"], style.padding);
319
+ putLen(d, ["padding-left", "padding-right"], style.paddingX);
320
+ putLen(d, ["padding-top", "padding-bottom"], style.paddingY);
321
+ putLen(d, ["margin"], style.margin);
322
+ if (style.marginX === "auto") d["margin-left"] = d["margin-right"] = "auto";
323
+ else putLen(d, ["margin-left", "margin-right"], style.marginX);
324
+ putLen(d, ["margin-top", "margin-bottom"], style.marginY);
325
+ putLen(d, ["border-radius"], style.radius);
326
+ putLen(d, ["max-width"], style.maxWidth);
327
+ putLen(d, ["min-height"], style.minHeight);
328
+ put(d, "aspect-ratio", typeof style.aspectRatio === "string" ? style.aspectRatio : void 0);
329
+ if (style.sticky === true) {
330
+ d["position"] = "sticky";
331
+ d["top"] = "0";
332
+ }
333
+ if (typeof style.zIndex === "number" && Number.isFinite(style.zIndex)) d["z-index"] = String(Math.trunc(style.zIndex));
334
+ const overflow = inSet(OVERFLOW, style.overflow);
335
+ if (overflow) d["overflow"] = overflow;
336
+ put(d, "background", typeof style.background === "string" ? style.background : void 0);
337
+ const backgroundSize = inSet(BACKGROUND_SIZE, style.backgroundSize);
338
+ if (backgroundSize) d["background-size"] = backgroundSize;
339
+ put(d, "background-position", typeof style.backgroundPosition === "string" ? style.backgroundPosition : void 0);
340
+ put(d, "color", typeof style.color === "string" ? style.color : void 0);
341
+ const opacity = numberToCss(style.opacity);
342
+ if (opacity !== null) d["opacity"] = opacity;
343
+ put(d, "font-family", typeof style.fontFamily === "string" ? style.fontFamily : void 0);
344
+ putLen(d, ["font-size"], style.fontSize);
345
+ const fontWeight = numberToCss(style.fontWeight);
346
+ if (fontWeight !== null) d["font-weight"] = fontWeight;
347
+ const lineHeight = numberToCss(style.lineHeight);
348
+ if (lineHeight !== null) d["line-height"] = lineHeight;
349
+ const textAlign = inSet(TEXT_ALIGN, style.textAlign);
350
+ if (textAlign) d["text-align"] = textAlign;
351
+ putLen(d, ["letter-spacing"], style.letterSpacing);
352
+ const textTransform = inSet(TEXT_TRANSFORM, style.textTransform);
353
+ if (textTransform) d["text-transform"] = textTransform;
354
+ put(d, "border", typeof style.border === "string" ? style.border : void 0);
355
+ put(d, "box-shadow", typeof style.boxShadow === "string" ? style.boxShadow : void 0);
356
+ put(d, "transform", typeof style.transform === "string" ? style.transform : void 0);
357
+ put(d, "filter", typeof style.filter === "string" ? style.filter : void 0);
358
+ putLen(d, ["gap"], style.gap);
359
+ const align = keyword(ALIGN, style.align);
360
+ if (align) d["align-items"] = align;
361
+ const justify = keyword(JUSTIFY, style.justify);
362
+ if (justify) d["justify-content"] = justify;
184
363
  return d;
185
364
  }
186
- function layoutDecls(node) {
365
+ function shownDisplay(node) {
366
+ if (node.layout === "stack") return "flex";
367
+ if (node.layout === "grid") return "grid";
368
+ return "block";
369
+ }
370
+ function gridColumns(style) {
371
+ const min = lengthToCss(style.columnMin);
372
+ if (min !== null) return `repeat(auto-fit, minmax(min(${min}, 100%), 1fr))`;
373
+ if (typeof style.columns === "number" && Number.isFinite(style.columns)) return `repeat(${Math.max(1, Math.trunc(style.columns))}, 1fr)`;
374
+ return void 0;
375
+ }
376
+ function containerDecls(node, style, isBase) {
187
377
  const d = {};
188
378
  if (node.layout === "stack") {
189
- d["display"] = "flex";
190
- d["flex-direction"] = node.style.base.direction ?? "column";
379
+ if (isBase) d["display"] = "flex";
380
+ const direction = inSet(DIRECTIONS, style.direction);
381
+ if (isBase || direction) d["flex-direction"] = direction ?? "column";
382
+ if (style.wrap !== void 0) d["flex-wrap"] = style.wrap ? "wrap" : "nowrap";
191
383
  } else if (node.layout === "grid") {
192
- d["display"] = "grid";
193
- d["grid-template-columns"] = `repeat(${node.style.base.columns ?? 1}, 1fr)`;
194
- } else if (node.layout === "free") {
384
+ if (isBase) d["display"] = "grid";
385
+ const cols = gridColumns(style);
386
+ if (isBase || cols !== void 0) d["grid-template-columns"] = cols ?? "repeat(1, 1fr)";
387
+ } else if (node.layout === "free" && isBase) {
195
388
  d["position"] = "relative";
196
389
  }
197
390
  return d;
198
391
  }
392
+ function visibilityDecls(node, style, isBase) {
393
+ if (style.hidden === true) return { display: "none" };
394
+ if (style.hidden === false && !isBase) return { display: shownDisplay(node) };
395
+ return {};
396
+ }
397
+ function themeDecls(theme) {
398
+ const d = {};
399
+ const walk = (value, path) => {
400
+ if (typeof value === "string" || typeof value === "number" && Number.isFinite(value)) {
401
+ const key = path.map((p) => p.toLowerCase().replace(/[^a-z0-9-]/g, "")).filter(Boolean).join("-");
402
+ if (key) put(d, `--cms-${key}`, String(value));
403
+ return;
404
+ }
405
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
406
+ for (const [k, v] of Object.entries(value)) walk(v, [...path, k]);
407
+ }
408
+ };
409
+ if (typeof theme === "object" && theme !== null && !Array.isArray(theme)) {
410
+ for (const [k, v] of Object.entries(theme)) {
411
+ if (k !== "name") walk(v, [k]);
412
+ }
413
+ }
414
+ return d;
415
+ }
199
416
  function constraintDecls(c, parentFree) {
200
417
  const d = {};
201
418
  const transforms = [];
202
419
  if (parentFree) {
203
420
  d["position"] = "absolute";
204
- if (c.left) d["left"] = lengthToCss(c.left);
205
- if (c.right) d["right"] = lengthToCss(c.right);
206
- if (c.top) d["top"] = lengthToCss(c.top);
207
- if (c.bottom) d["bottom"] = lengthToCss(c.bottom);
421
+ putLen(d, ["left"], c.left);
422
+ putLen(d, ["right"], c.right);
423
+ putLen(d, ["top"], c.top);
424
+ putLen(d, ["bottom"], c.bottom);
208
425
  if (c.centerX) {
209
426
  d["left"] = "50%";
210
427
  transforms.push("translateX(-50%)");
@@ -215,13 +432,15 @@ function constraintDecls(c, parentFree) {
215
432
  }
216
433
  if (transforms.length) d["transform"] = transforms.join(" ");
217
434
  }
218
- if (c.width) d["width"] = sizeToCss(c.width);
219
- if (c.height) d["height"] = sizeToCss(c.height);
435
+ const width = sizeToCss(c.width);
436
+ if (width !== null) d["width"] = width;
437
+ const height = sizeToCss(c.height);
438
+ if (height !== null) d["height"] = height;
220
439
  return d;
221
440
  }
222
441
  function isParentFree(doc, node) {
223
442
  if (node.parent === null) return false;
224
- const parent = doc.nodes[node.parent];
443
+ const parent = own(doc.nodes, node.parent);
225
444
  return !parent || parent.layout === void 0 || parent.layout === "free";
226
445
  }
227
446
  function serializeRule(selector, decls, indent) {
@@ -233,16 +452,20 @@ ${body}
233
452
  ${indent}}`;
234
453
  }
235
454
  function emitDocCss(doc) {
455
+ if (!hasNodeMap(doc)) return "";
236
456
  const breakpointPx = DEFAULT_BREAKPOINTS;
237
457
  const blocks = [];
458
+ const themeRule = serializeRule(":root", themeDecls(doc.theme), "");
459
+ if (themeRule) blocks.push(themeRule);
238
460
  for (const id of Object.keys(doc.nodes).sort()) {
239
461
  const node = doc.nodes[id];
240
462
  const selector = selectorFor(id);
241
463
  const parentFree = isParentFree(doc, node);
242
464
  const base = {
243
- ...layoutDecls(node),
465
+ ...containerDecls(node, node.style.base, true),
244
466
  ...node.constraints ? constraintDecls(node.constraints.base, parentFree) : {},
245
- ...styleDecls(node.style.base)
467
+ ...styleDecls(node.style.base),
468
+ ...visibilityDecls(node, node.style.base, true)
246
469
  };
247
470
  const baseRule = serializeRule(selector, base, "");
248
471
  if (baseRule) blocks.push(baseRule);
@@ -250,9 +473,12 @@ function emitDocCss(doc) {
250
473
  if (bp === "base") continue;
251
474
  const px = breakpointPx[bp];
252
475
  if (px === void 0) continue;
476
+ const bpStyle = node.style[bp];
253
477
  const decls = {
478
+ ...bpStyle ? containerDecls(node, bpStyle, false) : {},
254
479
  ...node.constraints?.[bp] ? constraintDecls(node.constraints[bp], parentFree) : {},
255
- ...node.style[bp] ? styleDecls(node.style[bp]) : {}
480
+ ...bpStyle ? styleDecls(bpStyle) : {},
481
+ ...bpStyle ? visibilityDecls(node, bpStyle, false) : {}
256
482
  };
257
483
  const inner = serializeRule(selector, decls, " ");
258
484
  if (inner) blocks.push(`@media (min-width: ${px}px) {