@unlayer/react-elements 0.1.16 → 0.1.18

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
@@ -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
- if (!childrenHtml) return "<p><br></p>";
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 || "h1";
215
- const dir = node.direction ? ` 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": {
@@ -572,16 +613,6 @@ function nextHtmlId(config, prefix) {
572
613
  ids[prefix] = (ids[prefix] || 0) + 1;
573
614
  return `${prefix}_${ids[prefix]}`;
574
615
  }
575
- function ensureMeta(values, type, index = 0) {
576
- return {
577
- ...values,
578
- _meta: {
579
- htmlID: `u_content_${type.toLowerCase()}_${index + 1}`,
580
- htmlClassNames: `u_content_${type.toLowerCase()}`,
581
- ...values._meta || {}
582
- }
583
- };
584
- }
585
616
  function renderComponent(config) {
586
617
  const { type, values, className, style, args = [], innerHTML, _config, exporter, metaContext } = config;
587
618
  try {
@@ -634,6 +665,7 @@ function createItemComponent(config) {
634
665
  style,
635
666
  // Internal props
636
667
  index = 0,
668
+ _metaHtmlId,
637
669
  colIndex = 0,
638
670
  cells = [],
639
671
  bodyValues = {},
@@ -651,11 +683,15 @@ function createItemComponent(config) {
651
683
  ...restProps
652
684
  });
653
685
  const finalValues = mergeValues(config.defaultValues, mappedValues);
654
- const valuesWithMeta = ensureMeta(
655
- finalValues,
656
- config.name.toLowerCase(),
657
- index
658
- );
686
+ const metaName = config.metaName ?? config.name.toLowerCase();
687
+ const valuesWithMeta = {
688
+ ...finalValues,
689
+ _meta: {
690
+ htmlID: _metaHtmlId ?? `u_content_${metaName}_${index + 1}`,
691
+ htmlClassNames: `u_content_${metaName}`,
692
+ ...finalValues._meta || {}
693
+ }
694
+ };
659
695
  const safeBodyValues = {
660
696
  contentWidth: "500px",
661
697
  ...bodyValues
@@ -704,7 +740,11 @@ function createItemComponent(config) {
704
740
  ItemComponent[UNLAYER_CONFIG_KEY] = {
705
741
  name: config.name,
706
742
  defaultValues: config.defaultValues,
707
- propMapper: config.propMapper
743
+ propMapper: config.propMapper,
744
+ contentType: config.contentType,
745
+ slug: config.slug,
746
+ metaName: config.metaName,
747
+ head: config.head
708
748
  };
709
749
  return ItemComponent;
710
750
  }
@@ -1115,7 +1155,7 @@ function getWidthPercentages(cells) {
1115
1155
  return { value, className };
1116
1156
  });
1117
1157
  }
1118
- function generateGridCSS(cells, mode, contentWidth = 600, mobileBreakpoint = 620) {
1158
+ function generateGridCSS(cells, mode, contentWidth = 600, mobileBreakpoint = 480) {
1119
1159
  const widths = getWidthPercentages(cells);
1120
1160
  if (mode === "email") {
1121
1161
  const minQuery = `@media only screen and (min-width: ${contentWidth + 20}px)`;
@@ -1152,21 +1192,14 @@ ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width:
1152
1192
  const columnCSS = widths.map(
1153
1193
  ({ value, className }) => `.u-row .u-col.u-col-${className} { flex: 0 0 ${value}%; max-width: ${value}%; }`
1154
1194
  ).join("\n");
1155
- const responsiveCSS = `
1156
- @media only screen and (max-width: ${mobileBreakpoint}px) {
1157
- .u-row { width: 100% !important; }
1158
- .u-row .u-col {
1159
- display: block !important;
1160
- width: 100% !important;
1161
- min-width: 320px !important;
1195
+ const responsiveCSS = mode === "document" ? "" : `
1196
+ @media (max-width: ${mobileBreakpoint}px) {
1197
+ .u_row .container { max-width: 100% !important; }
1198
+ .u-row:not(.no-stack) { flex-wrap: wrap; }
1199
+ .u-row:not(.no-stack) .u-col {
1200
+ flex: 0 0 100% !important;
1162
1201
  max-width: 100% !important;
1163
1202
  }
1164
- .u-row .u-col > div { margin: 0 auto; }
1165
- .no-stack .u-col {
1166
- min-width: 0 !important;
1167
- display: table-cell !important;
1168
- }
1169
- ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
1170
1203
  }`;
1171
1204
  return baseCSS + "\n" + columnCSS + "\n" + responsiveCSS;
1172
1205
  }
@@ -1333,9 +1366,12 @@ var Column = (props) => {
1333
1366
  if (typeof child.type === "function") {
1334
1367
  const ComponentType = child.type;
1335
1368
  const renderFn = ComponentType[UNLAYER_RENDER_KEY] || ComponentType;
1369
+ const componentName = ComponentType?.[UNLAYER_CONFIG_KEY]?.metaName ?? (ComponentType?.displayName || ComponentType?.name || "component").toLowerCase();
1370
+ const contentHtmlId = nextHtmlId(_config, `u_content_${componentName}`);
1336
1371
  const rendered = renderFn({
1337
1372
  ...child.props,
1338
1373
  _config,
1374
+ _metaHtmlId: contentHtmlId,
1339
1375
  colIndex: index,
1340
1376
  cells,
1341
1377
  bodyValues,
@@ -1344,15 +1380,13 @@ var Column = (props) => {
1344
1380
  });
1345
1381
  if (rendered && typeof rendered === "object" && rendered.props && rendered.props.dangerouslySetInnerHTML) {
1346
1382
  const componentHTML = rendered.props.dangerouslySetInnerHTML.__html;
1347
- const componentType = child.type;
1348
- const componentName = (componentType?.displayName || componentType?.name || "component").toLowerCase();
1349
1383
  const childProps = child.props;
1350
1384
  const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING2;
1351
1385
  const containerPadding = typeof rawContainerPadding === "number" ? `${rawContainerPadding}px` : rawContainerPadding;
1352
1386
  const contentValues = {
1353
1387
  containerPadding,
1354
1388
  _meta: {
1355
- htmlID: nextHtmlId(_config, `u_content_${componentName}`),
1389
+ htmlID: contentHtmlId,
1356
1390
  htmlClassNames: `u_content_${componentName}`
1357
1391
  }
1358
1392
  };
@@ -1523,7 +1557,7 @@ UnlayerProvider.displayName = "UnlayerProvider";
1523
1557
  function useUnlayerConfig() {
1524
1558
  return useContext(getUnlayerContext());
1525
1559
  }
1526
- function ensureMeta2(values, type, index = 0) {
1560
+ function ensureMeta(values, type, index = 0) {
1527
1561
  return {
1528
1562
  ...values,
1529
1563
  _meta: {
@@ -1604,17 +1638,17 @@ function walkItem(element, bodyValues, displayMode, headConfig, counters, styles
1604
1638
  const { children, ...restProps } = element.props;
1605
1639
  const mappedValues = propMapper({ children, ...restProps });
1606
1640
  const finalValues = mergeValues(defaultValues, mappedValues);
1607
- const contentType = name.toLowerCase();
1641
+ const contentType = config.metaName ?? name.toLowerCase();
1608
1642
  const count = nextCounter(counters, `u_content_${contentType}`);
1609
- const valuesWithMeta = ensureMeta2(finalValues, contentType, count - 1);
1610
- const head = heads[name];
1643
+ const valuesWithMeta = ensureMeta(finalValues, contentType, count - 1);
1644
+ const head = config.head ?? heads[name];
1611
1645
  callHead(head, valuesWithMeta, bodyValues, displayMode, headConfig, styles, scripts, tags);
1612
1646
  }
1613
1647
  function walkColumn(element, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags) {
1614
1648
  const semanticProps = extractSemanticProps(element.props);
1615
1649
  const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
1616
1650
  const count = nextCounter(counters, "u_column");
1617
- const columnValues = ensureMeta2(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
1651
+ const columnValues = ensureMeta(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
1618
1652
  const columnHead = heads["Column"];
1619
1653
  callHead(columnHead, columnValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1620
1654
  const children = collectChildren(element.props.children);
@@ -1626,7 +1660,7 @@ function walkRow(element, bodyValues, displayMode, headConfig, counters, styles,
1626
1660
  const semanticProps = extractSemanticProps(element.props, ["layout"]);
1627
1661
  const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
1628
1662
  const count = nextCounter(counters, "u_row");
1629
- const rowValues = ensureMeta2(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
1663
+ const rowValues = ensureMeta(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
1630
1664
  const rowHead = heads["Row"];
1631
1665
  callHead(rowHead, rowValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1632
1666
  const children = collectChildren(element.props.children);
@@ -1649,7 +1683,7 @@ function extractHeadFromTree(element, options) {
1649
1683
  const counters = {};
1650
1684
  const semanticProps = extractSemanticProps(element.props);
1651
1685
  const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
1652
- const bodyValues = ensureMeta2(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1686
+ const bodyValues = ensureMeta(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1653
1687
  const bodyHead = heads["Body"];
1654
1688
  callHead(bodyHead, bodyValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1655
1689
  const children = collectChildren(element.props.children);
@@ -1667,8 +1701,81 @@ function extractHeadFromTree(element, options) {
1667
1701
  };
1668
1702
  }
1669
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
+
1670
1767
  // src/utils/render-to-html.tsx
1671
- function renderToHtml(element, config) {
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) {
1672
1779
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
1673
1780
  try {
1674
1781
  const enriched = React.cloneElement(element, { config: mergedConfig });
@@ -1681,14 +1788,52 @@ Tip: Ensure your tree uses Body > Row > Column > Item structure.`
1681
1788
  );
1682
1789
  }
1683
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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
+ }
1684
1829
  function renderToPlainText(element, config) {
1685
- const html = renderToHtml(element, config);
1830
+ const html = renderBody(element, config);
1686
1831
  return htmlToPlainText(html);
1687
1832
  }
1688
1833
  function renderToHtmlParts(element, config) {
1689
- const body = renderToHtml(element, config);
1834
+ const body = renderBody(element, config);
1690
1835
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
1691
- const displayMode = element.props.mode ?? mergedConfig.mode ?? "web";
1836
+ const displayMode = resolveDisplayMode(element, mergedConfig);
1692
1837
  const { css, js, tags } = extractHeadFromTree(element, {
1693
1838
  displayMode,
1694
1839
  headConfig: mergedConfig.headConfig
@@ -1811,10 +1956,11 @@ function processItem(element, counters, layout = {}) {
1811
1956
  `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
1812
1957
  );
1813
1958
  }
1814
- const { name, defaultValues, propMapper } = config;
1815
- const contentType = toContentType(name);
1816
- const count = nextCounter2(counters, `u_content_${contentType}`);
1817
- const id = makeId(`u_content_${contentType}`, count);
1959
+ const { name, defaultValues, propMapper, slug } = config;
1960
+ const contentType = config.contentType ?? toContentType(name);
1961
+ const metaBase = `u_content_${config.metaName ?? contentType}`;
1962
+ const count = nextCounter2(counters, metaBase);
1963
+ const id = makeId(metaBase, count);
1818
1964
  const { children, ...restProps } = element.props;
1819
1965
  const mappedValues = propMapper({ children, ...restProps });
1820
1966
  const finalValues = mergeValues(defaultValues, mappedValues);
@@ -1827,7 +1973,7 @@ function processItem(element, counters, layout = {}) {
1827
1973
  ...finalValues,
1828
1974
  _meta: {
1829
1975
  htmlID: id,
1830
- htmlClassNames: `u_content_${contentType}`,
1976
+ htmlClassNames: metaBase,
1831
1977
  ...finalValues._meta || {}
1832
1978
  },
1833
1979
  selectable: true,
@@ -1844,7 +1990,7 @@ function processItem(element, counters, layout = {}) {
1844
1990
  });
1845
1991
  values.src = pinImageSrc(itemSrc, availableWidth);
1846
1992
  }
1847
- return { type: contentType, values };
1993
+ return slug ? { type: contentType, slug, values } : { type: contentType, values };
1848
1994
  }
1849
1995
  function processColumn(element, counters, layout = {}) {
1850
1996
  const count = nextCounter2(counters, "u_column");
@@ -1988,6 +2134,89 @@ function renderToJson(element) {
1988
2134
  };
1989
2135
  }
1990
2136
 
1991
- export { Body_default as Body, Button_default as Button, Column_default as Column, ColumnLayouts, DEFAULT_CONFIG, Divider_default as Divider, Document, Email, Heading_default as Heading, Html_default as Html, Image_default as Image, Menu_default as Menu, Page, Paragraph_default as Paragraph, Row_default as Row, Social_default as Social, Table_default as Table, UnlayerProvider, Video_default as Video, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
2137
+ // src/utils/register-tool.tsx
2138
+ function initialValuesFromOptions(options) {
2139
+ const result = {};
2140
+ for (const group of Object.values(options ?? {})) {
2141
+ for (const [property, option] of Object.entries(group?.options ?? {})) {
2142
+ if (!option || option.enabled === false) continue;
2143
+ if (typeof option.defaultValue !== "undefined") {
2144
+ result[property] = option.defaultValue;
2145
+ }
2146
+ }
2147
+ }
2148
+ return result;
2149
+ }
2150
+ function withSanitizer(exporter) {
2151
+ return (values, ...args) => {
2152
+ const html = String(exporter(values, ...args) ?? "");
2153
+ const meta = args[args.length - 1];
2154
+ const toSafeHtml = meta?.exporterConfig?.toSafeHtml;
2155
+ return typeof toSafeHtml === "function" ? toSafeHtml(html) : html;
2156
+ };
2157
+ }
2158
+ function withLinkNormalization(exporter, linkKeys) {
2159
+ if (linkKeys.length === 0) return exporter;
2160
+ return (values, ...args) => {
2161
+ const out = { ...values };
2162
+ for (const key of linkKeys) {
2163
+ const normalized = normalizeLinkValue(out[key]);
2164
+ if (normalized !== void 0) out[key] = normalized;
2165
+ }
2166
+ return exporter(out, ...args);
2167
+ };
2168
+ }
2169
+ function linkWidgetKeys(options) {
2170
+ const keys = [];
2171
+ for (const group of Object.values(options ?? {})) {
2172
+ for (const [key, option] of Object.entries(group?.options ?? {})) {
2173
+ if (option && option.widget === "link") keys.push(key);
2174
+ }
2175
+ }
2176
+ return keys;
2177
+ }
2178
+ function registerTool(tool) {
2179
+ if (!tool?.name || typeof tool.name !== "string") {
2180
+ throw new Error("[Unlayer] registerTool: `name` is required");
2181
+ }
2182
+ const exporters = tool.renderer?.exporters;
2183
+ if (!exporters || !exporters.web && !exporters.email) {
2184
+ throw new Error(
2185
+ `[Unlayer] registerTool("${tool.name}"): \`renderer.exporters\` needs at least a \`web\` or \`email\` exporter`
2186
+ );
2187
+ }
2188
+ const linkKeys = linkWidgetKeys(tool.options);
2189
+ const sanitized = {};
2190
+ for (const mode of ["web", "email", "document"]) {
2191
+ const exporter = exporters[mode];
2192
+ if (exporter) {
2193
+ sanitized[mode] = withSanitizer(withLinkNormalization(exporter, linkKeys));
2194
+ }
2195
+ }
2196
+ if (!sanitized.web && sanitized.email) sanitized.web = sanitized.email;
2197
+ return createItemComponent({
2198
+ name: tool.name,
2199
+ displayName: tool.displayName ?? tool.name,
2200
+ // Option-group widget defaults seed the values; tool-level `values`
2201
+ // win — the editor's precedence.
2202
+ defaultValues: {
2203
+ ...initialValuesFromOptions(tool.options),
2204
+ ...tool.values ?? {}
2205
+ },
2206
+ // Flat props are the tool's values; `values` is the escape hatch.
2207
+ propMapper: ({ children: _children, values, ...rest }) => ({
2208
+ ...values ?? {},
2209
+ ...rest
2210
+ }),
2211
+ exporters: sanitized,
2212
+ contentType: "custom",
2213
+ slug: tool.name,
2214
+ metaName: `custom_${tool.name}`,
2215
+ head: tool.renderer.head
2216
+ });
2217
+ }
2218
+ var registerElementsTool = registerTool;
2219
+
2220
+ export { Body_default as Body, Button_default as Button, Column_default as Column, ColumnLayouts, DEFAULT_CONFIG, Divider_default as Divider, Document, Email, Heading_default as Heading, Html_default as Html, Image_default as Image, Menu_default as Menu, Page, Paragraph_default as Paragraph, Row_default as Row, Social_default as Social, Table_default as Table, UnlayerProvider, Video_default as Video, htmlToTextJson, registerElementsTool, registerTool, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
1992
2221
  //# sourceMappingURL=index.js.map
1993
2222
  //# sourceMappingURL=index.js.map