Skip to content

Typen-Referenz ​

@templatical/types stellt alle gemeinsamen TypeScript-Typen, Block-Factory-Funktionen und Hilfsfunktionen bereit.

bash
npm install @templatical/types
bash
pnpm add @templatical/types
bash
yarn add @templatical/types
bash
bun add @templatical/types

Template-Struktur ​

TemplateContent ​

Der Root-Typ, der ein E-Mail-Template repräsentiert.

ts
interface TemplateContent {
  blocks: Block[];
  settings: TemplateSettings;
}

TemplateSettings ​

ts
interface TemplateSettings {
  width: number;                  // Template-Breite in Pixeln (Standard: 600)
  backgroundColor: string;        // Hintergrundfarbe
  textColor: string;              // Standard-Textfarbe; Textblöcke erben sie (Standard #1a1a1a)
  linkColor?: string;             // Linkfarbe; Links erben die Textfarbe, wenn nicht gesetzt
  linkUnderline: boolean;         // Textlinks unterstreichen (Standard true)
  fontFamily: string;             // Standard-Schriftfamilie
  preheaderText?: string;         // E-Mail-Preheader-Text
  locale: string;                 // BCP-47-Inhaltssprache; <html lang> (Standard 'en')
  locales?: string[];             // Weitere Sprachversionen; locale ist der Standard
  l10n?: Record<string, SettingsLocaleLayer>; // Sprachebenen der Einstellungen
  direction?: ContentDirection;   // 'ltr' | 'rtl'; ungesetzt folgt der Inhaltssprache
}

type ContentDirection = 'ltr' | 'rtl';

Block ​

Eine diskriminierte Union aller Blocktypen:

ts
type Block =
  | TitleBlock
  | ParagraphBlock
  | ImageBlock
  | ButtonBlock
  | SectionBlock
  | DividerBlock
  | VideoBlock
  | SpacerBlock
  | SocialIconsBlock
  | MenuBlock
  | TableBlock
  | HtmlBlock
  | CountdownBlock
  | CustomBlock
  | SlotBlock
  | WrapperBlock;

BlockType ​

ts
type BlockType =
  | 'title' | 'paragraph' | 'image' | 'button' | 'section'
  | 'divider' | 'video' | 'spacer' | 'social'
  | 'menu' | 'table' | 'html' | 'countdown' | 'custom'
  | 'slot' | 'wrapper';

Lokalisierung ​

Sprachversionen ergänzen optionale BaseBlock.l10n, TemplateSettings.locales und TemplateSettings.l10n. Die öffentlichen Deklarationen stammen aus @templatical/types; definitions ist standardmäßig [] zur Erkennung benutzerdefinierter Textfelder.

ts
import type {
  Block,
  CustomBlockDefinition,
  TemplateContent,
  TemplateSettings,
} from "@templatical/types";

// doNotTranslate is editor/strings-helper config, not stored localization data.
export interface LocalizedText {
  value: string;
  /** `sourceHash(defaultValue)` when the value was written or kept. */
  sourceHash: string;
}

/** One language's layer on a block. Members are omitted when empty. */
export interface BlockLocaleLayer {
  /** Translated text, by field path. */
  text?: Record<string, LocalizedText>;
  /** Any other property overridden for this language, by field path. */
  set?: Record<string, unknown>;
  /** The block is left out of this language. */
  hidden?: true;
}

/** One language's layer on the template settings. */
export interface SettingsLocaleLayer {
  text?: Record<string, LocalizedText>;
  set?: Record<string, unknown>;
}

export declare const TRANSLATABLE_FIELDS: Readonly<
  Record<string, readonly string[]>
>;
export declare const SETTINGS_TRANSLATABLE_FIELDS: readonly string[];
export declare function translatableFields(
  block: Block,
  definitions?: readonly CustomBlockDefinition[],
): string[];
export declare function readPath(target: unknown, path: string): unknown;
export declare function sourceHash(value: string): string;
export declare function localizeBlock(
  block: Block,
  locale: string,
  defaultLocale: string,
): Block;
export declare function resolveLocale(
  content: TemplateContent,
  locale: string,
  options?: { missing?: "throw" | "default" },
): TemplateContent;
export interface LocaleCoverage {
  translated: string[];
  missing: string[];
  stale: string[];
}
export declare function localeCoverage(
  content: TemplateContent,
  locale: string,
  definitions?: readonly CustomBlockDefinition[],
): LocaleCoverage;
export declare function defaultLocaleOf(content: TemplateContent): string;
export declare function isLocaleTag(value: unknown): value is string;
export declare function isSafePath(path: string): boolean;
export declare function isTranslatablePath(
  blockType: string,
  path: string,
): boolean;
export declare function writePath(
  target: Record<string, unknown>,
  path: string,
  value: unknown,
): boolean;

export interface AddLocaleOperation {
  op: "addLocale";
  locale: string;
  /** Index in `settings.locales` to insert at; default the end. Undo uses it to restore order. */
  position?: number;
}

