@json-render/vue 0.19.0 → 0.20.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 CHANGED
@@ -488,6 +488,8 @@ const spec = {
488
488
 
489
489
  ## Differences from `@json-render/react`
490
490
 
491
+ Nested lists can set `repeat.statePath` to `{ "$item": "field" }` to iterate an array on the enclosing repeat item.
492
+
491
493
  | API | React | Vue | Note |
492
494
  |-----|-------|-----|------|
493
495
  | `useStateStore().state` | `StateModel` | `ShallowRef<StateModel>` | Vue reactivity; use `state.value` |
@@ -16,7 +16,9 @@ var schema = defineSchema(
16
16
  /** Child element keys (flat reference) */
17
17
  children: s.array(s.string()),
18
18
  /** Visibility condition */
19
- visible: s.any()
19
+ visible: { ...s.any(), ...s.optional() },
20
+ /** Repeat children from a state array */
21
+ repeat: { ...s.any(), ...s.optional() }
20
22
  })
21
23
  )
22
24
  }),
@@ -65,12 +67,13 @@ var schema = defineSchema(
65
67
  // Element integrity
66
68
  "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.",
67
69
  "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.",
70
+ 'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
68
71
  // Field placement
69
72
  'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
70
73
  '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.',
71
74
  // State and data
72
75
  "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).",
73
- '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.',
76
+ '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"] }. For a nested list stored on the enclosing item, use "repeat": { "statePath": { "$item": "comments" }, "key": "id" }. The $item statePath form is valid only inside another repeat. 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.',
74
77
  // Design quality
75
78
  "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.",
76
79
  "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.",
@@ -82,4 +85,4 @@ var schema = defineSchema(
82
85
  export {
83
86
  schema
84
87
  };
85
- //# sourceMappingURL=chunk-WIPZLAF7.mjs.map
88
+ //# sourceMappingURL=chunk-F4CKBTOP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts"],"sourcesContent":["import { defineSchema } from \"@json-render/core\";\n\n/**\n * The schema for @json-render/vue\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas, and optional actions\n */\nexport const schema = defineSchema(\n (s) => ({\n // What the AI-generated SPEC looks like\n spec: s.object({\n /** Root element key */\n root: s.string(),\n /** Flat map of elements by key */\n elements: s.record(\n s.object({\n /** Component type from catalog */\n type: s.ref(\"catalog.components\"),\n /** Component props */\n props: s.propsOf(\"catalog.components\"),\n /** Child element keys (flat reference) */\n children: s.array(s.string()),\n /** Visibility condition */\n visible: { ...s.any(), ...s.optional() },\n /** Repeat children from a state array */\n repeat: { ...s.any(), ...s.optional() },\n }),\n ),\n }),\n\n // What the CATALOG must provide\n catalog: s.object({\n /** Component definitions */\n components: s.map({\n /** Zod schema for component props */\n props: s.zod(),\n /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */\n slots: s.array(s.string()),\n /** Description for AI generation hints */\n description: s.string(),\n /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */\n example: s.any(),\n }),\n /** Action definitions (optional) */\n actions: s.map({\n /** Zod schema for action params */\n params: s.zod(),\n /** Description for AI generation hints */\n description: s.string(),\n }),\n }),\n }),\n {\n builtInActions: [\n {\n name: \"setState\",\n description:\n \"Update a value in the state model at the given statePath. Params: { statePath: string, value: any }\",\n },\n {\n name: \"pushState\",\n description:\n '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.',\n },\n {\n name: \"removeState\",\n description:\n \"Remove an item from an array in state by index. Params: { statePath: string, index: number }\",\n },\n {\n name: \"validateForm\",\n description:\n \"Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.\",\n },\n ],\n defaultRules: [\n // Element integrity\n \"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.\",\n \"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.\",\n 'REQUIRED FIELDS: Every element MUST include a \"children\" array. Leaf elements (text, badges, inputs, images) use an empty array: \"children\": []. Omitting \"children\" fails validation.',\n\n // Field placement\n 'CRITICAL: The \"visible\" field goes on the ELEMENT object, NOT inside \"props\". Correct: {\"type\":\"<ComponentName>\",\"props\":{},\"visible\":{\"$state\":\"/tab\",\"eq\":\"home\"},\"children\":[...]}.',\n '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.',\n\n // State and data\n \"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).\",\n '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\"] }. For a nested list stored on the enclosing item, use \"repeat\": { \"statePath\": { \"$item\": \"comments\" }, \"key\": \"id\" }. The $item statePath form is valid only inside another repeat. 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.',\n\n // Design quality\n \"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.\",\n \"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.\",\n \"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.\",\n ],\n },\n);\n\n/**\n * Type for the Vue schema\n */\nexport type VueSchema = typeof schema;\n\n/**\n * Infer the spec type from a catalog\n */\nexport type VueSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n"],"mappings":";AAAA,SAAS,oBAAoB;AAStB,IAAM,SAAS;AAAA,EACpB,CAAC,OAAO;AAAA;AAAA,IAEN,MAAM,EAAE,OAAO;AAAA;AAAA,MAEb,MAAM,EAAE,OAAO;AAAA;AAAA,MAEf,UAAU,EAAE;AAAA,QACV,EAAE,OAAO;AAAA;AAAA,UAEP,MAAM,EAAE,IAAI,oBAAoB;AAAA;AAAA,UAEhC,OAAO,EAAE,QAAQ,oBAAoB;AAAA;AAAA,UAErC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA;AAAA,UAE5B,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,SAAS,EAAE;AAAA;AAAA,UAEvC,QAAQ,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,SAAS,EAAE;AAAA,QACxC,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA;AAAA,IAGD,SAAS,EAAE,OAAO;AAAA;AAAA,MAEhB,YAAY,EAAE,IAAI;AAAA;AAAA,QAEhB,OAAO,EAAE,IAAI;AAAA;AAAA,QAEb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA;AAAA,QAEzB,aAAa,EAAE,OAAO;AAAA;AAAA,QAEtB,SAAS,EAAE,IAAI;AAAA,MACjB,CAAC;AAAA;AAAA,MAED,SAAS,EAAE,IAAI;AAAA;AAAA,QAEb,QAAQ,EAAE,IAAI;AAAA;AAAA,QAEd,aAAa,EAAE,OAAO;AAAA,MACxB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EACA;AAAA,IACE,gBAAgB;AAAA,MACd;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,cAAc;AAAA;AAAA,MAEZ;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAGA;AAAA,MACA;AAAA;AAAA,MAGA;AAAA,MACA;AAAA;AAAA,MAGA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/dist/index.js CHANGED
@@ -542,9 +542,8 @@ var ActionProvider = (0, import_vue4.defineComponent)({
542
542
  handler,
543
543
  setState: set,
544
544
  navigate: props.navigate,
545
- executeAction: async (name) => {
546
- const subBinding = { action: name };
547
- await execute(subBinding);
545
+ executeAction: async (binding2) => {
546
+ await execute(binding2);
548
547
  }
549
548
  });
550
549
  } finally {
@@ -563,9 +562,8 @@ var ActionProvider = (0, import_vue4.defineComponent)({
563
562
  handler,
564
563
  setState: set,
565
564
  navigate: props.navigate,
566
- executeAction: async (name) => {
567
- const subBinding = { action: name };
568
- await execute(subBinding);
565
+ executeAction: async (binding2) => {
566
+ await execute(binding2);
569
567
  }
570
568
  });
571
569
  } finally {
@@ -786,7 +784,9 @@ var schema = (0, import_core5.defineSchema)(
786
784
  /** Child element keys (flat reference) */
787
785
  children: s.array(s.string()),
788
786
  /** Visibility condition */
789
- visible: s.any()
787
+ visible: { ...s.any(), ...s.optional() },
788
+ /** Repeat children from a state array */
789
+ repeat: { ...s.any(), ...s.optional() }
790
790
  })
791
791
  )
792
792
  }),
