@savvycal/mjml-editor 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"InteractivePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/InteractivePreview.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAID,wBAAgB,kBAAkB,CAAC,EACjC,UAAiB,EACjB,WAAuB,GACxB,EAAE,uBAAuB,2CAqLzB"}
1
+ {"version":3,"file":"InteractivePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/InteractivePreview.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAID,wBAAgB,kBAAkB,CAAC,EACjC,UAAiB,EACjB,WAAuB,GACxB,EAAE,uBAAuB,2CAwLzB"}
@@ -1,37 +1,39 @@
1
- import { jsxs as n, jsx as s } from "react/jsx-runtime";
2
- import { useRef as w, useState as h, useEffect as a, useCallback as E } from "react";
3
- import { useEditor as y } from "../../context/EditorContext.js";
4
- import { renderMjmlInteractive as C } from "../../lib/mjml/renderer.js";
5
- const I = 375;
6
- function S({
7
- showHeader: p = !0,
8
- previewMode: g = "desktop"
1
+ import { jsxs as r, jsx as l } from "react/jsx-runtime";
2
+ import { useRef as y, useState as g, useEffect as a, useCallback as C } from "react";
3
+ import { useEditor as I } from "../../context/EditorContext.js";
4
+ import { useNonce as N } from "../../context/NonceContext.js";
5
+ import { useRenderEndpoint as L } from "../../context/RenderEndpointContext.js";
6
+ import { renderMjmlInteractive as D } from "../../lib/mjml/renderer.js";
7
+ const B = 375;
8
+ function W({
9
+ showHeader: x = !0,
10
+ previewMode: k = "desktop"
9
11
  }) {
10
- const { state: r, selectBlock: d } = y(), o = w(null), [m, x] = h(r.document), [k, v] = h({
12
+ const { state: n, selectBlock: m } = I(), i = N(), u = L(), o = y(null), [f, v] = g(n.document), [E, w] = g({
11
13
  html: "",
12
14
  errors: []
13
15
  });
14
16
  a(() => {
15
17
  const e = setTimeout(() => {
16
- x(r.document);
18
+ v(n.document);
17
19
  }, 300);
18
20
  return () => clearTimeout(e);
19
- }, [r.document]), a(() => {
21
+ }, [n.document]), a(() => {
20
22
  let e = !1;
21
- return C(m).then((t) => {
22
- e || v(t);
23
+ return D(f, u).then((t) => {
24
+ e || w(t);
23
25
  }), () => {
24
26
  e = !0;
25
27
  };
26
- }, [m]);
27
- const { html: u, errors: l } = k, i = E(
28
+ }, [f, u]);
29
+ const { html: b, errors: s } = E, d = C(
28
30
  (e) => {
29
- e.data?.type === "BLOCK_SELECTED" && d(e.data.blockId);
31
+ e.data?.type === "BLOCK_SELECTED" && m(e.data.blockId);
30
32
  },
31
- [d]
33
+ [m]
32
34
  );
33
- a(() => (window.addEventListener("message", i), () => window.removeEventListener("message", i)), [i]);
34
- const c = r.selectedBlockId ? `.block-${r.selectedBlockId} { outline: 2px solid #6366f1 !important; outline-offset: -2px; position: relative; }` : "", b = `
35
+ a(() => (window.addEventListener("message", d), () => window.removeEventListener("message", d)), [d]);
36
+ const c = n.selectedBlockId ? `.block-${n.selectedBlockId} { outline: 2px solid #6366f1 !important; outline-offset: -2px; position: relative; }` : "", p = `
35
37
  (function() {
36
38
  document.addEventListener('click', function(e) {
37
39
  e.preventDefault();
@@ -64,11 +66,11 @@ function S({
64
66
  if (o.current) {
65
67
  const e = o.current.contentDocument;
66
68
  if (e) {
67
- e.open(), e.write(u), e.close();
69
+ e.open(), e.write(b), e.close();
68
70
  const t = e.createElement("script");
69
- t.textContent = b, e.body.appendChild(t);
70
- const f = e.createElement("style");
71
- f.textContent = `
71
+ i && (t.nonce = i), t.textContent = p, e.body.appendChild(t);
72
+ const h = e.createElement("style");
73
+ h.textContent = `
72
74
  [class*="block-"] {
73
75
  cursor: pointer;
74
76
  transition: outline 0.15s ease;
@@ -78,10 +80,10 @@ function S({
78
80
  outline-offset: -1px;
79
81
  }
80
82
  ${c}
81
- `, e.head.appendChild(f);
83
+ `, e.head.appendChild(h);
82
84
  }
83
85
  }
84
- }, [u, c, b]), a(() => {
86
+ }, [b, c, p, i]), a(() => {
85
87
  if (o.current) {
86
88
  const e = o.current.contentDocument;
87
89
  if (e) {
@@ -89,36 +91,36 @@ function S({
89
91
  t || (t = e.createElement("style"), t.id = "selection-highlight", e.head.appendChild(t)), t.textContent = c;
90
92
  }
91
93
  }
92
- }, [r.selectedBlockId, c]), /* @__PURE__ */ n("div", { className: "flex flex-col h-full", children: [
93
- p && /* @__PURE__ */ n("div", { className: "h-11 px-4 flex items-center justify-between border-b border-border bg-background", children: [
94
- /* @__PURE__ */ s("span", { className: "text-sm font-semibold text-foreground", children: "Preview" }),
95
- l.length > 0 && /* @__PURE__ */ n("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-md", children: [
96
- l.length,
94
+ }, [n.selectedBlockId, c]), /* @__PURE__ */ r("div", { className: "flex flex-col h-full", children: [
95
+ x && /* @__PURE__ */ r("div", { className: "h-11 px-4 flex items-center justify-between border-b border-border bg-background", children: [
96
+ /* @__PURE__ */ l("span", { className: "text-sm font-semibold text-foreground", children: "Preview" }),
97
+ s.length > 0 && /* @__PURE__ */ r("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-md", children: [
98
+ s.length,
97
99
  " warning",
98
- l.length !== 1 ? "s" : ""
100
+ s.length !== 1 ? "s" : ""
99
101
  ] })
100
102
  ] }),
101
- /* @__PURE__ */ s("div", { className: "flex-1 overflow-auto bg-muted flex justify-center", children: /* @__PURE__ */ s(
103
+ /* @__PURE__ */ l("div", { className: "flex-1 overflow-auto bg-muted flex justify-center", children: /* @__PURE__ */ l(
102
104
  "iframe",
103
105
  {
104
106
  ref: o,
105
107
  title: "Email Preview",
106
108
  className: "h-full border-0 bg-white transition-all duration-200",
107
109
  style: {
108
- width: g === "mobile" ? I : "100%",
110
+ width: k === "mobile" ? B : "100%",
109
111
  maxWidth: "100%"
110
112
  },
111
113
  sandbox: "allow-same-origin allow-scripts"
112
114
  }
113
115
  ) }),
114
- l.length > 0 && /* @__PURE__ */ n("div", { className: "max-h-28 overflow-auto border-t border-border bg-amber-50/50 dark:bg-amber-900/20 p-3", children: [
115
- /* @__PURE__ */ s("div", { className: "text-xs font-semibold text-amber-700 dark:text-amber-300 mb-2", children: "Warnings" }),
116
- /* @__PURE__ */ s("div", { className: "space-y-1", children: l.map((e, t) => /* @__PURE__ */ n(
116
+ s.length > 0 && /* @__PURE__ */ r("div", { className: "max-h-28 overflow-auto border-t border-border bg-amber-50/50 dark:bg-amber-900/20 p-3", children: [
117
+ /* @__PURE__ */ l("div", { className: "text-xs font-semibold text-amber-700 dark:text-amber-300 mb-2", children: "Warnings" }),
118
+ /* @__PURE__ */ l("div", { className: "space-y-1", children: s.map((e, t) => /* @__PURE__ */ r(
117
119
  "div",
118
120
  {
119
121
  className: "text-xs text-amber-600 dark:text-amber-400",
120
122
  children: [
121
- /* @__PURE__ */ n("span", { className: "font-mono", children: [
123
+ /* @__PURE__ */ r("span", { className: "font-mono", children: [
122
124
  "Line ",
123
125
  e.line,
124
126
  ":"
@@ -133,5 +135,5 @@ function S({
133
135
  ] });
134
136
  }
135
137
  export {
136
- S as InteractivePreview
138
+ W as InteractivePreview
137
139
  };
@@ -3,6 +3,12 @@ import { LiquidSchema } from '../../types/liquid';
3
3
  interface MjmlEditorProps {
4
4
  value: string;
5
5
  onChange: (mjml: string) => void;
6
+ /**
7
+ * URL to POST raw MJML content to for server-side rendering.
8
+ * The endpoint should accept a POST with `Content-Type: text/plain` body
9
+ * and return JSON: `{ html: string, errors: Array<{ line: number, message: string, tagName: string }> }`
10
+ */
11
+ renderEndpoint: string;
6
12
  className?: string;
7
13
  defaultTheme?: 'light' | 'dark' | 'system';
8
14
  liquidSchema?: LiquidSchema;
@@ -48,7 +54,13 @@ interface MjmlEditorProps {
48
54
  * @default false
49
55
  */
50
56
  defaultRightPanelOpen?: boolean;
57
+ /**
58
+ * CSP nonce to apply to dynamically injected script tags.
59
+ * Required when the host application uses a Content Security Policy
60
+ * with `script-src 'nonce-...'`.
61
+ */
62
+ nonce?: string;
51
63
  }
52
- export declare function MjmlEditor({ value, onChange, className, defaultTheme, liquidSchema, extensions, applyThemeToDocument, showThemeToggle, defaultLeftPanelOpen, defaultRightPanelOpen, }: MjmlEditorProps): import("react/jsx-runtime").JSX.Element;
64
+ export declare function MjmlEditor({ value, onChange, renderEndpoint, className, defaultTheme, liquidSchema, extensions, applyThemeToDocument, showThemeToggle, defaultLeftPanelOpen, defaultRightPanelOpen, nonce, }: MjmlEditorProps): import("react/jsx-runtime").JSX.Element;
53
65
  export {};
54
66
  //# sourceMappingURL=MjmlEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MjmlEditor.d.ts","sourceRoot":"","sources":["../../../src/components/editor/MjmlEditor.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAY,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAcnD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC3C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAoKD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAuB,EACvB,YAAY,EACZ,UAAU,EACV,oBAA2B,EAC3B,eAAsB,EACtB,oBAA2B,EAC3B,qBAA6B,GAC9B,EAAE,eAAe,2CA8CjB"}
1
+ {"version":3,"file":"MjmlEditor.d.ts","sourceRoot":"","sources":["../../../src/components/editor/MjmlEditor.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAY,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAcnD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC3C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoKD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,cAAc,EACd,SAAS,EACT,YAAuB,EACvB,YAAY,EACZ,UAAU,EACV,oBAA2B,EAC3B,eAAsB,EACtB,oBAA2B,EAC3B,qBAA6B,EAC7B,KAAK,GACN,EAAE,eAAe,2CAkDjB"}
@@ -1,97 +1,99 @@
1
1
  import { jsx as e, jsxs as w, Fragment as L } from "react/jsx-runtime";
2
- import { useState as a, useEffect as d, useCallback as D, useRef as M } from "react";
2
+ import { useState as a, useEffect as s, useCallback as D, useRef as M } from "react";
3
3
  import { EditorProvider as O, useEditor as j } from "../../context/EditorContext.js";
4
4
  import { ThemeProvider as x, useTheme as C } from "../../context/ThemeContext.js";
5
- import { LiquidSchemaProvider as S } from "../../context/LiquidSchemaContext.js";
6
- import { ExtensionsProvider as K } from "../../context/ExtensionsContext.js";
7
- import { OutlineTree as R, GLOBAL_STYLES_ID as B } from "./OutlineTree.js";
8
- import { EditorCanvas as A } from "./EditorCanvas.js";
9
- import { BlockInspector as F } from "./BlockInspector.js";
10
- import { GlobalStylesPanel as N } from "./GlobalStylesPanel.js";
5
+ import { LiquidSchemaProvider as R } from "../../context/LiquidSchemaContext.js";
6
+ import { ExtensionsProvider as S } from "../../context/ExtensionsContext.js";
7
+ import { NonceProvider as K } from "../../context/NonceContext.js";
8
+ import { RenderEndpointProvider as N } from "../../context/RenderEndpointContext.js";
9
+ import { OutlineTree as A, GLOBAL_STYLES_ID as B } from "./OutlineTree.js";
10
+ import { EditorCanvas as F } from "./EditorCanvas.js";
11
+ import { BlockInspector as $ } from "./BlockInspector.js";
12
+ import { GlobalStylesPanel as z } from "./GlobalStylesPanel.js";
11
13
  import { FloatingPanel as I } from "../ui/floating-panel.js";
12
- import { createEmptyDocument as P, parseMjml as $, serializeMjml as z } from "../../lib/mjml/parser.js";
13
- function G(n) {
14
+ import { createEmptyDocument as b, parseMjml as G, serializeMjml as H } from "../../lib/mjml/parser.js";
15
+ function _(n) {
14
16
  if (!n || n.trim() === "")
15
- return P();
17
+ return b();
16
18
  try {
17
- return $(n);
19
+ return G(n);
18
20
  } catch (o) {
19
- return console.error("Failed to parse MJML:", o), P();
21
+ return console.error("Failed to parse MJML:", o), b();
20
22
  }
21
23
  }
22
- function H({
24
+ function q({
23
25
  className: n,
24
26
  children: o
25
27
  }) {
26
- const { resolvedTheme: l } = C();
28
+ const { resolvedTheme: i } = C();
27
29
  return /* @__PURE__ */ e(
28
30
  "div",
29
31
  {
30
- className: `mjml-editor ${l} relative h-full w-full overflow-hidden bg-background text-foreground antialiased ${n || ""}`,
32
+ className: `mjml-editor ${i} relative h-full w-full overflow-hidden bg-background text-foreground antialiased ${n || ""}`,
31
33
  children: o
32
34
  }
33
35
  );
34
36
  }
35
- function _({
37
+ function J({
36
38
  onChange: n,
37
39
  showThemeToggle: o = !0,
38
- defaultLeftPanelOpen: l = !0,
39
- defaultRightPanelOpen: T = !1
40
+ defaultLeftPanelOpen: i = !0,
41
+ defaultRightPanelOpen: m = !1
40
42
  }) {
41
- const { state: r, undo: u, redo: f, canUndo: m, canRedo: p, deleteBlock: h, selectBlock: g } = j(), [s, k] = a(l), [c, i] = a(T), [E, b] = a("edit"), y = M(n);
42
- return d(() => {
43
- y.current = n;
44
- }, [n]), d(() => {
45
- r.selectedBlockId && i(!0);
46
- }, [r.selectedBlockId]), d(() => {
47
- const v = z(r.document);
48
- y.current(v);
49
- }, [r.document]), d(() => {
50
- const v = (t) => {
43
+ const { state: r, undo: f, redo: u, canUndo: p, canRedo: h, deleteBlock: g, selectBlock: v } = j(), [d, k] = a(i), [c, l] = a(m), [T, y] = a("edit"), P = M(n);
44
+ return s(() => {
45
+ P.current = n;
46
+ }, [n]), s(() => {
47
+ r.selectedBlockId && l(!0);
48
+ }, [r.selectedBlockId]), s(() => {
49
+ const E = H(r.document);
50
+ P.current(E);
51
+ }, [r.document]), s(() => {
52
+ const E = (t) => {
51
53
  if (!(t.target instanceof HTMLInputElement || t.target instanceof HTMLTextAreaElement || t.target?.isContentEditable)) {
52
54
  if ((t.metaKey || t.ctrlKey) && t.key === "z") {
53
- t.preventDefault(), t.shiftKey ? p && f() : m && u();
55
+ t.preventDefault(), t.shiftKey ? h && u() : p && f();
54
56
  return;
55
57
  }
56
58
  if ((t.key === "Delete" || t.key === "Backspace") && r.selectedBlockId && r.selectedBlockId !== B) {
57
- t.preventDefault(), h(r.selectedBlockId);
59
+ t.preventDefault(), g(r.selectedBlockId);
58
60
  return;
59
61
  }
60
62
  if (t.key === "Escape" && r.selectedBlockId) {
61
- t.preventDefault(), g(null);
63
+ t.preventDefault(), v(null);
62
64
  return;
63
65
  }
64
66
  }
65
67
  };
66
- return window.addEventListener("keydown", v), () => window.removeEventListener("keydown", v);
68
+ return window.addEventListener("keydown", E), () => window.removeEventListener("keydown", E);
67
69
  }, [
68
- u,
69
70
  f,
70
- m,
71
+ u,
71
72
  p,
72
73
  h,
73
74
  g,
75
+ v,
74
76
  r.selectedBlockId
75
77
  ]), /* @__PURE__ */ w("div", { className: "relative h-full overflow-hidden", children: [
76
78
  /* @__PURE__ */ e("div", { className: "absolute inset-0 bg-canvas", children: /* @__PURE__ */ e(
77
- A,
79
+ F,
78
80
  {
79
- activeTab: E,
80
- onTabChange: b,
81
- leftPanelOpen: s,
81
+ activeTab: T,
82
+ onTabChange: y,
83
+ leftPanelOpen: d,
82
84
  rightPanelOpen: c,
83
85
  showThemeToggle: o
84
86
  }
85
87
  ) }),
86
- E === "edit" && /* @__PURE__ */ w(L, { children: [
88
+ T === "edit" && /* @__PURE__ */ w(L, { children: [
87
89
  /* @__PURE__ */ e(
88
90
  I,
89
91
  {
90
92
  side: "left",
91
- isOpen: s,
92
- onToggle: () => k(!s),
93
+ isOpen: d,
94
+ onToggle: () => k(!d),
93
95
  width: 256,
94
- children: /* @__PURE__ */ e(R, { onTogglePanel: () => k(!1) })
96
+ children: /* @__PURE__ */ e(A, { onTogglePanel: () => k(!1) })
95
97
  }
96
98
  ),
97
99
  /* @__PURE__ */ e(
@@ -99,58 +101,60 @@ function _({
99
101
  {
100
102
  side: "right",
101
103
  isOpen: c,
102
- onToggle: () => i(!c),
104
+ onToggle: () => l(!c),
103
105
  width: 300,
104
106
  children: r.selectedBlockId === B ? /* @__PURE__ */ e(
105
- N,
107
+ z,
106
108
  {
107
- onTogglePanel: () => i(!1)
109
+ onTogglePanel: () => l(!1)
108
110
  }
109
- ) : /* @__PURE__ */ e(F, { onTogglePanel: () => i(!1) })
111
+ ) : /* @__PURE__ */ e($, { onTogglePanel: () => l(!1) })
110
112
  }
111
113
  )
112
114
  ] })
113
115
  ] });
114
116
  }
115
- function ne({
117
+ function de({
116
118
  value: n,
117
119
  onChange: o,
118
- className: l,
119
- defaultTheme: T = "system",
120
- liquidSchema: r,
120
+ renderEndpoint: i,
121
+ className: m,
122
+ defaultTheme: r = "system",
123
+ liquidSchema: f,
121
124
  extensions: u,
122
- applyThemeToDocument: f = !0,
123
- showThemeToggle: m = !0,
124
- defaultLeftPanelOpen: p = !0,
125
- defaultRightPanelOpen: h = !1
125
+ applyThemeToDocument: p = !0,
126
+ showThemeToggle: h = !0,
127
+ defaultLeftPanelOpen: g = !0,
128
+ defaultRightPanelOpen: v = !1,
129
+ nonce: d
126
130
  }) {
127
- const [g, s] = a(!1);
128
- d(() => {
129
- s(!0);
131
+ const [k, c] = a(!1);
132
+ s(() => {
133
+ c(!0);
130
134
  }, []);
131
- const [k] = a(() => G(n)), c = D(
132
- (i) => {
133
- o(i);
135
+ const [l] = a(() => _(n)), T = D(
136
+ (y) => {
137
+ o(y);
134
138
  },
135
139
  [o]
136
140
  );
137
- return g ? /* @__PURE__ */ e(
141
+ return k ? /* @__PURE__ */ e(
138
142
  x,
139
143
  {
140
- defaultTheme: T,
141
- applyToDocument: f,
142
- children: /* @__PURE__ */ e(K, { extensions: u, children: /* @__PURE__ */ e(S, { schema: r, children: /* @__PURE__ */ e(H, { className: l, children: /* @__PURE__ */ e(O, { initialDocument: k, children: /* @__PURE__ */ e(
143
- _,
144
+ defaultTheme: r,
145
+ applyToDocument: p,
146
+ children: /* @__PURE__ */ e(N, { renderEndpoint: i, children: /* @__PURE__ */ e(K, { nonce: d, children: /* @__PURE__ */ e(S, { extensions: u, children: /* @__PURE__ */ e(R, { schema: f, children: /* @__PURE__ */ e(q, { className: m, children: /* @__PURE__ */ e(O, { initialDocument: l, children: /* @__PURE__ */ e(
147
+ J,
144
148
  {
145
- onChange: c,
146
- showThemeToggle: m,
147
- defaultLeftPanelOpen: p,
148
- defaultRightPanelOpen: h
149
+ onChange: T,
150
+ showThemeToggle: h,
151
+ defaultLeftPanelOpen: g,
152
+ defaultRightPanelOpen: v
149
153
  }
150
- ) }) }) }) })
154
+ ) }) }) }) }) }) })
151
155
  }
152
- ) : /* @__PURE__ */ e("div", { className: `h-full w-full bg-background ${l || ""}` });
156
+ ) : /* @__PURE__ */ e("div", { className: `h-full w-full bg-background ${m || ""}` });
153
157
  }
154
158
  export {
155
- ne as MjmlEditor
159
+ de as MjmlEditor
156
160
  };
@@ -1 +1 @@
1
- {"version":3,"file":"SourcePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/SourcePreview.tsx"],"names":[],"mappings":"AAGA,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,UAAgB,GACjB,EAAE,kBAAkB,2CAqFpB"}
1
+ {"version":3,"file":"SourcePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/SourcePreview.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,UAAgB,GACjB,EAAE,kBAAkB,2CAsFpB"}
@@ -1,38 +1,39 @@
1
1
  import { jsxs as r, jsx as n } from "react/jsx-runtime";
2
- import { useRef as x, useState as b, useEffect as i } from "react";
3
- import { renderMjmlString as h } from "../../lib/mjml/renderer.js";
4
- function w({
2
+ import { useRef as h, useState as f, useEffect as i } from "react";
3
+ import { renderMjmlString as g } from "../../lib/mjml/renderer.js";
4
+ import { useRenderEndpoint as p } from "../../context/RenderEndpointContext.js";
5
+ function R({
5
6
  mjmlSource: s,
6
- debounceMs: m = 300
7
+ debounceMs: d = 300
7
8
  }) {
8
- const a = x(null), [l, f] = b(s), [u, d] = b({
9
+ const m = p(), o = h(null), [a, u] = f(s), [x, c] = f({
9
10
  html: "",
10
11
  errors: []
11
12
  });
12
13
  i(() => {
13
14
  const e = setTimeout(() => {
14
- f(s);
15
- }, m);
15
+ u(s);
16
+ }, d);
16
17
  return () => clearTimeout(e);
17
- }, [s, m]), i(() => {
18
- if (!l.trim()) {
19
- d({ html: "", errors: [] });
18
+ }, [s, d]), i(() => {
19
+ if (!a.trim()) {
20
+ c({ html: "", errors: [] });
20
21
  return;
21
22
  }
22
23
  let e = !1;
23
- return h(l).then((o) => {
24
- e || d(o);
24
+ return g(a, m).then((l) => {
25
+ e || c(l);
25
26
  }), () => {
26
27
  e = !0;
27
28
  };
28
- }, [l]);
29
- const { html: c, errors: t } = u;
29
+ }, [a, m]);
30
+ const { html: b, errors: t } = x;
30
31
  return i(() => {
31
- if (a.current) {
32
- const e = a.current.contentDocument;
33
- e && (e.open(), e.write(c), e.close());
32
+ if (o.current) {
33
+ const e = o.current.contentDocument;
34
+ e && (e.open(), e.write(b), e.close());
34
35
  }
35
- }, [c]), /* @__PURE__ */ r("div", { className: "flex flex-col h-full", children: [
36
+ }, [b]), /* @__PURE__ */ r("div", { className: "flex flex-col h-full", children: [
36
37
  /* @__PURE__ */ r("div", { className: "h-11 px-4 flex items-center justify-between border-b border-border bg-background", children: [
37
38
  /* @__PURE__ */ n("span", { className: "text-sm font-semibold text-foreground", children: "Preview" }),
38
39
  t.length > 0 && /* @__PURE__ */ r("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-md", children: [
@@ -44,7 +45,7 @@ function w({
44
45
  /* @__PURE__ */ n("div", { className: "flex-1 overflow-auto bg-muted", children: /* @__PURE__ */ n(
45
46
  "iframe",
46
47
  {
47
- ref: a,
48
+ ref: o,
48
49
  title: "Source Preview",
49
50
  className: "w-full h-full border-0 bg-white",
50
51
  sandbox: "allow-same-origin"
@@ -52,7 +53,7 @@ function w({
52
53
  ) }),
53
54
  t.length > 0 && /* @__PURE__ */ r("div", { className: "max-h-28 overflow-auto border-t border-border bg-amber-50/50 dark:bg-amber-900/20 p-3", children: [
54
55
  /* @__PURE__ */ n("div", { className: "text-xs font-semibold text-amber-700 dark:text-amber-300 mb-2", children: "Warnings" }),
55
- /* @__PURE__ */ n("div", { className: "space-y-1", children: t.map((e, o) => /* @__PURE__ */ r(
56
+ /* @__PURE__ */ n("div", { className: "space-y-1", children: t.map((e, l) => /* @__PURE__ */ r(
56
57
  "div",
57
58
  {
58
59
  className: "text-xs text-amber-600 dark:text-amber-400",
@@ -66,11 +67,11 @@ function w({
66
67
  e.message
67
68
  ]
68
69
  },
69
- o
70
+ l
70
71
  )) })
71
72
  ] })
72
73
  ] });
73
74
  }
74
75
  export {
75
- w as SourcePreview
76
+ R as SourcePreview
76
77
  };
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ interface NonceProviderProps {
3
+ children: ReactNode;
4
+ nonce?: string;
5
+ }
6
+ export declare function NonceProvider({ children, nonce }: NonceProviderProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function useNonce(): string | undefined;
8
+ export {};
9
+ //# sourceMappingURL=NonceContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NonceContext.d.ts","sourceRoot":"","sources":["../../src/context/NonceContext.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,kBAAkB,2CAIpE;AAED,wBAAgB,QAAQ,uBAEvB"}
@@ -0,0 +1,13 @@
1
+ import { jsx as r } from "react/jsx-runtime";
2
+ import { useContext as n, createContext as c } from "react";
3
+ const o = c(void 0);
4
+ function x({ children: e, nonce: t }) {
5
+ return /* @__PURE__ */ r(o.Provider, { value: t, children: e });
6
+ }
7
+ function f() {
8
+ return n(o);
9
+ }
10
+ export {
11
+ x as NonceProvider,
12
+ f as useNonce
13
+ };
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ interface RenderEndpointProviderProps {
3
+ children: ReactNode;
4
+ renderEndpoint: string;
5
+ }
6
+ export declare function RenderEndpointProvider({ children, renderEndpoint, }: RenderEndpointProviderProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function useRenderEndpoint(): string;
8
+ export {};
9
+ //# sourceMappingURL=RenderEndpointContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderEndpointContext.d.ts","sourceRoot":"","sources":["../../src/context/RenderEndpointContext.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,UAAU,2BAA2B;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,cAAc,GACf,EAAE,2BAA2B,2CAM7B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C"}
@@ -0,0 +1,21 @@
1
+ import { jsx as r } from "react/jsx-runtime";
2
+ import { useContext as t, createContext as d } from "react";
3
+ const e = d(void 0);
4
+ function u({
5
+ children: n,
6
+ renderEndpoint: o
7
+ }) {
8
+ return /* @__PURE__ */ r(e.Provider, { value: o, children: n });
9
+ }
10
+ function s() {
11
+ const n = t(e);
12
+ if (!n)
13
+ throw new Error(
14
+ "useRenderEndpoint must be used within MjmlEditor (no renderEndpoint provided)"
15
+ );
16
+ return n;
17
+ }
18
+ export {
19
+ u as RenderEndpointProvider,
20
+ s as useRenderEndpoint
21
+ };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { MjmlEditor } from './components/editor/MjmlEditor';
2
2
  export { ThemeProvider, useTheme } from './context/ThemeContext';
3
3
  export { ThemeToggle } from './components/ui/theme-toggle';
4
+ export { useNonce } from './context/NonceContext';
5
+ export { useRenderEndpoint } from './context/RenderEndpointContext';
4
6
  export type { MjmlNode, MjmlTagName, ContentBlockType, EditorState, EditorAction, EditorExtensions, } from './types/mjml';
5
7
  export type { LiquidSchema, LiquidSchemaItem } from './types/liquid';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,YAAY,EACV,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGlD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,YAAY,EACV,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import { MjmlEditor as r } from "./components/editor/MjmlEditor.js";
2
- import { ThemeProvider as t, useTheme as T } from "./context/ThemeContext.js";
3
- import { ThemeToggle as h } from "./components/ui/theme-toggle.js";
2
+ import { ThemeProvider as t, useTheme as p } from "./context/ThemeContext.js";
3
+ import { ThemeToggle as x } from "./components/ui/theme-toggle.js";
4
+ import { useNonce as n } from "./context/NonceContext.js";
5
+ import { useRenderEndpoint as h } from "./context/RenderEndpointContext.js";
4
6
  export {
5
7
  r as MjmlEditor,
6
8
  t as ThemeProvider,
7
- h as ThemeToggle,
8
- T as useTheme
9
+ x as ThemeToggle,
10
+ n as useNonce,
11
+ h as useRenderEndpoint,
12
+ p as useTheme
9
13
  };
@@ -8,19 +8,19 @@ export interface RenderResult {
8
8
  }[];
9
9
  }
10
10
  /**
11
- * Render MJML JSON to HTML.
12
- * Custom attributes (like sc-if) are stripped to prevent mjml-browser warnings.
11
+ * Render MJML JSON to HTML via the render endpoint.
12
+ * Custom attributes (like sc-if) are stripped to prevent warnings.
13
13
  */
14
- export declare function renderMjml(document: MjmlNode): Promise<RenderResult>;
14
+ export declare function renderMjml(document: MjmlNode, renderEndpoint: string): Promise<RenderResult>;
15
15
  /**
16
- * Render MJML string to HTML.
17
- * Custom attributes (like sc-if) are stripped to prevent mjml-browser warnings.
16
+ * Render MJML string to HTML via the render endpoint.
17
+ * Custom attributes (like sc-if) are stripped to prevent warnings.
18
18
  */
19
- export declare function renderMjmlString(mjmlString: string): Promise<RenderResult>;
19
+ export declare function renderMjmlString(mjmlString: string, renderEndpoint: string): Promise<RenderResult>;
20
20
  /**
21
21
  * Render MJML JSON to HTML with block IDs preserved as CSS classes.
22
22
  * This allows clicking elements in the preview to identify the source block.
23
- * Custom attributes (like sc-if) are stripped to prevent mjml-browser warnings.
23
+ * Custom attributes (like sc-if) are stripped to prevent warnings.
24
24
  */
25
- export declare function renderMjmlInteractive(document: MjmlNode): Promise<RenderResult>;
25
+ export declare function renderMjmlInteractive(document: MjmlNode, renderEndpoint: string): Promise<RenderResult>;
26
26
  //# sourceMappingURL=renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/lib/mjml/renderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC9D;AAqHD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAqB1E;AAkBD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAoBvB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/lib/mjml/renderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC9D;AAuHD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,YAAY,CAAC,CAYvB;AAkBD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,YAAY,CAAC,CAavB"}
@@ -1,73 +1,71 @@
1
1
  import "uuid";
2
- const u = ["sc-if"];
3
- function p(t) {
4
- const r = Object.fromEntries(
2
+ const p = ["sc-if"];
3
+ function u(t) {
4
+ const e = Object.fromEntries(
5
5
  Object.entries(t.attributes || {}).filter(
6
- ([e]) => !u.includes(e)
6
+ ([r]) => !p.includes(r)
7
7
  )
8
8
  );
9
9
  return {
10
10
  ...t,
11
- attributes: r,
12
- children: t.children?.map(p)
11
+ attributes: e,
12
+ children: t.children?.map(u)
13
13
  };
14
14
  }
15
- let o = null;
16
- async function g() {
17
- return o || (o = (await import("mjml-browser")).default), o;
15
+ async function m(t, e) {
16
+ const r = await fetch(t, {
17
+ method: "POST",
18
+ headers: { "Content-Type": "text/plain" },
19
+ body: e
20
+ });
21
+ if (!r.ok)
22
+ throw new Error(`Render endpoint returned ${r.status}`);
23
+ return r.json();
18
24
  }
19
- function m(t) {
25
+ function l(t) {
20
26
  if (!t._id || !t.tagName.startsWith("mj-"))
21
27
  return {
22
28
  ...t,
23
- children: t.children?.map(m)
29
+ children: t.children?.map(l)
24
30
  };
25
- const r = t.attributes["mj-class"] || "", e = `block-${t._id}`, s = r ? `${r} ${e}` : e;
31
+ const e = t.attributes["mj-class"] || "", r = `block-${t._id}`, n = e ? `${e} ${r}` : r;
26
32
  return {
27
33
  ...t,
28
- attributes: { ...t.attributes, "mj-class": s },
29
- children: t.children?.map(m)
34
+ attributes: { ...t.attributes, "mj-class": n },
35
+ children: t.children?.map(l)
30
36
  };
31
37
  }
32
- function d(t) {
33
- return h(t);
34
- }
35
- function h(t, r = 0) {
36
- const e = " ".repeat(r), s = t.tagName, l = Object.entries(t.attributes || {}).filter(
37
- ([c, i]) => c !== "_id" && i !== "" && i !== void 0
38
- ).map(([c, i]) => `${c}="${f(i)}"`).join(" "), n = l ? `<${s} ${l}>` : `<${s}>`, a = `</${s}>`;
38
+ function g(t, e = 0) {
39
+ const r = " ".repeat(e), n = t.tagName, i = Object.entries(t.attributes || {}).filter(
40
+ ([c, s]) => c !== "_id" && s !== "" && s !== void 0
41
+ ).map(([c, s]) => `${c}="${d(s)}"`).join(" "), a = i ? `<${n} ${i}>` : `<${n}>`, o = `</${n}>`;
39
42
  if (t.content !== void 0)
40
- return `${e}${n}${t.content}${a}`;
43
+ return `${r}${a}${t.content}${o}`;
41
44
  if (t.children && t.children.length > 0) {
42
- const c = t.children.map((i) => h(i, r + 1)).join(`
45
+ const c = t.children.map((s) => g(s, e + 1)).join(`
43
46
  `);
44
- return `${e}${n}
47
+ return `${r}${a}
45
48
  ${c}
46
- ${e}${a}`;
49
+ ${r}${o}`;
47
50
  }
48
- return `${e}${n}${a}`;
51
+ return `${r}${a}${o}`;
49
52
  }
50
- function f(t) {
53
+ function d(t) {
51
54
  return t.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52
55
  }
53
- function $(t) {
54
- for (const r of u) {
55
- const e = new RegExp(`\\s+${r}="[^"]*"`, "g");
56
- t = t.replace(e, "");
57
- const s = new RegExp(`\\s+${r}='[^']*'`, "g");
58
- t = t.replace(s, "");
56
+ function h(t) {
57
+ for (const e of p) {
58
+ const r = new RegExp(`\\s+${e}="[^"]*"`, "g");
59
+ t = t.replace(r, "");
60
+ const n = new RegExp(`\\s+${e}='[^']*'`, "g");
61
+ t = t.replace(n, "");
59
62
  }
60
63
  return t;
61
64
  }
62
- async function b(t) {
65
+ async function f(t, e) {
63
66
  try {
64
- const r = await g(), e = $(t), s = r(e, {
65
- validationLevel: "soft"
66
- });
67
- return {
68
- html: s.html,
69
- errors: s.errors || []
70
- };
67
+ const r = h(t);
68
+ return await m(e, r);
71
69
  } catch (r) {
72
70
  return console.error("MJML render error:", r), {
73
71
  html: '<p style="color: #dc2626; padding: 20px; background: white; margin: 0;">Error rendering email preview</p>',
@@ -75,15 +73,10 @@ async function b(t) {
75
73
  };
76
74
  }
77
75
  }
78
- async function M(t) {
76
+ async function b(t, e) {
79
77
  try {
80
- const r = await g(), e = p(t), s = m(e), l = d(s), n = r(l, {
81
- validationLevel: "soft"
82
- });
83
- return {
84
- html: n.html,
85
- errors: n.errors || []
86
- };
78
+ const r = u(t), n = l(r), i = g(n);
79
+ return await m(e, i);
87
80
  } catch (r) {
88
81
  return console.error("MJML render error:", r), {
89
82
  html: '<p style="color: #dc2626; padding: 20px; background: white; margin: 0;">Error rendering email preview</p>',
@@ -92,6 +85,6 @@ async function M(t) {
92
85
  }
93
86
  }
94
87
  export {
95
- M as renderMjmlInteractive,
96
- b as renderMjmlString
88
+ b as renderMjmlInteractive,
89
+ f as renderMjmlString
97
90
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvycal/mjml-editor",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,7 +62,6 @@
62
62
  "class-variance-authority": "^0.7.1",
63
63
  "clsx": "^2.1.1",
64
64
  "lucide-react": "^0.562.0",
65
- "mjml-browser": "^4.18.0",
66
65
  "react-arborist": "^3.4.3",
67
66
  "tailwind-merge": "^3.4.0",
68
67
  "uuid": "^13.0.0"