@templatical/editor 0.6.7 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +26 -1
  2. package/dist/{AccessibilityPanel-B2MT0M58.js → AccessibilityPanel-D-PqmHdH.js} +2 -2
  3. package/dist/{AiChatSidebar-w5ek3Z76.js → AiChatSidebar-BwLECwsO.js} +1 -1
  4. package/dist/{AiFeatureMenu-ChlAWywJ.js → AiFeatureMenu-CVHKharv.js} +3 -3
  5. package/dist/{BlockA11yBadge-C0S6kPC4.js → BlockA11yBadge-BFIw0h1m.js} +2 -2
  6. package/dist/{CloudEditor-DYYaScFe.js → CloudEditor-DdZatjUe.js} +30 -21
  7. package/dist/{CollaboratorBar-Bo8vtPId.js → CollaboratorBar-VZKOv_Zn.js} +2 -2
  8. package/dist/{CommentsSidebar-BQROg36f.js → CommentsSidebar-BiRtaXYD.js} +3 -3
  9. package/dist/{CountdownBlock-Bxqe7zwL.js → CountdownBlock-Cq8A8WrM.js} +1 -1
  10. package/dist/{CountdownToolbar-CpFAnjSo.js → CountdownToolbar-CojjlZet.js} +2 -2
  11. package/dist/{DesignReferenceSidebar-CfqpcWX6.js → DesignReferenceSidebar-Ci9HIGbf.js} +2 -2
  12. package/dist/ModuleBrowserModal-D7IYx1Nh.js +206 -0
  13. package/dist/{ModulePreviewCanvas-Cw9GeGus.js → ModulePreviewCanvas-I-uMcK5G.js} +2 -2
  14. package/dist/{NumberWithSuffix-Dw8dN1Pt.js → NumberWithSuffix-CLTBb2Rj.js} +1 -1
  15. package/dist/{ParagraphEditor-DjDiUzmv.js → ParagraphEditor-BRQTNDFO.js} +156 -151
  16. package/dist/{RichTextEditorContent-CK3Om7ES.js → RichTextEditorContent-C3QSg7gd.js} +32 -28
  17. package/dist/{SaveModuleDialog-CSUPmfRP.js → SaveModuleDialog-DGGGNZfm.js} +6 -6
  18. package/dist/{SnapshotHistory-DwX2fj6N.js → SnapshotHistory-C97Iw6xw.js} +3 -3
  19. package/dist/{TemplateScoringPanel-BIAeCAEP.js → TemplateScoringPanel-V79yrEPC.js} +3 -3
  20. package/dist/{TestEmailModal-CIlBvWWn.js → TestEmailModal-B7S8H-VG.js} +3 -3
  21. package/dist/{TitleEditor-BCV5k6wj.js → TitleEditor-DhFTYzrw.js} +65 -60
  22. package/dist/{TplModal-C3Hq9b58.js → TplModal-BgABm6ju.js} +21 -17
  23. package/dist/{blockTypeIcons-iUurP50H.js → blockTypeIcons-BSf-3RJ-.js} +1 -1
  24. package/dist/bundle-stats.json +7 -7
  25. package/dist/cdn/chunks/{AccessibilityPanel-DnNB30b0.js → AccessibilityPanel-kgNBRaZV.js} +28 -28
  26. package/dist/cdn/chunks/{AccessibilityPanel-DnNB30b0.js.map → AccessibilityPanel-kgNBRaZV.js.map} +1 -1
  27. package/dist/cdn/chunks/{AiFeatureMenu-BKBh_ueF.js → AiFeatureMenu-CRkzwdMg.js} +15 -15
  28. package/dist/cdn/chunks/{AiFeatureMenu-BKBh_ueF.js.map → AiFeatureMenu-CRkzwdMg.js.map} +1 -1
  29. package/dist/cdn/chunks/{BlockA11yBadge-CnBu14Fj.js → BlockA11yBadge-BfuH2Hrg.js} +9 -9
  30. package/dist/cdn/chunks/{BlockA11yBadge-CnBu14Fj.js.map → BlockA11yBadge-BfuH2Hrg.js.map} +1 -1
  31. package/dist/cdn/chunks/{CloudEditor-CmR17piA.js → CloudEditor-h5JXI8WQ.js} +252 -243
  32. package/dist/cdn/chunks/CloudEditor-h5JXI8WQ.js.map +1 -0
  33. package/dist/cdn/chunks/{CollaboratorBar-BejZaFtY.js → CollaboratorBar-CMl3lAZU.js} +15 -15
  34. package/dist/cdn/chunks/{CollaboratorBar-BejZaFtY.js.map → CollaboratorBar-CMl3lAZU.js.map} +1 -1
  35. package/dist/cdn/chunks/{CountdownBlock-CjvUEHhE.js → CountdownBlock-D1RoaOgF.js} +18 -18
  36. package/dist/cdn/chunks/{CountdownBlock-CjvUEHhE.js.map → CountdownBlock-D1RoaOgF.js.map} +1 -1
  37. package/dist/cdn/chunks/{CountdownToolbar-Caokkqsg.js → CountdownToolbar-DltwuOer.js} +42 -42
  38. package/dist/cdn/chunks/{CountdownToolbar-Caokkqsg.js.map → CountdownToolbar-DltwuOer.js.map} +1 -1
  39. package/dist/cdn/chunks/{ModuleBrowserModal-Tb9a7L-K.js → ModuleBrowserModal-3uu2mXR9.js} +36 -36
  40. package/dist/cdn/chunks/{ModuleBrowserModal-Tb9a7L-K.js.map → ModuleBrowserModal-3uu2mXR9.js.map} +1 -1
  41. package/dist/cdn/chunks/{ModulePreviewCanvas-JWIEv5oS.js → ModulePreviewCanvas-K9CQFr7p.js} +37 -37
  42. package/dist/cdn/chunks/{ModulePreviewCanvas-JWIEv5oS.js.map → ModulePreviewCanvas-K9CQFr7p.js.map} +1 -1
  43. package/dist/cdn/chunks/{NumberWithSuffix-DrV8eumz.js → NumberWithSuffix-BIFgtqGs.js} +74 -74
  44. package/dist/cdn/chunks/{NumberWithSuffix-DrV8eumz.js.map → NumberWithSuffix-BIFgtqGs.js.map} +1 -1
  45. package/dist/cdn/chunks/{ParagraphEditor-CSfShBAO.js → ParagraphEditor-DlpZX_x4.js} +180 -176
  46. package/dist/cdn/chunks/ParagraphEditor-DlpZX_x4.js.map +1 -0
  47. package/dist/cdn/chunks/{RichTextEditorContent-DgqZzl8n.js → RichTextEditorContent-CPGT8h-O.js} +40 -37
  48. package/dist/cdn/chunks/RichTextEditorContent-CPGT8h-O.js.map +1 -0
  49. package/dist/cdn/chunks/{SaveModuleDialog-CxTwrIgx.js → SaveModuleDialog-_uaJscj1.js} +24 -24
  50. package/dist/cdn/chunks/{SaveModuleDialog-CxTwrIgx.js.map → SaveModuleDialog-_uaJscj1.js.map} +1 -1
  51. package/dist/cdn/chunks/TitleEditor-Kerz6US8.js +175 -0
  52. package/dist/cdn/chunks/TitleEditor-Kerz6US8.js.map +1 -0
  53. package/dist/cdn/chunks/{blockTypeIcons-DMt-2qR6.js → blockTypeIcons-BeOhGtoo.js} +7 -7
  54. package/dist/cdn/chunks/{blockTypeIcons-DMt-2qR6.js.map → blockTypeIcons-BeOhGtoo.js.map} +1 -1
  55. package/dist/cdn/chunks/{draggable-CNhyCGIO.js → draggable-P6QWzy4g.js} +1182 -1171
  56. package/dist/cdn/chunks/{draggable-CNhyCGIO.js.map → draggable-P6QWzy4g.js.map} +1 -1
  57. package/dist/cdn/chunks/{extensions-CUvwrffu.js → extensions-BZYkPlKr.js} +107 -108
  58. package/dist/cdn/chunks/extensions-BZYkPlKr.js.map +1 -0
  59. package/dist/cdn/chunks/{features-U3nzKc-R.js → features-DzIN-Nua.js} +925 -896
  60. package/dist/cdn/chunks/features-DzIN-Nua.js.map +1 -0
  61. package/dist/cdn/chunks/{icons-QcjADKIW.js → icons-Nvr6w13E.js} +2 -2
  62. package/dist/cdn/chunks/{icons-QcjADKIW.js.map → icons-Nvr6w13E.js.map} +1 -1
  63. package/dist/cdn/chunks/{media-library-Zcd_GInj.js → media-library-BzT7BoGU.js} +1189 -1183
  64. package/dist/cdn/chunks/media-library-BzT7BoGU.js.map +1 -0
  65. package/dist/cdn/chunks/{quality-8eo6DM3p.js → quality-C328caFm.js} +45 -45
  66. package/dist/cdn/chunks/{quality-8eo6DM3p.js.map → quality-C328caFm.js.map} +1 -1
  67. package/dist/cdn/chunks/{renderer-eHJyPiJH.js → renderer-DYpHh_uV.js} +20 -20
  68. package/dist/cdn/chunks/{renderer-eHJyPiJH.js.map → renderer-DYpHh_uV.js.map} +1 -1
  69. package/dist/cdn/chunks/{src-CthVYW_o.js → src-DKNTQJVO.js} +144 -144
  70. package/dist/cdn/chunks/{src-CthVYW_o.js.map → src-DKNTQJVO.js.map} +1 -1
  71. package/dist/cdn/chunks/{styles-CV5w3kjq.js → styles-DaTht5d7.js} +924 -899
  72. package/dist/cdn/chunks/styles-DaTht5d7.js.map +1 -0
  73. package/dist/cdn/chunks/{tiptap-IyIsncxY.js → tiptap-BwTCLVWl.js} +7 -7
  74. package/dist/cdn/chunks/{tiptap-IyIsncxY.js.map → tiptap-BwTCLVWl.js.map} +1 -1
  75. package/dist/cdn/editor.css +1 -1
  76. package/dist/cdn/editor.js +197 -146
  77. package/dist/cdn/editor.js.map +1 -1
  78. package/dist/{extensions-BF39Siqk.js → extensions-D-J02CiP.js} +50 -51
  79. package/dist/index.d.ts +56 -11
  80. package/dist/{keys-Bqs_0du9.js → keys-B5SJtPWf.js} +3 -3
  81. package/dist/style.css +1 -1
  82. package/dist/{styles-BgmKdc2x.js → styles-D_fEz49o.js} +497 -472
  83. package/dist/templatical-editor.js +182 -131
  84. package/dist/{useCloudI18n-CL_AwWwi.js → useCloudI18n-ByEMykjO.js} +1 -1
  85. package/dist/{useEditorCore-CnXrv71D.js → useEditorCore-uCU9Ny0M.js} +637 -619
  86. package/dist/{useI18n-CgmQftNf.js → useI18n-PEB8ioi_.js} +1 -1
  87. package/dist/{useMergeTag-vpwrZ9eQ.js → useMergeTag-C47xwU7X.js} +2 -2
  88. package/dist/usePopoverRoot-BxJrqnMD.js +8 -0
  89. package/package.json +7 -7
  90. package/dist/ModuleBrowserModal-CKhsaPnA.js +0 -206
  91. package/dist/cdn/chunks/CloudEditor-CmR17piA.js.map +0 -1
  92. package/dist/cdn/chunks/ParagraphEditor-CSfShBAO.js.map +0 -1
  93. package/dist/cdn/chunks/RichTextEditorContent-DgqZzl8n.js.map +0 -1
  94. package/dist/cdn/chunks/TitleEditor-DlqV7ODD.js +0 -171
  95. package/dist/cdn/chunks/TitleEditor-DlqV7ODD.js.map +0 -1
  96. package/dist/cdn/chunks/extensions-CUvwrffu.js.map +0 -1
  97. package/dist/cdn/chunks/features-U3nzKc-R.js.map +0 -1
  98. package/dist/cdn/chunks/media-library-Zcd_GInj.js.map +0 -1
  99. package/dist/cdn/chunks/styles-CV5w3kjq.js.map +0 -1
  100. /package/dist/{de-DcVOh9Fp.js → de-BhIWu_bO.js} +0 -0
  101. /package/dist/{de-DCaaCE5s.js → de-Di4MEjjx.js} +0 -0
  102. /package/dist/{emojiData-PQyVa4bU.js → emojiData-DX3E0XT-.js} +0 -0
  103. /package/dist/{en-DXCyK4-X.js → en-BSuzi-Pd.js} +0 -0
  104. /package/dist/{en-TZVJ_f6v.js → en-D7HRbYah.js} +0 -0
  105. /package/dist/{pt-BR-Vq7D7c11.js → pt-BR-CCVBRais.js} +0 -0
  106. /package/dist/{pt-BR-BMGasLBa.js → pt-BR-K32lt6YP.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"editor.js","names":[],"sources":["../../src/Editor.vue","../../src/Editor.vue","../../src/i18n/index.ts","../../src/utils/toMjml.ts","../../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onMounted, onUnmounted } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport { useEditor } from \"@templatical/core\";\nimport type { TemplateContent, UiTheme } from \"@templatical/types\";\nimport { useEditorCore } from \"./composables/useEditorCore\";\nimport { resolveAccessibilityOptions } from \"./utils/resolveAccessibilityOptions\";\nimport type { Translations } from \"./i18n\";\nimport type { UseFontsReturn } from \"./composables/useFonts\";\n\nimport { RotateCcw } from \"@lucide/vue\";\nimport Canvas from \"./components/Canvas.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport ViewportToggle from \"./components/ViewportToggle.vue\";\nimport PreviewToggle from \"./components/PreviewToggle.vue\";\nimport DarkModeToggle from \"./components/DarkModeToggle.vue\";\nimport EditorFooter from \"./components/EditorFooter.vue\";\nimport \"./styles/index.css\";\n\nconst props = defineProps<{\n config: TemplaticalEditorConfig;\n translations: Translations;\n fontsManager: UseFontsReturn;\n}>();\n\n// --- Core editor state ---\nconst editor = useEditor({\n content: props.config.content!,\n templateDefaults: props.config.templateDefaults,\n});\n\n// --- Shared editor core (composables, provides, plugins, keyboard) ---\nconst core = useEditorCore({\n editor,\n config: {\n uiTheme: props.config.uiTheme,\n theme: props.config.theme,\n blockDefaults: props.config.blockDefaults,\n customBlocks: props.config.customBlocks,\n mergeTags: props.config.mergeTags,\n displayConditions: props.config.displayConditions,\n onRequestMedia: props.config.onRequestMedia,\n accessibility: resolveAccessibilityOptions(props.config),\n onSave: props.config.onSave\n ? () =>\n props.config.onSave!(JSON.parse(JSON.stringify(editor.state.content)))\n : undefined,\n },\n translations: props.translations,\n fontsManager: props.fontsManager,\n autoSaveOptions: props.config.onChange\n ? {\n onChange: () =>\n props.config.onChange!(\n JSON.parse(JSON.stringify(editor.state.content)),\n ),\n }\n : null,\n});\n\n// --- Lifecycle ---\nonMounted(async () => {\n await props.fontsManager.loadCustomFonts();\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\n// --- Public API (accessed via template ref from init()) ---\ndefineExpose({\n getContent: () => editor.content.value,\n setContent: (content: TemplateContent) => editor.setContent(content),\n setTheme: (theme: UiTheme) => editor.setUiTheme(theme),\n renderCustomBlock: core.registry.renderCustomBlock,\n});\n</script>\n\n<template>\n <div\n class=\"tpl tpl:relative tpl:h-full tpl:overflow-hidden\"\n :class=\"{ 'tpl:dark': editor.state.darkMode }\"\n :data-tpl-theme=\"core.resolvedTheme.value\"\n :style=\"core.themeStyles.value\"\n >\n <!-- Header — absolute, full width, above everything -->\n <header\n class=\"tpl-header tpl:absolute tpl:top-0 tpl:right-0 tpl:left-0 tpl:z-50 tpl:grid tpl:h-14 tpl:grid-cols-[1fr_auto_1fr] tpl:items-center tpl:px-4 tpl:shadow-[var(--tpl-shadow-md)] tpl:border-b tpl:border-[var(--tpl-border)]\"\n style=\"\n background-color: color-mix(in srgb, var(--tpl-bg) 80%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n \"\n >\n <!-- Left: empty (reserved for embedder customization) -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-2.5\"></div>\n\n <!-- Center: viewport + preview + dark mode -->\n <div class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-10\">\n <ViewportToggle\n :viewport=\"editor.state.viewport\"\n @change=\"editor.setViewport\"\n />\n <DarkModeToggle\n :dark-mode=\"editor.state.darkMode\"\n @change=\"editor.setDarkMode\"\n />\n <PreviewToggle\n :preview-mode=\"editor.state.previewMode\"\n @change=\"editor.setPreviewMode\"\n />\n </div>\n\n <!-- Right: empty in OSS mode -->\n <div\n class=\"tpl:flex tpl:min-w-[200px] tpl:items-center tpl:justify-end tpl:gap-3\"\n ></div>\n </header>\n\n <!-- Left sidebar — absolute, below header -->\n <Sidebar v-show=\"!editor.state.previewMode\" />\n\n <!-- Canvas area — absolute, fills remaining space -->\n <div\n class=\"tpl-body tpl:absolute tpl:bottom-0 tpl:overflow-auto tpl:bg-[var(--tpl-canvas-bg)]\"\n style=\"transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)\"\n :class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n 'tpl:top-14',\n ]\"\n >\n <!-- Restore hidden blocks button -->\n <div class=\"tpl:sticky tpl:top-0 tpl:z-40 tpl:h-0\">\n <Transition name=\"tpl-restore-btn\">\n <button\n v-if=\"core.conditionPreview.hasHiddenBlocks.value\"\n class=\"tpl:absolute tpl:left-1/2 tpl:top-2 tpl:-translate-x-1/2 tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-full tpl:border tpl:px-3.5 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:whitespace-nowrap tpl:shadow-md tpl:hover:opacity-80 tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)] tpl:border-[var(--tpl-warning)]\"\n style=\"backdrop-filter: blur(8px)\"\n @click=\"core.conditionPreview.reset()\"\n >\n <RotateCcw :size=\"13\" :stroke-width=\"2\" />\n {{ core.t.blockSettings.restoreHiddenBlocks }}\n </button>\n </Transition>\n </div>\n <div class=\"tpl:flex tpl:justify-center tpl:p-8\">\n <Canvas\n :viewport=\"editor.state.viewport\"\n :content=\"editor.content.value\"\n :selected-block-id=\"editor.state.selectedBlockId\"\n :dark-mode=\"editor.state.darkMode\"\n :preview-mode=\"editor.state.previewMode\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n ]\"\n />\n\n <!-- Keyboard reorder announcement region (visually hidden, screen-reader live) -->\n <div\n class=\"tpl-sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n :aria-label=\"core.t.landmarks.reorderAnnouncements\"\n >\n {{ core.keyboardReorder.announcement.value }}\n </div>\n\n <!-- Right sidebar — persisted with v-show -->\n <RightSidebar\n v-show=\"!editor.state.previewMode\"\n :selected-block=\"editor.selectedBlock.value\"\n :settings=\"editor.content.value.settings\"\n @update-block=\"\n (updates) => editor.updateBlock(editor.state.selectedBlockId!, updates)\n \"\n @delete-block=\"\n () => {\n if (editor.state.selectedBlockId) {\n core.blockActions.deleteBlock(editor.state.selectedBlockId);\n }\n }\n \"\n @duplicate-block=\"\n () => {\n if (editor.selectedBlock.value) {\n core.blockActions.duplicateBlock(editor.selectedBlock.value);\n }\n }\n \"\n @update-settings=\"(updates) => editor.updateSettings(updates)\"\n />\n </div>\n</template>\n\n<style scoped>\n.tpl-restore-btn-enter-active {\n transition:\n opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-restore-btn-leave-active {\n transition:\n opacity 150ms ease-in,\n transform 150ms ease-in;\n}\n\n.tpl-restore-btn-enter-from,\n.tpl-restore-btn-leave-to {\n opacity: 0;\n transform: translateY(-8px) scale(0.9);\n}\n\n.tpl-restore-btn-enter-to,\n.tpl-restore-btn-leave-from {\n opacity: 1;\n transform: translateY(0) scale(1);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, onUnmounted } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport { useEditor } from \"@templatical/core\";\nimport type { TemplateContent, UiTheme } from \"@templatical/types\";\nimport { useEditorCore } from \"./composables/useEditorCore\";\nimport { resolveAccessibilityOptions } from \"./utils/resolveAccessibilityOptions\";\nimport type { Translations } from \"./i18n\";\nimport type { UseFontsReturn } from \"./composables/useFonts\";\n\nimport { RotateCcw } from \"@lucide/vue\";\nimport Canvas from \"./components/Canvas.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport ViewportToggle from \"./components/ViewportToggle.vue\";\nimport PreviewToggle from \"./components/PreviewToggle.vue\";\nimport DarkModeToggle from \"./components/DarkModeToggle.vue\";\nimport EditorFooter from \"./components/EditorFooter.vue\";\nimport \"./styles/index.css\";\n\nconst props = defineProps<{\n config: TemplaticalEditorConfig;\n translations: Translations;\n fontsManager: UseFontsReturn;\n}>();\n\n// --- Core editor state ---\nconst editor = useEditor({\n content: props.config.content!,\n templateDefaults: props.config.templateDefaults,\n});\n\n// --- Shared editor core (composables, provides, plugins, keyboard) ---\nconst core = useEditorCore({\n editor,\n config: {\n uiTheme: props.config.uiTheme,\n theme: props.config.theme,\n blockDefaults: props.config.blockDefaults,\n customBlocks: props.config.customBlocks,\n mergeTags: props.config.mergeTags,\n displayConditions: props.config.displayConditions,\n onRequestMedia: props.config.onRequestMedia,\n accessibility: resolveAccessibilityOptions(props.config),\n onSave: props.config.onSave\n ? () =>\n props.config.onSave!(JSON.parse(JSON.stringify(editor.state.content)))\n : undefined,\n },\n translations: props.translations,\n fontsManager: props.fontsManager,\n autoSaveOptions: props.config.onChange\n ? {\n onChange: () =>\n props.config.onChange!(\n JSON.parse(JSON.stringify(editor.state.content)),\n ),\n }\n : null,\n});\n\n// --- Lifecycle ---\nonMounted(async () => {\n await props.fontsManager.loadCustomFonts();\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\n// --- Public API (accessed via template ref from init()) ---\ndefineExpose({\n getContent: () => editor.content.value,\n setContent: (content: TemplateContent) => editor.setContent(content),\n setTheme: (theme: UiTheme) => editor.setUiTheme(theme),\n renderCustomBlock: core.registry.renderCustomBlock,\n});\n</script>\n\n<template>\n <div\n class=\"tpl tpl:relative tpl:h-full tpl:overflow-hidden\"\n :class=\"{ 'tpl:dark': editor.state.darkMode }\"\n :data-tpl-theme=\"core.resolvedTheme.value\"\n :style=\"core.themeStyles.value\"\n >\n <!-- Header — absolute, full width, above everything -->\n <header\n class=\"tpl-header tpl:absolute tpl:top-0 tpl:right-0 tpl:left-0 tpl:z-50 tpl:grid tpl:h-14 tpl:grid-cols-[1fr_auto_1fr] tpl:items-center tpl:px-4 tpl:shadow-[var(--tpl-shadow-md)] tpl:border-b tpl:border-[var(--tpl-border)]\"\n style=\"\n background-color: color-mix(in srgb, var(--tpl-bg) 80%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n \"\n >\n <!-- Left: empty (reserved for embedder customization) -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-2.5\"></div>\n\n <!-- Center: viewport + preview + dark mode -->\n <div class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-10\">\n <ViewportToggle\n :viewport=\"editor.state.viewport\"\n @change=\"editor.setViewport\"\n />\n <DarkModeToggle\n :dark-mode=\"editor.state.darkMode\"\n @change=\"editor.setDarkMode\"\n />\n <PreviewToggle\n :preview-mode=\"editor.state.previewMode\"\n @change=\"editor.setPreviewMode\"\n />\n </div>\n\n <!-- Right: empty in OSS mode -->\n <div\n class=\"tpl:flex tpl:min-w-[200px] tpl:items-center tpl:justify-end tpl:gap-3\"\n ></div>\n </header>\n\n <!-- Left sidebar — absolute, below header -->\n <Sidebar v-show=\"!editor.state.previewMode\" />\n\n <!-- Canvas area — absolute, fills remaining space -->\n <div\n class=\"tpl-body tpl:absolute tpl:bottom-0 tpl:overflow-auto tpl:bg-[var(--tpl-canvas-bg)]\"\n style=\"transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)\"\n :class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n 'tpl:top-14',\n ]\"\n >\n <!-- Restore hidden blocks button -->\n <div class=\"tpl:sticky tpl:top-0 tpl:z-40 tpl:h-0\">\n <Transition name=\"tpl-restore-btn\">\n <button\n v-if=\"core.conditionPreview.hasHiddenBlocks.value\"\n class=\"tpl:absolute tpl:left-1/2 tpl:top-2 tpl:-translate-x-1/2 tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-full tpl:border tpl:px-3.5 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:whitespace-nowrap tpl:shadow-md tpl:hover:opacity-80 tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)] tpl:border-[var(--tpl-warning)]\"\n style=\"backdrop-filter: blur(8px)\"\n @click=\"core.conditionPreview.reset()\"\n >\n <RotateCcw :size=\"13\" :stroke-width=\"2\" />\n {{ core.t.blockSettings.restoreHiddenBlocks }}\n </button>\n </Transition>\n </div>\n <div class=\"tpl:flex tpl:justify-center tpl:p-8\">\n <Canvas\n :viewport=\"editor.state.viewport\"\n :content=\"editor.content.value\"\n :selected-block-id=\"editor.state.selectedBlockId\"\n :dark-mode=\"editor.state.darkMode\"\n :preview-mode=\"editor.state.previewMode\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n ]\"\n />\n\n <!-- Keyboard reorder announcement region (visually hidden, screen-reader live) -->\n <div\n class=\"tpl-sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n :aria-label=\"core.t.landmarks.reorderAnnouncements\"\n >\n {{ core.keyboardReorder.announcement.value }}\n </div>\n\n <!-- Right sidebar — persisted with v-show -->\n <RightSidebar\n v-show=\"!editor.state.previewMode\"\n :selected-block=\"editor.selectedBlock.value\"\n :settings=\"editor.content.value.settings\"\n @update-block=\"\n (updates) => editor.updateBlock(editor.state.selectedBlockId!, updates)\n \"\n @delete-block=\"\n () => {\n if (editor.state.selectedBlockId) {\n core.blockActions.deleteBlock(editor.state.selectedBlockId);\n }\n }\n \"\n @duplicate-block=\"\n () => {\n if (editor.selectedBlock.value) {\n core.blockActions.duplicateBlock(editor.selectedBlock.value);\n }\n }\n \"\n @update-settings=\"(updates) => editor.updateSettings(updates)\"\n />\n </div>\n</template>\n\n<style scoped>\n.tpl-restore-btn-enter-active {\n transition:\n opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-restore-btn-leave-active {\n transition:\n opacity 150ms ease-in,\n transform 150ms ease-in;\n}\n\n.tpl-restore-btn-enter-from,\n.tpl-restore-btn-leave-to {\n opacity: 0;\n transform: translateY(-8px) scale(0.9);\n}\n\n.tpl-restore-btn-enter-to,\n.tpl-restore-btn-leave-from {\n opacity: 1;\n transform: translateY(0) scale(1);\n}\n</style>\n","/// <reference types=\"vite/client\" />\n\nimport type en from \"./locales/en\";\nimport type cloudEn from \"./locales/cloud/en\";\n\nexport type Translations = typeof en;\nexport type CloudTranslations = typeof cloudEn;\nexport type TranslationKey = keyof Translations;\n\n// Vite resolves these globs at build time. Adding a new locale file\n// automatically registers it — no array to keep in sync.\nconst ossModules = import.meta.glob<{ default: Translations }>(\n \"./locales/*.ts\",\n);\nconst cloudModules = import.meta.glob<{ default: CloudTranslations }>(\n \"./locales/cloud/*.ts\",\n);\n\nfunction localesFromGlob(modules: Record<string, unknown>): string[] {\n return Object.keys(modules)\n .map((path) => path.match(/\\/([^/]+)\\.ts$/)?.[1])\n .filter((locale): locale is string => Boolean(locale));\n}\n\nconst supportedLocales = localesFromGlob(ossModules);\nconst supportedCloudLocales = localesFromGlob(cloudModules);\n\nfunction canonicalize(locale: string): string {\n return locale.trim().replace(/_/g, \"-\").toLowerCase();\n}\n\nfunction findSupportedLocale(\n locale: string,\n supported: string[],\n): string | undefined {\n const canonical = canonicalize(locale);\n return supported.find((s) => canonicalize(s) === canonical);\n}\n\n/**\n * Get the base language code from a locale string.\n * e.g., 'en-GB' -> 'en', 'de_DE' -> 'de'\n */\nexport function getBaseLocale(locale: string): string {\n return canonicalize(locale).split(\"-\")[0];\n}\n\nfunction tryResolveLocale(\n locale: string,\n supported: string[],\n): string | undefined {\n return (\n findSupportedLocale(locale, supported) ??\n findSupportedLocale(getBaseLocale(locale), supported)\n );\n}\n\nfunction resolveLocale(locale: string, supported: string[]): string {\n return tryResolveLocale(locale, supported) ?? \"en\";\n}\n\n/**\n * Load OSS translations for a given locale.\n * Falls back to English if the locale is not supported.\n */\nexport async function loadTranslations(locale: string): Promise<Translations> {\n const target = resolveLocale(locale, supportedLocales);\n const loader = ossModules[`./locales/${target}.ts`];\n const mod = await loader();\n return mod.default;\n}\n\n/**\n * Load cloud translations for a given locale.\n * Cloud translations cover features available only via initCloud() —\n * AI, comments, collaboration, scoring, snapshots, plan limits, etc.\n * OSS contributors don't need to translate these; cloud locales fall back\n * to English independently of the OSS locale.\n */\nexport async function loadCloudTranslations(\n locale: string,\n): Promise<CloudTranslations> {\n const target = resolveLocale(locale, supportedCloudLocales);\n const loader = cloudModules[`./locales/cloud/${target}.ts`];\n const mod = await loader();\n return mod.default;\n}\n\n/** Check if a locale has OSS translations (matched by exact locale, then base). */\nexport function isLocaleSupported(locale: string): boolean {\n return tryResolveLocale(locale, supportedLocales) !== undefined;\n}\n\n/** Check if a locale has cloud translations (matched by exact locale, then base). */\nexport function isCloudLocaleSupported(locale: string): boolean {\n return tryResolveLocale(locale, supportedCloudLocales) !== undefined;\n}\n\n/** List of OSS-supported locales. */\nexport function getSupportedLocales(): string[] {\n return [...supportedLocales];\n}\n\n/** List of cloud-supported locales. May be a subset of OSS locales. */\nexport function getSupportedCloudLocales(): string[] {\n return [...supportedCloudLocales];\n}\n","import type { CustomBlock, TemplateContent } from \"@templatical/types\";\n\n/**\n * Minimal slice of the editor surface needed by `toMjmlForInstance`.\n * Decoupled from the full `TemplaticalEditor` type so this helper can be\n * tested in isolation with a stub object.\n */\nexport interface ToMjmlSource {\n getContent(): TemplateContent;\n renderCustomBlock(block: CustomBlock): Promise<string>;\n}\n\n/**\n * Lazy-load `@templatical/renderer` and render the editor's current content\n * to MJML, wiring the editor's own custom block resolver into the renderer's\n * `renderCustomBlock` callback.\n *\n * The renderer is an optional peer dependency (small, MIT-licensed). It is\n * only loaded when an export is actually requested. Consumers that don't\n * need MJML export at all (e.g., embedding the editor in an app where the\n * backend handles export) can omit the install entirely; calling `toMjml()`\n * in that case throws a clear error naming the missing package.\n *\n * The dynamic import is cached by the module system, so subsequent calls\n * skip the import overhead.\n */\nexport async function toMjmlForInstance(\n instance: ToMjmlSource,\n): Promise<string> {\n let renderer: typeof import(\"@templatical/renderer\");\n try {\n renderer = await import(\"@templatical/renderer\");\n } catch {\n throw new Error(\n \"[Templatical] toMjml() requires the @templatical/renderer package. Please install it.\",\n );\n }\n return renderer.renderToMjml(instance.getContent(), {\n renderCustomBlock: instance.renderCustomBlock,\n });\n}\n","import { createApp, h, ref, type App, type Ref } from \"vue\";\nimport { INIT_TIMEOUT_MS } from \"./constants/timeouts\";\nimport type {\n BlockDefaults,\n CustomBlock,\n CustomBlockDefinition,\n DisplayConditionsConfig,\n FontsConfig,\n MediaResult,\n MergeTagsConfig,\n SaveResult,\n Template,\n TemplateContent,\n TemplateDefaults,\n ThemeOverrides,\n UiTheme,\n} from \"@templatical/types\";\nimport type { MediaRequestContext } from \"@templatical/media-library\";\n\nimport Editor from \"./Editor.vue\";\nimport { loadTranslations, loadCloudTranslations } from \"./i18n\";\nimport { useFonts } from \"./composables\";\nimport { toMjmlForInstance } from \"./utils/toMjml\";\n\n// ---------------------------------------------------------------------------\n// OSS config + return types\n// ---------------------------------------------------------------------------\n\nexport interface TemplaticalEditorConfig {\n container: string | HTMLElement;\n content?: TemplateContent;\n\n onChange?: (content: TemplateContent) => void;\n onSave?: (content: TemplateContent) => void;\n onError?: (error: Error) => void;\n\n onRequestMedia?: OnRequestMedia;\n\n mergeTags?: MergeTagsConfig;\n displayConditions?: DisplayConditionsConfig;\n customBlocks?: CustomBlockDefinition[];\n fonts?: FontsConfig;\n\n blockDefaults?: BlockDefaults;\n templateDefaults?: TemplateDefaults;\n\n theme?: ThemeOverrides;\n uiTheme?: UiTheme;\n locale?: string;\n\n /**\n * Show the \"Powered by Templatical\" footer. Defaults to `true`.\n * Set to `false` to hide the footer (no attribution required by the license).\n */\n branding?: boolean;\n\n /**\n * Accessibility linter (`@templatical/quality`) configuration.\n *\n * - When unset, the linter loads on demand once the user opens the panel.\n * - When `disabled: true`, the optional peer is never imported (saves the\n * chunk download) and the sidebar tab + inline badges are suppressed.\n * - `rules`/`thresholds` follow the shape exported by `@templatical/quality`.\n */\n accessibility?: import(\"@templatical/quality\").A11yOptions;\n}\n\n/** Function type for media browser requests, used by both OSS and Cloud editors. */\nexport type OnRequestMedia = (\n context?: MediaRequestContext,\n) => Promise<MediaResult | null>;\n\ninterface TemplaticalEditorBase {\n getContent(): TemplateContent;\n setContent(content: TemplateContent): void;\n setTheme(theme: UiTheme): void;\n unmount(): void;\n}\n\nexport interface TemplaticalEditor extends TemplaticalEditorBase {\n /**\n * Render the current template to an MJML string. Resolves custom blocks\n * via the editor's internal block registry. Throws if the optional\n * `@templatical/renderer` package is not installed.\n */\n toMjml(): Promise<string>;\n /**\n * Render a single custom block to its HTML representation, using the\n * registered custom block definition's template and the block's current\n * field values. Exposed for headless callers that want to reuse the\n * editor's renderer (e.g., to drive `@templatical/renderer`'s\n * `renderCustomBlock` option from outside the editor instance).\n */\n renderCustomBlock(block: CustomBlock): Promise<string>;\n}\n\n/**\n * Cloud editor does not expose `toMjml` or `renderCustomBlock`: the cloud\n * backend performs MJML conversion server-side with additional processing\n * (e.g., signed image URLs, attachment handling) that isn't available client\n * side. Use the cloud `save()` flow to persist content; the backend handles\n * MJML/HTML export from there.\n */\nexport interface TemplaticalCloudEditor extends TemplaticalEditorBase {\n create(content?: TemplateContent): Promise<Template>;\n load(templateId: string): Promise<Template>;\n save(): Promise<SaveResult>;\n}\n\n// ---------------------------------------------------------------------------\n// OSS init — sync\n// ---------------------------------------------------------------------------\n\nlet appInstance: App | null = null;\nconst editorRef: Ref<InstanceType<typeof Editor> | null> = ref(null);\n\nexport async function init(\n config: TemplaticalEditorConfig,\n): Promise<TemplaticalEditor> {\n const container =\n typeof config.container === \"string\"\n ? document.querySelector(config.container)\n : config.container;\n\n if (!container) {\n throw new Error(\n `[Templatical] Container element not found: ${config.container}`,\n );\n }\n\n // Load translations before mounting so child components can use useI18n synchronously\n const translations = await loadTranslations(config.locale ?? \"en\");\n\n // Create fonts manager to pass to Editor\n const fontsManager = useFonts(config.fonts);\n\n // Unmount any prior app *after* awaits — checking before the await would\n // let two concurrent init() calls both pass the guard while appInstance is\n // still null and orphan the first-mounted app.\n if (appInstance) {\n unmount();\n }\n\n appInstance = createApp({\n setup() {\n return () =>\n h(Editor, {\n config,\n translations,\n fontsManager,\n ref: editorRef,\n });\n },\n });\n\n appInstance.mount(container);\n\n const instance: TemplaticalEditor = {\n getContent() {\n if (editorRef.value) {\n return JSON.parse(JSON.stringify(editorRef.value.getContent()));\n }\n return JSON.parse(JSON.stringify(config.content));\n },\n setContent(content: TemplateContent) {\n if (editorRef.value) {\n editorRef.value.setContent(content);\n }\n config.content = content;\n },\n setTheme(theme: UiTheme) {\n if (editorRef.value) {\n editorRef.value.setTheme(theme);\n }\n },\n unmount,\n renderCustomBlock(block: CustomBlock) {\n if (!editorRef.value) {\n return Promise.reject(new Error(\"[Templatical] Editor not ready\"));\n }\n return editorRef.value.renderCustomBlock(block);\n },\n toMjml: () => toMjmlForInstance(instance),\n };\n\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// Cloud init — async, flat config, tree-shaken when not called\n// ---------------------------------------------------------------------------\n\nlet cloudAppInstance: App | null = null;\n\nconst cloudEditorRef: Ref<InstanceType<\n typeof import(\"./cloud/CloudEditor.vue\").default\n> | null> = ref(null);\n\nexport async function initCloud(\n config: import(\"./cloud/CloudEditor.vue\").TemplaticalCloudEditorConfig,\n): Promise<TemplaticalCloudEditor> {\n const container =\n typeof config.container === \"string\"\n ? document.querySelector(config.container)\n : config.container;\n\n if (!container) {\n throw new Error(\n `[Templatical] Container element not found: ${config.container}`,\n );\n }\n\n // Dynamic import — CloudEditor.vue is tree-shaken from the OSS bundle\n const { default: CloudEditor } = await import(\"./cloud/CloudEditor.vue\");\n\n // Load OSS + cloud translations in parallel so child components can use\n // useI18n / useCloudI18n synchronously\n const [translations, cloudTranslations] = await Promise.all([\n loadTranslations(config.locale ?? \"en\"),\n loadCloudTranslations(config.locale ?? \"en\"),\n ]);\n\n // Create fonts manager to pass to CloudEditor\n const fontsManager = useFonts(config.fonts);\n\n // Unmount any prior app *after* awaits — checking before the await would\n // let two concurrent initCloud() calls both pass the guard while\n // cloudAppInstance is still null and orphan the first-mounted app.\n if (cloudAppInstance) {\n unmountCloud();\n }\n\n // Promise that resolves when CloudEditor emits 'ready'\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"[Templatical] Cloud editor initialization timed out\"));\n }, INIT_TIMEOUT_MS);\n\n cloudAppInstance = createApp({\n setup() {\n return () =>\n h(CloudEditor, {\n config,\n translations,\n cloudTranslations,\n fontsManager,\n ref: cloudEditorRef,\n onReady: () => {\n clearTimeout(timeout);\n resolve();\n },\n });\n },\n });\n\n cloudAppInstance.mount(container);\n });\n\n await readyPromise;\n\n const instance: TemplaticalCloudEditor = {\n getContent() {\n if (cloudEditorRef.value) {\n return JSON.parse(JSON.stringify(cloudEditorRef.value.getContent()));\n }\n return JSON.parse(JSON.stringify(config.content));\n },\n setContent(content: TemplateContent) {\n if (cloudEditorRef.value) {\n cloudEditorRef.value.setContent(content);\n }\n },\n setTheme(theme: UiTheme) {\n if (cloudEditorRef.value) {\n cloudEditorRef.value.setTheme(theme);\n }\n },\n unmount: unmountCloud,\n create(content?: TemplateContent) {\n if (!cloudEditorRef.value) {\n return Promise.reject(\n new Error(\"[Templatical] Cloud editor not ready\"),\n );\n }\n return cloudEditorRef.value.create(content);\n },\n load(templateId: string) {\n if (!cloudEditorRef.value) {\n return Promise.reject(\n new Error(\"[Templatical] Cloud editor not ready\"),\n );\n }\n return cloudEditorRef.value.load(templateId);\n },\n save() {\n if (!cloudEditorRef.value) {\n return Promise.reject(\n new Error(\"[Templatical] Cloud editor not ready\"),\n );\n }\n return cloudEditorRef.value.save();\n },\n };\n\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// Unmount helpers\n// ---------------------------------------------------------------------------\n\nexport function unmount(): void {\n if (appInstance) {\n appInstance.unmount();\n appInstance = null;\n editorRef.value = null;\n }\n}\n\nfunction unmountCloud(): void {\n if (cloudAppInstance) {\n cloudAppInstance.unmount();\n cloudAppInstance = null;\n cloudEditorRef.value = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type { TemplaticalCloudEditorConfig } from \"./cloud/CloudEditor.vue\";\nexport type {\n BlockDefaults,\n TemplateContent,\n TemplateDefaults,\n ThemeOverrides,\n UiTheme,\n MergeTagsConfig,\n DisplayConditionsConfig,\n CustomBlockDefinition,\n ViewportSize,\n CustomFont,\n FontsConfig,\n SaveResult,\n Template,\n} from \"@templatical/types\";\n\nexport type { UseFontsReturn, FontOption } from \"./composables/useFonts\";\nexport { useFonts } from \"./composables/useFonts\";\nexport type { EditorCapabilities } from \"./types/editor-capabilities\";\n\nexport {\n getSupportedLocales,\n getSupportedCloudLocales,\n isLocaleSupported,\n isCloudLocaleSupported,\n getBaseLocale,\n} from \"./i18n\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"editor.js","names":[],"sources":["../../src/Editor.vue","../../src/Editor.vue","../../src/i18n/index.ts","../../src/utils/toMjml.ts","../../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onMounted, onUnmounted } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport { useEditor } from \"@templatical/core\";\nimport type { TemplateContent, UiTheme } from \"@templatical/types\";\nimport { useEditorCore } from \"./composables/useEditorCore\";\nimport { resolveAccessibilityOptions } from \"./utils/resolveAccessibilityOptions\";\nimport type { Translations } from \"./i18n\";\nimport type { UseFontsReturn } from \"./composables/useFonts\";\n\nimport { RotateCcw } from \"@lucide/vue\";\nimport Canvas from \"./components/Canvas.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport ViewportToggle from \"./components/ViewportToggle.vue\";\nimport PreviewToggle from \"./components/PreviewToggle.vue\";\nimport DarkModeToggle from \"./components/DarkModeToggle.vue\";\nimport EditorFooter from \"./components/EditorFooter.vue\";\nimport \"./styles/index.css\";\n\nconst props = defineProps<{\n config: TemplaticalEditorConfig;\n translations: Translations;\n fontsManager: UseFontsReturn;\n /**\n * Shadow root the editor is mounted into. Supplied by `init()` when\n * `shadowDom: true`; undefined in light-DOM mode. Passed through to\n * `useEditorCore` so shadow-DOM-aware composables can resolve the\n * effective root via `EDITOR_ROOT_KEY`.\n */\n shadowRoot?: ShadowRoot;\n}>();\n\n// --- Core editor state ---\nconst editor = useEditor({\n content: props.config.content!,\n templateDefaults: props.config.templateDefaults,\n});\n\n// --- Shared editor core (composables, provides, plugins, keyboard) ---\nconst core = useEditorCore({\n editor,\n config: {\n uiTheme: props.config.uiTheme,\n theme: props.config.theme,\n blockDefaults: props.config.blockDefaults,\n customBlocks: props.config.customBlocks,\n mergeTags: props.config.mergeTags,\n displayConditions: props.config.displayConditions,\n onRequestMedia: props.config.onRequestMedia,\n accessibility: resolveAccessibilityOptions(props.config),\n onSave: props.config.onSave\n ? () =>\n props.config.onSave!(JSON.parse(JSON.stringify(editor.state.content)))\n : undefined,\n },\n translations: props.translations,\n fontsManager: props.fontsManager,\n autoSaveOptions: props.config.onChange\n ? {\n onChange: () =>\n props.config.onChange!(\n JSON.parse(JSON.stringify(editor.state.content)),\n ),\n }\n : null,\n editorRoot: props.shadowRoot,\n});\n\n// --- Lifecycle ---\nonMounted(async () => {\n await props.fontsManager.loadCustomFonts();\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\n// --- Public API (accessed via template ref from init()) ---\ndefineExpose({\n getContent: () => editor.content.value,\n setContent: (content: TemplateContent) => editor.setContent(content),\n setTheme: (theme: UiTheme) => editor.setUiTheme(theme),\n renderCustomBlock: core.registry.renderCustomBlock,\n});\n</script>\n\n<template>\n <div\n class=\"tpl tpl:relative tpl:h-full tpl:overflow-hidden\"\n :class=\"{ 'tpl:dark': editor.state.darkMode }\"\n :data-tpl-theme=\"core.resolvedTheme.value\"\n :style=\"core.themeStyles.value\"\n >\n <!-- Header — absolute, full width, above everything -->\n <header\n class=\"tpl-header tpl:absolute tpl:top-0 tpl:right-0 tpl:left-0 tpl:z-50 tpl:grid tpl:h-14 tpl:grid-cols-[1fr_auto_1fr] tpl:items-center tpl:px-4 tpl:shadow-[var(--tpl-shadow-md)] tpl:border-b tpl:border-[var(--tpl-border)]\"\n style=\"\n background-color: color-mix(in srgb, var(--tpl-bg) 80%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n \"\n >\n <!-- Left: empty (reserved for embedder customization) -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-2.5\"></div>\n\n <!-- Center: viewport + preview + dark mode -->\n <div class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-10\">\n <ViewportToggle\n :viewport=\"editor.state.viewport\"\n @change=\"editor.setViewport\"\n />\n <DarkModeToggle\n :dark-mode=\"editor.state.darkMode\"\n @change=\"editor.setDarkMode\"\n />\n <PreviewToggle\n :preview-mode=\"editor.state.previewMode\"\n @change=\"editor.setPreviewMode\"\n />\n </div>\n\n <!-- Right: empty in OSS mode -->\n <div\n class=\"tpl:flex tpl:min-w-[200px] tpl:items-center tpl:justify-end tpl:gap-3\"\n ></div>\n </header>\n\n <!-- Left sidebar — absolute, below header -->\n <Sidebar v-show=\"!editor.state.previewMode\" />\n\n <!-- Canvas area — absolute, fills remaining space -->\n <div\n class=\"tpl-body tpl:absolute tpl:bottom-0 tpl:overflow-auto tpl:bg-[var(--tpl-canvas-bg)]\"\n style=\"transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)\"\n :class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n 'tpl:top-14',\n ]\"\n >\n <!-- Restore hidden blocks button -->\n <div class=\"tpl:sticky tpl:top-0 tpl:z-40 tpl:h-0\">\n <Transition name=\"tpl-restore-btn\">\n <button\n v-if=\"core.conditionPreview.hasHiddenBlocks.value\"\n class=\"tpl:absolute tpl:left-1/2 tpl:top-2 tpl:-translate-x-1/2 tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-full tpl:border tpl:px-3.5 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:whitespace-nowrap tpl:shadow-md tpl:hover:opacity-80 tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)] tpl:border-[var(--tpl-warning)]\"\n style=\"backdrop-filter: blur(8px)\"\n @click=\"core.conditionPreview.reset()\"\n >\n <RotateCcw :size=\"13\" :stroke-width=\"2\" />\n {{ core.t.blockSettings.restoreHiddenBlocks }}\n </button>\n </Transition>\n </div>\n <div class=\"tpl:flex tpl:justify-center tpl:p-8\">\n <Canvas\n :viewport=\"editor.state.viewport\"\n :content=\"editor.content.value\"\n :selected-block-id=\"editor.state.selectedBlockId\"\n :dark-mode=\"editor.state.darkMode\"\n :preview-mode=\"editor.state.previewMode\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n ]\"\n />\n\n <!-- Keyboard reorder announcement region (visually hidden, screen-reader live) -->\n <div\n class=\"tpl-sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n :aria-label=\"core.t.landmarks.reorderAnnouncements\"\n >\n {{ core.keyboardReorder.announcement.value }}\n </div>\n\n <!-- Right sidebar — persisted with v-show -->\n <RightSidebar\n v-show=\"!editor.state.previewMode\"\n :selected-block=\"editor.selectedBlock.value\"\n :settings=\"editor.content.value.settings\"\n @update-block=\"\n (updates) => editor.updateBlock(editor.state.selectedBlockId!, updates)\n \"\n @delete-block=\"\n () => {\n if (editor.state.selectedBlockId) {\n core.blockActions.deleteBlock(editor.state.selectedBlockId);\n }\n }\n \"\n @duplicate-block=\"\n () => {\n if (editor.selectedBlock.value) {\n core.blockActions.duplicateBlock(editor.selectedBlock.value);\n }\n }\n \"\n @update-settings=\"(updates) => editor.updateSettings(updates)\"\n />\n\n <!-- Popover mount — Teleport target for toolbars, link dialog, modal.\n Replaces the historical body-level teleport pattern so popups\n render inside the editor's effective DOM root (shadow-aware). -->\n <div\n :ref=\"(el) => (core.popoverRoot.value = el as HTMLElement | null)\"\n class=\"tpl-popover-root\"\n />\n </div>\n</template>\n\n<style scoped>\n.tpl-restore-btn-enter-active {\n transition:\n opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-restore-btn-leave-active {\n transition:\n opacity 150ms ease-in,\n transform 150ms ease-in;\n}\n\n.tpl-restore-btn-enter-from,\n.tpl-restore-btn-leave-to {\n opacity: 0;\n transform: translateY(-8px) scale(0.9);\n}\n\n.tpl-restore-btn-enter-to,\n.tpl-restore-btn-leave-from {\n opacity: 1;\n transform: translateY(0) scale(1);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onMounted, onUnmounted } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport { useEditor } from \"@templatical/core\";\nimport type { TemplateContent, UiTheme } from \"@templatical/types\";\nimport { useEditorCore } from \"./composables/useEditorCore\";\nimport { resolveAccessibilityOptions } from \"./utils/resolveAccessibilityOptions\";\nimport type { Translations } from \"./i18n\";\nimport type { UseFontsReturn } from \"./composables/useFonts\";\n\nimport { RotateCcw } from \"@lucide/vue\";\nimport Canvas from \"./components/Canvas.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport ViewportToggle from \"./components/ViewportToggle.vue\";\nimport PreviewToggle from \"./components/PreviewToggle.vue\";\nimport DarkModeToggle from \"./components/DarkModeToggle.vue\";\nimport EditorFooter from \"./components/EditorFooter.vue\";\nimport \"./styles/index.css\";\n\nconst props = defineProps<{\n config: TemplaticalEditorConfig;\n translations: Translations;\n fontsManager: UseFontsReturn;\n /**\n * Shadow root the editor is mounted into. Supplied by `init()` when\n * `shadowDom: true`; undefined in light-DOM mode. Passed through to\n * `useEditorCore` so shadow-DOM-aware composables can resolve the\n * effective root via `EDITOR_ROOT_KEY`.\n */\n shadowRoot?: ShadowRoot;\n}>();\n\n// --- Core editor state ---\nconst editor = useEditor({\n content: props.config.content!,\n templateDefaults: props.config.templateDefaults,\n});\n\n// --- Shared editor core (composables, provides, plugins, keyboard) ---\nconst core = useEditorCore({\n editor,\n config: {\n uiTheme: props.config.uiTheme,\n theme: props.config.theme,\n blockDefaults: props.config.blockDefaults,\n customBlocks: props.config.customBlocks,\n mergeTags: props.config.mergeTags,\n displayConditions: props.config.displayConditions,\n onRequestMedia: props.config.onRequestMedia,\n accessibility: resolveAccessibilityOptions(props.config),\n onSave: props.config.onSave\n ? () =>\n props.config.onSave!(JSON.parse(JSON.stringify(editor.state.content)))\n : undefined,\n },\n translations: props.translations,\n fontsManager: props.fontsManager,\n autoSaveOptions: props.config.onChange\n ? {\n onChange: () =>\n props.config.onChange!(\n JSON.parse(JSON.stringify(editor.state.content)),\n ),\n }\n : null,\n editorRoot: props.shadowRoot,\n});\n\n// --- Lifecycle ---\nonMounted(async () => {\n await props.fontsManager.loadCustomFonts();\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\n// --- Public API (accessed via template ref from init()) ---\ndefineExpose({\n getContent: () => editor.content.value,\n setContent: (content: TemplateContent) => editor.setContent(content),\n setTheme: (theme: UiTheme) => editor.setUiTheme(theme),\n renderCustomBlock: core.registry.renderCustomBlock,\n});\n</script>\n\n<template>\n <div\n class=\"tpl tpl:relative tpl:h-full tpl:overflow-hidden\"\n :class=\"{ 'tpl:dark': editor.state.darkMode }\"\n :data-tpl-theme=\"core.resolvedTheme.value\"\n :style=\"core.themeStyles.value\"\n >\n <!-- Header — absolute, full width, above everything -->\n <header\n class=\"tpl-header tpl:absolute tpl:top-0 tpl:right-0 tpl:left-0 tpl:z-50 tpl:grid tpl:h-14 tpl:grid-cols-[1fr_auto_1fr] tpl:items-center tpl:px-4 tpl:shadow-[var(--tpl-shadow-md)] tpl:border-b tpl:border-[var(--tpl-border)]\"\n style=\"\n background-color: color-mix(in srgb, var(--tpl-bg) 80%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n \"\n >\n <!-- Left: empty (reserved for embedder customization) -->\n <div class=\"tpl:flex tpl:items-center tpl:gap-2.5\"></div>\n\n <!-- Center: viewport + preview + dark mode -->\n <div class=\"tpl:flex tpl:items-center tpl:justify-center tpl:gap-10\">\n <ViewportToggle\n :viewport=\"editor.state.viewport\"\n @change=\"editor.setViewport\"\n />\n <DarkModeToggle\n :dark-mode=\"editor.state.darkMode\"\n @change=\"editor.setDarkMode\"\n />\n <PreviewToggle\n :preview-mode=\"editor.state.previewMode\"\n @change=\"editor.setPreviewMode\"\n />\n </div>\n\n <!-- Right: empty in OSS mode -->\n <div\n class=\"tpl:flex tpl:min-w-[200px] tpl:items-center tpl:justify-end tpl:gap-3\"\n ></div>\n </header>\n\n <!-- Left sidebar — absolute, below header -->\n <Sidebar v-show=\"!editor.state.previewMode\" />\n\n <!-- Canvas area — absolute, fills remaining space -->\n <div\n class=\"tpl-body tpl:absolute tpl:bottom-0 tpl:overflow-auto tpl:bg-[var(--tpl-canvas-bg)]\"\n style=\"transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)\"\n :class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n 'tpl:top-14',\n ]\"\n >\n <!-- Restore hidden blocks button -->\n <div class=\"tpl:sticky tpl:top-0 tpl:z-40 tpl:h-0\">\n <Transition name=\"tpl-restore-btn\">\n <button\n v-if=\"core.conditionPreview.hasHiddenBlocks.value\"\n class=\"tpl:absolute tpl:left-1/2 tpl:top-2 tpl:-translate-x-1/2 tpl:inline-flex tpl:items-center tpl:gap-1.5 tpl:rounded-full tpl:border tpl:px-3.5 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:whitespace-nowrap tpl:shadow-md tpl:hover:opacity-80 tpl:bg-[var(--tpl-warning-light)] tpl:text-[var(--tpl-warning)] tpl:border-[var(--tpl-warning)]\"\n style=\"backdrop-filter: blur(8px)\"\n @click=\"core.conditionPreview.reset()\"\n >\n <RotateCcw :size=\"13\" :stroke-width=\"2\" />\n {{ core.t.blockSettings.restoreHiddenBlocks }}\n </button>\n </Transition>\n </div>\n <div class=\"tpl:flex tpl:justify-center tpl:p-8\">\n <Canvas\n :viewport=\"editor.state.viewport\"\n :content=\"editor.content.value\"\n :selected-block-id=\"editor.state.selectedBlockId\"\n :dark-mode=\"editor.state.darkMode\"\n :preview-mode=\"editor.state.previewMode\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"[\n editor.state.previewMode\n ? 'tpl:left-0 tpl:right-0'\n : 'tpl:left-12 tpl:right-[320px]',\n ]\"\n />\n\n <!-- Keyboard reorder announcement region (visually hidden, screen-reader live) -->\n <div\n class=\"tpl-sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n :aria-label=\"core.t.landmarks.reorderAnnouncements\"\n >\n {{ core.keyboardReorder.announcement.value }}\n </div>\n\n <!-- Right sidebar — persisted with v-show -->\n <RightSidebar\n v-show=\"!editor.state.previewMode\"\n :selected-block=\"editor.selectedBlock.value\"\n :settings=\"editor.content.value.settings\"\n @update-block=\"\n (updates) => editor.updateBlock(editor.state.selectedBlockId!, updates)\n \"\n @delete-block=\"\n () => {\n if (editor.state.selectedBlockId) {\n core.blockActions.deleteBlock(editor.state.selectedBlockId);\n }\n }\n \"\n @duplicate-block=\"\n () => {\n if (editor.selectedBlock.value) {\n core.blockActions.duplicateBlock(editor.selectedBlock.value);\n }\n }\n \"\n @update-settings=\"(updates) => editor.updateSettings(updates)\"\n />\n\n <!-- Popover mount — Teleport target for toolbars, link dialog, modal.\n Replaces the historical body-level teleport pattern so popups\n render inside the editor's effective DOM root (shadow-aware). -->\n <div\n :ref=\"(el) => (core.popoverRoot.value = el as HTMLElement | null)\"\n class=\"tpl-popover-root\"\n />\n </div>\n</template>\n\n<style scoped>\n.tpl-restore-btn-enter-active {\n transition:\n opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),\n transform 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.tpl-restore-btn-leave-active {\n transition:\n opacity 150ms ease-in,\n transform 150ms ease-in;\n}\n\n.tpl-restore-btn-enter-from,\n.tpl-restore-btn-leave-to {\n opacity: 0;\n transform: translateY(-8px) scale(0.9);\n}\n\n.tpl-restore-btn-enter-to,\n.tpl-restore-btn-leave-from {\n opacity: 1;\n transform: translateY(0) scale(1);\n}\n</style>\n","/// <reference types=\"vite/client\" />\n\nimport type en from \"./locales/en\";\nimport type cloudEn from \"./locales/cloud/en\";\n\nexport type Translations = typeof en;\nexport type CloudTranslations = typeof cloudEn;\nexport type TranslationKey = keyof Translations;\n\n// Vite resolves these globs at build time. Adding a new locale file\n// automatically registers it — no array to keep in sync.\nconst ossModules = import.meta.glob<{ default: Translations }>(\n \"./locales/*.ts\",\n);\nconst cloudModules = import.meta.glob<{ default: CloudTranslations }>(\n \"./locales/cloud/*.ts\",\n);\n\nfunction localesFromGlob(modules: Record<string, unknown>): string[] {\n return Object.keys(modules)\n .map((path) => path.match(/\\/([^/]+)\\.ts$/)?.[1])\n .filter((locale): locale is string => Boolean(locale));\n}\n\nconst supportedLocales = localesFromGlob(ossModules);\nconst supportedCloudLocales = localesFromGlob(cloudModules);\n\nfunction canonicalize(locale: string): string {\n return locale.trim().replace(/_/g, \"-\").toLowerCase();\n}\n\nfunction findSupportedLocale(\n locale: string,\n supported: string[],\n): string | undefined {\n const canonical = canonicalize(locale);\n return supported.find((s) => canonicalize(s) === canonical);\n}\n\n/**\n * Get the base language code from a locale string.\n * e.g., 'en-GB' -> 'en', 'de_DE' -> 'de'\n */\nexport function getBaseLocale(locale: string): string {\n return canonicalize(locale).split(\"-\")[0];\n}\n\nfunction tryResolveLocale(\n locale: string,\n supported: string[],\n): string | undefined {\n return (\n findSupportedLocale(locale, supported) ??\n findSupportedLocale(getBaseLocale(locale), supported)\n );\n}\n\nfunction resolveLocale(locale: string, supported: string[]): string {\n return tryResolveLocale(locale, supported) ?? \"en\";\n}\n\n/**\n * Load OSS translations for a given locale.\n * Falls back to English if the locale is not supported.\n */\nexport async function loadTranslations(locale: string): Promise<Translations> {\n const target = resolveLocale(locale, supportedLocales);\n const loader = ossModules[`./locales/${target}.ts`];\n const mod = await loader();\n return mod.default;\n}\n\n/**\n * Load cloud translations for a given locale.\n * Cloud translations cover features available only via initCloud() —\n * AI, comments, collaboration, scoring, snapshots, plan limits, etc.\n * OSS contributors don't need to translate these; cloud locales fall back\n * to English independently of the OSS locale.\n */\nexport async function loadCloudTranslations(\n locale: string,\n): Promise<CloudTranslations> {\n const target = resolveLocale(locale, supportedCloudLocales);\n const loader = cloudModules[`./locales/cloud/${target}.ts`];\n const mod = await loader();\n return mod.default;\n}\n\n/** Check if a locale has OSS translations (matched by exact locale, then base). */\nexport function isLocaleSupported(locale: string): boolean {\n return tryResolveLocale(locale, supportedLocales) !== undefined;\n}\n\n/** Check if a locale has cloud translations (matched by exact locale, then base). */\nexport function isCloudLocaleSupported(locale: string): boolean {\n return tryResolveLocale(locale, supportedCloudLocales) !== undefined;\n}\n\n/** List of OSS-supported locales. */\nexport function getSupportedLocales(): string[] {\n return [...supportedLocales];\n}\n\n/** List of cloud-supported locales. May be a subset of OSS locales. */\nexport function getSupportedCloudLocales(): string[] {\n return [...supportedCloudLocales];\n}\n","import type { CustomBlock, TemplateContent } from \"@templatical/types\";\n\n/**\n * Minimal slice of the editor surface needed by `toMjmlForInstance`.\n * Decoupled from the full `TemplaticalEditor` type so this helper can be\n * tested in isolation with a stub object.\n */\nexport interface ToMjmlSource {\n getContent(): TemplateContent;\n renderCustomBlock(block: CustomBlock): Promise<string>;\n}\n\n/**\n * Lazy-load `@templatical/renderer` and render the editor's current content\n * to MJML, wiring the editor's own custom block resolver into the renderer's\n * `renderCustomBlock` callback.\n *\n * The renderer is an optional peer dependency (small, MIT-licensed). It is\n * only loaded when an export is actually requested. Consumers that don't\n * need MJML export at all (e.g., embedding the editor in an app where the\n * backend handles export) can omit the install entirely; calling `toMjml()`\n * in that case throws a clear error naming the missing package.\n *\n * The dynamic import is cached by the module system, so subsequent calls\n * skip the import overhead.\n */\nexport async function toMjmlForInstance(\n instance: ToMjmlSource,\n): Promise<string> {\n let renderer: typeof import(\"@templatical/renderer\");\n try {\n renderer = await import(\"@templatical/renderer\");\n } catch {\n throw new Error(\n \"[Templatical] toMjml() requires the @templatical/renderer package. Please install it.\",\n );\n }\n return renderer.renderToMjml(instance.getContent(), {\n renderCustomBlock: instance.renderCustomBlock,\n });\n}\n","// eslint-disable-next-line @typescript-eslint/triple-slash-reference -- ambient-module declaration must be loaded for cross-package typecheck (workspace path alias resolves to source)\n/// <reference path=\"./virtual-modules.d.ts\" />\nimport { createApp, h, ref, type App, type Ref } from \"vue\";\nimport { INIT_TIMEOUT_MS } from \"./constants/timeouts\";\nimport type {\n BlockDefaults,\n CustomBlock,\n CustomBlockDefinition,\n DisplayConditionsConfig,\n FontsConfig,\n MediaResult,\n MergeTagsConfig,\n SaveResult,\n Template,\n TemplateContent,\n TemplateDefaults,\n ThemeOverrides,\n UiTheme,\n} from \"@templatical/types\";\nimport type { MediaRequestContext } from \"@templatical/media-library\";\n\nimport Editor from \"./Editor.vue\";\nimport { loadTranslations, loadCloudTranslations } from \"./i18n\";\nimport { useFonts } from \"./composables\";\nimport { toMjmlForInstance } from \"./utils/toMjml\";\n// Compiled-CSS-as-string for shadow root adoption. The `virtual:editor-css`\n// module is owned by `scripts/inline-style-css-plugin.ts` — at build time it\n// captures every emitted CSS asset (Tailwind utilities + every `.vue` SFC\n// `<style>` block + `styles/index.css` rules) and replaces this import's\n// runtime value with the full library CSS string. In dev/test the plugin\n// returns `styles/index.css` source as a fallback.\n//\n// Separate concern from the side-effecting `import \"./styles/index.css\"` in\n// `Editor.vue` / `CloudEditor.vue`, which injects styles into `document.head`\n// for light-DOM mode and survives untouched.\n//\n// Ambient declaration for `virtual:editor-css` lives in `virtual-modules.d.ts`\n// referenced at the top of this file via triple-slash so it's visible to any\n// consumer typechecking through the workspace path alias.\nimport editorStylesInline from \"virtual:editor-css\";\n\n// ---------------------------------------------------------------------------\n// OSS config + return types\n// ---------------------------------------------------------------------------\n\nexport interface TemplaticalEditorConfig {\n container: string | HTMLElement;\n content?: TemplateContent;\n\n /**\n * Mount the editor inside a Shadow DOM (open mode) for CSS isolation\n * from the host page. Defaults to `true` — host stylesheets cannot\n * cascade past the shadow boundary into editor elements (`p`, `a`,\n * `input`, etc.), and editor utility classes never collide with host\n * class names.\n *\n * Set to `false` to mount in light DOM. Opt out when:\n * - Your host integration uses `document.querySelector` to reach\n * editor internals (with shadow DOM, use `container.shadowRoot\n * .querySelector(...)` instead).\n * - You need to support Firefox <101 or Safari <16.4, which lack the\n * `adoptedStyleSheets` API the shadow path relies on.\n *\n * Light-mode consumers should keep this set to `false` explicitly so\n * future SDK changes don't silently flip the default again.\n *\n * @default true\n */\n shadowDom?: boolean;\n\n onChange?: (content: TemplateContent) => void;\n onSave?: (content: TemplateContent) => void;\n onError?: (error: Error) => void;\n\n onRequestMedia?: OnRequestMedia;\n\n mergeTags?: MergeTagsConfig;\n displayConditions?: DisplayConditionsConfig;\n customBlocks?: CustomBlockDefinition[];\n fonts?: FontsConfig;\n\n blockDefaults?: BlockDefaults;\n templateDefaults?: TemplateDefaults;\n\n theme?: ThemeOverrides;\n uiTheme?: UiTheme;\n locale?: string;\n\n /**\n * Show the \"Powered by Templatical\" footer. Defaults to `true`.\n * Set to `false` to hide the footer (no attribution required by the license).\n */\n branding?: boolean;\n\n /**\n * Accessibility linter (`@templatical/quality`) configuration.\n *\n * - When unset, the linter loads on demand once the user opens the panel.\n * - When `disabled: true`, the optional peer is never imported (saves the\n * chunk download) and the sidebar tab + inline badges are suppressed.\n * - `rules`/`thresholds` follow the shape exported by `@templatical/quality`.\n */\n accessibility?: import(\"@templatical/quality\").A11yOptions;\n}\n\n/** Function type for media browser requests, used by both OSS and Cloud editors. */\nexport type OnRequestMedia = (\n context?: MediaRequestContext,\n) => Promise<MediaResult | null>;\n\ninterface TemplaticalEditorBase {\n getContent(): TemplateContent;\n setContent(content: TemplateContent): void;\n setTheme(theme: UiTheme): void;\n unmount(): void;\n}\n\nexport interface TemplaticalEditor extends TemplaticalEditorBase {\n /**\n * Render the current template to an MJML string. Resolves custom blocks\n * via the editor's internal block registry. Throws if the optional\n * `@templatical/renderer` package is not installed.\n */\n toMjml(): Promise<string>;\n /**\n * Render a single custom block to its HTML representation, using the\n * registered custom block definition's template and the block's current\n * field values. Exposed for headless callers that want to reuse the\n * editor's renderer (e.g., to drive `@templatical/renderer`'s\n * `renderCustomBlock` option from outside the editor instance).\n */\n renderCustomBlock(block: CustomBlock): Promise<string>;\n}\n\n/**\n * Cloud editor does not expose `toMjml` or `renderCustomBlock`: the cloud\n * backend performs MJML conversion server-side with additional processing\n * (e.g., signed image URLs, attachment handling) that isn't available client\n * side. Use the cloud `save()` flow to persist content; the backend handles\n * MJML/HTML export from there.\n */\nexport interface TemplaticalCloudEditor extends TemplaticalEditorBase {\n create(content?: TemplateContent): Promise<Template>;\n load(templateId: string): Promise<Template>;\n save(): Promise<SaveResult>;\n}\n\n// ---------------------------------------------------------------------------\n// Shadow root helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Module-cached `CSSStyleSheet` built once from the inline editor CSS string.\n * `adoptedStyleSheets` accepts the same sheet object across multiple shadow\n * roots — sharing one sheet costs zero per-instance memory, regardless of\n * how many editors mount.\n */\nlet cachedEditorStyleSheet: CSSStyleSheet | null = null;\nfunction getEditorStyleSheet(): CSSStyleSheet {\n if (cachedEditorStyleSheet === null) {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(editorStylesInline);\n cachedEditorStyleSheet = sheet;\n }\n return cachedEditorStyleSheet;\n}\n\ninterface MountTarget {\n target: Element;\n shadowRoot: ShadowRoot | null;\n /**\n * Disposer for any dev-mode side effects attached to the shadow root\n * (currently: the document.head `<style>` mirror's MutationObserver).\n * Always safe to call — no-op when no side effects were registered.\n */\n cleanup: () => void;\n}\n\n/**\n * Dev-only: mirror every `<style>` tag in `document.head` into the\n * shadow root's `adoptedStyleSheets`, and observe `document.head` so\n * Vite's HMR-injected style updates flow through to the shadow root\n * automatically.\n *\n * Background: Vite dev injects each `.vue` `<style scoped>` block as a\n * separate `<style>` element in `document.head` via HMR. Those don't\n * cross the shadow boundary, so a shadow-mounted editor in dev would\n * render with only the bundled `styles/index.css` rules — every SFC\n * scoped style (block selection outlines, sidebar layout, etc.) missing.\n *\n * In production builds, `inline-style-css-plugin.ts` captures every\n * emitted CSS asset at `generateBundle` time and inlines the full\n * library CSS as a single string adopted by the shadow root. This\n * dev-only mirror does the same thing at runtime by observing whatever\n * Vite ends up injecting.\n *\n * The dead-code-elimination on `import.meta.env.DEV` ensures the\n * observer + filter logic is stripped from production bundles entirely.\n *\n * Caveats:\n * - In a real consumer's Vite-dev environment, this would also adopt\n * the consumer's page-level styles (whatever they put in\n * `document.head`). That's harmless when consumers install the\n * editor from npm dist (the dev branch is dead-coded out). It only\n * matters when a consumer source-resolves this package, which is\n * unusual outside this repo's own playground.\n * - `replaceSync` strips `@import` rules per the CSSOM spec. Styles\n * containing `@import` are skipped silently (the catch below). The\n * primary bundled sheet covers Tailwind imports already, so this\n * should never matter in practice.\n */\nfunction attachDevStyleMirror(shadowRoot: ShadowRoot): () => void {\n if (!import.meta.env?.DEV) return () => {};\n\n function buildSheets(): CSSStyleSheet[] {\n const sheets: CSSStyleSheet[] = [];\n // shadow-ok: dev-only HMR style mirror (production path is plugin-driven via adoptedStyleSheets)\n document.head.querySelectorAll(\"style\").forEach((el) => {\n const text = el.textContent;\n if (!text) return;\n try {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(text);\n sheets.push(sheet);\n } catch {\n // Skip styles that contain disallowed constructs (e.g. `@import`).\n }\n });\n return sheets;\n }\n\n function refresh(): void {\n // Keep the editor's primary (bundled) sheet first so its declarations\n // are the cascade fallback; dev sheets adopted after may override them\n // — same precedence model as production where everything ends up in\n // one stylesheet anyway.\n shadowRoot.adoptedStyleSheets = [getEditorStyleSheet(), ...buildSheets()];\n }\n\n refresh();\n\n const observer = new MutationObserver(() => refresh());\n // childList catches new/removed <style> tags; characterData + subtree\n // catches Vite HMR mutating an existing style's textContent in place.\n // shadow-ok: dev-only HMR style mirror observer\n observer.observe(document.head, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n\n return () => observer.disconnect();\n}\n\n/**\n * Resolve where Vue should mount: directly on the consumer's container\n * (light-DOM mode) or on a fresh `<div>` inside a newly-attached open\n * shadow root (shadow mode). In shadow mode, also attaches the editor's\n * cached `CSSStyleSheet` to the root so chrome renders with the right\n * styles inside the boundary, plus a dev-only mirror that observes\n * `document.head` (see `attachDevStyleMirror`).\n *\n * Idempotent: a second call on the same container reuses any existing\n * shadow root, clearing its contents so a prior mount's stale DOM doesn't\n * accumulate.\n */\nfunction resolveMountTarget(\n container: Element,\n shadowDom: boolean | undefined,\n): MountTarget {\n if (!shadowDom) {\n return { target: container, shadowRoot: null, cleanup: () => {} };\n }\n\n const shadowRoot =\n container.shadowRoot ?? container.attachShadow({ mode: \"open\" });\n\n // Adopt the editor stylesheet. Idempotent — repeated assignment of the\n // same sheet is fine. Dev mirror (below) will overwrite this with the\n // primary sheet + mirrored sheets, then keep them in sync.\n shadowRoot.adoptedStyleSheets = [getEditorStyleSheet()];\n\n // Clear stale content from a prior mount (re-init on same container).\n while (shadowRoot.firstChild) {\n shadowRoot.removeChild(shadowRoot.firstChild);\n }\n\n const host = document.createElement(\"div\");\n host.className = \"tpl-editor-host\";\n // Match the container's layout. Without an explicit size the host is a\n // default block element (height: auto) and the editor's `tpl:h-full`\n // template root collapses to content height because the height-100% chain\n // breaks at the shadow boundary. `width: 100%` is redundant for block\n // elements but harmless and explicit.\n host.style.cssText = \"display:block;height:100%;width:100%;\";\n shadowRoot.appendChild(host);\n\n const cleanup = attachDevStyleMirror(shadowRoot);\n\n return { target: host, shadowRoot, cleanup };\n}\n\n// ---------------------------------------------------------------------------\n// OSS init — sync\n// ---------------------------------------------------------------------------\n\ninterface OssEntry {\n app: App;\n editorRef: Ref<InstanceType<typeof Editor> | null>;\n /** Tear down dev-mode side effects (style mirror observer, etc.). */\n cleanup: () => void;\n}\n\n// Per-container registry so two `init()` calls with different containers\n// produce independent editor instances (multi-instance support). Re-init\n// on the same container still auto-unmounts the previous instance.\nconst ossEntries = new Map<Element, OssEntry>();\n\n// \"Last init'd\" container preserves the legacy single-instance behavior\n// of the top-level `unmount()` export — the playground (and other one-\n// editor consumers) calls bare `unmount()` and expects it to tear down\n// whatever was most recently mounted.\nlet lastOssContainer: Element | null = null;\n\nfunction unmountOssContainer(container: Element): void {\n const entry = ossEntries.get(container);\n if (!entry) return;\n entry.cleanup();\n entry.app.unmount();\n ossEntries.delete(container);\n if (lastOssContainer === container) {\n lastOssContainer = null;\n }\n}\n\nexport async function init(\n config: TemplaticalEditorConfig,\n): Promise<TemplaticalEditor> {\n const container =\n typeof config.container === \"string\"\n ? document.querySelector(config.container)\n : config.container;\n\n if (!container) {\n throw new Error(\n `[Templatical] Container element not found: ${config.container}`,\n );\n }\n\n // Load translations before mounting so child components can use useI18n synchronously\n const translations = await loadTranslations(config.locale ?? \"en\");\n\n // Create fonts manager to pass to Editor\n const fontsManager = useFonts(config.fonts);\n\n // Auto-unmount any prior instance on the SAME container *after* awaits\n // — checking before the await would let two concurrent init() calls\n // both pass the guard and orphan the first-mounted app on this\n // container.\n unmountOssContainer(container);\n\n const mount = resolveMountTarget(container, config.shadowDom ?? true);\n const editorRef: Ref<InstanceType<typeof Editor> | null> = ref(null);\n\n const app = createApp({\n setup() {\n return () =>\n h(Editor, {\n config,\n translations,\n fontsManager,\n shadowRoot: mount.shadowRoot ?? undefined,\n ref: editorRef,\n });\n },\n });\n\n app.mount(mount.target);\n\n ossEntries.set(container, { app, editorRef, cleanup: mount.cleanup });\n lastOssContainer = container;\n\n const instance: TemplaticalEditor = {\n getContent() {\n if (editorRef.value) {\n return JSON.parse(JSON.stringify(editorRef.value.getContent()));\n }\n return JSON.parse(JSON.stringify(config.content));\n },\n setContent(content: TemplateContent) {\n if (editorRef.value) {\n editorRef.value.setContent(content);\n }\n config.content = content;\n },\n setTheme(theme: UiTheme) {\n if (editorRef.value) {\n editorRef.value.setTheme(theme);\n }\n },\n unmount: () => unmountOssContainer(container),\n renderCustomBlock(block: CustomBlock) {\n if (!editorRef.value) {\n return Promise.reject(new Error(\"[Templatical] Editor not ready\"));\n }\n return editorRef.value.renderCustomBlock(block);\n },\n toMjml: () => toMjmlForInstance(instance),\n };\n\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// Cloud init — async, flat config, tree-shaken when not called\n// ---------------------------------------------------------------------------\n\ninterface CloudEntry {\n app: App;\n editorRef: Ref<InstanceType<\n typeof import(\"./cloud/CloudEditor.vue\").default\n > | null>;\n cleanup: () => void;\n}\n\nconst cloudEntries = new Map<Element, CloudEntry>();\nlet lastCloudContainer: Element | null = null;\n\nfunction unmountCloudContainer(container: Element): void {\n const entry = cloudEntries.get(container);\n if (!entry) return;\n entry.cleanup();\n entry.app.unmount();\n cloudEntries.delete(container);\n if (lastCloudContainer === container) {\n lastCloudContainer = null;\n }\n}\n\nexport async function initCloud(\n config: import(\"./cloud/CloudEditor.vue\").TemplaticalCloudEditorConfig,\n): Promise<TemplaticalCloudEditor> {\n const container =\n typeof config.container === \"string\"\n ? document.querySelector(config.container)\n : config.container;\n\n if (!container) {\n throw new Error(\n `[Templatical] Container element not found: ${config.container}`,\n );\n }\n\n // Dynamic import — CloudEditor.vue is tree-shaken from the OSS bundle\n const { default: CloudEditor } = await import(\"./cloud/CloudEditor.vue\");\n\n // Load OSS + cloud translations in parallel so child components can use\n // useI18n / useCloudI18n synchronously\n const [translations, cloudTranslations] = await Promise.all([\n loadTranslations(config.locale ?? \"en\"),\n loadCloudTranslations(config.locale ?? \"en\"),\n ]);\n\n // Create fonts manager to pass to CloudEditor\n const fontsManager = useFonts(config.fonts);\n\n // Auto-unmount any prior instance on the SAME container *after* awaits\n // — checking before the await would let two concurrent initCloud()\n // calls both pass the guard and orphan the first-mounted app on this\n // container.\n unmountCloudContainer(container);\n\n const mount = resolveMountTarget(container, config.shadowDom ?? true);\n const cloudEditorRef: CloudEntry[\"editorRef\"] = ref(null);\n\n // Promise that resolves when CloudEditor emits 'ready'\n const readyPromise = new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(\"[Templatical] Cloud editor initialization timed out\"));\n }, INIT_TIMEOUT_MS);\n\n const app = createApp({\n setup() {\n return () =>\n h(CloudEditor, {\n config,\n translations,\n cloudTranslations,\n fontsManager,\n shadowRoot: mount.shadowRoot ?? undefined,\n ref: cloudEditorRef,\n onReady: () => {\n clearTimeout(timeout);\n resolve();\n },\n });\n },\n });\n\n app.mount(mount.target);\n\n cloudEntries.set(container, {\n app,\n editorRef: cloudEditorRef,\n cleanup: mount.cleanup,\n });\n lastCloudContainer = container;\n });\n\n await readyPromise;\n\n const instance: TemplaticalCloudEditor = {\n getContent() {\n if (cloudEditorRef.value) {\n return JSON.parse(JSON.stringify(cloudEditorRef.value.getContent()));\n }\n return JSON.parse(JSON.stringify(config.content));\n },\n setContent(content: TemplateContent) {\n if (cloudEditorRef.value) {\n cloudEditorRef.value.setContent(content);\n }\n },\n setTheme(theme: UiTheme) {\n if (cloudEditorRef.value) {\n cloudEditorRef.value.setTheme(theme);\n }\n },\n unmount: () => unmountCloudContainer(container),\n create(content?: TemplateContent) {\n if (!cloudEditorRef.value) {\n return Promise.reject(\n new Error(\"[Templatical] Cloud editor not ready\"),\n );\n }\n return cloudEditorRef.value.create(content);\n },\n load(templateId: string) {\n if (!cloudEditorRef.value) {\n return Promise.reject(\n new Error(\"[Templatical] Cloud editor not ready\"),\n );\n }\n return cloudEditorRef.value.load(templateId);\n },\n save() {\n if (!cloudEditorRef.value) {\n return Promise.reject(\n new Error(\"[Templatical] Cloud editor not ready\"),\n );\n }\n return cloudEditorRef.value.save();\n },\n };\n\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// Unmount helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Unmount the most-recently-created OSS editor. Single-instance legacy\n * API — callers managing multiple editors should use `instance.unmount()`\n * from each returned object, which targets the specific container.\n */\nexport function unmount(): void {\n if (lastOssContainer) {\n unmountOssContainer(lastOssContainer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type { TemplaticalCloudEditorConfig } from \"./cloud/CloudEditor.vue\";\nexport type {\n BlockDefaults,\n TemplateContent,\n TemplateDefaults,\n ThemeOverrides,\n UiTheme,\n MergeTagsConfig,\n DisplayConditionsConfig,\n CustomBlockDefinition,\n ViewportSize,\n CustomFont,\n FontsConfig,\n SaveResult,\n Template,\n} from \"@templatical/types\";\n\nexport type { UseFontsReturn, FontOption } from \"./composables/useFonts\";\nexport { useFonts } from \"./composables/useFonts\";\nexport type { EditorCapabilities } from \"./types/editor-capabilities\";\n\nexport {\n getSupportedLocales,\n getSupportedCloudLocales,\n isLocaleSupported,\n isCloudLocaleSupported,\n getBaseLocale,\n} from \"./i18n\";\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { A as e, B as t, E as n, M as r, S as i, X as a, a as o, at as s, b as c, c as l, d as u, f as d, h as f, l as p, m, n as h, ot as g, p as _, rt as v, s as y, st as b, z as x } from "./vue.runtime.esm-bundler-Bxqkjqhc.js";
2
2
  import { C as S, D as C, n as w, w as T } from "./dist-DJmnUmW9.js";
3
- import { t as E } from "./useI18n-CgmQftNf.js";
4
- import { t as D } from "./useMergeTag-vpwrZ9eQ.js";
3
+ import { t as E } from "./useI18n-PEB8ioi_.js";
4
+ import { t as D } from "./useMergeTag-C47xwU7X.js";
5
5
  import { F as O, Gt as k, _n as A, bn as j, i as M, o as N, p as P, u as F, vn as ee, xn as I } from "./dist-iLBdeBDR.js";
6
6
  import { n as L, t as R } from "./dist-Pk4MhWMP.js";
7
7
  //#region src/extensions/FontSize.ts
@@ -171,10 +171,8 @@ var te = M.create({
171
171
  //#endregion
172
172
  //#region src/extensions/isNodeSelected.ts
173
173
  function U(e, t) {
174
- let { selection: n } = e.state, { $from: r, $to: i } = n, a = !1;
175
- return e.state.doc.nodesBetween(r.pos, i.pos, (e) => {
176
- if (e.type.name === t) return a = !0, !1;
177
- }), !!(a || r.pos > 0 && r.nodeBefore?.type.name === t || r.nodeAfter?.type.name === t);
174
+ let { $from: n, $to: r } = e.state.selection;
175
+ return n.pos === r.pos ? n.pos > 0 && n.nodeBefore?.type.name === t || n.nodeAfter?.type.name === t : !1;
178
176
  }
179
177
  //#endregion
180
178
  //#region src/extensions/renderVueNodeView.ts
@@ -675,11 +673,12 @@ var he = M.create({
675
673
  return {
676
674
  mergeTags: [],
677
675
  char: "{{",
678
- emptyText: "No matching merge tags"
676
+ emptyText: "No matching merge tags",
677
+ popoverRoot: null
679
678
  };
680
679
  },
681
680
  addProseMirrorPlugins() {
682
- let e = this.options.mergeTags, t = this.options.emptyText, n = {
681
+ let e = this.options.mergeTags, t = this.options.emptyText, n = this.options.popoverRoot, r = {
683
682
  char: this.options.char,
684
683
  allowSpaces: !1,
685
684
  startOfLine: !1,
@@ -695,14 +694,14 @@ var he = M.create({
695
694
  }).run();
696
695
  },
697
696
  render: () => {
698
- let e = null, n = null, r = null, o = a([]), s = a(0), c = null, l = `tpl-merge-tag-suggestion-${++me}`, u = null, d = [];
699
- function f() {
700
- v(u?.() ?? null);
701
- }
697
+ let e = null, r = null, o = null, s = a([]), c = a(0), l = null, u = `tpl-merge-tag-suggestion-${++me}`, d = null, f = [];
702
698
  function p() {
703
- f(), requestAnimationFrame(f);
699
+ y(d?.() ?? null);
700
+ }
701
+ function m() {
702
+ p(), requestAnimationFrame(p);
704
703
  }
705
- function m(e) {
704
+ function g(e) {
706
705
  let t = [], n = e?.parentElement ?? null;
707
706
  for (; n && n !== document.body && n !== document.documentElement;) {
708
707
  let e = window.getComputedStyle(n), r = e.overflow + e.overflowX + e.overflowY;
@@ -710,28 +709,28 @@ var he = M.create({
710
709
  }
711
710
  return t;
712
711
  }
713
- function g(e) {
714
- d = [window, ...m(e)];
715
- for (let e of d) e.addEventListener("scroll", f, {
712
+ function _(e) {
713
+ f = [window, ...g(e)];
714
+ for (let e of f) e.addEventListener("scroll", p, {
716
715
  passive: !0,
717
716
  capture: !0
718
717
  });
719
- window.addEventListener("resize", f, { passive: !0 });
718
+ window.addEventListener("resize", p, { passive: !0 });
720
719
  }
721
- function _() {
722
- for (let e of d) e.removeEventListener("scroll", f, { capture: !0 });
723
- window.removeEventListener("resize", f), d = [];
720
+ function v() {
721
+ for (let e of f) e.removeEventListener("scroll", p, { capture: !0 });
722
+ window.removeEventListener("resize", p), f = [];
724
723
  }
725
- function v(e) {
726
- if (!n || !e || e.bottom < 0 || e.top > window.innerHeight) return;
727
- n.style.position = "fixed", n.style.left = `${e.left}px`, n.style.zIndex = "9999", n.style.top = `${e.bottom}px`;
728
- let t = n.offsetHeight;
724
+ function y(e) {
725
+ if (!r || !e || e.bottom < 0 || e.top > window.innerHeight) return;
726
+ r.style.position = "fixed", r.style.left = `${e.left}px`, r.style.zIndex = "9999", r.style.top = `${e.bottom}px`;
727
+ let t = r.offsetHeight;
729
728
  if (t !== 0 && window.innerHeight - e.bottom < t) {
730
- let r = Math.max(0, e.top - t);
731
- n.style.top = `${r}px`;
729
+ let n = Math.max(0, e.top - t);
730
+ r.style.top = `${n}px`;
732
731
  }
733
732
  }
734
- function y(e, t) {
733
+ function b(e, t) {
735
734
  let n = t?.closest("[data-tpl-theme]");
736
735
  if (!n) return;
737
736
  let r = n.getAttribute("data-tpl-theme");
@@ -743,55 +742,55 @@ var he = M.create({
743
742
  }
744
743
  e.style.fontFamily = i.fontFamily, e.style.fontSize = i.fontSize, e.style.lineHeight = i.lineHeight;
745
744
  }
746
- function b(e) {
747
- r && (e ? (r.setAttribute("role", "combobox"), r.setAttribute("aria-haspopup", "listbox"), r.setAttribute("aria-expanded", "true"), r.setAttribute("aria-controls", l)) : (r.removeAttribute("aria-expanded"), r.removeAttribute("aria-controls"), r.removeAttribute("aria-activedescendant"), r.removeAttribute("aria-haspopup"), r.removeAttribute("role")));
745
+ function x(e) {
746
+ o && (e ? (o.setAttribute("role", "combobox"), o.setAttribute("aria-haspopup", "listbox"), o.setAttribute("aria-expanded", "true"), o.setAttribute("aria-controls", u)) : (o.removeAttribute("aria-expanded"), o.removeAttribute("aria-controls"), o.removeAttribute("aria-activedescendant"), o.removeAttribute("aria-haspopup"), o.removeAttribute("role")));
748
747
  }
749
- function x() {
750
- if (r) {
751
- if (o.value.length === 0) {
752
- r.removeAttribute("aria-activedescendant");
748
+ function S() {
749
+ if (o) {
750
+ if (s.value.length === 0) {
751
+ o.removeAttribute("aria-activedescendant");
753
752
  return;
754
753
  }
755
- r.setAttribute("aria-activedescendant", `${l}-opt-${s.value}`);
754
+ o.setAttribute("aria-activedescendant", `${u}-opt-${c.value}`);
756
755
  }
757
756
  }
758
- function S(e) {
759
- c?.(e);
757
+ function C(e) {
758
+ l?.(e);
760
759
  }
761
760
  return {
762
761
  onStart: (a) => {
763
- o.value = a.items, s.value = 0, c = (e) => a.command(e), n = document.createElement("div"), n.setAttribute("data-testid", "merge-tag-suggestion-popup");
764
- let d = a.editor.view?.dom;
765
- r = d ?? null, y(n, d ?? null), document.body.appendChild(n), e = h({ render() {
762
+ s.value = a.items, c.value = 0, l = (e) => a.command(e), r = document.createElement("div"), r.setAttribute("data-testid", "merge-tag-suggestion-popup");
763
+ let f = a.editor.view?.dom;
764
+ o = f ?? null, b(r, f ?? null), (n?.value ?? document.body).appendChild(r), e = h({ render() {
766
765
  return i(pe, {
767
- items: o.value,
768
- selectedIndex: s.value,
766
+ items: s.value,
767
+ selectedIndex: c.value,
769
768
  emptyText: t,
770
- listId: l,
771
- onSelect: (e) => S(e),
769
+ listId: u,
770
+ onSelect: (e) => C(e),
772
771
  onHover: (e) => {
773
- s.value = e, x();
772
+ c.value = e, S();
774
773
  }
775
774
  });
776
- } }), e.mount(n), b(!0), x(), u = a.clientRect ?? null, p(), g(d ?? null);
775
+ } }), e.mount(r), x(!0), S(), d = a.clientRect ?? null, m(), _(f ?? null);
777
776
  },
778
777
  onUpdate: (e) => {
779
- o.value = e.items, s.value >= e.items.length && (s.value = 0), c = (t) => e.command(t), x(), u = e.clientRect ?? null, p();
778
+ s.value = e.items, c.value >= e.items.length && (c.value = 0), l = (t) => e.command(t), S(), d = e.clientRect ?? null, m();
780
779
  },
781
780
  onKeyDown: (e) => {
782
781
  if (e.event.key === "Escape") return !0;
783
- let t = $(e.event, o.value, s, S);
784
- return t && x(), t;
782
+ let t = $(e.event, s.value, c, C);
783
+ return t && S(), t;
785
784
  },
786
785
  onExit: () => {
787
- _(), b(!1), e?.unmount(), n?.remove(), e = null, n = null, r = null, c = null, u = null;
786
+ v(), x(!1), e?.unmount(), r?.remove(), e = null, r = null, o = null, l = null, d = null;
788
787
  }
789
788
  };
790
789
  }
791
790
  };
792
791
  return [ce({
793
792
  editor: this.editor,
794
- ...n
793
+ ...r
795
794
  })];
796
795
  }
797
796
  });
package/dist/index.d.ts CHANGED
@@ -122,6 +122,26 @@ export declare interface TemplaticalCloudEditor extends TemplaticalEditorBase {
122
122
  export declare interface TemplaticalCloudEditorConfig {
123
123
  container: string | HTMLElement;
124
124
  content?: TemplateContent_2;
125
+ /**
126
+ * Mount the editor inside a Shadow DOM (open mode) for CSS isolation
127
+ * from the host page. Defaults to `true` — host stylesheets cannot
128
+ * cascade past the shadow boundary into editor elements (`p`, `a`,
129
+ * `input`, etc.), and editor utility classes never collide with host
130
+ * class names.
131
+ *
132
+ * Set to `false` to mount in light DOM. Opt out when:
133
+ * - Your host integration uses `document.querySelector` to reach
134
+ * editor internals (with shadow DOM, use `container.shadowRoot
135
+ * .querySelector(...)` instead).
136
+ * - You need to support Firefox <101 or Safari <16.4, which lack the
137
+ * `adoptedStyleSheets` API the shadow path relies on.
138
+ *
139
+ * Light-mode consumers should keep this set to `false` explicitly so
140
+ * future SDK changes don't silently flip the default again.
141
+ *
142
+ * @default true
143
+ */
144
+ shadowDom?: boolean;
125
145
  auth: {
126
146
  url: string;
127
147
  baseUrl?: string;
@@ -198,6 +218,26 @@ declare interface TemplaticalEditorBase {
198
218
  export declare interface TemplaticalEditorConfig {
199
219
  container: string | HTMLElement;
200
220
  content?: TemplateContent;
221
+ /**
222
+ * Mount the editor inside a Shadow DOM (open mode) for CSS isolation
223
+ * from the host page. Defaults to `true` — host stylesheets cannot
224
+ * cascade past the shadow boundary into editor elements (`p`, `a`,
225
+ * `input`, etc.), and editor utility classes never collide with host
226
+ * class names.
227
+ *
228
+ * Set to `false` to mount in light DOM. Opt out when:
229
+ * - Your host integration uses `document.querySelector` to reach
230
+ * editor internals (with shadow DOM, use `container.shadowRoot
231
+ * .querySelector(...)` instead).
232
+ * - You need to support Firefox <101 or Safari <16.4, which lack the
233
+ * `adoptedStyleSheets` API the shadow path relies on.
234
+ *
235
+ * Light-mode consumers should keep this set to `false` explicitly so
236
+ * future SDK changes don't silently flip the default again.
237
+ *
238
+ * @default true
239
+ */
240
+ shadowDom?: boolean;
201
241
  onChange?: (content: TemplateContent) => void;
202
242
  onSave?: (content: TemplateContent) => void;
203
243
  onError?: (error: Error) => void;
@@ -231,6 +271,11 @@ export { ThemeOverrides }
231
271
 
232
272
  export { UiTheme }
233
273
 
274
+ /**
275
+ * Unmount the most-recently-created OSS editor. Single-instance legacy
276
+ * API — callers managing multiple editors should use `instance.unmount()`
277
+ * from each returned object, which targets the specific container.
278
+ */
234
279
  export declare function unmount(): void;
235
280
 
236
281
  export declare function useFonts(config?: FontsConfig_2): UseFontsReturn;
@@ -256,9 +301,9 @@ export { }
256
301
 
257
302
  declare module "@tiptap/core" {
258
303
  interface Commands<ReturnType> {
259
- letterSpacing: {
260
- setLetterSpacing: (spacing: string) => ReturnType;
261
- unsetLetterSpacing: () => ReturnType;
304
+ fontSize: {
305
+ setFontSize: (size: string) => ReturnType;
306
+ unsetFontSize: () => ReturnType;
262
307
  };
263
308
  }
264
309
  }
@@ -266,9 +311,9 @@ declare module "@tiptap/core" {
266
311
 
267
312
  declare module "@tiptap/core" {
268
313
  interface Commands<ReturnType> {
269
- fontSize: {
270
- setFontSize: (size: string) => ReturnType;
271
- unsetFontSize: () => ReturnType;
314
+ letterSpacing: {
315
+ setLetterSpacing: (spacing: string) => ReturnType;
316
+ unsetLetterSpacing: () => ReturnType;
272
317
  };
273
318
  }
274
319
  }
@@ -276,9 +321,8 @@ declare module "@tiptap/core" {
276
321
 
277
322
  declare module "@tiptap/core" {
278
323
  interface Commands<ReturnType> {
279
- lineHeight: {
280
- setLineHeight: (lineHeight: string) => ReturnType;
281
- unsetLineHeight: () => ReturnType;
324
+ mergeTagNode: {
325
+ insertMergeTag: (attrs: MergeTag) => ReturnType;
282
326
  };
283
327
  }
284
328
  }
@@ -286,8 +330,9 @@ declare module "@tiptap/core" {
286
330
 
287
331
  declare module "@tiptap/core" {
288
332
  interface Commands<ReturnType> {
289
- mergeTagNode: {
290
- insertMergeTag: (attrs: MergeTag) => ReturnType;
333
+ lineHeight: {
334
+ setLineHeight: (lineHeight: string) => ReturnType;
335
+ unsetLineHeight: () => ReturnType;
291
336
  };
292
337
  }
293
338
  }
@@ -1,10 +1,10 @@
1
1
  import { w as e } from "./vue.runtime.esm-bundler-Bxqkjqhc.js";
2
2
  //#region src/keys.ts
3
- var t = Symbol("translations"), n = Symbol("editor"), r = Symbol("history"), i = Symbol("blockActions"), a = Symbol("conditionPreview"), o = Symbol("fontsManager"), s = Symbol("themeStyles"), c = Symbol("tplUiTheme"), l = Symbol("blockDefaults"), u = Symbol("blockRegistry"), d = Symbol("customBlockDefinitions"), f = Symbol("mergeTags"), p = Symbol("mergeTagSyntax"), m = Symbol("onRequestMergeTag"), h = Symbol("mergeTagAutocomplete"), g = Symbol("onRequestMedia"), _ = Symbol("displayConditions"), v = Symbol("allowCustomConditions"), y = Symbol("capabilities"), b = Symbol("keyboardReorder"), x = Symbol("accessibilityLint"), S = Symbol("authManager"), C = Symbol("aiConfig"), w = Symbol("comments"), T = Symbol("savedModulesHeadless"), E = Symbol("scoring"), D = Symbol("cloudTranslations");
4
- function O(t, n) {
3
+ var t = Symbol("translations"), n = Symbol("editor"), r = Symbol("history"), i = Symbol("blockActions"), a = Symbol("conditionPreview"), o = Symbol("fontsManager"), s = Symbol("themeStyles"), c = Symbol("tplUiTheme"), l = Symbol("blockDefaults"), u = Symbol("blockRegistry"), d = Symbol("customBlockDefinitions"), f = Symbol("mergeTags"), p = Symbol("mergeTagSyntax"), m = Symbol("onRequestMergeTag"), h = Symbol("mergeTagAutocomplete"), g = Symbol("onRequestMedia"), _ = Symbol("displayConditions"), v = Symbol("allowCustomConditions"), y = Symbol("capabilities"), b = Symbol("keyboardReorder"), x = Symbol("accessibilityLint"), S = Symbol("editorRoot"), C = Symbol("popoverRoot"), w = Symbol("authManager"), T = Symbol("aiConfig"), E = Symbol("comments"), D = Symbol("savedModulesHeadless"), O = Symbol("scoring"), k = Symbol("cloudTranslations");
4
+ function A(t, n) {
5
5
  let r = e(t, null);
6
6
  if (r == null) throw Error(`${n} requires a provider for ${t.description ?? "unknown key"}. Ensure it is a descendant of Editor or CloudEditor.`);
7
7
  return r;
8
8
  }
9
9
  //#endregion
10
- export { T as C, c as D, t as E, O, m as S, s as T, b as _, i as a, p as b, y as c, a as d, d as f, r as g, o as h, S as i, D as l, n as m, C as n, l as o, _ as p, v as r, u as s, x as t, w as u, f as v, E as w, g as x, h as y };
10
+ export { A, m as C, s as D, O as E, t as O, g as S, D as T, r as _, i as a, h as b, y as c, a as d, d as f, o as g, S as h, w as i, c as k, k as l, n as m, T as n, l as o, _ as p, v as r, u as s, x as t, E as u, b as v, C as w, p as x, f as y };