@aswin.dev/editor 0.8.2 → 0.8.4

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 (85) hide show
  1. package/dist/{AiChatSidebar-DM2ri7KX.js → AiChatSidebar-DAJOuHPG.js} +1 -1
  2. package/dist/{AiFeatureMenu-BrZBJ0R5.js → AiFeatureMenu-CKnEDzme.js} +1 -1
  3. package/dist/{CloudEditor-BRpzzfQn.js → CloudEditor-DzdiTmvD.js} +9 -9
  4. package/dist/{CollaboratorBar-DsRXj3Kn.js → CollaboratorBar-C6TfqpyV.js} +1 -1
  5. package/dist/{DesignReferenceSidebar-B1iw3Bb7.js → DesignReferenceSidebar-C6yDJlD8.js} +1 -1
  6. package/dist/{ModuleBrowserModal-DVnd1dqR.js → ModuleBrowserModal-DUKuDjS-.js} +4 -4
  7. package/dist/{ModulePreviewCanvas-D9jBAoBQ.js → ModulePreviewCanvas-C9HTjfkN.js} +1 -1
  8. package/dist/{ParagraphEditor-CO3V0H_V.js → ParagraphEditor-CFuaZKGk.js} +2 -2
  9. package/dist/{SaveModuleDialog-CboNFwIA.js → SaveModuleDialog-QjgPFEDg.js} +2 -2
  10. package/dist/{SnapshotHistory-C26pvPre.js → SnapshotHistory-Dblt21_W.js} +1 -1
  11. package/dist/{TestEmailModal-B4OoUWb_.js → TestEmailModal-D_y3ZksG.js} +2 -2
  12. package/dist/{TitleEditor-DU_QWG_m.js → TitleEditor-CHmL3FFR.js} +2 -2
  13. package/dist/{TplModal-Dr6Do8oU.js → TplModal-DinL7DhQ.js} +1 -1
  14. package/dist/{blockTypeIcons-cRDr36gk.js → blockTypeIcons-JjmIZ_Vx.js} +1 -1
  15. package/dist/bundle-stats.json +6 -6
  16. package/dist/cdn/chunks/{AccessibilityPanel-Csd47zqI.js → AccessibilityPanel-BTADkTp2.js} +3 -3
  17. package/dist/cdn/chunks/{AccessibilityPanel-Csd47zqI.js.map → AccessibilityPanel-BTADkTp2.js.map} +1 -1
  18. package/dist/cdn/chunks/{AiFeatureMenu-BlnZF5pf.js → AiFeatureMenu-D99Pw32q.js} +2 -2
  19. package/dist/cdn/chunks/{AiFeatureMenu-BlnZF5pf.js.map → AiFeatureMenu-D99Pw32q.js.map} +1 -1
  20. package/dist/cdn/chunks/{BlockA11yBadge-VY8NqI9n.js → BlockA11yBadge-dtyeSSvY.js} +2 -2
  21. package/dist/cdn/chunks/{BlockA11yBadge-VY8NqI9n.js.map → BlockA11yBadge-dtyeSSvY.js.map} +1 -1
  22. package/dist/cdn/chunks/{CloudEditor-D0TrKlva.js → CloudEditor-nQ7qsrkA.js} +175 -175
  23. package/dist/cdn/chunks/{CloudEditor-D0TrKlva.js.map → CloudEditor-nQ7qsrkA.js.map} +1 -1
  24. package/dist/cdn/chunks/{CollaboratorBar-BhMNTI_h.js → CollaboratorBar-Cuy1BusU.js} +3 -3
  25. package/dist/cdn/chunks/{CollaboratorBar-BhMNTI_h.js.map → CollaboratorBar-Cuy1BusU.js.map} +1 -1
  26. package/dist/cdn/chunks/{CountdownToolbar-DsP6O1fl.js → CountdownToolbar-CqFM85Xv.js} +3 -3
  27. package/dist/cdn/chunks/{CountdownToolbar-DsP6O1fl.js.map → CountdownToolbar-CqFM85Xv.js.map} +1 -1
  28. package/dist/cdn/chunks/{ModuleBrowserModal-CpYPeiKv.js → ModuleBrowserModal-Dcxtq_dC.js} +5 -5
  29. package/dist/cdn/chunks/{ModuleBrowserModal-CpYPeiKv.js.map → ModuleBrowserModal-Dcxtq_dC.js.map} +1 -1
  30. package/dist/cdn/chunks/{ModulePreviewCanvas-BIYYnqUq.js → ModulePreviewCanvas-Didb4ZCU.js} +2 -2
  31. package/dist/cdn/chunks/{ModulePreviewCanvas-BIYYnqUq.js.map → ModulePreviewCanvas-Didb4ZCU.js.map} +1 -1
  32. package/dist/cdn/chunks/{ParagraphEditor-ZV5SYYw8.js → ParagraphEditor-BX2L35YF.js} +21 -21
  33. package/dist/cdn/chunks/{ParagraphEditor-ZV5SYYw8.js.map → ParagraphEditor-BX2L35YF.js.map} +1 -1
  34. package/dist/cdn/chunks/{RichTextEditorContent-Dx05ETtt.js → RichTextEditorContent-ywrdsvHY.js} +2 -2
  35. package/dist/cdn/chunks/{RichTextEditorContent-Dx05ETtt.js.map → RichTextEditorContent-ywrdsvHY.js.map} +1 -1
  36. package/dist/cdn/chunks/{SaveModuleDialog-DJEEK7Wb.js → SaveModuleDialog-Ddp-HCFB.js} +2 -2
  37. package/dist/cdn/chunks/{SaveModuleDialog-DJEEK7Wb.js.map → SaveModuleDialog-Ddp-HCFB.js.map} +1 -1
  38. package/dist/cdn/chunks/{SlidingPillSelect-BhPCkqVu.js → SlidingPillSelect-CS6N3vGq.js} +2 -2
  39. package/dist/cdn/chunks/{SlidingPillSelect-BhPCkqVu.js.map → SlidingPillSelect-CS6N3vGq.js.map} +1 -1
  40. package/dist/cdn/chunks/{TitleEditor-fu1A87Ld.js → TitleEditor-BTtgZzci.js} +8 -8
  41. package/dist/cdn/chunks/{TitleEditor-fu1A87Ld.js.map → TitleEditor-BTtgZzci.js.map} +1 -1
  42. package/dist/cdn/chunks/{blockTypeIcons-CJirTS-q.js → blockTypeIcons-B2x3gJsz.js} +3 -3
  43. package/dist/cdn/chunks/{blockTypeIcons-CJirTS-q.js.map → blockTypeIcons-B2x3gJsz.js.map} +1 -1
  44. package/dist/{de-CR1qAkAm.js → cdn/chunks/de-Dum1oCPV.js} +27 -3
  45. package/dist/cdn/chunks/de-Dum1oCPV.js.map +1 -0
  46. package/dist/{en-jbnp1n6M.js → cdn/chunks/en-ClDdvkbR.js} +27 -3
  47. package/dist/cdn/chunks/en-ClDdvkbR.js.map +1 -0
  48. package/dist/cdn/chunks/{extensions-BbFKsjyT.js → extensions-CdYSoz4B.js} +3 -3
  49. package/dist/cdn/chunks/{extensions-BbFKsjyT.js.map → extensions-CdYSoz4B.js.map} +1 -1
  50. package/dist/cdn/chunks/{features-uApxwJMz.js → features-BZeMkHMs.js} +1701 -1651
  51. package/dist/cdn/chunks/features-BZeMkHMs.js.map +1 -0
  52. package/dist/cdn/chunks/{icons-DZb4EX9m.js → icons-nP0Wj6Eq.js} +81 -66
  53. package/dist/cdn/chunks/icons-nP0Wj6Eq.js.map +1 -0
  54. package/dist/cdn/chunks/{media-library-B3g52j8R.js → media-library-54Ezt5wL.js} +3 -3
  55. package/dist/cdn/chunks/{media-library-B3g52j8R.js.map → media-library-54Ezt5wL.js.map} +1 -1
  56. package/dist/cdn/chunks/{quality-Ug5lFGHP.js → quality-Di1pUcCW.js} +59 -59
  57. package/dist/cdn/chunks/{quality-Ug5lFGHP.js.map → quality-Di1pUcCW.js.map} +1 -1
  58. package/dist/cdn/chunks/{renderer-jXCdXjV-.js → renderer-C4OuJhDG.js} +16 -16
  59. package/dist/cdn/chunks/{renderer-jXCdXjV-.js.map → renderer-C4OuJhDG.js.map} +1 -1
  60. package/dist/cdn/chunks/{src-FMtH5ZvJ.js → src-DF5NNH7S.js} +4 -4
  61. package/dist/cdn/chunks/{src-FMtH5ZvJ.js.map → src-DF5NNH7S.js.map} +1 -1
  62. package/dist/cdn/chunks/{styles-Co9vw4ag.js → styles-BZJ3yQ0G.js} +1633 -1361
  63. package/dist/cdn/chunks/styles-BZJ3yQ0G.js.map +1 -0
  64. package/dist/cdn/chunks/{tiptap-qXOh0vzV.js → tiptap-CZ52Pj8k.js} +2 -2
  65. package/dist/cdn/chunks/{tiptap-qXOh0vzV.js.map → tiptap-CZ52Pj8k.js.map} +1 -1
  66. package/dist/cdn/editor.css +1 -1
  67. package/dist/cdn/editor.js +12 -12
  68. package/dist/cdn/editor.js.map +1 -1
  69. package/dist/{cloud-COUuu_KE.js → cloud-9i5Nj7rp.js} +9 -9
  70. package/dist/{cdn/chunks/de-BsYijc0r.js → de-DP4gn3tf.js} +25 -5
  71. package/dist/{dist-DP82Y0rs.js → dist-Bcqepqv3.js} +222 -169
  72. package/dist/{cdn/chunks/en-DMu9hPIC.js → en-CyZxmGTU.js} +25 -5
  73. package/dist/{extensions-Bg22D6c8.js → extensions-CNPdJw_8.js} +2 -2
  74. package/dist/index.d.ts +8 -8
  75. package/dist/style.css +1 -1
  76. package/dist/{styles-DgL0UYj0.js → styles-Czir-MYq.js} +1561 -1274
  77. package/dist/templatical-editor.js +9 -9
  78. package/dist/{useEditorCore-CSlYQZWx.js → useEditorCore-BHSyoxSK.js} +16 -16
  79. package/dist/{useMergeTag-BVL3A4OO.js → useMergeTag-BfVcyNSv.js} +1 -1
  80. package/package.json +7 -7
  81. package/dist/cdn/chunks/de-BsYijc0r.js.map +0 -1
  82. package/dist/cdn/chunks/en-DMu9hPIC.js.map +0 -1
  83. package/dist/cdn/chunks/features-uApxwJMz.js.map +0 -1
  84. package/dist/cdn/chunks/icons-DZb4EX9m.js.map +0 -1
  85. package/dist/cdn/chunks/styles-Co9vw4ag.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { C as e, F as t, G as n, M as r, N as i, P as a, T as o, W as s, _ as c, b as l, ct as u, dt as d, f as ee, ft as f, g as p, h as m, l as h, m as g, n as te, r as _, tt as v, ut as y, v as b, x, y as S } from "./chunks/draggable-C-1_gch3.js";
