@json-render/solid 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1853 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ActionProvider: () => ActionProvider,
24
+ ConfirmDialog: () => ConfirmDialog,
25
+ JSONUIProvider: () => JSONUIProvider,
26
+ Renderer: () => Renderer,
27
+ RepeatScopeProvider: () => RepeatScopeProvider,
28
+ StateProvider: () => StateProvider,
29
+ ValidationProvider: () => ValidationProvider,
30
+ VisibilityProvider: () => VisibilityProvider,
31
+ buildSpecFromParts: () => buildSpecFromParts,
32
+ createRenderer: () => createRenderer,
33
+ createStateStore: () => import_core8.createStateStore,
34
+ defineRegistry: () => defineRegistry,
35
+ elementTreeSchema: () => elementTreeSchema,
36
+ flatToTree: () => flatToTree,
37
+ getTextFromParts: () => getTextFromParts,
38
+ schema: () => schema,
39
+ useAction: () => useAction,
40
+ useActions: () => useActions,
41
+ useBoundProp: () => useBoundProp,
42
+ useChatUI: () => useChatUI,
43
+ useFieldValidation: () => useFieldValidation,
44
+ useIsVisible: () => useIsVisible,
45
+ useJsonRenderMessage: () => useJsonRenderMessage,
46
+ useOptionalValidation: () => useOptionalValidation,
47
+ useRepeatScope: () => useRepeatScope,
48
+ useStateBinding: () => useStateBinding,
49
+ useStateStore: () => useStateStore,
50
+ useStateValue: () => useStateValue,
51
+ useUIStream: () => useUIStream,
52
+ useValidation: () => useValidation,
53
+ useVisibility: () => useVisibility
54
+ });
55
+ module.exports = __toCommonJS(index_exports);
56
+
57
+ // src/contexts/state.tsx
58
+ var import_web = require("solid-js/web");
59
+ var import_solid_js = require("solid-js");
60
+ var import_core = require("@json-render/core");
61
+ var import_store_utils = require("@json-render/core/store-utils");
62
+ var StateContext = (0, import_solid_js.createContext)(null);
63
+ function computeInitialFlat(isControlled, initialState) {
64
+ if (isControlled) return null;
65
+ if (Object.keys(initialState).length === 0) return {};
66
+ return (0, import_store_utils.flattenToPointers)(initialState);
67
+ }
68
+ function StateProvider(props) {
69
+ let internalStore;
70
+ if (!props.store) {
71
+ internalStore = (0, import_core.createStateStore)(props.initialState ?? {});
72
+ }
73
+ const store = () => props.store ?? internalStore;
74
+ const initialMode = props.store ? "controlled" : "uncontrolled";
75
+ let modeWarned = false;
76
+ if (typeof globalThis !== "undefined" && globalThis.process?.env?.NODE_ENV !== "production") {
77
+ (0, import_solid_js.createEffect)(() => {
78
+ const currentMode = props.store ? "controlled" : "uncontrolled";
79
+ if (currentMode !== initialMode && !modeWarned) {
80
+ modeWarned = true;
81
+ console.warn(`StateProvider: switching from ${initialMode} to ${currentMode} mode is not supported.`);
82
+ }
83
+ });
84
+ }
85
+ let prevInitialState = props.initialState;
86
+ let prevFlat = computeInitialFlat(!!props.store, props.initialState ?? {});
87
+ (0, import_solid_js.createEffect)(() => {
88
+ if (props.store) return;
89
+ const initialState = props.initialState ?? {};
90
+ if (initialState === prevInitialState) return;
91
+ prevInitialState = initialState;
92
+ const nextFlat = initialState && Object.keys(initialState).length > 0 ? (0, import_store_utils.flattenToPointers)(initialState) : {};
93
+ const prevFlatObj = prevFlat ?? {};
94
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(prevFlatObj), ...Object.keys(nextFlat)]);
95
+ const updates = {};
96
+ for (const key of allKeys) {
97
+ if (prevFlatObj[key] !== nextFlat[key]) {
98
+ updates[key] = key in nextFlat ? nextFlat[key] : void 0;
99
+ }
100
+ }
101
+ prevFlat = nextFlat;
102
+ if (Object.keys(updates).length > 0) {
103
+ store().update(updates);
104
+ }
105
+ });
106
+ const [state, setState] = (0, import_solid_js.createSignal)(store().getSnapshot(), {
107
+ equals: false
108
+ });
109
+ const changeListeners = /* @__PURE__ */ new Set();
110
+ const subscribeChanges = (listener) => {
111
+ changeListeners.add(listener);
112
+ return () => {
113
+ changeListeners.delete(listener);
114
+ };
115
+ };
116
+ const notifyChanges = (changes) => {
117
+ for (const listener of changeListeners) {
118
+ listener(changes);
119
+ }
120
+ };
121
+ (0, import_solid_js.createEffect)(() => {
122
+ const s = store();
123
+ setState(s.getSnapshot());
124
+ const unsubscribe = s.subscribe(() => {
125
+ setState(s.getSnapshot());
126
+ });
127
+ (0, import_solid_js.onCleanup)(unsubscribe);
128
+ });
129
+ const set = (path, value) => {
130
+ const s = store();
131
+ const prev = s.getSnapshot();
132
+ const prevValue = (0, import_core.getByPath)(prev, path);
133
+ s.set(path, value);
134
+ if (prevValue !== value) {
135
+ const changes = [{
136
+ path,
137
+ value
138
+ }];
139
+ notifyChanges(changes);
140
+ if (!props.store && s.getSnapshot() !== prev) {
141
+ props.onStateChange?.(changes);
142
+ }
143
+ }
144
+ };
145
+ const update = (updates) => {
146
+ const s = store();
147
+ const prev = s.getSnapshot();
148
+ s.update(updates);
149
+ const changes = [];
150
+ for (const [path, value] of Object.entries(updates)) {
151
+ if ((0, import_core.getByPath)(prev, path) !== value) {
152
+ changes.push({
153
+ path,
154
+ value
155
+ });
156
+ }
157
+ }
158
+ if (changes.length > 0) {
159
+ notifyChanges(changes);
160
+ if (!props.store && s.getSnapshot() !== prev) {
161
+ props.onStateChange?.(changes);
162
+ }
163
+ }
164
+ };
165
+ const get = (path) => store().get(path);
166
+ const getSnapshot = () => store().getSnapshot();
167
+ const ctx = {
168
+ get state() {
169
+ return state();
170
+ },
171
+ get,
172
+ set,
173
+ update,
174
+ getSnapshot,
175
+ subscribeChanges
176
+ };
177
+ return (0, import_web.createComponent)(StateContext.Provider, {
178
+ value: ctx,
179
+ get children() {
180
+ return props.children;
181
+ }
182
+ });
183
+ }
184
+ function useStateStore() {
185
+ const ctx = (0, import_solid_js.useContext)(StateContext);
186
+ if (!ctx) {
187
+ throw new Error("useStateStore must be used within a StateProvider");
188
+ }
189
+ return ctx;
190
+ }
191
+ function useStateValue(path) {
192
+ const store = useStateStore();
193
+ return (0, import_solid_js.createMemo)(() => (0, import_core.getByPath)(store.state, path));
194
+ }
195
+ function useStateBinding(path) {
196
+ const store = useStateStore();
197
+ const value = (0, import_solid_js.createMemo)(() => (0, import_core.getByPath)(store.state, path));
198
+ const setValue = (newValue) => store.set(path, newValue);
199
+ return [value, setValue];
200
+ }
201
+
202
+ // src/contexts/visibility.tsx
203
+ var import_web2 = require("solid-js/web");
204
+ var import_solid_js2 = require("solid-js");
205
+ var import_core2 = require("@json-render/core");
206
+ var VisibilityContext = (0, import_solid_js2.createContext)(null);
207
+ function VisibilityProvider(props) {
208
+ const stateStore = useStateStore();
209
+ const visibilityCtx = {
210
+ get stateModel() {
211
+ return stateStore.state;
212
+ }
213
+ };
214
+ const value = {
215
+ isVisible: (condition) => (0, import_core2.evaluateVisibility)(condition, visibilityCtx),
216
+ ctx: visibilityCtx
217
+ };
218
+ return (0, import_web2.createComponent)(VisibilityContext.Provider, {
219
+ value,
220
+ get children() {
221
+ return props.children;
222
+ }
223
+ });
224
+ }
225
+ function useVisibility() {
226
+ const ctx = (0, import_solid_js2.useContext)(VisibilityContext);
227
+ if (!ctx) {
228
+ throw new Error("useVisibility must be used within a VisibilityProvider");
229
+ }
230
+ return ctx;
231
+ }
232
+ function useIsVisible(condition) {
233
+ const {
234
+ isVisible
235
+ } = useVisibility();
236
+ return isVisible(condition);
237
+ }
238
+
239
+ // src/contexts/actions.tsx
240
+ var import_web4 = require("solid-js/web");
241
+ var import_web5 = require("solid-js/web");
242
+ var import_web6 = require("solid-js/web");
243
+ var import_web7 = require("solid-js/web");
244
+ var import_web8 = require("solid-js/web");
245
+ var import_web9 = require("solid-js/web");
246
+ var import_web10 = require("solid-js/web");
247
+ var import_web11 = require("solid-js/web");
248
+ var import_solid_js4 = require("solid-js");
249
+ var import_core4 = require("@json-render/core");
250
+
251
+ // src/contexts/validation.tsx
252
+ var import_web3 = require("solid-js/web");
253
+ var import_solid_js3 = require("solid-js");
254
+ var import_core3 = require("@json-render/core");
255
+ var ValidationContext = (0, import_solid_js3.createContext)(null);
256
+ function dynamicArgsEqual(a, b) {
257
+ if (a === b) return true;
258
+ if (!a || !b) return false;
259
+ const keysA = Object.keys(a);
260
+ const keysB = Object.keys(b);
261
+ if (keysA.length !== keysB.length) return false;
262
+ for (const key of keysA) {
263
+ const va = a[key];
264
+ const vb = b[key];
265
+ if (va === vb) continue;
266
+ if (typeof va === "object" && va !== null && typeof vb === "object" && vb !== null) {
267
+ const sa = va.$state;
268
+ const sb = vb.$state;
269
+ if (typeof sa === "string" && sa === sb) continue;
270
+ }
271
+ return false;
272
+ }
273
+ return true;
274
+ }
275
+ function validationConfigEqual(a, b) {
276
+ if (a === b) return true;
277
+ if (a.validateOn !== b.validateOn) return false;
278
+ const ac = a.checks ?? [];
279
+ const bc = b.checks ?? [];
280
+ if (ac.length !== bc.length) return false;
281
+ for (let i = 0; i < ac.length; i++) {
282
+ const ca = ac[i];
283
+ const cb = bc[i];
284
+ if (ca.type !== cb.type) return false;
285
+ if (ca.message !== cb.message) return false;
286
+ if (!dynamicArgsEqual(ca.args, cb.args)) return false;
287
+ }
288
+ return true;
289
+ }
290
+ function ValidationProvider(props) {
291
+ const {
292
+ getSnapshot
293
+ } = useStateStore();
294
+ const customFunctions = () => props.customFunctions ?? {};
295
+ const [fieldStates, setFieldStates] = (0, import_solid_js3.createSignal)({});
296
+ let fieldStatesRef = {};
297
+ const [fieldConfigs, setFieldConfigs] = (0, import_solid_js3.createSignal)({});
298
+ const registerField = (path, config) => {
299
+ setFieldConfigs((prev) => {
300
+ const existing = prev[path];
301
+ if (existing && validationConfigEqual(existing, config)) {
302
+ return prev;
303
+ }
304
+ return {
305
+ ...prev,
306
+ [path]: config
307
+ };
308
+ });
309
+ };
310
+ const validate = (path, config) => {
311
+ const currentState = getSnapshot();
312
+ const segments = path.split("/").filter(Boolean);
313
+ let value2 = currentState;
314
+ for (const seg of segments) {
315
+ if (value2 != null && typeof value2 === "object") {
316
+ value2 = value2[seg];
317
+ } else {
318
+ value2 = void 0;
319
+ break;
320
+ }
321
+ }
322
+ const result = (0, import_core3.runValidation)(config, {
323
+ value: value2,
324
+ stateModel: currentState,
325
+ customFunctions: customFunctions()
326
+ });
327
+ const newFieldState = {
328
+ touched: fieldStatesRef[path]?.touched ?? true,
329
+ validated: true,
330
+ result
331
+ };
332
+ fieldStatesRef = {
333
+ ...fieldStatesRef,
334
+ [path]: newFieldState
335
+ };
336
+ setFieldStates(fieldStatesRef);
337
+ return result;
338
+ };
339
+ const touch = (path) => {
340
+ fieldStatesRef = {
341
+ ...fieldStatesRef,
342
+ [path]: {
343
+ ...fieldStatesRef[path],
344
+ touched: true,
345
+ validated: fieldStatesRef[path]?.validated ?? false,
346
+ result: fieldStatesRef[path]?.result ?? null
347
+ }
348
+ };
349
+ setFieldStates(fieldStatesRef);
350
+ };
351
+ const clear = (path) => {
352
+ const {
353
+ [path]: _,
354
+ ...rest
355
+ } = fieldStatesRef;
356
+ fieldStatesRef = rest;
357
+ setFieldStates(rest);
358
+ };
359
+ const validateAll = () => {
360
+ let allValid = true;
361
+ for (const [path, config] of Object.entries(fieldConfigs())) {
362
+ const result = validate(path, config);
363
+ if (!result.valid) {
364
+ allValid = false;
365
+ }
366
+ }
367
+ return allValid;
368
+ };
369
+ const value = {
370
+ get customFunctions() {
371
+ return customFunctions();
372
+ },
373
+ get fieldStates() {
374
+ fieldStates();
375
+ return fieldStatesRef;
376
+ },
377
+ validate,
378
+ touch,
379
+ clear,
380
+ validateAll,
381
+ registerField
382
+ };
383
+ return (0, import_web3.createComponent)(ValidationContext.Provider, {
384
+ value,
385
+ get children() {
386
+ return props.children;
387
+ }
388
+ });
389
+ }
390
+ function useValidation() {
391
+ const ctx = (0, import_solid_js3.useContext)(ValidationContext);
392
+ if (!ctx) {
393
+ throw new Error("useValidation must be used within a ValidationProvider");
394
+ }
395
+ return ctx;
396
+ }
397
+ function useOptionalValidation() {
398
+ return (0, import_solid_js3.useContext)(ValidationContext);
399
+ }
400
+ function useFieldValidation(path, config) {
401
+ const validation = useValidation();
402
+ (0, import_solid_js3.createEffect)(() => {
403
+ if (path && config) {
404
+ validation.registerField(path, config);
405
+ }
406
+ });
407
+ const state = (0, import_solid_js3.createMemo)(() => {
408
+ const current = validation.fieldStates[path];
409
+ return current ?? {
410
+ touched: false,
411
+ validated: false,
412
+ result: null
413
+ };
414
+ });
415
+ const validate = () => validation.validate(path, config ?? {
416
+ checks: []
417
+ });
418
+ const touch = () => validation.touch(path);
419
+ const clear = () => validation.clear(path);
420
+ const errors = (0, import_solid_js3.createMemo)(() => state().result?.errors ?? []);
421
+ const isValid = (0, import_solid_js3.createMemo)(() => state().result?.valid ?? true);
422
+ return {
423
+ state,
424
+ validate,
425
+ touch,
426
+ clear,
427
+ errors,
428
+ isValid
429
+ };
430
+ }
431
+
432
+ // src/contexts/actions.tsx
433
+ var _tmpl$ = /* @__PURE__ */ (0, import_web4.template)(`<div style="position:fixed;inset:0;background-color:rgba(0, 0, 0, 0.5);display:flex;align-items:center;justify-content:center;z-index:50"><div style="background-color:white;border-radius:8px;padding:24px;max-width:400px;width:100%;box-shadow:0 20px 25px -5px rgba(0, 0, 0, 0.1)"><h3 style="margin:0 0 8px 0;font-size:18px;font-weight:600"></h3><p style="margin:0 0 24px 0;color:#6b7280"></p><div style=display:flex;gap:12px;justify-content:flex-end><button style="padding:8px 16px;border-radius:6px;border:1px solid #d1d5db;background-color:white;cursor:pointer"></button><button style="padding:8px 16px;border-radius:6px;border:none;color:white;cursor:pointer">`);
434
+ var idCounter = 0;
435
+ function generateUniqueId() {
436
+ idCounter += 1;
437
+ return `${Date.now()}-${idCounter}`;
438
+ }
439
+ function deepResolveValue(value, get) {
440
+ if (value === null || value === void 0) return value;
441
+ if (value === "$id") {
442
+ return generateUniqueId();
443
+ }
444
+ if (typeof value === "object" && !Array.isArray(value)) {
445
+ const obj = value;
446
+ const keys = Object.keys(obj);
447
+ if (keys.length === 1 && typeof obj.$state === "string") {
448
+ return get(obj.$state);
449
+ }
450
+ if (keys.length === 1 && "$id" in obj) {
451
+ return generateUniqueId();
452
+ }
453
+ }
454
+ if (Array.isArray(value)) {
455
+ return value.map((item) => deepResolveValue(item, get));
456
+ }
457
+ if (typeof value === "object") {
458
+ const resolved = {};
459
+ for (const [key, val] of Object.entries(value)) {
460
+ resolved[key] = deepResolveValue(val, get);
461
+ }
462
+ return resolved;
463
+ }
464
+ return value;
465
+ }
466
+ var ActionContext = (0, import_solid_js4.createContext)(null);
467
+ function ActionProvider(props) {
468
+ const {
469
+ get,
470
+ set,
471
+ getSnapshot
472
+ } = useStateStore();
473
+ const validation = useOptionalValidation();
474
+ const [handlers, setHandlers] = (0, import_solid_js4.createSignal)(props.handlers ?? {});
475
+ const [loadingActions, setLoadingActions] = (0, import_solid_js4.createSignal)(/* @__PURE__ */ new Set());
476
+ const [pendingConfirmation, setPendingConfirmation] = (0, import_solid_js4.createSignal)(null);
477
+ const registerHandler = (name, handler) => {
478
+ setHandlers((prev) => ({
479
+ ...prev,
480
+ [name]: handler
481
+ }));
482
+ };
483
+ const execute = async (binding) => {
484
+ const resolved = (0, import_core4.resolveAction)(binding, getSnapshot());
485
+ if (resolved.action === "setState" && resolved.params) {
486
+ const statePath = resolved.params.statePath;
487
+ const value = resolved.params.value;
488
+ if (statePath) {
489
+ set(statePath, value);
490
+ }
491
+ return;
492
+ }
493
+ if (resolved.action === "pushState" && resolved.params) {
494
+ const statePath = resolved.params.statePath;
495
+ const rawValue = resolved.params.value;
496
+ if (statePath) {
497
+ const resolvedValue = deepResolveValue(rawValue, get);
498
+ const arr = get(statePath) ?? [];
499
+ set(statePath, [...arr, resolvedValue]);
500
+ const clearStatePath = resolved.params.clearStatePath;
501
+ if (clearStatePath) {
502
+ set(clearStatePath, "");
503
+ }
504
+ }
505
+ return;
506
+ }
507
+ if (resolved.action === "removeState" && resolved.params) {
508
+ const statePath = resolved.params.statePath;
509
+ const index = resolved.params.index;
510
+ if (statePath !== void 0 && index !== void 0) {
511
+ const arr = get(statePath) ?? [];
512
+ set(statePath, arr.filter((_, i) => i !== index));
513
+ }
514
+ return;
515
+ }
516
+ if (resolved.action === "push" && resolved.params) {
517
+ const screen = resolved.params.screen;
518
+ if (screen) {
519
+ const currentScreen = get("/currentScreen");
520
+ const navStack = get("/navStack") ?? [];
521
+ if (currentScreen) {
522
+ set("/navStack", [...navStack, currentScreen]);
523
+ } else {
524
+ set("/navStack", [...navStack, ""]);
525
+ }
526
+ set("/currentScreen", screen);
527
+ }
528
+ return;
529
+ }
530
+ if (resolved.action === "pop") {
531
+ const navStack = get("/navStack") ?? [];
532
+ if (navStack.length > 0) {
533
+ const previousScreen = navStack[navStack.length - 1];
534
+ set("/navStack", navStack.slice(0, -1));
535
+ if (previousScreen) {
536
+ set("/currentScreen", previousScreen);
537
+ } else {
538
+ set("/currentScreen", void 0);
539
+ }
540
+ }
541
+ return;
542
+ }
543
+ if (resolved.action === "validateForm") {
544
+ const validateAll = validation?.validateAll;
545
+ if (!validateAll) {
546
+ console.warn("validateForm action was dispatched but no ValidationProvider is connected. Ensure ValidationProvider is rendered inside the provider tree.");
547
+ return;
548
+ }
549
+ const valid = validateAll();
550
+ const errors = {};
551
+ for (const [path, fs] of Object.entries(validation.fieldStates)) {
552
+ if (fs.result && !fs.result.valid) {
553
+ errors[path] = fs.result.errors;
554
+ }
555
+ }
556
+ const statePath = resolved.params?.statePath || "/formValidation";
557
+ set(statePath, {
558
+ valid,
559
+ errors
560
+ });
561
+ return;
562
+ }
563
+ const handler = handlers()[resolved.action];
564
+ if (!handler) {
565
+ console.warn(`No handler registered for action: ${resolved.action}`);
566
+ return;
567
+ }
568
+ if (resolved.confirm) {
569
+ return new Promise((resolve, reject) => {
570
+ setPendingConfirmation({
571
+ action: resolved,
572
+ handler,
573
+ resolve: () => {
574
+ setPendingConfirmation(null);
575
+ resolve();
576
+ },
577
+ reject: () => {
578
+ setPendingConfirmation(null);
579
+ reject(new Error("Action cancelled"));
580
+ }
581
+ });
582
+ }).then(async () => {
583
+ setLoadingActions((prev) => new Set(prev).add(resolved.action));
584
+ try {
585
+ await (0, import_core4.executeAction)({
586
+ action: resolved,
587
+ handler,
588
+ setState: set,
589
+ navigate: props.navigate,
590
+ executeAction: async (name) => {
591
+ const subBinding = {
592
+ action: name
593
+ };
594
+ await execute(subBinding);
595
+ }
596
+ });
597
+ } finally {
598
+ setLoadingActions((prev) => {
599
+ const next = new Set(prev);
600
+ next.delete(resolved.action);
601
+ return next;
602
+ });
603
+ }
604
+ });
605
+ }
606
+ setLoadingActions((prev) => new Set(prev).add(resolved.action));
607
+ try {
608
+ await (0, import_core4.executeAction)({
609
+ action: resolved,
610
+ handler,
611
+ setState: set,
612
+ navigate: props.navigate,
613
+ executeAction: async (name) => {
614
+ const subBinding = {
615
+ action: name
616
+ };
617
+ await execute(subBinding);
618
+ }
619
+ });
620
+ } finally {
621
+ setLoadingActions((prev) => {
622
+ const next = new Set(prev);
623
+ next.delete(resolved.action);
624
+ return next;
625
+ });
626
+ }
627
+ };
628
+ const confirm = () => {
629
+ pendingConfirmation()?.resolve();
630
+ };
631
+ const cancel = () => {
632
+ pendingConfirmation()?.reject();
633
+ };
634
+ const ctx = {
635
+ get handlers() {
636
+ return handlers();
637
+ },
638
+ get loadingActions() {
639
+ return loadingActions();
640
+ },
641
+ get pendingConfirmation() {
642
+ return pendingConfirmation();
643
+ },
644
+ execute,
645
+ confirm,
646
+ cancel,
647
+ registerHandler
648
+ };
649
+ return (0, import_web11.createComponent)(ActionContext.Provider, {
650
+ value: ctx,
651
+ get children() {
652
+ return props.children;
653
+ }
654
+ });
655
+ }
656
+ function useActions() {
657
+ const ctx = (0, import_solid_js4.useContext)(ActionContext);
658
+ if (!ctx) {
659
+ throw new Error("useActions must be used within an ActionProvider");
660
+ }
661
+ return ctx;
662
+ }
663
+ function useAction(binding) {
664
+ const actions = useActions();
665
+ return {
666
+ execute: () => actions.execute(binding),
667
+ get isLoading() {
668
+ return actions.loadingActions.has(binding.action);
669
+ }
670
+ };
671
+ }
672
+ function ConfirmDialog(props) {
673
+ const isDanger = () => props.confirm.variant === "danger";
674
+ return (() => {
675
+ var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling;
676
+ (0, import_web10.addEventListener)(_el$, "click", props.onCancel, true);
677
+ _el$2.$$click = (e) => e.stopPropagation();
678
+ (0, import_web9.insert)(_el$3, () => props.confirm.title);
679
+ (0, import_web9.insert)(_el$4, () => props.confirm.message);
680
+ (0, import_web10.addEventListener)(_el$6, "click", props.onCancel, true);
681
+ (0, import_web9.insert)(_el$6, () => props.confirm.cancelLabel ?? "Cancel");
682
+ (0, import_web10.addEventListener)(_el$7, "click", props.onConfirm, true);
683
+ (0, import_web9.insert)(_el$7, () => props.confirm.confirmLabel ?? "Confirm");
684
+ (0, import_web7.effect)((_$p) => (0, import_web6.setStyleProperty)(_el$7, "background-color", isDanger() ? "#dc2626" : "#3b82f6"));
685
+ return _el$;
686
+ })();
687
+ }
688
+ (0, import_web5.delegateEvents)(["click"]);
689
+
690
+ // src/contexts/repeat-scope.tsx
691
+ var import_web12 = require("solid-js/web");
692
+ var import_solid_js5 = require("solid-js");
693
+ var RepeatScopeContext = (0, import_solid_js5.createContext)(null);
694
+ function RepeatScopeProvider(props) {
695
+ return (0, import_web12.createComponent)(RepeatScopeContext.Provider, {
696
+ get value() {
697
+ return {
698
+ item: props.item,
699
+ index: props.index,
700
+ basePath: props.basePath
701
+ };
702
+ },
703
+ get children() {
704
+ return props.children;
705
+ }
706
+ });
707
+ }
708
+ function useRepeatScope() {
709
+ return (0, import_solid_js5.useContext)(RepeatScopeContext);
710
+ }
711
+
712
+ // src/schema.ts
713
+ var import_core5 = require("@json-render/core");
714
+ var schema = (0, import_core5.defineSchema)(
715
+ (s) => ({
716
+ // What the AI-generated SPEC looks like
717
+ spec: s.object({
718
+ /** Root element key */
719
+ root: s.string(),
720
+ /** Flat map of elements by key */
721
+ elements: s.record(
722
+ s.object({
723
+ /** Component type from catalog */
724
+ type: s.ref("catalog.components"),
725
+ /** Component props */
726
+ props: s.propsOf("catalog.components"),
727
+ /** Child element keys (flat reference) */
728
+ children: s.array(s.string()),
729
+ /** Visibility condition */
730
+ visible: s.any()
731
+ })
732
+ )
733
+ }),
734
+ // What the CATALOG must provide
735
+ catalog: s.object({
736
+ /** Component definitions */
737
+ components: s.map({
738
+ /** Zod schema for component props */
739
+ props: s.zod(),
740
+ /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
741
+ slots: s.array(s.string()),
742
+ /** Description for AI generation hints */
743
+ description: s.string(),
744
+ /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
745
+ example: s.any()
746
+ }),
747
+ /** Action definitions (optional) */
748
+ actions: s.map({
749
+ /** Zod schema for action params */
750
+ params: s.zod(),
751
+ /** Description for AI generation hints */
752
+ description: s.string()
753
+ })
754
+ })
755
+ }),
756
+ {
757
+ builtInActions: [
758
+ {
759
+ name: "setState",
760
+ description: "Update a value in the state model at the given statePath. Params: { statePath: string, value: any }"
761
+ },
762
+ {
763
+ name: "pushState",
764
+ description: 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.'
765
+ },
766
+ {
767
+ name: "removeState",
768
+ description: "Remove an item from an array in state by index. Params: { statePath: string, index: number }"
769
+ },
770
+ {
771
+ name: "validateForm",
772
+ description: "Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }."
773
+ }
774
+ ],
775
+ defaultRules: [
776
+ // Element integrity
777
+ "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.",
778
+ "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.",
779
+ // Field placement
780
+ 'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
781
+ 'CRITICAL: The "on" field goes on the ELEMENT object, NOT inside "props". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',
782
+ // State and data
783
+ "When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).",
784
+ 'When building repeating content backed by a state array (e.g. posts, products, items), use the "repeat" field on a container element. Example: { "type": "<ContainerComponent>", "props": {}, "repeat": { "statePath": "/posts", "key": "id" }, "children": ["post-card"] }. Replace <ContainerComponent> with an appropriate component from the AVAILABLE COMPONENTS list. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
785
+ // Design quality
786
+ "Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.",
787
+ "For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.",
788
+ "Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty."
789
+ ]
790
+ }
791
+ );
792
+ var elementTreeSchema = schema;
793
+
794
+ // src/index.ts
795
+ var import_core8 = require("@json-render/core");
796
+
797
+ // src/renderer.tsx
798
+ var import_web13 = require("solid-js/web");
799
+ var import_web14 = require("solid-js/web");
800
+ var import_solid_js6 = require("solid-js");
801
+ var import_core6 = require("@json-render/core");
802
+ var EMPTY_FUNCTIONS = {};
803
+ var FunctionsContext = (0, import_solid_js6.createContext)(EMPTY_FUNCTIONS);
804
+ function useFunctions() {
805
+ return (0, import_solid_js6.useContext)(FunctionsContext) ?? EMPTY_FUNCTIONS;
806
+ }
807
+ function ElementRenderer(props) {
808
+ const repeatScope = useRepeatScope();
809
+ const {
810
+ ctx
811
+ } = useVisibility();
812
+ const {
813
+ execute
814
+ } = useActions();
815
+ const stateStore = useStateStore();
816
+ const getSnapshot = () => stateStore.getSnapshot();
817
+ const functions = useFunctions();
818
+ const fullCtx = (0, import_solid_js6.createMemo)(() => {
819
+ const repeatItem = repeatScope?.item;
820
+ const repeatIndex = repeatScope?.index;
821
+ const repeatBasePath = repeatScope?.basePath;
822
+ return {
823
+ get stateModel() {
824
+ return ctx.stateModel;
825
+ },
826
+ ...repeatItem !== void 0 ? {
827
+ repeatItem
828
+ } : {},
829
+ ...repeatIndex !== void 0 ? {
830
+ repeatIndex
831
+ } : {},
832
+ ...repeatBasePath !== void 0 ? {
833
+ repeatBasePath
834
+ } : {},
835
+ functions
836
+ };
837
+ });
838
+ const isVisible = (0, import_solid_js6.createMemo)(() => props.element.visible === void 0 ? true : (0, import_core6.evaluateVisibility)(props.element.visible, fullCtx()));
839
+ const emit = async (eventName) => {
840
+ const onBindings = props.element.on;
841
+ const binding = onBindings?.[eventName];
842
+ if (!binding) return;
843
+ const actionBindings = Array.isArray(binding) ? binding : [binding];
844
+ for (const b of actionBindings) {
845
+ if (!b.params) {
846
+ await execute(b);
847
+ continue;
848
+ }
849
+ const liveCtx = {
850
+ ...fullCtx(),
851
+ stateModel: getSnapshot()
852
+ };
853
+ const resolved = {};
854
+ for (const [key, val] of Object.entries(b.params)) {
855
+ resolved[key] = (0, import_core6.resolveActionParam)(val, liveCtx);
856
+ }
857
+ await execute({
858
+ ...b,
859
+ params: resolved
860
+ });
861
+ }
862
+ };
863
+ const on = (eventName) => {
864
+ const onBindings = props.element.on;
865
+ const binding = onBindings?.[eventName];
866
+ if (!binding) {
867
+ return {
868
+ emit: () => {
869
+ },
870
+ shouldPreventDefault: false,
871
+ bound: false
872
+ };
873
+ }
874
+ const actionBindings = Array.isArray(binding) ? binding : [binding];
875
+ const shouldPreventDefault = actionBindings.some((b) => b.preventDefault);
876
+ return {
877
+ emit: () => emit(eventName),
878
+ shouldPreventDefault,
879
+ bound: true
880
+ };
881
+ };
882
+ (0, import_solid_js6.createEffect)(() => {
883
+ const watchConfig = props.element.watch;
884
+ if (!watchConfig) return;
885
+ const paths = Object.keys(watchConfig);
886
+ if (paths.length === 0) return;
887
+ const unsubscribe = stateStore.subscribeChanges((changes) => {
888
+ const changedPaths = new Set(changes.map((change) => change.path));
889
+ void (async () => {
890
+ for (const path of paths) {
891
+ if (!changedPaths.has(path)) continue;
892
+ const binding = watchConfig[path];
893
+ if (!binding) continue;
894
+ const bindings = Array.isArray(binding) ? binding : [binding];
895
+ for (const b of bindings) {
896
+ if (!b.params) {
897
+ await execute(b);
898
+ continue;
899
+ }
900
+ const liveCtx = {
901
+ ...fullCtx(),
902
+ stateModel: getSnapshot()
903
+ };
904
+ const resolved = {};
905
+ for (const [key, val] of Object.entries(b.params)) {
906
+ resolved[key] = (0, import_core6.resolveActionParam)(val, liveCtx);
907
+ }
908
+ await execute({
909
+ ...b,
910
+ params: resolved
911
+ });
912
+ }
913
+ }
914
+ })().catch(console.error);
915
+ });
916
+ (0, import_solid_js6.onCleanup)(() => {
917
+ unsubscribe();
918
+ });
919
+ });
920
+ const elementBindings = (0, import_solid_js6.createMemo)(() => {
921
+ const rawProps = props.element.props;
922
+ return (0, import_core6.resolveBindings)(rawProps, fullCtx());
923
+ });
924
+ const resolvedElement = (0, import_solid_js6.createMemo)(() => {
925
+ const rawProps = props.element.props;
926
+ const resolvedProps = (0, import_core6.resolveElementProps)(rawProps, fullCtx());
927
+ return resolvedProps !== props.element.props ? {
928
+ ...props.element,
929
+ props: resolvedProps
930
+ } : props.element;
931
+ });
932
+ return (0, import_web14.createComponent)(import_solid_js6.Show, {
933
+ get when() {
934
+ return isVisible();
935
+ },
936
+ get children() {
937
+ return (0, import_web14.createComponent)(import_solid_js6.ErrorBoundary, {
938
+ fallback: (err) => {
939
+ console.error(`[json-render] Rendering error in <${props.element.type}>:`, err);
940
+ return null;
941
+ },
942
+ get children() {
943
+ return (0, import_web14.createComponent)(ElementRendererContent, {
944
+ get resolvedElement() {
945
+ return resolvedElement();
946
+ },
947
+ get spec() {
948
+ return props.spec;
949
+ },
950
+ get registry() {
951
+ return props.registry;
952
+ },
953
+ get loading() {
954
+ return props.loading;
955
+ },
956
+ get fallback() {
957
+ return props.fallback;
958
+ },
959
+ emit,
960
+ on,
961
+ get bindings() {
962
+ return elementBindings();
963
+ }
964
+ });
965
+ }
966
+ });
967
+ }
968
+ });
969
+ }
970
+ function ElementRendererContent(props) {
971
+ const Comp = () => props.registry[props.resolvedElement.type] ?? props.fallback;
972
+ return (0, import_web14.createComponent)(import_solid_js6.Show, {
973
+ get when() {
974
+ return Comp();
975
+ },
976
+ get fallback() {
977
+ console.warn(`No renderer for component type: ${props.resolvedElement.type}`);
978
+ return null;
979
+ },
980
+ children: (ComponentFn) => {
981
+ const Component = ComponentFn();
982
+ const children = () => props.resolvedElement.repeat ? (0, import_web14.createComponent)(RepeatChildren, {
983
+ get element() {
984
+ return props.resolvedElement;
985
+ },
986
+ get spec() {
987
+ return props.spec;
988
+ },
989
+ get registry() {
990
+ return props.registry;
991
+ },
992
+ get loading() {
993
+ return props.loading;
994
+ },
995
+ get fallback() {
996
+ return props.fallback;
997
+ }
998
+ }) : (0, import_web14.createComponent)(import_solid_js6.For, {
999
+ get each() {
1000
+ return props.resolvedElement.children ?? [];
1001
+ },
1002
+ children: (childKey) => {
1003
+ const childElement = () => props.spec.elements[childKey];
1004
+ return (0, import_web14.createComponent)(import_solid_js6.Show, {
1005
+ get when() {
1006
+ return childElement();
1007
+ },
1008
+ get fallback() {
1009
+ if (!props.loading) {
1010
+ console.warn(`[json-render] Missing element "${childKey}" referenced as child of "${props.resolvedElement.type}". This element will not render.`);
1011
+ }
1012
+ return null;
1013
+ },
1014
+ children: (el) => (0, import_web14.createComponent)(ElementRenderer, {
1015
+ get element() {
1016
+ return el();
1017
+ },
1018
+ get spec() {
1019
+ return props.spec;
1020
+ },
1021
+ get registry() {
1022
+ return props.registry;
1023
+ },
1024
+ get loading() {
1025
+ return props.loading;
1026
+ },
1027
+ get fallback() {
1028
+ return props.fallback;
1029
+ }
1030
+ })
1031
+ });
1032
+ }
1033
+ });
1034
+ return (0, import_web14.createComponent)(Component, {
1035
+ get element() {
1036
+ return props.resolvedElement;
1037
+ },
1038
+ get emit() {
1039
+ return props.emit;
1040
+ },
1041
+ get on() {
1042
+ return props.on;
1043
+ },
1044
+ get bindings() {
1045
+ return props.bindings;
1046
+ },
1047
+ get loading() {
1048
+ return props.loading;
1049
+ },
1050
+ get children() {
1051
+ return children();
1052
+ }
1053
+ });
1054
+ }
1055
+ });
1056
+ }
1057
+ function RepeatChildren(props) {
1058
+ const stateStore = useStateStore();
1059
+ const repeat = () => props.element.repeat;
1060
+ const statePath = () => repeat().statePath;
1061
+ const items = () => (0, import_core6.getByPath)(stateStore.state, statePath()) ?? [];
1062
+ return (0, import_web14.createComponent)(import_solid_js6.For, {
1063
+ get each() {
1064
+ return items();
1065
+ },
1066
+ children: (itemValue, index) => {
1067
+ const key = () => {
1068
+ const r = repeat();
1069
+ return r.key && typeof itemValue === "object" && itemValue !== null ? String(itemValue[r.key] ?? index()) : String(index());
1070
+ };
1071
+ return (0, import_web14.createComponent)(RepeatScopeProvider, {
1072
+ item: itemValue,
1073
+ get index() {
1074
+ return index();
1075
+ },
1076
+ get basePath() {
1077
+ return `${statePath()}/${index()}`;
1078
+ },
1079
+ get children() {
1080
+ return (0, import_web14.createComponent)(import_solid_js6.For, {
1081
+ get each() {
1082
+ return props.element.children ?? [];
1083
+ },
1084
+ children: (childKey) => {
1085
+ const childElement = () => props.spec.elements[childKey];
1086
+ return (0, import_web14.createComponent)(import_solid_js6.Show, {
1087
+ get when() {
1088
+ return childElement();
1089
+ },
1090
+ get fallback() {
1091
+ if (!props.loading) {
1092
+ console.warn(`[json-render] Missing element "${childKey}" referenced as child of "${props.element.type}" (repeat). This element will not render.`);
1093
+ }
1094
+ return null;
1095
+ },
1096
+ children: (el) => (0, import_web14.createComponent)(ElementRenderer, {
1097
+ get element() {
1098
+ return el();
1099
+ },
1100
+ get spec() {
1101
+ return props.spec;
1102
+ },
1103
+ get registry() {
1104
+ return props.registry;
1105
+ },
1106
+ get loading() {
1107
+ return props.loading;
1108
+ },
1109
+ get fallback() {
1110
+ return props.fallback;
1111
+ }
1112
+ })
1113
+ });
1114
+ }
1115
+ });
1116
+ }
1117
+ });
1118
+ }
1119
+ });
1120
+ }
1121
+ function Renderer(props) {
1122
+ const rootElement = (0, import_solid_js6.createMemo)(() => {
1123
+ const spec = props.spec;
1124
+ if (!spec || !spec.root) return void 0;
1125
+ return spec.elements[spec.root];
1126
+ });
1127
+ return (0, import_web14.createComponent)(import_solid_js6.Show, {
1128
+ get when() {
1129
+ return rootElement();
1130
+ },
1131
+ children: (el) => (0, import_web14.createComponent)(ElementRenderer, {
1132
+ get element() {
1133
+ return el();
1134
+ },
1135
+ get spec() {
1136
+ return props.spec;
1137
+ },
1138
+ get registry() {
1139
+ return props.registry;
1140
+ },
1141
+ get loading() {
1142
+ return props.loading;
1143
+ },
1144
+ get fallback() {
1145
+ return props.fallback;
1146
+ }
1147
+ })
1148
+ });
1149
+ }
1150
+ function JSONUIProvider(props) {
1151
+ return (0, import_web14.createComponent)(StateProvider, {
1152
+ get store() {
1153
+ return props.store;
1154
+ },
1155
+ get initialState() {
1156
+ return props.initialState;
1157
+ },
1158
+ get onStateChange() {
1159
+ return props.onStateChange;
1160
+ },
1161
+ get children() {
1162
+ return (0, import_web14.createComponent)(VisibilityProvider, {
1163
+ get children() {
1164
+ return (0, import_web14.createComponent)(ValidationProvider, {
1165
+ get customFunctions() {
1166
+ return props.validationFunctions;
1167
+ },
1168
+ get children() {
1169
+ return (0, import_web14.createComponent)(ActionProvider, {
1170
+ get handlers() {
1171
+ return props.handlers;
1172
+ },
1173
+ get navigate() {
1174
+ return props.navigate;
1175
+ },
1176
+ get children() {
1177
+ return (0, import_web14.createComponent)(FunctionsContext.Provider, {
1178
+ get value() {
1179
+ return props.functions ?? EMPTY_FUNCTIONS;
1180
+ },
1181
+ get children() {
1182
+ return [(0, import_web13.memo)(() => props.children), (0, import_web14.createComponent)(ConfirmationDialogManager, {})];
1183
+ }
1184
+ });
1185
+ }
1186
+ });
1187
+ }
1188
+ });
1189
+ }
1190
+ });
1191
+ }
1192
+ });
1193
+ }
1194
+ function ConfirmationDialogManager() {
1195
+ const {
1196
+ pendingConfirmation,
1197
+ confirm,
1198
+ cancel
1199
+ } = useActions();
1200
+ return (0, import_web14.createComponent)(import_solid_js6.Show, {
1201
+ get when() {
1202
+ return pendingConfirmation?.action.confirm;
1203
+ },
1204
+ children: (confirmConfig) => (0, import_web14.createComponent)(ConfirmDialog, {
1205
+ get confirm() {
1206
+ return confirmConfig();
1207
+ },
1208
+ onConfirm: confirm,
1209
+ onCancel: cancel
1210
+ })
1211
+ });
1212
+ }
1213
+ function defineRegistry(_catalog, options) {
1214
+ const registry = {};
1215
+ if (options.components) {
1216
+ for (const [name, componentFn] of Object.entries(options.components)) {
1217
+ registry[name] = (renderProps) => {
1218
+ return componentFn({
1219
+ get props() {
1220
+ return renderProps.element.props;
1221
+ },
1222
+ get children() {
1223
+ return renderProps.children;
1224
+ },
1225
+ emit: renderProps.emit,
1226
+ on: renderProps.on,
1227
+ get bindings() {
1228
+ return renderProps.bindings;
1229
+ },
1230
+ get loading() {
1231
+ return renderProps.loading;
1232
+ }
1233
+ });
1234
+ };
1235
+ }
1236
+ }
1237
+ const actionMap = options.actions ? Object.entries(options.actions) : [];
1238
+ const handlers = (getSetState, getState) => {
1239
+ const result = {};
1240
+ for (const [name, actionFn] of actionMap) {
1241
+ result[name] = async (params) => {
1242
+ const setState = getSetState();
1243
+ const state = getState();
1244
+ if (setState) {
1245
+ await actionFn(params, setState, state);
1246
+ }
1247
+ };
1248
+ }
1249
+ return result;
1250
+ };
1251
+ const executeAction2 = async (actionName, params, setState, state = {}) => {
1252
+ const entry = actionMap.find(([name]) => name === actionName);
1253
+ if (entry) {
1254
+ await entry[1](params, setState, state);
1255
+ } else {
1256
+ console.warn(`Unknown action: ${actionName}`);
1257
+ }
1258
+ };
1259
+ return {
1260
+ registry,
1261
+ handlers,
1262
+ executeAction: executeAction2
1263
+ };
1264
+ }
1265
+ function createRenderer(catalog, components) {
1266
+ const registry = components;
1267
+ return function CatalogRenderer(props) {
1268
+ const actionHandlers = () => props.onAction ? new Proxy({}, {
1269
+ get: (_target, prop) => {
1270
+ return (params) => props.onAction(prop, params);
1271
+ },
1272
+ has: () => true
1273
+ }) : void 0;
1274
+ return (0, import_web14.createComponent)(StateProvider, {
1275
+ get store() {
1276
+ return props.store;
1277
+ },
1278
+ get initialState() {
1279
+ return props.state;
1280
+ },
1281
+ get onStateChange() {
1282
+ return props.onStateChange;
1283
+ },
1284
+ get children() {
1285
+ return (0, import_web14.createComponent)(VisibilityProvider, {
1286
+ get children() {
1287
+ return (0, import_web14.createComponent)(ValidationProvider, {
1288
+ get children() {
1289
+ return (0, import_web14.createComponent)(ActionProvider, {
1290
+ get handlers() {
1291
+ return actionHandlers();
1292
+ },
1293
+ get children() {
1294
+ return (0, import_web14.createComponent)(FunctionsContext.Provider, {
1295
+ get value() {
1296
+ return props.functions ?? EMPTY_FUNCTIONS;
1297
+ },
1298
+ get children() {
1299
+ return [(0, import_web14.createComponent)(Renderer, {
1300
+ get spec() {
1301
+ return props.spec;
1302
+ },
1303
+ registry,
1304
+ get loading() {
1305
+ return props.loading;
1306
+ },
1307
+ get fallback() {
1308
+ return props.fallback;
1309
+ }
1310
+ }), (0, import_web14.createComponent)(ConfirmationDialogManager, {})];
1311
+ }
1312
+ });
1313
+ }
1314
+ });
1315
+ }
1316
+ });
1317
+ }
1318
+ });
1319
+ }
1320
+ });
1321
+ };
1322
+ }
1323
+
1324
+ // src/hooks.ts
1325
+ var import_solid_js7 = require("solid-js");
1326
+ var import_core7 = require("@json-render/core");
1327
+ function parseLine(line) {
1328
+ try {
1329
+ const trimmed = line.trim();
1330
+ if (!trimmed || trimmed.startsWith("//")) {
1331
+ return null;
1332
+ }
1333
+ const parsed = JSON.parse(trimmed);
1334
+ if (parsed.__meta === "usage") {
1335
+ return {
1336
+ type: "usage",
1337
+ usage: {
1338
+ promptTokens: parsed.promptTokens ?? 0,
1339
+ completionTokens: parsed.completionTokens ?? 0,
1340
+ totalTokens: parsed.totalTokens ?? 0
1341
+ }
1342
+ };
1343
+ }
1344
+ return { type: "patch", patch: parsed };
1345
+ } catch {
1346
+ return null;
1347
+ }
1348
+ }
1349
+ function setSpecValue(newSpec, path, value) {
1350
+ if (path === "/root") {
1351
+ newSpec.root = value;
1352
+ return;
1353
+ }
1354
+ if (path === "/state") {
1355
+ newSpec.state = value;
1356
+ return;
1357
+ }
1358
+ if (path.startsWith("/state/")) {
1359
+ if (!newSpec.state) newSpec.state = {};
1360
+ const statePath = path.slice("/state".length);
1361
+ (0, import_core7.setByPath)(newSpec.state, statePath, value);
1362
+ return;
1363
+ }
1364
+ if (path.startsWith("/elements/")) {
1365
+ const pathParts = path.slice("/elements/".length).split("/");
1366
+ const elementKey = pathParts[0];
1367
+ if (!elementKey) return;
1368
+ if (pathParts.length === 1) {
1369
+ newSpec.elements[elementKey] = value;
1370
+ } else {
1371
+ const element = newSpec.elements[elementKey];
1372
+ if (element) {
1373
+ const propPath = "/" + pathParts.slice(1).join("/");
1374
+ const newElement = { ...element };
1375
+ (0, import_core7.setByPath)(
1376
+ newElement,
1377
+ propPath,
1378
+ value
1379
+ );
1380
+ newSpec.elements[elementKey] = newElement;
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+ function removeSpecValue(newSpec, path) {
1386
+ if (path === "/state") {
1387
+ delete newSpec.state;
1388
+ return;
1389
+ }
1390
+ if (path.startsWith("/state/") && newSpec.state) {
1391
+ const statePath = path.slice("/state".length);
1392
+ (0, import_core7.removeByPath)(newSpec.state, statePath);
1393
+ return;
1394
+ }
1395
+ if (path.startsWith("/elements/")) {
1396
+ const pathParts = path.slice("/elements/".length).split("/");
1397
+ const elementKey = pathParts[0];
1398
+ if (!elementKey) return;
1399
+ if (pathParts.length === 1) {
1400
+ const { [elementKey]: _, ...rest } = newSpec.elements;
1401
+ newSpec.elements = rest;
1402
+ } else {
1403
+ const element = newSpec.elements[elementKey];
1404
+ if (element) {
1405
+ const propPath = "/" + pathParts.slice(1).join("/");
1406
+ const newElement = { ...element };
1407
+ (0, import_core7.removeByPath)(
1408
+ newElement,
1409
+ propPath
1410
+ );
1411
+ newSpec.elements[elementKey] = newElement;
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1416
+ function getSpecValue(spec, path) {
1417
+ if (path === "/root") return spec.root;
1418
+ if (path === "/state") return spec.state;
1419
+ if (path.startsWith("/state/") && spec.state) {
1420
+ const statePath = path.slice("/state".length);
1421
+ return (0, import_core7.getByPath)(spec.state, statePath);
1422
+ }
1423
+ return (0, import_core7.getByPath)(spec, path);
1424
+ }
1425
+ function applyPatch(spec, patch) {
1426
+ const newSpec = {
1427
+ ...spec,
1428
+ elements: { ...spec.elements },
1429
+ ...spec.state ? { state: { ...spec.state } } : {}
1430
+ };
1431
+ switch (patch.op) {
1432
+ case "add":
1433
+ case "replace": {
1434
+ setSpecValue(newSpec, patch.path, patch.value);
1435
+ break;
1436
+ }
1437
+ case "remove": {
1438
+ removeSpecValue(newSpec, patch.path);
1439
+ break;
1440
+ }
1441
+ case "move": {
1442
+ if (!patch.from) break;
1443
+ const moveValue = getSpecValue(newSpec, patch.from);
1444
+ removeSpecValue(newSpec, patch.from);
1445
+ setSpecValue(newSpec, patch.path, moveValue);
1446
+ break;
1447
+ }
1448
+ case "copy": {
1449
+ if (!patch.from) break;
1450
+ const copyValue = getSpecValue(newSpec, patch.from);
1451
+ setSpecValue(newSpec, patch.path, copyValue);
1452
+ break;
1453
+ }
1454
+ case "test": {
1455
+ break;
1456
+ }
1457
+ }
1458
+ return newSpec;
1459
+ }
1460
+ function useUIStream(options) {
1461
+ const [spec, setSpec] = (0, import_solid_js7.createSignal)(null);
1462
+ const [isStreaming, setIsStreaming] = (0, import_solid_js7.createSignal)(false);
1463
+ const [error, setError] = (0, import_solid_js7.createSignal)(null);
1464
+ const [usage, setUsage] = (0, import_solid_js7.createSignal)(null);
1465
+ const [rawLines, setRawLines] = (0, import_solid_js7.createSignal)([]);
1466
+ let abortController = null;
1467
+ const clear = () => {
1468
+ setSpec(null);
1469
+ setError(null);
1470
+ };
1471
+ const send = async (prompt, context) => {
1472
+ abortController?.abort();
1473
+ abortController = new AbortController();
1474
+ setIsStreaming(true);
1475
+ setError(null);
1476
+ setUsage(null);
1477
+ setRawLines([]);
1478
+ const previousSpec = context?.previousSpec;
1479
+ let currentSpec = previousSpec && previousSpec.root ? { ...previousSpec, elements: { ...previousSpec.elements } } : { root: "", elements: {} };
1480
+ setSpec(currentSpec);
1481
+ try {
1482
+ const response = await fetch(options.api, {
1483
+ method: "POST",
1484
+ headers: { "Content-Type": "application/json" },
1485
+ body: JSON.stringify({
1486
+ prompt,
1487
+ context,
1488
+ currentSpec
1489
+ }),
1490
+ signal: abortController.signal
1491
+ });
1492
+ if (!response.ok) {
1493
+ let errorMessage = `HTTP error: ${response.status}`;
1494
+ try {
1495
+ const errorData = await response.json();
1496
+ if (errorData.message) {
1497
+ errorMessage = errorData.message;
1498
+ } else if (errorData.error) {
1499
+ errorMessage = errorData.error;
1500
+ }
1501
+ } catch {
1502
+ }
1503
+ throw new Error(errorMessage);
1504
+ }
1505
+ const reader = response.body?.getReader();
1506
+ if (!reader) {
1507
+ throw new Error("No response body");
1508
+ }
1509
+ const decoder = new TextDecoder();
1510
+ let buffer = "";
1511
+ while (true) {
1512
+ const { done, value } = await reader.read();
1513
+ if (done) break;
1514
+ buffer += decoder.decode(value, { stream: true });
1515
+ const lines = buffer.split("\n");
1516
+ buffer = lines.pop() ?? "";
1517
+ for (const line of lines) {
1518
+ const trimmed = line.trim();
1519
+ if (!trimmed) continue;
1520
+ const result = parseLine(trimmed);
1521
+ if (!result) continue;
1522
+ if (result.type === "usage") {
1523
+ setUsage(result.usage);
1524
+ } else {
1525
+ setRawLines((prev) => [...prev, trimmed]);
1526
+ currentSpec = applyPatch(currentSpec, result.patch);
1527
+ setSpec({ ...currentSpec });
1528
+ }
1529
+ }
1530
+ }
1531
+ if (buffer.trim()) {
1532
+ const trimmed = buffer.trim();
1533
+ const result = parseLine(trimmed);
1534
+ if (result) {
1535
+ if (result.type === "usage") {
1536
+ setUsage(result.usage);
1537
+ } else {
1538
+ setRawLines((prev) => [...prev, trimmed]);
1539
+ currentSpec = applyPatch(currentSpec, result.patch);
1540
+ setSpec({ ...currentSpec });
1541
+ }
1542
+ }
1543
+ }
1544
+ options.onComplete?.(currentSpec);
1545
+ } catch (err) {
1546
+ if (err.name === "AbortError") {
1547
+ return;
1548
+ }
1549
+ const resolvedError = err instanceof Error ? err : new Error(String(err));
1550
+ setError(resolvedError);
1551
+ options.onError?.(resolvedError);
1552
+ } finally {
1553
+ setIsStreaming(false);
1554
+ }
1555
+ };
1556
+ (0, import_solid_js7.onCleanup)(() => {
1557
+ abortController?.abort();
1558
+ });
1559
+ return {
1560
+ get spec() {
1561
+ return spec();
1562
+ },
1563
+ get isStreaming() {
1564
+ return isStreaming();
1565
+ },
1566
+ get error() {
1567
+ return error();
1568
+ },
1569
+ get usage() {
1570
+ return usage();
1571
+ },
1572
+ get rawLines() {
1573
+ return rawLines();
1574
+ },
1575
+ send,
1576
+ clear
1577
+ };
1578
+ }
1579
+ function flatToTree(elements) {
1580
+ const elementMap = {};
1581
+ let root = "";
1582
+ for (const element of elements) {
1583
+ elementMap[element.key] = {
1584
+ type: element.type,
1585
+ props: element.props,
1586
+ children: [],
1587
+ visible: element.visible
1588
+ };
1589
+ }
1590
+ for (const element of elements) {
1591
+ if (element.parentKey) {
1592
+ const parent = elementMap[element.parentKey];
1593
+ if (parent) {
1594
+ if (!parent.children) {
1595
+ parent.children = [];
1596
+ }
1597
+ parent.children.push(element.key);
1598
+ }
1599
+ } else {
1600
+ root = element.key;
1601
+ }
1602
+ }
1603
+ return { root, elements: elementMap };
1604
+ }
1605
+ function useBoundProp(propValue, bindingPath) {
1606
+ const { set } = useStateStore();
1607
+ const setValue = (value) => {
1608
+ if (bindingPath) set(bindingPath, value);
1609
+ };
1610
+ return [propValue, setValue];
1611
+ }
1612
+ function isSpecDataPart(data) {
1613
+ if (typeof data !== "object" || data === null) return false;
1614
+ const obj = data;
1615
+ switch (obj.type) {
1616
+ case "patch":
1617
+ return typeof obj.patch === "object" && obj.patch !== null;
1618
+ case "flat":
1619
+ case "nested":
1620
+ return typeof obj.spec === "object" && obj.spec !== null;
1621
+ default:
1622
+ return false;
1623
+ }
1624
+ }
1625
+ function buildSpecFromParts(parts) {
1626
+ const spec = { root: "", elements: {} };
1627
+ let hasSpec = false;
1628
+ for (const part of parts) {
1629
+ if (part.type === import_core7.SPEC_DATA_PART_TYPE) {
1630
+ if (!isSpecDataPart(part.data)) continue;
1631
+ const payload = part.data;
1632
+ if (payload.type === "patch") {
1633
+ hasSpec = true;
1634
+ (0, import_core7.applySpecPatch)(spec, payload.patch);
1635
+ } else if (payload.type === "flat") {
1636
+ hasSpec = true;
1637
+ Object.assign(spec, payload.spec);
1638
+ } else if (payload.type === "nested") {
1639
+ hasSpec = true;
1640
+ const flat = (0, import_core7.nestedToFlat)(payload.spec);
1641
+ Object.assign(spec, flat);
1642
+ }
1643
+ }
1644
+ }
1645
+ return hasSpec ? spec : null;
1646
+ }
1647
+ function getTextFromParts(parts) {
1648
+ return parts.filter(
1649
+ (p) => p.type === "text" && typeof p.text === "string"
1650
+ ).map((p) => p.text.trim()).filter(Boolean).join("\n\n");
1651
+ }
1652
+ function useJsonRenderMessage(getParts) {
1653
+ const result = (0, import_solid_js7.createMemo)(() => {
1654
+ const parts = getParts();
1655
+ return {
1656
+ spec: buildSpecFromParts(parts),
1657
+ text: getTextFromParts(parts)
1658
+ };
1659
+ });
1660
+ return {
1661
+ get spec() {
1662
+ return result().spec;
1663
+ },
1664
+ get text() {
1665
+ return result().text;
1666
+ },
1667
+ get hasSpec() {
1668
+ const s = result().spec;
1669
+ return s !== null && Object.keys(s.elements || {}).length > 0;
1670
+ }
1671
+ };
1672
+ }
1673
+ var chatMessageIdCounter = 0;
1674
+ function generateChatId() {
1675
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1676
+ return crypto.randomUUID();
1677
+ }
1678
+ chatMessageIdCounter += 1;
1679
+ return `msg-${Date.now()}-${chatMessageIdCounter}`;
1680
+ }
1681
+ function useChatUI(options) {
1682
+ const [messages, setMessages] = (0, import_solid_js7.createSignal)([]);
1683
+ const [isStreaming, setIsStreaming] = (0, import_solid_js7.createSignal)(false);
1684
+ const [error, setError] = (0, import_solid_js7.createSignal)(null);
1685
+ let abortController = null;
1686
+ const clear = () => {
1687
+ setMessages([]);
1688
+ setError(null);
1689
+ };
1690
+ const send = async (text) => {
1691
+ if (!text.trim()) return;
1692
+ abortController?.abort();
1693
+ abortController = new AbortController();
1694
+ const userMessage = {
1695
+ id: generateChatId(),
1696
+ role: "user",
1697
+ text: text.trim(),
1698
+ spec: null
1699
+ };
1700
+ const assistantId = generateChatId();
1701
+ const assistantMessage = {
1702
+ id: assistantId,
1703
+ role: "assistant",
1704
+ text: "",
1705
+ spec: null
1706
+ };
1707
+ setMessages((prev) => [...prev, userMessage, assistantMessage]);
1708
+ setIsStreaming(true);
1709
+ setError(null);
1710
+ const historyForApi = [
1711
+ ...messages().filter((m) => m.id !== userMessage.id && m.id !== assistantId).map((m) => ({
1712
+ role: m.role,
1713
+ content: m.text
1714
+ })),
1715
+ { role: "user", content: text.trim() }
1716
+ ];
1717
+ let accumulatedText = "";
1718
+ let currentSpec = { root: "", elements: {} };
1719
+ let hasSpec = false;
1720
+ try {
1721
+ const response = await fetch(options.api, {
1722
+ method: "POST",
1723
+ headers: { "Content-Type": "application/json" },
1724
+ body: JSON.stringify({ messages: historyForApi }),
1725
+ signal: abortController.signal
1726
+ });
1727
+ if (!response.ok) {
1728
+ let errorMessage = `HTTP error: ${response.status}`;
1729
+ try {
1730
+ const errorData = await response.json();
1731
+ if (errorData.message) {
1732
+ errorMessage = errorData.message;
1733
+ } else if (errorData.error) {
1734
+ errorMessage = errorData.error;
1735
+ }
1736
+ } catch {
1737
+ }
1738
+ throw new Error(errorMessage);
1739
+ }
1740
+ const reader = response.body?.getReader();
1741
+ if (!reader) {
1742
+ throw new Error("No response body");
1743
+ }
1744
+ const decoder = new TextDecoder();
1745
+ const parser = (0, import_core7.createMixedStreamParser)({
1746
+ onPatch(patch) {
1747
+ hasSpec = true;
1748
+ (0, import_core7.applySpecPatch)(currentSpec, patch);
1749
+ setMessages(
1750
+ (prev) => prev.map(
1751
+ (m) => m.id === assistantId ? {
1752
+ ...m,
1753
+ spec: {
1754
+ root: currentSpec.root,
1755
+ elements: { ...currentSpec.elements },
1756
+ ...currentSpec.state ? { state: { ...currentSpec.state } } : {}
1757
+ }
1758
+ } : m
1759
+ )
1760
+ );
1761
+ },
1762
+ onText(line) {
1763
+ accumulatedText += (accumulatedText ? "\n" : "") + line;
1764
+ setMessages(
1765
+ (prev) => prev.map(
1766
+ (m) => m.id === assistantId ? { ...m, text: accumulatedText } : m
1767
+ )
1768
+ );
1769
+ }
1770
+ });
1771
+ while (true) {
1772
+ const { done, value } = await reader.read();
1773
+ if (done) break;
1774
+ parser.push(decoder.decode(value, { stream: true }));
1775
+ }
1776
+ parser.flush();
1777
+ const finalMessage = {
1778
+ id: assistantId,
1779
+ role: "assistant",
1780
+ text: accumulatedText,
1781
+ spec: hasSpec ? {
1782
+ root: currentSpec.root,
1783
+ elements: { ...currentSpec.elements },
1784
+ ...currentSpec.state ? { state: { ...currentSpec.state } } : {}
1785
+ } : null
1786
+ };
1787
+ options.onComplete?.(finalMessage);
1788
+ } catch (err) {
1789
+ if (err.name === "AbortError") {
1790
+ return;
1791
+ }
1792
+ const resolvedError = err instanceof Error ? err : new Error(String(err));
1793
+ setError(resolvedError);
1794
+ setMessages(
1795
+ (prev) => prev.filter((m) => m.id !== assistantId || m.text.length > 0)
1796
+ );
1797
+ options.onError?.(resolvedError);
1798
+ } finally {
1799
+ setIsStreaming(false);
1800
+ }
1801
+ };
1802
+ (0, import_solid_js7.onCleanup)(() => {
1803
+ abortController?.abort();
1804
+ });
1805
+ return {
1806
+ get messages() {
1807
+ return messages();
1808
+ },
1809
+ get isStreaming() {
1810
+ return isStreaming();
1811
+ },
1812
+ get error() {
1813
+ return error();
1814
+ },
1815
+ send,
1816
+ clear
1817
+ };
1818
+ }
1819
+ // Annotate the CommonJS export names for ESM import in node:
1820
+ 0 && (module.exports = {
1821
+ ActionProvider,
1822
+ ConfirmDialog,
1823
+ JSONUIProvider,
1824
+ Renderer,
1825
+ RepeatScopeProvider,
1826
+ StateProvider,
1827
+ ValidationProvider,
1828
+ VisibilityProvider,
1829
+ buildSpecFromParts,
1830
+ createRenderer,
1831
+ createStateStore,
1832
+ defineRegistry,
1833
+ elementTreeSchema,
1834
+ flatToTree,
1835
+ getTextFromParts,
1836
+ schema,
1837
+ useAction,
1838
+ useActions,
1839
+ useBoundProp,
1840
+ useChatUI,
1841
+ useFieldValidation,
1842
+ useIsVisible,
1843
+ useJsonRenderMessage,
1844
+ useOptionalValidation,
1845
+ useRepeatScope,
1846
+ useStateBinding,
1847
+ useStateStore,
1848
+ useStateValue,
1849
+ useUIStream,
1850
+ useValidation,
1851
+ useVisibility
1852
+ });
1853
+ //# sourceMappingURL=index.js.map