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