@unlayer/react-elements 0.1.17 → 0.1.19

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;
@@ -896,7 +902,11 @@ declare function renderToPlainText(element: React__default.ReactElement, config?
896
902
  * Result of renderToHtmlParts — head and body as separate strings.
897
903
  */
898
904
  interface HtmlParts {
899
- /** `<head>` content: `<style>` blocks with component CSS, optional `<script>` tags */
905
+ /**
906
+ * Pre-assembled `<head>` content: the css in a `<style>` tag, the js in a
907
+ * `<script>` tag, and the tags — joined and ready to drop into a document.
908
+ * Equivalent to composing the granular fields below yourself.
909
+ */
900
910
  head: string;
901
911
  /**
902
912
  * The rendered body markup, wrapped in the renderer's host `<div>`. In
@@ -905,6 +915,21 @@ interface HtmlParts {
905
915
  * `<body>` tag when composing a full document.
906
916
  */
907
917
  body: string;
918
+ /** Raw component CSS (no `<style>` wrapper) — e.g. for inlining pipelines */
919
+ css: string;
920
+ /** Raw component JS (no `<script>` wrapper); empty for most email designs */
921
+ js: string;
922
+ /**
923
+ * Individual head tags (`<meta>`, `<link>`, ...) contributed by components.
924
+ * Not part of the editor's documented chunk parameters, but exposed here
925
+ * because `head` can contain them — without this field, reassembling the
926
+ * head from `css` + `js` would silently drop them. Usually empty.
927
+ *
928
+ * There is deliberately no `fonts` chunk: Elements has no font registry to
929
+ * resolve font URLs from — pass stylesheet URLs via renderToHtml's `fonts`
930
+ * option instead.
931
+ */
932
+ tags: string[];
908
933
  }
909
934
  /**
910
935
  * Renders an Unlayer element tree to separate head and body HTML strings.
@@ -1001,4 +1026,104 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
1001
1026
  declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
1002
1027
  declare function renderToJson(element: React__default.ReactElement): DesignJSON;
1003
1028
 
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 };
1029
+ /**
1030
+ * registerTool — render the editor's custom tools from code.
1031
+ *
1032
+ * Accepts the same tool definition object embedders already write for
1033
+ * `unlayer.registerTool` (see docs.unlayer.com › Custom Tools) and returns a
1034
+ * React component. One definition powers both runtimes:
1035
+ *
1036
+ * - The Builder uses `label`/`icon`/`options`/`renderer.Viewer`/`validator`
1037
+ * for its panel, canvas, and audit — Elements ignores those.
1038
+ * - Elements calls `renderer.exporters[mode](values)` to produce HTML and
1039
+ * collects `renderer.head` css/js like any built-in component, so code
1040
+ * output matches editor exports by construction — it is the same function.
1041
+ * - `renderToJson` emits `{ type: "custom", slug: <name>, values }`, the
1042
+ * shape the editor stores, so designs round-trip into the Builder where
1043
+ * the same tool is registered.
1044
+ *
1045
+ * Sanitization: like editor exports, the tool's HTML output is passed
1046
+ * through `toSafeHtml` when one is configured. The default config has NO
1047
+ * sanitizer — with untrusted values, configure `toSafeHtml` (same caveat
1048
+ * as the `<Html>` component).
1049
+ */
1050
+
1051
+ /** Exporter: `(values) => html`. Extra args mirror built-in exporters and can be ignored. */
1052
+ type ToolExporter = (values: any, ...args: any[]) => string;
1053
+ /** One property in an options group — Elements reads only the default. */
1054
+ interface ToolOptionConfig {
1055
+ defaultValue?: unknown;
1056
+ /** Disabled options contribute no default */
1057
+ enabled?: boolean;
1058
+ [editorOnly: string]: unknown;
1059
+ }
1060
+ /** A property group: `options.<group>.options.<property>` */
1061
+ interface ToolOptionGroup {
1062
+ options?: Record<string, ToolOptionConfig | undefined>;
1063
+ [editorOnly: string]: unknown;
1064
+ }
1065
+ /**
1066
+ * The subset of the `unlayer.registerTool` config Elements reads. Extra
1067
+ * editor-only fields (label, icon, Viewer, validator, transformer, ...)
1068
+ * are accepted and ignored, so the same object can be passed to both APIs.
1069
+ */
1070
+ interface ElementsToolConfig {
1071
+ /** Unique tool name — becomes the design JSON `slug` */
1072
+ name: string;
1073
+ /** Initial values for the tool's properties (win over option defaults) */
1074
+ values?: Record<string, any>;
1075
+ /**
1076
+ * Property groups. Real-world tools usually define their defaults here,
1077
+ * as each property's `defaultValue` — those seed the initial values,
1078
+ * with tool-level `values` taking precedence (the editor's order).
1079
+ */
1080
+ options?: Record<string, ToolOptionGroup | undefined>;
1081
+ renderer: {
1082
+ /** Per-mode HTML exporters. `document` falls back to `web`. */
1083
+ exporters: Partial<Record<RenderMode, ToolExporter>>;
1084
+ /** Optional head contributions collected into the document `<head>` */
1085
+ head?: {
1086
+ css?: (values: any, ...args: any[]) => string | undefined;
1087
+ js?: (values: any, ...args: any[]) => string | undefined;
1088
+ tags?: (values: any, ...args: any[]) => string[] | undefined;
1089
+ };
1090
+ /** Editor-only canvas renderer — ignored by Elements */
1091
+ Viewer?: unknown;
1092
+ };
1093
+ /** React DevTools name (defaults to the tool name) */
1094
+ displayName?: string;
1095
+ /** Editor-only fields (label, icon, options, validator, ...) — ignored */
1096
+ [editorOnly: string]: unknown;
1097
+ }
1098
+ /** Props: the tool's values as flat props, plus a `values` escape hatch. */
1099
+ type ElementsToolProps = BaseItemComponentProps & Record<string, any> & {
1100
+ values?: Record<string, any>;
1101
+ };
1102
+ /**
1103
+ * Create a React component from a custom tool definition — a custom tool
1104
+ * is the custom component you create: the same term (and the same config)
1105
+ * as the editor's `unlayer.registerTool`, so one definition serves both.
1106
+ *
1107
+ * @example
1108
+ * ```tsx
1109
+ * const Countdown = registerTool({
1110
+ * name: "countdown",
1111
+ * values: { endTime: "", digitColor: "#404040" },
1112
+ * renderer: {
1113
+ * exporters: {
1114
+ * web: (values) => `<div style="color:${values.digitColor}">…</div>`,
1115
+ * email: (values) => `<table role="presentation">…</table>`,
1116
+ * },
1117
+ * },
1118
+ * });
1119
+ *
1120
+ * <Email><Row><Column>
1121
+ * <Countdown endTime="2026-08-01T00:00:00Z" digitColor="#e11d48" />
1122
+ * </Column></Row></Email>
1123
+ * ```
1124
+ */
1125
+ declare function registerTool(tool: ElementsToolConfig): React__default.FC<ElementsToolProps>;
1126
+ /** Alias of {@link registerTool}. */
1127
+ declare const registerElementsTool: typeof registerTool;
1128
+
1129
+ 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;
@@ -896,7 +902,11 @@ declare function renderToPlainText(element: React__default.ReactElement, config?
896
902
  * Result of renderToHtmlParts — head and body as separate strings.
897
903
  */
898
904
  interface HtmlParts {
899
- /** `<head>` content: `<style>` blocks with component CSS, optional `<script>` tags */
905
+ /**
906
+ * Pre-assembled `<head>` content: the css in a `<style>` tag, the js in a
907
+ * `<script>` tag, and the tags — joined and ready to drop into a document.
908
+ * Equivalent to composing the granular fields below yourself.
909
+ */
900
910
  head: string;
901
911
  /**
902
912
  * The rendered body markup, wrapped in the renderer's host `<div>`. In
@@ -905,6 +915,21 @@ interface HtmlParts {
905
915
  * `<body>` tag when composing a full document.
906
916
  */
907
917
  body: string;
918
+ /** Raw component CSS (no `<style>` wrapper) — e.g. for inlining pipelines */
919
+ css: string;
920
+ /** Raw component JS (no `<script>` wrapper); empty for most email designs */
921
+ js: string;
922
+ /**
923
+ * Individual head tags (`<meta>`, `<link>`, ...) contributed by components.
924
+ * Not part of the editor's documented chunk parameters, but exposed here
925
+ * because `head` can contain them — without this field, reassembling the
926
+ * head from `css` + `js` would silently drop them. Usually empty.
927
+ *
928
+ * There is deliberately no `fonts` chunk: Elements has no font registry to
929
+ * resolve font URLs from — pass stylesheet URLs via renderToHtml's `fonts`
930
+ * option instead.
931
+ */
932
+ tags: string[];
908
933
  }
909
934
  /**
910
935
  * Renders an Unlayer element tree to separate head and body HTML strings.
@@ -1001,4 +1026,104 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
1001
1026
  declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
1002
1027
  declare function renderToJson(element: React__default.ReactElement): DesignJSON;
1003
1028
 
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 };
1029
+ /**
1030
+ * registerTool — render the editor's custom tools from code.
1031
+ *
1032
+ * Accepts the same tool definition object embedders already write for
1033
+ * `unlayer.registerTool` (see docs.unlayer.com › Custom Tools) and returns a
1034
+ * React component. One definition powers both runtimes:
1035
+ *
1036
+ * - The Builder uses `label`/`icon`/`options`/`renderer.Viewer`/`validator`
1037
+ * for its panel, canvas, and audit — Elements ignores those.
1038
+ * - Elements calls `renderer.exporters[mode](values)` to produce HTML and
1039
+ * collects `renderer.head` css/js like any built-in component, so code
1040
+ * output matches editor exports by construction — it is the same function.
1041
+ * - `renderToJson` emits `{ type: "custom", slug: <name>, values }`, the
1042
+ * shape the editor stores, so designs round-trip into the Builder where
1043
+ * the same tool is registered.
1044
+ *
1045
+ * Sanitization: like editor exports, the tool's HTML output is passed
1046
+ * through `toSafeHtml` when one is configured. The default config has NO
1047
+ * sanitizer — with untrusted values, configure `toSafeHtml` (same caveat
1048
+ * as the `<Html>` component).
1049
+ */
1050
+
1051
+ /** Exporter: `(values) => html`. Extra args mirror built-in exporters and can be ignored. */
1052
+ type ToolExporter = (values: any, ...args: any[]) => string;
1053
+ /** One property in an options group — Elements reads only the default. */
1054
+ interface ToolOptionConfig {
1055
+ defaultValue?: unknown;
1056
+ /** Disabled options contribute no default */
1057
+ enabled?: boolean;
1058
+ [editorOnly: string]: unknown;
1059
+ }
1060
+ /** A property group: `options.<group>.options.<property>` */
1061
+ interface ToolOptionGroup {
1062
+ options?: Record<string, ToolOptionConfig | undefined>;
1063
+ [editorOnly: string]: unknown;
1064
+ }
1065
+ /**
1066
+ * The subset of the `unlayer.registerTool` config Elements reads. Extra
1067
+ * editor-only fields (label, icon, Viewer, validator, transformer, ...)
1068
+ * are accepted and ignored, so the same object can be passed to both APIs.
1069
+ */
1070
+ interface ElementsToolConfig {
1071
+ /** Unique tool name — becomes the design JSON `slug` */
1072
+ name: string;
1073
+ /** Initial values for the tool's properties (win over option defaults) */
1074
+ values?: Record<string, any>;
1075
+ /**
1076
+ * Property groups. Real-world tools usually define their defaults here,
1077
+ * as each property's `defaultValue` — those seed the initial values,
1078
+ * with tool-level `values` taking precedence (the editor's order).
1079
+ */
1080
+ options?: Record<string, ToolOptionGroup | undefined>;
1081
+ renderer: {
1082
+ /** Per-mode HTML exporters. `document` falls back to `web`. */
1083
+ exporters: Partial<Record<RenderMode, ToolExporter>>;
1084
+ /** Optional head contributions collected into the document `<head>` */
1085
+ head?: {
1086
+ css?: (values: any, ...args: any[]) => string | undefined;
1087
+ js?: (values: any, ...args: any[]) => string | undefined;
1088
+ tags?: (values: any, ...args: any[]) => string[] | undefined;
1089
+ };
1090
+ /** Editor-only canvas renderer — ignored by Elements */
1091
+ Viewer?: unknown;
1092
+ };
1093
+ /** React DevTools name (defaults to the tool name) */
1094
+ displayName?: string;
1095
+ /** Editor-only fields (label, icon, options, validator, ...) — ignored */
1096
+ [editorOnly: string]: unknown;
1097
+ }
1098
+ /** Props: the tool's values as flat props, plus a `values` escape hatch. */
1099
+ type ElementsToolProps = BaseItemComponentProps & Record<string, any> & {
1100
+ values?: Record<string, any>;
1101
+ };
1102
+ /**
1103
+ * Create a React component from a custom tool definition — a custom tool
1104
+ * is the custom component you create: the same term (and the same config)
1105
+ * as the editor's `unlayer.registerTool`, so one definition serves both.
1106
+ *
1107
+ * @example
1108
+ * ```tsx
1109
+ * const Countdown = registerTool({
1110
+ * name: "countdown",
1111
+ * values: { endTime: "", digitColor: "#404040" },
1112
+ * renderer: {
1113
+ * exporters: {
1114
+ * web: (values) => `<div style="color:${values.digitColor}">…</div>`,
1115
+ * email: (values) => `<table role="presentation">…</table>`,
1116
+ * },
1117
+ * },
1118
+ * });
1119
+ *
1120
+ * <Email><Row><Column>
1121
+ * <Countdown endTime="2026-08-01T00:00:00Z" digitColor="#e11d48" />
1122
+ * </Column></Row></Email>
1123
+ * ```
1124
+ */
1125
+ declare function registerTool(tool: ElementsToolConfig): React__default.FC<ElementsToolProps>;
1126
+ /** Alias of {@link registerTool}. */
1127
+ declare const registerElementsTool: typeof registerTool;
1128
+
1129
+ 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);
@@ -1850,7 +1850,10 @@ function renderToHtmlParts(element, config) {
1850
1850
  }
1851
1851
  return {
1852
1852
  head: headParts.join("\n"),
1853
- body
1853
+ body,
1854
+ css,
1855
+ js,
1856
+ tags
1854
1857
  };
1855
1858
  }
