@unlayer/react-elements 0.1.12 → 0.1.14

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.d.cts CHANGED
@@ -308,11 +308,16 @@ interface BaseItemComponentProps {
308
308
  className?: string;
309
309
  style?: React__default.CSSProperties;
310
310
  mode?: RenderMode;
311
+ /** Padding of the content wrapper around this item — a number (→ px) or a CSS
312
+ * string ("10px", "16px 24px"). Applied by the containing Column. */
313
+ containerPadding?: SizeInput;
311
314
  index?: number;
312
315
  colIndex?: number;
313
316
  cells?: any[];
314
317
  bodyValues?: any;
315
318
  rowValues?: any;
319
+ /** @internal - this column's values, threaded by Column for width-aware exporters */
320
+ columnValues?: any;
316
321
  /** @internal - Unlayer config threaded from UnlayerProvider */
317
322
  _config?: UnlayerConfig;
318
323
  }
package/dist/index.d.ts CHANGED
@@ -308,11 +308,16 @@ interface BaseItemComponentProps {
308
308
  className?: string;
309
309
  style?: React__default.CSSProperties;
310
310
  mode?: RenderMode;
311
+ /** Padding of the content wrapper around this item — a number (→ px) or a CSS
312
+ * string ("10px", "16px 24px"). Applied by the containing Column. */
313
+ containerPadding?: SizeInput;
311
314
  index?: number;
312
315
  colIndex?: number;
313
316
  cells?: any[];
314
317
  bodyValues?: any;
315
318
  rowValues?: any;
319
+ /** @internal - this column's values, threaded by Column for width-aware exporters */
320
+ columnValues?: any;
316
321
  /** @internal - Unlayer config threaded from UnlayerProvider */
317
322
  _config?: UnlayerConfig;
318
323
  }
package/dist/index.js CHANGED
@@ -5,6 +5,81 @@ import ReactDOMServer, { renderToStaticMarkup } from 'react-dom/server';
5
5
 
6
6
  // src/components/Button.tsx
7
7
 
8
+ // src/utils/image-sizing.ts
9
+ function toPx(value) {
10
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
11
+ if (typeof value !== "string") return void 0;
12
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(value.trim());
13
+ return m ? parseFloat(m[1]) : void 0;
14
+ }
15
+ function edges(value) {
16
+ if (value == null) return { left: 0, right: 0 };
17
+ if (typeof value === "number") return { left: value, right: value };
18
+ const parts = String(value).trim().split(/\s+/).map((p) => parseFloat(p) || 0);
19
+ if (parts.length === 1) return { left: parts[0], right: parts[0] };
20
+ if (parts.length === 2 || parts.length === 3)
21
+ return { left: parts[1], right: parts[1] };
22
+ return { left: parts[3] || 0, right: parts[1] || 0 };
23
+ }
24
+ function borderEdges(border) {
25
+ if (!border || typeof border !== "object") return { left: 0, right: 0 };
26
+ const b = border;
27
+ const width = (v) => parseFloat(`${v ?? ""}`) || 0;
28
+ return {
29
+ left: width(b.borderLeftWidth),
30
+ right: width(b.borderRightWidth)
31
+ };
32
+ }
33
+ function fixedContentWidth(contentWidth) {
34
+ if (typeof contentWidth === "number")
35
+ return Number.isFinite(contentWidth) ? contentWidth : void 0;
36
+ if (typeof contentWidth === "string") {
37
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(contentWidth.trim());
38
+ if (m) return parseFloat(m[1]);
39
+ }
40
+ return void 0;
41
+ }
42
+ var FALLBACK_BODY_CONTENT_WIDTH = 500;
43
+ var DEFAULT_CONTAINER_PADDING = "10px";
44
+ function bodyContentWidthPx(contentWidth, fallback = FALLBACK_BODY_CONTENT_WIDTH) {
45
+ return fixedContentWidth(contentWidth) ?? fallback;
46
+ }
47
+ function contentSlotWidth(ctx) {
48
+ const { bodyValues = {}, rowValues = {}, columnValues = {} } = ctx;
49
+ const rowCells = ctx.rowCells && ctx.rowCells.length ? ctx.rowCells : [1];
50
+ const columnIndex = ctx.columnIndex ?? 0;
51
+ const bodyWidth = bodyContentWidthPx(bodyValues.contentWidth);
52
+ const bp = edges(bodyValues.padding);
53
+ const bb = borderEdges(bodyValues.border);
54
+ const bodyAvail = bodyWidth - bp.left - bp.right - bb.left - bb.right;
55
+ const rp = edges(rowValues.padding);
56
+ const rb = borderEdges(rowValues.border);
57
+ const rowAvail = bodyAvail - rp.left - rp.right - rb.left - rb.right;
58
+ const rowSpan = rowCells.reduce((a, b) => a + b, 0) || 1;
59
+ const colSpan = rowCells[columnIndex] || 1;
60
+ const colWidth = colSpan / rowSpan * rowAvail;
61
+ const cp = edges(columnValues.padding);
62
+ const cb = borderEdges(columnValues.border);
63
+ const colAvail = colWidth - cp.left - cp.right - cb.left - cb.right;
64
+ const ip = edges(ctx.containerPadding ?? DEFAULT_CONTAINER_PADDING);
65
+ return colAvail - ip.left - ip.right;
66
+ }
67
+ var PERCENT = /^\d+(?:\.\d+)?%$/;
68
+ function round2(n) {
69
+ return Math.round(n * 100) / 100;
70
+ }
71
+ function pinImageSrc(src, availableWidth) {
72
+ if (!src || typeof src !== "object") return src;
73
+ if (src.autoWidth !== false) return src;
74
+ const maxWidth = src.maxWidth;
75
+ if (typeof maxWidth === "string" && PERCENT.test(maxWidth.trim())) return src;
76
+ const pinPx = toPx(maxWidth);
77
+ if (pinPx == null) return src;
78
+ const avail = availableWidth && availableWidth > 0 ? availableWidth : void 0;
79
+ const pct = avail ? pinPx >= avail ? 100 : round2(pinPx / avail * 100) : 100;
80
+ return { ...src, autoWidth: false, maxWidth: `${pct}%` };
81
+ }
82
+
8
83
  // ../shared/dist/index.js
