@unlayer/react-elements 0.1.17 → 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.d.cts CHANGED
@@ -118,6 +118,8 @@ interface MenuItem {
118
118
  type RenderMode = "web" | "email" | "document";
119
119
  interface DesignContent {
120
120
  type: string;
121
+ /** Custom tool name — present when `type` is "custom" */
122
+ slug?: string;
121
123
  values: Record<string, any>;
122
124
  }
123
125
  interface DesignColumn {
@@ -312,6 +314,10 @@ interface BaseItemComponentProps {
312
314
  * string ("10px", "16px 24px"). Applied by the containing Column. */
313
315
  containerPadding?: SizeInput;
314
316
  index?: number;
317
+ /** @internal — content id allocated by the containing Column; keeps the
318
+ * value-level _meta.htmlID unique per instance and aligned with the
319
+ * wrapper id and head/json numbering. */
320
+ _metaHtmlId?: string;
315
321
  colIndex?: number;
316
322
  cells?: any[];
317
323
  bodyValues?: any;
@@ -1001,4 +1007,104 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
1001
1007
  declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
1002
1008
  declare function renderToJson(element: React__default.ReactElement): DesignJSON;
1003
1009
 
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 };
1010
+ /**
1011
+ * registerTool — render the editor's custom tools from code.
1012
+ *
1013
+ * Accepts the same tool definition object embedders already write for
1014
+ * `unlayer.registerTool` (see docs.unlayer.com › Custom Tools) and returns a
1015
+ * React component. One definition powers both runtimes:
1016
+ *
1017
+ * - The Builder uses `label`/`icon`/`options`/`renderer.Viewer`/`validator`
1018
+ * for its panel, canvas, and audit — Elements ignores those.
1019
+ * - Elements calls `renderer.exporters[mode](values)` to produce HTML and
1020
+ * collects `renderer.head` css/js like any built-in component, so code
1021
+ * output matches editor exports by construction — it is the same function.
1022
+ * - `renderToJson` emits `{ type: "custom", slug: <name>, values }`, the
1023
+ * shape the editor stores, so designs round-trip into the Builder where
1024
+ * the same tool is registered.
1025
+ *
1026
+ * Sanitization: like editor exports, the tool's HTML output is passed
1027
+ * through `toSafeHtml` when one is configured. The default config has NO
1028
+ * sanitizer — with untrusted values, configure `toSafeHtml` (same caveat
1029
+ * as the `<Html>` component).
1030
+ */
1031
+
1032
+ /** Exporter: `(values) => html`. Extra args mirror built-in exporters and can be ignored. */
1033
+ type ToolExporter = (values: any, ...args: any[]) => string;
1034
+ /** One property in an options group — Elements reads only the default. */
1035
+ interface ToolOptionConfig {
1036
+ defaultValue?: unknown;
1037
+ /** Disabled options contribute no default */
1038
+ enabled?: boolean;
1039
+ [editorOnly: string]: unknown;
1040
+ }
1041
+ /** A property group: `options.<group>.options.<property>` */
1042
+ interface ToolOptionGroup {
1043
+ options?: Record<string, ToolOptionConfig | undefined>;
1044
+ [editorOnly: string]: unknown;
1045
+ }
1046
+ /**
1047
+ * The subset of the `unlayer.registerTool` config Elements reads. Extra
1048
+ * editor-only fields (label, icon, Viewer, validator, transformer, ...)
1049
+ * are accepted and ignored, so the same object can be passed to both APIs.
1050
+ */
1051
+ interface ElementsToolConfig {
1052
+ /** Unique tool name — becomes the design JSON `slug` */
1053
+ name: string;
1054
+ /** Initial values for the tool's properties (win over option defaults) */
1055
+ values?: Record<string, any>;
1056
+ /**
1057
+ * Property groups. Real-world tools usually define their defaults here,
1058
+ * as each property's `defaultValue` — those seed the initial values,
1059
+ * with tool-level `values` taking precedence (the editor's order).
1060
+ */
1061
+ options?: Record<string, ToolOptionGroup | undefined>;
1062
+ renderer: {
1063
+ /** Per-mode HTML exporters. `document` falls back to `web`. */
1064
+ exporters: Partial<Record<RenderMode, ToolExporter>>;
1065
+ /** Optional head contributions collected into the document `<head>` */
1066
+ head?: {
1067
+ css?: (values: any, ...args: any[]) => string | undefined;
1068
+ js?: (values: any, ...args: any[]) => string | undefined;
1069
+ tags?: (values: any, ...args: any[]) => string[] | undefined;
1070
+ };
1071
+ /** Editor-only canvas renderer — ignored by Elements */
1072
+ Viewer?: unknown;
1073
+ };
1074
+ /** React DevTools name (defaults to the tool name) */
1075
+ displayName?: string;
1076
+ /** Editor-only fields (label, icon, options, validator, ...) — ignored */
1077
+ [editorOnly: string]: unknown;
1078
+ }
1079
+ /** Props: the tool's values as flat props, plus a `values` escape hatch. */
1080
+ type ElementsToolProps = BaseItemComponentProps & Record<string, any> & {
1081
+ values?: Record<string, any>;
1082
+ };
1083
+ /**
1084
+ * Create a React component from a custom tool definition — a custom tool
1085
+ * is the custom component you create: the same term (and the same config)
1086
+ * as the editor's `unlayer.registerTool`, so one definition serves both.
1087
+ *
1088
+ * @example
1089
+ * ```tsx
1090
+ * const Countdown = registerTool({
1091
+ * name: "countdown",
1092
+ * values: { endTime: "", digitColor: "#404040" },
1093
+ * renderer: {
1094
+ * exporters: {
1095
+ * web: (values) => `<div style="color:${values.digitColor}">…</div>`,
1096
+ * email: (values) => `<table role="presentation">…</table>`,
1097
+ * },
1098
+ * },
1099
+ * });
1100
+ *
1101
+ * <Email><Row><Column>
1102
+ * <Countdown endTime="2026-08-01T00:00:00Z" digitColor="#e11d48" />
1103
+ * </Column></Row></Email>
1104
+ * ```
1105
+ */
1106
+ declare function registerTool(tool: ElementsToolConfig): React__default.FC<ElementsToolProps>;
1107
+ /** Alias of {@link registerTool}. */
1108
+ declare const registerElementsTool: typeof registerTool;
1109
+
1110
+ 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, type ElementsToolConfig, type ElementsToolProps, 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 ToolExporter, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, registerElementsTool, registerTool, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
package/dist/index.d.ts CHANGED
@@ -118,6 +118,8 @@ interface MenuItem {
118
118
  type RenderMode = "web" | "email" | "document";
119
119
  interface DesignContent {
120
120
  type: string;
121
+ /** Custom tool name — present when `type` is "custom" */
122
+ slug?: string;
121
123
  values: Record<string, any>;
122
124
  }
123
125
  interface DesignColumn {
@@ -312,6 +314,10 @@ interface BaseItemComponentProps {
312
314
  * string ("10px", "16px 24px"). Applied by the containing Column. */
313
315
  containerPadding?: SizeInput;
314
316
  index?: number;
317
+ /** @internal — content id allocated by the containing Column; keeps the
318
+ * value-level _meta.htmlID unique per instance and aligned with the
319
+ * wrapper id and head/json numbering. */
320
+ _metaHtmlId?: string;
315
321
  colIndex?: number;
316
322
  cells?: any[];
317
323
  bodyValues?: any;
@@ -1001,4 +1007,104 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
1001
1007
  declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
1002
1008
  declare function renderToJson(element: React__default.ReactElement): DesignJSON;
1003
1009
 
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 };
1010
+ /**
1011
+ * registerTool — render the editor's custom tools from code.
1012
+ *
1013
+ * Accepts the same tool definition object embedders already write for
1014
+ * `unlayer.registerTool` (see docs.unlayer.com › Custom Tools) and returns a
1015
+ * React component. One definition powers both runtimes:
1016
+ *
1017
+ * - The Builder uses `label`/`icon`/`options`/`renderer.Viewer`/`validator`
1018
+ * for its panel, canvas, and audit — Elements ignores those.
1019
+ * - Elements calls `renderer.exporters[mode](values)` to produce HTML and
1020
+ * collects `renderer.head` css/js like any built-in component, so code
1021
+ * output matches editor exports by construction — it is the same function.
1022
+ * - `renderToJson` emits `{ type: "custom", slug: <name>, values }`, the
1023
+ * shape the editor stores, so designs round-trip into the Builder where
1024
+ * the same tool is registered.
1025
+ *
1026
+ * Sanitization: like editor exports, the tool's HTML output is passed
1027
+ * through `toSafeHtml` when one is configured. The default config has NO
1028
+ * sanitizer — with untrusted values, configure `toSafeHtml` (same caveat
1029
+ * as the `<Html>` component).
1030
+ */
1031
+
1032
+ /** Exporter: `(values) => html`. Extra args mirror built-in exporters and can be ignored. */
1033
+ type ToolExporter = (values: any, ...args: any[]) => string;
1034
+ /** One property in an options group — Elements reads only the default. */
1035
+ interface ToolOptionConfig {
1036
+ defaultValue?: unknown;
1037
+ /** Disabled options contribute no default */
1038
+ enabled?: boolean;
1039
+ [editorOnly: string]: unknown;
1040
+ }
1041
+ /** A property group: `options.<group>.options.<property>` */
1042
+ interface ToolOptionGroup {
1043
+ options?: Record<string, ToolOptionConfig | undefined>;
1044
+ [editorOnly: string]: unknown;
1045
+ }
1046
+ /**
1047
+ * The subset of the `unlayer.registerTool` config Elements reads. Extra
1048
+ * editor-only fields (label, icon, Viewer, validator, transformer, ...)
1049
+ * are accepted and ignored, so the same object can be passed to both APIs.
1050
+ */
1051
+ interface ElementsToolConfig {
1052
+ /** Unique tool name — becomes the design JSON `slug` */
1053
+ name: string;
1054
+ /** Initial values for the tool's properties (win over option defaults) */
1055
+ values?: Record<string, any>;
1056
+ /**
1057
+ * Property groups. Real-world tools usually define their defaults here,
1058
+ * as each property's `defaultValue` — those seed the initial values,
1059
+ * with tool-level `values` taking precedence (the editor's order).
1060
+ */
1061
+ options?: Record<string, ToolOptionGroup | undefined>;
1062
+ renderer: {
1063
+ /** Per-mode HTML exporters. `document` falls back to `web`. */
1064
+ exporters: Partial<Record<RenderMode, ToolExporter>>;
1065
+ /** Optional head contributions collected into the document `<head>` */
1066
+ head?: {
1067
+ css?: (values: any, ...args: any[]) => string | undefined;
1068
+ js?: (values: any, ...args: any[]) => string | undefined;
1069
+ tags?: (values: any, ...args: any[]) => string[] | undefined;
1070
+ };
1071
+ /** Editor-only canvas renderer — ignored by Elements */
1072
+ Viewer?: unknown;
1073
+ };
1074
+ /** React DevTools name (defaults to the tool name) */
1075
+ displayName?: string;
1076
+ /** Editor-only fields (label, icon, options, validator, ...) — ignored */
1077
+ [editorOnly: string]: unknown;
1078
+ }
1079
+ /** Props: the tool's values as flat props, plus a `values` escape hatch. */
1080
+ type ElementsToolProps = BaseItemComponentProps & Record<string, any> & {
1081
+ values?: Record<string, any>;
1082
+ };
1083
+ /**
1084
+ * Create a React component from a custom tool definition — a custom tool
1085
+ * is the custom component you create: the same term (and the same config)
1086
+ * as the editor's `unlayer.registerTool`, so one definition serves both.
1087
+ *
1088
+ * @example
1089
+ * ```tsx
1090
+ * const Countdown = registerTool({
1091
+ * name: "countdown",
1092
+ * values: { endTime: "", digitColor: "#404040" },
1093
+ * renderer: {
1094
+ * exporters: {
1095
+ * web: (values) => `<div style="color:${values.digitColor}">…</div>`,
1096
+ * email: (values) => `<table role="presentation">…</table>`,
1097
+ * },
1098
+ * },
1099
+ * });
1100
+ *
1101
+ * <Email><Row><Column>
1102
+ * <Countdown endTime="2026-08-01T00:00:00Z" digitColor="#e11d48" />
1103
+ * </Column></Row></Email>
1104
+ * ```
1105
+ */
1106
+ declare function registerTool(tool: ElementsToolConfig): React__default.FC<ElementsToolProps>;
1107
+ /** Alias of {@link registerTool}. */
1108
+ declare const registerElementsTool: typeof registerTool;
1109
+
1110
+ 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, type ElementsToolConfig, type ElementsToolProps, 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 ToolExporter, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, registerElementsTool, registerTool, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
package/dist/index.js CHANGED
@@ -613,16 +613,6 @@ function nextHtmlId(config, prefix) {
613
613
  ids[prefix] = (ids[prefix] || 0) + 1;
614
614
  return `${prefix}_${ids[prefix]}`;
615
615
  }
616
- function ensureMeta(values, type, index = 0) {
617
- return {
618
- ...values,
619
- _meta: {
620
- htmlID: `u_content_${type.toLowerCase()}_${index + 1}`,
621
- htmlClassNames: `u_content_${type.toLowerCase()}`,
622
- ...values._meta || {}
623
- }
624
- };
625
- }
626
616
  function renderComponent(config) {
627
617
  const { type, values, className, style, args = [], innerHTML, _config, exporter, metaContext } = config;
628
618
  try {
@@ -675,6 +665,7 @@ function createItemComponent(config) {
675
665
  style,
676
666
  // Internal props
677
667
  index = 0,
668
+ _metaHtmlId,
678
669
  colIndex = 0,
679
670
  cells = [],
680
671
  bodyValues = {},
@@ -692,11 +683,15 @@ function createItemComponent(config) {
692
683
  ...restProps
693
684
  });
694
685
  const finalValues = mergeValues(config.defaultValues, mappedValues);
695
- const valuesWithMeta = ensureMeta(
696
- finalValues,
697
- config.name.toLowerCase(),
698
- index
699
- );
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
+ };
700
695
  const safeBodyValues = {
701
696
  contentWidth: "500px",
702
697
  ...bodyValues
@@ -745,7 +740,11 @@ function createItemComponent(config) {
745
740
  ItemComponent[UNLAYER_CONFIG_KEY] = {
746
741
  name: config.name,
747
742
  defaultValues: config.defaultValues,
748
- propMapper: config.propMapper
743
+ propMapper: config.propMapper,
744
+ contentType: config.contentType,
745
+ slug: config.slug,
746
+ metaName: config.metaName,
747
+ head: config.head
749
748
  };
750
749
  return ItemComponent;
751
750
  }
@@ -1367,9 +1366,12 @@ var Column = (props) => {
1367
1366
  if (typeof child.type === "function") {
1368
1367
  const ComponentType = child.type;
1369
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}`);
1370
1371
  const rendered = renderFn({
1371
1372
  ...child.props,
1372
1373
  _config,
1374
+ _metaHtmlId: contentHtmlId,
1373
1375
  colIndex: index,
1374
1376
  cells,
1375
1377
  bodyValues,
@@ -1378,15 +1380,13 @@ var Column = (props) => {
1378
1380
  });
1379
1381
  if (rendered && typeof rendered === "object" && rendered.props && rendered.props.dangerouslySetInnerHTML) {
1380
1382
  const componentHTML = rendered.props.dangerouslySetInnerHTML.__html;
1381
- const componentType = child.type;
1382
- const componentName = (componentType?.displayName || componentType?.name || "component").toLowerCase();
1383
1383
  const childProps = child.props;
1384
1384
  const rawContainerPadding = childProps.containerPadding ?? childProps.values?.containerPadding ?? DEFAULT_CONTAINER_PADDING2;
1385
1385
  const containerPadding = typeof rawContainerPadding === "number" ? `${rawContainerPadding}px` : rawContainerPadding;
1386
1386
  const contentValues = {
1387
1387
  containerPadding,
1388
1388
  _meta: {
1389
- htmlID: nextHtmlId(_config, `u_content_${componentName}`),
1389
+ htmlID: contentHtmlId,
1390
1390
  htmlClassNames: `u_content_${componentName}`
1391
1391
  }
1392
1392
  };
@@ -1557,7 +1557,7 @@ UnlayerProvider.displayName = "UnlayerProvider";
1557
1557
  function useUnlayerConfig() {
1558
1558
  return useContext(getUnlayerContext());
1559
1559
  }
1560
- function ensureMeta2(values, type, index = 0) {
1560
+ function ensureMeta(values, type, index = 0) {
1561
1561
  return {
1562
1562
  ...values,
1563
1563
  _meta: {
@@ -1638,17 +1638,17 @@ function walkItem(element, bodyValues, displayMode, headConfig, counters, styles
1638
1638
  const { children, ...restProps } = element.props;
1639
1639
  const mappedValues = propMapper({ children, ...restProps });
1640
1640
  const finalValues = mergeValues(defaultValues, mappedValues);
1641
- const contentType = name.toLowerCase();
1641
+ const contentType = config.metaName ?? name.toLowerCase();
1642
1642
  const count = nextCounter(counters, `u_content_${contentType}`);
1643
- const valuesWithMeta = ensureMeta2(finalValues, contentType, count - 1);
1644
- const head = heads[name];
1643
+ const valuesWithMeta = ensureMeta(finalValues, contentType, count - 1);
1644
+ const head = config.head ?? heads[name];
1645
1645
  callHead(head, valuesWithMeta, bodyValues, displayMode, headConfig, styles, scripts, tags);
1646
1646
  }
1647
1647
  function walkColumn(element, bodyValues, rowValues, displayMode, headConfig, counters, styles, scripts, tags) {
1648
1648
  const semanticProps = extractSemanticProps(element.props);
1649
1649
  const mapped = mapSemanticProps(semanticProps, COLUMN_DEFAULTS, "Column");
1650
1650
  const count = nextCounter(counters, "u_column");
1651
- const columnValues = ensureMeta2(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
1651
+ const columnValues = ensureMeta(mergeValues(COLUMN_DEFAULTS, mapped), "column", count - 1);
1652
1652
  const columnHead = heads["Column"];
1653
1653
  callHead(columnHead, columnValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1654
1654
  const children = collectChildren(element.props.children);
@@ -1660,7 +1660,7 @@ function walkRow(element, bodyValues, displayMode, headConfig, counters, styles,
1660
1660
  const semanticProps = extractSemanticProps(element.props, ["layout"]);
1661
1661
  const mapped = mapSemanticProps(semanticProps, ROW_DEFAULTS, "Row");
1662
1662
  const count = nextCounter(counters, "u_row");
1663
- const rowValues = ensureMeta2(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
1663
+ const rowValues = ensureMeta(mergeValues(ROW_DEFAULTS, mapped), "row", count - 1);
1664
1664
  const rowHead = heads["Row"];
1665
1665
  callHead(rowHead, rowValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1666
1666
  const children = collectChildren(element.props.children);
@@ -1683,7 +1683,7 @@ function extractHeadFromTree(element, options) {
1683
1683
  const counters = {};
1684
1684
  const semanticProps = extractSemanticProps(element.props);
1685
1685
  const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
1686
- const bodyValues = ensureMeta2(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1686
+ const bodyValues = ensureMeta(mergeValues(BODY_DEFAULTS, mapped), "body", 0);
1687
1687
  const bodyHead = heads["Body"];
1688
1688
  callHead(bodyHead, bodyValues, bodyValues, displayMode, headConfig, styles, scripts, tags);
1689
1689
  const children = collectChildren(element.props.children);
@@ -1956,10 +1956,11 @@ function processItem(element, counters, layout = {}) {
1956
1956
  `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
1957
1957
  );
1958
1958
  }
1959
- const { name, defaultValues, propMapper } = config;
1960
- const contentType = toContentType(name);
1961
- const count = nextCounter2(counters, `u_content_${contentType}`);
1962
- 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);
1963
1964
  const { children, ...restProps } = element.props;
1964
1965
  const mappedValues = propMapper({ children, ...restProps });
1965
1966
  const finalValues = mergeValues(defaultValues, mappedValues);
@@ -1972,7 +1973,7 @@ function processItem(element, counters, layout = {}) {
1972
1973
  ...finalValues,
1973
1974
  _meta: {
1974
1975
  htmlID: id,
1975
- htmlClassNames: `u_content_${contentType}`,
1976
+ htmlClassNames: metaBase,
1976
1977
  ...finalValues._meta || {}
1977
1978
  },
1978
1979
  selectable: true,
@@ -1989,7 +1990,7 @@ function processItem(element, counters, layout = {}) {
1989
1990
  });
1990
1991
  values.src = pinImageSrc(itemSrc, availableWidth);
1991
1992
  }
1992
- return { type: contentType, values };
1993
+ return slug ? { type: contentType, slug, values } : { type: contentType, values };
1993
1994
  }
1994
1995
  function processColumn(element, counters, layout = {}) {
1995
1996
  const count = nextCounter2(counters, "u_column");
@@ -2133,6 +2134,89 @@ function renderToJson(element) {
2133
2134
  };
2134
2135
  }
2135
2136
 
2136
- 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 };
2137
2221
  //# sourceMappingURL=index.js.map
2138
2222
  //# sourceMappingURL=index.js.map