@koumoul/vjsf 3.0.0-beta.3 → 3.0.0-beta.30
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 +127 -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 +11 -1
- package/src/components/fragments/section-header.vue +1 -2
- 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 +101 -0
- package/src/components/fragments/text-field-menu.vue +5 -1
- package/src/components/node.vue +4 -3
- package/src/components/nodes/autocomplete.vue +9 -35
- package/src/components/nodes/checkbox-group.vue +36 -0
- package/src/components/nodes/checkbox.vue +6 -1
- package/src/components/nodes/color-picker.vue +2 -1
- package/src/components/nodes/date-picker.vue +1 -1
- package/src/components/nodes/expansion-panels.vue +22 -12
- package/src/components/nodes/list.vue +143 -88
- package/src/components/nodes/one-of-select.vue +42 -24
- package/src/components/nodes/radio-group.vue +50 -0
- package/src/components/nodes/select.vue +7 -27
- package/src/components/nodes/switch-group.vue +36 -0
- package/src/components/nodes/switch.vue +6 -3
- package/src/components/options.js +21 -5
- package/src/components/vjsf.vue +6 -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 -25
- 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/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/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-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.30",
|
|
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.25.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,96 @@
|
|
|
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
|
+
*/
|
|
68
|
+
const processFragment = (schema, getJSONRef, schemaId) => {
|
|
69
|
+
if (schema.$ref) {
|
|
70
|
+
const [refFragment, refSchemaId] = getJSONRef(schemaId, schema.$ref)
|
|
71
|
+
processFragment(refFragment, getJSONRef, refSchemaId)
|
|
72
|
+
}
|
|
10
73
|
if (!schema.layout) {
|
|
11
74
|
/** @type import('@json-layout/vocabulary').PartialCompObject */
|
|
12
75
|
const layout = {}
|
|
13
76
|
|
|
77
|
+
if (schema.separator || schema['x-separator']) {
|
|
78
|
+
layout.separator = schema.separator || schema['x-separator']
|
|
79
|
+
delete schema.separator
|
|
80
|
+
delete schema['x-separator']
|
|
81
|
+
}
|
|
82
|
+
|
|
14
83
|
if (schema['x-display'] === 'icon' && (schema.enum || schema.items?.enum)) {
|
|
15
84
|
layout.getItems = { itemIcon: schema['x-itemIcon'] || 'data.value' }
|
|
16
85
|
delete schema['x-display']
|
|
17
86
|
}
|
|
18
87
|
|
|
19
88
|
if (schema['x-display']) {
|
|
20
|
-
|
|
89
|
+
let display = schema['x-display']
|
|
90
|
+
if (display === 'radio') display = 'radio-group'
|
|
91
|
+
if (display === 'checkbox' && schema.type !== 'boolean') display = 'checkbox-group'
|
|
92
|
+
if (display === 'switch' && schema.type !== 'boolean') display = 'switch-group'
|
|
93
|
+
layout.comp = display
|
|
21
94
|
delete schema['x-display']
|
|
22
95
|
}
|
|
23
96
|
|
|
@@ -32,18 +105,20 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
32
105
|
}
|
|
33
106
|
|
|
34
107
|
if (schema['x-fromData']) {
|
|
35
|
-
layout.comp = 'select'
|
|
36
|
-
layout.getItems =
|
|
108
|
+
layout.comp = layout.comp ?? 'select'
|
|
109
|
+
layout.getItems = fixExpression(schema['x-fromData'])
|
|
37
110
|
delete schema['x-fromData']
|
|
38
111
|
}
|
|
39
112
|
|
|
113
|
+
if (schema['x-if']) {
|
|
114
|
+
layout.if = fixExpression(schema['x-if'])
|
|
115
|
+
delete schema['x-if']
|
|
116
|
+
}
|
|
117
|
+
|
|
40
118
|
if (schema['x-fromUrl']) {
|
|
41
119
|
/** @type string */
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (expressionMatch[1] !== 'q') url = url.replace(expressionMatch[0], '$' + expressionMatch[0])
|
|
45
|
-
}
|
|
46
|
-
layout.getItems = { url }
|
|
120
|
+
const url = schema['x-fromUrl']
|
|
121
|
+
layout.getItems = { url: fixExpression(url, 'js-tpl') }
|
|
47
122
|
delete schema['x-fromUrl']
|
|
48
123
|
}
|
|
49
124
|
if (layout.getItems && isPartialGetItemsObj(layout.getItems)) {
|
|
@@ -58,6 +133,10 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
58
133
|
delete schema['x-itemsProp']
|
|
59
134
|
}
|
|
60
135
|
|
|
136
|
+
if (schema['x-cols']) {
|
|
137
|
+
layout.cols = schema['x-cols']
|
|
138
|
+
}
|
|
139
|
+
|
|
61
140
|
// compact the layout keyword if possible
|
|
62
141
|
if (Object.keys(layout).length === 1 && 'comp' in layout) {
|
|
63
142
|
schema.layout = layout.comp
|
|
@@ -66,30 +145,51 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
66
145
|
}
|
|
67
146
|
}
|
|
68
147
|
|
|
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)
|
|
148
|
+
if (schema.properties) {
|
|
149
|
+
for (const propertyKey of Object.keys(schema.properties)) {
|
|
150
|
+
processFragment(schema.properties[propertyKey], getJSONRef, schemaId)
|
|
77
151
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (schema.allOf) {
|
|
155
|
+
for (const item of schema.allOf) processFragment(item, getJSONRef, schemaId)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (schema.oneOf) {
|
|
159
|
+
if (!schema.oneOfLayout) {
|
|
160
|
+
const constPropertyKey = Object.keys(schema.oneOf[0]?.properties || {})
|
|
161
|
+
.find(key => !!schema.oneOf[0]?.properties[key].const)
|
|
162
|
+
if (constPropertyKey) {
|
|
163
|
+
const constProperty = schema.oneOf[0]?.properties[constPropertyKey]
|
|
164
|
+
if (constProperty?.title) schema.oneOfLayout = { label: constProperty.title }
|
|
165
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
166
|
+
schema.required = schema.required.filter(key => key !== constPropertyKey)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
83
169
|
}
|
|
170
|
+
for (const item of schema.oneOf) processFragment(item, getJSONRef, schemaId)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (schema.anyOf) {
|
|
174
|
+
for (const item of schema.anyOf) processFragment(item, getJSONRef, schemaId)
|
|
84
175
|
}
|
|
85
176
|
|
|
86
177
|
if (schema.type === 'array' && schema.items) {
|
|
87
178
|
if (Array.isArray(schema.items)) {
|
|
88
|
-
for (const item of schema.items) processFragment(item)
|
|
179
|
+
for (const item of schema.items) processFragment(item, getJSONRef, schemaId)
|
|
89
180
|
} else {
|
|
90
|
-
processFragment(schema.items)
|
|
181
|
+
processFragment(schema.items, getJSONRef, schemaId)
|
|
91
182
|
}
|
|
92
183
|
}
|
|
184
|
+
if (schema.dependencies) {
|
|
185
|
+
for (const key of Object.keys(schema.dependencies)) {
|
|
186
|
+
processFragment(schema.dependencies[key], getJSONRef, schemaId)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (schema.if) {
|
|
190
|
+
if (schema.then) processFragment(schema.then, getJSONRef, schemaId)
|
|
191
|
+
if (schema.else) processFragment(schema.else, getJSONRef, schemaId)
|
|
192
|
+
}
|
|
93
193
|
}
|
|
94
194
|
|
|
95
195
|
/**
|
|
@@ -111,7 +211,7 @@ export function v2compat (_schema, _ajv, lang = 'en') {
|
|
|
111
211
|
|
|
112
212
|
const schema = /** @type {import("ajv").SchemaObject} */ (clone(_schema))
|
|
113
213
|
schema.$id = schema.$id ?? '_jl'
|
|
114
|
-
|
|
115
|
-
processFragment(schema)
|
|
214
|
+
const getJSONRef = resolveLocaleRefs(schema, ajv, lang)
|
|
215
|
+
processFragment(schema, getJSONRef, schema.$id)
|
|
116
216
|
return schema
|
|
117
217
|
}
|
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,
|
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
<v-alert
|
|
5
5
|
v-show="show"
|
|
6
6
|
color="info"
|
|
7
|
+
:density="node.options.density"
|
|
7
8
|
>
|
|
8
9
|
<div v-html="node.layout.help" />
|
|
9
10
|
</v-alert>
|
|
10
11
|
</v-slide-x-reverse-transition>
|
|
11
12
|
<v-btn
|
|
12
13
|
color="info"
|
|
13
|
-
class="vjsf-help-message-toggle"
|
|
14
|
+
:class="`vjsf-help-message-toggle vjsf-help-message-toggle-${node.options.density}`"
|
|
14
15
|
:icon="show ? 'mdi-close-circle' : 'mdi-information'"
|
|
15
16
|
density="compact"
|
|
17
|
+
:size="node.options.density !== 'default' ? 'small' : 'default'"
|
|
16
18
|
:title="show ? '' : node.messages.showHelp"
|
|
17
19
|
@click="show = !show"
|
|
18
20
|
/>
|
|
@@ -45,5 +47,13 @@ const show = ref(false)
|
|
|
45
47
|
right: -4px;
|
|
46
48
|
z-index: 1;
|
|
47
49
|
}
|
|
50
|
+
.vjsf-help-message-toggle-comfortable {
|
|
51
|
+
top: -4px;
|
|
52
|
+
right: -4px;
|
|
53
|
+
}
|
|
54
|
+
.vjsf-help-message-toggle-compact {
|
|
55
|
+
top: -4px;
|
|
56
|
+
right: -4px;
|
|
57
|
+
}
|
|
48
58
|
</style>
|
|
49
59
|
../../../types.js
|
|
@@ -45,7 +45,7 @@ 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
50
|
:density="node.options.density"
|
|
51
51
|
>
|
|
@@ -53,4 +53,3 @@ const titleClass = computed(() => {
|
|
|
53
53
|
</v-alert>
|
|
54
54
|
</div>
|
|
55
55
|
</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,101 @@
|
|
|
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
|
+
density: props.modelValue.options?.density,
|
|
57
|
+
key: item.key,
|
|
58
|
+
modelValue,
|
|
59
|
+
onClick: () => {
|
|
60
|
+
let newValue
|
|
61
|
+
if (props.modelValue.layout.multiple) {
|
|
62
|
+
newValue = props.modelValue.data ? [...props.modelValue.data] : []
|
|
63
|
+
if (newValue.includes(item.value)) {
|
|
64
|
+
newValue = newValue.filter((/** @type {any} */v) => v !== item.value)
|
|
65
|
+
} else {
|
|
66
|
+
newValue.push(item.value)
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
if (props.modelValue.data === item.value) {
|
|
70
|
+
newValue = undefined
|
|
71
|
+
} else {
|
|
72
|
+
newValue = item.value
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
props.statefulLayout.input(props.modelValue, newValue)
|
|
76
|
+
}
|
|
77
|
+
}))
|
|
78
|
+
}
|
|
79
|
+
children.push(h('div', { class: 'v-selection-control-group' }, checkboxes))
|
|
80
|
+
}
|
|
81
|
+
return children
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return slots
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
return () => {
|
|
90
|
+
return h(VInput, fieldProps.value, fieldSlots.value)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<style>
|
|
98
|
+
.vjsf-selection-group .v-selection-control-group>.v-input .v-selection-control {
|
|
99
|
+
min-height: auto;
|
|
100
|
+
}
|
|
101
|
+
</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
|
@@ -35,7 +35,7 @@ const nodeClasses = computed(() => {
|
|
|
35
35
|
return classes
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp]) {
|
|
38
|
+
if (props.modelValue.layout.comp !== 'none' && !props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp]) {
|
|
39
39
|
console.error(`vjsf: missing component to render vjsf node "${props.modelValue.layout.comp}", maybe you forgot to register a component from a plugin ?`)
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -43,6 +43,7 @@ if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp])
|
|
|
43
43
|
|
|
44
44
|
<template>
|
|
45
45
|
<v-col
|
|
46
|
+
v-if="modelValue.layout.comp !== 'none'"
|
|
46
47
|
:cols="modelValue.cols"
|
|
47
48
|
:class="nodeClasses"
|
|
48
49
|
>
|
|
@@ -56,7 +57,7 @@ if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp])
|
|
|
56
57
|
/>
|
|
57
58
|
|
|
58
59
|
<help-message
|
|
59
|
-
v-if="modelValue.layout.help"
|
|
60
|
+
v-if="modelValue.layout.help && !modelValue.options.summary"
|
|
60
61
|
:node="modelValue"
|
|
61
62
|
:class="beforeAfterClasses[modelValue.options.density]"
|
|
62
63
|
/>
|
|
@@ -69,7 +70,7 @@ if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp])
|
|
|
69
70
|
/>
|
|
70
71
|
<component
|
|
71
72
|
:is="props.statefulLayout.options.nodeComponents[modelValue.layout.comp]"
|
|
72
|
-
v-else
|
|
73
|
+
v-else
|
|
73
74
|
:model-value="modelValue"
|
|
74
75
|
:stateful-layout="statefulLayout"
|
|
75
76
|
/>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { VAutocomplete } from 'vuetify/components'
|
|
3
|
-
import { defineComponent, computed,
|
|
3
|
+
import { defineComponent, computed, h } from 'vue'
|
|
4
4
|
import { getInputProps, getCompSlots } from '../../utils/index.js'
|
|
5
|
+
import useGetItems from '../../composables/use-get-items.js'
|
|
5
6
|
import SelectItem from '../fragments/select-item.vue'
|
|
6
7
|
import SelectSelection from '../fragments/select-selection.vue'
|
|
7
8
|
|
|
@@ -13,54 +14,27 @@ export default defineComponent({
|
|
|
13
14
|
required: true
|
|
14
15
|
},
|
|
15
16
|
statefulLayout: {
|
|
16
|
-
|
|
17
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
|
|
17
18
|
type: Object,
|
|
18
19
|
required: true
|
|
19
20
|
}
|
|
20
21
|
},
|
|
21
22
|
setup (props) {
|
|
22
|
-
|
|
23
|
-
const items = shallowRef([])
|
|
24
|
-
/** @type import('vue').Ref<boolean> */
|
|
25
|
-
const loading = ref(false)
|
|
26
|
-
/** @type import('vue').Ref<string> */
|
|
27
|
-
const search = ref('')
|
|
23
|
+
const getItems = useGetItems(props)
|
|
28
24
|
|
|
29
25
|
const fieldProps = computed(() => {
|
|
30
26
|
const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['multiple'])
|
|
31
27
|
if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
|
|
32
28
|
fieldProps.noFilter = true
|
|
33
29
|
fieldProps['onUpdate:search'] = (/** @type string */searchValue) => {
|
|
34
|
-
search.value = searchValue
|
|
35
|
-
refresh()
|
|
30
|
+
getItems.search.value = searchValue
|
|
36
31
|
}
|
|
37
|
-
fieldProps
|
|
38
|
-
fieldProps.
|
|
39
|
-
fieldProps.
|
|
32
|
+
fieldProps.items = getItems.items.value
|
|
33
|
+
fieldProps.loading = getItems.loading.value
|
|
34
|
+
fieldProps.clearable = fieldProps.clearable ?? !props.modelValue.skeleton.required
|
|
40
35
|
return fieldProps
|
|
41
36
|
})
|
|
42
37
|
|
|
43
|
-
/** @type import('@json-layout/core').StateTree | null */
|
|
44
|
-
let lastStateTree = null
|
|
45
|
-
/** @type Record<string, any> | null */
|
|
46
|
-
let lastContext = null
|
|
47
|
-
/** @type string */
|
|
48
|
-
let lastSearch = ''
|
|
49
|
-
|
|
50
|
-
const refresh = async () => {
|
|
51
|
-
if (props.statefulLayout.stateTree === lastStateTree && props.statefulLayout.options.context === lastContext && search.value === lastSearch) return
|
|
52
|
-
loading.value = true
|
|
53
|
-
items.value = await props.statefulLayout.getItems(props.modelValue, search.value)
|
|
54
|
-
lastStateTree = props.statefulLayout.stateTree
|
|
55
|
-
lastContext = props.statefulLayout.options.context ?? null
|
|
56
|
-
lastSearch = search.value
|
|
57
|
-
loading.value = false
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!props.modelValue.layout.items) {
|
|
61
|
-
refresh()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
38
|
const fieldSlots = computed(() => {
|
|
65
39
|
const slots = getCompSlots(props.modelValue, props.statefulLayout)
|
|
66
40
|
if (!slots.item) {
|
|
@@ -74,7 +48,7 @@ export default defineComponent({
|
|
|
74
48
|
slots.selection = (/** @type {any} */ context) => h(SelectSelection, {
|
|
75
49
|
multiple: props.modelValue.layout.multiple,
|
|
76
50
|
last: props.modelValue.layout.multiple && context.index === props.modelValue.data.length - 1,
|
|
77
|
-
item: context.item.raw
|
|
51
|
+
item: getItems.prepareSelectedItem(context.item.raw, context.item.value)
|
|
78
52
|
})
|
|
79
53
|
}
|
|
80
54
|
return slots
|