@koumoul/vjsf 3.0.0-beta.3 → 3.0.0-beta.31
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 +21 -0
- package/package.json +5 -4
- package/src/compat/v2.js +132 -27
- package/src/compile/index.js +18 -4
- package/src/compile/options.js +3 -7
- package/src/compile/v-jsf-compiled.vue.ejs +1 -1
- package/src/components/fragments/help-message.vue +10 -1
- package/src/components/fragments/section-header.vue +1 -3
- package/src/components/fragments/select-item-icon.vue +1 -1
- package/src/components/fragments/select-selection.vue +2 -1
- package/src/components/fragments/selection-group.vue +100 -0
- package/src/components/fragments/text-field-menu.vue +5 -1
- package/src/components/node.vue +47 -42
- package/src/components/nodes/autocomplete.vue +11 -35
- package/src/components/nodes/checkbox-group.vue +39 -0
- package/src/components/nodes/checkbox.vue +9 -1
- package/src/components/nodes/color-picker.vue +5 -1
- package/src/components/nodes/combobox.vue +3 -0
- package/src/components/nodes/date-picker.vue +4 -2
- package/src/components/nodes/date-time-picker.vue +3 -0
- package/src/components/nodes/expansion-panels.vue +24 -12
- package/src/components/nodes/file-input.vue +3 -0
- package/src/components/nodes/list.vue +145 -91
- package/src/components/nodes/number-combobox.vue +3 -0
- package/src/components/nodes/number-field.vue +3 -0
- package/src/components/nodes/one-of-select.vue +45 -24
- package/src/components/nodes/radio-group.vue +53 -0
- package/src/components/nodes/section.vue +3 -0
- package/src/components/nodes/select.vue +10 -27
- package/src/components/nodes/slider.vue +3 -0
- package/src/components/nodes/stepper.vue +3 -0
- package/src/components/nodes/switch-group.vue +39 -0
- package/src/components/nodes/switch.vue +9 -3
- package/src/components/nodes/tabs.vue +10 -3
- package/src/components/nodes/text-field.vue +3 -0
- package/src/components/nodes/textarea.vue +3 -0
- package/src/components/nodes/vertical-tabs.vue +6 -1
- package/src/components/options.js +21 -5
- package/src/components/vjsf.vue +6 -0
- package/src/composables/use-comp-defaults.js +19 -0
- package/src/composables/use-dnd.js +1 -0
- package/src/composables/use-get-items.js +48 -0
- package/src/composables/use-vjsf.js +76 -40
- package/src/index.js +3 -0
- package/src/types.ts +21 -6
- package/src/utils/build.js +1 -1
- package/src/utils/index.js +0 -1
- package/src/utils/props.js +9 -30
- package/types/compat/v2.d.ts.map +1 -1
- package/types/compile/index.d.ts +2 -2
- package/types/compile/index.d.ts.map +1 -1
- package/types/compile/options.d.ts.map +1 -1
- package/types/components/fragments/selection-group.vue.d.ts +35 -0
- package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
- package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
- package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
- package/types/components/nodes/checkbox-group copy.vue.d.ts +27 -0
- package/types/components/nodes/checkbox-group copy.vue.d.ts.map +1 -0
- package/types/components/nodes/checkbox-group.vue.d.ts +27 -0
- package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
- package/types/components/nodes/combobox.vue.d.ts.map +1 -1
- package/types/components/nodes/file-input.vue.d.ts.map +1 -1
- package/types/components/nodes/number-combobox.vue.d.ts.map +1 -1
- package/types/components/nodes/number-field.vue.d.ts.map +1 -1
- package/types/components/nodes/radio-group.vue.d.ts +27 -0
- package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
- package/types/components/nodes/radio.vue.d.ts +10 -0
- package/types/components/nodes/radio.vue.d.ts.map +1 -0
- package/types/components/nodes/select.vue.d.ts.map +1 -1
- package/types/components/nodes/switch-group.vue.d.ts +27 -0
- package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
- package/types/components/nodes/text-field.vue.d.ts.map +1 -1
- package/types/components/nodes/textarea.vue.d.ts.map +1 -1
- package/types/components/options.d.ts +1 -1
- package/types/components/options.d.ts.map +1 -1
- package/types/components/vjsf.vue.d.ts +2 -2
- package/types/composables/use-comp-defaults.d.ts +8 -0
- package/types/composables/use-comp-defaults.d.ts.map +1 -0
- package/types/composables/use-dnd.d.ts.map +1 -1
- package/types/composables/use-get-items.d.ts +13 -0
- package/types/composables/use-get-items.d.ts.map +1 -0
- package/types/composables/use-vjsf.d.ts.map +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/types.d.ts +20 -8
- package/types/types.d.ts.map +1 -1
- package/types/utils/build.d.ts +1 -1
- package/types/utils/index.d.ts +0 -1
- package/types/utils/props.d.ts +3 -4
- package/types/utils/props.d.ts.map +1 -1
- package/src/utils/global-register.js +0 -13
- package/types/components/global-register.d.ts +0 -8
- package/types/components/global-register.d.ts.map +0 -1
- package/types/components/nodes/markdown.vue.d.ts +0 -27
- package/types/components/nodes/markdown.vue.d.ts.map +0 -1
- package/types/components/nodes/text-field copy.vue.d.ts +0 -10
- package/types/components/nodes/text-field copy.vue.d.ts.map +0 -1
- package/types/components/types.d.ts +0 -91
- package/types/components/types.d.ts.map +0 -1
- package/types/components/v-jsf.vue.d.ts +0 -13
- package/types/components/v-jsf.vue.d.ts.map +0 -1
- package/types/utils/clone.d.ts +0 -3
- package/types/utils/clone.d.ts.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# VJSF
|
|
2
|
+
|
|
3
|
+
*vuetify-json-schema-form* - *@koumoul/vjsf on npm*
|
|
4
|
+
|
|
5
|
+
Easily create beautiful forms that output valid data.
|
|
6
|
+
|
|
7
|
+
Based on [Vue.js](https://vuejs.org/) / [Vuetify](https://vuetifyjs.com/) / [JSON Schema](https://json-schema.org/) / [JSON Layout](https://github.com/json-layout/json-layout).
|
|
8
|
+
|
|
9
|
+
See [the documentation](https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/).
|
|
10
|
+
|
|
11
|
+
See [the documentation for deprecated v2](https://koumoul-dev.github.io/vuetify-jsonschema-form/2.x/).
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## Bug reports
|
|
16
|
+
|
|
17
|
+
Bug reports are created using github issues. The examples in the documentation include codepen links, as much as possible please save a duplicate codepen with the minimal schema/config to reproduce your problem.
|
|
18
|
+
|
|
19
|
+
## Contribute
|
|
20
|
+
|
|
21
|
+
See [CONTRIBUTE.md](./CONTRIBUTE.md).
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koumoul/vjsf",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.31",
|
|
4
4
|
"description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "vitest",
|
|
7
7
|
"build": "vue-tsc",
|
|
8
|
-
"watch:build": "vue-tsc --watch"
|
|
8
|
+
"watch:build": "vue-tsc --watch",
|
|
9
|
+
"prepublishOnly": "cp ../README.md README.md && cp ../LICENSE LICENSE"
|
|
9
10
|
},
|
|
10
11
|
"author": "Alban Mouton <alban.mouton@gmail.com>",
|
|
11
12
|
"license": "MIT",
|
|
@@ -72,10 +73,10 @@
|
|
|
72
73
|
},
|
|
73
74
|
"peerDependencies": {
|
|
74
75
|
"vue": "^3.4.3",
|
|
75
|
-
"vuetify": "^3.
|
|
76
|
+
"vuetify": "^3.6.8"
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
78
|
-
"@json-layout/core": "0.
|
|
79
|
+
"@json-layout/core": "0.26.0",
|
|
79
80
|
"@vueuse/core": "^10.5.0",
|
|
80
81
|
"debug": "^4.3.4",
|
|
81
82
|
"ejs": "^3.1.9"
|
package/src/compat/v2.js
CHANGED
|
@@ -1,23 +1,99 @@
|
|
|
1
1
|
import ajvModule from 'ajv'
|
|
2
2
|
import addFormats from 'ajv-formats'
|
|
3
|
-
import {
|
|
3
|
+
import { resolveLocaleRefs, clone } from '@json-layout/core'
|
|
4
4
|
import { isPartialGetItemsObj } from '@json-layout/vocabulary'
|
|
5
5
|
|
|
6
6
|
// @ts-ignore
|
|
7
7
|
const Ajv = /** @type {typeof ajvModule.default} */ (ajvModule)
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const expressionKeyMappings = {
|
|
10
|
+
modelRoot: 'rootData',
|
|
11
|
+
root: 'rootData',
|
|
12
|
+
model: 'data',
|
|
13
|
+
value: 'data'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} expression
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
const prefixExpression = (expression) => {
|
|
21
|
+
// looks like a simple expression missing the data. prefix
|
|
22
|
+
if (expression.match(/^[a-z.]*$/i) && !['data', 'context', 'rootData', 'parent'].some(key => expression.startsWith(key + '.'))) {
|
|
23
|
+
return 'rootData.' + expression
|
|
24
|
+
}
|
|
25
|
+
return expression
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @param {string} expression
|
|
31
|
+
* @param {'js-eval' | 'js-tpl'} [type]
|
|
32
|
+
* @returns {{type: 'js-eval' | 'js-tpl', expr: string, pure: boolean}}
|
|
33
|
+
*/
|
|
34
|
+
const fixExpression = (expression, type = 'js-eval') => {
|
|
35
|
+
let expr = expression
|
|
36
|
+
let pure = true
|
|
37
|
+
|
|
38
|
+
if (expr.includes('parent.value')) {
|
|
39
|
+
pure = false
|
|
40
|
+
expr = expr.replace(/parent\.value/g, 'parent.data')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const [key, value] of Object.entries(expressionKeyMappings)) {
|
|
44
|
+
expr = expr.replace(new RegExp(`${key}\\.`, 'g'), value + '.')
|
|
45
|
+
}
|
|
46
|
+
if (type === 'js-eval') {
|
|
47
|
+
expr = prefixExpression(expr)
|
|
48
|
+
}
|
|
49
|
+
if (type === 'js-tpl') {
|
|
50
|
+
for (const expressionMatch of expr.matchAll(/\{(.*?)\}/g)) {
|
|
51
|
+
if (expressionMatch[1] !== 'q') expr = expr.replace(expressionMatch[0], '${' + prefixExpression(expressionMatch[1]) + '}')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (expr.includes('rootData')) {
|
|
56
|
+
pure = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { type, expr, pure }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {import("ajv").SchemaObject} schema
|
|
65
|
+
* @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
|
|
66
|
+
* @param {string} schemaId
|
|
67
|
+
* @param {any[]} processed
|
|
68
|
+
*/
|
|
69
|
+
const processFragment = (schema, getJSONRef, schemaId, processed) => {
|
|
70
|
+
if (processed.includes(schema)) return
|
|
71
|
+
processed.push(schema)
|
|
72
|
+
if (schema.$ref) {
|
|
73
|
+
const [refFragment, refSchemaId] = getJSONRef(schemaId, schema.$ref)
|
|
74
|
+
processFragment(refFragment, getJSONRef, refSchemaId, processed)
|
|
75
|
+
}
|
|
10
76
|
if (!schema.layout) {
|
|
11
77
|
/** @type import('@json-layout/vocabulary').PartialCompObject */
|
|
12
78
|
const layout = {}
|
|
13
79
|
|
|
80
|
+
if (schema.separator || schema['x-separator']) {
|
|
81
|
+
layout.separator = schema.separator || schema['x-separator']
|
|
82
|
+
delete schema.separator
|
|
83
|
+
delete schema['x-separator']
|
|
84
|
+
}
|
|
85
|
+
|
|
14
86
|
if (schema['x-display'] === 'icon' && (schema.enum || schema.items?.enum)) {
|
|
15
87
|
layout.getItems = { itemIcon: schema['x-itemIcon'] || 'data.value' }
|
|
16
88
|
delete schema['x-display']
|
|
17
89
|
}
|
|
18
90
|
|
|
19
91
|
if (schema['x-display']) {
|
|
20
|
-
|
|
92
|
+
let display = schema['x-display']
|
|
93
|
+
if (display === 'radio') display = 'radio-group'
|
|
94
|
+
if (display === 'checkbox' && schema.type !== 'boolean') display = 'checkbox-group'
|
|
95
|
+
if (display === 'switch' && schema.type !== 'boolean') display = 'switch-group'
|
|
96
|
+
layout.comp = display
|
|
21
97
|
delete schema['x-display']
|
|
22
98
|
}
|
|
23
99
|
|
|
@@ -32,18 +108,20 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
32
108
|
}
|
|
33
109
|
|
|
34
110
|
if (schema['x-fromData']) {
|
|
35
|
-
layout.comp = 'select'
|
|
36
|
-
layout.getItems =
|
|
111
|
+
layout.comp = layout.comp ?? 'select'
|
|
112
|
+
layout.getItems = fixExpression(schema['x-fromData'])
|
|
37
113
|
delete schema['x-fromData']
|
|
38
114
|
}
|
|
39
115
|
|
|
116
|
+
if (schema['x-if']) {
|
|
117
|
+
layout.if = fixExpression(schema['x-if'])
|
|
118
|
+
delete schema['x-if']
|
|
119
|
+
}
|
|
120
|
+
|
|
40
121
|
if (schema['x-fromUrl']) {
|
|
41
122
|
/** @type string */
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (expressionMatch[1] !== 'q') url = url.replace(expressionMatch[0], '$' + expressionMatch[0])
|
|
45
|
-
}
|
|
46
|
-
layout.getItems = { url }
|
|
123
|
+
const url = schema['x-fromUrl']
|
|
124
|
+
layout.getItems = { url: fixExpression(url, 'js-tpl') }
|
|
47
125
|
delete schema['x-fromUrl']
|
|
48
126
|
}
|
|
49
127
|
if (layout.getItems && isPartialGetItemsObj(layout.getItems)) {
|
|
@@ -58,6 +136,10 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
58
136
|
delete schema['x-itemsProp']
|
|
59
137
|
}
|
|
60
138
|
|
|
139
|
+
if (schema['x-cols']) {
|
|
140
|
+
layout.cols = schema['x-cols']
|
|
141
|
+
}
|
|
142
|
+
|
|
61
143
|
// compact the layout keyword if possible
|
|
62
144
|
if (Object.keys(layout).length === 1 && 'comp' in layout) {
|
|
63
145
|
schema.layout = layout.comp
|
|
@@ -66,30 +148,51 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
66
148
|
}
|
|
67
149
|
}
|
|
68
150
|
|
|
69
|
-
if (schema.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
processFragment(schema.properties[propertyKey])
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (schema.allOf) {
|
|
76
|
-
for (const item of schema.allOf) processFragment(item)
|
|
151
|
+
if (schema.properties) {
|
|
152
|
+
for (const propertyKey of Object.keys(schema.properties)) {
|
|
153
|
+
processFragment(schema.properties[propertyKey], getJSONRef, schemaId, processed)
|
|
77
154
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (schema.allOf) {
|
|
158
|
+
for (const item of schema.allOf) processFragment(item, getJSONRef, schemaId, processed)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (schema.oneOf) {
|
|
162
|
+
if (!schema.oneOfLayout) {
|
|
163
|
+
const constPropertyKey = Object.keys(schema.oneOf[0]?.properties || {})
|
|
164
|
+
.find(key => !!schema.oneOf[0]?.properties[key].const)
|
|
165
|
+
if (constPropertyKey) {
|
|
166
|
+
const constProperty = schema.oneOf[0]?.properties[constPropertyKey]
|
|
167
|
+
if (constProperty?.title) schema.oneOfLayout = { label: constProperty.title }
|
|
168
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
169
|
+
schema.required = schema.required.filter(key => key !== constPropertyKey)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
83
172
|
}
|
|
173
|
+
for (const item of schema.oneOf) processFragment(item, getJSONRef, schemaId, processed)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (schema.anyOf) {
|
|
177
|
+
for (const item of schema.anyOf) processFragment(item, getJSONRef, schemaId, processed)
|
|
84
178
|
}
|
|
85
179
|
|
|
86
180
|
if (schema.type === 'array' && schema.items) {
|
|
87
181
|
if (Array.isArray(schema.items)) {
|
|
88
|
-
for (const item of schema.items) processFragment(item)
|
|
182
|
+
for (const item of schema.items) processFragment(item, getJSONRef, schemaId, processed)
|
|
89
183
|
} else {
|
|
90
|
-
processFragment(schema.items)
|
|
184
|
+
processFragment(schema.items, getJSONRef, schemaId, processed)
|
|
91
185
|
}
|
|
92
186
|
}
|
|
187
|
+
if (schema.dependencies) {
|
|
188
|
+
for (const key of Object.keys(schema.dependencies)) {
|
|
189
|
+
processFragment(schema.dependencies[key], getJSONRef, schemaId, processed)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (schema.if) {
|
|
193
|
+
if (schema.then) processFragment(schema.then, getJSONRef, schemaId, processed)
|
|
194
|
+
if (schema.else) processFragment(schema.else, getJSONRef, schemaId, processed)
|
|
195
|
+
}
|
|
93
196
|
}
|
|
94
197
|
|
|
95
198
|
/**
|
|
@@ -111,7 +214,9 @@ export function v2compat (_schema, _ajv, lang = 'en') {
|
|
|
111
214
|
|
|
112
215
|
const schema = /** @type {import("ajv").SchemaObject} */ (clone(_schema))
|
|
113
216
|
schema.$id = schema.$id ?? '_jl'
|
|
114
|
-
|
|
115
|
-
|
|
217
|
+
const getJSONRef = resolveLocaleRefs(schema, ajv, lang)
|
|
218
|
+
/** @type {any[]} */
|
|
219
|
+
const processed = []
|
|
220
|
+
processFragment(schema, getJSONRef, schema.$id, processed)
|
|
116
221
|
return schema
|
|
117
222
|
}
|
package/src/compile/index.js
CHANGED
|
@@ -37,10 +37,17 @@ function listComps (comps, layout) {
|
|
|
37
37
|
* @param {object} schema
|
|
38
38
|
* @param {import('../types.js').PartialVjsfCompileOptions} [options]
|
|
39
39
|
* @param {string} [baseImport]
|
|
40
|
-
* @returns {string}
|
|
40
|
+
* @returns {Promise<string>}
|
|
41
41
|
*/
|
|
42
|
-
export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
|
|
42
|
+
export async function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
|
|
43
43
|
const fullOptions = getFullOptions(options)
|
|
44
|
+
/** @type {Record<string, string>} */
|
|
45
|
+
const pluginsImportsByName = {}
|
|
46
|
+
for (const pluginImport of fullOptions.pluginsImports) {
|
|
47
|
+
const componentInfo = /** @type {import('@json-layout/vocabulary').ComponentInfo} */((await import(pluginImport + '/info.js')).default)
|
|
48
|
+
fullOptions.components[componentInfo.name] = componentInfo
|
|
49
|
+
pluginsImportsByName[componentInfo.name] = pluginImport
|
|
50
|
+
}
|
|
44
51
|
const compiledLayout = compileLayout(schema, { ...fullOptions, code: true })
|
|
45
52
|
const compiledLayoutCode = serializeCompiledLayout(compiledLayout)
|
|
46
53
|
/** @type Set<string> */
|
|
@@ -50,9 +57,16 @@ export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
|
|
|
50
57
|
}
|
|
51
58
|
comps.delete('none')
|
|
52
59
|
|
|
60
|
+
/** @type {Record<string, any>} */
|
|
61
|
+
const pluginsComponents = {}
|
|
62
|
+
|
|
53
63
|
const compImports = [...comps].map(comp => {
|
|
54
64
|
const compName = comp.replace(/-/g, '') + 'Node'
|
|
55
|
-
|
|
65
|
+
let compImport = `${baseImport}/components/nodes/${comp}.vue`
|
|
66
|
+
if (pluginsImportsByName[comp]) {
|
|
67
|
+
compImport = `${pluginsImportsByName[comp]}/node.vue`
|
|
68
|
+
pluginsComponents[comp] = fullOptions.components[comp]
|
|
69
|
+
}
|
|
56
70
|
return {
|
|
57
71
|
comp,
|
|
58
72
|
compName,
|
|
@@ -60,6 +74,6 @@ export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
|
|
|
60
74
|
}
|
|
61
75
|
})
|
|
62
76
|
|
|
63
|
-
const code = ejs.render(template, { compiledLayoutCode, compImports, baseImport })
|
|
77
|
+
const code = ejs.render(template, { compiledLayoutCode, compImports, baseImport, pluginsComponents })
|
|
64
78
|
return code
|
|
65
79
|
}
|
package/src/compile/options.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/** @type import("../types.js").PartialVjsfCompileOptions */
|
|
2
2
|
export const defaultOptions = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
3
|
+
pluginsImports: ['@koumoul/vjsf-markdown'],
|
|
4
|
+
components: {}
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -11,9 +10,6 @@ export const defaultOptions = {
|
|
|
11
10
|
* @returns
|
|
12
11
|
*/
|
|
13
12
|
export const getFullOptions = (options) => {
|
|
14
|
-
const fullOptions = {
|
|
15
|
-
...defaultOptions,
|
|
16
|
-
nodeComponentImports: { ...defaultOptions.nodeComponentImports, ...options.nodeComponentImports }
|
|
17
|
-
}
|
|
13
|
+
const fullOptions = { ...defaultOptions }
|
|
18
14
|
return /** @type import('../types.js').VjsfCompileOptions */ (fullOptions)
|
|
19
15
|
}
|
|
@@ -39,7 +39,7 @@ const emit = defineEmits(emits)
|
|
|
39
39
|
const { el, statefulLayout, stateTree } = useVjsf(
|
|
40
40
|
null,
|
|
41
41
|
computed(() => props.modelValue),
|
|
42
|
-
computed(() => props.options),
|
|
42
|
+
computed(() => ({...props.options, components: <%- JSON.stringify(pluginsComponents) %>})),
|
|
43
43
|
nodeComponents,
|
|
44
44
|
emit,
|
|
45
45
|
null,
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
</v-slide-x-reverse-transition>
|
|
11
11
|
<v-btn
|
|
12
12
|
color="info"
|
|
13
|
-
class="vjsf-help-message-toggle"
|
|
13
|
+
:class="`vjsf-help-message-toggle vjsf-help-message-toggle-${node.options.density}`"
|
|
14
14
|
:icon="show ? 'mdi-close-circle' : 'mdi-information'"
|
|
15
15
|
density="compact"
|
|
16
|
+
:size="node.options.density !== 'default' ? 'small' : 'default'"
|
|
16
17
|
:title="show ? '' : node.messages.showHelp"
|
|
17
18
|
@click="show = !show"
|
|
18
19
|
/>
|
|
@@ -45,5 +46,13 @@ const show = ref(false)
|
|
|
45
46
|
right: -4px;
|
|
46
47
|
z-index: 1;
|
|
47
48
|
}
|
|
49
|
+
.vjsf-help-message-toggle-comfortable {
|
|
50
|
+
top: -4px;
|
|
51
|
+
right: -4px;
|
|
52
|
+
}
|
|
53
|
+
.vjsf-help-message-toggle-compact {
|
|
54
|
+
top: -4px;
|
|
55
|
+
right: -4px;
|
|
56
|
+
}
|
|
48
57
|
</style>
|
|
49
58
|
../../../types.js
|
|
@@ -45,12 +45,10 @@ const titleClass = computed(() => {
|
|
|
45
45
|
</p>
|
|
46
46
|
<v-alert
|
|
47
47
|
v-if="node.error && node.validated"
|
|
48
|
-
|
|
48
|
+
type="error"
|
|
49
49
|
:class="`mt-${titleDepthBase - node.options.titleDepth}`"
|
|
50
|
-
:density="node.options.density"
|
|
51
50
|
>
|
|
52
51
|
{{ node.error }}
|
|
53
52
|
</v-alert>
|
|
54
53
|
</div>
|
|
55
54
|
</template>
|
|
56
|
-
../../../types.js
|
|
@@ -20,7 +20,7 @@ export default defineComponent({
|
|
|
20
20
|
} else if (isSVG.value) {
|
|
21
21
|
return h('div', { innerHTML: props.icon.replace('<svg ', '<svg class="v-icon__svg" '), class: 'v-icon' })
|
|
22
22
|
} else {
|
|
23
|
-
return h(VIcon, null, props.icon)
|
|
23
|
+
return h(VIcon, null, () => props.icon)
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -18,6 +18,7 @@ defineProps({
|
|
|
18
18
|
required: true
|
|
19
19
|
}
|
|
20
20
|
})
|
|
21
|
+
|
|
21
22
|
</script>
|
|
22
23
|
|
|
23
24
|
<template>
|
|
@@ -26,7 +27,7 @@ defineProps({
|
|
|
26
27
|
v-if="item.icon"
|
|
27
28
|
:icon="item.icon"
|
|
28
29
|
/>
|
|
29
|
-
{{ item.title }}
|
|
30
|
+
{{ item.title ?? item.key ?? item.value }}
|
|
30
31
|
<span
|
|
31
32
|
v-if="multiple && !last"
|
|
32
33
|
class="v-select__selection-comma"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { VInput, VLabel, VCheckbox, VSwitch, VSkeletonLoader } from 'vuetify/components'
|
|
3
|
+
import { defineComponent, h, computed } from 'vue'
|
|
4
|
+
import { getInputProps, getCompSlots } from '../../utils/index.js'
|
|
5
|
+
import useGetItems from '../../composables/use-get-items.js'
|
|
6
|
+
|
|
7
|
+
export default defineComponent({
|
|
8
|
+
props: {
|
|
9
|
+
modelValue: {
|
|
10
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfCheckboxGroupNode> */
|
|
11
|
+
type: Object,
|
|
12
|
+
required: true
|
|
13
|
+
},
|
|
14
|
+
statefulLayout: {
|
|
15
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
|
|
16
|
+
type: Object,
|
|
17
|
+
required: true
|
|
18
|
+
},
|
|
19
|
+
type: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
setup (props) {
|
|
25
|
+
const getItems = useGetItems(props)
|
|
26
|
+
|
|
27
|
+
const fieldProps = computed(() => {
|
|
28
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
29
|
+
fieldProps.class.push('v-radio-group') // reuse some styles from radio-group
|
|
30
|
+
fieldProps.class.push('vjsf-selection-group')
|
|
31
|
+
return fieldProps
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const fieldSlots = computed(() => {
|
|
35
|
+
const slots = getCompSlots(props.modelValue, props.statefulLayout)
|
|
36
|
+
|
|
37
|
+
if (!slots.default) {
|
|
38
|
+
slots.default = () => {
|
|
39
|
+
/** @type {import('vue').VNode[]} */
|
|
40
|
+
const children = [h(VLabel, { text: fieldProps.value.label })]
|
|
41
|
+
if (getItems.loading.value) {
|
|
42
|
+
children.push(h(VSkeletonLoader, { type: 'chip' }))
|
|
43
|
+
} else {
|
|
44
|
+
/** @type {import('vue').VNode[]} */
|
|
45
|
+
const checkboxes = []
|
|
46
|
+
for (const item of getItems.items.value) {
|
|
47
|
+
let modelValue = false
|
|
48
|
+
if (props.modelValue.layout.multiple) {
|
|
49
|
+
modelValue = props.modelValue.data?.includes(item.value)
|
|
50
|
+
} else {
|
|
51
|
+
modelValue = props.modelValue.data === item.value
|
|
52
|
+
}
|
|
53
|
+
checkboxes.push(h(props.type === 'switch' ? VSwitch : VCheckbox, {
|
|
54
|
+
label: item.title,
|
|
55
|
+
hideDetails: true,
|
|
56
|
+
key: item.key,
|
|
57
|
+
modelValue,
|
|
58
|
+
onClick: () => {
|
|
59
|
+
let newValue
|
|
60
|
+
if (props.modelValue.layout.multiple) {
|
|
61
|
+
newValue = props.modelValue.data ? [...props.modelValue.data] : []
|
|
62
|
+
if (newValue.includes(item.value)) {
|
|
63
|
+
newValue = newValue.filter((/** @type {any} */v) => v !== item.value)
|
|
64
|
+
} else {
|
|
65
|
+
newValue.push(item.value)
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
if (props.modelValue.data === item.value) {
|
|
69
|
+
newValue = undefined
|
|
70
|
+
} else {
|
|
71
|
+
newValue = item.value
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
props.statefulLayout.input(props.modelValue, newValue)
|
|
75
|
+
}
|
|
76
|
+
}))
|
|
77
|
+
}
|
|
78
|
+
children.push(h('div', { class: 'v-selection-control-group' }, checkboxes))
|
|
79
|
+
}
|
|
80
|
+
return children
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return slots
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
return () => {
|
|
89
|
+
return h(VInput, fieldProps.value, fieldSlots.value)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<style>
|
|
97
|
+
.vjsf-selection-group .v-selection-control-group>.v-input .v-selection-control {
|
|
98
|
+
min-height: auto;
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -24,11 +24,15 @@ const props = defineProps({
|
|
|
24
24
|
const fieldProps = computed(() => {
|
|
25
25
|
const fieldProps = getInputProps(props.modelValue, props.statefulLayout, [], false)
|
|
26
26
|
fieldProps.readonly = true
|
|
27
|
+
fieldProps.clearable = fieldProps.clearable ?? !props.modelValue.skeleton.required
|
|
28
|
+
fieldProps['onClick:clear'] = () => {
|
|
29
|
+
props.statefulLayout.input(props.modelValue, null)
|
|
30
|
+
}
|
|
27
31
|
return fieldProps
|
|
28
32
|
})
|
|
29
33
|
|
|
30
34
|
const menuProps = computed(() => {
|
|
31
|
-
const menuProps = getCompProps(props.modelValue
|
|
35
|
+
const menuProps = getCompProps(props.modelValue)
|
|
32
36
|
menuProps.closeOnContentClick = false
|
|
33
37
|
menuProps.disabled = true
|
|
34
38
|
return menuProps
|
package/src/components/node.vue
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from 'vue'
|
|
3
|
-
import { useTheme } from 'vuetify'
|
|
4
|
-
import { VCol } from 'vuetify/components'
|
|
3
|
+
import { useTheme, useDefaults } from 'vuetify'
|
|
4
|
+
import { VCol, VDefaultsProvider } from 'vuetify/components'
|
|
5
5
|
import NodeSlot from './fragments/node-slot.vue'
|
|
6
6
|
import HelpMessage from './fragments/help-message.vue'
|
|
7
7
|
|
|
8
|
+
useDefaults({}, 'VjsfNode')
|
|
9
|
+
|
|
8
10
|
const props = defineProps({
|
|
9
11
|
modelValue: {
|
|
10
12
|
/** @type import('vue').PropType<import('../types.js').VjsfNode> */
|
|
@@ -35,52 +37,55 @@ const nodeClasses = computed(() => {
|
|
|
35
37
|
return classes
|
|
36
38
|
})
|
|
37
39
|
|
|
38
|
-
if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp]) {
|
|
40
|
+
if (props.modelValue.layout.comp !== 'none' && !props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp]) {
|
|
39
41
|
console.error(`vjsf: missing component to render vjsf node "${props.modelValue.layout.comp}", maybe you forgot to register a component from a plugin ?`)
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
</script>
|
|
43
45
|
|
|
44
46
|
<template>
|
|
45
|
-
<v-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
<v-defaults-provider :defaults="{global: { density: props.modelValue.options.density }}">
|
|
48
|
+
<v-col
|
|
49
|
+
v-if="modelValue.layout.comp !== 'none'"
|
|
50
|
+
:cols="modelValue.cols"
|
|
51
|
+
:class="nodeClasses"
|
|
52
|
+
>
|
|
53
|
+
<node-slot
|
|
54
|
+
v-if="modelValue.layout.slots?.before"
|
|
55
|
+
key="before"
|
|
56
|
+
:layout-slot="modelValue.layout.slots?.before"
|
|
57
|
+
:node="modelValue"
|
|
58
|
+
:stateful-layout="statefulLayout"
|
|
59
|
+
:class="beforeAfterClasses[modelValue.options.density]"
|
|
60
|
+
/>
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
<help-message
|
|
63
|
+
v-if="modelValue.layout.help && !modelValue.options.summary"
|
|
64
|
+
:node="modelValue"
|
|
65
|
+
:class="beforeAfterClasses[modelValue.options.density]"
|
|
66
|
+
/>
|
|
67
|
+
<node-slot
|
|
68
|
+
v-if="modelValue.layout.slots?.component"
|
|
69
|
+
key="component"
|
|
70
|
+
:layout-slot="modelValue.layout.slots?.component"
|
|
71
|
+
:node="modelValue"
|
|
72
|
+
:stateful-layout="statefulLayout"
|
|
73
|
+
/>
|
|
74
|
+
<component
|
|
75
|
+
:is="props.statefulLayout.options.nodeComponents[modelValue.layout.comp]"
|
|
76
|
+
v-else
|
|
77
|
+
:model-value="modelValue"
|
|
78
|
+
:stateful-layout="statefulLayout"
|
|
79
|
+
/>
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
<node-slot
|
|
82
|
+
v-if="modelValue.layout.slots?.after"
|
|
83
|
+
key="after"
|
|
84
|
+
:layout-slot="modelValue.layout.slots?.after"
|
|
85
|
+
:node="modelValue"
|
|
86
|
+
:stateful-layout="statefulLayout"
|
|
87
|
+
:class="beforeAfterClasses[modelValue.options.density]"
|
|
88
|
+
/>
|
|
89
|
+
</v-col>
|
|
90
|
+
</v-defaults-provider>
|
|
86
91
|
</template>
|