@webskill/sdk 0.3.0 → 0.4.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.
Files changed (32) hide show
  1. package/dist/browser.d.ts +2 -2
  2. package/dist/browser.js +2 -6
  3. package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-KsujmL4b-Clx1kCnU.js} +278 -122
  4. package/dist/{dist-rorEJsNi.js → dist-C-Sh0MDU.js} +498 -282
  5. package/dist/{dist-ZKaM8j06.js → dist-D9Lcn5Pp.js} +527 -837
  6. package/dist/governance.d.ts +45 -10
  7. package/dist/governance.js +151 -24
  8. package/dist/{index-wiV5X8Rz.d.ts → index-CHXxDccV.d.ts} +62 -144
  9. package/dist/{index-8d-oEDww.d.ts → index-DLfR2Y6I.d.ts} +215 -23
  10. package/dist/index.d.ts +3 -3
  11. package/dist/index.js +3 -2
  12. package/dist/mcp.d.ts +2 -2
  13. package/dist/mcp.js +1 -1
  14. package/dist/memoryArtifactStore-BtOeB_hm-tj3fC5ip.js +78 -0
  15. package/dist/node.d.ts +8 -4
  16. package/dist/node.js +1 -1
  17. package/dist/{openUiLibrary-B8-Cvou9-BbpNTXS3.js → openUiLibrary-YLS-cxyT-C96jWDQq.js} +6 -5
  18. package/dist/{skillVersionStore-DOEI9ptb-BxbYL70B.d.ts → skillVersionStore-uyefLPR1-DXOzbksv.d.ts} +41 -10
  19. package/dist/{testing-CsrG3XLz.js → testing-DDCJWvgA.js} +7 -5
  20. package/dist/testing.d.ts +1 -1
  21. package/dist/testing.js +2 -2
  22. package/dist/{types-AmKCKJn_-VGabeXK4.d.ts → types-7Wcg--Vh-1YlQ4jF9.d.ts} +85 -71
  23. package/dist/ui-react.d.ts +329 -18
  24. package/dist/ui-react.js +3714 -3463
  25. package/dist/ui-vue.d.ts +1 -1
  26. package/dist/ui-vue.js +1 -1
  27. package/dist/ui.d.ts +4 -3
  28. package/dist/ui.js +3 -3
  29. package/dist/{webskillLitCatalog-CNaUpasU-BslMcxRZ.js → webskillLitCatalog-CSTbhBe_-CYIs5BX8.js} +312 -122
  30. package/package.json +1 -1
  31. package/dist/jsonRenderRegistry-9GrWP_hE-U6Do3Kid.js +0 -2468
  32. package/dist/memoryArtifactStore-C9lFVqPF-yFz6yJj0.js +0 -48