@@ -835,12 +835,13 @@ var schema = (0, import_core5.defineSchema)(
835
835
  // Element integrity
836
836
  "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.",
837
837
  "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.",
838
+ 'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
838
839
  // Field placement
839
840
  'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
840
841
  '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.',
841
842
  // State and data
842
843
  "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).",
843
- '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.',
844
+ '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"] }. For a nested list stored on the enclosing item, use "repeat": { "statePath": { "$item": "comments" }, "key": "id" }. The $item statePath form is valid only inside another repeat. 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.',
844
845
  // Design quality
845
846
  "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.",
846
847
  "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.",
@@ -1632,10 +1633,20 @@ var RepeatChildren = (0, import_vue7.defineComponent)({
1632
1633
  },
1633
1634
  setup(props) {
1634
1635
  const { state } = useStateStore();
1636
+ const parentScope = useRepeatScope();
1635
1637
  return () => {
1636
1638
  const repeat = props.element.repeat;
1637
1639
  if (!repeat?.statePath) return null;
1638
- const statePath = repeat.statePath;
1640
+ const statePath = (0, import_core7.resolveRepeatStatePath)(
1641
+ repeat.statePath,
1642
+ parentScope?.basePath
1643
+ );
1644
+ if (statePath === void 0) {
1645
+ console.warn(
1646
+ "[json-render/vue] $item in repeat.statePath used outside of a repeat scope"
1647
+ );
1648
+ return null;
1649
+ }
1639
1650
  const raw = (0, import_core7.getByPath)(state.value, statePath);
1640
1651
  const items = Array.isArray(raw) ? raw : [];
1641
1652
  return items.map((itemValue, index) => {
@@ -1644,7 +1655,12 @@ var RepeatChildren = (0, import_vue7.defineComponent)({
1644
1655
  ) : String(index);
1645
1656
  return (0, import_vue7.h)(
1646
1657
  RepeatScopeProvider,
1647
- { key, item: itemValue, index, basePath: `${statePath}/${index}` },
1658
+ {
1659
+ key,
1660
+ item: itemValue,
1661
+ index,
1662
+ basePath: (0, import_core7.resolveRepeatItemStatePath)(statePath, index)
1663
+ },
1648
1664
  {
1649
1665
  default: () => props.element.children?.map((childKey) => {
1650
1666
  const childElement = props.spec.elements[childKey];