create-email-template 0.1.1

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 (75) hide show
  1. package/README.md +356 -0
  2. package/dist/components/builder/block-palette.d.ts +4 -0
  3. package/dist/components/builder/block-palette.d.ts.map +1 -0
  4. package/dist/components/builder/canvas.d.ts +4 -0
  5. package/dist/components/builder/canvas.d.ts.map +1 -0
  6. package/dist/components/builder/editable-block-renderer.d.ts +7 -0
  7. package/dist/components/builder/editable-block-renderer.d.ts.map +1 -0
  8. package/dist/components/builder/email-builder.d.ts +5 -0
  9. package/dist/components/builder/email-builder.d.ts.map +1 -0
  10. package/dist/components/builder/inline-text-editor.d.ts +15 -0
  11. package/dist/components/builder/inline-text-editor.d.ts.map +1 -0
  12. package/dist/components/builder/properties-panel.d.ts +8 -0
  13. package/dist/components/builder/properties-panel.d.ts.map +1 -0
  14. package/dist/components/builder/selection-toolbar.d.ts +12 -0
  15. package/dist/components/builder/selection-toolbar.d.ts.map +1 -0
  16. package/dist/components/builder/sortable-item.d.ts +10 -0
  17. package/dist/components/builder/sortable-item.d.ts.map +1 -0
  18. package/dist/components/builder/variables-info-dialog.d.ts +7 -0
  19. package/dist/components/builder/variables-info-dialog.d.ts.map +1 -0
  20. package/dist/components/ui/button.d.ts +10 -0
  21. package/dist/components/ui/button.d.ts.map +1 -0
  22. package/dist/components/ui/dialog.d.ts +18 -0
  23. package/dist/components/ui/dialog.d.ts.map +1 -0
  24. package/dist/components/ui/input.d.ts +4 -0
  25. package/dist/components/ui/input.d.ts.map +1 -0
  26. package/dist/components/ui/label.d.ts +4 -0
  27. package/dist/components/ui/label.d.ts.map +1 -0
  28. package/dist/components/ui/sheet.d.ts +15 -0
  29. package/dist/components/ui/sheet.d.ts.map +1 -0
  30. package/dist/config/defaults.d.ts +3 -0
  31. package/dist/config/defaults.d.ts.map +1 -0
  32. package/dist/config/types.d.ts +125 -0
  33. package/dist/config/types.d.ts.map +1 -0
  34. package/dist/core/blocks.d.ts +33 -0
  35. package/dist/core/blocks.d.ts.map +1 -0
  36. package/dist/core/default-blocks.d.ts +2 -0
  37. package/dist/core/default-blocks.d.ts.map +1 -0
  38. package/dist/core/id.d.ts +2 -0
  39. package/dist/core/id.d.ts.map +1 -0
  40. package/dist/core/normalize.d.ts +2 -0
  41. package/dist/core/normalize.d.ts.map +1 -0
  42. package/dist/core/render.d.ts +10 -0
  43. package/dist/core/render.d.ts.map +1 -0
  44. package/dist/core/richtext.d.ts +2 -0
  45. package/dist/core/richtext.d.ts.map +1 -0
  46. package/dist/core/template.d.ts +2 -0
  47. package/dist/core/template.d.ts.map +1 -0
  48. package/dist/core/types.d.ts +2 -0
  49. package/dist/core/types.d.ts.map +1 -0
  50. package/dist/core/variables.d.ts +2 -0
  51. package/dist/core/variables.d.ts.map +1 -0
  52. package/dist/hooks/use-autosave.d.ts +14 -0
  53. package/dist/hooks/use-autosave.d.ts.map +1 -0
  54. package/dist/hooks/use-email-builder.d.ts +36 -0
  55. package/dist/hooks/use-email-builder.d.ts.map +1 -0
  56. package/dist/hooks/use-render-email.d.ts +15 -0
  57. package/dist/hooks/use-render-email.d.ts.map +1 -0
  58. package/dist/index.d.ts +25 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +27976 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/lib/utils.d.ts +3 -0
  63. package/dist/lib/utils.d.ts.map +1 -0
  64. package/dist/shims/use-sync-external-store.d.ts +4 -0
  65. package/dist/shims/use-sync-external-store.d.ts.map +1 -0
  66. package/dist/store/autosave-context.d.ts +13 -0
  67. package/dist/store/autosave-context.d.ts.map +1 -0
  68. package/dist/store/create-email-builder-store.d.ts +42 -0
  69. package/dist/store/create-email-builder-store.d.ts.map +1 -0
  70. package/dist/store/email-builder-provider.d.ts +14 -0
  71. package/dist/store/email-builder-provider.d.ts.map +1 -0
  72. package/dist/store/vanilla-store.d.ts +8 -0
  73. package/dist/store/vanilla-store.d.ts.map +1 -0
  74. package/dist/style.css +2 -0
  75. package/package.json +70 -0
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # create-email-template
2
+
3
+ Editor visual de plantillas de email por bloques para React: palette, canvas con drag & drop, edición de texto inline enriquecida, panel de propiedades, autoguardado, deshacer y preview en móvil/escritorio.
4
+
5
+ El JSON que produce se renderiza a HTML email-safe con su compañero [`create-email-renderer`](https://www.npmjs.com/package/create-email-renderer) (**sin React**, pensado para backend/edge). Ambas librerías comparten el mismo core, por lo que el preview del editor y el HTML final nunca divergen.
6
+
7
+ ## Instalación
8
+
9
+ ```sh
10
+ npm install create-email-template
11
+ ```
12
+
13
+ Requiere **React `^18 || ^19`** como peer dependency (usa `useSyncExternalStore`, nativo desde React 18).
14
+
15
+ ## Uso mínimo
16
+
17
+ ```tsx
18
+ import { EmailBuilder, EmailBuilderProvider } from "create-email-template";
19
+ import "create-email-template/style.css"; // ¡necesario! Estilos autocontenidos
20
+
21
+ export function EditorPage() {
22
+ return (
23
+ <EmailBuilderProvider>
24
+ <EmailBuilder />
25
+ </EmailBuilderProvider>
26
+ );
27
+ }
28
+ ```
29
+
30
+ `<EmailBuilder />` renderiza el editor completo (palette + canvas + panel de propiedades). Todo lo demás es configuración opcional.
31
+
32
+ > 📥 **¿Ya creaste una plantilla y quieres volver a editarla?** Ve a [Editar una plantilla existente](#editar-una-plantilla-existente-cargar-del-backend) — se carga con `hydrate()` y el autoguardado sigue guardando sobre esa misma plantilla.
33
+
34
+ ---
35
+
36
+ ## `<EmailBuilderProvider>` — configuración completa
37
+
38
+ ```tsx
39
+ <EmailBuilderProvider
40
+ config={{ /* opciones del builder (todas opcionales) */ }}
41
+ uploadImage={fnUpload} // opcional: subida de imágenes
42
+ autosave={{ /* opcional: autoguardado */ }}
43
+ >
44
+ ```
45
+
46
+ ### `config`
47
+
48
+ | Opción | Tipo | Default | Qué hace |
49
+ |---|---|---|---|
50
+ | `blockDefaults` | `Partial<Record<Bloque, Partial<Props>>>` | — | Valores iniciales por tipo de bloque. Se aplican **al arrastrar** el bloque al canvas. |
51
+ | `blockLibrary` | `BlockDefinition[]` | `DEFAULT_BLOCK_LIBRARY` (12 bloques) | Qué bloques aparecen en la palette y con qué defaultProps. |
52
+ | `palette` | `Partial<EmailPalette>` | `DEFAULT_PALETTE` | Colores base del tema: `INK` (texto), `DARK` (fondos oscuros), `GOLD` (acentos), `CREAM_DIM` (texto secundario). |
53
+ | `defaultSettings` | `Partial<EmailSettings>` | `DEFAULT_SETTINGS` | Fondo de página, grosor y redondeo del borde de la tarjeta. |
54
+ | `variables` | `EmailVariable[]` | `DEFAULT_VARIABLES` | Etiquetas `{key}` disponibles para el usuario. |
55
+ | `variableSections` | `EmailVariableSection[]` | agrupación única | Agrupa las variables por categoría en el dialog "Etiquetas disponibles". |
56
+ | `sampleContext` | `EmailContext` | `SAMPLE_CONTEXT` | Datos de ejemplo: al previsualizar, `{firstName}` se ve como el valor de aquí. |
57
+ | `labels` | `Partial<EmailBuilderLabels>` | `DEFAULT_LABELS` (español) | Textos de la UI del editor (i18n). |
58
+ | `historyLimit` | `number` | `50` | Profundidad máxima del historial de deshacer. |
59
+
60
+ #### Ejemplo real de `config`
61
+
62
+ ```tsx
63
+ <EmailBuilderProvider
64
+ config={{
65
+ // Al arrastrar un header, nace con la marca y el logo ya puestos
66
+ blockDefaults: {
67
+ header: { brandName: "Mi Marca", logoUrl: "https://mi-cdn.com/logo.png" },
68
+ text: { text: "Hola {firstName}, gracias por ser parte de nuestra comunidad." },
69
+ button: { label: "Quiero mi cupo", href: "{registerUrl}", backgroundColor: "#d7b227" },
70
+ footer: { brandName: "Mi Marca" },
71
+ },
72
+
73
+ // Personaliza el tema completo
74
+ palette: { INK: "#221d15", DARK: "#0d0b08", GOLD: "#d7b227", CREAM_DIM: "#b8ae9d" },
75
+ defaultSettings: { pageBackground: "#f5f1e8", cardBorderWidth: 1, cardBorderRadius: 4 },
76
+
77
+ // Contexto de ejemplo para el preview de variables
78
+ sampleContext: {
79
+ firstName: "Danny",
80
+ companyName: "Mi Empresa",
81
+ registerUrl: "https://mi-sitio.com/registro",
82
+ unsubscribeUrl: "https://mi-sitio.com/baja",
83
+ },
84
+
85
+ // Solo estas variables aparecen en el editor (si lo omites: todas las defaults)
86
+ variables: [
87
+ { key: "firstName", label: "Nombre" },
88
+ { key: "email", label: "Correo electrónico" },
89
+ { key: "registerUrl", label: "URL de registro" },
90
+ ],
91
+
92
+ // Más undo (cada snapshot es del estado completo de bloques)
93
+ historyLimit: 100,
94
+ }}
95
+ >
96
+ ```
97
+
98
+ #### Bloques disponibles y sus props
99
+
100
+ Todos los bloques aceptan `align` ("left" | "center" | "right"), `backgroundColor`, `paddingY` y `paddingX`.
101
+
102
+ | Tipo | Props específicas |
103
+ |---|---|
104
+ | `header` | `brandName`, `tagline?`, `logoUrl?` |
105
+ | `hero` | `title`, `subtitle?`, `imageUrl?` |
106
+ | `heading` | `text`, `size?`, `color?` |
107
+ | `text` | `text`, `size?`, `color?` |
108
+ | `list` | `items: string[]`, `icon?` ("✓", "•", "→"…) |
109
+ | `button` | `label`, `href` (acepta variables), `backgroundColor?`, `color?`, `blockBackgroundColor?` |
110
+ | `image` | `src`, `alt?`, `width?`, `href?` (si hay `href`, la imagen es clicable) |
111
+ | `quote` | `text`, `author?`, `borderColor?`, `borderWidth?`, `marginLeft?` |
112
+ | `columns` | `columns: { id: string; text: string }[]` |
113
+ | `divider` | `color?`, `height?` |
114
+ | `spacer` | `height?`, `backgroundColor?` |
115
+ | `footer` | `text?`, `brandName?`, `backgroundColor?` |
116
+
117
+ ### `uploadImage`
118
+
119
+ Se invoca cuando el usuario sube una imagen desde el panel de propiedades. Recibe el `File` y debe devolver la **URL pública** donde quedó alojada (S3, R2, tu API…):
120
+
121
+ ```tsx
122
+ uploadImage={async (file: File) => {
123
+ const form = new FormData();
124
+ form.append("file", file);
125
+ const res = await fetch("/api/uploads", { method: "POST", body: form });
126
+ const { url } = await res.json();
127
+ return url; // esta URL queda en la prop `src` del bloque image
128
+ }}
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Autoguardado (`autosave`) y guardado manual
134
+
135
+ Opt-in: sin la prop, no hay guardado automático. La librería **no hace network** — `onSave` es tu fetch.
136
+
137
+ ```tsx
138
+ import type { AutosavePayload } from "create-email-template";
139
+
140
+ async function onSave(payload: AutosavePayload) {
141
+ const res = await fetch(`/api/templates/${templateId}`, {
142
+ method: "PUT",
143
+ headers: { "Content-Type": "application/json" },
144
+ body: JSON.stringify(payload), // { content, settings } → tu BD
145
+ });
146
+ if (!res.ok) throw new Error("Error al guardar");
147
+ return res.json(); // lo que devuelvas llega a `lastResult` (ej. { json, html })
148
+ }
149
+
150
+ <EmailBuilderProvider
151
+ autosave={{
152
+ onSave, // obligatorio
153
+ intervalMs: 10_000, // default 10s
154
+ enabled: true, // interruptor sin desmontar el provider
155
+ onSaved: (r) => { // opcional: feedback por intento
156
+ if (!r.ok) toast.error("No se pudo guardar");
157
+ },
158
+ }}
159
+ >
160
+ ```
161
+
162
+ Cómo funciona el ciclo: cada `intervalMs`, si hay cambios (`dirty`) y no hay un guardado en vuelo, llama a `onSave` y limpia `dirty`. Si el usuario edita durante el guardado, el próximo tick toma los cambios — no se pierde nada.
163
+
164
+ ### `useAutosaveStatus()` — estado y botón de guardado manual
165
+
166
+ ```tsx
167
+ import { useAutosaveStatus } from "create-email-template";
168
+
169
+ function Toolbar() {
170
+ const { isSaving, lastSavedAt, lastError, lastResult, saveNow } = useAutosaveStatus();
171
+
172
+ return (
173
+ <>
174
+ {/* saveNow() dispara el MISMO onSave, inmediatamente → botón "Guardar" manual */}
175
+ <button onClick={() => saveNow()} disabled={isSaving}>
176
+ {isSaving ? "Guardando…" : "Guardar"}
177
+ </button>
178
+
179
+ {lastSavedAt && <span>Guardado a las {lastSavedAt.toLocaleTimeString()}</span>}
180
+ {lastError && <span className="error">Error al guardar</span>}
181
+
182
+ {/* lastResult = lo que devolvió tu onSave. Ej.: mostrar el HTML real del backend */}
183
+ {lastResult?.html && <iframe srcDoc={lastResult.html} title="Vista del servidor" />}
184
+ </>
185
+ );
186
+ }
187
+ ```
188
+
189
+ | Campo | Descripción |
190
+ |---|---|
191
+ | `isSaving` | Hay un guardado en vuelo. |
192
+ | `lastSavedAt` | Fecha del último guardado exitoso. |
193
+ | `lastError` | Error del último intento fallido (se limpia al guardar bien). |
194
+ | `lastResult` | Lo que devolvió tu `onSave` — ej. `{ json, html }` si tu backend renderiza y responde el HTML. |
195
+ | `saveNow()` | Guardado inmediato con los mismos guards: no hace nada si no hay cambios o ya hay uno en vuelo. |
196
+
197
+ ¿Solo botón manual, sin autoguardado? `autosave={{ enabled: false, onSave }}` — `saveNow()` sigue funcionando.
198
+
199
+ ---
200
+
201
+ ## Editar una plantilla existente (cargar del backend)
202
+
203
+ ¿Ya creaste una plantilla y quieres volver a editarla? El ciclo completo es: **fetch del JSON → `hydrate()` → el usuario edita → el autoguardado guarda sobre la misma plantilla**. Ejemplo de página completa:
204
+
205
+ ```tsx
206
+ import { useEffect, useState } from "react";
207
+ import {
208
+ EmailBuilder,
209
+ EmailBuilderProvider,
210
+ useEmailBuilderStoreInstance,
211
+ type AutosavePayload,
212
+ } from "create-email-template";
213
+ import "create-email-template/style.css";
214
+
215
+ // 1. Página: trae la plantilla y no monta el editor hasta que llegue
216
+ export function EditorPage({ templateId }: { templateId: string }) {
217
+ const [payload, setPayload] = useState<AutosavePayload | null>(null);
218
+
219
+ useEffect(() => {
220
+ fetch(`/api/templates/${templateId}`)
221
+ .then((r) => r.json())
222
+ .then((template) => setPayload(template.payload)); // { content, settings }
223
+ }, [templateId]);
224
+
225
+ if (!payload) return <p>Cargando plantilla…</p>;
226
+
227
+ return (
228
+ <EmailBuilderProvider
229
+ config={miConfig}
230
+ autosave={{
231
+ // 4. El autoguardado ahora actualiza ESA MISMA plantilla
232
+ onSave: (data) =>
233
+ fetch(`/api/templates/${templateId}`, {
234
+ method: "PUT",
235
+ headers: { "Content-Type": "application/json" },
236
+ body: JSON.stringify(data),
237
+ }),
238
+ }}
239
+ >
240
+ {/* 2. Componente puente: inyecta el payload en el store */}
241
+ <TemplateLoader payload={payload} />
242
+ {/* 3. El editor arranca ya con los bloques de la plantilla */}
243
+ <EmailBuilder />
244
+ </EmailBuilderProvider>
245
+ );
246
+ }
247
+
248
+ // Debe vivir DENTRO del provider (usa el store del builder). No renderiza nada.
249
+ function TemplateLoader({ payload }: { payload: AutosavePayload }) {
250
+ const store = useEmailBuilderStoreInstance();
251
+
252
+ useEffect(() => {
253
+ store.getState().hydrate({
254
+ blocks: payload.content,
255
+ settings: payload.settings, // opcional
256
+ });
257
+ }, [payload, store]);
258
+
259
+ return null;
260
+ }
261
+ ```
262
+
263
+ Qué hace `hydrate({ blocks, settings? })`:
264
+
265
+ - **Normaliza el payload**: props faltantes se completan con los defaults actuales, tipos desconocidos se descartan — plantillas guardadas con versiones anteriores del esquema cargan sin romperse.
266
+ - **Reinicia el historial de deshacer**: el estado cargado es la base nueva (el undo anterior se descarta).
267
+ - Deja `dirty: false`: el autoguardado **no dispara** hasta que el usuario edite de verdad.
268
+ - Los `id` de los bloques se conservan, así que el guardado actualiza en lugar de duplicar.
269
+
270
+ > 💡 Cargar antes de montar el editor (el `if (!payload)` del ejemplo) no es obligatorio, pero evita que el usuario edite un canvas vacío y su trabajo se sobrescriba cuando llegue el fetch.
271
+
272
+ ---
273
+
274
+ ## Deshacer
275
+
276
+ Undo **lineal** (solo hacia atrás, nunca adelante), en memoria:
277
+
278
+ - Cada acción (añadir/eliminar/reordenar/duplicar bloques, editar propiedades y ajustes) registra un snapshot. Tope: `historyLimit` (50).
279
+ - Las escrituras continuas se **agrupan**: Ctrl+Z deshace la frase escrita, no tecla por tecla.
280
+ - **Ctrl/Cmd+Z ya funciona** dentro de `<EmailBuilder />`. En un campo de texto editable se respeta el undo nativo.
281
+ - Deshacer marca `dirty: true` → el autoguardado persiste el estado deshecho en el siguiente tick.
282
+ - Al `hydrate()` el historial se vacía (el estado cargado es la nueva base).
283
+
284
+ Para tu propia UI de undo:
285
+
286
+ ```tsx
287
+ import { useEmailBuilderStore } from "create-email-template";
288
+
289
+ function UndoButton() {
290
+ const canUndo = useEmailBuilderStore((s) => s.past.length > 0);
291
+ const undo = useEmailBuilderStore((s) => s.undo);
292
+ return <button onClick={() => undo()} disabled={!canUndo}>Deshacer</button>;
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## `useRenderEmail()` — payload y render de preview
299
+
300
+ ```tsx
301
+ import { useRenderEmail } from "create-email-template";
302
+
303
+ function Acciones() {
304
+ const { getPayload, renderHtml, getHtml, resolve, html, isPending } = useRenderEmail();
305
+
306
+ const guardar = () => fetch("/api/templates", { method: "POST", body: JSON.stringify(getPayload()) });
307
+ const preview = () => renderHtml({ subject: "Hola {firstName}" }); // html se actualiza
308
+
309
+ return <button onClick={preview} disabled={isPending}>Refrescar preview</button>;
310
+ }
311
+ ```
312
+
313
+ | Miembro | Descripción |
314
+ |---|---|
315
+ | `getPayload()` | `{ content: EmailBlock[], settings }` — el JSON para persistir. |
316
+ | `renderHtml(opts?)` | Renderiza el preview con react-email (async). Opciones: `{ subject?, context? }`. |
317
+ | `getHtml(opts?)` | Igual que `renderHtml` pero cacheado: sin opciones devuelve el HTML ya generado. |
318
+ | `resolve(text, ctx?)` | Reemplaza `{etiquetas}` en un texto con el `sampleContext` (o el que pases). |
319
+ | `html` / `isPending` / `setHtml` | Estado reactivo del último render. |
320
+
321
+ ---
322
+
323
+ ## `useEmailBuilderStore(selector)` — estado y acciones
324
+
325
+ Suscripción selectiva (re-renderiza solo lo que cambia). Requiere el provider.
326
+
327
+ ```tsx
328
+ import { useEmailBuilderStore } from "create-email-template";
329
+
330
+ const blocks = useEmailBuilderStore((s) => s.blocks); // bloques actuales
331
+ const dirty = useEmailBuilderStore((s) => s.dirty); // ¿hay cambios sin guardar?
332
+ const canUndo = useEmailBuilderStore((s) => s.past.length > 0);
333
+ const undo = useEmailBuilderStore((s) => s.undo);
334
+ const addBlock = useEmailBuilderStore((s) => s.addBlock);
335
+
336
+ addBlock("text"); // añade un bloque de texto al final (con tus blockDefaults)
337
+ addBlock("button", 0); // …o en una posición específica
338
+ ```
339
+
340
+ **Estado:** `blocks`, `selectedId`, `settings`, `dirty`, `propertiesOpen`, `past` (pila de undo).
341
+
342
+ **Acciones:** `addBlock(type, index?)` · `removeBlock(id)` · `duplicateBlock(id)` · `updateBlockProps(id, props)` · `reorder(activeIndex, overIndex)` · `setSettings(patch)` · `select(id)` · `setPropertiesOpen(open)` · `hydrate({ blocks, settings? })` · `undo()` · `markSaved()` · `getPayload()`.
343
+
344
+ ¿Fuera de React? `createEmailBuilderStore(config)` crea el mismo store vanilla (`getState`/`setState`/`subscribe`) sin dependencias.
345
+
346
+ ---
347
+
348
+ ## Notas
349
+
350
+ - **Estilos**: importa `create-email-template/style.css` una vez. Todo va bajo la clase `.ter-theme` con clases `ter-*` — no colisiona con tu Tailwind/CSS.
351
+ - **Componentes sueltos**: también se exportan `BlockPalette`, `Canvas`, `PropertiesPanel`, `EditableBlockRenderer`, `InlineTextEditor`, `SelectionToolbar`, `VariablesInfoDialog` para armar un editor a medida.
352
+ - **Demo completa**: `apps/vite-test` del [monorepo](https://github.com/asmel2020/creator-email-templates) — editor con config de muestra, autoguardado, undo y dialog comparando el render de React vs. el HTML del backend.
353
+
354
+ ## Licencia
355
+
356
+ MIT
@@ -0,0 +1,4 @@
1
+ export declare const BlockPalette: ({ desktop }: {
2
+ desktop?: boolean;
3
+ }) => import("react").JSX.Element;
4
+ //# sourceMappingURL=block-palette.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-palette.d.ts","sourceRoot":"","sources":["../../../src/components/builder/block-palette.tsx"],"names":[],"mappings":"AAuCA,eAAO,MAAM,YAAY,GAAI,aAAqB;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,gCAatE,CAAA"}
@@ -0,0 +1,4 @@
1
+ export declare const SortableCanvas: ({ desktop, }: {
2
+ desktop?: boolean;
3
+ }) => import("react").JSX.Element;
4
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../../src/components/builder/canvas.tsx"],"names":[],"mappings":"AA6BA,eAAO,MAAM,cAAc,GAAI,cAE5B;IACD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,gCA+NA,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type EmailBlock } from "../../core/types";
2
+ interface Props {
3
+ block: EmailBlock;
4
+ }
5
+ export declare const EditableBlockRenderer: ({ block }: Props) => import("react").JSX.Element | null;
6
+ export {};
7
+ //# sourceMappingURL=editable-block-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable-block-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/builder/editable-block-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUlD,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,CAAA;CAClB;AAED,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,uCAycrD,CAAA"}
@@ -0,0 +1,5 @@
1
+ export interface EmailBuilderProps {
2
+ className?: string;
3
+ }
4
+ export declare const EmailBuilder: ({ className }: EmailBuilderProps) => import("react").JSX.Element;
5
+ //# sourceMappingURL=email-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-builder.d.ts","sourceRoot":"","sources":["../../../src/components/builder/email-builder.tsx"],"names":[],"mappings":"AAgCA,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,YAAY,GAAI,eAAe,iBAAiB,gCA6M5D,CAAA"}
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ interface Props {
3
+ value: string;
4
+ onChange: (next: string) => void;
5
+ rich?: boolean;
6
+ className?: string;
7
+ style?: React.CSSProperties;
8
+ onEnter?: () => void;
9
+ onDeleteWhenEmpty?: () => void;
10
+ autoFocus?: boolean;
11
+ placeholder?: string;
12
+ }
13
+ export declare const InlineTextEditor: ({ value, onChange, rich, className, style, onEnter, onDeleteWhenEmpty, autoFocus, placeholder, }: Props) => React.JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=inline-text-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-text-editor.d.ts","sourceRoot":"","sources":["../../../src/components/builder/inline-text-editor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAiDxE,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,GAAI,kGAU9B,KAAK,sBA+RP,CAAC"}
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { type EmailBlock } from "../../core/types";
3
+ interface Props {
4
+ block: EmailBlock;
5
+ }
6
+ export declare const PropertiesPanel: ({ block }: Props) => React.JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=properties-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"properties-panel.d.ts","sourceRoot":"","sources":["../../../src/components/builder/properties-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA2B,MAAM,OAAO,CAAA;AAI/C,OAAO,EAAE,KAAK,UAAU,EAAc,MAAM,kBAAkB,CAAA;AAyK9D,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,CAAA;CAClB;AAYD,eAAO,MAAM,eAAe,GAAI,WAAW,KAAK,sBA0U/C,CAAA"}
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ interface Props {
3
+ style: React.CSSProperties;
4
+ onFormat: (command: string, value?: string) => void;
5
+ innerRef?: React.Ref<HTMLDivElement>;
6
+ selectionSize: number | null;
7
+ selectionMaxSize: number;
8
+ selectionColor: string | null;
9
+ }
10
+ export declare const SelectionToolbar: ({ style, onFormat, innerRef, selectionSize, selectionMaxSize, selectionColor, }: Props) => React.JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=selection-toolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/builder/selection-toolbar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAcxC,UAAU,KAAK;IACb,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC;IAC3B,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAiBD,eAAO,MAAM,gBAAgB,GAAI,iFAO9B,KAAK,sBAwNP,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { type EmailBlock } from "../../core/types";
2
+ interface Props {
3
+ block: EmailBlock;
4
+ index: number;
5
+ isFirst?: boolean;
6
+ cardBorderRadius?: number;
7
+ }
8
+ export declare const SortableItem: ({ block, index, isFirst, cardBorderRadius, }: Props) => import("react").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=sortable-item.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sortable-item.d.ts","sourceRoot":"","sources":["../../../src/components/builder/sortable-item.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAgBlD,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,eAAO,MAAM,YAAY,GAAI,8CAK1B,KAAK,gCAyGP,CAAA"}
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ }
5
+ export declare const VariablesInfoDialog: ({ open, onOpenChange }: Props) => import("react").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=variables-info-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variables-info-dialog.d.ts","sourceRoot":"","sources":["../../../src/components/builder/variables-info-dialog.tsx"],"names":[],"mappings":"AAaA,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACtC;AA2GD,eAAO,MAAM,mBAAmB,GAAI,wBAAwB,KAAK,gCA4BhE,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
+ type ButtonVariant = "default" | "outline" | "secondary" | "ghost" | "destructive" | "link";
3
+ type ButtonSize = "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
4
+ export declare function Button({ className, variant, size, ...props }: ButtonPrimitive.Props & {
5
+ variant?: ButtonVariant;
6
+ size?: ButtonSize;
7
+ }): import("react").JSX.Element;
8
+ export declare const buttonVariants: () => string;
9
+ export {};
10
+ //# sourceMappingURL=button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAIjE,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAA;AAC3F,KAAK,UAAU,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAsB7F,wBAAgB,MAAM,CAAC,EACrB,SAAS,EACT,OAAmB,EACnB,IAAgB,EAChB,GAAG,KAAK,EACT,EAAE,eAAe,CAAC,KAAK,GAAG;IAAE,OAAO,CAAC,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,UAAU,CAAA;CAAE,+BAQxE;AAED,eAAO,MAAM,cAAc,cAAW,CAAA"}
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
3
+ declare function Dialog({ ...props }: DialogPrimitive.Root.Props): React.JSX.Element;
4
+ declare function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props): React.JSX.Element;
5
+ declare function DialogPortal({ ...props }: DialogPrimitive.Portal.Props): React.JSX.Element;
6
+ declare function DialogClose({ ...props }: DialogPrimitive.Close.Props): React.JSX.Element;
7
+ declare function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props): React.JSX.Element;
8
+ declare function DialogContent({ className, children, showCloseButton, ...props }: DialogPrimitive.Popup.Props & {
9
+ showCloseButton?: boolean;
10
+ }): React.JSX.Element;
11
+ declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
12
+ declare function DialogFooter({ className, showCloseButton, children, ...props }: React.ComponentProps<"div"> & {
13
+ showCloseButton?: boolean;
14
+ }): React.JSX.Element;
15
+ declare function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props): React.JSX.Element;
16
+ declare function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props): React.JSX.Element;
17
+ export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, };
18
+ //# sourceMappingURL=dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/components/ui/dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAMjE,iBAAS,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,qBAEvD;AAED,iBAAS,aAAa,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,OAAO,CAAC,KAAK,qBAEjE;AAED,iBAAS,YAAY,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,qBAE/D;AAED,iBAAS,WAAW,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,qBAE7D;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,CAAC,QAAQ,CAAC,KAAK,qBAQhC;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,eAAsB,EACtB,GAAG,KAAK,EACT,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,GAAG;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,qBA6BA;AAED,iBAAS,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAQzE;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,eAAuB,EACvB,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,qBAeA;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,qBAWxE;AAED,iBAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,CAAC,WAAW,CAAC,KAAK,qBAQnC;AAED,OAAO,EACL,MAAM,EACN,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,aAAa,GACd,CAAA"}
@@ -0,0 +1,4 @@
1
+ import * as React from "react";
2
+ declare function Input({ className, type, ...props }: React.ComponentProps<"input">): React.JSX.Element;
3
+ export { Input };
4
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/ui/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,qBAS1E;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -0,0 +1,4 @@
1
+ import * as React from "react";
2
+ declare function Label({ className, ...props }: React.ComponentProps<"label">): React.JSX.Element;
3
+ export { Label };
4
+ //# sourceMappingURL=label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../src/components/ui/label.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,qBAQpE;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
3
+ declare function Sheet({ ...props }: SheetPrimitive.Root.Props): React.JSX.Element;
4
+ declare function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props): React.JSX.Element;
5
+ declare function SheetClose({ ...props }: SheetPrimitive.Close.Props): React.JSX.Element;
6
+ declare function SheetContent({ className, children, side, showCloseButton, ...props }: SheetPrimitive.Popup.Props & {
7
+ side?: "top" | "right" | "bottom" | "left";
8
+ showCloseButton?: boolean;
9
+ }): React.JSX.Element;
10
+ declare function SheetHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
11
+ declare function SheetFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
12
+ declare function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props): React.JSX.Element;
13
+ declare function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props): React.JSX.Element;
14
+ export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, };
15
+ //# sourceMappingURL=sheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheet.d.ts","sourceRoot":"","sources":["../../../src/components/ui/sheet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAMhE,iBAAS,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,qBAErD;AAED,iBAAS,YAAY,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,qBAE/D;AAED,iBAAS,UAAU,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,qBAE3D;AAgBD,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,QAAQ,EACR,IAAc,EACd,eAAsB,EACtB,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,GAAG;IAC9B,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,qBA8BA;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAQxE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAQxE;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,qBAWtE;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,qBAQlC;AAED,OAAO,EACL,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { EmailBuilderConfig, ResolvedEmailBuilderConfig } from "./types";
2
+ export declare const resolveEmailBuilderConfig: (config?: EmailBuilderConfig) => ResolvedEmailBuilderConfig;
3
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,SAAS,CAAC;AAejB,eAAO,MAAM,yBAAyB,GACpC,SAAS,kBAAkB,KAC1B,0BAyCF,CAAC"}