@json-render/react 0.2.0 → 0.4.2

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/dist/index.js CHANGED
@@ -37,8 +37,12 @@ __export(index_exports, {
37
37
  Renderer: () => Renderer,
38
38
  ValidationProvider: () => ValidationProvider,
39
39
  VisibilityProvider: () => VisibilityProvider,
40
+ createRenderer: () => createRenderer,
40
41
  createRendererFromCatalog: () => createRendererFromCatalog,
42
+ defineRegistry: () => defineRegistry,
43
+ elementTreeSchema: () => elementTreeSchema,
41
44
  flatToTree: () => flatToTree,
45
+ schema: () => schema,
42
46
  useAction: () => useAction,
43
47
  useActions: () => useActions,
44
48
  useData: () => useData,
@@ -64,6 +68,16 @@ function DataProvider({
64
68
  children
65
69
  }) {
66
70
  const [data, setData] = (0, import_react.useState)(initialData);
71
+ const initialDataJsonRef = (0, import_react.useRef)(JSON.stringify(initialData));
72
+ (0, import_react.useEffect)(() => {
73
+ const newJson = JSON.stringify(initialData);
74
+ if (newJson !== initialDataJsonRef.current) {
75
+ initialDataJsonRef.current = newJson;
76
+ if (initialData && Object.keys(initialData).length > 0) {
77
+ setData((prev) => ({ ...prev, ...initialData }));
78
+ }
79
+ }
80
+ }, [initialData]);
67
81
  const get = (0, import_react.useCallback)((path) => (0, import_core.getByPath)(data, path), [data]);
68
82
  const set = (0, import_react.useCallback)(
69
83
  (path, value2) => {
@@ -109,12 +123,12 @@ function useData() {
109
123
  return ctx;
110
124
  }
111
125
  function useDataValue(path) {
112
- const { get } = useData();
113
- return get(path);
126
+ const { data } = useData();
127
+ return (0, import_core.getByPath)(data, path);
114
128
  }
115
129
  function useDataBinding(path) {
116
- const { get, set } = useData();
117
- const value = get(path);
130
+ const { data, set } = useData();
131
+ const value = (0, import_core.getByPath)(data, path);
118
132
  const setValue = (0, import_react.useCallback)(
119
133
  (newValue) => set(path, newValue),
120
134
  [path, set]
@@ -515,11 +529,58 @@ function useFieldValidation(path, config) {
515
529
  };
516
530
  }
517
531
 
532
+ // src/schema.ts
533
+ var import_core5 = require("@json-render/core");
534
+ var schema = (0, import_core5.defineSchema)((s) => ({
535
+ // What the AI-generated SPEC looks like
536
+ spec: s.object({
537
+ /** Root element key */
538
+ root: s.string(),
539
+ /** Flat map of elements by key */
540
+ elements: s.record(
541
+ s.object({
542
+ /** Unique key for this element */
543
+ key: s.string(),
544
+ /** Component type from catalog */
545
+ type: s.ref("catalog.components"),
546
+ /** Component props */
547
+ props: s.propsOf("catalog.components"),
548
+ /** Child element keys (flat reference) */
549
+ children: s.array(s.string()),
550
+ /** Parent element key (null for root) */
551
+ parentKey: s.string(),
552
+ /** Visibility condition */
553
+ visible: s.any()
554
+ })
555
+ )
556
+ }),
557
+ // What the CATALOG must provide
558
+ catalog: s.object({
559
+ /** Component definitions */
560
+ components: s.map({
561
+ /** Zod schema for component props */
562
+ props: s.zod(),
563
+ /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
564
+ slots: s.array(s.string()),
565
+ /** Description for AI generation hints */
566
+ description: s.string()
567
+ }),
568
+ /** Action definitions (optional) */
569
+ actions: s.map({
570
+ /** Zod schema for action params */
571
+ params: s.zod(),
572
+ /** Description for AI generation hints */
573
+ description: s.string()
574
+ })
575
+ })
576
+ }));
577
+ var elementTreeSchema = schema;
578
+
518
579
  // src/renderer.tsx
519
580
  var import_jsx_runtime5 = require("react/jsx-runtime");
520
581
  function ElementRenderer({
521
582
  element,
522
- tree,
583
+ spec,
523
584
  registry,
524
585
  loading,
525
586
  fallback
@@ -535,7 +596,7 @@ function ElementRenderer({
535
596
  return null;
536
597
  }
537
598
  const children = element.children?.map((childKey) => {
538
- const childElement = tree.elements[childKey];
599
+ const childElement = spec.elements[childKey];
539
600
  if (!childElement) {
540
601
  return null;
541
602
  }
@@ -543,7 +604,7 @@ function ElementRenderer({
543
604
  ElementRenderer,
544
605
  {
545
606
  element: childElement,
546
- tree,
607
+ spec,
547
608
  registry,
548
609
  loading,
549
610
  fallback
@@ -553,11 +614,11 @@ function ElementRenderer({
553
614
  });
554
615
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Component, { element, onAction: execute, loading, children });
555
616
  }
556
- function Renderer({ tree, registry, loading, fallback }) {
557
- if (!tree || !tree.root) {
617
+ function Renderer({ spec, registry, loading, fallback }) {
618
+ if (!spec || !spec.root) {
558
619
  return null;
559
620
  }
560
- const rootElement = tree.elements[tree.root];
621
+ const rootElement = spec.elements[spec.root];
561
622
  if (!rootElement) {
562
623
  return null;
563
624
  }
@@ -565,7 +626,7 @@ function Renderer({ tree, registry, loading, fallback }) {
565
626
  ElementRenderer,
566
627
  {
567
628
  element: rootElement,
568
- tree,
629
+ spec,
569
630
  registry,
570
631
  loading,
571
632
  fallback
@@ -614,10 +675,93 @@ function createRendererFromCatalog(_catalog, registry) {
614
675
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Renderer, { ...props, registry });
615
676
  };
616
677
  }
678
+ function defineRegistry(_catalog, options) {
679
+ const registry = {};
680
+ if (options.components) {
681
+ for (const [name, componentFn] of Object.entries(options.components)) {
682
+ registry[name] = ({
683
+ element,
684
+ children,
685
+ onAction,
686
+ loading
687
+ }) => {
688
+ return componentFn({
689
+ props: element.props,
690
+ children,
691
+ onAction,
692
+ loading
693
+ });
694
+ };
695
+ }
696
+ }
697
+ const actionMap = options.actions ? Object.entries(options.actions) : [];
698
+ const handlers = (getSetData, getData) => {
699
+ const result = {};
700
+ for (const [name, actionFn] of actionMap) {
701
+ result[name] = async (params) => {
702
+ const setData = getSetData();
703
+ const data = getData();
704
+ if (setData) {
705
+ await actionFn(params, setData, data);
706
+ }
707
+ };
708
+ }
709
+ return result;
710
+ };
711
+ const executeAction2 = async (actionName, params, setData, data = {}) => {
712
+ const entry = actionMap.find(([name]) => name === actionName);
713
+ if (entry) {
714
+ await entry[1](params, setData, data);
715
+ } else {
716
+ console.warn(`Unknown action: ${actionName}`);
717
+ }
718
+ };
719
+ return { registry, handlers, executeAction: executeAction2 };
720
+ }
721
+ function createRenderer(catalog, components) {
722
+ const registry = components;
723
+ return function CatalogRenderer({
724
+ spec,
725
+ data,
726
+ onAction,
727
+ onDataChange,
728
+ loading,
729
+ authState,
730
+ fallback
731
+ }) {
732
+ const actionHandlers = onAction ? {
733
+ __default__: (params) => {
734
+ const actionName = params.__actionName__;
735
+ const actionParams = params.__actionParams__;
736
+ return onAction(actionName, actionParams);
737
+ }
738
+ } : void 0;
739
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
740
+ DataProvider,
741
+ {
742
+ initialData: data,
743
+ authState,
744
+ onDataChange,
745
+ 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: [
746
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
747
+ Renderer,
748
+ {
749
+ spec,
750
+ registry,
751
+ loading,
752
+ fallback
753
+ }
754
+ ),
755
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConfirmationDialogManager, {})
756
+ ] }) }) })
757
+ }
758
+ );
759
+ };
760
+ }
617
761
 
618
762
  // src/hooks.ts
619
763
  var import_react5 = require("react");
620
- var import_core5 = require("@json-render/core");
764
+ var import_core6 = require("@json-render/core");
621
765
  function parsePatchLine(line) {
622
766
  try {
623
767
  const trimmed = line.trim();
@@ -629,33 +773,33 @@ function parsePatchLine(line) {
629
773
  return null;
630
774
  }
631
775
  }
632
- function applyPatch(tree, patch) {
633
- const newTree = { ...tree, elements: { ...tree.elements } };
776
+ function applyPatch(spec, patch) {
777
+ const newSpec = { ...spec, elements: { ...spec.elements } };
634
778
  switch (patch.op) {
635
779
  case "set":
636
780
  case "add":
637
781
  case "replace": {
638
782
  if (patch.path === "/root") {
639
- newTree.root = patch.value;
640
- return newTree;
783
+ newSpec.root = patch.value;
784
+ return newSpec;
641
785
  }
642
786
  if (patch.path.startsWith("/elements/")) {
643
787
  const pathParts = patch.path.slice("/elements/".length).split("/");
644
788
  const elementKey = pathParts[0];
645
- if (!elementKey) return newTree;
789
+ if (!elementKey) return newSpec;
646
790
  if (pathParts.length === 1) {
647
- newTree.elements[elementKey] = patch.value;
791
+ newSpec.elements[elementKey] = patch.value;
648
792
  } else {
649
- const element = newTree.elements[elementKey];
793
+ const element = newSpec.elements[elementKey];
650
794
  if (element) {
651
795
  const propPath = "/" + pathParts.slice(1).join("/");
652
796
  const newElement = { ...element };
653
- (0, import_core5.setByPath)(
797
+ (0, import_core6.setByPath)(
654
798
  newElement,
655
799
  propPath,
656
800
  patch.value
657
801
  );
658
- newTree.elements[elementKey] = newElement;
802
+ newSpec.elements[elementKey] = newElement;
659
803
  }
660
804
  }
661
805
  }
@@ -665,26 +809,26 @@ function applyPatch(tree, patch) {
665
809
  if (patch.path.startsWith("/elements/")) {
666
810
  const elementKey = patch.path.slice("/elements/".length).split("/")[0];
667
811
  if (elementKey) {
668
- const { [elementKey]: _, ...rest } = newTree.elements;
669
- newTree.elements = rest;
812
+ const { [elementKey]: _, ...rest } = newSpec.elements;
813
+ newSpec.elements = rest;
670
814
  }
671
815
  }
672
816
  break;
673
817
  }
674
818
  }
675
- return newTree;
819
+ return newSpec;
676
820
  }
677
821
  function useUIStream({
678
822
  api,
679
823
  onComplete,
680
824
  onError
681
825
  }) {
682
- const [tree, setTree] = (0, import_react5.useState)(null);
826
+ const [spec, setSpec] = (0, import_react5.useState)(null);
683
827
  const [isStreaming, setIsStreaming] = (0, import_react5.useState)(false);
684
828
  const [error, setError] = (0, import_react5.useState)(null);
685
829
  const abortControllerRef = (0, import_react5.useRef)(null);
686
830
  const clear = (0, import_react5.useCallback)(() => {
687
- setTree(null);
831
+ setSpec(null);
688
832
  setError(null);
689
833
  }, []);
690
834
  const send = (0, import_react5.useCallback)(
@@ -693,8 +837,9 @@ function useUIStream({
693
837
  abortControllerRef.current = new AbortController();
694
838
  setIsStreaming(true);
695
839
  setError(null);
696
- let currentTree = { root: "", elements: {} };
697
- setTree(currentTree);
840
+ const previousSpec = context?.previousSpec;
841
+ let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
842
+ setSpec(currentSpec);
698
843
  try {
699
844
  const response = await fetch(api, {
700
845
  method: "POST",
@@ -702,12 +847,22 @@ function useUIStream({
702
847
  body: JSON.stringify({
703
848
  prompt,
704
849
  context,
705
- currentTree
850
+ currentSpec
706
851
  }),
707
852
  signal: abortControllerRef.current.signal
708
853
  });
709
854
  if (!response.ok) {
710
- throw new Error(`HTTP error: ${response.status}`);
855
+ let errorMessage = `HTTP error: ${response.status}`;
856
+ try {
857
+ const errorData = await response.json();
858
+ if (errorData.message) {
859
+ errorMessage = errorData.message;
860
+ } else if (errorData.error) {
861
+ errorMessage = errorData.error;
862
+ }
863
+ } catch {
864
+ }
865
+ throw new Error(errorMessage);
711
866
  }
712
867
  const reader = response.body?.getReader();
713
868
  if (!reader) {
@@ -724,19 +879,19 @@ function useUIStream({
724
879
  for (const line of lines) {
725
880
  const patch = parsePatchLine(line);
726
881
  if (patch) {
727
- currentTree = applyPatch(currentTree, patch);
728
- setTree({ ...currentTree });
882
+ currentSpec = applyPatch(currentSpec, patch);
883
+ setSpec({ ...currentSpec });
729
884
  }
730
885
  }
731
886
  }
732
887
  if (buffer.trim()) {
733
888
  const patch = parsePatchLine(buffer);
734
889
  if (patch) {
735
- currentTree = applyPatch(currentTree, patch);
736
- setTree({ ...currentTree });
890
+ currentSpec = applyPatch(currentSpec, patch);
891
+ setSpec({ ...currentSpec });
737
892
  }
738
893
  }
739
- onComplete?.(currentTree);
894
+ onComplete?.(currentSpec);
740
895
  } catch (err) {
741
896
  if (err.name === "AbortError") {
742
897
  return;
@@ -756,7 +911,7 @@ function useUIStream({
756
911
  };
757
912
  }, []);
758
913
  return {
759
- tree,
914
+ spec,
760
915
  isStreaming,
761
916
  error,
762
917
  send,
@@ -799,8 +954,12 @@ function flatToTree(elements) {
799
954
  Renderer,
800
955
  ValidationProvider,
801
956
  VisibilityProvider,
957
+ createRenderer,
802
958
  createRendererFromCatalog,
959
+ defineRegistry,
960
+ elementTreeSchema,
803
961
  flatToTree,
962
+ schema,
804
963
  useAction,
805
964
  useActions,
806
965
  useData,