export interface RemoveLocaleOperation {
  op: "removeLocale";
  locale: string;
  /** Guard: the locale's block and settings layers must match exactly; absent means no layer. */
  expect?: {
    blocks: Record<string, BlockLocaleLayer>;
    settings?: SettingsLocaleLayer;
  };
}

export interface LocaleLayerExpect {
  text?: Record<string, LocalizedText | null>;
  /** `null` matches an absent override. */
  set?: Record<string, unknown>;
  hidden?: boolean;
}

export interface UpdateBlockLocaleOperation {
  op: "updateBlockLocale";
  blockId: string;
  locale: string;
  text?: Record<string, LocalizedText>;
  set?: Record<string, unknown>;
  unset?: string[];
  hidden?: boolean;
  expect?: LocaleLayerExpect;
}

export interface UpdateSettingsLocaleOperation {
  op: "updateSettingsLocale";
  locale: string;
  text?: Record<string, LocalizedText>;
  set?: Record<string, unknown>;
  unset?: string[];
  expect?: Omit<LocaleLayerExpect, "hidden">;
}

export declare function pruneLocaleOrphans(block: Block): Block;
export declare function pruneSettingsLocaleOrphans(
  settings: TemplateSettings,
): TemplateSettings;
export declare function isStructuralLocalePath(
  block: Block,
  path: string,
  value?: unknown,
): boolean;

/** Ids of blocks hidden in `locale`, depth first. Pure. */
export declare function localeHiddenBlocks(
  content: TemplateContent,
  locale: string,
): string[];
/** Field ids with a nonempty default that would keep the current default's value if `locale` became the default. Pure. */
export declare function defaultLocaleGaps(
  content: TemplateContent,
  locale: string,
  definitions?: readonly CustomBlockDefinition[],
): string[];
export interface SetDefaultLocaleOperation {
  op: "setDefaultLocale";
  /** A language already in `settings.locales`. */
  locale: string;
  /** Custom block ids mapped to registered text/textarea field paths, captured for replay. */
  customTextFields?: Record<string, string[]>;
}

readPath löst Arrayelemente über ihre ID auf und liefert undefined für einen nicht auflösbaren Pfad. writePath klont den Wert, behandelt null als Löschen der Eigenschaft und liefert false, wenn das übergeordnete Objekt fehlt. isSafePath lehnt leere und Prototyp-Schlüsselsegmente ab. isLocaleTag akzeptiert eine Sprache aus 2–3 Buchstaben mit optionalen alphanumerischen Untertags aus 2–8 Zeichen. defaultLocaleOf liefert settings.locale, mit Fallback auf en.

TRANSLATABLE_FIELDS listet die Textpfadmuster jedes Blocktyps, wobei * eine Element-ID oder bei benutzerdefinierten Blöcken einen Feldschlüssel bezeichnet: content von Titel, Absatz und HTML, alt von Bild und Video, text von Buttons, Countdown-Beschriftungen und expiredMessage, items.*.text bei Menüs, rows.*.cells.*.content bei Tabellen und fieldValues.* bei benutzerdefinierten Blöcken. SETTINGS_TRANSLATABLE_FIELDS ist ["preheaderText"]. translatableFields(block, definitions) setzt die Muster eines Blocks in konkrete Pfade um und behält nur benutzerdefinierte Felder, deren Definitionstyp text oder textarea ist. Ein Locale-text-Eintrag muss einen dieser Pfade nennen; jede andere Überschreibung gehört in set.

pruneLocaleOrphans(block: Block): Block entfernt rekursiv nicht mehr auflösbare Textpfade und Set-Pfade ohne auflösbares Elternobjekt. pruneSettingsLocaleOrphans(settings: TemplateSettings): TemplateSettings wendet dieselbe Regel auf Einstellungen an. Beide sind rein und lassen leere Lokalisierungsfelder weg. isStructuralLocalePath(block: Block, path: string, value?: unknown): boolean erkennt Sektionsspalten, vollständige integrierte Listen und Element-IDs, die Locale-Overrides nicht ersetzen dürfen. Für die Prüfung eines vollständigen Elementersatzes den vorgesehenen set-Wert übergeben: Die ID muss unverändert bleiben, und eine Tabellenzeile muss ihre Zell-IDs in derselben Reihenfolge behalten. Custom-Overrides unter fieldValues.* sind ausgenommen.

elementIdIssues(content: TemplateContent): { path: string; id: string }[] meldet IDs von Menüeinträgen, Social-Icons, Tabellenzeilen und Tabellenzellen mit . oder / in Tiefensuche, einschließlich Abschnittsspalten und Layout-Wrappern. Der Helfer ist rein; Pfade verwenden Array-Indizes, etwa blocks[2].items[0].id. validateTemplate meldet jedes Problem als <path>: Element id "<id>" must not contain "." or "/".; addBlock, updateBlock und setContent lehnen eine solche ID, die sie einführen (in einem neuen Block oder neu in ihrem Block), mit invalid_operation und der blockId des zugehörigen Blocks ab; eine ID, die ihr Block schon hat, bleibt bearbeitbar.

