@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.js CHANGED
@@ -1,9 +1,349 @@
1
- import { ButtonDefaults, DividerDefaults, HeadingDefaults, HtmlDefaults, ImageDefaults, MenuDefaults, ParagraphDefaults, SocialDefaults, TableDefaults, VideoDefaults, BodyDefaults, RowDefaults, ColumnDefaults, ButtonExporters, DividerExporters, HeadingExporters, HtmlExporters, ImageExporters, MenuExporters, ParagraphExporters, SocialExporters, TableExporters, VideoExporters, schemaVersion as schemaVersion$1, RowExporters, ContentExporters, ColumnExporters, BodyExporters, heads } from '@unlayer/exporters';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
3
- import React, { useMemo, useContext, createContext } from 'react';
4
- import ReactDOMServer, { renderToStaticMarkup } from 'react-dom/server';
1
+ import { ButtonDefaults, DividerDefaults, HeadingDefaults, HtmlDefaults, ImageDefaults, MenuDefaults, ParagraphDefaults, SocialDefaults, TableDefaults, VideoDefaults, BodyDefaults, RowDefaults, ColumnDefaults, RowExporters, ContentExporters, ColumnExporters, ButtonExporters, DividerExporters, HeadingExporters, HtmlExporters, ImageExporters, MenuExporters, ParagraphExporters, SocialExporters, TableExporters, VideoExporters, BodyExporters, schemaVersion as schemaVersion$1, heads } from '@unlayer/exporters';
2
+ import React4, { Suspense, useMemo, useContext, createContext } from 'react';
3
+ import * as defaultBuild from 'react-dom/server';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  // src/components/Button.tsx
7
+ var SHARED = Symbol.for("@unlayer/react-elements/collecting");
8
+ var frames = globalThis[SHARED] ?? (globalThis[SHARED] = []);
9
+ function collecting() {
10
+ return frames[frames.length - 1] ?? void 0;
11
+ }
12
+ function within(frame, run) {
13
+ frames.push(frame);
14
+ try {
15
+ return run();
16
+ } finally {
17
+ frames.pop();
18
+ }
19
+ }
20
+ var MARK = "unlayer-mark";
21
+ var RECORDS = Symbol.for("@unlayer/react-elements/records");
22
+ var COLLECTING_ID = "unlayer-collect-";
23
+ function marker(collection, at, edge) {
24
+ return React4.createElement(MARK, { key: `${at}${edge ?? ""}`, "data-n": collection.nonce, "data-i": at, ...edge ? { "data-e": edge } : {} });
25
+ }
26
+ var SHARED2 = Symbol.for("@unlayer/react-elements/render-builds");
27
+ var using = globalThis[SHARED2] ?? (globalThis[SHARED2] = []);
28
+ var same = (a, b) => a.renderToString === b.renderToString;
29
+ function dispatcher() {
30
+ const internals = React4;
31
+ 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;
32
+ }
33
+ var dispatchers = /* @__PURE__ */ new Map();
34
+ function dispatcherOf(build) {
35
+ if (!dispatchers.has(build)) {
36
+ let seen = null;
37
+ try {
38
+ class Probe extends React4.Component {
39
+ render() {
40
+ seen = dispatcher();
41
+ return null;
42
+ }
43
+ }
44
+ build.renderToStaticMarkup(React4.createElement(Probe));
45
+ } catch {
46
+ }
47
+ dispatchers.set(build, seen);
48
+ }
49
+ return dispatchers.get(build);
50
+ }
51
+ var second;
52
+ function secondBuild(outer) {
53
+ if (second === void 0) {
54
+ second = null;
55
+ const node = globalThis.process;
56
+ const requirers = [
57
+ // (`module`'s, read so that a bundler doesn't take it for a require of a path worked out when it runs.)
58
+ () => {
59
+ return void 0;
60
+ },
61
+ // From this module's URL (as a stack names it: webpack 4 can't parse the syntax for it), the app's script, then
62
+ // the folder Node started in; only where `react` is this React. Another React's isn't loaded (React 19's browser
63
+ // build keeps Node running).
64
+ ...[/file:\/\/\S+?(?=:\d+:\d+)/.exec(String(new Error().stack))?.[0], node?.argv?.[1], `${node?.cwd?.()}/`].map((from) => () => {
65
+ const required = node?.getBuiltinModule?.("module")?.createRequire(from);
66
+ return required?.("react")?.createElement === React4.createElement ? required : void 0;
67
+ })
68
+ ];
69
+ const ids = ["react-dom/server.bun", ...Number.parseInt(React4.version) >= 19 ? [] : ["react-dom/server.browser"], "react-dom/server.node"];
70
+ search: for (const id of ids) {
71
+ for (const requirer of requirers) {
72
+ try {
73
+ const build = requirer()?.(id);
74
+ if (build?.renderToStaticMarkup && !same(build, defaultBuild) && ![null, outer].includes(dispatcherOf(build))) {
75
+ second = build;
76
+ break search;
77
+ }
78
+ } catch {
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return second ?? void 0;
84
+ }
85
+ var buildOf = (outer) => outer === null ? void 0 : [defaultBuild, second].find((b) => !!b && dispatcherOf(b) === outer);
86
+ function withBuild(render, own = false) {
87
+ const outer = own ? null : dispatcher();
88
+ let build = current();
89
+ const theirs = !own && (using.length === 0 || collecting() !== void 0) ? buildOf(outer) : void 0;
90
+ const busy = [];
91
+ if (theirs) {
92
+ const free = (b) => !same(b, theirs) && !using.some((u) => same(u, b));
93
+ const other = free(defaultBuild) ? defaultBuild : secondBuild(outer);
94
+ if (other && free(other)) {
95
+ busy.push(theirs);
96
+ build = other;
97
+ } else build = theirs;
98
+ }
99
+ using.push(...busy, build);
100
+ try {
101
+ return render();
102
+ } catch (error) {
103
+ 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 });
104
+ throw error;
105
+ } finally {
106
+ using.length -= busy.length + 1;
107
+ }
108
+ }
109
+ function rendersHere(build) {
110
+ try {
111
+ build.renderToStaticMarkup(React4.createElement(() => null));
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+ function current() {
118
+ return using[using.length - 1] ?? defaultBuild;
119
+ }
120
+ var renderToStaticMarkup = (element2, options) => current().renderToStaticMarkup(element2, options);
121
+ var renderToString = (element2, options) => current().renderToString(element2, options);
122
+
123
+ // src/utils/unwrap-root.ts
124
+ var ROOT_NAMES = /* @__PURE__ */ new Set(["Body", "Email", "Page", "Document"]);
125
+ var CONTAINER_NAMES = /* @__PURE__ */ new Set([...ROOT_NAMES, "Row", "Column"]);
126
+ var MEMO = Symbol.for("react.memo");
127
+ var FORWARD_REF = Symbol.for("react.forward_ref");
128
+ var PROVIDER = Symbol.for("react.provider");
129
+ var CONTEXT = Symbol.for("react.context");
130
+ function getDisplayName(element2) {
131
+ return element2.type?.displayName;
132
+ }
133
+ function componentName(element2) {
134
+ const type = element2.type;
135
+ return type?.displayName || type?.name;
136
+ }
137
+ function isElementsType(type) {
138
+ return !!type?.[UNLAYER_RENDER_KEY] || CONTAINER_NAMES.has(type?.displayName);
139
+ }
140
+ function isElementsComponent(element2) {
141
+ return isElementsType(element2.type);
142
+ }
143
+ var ASYNC_TEMPLATE = "async and suspending templates aren't supported: load the data first and pass it as props";
144
+ var isThenable = (value) => typeof value?.then === "function";
145
+ var settle = (value) => {
146
+ if (isThenable(value)) value.then(void 0, () => {
147
+ });
148
+ };
149
+ function isAsyncComponent(type) {
150
+ while (type?.$$typeof === MEMO) type = type.type;
151
+ return (type?.$$typeof === FORWARD_REF ? type.render : type)?.[Symbol.toStringTag] === "AsyncFunction";
152
+ }
153
+ function callIsolated(call, prefix, providers = []) {
154
+ let result;
155
+ let error;
156
+ let ran = false;
157
+ let threw = false;
158
+ let calling = false;
159
+ const Probe = () => {
160
+ if (calling) return null;
161
+ ran = calling = true;
162
+ threw = false;
163
+ try {
164
+ result = call();
165
+ } catch (cause) {
166
+ threw = true;
167
+ error = cause;
168
+ } finally {
169
+ calling = false;
170
+ }
171
+ return null;
172
+ };
173
+ const probe = providers.reduceRight((inside, provider) => React4.cloneElement(provider, void 0, inside), React4.createElement(Probe));
174
+ renderToStaticMarkup(probe, prefix ? { identifierPrefix: prefix } : void 0);
175
+ settle(error);
176
+ settle(result);
177
+ if (threw) throw isThenable(error) ? new Error(ASYNC_TEMPLATE) : error;
178
+ if (!ran) throw new Error("the template wasn't rendered");
179
+ if (isThenable(result)) throw new Error(ASYNC_TEMPLATE);
180
+ return result;
181
+ }
182
+ function unwrapRoot(element2, why = {}) {
183
+ let current2 = element2;
184
+ const providers = [];
185
+ for (let depth = 0; ; depth++) {
186
+ if (ROOT_NAMES.has(getDisplayName(current2) ?? "")) break;
187
+ if (elementsOwn(current2)) break;
188
+ if (depth === 20) {
189
+ why.unread = "it wraps more than 20 components";
190
+ break;
191
+ }
192
+ const type = current2.type;
193
+ const props = current2.props;
194
+ if (type?.$$typeof === MEMO) {
195
+ current2 = React4.createElement(type.type, props);
196
+ continue;
197
+ }
198
+ if ((type?.$$typeof === PROVIDER || type?.$$typeof === CONTEXT) && "value" in props) {
199
+ const inside = React4.Children.toArray(props.children);
200
+ if (inside.length !== 1 || !React4.isValidElement(inside[0])) break;
201
+ providers.push(current2);
202
+ current2 = inside[0];
203
+ continue;
204
+ }
205
+ if (type === React4.Fragment || type === React4.StrictMode || type === React4.Profiler) {
206
+ const inside = React4.Children.toArray(props.children);
207
+ if (inside.length !== 1 || !React4.isValidElement(inside[0])) break;
208
+ current2 = inside[0];
209
+ continue;
210
+ }
211
+ let produced;
212
+ const prefix = depth ? `w${depth}-` : void 0;
213
+ if (type?.$$typeof === FORWARD_REF) produced = callIsolated(() => type.render({ ...props }, null), prefix, providers);
214
+ else if (typeof type === "function" && !type.prototype?.isReactComponent) produced = callIsolated(() => type({ ...props }), prefix, providers);
215
+ else {
216
+ if (type?.prototype?.isReactComponent) why.unread = `<${componentName(current2)}> is a class component, which isn't called to find it`;
217
+ break;
218
+ }
219
+ if (!React4.isValidElement(produced)) {
220
+ if (holdsRoot(produced)) why.unread = "it doesn't return a root element";
221
+ break;
222
+ }
223
+ current2 = produced;
224
+ }
225
+ if (!providers.length || !ROOT_NAMES.has(getDisplayName(current2) ?? "")) return current2;
226
+ const children = providers.reduceRight((inside, provider) => React4.cloneElement(provider, void 0, inside), current2.props.children);
227
+ return React4.cloneElement(current2, void 0, children);
228
+ }
229
+ var elementsOwn = (element2) => {
230
+ const type = element2.type;
231
+ return !!(type?.[UNLAYER_RENDER_KEY] || type?.[RECORDS]);
232
+ };
233
+ function holdsRoot(node) {
234
+ if (Array.isArray(node)) return node.some(holdsRoot);
235
+ if (!React4.isValidElement(node)) return false;
236
+ return ROOT_NAMES.has(getDisplayName(node) ?? "") || !elementsOwn(node) && holdsRoot(node.props.children);
237
+ }
238
+ var UNWRAP_ADVICE = "Pass the root element (<Email>\u2026</Email>), or a function component that returns one.";
239
+ function htmlRoot(element2, api) {
240
+ if (typeof element2.type === "string" || isElementsComponent(element2)) return element2;
241
+ let reason;
242
+ try {
243
+ const why = {};
244
+ const root = unwrapRoot(element2, why);
245
+ if (ROOT_NAMES.has(getDisplayName(root) ?? "")) return root;
246
+ if (!why.unread && !holdsRoot(root)) return element2;
247
+ reason = why.unread ?? "its root is inside other markup";
248
+ } catch (cause) {
249
+ if (cause instanceof Error && cause.message === ASYNC_TEMPLATE) throw new Error(`[Unlayer] ${api}: ${ASYNC_TEMPLATE}.`);
250
+ reason = cause instanceof Error ? cause.message : String(cause);
251
+ }
252
+ console.warn(`[Unlayer] ${api}: couldn't unwrap <${componentName(element2) || "wrapper"}> (${reason}): its root's fonts, lang, dir and phone styles are ignored. ${UNWRAP_ADVICE}`);
253
+ return element2;
254
+ }
255
+ function guard(children, collection, inContainer = false) {
256
+ return React4.Children.toArray(children).map((child) => {
257
+ const placed = guarded(child, collection, inContainer);
258
+ return placed === child && React4.isValidElement(child) ? React4.cloneElement(child) : placed;
259
+ });
260
+ }
261
+ function guarded(child, collection, inContainer) {
262
+ if (!React4.isValidElement(child)) return child;
263
+ if (isElementsType(child.type)) {
264
+ if (child.type[RECORDS]) return child;
265
+ if (!olderContainer(child.type)) return bounded(child, collection);
266
+ return recordedForOlder(child, collection, inContainer);
267
+ }
268
+ if (!child.type || typeof child.type === "object" && !("$$typeof" in child.type)) return child;
269
+ if (typeof child.type === "string") {
270
+ const inside = child.props.children;
271
+ const element2 = inside !== void 0 && hasComponent(inside) ? React4.cloneElement(child, void 0, guard(inside, collection, inContainer)) : child;
272
+ return collection.markup ? bounded(element2, collection) : element2;
273
+ }
274
+ if (child.type === React4.Fragment) {
275
+ return React4.createElement(React4.Fragment, { key: child.key }, guard(child.props.children, collection, inContainer));
276
+ }
277
+ return bounded(child, collection);
278
+ }
279
+ function bounded(child, collection) {
280
+ const at = collection.recorded.push({ kind: "boundary", element: child }) - 1;
281
+ if (isAsyncComponent(child.type)) return marker(collection, at, "f");
282
+ return React4.createElement(
283
+ Suspense,
284
+ { key: child.key ?? `b${at}`, fallback: marker(collection, at, "f") },
285
+ marker(collection, at, "o"),
286
+ child,
287
+ marker(collection, at, "c")
288
+ );
289
+ }
290
+ function olderContainer(type) {
291
+ return !/^(Row|Column)$/.test(type?.displayName ?? "") || /\b_config\b/.test(String(type));
292
+ }
293
+ function recordedForOlder(child, collection, inContainer) {
294
+ const type = child.type;
295
+ const props = child.props;
296
+ if (type.displayName === "Row" || type.displayName === "Column") {
297
+ const at2 = collection.recorded.push({ kind: type.displayName === "Row" ? "row" : "column", props }) - 1;
298
+ return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
299
+ marker(collection, at2, "o"),
300
+ guard(props.children, collection, true),
301
+ marker(collection, at2, "c")
302
+ ] }, child.key ?? `r${at2}`);
303
+ }
304
+ if (!type.__unlayerRender && !inContainer) return child;
305
+ if (!type.__unlayerRender) {
306
+ console.warn(`[Unlayer] <${type.displayName}> is a root: it can't be placed in a Row or Column, so it's left out.`);
307
+ return null;
308
+ }
309
+ const at = collection.recorded.push({ kind: "block", type: child.type, props }) - 1;
310
+ return React4.cloneElement(marker(collection, at), { key: child.key ?? `b${at}` });
311
+ }
312
+ function hasComponent(children) {
313
+ return React4.Children.toArray(children).some(
314
+ (child) => React4.isValidElement(child) && (typeof child.type === "string" || child.type === React4.Fragment ? hasComponent(child.props.children) : !isElementsType(child.type))
315
+ );
316
+ }
317
+ function Sentinel({ record, real }) {
318
+ let collected2 = !!collecting()?.blind;
319
+ if (!collected2) {
320
+ try {
321
+ collected2 = React4.useId().slice(1).startsWith(COLLECTING_ID);
322
+ } catch {
323
+ }
324
+ }
325
+ return collected2 ? record() : within(null, real);
326
+ }
327
+ var named = /* @__PURE__ */ new Map();
328
+ function whileCollecting(record, real, name) {
329
+ let type = named.get(name);
330
+ if (!type) named.set(name, type = Object.assign((props) => Sentinel(props), { displayName: name }));
331
+ return React4.createElement(type, { record, real });
332
+ }
333
+ function recordContainer(kind, props) {
334
+ const collection = collecting();
335
+ const at = collection.recorded.push({ kind, props }) - 1;
336
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
337
+ marker(collection, at, "o"),
338
+ guard(props.children, collection, true),
339
+ marker(collection, at, "c")
340
+ ] });
341
+ }
342
+ function recordBlock(type, props) {
343
+ const collection = collecting();
344
+ const at = collection.recorded.push({ kind: "block", type, props }) - 1;
345
+ return marker(collection, at);
346
+ }
7
347
 
