@rokkit/forms 1.6.0 → 1.8.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
@@ -73,10 +73,12 @@ import { FormBuilder } from '@rokkit/forms'
73
73
  const form = new FormBuilder(data, schema, layout)
74
74
 
75
75
  // form.elements → [{ scope, type, value, props }, ...]
76
+ // A field path is a JSON Forms scope ('#/address/city') or the bare path ('address/city')
76
77
  form.updateField('#/name', 'Alice')
77
78
  form.validateField('#/name')
78
- form.validateAll()
79
- form.isDirty('#/name') // true if value differs from initial
79
+ form.validate() // every visible field, including those inside groups
80
+ form.isFieldDirty('#/name') // true if the value differs from the initial snapshot
81
+ form.isDirty // any field changed
80
82
  ```
81
83
 
82
84
  ### Validation
@@ -1,280 +1,132 @@
1
1
  /**
2
- * FormBuilder class for dynamically generating forms from data structures
2
+ * The state behind a schema-driven form — a COMPOSITION of job classes (`./state/`), each
3
+ * owning one concern:
4
+ *
5
+ * values → definition → steps → validation → lookups, and `elements` derived from them all
6
+ * by the pure `buildElements` (`./elements.js`).
7
+ *
8
+ * It holds almost no logic itself: `next()` (validate the step, then advance), `updateField()`
9
+ * (write, drop hidden messages, re-fetch dependent lookups) and `getVisibleData()` are the only
10
+ * methods that span two jobs. Everything else is the public API FormRenderer, MultiStep and
11
+ * consumers use, kept as delegations so none of them changed when the class was taken apart.
3
12
  */
4
13
  export class FormBuilder {
5
14
  /**
6
- * Create a new FormBuilder instance
7
15
  * @param {Record<string, unknown>} [data={}] - Initial data object
8
- * @param {Object|null} [schema=null] - Optional schema override
9
- * @param {Object|null} [layout=null] - Optional layout override
16
+ * @param {Object|null} [schema=null] - Schema; derived from the data when omitted
17
+ * @param {Object|null} [layout=null] - Layout; derived from the schema, else the data, when omitted
10
18
  * @param {Object<string, import('./lookup.svelte.js').LookupConfig>} [lookups={}] - Lookup configurations
11
19
  */
12
20
  constructor(data?: Record<string, unknown>, schema?: Object | null, layout?: Object | null, lookups?: {
13
21
  [x: string]: import("./lookup.svelte.js").LookupConfig;
14
22
  });
15
- /** @type {FormElement[]} */
16
- elements: FormElement[];
17
- /** True when currentStep can advance to the next step */
18
- canAdvance: boolean;
19
- /** Combined schema+layout (scoped elements only) */
23
+ /** The form's data and its dirty baseline. */
24
+ values: FormValues;
25
+ /** Schema and layout, and how each derives from the other. */
26
+ definition: FormDefinition;
27
+ /** Multi-step navigation. */
28
+ steps: FormSteps;
29
+ /** Validation messages, and the ways of producing them. Suffixed: `validation` is the public map. */
30
+ validationState: FormValidation;
31
+ /** Fields whose options come from elsewhere, and what they depend on. */
32
+ lookups: FormLookups;
33
+ /** @type {import('./elements.js').FormElement[]} */
34
+ elements: import("./elements.js").FormElement[];
35
+ set data(value: {});
36
+ get data(): {};
37
+ /**
38
+ * Every method taking a field path accepts a bare slash path (`'settings/distance'`) or a
39
+ * JSON Forms scope (`'#/settings/distance'`) — see `toFieldPath`.
40
+ */
41
+ getValue(path: any): any;
42
+ /**
43
+ * Write a field, then drop messages of fields a `showWhen` just hid, then clear and re-fetch
44
+ * the lookups that depend on it.
45
+ * @param {string} path
46
+ * @param {any} value
47
+ * @param {boolean} [triggerLookups=true]
48
+ */
49
+ updateField(scopeOrPath: any, value: any, triggerLookups?: boolean): void;
50
+ /** The data without hidden fields' values. Does not mutate the data. */
51
+ getVisibleData(): {
52
+ [k: string]: any;
53
+ };
54
+ get isDirty(): boolean;
55
+ /** Top-level fields that differ from the initial snapshot. */
56
+ get dirtyFields(): SvelteSet<string>;
57
+ isFieldDirty(fieldPath: any): boolean;
58
+ /** Make the current data the baseline — after a successful save. */
59
+ snapshot(): void;
60
+ /** Restore the baseline and clear every message. */
61
+ reset(): void;
62
+ set schema(value: {});
63
+ /** Setting it may re-derive the layout — see FormDefinition. */
64
+ get schema(): {};
65
+ set layout(value: {});
66
+ /** A null layout derives from the schema, else the data. */
67
+ get layout(): {};
68
+ /** Schema merged with the scoped layout elements. */
20
69
  get combined(): any;
21
- /**
22
- * Set the data
23
- * @param {Record<string, unknown>} value - New data object
24
- */
25
- set data(value: Record<string, unknown>);
26
- /**
27
- * Get the current data
28
- * @returns {Record<string, unknown>} Current data object
29
- */
30
- get data(): Record<string, unknown>;
31
- /**
32
- * Set the schema
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.
40
- */
41
- set schema(value: Object);
42
- /**
43
- * Get the current schema
44
- * @returns {Object} Current schema object
45
- */
46
- get schema(): Object;
47
- /**
48
- * Set the layout
49
- * @param {Object} value - New layout object
50
- */
51
- set layout(value: Object);
52
- /**
53
- * Get the current layout
54
- * @returns {Object} Current layout object
55
- */
56
- get layout(): Object;
57
- /**
58
- * Set validation messages for fields
59
- * @param {Object} value - Validation object with field paths as keys
60
- */
61
- set validation(value: Object);
62
- /**
63
- * Get the current validation state
64
- * @returns {Object} Current validation object
65
- */
66
- get validation(): Object;
67
- /**
68
- * Get the lookup manager
69
- * @returns {ReturnType<typeof createLookupManager>|null}
70
- */
71
- get lookupManager(): ReturnType<typeof createLookupManager> | null;
72
- /**
73
- * Configure lookups for the form
74
- * @param {Object<string, import('./lookup.svelte.js').LookupConfig>} lookups - Lookup configurations
75
- */
76
- setLookups(lookups: {
77
- [x: string]: import("./lookup.svelte.js").LookupConfig;
78
- }): void;
79
- /**
80
- * Get lookup state for a field
81
- * @param {string} fieldPath - Field path
82
- * @returns {{ options: any[], loading: boolean, error: string|null, fields: Object, disabled: boolean }|null}
83
- */
84
- getLookupState(fieldPath: string): {
85
- options: any[];
86
- loading: boolean;
87
- error: string | null;
88
- fields: Object;
89
- disabled: boolean;
90
- } | null;
91
- /**
92
- * Check if a field is disabled due to unmet lookup dependencies
93
- * @param {string} path - Field path
94
- * @returns {boolean}
95
- */
96
- isFieldDisabled(path: string): boolean;
97
- /**
98
- * Manually refresh a field's lookup with the current form data
99
- * @param {string} path - Field path
100
- * @returns {Promise<void>}
101
- */
102
- refreshLookup(path: string): Promise<void>;
103
- /**
104
- * Check if a field has a lookup configured
105
- * @param {string} fieldPath - Field path
106
- * @returns {boolean}
107
- */
108
- hasLookup(fieldPath: string): boolean;
109
- /**
110
- * Initialize all lookups
111
- * @returns {Promise<void>}
112
- */
113
- initializeLookups(): Promise<void>;
114
- /**
115
- * Update a specific field value
116
- * @param {string} path - Field path (e.g., 'count', 'settings/distance')
117
- * @param {any} value - New value
118
- * @param {boolean} [triggerLookups=true] - Whether to trigger dependent lookups
119
- */
120
- updateField(path: string, value: any, triggerLookups?: boolean): void;
121
- /**
122
- * Get a field value by path
123
- * @param {string} path - Field path
124
- * @returns {any} Field value
125
- */
126
- getValue(path: string): any;
127
- /**
128
- * Set validation message for a specific field
129
- * @param {string} fieldPath - Field path (without '#/' prefix)
130
- * @param {Object|null} message - Validation message object or null to clear
131
- * @param {string} message.state - Message state: 'error', 'warning', 'info', 'success'
132
- * @param {string} message.text - Message text content
133
- */
70
+ set validation(value: {});
71
+ /** The validation messages, by field path. */
72
+ get validation(): {};
73
+ /** @param {string} fieldPath @param {Object|null} message - null clears it */
134
74
  setFieldValidation(fieldPath: string, message: Object | null): void;
135
- /**
136
- * Clear all validation messages
137
- */
138
75
  clearValidation(): void;
139
- /**
140
- * Validate a single field by path
141
- * @param {string} fieldPath - Field path (without '#/' prefix)
142
- * @returns {import('./validation.js').ValidationMessage|null} Validation result
143
- */
144
- validateField(fieldPath: string): import("./validation.js").ValidationMessage | null;
145
- /**
146
- * Validate all fields, populate validation state
147
- * @returns {Object} Validation results keyed by field path
148
- */
76
+ /** @returns {import('./validation.js').ValidationMessage|null} */
77
+ validateField(fieldPath: any): import("./validation.js").ValidationMessage | null;
78
+ /** Validate the whole form (every step of a wizard; the visible fields of a flat form). */
149
79
  validate(): Object;
150
- /**
151
- * Get form data with hidden field values stripped out
152
- * Hidden fields are those absent from this.elements (the derived list)
153
- * Does not mutate this.#data
154
- * @returns {Record<string, unknown>} Filtered data containing only visible field keys
155
- */
156
- getVisibleData(): Record<string, unknown>;
157
- /**
158
- * Whether all fields pass validation (no error-state messages)
159
- * @returns {boolean}
160
- */
80
+ /** Validate one step's fields. */
81
+ validateStep(index: any): boolean;
82
+ isStepValid(index?: number): boolean;
161
83
  get isValid(): boolean;
162
- /**
163
- * Array of current error messages with paths
164
- * @returns {Array<{path: string, state: string, text: string}>}
165
- */
84
+ /** @returns {Array<{path: string, state: string, text: string}>} */
166
85
  get errors(): Array<{
167
86
  path: string;
168
87
  state: string;
169
88
  text: string;
170
89
  }>;
171
- /**
172
- * Array of all validation messages with paths, ordered by severity
173
- * @returns {Array<{path: string, state: string, text: string}>}
174
- */
90
+ /** @returns {Array<{path: string, state: string, text: string}>} most severe first */
175
91
  get messages(): Array<{
176
92
  path: string;
177
93
  state: string;
178
94
  text: string;
179
95
  }>;
180
- /**
181
- * Whether any field has been modified from its initial value
182
- * @returns {boolean}
183
- */
184
- get isDirty(): boolean;
185
- /**
186
- * Set of field paths that differ from their initial values
187
- * @returns {Set<string>}
188
- */
189
- get dirtyFields(): Set<string>;
190
- /**
191
- * Check if a single field has been modified from its initial value
192
- * @param {string} fieldPath - Field path (without '#/' prefix)
193
- * @returns {boolean}
194
- */
195
- isFieldDirty(fieldPath: string): boolean;
196
- /**
197
- * Update the initial data snapshot to the current data.
198
- * Call after a successful save to clear dirty state.
199
- */
200
- snapshot(): void;
201
- /**
202
- * Reset form data to initial snapshot and clear validation
203
- */
204
- reset(): void;
205
- /** True when the layout contains step elements */
206
96
  get isMultiStep(): any;
207
- /** Number of step elements in the layout */
208
97
  get totalSteps(): any;
209
- /** Zero-based index of the active step */
210
98
  get currentStep(): number;
211
- /**
212
- * Validate the current step and advance if valid.
213
- * @returns {boolean} true if advanced, false if validation failed
214
- */
99
+ get canAdvance(): boolean;
100
+ /** Validate the current step, and advance only if it passes. */
215
101
  next(): boolean;
216
- /**
217
- * Move to the previous step without validation.
218
- * @returns {boolean} true if moved, false if already on first step
219
- */
102
+ /** Back one step, without validating. */
220
103
  prev(): boolean;
221
- /**
222
- * Navigate to a previously visited step (index < currentStep).
223
- * @param {number} index - Target step index
224
- */
225
- goToStep(index: number): void;
226
- /**
227
- * Validate all fields in a given step. Defaults to currentStep.
228
- * @param {number} [index] - Step index (defaults to currentStep)
229
- * @returns {boolean} true if no errors
230
- */
231
- isStepValid(index?: number): boolean;
232
- /**
233
- * Validate all fields belonging to a step by index.
234
- * @param {number} index - Step index
235
- * @returns {boolean} true if no errors
236
- */
237
- validateStep(index: number): boolean;
104
+ /** Return to a visited step. */
105
+ goToStep(index: any): void;
106
+ /** The underlying lookup manager, or null for a form without lookups. */
107
+ get lookupManager(): Object | null;
108
+ /** @param {Object<string, import('./lookup.svelte.js').LookupConfig>} lookups */
109
+ setLookups(lookups: {
110
+ [x: string]: import("./lookup.svelte.js").LookupConfig;
111
+ }): void;
112
+ /** @returns {{ options: any[], loading: boolean, error: string|null, fields: Object, disabled: boolean }|null} */
113
+ getLookupState(fieldPath: any): {
114
+ options: any[];
115
+ loading: boolean;
116
+ error: string | null;
117
+ fields: Object;
118
+ disabled: boolean;
119
+ } | null;
120
+ /** Disabled because a field its lookup depends on is not set yet. */
121
+ isFieldDisabled(path: any): any;
122
+ refreshLookup(path: any): Promise<void>;
123
+ hasLookup(fieldPath: any): any;
124
+ initializeLookups(): Promise<void>;
238
125
  #private;
239
126
  }
240
- export type FormElement = {
241
- /**
242
- * - JSON Pointer path (e.g., '#/email', '#/user/name')
243
- */
244
- scope: string;
245
- /**
246
- * - Input type (text, number, range, checkbox, select, etc.)
247
- */
248
- type: string;
249
- /**
250
- * - Current value from data
251
- */
252
- value: any;
253
- /**
254
- * - Whether to use custom child snippet (from layout)
255
- */
256
- override: boolean;
257
- /**
258
- * - Merged properties from schema + layout + validation
259
- */
260
- props: {
261
- label?: string | undefined;
262
- description?: string | undefined;
263
- placeholder?: string | undefined;
264
- required?: boolean | undefined;
265
- min?: number | undefined;
266
- max?: number | undefined;
267
- message?: {
268
- /**
269
- * - Message state: 'error', 'warning', 'info', 'success'
270
- */
271
- state?: string | undefined;
272
- /**
273
- * - Message text content
274
- */
275
- text?: string | undefined;
276
- } | undefined;
277
- dirty?: boolean | undefined;
278
- };
279
- };
280
- import { createLookupManager } from './lookup.svelte.js';
127
+ import { FormValues } from './state/FormValues.svelte.js';
128
+ import { FormDefinition } from './state/FormDefinition.svelte.js';
129
+ import { FormSteps } from './state/FormSteps.svelte.js';
130
+ import { FormValidation } from './state/FormValidation.svelte.js';
131
+ import { FormLookups } from './state/FormLookups.svelte.js';
132
+ import { SvelteSet } from 'svelte/reactivity';