sourceHash ist FNV-1a 32-Bit über UTF-8-Bytes, als acht kleingeschriebene Hexadezimalziffern: "" → 811c9dc5, "a" → e40c292c, "foobar" → bf9cf968. localizeBlock projiziert einen Block ohne Rekursion oder Ausfiltern ausgeblendeter Blöcke. resolveLocale(content, locale, options?: { missing?: "throw" | "default" }) projiziert ein vollständiges Template oder Layout einschließlich Wrapper-Kindern, entfernt ausgeblendete Blöcke rekursiv und entfernt Lokalisierungsfelder. Ohne missing oder mit missing: "throw" wirft eine unbekannte Version unknown_locale. Mit missing: "default" wird die Standardsprache des Dokuments projiziert und dessen Standard-settings.locale beibehalten. Bekannte Versionen bleiben unverändert. Beide Helfer sind rein.

localeCoverage liefert Feld-IDs (<blockId>/<path> oder settings/<path>) in Dokumentreihenfolge, Einstellungen zuletzt. Fehlende nichtleere Standardwerte stehen in missing, Text mit geändertem sourceHash in stale, aktueller Text in translated. Ausgeblendete Blöcke und ihre Nachfahren sind ausgeschlossen. Benutzerdefinierte Definitionen erkennen text- und textarea-Felder. Verwenden Sie die Abdeckung für eine Nichtstandardversion; sie projiziert oder validiert keine Locale.

addLocale, removeLocale, setDefaultLocale, updateBlockLocale und updateSettingsLocale erweitern Operation. addLocale.position ist standardmäßig das Ende; Entfernen kann mit expect die gesamte Ebene prüfen. Updates wenden unset, text, set, dann Block-hidden an und verwenden die verschachtelte Form LocaleLayerExpect. Siehe Lokalisierte Templates für Bearbeitung, Feldpfade, Fallback und die Upgrade-Regel.

SetDefaultLocaleOperation.customTextFields?: Record<string, string[]> enthält registrierte benutzerdefinierte text-/textarea-Pfade nach Block-ID. Erstellen Sie die Operation mit createSetDefaultLocaleOperation(content: TemplateContent, locale: string, definitions?: readonly CustomBlockDefinition[]): SetDefaultLocaleOperation (definitions ist standardmäßig []) und senden Sie sie unverändert an andere Sitzungen. Siehe Standardsprache für Validierung und Verhalten ohne Definitionen.

Übersetzung ​

TranslationProvider ist der unabhängige Vertrag für init({ translation }). TranslationRequest, TranslationField, TranslationResult, TranslationErrorCode und LocaleString sowie reine Helfer und Aufrufgrenzen werden aus @templatical/types exportiert:

ts
import type {
  CustomBlockDefinition,
  Operation,
  SyntaxPreset,
  TemplateContent,
} from "@templatical/types";

export type TranslationErrorCode =
  "rate_limited" | "limit_reached" | "content_too_large" | "translation_failed";

export interface TranslationField {
  /** A block field id, or `settings/<path>`. */
  id: string;
  /** Merge and logic tags masked as numbered placeholders. */
  text: string;
  format: "html" | "text";
}

export interface TranslationRequest {
  templateId: string | null;
  sourceLocale: string;
  targetLocale: string;
  /** At most 50 fields and 20,000 characters of text per call. */
  fields: TranslationField[];
}

export interface TranslationResult {
  fields: { id: string; text: string }[];
}

/** A translation backend. Reject with an SdkError or, when aborted, an AbortError. */
export interface TranslationProvider {
  translate(
    request: TranslationRequest,
    signal: AbortSignal,
  ): Promise<TranslationResult>;
}

export const TRANSLATION_MAX_FIELDS = 50;
export const TRANSLATION_MAX_CHARS = 20_000;

export interface LocaleString {
  id: string;
  format: "html" | "text";
  /** The masked default value. */
  source: string;
  /** The hash of the unmasked default at extraction time. */
  sourceHash: string;
  /** The stored translation masked with the source's token ids. */
  text?: string;
}

export declare const TRANSLATABLE_HTML_FIELDS: Readonly<
  Record<string, readonly string[]>
>;

export declare function maskTags(
  text: string,
  syntax: SyntaxPreset,
  format: "html" | "text",
  options?: { doNotTranslate?: readonly string[] },
): { text: string; tokens: string[] };

export declare function unmaskTags(
  text: string,
  tokens: string[],
): string | null;

