@koumoul/vjsf 3.0.0-beta.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/package.json +13 -20
- package/src/compat/v2.js +126 -27
- package/src/components/fragments/child-subtitle.vue +25 -0
- package/src/components/fragments/help-message.vue +33 -8
- package/src/components/fragments/section-header.vue +9 -7
- package/src/components/fragments/select-item-icon.vue +2 -2
- package/src/components/fragments/select-item.vue +2 -1
- package/src/components/fragments/select-selection.vue +2 -1
- package/src/components/fragments/selection-group.vue +105 -0
- package/src/components/fragments/text-field-menu.vue +16 -7
- package/src/components/node.vue +58 -41
- package/src/components/nodes/autocomplete.vue +14 -60
- package/src/components/nodes/card.vue +39 -0
- package/src/components/nodes/checkbox-group.vue +39 -0
- package/src/components/nodes/checkbox.vue +31 -26
- package/src/components/nodes/color-picker.vue +10 -5
- package/src/components/nodes/combobox.vue +17 -40
- package/src/components/nodes/date-picker.vue +30 -13
- package/src/components/nodes/date-time-picker.vue +83 -3
- package/src/components/nodes/expansion-panels.vue +17 -9
- package/src/components/nodes/file-input.vue +15 -11
- package/src/components/nodes/list.vue +246 -112
- package/src/components/nodes/number-combobox.vue +18 -39
- package/src/components/nodes/number-field.vue +17 -11
- package/src/components/nodes/one-of-select.vue +53 -27
- package/src/components/nodes/radio-group.vue +58 -0
- package/src/components/nodes/section.vue +4 -1
- package/src/components/nodes/select.vue +15 -54
- package/src/components/nodes/slider.vue +32 -29
- package/src/components/nodes/stepper.vue +10 -2
- package/src/components/nodes/switch-group.vue +39 -0
- package/src/components/nodes/switch.vue +31 -26
- package/src/components/nodes/tabs.vue +20 -8
- package/src/components/nodes/text-field.vue +10 -7
- package/src/components/nodes/textarea.vue +20 -12
- package/src/components/nodes/time-picker.vue +41 -1
- package/src/components/nodes/vertical-tabs.vue +16 -6
- package/src/components/tree.vue +1 -1
- package/src/components/vjsf.vue +11 -1
- package/src/composables/use-comp-defaults.js +19 -0
- package/src/composables/use-dnd.js +2 -1
- package/src/composables/use-get-items.js +53 -0
- package/src/composables/use-node.js +136 -0
- package/src/composables/use-select-node.js +67 -0
- package/src/composables/use-vjsf.js +74 -42
- package/src/index.js +5 -2
- package/src/options.js +67 -0
- package/src/types.ts +64 -33
- package/src/utils/arrays.js +37 -6
- 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 +3 -2
- package/types/compile/options.d.ts.map +1 -1
- package/types/components/fragments/child-subtitle.vue.d.ts +8 -0
- package/types/components/fragments/child-subtitle.vue.d.ts.map +1 -0
- package/types/components/fragments/help-message.vue.d.ts +2 -2
- package/types/components/fragments/node-slot.vue.d.ts +2 -44
- package/types/components/fragments/node-slot.vue.d.ts.map +1 -1
- package/types/components/fragments/section-header.vue.d.ts +4 -2
- package/types/components/fragments/select-item-icon.vue.d.ts +2 -12
- package/types/components/fragments/select-item.vue.d.ts +2 -2
- package/types/components/fragments/select-selection.vue.d.ts +2 -2
- package/types/components/fragments/selection-group.vue.d.ts +5 -0
- package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
- package/types/components/fragments/text-field-menu.vue.d.ts +2 -2
- package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
- package/types/components/node.vue.d.ts +2 -2
- package/types/components/nodes/autocomplete.vue.d.ts +2 -24
- package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
- package/types/components/nodes/card.vue.d.ts +10 -0
- package/types/components/nodes/card.vue.d.ts.map +1 -0
- package/types/components/nodes/checkbox-group.vue.d.ts +5 -0
- package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
- package/types/components/nodes/checkbox.vue.d.ts +3 -8
- package/types/components/nodes/color-picker.vue.d.ts +2 -2
- package/types/components/nodes/combobox.vue.d.ts +2 -24
- package/types/components/nodes/combobox.vue.d.ts.map +1 -1
- package/types/components/nodes/date-picker.vue.d.ts +2 -2
- package/types/components/nodes/date-time-picker.vue.d.ts +4 -4
- package/types/components/nodes/expansion-panels.vue.d.ts +2 -2
- package/types/components/nodes/file-input.vue.d.ts +2 -24
- package/types/components/nodes/file-input.vue.d.ts.map +1 -1
- package/types/components/nodes/list.vue.d.ts +2 -2
- package/types/components/nodes/number-combobox.vue.d.ts +2 -24
- package/types/components/nodes/number-combobox.vue.d.ts.map +1 -1
- package/types/components/nodes/number-field.vue.d.ts +2 -24
- package/types/components/nodes/number-field.vue.d.ts.map +1 -1
- package/types/components/nodes/one-of-select.vue.d.ts +2 -2
- package/types/components/nodes/radio-group.vue.d.ts +5 -0
- package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
- package/types/components/nodes/section.vue.d.ts +2 -2
- package/types/components/nodes/select.vue.d.ts +2 -24
- package/types/components/nodes/select.vue.d.ts.map +1 -1
- package/types/components/nodes/slider.vue.d.ts +3 -8
- package/types/components/nodes/stepper.vue.d.ts +2 -2
- package/types/components/nodes/switch-group.vue.d.ts +5 -0
- package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
- package/types/components/nodes/switch.vue.d.ts +3 -8
- package/types/components/nodes/tabs.vue.d.ts +2 -2
- package/types/components/nodes/text-field.vue.d.ts +2 -24
- package/types/components/nodes/text-field.vue.d.ts.map +1 -1
- package/types/components/nodes/textarea.vue.d.ts +2 -24
- package/types/components/nodes/textarea.vue.d.ts.map +1 -1
- package/types/components/nodes/time-picker.vue.d.ts +8 -1
- package/types/components/nodes/vertical-tabs.vue.d.ts +2 -2
- package/types/components/options.d.ts +1 -1
- package/types/components/options.d.ts.map +1 -1
- package/types/components/tree.vue.d.ts +2 -2
- package/types/components/vjsf.vue.d.ts +4 -4
- 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 +3 -3
- package/types/composables/use-dnd.d.ts.map +1 -1
- package/types/composables/use-field-props.d.ts +30 -0
- package/types/composables/use-field-props.d.ts.map +1 -0
- package/types/composables/use-field.d.ts +31 -0
- package/types/composables/use-field.d.ts.map +1 -0
- package/types/composables/use-get-items.d.ts +12 -0
- package/types/composables/use-get-items.d.ts.map +1 -0
- package/types/composables/use-node.d.ts +32 -0
- package/types/composables/use-node.d.ts.map +1 -0
- package/types/composables/use-select-field.d.ts +21 -0
- package/types/composables/use-select-field.d.ts.map +1 -0
- package/types/composables/use-select-node.d.ts +27 -0
- package/types/composables/use-select-node.d.ts.map +1 -0
- package/types/composables/use-select-props.d.ts +21 -0
- package/types/composables/use-select-props.d.ts.map +1 -0
- package/types/composables/use-select.d.ts +21 -0
- package/types/composables/use-select.d.ts.map +1 -0
- package/types/composables/use-vjsf.d.ts +2 -2
- package/types/composables/use-vjsf.d.ts.map +1 -1
- package/types/iconsets/default-aliases.d.ts +10 -0
- package/types/iconsets/default-aliases.d.ts.map +1 -0
- package/types/iconsets/mdi-svg.d.ts +3 -0
- package/types/iconsets/mdi-svg.d.ts.map +1 -0
- package/types/iconsets/mdi.d.ts +3 -0
- package/types/iconsets/mdi.d.ts.map +1 -0
- package/types/index.d.ts +5 -2
- package/types/index.d.ts.map +1 -1
- package/types/options.d.ts +9 -0
- package/types/options.d.ts.map +1 -0
- package/types/types.d.ts +65 -33
- package/types/types.d.ts.map +1 -1
- package/types/utils/arrays.d.ts +17 -4
- package/types/utils/arrays.d.ts.map +1 -1
- package/types/utils/index.d.ts +0 -3
- package/types/utils/props.d.ts +7 -0
- package/types/utils/props.d.ts.map +1 -1
- package/types/utils/slots.d.ts +8 -0
- package/types/utils/slots.d.ts.map +1 -1
- package/src/compile/index.js +0 -65
- package/src/compile/options.js +0 -19
- package/src/compile/v-jsf-compiled.vue.ejs +0 -61
- package/src/components/options.js +0 -27
- package/src/utils/global-register.js +0 -13
- package/src/utils/index.js +0 -5
- package/src/utils/props.js +0 -107
- package/src/utils/slots.js +0 -18
- package/types/utils/global-register.d.ts +0 -8
- package/types/utils/global-register.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
|
|
3
|
+
"version": "3.0.0",
|
|
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": "npm run build && cp ../README.md README.md && cp ../LICENSE LICENSE"
|
|
9
10
|
},
|
|
10
11
|
"author": "Alban Mouton <alban.mouton@gmail.com>",
|
|
11
12
|
"license": "MIT",
|
|
@@ -34,22 +35,16 @@
|
|
|
34
35
|
"default": "./src/components/*"
|
|
35
36
|
}
|
|
36
37
|
},
|
|
37
|
-
"./
|
|
38
|
+
"./utils/*.js": {
|
|
38
39
|
"import": {
|
|
39
|
-
"types": "./types/
|
|
40
|
-
"default": "./src/
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"./utils": {
|
|
44
|
-
"import": {
|
|
45
|
-
"types": "./types/utils/index.d.ts",
|
|
46
|
-
"default": "./src/utils/index.js"
|
|
40
|
+
"types": "./types/utils/*.d.ts",
|
|
41
|
+
"default": "./src/utils/*.js"
|
|
47
42
|
}
|
|
48
43
|
},
|
|
49
|
-
"./
|
|
44
|
+
"./composables/*.js": {
|
|
50
45
|
"import": {
|
|
51
|
-
"types": "./types/
|
|
52
|
-
"default": "./src/
|
|
46
|
+
"types": "./types/composables/*.d.ts",
|
|
47
|
+
"default": "./src/composables/*.js"
|
|
53
48
|
}
|
|
54
49
|
},
|
|
55
50
|
"./styles/*": {
|
|
@@ -72,19 +67,17 @@
|
|
|
72
67
|
},
|
|
73
68
|
"peerDependencies": {
|
|
74
69
|
"vue": "^3.4.3",
|
|
75
|
-
"vuetify": "^3.
|
|
70
|
+
"vuetify": "^3.6.13"
|
|
76
71
|
},
|
|
77
72
|
"dependencies": {
|
|
78
|
-
"@json-layout/core": "0.
|
|
73
|
+
"@json-layout/core": "0.33.3",
|
|
79
74
|
"@vueuse/core": "^10.5.0",
|
|
80
|
-
"debug": "^4.3.4"
|
|
81
|
-
"ejs": "^3.1.9"
|
|
75
|
+
"debug": "^4.3.4"
|
|
82
76
|
},
|
|
83
77
|
"devDependencies": {
|
|
84
78
|
"@types/debug": "^4.1.8",
|
|
85
|
-
"@types/ejs": "^3.1.2",
|
|
86
79
|
"vitest": "^1.1.1",
|
|
87
|
-
"vue": "^3.
|
|
80
|
+
"vue": "^3.5.6",
|
|
88
81
|
"vue-tsc": "^1.8.27"
|
|
89
82
|
}
|
|
90
83
|
}
|
package/src/compat/v2.js
CHANGED
|
@@ -1,12 +1,78 @@
|
|
|
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 = {}
|
|
@@ -23,7 +89,11 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
23
89
|
}
|
|
24
90
|
|
|
25
91
|
if (schema['x-display']) {
|
|
26
|
-
|
|
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
|
|
27
97
|
delete schema['x-display']
|
|
28
98
|
}
|
|
29
99
|
|
|
@@ -38,18 +108,20 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
38
108
|
}
|
|
39
109
|
|
|
40
110
|
if (schema['x-fromData']) {
|
|
41
|
-
layout.comp = 'select'
|
|
42
|
-
layout.getItems =
|
|
111
|
+
layout.comp = layout.comp ?? 'select'
|
|
112
|
+
layout.getItems = fixExpression(schema['x-fromData'])
|
|
43
113
|
delete schema['x-fromData']
|
|
44
114
|
}
|
|
45
115
|
|
|
116
|
+
if (schema['x-if']) {
|
|
117
|
+
layout.if = fixExpression(schema['x-if'])
|
|
118
|
+
delete schema['x-if']
|
|
119
|
+
}
|
|
120
|
+
|
|
46
121
|
if (schema['x-fromUrl']) {
|
|
47
122
|
/** @type string */
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (expressionMatch[1] !== 'q') url = url.replace(expressionMatch[0], '$' + expressionMatch[0])
|
|
51
|
-
}
|
|
52
|
-
layout.getItems = { url }
|
|
123
|
+
const url = schema['x-fromUrl']
|
|
124
|
+
layout.getItems = { url: fixExpression(url, 'js-tpl') }
|
|
53
125
|
delete schema['x-fromUrl']
|
|
54
126
|
}
|
|
55
127
|
if (layout.getItems && isPartialGetItemsObj(layout.getItems)) {
|
|
@@ -64,6 +136,10 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
64
136
|
delete schema['x-itemsProp']
|
|
65
137
|
}
|
|
66
138
|
|
|
139
|
+
if (schema['x-cols']) {
|
|
140
|
+
layout.cols = schema['x-cols']
|
|
141
|
+
}
|
|
142
|
+
|
|
67
143
|
// compact the layout keyword if possible
|
|
68
144
|
if (Object.keys(layout).length === 1 && 'comp' in layout) {
|
|
69
145
|
schema.layout = layout.comp
|
|
@@ -72,30 +148,51 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
|
72
148
|
}
|
|
73
149
|
}
|
|
74
150
|
|
|
75
|
-
if (schema.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
processFragment(schema.properties[propertyKey])
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (schema.allOf) {
|
|
82
|
-
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)
|
|
83
154
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
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)
|
|
90
178
|
}
|
|
91
179
|
|
|
92
180
|
if (schema.type === 'array' && schema.items) {
|
|
93
181
|
if (Array.isArray(schema.items)) {
|
|
94
|
-
for (const item of schema.items) processFragment(item)
|
|
182
|
+
for (const item of schema.items) processFragment(item, getJSONRef, schemaId, processed)
|
|
95
183
|
} else {
|
|
96
|
-
processFragment(schema.items)
|
|
184
|
+
processFragment(schema.items, getJSONRef, schemaId, processed)
|
|
97
185
|
}
|
|
98
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
|
+
}
|
|
99
196
|
}
|
|
100
197
|
|
|
101
198
|
/**
|
|
@@ -117,7 +214,9 @@ export function v2compat (_schema, _ajv, lang = 'en') {
|
|
|
117
214
|
|
|
118
215
|
const schema = /** @type {import("ajv").SchemaObject} */ (clone(_schema))
|
|
119
216
|
schema.$id = schema.$id ?? '_jl'
|
|
120
|
-
|
|
121
|
-
|
|
217
|
+
const getJSONRef = resolveLocaleRefs(schema, ajv, lang)
|
|
218
|
+
/** @type {any[]} */
|
|
219
|
+
const processed = []
|
|
220
|
+
processFragment(schema, getJSONRef, schema.$id, processed)
|
|
122
221
|
return schema
|
|
123
222
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { isSection } from '@json-layout/core'
|
|
4
|
+
|
|
5
|
+
const { modelValue } = defineProps({
|
|
6
|
+
modelValue: {
|
|
7
|
+
/** @type import('vue').PropType<import('@json-layout/core').StateNode> */
|
|
8
|
+
type: Object,
|
|
9
|
+
required: true
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const pClass = computed(() => {
|
|
14
|
+
if (modelValue.options.density === 'default') return 'mt-1 mb-5'
|
|
15
|
+
if (modelValue.options.density === 'comfortable') return 'mb-4'
|
|
16
|
+
return 'mb-3'
|
|
17
|
+
})
|
|
18
|
+
</script>
|
|
19
|
+
<template>
|
|
20
|
+
<p
|
|
21
|
+
v-if="isSection(modelValue) && modelValue.layout.subtitle"
|
|
22
|
+
:class="`text-subtitle ${pClass}`"
|
|
23
|
+
v-html="modelValue.layout.subtitle"
|
|
24
|
+
/>
|
|
25
|
+
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="vjsf-help-message">
|
|
2
|
+
<div :class="`vjsf-help-message vjsf-help-message-${node.options.density}`">
|
|
3
3
|
<v-slide-x-reverse-transition>
|
|
4
4
|
<v-alert
|
|
5
5
|
v-show="show"
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
</v-slide-x-reverse-transition>
|
|
11
11
|
<v-btn
|
|
12
12
|
color="info"
|
|
13
|
-
class="vjsf-help-message-toggle"
|
|
14
|
-
:icon="show ?
|
|
13
|
+
:class="`vjsf-help-message-toggle vjsf-help-message-toggle-${node.options.density}`"
|
|
14
|
+
:icon="show ? node.options.icons.close : node.options.icons.infoSymbol"
|
|
15
|
+
:border="0"
|
|
16
|
+
:elevation="show ? 0 : 2"
|
|
15
17
|
density="compact"
|
|
18
|
+
:size="node.options.density === 'default' ? 28 : 24"
|
|
16
19
|
:title="show ? '' : node.messages.showHelp"
|
|
17
20
|
@click="show = !show"
|
|
18
21
|
/>
|
|
@@ -20,7 +23,9 @@
|
|
|
20
23
|
</template>
|
|
21
24
|
|
|
22
25
|
<script setup>
|
|
23
|
-
import {
|
|
26
|
+
import { VSlideXReverseTransition } from 'vuetify/components/transitions'
|
|
27
|
+
import { VAlert } from 'vuetify/components/VAlert'
|
|
28
|
+
import { VBtn } from 'vuetify/components/VBtn'
|
|
24
29
|
import { ref } from 'vue'
|
|
25
30
|
|
|
26
31
|
defineProps({
|
|
@@ -37,13 +42,33 @@ const show = ref(false)
|
|
|
37
42
|
<style>
|
|
38
43
|
.vjsf-help-message {
|
|
39
44
|
position: relative;
|
|
40
|
-
|
|
45
|
+
}
|
|
46
|
+
.vjsf-help-message-compact {
|
|
47
|
+
margin-top: 2px;
|
|
48
|
+
margin-bottom: 2px;
|
|
49
|
+
min-height:24px;
|
|
50
|
+
}
|
|
51
|
+
.vjsf-help-message-comfortable {
|
|
52
|
+
margin-top: 4px;
|
|
53
|
+
margin-bottom: 4px;
|
|
54
|
+
min-height:24px;
|
|
55
|
+
}
|
|
56
|
+
.vjsf-help-message-default {
|
|
57
|
+
margin-top: 6px;
|
|
58
|
+
margin-bottom: 6px;
|
|
59
|
+
min-height:28px;
|
|
41
60
|
}
|
|
42
61
|
.vjsf-help-message-toggle {
|
|
43
62
|
position: absolute;
|
|
44
|
-
top:
|
|
45
|
-
right:
|
|
63
|
+
top: 0px;
|
|
64
|
+
right: 0px;
|
|
46
65
|
z-index: 1;
|
|
47
66
|
}
|
|
67
|
+
.vjsf-help-message .v-alert {
|
|
68
|
+
padding-right: 22px;
|
|
69
|
+
}
|
|
70
|
+
.vjsf-help-message-default .v-alert {
|
|
71
|
+
padding-right: 26px;
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
</style>
|
|
49
|
-
../../../types.js
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { VAlert } from 'vuetify/components'
|
|
2
|
+
import { VAlert } from 'vuetify/components/VAlert'
|
|
3
3
|
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const props = defineProps({
|
|
@@ -7,6 +7,10 @@ const props = defineProps({
|
|
|
7
7
|
/** @type import('vue').PropType<import('../../types.js').VjsfNode> */
|
|
8
8
|
type: Object,
|
|
9
9
|
required: true
|
|
10
|
+
},
|
|
11
|
+
hideTitle: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false
|
|
10
14
|
}
|
|
11
15
|
})
|
|
12
16
|
|
|
@@ -27,12 +31,12 @@ const titleClass = computed(() => {
|
|
|
27
31
|
|
|
28
32
|
<template>
|
|
29
33
|
<div
|
|
30
|
-
v-if="node.layout.title
|
|
34
|
+
v-if="(node.layout.title && !hideTitle) || node.layout.subtitle || (node.error && node.validated)"
|
|
31
35
|
:class="`mb-${titleDepthBase - node.options.titleDepth} mt-${titleDepthBase - node.options.titleDepth}`"
|
|
32
36
|
>
|
|
33
37
|
<component
|
|
34
38
|
:is="`h${node.options.titleDepth}`"
|
|
35
|
-
v-if="node.layout.title"
|
|
39
|
+
v-if="node.layout.title && !hideTitle"
|
|
36
40
|
:class="`${titleClass}`"
|
|
37
41
|
>
|
|
38
42
|
{{ node.layout.title }}
|
|
@@ -40,14 +44,12 @@ const titleClass = computed(() => {
|
|
|
40
44
|
<p
|
|
41
45
|
v-if="node.layout.subtitle"
|
|
42
46
|
:class="`text-subtitle mt-${titleDepthBase - node.options.titleDepth}`"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</p>
|
|
47
|
+
v-html="node.layout.subtitle"
|
|
48
|
+
/>
|
|
46
49
|
<v-alert
|
|
47
50
|
v-if="node.error && node.validated"
|
|
48
51
|
type="error"
|
|
49
52
|
:class="`mt-${titleDepthBase - node.options.titleDepth}`"
|
|
50
|
-
:density="node.options.density"
|
|
51
53
|
>
|
|
52
54
|
{{ node.error }}
|
|
53
55
|
</v-alert>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// cf https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/VSelect.tsx#L374
|
|
3
3
|
|
|
4
4
|
import { defineComponent, h, computed } from 'vue'
|
|
5
|
-
import { VIcon } from 'vuetify/components'
|
|
5
|
+
import { VIcon } from 'vuetify/components/VIcon'
|
|
6
6
|
|
|
7
7
|
export default defineComponent({
|
|
8
8
|
props: {
|
|
@@ -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
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
// cf https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/VSelect.tsx#L374
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { VCheckboxBtn } from 'vuetify/components/VCheckbox'
|
|
5
|
+
import { VListItem } from 'vuetify/components/VList'
|
|
5
6
|
import VSelectItemIcon from './select-item-icon.vue'
|
|
6
7
|
|
|
7
8
|
defineProps({
|
|
@@ -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,105 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { VSkeletonLoader } from 'vuetify/components/VSkeletonLoader'
|
|
3
|
+
import { VInput } from 'vuetify/components/VInput'
|
|
4
|
+
import { VLabel } from 'vuetify/components/VLabel'
|
|
5
|
+
import { VCheckbox } from 'vuetify/components/VCheckbox'
|
|
6
|
+
import { VSwitch } from 'vuetify/components/VSwitch'
|
|
7
|
+
import { defineComponent, h, computed, toRef } from 'vue'
|
|
8
|
+
import useField from '../../composables/use-node.js'
|
|
9
|
+
import useGetItems from '../../composables/use-get-items.js'
|
|
10
|
+
|
|
11
|
+
export default defineComponent({
|
|
12
|
+
props: {
|
|
13
|
+
modelValue: {
|
|
14
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfCheckboxGroupNode> */
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
},
|
|
18
|
+
statefulLayout: {
|
|
19
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
|
|
20
|
+
type: Object,
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
type: {
|
|
24
|
+
type: String,
|
|
25
|
+
required: true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
setup (props) {
|
|
29
|
+
const nodeRef = toRef(props, 'modelValue')
|
|
30
|
+
const getItems = useGetItems(nodeRef, props.statefulLayout)
|
|
31
|
+
const { inputProps, compSlots, localData, layout } = useField(nodeRef, props.statefulLayout, { bindData: false })
|
|
32
|
+
|
|
33
|
+
const fieldProps = computed(() => {
|
|
34
|
+
const fieldProps = { ...inputProps.value }
|
|
35
|
+
fieldProps.class.push('v-radio-group') // reuse some styles from radio-group
|
|
36
|
+
fieldProps.class.push('vjsf-selection-group')
|
|
37
|
+
return fieldProps
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const fieldSlots = computed(() => {
|
|
41
|
+
const slots = { ...compSlots.value }
|
|
42
|
+
|
|
43
|
+
if (!slots.default) {
|
|
44
|
+
slots.default = () => {
|
|
45
|
+
/** @type {import('vue').VNode[]} */
|
|
46
|
+
const children = [h(VLabel, { text: fieldProps.value.label })]
|
|
47
|
+
if (getItems.loading.value) {
|
|
48
|
+
children.push(h(VSkeletonLoader, { type: 'chip' }))
|
|
49
|
+
} else {
|
|
50
|
+
/** @type {import('vue').VNode[]} */
|
|
51
|
+
const checkboxes = []
|
|
52
|
+
for (const item of getItems.items.value) {
|
|
53
|
+
let modelValue = false
|
|
54
|
+
if (layout.value.multiple) {
|
|
55
|
+
modelValue = localData.value?.includes(item.value)
|
|
56
|
+
} else {
|
|
57
|
+
modelValue = localData.value === item.value
|
|
58
|
+
}
|
|
59
|
+
checkboxes.push(h(props.type === 'switch' ? VSwitch : VCheckbox, {
|
|
60
|
+
label: item.title,
|
|
61
|
+
hideDetails: true,
|
|
62
|
+
key: item.key,
|
|
63
|
+
modelValue,
|
|
64
|
+
onClick: () => {
|
|
65
|
+
let newValue
|
|
66
|
+
if (layout.value.multiple) {
|
|
67
|
+
newValue = props.modelValue.data ? [...props.modelValue.data] : []
|
|
68
|
+
if (newValue.includes(item.value)) {
|
|
69
|
+
newValue = newValue.filter((/** @type {any} */v) => v !== item.value)
|
|
70
|
+
} else {
|
|
71
|
+
newValue.push(item.value)
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
if (props.modelValue.data === item.value) {
|
|
75
|
+
newValue = undefined
|
|
76
|
+
} else {
|
|
77
|
+
newValue = item.value
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
props.statefulLayout.input(props.modelValue, newValue)
|
|
81
|
+
}
|
|
82
|
+
}))
|
|
83
|
+
}
|
|
84
|
+
children.push(h('div', { class: 'v-selection-control-group' }, checkboxes))
|
|
85
|
+
}
|
|
86
|
+
return children
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return slots
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
return h(VInput, fieldProps.value, fieldSlots.value)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
.vjsf-selection-group .v-selection-control-group>.v-input .v-selection-control {
|
|
103
|
+
min-height: auto;
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { VMenu
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { VMenu } from 'vuetify/components/VMenu'
|
|
3
|
+
import { VTextField } from 'vuetify/components/VTextField'
|
|
4
|
+
import { computed, ref, toRef } from 'vue'
|
|
5
|
+
import useField from '../../composables/use-node.js'
|
|
5
6
|
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
modelValue: {
|
|
@@ -21,21 +22,29 @@ const props = defineProps({
|
|
|
21
22
|
}
|
|
22
23
|
})
|
|
23
24
|
|
|
25
|
+
const { inputProps, skeleton, compProps, data } = useField(
|
|
26
|
+
toRef(props, 'modelValue'), props.statefulLayout, { isMainComp: false, bindData: false }
|
|
27
|
+
)
|
|
28
|
+
|
|
24
29
|
const fieldProps = computed(() => {
|
|
25
|
-
const fieldProps =
|
|
30
|
+
const fieldProps = { ...inputProps.value }
|
|
26
31
|
fieldProps.readonly = true
|
|
32
|
+
fieldProps.clearable = fieldProps.clearable ?? !skeleton.value.required
|
|
33
|
+
fieldProps['onClick:clear'] = () => {
|
|
34
|
+
props.statefulLayout.input(props.modelValue, null)
|
|
35
|
+
}
|
|
27
36
|
return fieldProps
|
|
28
37
|
})
|
|
29
38
|
|
|
30
39
|
const menuProps = computed(() => {
|
|
31
|
-
const menuProps =
|
|
40
|
+
const menuProps = { ...compProps.value }
|
|
32
41
|
menuProps.closeOnContentClick = false
|
|
33
42
|
menuProps.disabled = true
|
|
34
43
|
return menuProps
|
|
35
44
|
})
|
|
36
45
|
|
|
37
46
|
const textField = ref(null)
|
|
38
|
-
const menuOpened =
|
|
47
|
+
const menuOpened = defineModel('menuOpened', { type: Boolean, default: false })
|
|
39
48
|
|
|
40
49
|
</script>
|
|
41
50
|
|
|
@@ -43,7 +52,7 @@ const menuOpened = ref(false)
|
|
|
43
52
|
<v-text-field
|
|
44
53
|
ref="textField"
|
|
45
54
|
v-bind="fieldProps"
|
|
46
|
-
:model-value="formattedValue ??
|
|
55
|
+
:model-value="formattedValue ?? data"
|
|
47
56
|
@click:control="e => {menuOpened = !menuOpened; e.stopPropagation()}"
|
|
48
57
|
>
|
|
49
58
|
<template #prepend-inner>
|