@koumoul/vjsf 3.0.0-beta.4 → 3.0.0-beta.40
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/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 +104 -0
- package/src/components/fragments/text-field-menu.vue +8 -3
- 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 +4 -1
- package/src/components/nodes/color-picker.vue +6 -2
- package/src/components/nodes/combobox.vue +4 -1
- package/src/components/nodes/date-picker.vue +19 -10
- package/src/components/nodes/date-time-picker.vue +80 -3
- package/src/components/nodes/expansion-panels.vue +28 -12
- package/src/components/nodes/file-input.vue +4 -1
- package/src/components/nodes/list.vue +239 -104
- package/src/components/nodes/number-combobox.vue +4 -1
- package/src/components/nodes/number-field.vue +4 -1
- package/src/components/nodes/one-of-select.vue +46 -24
- package/src/components/nodes/radio-group.vue +55 -0
- package/src/components/nodes/section.vue +4 -1
- package/src/components/nodes/select.vue +13 -52
- package/src/components/nodes/slider.vue +4 -1
- package/src/components/nodes/stepper.vue +10 -2
- package/src/components/nodes/switch-group.vue +39 -0
- package/src/components/nodes/switch.vue +4 -1
- package/src/components/nodes/tabs.vue +18 -5
- package/src/components/nodes/text-field.vue +4 -1
- package/src/components/nodes/textarea.vue +4 -1
- package/src/components/nodes/time-picker.vue +38 -1
- package/src/components/nodes/vertical-tabs.vue +14 -3
- package/src/components/options.js +21 -4
- 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 +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 -7
- package/src/utils/arrays.js +37 -6
- package/src/utils/build.js +1 -1
- package/src/utils/index.js +0 -1
- package/src/utils/props.js +36 -12
- package/src/utils/slots.js +28 -0
- package/types/compat/v2.d.ts +10 -0
- package/types/compat/v2.d.ts.map +1 -0
- package/types/compile/index.d.ts +8 -0
- package/types/compile/index.d.ts.map +1 -0
- package/types/compile/options.d.ts +4 -0
- package/types/compile/options.d.ts.map +1 -0
- 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 +8 -0
- package/types/components/fragments/help-message.vue.d.ts.map +1 -0
- package/types/components/fragments/node-slot.vue.d.ts +47 -0
- package/types/components/fragments/node-slot.vue.d.ts.map +1 -0
- package/types/components/fragments/section-header.vue.d.ts +10 -0
- package/types/components/fragments/section-header.vue.d.ts.map +1 -0
- package/types/components/fragments/select-item-icon.vue.d.ts +15 -0
- package/types/components/fragments/select-item-icon.vue.d.ts.map +1 -0
- package/types/components/fragments/select-item.vue.d.ts +12 -0
- package/types/components/fragments/select-item.vue.d.ts.map +1 -0
- package/types/components/fragments/select-selection.vue.d.ts +12 -0
- package/types/components/fragments/select-selection.vue.d.ts.map +1 -0
- 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 +20 -0
- package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -0
- package/types/components/node.vue.d.ts +10 -0
- package/types/components/node.vue.d.ts.map +1 -0
- package/types/components/nodes/autocomplete.vue.d.ts +27 -0
- package/types/components/nodes/autocomplete.vue.d.ts.map +1 -0
- 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 +27 -0
- package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
- package/types/components/nodes/checkbox.vue.d.ts +10 -0
- package/types/components/nodes/checkbox.vue.d.ts.map +1 -0
- package/types/components/nodes/color-picker.vue.d.ts +10 -0
- package/types/components/nodes/color-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/combobox.vue.d.ts +27 -0
- package/types/components/nodes/combobox.vue.d.ts.map +1 -0
- package/types/components/nodes/date-picker.vue.d.ts +10 -0
- package/types/components/nodes/date-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/date-time-picker.vue.d.ts +10 -0
- package/types/components/nodes/date-time-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/expansion-panels.vue.d.ts +10 -0
- package/types/components/nodes/expansion-panels.vue.d.ts.map +1 -0
- package/types/components/nodes/file-input.vue.d.ts +27 -0
- package/types/components/nodes/file-input.vue.d.ts.map +1 -0
- package/types/components/nodes/list.vue.d.ts +10 -0
- package/types/components/nodes/list.vue.d.ts.map +1 -0
- package/types/components/nodes/number-combobox.vue.d.ts +27 -0
- package/types/components/nodes/number-combobox.vue.d.ts.map +1 -0
- package/types/components/nodes/number-field.vue.d.ts +27 -0
- package/types/components/nodes/number-field.vue.d.ts.map +1 -0
- package/types/components/nodes/one-of-select.vue.d.ts +10 -0
- package/types/components/nodes/one-of-select.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/section.vue.d.ts +10 -0
- package/types/components/nodes/section.vue.d.ts.map +1 -0
- package/types/components/nodes/select.vue.d.ts +27 -0
- package/types/components/nodes/select.vue.d.ts.map +1 -0
- package/types/components/nodes/slider.vue.d.ts +10 -0
- package/types/components/nodes/slider.vue.d.ts.map +1 -0
- package/types/components/nodes/stepper.vue.d.ts +10 -0
- package/types/components/nodes/stepper.vue.d.ts.map +1 -0
- 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/switch.vue.d.ts +10 -0
- package/types/components/nodes/switch.vue.d.ts.map +1 -0
- package/types/components/nodes/tabs.vue.d.ts +10 -0
- package/types/components/nodes/tabs.vue.d.ts.map +1 -0
- package/types/components/nodes/text-field.vue.d.ts +27 -0
- package/types/components/nodes/text-field.vue.d.ts.map +1 -0
- package/types/components/nodes/textarea.vue.d.ts +27 -0
- package/types/components/nodes/textarea.vue.d.ts.map +1 -0
- package/types/components/nodes/time-picker.vue.d.ts +10 -0
- package/types/components/nodes/time-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/vertical-tabs.vue.d.ts +10 -0
- package/types/components/nodes/vertical-tabs.vue.d.ts.map +1 -0
- package/types/components/options.d.ts +4 -0
- package/types/components/options.d.ts.map +1 -0
- package/types/components/tree.vue.d.ts +10 -0
- package/types/components/tree.vue.d.ts.map +1 -0
- package/types/components/vjsf.vue.d.ts +15 -0
- package/types/components/vjsf.vue.d.ts.map +1 -0
- 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 +25 -0
- package/types/composables/use-dnd.d.ts.map +1 -0
- 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 +16 -0
- package/types/composables/use-vjsf.d.ts.map +1 -0
- package/types/index.d.ts +7 -0
- package/types/index.d.ts.map +1 -0
- package/types/types.d.ts +96 -0
- package/types/types.d.ts.map +1 -0
- package/types/utils/arrays.d.ts +22 -0
- package/types/utils/arrays.d.ts.map +1 -0
- package/types/utils/build.d.ts +2 -0
- package/types/utils/build.d.ts.map +1 -0
- package/types/utils/dates.d.ts +7 -0
- package/types/utils/dates.d.ts.map +1 -0
- package/types/utils/index.d.ts +5 -0
- package/types/utils/index.d.ts.map +1 -0
- package/types/utils/props.d.ts +29 -0
- package/types/utils/props.d.ts.map +1 -0
- package/types/utils/slots.d.ts +15 -0
- package/types/utils/slots.d.ts.map +1 -0
- package/src/utils/global-register.js +0 -13
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.40",
|
|
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.13"
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
78
|
-
"@json-layout/core": "0.
|
|
79
|
+
"@json-layout/core": "0.31.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,
|
|
@@ -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 ? 'mdi-close
|
|
13
|
+
:class="`vjsf-help-message-toggle vjsf-help-message-toggle-${node.options.density}`"
|
|
14
|
+
:icon="show ? 'mdi-close' : 'mdi-information-symbol'"
|
|
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,104 @@
|
|
|
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 } from 'vue'
|
|
8
|
+
import { getInputProps, getCompSlots } from '../../utils/index.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 getItems = useGetItems(props)
|
|
30
|
+
|
|
31
|
+
const fieldProps = computed(() => {
|
|
32
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
33
|
+
fieldProps.class.push('v-radio-group') // reuse some styles from radio-group
|
|
34
|
+
fieldProps.class.push('vjsf-selection-group')
|
|
35
|
+
return fieldProps
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const fieldSlots = computed(() => {
|
|
39
|
+
const slots = getCompSlots(props.modelValue, props.statefulLayout)
|
|
40
|
+
|
|
41
|
+
if (!slots.default) {
|
|
42
|
+
slots.default = () => {
|
|
43
|
+
/** @type {import('vue').VNode[]} */
|
|
44
|
+
const children = [h(VLabel, { text: fieldProps.value.label })]
|
|
45
|
+
if (getItems.loading.value) {
|
|
46
|
+
children.push(h(VSkeletonLoader, { type: 'chip' }))
|
|
47
|
+
} else {
|
|
48
|
+
/** @type {import('vue').VNode[]} */
|
|
49
|
+
const checkboxes = []
|
|
50
|
+
for (const item of getItems.items.value) {
|
|
51
|
+
let modelValue = false
|
|
52
|
+
if (props.modelValue.layout.multiple) {
|
|
53
|
+
modelValue = props.modelValue.data?.includes(item.value)
|
|
54
|
+
} else {
|
|
55
|
+
modelValue = props.modelValue.data === item.value
|
|
56
|
+
}
|
|
57
|
+
checkboxes.push(h(props.type === 'switch' ? VSwitch : VCheckbox, {
|
|
58
|
+
label: item.title,
|
|
59
|
+
hideDetails: true,
|
|
60
|
+
key: item.key,
|
|
61
|
+
modelValue,
|
|
62
|
+
onClick: () => {
|
|
63
|
+
let newValue
|
|
64
|
+
if (props.modelValue.layout.multiple) {
|
|
65
|
+
newValue = props.modelValue.data ? [...props.modelValue.data] : []
|
|
66
|
+
if (newValue.includes(item.value)) {
|
|
67
|
+
newValue = newValue.filter((/** @type {any} */v) => v !== item.value)
|
|
68
|
+
} else {
|
|
69
|
+
newValue.push(item.value)
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
if (props.modelValue.data === item.value) {
|
|
73
|
+
newValue = undefined
|
|
74
|
+
} else {
|
|
75
|
+
newValue = item.value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
props.statefulLayout.input(props.modelValue, newValue)
|
|
79
|
+
}
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
children.push(h('div', { class: 'v-selection-control-group' }, checkboxes))
|
|
83
|
+
}
|
|
84
|
+
return children
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return slots
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
return () => {
|
|
93
|
+
return h(VInput, fieldProps.value, fieldSlots.value)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<style>
|
|
101
|
+
.vjsf-selection-group .v-selection-control-group>.v-input .v-selection-control {
|
|
102
|
+
min-height: auto;
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { VMenu
|
|
2
|
+
import { VMenu } from 'vuetify/components/VMenu'
|
|
3
|
+
import { VTextField } from 'vuetify/components/VTextField'
|
|
3
4
|
import { computed, ref } from 'vue'
|
|
4
5
|
import { getCompProps, getInputProps } from '../../utils/index.js'
|
|
5
6
|
|
|
@@ -24,18 +25,22 @@ const props = defineProps({
|
|
|
24
25
|
const fieldProps = computed(() => {
|
|
25
26
|
const fieldProps = getInputProps(props.modelValue, props.statefulLayout, [], false)
|
|
26
27
|
fieldProps.readonly = true
|
|
28
|
+
fieldProps.clearable = fieldProps.clearable ?? !props.modelValue.skeleton.required
|
|
29
|
+
fieldProps['onClick:clear'] = () => {
|
|
30
|
+
props.statefulLayout.input(props.modelValue, null)
|
|
31
|
+
}
|
|
27
32
|
return fieldProps
|
|
28
33
|
})
|
|
29
34
|
|
|
30
35
|
const menuProps = computed(() => {
|
|
31
|
-
const menuProps = getCompProps(props.modelValue
|
|
36
|
+
const menuProps = getCompProps(props.modelValue)
|
|
32
37
|
menuProps.closeOnContentClick = false
|
|
33
38
|
menuProps.disabled = true
|
|
34
39
|
return menuProps
|
|
35
40
|
})
|
|
36
41
|
|
|
37
42
|
const textField = ref(null)
|
|
38
|
-
const menuOpened =
|
|
43
|
+
const menuOpened = defineModel('menuOpened', { type: Boolean, default: false })
|
|
39
44
|
|
|
40
45
|
</script>
|
|
41
46
|
|