@unlayer/react-elements 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ButtonDefaults, DividerDefaults, HeadingDefaults, HtmlDefaults, ImageDefaults, MenuDefaults, ParagraphDefaults, SocialDefaults, TableDefaults, VideoDefaults, BodyDefaults, RowDefaults, ColumnDefaults, ButtonExporters, DividerExporters, HeadingExporters, HtmlExporters, ImageExporters, MenuExporters, ParagraphExporters, SocialExporters, TableExporters, VideoExporters, schemaVersion as schemaVersion$1, RowExporters, ColumnExporters, BodyExporters, heads } from '@unlayer/exporters';
1
+ import { ButtonDefaults, DividerDefaults, HeadingDefaults, HtmlDefaults, ImageDefaults, MenuDefaults, ParagraphDefaults, SocialDefaults, TableDefaults, VideoDefaults, BodyDefaults, RowDefaults, ColumnDefaults, ButtonExporters, DividerExporters, HeadingExporters, HtmlExporters, ImageExporters, MenuExporters, ParagraphExporters, SocialExporters, TableExporters, VideoExporters, schemaVersion as schemaVersion$1, RowExporters, ContentExporters, ColumnExporters, BodyExporters, heads } from '@unlayer/exporters';
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import React, { useMemo, useContext, createContext } from 'react';
4
4
  import ReactDOMServer, { renderToStaticMarkup } from 'react-dom/server';
@@ -201,16 +201,53 @@ function analyzeNestedStructure(defaultValues) {
201
201
  nestedStructureCache.set(defaultValues, nestedGroups);
202
202
  return nestedGroups;
203
203
  }
204
+ var PX_SIZE_KEYS = [
205
+ "fontSize",
206
+ "padding",
207
+ "containerPadding",
208
+ "borderRadius",
209
+ "letterSpacing"
210
+ ];
211
+ function normalizeCssProps(props) {
212
+ if (typeof props.fontFamily === "string") {
213
+ const v = props.fontFamily;
214
+ props.fontFamily = { label: v, value: v };
215
+ }
216
+ if (typeof props.fontWeight === "string" && /^\d+$/.test(props.fontWeight.trim())) {
217
+ props.fontWeight = Number(props.fontWeight.trim());
218
+ }
219
+ if (typeof props.lineHeight === "number") {
220
+ props.lineHeight = String(props.lineHeight);
221
+ }
222
+ for (const key of PX_SIZE_KEYS) {
223
+ const v = props[key];
224
+ if (typeof v === "number") {
225
+ props[key] = `${v}px`;
226
+ } else if (typeof v === "string" && /^\d+$/.test(v.trim())) {
227
+ props[key] = `${v.trim()}px`;
228
+ }
229
+ }
230
+ }
231
+ function flattenChildrenText(node) {
232
+ if (node == null || typeof node === "boolean") return "";
233
+ if (typeof node === "string") return node;
234
+ if (typeof node === "number") return String(node);
235
+ if (Array.isArray(node)) return node.map(flattenChildrenText).join("");
236
+ if (typeof node === "object" && "props" in node) {
237
+ return flattenChildrenText(node.props?.children);
238
+ }
239
+ return "";
240
+ }
204
241
  function mapSemanticProps(props, defaultValues, componentType) {
205
242
  const { children, values, ...restProps } = props;
206
243
  const userProps = { ...restProps };
207
244
  const result = values ? { ...values } : {};
208
245
  if (children !== void 0 && !result.text && !result.textJson) {
246
+ const textContent = typeof children === "string" ? children : flattenChildrenText(children);
209
247
  if (componentType === "Paragraph") {
210
- const textContent = typeof children === "string" ? children : String(children);
211
248
  result.textJson = textToTextJson(textContent);
212
249
  } else {
213
- result.text = children;
250
+ result.text = textContent;
214
251
  }
215
252
  }
216
253
  const textFromEscapeHatch = result.text;
@@ -236,6 +273,7 @@ function mapSemanticProps(props, defaultValues, componentType) {
236
273
  };
237
274
  }
238
275
  }
276
+ normalizeCssProps(userProps);
239
277
  const nestedGroups = analyzeNestedStructure(defaultValues);
240
278
  const nested = {};
241
279
  const flat = {};
@@ -270,6 +308,19 @@ function mapSemanticProps(props, defaultValues, componentType) {
270
308
  };
271
309
  }
272
310
  }