export declare function localeStrings(
  content: TemplateContent,
  locale: string,
  options: {
    definitions?: readonly CustomBlockDefinition[];
    syntax: SyntaxPreset;
    only: "gaps" | "all";
    blockId?: string;
    doNotTranslate?: readonly string[];
  },
): { strings: LocaleString[]; tokens: Record<string, string[]> };

export declare function applyLocaleStrings(
  content: TemplateContent,
  locale: string,
  values: { id: string; text: string; sourceHash: string }[],
  options: {
    definitions?: readonly CustomBlockDefinition[];
    syntax: SyntaxPreset;
    tokens: Record<string, string[]>;
    doNotTranslate?: readonly string[];
  },
): {
  operations: Operation[];
  rejected: {
    id: string;
    reason: "tags_changed" | "unknown_field" | "not_translatable";
  }[];
};

TRANSLATION_MAX_FIELDS = 50 und TRANSLATION_MAX_CHARS = 20_000 begrenzen jeden Aufruf. TRANSLATABLE_HTML_FIELDS ist { title: ["content"], paragraph: ["content"] }; jedes andere geeignete Feld ist text. HTML-Blöcke bleiben von Hand übersetzbar und werden von localeStrings ausgelassen.

maskTags(text, syntax, format, options?: { doNotTranslate?: readonly string[] }) maskiert ganze Chips nur in HTML und rohe Value-/Logik-Tags in beiden Formaten. Text-Tokens sind <x id="n"/>, Attribut-Tokens __xn__; beide teilen IDs ab 1 pro Feld in Quellreihenfolge. unmaskTags(text, tokens) stellt ursprüngliche Bytes nur bei exakter ID-Multimenge wieder her, andernfalls folgt null.

Übergeben Sie dasselbe doNotTranslate?: readonly string[] an maskTags, localeStrings und applyLocaleStrings, um konfigurierte Begriffe in Text und HTML-Attributwerten zu schützen. Begriffe werden getrimmt, unter Beachtung der Groß- und Kleinschreibung als ganze Wörter erkannt (Unicode-Buchstaben, Zahlen und _ sind Wortzeichen), längste zuerst und ohne Überlappung; Tags und Chips haben Vorrang. Begriffe teilen die ID-Folge der Tags; die exakte Prüfung stellt jedes Vorkommen Byte für Byte wieder her oder lehnt es als tags_changed ab. Standardmäßig gibt es keine Begriffe. Siehe Übersetzungsmaskierung für Limits und Editor-Konfiguration.

localeStrings verlangt explizit only: "gaps" | "all"; der Editor verwendet standardmäßig Lücken. Leere Standardwerte, überdeckende set-Überschreibungen und ausgeblendete Blöcke werden ausgelassen. blockId beschränkt die Auswahl auf diesen Block. Vorhandene Übersetzungen werden mit Quell-IDs maskiert. Allein die Abdeckung bestimmt Lücken (fehlend und veraltet) oder alle Felder (auch aktuelle). Lassen sich gespeicherte Tags oder geschützte Begriffe nicht mit den Tokens der aktuellen Quelle maskieren, enthält das Feld weiterhin den maskierten aktuellen Standard und sourceHash, jedoch kein text; die Übersetzung beginnt bei diesem aktuellen Standard. sourceHash stammt vom unmaskierten Standardwert zum Extraktionszeitpunkt.

applyLocaleStrings liefert Operationen, ohne sie anzuwenden: eine updateBlockLocale pro Block und höchstens eine updateSettingsLocale. Jede Übersetzung erhält den übergebenen Extraktionshash, keinen beim Anwenden neu berechneten Hash; ein veralteter Eintrag aus localeStrings, dessen text die gespeicherte Übersetzung ist, wird daher aktuell, wenn er unverändert angewendet wird. Überlappende set-Überschreibungen werden mit dem flachsten Pfad zuerst projiziert; tiefere Pfade haben Vorrang. Ablehnungsgründe sind tags_changed, unknown_field und not_translatable. Die Helfer bereinigen HTML nicht. Siehe JSON-Rundlauf und Provider-Vertrag.

HTML-URL-Richtlinie ​

SAFE_LINK_SCHEMES enthält die vom Editor akzeptierten Schemata. DANGEROUS_SCRIPT_SCHEMES enthält die vom Link-Linter gemeldeten Schemata für Skripte oder beliebige Nutzdaten. normalizeUrlForSchemeCheck(value: string): string entfernt Tab, LF und CR, führende Zeichen U+0000–U+0020 und nachgestellte Leerraumzeichen. extractUrlScheme(value: string): string | null liefert das normalisierte Schema in Kleinbuchstaben oder null für relative URLs und Fragmente. DATA_IMAGE_SRC_PATTERN erkennt die in src akzeptierten Bilddaten-MIME-Typen. isSafeHtmlUrl(value: string, attribute: string): boolean wendet diese gemeinsame URL-Wertrichtlinie an.

