@json-render/react 0.1.0 → 0.4.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 +253 -175
- package/dist/chunk-IGPI5WNB.mjs +52 -0
- package/dist/chunk-IGPI5WNB.mjs.map +1 -0
- package/dist/index.d.mts +139 -17
- package/dist/index.d.ts +139 -17
- package/dist/index.js +191 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +156 -62
- package/dist/index.mjs.map +1 -1
- package/dist/schema.d.mts +106 -0
- package/dist/schema.d.ts +106 -0
- package/dist/schema.js +77 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.mjs +9 -0
- package/dist/schema.mjs.map +1 -0
- package/package.json +15 -10
package/dist/index.js
CHANGED
|
@@ -37,8 +37,11 @@ __export(index_exports, {
|
|
|
37
37
|
Renderer: () => Renderer,
|
|
38
38
|
ValidationProvider: () => ValidationProvider,
|
|
39
39
|
VisibilityProvider: () => VisibilityProvider,
|
|
40
|
+
createRenderer: () => createRenderer,
|
|
40
41
|
createRendererFromCatalog: () => createRendererFromCatalog,
|
|
42
|
+
elementTreeSchema: () => elementTreeSchema,
|
|
41
43
|
flatToTree: () => flatToTree,
|
|
44
|
+
schema: () => schema,
|
|
42
45
|
useAction: () => useAction,
|
|
43
46
|
useActions: () => useActions,
|
|
44
47
|
useData: () => useData,
|
|
@@ -64,10 +67,17 @@ function DataProvider({
|
|
|
64
67
|
children
|
|
65
68
|
}) {
|
|
66
69
|
const [data, setData] = (0, import_react.useState)(initialData);
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const initialDataJsonRef = (0, import_react.useRef)(JSON.stringify(initialData));
|
|
71
|
+
(0, import_react.useEffect)(() => {
|
|
72
|
+
const newJson = JSON.stringify(initialData);
|
|
73
|
+
if (newJson !== initialDataJsonRef.current) {
|
|
74
|
+
initialDataJsonRef.current = newJson;
|
|
75
|
+
if (initialData && Object.keys(initialData).length > 0) {
|
|
76
|
+
setData((prev) => ({ ...prev, ...initialData }));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, [initialData]);
|
|
80
|
+
const get = (0, import_react.useCallback)((path) => (0, import_core.getByPath)(data, path), [data]);
|
|
71
81
|
const set = (0, import_react.useCallback)(
|
|
72
82
|
(path, value2) => {
|
|
73
83
|
setData((prev) => {
|
|
@@ -112,12 +122,12 @@ function useData() {
|
|
|
112
122
|
return ctx;
|
|
113
123
|
}
|
|
114
124
|
function useDataValue(path) {
|
|
115
|
-
const {
|
|
116
|
-
return
|
|
125
|
+
const { data } = useData();
|
|
126
|
+
return (0, import_core.getByPath)(data, path);
|
|
117
127
|
}
|
|
118
128
|
function useDataBinding(path) {
|
|
119
|
-
const {
|
|
120
|
-
const value =
|
|
129
|
+
const { data, set } = useData();
|
|
130
|
+
const value = (0, import_core.getByPath)(data, path);
|
|
121
131
|
const setValue = (0, import_react.useCallback)(
|
|
122
132
|
(newValue) => set(path, newValue),
|
|
123
133
|
[path, set]
|
|
@@ -175,9 +185,12 @@ function ActionProvider({
|
|
|
175
185
|
const [handlers, setHandlers] = (0, import_react3.useState)(initialHandlers);
|
|
176
186
|
const [loadingActions, setLoadingActions] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
|
177
187
|
const [pendingConfirmation, setPendingConfirmation] = (0, import_react3.useState)(null);
|
|
178
|
-
const registerHandler = (0, import_react3.useCallback)(
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
const registerHandler = (0, import_react3.useCallback)(
|
|
189
|
+
(name, handler) => {
|
|
190
|
+
setHandlers((prev) => ({ ...prev, [name]: handler }));
|
|
191
|
+
},
|
|
192
|
+
[]
|
|
193
|
+
);
|
|
181
194
|
const execute = (0, import_react3.useCallback)(
|
|
182
195
|
async (action) => {
|
|
183
196
|
const resolved = (0, import_core3.resolveAction)(action, data);
|
|
@@ -260,7 +273,15 @@ function ActionProvider({
|
|
|
260
273
|
cancel,
|
|
261
274
|
registerHandler
|
|
262
275
|
}),
|
|
263
|
-
[
|
|
276
|
+
[
|
|
277
|
+
handlers,
|
|
278
|
+
loadingActions,
|
|
279
|
+
pendingConfirmation,
|
|
280
|
+
execute,
|
|
281
|
+
confirm,
|
|
282
|
+
cancel,
|
|
283
|
+
registerHandler
|
|
284
|
+
]
|
|
264
285
|
);
|
|
265
286
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionContext.Provider, { value, children });
|
|
266
287
|
}
|
|
@@ -277,7 +298,11 @@ function useAction(action) {
|
|
|
277
298
|
const executeAction2 = (0, import_react3.useCallback)(() => execute(action), [execute, action]);
|
|
278
299
|
return { execute: executeAction2, isLoading };
|
|
279
300
|
}
|
|
280
|
-
function ConfirmDialog({
|
|
301
|
+
function ConfirmDialog({
|
|
302
|
+
confirm,
|
|
303
|
+
onConfirm,
|
|
304
|
+
onCancel
|
|
305
|
+
}) {
|
|
281
306
|
const isDanger = confirm.variant === "danger";
|
|
282
307
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
283
308
|
"div",
|
|
@@ -386,9 +411,12 @@ function ValidationProvider({
|
|
|
386
411
|
const { data, authState } = useData();
|
|
387
412
|
const [fieldStates, setFieldStates] = (0, import_react4.useState)({});
|
|
388
413
|
const [fieldConfigs, setFieldConfigs] = (0, import_react4.useState)({});
|
|
389
|
-
const registerField = (0, import_react4.useCallback)(
|
|
390
|
-
|
|
391
|
-
|
|
414
|
+
const registerField = (0, import_react4.useCallback)(
|
|
415
|
+
(path, config) => {
|
|
416
|
+
setFieldConfigs((prev) => ({ ...prev, [path]: config }));
|
|
417
|
+
},
|
|
418
|
+
[]
|
|
419
|
+
);
|
|
392
420
|
const validate = (0, import_react4.useCallback)(
|
|
393
421
|
(path, config) => {
|
|
394
422
|
const value2 = data[path.split("/").filter(Boolean).join(".")];
|
|
@@ -447,7 +475,15 @@ function ValidationProvider({
|
|
|
447
475
|
validateAll,
|
|
448
476
|
registerField
|
|
449
477
|
}),
|
|
450
|
-
[
|
|
478
|
+
[
|
|
479
|
+
customFunctions,
|
|
480
|
+
fieldStates,
|
|
481
|
+
validate,
|
|
482
|
+
touch,
|
|
483
|
+
clear,
|
|
484
|
+
validateAll,
|
|
485
|
+
registerField
|
|
486
|
+
]
|
|
451
487
|
);
|
|
452
488
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ValidationContext.Provider, { value, children });
|
|
453
489
|
}
|
|
@@ -492,11 +528,58 @@ function useFieldValidation(path, config) {
|
|
|
492
528
|
};
|
|
493
529
|
}
|
|
494
530
|
|
|
531
|
+
// src/schema.ts
|
|
532
|
+
var import_core5 = require("@json-render/core");
|
|
533
|
+
var schema = (0, import_core5.defineSchema)((s) => ({
|
|
534
|
+
// What the AI-generated SPEC looks like
|
|
535
|
+
spec: s.object({
|
|
536
|
+
/** Root element key */
|
|
537
|
+
root: s.string(),
|
|
538
|
+
/** Flat map of elements by key */
|
|
539
|
+
elements: s.record(
|
|
540
|
+
s.object({
|
|
541
|
+
/** Unique key for this element */
|
|
542
|
+
key: s.string(),
|
|
543
|
+
/** Component type from catalog */
|
|
544
|
+
type: s.ref("catalog.components"),
|
|
545
|
+
/** Component props */
|
|
546
|
+
props: s.propsOf("catalog.components"),
|
|
547
|
+
/** Child element keys (flat reference) */
|
|
548
|
+
children: s.array(s.string()),
|
|
549
|
+
/** Parent element key (null for root) */
|
|
550
|
+
parentKey: s.string(),
|
|
551
|
+
/** Visibility condition */
|
|
552
|
+
visible: s.any()
|
|
553
|
+
})
|
|
554
|
+
)
|
|
555
|
+
}),
|
|
556
|
+
// What the CATALOG must provide
|
|
557
|
+
catalog: s.object({
|
|
558
|
+
/** Component definitions */
|
|
559
|
+
components: s.map({
|
|
560
|
+
/** Zod schema for component props */
|
|
561
|
+
props: s.zod(),
|
|
562
|
+
/** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
|
|
563
|
+
slots: s.array(s.string()),
|
|
564
|
+
/** Description for AI generation hints */
|
|
565
|
+
description: s.string()
|
|
566
|
+
}),
|
|
567
|
+
/** Action definitions (optional) */
|
|
568
|
+
actions: s.map({
|
|
569
|
+
/** Zod schema for action params */
|
|
570
|
+
params: s.zod(),
|
|
571
|
+
/** Description for AI generation hints */
|
|
572
|
+
description: s.string()
|
|
573
|
+
})
|
|
574
|
+
})
|
|
575
|
+
}));
|
|
576
|
+
var elementTreeSchema = schema;
|
|
577
|
+
|
|
495
578
|
// src/renderer.tsx
|
|
496
579
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
497
580
|
function ElementRenderer({
|
|
498
581
|
element,
|
|
499
|
-
|
|
582
|
+
spec,
|
|
500
583
|
registry,
|
|
501
584
|
loading,
|
|
502
585
|
fallback
|
|
@@ -512,7 +595,7 @@ function ElementRenderer({
|
|
|
512
595
|
return null;
|
|
513
596
|
}
|
|
514
597
|
const children = element.children?.map((childKey) => {
|
|
515
|
-
const childElement =
|
|
598
|
+
const childElement = spec.elements[childKey];
|
|
516
599
|
if (!childElement) {
|
|
517
600
|
return null;
|
|
518
601
|
}
|
|
@@ -520,7 +603,7 @@ function ElementRenderer({
|
|
|
520
603
|
ElementRenderer,
|
|
521
604
|
{
|
|
522
605
|
element: childElement,
|
|
523
|
-
|
|
606
|
+
spec,
|
|
524
607
|
registry,
|
|
525
608
|
loading,
|
|
526
609
|
fallback
|
|
@@ -528,21 +611,13 @@ function ElementRenderer({
|
|
|
528
611
|
childKey
|
|
529
612
|
);
|
|
530
613
|
});
|
|
531
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
532
|
-
Component,
|
|
533
|
-
{
|
|
534
|
-
element,
|
|
535
|
-
onAction: execute,
|
|
536
|
-
loading,
|
|
537
|
-
children
|
|
538
|
-
}
|
|
539
|
-
);
|
|
614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Component, { element, onAction: execute, loading, children });
|
|
540
615
|
}
|
|
541
|
-
function Renderer({
|
|
542
|
-
if (!
|
|
616
|
+
function Renderer({ spec, registry, loading, fallback }) {
|
|
617
|
+
if (!spec || !spec.root) {
|
|
543
618
|
return null;
|
|
544
619
|
}
|
|
545
|
-
const rootElement =
|
|
620
|
+
const rootElement = spec.elements[spec.root];
|
|
546
621
|
if (!rootElement) {
|
|
547
622
|
return null;
|
|
548
623
|
}
|
|
@@ -550,7 +625,7 @@ function Renderer({ tree, registry, loading, fallback }) {
|
|
|
550
625
|
ElementRenderer,
|
|
551
626
|
{
|
|
552
627
|
element: rootElement,
|
|
553
|
-
|
|
628
|
+
spec,
|
|
554
629
|
registry,
|
|
555
630
|
loading,
|
|
556
631
|
fallback
|
|
@@ -599,10 +674,50 @@ function createRendererFromCatalog(_catalog, registry) {
|
|
|
599
674
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Renderer, { ...props, registry });
|
|
600
675
|
};
|
|
601
676
|
}
|
|
677
|
+
function createRenderer(catalog, components) {
|
|
678
|
+
const registry = components;
|
|
679
|
+
return function CatalogRenderer({
|
|
680
|
+
spec,
|
|
681
|
+
data,
|
|
682
|
+
onAction,
|
|
683
|
+
onDataChange,
|
|
684
|
+
loading,
|
|
685
|
+
authState,
|
|
686
|
+
fallback
|
|
687
|
+
}) {
|
|
688
|
+
const actionHandlers = onAction ? {
|
|
689
|
+
__default__: (params) => {
|
|
690
|
+
const actionName = params.__actionName__;
|
|
691
|
+
const actionParams = params.__actionParams__;
|
|
692
|
+
return onAction(actionName, actionParams);
|
|
693
|
+
}
|
|
694
|
+
} : void 0;
|
|
695
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
696
|
+
DataProvider,
|
|
697
|
+
{
|
|
698
|
+
initialData: data,
|
|
699
|
+
authState,
|
|
700
|
+
onDataChange,
|
|
701
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ValidationProvider, { children: [
|
|
702
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
703
|
+
Renderer,
|
|
704
|
+
{
|
|
705
|
+
spec,
|
|
706
|
+
registry,
|
|
707
|
+
loading,
|
|
708
|
+
fallback
|
|
709
|
+
}
|
|
710
|
+
),
|
|
711
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConfirmationDialogManager, {})
|
|
712
|
+
] }) }) })
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
};
|
|
716
|
+
}
|
|
602
717
|
|
|
603
718
|
// src/hooks.ts
|
|
604
719
|
var import_react5 = require("react");
|
|
605
|
-
var
|
|
720
|
+
var import_core6 = require("@json-render/core");
|
|
606
721
|
function parsePatchLine(line) {
|
|
607
722
|
try {
|
|
608
723
|
const trimmed = line.trim();
|
|
@@ -614,29 +729,33 @@ function parsePatchLine(line) {
|
|
|
614
729
|
return null;
|
|
615
730
|
}
|
|
616
731
|
}
|
|
617
|
-
function applyPatch(
|
|
618
|
-
const
|
|
732
|
+
function applyPatch(spec, patch) {
|
|
733
|
+
const newSpec = { ...spec, elements: { ...spec.elements } };
|
|
619
734
|
switch (patch.op) {
|
|
620
735
|
case "set":
|
|
621
736
|
case "add":
|
|
622
737
|
case "replace": {
|
|
623
738
|
if (patch.path === "/root") {
|
|
624
|
-
|
|
625
|
-
return
|
|
739
|
+
newSpec.root = patch.value;
|
|
740
|
+
return newSpec;
|
|
626
741
|
}
|
|
627
742
|
if (patch.path.startsWith("/elements/")) {
|
|
628
743
|
const pathParts = patch.path.slice("/elements/".length).split("/");
|
|
629
744
|
const elementKey = pathParts[0];
|
|
630
|
-
if (!elementKey) return
|
|
745
|
+
if (!elementKey) return newSpec;
|
|
631
746
|
if (pathParts.length === 1) {
|
|
632
|
-
|
|
747
|
+
newSpec.elements[elementKey] = patch.value;
|
|
633
748
|
} else {
|
|
634
|
-
const element =
|
|
749
|
+
const element = newSpec.elements[elementKey];
|
|
635
750
|
if (element) {
|
|
636
751
|
const propPath = "/" + pathParts.slice(1).join("/");
|
|
637
752
|
const newElement = { ...element };
|
|
638
|
-
(0,
|
|
639
|
-
|
|
753
|
+
(0, import_core6.setByPath)(
|
|
754
|
+
newElement,
|
|
755
|
+
propPath,
|
|
756
|
+
patch.value
|
|
757
|
+
);
|
|
758
|
+
newSpec.elements[elementKey] = newElement;
|
|
640
759
|
}
|
|
641
760
|
}
|
|
642
761
|
}
|
|
@@ -646,26 +765,26 @@ function applyPatch(tree, patch) {
|
|
|
646
765
|
if (patch.path.startsWith("/elements/")) {
|
|
647
766
|
const elementKey = patch.path.slice("/elements/".length).split("/")[0];
|
|
648
767
|
if (elementKey) {
|
|
649
|
-
const { [elementKey]: _, ...rest } =
|
|
650
|
-
|
|
768
|
+
const { [elementKey]: _, ...rest } = newSpec.elements;
|
|
769
|
+
newSpec.elements = rest;
|
|
651
770
|
}
|
|
652
771
|
}
|
|
653
772
|
break;
|
|
654
773
|
}
|
|
655
774
|
}
|
|
656
|
-
return
|
|
775
|
+
return newSpec;
|
|
657
776
|
}
|
|
658
777
|
function useUIStream({
|
|
659
778
|
api,
|
|
660
779
|
onComplete,
|
|
661
780
|
onError
|
|
662
781
|
}) {
|
|
663
|
-
const [
|
|
782
|
+
const [spec, setSpec] = (0, import_react5.useState)(null);
|
|
664
783
|
const [isStreaming, setIsStreaming] = (0, import_react5.useState)(false);
|
|
665
784
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
666
785
|
const abortControllerRef = (0, import_react5.useRef)(null);
|
|
667
786
|
const clear = (0, import_react5.useCallback)(() => {
|
|
668
|
-
|
|
787
|
+
setSpec(null);
|
|
669
788
|
setError(null);
|
|
670
789
|
}, []);
|
|
671
790
|
const send = (0, import_react5.useCallback)(
|
|
@@ -674,8 +793,9 @@ function useUIStream({
|
|
|
674
793
|
abortControllerRef.current = new AbortController();
|
|
675
794
|
setIsStreaming(true);
|
|
676
795
|
setError(null);
|
|
677
|
-
|
|
678
|
-
|
|
796
|
+
const previousSpec = context?.previousSpec;
|
|
797
|
+
let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
|
|
798
|
+
setSpec(currentSpec);
|
|
679
799
|
try {
|
|
680
800
|
const response = await fetch(api, {
|
|
681
801
|
method: "POST",
|
|
@@ -683,12 +803,22 @@ function useUIStream({
|
|
|
683
803
|
body: JSON.stringify({
|
|
684
804
|
prompt,
|
|
685
805
|
context,
|
|
686
|
-
|
|
806
|
+
currentSpec
|
|
687
807
|
}),
|
|
688
808
|
signal: abortControllerRef.current.signal
|
|
689
809
|
});
|
|
690
810
|
if (!response.ok) {
|
|
691
|
-
|
|
811
|
+
let errorMessage = `HTTP error: ${response.status}`;
|
|
812
|
+
try {
|
|
813
|
+
const errorData = await response.json();
|
|
814
|
+
if (errorData.message) {
|
|
815
|
+
errorMessage = errorData.message;
|
|
816
|
+
} else if (errorData.error) {
|
|
817
|
+
errorMessage = errorData.error;
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
throw new Error(errorMessage);
|
|
692
822
|
}
|
|
693
823
|
const reader = response.body?.getReader();
|
|
694
824
|
if (!reader) {
|
|
@@ -705,19 +835,19 @@ function useUIStream({
|
|
|
705
835
|
for (const line of lines) {
|
|
706
836
|
const patch = parsePatchLine(line);
|
|
707
837
|
if (patch) {
|
|
708
|
-
|
|
709
|
-
|
|
838
|
+
currentSpec = applyPatch(currentSpec, patch);
|
|
839
|
+
setSpec({ ...currentSpec });
|
|
710
840
|
}
|
|
711
841
|
}
|
|
712
842
|
}
|
|
713
843
|
if (buffer.trim()) {
|
|
714
844
|
const patch = parsePatchLine(buffer);
|
|
715
845
|
if (patch) {
|
|
716
|
-
|
|
717
|
-
|
|
846
|
+
currentSpec = applyPatch(currentSpec, patch);
|
|
847
|
+
setSpec({ ...currentSpec });
|
|
718
848
|
}
|
|
719
849
|
}
|
|
720
|
-
onComplete?.(
|
|
850
|
+
onComplete?.(currentSpec);
|
|
721
851
|
} catch (err) {
|
|
722
852
|
if (err.name === "AbortError") {
|
|
723
853
|
return;
|
|
@@ -737,7 +867,7 @@ function useUIStream({
|
|
|
737
867
|
};
|
|
738
868
|
}, []);
|
|
739
869
|
return {
|
|
740
|
-
|
|
870
|
+
spec,
|
|
741
871
|
isStreaming,
|
|
742
872
|
error,
|
|
743
873
|
send,
|
|
@@ -780,8 +910,11 @@ function flatToTree(elements) {
|
|
|
780
910
|
Renderer,
|
|
781
911
|
ValidationProvider,
|
|
782
912
|
VisibilityProvider,
|
|
913
|
+
createRenderer,
|
|
783
914
|
createRendererFromCatalog,
|
|
915
|
+
elementTreeSchema,
|
|
784
916
|
flatToTree,
|
|
917
|
+
schema,
|
|
785
918
|
useAction,
|
|
786
919
|
useActions,
|
|
787
920
|
useData,
|