8
348
  // src/utils/image-sizing.ts
9
349
  function toPx(value) {
@@ -15,11 +355,11 @@ function toPx(value) {
15
355
  function edges(value) {
16
356
  if (value == null) return { left: 0, right: 0 };
17
357
  if (typeof value === "number") return { left: value, right: value };
18
- const parts = String(value).trim().split(/\s+/).map((p) => parseFloat(p) || 0);
19
- if (parts.length === 1) return { left: parts[0], right: parts[0] };
20
- if (parts.length === 2 || parts.length === 3)
21
- return { left: parts[1], right: parts[1] };
22
- return { left: parts[3] || 0, right: parts[1] || 0 };
358
+ const parts2 = String(value).trim().split(/\s+/).map((p) => parseFloat(p) || 0);
359
+ if (parts2.length === 1) return { left: parts2[0], right: parts2[0] };
360
+ if (parts2.length === 2 || parts2.length === 3)
361
+ return { left: parts2[1], right: parts2[1] };
362
+ return { left: parts2[3] || 0, right: parts2[1] || 0 };
23
363
  }
24
364
  function borderEdges(border) {
25
365
  if (!border || typeof border !== "object") return { left: 0, right: 0 };
@@ -209,7 +549,7 @@ function paragraphAlign(format) {
209
549
  }
210
550
  return void 0;
211
551
  }
212
- function paragraphNodeToHtml(node, childrenHtml) {
552
+ function paragraphNodeToHtml(node, childrenHtml22) {
213
553
  const isInlineTool = !!node.isInlineTool;
214
554
  const tag = isInlineTool ? "span" : "p";
215
555
  const dir = dirAttribute(node.direction);
@@ -233,7 +573,7 @@ function paragraphNodeToHtml(node, childrenHtml) {
233
573
  styleParts.push(`text-align: ${align}`);
234
574
  }
235
575
  const style = styleParts.length > 0 ? ` style="${escapeHtml(styleParts.join("; ") + ";")}"` : "";
236
- return `<${tag}${dir}${style}>${childrenHtml || "<br>"}</${tag}>`;
576
+ return `<${tag}${dir}${style}>${childrenHtml22 || "<br>"}</${tag}>`;
237
577
  }
238
578
  function nodeToHtml(node) {
239
579
  if (!node || typeof node !== "object") return "";
@@ -244,32 +584,32 @@ function nodeToHtml(node) {
244
584
  if (type === "linebreak") {
245
585
  return "<br>";
246
586
  }
247
- const childrenHtml = (node.children || []).map(nodeToHtml).join("");
587
+ const childrenHtml22 = (node.children || []).map(nodeToHtml).join("");
248
588
  switch (type) {
249
589
  case "root":
250
- return childrenHtml;
590
+ return childrenHtml22;
251
591
  case "paragraph":
252
592
  case "extended-paragraph":
253
- return paragraphNodeToHtml(node, childrenHtml);
593
+ return paragraphNodeToHtml(node, childrenHtml22);
254
594
  case "heading": {
255
595
  const tag = /^h[1-6]$/.test(node.tag) ? node.tag : "h1";
256
596
  const dir = dirAttribute(node.direction);
257
- return `<${tag}${dir}>${childrenHtml}</${tag}>`;
597
+ return `<${tag}${dir}>${childrenHtml22}</${tag}>`;
258
598
  }
259
599
  case "link": {
260
600
  const href = node.url ? ` href="${escapeHtml(node.url)}"` : "";
261
601
  const target = node.target ? ` target="${escapeHtml(node.target)}"` : "";
262
602
  const rel = node.rel ? ` rel="${escapeHtml(node.rel)}"` : "";
263
- return `<a${href}${target}${rel}>${childrenHtml}</a>`;
603
+ return `<a${href}${target}${rel}>${childrenHtml22}</a>`;
264
604
  }
265
605
  case "list": {
266
606
  const tag = node.listType === "number" ? "ol" : "ul";
267
- return `<${tag}>${childrenHtml}</${tag}>`;
607
+ return `<${tag}>${childrenHtml22}</${tag}>`;
268
608
  }
269
609
  case "listitem":
270
- return `<li>${childrenHtml}</li>`;
610
+ return `<li>${childrenHtml22}</li>`;
271
611
  default:
272
- return childrenHtml;
612
+ return childrenHtml22;
273
613
  }
274
614
  }
275
615
  function generateHtmlFromTextJson(textJson) {
@@ -300,6 +640,57 @@ function mergeValues(defaults, userValues) {
300
640
  }
301
641
  return result;
302
642
  }
643
+ var CHANNEL = /^-?(?:\d+(?:\.\d*)?|\.\d+)%?$/;
644
+ function normalizeColor(value) {
645
+ const text = value.trim();
646
+ const open = text.indexOf("(");
647
+ if (!/^rgba?$/i.test(text.slice(0, open)) || !text.endsWith(")")) return value;
648
+ const [rgb, slashed, extra] = text.slice(open + 1, -1).split("/");
649
+ const channels = rgb.trim().split(/\s*,\s*|\s+/);
650
+ if (slashed !== void 0) channels.push(slashed.trim());
651
+ if (extra !== void 0 || channels.length > 4 || channels.length < (slashed === void 0 ? 3 : 4) || !channels.every((c) => CHANNEL.test(c))) return value;
652
+ const num = (text2, full) => text2.endsWith("%") ? parseFloat(text2) / 100 * full : parseFloat(text2);
653
+ const [r, g, b] = channels.slice(0, 3).map((text2) => Math.max(0, Math.min(255, Math.round(num(text2, 255)))));
654
+ const alpha = channels[3] === void 0 ? 1 : num(channels[3], 1);
655
+ if (alpha < 1) return `rgba(${r}, ${g}, ${b}, ${Math.max(0, alpha)})`;
656
+ return `#${[r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("")}`;
657
+ }
658
+ function cssValueSafe(text) {
659
+ if (/[<>{};]|\/\*|\*\//.test(text)) return false;
660
+ const open = [];
661
+ for (const c of text) {
662
+ if (c === "(" || c === "[") open.push(c === "(" ? ")" : "]");
663
+ else if ((c === ")" || c === "]") && open.pop() !== c) return false;
664
+ }
665
+ return !open.length;
666
+ }
667
+ function cssSafeValues(value) {
668
+ if (typeof value === "string") return cssValueSafe(value) ? value : void 0;
669
+ if (Array.isArray(value)) return value.map(cssSafeValues);
670
+ if (!isPlainObject(value)) return value;
671
+ const out = {};
672
+ for (const [k, v] of Object.entries(value)) {
673
+ const safe = cssSafeValues(v);
674
+ if (safe !== void 0 || v === void 0) out[k] = safe;
675
+ }
676
+ return out;
677
+ }
678
+ function normalizeFontStack(value) {
679
+ return value.replace(/"([^"]*)"?/g, (_, name) => `'${name.replace(/'/g, "\\'")}'`);
680
+ }
681
+ function normalizeCssValues(value, key) {
682
+ if (typeof value === "string") return key === "fontFamily" ? normalizeFontStack(value) : key === "color" || key?.endsWith("Color") ? normalizeColor(value) : value;
683
+ if (Array.isArray(value)) return value.map((item) => normalizeCssValues(item));
684
+ if (!isPlainObject(value)) return value;
685
+ const out = {};
686
+ for (const [k, v] of Object.entries(value)) out[k] = normalizeCssValues(v, key === "fontFamily" ? key : k);
687
+ return out;
688
+ }
689
+ function isPlainObject(value) {
690
+ if (value === null || typeof value !== "object") return false;
691
+ const proto = Object.getPrototypeOf(value);
692
+ return proto === Object.prototype || proto === null;
693
+ }
303
694
  var nestedStructureCache = /* @__PURE__ */ new WeakMap();
304
695
  function analyzeNestedStructure(defaultValues) {
305
696
  const cached = nestedStructureCache.get(defaultValues);
@@ -344,24 +735,39 @@ function normalizeCssProps(props) {
344
735
  }
345
736
  }
346
737
  }
347
- function flattenChildrenText(node) {
738
+ var ENTITIES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
739
+ function escapeText(text, quotes) {
740
+ return text.replace(quotes ? /[&<>"']/g : /[&<>]/g, (c) => ENTITIES[c]);
741
+ }
742
+ function escapeKeepingTags(text, quotes, escape = (part) => escapeText(part, quotes)) {
743
+ return text.split(/(<%[\w.]+%>)/).map((part, i) => i % 2 ? part : escape(part)).join("");
744
+ }
745
+ var menuText = (text) => escapeKeepingTags(text, false, (part) => part.replace(/&(?!(?:#\d+|#x[\da-f]+|[a-z][\da-z]*);)|[<>"]/gi, (c) => ENTITIES[c]));
746
+ function flattenChildrenText(node, breaks) {
348
747
  if (node == null || typeof node === "boolean") return "";
349
748
  if (typeof node === "string") return node;
350
749
  if (typeof node === "number") return String(node);
351
- if (Array.isArray(node)) return node.map(flattenChildrenText).join("");
750
+ if (Array.isArray(node)) return node.map((child) => flattenChildrenText(child, breaks)).join("");
352
751
  if (typeof node === "object" && "props" in node) {
353
- return flattenChildrenText(node.props?.children);
752
+ return breaks && node.type === "br" ? "<br/>" : flattenChildrenText(node.props?.children, breaks);
354
753
  }
355
754
  return "";
356
755
  }
756
+ function childrenHtml(children) {
757
+ return flattenChildrenText(children, true).split(/(<br\s*\/?>)/i).map((part, i) => i % 2 ? "<br/>" : escapeKeepingTags(part)).join("");
758
+ }
357
759
  function mapSemanticProps(props, defaultValues, componentType) {
358
- const { children, values, ...restProps } = props;
760
+ const { children, values, mobile, hideOnMobile: hideMobileProp, hideOnDesktop: hideDesktopProp, ...restProps } = props;
761
+ const hideOnMobile = componentType === "Column" ? void 0 : hideMobileProp;
762
+ const hideOnDesktop = componentType === "Column" ? void 0 : hideDesktopProp;
359
763
  const userProps = { ...restProps };
360
764
  const result = values ? { ...values } : {};
361
765
  if (children !== void 0 && !result.text && !result.textJson) {
362
766
  const textContent = typeof children === "string" ? children : flattenChildrenText(children);
363
767
  if (componentType === "Paragraph") {
364
768
  result.textJson = textToTextJson(textContent);
769
+ } else if (componentType === "Heading" || componentType === "Button") {
770
+ result.text = childrenHtml(children);
365
771
  } else {
366
772
  result.text = textContent;
367
773
  }
@@ -439,15 +845,15 @@ function mapSemanticProps(props, defaultValues, componentType) {
439
845
  }
440
846
  if (defaultValues && typeof defaultValues === "object" && "border" in defaultValues) {
441
847
  const borderSideRe = /^border(Top|Right|Bottom|Left)(Width|Style|Color)$/;
442
- const collected = {};
848
+ const collected2 = {};
443
849
  for (const key of Object.keys(final)) {
444
850
  if (borderSideRe.test(key)) {
445
- collected[key] = final[key];
851
+ collected2[key] = final[key];
446
852
  delete final[key];
447
853
  }
448
854
  }
449
- if (Object.keys(collected).length > 0) {
450
- final.border = { ...final.border || {}, ...collected };
855
+ if (Object.keys(collected2).length > 0) {
856
+ final.border = { ...final.border || {}, ...collected2 };
451
857
  }
452
858
  if (final.border && typeof final.border === "object") {
453
859
  const b = { ...final.border };
@@ -460,7 +866,31 @@ function mapSemanticProps(props, defaultValues, componentType) {
460
866
  final.border = b;
461
867
  }
462
868
  }
463
- return final;
869
+ if (mobile || hideOnMobile !== void 0 || hideOnDesktop !== void 0) {
870
+ const overrides = { ...final._override };
871
+ if (mobile || hideOnMobile !== void 0) {
872
+ const { width, maxWidth, autoWidth, ...phone } = mobile || {};
873
+ const mapped = mapSemanticProps(phone, defaultValues, componentType);
874
+ if (componentType === "Image" && (width !== void 0 || maxWidth !== void 0 || autoWidth !== void 0)) {
875
+ mapped.src = {
876
+ ...overrides.mobile?.src || {},
877
+ ...autoWidth !== void 0 ? { autoWidth } : { autoWidth: false },
878
+ ...width !== void 0 || maxWidth !== void 0 ? { maxWidth: width ?? maxWidth } : {}
879
+ };
880
+ } else if (width !== void 0 || autoWidth !== void 0) {
881
+ const key = componentType === "Button" ? "size" : "width";
882
+ mapped[key] = { autoWidth: autoWidth ?? false, ...width !== void 0 ? { width: typeof width === "number" ? `${width}px` : width } : {} };
883
+ }
884
+ overrides.mobile = { ...overrides.mobile, ...cssSafeValues(mapped), ...hideOnMobile !== void 0 ? { hideMobile: hideOnMobile } : {} };
885
+ }
886
+ if (hideOnDesktop !== void 0) overrides.desktop = { ...overrides.desktop, hideDesktop: hideOnDesktop };
887
+ final._override = overrides;
888
+ }
889
+ if (componentType === "Row" && final.noStackMobile === true) {
890
+ final._override = { ...final._override, mobile: { ...final._override?.mobile, noStackMobile: true } };
891
+ final.noStackMobile = false;
892
+ }
893
+ return normalizeCssValues(final);
464
894
  }
465
895
  function normalizeLinkValue(value) {
466
896
  if (value == null) return void 0;
@@ -482,26 +912,39 @@ function normalizeLinkValue(value) {
482
912
  }
483
913
  return void 0;
484
914
  }
485
- function normalizeValuesForExporter(values, componentName) {
915
+ function safeLink(link) {
916
+ const url = link.url === void 0 || link.url === null ? link.url : String(link.url);
917
+ return typeof url === "string" ? { ...link, url: url.replace(/<%[\w.]+%>|["<>]/g, (c) => c.length > 1 ? c : encodeURIComponent(c)) } : link;
918
+ }
919
+ function normalizeValuesForExporter(values, componentName2, mode) {
486
920
  if (values == null || typeof values !== "object") return values;
487
921
  const out = { ...values };
488
922
  for (const key of ["href", "action"]) {
489
923
  if (out[key] !== void 0) {
490
924
  const normalized = normalizeLinkValue(out[key]);
491
- if (normalized !== void 0) out[key] = normalized;
925
+ if (normalized !== void 0) out[key] = safeLink(normalized);
492
926
  }
493
927
  }
494
- if (componentName === "Menu" && out.menu && Array.isArray(out.menu.items)) {
928
+ if (componentName2 === "Button" && typeof out.text === "string") {
929
+ out.text = escapeKeepingTags(out.text, true);
930
+ }
931
+ if (componentName2 === "Menu" && out.menu && Array.isArray(out.menu.items)) {
495
932
  out.menu = {
496
933
  ...out.menu,
497
934
  items: out.menu.items.map((item) => {
498
- if (!item || item.link === void 0) return item;
499
- const normalized = normalizeLinkValue(item.link);
500
- if (normalized === void 0) return item;
501
- return { ...item, link: normalized };
935
+ if (!item) return item;
936
+ const next = mode === "email" && typeof item.text === "string" ? { ...item, text: menuText(item.text) } : item;
937
+ if (next.link === void 0) return next;
938
+ const normalized = normalizeLinkValue(next.link);
939
+ if (normalized === void 0) return next;
940
+ return { ...next, link: safeLink(normalized) };
502
941
  })
503
942
  };
504
943
  }
944
+ if (componentName2 === "Social" && out.icons && Array.isArray(out.icons.icons)) {
945
+ const named2 = (icon) => mode === "email" && typeof icon.name === "string" ? { ...icon, name: escapeText(icon.name, true) } : icon;
946
+ out.icons = { ...out.icons, icons: out.icons.icons.map((icon) => icon && typeof icon === "object" ? named2(safeLink(icon)) : icon) };
947
+ }
505
948
  return out;
506
949
  }
507
950
  var HTML_ENTITIES = {
@@ -594,9 +1037,11 @@ function htmlToPlainText(html) {
594
1037
  text = text.replace(/<head[\s\S]*?<\/head>/gi, "");
595
1038
  text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
596
1039
  text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
1040
+ text = text.replace(/<br\s*\/?>/gi, "\n");
597
1041
  text = text.replace(/<h[12][^>]*>([\s\S]*?)<\/h[12]>/gi, (_, content) => {
598
1042
  const clean = content.replace(/<[^>]+>/g, "").trim();
599
- return "\n" + clean.toUpperCase() + "\n";
1043
+ const upper = clean.replace(/&#?\w+;|[^&]+|&/g, (part) => /^&#?\w+;$/.test(part) ? part : part.toUpperCase());
1044
+ return "\n" + upper + "\n";
600
1045
  });
601
1046
  text = text.replace(/<h[3-6][^>]*>([\s\S]*?)<\/h[3-6]>/gi, (_, content) => {
602
1047
  const clean = content.replace(/<[^>]+>/g, "").trim();
@@ -618,7 +1063,6 @@ function htmlToPlainText(html) {
618
1063
  const clean = content.replace(/<[^>]+>/g, "").trim();
619
1064
  return "- " + clean + "\n";
620
1065
  });
621
- text = text.replace(/<br\s*\/?>/gi, "\n");
622
1066
  text = text.replace(/<\/(?:p|div|tr|blockquote|section|article|header|footer|main|nav|aside)>/gi, "\n");
623
1067
  text = text.replace(/<\/(?:ul|ol|table|thead|tbody|tfoot)>/gi, "\n");
624
1068
  text = text.replace(/<\/(?:td|th)>/gi, " ");
@@ -665,6 +1109,15 @@ function nextHtmlId(config, prefix) {
665
1109
  ids[prefix] = (ids[prefix] || 0) + 1;
666
1110
  return `${prefix}_${ids[prefix]}`;
667
1111
  }
1112
+ function safeName(value, spaces = false) {
1113
+ const text = String(value ?? "");
1114
+ return spaces ? text.replace(/[\t\n\f\r]/g, " ").replace(/["'<>&`\u0000-\u001f\u007f]/g, "") : text.replace(/["'<>&`\s\u0000-\u001f\u007f]/g, "");
1115
+ }
1116
+ var safeMeta = (meta) => ({ ...meta, htmlID: safeName(meta.htmlID), htmlClassNames: safeName(meta.htmlClassNames, true) });
1117
+ function textDirectionOf(value) {
1118
+ const dir = typeof value === "string" ? value.trim().toLowerCase() : "";
1119
+ return dir === "ltr" || dir === "rtl" || dir === "auto" ? dir : void 0;
1120
+ }
668
1121
  function renderComponent(config) {
669
1122
  const { type, values, className, style, args = [], innerHTML, _config, exporter, metaContext } = config;
670
1123
  try {
@@ -672,7 +1125,7 @@ function renderComponent(config) {
672
1125
  const exporterConfig = {
673
1126
  generateHtmlFromTextJson,
674
1127
  toSafeHtml: cfg.toSafeHtml,
675
- textDirection: cfg.textDirection,
1128
+ textDirection: textDirectionOf(cfg.textDirection),
676
1129
  cdnBaseUrl: cfg.cdnBaseUrl
677
1130
  };
678
1131
  let html;
@@ -736,21 +1189,20 @@ function createItemComponent(config) {
736
1189
  });
737
1190
  const finalValues = mergeValues(config.defaultValues, mappedValues);
738
1191
  const metaName = config.metaName ?? config.name.toLowerCase();
739
- const valuesWithMeta = {
740
- ...finalValues,
741
- _meta: {
742
- htmlID: _metaHtmlId ?? `u_content_${metaName}_${index + 1}`,
743
- htmlClassNames: `u_content_${metaName}`,
744
- ...finalValues._meta || {}
745
- }
1192
+ const meta = {
1193
+ htmlID: _metaHtmlId ?? `u_content_${metaName}_${index + 1}`,
1194
+ htmlClassNames: `u_content_${metaName}`,
1195
+ ...finalValues._meta || {}
746
1196
  };
1197
+ const valuesWithMeta = { ...finalValues, _meta: safeMeta(meta) };
747
1198
  const safeBodyValues = {
748
1199
  contentWidth: "500px",
749
1200
  ...bodyValues
750
1201
  };
751
1202
  const valuesForExporter = normalizeValuesForExporter(
752
1203
  valuesWithMeta,
753
- config.name
1204
+ config.name,
1205
+ mode
754
1206
  );
755
1207
  const exportSrc = valuesForExporter.src;
756
1208
  if (exportSrc && typeof exportSrc === "object" && exportSrc.autoWidth === false) {
@@ -785,10 +1237,12 @@ function createItemComponent(config) {
785
1237
  });
786
1238
  }
787
1239
  const ItemComponent = (props) => {
1240
+ if (collecting()) return whileCollecting(() => recordBlock(ItemComponent, props), () => renderFn(props), ItemComponent.displayName ?? "Block");
788
1241
  return renderFn(props);
789
1242
  };
790
1243
  ItemComponent.displayName = config.displayName || config.name;
791
1244
  ItemComponent[UNLAYER_RENDER_KEY] = renderFn;
1245
+ ItemComponent[RECORDS] = true;
792
1246
  ItemComponent[UNLAYER_CONFIG_KEY] = {
793
1247
  name: config.name,
794
1248
  defaultValues: config.defaultValues,
@@ -796,7 +1250,8 @@ function createItemComponent(config) {
796
1250
  contentType: config.contentType,
797
1251
  slug: config.slug,
798
1252
  metaName: config.metaName,
799
- head: config.head
1253
+ head: config.head,
1254
+ omitEmptyOutput: config.omitEmptyOutput
800
1255
  };
801
1256
  return ItemComponent;
802
1257
  }
@@ -816,9 +1271,9 @@ var Button = createItemComponent({
816
1271
  DEFAULT_VALUES,
817
1272
  "Button"
818
1273
  );
819
- const size = mapped.size;
820
- if (size && typeof size === "object" && !Array.isArray(size)) {
821
- const s = size;
1274
+ const size2 = mapped.size;
1275
+ if (size2 && typeof size2 === "object" && !Array.isArray(size2)) {
1276
+ const s = size2;
822
1277
  if (typeof s.width === "number") {
823
1278
  s.width = `${s.width}px`;
824
1279
  } else if (typeof s.width === "string" && /^\d+(?:\.\d+)?$/.test(s.width.trim())) {
@@ -1008,13 +1463,45 @@ var Paragraph = createItemComponent({
1008
1463
  exporters: ParagraphExporters
1009
1464
  });
1010
1465
  var Paragraph_default = Paragraph;
1011
- var DEFAULT_ICONS = SocialDefaults.icons ?? { iconType: "circle", icons: [] };
1466
+
1467
+ // src/components/PageBreak.tsx
1012
1468
  var DEFAULT_VALUES8 = {
1469
+ color: "#6b7280"
1470
+ };
1471
+ var pageBreakExporters = {
1472
+ // Page breaks are a document-only concept. Keep the unsupported renderers
1473
+ // inert so a component rendered outside <Document> does not throw.
1474
+ web: () => "",
1475
+ email: () => "",
1476
+ document: () => '<div class="u-content-page-break" aria-hidden="true"></div>'
1477
+ };
1478
+ var pageBreakHead = {
1479
+ css: (_values, _bodyValues, meta) => meta.displayMode === "document" ? `
1480
+ .u-content-page-break {
1481
+ break-after: page;
1482
+ page-break-after: always;
1483
+ }
1484
+ ` : void 0
1485
+ };
1486
+ var PageBreak = createItemComponent({
1487
+ name: "PageBreak",
1488
+ contentType: "page_break",
1489
+ metaName: "page_break",
1490
+ defaultValues: DEFAULT_VALUES8,
1491
+ propMapper: (props) => mapSemanticProps(props, DEFAULT_VALUES8, "PageBreak"),
1492
+ displayName: "PageBreak",
1493
+ exporters: pageBreakExporters,
1494
+ head: pageBreakHead,
1495
+ omitEmptyOutput: true
1496
+ });
1497
+ var PageBreak_default = PageBreak;
1498
+ var DEFAULT_ICONS = SocialDefaults.icons ?? { iconType: "circle", icons: [] };
1499
+ var DEFAULT_VALUES9 = {
1013
1500
  ...SocialDefaults
1014
1501
  };
1015
1502
  var Social = createItemComponent({
1016
1503
  name: "Social",
1017
- defaultValues: DEFAULT_VALUES8,
1504
+ defaultValues: DEFAULT_VALUES9,
1018
1505
  propMapper: (props) => {
1019
1506
  const { icons, iconType, ...rest } = props;
1020
1507
  const coerceSizes = (base) => {
@@ -1035,7 +1522,7 @@ var Social = createItemComponent({
1035
1522
  }));
1036
1523
  const base = mapSemanticProps(
1037
1524
  rest,
1038
- DEFAULT_VALUES8,
1525
+ DEFAULT_VALUES9,
1039
1526
  "Social"
1040
1527
  );
1041
1528
  base.icons = {
@@ -1047,14 +1534,14 @@ var Social = createItemComponent({
1047
1534
  if (iconType !== void 0) {
1048
1535
  const base = mapSemanticProps(
1049
1536
  rest,
1050
- DEFAULT_VALUES8,
1537
+ DEFAULT_VALUES9,
1051
1538
  "Social"
1052
1539
  );
1053
1540
  base.icons = { ...DEFAULT_ICONS, ...base.icons, iconType };
1054
1541
  return coerceSizes(base);
1055
1542
  }
1056
1543
  return coerceSizes(
1057
- mapSemanticProps(props, DEFAULT_VALUES8, "Social")
1544
+ mapSemanticProps(props, DEFAULT_VALUES9, "Social")
1058
1545
  );
1059
1546
  },
1060
1547
  displayName: "Social",
@@ -1062,20 +1549,20 @@ var Social = createItemComponent({
1062
1549
  });
1063
1550
  var Social_default = Social;
1064
1551
  var DEFAULT_TABLE = { headers: [], rows: [], footers: [] };
1065
- var DEFAULT_VALUES9 = {
1552
+ var DEFAULT_VALUES10 = {
1066
1553
  ...TableDefaults,
1067
1554
  table: DEFAULT_TABLE
1068
1555
  // Schema doesn't include table data structure
1069
1556
  };
1070
1557
  var Table = createItemComponent({
1071
1558
  name: "Table",
1072
- defaultValues: DEFAULT_VALUES9,
1559
+ defaultValues: DEFAULT_VALUES10,
1073
1560
  propMapper: (props) => {
1074
1561
  const { headers, data, columns, rows, ...rest } = props;
1075
1562
  if (headers || data || typeof columns === "number" || typeof rows === "number") {
1076
1563
  const base = mapSemanticProps(
1077
1564
  rest,
1078
- DEFAULT_VALUES9,
1565
+ DEFAULT_VALUES10,
1079
1566
  "Table"
1080
1567
  );
1081
1568
  const colCount = headers ? headers.length : typeof columns === "number" ? columns : data?.[0]?.length ?? 0;
@@ -1107,7 +1594,7 @@ var Table = createItemComponent({
1107
1594
  }
1108
1595
  return mapSemanticProps(
1109
1596
  props,
1110
- DEFAULT_VALUES9,
1597
+ DEFAULT_VALUES10,
1111
1598
  "Table"
1112
1599
  );
1113
1600
  },
@@ -1122,7 +1609,7 @@ var DEFAULT_VIDEO = {
1122
1609
  loading: false,
1123
1610
  ...VideoDefaults.video
1124
1611
  };
1125
- var DEFAULT_VALUES10 = {
1612
+ var DEFAULT_VALUES11 = {
1126
1613
  ...VideoDefaults,
1127
1614
  video: DEFAULT_VIDEO
1128
1615
  };
@@ -1150,14 +1637,14 @@ function parseVideoUrl(url) {
1150
1637
  }
1151
1638
  var Video = createItemComponent({
1152
1639
  name: "Video",
1153
- defaultValues: DEFAULT_VALUES10,
1640
+ defaultValues: DEFAULT_VALUES11,
1154
1641
  propMapper: (props) => {
1155
1642
  const { videoUrl, ...rest } = props;
1156
1643
  if (typeof videoUrl === "string") {
1157
1644
  const parsed = parseVideoUrl(videoUrl);
1158
1645
  const base = mapSemanticProps(
1159
1646
  rest,
1160
- DEFAULT_VALUES10,
1647
+ DEFAULT_VALUES11,
1161
1648
  "Video"
1162
1649
  );
1163
1650
  if (parsed) {
@@ -1167,7 +1654,7 @@ var Video = createItemComponent({
1167
1654
  }
1168
1655
  return mapSemanticProps(
1169
1656
  props,
1170
- DEFAULT_VALUES10,
1657
+ DEFAULT_VALUES11,
1171
1658
  "Video"
1172
1659
  );
1173
1660
  },
@@ -1195,7 +1682,36 @@ var {
1195
1682
  var BODY_DEFAULTS = bodyRest;
1196
1683
  var ROW_DEFAULTS = { ...RowDefaults };
1197
1684
  var COLUMN_DEFAULTS = { ...ColumnDefaults };
1198
- var DEFAULT_VALUES11 = ROW_DEFAULTS;
1685
+
1686
+ // src/utils/render-scope.ts
1687
+ var scoped;
1688
+ function scopeCache(key, make) {
1689
+ if (!scoped) return make();
1690
+ if (!scoped.has(key)) scoped.set(key, make());
1691
+ return scoped.get(key);
1692
+ }
1693
+ function withRenderScope(render, own = false) {
1694
+ const scope = () => {
1695
+ if (scoped) return render();
1696
+ scoped = /* @__PURE__ */ new WeakMap();
1697
+ try {
1698
+ return render();
1699
+ } finally {
1700
+ scoped = void 0;
1701
+ }
1702
+ };
1703
+ return withBuild(() => within(null, scope), own);
1704
+ }
1705
+ var warnedLoose = /* @__PURE__ */ new Set();
1706
+ function looseText(child, container) {
1707
+ const text = String(child);
1708
+ if (text.trim() && !warnedLoose.has(container)) {
1709
+ warnedLoose.add(container);
1710
+ 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.`);
1711
+ }
1712
+ return escapeKeepingTags(text, true);
1713
+ }
1714
+ var DEFAULT_VALUES12 = ROW_DEFAULTS;
1199
1715
  var DEFAULT_BODY_VALUES = BODY_DEFAULTS;
1200
1716
  function getWidthPercentages(cells) {
1201
1717
  if (cells.length === 0) return [];
@@ -1224,8 +1740,8 @@ ${maxQuery} {
1224
1740
  .u-row { width: 100% !important; }
1225
1741
  .u-row .u-col { display: block !important; width: 100% !important; min-width: 320px !important; max-width: 100% !important; }
1226
1742
  .u-row .u-col > div { margin: 0 auto; }
1227
- .no-stack .u-col { min-width: 0 !important; display: table-cell !important; }
1228
- ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
1743
+ .u-row.no-stack .u-col { min-width: 0 !important; display: table-cell !important; }
1744
+ ${widths.map(({ value, className }) => ` .u-row.no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
1229
1745
  }`;
1230
1746
  }
1231
1747
  const baseCSS = `
@@ -1265,17 +1781,18 @@ function renderRowToHtml(innerHTML, values, bodyValues, mode, cells, collection
1265
1781
  variant: mode
1266
1782
  });
1267
1783
  const contentWidth = toContentWidthPx(bodyValues);
1268
- const css = generateGridCSS(cells, mode, contentWidth);
1269
- return css ? `<style>${css}</style>${html}` : html;
1784
+ const css2 = generateGridCSS(cells, mode, contentWidth);
1785
+ return css2 ? `<style>${css2}</style>${html}` : html;
1270
1786
  }
1271
1787
  function processChildren(children, cells, bodyValues, rowValues, mode, _config) {
1272
1788
  if (!children) return "";
1273
1789
  let innerHTML = "";
1274
- const childrenArray = React.Children.toArray(children);
1275
- childrenArray.forEach((child, index) => {
1276
- if (!React.isValidElement(child)) {
1790
+ const childrenArray = React4.Children.toArray(children);
1791
+ let index = 0;
1792
+ childrenArray.forEach((child) => {
1793
+ if (!React4.isValidElement(child)) {
1277
1794
  if (typeof child === "string" || typeof child === "number") {
1278
- innerHTML += String(child);
1795
+ innerHTML += looseText(child, "Row");
1279
1796
  }
1280
1797
  return;
1281
1798
  }
@@ -1284,7 +1801,7 @@ function processChildren(children, cells, bodyValues, rowValues, mode, _config)
1284
1801
  if (isColumn && typeof child.type === "function") {
1285
1802
  const rendered = child.type({
1286
1803
  ...child.props,
1287
- index,
1804
+ index: index++,
1288
1805
  cells,
1289
1806
  bodyValues,
1290
1807
  rowValues,
@@ -1294,7 +1811,7 @@ function processChildren(children, cells, bodyValues, rowValues, mode, _config)
1294
1811
  if (rendered?.props?.dangerouslySetInnerHTML?.__html) {
1295
1812
  innerHTML += rendered.props.dangerouslySetInnerHTML.__html;
1296
1813
  }
1297
- } else if (React.isValidElement(child)) {
1814
+ } else if (React4.isValidElement(child)) {
1298
1815
  const name = child.type?.displayName || child.type?.name || "Unknown";
1299
1816
  console.warn(
1300
1817
  `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>`
@@ -1303,7 +1820,7 @@ function processChildren(children, cells, bodyValues, rowValues, mode, _config)
1303
1820
  });
1304
1821
  return innerHTML;
1305
1822
  }
1306
- var Row = (props) => {
1823
+ function renderRow(props) {
1307
1824
  const {
1308
1825
  layout,
1309
1826
  cells: propsCells,
@@ -1318,33 +1835,25 @@ var Row = (props) => {
1318
1835
  ...semanticProps
1319
1836
  } = props;
1320
1837
  const mode = modeProp ?? _config?.mode ?? "web";
1838
+ const columnCount = React4.Children.toArray(children).filter(
1839
+ (c) => React4.isValidElement(c) && /^Column$/.test(c.type?.displayName || c.type?.name || "")
1840
+ ).length;
1321
1841
  let cells;
1322
1842
  if (layout) {
1323
- validateColumnLayout(layout, React.Children.count(children));
1843
+ if (columnCount !== layout.expectedColumns) validateColumnLayout(layout, React4.Children.count(children));
1324
1844
  cells = layout.cells;
1325
1845
  } else if (propsCells) {
1326
1846
  cells = propsCells;
1327
1847
  } else {
1328
- const columnCount = React.Children.toArray(children).filter(
1329
- (c) => React.isValidElement(c) && /^Column$/.test(c.type?.displayName || c.type?.name || "")
1330
- ).length;
1331
1848
  cells = Array(Math.max(1, columnCount)).fill(1);
1332
1849
  }
1333
1850
  const safeBodyValues = { ...DEFAULT_BODY_VALUES, ...bodyValues };
1334
1851
  const values = mapSemanticProps(
1335
1852
  semanticProps,
1336
- DEFAULT_VALUES11,
1853
+ DEFAULT_VALUES12,
1337
1854
  "Row"
1338
1855
  );
1339
- const valuesWithMeta = {
1340
- ...values,
1341
- cells,
1342
- _meta: {
1343
- htmlID: nextHtmlId(_config, "u_row"),
1344
- htmlClassNames: "u_row",
1345
- ...values._meta || {}
1346
- }
1347
- };
1856
+ const valuesWithMeta = { ...values, cells, _meta: safeMeta({ htmlID: nextHtmlId(_config, "u_row"), htmlClassNames: "u_row", ...values._meta || {} }) };
1348
1857
  const innerHTML = processChildren(
1349
1858
  children,
1350
1859
  cells,
@@ -1367,11 +1876,12 @@ var Row = (props) => {
1367
1876
  console.error("Row rendering failed:", error);
1368
1877
  return /* @__PURE__ */ jsx("div", { className, style, children });
1369
1878
  }
1370
- };
1879
+ }
1880
+ var Row = (props) => collecting() ? whileCollecting(() => recordContainer("row", props), () => renderRow(props), "Row") : renderRow(props);
1371
1881
  Row.displayName = "Row";
1882
+ Row[RECORDS] = true;
1372
1883
  var Row_default = Row;
1373
- var DEFAULT_CONTAINER_PADDING2 = "10px";
1374
- var DEFAULT_VALUES12 = COLUMN_DEFAULTS;
1884
+ var DEFAULT_VALUES13 = COLUMN_DEFAULTS;
1375
1885
  function renderColumnToHtml(innerHTML, values, index, cells, bodyValues, rowValues, mode) {
1376
1886
  const columnExporter = ColumnExporters[mode] || ColumnExporters.web;
1377
1887
  return columnExporter(innerHTML, values, index, cells, bodyValues, rowValues);
@@ -1381,6 +1891,7 @@ function renderContentToHtml(innerHTML, values, bodyValues, mode) {
1381
1891
  return contentExporter(innerHTML, values, bodyValues, {});
1382
1892
  }
1383
1893
  var Column = (props) => {
1894
+ if (collecting()) return whileCollecting(() => recordContainer("column", props), () => Column(props), "Column");
1384
1895
  const {
1385
1896
  children,
1386
1897
  index = 0,
@@ -1396,30 +1907,23 @@ var Column = (props) => {
1396
1907
  const mode = modeProp ?? _config?.mode ?? "web";
1397
1908
  const values = mapSemanticProps(
1398
1909
  semanticProps,
1399
- DEFAULT_VALUES12,
1910
+ DEFAULT_VALUES13,
1400
1911
  "Column"
1401
1912
  );
1402
- const valuesWithMeta = {
1403
- ...values,
1404
- _meta: {
1405
- htmlID: nextHtmlId(_config, "u_column"),
1406
- htmlClassNames: "u_column",
1407
- ...values._meta || {}
1408
- }
1409
- };
1913
+ const valuesWithMeta = { ...values, _meta: safeMeta({ htmlID: nextHtmlId(_config, "u_column"), htmlClassNames: "u_column", ...values._meta || {} }) };
1410
1914
  let innerHTML = "";
1411
1915
  if (children) {
1412
1916
  try {
1413
- const childrenArray = React.Children.toArray(children);
1917
+ const childrenArray = React4.Children.toArray(children);
1414
1918
  childrenArray.forEach((child, childIndex) => {
1415
1919
  if (typeof child === "string" || typeof child === "number") {
1416
- innerHTML += String(child);
1417
- } else if (React.isValidElement(child)) {
1920
+ innerHTML += looseText(child, "Column");
1921
+ } else if (React4.isValidElement(child)) {
1418
1922
  if (typeof child.type === "function") {
1419
1923
  const ComponentType = child.type;
1420
1924
  const renderFn = ComponentType[UNLAYER_RENDER_KEY] || ComponentType;
1421
- const componentName = ComponentType?.[UNLAYER_CONFIG_KEY]?.metaName ?? (ComponentType?.displayName || ComponentType?.name || "component").toLowerCase();
1422
- const contentHtmlId = nextHtmlId(_config, `u_content_${componentName}`);
1925
+ const componentName2 = ComponentType?.[UNLAYER_CONFIG_KEY]?.metaName ?? (ComponentType?.displayName || ComponentType?.name || "component").toLowerCase();
1926
+ const contentHtmlId = nextHtmlId(_config, `u_content_${componentName2}`);
1423
1927
  const rendered = renderFn({
1424
1928
  ...child.props,
1425
1929
  _config,
@@ -1432,14 +1936,28 @@ var Column = (props) => {
1432
1936
  });
1433
1937
  if (rendered && typeof rendered === "object" && rendered.props && rendered.props.dangerouslySetInnerHTML) {
1434
1938
  const componentHTML = rendered.props.dangerouslySetInnerHTML.__html;
1939
+ if (!componentHTML && ComponentType?.[UNLAYER_CONFIG_KEY]?.omitEmptyOutput) return;
1435
1940
  const childProps = child.props;
1436
- const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING2;
1941
+ const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING;
1437
1942
  const containerPadding = typeof rawContainerPadding === "number" ? `${rawContainerPadding}px` : rawContainerPadding;
1943
+ const device = ComponentType?.[UNLAYER_CONFIG_KEY]?.contentType === "custom" ? {} : childProps;
1438
1944
  const contentValues = {
1439
1945
  containerPadding,
1946
+ _override: {
1947
+ ...childProps.values?._override,
1948
+ mobile: {
1949
+ ...childProps.values?._override?.mobile,
1950
+ ...device.hideOnMobile !== void 0 ? { hideMobile: device.hideOnMobile } : {}
1951
+ },
1952
+ desktop: {
1953
+ ...childProps.values?._override?.desktop,
1954
+ ...device.hideOnDesktop !== void 0 ? { hideDesktop: device.hideOnDesktop } : {}
1955
+ }
1956
+ },
1440
1957
  _meta: {
1441
1958
  htmlID: contentHtmlId,
1442
- htmlClassNames: `u_content_${componentName}`
1959
+ htmlClassNames: `u_content_${componentName2}`,
1960
+ ...childProps.values?._meta?.htmlID ? { htmlID: safeName(childProps.values._meta.htmlID) } : {}
1443
1961
  }
1444
1962
  };
1445
1963
  innerHTML += renderContentToHtml(
@@ -1478,8 +1996,331 @@ var Column = (props) => {
1478
1996
  }
1479
1997
  };
1480
1998
  Column.displayName = "Column";
1999
+ Column[RECORDS] = true;
1481
2000
  var Column_default = Column;
1482
- var DEFAULT_VALUES13 = BODY_DEFAULTS;
2001
+ var KEPT = Symbol.for("@unlayer/react-elements/kept");
2002
+ function parse(html, collection) {
2003
+ const root = [];
2004
+ const open = [root];
2005
+ const pattern = new RegExp(`<${MARK} data-n="${collection.nonce}" data-i="(\\d+)"(?: data-e="([ocf])")?></${MARK}>`, "g");
2006
+ let last = 0;
2007
+ for (const match of html.matchAll(pattern)) {
2008
+ const into = open[open.length - 1];
2009
+ if (match.index > last) into.push({ kind: "raw", html: html.slice(last, match.index) });
2010
+ last = match.index + match[0].length;
2011
+ const at = Number(match[1]);
2012
+ const edge = match[2];
2013
+ if (edge === "o") {
2014
+ const children = [];
2015
+ into.push({ kind: collection.recorded[at].kind, at, children });
2016
+ open.push(children);
2017
+ } else if (edge === "c") open.pop();
2018
+ else into.push({ kind: edge === "f" ? "failed" : "block", at });
2019
+ }
2020
+ if (last < html.length) open[open.length - 1].push({ kind: "raw", html: html.slice(last) });
2021
+ if (open.length !== 1) throw new Error("[Unlayer] a collected render's markers don't pair up");
2022
+ return root;
2023
+ }
2024
+ var TEXT_ENTITIES = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#x27;": "'" };
2025
+ var VOID = /^(area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)$/i;
2026
+ function parts(html) {
2027
+ const out = [];
2028
+ let at = 0;
2029
+ const text = (s) => s && out.push({ text: s.replace(/&amp;|&lt;|&gt;|&quot;|&#x27;/g, (e) => TEXT_ENTITIES[e]) });
2030
+ while (at < html.length) {
2031
+ const start = html.indexOf("<", at);
2032
+ if (start < 0) {
2033
+ text(html.slice(at));
2034
+ break;
2035
+ }
2036
+ text(html.slice(at, start));
2037
+ const name = /^<([a-zA-Z][\w:-]*)/.exec(html.slice(start))?.[1];
2038
+ if (!name) {
2039
+ out.push({ tag: "#", html: html.slice(start) });
2040
+ break;
2041
+ }
2042
+ const openEnd = html.indexOf(">", start);
2043
+ if (VOID.test(name) || html[openEnd - 1] === "/") {
2044
+ out.push({ tag: name, html: html.slice(start, openEnd + 1) });
2045
+ at = openEnd + 1;
2046
+ continue;
2047
+ }
2048
+ const tags = new RegExp(`<(/?)${name}(?=[\\s>/])`, "gi");
2049
+ tags.lastIndex = openEnd + 1;
2050
+ let depth = 1;
2051
+ let end = -1;
2052
+ for (let m = tags.exec(html); m; m = tags.exec(html)) {
2053
+ depth += m[1] ? -1 : 1;
2054
+ if (depth === 0) {
2055
+ end = m.index;
2056
+ break;
2057
+ }
2058
+ }
2059
+ if (end < 0) {
2060
+ out.push({ tag: "#", html: html.slice(start) });
2061
+ break;
2062
+ }
2063
+ const close = html.indexOf(">", end) + 1;
2064
+ out.push({ tag: name, html: html.slice(start, close), inner: html.slice(openEnd + 1, end) });
2065
+ at = close;
2066
+ }
2067
+ return out;
2068
+ }
2069
+ function innerType(type) {
2070
+ while (type?.$$typeof === MEMO) type = type.type;
2071
+ return type;
2072
+ }
2073
+ var recorded = (c, node) => c.collection.recorded[node.at];
2074
+ var nameOf = (element2) => {
2075
+ if (typeof element2.type === "string") return element2.type;
2076
+ const type = innerType(element2.type);
2077
+ return type?.displayName || type?.name || "component";
2078
+ };
2079
+ function leftOut(c, node) {
2080
+ if (c.fatal) throw c.fatal;
2081
+ if (!c.told.has(node.at)) {
2082
+ c.told.add(node.at);
2083
+ try {
2084
+ tell(recorded(c, node).element);
2085
+ } catch (error) {
2086
+ c.fatal = error;
2087
+ throw error;
2088
+ }
2089
+ }
2090
+ return [];
2091
+ }
2092
+ function calledAgain(element2) {
2093
+ const type = innerType(element2.type);
2094
+ if (type?.$$typeof !== FORWARD_REF && (typeof type !== "function" || type.prototype?.isReactComponent)) return void 0;
2095
+ const props = { ...element2.props };
2096
+ for (const key in type.defaultProps) if (props[key] === void 0) props[key] = type.defaultProps[key];
2097
+ return { out: callIsolated(() => type.$$typeof === FORWARD_REF ? type.render(props, null) : type(props)) };
2098
+ }
2099
+ function tell(element2) {
2100
+ const type = innerType(element2.type);
2101
+ const name = nameOf(element2);
2102
+ 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.`);
2103
+ if (isAsyncComponent(type)) return async();
2104
+ try {
2105
+ const again = calledAgain(element2);
2106
+ const inner = again ? collected(again.out) : collected(element2, false);
2107
+ const failed = (nodes) => nodes.flatMap((n) => n.kind === "failed" ? [n] : "children" in n ? failed(n.children) : []);
2108
+ const found = failed(inner.nodes);
2109
+ for (const node of found) tell(recorded(inner, node).element);
2110
+ if (!found.length) console.error(`[Unlayer] <${name}> failed while rendering, so it's left out.`);
2111
+ } catch (error) {
2112
+ const message = String(error?.message);
2113
+ if (/Element type is invalid|Objects are not valid as a React child|Minified React error #(130|31)\b/.test(message)) throw error;
2114
+ if (isThenable(error) || /async and suspending|suspended|Minified React error #426\b/.test(message)) async();
2115
+ else console.error(`[Unlayer] <${name}> threw while rendering, so it's left out:`, error);
2116
+ }
2117
+ }
2118
+ function markupOnly(c, nodes) {
2119
+ let out = "";
2120
+ for (const node of nodes) {
2121
+ if (node.kind === "raw") out += node.html;
2122
+ else if (node.kind === "failed") leftOut(c, node);
2123
+ else if (node.kind !== "boundary") return void 0;
2124
+ else {
2125
+ const inner = markupOnly(c, node.children);
2126
+ if (inner === void 0) return void 0;
2127
+ out += inner;
2128
+ }
2129
+ }
2130
+ return out;
2131
+ }
2132
+ function keptMarkup(c, node) {
2133
+ c.kept ?? (c.kept = /* @__PURE__ */ new Map());
2134
+ if (!c.kept.has(node.at)) c.kept.set(node.at, keptMarkupOnce(c, node));
2135
+ return c.kept.get(node.at);
2136
+ }
2137
+ function keptMarkupOnce(c, node) {
2138
+ const raw = markupOnly(c, node.children);
2139
+ if (raw === void 0) return void 0;
2140
+ const list = parts(raw);
2141
+ if (!list.some((p) => "tag" in p)) return void 0;
2142
+ const element2 = recorded(c, node).element;
2143
+ const one = list.length === 1 || list.some((p) => "tag" in p && p.tag === "#") ? /^<([a-zA-Z][\w:-]*)\b[^>]*>([\s\S]*)<\/\1>$/.exec(raw) : null;
2144
+ let given = false;
2145
+ if (one) {
2146
+ try {
2147
+ const out = calledAgain(element2)?.out;
2148
+ given = React4.isValidElement(out) && typeof out.type === "string" && out.props.dangerouslySetInnerHTML != null;
2149
+ } catch {
2150
+ given = one[1].toLowerCase() === "div";
2151
+ }
2152
+ }
2153
+ const html = one && given ? one[2] : raw;
2154
+ const Rendered = () => React4.createElement("div", { dangerouslySetInnerHTML: { __html: html } });
2155
+ Rendered.displayName = nameOf(element2);
2156
+ const { containerPadding, hideOnMobile, hideOnDesktop } = element2.props ?? {};
2157
+ Rendered[KEPT] = React4.createElement(Html_default, {
2158
+ html,
2159
+ ...containerPadding !== void 0 ? { containerPadding } : {},
2160
+ ...hideOnMobile !== void 0 ? { hideOnMobile } : {},
2161
+ ...hideOnDesktop !== void 0 ? { hideOnDesktop } : {}
2162
+ });
2163
+ return React4.createElement(Rendered, { ...element2.props });
2164
+ }
2165
+ function resolvedProps(c, props) {
2166
+ let out = props;
2167
+ for (const [key, value] of Object.entries(props)) {
2168
+ if (typeof value !== "string" || !value.includes(MARK)) continue;
2169
+ let html = value;
2170
+ try {
2171
+ if (value.includes(c.collection.nonce)) html = markup(c, parse(value, c.collection), (block) => within(null, () => renderToStaticMarkup(block)));
2172
+ } catch {
2173
+ }
2174
+ out = out === props ? { ...props } : out;
2175
+ out[key] = withoutMarkers(html, c.collection.nonce);
2176
+ }
2177
+ return out;
2178
+ }
2179
+ function withoutMarkers(html, nonce2) {
2180
+ const left = new RegExp(`<${MARK} data-n="${nonce2}" data-i="\\d+"(?: data-e="[ocf]")?></${MARK}>`, "g");
2181
+ if (!html.includes(nonce2) || !html.match(left)) return html;
2182
+ 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.`);
2183
+ return html.replace(left, "");
2184
+ }
2185
+ function markup(c, nodes, render) {
2186
+ return nodes.map((node) => {
2187
+ switch (node.kind) {
2188
+ case "raw":
2189
+ return node.html;
2190
+ case "boundary":
2191
+ return markup(c, node.children, render);
2192
+ case "failed":
2193
+ leftOut(c, node);
2194
+ return "";
2195
+ default:
2196
+ return render(element(c, node));
2197
+ }
2198
+ }).join("");
2199
+ }
2200
+ var keyed = (list) => list.map((child, i) => React4.isValidElement(child) && child.key == null ? React4.cloneElement(child, { key: `k${i}` }) : child);
2201
+ function element(c, node) {
2202
+ const r = recorded(c, node);
2203
+ if (node.kind === "block") return React4.createElement(r.type, resolvedProps(c, r.props));
2204
+ if (node.kind === "column") return React4.createElement(Column_default, { ...r.props, children: keyed(elements(c, node.children, "column")) });
2205
+ const props = { ...r.props };
2206
+ const children = keyed(elements(c, node.children, "row"));
2207
+ if (props.layout) {
2208
+ const columns = children.filter((child) => React4.isValidElement(child) && child.type === Column_default).length;
2209
+ if (columns === props.layout.expectedColumns || React4.Children.count(props.children) === props.layout.expectedColumns) {
2210
+ props.cells = props.layout.cells;
2211
+ delete props.layout;
2212
+ }
2213
+ }
2214
+ return React4.createElement(Row_default, { ...props, children });
2215
+ }
2216
+ function spanned(nodes) {
2217
+ const out = [];
2218
+ const spans = [];
2219
+ for (const node of nodes) {
2220
+ if (node.kind !== "raw") {
2221
+ out.push({ node, inside: spans.length > 0 });
2222
+ continue;
2223
+ }
2224
+ const html = node.html;
2225
+ const open = [];
2226
+ let closed = spans.length ? -1 : 0;
2227
+ for (const m of html.matchAll(/<!--[\s\S]*?(?:-->|$)|<(\/?)([a-zA-Z][\w:-]*)\b[^>]*>/g)) {
2228
+ if (!m[2]) continue;
2229
+ const name = m[2].toLowerCase();
2230
+ if (m[1]) {
2231
+ const i = open.map((o) => o.name).lastIndexOf(name);
2232
+ const across = spans.lastIndexOf(name);
2233
+ if (i >= 0) open.length = i;
2234
+ else if (across >= 0) {
2235
+ spans.length = across;
2236
+ open.length = 0;
2237
+ if (!spans.length) closed = m.index + m[0].length;
2238
+ }
2239
+ } else if (!VOID.test(name) && !m[0].endsWith("/>")) open.push({ name, at: m.index });
2240
+ }
2241
+ const piece = (from, to, inside) => {
2242
+ if (to > from) out.push({ node: { kind: "raw", html: html.slice(from, to) }, inside });
2243
+ };
2244
+ if (closed < 0) piece(0, html.length, true);
2245
+ else {
2246
+ piece(0, closed, true);
2247
+ piece(closed, open[0]?.at ?? html.length, false);
2248
+ if (open.length) piece(open[0].at, html.length, true);
2249
+ }
2250
+ spans.push(...open.map((o) => o.name));
2251
+ }
2252
+ return out;
2253
+ }
2254
+ function elements(c, nodes, place, owner, inMarkup = false) {
2255
+ const inColumn = place === "column";
2256
+ return (place === "root" || inMarkup ? nodes.map((node) => ({ node, inside: inMarkup })) : spanned(nodes)).flatMap(({ node, inside }) => {
2257
+ switch (node.kind) {
2258
+ case "failed":
2259
+ return leftOut(c, node);
2260
+ case "boundary": {
2261
+ const kept = inColumn && !inside ? keptMarkup(c, node) : void 0;
2262
+ if (kept) return [kept];
2263
+ const Named = () => null;
2264
+ Named.displayName = nameOf(recorded(c, node).element);
2265
+ if (inColumn && !inside && !c.told.has(node.at) && node.children.some((n) => n.kind === "raw" && parts(n.html).some((p) => "tag" in p))) {
2266
+ c.told.add(node.at);
2267
+ console.warn(`Column: <${Named.displayName}> rendered markup that isn't a block, so it's left out. Put HTML in an <Html> block.`);
2268
+ }
2269
+ return elements(c, node.children, place, Named, inside);
2270
+ }
2271
+ case "raw":
2272
+ if (inside) {
2273
+ const name = /^<([a-zA-Z][\w:-]*)/.exec(node.html)?.[1];
2274
+ return place === "row" && name && !inMarkup ? [React4.createElement(owner ?? name)] : [];
2275
+ }
2276
+ return parts(node.html).flatMap((p) => "text" in p ? [p.text] : inColumn ? [] : [React4.createElement(owner ?? (p.tag === "#" ? "div" : p.tag))]);
2277
+ default:
2278
+ return [element(c, node)];
2279
+ }
2280
+ });
2281
+ }
2282
+ function collected(children, guarded2 = true) {
2283
+ const once = (markup2) => {
2284
+ const collection = { nonce: nonce(), recorded: [], ...markup2 ? { markup: markup2 } : {}, ...idsSeen() ? {} : { blind: true } };
2285
+ const html = within(collection, () => renderToStaticMarkup(/* @__PURE__ */ jsx(Fragment, { children: guarded2 ? guard(children, collection) : children }), { identifierPrefix: COLLECTING_ID }));
2286
+ return { nodes: parse(html, collection), collection, told: /* @__PURE__ */ new Set() };
2287
+ };
2288
+ try {
2289
+ return once(false);
2290
+ } catch (error) {
2291
+ if (!guarded2) throw error;
2292
+ return once(true);
2293
+ }
2294
+ }
2295
+ function idsSeen() {
2296
+ let seen = false;
2297
+ const Probe = () => {
2298
+ try {
2299
+ seen = React4.useId().slice(1).startsWith(COLLECTING_ID);
2300
+ } catch {
2301
+ }
2302
+ return null;
2303
+ };
2304
+ renderToStaticMarkup(/* @__PURE__ */ jsx(Probe, {}), { identifierPrefix: COLLECTING_ID });
2305
+ return seen;
2306
+ }
2307
+ function nonce() {
2308
+ const random = globalThis.crypto?.getRandomValues?.(new Uint32Array(2));
2309
+ return random ? Array.from(random, (n) => n.toString(36)).join("") : `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
2310
+ }
2311
+ var collect = (children) => children !== null && typeof children === "object" ? scopeCache(children, () => collected(children)) : collected(children);
2312
+ function childrenHtml2(children, place) {
2313
+ const c = collect(children);
2314
+ return withoutMarkers(markup(c, c.nodes, (child) => within(null, () => renderToString(place(child)))), c.collection.nonce);
2315
+ }
2316
+ function normalizedChildren(children) {
2317
+ const c = collect(children);
2318
+ return elements(c, c.nodes, "root");
2319
+ }
2320
+ function normalizedRoot(root) {
2321
+ return React4.cloneElement(root, void 0, ...normalizedChildren(root.props.children));
2322
+ }
2323
+ var DEFAULT_VALUES14 = BODY_DEFAULTS;
1483
2324
  var MAX_PREVIEW_LENGTH = 150;
1484
2325
  var PADDING_CHARS = [
1485
2326
  "\xA0",
@@ -1490,6 +2331,9 @@ var PADDING_CHARS = [
1490
2331
  "\u200F",
1491
2332
  "\uFEFF"
1492
2333
  ];
2334
+ function escapePreviewText(text) {
2335
+ return text.split(/(<%[\w.]+%>)/).map((part, i) => i % 2 ? part : part.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
2336
+ }
1493
2337
  function generatePreviewHtml(text) {
1494
2338
  if (!text || text.trim().length === 0) return "";
1495
2339
  const truncated = text.length > MAX_PREVIEW_LENGTH ? text.slice(0, MAX_PREVIEW_LENGTH) : text;
@@ -1498,7 +2342,7 @@ function generatePreviewHtml(text) {
1498
2342
  for (let i = 0; i < paddingLength; i++) {
1499
2343
  padding += PADDING_CHARS[i % PADDING_CHARS.length];
1500
2344
  }
1501
- 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>`;
2345
+ 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>`;
1502
2346
  }
1503
2347
  function renderBodyToHtml(innerHTML, values, mode, previewText) {
1504
2348
  let finalInnerHtml = innerHTML;
@@ -1519,14 +2363,14 @@ function renderBodyToHtml(innerHTML, values, mode, previewText) {
1519
2363
  return raw.replace("min-height: 100vh; ", "").replace("min-height: 100vh;", "");
1520
2364
  }
1521
2365
  var Body = (props) => {
1522
- const { children, mode: modeProp, className, style, index = 0, config: configProp, previewText, ...semanticProps } = props;
2366
+ const { children, mode: modeProp, className, style, index = 0, config: configProp, previewText, fonts: _fonts, lang: _lang, ...semanticProps } = props;
1523
2367
  const resolvedConfig = { ...DEFAULT_CONFIG, ...configProp };
1524
2368
  const mode = modeProp ?? resolvedConfig.mode ?? "web";
1525
2369
  const _config = { ...resolvedConfig, mode };
1526
2370
  _config.__ids = {};
1527
2371
  const values = mergeValues(
1528
- DEFAULT_VALUES13,
1529
- mapSemanticProps(semanticProps, DEFAULT_VALUES13, "Body")
2372
+ DEFAULT_VALUES14,
2373
+ mapSemanticProps(semanticProps, DEFAULT_VALUES14, "Body")
1530
2374
  );
1531
2375
  const valuesWithMeta = {
1532
2376
  ...values,
@@ -1536,22 +2380,12 @@ var Body = (props) => {
1536
2380
  ...values._meta || {}
1537
2381
  }
1538
2382
  };
1539
- let enrichedChildren = children;
1540
- if (children) {
1541
- enrichedChildren = React.Children.map(children, (child) => {
1542
- if (React.isValidElement(child)) {
1543
- return React.cloneElement(child, {
1544
- _config,
1545
- bodyValues: values
1546
- });
1547
- }
1548
- return child;
1549
- });
1550
- }
1551
2383
  let innerHTML = "";
1552
- if (enrichedChildren) {
2384
+ if (children) {
1553
2385
  try {
1554
- innerHTML = ReactDOMServer.renderToString(enrichedChildren);
2386
+ innerHTML = withRenderScope(() => {
2387
+ return childrenHtml2(children, (child) => React4.cloneElement(child, { _config, bodyValues: values }));
2388
+ }, true);
1555
2389
  } catch (error) {
1556
2390
  console.error("Body: Failed to render children:", error);
1557
2391
  innerHTML = "";
@@ -1609,29 +2443,104 @@ UnlayerProvider.displayName = "UnlayerProvider";
1609
2443
  function useUnlayerConfig() {
1610
2444
  return useContext(getUnlayerContext());
1611
2445
  }
1612
- function ensureMeta(values, type, index = 0) {
1613
- return {
1614
- ...values,
1615
- _meta: {
1616
- htmlID: `u_content_${type.toLowerCase()}_${index + 1}`,
1617
- htmlClassNames: `u_content_${type.toLowerCase()}`,
1618
- ...values._meta || {}
2446
+
2447
+ // src/utils/device-overrides.ts
2448
+ var contentOptions = {
2449
+ Button: ["size", "fontSize", "lineHeight", "textAlign", "padding"],
2450
+ Image: ["src", "textAlign"],
2451
+ Heading: ["fontSize", "textAlign", "lineHeight"],
2452
+ Paragraph: ["fontSize", "textAlign", "lineHeight"],
2453
+ Menu: ["fontSize", "padding"],
2454
+ Divider: ["width", "textAlign"]
2455
+ };
2456
+ function css(value) {
2457
+ const text = String(value).trim();
2458
+ return text && !/[!\\"'\n\r]/.test(text) && cssValueSafe(text) ? text : void 0;
2459
+ }
2460
+ function ident(id) {
2461
+ 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}`);
2462
+ }
2463
+ function size(value) {
2464
+ const number = parseFloat(String(value));
2465
+ if (!Number.isFinite(number)) return;
2466
+ const rounded = parseFloat(number.toFixed(2));
2467
+ if (typeof value === "number" || String(value).endsWith("px")) return `${rounded}px`;
2468
+ if (String(value).endsWith("%")) return `${rounded}%`;
2469
+ }
2470
+ function collectDeviceStyles(values, collection, name, mode, styles) {
2471
+ const given = safeName(values._meta?.htmlID);
2472
+ if (!given) return;
2473
+ const id = ident(given);
2474
+ const options = collection === "contents" ? ["containerPadding", ...contentOptions[name] || []] : collection === "columns" ? ["padding", "border"] : ["padding"];
2475
+ for (const device of mode === "document" ? ["desktop"] : ["desktop", "mobile"]) {
2476
+ const overrides = values._override?.[device];
2477
+ if (!overrides) continue;
2478
+ for (const option of options) {
2479
+ if (!(option in overrides)) continue;
2480
+ const value = overrides[option];
2481
+ const property = option.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
2482
+ const prefix = collection === "rows" ? "row-" : collection === "columns" ? "col-" : "";
2483
+ const selector = (attribute) => `#${id} .v-${prefix}${property === attribute ? property : `${property}-${attribute}`}`;
2484
+ const push = (rule) => (styles[device] || (styles[device] = [])).push(rule);
2485
+ if (option === "padding" || option === "containerPadding") {
2486
+ if (!(value || value === 0) || css(value) === void 0) continue;
2487
+ const direct = collection === "rows" || mode === "web" && collection === "contents" && option === "containerPadding";
2488
+ const target = direct ? selector("padding").replace(`#${id} `, `#${id}`) : selector("padding");
2489
+ push(`${target} { padding: ${css(value)} !important; }`);
2490
+ if (mode === "email" && collection === "rows") {
2491
+ const parts2 = String(value).trim().split(/\s+/);
2492
+ const [top, bottom] = [parts2[0], parts2[2] || parts2[0]].map((part) => size(part) ?? css(part));
2493
+ if (top !== void 0 && bottom !== void 0) push(`${target}--vertical { padding-top: ${top} !important; padding-bottom: ${bottom} !important; }`);
2494
+ }
2495
+ } else if (option === "border") {
2496
+ if (value) push(`${selector("border")} { ${["Top", "Left", "Right", "Bottom"].map((d) => {
2497
+ const w = css(value[`border${d}Width`] || "0px") ?? "0px";
2498
+ return `border-${d.toLowerCase()}: ${w} ${css(value[`border${d}Style`] || "solid") ?? "solid"} ${parseInt(w) > 0 && css(value[`border${d}Color`]) || "transparent"} !important;`;
2499
+ }).join("")} }`);
2500
+ } else if (option === "src") {
2501
+ if (!value) continue;
2502
+ const merged = { ...values.src, ...value };
2503
+ const maximum = merged.autoWidth === false ? size(merged.maxWidth) : void 0;
2504
+ push(`${selector("width")} { width: ${maximum || "100%"} !important; }`);
2505
+ push(`${selector("max-width")} { max-width: ${size(maximum || merged.width || "100%")} !important; }`);
2506
+ } else if (option === "size" || option === "width") {
2507
+ if (!value) continue;
2508
+ push(`${selector("width")} { width: ${size(value.width) && !value.autoWidth ? size(value.width) : "auto"} !important; }`);
2509
+ push(`${selector("max-width")} { max-width: 100% !important; }`);
2510
+ } else {
2511
+ if (!(value || option === "lineHeight" && `${value}` === "0")) continue;
2512
+ const rendered = css(option === "fontSize" && typeof value === "number" ? `${value}px` : value);
2513
+ if (rendered === void 0) continue;
2514
+ push(`${selector(property)} { ${property}: ${rendered} !important; }`);
2515
+ }
1619
2516
  }
1620
- };
2517
+ }
1621
2518
  }
1622
- function getDisplayName(element) {
1623
- const type = element.type;
1624
- return type?.displayName || type?.name;
2519
+ function deviceStylesCss(styles, mode) {
2520
+ return Object.entries(styles).filter(([, rules]) => rules.length).map(([device, rules]) => {
2521
+ const query = device === "mobile" ? "@media (max-width: 480px)" : `@media (min-width: ${mode === "document" ? 1 : 481}px)`;
2522
+ return `${query} { ${rules.join(" ")} }`;
2523
+ }).join("\n");
1625
2524
  }
1626
- function collectChildren(node) {
1627
- const result = [];
1628
- React.Children.forEach(node, (child) => {
1629
- if (React.isValidElement(child)) {
1630
- result.push(child);
1631
- }
1632
- });
1633
- return result;
2525
+ function deviceVisibilityCss(mode) {
2526
+ if (mode === "document") return "";
2527
+ const extra = mode === "email" ? "max-height: 0px; overflow: hidden; " : "";
2528
+ 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" : "";
2529
+ return reset + `@media (max-width: 480px) { .hide-mobile { ${extra}display: none !important; } }
2530
+ @media (min-width: 481px) { .hide-desktop { ${extra}display: none !important; } }`;
2531
+ }
2532
+ var schemaVersion = schemaVersion$1 ?? 24;
2533
+ function unwrapForJson(element2) {
2534
+ try {
2535
+ return unwrapRoot(element2);
2536
+ } catch (cause) {
2537
+ const detail = cause instanceof Error ? cause.message : String(cause);
2538
+ throw new Error(
2539
+ `[Unlayer] renderToJson: could not unwrap <${componentName(element2) || "wrapper"}>. ${UNWRAP_ADVICE} (${detail})`
2540
+ );
2541
+ }
1634
2542
  }
2543
+ var collectChildren = (node) => React4.Children.toArray(node).filter(React4.isValidElement);
1635
2544
  function extractSemanticProps(props, extraKeys = []) {
1636
2545
  const internalKeys = /* @__PURE__ */ new Set([
1637
2546
  "children",
@@ -1646,6 +2555,8 @@ function extractSemanticProps(props, extraKeys = []) {
1646
2555
  "_config",
1647
2556
  "config",
1648
2557
  "previewText",
2558
+ "fonts",
2559
+ "lang",
1649
2560
  "layout",
1650
2561
  "collection",
1651
2562
  ...extraKeys
@@ -1662,68 +2573,313 @@ function nextCounter(counters, key) {
1662
2573
  counters[key] = (counters[key] || 0) + 1;
1663
2574
  return counters[key];
1664
2575
  }
1665
- function callHead(head, values, bodyValues, displayMode, headConfig, styles, scripts, tags) {
1666
- if (!head) return;
1667
- const headArgs = [
1668
- values,
1669
- bodyValues,
1670
- {
1671
- displayMode,
1672
- isViewer: false,
1673
- variant: null,
1674
- type: "rows",
1675
- headConfig
1676
- }
1677
- ];
1678
- const css = head.css?.(...headArgs);
1679
- if (css) styles.push(css);
1680
- const js = head.js?.(...headArgs);
1681
- if (js) scripts.push(js);
1682
- const headTags = head.tags?.(...headArgs);
1683
- if (headTags) tags.push(...headTags);
2576
+ function toContentType(displayName) {
2577
+ if (displayName === "Paragraph") return "text";
2578
+ return displayName.toLowerCase();
1684
2579
  }
1685
- function walkItem(element, bodyValues, displayMode, headConfig, counters, styles, scripts, tags) {
1686
- const componentType = element.type;
2580
+ function makeId(prefix, counter) {
2581
+ return `${prefix}_${counter}`;
2582
+ }
2583
+ function extractTextFromTextJson(textJson) {
2584
+ try {
2585
+ const parsed = JSON.parse(textJson);
2586
+ if (parsed.__html) return parsed.__html;
2587
+ const root = parsed?.root;
2588
+ if (!root?.children) return "";
2589
+ const paragraphs = [];
2590
+ for (const paragraph of root.children) {
2591
+ if (!paragraph?.children) continue;
2592
+ const parts2 = [];
2593
+ for (const node of paragraph.children) {
2594
+ if (node?.text) parts2.push(node.text);
2595
+ }
2596
+ paragraphs.push(parts2.join(""));
2597
+ }
2598
+ return paragraphs.join("\n");
2599
+ } catch {
2600
+ return "";
2601
+ }
2602
+ }
2603
+ function processItem(element2, counters, layout = {}, metaName) {
2604
+ const componentType = element2.type;
2605
+ const config = componentType[UNLAYER_CONFIG_KEY];
2606
+ if (!config && componentType?.[KEPT]) {
2607
+ return processItem(componentType[KEPT], counters, layout, (componentType.displayName || componentType.name || "component").toLowerCase());
2608
+ }
2609
+ if (!config) {
2610
+ const name2 = componentName(element2) || "Unknown";
2611
+ throw new Error(
2612
+ `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
2613
+ );
2614
+ }
2615
+ const { name, defaultValues, propMapper, slug } = config;
2616
+ const contentType = config.contentType ?? toContentType(name);
2617
+ const metaBase = `u_content_${metaName ?? config.metaName ?? contentType}`;
2618
+ const count = nextCounter(counters, metaBase);
2619
+ const id = makeId(metaBase, count);
2620
+ const { children, ...restProps } = element2.props;
2621
+ const mappedValues = propMapper({ children, ...restProps });
2622
+ const finalValues = mergeValues(defaultValues, mappedValues);
2623
+ if (contentType === "text" && finalValues.textJson) {
2624
+ finalValues.text = extractTextFromTextJson(finalValues.textJson);
2625
+ delete finalValues.textJson;
2626
+ }
2627
+ const values = {
2628
+ containerPadding: "10px",
2629
+ ...finalValues,
2630
+ _meta: {
2631
+ htmlID: id,
2632
+ htmlClassNames: metaBase,
2633
+ ...finalValues._meta || {}
2634
+ },
2635
+ selectable: true,
2636
+ draggable: true,
2637
+ duplicatable: true,
2638
+ deletable: true,
2639
+ hideable: true
2640
+ };
2641
+ const itemSrc = values.src;
2642
+ if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
2643
+ const availableWidth = contentSlotWidth({
2644
+ ...layout,
2645
+ containerPadding: values.containerPadding
2646
+ });
2647
+ values.src = pinImageSrc(itemSrc, availableWidth);
2648
+ }
2649
+ return slug ? { type: contentType, slug, values } : { type: contentType, values };
2650
+ }
2651
+ function processColumn(element2, counters, layout = {}) {
2652
+ const count = nextCounter(counters, "u_column");
2653
+ const id = makeId("u_column", count);
2654
+ const semanticProps = extractSemanticProps(element2.props);
2655
+ const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
2656
+ const values = mergeValues(COLUMN_DEFAULTS, mapped);
2657
+ const valuesWithMeta = {
2658
+ ...values,
2659
+ _meta: {
2660
+ htmlID: id,
2661
+ htmlClassNames: "u_column",
2662
+ ...values._meta || {}
2663
+ }
2664
+ };
2665
+ const contents = [];
2666
+ const children = collectChildren(element2.props.children);
2667
+ const itemLayout = { ...layout, columnValues: valuesWithMeta };
2668
+ for (const child of children) {
2669
+ contents.push(processItem(child, counters, itemLayout));
2670
+ }
2671
+ return { contents, values: valuesWithMeta };
2672
+ }
2673
+ function processRow(element2, counters, parentLayout = {}) {
2674
+ const count = nextCounter(counters, "u_row");
2675
+ const id = makeId("u_row", count);
2676
+ const { layout, cells: propsCells } = element2.props;
2677
+ let cells;
2678
+ if (layout) {
2679
+ cells = layout.cells;
2680
+ } else if (propsCells) {
2681
+ cells = propsCells;
2682
+ } else {
2683
+ const columnCount = Math.max(
2684
+ 1,
2685
+ collectChildren(element2.props.children).filter(
2686
+ (child) => getDisplayName(child) === "Column"
2687
+ ).length
2688
+ );
2689
+ cells = Array(columnCount).fill(1);
2690
+ }
2691
+ const semanticProps = extractSemanticProps(element2.props, ["layout"]);
2692
+ const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
2693
+ const values = mergeValues(ROW_DEFAULTS, mapped);
2694
+ const valuesWithMeta = {
2695
+ ...values,
2696
+ columns: values.columns ?? false,
2697
+ cells,
2698
+ _meta: {
2699
+ htmlID: id,
2700
+ htmlClassNames: "u_row",
2701
+ ...values._meta || {}
2702
+ },
2703
+ selectable: true,
2704
+ draggable: true,
2705
+ duplicatable: true,
2706
+ deletable: true,
2707
+ hideable: true
2708
+ };
2709
+ const columns = [];
2710
+ const children = collectChildren(element2.props.children);
2711
+ const columnLayout = {
2712
+ bodyValues: parentLayout.bodyValues,
2713
+ rowValues: valuesWithMeta,
2714
+ rowCells: cells
2715
+ };
2716
+ let columnIndex = 0;
2717
+ for (const child of children) {
2718
+ const name = getDisplayName(child);
2719
+ if (name === "Column") {
2720
+ columns.push(
2721
+ processColumn(child, counters, { ...columnLayout, columnIndex })
2722
+ );
2723
+ columnIndex += 1;
2724
+ } else {
2725
+ console.warn(
2726
+ `[Unlayer] renderToJson: <${componentName(child) ?? String(child.type)}> is not a valid Row child. Only <Column> is allowed.`
2727
+ );
2728
+ }
2729
+ }
2730
+ return { cells, columns, values: valuesWithMeta };
2731
+ }
2732
+ function processBody(element2, counters) {
2733
+ const id = "u_body";
2734
+ const semanticProps = extractSemanticProps(element2.props);
2735
+ const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
2736
+ const values = mergeValues(BODY_DEFAULTS, mapped);
2737
+ if ("textDirection" in values) {
2738
+ const direction = textDirectionOf(values.textDirection);
2739
+ if (direction) values.textDirection = direction;
2740
+ else delete values.textDirection;
2741
+ }
2742
+ const previewText = element2.props.previewText;
2743
+ if (previewText !== void 0) {
2744
+ values.preheaderText = previewText;
2745
+ }
2746
+ const valuesWithMeta = {
2747
+ ...values,
2748
+ _meta: {
2749
+ htmlID: id,
2750
+ htmlClassNames: "u_body",
2751
+ ...values._meta || {}
2752
+ }
2753
+ };
2754
+ const rows = [];
2755
+ const children = collectChildren(element2.props.children);
2756
+ for (const child of children) {
2757
+ const name = getDisplayName(child);
2758
+ if (name === "Row") {
2759
+ rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
2760
+ } else {
2761
+ console.warn(
2762
+ `[Unlayer] renderToJson: <${componentName(child) ?? String(child.type)}> is not a valid Body child. Only <Row> is allowed.`
2763
+ );
2764
+ }
2765
+ }
2766
+ return {
2767
+ rows,
2768
+ values: valuesWithMeta
2769
+ };
2770
+ }
2771
+ function renderRowToJson(element2) {
2772
+ const displayName = getDisplayName(element2);
2773
+ if (displayName !== "Row") {
2774
+ throw new Error(
2775
+ `[Unlayer] renderRowToJson: Element must be <Row>, but got <${componentName(element2) || "unknown"}>. For full designs, use renderToJson instead.`
2776
+ );
2777
+ }
2778
+ const counters = {};
2779
+ return withRenderScope(() => processRow(normalizedChildren(element2).find((c) => React4.isValidElement(c)) ?? element2, counters));
2780
+ }
2781
+ function renderToJson(element2) {
2782
+ return withRenderScope(() => designJson(element2));
2783
+ }
2784
+ function designJson(element2) {
2785
+ element2 = unwrapForJson(element2);
2786
+ const displayName = getDisplayName(element2);
2787
+ if (displayName && ROOT_NAMES.has(displayName)) element2 = normalizedRoot(element2);
2788
+ if (!displayName || !ROOT_NAMES.has(displayName)) {
2789
+ throw new Error(
2790
+ `[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>`
2791
+ );
2792
+ }
2793
+ const counters = {};
2794
+ const body = processBody(element2, counters);
2795
+ return {
2796
+ counters,
2797
+ body,
2798
+ schemaVersion
2799
+ };
2800
+ }
2801
+
2802
+ // src/utils/extract-head.ts
2803
+ function ensureMeta(values, type, index = 0) {
2804
+ return {
2805
+ ...values,
2806
+ _meta: {
2807
+ htmlID: `${["row", "column"].includes(type) ? "u" : "u_content"}_${type.toLowerCase()}_${index + 1}`,
2808
+ htmlClassNames: `u_content_${type.toLowerCase()}`,
2809
+ ...values._meta || {}
2810
+ }
2811
+ };
2812
+ }
2813
+ var collectChildren2 = (node) => React4.Children.toArray(node).filter(React4.isValidElement);
2814
+ function callHead(head, values, bodyValues, displayMode, headConfig, styles, scripts, tags) {
2815
+ if (!head) return;
2816
+ const headArgs = [
2817
+ values,
2818
+ bodyValues,
2819
+ {
2820
+ displayMode,
2821
+ isViewer: false,
2822
+ variant: null,
2823
+ type: "rows",
2824
+ headConfig
2825
+ }
2826
+ ];
2827
+ const css2 = head.css?.(...headArgs);
2828
+ if (css2) styles.push(css2);
2829
+ const js = head.js?.(...headArgs);
2830
+ if (js) scripts.push(js);
2831
+ const headTags = head.tags?.(...headArgs);
2832
+ if (headTags) tags.push(...headTags);
2833
+ }
2834
+ function walkItem(element2, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2835
+ const componentType = element2.type;
1687
2836
  const config = componentType[UNLAYER_CONFIG_KEY];
1688
- if (!config) return;
2837
+ if (!config) {
2838
+ if (componentType?.[KEPT]) nextCounter(counters, `u_content_${(componentType.displayName || componentType.name || "component").toLowerCase()}`);
2839
+ return;
2840
+ }
1689
2841
  const { name, defaultValues, propMapper } = config;
1690
- const { children, ...restProps } = element.props;
2842
+ const { children, ...restProps } = element2.props;
1691
2843
  const mappedValues = propMapper({ children, ...restProps });
1692
2844
  const finalValues = mergeValues(defaultValues, mappedValues);
1693
2845
  const contentType = config.metaName ?? name.toLowerCase();
1694
2846
  const count = nextCounter(counters, `u_content_${contentType}`);
1695
2847
  const valuesWithMeta = ensureMeta(finalValues, contentType, count - 1);
2848
+ collectDeviceStyles(valuesWithMeta, "contents", name, displayMode, deviceStyles);
1696
2849
  const head = config.head ?? heads[name];
1697
2850
  callHead(head, valuesWithMeta, bodyValues, displayMode, headConfig, styles, scripts, tags);
1698
2851
  }
1699
- function walkColumn(element, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags) {
1700
- const semanticProps = extractSemanticProps(element.props);
2852
+ function walkColumn(element2, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2853
+ const semanticProps = extractSemanticProps(element2.props);
1701
2854
  const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
1702
2855
  const count = nextCounter(counters, "u_column");
1703
2856
  const columnValues = ensureMeta(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
2857
+ collectDeviceStyles(columnValues, "columns", "Column", displayMode, deviceStyles);
1704
2858
  const columnHead = heads["Column"];
1705
2859
  callHead(columnHead, columnValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1706
- const children = collectChildren(element.props.children);
2860
+ const children = collectChildren2(element2.props.children);
1707
2861
  for (const child of children) {
1708
- walkItem(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags);
2862
+ walkItem(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1709
2863
  }
1710
2864
  }
1711
- function walkRow(element, bodyValues, displayMode, headConfig, counters, styles, scripts, tags) {
1712
- const semanticProps = extractSemanticProps(element.props, ["layout"]);
2865
+ function walkRow(element2, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2866
+ const semanticProps = extractSemanticProps(element2.props, ["layout"]);
1713
2867
  const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
1714
2868
  const count = nextCounter(counters, "u_row");
1715
2869
  const rowValues = ensureMeta(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
2870
+ collectDeviceStyles(rowValues, "rows", "Row", displayMode, deviceStyles);
1716
2871
  const rowHead = heads["Row"];
1717
2872
  callHead(rowHead, rowValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1718
- const children = collectChildren(element.props.children);
2873
+ const children = collectChildren2(element2.props.children);
1719
2874
  for (const child of children) {
1720
2875
  const name = getDisplayName(child);
1721
2876
  if (name === "Column") {
1722
- walkColumn(child, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags);
2877
+ walkColumn(child, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1723
2878
  }
1724
2879
  }
1725
2880
  }
1726
- function extractHeadFromTree(element, options) {
2881
+ function extractHeadFromTree(element2, options) {
2882
+ element2 = normalizedRoot(element2);
1727
2883
  const { displayMode } = options;
1728
2884
  const headConfig = {
1729
2885
  hasFeature: options.headConfig?.hasFeature ?? (() => false),
@@ -1733,18 +2889,22 @@ function extractHeadFromTree(element, options) {
1733
2889
  const scripts = [];
1734
2890
  const tags = [];
1735
2891
  const counters = {};
1736
- const semanticProps = extractSemanticProps(element.props);
2892
+ const deviceStyles = {};
2893
+ const semanticProps = extractSemanticProps(element2.props);
1737
2894
  const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
1738
2895
  const bodyValues = ensureMeta(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1739
2896
  const bodyHead = heads["Body"];
1740
2897
  callHead(bodyHead, bodyValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1741
- const children = collectChildren(element.props.children);
2898
+ const children = collectChildren2(element2.props.children);
1742
2899
  for (const child of children) {
1743
2900
  const name = getDisplayName(child);
1744
2901
  if (name === "Row") {
1745
- walkRow(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags);
2902
+ walkRow(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1746
2903
  }
1747
2904
  }
2905
+ styles.unshift(deviceVisibilityCss(displayMode));
2906
+ const overrides = deviceStylesCss(deviceStyles, displayMode);
2907
+ if (overrides) styles.push(overrides);
1748
2908
  const uniqueTags = [...new Set(tags.filter(Boolean))];
1749
2909
  return {
1750
2910
  css: styles.filter(Boolean).join("\n"),
@@ -1764,7 +2924,7 @@ function emailLayout(args) {
1764
2924
  fontLinks && `<!--[if !mso]><!-->${fontLinks}<!--<![endif]-->`
1765
2925
  ].filter(Boolean).join("\n ");
1766
2926
  return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1767
- <html${dir} xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
2927
+ <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">
1768
2928
  <head>
1769
2929
  <!--[if gte mso 9]>
1770
2930
  <xml>
@@ -1788,7 +2948,7 @@ function documentLayout(args) {
1788
2948
  const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
1789
2949
  const head = [titleTag, tagLines, styleTag, scriptTag, fontLinks].filter(Boolean).join("\n ");
1790
2950
  return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1791
- <html${dir} xmlns="http://www.w3.org/1999/xhtml">
2951
+ <html${dir}${args.lang || ""} xmlns="http://www.w3.org/1999/xhtml">
1792
2952
  <head>
1793
2953
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1794
2954
  ${head}
@@ -1801,7 +2961,7 @@ function webLayout(args) {
1801
2961
  const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
1802
2962
  const head = [titleTag, fontLinks, tagLines, styleTag, scriptTag].filter(Boolean).join("\n ");
1803
2963
  return `<!doctype html>
1804
- <html${dir}>
2964
+ <html${dir}${args.lang || ""}>
1805
2965
  <head>
1806
2966
  <meta charset="utf-8">
1807
2967
  <meta http-equiv="x-ua-compatible" content="ie=edge">
@@ -1822,20 +2982,25 @@ var MODE_BY_WRAPPER = {
1822
2982
  Page: "web",
1823
2983
  Document: "document"
1824
2984
  };
1825
- function resolveDisplayMode(element, mergedConfig) {
1826
- const displayName = element.type?.displayName;
2985
+ function resolveConfig(element2, config) {
2986
+ const props = element2.props;
2987
+ const textDirection = textDirectionOf(props.textDirection ?? props.values?.textDirection);
2988
+ return { ...DEFAULT_CONFIG, ...textDirection !== void 0 ? { textDirection } : {}, ...config };
2989
+ }
2990
+ function resolveDisplayMode(element2, mergedConfig) {
2991
+ const displayName = element2.type?.displayName;
1827
2992
  const wrapperMode = displayName ? MODE_BY_WRAPPER[displayName] : void 0;
1828
- return wrapperMode ?? element.props.mode ?? mergedConfig.mode ?? "web";
2993
+ return wrapperMode ?? element2.props.mode ?? mergedConfig.mode ?? "web";
1829
2994
  }
1830
- function renderBody(element, config) {
1831
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
2995
+ function renderBody(element2, config, name = "renderToHtml") {
2996
+ const mergedConfig = resolveConfig(element2, config);
1832
2997
  try {
1833
- const enriched = React.cloneElement(element, { config: mergedConfig });
2998
+ const enriched = React4.cloneElement(element2, { config: mergedConfig });
1834
2999
  return renderToStaticMarkup(enriched);
1835
3000
  } catch (error) {
1836
3001
  const message = error instanceof Error ? error.message : String(error);
1837
3002
  throw new Error(
1838
- `[Unlayer] renderToHtml failed: ${message}
3003
+ `[Unlayer] ${name} failed: ${message}
1839
3004
  Tip: Ensure your tree uses Body > Row > Column > Item structure.`
1840
3005
  );
1841
3006
  }
@@ -1847,30 +3012,41 @@ function stripOuterDiv(html) {
1847
3012
  function escapeForHtml(text) {
1848
3013
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1849
3014
  }
1850
- function renderToHtml(element, options) {
1851
- const { title, fonts = [], ...config } = options ?? {};
1852
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
1853
- const displayMode = resolveDisplayMode(element, mergedConfig);
1854
- const wrapperName = element.type?.displayName;
3015
+ function renderToHtml(element2, options) {
3016
+ return withRenderScope(() => htmlDocument(element2, options));
3017
+ }
3018
+ function styleText(css2) {
3019
+ return css2.replace(/<\/(style)/gi, "<\\/$1");
3020
+ }
3021
+ function htmlDocument(element2, options) {
3022
+ const { title, lang: optionLang, fonts: optionFonts = [], ...config } = options ?? {};
3023
+ element2 = htmlRoot(element2, "renderToHtml");
3024
+ const rootFonts = element2.props?.fonts ?? [];
3025
+ const fonts = [...rootFonts, ...optionFonts].filter((font, i, all) => font?.url && all.findIndex((f) => f?.url === font.url) === i);
3026
+ const lang = optionLang ?? element2.props.lang;
3027
+ const mergedConfig = resolveConfig(element2, config);
3028
+ const displayMode = resolveDisplayMode(element2, mergedConfig);
3029
+ const wrapperName = element2.type?.displayName;
1855
3030
  const isUnlayerWrapper = wrapperName === "Body" || (wrapperName ? wrapperName in MODE_BY_WRAPPER : false);
1856
- const rawBody = renderBody(element, config);
3031
+ const rawBody = renderBody(element2, config);
1857
3032
  const body = isUnlayerWrapper ? stripOuterDiv(rawBody) : rawBody;
1858
- const { css, js, tags } = extractHeadFromTree(element, {
3033
+ const { css: css2, js, tags } = extractHeadFromTree(element2, {
1859
3034
  displayMode,
1860
3035
  headConfig: mergedConfig.headConfig
1861
3036
  });
1862
3037
  const layoutArgs = {
3038
+ lang: lang ? ` lang="${escapeForHtml(String(lang))}"` : "",
1863
3039
  dir: mergedConfig.textDirection ? ` dir="${escapeForHtml(String(mergedConfig.textDirection))}"` : "",
1864
3040
  titleTag: title ? `<title>${escapeForHtml(title.trim())}</title>` : "",
1865
- styleTag: css ? `<style type="text/css">
1866
- ${css}
3041
+ styleTag: css2 ? `<style type="text/css">
3042
+ ${styleText(css2)}
1867
3043
  </style>` : "",
1868
3044
  scriptTag: js ? `<script type="application/javascript">
1869
3045
  ${js}
1870
3046
  </script>` : "",
1871
3047
  tagLines: tags.join("\n"),
1872
3048
  fontLinks: fonts.filter((font) => font?.url).map(
1873
- (font) => `<link href="${escapeForHtml(font.url)}" rel="stylesheet" type="text/css">`
3049
+ (font) => `<link href="${escapeForHtml(String(font.url))}" rel="stylesheet" type="text/css">`
1874
3050
  ).join(""),
1875
3051
  body
1876
3052
  };
@@ -1878,21 +3054,29 @@ ${js}
1878
3054
  if (displayMode === "document") return documentLayout(layoutArgs);
1879
3055
  return webLayout(layoutArgs);
1880
3056
  }
1881
- function renderToPlainText(element, config) {
1882
- const html = renderBody(element, config);
3057
+ function renderToPlainText(element2, config) {
3058
+ const html = withRenderScope(() => {
3059
+ const root = htmlRoot(element2, "renderToPlainText");
3060
+ normalizedRoot(root);
3061
+ return renderBody(root, config, "renderToPlainText");
3062
+ });
1883
3063
  return htmlToPlainText(html);
1884
3064
  }
1885
- function renderToHtmlParts(element, config) {
1886
- const body = renderBody(element, config);
1887
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
1888
- const displayMode = resolveDisplayMode(element, mergedConfig);
1889
- const { css, js, tags } = extractHeadFromTree(element, {
3065
+ function renderToHtmlParts(element2, config) {
3066
+ return withRenderScope(() => htmlParts(element2, config));
3067
+ }
3068
+ function htmlParts(element2, config) {
3069
+ element2 = htmlRoot(element2, "renderToHtmlParts");
3070
+ const body = renderBody(element2, config, "renderToHtmlParts");
3071
+ const mergedConfig = resolveConfig(element2, config);
3072
+ const displayMode = resolveDisplayMode(element2, mergedConfig);
3073
+ const { css: css2, js, tags } = extractHeadFromTree(element2, {
1890
3074
  displayMode,
1891
3075
  headConfig: mergedConfig.headConfig
1892
3076
  });
1893
3077
  const headParts = [];
1894
- if (css) {
1895
- headParts.push(`<style>${css}</style>`);
3078
+ if (css2) {
3079
+ headParts.push(`<style>${styleText(css2)}</style>`);
1896
3080
  }
1897
3081
  if (js) {
1898
3082
  headParts.push(`<script>${js}</script>`);
@@ -1903,291 +3087,11 @@ function renderToHtmlParts(element, config) {
1903
3087
  return {
1904
3088
  head: headParts.join("\n"),
1905
3089
  body,
1906
- css,
3090
+ css: css2,
1907
3091
  js,
1908
3092
  tags
1909
3093
  };
1910
3094
  }
1911
- var schemaVersion = schemaVersion$1 ?? 24;
1912
- function getDisplayName2(element) {
1913
- const type = element.type;
1914
- return type?.displayName || type?.name;
1915
- }
1916
- var VALID_ROOTS = /* @__PURE__ */ new Set(["Body", "Email", "Page", "Document"]);
1917
- function unwrapRoot(element) {
1918
- let current = element;
1919
- for (let depth = 0; depth < 10; depth++) {
1920
- const name = getDisplayName2(current);
1921
- if (name && VALID_ROOTS.has(name)) break;
1922
- const type = current.type;
1923
- const isPlainFunctionComponent = typeof type === "function" && !type.prototype?.isReactComponent;
1924
- if (!isPlainFunctionComponent) break;
1925
- let produced;
1926
- try {
1927
- produced = type({ ...current.props });
1928
- } catch (cause) {
1929
- const detail = cause instanceof Error ? cause.message : String(cause);
1930
- throw new Error(
1931
- `[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})`
1932
- );
1933
- }
1934
- if (!React.isValidElement(produced)) break;
1935
- current = produced;
1936
- }
1937
- return current;
1938
- }
1939
- function collectChildren2(node) {
1940
- const result = [];
1941
- React.Children.forEach(node, (child) => {
1942
- if (React.isValidElement(child)) {
1943
- result.push(child);
1944
- }
1945
- });
1946
- return result;
1947
- }
1948
- function extractSemanticProps2(props, extraKeys = []) {
1949
- const internalKeys = /* @__PURE__ */ new Set([
1950
- "children",
1951
- "mode",
1952
- "className",
1953
- "style",
1954
- "index",
1955
- "colIndex",
1956
- "cells",
1957
- "bodyValues",
1958
- "rowValues",
1959
- "_config",
1960
- "config",
1961
- "previewText",
1962
- "layout",
1963
- "collection",
1964
- ...extraKeys
1965
- ]);
1966
- const result = {};
1967
- for (const [key, value] of Object.entries(props)) {
1968
- if (!internalKeys.has(key) && value !== void 0) {
1969
- result[key] = value;
1970
- }
1971
- }
1972
- return result;
1973
- }
1974
- function nextCounter2(counters, key) {
1975
- counters[key] = (counters[key] || 0) + 1;
1976
- return counters[key];
1977
- }
1978
- function toContentType(displayName) {
1979
- if (displayName === "Paragraph") return "text";
1980
- return displayName.toLowerCase();
1981
- }
1982
- function makeId(prefix, counter) {
1983
- return `${prefix}_${counter}`;
1984
- }
1985
- function extractTextFromTextJson(textJson) {
1986
- try {
1987
- const parsed = JSON.parse(textJson);
1988
- if (parsed.__html) return parsed.__html;
1989
- const root = parsed?.root;
1990
- if (!root?.children) return "";
1991
- const paragraphs = [];
1992
- for (const paragraph of root.children) {
1993
- if (!paragraph?.children) continue;
1994
- const parts = [];
1995
- for (const node of paragraph.children) {
1996
- if (node?.text) parts.push(node.text);
1997
- }
1998
- paragraphs.push(parts.join(""));
1999
- }
2000
- return paragraphs.join("\n");
2001
- } catch {
2002
- return "";
2003
- }
2004
- }
2005
- function processItem(element, counters, layout = {}) {
2006
- const componentType = element.type;
2007
- const config = componentType[UNLAYER_CONFIG_KEY];
2008
- if (!config) {
2009
- const name2 = getDisplayName2(element) || "Unknown";
2010
- throw new Error(
2011
- `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
2012
- );
2013
- }
2014
- const { name, defaultValues, propMapper, slug } = config;
2015
- const contentType = config.contentType ?? toContentType(name);
2016
- const metaBase = `u_content_${config.metaName ?? contentType}`;
2017
- const count = nextCounter2(counters, metaBase);
2018
- const id = makeId(metaBase, count);
2019
- const { children, ...restProps } = element.props;
2020
- const mappedValues = propMapper({ children, ...restProps });
2021
- const finalValues = mergeValues(defaultValues, mappedValues);
2022
- if (contentType === "text" && finalValues.textJson) {
2023
- finalValues.text = extractTextFromTextJson(finalValues.textJson);
2024
- delete finalValues.textJson;
2025
- }
2026
- const values = {
2027
- containerPadding: "10px",
2028
- ...finalValues,
2029
- _meta: {
2030
- htmlID: id,
2031
- htmlClassNames: metaBase,
2032
- ...finalValues._meta || {}
2033
- },
2034
- selectable: true,
2035
- draggable: true,
2036
- duplicatable: true,
2037
- deletable: true,
2038
- hideable: true
2039
- };
2040
- const itemSrc = values.src;
2041
- if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
2042
- const availableWidth = contentSlotWidth({
2043
- ...layout,
2044
- containerPadding: values.containerPadding
2045
- });
2046
- values.src = pinImageSrc(itemSrc, availableWidth);
2047
- }
2048
- return slug ? { type: contentType, slug, values } : { type: contentType, values };
2049
- }
2050
- function processColumn(element, counters, layout = {}) {
2051
- const count = nextCounter2(counters, "u_column");
2052
- const id = makeId("u_column", count);
2053
- const semanticProps = extractSemanticProps2(element.props);
2054
- const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
2055
- const values = mergeValues(COLUMN_DEFAULTS, mapped);
2056
- const valuesWithMeta = {
2057
- ...values,
2058
- _meta: {
2059
- htmlID: id,
2060
- htmlClassNames: "u_column",
2061
- ...values._meta || {}
2062
- }
2063
- };
2064
- const contents = [];
2065
- const children = collectChildren2(element.props.children);
2066
- const itemLayout = { ...layout, columnValues: valuesWithMeta };
2067
- for (const child of children) {
2068
- contents.push(processItem(child, counters, itemLayout));
2069
- }
2070
- return { contents, values: valuesWithMeta };
2071
- }
2072
- function processRow(element, counters, parentLayout = {}) {
2073
- const count = nextCounter2(counters, "u_row");
2074
- const id = makeId("u_row", count);
2075
- const { layout, cells: propsCells } = element.props;
2076
- let cells;
2077
- if (layout) {
2078
- cells = layout.cells;
2079
- } else if (propsCells) {
2080
- cells = propsCells;
2081
- } else {
2082
- const columnCount = Math.max(
2083
- 1,
2084
- collectChildren2(element.props.children).filter(
2085
- (child) => getDisplayName2(child) === "Column"
2086
- ).length
2087
- );
2088
- cells = Array(columnCount).fill(1);
2089
- }
2090
- const semanticProps = extractSemanticProps2(element.props, ["layout"]);
2091
- const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
2092
- const values = mergeValues(ROW_DEFAULTS, mapped);
2093
- const valuesWithMeta = {
2094
- ...values,
2095
- columns: values.columns ?? false,
2096
- cells,
2097
- _meta: {
2098
- htmlID: id,
2099
- htmlClassNames: "u_row",
2100
- ...values._meta || {}
2101
- },
2102
- selectable: true,
2103
- draggable: true,
2104
- duplicatable: true,
2105
- deletable: true,
2106
- hideable: true
2107
- };
2108
- const columns = [];
2109
- const children = collectChildren2(element.props.children);
2110
- const columnLayout = {
2111
- bodyValues: parentLayout.bodyValues,
2112
- rowValues: valuesWithMeta,
2113
- rowCells: cells
2114
- };
2115
- let columnIndex = 0;
2116
- for (const child of children) {
2117
- const name = getDisplayName2(child);
2118
- if (name === "Column") {
2119
- columns.push(
2120
- processColumn(child, counters, { ...columnLayout, columnIndex })
2121
- );
2122
- columnIndex += 1;
2123
- } else {
2124
- console.warn(
2125
- `[Unlayer] renderToJson: <${name}> is not a valid Row child. Only <Column> is allowed.`
2126
- );
2127
- }
2128
- }
2129
- return { cells, columns, values: valuesWithMeta };
2130
- }
2131
- function processBody(element, counters) {
2132
- const id = "u_body";
2133
- const semanticProps = extractSemanticProps2(element.props);
2134
- const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
2135
- const values = mergeValues(BODY_DEFAULTS, mapped);
2136
- const previewText = element.props.previewText;
2137
- if (previewText !== void 0) {
2138
- values.preheaderText = previewText;
2139
- }
2140
- const valuesWithMeta = {
2141
- ...values,
2142
- _meta: {
2143
- htmlID: id,
2144
- htmlClassNames: "u_body",
2145
- ...values._meta || {}
2146
- }
2147
- };
2148
- const rows = [];
2149
- const children = collectChildren2(element.props.children);
2150
- for (const child of children) {
2151
- const name = getDisplayName2(child);
2152
- if (name === "Row") {
2153
- rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
2154
- } else {
2155
- console.warn(
2156
- `[Unlayer] renderToJson: <${name}> is not a valid Body child. Only <Row> is allowed.`
2157
- );
2158
- }
2159
- }
2160
- return {
2161
- rows,
2162
- values: valuesWithMeta
2163
- };
2164
- }
2165
- function renderRowToJson(element) {
2166
- const displayName = getDisplayName2(element);
2167
- if (displayName !== "Row") {
2168
- throw new Error(
2169
- `[Unlayer] renderRowToJson: Element must be <Row>, but got <${displayName || "unknown"}>. For full designs, use renderToJson instead.`
2170
- );
2171
- }
2172
- const counters = {};
2173
- return processRow(element, counters);
2174
- }
2175
- function renderToJson(element) {
2176
- element = unwrapRoot(element);
2177
- const displayName = getDisplayName2(element);
2178
- if (!displayName || !VALID_ROOTS.has(displayName)) {
2179
- throw new Error(
2180
- `[Unlayer] renderToJson: Root element must be <Body>, <Email>, <Page>, or <Document>, but got <${displayName || "unknown"}>. Wrap your content: <Body><Row><Column>...</Column></Row></Body>`
2181
- );
2182
- }
2183
- const counters = {};
2184
- const body = processBody(element, counters);
2185
- return {
2186
- counters,
2187
- body,
2188
- schemaVersion
2189
- };
2190
- }
2191
3095
 
2192
3096
  // src/utils/register-tool.tsx
2193
3097
  function initialValuesFromOptions(options) {
@@ -2272,6 +3176,6 @@ function registerTool(tool) {
2272
3176
  }
2273
3177
  var registerElementsTool = registerTool;
2274
3178
 
2275
- export { Body_default as Body, Button_default as Button, Column_default as Column, ColumnLayouts, DEFAULT_CONFIG, Divider_default as Divider, Document, Email, Heading_default as Heading, Html_default as Html, Image_default as Image, Menu_default as Menu, Page, Paragraph_default as Paragraph, Row_default as Row, Social_default as Social, Table_default as Table, UnlayerProvider, Video_default as Video, htmlToTextJson, registerElementsTool, registerTool, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
3179
+ export { Body_default as Body, Button_default as Button, Column_default as Column, ColumnLayouts, DEFAULT_CONFIG, Divider_default as Divider, Document, Email, Heading_default as Heading, Html_default as Html, Image_default as Image, Menu_default as Menu, Page, PageBreak_default as PageBreak, Paragraph_default as Paragraph, Row_default as Row, Social_default as Social, Table_default as Table, UnlayerProvider, Video_default as Video, htmlToTextJson, registerElementsTool, registerTool, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
2276
3180
  //# sourceMappingURL=index.js.map
2277
3181
  //# sourceMappingURL=index.js.map