@json-render/react-native 0.4.3

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 ADDED
@@ -0,0 +1,2682 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ActionProvider: () => ActionProvider,
34
+ ConfirmDialog: () => ConfirmDialog,
35
+ JSONUIProvider: () => JSONUIProvider,
36
+ Renderer: () => Renderer,
37
+ RepeatScopeProvider: () => RepeatScopeProvider,
38
+ StateProvider: () => StateProvider,
39
+ ValidationProvider: () => ValidationProvider,
40
+ VisibilityProvider: () => VisibilityProvider,
41
+ createRenderer: () => createRenderer,
42
+ createRendererFromCatalog: () => createRendererFromCatalog,
43
+ createStandardActionHandlers: () => createStandardActionHandlers,
44
+ defineRegistry: () => defineRegistry,
45
+ elementTreeSchema: () => elementTreeSchema,
46
+ flatToTree: () => flatToTree,
47
+ rewriteRepeatTokens: () => rewriteRepeatTokens,
48
+ schema: () => schema,
49
+ standardActionDefinitions: () => standardActionDefinitions,
50
+ standardComponentDefinitions: () => standardComponentDefinitions,
51
+ standardComponents: () => standardComponents,
52
+ useAction: () => useAction,
53
+ useActions: () => useActions,
54
+ useFieldValidation: () => useFieldValidation,
55
+ useIsVisible: () => useIsVisible,
56
+ useRepeatScope: () => useRepeatScope,
57
+ useStateBinding: () => useStateBinding,
58
+ useStateStore: () => useStateStore,
59
+ useStateValue: () => useStateValue,
60
+ useUIStream: () => useUIStream,
61
+ useValidation: () => useValidation,
62
+ useVisibility: () => useVisibility
63
+ });
64
+ module.exports = __toCommonJS(index_exports);
65
+
66
+ // src/contexts/state.tsx
67
+ var import_react = require("react");
68
+ var import_core = require("@json-render/core");
69
+ var import_jsx_runtime = require("react/jsx-runtime");
70
+ var StateContext = (0, import_react.createContext)(null);
71
+ function StateProvider({
72
+ initialState = {},
73
+ authState,
74
+ onStateChange,
75
+ children
76
+ }) {
77
+ const [state, setStateInternal] = (0, import_react.useState)(initialState);
78
+ const initialStateJsonRef = (0, import_react.useRef)(JSON.stringify(initialState));
79
+ (0, import_react.useEffect)(() => {
80
+ const newJson = JSON.stringify(initialState);
81
+ if (newJson !== initialStateJsonRef.current) {
82
+ initialStateJsonRef.current = newJson;
83
+ if (initialState && Object.keys(initialState).length > 0) {
84
+ setStateInternal((prev) => ({ ...prev, ...initialState }));
85
+ }
86
+ }
87
+ }, [initialState]);
88
+ const get = (0, import_react.useCallback)((path) => (0, import_core.getByPath)(state, path), [state]);
89
+ const set = (0, import_react.useCallback)(
90
+ (path, value2) => {
91
+ setStateInternal((prev) => {
92
+ const next = { ...prev };
93
+ (0, import_core.setByPath)(next, path, value2);
94
+ return next;
95
+ });
96
+ onStateChange?.(path, value2);
97
+ },
98
+ [onStateChange]
99
+ );
100
+ const update = (0, import_react.useCallback)(
101
+ (updates) => {
102
+ setStateInternal((prev) => {
103
+ const next = { ...prev };
104
+ for (const [path, value2] of Object.entries(updates)) {
105
+ (0, import_core.setByPath)(next, path, value2);
106
+ onStateChange?.(path, value2);
107
+ }
108
+ return next;
109
+ });
110
+ },
111
+ [onStateChange]
112
+ );
113
+ const value = (0, import_react.useMemo)(
114
+ () => ({
115
+ state,
116
+ authState,
117
+ get,
118
+ set,
119
+ update
120
+ }),
121
+ [state, authState, get, set, update]
122
+ );
123
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StateContext.Provider, { value, children });
124
+ }
125
+ function useStateStore() {
126
+ const ctx = (0, import_react.useContext)(StateContext);
127
+ if (!ctx) {
128
+ throw new Error("useStateStore must be used within a StateProvider");
129
+ }
130
+ return ctx;
131
+ }
132
+ function useStateValue(path) {
133
+ const { state } = useStateStore();
134
+ return (0, import_core.getByPath)(state, path);
135
+ }
136
+ function useStateBinding(path) {
137
+ const { state, set } = useStateStore();
138
+ const value = (0, import_core.getByPath)(state, path);
139
+ const setValue = (0, import_react.useCallback)(
140
+ (newValue) => set(path, newValue),
141
+ [path, set]
142
+ );
143
+ return [value, setValue];
144
+ }
145
+
146
+ // src/contexts/visibility.tsx
147
+ var import_react2 = require("react");
148
+ var import_core2 = require("@json-render/core");
149
+ var import_jsx_runtime2 = require("react/jsx-runtime");
150
+ var VisibilityContext = (0, import_react2.createContext)(null);
151
+ function VisibilityProvider({ children }) {
152
+ const { state, authState } = useStateStore();
153
+ const ctx = (0, import_react2.useMemo)(
154
+ () => ({
155
+ stateModel: state,
156
+ authState
157
+ }),
158
+ [state, authState]
159
+ );
160
+ const isVisible = (0, import_react2.useMemo)(
161
+ () => (condition) => (0, import_core2.evaluateVisibility)(condition, ctx),
162
+ [ctx]
163
+ );
164
+ const value = (0, import_react2.useMemo)(
165
+ () => ({ isVisible, ctx }),
166
+ [isVisible, ctx]
167
+ );
168
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VisibilityContext.Provider, { value, children });
169
+ }
170
+ function useVisibility() {
171
+ const ctx = (0, import_react2.useContext)(VisibilityContext);
172
+ if (!ctx) {
173
+ throw new Error("useVisibility must be used within a VisibilityProvider");
174
+ }
175
+ return ctx;
176
+ }
177
+ function useIsVisible(condition) {
178
+ const { isVisible } = useVisibility();
179
+ return isVisible(condition);
180
+ }
181
+
182
+ // src/contexts/actions.tsx
183
+ var import_react3 = require("react");
184
+ var import_react_native = require("react-native");
185
+ var import_core3 = require("@json-render/core");
186
+ var import_jsx_runtime3 = require("react/jsx-runtime");
187
+ var idCounter = 0;
188
+ function generateUniqueId() {
189
+ idCounter += 1;
190
+ return `${Date.now()}-${idCounter}`;
191
+ }
192
+ function deepResolveValue(value, get) {
193
+ if (value === null || value === void 0) return value;
194
+ if (value === "$id") {
195
+ return generateUniqueId();
196
+ }
197
+ if (typeof value === "object" && !Array.isArray(value)) {
198
+ const obj = value;
199
+ const keys = Object.keys(obj);
200
+ if (keys.length === 1 && typeof obj.path === "string") {
201
+ return get(obj.path);
202
+ }
203
+ if (keys.length === 1 && "$id" in obj) {
204
+ return generateUniqueId();
205
+ }
206
+ }
207
+ if (Array.isArray(value)) {
208
+ return value.map((item) => deepResolveValue(item, get));
209
+ }
210
+ if (typeof value === "object") {
211
+ const resolved = {};
212
+ for (const [key, val] of Object.entries(value)) {
213
+ resolved[key] = deepResolveValue(val, get);
214
+ }
215
+ return resolved;
216
+ }
217
+ return value;
218
+ }
219
+ var ActionContext = (0, import_react3.createContext)(null);
220
+ function ActionProvider({
221
+ handlers: initialHandlers = {},
222
+ navigate,
223
+ children
224
+ }) {
225
+ const { state, get, set } = useStateStore();
226
+ const [handlers, setHandlers] = (0, import_react3.useState)(initialHandlers);
227
+ const [loadingActions, setLoadingActions] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
228
+ const [pendingConfirmation, setPendingConfirmation] = (0, import_react3.useState)(null);
229
+ const registerHandler = (0, import_react3.useCallback)(
230
+ (name, handler) => {
231
+ setHandlers((prev) => ({ ...prev, [name]: handler }));
232
+ },
233
+ []
234
+ );
235
+ const execute = (0, import_react3.useCallback)(
236
+ async (binding) => {
237
+ const resolved = (0, import_core3.resolveAction)(binding, state);
238
+ if (resolved.action === "setState" && resolved.params) {
239
+ const path = resolved.params.path;
240
+ const value2 = resolved.params.value;
241
+ if (path) {
242
+ set(path, value2);
243
+ }
244
+ return;
245
+ }
246
+ if (resolved.action === "pushState" && resolved.params) {
247
+ const path = resolved.params.path;
248
+ const rawValue = resolved.params.value;
249
+ if (path) {
250
+ 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, "");
256
+ }
257
+ }
258
+ return;
259
+ }
260
+ if (resolved.action === "removeState" && resolved.params) {
261
+ const path = resolved.params.path;
262
+ const index = resolved.params.index;
263
+ if (path !== void 0 && index !== void 0) {
264
+ const arr = get(path) ?? [];
265
+ set(
266
+ path,
267
+ arr.filter((_, i) => i !== index)
268
+ );
269
+ }
270
+ return;
271
+ }
272
+ if (resolved.action === "push" && resolved.params) {
273
+ const screen = resolved.params.screen;
274
+ if (screen) {
275
+ const currentScreen = get("/currentScreen");
276
+ const navStack = get("/navStack") ?? [];
277
+ if (currentScreen) {
278
+ set("/navStack", [...navStack, currentScreen]);
279
+ } else {
280
+ set("/navStack", [...navStack, ""]);
281
+ }
282
+ set("/currentScreen", screen);
283
+ }
284
+ return;
285
+ }
286
+ if (resolved.action === "pop") {
287
+ const navStack = get("/navStack") ?? [];
288
+ if (navStack.length > 0) {
289
+ const previousScreen = navStack[navStack.length - 1];
290
+ set("/navStack", navStack.slice(0, -1));
291
+ if (previousScreen) {
292
+ set("/currentScreen", previousScreen);
293
+ } else {
294
+ set("/currentScreen", void 0);
295
+ }
296
+ }
297
+ return;
298
+ }
299
+ const handler = handlers[resolved.action];
300
+ if (!handler) {
301
+ console.warn(`No handler registered for action: ${resolved.action}`);
302
+ return;
303
+ }
304
+ if (resolved.confirm) {
305
+ return new Promise((resolve, reject) => {
306
+ setPendingConfirmation({
307
+ action: resolved,
308
+ handler,
309
+ resolve: () => {
310
+ setPendingConfirmation(null);
311
+ resolve();
312
+ },
313
+ reject: () => {
314
+ setPendingConfirmation(null);
315
+ reject(new Error("Action cancelled"));
316
+ }
317
+ });
318
+ }).then(async () => {
319
+ setLoadingActions((prev) => new Set(prev).add(resolved.action));
320
+ try {
321
+ await (0, import_core3.executeAction)({
322
+ action: resolved,
323
+ handler,
324
+ setState: set,
325
+ navigate,
326
+ executeAction: async (name) => {
327
+ const subBinding = { action: name };
328
+ await execute(subBinding);
329
+ }
330
+ });
331
+ } finally {
332
+ setLoadingActions((prev) => {
333
+ const next = new Set(prev);
334
+ next.delete(resolved.action);
335
+ return next;
336
+ });
337
+ }
338
+ });
339
+ }
340
+ setLoadingActions((prev) => new Set(prev).add(resolved.action));
341
+ try {
342
+ await (0, import_core3.executeAction)({
343
+ action: resolved,
344
+ handler,
345
+ setState: set,
346
+ navigate,
347
+ executeAction: async (name) => {
348
+ const subBinding = { action: name };
349
+ await execute(subBinding);
350
+ }
351
+ });
352
+ } finally {
353
+ setLoadingActions((prev) => {
354
+ const next = new Set(prev);
355
+ next.delete(resolved.action);
356
+ return next;
357
+ });
358
+ }
359
+ },
360
+ [state, handlers, get, set, navigate]
361
+ );
362
+ const confirm = (0, import_react3.useCallback)(() => {
363
+ pendingConfirmation?.resolve();
364
+ }, [pendingConfirmation]);
365
+ const cancel = (0, import_react3.useCallback)(() => {
366
+ pendingConfirmation?.reject();
367
+ }, [pendingConfirmation]);
368
+ const value = (0, import_react3.useMemo)(
369
+ () => ({
370
+ handlers,
371
+ loadingActions,
372
+ pendingConfirmation,
373
+ execute,
374
+ confirm,
375
+ cancel,
376
+ registerHandler
377
+ }),
378
+ [
379
+ handlers,
380
+ loadingActions,
381
+ pendingConfirmation,
382
+ execute,
383
+ confirm,
384
+ cancel,
385
+ registerHandler
386
+ ]
387
+ );
388
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionContext.Provider, { value, children });
389
+ }
390
+ function useActions() {
391
+ const ctx = (0, import_react3.useContext)(ActionContext);
392
+ if (!ctx) {
393
+ throw new Error("useActions must be used within an ActionProvider");
394
+ }
395
+ return ctx;
396
+ }
397
+ function useAction(binding) {
398
+ const { execute, loadingActions } = useActions();
399
+ const isLoading = loadingActions.has(binding.action);
400
+ const executeAction2 = (0, import_react3.useCallback)(() => execute(binding), [execute, binding]);
401
+ return { execute: executeAction2, isLoading };
402
+ }
403
+ function ConfirmDialog({
404
+ confirm,
405
+ onConfirm,
406
+ onCancel
407
+ }) {
408
+ const isDanger = confirm.variant === "danger";
409
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Modal, { transparent: true, animationType: "fade", visible: true, onRequestClose: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Pressable, { style: styles.overlay, onPress: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native.Pressable, { style: styles.dialog, onPress: () => {
410
+ }, children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Text, { style: styles.title, children: confirm.title }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Text, { style: styles.message, children: confirm.message }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native.View, { style: styles.buttons, children: [
414
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Pressable, { style: styles.cancelButton, onPress: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Text, { style: styles.cancelButtonText, children: confirm.cancelLabel ?? "Cancel" }) }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
416
+ import_react_native.Pressable,
417
+ {
418
+ style: [
419
+ styles.confirmButton,
420
+ isDanger && styles.confirmButtonDanger
421
+ ],
422
+ onPress: onConfirm,
423
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native.Text, { style: styles.confirmButtonText, children: confirm.confirmLabel ?? "Confirm" })
424
+ }
425
+ )
426
+ ] })
427
+ ] }) }) });
428
+ }
429
+ var styles = import_react_native.StyleSheet.create({
430
+ overlay: {
431
+ flex: 1,
432
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
433
+ alignItems: "center",
434
+ justifyContent: "center"
435
+ },
436
+ dialog: {
437
+ backgroundColor: "white",
438
+ borderRadius: 8,
439
+ padding: 24,
440
+ maxWidth: 400,
441
+ width: "90%",
442
+ shadowColor: "#000",
443
+ shadowOffset: { width: 0, height: 20 },
444
+ shadowOpacity: 0.1,
445
+ shadowRadius: 25,
446
+ elevation: 10
447
+ },
448
+ title: {
449
+ fontSize: 18,
450
+ fontWeight: "600",
451
+ marginBottom: 8
452
+ },
453
+ message: {
454
+ fontSize: 14,
455
+ color: "#6b7280",
456
+ marginBottom: 24
457
+ },
458
+ buttons: {
459
+ flexDirection: "row",
460
+ justifyContent: "flex-end",
461
+ gap: 12
462
+ },
463
+ cancelButton: {
464
+ paddingVertical: 8,
465
+ paddingHorizontal: 16,
466
+ borderRadius: 6,
467
+ borderWidth: 1,
468
+ borderColor: "#d1d5db",
469
+ backgroundColor: "white"
470
+ },
471
+ cancelButtonText: {
472
+ fontSize: 14,
473
+ color: "#374151"
474
+ },
475
+ confirmButton: {
476
+ paddingVertical: 8,
477
+ paddingHorizontal: 16,
478
+ borderRadius: 6,
479
+ backgroundColor: "#3b82f6"
480
+ },
481
+ confirmButtonDanger: {
482
+ backgroundColor: "#dc2626"
483
+ },
484
+ confirmButtonText: {
485
+ fontSize: 14,
486
+ color: "white"
487
+ }
488
+ });
489
+
490
+ // src/contexts/validation.tsx
491
+ var import_react4 = __toESM(require("react"));
492
+ var import_core4 = require("@json-render/core");
493
+ var import_jsx_runtime4 = require("react/jsx-runtime");
494
+ var ValidationContext = (0, import_react4.createContext)(null);
495
+ function ValidationProvider({
496
+ customFunctions = {},
497
+ children
498
+ }) {
499
+ const { state, authState } = useStateStore();
500
+ const [fieldStates, setFieldStates] = (0, import_react4.useState)({});
501
+ const [fieldConfigs, setFieldConfigs] = (0, import_react4.useState)({});
502
+ const registerField = (0, import_react4.useCallback)(
503
+ (path, config) => {
504
+ setFieldConfigs((prev) => ({ ...prev, [path]: config }));
505
+ },
506
+ []
507
+ );
508
+ const validate = (0, import_react4.useCallback)(
509
+ (path, config) => {
510
+ const value2 = state[path.split("/").filter(Boolean).join(".")];
511
+ const result = (0, import_core4.runValidation)(config, {
512
+ value: value2,
513
+ stateModel: state,
514
+ customFunctions,
515
+ authState
516
+ });
517
+ setFieldStates((prev) => ({
518
+ ...prev,
519
+ [path]: {
520
+ touched: prev[path]?.touched ?? true,
521
+ validated: true,
522
+ result
523
+ }
524
+ }));
525
+ return result;
526
+ },
527
+ [state, customFunctions, authState]
528
+ );
529
+ const touch = (0, import_react4.useCallback)((path) => {
530
+ setFieldStates((prev) => ({
531
+ ...prev,
532
+ [path]: {
533
+ ...prev[path],
534
+ touched: true,
535
+ validated: prev[path]?.validated ?? false,
536
+ result: prev[path]?.result ?? null
537
+ }
538
+ }));
539
+ }, []);
540
+ const clear = (0, import_react4.useCallback)((path) => {
541
+ setFieldStates((prev) => {
542
+ const { [path]: _, ...rest } = prev;
543
+ return rest;
544
+ });
545
+ }, []);
546
+ const validateAll = (0, import_react4.useCallback)(() => {
547
+ let allValid = true;
548
+ for (const [path, config] of Object.entries(fieldConfigs)) {
549
+ const result = validate(path, config);
550
+ if (!result.valid) {
551
+ allValid = false;
552
+ }
553
+ }
554
+ return allValid;
555
+ }, [fieldConfigs, validate]);
556
+ const value = (0, import_react4.useMemo)(
557
+ () => ({
558
+ customFunctions,
559
+ fieldStates,
560
+ validate,
561
+ touch,
562
+ clear,
563
+ validateAll,
564
+ registerField
565
+ }),
566
+ [
567
+ customFunctions,
568
+ fieldStates,
569
+ validate,
570
+ touch,
571
+ clear,
572
+ validateAll,
573
+ registerField
574
+ ]
575
+ );
576
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ValidationContext.Provider, { value, children });
577
+ }
578
+ function useValidation() {
579
+ const ctx = (0, import_react4.useContext)(ValidationContext);
580
+ if (!ctx) {
581
+ throw new Error("useValidation must be used within a ValidationProvider");
582
+ }
583
+ return ctx;
584
+ }
585
+ function useFieldValidation(path, config) {
586
+ const {
587
+ fieldStates,
588
+ validate: validateField,
589
+ touch: touchField,
590
+ clear: clearField,
591
+ registerField
592
+ } = useValidation();
593
+ import_react4.default.useEffect(() => {
594
+ if (config) {
595
+ registerField(path, config);
596
+ }
597
+ }, [path, config, registerField]);
598
+ const state = fieldStates[path] ?? {
599
+ touched: false,
600
+ validated: false,
601
+ result: null
602
+ };
603
+ const validate = (0, import_react4.useCallback)(
604
+ () => validateField(path, config ?? { checks: [] }),
605
+ [path, config, validateField]
606
+ );
607
+ const touch = (0, import_react4.useCallback)(() => touchField(path), [path, touchField]);
608
+ const clear = (0, import_react4.useCallback)(() => clearField(path), [path, clearField]);
609
+ return {
610
+ state,
611
+ validate,
612
+ touch,
613
+ clear,
614
+ errors: state.result?.errors ?? [],
615
+ isValid: state.result?.valid ?? true
616
+ };
617
+ }
618
+
619
+ // src/contexts/repeat-scope.tsx
620
+ var import_react5 = require("react");
621
+ var import_jsx_runtime5 = require("react/jsx-runtime");
622
+ var RepeatScopeContext = (0, import_react5.createContext)(null);
623
+ function RepeatScopeProvider({
624
+ basePath,
625
+ index,
626
+ children
627
+ }) {
628
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RepeatScopeContext.Provider, { value: { basePath, index }, children });
629
+ }
630
+ function useRepeatScope() {
631
+ return (0, import_react5.useContext)(RepeatScopeContext);
632
+ }
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
+
663
+ // src/schema.ts
664
+ var import_core5 = require("@json-render/core");
665
+ var schema = (0, import_core5.defineSchema)(
666
+ (s) => ({
667
+ // What the AI-generated SPEC looks like
668
+ spec: s.object({
669
+ /** Root element key */
670
+ root: s.string(),
671
+ /** Flat map of elements by key */
672
+ elements: s.record(
673
+ s.object({
674
+ /** Component type from catalog */
675
+ type: s.ref("catalog.components"),
676
+ /** Component props */
677
+ props: s.propsOf("catalog.components"),
678
+ /** Child element keys (flat reference) */
679
+ children: s.array(s.string()),
680
+ /** Visibility condition */
681
+ visible: s.any()
682
+ })
683
+ )
684
+ }),
685
+ // What the CATALOG must provide
686
+ catalog: s.object({
687
+ /** Component definitions */
688
+ components: s.map({
689
+ /** Zod schema for component props */
690
+ props: s.zod(),
691
+ /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
692
+ slots: s.array(s.string()),
693
+ /** Description for AI generation hints */
694
+ description: s.string()
695
+ }),
696
+ /** Action definitions (optional) */
697
+ actions: s.map({
698
+ /** Zod schema for action params */
699
+ params: s.zod(),
700
+ /** Description for AI generation hints */
701
+ description: s.string()
702
+ })
703
+ })
704
+ }),
705
+ {
706
+ defaultRules: [
707
+ // 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.",
710
+ // Element integrity
711
+ "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
+ "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.',
714
+ // 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":[...]}.',
716
+ // 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"] }.`,
720
+ "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
+ // 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" }',
725
+ " - 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.',
727
+ // 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" } }] }.',
730
+ "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").',
732
+ "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".'
734
+ ]
735
+ }
736
+ );
737
+ var elementTreeSchema = schema;
738
+
739
+ // src/renderer.tsx
740
+ var import_react6 = require("react");
741
+ var import_core6 = require("@json-render/core");
742
+
743
+ // src/components/standard.tsx
744
+ 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
+ }
780
+ );
781
+ }
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
+ );
798
+ }
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
815
+ }
816
+ );
817
+ }
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
828
+ }
829
+ );
830
+ }
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
840
+ }
841
+ }
842
+ );
843
+ }
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
+ );
856
+ }
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
+ }
872
+ }
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;
883
+ const level = p.level ?? "h2";
884
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
885
+ import_react_native2.Text,
886
+ {
887
+ style: {
888
+ fontSize: headingSizes[level],
889
+ fontWeight: "700",
890
+ color: p.color ?? "#111827",
891
+ textAlign: p.align ?? "left"
892
+ },
893
+ children: p.text
894
+ }
895
+ );
896
+ }
897
+ function ParagraphComponent({ element }) {
898
+ const p = element.props;
899
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
900
+ import_react_native2.Text,
901
+ {
902
+ numberOfLines: p.numberOfLines ?? void 0,
903
+ style: {
904
+ fontSize: p.fontSize ?? 16,
905
+ lineHeight: (p.fontSize ?? 16) * 1.5,
906
+ color: p.color ?? "#374151",
907
+ textAlign: p.align ?? "left"
908
+ },
909
+ children: p.text
910
+ }
911
+ );
912
+ }
913
+ var labelSizes = {
914
+ xs: 10,
915
+ sm: 12,
916
+ md: 14
917
+ };
918
+ function LabelComponent({ element }) {
919
+ const p = element.props;
920
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
921
+ import_react_native2.Text,
922
+ {
923
+ style: {
924
+ fontSize: labelSizes[p.size ?? "sm"],
925
+ fontWeight: p.bold ? "600" : "400",
926
+ color: p.color ?? "#6b7280"
927
+ },
928
+ children: p.text
929
+ }
930
+ );
931
+ }
932
+ function ImageComponent({ element }) {
933
+ const p = element.props;
934
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
935
+ import_react_native2.Image,
936
+ {
937
+ source: { uri: p.src },
938
+ accessibilityLabel: p.alt ?? void 0,
939
+ resizeMode: p.resizeMode ?? "cover",
940
+ style: {
941
+ width: p.width ?? "100%",
942
+ height: p.height ?? 200,
943
+ borderRadius: p.borderRadius ?? 0
944
+ }
945
+ }
946
+ );
947
+ }
948
+ var avatarSizeMap = {
949
+ sm: 32,
950
+ md: 48,
951
+ lg: 64,
952
+ xl: 96
953
+ };
954
+ function AvatarComponent({ element }) {
955
+ const p = element.props;
956
+ const size = avatarSizeMap[p.size ?? "md"];
957
+ const bgColor = p.backgroundColor ?? "#6366f1";
958
+ if (p.src) {
959
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
960
+ import_react_native2.Image,
961
+ {
962
+ source: { uri: p.src },
963
+ style: {
964
+ width: size,
965
+ height: size,
966
+ borderRadius: size / 2,
967
+ backgroundColor: "#e5e7eb"
968
+ }
969
+ }
970
+ );
971
+ }
972
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
973
+ import_react_native2.View,
974
+ {
975
+ style: {
976
+ width: size,
977
+ height: size,
978
+ borderRadius: size / 2,
979
+ backgroundColor: bgColor,
980
+ alignItems: "center",
981
+ justifyContent: "center"
982
+ },
983
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
984
+ import_react_native2.Text,
985
+ {
986
+ style: {
987
+ color: "#ffffff",
988
+ fontSize: size * 0.4,
989
+ fontWeight: "600"
990
+ },
991
+ children: p.initials ?? "?"
992
+ }
993
+ )
994
+ }
995
+ );
996
+ }
997
+ var badgeVariantColors = {
998
+ default: { bg: "#e5e7eb", text: "#374151" },
999
+ info: { bg: "#dbeafe", text: "#1d4ed8" },
1000
+ success: { bg: "#dcfce7", text: "#15803d" },
1001
+ warning: { bg: "#fef3c7", text: "#92400e" },
1002
+ error: { bg: "#fee2e2", text: "#dc2626" }
1003
+ };
1004
+ function BadgeComponent({ element }) {
1005
+ const p = element.props;
1006
+ const colors = badgeVariantColors[p.variant ?? "default"] ?? {
1007
+ bg: "#e5e7eb",
1008
+ text: "#374151"
1009
+ };
1010
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1011
+ import_react_native2.View,
1012
+ {
1013
+ style: {
1014
+ backgroundColor: colors.bg,
1015
+ paddingHorizontal: 8,
1016
+ paddingVertical: 2,
1017
+ borderRadius: 9999,
1018
+ alignSelf: "flex-start"
1019
+ },
1020
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1021
+ import_react_native2.Text,
1022
+ {
1023
+ style: {
1024
+ fontSize: 12,
1025
+ fontWeight: "600",
1026
+ color: colors.text
1027
+ },
1028
+ children: p.label
1029
+ }
1030
+ )
1031
+ }
1032
+ );
1033
+ }
1034
+ function ChipComponent({ element, emit }) {
1035
+ const p = element.props;
1036
+ const bgColor = p.selected ? "#6366f1" : p.backgroundColor ?? "#f3f4f6";
1037
+ const textColor = p.selected ? "#ffffff" : "#374151";
1038
+ const hasRemove = !!element.on?.remove;
1039
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1040
+ import_react_native2.Pressable,
1041
+ {
1042
+ onPress: () => emit?.("press"),
1043
+ style: {
1044
+ flexDirection: "row",
1045
+ alignItems: "center",
1046
+ backgroundColor: bgColor,
1047
+ paddingHorizontal: 12,
1048
+ paddingVertical: 6,
1049
+ borderRadius: 9999,
1050
+ alignSelf: "flex-start"
1051
+ },
1052
+ children: [
1053
+ /* @__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" }) })
1055
+ ]
1056
+ }
1057
+ );
1058
+ }
1059
+ var buttonVariantStyles = {
1060
+ primary: { bg: "#3b82f6", text: "#ffffff" },
1061
+ secondary: { bg: "#6b7280", text: "#ffffff" },
1062
+ danger: { bg: "#dc2626", text: "#ffffff" },
1063
+ outline: { bg: "transparent", text: "#3b82f6", border: "#3b82f6" },
1064
+ ghost: { bg: "transparent", text: "#374151" }
1065
+ };
1066
+ var buttonSizeStyles = {
1067
+ sm: { paddingH: 12, paddingV: 6, fontSize: 13 },
1068
+ md: { paddingH: 16, paddingV: 10, fontSize: 15 },
1069
+ lg: { paddingH: 24, paddingV: 14, fontSize: 17 }
1070
+ };
1071
+ function ButtonComponent({ element, emit }) {
1072
+ const p = element.props;
1073
+ const variant = buttonVariantStyles[p.variant ?? "primary"] ?? {
1074
+ bg: "#3b82f6",
1075
+ text: "#ffffff"
1076
+ };
1077
+ const size = buttonSizeStyles[p.size ?? "md"] ?? {
1078
+ paddingH: 16,
1079
+ paddingV: 10,
1080
+ fontSize: 15
1081
+ };
1082
+ const disabled = p.disabled || p.loading;
1083
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1084
+ import_react_native2.Pressable,
1085
+ {
1086
+ disabled,
1087
+ onPress: () => emit?.("press"),
1088
+ style: ({ pressed }) => ({
1089
+ backgroundColor: variant.bg,
1090
+ paddingHorizontal: size.paddingH,
1091
+ paddingVertical: size.paddingV,
1092
+ borderRadius: 8,
1093
+ alignItems: "center",
1094
+ justifyContent: "center",
1095
+ flexDirection: "row",
1096
+ opacity: disabled ? 0.5 : pressed ? 0.8 : 1,
1097
+ borderWidth: variant.border ? 1 : 0,
1098
+ borderColor: variant.border,
1099
+ alignSelf: "flex-start"
1100
+ }),
1101
+ children: [
1102
+ p.loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1103
+ import_react_native2.ActivityIndicator,
1104
+ {
1105
+ size: "small",
1106
+ color: variant.text,
1107
+ style: { marginRight: 8 }
1108
+ }
1109
+ ),
1110
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1111
+ import_react_native2.Text,
1112
+ {
1113
+ style: {
1114
+ color: variant.text,
1115
+ fontSize: size.fontSize,
1116
+ fontWeight: "600"
1117
+ },
1118
+ children: p.label
1119
+ }
1120
+ )
1121
+ ]
1122
+ }
1123
+ );
1124
+ }
1125
+ function TextInputComponent({ element }) {
1126
+ const p = element.props;
1127
+ const [boundValue, setBoundValue] = useStateBinding(
1128
+ p.statePath ?? ""
1129
+ );
1130
+ const displayValue = p.statePath ? boundValue ?? "" : p.value ?? "";
1131
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: p.flex != null ? { flex: p.flex } : void 0, children: [
1132
+ p.label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native2.Text, { style: inputStyles.label, children: p.label }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1134
+ import_react_native2.TextInput,
1135
+ {
1136
+ placeholder: p.placeholder ?? void 0,
1137
+ value: displayValue,
1138
+ onChangeText: p.statePath ? setBoundValue : void 0,
1139
+ secureTextEntry: p.secureTextEntry ?? false,
1140
+ keyboardType: p.keyboardType ?? "default",
1141
+ multiline: p.multiline ?? false,
1142
+ numberOfLines: p.numberOfLines ?? void 0,
1143
+ style: [
1144
+ inputStyles.input,
1145
+ p.multiline && {
1146
+ minHeight: (p.numberOfLines ?? 3) * 20,
1147
+ textAlignVertical: "top"
1148
+ }
1149
+ ]
1150
+ }
1151
+ )
1152
+ ] });
1153
+ }
1154
+ function SwitchComponent({ element }) {
1155
+ const p = element.props;
1156
+ const [boundValue, setBoundValue] = useStateBinding(
1157
+ p.statePath ?? ""
1158
+ );
1159
+ const displayValue = p.statePath ? boundValue ?? false : p.value ?? false;
1160
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native2.View, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
1161
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1162
+ import_react_native2.Switch,
1163
+ {
1164
+ value: displayValue,
1165
+ onValueChange: p.statePath ? setBoundValue : void 0,
1166
+ disabled: p.disabled ?? false
1167
+ }
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
+ );
1374
+ }
1375
+ function ListItemComponent({ element, emit }) {
1376
+ const p = element.props;
1377
+ const hasPress = !!element.on?.press;
1378
+ const content = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1379
+ import_react_native2.View,
1380
+ {
1381
+ style: {
1382
+ flexDirection: "row",
1383
+ alignItems: "center",
1384
+ paddingVertical: 12,
1385
+ paddingHorizontal: 16
1386
+ },
1387
+ children: [
1388
+ p.leading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1389
+ import_react_native2.Text,
1390
+ {
1391
+ style: {
1392
+ fontSize: 16,
1393
+ color: "#6b7280",
1394
+ marginRight: 12,
1395
+ width: 24,
1396
+ textAlign: "center"
1397
+ },
1398
+ children: p.leading
1399
+ }
1400
+ ),
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
+ function ElementRenderer({
1559
+ element,
1560
+ spec,
1561
+ registry,
1562
+ loading,
1563
+ fallback
1564
+ }) {
1565
+ const repeatScope = useRepeatScope();
1566
+ const { ctx } = useVisibility();
1567
+ const { execute } = useActions();
1568
+ let effectiveElement = element;
1569
+ if (repeatScope) {
1570
+ const rewrittenProps = rewriteRepeatTokens(
1571
+ element.props,
1572
+ repeatScope.basePath,
1573
+ repeatScope.index
1574
+ );
1575
+ const rewrittenVisible = element.visible !== void 0 ? rewriteRepeatTokens(
1576
+ element.visible,
1577
+ repeatScope.basePath,
1578
+ repeatScope.index
1579
+ ) : element.visible;
1580
+ const rewrittenOn = element.on !== void 0 ? rewriteRepeatTokens(
1581
+ element.on,
1582
+ repeatScope.basePath,
1583
+ repeatScope.index
1584
+ ) : element.on;
1585
+ if (rewrittenProps !== element.props || rewrittenVisible !== element.visible || rewrittenOn !== element.on) {
1586
+ effectiveElement = {
1587
+ ...element,
1588
+ props: rewrittenProps,
1589
+ visible: rewrittenVisible,
1590
+ on: rewrittenOn
1591
+ };
1592
+ }
1593
+ }
1594
+ const isVisible = useIsVisible(effectiveElement.visible);
1595
+ const onBindings = effectiveElement.on;
1596
+ const emit = (0, import_react6.useCallback)(
1597
+ (eventName) => {
1598
+ const binding = onBindings?.[eventName];
1599
+ if (!binding) return;
1600
+ const bindings = Array.isArray(binding) ? binding : [binding];
1601
+ for (const b of bindings) {
1602
+ execute(b);
1603
+ }
1604
+ },
1605
+ [onBindings, execute]
1606
+ );
1607
+ if (!isVisible) {
1608
+ return null;
1609
+ }
1610
+ const resolvedProps = (0, import_core6.resolveElementProps)(
1611
+ effectiveElement.props,
1612
+ ctx
1613
+ );
1614
+ const resolvedElement = resolvedProps !== effectiveElement.props ? { ...effectiveElement, props: resolvedProps } : effectiveElement;
1615
+ const Component = registry[resolvedElement.type] ?? fallback;
1616
+ if (!Component) {
1617
+ console.warn(`No renderer for component type: ${resolvedElement.type}`);
1618
+ return null;
1619
+ }
1620
+ const children = resolvedElement.repeat ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1621
+ RepeatChildren,
1622
+ {
1623
+ element: resolvedElement,
1624
+ spec,
1625
+ registry,
1626
+ loading,
1627
+ fallback
1628
+ }
1629
+ ) : resolvedElement.children?.map((childKey) => {
1630
+ const childElement = spec.elements[childKey];
1631
+ if (!childElement) {
1632
+ if (!loading) {
1633
+ console.warn(
1634
+ `[json-render] Missing element "${childKey}" referenced as child of "${resolvedElement.type}". This element will not render.`
1635
+ );
1636
+ }
1637
+ return null;
1638
+ }
1639
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1640
+ ElementRenderer,
1641
+ {
1642
+ element: childElement,
1643
+ spec,
1644
+ registry,
1645
+ loading,
1646
+ fallback
1647
+ },
1648
+ childKey
1649
+ );
1650
+ });
1651
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Component, { element: resolvedElement, emit, loading, children });
1652
+ }
1653
+ function RepeatChildren({
1654
+ element,
1655
+ spec,
1656
+ registry,
1657
+ loading,
1658
+ fallback
1659
+ }) {
1660
+ const { state } = useStateStore();
1661
+ const repeat = element.repeat;
1662
+ const statePath = repeat.path;
1663
+ const items = (0, import_core6.getByPath)(state, statePath) ?? [];
1664
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: items.map((item, index) => {
1665
+ const key = repeat.key && typeof item === "object" && item !== null ? String(item[repeat.key] ?? index) : String(index);
1666
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1667
+ RepeatScopeProvider,
1668
+ {
1669
+ basePath: `${statePath}/${index}`,
1670
+ index,
1671
+ children: element.children?.map((childKey) => {
1672
+ const childElement = spec.elements[childKey];
1673
+ if (!childElement) {
1674
+ if (!loading) {
1675
+ console.warn(
1676
+ `[json-render] Missing element "${childKey}" referenced as child of "${element.type}" (repeat). This element will not render.`
1677
+ );
1678
+ }
1679
+ return null;
1680
+ }
1681
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1682
+ ElementRenderer,
1683
+ {
1684
+ element: childElement,
1685
+ spec,
1686
+ registry,
1687
+ loading,
1688
+ fallback
1689
+ },
1690
+ childKey
1691
+ );
1692
+ })
1693
+ },
1694
+ key
1695
+ );
1696
+ }) });
1697
+ }
1698
+ function Renderer({
1699
+ spec,
1700
+ registry: customRegistry,
1701
+ includeStandard = true,
1702
+ loading,
1703
+ fallback
1704
+ }) {
1705
+ const registry = (0, import_react6.useMemo)(
1706
+ () => ({
1707
+ ...includeStandard ? standardComponents : {},
1708
+ ...customRegistry
1709
+ }),
1710
+ [customRegistry, includeStandard]
1711
+ );
1712
+ if (!spec || !spec.root) {
1713
+ return null;
1714
+ }
1715
+ const rootElement = spec.elements[spec.root];
1716
+ if (!rootElement) {
1717
+ return null;
1718
+ }
1719
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1720
+ ElementRenderer,
1721
+ {
1722
+ element: rootElement,
1723
+ spec,
1724
+ registry,
1725
+ loading,
1726
+ fallback
1727
+ }
1728
+ );
1729
+ }
1730
+ function JSONUIProvider({
1731
+ registry,
1732
+ initialState,
1733
+ authState,
1734
+ actionHandlers,
1735
+ navigate,
1736
+ validationFunctions,
1737
+ onStateChange,
1738
+ children
1739
+ }) {
1740
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1741
+ StateProvider,
1742
+ {
1743
+ initialState,
1744
+ authState,
1745
+ onStateChange,
1746
+ 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: [
1747
+ children,
1748
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmationDialogManager, {})
1749
+ ] }) }) })
1750
+ }
1751
+ );
1752
+ }
1753
+ function ConfirmationDialogManager() {
1754
+ const { pendingConfirmation, confirm, cancel } = useActions();
1755
+ if (!pendingConfirmation?.action.confirm) {
1756
+ return null;
1757
+ }
1758
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1759
+ ConfirmDialog,
1760
+ {
1761
+ confirm: pendingConfirmation.action.confirm,
1762
+ onConfirm: confirm,
1763
+ onCancel: cancel
1764
+ }
1765
+ );
1766
+ }
1767
+ function createRendererFromCatalog(_catalog, registry) {
1768
+ return function CatalogRenderer(props) {
1769
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Renderer, { ...props, registry });
1770
+ };
1771
+ }
1772
+ function defineRegistry(_catalog, options) {
1773
+ const registry = {};
1774
+ if (options.components) {
1775
+ for (const [name, componentFn] of Object.entries(options.components)) {
1776
+ registry[name] = ({
1777
+ element,
1778
+ children,
1779
+ emit,
1780
+ loading
1781
+ }) => {
1782
+ return componentFn({
1783
+ props: element.props,
1784
+ children,
1785
+ emit,
1786
+ loading
1787
+ });
1788
+ };
1789
+ }
1790
+ }
1791
+ const actionMap = options.actions ? Object.entries(options.actions) : [];
1792
+ const handlers = (getSetState, getState) => {
1793
+ const result = {};
1794
+ for (const [name, actionFn] of actionMap) {
1795
+ result[name] = async (params) => {
1796
+ const setState = getSetState();
1797
+ const state = getState();
1798
+ if (setState) {
1799
+ await actionFn(params, setState, state);
1800
+ }
1801
+ };
1802
+ }
1803
+ return result;
1804
+ };
1805
+ const executeAction2 = async (actionName, params, setState, state = {}) => {
1806
+ const entry = actionMap.find(([name]) => name === actionName);
1807
+ if (entry) {
1808
+ await entry[1](params, setState, state);
1809
+ } else {
1810
+ console.warn(`Unknown action: ${actionName}`);
1811
+ }
1812
+ };
1813
+ return { registry, handlers, executeAction: executeAction2 };
1814
+ }
1815
+ function createRenderer(catalog, components) {
1816
+ const registry = components;
1817
+ return function CatalogRenderer({
1818
+ spec,
1819
+ state,
1820
+ onAction,
1821
+ onStateChange,
1822
+ loading,
1823
+ authState,
1824
+ fallback
1825
+ }) {
1826
+ const actionHandlers = onAction ? {
1827
+ __default__: (params) => {
1828
+ const actionName = params.__actionName__;
1829
+ const actionParams = params.__actionParams__;
1830
+ return onAction(actionName, actionParams);
1831
+ }
1832
+ } : void 0;
1833
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1834
+ StateProvider,
1835
+ {
1836
+ initialState: state,
1837
+ authState,
1838
+ onStateChange,
1839
+ 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: [
1840
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1841
+ Renderer,
1842
+ {
1843
+ spec,
1844
+ registry,
1845
+ loading,
1846
+ fallback
1847
+ }
1848
+ ),
1849
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmationDialogManager, {})
1850
+ ] }) }) })
1851
+ }
1852
+ );
1853
+ };
1854
+ }
1855
+
1856
+ // src/hooks.ts
1857
+ var import_react7 = require("react");
1858
+ var import_core7 = require("@json-render/core");
1859
+ function looksLikeJson(line) {
1860
+ return line.startsWith("{") || line.startsWith("[");
1861
+ }
1862
+ function parsePatchLine(line) {
1863
+ const trimmed = line.trim();
1864
+ if (!trimmed || trimmed.startsWith("//")) {
1865
+ return { patch: null, malformed: false };
1866
+ }
1867
+ if (!looksLikeJson(trimmed)) {
1868
+ return { patch: null, malformed: false };
1869
+ }
1870
+ try {
1871
+ return { patch: JSON.parse(trimmed), malformed: false };
1872
+ } catch {
1873
+ }
1874
+ let attempt = trimmed;
1875
+ for (let i = 0; i < 3; i++) {
1876
+ const last = attempt[attempt.length - 1];
1877
+ if (last === "}" || last === "]") {
1878
+ attempt = attempt.slice(0, -1);
1879
+ try {
1880
+ const result = JSON.parse(attempt);
1881
+ console.warn(
1882
+ `[json-render] Recovered malformed JSONL line by removing ${i + 1} trailing '${last}'`
1883
+ );
1884
+ return { patch: result, malformed: false };
1885
+ } catch {
1886
+ }
1887
+ } else {
1888
+ break;
1889
+ }
1890
+ }
1891
+ return { patch: null, malformed: true };
1892
+ }
1893
+ function setSpecValue(newSpec, path, value) {
1894
+ if (path === "/root") {
1895
+ newSpec.root = value;
1896
+ return;
1897
+ }
1898
+ if (path === "/state") {
1899
+ newSpec.state = value;
1900
+ return;
1901
+ }
1902
+ if (path.startsWith("/state/")) {
1903
+ if (!newSpec.state) newSpec.state = {};
1904
+ const statePath = path.slice("/state".length);
1905
+ (0, import_core7.setByPath)(newSpec.state, statePath, value);
1906
+ return;
1907
+ }
1908
+ if (path.startsWith("/elements/")) {
1909
+ const pathParts = path.slice("/elements/".length).split("/");
1910
+ const elementKey = pathParts[0];
1911
+ if (!elementKey) return;
1912
+ if (pathParts.length === 1) {
1913
+ newSpec.elements[elementKey] = value;
1914
+ } else {
1915
+ const element = newSpec.elements[elementKey];
1916
+ if (element) {
1917
+ const propPath = "/" + pathParts.slice(1).join("/");
1918
+ const newElement = { ...element };
1919
+ (0, import_core7.setByPath)(
1920
+ newElement,
1921
+ propPath,
1922
+ value
1923
+ );
1924
+ newSpec.elements[elementKey] = newElement;
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ function removeSpecValue(newSpec, path) {
1930
+ if (path === "/state") {
1931
+ newSpec.state = void 0;
1932
+ return;
1933
+ }
1934
+ if (path.startsWith("/state/") && newSpec.state) {
1935
+ const statePath = path.slice("/state".length);
1936
+ (0, import_core7.removeByPath)(newSpec.state, statePath);
1937
+ return;
1938
+ }
1939
+ if (path.startsWith("/elements/")) {
1940
+ const pathParts = path.slice("/elements/".length).split("/");
1941
+ const elementKey = pathParts[0];
1942
+ if (!elementKey) return;
1943
+ if (pathParts.length === 1) {
1944
+ const { [elementKey]: _, ...rest } = newSpec.elements;
1945
+ newSpec.elements = rest;
1946
+ } else {
1947
+ const element = newSpec.elements[elementKey];
1948
+ if (element) {
1949
+ const propPath = "/" + pathParts.slice(1).join("/");
1950
+ const newElement = { ...element };
1951
+ (0, import_core7.removeByPath)(
1952
+ newElement,
1953
+ propPath
1954
+ );
1955
+ newSpec.elements[elementKey] = newElement;
1956
+ }
1957
+ }
1958
+ }
1959
+ }
1960
+ function getSpecValue(spec, path) {
1961
+ if (path === "/root") return spec.root;
1962
+ return (0, import_core7.getByPath)(spec, path);
1963
+ }
1964
+ function applyPatch(spec, patch) {
1965
+ const newSpec = { ...spec, elements: { ...spec.elements } };
1966
+ switch (patch.op) {
1967
+ case "add":
1968
+ case "replace": {
1969
+ setSpecValue(newSpec, patch.path, patch.value);
1970
+ break;
1971
+ }
1972
+ case "remove": {
1973
+ removeSpecValue(newSpec, patch.path);
1974
+ break;
1975
+ }
1976
+ case "move": {
1977
+ if (!patch.from) break;
1978
+ const moveValue = getSpecValue(newSpec, patch.from);
1979
+ removeSpecValue(newSpec, patch.from);
1980
+ setSpecValue(newSpec, patch.path, moveValue);
1981
+ break;
1982
+ }
1983
+ case "copy": {
1984
+ if (!patch.from) break;
1985
+ const copyValue = getSpecValue(newSpec, patch.from);
1986
+ setSpecValue(newSpec, patch.path, copyValue);
1987
+ break;
1988
+ }
1989
+ case "test": {
1990
+ break;
1991
+ }
1992
+ }
1993
+ return newSpec;
1994
+ }
1995
+ function useUIStream({
1996
+ api,
1997
+ onComplete,
1998
+ onError,
1999
+ fetch: fetchFn = globalThis.fetch,
2000
+ validate: enableValidation = false,
2001
+ maxRetries = 5
2002
+ }) {
2003
+ const [spec, setSpec] = (0, import_react7.useState)(null);
2004
+ const [isStreaming, setIsStreaming] = (0, import_react7.useState)(false);
2005
+ const [error, setError] = (0, import_react7.useState)(null);
2006
+ const [rawLines, setRawLines] = (0, import_react7.useState)([]);
2007
+ const abortControllerRef = (0, import_react7.useRef)(null);
2008
+ const stop = (0, import_react7.useCallback)(() => {
2009
+ abortControllerRef.current?.abort();
2010
+ abortControllerRef.current = null;
2011
+ setIsStreaming(false);
2012
+ }, []);
2013
+ const clear = (0, import_react7.useCallback)(() => {
2014
+ setSpec(null);
2015
+ setError(null);
2016
+ setRawLines([]);
2017
+ }, []);
2018
+ const streamRequest = (0, import_react7.useCallback)(
2019
+ async (prompt, context, initialSpec, abortOnMalformed) => {
2020
+ let currentSpec = initialSpec;
2021
+ setSpec(currentSpec);
2022
+ const malformedLines = [];
2023
+ const response = await fetchFn(api, {
2024
+ method: "POST",
2025
+ headers: { "Content-Type": "application/json" },
2026
+ body: JSON.stringify({
2027
+ prompt,
2028
+ context,
2029
+ currentSpec
2030
+ }),
2031
+ signal: abortControllerRef.current.signal
2032
+ });
2033
+ console.log("[useUIStream] response status:", response.status);
2034
+ if (!response.ok) {
2035
+ let errorMessage = `HTTP error: ${response.status}`;
2036
+ try {
2037
+ const errorData = await response.json();
2038
+ if (errorData.message) {
2039
+ errorMessage = errorData.message;
2040
+ } else if (errorData.error) {
2041
+ errorMessage = errorData.error;
2042
+ }
2043
+ } catch {
2044
+ }
2045
+ throw new Error(errorMessage);
2046
+ }
2047
+ const reader = response.body?.getReader();
2048
+ if (!reader) {
2049
+ throw new Error("No response body");
2050
+ }
2051
+ const decoder = new TextDecoder();
2052
+ let buffer = "";
2053
+ let chunkCount = 0;
2054
+ let totalBytes = 0;
2055
+ let aborted = false;
2056
+ while (true) {
2057
+ const { done, value } = await reader.read();
2058
+ if (done) {
2059
+ console.log(
2060
+ "[useUIStream] stream done. Total chunks:",
2061
+ chunkCount,
2062
+ "bytes:",
2063
+ totalBytes
2064
+ );
2065
+ break;
2066
+ }
2067
+ chunkCount++;
2068
+ totalBytes += value?.byteLength ?? 0;
2069
+ const decoded = decoder.decode(value, { stream: true });
2070
+ buffer += decoded;
2071
+ if (chunkCount <= 5) {
2072
+ console.log(
2073
+ `[useUIStream] chunk #${chunkCount} (${value?.byteLength ?? 0}b):`,
2074
+ decoded.slice(0, 200)
2075
+ );
2076
+ }
2077
+ const lines = buffer.split("\n");
2078
+ buffer = lines.pop() ?? "";
2079
+ const newLines = [];
2080
+ for (const line of lines) {
2081
+ if (line.trim()) {
2082
+ newLines.push(line);
2083
+ }
2084
+ const { patch, malformed } = parsePatchLine(line);
2085
+ if (patch) {
2086
+ console.log("[useUIStream] applied patch:", patch.op, patch.path);
2087
+ currentSpec = applyPatch(currentSpec, patch);
2088
+ setSpec({ ...currentSpec });
2089
+ } else if (malformed) {
2090
+ malformedLines.push(line.trim());
2091
+ console.warn(
2092
+ "[useUIStream] malformed JSON line:",
2093
+ line.trim().slice(0, 200)
2094
+ );
2095
+ if (abortOnMalformed) {
2096
+ console.warn(
2097
+ "[useUIStream] aborting stream due to malformed JSON, will retry"
2098
+ );
2099
+ await reader.cancel();
2100
+ aborted = true;
2101
+ break;
2102
+ }
2103
+ }
2104
+ }
2105
+ if (aborted) break;
2106
+ if (newLines.length > 0) {
2107
+ setRawLines((prev) => [...prev, ...newLines]);
2108
+ }
2109
+ }
2110
+ if (!aborted && buffer.trim()) {
2111
+ console.log("[useUIStream] remaining buffer:", buffer.slice(0, 200));
2112
+ setRawLines((prev) => [...prev, buffer]);
2113
+ const { patch, malformed } = parsePatchLine(buffer);
2114
+ if (patch) {
2115
+ currentSpec = applyPatch(currentSpec, patch);
2116
+ setSpec({ ...currentSpec });
2117
+ } else if (malformed) {
2118
+ malformedLines.push(buffer.trim());
2119
+ }
2120
+ }
2121
+ console.log(
2122
+ "[useUIStream] spec state - root:",
2123
+ currentSpec.root,
2124
+ "elements:",
2125
+ Object.keys(currentSpec.elements).length,
2126
+ "completed:",
2127
+ !aborted,
2128
+ "malformed:",
2129
+ malformedLines.length
2130
+ );
2131
+ return { spec: currentSpec, completed: !aborted, malformedLines };
2132
+ },
2133
+ [api, fetchFn]
2134
+ );
2135
+ const send = (0, import_react7.useCallback)(
2136
+ async (prompt, context) => {
2137
+ abortControllerRef.current?.abort();
2138
+ abortControllerRef.current = new AbortController();
2139
+ setIsStreaming(true);
2140
+ setError(null);
2141
+ setRawLines([]);
2142
+ const previousSpec = context?.previousSpec;
2143
+ let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
2144
+ let retriesUsed = 0;
2145
+ let currentPrompt = prompt;
2146
+ let currentContext = context;
2147
+ try {
2148
+ while (retriesUsed <= maxRetries) {
2149
+ const result = await streamRequest(
2150
+ currentPrompt,
2151
+ currentContext,
2152
+ currentSpec,
2153
+ enableValidation
2154
+ // only abort on malformed when validation is on
2155
+ );
2156
+ currentSpec = result.spec;
2157
+ if (!result.completed && result.malformedLines.length > 0) {
2158
+ if (retriesUsed >= maxRetries) {
2159
+ console.warn(
2160
+ "[useUIStream] malformed lines but max retries exhausted"
2161
+ );
2162
+ break;
2163
+ }
2164
+ retriesUsed++;
2165
+ console.warn(
2166
+ `[useUIStream] mid-stream retry ${retriesUsed}/${maxRetries} due to malformed JSON`
2167
+ );
2168
+ currentContext = { ...context, previousSpec: currentSpec };
2169
+ currentPrompt = `The previous generation contained malformed JSON that could not be parsed. The line was:
2170
+ ${result.malformedLines[result.malformedLines.length - 1]?.slice(0, 500)}
2171
+
2172
+ The current spec state is provided. Continue generating from where you left off. Output ONLY the remaining patches needed to complete the UI.`;
2173
+ continue;
2174
+ }
2175
+ if (!enableValidation || !currentSpec.root) {
2176
+ break;
2177
+ }
2178
+ const { spec: fixedSpec, fixes } = (0, import_core7.autoFixSpec)(currentSpec);
2179
+ if (fixes.length > 0) {
2180
+ console.log("[useUIStream] auto-fixed spec issues:", fixes);
2181
+ currentSpec = fixedSpec;
2182
+ setSpec({ ...currentSpec });
2183
+ }
2184
+ const validation = (0, import_core7.validateSpec)(currentSpec);
2185
+ if (validation.valid) {
2186
+ console.log("[useUIStream] spec validation passed");
2187
+ break;
2188
+ }
2189
+ const errors = validation.issues.filter(
2190
+ (i) => i.severity === "error"
2191
+ );
2192
+ if (retriesUsed >= maxRetries) {
2193
+ console.warn(
2194
+ "[useUIStream] spec validation failed, max retries exhausted:",
2195
+ errors.map((e) => e.message)
2196
+ );
2197
+ break;
2198
+ }
2199
+ retriesUsed++;
2200
+ const issueText = (0, import_core7.formatSpecIssues)(validation.issues);
2201
+ console.warn(
2202
+ `[useUIStream] post-stream retry ${retriesUsed}/${maxRetries}:`,
2203
+ errors.map((e) => e.message)
2204
+ );
2205
+ currentContext = { ...context, previousSpec: currentSpec };
2206
+ 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.
2207
+
2208
+ ` + issueText;
2209
+ }
2210
+ onComplete?.(currentSpec);
2211
+ } catch (err) {
2212
+ if (err.name === "AbortError") {
2213
+ return;
2214
+ }
2215
+ const error2 = err instanceof Error ? err : new Error(String(err));
2216
+ setError(error2);
2217
+ onError?.(error2);
2218
+ } finally {
2219
+ setIsStreaming(false);
2220
+ }
2221
+ },
2222
+ [
2223
+ api,
2224
+ fetchFn,
2225
+ onComplete,
2226
+ onError,
2227
+ enableValidation,
2228
+ maxRetries,
2229
+ streamRequest
2230
+ ]
2231
+ );
2232
+ (0, import_react7.useEffect)(() => {
2233
+ return () => {
2234
+ abortControllerRef.current?.abort();
2235
+ };
2236
+ }, []);
2237
+ return {
2238
+ spec,
2239
+ isStreaming,
2240
+ error,
2241
+ rawLines,
2242
+ send,
2243
+ stop,
2244
+ clear
2245
+ };
2246
+ }
2247
+ function flatToTree(elements) {
2248
+ const elementMap = {};
2249
+ let root = "";
2250
+ for (const element of elements) {
2251
+ elementMap[element.key] = {
2252
+ type: element.type,
2253
+ props: element.props,
2254
+ children: [],
2255
+ visible: element.visible
2256
+ };
2257
+ }
2258
+ for (const element of elements) {
2259
+ if (element.parentKey) {
2260
+ const parent = elementMap[element.parentKey];
2261
+ if (parent) {
2262
+ if (!parent.children) {
2263
+ parent.children = [];
2264
+ }
2265
+ parent.children.push(element.key);
2266
+ }
2267
+ } else {
2268
+ root = element.key;
2269
+ }
2270
+ }
2271
+ return { root, elements: elementMap };
2272
+ }
2273
+
2274
+ // src/catalog.ts
2275
+ var import_zod = require("zod");
2276
+ var standardComponentDefinitions = {
2277
+ // ==========================================================================
2278
+ // Layout Components
2279
+ // ==========================================================================
2280
+ Container: {
2281
+ props: import_zod.z.object({
2282
+ padding: import_zod.z.number().nullable(),
2283
+ paddingHorizontal: import_zod.z.number().nullable(),
2284
+ paddingVertical: import_zod.z.number().nullable(),
2285
+ margin: import_zod.z.number().nullable(),
2286
+ backgroundColor: import_zod.z.string().nullable(),
2287
+ borderRadius: import_zod.z.number().nullable(),
2288
+ flex: import_zod.z.number().nullable()
2289
+ }),
2290
+ slots: ["default"],
2291
+ description: "Generic container wrapper. Use for grouping elements with padding, margin, and background color."
2292
+ },
2293
+ Row: {
2294
+ props: import_zod.z.object({
2295
+ gap: import_zod.z.number().nullable(),
2296
+ alignItems: import_zod.z.enum(["flex-start", "center", "flex-end", "stretch", "baseline"]).nullable(),
2297
+ justifyContent: import_zod.z.enum([
2298
+ "flex-start",
2299
+ "center",
2300
+ "flex-end",
2301
+ "space-between",
2302
+ "space-around",
2303
+ "space-evenly"
2304
+ ]).nullable(),
2305
+ flexWrap: import_zod.z.enum(["wrap", "nowrap"]).nullable(),
2306
+ padding: import_zod.z.number().nullable(),
2307
+ flex: import_zod.z.number().nullable()
2308
+ }),
2309
+ slots: ["default"],
2310
+ description: "Horizontal flex layout. Use for placing elements side by side."
2311
+ },
2312
+ Column: {
2313
+ props: import_zod.z.object({
2314
+ gap: import_zod.z.number().nullable(),
2315
+ alignItems: import_zod.z.enum(["flex-start", "center", "flex-end", "stretch", "baseline"]).nullable(),
2316
+ justifyContent: import_zod.z.enum([
2317
+ "flex-start",
2318
+ "center",
2319
+ "flex-end",
2320
+ "space-between",
2321
+ "space-around",
2322
+ "space-evenly"
2323
+ ]).nullable(),
2324
+ padding: import_zod.z.number().nullable(),
2325
+ flex: import_zod.z.number().nullable()
2326
+ }),
2327
+ slots: ["default"],
2328
+ description: "Vertical flex layout. Use for stacking elements top to bottom."
2329
+ },
2330
+ ScrollContainer: {
2331
+ props: import_zod.z.object({
2332
+ horizontal: import_zod.z.boolean().nullable(),
2333
+ showsScrollIndicator: import_zod.z.boolean().nullable(),
2334
+ padding: import_zod.z.number().nullable(),
2335
+ backgroundColor: import_zod.z.string().nullable()
2336
+ }),
2337
+ slots: ["default"],
2338
+ description: "Scrollable container. Use for content that may overflow the screen."
2339
+ },
2340
+ SafeArea: {
2341
+ props: import_zod.z.object({
2342
+ backgroundColor: import_zod.z.string().nullable()
2343
+ }),
2344
+ slots: ["default"],
2345
+ description: "Safe area container that respects device notches and system bars. Use as the outermost wrapper for screens."
2346
+ },
2347
+ Spacer: {
2348
+ props: import_zod.z.object({
2349
+ size: import_zod.z.number().nullable(),
2350
+ flex: import_zod.z.number().nullable()
2351
+ }),
2352
+ slots: [],
2353
+ description: "Empty space between elements. Set size for fixed spacing or flex for flexible spacing."
2354
+ },
2355
+ Pressable: {
2356
+ props: import_zod.z.object({}),
2357
+ events: ["press", "longPress"],
2358
+ slots: ["default"],
2359
+ description: "Touchable wrapper that triggers events on press. Wrap any element to make it tappable. Bind on.press to setState to update state for visibility-driven UIs like tabs."
2360
+ },
2361
+ Divider: {
2362
+ props: import_zod.z.object({
2363
+ direction: import_zod.z.enum(["horizontal", "vertical"]).nullable(),
2364
+ color: import_zod.z.string().nullable(),
2365
+ thickness: import_zod.z.number().nullable(),
2366
+ margin: import_zod.z.number().nullable()
2367
+ }),
2368
+ slots: [],
2369
+ description: "Thin line separator between content sections."
2370
+ },
2371
+ // ==========================================================================
2372
+ // Content Components
2373
+ // ==========================================================================
2374
+ Heading: {
2375
+ props: import_zod.z.object({
2376
+ text: import_zod.z.string(),
2377
+ level: import_zod.z.enum(["h1", "h2", "h3", "h4"]).nullable(),
2378
+ color: import_zod.z.string().nullable(),
2379
+ align: import_zod.z.enum(["left", "center", "right"]).nullable()
2380
+ }),
2381
+ slots: [],
2382
+ description: "Heading text at various levels. h1 is largest, h4 is smallest."
2383
+ },
2384
+ Paragraph: {
2385
+ props: import_zod.z.object({
2386
+ text: import_zod.z.string(),
2387
+ color: import_zod.z.string().nullable(),
2388
+ align: import_zod.z.enum(["left", "center", "right"]).nullable(),
2389
+ numberOfLines: import_zod.z.number().nullable(),
2390
+ fontSize: import_zod.z.number().nullable()
2391
+ }),
2392
+ slots: [],
2393
+ description: "Body text paragraph. Use for descriptions and longer content."
2394
+ },
2395
+ Label: {
2396
+ props: import_zod.z.object({
2397
+ text: import_zod.z.string(),
2398
+ color: import_zod.z.string().nullable(),
2399
+ bold: import_zod.z.boolean().nullable(),
2400
+ size: import_zod.z.enum(["xs", "sm", "md"]).nullable()
2401
+ }),
2402
+ slots: [],
2403
+ description: "Small utility text. Use for captions, form labels, and secondary information."
2404
+ },
2405
+ Image: {
2406
+ props: import_zod.z.object({
2407
+ src: import_zod.z.string(),
2408
+ alt: import_zod.z.string().nullable(),
2409
+ width: import_zod.z.number().nullable(),
2410
+ height: import_zod.z.number().nullable(),
2411
+ resizeMode: import_zod.z.enum(["cover", "contain", "stretch", "center"]).nullable(),
2412
+ borderRadius: import_zod.z.number().nullable()
2413
+ }),
2414
+ slots: [],
2415
+ description: "Image display. Provide a source URL and optional dimensions."
2416
+ },
2417
+ Avatar: {
2418
+ props: import_zod.z.object({
2419
+ src: import_zod.z.string().nullable(),
2420
+ initials: import_zod.z.string().nullable(),
2421
+ size: import_zod.z.enum(["sm", "md", "lg", "xl"]).nullable(),
2422
+ backgroundColor: import_zod.z.string().nullable()
2423
+ }),
2424
+ slots: [],
2425
+ description: "Circular avatar showing an image or initials. Use for user profiles and contacts."
2426
+ },
2427
+ Badge: {
2428
+ props: import_zod.z.object({
2429
+ label: import_zod.z.string(),
2430
+ variant: import_zod.z.enum(["default", "info", "success", "warning", "error"]).nullable()
2431
+ }),
2432
+ slots: [],
2433
+ description: "Small colored indicator with a label. Use for status, counts, and categories."
2434
+ },
2435
+ Chip: {
2436
+ props: import_zod.z.object({
2437
+ label: import_zod.z.string(),
2438
+ selected: import_zod.z.boolean().nullable(),
2439
+ backgroundColor: import_zod.z.string().nullable()
2440
+ }),
2441
+ events: ["press", "remove"],
2442
+ slots: [],
2443
+ description: "Tag or filter chip. Bind on.remove for removable chips, on.press for selectable chips."
2444
+ },
2445
+ // ==========================================================================
2446
+ // Input Components
2447
+ // ==========================================================================
2448
+ Button: {
2449
+ props: import_zod.z.object({
2450
+ label: import_zod.z.string(),
2451
+ variant: import_zod.z.enum(["primary", "secondary", "danger", "outline", "ghost"]).nullable(),
2452
+ size: import_zod.z.enum(["sm", "md", "lg"]).nullable(),
2453
+ disabled: import_zod.z.boolean().nullable(),
2454
+ loading: import_zod.z.boolean().nullable()
2455
+ }),
2456
+ events: ["press"],
2457
+ slots: [],
2458
+ description: "Pressable button with label. Set variant for styling. Bind on.press for the handler to call on press."
2459
+ },
2460
+ TextInput: {
2461
+ props: import_zod.z.object({
2462
+ placeholder: import_zod.z.string().nullable(),
2463
+ value: import_zod.z.string().nullable(),
2464
+ statePath: import_zod.z.string().nullable(),
2465
+ secureTextEntry: import_zod.z.boolean().nullable(),
2466
+ keyboardType: import_zod.z.enum(["default", "email-address", "numeric", "phone-pad", "url"]).nullable(),
2467
+ multiline: import_zod.z.boolean().nullable(),
2468
+ numberOfLines: import_zod.z.number().nullable(),
2469
+ label: import_zod.z.string().nullable(),
2470
+ flex: import_zod.z.number().nullable()
2471
+ }),
2472
+ events: ["submit", "focus", "blur"],
2473
+ slots: [],
2474
+ 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."
2475
+ },
2476
+ Switch: {
2477
+ props: import_zod.z.object({
2478
+ value: import_zod.z.boolean().nullable(),
2479
+ statePath: import_zod.z.string().nullable(),
2480
+ label: import_zod.z.string().nullable(),
2481
+ disabled: import_zod.z.boolean().nullable()
2482
+ }),
2483
+ events: ["change"],
2484
+ slots: [],
2485
+ description: "Toggle switch. Use statePath to bind to the state model."
2486
+ },
2487
+ Checkbox: {
2488
+ props: import_zod.z.object({
2489
+ checked: import_zod.z.boolean().nullable(),
2490
+ statePath: import_zod.z.string().nullable(),
2491
+ label: import_zod.z.string().nullable(),
2492
+ disabled: import_zod.z.boolean().nullable()
2493
+ }),
2494
+ events: ["change"],
2495
+ slots: [],
2496
+ description: "Checkbox for boolean selections. Use statePath to bind to the state model."
2497
+ },
2498
+ Slider: {
2499
+ props: import_zod.z.object({
2500
+ min: import_zod.z.number().nullable(),
2501
+ max: import_zod.z.number().nullable(),
2502
+ step: import_zod.z.number().nullable(),
2503
+ value: import_zod.z.number().nullable(),
2504
+ statePath: import_zod.z.string().nullable(),
2505
+ label: import_zod.z.string().nullable(),
2506
+ color: import_zod.z.string().nullable()
2507
+ }),
2508
+ slots: [],
2509
+ description: "Range slider for numeric values. Use statePath to bind to the state model."
2510
+ },
2511
+ SearchBar: {
2512
+ props: import_zod.z.object({
2513
+ placeholder: import_zod.z.string().nullable(),
2514
+ value: import_zod.z.string().nullable(),
2515
+ statePath: import_zod.z.string().nullable()
2516
+ }),
2517
+ events: ["submit"],
2518
+ slots: [],
2519
+ description: "Search input with icon. Bind on.submit to trigger search on submit."
2520
+ },
2521
+ // ==========================================================================
2522
+ // Feedback Components
2523
+ // ==========================================================================
2524
+ Spinner: {
2525
+ props: import_zod.z.object({
2526
+ size: import_zod.z.enum(["small", "large"]).nullable(),
2527
+ color: import_zod.z.string().nullable()
2528
+ }),
2529
+ slots: [],
2530
+ description: "Loading spinner indicator. Use while content is loading."
2531
+ },
2532
+ ProgressBar: {
2533
+ props: import_zod.z.object({
2534
+ progress: import_zod.z.number(),
2535
+ color: import_zod.z.string().nullable(),
2536
+ trackColor: import_zod.z.string().nullable(),
2537
+ height: import_zod.z.number().nullable()
2538
+ }),
2539
+ slots: [],
2540
+ description: "Horizontal progress bar. Set progress from 0 to 1."
2541
+ },
2542
+ // ==========================================================================
2543
+ // Composite Components
2544
+ // ==========================================================================
2545
+ Card: {
2546
+ props: import_zod.z.object({
2547
+ title: import_zod.z.string().nullable(),
2548
+ subtitle: import_zod.z.string().nullable(),
2549
+ padding: import_zod.z.number().nullable(),
2550
+ backgroundColor: import_zod.z.string().nullable(),
2551
+ borderRadius: import_zod.z.number().nullable(),
2552
+ elevated: import_zod.z.boolean().nullable()
2553
+ }),
2554
+ slots: ["default"],
2555
+ description: "Elevated card container with optional title. Use for grouping related content."
2556
+ },
2557
+ ListItem: {
2558
+ props: import_zod.z.object({
2559
+ title: import_zod.z.string(),
2560
+ subtitle: import_zod.z.string().nullable(),
2561
+ leading: import_zod.z.string().nullable(),
2562
+ trailing: import_zod.z.string().nullable(),
2563
+ showChevron: import_zod.z.boolean().nullable()
2564
+ }),
2565
+ events: ["press"],
2566
+ slots: [],
2567
+ description: "List row with title, subtitle, and optional leading/trailing text. Bind on.press for the press handler."
2568
+ },
2569
+ Modal: {
2570
+ props: import_zod.z.object({
2571
+ visible: import_zod.z.boolean(),
2572
+ title: import_zod.z.string().nullable(),
2573
+ animationType: import_zod.z.enum(["slide", "fade", "none"]).nullable(),
2574
+ statePath: import_zod.z.string().nullable()
2575
+ }),
2576
+ slots: ["default"],
2577
+ description: "Modal overlay dialog. Use statePath to bind visibility to the state model."
2578
+ }
2579
+ };
2580
+ var standardActionDefinitions = {
2581
+ navigate: {
2582
+ params: import_zod.z.object({
2583
+ screen: import_zod.z.string(),
2584
+ params: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).nullable()
2585
+ }),
2586
+ description: "Navigate to a screen. Calls the user-provided navigation function."
2587
+ },
2588
+ goBack: {
2589
+ params: import_zod.z.object({}),
2590
+ description: "Navigate back to the previous screen."
2591
+ },
2592
+ showAlert: {
2593
+ params: import_zod.z.object({
2594
+ title: import_zod.z.string(),
2595
+ message: import_zod.z.string().nullable(),
2596
+ buttons: import_zod.z.array(
2597
+ import_zod.z.object({
2598
+ text: import_zod.z.string(),
2599
+ style: import_zod.z.enum(["default", "cancel", "destructive"]).nullable(),
2600
+ action: import_zod.z.string().nullable()
2601
+ })
2602
+ ).nullable()
2603
+ }),
2604
+ description: "Show a native alert dialog using Alert.alert()."
2605
+ },
2606
+ share: {
2607
+ params: import_zod.z.object({
2608
+ message: import_zod.z.string(),
2609
+ url: import_zod.z.string().nullable(),
2610
+ title: import_zod.z.string().nullable()
2611
+ }),
2612
+ description: "Open the native share sheet using Share.share()."
2613
+ },
2614
+ openURL: {
2615
+ params: import_zod.z.object({
2616
+ url: import_zod.z.string()
2617
+ }),
2618
+ description: "Open a URL in the default browser using Linking.openURL()."
2619
+ },
2620
+ setState: {
2621
+ params: import_zod.z.object({
2622
+ path: import_zod.z.string(),
2623
+ value: import_zod.z.unknown()
2624
+ }),
2625
+ description: "Update a value in the state model at the given path."
2626
+ },
2627
+ pushState: {
2628
+ params: import_zod.z.object({
2629
+ path: import_zod.z.string(),
2630
+ value: import_zod.z.unknown(),
2631
+ clearPath: import_zod.z.string().optional()
2632
+ }),
2633
+ 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" }.'
2634
+ },
2635
+ removeState: {
2636
+ params: import_zod.z.object({
2637
+ path: import_zod.z.string(),
2638
+ index: import_zod.z.number()
2639
+ }),
2640
+ description: "Remove an item from an array in the state model at the given index."
2641
+ },
2642
+ refresh: {
2643
+ params: import_zod.z.object({
2644
+ target: import_zod.z.string().nullable()
2645
+ }),
2646
+ description: "Trigger a data refresh. Optionally specify a target to refresh."
2647
+ }
2648
+ };
2649
+ // Annotate the CommonJS export names for ESM import in node:
2650
+ 0 && (module.exports = {
2651
+ ActionProvider,
2652
+ ConfirmDialog,
2653
+ JSONUIProvider,
2654
+ Renderer,
2655
+ RepeatScopeProvider,
2656
+ StateProvider,
2657
+ ValidationProvider,
2658
+ VisibilityProvider,
2659
+ createRenderer,
2660
+ createRendererFromCatalog,
2661
+ createStandardActionHandlers,
2662
+ defineRegistry,
2663
+ elementTreeSchema,
2664
+ flatToTree,
2665
+ rewriteRepeatTokens,
2666
+ schema,
2667
+ standardActionDefinitions,
2668
+ standardComponentDefinitions,
2669
+ standardComponents,
2670
+ useAction,
2671
+ useActions,
2672
+ useFieldValidation,
2673
+ useIsVisible,
2674
+ useRepeatScope,
2675
+ useStateBinding,
2676
+ useStateStore,
2677
+ useStateValue,
2678
+ useUIStream,
2679
+ useValidation,
2680
+ useVisibility
2681
+ });
2682
+ //# sourceMappingURL=index.js.map