{"id":50372416,"url":"https://github.com/Dragble/dragble-angular-editor","last_synced_at":"2026-06-01T10:00:42.163Z","repository":{"id":353558470,"uuid":"1218917579","full_name":"Dragble/dragble-angular-editor","owner":"Dragble","description":"AI Powered Embeddable email editor for React — build responsive HTML email templates, newsletters, and marketing campaigns with drag-and-drop","archived":false,"fork":false,"pushed_at":"2026-05-22T23:24:22.000Z","size":731,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T01:22:08.245Z","etag":null,"topics":["builder","email","email-builder","email-editor","email-marketing","email-template","html-email"],"latest_commit_sha":null,"homepage":"https://dragble.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dragble.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-23T10:48:05.000Z","updated_at":"2026-05-22T23:24:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Dragble/dragble-angular-editor","commit_stats":null,"previous_names":["dragble/dragble-angular-editor"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Dragble/dragble-angular-editor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dragble%2Fdragble-angular-editor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dragble%2Fdragble-angular-editor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dragble%2Fdragble-angular-editor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dragble%2Fdragble-angular-editor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dragble","download_url":"https://codeload.github.com/Dragble/dragble-angular-editor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dragble%2Fdragble-angular-editor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33769492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["builder","email","email-builder","email-editor","email-marketing","email-template","html-email"],"created_at":"2026-05-30T08:00:26.074Z","updated_at":"2026-06-01T10:00:42.154Z","avatar_url":"https://github.com/Dragble.png","language":"TypeScript","funding_links":[],"categories":["Third Party Components"],"sub_categories":["Editors"],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://dragble.com\"\u003e\n    \u003cimg src=\"logo.png\" alt=\"Dragble - AI-Powered Angular Email Template Builder\" width=\"300\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/dragble-angular-editor\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/dragble-angular-editor.svg\" alt=\"npm version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/Dragble/dragble-angular-editor/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"license\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# dragble-angular-editor\n\nThe **fully AI-powered** Angular editor for **email templates** and **landing pages**. Your end-users design visually with drag-and-drop — or describe what they want and watch AI agents build it live on the canvas. Powered by the built-in **Model Context Protocol (MCP)** server, connect [Claude Code](https://claude.com/code), [OpenCode](https://opencode.ai), [Codex](https://github.com/openai/codex), [Cursor](https://cursor.com), or your own AI backend directly to the editor. Structured tool calls mean guaranteed-valid output — no prompt engineering, no JSON hallucination, no broken layouts.\n\n[Dragble](https://dragble.com) brings two design experiences together in one Angular component: a polished visual editor for designers and a conversational AI surface for everyone else — backed by structured tool calls that produce guaranteed-valid HTML emails and landing pages every time.\n\n[Website](https://dragble.com) | [Documentation](https://docs.dragble.com) | [Dashboard](https://developers.dragble.com)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"editor_image.png\" alt=\"Dragble - AI-Powered Angular Email Editor with Drag and Drop\" width=\"700\" /\u003e\n\u003c/p\u003e\n\n## Features\n\n- Drag-and-drop **email template builder** with 20+ content blocks\n- **Fully AI-powered via MCP** — connect AI agents (Claude Code, OpenCode, Codex, Cursor) or your own AI backend to build designs live on the canvas. Structured tool calls mean guaranteed-valid output — no prompt engineering, no JSON hallucination\n- Responsive **HTML email** output compatible with all major email clients\n- **Newsletter editor** with merge tags, dynamic content, and display conditions\n- Visual **email designer** — no HTML/CSS knowledge required for end users\n- Export to HTML, JSON, image, PDF, or ZIP\n- Built-in image editor, AI content generation, and collaboration tools\n- Full TypeScript support\n- Works with NgModule and standalone components (Angular 14+)\n\n## Installation\n\nThe SDK is loaded from CDN automatically — you only need to install the Angular package.\n\n```bash\nnpm install dragble-angular-editor\n```\n\n```bash\nyarn add dragble-angular-editor\n```\n\n```bash\npnpm add dragble-angular-editor\n```\n\n## Editor Key\n\nAn `editorKey` is required to use the editor. You can get one by creating a project on the [Dragble Developer Dashboard](https://developers.dragble.com).\n\n## Usage\n\n### NgModule\n\n```typescript\nimport { NgModule } from \"@angular/core\";\nimport { BrowserModule } from \"@angular/platform-browser\";\nimport { DragbleEditorModule } from \"dragble-angular-editor\";\nimport { AppComponent } from \"./app.component\";\n\n@NgModule({\n  declarations: [AppComponent],\n  imports: [BrowserModule, DragbleEditorModule],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n```\n\n```typescript\nimport { Component, ViewChild } from \"@angular/core\";\nimport {\n  DragbleEditorComponent,\n  DesignJson,\n  DragbleSDK,\n} from \"dragble-angular-editor\";\n\n@Component({\n  selector: \"app-email-editor\",\n  template: `\n    \u003cdiv class=\"toolbar\"\u003e\n      \u003cbutton (click)=\"handleSave()\"\u003eSave\u003c/button\u003e\n      \u003cbutton (click)=\"handleExport()\"\u003eExport HTML\u003c/button\u003e\n    \u003c/div\u003e\n    \u003cdragble-editor\n      #editor\n      [editorKey]=\"'YOUR_EDITOR_KEY'\"\n      [editorMode]=\"'email'\"\n      [height]=\"800\"\n      (ready)=\"onReady($event)\"\n      (change)=\"onChange($event)\"\n    \u003e\u003c/dragble-editor\u003e\n  `,\n})\nexport class EmailEditorComponent {\n  @ViewChild(\"editor\") editor!: DragbleEditorComponent;\n\n  onReady(sdk: DragbleSDK): void {\n    console.log(\"Editor ready!\", sdk);\n  }\n\n  async onChange(data: { design: DesignJson; type: string }): Promise\u003cvoid\u003e {\n    // Design JSON is available directly from the callback\n    console.log(\"Design JSON:\", data.design);\n\n    // To get HTML, call exportHtml on the editor\n    const html = await this.editor.exportHtml();\n    console.log(\"HTML:\", html);\n  }\n\n  async handleSave(): Promise\u003cvoid\u003e {\n    const design = await this.editor.getDesign();\n    console.log(\"Design:\", design);\n  }\n\n  async handleExport(): Promise\u003cvoid\u003e {\n    const html = await this.editor.exportHtml();\n    console.log(\"HTML:\", html);\n  }\n}\n```\n\n### Standalone Component (Angular 17+)\n\n```typescript\nimport { Component, ViewChild } from \"@angular/core\";\nimport { DragbleEditorComponent } from \"dragble-angular-editor\";\n\n@Component({\n  selector: \"app-editor\",\n  standalone: true,\n  imports: [DragbleEditorComponent],\n  template: `\n    \u003cdragble-editor\n      #editor\n      [editorKey]=\"'YOUR_EDITOR_KEY'\"\n      [editorMode]=\"'email'\"\n      (ready)=\"onReady($event)\"\n    \u003e\u003c/dragble-editor\u003e\n  `,\n})\nexport class EditorComponent {\n  @ViewChild(\"editor\") editor!: DragbleEditorComponent;\n\n  onReady(): void {\n    console.log(\"Editor ready!\");\n  }\n}\n```\n\n## Complete Example\n\n```typescript\nimport { Component, ViewChild } from \"@angular/core\";\nimport {\n  DragbleEditorComponent,\n  DesignJson,\n  DragbleSDK,\n  EditorOptions,\n} from \"dragble-angular-editor\";\n\n@Component({\n  selector: \"app-advanced-email-builder\",\n  standalone: true,\n  imports: [DragbleEditorComponent],\n  styles: [\n    `\n      .advanced-email-builder {\n        height: 100vh;\n        display: flex;\n        flex-direction: column;\n      }\n\n      .toolbar {\n        padding: 12px;\n        border-bottom: 1px solid #ddd;\n        display: flex;\n        gap: 8px;\n        align-items: center;\n      }\n\n      .dirty-indicator {\n        color: orange;\n      }\n    `,\n  ],\n  template: `\n    \u003cdiv class=\"advanced-email-builder\"\u003e\n      \u003cdiv class=\"toolbar\"\u003e\n        \u003cbutton type=\"button\" (click)=\"editor.undo()\"\u003eUndo\u003c/button\u003e\n        \u003cbutton type=\"button\" (click)=\"editor.redo()\"\u003eRedo\u003c/button\u003e\n        \u003cbutton type=\"button\" (click)=\"editor.showPreview('desktop')\"\u003e\n          Preview\n        \u003c/button\u003e\n        \u003cbutton type=\"button\" (click)=\"handleExportHtml()\"\u003eExport HTML\u003c/button\u003e\n        \u003cbutton type=\"button\" (click)=\"handleExportImage()\"\u003e\n          Export Image\n        \u003c/button\u003e\n        \u003cspan *ngIf=\"isDirty\" class=\"dirty-indicator\"\u003eUnsaved changes\u003c/span\u003e\n      \u003c/div\u003e\n\n      \u003cdragble-editor\n        #editor\n        [editorKey]=\"'your-editor-key'\"\n        [editorMode]=\"'email'\"\n        [height]=\"'100%'\"\n        [designMode]=\"'live'\"\n        [options]=\"editorOptions\"\n        (ready)=\"handleReady($event)\"\n        (change)=\"handleChange($event)\"\n        (error)=\"handleError($event)\"\n      \u003e\u003c/dragble-editor\u003e\n    \u003c/div\u003e\n  `,\n})\nexport class AdvancedEmailBuilderComponent {\n  @ViewChild(\"editor\") editor!: DragbleEditorComponent;\n\n  isDirty = false;\n\n  editorOptions: EditorOptions = {\n    appearance: { theme: \"light\" },\n    features: {\n      preview: true,\n      undoRedo: true,\n      imageEditor: true,\n    },\n  };\n\n  handleReady(editor: DragbleSDK): void {\n    // Set merge tags (must pass a MergeTagsConfig object)\n    editor.setMergeTags({\n      customMergeTags: [\n        { name: \"First Name\", value: \"{{first_name}}\" },\n        { name: \"Last Name\", value: \"{{last_name}}\" },\n        { name: \"Company\", value: \"{{company}}\" },\n      ],\n      excludeDefaults: false,\n      sort: true,\n    });\n\n    // Set custom fonts\n    editor.setFonts({\n      showDefaultFonts: true,\n      customFonts: [{ label: \"Brand Font\", value: \"BrandFont, sans-serif\" }],\n    });\n\n    // Load saved design if available\n    const savedDesign = localStorage.getItem(\"email-design\");\n    if (savedDesign) {\n      editor.loadDesign(JSON.parse(savedDesign));\n    }\n  }\n\n  handleChange(data: { design: DesignJson; type: string }): void {\n    this.isDirty = true;\n    localStorage.setItem(\"email-design\", JSON.stringify(data.design));\n  }\n\n  async handleExportHtml(): Promise\u003cvoid\u003e {\n    const html = await this.editor.exportHtml();\n    const blob = new Blob([html], { type: \"text/html\" });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement(\"a\");\n    a.href = url;\n    a.download = \"email.html\";\n    a.click();\n    URL.revokeObjectURL(url);\n  }\n\n  async handleExportImage(): Promise\u003cvoid\u003e {\n    const data = await this.editor.exportImage();\n    window.open(data.url, \"_blank\");\n  }\n\n  handleError(error: Error): void {\n    console.error(error.message);\n  }\n}\n```\n\n## MCP — AI Integration\n\nConnect AI agents (Claude Code, OpenCode, Codex, Cursor, or your own AI backend) to the editor through the [Model Context Protocol](https://modelcontextprotocol.io). The AI calls structured tools — `add_row`, `add_heading`, `update_button`, `export_html` — that mutate design state live on the canvas. No prompt engineering, no JSON hallucination, no broken output.\n\n### Enabling MCP\n\nMCP is off by default. Set `features: { mcp: true }` to opt in:\n\n```html\n\u003cdragble-editor\n  #editor\n  [editorKey]=\"'db_pxl81cxn92wignwx'\"\n  [options]=\"{ features: { mcp: true } }\"\n\u003e\u003c/dragble-editor\u003e\n```\n\nMCP also requires a **Starter plan or higher**. Both conditions must be true — plan allows it AND SDK enables it.\n\n### Quick example — your backend controls the AI\n\n```typescript\nimport { Component, ViewChild } from \"@angular/core\";\nimport { DragbleEditorComponent } from \"dragble-angular-editor\";\n\n@Component({\n  selector: \"app-editor\",\n  template: `\n    \u003cbutton (click)=\"handleConnectAI()\"\u003eConnect AI\u003c/button\u003e\n    \u003cdragble-editor\n      #editor\n      [editorKey]=\"'db_pxl81cxn92wignwx'\"\n      [options]=\"{ features: { mcp: true } }\"\n    \u003e\u003c/dragble-editor\u003e\n  `,\n})\nexport class EditorComponent {\n  @ViewChild(\"editor\") editor!: DragbleEditorComponent;\n\n  async handleConnectAI() {\n    // The id is YOUR identifier — derive it from your own database/session\n    // so the same user editing the same document always gets the same MCP\n    // session. Example: if your logged-in user is \"alice123\" and they're\n    // editing document \"campaign-summer-2026\", build an id like this:\n    //\n    //   const id = \"alice123-campaign-summer-2026\";\n    //\n    // Format rules: 8-128 chars, only letters/digits/hyphens/underscores.\n    const userIdFromAuth = \"alice123\"; // from your auth/session\n    const docIdFromRoute = \"campaign-summer\"; // from your URL or DB row\n    const id = `${userIdFromAuth}-${docIdFromRoute}`;\n    const { sessionId } = await this.editor.editor!.connectMCP({ id });\n    // Pass sessionId to your backend — it calls MCP tools with your mcp_key\n  }\n}\n```\n\n### Quick example — end-user pairs their own AI client\n\n```typescript\nconst handleLetUserPair = async () =\u003e {\n  const editor = this.editor.editor!;\n  // Same id you'd use anywhere else for this user+document combination.\n  // 8-128 chars, only letters/digits/hyphens/underscores.\n  const id = \"alice123-campaign-summer-2026\";\n  await editor.connectMCP({ id });\n\n  // Explicitly generate a pairing code (not auto-generated)\n  const { code, expiresAt } = await editor.getPairingCode();\n  alert(`Paste this into Claude Code: ${code}`);\n};\n```\n\n### One controller per session\n\nEach session can be controlled by **either** your backend **or** an end-user's AI client (Claude Code, OpenCode), never both at the same time:\n\n- If your backend makes the first tool call → session is locked to **backend**. Pairing codes are rejected.\n- If a user pairs via pairing code first → session is locked to **paired client**. Backend tool calls are rejected.\n\nThis prevents two AI controllers from conflicting on the same design.\n\n### How it works\n\n1. **Enable MCP** in the SDK config: `features: { mcp: true }`.\n2. **Generate an MCP key** in the Dragble dashboard: Project → MCP Key → Generate. Store it in your backend env vars — never in browser code.\n3. **Call `editor.connectMCP({ id })`** where `id` is a stable identifier you control (see below).\n4. **Choose your AI path**: either your backend calls MCP tools directly (using the mcp_key), or you generate a pairing code for the end-user to connect their own AI client.\n5. **Mutations stream live** onto the editor canvas as the AI works.\n\n### The `id` parameter — why it matters\n\nThe `id` you pass to `connectMCP()` is a **Bring Your Own ID (BYOI)** that maps to your domain entities. It is NOT a random token — it is how Dragble identifies the session across browser refreshes, server restarts, and device switches.\n\n**Rules:**\n\n- 8–128 characters long\n- Only letters, numbers, hyphens, and underscores (`a-z A-Z 0-9 - _`)\n- Must be deterministic — the same user editing the same document should always produce the same `id`\n\n**Why these rules?**\n\n- The `id` is used in database lookups and URL paths — special characters or extreme lengths would break routing\n- Same `id` = resume the same session. Random UUIDs mean every page refresh creates a new session and loses AI context\n- Short IDs (\u003c 8 chars) are too easy to guess, long IDs (\u003e 128 chars) waste storage\n\n```typescript\n// Recommended: derive from your domain — concrete examples\neditor.connectMCP({ id: \"alice123-campaign-summer-2026\" }); // user + doc\neditor.connectMCP({ id: \"workspace_acme_template_welcome\" }); // workspace + template\neditor.connectMCP({ id: \"org-uber-eats-promo-q4-2026\" }); // org + campaign\neditor.connectMCP({ id: \"tenant_42_invoice_template_v3\" }); // tenant + entity\n\n// Valid but NOT recommended — random IDs break session continuity\n// (every page refresh creates a brand new session, AI loses context)\neditor.connectMCP({ id: crypto.randomUUID() });\n```\n\n### Disconnecting\n\n`disconnectMCP()` permanently destroys the session — the session cannot be reopened:\n\n```typescript\nconst { destroyed } = await editor.disconnectMCP();\n```\n\nYour backend can also force-destroy a session server-side (e.g., when a user's subscription ends):\n\n```bash\ncurl -X DELETE https://mcp.dragble.com/sessions/user-42-doc-99 \\\n  -H \"X-API-Key: db_mcp_your_key_here\"\n```\n\nIdle sessions are reaped after 2 hours of inactivity. Active sessions never expire — each tool call resets the timer.\n\n### MCP method reference\n\n| Method                                             | Returns                                                                 |\n| -------------------------------------------------- | ----------------------------------------------------------------------- |\n| `editor.connectMCP({ id, editorMode? })`           | `{ sessionId, resumed? }`                                               |\n| `editor.disconnectMCP()`                           | `{ destroyed }` — permanently deletes session                           |\n| `editor.getPairingCode()`                          | `{ code, expiresAt }` — generate a pairing code for end-user AI clients |\n| `editor.endPairing()`                              | `{ revoked }` — invalidate the active pairing code                      |\n| `editor.getMCPStatus()`                            | `{ paired: true, sessionId } \\| { paired: false, reason? }`             |\n| `editor.onAIToolFired(cb)`                         | unsubscribe fn — fires when AI calls any tool                           |\n\n### Full documentation\n\n- [MCP Overview](https://docs.dragble.com/mcp-server/overview)\n- [Credentials \u0026 Security](https://docs.dragble.com/mcp-server/credentials)\n- [AI Client Setup (OpenCode, Claude Code, Codex, etc.)](https://docs.dragble.com/mcp-server/ai-client-setup)\n\n## Inputs\n\n| Input               | Type                                     | Default      | Description                           |\n| ------------------- | ---------------------------------------- | ------------ | ------------------------------------- |\n| `editorKey`         | `string`                                 | **required** | Editor key for authentication         |\n| `design`            | `DesignJson \\| ModuleData \\| null`       | `undefined`  | Initial design to load                |\n| `editorMode`        | `EditorMode`                             | `\"email\"`    | `\"email\"`, `\"web\"`, or `\"popup\"`      |\n| `popup`             | `PopupConfig`                            | `undefined`  | Popup configuration                   |\n| `contentType`       | `EditorContentTypeValue`                 | `undefined`  | Set to `\"module\"` for single-row mode |\n| `ai`                | `AIConfig`                               | `undefined`  | AI features configuration             |\n| `locale`            | `string`                                 | `undefined`  | UI locale                             |\n| `translations`      | `Record\u003cstring, Record\u003cstring, string\u003e\u003e` | `undefined`  | Translation overrides                 |\n| `textDirection`     | `TextDirection`                          | `undefined`  | `\"ltr\"` or `\"rtl\"`                    |\n| `language`          | `Language`                               | `undefined`  | Template language                     |\n| `appearance`        | `AppearanceConfig`                       | `undefined`  | Visual customization                  |\n| `tools`             | `ToolsConfig`                            | `undefined`  | Tool enable/disable                   |\n| `customTools`       | `DragbleToolConfig[]`                    | `undefined`  | Custom tool definitions               |\n| `features`          | `FeaturesConfig`                         | `undefined`  | Feature toggles                       |\n| `fonts`             | `FontsConfig`                            | `undefined`  | Fonts configuration                   |\n| `bodyValues`        | `Record\u003cstring, unknown\u003e`                | `undefined`  | Body-level values                     |\n| `header`            | `unknown`                                | `undefined`  | Locked header row                     |\n| `footer`            | `unknown`                                | `undefined`  | Locked footer row                     |\n| `mergeTags`         | `MergeTagsConfig`                        | `undefined`  | Merge tags configuration              |\n| `specialLinks`      | `SpecialLinksConfig`                     | `undefined`  | Special links configuration           |\n| `modules`           | `Module[]`                               | `undefined`  | Custom modules                        |\n| `displayConditions` | `DisplayConditionsConfig`                | `undefined`  | Display conditions                    |\n| `editor`            | `EditorBehaviorConfig`                   | `undefined`  | Editor behavior settings              |\n| `customCSS`         | `string[]`                               | `undefined`  | Custom CSS URLs                       |\n| `customJS`          | `string[]`                               | `undefined`  | Custom JS URLs                        |\n| `height`            | `string \\| number`                       | `\"600px\"`    | Editor height                         |\n| `minHeight`         | `string \\| number`                       | `\"600px\"`    | Minimum height                        |\n| `options`           | `Partial\u003cEditorOptions\u003e`                 | `undefined`  | Additional editor options             |\n| `callbacks`         | `Omit\u003cDragbleCallbacks, ...\u003e`            | `undefined`  | SDK callbacks                         |\n\n| `collaboration` | `boolean \\| CollaborationFeaturesConfig` | `undefined` | Collaboration settings |\n| `user` | `UserInfo` | `undefined` | Current user info |\n| `designMode` | `\"edit\" \\| \"live\"` | `undefined` | Template permissions mode |\n\n## Outputs\n\n| Output          | Payload Type                           | Description                      |\n| --------------- | -------------------------------------- | -------------------------------- |\n| `ready`         | `DragbleSDK`                           | Emitted when the editor is ready |\n| `load`          | `unknown`                              | Emitted when a design is loaded  |\n| `change`        | `{ design: DesignJson; type: string }` | Emitted on every design change   |\n| `error`         | `Error`                                | Emitted when an error occurs     |\n| `commentAction` | `CommentAction`                        | Emitted on comment events        |\n\n## SDK Methods Reference\n\nAccess methods through `@ViewChild` on the Angular component. The wrapper exposes SDK methods directly, so call `this.editor.exportHtml()` from your component class. All export and getter methods return Promises.\n\n```typescript\n@ViewChild(\"editor\") editor!: DragbleEditorComponent;\n\nconst html = await this.editor.exportHtml();\n```\n\n### Design\n\n```typescript\nthis.editor.loadDesign(design, options?);                   // void\nconst result = await this.editor.loadDesignAsync(design, options?);\n// =\u003e { success, validRowsCount, invalidRowsCount, errors? }\nthis.editor.loadBlank(options?);                            // void\nconst { html, json } = await this.editor.getDesign();       // Promise\n```\n\n### Export\n\nAll export methods are **Promise-based**. There are no callback overloads.\n\n```typescript\nconst html = await this.editor.exportHtml(options?);        // Promise\u003cstring\u003e\nconst json = await this.editor.exportJson();                // Promise\u003cDesignJson\u003e\nconst text = await this.editor.exportPlainText();           // Promise\u003cstring\u003e\nconst imageData = await this.editor.exportImage(options?);  // Promise\u003cExportImageData\u003e\nconst pdfData = await this.editor.exportPdf(options?);      // Promise\u003cExportPdfData\u003e\nconst zipData = await this.editor.exportZip(options?);      // Promise\u003cExportZipData\u003e\nconst values = await this.editor.getPopupValues();          // Promise\u003cPopupValues | null\u003e\n```\n\n### Merge Tags\n\n`setMergeTags` accepts a `MergeTagsConfig` object, not a plain array.\n\n```typescript\nthis.editor.setMergeTags({\n  customMergeTags: [\n    { name: \"First Name\", value: \"{{first_name}}\" },\n    { name: \"Company\", value: \"{{company}}\" },\n  ],\n  excludeDefaults: false,\n  sort: true,\n});\nconst tags = await this.editor.getMergeTags(); // Promise\u003c(MergeTag | MergeTagGroup)[]\u003e\n```\n\n### Special Links\n\n`setSpecialLinks` accepts a `SpecialLinksConfig` object.\n\n```typescript\nthis.editor.setSpecialLinks({\n  customSpecialLinks: [{ name: \"Unsubscribe\", href: \"{{unsubscribe_url}}\" }],\n  excludeDefaults: false,\n});\nconst links = await this.editor.getSpecialLinks(); // Promise\u003c(SpecialLink | SpecialLinkGroup)[]\u003e\n```\n\n### Modules\n\n```typescript\nthis.editor.setModules(modules); // void\nthis.editor.setModulesLoading(loading); // void\nconst modules = await this.editor.getModules(); // Promise\u003cModule[]\u003e\n```\n\n### Fonts\n\n```typescript\nthis.editor.setFonts(config); // void\nconst fonts = await this.editor.getFonts(); // Promise\u003cFontsConfig\u003e\n```\n\n### Body Values\n\n```typescript\nthis.editor.setBodyValues({\n  backgroundColor: \"#f5f5f5\",\n  contentWidth: \"600px\",\n});\nconst values = await this.editor.getBodyValues(); // Promise\u003cSetBodyValuesOptions\u003e\n```\n\n### Editor Configuration\n\n```typescript\nthis.editor.setOptions(options); // void — Partial\u003cEditorOptions\u003e\nthis.editor.setToolsConfig(toolsConfig); // void\nthis.editor.setEditorMode(mode); // void\nthis.editor.setEditorConfig(config); // void\nconst config = await this.editor.getEditorConfig(); // Promise\u003cEditorBehaviorConfig\u003e\n```\n\n### Locale, Language \u0026 Text Direction\n\n```typescript\nthis.editor.setLocale(locale, translations?);            // void\nthis.editor.setLanguage(language);                       // void\nconst lang = await this.editor.getLanguage();            // Promise\u003cLanguage | null\u003e\nthis.editor.setTextDirection(direction);                 // void — 'ltr' | 'rtl'\nconst dir = await this.editor.getTextDirection();        // Promise\u003cTextDirection\u003e\n```\n\n### Appearance\n\n```typescript\nthis.editor.setAppearance(appearance); // void\n```\n\n### Undo / Redo / Save\n\n```typescript\nthis.editor.undo(); // void\nthis.editor.redo(); // void\nconst canUndo = await this.editor.canUndo(); // Promise\u003cboolean\u003e\nconst canRedo = await this.editor.canRedo(); // Promise\u003cboolean\u003e\nthis.editor.save(); // void\n```\n\n### Preview\n\n```typescript\nthis.editor.showPreview(device?);  // void — 'desktop' | 'tablet' | 'mobile'\nthis.editor.hidePreview();         // void\n```\n\n### Custom Tools\n\n```typescript\nawait this.editor.registerTool(config); // Promise\u003cvoid\u003e\nawait this.editor.unregisterTool(toolId); // Promise\u003cvoid\u003e\nconst tools = await this.editor.getTools(); // Promise\u003cArray\u003c{ id, label, baseToolType }\u003e\u003e\n```\n\n### Custom Widgets\n\n```typescript\nawait this.editor.createWidget(config); // Promise\u003cvoid\u003e\nawait this.editor.removeWidget(widgetName); // Promise\u003cvoid\u003e\n```\n\n### Collaboration \u0026 Comments\n\n```typescript\nthis.editor.showComment(commentId); // void\nthis.editor.openCommentPanel(rowId); // void\n```\n\n### Tabs \u0026 Branding\n\n```typescript\nthis.editor.updateTabs(tabs); // void\nthis.editor.setBrandingColors(config); // void\nthis.editor.registerColumns(cells); // void\n```\n\n### Display Conditions\n\n```typescript\nthis.editor.setDisplayConditions(config); // void\n```\n\n### Audit\n\n```typescript\nconst result = await this.editor.audit(options?);  // Promise\u003cAuditResult\u003e\n```\n\n### Asset Management\n\n```typescript\nconst { success, url, error } = await this.editor.uploadImage(file, options?);\nconst { assets, total } = await this.editor.listAssets(options?);\nconst { success, error } = await this.editor.deleteAsset(assetId);\nconst folders = await this.editor.listAssetFolders(parentId?);\nconst folder = await this.editor.createAssetFolder(name, parentId?);\nconst info = await this.editor.getStorageInfo();\n```\n\n### Status \u0026 Lifecycle\n\n```typescript\nthis.editor.isReady(); // boolean\nthis.editor.destroy(); // void\n```\n\n## Events\n\nAngular outputs (`(ready)`, `(load)`, `(change)`, `(error)`, `(commentAction)`) cover the common component integration points. For lower-level SDK events, subscribe with `addEventListener` after the editor is ready:\n\n```typescript\nconst unsubscribe = this.editor.addEventListener(\"design:updated\", (data) =\u003e {\n  console.log(\"Design changed:\", data);\n});\n\n// Or remove manually\nthis.editor.removeEventListener(\"design:updated\", callback);\n```\n\n### Available Events\n\n| Event                      | Description                 |\n| -------------------------- | --------------------------- |\n| `editor:ready`             | Editor initialized          |\n| `design:loaded`            | Design loaded               |\n| `design:updated`           | Design changed              |\n| `design:saved`             | Design saved                |\n| `row:selected`             | Row selected                |\n| `row:unselected`           | Row unselected              |\n| `column:selected`          | Column selected             |\n| `column:unselected`        | Column unselected           |\n| `content:selected`         | Content block selected      |\n| `content:unselected`       | Content block unselected    |\n| `content:modified`         | Content block modified      |\n| `content:added`            | Content block added         |\n| `content:deleted`          | Content block deleted       |\n| `preview:shown`            | Preview opened              |\n| `preview:hidden`           | Preview closed              |\n| `image:uploaded`           | Image uploaded successfully |\n| `image:error`              | Image upload error          |\n| `export:html`              | HTML exported               |\n| `export:plainText`         | Plain text exported         |\n| `export:image`             | Image exported              |\n| `save`                     | Save triggered              |\n| `save:success`             | Save succeeded              |\n| `save:error`               | Save failed                 |\n| `template:requested`       | Template requested          |\n| `element:selected`         | Element selected            |\n| `element:deselected`       | Element deselected          |\n| `export`                   | Export triggered            |\n| `displayCondition:applied` | Display condition applied   |\n| `displayCondition:removed` | Display condition removed   |\n| `displayCondition:updated` | Display condition updated   |\n\n## TypeScript\n\nAll types are exported from `dragble-angular-editor`:\n\n```typescript\nimport type {\n  DesignJson,\n  EditorMode,\n  DragbleSDK,\n  MergeTagsConfig,\n  AppearanceConfig,\n  FeaturesConfig,\n  ToolsConfig,\n  FontsConfig,\n} from \"dragble-angular-editor\";\n```\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to contribute to this project.\n\n## License\n\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDragble%2Fdragble-angular-editor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDragble%2Fdragble-angular-editor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDragble%2Fdragble-angular-editor/lists"}