@unlayer/react-elements 0.1.15 → 0.1.17
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/README.md +20 -2
- package/dist/index.cjs +188 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -18
- package/dist/index.d.ts +47 -18
- package/dist/index.js +188 -26
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.d.cts
CHANGED
|
@@ -826,32 +826,54 @@ declare const UnlayerProvider: React__default.FC<UnlayerProviderProps>;
|
|
|
826
826
|
declare function useUnlayerConfig(): UnlayerConfig;
|
|
827
827
|
|
|
828
828
|
/**
|
|
829
|
-
*
|
|
829
|
+
* Options for renderToHtml — config overrides plus document-level extras.
|
|
830
|
+
*/
|
|
831
|
+
interface RenderToHtmlOptions extends Partial<UnlayerConfig> {
|
|
832
|
+
/** Document `<title>` */
|
|
833
|
+
title?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Web-font stylesheets to load via `<link rel="stylesheet">` tags.
|
|
836
|
+
* Elements has no font registry, so pass the URLs for any non-system
|
|
837
|
+
* fonts your design uses (e.g. Google Fonts CSS URLs).
|
|
838
|
+
*/
|
|
839
|
+
fonts?: Array<{
|
|
840
|
+
url: string;
|
|
841
|
+
}>;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Renders an Unlayer element tree to a complete HTML document — from
|
|
845
|
+
* `<!DOCTYPE ...>` to `</html>` — matching the editor's exportHtml layouts.
|
|
830
846
|
*
|
|
831
|
-
* -
|
|
832
|
-
* -
|
|
833
|
-
*
|
|
847
|
+
* Per-mode document shells (mirroring the editor's layout templates):
|
|
848
|
+
* - `email`: XHTML transitional doctype, VML/Office namespaces, and the MSO
|
|
849
|
+
* conditional comments Outlook needs
|
|
850
|
+
* - `web`: HTML5 doctype with standard viewport/charset meta tags
|
|
851
|
+
* - `document`: XHTML transitional doctype for print/PDF pipelines
|
|
834
852
|
*
|
|
835
|
-
* @
|
|
836
|
-
*
|
|
837
|
-
*
|
|
853
|
+
* Use {@link renderToHtmlParts} instead when your app owns the document
|
|
854
|
+
* shell and only needs the head/body chunks.
|
|
855
|
+
*
|
|
856
|
+
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
857
|
+
* @param options - Config overrides (mode, cdnBaseUrl, etc.) plus `title` and `fonts`
|
|
858
|
+
* @returns Complete HTML document string
|
|
838
859
|
* @throws {Error} If rendering fails, with a helpful message
|
|
839
860
|
*
|
|
840
861
|
* @example
|
|
841
862
|
* ```tsx
|
|
842
|
-
* import { renderToHtml,
|
|
863
|
+
* import { renderToHtml, Email, Row, Column, Paragraph } from "@unlayer/react-elements";
|
|
843
864
|
*
|
|
844
865
|
* const html = renderToHtml(
|
|
845
|
-
* <
|
|
846
|
-
* {
|
|
866
|
+
* <Email><Row><Column><Paragraph>Hello</Paragraph></Column></Row></Email>,
|
|
867
|
+
* { title: "Welcome" }
|
|
847
868
|
* );
|
|
869
|
+
* // "<!DOCTYPE HTML PUBLIC ...><html ...>...</html>"
|
|
848
870
|
* ```
|
|
849
871
|
*/
|
|
850
|
-
declare function renderToHtml(element: React__default.ReactElement,
|
|
872
|
+
declare function renderToHtml(element: React__default.ReactElement, options?: RenderToHtmlOptions): string;
|
|
851
873
|
/**
|
|
852
874
|
* Renders an Unlayer element tree to a plain text string.
|
|
853
875
|
*
|
|
854
|
-
*
|
|
876
|
+
* Renders the body markup then converts to plain text using
|
|
855
877
|
* `htmlToPlainText`. Useful for generating the text/plain MIME part
|
|
856
878
|
* of multipart emails (critical for deliverability).
|
|
857
879
|
*
|
|
@@ -876,19 +898,26 @@ declare function renderToPlainText(element: React__default.ReactElement, config?
|
|
|
876
898
|
interface HtmlParts {
|
|
877
899
|
/** `<head>` content: `<style>` blocks with component CSS, optional `<script>` tags */
|
|
878
900
|
head: string;
|
|
879
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* The rendered body markup, wrapped in the renderer's host `<div>`. In
|
|
903
|
+
* email/document mode it contains the `<body>` tag itself; web mode has
|
|
904
|
+
* none. No doctype/html/head shell — embeddable as-is, or extract the
|
|
905
|
+
* `<body>` tag when composing a full document.
|
|
906
|
+
*/
|
|
880
907
|
body: string;
|
|
881
908
|
}
|
|
882
909
|
/**
|
|
883
910
|
* Renders an Unlayer element tree to separate head and body HTML strings.
|
|
884
911
|
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
* and
|
|
912
|
+
* Use this when your app owns the document shell — an existing page
|
|
913
|
+
* template, an ESP template with its own meta tags, an iframe srcdoc —
|
|
914
|
+
* and you want to place the `<style>` block and body markup yourself.
|
|
915
|
+
* For a complete ready-to-send document in one call, use
|
|
916
|
+
* {@link renderToHtml}.
|
|
888
917
|
*
|
|
889
918
|
* - `head` contains `<style>` tags with CSS generated by each component's
|
|
890
919
|
* head function (button hover colors, body fonts, link styles, etc.)
|
|
891
|
-
* - `body` is
|
|
920
|
+
* - `body` is the rendered body markup without any document shell
|
|
892
921
|
*
|
|
893
922
|
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
894
923
|
* @param config - Optional config overrides (mode, cdnBaseUrl, etc.)
|
|
@@ -972,4 +1001,4 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
|
|
|
972
1001
|
declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
|
|
973
1002
|
declare function renderToJson(element: React__default.ReactElement): DesignJSON;
|
|
974
1003
|
|
|
975
|
-
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
|
1004
|
+
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, type RenderToHtmlOptions, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
package/dist/index.d.ts
CHANGED
|
@@ -826,32 +826,54 @@ declare const UnlayerProvider: React__default.FC<UnlayerProviderProps>;
|
|
|
826
826
|
declare function useUnlayerConfig(): UnlayerConfig;
|
|
827
827
|
|
|
828
828
|
/**
|
|
829
|
-
*
|
|
829
|
+
* Options for renderToHtml — config overrides plus document-level extras.
|
|
830
|
+
*/
|
|
831
|
+
interface RenderToHtmlOptions extends Partial<UnlayerConfig> {
|
|
832
|
+
/** Document `<title>` */
|
|
833
|
+
title?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Web-font stylesheets to load via `<link rel="stylesheet">` tags.
|
|
836
|
+
* Elements has no font registry, so pass the URLs for any non-system
|
|
837
|
+
* fonts your design uses (e.g. Google Fonts CSS URLs).
|
|
838
|
+
*/
|
|
839
|
+
fonts?: Array<{
|
|
840
|
+
url: string;
|
|
841
|
+
}>;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Renders an Unlayer element tree to a complete HTML document — from
|
|
845
|
+
* `<!DOCTYPE ...>` to `</html>` — matching the editor's exportHtml layouts.
|
|
830
846
|
*
|
|
831
|
-
* -
|
|
832
|
-
* -
|
|
833
|
-
*
|
|
847
|
+
* Per-mode document shells (mirroring the editor's layout templates):
|
|
848
|
+
* - `email`: XHTML transitional doctype, VML/Office namespaces, and the MSO
|
|
849
|
+
* conditional comments Outlook needs
|
|
850
|
+
* - `web`: HTML5 doctype with standard viewport/charset meta tags
|
|
851
|
+
* - `document`: XHTML transitional doctype for print/PDF pipelines
|
|
834
852
|
*
|
|
835
|
-
* @
|
|
836
|
-
*
|
|
837
|
-
*
|
|
853
|
+
* Use {@link renderToHtmlParts} instead when your app owns the document
|
|
854
|
+
* shell and only needs the head/body chunks.
|
|
855
|
+
*
|
|
856
|
+
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
857
|
+
* @param options - Config overrides (mode, cdnBaseUrl, etc.) plus `title` and `fonts`
|
|
858
|
+
* @returns Complete HTML document string
|
|
838
859
|
* @throws {Error} If rendering fails, with a helpful message
|
|
839
860
|
*
|
|
840
861
|
* @example
|
|
841
862
|
* ```tsx
|
|
842
|
-
* import { renderToHtml,
|
|
863
|
+
* import { renderToHtml, Email, Row, Column, Paragraph } from "@unlayer/react-elements";
|
|
843
864
|
*
|
|
844
865
|
* const html = renderToHtml(
|
|
845
|
-
* <
|
|
846
|
-
* {
|
|
866
|
+
* <Email><Row><Column><Paragraph>Hello</Paragraph></Column></Row></Email>,
|
|
867
|
+
* { title: "Welcome" }
|
|
847
868
|
* );
|
|
869
|
+
* // "<!DOCTYPE HTML PUBLIC ...><html ...>...</html>"
|
|
848
870
|
* ```
|
|
849
871
|
*/
|
|
850
|
-
declare function renderToHtml(element: React__default.ReactElement,
|
|
872
|
+
declare function renderToHtml(element: React__default.ReactElement, options?: RenderToHtmlOptions): string;
|
|
851
873
|
/**
|
|
852
874
|
* Renders an Unlayer element tree to a plain text string.
|
|
853
875
|
*
|
|
854
|
-
*
|
|
876
|
+
* Renders the body markup then converts to plain text using
|
|
855
877
|
* `htmlToPlainText`. Useful for generating the text/plain MIME part
|
|
856
878
|
* of multipart emails (critical for deliverability).
|
|
857
879
|
*
|
|
@@ -876,19 +898,26 @@ declare function renderToPlainText(element: React__default.ReactElement, config?
|
|
|
876
898
|
interface HtmlParts {
|
|
877
899
|
/** `<head>` content: `<style>` blocks with component CSS, optional `<script>` tags */
|
|
878
900
|
head: string;
|
|
879
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* The rendered body markup, wrapped in the renderer's host `<div>`. In
|
|
903
|
+
* email/document mode it contains the `<body>` tag itself; web mode has
|
|
904
|
+
* none. No doctype/html/head shell — embeddable as-is, or extract the
|
|
905
|
+
* `<body>` tag when composing a full document.
|
|
906
|
+
*/
|
|
880
907
|
body: string;
|
|
881
908
|
}
|
|
882
909
|
/**
|
|
883
910
|
* Renders an Unlayer element tree to separate head and body HTML strings.
|
|
884
911
|
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
* and
|
|
912
|
+
* Use this when your app owns the document shell — an existing page
|
|
913
|
+
* template, an ESP template with its own meta tags, an iframe srcdoc —
|
|
914
|
+
* and you want to place the `<style>` block and body markup yourself.
|
|
915
|
+
* For a complete ready-to-send document in one call, use
|
|
916
|
+
* {@link renderToHtml}.
|
|
888
917
|
*
|
|
889
918
|
* - `head` contains `<style>` tags with CSS generated by each component's
|
|
890
919
|
* head function (button hover colors, body fonts, link styles, etc.)
|
|
891
|
-
* - `body` is
|
|
920
|
+
* - `body` is the rendered body markup without any document shell
|
|
892
921
|
*
|
|
893
922
|
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
894
923
|
* @param config - Optional config overrides (mode, cdnBaseUrl, etc.)
|
|
@@ -972,4 +1001,4 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
|
|
|
972
1001
|
declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
|
|
973
1002
|
declare function renderToJson(element: React__default.ReactElement): DesignJSON;
|
|
974
1003
|
|
|
975
|
-
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
|
1004
|
+
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, type RenderToHtmlOptions, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
package/dist/index.js
CHANGED
|
@@ -191,6 +191,50 @@ function textNodeToHtml(node) {
|
|
|
191
191
|
}
|
|
192
192
|
return html;
|
|
193
193
|
}
|
|
194
|
+
var NUMERIC_ALIGN = {
|
|
195
|
+
1: "left",
|
|
196
|
+
2: "center",
|
|
197
|
+
3: "right",
|
|
198
|
+
4: "justify"
|
|
199
|
+
};
|
|
200
|
+
function dirAttribute(direction) {
|
|
201
|
+
return direction === "ltr" || direction === "rtl" ? ` dir="${direction}"` : "";
|
|
202
|
+
}
|
|
203
|
+
function paragraphAlign(format) {
|
|
204
|
+
if (typeof format === "string") {
|
|
205
|
+
return ["left", "center", "right", "justify"].includes(format) ? format : void 0;
|
|
206
|
+
}
|
|
207
|
+
if (typeof format === "number") {
|
|
208
|
+
return NUMERIC_ALIGN[format];
|
|
209
|
+
}
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
function paragraphNodeToHtml(node, childrenHtml) {
|
|
213
|
+
const isInlineTool = !!node.isInlineTool;
|
|
214
|
+
const tag = isInlineTool ? "span" : "p";
|
|
215
|
+
const dir = dirAttribute(node.direction);
|
|
216
|
+
const styleParts = [];
|
|
217
|
+
if (node.style) {
|
|
218
|
+
styleParts.push(String(node.style).replace(/;+\s*$/, ""));
|
|
219
|
+
}
|
|
220
|
+
if (!isInlineTool) {
|
|
221
|
+
styleParts.push(
|
|
222
|
+
"margin: 0px",
|
|
223
|
+
"margin-block-start: 0px",
|
|
224
|
+
"margin-block-end: 0px"
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const indent = Number(node.indent) || 0;
|
|
228
|
+
if (indent > 0) {
|
|
229
|
+
styleParts.push(`padding-inline-start: ${indent * 40}px`);
|
|
230
|
+
}
|
|
231
|
+
const align = paragraphAlign(node.format);
|
|
232
|
+
if (align) {
|
|
233
|
+
styleParts.push(`text-align: ${align}`);
|
|
234
|
+
}
|
|
235
|
+
const style = styleParts.length > 0 ? ` style="${escapeHtml(styleParts.join("; ") + ";")}"` : "";
|
|
236
|
+
return `<${tag}${dir}${style}>${childrenHtml || "<br>"}</${tag}>`;
|
|
237
|
+
}
|
|
194
238
|
function nodeToHtml(node) {
|
|
195
239
|
if (!node || typeof node !== "object") return "";
|
|
196
240
|
const type = node.type;
|
|
@@ -205,14 +249,11 @@ function nodeToHtml(node) {
|
|
|
205
249
|
case "root":
|
|
206
250
|
return childrenHtml;
|
|
207
251
|
case "paragraph":
|
|
208
|
-
case "extended-paragraph":
|
|
209
|
-
|
|
210
|
-
const dir = node.direction ? ` dir="${node.direction}"` : "";
|
|
211
|
-
return `<p${dir}>${childrenHtml}</p>`;
|
|
212
|
-
}
|
|
252
|
+
case "extended-paragraph":
|
|
253
|
+
return paragraphNodeToHtml(node, childrenHtml);
|
|
213
254
|
case "heading": {
|
|
214
|
-
const tag = node.tag
|
|
215
|
-
const dir = node.direction
|
|
255
|
+
const tag = /^h[1-6]$/.test(node.tag) ? node.tag : "h1";
|
|
256
|
+
const dir = dirAttribute(node.direction);
|
|
216
257
|
return `<${tag}${dir}>${childrenHtml}</${tag}>`;
|
|
217
258
|
}
|
|
218
259
|
case "link": {
|
|
@@ -408,6 +449,16 @@ function mapSemanticProps(props, defaultValues, componentType) {
|
|
|
408
449
|
if (Object.keys(collected).length > 0) {
|
|
409
450
|
final.border = { ...final.border || {}, ...collected };
|
|
410
451
|
}
|
|
452
|
+
if (final.border && typeof final.border === "object") {
|
|
453
|
+
const b = { ...final.border };
|
|
454
|
+
for (const key of Object.keys(b)) {
|
|
455
|
+
if (!/Width$/.test(key)) continue;
|
|
456
|
+
const v = b[key];
|
|
457
|
+
if (typeof v === "number") b[key] = `${v}px`;
|
|
458
|
+
else if (typeof v === "string" && /^\d+(?:\.\d+)?$/.test(v.trim())) b[key] = `${v.trim()}px`;
|
|
459
|
+
}
|
|
460
|
+
final.border = b;
|
|
461
|
+
}
|
|
411
462
|
}
|
|
412
463
|
return final;
|
|
413
464
|
}
|
|
@@ -1105,7 +1156,7 @@ function getWidthPercentages(cells) {
|
|
|
1105
1156
|
return { value, className };
|
|
1106
1157
|
});
|
|
1107
1158
|
}
|
|
1108
|
-
function generateGridCSS(cells, mode, contentWidth = 600, mobileBreakpoint =
|
|
1159
|
+
function generateGridCSS(cells, mode, contentWidth = 600, mobileBreakpoint = 480) {
|
|
1109
1160
|
const widths = getWidthPercentages(cells);
|
|
1110
1161
|
if (mode === "email") {
|
|
1111
1162
|
const minQuery = `@media only screen and (min-width: ${contentWidth + 20}px)`;
|
|
@@ -1142,21 +1193,14 @@ ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width:
|
|
|
1142
1193
|
const columnCSS = widths.map(
|
|
1143
1194
|
({ value, className }) => `.u-row .u-col.u-col-${className} { flex: 0 0 ${value}%; max-width: ${value}%; }`
|
|
1144
1195
|
).join("\n");
|
|
1145
|
-
const responsiveCSS = `
|
|
1146
|
-
@media
|
|
1147
|
-
.
|
|
1148
|
-
.u-row
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
min-width: 320px !important;
|
|
1196
|
+
const responsiveCSS = mode === "document" ? "" : `
|
|
1197
|
+
@media (max-width: ${mobileBreakpoint}px) {
|
|
1198
|
+
.u_row .container { max-width: 100% !important; }
|
|
1199
|
+
.u-row:not(.no-stack) { flex-wrap: wrap; }
|
|
1200
|
+
.u-row:not(.no-stack) .u-col {
|
|
1201
|
+
flex: 0 0 100% !important;
|
|
1152
1202
|
max-width: 100% !important;
|
|
1153
1203
|
}
|
|
1154
|
-
.u-row .u-col > div { margin: 0 auto; }
|
|
1155
|
-
.no-stack .u-col {
|
|
1156
|
-
min-width: 0 !important;
|
|
1157
|
-
display: table-cell !important;
|
|
1158
|
-
}
|
|
1159
|
-
${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
|
|
1160
1204
|
}`;
|
|
1161
1205
|
return baseCSS + "\n" + columnCSS + "\n" + responsiveCSS;
|
|
1162
1206
|
}
|
|
@@ -1223,10 +1267,17 @@ var Row = (props) => {
|
|
|
1223
1267
|
...semanticProps
|
|
1224
1268
|
} = props;
|
|
1225
1269
|
const mode = modeProp ?? _config?.mode ?? "web";
|
|
1226
|
-
let cells
|
|
1270
|
+
let cells;
|
|
1227
1271
|
if (layout) {
|
|
1228
1272
|
validateColumnLayout(layout, React.Children.count(children));
|
|
1229
1273
|
cells = layout.cells;
|
|
1274
|
+
} else if (propsCells) {
|
|
1275
|
+
cells = propsCells;
|
|
1276
|
+
} else {
|
|
1277
|
+
const columnCount = React.Children.toArray(children).filter(
|
|
1278
|
+
(c) => React.isValidElement(c) && /^Column$/.test(c.type?.displayName || c.type?.name || "")
|
|
1279
|
+
).length;
|
|
1280
|
+
cells = Array(Math.max(1, columnCount)).fill(1);
|
|
1230
1281
|
}
|
|
1231
1282
|
const safeBodyValues = { ...DEFAULT_BODY_VALUES, ...bodyValues };
|
|
1232
1283
|
const values = mapSemanticProps(
|
|
@@ -1650,8 +1701,81 @@ function extractHeadFromTree(element, options) {
|
|
|
1650
1701
|
};
|
|
1651
1702
|
}
|
|
1652
1703
|
|
|
1704
|
+
// src/utils/document-layouts.ts
|
|
1705
|
+
function emailLayout(args) {
|
|
1706
|
+
const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
|
|
1707
|
+
const head = [
|
|
1708
|
+
titleTag,
|
|
1709
|
+
tagLines,
|
|
1710
|
+
styleTag,
|
|
1711
|
+
scriptTag,
|
|
1712
|
+
fontLinks && `<!--[if !mso]><!-->${fontLinks}<!--<![endif]-->`
|
|
1713
|
+
].filter(Boolean).join("\n ");
|
|
1714
|
+
return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
1715
|
+
<html${dir} xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
1716
|
+
<head>
|
|
1717
|
+
<!--[if gte mso 9]>
|
|
1718
|
+
<xml>
|
|
1719
|
+
<o:OfficeDocumentSettings>
|
|
1720
|
+
<o:AllowPNG/>
|
|
1721
|
+
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
1722
|
+
</o:OfficeDocumentSettings>
|
|
1723
|
+
</xml>
|
|
1724
|
+
<![endif]-->
|
|
1725
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
1726
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1727
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
1728
|
+
<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->
|
|
1729
|
+
${head}
|
|
1730
|
+
</head>
|
|
1731
|
+
${body}
|
|
1732
|
+
</html>
|
|
1733
|
+
`;
|
|
1734
|
+
}
|
|
1735
|
+
function documentLayout(args) {
|
|
1736
|
+
const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
|
|
1737
|
+
const head = [titleTag, tagLines, styleTag, scriptTag, fontLinks].filter(Boolean).join("\n ");
|
|
1738
|
+
return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
1739
|
+
<html${dir} xmlns="http://www.w3.org/1999/xhtml">
|
|
1740
|
+
<head>
|
|
1741
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
1742
|
+
${head}
|
|
1743
|
+
</head>
|
|
1744
|
+
${body}
|
|
1745
|
+
</html>
|
|
1746
|
+
`;
|
|
1747
|
+
}
|
|
1748
|
+
function webLayout(args) {
|
|
1749
|
+
const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
|
|
1750
|
+
const head = [titleTag, fontLinks, tagLines, styleTag, scriptTag].filter(Boolean).join("\n ");
|
|
1751
|
+
return `<!doctype html>
|
|
1752
|
+
<html${dir}>
|
|
1753
|
+
<head>
|
|
1754
|
+
<meta charset="utf-8">
|
|
1755
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
1756
|
+
<meta name="description" content="">
|
|
1757
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1758
|
+
${head}
|
|
1759
|
+
</head>
|
|
1760
|
+
<body>
|
|
1761
|
+
${body}
|
|
1762
|
+
</body>
|
|
1763
|
+
</html>
|
|
1764
|
+
`;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1653
1767
|
// src/utils/render-to-html.tsx
|
|
1654
|
-
|
|
1768
|
+
var MODE_BY_WRAPPER = {
|
|
1769
|
+
Email: "email",
|
|
1770
|
+
Page: "web",
|
|
1771
|
+
Document: "document"
|
|
1772
|
+
};
|
|
1773
|
+
function resolveDisplayMode(element, mergedConfig) {
|
|
1774
|
+
const displayName = element.type?.displayName;
|
|
1775
|
+
const wrapperMode = displayName ? MODE_BY_WRAPPER[displayName] : void 0;
|
|
1776
|
+
return wrapperMode ?? element.props.mode ?? mergedConfig.mode ?? "web";
|
|
1777
|
+
}
|
|
1778
|
+
function renderBody(element, config) {
|
|
1655
1779
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
1656
1780
|
try {
|
|
1657
1781
|
const enriched = React.cloneElement(element, { config: mergedConfig });
|
|
@@ -1664,14 +1788,52 @@ Tip: Ensure your tree uses Body > Row > Column > Item structure.`
|
|
|
1664
1788
|
);
|
|
1665
1789
|
}
|
|
1666
1790
|
}
|
|
1791
|
+
function stripOuterDiv(html) {
|
|
1792
|
+
const match = html.match(/^\s*<div[^>]*>([\s\S]*)<\/div>\s*$/);
|
|
1793
|
+
return match ? match[1] : html;
|
|
1794
|
+
}
|
|
1795
|
+
function escapeForHtml(text) {
|
|
1796
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1797
|
+
}
|
|
1798
|
+
function renderToHtml(element, options) {
|
|
1799
|
+
const { title, fonts = [], ...config } = options ?? {};
|
|
1800
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
1801
|
+
const displayMode = resolveDisplayMode(element, mergedConfig);
|
|
1802
|
+
const wrapperName = element.type?.displayName;
|
|
1803
|
+
const isUnlayerWrapper = wrapperName === "Body" || (wrapperName ? wrapperName in MODE_BY_WRAPPER : false);
|
|
1804
|
+
const rawBody = renderBody(element, config);
|
|
1805
|
+
const body = isUnlayerWrapper ? stripOuterDiv(rawBody) : rawBody;
|
|
1806
|
+
const { css, js, tags } = extractHeadFromTree(element, {
|
|
1807
|
+
displayMode,
|
|
1808
|
+
headConfig: mergedConfig.headConfig
|
|
1809
|
+
});
|
|
1810
|
+
const layoutArgs = {
|
|
1811
|
+
dir: mergedConfig.textDirection ? ` dir="${escapeForHtml(String(mergedConfig.textDirection))}"` : "",
|
|
1812
|
+
titleTag: title ? `<title>${escapeForHtml(title.trim())}</title>` : "",
|
|
1813
|
+
styleTag: css ? `<style type="text/css">
|
|
1814
|
+
${css}
|
|
1815
|
+
</style>` : "",
|
|
1816
|
+
scriptTag: js ? `<script type="application/javascript">
|
|
1817
|
+
${js}
|
|
1818
|
+
</script>` : "",
|
|
1819
|
+
tagLines: tags.join("\n"),
|
|
1820
|
+
fontLinks: fonts.filter((font) => font?.url).map(
|
|
1821
|
+
(font) => `<link href="${escapeForHtml(font.url)}" rel="stylesheet" type="text/css">`
|
|
1822
|
+
).join(""),
|
|
1823
|
+
body
|
|
1824
|
+
};
|
|
1825
|
+
if (displayMode === "email") return emailLayout(layoutArgs);
|
|
1826
|
+
if (displayMode === "document") return documentLayout(layoutArgs);
|
|
1827
|
+
return webLayout(layoutArgs);
|
|
1828
|
+
}
|
|
1667
1829
|
function renderToPlainText(element, config) {
|
|
1668
|
-
const html =
|
|
1830
|
+
const html = renderBody(element, config);
|
|
1669
1831
|
return htmlToPlainText(html);
|
|
1670
1832
|
}
|
|
1671
1833
|
function renderToHtmlParts(element, config) {
|
|
1672
|
-
const body =
|
|
1834
|
+
const body = renderBody(element, config);
|
|
1673
1835
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
1674
|
-
const displayMode = element
|
|
1836
|
+
const displayMode = resolveDisplayMode(element, mergedConfig);
|
|
1675
1837
|
const { css, js, tags } = extractHeadFromTree(element, {
|
|
1676
1838
|
displayMode,
|
|
1677
1839
|
headConfig: mergedConfig.headConfig
|