9
84
  var DEFAULT_CONFIG = {
10
85
  cdnBaseUrl: "https://cdn.tools.unlayer.com",
@@ -466,6 +541,12 @@ var ErrorFallback = ({
466
541
  ]
467
542
  }
468
543
  ) });
544
+ function nextHtmlId(config, prefix) {
545
+ if (!config) return `${prefix}_1`;
546
+ const ids = config.__ids ?? (config.__ids = {});
547
+ ids[prefix] = (ids[prefix] || 0) + 1;
548
+ return `${prefix}_${ids[prefix]}`;
549
+ }
469
550
  function ensureMeta(values, type, index = 0) {
470
551
  return {
471
552
  ...values,
@@ -477,7 +558,7 @@ function ensureMeta(values, type, index = 0) {
477
558
  };
478
559
  }
479
560
  function renderComponent(config) {
480
- const { type, values, className, style, args = [], innerHTML, _config, exporter } = config;
561
+ const { type, values, className, style, args = [], innerHTML, _config, exporter, metaContext } = config;
481
562
  try {
482
563
  const cfg = _config ?? DEFAULT_CONFIG;
483
564
  const exporterConfig = {
@@ -492,7 +573,8 @@ function renderComponent(config) {
492
573
  } else {
493
574
  const meta = {
494
575
  exporterConfig,
495
- mergeTagState: cfg.mergeTagState
576
+ mergeTagState: cfg.mergeTagState,
577
+ ...metaContext ?? {}
496
578
  };
497
579
  html = exporter(values, ...args, void 0, meta);
498
580
  }
@@ -531,6 +613,7 @@ function createItemComponent(config) {
531
613
  cells = [],
532
614
  bodyValues = {},
533
615
  rowValues = {},
616
+ columnValues = {},
534
617
  _config,
535
618
  // Children
536
619
  children,
@@ -549,13 +632,25 @@ function createItemComponent(config) {
549
632
  index
550
633
  );
551
634
  const safeBodyValues = {
552
- contentWidth: 600,
635
+ contentWidth: "500px",
553
636
  ...bodyValues
554
637
  };
555
638
  const valuesForExporter = normalizeValuesForExporter(
556
639
  valuesWithMeta,
557
640
  config.name
558
641
  );
642
+ const exportSrc = valuesForExporter.src;
643
+ if (exportSrc && typeof exportSrc === "object" && exportSrc.autoWidth === false) {
644
+ const availableWidth = contentSlotWidth({
645
+ bodyValues: safeBodyValues,
646
+ rowValues,
647
+ rowCells: cells,
648
+ columnIndex: colIndex,
649
+ columnValues,
650
+ containerPadding: props.containerPadding ?? props.values?.containerPadding
651
+ });
652
+ valuesForExporter.src = pinImageSrc(exportSrc, availableWidth);
653
+ }
559
654
  const exporter = config.exporters[mode] || config.exporters.web;
560
655
  return renderComponent({
561
656
  type: config.name,
@@ -563,6 +658,15 @@ function createItemComponent(config) {
563
658
  className,
564
659
  style,
565
660
  args: [index, colIndex, cells, safeBodyValues, rowValues],
661
+ // The 8th arg the exporters actually read: column/body context for
662
+ // width-aware rendering (Image's available-width calc), mirroring the editor.
663
+ metaContext: {
664
+ columnIndex: colIndex,
665
+ columnValues,
666
+ rowCells: cells,
667
+ rowValues,
668
+ bodyValues: safeBodyValues
669
+ },
566
670
  _config,
567
671
  exporter
568
672
  });
@@ -672,7 +776,7 @@ var Image = createItemComponent({
672
776
  name: "Image",
673
777
  defaultValues: DEFAULT_VALUES5,
674
778
  propMapper: (props) => {
675
- const { alt, src, ...rest } = props;
779
+ const { alt, src, width: widthProp, maxWidth: maxWidthProp, ...rest } = props;
676
780
  const restValues = rest.values;
677
781
  const normalizedRest = restValues && typeof restValues.src === "string" ? { ...rest, values: { ...restValues, src: { url: restValues.src } } } : rest;
678
782
  const base = mapSemanticProps(
@@ -692,21 +796,39 @@ var Image = createItemComponent({
692
796
  const start = isStringUrl ? { autoWidth: true, maxWidth: "100%" } : { ...DEFAULT_VALUES5.src };
693
797
  const merged = { ...start, ...userSrc };
694
798
  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]);
799
+ const asPercent = (v) => typeof v === "string" && pctRe.test(v.trim()) ? v.trim() : void 0;
800
+ const asPx = (v) => {
801
+ if (typeof v === "number" && Number.isFinite(v)) return v;
802
+ if (typeof v === "string") {
803
+ const t = v.trim();
804
+ if (pctRe.test(t)) return void 0;
805
+ const m = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(t);
806
+ if (m) return parseFloat(m[1]);
807
+ }
808
+ return void 0;
809
+ };
810
+ let displayPct;
811
+ let displayPx;
812
+ for (const candidate of [widthProp, maxWidthProp, userSrc.maxWidth]) {
813
+ if (candidate === void 0) continue;
814
+ const pct = asPercent(candidate);
815
+ if (pct) {
816
+ displayPct = pct;
817
+ break;
818
+ }
819
+ const px = asPx(candidate);
820
+ if (px != null) {
821
+ displayPx = px;
822
+ break;
703
823
  }
704
824
  }
705
- const displayPct = typeof merged.maxWidth === "string" && pctRe.test(merged.maxWidth.trim()) ? merged.maxWidth.trim() : void 0;
706
825
  if (userSrc.autoWidth === void 0) {
707
826
  if (displayPct && displayPct !== "100%") {
708
827
  merged.autoWidth = false;
709
828
  merged.maxWidth = displayPct;
829
+ } else if (displayPx != null) {
830
+ merged.autoWidth = false;
831
+ merged.maxWidth = displayPx;
710
832
  } else {
711
833
  merged.autoWidth = true;
712
834
  merged.maxWidth = "100%";
@@ -1015,13 +1137,7 @@ ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width:
1015
1137
  return baseCSS + "\n" + columnCSS + "\n" + responsiveCSS;
1016
1138
  }
1017
1139
  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;
1140
+ return bodyContentWidthPx(bodyValues?.contentWidth, fallback);
1025
1141
  }
1026
1142
  function renderRowToHtml(innerHTML, values, bodyValues, mode, cells, collection = "rows") {
1027
1143
  const rowExporter = RowExporters[mode] || RowExporters.web;
@@ -1098,7 +1214,7 @@ var Row = (props) => {
1098
1214
  ...values,
1099
1215
  cells,
1100
1216
  _meta: {
1101
- htmlID: `u_row_${index + 1}`,
1217
+ htmlID: nextHtmlId(_config, "u_row"),
1102
1218
  htmlClassNames: "u_row",
1103
1219
  ...values._meta || {}
1104
1220
  }
@@ -1128,7 +1244,7 @@ var Row = (props) => {
1128
1244
  };
1129
1245
  Row.displayName = "Row";
1130
1246
  var Row_default = Row;
1131
- var DEFAULT_CONTAINER_PADDING = "10px";
1247
+ var DEFAULT_CONTAINER_PADDING2 = "10px";
1132
1248
  var DEFAULT_VALUES12 = COLUMN_DEFAULTS;
1133
1249
  function renderColumnToHtml(innerHTML, values, index, cells, bodyValues, rowValues, mode) {
1134
1250
  const columnExporter = ColumnExporters[mode] || ColumnExporters.web;
@@ -1160,7 +1276,7 @@ var Column = (props) => {
1160
1276
  const valuesWithMeta = {
1161
1277
  ...values,
1162
1278
  _meta: {
1163
- htmlID: `u_column_${index + 1}`,
1279
+ htmlID: nextHtmlId(_config, "u_column"),
1164
1280
  htmlClassNames: "u_column",
1165
1281
  ...values._meta || {}
1166
1282
  }
@@ -1176,17 +1292,26 @@ var Column = (props) => {
1176
1292
  if (typeof child.type === "function") {
1177
1293
  const ComponentType = child.type;
1178
1294
  const renderFn = ComponentType[UNLAYER_RENDER_KEY] || ComponentType;
1179
- const rendered = renderFn({ ...child.props, _config });
1295
+ const rendered = renderFn({
1296
+ ...child.props,
1297
+ _config,
1298
+ colIndex: index,
1299
+ cells,
1300
+ bodyValues,
1301
+ rowValues,
1302
+ columnValues: valuesWithMeta
1303
+ });
1180
1304
  if (rendered && typeof rendered === "object" && rendered.props && rendered.props.dangerouslySetInnerHTML) {
1181
1305
  const componentHTML = rendered.props.dangerouslySetInnerHTML.__html;
1182
1306
  const componentType = child.type;
1183
1307
  const componentName = (componentType?.displayName || componentType?.name || "component").toLowerCase();
1184
1308
  const childProps = child.props;
1185
- const containerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING;
1309
+ const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING2;
1310
+ const containerPadding = typeof rawContainerPadding === "number" ? `${rawContainerPadding}px` : rawContainerPadding;
1186
1311
  const contentValues = {
1187
1312
  containerPadding,
1188
1313
  _meta: {
1189
- htmlID: `u_content_${componentName}_${childIndex + 1}`,
1314
+ htmlID: nextHtmlId(_config, `u_content_${componentName}`),
1190
1315
  htmlClassNames: `u_content_${componentName}`
1191
1316
  }
1192
1317
  };
@@ -1271,6 +1396,7 @@ var Body = (props) => {
1271
1396
  const resolvedConfig = { ...DEFAULT_CONFIG, ...configProp };
1272
1397
  const mode = modeProp ?? resolvedConfig.mode ?? "web";
1273
1398
  const _config = { ...resolvedConfig, mode };
1399
+ _config.__ids = {};
1274
1400
  const values = mergeValues(
1275
1401
  DEFAULT_VALUES13,
1276
1402
  mapSemanticProps(semanticProps, DEFAULT_VALUES13, "Body")
@@ -1278,7 +1404,7 @@ var Body = (props) => {
1278
1404
  const valuesWithMeta = {
1279
1405
  ...values,
1280
1406
  _meta: {
1281
- htmlID: `u_body_${index + 1}`,
1407
+ htmlID: nextHtmlId(_config, "u_body"),
1282
1408
  htmlClassNames: "u_body",
1283
1409
  ...values._meta || {}
1284
1410
  }
@@ -1635,7 +1761,7 @@ function extractTextFromTextJson(textJson) {
1635
1761
  return "";
1636
1762
  }
1637
1763
  }
1638
- function processItem(element, counters) {
1764
+ function processItem(element, counters, layout = {}) {
1639
1765
  const componentType = element.type;
1640
1766
  const config = componentType[UNLAYER_CONFIG_KEY];
1641
1767
  if (!config) {
@@ -1669,9 +1795,17 @@ function processItem(element, counters) {
1669
1795
  deletable: true,
1670
1796
  hideable: true
1671
1797
  };
1798
+ const itemSrc = values.src;
1799
+ if (itemSrc && typeof itemSrc === "object" && itemSrc.autoWidth === false) {
1800
+ const availableWidth = contentSlotWidth({
1801
+ ...layout,
1802
+ containerPadding: values.containerPadding
1803
+ });
1804
+ values.src = pinImageSrc(itemSrc, availableWidth);
1805
+ }
1672
1806
  return { type: contentType, values };
1673
1807
  }
1674
- function processColumn(element, counters) {
1808
+ function processColumn(element, counters, layout = {}) {
1675
1809
  const count = nextCounter2(counters, "u_column");
1676
1810
  const id = makeId("u_column", count);
1677
1811
  const semanticProps = extractSemanticProps2(element.props);
@@ -1687,12 +1821,13 @@ function processColumn(element, counters) {
1687
1821
  };
1688
1822
  const contents = [];
1689
1823
  const children = collectChildren2(element.props.children);
1824
+ const itemLayout = { ...layout, columnValues: valuesWithMeta };
1690
1825
  for (const child of children) {
1691
- contents.push(processItem(child, counters));
1826
+ contents.push(processItem(child, counters, itemLayout));
1692
1827
  }
1693
1828
  return { contents, values: valuesWithMeta };
1694
1829
  }
1695
- function processRow(element, counters) {
1830
+ function processRow(element, counters, parentLayout = {}) {
1696
1831
  const count = nextCounter2(counters, "u_row");
1697
1832
  const id = makeId("u_row", count);
1698
1833
  const { layout, cells: propsCells } = element.props;
@@ -1704,7 +1839,9 @@ function processRow(element, counters) {
1704
1839
  } else {
1705
1840
  const columnCount = Math.max(
1706
1841
  1,
1707
- collectChildren2(element.props.children).length
1842
+ collectChildren2(element.props.children).filter(
1843
+ (child) => getDisplayName2(child) === "Column"
1844
+ ).length
1708
1845
  );
1709
1846
  cells = Array(columnCount).fill(1);
1710
1847
  }
@@ -1728,10 +1865,19 @@ function processRow(element, counters) {
1728
1865
  };
1729
1866
  const columns = [];
1730
1867
  const children = collectChildren2(element.props.children);
1868
+ const columnLayout = {
1869
+ bodyValues: parentLayout.bodyValues,
1870
+ rowValues: valuesWithMeta,
1871
+ rowCells: cells
1872
+ };
1873
+ let columnIndex = 0;
1731
1874
  for (const child of children) {
1732
1875
  const name = getDisplayName2(child);
1733
1876
  if (name === "Column") {
1734
- columns.push(processColumn(child, counters));
1877
+ columns.push(
1878
+ processColumn(child, counters, { ...columnLayout, columnIndex })
1879
+ );
1880
+ columnIndex += 1;
1735
1881
  } else {
1736
1882
  console.warn(
1737
1883
  `[Unlayer] renderToJson: <${name}> is not a valid Row child. Only <Column> is allowed.`
@@ -1762,7 +1908,7 @@ function processBody(element, counters) {
1762
1908
  for (const child of children) {
1763
1909
  const name = getDisplayName2(child);
1764
1910
  if (name === "Row") {
1765
- rows.push(processRow(child, counters));
1911
+ rows.push(processRow(child, counters, { bodyValues: valuesWithMeta }));
1766
1912
  } else {
1767
1913
  console.warn(
1768
1914
  `[Unlayer] renderToJson: <${name}> is not a valid Body child. Only <Row> is allowed.`