isSafeUrlAttribute(name: string, value: string): boolean wendet isSafeHtmlUrl auf HTML_URL_ATTRIBUTES an, die gemeinsame Menge des Sanitizer-Hooks im Editor: href, xlink:href, background, poster und src. Übergeben Sie den Wert mit dekodierten Zeichenreferenzen, so wie ein Browser ihn liest; Attributnamen werden ohne Beachtung der Groß- und Kleinschreibung verglichen. Nach der Normalisierung akzeptieren diese Attribute einen leeren Wert, eine URL ohne Schema, ein #-Fragment oder die Schemata http, https, mailto, tel, ftp, ftps, sms, xmpp und cid. Nur src akzeptiert zusätzlich eine data:image/-URL vom Typ png, jpeg, jpg, gif, webp oder svg+xml, gefolgt von ;. Andere Schemata ergeben false. Die gemeinsame Menge HTML_FORBIDDEN_ATTRIBUTES aus der Sanitizer-Konfiguration (srcdoc, ping, formaction und action) ergibt immer false, auch bei leeren Werten oder HTTPS-Werten. Andere Attribute ergeben true. Der DOMPurify-Sanitizer des Editors und template-tools strings import verwenden diese gemeinsame Richtlinie; der Helfer validiert keine Tags, on*-Handler oder weiteren DOMPurify-Regeln. Diese prüfen Aufrufer getrennt.

Basistypen ​

BaseBlock ​

Alle Blöcke erweitern dieses Interface.

ts
interface BaseBlock {
  id: string;
  type: string;
  styles: BlockStyles;
  visibility?: BlockVisibility;
  displayCondition?: DisplayCondition;
  l10n?: Record<string, BlockLocaleLayer>;
}

BlockStyles ​

ts
interface BlockStyles {
  padding: SpacingValue;
  backgroundColor?: string;
}

SpacingValue ​

ts
interface SpacingValue {
  top: number;
  right: number;
  bottom: number;
  left: number;
}

BorderValue ​

Ein Rahmen pro Seite, wie SpacingValue: Jede Seite hat eigene Breite, eigenen Stil und eigene Farbe; Breite 0 lässt die Seite weg. Sektionen, Bilder und Schaltflächen unterstützen ihn.

ts
interface BorderSideValue {
  width: number;   // px; 0 = Seite nicht gezeichnet
  style: 'solid' | 'dashed' | 'dotted';
  color: string;
}

interface BorderValue {
  top: BorderSideValue;
  right: BorderSideValue;
  bottom: BorderSideValue;
  left: BorderSideValue;
}

BorderRadiusValue ​

Ein Eckenradius in px: eine Zahl für alle Ecken oder einer pro Ecke. Sektionen, Sektions-Wrapper, Bilder und Schaltflächen unterstützen ihn.

ts
type BorderRadiusValue =
  | number
  | { topLeft: number; topRight: number; bottomRight: number; bottomLeft: number };

BlockVisibility ​

Steuert, auf welchen Viewports ein Block sichtbar ist.

ts
interface BlockVisibility {
  desktop: boolean;
  mobile: boolean;
}

Blocktypen ​

Detaillierte Beschreibungen der einzelnen Blocktypen finden Sie im Blocks-Leitfaden.

TitleBlock ​

ts
interface TitleBlock extends BaseBlock {
  type: 'title';
  content: string;
  level: HeadingLevel;
  color?: string;
  textAlign: 'left' | 'center' | 'right';
  fontFamily?: string;
}

type HeadingLevel = 1 | 2 | 3 | 4;

ParagraphBlock ​

ts
interface ParagraphBlock extends BaseBlock {
  type: 'paragraph';
  content: string;
  paragraphSpacing?: number;
}

ImageBlock ​

ts
interface ImageBlock extends BaseBlock {
  type: 'image';
  src: string;
  alt: string;
  width: number | 'full';
  /** Ohne Angabe wird die Höhe aus der Breite abgeleitet, das Seitenverhältnis bleibt erhalten. */
  height?: number;
  /** Die eigene Größe der Bilddatei, für die Seitenverhältnis-Sperre. Wird nie gerendert. */
  naturalWidth?: number;
  naturalHeight?: number;
  align: 'left' | 'center' | 'right';
  /** Eckenradius in px. Ohne Angabe oder 0 bleiben die Ecken eckig. */
  borderRadius?: BorderRadiusValue;
  /** Ohne Angabe = kein Rahmen. */
  border?: BorderValue;
  linkUrl?: string;
  linkOpenInNewTab?: boolean;
  placeholderUrl?: string;
  decorative?: boolean;
}

ButtonBlock ​

ts
interface ButtonBlock extends BaseBlock {
  type: 'button';
  text: string;
  url: string;
  backgroundColor: string;
  textColor: string;
  borderRadius: BorderRadiusValue;
  /** Outline-Schaltfläche: backgroundColor 'transparent' (das Schlüsselwort) plus eine textColor. Ein leerer Hintergrund wird als dieses Schlüsselwort exportiert. */
  border?: BorderValue;
  fontSize: number;
  buttonPadding: SpacingValue;
  fontFamily?: string;
  openInNewTab?: boolean;
  width?: number | 'full';
  align: 'left' | 'center' | 'right';
}

