@json-render/core 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -16
- package/dist/index.d.mts +424 -212
- package/dist/index.d.ts +424 -212
- package/dist/index.js +768 -527
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +759 -522
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ export const schema = defineSchema((s) => ({
|
|
|
27
27
|
root: s.object({
|
|
28
28
|
type: s.ref("catalog.components"),
|
|
29
29
|
props: s.propsOf("catalog.components"),
|
|
30
|
-
children: s.array(s.
|
|
30
|
+
children: s.array(s.string()), // Element keys (flat spec format)
|
|
31
31
|
}),
|
|
32
32
|
}),
|
|
33
33
|
catalog: s.object({
|
|
@@ -119,9 +119,9 @@ const finalSpec = compiler.getResult();
|
|
|
119
119
|
SpecStream format uses [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations (each line is a patch):
|
|
120
120
|
|
|
121
121
|
```jsonl
|
|
122
|
-
{"op":"add","path":"/root
|
|
123
|
-
{"op":"add","path":"/
|
|
124
|
-
{"op":"add","path":"/
|
|
122
|
+
{"op":"add","path":"/root","value":"card-1"}
|
|
123
|
+
{"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Hello"},"children":["btn-1"]}}
|
|
124
|
+
{"op":"add","path":"/elements/btn-1","value":{"type":"Button","props":{"label":"Click"},"children":[]}}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
All six RFC 6902 operations are supported: `add`, `remove`, `replace`, `move`, `copy`, `test`.
|
|
@@ -192,7 +192,7 @@ const spec = compileSpecStream<MySpec>(jsonlString);
|
|
|
192
192
|
|
|
193
193
|
| Export | Purpose |
|
|
194
194
|
|--------|---------|
|
|
195
|
-
| `validateSpec(spec,
|
|
195
|
+
| `validateSpec(spec, options?)` | Validate spec structure and return issues |
|
|
196
196
|
| `autoFixSpec(spec)` | Auto-fix common spec issues (returns corrected copy) |
|
|
197
197
|
| `formatSpecIssues(issues)` | Format validation issues as readable strings |
|
|
198
198
|
|
|
@@ -211,17 +211,33 @@ const spec = compileSpecStream<MySpec>(jsonlString);
|
|
|
211
211
|
|
|
212
212
|
Any prop value can be a dynamic expression that resolves based on data state at render time. Expressions are resolved by the renderer before props reach components.
|
|
213
213
|
|
|
214
|
-
### Data Binding (`$
|
|
214
|
+
### Data Binding (`$state`)
|
|
215
215
|
|
|
216
216
|
Read a value directly from the state model:
|
|
217
217
|
|
|
218
218
|
```json
|
|
219
219
|
{
|
|
220
|
-
"color": { "$
|
|
221
|
-
"label": { "$
|
|
220
|
+
"color": { "$state": "/theme/primary" },
|
|
221
|
+
"label": { "$state": "/user/name" }
|
|
222
222
|
}
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
+
### Two-Way Binding (`$bindState` / `$bindItem`)
|
|
226
|
+
|
|
227
|
+
Use `{ "$bindState": "/path" }` on the natural value prop for form components that need read/write access. The component reads from and writes to the state path:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"type": "Input",
|
|
232
|
+
"props": {
|
|
233
|
+
"value": { "$bindState": "/form/email" },
|
|
234
|
+
"placeholder": "Email"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Inside a repeat scope, use `{ "$bindItem": "completed" }` to bind to a field on the current item:
|
|
240
|
+
|
|
225
241
|
### Conditional (`$cond` / `$then` / `$else`)
|
|
226
242
|
|
|
227
243
|
Evaluate a condition (same syntax as visibility conditions) and pick a value:
|
|
@@ -229,12 +245,12 @@ Evaluate a condition (same syntax as visibility conditions) and pick a value:
|
|
|
229
245
|
```json
|
|
230
246
|
{
|
|
231
247
|
"color": {
|
|
232
|
-
"$cond": { "
|
|
248
|
+
"$cond": { "$state": "/activeTab", "eq": "home" },
|
|
233
249
|
"$then": "#007AFF",
|
|
234
250
|
"$else": "#8E8E93"
|
|
235
251
|
},
|
|
236
252
|
"name": {
|
|
237
|
-
"$cond": { "
|
|
253
|
+
"$cond": { "$state": "/activeTab", "eq": "home" },
|
|
238
254
|
"$then": "home",
|
|
239
255
|
"$else": "home-outline"
|
|
240
256
|
}
|
|
@@ -246,13 +262,33 @@ Evaluate a condition (same syntax as visibility conditions) and pick a value:
|
|
|
246
262
|
```json
|
|
247
263
|
{
|
|
248
264
|
"label": {
|
|
249
|
-
"$cond": { "
|
|
250
|
-
"$then": { "$
|
|
265
|
+
"$cond": { "$state": "/user/isAdmin" },
|
|
266
|
+
"$then": { "$state": "/admin/greeting" },
|
|
251
267
|
"$else": "Welcome"
|
|
252
268
|
}
|
|
253
269
|
}
|
|
254
270
|
```
|
|
255
271
|
|
|
272
|
+
### Repeat Item (`$item`)
|
|
273
|
+
|
|
274
|
+
Inside children of a repeated element, read a field from the current array item:
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{ "$item": "title" }
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Use `""` to get the entire item object. `$item` takes a path string because items are typically objects with nested fields to navigate.
|
|
281
|
+
|
|
282
|
+
### Repeat Index (`$index`)
|
|
283
|
+
|
|
284
|
+
Get the current array index inside a repeat:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{ "$index": true }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`$index` uses `true` as a sentinel flag because the index is a scalar value with no sub-path to navigate (unlike `$item` which needs a path).
|
|
291
|
+
|
|
256
292
|
### API
|
|
257
293
|
|
|
258
294
|
```typescript
|
|
@@ -260,7 +296,7 @@ import { resolvePropValue, resolveElementProps } from "@json-render/core";
|
|
|
260
296
|
|
|
261
297
|
// Resolve a single value
|
|
262
298
|
const color = resolvePropValue(
|
|
263
|
-
{ $cond: {
|
|
299
|
+
{ $cond: { $state: "/active", eq: "yes" }, $then: "blue", $else: "gray" },
|
|
264
300
|
{ stateModel: myState }
|
|
265
301
|
);
|
|
266
302
|
|
|
@@ -268,6 +304,45 @@ const color = resolvePropValue(
|
|
|
268
304
|
const resolved = resolveElementProps(element.props, { stateModel: myState });
|
|
269
305
|
```
|
|
270
306
|
|
|
307
|
+
## Visibility Conditions
|
|
308
|
+
|
|
309
|
+
Visibility conditions control when elements are shown. `VisibilityContext` is `{ stateModel: StateModel, repeatItem?: unknown, repeatIndex?: number }`.
|
|
310
|
+
|
|
311
|
+
### Syntax
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
{ "$state": "/path" } // truthiness
|
|
315
|
+
{ "$state": "/path", "not": true } // falsy
|
|
316
|
+
{ "$state": "/path", "eq": value } // equality
|
|
317
|
+
{ "$state": "/path", "neq": value } // inequality
|
|
318
|
+
{ "$state": "/path", "gt": number } // greater than
|
|
319
|
+
{ "$item": "field" } // repeat item field
|
|
320
|
+
{ "$index": true, "gt": 0 } // repeat index
|
|
321
|
+
[ condition, condition ] // implicit AND
|
|
322
|
+
{ "$and": [ condition, condition ] } // explicit AND
|
|
323
|
+
{ "$or": [ condition, condition ] } // OR
|
|
324
|
+
true / false // always / never
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### TypeScript Helpers
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { visibility } from "@json-render/core";
|
|
331
|
+
|
|
332
|
+
visibility.always // true
|
|
333
|
+
visibility.never // false
|
|
334
|
+
visibility.when("/path") // { $state: "/path" }
|
|
335
|
+
visibility.unless("/path") // { $state: "/path", not: true }
|
|
336
|
+
visibility.eq("/path", val) // { $state: "/path", eq: val }
|
|
337
|
+
visibility.neq("/path", val) // { $state: "/path", neq: val }
|
|
338
|
+
visibility.gt("/path", n) // { $state: "/path", gt: n }
|
|
339
|
+
visibility.gte("/path", n) // { $state: "/path", gte: n }
|
|
340
|
+
visibility.lt("/path", n) // { $state: "/path", lt: n }
|
|
341
|
+
visibility.lte("/path", n) // { $state: "/path", lte: n }
|
|
342
|
+
visibility.and(cond1, cond2) // { $and: [cond1, cond2] }
|
|
343
|
+
visibility.or(cond1, cond2) // { $or: [cond1, cond2] }
|
|
344
|
+
```
|
|
345
|
+
|
|
271
346
|
## User Prompt Builder
|
|
272
347
|
|
|
273
348
|
Build structured user prompts for AI generation, with support for refinement and state context:
|
|
@@ -299,7 +374,7 @@ Validate spec structure and auto-fix common issues:
|
|
|
299
374
|
import { validateSpec, autoFixSpec, formatSpecIssues } from "@json-render/core";
|
|
300
375
|
|
|
301
376
|
// Validate a spec
|
|
302
|
-
const { valid, issues } = validateSpec(spec
|
|
377
|
+
const { valid, issues } = validateSpec(spec);
|
|
303
378
|
|
|
304
379
|
// Format issues for display
|
|
305
380
|
console.log(formatSpecIssues(issues));
|
|
@@ -313,8 +388,8 @@ const fixed = autoFixSpec(spec);
|
|
|
313
388
|
json-render supports completely different spec formats for different renderers:
|
|
314
389
|
|
|
315
390
|
```typescript
|
|
316
|
-
// React:
|
|
317
|
-
{ root: { type: "Card", props: {...}, children: [...] } }
|
|
391
|
+
// React: Flat element map
|
|
392
|
+
{ root: "card-1", elements: { "card-1": { type: "Card", props: {...}, children: [...] } } }
|
|
318
393
|
|
|
319
394
|
// Remotion: Timeline
|
|
320
395
|
{ composition: {...}, tracks: [...], clips: [...] }
|