@json-render/react-native 0.5.1 → 0.6.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.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  standardActionDefinitions,
3
3
  standardComponentDefinitions
4
- } from "./chunk-MP3Y6GEF.mjs";
4
+ } from "./chunk-QYY3Q42V.mjs";
5
5
  import {
6
6
  elementTreeSchema,
7
7
  schema
8
- } from "./chunk-VBNJFRLI.mjs";
8
+ } from "./chunk-T27PQA6P.mjs";
9
9
 
10
10
  // src/contexts/state.tsx
11
11
  import {
@@ -17,19 +17,17 @@ import {
17
17
  useEffect,
18
18
  useRef
19
19
  } from "react";
20
- import {
21
- getByPath,
22
- setByPath
23
- } from "@json-render/core";
20
+ import { getByPath, setByPath } from "@json-render/core";
24
21
  import { jsx } from "react/jsx-runtime";
25
22
  var StateContext = createContext(null);
26
23
  function StateProvider({
27
24
  initialState = {},
28
- authState,
29
25
  onStateChange,
30
26
  children
31
27
  }) {
32
28
  const [state, setStateInternal] = useState(initialState);
29
+ const stateRef = useRef(state);
30
+ stateRef.current = state;
33
31
  const initialStateJsonRef = useRef(JSON.stringify(initialState));
34
32
  useEffect(() => {
35
33
  const newJson = JSON.stringify(initialState);
@@ -40,7 +38,10 @@ function StateProvider({
40
38
  }
41
39
  }
42
40
  }, [initialState]);
43
- const get = useCallback((path) => getByPath(state, path), [state]);
41
+ const get = useCallback(
42
+ (path) => getByPath(stateRef.current, path),
43
+ []
44
+ );
44
45
  const set = useCallback(
45
46
  (path, value2) => {
46
47
  setStateInternal((prev) => {
@@ -54,26 +55,28 @@ function StateProvider({
54
55
  );
55
56
  const update = useCallback(
56
57
  (updates) => {
58
+ const entries = Object.entries(updates);
57
59
  setStateInternal((prev) => {
58
60
  const next = { ...prev };
59
- for (const [path, value2] of Object.entries(updates)) {
61
+ for (const [path, value2] of entries) {
60
62
  setByPath(next, path, value2);
61
- onStateChange?.(path, value2);
62
63
  }
63
64
  return next;
64
65
  });
66
+ for (const [path, value2] of entries) {
67
+ onStateChange?.(path, value2);
68
+ }
65
69
  },
66
70
  [onStateChange]
67
71
  );
68
72
  const value = useMemo(
69
73
  () => ({
70
74
  state,
71
- authState,
72
75
  get,
73
76
  set,
74
77
  update
75
78
  }),
76
- [state, authState, get, set, update]
79
+ [state, get, set, update]
77
80
  );
78
81
  return /* @__PURE__ */ jsx(StateContext.Provider, { value, children });
79
82
  }
@@ -110,13 +113,12 @@ import {
110
113
  import { jsx as jsx2 } from "react/jsx-runtime";
111
114
  var VisibilityContext = createContext2(null);
112
115
  function VisibilityProvider({ children }) {
113
- const { state, authState } = useStateStore();
116
+ const { state } = useStateStore();
114
117
  const ctx = useMemo2(
115
118
  () => ({
116
- stateModel: state,
117
- authState
119
+ stateModel: state
118
120
  }),
119
- [state, authState]
121
+ [state]
120
122
  );
121
123
  const isVisible = useMemo2(
122
124
  () => (condition) => evaluateVisibility(condition, ctx),
@@ -167,8 +169,8 @@ function deepResolveValue(value, get) {
167
169
  if (typeof value === "object" && !Array.isArray(value)) {
168
170
  const obj = value;
169
171
  const keys = Object.keys(obj);
170
- if (keys.length === 1 && typeof obj.path === "string") {
171
- return get(obj.path);
172
+ if (keys.length === 1 && typeof obj.$state === "string") {
173
+ return get(obj.$state);
172
174
  }
173
175
  if (keys.length === 1 && "$id" in obj) {
174
176
  return generateUniqueId();
@@ -206,34 +208,34 @@ function ActionProvider({
206
208
  async (binding) => {
207
209
  const resolved = resolveAction(binding, state);
208
210
  if (resolved.action === "setState" && resolved.params) {
209
- const path = resolved.params.path;
211
+ const statePath = resolved.params.statePath;
210
212
  const value2 = resolved.params.value;
211
- if (path) {
212
- set(path, value2);
213
+ if (statePath) {
214
+ set(statePath, value2);
213
215
  }
214
216
  return;
215
217
  }
216
218
  if (resolved.action === "pushState" && resolved.params) {
217
- const path = resolved.params.path;
219
+ const statePath = resolved.params.statePath;
218
220
  const rawValue = resolved.params.value;
219
- if (path) {
221
+ if (statePath) {
220
222
  const resolvedValue = deepResolveValue(rawValue, get);
221
- const arr = get(path) ?? [];
222
- set(path, [...arr, resolvedValue]);
223
- const clearPath = resolved.params.clearPath;
224
- if (clearPath) {
225
- set(clearPath, "");
223
+ const arr = get(statePath) ?? [];
224
+ set(statePath, [...arr, resolvedValue]);
225
+ const clearStatePath = resolved.params.clearStatePath;
226
+ if (clearStatePath) {
227
+ set(clearStatePath, "");
226
228
  }
227
229
  }
228
230
  return;
229
231
  }
230
232
  if (resolved.action === "removeState" && resolved.params) {
231
- const path = resolved.params.path;
233
+ const statePath = resolved.params.statePath;
232
234
  const index = resolved.params.index;
233
- if (path !== void 0 && index !== void 0) {
234
- const arr = get(path) ?? [];
235
+ if (statePath !== void 0 && index !== void 0) {
236
+ const arr = get(statePath) ?? [];
235
237
  set(
236
- path,
238
+ statePath,
237
239
  arr.filter((_, i) => i !== index)
238
240
  );
239
241
  }
@@ -470,27 +472,75 @@ import {
470
472
  } from "@json-render/core";
471
473
  import { jsx as jsx4 } from "react/jsx-runtime";
472
474
  var ValidationContext = createContext4(null);
475
+ function dynamicArgsEqual(a, b) {
476
+ if (a === b) return true;
477
+ if (!a || !b) return false;
478
+ const keysA = Object.keys(a);
479
+ const keysB = Object.keys(b);
480
+ if (keysA.length !== keysB.length) return false;
481
+ for (const key of keysA) {
482
+ const va = a[key];
483
+ const vb = b[key];
484
+ if (va === vb) continue;
485
+ if (typeof va === "object" && va !== null && typeof vb === "object" && vb !== null) {
486
+ const sa = va.$state;
487
+ const sb = vb.$state;
488
+ if (typeof sa === "string" && sa === sb) continue;
489
+ }
490
+ return false;
491
+ }
492
+ return true;
493
+ }
494
+ function validationConfigEqual(a, b) {
495
+ if (a === b) return true;
496
+ if (a.validateOn !== b.validateOn) return false;
497
+ const ac = a.checks ?? [];
498
+ const bc = b.checks ?? [];
499
+ if (ac.length !== bc.length) return false;
500
+ for (let i = 0; i < ac.length; i++) {
501
+ const ca = ac[i];
502
+ const cb = bc[i];
503
+ if (ca.type !== cb.type) return false;
504
+ if (ca.message !== cb.message) return false;
505
+ if (!dynamicArgsEqual(ca.args, cb.args)) return false;
506
+ }
507
+ return true;
508
+ }
473
509
  function ValidationProvider({
474
510
  customFunctions = {},
475
511
  children
476
512
  }) {
477
- const { state, authState } = useStateStore();
513
+ const { state } = useStateStore();
478
514
  const [fieldStates, setFieldStates] = useState3({});
479
515
  const [fieldConfigs, setFieldConfigs] = useState3({});
480
516
  const registerField = useCallback3(
481
517
  (path, config) => {
482
- setFieldConfigs((prev) => ({ ...prev, [path]: config }));
518
+ setFieldConfigs((prev) => {
519
+ const existing = prev[path];
520
+ if (existing && validationConfigEqual(existing, config)) {
521
+ return prev;
522
+ }
523
+ return { ...prev, [path]: config };
524
+ });
483
525
  },
484
526
  []
485
527
  );
486
528
  const validate = useCallback3(
487
529
  (path, config) => {
488
- const value2 = state[path.split("/").filter(Boolean).join(".")];
530
+ const segments = path.split("/").filter(Boolean);
531
+ let value2 = state;
532
+ for (const seg of segments) {
533
+ if (value2 != null && typeof value2 === "object") {
534
+ value2 = value2[seg];
535
+ } else {
536
+ value2 = void 0;
537
+ break;
538
+ }
539
+ }
489
540
  const result = runValidation(config, {
490
541
  value: value2,
491
542
  stateModel: state,
492
- customFunctions,
493
- authState
543
+ customFunctions
494
544
  });
495
545
  setFieldStates((prev) => ({
496
546
  ...prev,
@@ -502,7 +552,7 @@ function ValidationProvider({
502
552
  }));
503
553
  return result;
504
554
  },
505
- [state, customFunctions, authState]
555
+ [state, customFunctions]
506
556
  );
507
557
  const touch = useCallback3((path) => {
508
558
  setFieldStates((prev) => ({
@@ -599,51 +649,29 @@ import { createContext as createContext5, useContext as useContext5 } from "reac
599
649
  import { jsx as jsx5 } from "react/jsx-runtime";
600
650
  var RepeatScopeContext = createContext5(null);
601
651
  function RepeatScopeProvider({
602
- basePath,
652
+ item,
603
653
  index,
654
+ basePath,
604
655
  children
605
656
  }) {
606
- return /* @__PURE__ */ jsx5(RepeatScopeContext.Provider, { value: { basePath, index }, children });
657
+ return /* @__PURE__ */ jsx5(RepeatScopeContext.Provider, { value: { item, index, basePath }, children });
607
658
  }
608
659
  function useRepeatScope() {
609
660
  return useContext5(RepeatScopeContext);
610
661
  }
611
- function rewriteRepeatTokens(value, basePath, index) {
612
- if (typeof value === "string") {
613
- if (value === "$index") return index;
614
- if (value === "$item") return basePath;
615
- if (value.startsWith("$item/"))
616
- return basePath + value.slice("$item".length);
617
- return value;
618
- }
619
- if (Array.isArray(value)) {
620
- let changed = false;
621
- const result = value.map((item) => {
622
- const rewritten = rewriteRepeatTokens(item, basePath, index);
623
- if (rewritten !== item) changed = true;
624
- return rewritten;
625
- });
626
- return changed ? result : value;
627
- }
628
- if (typeof value === "object" && value !== null) {
629
- let changed = false;
630
- const result = {};
631
- for (const [key, val] of Object.entries(value)) {
632
- const rewritten = rewriteRepeatTokens(val, basePath, index);
633
- if (rewritten !== val) changed = true;
634
- result[key] = rewritten;
635
- }
636
- return changed ? result : value;
637
- }
638
- return value;
639
- }
640
662
 
641
663
  // src/renderer.tsx
642
664
  import React6, {
643
- useCallback as useCallback4,
665
+ useCallback as useCallback5,
644
666
  useMemo as useMemo5
645
667
  } from "react";
646
- import { resolveElementProps, getByPath as getByPath2 } from "@json-render/core";
668
+ import {
669
+ resolveElementProps,
670
+ resolveBindings,
671
+ resolveActionParam,
672
+ evaluateVisibility as evaluateVisibility2,
673
+ getByPath as getByPath3
674
+ } from "@json-render/core";
647
675
 
648
676
  // src/components/standard.tsx
649
677
  import {
@@ -662,1562 +690,1508 @@ import {
662
690
  Share,
663
691
  Linking
664
692
  } from "react-native";
665
- import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
666
- function ContainerComponent({ element, children }) {
667
- const p = element.props;
668
- return /* @__PURE__ */ jsx6(
669
- View2,
670
- {
671
- style: {
672
- padding: p.padding ?? void 0,
673
- paddingHorizontal: p.paddingHorizontal ?? void 0,
674
- paddingVertical: p.paddingVertical ?? void 0,
675
- margin: p.margin ?? void 0,
676
- backgroundColor: p.backgroundColor ?? void 0,
677
- borderRadius: p.borderRadius ?? void 0,
678
- flex: p.flex ?? void 0
679
- },
680
- children
681
- }
682
- );
683
- }
684
- function RowComponent({ element, children }) {
685
- const p = element.props;
686
- return /* @__PURE__ */ jsx6(
687
- View2,
688
- {
689
- style: {
690
- flexDirection: "row",
691
- gap: p.gap ?? void 0,
692
- alignItems: p.alignItems ?? void 0,
693
- justifyContent: p.justifyContent ?? void 0,
694
- flexWrap: p.flexWrap ?? void 0,
695
- padding: p.padding ?? void 0,
696
- flex: p.flex ?? void 0
697
- },
698
- children
699
- }
700
- );
701
- }
702
- function ColumnComponent({ element, children }) {
703
- const p = element.props;
704
- return /* @__PURE__ */ jsx6(
705
- View2,
706
- {
707
- style: {
708
- flexDirection: "column",
709
- gap: p.gap ?? void 0,
710
- alignItems: p.alignItems ?? void 0,
711
- justifyContent: p.justifyContent ?? void 0,
712
- padding: p.padding ?? void 0,
713
- flex: p.flex ?? void 0
714
- },
715
- children
716
- }
717
- );
718
- }
719
- function ScrollContainerComponent({ element, children }) {
720
- const p = element.props;
721
- return /* @__PURE__ */ jsx6(
722
- ScrollView,
723
- {
724
- horizontal: p.horizontal ?? false,
725
- showsVerticalScrollIndicator: p.showsScrollIndicator ?? true,
726
- showsHorizontalScrollIndicator: p.showsScrollIndicator ?? true,
727
- contentContainerStyle: {
728
- padding: p.padding ?? void 0
729
- },
730
- style: {
731
- flex: p.flex ?? 1,
732
- backgroundColor: p.backgroundColor ?? void 0
733
- },
734
- children
735
- }
693
+
694
+ // src/hooks.ts
695
+ import { useState as useState4, useCallback as useCallback4, useRef as useRef2, useEffect as useEffect2 } from "react";
696
+ import {
697
+ setByPath as setByPath2,
698
+ getByPath as getByPath2,
699
+ removeByPath,
700
+ validateSpec,
701
+ autoFixSpec,
702
+ formatSpecIssues
703
+ } from "@json-render/core";
704
+ function useBoundProp(propValue, bindingPath) {
705
+ const { set } = useStateStore();
706
+ const setValue = useCallback4(
707
+ (value) => {
708
+ if (bindingPath) set(bindingPath, value);
709
+ },
710
+ [bindingPath, set]
736
711
  );
712
+ return [propValue, setValue];
737
713
  }
738
- function SafeAreaComponent({ element, children }) {
739
- const p = element.props;
740
- return /* @__PURE__ */ jsx6(
741
- SafeAreaView,
742
- {
743
- style: {
744
- flex: 1,
745
- backgroundColor: p.backgroundColor ?? void 0
746
- },
747
- children
748
- }
749
- );
714
+ function looksLikeJson(line) {
715
+ return line.startsWith("{") || line.startsWith("[");
750
716
  }
751
- function SpacerComponent({ element }) {
752
- const p = element.props;
753
- return /* @__PURE__ */ jsx6(
754
- View2,
755
- {
756
- style: {
757
- width: p.size ?? void 0,
758
- height: p.size ?? void 0,
759
- flex: p.flex ?? void 0
717
+ function parsePatchLine(line) {
718
+ const trimmed = line.trim();
719
+ if (!trimmed || trimmed.startsWith("//")) {
720
+ return { patch: null, malformed: false };
721
+ }
722
+ if (!looksLikeJson(trimmed)) {
723
+ return { patch: null, malformed: false };
724
+ }
725
+ try {
726
+ return { patch: JSON.parse(trimmed), malformed: false };
727
+ } catch {
728
+ }
729
+ let attempt = trimmed;
730
+ for (let i = 0; i < 3; i++) {
731
+ const last = attempt[attempt.length - 1];
732
+ if (last === "}" || last === "]") {
733
+ attempt = attempt.slice(0, -1);
734
+ try {
735
+ const result = JSON.parse(attempt);
736
+ console.warn(
737
+ `[json-render] Recovered malformed JSONL line by removing ${i + 1} trailing '${last}'`
738
+ );
739
+ return { patch: result, malformed: false };
740
+ } catch {
760
741
  }
742
+ } else {
743
+ break;
761
744
  }
762
- );
745
+ }
746
+ return { patch: null, malformed: true };
763
747
  }
764
- function PressableComponent({ children, emit }) {
765
- return /* @__PURE__ */ jsx6(
766
- Pressable2,
767
- {
768
- onPress: () => emit?.("press"),
769
- onLongPress: () => emit?.("longPress"),
770
- style: ({ pressed }) => ({
771
- opacity: pressed ? 0.7 : 1
772
- }),
773
- children
748
+ function setSpecValue(newSpec, path, value) {
749
+ if (path === "/root") {
750
+ newSpec.root = value;
751
+ return;
752
+ }
753
+ if (path === "/state") {
754
+ newSpec.state = value;
755
+ return;
756
+ }
757
+ if (path.startsWith("/state/")) {
758
+ if (!newSpec.state) newSpec.state = {};
759
+ const statePath = path.slice("/state".length);
760
+ setByPath2(newSpec.state, statePath, value);
761
+ return;
762
+ }
763
+ if (path.startsWith("/elements/")) {
764
+ const pathParts = path.slice("/elements/".length).split("/");
765
+ const elementKey = pathParts[0];
766
+ if (!elementKey) return;
767
+ if (pathParts.length === 1) {
768
+ newSpec.elements[elementKey] = value;
769
+ } else {
770
+ const element = newSpec.elements[elementKey];
771
+ if (element) {
772
+ const propPath = "/" + pathParts.slice(1).join("/");
773
+ const newElement = { ...element };
774
+ setByPath2(
775
+ newElement,
776
+ propPath,
777
+ value
778
+ );
779
+ newSpec.elements[elementKey] = newElement;
780
+ }
774
781
  }
775
- );
782
+ }
776
783
  }
777
- function DividerComponent({ element }) {
778
- const p = element.props;
779
- const isVertical = p.direction === "vertical";
780
- const thickness = p.thickness ?? 1;
781
- const color = p.color ?? "#e5e7eb";
782
- return /* @__PURE__ */ jsx6(
783
- View2,
784
- {
785
- style: {
786
- width: isVertical ? thickness : "100%",
787
- height: isVertical ? "100%" : thickness,
788
- backgroundColor: color,
789
- marginVertical: isVertical ? 0 : p.margin ?? 8,
790
- marginHorizontal: isVertical ? p.margin ?? 8 : 0
784
+ function removeSpecValue(newSpec, path) {
785
+ if (path === "/state") {
786
+ newSpec.state = void 0;
787
+ return;
788
+ }
789
+ if (path.startsWith("/state/") && newSpec.state) {
790
+ const statePath = path.slice("/state".length);
791
+ removeByPath(newSpec.state, statePath);
792
+ return;
793
+ }
794
+ if (path.startsWith("/elements/")) {
795
+ const pathParts = path.slice("/elements/".length).split("/");
796
+ const elementKey = pathParts[0];
797
+ if (!elementKey) return;
798
+ if (pathParts.length === 1) {
799
+ const { [elementKey]: _, ...rest } = newSpec.elements;
800
+ newSpec.elements = rest;
801
+ } else {
802
+ const element = newSpec.elements[elementKey];
803
+ if (element) {
804
+ const propPath = "/" + pathParts.slice(1).join("/");
805
+ const newElement = { ...element };
806
+ removeByPath(
807
+ newElement,
808
+ propPath
809
+ );
810
+ newSpec.elements[elementKey] = newElement;
791
811
  }
792
812
  }
793
- );
813
+ }
794
814
  }
795
- var headingSizes = {
796
- h1: 32,
797
- h2: 24,
798
- h3: 20,
799
- h4: 16
800
- };
801
- function HeadingComponent({ element }) {
802
- const p = element.props;
803
- const level = p.level ?? "h2";
804
- return /* @__PURE__ */ jsx6(
805
- Text2,
806
- {
807
- style: {
808
- fontSize: headingSizes[level],
809
- fontWeight: "700",
810
- color: p.color ?? "#111827",
811
- textAlign: p.align ?? "left"
812
- },
813
- children: p.text
814
- }
815
- );
815
+ function getSpecValue(spec, path) {
816
+ if (path === "/root") return spec.root;
817
+ return getByPath2(spec, path);
816
818
  }
817
- function ParagraphComponent({ element }) {
818
- const p = element.props;
819
- return /* @__PURE__ */ jsx6(
820
- Text2,
821
- {
822
- numberOfLines: p.numberOfLines ?? void 0,
823
- style: {
824
- fontSize: p.fontSize ?? 16,
825
- lineHeight: (p.fontSize ?? 16) * 1.5,
826
- color: p.color ?? "#374151",
827
- textAlign: p.align ?? "left"
828
- },
829
- children: p.text
819
+ function applyPatch(spec, patch) {
820
+ const newSpec = { ...spec, elements: { ...spec.elements } };
821
+ switch (patch.op) {
822
+ case "add":
823
+ case "replace": {
824
+ setSpecValue(newSpec, patch.path, patch.value);
825
+ break;
830
826
  }
831
- );
832
- }
833
- var labelSizes = {
834
- xs: 10,
835
- sm: 12,
836
- md: 14
837
- };
838
- function LabelComponent({ element }) {
839
- const p = element.props;
840
- return /* @__PURE__ */ jsx6(
841
- Text2,
842
- {
843
- style: {
844
- fontSize: labelSizes[p.size ?? "sm"],
845
- fontWeight: p.bold ? "600" : "400",
846
- color: p.color ?? "#6b7280"
847
- },
848
- children: p.text
827
+ case "remove": {
828
+ removeSpecValue(newSpec, patch.path);
829
+ break;
849
830
  }
850
- );
851
- }
852
- function ImageComponent({ element }) {
853
- const p = element.props;
854
- return /* @__PURE__ */ jsx6(
855
- RNImage,
856
- {
857
- source: { uri: p.src },
858
- accessibilityLabel: p.alt ?? void 0,
859
- resizeMode: p.resizeMode ?? "cover",
860
- style: {
861
- width: p.width ?? "100%",
862
- height: p.height ?? 200,
863
- borderRadius: p.borderRadius ?? 0
864
- }
831
+ case "move": {
832
+ if (!patch.from) break;
833
+ const moveValue = getSpecValue(newSpec, patch.from);
834
+ removeSpecValue(newSpec, patch.from);
835
+ setSpecValue(newSpec, patch.path, moveValue);
836
+ break;
865
837
  }
866
- );
838
+ case "copy": {
839
+ if (!patch.from) break;
840
+ const copyValue = getSpecValue(newSpec, patch.from);
841
+ setSpecValue(newSpec, patch.path, copyValue);
842
+ break;
843
+ }
844
+ case "test": {
845
+ break;
846
+ }
847
+ }
848
+ return newSpec;
867
849
  }
868
- var avatarSizeMap = {
869
- sm: 32,
870
- md: 48,
871
- lg: 64,
872
- xl: 96
873
- };
874
- function AvatarComponent({ element }) {
875
- const p = element.props;
876
- const size = avatarSizeMap[p.size ?? "md"];
877
- const bgColor = p.backgroundColor ?? "#6366f1";
878
- if (p.src) {
879
- return /* @__PURE__ */ jsx6(
880
- RNImage,
881
- {
882
- source: { uri: p.src },
883
- style: {
884
- width: size,
885
- height: size,
886
- borderRadius: size / 2,
887
- backgroundColor: "#e5e7eb"
850
+ function useUIStream({
851
+ api,
852
+ onComplete,
853
+ onError,
854
+ fetch: fetchFn = globalThis.fetch,
855
+ validate: enableValidation = false,
856
+ maxRetries = 5
857
+ }) {
858
+ const [spec, setSpec] = useState4(null);
859
+ const [isStreaming, setIsStreaming] = useState4(false);
860
+ const [error, setError] = useState4(null);
861
+ const [rawLines, setRawLines] = useState4([]);
862
+ const abortControllerRef = useRef2(null);
863
+ const stop = useCallback4(() => {
864
+ abortControllerRef.current?.abort();
865
+ abortControllerRef.current = null;
866
+ setIsStreaming(false);
867
+ }, []);
868
+ const clear = useCallback4(() => {
869
+ setSpec(null);
870
+ setError(null);
871
+ setRawLines([]);
872
+ }, []);
873
+ const streamRequest = useCallback4(
874
+ async (prompt, context, initialSpec, abortOnMalformed) => {
875
+ let currentSpec = initialSpec;
876
+ setSpec(currentSpec);
877
+ const malformedLines = [];
878
+ const response = await fetchFn(api, {
879
+ method: "POST",
880
+ headers: { "Content-Type": "application/json" },
881
+ body: JSON.stringify({
882
+ prompt,
883
+ context,
884
+ currentSpec
885
+ }),
886
+ signal: abortControllerRef.current.signal
887
+ });
888
+ if (!response.ok) {
889
+ let errorMessage = `HTTP error: ${response.status}`;
890
+ try {
891
+ const errorData = await response.json();
892
+ if (errorData.message) {
893
+ errorMessage = errorData.message;
894
+ } else if (errorData.error) {
895
+ errorMessage = errorData.error;
896
+ }
897
+ } catch {
888
898
  }
899
+ throw new Error(errorMessage);
889
900
  }
890
- );
891
- }
892
- return /* @__PURE__ */ jsx6(
893
- View2,
894
- {
895
- style: {
896
- width: size,
897
- height: size,
898
- borderRadius: size / 2,
899
- backgroundColor: bgColor,
900
- alignItems: "center",
901
- justifyContent: "center"
902
- },
903
- children: /* @__PURE__ */ jsx6(
904
- Text2,
905
- {
906
- style: {
907
- color: "#ffffff",
908
- fontSize: size * 0.4,
909
- fontWeight: "600"
910
- },
911
- children: p.initials ?? "?"
901
+ const reader = response.body?.getReader();
902
+ if (!reader) {
903
+ throw new Error("No response body");
904
+ }
905
+ const decoder = new TextDecoder();
906
+ let buffer = "";
907
+ let aborted = false;
908
+ while (true) {
909
+ const { done, value } = await reader.read();
910
+ if (done) {
911
+ break;
912
912
  }
913
- )
914
- }
915
- );
916
- }
917
- var badgeVariantColors = {
918
- default: { bg: "#e5e7eb", text: "#374151" },
919
- info: { bg: "#dbeafe", text: "#1d4ed8" },
920
- success: { bg: "#dcfce7", text: "#15803d" },
921
- warning: { bg: "#fef3c7", text: "#92400e" },
922
- error: { bg: "#fee2e2", text: "#dc2626" }
923
- };
924
- function BadgeComponent({ element }) {
925
- const p = element.props;
926
- const colors = badgeVariantColors[p.variant ?? "default"] ?? {
927
- bg: "#e5e7eb",
928
- text: "#374151"
929
- };
930
- return /* @__PURE__ */ jsx6(
931
- View2,
932
- {
933
- style: {
934
- backgroundColor: colors.bg,
935
- paddingHorizontal: 8,
936
- paddingVertical: 2,
937
- borderRadius: 9999,
938
- alignSelf: "flex-start"
939
- },
940
- children: /* @__PURE__ */ jsx6(
941
- Text2,
942
- {
943
- style: {
944
- fontSize: 12,
945
- fontWeight: "600",
946
- color: colors.text
947
- },
948
- children: p.label
913
+ const decoded = decoder.decode(value, { stream: true });
914
+ buffer += decoded;
915
+ const lines = buffer.split("\n");
916
+ buffer = lines.pop() ?? "";
917
+ const newLines = [];
918
+ for (const line of lines) {
919
+ if (line.trim()) {
920
+ newLines.push(line);
921
+ }
922
+ const { patch, malformed } = parsePatchLine(line);
923
+ if (patch) {
924
+ currentSpec = applyPatch(currentSpec, patch);
925
+ setSpec({ ...currentSpec });
926
+ } else if (malformed) {
927
+ malformedLines.push(line.trim());
928
+ if (abortOnMalformed) {
929
+ await reader.cancel();
930
+ aborted = true;
931
+ break;
932
+ }
933
+ }
949
934
  }
950
- )
951
- }
952
- );
953
- }
954
- function ChipComponent({ element, emit }) {
955
- const p = element.props;
956
- const bgColor = p.selected ? "#6366f1" : p.backgroundColor ?? "#f3f4f6";
957
- const textColor = p.selected ? "#ffffff" : "#374151";
958
- const hasRemove = !!element.on?.remove;
959
- return /* @__PURE__ */ jsxs2(
960
- Pressable2,
961
- {
962
- onPress: () => emit?.("press"),
963
- style: {
964
- flexDirection: "row",
965
- alignItems: "center",
966
- backgroundColor: bgColor,
967
- paddingHorizontal: 12,
968
- paddingVertical: 6,
969
- borderRadius: 9999,
970
- alignSelf: "flex-start"
971
- },
972
- children: [
973
- /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: textColor }, children: p.label }),
974
- hasRemove && /* @__PURE__ */ jsx6(Pressable2, { onPress: () => emit?.("remove"), style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: textColor, fontWeight: "700" }, children: "x" }) })
975
- ]
976
- }
935
+ if (aborted) break;
936
+ if (newLines.length > 0) {
937
+ setRawLines((prev) => [...prev, ...newLines]);
938
+ }
939
+ }
940
+ if (!aborted && buffer.trim()) {
941
+ setRawLines((prev) => [...prev, buffer]);
942
+ const { patch, malformed } = parsePatchLine(buffer);
943
+ if (patch) {
944
+ currentSpec = applyPatch(currentSpec, patch);
945
+ setSpec({ ...currentSpec });
946
+ } else if (malformed) {
947
+ malformedLines.push(buffer.trim());
948
+ }
949
+ }
950
+ return { spec: currentSpec, completed: !aborted, malformedLines };
951
+ },
952
+ [api, fetchFn]
977
953
  );
978
- }
979
- var buttonVariantStyles = {
980
- primary: { bg: "#3b82f6", text: "#ffffff" },
981
- secondary: { bg: "#6b7280", text: "#ffffff" },
982
- danger: { bg: "#dc2626", text: "#ffffff" },
983
- outline: { bg: "transparent", text: "#3b82f6", border: "#3b82f6" },
984
- ghost: { bg: "transparent", text: "#374151" }
985
- };
986
- var buttonSizeStyles = {
987
- sm: { paddingH: 12, paddingV: 6, fontSize: 13 },
988
- md: { paddingH: 16, paddingV: 10, fontSize: 15 },
989
- lg: { paddingH: 24, paddingV: 14, fontSize: 17 }
990
- };
991
- function ButtonComponent({ element, emit }) {
992
- const p = element.props;
993
- const variant = buttonVariantStyles[p.variant ?? "primary"] ?? {
994
- bg: "#3b82f6",
995
- text: "#ffffff"
996
- };
997
- const size = buttonSizeStyles[p.size ?? "md"] ?? {
998
- paddingH: 16,
999
- paddingV: 10,
1000
- fontSize: 15
1001
- };
1002
- const disabled = p.disabled || p.loading;
1003
- return /* @__PURE__ */ jsxs2(
1004
- Pressable2,
1005
- {
1006
- disabled,
1007
- onPress: () => emit?.("press"),
1008
- style: ({ pressed }) => ({
1009
- backgroundColor: variant.bg,
1010
- paddingHorizontal: size.paddingH,
1011
- paddingVertical: size.paddingV,
1012
- borderRadius: 8,
1013
- alignItems: "center",
1014
- justifyContent: "center",
1015
- flexDirection: "row",
1016
- opacity: disabled ? 0.5 : pressed ? 0.8 : 1,
1017
- borderWidth: variant.border ? 1 : 0,
1018
- borderColor: variant.border,
1019
- alignSelf: "flex-start"
1020
- }),
1021
- children: [
1022
- p.loading && /* @__PURE__ */ jsx6(
1023
- ActivityIndicator,
1024
- {
1025
- size: "small",
1026
- color: variant.text,
1027
- style: { marginRight: 8 }
954
+ const send = useCallback4(
955
+ async (prompt, context) => {
956
+ abortControllerRef.current?.abort();
957
+ abortControllerRef.current = new AbortController();
958
+ setIsStreaming(true);
959
+ setError(null);
960
+ setRawLines([]);
961
+ const previousSpec = context?.previousSpec;
962
+ let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
963
+ let retriesUsed = 0;
964
+ let currentPrompt = prompt;
965
+ let currentContext = context;
966
+ try {
967
+ while (retriesUsed <= maxRetries) {
968
+ const result = await streamRequest(
969
+ currentPrompt,
970
+ currentContext,
971
+ currentSpec,
972
+ enableValidation
973
+ // only abort on malformed when validation is on
974
+ );
975
+ currentSpec = result.spec;
976
+ if (!result.completed && result.malformedLines.length > 0) {
977
+ if (retriesUsed >= maxRetries) {
978
+ break;
979
+ }
980
+ retriesUsed++;
981
+ currentContext = { ...context, previousSpec: currentSpec };
982
+ currentPrompt = `The previous generation contained malformed JSON that could not be parsed. The line was:
983
+ ${result.malformedLines[result.malformedLines.length - 1]?.slice(0, 500)}
984
+
985
+ The current spec state is provided. Continue generating from where you left off. Output ONLY the remaining patches needed to complete the UI.`;
986
+ continue;
1028
987
  }
1029
- ),
1030
- /* @__PURE__ */ jsx6(
1031
- Text2,
1032
- {
1033
- style: {
1034
- color: variant.text,
1035
- fontSize: size.fontSize,
1036
- fontWeight: "600"
1037
- },
1038
- children: p.label
988
+ if (!enableValidation || !currentSpec.root) {
989
+ break;
1039
990
  }
1040
- )
1041
- ]
1042
- }
991
+ const { spec: fixedSpec, fixes } = autoFixSpec(currentSpec);
992
+ if (fixes.length > 0) {
993
+ currentSpec = fixedSpec;
994
+ setSpec({ ...currentSpec });
995
+ }
996
+ const validation = validateSpec(currentSpec);
997
+ if (validation.valid) {
998
+ break;
999
+ }
1000
+ const errors = validation.issues.filter(
1001
+ (i) => i.severity === "error"
1002
+ );
1003
+ if (retriesUsed >= maxRetries) {
1004
+ break;
1005
+ }
1006
+ retriesUsed++;
1007
+ const issueText = formatSpecIssues(validation.issues);
1008
+ currentContext = { ...context, previousSpec: currentSpec };
1009
+ currentPrompt = `FIX THE FOLLOWING ERRORS in the current UI spec. Output ONLY the patches needed to fix these issues, do not recreate the entire UI.
1010
+
1011
+ ` + issueText;
1012
+ }
1013
+ onComplete?.(currentSpec);
1014
+ } catch (err) {
1015
+ if (err.name === "AbortError") {
1016
+ return;
1017
+ }
1018
+ const error2 = err instanceof Error ? err : new Error(String(err));
1019
+ setError(error2);
1020
+ onError?.(error2);
1021
+ } finally {
1022
+ setIsStreaming(false);
1023
+ }
1024
+ },
1025
+ [
1026
+ api,
1027
+ fetchFn,
1028
+ onComplete,
1029
+ onError,
1030
+ enableValidation,
1031
+ maxRetries,
1032
+ streamRequest
1033
+ ]
1043
1034
  );
1035
+ useEffect2(() => {
1036
+ return () => {
1037
+ abortControllerRef.current?.abort();
1038
+ };
1039
+ }, []);
1040
+ return {
1041
+ spec,
1042
+ isStreaming,
1043
+ error,
1044
+ rawLines,
1045
+ send,
1046
+ stop,
1047
+ clear
1048
+ };
1044
1049
  }
1045
- function TextInputComponent({ element }) {
1046
- const p = element.props;
1047
- const [boundValue, setBoundValue] = useStateBinding(
1048
- p.statePath ?? ""
1049
- );
1050
- const displayValue = p.statePath ? boundValue ?? "" : p.value ?? "";
1051
- return /* @__PURE__ */ jsxs2(View2, { style: p.flex != null ? { flex: p.flex } : void 0, children: [
1052
- p.label && /* @__PURE__ */ jsx6(Text2, { style: inputStyles.label, children: p.label }),
1053
- /* @__PURE__ */ jsx6(
1054
- RNTextInput,
1055
- {
1056
- placeholder: p.placeholder ?? void 0,
1057
- value: displayValue,
1058
- onChangeText: p.statePath ? setBoundValue : void 0,
1059
- secureTextEntry: p.secureTextEntry ?? false,
1060
- keyboardType: p.keyboardType ?? "default",
1061
- multiline: p.multiline ?? false,
1062
- numberOfLines: p.numberOfLines ?? void 0,
1063
- style: [
1064
- inputStyles.input,
1065
- p.multiline && {
1066
- minHeight: (p.numberOfLines ?? 3) * 20,
1067
- textAlignVertical: "top"
1068
- }
1069
- ]
1050
+ function flatToTree(elements) {
1051
+ const elementMap = {};
1052
+ let root = "";
1053
+ for (const element of elements) {
1054
+ elementMap[element.key] = {
1055
+ type: element.type,
1056
+ props: element.props,
1057
+ children: [],
1058
+ visible: element.visible
1059
+ };
1060
+ }
1061
+ for (const element of elements) {
1062
+ if (element.parentKey) {
1063
+ const parent = elementMap[element.parentKey];
1064
+ if (parent) {
1065
+ if (!parent.children) {
1066
+ parent.children = [];
1067
+ }
1068
+ parent.children.push(element.key);
1070
1069
  }
1071
- )
1072
- ] });
1070
+ } else {
1071
+ root = element.key;
1072
+ }
1073
+ }
1074
+ return { root, elements: elementMap };
1073
1075
  }
1074
- function SwitchComponent({ element }) {
1076
+
1077
+ // src/components/standard.tsx
1078
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1079
+ function ContainerComponent({ element, children }) {
1075
1080
  const p = element.props;
1076
- const [boundValue, setBoundValue] = useStateBinding(
1077
- p.statePath ?? ""
1078
- );
1079
- const displayValue = p.statePath ? boundValue ?? false : p.value ?? false;
1080
- return /* @__PURE__ */ jsxs2(View2, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
1081
- /* @__PURE__ */ jsx6(
1082
- RNSwitch,
1083
- {
1084
- value: displayValue,
1085
- onValueChange: p.statePath ? setBoundValue : void 0,
1086
- disabled: p.disabled ?? false
1087
- }
1088
- ),
1089
- p.label && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1090
- ] });
1081
+ return /* @__PURE__ */ jsx6(
1082
+ View2,
1083
+ {
1084
+ style: {
1085
+ padding: p.padding ?? void 0,
1086
+ paddingHorizontal: p.paddingHorizontal ?? void 0,
1087
+ paddingVertical: p.paddingVertical ?? void 0,
1088
+ margin: p.margin ?? void 0,
1089
+ backgroundColor: p.backgroundColor ?? void 0,
1090
+ borderRadius: p.borderRadius ?? void 0,
1091
+ flex: p.flex ?? void 0
1092
+ },
1093
+ children
1094
+ }
1095
+ );
1091
1096
  }
1092
- function CheckboxComponent({ element }) {
1097
+ function RowComponent({ element, children }) {
1093
1098
  const p = element.props;
1094
- const [boundValue, setBoundValue] = useStateBinding(
1095
- p.statePath ?? ""
1096
- );
1097
- const checked = p.statePath ? boundValue ?? false : p.checked ?? false;
1098
- return /* @__PURE__ */ jsxs2(
1099
- Pressable2,
1099
+ return /* @__PURE__ */ jsx6(
1100
+ View2,
1100
1101
  {
1101
- disabled: p.disabled ?? false,
1102
- onPress: () => {
1103
- if (p.statePath) {
1104
- setBoundValue(!checked);
1105
- }
1106
- },
1107
1102
  style: {
1108
1103
  flexDirection: "row",
1109
- alignItems: "center",
1110
- gap: 8,
1111
- opacity: p.disabled ? 0.5 : 1
1104
+ gap: p.gap ?? void 0,
1105
+ alignItems: p.alignItems ?? void 0,
1106
+ justifyContent: p.justifyContent ?? void 0,
1107
+ flexWrap: p.flexWrap ?? void 0,
1108
+ padding: p.padding ?? void 0,
1109
+ flex: p.flex ?? void 0
1112
1110
  },
1113
- children: [
1114
- /* @__PURE__ */ jsx6(
1115
- View2,
1116
- {
1117
- style: {
1118
- width: 20,
1119
- height: 20,
1120
- borderRadius: 4,
1121
- borderWidth: 2,
1122
- borderColor: checked ? "#3b82f6" : "#d1d5db",
1123
- backgroundColor: checked ? "#3b82f6" : "transparent",
1124
- alignItems: "center",
1125
- justifyContent: "center"
1126
- },
1127
- children: checked && /* @__PURE__ */ jsx6(Text2, { style: { color: "#ffffff", fontSize: 12, fontWeight: "700" }, children: "\u2713" })
1128
- }
1129
- ),
1130
- p.label && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1131
- ]
1111
+ children
1132
1112
  }
1133
1113
  );
1134
1114
  }
1135
- function SliderComponent({ element }) {
1115
+ function ColumnComponent({ element, children }) {
1136
1116
  const p = element.props;
1137
- const min = p.min ?? 0;
1138
- const max = p.max ?? 100;
1139
- const value = p.value ?? min;
1140
- const progress = max > min ? (value - min) / (max - min) : 0;
1141
- const trackColor = p.color ?? "#3b82f6";
1142
- return /* @__PURE__ */ jsxs2(View2, { children: [
1143
- p.label && /* @__PURE__ */ jsxs2(
1144
- View2,
1145
- {
1146
- style: {
1147
- flexDirection: "row",
1148
- justifyContent: "space-between",
1149
- marginBottom: 4
1150
- },
1151
- children: [
1152
- /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#374151" }, children: p.label }),
1153
- /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#6b7280" }, children: value })
1154
- ]
1155
- }
1156
- ),
1157
- /* @__PURE__ */ jsx6(
1158
- View2,
1159
- {
1160
- style: {
1161
- height: 6,
1162
- backgroundColor: "#e5e7eb",
1163
- borderRadius: 3,
1164
- overflow: "hidden"
1165
- },
1166
- children: /* @__PURE__ */ jsx6(
1167
- View2,
1168
- {
1169
- style: {
1170
- width: `${Math.min(Math.max(progress * 100, 0), 100)}%`,
1171
- height: "100%",
1172
- backgroundColor: trackColor,
1173
- borderRadius: 3
1174
- }
1175
- }
1176
- )
1177
- }
1178
- )
1179
- ] });
1117
+ return /* @__PURE__ */ jsx6(
1118
+ View2,
1119
+ {
1120
+ style: {
1121
+ flexDirection: "column",
1122
+ gap: p.gap ?? void 0,
1123
+ alignItems: p.alignItems ?? void 0,
1124
+ justifyContent: p.justifyContent ?? void 0,
1125
+ padding: p.padding ?? void 0,
1126
+ flex: p.flex ?? void 0
1127
+ },
1128
+ children
1129
+ }
1130
+ );
1180
1131
  }
1181
- function SearchBarComponent({ element, emit }) {
1132
+ function ScrollContainerComponent({ element, children }) {
1182
1133
  const p = element.props;
1183
- return /* @__PURE__ */ jsxs2(
1184
- View2,
1134
+ return /* @__PURE__ */ jsx6(
1135
+ ScrollView,
1185
1136
  {
1137
+ horizontal: p.horizontal ?? false,
1138
+ showsVerticalScrollIndicator: p.showsScrollIndicator ?? true,
1139
+ showsHorizontalScrollIndicator: p.showsScrollIndicator ?? true,
1140
+ contentContainerStyle: {
1141
+ padding: p.padding ?? void 0
1142
+ },
1186
1143
  style: {
1187
- flexDirection: "row",
1188
- alignItems: "center",
1189
- backgroundColor: "#f3f4f6",
1190
- borderRadius: 8,
1191
- paddingHorizontal: 12
1144
+ flex: p.flex ?? 1,
1145
+ backgroundColor: p.backgroundColor ?? void 0
1192
1146
  },
1193
- children: [
1194
- /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#9ca3af", marginRight: 8 }, children: "Search" }),
1195
- /* @__PURE__ */ jsx6(
1196
- RNTextInput,
1197
- {
1198
- placeholder: p.placeholder ?? "Search...",
1199
- value: p.value ?? void 0,
1200
- returnKeyType: "search",
1201
- onSubmitEditing: () => emit?.("submit"),
1202
- style: {
1203
- flex: 1,
1204
- paddingVertical: 10,
1205
- fontSize: 16,
1206
- color: "#111827"
1207
- }
1208
- }
1209
- )
1210
- ]
1147
+ children
1211
1148
  }
1212
1149
  );
1213
1150
  }
1214
- function SpinnerComponent({ element }) {
1151
+ function SafeAreaComponent({ element, children }) {
1215
1152
  const p = element.props;
1216
- return /* @__PURE__ */ jsx6(ActivityIndicator, { size: p.size ?? "small", color: p.color ?? "#3b82f6" });
1153
+ return /* @__PURE__ */ jsx6(
1154
+ SafeAreaView,
1155
+ {
1156
+ style: {
1157
+ flex: 1,
1158
+ backgroundColor: p.backgroundColor ?? void 0
1159
+ },
1160
+ children
1161
+ }
1162
+ );
1217
1163
  }
1218
- function ProgressBarComponent({ element }) {
1164
+ function SpacerComponent({ element }) {
1219
1165
  const p = element.props;
1220
- const height = p.height ?? 6;
1221
- const progress = Math.min(Math.max(p.progress, 0), 1);
1222
1166
  return /* @__PURE__ */ jsx6(
1223
1167
  View2,
1224
1168
  {
1225
1169
  style: {
1226
- height,
1227
- backgroundColor: p.trackColor ?? "#e5e7eb",
1228
- borderRadius: height / 2,
1229
- overflow: "hidden"
1230
- },
1231
- children: /* @__PURE__ */ jsx6(
1232
- View2,
1233
- {
1234
- style: {
1235
- width: `${progress * 100}%`,
1236
- height: "100%",
1237
- backgroundColor: p.color ?? "#3b82f6",
1238
- borderRadius: height / 2
1239
- }
1240
- }
1241
- )
1170
+ width: p.size ?? void 0,
1171
+ height: p.size ?? void 0,
1172
+ flex: p.flex ?? void 0
1173
+ }
1242
1174
  }
1243
1175
  );
1244
1176
  }
1245
- function CardComponent({ element, children }) {
1177
+ function PressableComponent({ children, emit }) {
1178
+ return /* @__PURE__ */ jsx6(
1179
+ Pressable2,
1180
+ {
1181
+ onPress: () => emit("press"),
1182
+ onLongPress: () => emit("longPress"),
1183
+ style: ({ pressed }) => ({
1184
+ opacity: pressed ? 0.7 : 1
1185
+ }),
1186
+ children
1187
+ }
1188
+ );
1189
+ }
1190
+ function DividerComponent({ element }) {
1246
1191
  const p = element.props;
1247
- const elevated = p.elevated !== false;
1248
- const padding = p.padding ?? 16;
1249
- return /* @__PURE__ */ jsxs2(
1192
+ const isVertical = p.direction === "vertical";
1193
+ const thickness = p.thickness ?? 1;
1194
+ const color = p.color ?? "#e5e7eb";
1195
+ return /* @__PURE__ */ jsx6(
1250
1196
  View2,
1251
1197
  {
1252
- style: [
1253
- {
1254
- backgroundColor: p.backgroundColor ?? "#ffffff",
1255
- borderRadius: p.borderRadius ?? 12,
1256
- padding
1257
- },
1258
- elevated && {
1259
- shadowColor: "#000",
1260
- shadowOffset: { width: 0, height: 2 },
1261
- shadowOpacity: 0.1,
1262
- shadowRadius: 8,
1263
- elevation: 3
1264
- }
1265
- ],
1266
- children: [
1267
- p.title && /* @__PURE__ */ jsx6(
1268
- Text2,
1269
- {
1270
- style: {
1271
- fontSize: 18,
1272
- fontWeight: "600",
1273
- color: "#111827",
1274
- marginBottom: p.subtitle ? 2 : 12
1275
- },
1276
- children: p.title
1277
- }
1278
- ),
1279
- p.subtitle && /* @__PURE__ */ jsx6(
1280
- Text2,
1281
- {
1282
- style: {
1283
- fontSize: 14,
1284
- color: "#6b7280",
1285
- marginBottom: 12
1286
- },
1287
- children: p.subtitle
1288
- }
1289
- ),
1290
- children
1291
- ]
1198
+ style: {
1199
+ width: isVertical ? thickness : "100%",
1200
+ height: isVertical ? "100%" : thickness,
1201
+ backgroundColor: color,
1202
+ marginVertical: isVertical ? 0 : p.margin ?? 8,
1203
+ marginHorizontal: isVertical ? p.margin ?? 8 : 0
1204
+ }
1205
+ }
1206
+ );
1207
+ }
1208
+ var headingSizes = {
1209
+ h1: 32,
1210
+ h2: 24,
1211
+ h3: 20,
1212
+ h4: 16
1213
+ };
1214
+ function HeadingComponent({ element }) {
1215
+ const p = element.props;
1216
+ const level = p.level ?? "h2";
1217
+ return /* @__PURE__ */ jsx6(
1218
+ Text2,
1219
+ {
1220
+ style: {
1221
+ fontSize: headingSizes[level],
1222
+ fontWeight: "700",
1223
+ color: p.color ?? "#111827",
1224
+ textAlign: p.align ?? "left"
1225
+ },
1226
+ children: p.text
1292
1227
  }
1293
1228
  );
1294
1229
  }
1295
- function ListItemComponent({ element, emit }) {
1230
+ function ParagraphComponent({ element }) {
1296
1231
  const p = element.props;
1297
- const hasPress = !!element.on?.press;
1298
- const content = /* @__PURE__ */ jsxs2(
1299
- View2,
1232
+ return /* @__PURE__ */ jsx6(
1233
+ Text2,
1300
1234
  {
1235
+ numberOfLines: p.numberOfLines ?? void 0,
1301
1236
  style: {
1302
- flexDirection: "row",
1303
- alignItems: "center",
1304
- paddingVertical: 12,
1305
- paddingHorizontal: 16
1237
+ fontSize: p.fontSize ?? 16,
1238
+ lineHeight: (p.fontSize ?? 16) * 1.5,
1239
+ color: p.color ?? "#374151",
1240
+ textAlign: p.align ?? "left"
1306
1241
  },
1307
- children: [
1308
- p.leading && /* @__PURE__ */ jsx6(
1309
- Text2,
1310
- {
1311
- style: {
1312
- fontSize: 16,
1313
- color: "#6b7280",
1314
- marginRight: 12,
1315
- width: 24,
1316
- textAlign: "center"
1317
- },
1318
- children: p.leading
1319
- }
1320
- ),
1321
- /* @__PURE__ */ jsxs2(View2, { style: { flex: 1 }, children: [
1322
- /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#111827" }, children: p.title }),
1323
- p.subtitle && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#6b7280", marginTop: 2 }, children: p.subtitle })
1324
- ] }),
1325
- p.trailing && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#6b7280", marginLeft: 8 }, children: p.trailing }),
1326
- p.showChevron && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#9ca3af", marginLeft: 8 }, children: ">" })
1327
- ]
1242
+ children: p.text
1328
1243
  }
1329
1244
  );
1330
- if (hasPress) {
1331
- return /* @__PURE__ */ jsx6(
1332
- Pressable2,
1333
- {
1334
- onPress: () => emit?.("press"),
1335
- style: ({ pressed }) => ({
1336
- opacity: pressed ? 0.7 : 1,
1337
- backgroundColor: pressed ? "#f9fafb" : "transparent"
1338
- }),
1339
- children: content
1340
- }
1341
- );
1342
- }
1343
- return content;
1344
1245
  }
1345
- function ModalComponent({ element, children }) {
1246
+ var labelSizes = {
1247
+ xs: 10,
1248
+ sm: 12,
1249
+ md: 14
1250
+ };
1251
+ function LabelComponent({ element }) {
1346
1252
  const p = element.props;
1347
1253
  return /* @__PURE__ */ jsx6(
1348
- RNModal,
1254
+ Text2,
1349
1255
  {
1350
- visible: p.visible,
1351
- animationType: p.animationType ?? "slide",
1352
- transparent: true,
1353
- children: /* @__PURE__ */ jsx6(View2, { style: modalStyles.overlay, children: /* @__PURE__ */ jsxs2(View2, { style: modalStyles.content, children: [
1354
- p.title && /* @__PURE__ */ jsx6(Text2, { style: modalStyles.title, children: p.title }),
1355
- children
1356
- ] }) })
1256
+ style: {
1257
+ fontSize: labelSizes[p.size ?? "sm"],
1258
+ fontWeight: p.bold ? "600" : "400",
1259
+ color: p.color ?? "#6b7280"
1260
+ },
1261
+ children: p.text
1357
1262
  }
1358
1263
  );
1359
1264
  }
1360
- function createStandardActionHandlers(options) {
1361
- return {
1362
- navigate: async (params) => {
1363
- const screen = params.screen;
1364
- const navParams = params.params;
1365
- options?.navigate?.(screen, navParams ?? void 0);
1366
- },
1367
- goBack: async () => {
1368
- options?.goBack?.();
1369
- },
1370
- showAlert: async (params) => {
1371
- const title = params.title;
1372
- const message = params.message ?? void 0;
1373
- const buttons = params.buttons;
1374
- const alertButtons = buttons?.map((btn) => ({
1375
- text: btn.text,
1376
- style: btn.style
1377
- }));
1378
- Alert.alert(title, message, alertButtons);
1379
- },
1380
- share: async (params) => {
1381
- const message = params.message;
1382
- const url = params.url ?? void 0;
1383
- const title = params.title ?? void 0;
1384
- await Share.share({ message, url, title });
1385
- },
1386
- openURL: async (params) => {
1387
- const url = params.url;
1388
- const canOpen = await Linking.canOpenURL(url);
1389
- if (canOpen) {
1390
- await Linking.openURL(url);
1265
+ function ImageComponent({ element }) {
1266
+ const p = element.props;
1267
+ return /* @__PURE__ */ jsx6(
1268
+ RNImage,
1269
+ {
1270
+ source: { uri: p.src },
1271
+ accessibilityLabel: p.alt ?? void 0,
1272
+ resizeMode: p.resizeMode ?? "cover",
1273
+ style: {
1274
+ width: p.width ?? "100%",
1275
+ height: p.height ?? 200,
1276
+ borderRadius: p.borderRadius ?? 0
1391
1277
  }
1392
- },
1393
- setData: async (params) => {
1394
- const path = params.path;
1395
- const value = params.value;
1396
- options?.onSetData?.(path, value);
1397
- },
1398
- refresh: async (params) => {
1399
- const target = params.target ?? void 0;
1400
- options?.onRefresh?.(target);
1401
1278
  }
1402
- };
1279
+ );
1403
1280
  }
1404
- var standardComponents = {
1405
- // Layout
1406
- Container: ContainerComponent,
1407
- Row: RowComponent,
1408
- Column: ColumnComponent,
1409
- ScrollContainer: ScrollContainerComponent,
1410
- SafeArea: SafeAreaComponent,
1411
- Pressable: PressableComponent,
1412
- Spacer: SpacerComponent,
1413
- Divider: DividerComponent,
1414
- // Content
1415
- Heading: HeadingComponent,
1416
- Paragraph: ParagraphComponent,
1417
- Label: LabelComponent,
1418
- Image: ImageComponent,
1419
- Avatar: AvatarComponent,
1420
- Badge: BadgeComponent,
1421
- Chip: ChipComponent,
1422
- // Input
1423
- Button: ButtonComponent,
1424
- TextInput: TextInputComponent,
1425
- Switch: SwitchComponent,
1426
- Checkbox: CheckboxComponent,
1427
- Slider: SliderComponent,
1428
- SearchBar: SearchBarComponent,
1429
- // Feedback
1430
- Spinner: SpinnerComponent,
1431
- ProgressBar: ProgressBarComponent,
1432
- // Composite
1433
- Card: CardComponent,
1434
- ListItem: ListItemComponent,
1435
- Modal: ModalComponent
1436
- };
1437
- var inputStyles = StyleSheet2.create({
1438
- label: {
1439
- fontSize: 14,
1440
- fontWeight: "500",
1441
- color: "#374151",
1442
- marginBottom: 4
1443
- },
1444
- input: {
1445
- borderWidth: 1,
1446
- borderColor: "#d1d5db",
1447
- borderRadius: 8,
1448
- paddingHorizontal: 12,
1449
- paddingVertical: 10,
1450
- fontSize: 16,
1451
- color: "#111827",
1452
- backgroundColor: "#ffffff"
1453
- }
1454
- });
1455
- var modalStyles = StyleSheet2.create({
1456
- overlay: {
1457
- flex: 1,
1458
- backgroundColor: "rgba(0, 0, 0, 0.5)",
1459
- justifyContent: "flex-end"
1460
- },
1461
- content: {
1462
- backgroundColor: "#ffffff",
1463
- borderTopLeftRadius: 16,
1464
- borderTopRightRadius: 16,
1465
- padding: 24,
1466
- maxHeight: "80%"
1467
- },
1468
- title: {
1469
- fontSize: 20,
1470
- fontWeight: "600",
1471
- color: "#111827",
1472
- marginBottom: 16
1473
- }
1474
- });
1475
-
1476
- // src/renderer.tsx
1477
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1478
- var ElementErrorBoundary = class extends React6.Component {
1479
- constructor(props) {
1480
- super(props);
1481
- this.state = { hasError: false };
1482
- }
1483
- static getDerivedStateFromError() {
1484
- return { hasError: true };
1485
- }
1486
- componentDidCatch(error, info) {
1487
- console.error(
1488
- `[json-render] Rendering error in <${this.props.elementType}>:`,
1489
- error,
1490
- info.componentStack
1491
- );
1492
- }
1493
- render() {
1494
- if (this.state.hasError) {
1495
- return null;
1496
- }
1497
- return this.props.children;
1498
- }
1281
+ var avatarSizeMap = {
1282
+ sm: 32,
1283
+ md: 48,
1284
+ lg: 64,
1285
+ xl: 96
1499
1286
  };
1500
- function ElementRenderer({
1501
- element,
1502
- spec,
1503
- registry,
1504
- loading,
1505
- fallback
1506
- }) {
1507
- const repeatScope = useRepeatScope();
1508
- const { ctx } = useVisibility();
1509
- const { execute } = useActions();
1510
- let effectiveElement = element;
1511
- if (repeatScope) {
1512
- const rewrittenProps = rewriteRepeatTokens(
1513
- element.props,
1514
- repeatScope.basePath,
1515
- repeatScope.index
1287
+ function AvatarComponent({ element }) {
1288
+ const p = element.props;
1289
+ const size = avatarSizeMap[p.size ?? "md"];
1290
+ const bgColor = p.backgroundColor ?? "#6366f1";
1291
+ if (p.src) {
1292
+ return /* @__PURE__ */ jsx6(
1293
+ RNImage,
1294
+ {
1295
+ source: { uri: p.src },
1296
+ style: {
1297
+ width: size,
1298
+ height: size,
1299
+ borderRadius: size / 2,
1300
+ backgroundColor: "#e5e7eb"
1301
+ }
1302
+ }
1516
1303
  );
1517
- const rewrittenVisible = element.visible !== void 0 ? rewriteRepeatTokens(
1518
- element.visible,
1519
- repeatScope.basePath,
1520
- repeatScope.index
1521
- ) : element.visible;
1522
- const rewrittenOn = element.on !== void 0 ? rewriteRepeatTokens(
1523
- element.on,
1524
- repeatScope.basePath,
1525
- repeatScope.index
1526
- ) : element.on;
1527
- if (rewrittenProps !== element.props || rewrittenVisible !== element.visible || rewrittenOn !== element.on) {
1528
- effectiveElement = {
1529
- ...element,
1530
- props: rewrittenProps,
1531
- visible: rewrittenVisible,
1532
- on: rewrittenOn
1533
- };
1534
- }
1535
1304
  }
1536
- const isVisible = useIsVisible(effectiveElement.visible);
1537
- const onBindings = effectiveElement.on;
1538
- const emit = useCallback4(
1539
- (eventName) => {
1540
- const binding = onBindings?.[eventName];
1541
- if (!binding) return;
1542
- const bindings = Array.isArray(binding) ? binding : [binding];
1543
- for (const b of bindings) {
1544
- execute(b);
1545
- }
1546
- },
1547
- [onBindings, execute]
1305
+ return /* @__PURE__ */ jsx6(
1306
+ View2,
1307
+ {
1308
+ style: {
1309
+ width: size,
1310
+ height: size,
1311
+ borderRadius: size / 2,
1312
+ backgroundColor: bgColor,
1313
+ alignItems: "center",
1314
+ justifyContent: "center"
1315
+ },
1316
+ children: /* @__PURE__ */ jsx6(
1317
+ Text2,
1318
+ {
1319
+ style: {
1320
+ color: "#ffffff",
1321
+ fontSize: size * 0.4,
1322
+ fontWeight: "600"
1323
+ },
1324
+ children: p.initials ?? "?"
1325
+ }
1326
+ )
1327
+ }
1548
1328
  );
1549
- if (!isVisible) {
1550
- return null;
1551
- }
1552
- const resolvedProps = resolveElementProps(
1553
- effectiveElement.props,
1554
- ctx
1329
+ }
1330
+ var badgeVariantColors = {
1331
+ default: { bg: "#e5e7eb", text: "#374151" },
1332
+ info: { bg: "#dbeafe", text: "#1d4ed8" },
1333
+ success: { bg: "#dcfce7", text: "#15803d" },
1334
+ warning: { bg: "#fef3c7", text: "#92400e" },
1335
+ error: { bg: "#fee2e2", text: "#dc2626" }
1336
+ };
1337
+ function BadgeComponent({ element }) {
1338
+ const p = element.props;
1339
+ const colors = badgeVariantColors[p.variant ?? "default"] ?? {
1340
+ bg: "#e5e7eb",
1341
+ text: "#374151"
1342
+ };
1343
+ return /* @__PURE__ */ jsx6(
1344
+ View2,
1345
+ {
1346
+ style: {
1347
+ backgroundColor: colors.bg,
1348
+ paddingHorizontal: 8,
1349
+ paddingVertical: 2,
1350
+ borderRadius: 9999,
1351
+ alignSelf: "flex-start"
1352
+ },
1353
+ children: /* @__PURE__ */ jsx6(
1354
+ Text2,
1355
+ {
1356
+ style: {
1357
+ fontSize: 12,
1358
+ fontWeight: "600",
1359
+ color: colors.text
1360
+ },
1361
+ children: p.label
1362
+ }
1363
+ )
1364
+ }
1555
1365
  );
1556
- const resolvedElement = resolvedProps !== effectiveElement.props ? { ...effectiveElement, props: resolvedProps } : effectiveElement;
1557
- const Component = registry[resolvedElement.type] ?? fallback;
1558
- if (!Component) {
1559
- console.warn(`No renderer for component type: ${resolvedElement.type}`);
1560
- return null;
1561
- }
1562
- const children = resolvedElement.repeat ? /* @__PURE__ */ jsx7(
1563
- RepeatChildren,
1366
+ }
1367
+ function ChipComponent({ element, emit }) {
1368
+ const p = element.props;
1369
+ const bgColor = p.selected ? "#6366f1" : p.backgroundColor ?? "#f3f4f6";
1370
+ const textColor = p.selected ? "#ffffff" : "#374151";
1371
+ const hasRemove = !!element.on?.remove;
1372
+ return /* @__PURE__ */ jsxs2(
1373
+ Pressable2,
1564
1374
  {
1565
- element: resolvedElement,
1566
- spec,
1567
- registry,
1568
- loading,
1569
- fallback
1375
+ onPress: () => emit("press"),
1376
+ style: {
1377
+ flexDirection: "row",
1378
+ alignItems: "center",
1379
+ backgroundColor: bgColor,
1380
+ paddingHorizontal: 12,
1381
+ paddingVertical: 6,
1382
+ borderRadius: 9999,
1383
+ alignSelf: "flex-start"
1384
+ },
1385
+ children: [
1386
+ /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: textColor }, children: p.label }),
1387
+ hasRemove && /* @__PURE__ */ jsx6(Pressable2, { onPress: () => emit("remove"), style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: textColor, fontWeight: "700" }, children: "x" }) })
1388
+ ]
1570
1389
  }
1571
- ) : resolvedElement.children?.map((childKey) => {
1572
- const childElement = spec.elements[childKey];
1573
- if (!childElement) {
1574
- if (!loading) {
1575
- console.warn(
1576
- `[json-render] Missing element "${childKey}" referenced as child of "${resolvedElement.type}". This element will not render.`
1577
- );
1578
- }
1579
- return null;
1390
+ );
1391
+ }
1392
+ var buttonVariantStyles = {
1393
+ primary: { bg: "#3b82f6", text: "#ffffff" },
1394
+ secondary: { bg: "#6b7280", text: "#ffffff" },
1395
+ danger: { bg: "#dc2626", text: "#ffffff" },
1396
+ outline: { bg: "transparent", text: "#3b82f6", border: "#3b82f6" },
1397
+ ghost: { bg: "transparent", text: "#374151" }
1398
+ };
1399
+ var buttonSizeStyles = {
1400
+ sm: { paddingH: 12, paddingV: 6, fontSize: 13 },
1401
+ md: { paddingH: 16, paddingV: 10, fontSize: 15 },
1402
+ lg: { paddingH: 24, paddingV: 14, fontSize: 17 }
1403
+ };
1404
+ function ButtonComponent({ element, emit }) {
1405
+ const p = element.props;
1406
+ const variant = buttonVariantStyles[p.variant ?? "primary"] ?? {
1407
+ bg: "#3b82f6",
1408
+ text: "#ffffff"
1409
+ };
1410
+ const size = buttonSizeStyles[p.size ?? "md"] ?? {
1411
+ paddingH: 16,
1412
+ paddingV: 10,
1413
+ fontSize: 15
1414
+ };
1415
+ const disabled = p.disabled || p.loading;
1416
+ return /* @__PURE__ */ jsxs2(
1417
+ Pressable2,
1418
+ {
1419
+ disabled,
1420
+ onPress: () => emit("press"),
1421
+ style: ({ pressed }) => ({
1422
+ backgroundColor: variant.bg,
1423
+ paddingHorizontal: size.paddingH,
1424
+ paddingVertical: size.paddingV,
1425
+ borderRadius: 8,
1426
+ alignItems: "center",
1427
+ justifyContent: "center",
1428
+ flexDirection: "row",
1429
+ opacity: disabled ? 0.5 : pressed ? 0.8 : 1,
1430
+ borderWidth: variant.border ? 1 : 0,
1431
+ borderColor: variant.border,
1432
+ alignSelf: "flex-start"
1433
+ }),
1434
+ children: [
1435
+ p.loading && /* @__PURE__ */ jsx6(
1436
+ ActivityIndicator,
1437
+ {
1438
+ size: "small",
1439
+ color: variant.text,
1440
+ style: { marginRight: 8 }
1441
+ }
1442
+ ),
1443
+ /* @__PURE__ */ jsx6(
1444
+ Text2,
1445
+ {
1446
+ style: {
1447
+ color: variant.text,
1448
+ fontSize: size.fontSize,
1449
+ fontWeight: "600"
1450
+ },
1451
+ children: p.label
1452
+ }
1453
+ )
1454
+ ]
1580
1455
  }
1581
- return /* @__PURE__ */ jsx7(
1582
- ElementRenderer,
1583
- {
1584
- element: childElement,
1585
- spec,
1586
- registry,
1587
- loading,
1588
- fallback
1589
- },
1590
- childKey
1591
- );
1592
- });
1593
- return /* @__PURE__ */ jsx7(ElementErrorBoundary, { elementType: resolvedElement.type, children: /* @__PURE__ */ jsx7(Component, { element: resolvedElement, emit, loading, children }) });
1456
+ );
1594
1457
  }
1595
- function RepeatChildren({
1596
- element,
1597
- spec,
1598
- registry,
1599
- loading,
1600
- fallback
1601
- }) {
1602
- const { state } = useStateStore();
1603
- const repeat = element.repeat;
1604
- const statePath = repeat.path;
1605
- const items = getByPath2(state, statePath) ?? [];
1606
- return /* @__PURE__ */ jsx7(Fragment, { children: items.map((item, index) => {
1607
- const key = repeat.key && typeof item === "object" && item !== null ? String(item[repeat.key] ?? index) : String(index);
1608
- return /* @__PURE__ */ jsx7(
1609
- RepeatScopeProvider,
1458
+ function TextInputComponent({ element, bindings }) {
1459
+ const p = element.props;
1460
+ const [boundValue, setBoundValue] = useBoundProp(
1461
+ p.value,
1462
+ bindings?.value
1463
+ );
1464
+ const displayValue = boundValue ?? "";
1465
+ return /* @__PURE__ */ jsxs2(View2, { style: p.flex != null ? { flex: p.flex } : void 0, children: [
1466
+ p.label && /* @__PURE__ */ jsx6(Text2, { style: inputStyles.label, children: p.label }),
1467
+ /* @__PURE__ */ jsx6(
1468
+ RNTextInput,
1610
1469
  {
1611
- basePath: `${statePath}/${index}`,
1612
- index,
1613
- children: element.children?.map((childKey) => {
1614
- const childElement = spec.elements[childKey];
1615
- if (!childElement) {
1616
- if (!loading) {
1617
- console.warn(
1618
- `[json-render] Missing element "${childKey}" referenced as child of "${element.type}" (repeat). This element will not render.`
1619
- );
1620
- }
1621
- return null;
1470
+ placeholder: p.placeholder ?? void 0,
1471
+ value: displayValue,
1472
+ onChangeText: bindings?.value ? setBoundValue : void 0,
1473
+ secureTextEntry: p.secureTextEntry ?? false,
1474
+ keyboardType: p.keyboardType ?? "default",
1475
+ multiline: p.multiline ?? false,
1476
+ numberOfLines: p.numberOfLines ?? void 0,
1477
+ style: [
1478
+ inputStyles.input,
1479
+ p.multiline && {
1480
+ minHeight: (p.numberOfLines ?? 3) * 20,
1481
+ textAlignVertical: "top"
1622
1482
  }
1623
- return /* @__PURE__ */ jsx7(
1624
- ElementRenderer,
1625
- {
1626
- element: childElement,
1627
- spec,
1628
- registry,
1629
- loading,
1630
- fallback
1631
- },
1632
- childKey
1633
- );
1634
- })
1635
- },
1636
- key
1637
- );
1638
- }) });
1483
+ ]
1484
+ }
1485
+ )
1486
+ ] });
1487
+ }
1488
+ function SwitchComponent({ element, bindings }) {
1489
+ const p = element.props;
1490
+ const [checked, setChecked] = useBoundProp(
1491
+ p.checked,
1492
+ bindings?.checked
1493
+ );
1494
+ const displayValue = checked ?? false;
1495
+ return /* @__PURE__ */ jsxs2(View2, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
1496
+ /* @__PURE__ */ jsx6(
1497
+ RNSwitch,
1498
+ {
1499
+ value: displayValue,
1500
+ onValueChange: bindings?.checked ? setChecked : void 0,
1501
+ disabled: p.disabled ?? false
1502
+ }
1503
+ ),
1504
+ p.label && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1505
+ ] });
1639
1506
  }
1640
- function Renderer({
1641
- spec,
1642
- registry: customRegistry,
1643
- includeStandard = true,
1644
- loading,
1645
- fallback
1646
- }) {
1647
- const registry = useMemo5(
1648
- () => ({
1649
- ...includeStandard ? standardComponents : {},
1650
- ...customRegistry
1651
- }),
1652
- [customRegistry, includeStandard]
1507
+ function CheckboxComponent({ element, bindings }) {
1508
+ const p = element.props;
1509
+ const [checked, setChecked] = useBoundProp(
1510
+ p.checked,
1511
+ bindings?.checked
1653
1512
  );
1654
- if (!spec || !spec.root) {
1655
- return null;
1656
- }
1657
- const rootElement = spec.elements[spec.root];
1658
- if (!rootElement) {
1659
- return null;
1660
- }
1661
- return /* @__PURE__ */ jsx7(
1662
- ElementRenderer,
1513
+ const isChecked = checked ?? false;
1514
+ return /* @__PURE__ */ jsxs2(
1515
+ Pressable2,
1663
1516
  {
1664
- element: rootElement,
1665
- spec,
1666
- registry,
1667
- loading,
1668
- fallback
1517
+ disabled: p.disabled ?? false,
1518
+ onPress: () => {
1519
+ if (bindings?.checked) {
1520
+ setChecked(!isChecked);
1521
+ }
1522
+ },
1523
+ style: {
1524
+ flexDirection: "row",
1525
+ alignItems: "center",
1526
+ gap: 8,
1527
+ opacity: p.disabled ? 0.5 : 1
1528
+ },
1529
+ children: [
1530
+ /* @__PURE__ */ jsx6(
1531
+ View2,
1532
+ {
1533
+ style: {
1534
+ width: 20,
1535
+ height: 20,
1536
+ borderRadius: 4,
1537
+ borderWidth: 2,
1538
+ borderColor: isChecked ? "#3b82f6" : "#d1d5db",
1539
+ backgroundColor: isChecked ? "#3b82f6" : "transparent",
1540
+ alignItems: "center",
1541
+ justifyContent: "center"
1542
+ },
1543
+ children: isChecked && /* @__PURE__ */ jsx6(Text2, { style: { color: "#ffffff", fontSize: 12, fontWeight: "700" }, children: "\u2713" })
1544
+ }
1545
+ ),
1546
+ p.label && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1547
+ ]
1669
1548
  }
1670
1549
  );
1671
1550
  }
1672
- function JSONUIProvider({
1673
- registry,
1674
- initialState,
1675
- authState,
1676
- actionHandlers,
1677
- navigate,
1678
- validationFunctions,
1679
- onStateChange,
1680
- children
1681
- }) {
1682
- return /* @__PURE__ */ jsx7(
1683
- StateProvider,
1551
+ function SliderComponent({ element }) {
1552
+ const p = element.props;
1553
+ const min = p.min ?? 0;
1554
+ const max = p.max ?? 100;
1555
+ const value = p.value ?? min;
1556
+ const progress = max > min ? (value - min) / (max - min) : 0;
1557
+ const trackColor = p.color ?? "#3b82f6";
1558
+ return /* @__PURE__ */ jsxs2(View2, { children: [
1559
+ p.label && /* @__PURE__ */ jsxs2(
1560
+ View2,
1561
+ {
1562
+ style: {
1563
+ flexDirection: "row",
1564
+ justifyContent: "space-between",
1565
+ marginBottom: 4
1566
+ },
1567
+ children: [
1568
+ /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#374151" }, children: p.label }),
1569
+ /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#6b7280" }, children: value })
1570
+ ]
1571
+ }
1572
+ ),
1573
+ /* @__PURE__ */ jsx6(
1574
+ View2,
1575
+ {
1576
+ style: {
1577
+ height: 6,
1578
+ backgroundColor: "#e5e7eb",
1579
+ borderRadius: 3,
1580
+ overflow: "hidden"
1581
+ },
1582
+ children: /* @__PURE__ */ jsx6(
1583
+ View2,
1584
+ {
1585
+ style: {
1586
+ width: `${Math.min(Math.max(progress * 100, 0), 100)}%`,
1587
+ height: "100%",
1588
+ backgroundColor: trackColor,
1589
+ borderRadius: 3
1590
+ }
1591
+ }
1592
+ )
1593
+ }
1594
+ )
1595
+ ] });
1596
+ }
1597
+ function SearchBarComponent({ element, emit }) {
1598
+ const p = element.props;
1599
+ return /* @__PURE__ */ jsxs2(
1600
+ View2,
1684
1601
  {
1685
- initialState,
1686
- authState,
1687
- onStateChange,
1688
- children: /* @__PURE__ */ jsx7(VisibilityProvider, { children: /* @__PURE__ */ jsx7(ActionProvider, { handlers: actionHandlers, navigate, children: /* @__PURE__ */ jsxs3(ValidationProvider, { customFunctions: validationFunctions, children: [
1689
- children,
1690
- /* @__PURE__ */ jsx7(ConfirmationDialogManager, {})
1691
- ] }) }) })
1602
+ style: {
1603
+ flexDirection: "row",
1604
+ alignItems: "center",
1605
+ backgroundColor: "#f3f4f6",
1606
+ borderRadius: 8,
1607
+ paddingHorizontal: 12
1608
+ },
1609
+ children: [
1610
+ /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#9ca3af", marginRight: 8 }, children: "Search" }),
1611
+ /* @__PURE__ */ jsx6(
1612
+ RNTextInput,
1613
+ {
1614
+ placeholder: p.placeholder ?? "Search...",
1615
+ value: p.value ?? void 0,
1616
+ returnKeyType: "search",
1617
+ onSubmitEditing: () => emit("submit"),
1618
+ style: {
1619
+ flex: 1,
1620
+ paddingVertical: 10,
1621
+ fontSize: 16,
1622
+ color: "#111827"
1623
+ }
1624
+ }
1625
+ )
1626
+ ]
1692
1627
  }
1693
1628
  );
1694
1629
  }
1695
- function ConfirmationDialogManager() {
1696
- const { pendingConfirmation, confirm, cancel } = useActions();
1697
- if (!pendingConfirmation?.action.confirm) {
1698
- return null;
1699
- }
1700
- return /* @__PURE__ */ jsx7(
1701
- ConfirmDialog,
1630
+ function SpinnerComponent({ element }) {
1631
+ const p = element.props;
1632
+ return /* @__PURE__ */ jsx6(ActivityIndicator, { size: p.size ?? "small", color: p.color ?? "#3b82f6" });
1633
+ }
1634
+ function ProgressBarComponent({ element }) {
1635
+ const p = element.props;
1636
+ const height = p.height ?? 6;
1637
+ const progress = Math.min(Math.max(p.progress, 0), 1);
1638
+ return /* @__PURE__ */ jsx6(
1639
+ View2,
1702
1640
  {
1703
- confirm: pendingConfirmation.action.confirm,
1704
- onConfirm: confirm,
1705
- onCancel: cancel
1641
+ style: {
1642
+ height,
1643
+ backgroundColor: p.trackColor ?? "#e5e7eb",
1644
+ borderRadius: height / 2,
1645
+ overflow: "hidden"
1646
+ },
1647
+ children: /* @__PURE__ */ jsx6(
1648
+ View2,
1649
+ {
1650
+ style: {
1651
+ width: `${progress * 100}%`,
1652
+ height: "100%",
1653
+ backgroundColor: p.color ?? "#3b82f6",
1654
+ borderRadius: height / 2
1655
+ }
1656
+ }
1657
+ )
1706
1658
  }
1707
1659
  );
1708
1660
  }
1709
- function createRendererFromCatalog(_catalog, registry) {
1710
- return function CatalogRenderer(props) {
1711
- return /* @__PURE__ */ jsx7(Renderer, { ...props, registry });
1712
- };
1713
- }
1714
- function defineRegistry(_catalog, options) {
1715
- const registry = {};
1716
- if (options.components) {
1717
- for (const [name, componentFn] of Object.entries(options.components)) {
1718
- registry[name] = ({
1719
- element,
1720
- children,
1721
- emit,
1722
- loading
1723
- }) => {
1724
- return componentFn({
1725
- props: element.props,
1726
- children,
1727
- emit,
1728
- loading
1729
- });
1730
- };
1731
- }
1732
- }
1733
- const actionMap = options.actions ? Object.entries(options.actions) : [];
1734
- const handlers = (getSetState, getState) => {
1735
- const result = {};
1736
- for (const [name, actionFn] of actionMap) {
1737
- result[name] = async (params) => {
1738
- const setState = getSetState();
1739
- const state = getState();
1740
- if (setState) {
1741
- await actionFn(params, setState, state);
1661
+ function CardComponent({ element, children }) {
1662
+ const p = element.props;
1663
+ const elevated = p.elevated !== false;
1664
+ const padding = p.padding ?? 16;
1665
+ return /* @__PURE__ */ jsxs2(
1666
+ View2,
1667
+ {
1668
+ style: [
1669
+ {
1670
+ backgroundColor: p.backgroundColor ?? "#ffffff",
1671
+ borderRadius: p.borderRadius ?? 12,
1672
+ padding
1673
+ },
1674
+ elevated && {
1675
+ shadowColor: "#000",
1676
+ shadowOffset: { width: 0, height: 2 },
1677
+ shadowOpacity: 0.1,
1678
+ shadowRadius: 8,
1679
+ elevation: 3
1742
1680
  }
1743
- };
1744
- }
1745
- return result;
1746
- };
1747
- const executeAction2 = async (actionName, params, setState, state = {}) => {
1748
- const entry = actionMap.find(([name]) => name === actionName);
1749
- if (entry) {
1750
- await entry[1](params, setState, state);
1751
- } else {
1752
- console.warn(`Unknown action: ${actionName}`);
1681
+ ],
1682
+ children: [
1683
+ p.title && /* @__PURE__ */ jsx6(
1684
+ Text2,
1685
+ {
1686
+ style: {
1687
+ fontSize: 18,
1688
+ fontWeight: "600",
1689
+ color: "#111827",
1690
+ marginBottom: p.subtitle ? 2 : 12
1691
+ },
1692
+ children: p.title
1693
+ }
1694
+ ),
1695
+ p.subtitle && /* @__PURE__ */ jsx6(
1696
+ Text2,
1697
+ {
1698
+ style: {
1699
+ fontSize: 14,
1700
+ color: "#6b7280",
1701
+ marginBottom: 12
1702
+ },
1703
+ children: p.subtitle
1704
+ }
1705
+ ),
1706
+ children
1707
+ ]
1753
1708
  }
1754
- };
1755
- return { registry, handlers, executeAction: executeAction2 };
1709
+ );
1756
1710
  }
1757
- function createRenderer(catalog, components) {
1758
- const registry = components;
1759
- return function CatalogRenderer({
1760
- spec,
1761
- state,
1762
- onAction,
1763
- onStateChange,
1764
- loading,
1765
- authState,
1766
- fallback
1767
- }) {
1768
- const actionHandlers = onAction ? {
1769
- __default__: (params) => {
1770
- const actionName = params.__actionName__;
1771
- const actionParams = params.__actionParams__;
1772
- return onAction(actionName, actionParams);
1773
- }
1774
- } : void 0;
1775
- return /* @__PURE__ */ jsx7(
1776
- StateProvider,
1711
+ function ListItemComponent({ element, emit }) {
1712
+ const p = element.props;
1713
+ const hasPress = !!element.on?.press;
1714
+ const content = /* @__PURE__ */ jsxs2(
1715
+ View2,
1716
+ {
1717
+ style: {
1718
+ flexDirection: "row",
1719
+ alignItems: "center",
1720
+ paddingVertical: 12,
1721
+ paddingHorizontal: 16
1722
+ },
1723
+ children: [
1724
+ p.leading && /* @__PURE__ */ jsx6(
1725
+ Text2,
1726
+ {
1727
+ style: {
1728
+ fontSize: 16,
1729
+ color: "#6b7280",
1730
+ marginRight: 12,
1731
+ width: 24,
1732
+ textAlign: "center"
1733
+ },
1734
+ children: p.leading
1735
+ }
1736
+ ),
1737
+ /* @__PURE__ */ jsxs2(View2, { style: { flex: 1 }, children: [
1738
+ /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#111827" }, children: p.title }),
1739
+ p.subtitle && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#6b7280", marginTop: 2 }, children: p.subtitle })
1740
+ ] }),
1741
+ p.trailing && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 14, color: "#6b7280", marginLeft: 8 }, children: p.trailing }),
1742
+ p.showChevron && /* @__PURE__ */ jsx6(Text2, { style: { fontSize: 16, color: "#9ca3af", marginLeft: 8 }, children: ">" })
1743
+ ]
1744
+ }
1745
+ );
1746
+ if (hasPress) {
1747
+ return /* @__PURE__ */ jsx6(
1748
+ Pressable2,
1777
1749
  {
1778
- initialState: state,
1779
- authState,
1780
- onStateChange,
1781
- children: /* @__PURE__ */ jsx7(VisibilityProvider, { children: /* @__PURE__ */ jsx7(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ jsxs3(ValidationProvider, { children: [
1782
- /* @__PURE__ */ jsx7(
1783
- Renderer,
1784
- {
1785
- spec,
1786
- registry,
1787
- loading,
1788
- fallback
1789
- }
1790
- ),
1791
- /* @__PURE__ */ jsx7(ConfirmationDialogManager, {})
1792
- ] }) }) })
1750
+ onPress: () => emit("press"),
1751
+ style: ({ pressed }) => ({
1752
+ opacity: pressed ? 0.7 : 1,
1753
+ backgroundColor: pressed ? "#f9fafb" : "transparent"
1754
+ }),
1755
+ children: content
1793
1756
  }
1794
1757
  );
1795
- };
1758
+ }
1759
+ return content;
1796
1760
  }
1797
-
1798
- // src/hooks.ts
1799
- import { useState as useState4, useCallback as useCallback5, useRef as useRef2, useEffect as useEffect2 } from "react";
1800
- import {
1801
- setByPath as setByPath2,
1802
- getByPath as getByPath3,
1803
- removeByPath,
1804
- validateSpec,
1805
- autoFixSpec,
1806
- formatSpecIssues
1807
- } from "@json-render/core";
1808
- function looksLikeJson(line) {
1809
- return line.startsWith("{") || line.startsWith("[");
1761
+ function ModalComponent({ element, children }) {
1762
+ const p = element.props;
1763
+ return /* @__PURE__ */ jsx6(
1764
+ RNModal,
1765
+ {
1766
+ visible: p.visible,
1767
+ animationType: p.animationType ?? "slide",
1768
+ transparent: true,
1769
+ children: /* @__PURE__ */ jsx6(View2, { style: modalStyles.overlay, children: /* @__PURE__ */ jsxs2(View2, { style: modalStyles.content, children: [
1770
+ p.title && /* @__PURE__ */ jsx6(Text2, { style: modalStyles.title, children: p.title }),
1771
+ children
1772
+ ] }) })
1773
+ }
1774
+ );
1810
1775
  }
1811
- function parsePatchLine(line) {
1812
- const trimmed = line.trim();
1813
- if (!trimmed || trimmed.startsWith("//")) {
1814
- return { patch: null, malformed: false };
1815
- }
1816
- if (!looksLikeJson(trimmed)) {
1817
- return { patch: null, malformed: false };
1818
- }
1819
- try {
1820
- return { patch: JSON.parse(trimmed), malformed: false };
1821
- } catch {
1822
- }
1823
- let attempt = trimmed;
1824
- for (let i = 0; i < 3; i++) {
1825
- const last = attempt[attempt.length - 1];
1826
- if (last === "}" || last === "]") {
1827
- attempt = attempt.slice(0, -1);
1828
- try {
1829
- const result = JSON.parse(attempt);
1830
- console.warn(
1831
- `[json-render] Recovered malformed JSONL line by removing ${i + 1} trailing '${last}'`
1832
- );
1833
- return { patch: result, malformed: false };
1834
- } catch {
1776
+ function createStandardActionHandlers(options) {
1777
+ return {
1778
+ navigate: async (params) => {
1779
+ const screen = params.screen;
1780
+ const navParams = params.params;
1781
+ options?.navigate?.(screen, navParams ?? void 0);
1782
+ },
1783
+ goBack: async () => {
1784
+ options?.goBack?.();
1785
+ },
1786
+ showAlert: async (params) => {
1787
+ const title = params.title;
1788
+ const message = params.message ?? void 0;
1789
+ const buttons = params.buttons;
1790
+ const alertButtons = buttons?.map((btn) => ({
1791
+ text: btn.text,
1792
+ style: btn.style
1793
+ }));
1794
+ Alert.alert(title, message, alertButtons);
1795
+ },
1796
+ share: async (params) => {
1797
+ const message = params.message;
1798
+ const url = params.url ?? void 0;
1799
+ const title = params.title ?? void 0;
1800
+ await Share.share({ message, url, title });
1801
+ },
1802
+ openURL: async (params) => {
1803
+ const url = params.url;
1804
+ const canOpen = await Linking.canOpenURL(url);
1805
+ if (canOpen) {
1806
+ await Linking.openURL(url);
1835
1807
  }
1836
- } else {
1837
- break;
1808
+ },
1809
+ setData: async (params) => {
1810
+ const statePath = params.statePath;
1811
+ const value = params.value;
1812
+ options?.onSetData?.(statePath, value);
1813
+ },
1814
+ refresh: async (params) => {
1815
+ const target = params.target ?? void 0;
1816
+ options?.onRefresh?.(target);
1838
1817
  }
1839
- }
1840
- return { patch: null, malformed: true };
1818
+ };
1841
1819
  }
1842
- function setSpecValue(newSpec, path, value) {
1843
- if (path === "/root") {
1844
- newSpec.root = value;
1845
- return;
1846
- }
1847
- if (path === "/state") {
1848
- newSpec.state = value;
1849
- return;
1850
- }
1851
- if (path.startsWith("/state/")) {
1852
- if (!newSpec.state) newSpec.state = {};
1853
- const statePath = path.slice("/state".length);
1854
- setByPath2(newSpec.state, statePath, value);
1855
- return;
1820
+ var standardComponents = {
1821
+ // Layout
1822
+ Container: ContainerComponent,
1823
+ Row: RowComponent,
1824
+ Column: ColumnComponent,
1825
+ ScrollContainer: ScrollContainerComponent,
1826
+ SafeArea: SafeAreaComponent,
1827
+ Pressable: PressableComponent,
1828
+ Spacer: SpacerComponent,
1829
+ Divider: DividerComponent,
1830
+ // Content
1831
+ Heading: HeadingComponent,
1832
+ Paragraph: ParagraphComponent,
1833
+ Label: LabelComponent,
1834
+ Image: ImageComponent,
1835
+ Avatar: AvatarComponent,
1836
+ Badge: BadgeComponent,
1837
+ Chip: ChipComponent,
1838
+ // Input
1839
+ Button: ButtonComponent,
1840
+ TextInput: TextInputComponent,
1841
+ Switch: SwitchComponent,
1842
+ Checkbox: CheckboxComponent,
1843
+ Slider: SliderComponent,
1844
+ SearchBar: SearchBarComponent,
1845
+ // Feedback
1846
+ Spinner: SpinnerComponent,
1847
+ ProgressBar: ProgressBarComponent,
1848
+ // Composite
1849
+ Card: CardComponent,
1850
+ ListItem: ListItemComponent,
1851
+ Modal: ModalComponent
1852
+ };
1853
+ var inputStyles = StyleSheet2.create({
1854
+ label: {
1855
+ fontSize: 14,
1856
+ fontWeight: "500",
1857
+ color: "#374151",
1858
+ marginBottom: 4
1859
+ },
1860
+ input: {
1861
+ borderWidth: 1,
1862
+ borderColor: "#d1d5db",
1863
+ borderRadius: 8,
1864
+ paddingHorizontal: 12,
1865
+ paddingVertical: 10,
1866
+ fontSize: 16,
1867
+ color: "#111827",
1868
+ backgroundColor: "#ffffff"
1856
1869
  }
1857
- if (path.startsWith("/elements/")) {
1858
- const pathParts = path.slice("/elements/".length).split("/");
1859
- const elementKey = pathParts[0];
1860
- if (!elementKey) return;
1861
- if (pathParts.length === 1) {
1862
- newSpec.elements[elementKey] = value;
1863
- } else {
1864
- const element = newSpec.elements[elementKey];
1865
- if (element) {
1866
- const propPath = "/" + pathParts.slice(1).join("/");
1867
- const newElement = { ...element };
1868
- setByPath2(
1869
- newElement,
1870
- propPath,
1871
- value
1872
- );
1873
- newSpec.elements[elementKey] = newElement;
1874
- }
1875
- }
1870
+ });
1871
+ var modalStyles = StyleSheet2.create({
1872
+ overlay: {
1873
+ flex: 1,
1874
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1875
+ justifyContent: "flex-end"
1876
+ },
1877
+ content: {
1878
+ backgroundColor: "#ffffff",
1879
+ borderTopLeftRadius: 16,
1880
+ borderTopRightRadius: 16,
1881
+ padding: 24,
1882
+ maxHeight: "80%"
1883
+ },
1884
+ title: {
1885
+ fontSize: 20,
1886
+ fontWeight: "600",
1887
+ color: "#111827",
1888
+ marginBottom: 16
1876
1889
  }
1877
- }
1878
- function removeSpecValue(newSpec, path) {
1879
- if (path === "/state") {
1880
- newSpec.state = void 0;
1881
- return;
1890
+ });
1891
+
1892
+ // src/renderer.tsx
1893
+ import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1894
+ var ElementErrorBoundary = class extends React6.Component {
1895
+ constructor(props) {
1896
+ super(props);
1897
+ this.state = { hasError: false };
1882
1898
  }
1883
- if (path.startsWith("/state/") && newSpec.state) {
1884
- const statePath = path.slice("/state".length);
1885
- removeByPath(newSpec.state, statePath);
1886
- return;
1899
+ static getDerivedStateFromError() {
1900
+ return { hasError: true };
1887
1901
  }
1888
- if (path.startsWith("/elements/")) {
1889
- const pathParts = path.slice("/elements/".length).split("/");
1890
- const elementKey = pathParts[0];
1891
- if (!elementKey) return;
1892
- if (pathParts.length === 1) {
1893
- const { [elementKey]: _, ...rest } = newSpec.elements;
1894
- newSpec.elements = rest;
1895
- } else {
1896
- const element = newSpec.elements[elementKey];
1897
- if (element) {
1898
- const propPath = "/" + pathParts.slice(1).join("/");
1899
- const newElement = { ...element };
1900
- removeByPath(
1901
- newElement,
1902
- propPath
1903
- );
1904
- newSpec.elements[elementKey] = newElement;
1905
- }
1906
- }
1902
+ componentDidCatch(error, info) {
1903
+ console.error(
1904
+ `[json-render] Rendering error in <${this.props.elementType}>:`,
1905
+ error,
1906
+ info.componentStack
1907
+ );
1907
1908
  }
1908
- }
1909
- function getSpecValue(spec, path) {
1910
- if (path === "/root") return spec.root;
1911
- return getByPath3(spec, path);
1912
- }
1913
- function applyPatch(spec, patch) {
1914
- const newSpec = { ...spec, elements: { ...spec.elements } };
1915
- switch (patch.op) {
1916
- case "add":
1917
- case "replace": {
1918
- setSpecValue(newSpec, patch.path, patch.value);
1919
- break;
1920
- }
1921
- case "remove": {
1922
- removeSpecValue(newSpec, patch.path);
1923
- break;
1924
- }
1925
- case "move": {
1926
- if (!patch.from) break;
1927
- const moveValue = getSpecValue(newSpec, patch.from);
1928
- removeSpecValue(newSpec, patch.from);
1929
- setSpecValue(newSpec, patch.path, moveValue);
1930
- break;
1931
- }
1932
- case "copy": {
1933
- if (!patch.from) break;
1934
- const copyValue = getSpecValue(newSpec, patch.from);
1935
- setSpecValue(newSpec, patch.path, copyValue);
1936
- break;
1937
- }
1938
- case "test": {
1939
- break;
1909
+ render() {
1910
+ if (this.state.hasError) {
1911
+ return null;
1940
1912
  }
1913
+ return this.props.children;
1941
1914
  }
1942
- return newSpec;
1943
- }
1944
- function useUIStream({
1945
- api,
1946
- onComplete,
1947
- onError,
1948
- fetch: fetchFn = globalThis.fetch,
1949
- validate: enableValidation = false,
1950
- maxRetries = 5
1915
+ };
1916
+ var ElementRenderer = React6.memo(function ElementRenderer2({
1917
+ element,
1918
+ spec,
1919
+ registry,
1920
+ loading,
1921
+ fallback
1951
1922
  }) {
1952
- const [spec, setSpec] = useState4(null);
1953
- const [isStreaming, setIsStreaming] = useState4(false);
1954
- const [error, setError] = useState4(null);
1955
- const [rawLines, setRawLines] = useState4([]);
1956
- const abortControllerRef = useRef2(null);
1957
- const stop = useCallback5(() => {
1958
- abortControllerRef.current?.abort();
1959
- abortControllerRef.current = null;
1960
- setIsStreaming(false);
1961
- }, []);
1962
- const clear = useCallback5(() => {
1963
- setSpec(null);
1964
- setError(null);
1965
- setRawLines([]);
1966
- }, []);
1967
- const streamRequest = useCallback5(
1968
- async (prompt, context, initialSpec, abortOnMalformed) => {
1969
- let currentSpec = initialSpec;
1970
- setSpec(currentSpec);
1971
- const malformedLines = [];
1972
- const response = await fetchFn(api, {
1973
- method: "POST",
1974
- headers: { "Content-Type": "application/json" },
1975
- body: JSON.stringify({
1976
- prompt,
1977
- context,
1978
- currentSpec
1979
- }),
1980
- signal: abortControllerRef.current.signal
1981
- });
1982
- console.log("[useUIStream] response status:", response.status);
1983
- if (!response.ok) {
1984
- let errorMessage = `HTTP error: ${response.status}`;
1985
- try {
1986
- const errorData = await response.json();
1987
- if (errorData.message) {
1988
- errorMessage = errorData.message;
1989
- } else if (errorData.error) {
1990
- errorMessage = errorData.error;
1991
- }
1992
- } catch {
1993
- }
1994
- throw new Error(errorMessage);
1995
- }
1996
- const reader = response.body?.getReader();
1997
- if (!reader) {
1998
- throw new Error("No response body");
1999
- }
2000
- const decoder = new TextDecoder();
2001
- let buffer = "";
2002
- let chunkCount = 0;
2003
- let totalBytes = 0;
2004
- let aborted = false;
2005
- while (true) {
2006
- const { done, value } = await reader.read();
2007
- if (done) {
2008
- console.log(
2009
- "[useUIStream] stream done. Total chunks:",
2010
- chunkCount,
2011
- "bytes:",
2012
- totalBytes
2013
- );
2014
- break;
2015
- }
2016
- chunkCount++;
2017
- totalBytes += value?.byteLength ?? 0;
2018
- const decoded = decoder.decode(value, { stream: true });
2019
- buffer += decoded;
2020
- if (chunkCount <= 5) {
2021
- console.log(
2022
- `[useUIStream] chunk #${chunkCount} (${value?.byteLength ?? 0}b):`,
2023
- decoded.slice(0, 200)
2024
- );
2025
- }
2026
- const lines = buffer.split("\n");
2027
- buffer = lines.pop() ?? "";
2028
- const newLines = [];
2029
- for (const line of lines) {
2030
- if (line.trim()) {
2031
- newLines.push(line);
2032
- }
2033
- const { patch, malformed } = parsePatchLine(line);
2034
- if (patch) {
2035
- console.log("[useUIStream] applied patch:", patch.op, patch.path);
2036
- currentSpec = applyPatch(currentSpec, patch);
2037
- setSpec({ ...currentSpec });
2038
- } else if (malformed) {
2039
- malformedLines.push(line.trim());
2040
- console.warn(
2041
- "[useUIStream] malformed JSON line:",
2042
- line.trim().slice(0, 200)
2043
- );
2044
- if (abortOnMalformed) {
2045
- console.warn(
2046
- "[useUIStream] aborting stream due to malformed JSON, will retry"
2047
- );
2048
- await reader.cancel();
2049
- aborted = true;
2050
- break;
2051
- }
2052
- }
2053
- }
2054
- if (aborted) break;
2055
- if (newLines.length > 0) {
2056
- setRawLines((prev) => [...prev, ...newLines]);
1923
+ const repeatScope = useRepeatScope();
1924
+ const { ctx } = useVisibility();
1925
+ const { execute } = useActions();
1926
+ const fullCtx = useMemo5(
1927
+ () => repeatScope ? {
1928
+ ...ctx,
1929
+ repeatItem: repeatScope.item,
1930
+ repeatIndex: repeatScope.index,
1931
+ repeatBasePath: repeatScope.basePath
1932
+ } : ctx,
1933
+ [ctx, repeatScope]
1934
+ );
1935
+ const isVisible = element.visible === void 0 ? true : evaluateVisibility2(element.visible, fullCtx);
1936
+ const onBindings = element.on;
1937
+ const emit = useCallback5(
1938
+ (eventName) => {
1939
+ const binding = onBindings?.[eventName];
1940
+ if (!binding) return;
1941
+ const actionBindings = Array.isArray(binding) ? binding : [binding];
1942
+ for (const b of actionBindings) {
1943
+ if (!b.params) {
1944
+ execute(b);
1945
+ continue;
2057
1946
  }
2058
- }
2059
- if (!aborted && buffer.trim()) {
2060
- console.log("[useUIStream] remaining buffer:", buffer.slice(0, 200));
2061
- setRawLines((prev) => [...prev, buffer]);
2062
- const { patch, malformed } = parsePatchLine(buffer);
2063
- if (patch) {
2064
- currentSpec = applyPatch(currentSpec, patch);
2065
- setSpec({ ...currentSpec });
2066
- } else if (malformed) {
2067
- malformedLines.push(buffer.trim());
1947
+ const resolved = {};
1948
+ for (const [key, val] of Object.entries(b.params)) {
1949
+ resolved[key] = resolveActionParam(val, fullCtx);
2068
1950
  }
1951
+ execute({ ...b, params: resolved });
2069
1952
  }
2070
- console.log(
2071
- "[useUIStream] spec state - root:",
2072
- currentSpec.root,
2073
- "elements:",
2074
- Object.keys(currentSpec.elements).length,
2075
- "completed:",
2076
- !aborted,
2077
- "malformed:",
2078
- malformedLines.length
2079
- );
2080
- return { spec: currentSpec, completed: !aborted, malformedLines };
2081
1953
  },
2082
- [api, fetchFn]
1954
+ [onBindings, execute, fullCtx]
2083
1955
  );
2084
- const send = useCallback5(
2085
- async (prompt, context) => {
2086
- abortControllerRef.current?.abort();
2087
- abortControllerRef.current = new AbortController();
2088
- setIsStreaming(true);
2089
- setError(null);
2090
- setRawLines([]);
2091
- const previousSpec = context?.previousSpec;
2092
- let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
2093
- let retriesUsed = 0;
2094
- let currentPrompt = prompt;
2095
- let currentContext = context;
2096
- try {
2097
- while (retriesUsed <= maxRetries) {
2098
- const result = await streamRequest(
2099
- currentPrompt,
2100
- currentContext,
2101
- currentSpec,
2102
- enableValidation
2103
- // only abort on malformed when validation is on
2104
- );
2105
- currentSpec = result.spec;
2106
- if (!result.completed && result.malformedLines.length > 0) {
2107
- if (retriesUsed >= maxRetries) {
1956
+ if (!isVisible) {
1957
+ return null;
1958
+ }
1959
+ const rawProps = element.props;
1960
+ const elementBindings = resolveBindings(rawProps, fullCtx);
1961
+ const resolvedProps = resolveElementProps(rawProps, fullCtx);
1962
+ const resolvedElement = resolvedProps !== element.props ? { ...element, props: resolvedProps } : element;
1963
+ const Component = registry[resolvedElement.type] ?? fallback;
1964
+ if (!Component) {
1965
+ console.warn(`No renderer for component type: ${resolvedElement.type}`);
1966
+ return null;
1967
+ }
1968
+ const children = resolvedElement.repeat ? /* @__PURE__ */ jsx7(
1969
+ RepeatChildren,
1970
+ {
1971
+ element: resolvedElement,
1972
+ spec,
1973
+ registry,
1974
+ loading,
1975
+ fallback
1976
+ }
1977
+ ) : resolvedElement.children?.map((childKey) => {
1978
+ const childElement = spec.elements[childKey];
1979
+ if (!childElement) {
1980
+ if (!loading) {
1981
+ console.warn(
1982
+ `[json-render] Missing element "${childKey}" referenced as child of "${resolvedElement.type}". This element will not render.`
1983
+ );
1984
+ }
1985
+ return null;
1986
+ }
1987
+ return /* @__PURE__ */ jsx7(
1988
+ ElementRenderer2,
1989
+ {
1990
+ element: childElement,
1991
+ spec,
1992
+ registry,
1993
+ loading,
1994
+ fallback
1995
+ },
1996
+ childKey
1997
+ );
1998
+ });
1999
+ return /* @__PURE__ */ jsx7(ElementErrorBoundary, { elementType: resolvedElement.type, children: /* @__PURE__ */ jsx7(
2000
+ Component,
2001
+ {
2002
+ element: resolvedElement,
2003
+ emit,
2004
+ bindings: elementBindings,
2005
+ loading,
2006
+ children
2007
+ }
2008
+ ) });
2009
+ });
2010
+ function RepeatChildren({
2011
+ element,
2012
+ spec,
2013
+ registry,
2014
+ loading,
2015
+ fallback
2016
+ }) {
2017
+ const { state } = useStateStore();
2018
+ const repeat = element.repeat;
2019
+ const statePath = repeat.statePath;
2020
+ const items = getByPath3(state, statePath) ?? [];
2021
+ return /* @__PURE__ */ jsx7(Fragment, { children: items.map((itemValue, index) => {
2022
+ const key = repeat.key && typeof itemValue === "object" && itemValue !== null ? String(
2023
+ itemValue[repeat.key] ?? index
2024
+ ) : String(index);
2025
+ return /* @__PURE__ */ jsx7(
2026
+ RepeatScopeProvider,
2027
+ {
2028
+ item: itemValue,
2029
+ index,
2030
+ basePath: `${statePath}/${index}`,
2031
+ children: element.children?.map((childKey) => {
2032
+ const childElement = spec.elements[childKey];
2033
+ if (!childElement) {
2034
+ if (!loading) {
2108
2035
  console.warn(
2109
- "[useUIStream] malformed lines but max retries exhausted"
2036
+ `[json-render] Missing element "${childKey}" referenced as child of "${element.type}" (repeat). This element will not render.`
2110
2037
  );
2111
- break;
2112
2038
  }
2113
- retriesUsed++;
2114
- console.warn(
2115
- `[useUIStream] mid-stream retry ${retriesUsed}/${maxRetries} due to malformed JSON`
2116
- );
2117
- currentContext = { ...context, previousSpec: currentSpec };
2118
- currentPrompt = `The previous generation contained malformed JSON that could not be parsed. The line was:
2119
- ${result.malformedLines[result.malformedLines.length - 1]?.slice(0, 500)}
2120
-
2121
- The current spec state is provided. Continue generating from where you left off. Output ONLY the remaining patches needed to complete the UI.`;
2122
- continue;
2123
- }
2124
- if (!enableValidation || !currentSpec.root) {
2125
- break;
2126
- }
2127
- const { spec: fixedSpec, fixes } = autoFixSpec(currentSpec);
2128
- if (fixes.length > 0) {
2129
- console.log("[useUIStream] auto-fixed spec issues:", fixes);
2130
- currentSpec = fixedSpec;
2131
- setSpec({ ...currentSpec });
2132
- }
2133
- const validation = validateSpec(currentSpec);
2134
- if (validation.valid) {
2135
- console.log("[useUIStream] spec validation passed");
2136
- break;
2137
- }
2138
- const errors = validation.issues.filter(
2139
- (i) => i.severity === "error"
2140
- );
2141
- if (retriesUsed >= maxRetries) {
2142
- console.warn(
2143
- "[useUIStream] spec validation failed, max retries exhausted:",
2144
- errors.map((e) => e.message)
2145
- );
2146
- break;
2039
+ return null;
2147
2040
  }
2148
- retriesUsed++;
2149
- const issueText = formatSpecIssues(validation.issues);
2150
- console.warn(
2151
- `[useUIStream] post-stream retry ${retriesUsed}/${maxRetries}:`,
2152
- errors.map((e) => e.message)
2041
+ return /* @__PURE__ */ jsx7(
2042
+ ElementRenderer,
2043
+ {
2044
+ element: childElement,
2045
+ spec,
2046
+ registry,
2047
+ loading,
2048
+ fallback
2049
+ },
2050
+ childKey
2153
2051
  );
2154
- currentContext = { ...context, previousSpec: currentSpec };
2155
- currentPrompt = `FIX THE FOLLOWING ERRORS in the current UI spec. Output ONLY the patches needed to fix these issues, do not recreate the entire UI.
2156
-
2157
- ` + issueText;
2158
- }
2159
- onComplete?.(currentSpec);
2160
- } catch (err) {
2161
- if (err.name === "AbortError") {
2162
- return;
2163
- }
2164
- const error2 = err instanceof Error ? err : new Error(String(err));
2165
- setError(error2);
2166
- onError?.(error2);
2167
- } finally {
2168
- setIsStreaming(false);
2169
- }
2170
- },
2171
- [
2172
- api,
2173
- fetchFn,
2174
- onComplete,
2175
- onError,
2176
- enableValidation,
2177
- maxRetries,
2178
- streamRequest
2179
- ]
2052
+ })
2053
+ },
2054
+ key
2055
+ );
2056
+ }) });
2057
+ }
2058
+ function Renderer({
2059
+ spec,
2060
+ registry: customRegistry,
2061
+ includeStandard = true,
2062
+ loading,
2063
+ fallback
2064
+ }) {
2065
+ const registry = useMemo5(
2066
+ () => ({
2067
+ ...includeStandard ? standardComponents : {},
2068
+ ...customRegistry
2069
+ }),
2070
+ [customRegistry, includeStandard]
2071
+ );
2072
+ if (!spec || !spec.root) {
2073
+ return null;
2074
+ }
2075
+ const rootElement = spec.elements[spec.root];
2076
+ if (!rootElement) {
2077
+ return null;
2078
+ }
2079
+ return /* @__PURE__ */ jsx7(
2080
+ ElementRenderer,
2081
+ {
2082
+ element: rootElement,
2083
+ spec,
2084
+ registry,
2085
+ loading,
2086
+ fallback
2087
+ }
2180
2088
  );
2181
- useEffect2(() => {
2182
- return () => {
2183
- abortControllerRef.current?.abort();
2184
- };
2185
- }, []);
2186
- return {
2187
- spec,
2188
- isStreaming,
2189
- error,
2190
- rawLines,
2191
- send,
2192
- stop,
2193
- clear
2194
- };
2195
2089
  }
2196
- function flatToTree(elements) {
2197
- const elementMap = {};
2198
- let root = "";
2199
- for (const element of elements) {
2200
- elementMap[element.key] = {
2201
- type: element.type,
2202
- props: element.props,
2203
- children: [],
2204
- visible: element.visible
2205
- };
2090
+ function JSONUIProvider({
2091
+ registry,
2092
+ initialState,
2093
+ handlers,
2094
+ navigate,
2095
+ validationFunctions,
2096
+ onStateChange,
2097
+ children
2098
+ }) {
2099
+ return /* @__PURE__ */ jsx7(StateProvider, { initialState, onStateChange, children: /* @__PURE__ */ jsx7(VisibilityProvider, { children: /* @__PURE__ */ jsx7(ActionProvider, { handlers, navigate, children: /* @__PURE__ */ jsxs3(ValidationProvider, { customFunctions: validationFunctions, children: [
2100
+ children,
2101
+ /* @__PURE__ */ jsx7(ConfirmationDialogManager, {})
2102
+ ] }) }) }) });
2103
+ }
2104
+ function ConfirmationDialogManager() {
2105
+ const { pendingConfirmation, confirm, cancel } = useActions();
2106
+ if (!pendingConfirmation?.action.confirm) {
2107
+ return null;
2206
2108
  }
2207
- for (const element of elements) {
2208
- if (element.parentKey) {
2209
- const parent = elementMap[element.parentKey];
2210
- if (parent) {
2211
- if (!parent.children) {
2212
- parent.children = [];
2109
+ return /* @__PURE__ */ jsx7(
2110
+ ConfirmDialog,
2111
+ {
2112
+ confirm: pendingConfirmation.action.confirm,
2113
+ onConfirm: confirm,
2114
+ onCancel: cancel
2115
+ }
2116
+ );
2117
+ }
2118
+ function defineRegistry(_catalog, options) {
2119
+ const registry = {};
2120
+ if (options.components) {
2121
+ for (const [name, componentFn] of Object.entries(options.components)) {
2122
+ registry[name] = ({
2123
+ element,
2124
+ children,
2125
+ emit,
2126
+ bindings,
2127
+ loading
2128
+ }) => {
2129
+ return componentFn({
2130
+ props: element.props,
2131
+ children,
2132
+ emit,
2133
+ bindings,
2134
+ loading
2135
+ });
2136
+ };
2137
+ }
2138
+ }
2139
+ const actionMap = options.actions ? Object.entries(options.actions) : [];
2140
+ const handlers = (getSetState, getState) => {
2141
+ const result = {};
2142
+ for (const [name, actionFn] of actionMap) {
2143
+ result[name] = async (params) => {
2144
+ const setState = getSetState();
2145
+ const state = getState();
2146
+ if (setState) {
2147
+ await actionFn(params, setState, state);
2213
2148
  }
2214
- parent.children.push(element.key);
2215
- }
2149
+ };
2150
+ }
2151
+ return result;
2152
+ };
2153
+ const executeAction2 = async (actionName, params, setState, state = {}) => {
2154
+ const entry = actionMap.find(([name]) => name === actionName);
2155
+ if (entry) {
2156
+ await entry[1](params, setState, state);
2216
2157
  } else {
2217
- root = element.key;
2158
+ console.warn(`Unknown action: ${actionName}`);
2218
2159
  }
2219
- }
2220
- return { root, elements: elementMap };
2160
+ };
2161
+ return { registry, handlers, executeAction: executeAction2 };
2162
+ }
2163
+ function createRenderer(catalog, components) {
2164
+ const registry = components;
2165
+ return function CatalogRenderer({
2166
+ spec,
2167
+ state,
2168
+ onAction,
2169
+ onStateChange,
2170
+ loading,
2171
+ fallback
2172
+ }) {
2173
+ const actionHandlers = onAction ? new Proxy(
2174
+ {},
2175
+ {
2176
+ get: (_target, prop) => {
2177
+ return (params) => onAction(prop, params);
2178
+ },
2179
+ has: () => true
2180
+ }
2181
+ ) : void 0;
2182
+ return /* @__PURE__ */ jsx7(StateProvider, { initialState: state, onStateChange, children: /* @__PURE__ */ jsx7(VisibilityProvider, { children: /* @__PURE__ */ jsx7(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ jsxs3(ValidationProvider, { children: [
2183
+ /* @__PURE__ */ jsx7(
2184
+ Renderer,
2185
+ {
2186
+ spec,
2187
+ registry,
2188
+ loading,
2189
+ fallback
2190
+ }
2191
+ ),
2192
+ /* @__PURE__ */ jsx7(ConfirmationDialogManager, {})
2193
+ ] }) }) }) });
2194
+ };
2221
2195
  }
2222
2196
  export {
2223
2197
  ActionProvider,
@@ -2229,18 +2203,17 @@ export {
2229
2203
  ValidationProvider,
2230
2204
  VisibilityProvider,
2231
2205
  createRenderer,
2232
- createRendererFromCatalog,
2233
2206
  createStandardActionHandlers,
2234
2207
  defineRegistry,
2235
2208
  elementTreeSchema,
2236
2209
  flatToTree,
2237
- rewriteRepeatTokens,
2238
2210
  schema,
2239
2211
  standardActionDefinitions,
2240
2212
  standardComponentDefinitions,
2241
2213
  standardComponents,
2242
2214
  useAction,
2243
2215
  useActions,
2216
+ useBoundProp,
2244
2217
  useFieldValidation,
2245
2218
  useIsVisible,
2246
2219
  useRepeatScope,