1856
1859
  var schemaVersion = schemaVersion$1 ?? 24;
@@ -1956,10 +1959,11 @@ function processItem(element, counters, layout = {}) {
1956
1959
  `[Unlayer] renderToJson: <${name2}> is not a recognized Unlayer item component. Only components created with createItemComponent are supported.`
1957
1960
  );
1958
1961
  }
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);
1962
+ const { name, defaultValues, propMapper, slug } = config;
1963
+ const contentType = config.contentType ?? toContentType(name);
1964
+ const metaBase = `u_content_${config.metaName ?? contentType}`;
1965
+ const count = nextCounter2(counters, metaBase);
1966
+ const id = makeId(metaBase, count);
1963
1967
  const { children, ...restProps } = element.props;
1964
1968
  const mappedValues = propMapper({ children, ...restProps });
1965
1969
  const finalValues = mergeValues(defaultValues, mappedValues);
@@ -1972,7 +1976,7 @@ function processItem(element, counters, layout = {}) {
1972
1976
  ...finalValues,
1973
1977
  _meta: {
1974
1978
  htmlID: id,
1975
- htmlClassNames: `u_content_${contentType}`,
1979
+ htmlClassNames: metaBase,
1976
1980
  ...finalValues._meta || {}
1977
1981
  },
1978
1982
  selectable: true,
@@ -1989,7 +1993,7 @@ function processItem(element, counters, layout = {}) {
1989
1993
  });
