@unlayer/react-elements 0.1.22 → 0.2.0

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