@koumoul/vjsf 2.23.2 → 3.0.0-alpha.1

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.
Files changed (162) hide show
  1. package/package.json +56 -87
  2. package/src/compat/v2.js +120 -0
  3. package/src/compile/index.js +51 -0
  4. package/src/compile/v-jsf-compiled.vue.ejs +83 -0
  5. package/src/components/fragments/help-message.vue +48 -0
  6. package/src/components/fragments/node-slot.vue +49 -0
  7. package/src/components/fragments/section-header.vue +55 -0
  8. package/src/components/fragments/select-item-icon.vue +28 -0
  9. package/src/components/fragments/select-item.vue +43 -0
  10. package/src/components/fragments/select-selection.vue +35 -0
  11. package/src/components/fragments/text-field-menu.vue +68 -0
  12. package/src/components/node.vue +69 -0
  13. package/src/components/nodes/autocomplete.vue +95 -0
  14. package/src/components/nodes/checkbox.vue +27 -0
  15. package/src/components/nodes/color-picker.vue +43 -0
  16. package/src/components/nodes/combobox.vue +73 -0
  17. package/src/components/nodes/date-picker.vue +45 -0
  18. package/src/components/nodes/date-time-picker.vue +20 -0
  19. package/src/components/nodes/expansion-panels.vue +52 -0
  20. package/src/components/nodes/list.vue +112 -0
  21. package/src/components/nodes/markdown.vue +29 -0
  22. package/src/components/nodes/number-combobox.vue +73 -0
  23. package/src/components/nodes/number-field.vue +34 -0
  24. package/src/components/nodes/one-of-select.vue +56 -0
  25. package/src/components/nodes/section.vue +30 -0
  26. package/src/components/nodes/select.vue +79 -0
  27. package/src/components/nodes/slider.vue +34 -0
  28. package/src/components/nodes/switch.vue +29 -0
  29. package/src/components/nodes/tabs.vue +63 -0
  30. package/src/components/nodes/text-field.vue +29 -0
  31. package/src/components/nodes/textarea.vue +29 -0
  32. package/src/components/nodes/time-picker.vue +7 -0
  33. package/src/components/nodes/vertical-tabs.vue +70 -0
  34. package/src/components/options.js +25 -0
  35. package/src/components/tree.vue +26 -0
  36. package/src/components/types.ts +63 -0
  37. package/src/components/vjsf.vue +195 -0
  38. package/src/index.js +3 -0
  39. package/src/utils/clone.js +3 -0
  40. package/src/utils/dates.js +52 -0
  41. package/src/utils/props.js +87 -0
  42. package/src/utils/slots.js +19 -0
  43. package/types/compat/v2.d.ts +10 -0
  44. package/types/compat/v2.d.ts.map +1 -0
  45. package/types/compile/index.d.ts +7 -0
  46. package/types/compile/index.d.ts.map +1 -0
  47. package/types/components/fragments/help-message.vue.d.ts +8 -0
  48. package/types/components/fragments/help-message.vue.d.ts.map +1 -0
  49. package/types/components/fragments/node-slot.vue.d.ts +47 -0
  50. package/types/components/fragments/node-slot.vue.d.ts.map +1 -0
  51. package/types/components/fragments/section-header.vue.d.ts +8 -0
  52. package/types/components/fragments/section-header.vue.d.ts.map +1 -0
  53. package/types/components/fragments/select-item-icon.vue.d.ts +15 -0
  54. package/types/components/fragments/select-item-icon.vue.d.ts.map +1 -0
  55. package/types/components/fragments/select-item.vue.d.ts +12 -0
  56. package/types/components/fragments/select-item.vue.d.ts.map +1 -0
  57. package/types/components/fragments/select-selection.vue.d.ts +12 -0
  58. package/types/components/fragments/select-selection.vue.d.ts.map +1 -0
  59. package/types/components/fragments/text-field-menu.vue.d.ts +20 -0
  60. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -0
  61. package/types/components/node.vue.d.ts +10 -0
  62. package/types/components/node.vue.d.ts.map +1 -0
  63. package/types/components/nodes/autocomplete.vue.d.ts +27 -0
  64. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -0
  65. package/types/components/nodes/checkbox.vue.d.ts +10 -0
  66. package/types/components/nodes/checkbox.vue.d.ts.map +1 -0
  67. package/types/components/nodes/color-picker.vue.d.ts +10 -0
  68. package/types/components/nodes/color-picker.vue.d.ts.map +1 -0
  69. package/types/components/nodes/combobox.vue.d.ts +27 -0
  70. package/types/components/nodes/combobox.vue.d.ts.map +1 -0
  71. package/types/components/nodes/date-picker.vue.d.ts +10 -0
  72. package/types/components/nodes/date-picker.vue.d.ts.map +1 -0
  73. package/types/components/nodes/date-time-picker.vue.d.ts +10 -0
  74. package/types/components/nodes/date-time-picker.vue.d.ts.map +1 -0
  75. package/types/components/nodes/expansion-panels.vue.d.ts +10 -0
  76. package/types/components/nodes/expansion-panels.vue.d.ts.map +1 -0
  77. package/types/components/nodes/list.vue.d.ts +10 -0
  78. package/types/components/nodes/list.vue.d.ts.map +1 -0
  79. package/types/components/nodes/markdown.vue.d.ts +27 -0
  80. package/types/components/nodes/markdown.vue.d.ts.map +1 -0
  81. package/types/components/nodes/number-combobox.vue.d.ts +27 -0
  82. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -0
  83. package/types/components/nodes/number-field.vue.d.ts +27 -0
  84. package/types/components/nodes/number-field.vue.d.ts.map +1 -0
  85. package/types/components/nodes/one-of-select.vue.d.ts +10 -0
  86. package/types/components/nodes/one-of-select.vue.d.ts.map +1 -0
  87. package/types/components/nodes/section.vue.d.ts +10 -0
  88. package/types/components/nodes/section.vue.d.ts.map +1 -0
  89. package/types/components/nodes/select.vue.d.ts +27 -0
  90. package/types/components/nodes/select.vue.d.ts.map +1 -0
  91. package/types/components/nodes/slider.vue.d.ts +10 -0
  92. package/types/components/nodes/slider.vue.d.ts.map +1 -0
  93. package/types/components/nodes/switch.vue.d.ts +10 -0
  94. package/types/components/nodes/switch.vue.d.ts.map +1 -0
  95. package/types/components/nodes/tabs.vue.d.ts +10 -0
  96. package/types/components/nodes/tabs.vue.d.ts.map +1 -0
  97. package/types/components/nodes/text-field copy.vue.d.ts +10 -0
  98. package/types/components/nodes/text-field copy.vue.d.ts.map +1 -0
  99. package/types/components/nodes/text-field.vue.d.ts +27 -0
  100. package/types/components/nodes/text-field.vue.d.ts.map +1 -0
  101. package/types/components/nodes/textarea.vue.d.ts +27 -0
  102. package/types/components/nodes/textarea.vue.d.ts.map +1 -0
  103. package/types/components/nodes/time-picker.vue.d.ts +3 -0
  104. package/types/components/nodes/time-picker.vue.d.ts.map +1 -0
  105. package/types/components/nodes/vertical-tabs.vue.d.ts +10 -0
  106. package/types/components/nodes/vertical-tabs.vue.d.ts.map +1 -0
  107. package/types/components/options.d.ts +3 -0
  108. package/types/components/options.d.ts.map +1 -0
  109. package/types/components/tree.vue.d.ts +10 -0
  110. package/types/components/tree.vue.d.ts.map +1 -0
  111. package/types/components/types.d.ts +75 -0
  112. package/types/components/types.d.ts.map +1 -0
  113. package/types/components/v-jsf.vue.d.ts +13 -0
  114. package/types/components/v-jsf.vue.d.ts.map +1 -0
  115. package/types/components/vjsf.vue.d.ts +16 -0
  116. package/types/components/vjsf.vue.d.ts.map +1 -0
  117. package/types/index.d.ts +4 -0
  118. package/types/index.d.ts.map +1 -0
  119. package/types/utils/clone.d.ts +3 -0
  120. package/types/utils/clone.d.ts.map +1 -0
  121. package/types/utils/dates.d.ts +7 -0
  122. package/types/utils/dates.d.ts.map +1 -0
  123. package/types/utils/props.d.ts +21 -0
  124. package/types/utils/props.d.ts.map +1 -0
  125. package/types/utils/slots.d.ts +7 -0
  126. package/types/utils/slots.d.ts.map +1 -0
  127. package/.eslintignore +0 -9
  128. package/.eslintrc.js +0 -38
  129. package/.github/workflows/scrape-doc.yml +0 -14
  130. package/.nvmrc +0 -1
  131. package/CONTRIBUTE.md +0 -61
  132. package/FUNDING.yml +0 -1
  133. package/README.md +0 -19
  134. package/babel.config.js +0 -4
  135. package/dist/main.css +0 -67
  136. package/dist/main.js +0 -2
  137. package/dist/main.js.LICENSE.txt +0 -10
  138. package/dist/third-party.js +0 -8
  139. package/dist/third-party.js.LICENSE.txt +0 -8
  140. package/lib/VJsf.css +0 -67
  141. package/lib/VJsf.js +0 -117
  142. package/lib/VJsfNoDeps.js +0 -528
  143. package/lib/deps/third-party.js +0 -16
  144. package/lib/mixins/ColorProperty.js +0 -45
  145. package/lib/mixins/DateProperty.js +0 -170
  146. package/lib/mixins/Dependent.js +0 -69
  147. package/lib/mixins/EditableArray.js +0 -418
  148. package/lib/mixins/FileProperty.js +0 -81
  149. package/lib/mixins/MarkdownEditor.js +0 -183
  150. package/lib/mixins/ObjectContainer.js +0 -351
  151. package/lib/mixins/SelectProperty.js +0 -400
  152. package/lib/mixins/SimpleProperty.js +0 -165
  153. package/lib/mixins/Tooltip.js +0 -42
  154. package/lib/mixins/Validatable.js +0 -119
  155. package/lib/utils/expr-eval-parser.js +0 -47
  156. package/lib/utils/is-cyclic.js +0 -34
  157. package/lib/utils/json-refs.js +0 -209
  158. package/lib/utils/options.js +0 -328
  159. package/lib/utils/rules.js +0 -81
  160. package/lib/utils/schema.js +0 -100
  161. package/lib/utils/select.js +0 -141
  162. package/webpack.config.js +0 -46
