@koumoul/vjsf 3.0.0-beta.9 → 3.0.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/README.md +21 -0
  2. package/package.json +13 -20
  3. package/src/compat/v2.js +126 -27
  4. package/src/components/fragments/child-subtitle.vue +25 -0
  5. package/src/components/fragments/help-message.vue +33 -8
  6. package/src/components/fragments/section-header.vue +9 -7
  7. package/src/components/fragments/select-item-icon.vue +2 -2
  8. package/src/components/fragments/select-item.vue +2 -1
  9. package/src/components/fragments/select-selection.vue +2 -1
  10. package/src/components/fragments/selection-group.vue +105 -0
  11. package/src/components/fragments/text-field-menu.vue +16 -7
  12. package/src/components/node.vue +58 -41
  13. package/src/components/nodes/autocomplete.vue +14 -60
  14. package/src/components/nodes/card.vue +39 -0
  15. package/src/components/nodes/checkbox-group.vue +39 -0
  16. package/src/components/nodes/checkbox.vue +31 -26
  17. package/src/components/nodes/color-picker.vue +10 -5
  18. package/src/components/nodes/combobox.vue +17 -40
  19. package/src/components/nodes/date-picker.vue +33 -12
  20. package/src/components/nodes/date-time-picker.vue +86 -3
  21. package/src/components/nodes/expansion-panels.vue +17 -9
  22. package/src/components/nodes/file-input.vue +15 -11
  23. package/src/components/nodes/list.vue +246 -112
  24. package/src/components/nodes/number-combobox.vue +18 -39
  25. package/src/components/nodes/number-field.vue +17 -11
  26. package/src/components/nodes/one-of-select.vue +53 -27
  27. package/src/components/nodes/radio-group.vue +58 -0
  28. package/src/components/nodes/section.vue +4 -1
  29. package/src/components/nodes/select.vue +15 -54
  30. package/src/components/nodes/slider.vue +32 -29
  31. package/src/components/nodes/stepper.vue +10 -2
  32. package/src/components/nodes/switch-group.vue +39 -0
  33. package/src/components/nodes/switch.vue +31 -26
  34. package/src/components/nodes/tabs.vue +20 -8
  35. package/src/components/nodes/text-field.vue +10 -7
  36. package/src/components/nodes/textarea.vue +20 -12
  37. package/src/components/nodes/time-picker.vue +45 -1
  38. package/src/components/nodes/vertical-tabs.vue +16 -6
  39. package/src/components/tree.vue +1 -1
  40. package/src/components/vjsf.vue +11 -1
  41. package/src/composables/use-comp-defaults.js +19 -0
  42. package/src/composables/use-dnd.js +2 -1
  43. package/src/composables/use-get-items.js +53 -0
  44. package/src/composables/use-node.js +136 -0
  45. package/src/composables/use-select-node.js +67 -0
  46. package/src/composables/use-vjsf.js +72 -51
  47. package/src/index.js +5 -2
  48. package/src/options.js +67 -0
  49. package/src/types.ts +64 -33
  50. package/src/utils/arrays.js +37 -6
  51. package/types/compat/v2.d.ts.map +1 -1
  52. package/types/compile/index.d.ts +2 -2
  53. package/types/compile/index.d.ts.map +1 -1
  54. package/types/compile/options.d.ts +3 -2
  55. package/types/compile/options.d.ts.map +1 -1
  56. package/types/components/fragments/child-subtitle.vue.d.ts +8 -0
  57. package/types/components/fragments/child-subtitle.vue.d.ts.map +1 -0
  58. package/types/components/fragments/help-message.vue.d.ts +2 -2
  59. package/types/components/fragments/node-slot.vue.d.ts +2 -44
  60. package/types/components/fragments/node-slot.vue.d.ts.map +1 -1
  61. package/types/components/fragments/section-header.vue.d.ts +4 -2
  62. package/types/components/fragments/select-item-icon.vue.d.ts +2 -12
  63. package/types/components/fragments/select-item.vue.d.ts +2 -2
  64. package/types/components/fragments/select-selection.vue.d.ts +2 -2
  65. package/types/components/fragments/selection-group.vue.d.ts +5 -0
  66. package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
  67. package/types/components/fragments/text-field-menu.vue.d.ts +2 -2
  68. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
  69. package/types/components/node.vue.d.ts +2 -2
  70. package/types/components/nodes/autocomplete.vue.d.ts +2 -24
  71. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
  72. package/types/components/nodes/card.vue.d.ts +10 -0
  73. package/types/components/nodes/card.vue.d.ts.map +1 -0
  74. package/types/components/nodes/checkbox-group.vue.d.ts +5 -0
  75. package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
  76. package/types/components/nodes/checkbox.vue.d.ts +3 -8
  77. package/types/components/nodes/color-picker.vue.d.ts +2 -2
  78. package/types/components/nodes/combobox.vue.d.ts +2 -24
  79. package/types/components/nodes/combobox.vue.d.ts.map +1 -1
  80. package/types/components/nodes/date-picker.vue.d.ts +2 -2
  81. package/types/components/nodes/date-time-picker.vue.d.ts +4 -4
  82. package/types/components/nodes/expansion-panels.vue.d.ts +2 -2
  83. package/types/components/nodes/file-input.vue.d.ts +2 -24
  84. package/types/components/nodes/file-input.vue.d.ts.map +1 -1
  85. package/types/components/nodes/list.vue.d.ts +2 -2
  86. package/types/components/nodes/number-combobox.vue.d.ts +2 -24
  87. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -1
  88. package/types/components/nodes/number-field.vue.d.ts +2 -24
  89. package/types/components/nodes/number-field.vue.d.ts.map +1 -1
  90. package/types/components/nodes/one-of-select.vue.d.ts +2 -2
  91. package/types/components/nodes/radio-group.vue.d.ts +5 -0
  92. package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
  93. package/types/components/nodes/section.vue.d.ts +2 -2
  94. package/types/components/nodes/select.vue.d.ts +2 -24
  95. package/types/components/nodes/select.vue.d.ts.map +1 -1
  96. package/types/components/nodes/slider.vue.d.ts +3 -8
  97. package/types/components/nodes/stepper.vue.d.ts +2 -2
  98. package/types/components/nodes/switch-group.vue.d.ts +5 -0
  99. package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
  100. package/types/components/nodes/switch.vue.d.ts +3 -8
  101. package/types/components/nodes/tabs.vue.d.ts +2 -2
  102. package/types/components/nodes/text-field.vue.d.ts +2 -24
  103. package/types/components/nodes/text-field.vue.d.ts.map +1 -1
  104. package/types/components/nodes/textarea.vue.d.ts +2 -24
  105. package/types/components/nodes/textarea.vue.d.ts.map +1 -1
  106. package/types/components/nodes/time-picker.vue.d.ts +8 -1
  107. package/types/components/nodes/vertical-tabs.vue.d.ts +2 -2
  108. package/types/components/options.d.ts +1 -1
  109. package/types/components/options.d.ts.map +1 -1
  110. package/types/components/tree.vue.d.ts +2 -2
  111. package/types/components/vjsf.vue.d.ts +5 -5
  112. package/types/composables/use-comp-defaults.d.ts +8 -0
  113. package/types/composables/use-comp-defaults.d.ts.map +1 -0
  114. package/types/composables/use-dnd.d.ts +3 -3
  115. package/types/composables/use-dnd.d.ts.map +1 -1
  116. package/types/composables/use-field-props.d.ts +30 -0
  117. package/types/composables/use-field-props.d.ts.map +1 -0
  118. package/types/composables/use-field.d.ts +31 -0
  119. package/types/composables/use-field.d.ts.map +1 -0
  120. package/types/composables/use-get-items.d.ts +12 -0
  121. package/types/composables/use-get-items.d.ts.map +1 -0
  122. package/types/composables/use-node.d.ts +32 -0
  123. package/types/composables/use-node.d.ts.map +1 -0
  124. package/types/composables/use-select-field.d.ts +21 -0
  125. package/types/composables/use-select-field.d.ts.map +1 -0
  126. package/types/composables/use-select-node.d.ts +27 -0
  127. package/types/composables/use-select-node.d.ts.map +1 -0
  128. package/types/composables/use-select-props.d.ts +21 -0
  129. package/types/composables/use-select-props.d.ts.map +1 -0
  130. package/types/composables/use-select.d.ts +21 -0
  131. package/types/composables/use-select.d.ts.map +1 -0
  132. package/types/composables/use-vjsf.d.ts +2 -2
  133. package/types/composables/use-vjsf.d.ts.map +1 -1
  134. package/types/iconsets/default-aliases.d.ts +10 -0
  135. package/types/iconsets/default-aliases.d.ts.map +1 -0
  136. package/types/iconsets/mdi-svg.d.ts +3 -0
  137. package/types/iconsets/mdi-svg.d.ts.map +1 -0
  138. package/types/iconsets/mdi.d.ts +3 -0
  139. package/types/iconsets/mdi.d.ts.map +1 -0
  140. package/types/index.d.ts +5 -2
  141. package/types/index.d.ts.map +1 -1
  142. package/types/options.d.ts +9 -0
  143. package/types/options.d.ts.map +1 -0
  144. package/types/types.d.ts +65 -33
  145. package/types/types.d.ts.map +1 -1
  146. package/types/utils/arrays.d.ts +17 -4
  147. package/types/utils/arrays.d.ts.map +1 -1
  148. package/types/utils/index.d.ts +0 -3
  149. package/types/utils/props.d.ts +7 -0
  150. package/types/utils/props.d.ts.map +1 -1
  151. package/types/utils/slots.d.ts +8 -0
  152. package/types/utils/slots.d.ts.map +1 -1
  153. package/src/compile/index.js +0 -65
  154. package/src/compile/options.js +0 -19
  155. package/src/compile/v-jsf-compiled.vue.ejs +0 -61
  156. package/src/components/options.js +0 -27
  157. package/src/utils/global-register.js +0 -13
  158. package/src/utils/index.js +0 -5
  159. package/src/utils/props.js +0 -107
  160. package/src/utils/slots.js +0 -18
  161. package/types/utils/global-register.d.ts +0 -8
  162. package/types/utils/global-register.d.ts.map +0 -1
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # VJSF
2
+
3
+ *vuetify-json-schema-form* - *@koumoul/vjsf on npm*
4
+
5
+ Easily create beautiful forms that output valid data.
6
+
7
+ Based on [Vue.js](https://vuejs.org/) / [Vuetify](https://vuetifyjs.com/) / [JSON Schema](https://json-schema.org/) / [JSON Layout](https://github.com/json-layout/json-layout).
8
+
9
+ See [the documentation](https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/).
10
+
11
+ See [the documentation for deprecated v2](https://koumoul-dev.github.io/vuetify-jsonschema-form/2.x/).
12
+
13
+ ![](doc/static/vjsf.gif)
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.9",
3
+ "version": "3.0.1",
4
4
  "description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
5
5
  "scripts": {
6
6
  "test": "vitest",
7
7
  "build": "vue-tsc",
8
- "watch:build": "vue-tsc --watch"
8
+ "watch:build": "vue-tsc --watch",
9
+ "prepublishOnly": "npm run build && cp ../README.md README.md && cp ../LICENSE LICENSE"
9
10
  },
10
11
  "author": "Alban Mouton <alban.mouton@gmail.com>",
11
12
  "license": "MIT",
@@ -34,22 +35,16 @@
34
35
  "default": "./src/components/*"
35
36
  }
36
37
  },
