@particle-academy/fancy-cms-ui 0.7.1 → 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-CUNaBqTs.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-CUNaBqTs.cjs';
3
- import { P as PageOp } from './useEditor-wdlYQMHg.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-wdlYQMHg.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-CUNaBqTs.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-CUNaBqTs.js';
3
- import { P as PageOp } from './useEditor-aPH5falI.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-aPH5falI.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-R7HDS4VG.js';
2
- export { ADD_DEFAULTS, ADD_MENU, CONTAINER_TYPES, Canvas, Editor, Inspector, LayersPanel, buildInsertOp, editorReduce, initEditor, resolveInsertParent, useEditor } from './chunk-SYRU2X45.js';
3
- import { isBinding } from './chunk-LY6ID2J2.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-LY6ID2J2.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,10 +1,10 @@
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
7
+ // src/react/RenderNode.tsx
8
8
 
9
9
  // src/document/types.ts
10
10
  function isBinding(v) {
@@ -31,6 +31,15 @@ function emptyDoc(id, slug = "/") {
31
31
  };
32
32
  }
33
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
+
34
43
  // src/document/reduce.ts
35
44
  function childrenOf(doc, parent) {
36
45
  return fancyDocCommons.childrenOf(doc, parent);
@@ -47,6 +56,48 @@ function resolveValue(v, data) {
47
56
  if (isBinding(v)) return getPath(data, v.$bind);
48
57
  return v;
49
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
+ }
50
101
  var defaultRegistry = {
51
102
  section: ({ children }) => children,
52
103
  stack: ({ children }) => children,
@@ -59,7 +110,12 @@ var defaultRegistry = {
59
110
  // Rich text — inline-formatted HTML (gradient spans, links, <code>, …) that
60
111
  // plain `text` can't express. The author owns the markup (it's their content).
61
112
  richtext: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: text(node.props.html) } }),
62
- 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
+ },
63
119
  divider: () => /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: "1px solid #e2e8f0", margin: 0 } }),
64
120
  callout: ({ node, text }) => {
65
121
  const tone = { info: "#3b82f6", success: "#10b981", warning: "#f59e0b", danger: "#ef4444" }[text(node.props.variant)] ?? "#3b82f6";
@@ -71,15 +127,20 @@ var defaultRegistry = {
71
127
  const variant = text(node.props.variant) || "primary";
72
128
  return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-cms-button": variant, style: buttonStyle(variant), children: label });
73
129
  },
74
- image: ({ node, text }) => /* @__PURE__ */ jsxRuntime.jsx(
75
- "img",
76
- {
77
- src: text(node.props.src),
78
- alt: text(node.props.alt),
79
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
80
- }
81
- )
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
+ }
82
142
  };
