@unlayer/react-elements 0.1.21 → 0.2.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.cjs CHANGED
@@ -1,16 +1,375 @@
1
1
  'use strict';
2
2
 
3
3
  var exporters = require('@unlayer/exporters');
4
+ var React4 = require('react');
5
+ var defaultBuild = require('react-dom/server');
4
6
  var jsxRuntime = require('react/jsx-runtime');
5
- var React = require('react');
6
- var ReactDOMServer = require('react-dom/server');
7
7
 
8
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
 
10
- var React__default = /*#__PURE__*/_interopDefault(React);
11
- var ReactDOMServer__default = /*#__PURE__*/_interopDefault(ReactDOMServer);
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
29
+ var defaultBuild__namespace = /*#__PURE__*/_interopNamespace(defaultBuild);
12
30
 
13
31
  // src/components/Button.tsx
32
+ var SHARED = Symbol.for("@unlayer/react-elements/collecting");
33
+ var frames = globalThis[SHARED] ?? (globalThis[SHARED] = []);
34
+ function collecting() {
35
+ return frames[frames.length - 1] ?? void 0;
36
+ }
37
+ function within(frame, run) {
38
+ frames.push(frame);
39
+ try {
40
+ return run();
41
+ } finally {
42
+ frames.pop();
43
+ }
44
+ }
45
+ var MARK = "unlayer-mark";
46
+ var RECORDS = Symbol.for("@unlayer/react-elements/records");
47
+ var COLLECTING_ID = "unlayer-collect-";
48
+ function marker(collection, at, edge) {
49
+ return React4__default.default.createElement(MARK, { key: `${at}${edge ?? ""}`, "data-n": collection.nonce, "data-i": at, ...edge ? { "data-e": edge } : {} });
50
+ }
51
+ var SHARED2 = Symbol.for("@unlayer/react-elements/render-builds");
52
+ var using = globalThis[SHARED2] ?? (globalThis[SHARED2] = []);
53
+ var same = (a, b) => a.renderToString === b.renderToString;
54
+ function dispatcher() {
55
+ const internals = React4__default.default;
56
+ return internals.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE?.H ?? internals.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentDispatcher?.current ?? null;
57
+ }
58
+ var dispatchers = /* @__PURE__ */ new Map();
59
+ function dispatcherOf(build) {
60
+ if (!dispatchers.has(build)) {
61
+ let seen = null;
62
+ try {
63
+ class Probe extends React4__default.default.Component {
64
+ render() {
65
+ seen = dispatcher();
66
+ return null;
67
+ }
68
+ }
69
+ build.renderToStaticMarkup(React4__default.default.createElement(Probe));
70
+ } catch {
71
+ }
72
+ dispatchers.set(build, seen);
73
+ }
74
+ return dispatchers.get(build);
75
+ }
76
+ var second;
77
+ function secondBuild(outer) {
78
+ if (second === void 0) {
79
+ second = null;
80
+ const node = globalThis.process;
81
+ const requirers = [
82
+ // (`module`'s, read so that a bundler doesn't take it for a require of a path worked out when it runs.)
83
+ () => {
84
+ const own = typeof module === "object" ? Reflect.get(module ?? {}, "require") : void 0;
85
+ return typeof own === "function" ? (id) => own.call(module, id) : void 0;
86
+ },
87
+ // From this module's URL (as a stack names it: webpack 4 can't parse the syntax for it), the app's script, then
88
+ // the folder Node started in; only where `react` is this React. Another React's isn't loaded (React 19's browser
89
+ // build keeps Node running).
90
+ ...[/file:\/\/\S+?(?=:\d+:\d+)/.exec(String(new Error().stack))?.[0], node?.argv?.[1], `${node?.cwd?.()}/`].map((from) => () => {
91
+ const required = node?.getBuiltinModule?.("module")?.createRequire(from);
92
+ return required?.("react")?.createElement === React4__default.default.createElement ? required : void 0;
93
+ })
94
+ ];
95
+ const ids = ["react-dom/server.bun", ...Number.parseInt(React4__default.default.version) >= 19 ? [] : ["react-dom/server.browser"], "react-dom/server.node"];
96
+ search: for (const id of ids) {
97
+ for (const requirer of requirers) {
98
+ try {
99
+ const build = requirer()?.(id);
100
+ if (build?.renderToStaticMarkup && !same(build, defaultBuild__namespace) && ![null, outer].includes(dispatcherOf(build))) {
101
+ second = build;
102
+ break search;
103
+ }
104
+ } catch {
105
+ }
106
+ }
107
+ }
108
+ }
109
+ return second ?? void 0;
110
+ }
111
+ var buildOf = (outer) => outer === null ? void 0 : [defaultBuild__namespace, second].find((b) => !!b && dispatcherOf(b) === outer);
112
+ function withBuild(render, own = false) {
113
+ const outer = own ? null : dispatcher();
114
+ let build = current();
115
+ const theirs = !own && (using.length === 0 || collecting() !== void 0) ? buildOf(outer) : void 0;
116
+ const busy = [];
117
+ if (theirs) {
118
+ const free = (b) => !same(b, theirs) && !using.some((u) => same(u, b));
119
+ const other = free(defaultBuild__namespace) ? defaultBuild__namespace : secondBuild(outer);
120
+ if (other && free(other)) {
121
+ busy.push(theirs);
122
+ build = other;
123
+ } else build = theirs;
124
+ }
125
+ using.push(...busy, build);
126
+ try {
127
+ return render();
128
+ } catch (error) {
129
+ if (build !== theirs && !rendersHere(build)) throw Object.assign(new Error("[Unlayer] react-dom can't render with this React (React Server Components', as in Next.js's edge runtime): render emails in the Node.js runtime."), { cause: error });
130
+ throw error;
131
+ } finally {
132
+ using.length -= busy.length + 1;
133
+ }
134
+ }
135
+ function rendersHere(build) {
136
+ try {
137
+ build.renderToStaticMarkup(React4__default.default.createElement(() => null));
138
+ return true;
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+ function current() {
144
+ return using[using.length - 1] ?? defaultBuild__namespace;
145
+ }
146
+ var renderToStaticMarkup = (element2, options) => current().renderToStaticMarkup(element2, options);
147
+ var renderToString = (element2, options) => current().renderToString(element2, options);
148
+
149
+ // src/utils/unwrap-root.ts
150
+ var ROOT_NAMES = /* @__PURE__ */ new Set(["Body", "Email", "Page", "Document"]);
151
+ var CONTAINER_NAMES = /* @__PURE__ */ new Set([...ROOT_NAMES, "Row", "Column"]);
152
+ var MEMO = Symbol.for("react.memo");
153
+ var FORWARD_REF = Symbol.for("react.forward_ref");
154
+ var PROVIDER = Symbol.for("react.provider");
155
+ var CONTEXT = Symbol.for("react.context");
156
+ function getDisplayName(element2) {
157
+ return element2.type?.displayName;
158
+ }
159
+ function componentName(element2) {
160
+ const type = element2.type;
161
+ return type?.displayName || type?.name;
162
+ }
163
+ function isElementsType(type) {
164
+ return !!type?.[UNLAYER_RENDER_KEY] || CONTAINER_NAMES.has(type?.displayName);
165
+ }
166
+ function isElementsComponent(element2) {
167
+ return isElementsType(element2.type);
168
+ }
169
+ var ASYNC_TEMPLATE = "async and suspending templates aren't supported: load the data first and pass it as props";
170
+ var isThenable = (value) => typeof value?.then === "function";
171
+ var settle = (value) => {
172
+ if (isThenable(value)) value.then(void 0, () => {
173
+ });
174
+ };
175
+ function isAsyncComponent(type) {
176
+ while (type?.$$typeof === MEMO) type = type.type;
177
+ return (type?.$$typeof === FORWARD_REF ? type.render : type)?.[Symbol.toStringTag] === "AsyncFunction";
178
+ }
179
+ function callIsolated(call, prefix, providers = []) {
180
+ let result;
181
+ let error;
182
+ let ran = false;
183
+ let threw = false;
184
+ let calling = false;
185
+ const Probe = () => {
186
+ if (calling) return null;
187
+ ran = calling = true;
188
+ threw = false;
189
+ try {
190
+ result = call();
191
+ } catch (cause) {
192
+ threw = true;
193
+ error = cause;
194
+ } finally {
195
+ calling = false;
196
+ }
197
+ return null;
198
+ };
199
+ const probe = providers.reduceRight((inside, provider) => React4__default.default.cloneElement(provider, void 0, inside), React4__default.default.createElement(Probe));
200
+ renderToStaticMarkup(probe, prefix ? { identifierPrefix: prefix } : void 0);
201
+ settle(error);
202
+ settle(result);
203
+ if (threw) throw isThenable(error) ? new Error(ASYNC_TEMPLATE) : error;
204
+ if (!ran) throw new Error("the template wasn't rendered");
205
+ if (isThenable(result)) throw new Error(ASYNC_TEMPLATE);
206
+ return result;
207
+ }
208
+ function unwrapRoot(element2, why = {}) {
209
+ let current2 = element2;
210
+ const providers = [];
211
+ for (let depth = 0; ; depth++) {
212
+ if (ROOT_NAMES.has(getDisplayName(current2) ?? "")) break;
213
+ if (elementsOwn(current2)) break;
214
+ if (depth === 20) {
215
+ why.unread = "it wraps more than 20 components";
216
+ break;
217
+ }
218
+ const type = current2.type;
219
+ const props = current2.props;
220
+ if (type?.$$typeof === MEMO) {
221
+ current2 = React4__default.default.createElement(type.type, props);
222
+ continue;
223
+ }
224
+ if ((type?.$$typeof === PROVIDER || type?.$$typeof === CONTEXT) && "value" in props) {
225
+ const inside = React4__default.default.Children.toArray(props.children);
226
+ if (inside.length !== 1 || !React4__default.default.isValidElement(inside[0])) break;
227
+ providers.push(current2);
228
+ current2 = inside[0];
229
+ continue;
230
+ }
231
+ if (type === React4__default.default.Fragment || type === React4__default.default.StrictMode || type === React4__default.default.Profiler) {
232
+ const inside = React4__default.default.Children.toArray(props.children);
233
+ if (inside.length !== 1 || !React4__default.default.isValidElement(inside[0])) break;
234
+ current2 = inside[0];
235
+ continue;
236
+ }
237
+ let produced;
238
+ const prefix = depth ? `w${depth}-` : void 0;
239
+ if (type?.$$typeof === FORWARD_REF) produced = callIsolated(() => type.render({ ...props }, null), prefix, providers);
240
+ else if (typeof type === "function" && !type.prototype?.isReactComponent) produced = callIsolated(() => type({ ...props }), prefix, providers);
241
+ else {
242
+ if (type?.prototype?.isReactComponent) why.unread = `<${componentName(current2)}> is a class component, which isn't called to find it`;
243
+ break;
244
+ }
245
+ if (!React4__default.default.isValidElement(produced)) {
246
+ if (holdsRoot(produced)) why.unread = "it doesn't return a root element";
247
+ break;
248
+ }
249
+ current2 = produced;
250
+ }
251
+ if (!providers.length || !ROOT_NAMES.has(getDisplayName(current2) ?? "")) return current2;
252
+ const children = providers.reduceRight((inside, provider) => React4__default.default.cloneElement(provider, void 0, inside), current2.props.children);
253
+ return React4__default.default.cloneElement(current2, void 0, children);
254
+ }
255
+ var elementsOwn = (element2) => {
256
+ const type = element2.type;
257
+ return !!(type?.[UNLAYER_RENDER_KEY] || type?.[RECORDS]);
258
+ };
259
+ function holdsRoot(node) {
260
+ if (Array.isArray(node)) return node.some(holdsRoot);
261
+ if (!React4__default.default.isValidElement(node)) return false;
262
+ return ROOT_NAMES.has(getDisplayName(node) ?? "") || !elementsOwn(node) && holdsRoot(node.props.children);
263
+ }
264
+ var UNWRAP_ADVICE = "Pass the root element (<Email>\u2026</Email>), or a function component that returns one.";
265
+ function htmlRoot(element2, api) {
266
+ if (typeof element2.type === "string" || isElementsComponent(element2)) return element2;
267
+ let reason;
268
+ try {
269
+ const why = {};
270
+ const root = unwrapRoot(element2, why);
271
+ if (ROOT_NAMES.has(getDisplayName(root) ?? "")) return root;
272
+ if (!why.unread && !holdsRoot(root)) return element2;
273
+ reason = why.unread ?? "its root is inside other markup";
274
+ } catch (cause) {
275
+ if (cause instanceof Error && cause.message === ASYNC_TEMPLATE) throw new Error(`[Unlayer] ${api}: ${ASYNC_TEMPLATE}.`);
276
+ reason = cause instanceof Error ? cause.message : String(cause);
277
+ }
278
+ console.warn(`[Unlayer] ${api}: couldn't unwrap <${componentName(element2) || "wrapper"}> (${reason}): its root's fonts, lang, dir and phone styles are ignored. ${UNWRAP_ADVICE}`);
279
+ return element2;
280
+ }
281
+ function guard(children, collection, inContainer = false) {
282
+ return React4__default.default.Children.toArray(children).map((child) => {
283
+ const placed = guarded(child, collection, inContainer);
284
+ return placed === child && React4__default.default.isValidElement(child) ? React4__default.default.cloneElement(child) : placed;
285
+ });
286
+ }
287
+ function guarded(child, collection, inContainer) {
288
+ if (!React4__default.default.isValidElement(child)) return child;
289
+ if (isElementsType(child.type)) {
290
+ if (child.type[RECORDS]) return child;
291
+ if (!olderContainer(child.type)) return bounded(child, collection);
292
+ return recordedForOlder(child, collection, inContainer);
293
+ }
294
+ if (!child.type || typeof child.type === "object" && !("$$typeof" in child.type)) return child;
295
+ if (typeof child.type === "string") {
296
+ const inside = child.props.children;
297
+ const element2 = inside !== void 0 && hasComponent(inside) ? React4__default.default.cloneElement(child, void 0, guard(inside, collection, inContainer)) : child;
298
+ return collection.markup ? bounded(element2, collection) : element2;
299
+ }
300
+ if (child.type === React4__default.default.Fragment) {
301
+ return React4__default.default.createElement(React4__default.default.Fragment, { key: child.key }, guard(child.props.children, collection, inContainer));
302
+ }
303
+ return bounded(child, collection);
304
+ }
305
+ function bounded(child, collection) {
306
+ const at = collection.recorded.push({ kind: "boundary", element: child }) - 1;
307
+ if (isAsyncComponent(child.type)) return marker(collection, at, "f");
308
+ return React4__default.default.createElement(
309
+ React4.Suspense,
310
+ { key: child.key ?? `b${at}`, fallback: marker(collection, at, "f") },
311
+ marker(collection, at, "o"),
312
+ child,
313
+ marker(collection, at, "c")
314
+ );
315
+ }
316
+ function olderContainer(type) {
317
+ return !/^(Row|Column)$/.test(type?.displayName ?? "") || /\b_config\b/.test(String(type));
318
+ }
319
+ function recordedForOlder(child, collection, inContainer) {
320
+ const type = child.type;
321
+ const props = child.props;
322
+ if (type.displayName === "Row" || type.displayName === "Column") {
323
+ const at2 = collection.recorded.push({ kind: type.displayName === "Row" ? "row" : "column", props }) - 1;
324
+ return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
325
+ marker(collection, at2, "o"),
326
+ guard(props.children, collection, true),
327
+ marker(collection, at2, "c")
328
+ ] }, child.key ?? `r${at2}`);
329
+ }
330
+ if (!type.__unlayerRender && !inContainer) return child;
331
+ if (!type.__unlayerRender) {
332
+ console.warn(`[Unlayer] <${type.displayName}> is a root: it can't be placed in a Row or Column, so it's left out.`);
333
+ return null;
334
+ }
335
+ const at = collection.recorded.push({ kind: "block", type: child.type, props }) - 1;
336
+ return React4__default.default.cloneElement(marker(collection, at), { key: child.key ?? `b${at}` });
337
+ }
338
+ function hasComponent(children) {
339
+ return React4__default.default.Children.toArray(children).some(
340
+ (child) => React4__default.default.isValidElement(child) && (typeof child.type === "string" || child.type === React4__default.default.Fragment ? hasComponent(child.props.children) : !isElementsType(child.type))
341
+ );
342
+ }
343
+ function Sentinel({ record, real }) {
344
+ let collected2 = !!collecting()?.blind;
345
+ if (!collected2) {
346
+ try {
347
+ collected2 = React4__default.default.useId().slice(1).startsWith(COLLECTING_ID);
348
+ } catch {
349
+ }
350
+ }
351
+ return collected2 ? record() : within(null, real);
352
+ }
353
+ var named = /* @__PURE__ */ new Map();
354
+ function whileCollecting(record, real, name) {
355
+ let type = named.get(name);
356
+ if (!type) named.set(name, type = Object.assign((props) => Sentinel(props), { displayName: name }));
357
+ return React4__default.default.createElement(type, { record, real });
358
+ }
359
+ function recordContainer(kind, props) {
360
+ const collection = collecting();
361
+ const at = collection.recorded.push({ kind, props }) - 1;
362
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
363
+ marker(collection, at, "o"),
364
+ guard(props.children, collection, true),
365
+ marker(collection, at, "c")
366
+ ] });
367
+ }
368
+ function recordBlock(type, props) {
369
+ const collection = collecting();
370
+ const at = collection.recorded.push({ kind: "block", type, props }) - 1;
371
+ return marker(collection, at);
372
+ }
14
373
 
