@unlayer/react-elements 0.1.13 → 0.1.15

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
@@ -12,6 +12,81 @@ var ReactDOMServer__default = /*#__PURE__*/_interopDefault(ReactDOMServer);
12
12
 
13
13
  // src/components/Button.tsx
14
14
 
15
+ // src/utils/image-sizing.ts
16
+ function toPx(value) {
17
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
18
+ if (typeof value !== "string") return void 0;
19
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(value.trim());
20
+ return m ? parseFloat(m[1]) : void 0;
21
+ }
22
+ function edges(value) {
23
+ if (value == null) return { left: 0, right: 0 };
24
+ 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 };
30
+ }
31
+ function borderEdges(border) {
32
+ if (!border || typeof border !== "object") return { left: 0, right: 0 };
33
+ const b = border;
34
+ const width = (v) => parseFloat(`${v ?? ""}`) || 0;
35
+ return {
36
+ left: width(b.borderLeftWidth),
37
+ right: width(b.borderRightWidth)
38
+ };
39
+ }
40
+ function fixedContentWidth(contentWidth) {
41
+ if (typeof contentWidth === "number")
42
+ return Number.isFinite(contentWidth) ? contentWidth : void 0;
43
+ if (typeof contentWidth === "string") {
44
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(contentWidth.trim());
45
+ if (m) return parseFloat(m[1]);
46
+ }
47
+ return void 0;
48
+ }
49
+ var FALLBACK_BODY_CONTENT_WIDTH = 500;
50
+ var DEFAULT_CONTAINER_PADDING = "10px";
51
+ function bodyContentWidthPx(contentWidth, fallback = FALLBACK_BODY_CONTENT_WIDTH) {
52
+ return fixedContentWidth(contentWidth) ?? fallback;
53
+ }
54
+ function contentSlotWidth(ctx) {
55
+ const { bodyValues = {}, rowValues = {}, columnValues = {} } = ctx;
56
+ const rowCells = ctx.rowCells && ctx.rowCells.length ? ctx.rowCells : [1];
57
+ const columnIndex = ctx.columnIndex ?? 0;
58
+ const bodyWidth = bodyContentWidthPx(bodyValues.contentWidth);
59
+ const bp = edges(bodyValues.padding);
60
+ const bb = borderEdges(bodyValues.border);
61
+ const bodyAvail = bodyWidth - bp.left - bp.right - bb.left - bb.right;
62
+ const rp = edges(rowValues.padding);
63
+ const rb = borderEdges(rowValues.border);
64
+ const rowAvail = bodyAvail - rp.left - rp.right - rb.left - rb.right;
65
+ const rowSpan = rowCells.reduce((a, b) => a + b, 0) || 1;
66
+ const colSpan = rowCells[columnIndex] || 1;
67
+ const colWidth = colSpan / rowSpan * rowAvail;
68
+ const cp = edges(columnValues.padding);
69
+ const cb = borderEdges(columnValues.border);
70
+ const colAvail = colWidth - cp.left - cp.right - cb.left - cb.right;
71
+ const ip = edges(ctx.containerPadding ?? DEFAULT_CONTAINER_PADDING);
72
+ return colAvail - ip.left - ip.right;
73
+ }
74
+ var PERCENT = /^\d+(?:\.\d+)?%$/;
75
+ function round2(n) {
76
+ return Math.round(n * 100) / 100;
77
+ }
78
+ function pinImageSrc(src, availableWidth) {
79
+ if (!src || typeof src !== "object") return src;
80
+ if (src.autoWidth !== false) return src;
81
+ const maxWidth = src.maxWidth;
82
+ if (typeof maxWidth === "string" && PERCENT.test(maxWidth.trim())) return src;
83
+ const pinPx = toPx(maxWidth);
84
+ if (pinPx == null) return src;
85
+ const avail = availableWidth && availableWidth > 0 ? availableWidth : void 0;
86
+ const pct = avail ? pinPx >= avail ? 100 : round2(pinPx / avail * 100) : 100;
87
+ return { ...src, autoWidth: false, maxWidth: `${pct}%` };
88
+ }
89
+
15
90
  // ../shared/dist/index.js
