@templatical/types
Advanced tools
+564
-554
@@ -0,33 +1,34 @@ | ||
| //#region src/blocks.d.ts | ||
| interface SpacingValue { | ||
| top: number; | ||
| right: number; | ||
| bottom: number; | ||
| left: number; | ||
| top: number; | ||
| right: number; | ||
| bottom: number; | ||
| left: number; | ||
| } | ||
| interface BlockStyles { | ||
| padding: SpacingValue; | ||
| backgroundColor?: string; | ||
| padding: SpacingValue; | ||
| backgroundColor?: string; | ||
| } | ||
| interface BlockVisibility { | ||
| desktop: boolean; | ||
| mobile: boolean; | ||
| desktop: boolean; | ||
| mobile: boolean; | ||
| } | ||
| interface BaseBlock { | ||
| id: string; | ||
| type: string; | ||
| styles: BlockStyles; | ||
| visibility?: BlockVisibility; | ||
| displayCondition?: { | ||
| label: string; | ||
| before: string; | ||
| after: string; | ||
| group?: string; | ||
| description?: string; | ||
| }; | ||
| id: string; | ||
| type: string; | ||
| styles: BlockStyles; | ||
| visibility?: BlockVisibility; | ||
| displayCondition?: { | ||
| label: string; | ||
| before: string; | ||
| after: string; | ||
| group?: string; | ||
| description?: string; | ||
| }; | ||
| } | ||
| type ColumnLayout = "1" | "2" | "3" | "2-1" | "1-2"; | ||
| interface SectionBlock extends BaseBlock { | ||
| type: "section"; | ||
| columns: ColumnLayout; | ||
| children: Block[][]; | ||
| type: "section"; | ||
| columns: ColumnLayout; | ||
| children: Block[][]; | ||
| } | ||
@@ -37,52 +38,52 @@ type HeadingLevel = 1 | 2 | 3 | 4; | ||
| interface TitleBlock extends BaseBlock { | ||
| type: "title"; | ||
| content: string; | ||
| level: HeadingLevel; | ||
| color: string; | ||
| textAlign: "left" | "center" | "right"; | ||
| fontFamily?: string; | ||
| type: "title"; | ||
| content: string; | ||
| level: HeadingLevel; | ||
| color: string; | ||
| textAlign: "left" | "center" | "right"; | ||
| fontFamily?: string; | ||
| } | ||
| interface ParagraphBlock extends BaseBlock { | ||
| type: "paragraph"; | ||
| content: string; | ||
| type: "paragraph"; | ||
| content: string; | ||
| } | ||
| interface ImageBlock extends BaseBlock { | ||
| type: "image"; | ||
| src: string; | ||
| alt: string; | ||
| width: number | "full"; | ||
| align: "left" | "center" | "right"; | ||
| linkUrl?: string; | ||
| linkOpenInNewTab?: boolean; | ||
| placeholderUrl?: string; | ||
| decorative?: boolean; | ||
| type: "image"; | ||
| src: string; | ||
| alt: string; | ||
| width: number | "full"; | ||
| align: "left" | "center" | "right"; | ||
| linkUrl?: string; | ||
| linkOpenInNewTab?: boolean; | ||
| placeholderUrl?: string; | ||
| decorative?: boolean; | ||
| } | ||
| interface ButtonBlock extends BaseBlock { | ||
| type: "button"; | ||
| text: string; | ||
| url: string; | ||
| openInNewTab?: boolean; | ||
| backgroundColor: string; | ||
| textColor: string; | ||
| borderRadius: number; | ||
| fontSize: number; | ||
| buttonPadding: SpacingValue; | ||
| fontFamily?: string; | ||
| type: "button"; | ||
| text: string; | ||
| url: string; | ||
| openInNewTab?: boolean; | ||
| backgroundColor: string; | ||
| textColor: string; | ||
| borderRadius: number; | ||
| fontSize: number; | ||
| buttonPadding: SpacingValue; | ||
| fontFamily?: string; | ||
| } | ||
| interface DividerBlock extends BaseBlock { | ||
| type: "divider"; | ||
| lineStyle: "solid" | "dashed" | "dotted"; | ||
| color: string; | ||
| thickness: number; | ||
| width: number | "full"; | ||
| type: "divider"; | ||
| lineStyle: "solid" | "dashed" | "dotted"; | ||
| color: string; | ||
| thickness: number; | ||
| width: number | "full"; | ||
| } | ||
| interface VideoBlock extends BaseBlock { | ||
| type: "video"; | ||
| url: string; | ||
| openInNewTab?: boolean; | ||
| thumbnailUrl: string; | ||
| alt: string; | ||
| width: number | "full"; | ||
| align: "left" | "center" | "right"; | ||
| placeholderUrl?: string; | ||
| type: "video"; | ||
| url: string; | ||
| openInNewTab?: boolean; | ||
| thumbnailUrl: string; | ||
| alt: string; | ||
| width: number | "full"; | ||
| align: "left" | "center" | "right"; | ||
| placeholderUrl?: string; | ||
| } | ||
@@ -93,97 +94,98 @@ type SocialPlatform = "facebook" | "twitter" | "instagram" | "linkedin" | "youtube" | "tiktok" | "pinterest" | "email" | "whatsapp" | "telegram" | "discord" | "snapchat" | "reddit" | "github" | "dribbble" | "behance"; | ||
| interface SocialIcon { | ||
| id: string; | ||
| platform: SocialPlatform; | ||
| url: string; | ||
| id: string; | ||
| platform: SocialPlatform; | ||
| url: string; | ||
| } | ||
| interface SocialIconsBlock extends BaseBlock { | ||
| type: "social"; | ||
| icons: SocialIcon[]; | ||
| iconStyle: SocialIconStyle; | ||
| iconSize: SocialIconSize; | ||
| spacing: number; | ||
| align: "left" | "center" | "right"; | ||
| type: "social"; | ||
| icons: SocialIcon[]; | ||
| iconStyle: SocialIconStyle; | ||
| iconSize: SocialIconSize; | ||
| spacing: number; | ||
| align: "left" | "center" | "right"; | ||
| } | ||
| interface SpacerBlock extends BaseBlock { | ||
| type: "spacer"; | ||
| height: number; | ||
| type: "spacer"; | ||
| height: number; | ||
| } | ||
| interface HtmlBlock extends BaseBlock { | ||
| type: "html"; | ||
| content: string; | ||
| type: "html"; | ||
| content: string; | ||
| } | ||
| interface MenuItemData { | ||
| id: string; | ||
| text: string; | ||
| url: string; | ||
| openInNewTab: boolean; | ||
| bold: boolean; | ||
| underline: boolean; | ||
| color?: string; | ||
| id: string; | ||
| text: string; | ||
| url: string; | ||
| openInNewTab: boolean; | ||
| bold: boolean; | ||
| underline: boolean; | ||
| color?: string; | ||
| } | ||
| 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; | ||
| type: "menu"; | ||
| items: MenuItemData[]; | ||
| fontSize: number; | ||
| fontFamily?: string; | ||
| color: string; | ||
| linkColor?: string; | ||
| textAlign: "left" | "center" | "right"; | ||
| separator: string; | ||
| separatorColor: string; | ||
| spacing: number; | ||
| } | ||
| interface TableCellData { | ||
| id: string; | ||
| content: string; | ||
| id: string; | ||
| content: string; | ||
| } | ||
| interface TableRowData { | ||
| id: string; | ||
| cells: TableCellData[]; | ||
| id: string; | ||
| cells: TableCellData[]; | ||
| } | ||
| 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"; | ||
| 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 CountdownBlock extends BaseBlock { | ||
| type: "countdown"; | ||
| targetDate: string; | ||
| timezone: string; | ||
| 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; | ||
| hideOnExpiry: boolean; | ||
| type: "countdown"; | ||
| targetDate: string; | ||
| timezone: string; | ||
| 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; | ||
| hideOnExpiry: boolean; | ||
| } | ||
| interface CustomBlock extends BaseBlock { | ||
| type: "custom"; | ||
| customType: string; | ||
| fieldValues: Record<string, unknown>; | ||
| renderedHtml?: string; | ||
| dataSourceFetched?: boolean; | ||
| type: "custom"; | ||
| customType: string; | ||
| fieldValues: Record<string, unknown>; | ||
| renderedHtml?: string; | ||
| dataSourceFetched?: boolean; | ||
| } | ||
| type Block = SectionBlock | TitleBlock | ParagraphBlock | ImageBlock | ButtonBlock | DividerBlock | VideoBlock | SocialIconsBlock | SpacerBlock | HtmlBlock | MenuBlock | TableBlock | CountdownBlock | CustomBlock; | ||
| type BlockType = Block["type"]; | ||
| //#endregion | ||
| //#region src/guards.d.ts | ||
| declare function isSection(block: Block): block is SectionBlock; | ||
@@ -203,18 +205,19 @@ declare function isTitle(block: Block): block is TitleBlock; | ||
| declare function isCustomBlock(block: Block): block is CustomBlock; | ||
| //#endregion | ||
| //#region src/defaults.d.ts | ||
| type BlockDefaultsFor<T> = Partial<Omit<T, "id" | "type">>; | ||
| interface BlockDefaults { | ||
| title?: BlockDefaultsFor<TitleBlock>; | ||
| paragraph?: BlockDefaultsFor<ParagraphBlock>; | ||
| image?: BlockDefaultsFor<ImageBlock>; | ||
| button?: BlockDefaultsFor<ButtonBlock>; | ||
| divider?: BlockDefaultsFor<DividerBlock>; | ||
| section?: BlockDefaultsFor<SectionBlock>; | ||
| video?: BlockDefaultsFor<VideoBlock>; | ||
| social?: BlockDefaultsFor<SocialIconsBlock>; | ||
| spacer?: BlockDefaultsFor<SpacerBlock>; | ||
| html?: BlockDefaultsFor<HtmlBlock>; | ||
| menu?: BlockDefaultsFor<MenuBlock>; | ||
| table?: BlockDefaultsFor<TableBlock>; | ||
| countdown?: BlockDefaultsFor<CountdownBlock>; | ||
| title?: BlockDefaultsFor<TitleBlock>; | ||
| paragraph?: BlockDefaultsFor<ParagraphBlock>; | ||
| image?: BlockDefaultsFor<ImageBlock>; | ||
| button?: BlockDefaultsFor<ButtonBlock>; | ||
| divider?: BlockDefaultsFor<DividerBlock>; | ||
| section?: BlockDefaultsFor<SectionBlock>; | ||
| video?: BlockDefaultsFor<VideoBlock>; | ||
| social?: BlockDefaultsFor<SocialIconsBlock>; | ||
| spacer?: BlockDefaultsFor<SpacerBlock>; | ||
| html?: BlockDefaultsFor<HtmlBlock>; | ||
| menu?: BlockDefaultsFor<MenuBlock>; | ||
| table?: BlockDefaultsFor<TableBlock>; | ||
| countdown?: BlockDefaultsFor<CountdownBlock>; | ||
| } | ||
@@ -238,124 +241,125 @@ type TemplateDefaults = Partial<TemplateSettings>; | ||
| declare function deepMergeDefaults<T extends object>(base: T, overrides: Partial<T>): T; | ||
| //#endregion | ||
| //#region src/template.d.ts | ||
| interface TemplateSettings { | ||
| width: number; | ||
| backgroundColor: string; | ||
| fontFamily: string; | ||
| preheaderText?: string; | ||
| /** | ||
| * BCP-47 language code for the rendered email's `<html lang>`. Drives | ||
| * screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`. | ||
| */ | ||
| locale: string; | ||
| width: number; | ||
| backgroundColor: string; | ||
| fontFamily: string; | ||
| preheaderText?: string; | ||
| /** | ||
| * BCP-47 language code for the rendered email's `<html lang>`. Drives | ||
| * screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`. | ||
| */ | ||
| locale: string; | ||
| } | ||
| interface TemplateContent { | ||
| blocks: Block[]; | ||
| settings: TemplateSettings; | ||
| blocks: Block[]; | ||
| settings: TemplateSettings; | ||
| } | ||
| declare function createDefaultTemplateContent(defaultFontFamily?: string, templateDefaults?: TemplateDefaults): TemplateContent; | ||
| /** @see https://templatical.com/docs/v1/custom-blocks */ | ||
| //#endregion | ||
| //#region src/custom-blocks.d.ts | ||
| type CustomBlockFieldType = "text" | "textarea" | "image" | "color" | "number" | "select" | "boolean" | "repeatable"; | ||
| interface CustomBlockFieldBase { | ||
| key: string; | ||
| label: string; | ||
| required?: boolean; | ||
| placeholder?: string; | ||
| readOnly?: boolean; | ||
| key: string; | ||
| label: string; | ||
| required?: boolean; | ||
| placeholder?: string; | ||
| readOnly?: boolean; | ||
| } | ||
| interface CustomBlockTextField extends CustomBlockFieldBase { | ||
| type: "text"; | ||
| default?: string; | ||
| type: "text"; | ||
| default?: string; | ||
| } | ||
| interface CustomBlockTextareaField extends CustomBlockFieldBase { | ||
| type: "textarea"; | ||
| default?: string; | ||
| type: "textarea"; | ||
| default?: string; | ||
| } | ||
| interface CustomBlockImageField extends CustomBlockFieldBase { | ||
| type: "image"; | ||
| default?: string; | ||
| type: "image"; | ||
| default?: string; | ||
| } | ||
| interface CustomBlockColorField extends CustomBlockFieldBase { | ||
| type: "color"; | ||
| default?: string; | ||
| type: "color"; | ||
| default?: string; | ||
| } | ||
| interface CustomBlockNumberField extends CustomBlockFieldBase { | ||
| type: "number"; | ||
| default?: number; | ||
| min?: number; | ||
| max?: number; | ||
| step?: number; | ||
| type: "number"; | ||
| default?: number; | ||
| min?: number; | ||
| max?: number; | ||
| step?: number; | ||
| } | ||
| interface SelectOption { | ||
| label: string; | ||
| value: string; | ||
| label: string; | ||
| value: string; | ||
| } | ||
| interface CustomBlockSelectField extends CustomBlockFieldBase { | ||
| type: "select"; | ||
| options: SelectOption[]; | ||
| default?: string; | ||
| type: "select"; | ||
| options: SelectOption[]; | ||
| default?: string; | ||
| } | ||
| interface CustomBlockBooleanField extends CustomBlockFieldBase { | ||
| type: "boolean"; | ||
| default?: boolean; | ||
| type: "boolean"; | ||
| default?: boolean; | ||
| } | ||
| interface CustomBlockRepeatableField extends CustomBlockFieldBase { | ||
| type: "repeatable"; | ||
| fields: Exclude<CustomBlockField, CustomBlockRepeatableField>[]; | ||
| default?: Record<string, unknown>[]; | ||
| minItems?: number; | ||
| maxItems?: number; | ||
| type: "repeatable"; | ||
| fields: Exclude<CustomBlockField, CustomBlockRepeatableField>[]; | ||
| default?: Record<string, unknown>[]; | ||
| minItems?: number; | ||
| maxItems?: number; | ||
| } | ||
| type CustomBlockField = CustomBlockTextField | CustomBlockTextareaField | CustomBlockImageField | CustomBlockColorField | CustomBlockNumberField | CustomBlockSelectField | CustomBlockBooleanField | CustomBlockRepeatableField; | ||
| interface CustomBlockDefinition { | ||
| type: string; | ||
| name: string; | ||
| icon?: string; | ||
| description?: string; | ||
| fields: CustomBlockField[]; | ||
| template: string; | ||
| dataSource?: DataSourceConfig; | ||
| /** | ||
| * Default block styles applied when a new instance of this custom block is | ||
| * created. Deep-merged over the built-in defaults — only specify the fields | ||
| * you want to override. Controls both the editor canvas wrapper and the | ||
| * rendered MJML/email output. | ||
| * | ||
| * @example | ||
| * defaultStyles: { | ||
| * padding: { top: 0, right: 0, bottom: 0, left: 0 }, | ||
| * } | ||
| */ | ||
| defaultStyles?: Partial<BlockStyles>; | ||
| /** | ||
| * Optional CSS rules attached to this custom block definition. Emitted once | ||
| * (deduped across instances) into `<mj-head><mj-style>…</mj-style></mj-head>` | ||
| * in the rendered MJML, and adopted into the editor canvas (shadow root or | ||
| * light-DOM mount) so authored responsive/hover/font behavior previews | ||
| * inside the editor. | ||
| * | ||
| * Use this for media queries, hover states, or any CSS that should apply | ||
| * once per definition rather than per block instance. Class names are not | ||
| * scoped by the SDK — namespace them yourself (e.g. `.tplc-<type>-<el>`) to | ||
| * avoid collisions with other definitions or built-in editor styles. | ||
| * | ||
| * @example | ||
| * stylesheet: ` | ||
| * @media (max-width: 480px) { | ||
| * .tplc-image-text-cell { display: block !important; width: 100% !important; } | ||
| * } | ||
| * ` | ||
| */ | ||
| stylesheet?: string; | ||
| type: string; | ||
| name: string; | ||
| icon?: string; | ||
| description?: string; | ||
| fields: CustomBlockField[]; | ||
| template: string; | ||
| dataSource?: DataSourceConfig; | ||
| /** | ||
| * Default block styles applied when a new instance of this custom block is | ||
| * created. Deep-merged over the built-in defaults — only specify the fields | ||
| * you want to override. Controls both the editor canvas wrapper and the | ||
| * rendered MJML/email output. | ||
| * | ||
| * @example | ||
| * defaultStyles: { | ||
| * padding: { top: 0, right: 0, bottom: 0, left: 0 }, | ||
| * } | ||
| */ | ||
| defaultStyles?: Partial<BlockStyles>; | ||
| /** | ||
| * Optional CSS rules attached to this custom block definition. Emitted once | ||
| * (deduped across instances) into `<mj-head><mj-style>…</mj-style></mj-head>` | ||
| * in the rendered MJML, and adopted into the editor canvas (shadow root or | ||
| * light-DOM mount) so authored responsive/hover/font behavior previews | ||
| * inside the editor. | ||
| * | ||
| * Use this for media queries, hover states, or any CSS that should apply | ||
| * once per definition rather than per block instance. Class names are not | ||
| * scoped by the SDK — namespace them yourself (e.g. `.tplc-<type>-<el>`) to | ||
| * avoid collisions with other definitions or built-in editor styles. | ||
| * | ||
| * @example | ||
| * stylesheet: ` | ||
| * @media (max-width: 480px) { | ||
| * .tplc-image-text-cell { display: block !important; width: 100% !important; } | ||
| * } | ||
| * ` | ||
| */ | ||
| stylesheet?: string; | ||
| } | ||
| interface DataSourceFetchContext { | ||
| fieldValues: Record<string, unknown>; | ||
| blockId: string; | ||
| fieldValues: Record<string, unknown>; | ||
| blockId: string; | ||
| } | ||
| interface DataSourceConfig { | ||
| label: string; | ||
| onFetch: (context: DataSourceFetchContext) => Promise<Record<string, unknown> | null>; | ||
| label: string; | ||
| onFetch: (context: DataSourceFetchContext) => Promise<Record<string, unknown> | null>; | ||
| } | ||
| //#endregion | ||
| //#region src/factory.d.ts | ||
| declare function generateId(): string; | ||
@@ -378,110 +382,113 @@ declare function createTitleBlock(partial?: Partial<TitleBlock>): TitleBlock; | ||
| declare function cloneBlock(block: Block): Block; | ||
| //#endregion | ||
| //#region src/events.d.ts | ||
| declare class EventEmitter<TEvents extends Record<string, unknown> = Record<string, unknown>> { | ||
| private handlers; | ||
| on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): () => void; | ||
| off<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): void; | ||
| emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void; | ||
| removeAllListeners(event?: keyof TEvents): void; | ||
| listenerCount(event: keyof TEvents): number; | ||
| private handlers; | ||
| on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): () => void; | ||
| off<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): void; | ||
| emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void; | ||
| removeAllListeners(event?: keyof TEvents): void; | ||
| listenerCount(event: keyof TEvents): number; | ||
| } | ||
| //#endregion | ||
| //#region src/config.d.ts | ||
| type ViewportSize = "desktop" | "mobile"; | ||
| type UiTheme = "light" | "dark" | "auto"; | ||
| interface CustomFont { | ||
| name: string; | ||
| url: string; | ||
| fallback?: string; | ||
| name: string; | ||
| url: string; | ||
| fallback?: string; | ||
| } | ||
| interface FontsConfig { | ||
| defaultFallback?: string; | ||
| defaultFont?: string; | ||
| customFonts?: CustomFont[]; | ||
| defaultFallback?: string; | ||
| defaultFont?: string; | ||
| customFonts?: CustomFont[]; | ||
| } | ||
| interface ExportResult { | ||
| html: string; | ||
| mjml: string; | ||
| html: string; | ||
| mjml: string; | ||
| } | ||
| interface MergeTag { | ||
| label: string; | ||
| value: string; | ||
| /** | ||
| * Optional grouping label used by the built-in merge tag picker to | ||
| * section the list. When no tag in the configured array carries | ||
| * `group`, the picker renders a plain flat list with no headers. | ||
| * Ignored by the renderer and by typing-autocomplete. | ||
| */ | ||
| group?: string; | ||
| /** | ||
| * Optional helper text shown beneath the tag in the built-in merge | ||
| * tag picker. Not rendered anywhere else (toolbar, autocomplete, | ||
| * MJML output) and not stored on the inserted document node. | ||
| */ | ||
| description?: string; | ||
| label: string; | ||
| value: string; | ||
| /** | ||
| * Optional grouping label used by the built-in merge tag picker to | ||
| * section the list. When no tag in the configured array carries | ||
| * `group`, the picker renders a plain flat list with no headers. | ||
| * Ignored by the renderer and by typing-autocomplete. | ||
| */ | ||
| group?: string; | ||
| /** | ||
| * Optional helper text shown beneath the tag in the built-in merge | ||
| * tag picker. Not rendered anywhere else (toolbar, autocomplete, | ||
| * MJML output) and not stored on the inserted document node. | ||
| */ | ||
| description?: string; | ||
| } | ||
| interface MediaResult { | ||
| url: string; | ||
| alt?: string; | ||
| url: string; | ||
| alt?: string; | ||
| } | ||
| interface MergeTagsConfig { | ||
| syntax?: SyntaxPresetName | SyntaxPreset; | ||
| tags?: MergeTag[]; | ||
| onRequest?: () => Promise<MergeTag | null>; | ||
| /** | ||
| * Enables typing-based autocomplete in rich text fields. When the user | ||
| * types the syntax opener (e.g. `{{`), a popup lists matching `tags`. | ||
| * | ||
| * Defaults to `true`. Effective only when `tags` is non-empty AND | ||
| * `syntax` matches a built-in preset (custom regex syntaxes cannot be | ||
| * mapped to a trigger string and silently disable autocomplete). | ||
| */ | ||
| autocomplete?: boolean; | ||
| syntax?: SyntaxPresetName | SyntaxPreset; | ||
| tags?: MergeTag[]; | ||
| onRequest?: () => Promise<MergeTag | null>; | ||
| /** | ||
| * Enables typing-based autocomplete in rich text fields. When the user | ||
| * types the syntax opener (e.g. `{{`), a popup lists matching `tags`. | ||
| * | ||
| * Defaults to `true`. Effective only when `tags` is non-empty AND | ||
| * `syntax` matches a built-in preset (custom regex syntaxes cannot be | ||
| * mapped to a trigger string and silently disable autocomplete). | ||
| */ | ||
| autocomplete?: boolean; | ||
| } | ||
| interface DisplayCondition { | ||
| label: string; | ||
| before: string; | ||
| after: string; | ||
| group?: string; | ||
| description?: string; | ||
| label: string; | ||
| before: string; | ||
| after: string; | ||
| group?: string; | ||
| description?: string; | ||
| } | ||
| interface DisplayConditionsConfig { | ||
| conditions: DisplayCondition[]; | ||
| allowCustom?: boolean; | ||
| conditions: DisplayCondition[]; | ||
| allowCustom?: boolean; | ||
| } | ||
| 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">; | ||
| 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">; | ||
| } | ||
| declare class SdkError extends Error { | ||
| readonly statusCode?: number | undefined; | ||
| constructor(message: string, statusCode?: number | undefined); | ||
| get isNotFound(): boolean; | ||
| get isUnauthorized(): boolean; | ||
| get isServerError(): boolean; | ||
| readonly statusCode?: number | undefined; | ||
| constructor(message: string, statusCode?: number | undefined); | ||
| get isNotFound(): boolean; | ||
| get isUnauthorized(): boolean; | ||
| get isServerError(): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/merge-tags.d.ts | ||
| interface SyntaxPreset { | ||
| value: RegExp; | ||
| logic: RegExp; | ||
| value: RegExp; | ||
| logic: RegExp; | ||
| } | ||
@@ -505,63 +512,65 @@ type SyntaxPresetName = "liquid" | "handlebars" | "mailchimp" | "ampscript"; | ||
| declare function resolveHtmlLogicMergeTagLabels(html: string, syntax: SyntaxPreset): string; | ||
| //#endregion | ||
| //#region ../media-library/src/types.d.ts | ||
| type MediaCategory = "images" | "documents" | "videos" | "audio"; | ||
| interface MediaItem { | ||
| id: string; | ||
| filename: string; | ||
| mime_type: string; | ||
| size: number; | ||
| url: string; | ||
| small_url: string | null; | ||
| medium_url: string | null; | ||
| large_url: string | null; | ||
| folder_id: string | null; | ||
| conversions_generated: boolean; | ||
| width: number | null; | ||
| height: number | null; | ||
| alt_text: string; | ||
| created_at: string; | ||
| updated_at: string; | ||
| id: string; | ||
| filename: string; | ||
| mime_type: string; | ||
| size: number; | ||
| url: string; | ||
| small_url: string | null; | ||
| medium_url: string | null; | ||
| large_url: string | null; | ||
| folder_id: string | null; | ||
| conversions_generated: boolean; | ||
| width: number | null; | ||
| height: number | null; | ||
| alt_text: string; | ||
| created_at: string; | ||
| updated_at: string; | ||
| } | ||
| interface MediaRequestContext { | ||
| accept?: MediaCategory[]; | ||
| accept?: MediaCategory[]; | ||
| } | ||
| interface StorageInfo { | ||
| used_bytes: number; | ||
| limit_bytes: number; | ||
| used_bytes: number; | ||
| limit_bytes: number; | ||
| } | ||
| interface MediaCategoryData { | ||
| mime_types: string[]; | ||
| extensions: string[]; | ||
| mime_types: string[]; | ||
| extensions: string[]; | ||
| } | ||
| interface MediaConfig { | ||
| use_media_library: boolean; | ||
| categories: Record<string, MediaCategoryData>; | ||
| max_file_size: number; | ||
| use_media_library: boolean; | ||
| categories: Record<string, MediaCategoryData>; | ||
| max_file_size: number; | ||
| } | ||
| //#endregion | ||
| //#region src/cloud.d.ts | ||
| interface Template { | ||
| id: string; | ||
| content: TemplateContent; | ||
| id: string; | ||
| content: TemplateContent; | ||
| } | ||
| interface TemplateSnapshot { | ||
| id: string; | ||
| template_id: string; | ||
| content: TemplateContent; | ||
| is_autosave: boolean; | ||
| created_at: string; | ||
| id: string; | ||
| template_id: string; | ||
| content: TemplateContent; | ||
| is_autosave: boolean; | ||
| created_at: string; | ||
| } | ||
| interface Comment { | ||
| id: string; | ||
| template_id: string; | ||
| block_id: string | null; | ||
| parent_id: string | null; | ||
| body: string; | ||
| author_identifier: string; | ||
| author_name: string; | ||
| resolved_at: string | null; | ||
| resolved_by_identifier: string | null; | ||
| resolved_by_name: string | null; | ||
| created_at: string; | ||
| updated_at: string; | ||
| replies: Comment[]; | ||
| id: string; | ||
| template_id: string; | ||
| block_id: string | null; | ||
| parent_id: string | null; | ||
| body: string; | ||
| author_identifier: string; | ||
| author_name: string; | ||
| resolved_at: string | null; | ||
| resolved_by_identifier: string | null; | ||
| resolved_by_name: string | null; | ||
| created_at: string; | ||
| updated_at: string; | ||
| replies: Comment[]; | ||
| } | ||
@@ -571,11 +580,11 @@ type CommentThread = Comment; | ||
| interface CommentEvent { | ||
| type: CommentEventType; | ||
| comment: Comment; | ||
| type: CommentEventType; | ||
| comment: Comment; | ||
| } | ||
| interface SavedModule { | ||
| id: string; | ||
| name: string; | ||
| content: Block[]; | ||
| created_at: string; | ||
| updated_at: string; | ||
| id: string; | ||
| name: string; | ||
| content: Block[]; | ||
| created_at: string; | ||
| updated_at: string; | ||
| } | ||
@@ -585,251 +594,252 @@ type FindingSeverity = "high" | "medium" | "low"; | ||
| interface ScoringFinding { | ||
| id: string; | ||
| severity: FindingSeverity; | ||
| message: string; | ||
| blockId: string | null; | ||
| category: ScoringCategory; | ||
| suggestion: string; | ||
| id: string; | ||
| severity: FindingSeverity; | ||
| message: string; | ||
| blockId: string | null; | ||
| category: ScoringCategory; | ||
| suggestion: string; | ||
| } | ||
| interface CategoryScore { | ||
| score: number; | ||
| findings: ScoringFinding[]; | ||
| score: number; | ||
| findings: ScoringFinding[]; | ||
| } | ||
| interface ScoringResult { | ||
| score: number; | ||
| categories: Record<ScoringCategory, CategoryScore>; | ||
| score: number; | ||
| categories: Record<ScoringCategory, CategoryScore>; | ||
| } | ||
| interface HealthCheckResult { | ||
| api: { | ||
| ok: boolean; | ||
| latency: number; | ||
| }; | ||
| websocket: { | ||
| ok: boolean; | ||
| error?: string; | ||
| }; | ||
| auth: { | ||
| ok: boolean; | ||
| error?: string; | ||
| }; | ||
| overall: boolean; | ||
| api: { | ||
| ok: boolean; | ||
| latency: number; | ||
| }; | ||
| websocket: { | ||
| ok: boolean; | ||
| error?: string; | ||
| }; | ||
| auth: { | ||
| ok: boolean; | ||
| error?: string; | ||
| }; | ||
| overall: boolean; | ||
| } | ||
| interface TokenData { | ||
| token: string; | ||
| expires_at: number; | ||
| project_id: string; | ||
| tenant: string; | ||
| test_email?: { | ||
| allowed_emails: string[]; | ||
| signature: string; | ||
| }; | ||
| user?: { | ||
| id: string; | ||
| name: string; | ||
| signature: string; | ||
| }; | ||
| token: string; | ||
| expires_at: number; | ||
| project_id: string; | ||
| tenant: string; | ||
| test_email?: { | ||
| allowed_emails: string[]; | ||
| signature: string; | ||
| }; | ||
| user?: { | ||
| id: string; | ||
| name: string; | ||
| signature: string; | ||
| }; | ||
| } | ||
| interface AuthRequestOptions { | ||
| method?: "GET" | "POST"; | ||
| headers?: Record<string, string>; | ||
| body?: Record<string, unknown>; | ||
| credentials?: RequestCredentials; | ||
| method?: "GET" | "POST"; | ||
| headers?: Record<string, string>; | ||
| body?: Record<string, unknown>; | ||
| credentials?: RequestCredentials; | ||
| } | ||
| interface AuthConfig { | ||
| url: string; | ||
| baseUrl?: string; | ||
| requestOptions?: AuthRequestOptions; | ||
| onError?: (error: Error) => void; | ||
| url: string; | ||
| baseUrl?: string; | ||
| requestOptions?: AuthRequestOptions; | ||
| onError?: (error: Error) => void; | ||
| } | ||
| interface TestEmailConfig { | ||
| allowedEmails: string[]; | ||
| signature: string; | ||
| allowedEmails: string[]; | ||
| signature: string; | ||
| } | ||
| interface UserConfig { | ||
| id: string; | ||
| name: string; | ||
| signature: string; | ||
| id: string; | ||
| name: string; | ||
| signature: string; | ||
| } | ||
| interface DirectAuthConfig { | ||
| mode: "direct"; | ||
| clientId: string; | ||
| clientSecret: string; | ||
| tenant: string; | ||
| baseUrl?: string; | ||
| mode: "direct"; | ||
| clientId: string; | ||
| clientSecret: string; | ||
| tenant: string; | ||
| baseUrl?: string; | ||
| } | ||
| interface ProxyAuthConfig { | ||
| mode: "proxy"; | ||
| url: string; | ||
| baseUrl?: string; | ||
| requestOptions?: AuthRequestOptions; | ||
| mode: "proxy"; | ||
| url: string; | ||
| baseUrl?: string; | ||
| requestOptions?: AuthRequestOptions; | ||
| } | ||
| type SdkAuthConfig = DirectAuthConfig | ProxyAuthConfig; | ||
| interface Collaborator { | ||
| id: string; | ||
| name: string; | ||
| color: string; | ||
| selectedBlockId: string | null; | ||
| id: string; | ||
| name: string; | ||
| color: string; | ||
| selectedBlockId: string | null; | ||
| } | ||
| type McpOperation = "add_block" | "update_block" | "delete_block" | "move_block" | "update_settings" | "set_content" | "update_block_style"; | ||
| interface McpOperationPayload { | ||
| operation: McpOperation; | ||
| data: Record<string, unknown>; | ||
| timestamp: number; | ||
| operation: McpOperation; | ||
| data: Record<string, unknown>; | ||
| timestamp: number; | ||
| } | ||
| interface SaveResult { | ||
| templateId: string; | ||
| html: string; | ||
| mjml: string; | ||
| content: TemplateContent; | ||
| templateId: string; | ||
| html: string; | ||
| mjml: string; | ||
| content: TemplateContent; | ||
| } | ||
| interface AiConfig { | ||
| chat?: boolean; | ||
| scoring?: boolean; | ||
| designToTemplate?: boolean; | ||
| rewrite?: boolean; | ||
| chat?: boolean; | ||
| scoring?: boolean; | ||
| designToTemplate?: boolean; | ||
| rewrite?: boolean; | ||
| } | ||
| interface McpConfig { | ||
| enabled: boolean; | ||
| onOperation?: (payload: McpOperationPayload) => void; | ||
| enabled: boolean; | ||
| onOperation?: (payload: McpOperationPayload) => void; | ||
| } | ||
| interface CollaborationConfig { | ||
| enabled: boolean; | ||
| onCollaboratorJoined?: (collaborator: Collaborator) => void; | ||
| onCollaboratorLeft?: (collaborator: Collaborator) => void; | ||
| onBlockLocked?: (event: { | ||
| blockId: string; | ||
| collaborator: Collaborator; | ||
| }) => void; | ||
| onBlockUnlocked?: (event: { | ||
| blockId: string; | ||
| collaborator: Collaborator; | ||
| }) => void; | ||
| enabled: boolean; | ||
| onCollaboratorJoined?: (collaborator: Collaborator) => void; | ||
| onCollaboratorLeft?: (collaborator: Collaborator) => void; | ||
| onBlockLocked?: (event: { | ||
| blockId: string; | ||
| collaborator: Collaborator; | ||
| }) => void; | ||
| onBlockUnlocked?: (event: { | ||
| blockId: string; | ||
| collaborator: Collaborator; | ||
| }) => void; | ||
| } | ||
| interface WebSocketServerConfig { | ||
| host: string; | ||
| port: number; | ||
| app_key: string; | ||
| host: string; | ||
| port: number; | ||
| app_key: string; | ||
| } | ||
| interface TemplaticalConfig { | ||
| container: string | HTMLElement; | ||
| auth: Omit<AuthConfig, "onError">; | ||
| baseUrl?: string; | ||
| theme?: ThemeOverrides; | ||
| locale?: string; | ||
| ai?: AiConfig | false; | ||
| onCreate?: (template: Template) => void; | ||
| onLoad?: (template: Template) => void; | ||
| onSave?: (result: SaveResult) => void; | ||
| onError?: (error: Error) => void; | ||
| onUnmount?: () => void; | ||
| mergeTags?: MergeTagsConfig; | ||
| onRequestMedia?: (context: MediaRequestContext) => Promise<MediaItem | null>; | ||
| displayConditions?: DisplayConditionsConfig; | ||
| fonts?: FontsConfig; | ||
| autoSave?: boolean; | ||
| autoSaveDebounce?: number; | ||
| onBeforeTestEmail?: (html: string) => string | Promise<string>; | ||
| customBlocks?: CustomBlockDefinition[]; | ||
| commenting?: boolean; | ||
| onComment?: (event: CommentEvent) => void; | ||
| mcp?: McpConfig; | ||
| collaboration?: CollaborationConfig; | ||
| modules?: boolean; | ||
| container: string | HTMLElement; | ||
| auth: Omit<AuthConfig, "onError">; | ||
| baseUrl?: string; | ||
| theme?: ThemeOverrides; | ||
| locale?: string; | ||
| ai?: AiConfig | false; | ||
| onCreate?: (template: Template) => void; | ||
| onLoad?: (template: Template) => void; | ||
| onSave?: (result: SaveResult) => void; | ||
| onError?: (error: Error) => void; | ||
| onUnmount?: () => void; | ||
| mergeTags?: MergeTagsConfig; | ||
| onRequestMedia?: (context: MediaRequestContext) => Promise<MediaItem | null>; | ||
| displayConditions?: DisplayConditionsConfig; | ||
| fonts?: FontsConfig; | ||
| autoSave?: boolean; | ||
| autoSaveDebounce?: number; | ||
| onBeforeTestEmail?: (html: string) => string | Promise<string>; | ||
| customBlocks?: CustomBlockDefinition[]; | ||
| commenting?: boolean; | ||
| onComment?: (event: CommentEvent) => void; | ||
| mcp?: McpConfig; | ||
| collaboration?: CollaborationConfig; | ||
| modules?: boolean; | ||
| } | ||
| interface TemplaticalInstance { | ||
| setTheme(theme: ThemeOverrides): void; | ||
| create(content?: TemplateContent): Promise<Template>; | ||
| load(templateId: string): Promise<Template>; | ||
| save(): Promise<SaveResult>; | ||
| unmount(): void; | ||
| setTheme(theme: ThemeOverrides): void; | ||
| create(content?: TemplateContent): Promise<Template>; | ||
| load(templateId: string): Promise<Template>; | ||
| save(): Promise<SaveResult>; | ||
| unmount(): void; | ||
| } | ||
| interface EditorState { | ||
| template: Template | null; | ||
| content: TemplateContent; | ||
| selectedBlockId: string | null; | ||
| viewport: ViewportSize; | ||
| darkMode: boolean; | ||
| previewMode: boolean; | ||
| isDirty: boolean; | ||
| isSaving: boolean; | ||
| isLoading: boolean; | ||
| uiTheme: UiTheme; | ||
| template: Template | null; | ||
| content: TemplateContent; | ||
| selectedBlockId: string | null; | ||
| viewport: ViewportSize; | ||
| darkMode: boolean; | ||
| previewMode: boolean; | ||
| isDirty: boolean; | ||
| isSaving: boolean; | ||
| isLoading: boolean; | ||
| uiTheme: UiTheme; | ||
| } | ||
| interface ApiResponse<T> { | ||
| data: T; | ||
| data: T; | ||
| } | ||
| interface ApiError { | ||
| message: string; | ||
| errors?: Record<string, string[]>; | ||
| message: string; | ||
| errors?: Record<string, string[]>; | ||
| } | ||
| interface PlanFeatures { | ||
| media_folders: boolean; | ||
| import_from_url: boolean; | ||
| auto_save: boolean; | ||
| custom_fonts: boolean; | ||
| theme_customization: boolean; | ||
| html_block: boolean; | ||
| export_mjml: boolean; | ||
| white_label: boolean; | ||
| test_email: boolean; | ||
| ai_generation: boolean; | ||
| custom_blocks: boolean; | ||
| commenting: boolean; | ||
| collaboration: boolean; | ||
| saved_modules: boolean; | ||
| headless_sdk: boolean; | ||
| pluggable_media: boolean; | ||
| media_folders: boolean; | ||
| import_from_url: boolean; | ||
| auto_save: boolean; | ||
| custom_fonts: boolean; | ||
| theme_customization: boolean; | ||
| html_block: boolean; | ||
| export_mjml: boolean; | ||
| white_label: boolean; | ||
| test_email: boolean; | ||
| ai_generation: boolean; | ||
| custom_blocks: boolean; | ||
| commenting: boolean; | ||
| collaboration: boolean; | ||
| saved_modules: boolean; | ||
| headless_sdk: boolean; | ||
| pluggable_media: boolean; | ||
| } | ||
| interface PlanLimits { | ||
| max_file_size_mb: number; | ||
| max_templates: number | null; | ||
| media_categories: string[]; | ||
| storage_limit_bytes: number; | ||
| max_file_size_mb: number; | ||
| max_templates: number | null; | ||
| media_categories: string[]; | ||
| storage_limit_bytes: number; | ||
| } | ||
| interface PlanConfig { | ||
| features: PlanFeatures; | ||
| limits: PlanLimits; | ||
| template_count: number; | ||
| plan: string; | ||
| media: MediaConfig; | ||
| storage: StorageInfo; | ||
| websocket: WebSocketServerConfig; | ||
| accessibility?: { | ||
| blockOnError?: boolean; | ||
| }; | ||
| features: PlanFeatures; | ||
| limits: PlanLimits; | ||
| template_count: number; | ||
| plan: string; | ||
| media: MediaConfig; | ||
| storage: StorageInfo; | ||
| websocket: WebSocketServerConfig; | ||
| accessibility?: { | ||
| blockOnError?: boolean; | ||
| }; | ||
| } | ||
| interface AiChatMessage { | ||
| id: string; | ||
| role: "user" | "assistant"; | ||
| content: string; | ||
| timestamp: number; | ||
| id: string; | ||
| role: "user" | "assistant"; | ||
| content: string; | ||
| timestamp: number; | ||
| } | ||
| interface CreateCommentData { | ||
| body: string; | ||
| blockId?: string; | ||
| parentId?: string; | ||
| authorIdentifier: string; | ||
| authorName: string; | ||
| body: string; | ||
| blockId?: string; | ||
| parentId?: string; | ||
| authorIdentifier: string; | ||
| authorName: string; | ||
| } | ||
| interface UpdateCommentData { | ||
| body: string; | ||
| body: string; | ||
| } | ||
| interface AiGenerateOptions { | ||
| conversationId?: string; | ||
| conversationId?: string; | ||
| } | ||
| interface AiStreamEvent { | ||
| type: "text" | "done" | "error"; | ||
| text?: string; | ||
| content?: TemplateContent; | ||
| conversationId?: string; | ||
| error?: string; | ||
| type: "text" | "done" | "error"; | ||
| text?: string; | ||
| content?: TemplateContent; | ||
| conversationId?: string; | ||
| error?: string; | ||
| } | ||
| interface RewriteData { | ||
| text: string; | ||
| instruction: string; | ||
| blockId: string; | ||
| text: string; | ||
| instruction: string; | ||
| blockId: string; | ||
| } | ||
| interface AiScoreOptions { | ||
| fixFindingId?: string; | ||
| fixFindingId?: string; | ||
| } | ||
| //#endregion | ||
| export { type AiChatMessage, type AiConfig, type AiGenerateOptions, type AiScoreOptions, type AiStreamEvent, type ApiError, type ApiResponse, type AuthConfig, type AuthRequestOptions, BUTTON_BLOCK_DEFAULTS, type BaseBlock, type Block, type BlockDefaults, type BlockStyles, type BlockType, type BlockVisibility, type ButtonBlock, COUNTDOWN_BLOCK_DEFAULTS, type CategoryScore, type CollaborationConfig, type Collaborator, type ColumnLayout, type Comment, type CommentEvent, type CommentEventType, type CommentThread, type CountdownBlock, type CreateCommentData, type CustomBlock, type CustomBlockBooleanField, type CustomBlockColorField, type CustomBlockDefinition, type CustomBlockField, type CustomBlockFieldBase, type CustomBlockFieldType, type CustomBlockImageField, type CustomBlockNumberField, type CustomBlockRepeatableField, type CustomBlockSelectField, type CustomBlockTextField, type CustomBlockTextareaField, type CustomFont, DEFAULT_BLOCK_DEFAULTS, DEFAULT_TEMPLATE_DEFAULTS, DIVIDER_BLOCK_DEFAULTS, type DataSourceConfig, type DataSourceFetchContext, type DirectAuthConfig, type DisplayCondition, type DisplayConditionsConfig, type DividerBlock, type EditorState, EventEmitter, type ExportResult, type FindingSeverity, type FontsConfig, HEADING_LEVEL_FONT_SIZE, HTML_BLOCK_DEFAULTS, type HeadingLevel, type HealthCheckResult, type HtmlBlock, IMAGE_BLOCK_DEFAULTS, type ImageBlock, MENU_BLOCK_DEFAULTS, type McpConfig, type McpOperation, type McpOperationPayload, type MediaResult, type MenuBlock, type MenuItemData, type MergeTag, type MergeTagsConfig, PARAGRAPH_BLOCK_DEFAULTS, type ParagraphBlock, type PlanConfig, type PlanFeatures, type PlanLimits, type ProxyAuthConfig, type RewriteData, SECTION_BLOCK_DEFAULTS, SOCIAL_ICONS_BLOCK_DEFAULTS, SPACER_BLOCK_DEFAULTS, SYNTAX_PRESETS, type SaveResult, type SavedModule, type ScoringCategory, type ScoringFinding, type ScoringResult, type SdkAuthConfig, SdkError, type SectionBlock, type SelectOption, type SocialIcon, type SocialIconSize, type SocialIconStyle, type SocialIconsBlock, type SocialPlatform, type SpacerBlock, type SpacingValue, type SyntaxPreset, type SyntaxPresetName, TABLE_BLOCK_DEFAULTS, TITLE_BLOCK_DEFAULTS, type TableBlock, type TableCellData, type TableRowData, type Template, type TemplateContent, type TemplateDefaults, type TemplateSettings, type TemplateSnapshot, type TemplaticalConfig, type TemplaticalInstance, type TestEmailConfig, type ThemeOverrides, type TitleBlock, type TokenData, type UiTheme, type UpdateCommentData, type UserConfig, VIDEO_BLOCK_DEFAULTS, type VideoBlock, type ViewportSize, type WebSocketServerConfig, cloneBlock, containsMergeTag, createBlock, createButtonBlock, createCountdownBlock, createCustomBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createMenuBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTableBlock, createTitleBlock, createVideoBlock, deepMergeDefaults, generateId, getLogicMergeTagKeyword, getMergeTagLabel, getSyntaxTriggerChar, isButton, isCountdown, isCustomBlock, isDivider, isHtml, isImage, isLogicMergeTagValue, isMenu, isMergeTagValue, isParagraph, isSection, isSocialIcons, isSpacer, isTable, isTitle, isVideo, resolveHtmlLogicMergeTagLabels, resolveHtmlMergeTagLabels, resolveSyntax, restoreMergeTagMarkup }; | ||
| //# sourceMappingURL=index.d.ts.map |
+444
-574
@@ -1,697 +0,567 @@ | ||
| // src/blocks.ts | ||
| var HEADING_LEVEL_FONT_SIZE = { | ||
| 1: 36, | ||
| 2: 28, | ||
| 3: 22, | ||
| 4: 18 | ||
| //#region src/blocks.ts | ||
| const HEADING_LEVEL_FONT_SIZE = { | ||
| 1: 36, | ||
| 2: 28, | ||
| 3: 22, | ||
| 4: 18 | ||
| }; | ||
| // src/guards.ts | ||
| //#endregion | ||
| //#region src/guards.ts | ||
| function isSection(block) { | ||
| return block.type === "section"; | ||
| return block.type === "section"; | ||
| } | ||
| function isTitle(block) { | ||
| return block.type === "title"; | ||
| return block.type === "title"; | ||
| } | ||
| function isParagraph(block) { | ||
| return block.type === "paragraph"; | ||
| return block.type === "paragraph"; | ||
| } | ||
| function isImage(block) { | ||
| return block.type === "image"; | ||
| return block.type === "image"; | ||
| } | ||
| function isButton(block) { | ||
| return block.type === "button"; | ||
| return block.type === "button"; | ||
| } | ||
| function isDivider(block) { | ||
| return block.type === "divider"; | ||
| return block.type === "divider"; | ||
| } | ||
| function isVideo(block) { | ||
| return block.type === "video"; | ||
| return block.type === "video"; | ||
| } | ||
| function isSocialIcons(block) { | ||
| return block.type === "social"; | ||
| return block.type === "social"; | ||
| } | ||
| function isSpacer(block) { | ||
| return block.type === "spacer"; | ||
| return block.type === "spacer"; | ||
| } | ||
| function isHtml(block) { | ||
| return block.type === "html"; | ||
| return block.type === "html"; | ||
| } | ||
| function isMenu(block) { | ||
| return block.type === "menu"; | ||
| return block.type === "menu"; | ||
| } | ||
| function isTable(block) { | ||
| return block.type === "table"; | ||
| return block.type === "table"; | ||
| } | ||
| function isCountdown(block) { | ||
| return block.type === "countdown"; | ||
| return block.type === "countdown"; | ||
| } | ||
| function isCustomBlock(block) { | ||
| return block.type === "custom"; | ||
| return block.type === "custom"; | ||
| } | ||
| // src/defaults.ts | ||
| var TITLE_BLOCK_DEFAULTS = { | ||
| content: "<p>Enter your title</p>", | ||
| level: 2, | ||
| color: "#1a1a1a", | ||
| textAlign: "left" | ||
| //#endregion | ||
| //#region src/defaults.ts | ||
| const TITLE_BLOCK_DEFAULTS = { | ||
| content: "<p>Enter your title</p>", | ||
| level: 2, | ||
| color: "#1a1a1a", | ||
| textAlign: "left" | ||
| }; | ||
| var PARAGRAPH_BLOCK_DEFAULTS = { | ||
| content: "<p>Enter your text here</p>" | ||
| const PARAGRAPH_BLOCK_DEFAULTS = { content: "<p>Enter your text here</p>" }; | ||
| const IMAGE_BLOCK_DEFAULTS = { | ||
| src: "", | ||
| alt: "", | ||
| width: "full", | ||
| align: "center" | ||
| }; | ||
| var IMAGE_BLOCK_DEFAULTS = { | ||
| src: "", | ||
| alt: "", | ||
| width: "full", | ||
| align: "center" | ||
| const BUTTON_BLOCK_DEFAULTS = { | ||
| text: "Click Here", | ||
| url: "", | ||
| backgroundColor: "#333333", | ||
| textColor: "#ffffff", | ||
| borderRadius: 6, | ||
| fontSize: 15, | ||
| buttonPadding: { | ||
| top: 12, | ||
| right: 24, | ||
| bottom: 12, | ||
| left: 24 | ||
| } | ||
| }; | ||
| var BUTTON_BLOCK_DEFAULTS = { | ||
| text: "Click Here", | ||
| url: "", | ||
| backgroundColor: "#333333", | ||
| textColor: "#ffffff", | ||
| borderRadius: 6, | ||
| fontSize: 15, | ||
| buttonPadding: { top: 12, right: 24, bottom: 12, left: 24 } | ||
| const DIVIDER_BLOCK_DEFAULTS = { | ||
| lineStyle: "solid", | ||
| color: "#e0e0e0", | ||
| thickness: 1, | ||
| width: "full" | ||
| }; | ||
| var DIVIDER_BLOCK_DEFAULTS = { | ||
| lineStyle: "solid", | ||
| color: "#e0e0e0", | ||
| thickness: 1, | ||
| width: "full" | ||
| const SECTION_BLOCK_DEFAULTS = { columns: "1" }; | ||
| const VIDEO_BLOCK_DEFAULTS = { | ||
| url: "", | ||
| thumbnailUrl: "", | ||
| alt: "Video", | ||
| width: "full", | ||
| align: "center" | ||
| }; | ||
| var SECTION_BLOCK_DEFAULTS = { | ||
| columns: "1" | ||
| const SOCIAL_ICONS_BLOCK_DEFAULTS = { | ||
| iconStyle: "solid", | ||
| iconSize: "medium", | ||
| spacing: 10, | ||
| align: "center" | ||
| }; | ||
| var VIDEO_BLOCK_DEFAULTS = { | ||
| url: "", | ||
| thumbnailUrl: "", | ||
| alt: "Video", | ||
| width: "full", | ||
| align: "center" | ||
| const SPACER_BLOCK_DEFAULTS = { height: 24 }; | ||
| const HTML_BLOCK_DEFAULTS = { content: "" }; | ||
| const MENU_BLOCK_DEFAULTS = { | ||
| fontSize: 15, | ||
| color: "#1a1a1a", | ||
| textAlign: "center", | ||
| separator: "|", | ||
| separatorColor: "#e0e0e0", | ||
| spacing: 10 | ||
| }; | ||
| var SOCIAL_ICONS_BLOCK_DEFAULTS = { | ||
| iconStyle: "solid", | ||
| iconSize: "medium", | ||
| spacing: 10, | ||
| align: "center" | ||
| const TABLE_BLOCK_DEFAULTS = { | ||
| hasHeaderRow: true, | ||
| borderColor: "#e0e0e0", | ||
| borderWidth: 1, | ||
| cellPadding: 8, | ||
| fontSize: 15, | ||
| color: "#1a1a1a", | ||
| textAlign: "left" | ||
| }; | ||
| var SPACER_BLOCK_DEFAULTS = { | ||
| height: 24 | ||
| const COUNTDOWN_BLOCK_DEFAULTS = { | ||
| targetDate: "", | ||
| timezone: "UTC", | ||
| showDays: true, | ||
| showHours: true, | ||
| showMinutes: true, | ||
| showSeconds: true, | ||
| separator: ":", | ||
| digitFontSize: 32, | ||
| digitColor: "#1a1a1a", | ||
| labelColor: "#6b7280", | ||
| labelFontSize: 12, | ||
| backgroundColor: "#ffffff", | ||
| labelDays: "Days", | ||
| labelHours: "Hours", | ||
| labelMinutes: "Minutes", | ||
| labelSeconds: "Seconds", | ||
| expiredMessage: "This offer has expired", | ||
| expiredImageUrl: "", | ||
| hideOnExpiry: false | ||
| }; | ||
| var HTML_BLOCK_DEFAULTS = { | ||
| content: "" | ||
| const DEFAULT_BLOCK_DEFAULTS = { | ||
| title: TITLE_BLOCK_DEFAULTS, | ||
| paragraph: PARAGRAPH_BLOCK_DEFAULTS, | ||
| image: IMAGE_BLOCK_DEFAULTS, | ||
| button: BUTTON_BLOCK_DEFAULTS, | ||
| divider: DIVIDER_BLOCK_DEFAULTS, | ||
| section: SECTION_BLOCK_DEFAULTS, | ||
| video: VIDEO_BLOCK_DEFAULTS, | ||
| social: SOCIAL_ICONS_BLOCK_DEFAULTS, | ||
| spacer: SPACER_BLOCK_DEFAULTS, | ||
| html: HTML_BLOCK_DEFAULTS, | ||
| menu: MENU_BLOCK_DEFAULTS, | ||
| table: TABLE_BLOCK_DEFAULTS, | ||
| countdown: COUNTDOWN_BLOCK_DEFAULTS | ||
| }; | ||
| var MENU_BLOCK_DEFAULTS = { | ||
| fontSize: 15, | ||
| color: "#1a1a1a", | ||
| textAlign: "center", | ||
| separator: "|", | ||
| separatorColor: "#e0e0e0", | ||
| spacing: 10 | ||
| const DEFAULT_TEMPLATE_DEFAULTS = { | ||
| width: 600, | ||
| backgroundColor: "#ffffff", | ||
| fontFamily: "Arial", | ||
| locale: "en" | ||
| }; | ||
| var TABLE_BLOCK_DEFAULTS = { | ||
| hasHeaderRow: true, | ||
| borderColor: "#e0e0e0", | ||
| borderWidth: 1, | ||
| cellPadding: 8, | ||
| fontSize: 15, | ||
| color: "#1a1a1a", | ||
| textAlign: "left" | ||
| }; | ||
| var COUNTDOWN_BLOCK_DEFAULTS = { | ||
| targetDate: "", | ||
| timezone: "UTC", | ||
| showDays: true, | ||
| showHours: true, | ||
| showMinutes: true, | ||
| showSeconds: true, | ||
| separator: ":", | ||
| digitFontSize: 32, | ||
| digitColor: "#1a1a1a", | ||
| labelColor: "#6b7280", | ||
| labelFontSize: 12, | ||
| backgroundColor: "#ffffff", | ||
| labelDays: "Days", | ||
| labelHours: "Hours", | ||
| labelMinutes: "Minutes", | ||
| labelSeconds: "Seconds", | ||
| expiredMessage: "This offer has expired", | ||
| expiredImageUrl: "", | ||
| hideOnExpiry: false | ||
| }; | ||
| var DEFAULT_BLOCK_DEFAULTS = { | ||
| title: TITLE_BLOCK_DEFAULTS, | ||
| paragraph: PARAGRAPH_BLOCK_DEFAULTS, | ||
| image: IMAGE_BLOCK_DEFAULTS, | ||
| button: BUTTON_BLOCK_DEFAULTS, | ||
| divider: DIVIDER_BLOCK_DEFAULTS, | ||
| section: SECTION_BLOCK_DEFAULTS, | ||
| video: VIDEO_BLOCK_DEFAULTS, | ||
| social: SOCIAL_ICONS_BLOCK_DEFAULTS, | ||
| spacer: SPACER_BLOCK_DEFAULTS, | ||
| html: HTML_BLOCK_DEFAULTS, | ||
| menu: MENU_BLOCK_DEFAULTS, | ||
| table: TABLE_BLOCK_DEFAULTS, | ||
| countdown: COUNTDOWN_BLOCK_DEFAULTS | ||
| }; | ||
| var DEFAULT_TEMPLATE_DEFAULTS = { | ||
| width: 600, | ||
| backgroundColor: "#ffffff", | ||
| fontFamily: "Arial", | ||
| locale: "en" | ||
| }; | ||
| function isPlainObject(value) { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype; | ||
| return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype; | ||
| } | ||
| function deepMergeDefaults(base, overrides) { | ||
| const result = { ...base }; | ||
| for (const key of Object.keys(overrides)) { | ||
| const baseVal = base[key]; | ||
| const overrideVal = overrides[key]; | ||
| if (overrideVal === void 0) { | ||
| continue; | ||
| } | ||
| if (isPlainObject(baseVal) && isPlainObject(overrideVal)) { | ||
| result[key] = deepMergeDefaults( | ||
| baseVal, | ||
| overrideVal | ||
| ); | ||
| } else { | ||
| result[key] = overrideVal; | ||
| } | ||
| } | ||
| return result; | ||
| const result = { ...base }; | ||
| for (const key of Object.keys(overrides)) { | ||
| const baseVal = base[key]; | ||
| const overrideVal = overrides[key]; | ||
| if (overrideVal === void 0) continue; | ||
| if (isPlainObject(baseVal) && isPlainObject(overrideVal)) result[key] = deepMergeDefaults(baseVal, overrideVal); | ||
| else result[key] = overrideVal; | ||
| } | ||
| return result; | ||
| } | ||
| // src/template.ts | ||
| //#endregion | ||
| //#region src/template.ts | ||
| function createDefaultTemplateContent(defaultFontFamily = "Arial", templateDefaults) { | ||
| return { | ||
| blocks: [], | ||
| settings: { | ||
| ...DEFAULT_TEMPLATE_DEFAULTS, | ||
| fontFamily: defaultFontFamily, | ||
| ...templateDefaults | ||
| } | ||
| }; | ||
| return { | ||
| blocks: [], | ||
| settings: { | ||
| ...DEFAULT_TEMPLATE_DEFAULTS, | ||
| fontFamily: defaultFontFamily, | ||
| ...templateDefaults | ||
| } | ||
| }; | ||
| } | ||
| // src/factory.ts | ||
| //#endregion | ||
| //#region src/factory.ts | ||
| function applyDefaults(base, partial) { | ||
| if (!partial || Object.keys(partial).length === 0) return base; | ||
| return deepMergeDefaults(base, partial); | ||
| if (!partial || Object.keys(partial).length === 0) return base; | ||
| return deepMergeDefaults(base, partial); | ||
| } | ||
| function generateId() { | ||
| return crypto.randomUUID(); | ||
| return crypto.randomUUID(); | ||
| } | ||
| function createDefaultSpacing(value = 0) { | ||
| return { top: value, right: value, bottom: value, left: value }; | ||
| return { | ||
| top: value, | ||
| right: value, | ||
| bottom: value, | ||
| left: value | ||
| }; | ||
| } | ||
| function createDefaultStyles(padding = 10) { | ||
| return { | ||
| padding: createDefaultSpacing(padding) | ||
| }; | ||
| return { padding: createDefaultSpacing(padding) }; | ||
| } | ||
| function createTitleBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "title", | ||
| ...TITLE_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "title", | ||
| ...TITLE_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createParagraphBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "paragraph", | ||
| ...PARAGRAPH_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "paragraph", | ||
| ...PARAGRAPH_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createImageBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "image", | ||
| ...IMAGE_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "image", | ||
| ...IMAGE_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createButtonBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "button", | ||
| ...BUTTON_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "button", | ||
| ...BUTTON_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createDividerBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "divider", | ||
| ...DIVIDER_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles(20) | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "divider", | ||
| ...DIVIDER_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles(20) | ||
| }, partial); | ||
| } | ||
| function createSectionBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "section", | ||
| ...SECTION_BLOCK_DEFAULTS, | ||
| children: [[]], | ||
| styles: createDefaultStyles(20) | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "section", | ||
| ...SECTION_BLOCK_DEFAULTS, | ||
| children: [[]], | ||
| styles: createDefaultStyles(20) | ||
| }, partial); | ||
| } | ||
| function createVideoBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "video", | ||
| ...VIDEO_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "video", | ||
| ...VIDEO_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createSocialIconsBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "social", | ||
| icons: [], | ||
| ...SOCIAL_ICONS_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "social", | ||
| icons: [], | ||
| ...SOCIAL_ICONS_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createSpacerBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "spacer", | ||
| ...SPACER_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles(0) | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "spacer", | ||
| ...SPACER_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles(0) | ||
| }, partial); | ||
| } | ||
| function createHtmlBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "html", | ||
| ...HTML_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "html", | ||
| ...HTML_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createMenuBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "menu", | ||
| items: [], | ||
| ...MENU_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "menu", | ||
| items: [], | ||
| ...MENU_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createDefaultTableRows(columns, rows) { | ||
| return Array.from({ length: rows }, () => ({ | ||
| id: generateId(), | ||
| cells: Array.from( | ||
| { length: columns }, | ||
| () => ({ | ||
| id: generateId(), | ||
| content: "" | ||
| }) | ||
| ) | ||
| })); | ||
| return Array.from({ length: rows }, () => ({ | ||
| id: generateId(), | ||
| cells: Array.from({ length: columns }, () => ({ | ||
| id: generateId(), | ||
| content: "" | ||
| })) | ||
| })); | ||
| } | ||
| function createTableBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "table", | ||
| rows: createDefaultTableRows(3, 3), | ||
| ...TABLE_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "table", | ||
| rows: createDefaultTableRows(3, 3), | ||
| ...TABLE_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function createCountdownBlock(partial = {}) { | ||
| const base = { | ||
| id: generateId(), | ||
| type: "countdown", | ||
| ...COUNTDOWN_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }; | ||
| return applyDefaults(base, partial); | ||
| return applyDefaults({ | ||
| id: generateId(), | ||
| type: "countdown", | ||
| ...COUNTDOWN_BLOCK_DEFAULTS, | ||
| styles: createDefaultStyles() | ||
| }, partial); | ||
| } | ||
| function getFieldDefault(field) { | ||
| if (field.type === "repeatable") { | ||
| return field.default ?? []; | ||
| } | ||
| if (field.type === "boolean") { | ||
| return field.default ?? false; | ||
| } | ||
| if (field.type === "number") { | ||
| return field.default ?? 0; | ||
| } | ||
| return field.default ?? ""; | ||
| if (field.type === "repeatable") return field.default ?? []; | ||
| if (field.type === "boolean") return field.default ?? false; | ||
| if (field.type === "number") return field.default ?? 0; | ||
| return field.default ?? ""; | ||
| } | ||
| function createCustomBlock(definition) { | ||
| const fieldValues = {}; | ||
| for (const field of definition.fields) { | ||
| fieldValues[field.key] = getFieldDefault(field); | ||
| } | ||
| const styles = applyDefaults(createDefaultStyles(), definition.defaultStyles); | ||
| return { | ||
| id: generateId(), | ||
| type: "custom", | ||
| customType: definition.type, | ||
| fieldValues, | ||
| styles, | ||
| ...definition.dataSource ? { dataSourceFetched: false } : {} | ||
| }; | ||
| const fieldValues = {}; | ||
| for (const field of definition.fields) fieldValues[field.key] = getFieldDefault(field); | ||
| const styles = applyDefaults(createDefaultStyles(), definition.defaultStyles); | ||
| return { | ||
| id: generateId(), | ||
| type: "custom", | ||
| customType: definition.type, | ||
| fieldValues, | ||
| styles, | ||
| ...definition.dataSource ? { dataSourceFetched: false } : {} | ||
| }; | ||
| } | ||
| function createBlock(type, blockDefaults) { | ||
| switch (type) { | ||
| case "section": | ||
| return createSectionBlock(blockDefaults?.section); | ||
| case "title": | ||
| return createTitleBlock(blockDefaults?.title); | ||
| case "paragraph": | ||
| return createParagraphBlock(blockDefaults?.paragraph); | ||
| case "image": | ||
| return createImageBlock(blockDefaults?.image); | ||
| case "button": | ||
| return createButtonBlock(blockDefaults?.button); | ||
| case "divider": | ||
| return createDividerBlock(blockDefaults?.divider); | ||
| case "video": | ||
| return createVideoBlock(blockDefaults?.video); | ||
| case "social": | ||
| return createSocialIconsBlock(blockDefaults?.social); | ||
| case "spacer": | ||
| return createSpacerBlock(blockDefaults?.spacer); | ||
| case "html": | ||
| return createHtmlBlock(blockDefaults?.html); | ||
| case "menu": | ||
| return createMenuBlock(blockDefaults?.menu); | ||
| case "table": | ||
| return createTableBlock(blockDefaults?.table); | ||
| case "countdown": | ||
| return createCountdownBlock(blockDefaults?.countdown); | ||
| default: | ||
| throw new Error(`Unknown block type: ${type}`); | ||
| } | ||
| switch (type) { | ||
| case "section": return createSectionBlock(blockDefaults?.section); | ||
| case "title": return createTitleBlock(blockDefaults?.title); | ||
| case "paragraph": return createParagraphBlock(blockDefaults?.paragraph); | ||
| case "image": return createImageBlock(blockDefaults?.image); | ||
| case "button": return createButtonBlock(blockDefaults?.button); | ||
| case "divider": return createDividerBlock(blockDefaults?.divider); | ||
| case "video": return createVideoBlock(blockDefaults?.video); | ||
| case "social": return createSocialIconsBlock(blockDefaults?.social); | ||
| case "spacer": return createSpacerBlock(blockDefaults?.spacer); | ||
| case "html": return createHtmlBlock(blockDefaults?.html); | ||
| case "menu": return createMenuBlock(blockDefaults?.menu); | ||
| case "table": return createTableBlock(blockDefaults?.table); | ||
| case "countdown": return createCountdownBlock(blockDefaults?.countdown); | ||
| default: throw new Error(`Unknown block type: ${type}`); | ||
| } | ||
| } | ||
| function cloneBlock(block) { | ||
| const cloned = JSON.parse(JSON.stringify(block)); | ||
| cloned.id = generateId(); | ||
| if (cloned.type === "section") { | ||
| cloned.children = cloned.children.map( | ||
| (column) => column.map((child) => cloneBlock(child)) | ||
| ); | ||
| } | ||
| return cloned; | ||
| const cloned = JSON.parse(JSON.stringify(block)); | ||
| cloned.id = generateId(); | ||
| if (cloned.type === "section") cloned.children = cloned.children.map((column) => column.map((child) => cloneBlock(child))); | ||
| return cloned; | ||
| } | ||
| // src/events.ts | ||
| //#endregion | ||
| //#region src/events.ts | ||
| var EventEmitter = class { | ||
| handlers = /* @__PURE__ */ new Map(); | ||
| on(event, handler) { | ||
| if (!this.handlers.has(event)) { | ||
| this.handlers.set(event, /* @__PURE__ */ new Set()); | ||
| } | ||
| const set = this.handlers.get(event); | ||
| set.add(handler); | ||
| return () => { | ||
| set.delete(handler); | ||
| if (set.size === 0) { | ||
| this.handlers.delete(event); | ||
| } | ||
| }; | ||
| } | ||
| off(event, handler) { | ||
| const set = this.handlers.get(event); | ||
| if (!set) { | ||
| return; | ||
| } | ||
| set.delete(handler); | ||
| if (set.size === 0) { | ||
| this.handlers.delete(event); | ||
| } | ||
| } | ||
| emit(event, data) { | ||
| const set = this.handlers.get(event); | ||
| if (!set) { | ||
| return; | ||
| } | ||
| for (const handler of [...set]) { | ||
| handler(data); | ||
| } | ||
| } | ||
| removeAllListeners(event) { | ||
| if (event) { | ||
| this.handlers.delete(event); | ||
| } else { | ||
| this.handlers.clear(); | ||
| } | ||
| } | ||
| listenerCount(event) { | ||
| return this.handlers.get(event)?.size ?? 0; | ||
| } | ||
| handlers = /* @__PURE__ */ new Map(); | ||
| on(event, handler) { | ||
| if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set()); | ||
| const set = this.handlers.get(event); | ||
| set.add(handler); | ||
| return () => { | ||
| set.delete(handler); | ||
| if (set.size === 0) this.handlers.delete(event); | ||
| }; | ||
| } | ||
| off(event, handler) { | ||
| const set = this.handlers.get(event); | ||
| if (!set) return; | ||
| set.delete(handler); | ||
| if (set.size === 0) this.handlers.delete(event); | ||
| } | ||
| emit(event, data) { | ||
| const set = this.handlers.get(event); | ||
| if (!set) return; | ||
| for (const handler of [...set]) handler(data); | ||
| } | ||
| removeAllListeners(event) { | ||
| if (event) this.handlers.delete(event); | ||
| else this.handlers.clear(); | ||
| } | ||
| listenerCount(event) { | ||
| return this.handlers.get(event)?.size ?? 0; | ||
| } | ||
| }; | ||
| // src/merge-tags.ts | ||
| var SYNTAX_PRESETS = { | ||
| liquid: { value: /\{\{.+?\}\}/g, logic: /\{%-?\s*(\w+).*?-?%\}/g }, | ||
| handlebars: { | ||
| value: /\{\{\{?.+?\}?\}\}/g, | ||
| logic: /\{\{[#/](\w+).*?\}\}/g | ||
| }, | ||
| mailchimp: { value: /\*\|\w+\|\*/g, logic: /\*\|(\w+)[:|].*?\|\*/g }, | ||
| ampscript: { value: /%%=.+?=%%/g, logic: /%%\[\s*(\w+).*?\]%%/g } | ||
| //#endregion | ||
| //#region src/merge-tags.ts | ||
| const SYNTAX_PRESETS = { | ||
| liquid: { | ||
| value: /\{\{.+?\}\}/g, | ||
| logic: /\{%-?\s*(\w+).*?-?%\}/g | ||
| }, | ||
| handlebars: { | ||
| value: /\{\{\{?.+?\}?\}\}/g, | ||
| logic: /\{\{[#/](\w+).*?\}\}/g | ||
| }, | ||
| mailchimp: { | ||
| value: /\*\|\w+\|\*/g, | ||
| logic: /\*\|(\w+)[:|].*?\|\*/g | ||
| }, | ||
| ampscript: { | ||
| value: /%%=.+?=%%/g, | ||
| logic: /%%\[\s*(\w+).*?\]%%/g | ||
| } | ||
| }; | ||
| var SYNTAX_TRIGGER_CHARS = { | ||
| liquid: "{{", | ||
| handlebars: "{{", | ||
| mailchimp: "*|", | ||
| ampscript: "%%=" | ||
| const SYNTAX_TRIGGER_CHARS = { | ||
| liquid: "{{", | ||
| handlebars: "{{", | ||
| mailchimp: "*|", | ||
| ampscript: "%%=" | ||
| }; | ||
| /** | ||
| * Resolves the autocomplete trigger string for a syntax preset. | ||
| * Returns null when the syntax doesn't match any built-in preset | ||
| * (custom regex syntax — autocomplete cannot be enabled safely). | ||
| */ | ||
| function getSyntaxTriggerChar(syntax) { | ||
| for (const name of Object.keys(SYNTAX_PRESETS)) { | ||
| if (SYNTAX_PRESETS[name].value.source === syntax.value.source) { | ||
| return SYNTAX_TRIGGER_CHARS[name]; | ||
| } | ||
| } | ||
| return null; | ||
| for (const name of Object.keys(SYNTAX_PRESETS)) if (SYNTAX_PRESETS[name].value.source === syntax.value.source) return SYNTAX_TRIGGER_CHARS[name]; | ||
| return null; | ||
| } | ||
| function resolveSyntax(syntax) { | ||
| if (!syntax) { | ||
| return SYNTAX_PRESETS.liquid; | ||
| } | ||
| if (typeof syntax === "string") { | ||
| return SYNTAX_PRESETS[syntax] ?? SYNTAX_PRESETS.liquid; | ||
| } | ||
| return syntax; | ||
| if (!syntax) return SYNTAX_PRESETS.liquid; | ||
| if (typeof syntax === "string") return SYNTAX_PRESETS[syntax] ?? SYNTAX_PRESETS.liquid; | ||
| return syntax; | ||
| } | ||
| function escapeRegExp(str) { | ||
| return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
| function anchoredRegex(pattern) { | ||
| const source = pattern.source; | ||
| const flags = pattern.flags.replace("g", ""); | ||
| return new RegExp(`^${source}$`, flags); | ||
| const source = pattern.source; | ||
| const flags = pattern.flags.replace("g", ""); | ||
| return new RegExp(`^${source}$`, flags); | ||
| } | ||
| function isMergeTagValue(value, syntax) { | ||
| const trimmed = value?.trim() || ""; | ||
| if (anchoredRegex(syntax.logic).test(trimmed)) { | ||
| return false; | ||
| } | ||
| return anchoredRegex(syntax.value).test(trimmed); | ||
| const trimmed = value?.trim() || ""; | ||
| if (anchoredRegex(syntax.logic).test(trimmed)) return false; | ||
| return anchoredRegex(syntax.value).test(trimmed); | ||
| } | ||
| function getMergeTagLabel(value, mergeTags) { | ||
| const found = mergeTags.find((p) => p.value === value); | ||
| if (found) { | ||
| return found.label; | ||
| } | ||
| return value; | ||
| const found = mergeTags.find((p) => p.value === value); | ||
| if (found) return found.label; | ||
| return value; | ||
| } | ||
| function resolveHtmlMergeTagLabels(html, mergeTags) { | ||
| return rewriteSpanByAttr( | ||
| html, | ||
| "data-merge-tag", | ||
| (value) => getMergeTagLabel(value, mergeTags) | ||
| ); | ||
| return rewriteSpanByAttr(html, "data-merge-tag", (value) => getMergeTagLabel(value, mergeTags)); | ||
| } | ||
| function containsMergeTag(value, syntax) { | ||
| if (!value) return false; | ||
| const valueRegex = new RegExp(syntax.value.source, syntax.value.flags); | ||
| const logicRegex = new RegExp(syntax.logic.source, syntax.logic.flags); | ||
| return valueRegex.test(value) || logicRegex.test(value); | ||
| if (!value) return false; | ||
| const valueRegex = new RegExp(syntax.value.source, syntax.value.flags); | ||
| const logicRegex = new RegExp(syntax.logic.source, syntax.logic.flags); | ||
| return valueRegex.test(value) || logicRegex.test(value); | ||
| } | ||
| function restoreMergeTagMarkup(html, mergeTags, syntax) { | ||
| let result = html; | ||
| for (const tag of mergeTags) { | ||
| const escaped = escapeRegExp(tag.value); | ||
| const pattern = new RegExp(`(?<!data-merge-tag=")${escaped}`, "g"); | ||
| result = result.replace(pattern, (match) => { | ||
| const label = getMergeTagLabel(match, mergeTags); | ||
| return `<span data-merge-tag="${match}">${label}</span>`; | ||
| }); | ||
| } | ||
| const logicRegex = new RegExp( | ||
| `(?<!data-logic-merge-tag=")${syntax.logic.source}`, | ||
| syntax.logic.flags | ||
| ); | ||
| result = result.replace(logicRegex, (match) => { | ||
| const keyword = getLogicMergeTagKeyword(match, syntax); | ||
| return `<span data-logic-merge-tag="${match}">${keyword}</span>`; | ||
| }); | ||
| return result; | ||
| let result = html; | ||
| for (const tag of mergeTags) { | ||
| const escaped = escapeRegExp(tag.value); | ||
| const pattern = new RegExp(`(?<!data-merge-tag=")${escaped}`, "g"); | ||
| result = result.replace(pattern, (match) => { | ||
| return `<span data-merge-tag="${match}">${getMergeTagLabel(match, mergeTags)}</span>`; | ||
| }); | ||
| } | ||
| const logicRegex = new RegExp(`(?<!data-logic-merge-tag=")${syntax.logic.source}`, syntax.logic.flags); | ||
| result = result.replace(logicRegex, (match) => { | ||
| return `<span data-logic-merge-tag="${match}">${getLogicMergeTagKeyword(match, syntax)}</span>`; | ||
| }); | ||
| return result; | ||
| } | ||
| function isLogicMergeTagValue(value, syntax) { | ||
| return anchoredRegex(syntax.logic).test(value?.trim() || ""); | ||
| return anchoredRegex(syntax.logic).test(value?.trim() || ""); | ||
| } | ||
| function getLogicMergeTagKeyword(value, syntax) { | ||
| const regex = new RegExp( | ||
| syntax.logic.source, | ||
| syntax.logic.flags.replace("g", "") | ||
| ); | ||
| const match = value.match(regex); | ||
| return match && match[1] ? match[1].toUpperCase() : value; | ||
| const regex = new RegExp(syntax.logic.source, syntax.logic.flags.replace("g", "")); | ||
| const match = value.match(regex); | ||
| return match && match[1] ? match[1].toUpperCase() : value; | ||
| } | ||
| function resolveHtmlLogicMergeTagLabels(html, syntax) { | ||
| return rewriteSpanByAttr( | ||
| html, | ||
| "data-logic-merge-tag", | ||
| (value) => getLogicMergeTagKeyword(value, syntax) | ||
| ); | ||
| return rewriteSpanByAttr(html, "data-logic-merge-tag", (value) => getLogicMergeTagKeyword(value, syntax)); | ||
| } | ||
| /** | ||
| * Walk `html` and rewrite the inner text of every `<span … {attrName}="…">…</span>` | ||
| * by passing the attribute value through `relabel`. Linear in `html.length`: | ||
| * each `indexOf` advances the cursor monotonically, and no regex backtracking | ||
| * can run over the whole string. | ||
| * | ||
| * Replaces the original `/<span[^>]*…[^>]*>(.*?)<\/span>/g` pattern, which | ||
| * was polynomial-ReDoS over inputs that contained many `<span` starts with | ||
| * no closing `>`. | ||
| */ | ||
| function rewriteSpanByAttr(html, attrName, relabel) { | ||
| const attrPattern = new RegExp(`(?:^|\\s)${attrName}="([^"<>]*)"`); | ||
| let out = ""; | ||
| let i = 0; | ||
| while (i < html.length) { | ||
| const open = html.indexOf("<span", i); | ||
| if (open === -1) { | ||
| out += html.substring(i); | ||
| break; | ||
| } | ||
| const afterTagName = html[open + 5]; | ||
| if (afterTagName !== ">" && afterTagName !== " " && afterTagName !== " " && afterTagName !== "\n" && afterTagName !== "\r" && afterTagName !== "/") { | ||
| out += html.substring(i, open + 5); | ||
| i = open + 5; | ||
| continue; | ||
| } | ||
| const openEnd = html.indexOf(">", open + 5); | ||
| if (openEnd === -1) { | ||
| out += html.substring(i); | ||
| break; | ||
| } | ||
| const closeStart = html.indexOf("</span>", openEnd + 1); | ||
| if (closeStart === -1) { | ||
| out += html.substring(i); | ||
| break; | ||
| } | ||
| const attrs = html.substring(open + 5, openEnd); | ||
| const attrMatch = attrPattern.exec(attrs); | ||
| if (!attrMatch) { | ||
| out += html.substring(i, open + 5); | ||
| i = open + 5; | ||
| continue; | ||
| } | ||
| const value = attrMatch[1]; | ||
| const newLabel = relabel(value); | ||
| out += html.substring(i, openEnd + 1); | ||
| out += newLabel; | ||
| out += "</span>"; | ||
| i = closeStart + 7; | ||
| } | ||
| return out; | ||
| const attrPattern = new RegExp(`(?:^|\\s)${attrName}="([^"<>]*)"`); | ||
| let out = ""; | ||
| let i = 0; | ||
| while (i < html.length) { | ||
| const open = html.indexOf("<span", i); | ||
| if (open === -1) { | ||
| out += html.substring(i); | ||
| break; | ||
| } | ||
| const afterTagName = html[open + 5]; | ||
| if (afterTagName !== ">" && afterTagName !== " " && afterTagName !== " " && afterTagName !== "\n" && afterTagName !== "\r" && afterTagName !== "/") { | ||
| out += html.substring(i, open + 5); | ||
| i = open + 5; | ||
| continue; | ||
| } | ||
| const openEnd = html.indexOf(">", open + 5); | ||
| if (openEnd === -1) { | ||
| out += html.substring(i); | ||
| break; | ||
| } | ||
| const closeStart = html.indexOf("</span>", openEnd + 1); | ||
| if (closeStart === -1) { | ||
| out += html.substring(i); | ||
| break; | ||
| } | ||
| const attrs = html.substring(open + 5, openEnd); | ||
| const attrMatch = attrPattern.exec(attrs); | ||
| if (!attrMatch) { | ||
| out += html.substring(i, open + 5); | ||
| i = open + 5; | ||
| continue; | ||
| } | ||
| const value = attrMatch[1]; | ||
| const newLabel = relabel(value); | ||
| out += html.substring(i, openEnd + 1); | ||
| out += newLabel; | ||
| out += "</span>"; | ||
| i = closeStart + 7; | ||
| } | ||
| return out; | ||
| } | ||
| // src/config.ts | ||
| //#endregion | ||
| //#region src/config.ts | ||
| var SdkError = class extends Error { | ||
| constructor(message, statusCode) { | ||
| super(message); | ||
| this.statusCode = statusCode; | ||
| this.name = "SdkError"; | ||
| } | ||
| statusCode; | ||
| get isNotFound() { | ||
| return this.statusCode === 404; | ||
| } | ||
| get isUnauthorized() { | ||
| return this.statusCode === 401; | ||
| } | ||
| get isServerError() { | ||
| return this.statusCode !== void 0 && this.statusCode >= 500; | ||
| } | ||
| statusCode; | ||
| constructor(message, statusCode) { | ||
| super(message); | ||
| this.statusCode = statusCode; | ||
| this.name = "SdkError"; | ||
| } | ||
| get isNotFound() { | ||
| return this.statusCode === 404; | ||
| } | ||
| get isUnauthorized() { | ||
| return this.statusCode === 401; | ||
| } | ||
| get isServerError() { | ||
| return this.statusCode !== void 0 && this.statusCode >= 500; | ||
| } | ||
| }; | ||
| export { | ||
| BUTTON_BLOCK_DEFAULTS, | ||
| COUNTDOWN_BLOCK_DEFAULTS, | ||
| DEFAULT_BLOCK_DEFAULTS, | ||
| DEFAULT_TEMPLATE_DEFAULTS, | ||
| DIVIDER_BLOCK_DEFAULTS, | ||
| EventEmitter, | ||
| HEADING_LEVEL_FONT_SIZE, | ||
| HTML_BLOCK_DEFAULTS, | ||
| IMAGE_BLOCK_DEFAULTS, | ||
| MENU_BLOCK_DEFAULTS, | ||
| PARAGRAPH_BLOCK_DEFAULTS, | ||
| SECTION_BLOCK_DEFAULTS, | ||
| SOCIAL_ICONS_BLOCK_DEFAULTS, | ||
| SPACER_BLOCK_DEFAULTS, | ||
| SYNTAX_PRESETS, | ||
| SdkError, | ||
| TABLE_BLOCK_DEFAULTS, | ||
| TITLE_BLOCK_DEFAULTS, | ||
| VIDEO_BLOCK_DEFAULTS, | ||
| cloneBlock, | ||
| containsMergeTag, | ||
| createBlock, | ||
| createButtonBlock, | ||
| createCountdownBlock, | ||
| createCustomBlock, | ||
| createDefaultTemplateContent, | ||
| createDividerBlock, | ||
| createHtmlBlock, | ||
| createImageBlock, | ||
| createMenuBlock, | ||
| createParagraphBlock, | ||
| createSectionBlock, | ||
| createSocialIconsBlock, | ||
| createSpacerBlock, | ||
| createTableBlock, | ||
| createTitleBlock, | ||
| createVideoBlock, | ||
| deepMergeDefaults, | ||
| generateId, | ||
| getLogicMergeTagKeyword, | ||
| getMergeTagLabel, | ||
| getSyntaxTriggerChar, | ||
| isButton, | ||
| isCountdown, | ||
| isCustomBlock, | ||
| isDivider, | ||
| isHtml, | ||
| isImage, | ||
| isLogicMergeTagValue, | ||
| isMenu, | ||
| isMergeTagValue, | ||
| isParagraph, | ||
| isSection, | ||
| isSocialIcons, | ||
| isSpacer, | ||
| isTable, | ||
| isTitle, | ||
| isVideo, | ||
| resolveHtmlLogicMergeTagLabels, | ||
| resolveHtmlMergeTagLabels, | ||
| resolveSyntax, | ||
| restoreMergeTagMarkup | ||
| }; | ||
| //#endregion | ||
| export { BUTTON_BLOCK_DEFAULTS, COUNTDOWN_BLOCK_DEFAULTS, DEFAULT_BLOCK_DEFAULTS, DEFAULT_TEMPLATE_DEFAULTS, DIVIDER_BLOCK_DEFAULTS, EventEmitter, HEADING_LEVEL_FONT_SIZE, HTML_BLOCK_DEFAULTS, IMAGE_BLOCK_DEFAULTS, MENU_BLOCK_DEFAULTS, PARAGRAPH_BLOCK_DEFAULTS, SECTION_BLOCK_DEFAULTS, SOCIAL_ICONS_BLOCK_DEFAULTS, SPACER_BLOCK_DEFAULTS, SYNTAX_PRESETS, SdkError, TABLE_BLOCK_DEFAULTS, TITLE_BLOCK_DEFAULTS, VIDEO_BLOCK_DEFAULTS, cloneBlock, containsMergeTag, createBlock, createButtonBlock, createCountdownBlock, createCustomBlock, createDefaultTemplateContent, createDividerBlock, createHtmlBlock, createImageBlock, createMenuBlock, createParagraphBlock, createSectionBlock, createSocialIconsBlock, createSpacerBlock, createTableBlock, createTitleBlock, createVideoBlock, deepMergeDefaults, generateId, getLogicMergeTagKeyword, getMergeTagLabel, getSyntaxTriggerChar, isButton, isCountdown, isCustomBlock, isDivider, isHtml, isImage, isLogicMergeTagValue, isMenu, isMergeTagValue, isParagraph, isSection, isSocialIcons, isSpacer, isTable, isTitle, isVideo, resolveHtmlLogicMergeTagLabels, resolveHtmlMergeTagLabels, resolveSyntax, restoreMergeTagMarkup }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/blocks.ts","../src/guards.ts","../src/defaults.ts","../src/template.ts","../src/factory.ts","../src/events.ts","../src/merge-tags.ts","../src/config.ts"],"sourcesContent":["export interface SpacingValue {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport interface BlockStyles {\n padding: SpacingValue;\n backgroundColor?: string;\n}\n\nexport interface BlockVisibility {\n desktop: boolean;\n mobile: boolean;\n}\n\nexport interface BaseBlock {\n id: string;\n type: string;\n styles: BlockStyles;\n visibility?: BlockVisibility;\n displayCondition?: {\n label: string;\n before: string;\n after: string;\n group?: string;\n description?: string;\n };\n}\n\nexport type ColumnLayout = \"1\" | \"2\" | \"3\" | \"2-1\" | \"1-2\";\n\nexport interface SectionBlock extends BaseBlock {\n type: \"section\";\n columns: ColumnLayout;\n children: Block[][];\n}\n\nexport type HeadingLevel = 1 | 2 | 3 | 4;\n\nexport const HEADING_LEVEL_FONT_SIZE: Record<HeadingLevel, number> = {\n 1: 36,\n 2: 28,\n 3: 22,\n 4: 18,\n};\n\nexport interface TitleBlock extends BaseBlock {\n type: \"title\";\n content: string;\n level: HeadingLevel;\n color: string;\n textAlign: \"left\" | \"center\" | \"right\";\n fontFamily?: string;\n}\n\nexport interface ParagraphBlock extends BaseBlock {\n type: \"paragraph\";\n content: string;\n}\n\nexport interface ImageBlock extends BaseBlock {\n type: \"image\";\n src: string;\n alt: string;\n width: number | \"full\";\n align: \"left\" | \"center\" | \"right\";\n linkUrl?: string;\n linkOpenInNewTab?: boolean;\n placeholderUrl?: string;\n decorative?: boolean;\n}\n\nexport interface ButtonBlock extends BaseBlock {\n type: \"button\";\n text: string;\n url: string;\n openInNewTab?: boolean;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n fontSize: number;\n buttonPadding: SpacingValue;\n fontFamily?: string;\n}\n\nexport interface DividerBlock extends BaseBlock {\n type: \"divider\";\n lineStyle: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n thickness: number;\n width: number | \"full\";\n}\n\nexport interface VideoBlock extends BaseBlock {\n type: \"video\";\n url: string;\n openInNewTab?: boolean;\n thumbnailUrl: string;\n alt: string;\n width: number | \"full\";\n align: \"left\" | \"center\" | \"right\";\n placeholderUrl?: string;\n}\n\nexport type SocialPlatform =\n | \"facebook\"\n | \"twitter\"\n | \"instagram\"\n | \"linkedin\"\n | \"youtube\"\n | \"tiktok\"\n | \"pinterest\"\n | \"email\"\n | \"whatsapp\"\n | \"telegram\"\n | \"discord\"\n | \"snapchat\"\n | \"reddit\"\n | \"github\"\n | \"dribbble\"\n | \"behance\";\n\nexport type SocialIconStyle =\n | \"solid\"\n | \"outlined\"\n | \"rounded\"\n | \"square\"\n | \"circle\";\n\nexport type SocialIconSize = \"small\" | \"medium\" | \"large\";\n\nexport interface SocialIcon {\n id: string;\n platform: SocialPlatform;\n url: string;\n}\n\nexport interface SocialIconsBlock extends BaseBlock {\n type: \"social\";\n icons: SocialIcon[];\n iconStyle: SocialIconStyle;\n iconSize: SocialIconSize;\n spacing: number;\n align: \"left\" | \"center\" | \"right\";\n}\n\nexport interface SpacerBlock extends BaseBlock {\n type: \"spacer\";\n height: number;\n}\n\nexport interface HtmlBlock extends BaseBlock {\n type: \"html\";\n content: string;\n}\n\nexport interface MenuItemData {\n id: string;\n text: string;\n url: string;\n openInNewTab: boolean;\n bold: boolean;\n underline: boolean;\n color?: string;\n}\n\nexport interface MenuBlock extends BaseBlock {\n type: \"menu\";\n items: MenuItemData[];\n fontSize: number;\n fontFamily?: string;\n color: string;\n linkColor?: string;\n textAlign: \"left\" | \"center\" | \"right\";\n separator: string;\n separatorColor: string;\n spacing: number;\n}\n\nexport interface TableCellData {\n id: string;\n content: string;\n}\n\nexport interface TableRowData {\n id: string;\n cells: TableCellData[];\n}\n\nexport interface TableBlock extends BaseBlock {\n type: \"table\";\n rows: TableRowData[];\n hasHeaderRow: boolean;\n headerBackgroundColor?: string;\n borderColor: string;\n borderWidth: number;\n cellPadding: number;\n fontSize: number;\n fontFamily?: string;\n color: string;\n textAlign: \"left\" | \"center\" | \"right\";\n}\n\nexport interface CountdownBlock extends BaseBlock {\n type: \"countdown\";\n targetDate: string;\n timezone: string;\n showDays: boolean;\n showHours: boolean;\n showMinutes: boolean;\n showSeconds: boolean;\n separator: \":\" | \"-\" | \" \";\n digitFontSize: number;\n digitColor: string;\n labelColor: string;\n labelFontSize: number;\n backgroundColor: string;\n fontFamily?: string;\n labelDays: string;\n labelHours: string;\n labelMinutes: string;\n labelSeconds: string;\n expiredMessage: string;\n expiredImageUrl: string;\n hideOnExpiry: boolean;\n}\n\nexport interface CustomBlock extends BaseBlock {\n type: \"custom\";\n customType: string;\n fieldValues: Record<string, unknown>;\n renderedHtml?: string;\n dataSourceFetched?: boolean;\n}\n\nexport type Block =\n | SectionBlock\n | TitleBlock\n | ParagraphBlock\n | ImageBlock\n | ButtonBlock\n | DividerBlock\n | VideoBlock\n | SocialIconsBlock\n | SpacerBlock\n | HtmlBlock\n | MenuBlock\n | TableBlock\n | CountdownBlock\n | CustomBlock;\n\nexport type BlockType = Block[\"type\"];\n","import type {\n Block,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\n\nexport function isSection(block: Block): block is SectionBlock {\n return block.type === \"section\";\n}\n\nexport function isTitle(block: Block): block is TitleBlock {\n return block.type === \"title\";\n}\n\nexport function isParagraph(block: Block): block is ParagraphBlock {\n return block.type === \"paragraph\";\n}\n\nexport function isImage(block: Block): block is ImageBlock {\n return block.type === \"image\";\n}\n\nexport function isButton(block: Block): block is ButtonBlock {\n return block.type === \"button\";\n}\n\nexport function isDivider(block: Block): block is DividerBlock {\n return block.type === \"divider\";\n}\n\nexport function isVideo(block: Block): block is VideoBlock {\n return block.type === \"video\";\n}\n\nexport function isSocialIcons(block: Block): block is SocialIconsBlock {\n return block.type === \"social\";\n}\n\nexport function isSpacer(block: Block): block is SpacerBlock {\n return block.type === \"spacer\";\n}\n\nexport function isHtml(block: Block): block is HtmlBlock {\n return block.type === \"html\";\n}\n\nexport function isMenu(block: Block): block is MenuBlock {\n return block.type === \"menu\";\n}\n\nexport function isTable(block: Block): block is TableBlock {\n return block.type === \"table\";\n}\n\nexport function isCountdown(block: Block): block is CountdownBlock {\n return block.type === \"countdown\";\n}\n\nexport function isCustomBlock(block: Block): block is CustomBlock {\n return block.type === \"custom\";\n}\n","import type {\n ButtonBlock,\n CountdownBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\nimport type { TemplateSettings } from \"./template\";\n\ntype BlockDefaultsFor<T> = Partial<Omit<T, \"id\" | \"type\">>;\n\nexport interface BlockDefaults {\n title?: BlockDefaultsFor<TitleBlock>;\n paragraph?: BlockDefaultsFor<ParagraphBlock>;\n image?: BlockDefaultsFor<ImageBlock>;\n button?: BlockDefaultsFor<ButtonBlock>;\n divider?: BlockDefaultsFor<DividerBlock>;\n section?: BlockDefaultsFor<SectionBlock>;\n video?: BlockDefaultsFor<VideoBlock>;\n social?: BlockDefaultsFor<SocialIconsBlock>;\n spacer?: BlockDefaultsFor<SpacerBlock>;\n html?: BlockDefaultsFor<HtmlBlock>;\n menu?: BlockDefaultsFor<MenuBlock>;\n table?: BlockDefaultsFor<TableBlock>;\n countdown?: BlockDefaultsFor<CountdownBlock>;\n}\n\nexport type TemplateDefaults = Partial<TemplateSettings>;\n\n// ---------------------------------------------------------------------------\n// Built-in default values — single source of truth for factories & consumers\n// ---------------------------------------------------------------------------\n\nexport const TITLE_BLOCK_DEFAULTS: BlockDefaultsFor<TitleBlock> = {\n content: \"<p>Enter your title</p>\",\n level: 2,\n color: \"#1a1a1a\",\n textAlign: \"left\",\n};\n\nexport const PARAGRAPH_BLOCK_DEFAULTS: BlockDefaultsFor<ParagraphBlock> = {\n content: \"<p>Enter your text here</p>\",\n};\n\nexport const IMAGE_BLOCK_DEFAULTS: BlockDefaultsFor<ImageBlock> = {\n src: \"\",\n alt: \"\",\n width: \"full\",\n align: \"center\",\n};\n\nexport const BUTTON_BLOCK_DEFAULTS: BlockDefaultsFor<ButtonBlock> = {\n text: \"Click Here\",\n url: \"\",\n backgroundColor: \"#333333\",\n textColor: \"#ffffff\",\n borderRadius: 6,\n fontSize: 15,\n buttonPadding: { top: 12, right: 24, bottom: 12, left: 24 },\n};\n\nexport const DIVIDER_BLOCK_DEFAULTS: BlockDefaultsFor<DividerBlock> = {\n lineStyle: \"solid\",\n color: \"#e0e0e0\",\n thickness: 1,\n width: \"full\",\n};\n\nexport const SECTION_BLOCK_DEFAULTS: BlockDefaultsFor<SectionBlock> = {\n columns: \"1\",\n};\n\nexport const VIDEO_BLOCK_DEFAULTS: BlockDefaultsFor<VideoBlock> = {\n url: \"\",\n thumbnailUrl: \"\",\n alt: \"Video\",\n width: \"full\",\n align: \"center\",\n};\n\nexport const SOCIAL_ICONS_BLOCK_DEFAULTS: BlockDefaultsFor<SocialIconsBlock> = {\n iconStyle: \"solid\",\n iconSize: \"medium\",\n spacing: 10,\n align: \"center\",\n};\n\nexport const SPACER_BLOCK_DEFAULTS: BlockDefaultsFor<SpacerBlock> = {\n height: 24,\n};\n\nexport const HTML_BLOCK_DEFAULTS: BlockDefaultsFor<HtmlBlock> = {\n content: \"\",\n};\n\nexport const MENU_BLOCK_DEFAULTS: BlockDefaultsFor<MenuBlock> = {\n fontSize: 15,\n color: \"#1a1a1a\",\n textAlign: \"center\",\n separator: \"|\",\n separatorColor: \"#e0e0e0\",\n spacing: 10,\n};\n\nexport const TABLE_BLOCK_DEFAULTS: BlockDefaultsFor<TableBlock> = {\n hasHeaderRow: true,\n borderColor: \"#e0e0e0\",\n borderWidth: 1,\n cellPadding: 8,\n fontSize: 15,\n color: \"#1a1a1a\",\n textAlign: \"left\",\n};\n\nexport const COUNTDOWN_BLOCK_DEFAULTS: BlockDefaultsFor<CountdownBlock> = {\n targetDate: \"\",\n timezone: \"UTC\",\n showDays: true,\n showHours: true,\n showMinutes: true,\n showSeconds: true,\n separator: \":\",\n digitFontSize: 32,\n digitColor: \"#1a1a1a\",\n labelColor: \"#6b7280\",\n labelFontSize: 12,\n backgroundColor: \"#ffffff\",\n labelDays: \"Days\",\n labelHours: \"Hours\",\n labelMinutes: \"Minutes\",\n labelSeconds: \"Seconds\",\n expiredMessage: \"This offer has expired\",\n expiredImageUrl: \"\",\n hideOnExpiry: false,\n};\n\nexport const DEFAULT_BLOCK_DEFAULTS: Required<BlockDefaults> = {\n title: TITLE_BLOCK_DEFAULTS,\n paragraph: PARAGRAPH_BLOCK_DEFAULTS,\n image: IMAGE_BLOCK_DEFAULTS,\n button: BUTTON_BLOCK_DEFAULTS,\n divider: DIVIDER_BLOCK_DEFAULTS,\n section: SECTION_BLOCK_DEFAULTS,\n video: VIDEO_BLOCK_DEFAULTS,\n social: SOCIAL_ICONS_BLOCK_DEFAULTS,\n spacer: SPACER_BLOCK_DEFAULTS,\n html: HTML_BLOCK_DEFAULTS,\n menu: MENU_BLOCK_DEFAULTS,\n table: TABLE_BLOCK_DEFAULTS,\n countdown: COUNTDOWN_BLOCK_DEFAULTS,\n};\n\nexport const DEFAULT_TEMPLATE_DEFAULTS: TemplateDefaults = {\n width: 600,\n backgroundColor: \"#ffffff\",\n fontFamily: \"Arial\",\n locale: \"en\",\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype\n );\n}\n\nexport function deepMergeDefaults<T extends object>(\n base: T,\n overrides: Partial<T>,\n): T {\n const result = { ...base };\n\n for (const key of Object.keys(overrides) as Array<keyof T>) {\n const baseVal = base[key];\n const overrideVal = overrides[key];\n\n if (overrideVal === undefined) {\n continue;\n }\n\n if (isPlainObject(baseVal) && isPlainObject(overrideVal)) {\n result[key] = deepMergeDefaults(\n baseVal,\n overrideVal as Partial<typeof baseVal>,\n ) as T[keyof T];\n } else {\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n","import type { Block } from \"./blocks\";\nimport { DEFAULT_TEMPLATE_DEFAULTS } from \"./defaults\";\nimport type { TemplateDefaults } from \"./defaults\";\n\nexport interface TemplateSettings {\n width: number;\n backgroundColor: string;\n fontFamily: string;\n preheaderText?: string;\n /**\n * BCP-47 language code for the rendered email's `<html lang>`. Drives\n * screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`.\n */\n locale: string;\n}\n\nexport interface TemplateContent {\n blocks: Block[];\n settings: TemplateSettings;\n}\n\nexport function createDefaultTemplateContent(\n defaultFontFamily = \"Arial\",\n templateDefaults?: TemplateDefaults,\n): TemplateContent {\n return {\n blocks: [],\n settings: {\n ...DEFAULT_TEMPLATE_DEFAULTS,\n fontFamily: defaultFontFamily,\n ...templateDefaults,\n } as TemplateSettings,\n };\n}\n","import type {\n Block,\n BlockStyles,\n BlockType,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n SpacingValue,\n TableBlock,\n TableCellData,\n TableRowData,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\nimport type { CustomBlockDefinition, CustomBlockField } from \"./custom-blocks\";\nimport type { BlockDefaults } from \"./defaults\";\nimport {\n BUTTON_BLOCK_DEFAULTS,\n COUNTDOWN_BLOCK_DEFAULTS,\n deepMergeDefaults,\n DIVIDER_BLOCK_DEFAULTS,\n HTML_BLOCK_DEFAULTS,\n IMAGE_BLOCK_DEFAULTS,\n MENU_BLOCK_DEFAULTS,\n PARAGRAPH_BLOCK_DEFAULTS,\n SECTION_BLOCK_DEFAULTS,\n SOCIAL_ICONS_BLOCK_DEFAULTS,\n SPACER_BLOCK_DEFAULTS,\n TABLE_BLOCK_DEFAULTS,\n TITLE_BLOCK_DEFAULTS,\n VIDEO_BLOCK_DEFAULTS,\n} from \"./defaults\";\n\nfunction applyDefaults<T extends object>(\n base: T,\n partial: Partial<T> | undefined,\n): T {\n if (!partial || Object.keys(partial).length === 0) return base;\n return deepMergeDefaults(base, partial);\n}\n\nexport function generateId(): string {\n return crypto.randomUUID();\n}\n\nfunction createDefaultSpacing(value = 0): SpacingValue {\n return { top: value, right: value, bottom: value, left: value };\n}\n\nfunction createDefaultStyles(padding = 10): BlockStyles {\n return {\n padding: createDefaultSpacing(padding),\n };\n}\n\nexport function createTitleBlock(\n partial: Partial<TitleBlock> = {},\n): TitleBlock {\n const base: TitleBlock = {\n id: generateId(),\n type: \"title\",\n ...TITLE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as TitleBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createParagraphBlock(\n partial: Partial<ParagraphBlock> = {},\n): ParagraphBlock {\n const base: ParagraphBlock = {\n id: generateId(),\n type: \"paragraph\",\n ...PARAGRAPH_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ParagraphBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createImageBlock(\n partial: Partial<ImageBlock> = {},\n): ImageBlock {\n const base: ImageBlock = {\n id: generateId(),\n type: \"image\",\n ...IMAGE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ImageBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createButtonBlock(\n partial: Partial<ButtonBlock> = {},\n): ButtonBlock {\n const base: ButtonBlock = {\n id: generateId(),\n type: \"button\",\n ...BUTTON_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ButtonBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createDividerBlock(\n partial: Partial<DividerBlock> = {},\n): DividerBlock {\n const base: DividerBlock = {\n id: generateId(),\n type: \"divider\",\n ...DIVIDER_BLOCK_DEFAULTS,\n styles: createDefaultStyles(20),\n } as DividerBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSectionBlock(\n partial: Partial<SectionBlock> = {},\n): SectionBlock {\n const base: SectionBlock = {\n id: generateId(),\n type: \"section\",\n ...SECTION_BLOCK_DEFAULTS,\n children: [[]],\n styles: createDefaultStyles(20),\n } as SectionBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createVideoBlock(\n partial: Partial<VideoBlock> = {},\n): VideoBlock {\n const base: VideoBlock = {\n id: generateId(),\n type: \"video\",\n ...VIDEO_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as VideoBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSocialIconsBlock(\n partial: Partial<SocialIconsBlock> = {},\n): SocialIconsBlock {\n const base: SocialIconsBlock = {\n id: generateId(),\n type: \"social\",\n icons: [],\n ...SOCIAL_ICONS_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as SocialIconsBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSpacerBlock(\n partial: Partial<SpacerBlock> = {},\n): SpacerBlock {\n const base: SpacerBlock = {\n id: generateId(),\n type: \"spacer\",\n ...SPACER_BLOCK_DEFAULTS,\n styles: createDefaultStyles(0),\n } as SpacerBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createHtmlBlock(partial: Partial<HtmlBlock> = {}): HtmlBlock {\n const base: HtmlBlock = {\n id: generateId(),\n type: \"html\",\n ...HTML_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as HtmlBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createMenuBlock(partial: Partial<MenuBlock> = {}): MenuBlock {\n const base: MenuBlock = {\n id: generateId(),\n type: \"menu\",\n items: [],\n ...MENU_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as MenuBlock;\n return applyDefaults(base, partial);\n}\n\nfunction createDefaultTableRows(columns: number, rows: number): TableRowData[] {\n return Array.from({ length: rows }, () => ({\n id: generateId(),\n cells: Array.from(\n { length: columns },\n (): TableCellData => ({\n id: generateId(),\n content: \"\",\n }),\n ),\n }));\n}\n\nexport function createTableBlock(\n partial: Partial<TableBlock> = {},\n): TableBlock {\n const base: TableBlock = {\n id: generateId(),\n type: \"table\",\n rows: createDefaultTableRows(3, 3),\n ...TABLE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as TableBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createCountdownBlock(\n partial: Partial<CountdownBlock> = {},\n): CountdownBlock {\n const base: CountdownBlock = {\n id: generateId(),\n type: \"countdown\",\n ...COUNTDOWN_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as CountdownBlock;\n return applyDefaults(base, partial);\n}\n\nfunction getFieldDefault(field: CustomBlockField): unknown {\n if (field.type === \"repeatable\") {\n return field.default ?? [];\n }\n if (field.type === \"boolean\") {\n return field.default ?? false;\n }\n if (field.type === \"number\") {\n return field.default ?? 0;\n }\n\n return field.default ?? \"\";\n}\n\nexport function createCustomBlock(\n definition: CustomBlockDefinition,\n): CustomBlock {\n const fieldValues: Record<string, unknown> = {};\n\n for (const field of definition.fields) {\n fieldValues[field.key] = getFieldDefault(field);\n }\n\n const styles = applyDefaults(createDefaultStyles(), definition.defaultStyles);\n\n return {\n id: generateId(),\n type: \"custom\",\n customType: definition.type,\n fieldValues,\n styles,\n ...(definition.dataSource ? { dataSourceFetched: false } : {}),\n };\n}\n\nexport function createBlock(\n type: BlockType,\n blockDefaults?: BlockDefaults,\n): Block {\n switch (type) {\n case \"section\":\n return createSectionBlock(blockDefaults?.section);\n case \"title\":\n return createTitleBlock(blockDefaults?.title);\n case \"paragraph\":\n return createParagraphBlock(blockDefaults?.paragraph);\n case \"image\":\n return createImageBlock(blockDefaults?.image);\n case \"button\":\n return createButtonBlock(blockDefaults?.button);\n case \"divider\":\n return createDividerBlock(blockDefaults?.divider);\n case \"video\":\n return createVideoBlock(blockDefaults?.video);\n case \"social\":\n return createSocialIconsBlock(blockDefaults?.social);\n case \"spacer\":\n return createSpacerBlock(blockDefaults?.spacer);\n case \"html\":\n return createHtmlBlock(blockDefaults?.html);\n case \"menu\":\n return createMenuBlock(blockDefaults?.menu);\n case \"table\":\n return createTableBlock(blockDefaults?.table);\n case \"countdown\":\n return createCountdownBlock(blockDefaults?.countdown);\n default:\n throw new Error(`Unknown block type: ${type}`);\n }\n}\n\nexport function cloneBlock(block: Block): Block {\n const cloned = JSON.parse(JSON.stringify(block)) as Block;\n cloned.id = generateId();\n\n if (cloned.type === \"section\") {\n cloned.children = cloned.children.map((column) =>\n column.map((child) => cloneBlock(child)),\n );\n }\n\n return cloned;\n}\n","export class EventEmitter<\n TEvents extends Record<string, unknown> = Record<string, unknown>,\n> {\n private handlers = new Map<keyof TEvents, Set<(data: never) => void>>();\n\n on<K extends keyof TEvents>(\n event: K,\n handler: (data: TEvents[K]) => void,\n ): () => void {\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Set());\n }\n\n const set = this.handlers.get(event)!;\n set.add(handler as (data: never) => void);\n\n return () => {\n set.delete(handler as (data: never) => void);\n if (set.size === 0) {\n this.handlers.delete(event);\n }\n };\n }\n\n off<K extends keyof TEvents>(\n event: K,\n handler: (data: TEvents[K]) => void,\n ): void {\n const set = this.handlers.get(event);\n if (!set) {\n return;\n }\n\n set.delete(handler as (data: never) => void);\n if (set.size === 0) {\n this.handlers.delete(event);\n }\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n const set = this.handlers.get(event);\n if (!set) {\n return;\n }\n\n // Copy to avoid issues with handlers modifying the set during iteration\n for (const handler of [...set]) {\n handler(data as never);\n }\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.handlers.delete(event);\n } else {\n this.handlers.clear();\n }\n }\n\n listenerCount(event: keyof TEvents): number {\n return this.handlers.get(event)?.size ?? 0;\n }\n}\n","import type { MergeTag } from \"./config\";\n\n// --- Syntax Presets ---\n\nexport interface SyntaxPreset {\n value: RegExp;\n logic: RegExp;\n}\n\nexport type SyntaxPresetName =\n | \"liquid\"\n | \"handlebars\"\n | \"mailchimp\"\n | \"ampscript\";\n\nexport const SYNTAX_PRESETS: Record<SyntaxPresetName, SyntaxPreset> = {\n liquid: { value: /\\{\\{.+?\\}\\}/g, logic: /\\{%-?\\s*(\\w+).*?-?%\\}/g },\n handlebars: {\n value: /\\{\\{\\{?.+?\\}?\\}\\}/g,\n logic: /\\{\\{[#/](\\w+).*?\\}\\}/g,\n },\n mailchimp: { value: /\\*\\|\\w+\\|\\*/g, logic: /\\*\\|(\\w+)[:|].*?\\|\\*/g },\n ampscript: { value: /%%=.+?=%%/g, logic: /%%\\[\\s*(\\w+).*?\\]%%/g },\n};\n\nconst SYNTAX_TRIGGER_CHARS: Record<SyntaxPresetName, string> = {\n liquid: \"{{\",\n handlebars: \"{{\",\n mailchimp: \"*|\",\n ampscript: \"%%=\",\n};\n\n/**\n * Resolves the autocomplete trigger string for a syntax preset.\n * Returns null when the syntax doesn't match any built-in preset\n * (custom regex syntax — autocomplete cannot be enabled safely).\n */\nexport function getSyntaxTriggerChar(syntax: SyntaxPreset): string | null {\n for (const name of Object.keys(SYNTAX_PRESETS) as SyntaxPresetName[]) {\n if (SYNTAX_PRESETS[name].value.source === syntax.value.source) {\n return SYNTAX_TRIGGER_CHARS[name];\n }\n }\n return null;\n}\n\nexport function resolveSyntax(\n syntax?: SyntaxPresetName | SyntaxPreset,\n): SyntaxPreset {\n if (!syntax) {\n return SYNTAX_PRESETS.liquid;\n }\n\n if (typeof syntax === \"string\") {\n return SYNTAX_PRESETS[syntax] ?? SYNTAX_PRESETS.liquid;\n }\n\n return syntax;\n}\n\n// --- Merge Tag Utilities ---\n\nfunction escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction anchoredRegex(pattern: RegExp): RegExp {\n const source = pattern.source;\n const flags = pattern.flags.replace(\"g\", \"\");\n return new RegExp(`^${source}$`, flags);\n}\n\nexport function isMergeTagValue(value: string, syntax: SyntaxPreset): boolean {\n const trimmed = value?.trim() || \"\";\n // Handlebars (and similar) value regex is liberal enough to also match\n // logic tags like `{{#each items}}`. Exclude logic-shaped tags so callers\n // that rely on this discriminator (UI segmentation, label rendering)\n // don't misclassify them.\n if (anchoredRegex(syntax.logic).test(trimmed)) {\n return false;\n }\n return anchoredRegex(syntax.value).test(trimmed);\n}\n\nexport function getMergeTagLabel(value: string, mergeTags: MergeTag[]): string {\n const found = mergeTags.find((p) => p.value === value);\n if (found) {\n return found.label;\n }\n return value;\n}\n\nexport function resolveHtmlMergeTagLabels(\n html: string,\n mergeTags: MergeTag[],\n): string {\n return rewriteSpanByAttr(html, \"data-merge-tag\", (value) =>\n getMergeTagLabel(value, mergeTags),\n );\n}\n\nexport function containsMergeTag(value: string, syntax: SyntaxPreset): boolean {\n if (!value) return false;\n\n const valueRegex = new RegExp(syntax.value.source, syntax.value.flags);\n const logicRegex = new RegExp(syntax.logic.source, syntax.logic.flags);\n\n return valueRegex.test(value) || logicRegex.test(value);\n}\n\nexport function restoreMergeTagMarkup(\n html: string,\n mergeTags: MergeTag[],\n syntax: SyntaxPreset,\n): string {\n let result = html;\n\n for (const tag of mergeTags) {\n const escaped = escapeRegExp(tag.value);\n const pattern = new RegExp(`(?<!data-merge-tag=\")${escaped}`, \"g\");\n result = result.replace(pattern, (match) => {\n const label = getMergeTagLabel(match, mergeTags);\n return `<span data-merge-tag=\"${match}\">${label}</span>`;\n });\n }\n\n const logicRegex = new RegExp(\n `(?<!data-logic-merge-tag=\")${syntax.logic.source}`,\n syntax.logic.flags,\n );\n result = result.replace(logicRegex, (match) => {\n const keyword = getLogicMergeTagKeyword(match, syntax);\n return `<span data-logic-merge-tag=\"${match}\">${keyword}</span>`;\n });\n\n return result;\n}\n\nexport function isLogicMergeTagValue(\n value: string,\n syntax: SyntaxPreset,\n): boolean {\n return anchoredRegex(syntax.logic).test(value?.trim() || \"\");\n}\n\nexport function getLogicMergeTagKeyword(\n value: string,\n syntax: SyntaxPreset,\n): string {\n const regex = new RegExp(\n syntax.logic.source,\n syntax.logic.flags.replace(\"g\", \"\"),\n );\n const match = value.match(regex);\n return match && match[1] ? match[1].toUpperCase() : value;\n}\n\nexport function resolveHtmlLogicMergeTagLabels(\n html: string,\n syntax: SyntaxPreset,\n): string {\n return rewriteSpanByAttr(html, \"data-logic-merge-tag\", (value) =>\n getLogicMergeTagKeyword(value, syntax),\n );\n}\n\n/**\n * Walk `html` and rewrite the inner text of every `<span … {attrName}=\"…\">…</span>`\n * by passing the attribute value through `relabel`. Linear in `html.length`:\n * each `indexOf` advances the cursor monotonically, and no regex backtracking\n * can run over the whole string.\n *\n * Replaces the original `/<span[^>]*…[^>]*>(.*?)<\\/span>/g` pattern, which\n * was polynomial-ReDoS over inputs that contained many `<span` starts with\n * no closing `>`.\n */\nfunction rewriteSpanByAttr(\n html: string,\n attrName: string,\n relabel: (value: string) => string,\n): string {\n // Anchored on `>` per match. `[^<>\"]*` for the attribute value fails fast\n // on a missing closing quote instead of backtracking across the input.\n const attrPattern = new RegExp(`(?:^|\\\\s)${attrName}=\"([^\"<>]*)\"`);\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<span\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 5];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 5);\n i = open + 5;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 5);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</span>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const attrs = html.substring(open + 5, openEnd);\n const attrMatch = attrPattern.exec(attrs);\n if (!attrMatch) {\n // This `<span>` isn't the one we're looking for — emit up to and\n // including the `<span` literal and let the next iteration scan\n // inward. Skipping straight to the matching `</span>` would swallow\n // any nested merge-tag span in the same loop iteration.\n out += html.substring(i, open + 5);\n i = open + 5;\n continue;\n }\n const value = attrMatch[1];\n const newLabel = relabel(value);\n out += html.substring(i, openEnd + 1);\n out += newLabel;\n out += \"</span>\";\n i = closeStart + 7;\n }\n return out;\n}\n","import type { SyntaxPreset, SyntaxPresetName } from \"./merge-tags\";\n\nexport type ViewportSize = \"desktop\" | \"mobile\";\n\nexport type UiTheme = \"light\" | \"dark\" | \"auto\";\n\nexport interface CustomFont {\n name: string;\n url: string;\n fallback?: string;\n}\n\nexport interface FontsConfig {\n defaultFallback?: string;\n defaultFont?: string;\n customFonts?: CustomFont[];\n}\n\nexport interface ExportResult {\n html: string;\n mjml: string;\n}\n\nexport interface MergeTag {\n label: string;\n value: string;\n /**\n * Optional grouping label used by the built-in merge tag picker to\n * section the list. When no tag in the configured array carries\n * `group`, the picker renders a plain flat list with no headers.\n * Ignored by the renderer and by typing-autocomplete.\n */\n group?: string;\n /**\n * Optional helper text shown beneath the tag in the built-in merge\n * tag picker. Not rendered anywhere else (toolbar, autocomplete,\n * MJML output) and not stored on the inserted document node.\n */\n description?: string;\n}\n\nexport interface MediaResult {\n url: string;\n alt?: string;\n}\n\nexport interface MergeTagsConfig {\n syntax?: SyntaxPresetName | SyntaxPreset;\n tags?: MergeTag[];\n onRequest?: () => Promise<MergeTag | null>;\n /**\n * Enables typing-based autocomplete in rich text fields. When the user\n * types the syntax opener (e.g. `{{`), a popup lists matching `tags`.\n *\n * Defaults to `true`. Effective only when `tags` is non-empty AND\n * `syntax` matches a built-in preset (custom regex syntaxes cannot be\n * mapped to a trigger string and silently disable autocomplete).\n */\n autocomplete?: boolean;\n}\n\nexport interface DisplayCondition {\n label: string;\n before: string;\n after: string;\n group?: string;\n description?: string;\n}\n\nexport interface DisplayConditionsConfig {\n conditions: DisplayCondition[];\n allowCustom?: boolean;\n}\n\nexport interface ThemeOverrides {\n bg?: string;\n bgElevated?: string;\n bgHover?: string;\n bgActive?: string;\n border?: string;\n borderLight?: string;\n text?: string;\n textMuted?: string;\n textDim?: string;\n primary?: string;\n primaryHover?: string;\n primaryLight?: string;\n secondary?: string;\n secondaryHover?: string;\n secondaryLight?: string;\n success?: string;\n successLight?: string;\n warning?: string;\n warningLight?: string;\n danger?: string;\n dangerLight?: string;\n canvasBg?: string;\n dark?: Omit<ThemeOverrides, \"dark\">;\n}\n\nexport class SdkError extends Error {\n constructor(\n message: string,\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"SdkError\";\n }\n\n get isNotFound(): boolean {\n return this.statusCode === 404;\n }\n\n get isUnauthorized(): boolean {\n return this.statusCode === 401;\n }\n\n get isServerError(): boolean {\n return this.statusCode !== undefined && this.statusCode >= 500;\n }\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../src/blocks.ts","../src/guards.ts","../src/defaults.ts","../src/template.ts","../src/factory.ts","../src/events.ts","../src/merge-tags.ts","../src/config.ts"],"sourcesContent":["export interface SpacingValue {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport interface BlockStyles {\n padding: SpacingValue;\n backgroundColor?: string;\n}\n\nexport interface BlockVisibility {\n desktop: boolean;\n mobile: boolean;\n}\n\nexport interface BaseBlock {\n id: string;\n type: string;\n styles: BlockStyles;\n visibility?: BlockVisibility;\n displayCondition?: {\n label: string;\n before: string;\n after: string;\n group?: string;\n description?: string;\n };\n}\n\nexport type ColumnLayout = \"1\" | \"2\" | \"3\" | \"2-1\" | \"1-2\";\n\nexport interface SectionBlock extends BaseBlock {\n type: \"section\";\n columns: ColumnLayout;\n children: Block[][];\n}\n\nexport type HeadingLevel = 1 | 2 | 3 | 4;\n\nexport const HEADING_LEVEL_FONT_SIZE: Record<HeadingLevel, number> = {\n 1: 36,\n 2: 28,\n 3: 22,\n 4: 18,\n};\n\nexport interface TitleBlock extends BaseBlock {\n type: \"title\";\n content: string;\n level: HeadingLevel;\n color: string;\n textAlign: \"left\" | \"center\" | \"right\";\n fontFamily?: string;\n}\n\nexport interface ParagraphBlock extends BaseBlock {\n type: \"paragraph\";\n content: string;\n}\n\nexport interface ImageBlock extends BaseBlock {\n type: \"image\";\n src: string;\n alt: string;\n width: number | \"full\";\n align: \"left\" | \"center\" | \"right\";\n linkUrl?: string;\n linkOpenInNewTab?: boolean;\n placeholderUrl?: string;\n decorative?: boolean;\n}\n\nexport interface ButtonBlock extends BaseBlock {\n type: \"button\";\n text: string;\n url: string;\n openInNewTab?: boolean;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n fontSize: number;\n buttonPadding: SpacingValue;\n fontFamily?: string;\n}\n\nexport interface DividerBlock extends BaseBlock {\n type: \"divider\";\n lineStyle: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n thickness: number;\n width: number | \"full\";\n}\n\nexport interface VideoBlock extends BaseBlock {\n type: \"video\";\n url: string;\n openInNewTab?: boolean;\n thumbnailUrl: string;\n alt: string;\n width: number | \"full\";\n align: \"left\" | \"center\" | \"right\";\n placeholderUrl?: string;\n}\n\nexport type SocialPlatform =\n | \"facebook\"\n | \"twitter\"\n | \"instagram\"\n | \"linkedin\"\n | \"youtube\"\n | \"tiktok\"\n | \"pinterest\"\n | \"email\"\n | \"whatsapp\"\n | \"telegram\"\n | \"discord\"\n | \"snapchat\"\n | \"reddit\"\n | \"github\"\n | \"dribbble\"\n | \"behance\";\n\nexport type SocialIconStyle =\n | \"solid\"\n | \"outlined\"\n | \"rounded\"\n | \"square\"\n | \"circle\";\n\nexport type SocialIconSize = \"small\" | \"medium\" | \"large\";\n\nexport interface SocialIcon {\n id: string;\n platform: SocialPlatform;\n url: string;\n}\n\nexport interface SocialIconsBlock extends BaseBlock {\n type: \"social\";\n icons: SocialIcon[];\n iconStyle: SocialIconStyle;\n iconSize: SocialIconSize;\n spacing: number;\n align: \"left\" | \"center\" | \"right\";\n}\n\nexport interface SpacerBlock extends BaseBlock {\n type: \"spacer\";\n height: number;\n}\n\nexport interface HtmlBlock extends BaseBlock {\n type: \"html\";\n content: string;\n}\n\nexport interface MenuItemData {\n id: string;\n text: string;\n url: string;\n openInNewTab: boolean;\n bold: boolean;\n underline: boolean;\n color?: string;\n}\n\nexport interface MenuBlock extends BaseBlock {\n type: \"menu\";\n items: MenuItemData[];\n fontSize: number;\n fontFamily?: string;\n color: string;\n linkColor?: string;\n textAlign: \"left\" | \"center\" | \"right\";\n separator: string;\n separatorColor: string;\n spacing: number;\n}\n\nexport interface TableCellData {\n id: string;\n content: string;\n}\n\nexport interface TableRowData {\n id: string;\n cells: TableCellData[];\n}\n\nexport interface TableBlock extends BaseBlock {\n type: \"table\";\n rows: TableRowData[];\n hasHeaderRow: boolean;\n headerBackgroundColor?: string;\n borderColor: string;\n borderWidth: number;\n cellPadding: number;\n fontSize: number;\n fontFamily?: string;\n color: string;\n textAlign: \"left\" | \"center\" | \"right\";\n}\n\nexport interface CountdownBlock extends BaseBlock {\n type: \"countdown\";\n targetDate: string;\n timezone: string;\n showDays: boolean;\n showHours: boolean;\n showMinutes: boolean;\n showSeconds: boolean;\n separator: \":\" | \"-\" | \" \";\n digitFontSize: number;\n digitColor: string;\n labelColor: string;\n labelFontSize: number;\n backgroundColor: string;\n fontFamily?: string;\n labelDays: string;\n labelHours: string;\n labelMinutes: string;\n labelSeconds: string;\n expiredMessage: string;\n expiredImageUrl: string;\n hideOnExpiry: boolean;\n}\n\nexport interface CustomBlock extends BaseBlock {\n type: \"custom\";\n customType: string;\n fieldValues: Record<string, unknown>;\n renderedHtml?: string;\n dataSourceFetched?: boolean;\n}\n\nexport type Block =\n | SectionBlock\n | TitleBlock\n | ParagraphBlock\n | ImageBlock\n | ButtonBlock\n | DividerBlock\n | VideoBlock\n | SocialIconsBlock\n | SpacerBlock\n | HtmlBlock\n | MenuBlock\n | TableBlock\n | CountdownBlock\n | CustomBlock;\n\nexport type BlockType = Block[\"type\"];\n","import type {\n Block,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\n\nexport function isSection(block: Block): block is SectionBlock {\n return block.type === \"section\";\n}\n\nexport function isTitle(block: Block): block is TitleBlock {\n return block.type === \"title\";\n}\n\nexport function isParagraph(block: Block): block is ParagraphBlock {\n return block.type === \"paragraph\";\n}\n\nexport function isImage(block: Block): block is ImageBlock {\n return block.type === \"image\";\n}\n\nexport function isButton(block: Block): block is ButtonBlock {\n return block.type === \"button\";\n}\n\nexport function isDivider(block: Block): block is DividerBlock {\n return block.type === \"divider\";\n}\n\nexport function isVideo(block: Block): block is VideoBlock {\n return block.type === \"video\";\n}\n\nexport function isSocialIcons(block: Block): block is SocialIconsBlock {\n return block.type === \"social\";\n}\n\nexport function isSpacer(block: Block): block is SpacerBlock {\n return block.type === \"spacer\";\n}\n\nexport function isHtml(block: Block): block is HtmlBlock {\n return block.type === \"html\";\n}\n\nexport function isMenu(block: Block): block is MenuBlock {\n return block.type === \"menu\";\n}\n\nexport function isTable(block: Block): block is TableBlock {\n return block.type === \"table\";\n}\n\nexport function isCountdown(block: Block): block is CountdownBlock {\n return block.type === \"countdown\";\n}\n\nexport function isCustomBlock(block: Block): block is CustomBlock {\n return block.type === \"custom\";\n}\n","import type {\n ButtonBlock,\n CountdownBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\nimport type { TemplateSettings } from \"./template\";\n\ntype BlockDefaultsFor<T> = Partial<Omit<T, \"id\" | \"type\">>;\n\nexport interface BlockDefaults {\n title?: BlockDefaultsFor<TitleBlock>;\n paragraph?: BlockDefaultsFor<ParagraphBlock>;\n image?: BlockDefaultsFor<ImageBlock>;\n button?: BlockDefaultsFor<ButtonBlock>;\n divider?: BlockDefaultsFor<DividerBlock>;\n section?: BlockDefaultsFor<SectionBlock>;\n video?: BlockDefaultsFor<VideoBlock>;\n social?: BlockDefaultsFor<SocialIconsBlock>;\n spacer?: BlockDefaultsFor<SpacerBlock>;\n html?: BlockDefaultsFor<HtmlBlock>;\n menu?: BlockDefaultsFor<MenuBlock>;\n table?: BlockDefaultsFor<TableBlock>;\n countdown?: BlockDefaultsFor<CountdownBlock>;\n}\n\nexport type TemplateDefaults = Partial<TemplateSettings>;\n\n// ---------------------------------------------------------------------------\n// Built-in default values — single source of truth for factories & consumers\n// ---------------------------------------------------------------------------\n\nexport const TITLE_BLOCK_DEFAULTS: BlockDefaultsFor<TitleBlock> = {\n content: \"<p>Enter your title</p>\",\n level: 2,\n color: \"#1a1a1a\",\n textAlign: \"left\",\n};\n\nexport const PARAGRAPH_BLOCK_DEFAULTS: BlockDefaultsFor<ParagraphBlock> = {\n content: \"<p>Enter your text here</p>\",\n};\n\nexport const IMAGE_BLOCK_DEFAULTS: BlockDefaultsFor<ImageBlock> = {\n src: \"\",\n alt: \"\",\n width: \"full\",\n align: \"center\",\n};\n\nexport const BUTTON_BLOCK_DEFAULTS: BlockDefaultsFor<ButtonBlock> = {\n text: \"Click Here\",\n url: \"\",\n backgroundColor: \"#333333\",\n textColor: \"#ffffff\",\n borderRadius: 6,\n fontSize: 15,\n buttonPadding: { top: 12, right: 24, bottom: 12, left: 24 },\n};\n\nexport const DIVIDER_BLOCK_DEFAULTS: BlockDefaultsFor<DividerBlock> = {\n lineStyle: \"solid\",\n color: \"#e0e0e0\",\n thickness: 1,\n width: \"full\",\n};\n\nexport const SECTION_BLOCK_DEFAULTS: BlockDefaultsFor<SectionBlock> = {\n columns: \"1\",\n};\n\nexport const VIDEO_BLOCK_DEFAULTS: BlockDefaultsFor<VideoBlock> = {\n url: \"\",\n thumbnailUrl: \"\",\n alt: \"Video\",\n width: \"full\",\n align: \"center\",\n};\n\nexport const SOCIAL_ICONS_BLOCK_DEFAULTS: BlockDefaultsFor<SocialIconsBlock> = {\n iconStyle: \"solid\",\n iconSize: \"medium\",\n spacing: 10,\n align: \"center\",\n};\n\nexport const SPACER_BLOCK_DEFAULTS: BlockDefaultsFor<SpacerBlock> = {\n height: 24,\n};\n\nexport const HTML_BLOCK_DEFAULTS: BlockDefaultsFor<HtmlBlock> = {\n content: \"\",\n};\n\nexport const MENU_BLOCK_DEFAULTS: BlockDefaultsFor<MenuBlock> = {\n fontSize: 15,\n color: \"#1a1a1a\",\n textAlign: \"center\",\n separator: \"|\",\n separatorColor: \"#e0e0e0\",\n spacing: 10,\n};\n\nexport const TABLE_BLOCK_DEFAULTS: BlockDefaultsFor<TableBlock> = {\n hasHeaderRow: true,\n borderColor: \"#e0e0e0\",\n borderWidth: 1,\n cellPadding: 8,\n fontSize: 15,\n color: \"#1a1a1a\",\n textAlign: \"left\",\n};\n\nexport const COUNTDOWN_BLOCK_DEFAULTS: BlockDefaultsFor<CountdownBlock> = {\n targetDate: \"\",\n timezone: \"UTC\",\n showDays: true,\n showHours: true,\n showMinutes: true,\n showSeconds: true,\n separator: \":\",\n digitFontSize: 32,\n digitColor: \"#1a1a1a\",\n labelColor: \"#6b7280\",\n labelFontSize: 12,\n backgroundColor: \"#ffffff\",\n labelDays: \"Days\",\n labelHours: \"Hours\",\n labelMinutes: \"Minutes\",\n labelSeconds: \"Seconds\",\n expiredMessage: \"This offer has expired\",\n expiredImageUrl: \"\",\n hideOnExpiry: false,\n};\n\nexport const DEFAULT_BLOCK_DEFAULTS: Required<BlockDefaults> = {\n title: TITLE_BLOCK_DEFAULTS,\n paragraph: PARAGRAPH_BLOCK_DEFAULTS,\n image: IMAGE_BLOCK_DEFAULTS,\n button: BUTTON_BLOCK_DEFAULTS,\n divider: DIVIDER_BLOCK_DEFAULTS,\n section: SECTION_BLOCK_DEFAULTS,\n video: VIDEO_BLOCK_DEFAULTS,\n social: SOCIAL_ICONS_BLOCK_DEFAULTS,\n spacer: SPACER_BLOCK_DEFAULTS,\n html: HTML_BLOCK_DEFAULTS,\n menu: MENU_BLOCK_DEFAULTS,\n table: TABLE_BLOCK_DEFAULTS,\n countdown: COUNTDOWN_BLOCK_DEFAULTS,\n};\n\nexport const DEFAULT_TEMPLATE_DEFAULTS: TemplateDefaults = {\n width: 600,\n backgroundColor: \"#ffffff\",\n fontFamily: \"Arial\",\n locale: \"en\",\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype\n );\n}\n\nexport function deepMergeDefaults<T extends object>(\n base: T,\n overrides: Partial<T>,\n): T {\n const result = { ...base };\n\n for (const key of Object.keys(overrides) as Array<keyof T>) {\n const baseVal = base[key];\n const overrideVal = overrides[key];\n\n if (overrideVal === undefined) {\n continue;\n }\n\n if (isPlainObject(baseVal) && isPlainObject(overrideVal)) {\n result[key] = deepMergeDefaults(\n baseVal,\n overrideVal as Partial<typeof baseVal>,\n ) as T[keyof T];\n } else {\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n","import type { Block } from \"./blocks\";\nimport { DEFAULT_TEMPLATE_DEFAULTS } from \"./defaults\";\nimport type { TemplateDefaults } from \"./defaults\";\n\nexport interface TemplateSettings {\n width: number;\n backgroundColor: string;\n fontFamily: string;\n preheaderText?: string;\n /**\n * BCP-47 language code for the rendered email's `<html lang>`. Drives\n * screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`.\n */\n locale: string;\n}\n\nexport interface TemplateContent {\n blocks: Block[];\n settings: TemplateSettings;\n}\n\nexport function createDefaultTemplateContent(\n defaultFontFamily = \"Arial\",\n templateDefaults?: TemplateDefaults,\n): TemplateContent {\n return {\n blocks: [],\n settings: {\n ...DEFAULT_TEMPLATE_DEFAULTS,\n fontFamily: defaultFontFamily,\n ...templateDefaults,\n } as TemplateSettings,\n };\n}\n","import type {\n Block,\n BlockStyles,\n BlockType,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n SpacingValue,\n TableBlock,\n TableCellData,\n TableRowData,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\nimport type { CustomBlockDefinition, CustomBlockField } from \"./custom-blocks\";\nimport type { BlockDefaults } from \"./defaults\";\nimport {\n BUTTON_BLOCK_DEFAULTS,\n COUNTDOWN_BLOCK_DEFAULTS,\n deepMergeDefaults,\n DIVIDER_BLOCK_DEFAULTS,\n HTML_BLOCK_DEFAULTS,\n IMAGE_BLOCK_DEFAULTS,\n MENU_BLOCK_DEFAULTS,\n PARAGRAPH_BLOCK_DEFAULTS,\n SECTION_BLOCK_DEFAULTS,\n SOCIAL_ICONS_BLOCK_DEFAULTS,\n SPACER_BLOCK_DEFAULTS,\n TABLE_BLOCK_DEFAULTS,\n TITLE_BLOCK_DEFAULTS,\n VIDEO_BLOCK_DEFAULTS,\n} from \"./defaults\";\n\nfunction applyDefaults<T extends object>(\n base: T,\n partial: Partial<T> | undefined,\n): T {\n if (!partial || Object.keys(partial).length === 0) return base;\n return deepMergeDefaults(base, partial);\n}\n\nexport function generateId(): string {\n return crypto.randomUUID();\n}\n\nfunction createDefaultSpacing(value = 0): SpacingValue {\n return { top: value, right: value, bottom: value, left: value };\n}\n\nfunction createDefaultStyles(padding = 10): BlockStyles {\n return {\n padding: createDefaultSpacing(padding),\n };\n}\n\nexport function createTitleBlock(\n partial: Partial<TitleBlock> = {},\n): TitleBlock {\n const base: TitleBlock = {\n id: generateId(),\n type: \"title\",\n ...TITLE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as TitleBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createParagraphBlock(\n partial: Partial<ParagraphBlock> = {},\n): ParagraphBlock {\n const base: ParagraphBlock = {\n id: generateId(),\n type: \"paragraph\",\n ...PARAGRAPH_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ParagraphBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createImageBlock(\n partial: Partial<ImageBlock> = {},\n): ImageBlock {\n const base: ImageBlock = {\n id: generateId(),\n type: \"image\",\n ...IMAGE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ImageBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createButtonBlock(\n partial: Partial<ButtonBlock> = {},\n): ButtonBlock {\n const base: ButtonBlock = {\n id: generateId(),\n type: \"button\",\n ...BUTTON_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ButtonBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createDividerBlock(\n partial: Partial<DividerBlock> = {},\n): DividerBlock {\n const base: DividerBlock = {\n id: generateId(),\n type: \"divider\",\n ...DIVIDER_BLOCK_DEFAULTS,\n styles: createDefaultStyles(20),\n } as DividerBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSectionBlock(\n partial: Partial<SectionBlock> = {},\n): SectionBlock {\n const base: SectionBlock = {\n id: generateId(),\n type: \"section\",\n ...SECTION_BLOCK_DEFAULTS,\n children: [[]],\n styles: createDefaultStyles(20),\n } as SectionBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createVideoBlock(\n partial: Partial<VideoBlock> = {},\n): VideoBlock {\n const base: VideoBlock = {\n id: generateId(),\n type: \"video\",\n ...VIDEO_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as VideoBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSocialIconsBlock(\n partial: Partial<SocialIconsBlock> = {},\n): SocialIconsBlock {\n const base: SocialIconsBlock = {\n id: generateId(),\n type: \"social\",\n icons: [],\n ...SOCIAL_ICONS_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as SocialIconsBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSpacerBlock(\n partial: Partial<SpacerBlock> = {},\n): SpacerBlock {\n const base: SpacerBlock = {\n id: generateId(),\n type: \"spacer\",\n ...SPACER_BLOCK_DEFAULTS,\n styles: createDefaultStyles(0),\n } as SpacerBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createHtmlBlock(partial: Partial<HtmlBlock> = {}): HtmlBlock {\n const base: HtmlBlock = {\n id: generateId(),\n type: \"html\",\n ...HTML_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as HtmlBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createMenuBlock(partial: Partial<MenuBlock> = {}): MenuBlock {\n const base: MenuBlock = {\n id: generateId(),\n type: \"menu\",\n items: [],\n ...MENU_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as MenuBlock;\n return applyDefaults(base, partial);\n}\n\nfunction createDefaultTableRows(columns: number, rows: number): TableRowData[] {\n return Array.from({ length: rows }, () => ({\n id: generateId(),\n cells: Array.from(\n { length: columns },\n (): TableCellData => ({\n id: generateId(),\n content: \"\",\n }),\n ),\n }));\n}\n\nexport function createTableBlock(\n partial: Partial<TableBlock> = {},\n): TableBlock {\n const base: TableBlock = {\n id: generateId(),\n type: \"table\",\n rows: createDefaultTableRows(3, 3),\n ...TABLE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as TableBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createCountdownBlock(\n partial: Partial<CountdownBlock> = {},\n): CountdownBlock {\n const base: CountdownBlock = {\n id: generateId(),\n type: \"countdown\",\n ...COUNTDOWN_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as CountdownBlock;\n return applyDefaults(base, partial);\n}\n\nfunction getFieldDefault(field: CustomBlockField): unknown {\n if (field.type === \"repeatable\") {\n return field.default ?? [];\n }\n if (field.type === \"boolean\") {\n return field.default ?? false;\n }\n if (field.type === \"number\") {\n return field.default ?? 0;\n }\n\n return field.default ?? \"\";\n}\n\nexport function createCustomBlock(\n definition: CustomBlockDefinition,\n): CustomBlock {\n const fieldValues: Record<string, unknown> = {};\n\n for (const field of definition.fields) {\n fieldValues[field.key] = getFieldDefault(field);\n }\n\n const styles = applyDefaults(createDefaultStyles(), definition.defaultStyles);\n\n return {\n id: generateId(),\n type: \"custom\",\n customType: definition.type,\n fieldValues,\n styles,\n ...(definition.dataSource ? { dataSourceFetched: false } : {}),\n };\n}\n\nexport function createBlock(\n type: BlockType,\n blockDefaults?: BlockDefaults,\n): Block {\n switch (type) {\n case \"section\":\n return createSectionBlock(blockDefaults?.section);\n case \"title\":\n return createTitleBlock(blockDefaults?.title);\n case \"paragraph\":\n return createParagraphBlock(blockDefaults?.paragraph);\n case \"image\":\n return createImageBlock(blockDefaults?.image);\n case \"button\":\n return createButtonBlock(blockDefaults?.button);\n case \"divider\":\n return createDividerBlock(blockDefaults?.divider);\n case \"video\":\n return createVideoBlock(blockDefaults?.video);\n case \"social\":\n return createSocialIconsBlock(blockDefaults?.social);\n case \"spacer\":\n return createSpacerBlock(blockDefaults?.spacer);\n case \"html\":\n return createHtmlBlock(blockDefaults?.html);\n case \"menu\":\n return createMenuBlock(blockDefaults?.menu);\n case \"table\":\n return createTableBlock(blockDefaults?.table);\n case \"countdown\":\n return createCountdownBlock(blockDefaults?.countdown);\n default:\n throw new Error(`Unknown block type: ${type}`);\n }\n}\n\nexport function cloneBlock(block: Block): Block {\n const cloned = JSON.parse(JSON.stringify(block)) as Block;\n cloned.id = generateId();\n\n if (cloned.type === \"section\") {\n cloned.children = cloned.children.map((column) =>\n column.map((child) => cloneBlock(child)),\n );\n }\n\n return cloned;\n}\n","export class EventEmitter<\n TEvents extends Record<string, unknown> = Record<string, unknown>,\n> {\n private handlers = new Map<keyof TEvents, Set<(data: never) => void>>();\n\n on<K extends keyof TEvents>(\n event: K,\n handler: (data: TEvents[K]) => void,\n ): () => void {\n if (!this.handlers.has(event)) {\n this.handlers.set(event, new Set());\n }\n\n const set = this.handlers.get(event)!;\n set.add(handler as (data: never) => void);\n\n return () => {\n set.delete(handler as (data: never) => void);\n if (set.size === 0) {\n this.handlers.delete(event);\n }\n };\n }\n\n off<K extends keyof TEvents>(\n event: K,\n handler: (data: TEvents[K]) => void,\n ): void {\n const set = this.handlers.get(event);\n if (!set) {\n return;\n }\n\n set.delete(handler as (data: never) => void);\n if (set.size === 0) {\n this.handlers.delete(event);\n }\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n const set = this.handlers.get(event);\n if (!set) {\n return;\n }\n\n // Copy to avoid issues with handlers modifying the set during iteration\n for (const handler of [...set]) {\n handler(data as never);\n }\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.handlers.delete(event);\n } else {\n this.handlers.clear();\n }\n }\n\n listenerCount(event: keyof TEvents): number {\n return this.handlers.get(event)?.size ?? 0;\n }\n}\n","import type { MergeTag } from \"./config\";\n\n// --- Syntax Presets ---\n\nexport interface SyntaxPreset {\n value: RegExp;\n logic: RegExp;\n}\n\nexport type SyntaxPresetName =\n | \"liquid\"\n | \"handlebars\"\n | \"mailchimp\"\n | \"ampscript\";\n\nexport const SYNTAX_PRESETS: Record<SyntaxPresetName, SyntaxPreset> = {\n liquid: { value: /\\{\\{.+?\\}\\}/g, logic: /\\{%-?\\s*(\\w+).*?-?%\\}/g },\n handlebars: {\n value: /\\{\\{\\{?.+?\\}?\\}\\}/g,\n logic: /\\{\\{[#/](\\w+).*?\\}\\}/g,\n },\n mailchimp: { value: /\\*\\|\\w+\\|\\*/g, logic: /\\*\\|(\\w+)[:|].*?\\|\\*/g },\n ampscript: { value: /%%=.+?=%%/g, logic: /%%\\[\\s*(\\w+).*?\\]%%/g },\n};\n\nconst SYNTAX_TRIGGER_CHARS: Record<SyntaxPresetName, string> = {\n liquid: \"{{\",\n handlebars: \"{{\",\n mailchimp: \"*|\",\n ampscript: \"%%=\",\n};\n\n/**\n * Resolves the autocomplete trigger string for a syntax preset.\n * Returns null when the syntax doesn't match any built-in preset\n * (custom regex syntax — autocomplete cannot be enabled safely).\n */\nexport function getSyntaxTriggerChar(syntax: SyntaxPreset): string | null {\n for (const name of Object.keys(SYNTAX_PRESETS) as SyntaxPresetName[]) {\n if (SYNTAX_PRESETS[name].value.source === syntax.value.source) {\n return SYNTAX_TRIGGER_CHARS[name];\n }\n }\n return null;\n}\n\nexport function resolveSyntax(\n syntax?: SyntaxPresetName | SyntaxPreset,\n): SyntaxPreset {\n if (!syntax) {\n return SYNTAX_PRESETS.liquid;\n }\n\n if (typeof syntax === \"string\") {\n return SYNTAX_PRESETS[syntax] ?? SYNTAX_PRESETS.liquid;\n }\n\n return syntax;\n}\n\n// --- Merge Tag Utilities ---\n\nfunction escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction anchoredRegex(pattern: RegExp): RegExp {\n const source = pattern.source;\n const flags = pattern.flags.replace(\"g\", \"\");\n return new RegExp(`^${source}$`, flags);\n}\n\nexport function isMergeTagValue(value: string, syntax: SyntaxPreset): boolean {\n const trimmed = value?.trim() || \"\";\n // Handlebars (and similar) value regex is liberal enough to also match\n // logic tags like `{{#each items}}`. Exclude logic-shaped tags so callers\n // that rely on this discriminator (UI segmentation, label rendering)\n // don't misclassify them.\n if (anchoredRegex(syntax.logic).test(trimmed)) {\n return false;\n }\n return anchoredRegex(syntax.value).test(trimmed);\n}\n\nexport function getMergeTagLabel(value: string, mergeTags: MergeTag[]): string {\n const found = mergeTags.find((p) => p.value === value);\n if (found) {\n return found.label;\n }\n return value;\n}\n\nexport function resolveHtmlMergeTagLabels(\n html: string,\n mergeTags: MergeTag[],\n): string {\n return rewriteSpanByAttr(html, \"data-merge-tag\", (value) =>\n getMergeTagLabel(value, mergeTags),\n );\n}\n\nexport function containsMergeTag(value: string, syntax: SyntaxPreset): boolean {\n if (!value) return false;\n\n const valueRegex = new RegExp(syntax.value.source, syntax.value.flags);\n const logicRegex = new RegExp(syntax.logic.source, syntax.logic.flags);\n\n return valueRegex.test(value) || logicRegex.test(value);\n}\n\nexport function restoreMergeTagMarkup(\n html: string,\n mergeTags: MergeTag[],\n syntax: SyntaxPreset,\n): string {\n let result = html;\n\n for (const tag of mergeTags) {\n const escaped = escapeRegExp(tag.value);\n const pattern = new RegExp(`(?<!data-merge-tag=\")${escaped}`, \"g\");\n result = result.replace(pattern, (match) => {\n const label = getMergeTagLabel(match, mergeTags);\n return `<span data-merge-tag=\"${match}\">${label}</span>`;\n });\n }\n\n const logicRegex = new RegExp(\n `(?<!data-logic-merge-tag=\")${syntax.logic.source}`,\n syntax.logic.flags,\n );\n result = result.replace(logicRegex, (match) => {\n const keyword = getLogicMergeTagKeyword(match, syntax);\n return `<span data-logic-merge-tag=\"${match}\">${keyword}</span>`;\n });\n\n return result;\n}\n\nexport function isLogicMergeTagValue(\n value: string,\n syntax: SyntaxPreset,\n): boolean {\n return anchoredRegex(syntax.logic).test(value?.trim() || \"\");\n}\n\nexport function getLogicMergeTagKeyword(\n value: string,\n syntax: SyntaxPreset,\n): string {\n const regex = new RegExp(\n syntax.logic.source,\n syntax.logic.flags.replace(\"g\", \"\"),\n );\n const match = value.match(regex);\n return match && match[1] ? match[1].toUpperCase() : value;\n}\n\nexport function resolveHtmlLogicMergeTagLabels(\n html: string,\n syntax: SyntaxPreset,\n): string {\n return rewriteSpanByAttr(html, \"data-logic-merge-tag\", (value) =>\n getLogicMergeTagKeyword(value, syntax),\n );\n}\n\n/**\n * Walk `html` and rewrite the inner text of every `<span … {attrName}=\"…\">…</span>`\n * by passing the attribute value through `relabel`. Linear in `html.length`:\n * each `indexOf` advances the cursor monotonically, and no regex backtracking\n * can run over the whole string.\n *\n * Replaces the original `/<span[^>]*…[^>]*>(.*?)<\\/span>/g` pattern, which\n * was polynomial-ReDoS over inputs that contained many `<span` starts with\n * no closing `>`.\n */\nfunction rewriteSpanByAttr(\n html: string,\n attrName: string,\n relabel: (value: string) => string,\n): string {\n // Anchored on `>` per match. `[^<>\"]*` for the attribute value fails fast\n // on a missing closing quote instead of backtracking across the input.\n const attrPattern = new RegExp(`(?:^|\\\\s)${attrName}=\"([^\"<>]*)\"`);\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<span\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 5];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 5);\n i = open + 5;\n continue;\n }\n const openEnd = html.indexOf(\">\", open + 5);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</span>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const attrs = html.substring(open + 5, openEnd);\n const attrMatch = attrPattern.exec(attrs);\n if (!attrMatch) {\n // This `<span>` isn't the one we're looking for — emit up to and\n // including the `<span` literal and let the next iteration scan\n // inward. Skipping straight to the matching `</span>` would swallow\n // any nested merge-tag span in the same loop iteration.\n out += html.substring(i, open + 5);\n i = open + 5;\n continue;\n }\n const value = attrMatch[1];\n const newLabel = relabel(value);\n out += html.substring(i, openEnd + 1);\n out += newLabel;\n out += \"</span>\";\n i = closeStart + 7;\n }\n return out;\n}\n","import type { SyntaxPreset, SyntaxPresetName } from \"./merge-tags\";\n\nexport type ViewportSize = \"desktop\" | \"mobile\";\n\nexport type UiTheme = \"light\" | \"dark\" | \"auto\";\n\nexport interface CustomFont {\n name: string;\n url: string;\n fallback?: string;\n}\n\nexport interface FontsConfig {\n defaultFallback?: string;\n defaultFont?: string;\n customFonts?: CustomFont[];\n}\n\nexport interface ExportResult {\n html: string;\n mjml: string;\n}\n\nexport interface MergeTag {\n label: string;\n value: string;\n /**\n * Optional grouping label used by the built-in merge tag picker to\n * section the list. When no tag in the configured array carries\n * `group`, the picker renders a plain flat list with no headers.\n * Ignored by the renderer and by typing-autocomplete.\n */\n group?: string;\n /**\n * Optional helper text shown beneath the tag in the built-in merge\n * tag picker. Not rendered anywhere else (toolbar, autocomplete,\n * MJML output) and not stored on the inserted document node.\n */\n description?: string;\n}\n\nexport interface MediaResult {\n url: string;\n alt?: string;\n}\n\nexport interface MergeTagsConfig {\n syntax?: SyntaxPresetName | SyntaxPreset;\n tags?: MergeTag[];\n onRequest?: () => Promise<MergeTag | null>;\n /**\n * Enables typing-based autocomplete in rich text fields. When the user\n * types the syntax opener (e.g. `{{`), a popup lists matching `tags`.\n *\n * Defaults to `true`. Effective only when `tags` is non-empty AND\n * `syntax` matches a built-in preset (custom regex syntaxes cannot be\n * mapped to a trigger string and silently disable autocomplete).\n */\n autocomplete?: boolean;\n}\n\nexport interface DisplayCondition {\n label: string;\n before: string;\n after: string;\n group?: string;\n description?: string;\n}\n\nexport interface DisplayConditionsConfig {\n conditions: DisplayCondition[];\n allowCustom?: boolean;\n}\n\nexport interface ThemeOverrides {\n bg?: string;\n bgElevated?: string;\n bgHover?: string;\n bgActive?: string;\n border?: string;\n borderLight?: string;\n text?: string;\n textMuted?: string;\n textDim?: string;\n primary?: string;\n primaryHover?: string;\n primaryLight?: string;\n secondary?: string;\n secondaryHover?: string;\n secondaryLight?: string;\n success?: string;\n successLight?: string;\n warning?: string;\n warningLight?: string;\n danger?: string;\n dangerLight?: string;\n canvasBg?: string;\n dark?: Omit<ThemeOverrides, \"dark\">;\n}\n\nexport class SdkError extends Error {\n constructor(\n message: string,\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"SdkError\";\n }\n\n get isNotFound(): boolean {\n return this.statusCode === 404;\n }\n\n get isUnauthorized(): boolean {\n return this.statusCode === 401;\n }\n\n get isServerError(): boolean {\n return this.statusCode !== undefined && this.statusCode >= 500;\n }\n}\n"],"mappings":"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"} |
+3
-4
| { | ||
| "name": "@templatical/types", | ||
| "description": "Shared TypeScript types, block factory functions, and event emitter for Templatical email editor", | ||
| "version": "0.10.0", | ||
| "version": "0.10.1", | ||
| "bugs": "https://github.com/templatical/sdk/issues", | ||
| "devDependencies": { | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^6.0.3", | ||
| "vitest": "^4.1.7", | ||
| "@templatical/media-library": "0.10.0" | ||
| "@templatical/media-library": "0.10.1" | ||
| }, | ||
@@ -43,3 +42,3 @@ "exports": { | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "build": "tsdown", | ||
| "test": "vitest run --config vitest.config.ts", | ||
@@ -46,0 +45,0 @@ "typecheck": "tsc --noEmit" |
3
-25%93443
-3.3%1416
-6.53%