@rokkit/forms 1.0.0-next.136 → 1.0.0-next.138
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/dist/src/lib/builder.svelte.d.ts +0 -18
- package/dist/src/lib/lookup.svelte.d.ts +18 -0
- package/package.json +1 -1
- package/src/FormRenderer.svelte +26 -53
- package/src/display/DisplayCardGrid.svelte +21 -16
- package/src/display/DisplayTable.svelte +18 -22
- package/src/display/DisplayValue.svelte +10 -16
- package/src/input/InputRadio.svelte +2 -2
- package/src/lib/builder.svelte.js +346 -210
- package/src/lib/lookup.svelte.js +226 -228
- package/src/lib/validation.js +128 -98
package/src/lib/validation.js
CHANGED
|
@@ -19,103 +19,117 @@ function isEmpty(value) {
|
|
|
19
19
|
return value === undefined || value === null || value === ''
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** @returns {ValidationMessage} */
|
|
23
|
+
function errorMsg(text) {
|
|
24
|
+
return { state: 'error', text }
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
|
-
* Validate string
|
|
28
|
+
* Validate string pattern constraint
|
|
24
29
|
* @private
|
|
25
|
-
* @param {string} value - String value
|
|
26
|
-
* @param {Object} schema - Field schema
|
|
27
|
-
* @param {string} label - Field label
|
|
28
|
-
* @returns {ValidationMessage|null}
|
|
29
30
|
*/
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const regex = new RegExp(schema.pattern)
|
|
37
|
-
if (!regex.test(stringValue)) {
|
|
38
|
-
return {
|
|
39
|
-
state: 'error',
|
|
40
|
-
text: `${label} format is invalid`
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
31
|
+
function validateStringPattern(stringValue, schema, label) {
|
|
32
|
+
if (!schema.pattern) return null
|
|
33
|
+
const regex = new RegExp(schema.pattern)
|
|
34
|
+
if (!regex.test(stringValue)) return errorMsg(`${label} format is invalid`)
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
44
37
|
|
|
45
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Validate string length constraints
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
42
|
+
function validateStringLength(stringValue, schema, label) {
|
|
46
43
|
if (schema.minLength !== undefined && stringValue.length < schema.minLength) {
|
|
47
|
-
return {
|
|
48
|
-
state: 'error',
|
|
49
|
-
text: `${label} must be at least ${schema.minLength} characters`
|
|
50
|
-
}
|
|
44
|
+
return errorMsg(`${label} must be at least ${schema.minLength} characters`)
|
|
51
45
|
}
|
|
52
|
-
|
|
53
46
|
if (schema.maxLength !== undefined && stringValue.length > schema.maxLength) {
|
|
54
|
-
return {
|
|
55
|
-
state: 'error',
|
|
56
|
-
text: `${label} must be no more than ${schema.maxLength} characters`
|
|
57
|
-
}
|
|
47
|
+
return errorMsg(`${label} must be no more than ${schema.maxLength} characters`)
|
|
58
48
|
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Validate string enum constraint
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
function validateStringEnum(stringValue, schema, label) {
|
|
61
57
|
if (schema.enum && !schema.enum.includes(stringValue)) {
|
|
62
|
-
return {
|
|
63
|
-
state: 'error',
|
|
64
|
-
text: `${label} must be one of: ${schema.enum.join(', ')}`
|
|
65
|
-
}
|
|
58
|
+
return errorMsg(`${label} must be one of: ${schema.enum.join(', ')}`)
|
|
66
59
|
}
|
|
67
|
-
|
|
68
60
|
return null
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
/**
|
|
72
|
-
* Validate
|
|
64
|
+
* Validate string field
|
|
73
65
|
* @private
|
|
74
|
-
* @param {
|
|
66
|
+
* @param {string} value - String value
|
|
75
67
|
* @param {Object} schema - Field schema
|
|
76
68
|
* @param {string} label - Field label
|
|
77
69
|
* @returns {ValidationMessage|null}
|
|
78
70
|
*/
|
|
79
|
-
function
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
}
|
|
71
|
+
function validateString(value, schema, label) {
|
|
72
|
+
const stringValue = String(value)
|
|
73
|
+
return (
|
|
74
|
+
validateStringPattern(stringValue, schema, label) ||
|
|
75
|
+
validateStringLength(stringValue, schema, label) ||
|
|
76
|
+
validateStringEnum(stringValue, schema, label)
|
|
77
|
+
)
|
|
78
|
+
}
|
|
89
79
|
|
|
90
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Validate number integer constraint
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
function validateNumberInteger(numValue, schema, label) {
|
|
91
85
|
if (schema.type === 'integer' && !Number.isInteger(numValue)) {
|
|
92
|
-
return {
|
|
93
|
-
state: 'error',
|
|
94
|
-
text: `${label} must be a whole number`
|
|
95
|
-
}
|
|
86
|
+
return errorMsg(`${label} must be a whole number`)
|
|
96
87
|
}
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
97
90
|
|
|
98
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Validate number minimum constraint
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
function validateNumberMin(numValue, schema, label) {
|
|
99
96
|
const minimum = schema.min !== undefined ? schema.min : schema.minimum
|
|
100
97
|
if (minimum !== undefined && numValue < minimum) {
|
|
101
|
-
return {
|
|
102
|
-
state: 'error',
|
|
103
|
-
text: `${label} must be at least ${minimum}`
|
|
104
|
-
}
|
|
98
|
+
return errorMsg(`${label} must be at least ${minimum}`)
|
|
105
99
|
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
106
102
|
|
|
107
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Validate number maximum constraint
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
function validateNumberMax(numValue, schema, label) {
|
|
108
108
|
const maximum = schema.max !== undefined ? schema.max : schema.maximum
|
|
109
109
|
if (maximum !== undefined && numValue > maximum) {
|
|
110
|
-
return {
|
|
111
|
-
state: 'error',
|
|
112
|
-
text: `${label} must be no more than ${maximum}`
|
|
113
|
-
}
|
|
110
|
+
return errorMsg(`${label} must be no more than ${maximum}`)
|
|
114
111
|
}
|
|
115
|
-
|
|
116
112
|
return null
|
|
117
113
|
}
|
|
118
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Validate number field
|
|
117
|
+
* @private
|
|
118
|
+
* @param {number} value - Number value
|
|
119
|
+
* @param {Object} schema - Field schema
|
|
120
|
+
* @param {string} label - Field label
|
|
121
|
+
* @returns {ValidationMessage|null}
|
|
122
|
+
*/
|
|
123
|
+
function validateNumber(value, schema, label) {
|
|
124
|
+
const numValue = Number(value)
|
|
125
|
+
if (isNaN(numValue)) return errorMsg(`${label} must be a valid number`)
|
|
126
|
+
return (
|
|
127
|
+
validateNumberInteger(numValue, schema, label) ||
|
|
128
|
+
validateNumberMin(numValue, schema, label) ||
|
|
129
|
+
validateNumberMax(numValue, schema, label)
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
119
133
|
/**
|
|
120
134
|
* Validate boolean field
|
|
121
135
|
* @private
|
|
@@ -130,10 +144,7 @@ function validateBoolean(value, schema, label) {
|
|
|
130
144
|
|
|
131
145
|
// For required boolean fields, we might want to ensure it's explicitly true
|
|
132
146
|
if (schema.required && schema.mustBeTrue && !boolValue) {
|
|
133
|
-
return {
|
|
134
|
-
state: 'error',
|
|
135
|
-
text: `${label} must be accepted`
|
|
136
|
-
}
|
|
147
|
+
return errorMsg(`${label} must be accepted`)
|
|
137
148
|
}
|
|
138
149
|
|
|
139
150
|
return null
|
|
@@ -210,27 +221,10 @@ function getValueByPath(data, path) {
|
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
/**
|
|
213
|
-
*
|
|
214
|
-
* @
|
|
215
|
-
* @param {Object} fieldSchema - Field schema definition
|
|
216
|
-
* @param {string} [fieldLabel] - Field label for error messages
|
|
217
|
-
* @returns {ValidationMessage|null} Validation message or null if valid
|
|
224
|
+
* Dispatch to the type-specific validator
|
|
225
|
+
* @private
|
|
218
226
|
*/
|
|
219
|
-
|
|
220
|
-
if (!fieldSchema) return null
|
|
221
|
-
|
|
222
|
-
// Required field validation
|
|
223
|
-
if (fieldSchema.required && isEmpty(value)) {
|
|
224
|
-
return {
|
|
225
|
-
state: 'error',
|
|
226
|
-
text: `${fieldLabel} is required`
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Skip other validations if field is empty and not required
|
|
231
|
-
if (isEmpty(value)) return null
|
|
232
|
-
|
|
233
|
-
// Type-specific validations
|
|
227
|
+
function validateByType(value, fieldSchema, fieldLabel) {
|
|
234
228
|
switch (fieldSchema.type) {
|
|
235
229
|
case 'string':
|
|
236
230
|
return validateString(value, fieldSchema, fieldLabel)
|
|
@@ -243,6 +237,52 @@ export function validateField(value, fieldSchema, fieldLabel = 'Field') {
|
|
|
243
237
|
return null
|
|
244
238
|
}
|
|
245
239
|
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check presence (required + empty) before type validation.
|
|
243
|
+
* Returns an error message, null if valid/skippable, or 'skip' sentinel to skip type checks.
|
|
244
|
+
* @private
|
|
245
|
+
* @returns {ValidationMessage|null|'skip'}
|
|
246
|
+
*/
|
|
247
|
+
function checkPresence(value, fieldSchema, fieldLabel) {
|
|
248
|
+
if (fieldSchema.required && isEmpty(value)) return errorMsg(`${fieldLabel} is required`)
|
|
249
|
+
if (isEmpty(value)) return 'skip'
|
|
250
|
+
return null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Validate a single field value against its schema
|
|
255
|
+
* @param {any} value - Field value to validate
|
|
256
|
+
* @param {Object} fieldSchema - Field schema definition
|
|
257
|
+
* @param {string} [fieldLabel] - Field label for error messages
|
|
258
|
+
* @returns {ValidationMessage|null} Validation message or null if valid
|
|
259
|
+
*/
|
|
260
|
+
export function validateField(value, fieldSchema, fieldLabel = 'Field') {
|
|
261
|
+
if (!fieldSchema) return null
|
|
262
|
+
const presenceResult = checkPresence(value, fieldSchema, fieldLabel)
|
|
263
|
+
if (presenceResult === 'skip') return null
|
|
264
|
+
if (presenceResult) return presenceResult
|
|
265
|
+
return validateByType(value, fieldSchema, fieldLabel)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validate one element from the layout
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
function validateElement(element, data, schema, results) {
|
|
273
|
+
if (!element.scope) return
|
|
274
|
+
|
|
275
|
+
const fieldPath = element.scope.replace(/^#\//, '')
|
|
276
|
+
const fieldSchema = getFieldSchema(fieldPath, schema)
|
|
277
|
+
const fieldLabel = element.label || element.title || fieldPath
|
|
278
|
+
const value = getValueByPath(data, fieldPath)
|
|
279
|
+
|
|
280
|
+
const result = validateField(value, fieldSchema, fieldLabel)
|
|
281
|
+
if (result) {
|
|
282
|
+
results[fieldPath] = result
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
246
286
|
/**
|
|
247
287
|
* Validate all fields in a data object
|
|
248
288
|
* @param {Object} data - Data object to validate
|
|
@@ -256,17 +296,7 @@ export function validateAll(data, schema, layout) {
|
|
|
256
296
|
if (!layout.elements || !schema.properties) return validationResults
|
|
257
297
|
|
|
258
298
|
for (const element of layout.elements) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const fieldPath = element.scope.replace(/^#\//, '')
|
|
262
|
-
const fieldSchema = getFieldSchema(fieldPath, schema)
|
|
263
|
-
const fieldLabel = element.label || element.title || fieldPath
|
|
264
|
-
const value = getValueByPath(data, fieldPath)
|
|
265
|
-
|
|
266
|
-
const result = validateField(value, fieldSchema, fieldLabel)
|
|
267
|
-
if (result) {
|
|
268
|
-
validationResults[fieldPath] = result
|
|
269
|
-
}
|
|
299
|
+
validateElement(element, data, schema, validationResults)
|
|
270
300
|
}
|
|
271
301
|
|
|
272
302
|
return validationResults
|