@koumoul/vjsf 2.22.0 → 3.0.0-alpha.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/package.json +50 -86
- package/src/compat/v2.js +82 -0
- package/src/compile/index.js +32 -0
- package/src/compile/v-jsf-compiled.vue.ejs +83 -0
- package/src/components/fragments/node-slot.vue +49 -0
- package/src/components/fragments/section-header.vue +52 -0
- package/src/components/fragments/text-field-menu.vue +68 -0
- package/src/components/node.vue +60 -0
- package/src/components/nodes/checkbox.vue +27 -0
- package/src/components/nodes/color-picker.vue +43 -0
- package/src/components/nodes/date-picker.vue +45 -0
- package/src/components/nodes/date-time-picker.vue +20 -0
- package/src/components/nodes/expansion-panels.vue +52 -0
- package/src/components/nodes/list.vue +112 -0
- package/src/components/nodes/number-field.vue +35 -0
- package/src/components/nodes/one-of-select.vue +56 -0
- package/src/components/nodes/section.vue +30 -0
- package/src/components/nodes/select.vue +59 -0
- package/src/components/nodes/slider.vue +34 -0
- package/src/components/nodes/switch.vue +29 -0
- package/src/components/nodes/tabs.vue +63 -0
- package/src/components/nodes/text-field.vue +29 -0
- package/src/components/nodes/textarea.vue +29 -0
- package/src/components/nodes/time-picker.vue +7 -0
- package/src/components/nodes/vertical-tabs.vue +70 -0
- package/src/components/options.js +25 -0
- package/src/components/tree.vue +25 -0
- package/src/components/types.ts +59 -0
- package/src/components/vjsf.vue +168 -0
- package/src/index.js +2 -0
- package/src/utils/clone.js +3 -0
- package/src/utils/dates.js +52 -0
- package/src/utils/props.js +79 -0
- package/src/utils/slots.js +19 -0
- package/types/compat/v2.d.ts +10 -0
- package/types/compat/v2.d.ts.map +1 -0
- package/types/compile/index.d.ts +7 -0
- package/types/compile/index.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 +8 -0
- package/types/components/fragments/section-header.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/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/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/list.vue.d.ts +10 -0
- package/types/components/nodes/list.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/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 +10 -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/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 copy.vue.d.ts +10 -0
- package/types/components/nodes/text-field copy.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 +3 -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 +3 -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/types.d.ts +71 -0
- package/types/components/types.d.ts.map +1 -0
- package/types/components/v-jsf.vue.d.ts +13 -0
- package/types/components/v-jsf.vue.d.ts.map +1 -0
- package/types/components/vjsf.vue.d.ts +13 -0
- package/types/components/vjsf.vue.d.ts.map +1 -0
- package/types/index.d.ts +3 -0
- package/types/index.d.ts.map +1 -0
- package/types/utils/clone.d.ts +3 -0
- package/types/utils/clone.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/props.d.ts +20 -0
- package/types/utils/props.d.ts.map +1 -0
- package/types/utils/slots.d.ts +7 -0
- package/types/utils/slots.d.ts.map +1 -0
- package/.eslintignore +0 -9
- package/.eslintrc.js +0 -38
- package/.github/workflows/scrape-doc.yml +0 -14
- package/.nvmrc +0 -1
- package/CONTRIBUTE.md +0 -61
- package/FUNDING.yml +0 -1
- package/README.md +0 -19
- package/babel.config.js +0 -4
- package/dist/main.css +0 -63
- package/dist/main.js +0 -2
- package/dist/main.js.LICENSE.txt +0 -10
- package/dist/third-party.js +0 -8
- package/dist/third-party.js.LICENSE.txt +0 -8
- package/lib/VJsf.css +0 -63
- package/lib/VJsf.js +0 -117
- package/lib/VJsfNoDeps.js +0 -517
- package/lib/deps/third-party.js +0 -16
- package/lib/mixins/ColorProperty.js +0 -45
- package/lib/mixins/DateProperty.js +0 -170
- package/lib/mixins/Dependent.js +0 -69
- package/lib/mixins/EditableArray.js +0 -418
- package/lib/mixins/FileProperty.js +0 -81
- package/lib/mixins/MarkdownEditor.js +0 -183
- package/lib/mixins/ObjectContainer.js +0 -351
- package/lib/mixins/SelectProperty.js +0 -400
- package/lib/mixins/SimpleProperty.js +0 -165
- package/lib/mixins/Tooltip.js +0 -42
- package/lib/mixins/Validatable.js +0 -119
- package/lib/utils/expr-eval-parser.js +0 -21
- package/lib/utils/is-cyclic.js +0 -34
- package/lib/utils/json-refs.js +0 -209
- package/lib/utils/options.js +0 -328
- package/lib/utils/rules.js +0 -81
- package/lib/utils/schema.js +0 -100
- package/lib/utils/select.js +0 -141
- package/webpack.config.js +0 -46
package/package.json
CHANGED
|
@@ -1,99 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koumoul/vjsf",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.0",
|
|
4
4
|
"description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
|
|
5
|
-
"main": "dist/main.js",
|
|
6
5
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"build": "
|
|
10
|
-
"prepare": "npm run lint && npm test && npm run build && npm run doc-build",
|
|
11
|
-
"postpublish": "gh-pages-multi deploy -v -s doc/dist -t latest",
|
|
12
|
-
"test": "jest",
|
|
13
|
-
"test-watch": "jest --watch",
|
|
14
|
-
"test-update": "jest --updateSnapshot",
|
|
15
|
-
"doc-build": "(cd doc && TARGET=https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/ nuxt generate)",
|
|
16
|
-
"doc-master": "(cd doc && TARGET=https://koumoul-dev.github.io/vuetify-jsonschema-form/master/ nuxt generate) && gh-pages-multi deploy -v -s doc/dist -t master",
|
|
17
|
-
"analyze": "webpack --profile --json > dist/stats.json && webpack-bundle-analyzer dist/stats.json"
|
|
18
|
-
},
|
|
19
|
-
"jest": {
|
|
20
|
-
"moduleFileExtensions": [
|
|
21
|
-
"js",
|
|
22
|
-
"json",
|
|
23
|
-
"vue"
|
|
24
|
-
],
|
|
25
|
-
"transform": {
|
|
26
|
-
".*\\.(vue)$": "vue-jest",
|
|
27
|
-
"^.+\\.js$": "<rootDir>/node_modules/babel-jest"
|
|
28
|
-
},
|
|
29
|
-
"collectCoverage": true,
|
|
30
|
-
"collectCoverageFrom": [
|
|
31
|
-
"lib/**/*.{js,vue}"
|
|
32
|
-
],
|
|
33
|
-
"snapshotSerializers": [
|
|
34
|
-
"jest-serializer-vue"
|
|
35
|
-
],
|
|
36
|
-
"testEnvironment": "jsdom"
|
|
37
|
-
},
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "git+https://github.com/koumoul-dev/vuetify-jsonschema-form.git"
|
|
6
|
+
"test": "vitest",
|
|
7
|
+
"build": "vue-tsc",
|
|
8
|
+
"watch:build": "vue-tsc --watch"
|
|
41
9
|
},
|
|
42
10
|
"author": "Alban Mouton <alban.mouton@gmail.com>",
|
|
43
11
|
"license": "MIT",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
12
|
+
"files": [
|
|
13
|
+
"types",
|
|
14
|
+
"src",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"default": "./src/index.js",
|
|
22
|
+
"types": "./types/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"./components/*": {
|
|
26
|
+
"import": {
|
|
27
|
+
"default": "./src/components/*",
|
|
28
|
+
"types": "./types/components/*.d.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"./compile": {
|
|
32
|
+
"import": {
|
|
33
|
+
"default": "./src/compile/index.js",
|
|
34
|
+
"types": "./types/compile/index.d.ts"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"./compat/v2": {
|
|
38
|
+
"import": {
|
|
39
|
+
"default": "./src/compat/v2.js",
|
|
40
|
+
"types": "./types/compat/v2.d.ts"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
66
43
|
},
|
|
67
44
|
"peerDependencies": {
|
|
68
|
-
"
|
|
45
|
+
"vue": "^3.3.4",
|
|
46
|
+
"vuetify": "^3.3.20"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@json-layout/core": "0.1.0",
|
|
50
|
+
"@json-layout/vocabulary": "0.1.0",
|
|
51
|
+
"@vueuse/core": "^10.5.0",
|
|
52
|
+
"debug": "^4.3.4",
|
|
53
|
+
"ejs": "^3.1.9",
|
|
54
|
+
"rfdc": "^1.3.0"
|
|
69
55
|
},
|
|
70
56
|
"devDependencies": {
|
|
71
|
-
"@
|
|
72
|
-
"@
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"@vue/test-utils": "^1.3.0",
|
|
77
|
-
"babel-eslint": "^10.1.0",
|
|
78
|
-
"babel-jest": "^27.5.1",
|
|
79
|
-
"babel-loader": "^8.2.4",
|
|
80
|
-
"eslint": "^6.1.0",
|
|
81
|
-
"eslint-config-standard": "^13.0.1",
|
|
82
|
-
"eslint-plugin-import": "^2.18.2",
|
|
83
|
-
"eslint-plugin-jest": "^23.1.1",
|
|
84
|
-
"eslint-plugin-node": "^9.1.0",
|
|
85
|
-
"eslint-plugin-promise": "^4.2.1",
|
|
86
|
-
"eslint-plugin-standard": "^4.0.0",
|
|
87
|
-
"eslint-plugin-vue": "^7.20.0",
|
|
88
|
-
"jest": "^27.5.1",
|
|
89
|
-
"jest-serializer-vue": "^2.0.2",
|
|
90
|
-
"random-words": "^1.1.2",
|
|
91
|
-
"v-mask": "^2.3.0",
|
|
92
|
-
"vue-axios": "^2.1.5",
|
|
93
|
-
"vue-jest": "^3.0.7",
|
|
94
|
-
"vue-loader": "^15.9.8",
|
|
95
|
-
"webpack": "^5.72.0",
|
|
96
|
-
"webpack-bundle-analyzer": "^4.5.0",
|
|
97
|
-
"webpack-cli": "^4.9.2"
|
|
57
|
+
"@types/debug": "^4.1.8",
|
|
58
|
+
"@types/ejs": "^3.1.2",
|
|
59
|
+
"vitest": "^0.34.5",
|
|
60
|
+
"vue": "^3.3.4",
|
|
61
|
+
"vue-tsc": "^1.8.15"
|
|
98
62
|
}
|
|
99
63
|
}
|
package/src/compat/v2.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import ajvModule from 'ajv'
|
|
2
|
+
import rfdc from 'rfdc'
|
|
3
|
+
import addFormats from 'ajv-formats'
|
|
4
|
+
import { resolveRefs } from '@json-layout/core'
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
const Ajv = /** @type {typeof ajvModule.default} */ (ajvModule)
|
|
8
|
+
|
|
9
|
+
const clone = rfdc()
|
|
10
|
+
|
|
11
|
+
const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
|
|
12
|
+
if (!schema.layout) {
|
|
13
|
+
schema.layout = {}
|
|
14
|
+
if (schema['x-display']) {
|
|
15
|
+
schema.layout.comp = schema['x-display']
|
|
16
|
+
delete schema['x-display']
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (schema.format === 'hexcolor') {
|
|
20
|
+
schema.layout.comp = 'color-picker'
|
|
21
|
+
delete schema.format
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (schema['x-props']) {
|
|
25
|
+
schema.layout.props = schema['x-props']
|
|
26
|
+
delete schema['x-props']
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (Object.keys(schema.layout).length === 1 && 'comp' in schema.layout) {
|
|
30
|
+
schema.layout = schema.layout.comp
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (schema.type === 'object') {
|
|
35
|
+
if (schema.properties) {
|
|
36
|
+
for (const propertyKey of Object.keys(schema.properties)) {
|
|
37
|
+
processFragment(schema.properties[propertyKey])
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (schema.allOf) {
|
|
41
|
+
for (const item of schema.allOf) processFragment(item)
|
|
42
|
+
}
|
|
43
|
+
if (schema.oneOf) {
|
|
44
|
+
for (const item of schema.oneOf) processFragment(item)
|
|
45
|
+
}
|
|
46
|
+
if (schema.anyOf) {
|
|
47
|
+
for (const item of schema.anyOf) processFragment(item)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (schema.type === 'array' && schema.items) {
|
|
52
|
+
if (Array.isArray(schema.items)) {
|
|
53
|
+
for (const item of schema.items) processFragment(item)
|
|
54
|
+
} else {
|
|
55
|
+
processFragment(schema.items)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
*
|
|
62
|
+
* @param {object} _schema
|
|
63
|
+
* @param {import("ajv").default} [_ajv]
|
|
64
|
+
* @param {string} lang
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
export function v2compat (_schema, _ajv, lang = 'en') {
|
|
68
|
+
let ajv = _ajv
|
|
69
|
+
if (!ajv) {
|
|
70
|
+
/** @type {import('ajv').Options} */
|
|
71
|
+
const ajvOpts = { strict: false, allErrors: true }
|
|
72
|
+
const newAjv = new Ajv(ajvOpts)
|
|
73
|
+
addFormats.default(newAjv)
|
|
74
|
+
ajv = newAjv
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const schema = /** @type {import("ajv").SchemaObject} */ (clone(_schema))
|
|
78
|
+
schema.$id = schema.$id ?? '_jl'
|
|
79
|
+
resolveRefs(schema, ajv, lang)
|
|
80
|
+
processFragment(schema)
|
|
81
|
+
return schema
|
|
82
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
2
|
+
import { fileURLToPath } from 'url'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import ejs from 'ejs'
|
|
5
|
+
import { compile as compileLayout } from '@json-layout/core'
|
|
6
|
+
import { serialize as serializeCompiledLayout } from '@json-layout/core/src/compile/serialize'
|
|
7
|
+
import { isCompObject, isSwitchStruct } from '@json-layout/vocabulary'
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
|
|
11
|
+
const template = readFileSync(path.join(__dirname, 'v-jsf-compiled.vue.ejs'), 'utf8')
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} schema
|
|
15
|
+
* @param {string} baseImport
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function compile (schema, baseImport = '@koumoul/vjsf/components') {
|
|
19
|
+
const compiledLayout = compileLayout(schema, { code: true })
|
|
20
|
+
const compiledLayoutCode = serializeCompiledLayout(compiledLayout)
|
|
21
|
+
/** @type Set<string> */
|
|
22
|
+
const comps = new Set([])
|
|
23
|
+
for (const layout of Object.values(compiledLayout.normalizedLayouts)) {
|
|
24
|
+
if (isCompObject(layout)) comps.add(layout.comp)
|
|
25
|
+
if (isSwitchStruct(layout)) {
|
|
26
|
+
for (const switchCase of layout.switch) comps.add(switchCase.comp)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
comps.delete('none')
|
|
30
|
+
const code = ejs.render(template, { compiledLayoutCode, comps, baseImport })
|
|
31
|
+
return code
|
|
32
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import { StatefulLayout } from '@json-layout/core'
|
|
5
|
+
import { ref, shallowRef, getCurrentInstance, useSlots } from 'vue'
|
|
6
|
+
import { useElementSize } from '@vueuse/core'
|
|
7
|
+
|
|
8
|
+
import { defaultOptions } from '<%- baseImport %>/options.js'
|
|
9
|
+
import Tree from '<%- baseImport %>/tree.vue'
|
|
10
|
+
<% comps.forEach(function(comp){ %>
|
|
11
|
+
import <%= comp.replace(/-/g, '') %>Node from '<%- baseImport %>/nodes/<%= comp %>.vue'
|
|
12
|
+
<% }); %>
|
|
13
|
+
|
|
14
|
+
<%- compiledLayoutCode %>
|
|
15
|
+
|
|
16
|
+
const vueInstance = getCurrentInstance()
|
|
17
|
+
<% comps.forEach(function(comp){ %>
|
|
18
|
+
if (!vueInstance?.appContext.app.component('vjsf-node-<%= comp %>')) {
|
|
19
|
+
vueInstance.appContext.app.component('vjsf-node-<%= comp %>', <%= comp.replace(/-/g, '') %>Node)
|
|
20
|
+
}
|
|
21
|
+
<% }); %>
|
|
22
|
+
|
|
23
|
+
const props = defineProps(['modelValue', 'options'])
|
|
24
|
+
const emit = defineEmits(['update:modelValue', 'update:state'])
|
|
25
|
+
|
|
26
|
+
const statefulLayout = shallowRef(null)
|
|
27
|
+
const stateTree = shallowRef(null)
|
|
28
|
+
|
|
29
|
+
const el = ref(null)
|
|
30
|
+
const { width } = useElementSize(el)
|
|
31
|
+
|
|
32
|
+
const slots = useSlots()
|
|
33
|
+
|
|
34
|
+
const fullOptions = computed(() => {
|
|
35
|
+
if (!width.value) return null
|
|
36
|
+
return {
|
|
37
|
+
...defaultOptions,
|
|
38
|
+
...props.options,
|
|
39
|
+
context: props.options.context ? JSON.parse(JSON.stringify(props.options.context)) : {},
|
|
40
|
+
width: Math.round(width.value),
|
|
41
|
+
vjsfSlots: { ...slots }
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const initStatefulLayout = () => {
|
|
46
|
+
if (!fullOptions.value) return
|
|
47
|
+
const _statefulLayout = new StatefulLayout(compiledLayout, compiledLayout.skeletonTree, fullOptions.value, props.modelValue)
|
|
48
|
+
statefulLayout.value = _statefulLayout
|
|
49
|
+
stateTree.value = _statefulLayout.stateTree
|
|
50
|
+
_statefulLayout.events.on('update', () => {
|
|
51
|
+
stateTree.value = _statefulLayout.stateTree
|
|
52
|
+
emit('update:modelValue', _statefulLayout.data)
|
|
53
|
+
emit('update:state', _statefulLayout)
|
|
54
|
+
})
|
|
55
|
+
emit('update:state', _statefulLayout)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
watch(fullOptions, (newOptions) => {
|
|
59
|
+
if (!newOptions) {
|
|
60
|
+
statefulLayout.value = null
|
|
61
|
+
} else if (statefulLayout.value) {
|
|
62
|
+
statefulLayout.value.options = newOptions
|
|
63
|
+
} else {
|
|
64
|
+
initStatefulLayout()
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// case where data is updated from outside
|
|
69
|
+
watch(() => props.modelValue, (newData) => {
|
|
70
|
+
if (statefulLayout.value && statefulLayout.value.data !== newData) statefulLayout.value.data = newData
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div ref="el">
|
|
77
|
+
<tree
|
|
78
|
+
v-if="statefulLayout && stateTree"
|
|
79
|
+
:model-value="stateTree"
|
|
80
|
+
:stateful-layout="statefulLayout"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import { isTextSlot, isMarkdownSlot, isNameSlot } from '@json-layout/vocabulary'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
props: {
|
|
9
|
+
layoutSlot: {
|
|
10
|
+
/** @type import('vue').PropType<import('@json-layout/vocabulary').Slot> */
|
|
11
|
+
type: Object,
|
|
12
|
+
required: true
|
|
13
|
+
},
|
|
14
|
+
node: {
|
|
15
|
+
/** @type import('vue').PropType<import('../types.js').VjsfNode> */
|
|
16
|
+
type: Object,
|
|
17
|
+
required: true
|
|
18
|
+
},
|
|
19
|
+
statefulLayout: {
|
|
20
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
21
|
+
type: Object,
|
|
22
|
+
required: true
|
|
23
|
+
},
|
|
24
|
+
tag: {
|
|
25
|
+
/** @type import('vue').PropType<string> */
|
|
26
|
+
type: String,
|
|
27
|
+
default: null
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
render () {
|
|
31
|
+
const renderTag = this.tag ?? (isTextSlot(this.layoutSlot) ? 'p' : 'div')
|
|
32
|
+
|
|
33
|
+
if (isTextSlot(this.layoutSlot)) {
|
|
34
|
+
return h(renderTag, this.layoutSlot.text)
|
|
35
|
+
}
|
|
36
|
+
if (isMarkdownSlot(this.layoutSlot)) {
|
|
37
|
+
return h(renderTag, { innerHTML: this.layoutSlot.markdown })
|
|
38
|
+
}
|
|
39
|
+
if (isNameSlot(this.layoutSlot)) {
|
|
40
|
+
if (!this.statefulLayout.options.vjsfSlots[this.layoutSlot.name]) {
|
|
41
|
+
console.error(`layout references a code slot "${this.layoutSlot.name}" that was not provided.`)
|
|
42
|
+
} else {
|
|
43
|
+
return h(renderTag, this.statefulLayout.options.vjsfSlots[this.layoutSlot.name]({ node: this.node, statefulLayout: this.statefulLayout }))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VAlert } from 'vuetify/components'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
node: {
|
|
7
|
+
/** @type import('vue').PropType<import('../types.js').VjsfNode> */
|
|
8
|
+
type: Object,
|
|
9
|
+
required: true
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const titleDepthBase = computed(() => {
|
|
14
|
+
if (props.node.options.density === 'compact') return 6
|
|
15
|
+
if (props.node.options.density === 'comfortable') return 7
|
|
16
|
+
return 8
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const classes = ['text-h1', 'text-h2', 'text-h3', 'text-h4', 'text-h5', 'text-h6', 'text-subtitle-1', 'text-subtitle-2']
|
|
20
|
+
const titleClass = computed(() => {
|
|
21
|
+
const index = props.node.options.titleDepth
|
|
22
|
+
if (props.node.options.density === 'compact') return classes[index + 2]
|
|
23
|
+
if (props.node.options.density === 'comfortable') return classes[index + 1]
|
|
24
|
+
return classes[index]
|
|
25
|
+
})
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div :class="`mb-${titleDepthBase - node.options.titleDepth} mt-${titleDepthBase - node.options.titleDepth}`">
|
|
30
|
+
<component
|
|
31
|
+
:is="`h${node.options.titleDepth}`"
|
|
32
|
+
v-if="node.layout.title"
|
|
33
|
+
:class="`${titleClass}`"
|
|
34
|
+
>
|
|
35
|
+
{{ node.layout.title }}
|
|
36
|
+
</component>
|
|
37
|
+
<p
|
|
38
|
+
v-if="node.layout.subtitle"
|
|
39
|
+
:class="`text-subtitle mt-${titleDepthBase - node.options.titleDepth}`"
|
|
40
|
+
>
|
|
41
|
+
{{ node.layout.subtitle }}
|
|
42
|
+
</p>
|
|
43
|
+
<v-alert
|
|
44
|
+
v-if="node.error && node.validated"
|
|
45
|
+
v-bind="node.options.errorAlertProps"
|
|
46
|
+
:class="`mt-${titleDepthBase - node.options.titleDepth}`"
|
|
47
|
+
:density="node.options.density"
|
|
48
|
+
>
|
|
49
|
+
{{ node.error }}
|
|
50
|
+
</v-alert>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VMenu, VTextField } from 'vuetify/components'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
import { getCompProps, getInputProps } from '../../utils/props.js'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
modelValue: {
|
|
8
|
+
/** @type import('vue').PropType<import('../types.js').VjsfNode> */
|
|
9
|
+
type: Object,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
statefulLayout: {
|
|
13
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
},
|
|
17
|
+
formattedValue: {
|
|
18
|
+
/** @type import('vue').PropType<string | null> */
|
|
19
|
+
type: String,
|
|
20
|
+
default: null
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const fieldProps = computed(() => {
|
|
25
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout, false)
|
|
26
|
+
fieldProps.readonly = true
|
|
27
|
+
return fieldProps
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const menuProps = computed(() => {
|
|
31
|
+
const menuProps = getCompProps(props.modelValue, 'menu', false)
|
|
32
|
+
menuProps.closeOnContentClick = false
|
|
33
|
+
menuProps.disabled = true
|
|
34
|
+
return menuProps
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const textField = ref(null)
|
|
38
|
+
const menuOpened = ref(false)
|
|
39
|
+
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<v-text-field
|
|
44
|
+
ref="textField"
|
|
45
|
+
v-bind="fieldProps"
|
|
46
|
+
:model-value="formattedValue ?? modelValue.data"
|
|
47
|
+
@click:control="e => {menuOpened = !menuOpened; e.stopPropagation()}"
|
|
48
|
+
>
|
|
49
|
+
<template #prepend-inner>
|
|
50
|
+
<slot name="prepend-inner" />
|
|
51
|
+
</template>
|
|
52
|
+
</v-text-field>
|
|
53
|
+
<v-menu
|
|
54
|
+
v-if="textField"
|
|
55
|
+
v-bind="menuProps"
|
|
56
|
+
v-model="menuOpened"
|
|
57
|
+
class="vjsf-text-field-menu"
|
|
58
|
+
:activator="textField"
|
|
59
|
+
>
|
|
60
|
+
<slot :close="() => menuOpened = false" />
|
|
61
|
+
</v-menu>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.vjsf-text-field-menu .v-sheet.v-color-picker {
|
|
66
|
+
overflow-x: hidden;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import nodeSlot from './fragments/node-slot.vue'
|
|
3
|
+
|
|
4
|
+
defineProps({
|
|
5
|
+
modelValue: {
|
|
6
|
+
/** @type import('vue').PropType<import('./types.js').VjsfNode> */
|
|
7
|
+
type: Object,
|
|
8
|
+
required: true
|
|
9
|
+
},
|
|
10
|
+
statefulLayout: {
|
|
11
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
12
|
+
type: Object,
|
|
13
|
+
required: true
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/** @type Record<import('./types.js').Density, string> */
|
|
18
|
+
const beforeAfterClasses = {
|
|
19
|
+
compact: 'my-1',
|
|
20
|
+
comfortable: 'my-2',
|
|
21
|
+
default: 'my-3'
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<v-col
|
|
27
|
+
:cols="modelValue.cols"
|
|
28
|
+
>
|
|
29
|
+
<node-slot
|
|
30
|
+
v-if="modelValue.layout.slots?.before"
|
|
31
|
+
key="before"
|
|
32
|
+
:layout-slot="modelValue.layout.slots?.before"
|
|
33
|
+
:node="modelValue"
|
|
34
|
+
:stateful-layout="statefulLayout"
|
|
35
|
+
:class="beforeAfterClasses[/** @type import('./types.js').VjsfOptions */(modelValue.options).density]"
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<node-slot
|
|
39
|
+
v-if="modelValue.layout.slots?.component"
|
|
40
|
+
key="component"
|
|
41
|
+
:layout-slot="modelValue.layout.slots?.component"
|
|
42
|
+
:node="modelValue"
|
|
43
|
+
:stateful-layout="statefulLayout"
|
|
44
|
+
/>
|
|
45
|
+
<component
|
|
46
|
+
:is="`vjsf-node-${modelValue.layout.comp}`"
|
|
47
|
+
v-else-if="modelValue.layout.comp !== 'none'"
|
|
48
|
+
:model-value="modelValue"
|
|
49
|
+
:stateful-layout="statefulLayout"
|
|
50
|
+
/>
|
|
51
|
+
<node-slot
|
|
52
|
+
v-if="modelValue.layout.slots?.after"
|
|
53
|
+
key="after"
|
|
54
|
+
:layout-slot="modelValue.layout.slots?.after"
|
|
55
|
+
:node="modelValue"
|
|
56
|
+
:stateful-layout="statefulLayout"
|
|
57
|
+
:class="beforeAfterClasses[/** @type import('./types.js').VjsfOptions */(modelValue.options).density]"
|
|
58
|
+
/>
|
|
59
|
+
</v-col>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VCheckbox } from 'vuetify/components'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { getInputProps } from '../../utils/props.js'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
modelValue: {
|
|
8
|
+
/** @type import('vue').PropType<import('../types.js').VjsfCheckboxNode> */
|
|
9
|
+
type: Object,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
statefulLayout: {
|
|
13
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout))
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<v-checkbox
|
|
24
|
+
v-bind="fieldProps"
|
|
25
|
+
@update:model-value="value => statefulLayout.input(modelValue, value)"
|
|
26
|
+
/>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import TextFieldMenu from '../fragments/text-field-menu.vue'
|
|
3
|
+
import { VColorPicker } from 'vuetify/components'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { getCompProps } from '../../utils/props.js'
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
modelValue: {
|
|
9
|
+
/** @type import('vue').PropType<import('../types.js').VjsfColorPickerNode> */
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
statefulLayout: {
|
|
14
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const colorPickerProps = computed(() => {
|
|
21
|
+
const colorPickerProps = getCompProps(props.modelValue, 'colorPicker', true)
|
|
22
|
+
return colorPickerProps
|
|
23
|
+
})
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<text-field-menu
|
|
28
|
+
:model-value="modelValue"
|
|
29
|
+
:stateful-layout="statefulLayout"
|
|
30
|
+
:formatted-value="modelValue.data"
|
|
31
|
+
>
|
|
32
|
+
<template
|
|
33
|
+
v-if="modelValue.data"
|
|
34
|
+
#prepend-inner
|
|
35
|
+
>
|
|
36
|
+
<div :style="`height:30px; width: 30px; border-radius: 40px; margin-right:6px; background: ${modelValue.data};`" />
|
|
37
|
+
</template>
|
|
38
|
+
<v-color-picker
|
|
39
|
+
v-bind="colorPickerProps"
|
|
40
|
+
@update:model-value="value => statefulLayout.input(modelValue, value)"
|
|
41
|
+
/>
|
|
42
|
+
</text-field-menu>
|
|
43
|
+
</template>
|