@rokkit/forms 1.3.1 → 1.3.3

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.
@@ -31,6 +31,12 @@ export class FormBuilder {
31
31
  /**
32
32
  * Set the schema
33
33
  * @param {Object} value - New schema object
34
+ *
35
+ * When the caller explicitly provides a schema, that schema is the source
36
+ * of truth for which fields exist — the layout may have been derived
37
+ * earlier from empty/partial data and would silently hide those fields.
38
+ * Re-derive the layout from the new schema so the two stay consistent,
39
+ * unless the caller has already installed an explicit non-empty layout.
34
40
  */
35
41
  set schema(value: Object);
36
42
  /**
@@ -5,3 +5,15 @@
5
5
  * @returns {import('../types').DataLayout}
6
6
  */
7
7
  export function deriveLayoutFromValue(value: any, scope?: string): import("../types").DataLayout;
8
+ /**
9
+ * Derive a layout directly from a JSON-Schema definition. Used when a schema
10
+ * is supplied but no explicit layout is — one element per top-level property
11
+ * in declaration order. This is the correct fallback when a schema is
12
+ * present: the schema is the source of truth for which fields exist, not the
13
+ * (possibly-empty) initial data.
14
+ *
15
+ * @param {Object} schema JSON-Schema fragment (object type with `properties`)
16
+ * @param {string} scope Base scope pointer (defaults to root)
17
+ * @returns {import('../types').DataLayout}
18
+ */
19
+ export function deriveLayoutFromSchema(schema: Object, scope?: string): import("../types").DataLayout;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/forms",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Form building components for Rokkit applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  import { SvelteMap, SvelteSet } from 'svelte/reactivity'
2
2
  import { deriveSchemaFromValue } from './schema.js'
3
- import { deriveLayoutFromValue } from './layout.js'
3
+ import { deriveLayoutFromValue, deriveLayoutFromSchema } from './layout.js'
4
4
  import { getSchemaWithLayout } from './fields.js'
5
5
  import { createLookupManager } from './lookup.svelte.js'
6
6
  import { evaluateCondition } from './conditions.js'
@@ -169,9 +169,20 @@ export class FormBuilder {
169
169
  /**
170
170
  * Set the schema
171
171
  * @param {Object} value - New schema object
172
+ *
173
+ * When the caller explicitly provides a schema, that schema is the source
174
+ * of truth for which fields exist — the layout may have been derived
175
+ * earlier from empty/partial data and would silently hide those fields.
176
+ * Re-derive the layout from the new schema so the two stay consistent,
177
+ * unless the caller has already installed an explicit non-empty layout.
172
178
  */
173
179
  set schema(value) {
174
180
  this.#schema = value ?? deriveSchemaFromValue(this.#data)
181
+ const layout = this.#layout
182
+ const hasExplicitLayout = layout && Array.isArray(layout.elements) && layout.elements.length > 0
183
+ if (!hasExplicitLayout) {
184
+ this.#layout = deriveLayoutFromSchema(this.#schema)
185
+ }
175
186
  }
176
187
 
177
188
  /**
@@ -187,7 +198,21 @@ export class FormBuilder {
187
198
  * @param {Object} value - New layout object
188
199
  */
189
200
  set layout(value) {
190
- this.#layout = value ?? deriveLayoutFromValue(this.#data)
201
+ if (value) {
202
+ this.#layout = value
203
+ return
204
+ }
205
+ // No explicit layout — prefer schema-derived over data-derived so
206
+ // declared fields render even when initial data is `{}` (a common
207
+ // pattern for LLM-generated forms and reset forms).
208
+ const schema = this.#schema
209
+ const hasSchemaProperties =
210
+ schema && typeof schema === 'object' && schema.properties &&
211
+ typeof schema.properties === 'object' &&
212
+ Object.keys(schema.properties).length > 0
213
+ this.#layout = hasSchemaProperties
214
+ ? deriveLayoutFromSchema(schema)
215
+ : deriveLayoutFromValue(this.#data)
191
216
  }
192
217
 
193
218
  /**
package/src/lib/layout.js CHANGED
@@ -60,3 +60,47 @@ export function deriveLayoutFromValue(value, scope = '#') {
60
60
  }
61
61
  return { type: 'vertical', elements: [{ scope }] }
62
62
  }
63
+
64
+ /**
65
+ * Build an inner element from a JSON-Schema property. Objects recurse into a
66
+ * nested {title, scope, type:'vertical', elements:[...]} group; primitives
67
+ * yield a scoped leaf. Mirrors `deriveElementLayout` but reads the shape from
68
+ * schema's `properties` rather than from the data value.
69
+ *
70
+ * @param {Object} propSchema
71
+ * @param {string} scope
72
+ * @param {string} label
73
+ * @returns {import('../types').LayoutElement}
74
+ */
75
+ function deriveElementFromSchema(propSchema, scope, label) {
76
+ const path = `${scope}/${label}`
77
+ if (propSchema && propSchema.type === 'object' && isObject(propSchema.properties)) {
78
+ return {
79
+ title: label,
80
+ scope: path,
81
+ ...deriveLayoutFromSchema(propSchema, path)
82
+ }
83
+ }
84
+ return { label, scope: path }
85
+ }
86
+
87
+ /**
88
+ * Derive a layout directly from a JSON-Schema definition. Used when a schema
89
+ * is supplied but no explicit layout is — one element per top-level property
90
+ * in declaration order. This is the correct fallback when a schema is
91
+ * present: the schema is the source of truth for which fields exist, not the
92
+ * (possibly-empty) initial data.
93
+ *
94
+ * @param {Object} schema JSON-Schema fragment (object type with `properties`)
95
+ * @param {string} scope Base scope pointer (defaults to root)
96
+ * @returns {import('../types').DataLayout}
97
+ */
98
+ export function deriveLayoutFromSchema(schema, scope = '#') {
99
+ if (!isObject(schema) || !isObject(schema.properties)) {
100
+ return { type: 'vertical', elements: [] }
101
+ }
102
+ const elements = Object.entries(schema.properties).map(([label, propSchema]) =>
103
+ deriveElementFromSchema(propSchema, scope, label)
104
+ )
105
+ return { type: 'vertical', elements }
106
+ }