@unlayer/react-elements 0.1.22 → 0.2.1

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,332 @@ 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
+ var RENDERS = Symbol.for("@unlayer/react-elements/renders");
2334
+ function nonce() {
2335
+ const shared = globalThis;
2336
+ const clock = globalThis.performance;
2337
+ return [shared[RENDERS] = (shared[RENDERS] ?? 0) + 1, (clock?.timeOrigin ?? 0) * 1e3, (clock?.now() ?? 0) * 1e6].map((n) => Math.floor(n).toString(36)).join("_");
2338
+ }
2339
+ var collect = (children) => children !== null && typeof children === "object" ? scopeCache(children, () => collected(children)) : collected(children);
2340
+ function childrenHtml2(children, place) {
2341
+ const c = collect(children);
2342
+ return withoutMarkers(markup(c, c.nodes, (child) => within(null, () => renderToString(place(child)))), c.collection.nonce);
2343
+ }
2344
+ function normalizedChildren(children) {
2345
+ const c = collect(children);
2346
+ return elements(c, c.nodes, "root");
2347
+ }
2348
+ function normalizedRoot(root) {
2349
+ return React4__default.default.cloneElement(root, void 0, ...normalizedChildren(root.props.children));
2350
+ }
1523
2351
  var DEFAULT_VALUES14 = BODY_DEFAULTS;
1524
2352
  var MAX_PREVIEW_LENGTH = 150;
