@boxcc/maildeno-editor 0.0.0-stage → 0.4.13

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 (57) hide show
  1. package/CHANGELOG.md +479 -0
  2. package/README.md +441 -2
  3. package/dist/DatePicker-AdfGW0Tw.js +5347 -0
  4. package/dist/DatePicker-aNFlsoOb.js +5347 -0
  5. package/dist/Dialog-By0JLN8s.js +6294 -0
  6. package/dist/HealthIndicator-4Iw6N-g_.js +1216 -0
  7. package/dist/HealthIndicator-ZohgzF2_.js +1217 -0
  8. package/dist/PopperContent-N2nAEFeH.js +2538 -0
  9. package/dist/PopperContent-eLmz1Gjd.js +2578 -0
  10. package/dist/PreviewCanvas-DzpLghCo.js +534 -0
  11. package/dist/PreviewCanvas-QN4C68KA.js +534 -0
  12. package/dist/RichTextEditor-CmM4XRFC.js +12218 -0
  13. package/dist/RichTextEditor-Cz1-_ZtA.js +12217 -0
  14. package/dist/RowPreviewChildren-BYaMZID3.js +4 -0
  15. package/dist/RowPreviewChildren-_A9gPQCs.js +4 -0
  16. package/dist/RowPreviewLegacyComponent-Df_1HTly.js +403 -0
  17. package/dist/RowPreviewLegacyComponent-NECG3wpn.js +403 -0
  18. package/dist/RowPreviewRow-DPaSbxmB.js +81 -0
  19. package/dist/RowPreviewRow-Dx4ExtSN.js +81 -0
  20. package/dist/SavedRowsPanel-DqSHB6Jk.js +492 -0
  21. package/dist/SavedRowsPanel-DyXjSOEt.js +493 -0
  22. package/dist/_plugin-vue_export-helper-Dj7HTbfw.js +8 -0
  23. package/dist/adapters/errors.d.ts +59 -0
  24. package/dist/adapters/localStorageAdapter.d.ts +6 -0
  25. package/dist/adapters/types.d.ts +151 -0
  26. package/dist/babel-DV7WPQI0.js +10043 -0
  27. package/dist/blocks/registry.d.ts +23 -0
  28. package/dist/blocks/types.d.ts +73 -0
  29. package/dist/borderRadius-CkSa1lQ9.js +75 -0
  30. package/dist/components/features/emailBuilder/EmailEditor.vue.d.ts +182 -0
  31. package/dist/composables/emailBuilder/export/logic/espLogicWrapper.d.ts +76 -0
  32. package/dist/composables/emailBuilder/persistence/useEmailBuilderPersistence.d.ts +38 -0
  33. package/dist/composables/emailBuilder/transform/pipeline/optimize.d.ts +100 -0
  34. package/dist/composables/system/useColorScheme.d.ts +16 -0
  35. package/dist/element-B3gq8Xvd.js +21500 -0
  36. package/dist/element.d.ts +6 -0
  37. package/dist/element.js +5 -0
  38. package/dist/esp/registry.d.ts +33 -0
  39. package/dist/estree-DY9POgc0.js +8195 -0
  40. package/dist/html-wb0gSX1o.js +7273 -0
  41. package/dist/index.d.ts +17 -0
  42. package/dist/index.js +14992 -0
  43. package/dist/init.d.ts +178 -0
  44. package/dist/init.js +137 -0
  45. package/dist/maildeno-editor.css +2 -0
  46. package/dist/merge-tags/registry.d.ts +33 -0
  47. package/dist/mergeTagDefinitions-Cler19nv.js +27 -0
  48. package/dist/standalone-DHvJ30Td.js +3554 -0
  49. package/dist/theme.d.ts +206 -0
  50. package/dist/types/assistant.d.ts +56 -0
  51. package/dist/useEmailBuilder-B19p-2yp.js +4924 -0
  52. package/dist/useEmailBuilder-BR4wfai1.js +4924 -0
  53. package/dist/useToast-DD11IxqP.js +194 -0
  54. package/dist/utils/border.d.ts +15 -0
  55. package/dist/utils/borderRadius.d.ts +10 -0
  56. package/dist/vue.runtime.esm-bundler-D2AjRP90.js +6117 -0
  57. package/package.json +100 -4
