@json-render/react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +238 -0
- package/dist/index.d.mts +315 -0
- package/dist/index.d.ts +315 -0
- package/dist/index.js +796 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +769 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
// src/contexts/data.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useState,
|
|
6
|
+
useCallback,
|
|
7
|
+
useMemo
|
|
8
|
+
} from "react";
|
|
9
|
+
import {
|
|
10
|
+
getByPath,
|
|
11
|
+
setByPath
|
|
12
|
+
} from "@json-render/core";
|
|
13
|
+
import { jsx } from "react/jsx-runtime";
|
|
14
|
+
var DataContext = createContext(null);
|
|
15
|
+
function DataProvider({
|
|
16
|
+
initialData = {},
|
|
17
|
+
authState,
|
|
18
|
+
onDataChange,
|
|
19
|
+
children
|
|
20
|
+
}) {
|
|
21
|
+
const [data, setData] = useState(initialData);
|
|
22
|
+
const get = useCallback(
|
|
23
|
+
(path) => getByPath(data, path),
|
|
24
|
+
[data]
|
|
25
|
+
);
|
|
26
|
+
const set = useCallback(
|
|
27
|
+
(path, value2) => {
|
|
28
|
+
setData((prev) => {
|
|
29
|
+
const next = { ...prev };
|
|
30
|
+
setByPath(next, path, value2);
|
|
31
|
+
return next;
|
|
32
|
+
});
|
|
33
|
+
onDataChange?.(path, value2);
|
|
34
|
+
},
|
|
35
|
+
[onDataChange]
|
|
36
|
+
);
|
|
37
|
+
const update = useCallback(
|
|
38
|
+
(updates) => {
|
|
39
|
+
setData((prev) => {
|
|
40
|
+
const next = { ...prev };
|
|
41
|
+
for (const [path, value2] of Object.entries(updates)) {
|
|
42
|
+
setByPath(next, path, value2);
|
|
43
|
+
onDataChange?.(path, value2);
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
[onDataChange]
|
|
49
|
+
);
|
|
50
|
+
const value = useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
data,
|
|
53
|
+
authState,
|
|
54
|
+
get,
|
|
55
|
+
set,
|
|
56
|
+
update
|
|
57
|
+
}),
|
|
58
|
+
[data, authState, get, set, update]
|
|
59
|
+
);
|
|
60
|
+
return /* @__PURE__ */ jsx(DataContext.Provider, { value, children });
|
|
61
|
+
}
|
|
62
|
+
function useData() {
|
|
63
|
+
const ctx = useContext(DataContext);
|
|
64
|
+
if (!ctx) {
|
|
65
|
+
throw new Error("useData must be used within a DataProvider");
|
|
66
|
+
}
|
|
67
|
+
return ctx;
|
|
68
|
+
}
|
|
69
|
+
function useDataValue(path) {
|
|
70
|
+
const { get } = useData();
|
|
71
|
+
return get(path);
|
|
72
|
+
}
|
|
73
|
+
function useDataBinding(path) {
|
|
74
|
+
const { get, set } = useData();
|
|
75
|
+
const value = get(path);
|
|
76
|
+
const setValue = useCallback(
|
|
77
|
+
(newValue) => set(path, newValue),
|
|
78
|
+
[path, set]
|
|
79
|
+
);
|
|
80
|
+
return [value, setValue];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/contexts/visibility.tsx
|
|
84
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
85
|
+
import {
|
|
86
|
+
evaluateVisibility
|
|
87
|
+
} from "@json-render/core";
|
|
88
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
89
|
+
var VisibilityContext = createContext2(null);
|
|
90
|
+
function VisibilityProvider({ children }) {
|
|
91
|
+
const { data, authState } = useData();
|
|
92
|
+
const ctx = useMemo2(
|
|
93
|
+
() => ({
|
|
94
|
+
dataModel: data,
|
|
95
|
+
authState
|
|
96
|
+
}),
|
|
97
|
+
[data, authState]
|
|
98
|
+
);
|
|
99
|
+
const isVisible = useMemo2(
|
|
100
|
+
() => (condition) => evaluateVisibility(condition, ctx),
|
|
101
|
+
[ctx]
|
|
102
|
+
);
|
|
103
|
+
const value = useMemo2(
|
|
104
|
+
() => ({ isVisible, ctx }),
|
|
105
|
+
[isVisible, ctx]
|
|
106
|
+
);
|
|
107
|
+
return /* @__PURE__ */ jsx2(VisibilityContext.Provider, { value, children });
|
|
108
|
+
}
|
|
109
|
+
function useVisibility() {
|
|
110
|
+
const ctx = useContext2(VisibilityContext);
|
|
111
|
+
if (!ctx) {
|
|
112
|
+
throw new Error("useVisibility must be used within a VisibilityProvider");
|
|
113
|
+
}
|
|
114
|
+
return ctx;
|
|
115
|
+
}
|
|
116
|
+
function useIsVisible(condition) {
|
|
117
|
+
const { isVisible } = useVisibility();
|
|
118
|
+
return isVisible(condition);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/contexts/actions.tsx
|
|
122
|
+
import {
|
|
123
|
+
createContext as createContext3,
|
|
124
|
+
useContext as useContext3,
|
|
125
|
+
useState as useState2,
|
|
126
|
+
useCallback as useCallback2,
|
|
127
|
+
useMemo as useMemo3
|
|
128
|
+
} from "react";
|
|
129
|
+
import {
|
|
130
|
+
resolveAction,
|
|
131
|
+
executeAction
|
|
132
|
+
} from "@json-render/core";
|
|
133
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
134
|
+
var ActionContext = createContext3(null);
|
|
135
|
+
function ActionProvider({
|
|
136
|
+
handlers: initialHandlers = {},
|
|
137
|
+
navigate,
|
|
138
|
+
children
|
|
139
|
+
}) {
|
|
140
|
+
const { data, set } = useData();
|
|
141
|
+
const [handlers, setHandlers] = useState2(initialHandlers);
|
|
142
|
+
const [loadingActions, setLoadingActions] = useState2(/* @__PURE__ */ new Set());
|
|
143
|
+
const [pendingConfirmation, setPendingConfirmation] = useState2(null);
|
|
144
|
+
const registerHandler = useCallback2((name, handler) => {
|
|
145
|
+
setHandlers((prev) => ({ ...prev, [name]: handler }));
|
|
146
|
+
}, []);
|
|
147
|
+
const execute = useCallback2(
|
|
148
|
+
async (action) => {
|
|
149
|
+
const resolved = resolveAction(action, data);
|
|
150
|
+
const handler = handlers[resolved.name];
|
|
151
|
+
if (!handler) {
|
|
152
|
+
console.warn(`No handler registered for action: ${resolved.name}`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (resolved.confirm) {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
setPendingConfirmation({
|
|
158
|
+
action: resolved,
|
|
159
|
+
handler,
|
|
160
|
+
resolve: () => {
|
|
161
|
+
setPendingConfirmation(null);
|
|
162
|
+
resolve();
|
|
163
|
+
},
|
|
164
|
+
reject: () => {
|
|
165
|
+
setPendingConfirmation(null);
|
|
166
|
+
reject(new Error("Action cancelled"));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}).then(async () => {
|
|
170
|
+
setLoadingActions((prev) => new Set(prev).add(resolved.name));
|
|
171
|
+
try {
|
|
172
|
+
await executeAction({
|
|
173
|
+
action: resolved,
|
|
174
|
+
handler,
|
|
175
|
+
setData: set,
|
|
176
|
+
navigate,
|
|
177
|
+
executeAction: async (name) => {
|
|
178
|
+
const subAction = { name };
|
|
179
|
+
await execute(subAction);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} finally {
|
|
183
|
+
setLoadingActions((prev) => {
|
|
184
|
+
const next = new Set(prev);
|
|
185
|
+
next.delete(resolved.name);
|
|
186
|
+
return next;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
setLoadingActions((prev) => new Set(prev).add(resolved.name));
|
|
192
|
+
try {
|
|
193
|
+
await executeAction({
|
|
194
|
+
action: resolved,
|
|
195
|
+
handler,
|
|
196
|
+
setData: set,
|
|
197
|
+
navigate,
|
|
198
|
+
executeAction: async (name) => {
|
|
199
|
+
const subAction = { name };
|
|
200
|
+
await execute(subAction);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
} finally {
|
|
204
|
+
setLoadingActions((prev) => {
|
|
205
|
+
const next = new Set(prev);
|
|
206
|
+
next.delete(resolved.name);
|
|
207
|
+
return next;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
[data, handlers, set, navigate]
|
|
212
|
+
);
|
|
213
|
+
const confirm = useCallback2(() => {
|
|
214
|
+
pendingConfirmation?.resolve();
|
|
215
|
+
}, [pendingConfirmation]);
|
|
216
|
+
const cancel = useCallback2(() => {
|
|
217
|
+
pendingConfirmation?.reject();
|
|
218
|
+
}, [pendingConfirmation]);
|
|
219
|
+
const value = useMemo3(
|
|
220
|
+
() => ({
|
|
221
|
+
handlers,
|
|
222
|
+
loadingActions,
|
|
223
|
+
pendingConfirmation,
|
|
224
|
+
execute,
|
|
225
|
+
confirm,
|
|
226
|
+
cancel,
|
|
227
|
+
registerHandler
|
|
228
|
+
}),
|
|
229
|
+
[handlers, loadingActions, pendingConfirmation, execute, confirm, cancel, registerHandler]
|
|
230
|
+
);
|
|
231
|
+
return /* @__PURE__ */ jsx3(ActionContext.Provider, { value, children });
|
|
232
|
+
}
|
|
233
|
+
function useActions() {
|
|
234
|
+
const ctx = useContext3(ActionContext);
|
|
235
|
+
if (!ctx) {
|
|
236
|
+
throw new Error("useActions must be used within an ActionProvider");
|
|
237
|
+
}
|
|
238
|
+
return ctx;
|
|
239
|
+
}
|
|
240
|
+
function useAction(action) {
|
|
241
|
+
const { execute, loadingActions } = useActions();
|
|
242
|
+
const isLoading = loadingActions.has(action.name);
|
|
243
|
+
const executeAction2 = useCallback2(() => execute(action), [execute, action]);
|
|
244
|
+
return { execute: executeAction2, isLoading };
|
|
245
|
+
}
|
|
246
|
+
function ConfirmDialog({ confirm, onConfirm, onCancel }) {
|
|
247
|
+
const isDanger = confirm.variant === "danger";
|
|
248
|
+
return /* @__PURE__ */ jsx3(
|
|
249
|
+
"div",
|
|
250
|
+
{
|
|
251
|
+
style: {
|
|
252
|
+
position: "fixed",
|
|
253
|
+
inset: 0,
|
|
254
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
255
|
+
display: "flex",
|
|
256
|
+
alignItems: "center",
|
|
257
|
+
justifyContent: "center",
|
|
258
|
+
zIndex: 50
|
|
259
|
+
},
|
|
260
|
+
onClick: onCancel,
|
|
261
|
+
children: /* @__PURE__ */ jsxs(
|
|
262
|
+
"div",
|
|
263
|
+
{
|
|
264
|
+
style: {
|
|
265
|
+
backgroundColor: "white",
|
|
266
|
+
borderRadius: "8px",
|
|
267
|
+
padding: "24px",
|
|
268
|
+
maxWidth: "400px",
|
|
269
|
+
width: "100%",
|
|
270
|
+
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)"
|
|
271
|
+
},
|
|
272
|
+
onClick: (e) => e.stopPropagation(),
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ jsx3(
|
|
275
|
+
"h3",
|
|
276
|
+
{
|
|
277
|
+
style: {
|
|
278
|
+
margin: "0 0 8px 0",
|
|
279
|
+
fontSize: "18px",
|
|
280
|
+
fontWeight: 600
|
|
281
|
+
},
|
|
282
|
+
children: confirm.title
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
/* @__PURE__ */ jsx3(
|
|
286
|
+
"p",
|
|
287
|
+
{
|
|
288
|
+
style: {
|
|
289
|
+
margin: "0 0 24px 0",
|
|
290
|
+
color: "#6b7280"
|
|
291
|
+
},
|
|
292
|
+
children: confirm.message
|
|
293
|
+
}
|
|
294
|
+
),
|
|
295
|
+
/* @__PURE__ */ jsxs(
|
|
296
|
+
"div",
|
|
297
|
+
{
|
|
298
|
+
style: {
|
|
299
|
+
display: "flex",
|
|
300
|
+
gap: "12px",
|
|
301
|
+
justifyContent: "flex-end"
|
|
302
|
+
},
|
|
303
|
+
children: [
|
|
304
|
+
/* @__PURE__ */ jsx3(
|
|
305
|
+
"button",
|
|
306
|
+
{
|
|
307
|
+
onClick: onCancel,
|
|
308
|
+
style: {
|
|
309
|
+
padding: "8px 16px",
|
|
310
|
+
borderRadius: "6px",
|
|
311
|
+
border: "1px solid #d1d5db",
|
|
312
|
+
backgroundColor: "white",
|
|
313
|
+
cursor: "pointer"
|
|
314
|
+
},
|
|
315
|
+
children: confirm.cancelLabel ?? "Cancel"
|
|
316
|
+
}
|
|
317
|
+
),
|
|
318
|
+
/* @__PURE__ */ jsx3(
|
|
319
|
+
"button",
|
|
320
|
+
{
|
|
321
|
+
onClick: onConfirm,
|
|
322
|
+
style: {
|
|
323
|
+
padding: "8px 16px",
|
|
324
|
+
borderRadius: "6px",
|
|
325
|
+
border: "none",
|
|
326
|
+
backgroundColor: isDanger ? "#dc2626" : "#3b82f6",
|
|
327
|
+
color: "white",
|
|
328
|
+
cursor: "pointer"
|
|
329
|
+
},
|
|
330
|
+
children: confirm.confirmLabel ?? "Confirm"
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/contexts/validation.tsx
|
|
344
|
+
import React4, {
|
|
345
|
+
createContext as createContext4,
|
|
346
|
+
useContext as useContext4,
|
|
347
|
+
useState as useState3,
|
|
348
|
+
useCallback as useCallback3,
|
|
349
|
+
useMemo as useMemo4
|
|
350
|
+
} from "react";
|
|
351
|
+
import {
|
|
352
|
+
runValidation
|
|
353
|
+
} from "@json-render/core";
|
|
354
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
355
|
+
var ValidationContext = createContext4(null);
|
|
356
|
+
function ValidationProvider({
|
|
357
|
+
customFunctions = {},
|
|
358
|
+
children
|
|
359
|
+
}) {
|
|
360
|
+
const { data, authState } = useData();
|
|
361
|
+
const [fieldStates, setFieldStates] = useState3({});
|
|
362
|
+
const [fieldConfigs, setFieldConfigs] = useState3({});
|
|
363
|
+
const registerField = useCallback3((path, config) => {
|
|
364
|
+
setFieldConfigs((prev) => ({ ...prev, [path]: config }));
|
|
365
|
+
}, []);
|
|
366
|
+
const validate = useCallback3(
|
|
367
|
+
(path, config) => {
|
|
368
|
+
const value2 = data[path.split("/").filter(Boolean).join(".")];
|
|
369
|
+
const result = runValidation(config, {
|
|
370
|
+
value: value2,
|
|
371
|
+
dataModel: data,
|
|
372
|
+
customFunctions,
|
|
373
|
+
authState
|
|
374
|
+
});
|
|
375
|
+
setFieldStates((prev) => ({
|
|
376
|
+
...prev,
|
|
377
|
+
[path]: {
|
|
378
|
+
touched: prev[path]?.touched ?? true,
|
|
379
|
+
validated: true,
|
|
380
|
+
result
|
|
381
|
+
}
|
|
382
|
+
}));
|
|
383
|
+
return result;
|
|
384
|
+
},
|
|
385
|
+
[data, customFunctions, authState]
|
|
386
|
+
);
|
|
387
|
+
const touch = useCallback3((path) => {
|
|
388
|
+
setFieldStates((prev) => ({
|
|
389
|
+
...prev,
|
|
390
|
+
[path]: {
|
|
391
|
+
...prev[path],
|
|
392
|
+
touched: true,
|
|
393
|
+
validated: prev[path]?.validated ?? false,
|
|
394
|
+
result: prev[path]?.result ?? null
|
|
395
|
+
}
|
|
396
|
+
}));
|
|
397
|
+
}, []);
|
|
398
|
+
const clear = useCallback3((path) => {
|
|
399
|
+
setFieldStates((prev) => {
|
|
400
|
+
const { [path]: _, ...rest } = prev;
|
|
401
|
+
return rest;
|
|
402
|
+
});
|
|
403
|
+
}, []);
|
|
404
|
+
const validateAll = useCallback3(() => {
|
|
405
|
+
let allValid = true;
|
|
406
|
+
for (const [path, config] of Object.entries(fieldConfigs)) {
|
|
407
|
+
const result = validate(path, config);
|
|
408
|
+
if (!result.valid) {
|
|
409
|
+
allValid = false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return allValid;
|
|
413
|
+
}, [fieldConfigs, validate]);
|
|
414
|
+
const value = useMemo4(
|
|
415
|
+
() => ({
|
|
416
|
+
customFunctions,
|
|
417
|
+
fieldStates,
|
|
418
|
+
validate,
|
|
419
|
+
touch,
|
|
420
|
+
clear,
|
|
421
|
+
validateAll,
|
|
422
|
+
registerField
|
|
423
|
+
}),
|
|
424
|
+
[customFunctions, fieldStates, validate, touch, clear, validateAll, registerField]
|
|
425
|
+
);
|
|
426
|
+
return /* @__PURE__ */ jsx4(ValidationContext.Provider, { value, children });
|
|
427
|
+
}
|
|
428
|
+
function useValidation() {
|
|
429
|
+
const ctx = useContext4(ValidationContext);
|
|
430
|
+
if (!ctx) {
|
|
431
|
+
throw new Error("useValidation must be used within a ValidationProvider");
|
|
432
|
+
}
|
|
433
|
+
return ctx;
|
|
434
|
+
}
|
|
435
|
+
function useFieldValidation(path, config) {
|
|
436
|
+
const {
|
|
437
|
+
fieldStates,
|
|
438
|
+
validate: validateField,
|
|
439
|
+
touch: touchField,
|
|
440
|
+
clear: clearField,
|
|
441
|
+
registerField
|
|
442
|
+
} = useValidation();
|
|
443
|
+
React4.useEffect(() => {
|
|
444
|
+
if (config) {
|
|
445
|
+
registerField(path, config);
|
|
446
|
+
}
|
|
447
|
+
}, [path, config, registerField]);
|
|
448
|
+
const state = fieldStates[path] ?? {
|
|
449
|
+
touched: false,
|
|
450
|
+
validated: false,
|
|
451
|
+
result: null
|
|
452
|
+
};
|
|
453
|
+
const validate = useCallback3(
|
|
454
|
+
() => validateField(path, config ?? { checks: [] }),
|
|
455
|
+
[path, config, validateField]
|
|
456
|
+
);
|
|
457
|
+
const touch = useCallback3(() => touchField(path), [path, touchField]);
|
|
458
|
+
const clear = useCallback3(() => clearField(path), [path, clearField]);
|
|
459
|
+
return {
|
|
460
|
+
state,
|
|
461
|
+
validate,
|
|
462
|
+
touch,
|
|
463
|
+
clear,
|
|
464
|
+
errors: state.result?.errors ?? [],
|
|
465
|
+
isValid: state.result?.valid ?? true
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/renderer.tsx
|
|
470
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
471
|
+
function ElementRenderer({
|
|
472
|
+
element,
|
|
473
|
+
tree,
|
|
474
|
+
registry,
|
|
475
|
+
loading,
|
|
476
|
+
fallback
|
|
477
|
+
}) {
|
|
478
|
+
const isVisible = useIsVisible(element.visible);
|
|
479
|
+
const { execute } = useActions();
|
|
480
|
+
if (!isVisible) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
const Component = registry[element.type] ?? fallback;
|
|
484
|
+
if (!Component) {
|
|
485
|
+
console.warn(`No renderer for component type: ${element.type}`);
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
const children = element.children?.map((childKey) => {
|
|
489
|
+
const childElement = tree.elements[childKey];
|
|
490
|
+
if (!childElement) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
return /* @__PURE__ */ jsx5(
|
|
494
|
+
ElementRenderer,
|
|
495
|
+
{
|
|
496
|
+
element: childElement,
|
|
497
|
+
tree,
|
|
498
|
+
registry,
|
|
499
|
+
loading,
|
|
500
|
+
fallback
|
|
501
|
+
},
|
|
502
|
+
childKey
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
return /* @__PURE__ */ jsx5(
|
|
506
|
+
Component,
|
|
507
|
+
{
|
|
508
|
+
element,
|
|
509
|
+
onAction: execute,
|
|
510
|
+
loading,
|
|
511
|
+
children
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
function Renderer({ tree, registry, loading, fallback }) {
|
|
516
|
+
if (!tree || !tree.root) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
const rootElement = tree.elements[tree.root];
|
|
520
|
+
if (!rootElement) {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
return /* @__PURE__ */ jsx5(
|
|
524
|
+
ElementRenderer,
|
|
525
|
+
{
|
|
526
|
+
element: rootElement,
|
|
527
|
+
tree,
|
|
528
|
+
registry,
|
|
529
|
+
loading,
|
|
530
|
+
fallback
|
|
531
|
+
}
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
function JSONUIProvider({
|
|
535
|
+
registry,
|
|
536
|
+
initialData,
|
|
537
|
+
authState,
|
|
538
|
+
actionHandlers,
|
|
539
|
+
navigate,
|
|
540
|
+
validationFunctions,
|
|
541
|
+
onDataChange,
|
|
542
|
+
children
|
|
543
|
+
}) {
|
|
544
|
+
return /* @__PURE__ */ jsx5(
|
|
545
|
+
DataProvider,
|
|
546
|
+
{
|
|
547
|
+
initialData,
|
|
548
|
+
authState,
|
|
549
|
+
onDataChange,
|
|
550
|
+
children: /* @__PURE__ */ jsx5(VisibilityProvider, { children: /* @__PURE__ */ jsx5(ActionProvider, { handlers: actionHandlers, navigate, children: /* @__PURE__ */ jsxs2(ValidationProvider, { customFunctions: validationFunctions, children: [
|
|
551
|
+
children,
|
|
552
|
+
/* @__PURE__ */ jsx5(ConfirmationDialogManager, {})
|
|
553
|
+
] }) }) })
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
function ConfirmationDialogManager() {
|
|
558
|
+
const { pendingConfirmation, confirm, cancel } = useActions();
|
|
559
|
+
if (!pendingConfirmation?.action.confirm) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
return /* @__PURE__ */ jsx5(
|
|
563
|
+
ConfirmDialog,
|
|
564
|
+
{
|
|
565
|
+
confirm: pendingConfirmation.action.confirm,
|
|
566
|
+
onConfirm: confirm,
|
|
567
|
+
onCancel: cancel
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
function createRendererFromCatalog(_catalog, registry) {
|
|
572
|
+
return function CatalogRenderer(props) {
|
|
573
|
+
return /* @__PURE__ */ jsx5(Renderer, { ...props, registry });
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/hooks.ts
|
|
578
|
+
import { useState as useState4, useCallback as useCallback4, useRef, useEffect } from "react";
|
|
579
|
+
import { setByPath as setByPath2 } from "@json-render/core";
|
|
580
|
+
function parsePatchLine(line) {
|
|
581
|
+
try {
|
|
582
|
+
const trimmed = line.trim();
|
|
583
|
+
if (!trimmed || trimmed.startsWith("//")) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
return JSON.parse(trimmed);
|
|
587
|
+
} catch {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function applyPatch(tree, patch) {
|
|
592
|
+
const newTree = { ...tree, elements: { ...tree.elements } };
|
|
593
|
+
switch (patch.op) {
|
|
594
|
+
case "set":
|
|
595
|
+
case "add":
|
|
596
|
+
case "replace": {
|
|
597
|
+
if (patch.path === "/root") {
|
|
598
|
+
newTree.root = patch.value;
|
|
599
|
+
return newTree;
|
|
600
|
+
}
|
|
601
|
+
if (patch.path.startsWith("/elements/")) {
|
|
602
|
+
const pathParts = patch.path.slice("/elements/".length).split("/");
|
|
603
|
+
const elementKey = pathParts[0];
|
|
604
|
+
if (!elementKey) return newTree;
|
|
605
|
+
if (pathParts.length === 1) {
|
|
606
|
+
newTree.elements[elementKey] = patch.value;
|
|
607
|
+
} else {
|
|
608
|
+
const element = newTree.elements[elementKey];
|
|
609
|
+
if (element) {
|
|
610
|
+
const propPath = "/" + pathParts.slice(1).join("/");
|
|
611
|
+
const newElement = { ...element };
|
|
612
|
+
setByPath2(newElement, propPath, patch.value);
|
|
613
|
+
newTree.elements[elementKey] = newElement;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "remove": {
|
|
620
|
+
if (patch.path.startsWith("/elements/")) {
|
|
621
|
+
const elementKey = patch.path.slice("/elements/".length).split("/")[0];
|
|
622
|
+
if (elementKey) {
|
|
623
|
+
const { [elementKey]: _, ...rest } = newTree.elements;
|
|
624
|
+
newTree.elements = rest;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return newTree;
|
|
631
|
+
}
|
|
632
|
+
function useUIStream({
|
|
633
|
+
api,
|
|
634
|
+
onComplete,
|
|
635
|
+
onError
|
|
636
|
+
}) {
|
|
637
|
+
const [tree, setTree] = useState4(null);
|
|
638
|
+
const [isStreaming, setIsStreaming] = useState4(false);
|
|
639
|
+
const [error, setError] = useState4(null);
|
|
640
|
+
const abortControllerRef = useRef(null);
|
|
641
|
+
const clear = useCallback4(() => {
|
|
642
|
+
setTree(null);
|
|
643
|
+
setError(null);
|
|
644
|
+
}, []);
|
|
645
|
+
const send = useCallback4(
|
|
646
|
+
async (prompt, context) => {
|
|
647
|
+
abortControllerRef.current?.abort();
|
|
648
|
+
abortControllerRef.current = new AbortController();
|
|
649
|
+
setIsStreaming(true);
|
|
650
|
+
setError(null);
|
|
651
|
+
let currentTree = { root: "", elements: {} };
|
|
652
|
+
setTree(currentTree);
|
|
653
|
+
try {
|
|
654
|
+
const response = await fetch(api, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers: { "Content-Type": "application/json" },
|
|
657
|
+
body: JSON.stringify({
|
|
658
|
+
prompt,
|
|
659
|
+
context,
|
|
660
|
+
currentTree
|
|
661
|
+
}),
|
|
662
|
+
signal: abortControllerRef.current.signal
|
|
663
|
+
});
|
|
664
|
+
if (!response.ok) {
|
|
665
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
666
|
+
}
|
|
667
|
+
const reader = response.body?.getReader();
|
|
668
|
+
if (!reader) {
|
|
669
|
+
throw new Error("No response body");
|
|
670
|
+
}
|
|
671
|
+
const decoder = new TextDecoder();
|
|
672
|
+
let buffer = "";
|
|
673
|
+
while (true) {
|
|
674
|
+
const { done, value } = await reader.read();
|
|
675
|
+
if (done) break;
|
|
676
|
+
buffer += decoder.decode(value, { stream: true });
|
|
677
|
+
const lines = buffer.split("\n");
|
|
678
|
+
buffer = lines.pop() ?? "";
|
|
679
|
+
for (const line of lines) {
|
|
680
|
+
const patch = parsePatchLine(line);
|
|
681
|
+
if (patch) {
|
|
682
|
+
currentTree = applyPatch(currentTree, patch);
|
|
683
|
+
setTree({ ...currentTree });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (buffer.trim()) {
|
|
688
|
+
const patch = parsePatchLine(buffer);
|
|
689
|
+
if (patch) {
|
|
690
|
+
currentTree = applyPatch(currentTree, patch);
|
|
691
|
+
setTree({ ...currentTree });
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
onComplete?.(currentTree);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
if (err.name === "AbortError") {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
700
|
+
setError(error2);
|
|
701
|
+
onError?.(error2);
|
|
702
|
+
} finally {
|
|
703
|
+
setIsStreaming(false);
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
[api, onComplete, onError]
|
|
707
|
+
);
|
|
708
|
+
useEffect(() => {
|
|
709
|
+
return () => {
|
|
710
|
+
abortControllerRef.current?.abort();
|
|
711
|
+
};
|
|
712
|
+
}, []);
|
|
713
|
+
return {
|
|
714
|
+
tree,
|
|
715
|
+
isStreaming,
|
|
716
|
+
error,
|
|
717
|
+
send,
|
|
718
|
+
clear
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function flatToTree(elements) {
|
|
722
|
+
const elementMap = {};
|
|
723
|
+
let root = "";
|
|
724
|
+
for (const element of elements) {
|
|
725
|
+
elementMap[element.key] = {
|
|
726
|
+
key: element.key,
|
|
727
|
+
type: element.type,
|
|
728
|
+
props: element.props,
|
|
729
|
+
children: [],
|
|
730
|
+
visible: element.visible
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
for (const element of elements) {
|
|
734
|
+
if (element.parentKey) {
|
|
735
|
+
const parent = elementMap[element.parentKey];
|
|
736
|
+
if (parent) {
|
|
737
|
+
if (!parent.children) {
|
|
738
|
+
parent.children = [];
|
|
739
|
+
}
|
|
740
|
+
parent.children.push(element.key);
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
root = element.key;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return { root, elements: elementMap };
|
|
747
|
+
}
|
|
748
|
+
export {
|
|
749
|
+
ActionProvider,
|
|
750
|
+
ConfirmDialog,
|
|
751
|
+
DataProvider,
|
|
752
|
+
JSONUIProvider,
|
|
753
|
+
Renderer,
|
|
754
|
+
ValidationProvider,
|
|
755
|
+
VisibilityProvider,
|
|
756
|
+
createRendererFromCatalog,
|
|
757
|
+
flatToTree,
|
|
758
|
+
useAction,
|
|
759
|
+
useActions,
|
|
760
|
+
useData,
|
|
761
|
+
useDataBinding,
|
|
762
|
+
useDataValue,
|
|
763
|
+
useFieldValidation,
|
|
764
|
+
useIsVisible,
|
|
765
|
+
useUIStream,
|
|
766
|
+
useValidation,
|
|
767
|
+
useVisibility
|
|
768
|
+
};
|
|
769
|
+
//# sourceMappingURL=index.mjs.map
|