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