Deutsch
Typen-Referenz
@templatical/types stellt alle gemeinsamen TypeScript-Typen, Block-Factory-Funktionen und Hilfsfunktionen bereit.
bash
npm install @templatical/typesbash
pnpm add @templatical/typesbash
yarn add @templatical/typesbash
bun add @templatical/typesTemplate-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')
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';Basistypen
BaseBlock
Alle Blöcke erweitern dieses Interface.
ts
interface BaseBlock {
id: string;
type: string;
styles: BlockStyles;
visibility?: BlockVisibility;
displayCondition?: DisplayCondition;
}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';MenuBlock
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;
Tag: abstract new (...args: any[]) => any;
CycleTag: new (...args: any[]) => any;
Value: new (...args: any[]) => any;
toValue: (value: 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 entfernenMethoden
| Methode | Beschreibung |
|---|---|
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 und seine sechs Varianten, 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.