@@ -0,0 +1,23 @@
1
+ import { BlockDefinition } from './types';
2
+ /**
3
+ * Registers a block type.
4
+ *
5
+ * ```ts
6
+ * registerBlock({
7
+ * name: "product-card",
8
+ * label: "Product Card",
9
+ * icon: "<svg …>",
10
+ * schema: { … },
11
+ * renderCanvas, renderSettings, renderEmail,
12
+ * });
13
+ * ```
14
+ *
15
+ * A two-argument form, `registerBlock(id, definition)`, is also accepted. The
16
+ * definition already carries `name`, so the separate id is redundant — but the
17
+ * built-in blocks are registered that way and external code may be too, so
18
+ * both keep working. When both are given, the explicit id wins.
19
+ */
20
+ export declare function registerBlock(definition: BlockDefinition): void;
21
+ export declare function registerBlock(id: string, definition: BlockDefinition): void;
22
+ export declare function getBlock(id: string): BlockDefinition | undefined;
23
+ export declare function getAllBlocks(): ReadonlyMap<string, BlockDefinition>;
@@ -0,0 +1,73 @@
1
+ import { Component } from 'vue';
2
+ /**
3
+ * Confirmed against the real generators (html.ts/mjml.ts/react-email.ts):
4
+ * each is a switch(type) dispatcher with no default case — an unmapped type
5
+ * renders as an empty string, silently. That's why all three export
6
+ * renderers are required, not optional-with-fallback.
7
+ *
8
+ * Confirmed against CanvasComponent.vue and ContentTab.vue: block dispatch
9
+ * actually happens in five places, not three — canvas rendering and the
10
+ * settings panel are separate v-if chains the original README draft didn't
11
+ * account for. Same "no silent gap" rule applies here for consistency:
12
+ * renderCanvas and renderSettings are required too.
13
+ */
14
+ export interface BlockRenderContext {
15
+ uid: string;
16
+ marginStyle: string;
17
+ paddingStyle: string;
18
+ escapeHtml: (s: string) => string;
19
+ escapeAttr: (s: string) => string;
20
+ safeUrl: (s: string) => string;
21
+ resolveBgCss: (gradient: any, fallback: string) => string;
22
+ resolveBgVml: (gradient: any, fallback: string) => {
23
+ vmlOpen: string;
24
+ vmlClose: string;
25
+ };
26
+ getResponsiveClasses: (desktopHide: boolean, mobileHide: boolean, suffix?: string) => string;
27
+ /** React Email target only — builds JSX style objects instead of CSS
28
+ * strings (confirmed: hand-rolled, not the real react/mjml packages). */
29
+ react: {
30
+ parseCssString: (css: string) => Record<string, string>;
31
+ parseMarginPaddingDiscrete: (margin?: any, padding?: any) => Record<string, string>;
32
+ normalizeFontFamily: (value: string) => string;
33
+ styleObj: (obj: Record<string, any>) => string;
34
+ normalizeInlineStylesToReact: (html: string) => string;
35
+ buildInlineJsx: (component: "Text" | "Heading", className: string, styleRecord: Record<string, any>, children: string, as?: string) => string;
36
+ };
37
+ }
38
+ export interface BlockDefinition {
39
+ name: string;
40
+ /**
41
+ * Shown in the sidebar tooltip and the block's info dialog. Falls back to
42
+ * `name` when omitted.
43
+ */
44
+ label?: string;
45
+ /**
46
+ * Sidebar icon. Either a raw SVG string or a Vue component.
47
+ *
48
+ * A string is the common case — paste the SVG from whatever icon set you
49
+ * use, no import or build step. It is rendered inline so it inherits
50
+ * `currentColor` and the surrounding sizing, which keeps a custom block
51
+ * visually consistent with the built-in ones.
52
+ *
53
+ * Without an icon a registered block still works, but has no sidebar
54
+ * entry — it can only be added programmatically.
55
+ */
56
+ icon?: string | Component;
57
+ /** Drives future tooling (docs, a generic settings fallback for
58
+ * third-party blocks). Built-ins point renderSettings at their existing
59
+ * bespoke panel instead of relying on this to generate one. */
60
+ schema: Record<string, unknown>;
61
+ /** Vue component, receives a single `component` prop — the block's own
62
+ * node from the tree ({ id, componentType, props, ... }). Matches the
63
+ * existing renderer pattern (ImageRenderer, AnchorRenderer, etc). */
64
+ renderCanvas: Component;
65
+ /** Vue component, no props — reads the selected component itself via
66
+ * useEmailBuilder(), same as every existing panel (ButtonPanel, etc). */
67
+ renderSettings: Component;
68
+ renderEmail: {
69
+ html: (props: any, ctx: BlockRenderContext) => string;
70
+ mjml: (props: any, ctx: BlockRenderContext) => string;
71
+ reactEmail: (props: any, ctx: BlockRenderContext) => string;
72
+ };
73
+ }
@@ -0,0 +1,75 @@
1
+ var i = [
2
+ "top",
3
+ "right",
4
+ "bottom",
5
+ "left"
6
+ ];
7
+ function n(t) {
8
+ const s = {
9
+ width: t?.width ?? 0,
10
+ color: t?.color ?? "transparent",
11
+ style: t?.style ?? "solid"
12
+ };
13
+ return Object.fromEntries(i.map((o) => [o, {
14
+ ...s,
15
+ ...t?.sides?.[o]
16
+ }]));
17
+ }
18
+ function f(t) {
19
+ const { sides: s, ...o } = t;
20
+ return s ? {
21
+ ...o,
22
+ ...n(t).top
23
+ } : {
24
+ ...o,
25
+ sides: n(t)
26
+ };
27
+ }
28
+ function m(t) {
29
+ return Object.values(n(t)).some((s) => s.width > 0 && s.style !== "none");
30
+ }
31
+ function p(t) {
32
+ const s = n(t);
33
+ if (!t?.sides) {
34
+ const { width: o, style: r, color: e } = s.top;
35
+ return { border: `${o}px ${r} ${e}` };
36
+ }
37
+ return Object.fromEntries(i.map((o) => {
38
+ const { width: r, style: e, color: c } = s[o];
39
+ return [`border${o[0].toUpperCase()}${o.slice(1)}`, `${r}px ${e} ${c}`];
40
+ }));
41
+ }
42
+ function u(t, s = !1) {
43
+ return Object.entries(p(t)).map(([o, r]) => `${o.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`)}:${r}${s ? " !important" : ""}`).join(";");
44
+ }
45
+ function a(t = 0) {
46
+ return typeof t == "number" ? {
47
+ topLeft: t,
48
+ topRight: t,
49
+ bottomRight: t,
50
+ bottomLeft: t
51
+ } : { ...t };
52
+ }
53
+ function b(t = 0) {
54
+ return Object.values(a(t)).some((s) => s > 0);
55
+ }
56
+ function d(t = 0, s = 0) {
57
+ const o = (r) => `${r > 0 ? r + s : 0}px`;
58
+ return typeof t == "number" ? o(t) : [
59
+ t.topLeft,
60
+ t.topRight,
61
+ t.bottomRight,
62
+ t.bottomLeft
63
+ ].map(o).join(" ");
64
+ }
65
+ export {
66
+ n as a,
67
+ m as c,
68
+ i,
69
+ f as l,
70
+ a as n,
71
+ p as o,
72
+ d as r,
73
+ u as s,
74
+ b as t
75
+ };
@@ -0,0 +1,182 @@
1
+ import { ColorMode } from '../../../composables/system/useColorScheme';
2
+ import { PartialStorageAdapter } from '../../../adapters/types';
3
+ import { ThemeOptions } from '../../../theme';
4
+ import { AssistantMount, EditorWriteApi } from '../../../types/assistant';
5
+ type __VLS_Props = {
6
+ isReady?: boolean;
7
+ storageAdapter?: PartialStorageAdapter;
8
+ /** If given, loaded via the adapter on mount. Omit to start blank
9
+ * (create mode) — matches the original app's /create vs /edit split,
10
+ * now driven by a prop instead of a route. */
11
+ templateId?: string;
12
+ /** Applied via CSS custom properties (see theme.ts) — works whether this
13
+ * component is used directly or wrapped by element.ts's
14
+ * defineCustomElement, because both write to the same place: this
15
+ * component's own root element, which is where the package's bundled
16
+ * defaults land too once the library build has scoped them. Reactive:
17
+ * assigning a new object re-themes live, no remount. */
18
+ theme?: ThemeOptions;
19
+ /**
20
+ * Which half of `theme` applies.
21
+ *
22
+ * `auto` (the default) follows the host page: useColorScheme.ts watches
23
+ * for a `dark` class on <html> or <body> and mirrors it onto the editor
24
+ * root. A host that already has a Tailwind-style theme switcher needs no
25
+ * wiring at all — flipping the class moves the editor in the same frame.
26
+ *
27
+ * Pass `light` or `dark` to pin the editor regardless of the page: a
28
+ * light editor deliberately embedded in a dark shell, or a host whose
29
+ * switcher signals something other than a class.
30
+ */
31
+ colorMode?: ColorMode;
32
+ /** If provided, the "Send test" button appears in the header. Editor
33
+ * owns the form/validation UI; this callback owns what actually
34
+ * happens on submit (SMTP, an API, anything) — same host-owns-the-
35
+ * operation pattern as the storage adapter. */
36
+ /**
37
+ * Called after a successful save. Its presence is what reveals the Save
38
+ * button — same idiom as onSendTestEmail below.
39
+ *
40
+ * Gating on an explicit handler rather than on the storage adapter,
41
+ * because the adapter can't answer the question: PartialStorageAdapter
42
+ * is merged with the localStorage defaults, so `saveTemplate` always
43
+ * exists by the time anything reads it. A host that wants a read-only or
44
+ * guest editor simply omits this.
45
+ *
46
+ * The `save` event still fires alongside, so `@save` keeps working — and
47
+ * because Vue compiles `@save="fn"` to an `onSave` prop, writing the
48
+ * listener is itself enough to reveal the button.
49
+ */
50
+ onSave?: (payload: {
51
+ templateId: string | null;
52
+ }) => void;
53
+ /**
54
+ * The same handler, under a name that doesn't begin with "on".
55
+ *
56
+ * For the custom-element path only. Vue's defineCustomElement treats
57
+ * `on*` keys as event listeners rather than props, so `el.onSave = fn`
58
+ * never reaches this component — the button appeared and the callback
59
+ * never fired. init() sets this instead. Vue hosts should use `@save`.
60
+ */
61
+ saveHandler?: (payload: {
62
+ templateId: string | null;
63
+ }) => void;
64
+ onSendTestEmail?: (payload: {
65
+ to: string;
66
+ subject: string;
67
+ html: string;
68
+ }) => Promise<void> | void;
69
+ /** Explicit UI restrictions, layered on top of the implicit ones
70
+ * (e.g. onSendTestEmail's mere presence/absence already gates that
71
+ * button — capabilities doesn't replace that, it adds to it).
72
+ * Omit any key to leave that area fully enabled — this restricts,
73
+ * it never grants something that wasn't already possible. */
74
+ capabilities?: {
75
+ /** Which formats appear in the Export dropdown. Omit for all four. */
76
+ export?: Array<"html" | "mjml" | "react" | "json">;
77
+ /** Which saved-row controls this user gets: the canvas row's bookmark
78
+ * button, and the panel's per-row rename and delete. Omit a key for
79
+ * "allowed", the same as omitting the object. For a host with roles,
80
+ * this is what stops a viewer being shown three buttons that can only
81
+ * 403 — the toast their adapter raises afterwards is the backstop, not
82
+ * the design. Does not touch the Shared tab, which is read-only for
83
+ * everyone because no adapter method backs writing to it. */
84
+ savedRows?: {
85
+ create?: boolean;
86
+ rename?: boolean;
87
+ delete?: boolean;
88
+ };
89
+ };
90
+ /** Name shown in the loading overlay and in the desktop-only notice's
91
+ * "Powered by" line.
92
+ *
93
+ * Deliberately a prop rather than a theme token: `theme` carries
94
+ * colours, and this is text, so it has no sensible home in the token
95
+ * map. It is also the one piece of chrome a host cannot restyle away,
96
+ * since the notice replaces the entire editor on small screens.
97
+ *
98
+ * No default is declared here on purpose. Both consumers resolve an
99
+ * undefined value from DEFAULT_BRAND_NAME in brand.ts, so there is one
100
+ * place to change it rather than three that can drift — and declaring a
101
+ * default here would also make the empty-string case unreachable, since
102
+ * the prop would never arrive undefined. Omit for the package default;
103
+ * pass an empty string to render no wordmark and drop the attribution
104
+ * line entirely. */
105
+ brandName?: string;
106
+ /** Replaces the saved-templates panel (and its header button) with
107
+ * version history.
108
+ *
109
+ * A swap rather than an addition, because the two answer different
110
+ * questions — "which template?" versus "which state of this template?"
111
+ * — and a host that has its own template browser elsewhere in its app
112
+ * has no use for the former inside the editor. Two panels competing for
113
+ * the same slot would also mean two header buttons and a layout
114
+ * decision the editor shouldn't be making.
115
+ *
116
+ * Top-level rather than a `capabilities` key on purpose: capabilities
117
+ * documents itself as only ever restricting, never granting, and this
118
+ * grants.
119
+ *
120
+ * The panel still degrades per-method — see the version methods on
121
+ * EditorStorageAdapter. Setting this with an adapter that can't list
122
+ * versions gets an honest "not supported" message, not a broken panel. */
123
+ versions?: boolean;
124
+ /** Fills the assistant drawer from a non-Vue host (init(), React,
125
+ * vanilla). Vue hosts use the #assistant slot instead; either one being
126
+ * present reveals the trigger button, and neither means no assistant UI
127
+ * renders at all.
128
+ *
129
+ * The editor supplies the drawer, its trigger, open/close state, escape
130
+ * handling, focus return and shadow-DOM-safe teleporting. What goes
131
+ * inside is entirely the host's — see the AssistantPanel comment for
132
+ * why the assistant itself isn't in this package. */
133
+ assistant?: AssistantMount;
134
+ };
135
+ declare var __VLS_17: {
136
+ templateId: string | null;
137
+ isSaving: boolean;
138
+ saveStatus: import('../../..').AutoSaveStatus;
139
+ }, __VLS_80: {
140
+ editor: EditorWriteApi;
141
+ };
142
+ type __VLS_Slots = {} & {
143
+ 'header-actions'?: (props: typeof __VLS_17) => any;
144
+ } & {
145
+ assistant?: (props: typeof __VLS_80) => any;
146
+ };
147
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
148
+ getHtml: (mode?: "prune" | "wrap") => string | null;
149
+ getMjml: (mode?: "prune" | "wrap") => string | null;
150
+ getReactEmail: (mode?: "prune" | "wrap") => string | null;
151
+ getJson: () => {
152
+ template_id: string;
153
+ template_name: string;
154
+ canvas: Partial<import('../../..').Canvas>;
155
+ rows: Partial<import('../../..').NestedRow | import('../../..').RowSpacer>[];
156
+ schema_version: string;
157
+ } | null;
158
+ setJson: (data: any, opts?: {
159
+ history?: "undoable" | "reset";
160
+ }) => void;
161
+ getSelection: () => {
162
+ id: string;
163
+ type: string | null;
164
+ } | null;
165
+ setSelection: (id: string | null) => boolean;
166
+ onChange: (cb: () => void) => (() => void);
167
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
168
+ ready: () => any;
169
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
170
+ onReady?: (() => any) | undefined;
171
+ }>, {
172
+ isReady: boolean;
173
+ colorMode: ColorMode;
174
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
175
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
176
+ declare const _default: typeof __VLS_export;
177
+ export default _default;
178
+ type __VLS_WithSlots<T, S> = T & {
179
+ new (): {
180
+ $slots: S;
181
+ };
182
+ };
@@ -0,0 +1,76 @@
1
+ export type KnownESPSyntax = "handlebars" | "sendgrid" | "mailchimp" | "klaviyo" | "liquid" | "braze" | "sfmc" | "hubspot" | "iterable" | "activecampaign" | "marketo" | "campaign_monitor" | "pardot" | "mso";
2
+ export type ESPSyntax = KnownESPSyntax | (string & {});
3
+ export interface ESPSyntaxMeta {
4
+ label: string;
5
+ group: "handlebars" | "liquid" | "ampscript" | "custom" | "mso";
6
+ description: string;
7
+ docsUrl?: string;
8
+ /**
9
+ * Whether this ESP supports boolean nesting / parenthesised sub-expressions.
10
+ * When false, buildFullExpression flattens all rules to a single level and
11
+ * the UI should warn the user that nested groups are not supported.
12
+ */
13
+ supportsNesting: boolean;
14
+ }
15
+ export declare const ESP_SYNTAX_META: Record<KnownESPSyntax, ESPSyntaxMeta>;
16
+ export type SupportLevel = "native" | "helper" | "fallback" | "none";
17
+ export interface OperatorSupportRow {
18
+ syntax: ESPSyntax;
19
+ label: string;
20
+ eq: SupportLevel;
21
+ neq: SupportLevel;
22
+ contains: SupportLevel;
23
+ not_contains: SupportLevel;
24
+ starts_with: SupportLevel;
25
+ ends_with: SupportLevel;
26
+ in: SupportLevel;
27
+ not_in: SupportLevel;
28
+ numeric: SupportLevel;
29
+ is_empty: SupportLevel;
30
+ /** date_before / date_after / date_on */
31
+ date: SupportLevel;
32
+ }
33
+ export declare const OPERATOR_SUPPORT_MATRIX: OperatorSupportRow[];
34
+ export interface VisibilityRuleForESP {
35
+ tag: string;
36
+ operator: string;
37
+ value: string;
38
+ }
39
+ export interface VisibilityGroupForESP {
40
+ match: "all" | "any";
41
+ rules: VisibilityRuleForESP[];
42
+ }
43
+ export interface VisibilityConfigForESP {
44
+ enabled: boolean;
45
+ match: "all" | "any";
46
+ rules: VisibilityRuleForESP[];
47
+ groups?: VisibilityGroupForESP[];
48
+ }
49
+ /**
50
+ * Wraps `content` in ESP conditional tags derived from `config`.
51
+ *
52
+ * Returns `content` unchanged when:
53
+ * • config is undefined / null
54
+ * • config.enabled is false
55
+ * • config has no rules and no groups (or all produce null expressions)
56
+ *
57
+ * Mailchimp: emits nested *|IF|* blocks instead of inline AND/OR because
58
+ * Mailchimp's parser does not reliably support complex boolean expressions.
59
+ *
60
+ * Liquid ESPs (Klaviyo, Braze, HubSpot, generic Liquid): for date_before /
61
+ * date_after rules, emits {% assign %} prelude lines before the {% if %} tag
62
+ * so that the unix-timestamp filter is captured in a variable rather than
63
+ * evaluated inline (which most Liquid engines do not support).
64
+ */
65
+ export declare const wrapWithESPLogic: (content: string, config: VisibilityConfigForESP | undefined | null, syntax?: ESPSyntax) => string;
66
+ /**
67
+ * Preview the generated open/close tags without actual content.
68
+ * Useful for rendering a live preview in the VisibilityWrapESP component.
69
+ *
70
+ * For Mailchimp the openTag preview shows the first *|IF:…|* expression.
71
+ * For Liquid date rules the openTag includes any {% assign %} preludes.
72
+ */
73
+ export declare const previewESPTags: (config: VisibilityConfigForESP | undefined | null, syntax?: ESPSyntax) => {
74
+ openTag: string;
75
+ closeTag: string;
76
+ } | null;
@@ -0,0 +1,38 @@
1
+ export type AutoSaveStatus = "idle" | "saving" | "saved" | "error";
2
+ export interface AutoSavePayload {
3
+ schemaVersion: string;
4
+ /** Which template this draft belongs to. Null for brand-new unsaved documents. */
5
+ templateId: string | null;
6
+ expiresAt: number;
7
+ touchedAt: number;
8
+ updatedAt: string;
9
+ /** Template display name — cached so the page title can be set without a DB fetch. */
10
+ name?: string;
11
+ /** Template tags — cached alongside name to fully avoid a DB fetch on draft restore. */
12
+ tags?: any[];
13
+ content: {
14
+ rows: any[];
15
+ canvas: Record<string, any>;
16
+ };
17
+ }
18
+ export interface RestoredDraft {
19
+ rows: any[];
20
+ canvas: Record<string, any>;
21
+ updatedAt: string;
22
+ /** Reflects the templateId stored in the payload — use this to verify the
23
+ * draft belongs to the currently open template before restoring it. */
24
+ templateId: string | null;
25
+ /** Cached template display name — set when the template was first loaded from DB. */
26
+ name?: string;
27
+ /** Cached template tags — set when the template was first loaded from DB. */
28
+ tags?: any[];
29
+ }
30
+ export declare const useEmailBuilderPersistence: () => {
31
+ saveToLocal: (rows: any[], canvas: Record<string, any>, templateId?: string | null, name?: string, tags?: any[]) => void;
32
+ flushSave: (rows: any[], canvas: Record<string, any>, templateId?: string | null, name?: string, tags?: any[]) => void;
33
+ loadFromLocal: () => RestoredDraft | null;
34
+ clearLocal: () => void;
35
+ hasDraft: (templateId?: string) => boolean;
36
+ saveStatus: import('vue').Ref<AutoSaveStatus, AutoSaveStatus>;
37
+ lastSaved: import('vue').Ref<Date | null, Date | null>;
38
+ };
@@ -0,0 +1,100 @@
1
+ import { BorderValue } from '../../../../utils/border';
2
+ import { BorderRadius } from '../../../../utils/borderRadius';
3
+ /**
4
+ * optimize.ts
5
+ * Strips default/noise values from rows, columns, components, and canvas before
6
+ * JSON export. Pairs with hydrate.ts — everything stripped here is restored on import.
7
+ *
8
+ * Supports both new `children` shape and legacy `components` shape per column.
9
+ */
10
+ export interface VisibilityConfig {
11
+ enabled: boolean;
12
+ match: "all" | "any";
13
+ rules: unknown[];
14
+ }
15
+ export interface BorderConfig extends BorderValue {
16
+ radius?: BorderRadius;
17
+ }
18
+ export interface GradientConfig {
19
+ useGradient: boolean;
20
+ solid: string;
21
+ gradient: {
22
+ type: "linear" | "radial";
23
+ direction: string;
24
+ colors: Array<{
25
+ color: string;
26
+ position: number;
27
+ }>;
28
+ };
29
+ }
30
+ interface Component {
31
+ id: string;
32
+ /** New discriminated shape uses "component"; legacy uses the componentType directly */
33
+ type: string;
34
+ /** Present when type === "component" */
35
+ componentType?: string;
36
+ props: Record<string, unknown>;
37
+ }
38
+ type CanvasChild = Component | NestedRow | RowSpacer;
39
+ export interface Column {
40
+ id: string;
41
+ width: number;
42
+ /** New shape */
43
+ children?: CanvasChild[];
44
+ /** Legacy shape — kept for backward compat */
45
+ components?: Component[];
46
+ backgroundColor?: string;
47
+ backgroundGradient?: GradientConfig;
48
+ backgroundImage?: string;
49
+ backgroundSize?: string;
50
+ backgroundPosition?: string;
51
+ backgroundRepeat?: string;
52
+ padding?: Record<string, number>;
53
+ border?: BorderConfig;
54
+ verticalAlign?: string;
55
+ [key: string]: unknown;
56
+ }
57
+ export interface NestedRow {
58
+ id: string;
59
+ type: "row";
60
+ name?: string;
61
+ columns?: Column[];
62
+ visibility?: VisibilityConfig;
63
+ border?: BorderConfig;
64
+ backgroundImage?: string;
65
+ backgroundSize?: string;
66
+ backgroundPosition?: string;
67
+ backgroundRepeat?: string;
68
+ padding?: Record<string, number>;
69
+ backgroundGradient?: GradientConfig;
70
+ minHeight?: number;
71
+ [key: string]: unknown;
72
+ }
73
+ export interface RowSpacer {
74
+ id: string;
75
+ type: "row-spacer";
76
+ name?: string;
77
+ height?: number;
78
+ backgroundColor?: string;
79
+ backgroundGradient?: GradientConfig;
80
+ visibility?: VisibilityConfig;
81
+ [key: string]: unknown;
82
+ }
83
+ type Row = NestedRow | RowSpacer;
84
+ export interface Canvas {
85
+ language?: string;
86
+ preheaderText?: string;
87
+ bodyBackgroundColor?: string;
88
+ bodyBackgroundImage?: string;
89
+ bodyBackgroundSize?: string;
90
+ bodyBackgroundPosition?: string;
91
+ bodyBackgroundRepeat?: string;
92
+ backgroundColor?: string;
93
+ width?: number;
94
+ padding?: Record<string, number>;
95
+ mobileBreakpoint?: number;
96
+ [key: string]: unknown;
97
+ }
98
+ export declare const optimizeCanvas: (canvas: Canvas) => Partial<Canvas>;
99
+ export declare const optimizeRows: (rows: Row[]) => Partial<Row>[];
100
+ export {};
@@ -0,0 +1,16 @@
1
+ import { Ref } from 'vue';
2
+ /**
3
+ * `auto` follows the host page. `light` and `dark` pin the editor regardless
4
+ * of what the page is doing — useful when the editor is embedded in a shell
5
+ * whose theme it should not inherit.
6
+ */
7
+ export type ColorMode = "light" | "dark" | "auto";
8
+ /**
9
+ * @param mode The `colorMode` prop, as a ref.
10
+ * @param host The custom-element host when running under element.ts, else
11
+ * null. Reflected onto as well, so `:host(.dark)` matches in an
12
+ * unscoped shadow build.
13
+ */
14
+ export declare function useColorScheme(mode: Ref<ColorMode | undefined>, host?: HTMLElement | null): {
15
+ isDark: import('vue').ComputedRef<boolean>;
16
+ };