@kgalexander/mcreate 0.0.7 → 0.0.9

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.
package/README.md CHANGED
@@ -8,6 +8,20 @@ mcreate package
8
8
  npm install @kgalexander/mcreate
9
9
  ```
10
10
 
11
+ ## Tailwind CSS v4 Setup
12
+
13
+ This package uses Tailwind CSS utility classes. For styles to work correctly, you need to configure Tailwind to scan the package for class names.
14
+
15
+ Add the `@source` directive to your `globals.css` (or main CSS file):
16
+
17
+ ```css
18
+ @import "tailwindcss";
19
+
20
+ @source "../node_modules/@kgalexander/mcreate";
21
+ ```
22
+
23
+ > **Why is this needed?** Tailwind CSS v4 doesn't scan `node_modules` by default. The `@source` directive tells Tailwind to include class names from this package when compiling your CSS.
24
+
11
25
  ## Local Development with npm link
12
26
 
13
27
  When using `npm link` to develop this package locally with a Next.js application, you need to configure Next.js to resolve symlinked packages outside the project directory.
@@ -1875,6 +1875,8 @@ var useEditorStore = create()(
1875
1875
  immer((set) => ({
1876
1876
  // Initial state
1877
1877
  template: defaultTemplate,
1878
+ templateId: null,
1879
+ onSave: null,
1878
1880
  previewMode: false,
1879
1881
  focusIdx: null,
1880
1882
  hoverIdx: null,
@@ -1893,6 +1895,48 @@ var useEditorStore = create()(
1893
1895
  historyIndex: -1,
1894
1896
  isUndoRedoAction: false,
1895
1897
  lastHistoryPushTime: 0,
1898
+ // Auto-save tracking
1899
+ lastSavedSnapshot: null,
1900
+ isSaving: false,
1901
+ // Initialize store with external template (for npm package usage)
1902
+ initializeWithTemplate: (templateId, template, onSave) => {
1903
+ set((state) => {
1904
+ state.templateId = templateId;
1905
+ state.template = template;
1906
+ state.onSave = onSave ?? null;
1907
+ state.templateSize = calculateTemplateSize(template);
1908
+ state.isAtSizeLimit = false;
1909
+ state.history = [cloneDeep(template)];
1910
+ state.historyIndex = 0;
1911
+ state.focusIdx = null;
1912
+ state.hoverIdx = null;
1913
+ state.textEditing = null;
1914
+ state.lastSavedSnapshot = JSON.stringify(template);
1915
+ });
1916
+ },
1917
+ // Template metadata actions
1918
+ setTemplateName: (name) => {
1919
+ set((state) => {
1920
+ state.template.name = name;
1921
+ });
1922
+ useEditorStore.getState().pushHistory();
1923
+ },
1924
+ // Auto-save actions
1925
+ markAsSaved: () => {
1926
+ set((state) => {
1927
+ state.lastSavedSnapshot = JSON.stringify(state.template);
1928
+ });
1929
+ },
1930
+ hasUnsavedChanges: () => {
1931
+ const state = useEditorStore.getState();
1932
+ if (!state.lastSavedSnapshot) return true;
1933
+ return JSON.stringify(state.template) !== state.lastSavedSnapshot;
1934
+ },
1935
+ setIsSaving: (saving) => {
1936
+ set((state) => {
1937
+ state.isSaving = saving;
1938
+ });
1939
+ },
1896
1940
  // Selection actions
1897
1941
  setFocusIdx: (idx) => {
1898
1942
  set((state) => {
@@ -4341,6 +4385,11 @@ function DialogPortal({
4341
4385
  }) {
4342
4386
  return /* @__PURE__ */ jsx4(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
4343
4387
  }
4388
+ function DialogClose({
4389
+ ...props
4390
+ }) {
4391
+ return /* @__PURE__ */ jsx4(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
4392
+ }
4344
4393
  function DialogOverlay({
4345
4394
  className,
4346
4395
  ...props
@@ -4392,6 +4441,38 @@ function DialogContent({
4392
4441
  )
4393
4442
  ] });
4394
4443
  }
4444
+ function DialogHeader({ className, ...props }) {
4445
+ return /* @__PURE__ */ jsx4(
4446
+ "div",
4447
+ {
4448
+ "data-slot": "dialog-header",
4449
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
4450
+ ...props
4451
+ }
4452
+ );
4453
+ }
4454
+ function DialogFooter({
4455
+ className,
4456
+ showCloseButton = false,
4457
+ children,
4458
+ ...props
4459
+ }) {
4460
+ return /* @__PURE__ */ jsxs2(
4461
+ "div",
4462
+ {
4463
+ "data-slot": "dialog-footer",
4464
+ className: cn(
4465
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
4466
+ className
4467
+ ),
4468
+ ...props,
4469
+ children: [
4470
+ children,
4471
+ showCloseButton && /* @__PURE__ */ jsx4(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx4(Button, { variant: "outline", children: "Close" }) })
4472
+ ]
4473
+ }
4474
+ );
4475
+ }
4395
4476
  function DialogTitle({
4396
4477
  className,
4397
4478
  ...props
@@ -13896,7 +13977,7 @@ async function compileMjml(mjml) {
13896
13977
  if (cached) {
13897
13978
  return cached;
13898
13979
  }
13899
- const response = await fetch("/api/mjml", {
13980
+ const response = await fetch("/api/mrender", {
13900
13981
  method: "POST",
13901
13982
  headers: { "Content-Type": "application/json" },
13902
13983
  body: JSON.stringify({ mjml })
@@ -14062,9 +14143,6 @@ function getCompanyFooterSection(template) {
14062
14143
  }
14063
14144
  function Editor({ setEditorLoading }) {
14064
14145
  useUndoRedoKeyboard();
14065
- useEffect19(() => {
14066
- useEditorStore.getState().clearHistory();
14067
- }, []);
14068
14146
  const template = useEditorStore((state) => state.template);
14069
14147
  const previewMode = useEditorStore((state) => state.previewMode);
14070
14148
  const focusIdx = useEditorStore((state) => state.focusIdx);
@@ -15002,7 +15080,12 @@ export {
15002
15080
  SOCIAL_OPTIONS,
15003
15081
  parseBorder,
15004
15082
  formatBorder,
15083
+ formatPrice,
15005
15084
  parsePrice,
15085
+ formatNumber,
15086
+ formatOpenHouseDate,
15087
+ formatOpenHouseTime,
15088
+ json2mjml,
15006
15089
  MAX_TEMPLATE_SIZE,
15007
15090
  useEditorStore,
15008
15091
  BUTTON_ALIGNMENTS,
@@ -15029,7 +15112,10 @@ export {
15029
15112
  Button,
15030
15113
  Dialog,
15031
15114
  DialogTrigger,
15115
+ DialogClose,
15032
15116
  DialogContent,
15117
+ DialogHeader,
15118
+ DialogFooter,
15033
15119
  DialogTitle,
15034
15120
  QUICK_BLOCKS,
15035
15121
  Tooltip,
@@ -5,7 +5,7 @@ import {
5
5
  MAILLOW_EMAIL_EDITOR_VERSION,
6
6
  Preview,
7
7
  useEditorStore
8
- } from "./chunk-4J3VEZPB.mjs";
8
+ } from "./chunk-Y52MW7DH.mjs";
9
9
  export {
10
10
  Editor,
11
11
  History,
package/dist/index.d.mts CHANGED
@@ -1,15 +1,517 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { BodyComponent } from 'mjml-core';
3
+
4
+ type PageElement = {
5
+ id?: string;
6
+ type: 'page';
7
+ tagName: 'mj-body';
8
+ data: {
9
+ value: {
10
+ breakpoint?: string;
11
+ headAttributes?: string;
12
+ fontSize?: string;
13
+ fontWeight?: string;
14
+ lineHeight?: string;
15
+ 'font-size'?: string;
16
+ 'font-weight'?: string;
17
+ 'line-height'?: string;
18
+ headStyles?: string[];
19
+ fonts?: {
20
+ name?: string;
21
+ href?: string;
22
+ }[];
23
+ responsive?: boolean;
24
+ fontFamily?: string;
25
+ 'font-family'?: string;
26
+ textColor?: string;
27
+ 'text-color'?: string;
28
+ showCompanyFooter?: boolean;
29
+ [key: string]: any;
30
+ };
31
+ };
32
+ attributes: {
33
+ [key: string]: string;
34
+ };
35
+ children: (SectionElement | SectionColumnElement | SectionPropertyKmElement | SectionPropertySingleTwoElement | SectionPropertyTripleElement)[];
36
+ };
37
+ type SectionElement = {
38
+ type: 'section';
39
+ tagName: 'mj-section';
40
+ data: {
41
+ value: {
42
+ columnRows?: number;
43
+ columnWidths?: string[];
44
+ noWrap?: boolean;
45
+ isCompanyFooter?: boolean;
46
+ [key: string]: any;
47
+ };
48
+ };
49
+ attributes: {
50
+ [key: string]: string;
51
+ };
52
+ children: (ColumnElement)[];
53
+ };
54
+ type SectionColumnElement = {
55
+ type: 'section-column';
56
+ tagName: 'mj-section';
57
+ data: {
58
+ value: {
59
+ columnRows?: number;
60
+ columnWidths?: string[];
61
+ noWrap?: boolean;
62
+ [key: string]: any;
63
+ };
64
+ };
65
+ attributes: {
66
+ [key: string]: string;
67
+ };
68
+ children: (ColumnElement)[];
69
+ };
70
+ type SectionPropertyKmElement = {
71
+ type: 'section-property-km';
72
+ tagName: 'mj-section';
73
+ data: {
74
+ value: {
75
+ noWrap?: boolean;
76
+ [key: string]: any;
77
+ };
78
+ };
79
+ attributes: {
80
+ [key: string]: string;
81
+ };
82
+ children: [ColumnElement];
83
+ };
84
+ type SectionPropertySingleTwoElement = {
85
+ type: 'section-property-single-two';
86
+ tagName: 'mj-section';
87
+ data: {
88
+ value: {
89
+ noWrap?: boolean;
90
+ [key: string]: any;
91
+ };
92
+ };
93
+ attributes: {
94
+ [key: string]: string;
95
+ };
96
+ children: [ColumnElement];
97
+ };
98
+ type ColumnElement = {
99
+ type: 'column';
100
+ tagName: 'mj-column';
101
+ data: {
102
+ value: {
103
+ [key: string]: any;
104
+ };
105
+ };
106
+ attributes: {
107
+ [key: string]: string;
108
+ };
109
+ children: (TextElement | ButtonElement | SpaceElement | DividerElement | ImageElement | SocialElement | PropertyCardElement | PropertyCardSingleTwoElement | PropertyCardTripleElement)[];
110
+ };
111
+ type TextElement = {
112
+ type: 'text';
113
+ tagName: 'mj-text';
114
+ data: {
115
+ value: {
116
+ content: string;
117
+ [key: string]: any;
118
+ };
119
+ };
120
+ attributes: {
121
+ [key: string]: string;
122
+ };
123
+ };
124
+ type ButtonElement = {
125
+ type: 'button';
126
+ tagName: 'mj-button';
127
+ data: {
128
+ value: {
129
+ content: string;
130
+ [key: string]: any;
131
+ };
132
+ };
133
+ attributes: {
134
+ [key: string]: string;
135
+ };
136
+ };
137
+ type PropertyCardElement = {
138
+ type: 'property-card';
139
+ tagName: 'mj-colproperty';
140
+ data: {
141
+ value: {
142
+ [key: string]: any;
143
+ };
144
+ };
145
+ attributes: {
146
+ [key: string]: string;
147
+ };
148
+ };
149
+ type PropertyCardSingleTwoElement = {
150
+ type: 'property-card-single-two';
151
+ tagName: 'mj-propertysingletwo';
152
+ data: {
153
+ value: {
154
+ [key: string]: any;
155
+ };
156
+ };
157
+ attributes: {
158
+ [key: string]: string;
159
+ };
160
+ };
161
+ type PropertyCardTripleItemElement = {
162
+ type: 'property-card-triple-item';
163
+ tagName: 'mj-propertytripleitem';
164
+ data: {
165
+ value: {
166
+ [key: string]: any;
167
+ };
168
+ };
169
+ attributes: {
170
+ [key: string]: string;
171
+ };
172
+ };
173
+ type PropertyCardTripleElement = {
174
+ type: 'property-card-triple';
175
+ tagName: 'mj-propertytriple';
176
+ data: {
177
+ value: {
178
+ [key: string]: any;
179
+ };
180
+ };
181
+ attributes: {
182
+ [key: string]: string;
183
+ };
184
+ children: PropertyCardTripleItemElement[];
185
+ };
186
+ type SectionPropertyTripleElement = {
187
+ type: 'section-property-triple';
188
+ tagName: 'mj-section';
189
+ data: {
190
+ value: {
191
+ noWrap?: boolean;
192
+ [key: string]: any;
193
+ };
194
+ };
195
+ attributes: {
196
+ [key: string]: string;
197
+ };
198
+ children: [ColumnElement];
199
+ };
200
+ type SpaceElement = {
201
+ type: 'space';
202
+ tagName: 'mj-spacer';
203
+ attributes: {
204
+ [key: string]: string;
205
+ };
206
+ };
207
+ type DividerElement = {
208
+ type: 'divider';
209
+ tagName: 'mj-divider';
210
+ attributes: {
211
+ [key: string]: string;
212
+ };
213
+ };
214
+ type ImageElement = {
215
+ type: 'image';
216
+ tagName: 'mj-image';
217
+ attributes: {
218
+ [key: string]: string;
219
+ };
220
+ };
221
+ type SocialStyle = 'Icon + Text' | 'Icon' | 'Text';
222
+ type IconSize = 'Small' | 'Medium' | 'Large';
223
+ type IconStyle = 'Regular' | 'Filled' | 'Outline';
224
+ type IconColor = 'Colored' | 'Dark' | 'Light' | 'Gray';
225
+ type SocialElement = {
226
+ type: 'social';
227
+ tagName: 'mj-social';
228
+ data: {
229
+ value: {
230
+ socialStyle: SocialStyle;
231
+ iconSize: IconSize;
232
+ iconStyle: IconStyle;
233
+ iconColor: IconColor;
234
+ };
235
+ };
236
+ attributes: {
237
+ [key: string]: string;
238
+ };
239
+ children: (SocialItemElement)[];
240
+ };
241
+ type SocialItemElement = {
242
+ type: 'social-item';
243
+ tagName: 'mj-social-element';
244
+ data: {
245
+ value: {
246
+ content: string;
247
+ socialType: string;
248
+ };
249
+ };
250
+ attributes: {
251
+ [key: string]: string;
252
+ };
253
+ };
254
+ type TemplateJSON = {
255
+ id: string;
256
+ name: string;
257
+ version: string;
258
+ published: boolean;
259
+ creator: string;
260
+ content: PageElement[];
261
+ };
262
+
263
+ type OnSaveCallback = (templateId: string, template: TemplateJSON) => void | Promise<void>;
2
264
 
3
265
  interface EditorProps {
4
- defaultContent?: string;
5
266
  setEditorLoading?: (loading: boolean) => void;
6
267
  editorLoading?: boolean;
7
268
  }
8
269
  declare function Editor({ setEditorLoading }: EditorProps): react_jsx_runtime.JSX.Element;
9
270
 
10
- declare function TemplatePage({ templateId, initialTemplate }: {
271
+ declare function TemplatePage({ templateId, initialTemplate, onSave }: {
11
272
  templateId: string;
12
- initialTemplate: any;
273
+ initialTemplate: TemplateJSON;
274
+ onSave?: OnSaveCallback;
13
275
  }): react_jsx_runtime.JSX.Element;
14
276
 
15
- export { Editor, TemplatePage };
277
+ declare class MjColproperty extends BodyComponent {
278
+ static componentName: string;
279
+ static endingTag: boolean;
280
+ static dependencies: {
281
+ 'mj-column': string[];
282
+ 'mj-colproperty': never[];
283
+ };
284
+ static allowedAttributes: {
285
+ 'image-src': string;
286
+ 'image-alt': string;
287
+ price: string;
288
+ address: string;
289
+ city: string;
290
+ country: string;
291
+ beds: string;
292
+ baths: string;
293
+ sqft: string;
294
+ href: string;
295
+ status: string;
296
+ 'status-color': string;
297
+ 'is-new': string;
298
+ brokerage: string;
299
+ 'mls-logo': string;
300
+ width: string;
301
+ 'border-radius': string;
302
+ description: string;
303
+ 'is-open-house': string;
304
+ 'open-house-date': string;
305
+ 'open-house-time': string;
306
+ border: string;
307
+ 'font-family': string;
308
+ 'text-color': string;
309
+ 'is-description': string;
310
+ 'is-brokerage': string;
311
+ 'is-status': string;
312
+ };
313
+ static defaultAttributes: {
314
+ 'image-src': string;
315
+ 'image-alt': string;
316
+ price: string;
317
+ address: string;
318
+ city: string;
319
+ country: string;
320
+ beds: string;
321
+ baths: string;
322
+ sqft: string;
323
+ href: string;
324
+ status: string;
325
+ 'status-color': string;
326
+ 'is-new': string;
327
+ brokerage: string;
328
+ 'mls-logo': string;
329
+ 'border-radius': string;
330
+ width: string;
331
+ description: string;
332
+ 'is-open-house': string;
333
+ 'open-house-date': string;
334
+ 'open-house-time': string;
335
+ border: string;
336
+ 'font-family': string;
337
+ 'text-color': string;
338
+ 'is-description': string;
339
+ 'is-brokerage': string;
340
+ 'is-status': string;
341
+ };
342
+ componentHeadStyle: (breakpoint: number) => string;
343
+ render(): string;
344
+ }
345
+
346
+ declare class MjPropertysingletwo extends BodyComponent {
347
+ static componentName: string;
348
+ static endingTag: boolean;
349
+ borderRadius: string;
350
+ innerBorderRadius: string;
351
+ uniqueId: string;
352
+ constructor(initialDatas?: {});
353
+ static dependencies: {
354
+ 'mj-column': string[];
355
+ 'mj-propertysingletwo': never[];
356
+ };
357
+ static allowedAttributes: {
358
+ 'image-src': string;
359
+ 'image-alt': string;
360
+ price: string;
361
+ address: string;
362
+ city: string;
363
+ country: string;
364
+ beds: string;
365
+ baths: string;
366
+ sqft: string;
367
+ href: string;
368
+ status: string;
369
+ 'status-color': string;
370
+ 'is-new': string;
371
+ brokerage: string;
372
+ 'mls-logo': string;
373
+ width: string;
374
+ 'border-radius': string;
375
+ description: string;
376
+ 'is-open-house': string;
377
+ 'open-house-date': string;
378
+ 'open-house-time': string;
379
+ border: string;
380
+ 'font-family': string;
381
+ 'text-color': string;
382
+ 'is-description': string;
383
+ 'is-brokerage': string;
384
+ 'is-status': string;
385
+ };
386
+ static defaultAttributes: {
387
+ 'image-src': string;
388
+ 'image-alt': string;
389
+ price: string;
390
+ address: string;
391
+ city: string;
392
+ country: string;
393
+ beds: string;
394
+ baths: string;
395
+ sqft: string;
396
+ href: string;
397
+ status: string;
398
+ 'status-color': string;
399
+ 'is-new': string;
400
+ brokerage: string;
401
+ 'mls-logo': string;
402
+ 'border-radius': string;
403
+ width: string;
404
+ description: string;
405
+ 'is-open-house': string;
406
+ 'open-house-date': string;
407
+ 'open-house-time': string;
408
+ border: string;
409
+ 'font-family': string;
410
+ 'text-color': string;
411
+ 'is-description': string;
412
+ 'is-brokerage': string;
413
+ 'is-status': string;
414
+ };
415
+ componentHeadStyle: (breakpoint: number) => string;
416
+ render(): string;
417
+ }
418
+
419
+ declare class MjPropertytriplebetter extends BodyComponent {
420
+ static componentName: string;
421
+ static endingTag: boolean;
422
+ borderRadius: string;
423
+ innerBorderRadius: string;
424
+ uniqueId: string;
425
+ constructor(initialDatas?: {});
426
+ static dependencies: {
427
+ 'mj-column': string[];
428
+ 'mj-propertytriple': never[];
429
+ };
430
+ static allowedAttributes: {
431
+ width: string;
432
+ 'card-width': string;
433
+ gap: string;
434
+ 'border-radius': string;
435
+ border: string;
436
+ 'image-height': string;
437
+ 'font-family': string;
438
+ 'text-color': string;
439
+ 'background-color': string;
440
+ 'image-src-1': string;
441
+ 'href-1': string;
442
+ 'price-1': string;
443
+ 'beds-1': string;
444
+ 'baths-1': string;
445
+ 'sqft-1': string;
446
+ 'city-1': string;
447
+ 'image-src-2': string;
448
+ 'href-2': string;
449
+ 'price-2': string;
450
+ 'beds-2': string;
451
+ 'baths-2': string;
452
+ 'sqft-2': string;
453
+ 'city-2': string;
454
+ 'image-src-3': string;
455
+ 'href-3': string;
456
+ 'price-3': string;
457
+ 'beds-3': string;
458
+ 'baths-3': string;
459
+ 'sqft-3': string;
460
+ 'city-3': string;
461
+ };
462
+ static defaultAttributes: {
463
+ width: string;
464
+ 'card-width': string;
465
+ gap: string;
466
+ 'border-radius': string;
467
+ border: string;
468
+ 'image-height': string;
469
+ 'font-family': string;
470
+ 'text-color': string;
471
+ 'background-color': string;
472
+ 'image-src-1': string;
473
+ 'href-1': string;
474
+ 'price-1': string;
475
+ 'beds-1': string;
476
+ 'baths-1': string;
477
+ 'sqft-1': string;
478
+ 'city-1': string;
479
+ 'image-src-2': string;
480
+ 'href-2': string;
481
+ 'price-2': string;
482
+ 'beds-2': string;
483
+ 'baths-2': string;
484
+ 'sqft-2': string;
485
+ 'city-2': string;
486
+ 'image-src-3': string;
487
+ 'href-3': string;
488
+ 'price-3': string;
489
+ 'beds-3': string;
490
+ 'baths-3': string;
491
+ 'sqft-3': string;
492
+ 'city-3': string;
493
+ };
494
+ componentHeadStyle: (breakpoint: number) => string;
495
+ renderCard(index: number): string;
496
+ render(): string;
497
+ }
498
+
499
+ /**
500
+ * JSON to MJML Converter
501
+ * Converts template JSON to MJML string for rendering
502
+ */
503
+
504
+ type RenderMode = 'production' | 'editing';
505
+ interface RenderOptions {
506
+ isPaid?: boolean;
507
+ }
508
+ /**
509
+ * Convert template JSON to MJML string
510
+ * @param template - Template JSON object
511
+ * @param mode - Render mode: 'production' for clean output, 'editing' for tracking classes
512
+ * @param options - Render options including isPaid for company footer visibility
513
+ * @returns MJML string
514
+ */
515
+ declare function json2mjml(template: TemplateJSON, mode?: RenderMode, options?: RenderOptions): string;
516
+
517
+ export { Editor, MjColproperty, MjPropertysingletwo, MjPropertytriplebetter, type OnSaveCallback, type TemplateJSON, TemplatePage, MjColproperty as default, json2mjml };