@json-render/solid 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
@@ -227,6 +227,8 @@ await stream.send("Build me a dashboard");
227
227
 
228
228
  ## Differences from `@json-render/react`
229
229
 
230
+ Nested lists can set `repeat.statePath` to `{ "$item": "field" }` to iterate an array on the enclosing repeat item.
231
+
230
232
  Most APIs are intentionally aligned, but there are runtime behavior differences due to Solid:
231
233
 
232
234
  - Solid components run once, then update via signals.
@@ -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.",
@@ -84,4 +87,4 @@ export {
84
87
  schema,
85
88
  elementTreeSchema
86
89
  };
87
- //# sourceMappingURL=chunk-XTSB6FIM.mjs.map
90
+ //# sourceMappingURL=chunk-ZTWTJIEL.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/solid\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 Solid schema\n */\nexport type SolidSchema = typeof schema;\n\n/**\n * Infer the spec type from a catalog\n */\nexport type SolidSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n\n// Backward compatibility aliases\n/** @deprecated Use `schema` instead */\nexport const elementTreeSchema = schema;\n/** @deprecated Use `SolidSchema` instead */\nexport type ElementTreeSchema = SolidSchema;\n/** @deprecated Use `SolidSpec` instead */\nexport type ElementTreeSpec<T> = SolidSpec<T>;\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;AAkBO,IAAM,oBAAoB;","names":[]}
package/dist/index.js CHANGED
@@ -598,11 +598,8 @@ function ActionProvider(props) {
598
598
  handler,
599
599
  setState: set,
600
600
  navigate: props.navigate,
601
- executeAction: async (name) => {
602
- const subBinding = {
603
- action: name
604
- };
605
- await execute(subBinding);
601
+ executeAction: async (binding2) => {
602
+ await execute(binding2);
606
603
  }
607
604
  });
608
605
  } finally {
@@ -621,11 +618,8 @@ function ActionProvider(props) {
621
618
  handler,
622
619
  setState: set,
623
620
  navigate: props.navigate,
624
- executeAction: async (name) => {
625
- const subBinding = {
626
- action: name
627
- };
628
- await execute(subBinding);
621
+ executeAction: async (binding2) => {
622
+ await execute(binding2);
629
623
  }
630
624
  });
631
625
  } finally {
@@ -753,7 +747,9 @@ var schema = (0, import_core5.defineSchema)(
753
747
  /** Child element keys (flat reference) */
754
748
  children: s.array(s.string()),
755
749
  /** Visibility condition */
756
- visible: s.any()
750
+ visible: { ...s.any(), ...s.optional() },
751
+ /** Repeat children from a state array */
752
+ repeat: { ...s.any(), ...s.optional() }
757
753
  })
758
754
  )
759
755
  }),
@@ -802,12 +798,13 @@ var schema = (0, import_core5.defineSchema)(
802
798
  // Element integrity
803
799
  "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.",
804
800
  "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.",
801
+ 'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
805
802
  // Field placement
806
803
  'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
807
804
  '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.',
808
805
  // State and data
809
806
  "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).",
810
- '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.',
807
+ '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.',
811
808
  // Design quality
812
809
  "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.",
813
810
  "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.",
@@ -1117,8 +1114,15 @@ function ElementRendererContent(props) {
1117
1114
  function RepeatChildren(props) {
1118
1115
  const stateStore = useStateStore();
1119
1116
  const repeat = () => props.element.repeat;
1120
- const statePath = () => repeat().statePath;
1121
- const items = () => (0, import_core6.getByPath)(stateStore.state, statePath()) ?? [];
1117
+ const parentScope = useRepeatScope();
1118
+ const statePath = () => {
1119
+ const resolved = (0, import_core6.resolveRepeatStatePath)(repeat().statePath, parentScope?.basePath);
1120
+ if (resolved === void 0) {
1121
+ console.warn("[json-render/solid] $item in repeat.statePath used outside of a repeat scope");
1122
+ }
1123
+ return resolved;
1124
+ };
1125
+ const items = () => statePath() === void 0 ? [] : (0, import_core6.getByPath)(stateStore.state, statePath()) ?? [];
1122
1126
  return (0, import_web18.createComponent)(import_solid_js6.For, {
1123
1127
  get each() {
1124
1128
  return items();
@@ -1134,7 +1138,7 @@ function RepeatChildren(props) {
1134
1138
  return index();
1135
1139
  },
1136
1140
  get basePath() {
1137
- return `${statePath()}/${index()}`;
1141
+ return (0, import_core6.resolveRepeatItemStatePath)(statePath(), index());
1138
1142
  },
1139
1143
  get children() {
1140
1144
  return (0, import_web18.createComponent)(import_solid_js6.For, {