@@ -1,2468 +0,0 @@
1
- import { ft as uiCatalog } from "./dist-ZKaM8j06.js";
2
- import { n as catalogComponentImpls } from "./catalogComponents-C_V39rbF-BOHveMWa.js";
3
- import { z } from "zod";
4
- import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
-
7
- //#region ../../node_modules/.pnpm/@json-render+core@0.19.0_zod@4.4.3/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.mjs
8
- var DynamicValueSchema = z.union([
9
- z.string(),
10
- z.number(),
11
- z.boolean(),
12
- z.null(),
13
- z.object({ $state: z.string() })
14
- ]);
15
- var DynamicStringSchema = z.union([z.string(), z.object({ $state: z.string() })]);
16
- var DynamicNumberSchema = z.union([z.number(), z.object({ $state: z.string() })]);
17
- var DynamicBooleanSchema = z.union([z.boolean(), z.object({ $state: z.string() })]);
18
- function resolveDynamicValue(value, stateModel) {
19
- if (value === null || value === void 0) return;
20
- if (typeof value === "object" && "$state" in value) return getByPath(stateModel, value.$state);
21
- return value;
22
- }
23
- function unescapeJsonPointer(token) {
24
- return token.replace(/~1/g, "/").replace(/~0/g, "~");
25
- }
26
- function parseJsonPointer(path) {
27
- return (path.startsWith("/") ? path.slice(1).split("/") : path.split("/")).map(unescapeJsonPointer);
28
- }
29
- function getByPath(obj, path) {
30
- if (!path || path === "/") return obj;
31
- const segments = parseJsonPointer(path);
32
- let current = obj;
33
- for (const segment of segments) {
34
- if (current === null || current === void 0) return;
35
- if (Array.isArray(current)) current = current[parseInt(segment, 10)];
36
- else if (typeof current === "object") current = current[segment];
37
- else return;
38
- }
39
- return current;
40
- }
41
- var SPEC_DATA_PART = "spec";
42
- var SPEC_DATA_PART_TYPE = `data-${SPEC_DATA_PART}`;
43
- function immutableSetByPath(root, path, value) {
44
- const segments = parseJsonPointer(path);
45
- if (segments.length === 0) return root;
46
- const result = { ...root };
47
- let current = result;
48
- for (let i = 0; i < segments.length - 1; i++) {
49
- const seg = segments[i];
50
- const child = current[seg];
51
- if (Array.isArray(child)) current[seg] = [...child];
52
- else if (child !== null && typeof child === "object") current[seg] = { ...child };
53
- else {
54
- const nextSeg = segments[i + 1];
55
- current[seg] = nextSeg !== void 0 && /^\d+$/.test(nextSeg) ? [] : {};
56
- }
57
- current = current[seg];
58
- }
59
- const lastSeg = segments[segments.length - 1];
60
- if (Array.isArray(current)) if (lastSeg === "-") current.push(value);
61
- else current[parseInt(lastSeg, 10)] = value;
62
- else current[lastSeg] = value;
63
- return result;
64
- }
65
- function createStateStore(initialState = {}) {
66
- let state = { ...initialState };
67
- const listeners = /* @__PURE__ */ new Set();
68
- function notify() {
69
- for (const listener of listeners) listener();
70
- }
71
- return {
72
- get(path) {
73
- return getByPath(state, path);
74
- },
75
- set(path, value) {
76
- if (getByPath(state, path) === value) return;
77
- state = immutableSetByPath(state, path, value);
78
- notify();
79
- },
80
- update(updates) {
81
- let changed = false;
82
- let next = state;
83
- for (const [path, value] of Object.entries(updates)) if (getByPath(next, path) !== value) {
84
- next = immutableSetByPath(next, path, value);
85
- changed = true;
86
- }
87
- if (!changed) return;
88
- state = next;
89
- notify();
90
- },
91
- getSnapshot() {
92
- return state;
93
- },
94
- getServerSnapshot() {
95
- return state;
96
- },
97
- subscribe(listener) {
98
- listeners.add(listener);
99
- return () => {
100
- listeners.delete(listener);
101
- };
102
- }
103
- };
104
- }
105
- var MAX_FLATTEN_DEPTH = 20;
106
- function flattenToPointers(obj, prefix = "", _depth = 0, _seen, _warned) {
107
- const seen = _seen ?? /* @__PURE__ */ new Set();
108
- const warned = _warned ?? { current: false };
109
- const result = {};
110
- for (const [key, value] of Object.entries(obj)) {
111
- const pointer = `${prefix}/${key}`;
112
- if (_depth < MAX_FLATTEN_DEPTH && value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && !seen.has(value)) {
113
- seen.add(value);
114
- Object.assign(result, flattenToPointers(value, pointer, _depth + 1, seen, warned));
115
- } else {
116
- if (process.env.NODE_ENV !== "production" && !warned.current && _depth >= MAX_FLATTEN_DEPTH && value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && !seen.has(value)) {
117
- warned.current = true;
118
- console.warn(`flattenToPointers: depth limit (${MAX_FLATTEN_DEPTH}) reached. Nested state beyond this depth will be treated as a leaf value.`);
119
- }
120
- result[pointer] = value;
121
- }
122
- }
123
- return result;
124
- }
125
-
126
- //#endregion
127
- //#region ../../node_modules/.pnpm/@json-render+core@0.19.0_zod@4.4.3/node_modules/@json-render/core/dist/index.mjs
128
- var numericOrStateRef = z.union([z.number(), z.object({ $state: z.string() })]);
129
- var comparisonOps = {
130
- eq: z.unknown().optional(),
131
- neq: z.unknown().optional(),
132
- gt: numericOrStateRef.optional(),
133
- gte: numericOrStateRef.optional(),
134
- lt: numericOrStateRef.optional(),
135
- lte: numericOrStateRef.optional(),
136
- not: z.literal(true).optional()
137
- };
138
- var StateConditionSchema = z.object({
139
- $state: z.string(),
140
- ...comparisonOps
141
- });
142
- var ItemConditionSchema = z.object({
143
- $item: z.string(),
144
- ...comparisonOps
145
- });
146
- var IndexConditionSchema = z.object({
147
- $index: z.literal(true),
148
- ...comparisonOps
149
- });
150
- var SingleConditionSchema = z.union([
151
- StateConditionSchema,
152
- ItemConditionSchema,
153
- IndexConditionSchema
154
- ]);
155
- var VisibilityConditionSchema = z.lazy(() => z.union([
156
- z.boolean(),
157
- SingleConditionSchema,
158
- z.array(SingleConditionSchema),
159
- z.object({ $and: z.array(VisibilityConditionSchema) }),
160
- z.object({ $or: z.array(VisibilityConditionSchema) })
161
- ]));
162
- function resolveComparisonValue(value, ctx) {
163
- if (typeof value === "object" && value !== null) {
164
- if ("$state" in value && typeof value.$state === "string") return getByPath(ctx.stateModel, value.$state);
165
- }
166
- return value;
167
- }
168
- function isItemCondition(cond) {
169
- return "$item" in cond;
170
- }
171
- function isIndexCondition(cond) {
172
- return "$index" in cond;
173
- }
174
- function resolveConditionValue(cond, ctx) {
175
- if (isIndexCondition(cond)) return ctx.repeatIndex;
176
- if (isItemCondition(cond)) {
177
- if (ctx.repeatItem === void 0) return void 0;
178
- return cond.$item === "" ? ctx.repeatItem : getByPath(ctx.repeatItem, cond.$item);
179
- }
180
- return getByPath(ctx.stateModel, cond.$state);
181
- }
182
- function evaluateCondition(cond, ctx) {
183
- const value = resolveConditionValue(cond, ctx);
184
- let result;
185
- if (cond.eq !== void 0) result = value === resolveComparisonValue(cond.eq, ctx);
186
- else if (cond.neq !== void 0) result = value !== resolveComparisonValue(cond.neq, ctx);
187
- else if (cond.gt !== void 0) {
188
- const rhs = resolveComparisonValue(cond.gt, ctx);
189
- result = typeof value === "number" && typeof rhs === "number" ? value > rhs : false;
190
- } else if (cond.gte !== void 0) {
191
- const rhs = resolveComparisonValue(cond.gte, ctx);
192
- result = typeof value === "number" && typeof rhs === "number" ? value >= rhs : false;
193
- } else if (cond.lt !== void 0) {
194
- const rhs = resolveComparisonValue(cond.lt, ctx);
195
- result = typeof value === "number" && typeof rhs === "number" ? value < rhs : false;
196
- } else if (cond.lte !== void 0) {
197
- const rhs = resolveComparisonValue(cond.lte, ctx);
198
- result = typeof value === "number" && typeof rhs === "number" ? value <= rhs : false;
199
- } else result = Boolean(value);
200
- return cond.not === true ? !result : result;
201
- }
202
- function isAndCondition(condition) {
203
- return typeof condition === "object" && condition !== null && !Array.isArray(condition) && "$and" in condition;
204
- }
205
- function isOrCondition(condition) {
206
- return typeof condition === "object" && condition !== null && !Array.isArray(condition) && "$or" in condition;
207
- }
208
- function evaluateVisibility(condition, ctx) {
209
- if (condition === void 0) return true;
210
- if (typeof condition === "boolean") return condition;
211
- if (Array.isArray(condition)) return condition.every((c) => evaluateCondition(c, ctx));
212
- if (isAndCondition(condition)) return condition.$and.every((child) => evaluateVisibility(child, ctx));
213
- if (isOrCondition(condition)) return condition.$or.some((child) => evaluateVisibility(child, ctx));
214
- return evaluateCondition(condition, ctx);
215
- }
216
- function createDirectiveRegistry(directives) {
217
- const registry = /* @__PURE__ */ new Map();
218
- for (const d of directives) registry.set(d.name, d);
219
- return registry;
220
- }
221
- function findDirective(value, directives) {
222
- if (!directives || directives.size === 0) return void 0;
223
- let match;
224
- for (const [key, def] of directives) if (key in value) {
225
- if (match) throw new Error(`Ambiguous directive: object has multiple directive keys ("${match.name}" and "${key}")`);
226
- match = def;
227
- }
228
- return match;
229
- }
230
- function isStateExpression(value) {
231
- return typeof value === "object" && value !== null && "$state" in value && typeof value.$state === "string";
232
- }
233
- function isItemExpression(value) {
234
- return typeof value === "object" && value !== null && "$item" in value && typeof value.$item === "string";
235
- }
236
- function isIndexExpression(value) {
237
- return typeof value === "object" && value !== null && "$index" in value && value.$index === true;
238
- }
239
- function isBindStateExpression(value) {
240
- return typeof value === "object" && value !== null && "$bindState" in value && typeof value.$bindState === "string";
241
- }
242
- function isBindItemExpression(value) {
243
- return typeof value === "object" && value !== null && "$bindItem" in value && typeof value.$bindItem === "string";
244
- }
245
- function isCondExpression(value) {
246
- return typeof value === "object" && value !== null && "$cond" in value && "$then" in value && "$else" in value;
247
- }
248
- function isComputedExpression(value) {
249
- return typeof value === "object" && value !== null && "$computed" in value && typeof value.$computed === "string";
250
- }
251
- function isTemplateExpression(value) {
252
- return typeof value === "object" && value !== null && "$template" in value && typeof value.$template === "string";
253
- }
254
- var WARNED_COMPUTED_MAX = 100;
255
- var warnedComputedFns = /* @__PURE__ */ new Set();
256
- function resolveBindItemPath(itemPath, ctx) {
257
- if (ctx.repeatBasePath == null) {
258
- console.warn(`$bindItem used outside repeat scope: "${itemPath}"`);
259
- return;
260
- }
261
- if (itemPath === "") return ctx.repeatBasePath;
262
- return ctx.repeatBasePath + "/" + itemPath;
263
- }
264
- function resolvePropValue(value, ctx) {
265
- if (value === null || value === void 0) return value;
266
- if (isStateExpression(value)) return getByPath(ctx.stateModel, value.$state);
267
- if (isItemExpression(value)) {
268
- if (ctx.repeatItem === void 0) return void 0;
269
- return value.$item === "" ? ctx.repeatItem : getByPath(ctx.repeatItem, value.$item);
270
- }
271
- if (isIndexExpression(value)) return ctx.repeatIndex;
272
- if (isBindStateExpression(value)) return getByPath(ctx.stateModel, value.$bindState);
273
- if (isBindItemExpression(value)) {
274
- const resolvedPath = resolveBindItemPath(value.$bindItem, ctx);
275
- if (resolvedPath === void 0) return void 0;
276
- return getByPath(ctx.stateModel, resolvedPath);
277
- }
278
- if (isCondExpression(value)) return resolvePropValue(evaluateVisibility(value.$cond, ctx) ? value.$then : value.$else, ctx);
279
- if (isComputedExpression(value)) {
280
- const fn = ctx.functions?.[value.$computed];
281
- if (!fn) {
282
- if (!warnedComputedFns.has(value.$computed)) {
283
- if (warnedComputedFns.size < WARNED_COMPUTED_MAX) warnedComputedFns.add(value.$computed);
284
- console.warn(`Unknown $computed function: "${value.$computed}"`);
285
- }
286
- return;
287
- }
288
- const resolvedArgs = {};
289
- if (value.args) for (const [key, arg] of Object.entries(value.args)) resolvedArgs[key] = resolvePropValue(arg, ctx);
290
- return fn(resolvedArgs);
291
- }
292
- if (isTemplateExpression(value)) return value.$template.replace(/\$\{([^}]+)\}/g, (_match, rawPath) => {
293
- if (rawPath.startsWith("/")) {
294
- const resolved2 = getByPath(ctx.stateModel, rawPath);
295
- return resolved2 != null ? String(resolved2) : "";
296
- }
297
- if (ctx.repeatItem !== void 0) {
298
- const fromItem = getByPath(ctx.repeatItem, rawPath);
299
- if (fromItem != null) return String(fromItem);
300
- }
301
- const resolved = getByPath(ctx.stateModel, "/" + rawPath);
302
- return resolved != null ? String(resolved) : "";
303
- });
304
- if (Array.isArray(value)) return value.map((item) => resolvePropValue(item, ctx));
305
- if (typeof value === "object") {
306
- const directive = findDirective(value, ctx.directives);
307
- if (directive) return directive.resolve(value, ctx);
308
- const resolved = {};
309
- for (const [key, val] of Object.entries(value)) resolved[key] = resolvePropValue(val, ctx);
310
- return resolved;
311
- }
312
- return value;
313
- }
314
- function resolveElementProps(props, ctx) {
315
- const resolved = {};
316
- for (const [key, value] of Object.entries(props)) resolved[key] = resolvePropValue(value, ctx);
317
- return resolved;
318
- }
319
- function resolveBindings(props, ctx) {
320
- let bindings;
321
- for (const [key, value] of Object.entries(props)) if (isBindStateExpression(value)) {
322
- if (!bindings) bindings = {};
323
- bindings[key] = value.$bindState;
324
- } else if (isBindItemExpression(value)) {
325
- const resolved = resolveBindItemPath(value.$bindItem, ctx);
326
- if (resolved !== void 0) {
327
- if (!bindings) bindings = {};
328
- bindings[key] = resolved;
329
- }
330
- }
331
- return bindings;
332
- }
333
- function resolveActionParam(value, ctx) {
334
- if (isItemExpression(value)) return resolveBindItemPath(value.$item, ctx);
335
- if (isIndexExpression(value)) return ctx.repeatIndex;
336
- return resolvePropValue(value, ctx);
337
- }
338
- var observers = /* @__PURE__ */ new Set();
339
- function notifyActionDispatch(evt) {
340
- for (const o of observers) {
341
- const fn = o.onDispatch;
342
- if (!fn) continue;
343
- try {
344
- fn(evt);
345
- } catch (err) {
346
- if (process.env.NODE_ENV !== "production") console.error("[json-render] action observer threw in onDispatch:", err);
347
- }
348
- }
349
- }
350
- function notifyActionSettle(evt) {
351
- for (const o of observers) {
352
- const fn = o.onSettle;
353
- if (!fn) continue;
354
- try {
355
- fn(evt);
356
- } catch (err) {
357
- if (process.env.NODE_ENV !== "production") console.error("[json-render] action observer threw in onSettle:", err);
358
- }
359
- }
360
- }
361
- var dispatchCounter = 0;
362
- function nextActionDispatchId() {
363
- dispatchCounter += 1;
364
- return `${Date.now()}-${dispatchCounter}`;
365
- }
366
- var activeCount = 0;
367
- var listeners = /* @__PURE__ */ new Set();
368
- function isDevtoolsActive() {
369
- return activeCount > 0;
370
- }
371
- function subscribeDevtoolsActive(listener) {
372
- listeners.add(listener);
373
- return () => {
374
- listeners.delete(listener);
375
- };
376
- }
377
- var ActionConfirmSchema = z.object({
378
- title: z.string(),
379
- message: z.string(),
380
- confirmLabel: z.string().optional(),
381
- cancelLabel: z.string().optional(),
382
- variant: z.enum(["default", "danger"]).optional()
383
- });
384
- var ActionOnSuccessSchema = z.union([
385
- z.object({ navigate: z.string() }),
386
- z.object({ set: z.record(z.string(), z.unknown()) }),
387
- z.object({ action: z.string() })
388
- ]);
389
- var ActionOnErrorSchema = z.union([z.object({ set: z.record(z.string(), z.unknown()) }), z.object({ action: z.string() })]);
390
- var ActionBindingSchema = z.object({
391
- action: z.string(),
392
- params: z.record(z.string(), DynamicValueSchema).optional(),
393
- confirm: ActionConfirmSchema.optional(),
394
- onSuccess: ActionOnSuccessSchema.optional(),
395
- onError: ActionOnErrorSchema.optional(),
396
- preventDefault: z.boolean().optional()
397
- });
398
- function resolveAction(binding, stateModel) {
399
- const resolvedParams = {};
400
- if (binding.params) for (const [key, value] of Object.entries(binding.params)) resolvedParams[key] = resolveDynamicValue(value, stateModel);
401
- let confirm = binding.confirm;
402
- if (confirm) confirm = {
403
- ...confirm,
404
- message: interpolateString(confirm.message, stateModel),
405
- title: interpolateString(confirm.title, stateModel)
406
- };
407
- return {
408
- action: binding.action,
409
- params: resolvedParams,
410
- confirm,
411
- onSuccess: binding.onSuccess,
412
- onError: binding.onError
413
- };
414
- }
415
- function interpolateString(template, stateModel) {
416
- return template.replace(/\$\{([^}]+)\}/g, (_, path) => {
417
- const value = resolveDynamicValue({ $state: path }, stateModel);
418
- return String(value ?? "");
419
- });
420
- }
421
- async function executeAction(ctx) {
422
- const { action: action2, handler, setState, navigate, executeAction: executeAction2 } = ctx;
423
- try {
424
- await handler(action2.params);
425
- if (action2.onSuccess) {
426
- if ("navigate" in action2.onSuccess && navigate) navigate(action2.onSuccess.navigate);
427
- else if ("set" in action2.onSuccess) for (const [path, value] of Object.entries(action2.onSuccess.set)) setState(path, value);
428
- else if ("action" in action2.onSuccess && executeAction2) await executeAction2(action2.onSuccess.action);
429
- }
430
- } catch (error) {
431
- if (action2.onError) {
432
- if ("set" in action2.onError) for (const [path, value] of Object.entries(action2.onError.set)) setState(path, typeof value === "string" && value === "$error.message" ? error.message : value);
433
- else if ("action" in action2.onError && executeAction2) await executeAction2(action2.onError.action);
434
- } else throw error;
435
- }
436
- }
437
- var ValidationCheckSchema = z.object({
438
- type: z.string(),
439
- args: z.record(z.string(), DynamicValueSchema).optional(),
440
- message: z.string()
441
- });
442
- var ValidationConfigSchema = z.object({
443
- checks: z.array(ValidationCheckSchema).optional(),
444
- validateOn: z.enum([
445
- "change",
446
- "blur",
447
- "submit"
448
- ]).optional(),
449
- enabled: VisibilityConditionSchema.optional()
450
- });
451
- var matchesImpl = (value, args) => {
452
- return value === args?.other;
453
- };
454
- var builtInValidationFunctions = {
455
- /**
456
- * Check if value is not null, undefined, or empty string
457
- */
458
- required: (value) => {
459
- if (value === null || value === void 0) return false;
460
- if (typeof value === "string") return value.trim().length > 0;
461
- if (Array.isArray(value)) return value.length > 0;
462
- return true;
463
- },
464
- /**
465
- * Check if value is a valid email address
466
- */
467
- email: (value) => {
468
- if (typeof value !== "string") return false;
469
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
470
- },
471
- /**
472
- * Check minimum string length
473
- */
474
- minLength: (value, args) => {
475
- if (typeof value !== "string") return false;
476
- const min = args?.min;
477
- if (typeof min !== "number") return false;
478
- return value.length >= min;
479
- },
480
- /**
481
- * Check maximum string length
482
- */
483
- maxLength: (value, args) => {
484
- if (typeof value !== "string") return false;
485
- const max = args?.max;
486
- if (typeof max !== "number") return false;
487
- return value.length <= max;
488
- },
489
- /**
490
- * Check if string matches a regex pattern
491
- */
492
- pattern: (value, args) => {
493
- if (typeof value !== "string") return false;
494
- const pattern = args?.pattern;
495
- if (typeof pattern !== "string") return false;
496
- try {
497
- return new RegExp(pattern).test(value);
498
- } catch {
499
- return false;
500
- }
501
- },
502
- /**
503
- * Check minimum numeric value
504
- */
505
- min: (value, args) => {
506
- if (typeof value !== "number") return false;
507
- const min = args?.min;
508
- if (typeof min !== "number") return false;
509
- return value >= min;
510
- },
511
- /**
512
- * Check maximum numeric value
513
- */
514
- max: (value, args) => {
515
- if (typeof value !== "number") return false;
516
- const max = args?.max;
517
- if (typeof max !== "number") return false;
518
- return value <= max;
519
- },
520
- /**
521
- * Check if value is a number
522
- */
523
- numeric: (value) => {
524
- if (typeof value === "number") return !isNaN(value);
525
- if (typeof value === "string") return !isNaN(parseFloat(value));
526
- return false;
527
- },
528
- /**
529
- * Check if value is a valid URL
530
- */
531
- url: (value) => {
532
- if (typeof value !== "string") return false;
533
- try {
534
- new URL(value);
535
- return true;
536
- } catch {
537
- return false;
538
- }
539
- },
540
- /**
541
- * Check if value matches another field
542
- */
543
- matches: matchesImpl,
544
- /**
545
- * Alias for matches with a more descriptive name for cross-field equality
546
- */
547
- equalTo: matchesImpl,
548
- /**
549
- * Check if value is less than another field's value.
550
- * Supports numbers, strings (useful for ISO date comparison), and
551
- * cross-type numeric coercion (e.g. string "3" vs number 5).
552
- */
553
- lessThan: (value, args) => {
554
- const other = args?.other;
555
- if (value == null || other == null || value === "" || other === "") return false;
556
- if (typeof value === "number" && typeof other === "number") return value < other;
557
- if (typeof value === "string" && typeof other === "string") return value < other;
558
- const numVal = Number(value);
559
- const numOther = Number(other);
560
- if (!isNaN(numVal) && !isNaN(numOther)) return numVal < numOther;
561
- return false;
562
- },
563
- /**
564
- * Check if value is greater than another field's value.
565
- * Supports numbers, strings (useful for ISO date comparison), and
566
- * cross-type numeric coercion (e.g. string "7" vs number 5).
567
- */
568
- greaterThan: (value, args) => {
569
- const other = args?.other;
570
- if (value == null || other == null || value === "" || other === "") return false;
571
- if (typeof value === "number" && typeof other === "number") return value > other;
572
- if (typeof value === "string" && typeof other === "string") return value > other;
573
- const numVal = Number(value);
574
- const numOther = Number(other);
575
- if (!isNaN(numVal) && !isNaN(numOther)) return numVal > numOther;
576
- return false;
577
- },
578
- /**
579
- * Required only when a condition is met.
580
- * Uses JS truthiness: 0, false, "", null, and undefined are all
581
- * treated as "condition not met" (field not required), matching
582
- * the visibility system's bare-condition semantics.
583
- */
584
- requiredIf: (value, args) => {
585
- if (!args?.field) return true;
586
- if (value === null || value === void 0) return false;
587
- if (typeof value === "string") return value.trim().length > 0;
588
- if (Array.isArray(value)) return value.length > 0;
589
- return true;
590
- }
591
- };
592
- function runValidationCheck(check2, ctx) {
593
- const { value, stateModel, customFunctions } = ctx;
594
- const resolvedArgs = {};
595
- if (check2.args) for (const [key, argValue] of Object.entries(check2.args)) resolvedArgs[key] = resolvePropValue(argValue, { stateModel });
596
- const validationFn = builtInValidationFunctions[check2.type] ?? customFunctions?.[check2.type];
597
- if (!validationFn) {
598
- console.warn(`Unknown validation function: ${check2.type}`);
599
- return {
600
- type: check2.type,
601
- valid: true,
602
- message: check2.message
603
- };
604
- }
605
- const valid = validationFn(value, resolvedArgs);
606
- return {
607
- type: check2.type,
608
- valid,
609
- message: check2.message
610
- };
611
- }
612
- function runValidation(config, ctx) {
613
- const checks = [];
614
- const errors = [];
615
- if (config.enabled) {
616
- if (!evaluateVisibility(config.enabled, { stateModel: ctx.stateModel })) return {
617
- valid: true,
618
- errors: [],
619
- checks: []
620
- };
621
- }
622
- if (config.checks) for (const check2 of config.checks) {
623
- const result = runValidationCheck(check2, ctx);
624
- checks.push(result);
625
- if (!result.valid) errors.push(result.message);
626
- }
627
- return {
628
- valid: errors.length === 0,
629
- errors,
630
- checks
631
- };
632
- }
633
- var DEFAULT_MODES = ["patch"];
634
- function normalizeModes(config) {
635
- if (!config?.modes?.length) return DEFAULT_MODES;
636
- return config.modes;
637
- }
638
- function jsonPatchInstructions() {
639
- return [
640
- "PATCH MODE (RFC 6902 JSON Patch):",
641
- "Output one JSON object per line. Each line is a patch operation.",
642
- "- Add: {\"op\":\"add\",\"path\":\"/elements/new-key\",\"value\":{...}}",
643
- "- Replace: {\"op\":\"replace\",\"path\":\"/elements/existing-key\",\"value\":{...}}",
644
- "- Remove: {\"op\":\"remove\",\"path\":\"/elements/old-key\"}",
645
- "Only output patches for what needs to change."
646
- ].join("\n");
647
- }
648
- function jsonMergeInstructions() {
649
- return [
650
- "MERGE MODE (RFC 7396 JSON Merge Patch):",
651
- "Output a single JSON object on one line with __json_edit set to true.",
652
- "Include only the keys that changed. Unmentioned keys are preserved.",
653
- "Set a key to null to delete it.",
654
- "",
655
- "Example (update a title and add an element):",
656
- "{\"__json_edit\":true,\"elements\":{\"main\":{\"props\":{\"title\":\"New Title\"}},\"new-el\":{\"type\":\"Card\",\"props\":{},\"children\":[]}}}",
657
- "",
658
- "Example (delete an element):",
659
- "{\"__json_edit\":true,\"elements\":{\"old-widget\":null}}"
660
- ].join("\n");
661
- }
662
- function jsonDiffInstructions() {
663
- return [
664
- "DIFF MODE (unified diff):",
665
- "Output a unified diff inside a ```diff code fence.",
666
- "The diff applies against the JSON-serialized current spec.",
667
- "",
668
- "Example:",
669
- "```diff",
670
- "--- a/spec.json",
671
- "+++ b/spec.json",
672
- "@@ -3,1 +3,1 @@",
673
- "- \"title\": \"Login\"",
674
- "+ \"title\": \"Welcome Back\"",
675
- "```"
676
- ].join("\n");
677
- }
678
- function yamlPatchInstructions() {
679
- return [
680
- "PATCH MODE (RFC 6902 JSON Patch):",
681
- "Output RFC 6902 JSON Patch lines inside a ```yaml-patch code fence.",
682
- "Each line is one JSON patch operation.",
683
- "",
684
- "Example:",
685
- "```yaml-patch",
686
- "{\"op\":\"replace\",\"path\":\"/elements/main/props/title\",\"value\":\"New Title\"}",
687
- "{\"op\":\"add\",\"path\":\"/elements/new-el\",\"value\":{\"type\":\"Card\",\"props\":{},\"children\":[]}}",
688
- "```"
689
- ].join("\n");
690
- }
691
- function yamlMergeInstructions() {
692
- return [
693
- "MERGE MODE (RFC 7396 JSON Merge Patch):",
694
- "Output only the changed parts in a ```yaml-edit code fence.",
695
- "Uses deep merge semantics: only keys you include are updated. Unmentioned elements and props are preserved.",
696
- "Set a key to null to delete it.",
697
- "",
698
- "Example edit (update title, add a new element):",
699
- "```yaml-edit",
700
- "elements:",
701
- " main:",
702
- " props:",
703
- " title: Updated Title",
704
- " new-chart:",
705
- " type: Card",
706
- " props: {}",
707
- " children: []",
708
- "```",
709
- "",
710
- "Example deletion:",
711
- "```yaml-edit",
712
- "elements:",
713
- " old-widget: null",
714
- "```"
715
- ].join("\n");
716
- }
717
- function yamlDiffInstructions() {
718
- return [
719
- "DIFF MODE (unified diff):",
720
- "Output a unified diff inside a ```diff code fence.",
721
- "The diff applies against the YAML-serialized current spec.",
722
- "",
723
- "Example:",
724
- "```diff",
725
- "--- a/spec.yaml",
726
- "+++ b/spec.yaml",
727
- "@@ -6,1 +6,1 @@",
728
- "- title: Login",
729
- "+ title: Welcome Back",
730
- "```"
731
- ].join("\n");
732
- }
733
- function modeSelectionGuidance(modes) {
734
- if (modes.length === 1) return "";
735
- const parts = ["Choose the best edit strategy for the requested change:"];
736
- if (modes.includes("patch")) parts.push("- PATCH: best for precise, targeted single-field updates");
737
- if (modes.includes("merge")) parts.push("- MERGE: best for structural changes (add/remove elements, reparent children, update multiple props at once)");
738
- if (modes.includes("diff")) parts.push("- DIFF: best for small text-level changes when you can see the exact lines to change");
739
- return parts.join("\n");
740
- }
741
- function buildEditInstructions(config, format) {
742
- const modes = normalizeModes(config);
743
- const sections = [];
744
- sections.push("EDITING EXISTING SPECS:");
745
- sections.push("");
746
- const guidance = modeSelectionGuidance(modes);
747
- if (guidance) {
748
- sections.push(guidance);
749
- sections.push("");
750
- }
751
- for (const mode of modes) {
752
- if (format === "json") switch (mode) {
753
- case "patch":
754
- sections.push(jsonPatchInstructions());
755
- break;
756
- case "merge":
757
- sections.push(jsonMergeInstructions());
758
- break;
759
- case "diff":
760
- sections.push(jsonDiffInstructions());
761
- break;
762
- }
763
- else switch (mode) {
764
- case "patch":
765
- sections.push(yamlPatchInstructions());
766
- break;
767
- case "merge":
768
- sections.push(yamlMergeInstructions());
769
- break;
770
- case "diff":
771
- sections.push(yamlDiffInstructions());
772
- break;
773
- }
774
- sections.push("");
775
- }
776
- return sections.join("\n");
777
- }
778
- function createBuilder() {
779
- return {
780
- string: () => ({ kind: "string" }),
781
- number: () => ({ kind: "number" }),
782
- boolean: () => ({ kind: "boolean" }),
783
- array: (item) => ({
784
- kind: "array",
785
- inner: item
786
- }),
787
- object: (shape) => ({
788
- kind: "object",
789
- inner: shape
790
- }),
791
- record: (value) => ({
792
- kind: "record",
793
- inner: value
794
- }),
795
- any: () => ({ kind: "any" }),
796
- zod: () => ({ kind: "zod" }),
797
- ref: (path) => ({
798
- kind: "ref",
799
- inner: path
800
- }),
801
- propsOf: (path) => ({
802
- kind: "propsOf",
803
- inner: path
804
- }),
805
- map: (entryShape) => ({
806
- kind: "map",
807
- inner: entryShape
808
- }),
809
- optional: () => ({ optional: true })
810
- };
811
- }
812
- function defineSchema(builder, options) {
813
- return {
814
- definition: builder(createBuilder()),
815
- promptTemplate: options?.promptTemplate,
816
- defaultRules: options?.defaultRules,
817
- builtInActions: options?.builtInActions,
818
- createCatalog(catalog) {
819
- return createCatalogFromSchema(this, catalog);
820
- }
821
- };
822
- }
823
- function createCatalogFromSchema(schema, catalogData) {
824
- const components = catalogData.components;
825
- const actions = catalogData.actions;
826
- const componentNames = components ? Object.keys(components) : [];
827
- const actionNames = actions ? Object.keys(actions) : [];
828
- const zodSchema = buildZodSchemaFromDefinition(schema.definition, catalogData);
829
- return {
830
- schema,
831
- data: catalogData,
832
- componentNames,
833
- actionNames,
834
- prompt(options = {}) {
835
- return generatePrompt(this, options);
836
- },
837
- jsonSchema(options = {}) {
838
- return zodToJsonSchema(zodSchema, options.strict ?? false);
839
- },
840
- validate(spec) {
841
- const result = zodSchema.safeParse(spec);
842
- if (result.success) return {
843
- success: true,
844
- data: result.data
845
- };
846
- return {
847
- success: false,
848
- error: result.error
849
- };
850
- },
851
- zodSchema() {
852
- return zodSchema;
853
- },
854
- get _specType() {
855
- throw new Error("_specType is only for type inference");
856
- }
857
- };
858
- }
859
- function buildZodSchemaFromDefinition(definition, catalogData) {
860
- return buildZodType(definition.spec, catalogData);
861
- }
862
- function buildZodType(schemaType, catalogData) {
863
- switch (schemaType.kind) {
864
- case "string": return z.string();
865
- case "number": return z.number();
866
- case "boolean": return z.boolean();
867
- case "any": return z.any();
868
- case "array": {
869
- const inner = buildZodType(schemaType.inner, catalogData);
870
- return z.array(inner);
871
- }
872
- case "object": {
873
- const shape = schemaType.inner;
874
- const zodShape = {};
875
- for (const [key, value] of Object.entries(shape)) {
876
- let zodType = buildZodType(value, catalogData);
877
- if (value.optional) zodType = zodType.optional();
878
- zodShape[key] = zodType;
879
- }
880
- return z.object(zodShape);
881
- }
882
- case "record": {
883
- const inner = buildZodType(schemaType.inner, catalogData);
884
- return z.record(z.string(), inner);
885
- }
886
- case "ref": {
887
- const path = schemaType.inner;
888
- const keys = getKeysFromPath(path, catalogData);
889
- if (keys.length === 0) return z.string();
890
- if (keys.length === 1) return z.literal(keys[0]);
891
- return z.enum(keys);
892
- }
893
- case "propsOf": {
894
- const path = schemaType.inner;
895
- const propsSchemas = getPropsFromPath(path, catalogData);
896
- if (propsSchemas.length === 0) return z.record(z.string(), z.unknown());
897
- if (propsSchemas.length === 1) return propsSchemas[0];
898
- return z.record(z.string(), z.unknown());
899
- }
900
- default: return z.unknown();
901
- }
902
- }
903
- function getKeysFromPath(path, catalogData) {
904
- const parts = path.split(".");
905
- let current = { catalog: catalogData };
906
- for (const part of parts) if (current && typeof current === "object") current = current[part];
907
- else return [];
908
- if (current && typeof current === "object") return Object.keys(current);
909
- return [];
910
- }
911
- function getPropsFromPath(path, catalogData) {
912
- const parts = path.split(".");
913
- let current = { catalog: catalogData };
914
- for (const part of parts) if (current && typeof current === "object") current = current[part];
915
- else return [];
916
- if (current && typeof current === "object") return Object.values(current).map((entry) => entry.props).filter((props) => props !== void 0);
917
- return [];
918
- }
919
- function generatePrompt(catalog, options) {
920
- if (catalog.schema.promptTemplate) {
921
- const context = {
922
- catalog: catalog.data,
923
- componentNames: catalog.componentNames,
924
- actionNames: catalog.actionNames,
925
- options,
926
- formatZodType
927
- };
928
- return catalog.schema.promptTemplate(context);
929
- }
930
- const { system = "You are a UI generator that outputs JSON.", customRules = [], mode: rawMode = "standalone" } = options;
931
- const mode = rawMode === "chat" ? (console.warn("[json-render] mode \"chat\" is deprecated, use \"inline\" instead"), "inline") : rawMode === "generate" ? (console.warn("[json-render] mode \"generate\" is deprecated, use \"standalone\" instead"), "standalone") : rawMode;
932
- const lines = [];
933
- lines.push(system);
934
- lines.push("");
935
- if (mode === "inline") {
936
- lines.push("OUTPUT FORMAT (text + JSONL, RFC 6902 JSON Patch):");
937
- lines.push("You respond conversationally. When generating UI, first write a brief explanation (1-3 sentences), then output JSONL patch lines wrapped in a ```spec code fence.");
938
- lines.push("The JSONL lines use RFC 6902 JSON Patch operations to build a UI tree. Always wrap them in a ```spec fence block:");
939
- lines.push(" ```spec");
940
- lines.push(" {\"op\":\"add\",\"path\":\"/root\",\"value\":\"main\"}");
941
- lines.push(" {\"op\":\"add\",\"path\":\"/elements/main\",\"value\":{\"type\":\"Card\",\"props\":{\"title\":\"Hello\"},\"children\":[]}}");
942
- lines.push(" ```");
943
- lines.push("If the user's message does not require a UI (e.g. a greeting or clarifying question), respond with text only — no JSONL.");
944
- } else {
945
- lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
946
- lines.push("Output JSONL (one JSON object per line) using RFC 6902 JSON Patch operations to build a UI tree.");
947
- }
948
- lines.push("Each line is a JSON patch operation (add, remove, replace). Start with /root, then stream /elements and /state patches interleaved so the UI fills in progressively as it streams.");
949
- lines.push("");
950
- lines.push("Example output (each line is a separate JSON object):");
951
- lines.push("");
952
- const allComponents = catalog.data.components;
953
- const cn = catalog.componentNames;
954
- const comp1 = cn[0] || "Component";
955
- const comp2 = cn.length > 1 ? cn[1] : comp1;
956
- const comp1Def = allComponents?.[comp1];
957
- const comp2Def = allComponents?.[comp2];
958
- const comp1Props = comp1Def ? getExampleProps(comp1Def) : {};
959
- const comp2Props = comp2Def ? getExampleProps(comp2Def) : {};
960
- const dynamicPropName = comp2Def?.props ? findFirstStringProp(comp2Def.props) : null;
961
- const dynamicProps = dynamicPropName ? {
962
- ...comp2Props,
963
- [dynamicPropName]: { $item: "title" }
964
- } : comp2Props;
965
- const exampleOutput = [
966
- JSON.stringify({
967
- op: "add",
968
- path: "/root",
969
- value: "main"
970
- }),
971
- JSON.stringify({
972
- op: "add",
973
- path: "/elements/main",
974
- value: {
975
- type: comp1,
976
- props: comp1Props,
977
- children: ["child-1", "list"]
978
- }
979
- }),
980
- JSON.stringify({
981
- op: "add",
982
- path: "/elements/child-1",
983
- value: {
984
- type: comp2,
985
- props: comp2Props,
986
- children: []
987
- }
988
- }),
989
- JSON.stringify({
990
- op: "add",
991
- path: "/elements/list",
992
- value: {
993
- type: comp1,
994
- props: comp1Props,
995
- repeat: {
996
- statePath: "/items",
997
- key: "id"
998
- },
999
- children: ["item"]
1000
- }
1001
- }),
1002
- JSON.stringify({
1003
- op: "add",
1004
- path: "/elements/item",
1005
- value: {
1006
- type: comp2,
1007
- props: dynamicProps,
1008
- children: []
1009
- }
1010
- }),
1011
- JSON.stringify({
1012
- op: "add",
1013
- path: "/state/items",
1014
- value: []
1015
- }),
1016
- JSON.stringify({
1017
- op: "add",
1018
- path: "/state/items/0",
1019
- value: {
1020
- id: "1",
1021
- title: "First Item"
1022
- }
1023
- }),
1024
- JSON.stringify({
1025
- op: "add",
1026
- path: "/state/items/1",
1027
- value: {
1028
- id: "2",
1029
- title: "Second Item"
1030
- }
1031
- })
1032
- ].join("\n");
1033
- lines.push(`${exampleOutput}
1034
-
1035
- Note: state patches appear right after the elements that use them, so the UI fills in as it streams. ONLY use component types from the AVAILABLE COMPONENTS list below.`);
1036
- lines.push("");
1037
- lines.push("INITIAL STATE:");
1038
- lines.push("Specs include a /state field to seed the state model. Components with { $bindState } or { $bindItem } read from and write to this state, and $state expressions read from it.");
1039
- lines.push("CRITICAL: You MUST include state patches whenever your UI displays data via $state, $bindState, $bindItem, $item, or $index expressions, or uses repeat to iterate over arrays. Without state, these references resolve to nothing and repeat lists render zero items.");
1040
- lines.push("Output state patches right after the elements that reference them, so the UI fills in progressively as it streams.");
1041
- lines.push("Stream state progressively - output one patch per array item instead of one giant blob:");
1042
- lines.push(" For arrays: {\"op\":\"add\",\"path\":\"/state/posts/0\",\"value\":{\"id\":\"1\",\"title\":\"First Post\",...}} then /state/posts/1, /state/posts/2, etc.");
1043
- lines.push(" For scalars: {\"op\":\"add\",\"path\":\"/state/newTodoText\",\"value\":\"\"}");
1044
- lines.push(" Initialize the array first if needed: {\"op\":\"add\",\"path\":\"/state/posts\",\"value\":[]}");
1045
- lines.push("When content comes from the state model, use { \"$state\": \"/some/path\" } dynamic props to display it instead of hardcoding the same value in both state and props. The state model is the single source of truth.");
1046
- lines.push("Include realistic sample data in state. For blogs: 3-4 posts with titles, excerpts, authors, dates. For product lists: 3-5 items with names, prices, descriptions. Never leave arrays empty.");
1047
- lines.push("");
1048
- lines.push("DYNAMIC LISTS (repeat field):");
1049
- lines.push("Any element can have a top-level \"repeat\" field to render its children once per item in a state array: { \"repeat\": { \"statePath\": \"/arrayPath\", \"key\": \"id\" } }.");
1050
- lines.push("The element itself renders once (as the container), and its children are expanded once per array item. \"statePath\" is the state array path. \"key\" is an optional field name on each item for stable React keys.");
1051
- lines.push(`Example: ${JSON.stringify({
1052
- type: comp1,
1053
- props: comp1Props,
1054
- repeat: {
1055
- statePath: "/todos",
1056
- key: "id"
1057
- },
1058
- children: ["todo-item"]
1059
- })}`);
1060
- lines.push("Inside children of a repeated element, use { \"$item\": \"field\" } to read a field from the current item, and { \"$index\": true } to get the current array index. For two-way binding to an item field use { \"$bindItem\": \"completed\" } on the appropriate prop.");
1061
- lines.push("ALWAYS use the repeat field for lists backed by state arrays. NEVER hardcode individual elements for each array item.");
1062
- lines.push("IMPORTANT: \"repeat\" is a top-level field on the element (sibling of type/props/children), NOT inside props.");
1063
- lines.push("");
1064
- lines.push("ARRAY STATE ACTIONS:");
1065
- lines.push("Use action \"pushState\" to append items to arrays. Params: { statePath: \"/arrayPath\", value: { ...item }, clearStatePath: \"/inputPath\" }.");
1066
- lines.push("Values inside pushState can contain { \"$state\": \"/statePath\" } references to read current state (e.g. the text from an input field).");
1067
- lines.push("Use \"$id\" inside a pushState value to auto-generate a unique ID.");
1068
- lines.push("Example: on: { \"press\": { \"action\": \"pushState\", \"params\": { \"statePath\": \"/todos\", \"value\": { \"id\": \"$id\", \"title\": { \"$state\": \"/newTodoText\" }, \"completed\": false }, \"clearStatePath\": \"/newTodoText\" } } }");
1069
- lines.push(`Use action "removeState" to remove items from arrays by index. Params: { statePath: "/arrayPath", index: N }. Inside a repeated element's children, use { "$index": true } for the current item index. Action params support the same expressions as props: { "$item": "field" } resolves to the absolute state path, { "$index": true } resolves to the index number, and { "$state": "/path" } reads a value from state.`);
1070
- lines.push("For lists where users can add/remove items (todos, carts, etc.), use pushState and removeState instead of hardcoding with setState.");
1071
- lines.push("");
1072
- lines.push("IMPORTANT: State paths use RFC 6901 JSON Pointer syntax (e.g. \"/todos/0/title\"). Do NOT use JavaScript-style dot notation (e.g. \"/todos.length\" is WRONG). To generate unique IDs for new items, use \"$id\" instead of trying to read array length.");
1073
- lines.push("");
1074
- const components = allComponents;
1075
- if (components) {
1076
- lines.push(`AVAILABLE COMPONENTS (${catalog.componentNames.length}):`);
1077
- lines.push("");
1078
- for (const [name, def] of Object.entries(components)) {
1079
- const propsStr = def.props ? formatZodType(def.props) : "{}";
1080
- const childrenStr = def.slots && def.slots.length > 0 ? " [accepts children]" : "";
1081
- const eventsStr = def.events && def.events.length > 0 ? ` [events: ${def.events.join(", ")}]` : "";
1082
- const descStr = def.description ? ` - ${def.description}` : "";
1083
- lines.push(`- ${name}: ${propsStr}${descStr}${childrenStr}${eventsStr}`);
1084
- }
1085
- lines.push("");
1086
- }
1087
- const actions = catalog.data.actions;
1088
- const builtInActions = catalog.schema.builtInActions ?? [];
1089
- const hasCustomActions = actions && catalog.actionNames.length > 0;
1090
- const hasBuiltInActions = builtInActions.length > 0;
1091
- if (hasCustomActions || hasBuiltInActions) {
1092
- lines.push("AVAILABLE ACTIONS:");
1093
- lines.push("");
1094
- for (const action2 of builtInActions) lines.push(`- ${action2.name}: ${action2.description} [built-in]`);
1095
- if (hasCustomActions) for (const [name, def] of Object.entries(actions)) lines.push(`- ${name}${def.description ? `: ${def.description}` : ""}`);
1096
- lines.push("");
1097
- }
1098
- lines.push("EVENTS (the `on` field):");
1099
- lines.push("Elements can have an optional `on` field to bind events to actions. The `on` field is a top-level field on the element (sibling of type/props/children), NOT inside props.");
1100
- lines.push("Each key in `on` is an event name (from the component's supported events), and the value is an action binding: `{ \"action\": \"<actionName>\", \"params\": { ... } }`.");
1101
- lines.push("");
1102
- lines.push("Example:");
1103
- lines.push(` ${JSON.stringify({
1104
- type: comp1,
1105
- props: comp1Props,
1106
- on: { press: {
1107
- action: "setState",
1108
- params: {
1109
- statePath: "/saved",
1110
- value: true
1111
- }
1112
- } },
1113
- children: []
1114
- })}`);
1115
- lines.push("");
1116
- lines.push("Action params can use dynamic references to read from state: { \"$state\": \"/statePath\" }.");
1117
- lines.push("IMPORTANT: Do NOT put action/actionParams inside props. Always use the `on` field for event bindings.");
1118
- lines.push("");
1119
- lines.push("VISIBILITY CONDITIONS:");
1120
- lines.push("Elements can have an optional `visible` field to conditionally show/hide based on state. IMPORTANT: `visible` is a top-level field on the element object (sibling of type/props/children), NOT inside props.");
1121
- lines.push(`Correct: ${JSON.stringify({
1122
- type: comp1,
1123
- props: comp1Props,
1124
- visible: {
1125
- $state: "/activeTab",
1126
- eq: "home"
1127
- },
1128
- children: ["..."]
1129
- })}`);
1130
- lines.push("- `{ \"$state\": \"/path\" }` - visible when state at path is truthy");
1131
- lines.push("- `{ \"$state\": \"/path\", \"not\": true }` - visible when state at path is falsy");
1132
- lines.push("- `{ \"$state\": \"/path\", \"eq\": \"value\" }` - visible when state equals value");
1133
- lines.push("- `{ \"$state\": \"/path\", \"neq\": \"value\" }` - visible when state does not equal value");
1134
- lines.push("- `{ \"$state\": \"/path\", \"gt\": N }` / `gte` / `lt` / `lte` - numeric comparisons");
1135
- lines.push("- Use ONE operator per condition (eq, neq, gt, gte, lt, lte). Do not combine multiple operators.");
1136
- lines.push("- Any condition can add `\"not\": true` to invert its result");
1137
- lines.push("- `[condition, condition]` - all conditions must be true (implicit AND)");
1138
- lines.push("- `{ \"$and\": [condition, condition] }` - explicit AND (use when nesting inside $or)");
1139
- lines.push("- `{ \"$or\": [condition, condition] }` - at least one must be true (OR)");
1140
- lines.push("- `true` / `false` - always visible/hidden");
1141
- lines.push("");
1142
- lines.push("Use a component with on.press bound to setState to update state and drive visibility.");
1143
- lines.push(`Example: A ${comp1} with on: { "press": { "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } } } sets state, then a container with visible: { "$state": "/activeTab", "eq": "home" } shows only when that tab is active.`);
1144
- lines.push("");
1145
- lines.push("For tab patterns where the first/default tab should be visible when no tab is selected yet, use $or to handle both cases: visible: { \"$or\": [{ \"$state\": \"/activeTab\", \"eq\": \"home\" }, { \"$state\": \"/activeTab\", \"not\": true }] }. This ensures the first tab is visible both when explicitly selected AND when /activeTab is not yet set.");
1146
- lines.push("");
1147
- lines.push("DYNAMIC PROPS:");
1148
- lines.push("Any prop value can be a dynamic expression that resolves based on state. Three forms are supported:");
1149
- lines.push("");
1150
- lines.push("1. Read-only state: `{ \"$state\": \"/statePath\" }` - resolves to the value at that state path (one-way read).");
1151
- lines.push(" Example: `\"color\": { \"$state\": \"/theme/primary\" }` reads the color from state.");
1152
- lines.push("");
1153
- lines.push("2. Two-way binding: `{ \"$bindState\": \"/statePath\" }` - resolves to the value at the state path AND enables write-back. Use on form input props (value, checked, pressed, etc.).");
1154
- lines.push(" Example: `\"value\": { \"$bindState\": \"/form/email\" }` binds the input value to /form/email.");
1155
- lines.push(" Inside repeat scopes: `\"checked\": { \"$bindItem\": \"completed\" }` binds to the current item's completed field.");
1156
- lines.push("");
1157
- lines.push("3. Conditional: `{ \"$cond\": <condition>, \"$then\": <value>, \"$else\": <value> }` - evaluates the condition (same syntax as visibility conditions) and picks the matching value.");
1158
- lines.push(" Example: `\"color\": { \"$cond\": { \"$state\": \"/activeTab\", \"eq\": \"home\" }, \"$then\": \"#007AFF\", \"$else\": \"#8E8E93\" }`");
1159
- lines.push("");
1160
- lines.push("Use $bindState for form inputs (text fields, checkboxes, selects, sliders, etc.) and $state for read-only data display. Inside repeat scopes, use $bindItem for form inputs bound to the current item. Use dynamic props instead of duplicating elements with opposing visible conditions when only prop values differ.");
1161
- lines.push("");
1162
- lines.push("4. Template: `{ \"$template\": \"Hello, ${/name}!\" }` - interpolates references in the string. Absolute paths like `${/path}` resolve against the state model. Bare names like `${field}` resolve against the current repeat item first, then fall back to the state model at `/<field>`.");
1163
- lines.push(" Example: `\"label\": { \"$template\": \"Items: ${/cart/count} | Total: ${/cart/total}\" }` renders \"Items: 3 | Total: 42.00\" when /cart/count is 3 and /cart/total is 42.00. Inside a repeat, `{ \"$template\": \"${name} - ${email}\" }` reads name and email from each item.");
1164
- lines.push("");
1165
- const catalogFunctions = catalog.data.functions;
1166
- if (catalogFunctions && Object.keys(catalogFunctions).length > 0) {
1167
- lines.push("5. Computed: `{ \"$computed\": \"<functionName>\", \"args\": { \"key\": <expression> } }` - calls a registered function with resolved args and returns the result.");
1168
- lines.push(" Example: `\"value\": { \"$computed\": \"fullName\", \"args\": { \"first\": { \"$state\": \"/form/firstName\" }, \"last\": { \"$state\": \"/form/lastName\" } } }`");
1169
- lines.push(" Available functions:");
1170
- for (const name of Object.keys(catalogFunctions)) lines.push(` - ${name}`);
1171
- lines.push("");
1172
- }
1173
- const directives = options.directives;
1174
- if (directives && directives.length > 0) {
1175
- lines.push("CUSTOM DYNAMIC VALUES:");
1176
- lines.push("");
1177
- for (const d of directives) {
1178
- const desc = d.description ? ` (${d.description})` : "";
1179
- lines.push(`- ${d.name}${desc}: ${formatZodType(d.schema)}`);
1180
- }
1181
- lines.push("");
1182
- lines.push("Directives compose: any value field can contain another directive or a $state expression, resolved inside-out.");
1183
- lines.push("");
1184
- }
1185
- if (allComponents ? Object.entries(allComponents).some(([, def]) => {
1186
- if (!def.props) return false;
1187
- return formatZodType(def.props).includes("checks");
1188
- }) : false) {
1189
- lines.push("VALIDATION:");
1190
- lines.push("Form components that accept a `checks` prop support client-side validation.");
1191
- lines.push("Each check is an object: { \"type\": \"<name>\", \"message\": \"...\", \"args\": { ... } }");
1192
- lines.push("");
1193
- lines.push("Built-in validation types:");
1194
- lines.push(" - required — value must be non-empty");
1195
- lines.push(" - email — valid email format");
1196
- lines.push(" - minLength — minimum string length (args: { \"min\": N })");
1197
- lines.push(" - maxLength — maximum string length (args: { \"max\": N })");
1198
- lines.push(" - pattern — match a regex (args: { \"pattern\": \"regex\" })");
1199
- lines.push(" - min — minimum numeric value (args: { \"min\": N })");
1200
- lines.push(" - max — maximum numeric value (args: { \"max\": N })");
1201
- lines.push(" - numeric — value must be a number");
1202
- lines.push(" - url — valid URL format");
1203
- lines.push(" - matches — must equal another field (args: { \"other\": { \"$state\": \"/path\" } })");
1204
- lines.push(" - equalTo — alias for matches (args: { \"other\": { \"$state\": \"/path\" } })");
1205
- lines.push(" - lessThan — value must be less than another field (args: { \"other\": { \"$state\": \"/path\" } })");
1206
- lines.push(" - greaterThan — value must be greater than another field (args: { \"other\": { \"$state\": \"/path\" } })");
1207
- lines.push(" - requiredIf — required only when another field is truthy (args: { \"field\": { \"$state\": \"/path\" } })");
1208
- lines.push("");
1209
- lines.push("Example:");
1210
- lines.push(" \"checks\": [{ \"type\": \"required\", \"message\": \"Email is required\" }, { \"type\": \"email\", \"message\": \"Invalid email\" }]");
1211
- lines.push("");
1212
- lines.push("IMPORTANT: When using checks, the component must also have a { $bindState } or { $bindItem } on its value/checked prop for two-way binding.");
1213
- lines.push("Always include validation checks on form inputs for a good user experience (e.g. required, email, minLength).");
1214
- lines.push("");
1215
- }
1216
- if (hasCustomActions || hasBuiltInActions) {
1217
- lines.push("STATE WATCHERS:");
1218
- lines.push("Elements can have an optional `watch` field to react to state changes and trigger actions. The `watch` field is a top-level field on the element (sibling of type/props/children), NOT inside props.");
1219
- lines.push("Maps state paths (JSON Pointers) to action bindings. When the value at a watched path changes, the bound actions fire automatically.");
1220
- lines.push("");
1221
- lines.push("Example (cascading select — country changes trigger city loading):");
1222
- lines.push(` ${JSON.stringify({
1223
- type: "Select",
1224
- props: {
1225
- value: { $bindState: "/form/country" },
1226
- options: [
1227
- "US",
1228
- "Canada",
1229
- "UK"
1230
- ]
1231
- },
1232
- watch: { "/form/country": {
1233
- action: "loadCities",
1234
- params: { country: { $state: "/form/country" } }
1235
- } },
1236
- children: []
1237
- })}`);
1238
- lines.push("");
1239
- lines.push("Use `watch` for cascading dependencies where changing one field should trigger side effects (loading data, resetting dependent fields, computing derived values).");
1240
- lines.push("IMPORTANT: `watch` is a top-level field on the element (sibling of type/props/children), NOT inside props. Watchers only fire when the value changes, not on initial render.");
1241
- lines.push("");
1242
- }
1243
- const editModes = options.editModes;
1244
- if (editModes && editModes.length > 0) lines.push(buildEditInstructions({ modes: editModes }, "json"));
1245
- lines.push("RULES:");
1246
- const baseRules = mode === "inline" ? [
1247
- "When generating UI, wrap all JSONL patches in a ```spec code fence - one JSON object per line inside the fence",
1248
- "Write a brief conversational response before any JSONL output",
1249
- "First set root: {\"op\":\"add\",\"path\":\"/root\",\"value\":\"<root-key>\"}",
1250
- "Then add each element: {\"op\":\"add\",\"path\":\"/elements/<key>\",\"value\":{...}}",
1251
- "Output /state patches right after the elements that use them, one per array item for progressive loading. REQUIRED whenever using $state, $bindState, $bindItem, $item, $index, or repeat.",
1252
- "ONLY use components listed above",
1253
- "Each element value needs: type, props, children (array of child keys)",
1254
- "Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')"
1255
- ] : [
1256
- "Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
1257
- "First set root: {\"op\":\"add\",\"path\":\"/root\",\"value\":\"<root-key>\"}",
1258
- "Then add each element: {\"op\":\"add\",\"path\":\"/elements/<key>\",\"value\":{...}}",
1259
- "Output /state patches right after the elements that use them, one per array item for progressive loading. REQUIRED whenever using $state, $bindState, $bindItem, $item, $index, or repeat.",
1260
- "ONLY use components listed above",
1261
- "Each element value needs: type, props, children (array of child keys)",
1262
- "Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')"
1263
- ];
1264
- const schemaRules = catalog.schema.defaultRules ?? [];
1265
- [
1266
- ...baseRules,
1267
- ...schemaRules,
1268
- ...customRules
1269
- ].forEach((rule, i) => {
1270
- lines.push(`${i + 1}. ${rule}`);
1271
- });
1272
- return lines.join("\n");
1273
- }
1274
- function getExampleProps(def) {
1275
- if (def.example && Object.keys(def.example).length > 0) return def.example;
1276
- if (def.props) return generateExamplePropsFromZod(def.props);
1277
- return {};
1278
- }
1279
- function generateExamplePropsFromZod(schema) {
1280
- if (!schema || !schema._def) return {};
1281
- const def = schema._def;
1282
- const typeName = getZodTypeName(schema);
1283
- if (typeName !== "ZodObject" && typeName !== "object") return {};
1284
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1285
- if (!shape) return {};
1286
- const result = {};
1287
- for (const [key, value] of Object.entries(shape)) {
1288
- const innerTypeName = getZodTypeName(value);
1289
- if (innerTypeName === "ZodOptional" || innerTypeName === "optional" || innerTypeName === "ZodNullable" || innerTypeName === "nullable") continue;
1290
- result[key] = generateExampleValue(value);
1291
- }
1292
- return result;
1293
- }
1294
- function generateExampleValue(schema) {
1295
- if (!schema || !schema._def) return "...";
1296
- const def = schema._def;
1297
- switch (getZodTypeName(schema)) {
1298
- case "ZodString":
1299
- case "string": return "example";
1300
- case "ZodNumber":
1301
- case "number": return 0;
1302
- case "ZodBoolean":
1303
- case "boolean": return true;
1304
- case "ZodLiteral":
1305
- case "literal": return def.value;
1306
- case "ZodEnum":
1307
- case "enum":
1308
- if (Array.isArray(def.values) && def.values.length > 0) return def.values[0];
1309
- if (def.entries && typeof def.entries === "object") {
1310
- const values = Object.values(def.entries);
1311
- return values.length > 0 ? values[0] : "example";
1312
- }
1313
- return "example";
1314
- case "ZodOptional":
1315
- case "optional":
1316
- case "ZodNullable":
1317
- case "nullable":
1318
- case "ZodDefault":
1319
- case "default": {
1320
- const inner = def.innerType ?? def.wrapped;
1321
- return inner ? generateExampleValue(inner) : null;
1322
- }
1323
- case "ZodArray":
1324
- case "array": return [];
1325
- case "ZodObject":
1326
- case "object": return generateExamplePropsFromZod(schema);
1327
- case "ZodUnion":
1328
- case "union": {
1329
- const options = def.options;
1330
- return options && options.length > 0 ? generateExampleValue(options[0]) : "...";
1331
- }
1332
- default: return "...";
1333
- }
1334
- }
1335
- function findFirstStringProp(schema) {
1336
- if (!schema || !schema._def) return null;
1337
- const def = schema._def;
1338
- const typeName = getZodTypeName(schema);
1339
- if (typeName !== "ZodObject" && typeName !== "object") return null;
1340
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1341
- if (!shape) return null;
1342
- for (const [key, value] of Object.entries(shape)) {
1343
- const innerTypeName = getZodTypeName(value);
1344
- if (innerTypeName === "ZodOptional" || innerTypeName === "optional" || innerTypeName === "ZodNullable" || innerTypeName === "nullable") continue;
1345
- if (innerTypeName === "ZodString" || innerTypeName === "string") return key;
1346
- }
1347
- return null;
1348
- }
1349
- function getZodTypeName(schema) {
1350
- if (!schema || !schema._def) return "";
1351
- const def = schema._def;
1352
- return def.typeName ?? def.type ?? "";
1353
- }
1354
- function formatZodType(schema) {
1355
- if (!schema || !schema._def) return "unknown";
1356
- const def = schema._def;
1357
- switch (getZodTypeName(schema)) {
1358
- case "ZodString":
1359
- case "string": return "string";
1360
- case "ZodNumber":
1361
- case "number": return "number";
1362
- case "ZodBoolean":
1363
- case "boolean": return "boolean";
1364
- case "ZodLiteral":
1365
- case "literal": {
1366
- const litValue = def.values?.[0] ?? def.value;
1367
- return JSON.stringify(litValue);
1368
- }
1369
- case "ZodEnum":
1370
- case "enum": {
1371
- let values;
1372
- if (Array.isArray(def.values)) values = def.values;
1373
- else if (def.entries && typeof def.entries === "object") values = Object.values(def.entries);
1374
- else return "enum";
1375
- return values.map((v) => `"${v}"`).join(" | ");
1376
- }
1377
- case "ZodArray":
1378
- case "array": {
1379
- const inner = typeof def.element === "object" ? def.element : typeof def.type === "object" ? def.type : void 0;
1380
- return inner ? `Array<${formatZodType(inner)}>` : "Array<unknown>";
1381
- }
1382
- case "ZodObject":
1383
- case "object": {
1384
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1385
- if (!shape) return "object";
1386
- return `{ ${Object.entries(shape).map(([key, value]) => {
1387
- const innerTypeName = getZodTypeName(value);
1388
- return `${key}${innerTypeName === "ZodOptional" || innerTypeName === "ZodNullable" || innerTypeName === "optional" || innerTypeName === "nullable" ? "?" : ""}: ${formatZodType(value)}`;
1389
- }).join(", ")} }`;
1390
- }
1391
- case "ZodOptional":
1392
- case "optional":
1393
- case "ZodNullable":
1394
- case "nullable": {
1395
- const inner = def.innerType ?? def.wrapped;
1396
- return inner ? formatZodType(inner) : "unknown";
1397
- }
1398
- case "ZodUnion":
1399
- case "union": {
1400
- const options = def.options;
1401
- return options ? options.map((opt) => formatZodType(opt)).join(" | ") : "unknown";
1402
- }
1403
- case "ZodRecord":
1404
- case "record": {
1405
- const keyType = def.keyType ?? void 0;
1406
- const valueType = def.valueType ?? def.element ?? void 0;
1407
- return `Record<${keyType ? formatZodType(keyType) : "string"}, ${valueType ? formatZodType(valueType) : "unknown"}>`;
1408
- }
1409
- case "ZodDefault":
1410
- case "default": {
1411
- const inner = def.innerType ?? def.wrapped;
1412
- return inner ? formatZodType(inner) : "unknown";
1413
- }
1414
- default: return "unknown";
1415
- }
1416
- }
1417
- function zodTypeName(def) {
1418
- if (typeof def.type === "string") return def.type;
1419
- if (typeof def.typeName === "string") return def.typeName;
1420
- return "";
1421
- }
1422
- function normalizeTypeName(raw) {
1423
- if (raw.startsWith("Zod")) return raw.slice(3).toLowerCase();
1424
- return raw.toLowerCase();
1425
- }
1426
- function zodToJsonSchema(schema, strict = false) {
1427
- const def = schema._def;
1428
- switch (normalizeTypeName(zodTypeName(def))) {
1429
- case "string": return { type: "string" };
1430
- case "number": return { type: "number" };
1431
- case "boolean": return { type: "boolean" };
1432
- case "literal": {
1433
- const values = def.values;
1434
- return { const: values ? values[0] : def.value };
1435
- }
1436
- case "enum": {
1437
- const entries = def.entries;
1438
- return { enum: (entries ? Object.values(entries) : def.values) ?? [] };
1439
- }
1440
- case "array": {
1441
- const inner = def.element ?? def.type;
1442
- return {
1443
- type: "array",
1444
- items: inner ? zodToJsonSchema(inner, strict) : {}
1445
- };
1446
- }
1447
- case "object": {
1448
- const rawShape = def.shape;
1449
- const shape = typeof rawShape === "function" ? rawShape() : rawShape;
1450
- if (!shape) {
1451
- if (strict) return {
1452
- type: "object",
1453
- properties: {},
1454
- required: [],
1455
- additionalProperties: false
1456
- };
1457
- return { type: "object" };
1458
- }
1459
- const properties = {};
1460
- const required = [];
1461
- for (const [key, value] of Object.entries(shape)) {
1462
- const innerDef = value._def;
1463
- const innerKind = normalizeTypeName(zodTypeName(innerDef));
1464
- const isOptional = innerKind === "optional" || innerKind === "nullable";
1465
- if (strict) {
1466
- required.push(key);
1467
- if (isOptional) properties[key] = { anyOf: [zodToJsonSchema(value, strict), { type: "null" }] };
1468
- else properties[key] = zodToJsonSchema(value, strict);
1469
- } else {
1470
- properties[key] = zodToJsonSchema(value);
1471
- if (!isOptional) required.push(key);
1472
- }
1473
- }
1474
- return {
1475
- type: "object",
1476
- properties,
1477
- required: required.length > 0 ? required : void 0,
1478
- additionalProperties: false
1479
- };
1480
- }
1481
- case "record": {
1482
- const valueType = def.valueType;
1483
- if (strict) return {
1484
- type: "object",
1485
- properties: {},
1486
- required: [],
1487
- additionalProperties: false
1488
- };
1489
- return {
1490
- type: "object",
1491
- additionalProperties: valueType ? zodToJsonSchema(valueType) : true
1492
- };
1493
- }
1494
- case "optional":
1495
- case "nullable": {
1496
- const inner = def.innerType;
1497
- return inner ? zodToJsonSchema(inner, strict) : {};
1498
- }
1499
- case "union": {
1500
- const options = def.options;
1501
- return options ? { anyOf: options.map((o) => zodToJsonSchema(o, strict)) } : {};
1502
- }
1503
- case "any":
1504
- case "unknown":
1505
- if (strict) return {
1506
- type: "object",
1507
- properties: {},
1508
- required: [],
1509
- additionalProperties: false
1510
- };
1511
- return {};
1512
- default: return {};
1513
- }
1514
- }
1515
- function defineCatalog(schema, catalog) {
1516
- return schema.createCatalog(catalog);
1517
- }
1518
-
1519
- //#endregion
1520
- //#region ../../node_modules/.pnpm/@json-render+react@0.19.0_react@19.2.8_zod@4.4.3/node_modules/@json-render/react/dist/chunk-WYDS23XB.mjs
1521
- var schema = defineSchema((s) => ({
1522
- spec: s.object({
1523
- /** Root element key */
1524
- root: s.string(),
1525
- /** Flat map of elements by key */
1526
- elements: s.record(s.object({
1527
- /** Component type from catalog */
1528
- type: s.ref("catalog.components"),
1529
- /** Component props */
1530
- props: s.propsOf("catalog.components"),
1531
- /** Child element keys (flat reference) */
1532
- children: s.array(s.string()),
1533
- /** Visibility condition */
1534
- visible: s.any()
1535
- }))
1536
- }),
1537
- catalog: s.object({
1538
- /** Component definitions */
1539
- components: s.map({
1540
- /** Zod schema for component props */
1541
- props: s.zod(),
1542
- /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
1543
- slots: s.array(s.string()),
1544
- /** Description for AI generation hints */
1545
- description: s.string(),
1546
- /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
1547
- example: s.any()
1548
- }),
1549
- /** Action definitions (optional) */
1550
- actions: s.map({
1551
- /** Zod schema for action params */
1552
- params: s.zod(),
1553
- /** Description for AI generation hints */
1554
- description: s.string()
1555
- })
1556
- })
1557
- }), {
1558
- builtInActions: [
1559
- {
1560
- name: "setState",
1561
- description: "Update a value in the state model at the given statePath. Params: { statePath: string, value: any }"
1562
- },
1563
- {
1564
- name: "pushState",
1565
- 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."
1566
- },
1567
- {
1568
- name: "removeState",
1569
- description: "Remove an item from an array in state by index. Params: { statePath: string, index: number }"
1570
- },
1571
- {
1572
- name: "validateForm",
1573
- 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[]> }."
1574
- }
1575
- ],
1576
- defaultRules: [
1577
- "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.",
1578
- "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.",
1579
- "CRITICAL: The \"visible\" field goes on the ELEMENT object, NOT inside \"props\". Correct: {\"type\":\"<ComponentName>\",\"props\":{},\"visible\":{\"$state\":\"/tab\",\"eq\":\"home\"},\"children\":[...]}.",
1580
- "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.",
1581
- "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).",
1582
- "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.",
1583
- "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.",
1584
- "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.",
1585
- "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."
1586
- ]
1587
- });
1588
-
1589
- //#endregion
1590
- //#region ../../node_modules/.pnpm/@json-render+react@0.19.0_react@19.2.8_zod@4.4.3/node_modules/@json-render/react/dist/index.mjs
1591
- var StateContext = createContext(null);
1592
- function computeInitialFlat(isControlled, initialState) {
1593
- if (isControlled) return null;
1594
- if (Object.keys(initialState).length === 0) return {};
1595
- return flattenToPointers(initialState);
1596
- }
1597
- function StateProvider({ store: externalStore, initialState = {}, onStateChange, children }) {
1598
- const internalStoreRef = useRef(void 0);
1599
- if (!externalStore && !internalStoreRef.current) internalStoreRef.current = createStateStore(initialState);
1600
- const store = externalStore ?? internalStoreRef.current;
1601
- const storeRef = useRef(store);
1602
- storeRef.current = store;
1603
- const isControlledRef = useRef(!!externalStore);
1604
- isControlledRef.current = !!externalStore;
1605
- const initialModeRef = useRef(externalStore ? "controlled" : "uncontrolled");
1606
- const modeWarnedRef = useRef(false);
1607
- if (process.env.NODE_ENV !== "production") {
1608
- const currentMode = externalStore ? "controlled" : "uncontrolled";
1609
- if (currentMode !== initialModeRef.current && !modeWarnedRef.current) {
1610
- modeWarnedRef.current = true;
1611
- console.warn(`StateProvider: switching from ${initialModeRef.current} to ${currentMode} mode is not supported.`);
1612
- }
1613
- }
1614
- const prevInitialStateRef = useRef(initialState);
1615
- const prevFlatRef = useRef(computeInitialFlat(!!externalStore, initialState));
1616
- useEffect(() => {
1617
- if (externalStore) return;
1618
- if (initialState === prevInitialStateRef.current) return;
1619
- prevInitialStateRef.current = initialState;
1620
- const nextFlat = initialState && Object.keys(initialState).length > 0 ? flattenToPointers(initialState) : {};
1621
- const prevFlat = prevFlatRef.current ?? {};
1622
- const allKeys = /* @__PURE__ */ new Set([...Object.keys(prevFlat), ...Object.keys(nextFlat)]);
1623
- const updates = {};
1624
- for (const key of allKeys) if (prevFlat[key] !== nextFlat[key]) updates[key] = key in nextFlat ? nextFlat[key] : void 0;
1625
- prevFlatRef.current = nextFlat;
1626
- if (Object.keys(updates).length > 0) store.update(updates);
1627
- }, [
1628
- externalStore,
1629
- initialState,
1630
- store
1631
- ]);
1632
- const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot ?? store.getSnapshot);
1633
- const onStateChangeRef = useRef(onStateChange);
1634
- onStateChangeRef.current = onStateChange;
1635
- const set = useCallback((path, value2) => {
1636
- const s = storeRef.current;
1637
- const prev = s.getSnapshot();
1638
- s.set(path, value2);
1639
- if (!isControlledRef.current && s.getSnapshot() !== prev) onStateChangeRef.current?.([{
1640
- path,
1641
- value: value2
1642
- }]);
1643
- }, []);
1644
- const update = useCallback((updates) => {
1645
- const s = storeRef.current;
1646
- const prev = s.getSnapshot();
1647
- s.update(updates);
1648
- if (!isControlledRef.current && s.getSnapshot() !== prev) {
1649
- const changes = [];
1650
- for (const [path, value2] of Object.entries(updates)) if (getByPath(prev, path) !== value2) changes.push({
1651
- path,
1652
- value: value2
1653
- });
1654
- if (changes.length > 0) onStateChangeRef.current?.(changes);
1655
- }
1656
- }, []);
1657
- const get = useCallback((path) => storeRef.current.get(path), []);
1658
- const getSnapshot = useCallback(() => storeRef.current.getSnapshot(), []);
1659
- const value = useMemo(() => ({
1660
- state,
1661
- get,
1662
- set,
1663
- update,
1664
- getSnapshot
1665
- }), [
1666
- state,
1667
- get,
1668
- set,
1669
- update,
1670
- getSnapshot
1671
- ]);
1672
- return /* @__PURE__ */ jsx(StateContext.Provider, {
1673
- value,
1674
- children
1675
- });
1676
- }
1677
- function useStateStore() {
1678
- const ctx = useContext(StateContext);
1679
- if (!ctx) throw new Error("useStateStore must be used within a StateProvider");
1680
- return ctx;
1681
- }
1682
- var VisibilityContext = createContext(null);
1683
- function VisibilityProvider({ children }) {
1684
- const { state } = useStateStore();
1685
- const ctx = useMemo(() => ({ stateModel: state }), [state]);
1686
- const isVisible = useMemo(() => (condition) => evaluateVisibility(condition, ctx), [ctx]);
1687
- const value = useMemo(() => ({
1688
- isVisible,
1689
- ctx
1690
- }), [isVisible, ctx]);
1691
- return /* @__PURE__ */ jsx(VisibilityContext.Provider, {
1692
- value,
1693
- children
1694
- });
1695
- }
1696
- function useVisibility() {
1697
- const ctx = useContext(VisibilityContext);
1698
- if (!ctx) throw new Error("useVisibility must be used within a VisibilityProvider");
1699
- return ctx;
1700
- }
1701
- var ValidationContext = createContext(null);
1702
- function dynamicArgsEqual(a, b) {
1703
- if (a === b) return true;
1704
- if (!a || !b) return false;
1705
- const keysA = Object.keys(a);
1706
- const keysB = Object.keys(b);
1707
- if (keysA.length !== keysB.length) return false;
1708
- for (const key of keysA) {
1709
- const va = a[key];
1710
- const vb = b[key];
1711
- if (va === vb) continue;
1712
- if (typeof va === "object" && va !== null && typeof vb === "object" && vb !== null) {
1713
- const sa = va.$state;
1714
- const sb = vb.$state;
1715
- if (typeof sa === "string" && sa === sb) continue;
1716
- }
1717
- return false;
1718
- }
1719
- return true;
1720
- }
1721
- function validationConfigEqual(a, b) {
1722
- if (a === b) return true;
1723
- if (a.validateOn !== b.validateOn) return false;
1724
- const ac = a.checks ?? [];
1725
- const bc = b.checks ?? [];
1726
- if (ac.length !== bc.length) return false;
1727
- for (let i = 0; i < ac.length; i++) {
1728
- const ca = ac[i];
1729
- const cb = bc[i];
1730
- if (ca.type !== cb.type) return false;
1731
- if (ca.message !== cb.message) return false;
1732
- if (!dynamicArgsEqual(ca.args, cb.args)) return false;
1733
- }
1734
- return true;
1735
- }
1736
- function ValidationProvider({ customFunctions = {}, children }) {
1737
- const { state, getSnapshot } = useStateStore();
1738
- const [fieldStates, setFieldStates] = useState({});
1739
- const fieldStatesRef = useRef({});
1740
- const [fieldConfigs, setFieldConfigs] = useState({});
1741
- const registerField = useCallback((path, config) => {
1742
- setFieldConfigs((prev) => {
1743
- const existing = prev[path];
1744
- if (existing && validationConfigEqual(existing, config)) return prev;
1745
- return {
1746
- ...prev,
1747
- [path]: config
1748
- };
1749
- });
1750
- }, []);
1751
- const validate = useCallback((path, config) => {
1752
- const currentState = getSnapshot();
1753
- const segments = path.split("/").filter(Boolean);
1754
- let value2 = currentState;
1755
- for (const seg of segments) if (value2 != null && typeof value2 === "object") value2 = value2[seg];
1756
- else {
1757
- value2 = void 0;
1758
- break;
1759
- }
1760
- const result = runValidation(config, {
1761
- value: value2,
1762
- stateModel: currentState,
1763
- customFunctions
1764
- });
1765
- const newFieldState = {
1766
- touched: fieldStatesRef.current[path]?.touched ?? true,
1767
- validated: true,
1768
- result
1769
- };
1770
- fieldStatesRef.current = {
1771
- ...fieldStatesRef.current,
1772
- [path]: newFieldState
1773
- };
1774
- setFieldStates(fieldStatesRef.current);
1775
- return result;
1776
- }, [customFunctions, getSnapshot]);
1777
- const touch = useCallback((path) => {
1778
- fieldStatesRef.current = {
1779
- ...fieldStatesRef.current,
1780
- [path]: {
1781
- ...fieldStatesRef.current[path],
1782
- touched: true,
1783
- validated: fieldStatesRef.current[path]?.validated ?? false,
1784
- result: fieldStatesRef.current[path]?.result ?? null
1785
- }
1786
- };
1787
- setFieldStates(fieldStatesRef.current);
1788
- }, []);
1789
- const clear = useCallback((path) => {
1790
- const { [path]: _, ...rest } = fieldStatesRef.current;
1791
- fieldStatesRef.current = rest;
1792
- setFieldStates(rest);
1793
- }, []);
1794
- const validateAll = useCallback(() => {
1795
- let allValid = true;
1796
- for (const [path, config] of Object.entries(fieldConfigs)) if (!validate(path, config).valid) allValid = false;
1797
- return allValid;
1798
- }, [fieldConfigs, validate]);
1799
- const value = useMemo(() => ({
1800
- customFunctions,
1801
- get fieldStates() {
1802
- return fieldStatesRef.current;
1803
- },
1804
- validate,
1805
- touch,
1806
- clear,
1807
- validateAll,
1808
- registerField
1809
- }), [
1810
- customFunctions,
1811
- fieldStates,
1812
- validate,
1813
- touch,
1814
- clear,
1815
- validateAll,
1816
- registerField
1817
- ]);
1818
- return /* @__PURE__ */ jsx(ValidationContext.Provider, {
1819
- value,
1820
- children
1821
- });
1822
- }
1823
- function useOptionalValidation() {
1824
- return useContext(ValidationContext);
1825
- }
1826
- var idCounter = 0;
1827
- function generateUniqueId() {
1828
- idCounter += 1;
1829
- return `${Date.now()}-${idCounter}`;
1830
- }
1831
- function deepResolveValue(value, get) {
1832
- if (value === null || value === void 0) return value;
1833
- if (value === "$id") return generateUniqueId();
1834
- if (typeof value === "object" && !Array.isArray(value)) {
1835
- const obj = value;
1836
- const keys = Object.keys(obj);
1837
- if (keys.length === 1 && typeof obj.$state === "string") return get(obj.$state);
1838
- if (keys.length === 1 && "$id" in obj) return generateUniqueId();
1839
- }
1840
- if (Array.isArray(value)) return value.map((item) => deepResolveValue(item, get));
1841
- if (typeof value === "object") {
1842
- const resolved = {};
1843
- for (const [key, val] of Object.entries(value)) resolved[key] = deepResolveValue(val, get);
1844
- return resolved;
1845
- }
1846
- return value;
1847
- }
1848
- var ActionContext = createContext(null);
1849
- function ActionProvider({ handlers: initialHandlers = {}, navigate, children }) {
1850
- const { get, set, getSnapshot } = useStateStore();
1851
- const validation = useOptionalValidation();
1852
- const [handlers, setHandlers] = useState(initialHandlers);
1853
- const [loadingActions, setLoadingActions] = useState(/* @__PURE__ */ new Set());
1854
- const [pendingConfirmation, setPendingConfirmation] = useState(null);
1855
- const registerHandler = useCallback((name, handler) => {
1856
- setHandlers((prev) => ({
1857
- ...prev,
1858
- [name]: handler
1859
- }));
1860
- }, []);
1861
- const execute = useCallback(async (binding) => {
1862
- const resolved = resolveAction(binding, getSnapshot());
1863
- const dispatchId = nextActionDispatchId();
1864
- const dispatchedAt = Date.now();
1865
- notifyActionDispatch({
1866
- id: dispatchId,
1867
- name: resolved.action,
1868
- params: resolved.params,
1869
- at: dispatchedAt
1870
- });
1871
- let __ok = true;
1872
- let __error = void 0;
1873
- try {
1874
- if (resolved.action === "setState" && resolved.params) {
1875
- const statePath = resolved.params.statePath;
1876
- const value2 = resolved.params.value;
1877
- if (statePath) set(statePath, value2);
1878
- return;
1879
- }
1880
- if (resolved.action === "pushState" && resolved.params) {
1881
- const statePath = resolved.params.statePath;
1882
- const rawValue = resolved.params.value;
1883
- if (statePath) {
1884
- const resolvedValue = deepResolveValue(rawValue, get);
1885
- const arr = get(statePath) ?? [];
1886
- set(statePath, [...arr, resolvedValue]);
1887
- const clearStatePath = resolved.params.clearStatePath;
1888
- if (clearStatePath) set(clearStatePath, "");
1889
- }
1890
- return;
1891
- }
1892
- if (resolved.action === "removeState" && resolved.params) {
1893
- const statePath = resolved.params.statePath;
1894
- const index = resolved.params.index;
1895
- if (statePath !== void 0 && index !== void 0) {
1896
- const arr = get(statePath) ?? [];
1897
- set(statePath, arr.filter((_, i) => i !== index));
1898
- }
1899
- return;
1900
- }
1901
- if (resolved.action === "push" && resolved.params) {
1902
- const screen = resolved.params.screen;
1903
- if (screen) {
1904
- const currentScreen = get("/currentScreen");
1905
- const navStack = get("/navStack") ?? [];
1906
- if (currentScreen) set("/navStack", [...navStack, currentScreen]);
1907
- else set("/navStack", [...navStack, ""]);
1908
- set("/currentScreen", screen);
1909
- }
1910
- return;
1911
- }
1912
- if (resolved.action === "pop") {
1913
- const navStack = get("/navStack") ?? [];
1914
- if (navStack.length > 0) {
1915
- const previousScreen = navStack[navStack.length - 1];
1916
- set("/navStack", navStack.slice(0, -1));
1917
- if (previousScreen) set("/currentScreen", previousScreen);
1918
- else set("/currentScreen", void 0);
1919
- }
1920
- return;
1921
- }
1922
- if (resolved.action === "validateForm") {
1923
- const validateAll = validation?.validateAll;
1924
- if (!validateAll) {
1925
- console.warn("validateForm action was dispatched but no ValidationProvider is connected. Ensure ValidationProvider is rendered inside the provider tree.");
1926
- return;
1927
- }
1928
- const valid = validateAll();
1929
- const errors = {};
1930
- for (const [path, fs] of Object.entries(validation.fieldStates)) if (fs.result && !fs.result.valid) errors[path] = fs.result.errors;
1931
- const statePath = resolved.params?.statePath || "/formValidation";
1932
- set(statePath, {
1933
- valid,
1934
- errors
1935
- });
1936
- return;
1937
- }
1938
- const handler = handlers[resolved.action];
1939
- if (!handler) {
1940
- console.warn(`No handler registered for action: ${resolved.action}`);
1941
- return;
1942
- }
1943
- if (resolved.confirm) return new Promise((resolve, reject) => {
1944
- setPendingConfirmation({
1945
- action: resolved,
1946
- handler,
1947
- resolve: () => {
1948
- setPendingConfirmation(null);
1949
- resolve();
1950
- },
1951
- reject: () => {
1952
- setPendingConfirmation(null);
1953
- reject(/* @__PURE__ */ new Error("Action cancelled"));
1954
- }
1955
- });
1956
- }).then(async () => {
1957
- setLoadingActions((prev) => new Set(prev).add(resolved.action));
1958
- try {
1959
- await executeAction({
1960
- action: resolved,
1961
- handler,
1962
- setState: set,
1963
- navigate,
1964
- executeAction: async (name) => {
1965
- await execute({ action: name });
1966
- }
1967
- });
1968
- } finally {
1969
- setLoadingActions((prev) => {
1970
- const next = new Set(prev);
1971
- next.delete(resolved.action);
1972
- return next;
1973
- });
1974
- }
1975
- });
1976
- setLoadingActions((prev) => new Set(prev).add(resolved.action));
1977
- try {
1978
- await executeAction({
1979
- action: resolved,
1980
- handler,
1981
- setState: set,
1982
- navigate,
1983
- executeAction: async (name) => {
1984
- await execute({ action: name });
1985
- }
1986
- });
1987
- } finally {
1988
- setLoadingActions((prev) => {
1989
- const next = new Set(prev);
1990
- next.delete(resolved.action);
1991
- return next;
1992
- });
1993
- }
1994
- } catch (err) {
1995
- __ok = false;
1996
- __error = err;
1997
- throw err;
1998
- } finally {
1999
- const now = Date.now();
2000
- notifyActionSettle({
2001
- id: dispatchId,
2002
- name: resolved.action,
2003
- ok: __ok,
2004
- at: now,
2005
- durationMs: now - dispatchedAt,
2006
- error: __error
2007
- });
2008
- }
2009
- }, [
2010
- handlers,
2011
- get,
2012
- set,
2013
- getSnapshot,
2014
- navigate,
2015
- validation
2016
- ]);
2017
- const confirm = useCallback(() => {
2018
- pendingConfirmation?.resolve();
2019
- }, [pendingConfirmation]);
2020
- const cancel = useCallback(() => {
2021
- pendingConfirmation?.reject();
2022
- }, [pendingConfirmation]);
2023
- const value = useMemo(() => ({
2024
- handlers,
2025
- loadingActions,
2026
- pendingConfirmation,
2027
- execute,
2028
- confirm,
2029
- cancel,
2030
- registerHandler
2031
- }), [
2032
- handlers,
2033
- loadingActions,
2034
- pendingConfirmation,
2035
- execute,
2036
- confirm,
2037
- cancel,
2038
- registerHandler
2039
- ]);
2040
- return /* @__PURE__ */ jsx(ActionContext.Provider, {
2041
- value,
2042
- children
2043
- });
2044
- }
2045
- function useActions() {
2046
- const ctx = useContext(ActionContext);
2047
- if (!ctx) throw new Error("useActions must be used within an ActionProvider");
2048
- return ctx;
2049
- }
2050
- function ConfirmDialog({ confirm, onConfirm, onCancel }) {
2051
- const isDanger = confirm.variant === "danger";
2052
- return /* @__PURE__ */ jsx("div", {
2053
- style: {
2054
- position: "fixed",
2055
- inset: 0,
2056
- backgroundColor: "rgba(0, 0, 0, 0.5)",
2057
- display: "flex",
2058
- alignItems: "center",
2059
- justifyContent: "center",
2060
- zIndex: 50
2061
- },
2062
- onClick: onCancel,
2063
- children: /* @__PURE__ */ jsxs("div", {
2064
- style: {
2065
- backgroundColor: "white",
2066
- borderRadius: "8px",
2067
- padding: "24px",
2068
- maxWidth: "400px",
2069
- width: "100%",
2070
- boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)"
2071
- },
2072
- onClick: (e) => e.stopPropagation(),
2073
- children: [
2074
- /* @__PURE__ */ jsx("h3", {
2075
- style: {
2076
- margin: "0 0 8px 0",
2077
- fontSize: "18px",
2078
- fontWeight: 600
2079
- },
2080
- children: confirm.title
2081
- }),
2082
- /* @__PURE__ */ jsx("p", {
2083
- style: {
2084
- margin: "0 0 24px 0",
2085
- color: "#6b7280"
2086
- },
2087
- children: confirm.message
2088
- }),
2089
- /* @__PURE__ */ jsxs("div", {
2090
- style: {
2091
- display: "flex",
2092
- gap: "12px",
2093
- justifyContent: "flex-end"
2094
- },
2095
- children: [/* @__PURE__ */ jsx("button", {
2096
- onClick: onCancel,
2097
- style: {
2098
- padding: "8px 16px",
2099
- borderRadius: "6px",
2100
- border: "1px solid #d1d5db",
2101
- backgroundColor: "white",
2102
- cursor: "pointer"
2103
- },
2104
- children: confirm.cancelLabel ?? "Cancel"
2105
- }), /* @__PURE__ */ jsx("button", {
2106
- onClick: onConfirm,
2107
- style: {
2108
- padding: "8px 16px",
2109
- borderRadius: "6px",
2110
- border: "none",
2111
- backgroundColor: isDanger ? "#dc2626" : "#3b82f6",
2112
- color: "white",
2113
- cursor: "pointer"
2114
- },
2115
- children: confirm.confirmLabel ?? "Confirm"
2116
- })]
2117
- })
2118
- ]
2119
- })
2120
- });
2121
- }
2122
- var RepeatScopeContext = createContext(null);
2123
- function RepeatScopeProvider({ item, index, basePath, children }) {
2124
- return /* @__PURE__ */ jsx(RepeatScopeContext.Provider, {
2125
- value: {
2126
- item,
2127
- index,
2128
- basePath
2129
- },
2130
- children
2131
- });
2132
- }
2133
- function useRepeatScope() {
2134
- return useContext(RepeatScopeContext);
2135
- }
2136
- var ElementErrorBoundary = class extends React.Component {
2137
- constructor(props) {
2138
- super(props);
2139
- this.state = { hasError: false };
2140
- }
2141
- static getDerivedStateFromError() {
2142
- return { hasError: true };
2143
- }
2144
- componentDidCatch(error, info) {
2145
- console.error(`[json-render] Rendering error in <${this.props.elementType}>:`, error, info.componentStack);
2146
- }
2147
- render() {
2148
- if (this.state.hasError) return null;
2149
- return this.props.children;
2150
- }
2151
- };
2152
- var EMPTY_FUNCTIONS = {};
2153
- var FunctionsContext = React.createContext(EMPTY_FUNCTIONS);
2154
- function useFunctions() {
2155
- return React.useContext(FunctionsContext);
2156
- }
2157
- var DirectivesContext = React.createContext(void 0);
2158
- function useDirectives() {
2159
- return React.useContext(DirectivesContext);
2160
- }
2161
- function useDevtoolsActive() {
2162
- return React.useSyncExternalStore(subscribeDevtoolsActive, isDevtoolsActive, () => false);
2163
- }
2164
- var ElementRenderer = React.memo(function ElementRenderer2({ element, elementKey, spec, registry, loading, fallback }) {
2165
- const devtoolsActive = useDevtoolsActive();
2166
- const repeatScope = useRepeatScope();
2167
- const { ctx } = useVisibility();
2168
- const { execute } = useActions();
2169
- const { getSnapshot, state: watchState } = useStateStore();
2170
- const functions = useFunctions();
2171
- const directives = useDirectives();
2172
- const fullCtx = useMemo(() => {
2173
- const base = repeatScope ? {
2174
- ...ctx,
2175
- repeatItem: repeatScope.item,
2176
- repeatIndex: repeatScope.index,
2177
- repeatBasePath: repeatScope.basePath
2178
- } : { ...ctx };
2179
- base.functions = functions;
2180
- base.directives = directives;
2181
- return base;
2182
- }, [
2183
- ctx,
2184
- repeatScope,
2185
- functions,
2186
- directives
2187
- ]);
2188
- const isVisible = element.visible === void 0 ? true : evaluateVisibility(element.visible, fullCtx);
2189
- const onBindings = element.on;
2190
- const emit = useCallback(async (eventName) => {
2191
- const binding = onBindings?.[eventName];
2192
- if (!binding) return;
2193
- const actionBindings = Array.isArray(binding) ? binding : [binding];
2194
- for (const b of actionBindings) {
2195
- if (!b.params) {
2196
- await execute(b);
2197
- continue;
2198
- }
2199
- const liveCtx = {
2200
- ...fullCtx,
2201
- stateModel: getSnapshot()
2202
- };
2203
- const resolved = {};
2204
- for (const [key, val] of Object.entries(b.params)) resolved[key] = resolveActionParam(val, liveCtx);
2205
- await execute({
2206
- ...b,
2207
- params: resolved
2208
- });
2209
- }
2210
- }, [
2211
- onBindings,
2212
- execute,
2213
- fullCtx,
2214
- getSnapshot
2215
- ]);
2216
- const on = useCallback((eventName) => {
2217
- const binding = onBindings?.[eventName];
2218
- if (!binding) return {
2219
- emit: () => {},
2220
- shouldPreventDefault: false,
2221
- bound: false
2222
- };
2223
- return {
2224
- emit: () => emit(eventName),
2225
- shouldPreventDefault: (Array.isArray(binding) ? binding : [binding]).some((b) => b.preventDefault),
2226
- bound: true
2227
- };
2228
- }, [onBindings, emit]);
2229
- const watchConfig = element.watch;
2230
- const prevWatchValues = useRef(null);
2231
- const stableWatchRef = useRef(void 0);
2232
- const watchedValues = useMemo(() => {
2233
- if (!watchConfig) return void 0;
2234
- const values = {};
2235
- for (const path of Object.keys(watchConfig)) values[path] = getByPath(watchState, path);
2236
- const prev = stableWatchRef.current;
2237
- if (prev) {
2238
- const keys = Object.keys(values);
2239
- if (keys.length === Object.keys(prev).length && keys.every((k) => values[k] === prev[k])) return prev;
2240
- }
2241
- stableWatchRef.current = values;
2242
- return values;
2243
- }, [watchConfig, watchState]);
2244
- useEffect(() => {
2245
- if (!watchConfig || !watchedValues) return;
2246
- const paths = Object.keys(watchConfig);
2247
- if (paths.length === 0) return;
2248
- const prev = prevWatchValues.current;
2249
- prevWatchValues.current = watchedValues;
2250
- if (prev === null) return;
2251
- let cancelled = false;
2252
- (async () => {
2253
- for (const path of paths) {
2254
- if (cancelled) break;
2255
- if (watchedValues[path] !== prev[path]) {
2256
- const binding = watchConfig[path];
2257
- if (!binding) continue;
2258
- const bindings = Array.isArray(binding) ? binding : [binding];
2259
- for (const b of bindings) {
2260
- if (cancelled) break;
2261
- if (!b.params) {
2262
- await execute(b);
2263
- if (cancelled) break;
2264
- continue;
2265
- }
2266
- const liveCtx = {
2267
- ...fullCtx,
2268
- stateModel: getSnapshot()
2269
- };
2270
- const resolved = {};
2271
- for (const [key, val] of Object.entries(b.params)) resolved[key] = resolveActionParam(val, liveCtx);
2272
- await execute({
2273
- ...b,
2274
- params: resolved
2275
- });
2276
- if (cancelled) break;
2277
- }
2278
- }
2279
- }
2280
- })().catch(console.error);
2281
- return () => {
2282
- cancelled = true;
2283
- };
2284
- }, [
2285
- watchConfig,
2286
- watchedValues,
2287
- execute,
2288
- fullCtx,
2289
- getSnapshot
2290
- ]);
2291
- if (!isVisible) return null;
2292
- const rawProps = element.props;
2293
- const elementBindings = resolveBindings(rawProps, fullCtx);
2294
- const resolvedProps = resolveElementProps(rawProps, fullCtx);
2295
- const resolvedElement = resolvedProps !== element.props ? {
2296
- ...element,
2297
- props: resolvedProps
2298
- } : element;
2299
- const Component = registry[resolvedElement.type] ?? fallback;
2300
- if (!Component) {
2301
- console.warn(`No renderer for component type: ${resolvedElement.type}`);
2302
- return null;
2303
- }
2304
- const rendered = /* @__PURE__ */ jsx(Component, {
2305
- element: resolvedElement,
2306
- emit,
2307
- on,
2308
- bindings: elementBindings,
2309
- loading,
2310
- children: resolvedElement.repeat ? /* @__PURE__ */ jsx(RepeatChildren, {
2311
- element: resolvedElement,
2312
- spec,
2313
- registry,
2314
- loading,
2315
- fallback
2316
- }) : resolvedElement.children?.map((childKey) => {
2317
- const childElement = spec.elements[childKey];
2318
- if (!childElement) {
2319
- if (!loading) console.warn(`[json-render] Missing element "${childKey}" referenced as child of "${resolvedElement.type}". This element will not render.`);
2320
- return null;
2321
- }
2322
- return /* @__PURE__ */ jsx(ElementRenderer2, {
2323
- element: childElement,
2324
- elementKey: childKey,
2325
- spec,
2326
- registry,
2327
- loading,
2328
- fallback
2329
- }, childKey);
2330
- })
2331
- });
2332
- const tagged = devtoolsActive && elementKey ? /* @__PURE__ */ jsx("span", {
2333
- "data-jr-key": elementKey,
2334
- style: { display: "contents" },
2335
- children: rendered
2336
- }) : rendered;
2337
- return /* @__PURE__ */ jsx(ElementErrorBoundary, {
2338
- elementType: resolvedElement.type,
2339
- children: tagged
2340
- });
2341
- });
2342
- function RepeatChildren({ element, spec, registry, loading, fallback }) {
2343
- const { state } = useStateStore();
2344
- const repeat = element.repeat;
2345
- const statePath = repeat.statePath;
2346
- return /* @__PURE__ */ jsx(Fragment$1, { children: (getByPath(state, statePath) ?? []).map((itemValue, index) => {
2347
- const key = repeat.key && typeof itemValue === "object" && itemValue !== null ? String(itemValue[repeat.key] ?? index) : String(index);
2348
- return /* @__PURE__ */ jsx(RepeatScopeProvider, {
2349
- item: itemValue,
2350
- index,
2351
- basePath: `${statePath}/${index}`,
2352
- children: element.children?.map((childKey) => {
2353
- const childElement = spec.elements[childKey];
2354
- if (!childElement) {
2355
- if (!loading) console.warn(`[json-render] Missing element "${childKey}" referenced as child of "${element.type}" (repeat). This element will not render.`);
2356
- return null;
2357
- }
2358
- return /* @__PURE__ */ jsx(ElementRenderer, {
2359
- element: childElement,
2360
- elementKey: childKey,
2361
- spec,
2362
- registry,
2363
- loading,
2364
- fallback
2365
- }, childKey);
2366
- })
2367
- }, key);
2368
- }) });
2369
- }
2370
- function Renderer({ spec, registry, loading, fallback }) {
2371
- if (!spec || !spec.root) return null;
2372
- const rootElement = spec.elements[spec.root];
2373
- if (!rootElement) return null;
2374
- return /* @__PURE__ */ jsx(ElementRenderer, {
2375
- element: rootElement,
2376
- elementKey: spec.root,
2377
- spec,
2378
- registry,
2379
- loading,
2380
- fallback
2381
- });
2382
- }
2383
- function JSONUIProvider({ registry, store, initialState, handlers, navigate, validationFunctions, functions, directives, onStateChange, children }) {
2384
- const directiveRegistry = useMemo(() => directives ? createDirectiveRegistry(directives) : void 0, [directives]);
2385
- return /* @__PURE__ */ jsx(StateProvider, {
2386
- store,
2387
- initialState,
2388
- onStateChange,
2389
- children: /* @__PURE__ */ jsx(VisibilityProvider, { children: /* @__PURE__ */ jsx(ValidationProvider, {
2390
- customFunctions: validationFunctions,
2391
- children: /* @__PURE__ */ jsx(ActionProvider, {
2392
- handlers,
2393
- navigate,
2394
- children: /* @__PURE__ */ jsx(FunctionsContext.Provider, {
2395
- value: functions ?? EMPTY_FUNCTIONS,
2396
- children: /* @__PURE__ */ jsxs(DirectivesContext.Provider, {
2397
- value: directiveRegistry,
2398
- children: [children, /* @__PURE__ */ jsx(ConfirmationDialogManager, {})]
2399
- })
2400
- })
2401
- })
2402
- }) })
2403
- });
2404
- }
2405
- function ConfirmationDialogManager() {
2406
- const { pendingConfirmation, confirm, cancel } = useActions();
2407
- if (!pendingConfirmation?.action.confirm) return null;
2408
- return /* @__PURE__ */ jsx(ConfirmDialog, {
2409
- confirm: pendingConfirmation.action.confirm,
2410
- onConfirm: confirm,
2411
- onCancel: cancel
2412
- });
2413
- }
2414
- function defineRegistry(_catalog, options) {
2415
- const registry = {};
2416
- if (options.components) for (const [name, componentFn] of Object.entries(options.components)) registry[name] = ({ element, children, emit, on, bindings, loading }) => {
2417
- return componentFn({
2418
- props: element.props,
2419
- children,
2420
- emit,
2421
- on,
2422
- bindings,
2423
- loading
2424
- });
2425
- };
2426
- const actionMap = options.actions ? Object.entries(options.actions) : [];
2427
- const handlers = (getSetState, getState) => {
2428
- const result = {};
2429
- for (const [name, actionFn] of actionMap) result[name] = async (params) => {
2430
- const setState = getSetState();
2431
- const state = getState();
2432
- if (setState) await actionFn(params, setState, state);
2433
- };
2434
- return result;
2435
- };
2436
- const executeAction2 = async (actionName, params, setState, state = {}) => {
2437
- const entry = actionMap.find(([name]) => name === actionName);
2438
- if (entry) await entry[1](params, setState, state);
2439
- else console.warn(`Unknown action: ${actionName}`);
2440
- };
2441
- return {
2442
- registry,
2443
- handlers,
2444
- executeAction: executeAction2
2445
- };
2446
- }
2447
-
2448
- //#endregion
2449
- //#region ../ui-react/dist/jsonRenderRegistry-9GrWP_hE.js
2450
- /** catalog 的 json-render 投影:props 复用同一份 zod schema,不维护第二份定义 */
2451
- const jsonRenderCatalog = defineCatalog(schema, {
2452
- components: Object.fromEntries(uiCatalog.components.map((def) => [def.name, {
2453
- props: def.props,
2454
- description: def.description,
2455
- ...def.children !== void 0 ? { slots: ["default"] } : {},
2456
- ...def.example?.props ? { example: def.example.props } : {}
2457
- }])),
2458
- actions: Object.fromEntries(uiCatalog.actions.map((action) => [action.name, { description: action.description }]))
2459
- });
2460
- /** registry 是白名单:catalog 之外的组件名在 Renderer 里直接落到 fallback */
2461
- const { registry: jsonRenderRegistry } = defineRegistry(jsonRenderCatalog, {
2462
- components: catalogComponentImpls,
2463
- actions: Object.fromEntries(uiCatalog.actions.map((action) => [action.name, () => void 0]))
2464
- });
2465
- const JSON_RENDER_COMPONENTS = Object.keys(catalogComponentImpls);
2466
-
2467
- //#endregion
2468
- export { JSONUIProvider, JSON_RENDER_COMPONENTS, Renderer, jsonRenderCatalog, jsonRenderRegistry };