37
- "./composables/*": {
38
+ "./utils/*.js": {
38
39
  "import": {
39
- "types": "./types/composables/*.d.ts",
40
- "default": "./src/composables/*"
41
- }
42
- },
43
- "./utils": {
44
- "import": {
45
- "types": "./types/utils/index.d.ts",
46
- "default": "./src/utils/index.js"
40
+ "types": "./types/utils/*.d.ts",
41
+ "default": "./src/utils/*.js"
47
42
  }
48
43
  },
49
- "./utils/*": {
44
+ "./composables/*.js": {
50
45
  "import": {
51
- "types": "./types/utils/*.d.ts",
52
- "default": "./src/utils/*"
46
+ "types": "./types/composables/*.d.ts",
47
+ "default": "./src/composables/*.js"
53
48
  }
54
49
  },
55
50
  "./styles/*": {
@@ -72,19 +67,17 @@
72
67
  },
73
68
  "peerDependencies": {
74
69
  "vue": "^3.4.3",
75
- "vuetify": "^3.4.9"
70
+ "vuetify": "^3.6.13"
76
71
  },
77
72
  "dependencies": {
78
- "@json-layout/core": "0.14.0",
73
+ "@json-layout/core": "0.33.4",
79
74
  "@vueuse/core": "^10.5.0",
80
- "debug": "^4.3.4",
81
- "ejs": "^3.1.9"
75
+ "debug": "^4.3.4"
82
76
  },
83
77
  "devDependencies": {
84
78
  "@types/debug": "^4.1.8",
85
- "@types/ejs": "^3.1.2",
86
79
  "vitest": "^1.1.1",
87
- "vue": "^3.4.3",
80
+ "vue": "^3.5.6",
88
81
  "vue-tsc": "^1.8.27"
89
82
  }
90
83
  }
package/src/compat/v2.js CHANGED
@@ -1,12 +1,78 @@
1
1
  import ajvModule from 'ajv'
2
2
  import addFormats from 'ajv-formats'
3
- import { resolveRefs, clone } from '@json-layout/core'
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 processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
9
+ const expressionKeyMappings = {
10
+ modelRoot: 'rootData',
11
+ root: 'rootData',
12
+ model: 'data',
13
+ value: 'data'
14
+ }
15
+
16
+ /**
17
+ * @param {string} expression
18
+ * @returns {string}
19
+ */
20
+ const prefixExpression = (expression) => {
21
+ // looks like a simple expression missing the data. prefix
22
+ if (expression.match(/^[a-z.]*$/i) && !['data', 'context', 'rootData', 'parent'].some(key => expression.startsWith(key + '.'))) {
23
+ return 'rootData.' + expression
24
+ }
25
+ return expression
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @param {string} expression
31
+ * @param {'js-eval' | 'js-tpl'} [type]
32
+ * @returns {{type: 'js-eval' | 'js-tpl', expr: string, pure: boolean}}
33
+ */
34
+ const fixExpression = (expression, type = 'js-eval') => {
35
+ let expr = expression
36
+ let pure = true
37
+
38
+ if (expr.includes('parent.value')) {
39
+ pure = false
40
+ expr = expr.replace(/parent\.value/g, 'parent.data')
41
+ }
42
+
43
+ for (const [key, value] of Object.entries(expressionKeyMappings)) {
44
+ expr = expr.replace(new RegExp(`${key}\\.`, 'g'), value + '.')
45
+ }
46
+ if (type === 'js-eval') {
47
+ expr = prefixExpression(expr)
48
+ }
49
+ if (type === 'js-tpl') {
50
+ for (const expressionMatch of expr.matchAll(/\{(.*?)\}/g)) {
51
+ if (expressionMatch[1] !== 'q') expr = expr.replace(expressionMatch[0], '${' + prefixExpression(expressionMatch[1]) + '}')
52
+ }
53
+ }
54
+
55
+ if (expr.includes('rootData')) {
56
+ pure = false
57
+ }
58
+
59
+ return { type, expr, pure }
60
+ }
61
+
62
+ /**
63
+ *
64
+ * @param {import("ajv").SchemaObject} schema
65
+ * @param {(schemaId: string, ref: string) => [any, string, string]} getJSONRef
66
+ * @param {string} schemaId
67
+ * @param {any[]} processed
68
+ */
69
+ const processFragment = (schema, getJSONRef, schemaId, processed) => {
70
+ if (processed.includes(schema)) return
71
+ processed.push(schema)
72
+ if (schema.$ref) {
73
+ const [refFragment, refSchemaId] = getJSONRef(schemaId, schema.$ref)
74
+ processFragment(refFragment, getJSONRef, refSchemaId, processed)
75
+ }
10
76
  if (!schema.layout) {
11
77
  /** @type import('@json-layout/vocabulary').PartialCompObject */
12
78
  const layout = {}
@@ -23,7 +89,11 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
23
89
  }
24
90
 
25
91
  if (schema['x-display']) {
26
- layout.comp = schema['x-display']
92
+ let display = schema['x-display']
93
+ if (display === 'radio') display = 'radio-group'
94
+ if (display === 'checkbox' && schema.type !== 'boolean') display = 'checkbox-group'
95
+ if (display === 'switch' && schema.type !== 'boolean') display = 'switch-group'
96
+ layout.comp = display
27
97
  delete schema['x-display']
28
98
  }
29
99
 
@@ -38,18 +108,20 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
38
108
  }
39
109
 
40
110
  if (schema['x-fromData']) {
41
- layout.comp = 'select'
42
- layout.getItems = { expr: schema['x-fromData'] }
111
+ layout.comp = layout.comp ?? 'select'
112
+ layout.getItems = fixExpression(schema['x-fromData'])
43
113
  delete schema['x-fromData']
44
114
  }
45
115
 
116
+ if (schema['x-if']) {
117
+ layout.if = fixExpression(schema['x-if'])
118
+ delete schema['x-if']
119
+ }
120
+
46
121
  if (schema['x-fromUrl']) {
47
122
  /** @type string */
48
- let url = schema['x-fromUrl']
49
- for (const expressionMatch of url.matchAll(/\{(.*?)\}/g)) {
50
- if (expressionMatch[1] !== 'q') url = url.replace(expressionMatch[0], '$' + expressionMatch[0])
51
- }
52
- layout.getItems = { url }
123
+ const url = schema['x-fromUrl']
124
+ layout.getItems = { url: fixExpression(url, 'js-tpl') }
53
125
  delete schema['x-fromUrl']
54
126
  }
55
127
  if (layout.getItems && isPartialGetItemsObj(layout.getItems)) {
@@ -64,6 +136,10 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
64
136
  delete schema['x-itemsProp']
65
137
  }
66
138
 
139
+ if (schema['x-cols']) {
140
+ layout.cols = schema['x-cols']
141
+ }
142
+
67
143
  // compact the layout keyword if possible
68
144
  if (Object.keys(layout).length === 1 && 'comp' in layout) {
69
145
  schema.layout = layout.comp
@@ -72,30 +148,51 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
72
148
  }
73
149
  }
74
150
 
75
- if (schema.type === 'object') {
76
- if (schema.properties) {
77
- for (const propertyKey of Object.keys(schema.properties)) {
78
- processFragment(schema.properties[propertyKey])
79
- }
80
- }
81
- if (schema.allOf) {
82
- for (const item of schema.allOf) processFragment(item)
151
+ if (schema.properties) {
152
+ for (const propertyKey of Object.keys(schema.properties)) {
153
+ processFragment(schema.properties[propertyKey], getJSONRef, schemaId, processed)
83
154
  }
84
- if (schema.oneOf) {
85
- for (const item of schema.oneOf) processFragment(item)
86
- }
87
- if (schema.anyOf) {
88
- for (const item of schema.anyOf) processFragment(item)
155
+ }
156
+
157
+ if (schema.allOf) {
158
+ for (const item of schema.allOf) processFragment(item, getJSONRef, schemaId, processed)
159
+ }
160
+
161
+ if (schema.oneOf) {
162
+ if (!schema.oneOfLayout) {
163
+ const constPropertyKey = Object.keys(schema.oneOf[0]?.properties || {})
164
+ .find(key => !!schema.oneOf[0]?.properties[key].const)
165
+ if (constPropertyKey) {
166
+ const constProperty = schema.oneOf[0]?.properties[constPropertyKey]
167
+ if (constProperty?.title) schema.oneOfLayout = { label: constProperty.title }
168
+ if (schema.required && Array.isArray(schema.required)) {
169
+ schema.required = schema.required.filter(key => key !== constPropertyKey)
170
+ }
171
+ }
89
172
  }
173
+ for (const item of schema.oneOf) processFragment(item, getJSONRef, schemaId, processed)
174
+ }
175
+
176
+ if (schema.anyOf) {
177
+ for (const item of schema.anyOf) processFragment(item, getJSONRef, schemaId, processed)
90
178
  }
91
179
 
92
180
  if (schema.type === 'array' && schema.items) {
93
181
  if (Array.isArray(schema.items)) {
94
- for (const item of schema.items) processFragment(item)
182
+ for (const item of schema.items) processFragment(item, getJSONRef, schemaId, processed)
95
183
  } else {
96
- processFragment(schema.items)
184
+ processFragment(schema.items, getJSONRef, schemaId, processed)
97
185
  }
98
186
  }
187
+ if (schema.dependencies) {
188
+ for (const key of Object.keys(schema.dependencies)) {
189
+ processFragment(schema.dependencies[key], getJSONRef, schemaId, processed)
190
+ }
191
+ }
192
+ if (schema.if) {
193
+ if (schema.then) processFragment(schema.then, getJSONRef, schemaId, processed)
194
+ if (schema.else) processFragment(schema.else, getJSONRef, schemaId, processed)
195
+ }
99
196
  }
100
197
 
101
198
  /**
@@ -117,7 +214,9 @@ export function v2compat (_schema, _ajv, lang = 'en') {
117
214
 
118
215
  const schema = /** @type {import("ajv").SchemaObject} */ (clone(_schema))
119
216
  schema.$id = schema.$id ?? '_jl'
120
- resolveRefs(schema, ajv, lang)
121
- processFragment(schema)
217
+ const getJSONRef = resolveLocaleRefs(schema, ajv, lang)
218
+ /** @type {any[]} */
219
+ const processed = []
220
+ processFragment(schema, getJSONRef, schema.$id, processed)
122
221
  return schema
123
222
  }
@@ -0,0 +1,25 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+ import { isSection } from '@json-layout/core'
4
+
5
+ const { modelValue } = defineProps({
6
+ modelValue: {
7
+ /** @type import('vue').PropType<import('@json-layout/core').StateNode> */
8
+ type: Object,
9
+ required: true
10
+ }
11
+ })
12
+
13
+ const pClass = computed(() => {
14
+ if (modelValue.options.density === 'default') return 'mt-1 mb-5'
15
+ if (modelValue.options.density === 'comfortable') return 'mb-4'
16
+ return 'mb-3'
17
+ })
18
+ </script>
19
+ <template>
20
+ <p
21
+ v-if="isSection(modelValue) && modelValue.layout.subtitle"
22
+ :class="`text-subtitle ${pClass}`"
23
+ v-html="modelValue.layout.subtitle"
24
+ />
25
+ </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="vjsf-help-message">
2
+ <div :class="`vjsf-help-message vjsf-help-message-${node.options.density}`">
3
3
  <v-slide-x-reverse-transition>
4
4
  <v-alert
5
5
  v-show="show"
@@ -10,9 +10,12 @@
10
10
  </v-slide-x-reverse-transition>
11
11
  <v-btn
12
12
  color="info"
13
- class="vjsf-help-message-toggle"
14
- :icon="show ? 'mdi-close-circle' : 'mdi-information'"
13
+ :class="`vjsf-help-message-toggle vjsf-help-message-toggle-${node.options.density}`"
14
+ :icon="show ? node.options.icons.close : node.options.icons.infoSymbol"
15
+ :border="0"
16
+ :elevation="show ? 0 : 2"
15
17
  density="compact"
18
+ :size="node.options.density === 'default' ? 28 : 24"
16
19
  :title="show ? '' : node.messages.showHelp"
17
20
  @click="show = !show"
18
21
  />
@@ -20,7 +23,9 @@
20
23
  </template>
21
24
 
22
25
  <script setup>
23
- import { VAlert, VBtn, VSlideXReverseTransition } from 'vuetify/components'
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
- min-height: 10px;
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: -10px;
45
- right: -4px;
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 ?? node.layout.subtitle ?? (node.error && node.validated)"
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
- {{ node.layout.subtitle }}
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 { VListItem, VCheckboxBtn } from 'vuetify/components'
4
+ import { VCheckboxBtn } from 'vuetify/components/VCheckbox'
5
+ import { VListItem } from 'vuetify/components/VList'
5
6
  import VSelectItemIcon from './select-item-icon.vue'
6
7
 
7
8
  defineProps({
@@ -18,6 +18,7 @@ defineProps({
18
18
  required: true
19
19
  }
20
20
  })
21
+
21
22
  </script>
22
23
 
23
24
  <template>
@@ -26,7 +27,7 @@ defineProps({
26
27
  v-if="item.icon"
27
28
  :icon="item.icon"
28
29
  />
29
- {{ item.title }}
30
+ {{ item.title ?? item.key ?? item.value }}
30
31
  <span
31
32
  v-if="multiple && !last"
32
33
  class="v-select__selection-comma"
@@ -0,0 +1,105 @@
1
+ <script>
2
+ import { VSkeletonLoader } from 'vuetify/components/VSkeletonLoader'
3
+ import { VInput } from 'vuetify/components/VInput'
4
+ import { VLabel } from 'vuetify/components/VLabel'
5
+ import { VCheckbox } from 'vuetify/components/VCheckbox'
6
+ import { VSwitch } from 'vuetify/components/VSwitch'
7
+ import { defineComponent, h, computed, toRef } from 'vue'
8
+ import useField from '../../composables/use-node.js'
9
+ import useGetItems from '../../composables/use-get-items.js'
10
+
11
+ export default defineComponent({
12
+ props: {
13
+ modelValue: {
14
+ /** @type import('vue').PropType<import('../../types.js').VjsfCheckboxGroupNode> */
15
+ type: Object,
16
+ required: true
17
+ },
18
+ statefulLayout: {
19
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
20
+ type: Object,
21
+ required: true
22
+ },
23
+ type: {
24
+ type: String,
25
+ required: true
26
+ }
27
+ },
28
+ setup (props) {
29
+ const nodeRef = toRef(props, 'modelValue')
30
+ const getItems = useGetItems(nodeRef, props.statefulLayout)
31
+ const { inputProps, compSlots, localData, layout } = useField(nodeRef, props.statefulLayout, { bindData: false })
32
+
33
+ const fieldProps = computed(() => {
34
+ const fieldProps = { ...inputProps.value }
35
+ fieldProps.class.push('v-radio-group') // reuse some styles from radio-group
36
+ fieldProps.class.push('vjsf-selection-group')
37
+ return fieldProps
38
+ })
39
+
40
+ const fieldSlots = computed(() => {
41
+ const slots = { ...compSlots.value }
42
+
43
+ if (!slots.default) {
44
+ slots.default = () => {
45
+ /** @type {import('vue').VNode[]} */
46
+ const children = [h(VLabel, { text: fieldProps.value.label })]
47
+ if (getItems.loading.value) {
48
+ children.push(h(VSkeletonLoader, { type: 'chip' }))
49
+ } else {
50
+ /** @type {import('vue').VNode[]} */
51
+ const checkboxes = []
52
+ for (const item of getItems.items.value) {
53
+ let modelValue = false
54
+ if (layout.value.multiple) {
55
+ modelValue = localData.value?.includes(item.value)
56
+ } else {
57
+ modelValue = localData.value === item.value
58
+ }
59
+ checkboxes.push(h(props.type === 'switch' ? VSwitch : VCheckbox, {
60
+ label: item.title,
61
+ hideDetails: true,
62
+ key: item.key,
63
+ modelValue,
64
+ onClick: () => {
65
+ let newValue
66
+ if (layout.value.multiple) {
67
+ newValue = props.modelValue.data ? [...props.modelValue.data] : []
68
+ if (newValue.includes(item.value)) {
69
+ newValue = newValue.filter((/** @type {any} */v) => v !== item.value)
70
+ } else {
71
+ newValue.push(item.value)
72
+ }
73
+ } else {
74
+ if (props.modelValue.data === item.value) {
75
+ newValue = undefined
76
+ } else {
77
+ newValue = item.value
78
+ }
79
+ }
80
+ props.statefulLayout.input(props.modelValue, newValue)
81
+ }
82
+ }))
83
+ }
84
+ children.push(h('div', { class: 'v-selection-control-group' }, checkboxes))
85
+ }
86
+ return children
87
+ }
88
+ }
89
+
90
+ return slots
91
+ })
92
+
93
+ return () => {
94
+ return h(VInput, fieldProps.value, fieldSlots.value)
95
+ }
96
+ }
97
+ })
98
+
99
+ </script>
100
+
101
+ <style>
102
+ .vjsf-selection-group .v-selection-control-group>.v-input .v-selection-control {
103
+ min-height: auto;
104
+ }
105
+ </style>
@@ -1,7 +1,8 @@
1
1
  <script setup>
2
- import { VMenu, VTextField } from 'vuetify/components'
3
- import { computed, ref } from 'vue'
4
- import { getCompProps, getInputProps } from '../../utils/index.js'
2
+ import { VMenu } from 'vuetify/components/VMenu'
3
+ import { VTextField } from 'vuetify/components/VTextField'
4
+ import { computed, ref, toRef } from 'vue'
5
+ import useField from '../../composables/use-node.js'
5
6
 
6
7
  const props = defineProps({
7
8
  modelValue: {
@@ -21,21 +22,29 @@ const props = defineProps({
21
22
  }
22
23
  })
23
24
 
25
+ const { inputProps, skeleton, compProps, data } = useField(
26
+ toRef(props, 'modelValue'), props.statefulLayout, { isMainComp: false, bindData: false }
27
+ )
28
+
24
29
  const fieldProps = computed(() => {
25
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout, [], false)
30
+ const fieldProps = { ...inputProps.value }
26
31
  fieldProps.readonly = true
32
+ fieldProps.clearable = fieldProps.clearable ?? !skeleton.value.required
33
+ fieldProps['onClick:clear'] = () => {
34
+ props.statefulLayout.input(props.modelValue, null)
35
+ }
27
36
  return fieldProps
28
37
  })
29
38
 
30
39
  const menuProps = computed(() => {
31
- const menuProps = getCompProps(props.modelValue)
40
+ const menuProps = { ...compProps.value }
32
41
  menuProps.closeOnContentClick = false
33
42
  menuProps.disabled = true
34
43
  return menuProps
35
44
  })
36
45
 
37
46
  const textField = ref(null)
38
- const menuOpened = ref(false)
47
+ const menuOpened = defineModel('menuOpened', { type: Boolean, default: false })
39
48
 
40
49
  </script>
41
50
 
@@ -43,7 +52,7 @@ const menuOpened = ref(false)
43
52
  <v-text-field
44
53
  ref="textField"
45
54
  v-bind="fieldProps"
46
- :model-value="formattedValue ?? modelValue.data"
55
+ :model-value="formattedValue ?? data"
47
56
  @click:control="e => {menuOpened = !menuOpened; e.stopPropagation()}"
48
57
  >
49
58
  <template #prepend-inner>