SectionBlock ​

Container für mehrspaltige Layouts.

ts
interface SectionBlock extends BaseBlock {
  type: 'section';
  columns: ColumnLayout;
  children: Block[][];
  stackOnMobile?: boolean;
  borderRadius?: BorderRadiusValue;
  border?: BorderValue;
  backgroundImage?: BackgroundImage;
  hero?: SectionHero;
  wrapper?: SectionWrapper;
}

interface SectionWrapper {
  backgroundColor?: string;
  padding?: SpacingValue;
  borderRadius?: BorderRadiusValue;
  backgroundImage?: BackgroundImage;
}

type ColumnLayout = '1' | '2' | '3' | '2-1' | '1-2';

interface BackgroundImage {
  url: string;
  size: 'cover' | 'contain' | 'auto';
  position: {
    x: 'left' | 'center' | 'right';
    y: 'top' | 'center' | 'bottom';
  };
  repeat: boolean; // false: painted once
  naturalWidth?: number;   // the image file's own size in px; a hero's 'image' height keeps this shape
  naturalHeight?: number;
}

interface SectionHero {
  height: 'image' | number; // 'image': as tall as the image across the desktop box; a number: fixed px, padding included
  verticalAlign: 'top' | 'middle' | 'bottom';
}

DividerBlock ​

ts
/** "0%" bis "100%" */
type DividerPercentWidth = `${number}%`;

interface DividerBlock extends BaseBlock {
  type: 'divider';
  lineStyle: 'solid' | 'dashed' | 'dotted';
  color: string;
  thickness: number;
  width: number | 'full' | DividerPercentWidth;
}

VideoBlock ​

Wird als verlinktes Thumbnail-Bild gerendert -- E-Mail-Clients unterstützen keine eingebettete Wiedergabe.

ts
interface VideoBlock extends BaseBlock {
  type: 'video';
  url: string;
  openInNewTab?: boolean;
  thumbnailUrl: string;   // Wird aus einer YouTube-/Vimeo-URL abgeleitet, wenn leer
  alt: string;
  width: number | 'full';
  /** Ohne Angabe wird die Höhe aus der Breite abgeleitet, das Seitenverhältnis bleibt erhalten. */
  height?: number;
  align: 'left' | 'center' | 'right';
  placeholderUrl?: string;
}

SpacerBlock ​

ts
interface SpacerBlock extends BaseBlock {
  type: 'spacer';
  height: number;
}

SocialIconsBlock ​

ts
interface SocialIconsBlock extends BaseBlock {
  type: 'social';
  icons: SocialIcon[];
  iconStyle: SocialIconStyle;
  iconTone?: SocialIconTone; // ohne Angabe: 'brand'
  iconSize: SocialIconSize;
  spacing: number;
  align: 'left' | 'center' | 'right';
}

interface SocialIcon {
  id: string;
  platform: SocialPlatform;
  url: string;
}

type SocialPlatform =
  | 'facebook' | 'twitter' | 'instagram' | 'linkedin'
  | 'youtube' | 'tiktok' | 'pinterest' | 'email'
  | 'whatsapp' | 'telegram' | 'discord' | 'snapchat'
  | 'reddit' | 'github' | 'dribbble' | 'behance'
  | 'website';


type SocialIconStyle =
  | 'solid' | 'outlined' | 'rounded' | 'square' | 'circle' | 'plain';
type SocialIconTone = 'brand' | 'dark' | 'light';
type SocialIconSize = 'small' | 'medium' | 'large';
ts
interface MenuBlock extends BaseBlock {
  type: 'menu';
  items: MenuItemData[];
  fontSize: number;
  fontFamily?: string;
  color: string;
  linkColor?: string;
  textAlign: 'left' | 'center' | 'right';
  separator: string;
  separatorColor: string;
  spacing: number;
}

interface MenuItemData {
  id: string;
  text: string;
  url: string;
  openInNewTab: boolean;
  bold: boolean;
  underline: boolean;
  color?: string;
}

TableBlock ​

ts
interface TableBlock extends BaseBlock {
  type: 'table';
  rows: TableRowData[];
  hasHeaderRow: boolean;
  headerBackgroundColor?: string;
  borderColor: string;
  borderWidth: number;
  cellPadding: number;
  fontSize: number;
  fontFamily?: string;
  color: string;
  textAlign: 'left' | 'center' | 'right';
}

interface TableRowData {
  id: string;
  cells: TableCellData[];
}

interface TableCellData {
  id: string;
  content: string;
}

HtmlBlock ​

ts
interface HtmlBlock extends BaseBlock {
  type: 'html';
  content: string;          // Rohes HTML
}

CountdownBlock ​