package/package.json CHANGED
@@ -1,100 +1,69 @@
1
1
  {
2
2
  "name": "@koumoul/vjsf",
3
- "version": "2.23.2",
3
+ "version": "3.0.0-alpha.1",
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
- "lint": "eslint --ext js .",
8
- "lint-fix": "eslint --fix --ext js .",
9
- "build": "rm -rf dist && webpack && cp lib/VJsf.css dist/main.css",
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
- "doc-2x": "(cd doc && TARGET=https://koumoul-dev.github.io/vuetify-jsonschema-form/2.x/ nuxt generate) && gh-pages-multi deploy -v -s doc/dist -t 2.x",
18
- "analyze": "webpack --profile --json > dist/stats.json && webpack-bundle-analyzer dist/stats.json"
19
- },
20
- "jest": {
21
- "moduleFileExtensions": [
22
- "js",
23
- "json",
24
- "vue"
25
- ],
26
- "transform": {
27
- ".*\\.(vue)$": "vue-jest",
28
- "^.+\\.js$": "<rootDir>/node_modules/babel-jest"
29
- },
30
- "collectCoverage": true,
31
- "collectCoverageFrom": [
32
- "lib/**/*.{js,vue}"
33
- ],
34
- "snapshotSerializers": [
35
- "jest-serializer-vue"
36
- ],
37
- "testEnvironment": "jsdom"
38
- },
39
- "repository": {
40
- "type": "git",
41
- "url": "git+https://github.com/koumoul-dev/vuetify-jsonschema-form.git"
6
+ "test": "vitest",
7
+ "build": "vue-tsc",
8
+ "watch:build": "vue-tsc --watch"
42
9
  },