2
- import { $ as C, Bn as ne, Dn as re, fn as w, k as ie, lr as T, on as ae, ot as E, zn as oe } from "./chunks/features-uApxwJMz.js";
3
- import { D as se } from "./chunks/icons-DZb4EX9m.js";
4
- import { a as ce, c as le, d as ue, f as de, g as fe, h as D, i as pe, l as me, m as O, n as he, o as k, p as A, r as j, s as M, t as N, u as P } from "./chunks/styles-Co9vw4ag.js";
2
+ import { $ as C, Bn as ne, Dn as re, fn as w, k as ie, on as ae, ot as T, ur as E, zn as oe } from "./chunks/features-BZeMkHMs.js";
3
+ import { D as se } from "./chunks/icons-nP0Wj6Eq.js";
4
+ import { a as ce, c as le, d as ue, f as de, g as fe, h as D, i as pe, l as me, m as O, n as he, o as k, p as A, r as j, s as M, t as N, u as P } from "./chunks/styles-BZJ3yQ0G.js";
5
5
  //#region src/Editor.vue?vue&type=script&setup=true&lang.ts
6
6
  var F = ["data-tpl-theme"], I = {
7
7
  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)]",
@@ -13,10 +13,10 @@ var F = ["data-tpl-theme"], I = {
13
13
  }, L = { class: "tpl:flex tpl:items-center tpl:justify-center tpl:gap-10" }, R = { class: "tpl:sticky tpl:top-0 tpl:z-40 tpl:h-0" }, ge = {
14
14
  key: 0,
15
15
  class: "tpl:flex tpl:w-full tpl:flex-col tpl:gap-3 tpl:px-4 tpl:pt-4 tpl:pb-6"
16
- }, _e = { class: "tpl-popup-browser-frame tpl:flex tpl:min-h-[calc(100vh-11rem)] tpl:w-full tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-xl)]" }, ve = {
16
+ }, _e = { class: "tpl-popup-browser-frame tpl:flex tpl:min-h-[calc(100vh-11rem)] tpl:w-full tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-canvas-bg)] tpl:shadow-[var(--tpl-shadow-xl)]" }, ve = {
17
17
  key: 1,
18
18
  class: "tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:gap-3 tpl:p-8"
19
- }, ye = ["aria-label"], z = /* @__PURE__ */ E(/* @__PURE__ */ e({
19
+ }, ye = ["aria-label"], z = /* @__PURE__ */ T(/* @__PURE__ */ e({
20
20
  __name: "Editor",
21
21
  props: {
22
22
  config: {},
@@ -125,7 +125,7 @@ var F = ["data-tpl-theme"], I = {
125
125
  })]), z.value ? (a(), b("div", ge, [x(O, {
126
126
  "preview-mode": u(T).state.previewMode,
127
127
  "multi-page-canvas": e.config.multiPageCanvas ?? !1
128
- }, null, 8, ["preview-mode", "multi-page-canvas"]), m("div", _e, [r[7] ||= S("<div class=\"tpl-popup-browser-bar tpl:relative tpl:flex tpl:h-9 tpl:items-center tpl:gap-1.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3\" aria-hidden=\"true\" data-v-186f5453><span class=\"tpl:block tpl:size-3 tpl:rounded-full\" style=\"background-color:#ff5f57;\" data-v-186f5453></span><span class=\"tpl:block tpl:size-3 tpl:rounded-full\" style=\"background-color:#febc2e;\" data-v-186f5453></span><span class=\"tpl:block tpl:size-3 tpl:rounded-full\" style=\"background-color:#28c840;\" data-v-186f5453></span><div class=\"tpl:pointer-events-none tpl:absolute tpl:left-1/2 tpl:top-1/2 tpl:-translate-x-1/2 tpl:-translate-y-1/2 tpl:rounded-md tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-0.5 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tracking-tight\" data-v-186f5453> yourstore.com </div></div>", 1), x(D, {
128
+ }, null, 8, ["preview-mode", "multi-page-canvas"]), m("div", _e, [r[7] ||= S("<div class=\"tpl-popup-browser-bar tpl:relative tpl:flex tpl:h-9 tpl:items-center tpl:gap-1.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3\" aria-hidden=\"true\" data-v-d6660368><span class=\"tpl:block tpl:size-3 tpl:rounded-full\" style=\"background-color:#ff5f57;\" data-v-d6660368></span><span class=\"tpl:block tpl:size-3 tpl:rounded-full\" style=\"background-color:#febc2e;\" data-v-d6660368></span><span class=\"tpl:block tpl:size-3 tpl:rounded-full\" style=\"background-color:#28c840;\" data-v-d6660368></span><div class=\"tpl:pointer-events-none tpl:absolute tpl:left-1/2 tpl:top-1/2 tpl:-translate-x-1/2 tpl:-translate-y-1/2 tpl:rounded-md tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-0.5 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tracking-tight\" data-v-d6660368> yourstore.com </div></div>", 1), x(D, {
129
129
  class: "tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col",
130
130
  viewport: u(T).state.viewport,
131
131
  content: u(T).content.value,
@@ -194,9 +194,9 @@ var F = ["data-tpl-theme"], I = {
194
194
  }, null, 8, ["tour-config", "dark-mode"])) : c("", !0)
195
195
  ], 14, F));
196
196
  }
197
- }), [["__scopeId", "data-v-186f5453"]]), B = /* @__PURE__ */ Object.assign({
198
- "./locales/de.ts": () => import("./chunks/de-BsYijc0r.js"),
199
- "./locales/en.ts": () => import("./chunks/en-DMu9hPIC.js")
197
+ }), [["__scopeId", "data-v-d6660368"]]), B = /* @__PURE__ */ Object.assign({
198
+ "./locales/de.ts": () => import("./chunks/de-Dum1oCPV.js"),
199
+ "./locales/en.ts": () => import("./chunks/en-ClDdvkbR.js")
200
200
  }), V = /* @__PURE__ */ Object.assign({
201
201
  "./locales/cloud/de.ts": () => import("./chunks/de-BPHtelu7.js"),
202
202
  "./locales/cloud/en.ts": () => import("./chunks/en-DejwuJhw.js")
@@ -225,7 +225,7 @@ async function be(e) {
225
225
  async function xe(e) {
226
226
  let t;
227
227
  try {
228
- t = await import("./chunks/renderer-jXCdXjV-.js").then((e) => e.t);
228
+ t = await import("./chunks/renderer-C4OuJhDG.js").then((e) => e.t);
229
229
  } catch {
230
230
  throw Error("[Templatical] toMjml() requires the @aswin.dev/renderer package. Please install it.");
231
231
  }
@@ -292,11 +292,11 @@ var X = null, Z = v(null);
292
292
  async function Ce(e) {
293
293
  let t = typeof e.container == "string" ? document.querySelector(e.container) : e.container;
294
294
  if (!t) throw Error(`[Templatical] Container element not found: ${e.container}`);
295
- let { default: n } = await import("./chunks/CloudEditor-D0TrKlva.js"), [r, i] = await Promise.all([q(e.locale ?? "en"), be(e.locale ?? "en")]), a = C(e.fonts);
295
+ let { default: n } = await import("./chunks/CloudEditor-nQ7qsrkA.js"), [r, i] = await Promise.all([q(e.locale ?? "en"), be(e.locale ?? "en")]), a = C(e.fonts);
296
296
  return X && $(), await new Promise((s, c) => {
297
297
  let l = setTimeout(() => {
298
298
  c(/* @__PURE__ */ Error("[Templatical] Cloud editor initialization timed out"));
299
- }, T);
299
+ }, E);
300
300
  X = _({ setup() {
301
301
  return () => o(n, {
302
302
  config: e,
@@ -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 { computed, onMounted, onUnmounted, provide, ref } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport type { EditorTourStartOptions } from \"./types/editor-tour\";\nimport { useEditor } from \"@aswin.dev/core\";\nimport type { TemplateContent, UiTheme } from \"@aswin.dev/types\";\nimport {\n createDefaultPopupEmbedSettings,\n POPUP_SIZE_PRESET_WIDTH,\n} from \"@aswin.dev/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 CanvasPagesNav from \"./components/CanvasPagesNav.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport HistoryUndoRedo from \"./components/HistoryUndoRedo.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 EditorTour from \"./components/EditorTour.vue\";\nimport PopupDisplayRulesView from \"./components/PopupDisplayRulesView.vue\";\nimport PopupScheduleView from \"./components/PopupScheduleView.vue\";\nimport \"./styles/index.css\";\nimport { POPUP_RAIL_TAB_KEY } from \"./keys\";\nimport type { PopupRailTabId } from \"./types/popup-rail-tab\";\nimport { normalizeEditorType } from \"./types/editor-type\";\nimport {\n clearTourDismissedState,\n readTourDismissedState,\n} from \"./utils/tourStorage\";\n\nconst editorTourRef = ref<InstanceType<typeof EditorTour> | null>(null);\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 editorType: props.config.editorType,\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 if (\n normalizeEditorType(props.config.editorType) === \"popup\" &&\n !editor.content.value.settings.popup\n ) {\n const defaults = createDefaultPopupEmbedSettings();\n editor.updateSettings({\n popup: defaults,\n width: POPUP_SIZE_PRESET_WIDTH[defaults.design.sizePreset],\n });\n }\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\nconst popupRailTab = ref<PopupRailTabId>(\"blocks\");\nprovide(POPUP_RAIL_TAB_KEY, popupRailTab);\n\nconst isPopupEditor = computed(\n () => normalizeEditorType(props.config.editorType) === \"popup\",\n);\n\nconst popupFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"displayRules\",\n);\n\nconst popupScheduleFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"schedule\",\n);\n\n/** Design-only: 320px strip beside the canvas (not schedule — schedule uses full workspace). */\nconst popupWideAdjacentPanel = computed(() => popupRailTab.value === \"design\");\n\nconst popupHidesRightSidebar = computed(\n () =>\n isPopupEditor.value &&\n (popupFullWorkspace.value ||\n popupScheduleFullWorkspace.value ||\n popupWideAdjacentPanel.value),\n);\n\n/** Popup: left inset = 72px rail only (display rules / schedule), or + blocks/design strips when applicable. */\nconst editorChromeHorizontalInset = computed(() => {\n if (editor.state.previewMode) return [\"tpl:left-0\", \"tpl:right-0\"];\n if (!isPopupEditor.value) {\n return [\"tpl:left-12\", \"tpl:right-[320px]\"];\n }\n let left: string;\n if (popupRailTab.value === \"blocks\") {\n left = \"tpl:left-[272px]\";\n } else if (popupWideAdjacentPanel.value) {\n left = \"tpl:left-[392px]\";\n } else {\n left = \"tpl:left-[72px]\";\n }\n const right = popupHidesRightSidebar.value\n ? \"tpl:right-0\"\n : \"tpl:right-[320px]\";\n return [left, right];\n});\n\nconst popupSettingsWorkspaceBg = computed(\n () => popupFullWorkspace.value || popupScheduleFullWorkspace.value,\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 undo: () => core.history.undo(),\n redo: () => core.history.redo(),\n canUndo: () => core.history.canUndo.value,\n canRedo: () => core.history.canRedo.value,\n renderCustomBlock: core.registry.renderCustomBlock,\n startTour: (options?: EditorTourStartOptions) =>\n editorTourRef.value?.start(options),\n dismissTour: () => editorTourRef.value?.dismiss(),\n resetTourDismissed: () => clearTourDismissedState(props.config.tour),\n isTourDismissed: () =>\n props.config.tour ? readTourDismissedState(props.config.tour) : true,\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 <HistoryUndoRedo />\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\"\n :style=\"{\n transition: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',\n backgroundColor: popupSettingsWorkspaceBg\n ? 'var(--tpl-bg)'\n : 'var(--tpl-canvas-bg)',\n }\"\n :class=\"[...editorChromeHorizontalInset, 'tpl:top-14']\"\n >\n <template v-if=\"isPopupEditor && popupRailTab === 'displayRules'\">\n <PopupDisplayRulesView />\n </template>\n <template v-else-if=\"isPopupEditor && popupRailTab === 'schedule'\">\n <PopupScheduleView layout=\"standalone\" />\n </template>\n <template v-else>\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 <!-- Popup editor: full-width browser chrome, with steps strip above it -->\n <div\n v-if=\"isPopupEditor\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:gap-3 tpl:px-4 tpl:pt-4 tpl:pb-6\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\n <div\n class=\"tpl-popup-browser-frame tpl:flex tpl:min-h-[calc(100vh-11rem)] tpl:w-full tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-xl)]\"\n >\n <div\n class=\"tpl-popup-browser-bar tpl:relative tpl:flex tpl:h-9 tpl:items-center tpl:gap-1.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3\"\n aria-hidden=\"true\"\n >\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #ff5f57\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #febc2e\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #28c840\"\n />\n <div\n class=\"tpl:pointer-events-none tpl:absolute tpl:left-1/2 tpl:top-1/2 tpl:-translate-x-1/2 tpl:-translate-y-1/2 tpl:rounded-md tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-0.5 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tracking-tight\"\n >\n yourstore.com\n </div>\n </div>\n <Canvas\n class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col\"\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <!-- Email editor: original centered canvas (with internal step strip from CanvasPagesNav) -->\n <div\n v-else\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:gap-3 tpl:p-8\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </template>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"editorChromeHorizontalInset\"\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 && !popupHidesRightSidebar\"\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 <EditorTour\n v-if=\"config.tour\"\n ref=\"editorTourRef\"\n :tour-config=\"config.tour\"\n :dark-mode=\"editor.state.darkMode\"\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 { computed, onMounted, onUnmounted, provide, ref } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport type { EditorTourStartOptions } from \"./types/editor-tour\";\nimport { useEditor } from \"@aswin.dev/core\";\nimport type { TemplateContent, UiTheme } from \"@aswin.dev/types\";\nimport {\n createDefaultPopupEmbedSettings,\n POPUP_SIZE_PRESET_WIDTH,\n} from \"@aswin.dev/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 CanvasPagesNav from \"./components/CanvasPagesNav.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport HistoryUndoRedo from \"./components/HistoryUndoRedo.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 EditorTour from \"./components/EditorTour.vue\";\nimport PopupDisplayRulesView from \"./components/PopupDisplayRulesView.vue\";\nimport PopupScheduleView from \"./components/PopupScheduleView.vue\";\nimport \"./styles/index.css\";\nimport { POPUP_RAIL_TAB_KEY } from \"./keys\";\nimport type { PopupRailTabId } from \"./types/popup-rail-tab\";\nimport { normalizeEditorType } from \"./types/editor-type\";\nimport {\n clearTourDismissedState,\n readTourDismissedState,\n} from \"./utils/tourStorage\";\n\nconst editorTourRef = ref<InstanceType<typeof EditorTour> | null>(null);\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 editorType: props.config.editorType,\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 if (\n normalizeEditorType(props.config.editorType) === \"popup\" &&\n !editor.content.value.settings.popup\n ) {\n const defaults = createDefaultPopupEmbedSettings();\n editor.updateSettings({\n popup: defaults,\n width: POPUP_SIZE_PRESET_WIDTH[defaults.design.sizePreset],\n });\n }\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\nconst popupRailTab = ref<PopupRailTabId>(\"blocks\");\nprovide(POPUP_RAIL_TAB_KEY, popupRailTab);\n\nconst isPopupEditor = computed(\n () => normalizeEditorType(props.config.editorType) === \"popup\",\n);\n\nconst popupFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"displayRules\",\n);\n\nconst popupScheduleFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"schedule\",\n);\n\n/** Design-only: 320px strip beside the canvas (not schedule — schedule uses full workspace). */\nconst popupWideAdjacentPanel = computed(() => popupRailTab.value === \"design\");\n\nconst popupHidesRightSidebar = computed(\n () =>\n isPopupEditor.value &&\n (popupFullWorkspace.value ||\n popupScheduleFullWorkspace.value ||\n popupWideAdjacentPanel.value),\n);\n\n/** Popup: left inset = 72px rail only (display rules / schedule), or + blocks/design strips when applicable. */\nconst editorChromeHorizontalInset = computed(() => {\n if (editor.state.previewMode) return [\"tpl:left-0\", \"tpl:right-0\"];\n if (!isPopupEditor.value) {\n return [\"tpl:left-12\", \"tpl:right-[320px]\"];\n }\n let left: string;\n if (popupRailTab.value === \"blocks\") {\n left = \"tpl:left-[272px]\";\n } else if (popupWideAdjacentPanel.value) {\n left = \"tpl:left-[392px]\";\n } else {\n left = \"tpl:left-[72px]\";\n }\n const right = popupHidesRightSidebar.value\n ? \"tpl:right-0\"\n : \"tpl:right-[320px]\";\n return [left, right];\n});\n\nconst popupSettingsWorkspaceBg = computed(\n () => popupFullWorkspace.value || popupScheduleFullWorkspace.value,\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 undo: () => core.history.undo(),\n redo: () => core.history.redo(),\n canUndo: () => core.history.canUndo.value,\n canRedo: () => core.history.canRedo.value,\n renderCustomBlock: core.registry.renderCustomBlock,\n startTour: (options?: EditorTourStartOptions) =>\n editorTourRef.value?.start(options),\n dismissTour: () => editorTourRef.value?.dismiss(),\n resetTourDismissed: () => clearTourDismissedState(props.config.tour),\n isTourDismissed: () =>\n props.config.tour ? readTourDismissedState(props.config.tour) : true,\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 <HistoryUndoRedo />\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\"\n :style=\"{\n transition: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',\n backgroundColor: popupSettingsWorkspaceBg\n ? 'var(--tpl-bg)'\n : 'var(--tpl-canvas-bg)',\n }\"\n :class=\"[...editorChromeHorizontalInset, 'tpl:top-14']\"\n >\n <template v-if=\"isPopupEditor && popupRailTab === 'displayRules'\">\n <PopupDisplayRulesView />\n </template>\n <template v-else-if=\"isPopupEditor && popupRailTab === 'schedule'\">\n <PopupScheduleView layout=\"standalone\" />\n </template>\n <template v-else>\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 <!-- Popup editor: full-width browser chrome, with steps strip above it -->\n <div\n v-if=\"isPopupEditor\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:gap-3 tpl:px-4 tpl:pt-4 tpl:pb-6\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\n <div\n class=\"tpl-popup-browser-frame tpl:flex tpl:min-h-[calc(100vh-11rem)] tpl:w-full tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg)] tpl:shadow-[var(--tpl-shadow-xl)]\"\n >\n <div\n class=\"tpl-popup-browser-bar tpl:relative tpl:flex tpl:h-9 tpl:items-center tpl:gap-1.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3\"\n aria-hidden=\"true\"\n >\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #ff5f57\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #febc2e\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #28c840\"\n />\n <div\n class=\"tpl:pointer-events-none tpl:absolute tpl:left-1/2 tpl:top-1/2 tpl:-translate-x-1/2 tpl:-translate-y-1/2 tpl:rounded-md tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-0.5 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tracking-tight\"\n >\n yourstore.com\n </div>\n </div>\n <Canvas\n class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col\"\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <!-- Email editor: original centered canvas (with internal step strip from CanvasPagesNav) -->\n <div\n v-else\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:gap-3 tpl:p-8\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </template>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"editorChromeHorizontalInset\"\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 && !popupHidesRightSidebar\"\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 <EditorTour\n v-if=\"config.tour\"\n ref=\"editorTourRef\"\n :tour-config=\"config.tour\"\n :dark-mode=\"editor.state.darkMode\"\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\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 locale.split(\"-\")[0].toLowerCase();\n}\n\nfunction resolveLocale(locale: string, supported: string[]): string {\n const base = getBaseLocale(locale);\n return supported.includes(base) ? base : \"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 base, e.g. en-GB → en). */\nexport function isLocaleSupported(locale: string): boolean {\n return (\n supportedLocales.includes(locale) ||\n supportedLocales.includes(getBaseLocale(locale))\n );\n}\n\n/** Check if a locale has cloud translations (matched by base). */\nexport function isCloudLocaleSupported(locale: string): boolean {\n return (\n supportedCloudLocales.includes(locale) ||\n supportedCloudLocales.includes(getBaseLocale(locale))\n );\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 \"@aswin.dev/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 `@aswin.dev/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(\"@aswin.dev/renderer\");\n try {\n renderer = await import(\"@aswin.dev/renderer\");\n } catch {\n throw new Error(\n \"[Templatical] toMjml() requires the @aswin.dev/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 \"@aswin.dev/types\";\nimport type { MediaRequestContext } from \"@aswin.dev/media-library\";\n\nimport Editor from \"./Editor.vue\";\nimport type { EditorType } from \"./types/editor-type\";\nimport type {\n EditorTourConfig,\n EditorTourStartOptions,\n} from \"./types/editor-tour\";\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 * Multi-step canvas (popup flows): step strip above the canvas; content exposes\n * {@link TemplateContent.canvasPages} and {@link TemplateContent.activeCanvasPageId}.\n */\n multiPageCanvas?: boolean;\n\n /**\n * `'email'` (default): single hover-expand block rail. `'popup'`: adds a left icon rail\n * (Design, Blocks, Tab, …) beside the same block palette when Blocks is selected.\n * The value is provided through `EDITOR_TYPE_KEY`. Case-insensitive strings such as\n * `'POPUP'` are accepted.\n */\n editorType?: EditorType | string;\n\n /**\n * Accessibility linter (`@aswin.dev/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 `@aswin.dev/quality`.\n */\n accessibility?: import(\"@aswin.dev/quality\").A11yOptions;\n\n /**\n * Optional spotlight tour (inside or outside the editor DOM via selectors).\n * Use `startTour` / `isTourDismissed` on the instance; default copy lives under locale `tour.defaults`.\n */\n tour?: EditorTourConfig;\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 /** Requires `tour` in editor config; otherwise no-ops. */\n startTour(options?: EditorTourStartOptions): void;\n dismissTour(): void;\n resetTourDismissed(): void;\n /** `true` when no tour is configured or the user dismissed a persisted tour. */\n isTourDismissed(): boolean;\n /**\n * Undo the last canvas/template change (same stack as toolbar and Cmd/Ctrl+Z).\n * Rich text fields use TipTap’s own undo while focused.\n */\n undo(): void;\n /** Redo (same stack as toolbar and Cmd/Ctrl+Shift+Z). */\n redo(): void;\n /** Whether a canvas/template undo is available. */\n canUndo(): boolean;\n /** Whether redo is available. */\n canRedo(): boolean;\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 * `@aswin.dev/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 `@aswin.dev/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 startTour(options?: EditorTourStartOptions) {\n editorRef.value?.startTour?.(options);\n },\n dismissTour() {\n editorRef.value?.dismissTour?.();\n },\n resetTourDismissed() {\n editorRef.value?.resetTourDismissed?.();\n },\n isTourDismissed() {\n return editorRef.value?.isTourDismissed?.() ?? true;\n },\n undo() {\n editorRef.value?.undo?.();\n },\n redo() {\n editorRef.value?.redo?.();\n },\n canUndo() {\n return editorRef.value?.canUndo?.() ?? false;\n },\n canRedo() {\n return editorRef.value?.canRedo?.() ?? false;\n },\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 startTour(options?: EditorTourStartOptions) {\n cloudEditorRef.value?.startTour?.(options);\n },\n dismissTour() {\n cloudEditorRef.value?.dismissTour?.();\n },\n resetTourDismissed() {\n cloudEditorRef.value?.resetTourDismissed?.();\n },\n isTourDismissed() {\n return cloudEditorRef.value?.isTourDismissed?.() ?? true;\n },\n undo() {\n cloudEditorRef.value?.undo?.();\n },\n redo() {\n cloudEditorRef.value?.redo?.();\n },\n canUndo() {\n return cloudEditorRef.value?.canUndo?.() ?? false;\n },\n canRedo() {\n return cloudEditorRef.value?.canRedo?.() ?? false;\n },\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 \"@aswin.dev/types\";\n\nexport type { UseFontsReturn, FontOption } from \"./composables/useFonts\";\nexport { useFonts } from \"./composables/useFonts\";\nexport type { EditorCapabilities } from \"./types/editor-capabilities\";\nexport type { EditorType } from \"./types/editor-type\";\nexport type {\n EditorTourConfig,\n EditorTourStep,\n EditorTourStepPlacement,\n EditorTourStartOptions,\n} from \"./types/editor-tour\";\nexport { DEFAULT_EDITOR_TOUR_STORAGE_KEY } from \"./types/editor-tour\";\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 { computed, onMounted, onUnmounted, provide, ref } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport type { EditorTourStartOptions } from \"./types/editor-tour\";\nimport { useEditor } from \"@aswin.dev/core\";\nimport type { TemplateContent, UiTheme } from \"@aswin.dev/types\";\nimport {\n createDefaultPopupEmbedSettings,\n POPUP_SIZE_PRESET_WIDTH,\n} from \"@aswin.dev/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 CanvasPagesNav from \"./components/CanvasPagesNav.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport HistoryUndoRedo from \"./components/HistoryUndoRedo.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 EditorTour from \"./components/EditorTour.vue\";\nimport PopupDisplayRulesView from \"./components/PopupDisplayRulesView.vue\";\nimport PopupScheduleView from \"./components/PopupScheduleView.vue\";\nimport \"./styles/index.css\";\nimport { POPUP_RAIL_TAB_KEY } from \"./keys\";\nimport type { PopupRailTabId } from \"./types/popup-rail-tab\";\nimport { normalizeEditorType } from \"./types/editor-type\";\nimport {\n clearTourDismissedState,\n readTourDismissedState,\n} from \"./utils/tourStorage\";\n\nconst editorTourRef = ref<InstanceType<typeof EditorTour> | null>(null);\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 editorType: props.config.editorType,\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 if (\n normalizeEditorType(props.config.editorType) === \"popup\" &&\n !editor.content.value.settings.popup\n ) {\n const defaults = createDefaultPopupEmbedSettings();\n editor.updateSettings({\n popup: defaults,\n width: POPUP_SIZE_PRESET_WIDTH[defaults.design.sizePreset],\n });\n }\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\nconst popupRailTab = ref<PopupRailTabId>(\"blocks\");\nprovide(POPUP_RAIL_TAB_KEY, popupRailTab);\n\nconst isPopupEditor = computed(\n () => normalizeEditorType(props.config.editorType) === \"popup\",\n);\n\nconst popupFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"displayRules\",\n);\n\nconst popupScheduleFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"schedule\",\n);\n\n/** Design-only: 320px strip beside the canvas (not schedule — schedule uses full workspace). */\nconst popupWideAdjacentPanel = computed(() => popupRailTab.value === \"design\");\n\nconst popupHidesRightSidebar = computed(\n () =>\n isPopupEditor.value &&\n (popupFullWorkspace.value ||\n popupScheduleFullWorkspace.value ||\n popupWideAdjacentPanel.value),\n);\n\n/** Popup: left inset = 72px rail only (display rules / schedule), or + blocks/design strips when applicable. */\nconst editorChromeHorizontalInset = computed(() => {\n if (editor.state.previewMode) return [\"tpl:left-0\", \"tpl:right-0\"];\n if (!isPopupEditor.value) {\n return [\"tpl:left-12\", \"tpl:right-[320px]\"];\n }\n let left: string;\n if (popupRailTab.value === \"blocks\") {\n left = \"tpl:left-[272px]\";\n } else if (popupWideAdjacentPanel.value) {\n left = \"tpl:left-[392px]\";\n } else {\n left = \"tpl:left-[72px]\";\n }\n const right = popupHidesRightSidebar.value\n ? \"tpl:right-0\"\n : \"tpl:right-[320px]\";\n return [left, right];\n});\n\nconst popupSettingsWorkspaceBg = computed(\n () => popupFullWorkspace.value || popupScheduleFullWorkspace.value,\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 undo: () => core.history.undo(),\n redo: () => core.history.redo(),\n canUndo: () => core.history.canUndo.value,\n canRedo: () => core.history.canRedo.value,\n renderCustomBlock: core.registry.renderCustomBlock,\n startTour: (options?: EditorTourStartOptions) =>\n editorTourRef.value?.start(options),\n dismissTour: () => editorTourRef.value?.dismiss(),\n resetTourDismissed: () => clearTourDismissedState(props.config.tour),\n isTourDismissed: () =>\n props.config.tour ? readTourDismissedState(props.config.tour) : true,\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 <HistoryUndoRedo />\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\"\n :style=\"{\n transition: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',\n backgroundColor: popupSettingsWorkspaceBg\n ? 'var(--tpl-bg)'\n : 'var(--tpl-canvas-bg)',\n }\"\n :class=\"[...editorChromeHorizontalInset, 'tpl:top-14']\"\n >\n <template v-if=\"isPopupEditor && popupRailTab === 'displayRules'\">\n <PopupDisplayRulesView />\n </template>\n <template v-else-if=\"isPopupEditor && popupRailTab === 'schedule'\">\n <PopupScheduleView layout=\"standalone\" />\n </template>\n <template v-else>\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 <!-- Popup editor: full-width browser chrome, with steps strip above it -->\n <div\n v-if=\"isPopupEditor\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:gap-3 tpl:px-4 tpl:pt-4 tpl:pb-6\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\n <div\n class=\"tpl-popup-browser-frame tpl:flex tpl:min-h-[calc(100vh-11rem)] tpl:w-full tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-canvas-bg)] tpl:shadow-[var(--tpl-shadow-xl)]\"\n >\n <div\n class=\"tpl-popup-browser-bar tpl:relative tpl:flex tpl:h-9 tpl:items-center tpl:gap-1.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3\"\n aria-hidden=\"true\"\n >\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #ff5f57\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #febc2e\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #28c840\"\n />\n <div\n class=\"tpl:pointer-events-none tpl:absolute tpl:left-1/2 tpl:top-1/2 tpl:-translate-x-1/2 tpl:-translate-y-1/2 tpl:rounded-md tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-0.5 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tracking-tight\"\n >\n yourstore.com\n </div>\n </div>\n <Canvas\n class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col\"\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <!-- Email editor: original centered canvas (with internal step strip from CanvasPagesNav) -->\n <div\n v-else\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:gap-3 tpl:p-8\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </template>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"editorChromeHorizontalInset\"\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 && !popupHidesRightSidebar\"\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 <EditorTour\n v-if=\"config.tour\"\n ref=\"editorTourRef\"\n :tour-config=\"config.tour\"\n :dark-mode=\"editor.state.darkMode\"\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 { computed, onMounted, onUnmounted, provide, ref } from \"vue\";\nimport type { TemplaticalEditorConfig } from \"./index\";\nimport type { EditorTourStartOptions } from \"./types/editor-tour\";\nimport { useEditor } from \"@aswin.dev/core\";\nimport type { TemplateContent, UiTheme } from \"@aswin.dev/types\";\nimport {\n createDefaultPopupEmbedSettings,\n POPUP_SIZE_PRESET_WIDTH,\n} from \"@aswin.dev/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 CanvasPagesNav from \"./components/CanvasPagesNav.vue\";\nimport Sidebar from \"./components/Sidebar.vue\";\nimport RightSidebar from \"./components/RightSidebar.vue\";\nimport HistoryUndoRedo from \"./components/HistoryUndoRedo.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 EditorTour from \"./components/EditorTour.vue\";\nimport PopupDisplayRulesView from \"./components/PopupDisplayRulesView.vue\";\nimport PopupScheduleView from \"./components/PopupScheduleView.vue\";\nimport \"./styles/index.css\";\nimport { POPUP_RAIL_TAB_KEY } from \"./keys\";\nimport type { PopupRailTabId } from \"./types/popup-rail-tab\";\nimport { normalizeEditorType } from \"./types/editor-type\";\nimport {\n clearTourDismissedState,\n readTourDismissedState,\n} from \"./utils/tourStorage\";\n\nconst editorTourRef = ref<InstanceType<typeof EditorTour> | null>(null);\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 editorType: props.config.editorType,\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 if (\n normalizeEditorType(props.config.editorType) === \"popup\" &&\n !editor.content.value.settings.popup\n ) {\n const defaults = createDefaultPopupEmbedSettings();\n editor.updateSettings({\n popup: defaults,\n width: POPUP_SIZE_PRESET_WIDTH[defaults.design.sizePreset],\n });\n }\n});\n\nonUnmounted(() => {\n props.fontsManager.cleanupFontLinks();\n core.destroy();\n});\n\nconst popupRailTab = ref<PopupRailTabId>(\"blocks\");\nprovide(POPUP_RAIL_TAB_KEY, popupRailTab);\n\nconst isPopupEditor = computed(\n () => normalizeEditorType(props.config.editorType) === \"popup\",\n);\n\nconst popupFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"displayRules\",\n);\n\nconst popupScheduleFullWorkspace = computed(\n () => isPopupEditor.value && popupRailTab.value === \"schedule\",\n);\n\n/** Design-only: 320px strip beside the canvas (not schedule — schedule uses full workspace). */\nconst popupWideAdjacentPanel = computed(() => popupRailTab.value === \"design\");\n\nconst popupHidesRightSidebar = computed(\n () =>\n isPopupEditor.value &&\n (popupFullWorkspace.value ||\n popupScheduleFullWorkspace.value ||\n popupWideAdjacentPanel.value),\n);\n\n/** Popup: left inset = 72px rail only (display rules / schedule), or + blocks/design strips when applicable. */\nconst editorChromeHorizontalInset = computed(() => {\n if (editor.state.previewMode) return [\"tpl:left-0\", \"tpl:right-0\"];\n if (!isPopupEditor.value) {\n return [\"tpl:left-12\", \"tpl:right-[320px]\"];\n }\n let left: string;\n if (popupRailTab.value === \"blocks\") {\n left = \"tpl:left-[272px]\";\n } else if (popupWideAdjacentPanel.value) {\n left = \"tpl:left-[392px]\";\n } else {\n left = \"tpl:left-[72px]\";\n }\n const right = popupHidesRightSidebar.value\n ? \"tpl:right-0\"\n : \"tpl:right-[320px]\";\n return [left, right];\n});\n\nconst popupSettingsWorkspaceBg = computed(\n () => popupFullWorkspace.value || popupScheduleFullWorkspace.value,\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 undo: () => core.history.undo(),\n redo: () => core.history.redo(),\n canUndo: () => core.history.canUndo.value,\n canRedo: () => core.history.canRedo.value,\n renderCustomBlock: core.registry.renderCustomBlock,\n startTour: (options?: EditorTourStartOptions) =>\n editorTourRef.value?.start(options),\n dismissTour: () => editorTourRef.value?.dismiss(),\n resetTourDismissed: () => clearTourDismissedState(props.config.tour),\n isTourDismissed: () =>\n props.config.tour ? readTourDismissedState(props.config.tour) : true,\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 <HistoryUndoRedo />\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\"\n :style=\"{\n transition: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',\n backgroundColor: popupSettingsWorkspaceBg\n ? 'var(--tpl-bg)'\n : 'var(--tpl-canvas-bg)',\n }\"\n :class=\"[...editorChromeHorizontalInset, 'tpl:top-14']\"\n >\n <template v-if=\"isPopupEditor && popupRailTab === 'displayRules'\">\n <PopupDisplayRulesView />\n </template>\n <template v-else-if=\"isPopupEditor && popupRailTab === 'schedule'\">\n <PopupScheduleView layout=\"standalone\" />\n </template>\n <template v-else>\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 <!-- Popup editor: full-width browser chrome, with steps strip above it -->\n <div\n v-if=\"isPopupEditor\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:gap-3 tpl:px-4 tpl:pt-4 tpl:pb-6\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\n <div\n class=\"tpl-popup-browser-frame tpl:flex tpl:min-h-[calc(100vh-11rem)] tpl:w-full tpl:flex-col tpl:overflow-hidden tpl:rounded-xl tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-canvas-bg)] tpl:shadow-[var(--tpl-shadow-xl)]\"\n >\n <div\n class=\"tpl-popup-browser-bar tpl:relative tpl:flex tpl:h-9 tpl:items-center tpl:gap-1.5 tpl:border-b tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:px-3\"\n aria-hidden=\"true\"\n >\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #ff5f57\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #febc2e\"\n />\n <span\n class=\"tpl:block tpl:size-3 tpl:rounded-full\"\n style=\"background-color: #28c840\"\n />\n <div\n class=\"tpl:pointer-events-none tpl:absolute tpl:left-1/2 tpl:top-1/2 tpl:-translate-x-1/2 tpl:-translate-y-1/2 tpl:rounded-md tpl:bg-[var(--tpl-bg)] tpl:px-3 tpl:py-0.5 tpl:text-[11px] tpl:font-medium tpl:text-[var(--tpl-text-muted)] tpl:tracking-tight\"\n >\n yourstore.com\n </div>\n </div>\n <Canvas\n class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col\"\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </div>\n\n <!-- Email editor: original centered canvas (with internal step strip from CanvasPagesNav) -->\n <div\n v-else\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-center tpl:gap-3 tpl:p-8\"\n >\n <CanvasPagesNav\n :preview-mode=\"editor.state.previewMode\"\n :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n />\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 :multi-page-canvas=\"config.multiPageCanvas ?? false\"\n @select-block=\"editor.selectBlock\"\n />\n </div>\n </template>\n </div>\n\n <EditorFooter\n v-if=\"config.branding !== false\"\n :position-class=\"editorChromeHorizontalInset\"\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 && !popupHidesRightSidebar\"\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 <EditorTour\n v-if=\"config.tour\"\n ref=\"editorTourRef\"\n :tour-config=\"config.tour\"\n :dark-mode=\"editor.state.darkMode\"\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\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 locale.split(\"-\")[0].toLowerCase();\n}\n\nfunction resolveLocale(locale: string, supported: string[]): string {\n const base = getBaseLocale(locale);\n return supported.includes(base) ? base : \"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 base, e.g. en-GB → en). */\nexport function isLocaleSupported(locale: string): boolean {\n return (\n supportedLocales.includes(locale) ||\n supportedLocales.includes(getBaseLocale(locale))\n );\n}\n\n/** Check if a locale has cloud translations (matched by base). */\nexport function isCloudLocaleSupported(locale: string): boolean {\n return (\n supportedCloudLocales.includes(locale) ||\n supportedCloudLocales.includes(getBaseLocale(locale))\n );\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 \"@aswin.dev/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 `@aswin.dev/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(\"@aswin.dev/renderer\");\n try {\n renderer = await import(\"@aswin.dev/renderer\");\n } catch {\n throw new Error(\n \"[Templatical] toMjml() requires the @aswin.dev/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 \"@aswin.dev/types\";\nimport type { MediaRequestContext } from \"@aswin.dev/media-library\";\n\nimport Editor from \"./Editor.vue\";\nimport type { EditorType } from \"./types/editor-type\";\nimport type {\n EditorTourConfig,\n EditorTourStartOptions,\n} from \"./types/editor-tour\";\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 * Multi-step canvas (popup flows): step strip above the canvas; content exposes\n * {@link TemplateContent.canvasPages} and {@link TemplateContent.activeCanvasPageId}.\n */\n multiPageCanvas?: boolean;\n\n /**\n * `'email'` (default): single hover-expand block rail. `'popup'`: adds a left icon rail\n * (Design, Blocks, Tab, …) beside the same block palette when Blocks is selected.\n * The value is provided through `EDITOR_TYPE_KEY`. Case-insensitive strings such as\n * `'POPUP'` are accepted.\n */\n editorType?: EditorType | string;\n\n /**\n * Accessibility linter (`@aswin.dev/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 `@aswin.dev/quality`.\n */\n accessibility?: import(\"@aswin.dev/quality\").A11yOptions;\n\n /**\n * Optional spotlight tour (inside or outside the editor DOM via selectors).\n * Use `startTour` / `isTourDismissed` on the instance; default copy lives under locale `tour.defaults`.\n */\n tour?: EditorTourConfig;\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 /** Requires `tour` in editor config; otherwise no-ops. */\n startTour(options?: EditorTourStartOptions): void;\n dismissTour(): void;\n resetTourDismissed(): void;\n /** `true` when no tour is configured or the user dismissed a persisted tour. */\n isTourDismissed(): boolean;\n /**\n * Undo the last canvas/template change (same stack as toolbar and Cmd/Ctrl+Z).\n * Rich text fields use TipTap’s own undo while focused.\n */\n undo(): void;\n /** Redo (same stack as toolbar and Cmd/Ctrl+Shift+Z). */\n redo(): void;\n /** Whether a canvas/template undo is available. */\n canUndo(): boolean;\n /** Whether redo is available. */\n canRedo(): boolean;\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 * `@aswin.dev/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 `@aswin.dev/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 startTour(options?: EditorTourStartOptions) {\n editorRef.value?.startTour?.(options);\n },\n dismissTour() {\n editorRef.value?.dismissTour?.();\n },\n resetTourDismissed() {\n editorRef.value?.resetTourDismissed?.();\n },\n isTourDismissed() {\n return editorRef.value?.isTourDismissed?.() ?? true;\n },\n undo() {\n editorRef.value?.undo?.();\n },\n redo() {\n editorRef.value?.redo?.();\n },\n canUndo() {\n return editorRef.value?.canUndo?.() ?? false;\n },\n canRedo() {\n return editorRef.value?.canRedo?.() ?? false;\n },\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 startTour(options?: EditorTourStartOptions) {\n cloudEditorRef.value?.startTour?.(options);\n },\n dismissTour() {\n cloudEditorRef.value?.dismissTour?.();\n },\n resetTourDismissed() {\n cloudEditorRef.value?.resetTourDismissed?.();\n },\n isTourDismissed() {\n return cloudEditorRef.value?.isTourDismissed?.() ?? true;\n },\n undo() {\n cloudEditorRef.value?.undo?.();\n },\n redo() {\n cloudEditorRef.value?.redo?.();\n },\n canUndo() {\n return cloudEditorRef.value?.canUndo?.() ?? false;\n },\n canRedo() {\n return cloudEditorRef.value?.canRedo?.() ?? false;\n },\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 \"@aswin.dev/types\";\n\nexport type { UseFontsReturn, FontOption } from \"./composables/useFonts\";\nexport { useFonts } from \"./composables/useFonts\";\nexport type { EditorCapabilities } from \"./types/editor-capabilities\";\nexport type { EditorType } from \"./types/editor-type\";\nexport type {\n EditorTourConfig,\n EditorTourStep,\n EditorTourStepPlacement,\n EditorTourStartOptions,\n} from \"./types/editor-tour\";\nexport { DEFAULT_EDITOR_TOUR_STORAGE_KEY } from \"./types/editor-tour\";\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { $ as e, Q as t, V as n, Z as r, et as i, m as a } from "./vue.runtime.esm-bundler-CjauPXjj.js";
2
- import { L as o, a as s, p as c } from "./dist-DP82Y0rs.js";
2
+ import { a as o, p as s, z as c } from "./dist-Bcqepqv3.js";
3
3
  //#region ../core/dist/cloud/index.js
4
4
  var l = class e {
5
5
  static DEFAULT_BASE_URL = "https://templatical.com";
@@ -75,7 +75,7 @@ var l = class e {
75
75
  };
76
76
  e === "POST" && this.requestOptions.body && (t["Content-Type"] = "application/json", n.body = JSON.stringify(this.requestOptions.body));
77
77
  let r = await fetch(this.url, n);
78
- if (!r.ok) throw new s(`Token refresh failed: ${r.status}`, r.status);
78
+ if (!r.ok) throw new o(`Token refresh failed: ${r.status}`, r.status);
79
79
  let i = await r.json();
80
80
  if (!i.token || !i.expires_at || !i.project_id || !i.tenant) throw Error("Invalid token response: missing token, expires_at, project_id, or tenant");
81
81
  return this.accessToken = i.token, this.expiresAt = /* @__PURE__ */ new Date(i.expires_at * 1e3), this._projectId = i.project_id, this._tenantSlug = i.tenant, i.test_email?.allowed_emails && i.test_email?.signature ? this._testEmailConfig = {
@@ -177,7 +177,7 @@ var d = "/api/v1/projects/{project}/tenants/{tenant}", f = `${d}/templates/{temp
177
177
  });
178
178
  if (!n.ok) {
179
179
  let e = await n.json().catch(() => ({ message: `HTTP error ${n.status}` }));
180
- throw new s(this.extractFirstValidationError(e), n.status);
180
+ throw new o(this.extractFirstValidationError(e), n.status);
181
181
  }
182
182
  if (n.status !== 204) return (await n.json()).data;
183
183
  }
@@ -434,7 +434,7 @@ function x(e, t) {
434
434
  function S(n) {
435
435
  let r = new v(n.authManager), i = t({
436
436
  template: null,
437
- content: o(c(n.defaultFontFamily, n.templateDefaults)),
437
+ content: c(s(n.defaultFontFamily, n.templateDefaults)),
438
438
  selectedBlockId: null,
439
439
  viewport: "desktop",
440
440
  darkMode: !1,
@@ -446,7 +446,7 @@ function S(n) {
446
446
  }), l = a({
447
447
  get: () => i.content,
448
448
  set: (e) => {
449
- i.content = o(e), i.isDirty = !0;
449
+ i.content = c(e), i.isDirty = !0;
450
450
  }
451
451
  }), u = a(() => i.selectedBlockId ? f(i.content.blocks, i.selectedBlockId) : null), d = a(() => {
452
452
  let e = /* @__PURE__ */ new Set(), t = i.template?.content?.blocks;
@@ -483,7 +483,7 @@ function S(n) {
483
483
  return n.lockedBlocks?.value.has(e) ?? !1;
484
484
  }
485
485
  function h(e, t = !0) {
486
- i.content = o(e), t && (i.isDirty = !0);
486
+ i.content = c(e), t && (i.isDirty = !0);
487
487
  }
488
488
  function g(e) {
489
489
  e && m(e) || (i.selectedBlockId = e);
@@ -545,7 +545,7 @@ function S(n) {
545
545
  async function D(e) {
546
546
  i.isLoading = !0;
547
547
  try {
548
- e && (i.content = o(e));
548
+ e && (i.content = c(e));
549
549
  let t = await r.createTemplate(i.content);
550
550
  return i.template = t, i.isDirty = !1, t;
551
551
  } catch (e) {
@@ -558,7 +558,7 @@ function S(n) {
558
558
  i.isLoading = !0;
559
559
  try {
560
560
  let t = await r.getTemplate(e);
561
- return i.template = t, i.content = o(t.content), i.isDirty = !1, t;
561
+ return i.template = t, i.content = c(t.content), i.isDirty = !1, t;
562
562
  } catch (e) {
563
563
  throw n.onError?.(e), e;
564
564
  } finally {
@@ -566,7 +566,7 @@ function S(n) {
566
566
  }
567
567
  }
568
568
  async function k() {
569
- if (!i.template?.id) throw new s("No template loaded. Call create() or load() before saving.");
569
+ if (!i.template?.id) throw new o("No template loaded. Call create() or load() before saving.");
570
570
  i.isSaving = !0;
571
571
  try {
572
572
  let e = await r.updateTemplate(i.template.id, i.content);
@@ -2,7 +2,7 @@
2
2
  var e = {
3
3
  footer: {
4
4
  poweredBy: "Erstellt mit",
5
- openSource: "Open Source"
5
+ help: "Hilfe"
6
6
  },
7
7
  tour: {
8
8
  next: "Weiter",
@@ -314,6 +314,7 @@ var e = {
314
314
  modeAuto: "Rabattcode",
315
315
  modeManual: "Shopify-Rabatt manuell eingeben",
316
316
  autoHint: "Für jedes Abonnement einen einmaligen, nicht wiederverwendbaren Code erzeugen.",
317
+ dynamicModesRequirePriorFormHint: "Der dynamische Code ist erst nach dem Opt-in-Schritt verfügbar.",
317
318
  selectType: "Typ wählen",
318
319
  types: {
319
320
  percentageOff: "Prozent rabatt",
@@ -771,7 +772,26 @@ var e = {
771
772
  triggerOnHoverTitle: "Beim Hover",
772
773
  triggerOnHoverDesc: "Wenn ein Besucher über ein Element fährt.",
773
774
  triggerOnCustomTitle: "Eigenes Ereignis",
774
- triggerOnCustomDesc: "Einen eigenen Trigger verwenden."
775
+ triggerOnCustomDesc: "Einen eigenen Trigger verwenden.",
776
+ frequencyHeading: "Häufigkeit",
777
+ frequencyIntro: "Legen Sie fest, wie oft Besucher diese Kampagne sehen.",
778
+ frequencyShowAgainTitle: "Erneut anzeigen",
779
+ frequencyShowAgainDesc: "Legen Sie fest, wie oft Ihre Kampagne eingeblendet wird.",
780
+ frequencyEveryPageLoad: "Bei jedem Seitenaufruf",
781
+ frequencyEverySession: "Pro Sitzung",
782
+ frequencyEveryPrefix: "Alle",
783
+ frequencyIntervalUnitMinutes: "Minuten",
784
+ frequencyIntervalUnitHours: "Stunden",
785
+ frequencyIntervalUnitDays: "Tage",
786
+ frequencyStopShowingTitle: "Nicht mehr anzeigen",
787
+ frequencyStopShowingDesc: "Legen Sie fest, wann ein Besucher die Kampagne nicht mehr sieht.",
788
+ frequencyStopAfterSignup: "Nachdem sich ein Besucher angemeldet oder geklickt hat",
789
+ frequencyStopAfterSeen: "Nachdem ein Besucher die Kampagne gesehen hat",
790
+ frequencyStopTimesSuffix: "Mal",
791
+ frequencyStopAfterClosedPrefix: "Nachdem ein Besucher die Kampagne geschlossen hat,",
792
+ frequencyClosedNeverAgain: "und nie wieder anzeigen",
793
+ frequencyClosedRespectRules: "aber die Regeln für „Erneut anzeigen“ beachten",
794
+ frequencyAfterClosedHelp: "Was passiert, wenn jemand das Popup ohne Anmeldung oder Klick schließt."
775
795
  },
776
796
  popupDesign: {
777
797
  tabStyle: "Stil",
@@ -854,7 +874,9 @@ var e = {
854
874
  browseModules: "Gespeicherte Module durchsuchen",
855
875
  expandSidebar: "Block-Seitenleiste erweitern",
856
876
  palette: "Blockpalette",
857
- insertBlock: "{block}-Block einfügen"
877
+ insertBlock: "{block}-Block einfügen",
878
+ paletteDiscountCouponTaken: "Es ist nur ein Rabattcoupon für alle Schritte zulässig.",
879
+ paletteFormTaken: "Es ist nur ein Formularblock für alle Schritte zulässig."
858
880
  },
859
881
  landmarks: {
860
882
  canvas: "E-Mail-Leinwand",
@@ -883,5 +905,3 @@ var e = {
883
905
  };
884
906
  //#endregion
885
907
  export { e as default };
886
-
887
- //# sourceMappingURL=de-BsYijc0r.js.map