@koumoul/vjsf 2.3.1 → 2.5.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.
- package/README.md +1 -1
- package/dist/main.js +1 -1
- package/dist/third-party.js +16 -4
- package/lib/VJsfNoDeps.js +61 -25
- package/lib/deps/third-party.js +2 -0
- package/lib/mixins/EditableArray.js +2 -0
- package/lib/mixins/ObjectContainer.js +10 -6
- package/lib/mixins/SelectProperty.js +5 -4
- package/lib/mixins/SimpleProperty.js +1 -1
- package/lib/mixins/Validatable.js +20 -6
- package/lib/utils/options.js +3 -1
- package/lib/utils/rules.js +15 -1
- package/lib/utils/schema.js +13 -0
- package/package.json +5 -3
package/lib/VJsfNoDeps.js
CHANGED
|
@@ -16,8 +16,9 @@ import MarkdownEditor from './mixins/MarkdownEditor'
|
|
|
16
16
|
import Tooltip from './mixins/Tooltip'
|
|
17
17
|
import Validatable from './mixins/Validatable'
|
|
18
18
|
import Dependent from './mixins/Dependent'
|
|
19
|
+
const expr = require('property-expr')
|
|
19
20
|
|
|
20
|
-
const debugFlag = navigator.cookieEnabled
|
|
21
|
+
const debugFlag = global.navigator && global.navigator.cookieEnabled && global.localStorage && global.localStorage.debug
|
|
21
22
|
|
|
22
23
|
const mountingIncs = {}
|
|
23
24
|
|
|
@@ -42,6 +43,7 @@ export default {
|
|
|
42
43
|
schema: { type: Object, required: true },
|
|
43
44
|
value: { required: true },
|
|
44
45
|
options: { type: Object },
|
|
46
|
+
optionsRoot: { type: Object },
|
|
45
47
|
modelRoot: { type: [Object, Array, String, Number, Boolean] },
|
|
46
48
|
modelKey: { type: [String, Number], default: 'root' },
|
|
47
49
|
parentKey: { type: String, default: '' },
|
|
@@ -55,6 +57,9 @@ export default {
|
|
|
55
57
|
}
|
|
56
58
|
},
|
|
57
59
|
computed: {
|
|
60
|
+
initialOptions() {
|
|
61
|
+
return this.fullKey === 'root' ? (this.options || {}) : this.optionsRoot
|
|
62
|
+
},
|
|
58
63
|
fullOptions() {
|
|
59
64
|
this.debug('compute fullOptions')
|
|
60
65
|
const _global = (typeof window !== 'undefined' && window) || (typeof global !== 'undefined' && global) || {}
|
|
@@ -72,17 +77,32 @@ export default {
|
|
|
72
77
|
|
|
73
78
|
fullOptions.httpLib = fullOptions.httpLib || this.axios || this.$http || this.$axios || _global.axios
|
|
74
79
|
|
|
80
|
+
// validator function generator is either given or prepared using ajv if present in the context
|
|
75
81
|
if (!fullOptions.validator) {
|
|
82
|
+
const ajvLocalize = fullOptions.ajvLocalize || _global.ajvLocalize
|
|
83
|
+
const ajvAddFormats = fullOptions.ajvAddFormats || _global.ajvAddFormats
|
|
84
|
+
const localizeAjv = !!ajvLocalize && fullOptions.locale && ajvLocalize[fullOptions.locale]
|
|
76
85
|
let ajv = fullOptions.ajv
|
|
77
86
|
if (!ajv) {
|
|
78
|
-
const Ajv = _global.Ajv || (_global.ajv7 && _global.ajv7.default) || (_global.ajv2019 && _global.ajv2019.default)
|
|
79
|
-
|
|
87
|
+
const Ajv = fullOptions.Ajv || _global.Ajv || (_global.ajv7 && _global.ajv7.default) || (_global.ajv2019 && _global.ajv2019.default)
|
|
88
|
+
// TODO: use strict mode but remove our x-* annotations before
|
|
89
|
+
if (Ajv) {
|
|
90
|
+
ajv = new Ajv(localizeAjv ? { allErrors: true, messages: false, strict: false } : { strict: false })
|
|
91
|
+
if (ajvAddFormats) ajvAddFormats(ajv)
|
|
92
|
+
ajv.addFormat('hexcolor', /^#[0-9A-Fa-f]{6,8}$/)
|
|
93
|
+
}
|
|
80
94
|
}
|
|
81
95
|
if (ajv) {
|
|
82
96
|
fullOptions.validator = (schema) => {
|
|
83
97
|
const validate = ajv.compile(schema)
|
|
84
98
|
return (model) => {
|
|
85
|
-
|
|
99
|
+
const valid = validate(model)
|
|
100
|
+
if (!valid) {
|
|
101
|
+
if (localizeAjv) {
|
|
102
|
+
ajvLocalize[fullOptions.locale](validate.errors)
|
|
103
|
+
}
|
|
104
|
+
return ajv.errorsText(validate.errors, { dataVar: '' })
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
}
|
|
@@ -133,7 +153,7 @@ export default {
|
|
|
133
153
|
rules() {
|
|
134
154
|
this.debug('compute rules')
|
|
135
155
|
if (!this.fullSchema) return
|
|
136
|
-
return getRules(this.fullSchema, this.fullOptions, this.required, this.isOneOfSelect)
|
|
156
|
+
return getRules(this.schema, this.fullSchema, this.fullOptions, this.required, this.isOneOfSelect)
|
|
137
157
|
},
|
|
138
158
|
disabled() {
|
|
139
159
|
if (!this.fullSchema) return
|
|
@@ -254,7 +274,7 @@ export default {
|
|
|
254
274
|
return
|
|
255
275
|
}
|
|
256
276
|
|
|
257
|
-
if (this.fullSchema['x-if'] && !this.
|
|
277
|
+
if (this.fullSchema['x-if'] && !this.getFromExpr(this.fullSchema['x-if'])) {
|
|
258
278
|
return
|
|
259
279
|
}
|
|
260
280
|
|
|
@@ -309,15 +329,35 @@ export default {
|
|
|
309
329
|
}
|
|
310
330
|
return this._vjsf_cache[key].value
|
|
311
331
|
},
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
332
|
+
// used by all functionalities that require looking into the data or the context (x-if, fromData, etc)
|
|
333
|
+
getFromExpr(exp) {
|
|
334
|
+
const expData = {
|
|
335
|
+
modelRoot: this.modelRoot,
|
|
336
|
+
root: this.modelRoot,
|
|
337
|
+
model: this.value,
|
|
338
|
+
value: this.value,
|
|
339
|
+
context: this.options.context
|
|
340
|
+
}
|
|
341
|
+
this._vjsf_getters = this._vjsf_getters || {}
|
|
342
|
+
|
|
343
|
+
if (this.initialOptions.evalMethod === 'newFunction') {
|
|
344
|
+
// use a powerful meta-programming approach with "new Function", not safe if the schema is user-submitted
|
|
345
|
+
// eslint-disable-next-line no-new-func
|
|
346
|
+
this._vjsf_getters[exp] = this._vjsf_getters[exp] || new Function(...Object.keys(expData), `return ${exp}`)
|
|
347
|
+
return this._vjsf_getters[exp](...Object.values(expData))
|
|
348
|
+
} else {
|
|
349
|
+
exp = this.prefixExpr(exp)
|
|
350
|
+
// otherwise a safer but not as powerful deep getter method
|
|
351
|
+
this._vjsf_getters[exp] = this._vjsf_getters[exp] || expr.getter(exp, true)
|
|
352
|
+
return this._vjsf_getters[exp](expData)
|
|
319
353
|
}
|
|
320
|
-
|
|
354
|
+
},
|
|
355
|
+
// used by getFromExpr to support simpler expressions that look into the root model by default
|
|
356
|
+
prefixExpr(key) {
|
|
357
|
+
if (key.startsWith('context.') || key.startsWith('model.') || key.startsWith('value.') || key.startsWith('modelRoot.') || key.startsWith('root.')) return key
|
|
358
|
+
// no specific prefix found, we use modelRoot for retro-compatibility
|
|
359
|
+
if (this.modelRoot) return 'root.' + key
|
|
360
|
+
return 'model.' + key
|
|
321
361
|
},
|
|
322
362
|
renderPropSlots(h) {
|
|
323
363
|
const slots = []
|
|
@@ -329,28 +369,32 @@ export default {
|
|
|
329
369
|
})
|
|
330
370
|
return slots
|
|
331
371
|
},
|
|
332
|
-
change() {
|
|
372
|
+
change(fastForward = true) {
|
|
333
373
|
if (!this.changed) return
|
|
334
374
|
// let input events be interpreted before sending this.value in change event
|
|
335
375
|
this.$nextTick(() => {
|
|
336
376
|
this.updateSelectItems()
|
|
377
|
+
if (fastForward) this.fastForwardEvent('change-child', { fullKey: this.fullKey, value: this.value })
|
|
337
378
|
this.$emit('change', this.value)
|
|
338
379
|
this.changed = false
|
|
339
380
|
})
|
|
340
381
|
},
|
|
341
|
-
input(value, initial = false) {
|
|
382
|
+
input(value, initial = false, fastForward = true) {
|
|
342
383
|
// this.debug('input', JSON.stringify([this.value, value]))
|
|
343
384
|
if (value === null || value === undefined || value === '') {
|
|
344
385
|
if (this.fullSchema.nullable) {
|
|
345
386
|
if (this.value !== null) {
|
|
346
387
|
this.changed = true
|
|
388
|
+
if (fastForward) this.fastForwardEvent('input-child', { fullKey: this.fullKey, value: null, oldValue: this.value })
|
|
347
389
|
this.$emit('input', null)
|
|
348
390
|
} else if (initial) {
|
|
391
|
+
if (fastForward) this.fastForwardEvent('input-child', { fullKey: this.fullKey, value: null, oldValue: this.value })
|
|
349
392
|
this.$emit('input', null)
|
|
350
393
|
}
|
|
351
394
|
} else {
|
|
352
395
|
if (this.value !== undefined) {
|
|
353
396
|
this.changed = true
|
|
397
|
+
if (fastForward) this.fastForwardEvent('input-child', { fullKey: this.fullKey, value: undefined, oldValue: this.value })
|
|
354
398
|
this.$emit('input', undefined)
|
|
355
399
|
}
|
|
356
400
|
}
|
|
@@ -358,6 +402,7 @@ export default {
|
|
|
358
402
|
if (!deepEqual(value, this.value)) {
|
|
359
403
|
this.changed = true
|
|
360
404
|
// console.log(this.fullKey, isCyclic(value), value)
|
|
405
|
+
if (fastForward) this.fastForwardEvent('input-child', { fullKey: this.fullKey, value, oldValue: this.value })
|
|
361
406
|
this.$emit('input', value)
|
|
362
407
|
}
|
|
363
408
|
}
|
|
@@ -436,15 +481,6 @@ export default {
|
|
|
436
481
|
value = this.value.filter(item => ![undefined, null].includes(item))
|
|
437
482
|
}
|
|
438
483
|
return this.input(this.fixProperties(value), true)
|
|
439
|
-
},
|
|
440
|
-
watchKey(key) {
|
|
441
|
-
if (key.startsWith('context.')) return 'options.' + key
|
|
442
|
-
if (key.startsWith('root.')) return key.replace('root.', 'modelRoot.')
|
|
443
|
-
if (key.startsWith('modelRoot.')) return key
|
|
444
|
-
|
|
445
|
-
// no specific prefix found, we use modelRoot for retro-compatibility
|
|
446
|
-
if (this.modelRoot) return 'modelRoot.' + key
|
|
447
|
-
return 'value.' + key
|
|
448
484
|
}
|
|
449
485
|
}
|
|
450
486
|
}
|
package/lib/deps/third-party.js
CHANGED
|
@@ -7,3 +7,5 @@ const _global = (typeof window !== 'undefined' && window) || (typeof global !==
|
|
|
7
7
|
_global.markdownit = require('markdown-it')
|
|
8
8
|
Vue.component('draggable', Draggable)
|
|
9
9
|
_global.Ajv = require('ajv')
|
|
10
|
+
_global.ajvLocalize = require('ajv-i18n')
|
|
11
|
+
_global.ajvAddFormats = require('ajv-formats')
|
|
@@ -104,6 +104,7 @@ export default {
|
|
|
104
104
|
modelKey,
|
|
105
105
|
parentKey: `${this.fullKey}.`,
|
|
106
106
|
options: { ...this.fullOptions, hideReadOnly: false },
|
|
107
|
+
optionsRoot: this.initialOptions,
|
|
107
108
|
sectionDepth: this.sectionDepth + 1,
|
|
108
109
|
separateValidation: false
|
|
109
110
|
},
|
|
@@ -171,6 +172,7 @@ export default {
|
|
|
171
172
|
modelKey: `item-${i}`,
|
|
172
173
|
parentKey: `${this.fullKey}.`,
|
|
173
174
|
options,
|
|
175
|
+
optionsRoot: this.initialOptions,
|
|
174
176
|
sectionDepth: this.sectionDepth + 1,
|
|
175
177
|
separateValidation: this.fullOptions.editMode !== 'inline'
|
|
176
178
|
},
|
|
@@ -56,7 +56,7 @@ export default {
|
|
|
56
56
|
this.$nextTick(() => {
|
|
57
57
|
this.showCurrentOneOf = true
|
|
58
58
|
if (!this.currentOneOf) this.$set(this.subModels, 'currentOneOf', {})
|
|
59
|
-
else this.input(this.fixProperties(this.value))
|
|
59
|
+
else this.input(this.fixProperties(this.value), false, false)
|
|
60
60
|
if (this.triggerChangeCurrentOneOf) {
|
|
61
61
|
this.$nextTick(() => {
|
|
62
62
|
this.triggerChangeCurrentOneOf = false
|
|
@@ -68,7 +68,7 @@ export default {
|
|
|
68
68
|
subModels: {
|
|
69
69
|
handler() {
|
|
70
70
|
this.debug('watched subModels')
|
|
71
|
-
this.input(this.fixProperties(this.value))
|
|
71
|
+
this.input(this.fixProperties(this.value), false, false)
|
|
72
72
|
},
|
|
73
73
|
deep: true
|
|
74
74
|
}
|
|
@@ -185,7 +185,7 @@ export default {
|
|
|
185
185
|
if (schema.default !== undefined) value = copy(schema.default)
|
|
186
186
|
if (value !== undefined && value !== null) {
|
|
187
187
|
this.$set(wrapper, modelKey, value)
|
|
188
|
-
if (!subModelKey) this.input(wrapper)
|
|
188
|
+
if (!subModelKey) this.input(wrapper, false, false)
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
return h('v-jsf', {
|
|
@@ -197,6 +197,7 @@ export default {
|
|
|
197
197
|
parentKey: `${this.fullKey}.`,
|
|
198
198
|
required: forceRequired || !!(this.fullSchema.required && this.fullSchema.required.includes(schema.key)),
|
|
199
199
|
options: { ...this.fullOptions, autofocus: this.fullOptions.autofocus && this.objectContainerChildrenCount === 1 },
|
|
200
|
+
optionsRoot: this.initialOptions,
|
|
200
201
|
sectionDepth
|
|
201
202
|
},
|
|
202
203
|
class: this.fullOptions.childrenClass,
|
|
@@ -216,9 +217,9 @@ export default {
|
|
|
216
217
|
} else {
|
|
217
218
|
this.$set(wrapper, modelKey, v)
|
|
218
219
|
}
|
|
219
|
-
if (!subModelKey) this.input(wrapper)
|
|
220
|
+
if (!subModelKey) this.input(wrapper, false, false)
|
|
220
221
|
},
|
|
221
|
-
change: v => this.change()
|
|
222
|
+
change: v => this.change(false)
|
|
222
223
|
}
|
|
223
224
|
}, this.childSlots(h, schema.key))
|
|
224
225
|
},
|
|
@@ -303,7 +304,7 @@ export default {
|
|
|
303
304
|
value: this.currentOneOf,
|
|
304
305
|
label: (this.subSchemasConstProp && this.subSchemasConstProp.title) || this.fullSchema.title,
|
|
305
306
|
items: this.subSchemas
|
|
306
|
-
.filter(item => !item['x-if'] || !!this.
|
|
307
|
+
.filter(item => !item['x-if'] || !!this.getFromExpr(item['x-if']))
|
|
307
308
|
.filter(item => item.properties && item.properties[this.subSchemasConstProp.key]),
|
|
308
309
|
required: this.subSchemasRequired,
|
|
309
310
|
clearable: !this.subSchemasRequired,
|
|
@@ -324,6 +325,9 @@ export default {
|
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
return [h('v-row', { class: `ma-0 ${this.fullOptions.objectContainerClass}` }, [
|
|
328
|
+
// display a local error only we don't already have an error displayed in the children
|
|
329
|
+
(this.localRuleError && !this.dedupChildrenWithValidatedErrors.length) && h('v-col', { props: { cols: 12 }, class: { 'px-0': true, 'error--text': true } }, this.localRuleError),
|
|
330
|
+
// display the description as block of text on top of section
|
|
327
331
|
this.fullSchema.description && !this.subSchemasConstProp && h('v-col', { props: { cols: 12 }, class: { 'pa-0': true }, domProps: { innerHTML: this.htmlDescription } })]
|
|
328
332
|
.concat(flatChildren).concat(sectionsChildren))
|
|
329
333
|
]
|
|
@@ -27,7 +27,8 @@ export default {
|
|
|
27
27
|
oneOfSelect() {
|
|
28
28
|
if (!this.fullSchema) return
|
|
29
29
|
if (this.fullSchema.type === 'array' && this.fullSchema.items && ['string', 'integer', 'number'].includes(this.fullSchema.items.type) && (this.fullSchema.items.oneOf || this.fullSchema.items.anyOf)) return true
|
|
30
|
-
if (['string', 'integer', 'number'].includes(this.fullSchema.type) &&
|
|
30
|
+
if (['string', 'integer', 'number'].includes(this.fullSchema.type) && this.fullSchema.oneOf && this.fullSchema.oneOf[0] && this.fullSchema.oneOf[0].const !== undefined) return true
|
|
31
|
+
if (['string', 'integer', 'number'].includes(this.fullSchema.type) && this.fullSchema.anyOf && this.fullSchema.anyOf[0] && this.fullSchema.anyOf[0].const !== undefined) return true
|
|
31
32
|
return false
|
|
32
33
|
},
|
|
33
34
|
examplesSelect() {
|
|
@@ -104,7 +105,7 @@ export default {
|
|
|
104
105
|
const of = schema.anyOf || schema.oneOf
|
|
105
106
|
this.openEndedSelect = schema.anyOf && !!schema.anyOf.find(item => !item.const && !item.enum)
|
|
106
107
|
this.rawSelectItems = of
|
|
107
|
-
.filter(item => !item['x-if'] || !!this.
|
|
108
|
+
.filter(item => !item['x-if'] || !!this.getFromExpr(item['x-if']))
|
|
108
109
|
.filter(item => !!item.const || !!item.enum)
|
|
109
110
|
.map(item => ({ ...item, [this.itemKey]: item.const || (item.enum && item.enum[0]), [this.itemTitle]: item.title }))
|
|
110
111
|
}
|
|
@@ -119,14 +120,14 @@ export default {
|
|
|
119
120
|
// Case of a select based on an array somewhere in the data
|
|
120
121
|
if (this.fullSchema['x-fromData']) {
|
|
121
122
|
this.openEndedSelect = this.customTag === 'v-combobox' || this.fullSchema['x-display'] === 'combobox'
|
|
122
|
-
this.$watch(this.
|
|
123
|
+
this.$watch(() => this.getFromExpr(this.fullSchema['x-fromData']), (val) => {
|
|
123
124
|
this.rawSelectItems = val
|
|
124
125
|
}, { immediate: true })
|
|
125
126
|
}
|
|
126
127
|
// Watch the dynamic parts of the URL used to fill the select field
|
|
127
128
|
if (this.fromUrlKeys) {
|
|
128
129
|
this.fromUrlKeys.forEach(key => {
|
|
129
|
-
this.$watch(this.
|
|
130
|
+
this.$watch(() => this.getFromExpr(key), (val) => {
|
|
130
131
|
this.fromUrlParams[key] = val
|
|
131
132
|
this.fetchSelectItems()
|
|
132
133
|
}, { immediate: true })
|
|
@@ -83,7 +83,7 @@ export default {
|
|
|
83
83
|
props.appendIcon = ''
|
|
84
84
|
props.type = 'string'
|
|
85
85
|
props.validateOnBlur = true
|
|
86
|
-
const itemRules = getRules(schemaUtils.prepareFullSchema(this.fullSchema.items, null, this.fullOptions), this.fullOptions)
|
|
86
|
+
const itemRules = getRules(this.fullSchema.items, schemaUtils.prepareFullSchema(this.fullSchema.items, null, this.fullOptions), this.fullOptions)
|
|
87
87
|
props.rules = props.rules.concat([(values) => {
|
|
88
88
|
const valuesMessages = values.map(value => {
|
|
89
89
|
const brokenRule = itemRules.find(rule => {
|
|
@@ -8,7 +8,8 @@ export default {
|
|
|
8
8
|
return {
|
|
9
9
|
form: {
|
|
10
10
|
register: this.register,
|
|
11
|
-
unregister: this.unregister
|
|
11
|
+
unregister: this.unregister,
|
|
12
|
+
fastForwardEvent: this.fastForwardEvent
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
},
|
|
@@ -33,15 +34,23 @@ export default {
|
|
|
33
34
|
},
|
|
34
35
|
hasError() {
|
|
35
36
|
this.debug('compute hasError')
|
|
36
|
-
return !!this.inputs.find(input => input.hasError) || !!this.
|
|
37
|
+
return !!this.inputs.find(input => input.hasError) || !!this.localRuleError
|
|
37
38
|
},
|
|
38
|
-
|
|
39
|
-
this.debug('compute
|
|
40
|
-
return !!this.inputs.find(input => input.
|
|
39
|
+
hasValidatedError() {
|
|
40
|
+
this.debug('compute hasValidatedError')
|
|
41
|
+
return !!this.inputs.find(input => input.hasValidatedError || (input.hasError && (input.validated || input.shouldValidate))) ||
|
|
42
|
+
(this.localRuleError && (this.validated || this.shouldValidate))
|
|
41
43
|
},
|
|
42
44
|
childrenWithValidatedErrors() {
|
|
43
45
|
this.debug('compute childrenWithValidatedErrors')
|
|
44
|
-
return Object.keys(this.childrenInputs).filter(key => !!this.childrenInputs[key].
|
|
46
|
+
return Object.keys(this.childrenInputs).filter(key => !!this.childrenInputs[key].hasValidatedError)
|
|
47
|
+
},
|
|
48
|
+
localRuleError() {
|
|
49
|
+
if (!this.validated || !this.rules || !this.rules.length) return false
|
|
50
|
+
const brokenRule = this.rules.find(rule => {
|
|
51
|
+
return typeof rule(this.value) === 'string'
|
|
52
|
+
})
|
|
53
|
+
return brokenRule && brokenRule(this.value)
|
|
45
54
|
}
|
|
46
55
|
},
|
|
47
56
|
watch: {
|
|
@@ -97,6 +106,11 @@ export default {
|
|
|
97
106
|
if (initialValidation === 'defined' && this.initiallyDefined && !this.isObjectContainer) {
|
|
98
107
|
this.validate(true)
|
|
99
108
|
}
|
|
109
|
+
},
|
|
110
|
+
fastForwardEvent(eventName, data) {
|
|
111
|
+
// emit an event to the top of the vjsf instances tree exactly as it is
|
|
112
|
+
if (this.fullKey === 'root') this.$emit(eventName, data)
|
|
113
|
+
else this.form.fastForwardEvent(eventName, data)
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
}
|
package/lib/utils/options.js
CHANGED
package/lib/utils/rules.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { deepEqual } from 'fast-equals'
|
|
2
2
|
|
|
3
|
-
export const getRules = (fullSchema, options, required, isOneOfSelect) => {
|
|
3
|
+
export const getRules = (schema, fullSchema, options, required, isOneOfSelect) => {
|
|
4
4
|
const rules = []
|
|
5
5
|
if (required) {
|
|
6
6
|
rules.push((val) => (val !== undefined && val !== null && val !== '') || options.messages.required)
|
|
@@ -54,6 +54,11 @@ export const getRules = (fullSchema, options, required, isOneOfSelect) => {
|
|
|
54
54
|
rules.push((val) => (val === undefined || val === null || !val.find(valItem => !fullSchema.items.oneOf.find(item => deepEqual(item.const, valItem)))) || '')
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// ajv validation
|
|
58
|
+
if (options.validator && options.useValidator) {
|
|
59
|
+
rules.push(getAjvRule(schema, options.validator))
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
const customRules = (fullSchema['x-rules'] || []).map(rule => {
|
|
58
63
|
if (typeof rule === 'string') {
|
|
59
64
|
const ruleFunction = options.rules && options.rules[rule]
|
|
@@ -65,3 +70,12 @@ export const getRules = (fullSchema, options, required, isOneOfSelect) => {
|
|
|
65
70
|
}).filter(rule => !!rule)
|
|
66
71
|
return rules.concat(customRules)
|
|
67
72
|
}
|
|
73
|
+
|
|
74
|
+
const getAjvRule = (schema, validator, locale) => {
|
|
75
|
+
const validate = validator(schema)
|
|
76
|
+
return (val) => {
|
|
77
|
+
if (val === null || val === undefined) return true
|
|
78
|
+
const error = validate(val)
|
|
79
|
+
return !error || error
|
|
80
|
+
}
|
|
81
|
+
}
|
package/lib/utils/schema.js
CHANGED
|
@@ -30,6 +30,13 @@ schemaUtils.prepareFullSchema = (schema, value, options) => {
|
|
|
30
30
|
|
|
31
31
|
if (!fullSchema.type && fullSchema.properties) fullSchema.type = 'object'
|
|
32
32
|
|
|
33
|
+
// detect type from combination info
|
|
34
|
+
if (!fullSchema.type) {
|
|
35
|
+
const combination = fullSchema.anyOf || fullSchema.oneOf || fullSchema.allOf
|
|
36
|
+
const typedCombinationItem = combination && combination.find(c => !!c.type)
|
|
37
|
+
if (typedCombinationItem) fullSchema.type = typedCombinationItem.type
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
// manage null type in an array, for example ['string', 'null']
|
|
34
41
|
if (Array.isArray(fullSchema.type)) {
|
|
35
42
|
fullSchema.nullable = fullSchema.type.includes('null')
|
|
@@ -44,6 +51,12 @@ schemaUtils.prepareFullSchema = (schema, value, options) => {
|
|
|
44
51
|
})
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
// enum with a single item can be used as another way to express const
|
|
55
|
+
if (fullSchema.enum && fullSchema.enum.length === 1) {
|
|
56
|
+
fullSchema.const = fullSchema.enum[0]
|
|
57
|
+
delete fullSchema.enum
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
if (fullSchema.type !== 'object') return fullSchema
|
|
48
61
|
|
|
49
62
|
// Properties as array for easier loops
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koumoul/vjsf",
|
|
3
|
-
"version": "2.3
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -47,14 +47,16 @@
|
|
|
47
47
|
"homepage": "https://github.com/koumoul-dev/vuetify-jsonschema-form#readme",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@mdi/js": "^5.5.55",
|
|
50
|
-
"ajv": "^6.
|
|
50
|
+
"ajv": "^8.6.2",
|
|
51
|
+
"ajv-formats": "^2.1.1",
|
|
52
|
+
"ajv-i18n": "^4.1.0",
|
|
51
53
|
"debounce": "^1.2.0",
|
|
52
54
|
"fast-copy": "^2.1.1",
|
|
53
55
|
"fast-equals": "^2.0.0",
|
|
54
56
|
"markdown-it": "^8.4.2",
|
|
55
57
|
"match-all": "^1.2.5",
|
|
56
58
|
"object-hash": "^2.1.1",
|
|
57
|
-
"property-expr": "^
|
|
59
|
+
"property-expr": "^2.0.4",
|
|
58
60
|
"vuedraggable": "^2.24.3"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|