143
+ var IMAGE_FITS = /* @__PURE__ */ new Set(["cover", "contain", "fill", "none", "scale-down"]);
83
144
  function buttonStyle(variant) {
84
145
  const base = {
85
146
  display: "inline-flex",
@@ -98,7 +159,7 @@ function buttonStyle(variant) {
98
159
  return { ...base, background: "#7c3aed", color: "#fff", border: "1px solid #7c3aed" };
99
160
  }
100
161
  function RenderNode({ doc, id, registry = defaultRegistry, data }) {
101
- const node = doc.nodes[id];
162
+ const node = own(doc.nodes, id);
102
163
  if (!node) return null;
103
164
  if (node.type === "repeater") {
104
165
  const arr = resolveValue(node.props.items, data);
@@ -114,7 +175,19 @@ function RenderNode({ doc, id, registry = defaultRegistry, data }) {
114
175
  };
115
176
  const renderer = registry[node.type];
116
177
  const inner = renderer ? renderer({ node, doc, children: kids, data, resolve, text }) : node.island ? null : kids;
117
- 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
+ );
118
191
  }
119
192
  function normalizeDoc(input) {
120
193
  if (hasNodeMap(input)) return input;
@@ -132,7 +205,7 @@ function migrateDocVerbose(input) {
132
205
  const ordered = [];
133
206
  const seen = /* @__PURE__ */ new Set();
134
207
  for (const id of sections) {
135
- if (!normalized.nodes[id]) {
208
+ if (!own(normalized.nodes, id)) {
136
209
  danglingIds.push(id);
137
210
  continue;
138
211
  }
@@ -146,7 +219,7 @@ function migrateDocVerbose(input) {
146
219
  let prev = null;
147
220
  for (const id of finalOrder) {
148
221
  const key = fancyDocCommons.fractionalKey(prev, null);
149
- nodes[id] = { ...nodes[id], order: key };
222
+ setOwn(nodes, id, { ...own(nodes, id), order: key });
150
223
  prev = key;
151
224
  }
152
225
  const { sections: _dropped, ...rest } = legacy;
@@ -162,15 +235,36 @@ function migrateDoc(input) {
162
235
  }
163
236
 
164
237
  // src/render/css.ts
165
- 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"]);
166
250
  function lengthToCss(len) {
167
- 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}`;
168
255
  }
169
256
  function sizeToCss(size) {
170
257
  if (size === "fill") return "100%";
171
258
  if (size === "hug") return "auto";
172
259
  return lengthToCss(size);
173
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
+ }
174
268
  var ALIGN = {
175
269
  start: "flex-start",
176
270
  center: "center",
@@ -184,51 +278,150 @@ var JUSTIFY = {
184
278
  between: "space-between",
185
279
  around: "space-around"
186
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
+ }
187
316
  function styleDecls(style) {
188
317
  const d = {};
189
- if (style.padding) d["padding"] = lengthToCss(style.padding);
190
- if (style.margin) d["margin"] = lengthToCss(style.margin);
191
- if (style.radius) d["border-radius"] = lengthToCss(style.radius);
192
- if (style.background !== void 0) d["background"] = style.background;
193
- if (style.color !== void 0) d["color"] = style.color;
194
- if (style.opacity !== void 0) d["opacity"] = String(style.opacity);
195
- if (style.fontFamily !== void 0) d["font-family"] = style.fontFamily;
196
- if (style.fontSize) d["font-size"] = lengthToCss(style.fontSize);
197
- if (style.fontWeight !== void 0) d["font-weight"] = String(style.fontWeight);
198
- if (style.lineHeight !== void 0) d["line-height"] = String(style.lineHeight);
199
- if (style.textAlign !== void 0) d["text-align"] = style.textAlign;
200
- if (style.letterSpacing) d["letter-spacing"] = lengthToCss(style.letterSpacing);
201
- if (style.border !== void 0) d["border"] = style.border;
202
- if (style.boxShadow !== void 0) d["box-shadow"] = style.boxShadow;
203
- if (style.transform !== void 0) d["transform"] = style.transform;
204
- if (style.filter !== void 0) d["filter"] = style.filter;
205
- if (style.gap) d["gap"] = lengthToCss(style.gap);
206
- if (style.align !== void 0) d["align-items"] = ALIGN[style.align] ?? style.align;
207
- 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;
208
363
  return d;
209
364
  }
210
- 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) {
211
377
  const d = {};
212
378
  if (node.layout === "stack") {
213
- d["display"] = "flex";
214
- 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";
215
383
  } else if (node.layout === "grid") {
216
- d["display"] = "grid";
217
- d["grid-template-columns"] = `repeat(${node.style.base.columns ?? 1}, 1fr)`;
218
- } 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) {
219
388
  d["position"] = "relative";
220
389
  }
221
390
  return d;
222
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
+ }
223
416
  function constraintDecls(c, parentFree) {
224
417
  const d = {};
225
418
  const transforms = [];
226
419
  if (parentFree) {
227
420
  d["position"] = "absolute";
228
- if (c.left) d["left"] = lengthToCss(c.left);
229
- if (c.right) d["right"] = lengthToCss(c.right);
230
- if (c.top) d["top"] = lengthToCss(c.top);
231
- 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);
232
425
  if (c.centerX) {
233
426
  d["left"] = "50%";
234
427
  transforms.push("translateX(-50%)");
@@ -239,13 +432,15 @@ function constraintDecls(c, parentFree) {
239
432
  }
240
433
  if (transforms.length) d["transform"] = transforms.join(" ");
241
434
  }
242
- if (c.width) d["width"] = sizeToCss(c.width);
243
- 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;
244
439
  return d;
245
440
  }
246
441
  function isParentFree(doc, node) {
247
442
  if (node.parent === null) return false;
248
- const parent = doc.nodes[node.parent];
443
+ const parent = own(doc.nodes, node.parent);
249
444
  return !parent || parent.layout === void 0 || parent.layout === "free";
250
445
  }
251
446
  function serializeRule(selector, decls, indent) {
@@ -260,14 +455,17 @@ function emitDocCss(doc) {
260
455
  if (!hasNodeMap(doc)) return "";
261
456
  const breakpointPx = DEFAULT_BREAKPOINTS;
262
457
  const blocks = [];
458
+ const themeRule = serializeRule(":root", themeDecls(doc.theme), "");
459
+ if (themeRule) blocks.push(themeRule);
263
460
  for (const id of Object.keys(doc.nodes).sort()) {
264
461
  const node = doc.nodes[id];
265
462
  const selector = selectorFor(id);
266
463
  const parentFree = isParentFree(doc, node);
267
464
  const base = {
268
- ...layoutDecls(node),
465
+ ...containerDecls(node, node.style.base, true),
269
466
  ...node.constraints ? constraintDecls(node.constraints.base, parentFree) : {},
270
- ...styleDecls(node.style.base)
467
+ ...styleDecls(node.style.base),
468
+ ...visibilityDecls(node, node.style.base, true)
271
469
  };
272
470
  const baseRule = serializeRule(selector, base, "");
273
471
  if (baseRule) blocks.push(baseRule);
@@ -275,9 +473,12 @@ function emitDocCss(doc) {
275
473
  if (bp === "base") continue;
276
474
  const px = breakpointPx[bp];
277
475
  if (px === void 0) continue;
476
+ const bpStyle = node.style[bp];
278
477
  const decls = {
478
+ ...bpStyle ? containerDecls(node, bpStyle, false) : {},
279
479
  ...node.constraints?.[bp] ? constraintDecls(node.constraints[bp], parentFree) : {},
280
- ...node.style[bp] ? styleDecls(node.style[bp]) : {}
480
+ ...bpStyle ? styleDecls(bpStyle) : {},
481
+ ...bpStyle ? visibilityDecls(node, bpStyle, false) : {}
281
482
  };
282
483
  const inner = serializeRule(selector, decls, " ");
283
484
  if (inner) blocks.push(`@media (min-width: ${px}px) {