43
10
  "author": "Alban Mouton <alban.mouton@gmail.com>",
44
11
  "license": "MIT",
45
- "bugs": {
46
- "url": "https://github.com/koumoul-dev/vuetify-jsonschema-form/issues"
47
- },
48
- "homepage": "https://github.com/koumoul-dev/vuetify-jsonschema-form#readme",
49
- "dependencies": {
50
- "debounce": "^1.2.1",
51
- "debounce-promise": "^3.1.2",
52
- "debug": "^4.3.3",
53
- "fast-copy": "^2.1.1",
54
- "fast-equals": "^2.0.4",
55
- "match-all": "^1.2.6"
56
- },
57
- "optionalDependencies": {
58
- "@mdi/font": "^6.5.95",
59
- "@mdi/js": "^6.5.95",
60
- "ajv": "^8.11.0",
61
- "ajv-formats": "^2.1.1",
62
- "ajv-i18n": "^4.2.0",
63
- "expr-eval": "^2.0.2",
64
- "markdown-it": "^12.3.2",
65
- "property-expr": "^2.0.5",
66
- "vuedraggable": "^2.24.3"
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
+ }
67
43
  },
68
44
  "peerDependencies": {
69
- "vuetify": "^2.0.0"
45
+ "vue": "^3.3.4",
46
+ "vuetify": "^3.4.2"
47
+ },
48
+ "dependencies": {
49
+ "@json-layout/core": "0.2.0",
50
+ "@json-layout/vocabulary": "0.2.0",
51
+ "@vueuse/core": "^10.5.0",
52
+ "debug": "^4.3.4",
53
+ "easymde": "^2.18.0",
54
+ "ejs": "^3.1.9",
55
+ "rfdc": "^1.3.0"
70
56
  },
