@json-render/react-email 0.8.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.
- package/LICENSE +201 -0
- package/README.md +147 -0
- package/dist/catalog-types-Dh9gul8V.d.mts +17 -0
- package/dist/catalog-types-Dh9gul8V.d.ts +17 -0
- package/dist/catalog.d.mts +216 -0
- package/dist/catalog.d.ts +216 -0
- package/dist/catalog.js +203 -0
- package/dist/catalog.js.map +1 -0
- package/dist/catalog.mjs +7 -0
- package/dist/catalog.mjs.map +1 -0
- package/dist/chunk-3YN5NZJ7.mjs +44 -0
- package/dist/chunk-3YN5NZJ7.mjs.map +1 -0
- package/dist/chunk-GXJW4OIL.mjs +179 -0
- package/dist/chunk-GXJW4OIL.mjs.map +1 -0
- package/dist/chunk-MPWIHNXF.mjs +253 -0
- package/dist/chunk-MPWIHNXF.mjs.map +1 -0
- package/dist/index.d.mts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +1298 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +847 -0
- package/dist/index.mjs.map +1 -0
- package/dist/render-DFHuQRW0.d.ts +80 -0
- package/dist/render-kNHi7tZ2.d.mts +80 -0
- package/dist/render.d.mts +5 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.js +259 -0
- package/dist/render.js.map +1 -0
- package/dist/render.mjs +11 -0
- package/dist/render.mjs.map +1 -0
- package/dist/server.d.mts +43 -0
- package/dist/server.d.ts +43 -0
- package/dist/server.js +247 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +11 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +78 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ActionProvider: () => ActionProvider,
|
|
34
|
+
ConfirmDialog: () => ConfirmDialog,
|
|
35
|
+
JSONUIProvider: () => JSONUIProvider,
|
|
36
|
+
Renderer: () => Renderer,
|
|
37
|
+
RepeatScopeProvider: () => RepeatScopeProvider,
|
|
38
|
+
StateProvider: () => StateProvider,
|
|
39
|
+
ValidationProvider: () => ValidationProvider,
|
|
40
|
+
VisibilityProvider: () => VisibilityProvider,
|
|
41
|
+
createRenderer: () => createRenderer,
|
|
42
|
+
defineRegistry: () => defineRegistry,
|
|
43
|
+
renderToHtml: () => renderToHtml,
|
|
44
|
+
renderToPlainText: () => renderToPlainText,
|
|
45
|
+
schema: () => schema,
|
|
46
|
+
standardComponentDefinitions: () => standardComponentDefinitions,
|
|
47
|
+
standardComponents: () => standardComponents,
|
|
48
|
+
useAction: () => useAction,
|
|
49
|
+
useActions: () => useActions,
|
|
50
|
+
useFieldValidation: () => useFieldValidation,
|
|
51
|
+
useIsVisible: () => useIsVisible,
|
|
52
|
+
useRepeatScope: () => useRepeatScope,
|
|
53
|
+
useStateBinding: () => useStateBinding,
|
|
54
|
+
useStateStore: () => useStateStore,
|
|
55
|
+
useStateValue: () => useStateValue,
|
|
56
|
+
useValidation: () => useValidation,
|
|
57
|
+
useVisibility: () => useVisibility
|
|
58
|
+
});
|
|
59
|
+
module.exports = __toCommonJS(index_exports);
|
|
60
|
+
|
|
61
|
+
// src/schema.ts
|
|
62
|
+
var import_core = require("@json-render/core");
|
|
63
|
+
var schema = (0, import_core.defineSchema)(
|
|
64
|
+
(s) => ({
|
|
65
|
+
spec: s.object({
|
|
66
|
+
root: s.string(),
|
|
67
|
+
elements: s.record(
|
|
68
|
+
s.object({
|
|
69
|
+
type: s.ref("catalog.components"),
|
|
70
|
+
props: s.propsOf("catalog.components"),
|
|
71
|
+
children: s.array(s.string()),
|
|
72
|
+
visible: s.any()
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
}),
|
|
76
|
+
catalog: s.object({
|
|
77
|
+
components: s.map({
|
|
78
|
+
props: s.zod(),
|
|
79
|
+
slots: s.array(s.string()),
|
|
80
|
+
description: s.string(),
|
|
81
|
+
example: s.any()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
}),
|
|
85
|
+
{
|
|
86
|
+
defaultRules: [
|
|
87
|
+
"The root element MUST be an Html component. Its children MUST include Head and Body components.",
|
|
88
|
+
"Body should contain a Container component to constrain width (typically 600px max for email clients).",
|
|
89
|
+
"All styles MUST be inline. Email clients strip <style> tags, so every component that accepts a style prop should use it for visual customization.",
|
|
90
|
+
"Image src must be a fully qualified URL (absolute, not relative). For placeholder images use https://picsum.photos/{width}/{height}?random={n}.",
|
|
91
|
+
"Emails are static documents. There are no interactive actions or form inputs.",
|
|
92
|
+
"Use Section, Row, and Column for layout. These map to table-based email structures for maximum compatibility.",
|
|
93
|
+
"Use Preview to set the preview text shown in email client inboxes before opening.",
|
|
94
|
+
"Use Heading (h1-h6) and Text for all text content. Raw strings are not supported.",
|
|
95
|
+
"Button renders as a link styled as a button. Always provide both text and href.",
|
|
96
|
+
"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist."
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// src/contexts/state.tsx
|
|
102
|
+
var import_react = require("react");
|
|
103
|
+
var import_core2 = require("@json-render/core");
|
|
104
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
105
|
+
var StateContext = (0, import_react.createContext)(null);
|
|
106
|
+
function StateProvider({
|
|
107
|
+
initialState = {},
|
|
108
|
+
onStateChange,
|
|
109
|
+
children
|
|
110
|
+
}) {
|
|
111
|
+
const [state, setStateInternal] = (0, import_react.useState)(initialState);
|
|
112
|
+
const stateRef = (0, import_react.useRef)(state);
|
|
113
|
+
stateRef.current = state;
|
|
114
|
+
const initialStateJsonRef = (0, import_react.useRef)(JSON.stringify(initialState));
|
|
115
|
+
(0, import_react.useEffect)(() => {
|
|
116
|
+
const newJson = JSON.stringify(initialState);
|
|
117
|
+
if (newJson !== initialStateJsonRef.current) {
|
|
118
|
+
initialStateJsonRef.current = newJson;
|
|
119
|
+
if (initialState && Object.keys(initialState).length > 0) {
|
|
120
|
+
setStateInternal((prev) => ({ ...prev, ...initialState }));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}, [initialState]);
|
|
124
|
+
const get = (0, import_react.useCallback)(
|
|
125
|
+
(path) => (0, import_core2.getByPath)(stateRef.current, path),
|
|
126
|
+
[]
|
|
127
|
+
);
|
|
128
|
+
const set = (0, import_react.useCallback)(
|
|
129
|
+
(path, value2) => {
|
|
130
|
+
setStateInternal((prev) => {
|
|
131
|
+
const next = { ...prev };
|
|
132
|
+
(0, import_core2.setByPath)(next, path, value2);
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
onStateChange?.(path, value2);
|
|
136
|
+
},
|
|
137
|
+
[onStateChange]
|
|
138
|
+
);
|
|
139
|
+
const update = (0, import_react.useCallback)(
|
|
140
|
+
(updates) => {
|
|
141
|
+
const entries = Object.entries(updates);
|
|
142
|
+
setStateInternal((prev) => {
|
|
143
|
+
const next = { ...prev };
|
|
144
|
+
for (const [path, value2] of entries) {
|
|
145
|
+
(0, import_core2.setByPath)(next, path, value2);
|
|
146
|
+
}
|
|
147
|
+
return next;
|
|
148
|
+
});
|
|
149
|
+
for (const [path, value2] of entries) {
|
|
150
|
+
onStateChange?.(path, value2);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
[onStateChange]
|
|
154
|
+
);
|
|
155
|
+
const value = (0, import_react.useMemo)(
|
|
156
|
+
() => ({ state, get, set, update }),
|
|
157
|
+
[state, get, set, update]
|
|
158
|
+
);
|
|
159
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StateContext.Provider, { value, children });
|
|
160
|
+
}
|
|
161
|
+
function useStateStore() {
|
|
162
|
+
const ctx = (0, import_react.useContext)(StateContext);
|
|
163
|
+
if (!ctx) {
|
|
164
|
+
throw new Error("useStateStore must be used within a StateProvider");
|
|
165
|
+
}
|
|
166
|
+
return ctx;
|
|
167
|
+
}
|
|
168
|
+
function useStateValue(path) {
|
|
169
|
+
const { state } = useStateStore();
|
|
170
|
+
return (0, import_core2.getByPath)(state, path);
|
|
171
|
+
}
|
|
172
|
+
function useStateBinding(path) {
|
|
173
|
+
const { state, set } = useStateStore();
|
|
174
|
+
const value = (0, import_core2.getByPath)(state, path);
|
|
175
|
+
const setValue = (0, import_react.useCallback)(
|
|
176
|
+
(newValue) => set(path, newValue),
|
|
177
|
+
[path, set]
|
|
178
|
+
);
|
|
179
|
+
return [value, setValue];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/contexts/visibility.tsx
|
|
183
|
+
var import_react2 = require("react");
|
|
184
|
+
var import_core3 = require("@json-render/core");
|
|
185
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
186
|
+
var VisibilityContext = (0, import_react2.createContext)(null);
|
|
187
|
+
function VisibilityProvider({ children }) {
|
|
188
|
+
const { state } = useStateStore();
|
|
189
|
+
const ctx = (0, import_react2.useMemo)(
|
|
190
|
+
() => ({ stateModel: state }),
|
|
191
|
+
[state]
|
|
192
|
+
);
|
|
193
|
+
const isVisible = (0, import_react2.useMemo)(
|
|
194
|
+
() => (condition) => (0, import_core3.evaluateVisibility)(condition, ctx),
|
|
195
|
+
[ctx]
|
|
196
|
+
);
|
|
197
|
+
const value = (0, import_react2.useMemo)(
|
|
198
|
+
() => ({ isVisible, ctx }),
|
|
199
|
+
[isVisible, ctx]
|
|
200
|
+
);
|
|
201
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VisibilityContext.Provider, { value, children });
|
|
202
|
+
}
|
|
203
|
+
function useVisibility() {
|
|
204
|
+
const ctx = (0, import_react2.useContext)(VisibilityContext);
|
|
205
|
+
if (!ctx) {
|
|
206
|
+
throw new Error("useVisibility must be used within a VisibilityProvider");
|
|
207
|
+
}
|
|
208
|
+
return ctx;
|
|
209
|
+
}
|
|
210
|
+
function useIsVisible(condition) {
|
|
211
|
+
const { isVisible } = useVisibility();
|
|
212
|
+
return isVisible(condition);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/contexts/actions.tsx
|
|
216
|
+
var import_react3 = require("react");
|
|
217
|
+
var import_core4 = require("@json-render/core");
|
|
218
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
219
|
+
function generateUniqueId() {
|
|
220
|
+
return crypto.randomUUID();
|
|
221
|
+
}
|
|
222
|
+
function deepResolveValue(value, get) {
|
|
223
|
+
if (value === null || value === void 0) return value;
|
|
224
|
+
if (value === "$id") {
|
|
225
|
+
return generateUniqueId();
|
|
226
|
+
}
|
|
227
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
228
|
+
const obj = value;
|
|
229
|
+
const keys = Object.keys(obj);
|
|
230
|
+
if (keys.length === 1 && typeof obj.$state === "string") {
|
|
231
|
+
return get(obj.$state);
|
|
232
|
+
}
|
|
233
|
+
if (keys.length === 1 && "$id" in obj) {
|
|
234
|
+
return generateUniqueId();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(value)) {
|
|
238
|
+
return value.map((item) => deepResolveValue(item, get));
|
|
239
|
+
}
|
|
240
|
+
if (typeof value === "object") {
|
|
241
|
+
const resolved = {};
|
|
242
|
+
for (const [key, val] of Object.entries(value)) {
|
|
243
|
+
resolved[key] = deepResolveValue(val, get);
|
|
244
|
+
}
|
|
245
|
+
return resolved;
|
|
246
|
+
}
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
var ActionContext = (0, import_react3.createContext)(null);
|
|
250
|
+
function ActionProvider({
|
|
251
|
+
handlers: initialHandlers = {},
|
|
252
|
+
navigate,
|
|
253
|
+
children
|
|
254
|
+
}) {
|
|
255
|
+
const { state, get, set } = useStateStore();
|
|
256
|
+
const [handlers, setHandlers] = (0, import_react3.useState)(initialHandlers);
|
|
257
|
+
const [loadingActions, setLoadingActions] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
|
258
|
+
const [pendingConfirmation, setPendingConfirmation] = (0, import_react3.useState)(null);
|
|
259
|
+
const registerHandler = (0, import_react3.useCallback)(
|
|
260
|
+
(name, handler) => {
|
|
261
|
+
setHandlers((prev) => ({ ...prev, [name]: handler }));
|
|
262
|
+
},
|
|
263
|
+
[]
|
|
264
|
+
);
|
|
265
|
+
const execute = (0, import_react3.useCallback)(
|
|
266
|
+
async (binding) => {
|
|
267
|
+
const resolved = (0, import_core4.resolveAction)(binding, state);
|
|
268
|
+
if (resolved.action === "setState" && resolved.params) {
|
|
269
|
+
const statePath = resolved.params.statePath;
|
|
270
|
+
const value2 = resolved.params.value;
|
|
271
|
+
if (statePath) {
|
|
272
|
+
set(statePath, value2);
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (resolved.action === "pushState" && resolved.params) {
|
|
277
|
+
const statePath = resolved.params.statePath;
|
|
278
|
+
const rawValue = resolved.params.value;
|
|
279
|
+
if (statePath) {
|
|
280
|
+
const resolvedValue = deepResolveValue(rawValue, get);
|
|
281
|
+
const arr = get(statePath) ?? [];
|
|
282
|
+
set(statePath, [...arr, resolvedValue]);
|
|
283
|
+
const clearStatePath = resolved.params.clearStatePath;
|
|
284
|
+
if (clearStatePath) {
|
|
285
|
+
set(clearStatePath, "");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (resolved.action === "removeState" && resolved.params) {
|
|
291
|
+
const statePath = resolved.params.statePath;
|
|
292
|
+
const index = resolved.params.index;
|
|
293
|
+
if (statePath !== void 0 && index !== void 0) {
|
|
294
|
+
const arr = get(statePath) ?? [];
|
|
295
|
+
set(
|
|
296
|
+
statePath,
|
|
297
|
+
arr.filter((_, i) => i !== index)
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const handler = handlers[resolved.action];
|
|
303
|
+
if (!handler) {
|
|
304
|
+
console.warn(`No handler registered for action: ${resolved.action}`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (resolved.confirm) {
|
|
308
|
+
return new Promise((resolve, reject) => {
|
|
309
|
+
setPendingConfirmation({
|
|
310
|
+
action: resolved,
|
|
311
|
+
handler,
|
|
312
|
+
resolve: () => {
|
|
313
|
+
setPendingConfirmation(null);
|
|
314
|
+
resolve();
|
|
315
|
+
},
|
|
316
|
+
reject: () => {
|
|
317
|
+
setPendingConfirmation(null);
|
|
318
|
+
reject(new Error("Action cancelled"));
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}).then(async () => {
|
|
322
|
+
setLoadingActions((prev) => new Set(prev).add(resolved.action));
|
|
323
|
+
try {
|
|
324
|
+
await (0, import_core4.executeAction)({
|
|
325
|
+
action: resolved,
|
|
326
|
+
handler,
|
|
327
|
+
setState: set,
|
|
328
|
+
navigate,
|
|
329
|
+
executeAction: async (name) => {
|
|
330
|
+
const subBinding = { action: name };
|
|
331
|
+
await execute(subBinding);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
} finally {
|
|
335
|
+
setLoadingActions((prev) => {
|
|
336
|
+
const next = new Set(prev);
|
|
337
|
+
next.delete(resolved.action);
|
|
338
|
+
return next;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
setLoadingActions((prev) => new Set(prev).add(resolved.action));
|
|
344
|
+
try {
|
|
345
|
+
await (0, import_core4.executeAction)({
|
|
346
|
+
action: resolved,
|
|
347
|
+
handler,
|
|
348
|
+
setState: set,
|
|
349
|
+
navigate,
|
|
350
|
+
executeAction: async (name) => {
|
|
351
|
+
const subBinding = { action: name };
|
|
352
|
+
await execute(subBinding);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
} finally {
|
|
356
|
+
setLoadingActions((prev) => {
|
|
357
|
+
const next = new Set(prev);
|
|
358
|
+
next.delete(resolved.action);
|
|
359
|
+
return next;
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
[state, handlers, get, set, navigate]
|
|
364
|
+
);
|
|
365
|
+
const confirm = (0, import_react3.useCallback)(() => {
|
|
366
|
+
pendingConfirmation?.resolve();
|
|
367
|
+
}, [pendingConfirmation]);
|
|
368
|
+
const cancel = (0, import_react3.useCallback)(() => {
|
|
369
|
+
pendingConfirmation?.reject();
|
|
370
|
+
}, [pendingConfirmation]);
|
|
371
|
+
const value = (0, import_react3.useMemo)(
|
|
372
|
+
() => ({
|
|
373
|
+
handlers,
|
|
374
|
+
loadingActions,
|
|
375
|
+
pendingConfirmation,
|
|
376
|
+
execute,
|
|
377
|
+
confirm,
|
|
378
|
+
cancel,
|
|
379
|
+
registerHandler
|
|
380
|
+
}),
|
|
381
|
+
[
|
|
382
|
+
handlers,
|
|
383
|
+
loadingActions,
|
|
384
|
+
pendingConfirmation,
|
|
385
|
+
execute,
|
|
386
|
+
confirm,
|
|
387
|
+
cancel,
|
|
388
|
+
registerHandler
|
|
389
|
+
]
|
|
390
|
+
);
|
|
391
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionContext.Provider, { value, children });
|
|
392
|
+
}
|
|
393
|
+
function useActions() {
|
|
394
|
+
const ctx = (0, import_react3.useContext)(ActionContext);
|
|
395
|
+
if (!ctx) {
|
|
396
|
+
throw new Error("useActions must be used within an ActionProvider");
|
|
397
|
+
}
|
|
398
|
+
return ctx;
|
|
399
|
+
}
|
|
400
|
+
function useAction(binding) {
|
|
401
|
+
const { execute, loadingActions } = useActions();
|
|
402
|
+
const isLoading = loadingActions.has(binding.action);
|
|
403
|
+
const executeAction2 = (0, import_react3.useCallback)(() => execute(binding), [execute, binding]);
|
|
404
|
+
return { execute: executeAction2, isLoading };
|
|
405
|
+
}
|
|
406
|
+
function ConfirmDialog(_props) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/contexts/validation.tsx
|
|
411
|
+
var import_react4 = __toESM(require("react"));
|
|
412
|
+
var import_core5 = require("@json-render/core");
|
|
413
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
414
|
+
var ValidationContext = (0, import_react4.createContext)(null);
|
|
415
|
+
function dynamicArgsEqual(a, b) {
|
|
416
|
+
if (a === b) return true;
|
|
417
|
+
if (!a || !b) return false;
|
|
418
|
+
const keysA = Object.keys(a);
|
|
419
|
+
const keysB = Object.keys(b);
|
|
420
|
+
if (keysA.length !== keysB.length) return false;
|
|
421
|
+
for (const key of keysA) {
|
|
422
|
+
const va = a[key];
|
|
423
|
+
const vb = b[key];
|
|
424
|
+
if (va === vb) continue;
|
|
425
|
+
if (typeof va === "object" && va !== null && typeof vb === "object" && vb !== null) {
|
|
426
|
+
const sa = va.$state;
|
|
427
|
+
const sb = vb.$state;
|
|
428
|
+
if (typeof sa === "string" && sa === sb) continue;
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
function validationConfigEqual(a, b) {
|
|
435
|
+
if (a === b) return true;
|
|
436
|
+
if (a.validateOn !== b.validateOn) return false;
|
|
437
|
+
const ac = a.checks ?? [];
|
|
438
|
+
const bc = b.checks ?? [];
|
|
439
|
+
if (ac.length !== bc.length) return false;
|
|
440
|
+
for (let i = 0; i < ac.length; i++) {
|
|
441
|
+
const ca = ac[i];
|
|
442
|
+
const cb = bc[i];
|
|
443
|
+
if (ca.type !== cb.type) return false;
|
|
444
|
+
if (ca.message !== cb.message) return false;
|
|
445
|
+
if (!dynamicArgsEqual(ca.args, cb.args)) return false;
|
|
446
|
+
}
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
function ValidationProvider({
|
|
450
|
+
customFunctions = {},
|
|
451
|
+
children
|
|
452
|
+
}) {
|
|
453
|
+
const { state } = useStateStore();
|
|
454
|
+
const [fieldStates, setFieldStates] = (0, import_react4.useState)({});
|
|
455
|
+
const [fieldConfigs, setFieldConfigs] = (0, import_react4.useState)({});
|
|
456
|
+
const registerField = (0, import_react4.useCallback)(
|
|
457
|
+
(path, config) => {
|
|
458
|
+
setFieldConfigs((prev) => {
|
|
459
|
+
const existing = prev[path];
|
|
460
|
+
if (existing && validationConfigEqual(existing, config)) {
|
|
461
|
+
return prev;
|
|
462
|
+
}
|
|
463
|
+
return { ...prev, [path]: config };
|
|
464
|
+
});
|
|
465
|
+
},
|
|
466
|
+
[]
|
|
467
|
+
);
|
|
468
|
+
const validate = (0, import_react4.useCallback)(
|
|
469
|
+
(path, config) => {
|
|
470
|
+
const segments = path.split("/").filter(Boolean);
|
|
471
|
+
let value2 = state;
|
|
472
|
+
for (const seg of segments) {
|
|
473
|
+
if (value2 != null && typeof value2 === "object") {
|
|
474
|
+
value2 = value2[seg];
|
|
475
|
+
} else {
|
|
476
|
+
value2 = void 0;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const result = (0, import_core5.runValidation)(config, {
|
|
481
|
+
value: value2,
|
|
482
|
+
stateModel: state,
|
|
483
|
+
customFunctions
|
|
484
|
+
});
|
|
485
|
+
setFieldStates((prev) => ({
|
|
486
|
+
...prev,
|
|
487
|
+
[path]: {
|
|
488
|
+
touched: prev[path]?.touched ?? true,
|
|
489
|
+
validated: true,
|
|
490
|
+
result
|
|
491
|
+
}
|
|
492
|
+
}));
|
|
493
|
+
return result;
|
|
494
|
+
},
|
|
495
|
+
[state, customFunctions]
|
|
496
|
+
);
|
|
497
|
+
const touch = (0, import_react4.useCallback)((path) => {
|
|
498
|
+
setFieldStates((prev) => ({
|
|
499
|
+
...prev,
|
|
500
|
+
[path]: {
|
|
501
|
+
...prev[path],
|
|
502
|
+
touched: true,
|
|
503
|
+
validated: prev[path]?.validated ?? false,
|
|
504
|
+
result: prev[path]?.result ?? null
|
|
505
|
+
}
|
|
506
|
+
}));
|
|
507
|
+
}, []);
|
|
508
|
+
const clear = (0, import_react4.useCallback)((path) => {
|
|
509
|
+
setFieldStates((prev) => {
|
|
510
|
+
const { [path]: _, ...rest } = prev;
|
|
511
|
+
return rest;
|
|
512
|
+
});
|
|
513
|
+
}, []);
|
|
514
|
+
const validateAll = (0, import_react4.useCallback)(() => {
|
|
515
|
+
let allValid = true;
|
|
516
|
+
for (const [path, config] of Object.entries(fieldConfigs)) {
|
|
517
|
+
const result = validate(path, config);
|
|
518
|
+
if (!result.valid) {
|
|
519
|
+
allValid = false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return allValid;
|
|
523
|
+
}, [fieldConfigs, validate]);
|
|
524
|
+
const value = (0, import_react4.useMemo)(
|
|
525
|
+
() => ({
|
|
526
|
+
customFunctions,
|
|
527
|
+
fieldStates,
|
|
528
|
+
validate,
|
|
529
|
+
touch,
|
|
530
|
+
clear,
|
|
531
|
+
validateAll,
|
|
532
|
+
registerField
|
|
533
|
+
}),
|
|
534
|
+
[
|
|
535
|
+
customFunctions,
|
|
536
|
+
fieldStates,
|
|
537
|
+
validate,
|
|
538
|
+
touch,
|
|
539
|
+
clear,
|
|
540
|
+
validateAll,
|
|
541
|
+
registerField
|
|
542
|
+
]
|
|
543
|
+
);
|
|
544
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ValidationContext.Provider, { value, children });
|
|
545
|
+
}
|
|
546
|
+
function useValidation() {
|
|
547
|
+
const ctx = (0, import_react4.useContext)(ValidationContext);
|
|
548
|
+
if (!ctx) {
|
|
549
|
+
throw new Error("useValidation must be used within a ValidationProvider");
|
|
550
|
+
}
|
|
551
|
+
return ctx;
|
|
552
|
+
}
|
|
553
|
+
function useFieldValidation(path, config) {
|
|
554
|
+
const {
|
|
555
|
+
fieldStates,
|
|
556
|
+
validate: validateField,
|
|
557
|
+
touch: touchField,
|
|
558
|
+
clear: clearField,
|
|
559
|
+
registerField
|
|
560
|
+
} = useValidation();
|
|
561
|
+
import_react4.default.useEffect(() => {
|
|
562
|
+
if (config) {
|
|
563
|
+
registerField(path, config);
|
|
564
|
+
}
|
|
565
|
+
}, [path, config, registerField]);
|
|
566
|
+
const state = fieldStates[path] ?? {
|
|
567
|
+
touched: false,
|
|
568
|
+
validated: false,
|
|
569
|
+
result: null
|
|
570
|
+
};
|
|
571
|
+
const validate = (0, import_react4.useCallback)(
|
|
572
|
+
() => validateField(path, config ?? { checks: [] }),
|
|
573
|
+
[path, config, validateField]
|
|
574
|
+
);
|
|
575
|
+
const touch = (0, import_react4.useCallback)(() => touchField(path), [path, touchField]);
|
|
576
|
+
const clear = (0, import_react4.useCallback)(() => clearField(path), [path, clearField]);
|
|
577
|
+
return {
|
|
578
|
+
state,
|
|
579
|
+
validate,
|
|
580
|
+
touch,
|
|
581
|
+
clear,
|
|
582
|
+
errors: state.result?.errors ?? [],
|
|
583
|
+
isValid: state.result?.valid ?? true
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/contexts/repeat-scope.tsx
|
|
588
|
+
var import_react5 = require("react");
|
|
589
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
590
|
+
var RepeatScopeContext = (0, import_react5.createContext)(null);
|
|
591
|
+
function RepeatScopeProvider({
|
|
592
|
+
item,
|
|
593
|
+
index,
|
|
594
|
+
basePath,
|
|
595
|
+
children
|
|
596
|
+
}) {
|
|
597
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RepeatScopeContext.Provider, { value: { item, index, basePath }, children });
|
|
598
|
+
}
|
|
599
|
+
function useRepeatScope() {
|
|
600
|
+
return (0, import_react5.useContext)(RepeatScopeContext);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/renderer.tsx
|
|
604
|
+
var import_react6 = __toESM(require("react"));
|
|
605
|
+
var import_core6 = require("@json-render/core");
|
|
606
|
+
|
|
607
|
+
// src/components/standard.tsx
|
|
608
|
+
var import_components = require("@react-email/components");
|
|
609
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
610
|
+
function HtmlComponent({
|
|
611
|
+
element,
|
|
612
|
+
children
|
|
613
|
+
}) {
|
|
614
|
+
const p = element.props;
|
|
615
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Html, { lang: p.lang ?? void 0, dir: p.dir ?? void 0, children });
|
|
616
|
+
}
|
|
617
|
+
function HeadComponent({
|
|
618
|
+
children
|
|
619
|
+
}) {
|
|
620
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Head, { children });
|
|
621
|
+
}
|
|
622
|
+
function BodyComponent({
|
|
623
|
+
element,
|
|
624
|
+
children
|
|
625
|
+
}) {
|
|
626
|
+
const p = element.props;
|
|
627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Body, { style: p.style ?? void 0, children });
|
|
628
|
+
}
|
|
629
|
+
function ContainerComponent({
|
|
630
|
+
element,
|
|
631
|
+
children
|
|
632
|
+
}) {
|
|
633
|
+
const p = element.props;
|
|
634
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Container, { style: p.style ?? void 0, children });
|
|
635
|
+
}
|
|
636
|
+
function SectionComponent({
|
|
637
|
+
element,
|
|
638
|
+
children
|
|
639
|
+
}) {
|
|
640
|
+
const p = element.props;
|
|
641
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Section, { style: p.style ?? void 0, children });
|
|
642
|
+
}
|
|
643
|
+
function RowComponent({
|
|
644
|
+
element,
|
|
645
|
+
children
|
|
646
|
+
}) {
|
|
647
|
+
const p = element.props;
|
|
648
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Row, { style: p.style ?? void 0, children });
|
|
649
|
+
}
|
|
650
|
+
function ColumnComponent({
|
|
651
|
+
element,
|
|
652
|
+
children
|
|
653
|
+
}) {
|
|
654
|
+
const p = element.props;
|
|
655
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Column, { style: p.style ?? void 0, children });
|
|
656
|
+
}
|
|
657
|
+
function HeadingComponent({
|
|
658
|
+
element
|
|
659
|
+
}) {
|
|
660
|
+
const p = element.props;
|
|
661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Heading, { as: p.as ?? "h2", style: p.style ?? void 0, children: p.text });
|
|
662
|
+
}
|
|
663
|
+
function TextComponent({
|
|
664
|
+
element
|
|
665
|
+
}) {
|
|
666
|
+
const p = element.props;
|
|
667
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Text, { style: p.style ?? void 0, children: p.text });
|
|
668
|
+
}
|
|
669
|
+
function LinkComponent({
|
|
670
|
+
element
|
|
671
|
+
}) {
|
|
672
|
+
const p = element.props;
|
|
673
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Link, { href: p.href, style: p.style ?? void 0, children: p.text });
|
|
674
|
+
}
|
|
675
|
+
function ButtonComponent({
|
|
676
|
+
element
|
|
677
|
+
}) {
|
|
678
|
+
const p = element.props;
|
|
679
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Button, { href: p.href, style: p.style ?? void 0, children: p.text });
|
|
680
|
+
}
|
|
681
|
+
function ImageComponent({
|
|
682
|
+
element
|
|
683
|
+
}) {
|
|
684
|
+
const p = element.props;
|
|
685
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
686
|
+
import_components.Img,
|
|
687
|
+
{
|
|
688
|
+
src: p.src,
|
|
689
|
+
alt: p.alt ?? void 0,
|
|
690
|
+
width: p.width ?? void 0,
|
|
691
|
+
height: p.height ?? void 0,
|
|
692
|
+
style: p.style ?? void 0
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
function HrComponent({
|
|
697
|
+
element
|
|
698
|
+
}) {
|
|
699
|
+
const p = element.props;
|
|
700
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Hr, { style: p.style ?? void 0 });
|
|
701
|
+
}
|
|
702
|
+
function PreviewComponent({
|
|
703
|
+
element
|
|
704
|
+
}) {
|
|
705
|
+
const p = element.props;
|
|
706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_components.Preview, { children: p.text });
|
|
707
|
+
}
|
|
708
|
+
function MarkdownComponent({
|
|
709
|
+
element
|
|
710
|
+
}) {
|
|
711
|
+
const p = element.props;
|
|
712
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
713
|
+
import_components.Markdown,
|
|
714
|
+
{
|
|
715
|
+
markdownContainerStyles: p.markdownContainerStyles ?? void 0,
|
|
716
|
+
markdownCustomStyles: p.markdownCustomStyles ?? void 0,
|
|
717
|
+
children: p.content
|
|
718
|
+
}
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
var standardComponents = {
|
|
722
|
+
Html: HtmlComponent,
|
|
723
|
+
Head: HeadComponent,
|
|
724
|
+
Body: BodyComponent,
|
|
725
|
+
Container: ContainerComponent,
|
|
726
|
+
Section: SectionComponent,
|
|
727
|
+
Row: RowComponent,
|
|
728
|
+
Column: ColumnComponent,
|
|
729
|
+
Heading: HeadingComponent,
|
|
730
|
+
Text: TextComponent,
|
|
731
|
+
Link: LinkComponent,
|
|
732
|
+
Button: ButtonComponent,
|
|
733
|
+
Image: ImageComponent,
|
|
734
|
+
Hr: HrComponent,
|
|
735
|
+
Preview: PreviewComponent,
|
|
736
|
+
Markdown: MarkdownComponent
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/renderer.tsx
|
|
740
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
741
|
+
var ElementErrorBoundary = class extends import_react6.default.Component {
|
|
742
|
+
constructor(props) {
|
|
743
|
+
super(props);
|
|
744
|
+
this.state = { hasError: false };
|
|
745
|
+
}
|
|
746
|
+
static getDerivedStateFromError() {
|
|
747
|
+
return { hasError: true };
|
|
748
|
+
}
|
|
749
|
+
componentDidCatch(error, info) {
|
|
750
|
+
console.error(
|
|
751
|
+
`[json-render/react-email] Rendering error in <${this.props.elementType}>:`,
|
|
752
|
+
error,
|
|
753
|
+
info.componentStack
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
render() {
|
|
757
|
+
if (this.state.hasError) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
return this.props.children;
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
var ElementRenderer = import_react6.default.memo(function ElementRenderer2({
|
|
764
|
+
element,
|
|
765
|
+
spec,
|
|
766
|
+
registry,
|
|
767
|
+
loading,
|
|
768
|
+
fallback
|
|
769
|
+
}) {
|
|
770
|
+
const repeatScope = useRepeatScope();
|
|
771
|
+
const { ctx } = useVisibility();
|
|
772
|
+
const { execute } = useActions();
|
|
773
|
+
const fullCtx = (0, import_react6.useMemo)(
|
|
774
|
+
() => repeatScope ? {
|
|
775
|
+
...ctx,
|
|
776
|
+
repeatItem: repeatScope.item,
|
|
777
|
+
repeatIndex: repeatScope.index,
|
|
778
|
+
repeatBasePath: repeatScope.basePath
|
|
779
|
+
} : ctx,
|
|
780
|
+
[ctx, repeatScope]
|
|
781
|
+
);
|
|
782
|
+
const isVisible = element.visible === void 0 ? true : (0, import_core6.evaluateVisibility)(element.visible, fullCtx);
|
|
783
|
+
const onBindings = element.on;
|
|
784
|
+
const emit = (0, import_react6.useCallback)(
|
|
785
|
+
(eventName) => {
|
|
786
|
+
const binding = onBindings?.[eventName];
|
|
787
|
+
if (!binding) return;
|
|
788
|
+
const actionBindings = Array.isArray(binding) ? binding : [binding];
|
|
789
|
+
for (const b of actionBindings) {
|
|
790
|
+
if (!b.params) {
|
|
791
|
+
execute(b);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const resolved = {};
|
|
795
|
+
for (const [key, val] of Object.entries(b.params)) {
|
|
796
|
+
resolved[key] = (0, import_core6.resolveActionParam)(val, fullCtx);
|
|
797
|
+
}
|
|
798
|
+
execute({ ...b, params: resolved });
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
[onBindings, execute, fullCtx]
|
|
802
|
+
);
|
|
803
|
+
if (!isVisible) {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
const rawProps = element.props;
|
|
807
|
+
const elementBindings = (0, import_core6.resolveBindings)(rawProps, fullCtx);
|
|
808
|
+
const resolvedProps = (0, import_core6.resolveElementProps)(rawProps, fullCtx);
|
|
809
|
+
const resolvedElement = resolvedProps !== element.props ? { ...element, props: resolvedProps } : element;
|
|
810
|
+
const Component = registry[resolvedElement.type] ?? fallback;
|
|
811
|
+
if (!Component) {
|
|
812
|
+
console.warn(
|
|
813
|
+
`[json-render/react-email] No renderer for component type: ${resolvedElement.type}`
|
|
814
|
+
);
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
const children = resolvedElement.repeat ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
818
|
+
RepeatChildren,
|
|
819
|
+
{
|
|
820
|
+
element: resolvedElement,
|
|
821
|
+
spec,
|
|
822
|
+
registry,
|
|
823
|
+
loading,
|
|
824
|
+
fallback
|
|
825
|
+
}
|
|
826
|
+
) : resolvedElement.children?.map((childKey) => {
|
|
827
|
+
const childElement = spec.elements[childKey];
|
|
828
|
+
if (!childElement) {
|
|
829
|
+
if (!loading) {
|
|
830
|
+
console.warn(
|
|
831
|
+
`[json-render/react-email] Missing element "${childKey}" referenced as child of "${resolvedElement.type}".`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
837
|
+
ElementRenderer2,
|
|
838
|
+
{
|
|
839
|
+
element: childElement,
|
|
840
|
+
spec,
|
|
841
|
+
registry,
|
|
842
|
+
loading,
|
|
843
|
+
fallback
|
|
844
|
+
},
|
|
845
|
+
childKey
|
|
846
|
+
);
|
|
847
|
+
});
|
|
848
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ElementErrorBoundary, { elementType: resolvedElement.type, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
849
|
+
Component,
|
|
850
|
+
{
|
|
851
|
+
element: resolvedElement,
|
|
852
|
+
emit,
|
|
853
|
+
bindings: elementBindings,
|
|
854
|
+
loading,
|
|
855
|
+
children
|
|
856
|
+
}
|
|
857
|
+
) });
|
|
858
|
+
});
|
|
859
|
+
function RepeatChildren({
|
|
860
|
+
element,
|
|
861
|
+
spec,
|
|
862
|
+
registry,
|
|
863
|
+
loading,
|
|
864
|
+
fallback
|
|
865
|
+
}) {
|
|
866
|
+
const { state } = useStateStore();
|
|
867
|
+
const repeat = element.repeat;
|
|
868
|
+
const statePath = repeat.statePath;
|
|
869
|
+
const items = (0, import_core6.getByPath)(state, statePath) ?? [];
|
|
870
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: items.map((itemValue, index) => {
|
|
871
|
+
const key = repeat.key && typeof itemValue === "object" && itemValue !== null ? String(
|
|
872
|
+
itemValue[repeat.key] ?? index
|
|
873
|
+
) : String(index);
|
|
874
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
875
|
+
RepeatScopeProvider,
|
|
876
|
+
{
|
|
877
|
+
item: itemValue,
|
|
878
|
+
index,
|
|
879
|
+
basePath: `${statePath}/${index}`,
|
|
880
|
+
children: element.children?.map((childKey) => {
|
|
881
|
+
const childElement = spec.elements[childKey];
|
|
882
|
+
if (!childElement) {
|
|
883
|
+
if (!loading) {
|
|
884
|
+
console.warn(
|
|
885
|
+
`[json-render/react-email] Missing element "${childKey}" referenced as child of "${element.type}" (repeat).`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
891
|
+
ElementRenderer,
|
|
892
|
+
{
|
|
893
|
+
element: childElement,
|
|
894
|
+
spec,
|
|
895
|
+
registry,
|
|
896
|
+
loading,
|
|
897
|
+
fallback
|
|
898
|
+
},
|
|
899
|
+
childKey
|
|
900
|
+
);
|
|
901
|
+
})
|
|
902
|
+
},
|
|
903
|
+
key
|
|
904
|
+
);
|
|
905
|
+
}) });
|
|
906
|
+
}
|
|
907
|
+
function Renderer({
|
|
908
|
+
spec,
|
|
909
|
+
registry: customRegistry,
|
|
910
|
+
includeStandard = true,
|
|
911
|
+
loading,
|
|
912
|
+
fallback
|
|
913
|
+
}) {
|
|
914
|
+
const registry = (0, import_react6.useMemo)(
|
|
915
|
+
() => ({
|
|
916
|
+
...includeStandard ? standardComponents : {},
|
|
917
|
+
...customRegistry
|
|
918
|
+
}),
|
|
919
|
+
[customRegistry, includeStandard]
|
|
920
|
+
);
|
|
921
|
+
if (!spec || !spec.root) {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
const rootElement = spec.elements[spec.root];
|
|
925
|
+
if (!rootElement) {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
929
|
+
ElementRenderer,
|
|
930
|
+
{
|
|
931
|
+
element: rootElement,
|
|
932
|
+
spec,
|
|
933
|
+
registry,
|
|
934
|
+
loading,
|
|
935
|
+
fallback
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function JSONUIProvider({
|
|
940
|
+
initialState,
|
|
941
|
+
handlers,
|
|
942
|
+
navigate,
|
|
943
|
+
validationFunctions,
|
|
944
|
+
onStateChange,
|
|
945
|
+
children
|
|
946
|
+
}) {
|
|
947
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StateProvider, { initialState, onStateChange, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionProvider, { handlers, navigate, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ValidationProvider, { customFunctions: validationFunctions, children }) }) }) });
|
|
948
|
+
}
|
|
949
|
+
function defineRegistry(_catalog, options) {
|
|
950
|
+
const registry = {};
|
|
951
|
+
if (options.components) {
|
|
952
|
+
for (const [name, componentFn] of Object.entries(options.components)) {
|
|
953
|
+
registry[name] = ({
|
|
954
|
+
element,
|
|
955
|
+
children,
|
|
956
|
+
emit,
|
|
957
|
+
bindings,
|
|
958
|
+
loading
|
|
959
|
+
}) => {
|
|
960
|
+
return componentFn({
|
|
961
|
+
props: element.props,
|
|
962
|
+
children,
|
|
963
|
+
emit,
|
|
964
|
+
bindings,
|
|
965
|
+
loading
|
|
966
|
+
});
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return { registry };
|
|
971
|
+
}
|
|
972
|
+
function createRenderer(catalog, components) {
|
|
973
|
+
const registry = components;
|
|
974
|
+
return function CatalogRenderer({
|
|
975
|
+
spec,
|
|
976
|
+
state,
|
|
977
|
+
onAction,
|
|
978
|
+
onStateChange,
|
|
979
|
+
loading,
|
|
980
|
+
fallback
|
|
981
|
+
}) {
|
|
982
|
+
const actionHandlers = onAction ? new Proxy(
|
|
983
|
+
{},
|
|
984
|
+
{
|
|
985
|
+
get: (_target, prop) => {
|
|
986
|
+
return (params) => onAction(prop, params);
|
|
987
|
+
},
|
|
988
|
+
has: () => true
|
|
989
|
+
}
|
|
990
|
+
) : void 0;
|
|
991
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StateProvider, { initialState: state, onStateChange, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ValidationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
992
|
+
Renderer,
|
|
993
|
+
{
|
|
994
|
+
spec,
|
|
995
|
+
registry,
|
|
996
|
+
loading,
|
|
997
|
+
fallback
|
|
998
|
+
}
|
|
999
|
+
) }) }) }) });
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/render.tsx
|
|
1004
|
+
var import_render = require("@react-email/render");
|
|
1005
|
+
var import_core7 = require("@json-render/core");
|
|
1006
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1007
|
+
var noopEmit = () => {
|
|
1008
|
+
};
|
|
1009
|
+
function renderElement(elementKey, spec, registry, stateModel, repeatItem, repeatIndex, repeatBasePath) {
|
|
1010
|
+
const element = spec.elements[elementKey];
|
|
1011
|
+
if (!element) return null;
|
|
1012
|
+
const ctx = {
|
|
1013
|
+
stateModel,
|
|
1014
|
+
repeatItem,
|
|
1015
|
+
repeatIndex,
|
|
1016
|
+
repeatBasePath
|
|
1017
|
+
};
|
|
1018
|
+
if (element.visible !== void 0) {
|
|
1019
|
+
if (!(0, import_core7.evaluateVisibility)(element.visible, ctx)) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
const resolvedProps = (0, import_core7.resolveElementProps)(
|
|
1024
|
+
element.props,
|
|
1025
|
+
ctx
|
|
1026
|
+
);
|
|
1027
|
+
const resolvedElement = { ...element, props: resolvedProps };
|
|
1028
|
+
const Component = registry[resolvedElement.type];
|
|
1029
|
+
if (!Component) return null;
|
|
1030
|
+
if (resolvedElement.repeat) {
|
|
1031
|
+
const items = (0, import_core7.getByPath)(stateModel, resolvedElement.repeat.statePath) ?? [];
|
|
1032
|
+
const repeat = resolvedElement.repeat;
|
|
1033
|
+
const fragments = items.map((item, index) => {
|
|
1034
|
+
const repeatKey = repeat.key;
|
|
1035
|
+
const key = repeatKey && typeof item === "object" && item !== null ? String(item[repeatKey] ?? index) : String(index);
|
|
1036
|
+
const childPath = `${repeat.statePath}/${index}`;
|
|
1037
|
+
const children2 = resolvedElement.children?.map(
|
|
1038
|
+
(childKey) => renderElement(
|
|
1039
|
+
childKey,
|
|
1040
|
+
spec,
|
|
1041
|
+
registry,
|
|
1042
|
+
stateModel,
|
|
1043
|
+
item,
|
|
1044
|
+
index,
|
|
1045
|
+
childPath
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
1048
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Component, { element: resolvedElement, emit: noopEmit, children: children2 }, key);
|
|
1049
|
+
});
|
|
1050
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: fragments });
|
|
1051
|
+
}
|
|
1052
|
+
const children = resolvedElement.children?.map(
|
|
1053
|
+
(childKey) => renderElement(
|
|
1054
|
+
childKey,
|
|
1055
|
+
spec,
|
|
1056
|
+
registry,
|
|
1057
|
+
stateModel,
|
|
1058
|
+
repeatItem,
|
|
1059
|
+
repeatIndex,
|
|
1060
|
+
repeatBasePath
|
|
1061
|
+
)
|
|
1062
|
+
);
|
|
1063
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Component, { element: resolvedElement, emit: noopEmit, children: children && children.length > 0 ? children : void 0 }, elementKey);
|
|
1064
|
+
}
|
|
1065
|
+
function buildDocument(spec, options = {}) {
|
|
1066
|
+
const {
|
|
1067
|
+
registry: customRegistry,
|
|
1068
|
+
includeStandard = true,
|
|
1069
|
+
state = {}
|
|
1070
|
+
} = options;
|
|
1071
|
+
const mergedState = {
|
|
1072
|
+
...spec.state,
|
|
1073
|
+
...state
|
|
1074
|
+
};
|
|
1075
|
+
const registry = {
|
|
1076
|
+
...includeStandard ? standardComponents : {},
|
|
1077
|
+
...customRegistry
|
|
1078
|
+
};
|
|
1079
|
+
const root = renderElement(spec.root, spec, registry, mergedState);
|
|
1080
|
+
if (!root) {
|
|
1081
|
+
console.warn(
|
|
1082
|
+
`[json-render/react-email] Root element "${spec.root}" not found in spec.elements`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
return root ?? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, {});
|
|
1086
|
+
}
|
|
1087
|
+
async function renderToHtml(spec, options) {
|
|
1088
|
+
const document = buildDocument(spec, options);
|
|
1089
|
+
return (0, import_render.render)(document);
|
|
1090
|
+
}
|
|
1091
|
+
async function renderToPlainText(spec, options) {
|
|
1092
|
+
const document = buildDocument(spec, options);
|
|
1093
|
+
return (0, import_render.render)(document, { plainText: true });
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/catalog.ts
|
|
1097
|
+
var import_zod = require("zod");
|
|
1098
|
+
var styleSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.any()).nullable();
|
|
1099
|
+
var standardComponentDefinitions = {
|
|
1100
|
+
// ==========================================================================
|
|
1101
|
+
// Document Structure
|
|
1102
|
+
// ==========================================================================
|
|
1103
|
+
Html: {
|
|
1104
|
+
props: import_zod.z.object({
|
|
1105
|
+
lang: import_zod.z.string().nullable(),
|
|
1106
|
+
dir: import_zod.z.enum(["ltr", "rtl"]).nullable()
|
|
1107
|
+
}),
|
|
1108
|
+
slots: ["default"],
|
|
1109
|
+
description: "Top-level HTML email wrapper. Must be the root element. Children should include Head and Body.",
|
|
1110
|
+
example: { lang: "en", dir: "ltr" }
|
|
1111
|
+
},
|
|
1112
|
+
Head: {
|
|
1113
|
+
props: import_zod.z.object({}),
|
|
1114
|
+
slots: ["default"],
|
|
1115
|
+
description: "Email head section. Place inside Html. Can contain metadata but typically left empty.",
|
|
1116
|
+
example: {}
|
|
1117
|
+
},
|
|
1118
|
+
Body: {
|
|
1119
|
+
props: import_zod.z.object({
|
|
1120
|
+
style: styleSchema
|
|
1121
|
+
}),
|
|
1122
|
+
slots: ["default"],
|
|
1123
|
+
description: "Email body wrapper. Place inside Html after Head. Contains all visible email content.",
|
|
1124
|
+
example: { style: { backgroundColor: "#f6f9fc" } }
|
|
1125
|
+
},
|
|
1126
|
+
Container: {
|
|
1127
|
+
props: import_zod.z.object({
|
|
1128
|
+
style: styleSchema
|
|
1129
|
+
}),
|
|
1130
|
+
slots: ["default"],
|
|
1131
|
+
description: "Constrains content width for email clients. Place inside Body. Typically max-width 600px.",
|
|
1132
|
+
example: {
|
|
1133
|
+
style: {
|
|
1134
|
+
maxWidth: "600px",
|
|
1135
|
+
margin: "0 auto",
|
|
1136
|
+
padding: "20px 0 48px"
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
Section: {
|
|
1141
|
+
props: import_zod.z.object({
|
|
1142
|
+
style: styleSchema
|
|
1143
|
+
}),
|
|
1144
|
+
slots: ["default"],
|
|
1145
|
+
description: "Groups related content. Renders as a table-based section for email compatibility.",
|
|
1146
|
+
example: { style: { padding: "24px", backgroundColor: "#ffffff" } }
|
|
1147
|
+
},
|
|
1148
|
+
Row: {
|
|
1149
|
+
props: import_zod.z.object({
|
|
1150
|
+
style: styleSchema
|
|
1151
|
+
}),
|
|
1152
|
+
slots: ["default"],
|
|
1153
|
+
description: "Horizontal layout row. Use inside Section for multi-column layouts.",
|
|
1154
|
+
example: { style: {} }
|
|
1155
|
+
},
|
|
1156
|
+
Column: {
|
|
1157
|
+
props: import_zod.z.object({
|
|
1158
|
+
style: styleSchema
|
|
1159
|
+
}),
|
|
1160
|
+
slots: ["default"],
|
|
1161
|
+
description: "Column within a Row. Set width via style for proportional layouts.",
|
|
1162
|
+
example: { style: { width: "50%" } }
|
|
1163
|
+
},
|
|
1164
|
+
// ==========================================================================
|
|
1165
|
+
// Content Components
|
|
1166
|
+
// ==========================================================================
|
|
1167
|
+
Heading: {
|
|
1168
|
+
props: import_zod.z.object({
|
|
1169
|
+
text: import_zod.z.string(),
|
|
1170
|
+
as: import_zod.z.enum(["h1", "h2", "h3", "h4", "h5", "h6"]).nullable(),
|
|
1171
|
+
style: styleSchema
|
|
1172
|
+
}),
|
|
1173
|
+
slots: [],
|
|
1174
|
+
description: "Heading text at various levels. h1 is largest, h6 is smallest.",
|
|
1175
|
+
example: { text: "Welcome!", as: "h1" }
|
|
1176
|
+
},
|
|
1177
|
+
Text: {
|
|
1178
|
+
props: import_zod.z.object({
|
|
1179
|
+
text: import_zod.z.string(),
|
|
1180
|
+
style: styleSchema
|
|
1181
|
+
}),
|
|
1182
|
+
slots: [],
|
|
1183
|
+
description: "Body text paragraph. Use style for font size, color, weight, and alignment.",
|
|
1184
|
+
example: { text: "Thank you for signing up." }
|
|
1185
|
+
},
|
|
1186
|
+
Link: {
|
|
1187
|
+
props: import_zod.z.object({
|
|
1188
|
+
text: import_zod.z.string(),
|
|
1189
|
+
href: import_zod.z.string(),
|
|
1190
|
+
style: styleSchema
|
|
1191
|
+
}),
|
|
1192
|
+
slots: [],
|
|
1193
|
+
description: "Hyperlink with visible text and a URL.",
|
|
1194
|
+
example: {
|
|
1195
|
+
text: "Visit our website",
|
|
1196
|
+
href: "https://example.com",
|
|
1197
|
+
style: { color: "#2563eb" }
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
Button: {
|
|
1201
|
+
props: import_zod.z.object({
|
|
1202
|
+
text: import_zod.z.string(),
|
|
1203
|
+
href: import_zod.z.string(),
|
|
1204
|
+
style: styleSchema
|
|
1205
|
+
}),
|
|
1206
|
+
slots: [],
|
|
1207
|
+
description: "Call-to-action button rendered as a link styled as a button. Provide text and href.",
|
|
1208
|
+
example: {
|
|
1209
|
+
text: "Get Started",
|
|
1210
|
+
href: "https://example.com",
|
|
1211
|
+
style: {
|
|
1212
|
+
backgroundColor: "#5F51E8",
|
|
1213
|
+
borderRadius: "3px",
|
|
1214
|
+
color: "#fff",
|
|
1215
|
+
padding: "12px 20px"
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
},
|
|
1219
|
+
Image: {
|
|
1220
|
+
props: import_zod.z.object({
|
|
1221
|
+
src: import_zod.z.string(),
|
|
1222
|
+
alt: import_zod.z.string().nullable(),
|
|
1223
|
+
width: import_zod.z.number().nullable(),
|
|
1224
|
+
height: import_zod.z.number().nullable(),
|
|
1225
|
+
style: styleSchema
|
|
1226
|
+
}),
|
|
1227
|
+
slots: [],
|
|
1228
|
+
description: "Image from a URL. src must be a fully qualified URL. Specify width and height for consistent rendering.",
|
|
1229
|
+
example: {
|
|
1230
|
+
src: "https://picsum.photos/400/200?random=1",
|
|
1231
|
+
alt: "Hero image",
|
|
1232
|
+
width: 400,
|
|
1233
|
+
height: 200
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
Hr: {
|
|
1237
|
+
props: import_zod.z.object({
|
|
1238
|
+
style: styleSchema
|
|
1239
|
+
}),
|
|
1240
|
+
slots: [],
|
|
1241
|
+
description: "Horizontal rule separator between content sections.",
|
|
1242
|
+
example: {
|
|
1243
|
+
style: { borderColor: "#e6ebf1", margin: "20px 0" }
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
// ==========================================================================
|
|
1247
|
+
// Utility Components
|
|
1248
|
+
// ==========================================================================
|
|
1249
|
+
Preview: {
|
|
1250
|
+
props: import_zod.z.object({
|
|
1251
|
+
text: import_zod.z.string()
|
|
1252
|
+
}),
|
|
1253
|
+
slots: [],
|
|
1254
|
+
description: "Preview text shown in email client inboxes before the email is opened. Place inside Html.",
|
|
1255
|
+
example: { text: "You have a new message from Acme Corp" }
|
|
1256
|
+
},
|
|
1257
|
+
Markdown: {
|
|
1258
|
+
props: import_zod.z.object({
|
|
1259
|
+
content: import_zod.z.string(),
|
|
1260
|
+
markdownContainerStyles: styleSchema,
|
|
1261
|
+
markdownCustomStyles: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).nullable()
|
|
1262
|
+
}),
|
|
1263
|
+
slots: [],
|
|
1264
|
+
description: "Renders markdown content as email-safe HTML. Supports headings, paragraphs, lists, links, bold, italic, and code.",
|
|
1265
|
+
example: {
|
|
1266
|
+
content: "# Hello\n\nThis is **bold** and *italic* text."
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1271
|
+
0 && (module.exports = {
|
|
1272
|
+
ActionProvider,
|
|
1273
|
+
ConfirmDialog,
|
|
1274
|
+
JSONUIProvider,
|
|
1275
|
+
Renderer,
|
|
1276
|
+
RepeatScopeProvider,
|
|
1277
|
+
StateProvider,
|
|
1278
|
+
ValidationProvider,
|
|
1279
|
+
VisibilityProvider,
|
|
1280
|
+
createRenderer,
|
|
1281
|
+
defineRegistry,
|
|
1282
|
+
renderToHtml,
|
|
1283
|
+
renderToPlainText,
|
|
1284
|
+
schema,
|
|
1285
|
+
standardComponentDefinitions,
|
|
1286
|
+
standardComponents,
|
|
1287
|
+
useAction,
|
|
1288
|
+
useActions,
|
|
1289
|
+
useFieldValidation,
|
|
1290
|
+
useIsVisible,
|
|
1291
|
+
useRepeatScope,
|
|
1292
|
+
useStateBinding,
|
|
1293
|
+
useStateStore,
|
|
1294
|
+
useStateValue,
|
|
1295
|
+
useValidation,
|
|
1296
|
+
useVisibility
|
|
1297
|
+
});
|
|
1298
|
+
//# sourceMappingURL=index.js.map
|