16
91
  var DEFAULT_CONFIG = {
17
92
  cdnBaseUrl: "https://cdn.tools.unlayer.com",
@@ -271,14 +346,27 @@ function mapSemanticProps(props, defaultValues, componentType) {
271
346
  delete userProps.html;
272
347
  delete result.html;
273
348
  }
274
- for (const key of ["href", "action"]) {
275
- const v = userProps[key];
349
+ const canonicalizeLink = (v) => {
276
350
  if (typeof v === "string") {
277
- userProps[key] = {
278
- name: "web",
279
- values: { href: v, target: "_blank" }
280
- };
351
+ return { name: "web", values: { href: v, target: "_blank" } };
352
+ }
353
+ if (v && typeof v === "object" && "name" in v && v.attrs && typeof v.attrs === "object") {
354
+ const { href: attrsHref, target: attrsTarget, ...customAttrs } = v.attrs;
355
+ if (attrsHref !== void 0 || attrsTarget !== void 0) {
356
+ const linkValues = { ...v.values };
357
+ if (!linkValues.href && attrsHref !== void 0) linkValues.href = attrsHref;
358
+ if (!linkValues.target && attrsTarget !== void 0) linkValues.target = attrsTarget;
359
+ const next = { ...v, values: linkValues };
360
+ if (Object.keys(customAttrs).length) next.attrs = customAttrs;
361
+ else delete next.attrs;
362
+ return next;
363
+ }
281
364
  }
365
+ return v;
366
+ };
367
+ for (const key of ["href", "action"]) {
368
+ if (userProps[key] !== void 0) userProps[key] = canonicalizeLink(userProps[key]);
369
+ if (result[key] !== void 0) result[key] = canonicalizeLink(result[key]);
282
370
  }
283
371
  normalizeCssProps(userProps);
284
372
  const nestedGroups = analyzeNestedStructure(defaultValues);
@@ -338,12 +426,14 @@ function normalizeLinkValue(value) {
338
426
  if (typeof value !== "object") return void 0;
339
427
  const v = value;
340
428
  if ("url" in v) return v;
341
- if ("name" in v && v.values && typeof v.values === "object") {
342
- const inner = v.values;
429
+ if ("name" in v && (v.values && typeof v.values === "object" || v.attrs && typeof v.attrs === "object")) {
430
+ const inner = v.values && typeof v.values === "object" ? v.values : {};
431
+ const attrs = v.attrs && typeof v.attrs === "object" ? v.attrs : {};
432
+ const { href: attrsHref, target: attrsTarget, ...customAttrs } = attrs;
343
433
  return {
344
- url: inner.href ?? "",
345
- target: inner.target ?? "_blank",
346
- ...v.attrs ?? {}
434
+ url: inner.href || attrsHref || "",
435
+ target: inner.target || attrsTarget || "_blank",
436
+ ...customAttrs
347
437
  };
348
438
  }
349
439
  return void 0;
@@ -571,6 +661,18 @@ function createItemComponent(config) {
571
661
  valuesWithMeta,
572
662
  config.name
573
663
  );
664
+ const exportSrc = valuesForExporter.src;
665
+ if (exportSrc && typeof exportSrc === "object" && exportSrc.autoWidth === false) {
666
+ const availableWidth = contentSlotWidth({
667
+ bodyValues: safeBodyValues,
668
+ rowValues,
669
+ rowCells: cells,
670
+ columnIndex: colIndex,
671
+ columnValues,
672
+ containerPadding: props.containerPadding ?? props.values?.containerPadding
673
+ });
674
+ valuesForExporter.src = pinImageSrc(exportSrc, availableWidth);
675
+ }
574
676
  const exporter = config.exporters[mode] || config.exporters.web;
575
677
  return renderComponent({
576
678
  type: config.name,
@@ -683,11 +785,11 @@ var Html = createItemComponent({
683
785
  exporters: exporters.HtmlExporters
684
786
  });
685
787
  var Html_default = Html;
788
+ var { height: _placeholderHeight, ...defaultSrc } = exporters.ImageDefaults.src;
686
789
  var DEFAULT_VALUES5 = {
687
790
  ...exporters.ImageDefaults,
688
- // Override src with autoWidth/maxWidth for responsive rendering
689
791
  src: {
690
- ...exporters.ImageDefaults.src,
792
+ ...defaultSrc,
691
793
  autoWidth: true,
692
794
  maxWidth: "100%"
693
795
  }
@@ -696,7 +798,7 @@ var Image = createItemComponent({
696
798
  name: "Image",
697
799
  defaultValues: DEFAULT_VALUES5,
698
800
  propMapper: (props) => {
699
- const { alt, src, ...rest } = props;
801
+ const { alt, src, width: widthProp, maxWidth: maxWidthProp, ...rest } = props;
700
802
  const restValues = rest.values;
701
803
  const normalizedRest = restValues && typeof restValues.src === "string" ? { ...rest, values: { ...restValues, src: { url: restValues.src } } } : rest;
702
804
  const base = mapSemanticProps(
@@ -716,21 +818,39 @@ var Image = createItemComponent({
716
818
  const start = isStringUrl ? { autoWidth: true, maxWidth: "100%" } : { ...DEFAULT_VALUES5.src };
717
819
  const merged = { ...start, ...userSrc };
718
820
  const pctRe = /^\d+(?:\.\d+)?%$/;
719
- if (typeof merged.width === "string") {
720
- const t = merged.width.trim();
721
- if (pctRe.test(t)) {
722
- if (userSrc.maxWidth === void 0) merged.maxWidth = t;
723
- delete merged.width;
724
- } else {
725
- const px = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(t);
726
- if (px) merged.width = parseFloat(px[1]);
821
+ const asPercent = (v) => typeof v === "string" && pctRe.test(v.trim()) ? v.trim() : void 0;
822
+ const asPx = (v) => {
823
+ if (typeof v === "number" && Number.isFinite(v)) return v;
824
+ if (typeof v === "string") {
825
+ const t = v.trim();
826
+ if (pctRe.test(t)) return void 0;
827
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(t);
828
+ if (m) return parseFloat(m[1]);
829
+ }
830
+ return void 0;
831
+ };
832
+ let displayPct;
833
+ let displayPx;
834
+ for (const candidate of [widthProp, maxWidthProp, userSrc.maxWidth, userSrc.width]) {
835
+ if (candidate === void 0) continue;
836
+ const pct = asPercent(candidate);
837
+ if (pct) {
838
+ displayPct = pct;
839
+ break;
840
+ }
841
+ const px = asPx(candidate);
842
+ if (px != null) {
843
+ displayPx = px;
844
+ break;
727
845
  }
728
846
  }
729
- const displayPct = typeof merged.maxWidth === "string" && pctRe.test(merged.maxWidth.trim()) ? merged.maxWidth.trim() : void 0;
730
847
  if (userSrc.autoWidth === void 0) {
731
848
  if (displayPct && displayPct !== "100%") {
732
849
  merged.autoWidth = false;
733
850
  merged.maxWidth = displayPct;
851
+ } else if (displayPx != null) {
852
+ merged.autoWidth = false;
853
+ merged.maxWidth = displayPx;
734
854
  } else {
735
855
  merged.autoWidth = true;
736
856
  merged.maxWidth = "100%";
@@ -802,6 +922,17 @@ var Social = createItemComponent({
802
922
  defaultValues: DEFAULT_VALUES8,
803
923
  propMapper: (props) => {
804
924
  const { icons, iconType, ...rest } = props;
925
+ const coerceSizes = (base) => {
926
+ for (const key of ["iconSize", "spacing"]) {
927
+ const v = base[key];
928
+ if (typeof v === "string") {
929
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(v.trim());
930
+ if (m) base[key] = parseFloat(m[1]);
931
+ else delete base[key];
932
+ }
933
+ }
934
+ return base;
935
+ };
805
936
  if (Array.isArray(icons)) {
806
937
  const mapped = icons.map((icon) => ({
807
938
  name: icon.name,
@@ -816,7 +947,7 @@ var Social = createItemComponent({
816
947
  iconType: iconType ?? base.icons?.iconType ?? "circle",
817
948
  icons: mapped
818
949
  };
819
- return base;
950
+ return coerceSizes(base);
820
951
  }
821
952
  if (iconType !== void 0) {
822
953
  const base = mapSemanticProps(
@@ -825,12 +956,10 @@ var Social = createItemComponent({
825
956
  "Social"
826
957
  );
827
958
  base.icons = { ...DEFAULT_ICONS, ...base.icons, iconType };
828
- return base;
959
+ return coerceSizes(base);
829
960
  }
830
- return mapSemanticProps(
831
- props,
832
- DEFAULT_VALUES8,
833
- "Social"
961
+ return coerceSizes(
962
+ mapSemanticProps(props, DEFAULT_VALUES8, "Social")
834
963
  );
835
964
  },
836
965
  displayName: "Social",
@@ -1039,13 +1168,7 @@ ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width:
1039
1168
  return baseCSS + "\n" + columnCSS + "\n" + responsiveCSS;
1040
1169
  }
1041
1170
  function toContentWidthPx(bodyValues, fallback = 500) {
1042
- const raw = bodyValues?.contentWidth;
1043
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
1044
- if (typeof raw === "string") {
1045
- const n = parseInt(raw, 10);
1046
- if (Number.isFinite(n)) return n;
1047
- }
1048
- return fallback;
1171
+ return bodyContentWidthPx(bodyValues?.contentWidth, fallback);
1049
1172
  }
1050
1173
  function renderRowToHtml(innerHTML, values, bodyValues, mode, cells, collection = "rows") {
1051
1174
  const rowExporter = exporters.RowExporters[mode] || exporters.RowExporters.web;
@@ -1152,7 +1275,7 @@ var Row = (props) => {
1152
1275
  };
1153
1276
  Row.displayName = "Row";
1154
1277
  var Row_default = Row;
1155
- var DEFAULT_CONTAINER_PADDING = "10px";
1278
+ var DEFAULT_CONTAINER_PADDING2 = "10px";
1156
1279
  var DEFAULT_VALUES12 = COLUMN_DEFAULTS;
1157
1280
  function renderColumnToHtml(innerHTML, values, index, cells, bodyValues, rowValues, mode) {
1158
1281
  const columnExporter = exporters.ColumnExporters[mode] || exporters.ColumnExporters.web;
@@ -1214,7 +1337,7 @@ var Column = (props) => {
1214
1337
  const componentType = child.type;
1215
1338
  const componentName = (componentType?.displayName || componentType?.name || "component").toLowerCase();
1216
1339
  const childProps = child.props;
1217
- const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING;
1340
+ const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING2;
1218
1341
  const containerPadding = typeof rawContainerPadding === "number" ? `${rawContainerPadding}px` : rawContainerPadding;
1219
1342
  const contentValues = {
1220
1343
  containerPadding,
@@ -1669,7 +1792,7 @@ function extractTextFromTextJson(textJson) {
1669
1792
  return "";
1670
1793
  }
1671
1794
  }
1672
- function processItem(element, counters) {
1795
+ function processItem(element, counters, layout = {}) {
1673
1796
  const componentType = element.type;
1674
1797
  const config = componentType[UNLAYER_CONFIG_KEY];
1675
1798
  if (!config) {
@@ -1703,9 +1826,17 @@ function processItem(element, counters) {
1703
1826
  deletable: true,
1704
1827
  hideable: true
1705
1828
  };
1829
+ const itemSrc = values.src;
1830
+ if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
1831
+ const availableWidth = contentSlotWidth({
1832
+ ...layout,
1833
+ containerPadding: values.containerPadding
1834
+ });
1835
+ values.src = pinImageSrc(itemSrc, availableWidth);
1836
+ }
1706
1837
  return { type: contentType, values };
1707
1838
  }
1708
- function processColumn(element, counters) {
1839
+ function processColumn(element, counters, layout = {}) {
1709
1840
  const count = nextCounter2(counters, "u_column");
1710
1841
  const id = makeId("u_column", count);
1711
1842
  const semanticProps = extractSemanticProps2(element.props);
@@ -1721,12 +1852,13 @@ function processColumn(element, counters) {
1721
1852
  };
1722
1853
  const contents = [];
1723
1854
  const children = collectChildren2(element.props.children);
1855
+ const itemLayout = { ...layout, columnValues: valuesWithMeta };
1724
1856
  for (const child of children) {
1725
- contents.push(processItem(child, counters));
1857
+ contents.push(processItem(child, counters, itemLayout));
1726
1858
  }
1727
1859
  return { contents, values: valuesWithMeta };
1728
1860
  }
1729
- function processRow(element, counters) {
1861
+ function processRow(element, counters, parentLayout = {}) {
1730
1862
  const count = nextCounter2(counters, "u_row");
1731
1863
  const id = makeId("u_row", count);
1732
1864
  const { layout, cells: propsCells } = element.props;
@@ -1738,7 +1870,9 @@ function processRow(element, counters) {
1738
1870
  } else {
1739
1871
  const columnCount = Math.max(
1740
1872
  1,
1741
- collectChildren2(element.props.children).length
1873
+ collectChildren2(element.props.children).filter(
1874
+ (child) => getDisplayName2(child) === "Column"
1875
+ ).length
1742
1876
  );
1743
1877
  cells = Array(columnCount).fill(1);
1744
1878
  }
@@ -1762,10 +1896,19 @@ function processRow(element, counters) {
1762
1896
  };
1763
1897
  const columns = [];
1764
1898
  const children = collectChildren2(element.props.children);
1899
+ const columnLayout = {
1900
+ bodyValues: parentLayout.bodyValues,
1901
+ rowValues: valuesWithMeta,
1902
+ rowCells: cells
1903
+ };
1904
+ let columnIndex = 0;
1765
1905
  for (const child of children) {
1766
1906
  const name = getDisplayName2(child);
1767
1907
  if (name === "Column") {
1768
- columns.push(processColumn(child, counters));
1908
+ columns.push(
1909
+ processColumn(child, counters, { ...columnLayout, columnIndex })
1910
+ );
1911
+ columnIndex += 1;
1769
1912
  } else {
1770
1913
  console.warn(
1771
1914
  `[Unlayer] renderToJson: <${name}> is not a valid Row child. Only <Column> is allowed.`
@@ -1796,7 +1939,7 @@ function processBody(element, counters) {
1796
1939
  for (const child of children) {
1797
1940
  const name = getDisplayName2(child);
1798
1941
  if (name === "Row") {
1799
- rows.push(processRow(child, counters));
1942
+ rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
1800
1943
  } else {
1801
1944
  console.warn(
1802
1945
  `[Unlayer] renderToJson: <${name}> is not a valid Body child. Only <Row> is allowed.`