311
+ if (defaultValues && typeof defaultValues === "object" && "border" in defaultValues) {
312
+ const borderSideRe = /^border(Top|Right|Bottom|Left)(Width|Style|Color)$/;
313
+ const collected = {};
314
+ for (const key of Object.keys(final)) {
315
+ if (borderSideRe.test(key)) {
316
+ collected[key] = final[key];
317
+ delete final[key];
318
+ }
319
+ }
320
+ if (Object.keys(collected).length > 0) {
321
+ final.border = { ...final.border || {}, ...collected };
322
+ }
323
+ }
273
324
  return final;
274
325
  }
275
326
  function normalizeLinkValue(value) {
@@ -538,7 +589,26 @@ var DEFAULT_VALUES = {
538
589
  var Button = createItemComponent({
539
590
  name: "Button",
540
591
  defaultValues: DEFAULT_VALUES,
541
- propMapper: (props) => mapSemanticProps(props, DEFAULT_VALUES, "Button"),
592
+ propMapper: (props) => {
593
+ const mapped = mapSemanticProps(
594
+ props,
595
+ DEFAULT_VALUES,
596
+ "Button"
597
+ );
598
+ const size = mapped.size;
599
+ if (size && typeof size === "object" && !Array.isArray(size)) {
600
+ const s = size;
601
+ if (typeof s.width === "number") {
602
+ s.width = `${s.width}px`;
603
+ } else if (typeof s.width === "string" && /^\d+(?:\.\d+)?$/.test(s.width.trim())) {
604
+ s.width = `${s.width.trim()}px`;
605
+ }
606
+ if (s.width !== void 0 && s.autoWidth === void 0) {
607
+ s.autoWidth = false;
608
+ }
609
+ }
610
+ return mapped;
611
+ },
542
612
  displayName: "Button",
543
613
  exporters: ButtonExporters
544
614
  });
@@ -603,18 +673,46 @@ var Image = createItemComponent({
603
673
  defaultValues: DEFAULT_VALUES5,
604
674
  propMapper: (props) => {
605
675
  const { alt, src, ...rest } = props;
676
+ const restValues = rest.values;
677
+ const normalizedRest = restValues && typeof restValues.src === "string" ? { ...rest, values: { ...restValues, src: { url: restValues.src } } } : rest;
606
678
  const base = mapSemanticProps(
607
- rest,
679
+ normalizedRest,
608
680
  DEFAULT_VALUES5,
609
681
  "Image"
610
682
  );
611
683
  if (alt !== void 0) {
612
684
  base.altText = alt;
613
685
  }
614
- if (typeof src === "string") {
615
- base.src = { url: src, autoWidth: true, maxWidth: "100%" };
616
- } else if (src !== void 0) {
617
- base.src = { ...DEFAULT_VALUES5.src, ...src };
686
+ const baseSrc = base.src;
687
+ const fromValues = baseSrc && typeof baseSrc === "object" && !Array.isArray(baseSrc) ? baseSrc : typeof baseSrc === "string" ? { url: baseSrc } : {};
688
+ const fromProp = typeof src === "string" ? { url: src } : src ?? {};
689
+ const userSrc = { ...fromValues, ...fromProp };
690
+ if (src !== void 0 || baseSrc !== void 0) {
691
+ const isStringUrl = typeof src === "string" || src === void 0 && typeof baseSrc === "string";
692
+ const start = isStringUrl ? { autoWidth: true, maxWidth: "100%" } : { ...DEFAULT_VALUES5.src };
693
+ const merged = { ...start, ...userSrc };
694
+ const pctRe = /^\d+(?:\.\d+)?%$/;
695
+ if (typeof merged.width === "string") {
696
+ const t = merged.width.trim();
697
+ if (pctRe.test(t)) {
698
+ if (userSrc.maxWidth === void 0) merged.maxWidth = t;
699
+ delete merged.width;
700
+ } else {
701
+ const px = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(t);
702
+ if (px) merged.width = parseFloat(px[1]);
703
+ }
704
+ }
705
+ const displayPct = typeof merged.maxWidth === "string" && pctRe.test(merged.maxWidth.trim()) ? merged.maxWidth.trim() : void 0;
706
+ if (userSrc.autoWidth === void 0) {
707
+ if (displayPct && displayPct !== "100%") {
708
+ merged.autoWidth = false;
709
+ merged.maxWidth = displayPct;
710
+ } else {
711
+ merged.autoWidth = true;
712
+ merged.maxWidth = "100%";
713
+ }
714
+ }
715
+ base.src = merged;
618
716
  }
619
717
  return base;
620
718
  },
@@ -725,25 +823,37 @@ var Table = createItemComponent({
725
823
  name: "Table",
726
824
  defaultValues: DEFAULT_VALUES9,
727
825
  propMapper: (props) => {
728
- const { headers, data, ...rest } = props;
729
- if (headers || data) {
826
+ const { headers, data, columns, rows, ...rest } = props;
827
+ if (headers || data || typeof columns === "number" || typeof rows === "number") {
730
828
  const base = mapSemanticProps(
731
829
  rest,
732
830
  DEFAULT_VALUES9,
733
831
  "Table"
734
832
  );
833
+ const colCount = headers ? headers.length : typeof columns === "number" ? columns : data?.[0]?.length ?? 0;
834
+ const blankCells = (n) => Array.from({ length: n }, () => ({ text: "", width: 0 }));
735
835
  const tableHeaders = headers ? [{ cells: headers.map((text) => ({ text, width: 0 })), height: 0 }] : [];
736
836
  const tableRows = data ? data.map((row) => ({
737
837
  cells: row.map((text) => ({ text, width: 0 })),
738
838
  height: 0
739
- })) : [];
839
+ })) : typeof rows === "number" ? (
840
+ // No data: build an empty grid sized by `columns` × `rows`.
841
+ Array.from({ length: rows }, () => ({
842
+ cells: blankCells(colCount),
843
+ height: 0
844
+ }))
845
+ ) : [];
740
846
  base.table = { headers: tableHeaders, rows: tableRows, footers: [] };
847
+ if (headers || typeof columns === "number") {
848
+ base.columns = colCount;
849
+ }
741
850
  if (headers) {
742
- base.columns = headers.length;
743
851
  base.enableHeader = true;
744
852
  }
745
853
  if (data) {
746
854
  base.rows = data.length;
855
+ } else if (typeof rows === "number") {
856
+ base.rows = rows;
747
857
  }
748
858
  return base;
749
859
  }
@@ -904,13 +1014,23 @@ ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width:
904
1014
  }`;
905
1015
  return baseCSS + "\n" + columnCSS + "\n" + responsiveCSS;
906
1016
  }
1017
+ function toContentWidthPx(bodyValues, fallback = 500) {
1018
+ const raw = bodyValues?.contentWidth;
1019
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
1020
+ if (typeof raw === "string") {
1021
+ const n = parseInt(raw, 10);
1022
+ if (Number.isFinite(n)) return n;
1023
+ }
1024
+ return fallback;
1025
+ }
907
1026
  function renderRowToHtml(innerHTML, values, bodyValues, mode, cells, collection = "rows") {
908
1027
  const rowExporter = RowExporters[mode] || RowExporters.web;
909
1028
  const html = rowExporter(innerHTML, values, bodyValues, {
910
1029
  collection,
911
1030
  variant: mode
912
1031
  });
913
- const css = generateGridCSS(cells, mode);
1032
+ const contentWidth = toContentWidthPx(bodyValues);
1033
+ const css = generateGridCSS(cells, mode, contentWidth);
914
1034
  return css ? `<style>${css}</style>${html}` : html;
915
1035
  }
916
1036
  function processChildren(children, cells, bodyValues, rowValues, mode, _config) {
@@ -1008,11 +1128,16 @@ var Row = (props) => {
1008
1128
  };
1009
1129
  Row.displayName = "Row";
1010
1130
  var Row_default = Row;
1131
+ var DEFAULT_CONTAINER_PADDING = "10px";
1011
1132
  var DEFAULT_VALUES12 = COLUMN_DEFAULTS;
1012
1133
  function renderColumnToHtml(innerHTML, values, index, cells, bodyValues, rowValues, mode) {
1013
1134
  const columnExporter = ColumnExporters[mode] || ColumnExporters.web;
1014
1135
  return columnExporter(innerHTML, values, index, cells, bodyValues, rowValues);
1015
1136
  }
1137
+ function renderContentToHtml(innerHTML, values, bodyValues, mode) {
1138
+ const contentExporter = ContentExporters[mode] || ContentExporters.web;
1139
+ return contentExporter(innerHTML, values, bodyValues, {});
1140
+ }
1016
1141
  var Column = (props) => {
1017
1142
  const {
1018
1143
  children,
@@ -1055,10 +1180,22 @@ var Column = (props) => {
1055
1180
  if (rendered && typeof rendered === "object" && rendered.props && rendered.props.dangerouslySetInnerHTML) {
1056
1181
  const componentHTML = rendered.props.dangerouslySetInnerHTML.__html;
1057
1182
  const componentType = child.type;
1058
- const componentName = componentType?.displayName || componentType?.name || "component";
1059
- const componentProps = child.props;
1060
- const containerPadding = componentProps.values?.containerPadding || DEFAULT_VALUES12.padding || "10px";
1061
- innerHTML += `<div id="u_content_${componentName.toLowerCase()}_${childIndex + 1}" class="u_content_${componentName.toLowerCase()}" style="padding: ${containerPadding};">${componentHTML}</div>`;
1183
+ const componentName = (componentType?.displayName || componentType?.name || "component").toLowerCase();
1184
+ const childProps = child.props;
1185
+ const containerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING;
1186
+ const contentValues = {
1187
+ containerPadding,
1188
+ _meta: {
1189
+ htmlID: `u_content_${componentName}_${childIndex + 1}`,
1190
+ htmlClassNames: `u_content_${componentName}`
1191
+ }
1192
+ };
1193
+ innerHTML += renderContentToHtml(
1194
+ componentHTML,
1195
+ contentValues,
1196
+ bodyValues,
1197
+ mode
1198
+ );
1062
1199
  } else if (rendered) {
1063
1200
  const name = child.type?.displayName || child.type?.name || "Unknown";
1064
1201
  console.warn(
@@ -1120,7 +1257,13 @@ function renderBodyToHtml(innerHTML, values, mode, previewText) {
1120
1257
  }
1121
1258
  }
1122
1259
  const bodyExporter = BodyExporters[mode] || BodyExporters.web;
1123
- const raw = mode === "document" ? bodyExporter(finalInnerHtml, values, { type: "" }) : bodyExporter(finalInnerHtml, values, values);
1260
+ const raw = mode === "document" ? bodyExporter(finalInnerHtml, values, { type: "" }) : mode === "email" ? (
1261
+ // The email body exporter reads body context (contentWidth,
1262
+ // contentAlign) from the `bodyValues` field of its 3rd argument.
1263
+ // Passing `values` directly left it undefined, so the Outlook (MSO)
1264
+ // table fell back to 600px regardless of contentWidth.
1265
+ bodyExporter(finalInnerHtml, values, { bodyValues: values })
1266
+ ) : bodyExporter(finalInnerHtml, values, values);
1124
1267
  return raw.replace("min-height: 100vh; ", "").replace("min-height: 100vh;", "");
1125
1268
  }
1126
1269
  var Body = (props) => {
@@ -1128,7 +1271,10 @@ var Body = (props) => {
1128
1271
  const resolvedConfig = { ...DEFAULT_CONFIG, ...configProp };
1129
1272
  const mode = modeProp ?? resolvedConfig.mode ?? "web";
1130
1273
  const _config = { ...resolvedConfig, mode };
1131
- const values = mapSemanticProps(semanticProps, DEFAULT_VALUES13, "Body");
1274
+ const values = mergeValues(
1275
+ DEFAULT_VALUES13,
1276
+ mapSemanticProps(semanticProps, DEFAULT_VALUES13, "Body")
1277
+ );
1132
1278
  const valuesWithMeta = {
1133
1279
  ...values,
1134
1280
  _meta: {
@@ -1141,7 +1287,10 @@ var Body = (props) => {
1141
1287
  if (children) {
1142
1288
  enrichedChildren = React.Children.map(children, (child) => {
1143
1289
  if (React.isValidElement(child)) {
1144
- return React.cloneElement(child, { _config });
1290
+ return React.cloneElement(child, {
1291
+ _config,
1292
+ bodyValues: values
1293
+ });
1145
1294
  }
1146
1295
  return child;
1147
1296
  });
@@ -1573,6 +1722,10 @@ function processBody(element, counters) {
1573
1722
  const semanticProps = extractSemanticProps2(element.props);
1574
1723
  const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
1575
1724
  const values = mergeValues(BODY_DEFAULTS, mapped);
1725
+ const previewText = element.props.previewText;
1726
+ if (previewText !== void 0) {
1727
+ values.preheaderText = previewText;
1728
+ }
1576
1729
  const valuesWithMeta = {
1577
1730
  ...values,
1578
1731
  _meta: {