@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/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 get = (0, import_react.useCallback)(
68
- (path) => (0, import_core.getByPath)(data, path),
69
- [data]
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 { get } = useData();
116
- return get(path);
125
+ const { data } = useData();
126
+ return (0, import_core.getByPath)(data, path);
117
127
  }
118
128
  function useDataBinding(path) {
119
- const { get, set } = useData();
120
- const value = get(path);
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)((name, handler) => {
179
- setHandlers((prev) => ({ ...prev, [name]: handler }));
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
- [handlers, loadingActions, pendingConfirmation, execute, confirm, cancel, registerHandler]
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({ confirm, onConfirm, onCancel }) {
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)((path, config) => {
390
- setFieldConfigs((prev) => ({ ...prev, [path]: config }));
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
- [customFunctions, fieldStates, validate, touch, clear, validateAll, registerField]
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
- tree,
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 = tree.elements[childKey];
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
- tree,
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({ tree, registry, loading, fallback }) {
542
- if (!tree || !tree.root) {
616
+ function Renderer({ spec, registry, loading, fallback }) {
617
+ if (!spec || !spec.root) {
543
618
  return null;
544
619
  }
545
- const rootElement = tree.elements[tree.root];
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
- tree,
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 import_core5 = require("@json-render/core");
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(tree, patch) {
618
- const newTree = { ...tree, elements: { ...tree.elements } };
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
- newTree.root = patch.value;
625
- return newTree;
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 newTree;
745
+ if (!elementKey) return newSpec;
631
746
  if (pathParts.length === 1) {
632
- newTree.elements[elementKey] = patch.value;
747
+ newSpec.elements[elementKey] = patch.value;
633
748
  } else {
634
- const element = newTree.elements[elementKey];
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, import_core5.setByPath)(newElement, propPath, patch.value);
639
- newTree.elements[elementKey] = newElement;
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 } = newTree.elements;
650
- newTree.elements = rest;
768
+ const { [elementKey]: _, ...rest } = newSpec.elements;
769
+ newSpec.elements = rest;
651
770
  }
652
771
  }
653
772
  break;
654
773
  }
655
774
  }
656
- return newTree;
775
+ return newSpec;
657
776
  }
658
777
  function useUIStream({
659
778
  api,
660
779
  onComplete,
661
780
  onError
662
781
  }) {
663
- const [tree, setTree] = (0, import_react5.useState)(null);
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
- setTree(null);
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
- let currentTree = { root: "", elements: {} };
678
- setTree(currentTree);
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
- currentTree
806
+ currentSpec
687
807
  }),
688
808
  signal: abortControllerRef.current.signal
689
809
  });
690
810
  if (!response.ok) {
691
- throw new Error(`HTTP error: ${response.status}`);
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
- currentTree = applyPatch(currentTree, patch);
709
- setTree({ ...currentTree });
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
- currentTree = applyPatch(currentTree, patch);
717
- setTree({ ...currentTree });
846
+ currentSpec = applyPatch(currentSpec, patch);
847
+ setSpec({ ...currentSpec });
718
848
  }
719
849
  }
720
- onComplete?.(currentTree);
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
- tree,
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,