15
374
  // src/utils/image-sizing.ts
16
375
  function toPx(value) {
@@ -22,11 +381,11 @@ function toPx(value) {
22
381
  function edges(value) {
23
382
  if (value == null) return { left: 0, right: 0 };
24
383
  if (typeof value === "number") return { left: value, right: value };
25
- const parts = String(value).trim().split(/\s+/).map((p) => parseFloat(p) || 0);
26
- if (parts.length === 1) return { left: parts[0], right: parts[0] };
27
- if (parts.length === 2 || parts.length === 3)
28
- return { left: parts[1], right: parts[1] };
29
- return { left: parts[3] || 0, right: parts[1] || 0 };
384
+ const parts2 = String(value).trim().split(/\s+/).map((p) => parseFloat(p) || 0);
385
+ if (parts2.length === 1) return { left: parts2[0], right: parts2[0] };
386
+ if (parts2.length === 2 || parts2.length === 3)
387
+ return { left: parts2[1], right: parts2[1] };
388
+ return { left: parts2[3] || 0, right: parts2[1] || 0 };
30
389
  }
31
390
  function borderEdges(border) {
32
391
  if (!border || typeof border !== "object") return { left: 0, right: 0 };
@@ -216,7 +575,7 @@ function paragraphAlign(format) {
216
575
  }
217
576
  return void 0;
218
577
  }
219
- function paragraphNodeToHtml(node, childrenHtml) {
578
+ function paragraphNodeToHtml(node, childrenHtml22) {
220
579
  const isInlineTool = !!node.isInlineTool;
221
580
  const tag = isInlineTool ? "span" : "p";
222
581
  const dir = dirAttribute(node.direction);
@@ -240,7 +599,7 @@ function paragraphNodeToHtml(node, childrenHtml) {
240
599
  styleParts.push(`text-align: ${align}`);
241
600
  }
242
601
  const style = styleParts.length > 0 ? ` style="${escapeHtml(styleParts.join("; ") + ";")}"` : "";
243
- return `<${tag}${dir}${style}>${childrenHtml || "<br>"}</${tag}>`;
602
+ return `<${tag}${dir}${style}>${childrenHtml22 || "<br>"}</${tag}>`;
244
603
  }
245
604
  function nodeToHtml(node) {
246
605
  if (!node || typeof node !== "object") return "";
@@ -251,32 +610,32 @@ function nodeToHtml(node) {
251
610
  if (type === "linebreak") {
252
611
  return "<br>";
253
612
  }
254
- const childrenHtml = (node.children || []).map(nodeToHtml).join("");
613
+ const childrenHtml22 = (node.children || []).map(nodeToHtml).join("");
255
614
  switch (type) {
256
615
  case "root":
257
- return childrenHtml;
616
+ return childrenHtml22;
258
617
  case "paragraph":
259
618
  case "extended-paragraph":
260
- return paragraphNodeToHtml(node, childrenHtml);
619
+ return paragraphNodeToHtml(node, childrenHtml22);
261
620
  case "heading": {
262
621
  const tag = /^h[1-6]$/.test(node.tag) ? node.tag : "h1";
263
622
  const dir = dirAttribute(node.direction);
264
- return `<${tag}${dir}>${childrenHtml}</${tag}>`;
623
+ return `<${tag}${dir}>${childrenHtml22}</${tag}>`;
265
624
  }
266
625
  case "link": {
267
626
  const href = node.url ? ` href="${escapeHtml(node.url)}"` : "";
268
627
  const target = node.target ? ` target="${escapeHtml(node.target)}"` : "";
269
628
  const rel = node.rel ? ` rel="${escapeHtml(node.rel)}"` : "";
270
- return `<a${href}${target}${rel}>${childrenHtml}</a>`;
629
+ return `<a${href}${target}${rel}>${childrenHtml22}</a>`;
271
630
  }
272
631
  case "list": {
273
632
  const tag = node.listType === "number" ? "ol" : "ul";
274
- return `<${tag}>${childrenHtml}</${tag}>`;
633
+ return `<${tag}>${childrenHtml22}</${tag}>`;
275
634
  }
276
635
  case "listitem":
277
- return `<li>${childrenHtml}</li>`;
636
+ return `<li>${childrenHtml22}</li>`;
278
637
  default:
279
- return childrenHtml;
638
+ return childrenHtml22;
280
639
  }
281
640
  }
282
641
  function generateHtmlFromTextJson(textJson) {
@@ -307,6 +666,57 @@ function mergeValues(defaults, userValues) {
307
666
  }
308
667
  return result;
309
668
  }
669
+ var CHANNEL = /^-?(?:\d+(?:\.\d*)?|\.\d+)%?$/;
670
+ function normalizeColor(value) {
671
+ const text = value.trim();
672
+ const open = text.indexOf("(");
673
+ if (!/^rgba?$/i.test(text.slice(0, open)) || !text.endsWith(")")) return value;
674
+ const [rgb, slashed, extra] = text.slice(open + 1, -1).split("/");
675
+ const channels = rgb.trim().split(/\s*,\s*|\s+/);
676
+ if (slashed !== void 0) channels.push(slashed.trim());
677
+ if (extra !== void 0 || channels.length > 4 || channels.length < (slashed === void 0 ? 3 : 4) || !channels.every((c) => CHANNEL.test(c))) return value;
678
+ const num = (text2, full) => text2.endsWith("%") ? parseFloat(text2) / 100 * full : parseFloat(text2);
679
+ const [r, g, b] = channels.slice(0, 3).map((text2) => Math.max(0, Math.min(255, Math.round(num(text2, 255)))));
680
+ const alpha = channels[3] === void 0 ? 1 : num(channels[3], 1);
681
+ if (alpha < 1) return `rgba(${r}, ${g}, ${b}, ${Math.max(0, alpha)})`;
682
+ return `#${[r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("")}`;
683
+ }
684
+ function cssValueSafe(text) {
685
+ if (/[<>{};]|\/\*|\*\//.test(text)) return false;
686
+ const open = [];
687
+ for (const c of text) {
688
+ if (c === "(" || c === "[") open.push(c === "(" ? ")" : "]");
689
+ else if ((c === ")" || c === "]") && open.pop() !== c) return false;
690
+ }
691
+ return !open.length;
692
+ }
693
+ function cssSafeValues(value) {
694
+ if (typeof value === "string") return cssValueSafe(value) ? value : void 0;
695
+ if (Array.isArray(value)) return value.map(cssSafeValues);
696
+ if (!isPlainObject(value)) return value;
697
+ const out = {};
698
+ for (const [k, v] of Object.entries(value)) {
699
+ const safe = cssSafeValues(v);
700
+ if (safe !== void 0 || v === void 0) out[k] = safe;
701
+ }
702
+ return out;
703
+ }
704
+ function normalizeFontStack(value) {
705
+ return value.replace(/"([^"]*)"?/g, (_, name) => `'${name.replace(/'/g, "\\'")}'`);
706
+ }
707
+ function normalizeCssValues(value, key) {
708
+ if (typeof value === "string") return key === "fontFamily" ? normalizeFontStack(value) : key === "color" || key?.endsWith("Color") ? normalizeColor(value) : value;
709
+ if (Array.isArray(value)) return value.map((item) => normalizeCssValues(item));
710
+ if (!isPlainObject(value)) return value;
711
+ const out = {};
712
+ for (const [k, v] of Object.entries(value)) out[k] = normalizeCssValues(v, key === "fontFamily" ? key : k);
713
+ return out;
714
+ }
715
+ function isPlainObject(value) {
716
+ if (value === null || typeof value !== "object") return false;
717
+ const proto = Object.getPrototypeOf(value);
718
+ return proto === Object.prototype || proto === null;
719
+ }
310
720
  var nestedStructureCache = /* @__PURE__ */ new WeakMap();
311
721
  function analyzeNestedStructure(defaultValues) {
312
722
  const cached = nestedStructureCache.get(defaultValues);
@@ -351,24 +761,39 @@ function normalizeCssProps(props) {
351
761
  }
352
762
  }
353
763
  }
354
- function flattenChildrenText(node) {
764
+ var ENTITIES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
765
+ function escapeText(text, quotes) {
766
+ return text.replace(quotes ? /[&<>"']/g : /[&<>]/g, (c) => ENTITIES[c]);
767
+ }
768
+ function escapeKeepingTags(text, quotes, escape = (part) => escapeText(part, quotes)) {
769
+ return text.split(/(<%[\w.]+%>)/).map((part, i) => i % 2 ? part : escape(part)).join("");
770
+ }
771
+ var menuText = (text) => escapeKeepingTags(text, false, (part) => part.replace(/&(?!(?:#\d+|#x[\da-f]+|[a-z][\da-z]*);)|[<>"]/gi, (c) => ENTITIES[c]));
772
+ function flattenChildrenText(node, breaks) {
355
773
  if (node == null || typeof node === "boolean") return "";
356
774
  if (typeof node === "string") return node;
357
775
  if (typeof node === "number") return String(node);
358
- if (Array.isArray(node)) return node.map(flattenChildrenText).join("");
776
+ if (Array.isArray(node)) return node.map((child) => flattenChildrenText(child, breaks)).join("");
359
777
  if (typeof node === "object" && "props" in node) {
360
- return flattenChildrenText(node.props?.children);
778
+ return breaks && node.type === "br" ? "<br/>" : flattenChildrenText(node.props?.children, breaks);
361
779
  }
362
780
  return "";
363
781
  }
782
+ function childrenHtml(children) {
783
+ return flattenChildrenText(children, true).split(/(<br\s*\/?>)/i).map((part, i) => i % 2 ? "<br/>" : escapeKeepingTags(part)).join("");
784
+ }
364
785
  function mapSemanticProps(props, defaultValues, componentType) {
365
- const { children, values, ...restProps } = props;
786
+ const { children, values, mobile, hideOnMobile: hideMobileProp, hideOnDesktop: hideDesktopProp, ...restProps } = props;
787
+ const hideOnMobile = componentType === "Column" ? void 0 : hideMobileProp;
788
+ const hideOnDesktop = componentType === "Column" ? void 0 : hideDesktopProp;
366
789
  const userProps = { ...restProps };
367
790
  const result = values ? { ...values } : {};
368
791
  if (children !== void 0 && !result.text && !result.textJson) {
369
792
  const textContent = typeof children === "string" ? children : flattenChildrenText(children);
370
793
  if (componentType === "Paragraph") {
371
794
  result.textJson = textToTextJson(textContent);
795
+ } else if (componentType === "Heading" || componentType === "Button") {
796
+ result.text = childrenHtml(children);
372
797
  } else {
373
798
  result.text = textContent;
374
799
  }
@@ -446,15 +871,15 @@ function mapSemanticProps(props, defaultValues, componentType) {
446
871
  }
447
872
  if (defaultValues && typeof defaultValues === "object" && "border" in defaultValues) {
448
873
  const borderSideRe = /^border(Top|Right|Bottom|Left)(Width|Style|Color)$/;
449
- const collected = {};
874
+ const collected2 = {};
450
875
  for (const key of Object.keys(final)) {
451
876
  if (borderSideRe.test(key)) {
452
- collected[key] = final[key];
877
+ collected2[key] = final[key];
453
878
  delete final[key];
454
879
  }
455
880
  }
456
- if (Object.keys(collected).length > 0) {
457
- final.border = { ...final.border || {}, ...collected };
881
+ if (Object.keys(collected2).length > 0) {
882
+ final.border = { ...final.border || {}, ...collected2 };
458
883
  }
459
884
  if (final.border && typeof final.border === "object") {
460
885
  const b = { ...final.border };
@@ -467,7 +892,31 @@ function mapSemanticProps(props, defaultValues, componentType) {
467
892
  final.border = b;
468
893
  }
469
894
  }
470
- return final;
895
+ if (mobile || hideOnMobile !== void 0 || hideOnDesktop !== void 0) {
896
+ const overrides = { ...final._override };
897
+ if (mobile || hideOnMobile !== void 0) {
898
+ const { width, maxWidth, autoWidth, ...phone } = mobile || {};
899
+ const mapped = mapSemanticProps(phone, defaultValues, componentType);
900
+ if (componentType === "Image" && (width !== void 0 || maxWidth !== void 0 || autoWidth !== void 0)) {
901
+ mapped.src = {
902
+ ...overrides.mobile?.src || {},
903
+ ...autoWidth !== void 0 ? { autoWidth } : { autoWidth: false },
904
+ ...width !== void 0 || maxWidth !== void 0 ? { maxWidth: width ?? maxWidth } : {}
905
+ };
906
+ } else if (width !== void 0 || autoWidth !== void 0) {
907
+ const key = componentType === "Button" ? "size" : "width";
908
+ mapped[key] = { autoWidth: autoWidth ?? false, ...width !== void 0 ? { width: typeof width === "number" ? `${width}px` : width } : {} };
909
+ }
910
+ overrides.mobile = { ...overrides.mobile, ...cssSafeValues(mapped), ...hideOnMobile !== void 0 ? { hideMobile: hideOnMobile } : {} };
911
+ }
912
+ if (hideOnDesktop !== void 0) overrides.desktop = { ...overrides.desktop, hideDesktop: hideOnDesktop };
913
+ final._override = overrides;
914
+ }
915
+ if (componentType === "Row" && final.noStackMobile === true) {
916
+ final._override = { ...final._override, mobile: { ...final._override?.mobile, noStackMobile: true } };
917
+ final.noStackMobile = false;
918
+ }
919
+ return normalizeCssValues(final);
471
920
  }
472
921
  function normalizeLinkValue(value) {
473
922
  if (value == null) return void 0;
@@ -489,26 +938,39 @@ function normalizeLinkValue(value) {
489
938
  }
490
939
  return void 0;
491
940
  }
492
- function normalizeValuesForExporter(values, componentName) {
941
+ function safeLink(link) {
942
+ const url = link.url === void 0 || link.url === null ? link.url : String(link.url);
943
+ return typeof url === "string" ? { ...link, url: url.replace(/<%[\w.]+%>|["<>]/g, (c) => c.length > 1 ? c : encodeURIComponent(c)) } : link;
944
+ }
945
+ function normalizeValuesForExporter(values, componentName2, mode) {
493
946
  if (values == null || typeof values !== "object") return values;
494
947
  const out = { ...values };
495
948
  for (const key of ["href", "action"]) {
496
949
  if (out[key] !== void 0) {
497
950
  const normalized = normalizeLinkValue(out[key]);
498
- if (normalized !== void 0) out[key] = normalized;
951
+ if (normalized !== void 0) out[key] = safeLink(normalized);
499
952
  }
500
953
  }
501
- if (componentName === "Menu" && out.menu && Array.isArray(out.menu.items)) {
954
+ if (componentName2 === "Button" && typeof out.text === "string") {
955
+ out.text = escapeKeepingTags(out.text, true);
956
+ }
957
+ if (componentName2 === "Menu" && out.menu && Array.isArray(out.menu.items)) {
502
958
  out.menu = {
503
959
  ...out.menu,
504
960
  items: out.menu.items.map((item) => {
505
- if (!item || item.link === void 0) return item;
506
- const normalized = normalizeLinkValue(item.link);
507
- if (normalized === void 0) return item;
508
- return { ...item, link: normalized };
961
+ if (!item) return item;
962
+ const next = mode === "email" && typeof item.text === "string" ? { ...item, text: menuText(item.text) } : item;
963
+ if (next.link === void 0) return next;
964
+ const normalized = normalizeLinkValue(next.link);
965
+ if (normalized === void 0) return next;
966
+ return { ...next, link: safeLink(normalized) };
509
967
  })
510
968
  };
511
969
  }
970
+ if (componentName2 === "Social" && out.icons && Array.isArray(out.icons.icons)) {
971
+ const named2 = (icon) => mode === "email" && typeof icon.name === "string" ? { ...icon, name: escapeText(icon.name, true) } : icon;
972
+ out.icons = { ...out.icons, icons: out.icons.icons.map((icon) => icon && typeof icon === "object" ? named2(safeLink(icon)) : icon) };
973
+ }
512
974
  return out;
513
975
  }
514
976
  var HTML_ENTITIES = {
@@ -601,9 +1063,11 @@ function htmlToPlainText(html) {
601
1063
  text = text.replace(/<head[\s\S]*?<\/head>/gi, "");
602
1064
  text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
603
1065
  text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
1066
+ text = text.replace(/<br\s*\/?>/gi, "\n");
604
1067
  text = text.replace(/<h[12][^>]*>([\s\S]*?)<\/h[12]>/gi, (_, content) => {
605
1068
  const clean = content.replace(/<[^>]+>/g, "").trim();
606
- return "\n" + clean.toUpperCase() + "\n";
1069
+ const upper = clean.replace(/&#?\w+;|[^&]+|&/g, (part) => /^&#?\w+;$/.test(part) ? part : part.toUpperCase());
1070
+ return "\n" + upper + "\n";
607
1071
  });
608
1072
  text = text.replace(/<h[3-6][^>]*>([\s\S]*?)<\/h[3-6]>/gi, (_, content) => {
609
1073
  const clean = content.replace(/<[^>]+>/g, "").trim();
@@ -625,7 +1089,6 @@ function htmlToPlainText(html) {
625
1089
  const clean = content.replace(/<[^>]+>/g, "").trim();
626
1090
  return "- " + clean + "\n";
627
1091
  });
628
- text = text.replace(/<br\s*\/?>/gi, "\n");
629
1092
  text = text.replace(/<\/(?:p|div|tr|blockquote|section|article|header|footer|main|nav|aside)>/gi, "\n");
630
1093
  text = text.replace(/<\/(?:ul|ol|table|thead|tbody|tfoot)>/gi, "\n");
631
1094
  text = text.replace(/<\/(?:td|th)>/gi, " ");
@@ -672,6 +1135,15 @@ function nextHtmlId(config, prefix) {
672
1135
  ids[prefix] = (ids[prefix] || 0) + 1;
673
1136
  return `${prefix}_${ids[prefix]}`;
674
1137
  }
1138
+ function safeName(value, spaces = false) {
1139
+ const text = String(value ?? "");
1140
+ return spaces ? text.replace(/[\t\n\f\r]/g, " ").replace(/["'<>&`\u0000-\u001f\u007f]/g, "") : text.replace(/["'<>&`\s\u0000-\u001f\u007f]/g, "");
1141
+ }
1142
+ var safeMeta = (meta) => ({ ...meta, htmlID: safeName(meta.htmlID), htmlClassNames: safeName(meta.htmlClassNames, true) });
1143
+ function textDirectionOf(value) {
1144
+ const dir = typeof value === "string" ? value.trim().toLowerCase() : "";
1145
+ return dir === "ltr" || dir === "rtl" || dir === "auto" ? dir : void 0;
1146
+ }
675
1147
  function renderComponent(config) {
676
1148
  const { type, values, className, style, args = [], innerHTML, _config, exporter, metaContext } = config;
677
1149
  try {
@@ -679,7 +1151,7 @@ function renderComponent(config) {
679
1151
  const exporterConfig = {
680
1152
  generateHtmlFromTextJson,
681
1153
  toSafeHtml: cfg.toSafeHtml,
682
- textDirection: cfg.textDirection,
1154
+ textDirection: textDirectionOf(cfg.textDirection),
683
1155
  cdnBaseUrl: cfg.cdnBaseUrl
684
1156
  };
685
1157
  let html;
@@ -743,21 +1215,20 @@ function createItemComponent(config) {
743
1215
  });
744
1216
  const finalValues = mergeValues(config.defaultValues, mappedValues);
745
1217
  const metaName = config.metaName ?? config.name.toLowerCase();
746
- const valuesWithMeta = {
747
- ...finalValues,
748
- _meta: {
749
- htmlID: _metaHtmlId ?? `u_content_${metaName}_${index + 1}`,
750
- htmlClassNames: `u_content_${metaName}`,
751
- ...finalValues._meta || {}
752
- }
1218
+ const meta = {
1219
+ htmlID: _metaHtmlId ?? `u_content_${metaName}_${index + 1}`,
1220
+ htmlClassNames: `u_content_${metaName}`,
1221
+ ...finalValues._meta || {}
753
1222
  };
1223
+ const valuesWithMeta = { ...finalValues, _meta: safeMeta(meta) };
754
1224
  const safeBodyValues = {
755
1225
  contentWidth: "500px",
756
1226
  ...bodyValues
757
1227
  };
758
1228
  const valuesForExporter = normalizeValuesForExporter(
759
1229
  valuesWithMeta,
760
- config.name
1230
+ config.name,
1231
+ mode
761
1232
  );
762
1233
  const exportSrc = valuesForExporter.src;
763
1234
  if (exportSrc && typeof exportSrc === "object" && exportSrc.autoWidth === false) {
@@ -792,10 +1263,12 @@ function createItemComponent(config) {
792
1263
  });
793
1264
  }
794
1265
  const ItemComponent = (props) => {
1266
+ if (collecting()) return whileCollecting(() => recordBlock(ItemComponent, props), () => renderFn(props), ItemComponent.displayName ?? "Block");
795
1267
  return renderFn(props);
796
1268
  };
797
1269
  ItemComponent.displayName = config.displayName || config.name;
798
1270
  ItemComponent[UNLAYER_RENDER_KEY] = renderFn;
1271
+ ItemComponent[RECORDS] = true;
799
1272
  ItemComponent[UNLAYER_CONFIG_KEY] = {
800
1273
  name: config.name,
801
1274
  defaultValues: config.defaultValues,
@@ -803,7 +1276,8 @@ function createItemComponent(config) {
803
1276
  contentType: config.contentType,
804
1277
  slug: config.slug,
805
1278
  metaName: config.metaName,
806
- head: config.head
1279
+ head: config.head,
1280
+ omitEmptyOutput: config.omitEmptyOutput
807
1281
  };
808
1282
  return ItemComponent;
809
1283
  }
@@ -823,9 +1297,9 @@ var Button = createItemComponent({
823
1297
  DEFAULT_VALUES,
824
1298
  "Button"
825
1299
  );
826
- const size = mapped.size;
827
- if (size && typeof size === "object" && !Array.isArray(size)) {
828
- const s = size;
1300
+ const size2 = mapped.size;
1301
+ if (size2 && typeof size2 === "object" && !Array.isArray(size2)) {
1302
+ const s = size2;
829
1303
  if (typeof s.width === "number") {
830
1304
  s.width = `${s.width}px`;
831
1305
  } else if (typeof s.width === "string" && /^\d+(?:\.\d+)?$/.test(s.width.trim())) {
@@ -1015,13 +1489,45 @@ var Paragraph = createItemComponent({
1015
1489
  exporters: exporters.ParagraphExporters
1016
1490
  });
1017
1491
  var Paragraph_default = Paragraph;
1018
- var DEFAULT_ICONS = exporters.SocialDefaults.icons ?? { iconType: "circle", icons: [] };
1492
+
1493
+ // src/components/PageBreak.tsx
1019
1494
  var DEFAULT_VALUES8 = {
1495
+ color: "#6b7280"
1496
+ };
1497
+ var pageBreakExporters = {
1498
+ // Page breaks are a document-only concept. Keep the unsupported renderers
1499
+ // inert so a component rendered outside <Document> does not throw.
1500
+ web: () => "",
1501
+ email: () => "",
1502
+ document: () => '<div class="u-content-page-break" aria-hidden="true"></div>'
1503
+ };
1504
+ var pageBreakHead = {
1505
+ css: (_values, _bodyValues, meta) => meta.displayMode === "document" ? `
1506
+ .u-content-page-break {
1507
+ break-after: page;
1508
+ page-break-after: always;
1509
+ }
1510
+ ` : void 0
1511
+ };
1512
+ var PageBreak = createItemComponent({
1513
+ name: "PageBreak",
1514
+ contentType: "page_break",
1515
+ metaName: "page_break",
1516
+ defaultValues: DEFAULT_VALUES8,
1517
+ propMapper: (props) => mapSemanticProps(props, DEFAULT_VALUES8, "PageBreak"),
1518
+ displayName: "PageBreak",
1519
+ exporters: pageBreakExporters,
1520
+ head: pageBreakHead,
1521
+ omitEmptyOutput: true
1522
+ });
1523
+ var PageBreak_default = PageBreak;
1524
+ var DEFAULT_ICONS = exporters.SocialDefaults.icons ?? { iconType: "circle", icons: [] };
1525
+ var DEFAULT_VALUES9 = {
1020
1526
  ...exporters.SocialDefaults
1021
1527
  };
1022
1528
  var Social = createItemComponent({
1023
1529
  name: "Social",
1024
- defaultValues: DEFAULT_VALUES8,
1530
+ defaultValues: DEFAULT_VALUES9,
1025
1531
  propMapper: (props) => {
1026
1532
  const { icons, iconType, ...rest } = props;
1027
1533
  const coerceSizes = (base) => {
@@ -1042,7 +1548,7 @@ var Social = createItemComponent({
1042
1548
  }));
1043
1549
  const base = mapSemanticProps(
1044
1550
  rest,
1045
- DEFAULT_VALUES8,
1551
+ DEFAULT_VALUES9,
1046
1552
  "Social"
1047
1553
  );
1048
1554
  base.icons = {
@@ -1054,14 +1560,14 @@ var Social = createItemComponent({
1054
1560
  if (iconType !== void 0) {
1055
1561
  const base = mapSemanticProps(
1056
1562
  rest,
1057
- DEFAULT_VALUES8,
1563
+ DEFAULT_VALUES9,
1058
1564
  "Social"
1059
1565
  );
1060
1566
  base.icons = { ...DEFAULT_ICONS, ...base.icons, iconType };
1061
1567
  return coerceSizes(base);
1062
1568
  }
1063
1569
  return coerceSizes(
1064
- mapSemanticProps(props, DEFAULT_VALUES8, "Social")
1570
+ mapSemanticProps(props, DEFAULT_VALUES9, "Social")
1065
1571
  );
1066
1572
  },
1067
1573
  displayName: "Social",
@@ -1069,20 +1575,20 @@ var Social = createItemComponent({
1069
1575
  });
1070
1576
  var Social_default = Social;
1071
1577
  var DEFAULT_TABLE = { headers: [], rows: [], footers: [] };
1072
- var DEFAULT_VALUES9 = {
1578
+ var DEFAULT_VALUES10 = {
1073
1579
  ...exporters.TableDefaults,
1074
1580
  table: DEFAULT_TABLE
1075
1581
  // Schema doesn't include table data structure
1076
1582
  };
1077
1583
  var Table = createItemComponent({
1078
1584
  name: "Table",
1079
- defaultValues: DEFAULT_VALUES9,
1585
+ defaultValues: DEFAULT_VALUES10,
1080
1586
  propMapper: (props) => {
1081
1587
  const { headers, data, columns, rows, ...rest } = props;
1082
1588
  if (headers || data || typeof columns === "number" || typeof rows === "number") {
1083
1589
  const base = mapSemanticProps(
1084
1590
  rest,
1085
- DEFAULT_VALUES9,
1591
+ DEFAULT_VALUES10,
1086
1592
  "Table"
1087
1593
  );
1088
1594
  const colCount = headers ? headers.length : typeof columns === "number" ? columns : data?.[0]?.length ?? 0;
@@ -1114,7 +1620,7 @@ var Table = createItemComponent({
1114
1620
  }
1115
1621
  return mapSemanticProps(
1116
1622
  props,
1117
- DEFAULT_VALUES9,
1623
+ DEFAULT_VALUES10,
1118
1624
  "Table"
1119
1625
  );
1120
1626
  },
@@ -1129,7 +1635,7 @@ var DEFAULT_VIDEO = {
1129
1635
  loading: false,
1130
1636
  ...exporters.VideoDefaults.video
1131
1637
  };
1132
- var DEFAULT_VALUES10 = {
1638
+ var DEFAULT_VALUES11 = {
1133
1639
  ...exporters.VideoDefaults,
1134
1640
  video: DEFAULT_VIDEO
1135
1641
  };
@@ -1157,14 +1663,14 @@ function parseVideoUrl(url) {
1157
1663
  }
1158
1664
  var Video = createItemComponent({
1159
1665
  name: "Video",
1160
- defaultValues: DEFAULT_VALUES10,
1666
+ defaultValues: DEFAULT_VALUES11,
1161
1667
  propMapper: (props) => {
1162
1668
  const { videoUrl, ...rest } = props;
1163
1669
  if (typeof videoUrl === "string") {
1164
1670
  const parsed = parseVideoUrl(videoUrl);
1165
1671
  const base = mapSemanticProps(
1166
1672
  rest,
1167
- DEFAULT_VALUES10,
1673
+ DEFAULT_VALUES11,
1168
1674
  "Video"
1169
1675
  );
1170
1676
  if (parsed) {
@@ -1174,7 +1680,7 @@ var Video = createItemComponent({
1174
1680
  }
1175
1681
  return mapSemanticProps(
1176
1682
  props,
1177
- DEFAULT_VALUES10,
1683
+ DEFAULT_VALUES11,
1178
1684
  "Video"
1179
1685
  );
1180
1686
  },
@@ -1202,7 +1708,36 @@ var {
1202
1708
  var BODY_DEFAULTS = bodyRest;
1203
1709
  var ROW_DEFAULTS = { ...exporters.RowDefaults };
1204
1710
  var COLUMN_DEFAULTS = { ...exporters.ColumnDefaults };
1205
- var DEFAULT_VALUES11 = ROW_DEFAULTS;
1711
+
1712
+ // src/utils/render-scope.ts
1713
+ var scoped;
1714
+ function scopeCache(key, make) {
1715
+ if (!scoped) return make();
1716
+ if (!scoped.has(key)) scoped.set(key, make());
1717
+ return scoped.get(key);
1718
+ }
1719
+ function withRenderScope(render, own = false) {
1720
+ const scope = () => {
1721
+ if (scoped) return render();
1722
+ scoped = /* @__PURE__ */ new WeakMap();
1723
+ try {
1724
+ return render();
1725
+ } finally {
1726
+ scoped = void 0;
1727
+ }
1728
+ };
1729
+ return withBuild(() => within(null, scope), own);
1730
+ }
1731
+ var warnedLoose = /* @__PURE__ */ new Set();
1732
+ function looseText(child, container) {
1733
+ const text = String(child);
1734
+ if (text.trim() && !warnedLoose.has(container)) {
1735
+ warnedLoose.add(container);
1736
+ console.warn(`${container}: text placed straight in a ${container} shows as text and isn't kept in the editor's design. Put it in a <Paragraph>, or HTML in an <Html> block.`);
1737
+ }
1738
+ return escapeKeepingTags(text, true);
1739
+ }
1740
+ var DEFAULT_VALUES12 = ROW_DEFAULTS;
1206
1741
  var DEFAULT_BODY_VALUES = BODY_DEFAULTS;
1207
1742
  function getWidthPercentages(cells) {
1208
1743
  if (cells.length === 0) return [];
@@ -1231,8 +1766,8 @@ ${maxQuery} {
1231
1766
  .u-row { width: 100% !important; }
1232
1767
  .u-row .u-col { display: block !important; width: 100% !important; min-width: 320px !important; max-width: 100% !important; }
1233
1768
  .u-row .u-col > div { margin: 0 auto; }
1234
- .no-stack .u-col { min-width: 0 !important; display: table-cell !important; }
1235
- ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
1769
+ .u-row.no-stack .u-col { min-width: 0 !important; display: table-cell !important; }
1770
+ ${widths.map(({ value, className }) => ` .u-row.no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
1236
1771
  }`;
1237
1772
  }
1238
1773
  const baseCSS = `
@@ -1272,17 +1807,18 @@ function renderRowToHtml(innerHTML, values, bodyValues, mode, cells, collection
1272
1807
  variant: mode
1273
1808
  });
1274
1809
  const contentWidth = toContentWidthPx(bodyValues);
1275
- const css = generateGridCSS(cells, mode, contentWidth);
1276
- return css ? `<style>${css}</style>${html}` : html;
1810
+ const css2 = generateGridCSS(cells, mode, contentWidth);
1811
+ return css2 ? `<style>${css2}</style>${html}` : html;
1277
1812
  }
1278
1813
  function processChildren(children, cells, bodyValues, rowValues, mode, _config) {
1279
1814
  if (!children) return "";
1280
1815
  let innerHTML = "";
1281
- const childrenArray = React__default.default.Children.toArray(children);
1282
- childrenArray.forEach((child, index) => {
1283
- if (!React__default.default.isValidElement(child)) {
1816
+ const childrenArray = React4__default.default.Children.toArray(children);
1817
+ let index = 0;
1818
+ childrenArray.forEach((child) => {
1819
+ if (!React4__default.default.isValidElement(child)) {
1284
1820
  if (typeof child === "string" || typeof child === "number") {
1285
- innerHTML += String(child);
1821
+ innerHTML += looseText(child, "Row");
1286
1822
  }
1287
1823
  return;
1288
1824
  }
@@ -1291,7 +1827,7 @@ function processChildren(children, cells, bodyValues, rowValues, mode, _config)
1291
1827
  if (isColumn && typeof child.type === "function") {
1292
1828
  const rendered = child.type({
1293
1829
  ...child.props,
1294
- index,
1830
+ index: index++,
1295
1831
  cells,
1296
1832
  bodyValues,
1297
1833
  rowValues,
@@ -1301,7 +1837,7 @@ function processChildren(children, cells, bodyValues, rowValues, mode, _config)
1301
1837
  if (rendered?.props?.dangerouslySetInnerHTML?.__html) {
1302
1838
  innerHTML += rendered.props.dangerouslySetInnerHTML.__html;
1303
1839
  }
1304
- } else if (React__default.default.isValidElement(child)) {
1840
+ } else if (React4__default.default.isValidElement(child)) {
1305
1841
  const name = child.type?.displayName || child.type?.name || "Unknown";
1306
1842
  console.warn(
1307
1843
  `Row: <${name}> is not a valid Row child. Only <Column> components can be direct children of <Row>. Wrap it in a <Column>: <Row><Column><${name} /></Column></Row>`
@@ -1310,7 +1846,7 @@ function processChildren(children, cells, bodyValues, rowValues, mode, _config)
1310
1846
  });
1311
1847
  return innerHTML;
1312
1848
  }
1313
- var Row = (props) => {
1849
+ function renderRow(props) {
1314
1850
  const {
1315
1851
  layout,
1316
1852
  cells: propsCells,
@@ -1325,33 +1861,25 @@ var Row = (props) => {
1325
1861
  ...semanticProps
1326
1862
  } = props;
1327
1863
  const mode = modeProp ?? _config?.mode ?? "web";
1864
+ const columnCount = React4__default.default.Children.toArray(children).filter(
1865
+ (c) => React4__default.default.isValidElement(c) && /^Column$/.test(c.type?.displayName || c.type?.name || "")
1866
+ ).length;
1328
1867
  let cells;
1329
1868
  if (layout) {
1330
- validateColumnLayout(layout, React__default.default.Children.count(children));
1869
+ if (columnCount !== layout.expectedColumns) validateColumnLayout(layout, React4__default.default.Children.count(children));
1331
1870
  cells = layout.cells;
1332
1871
  } else if (propsCells) {
1333
1872
  cells = propsCells;
1334
1873
  } else {
1335
- const columnCount = React__default.default.Children.toArray(children).filter(
1336
- (c) => React__default.default.isValidElement(c) && /^Column$/.test(c.type?.displayName || c.type?.name || "")
1337
- ).length;
1338
1874
  cells = Array(Math.max(1, columnCount)).fill(1);
1339
1875
  }
1340
1876
  const safeBodyValues = { ...DEFAULT_BODY_VALUES, ...bodyValues };
1341
1877
  const values = mapSemanticProps(
1342
1878
  semanticProps,
1343
- DEFAULT_VALUES11,
1879
+ DEFAULT_VALUES12,
1344
1880
  "Row"
1345
1881
  );
1346
- const valuesWithMeta = {
1347
- ...values,
1348
- cells,
1349
- _meta: {
1350
- htmlID: nextHtmlId(_config, "u_row"),
1351
- htmlClassNames: "u_row",
1352
- ...values._meta || {}
1353
- }
1354
- };
1882
+ const valuesWithMeta = { ...values, cells, _meta: safeMeta({ htmlID: nextHtmlId(_config, "u_row"), htmlClassNames: "u_row", ...values._meta || {} }) };
1355
1883
  const innerHTML = processChildren(
1356
1884
  children,
1357
1885
  cells,
@@ -1374,11 +1902,12 @@ var Row = (props) => {
1374
1902
  console.error("Row rendering failed:", error);
1375
1903
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children });
1376
1904
  }
1377
- };
1905
+ }
1906
+ var Row = (props) => collecting() ? whileCollecting(() => recordContainer("row", props), () => renderRow(props), "Row") : renderRow(props);
1378
1907
  Row.displayName = "Row";
1908
+ Row[RECORDS] = true;
1379
1909
  var Row_default = Row;
1380
- var DEFAULT_CONTAINER_PADDING2 = "10px";
1381
- var DEFAULT_VALUES12 = COLUMN_DEFAULTS;
1910
+ var DEFAULT_VALUES13 = COLUMN_DEFAULTS;
1382
1911
  function renderColumnToHtml(innerHTML, values, index, cells, bodyValues, rowValues, mode) {
1383
1912
  const columnExporter = exporters.ColumnExporters[mode] || exporters.ColumnExporters.web;
1384
1913
  return columnExporter(innerHTML, values, index, cells, bodyValues, rowValues);
@@ -1388,6 +1917,7 @@ function renderContentToHtml(innerHTML, values, bodyValues, mode) {
1388
1917
  return contentExporter(innerHTML, values, bodyValues, {});
1389
1918
  }
1390
1919
  var Column = (props) => {
1920
+ if (collecting()) return whileCollecting(() => recordContainer("column", props), () => Column(props), "Column");
1391
1921
  const {
1392
1922
  children,
1393
1923
  index = 0,
@@ -1403,30 +1933,23 @@ var Column = (props) => {
1403
1933
  const mode = modeProp ?? _config?.mode ?? "web";
1404
1934
  const values = mapSemanticProps(
1405
1935
  semanticProps,
1406
- DEFAULT_VALUES12,
1936
+ DEFAULT_VALUES13,
1407
1937
  "Column"
1408
1938
  );
1409
- const valuesWithMeta = {
1410
- ...values,
1411
- _meta: {
1412
- htmlID: nextHtmlId(_config, "u_column"),
1413
- htmlClassNames: "u_column",
1414
- ...values._meta || {}
1415
- }
1416
- };
1939
+ const valuesWithMeta = { ...values, _meta: safeMeta({ htmlID: nextHtmlId(_config, "u_column"), htmlClassNames: "u_column", ...values._meta || {} }) };
1417
1940
  let innerHTML = "";
1418
1941
  if (children) {
1419
1942
  try {
1420
- const childrenArray = React__default.default.Children.toArray(children);
1943
+ const childrenArray = React4__default.default.Children.toArray(children);
1421
1944
  childrenArray.forEach((child, childIndex) => {
1422
1945
  if (typeof child === "string" || typeof child === "number") {
1423
- innerHTML += String(child);
1424
- } else if (React__default.default.isValidElement(child)) {
1946
+ innerHTML += looseText(child, "Column");
1947
+ } else if (React4__default.default.isValidElement(child)) {
1425
1948
  if (typeof child.type === "function") {
1426
1949
  const ComponentType = child.type;
1427
1950
  const renderFn = ComponentType[UNLAYER_RENDER_KEY] || ComponentType;
1428
- const componentName = ComponentType?.[UNLAYER_CONFIG_KEY]?.metaName ?? (ComponentType?.displayName || ComponentType?.name || "component").toLowerCase();
1429
- const contentHtmlId = nextHtmlId(_config, `u_content_${componentName}`);
1951
+ const componentName2 = ComponentType?.[UNLAYER_CONFIG_KEY]?.metaName ?? (ComponentType?.displayName || ComponentType?.name || "component").toLowerCase();
1952
+ const contentHtmlId = nextHtmlId(_config, `u_content_${componentName2}`);
1430
1953
  const rendered = renderFn({
1431
1954
  ...child.props,
1432
1955
  _config,
@@ -1439,14 +1962,28 @@ var Column = (props) => {
1439
1962
  });
1440
1963
  if (rendered && typeof rendered === "object" && rendered.props && rendered.props.dangerouslySetInnerHTML) {
1441
1964
  const componentHTML = rendered.props.dangerouslySetInnerHTML.__html;
1965
+ if (!componentHTML && ComponentType?.[UNLAYER_CONFIG_KEY]?.omitEmptyOutput) return;
1442
1966
  const childProps = child.props;
1443
- const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING2;
1967
+ const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING;
1444
1968
  const containerPadding = typeof rawContainerPadding === "number" ? `${rawContainerPadding}px` : rawContainerPadding;
1969
+ const device = ComponentType?.[UNLAYER_CONFIG_KEY]?.contentType === "custom" ? {} : childProps;
1445
1970
  const contentValues = {
1446
1971
  containerPadding,
1972
+ _override: {
1973
+ ...childProps.values?._override,
1974
+ mobile: {
1975
+ ...childProps.values?._override?.mobile,
1976
+ ...device.hideOnMobile !== void 0 ? { hideMobile: device.hideOnMobile } : {}
1977
+ },
1978
+ desktop: {
1979
+ ...childProps.values?._override?.desktop,
1980
+ ...device.hideOnDesktop !== void 0 ? { hideDesktop: device.hideOnDesktop } : {}
1981
+ }
1982
+ },
1447
1983
  _meta: {
1448
1984
  htmlID: contentHtmlId,
1449
- htmlClassNames: `u_content_${componentName}`
1985
+ htmlClassNames: `u_content_${componentName2}`,
1986
+ ...childProps.values?._meta?.htmlID ? { htmlID: safeName(childProps.values._meta.htmlID) } : {}
1450
1987
  }
1451
1988
  };
1452
1989
  innerHTML += renderContentToHtml(
@@ -1485,8 +2022,331 @@ var Column = (props) => {
1485
2022
  }
1486
2023
  };
1487
2024
  Column.displayName = "Column";
2025
+ Column[RECORDS] = true;
1488
2026
  var Column_default = Column;
1489
- var DEFAULT_VALUES13 = BODY_DEFAULTS;
2027
+ var KEPT = Symbol.for("@unlayer/react-elements/kept");
2028
+ function parse(html, collection) {
2029
+ const root = [];
2030
+ const open = [root];
2031
+ const pattern = new RegExp(`<${MARK} data-n="${collection.nonce}" data-i="(\\d+)"(?: data-e="([ocf])")?></${MARK}>`, "g");
2032
+ let last = 0;
2033
+ for (const match of html.matchAll(pattern)) {
2034
+ const into = open[open.length - 1];
2035
+ if (match.index > last) into.push({ kind: "raw", html: html.slice(last, match.index) });
2036
+ last = match.index + match[0].length;
2037
+ const at = Number(match[1]);
2038
+ const edge = match[2];
2039
+ if (edge === "o") {
2040
+ const children = [];
2041
+ into.push({ kind: collection.recorded[at].kind, at, children });
2042
+ open.push(children);
2043
+ } else if (edge === "c") open.pop();
2044
+ else into.push({ kind: edge === "f" ? "failed" : "block", at });
2045
+ }
2046
+ if (last < html.length) open[open.length - 1].push({ kind: "raw", html: html.slice(last) });
2047
+ if (open.length !== 1) throw new Error("[Unlayer] a collected render's markers don't pair up");
2048
+ return root;
2049
+ }
2050
+ var TEXT_ENTITIES = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#x27;": "'" };
2051
+ var VOID = /^(area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)$/i;
2052
+ function parts(html) {
2053
+ const out = [];
2054
+ let at = 0;
2055
+ const text = (s) => s && out.push({ text: s.replace(/&amp;|&lt;|&gt;|&quot;|&#x27;/g, (e) => TEXT_ENTITIES[e]) });
2056
+ while (at < html.length) {
2057
+ const start = html.indexOf("<", at);
2058
+ if (start < 0) {
2059
+ text(html.slice(at));
2060
+ break;
2061
+ }
2062
+ text(html.slice(at, start));
2063
+ const name = /^<([a-zA-Z][\w:-]*)/.exec(html.slice(start))?.[1];
2064
+ if (!name) {
2065
+ out.push({ tag: "#", html: html.slice(start) });
2066
+ break;
2067
+ }
2068
+ const openEnd = html.indexOf(">", start);
2069
+ if (VOID.test(name) || html[openEnd - 1] === "/") {
2070
+ out.push({ tag: name, html: html.slice(start, openEnd + 1) });
2071
+ at = openEnd + 1;
2072
+ continue;
2073
+ }
2074
+ const tags = new RegExp(`<(/?)${name}(?=[\\s>/])`, "gi");
2075
+ tags.lastIndex = openEnd + 1;
2076
+ let depth = 1;
2077
+ let end = -1;
2078
+ for (let m = tags.exec(html); m; m = tags.exec(html)) {
2079
+ depth += m[1] ? -1 : 1;
2080
+ if (depth === 0) {
2081
+ end = m.index;
2082
+ break;
2083
+ }
2084
+ }
2085
+ if (end < 0) {
2086
+ out.push({ tag: "#", html: html.slice(start) });
2087
+ break;
2088
+ }
2089
+ const close = html.indexOf(">", end) + 1;
2090
+ out.push({ tag: name, html: html.slice(start, close), inner: html.slice(openEnd + 1, end) });
2091
+ at = close;
2092
+ }
2093
+ return out;
2094
+ }
2095
+ function innerType(type) {
2096
+ while (type?.$$typeof === MEMO) type = type.type;
2097
+ return type;
2098
+ }
2099
+ var recorded = (c, node) => c.collection.recorded[node.at];
2100
+ var nameOf = (element2) => {
2101
+ if (typeof element2.type === "string") return element2.type;
2102
+ const type = innerType(element2.type);
2103
+ return type?.displayName || type?.name || "component";
2104
+ };
2105
+ function leftOut(c, node) {
2106
+ if (c.fatal) throw c.fatal;
2107
+ if (!c.told.has(node.at)) {
2108
+ c.told.add(node.at);
2109
+ try {
2110
+ tell(recorded(c, node).element);
2111
+ } catch (error) {
2112
+ c.fatal = error;
2113
+ throw error;
2114
+ }
2115
+ }
2116
+ return [];
2117
+ }
2118
+ function calledAgain(element2) {
2119
+ const type = innerType(element2.type);
2120
+ if (type?.$$typeof !== FORWARD_REF && (typeof type !== "function" || type.prototype?.isReactComponent)) return void 0;
2121
+ const props = { ...element2.props };
2122
+ for (const key in type.defaultProps) if (props[key] === void 0) props[key] = type.defaultProps[key];
2123
+ return { out: callIsolated(() => type.$$typeof === FORWARD_REF ? type.render(props, null) : type(props)) };
2124
+ }
2125
+ function tell(element2) {
2126
+ const type = innerType(element2.type);
2127
+ const name = nameOf(element2);
2128
+ const async = () => console.warn(`[Unlayer] <${name}> is async or suspends: Elements renders synchronously, so it's left out. Load its data first and pass it as props.`);
2129
+ if (isAsyncComponent(type)) return async();
2130
+ try {
2131
+ const again = calledAgain(element2);
2132
+ const inner = again ? collected(again.out) : collected(element2, false);
2133
+ const failed = (nodes) => nodes.flatMap((n) => n.kind === "failed" ? [n] : "children" in n ? failed(n.children) : []);
2134
+ const found = failed(inner.nodes);
2135
+ for (const node of found) tell(recorded(inner, node).element);
2136
+ if (!found.length) console.error(`[Unlayer] <${name}> failed while rendering, so it's left out.`);
2137
+ } catch (error) {
2138
+ const message = String(error?.message);
2139
+ if (/Element type is invalid|Objects are not valid as a React child|Minified React error #(130|31)\b/.test(message)) throw error;
2140
+ if (isThenable(error) || /async and suspending|suspended|Minified React error #426\b/.test(message)) async();
2141
+ else console.error(`[Unlayer] <${name}> threw while rendering, so it's left out:`, error);
2142
+ }
2143
+ }
2144
+ function markupOnly(c, nodes) {
2145
+ let out = "";
2146
+ for (const node of nodes) {
2147
+ if (node.kind === "raw") out += node.html;
2148
+ else if (node.kind === "failed") leftOut(c, node);
2149
+ else if (node.kind !== "boundary") return void 0;
2150
+ else {
2151
+ const inner = markupOnly(c, node.children);
2152
+ if (inner === void 0) return void 0;
2153
+ out += inner;
2154
+ }
2155
+ }
2156
+ return out;
2157
+ }
2158
+ function keptMarkup(c, node) {
2159
+ c.kept ?? (c.kept = /* @__PURE__ */ new Map());
2160
+ if (!c.kept.has(node.at)) c.kept.set(node.at, keptMarkupOnce(c, node));
2161
+ return c.kept.get(node.at);
2162
+ }
2163
+ function keptMarkupOnce(c, node) {
2164
+ const raw = markupOnly(c, node.children);
2165
+ if (raw === void 0) return void 0;
2166
+ const list = parts(raw);
2167
+ if (!list.some((p) => "tag" in p)) return void 0;
2168
+ const element2 = recorded(c, node).element;
2169
+ const one = list.length === 1 || list.some((p) => "tag" in p && p.tag === "#") ? /^<([a-zA-Z][\w:-]*)\b[^>]*>([\s\S]*)<\/\1>$/.exec(raw) : null;
2170
+ let given = false;
2171
+ if (one) {
2172
+ try {
2173
+ const out = calledAgain(element2)?.out;
2174
+ given = React4__default.default.isValidElement(out) && typeof out.type === "string" && out.props.dangerouslySetInnerHTML != null;
2175
+ } catch {
2176
+ given = one[1].toLowerCase() === "div";
2177
+ }
2178
+ }
2179
+ const html = one && given ? one[2] : raw;
2180
+ const Rendered = () => React4__default.default.createElement("div", { dangerouslySetInnerHTML: { __html: html } });
2181
+ Rendered.displayName = nameOf(element2);
2182
+ const { containerPadding, hideOnMobile, hideOnDesktop } = element2.props ?? {};
2183
+ Rendered[KEPT] = React4__default.default.createElement(Html_default, {
2184
+ html,
2185
+ ...containerPadding !== void 0 ? { containerPadding } : {},
2186
+ ...hideOnMobile !== void 0 ? { hideOnMobile } : {},
2187
+ ...hideOnDesktop !== void 0 ? { hideOnDesktop } : {}
2188
+ });
2189
+ return React4__default.default.createElement(Rendered, { ...element2.props });
2190
+ }
2191
+ function resolvedProps(c, props) {
2192
+ let out = props;
2193
+ for (const [key, value] of Object.entries(props)) {
2194
+ if (typeof value !== "string" || !value.includes(MARK)) continue;
2195
+ let html = value;
2196
+ try {
2197
+ if (value.includes(c.collection.nonce)) html = markup(c, parse(value, c.collection), (block) => within(null, () => renderToStaticMarkup(block)));
2198
+ } catch {
2199
+ }
2200
+ out = out === props ? { ...props } : out;
2201
+ out[key] = withoutMarkers(html, c.collection.nonce);
2202
+ }
2203
+ return out;
2204
+ }
2205
+ function withoutMarkers(html, nonce2) {
2206
+ const left = new RegExp(`<${MARK} data-n="${nonce2}" data-i="\\d+"(?: data-e="[ocf]")?></${MARK}>`, "g");
2207
+ if (!html.includes(nonce2) || !html.match(left)) return html;
2208
+ console.error(`[Unlayer] an Elements block rendered inside a component left a marker behind; it's taken out, and the block it stood for is missing.`);
2209
+ return html.replace(left, "");
2210
+ }
2211
+ function markup(c, nodes, render) {
2212
+ return nodes.map((node) => {
2213
+ switch (node.kind) {
2214
+ case "raw":
2215
+ return node.html;
2216
+ case "boundary":
2217
+ return markup(c, node.children, render);
2218
+ case "failed":
2219
+ leftOut(c, node);
2220
+ return "";
2221
+ default:
2222
+ return render(element(c, node));
2223
+ }
2224
+ }).join("");
2225
+ }
2226
+ var keyed = (list) => list.map((child, i) => React4__default.default.isValidElement(child) && child.key == null ? React4__default.default.cloneElement(child, { key: `k${i}` }) : child);
2227
+ function element(c, node) {
2228
+ const r = recorded(c, node);
2229
+ if (node.kind === "block") return React4__default.default.createElement(r.type, resolvedProps(c, r.props));
2230
+ if (node.kind === "column") return React4__default.default.createElement(Column_default, { ...r.props, children: keyed(elements(c, node.children, "column")) });
2231
+ const props = { ...r.props };
2232
+ const children = keyed(elements(c, node.children, "row"));
2233
+ if (props.layout) {
2234
+ const columns = children.filter((child) => React4__default.default.isValidElement(child) && child.type === Column_default).length;
2235
+ if (columns === props.layout.expectedColumns || React4__default.default.Children.count(props.children) === props.layout.expectedColumns) {
2236
+ props.cells = props.layout.cells;
2237
+ delete props.layout;
2238
+ }
2239
+ }
2240
+ return React4__default.default.createElement(Row_default, { ...props, children });
2241
+ }
2242
+ function spanned(nodes) {
2243
+ const out = [];
2244
+ const spans = [];
2245
+ for (const node of nodes) {
2246
+ if (node.kind !== "raw") {
2247
+ out.push({ node, inside: spans.length > 0 });
2248
+ continue;
2249
+ }
2250
+ const html = node.html;
2251
+ const open = [];
2252
+ let closed = spans.length ? -1 : 0;
2253
+ for (const m of html.matchAll(/<!--[\s\S]*?(?:-->|$)|<(\/?)([a-zA-Z][\w:-]*)\b[^>]*>/g)) {
2254
+ if (!m[2]) continue;
2255
+ const name = m[2].toLowerCase();
2256
+ if (m[1]) {
2257
+ const i = open.map((o) => o.name).lastIndexOf(name);
2258
+ const across = spans.lastIndexOf(name);
2259
+ if (i >= 0) open.length = i;
2260
+ else if (across >= 0) {
2261
+ spans.length = across;
2262
+ open.length = 0;
2263
+ if (!spans.length) closed = m.index + m[0].length;
2264
+ }
2265
+ } else if (!VOID.test(name) && !m[0].endsWith("/>")) open.push({ name, at: m.index });
2266
+ }
2267
+ const piece = (from, to, inside) => {
2268
+ if (to > from) out.push({ node: { kind: "raw", html: html.slice(from, to) }, inside });
2269
+ };
2270
+ if (closed < 0) piece(0, html.length, true);
2271
+ else {
2272
+ piece(0, closed, true);
2273
+ piece(closed, open[0]?.at ?? html.length, false);
2274
+ if (open.length) piece(open[0].at, html.length, true);
2275
+ }
2276
+ spans.push(...open.map((o) => o.name));
2277
+ }
2278
+ return out;
2279
+ }
2280
+ function elements(c, nodes, place, owner, inMarkup = false) {
2281
+ const inColumn = place === "column";
2282
+ return (place === "root" || inMarkup ? nodes.map((node) => ({ node, inside: inMarkup })) : spanned(nodes)).flatMap(({ node, inside }) => {
2283
+ switch (node.kind) {
2284
+ case "failed":
2285
+ return leftOut(c, node);
2286
+ case "boundary": {
2287
+ const kept = inColumn && !inside ? keptMarkup(c, node) : void 0;
2288
+ if (kept) return [kept];
2289
+ const Named = () => null;
2290
+ Named.displayName = nameOf(recorded(c, node).element);
2291
+ if (inColumn && !inside && !c.told.has(node.at) && node.children.some((n) => n.kind === "raw" && parts(n.html).some((p) => "tag" in p))) {
2292
+ c.told.add(node.at);
2293
+ console.warn(`Column: <${Named.displayName}> rendered markup that isn't a block, so it's left out. Put HTML in an <Html> block.`);
2294
+ }
2295
+ return elements(c, node.children, place, Named, inside);
2296
+ }
2297
+ case "raw":
2298
+ if (inside) {
2299
+ const name = /^<([a-zA-Z][\w:-]*)/.exec(node.html)?.[1];
2300
+ return place === "row" && name && !inMarkup ? [React4__default.default.createElement(owner ?? name)] : [];
2301
+ }
2302
+ return parts(node.html).flatMap((p) => "text" in p ? [p.text] : inColumn ? [] : [React4__default.default.createElement(owner ?? (p.tag === "#" ? "div" : p.tag))]);
2303
+ default:
2304
+ return [element(c, node)];
2305
+ }
2306
+ });
2307
+ }
2308
+ function collected(children, guarded2 = true) {
2309
+ const once = (markup2) => {
2310
+ const collection = { nonce: nonce(), recorded: [], ...markup2 ? { markup: markup2 } : {}, ...idsSeen() ? {} : { blind: true } };
2311
+ const html = within(collection, () => renderToStaticMarkup(/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: guarded2 ? guard(children, collection) : children }), { identifierPrefix: COLLECTING_ID }));
2312
+ return { nodes: parse(html, collection), collection, told: /* @__PURE__ */ new Set() };
2313
+ };
2314
+ try {
2315
+ return once(false);
2316
+ } catch (error) {
2317
+ if (!guarded2) throw error;
2318
+ return once(true);
2319
+ }
2320
+ }
2321
+ function idsSeen() {
2322
+ let seen = false;
2323
+ const Probe = () => {
2324
+ try {
2325
+ seen = React4__default.default.useId().slice(1).startsWith(COLLECTING_ID);
2326
+ } catch {
2327
+ }
2328
+ return null;
2329
+ };
2330
+ renderToStaticMarkup(/* @__PURE__ */ jsxRuntime.jsx(Probe, {}), { identifierPrefix: COLLECTING_ID });
2331
+ return seen;
2332
+ }
2333
+ function nonce() {
2334
+ const random = globalThis.crypto?.getRandomValues?.(new Uint32Array(2));
2335
+ return random ? Array.from(random, (n) => n.toString(36)).join("") : `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
2336
+ }
2337
+ var collect = (children) => children !== null && typeof children === "object" ? scopeCache(children, () => collected(children)) : collected(children);
2338
+ function childrenHtml2(children, place) {
2339
+ const c = collect(children);
2340
+ return withoutMarkers(markup(c, c.nodes, (child) => within(null, () => renderToString(place(child)))), c.collection.nonce);
2341
+ }
2342
+ function normalizedChildren(children) {
2343
+ const c = collect(children);
2344
+ return elements(c, c.nodes, "root");
2345
+ }
2346
+ function normalizedRoot(root) {
2347
+ return React4__default.default.cloneElement(root, void 0, ...normalizedChildren(root.props.children));
2348
+ }
2349
+ var DEFAULT_VALUES14 = BODY_DEFAULTS;
1490
2350
  var MAX_PREVIEW_LENGTH = 150;
1491
2351
  var PADDING_CHARS = [
1492
2352
  "\xA0",
@@ -1497,6 +2357,9 @@ var PADDING_CHARS = [
1497
2357
  "\u200F",
1498
2358
  "\uFEFF"
1499
2359
  ];
2360
+ function escapePreviewText(text) {
2361
+ return text.split(/(<%[\w.]+%>)/).map((part, i) => i % 2 ? part : part.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
2362
+ }
1500
2363
  function generatePreviewHtml(text) {
1501
2364
  if (!text || text.trim().length === 0) return "";
1502
2365
  const truncated = text.length > MAX_PREVIEW_LENGTH ? text.slice(0, MAX_PREVIEW_LENGTH) : text;
@@ -1505,7 +2368,7 @@ function generatePreviewHtml(text) {
1505
2368
  for (let i = 0; i < paddingLength; i++) {
1506
2369
  padding += PADDING_CHARS[i % PADDING_CHARS.length];
1507
2370
  }
1508
- return `<div data-skip-in-text="true" style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">` + truncated + padding + `</div>`;
2371
+ return `<div data-skip-in-text="true" style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">` + escapePreviewText(truncated) + padding + `</div>`;
1509
2372
  }
1510
2373
  function renderBodyToHtml(innerHTML, values, mode, previewText) {
1511
2374
  let finalInnerHtml = innerHTML;
@@ -1526,14 +2389,14 @@ function renderBodyToHtml(innerHTML, values, mode, previewText) {
1526
2389
  return raw.replace("min-height: 100vh; ", "").replace("min-height: 100vh;", "");
1527
2390
  }
1528
2391
  var Body = (props) => {
1529
- const { children, mode: modeProp, className, style, index = 0, config: configProp, previewText, ...semanticProps } = props;
2392
+ const { children, mode: modeProp, className, style, index = 0, config: configProp, previewText, fonts: _fonts, lang: _lang, ...semanticProps } = props;
1530
2393
  const resolvedConfig = { ...DEFAULT_CONFIG, ...configProp };
1531
2394
  const mode = modeProp ?? resolvedConfig.mode ?? "web";
1532
2395
  const _config = { ...resolvedConfig, mode };
1533
2396
  _config.__ids = {};
1534
2397
  const values = mergeValues(
1535
- DEFAULT_VALUES13,
1536
- mapSemanticProps(semanticProps, DEFAULT_VALUES13, "Body")
2398
+ DEFAULT_VALUES14,
2399
+ mapSemanticProps(semanticProps, DEFAULT_VALUES14, "Body")
1537
2400
  );
1538
2401
  const valuesWithMeta = {
1539
2402
  ...values,
@@ -1543,22 +2406,12 @@ var Body = (props) => {
1543
2406
  ...values._meta || {}
1544
2407
  }
1545
2408
  };
1546
- let enrichedChildren = children;
1547
- if (children) {
1548
- enrichedChildren = React__default.default.Children.map(children, (child) => {
1549
- if (React__default.default.isValidElement(child)) {
1550
- return React__default.default.cloneElement(child, {
1551
- _config,
1552
- bodyValues: values
1553
- });
1554
- }
1555
- return child;
1556
- });
1557
- }
1558
2409
  let innerHTML = "";
1559
- if (enrichedChildren) {
2410
+ if (children) {
1560
2411
  try {
1561
- innerHTML = ReactDOMServer__default.default.renderToString(enrichedChildren);
2412
+ innerHTML = withRenderScope(() => {
2413
+ return childrenHtml2(children, (child) => React4__default.default.cloneElement(child, { _config, bodyValues: values }));
2414
+ }, true);
1562
2415
  } catch (error) {
1563
2416
  console.error("Body: Failed to render children:", error);
1564
2417
  innerHTML = "";
@@ -1597,7 +2450,7 @@ var PROVIDER_ACTIVE_KEY = "__unlayerProviderActive";
1597
2450
  var _UnlayerContext = null;
1598
2451
  function getUnlayerContext() {
1599
2452
  if (!_UnlayerContext) {
1600
- _UnlayerContext = React.createContext(DEFAULT_CONFIG);
2453
+ _UnlayerContext = React4.createContext(DEFAULT_CONFIG);
1601
2454
  }
1602
2455
  return _UnlayerContext;
1603
2456
  }
@@ -1605,7 +2458,7 @@ var UnlayerProvider = ({
1605
2458
  config,
1606
2459
  children
1607
2460
  }) => {
1608
- const merged = React.useMemo(
2461
+ const merged = React4.useMemo(
1609
2462
  () => ({ ...DEFAULT_CONFIG, ...config, [PROVIDER_ACTIVE_KEY]: true }),
1610
2463
  [config]
1611
2464
  );
@@ -1614,31 +2467,106 @@ var UnlayerProvider = ({
1614
2467
  };
1615
2468
  UnlayerProvider.displayName = "UnlayerProvider";
1616
2469
  function useUnlayerConfig() {
1617
- return React.useContext(getUnlayerContext());
2470
+ return React4.useContext(getUnlayerContext());
1618
2471
  }
1619
- function ensureMeta(values, type, index = 0) {
1620
- return {
1621
- ...values,
1622
- _meta: {
1623
- htmlID: `u_content_${type.toLowerCase()}_${index + 1}`,
1624
- htmlClassNames: `u_content_${type.toLowerCase()}`,
1625
- ...values._meta || {}
2472
+
2473
+ // src/utils/device-overrides.ts
2474
+ var contentOptions = {
2475
+ Button: ["size", "fontSize", "lineHeight", "textAlign", "padding"],
2476
+ Image: ["src", "textAlign"],
2477
+ Heading: ["fontSize", "textAlign", "lineHeight"],
2478
+ Paragraph: ["fontSize", "textAlign", "lineHeight"],
2479
+ Menu: ["fontSize", "padding"],
2480
+ Divider: ["width", "textAlign"]
2481
+ };
2482
+ function css(value) {
2483
+ const text = String(value).trim();
2484
+ return text && !/[!\\"'\n\r]/.test(text) && cssValueSafe(text) ? text : void 0;
2485
+ }
2486
+ function ident(id) {
2487
+ return id.replace(/^-?\d|^-$|[^\w\u0080-\uffff-]/g, (c) => /\d/.test(c.slice(-1)) ? `${c.slice(0, -1)}\\${c.slice(-1).charCodeAt(0).toString(16)} ` : `\\${c}`);
2488
+ }
2489
+ function size(value) {
2490
+ const number = parseFloat(String(value));
2491
+ if (!Number.isFinite(number)) return;
2492
+ const rounded = parseFloat(number.toFixed(2));
2493
+ if (typeof value === "number" || String(value).endsWith("px")) return `${rounded}px`;
2494
+ if (String(value).endsWith("%")) return `${rounded}%`;
2495
+ }
2496
+ function collectDeviceStyles(values, collection, name, mode, styles) {
2497
+ const given = safeName(values._meta?.htmlID);
2498
+ if (!given) return;
2499
+ const id = ident(given);
2500
+ const options = collection === "contents" ? ["containerPadding", ...contentOptions[name] || []] : collection === "columns" ? ["padding", "border"] : ["padding"];
2501
+ for (const device of mode === "document" ? ["desktop"] : ["desktop", "mobile"]) {
2502
+ const overrides = values._override?.[device];
2503
+ if (!overrides) continue;
2504
+ for (const option of options) {
2505
+ if (!(option in overrides)) continue;
2506
+ const value = overrides[option];
2507
+ const property = option.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
2508
+ const prefix = collection === "rows" ? "row-" : collection === "columns" ? "col-" : "";
2509
+ const selector = (attribute) => `#${id} .v-${prefix}${property === attribute ? property : `${property}-${attribute}`}`;
2510
+ const push = (rule) => (styles[device] || (styles[device] = [])).push(rule);
2511
+ if (option === "padding" || option === "containerPadding") {
2512
+ if (!(value || value === 0) || css(value) === void 0) continue;
2513
+ const direct = collection === "rows" || mode === "web" && collection === "contents" && option === "containerPadding";
2514
+ const target = direct ? selector("padding").replace(`#${id} `, `#${id}`) : selector("padding");
2515
+ push(`${target} { padding: ${css(value)} !important; }`);
2516
+ if (mode === "email" && collection === "rows") {
2517
+ const parts2 = String(value).trim().split(/\s+/);
2518
+ const [top, bottom] = [parts2[0], parts2[2] || parts2[0]].map((part) => size(part) ?? css(part));
2519
+ if (top !== void 0 && bottom !== void 0) push(`${target}--vertical { padding-top: ${top} !important; padding-bottom: ${bottom} !important; }`);
2520
+ }
2521
+ } else if (option === "border") {
2522
+ if (value) push(`${selector("border")} { ${["Top", "Left", "Right", "Bottom"].map((d) => {
2523
+ const w = css(value[`border${d}Width`] || "0px") ?? "0px";
2524
+ return `border-${d.toLowerCase()}: ${w} ${css(value[`border${d}Style`] || "solid") ?? "solid"} ${parseInt(w) > 0 && css(value[`border${d}Color`]) || "transparent"} !important;`;
2525
+ }).join("")} }`);
2526
+ } else if (option === "src") {
2527
+ if (!value) continue;
2528
+ const merged = { ...values.src, ...value };
2529
+ const maximum = merged.autoWidth === false ? size(merged.maxWidth) : void 0;
2530
+ push(`${selector("width")} { width: ${maximum || "100%"} !important; }`);
2531
+ push(`${selector("max-width")} { max-width: ${size(maximum || merged.width || "100%")} !important; }`);
2532
+ } else if (option === "size" || option === "width") {
2533
+ if (!value) continue;
2534
+ push(`${selector("width")} { width: ${size(value.width) && !value.autoWidth ? size(value.width) : "auto"} !important; }`);
2535
+ push(`${selector("max-width")} { max-width: 100% !important; }`);
2536
+ } else {
2537
+ if (!(value || option === "lineHeight" && `${value}` === "0")) continue;
2538
+ const rendered = css(option === "fontSize" && typeof value === "number" ? `${value}px` : value);
2539
+ if (rendered === void 0) continue;
2540
+ push(`${selector(property)} { ${property}: ${rendered} !important; }`);
2541
+ }
1626
2542
  }
1627
- };
2543
+ }
1628
2544
  }
1629
- function getDisplayName(element) {
1630
- const type = element.type;
1631
- return type?.displayName || type?.name;
2545
+ function deviceStylesCss(styles, mode) {
2546
+ return Object.entries(styles).filter(([, rules]) => rules.length).map(([device, rules]) => {
2547
+ const query = device === "mobile" ? "@media (max-width: 480px)" : `@media (min-width: ${mode === "document" ? 1 : 481}px)`;
2548
+ return `${query} { ${rules.join(" ")} }`;
2549
+ }).join("\n");
1632
2550
  }
1633
- function collectChildren(node) {
1634
- const result = [];
1635
- React__default.default.Children.forEach(node, (child) => {
1636
- if (React__default.default.isValidElement(child)) {
1637
- result.push(child);
1638
- }
1639
- });
1640
- return result;
2551
+ function deviceVisibilityCss(mode) {
2552
+ if (mode === "document") return "";
2553
+ const extra = mode === "email" ? "max-height: 0px; overflow: hidden; " : "";
2554
+ const reset = mode === "email" ? "@media (min-width: 0px) { .hide-default__display-block { display: block !important; mso-hide: unset !important; } .hide-default__display-table { display: table !important; mso-hide: unset !important; } }\n" : "";
2555
+ return reset + `@media (max-width: 480px) { .hide-mobile { ${extra}display: none !important; } }
2556
+ @media (min-width: 481px) { .hide-desktop { ${extra}display: none !important; } }`;
2557
+ }
2558
+ var schemaVersion = exporters.schemaVersion ?? 24;
2559
+ function unwrapForJson(element2) {
2560
+ try {
2561
+ return unwrapRoot(element2);
2562
+ } catch (cause) {
2563
+ const detail = cause instanceof Error ? cause.message : String(cause);
2564
+ throw new Error(
2565
+ `[Unlayer] renderToJson: could not unwrap <${componentName(element2) || "wrapper"}>. ${UNWRAP_ADVICE} (${detail})`
2566
+ );
2567
+ }
1641
2568
  }
2569
+ var collectChildren = (node) => React4__default.default.Children.toArray(node).filter(React4__default.default.isValidElement);
1642
2570
  function extractSemanticProps(props, extraKeys = []) {
1643
2571
  const internalKeys = /* @__PURE__ */ new Set([
1644
2572
  "children",
@@ -1653,6 +2581,8 @@ function extractSemanticProps(props, extraKeys = []) {
1653
2581
  "_config",
1654
2582
  "config",
1655
2583
  "previewText",
2584
+ "fonts",
2585
+ "lang",
1656
2586
  "layout",
1657
2587
  "collection",
1658
2588
  ...extraKeys
@@ -1669,68 +2599,313 @@ function nextCounter(counters, key) {
1669
2599
  counters[key] = (counters[key] || 0) + 1;
1670
2600
  return counters[key];
1671
2601
  }
1672
- function callHead(head, values, bodyValues, displayMode, headConfig, styles, scripts, tags) {
1673
- if (!head) return;
1674
- const headArgs = [
1675
- values,
1676
- bodyValues,
1677
- {
1678
- displayMode,
1679
- isViewer: false,
1680
- variant: null,
1681
- type: "rows",
1682
- headConfig
1683
- }
1684
- ];
1685
- const css = head.css?.(...headArgs);
1686
- if (css) styles.push(css);
1687
- const js = head.js?.(...headArgs);
2602
+ function toContentType(displayName) {
2603
+ if (displayName === "Paragraph") return "text";
2604
+ return displayName.toLowerCase();
2605
+ }
2606
+ function makeId(prefix, counter) {
2607
+ return `${prefix}_${counter}`;
2608
+ }
2609
+ function extractTextFromTextJson(textJson) {
2610
+ try {
2611
+ const parsed = JSON.parse(textJson);
2612
+ if (parsed.__html) return parsed.__html;
2613
+ const root = parsed?.root;
2614
+ if (!root?.children) return "";
2615
+ const paragraphs = [];
2616
+ for (const paragraph of root.children) {
2617
+ if (!paragraph?.children) continue;
2618
+ const parts2 = [];
2619
+ for (const node of paragraph.children) {
2620
+ if (node?.text) parts2.push(node.text);
2621
+ }
2622
+ paragraphs.push(parts2.join(""));
2623
+ }
2624
+ return paragraphs.join("\n");
2625
+ } catch {
2626
+ return "";
2627
+ }
2628
+ }
2629
+ function processItem(element2, counters, layout = {}, metaName) {
2630
+ const componentType = element2.type;
2631
+ const config = componentType[UNLAYER_CONFIG_KEY];
2632
+ if (!config && componentType?.[KEPT]) {
2633
+ return processItem(componentType[KEPT], counters, layout, (componentType.displayName || componentType.name || "component").toLowerCase());
2634
+ }
2635
+ if (!config) {
2636
+ const name2 = componentName(element2) || "Unknown";
2637
+ throw new Error(
2638
+ `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
2639
+ );
2640
+ }
2641
+ const { name, defaultValues, propMapper, slug } = config;
2642
+ const contentType = config.contentType ?? toContentType(name);
2643
+ const metaBase = `u_content_${metaName ?? config.metaName ?? contentType}`;
2644
+ const count = nextCounter(counters, metaBase);
2645
+ const id = makeId(metaBase, count);
2646
+ const { children, ...restProps } = element2.props;
2647
+ const mappedValues = propMapper({ children, ...restProps });
2648
+ const finalValues = mergeValues(defaultValues, mappedValues);
2649
+ if (contentType === "text" && finalValues.textJson) {
2650
+ finalValues.text = extractTextFromTextJson(finalValues.textJson);
2651
+ delete finalValues.textJson;
2652
+ }
2653
+ const values = {
2654
+ containerPadding: "10px",
2655
+ ...finalValues,
2656
+ _meta: {
2657
+ htmlID: id,
2658
+ htmlClassNames: metaBase,
2659
+ ...finalValues._meta || {}
2660
+ },
2661
+ selectable: true,
2662
+ draggable: true,
2663
+ duplicatable: true,
2664
+ deletable: true,
2665
+ hideable: true
2666
+ };
2667
+ const itemSrc = values.src;
2668
+ if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
2669
+ const availableWidth = contentSlotWidth({
2670
+ ...layout,
2671
+ containerPadding: values.containerPadding
2672
+ });
2673
+ values.src = pinImageSrc(itemSrc, availableWidth);
2674
+ }
2675
+ return slug ? { type: contentType, slug, values } : { type: contentType, values };
2676
+ }
2677
+ function processColumn(element2, counters, layout = {}) {
2678
+ const count = nextCounter(counters, "u_column");
2679
+ const id = makeId("u_column", count);
2680
+ const semanticProps = extractSemanticProps(element2.props);
2681
+ const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
2682
+ const values = mergeValues(COLUMN_DEFAULTS, mapped);
2683
+ const valuesWithMeta = {
2684
+ ...values,
2685
+ _meta: {
2686
+ htmlID: id,
2687
+ htmlClassNames: "u_column",
2688
+ ...values._meta || {}
2689
+ }
2690
+ };
2691
+ const contents = [];
2692
+ const children = collectChildren(element2.props.children);
2693
+ const itemLayout = { ...layout, columnValues: valuesWithMeta };
2694
+ for (const child of children) {
2695
+ contents.push(processItem(child, counters, itemLayout));
2696
+ }
2697
+ return { contents, values: valuesWithMeta };
2698
+ }
2699
+ function processRow(element2, counters, parentLayout = {}) {
2700
+ const count = nextCounter(counters, "u_row");
2701
+ const id = makeId("u_row", count);
2702
+ const { layout, cells: propsCells } = element2.props;
2703
+ let cells;
2704
+ if (layout) {
2705
+ cells = layout.cells;
2706
+ } else if (propsCells) {
2707
+ cells = propsCells;
2708
+ } else {
2709
+ const columnCount = Math.max(
2710
+ 1,
2711
+ collectChildren(element2.props.children).filter(
2712
+ (child) => getDisplayName(child) === "Column"
2713
+ ).length
2714
+ );
2715
+ cells = Array(columnCount).fill(1);
2716
+ }
2717
+ const semanticProps = extractSemanticProps(element2.props, ["layout"]);
2718
+ const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
2719
+ const values = mergeValues(ROW_DEFAULTS, mapped);
2720
+ const valuesWithMeta = {
2721
+ ...values,
2722
+ columns: values.columns ?? false,
2723
+ cells,
2724
+ _meta: {
2725
+ htmlID: id,
2726
+ htmlClassNames: "u_row",
2727
+ ...values._meta || {}
2728
+ },
2729
+ selectable: true,
2730
+ draggable: true,
2731
+ duplicatable: true,
2732
+ deletable: true,
2733
+ hideable: true
2734
+ };
2735
+ const columns = [];
2736
+ const children = collectChildren(element2.props.children);
2737
+ const columnLayout = {
2738
+ bodyValues: parentLayout.bodyValues,
2739
+ rowValues: valuesWithMeta,
2740
+ rowCells: cells
2741
+ };
2742
+ let columnIndex = 0;
2743
+ for (const child of children) {
2744
+ const name = getDisplayName(child);
2745
+ if (name === "Column") {
2746
+ columns.push(
2747
+ processColumn(child, counters, { ...columnLayout, columnIndex })
2748
+ );
2749
+ columnIndex += 1;
2750
+ } else {
2751
+ console.warn(
2752
+ `[Unlayer] renderToJson: <${componentName(child) ?? String(child.type)}> is not a valid Row child. Only <Column> is allowed.`
2753
+ );
2754
+ }
2755
+ }
2756
+ return { cells, columns, values: valuesWithMeta };
2757
+ }
2758
+ function processBody(element2, counters) {
2759
+ const id = "u_body";
2760
+ const semanticProps = extractSemanticProps(element2.props);
2761
+ const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
2762
+ const values = mergeValues(BODY_DEFAULTS, mapped);
2763
+ if ("textDirection" in values) {
2764
+ const direction = textDirectionOf(values.textDirection);
2765
+ if (direction) values.textDirection = direction;
2766
+ else delete values.textDirection;
2767
+ }
2768
+ const previewText = element2.props.previewText;
2769
+ if (previewText !== void 0) {
2770
+ values.preheaderText = previewText;
2771
+ }
2772
+ const valuesWithMeta = {
2773
+ ...values,
2774
+ _meta: {
2775
+ htmlID: id,
2776
+ htmlClassNames: "u_body",
2777
+ ...values._meta || {}
2778
+ }
2779
+ };
2780
+ const rows = [];
2781
+ const children = collectChildren(element2.props.children);
2782
+ for (const child of children) {
2783
+ const name = getDisplayName(child);
2784
+ if (name === "Row") {
2785
+ rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
2786
+ } else {
2787
+ console.warn(
2788
+ `[Unlayer] renderToJson: <${componentName(child) ?? String(child.type)}> is not a valid Body child. Only <Row> is allowed.`
2789
+ );
2790
+ }
2791
+ }
2792
+ return {
2793
+ rows,
2794
+ values: valuesWithMeta
2795
+ };
2796
+ }
2797
+ function renderRowToJson(element2) {
2798
+ const displayName = getDisplayName(element2);
2799
+ if (displayName !== "Row") {
2800
+ throw new Error(
2801
+ `[Unlayer] renderRowToJson: Element must be <Row>, but got <${componentName(element2) || "unknown"}>. For full designs, use renderToJson instead.`
2802
+ );
2803
+ }
2804
+ const counters = {};
2805
+ return withRenderScope(() => processRow(normalizedChildren(element2).find((c) => React4__default.default.isValidElement(c)) ?? element2, counters));
2806
+ }
2807
+ function renderToJson(element2) {
2808
+ return withRenderScope(() => designJson(element2));
2809
+ }
2810
+ function designJson(element2) {
2811
+ element2 = unwrapForJson(element2);
2812
+ const displayName = getDisplayName(element2);
2813
+ if (displayName && ROOT_NAMES.has(displayName)) element2 = normalizedRoot(element2);
2814
+ if (!displayName || !ROOT_NAMES.has(displayName)) {
2815
+ throw new Error(
2816
+ `[Unlayer] renderToJson: Root element must be <Body>, <Email>, <Page>, or <Document>, but got <${componentName(element2) || "unknown"}>. Wrap your content: <Body><Row><Column>...</Column></Row></Body>`
2817
+ );
2818
+ }
2819
+ const counters = {};
2820
+ const body = processBody(element2, counters);
2821
+ return {
2822
+ counters,
2823
+ body,
2824
+ schemaVersion
2825
+ };
2826
+ }
2827
+
2828
+ // src/utils/extract-head.ts
2829
+ function ensureMeta(values, type, index = 0) {
2830
+ return {
2831
+ ...values,
2832
+ _meta: {
2833
+ htmlID: `${["row", "column"].includes(type) ? "u" : "u_content"}_${type.toLowerCase()}_${index + 1}`,
2834
+ htmlClassNames: `u_content_${type.toLowerCase()}`,
2835
+ ...values._meta || {}
2836
+ }
2837
+ };
2838
+ }
2839
+ var collectChildren2 = (node) => React4__default.default.Children.toArray(node).filter(React4__default.default.isValidElement);
2840
+ function callHead(head, values, bodyValues, displayMode, headConfig, styles, scripts, tags) {
2841
+ if (!head) return;
2842
+ const headArgs = [
2843
+ values,
2844
+ bodyValues,
2845
+ {
2846
+ displayMode,
2847
+ isViewer: false,
2848
+ variant: null,
2849
+ type: "rows",
2850
+ headConfig
2851
+ }
2852
+ ];
2853
+ const css2 = head.css?.(...headArgs);
2854
+ if (css2) styles.push(css2);
2855
+ const js = head.js?.(...headArgs);
1688
2856
  if (js) scripts.push(js);
1689
2857
  const headTags = head.tags?.(...headArgs);
1690
2858
  if (headTags) tags.push(...headTags);
1691
2859
  }
1692
- function walkItem(element, bodyValues, displayMode, headConfig, counters, styles, scripts, tags) {
1693
- const componentType = element.type;
2860
+ function walkItem(element2, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2861
+ const componentType = element2.type;
1694
2862
  const config = componentType[UNLAYER_CONFIG_KEY];
1695
- if (!config) return;
2863
+ if (!config) {
2864
+ if (componentType?.[KEPT]) nextCounter(counters, `u_content_${(componentType.displayName || componentType.name || "component").toLowerCase()}`);
2865
+ return;
2866
+ }
1696
2867
  const { name, defaultValues, propMapper } = config;
1697
- const { children, ...restProps } = element.props;
2868
+ const { children, ...restProps } = element2.props;
1698
2869
  const mappedValues = propMapper({ children, ...restProps });
1699
2870
  const finalValues = mergeValues(defaultValues, mappedValues);
1700
2871
  const contentType = config.metaName ?? name.toLowerCase();
1701
2872
  const count = nextCounter(counters, `u_content_${contentType}`);
1702
2873
  const valuesWithMeta = ensureMeta(finalValues, contentType, count - 1);
2874
+ collectDeviceStyles(valuesWithMeta, "contents", name, displayMode, deviceStyles);
1703
2875
  const head = config.head ?? exporters.heads[name];
1704
2876
  callHead(head, valuesWithMeta, bodyValues, displayMode, headConfig, styles, scripts, tags);
1705
2877
  }
1706
- function walkColumn(element, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags) {
1707
- const semanticProps = extractSemanticProps(element.props);
2878
+ function walkColumn(element2, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2879
+ const semanticProps = extractSemanticProps(element2.props);
1708
2880
  const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
1709
2881
  const count = nextCounter(counters, "u_column");
1710
2882
  const columnValues = ensureMeta(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
2883
+ collectDeviceStyles(columnValues, "columns", "Column", displayMode, deviceStyles);
1711
2884
  const columnHead = exporters.heads["Column"];
1712
2885
  callHead(columnHead, columnValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1713
- const children = collectChildren(element.props.children);
2886
+ const children = collectChildren2(element2.props.children);
1714
2887
  for (const child of children) {
1715
- walkItem(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags);
2888
+ walkItem(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1716
2889
  }
1717
2890
  }
1718
- function walkRow(element, bodyValues, displayMode, headConfig, counters, styles, scripts, tags) {
1719
- const semanticProps = extractSemanticProps(element.props, ["layout"]);
2891
+ function walkRow(element2, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2892
+ const semanticProps = extractSemanticProps(element2.props, ["layout"]);
1720
2893
  const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
1721
2894
  const count = nextCounter(counters, "u_row");
1722
2895
  const rowValues = ensureMeta(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
2896
+ collectDeviceStyles(rowValues, "rows", "Row", displayMode, deviceStyles);
1723
2897
  const rowHead = exporters.heads["Row"];
1724
2898
  callHead(rowHead, rowValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1725
- const children = collectChildren(element.props.children);
2899
+ const children = collectChildren2(element2.props.children);
1726
2900
  for (const child of children) {
1727
2901
  const name = getDisplayName(child);
1728
2902
  if (name === "Column") {
1729
- walkColumn(child, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags);
2903
+ walkColumn(child, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1730
2904
  }
1731
2905
  }
1732
2906
  }
1733
- function extractHeadFromTree(element, options) {
2907
+ function extractHeadFromTree(element2, options) {
2908
+ element2 = normalizedRoot(element2);
1734
2909
  const { displayMode } = options;
1735
2910
  const headConfig = {
1736
2911
  hasFeature: options.headConfig?.hasFeature ?? (() => false),
@@ -1740,18 +2915,22 @@ function extractHeadFromTree(element, options) {
1740
2915
  const scripts = [];
1741
2916
  const tags = [];
1742
2917
  const counters = {};
1743
- const semanticProps = extractSemanticProps(element.props);
2918
+ const deviceStyles = {};
2919
+ const semanticProps = extractSemanticProps(element2.props);
1744
2920
  const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
1745
2921
  const bodyValues = ensureMeta(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1746
2922
  const bodyHead = exporters.heads["Body"];
1747
2923
  callHead(bodyHead, bodyValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1748
- const children = collectChildren(element.props.children);
2924
+ const children = collectChildren2(element2.props.children);
1749
2925
  for (const child of children) {
1750
2926
  const name = getDisplayName(child);
1751
2927
  if (name === "Row") {
1752
- walkRow(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags);
2928
+ walkRow(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1753
2929
  }
1754
2930
  }
2931
+ styles.unshift(deviceVisibilityCss(displayMode));
2932
+ const overrides = deviceStylesCss(deviceStyles, displayMode);
2933
+ if (overrides) styles.push(overrides);
1755
2934
  const uniqueTags = [...new Set(tags.filter(Boolean))];
1756
2935
  return {
1757
2936
  css: styles.filter(Boolean).join("\n"),
@@ -1771,7 +2950,7 @@ function emailLayout(args) {
1771
2950
  fontLinks && `<!--[if !mso]><!-->${fontLinks}<!--<![endif]-->`
1772
2951
  ].filter(Boolean).join("\n ");
1773
2952
  return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1774
- <html${dir} xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
2953
+ <html${dir}${args.lang || ""} xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
1775
2954
  <head>
1776
2955
  <!--[if gte mso 9]>
1777
2956
  <xml>
@@ -1795,7 +2974,7 @@ function documentLayout(args) {
1795
2974
  const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
1796
2975
  const head = [titleTag, tagLines, styleTag, scriptTag, fontLinks].filter(Boolean).join("\n ");
1797
2976
  return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1798
- <html${dir} xmlns="http://www.w3.org/1999/xhtml">
2977
+ <html${dir}${args.lang || ""} xmlns="http://www.w3.org/1999/xhtml">
1799
2978
  <head>
1800
2979
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1801
2980
  ${head}
@@ -1808,7 +2987,7 @@ function webLayout(args) {
1808
2987
  const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
1809
2988
  const head = [titleTag, fontLinks, tagLines, styleTag, scriptTag].filter(Boolean).join("\n ");
1810
2989
  return `<!doctype html>
1811
- <html${dir}>
2990
+ <html${dir}${args.lang || ""}>
1812
2991
  <head>
1813
2992
  <meta charset="utf-8">
1814
2993
  <meta http-equiv="x-ua-compatible" content="ie=edge">
@@ -1829,20 +3008,25 @@ var MODE_BY_WRAPPER = {
1829
3008
  Page: "web",
1830
3009
  Document: "document"
1831
3010
  };
1832
- function resolveDisplayMode(element, mergedConfig) {
1833
- const displayName = element.type?.displayName;
3011
+ function resolveConfig(element2, config) {
3012
+ const props = element2.props;
3013
+ const textDirection = textDirectionOf(props.textDirection ?? props.values?.textDirection);
3014
+ return { ...DEFAULT_CONFIG, ...textDirection !== void 0 ? { textDirection } : {}, ...config };
3015
+ }
3016
+ function resolveDisplayMode(element2, mergedConfig) {
3017
+ const displayName = element2.type?.displayName;
1834
3018
  const wrapperMode = displayName ? MODE_BY_WRAPPER[displayName] : void 0;
1835
- return wrapperMode ?? element.props.mode ?? mergedConfig.mode ?? "web";
3019
+ return wrapperMode ?? element2.props.mode ?? mergedConfig.mode ?? "web";
1836
3020
  }
1837
- function renderBody(element, config) {
1838
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
3021
+ function renderBody(element2, config, name = "renderToHtml") {
3022
+ const mergedConfig = resolveConfig(element2, config);
1839
3023
  try {
1840
- const enriched = React__default.default.cloneElement(element, { config: mergedConfig });
1841
- return ReactDOMServer.renderToStaticMarkup(enriched);
3024
+ const enriched = React4__default.default.cloneElement(element2, { config: mergedConfig });
3025
+ return renderToStaticMarkup(enriched);
1842
3026
  } catch (error) {
1843
3027
  const message = error instanceof Error ? error.message : String(error);
1844
3028
  throw new Error(
1845
- `[Unlayer] renderToHtml failed: ${message}
3029
+ `[Unlayer] ${name} failed: ${message}
1846
3030
  Tip: Ensure your tree uses Body > Row > Column > Item structure.`
1847
3031
  );
1848
3032
  }
@@ -1854,30 +3038,41 @@ function stripOuterDiv(html) {
1854
3038
  function escapeForHtml(text) {
1855
3039
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1856
3040
  }
1857
- function renderToHtml(element, options) {
1858
- const { title, fonts = [], ...config } = options ?? {};
1859
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
1860
- const displayMode = resolveDisplayMode(element, mergedConfig);
1861
- const wrapperName = element.type?.displayName;
3041
+ function renderToHtml(element2, options) {
3042
+ return withRenderScope(() => htmlDocument(element2, options));
3043
+ }
3044
+ function styleText(css2) {
3045
+ return css2.replace(/<\/(style)/gi, "<\\/$1");
3046
+ }
3047
+ function htmlDocument(element2, options) {
3048
+ const { title, lang: optionLang, fonts: optionFonts = [], ...config } = options ?? {};
3049
+ element2 = htmlRoot(element2, "renderToHtml");
3050
+ const rootFonts = element2.props?.fonts ?? [];
3051
+ const fonts = [...rootFonts, ...optionFonts].filter((font, i, all) => font?.url && all.findIndex((f) => f?.url === font.url) === i);
3052
+ const lang = optionLang ?? element2.props.lang;
3053
+ const mergedConfig = resolveConfig(element2, config);
3054
+ const displayMode = resolveDisplayMode(element2, mergedConfig);
3055
+ const wrapperName = element2.type?.displayName;
1862
3056
  const isUnlayerWrapper = wrapperName === "Body" || (wrapperName ? wrapperName in MODE_BY_WRAPPER : false);
1863
- const rawBody = renderBody(element, config);
3057
+ const rawBody = renderBody(element2, config);
1864
3058
  const body = isUnlayerWrapper ? stripOuterDiv(rawBody) : rawBody;
1865
- const { css, js, tags } = extractHeadFromTree(element, {
3059
+ const { css: css2, js, tags } = extractHeadFromTree(element2, {
1866
3060
  displayMode,
1867
3061
  headConfig: mergedConfig.headConfig
1868
3062
  });
1869
3063
  const layoutArgs = {
3064
+ lang: lang ? ` lang="${escapeForHtml(String(lang))}"` : "",
1870
3065
  dir: mergedConfig.textDirection ? ` dir="${escapeForHtml(String(mergedConfig.textDirection))}"` : "",
1871
3066
  titleTag: title ? `<title>${escapeForHtml(title.trim())}</title>` : "",
1872
- styleTag: css ? `<style type="text/css">
1873
- ${css}
3067
+ styleTag: css2 ? `<style type="text/css">
3068
+ ${styleText(css2)}
1874
3069
  </style>` : "",
1875
3070
  scriptTag: js ? `<script type="application/javascript">
1876
3071
  ${js}
1877
3072
  </script>` : "",
1878
3073
  tagLines: tags.join("\n"),
1879
3074
  fontLinks: fonts.filter((font) => font?.url).map(
1880
- (font) => `<link href="${escapeForHtml(font.url)}" rel="stylesheet" type="text/css">`
3075
+ (font) => `<link href="${escapeForHtml(String(font.url))}" rel="stylesheet" type="text/css">`
1881
3076
  ).join(""),
1882
3077
  body
1883
3078
  };
@@ -1885,21 +3080,29 @@ ${js}
1885
3080
  if (displayMode === "document") return documentLayout(layoutArgs);
1886
3081
  return webLayout(layoutArgs);
1887
3082
  }
1888
- function renderToPlainText(element, config) {
1889
- const html = renderBody(element, config);
3083
+ function renderToPlainText(element2, config) {
3084
+ const html = withRenderScope(() => {
3085
+ const root = htmlRoot(element2, "renderToPlainText");
3086
+ normalizedRoot(root);
3087
+ return renderBody(root, config, "renderToPlainText");
3088
+ });
1890
3089
  return htmlToPlainText(html);
1891
3090
  }
1892
- function renderToHtmlParts(element, config) {
1893
- const body = renderBody(element, config);
1894
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
1895
- const displayMode = resolveDisplayMode(element, mergedConfig);
1896
- const { css, js, tags } = extractHeadFromTree(element, {
3091
+ function renderToHtmlParts(element2, config) {
3092
+ return withRenderScope(() => htmlParts(element2, config));
3093
+ }
3094
+ function htmlParts(element2, config) {
3095
+ element2 = htmlRoot(element2, "renderToHtmlParts");
3096
+ const body = renderBody(element2, config, "renderToHtmlParts");
3097
+ const mergedConfig = resolveConfig(element2, config);
3098
+ const displayMode = resolveDisplayMode(element2, mergedConfig);
3099
+ const { css: css2, js, tags } = extractHeadFromTree(element2, {
1897
3100
  displayMode,
1898
3101
  headConfig: mergedConfig.headConfig
1899
3102
  });
1900
3103
  const headParts = [];
1901
- if (css) {
1902
- headParts.push(`<style>${css}</style>`);
3104
+ if (css2) {
3105
+ headParts.push(`<style>${styleText(css2)}</style>`);
1903
3106
  }
1904
3107
  if (js) {
1905
3108
  headParts.push(`<script>${js}</script>`);
@@ -1910,291 +3113,11 @@ function renderToHtmlParts(element, config) {
1910
3113
  return {
1911
3114
  head: headParts.join("\n"),
1912
3115
  body,
1913
- css,
3116
+ css: css2,
1914
3117
  js,
1915
3118
  tags
1916
3119
  };
1917
3120
  }
1918
- var schemaVersion = exporters.schemaVersion ?? 24;
1919
- function getDisplayName2(element) {
1920
- const type = element.type;
1921
- return type?.displayName || type?.name;
1922
- }
1923
- var VALID_ROOTS = /* @__PURE__ */ new Set(["Body", "Email", "Page", "Document"]);
1924
- function unwrapRoot(element) {
1925
- let current = element;
1926
- for (let depth = 0; depth < 10; depth++) {
1927
- const name = getDisplayName2(current);
1928
- if (name && VALID_ROOTS.has(name)) break;
1929
- const type = current.type;
1930
- const isPlainFunctionComponent = typeof type === "function" && !type.prototype?.isReactComponent;
1931
- if (!isPlainFunctionComponent) break;
1932
- let produced;
1933
- try {
1934
- produced = type({ ...current.props });
1935
- } catch (cause) {
1936
- const detail = cause instanceof Error ? cause.message : String(cause);
1937
- throw new Error(
1938
- `[Unlayer] renderToJson: could not unwrap <${name || "wrapper"}>. A wrapper must be a plain component that synchronously returns a root (<Email>, <Page>, <Document>, or <Body>) and uses no React hooks. Pass the root element directly \u2014 e.g. renderToJson(<Email>\u2026</Email>). (${detail})`
1939
- );
1940
- }
1941
- if (!React__default.default.isValidElement(produced)) break;
1942
- current = produced;
1943
- }
1944
- return current;
1945
- }
1946
- function collectChildren2(node) {
1947
- const result = [];
1948
- React__default.default.Children.forEach(node, (child) => {
1949
- if (React__default.default.isValidElement(child)) {
1950
- result.push(child);
1951
- }
1952
- });
1953
- return result;
1954
- }
1955
- function extractSemanticProps2(props, extraKeys = []) {
1956
- const internalKeys = /* @__PURE__ */ new Set([
1957
- "children",
1958
- "mode",
1959
- "className",
1960
- "style",
1961
- "index",
1962
- "colIndex",
1963
- "cells",
1964
- "bodyValues",
1965
- "rowValues",
1966
- "_config",
1967
- "config",
1968
- "previewText",
1969
- "layout",
1970
- "collection",
1971
- ...extraKeys
1972
- ]);
1973
- const result = {};
1974
- for (const [key, value] of Object.entries(props)) {
1975
- if (!internalKeys.has(key) && value !== void 0) {
1976
- result[key] = value;
1977
- }
1978
- }
1979
- return result;
1980
- }
1981
- function nextCounter2(counters, key) {
1982
- counters[key] = (counters[key] || 0) + 1;
1983
- return counters[key];
1984
- }
1985
- function toContentType(displayName) {
1986
- if (displayName === "Paragraph") return "text";
1987
- return displayName.toLowerCase();
1988
- }
1989
- function makeId(prefix, counter) {
1990
- return `${prefix}_${counter}`;
1991
- }
1992
- function extractTextFromTextJson(textJson) {
1993
- try {
1994
- const parsed = JSON.parse(textJson);
1995
- if (parsed.__html) return parsed.__html;
1996
- const root = parsed?.root;
1997
- if (!root?.children) return "";
1998
- const paragraphs = [];
1999
- for (const paragraph of root.children) {
2000
- if (!paragraph?.children) continue;
2001
- const parts = [];
2002
- for (const node of paragraph.children) {
2003
- if (node?.text) parts.push(node.text);
2004
- }
2005
- paragraphs.push(parts.join(""));
2006
- }
2007
- return paragraphs.join("\n");
2008
- } catch {
2009
- return "";
2010
- }
2011
- }
2012
- function processItem(element, counters, layout = {}) {
2013
- const componentType = element.type;
2014
- const config = componentType[UNLAYER_CONFIG_KEY];
2015
- if (!config) {
2016
- const name2 = getDisplayName2(element) || "Unknown";
2017
- throw new Error(
2018
- `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
2019
- );
2020
- }
2021
- const { name, defaultValues, propMapper, slug } = config;
2022
- const contentType = config.contentType ?? toContentType(name);
2023
- const metaBase = `u_content_${config.metaName ?? contentType}`;
2024
- const count = nextCounter2(counters, metaBase);
2025
- const id = makeId(metaBase, count);
2026
- const { children, ...restProps } = element.props;
2027
- const mappedValues = propMapper({ children, ...restProps });
2028
- const finalValues = mergeValues(defaultValues, mappedValues);
2029
- if (contentType === "text" && finalValues.textJson) {
2030
- finalValues.text = extractTextFromTextJson(finalValues.textJson);
2031
- delete finalValues.textJson;
2032
- }
2033
- const values = {
2034
- containerPadding: "10px",
2035
- ...finalValues,
2036
- _meta: {
2037
- htmlID: id,
2038
- htmlClassNames: metaBase,
2039
- ...finalValues._meta || {}
2040
- },
2041
- selectable: true,
2042
- draggable: true,
2043
- duplicatable: true,
2044
- deletable: true,
2045
- hideable: true
2046
- };
2047
- const itemSrc = values.src;
2048
- if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
2049
- const availableWidth = contentSlotWidth({
2050
- ...layout,
2051
- containerPadding: values.containerPadding
2052
- });
2053
- values.src = pinImageSrc(itemSrc, availableWidth);
2054
- }
2055
- return slug ? { type: contentType, slug, values } : { type: contentType, values };
2056
- }
2057
- function processColumn(element, counters, layout = {}) {
2058
- const count = nextCounter2(counters, "u_column");
2059
- const id = makeId("u_column", count);
2060
- const semanticProps = extractSemanticProps2(element.props);
2061
- const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
2062
- const values = mergeValues(COLUMN_DEFAULTS, mapped);
2063
- const valuesWithMeta = {
2064
- ...values,
2065
- _meta: {
2066
- htmlID: id,
2067
- htmlClassNames: "u_column",
2068
- ...values._meta || {}
2069
- }
2070
- };
2071
- const contents = [];
2072
- const children = collectChildren2(element.props.children);
2073
- const itemLayout = { ...layout, columnValues: valuesWithMeta };
2074
- for (const child of children) {
2075
- contents.push(processItem(child, counters, itemLayout));
2076
- }
2077
- return { contents, values: valuesWithMeta };
2078
- }
2079
- function processRow(element, counters, parentLayout = {}) {
2080
- const count = nextCounter2(counters, "u_row");
2081
- const id = makeId("u_row", count);
2082
- const { layout, cells: propsCells } = element.props;
2083
- let cells;
2084
- if (layout) {
2085
- cells = layout.cells;
2086
- } else if (propsCells) {
2087
- cells = propsCells;
2088
- } else {
2089
- const columnCount = Math.max(
2090
- 1,
2091
- collectChildren2(element.props.children).filter(
2092
- (child) => getDisplayName2(child) === "Column"
2093
- ).length
2094
- );
2095
- cells = Array(columnCount).fill(1);
2096
- }
2097
- const semanticProps = extractSemanticProps2(element.props, ["layout"]);
2098
- const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
2099
- const values = mergeValues(ROW_DEFAULTS, mapped);
2100
- const valuesWithMeta = {
2101
- ...values,
2102
- columns: values.columns ?? false,
2103
- cells,
2104
- _meta: {
2105
- htmlID: id,
2106
- htmlClassNames: "u_row",
2107
- ...values._meta || {}
2108
- },
2109
- selectable: true,
2110
- draggable: true,
2111
- duplicatable: true,
2112
- deletable: true,
2113
- hideable: true
2114
- };
2115
- const columns = [];
2116
- const children = collectChildren2(element.props.children);
2117
- const columnLayout = {
2118
- bodyValues: parentLayout.bodyValues,
2119
- rowValues: valuesWithMeta,
2120
- rowCells: cells
2121
- };
2122
- let columnIndex = 0;
2123
- for (const child of children) {
2124
- const name = getDisplayName2(child);
2125
- if (name === "Column") {
2126
- columns.push(
2127
- processColumn(child, counters, { ...columnLayout, columnIndex })
2128
- );
2129
- columnIndex += 1;
2130
- } else {
2131
- console.warn(
2132
- `[Unlayer] renderToJson: <${name}> is not a valid Row child. Only <Column> is allowed.`
2133
- );
2134
- }
2135
- }
2136
- return { cells, columns, values: valuesWithMeta };
2137
- }
2138
- function processBody(element, counters) {
2139
- const id = "u_body";
2140
- const semanticProps = extractSemanticProps2(element.props);
2141
- const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
2142
- const values = mergeValues(BODY_DEFAULTS, mapped);
2143
- const previewText = element.props.previewText;
2144
- if (previewText !== void 0) {
2145
- values.preheaderText = previewText;
2146
- }
2147
- const valuesWithMeta = {
2148
- ...values,
2149
- _meta: {
2150
- htmlID: id,
2151
- htmlClassNames: "u_body",
2152
- ...values._meta || {}
2153
- }
2154
- };
2155
- const rows = [];
2156
- const children = collectChildren2(element.props.children);
2157
- for (const child of children) {
2158
- const name = getDisplayName2(child);
2159
- if (name === "Row") {
2160
- rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
2161
- } else {
2162
- console.warn(
2163
- `[Unlayer] renderToJson: <${name}> is not a valid Body child. Only <Row> is allowed.`
2164
- );
2165
- }
2166
- }
2167
- return {
2168
- rows,
2169
- values: valuesWithMeta
2170
- };
2171
- }
2172
- function renderRowToJson(element) {
2173
- const displayName = getDisplayName2(element);
2174
- if (displayName !== "Row") {
2175
- throw new Error(
2176
- `[Unlayer] renderRowToJson: Element must be <Row>, but got <${displayName || "unknown"}>. For full designs, use renderToJson instead.`
2177
- );
2178
- }
2179
- const counters = {};
2180
- return processRow(element, counters);
2181
- }
2182
- function renderToJson(element) {
2183
- element = unwrapRoot(element);
2184
- const displayName = getDisplayName2(element);
2185
- if (!displayName || !VALID_ROOTS.has(displayName)) {
2186
- throw new Error(
2187
- `[Unlayer] renderToJson: Root element must be <Body>, <Email>, <Page>, or <Document>, but got <${displayName || "unknown"}>. Wrap your content: <Body><Row><Column>...</Column></Row></Body>`
2188
- );
2189
- }
2190
- const counters = {};
2191
- const body = processBody(element, counters);
2192
- return {
2193
- counters,
2194
- body,
2195
- schemaVersion
2196
- };
2197
- }
2198
3121
 
2199
3122
  // src/utils/register-tool.tsx
2200
3123
  function initialValuesFromOptions(options) {
@@ -2292,6 +3215,7 @@ exports.Html = Html_default;
2292
3215
  exports.Image = Image_default;
2293
3216
  exports.Menu = Menu_default;
2294
3217
  exports.Page = Page;
3218
+ exports.PageBreak = PageBreak_default;
2295
3219
  exports.Paragraph = Paragraph_default;
2296
3220
  exports.Row = Row_default;
2297
3221
  exports.Social = Social_default;