Wird als animiertes GIF gerendert, was das Templatical-Cloud-Backend erfordert -- @templatical/renderer gibt stattdessen einen templatical:unrenderable-block-Marker aus. Siehe Blöcke ohne Renderer.

ts
interface CountdownBlock extends BaseBlock {
  type: 'countdown';
  targetDate: string;       // Lokales Datum/Uhrzeit, 'YYYY-MM-DDTHH:mm'
  timezone: string;         // IANA-Name, in dem das Ziel gelesen wird, z. B. 'Europe/Berlin'
  showDays: boolean;
  showHours: boolean;
  showMinutes: boolean;
  showSeconds: boolean;
  separator: ':' | '-' | ' ';
  digitFontSize: number;
  digitColor: string;
  labelColor: string;
  labelFontSize: number;
  backgroundColor: string;
  fontFamily?: string;
  labelDays: string;
  labelHours: string;
  labelMinutes: string;
  labelSeconds: string;
  expiredMessage: string;
  expiredImageUrl: string;  // Wird nach Ablauf statt des Timers angezeigt
  hideOnExpiry: boolean;
}

CustomBlock ​

ts
interface CustomBlock extends BaseBlock {
  type: 'custom';
  customType: string;
  fieldValues: Record<string, unknown>;
  renderedHtml?: string;
  renderedStylesheet?: string;
  dataSourceFetched?: boolean;
}

interface SlotBlock extends BaseBlock {
  type: 'slot';
}

interface WrapperBlock extends BaseBlock {
  type: 'wrapper';
  children: Block[];
  borderRadius?: number;
  backgroundImage?: BackgroundImage;
}

slot und wrapper sind Layout-Markierungen. Der Skill gibt sie nie aus. Siehe Layout.

Konfigurationstypen ​

MergeTag ​

ts
interface MergeTag {
  label: string;
  value: string;
  group?: string;        // nur Picker: Gruppierungslabel
  description?: string;  // nur Picker: Hilfetext
  sample?: string;       // nur Vorschau: Beispielwert; nie im MJML
}

interface MergeTagRequestContext {
  reason: 'insert' | 'edit';
  current?: MergeTag;    // beim Bearbeiten, wenn das Token zu tags passt
}

MergeTagsConfig ​

ts
interface MergeTagsConfig {
  syntax?: SyntaxPresetName | SyntaxPreset;
  tags?: MergeTag[];
  onRequest?: (context?: MergeTagRequestContext) => Promise<MergeTag | null>;
  showRawValue?: boolean;   // Roh-Token im Tooltip anzeigen (Standard true)
  autocomplete?: boolean;   // tippbasiertes Autocomplete (Standard true)
}

type SyntaxPresetName = 'liquid' | 'handlebars' | 'mailchimp' | 'ampscript';

DisplayCondition ​

ts
interface DisplayCondition {
  label: string;
  before: string;
  after: string;
  group?: string;
  description?: string;
}

DisplayConditionsConfig ​

ts
interface DisplayConditionsConfig {
  conditions: DisplayCondition[];
  allowCustom?: boolean;
}

CustomBlockDefinition ​

ts
interface CustomBlockDefinition {
  type: string;
  name: string;
  icon?: string;
  description?: string;
  fields: CustomBlockField[];
  template: string;                       // Liquid-Template
  dataSource?: DataSourceConfig;
  defaultStyles?: Partial<BlockStyles>;   // Stile für neue Instanzen
  stylesheet?: string;                    // CSS, einmal in mj-head emittiert
}

Details zu den Feldtypen finden Sie unter Benutzerdefinierte Blöcke.

ThemeOverrides ​

ts
interface ThemeOverrides {
  bg?: string;
  bgElevated?: string;
  bgHover?: string;
  bgActive?: string;
  border?: string;
  borderLight?: string;
  text?: string;
  textMuted?: string;
  textDim?: string;
  primary?: string;
  primaryHover?: string;
  primaryLight?: string;
  secondary?: string;
  secondaryHover?: string;
  secondaryLight?: string;
  success?: string;
  successLight?: string;
  warning?: string;
  warningLight?: string;
  danger?: string;
  dangerLight?: string;
  canvasBg?: string;
  dark?: Omit<ThemeOverrides, 'dark'>;
}

FontsConfig ​

ts
interface FontsConfig {
  defaultFallback?: string;
  defaultFont?: string;
  customFonts?: CustomFont[];
  builtIns?: boolean | string[];  // true/weggelassen: alle sieben; false: keine; string[]: Allowlist — siehe /de/guide/fonts
}

interface CustomFont {
  name: string;
  url: string;
  fallback?: string;
}

ViewportSize ​

ts
type ViewportSize = 'desktop' | 'mobile';

Factory-Funktionen ​