1525
2353
  var PADDING_CHARS = [
@@ -1531,6 +2359,9 @@ var PADDING_CHARS = [
1531
2359
  "\u200F",
1532
2360
  "\uFEFF"
1533
2361
  ];
2362
+ function escapePreviewText(text) {
2363
+ return text.split(/(<%[\w.]+%>)/).map((part, i) => i % 2 ? part : part.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
2364
+ }
1534
2365
  function generatePreviewHtml(text) {
1535
2366
  if (!text || text.trim().length === 0) return "";
1536
2367
  const truncated = text.length > MAX_PREVIEW_LENGTH ? text.slice(0, MAX_PREVIEW_LENGTH) : text;
@@ -1539,7 +2370,7 @@ function generatePreviewHtml(text) {
1539
2370
  for (let i = 0; i < paddingLength; i++) {
1540
2371
  padding += PADDING_CHARS[i % PADDING_CHARS.length];
1541
2372
  }
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>`;
2373
+ 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
2374
  }
1544
2375
  function renderBodyToHtml(innerHTML, values, mode, previewText) {
1545
2376
  let finalInnerHtml = innerHTML;
@@ -1560,7 +2391,7 @@ function renderBodyToHtml(innerHTML, values, mode, previewText) {
1560
2391
  return raw.replace("min-height: 100vh; ", "").replace("min-height: 100vh;", "");
1561
2392
  }
1562
2393
  var Body = (props) => {
1563
- const { children, mode: modeProp, className, style, index = 0, config: configProp, previewText, ...semanticProps } = props;
2394
+ const { children, mode: modeProp, className, style, index = 0, config: configProp, previewText, fonts: _fonts, lang: _lang, ...semanticProps } = props;
1564
2395
  const resolvedConfig = { ...DEFAULT_CONFIG, ...configProp };
1565
2396
  const mode = modeProp ?? resolvedConfig.mode ?? "web";
1566
2397
  const _config = { ...resolvedConfig, mode };
@@ -1577,22 +2408,12 @@ var Body = (props) => {
1577
2408
  ...values._meta || {}
1578
2409
  }
1579
2410
  };
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
2411
  let innerHTML = "";
1593
- if (enrichedChildren) {
2412
+ if (children) {
1594
2413
  try {
1595
- innerHTML = ReactDOMServer__default.default.renderToString(enrichedChildren);
2414
+ innerHTML = withRenderScope(() => {
2415
+ return childrenHtml2(children, (child) => React4__default.default.cloneElement(child, { _config, bodyValues: values }));
2416
+ }, true);
1596
2417
  } catch (error) {
1597
2418
  console.error("Body: Failed to render children:", error);
1598
2419
  innerHTML = "";
@@ -1631,7 +2452,7 @@ var PROVIDER_ACTIVE_KEY = "__unlayerProviderActive";
1631
2452
  var _UnlayerContext = null;
1632
2453
  function getUnlayerContext() {
1633
2454
  if (!_UnlayerContext) {
1634
- _UnlayerContext = React.createContext(DEFAULT_CONFIG);
2455
+ _UnlayerContext = React4.createContext(DEFAULT_CONFIG);
1635
2456
  }
1636
2457
  return _UnlayerContext;
1637
2458
  }
@@ -1639,7 +2460,7 @@ var UnlayerProvider = ({
1639
2460
  config,
1640
2461
  children
1641
2462
  }) => {
1642
- const merged = React.useMemo(
2463
+ const merged = React4.useMemo(
1643
2464
  () => ({ ...DEFAULT_CONFIG, ...config, [PROVIDER_ACTIVE_KEY]: true }),
1644
2465
  [config]
1645
2466
  );
@@ -1648,31 +2469,106 @@ var UnlayerProvider = ({
1648
2469
  };
1649
2470
  UnlayerProvider.displayName = "UnlayerProvider";
1650
2471
  function useUnlayerConfig() {
1651
- return React.useContext(getUnlayerContext());
2472
+ return React4.useContext(getUnlayerContext());
1652
2473
  }
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 || {}
2474
+
2475
+ // src/utils/device-overrides.ts
2476
+ var contentOptions = {
2477
+ Button: ["size", "fontSize", "lineHeight", "textAlign", "padding"],
2478
+ Image: ["src", "textAlign"],
2479
+ Heading: ["fontSize", "textAlign", "lineHeight"],
2480
+ Paragraph: ["fontSize", "textAlign", "lineHeight"],
2481
+ Menu: ["fontSize", "padding"],
2482
+ Divider: ["width", "textAlign"]
2483
+ };
2484
+ function css(value) {
2485
+ const text = String(value).trim();
2486
+ return text && !/[!\\"'\n\r]/.test(text) && cssValueSafe(text) ? text : void 0;
2487
+ }
2488
+ function ident(id) {
2489
+ 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}`);
2490
+ }
2491
+ function size(value) {
2492
+ const number = parseFloat(String(value));
2493
+ if (!Number.isFinite(number)) return;
2494
+ const rounded = parseFloat(number.toFixed(2));
2495
+ if (typeof value === "number" || String(value).endsWith("px")) return `${rounded}px`;
2496
+ if (String(value).endsWith("%")) return `${rounded}%`;
2497
+ }
2498
+ function collectDeviceStyles(values, collection, name, mode, styles) {
2499
+ const given = safeName(values._meta?.htmlID);
2500
+ if (!given) return;
2501
+ const id = ident(given);
2502
+ const options = collection === "contents" ? ["containerPadding", ...contentOptions[name] || []] : collection === "columns" ? ["padding", "border"] : ["padding"];
2503
+ for (const device of mode === "document" ? ["desktop"] : ["desktop", "mobile"]) {
2504
+ const overrides = values._override?.[device];
2505
+ if (!overrides) continue;
2506
+ for (const option of options) {
2507
+ if (!(option in overrides)) continue;
2508
+ const value = overrides[option];
2509
+ const property = option.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
2510
+ const prefix = collection === "rows" ? "row-" : collection === "columns" ? "col-" : "";
2511
+ const selector = (attribute) => `#${id} .v-${prefix}${property === attribute ? property : `${property}-${attribute}`}`;
2512
+ const push = (rule) => (styles[device] || (styles[device] = [])).push(rule);
2513
+ if (option === "padding" || option === "containerPadding") {
2514
+ if (!(value || value === 0) || css(value) === void 0) continue;
2515
+ const direct = collection === "rows" || mode === "web" && collection === "contents" && option === "containerPadding";
2516
+ const target = direct ? selector("padding").replace(`#${id} `, `#${id}`) : selector("padding");
2517
+ push(`${target} { padding: ${css(value)} !important; }`);
2518
+ if (mode === "email" && collection === "rows") {
2519
+ const parts2 = String(value).trim().split(/\s+/);
2520
+ const [top, bottom] = [parts2[0], parts2[2] || parts2[0]].map((part) => size(part) ?? css(part));
2521
+ if (top !== void 0 && bottom !== void 0) push(`${target}--vertical { padding-top: ${top} !important; padding-bottom: ${bottom} !important; }`);
2522
+ }
2523
+ } else if (option === "border") {
2524
+ if (value) push(`${selector("border")} { ${["Top", "Left", "Right", "Bottom"].map((d) => {
2525
+ const w = css(value[`border${d}Width`] || "0px") ?? "0px";
2526
+ return `border-${d.toLowerCase()}: ${w} ${css(value[`border${d}Style`] || "solid") ?? "solid"} ${parseInt(w) > 0 && css(value[`border${d}Color`]) || "transparent"} !important;`;
2527
+ }).join("")} }`);
2528
+ } else if (option === "src") {
2529
+ if (!value) continue;
2530
+ const merged = { ...values.src, ...value };
2531
+ const maximum = merged.autoWidth === false ? size(merged.maxWidth) : void 0;
2532
+ push(`${selector("width")} { width: ${maximum || "100%"} !important; }`);
2533
+ push(`${selector("max-width")} { max-width: ${size(maximum || merged.width || "100%")} !important; }`);
2534
+ } else if (option === "size" || option === "width") {
2535
+ if (!value) continue;
2536
+ push(`${selector("width")} { width: ${size(value.width) && !value.autoWidth ? size(value.width) : "auto"} !important; }`);
2537
+ push(`${selector("max-width")} { max-width: 100% !important; }`);
2538
+ } else {
2539
+ if (!(value || option === "lineHeight" && `${value}` === "0")) continue;
2540
+ const rendered = css(option === "fontSize" && typeof value === "number" ? `${value}px` : value);
2541
+ if (rendered === void 0) continue;
2542
+ push(`${selector(property)} { ${property}: ${rendered} !important; }`);
2543
+ }
1660
2544
  }
1661
- };
2545
+ }
1662
2546
  }
1663
- function getDisplayName(element) {
1664
- const type = element.type;
1665
- return type?.displayName || type?.name;
2547
+ function deviceStylesCss(styles, mode) {
2548
+ return Object.entries(styles).filter(([, rules]) => rules.length).map(([device, rules]) => {
2549
+ const query = device === "mobile" ? "@media (max-width: 480px)" : `@media (min-width: ${mode === "document" ? 1 : 481}px)`;
2550
+ return `${query} { ${rules.join(" ")} }`;
2551
+ }).join("\n");
1666
2552
  }
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;
2553
+ function deviceVisibilityCss(mode) {
2554
+ if (mode === "document") return "";
2555
+ const extra = mode === "email" ? "max-height: 0px; overflow: hidden; " : "";
2556
+ 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" : "";
2557
+ return reset + `@media (max-width: 480px) { .hide-mobile { ${extra}display: none !important; } }
2558
+ @media (min-width: 481px) { .hide-desktop { ${extra}display: none !important; } }`;
2559
+ }
2560
+ var schemaVersion = exporters.schemaVersion ?? 24;
2561
+ function unwrapForJson(element2) {
2562
+ try {
2563
+ return unwrapRoot(element2);
2564
+ } catch (cause) {
2565
+ const detail = cause instanceof Error ? cause.message : String(cause);
2566
+ throw new Error(
2567
+ `[Unlayer] renderToJson: could not unwrap <${componentName(element2) || "wrapper"}>. ${UNWRAP_ADVICE} (${detail})`
2568
+ );
2569
+ }
1675
2570
  }
2571
+ var collectChildren = (node) => React4__default.default.Children.toArray(node).filter(React4__default.default.isValidElement);
1676
2572
  function extractSemanticProps(props, extraKeys = []) {
1677
2573
  const internalKeys = /* @__PURE__ */ new Set([
1678
2574
  "children",
@@ -1687,6 +2583,8 @@ function extractSemanticProps(props, extraKeys = []) {
1687
2583
  "_config",
1688
2584
  "config",
1689
2585
  "previewText",
2586
+ "fonts",
2587
+ "lang",
1690
2588
  "layout",
1691
2589
  "collection",
1692
2590
  ...extraKeys
@@ -1703,68 +2601,313 @@ function nextCounter(counters, key) {
1703
2601
  counters[key] = (counters[key] || 0) + 1;
1704
2602
  return counters[key];
1705
2603
  }
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
2604
+ function toContentType(displayName) {
2605
+ if (displayName === "Paragraph") return "text";
2606
+ return displayName.toLowerCase();
2607
+ }
2608
+ function makeId(prefix, counter) {
2609
+ return `${prefix}_${counter}`;
2610
+ }
2611
+ function extractTextFromTextJson(textJson) {
2612
+ try {
2613
+ const parsed = JSON.parse(textJson);
2614
+ if (parsed.__html) return parsed.__html;
2615
+ const root = parsed?.root;
2616
+ if (!root?.children) return "";
2617
+ const paragraphs = [];
2618
+ for (const paragraph of root.children) {
2619
+ if (!paragraph?.children) continue;
2620
+ const parts2 = [];
2621
+ for (const node of paragraph.children) {
2622
+ if (node?.text) parts2.push(node.text);
2623
+ }
2624
+ paragraphs.push(parts2.join(""));
1717
2625
  }
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);
2626
+ return paragraphs.join("\n");
2627
+ } catch {
2628
+ return "";
2629
+ }
1725
2630
  }
1726
- function walkItem(element, bodyValues, displayMode, headConfig, counters, styles, scripts, tags) {
1727
- const componentType = element.type;
2631
+ function processItem(element2, counters, layout = {}, metaName) {
2632
+ const componentType = element2.type;
1728
2633
  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}`);
2634
+ if (!config && componentType?.[KEPT]) {
2635
+ return processItem(componentType[KEPT], counters, layout, (componentType.displayName || componentType.name || "component").toLowerCase());
2636
+ }
2637
+ if (!config) {
2638
+ const name2 = componentName(element2) || "Unknown";
2639
+ throw new Error(
2640
+ `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
2641
+ );
2642
+ }
2643
+ const { name, defaultValues, propMapper, slug } = config;
2644
+ const contentType = config.contentType ?? toContentType(name);
2645
+ const metaBase = `u_content_${metaName ?? config.metaName ?? contentType}`;
2646
+ const count = nextCounter(counters, metaBase);
2647
+ const id = makeId(metaBase, count);
2648
+ const { children, ...restProps } = element2.props;
2649
+ const mappedValues = propMapper({ children, ...restProps });
2650
+ const finalValues = mergeValues(defaultValues, mappedValues);
2651
+ if (contentType === "text" && finalValues.textJson) {
2652
+ finalValues.text = extractTextFromTextJson(finalValues.textJson);
2653
+ delete finalValues.textJson;
2654
+ }
2655
+ const values = {
2656
+ containerPadding: "10px",
2657
+ ...finalValues,
2658
+ _meta: {
2659
+ htmlID: id,
2660
+ htmlClassNames: metaBase,
2661
+ ...finalValues._meta || {}
2662
+ },
2663
+ selectable: true,
2664
+ draggable: true,
2665
+ duplicatable: true,
2666
+ deletable: true,
2667
+ hideable: true
2668
+ };
2669
+ const itemSrc = values.src;
2670
+ if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
2671
+ const availableWidth = contentSlotWidth({
2672
+ ...layout,
2673
+ containerPadding: values.containerPadding
2674
+ });
2675
+ values.src = pinImageSrc(itemSrc, availableWidth);
2676
+ }
2677
+ return slug ? { type: contentType, slug, values } : { type: contentType, values };
2678
+ }
2679
+ function processColumn(element2, counters, layout = {}) {
2680
+ const count = nextCounter(counters, "u_column");
2681
+ const id = makeId("u_column", count);
2682
+ const semanticProps = extractSemanticProps(element2.props);
2683
+ const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
2684
+ const values = mergeValues(COLUMN_DEFAULTS, mapped);
2685
+ const valuesWithMeta = {
2686
+ ...values,
2687
+ _meta: {
2688
+ htmlID: id,
2689
+ htmlClassNames: "u_column",
2690
+ ...values._meta || {}
2691
+ }
2692
+ };
2693
+ const contents = [];
2694
+ const children = collectChildren(element2.props.children);
2695
+ const itemLayout = { ...layout, columnValues: valuesWithMeta };
2696
+ for (const child of children) {
2697
+ contents.push(processItem(child, counters, itemLayout));
2698
+ }
2699
+ return { contents, values: valuesWithMeta };
2700
+ }
2701
+ function processRow(element2, counters, parentLayout = {}) {
2702
+ const count = nextCounter(counters, "u_row");
2703
+ const id = makeId("u_row", count);
2704
+ const { layout, cells: propsCells } = element2.props;
2705
+ let cells;
2706
+ if (layout) {
2707
+ cells = layout.cells;
2708
+ } else if (propsCells) {
2709
+ cells = propsCells;
2710
+ } else {
2711
+ const columnCount = Math.max(
2712
+ 1,
2713
+ collectChildren(element2.props.children).filter(
2714
+ (child) => getDisplayName(child) === "Column"
2715
+ ).length
2716
+ );
2717
+ cells = Array(columnCount).fill(1);
2718
+ }
2719
+ const semanticProps = extractSemanticProps(element2.props, ["layout"]);
2720
+ const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
2721
+ const values = mergeValues(ROW_DEFAULTS, mapped);
2722
+ const valuesWithMeta = {
2723
+ ...values,
2724
+ columns: values.columns ?? false,
2725
+ cells,
2726
+ _meta: {
2727
+ htmlID: id,
2728
+ htmlClassNames: "u_row",
2729
+ ...values._meta || {}
2730
+ },
2731
+ selectable: true,
2732
+ draggable: true,
2733
+ duplicatable: true,
2734
+ deletable: true,
2735
+ hideable: true
2736
+ };
2737
+ const columns = [];
2738
+ const children = collectChildren(element2.props.children);
2739
+ const columnLayout = {
2740
+ bodyValues: parentLayout.bodyValues,
2741
+ rowValues: valuesWithMeta,
2742
+ rowCells: cells
2743
+ };
2744
+ let columnIndex = 0;
2745
+ for (const child of children) {
2746
+ const name = getDisplayName(child);
2747
+ if (name === "Column") {
2748
+ columns.push(
2749
+ processColumn(child, counters, { ...columnLayout, columnIndex })
2750
+ );
2751
+ columnIndex += 1;
2752
+ } else {
2753
+ console.warn(
2754
+ `[Unlayer] renderToJson: <${componentName(child) ?? String(child.type)}> is not a valid Row child. Only <Column> is allowed.`
2755
+ );
2756
+ }
2757
+ }
2758
+ return { cells, columns, values: valuesWithMeta };
2759
+ }
2760
+ function processBody(element2, counters) {
2761
+ const id = "u_body";
2762
+ const semanticProps = extractSemanticProps(element2.props);
2763
+ const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
2764
+ const values = mergeValues(BODY_DEFAULTS, mapped);
2765
+ if ("textDirection" in values) {
2766
+ const direction = textDirectionOf(values.textDirection);
2767
+ if (direction) values.textDirection = direction;
2768
+ else delete values.textDirection;
2769
+ }
2770
+ const previewText = element2.props.previewText;
2771
+ if (previewText !== void 0) {
2772
+ values.preheaderText = previewText;
2773
+ }
2774
+ const valuesWithMeta = {
2775
+ ...values,
2776
+ _meta: {
2777
+ htmlID: id,
2778
+ htmlClassNames: "u_body",
2779
+ ...values._meta || {}
2780
+ }
2781
+ };
2782
+ const rows = [];
2783
+ const children = collectChildren(element2.props.children);
2784
+ for (const child of children) {
2785
+ const name = getDisplayName(child);
2786
+ if (name === "Row") {
2787
+ rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
2788
+ } else {
2789
+ console.warn(
2790
+ `[Unlayer] renderToJson: <${componentName(child) ?? String(child.type)}> is not a valid Body child. Only <Row> is allowed.`
2791
+ );
2792
+ }
2793
+ }
2794
+ return {
2795
+ rows,
2796
+ values: valuesWithMeta
2797
+ };
2798
+ }
2799
+ function renderRowToJson(element2) {
2800
+ const displayName = getDisplayName(element2);
2801
+ if (displayName !== "Row") {
2802
+ throw new Error(
2803
+ `[Unlayer] renderRowToJson: Element must be <Row>, but got <${componentName(element2) || "unknown"}>. For full designs, use renderToJson instead.`
2804
+ );
2805
+ }
2806
+ const counters = {};
2807
+ return withRenderScope(() => processRow(normalizedChildren(element2).find((c) => React4__default.default.isValidElement(c)) ?? element2, counters));
2808
+ }
2809
+ function renderToJson(element2) {
2810
+ return withRenderScope(() => designJson(element2));
2811
+ }
2812
+ function designJson(element2) {
2813
+ element2 = unwrapForJson(element2);
2814
+ const displayName = getDisplayName(element2);
2815
+ if (displayName && ROOT_NAMES.has(displayName)) element2 = normalizedRoot(element2);
2816
+ if (!displayName || !ROOT_NAMES.has(displayName)) {
2817
+ throw new Error(
2818
+ `[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>`
2819
+ );
2820
+ }
2821
+ const counters = {};
2822
+ const body = processBody(element2, counters);
2823
+ return {
2824
+ counters,
2825
+ body,
2826
+ schemaVersion
2827
+ };
2828
+ }
2829
+
2830
+ // src/utils/extract-head.ts
2831
+ function ensureMeta(values, type, index = 0) {
2832
+ return {
2833
+ ...values,
2834
+ _meta: {
2835
+ htmlID: `${["row", "column"].includes(type) ? "u" : "u_content"}_${type.toLowerCase()}_${index + 1}`,
2836
+ htmlClassNames: `u_content_${type.toLowerCase()}`,
2837
+ ...values._meta || {}
2838
+ }
2839
+ };
2840
+ }
2841
+ var collectChildren2 = (node) => React4__default.default.Children.toArray(node).filter(React4__default.default.isValidElement);
2842
+ function callHead(head, values, bodyValues, displayMode, headConfig, styles, scripts, tags) {
2843
+ if (!head) return;
2844
+ const headArgs = [
2845
+ values,
2846
+ bodyValues,
2847
+ {
2848
+ displayMode,
2849
+ isViewer: false,
2850
+ variant: null,
2851
+ type: "rows",
2852
+ headConfig
2853
+ }
2854
+ ];
2855
+ const css2 = head.css?.(...headArgs);
2856
+ if (css2) styles.push(css2);
2857
+ const js = head.js?.(...headArgs);
2858
+ if (js) scripts.push(js);
2859
+ const headTags = head.tags?.(...headArgs);
2860
+ if (headTags) tags.push(...headTags);
2861
+ }
2862
+ function walkItem(element2, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2863
+ const componentType = element2.type;
2864
+ const config = componentType[UNLAYER_CONFIG_KEY];
2865
+ if (!config) {
2866
+ if (componentType?.[KEPT]) nextCounter(counters, `u_content_${(componentType.displayName || componentType.name || "component").toLowerCase()}`);
2867
+ return;
2868
+ }
2869
+ const { name, defaultValues, propMapper } = config;
2870
+ const { children, ...restProps } = element2.props;
2871
+ const mappedValues = propMapper({ children, ...restProps });
2872
+ const finalValues = mergeValues(defaultValues, mappedValues);
2873
+ const contentType = config.metaName ?? name.toLowerCase();
2874
+ const count = nextCounter(counters, `u_content_${contentType}`);
1736
2875
  const valuesWithMeta = ensureMeta(finalValues, contentType, count - 1);
2876
+ collectDeviceStyles(valuesWithMeta, "contents", name, displayMode, deviceStyles);
1737
2877
  const head = config.head ?? exporters.heads[name];
1738
2878
  callHead(head, valuesWithMeta, bodyValues, displayMode, headConfig, styles, scripts, tags);
1739
2879
  }
1740
- function walkColumn(element, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags) {
1741
- const semanticProps = extractSemanticProps(element.props);
2880
+ function walkColumn(element2, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2881
+ const semanticProps = extractSemanticProps(element2.props);
1742
2882
  const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
1743
2883
  const count = nextCounter(counters, "u_column");
1744
2884
  const columnValues = ensureMeta(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
2885
+ collectDeviceStyles(columnValues, "columns", "Column", displayMode, deviceStyles);
1745
2886
  const columnHead = exporters.heads["Column"];
1746
2887
  callHead(columnHead, columnValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1747
- const children = collectChildren(element.props.children);
2888
+ const children = collectChildren2(element2.props.children);
1748
2889
  for (const child of children) {
1749
- walkItem(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags);
2890
+ walkItem(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1750
2891
  }
1751
2892
  }
1752
- function walkRow(element, bodyValues, displayMode, headConfig, counters, styles, scripts, tags) {
1753
- const semanticProps = extractSemanticProps(element.props, ["layout"]);
2893
+ function walkRow(element2, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles) {
2894
+ const semanticProps = extractSemanticProps(element2.props, ["layout"]);
1754
2895
  const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
1755
2896
  const count = nextCounter(counters, "u_row");
1756
2897
  const rowValues = ensureMeta(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
2898
+ collectDeviceStyles(rowValues, "rows", "Row", displayMode, deviceStyles);
1757
2899
  const rowHead = exporters.heads["Row"];
1758
2900
  callHead(rowHead, rowValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1759
- const children = collectChildren(element.props.children);
2901
+ const children = collectChildren2(element2.props.children);
1760
2902
  for (const child of children) {
1761
2903
  const name = getDisplayName(child);
1762
2904
  if (name === "Column") {
1763
- walkColumn(child, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags);
2905
+ walkColumn(child, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1764
2906
  }
1765
2907
  }
1766
2908
  }
1767
- function extractHeadFromTree(element, options) {
2909
+ function extractHeadFromTree(element2, options) {
2910
+ element2 = normalizedRoot(element2);
1768
2911
  const { displayMode } = options;
1769
2912
  const headConfig = {
1770
2913
  hasFeature: options.headConfig?.hasFeature ?? (() => false),
@@ -1774,18 +2917,22 @@ function extractHeadFromTree(element, options) {
1774
2917
  const scripts = [];
1775
2918
  const tags = [];
1776
2919
  const counters = {};
1777
- const semanticProps = extractSemanticProps(element.props);
2920
+ const deviceStyles = {};
2921
+ const semanticProps = extractSemanticProps(element2.props);
1778
2922
  const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
1779
2923
  const bodyValues = ensureMeta(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1780
2924
  const bodyHead = exporters.heads["Body"];
1781
2925
  callHead(bodyHead, bodyValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1782
- const children = collectChildren(element.props.children);
2926
+ const children = collectChildren2(element2.props.children);
1783
2927
  for (const child of children) {
1784
2928
  const name = getDisplayName(child);
1785
2929
  if (name === "Row") {
1786
- walkRow(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags);
2930
+ walkRow(child, bodyValues, displayMode, headConfig, counters, styles, scripts, tags, deviceStyles);
1787
2931
  }
1788
2932
  }
2933
+ styles.unshift(deviceVisibilityCss(displayMode));
2934
+ const overrides = deviceStylesCss(deviceStyles, displayMode);
2935
+ if (overrides) styles.push(overrides);
1789
2936
  const uniqueTags = [...new Set(tags.filter(Boolean))];
1790
2937
  return {
1791
2938
  css: styles.filter(Boolean).join("\n"),
@@ -1805,7 +2952,7 @@ function emailLayout(args) {
1805
2952
  fontLinks && `<!--[if !mso]><!-->${fontLinks}<!--<![endif]-->`
1806
2953
  ].filter(Boolean).join("\n ");
1807
2954
  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">
2955
+ <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
2956
  <head>
1810
2957
  <!--[if gte mso 9]>
1811
2958
  <xml>
@@ -1829,7 +2976,7 @@ function documentLayout(args) {
1829
2976
  const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
1830
2977
  const head = [titleTag, tagLines, styleTag, scriptTag, fontLinks].filter(Boolean).join("\n ");
1831
2978
  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">
2979
+ <html${dir}${args.lang || ""} xmlns="http://www.w3.org/1999/xhtml">
1833
2980
  <head>
1834
2981
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1835
2982
  ${head}
@@ -1842,7 +2989,7 @@ function webLayout(args) {
1842
2989
  const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
1843
2990
  const head = [titleTag, fontLinks, tagLines, styleTag, scriptTag].filter(Boolean).join("\n ");
1844
2991
  return `<!doctype html>
1845
- <html${dir}>
2992
+ <html${dir}${args.lang || ""}>
1846
2993
  <head>
1847
2994
  <meta charset="utf-8">
1848
2995
  <meta http-equiv="x-ua-compatible" content="ie=edge">
@@ -1863,20 +3010,25 @@ var MODE_BY_WRAPPER = {
1863
3010
  Page: "web",
1864
3011
  Document: "document"
1865
3012
  };
1866
- function resolveDisplayMode(element, mergedConfig) {
1867
- const displayName = element.type?.displayName;
3013
+ function resolveConfig(element2, config) {
3014
+ const props = element2.props;
3015
+ const textDirection = textDirectionOf(props.textDirection ?? props.values?.textDirection);
3016
+ return { ...DEFAULT_CONFIG, ...textDirection !== void 0 ? { textDirection } : {}, ...config };
3017
+ }
3018
+ function resolveDisplayMode(element2, mergedConfig) {
3019
+ const displayName = element2.type?.displayName;
1868
3020
  const wrapperMode = displayName ? MODE_BY_WRAPPER[displayName] : void 0;
1869
- return wrapperMode ?? element.props.mode ?? mergedConfig.mode ?? "web";
3021
+ return wrapperMode ?? element2.props.mode ?? mergedConfig.mode ?? "web";
1870
3022
  }
1871
- function renderBody(element, config) {
1872
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
3023
+ function renderBody(element2, config, name = "renderToHtml") {
3024
+ const mergedConfig = resolveConfig(element2, config);
1873
3025
  try {
1874
- const enriched = React__default.default.cloneElement(element, { config: mergedConfig });
1875
- return ReactDOMServer.renderToStaticMarkup(enriched);
3026
+ const enriched = React4__default.default.cloneElement(element2, { config: mergedConfig });
3027
+ return renderToStaticMarkup(enriched);
1876
3028
  } catch (error) {
1877
3029
  const message = error instanceof Error ? error.message : String(error);
1878
3030
  throw new Error(
1879
- `[Unlayer] renderToHtml failed: ${message}
3031
+ `[Unlayer] ${name} failed: ${message}
1880
3032
  Tip: Ensure your tree uses Body > Row > Column > Item structure.`
1881
3033
  );
1882
3034
  }
@@ -1888,30 +3040,41 @@ function stripOuterDiv(html) {
1888
3040
  function escapeForHtml(text) {
1889
3041
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1890
3042
  }
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;
3043
+ function renderToHtml(element2, options) {
3044
+ return withRenderScope(() => htmlDocument(element2, options));
3045
+ }
3046
+ function styleText(css2) {
3047
+ return css2.replace(/<\/(style)/gi, "<\\/$1");
3048
+ }
3049
+ function htmlDocument(element2, options) {
3050
+ const { title, lang: optionLang, fonts: optionFonts = [], ...config } = options ?? {};
3051
+ element2 = htmlRoot(element2, "renderToHtml");
3052
+ const rootFonts = element2.props?.fonts ?? [];
3053
+ const fonts = [...rootFonts, ...optionFonts].filter((font, i, all) => font?.url && all.findIndex((f) => f?.url === font.url) === i);
3054
+ const lang = optionLang ?? element2.props.lang;
3055
+ const mergedConfig = resolveConfig(element2, config);
3056
+ const displayMode = resolveDisplayMode(element2, mergedConfig);
3057
+ const wrapperName = element2.type?.displayName;
1896
3058
  const isUnlayerWrapper = wrapperName === "Body" || (wrapperName ? wrapperName in MODE_BY_WRAPPER : false);
1897
- const rawBody = renderBody(element, config);
3059
+ const rawBody = renderBody(element2, config);
1898
3060
  const body = isUnlayerWrapper ? stripOuterDiv(rawBody) : rawBody;
1899
- const { css, js, tags } = extractHeadFromTree(element, {
3061
+ const { css: css2, js, tags } = extractHeadFromTree(element2, {
1900
3062
  displayMode,
1901
3063
  headConfig: mergedConfig.headConfig
1902
3064
  });
1903
3065
  const layoutArgs = {
3066
+ lang: lang ? ` lang="${escapeForHtml(String(lang))}"` : "",
1904
3067
  dir: mergedConfig.textDirection ? ` dir="${escapeForHtml(String(mergedConfig.textDirection))}"` : "",
1905
3068
  titleTag: title ? `<title>${escapeForHtml(title.trim())}</title>` : "",
1906
- styleTag: css ? `<style type="text/css">
1907
- ${css}
3069
+ styleTag: css2 ? `<style type="text/css">
3070
+ ${styleText(css2)}
1908
3071
  </style>` : "",
1909
3072
  scriptTag: js ? `<script type="application/javascript">
1910
3073
  ${js}
1911
3074
  </script>` : "",
1912
3075
  tagLines: tags.join("\n"),
1913
3076
  fontLinks: fonts.filter((font) => font?.url).map(
1914
- (font) => `<link href="${escapeForHtml(font.url)}" rel="stylesheet" type="text/css">`
3077
+ (font) => `<link href="${escapeForHtml(String(font.url))}" rel="stylesheet" type="text/css">`
1915
3078
  ).join(""),
1916
3079
  body
1917
3080
  };
@@ -1919,21 +3082,29 @@ ${js}
1919
3082
  if (displayMode === "document") return documentLayout(layoutArgs);
1920
3083
  return webLayout(layoutArgs);
1921
3084
  }
1922
- function renderToPlainText(element, config) {
1923
- const html = renderBody(element, config);
3085
+ function renderToPlainText(element2, config) {
3086
+ const html = withRenderScope(() => {
3087
+ const root = htmlRoot(element2, "renderToPlainText");
3088
+ normalizedRoot(root);
3089
+ return renderBody(root, config, "renderToPlainText");
3090
+ });
1924
3091
  return htmlToPlainText(html);
1925
3092
  }
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, {
3093
+ function renderToHtmlParts(element2, config) {
3094
+ return withRenderScope(() => htmlParts(element2, config));
3095
+ }
3096
+ function htmlParts(element2, config) {
3097
+ element2 = htmlRoot(element2, "renderToHtmlParts");
3098
+ const body = renderBody(element2, config, "renderToHtmlParts");
3099
+ const mergedConfig = resolveConfig(element2, config);
3100
+ const displayMode = resolveDisplayMode(element2, mergedConfig);
3101
+ const { css: css2, js, tags } = extractHeadFromTree(element2, {
1931
3102
  displayMode,
1932
3103
  headConfig: mergedConfig.headConfig
1933
3104
  });
1934
3105
  const headParts = [];
1935
- if (css) {
1936
- headParts.push(`<style>${css}</style>`);
3106
+ if (css2) {
3107
+ headParts.push(`<style>${styleText(css2)}</style>`);
1937
3108
  }
1938
3109
  if (js) {
1939
3110
  headParts.push(`<script>${js}</script>`);
@@ -1944,291 +3115,11 @@ function renderToHtmlParts(element, config) {
1944
3115
  return {
1945
3116
  head: headParts.join("\n"),
1946
3117
  body,
1947
- css,
3118
+ css: css2,
1948
3119
  js,
1949
3120
  tags
1950
3121
  };
1951
3122
  }
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
3123
 
2233
3124
  // src/utils/register-tool.tsx
2234
3125
  function initialValuesFromOptions(options) {