1990
1994
  values.src = pinImageSrc(itemSrc, availableWidth);
1991
1995
  }
1992
- return { type: contentType, values };
1996
+ return slug ? { type: contentType, slug, values } : { type: contentType, values };
1993
1997
  }
1994
1998
  function processColumn(element, counters, layout = {}) {
1995
1999
  const count = nextCounter2(counters, "u_column");
@@ -2133,6 +2137,89 @@ function renderToJson(element) {
2133
2137
  };
2134
2138
  }
2135
2139
 
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 };
2140
+ // src/utils/register-tool.tsx
2141
+ function initialValuesFromOptions(options) {
2142
+ const result = {};
2143
+ for (const group of Object.values(options ?? {})) {
2144
+ for (const [property, option] of Object.entries(group?.options ?? {})) {
2145
+ if (!option || option.enabled === false) continue;
2146
+ if (typeof option.defaultValue !== "undefined") {
2147
+ result[property] = option.defaultValue;
2148
+ }
2149
+ }
2150
+ }
2151
+ return result;
2152
+ }
2153
+ function withSanitizer(exporter) {
2154
+ return (values, ...args) => {
2155
+ const html = String(exporter(values, ...args) ?? "");
2156
+ const meta = args[args.length - 1];
2157
+ const toSafeHtml = meta?.exporterConfig?.toSafeHtml;
2158
+ return typeof toSafeHtml === "function" ? toSafeHtml(html) : html;
2159
+ };
2160
+ }
2161
+ function withLinkNormalization(exporter, linkKeys) {
2162
+ if (linkKeys.length === 0) return exporter;
2163
+ return (values, ...args) => {
2164
+ const out = { ...values };
2165
+ for (const key of linkKeys) {
2166
+ const normalized = normalizeLinkValue(out[key]);
2167
+ if (normalized !== void 0) out[key] = normalized;
2168
+ }
2169
+ return exporter(out, ...args);
2170
+ };
2171
+ }
2172
+ function linkWidgetKeys(options) {
2173
+ const keys = [];
2174
+ for (const group of Object.values(options ?? {})) {
2175
+ for (const [key, option] of Object.entries(group?.options ?? {})) {
2176
+ if (option && option.widget === "link") keys.push(key);
2177
+ }
2178
+ }
2179
+ return keys;
2180
+ }
2181
+ function registerTool(tool) {
2182
+ if (!tool?.name || typeof tool.name !== "string") {
2183
+ throw new Error("[Unlayer] registerTool: `name` is required");
2184
+ }
2185
+ const exporters = tool.renderer?.exporters;
2186
+ if (!exporters || !exporters.web && !exporters.email) {
2187
+ throw new Error(
2188
+ `[Unlayer] registerTool("${tool.name}"): \`renderer.exporters\` needs at least a \`web\` or \`email\` exporter`
2189
+ );
2190
+ }
2191
+ const linkKeys = linkWidgetKeys(tool.options);
2192
+ const sanitized = {};
2193
+ for (const mode of ["web", "email", "document"]) {
2194
+ const exporter = exporters[mode];
2195
+ if (exporter) {
2196
+ sanitized[mode] = withSanitizer(withLinkNormalization(exporter, linkKeys));
2197
+ }
2198
+ }
2199
+ if (!sanitized.web && sanitized.email) sanitized.web = sanitized.email;
2200
+ return createItemComponent({
2201
+ name: tool.name,
2202
+ displayName: tool.displayName ?? tool.name,
2203
+ // Option-group widget defaults seed the values; tool-level `values`
2204
+ // win — the editor's precedence.
2205
+ defaultValues: {
2206
+ ...initialValuesFromOptions(tool.options),
2207
+ ...tool.values ?? {}
2208
+ },
2209
+ // Flat props are the tool's values; `values` is the escape hatch.
2210
+ propMapper: ({ children: _children, values, ...rest }) => ({
2211
+ ...values ?? {},
2212
+ ...rest
2213
+ }),
2214
+ exporters: sanitized,
2215
+ contentType: "custom",
2216
+ slug: tool.name,
2217
+ metaName: `custom_${tool.name}`,
2218
+ head: tool.renderer.head
2219
+ });
2220
+ }
2221
+ var registerElementsTool = registerTool;
2222
+
2223
+ 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
2224
  //# sourceMappingURL=index.js.map
2138
2225
  //# sourceMappingURL=index.js.map