71
57
  "devDependencies": {
72
- "@babel/core": "^7.16.12",
73
- "@babel/plugin-transform-runtime": "^7.17.0",
74
- "@babel/preset-env": "^7.16.11",
75
- "@koumoul/data-fair-search-widget": "^0.3.0",
76
- "@koumoul/gh-pages-multi": "^0.6.0",
77
- "@vue/test-utils": "^1.3.0",
78
- "babel-eslint": "^10.1.0",
79
- "babel-jest": "^27.5.1",
80
- "babel-loader": "^8.2.4",
81
- "eslint": "^6.1.0",
82
- "eslint-config-standard": "^13.0.1",
83
- "eslint-plugin-import": "^2.18.2",
84
- "eslint-plugin-jest": "^23.1.1",
85
- "eslint-plugin-node": "^9.1.0",
86
- "eslint-plugin-promise": "^4.2.1",
87
- "eslint-plugin-standard": "^4.0.0",
88
- "eslint-plugin-vue": "^7.20.0",
89
- "jest": "^27.5.1",
90
- "jest-serializer-vue": "^2.0.2",
91
- "random-words": "^1.1.2",
92
- "v-mask": "^2.3.0",
93
- "vue-axios": "^2.1.5",
94
- "vue-jest": "^3.0.7",
95
- "vue-loader": "^15.9.8",
96
- "webpack": "^5.72.0",
97
- "webpack-bundle-analyzer": "^4.5.0",
98
- "webpack-cli": "^4.9.2"
58
+ "@types/debug": "^4.1.8",
59
+ "@types/ejs": "^3.1.2",
60
+ "relative-deps": "^1.0.7",
61
+ "vitest": "^0.34.5",
62
+ "vue": "^3.3.4",
63
+ "vue-tsc": "^1.8.15"
64
+ },
65
+ "relativeDependencies": {
66
+ "@json-layout/core": "../../json-layout/core/",
67
+ "@json-layout/vocabulary": "../../json-layout/vocabulary/"
99
68
  }
100
69
  }