Die folgenden Block-Factory-Funktionen akzeptieren ein optionales Partial-Override-Objekt und geben einen vollständigen Block mit generierter ID zurück. createCustomBlockLiquid(liquidjs) wird ebenfalls für das Headless-Rendering benutzerdefinierter Blöcke exportiert; der Leitfaden für benutzerdefinierte Blöcke beschreibt den Vertrag und den optionalen liquidjs-Peer.

Die öffentliche Modulstruktur der Factory hat keine Deklarationsabhängigkeit von liquidjs. Mit dem tatsächlichen Modul bleibt dessen Liquid-Instanztyp erhalten:

ts
export interface CustomBlockLiquidModule {
  Liquid: new (...args: any[]) => any;
}

export declare function createCustomBlockLiquid<M extends CustomBlockLiquidModule>(module: M): InstanceType<M['Liquid']>;
ts
import {
  createTitleBlock,
  createParagraphBlock,
  createImageBlock,
  createButtonBlock,
  createSectionBlock,
  createBackgroundImage,
  createDividerBlock,
  createVideoBlock,
  createSpacerBlock,
  createSocialIconsBlock,
  createMenuBlock,
  createTableBlock,
  createHtmlBlock,
  createCountdownBlock,
  createCustomBlock,
  createBlock,
  cloneBlock,
  createDefaultTemplateContent,
  generateId,
} from '@templatical/types';

// Countdown-GIFs brauchen Cloud oder ein `blockRenderers.countdown`-Override; der OSS-Renderer gibt einen Platzhalter aus.

// Mit Standardwerten erstellen
const paragraph = createParagraphBlock();

// Mit Überschreibungen erstellen
const heading = createTitleBlock({
  content: '<h1>Hello</h1>',
  level: 1,
});

// Beliebigen Block per Typ-String erstellen
const block = createBlock('button');

// Ein Hintergrundbild einer Sektion aus seiner URL: zentriert, cover, nicht gekachelt
const section = createSectionBlock({
  backgroundImage: createBackgroundImage('https://cdn.example.com/hero.jpg'),
});

// Deep Clone mit neuen IDs, auch für verschachtelte Zeilen, Einträge und Icons
const copy = cloneBlock(existingBlock);

// Leeres Template
const template = createDefaultTemplateContent();

// Eine UUID generieren
const id = generateId();

Type Guards ​

ts
import {
  isTitle, isParagraph, isImage, isButton, isSection,
  isDivider, isVideo, isSpacer, isSocialIcons,
  isMenu, isTable, isHtml, isCountdown, isCustomBlock,
} from '@templatical/types';

if (isTitle(block)) {
  console.log(block.level); // TypeScript weiß, dass dies ein TitleBlock ist
}

EventEmitter ​

Ein typisierter Event-Emitter für Subscription-Muster.

ts
import { EventEmitter } from '@templatical/types';

type Events = {
  change: TemplateContent;
  select: string | null;
};

const emitter = new EventEmitter<Events>();

const unsubscribe = emitter.on('change', (content) => {
  console.log('Changed:', content);
});

emitter.emit('change', templateContent);
unsubscribe(); // Listener entfernen

Methoden ​

MethodeBeschreibung
on(event, handler)Abonnieren. Gibt eine Unsubscribe-Funktion zurück
off(event, handler)Einen bestimmten Handler abbestellen
emit(event, data)Ein Ereignis auslösen
removeAllListeners(event?)Alle Listener entfernen, optional für ein bestimmtes Ereignis
listenerCount(event)Anzahl der Listener für ein Ereignis

Speicherverträge ​

Template, TemplatePatch, SavedBlockInput, CommentsProvider, MediaProvider und die übrigen BYO-Speicherformen stehen unter Backend anbinden. Diese Seite ist das Blockmodell und die Editor-Config.

Operationen und Zusammenarbeit ​

Operation, einschließlich der Locale-Operationen, BlockParent, OperationErrorCode, OperationError, applyOperations, applyOperationsInPlace und getColumnCount beschreibt die Operations-Referenz. Der Collaboration-Vertrag — CollaborationProvider, CollaborationHandlers, OperationBatch, RevisionedBatch, CollaborationActor, CollaborationMember, BlockLock, BlockLockHolder, CollaborationConflict und CollaborationRemoteChange — steht unter Zusammenarbeit. Template trägt revision und locks für einen Speicher, der zusammenarbeitet.

KI ​

AiProvider, AiConversationProvider, die KI-Anfragen, Handler, Ergebnisse, AiChatMessage und AiMergeTags bilden den MIT-Provider-Vertrag in @templatical/types. KI beschreibt den Vertrag; die KI-API-Referenz beschreibt HTTP-Client, Server-Kit und Protokoll.

SdkError(message, statusCode?, code?, details?) trägt KI-Fehlercodes und optionale details. ThrottledError erweitert ihn um retryAfterMs, Status 429 und den Code rate_limited. invertOperations(content, ops) liefert die abgesicherte Umkehrung eines Batches; es liegt in @templatical/types und wird von @templatical/core erneut exportiert.