@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.js CHANGED
@@ -39,18 +39,17 @@ __export(index_exports, {
39
39
  ValidationProvider: () => ValidationProvider,
40
40
  VisibilityProvider: () => VisibilityProvider,
41
41
  createRenderer: () => createRenderer,
42
- createRendererFromCatalog: () => createRendererFromCatalog,
43
42
  createStandardActionHandlers: () => createStandardActionHandlers,
44
43
  defineRegistry: () => defineRegistry,
45
44
  elementTreeSchema: () => elementTreeSchema,
46
45
  flatToTree: () => flatToTree,
47
- rewriteRepeatTokens: () => rewriteRepeatTokens,
48
46
  schema: () => schema,
49
47
  standardActionDefinitions: () => standardActionDefinitions,
50
48
  standardComponentDefinitions: () => standardComponentDefinitions,
51
49
  standardComponents: () => standardComponents,
52
50
  useAction: () => useAction,
53
51
  useActions: () => useActions,
52
+ useBoundProp: () => useBoundProp,
54
53
  useFieldValidation: () => useFieldValidation,
55
54
  useIsVisible: () => useIsVisible,
56
55
  useRepeatScope: () => useRepeatScope,
@@ -70,11 +69,12 @@ var import_jsx_runtime = require("react/jsx-runtime");
70
69
  var StateContext = (0, import_react.createContext)(null);
71
70
  function StateProvider({
72
71
  initialState = {},
73
- authState,
74
72
  onStateChange,
75
73
  children
76
74
  }) {
77
75
  const [state, setStateInternal] = (0, import_react.useState)(initialState);
76
+ const stateRef = (0, import_react.useRef)(state);
77
+ stateRef.current = state;
78
78
  const initialStateJsonRef = (0, import_react.useRef)(JSON.stringify(initialState));
79
79
  (0, import_react.useEffect)(() => {
80
80
  const newJson = JSON.stringify(initialState);
@@ -85,7 +85,10 @@ function StateProvider({
85
85
  }
86
86
  }
87
87
  }, [initialState]);
88
- const get = (0, import_react.useCallback)((path) => (0, import_core.getByPath)(state, path), [state]);
88
+ const get = (0, import_react.useCallback)(
89
+ (path) => (0, import_core.getByPath)(stateRef.current, path),
90
+ []
91
+ );
89
92
  const set = (0, import_react.useCallback)(
90
93
  (path, value2) => {
91
94
  setStateInternal((prev) => {
@@ -99,26 +102,28 @@ function StateProvider({
99
102
  );
100
103
  const update = (0, import_react.useCallback)(
101
104
  (updates) => {
105
+ const entries = Object.entries(updates);
102
106
  setStateInternal((prev) => {
103
107
  const next = { ...prev };
104
- for (const [path, value2] of Object.entries(updates)) {
108
+ for (const [path, value2] of entries) {
105
109
  (0, import_core.setByPath)(next, path, value2);
106
- onStateChange?.(path, value2);
107
110
  }
108
111
  return next;
109
112
  });
113
+ for (const [path, value2] of entries) {
114
+ onStateChange?.(path, value2);
115
+ }
110
116
  },
111
117
  [onStateChange]
112
118
  );
113
119
  const value = (0, import_react.useMemo)(
114
120
  () => ({
115
121
  state,
116
- authState,
117
122
  get,
118
123
  set,
119
124
  update
120
125
  }),
121
- [state, authState, get, set, update]
126
+ [state, get, set, update]
122
127
  );
123
128
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StateContext.Provider, { value, children });
124
129
  }
@@ -149,13 +154,12 @@ var import_core2 = require("@json-render/core");
149
154
  var import_jsx_runtime2 = require("react/jsx-runtime");
150
155
  var VisibilityContext = (0, import_react2.createContext)(null);
151
156
  function VisibilityProvider({ children }) {
152
- const { state, authState } = useStateStore();
157
+ const { state } = useStateStore();
153
158
  const ctx = (0, import_react2.useMemo)(
154
159
  () => ({
155
- stateModel: state,
156
- authState
160
+ stateModel: state
157
161
  }),
158
- [state, authState]
162
+ [state]
159
163
  );
160
164
  const isVisible = (0, import_react2.useMemo)(
161
165
  () => (condition) => (0, import_core2.evaluateVisibility)(condition, ctx),
@@ -197,8 +201,8 @@ function deepResolveValue(value, get) {
197
201
  if (typeof value === "object" && !Array.isArray(value)) {
198
202
  const obj = value;
199
203
  const keys = Object.keys(obj);
200
- if (keys.length === 1 && typeof obj.path === "string") {
201
- return get(obj.path);
204
+ if (keys.length === 1 && typeof obj.$state === "string") {
205
+ return get(obj.$state);
202
206
  }
203
207
  if (keys.length === 1 && "$id" in obj) {
204
208
  return generateUniqueId();
@@ -236,34 +240,34 @@ function ActionProvider({
236
240
  async (binding) => {
237
241
  const resolved = (0, import_core3.resolveAction)(binding, state);
238
242
  if (resolved.action === "setState" && resolved.params) {
239
- const path = resolved.params.path;
243
+ const statePath = resolved.params.statePath;
240
244
  const value2 = resolved.params.value;
241
- if (path) {
242
- set(path, value2);
245
+ if (statePath) {
246
+ set(statePath, value2);
243
247
  }
244
248
  return;
245
249
  }
246
250
  if (resolved.action === "pushState" && resolved.params) {
247
- const path = resolved.params.path;
251
+ const statePath = resolved.params.statePath;
248
252
  const rawValue = resolved.params.value;
249
- if (path) {
253
+ if (statePath) {
250
254
  const resolvedValue = deepResolveValue(rawValue, get);
251
- const arr = get(path) ?? [];
252
- set(path, [...arr, resolvedValue]);
253
- const clearPath = resolved.params.clearPath;
254
- if (clearPath) {
255
- set(clearPath, "");
255
+ const arr = get(statePath) ?? [];
256
+ set(statePath, [...arr, resolvedValue]);
257
+ const clearStatePath = resolved.params.clearStatePath;
258
+ if (clearStatePath) {
259
+ set(clearStatePath, "");
256
260
  }
257
261
  }
258
262
  return;
259
263
  }
260
264
  if (resolved.action === "removeState" && resolved.params) {
261
- const path = resolved.params.path;
265
+ const statePath = resolved.params.statePath;
262
266
  const index = resolved.params.index;
263
- if (path !== void 0 && index !== void 0) {
264
- const arr = get(path) ?? [];
267
+ if (statePath !== void 0 && index !== void 0) {
268
+ const arr = get(statePath) ?? [];
265
269
  set(
266
- path,
270
+ statePath,
267
271
  arr.filter((_, i) => i !== index)
268
272
  );
269
273
  }
@@ -492,27 +496,75 @@ var import_react4 = __toESM(require("react"));
492
496
  var import_core4 = require("@json-render/core");
493
497
  var import_jsx_runtime4 = require("react/jsx-runtime");
494
498
  var ValidationContext = (0, import_react4.createContext)(null);
499
+ function dynamicArgsEqual(a, b) {
500
+ if (a === b) return true;
501
+ if (!a || !b) return false;
502
+ const keysA = Object.keys(a);
503
+ const keysB = Object.keys(b);
504
+ if (keysA.length !== keysB.length) return false;
505
+ for (const key of keysA) {
506
+ const va = a[key];
507
+ const vb = b[key];
508
+ if (va === vb) continue;
509
+ if (typeof va === "object" && va !== null && typeof vb === "object" && vb !== null) {
510
+ const sa = va.$state;
511
+ const sb = vb.$state;
512
+ if (typeof sa === "string" && sa === sb) continue;
513
+ }
514
+ return false;
515
+ }
516
+ return true;
517
+ }
518
+ function validationConfigEqual(a, b) {
519
+ if (a === b) return true;
520
+ if (a.validateOn !== b.validateOn) return false;
521
+ const ac = a.checks ?? [];
522
+ const bc = b.checks ?? [];
523
+ if (ac.length !== bc.length) return false;
524
+ for (let i = 0; i < ac.length; i++) {
525
+ const ca = ac[i];
526
+ const cb = bc[i];
527
+ if (ca.type !== cb.type) return false;
528
+ if (ca.message !== cb.message) return false;
529
+ if (!dynamicArgsEqual(ca.args, cb.args)) return false;
530
+ }
531
+ return true;
532
+ }
495
533
  function ValidationProvider({
496
534
  customFunctions = {},
497
535
  children
498
536
  }) {
499
- const { state, authState } = useStateStore();
537
+ const { state } = useStateStore();
500
538
  const [fieldStates, setFieldStates] = (0, import_react4.useState)({});
501
539
  const [fieldConfigs, setFieldConfigs] = (0, import_react4.useState)({});
502
540
  const registerField = (0, import_react4.useCallback)(
503
541
  (path, config) => {
504
- setFieldConfigs((prev) => ({ ...prev, [path]: config }));
542
+ setFieldConfigs((prev) => {
543
+ const existing = prev[path];
544
+ if (existing && validationConfigEqual(existing, config)) {
545
+ return prev;
546
+ }
547
+ return { ...prev, [path]: config };
548
+ });
505
549
  },
506
550
  []
507
551
  );
508
552
  const validate = (0, import_react4.useCallback)(
509
553
  (path, config) => {
510
- const value2 = state[path.split("/").filter(Boolean).join(".")];
554
+ const segments = path.split("/").filter(Boolean);
555
+ let value2 = state;
556
+ for (const seg of segments) {
557
+ if (value2 != null && typeof value2 === "object") {
558
+ value2 = value2[seg];
559
+ } else {
560
+ value2 = void 0;
561
+ break;
562
+ }
563
+ }
511
564
  const result = (0, import_core4.runValidation)(config, {
512
565
  value: value2,
513
566
  stateModel: state,
514
- customFunctions,
515
- authState
567
+ customFunctions
516
568
  });
517
569
  setFieldStates((prev) => ({
518
570
  ...prev,
@@ -524,7 +576,7 @@ function ValidationProvider({
524
576
  }));
525
577
  return result;
526
578
  },
527
- [state, customFunctions, authState]
579
+ [state, customFunctions]
528
580
  );
529
581
  const touch = (0, import_react4.useCallback)((path) => {
530
582
  setFieldStates((prev) => ({
@@ -621,44 +673,16 @@ var import_react5 = require("react");
621
673
  var import_jsx_runtime5 = require("react/jsx-runtime");
622
674
  var RepeatScopeContext = (0, import_react5.createContext)(null);
623
675
  function RepeatScopeProvider({
624
- basePath,
676
+ item,
625
677
  index,
678
+ basePath,
626
679
  children
627
680
  }) {
628
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RepeatScopeContext.Provider, { value: { basePath, index }, children });
681
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RepeatScopeContext.Provider, { value: { item, index, basePath }, children });
629
682
  }
630
683
  function useRepeatScope() {
631
684
  return (0, import_react5.useContext)(RepeatScopeContext);
632
685
  }
633
- function rewriteRepeatTokens(value, basePath, index) {
634
- if (typeof value === "string") {
635
- if (value === "$index") return index;
636
- if (value === "$item") return basePath;
637
- if (value.startsWith("$item/"))
638
- return basePath + value.slice("$item".length);
639
- return value;
640
- }
641
- if (Array.isArray(value)) {
642
- let changed = false;
643
- const result = value.map((item) => {
644
- const rewritten = rewriteRepeatTokens(item, basePath, index);
645
- if (rewritten !== item) changed = true;
646
- return rewritten;
647
- });
648
- return changed ? result : value;
649
- }
650
- if (typeof value === "object" && value !== null) {
651
- let changed = false;
652
- const result = {};
653
- for (const [key, val] of Object.entries(value)) {
654
- const rewritten = rewriteRepeatTokens(val, basePath, index);
655
- if (rewritten !== val) changed = true;
656
- result[key] = rewritten;
657
- }
658
- return changed ? result : value;
659
- }
660
- return value;
661
- }
662
686
 
663
687
  // src/schema.ts
664
688
  var import_core5 = require("@json-render/core");
@@ -691,7 +715,9 @@ var schema = (0, import_core5.defineSchema)(
691
715
  /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
692
716
  slots: s.array(s.string()),
693
717
  /** Description for AI generation hints */
694
- description: s.string()
718
+ description: s.string(),
719
+ /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
720
+ example: s.any()
695
721
  }),
696
722
  /** Action definitions (optional) */
697
723
  actions: s.map({
@@ -705,181 +731,559 @@ var schema = (0, import_core5.defineSchema)(
705
731
  {
706
732
  defaultRules: [
707
733
  // Layout patterns
708
- "FIXED BOTTOM BAR PATTERN: When building a screen with a fixed header and/or fixed bottom tab bar, the outermost Column must have flex:1 so it fills the SafeArea. The scrollable content area must also have flex:1. Structure: SafeArea > Column(flex:1, gap:0) > [header, Container(flex:1) > [ScrollContainer(...)], bottom-tabs]. Both the outer Column AND the content Container need flex:1.",
709
- "NEVER place a bottom tab bar or fixed footer inside a ScrollContainer. It must be a sibling AFTER the flex:1 container that holds the ScrollContainer.",
734
+ "FIXED BOTTOM BAR PATTERN: When building a screen with a fixed header and/or fixed bottom tab bar, the outermost vertical layout component must have flex:1 so it fills the screen. The scrollable content area must also have flex:1. Structure: screen wrapper > vertical layout(flex:1, gap:0) > [header, content wrapper(flex:1) > [scroll container(...)], bottom-tabs]. Both the outer layout AND the content wrapper need flex:1. ONLY use components from the AVAILABLE COMPONENTS list.",
735
+ "NEVER place a bottom tab bar or fixed footer inside a scroll container. It must be a sibling AFTER the flex:1 container that holds the scroll content.",
710
736
  // Element integrity
711
737
  "CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.",
712
738
  "SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.",
713
- 'When building repeating content backed by a state array (e.g. todos, posts, cart items), use the "repeat" field on the container element. Example: { "type": "Column", "props": { "gap": 8 }, "repeat": { "path": "/todos", "key": "id" }, "children": ["todo-item"] }. Inside repeated children, use "$item/field" for per-item state paths and "$index" for the current array index. Do NOT hardcode individual elements for each array item.',
739
+ 'When building repeating content backed by a state array (e.g. todos, posts, cart items), use the "repeat" field on a container element from the AVAILABLE COMPONENTS list. Example: { "type": "<ContainerComponent>", "props": { "gap": 8 }, "repeat": { "statePath": "/todos", "key": "id" }, "children": ["todo-item"] }. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
714
740
  // Visible field placement
715
- 'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"Column","props":{"gap":8},"visible":{"eq":[{"path":"/activeTab"},"home"]},"children":[...]}. WRONG: {"type":"Column","props":{"gap":8,"visible":{...}},"children":[...]}.',
741
+ 'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/activeTab","eq":"home"},"children":[...]}. WRONG: {"type":"<ComponentName>","props":{},"visible":{...},"children":[...]} with visible inside props.',
716
742
  // Tab navigation pattern
717
- "TAB NAVIGATION PATTERN: When building a UI with multiple tabs, use Pressable + setState action + visible conditions to make tabs functional.",
718
- 'Each tab button should be a Pressable wrapping its icon/label children, with action "setState" and actionParams { "path": "/activeTab", "value": "tabName" }.',
719
- `Each tab's content section should have a visible condition: { "eq": [{ "path": "/activeTab" }, "tabName"] }.`,
743
+ "TAB NAVIGATION PATTERN: When building a UI with multiple tabs, use a pressable/tappable component + setState action + visible conditions to make tabs functional. ONLY use components from the AVAILABLE COMPONENTS list.",
744
+ 'Each tab button should be a pressable component wrapping its icon/label children, with action "setState" and actionParams { "statePath": "/activeTab", "value": "tabName" }.',
745
+ `Each tab's content section should have a visible condition: { "$state": "/activeTab", "eq": "tabName" }.`,
720
746
  "The first tab's content should NOT have a visible condition (so it shows by default when no tab is selected yet). All other tabs MUST have a visible condition.",
721
747
  // Tab active state highlighting (using dynamic props)
722
- "TAB ACTIVE STYLING: Use $cond dynamic props on the Icon inside each tab Pressable so a single Icon changes appearance based on the active tab.",
723
- ' - For the icon name: { "$cond": { "eq": [{ "path": "/activeTab" }, "thisTabName"] }, "$then": "home", "$else": "home-outline" }',
724
- ' - For the icon color: { "$cond": { "eq": [{ "path": "/activeTab" }, "thisTabName"] }, "$then": "#007AFF", "$else": "#8E8E93" }',
748
+ "TAB ACTIVE STYLING: Use $cond dynamic props on icon elements inside each tab button so a single icon changes appearance based on the active tab.",
749
+ ' - For the icon name: { "$cond": { "$state": "/activeTab", "eq": "thisTabName" }, "$then": "home", "$else": "home-outline" }',
750
+ ' - For the icon color: { "$cond": { "$state": "/activeTab", "eq": "thisTabName" }, "$then": "#007AFF", "$else": "#8E8E93" }',
725
751
  " - For labels, use $cond on the color prop similarly.",
726
- ' - For the FIRST/DEFAULT tab, use { "$cond": { "or": [{ "eq": [{ "path": "/activeTab" }, "thisTabName"] }, { "not": { "path": "/activeTab" } }] }, "$then": "#007AFF", "$else": "#8E8E93" } so it appears active before any tab is tapped.',
752
+ ' - For the FIRST/DEFAULT tab, use { "$cond": [{ "$state": "/activeTab", "eq": "thisTabName" }], "$then": "#007AFF", "$else": "#8E8E93" } so it appears active before any tab is tapped. When no tab is selected yet, include a default tab with no visible condition.',
727
753
  // Push/pop screen navigation (all screens in one spec)
728
- 'SCREEN NAVIGATION: Use Pressable with action "push" and actionParams { "screen": "screenName" } to navigate to a new screen. Use action "pop" to go back. All screens must be defined in the SAME spec.',
729
- 'Each screen section uses a visible condition on /currentScreen: { "eq": [{ "path": "/currentScreen" }, "screenName"] }. The default/home screen should also be visible when /currentScreen is not set: { "or": [{ "eq": [{ "path": "/currentScreen" }, "home"] }, { "not": { "path": "/currentScreen" } }] }.',
754
+ 'SCREEN NAVIGATION: Use a pressable component with action "push" and actionParams { "screen": "screenName" } to navigate to a new screen. Use action "pop" to go back. All screens must be defined in the SAME spec. ONLY use components from the AVAILABLE COMPONENTS list.',
755
+ 'Each screen section uses a visible condition on /currentScreen: { "$state": "/currentScreen", "eq": "screenName" }. The default/home screen should show by default (no visible condition) and all other screens should have the appropriate visible condition.',
730
756
  "push automatically maintains a /navStack in the state model so pop always returns to the previous screen.",
731
- 'Include a back button on pushed screens using action "pop". Example: Pressable(action:"pop") > Row > Icon(name:"chevron-back") + Label(text:"Back").',
757
+ 'Include a back button on pushed screens using action "pop". Example: pressable(action:"pop") > row layout > back icon + back label. ONLY use components from the AVAILABLE COMPONENTS list.',
732
758
  "Use push/pop for drill-down flows: tapping a list item to see details, opening a profile, etc. Use setState + visible conditions for tab switching within a screen.",
733
- 'Example: A list screen with items that push to detail: Pressable(action:"push", actionParams:{screen:"repo-detail"}) wrapping each list item card. The detail screen section has visible:{"eq":[{"path":"/currentScreen"},"repo-detail"]} and contains a back button with action:"pop".'
759
+ 'Example: A list screen with items that push to detail: a pressable component with action:"push" and actionParams:{screen:"detail"} wrapping each list item. The detail screen section has visible:{"$state":"/currentScreen","eq":"detail"} and contains a back button with action:"pop". ONLY use components from the AVAILABLE COMPONENTS list.'
734
760
  ]
735
761
  }
736
762
  );
737
763
  var elementTreeSchema = schema;
738
764
 
739
765
  // src/renderer.tsx
740
- var import_react6 = __toESM(require("react"));
741
- var import_core6 = require("@json-render/core");
766
+ var import_react7 = __toESM(require("react"));
767
+ var import_core7 = require("@json-render/core");
742
768
 
743
769
  // src/components/standard.tsx
744
770
  var import_react_native2 = require("react-native");
745
- var import_jsx_runtime6 = require("react/jsx-runtime");
746
- function ContainerComponent({ element, children }) {
747
- const p = element.props;
748
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
749
- import_react_native2.View,
750
- {
751
- style: {
752
- padding: p.padding ?? void 0,
753
- paddingHorizontal: p.paddingHorizontal ?? void 0,
754
- paddingVertical: p.paddingVertical ?? void 0,
755
- margin: p.margin ?? void 0,
756
- backgroundColor: p.backgroundColor ?? void 0,
757
- borderRadius: p.borderRadius ?? void 0,
758
- flex: p.flex ?? void 0
759
- },
760
- children
761
- }
762
- );
763
- }
764
- function RowComponent({ element, children }) {
765
- const p = element.props;
766
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
767
- import_react_native2.View,
768
- {
769
- style: {
770
- flexDirection: "row",
771
- gap: p.gap ?? void 0,
772
- alignItems: p.alignItems ?? void 0,
773
- justifyContent: p.justifyContent ?? void 0,
774
- flexWrap: p.flexWrap ?? void 0,
775
- padding: p.padding ?? void 0,
776
- flex: p.flex ?? void 0
777
- },
778
- children
779
- }
771
+
772
+ // src/hooks.ts
773
+ var import_react6 = require("react");
774
+ var import_core6 = require("@json-render/core");
775
+ function useBoundProp(propValue, bindingPath) {
776
+ const { set } = useStateStore();
777
+ const setValue = (0, import_react6.useCallback)(
778
+ (value) => {
779
+ if (bindingPath) set(bindingPath, value);
780
+ },
781
+ [bindingPath, set]
780
782
  );
783
+ return [propValue, setValue];
781
784
  }
782
- function ColumnComponent({ element, children }) {
783
- const p = element.props;
784
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
785
- import_react_native2.View,
786
- {
787
- style: {
788
- flexDirection: "column",
789
- gap: p.gap ?? void 0,
790
- alignItems: p.alignItems ?? void 0,
791
- justifyContent: p.justifyContent ?? void 0,
792
- padding: p.padding ?? void 0,
793
- flex: p.flex ?? void 0
794
- },
795
- children
796
- }
797
- );
785
+ function looksLikeJson(line) {
786
+ return line.startsWith("{") || line.startsWith("[");
798
787
  }
799
- function ScrollContainerComponent({ element, children }) {
800
- const p = element.props;
801
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
802
- import_react_native2.ScrollView,
803
- {
804
- horizontal: p.horizontal ?? false,
805
- showsVerticalScrollIndicator: p.showsScrollIndicator ?? true,
806
- showsHorizontalScrollIndicator: p.showsScrollIndicator ?? true,
807
- contentContainerStyle: {
808
- padding: p.padding ?? void 0
809
- },
810
- style: {
811
- flex: p.flex ?? 1,
812
- backgroundColor: p.backgroundColor ?? void 0
813
- },
814
- children
788
+ function parsePatchLine(line) {
789
+ const trimmed = line.trim();
790
+ if (!trimmed || trimmed.startsWith("//")) {
791
+ return { patch: null, malformed: false };
792
+ }
793
+ if (!looksLikeJson(trimmed)) {
794
+ return { patch: null, malformed: false };
795
+ }
796
+ try {
797
+ return { patch: JSON.parse(trimmed), malformed: false };
798
+ } catch {
799
+ }
800
+ let attempt = trimmed;
801
+ for (let i = 0; i < 3; i++) {
802
+ const last = attempt[attempt.length - 1];
803
+ if (last === "}" || last === "]") {
804
+ attempt = attempt.slice(0, -1);
805
+ try {
806
+ const result = JSON.parse(attempt);
807
+ console.warn(
808
+ `[json-render] Recovered malformed JSONL line by removing ${i + 1} trailing '${last}'`
809
+ );
810
+ return { patch: result, malformed: false };
811
+ } catch {
812
+ }
813
+ } else {
814
+ break;
815
815
  }
816
- );
816
+ }
817
+ return { patch: null, malformed: true };
817
818
  }
818
- function SafeAreaComponent({ element, children }) {
819
- const p = element.props;
820
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
821
- import_react_native2.SafeAreaView,
822
- {
823
- style: {
824
- flex: 1,
825
- backgroundColor: p.backgroundColor ?? void 0
826
- },
827
- children
819
+ function setSpecValue(newSpec, path, value) {
820
+ if (path === "/root") {
821
+ newSpec.root = value;
822
+ return;
823
+ }
824
+ if (path === "/state") {
825
+ newSpec.state = value;
826
+ return;
827
+ }
828
+ if (path.startsWith("/state/")) {
829
+ if (!newSpec.state) newSpec.state = {};
830
+ const statePath = path.slice("/state".length);
831
+ (0, import_core6.setByPath)(newSpec.state, statePath, value);
832
+ return;
833
+ }
834
+ if (path.startsWith("/elements/")) {
835
+ const pathParts = path.slice("/elements/".length).split("/");
836
+ const elementKey = pathParts[0];
837
+ if (!elementKey) return;
838
+ if (pathParts.length === 1) {
839
+ newSpec.elements[elementKey] = value;
840
+ } else {
841
+ const element = newSpec.elements[elementKey];
842
+ if (element) {
843
+ const propPath = "/" + pathParts.slice(1).join("/");
844
+ const newElement = { ...element };
845
+ (0, import_core6.setByPath)(
846
+ newElement,
847
+ propPath,
848
+ value
849
+ );
850
+ newSpec.elements[elementKey] = newElement;
851
+ }
828
852
  }
829
- );
853
+ }
830
854
  }
831
- function SpacerComponent({ element }) {
832
- const p = element.props;
833
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
834
- import_react_native2.View,
835
- {
836
- style: {
837
- width: p.size ?? void 0,
838
- height: p.size ?? void 0,
839
- flex: p.flex ?? void 0
855
+ function removeSpecValue(newSpec, path) {
856
+ if (path === "/state") {
857
+ newSpec.state = void 0;
858
+ return;
859
+ }
860
+ if (path.startsWith("/state/") && newSpec.state) {
861
+ const statePath = path.slice("/state".length);
862
+ (0, import_core6.removeByPath)(newSpec.state, statePath);
863
+ return;
864
+ }
865
+ if (path.startsWith("/elements/")) {
866
+ const pathParts = path.slice("/elements/".length).split("/");
867
+ const elementKey = pathParts[0];
868
+ if (!elementKey) return;
869
+ if (pathParts.length === 1) {
870
+ const { [elementKey]: _, ...rest } = newSpec.elements;
871
+ newSpec.elements = rest;
872
+ } else {
873
+ const element = newSpec.elements[elementKey];
874
+ if (element) {
875
+ const propPath = "/" + pathParts.slice(1).join("/");
876
+ const newElement = { ...element };
877
+ (0, import_core6.removeByPath)(
878
+ newElement,
879
+ propPath
880
+ );
881
+ newSpec.elements[elementKey] = newElement;
840
882
  }
841
883
  }
842
- );
884
+ }
843
885
  }
844
- function PressableComponent({ children, emit }) {
845
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
846
- import_react_native2.Pressable,
847
- {
848
- onPress: () => emit?.("press"),
849
- onLongPress: () => emit?.("longPress"),
850
- style: ({ pressed }) => ({
851
- opacity: pressed ? 0.7 : 1
852
- }),
853
- children
854
- }
855
- );
886
+ function getSpecValue(spec, path) {
887
+ if (path === "/root") return spec.root;
888
+ return (0, import_core6.getByPath)(spec, path);
856
889
  }
857
- function DividerComponent({ element }) {
858
- const p = element.props;
859
- const isVertical = p.direction === "vertical";
860
- const thickness = p.thickness ?? 1;
861
- const color = p.color ?? "#e5e7eb";
862
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
863
- import_react_native2.View,
864
- {
865
- style: {
866
- width: isVertical ? thickness : "100%",
867
- height: isVertical ? "100%" : thickness,
868
- backgroundColor: color,
869
- marginVertical: isVertical ? 0 : p.margin ?? 8,
870
- marginHorizontal: isVertical ? p.margin ?? 8 : 0
871
- }
890
+ function applyPatch(spec, patch) {
891
+ const newSpec = { ...spec, elements: { ...spec.elements } };
892
+ switch (patch.op) {
893
+ case "add":
894
+ case "replace": {
895
+ setSpecValue(newSpec, patch.path, patch.value);
896
+ break;
872
897
  }
873
- );
874
- }
875
- var headingSizes = {
876
- h1: 32,
877
- h2: 24,
878
- h3: 20,
879
- h4: 16
880
- };
881
- function HeadingComponent({ element }) {
882
- const p = element.props;
898
+ case "remove": {
899
+ removeSpecValue(newSpec, patch.path);
900
+ break;
901
+ }
902
+ case "move": {
903
+ if (!patch.from) break;
904
+ const moveValue = getSpecValue(newSpec, patch.from);
905
+ removeSpecValue(newSpec, patch.from);
906
+ setSpecValue(newSpec, patch.path, moveValue);
907
+ break;
908
+ }
909
+ case "copy": {
910
+ if (!patch.from) break;
911
+ const copyValue = getSpecValue(newSpec, patch.from);
912
+ setSpecValue(newSpec, patch.path, copyValue);
913
+ break;
914
+ }
915
+ case "test": {
916
+ break;
917
+ }
918
+ }
919
+ return newSpec;
920
+ }
921
+ function useUIStream({
922
+ api,
923
+ onComplete,
924
+ onError,
925
+ fetch: fetchFn = globalThis.fetch,
926
+ validate: enableValidation = false,
927
+ maxRetries = 5
928
+ }) {
929
+ const [spec, setSpec] = (0, import_react6.useState)(null);
930
+ const [isStreaming, setIsStreaming] = (0, import_react6.useState)(false);
931
+ const [error, setError] = (0, import_react6.useState)(null);
932
+ const [rawLines, setRawLines] = (0, import_react6.useState)([]);
933
+ const abortControllerRef = (0, import_react6.useRef)(null);
934
+ const stop = (0, import_react6.useCallback)(() => {
935
+ abortControllerRef.current?.abort();
936
+ abortControllerRef.current = null;
937
+ setIsStreaming(false);
938
+ }, []);
939
+ const clear = (0, import_react6.useCallback)(() => {
940
+ setSpec(null);
941
+ setError(null);
942
+ setRawLines([]);
943
+ }, []);
944
+ const streamRequest = (0, import_react6.useCallback)(
945
+ async (prompt, context, initialSpec, abortOnMalformed) => {
946
+ let currentSpec = initialSpec;
947
+ setSpec(currentSpec);
948
+ const malformedLines = [];
949
+ const response = await fetchFn(api, {
950
+ method: "POST",
951
+ headers: { "Content-Type": "application/json" },
952
+ body: JSON.stringify({
953
+ prompt,
954
+ context,
955
+ currentSpec
956
+ }),
957
+ signal: abortControllerRef.current.signal
958
+ });
959
+ if (!response.ok) {
960
+ let errorMessage = `HTTP error: ${response.status}`;
961
+ try {
962
+ const errorData = await response.json();
963
+ if (errorData.message) {
964
+ errorMessage = errorData.message;
965
+ } else if (errorData.error) {
966
+ errorMessage = errorData.error;
967
+ }
968
+ } catch {
969
+ }
970
+ throw new Error(errorMessage);
971
+ }
972
+ const reader = response.body?.getReader();
973
+ if (!reader) {
974
+ throw new Error("No response body");
975
+ }
976
+ const decoder = new TextDecoder();
977
+ let buffer = "";
978
+ let aborted = false;
979
+ while (true) {
980
+ const { done, value } = await reader.read();
981
+ if (done) {
982
+ break;
983
+ }
984
+ const decoded = decoder.decode(value, { stream: true });
985
+ buffer += decoded;
986
+ const lines = buffer.split("\n");
987
+ buffer = lines.pop() ?? "";
988
+ const newLines = [];
989
+ for (const line of lines) {
990
+ if (line.trim()) {
991
+ newLines.push(line);
992
+ }
993
+ const { patch, malformed } = parsePatchLine(line);
994
+ if (patch) {
995
+ currentSpec = applyPatch(currentSpec, patch);
996
+ setSpec({ ...currentSpec });
997
+ } else if (malformed) {
998
+ malformedLines.push(line.trim());
999
+ if (abortOnMalformed) {
1000
+ await reader.cancel();
1001
+ aborted = true;
1002
+ break;
1003
+ }
1004
+ }
1005
+ }
1006
+ if (aborted) break;
1007
+ if (newLines.length > 0) {
1008
+ setRawLines((prev) => [...prev, ...newLines]);
1009
+ }
1010
+ }
1011
+ if (!aborted && buffer.trim()) {
1012
+ setRawLines((prev) => [...prev, buffer]);
1013
+ const { patch, malformed } = parsePatchLine(buffer);
1014
+ if (patch) {
1015
+ currentSpec = applyPatch(currentSpec, patch);
1016
+ setSpec({ ...currentSpec });
1017
+ } else if (malformed) {
1018
+ malformedLines.push(buffer.trim());
1019
+ }
1020
+ }
1021
+ return { spec: currentSpec, completed: !aborted, malformedLines };
1022
+ },
1023
+ [api, fetchFn]
1024
+ );
1025
+ const send = (0, import_react6.useCallback)(
1026
+ async (prompt, context) => {
1027
+ abortControllerRef.current?.abort();
1028
+ abortControllerRef.current = new AbortController();
1029
+ setIsStreaming(true);
1030
+ setError(null);
1031
+ setRawLines([]);
1032
+ const previousSpec = context?.previousSpec;
1033
+ let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
1034
+ let retriesUsed = 0;
1035
+ let currentPrompt = prompt;
1036
+ let currentContext = context;
1037
+ try {
1038
+ while (retriesUsed <= maxRetries) {
1039
+ const result = await streamRequest(
1040
+ currentPrompt,
1041
+ currentContext,
1042
+ currentSpec,
1043
+ enableValidation
1044
+ // only abort on malformed when validation is on
1045
+ );
1046
+ currentSpec = result.spec;
1047
+ if (!result.completed && result.malformedLines.length > 0) {
1048
+ if (retriesUsed >= maxRetries) {
1049
+ break;
1050
+ }
1051
+ retriesUsed++;
1052
+ currentContext = { ...context, previousSpec: currentSpec };
1053
+ currentPrompt = `The previous generation contained malformed JSON that could not be parsed. The line was:
1054
+ ${result.malformedLines[result.malformedLines.length - 1]?.slice(0, 500)}
1055
+
1056
+ The current spec state is provided. Continue generating from where you left off. Output ONLY the remaining patches needed to complete the UI.`;
1057
+ continue;
1058
+ }
1059
+ if (!enableValidation || !currentSpec.root) {
1060
+ break;
1061
+ }
1062
+ const { spec: fixedSpec, fixes } = (0, import_core6.autoFixSpec)(currentSpec);
1063
+ if (fixes.length > 0) {
1064
+ currentSpec = fixedSpec;
1065
+ setSpec({ ...currentSpec });
1066
+ }
1067
+ const validation = (0, import_core6.validateSpec)(currentSpec);
1068
+ if (validation.valid) {
1069
+ break;
1070
+ }
1071
+ const errors = validation.issues.filter(
1072
+ (i) => i.severity === "error"
1073
+ );
1074
+ if (retriesUsed >= maxRetries) {
1075
+ break;
1076
+ }
1077
+ retriesUsed++;
1078
+ const issueText = (0, import_core6.formatSpecIssues)(validation.issues);
1079
+ currentContext = { ...context, previousSpec: currentSpec };
1080
+ 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.
1081
+
1082
+ ` + issueText;
1083
+ }
1084
+ onComplete?.(currentSpec);
1085
+ } catch (err) {
1086
+ if (err.name === "AbortError") {
1087
+ return;
1088
+ }
1089
+ const error2 = err instanceof Error ? err : new Error(String(err));
1090
+ setError(error2);
1091
+ onError?.(error2);
1092
+ } finally {
1093
+ setIsStreaming(false);
1094
+ }
1095
+ },
1096
+ [
1097
+ api,
1098
+ fetchFn,
1099
+ onComplete,
1100
+ onError,
1101
+ enableValidation,
1102
+ maxRetries,
1103
+ streamRequest
1104
+ ]
1105
+ );
1106
+ (0, import_react6.useEffect)(() => {
1107
+ return () => {
1108
+ abortControllerRef.current?.abort();
1109
+ };
1110
+ }, []);
1111
+ return {
1112
+ spec,
1113
+ isStreaming,
1114
+ error,
1115
+ rawLines,
1116
+ send,
1117
+ stop,
1118
+ clear
1119
+ };
1120
+ }
1121
+ function flatToTree(elements) {
1122
+ const elementMap = {};
1123
+ let root = "";
1124
+ for (const element of elements) {
1125
+ elementMap[element.key] = {
1126
+ type: element.type,
1127
+ props: element.props,
1128
+ children: [],
1129
+ visible: element.visible
1130
+ };
1131
+ }
1132
+ for (const element of elements) {
1133
+ if (element.parentKey) {
1134
+ const parent = elementMap[element.parentKey];
1135
+ if (parent) {
1136
+ if (!parent.children) {
1137
+ parent.children = [];
1138
+ }
1139
+ parent.children.push(element.key);
1140
+ }
1141
+ } else {
1142
+ root = element.key;
1143
+ }
1144
+ }
1145
+ return { root, elements: elementMap };
1146
+ }
1147
+
1148
+ // src/components/standard.tsx
1149
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1150
+ function ContainerComponent({ element, children }) {
1151
+ const p = element.props;
1152
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1153
+ import_react_native2.View,
1154
+ {
1155
+ style: {
1156
+ padding: p.padding ?? void 0,
1157
+ paddingHorizontal: p.paddingHorizontal ?? void 0,
1158
+ paddingVertical: p.paddingVertical ?? void 0,
1159
+ margin: p.margin ?? void 0,
1160
+ backgroundColor: p.backgroundColor ?? void 0,
1161
+ borderRadius: p.borderRadius ?? void 0,
1162
+ flex: p.flex ?? void 0
1163
+ },
1164
+ children
1165
+ }
1166
+ );
1167
+ }
1168
+ function RowComponent({ element, children }) {
1169
+ const p = element.props;
1170
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1171
+ import_react_native2.View,
1172
+ {
1173
+ style: {
1174
+ flexDirection: "row",
1175
+ gap: p.gap ?? void 0,
1176
+ alignItems: p.alignItems ?? void 0,
1177
+ justifyContent: p.justifyContent ?? void 0,
1178
+ flexWrap: p.flexWrap ?? void 0,
1179
+ padding: p.padding ?? void 0,
1180
+ flex: p.flex ?? void 0
1181
+ },
1182
+ children
1183
+ }
1184
+ );
1185
+ }
1186
+ function ColumnComponent({ element, children }) {
1187
+ const p = element.props;
1188
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1189
+ import_react_native2.View,
1190
+ {
1191
+ style: {
1192
+ flexDirection: "column",
1193
+ gap: p.gap ?? void 0,
1194
+ alignItems: p.alignItems ?? void 0,
1195
+ justifyContent: p.justifyContent ?? void 0,
1196
+ padding: p.padding ?? void 0,
1197
+ flex: p.flex ?? void 0
1198
+ },
1199
+ children
1200
+ }
1201
+ );
1202
+ }
1203
+ function ScrollContainerComponent({ element, children }) {
1204
+ const p = element.props;
1205
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1206
+ import_react_native2.ScrollView,
1207
+ {
1208
+ horizontal: p.horizontal ?? false,
1209
+ showsVerticalScrollIndicator: p.showsScrollIndicator ?? true,
1210
+ showsHorizontalScrollIndicator: p.showsScrollIndicator ?? true,
1211
+ contentContainerStyle: {
1212
+ padding: p.padding ?? void 0
1213
+ },
1214
+ style: {
1215
+ flex: p.flex ?? 1,
1216
+ backgroundColor: p.backgroundColor ?? void 0
1217
+ },
1218
+ children
1219
+ }
1220
+ );
1221
+ }
1222
+ function SafeAreaComponent({ element, children }) {
1223
+ const p = element.props;
1224
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1225
+ import_react_native2.SafeAreaView,
1226
+ {
1227
+ style: {
1228
+ flex: 1,
1229
+ backgroundColor: p.backgroundColor ?? void 0
1230
+ },
1231
+ children
1232
+ }
1233
+ );
1234
+ }
1235
+ function SpacerComponent({ element }) {
1236
+ const p = element.props;
1237
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1238
+ import_react_native2.View,
1239
+ {
1240
+ style: {
1241
+ width: p.size ?? void 0,
1242
+ height: p.size ?? void 0,
1243
+ flex: p.flex ?? void 0
1244
+ }
1245
+ }
1246
+ );
1247
+ }
1248
+ function PressableComponent({ children, emit }) {
1249
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1250
+ import_react_native2.Pressable,
1251
+ {
1252
+ onPress: () => emit("press"),
1253
+ onLongPress: () => emit("longPress"),
1254
+ style: ({ pressed }) => ({
1255
+ opacity: pressed ? 0.7 : 1
1256
+ }),
1257
+ children
1258
+ }
1259
+ );
1260
+ }
1261
+ function DividerComponent({ element }) {
1262
+ const p = element.props;
1263
+ const isVertical = p.direction === "vertical";
1264
+ const thickness = p.thickness ?? 1;
1265
+ const color = p.color ?? "#e5e7eb";
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1267
+ import_react_native2.View,
1268
+ {
1269
+ style: {
1270
+ width: isVertical ? thickness : "100%",
1271
+ height: isVertical ? "100%" : thickness,
1272
+ backgroundColor: color,
1273
+ marginVertical: isVertical ? 0 : p.margin ?? 8,
1274
+ marginHorizontal: isVertical ? p.margin ?? 8 : 0
1275
+ }
1276
+ }
1277
+ );
1278
+ }
1279
+ var headingSizes = {
1280
+ h1: 32,
1281
+ h2: 24,
1282
+ h3: 20,
1283
+ h4: 16
1284
+ };
1285
+ function HeadingComponent({ element }) {
1286
+ const p = element.props;
883
1287
  const level = p.level ?? "h2";
884
1288
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
885
1289
  import_react_native2.Text,
@@ -1039,7 +1443,7 @@ function ChipComponent({ element, emit }) {
1039
1443
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1040
1444
  import_react_native2.Pressable,
1041
1445
  {
1042
- onPress: () => emit?.("press"),
1446
+ onPress: () => emit("press"),
1043
1447
  style: {
1044
1448
  flexDirection: "row",
1045
1449
  alignItems: "center",
@@ -1051,7 +1455,7 @@ function ChipComponent({ element, emit }) {
1051
1455
  },
1052
1456
  children: [
1053
1457
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: textColor }, children: p.label }),
1054
- hasRemove && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Pressable, { onPress: () => emit?.("remove"), style: { marginLeft: 4 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: textColor, fontWeight: "700" }, children: "x" }) })
1458
+ hasRemove && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Pressable, { onPress: () => emit("remove"), style: { marginLeft: 4 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: textColor, fontWeight: "700" }, children: "x" }) })
1055
1459
  ]
1056
1460
  }
1057
1461
  );
@@ -1084,7 +1488,7 @@ function ButtonComponent({ element, emit }) {
1084
1488
  import_react_native2.Pressable,
1085
1489
  {
1086
1490
  disabled,
1087
- onPress: () => emit?.("press"),
1491
+ onPress: () => emit("press"),
1088
1492
  style: ({ pressed }) => ({
1089
1493
  backgroundColor: variant.bg,
1090
1494
  paddingHorizontal: size.paddingH,
@@ -1122,12 +1526,13 @@ function ButtonComponent({ element, emit }) {
1122
1526
  }
1123
1527
  );
1124
1528
  }
1125
- function TextInputComponent({ element }) {
1529
+ function TextInputComponent({ element, bindings }) {
1126
1530
  const p = element.props;
1127
- const [boundValue, setBoundValue] = useStateBinding(
1128
- p.statePath ?? ""
1531
+ const [boundValue, setBoundValue] = useBoundProp(
1532
+ p.value,
1533
+ bindings?.value
1129
1534
  );
1130
- const displayValue = p.statePath ? boundValue ?? "" : p.value ?? "";
1535
+ const displayValue = boundValue ?? "";
1131
1536
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: p.flex != null ? { flex: p.flex } : void 0, children: [
1132
1537
  p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: inputStyles.label, children: p.label }),
1133
1538
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -1135,7 +1540,7 @@ function TextInputComponent({ element }) {
1135
1540
  {
1136
1541
  placeholder: p.placeholder ?? void 0,
1137
1542
  value: displayValue,
1138
- onChangeText: p.statePath ? setBoundValue : void 0,
1543
+ onChangeText: bindings?.value ? setBoundValue : void 0,
1139
1544
  secureTextEntry: p.secureTextEntry ?? false,
1140
1545
  keyboardType: p.keyboardType ?? "default",
1141
1546
  multiline: p.multiline ?? false,
@@ -1151,1146 +1556,713 @@ function TextInputComponent({ element }) {
1151
1556
  )
1152
1557
  ] });
1153
1558
  }
1154
- function SwitchComponent({ element }) {
1559
+ function SwitchComponent({ element, bindings }) {
1155
1560
  const p = element.props;
1156
- const [boundValue, setBoundValue] = useStateBinding(
1157
- p.statePath ?? ""
1561
+ const [checked, setChecked] = useBoundProp(
1562
+ p.checked,
1563
+ bindings?.checked
1158
1564
  );
1159
- const displayValue = p.statePath ? boundValue ?? false : p.value ?? false;
1565
+ const displayValue = checked ?? false;
1160
1566
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
1161
1567
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1162
1568
  import_react_native2.Switch,
1163
1569
  {
1164
1570
  value: displayValue,
1165
- onValueChange: p.statePath ? setBoundValue : void 0,
1571
+ onValueChange: bindings?.checked ? setChecked : void 0,
1166
1572
  disabled: p.disabled ?? false
1167
1573
  }
1168
- ),
1169
- p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1170
- ] });
1171
- }
1172
- function CheckboxComponent({ element }) {
1173
- const p = element.props;
1174
- const [boundValue, setBoundValue] = useStateBinding(
1175
- p.statePath ?? ""
1176
- );
1177
- const checked = p.statePath ? boundValue ?? false : p.checked ?? false;
1178
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1179
- import_react_native2.Pressable,
1180
- {
1181
- disabled: p.disabled ?? false,
1182
- onPress: () => {
1183
- if (p.statePath) {
1184
- setBoundValue(!checked);
1185
- }
1186
- },
1187
- style: {
1188
- flexDirection: "row",
1189
- alignItems: "center",
1190
- gap: 8,
1191
- opacity: p.disabled ? 0.5 : 1
1192
- },
1193
- children: [
1194
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1195
- import_react_native2.View,
1196
- {
1197
- style: {
1198
- width: 20,
1199
- height: 20,
1200
- borderRadius: 4,
1201
- borderWidth: 2,
1202
- borderColor: checked ? "#3b82f6" : "#d1d5db",
1203
- backgroundColor: checked ? "#3b82f6" : "transparent",
1204
- alignItems: "center",
1205
- justifyContent: "center"
1206
- },
1207
- children: checked && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { color: "#ffffff", fontSize: 12, fontWeight: "700" }, children: "\u2713" })
1208
- }
1209
- ),
1210
- p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1211
- ]
1212
- }
1213
- );
1214
- }
1215
- function SliderComponent({ element }) {
1216
- const p = element.props;
1217
- const min = p.min ?? 0;
1218
- const max = p.max ?? 100;
1219
- const value = p.value ?? min;
1220
- const progress = max > min ? (value - min) / (max - min) : 0;
1221
- const trackColor = p.color ?? "#3b82f6";
1222
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { children: [
1223
- p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1224
- import_react_native2.View,
1225
- {
1226
- style: {
1227
- flexDirection: "row",
1228
- justifyContent: "space-between",
1229
- marginBottom: 4
1230
- },
1231
- children: [
1232
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#374151" }, children: p.label }),
1233
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#6b7280" }, children: value })
1234
- ]
1235
- }
1236
- ),
1237
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1238
- import_react_native2.View,
1239
- {
1240
- style: {
1241
- height: 6,
1242
- backgroundColor: "#e5e7eb",
1243
- borderRadius: 3,
1244
- overflow: "hidden"
1245
- },
1246
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1247
- import_react_native2.View,
1248
- {
1249
- style: {
1250
- width: `${Math.min(Math.max(progress * 100, 0), 100)}%`,
1251
- height: "100%",
1252
- backgroundColor: trackColor,
1253
- borderRadius: 3
1254
- }
1255
- }
1256
- )
1257
- }
1258
- )
1259
- ] });
1260
- }
1261
- function SearchBarComponent({ element, emit }) {
1262
- const p = element.props;
1263
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1264
- import_react_native2.View,
1265
- {
1266
- style: {
1267
- flexDirection: "row",
1268
- alignItems: "center",
1269
- backgroundColor: "#f3f4f6",
1270
- borderRadius: 8,
1271
- paddingHorizontal: 12
1272
- },
1273
- children: [
1274
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#9ca3af", marginRight: 8 }, children: "Search" }),
1275
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1276
- import_react_native2.TextInput,
1277
- {
1278
- placeholder: p.placeholder ?? "Search...",
1279
- value: p.value ?? void 0,
1280
- returnKeyType: "search",
1281
- onSubmitEditing: () => emit?.("submit"),
1282
- style: {
1283
- flex: 1,
1284
- paddingVertical: 10,
1285
- fontSize: 16,
1286
- color: "#111827"
1287
- }
1288
- }
1289
- )
1290
- ]
1291
- }
1292
- );
1293
- }
1294
- function SpinnerComponent({ element }) {
1295
- const p = element.props;
1296
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.ActivityIndicator, { size: p.size ?? "small", color: p.color ?? "#3b82f6" });
1297
- }
1298
- function ProgressBarComponent({ element }) {
1299
- const p = element.props;
1300
- const height = p.height ?? 6;
1301
- const progress = Math.min(Math.max(p.progress, 0), 1);
1302
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1303
- import_react_native2.View,
1304
- {
1305
- style: {
1306
- height,
1307
- backgroundColor: p.trackColor ?? "#e5e7eb",
1308
- borderRadius: height / 2,
1309
- overflow: "hidden"
1310
- },
1311
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1312
- import_react_native2.View,
1313
- {
1314
- style: {
1315
- width: `${progress * 100}%`,
1316
- height: "100%",
1317
- backgroundColor: p.color ?? "#3b82f6",
1318
- borderRadius: height / 2
1319
- }
1320
- }
1321
- )
1322
- }
1323
- );
1324
- }
1325
- function CardComponent({ element, children }) {
1326
- const p = element.props;
1327
- const elevated = p.elevated !== false;
1328
- const padding = p.padding ?? 16;
1329
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1330
- import_react_native2.View,
1331
- {
1332
- style: [
1333
- {
1334
- backgroundColor: p.backgroundColor ?? "#ffffff",
1335
- borderRadius: p.borderRadius ?? 12,
1336
- padding
1337
- },
1338
- elevated && {
1339
- shadowColor: "#000",
1340
- shadowOffset: { width: 0, height: 2 },
1341
- shadowOpacity: 0.1,
1342
- shadowRadius: 8,
1343
- elevation: 3
1344
- }
1345
- ],
1346
- children: [
1347
- p.title && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1348
- import_react_native2.Text,
1349
- {
1350
- style: {
1351
- fontSize: 18,
1352
- fontWeight: "600",
1353
- color: "#111827",
1354
- marginBottom: p.subtitle ? 2 : 12
1355
- },
1356
- children: p.title
1357
- }
1358
- ),
1359
- p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1360
- import_react_native2.Text,
1361
- {
1362
- style: {
1363
- fontSize: 14,
1364
- color: "#6b7280",
1365
- marginBottom: 12
1366
- },
1367
- children: p.subtitle
1368
- }
1369
- ),
1370
- children
1371
- ]
1372
- }
1373
- );
1574
+ ),
1575
+ p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1576
+ ] });
1374
1577
  }
1375
- function ListItemComponent({ element, emit }) {
1578
+ function CheckboxComponent({ element, bindings }) {
1376
1579
  const p = element.props;
1377
- const hasPress = !!element.on?.press;
1378
- const content = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1379
- import_react_native2.View,
1580
+ const [checked, setChecked] = useBoundProp(
1581
+ p.checked,
1582
+ bindings?.checked
1583
+ );
1584
+ const isChecked = checked ?? false;
1585
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1586
+ import_react_native2.Pressable,
1380
1587
  {
1588
+ disabled: p.disabled ?? false,
1589
+ onPress: () => {
1590
+ if (bindings?.checked) {
1591
+ setChecked(!isChecked);
1592
+ }
1593
+ },
1381
1594
  style: {
1382
1595
  flexDirection: "row",
1383
1596
  alignItems: "center",
1384
- paddingVertical: 12,
1385
- paddingHorizontal: 16
1597
+ gap: 8,
1598
+ opacity: p.disabled ? 0.5 : 1
1386
1599
  },
1387
1600
  children: [
1388
- p.leading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1389
- import_react_native2.Text,
1601
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1602
+ import_react_native2.View,
1390
1603
  {
1391
1604
  style: {
1392
- fontSize: 16,
1393
- color: "#6b7280",
1394
- marginRight: 12,
1395
- width: 24,
1396
- textAlign: "center"
1605
+ width: 20,
1606
+ height: 20,
1607
+ borderRadius: 4,
1608
+ borderWidth: 2,
1609
+ borderColor: isChecked ? "#3b82f6" : "#d1d5db",
1610
+ backgroundColor: isChecked ? "#3b82f6" : "transparent",
1611
+ alignItems: "center",
1612
+ justifyContent: "center"
1397
1613
  },
1398
- children: p.leading
1614
+ children: isChecked && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { color: "#ffffff", fontSize: 12, fontWeight: "700" }, children: "\u2713" })
1399
1615
  }
1400
1616
  ),
1401
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: { flex: 1 }, children: [
1402
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#111827" }, children: p.title }),
1403
- p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#6b7280", marginTop: 2 }, children: p.subtitle })
1404
- ] }),
1405
- p.trailing && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#6b7280", marginLeft: 8 }, children: p.trailing }),
1406
- p.showChevron && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#9ca3af", marginLeft: 8 }, children: ">" })
1407
- ]
1408
- }
1409
- );
1410
- if (hasPress) {
1411
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1412
- import_react_native2.Pressable,
1413
- {
1414
- onPress: () => emit?.("press"),
1415
- style: ({ pressed }) => ({
1416
- opacity: pressed ? 0.7 : 1,
1417
- backgroundColor: pressed ? "#f9fafb" : "transparent"
1418
- }),
1419
- children: content
1420
- }
1421
- );
1422
- }
1423
- return content;
1424
- }
1425
- function ModalComponent({ element, children }) {
1426
- const p = element.props;
1427
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1428
- import_react_native2.Modal,
1429
- {
1430
- visible: p.visible,
1431
- animationType: p.animationType ?? "slide",
1432
- transparent: true,
1433
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.View, { style: modalStyles.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: modalStyles.content, children: [
1434
- p.title && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: modalStyles.title, children: p.title }),
1435
- children
1436
- ] }) })
1437
- }
1438
- );
1439
- }
1440
- function createStandardActionHandlers(options) {
1441
- return {
1442
- navigate: async (params) => {
1443
- const screen = params.screen;
1444
- const navParams = params.params;
1445
- options?.navigate?.(screen, navParams ?? void 0);
1446
- },
1447
- goBack: async () => {
1448
- options?.goBack?.();
1449
- },
1450
- showAlert: async (params) => {
1451
- const title = params.title;
1452
- const message = params.message ?? void 0;
1453
- const buttons = params.buttons;
1454
- const alertButtons = buttons?.map((btn) => ({
1455
- text: btn.text,
1456
- style: btn.style
1457
- }));
1458
- import_react_native2.Alert.alert(title, message, alertButtons);
1459
- },
1460
- share: async (params) => {
1461
- const message = params.message;
1462
- const url = params.url ?? void 0;
1463
- const title = params.title ?? void 0;
1464
- await import_react_native2.Share.share({ message, url, title });
1465
- },
1466
- openURL: async (params) => {
1467
- const url = params.url;
1468
- const canOpen = await import_react_native2.Linking.canOpenURL(url);
1469
- if (canOpen) {
1470
- await import_react_native2.Linking.openURL(url);
1471
- }
1472
- },
1473
- setData: async (params) => {
1474
- const path = params.path;
1475
- const value = params.value;
1476
- options?.onSetData?.(path, value);
1477
- },
1478
- refresh: async (params) => {
1479
- const target = params.target ?? void 0;
1480
- options?.onRefresh?.(target);
1481
- }
1482
- };
1483
- }
1484
- var standardComponents = {
1485
- // Layout
1486
- Container: ContainerComponent,
1487
- Row: RowComponent,
1488
- Column: ColumnComponent,
1489
- ScrollContainer: ScrollContainerComponent,
1490
- SafeArea: SafeAreaComponent,
1491
- Pressable: PressableComponent,
1492
- Spacer: SpacerComponent,
1493
- Divider: DividerComponent,
1494
- // Content
1495
- Heading: HeadingComponent,
1496
- Paragraph: ParagraphComponent,
1497
- Label: LabelComponent,
1498
- Image: ImageComponent,
1499
- Avatar: AvatarComponent,
1500
- Badge: BadgeComponent,
1501
- Chip: ChipComponent,
1502
- // Input
1503
- Button: ButtonComponent,
1504
- TextInput: TextInputComponent,
1505
- Switch: SwitchComponent,
1506
- Checkbox: CheckboxComponent,
1507
- Slider: SliderComponent,
1508
- SearchBar: SearchBarComponent,
1509
- // Feedback
1510
- Spinner: SpinnerComponent,
1511
- ProgressBar: ProgressBarComponent,
1512
- // Composite
1513
- Card: CardComponent,
1514
- ListItem: ListItemComponent,
1515
- Modal: ModalComponent
1516
- };
1517
- var inputStyles = import_react_native2.StyleSheet.create({
1518
- label: {
1519
- fontSize: 14,
1520
- fontWeight: "500",
1521
- color: "#374151",
1522
- marginBottom: 4
1523
- },
1524
- input: {
1525
- borderWidth: 1,
1526
- borderColor: "#d1d5db",
1527
- borderRadius: 8,
1528
- paddingHorizontal: 12,
1529
- paddingVertical: 10,
1530
- fontSize: 16,
1531
- color: "#111827",
1532
- backgroundColor: "#ffffff"
1533
- }
1534
- });
1535
- var modalStyles = import_react_native2.StyleSheet.create({
1536
- overlay: {
1537
- flex: 1,
1538
- backgroundColor: "rgba(0, 0, 0, 0.5)",
1539
- justifyContent: "flex-end"
1540
- },
1541
- content: {
1542
- backgroundColor: "#ffffff",
1543
- borderTopLeftRadius: 16,
1544
- borderTopRightRadius: 16,
1545
- padding: 24,
1546
- maxHeight: "80%"
1547
- },
1548
- title: {
1549
- fontSize: 20,
1550
- fontWeight: "600",
1551
- color: "#111827",
1552
- marginBottom: 16
1553
- }
1554
- });
1555
-
1556
- // src/renderer.tsx
1557
- var import_jsx_runtime7 = require("react/jsx-runtime");
1558
- var ElementErrorBoundary = class extends import_react6.default.Component {
1559
- constructor(props) {
1560
- super(props);
1561
- this.state = { hasError: false };
1562
- }
1563
- static getDerivedStateFromError() {
1564
- return { hasError: true };
1565
- }
1566
- componentDidCatch(error, info) {
1567
- console.error(
1568
- `[json-render] Rendering error in <${this.props.elementType}>:`,
1569
- error,
1570
- info.componentStack
1571
- );
1572
- }
1573
- render() {
1574
- if (this.state.hasError) {
1575
- return null;
1576
- }
1577
- return this.props.children;
1578
- }
1579
- };
1580
- function ElementRenderer({
1581
- element,
1582
- spec,
1583
- registry,
1584
- loading,
1585
- fallback
1586
- }) {
1587
- const repeatScope = useRepeatScope();
1588
- const { ctx } = useVisibility();
1589
- const { execute } = useActions();
1590
- let effectiveElement = element;
1591
- if (repeatScope) {
1592
- const rewrittenProps = rewriteRepeatTokens(
1593
- element.props,
1594
- repeatScope.basePath,
1595
- repeatScope.index
1596
- );
1597
- const rewrittenVisible = element.visible !== void 0 ? rewriteRepeatTokens(
1598
- element.visible,
1599
- repeatScope.basePath,
1600
- repeatScope.index
1601
- ) : element.visible;
1602
- const rewrittenOn = element.on !== void 0 ? rewriteRepeatTokens(
1603
- element.on,
1604
- repeatScope.basePath,
1605
- repeatScope.index
1606
- ) : element.on;
1607
- if (rewrittenProps !== element.props || rewrittenVisible !== element.visible || rewrittenOn !== element.on) {
1608
- effectiveElement = {
1609
- ...element,
1610
- props: rewrittenProps,
1611
- visible: rewrittenVisible,
1612
- on: rewrittenOn
1613
- };
1614
- }
1615
- }
1616
- const isVisible = useIsVisible(effectiveElement.visible);
1617
- const onBindings = effectiveElement.on;
1618
- const emit = (0, import_react6.useCallback)(
1619
- (eventName) => {
1620
- const binding = onBindings?.[eventName];
1621
- if (!binding) return;
1622
- const bindings = Array.isArray(binding) ? binding : [binding];
1623
- for (const b of bindings) {
1624
- execute(b);
1625
- }
1626
- },
1627
- [onBindings, execute]
1628
- );
1629
- if (!isVisible) {
1630
- return null;
1631
- }
1632
- const resolvedProps = (0, import_core6.resolveElementProps)(
1633
- effectiveElement.props,
1634
- ctx
1635
- );
1636
- const resolvedElement = resolvedProps !== effectiveElement.props ? { ...effectiveElement, props: resolvedProps } : effectiveElement;
1637
- const Component = registry[resolvedElement.type] ?? fallback;
1638
- if (!Component) {
1639
- console.warn(`No renderer for component type: ${resolvedElement.type}`);
1640
- return null;
1641
- }
1642
- const children = resolvedElement.repeat ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1643
- RepeatChildren,
1644
- {
1645
- element: resolvedElement,
1646
- spec,
1647
- registry,
1648
- loading,
1649
- fallback
1650
- }
1651
- ) : resolvedElement.children?.map((childKey) => {
1652
- const childElement = spec.elements[childKey];
1653
- if (!childElement) {
1654
- if (!loading) {
1655
- console.warn(
1656
- `[json-render] Missing element "${childKey}" referenced as child of "${resolvedElement.type}". This element will not render.`
1657
- );
1658
- }
1659
- return null;
1617
+ p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#374151" }, children: p.label })
1618
+ ]
1660
1619
  }
1661
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1662
- ElementRenderer,
1663
- {
1664
- element: childElement,
1665
- spec,
1666
- registry,
1667
- loading,
1668
- fallback
1669
- },
1670
- childKey
1671
- );
1672
- });
1673
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ElementErrorBoundary, { elementType: resolvedElement.type, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Component, { element: resolvedElement, emit, loading, children }) });
1620
+ );
1674
1621
  }
1675
- function RepeatChildren({
1676
- element,
1677
- spec,
1678
- registry,
1679
- loading,
1680
- fallback
1681
- }) {
1682
- const { state } = useStateStore();
1683
- const repeat = element.repeat;
1684
- const statePath = repeat.path;
1685
- const items = (0, import_core6.getByPath)(state, statePath) ?? [];
1686
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: items.map((item, index) => {
1687
- const key = repeat.key && typeof item === "object" && item !== null ? String(item[repeat.key] ?? index) : String(index);
1688
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1689
- RepeatScopeProvider,
1622
+ function SliderComponent({ element }) {
1623
+ const p = element.props;
1624
+ const min = p.min ?? 0;
1625
+ const max = p.max ?? 100;
1626
+ const value = p.value ?? min;
1627
+ const progress = max > min ? (value - min) / (max - min) : 0;
1628
+ const trackColor = p.color ?? "#3b82f6";
1629
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { children: [
1630
+ p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1631
+ import_react_native2.View,
1690
1632
  {
1691
- basePath: `${statePath}/${index}`,
1692
- index,
1693
- children: element.children?.map((childKey) => {
1694
- const childElement = spec.elements[childKey];
1695
- if (!childElement) {
1696
- if (!loading) {
1697
- console.warn(
1698
- `[json-render] Missing element "${childKey}" referenced as child of "${element.type}" (repeat). This element will not render.`
1699
- );
1633
+ style: {
1634
+ flexDirection: "row",
1635
+ justifyContent: "space-between",
1636
+ marginBottom: 4
1637
+ },
1638
+ children: [
1639
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#374151" }, children: p.label }),
1640
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#6b7280" }, children: value })
1641
+ ]
1642
+ }
1643
+ ),
1644
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1645
+ import_react_native2.View,
1646
+ {
1647
+ style: {
1648
+ height: 6,
1649
+ backgroundColor: "#e5e7eb",
1650
+ borderRadius: 3,
1651
+ overflow: "hidden"
1652
+ },
1653
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1654
+ import_react_native2.View,
1655
+ {
1656
+ style: {
1657
+ width: `${Math.min(Math.max(progress * 100, 0), 100)}%`,
1658
+ height: "100%",
1659
+ backgroundColor: trackColor,
1660
+ borderRadius: 3
1700
1661
  }
1701
- return null;
1702
1662
  }
1703
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1704
- ElementRenderer,
1705
- {
1706
- element: childElement,
1707
- spec,
1708
- registry,
1709
- loading,
1710
- fallback
1711
- },
1712
- childKey
1713
- );
1714
- })
1715
- },
1716
- key
1717
- );
1718
- }) });
1663
+ )
1664
+ }
1665
+ )
1666
+ ] });
1719
1667
  }
1720
- function Renderer({
1721
- spec,
1722
- registry: customRegistry,
1723
- includeStandard = true,
1724
- loading,
1725
- fallback
1726
- }) {
1727
- const registry = (0, import_react6.useMemo)(
1728
- () => ({
1729
- ...includeStandard ? standardComponents : {},
1730
- ...customRegistry
1731
- }),
1732
- [customRegistry, includeStandard]
1733
- );
1734
- if (!spec || !spec.root) {
1735
- return null;
1736
- }
1737
- const rootElement = spec.elements[spec.root];
1738
- if (!rootElement) {
1739
- return null;
1740
- }
1741
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1742
- ElementRenderer,
1668
+ function SearchBarComponent({ element, emit }) {
1669
+ const p = element.props;
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1671
+ import_react_native2.View,
1743
1672
  {
1744
- element: rootElement,
1745
- spec,
1746
- registry,
1747
- loading,
1748
- fallback
1673
+ style: {
1674
+ flexDirection: "row",
1675
+ alignItems: "center",
1676
+ backgroundColor: "#f3f4f6",
1677
+ borderRadius: 8,
1678
+ paddingHorizontal: 12
1679
+ },
1680
+ children: [
1681
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#9ca3af", marginRight: 8 }, children: "Search" }),
1682
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1683
+ import_react_native2.TextInput,
1684
+ {
1685
+ placeholder: p.placeholder ?? "Search...",
1686
+ value: p.value ?? void 0,
1687
+ returnKeyType: "search",
1688
+ onSubmitEditing: () => emit("submit"),
1689
+ style: {
1690
+ flex: 1,
1691
+ paddingVertical: 10,
1692
+ fontSize: 16,
1693
+ color: "#111827"
1694
+ }
1695
+ }
1696
+ )
1697
+ ]
1749
1698
  }
1750
1699
  );
1751
1700
  }
1752
- function JSONUIProvider({
1753
- registry,
1754
- initialState,
1755
- authState,
1756
- actionHandlers,
1757
- navigate,
1758
- validationFunctions,
1759
- onStateChange,
1760
- children
1761
- }) {
1762
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1763
- StateProvider,
1764
- {
1765
- initialState,
1766
- authState,
1767
- onStateChange,
1768
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionProvider, { handlers: actionHandlers, navigate, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ValidationProvider, { customFunctions: validationFunctions, children: [
1769
- children,
1770
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmationDialogManager, {})
1771
- ] }) }) })
1772
- }
1773
- );
1701
+ function SpinnerComponent({ element }) {
1702
+ const p = element.props;
1703
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.ActivityIndicator, { size: p.size ?? "small", color: p.color ?? "#3b82f6" });
1774
1704
  }
1775
- function ConfirmationDialogManager() {
1776
- const { pendingConfirmation, confirm, cancel } = useActions();
1777
- if (!pendingConfirmation?.action.confirm) {
1778
- return null;
1779
- }
1780
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1781
- ConfirmDialog,
1705
+ function ProgressBarComponent({ element }) {
1706
+ const p = element.props;
1707
+ const height = p.height ?? 6;
1708
+ const progress = Math.min(Math.max(p.progress, 0), 1);
1709
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1710
+ import_react_native2.View,
1782
1711
  {
1783
- confirm: pendingConfirmation.action.confirm,
1784
- onConfirm: confirm,
1785
- onCancel: cancel
1712
+ style: {
1713
+ height,
1714
+ backgroundColor: p.trackColor ?? "#e5e7eb",
1715
+ borderRadius: height / 2,
1716
+ overflow: "hidden"
1717
+ },
1718
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1719
+ import_react_native2.View,
1720
+ {
1721
+ style: {
1722
+ width: `${progress * 100}%`,
1723
+ height: "100%",
1724
+ backgroundColor: p.color ?? "#3b82f6",
1725
+ borderRadius: height / 2
1726
+ }
1727
+ }
1728
+ )
1786
1729
  }
1787
1730
  );
1788
1731
  }
1789
- function createRendererFromCatalog(_catalog, registry) {
1790
- return function CatalogRenderer(props) {
1791
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Renderer, { ...props, registry });
1792
- };
1793
- }
1794
- function defineRegistry(_catalog, options) {
1795
- const registry = {};
1796
- if (options.components) {
1797
- for (const [name, componentFn] of Object.entries(options.components)) {
1798
- registry[name] = ({
1799
- element,
1800
- children,
1801
- emit,
1802
- loading
1803
- }) => {
1804
- return componentFn({
1805
- props: element.props,
1806
- children,
1807
- emit,
1808
- loading
1809
- });
1810
- };
1811
- }
1812
- }
1813
- const actionMap = options.actions ? Object.entries(options.actions) : [];
1814
- const handlers = (getSetState, getState) => {
1815
- const result = {};
1816
- for (const [name, actionFn] of actionMap) {
1817
- result[name] = async (params) => {
1818
- const setState = getSetState();
1819
- const state = getState();
1820
- if (setState) {
1821
- await actionFn(params, setState, state);
1822
- }
1823
- };
1824
- }
1825
- return result;
1826
- };
1827
- const executeAction2 = async (actionName, params, setState, state = {}) => {
1828
- const entry = actionMap.find(([name]) => name === actionName);
1829
- if (entry) {
1830
- await entry[1](params, setState, state);
1831
- } else {
1832
- console.warn(`Unknown action: ${actionName}`);
1732
+ function CardComponent({ element, children }) {
1733
+ const p = element.props;
1734
+ const elevated = p.elevated !== false;
1735
+ const padding = p.padding ?? 16;
1736
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1737
+ import_react_native2.View,
1738
+ {
1739
+ style: [
1740
+ {
1741
+ backgroundColor: p.backgroundColor ?? "#ffffff",
1742
+ borderRadius: p.borderRadius ?? 12,
1743
+ padding
1744
+ },
1745
+ elevated && {
1746
+ shadowColor: "#000",
1747
+ shadowOffset: { width: 0, height: 2 },
1748
+ shadowOpacity: 0.1,
1749
+ shadowRadius: 8,
1750
+ elevation: 3
1751
+ }
1752
+ ],
1753
+ children: [
1754
+ p.title && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1755
+ import_react_native2.Text,
1756
+ {
1757
+ style: {
1758
+ fontSize: 18,
1759
+ fontWeight: "600",
1760
+ color: "#111827",
1761
+ marginBottom: p.subtitle ? 2 : 12
1762
+ },
1763
+ children: p.title
1764
+ }
1765
+ ),
1766
+ p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1767
+ import_react_native2.Text,
1768
+ {
1769
+ style: {
1770
+ fontSize: 14,
1771
+ color: "#6b7280",
1772
+ marginBottom: 12
1773
+ },
1774
+ children: p.subtitle
1775
+ }
1776
+ ),
1777
+ children
1778
+ ]
1833
1779
  }
1834
- };
1835
- return { registry, handlers, executeAction: executeAction2 };
1780
+ );
1836
1781
  }
1837
- function createRenderer(catalog, components) {
1838
- const registry = components;
1839
- return function CatalogRenderer({
1840
- spec,
1841
- state,
1842
- onAction,
1843
- onStateChange,
1844
- loading,
1845
- authState,
1846
- fallback
1847
- }) {
1848
- const actionHandlers = onAction ? {
1849
- __default__: (params) => {
1850
- const actionName = params.__actionName__;
1851
- const actionParams = params.__actionParams__;
1852
- return onAction(actionName, actionParams);
1853
- }
1854
- } : void 0;
1855
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1856
- StateProvider,
1782
+ function ListItemComponent({ element, emit }) {
1783
+ const p = element.props;
1784
+ const hasPress = !!element.on?.press;
1785
+ const content = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1786
+ import_react_native2.View,
1787
+ {
1788
+ style: {
1789
+ flexDirection: "row",
1790
+ alignItems: "center",
1791
+ paddingVertical: 12,
1792
+ paddingHorizontal: 16
1793
+ },
1794
+ children: [
1795
+ p.leading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1796
+ import_react_native2.Text,
1797
+ {
1798
+ style: {
1799
+ fontSize: 16,
1800
+ color: "#6b7280",
1801
+ marginRight: 12,
1802
+ width: 24,
1803
+ textAlign: "center"
1804
+ },
1805
+ children: p.leading
1806
+ }
1807
+ ),
1808
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: { flex: 1 }, children: [
1809
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#111827" }, children: p.title }),
1810
+ p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#6b7280", marginTop: 2 }, children: p.subtitle })
1811
+ ] }),
1812
+ p.trailing && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 14, color: "#6b7280", marginLeft: 8 }, children: p.trailing }),
1813
+ p.showChevron && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: { fontSize: 16, color: "#9ca3af", marginLeft: 8 }, children: ">" })
1814
+ ]
1815
+ }
1816
+ );
1817
+ if (hasPress) {
1818
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1819
+ import_react_native2.Pressable,
1857
1820
  {
1858
- initialState: state,
1859
- authState,
1860
- onStateChange,
1861
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ValidationProvider, { children: [
1862
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1863
- Renderer,
1864
- {
1865
- spec,
1866
- registry,
1867
- loading,
1868
- fallback
1869
- }
1870
- ),
1871
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmationDialogManager, {})
1872
- ] }) }) })
1821
+ onPress: () => emit("press"),
1822
+ style: ({ pressed }) => ({
1823
+ opacity: pressed ? 0.7 : 1,
1824
+ backgroundColor: pressed ? "#f9fafb" : "transparent"
1825
+ }),
1826
+ children: content
1873
1827
  }
1874
1828
  );
1875
- };
1876
- }
1877
-
1878
- // src/hooks.ts
1879
- var import_react7 = require("react");
1880
- var import_core7 = require("@json-render/core");
1881
- function looksLikeJson(line) {
1882
- return line.startsWith("{") || line.startsWith("[");
1883
- }
1884
- function parsePatchLine(line) {
1885
- const trimmed = line.trim();
1886
- if (!trimmed || trimmed.startsWith("//")) {
1887
- return { patch: null, malformed: false };
1888
1829
  }
1889
- if (!looksLikeJson(trimmed)) {
1890
- return { patch: null, malformed: false };
1891
- }
1892
- try {
1893
- return { patch: JSON.parse(trimmed), malformed: false };
1894
- } catch {
1895
- }
1896
- let attempt = trimmed;
1897
- for (let i = 0; i < 3; i++) {
1898
- const last = attempt[attempt.length - 1];
1899
- if (last === "}" || last === "]") {
1900
- attempt = attempt.slice(0, -1);
1901
- try {
1902
- const result = JSON.parse(attempt);
1903
- console.warn(
1904
- `[json-render] Recovered malformed JSONL line by removing ${i + 1} trailing '${last}'`
1905
- );
1906
- return { patch: result, malformed: false };
1907
- } catch {
1908
- }
1909
- } else {
1910
- break;
1830
+ return content;
1831
+ }
1832
+ function ModalComponent({ element, children }) {
1833
+ const p = element.props;
1834
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1835
+ import_react_native2.Modal,
1836
+ {
1837
+ visible: p.visible,
1838
+ animationType: p.animationType ?? "slide",
1839
+ transparent: true,
1840
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.View, { style: modalStyles.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: modalStyles.content, children: [
1841
+ p.title && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: modalStyles.title, children: p.title }),
1842
+ children
1843
+ ] }) })
1911
1844
  }
1912
- }
1913
- return { patch: null, malformed: true };
1845
+ );
1914
1846
  }
1915
- function setSpecValue(newSpec, path, value) {
1916
- if (path === "/root") {
1917
- newSpec.root = value;
1918
- return;
1919
- }
1920
- if (path === "/state") {
1921
- newSpec.state = value;
1922
- return;
1923
- }
1924
- if (path.startsWith("/state/")) {
1925
- if (!newSpec.state) newSpec.state = {};
1926
- const statePath = path.slice("/state".length);
1927
- (0, import_core7.setByPath)(newSpec.state, statePath, value);
1928
- return;
1929
- }
1930
- if (path.startsWith("/elements/")) {
1931
- const pathParts = path.slice("/elements/".length).split("/");
1932
- const elementKey = pathParts[0];
1933
- if (!elementKey) return;
1934
- if (pathParts.length === 1) {
1935
- newSpec.elements[elementKey] = value;
1936
- } else {
1937
- const element = newSpec.elements[elementKey];
1938
- if (element) {
1939
- const propPath = "/" + pathParts.slice(1).join("/");
1940
- const newElement = { ...element };
1941
- (0, import_core7.setByPath)(
1942
- newElement,
1943
- propPath,
1944
- value
1945
- );
1946
- newSpec.elements[elementKey] = newElement;
1847
+ function createStandardActionHandlers(options) {
1848
+ return {
1849
+ navigate: async (params) => {
1850
+ const screen = params.screen;
1851
+ const navParams = params.params;
1852
+ options?.navigate?.(screen, navParams ?? void 0);
1853
+ },
1854
+ goBack: async () => {
1855
+ options?.goBack?.();
1856
+ },
1857
+ showAlert: async (params) => {
1858
+ const title = params.title;
1859
+ const message = params.message ?? void 0;
1860
+ const buttons = params.buttons;
1861
+ const alertButtons = buttons?.map((btn) => ({
1862
+ text: btn.text,
1863
+ style: btn.style
1864
+ }));
1865
+ import_react_native2.Alert.alert(title, message, alertButtons);
1866
+ },
1867
+ share: async (params) => {
1868
+ const message = params.message;
1869
+ const url = params.url ?? void 0;
1870
+ const title = params.title ?? void 0;
1871
+ await import_react_native2.Share.share({ message, url, title });
1872
+ },
1873
+ openURL: async (params) => {
1874
+ const url = params.url;
1875
+ const canOpen = await import_react_native2.Linking.canOpenURL(url);
1876
+ if (canOpen) {
1877
+ await import_react_native2.Linking.openURL(url);
1947
1878
  }
1879
+ },
1880
+ setData: async (params) => {
1881
+ const statePath = params.statePath;
1882
+ const value = params.value;
1883
+ options?.onSetData?.(statePath, value);
1884
+ },
1885
+ refresh: async (params) => {
1886
+ const target = params.target ?? void 0;
1887
+ options?.onRefresh?.(target);
1948
1888
  }
1889
+ };
1890
+ }
1891
+ var standardComponents = {
1892
+ // Layout
1893
+ Container: ContainerComponent,
1894
+ Row: RowComponent,
1895
+ Column: ColumnComponent,
1896
+ ScrollContainer: ScrollContainerComponent,
1897
+ SafeArea: SafeAreaComponent,
1898
+ Pressable: PressableComponent,
1899
+ Spacer: SpacerComponent,
1900
+ Divider: DividerComponent,
1901
+ // Content
1902
+ Heading: HeadingComponent,
1903
+ Paragraph: ParagraphComponent,
1904
+ Label: LabelComponent,
1905
+ Image: ImageComponent,
1906
+ Avatar: AvatarComponent,
1907
+ Badge: BadgeComponent,
1908
+ Chip: ChipComponent,
1909
+ // Input
1910
+ Button: ButtonComponent,
1911
+ TextInput: TextInputComponent,
1912
+ Switch: SwitchComponent,
1913
+ Checkbox: CheckboxComponent,
1914
+ Slider: SliderComponent,
1915
+ SearchBar: SearchBarComponent,
1916
+ // Feedback
1917
+ Spinner: SpinnerComponent,
1918
+ ProgressBar: ProgressBarComponent,
1919
+ // Composite
1920
+ Card: CardComponent,
1921
+ ListItem: ListItemComponent,
1922
+ Modal: ModalComponent
1923
+ };
1924
+ var inputStyles = import_react_native2.StyleSheet.create({
1925
+ label: {
1926
+ fontSize: 14,
1927
+ fontWeight: "500",
1928
+ color: "#374151",
1929
+ marginBottom: 4
1930
+ },
1931
+ input: {
1932
+ borderWidth: 1,
1933
+ borderColor: "#d1d5db",
1934
+ borderRadius: 8,
1935
+ paddingHorizontal: 12,
1936
+ paddingVertical: 10,
1937
+ fontSize: 16,
1938
+ color: "#111827",
1939
+ backgroundColor: "#ffffff"
1949
1940
  }
1950
- }
1951
- function removeSpecValue(newSpec, path) {
1952
- if (path === "/state") {
1953
- newSpec.state = void 0;
1954
- return;
1941
+ });
1942
+ var modalStyles = import_react_native2.StyleSheet.create({
1943
+ overlay: {
1944
+ flex: 1,
1945
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1946
+ justifyContent: "flex-end"
1947
+ },
1948
+ content: {
1949
+ backgroundColor: "#ffffff",
1950
+ borderTopLeftRadius: 16,
1951
+ borderTopRightRadius: 16,
1952
+ padding: 24,
1953
+ maxHeight: "80%"
1954
+ },
1955
+ title: {
1956
+ fontSize: 20,
1957
+ fontWeight: "600",
1958
+ color: "#111827",
1959
+ marginBottom: 16
1955
1960
  }
1956
- if (path.startsWith("/state/") && newSpec.state) {
1957
- const statePath = path.slice("/state".length);
1958
- (0, import_core7.removeByPath)(newSpec.state, statePath);
1959
- return;
1961
+ });
1962
+
1963
+ // src/renderer.tsx
1964
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1965
+ var ElementErrorBoundary = class extends import_react7.default.Component {
1966
+ constructor(props) {
1967
+ super(props);
1968
+ this.state = { hasError: false };
1960
1969
  }
1961
- if (path.startsWith("/elements/")) {
1962
- const pathParts = path.slice("/elements/".length).split("/");
1963
- const elementKey = pathParts[0];
1964
- if (!elementKey) return;
1965
- if (pathParts.length === 1) {
1966
- const { [elementKey]: _, ...rest } = newSpec.elements;
1967
- newSpec.elements = rest;
1968
- } else {
1969
- const element = newSpec.elements[elementKey];
1970
- if (element) {
1971
- const propPath = "/" + pathParts.slice(1).join("/");
1972
- const newElement = { ...element };
1973
- (0, import_core7.removeByPath)(
1974
- newElement,
1975
- propPath
1976
- );
1977
- newSpec.elements[elementKey] = newElement;
1978
- }
1979
- }
1970
+ static getDerivedStateFromError() {
1971
+ return { hasError: true };
1980
1972
  }
1981
- }
1982
- function getSpecValue(spec, path) {
1983
- if (path === "/root") return spec.root;
1984
- return (0, import_core7.getByPath)(spec, path);
1985
- }
1986
- function applyPatch(spec, patch) {
1987
- const newSpec = { ...spec, elements: { ...spec.elements } };
1988
- switch (patch.op) {
1989
- case "add":
1990
- case "replace": {
1991
- setSpecValue(newSpec, patch.path, patch.value);
1992
- break;
1993
- }
1994
- case "remove": {
1995
- removeSpecValue(newSpec, patch.path);
1996
- break;
1997
- }
1998
- case "move": {
1999
- if (!patch.from) break;
2000
- const moveValue = getSpecValue(newSpec, patch.from);
2001
- removeSpecValue(newSpec, patch.from);
2002
- setSpecValue(newSpec, patch.path, moveValue);
2003
- break;
2004
- }
2005
- case "copy": {
2006
- if (!patch.from) break;
2007
- const copyValue = getSpecValue(newSpec, patch.from);
2008
- setSpecValue(newSpec, patch.path, copyValue);
2009
- break;
2010
- }
2011
- case "test": {
2012
- break;
1973
+ componentDidCatch(error, info) {
1974
+ console.error(
1975
+ `[json-render] Rendering error in <${this.props.elementType}>:`,
1976
+ error,
1977
+ info.componentStack
1978
+ );
1979
+ }
1980
+ render() {
1981
+ if (this.state.hasError) {
1982
+ return null;
2013
1983
  }
1984
+ return this.props.children;
2014
1985
  }
2015
- return newSpec;
2016
- }
2017
- function useUIStream({
2018
- api,
2019
- onComplete,
2020
- onError,
2021
- fetch: fetchFn = globalThis.fetch,
2022
- validate: enableValidation = false,
2023
- maxRetries = 5
1986
+ };
1987
+ var ElementRenderer = import_react7.default.memo(function ElementRenderer2({
1988
+ element,
1989
+ spec,
1990
+ registry,
1991
+ loading,
1992
+ fallback
2024
1993
  }) {
2025
- const [spec, setSpec] = (0, import_react7.useState)(null);
2026
- const [isStreaming, setIsStreaming] = (0, import_react7.useState)(false);
2027
- const [error, setError] = (0, import_react7.useState)(null);
2028
- const [rawLines, setRawLines] = (0, import_react7.useState)([]);
2029
- const abortControllerRef = (0, import_react7.useRef)(null);
2030
- const stop = (0, import_react7.useCallback)(() => {
2031
- abortControllerRef.current?.abort();
2032
- abortControllerRef.current = null;
2033
- setIsStreaming(false);
2034
- }, []);
2035
- const clear = (0, import_react7.useCallback)(() => {
2036
- setSpec(null);
2037
- setError(null);
2038
- setRawLines([]);
2039
- }, []);
2040
- const streamRequest = (0, import_react7.useCallback)(
2041
- async (prompt, context, initialSpec, abortOnMalformed) => {
2042
- let currentSpec = initialSpec;
2043
- setSpec(currentSpec);
2044
- const malformedLines = [];
2045
- const response = await fetchFn(api, {
2046
- method: "POST",
2047
- headers: { "Content-Type": "application/json" },
2048
- body: JSON.stringify({
2049
- prompt,
2050
- context,
2051
- currentSpec
2052
- }),
2053
- signal: abortControllerRef.current.signal
2054
- });
2055
- console.log("[useUIStream] response status:", response.status);
2056
- if (!response.ok) {
2057
- let errorMessage = `HTTP error: ${response.status}`;
2058
- try {
2059
- const errorData = await response.json();
2060
- if (errorData.message) {
2061
- errorMessage = errorData.message;
2062
- } else if (errorData.error) {
2063
- errorMessage = errorData.error;
2064
- }
2065
- } catch {
2066
- }
2067
- throw new Error(errorMessage);
2068
- }
2069
- const reader = response.body?.getReader();
2070
- if (!reader) {
2071
- throw new Error("No response body");
2072
- }
2073
- const decoder = new TextDecoder();
2074
- let buffer = "";
2075
- let chunkCount = 0;
2076
- let totalBytes = 0;
2077
- let aborted = false;
2078
- while (true) {
2079
- const { done, value } = await reader.read();
2080
- if (done) {
2081
- console.log(
2082
- "[useUIStream] stream done. Total chunks:",
2083
- chunkCount,
2084
- "bytes:",
2085
- totalBytes
2086
- );
2087
- break;
2088
- }
2089
- chunkCount++;
2090
- totalBytes += value?.byteLength ?? 0;
2091
- const decoded = decoder.decode(value, { stream: true });
2092
- buffer += decoded;
2093
- if (chunkCount <= 5) {
2094
- console.log(
2095
- `[useUIStream] chunk #${chunkCount} (${value?.byteLength ?? 0}b):`,
2096
- decoded.slice(0, 200)
2097
- );
2098
- }
2099
- const lines = buffer.split("\n");
2100
- buffer = lines.pop() ?? "";
2101
- const newLines = [];
2102
- for (const line of lines) {
2103
- if (line.trim()) {
2104
- newLines.push(line);
2105
- }
2106
- const { patch, malformed } = parsePatchLine(line);
2107
- if (patch) {
2108
- console.log("[useUIStream] applied patch:", patch.op, patch.path);
2109
- currentSpec = applyPatch(currentSpec, patch);
2110
- setSpec({ ...currentSpec });
2111
- } else if (malformed) {
2112
- malformedLines.push(line.trim());
2113
- console.warn(
2114
- "[useUIStream] malformed JSON line:",
2115
- line.trim().slice(0, 200)
2116
- );
2117
- if (abortOnMalformed) {
2118
- console.warn(
2119
- "[useUIStream] aborting stream due to malformed JSON, will retry"
2120
- );
2121
- await reader.cancel();
2122
- aborted = true;
2123
- break;
2124
- }
2125
- }
2126
- }
2127
- if (aborted) break;
2128
- if (newLines.length > 0) {
2129
- setRawLines((prev) => [...prev, ...newLines]);
1994
+ const repeatScope = useRepeatScope();
1995
+ const { ctx } = useVisibility();
1996
+ const { execute } = useActions();
1997
+ const fullCtx = (0, import_react7.useMemo)(
1998
+ () => repeatScope ? {
1999
+ ...ctx,
2000
+ repeatItem: repeatScope.item,
2001
+ repeatIndex: repeatScope.index,
2002
+ repeatBasePath: repeatScope.basePath
2003
+ } : ctx,
2004
+ [ctx, repeatScope]
2005
+ );
2006
+ const isVisible = element.visible === void 0 ? true : (0, import_core7.evaluateVisibility)(element.visible, fullCtx);
2007
+ const onBindings = element.on;
2008
+ const emit = (0, import_react7.useCallback)(
2009
+ (eventName) => {
2010
+ const binding = onBindings?.[eventName];
2011
+ if (!binding) return;
2012
+ const actionBindings = Array.isArray(binding) ? binding : [binding];
2013
+ for (const b of actionBindings) {
2014
+ if (!b.params) {
2015
+ execute(b);
2016
+ continue;
2130
2017
  }
2131
- }
2132
- if (!aborted && buffer.trim()) {
2133
- console.log("[useUIStream] remaining buffer:", buffer.slice(0, 200));
2134
- setRawLines((prev) => [...prev, buffer]);
2135
- const { patch, malformed } = parsePatchLine(buffer);
2136
- if (patch) {
2137
- currentSpec = applyPatch(currentSpec, patch);
2138
- setSpec({ ...currentSpec });
2139
- } else if (malformed) {
2140
- malformedLines.push(buffer.trim());
2018
+ const resolved = {};
2019
+ for (const [key, val] of Object.entries(b.params)) {
2020
+ resolved[key] = (0, import_core7.resolveActionParam)(val, fullCtx);
2141
2021
  }
2022
+ execute({ ...b, params: resolved });
2142
2023
  }
2143
- console.log(
2144
- "[useUIStream] spec state - root:",
2145
- currentSpec.root,
2146
- "elements:",
2147
- Object.keys(currentSpec.elements).length,
2148
- "completed:",
2149
- !aborted,
2150
- "malformed:",
2151
- malformedLines.length
2152
- );
2153
- return { spec: currentSpec, completed: !aborted, malformedLines };
2154
2024
  },
2155
- [api, fetchFn]
2025
+ [onBindings, execute, fullCtx]
2156
2026
  );
2157
- const send = (0, import_react7.useCallback)(
2158
- async (prompt, context) => {
2159
- abortControllerRef.current?.abort();
2160
- abortControllerRef.current = new AbortController();
2161
- setIsStreaming(true);
2162
- setError(null);
2163
- setRawLines([]);
2164
- const previousSpec = context?.previousSpec;
2165
- let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
2166
- let retriesUsed = 0;
2167
- let currentPrompt = prompt;
2168
- let currentContext = context;
2169
- try {
2170
- while (retriesUsed <= maxRetries) {
2171
- const result = await streamRequest(
2172
- currentPrompt,
2173
- currentContext,
2174
- currentSpec,
2175
- enableValidation
2176
- // only abort on malformed when validation is on
2177
- );
2178
- currentSpec = result.spec;
2179
- if (!result.completed && result.malformedLines.length > 0) {
2180
- if (retriesUsed >= maxRetries) {
2027
+ if (!isVisible) {
2028
+ return null;
2029
+ }
2030
+ const rawProps = element.props;
2031
+ const elementBindings = (0, import_core7.resolveBindings)(rawProps, fullCtx);
2032
+ const resolvedProps = (0, import_core7.resolveElementProps)(rawProps, fullCtx);
2033
+ const resolvedElement = resolvedProps !== element.props ? { ...element, props: resolvedProps } : element;
2034
+ const Component = registry[resolvedElement.type] ?? fallback;
2035
+ if (!Component) {
2036
+ console.warn(`No renderer for component type: ${resolvedElement.type}`);
2037
+ return null;
2038
+ }
2039
+ const children = resolvedElement.repeat ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2040
+ RepeatChildren,
2041
+ {
2042
+ element: resolvedElement,
2043
+ spec,
2044
+ registry,
2045
+ loading,
2046
+ fallback
2047
+ }
2048
+ ) : resolvedElement.children?.map((childKey) => {
2049
+ const childElement = spec.elements[childKey];
2050
+ if (!childElement) {
2051
+ if (!loading) {
2052
+ console.warn(
2053
+ `[json-render] Missing element "${childKey}" referenced as child of "${resolvedElement.type}". This element will not render.`
2054
+ );
2055
+ }
2056
+ return null;
2057
+ }
2058
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2059
+ ElementRenderer2,
2060
+ {
2061
+ element: childElement,
2062
+ spec,
2063
+ registry,
2064
+ loading,
2065
+ fallback
2066
+ },
2067
+ childKey
2068
+ );
2069
+ });
2070
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ElementErrorBoundary, { elementType: resolvedElement.type, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2071
+ Component,
2072
+ {
2073
+ element: resolvedElement,
2074
+ emit,
2075
+ bindings: elementBindings,
2076
+ loading,
2077
+ children
2078
+ }
2079
+ ) });
2080
+ });
2081
+ function RepeatChildren({
2082
+ element,
2083
+ spec,
2084
+ registry,
2085
+ loading,
2086
+ fallback
2087
+ }) {
2088
+ const { state } = useStateStore();
2089
+ const repeat = element.repeat;
2090
+ const statePath = repeat.statePath;
2091
+ const items = (0, import_core7.getByPath)(state, statePath) ?? [];
2092
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: items.map((itemValue, index) => {
2093
+ const key = repeat.key && typeof itemValue === "object" && itemValue !== null ? String(
2094
+ itemValue[repeat.key] ?? index
2095
+ ) : String(index);
2096
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2097
+ RepeatScopeProvider,
2098
+ {
2099
+ item: itemValue,
2100
+ index,
2101
+ basePath: `${statePath}/${index}`,
2102
+ children: element.children?.map((childKey) => {
2103
+ const childElement = spec.elements[childKey];
2104
+ if (!childElement) {
2105
+ if (!loading) {
2181
2106
  console.warn(
2182
- "[useUIStream] malformed lines but max retries exhausted"
2107
+ `[json-render] Missing element "${childKey}" referenced as child of "${element.type}" (repeat). This element will not render.`
2183
2108
  );
2184
- break;
2185
2109
  }
2186
- retriesUsed++;
2187
- console.warn(
2188
- `[useUIStream] mid-stream retry ${retriesUsed}/${maxRetries} due to malformed JSON`
2189
- );
2190
- currentContext = { ...context, previousSpec: currentSpec };
2191
- currentPrompt = `The previous generation contained malformed JSON that could not be parsed. The line was:
2192
- ${result.malformedLines[result.malformedLines.length - 1]?.slice(0, 500)}
2193
-
2194
- The current spec state is provided. Continue generating from where you left off. Output ONLY the remaining patches needed to complete the UI.`;
2195
- continue;
2196
- }
2197
- if (!enableValidation || !currentSpec.root) {
2198
- break;
2199
- }
2200
- const { spec: fixedSpec, fixes } = (0, import_core7.autoFixSpec)(currentSpec);
2201
- if (fixes.length > 0) {
2202
- console.log("[useUIStream] auto-fixed spec issues:", fixes);
2203
- currentSpec = fixedSpec;
2204
- setSpec({ ...currentSpec });
2205
- }
2206
- const validation = (0, import_core7.validateSpec)(currentSpec);
2207
- if (validation.valid) {
2208
- console.log("[useUIStream] spec validation passed");
2209
- break;
2210
- }
2211
- const errors = validation.issues.filter(
2212
- (i) => i.severity === "error"
2213
- );
2214
- if (retriesUsed >= maxRetries) {
2215
- console.warn(
2216
- "[useUIStream] spec validation failed, max retries exhausted:",
2217
- errors.map((e) => e.message)
2218
- );
2219
- break;
2110
+ return null;
2220
2111
  }
2221
- retriesUsed++;
2222
- const issueText = (0, import_core7.formatSpecIssues)(validation.issues);
2223
- console.warn(
2224
- `[useUIStream] post-stream retry ${retriesUsed}/${maxRetries}:`,
2225
- errors.map((e) => e.message)
2112
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2113
+ ElementRenderer,
2114
+ {
2115
+ element: childElement,
2116
+ spec,
2117
+ registry,
2118
+ loading,
2119
+ fallback
2120
+ },
2121
+ childKey
2226
2122
  );
2227
- currentContext = { ...context, previousSpec: currentSpec };
2228
- 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.
2229
-
2230
- ` + issueText;
2231
- }
2232
- onComplete?.(currentSpec);
2233
- } catch (err) {
2234
- if (err.name === "AbortError") {
2235
- return;
2236
- }
2237
- const error2 = err instanceof Error ? err : new Error(String(err));
2238
- setError(error2);
2239
- onError?.(error2);
2240
- } finally {
2241
- setIsStreaming(false);
2242
- }
2243
- },
2244
- [
2245
- api,
2246
- fetchFn,
2247
- onComplete,
2248
- onError,
2249
- enableValidation,
2250
- maxRetries,
2251
- streamRequest
2252
- ]
2123
+ })
2124
+ },
2125
+ key
2126
+ );
2127
+ }) });
2128
+ }
2129
+ function Renderer({
2130
+ spec,
2131
+ registry: customRegistry,
2132
+ includeStandard = true,
2133
+ loading,
2134
+ fallback
2135
+ }) {
2136
+ const registry = (0, import_react7.useMemo)(
2137
+ () => ({
2138
+ ...includeStandard ? standardComponents : {},
2139
+ ...customRegistry
2140
+ }),
2141
+ [customRegistry, includeStandard]
2142
+ );
2143
+ if (!spec || !spec.root) {
2144
+ return null;
2145
+ }
2146
+ const rootElement = spec.elements[spec.root];
2147
+ if (!rootElement) {
2148
+ return null;
2149
+ }
2150
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2151
+ ElementRenderer,
2152
+ {
2153
+ element: rootElement,
2154
+ spec,
2155
+ registry,
2156
+ loading,
2157
+ fallback
2158
+ }
2253
2159
  );
2254
- (0, import_react7.useEffect)(() => {
2255
- return () => {
2256
- abortControllerRef.current?.abort();
2257
- };
2258
- }, []);
2259
- return {
2260
- spec,
2261
- isStreaming,
2262
- error,
2263
- rawLines,
2264
- send,
2265
- stop,
2266
- clear
2267
- };
2268
2160
  }
2269
- function flatToTree(elements) {
2270
- const elementMap = {};
2271
- let root = "";
2272
- for (const element of elements) {
2273
- elementMap[element.key] = {
2274
- type: element.type,
2275
- props: element.props,
2276
- children: [],
2277
- visible: element.visible
2278
- };
2161
+ function JSONUIProvider({
2162
+ registry,
2163
+ initialState,
2164
+ handlers,
2165
+ navigate,
2166
+ validationFunctions,
2167
+ onStateChange,
2168
+ children
2169
+ }) {
2170
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StateProvider, { initialState, onStateChange, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionProvider, { handlers, navigate, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ValidationProvider, { customFunctions: validationFunctions, children: [
2171
+ children,
2172
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmationDialogManager, {})
2173
+ ] }) }) }) });
2174
+ }
2175
+ function ConfirmationDialogManager() {
2176
+ const { pendingConfirmation, confirm, cancel } = useActions();
2177
+ if (!pendingConfirmation?.action.confirm) {
2178
+ return null;
2279
2179
  }
2280
- for (const element of elements) {
2281
- if (element.parentKey) {
2282
- const parent = elementMap[element.parentKey];
2283
- if (parent) {
2284
- if (!parent.children) {
2285
- parent.children = [];
2180
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2181
+ ConfirmDialog,
2182
+ {
2183
+ confirm: pendingConfirmation.action.confirm,
2184
+ onConfirm: confirm,
2185
+ onCancel: cancel
2186
+ }
2187
+ );
2188
+ }
2189
+ function defineRegistry(_catalog, options) {
2190
+ const registry = {};
2191
+ if (options.components) {
2192
+ for (const [name, componentFn] of Object.entries(options.components)) {
2193
+ registry[name] = ({
2194
+ element,
2195
+ children,
2196
+ emit,
2197
+ bindings,
2198
+ loading
2199
+ }) => {
2200
+ return componentFn({
2201
+ props: element.props,
2202
+ children,
2203
+ emit,
2204
+ bindings,
2205
+ loading
2206
+ });
2207
+ };
2208
+ }
2209
+ }
2210
+ const actionMap = options.actions ? Object.entries(options.actions) : [];
2211
+ const handlers = (getSetState, getState) => {
2212
+ const result = {};
2213
+ for (const [name, actionFn] of actionMap) {
2214
+ result[name] = async (params) => {
2215
+ const setState = getSetState();
2216
+ const state = getState();
2217
+ if (setState) {
2218
+ await actionFn(params, setState, state);
2286
2219
  }
2287
- parent.children.push(element.key);
2288
- }
2220
+ };
2221
+ }
2222
+ return result;
2223
+ };
2224
+ const executeAction2 = async (actionName, params, setState, state = {}) => {
2225
+ const entry = actionMap.find(([name]) => name === actionName);
2226
+ if (entry) {
2227
+ await entry[1](params, setState, state);
2289
2228
  } else {
2290
- root = element.key;
2229
+ console.warn(`Unknown action: ${actionName}`);
2291
2230
  }
2292
- }
2293
- return { root, elements: elementMap };
2231
+ };
2232
+ return { registry, handlers, executeAction: executeAction2 };
2233
+ }
2234
+ function createRenderer(catalog, components) {
2235
+ const registry = components;
2236
+ return function CatalogRenderer({
2237
+ spec,
2238
+ state,
2239
+ onAction,
2240
+ onStateChange,
2241
+ loading,
2242
+ fallback
2243
+ }) {
2244
+ const actionHandlers = onAction ? new Proxy(
2245
+ {},
2246
+ {
2247
+ get: (_target, prop) => {
2248
+ return (params) => onAction(prop, params);
2249
+ },
2250
+ has: () => true
2251
+ }
2252
+ ) : void 0;
2253
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StateProvider, { initialState: state, onStateChange, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VisibilityProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionProvider, { handlers: actionHandlers, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ValidationProvider, { children: [
2254
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2255
+ Renderer,
2256
+ {
2257
+ spec,
2258
+ registry,
2259
+ loading,
2260
+ fallback
2261
+ }
2262
+ ),
2263
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmationDialogManager, {})
2264
+ ] }) }) }) });
2265
+ };
2294
2266
  }
2295
2267
 
2296
2268
  // src/catalog.ts
@@ -2310,7 +2282,8 @@ var standardComponentDefinitions = {
2310
2282
  flex: import_zod.z.number().nullable()
2311
2283
  }),
2312
2284
  slots: ["default"],
2313
- description: "Generic container wrapper. Use for grouping elements with padding, margin, and background color."
2285
+ description: "Generic container wrapper. Use for grouping elements with padding, margin, and background color.",
2286
+ example: { padding: 16, backgroundColor: "#FFFFFF" }
2314
2287
  },
2315
2288
  Row: {
2316
2289
  props: import_zod.z.object({
@@ -2329,7 +2302,8 @@ var standardComponentDefinitions = {
2329
2302
  flex: import_zod.z.number().nullable()
2330
2303
  }),
2331
2304
  slots: ["default"],
2332
- description: "Horizontal flex layout. Use for placing elements side by side."
2305
+ description: "Horizontal flex layout. Use for placing elements side by side.",
2306
+ example: { gap: 12, alignItems: "center" }
2333
2307
  },
2334
2308
  Column: {
2335
2309
  props: import_zod.z.object({
@@ -2347,7 +2321,8 @@ var standardComponentDefinitions = {
2347
2321
  flex: import_zod.z.number().nullable()
2348
2322
  }),
2349
2323
  slots: ["default"],
2350
- description: "Vertical flex layout. Use for stacking elements top to bottom."
2324
+ description: "Vertical flex layout. Use for stacking elements top to bottom.",
2325
+ example: { gap: 12, padding: 16 }
2351
2326
  },
2352
2327
  ScrollContainer: {
2353
2328
  props: import_zod.z.object({
@@ -2401,7 +2376,8 @@ var standardComponentDefinitions = {
2401
2376
  align: import_zod.z.enum(["left", "center", "right"]).nullable()
2402
2377
  }),
2403
2378
  slots: [],
2404
- description: "Heading text at various levels. h1 is largest, h4 is smallest."
2379
+ description: "Heading text at various levels. h1 is largest, h4 is smallest.",
2380
+ example: { text: "Welcome", level: "h1" }
2405
2381
  },
2406
2382
  Paragraph: {
2407
2383
  props: import_zod.z.object({
@@ -2412,7 +2388,8 @@ var standardComponentDefinitions = {
2412
2388
  fontSize: import_zod.z.number().nullable()
2413
2389
  }),
2414
2390
  slots: [],
2415
- description: "Body text paragraph. Use for descriptions and longer content."
2391
+ description: "Body text paragraph. Use for descriptions and longer content.",
2392
+ example: { text: "This is a paragraph of body text." }
2416
2393
  },
2417
2394
  Label: {
2418
2395
  props: import_zod.z.object({
@@ -2422,7 +2399,8 @@ var standardComponentDefinitions = {
2422
2399
  size: import_zod.z.enum(["xs", "sm", "md"]).nullable()
2423
2400
  }),
2424
2401
  slots: [],
2425
- description: "Small utility text. Use for captions, form labels, and secondary information."
2402
+ description: "Small utility text. Use for captions, form labels, and secondary information.",
2403
+ example: { text: "Status", size: "sm" }
2426
2404
  },
2427
2405
  Image: {
2428
2406
  props: import_zod.z.object({
@@ -2434,7 +2412,13 @@ var standardComponentDefinitions = {
2434
2412
  borderRadius: import_zod.z.number().nullable()
2435
2413
  }),
2436
2414
  slots: [],
2437
- description: "Image display. Provide a source URL and optional dimensions."
2415
+ description: "Image display. Provide a source URL and optional dimensions.",
2416
+ example: {
2417
+ src: "https://picsum.photos/300/200",
2418
+ alt: "Photo",
2419
+ width: 300,
2420
+ height: 200
2421
+ }
2438
2422
  },
2439
2423
  Avatar: {
2440
2424
  props: import_zod.z.object({
@@ -2444,7 +2428,8 @@ var standardComponentDefinitions = {
2444
2428
  backgroundColor: import_zod.z.string().nullable()
2445
2429
  }),
2446
2430
  slots: [],
2447
- description: "Circular avatar showing an image or initials. Use for user profiles and contacts."
2431
+ description: "Circular avatar showing an image or initials. Use for user profiles and contacts.",
2432
+ example: { initials: "JD", size: "md" }
2448
2433
  },
2449
2434
  Badge: {
2450
2435
  props: import_zod.z.object({
@@ -2452,7 +2437,8 @@ var standardComponentDefinitions = {
2452
2437
  variant: import_zod.z.enum(["default", "info", "success", "warning", "error"]).nullable()
2453
2438
  }),
2454
2439
  slots: [],
2455
- description: "Small colored indicator with a label. Use for status, counts, and categories."
2440
+ description: "Small colored indicator with a label. Use for status, counts, and categories.",
2441
+ example: { label: "New", variant: "info" }
2456
2442
  },
2457
2443
  Chip: {
2458
2444
  props: import_zod.z.object({
@@ -2477,13 +2463,13 @@ var standardComponentDefinitions = {
2477
2463
  }),
2478
2464
  events: ["press"],
2479
2465
  slots: [],
2480
- description: "Pressable button with label. Set variant for styling. Bind on.press for the handler to call on press."
2466
+ description: "Pressable button with label. Set variant for styling. Bind on.press for the handler to call on press.",
2467
+ example: { label: "Submit", variant: "primary" }
2481
2468
  },
2482
2469
  TextInput: {
2483
2470
  props: import_zod.z.object({
2484
2471
  placeholder: import_zod.z.string().nullable(),
2485
2472
  value: import_zod.z.string().nullable(),
2486
- statePath: import_zod.z.string().nullable(),
2487
2473
  secureTextEntry: import_zod.z.boolean().nullable(),
2488
2474
  keyboardType: import_zod.z.enum(["default", "email-address", "numeric", "phone-pad", "url"]).nullable(),
2489
2475
  multiline: import_zod.z.boolean().nullable(),
@@ -2493,29 +2479,31 @@ var standardComponentDefinitions = {
2493
2479
  }),
2494
2480
  events: ["submit", "focus", "blur"],
2495
2481
  slots: [],
2496
- description: "Text input field. Use statePath to bind to the state model for two-way binding. The value typed by the user is stored at the statePath."
2482
+ description: "Text input field. Use $bindState to bind to the state model for two-way binding. The value typed by the user is stored at the bound path.",
2483
+ example: {
2484
+ placeholder: "Enter text...",
2485
+ label: "Name"
2486
+ }
2497
2487
  },
2498
2488
  Switch: {
2499
2489
  props: import_zod.z.object({
2500
- value: import_zod.z.boolean().nullable(),
2501
- statePath: import_zod.z.string().nullable(),
2490
+ checked: import_zod.z.boolean().nullable(),
2502
2491
  label: import_zod.z.string().nullable(),
2503
2492
  disabled: import_zod.z.boolean().nullable()
2504
2493
  }),
2505
2494
  events: ["change"],
2506
2495
  slots: [],
2507
- description: "Toggle switch. Use statePath to bind to the state model."
2496
+ description: "Toggle switch. Use $bindState to bind to the state model."
2508
2497
  },
2509
2498
  Checkbox: {
2510
2499
  props: import_zod.z.object({
2511
2500
  checked: import_zod.z.boolean().nullable(),
2512
- statePath: import_zod.z.string().nullable(),
2513
2501
  label: import_zod.z.string().nullable(),
2514
2502
  disabled: import_zod.z.boolean().nullable()
2515
2503
  }),
2516
2504
  events: ["change"],
2517
2505
  slots: [],
2518
- description: "Checkbox for boolean selections. Use statePath to bind to the state model."
2506
+ description: "Checkbox for boolean selections. Use $bindState to bind to the state model."
2519
2507
  },
2520
2508
  Slider: {
2521
2509
  props: import_zod.z.object({
@@ -2523,18 +2511,16 @@ var standardComponentDefinitions = {
2523
2511
  max: import_zod.z.number().nullable(),
2524
2512
  step: import_zod.z.number().nullable(),
2525
2513
  value: import_zod.z.number().nullable(),
2526
- statePath: import_zod.z.string().nullable(),
2527
2514
  label: import_zod.z.string().nullable(),
2528
2515
  color: import_zod.z.string().nullable()
2529
2516
  }),
2530
2517
  slots: [],
2531
- description: "Range slider for numeric values. Use statePath to bind to the state model."
2518
+ description: "Range slider for numeric values. Use $bindState to bind to the state model."
2532
2519
  },
2533
2520
  SearchBar: {
2534
2521
  props: import_zod.z.object({
2535
2522
  placeholder: import_zod.z.string().nullable(),
2536
- value: import_zod.z.string().nullable(),
2537
- statePath: import_zod.z.string().nullable()
2523
+ value: import_zod.z.string().nullable()
2538
2524
  }),
2539
2525
  events: ["submit"],
2540
2526
  slots: [],
@@ -2574,7 +2560,8 @@ var standardComponentDefinitions = {
2574
2560
  elevated: import_zod.z.boolean().nullable()
2575
2561
  }),
2576
2562
  slots: ["default"],
2577
- description: "Elevated card container with optional title. Use for grouping related content."
2563
+ description: "Elevated card container with optional title. Use for grouping related content.",
2564
+ example: { title: "Details", padding: 16, elevated: true }
2578
2565
  },
2579
2566
  ListItem: {
2580
2567
  props: import_zod.z.object({
@@ -2586,17 +2573,21 @@ var standardComponentDefinitions = {
2586
2573
  }),
2587
2574
  events: ["press"],
2588
2575
  slots: [],
2589
- description: "List row with title, subtitle, and optional leading/trailing text. Bind on.press for the press handler."
2576
+ description: "List row with title, subtitle, and optional leading/trailing text. Bind on.press for the press handler.",
2577
+ example: {
2578
+ title: "Settings",
2579
+ subtitle: "Manage your preferences",
2580
+ showChevron: true
2581
+ }
2590
2582
  },
2591
2583
  Modal: {
2592
2584
  props: import_zod.z.object({
2593
2585
  visible: import_zod.z.boolean(),
2594
2586
  title: import_zod.z.string().nullable(),
2595
- animationType: import_zod.z.enum(["slide", "fade", "none"]).nullable(),
2596
- statePath: import_zod.z.string().nullable()
2587
+ animationType: import_zod.z.enum(["slide", "fade", "none"]).nullable()
2597
2588
  }),
2598
2589
  slots: ["default"],
2599
- description: "Modal overlay dialog. Use statePath to bind visibility to the state model."
2590
+ description: "Modal overlay dialog. Use $bindState on visible to bind visibility to the state model."
2600
2591
  }
2601
2592
  };
2602
2593
  var standardActionDefinitions = {
@@ -2641,22 +2632,22 @@ var standardActionDefinitions = {
2641
2632
  },
2642
2633
  setState: {
2643
2634
  params: import_zod.z.object({
2644
- path: import_zod.z.string(),
2635
+ statePath: import_zod.z.string(),
2645
2636
  value: import_zod.z.unknown()
2646
2637
  }),
2647
- description: "Update a value in the state model at the given path."
2638
+ description: "Update a value in the state model at the given statePath."
2648
2639
  },
2649
2640
  pushState: {
2650
2641
  params: import_zod.z.object({
2651
- path: import_zod.z.string(),
2642
+ statePath: import_zod.z.string(),
2652
2643
  value: import_zod.z.unknown(),
2653
- clearPath: import_zod.z.string().optional()
2644
+ clearStatePath: import_zod.z.string().optional()
2654
2645
  }),
2655
- description: 'Append an item to an array in the state model. The value can contain { path: "/statePath" } references to read from current state, and "$id" to auto-generate a unique ID. Use clearPath to reset another path after pushing (e.g. clear an input field). Example: { path: "/todos", value: { id: "$id", title: { path: "/newTodoText" }, completed: false }, clearPath: "/newTodoText" }.'
2646
+ description: 'Append an item to an array in the state model. The value can contain { $state: "/statePath" } references to read from current state, and "$id" to auto-generate a unique ID. Use clearStatePath to reset another path after pushing (e.g. clear an input field). Example: { statePath: "/todos", value: { id: "$id", title: { $state: "/newTodoText" }, completed: false }, clearStatePath: "/newTodoText" }.'
2656
2647
  },
2657
2648
  removeState: {
2658
2649
  params: import_zod.z.object({
2659
- path: import_zod.z.string(),
2650
+ statePath: import_zod.z.string(),
2660
2651
  index: import_zod.z.number()
2661
2652
  }),
2662
2653
  description: "Remove an item from an array in the state model at the given index."
@@ -2679,18 +2670,17 @@ var standardActionDefinitions = {
2679
2670
  ValidationProvider,
2680
2671
  VisibilityProvider,
2681
2672
  createRenderer,
2682
- createRendererFromCatalog,
2683
2673
  createStandardActionHandlers,
2684
2674
  defineRegistry,
2685
2675
  elementTreeSchema,
2686
2676
  flatToTree,
2687
- rewriteRepeatTokens,
2688
2677
  schema,
2689
2678
  standardActionDefinitions,
2690
2679
  standardComponentDefinitions,
2691
2680
  standardComponents,
2692
2681
  useAction,
2693
2682
  useActions,
2683
+ useBoundProp,
2694
2684
  useFieldValidation,
2695
2685
  useIsVisible,
2696
2686
  useRepeatScope,