@@ -0,0 +1,120 @@
1
+ import ajvModule from 'ajv'
2
+ import rfdc from 'rfdc'
3
+ import addFormats from 'ajv-formats'
4
+ import { resolveRefs } from '@json-layout/core'
5
+ import { isPartialGetItemsObj } from '@json-layout/vocabulary'
6
+
7
+ // @ts-ignore
8
+ const Ajv = /** @type {typeof ajvModule.default} */ (ajvModule)
9
+
10
+ const clone = rfdc()
11
+
12
+ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
13
+ if (!schema.layout) {
14
+ /** @type import('@json-layout/vocabulary').PartialCompObject */
15
+ const layout = {}
16
+
17
+ if (schema['x-display'] === 'icon' && (schema.enum || schema.items?.enum)) {
18
+ layout.getItems = { itemIcon: schema['x-itemIcon'] || 'data.value' }
19
+ delete schema['x-display']
20
+ }
21
+
22
+ if (schema['x-display']) {
23
+ layout.comp = schema['x-display']
24
+ delete schema['x-display']
25
+ }
26
+
27
+ if (schema.format === 'hexcolor') {
28
+ layout.comp = 'color-picker'
29
+ delete schema.format
30
+ }
31
+
32
+ if (schema['x-props']) {
33
+ layout.props = schema['x-props']
34
+ delete schema['x-props']
35
+ }
36
+
37
+ if (schema['x-fromData']) {
38
+ layout.comp = 'select'
39
+ layout.getItems = { expr: schema['x-fromData'] }
40
+ delete schema['x-fromData']
41
+ }
42
+
43
+ if (schema['x-fromUrl']) {
44
+ /** @type string */
45
+ let url = schema['x-fromUrl']
46
+ for (const expressionMatch of url.matchAll(/\{(.*?)\}/g)) {
47
+ if (expressionMatch[1] !== 'q') url = url.replace(expressionMatch[0], '$' + expressionMatch[0])
48
+ }
49
+ layout.getItems = { url }
50
+ delete schema['x-fromUrl']
51
+ }
52
+ if (layout.getItems && isPartialGetItemsObj(layout.getItems)) {
53
+ if (schema['x-itemKey']) layout.getItems.itemKey = `data["${schema['x-itemKey']}"]`
54
+ if (schema['x-itemTitle']) layout.getItems.itemTitle = `data["${schema['x-itemTitle']}"]`
55
+ if (schema['x-itemValue']) layout.getItems.itemValue = `data["${schema['x-itemValue']}"]`
56
+ if (schema['x-itemIcon']) layout.getItems.itemIcon = `data["${schema['x-itemIcon']}"]`
57
+ if (schema['x-itemsProp']) layout.getItems.itemsResults = `data["${schema['x-itemsProp']}"]`
58
+ delete schema['x-itemKey']
59
+ delete schema['x-itemTitle']
60
+ delete schema['x-itemValue']
61
+ delete schema['x-itemsProp']
62
+ }
63
+
64
+ // compact the layout keyword if possible
65
+ if (Object.keys(layout).length === 1 && 'comp' in layout) {
66
+ schema.layout = layout.comp
67
+ } else if (Object.keys(layout).length > 0) {
68
+ schema.layout = layout
69
+ }
70
+ }
71
+
72
+ if (schema.type === 'object') {
73
+ if (schema.properties) {
74
+ for (const propertyKey of Object.keys(schema.properties)) {
75
+ processFragment(schema.properties[propertyKey])
76
+ }
77
+ }
78
+ if (schema.allOf) {
79
+ for (const item of schema.allOf) processFragment(item)
80
+ }
81
+ if (schema.oneOf) {
82
+ for (const item of schema.oneOf) processFragment(item)
83
+ }
84
+ if (schema.anyOf) {
85
+ for (const item of schema.anyOf) processFragment(item)
86
+ }
87
+ }
88
+
89
+ if (schema.type === 'array' && schema.items) {
90
+ if (Array.isArray(schema.items)) {
91
+ for (const item of schema.items) processFragment(item)
92
+ } else {
93
+ processFragment(schema.items)
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ *
100
+ * @param {object} _schema
101
+ * @param {import("ajv").default} [_ajv]
102
+ * @param {string} lang
103
+ * @returns
104
+ */
105
+ export function v2compat (_schema, _ajv, lang = 'en') {
106
+ let ajv = _ajv
107
+ if (!ajv) {
108
+ /** @type {import('ajv').Options} */
109
+ const ajvOpts = { strict: false, allErrors: true }
110
+ const newAjv = new Ajv(ajvOpts)
111
+ addFormats.default(newAjv)
112
+ ajv = newAjv
113
+ }
114
+
115
+ const schema = /** @type {import("ajv").SchemaObject} */ (clone(_schema))
116
+ schema.$id = schema.$id ?? '_jl'
117
+ resolveRefs(schema, ajv, lang)
118
+ processFragment(schema)
119
+ return schema
120
+ }
@@ -0,0 +1,51 @@
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 { childIsCompObject, 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
+ *
15
+ * @param {Set<string>} comps
16
+ * @param {import('@json-layout/vocabulary').NormalizedLayout} layout
17
+ */
18
+ function listComps (comps, layout) {
19
+ if (isCompObject(layout)) {
20
+ comps.add(layout.comp)
21
+ if (layout.children) {
22
+ for (const child of /** @type {import('@json-layout/vocabulary').Children} */(layout.children)) {
23
+ if (childIsCompObject(child)) {
24
+ listComps(comps, child)
25
+ }
26
+ }
27
+ }
28
+ } else if (isSwitchStruct(layout)) {
29
+ for (const switchCase of layout.switch) {
30
+ listComps(comps, switchCase)
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param {object} schema
37
+ * @param {string} baseImport
38
+ * @returns {string}
39
+ */
40
+ export function compile (schema, baseImport = '@koumoul/vjsf/components') {
41
+ const compiledLayout = compileLayout(schema, { code: true })
42
+ const compiledLayoutCode = serializeCompiledLayout(compiledLayout)
43
+ /** @type Set<string> */
44
+ const comps = new Set([])
45
+ for (const layout of Object.values(compiledLayout.normalizedLayouts)) {
46
+ listComps(comps, layout)
47
+ }
48
+ comps.delete('none')
49
+ const code = ejs.render(template, { compiledLayoutCode, comps, baseImport })
50
+ return code
51
+ }
@@ -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,48 @@
1
+ <template>
2
+ <div class="vjsf-help-message">
3
+ <v-slide-x-reverse-transition>
4
+ <v-alert
5
+ v-show="show"
6
+ color="info"
7
+ >
8
+ <div v-html="node.layout.help" />
9
+ </v-alert>
10
+ </v-slide-x-reverse-transition>
11
+ <v-btn
12
+ color="info"
13
+ class="vjsf-help-message-toggle"
14
+ :icon="show ? 'mdi-close-circle' : 'mdi-information'"
15
+ density="compact"
16
+ :title="show ? '' : node.messages.showHelp"
17
+ @click="show = !show"
18
+ />
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+ import { VAlert, VBtn } from 'vuetify/components'
24
+ import { ref } from 'vue'
25
+
26
+ defineProps({
27
+ node: {
28
+ /** @type import('vue').PropType<import('../types.js').VjsfNode> */
29
+ type: Object,
30
+ required: true
31
+ }
32
+ })
33
+
34
+ const show = ref(false)
35
+ </script>
36
+
37
+ <style>
38
+ .vjsf-help-message {
39
+ position: relative;
40
+ min-height: 10px;
41
+ }
42
+ .vjsf-help-message-toggle {
43
+ position: absolute;
44
+ top: -10px;
45
+ right: -4px;
46
+ z-index: 1;
47
+ }
48
+ </style>
@@ -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,55 @@
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
30
+ v-if="node.layout.title ?? node.layout.subtitle ?? (node.error && node.validated)"
31
+ :class="`mb-${titleDepthBase - node.options.titleDepth} mt-${titleDepthBase - node.options.titleDepth}`"
32
+ >
33
+ <component
34
+ :is="`h${node.options.titleDepth}`"
35
+ v-if="node.layout.title"
36
+ :class="`${titleClass}`"
37
+ >
38
+ {{ node.layout.title }}
39
+ </component>
40
+ <p
41
+ v-if="node.layout.subtitle"
42
+ :class="`text-subtitle mt-${titleDepthBase - node.options.titleDepth}`"
43
+ >
44
+ {{ node.layout.subtitle }}
45
+ </p>
46
+ <v-alert
47
+ v-if="node.error && node.validated"
48
+ v-bind="node.options.errorAlertProps"
49
+ :class="`mt-${titleDepthBase - node.options.titleDepth}`"
50
+ :density="node.options.density"
51
+ >
52
+ {{ node.error }}
53
+ </v-alert>
54
+ </div>
55
+ </template>
@@ -0,0 +1,28 @@
1
+ <script>
2
+ // cf https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/VSelect.tsx#L374
3
+
4
+ import { defineComponent, h, computed } from 'vue'
5
+ import { VIcon } from 'vuetify/components'
6
+
7
+ export default defineComponent({
8
+ props: {
9
+ icon: {
10
+ type: String,
11
+ required: true
12
+ }
13
+ },
14
+ setup (props) {
15
+ const isUrl = computed(() => props.icon.startsWith('http://') || props.icon.startsWith('https://'))
16
+ const isSVG = computed(() => props.icon.startsWith('<?xml') || props.icon.startsWith('<svg'))
17
+ return () => {
18
+ if (isUrl.value) {
19
+ return h('img', { src: props.icon, style: 'height:100%;width:100%;' })
20
+ } else if (isSVG.value) {
21
+ return h('div', { innerHTML: props.icon.replace('<svg ', '<svg class="v-icon__svg" '), class: 'v-icon' })
22
+ } else {
23
+ return h(VIcon, null, props.icon)
24
+ }
25
+ }
26
+ }
27
+ })
28
+ </script>
@@ -0,0 +1,43 @@
1
+ <script setup>
2
+ // cf https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/VSelect.tsx#L374
3
+
4
+ import { VListItem, VCheckboxBtn } from 'vuetify/components'
5
+ import VSelectItemIcon from './select-item-icon.vue'
6
+
7
+ defineProps({
8
+ multiple: {
9
+ type: Boolean,
10
+ default: false
11
+ },
12
+ itemProps: {
13
+ type: Object,
14
+ required: true
15
+ },
16
+ item: {
17
+ /** @type import('vue').PropType<import('@json-layout/vocabulary').SelectItem> */
18
+ type: Object,
19
+ required: true
20
+ }
21
+ })
22
+ </script>
23
+
24
+ <template>
25
+ <v-list-item v-bind="itemProps">
26
+ <template
27
+ v-if="item.icon || multiple"
28
+ #prepend="{isSelected}"
29
+ >
30
+ <v-checkbox-btn
31
+ v-if="multiple"
32
+ :key="item.key"
33
+ :ripple="false"
34
+ tabindex="-1"
35
+ :model-value="isSelected"
36
+ />
37
+ <v-select-item-icon
38
+ v-if="item.icon"
39
+ :icon="item.icon"
40
+ />
41
+ </template>
42
+ </v-list-item>
43
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup>
2
+ // cf https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/VSelect.tsx#L453
3
+
4
+ import VSelectItemIcon from './select-item-icon.vue'
5
+
6
+ defineProps({
7
+ multiple: {
8
+ type: Boolean,
9
+ default: false
10
+ },
11
+ last: {
12
+ type: Boolean,
13
+ default: false
14
+ },
15
+ item: {
16
+ /** @type import('vue').PropType<import('@json-layout/vocabulary').SelectItem> */
17
+ type: Object,
18
+ required: true
19
+ }
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <span class="v-select__selection-text">
25
+ <v-select-item-icon
26
+ v-if="item.icon"
27
+ :icon="item.icon"
28
+ />
29
+ {{ item.title }}
30
+ <span
31
+ v-if="multiple && !last"
32
+ class="v-select__selection-comma"
33
+ >,</span>
34
+ </span>
35
+ </template>