@koumoul/vjsf 3.0.0-alpha.0 → 3.0.0-alpha.10

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 (129) hide show
  1. package/package.json +37 -10
  2. package/src/compat/v2.js +45 -10
  3. package/src/compile/index.js +42 -9
  4. package/src/compile/options.js +19 -0
  5. package/src/compile/v-jsf-compiled.vue.ejs +35 -57
  6. package/src/components/fragments/help-message.vue +49 -0
  7. package/src/components/fragments/node-slot.vue +3 -3
  8. package/src/components/fragments/section-header.vue +6 -2
  9. package/src/components/fragments/select-item-icon.vue +28 -0
  10. package/src/components/fragments/select-item.vue +43 -0
  11. package/src/components/fragments/select-selection.vue +35 -0
  12. package/src/components/fragments/text-field-menu.vue +4 -4
  13. package/src/components/node.vue +35 -9
  14. package/src/components/nodes/autocomplete.vue +88 -0
  15. package/src/components/nodes/checkbox.vue +3 -3
  16. package/src/components/nodes/color-picker.vue +3 -3
  17. package/src/components/nodes/combobox.vue +72 -0
  18. package/src/components/nodes/date-picker.vue +5 -6
  19. package/src/components/nodes/date-time-picker.vue +2 -2
  20. package/src/components/nodes/expansion-panels.vue +4 -4
  21. package/src/components/nodes/file-input.vue +43 -0
  22. package/src/components/nodes/list.vue +177 -91
  23. package/src/components/nodes/number-combobox.vue +72 -0
  24. package/src/components/nodes/number-field.vue +4 -6
  25. package/src/components/nodes/one-of-select.vue +4 -4
  26. package/src/components/nodes/section.vue +4 -3
  27. package/src/components/nodes/select.vue +72 -53
  28. package/src/components/nodes/slider.vue +3 -3
  29. package/src/components/nodes/stepper.vue +96 -0
  30. package/src/components/nodes/switch.vue +3 -3
  31. package/src/components/nodes/tabs.vue +4 -4
  32. package/src/components/nodes/text-field.vue +10 -5
  33. package/src/components/nodes/textarea.vue +26 -6
  34. package/src/components/nodes/vertical-tabs.vue +4 -4
  35. package/src/components/options.js +27 -2
  36. package/src/components/tree.vue +4 -3
  37. package/src/components/vjsf.vue +41 -103
  38. package/src/composables/use-dnd.js +69 -0
  39. package/src/composables/use-vjsf.js +122 -0
  40. package/src/index.js +3 -1
  41. package/src/styles/vjsf.css +14 -0
  42. package/src/{components/types.ts → types.ts} +26 -1
  43. package/src/utils/arrays.js +15 -0
  44. package/src/utils/build.js +1 -0
  45. package/src/utils/global-register.js +13 -0
  46. package/src/utils/index.js +5 -0
  47. package/src/utils/props.js +29 -10
  48. package/src/utils/slots.js +2 -3
  49. package/types/compat/v2.d.ts.map +1 -1
  50. package/types/compile/index.d.ts +3 -2
  51. package/types/compile/index.d.ts.map +1 -1
  52. package/types/compile/options.d.ts +4 -0
  53. package/types/compile/options.d.ts.map +1 -0
  54. package/types/components/fragments/help-message.vue.d.ts +8 -0
  55. package/types/components/fragments/help-message.vue.d.ts.map +1 -0
  56. package/types/components/fragments/node-slot.vue.d.ts +9 -9
  57. package/types/components/fragments/node-slot.vue.d.ts.map +1 -1
  58. package/types/components/fragments/section-header.vue.d.ts +3 -3
  59. package/types/components/fragments/select-item-icon.vue.d.ts +15 -0
  60. package/types/components/fragments/select-item-icon.vue.d.ts.map +1 -0
  61. package/types/components/fragments/select-item.vue.d.ts +12 -0
  62. package/types/components/fragments/select-item.vue.d.ts.map +1 -0
  63. package/types/components/fragments/select-selection.vue.d.ts +12 -0
  64. package/types/components/fragments/select-selection.vue.d.ts.map +1 -0
  65. package/types/components/fragments/text-field-menu.vue.d.ts +5 -5
  66. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
  67. package/types/components/global-register.d.ts +8 -0
  68. package/types/components/global-register.d.ts.map +1 -0
  69. package/types/components/node.vue.d.ts +5 -5
  70. package/types/components/nodes/autocomplete.vue.d.ts +27 -0
  71. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -0
  72. package/types/components/nodes/checkbox.vue.d.ts +5 -5
  73. package/types/components/nodes/color-picker.vue.d.ts +5 -5
  74. package/types/components/nodes/combobox.vue.d.ts +27 -0
  75. package/types/components/nodes/combobox.vue.d.ts.map +1 -0
  76. package/types/components/nodes/date-picker.vue.d.ts +5 -5
  77. package/types/components/nodes/date-time-picker.vue.d.ts +5 -5
  78. package/types/components/nodes/expansion-panels.vue.d.ts +5 -5
  79. package/types/components/nodes/file-input.vue.d.ts +27 -0
  80. package/types/components/nodes/file-input.vue.d.ts.map +1 -0
  81. package/types/components/nodes/list.vue.d.ts +5 -5
  82. package/types/components/nodes/markdown.vue.d.ts +27 -0
  83. package/types/components/nodes/markdown.vue.d.ts.map +1 -0
  84. package/types/components/nodes/number-combobox.vue.d.ts +27 -0
  85. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -0
  86. package/types/components/nodes/number-field.vue.d.ts +9 -9
  87. package/types/components/nodes/number-field.vue.d.ts.map +1 -1
  88. package/types/components/nodes/one-of-select.vue.d.ts +5 -5
  89. package/types/components/nodes/section.vue.d.ts +5 -5
  90. package/types/components/nodes/select.vue.d.ts +25 -8
  91. package/types/components/nodes/select.vue.d.ts.map +1 -1
  92. package/types/components/nodes/slider.vue.d.ts +5 -5
  93. package/types/components/nodes/stepper.vue.d.ts +10 -0
  94. package/types/components/nodes/stepper.vue.d.ts.map +1 -0
  95. package/types/components/nodes/switch.vue.d.ts +5 -5
  96. package/types/components/nodes/tabs.vue.d.ts +5 -5
  97. package/types/components/nodes/text-field.vue.d.ts +9 -9
  98. package/types/components/nodes/text-field.vue.d.ts.map +1 -1
  99. package/types/components/nodes/textarea.vue.d.ts +9 -9
  100. package/types/components/nodes/textarea.vue.d.ts.map +1 -1
  101. package/types/components/nodes/time-picker.vue.d.ts +1 -1
  102. package/types/components/nodes/vertical-tabs.vue.d.ts +5 -5
  103. package/types/components/options.d.ts +3 -2
  104. package/types/components/options.d.ts.map +1 -1
  105. package/types/components/tree.vue.d.ts +3 -3
  106. package/types/components/types.d.ts +22 -2
  107. package/types/components/types.d.ts.map +1 -1
  108. package/types/components/vjsf.vue.d.ts +8 -6
  109. package/types/composables/use-dnd.d.ts +25 -0
  110. package/types/composables/use-dnd.d.ts.map +1 -0
  111. package/types/composables/use-vjsf.d.ts +16 -0
  112. package/types/composables/use-vjsf.d.ts.map +1 -0
  113. package/types/index.d.ts +3 -1
  114. package/types/index.d.ts.map +1 -1
  115. package/types/types.d.ts +96 -0
  116. package/types/types.d.ts.map +1 -0
  117. package/types/utils/arrays.d.ts +9 -0
  118. package/types/utils/arrays.d.ts.map +1 -0
  119. package/types/utils/build.d.ts +2 -0
  120. package/types/utils/build.d.ts.map +1 -0
  121. package/types/utils/global-register.d.ts +8 -0
  122. package/types/utils/global-register.d.ts.map +1 -0
  123. package/types/utils/index.d.ts +6 -0
  124. package/types/utils/index.d.ts.map +1 -0
  125. package/types/utils/props.d.ts +8 -5
  126. package/types/utils/props.d.ts.map +1 -1
  127. package/types/utils/slots.d.ts +3 -3
  128. package/types/utils/slots.d.ts.map +1 -1
  129. package/src/utils/clone.js +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koumoul/vjsf",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.0-alpha.10",
4
4
  "description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
5
5
  "scripts": {
6
6
  "test": "vitest",
@@ -22,12 +22,41 @@
22
22
  "types": "./types/index.d.ts"
23
23
  }
24
24
  },
25
+ "./types.js": {
26
+ "import": {
27
+ "default": "./src/types.js",
28
+ "types": "./types/types.d.ts"
29
+ }
30
+ },
25
31
  "./components/*": {
26
32
  "import": {
27
33
  "default": "./src/components/*",
28
34
  "types": "./types/components/*.d.ts"
29
35
  }
30
36
  },
37
+ "./composables/*": {
38
+ "import": {
39
+ "default": "./src/composables/*",
40
+ "types": "./types/composables/*.d.ts"
41
+ }
42
+ },
43
+ "./utils": {
44
+ "import": {
45
+ "default": "./src/utils/index.js",
46
+ "types": "./types/utils/index.d.ts"
47
+ }
48
+ },
49
+ "./utils/*": {
50
+ "import": {
51
+ "default": "./src/utils/*",
52
+ "types": "./types/utils/*.d.ts"
53
+ }
54
+ },
55
+ "./styles/*": {
56
+ "import": {
57
+ "default": "./src/styles/*"
58
+ }
59
+ },
31
60
  "./compile": {
32
61
  "import": {
33
62
  "default": "./src/compile/index.js",
@@ -42,22 +71,20 @@
42
71
  }
43
72
  },
44
73
  "peerDependencies": {
45
- "vue": "^3.3.4",
46
- "vuetify": "^3.3.20"
74
+ "vue": "^3.4.3",
75
+ "vuetify": "^3.4.9"
47
76
  },
48
77
  "dependencies": {
49
- "@json-layout/core": "0.1.0",
50
- "@json-layout/vocabulary": "0.1.0",
78
+ "@json-layout/core": "0.8.0",
51
79
  "@vueuse/core": "^10.5.0",
52
80
  "debug": "^4.3.4",
53
- "ejs": "^3.1.9",
54
- "rfdc": "^1.3.0"
81
+ "ejs": "^3.1.9"
55
82
  },
56
83
  "devDependencies": {
57
84
  "@types/debug": "^4.1.8",
58
85
  "@types/ejs": "^3.1.2",
59
- "vitest": "^0.34.5",
60
- "vue": "^3.3.4",
61
- "vue-tsc": "^1.8.15"
86
+ "vitest": "^1.1.1",
87
+ "vue": "^3.4.3",
88
+ "vue-tsc": "^1.8.27"
62
89
  }
63
90
  }
package/src/compat/v2.js CHANGED
@@ -1,33 +1,68 @@
1
1
  import ajvModule from 'ajv'
2
- import rfdc from 'rfdc'
3
2
  import addFormats from 'ajv-formats'
4
- import { resolveRefs } from '@json-layout/core'
3
+ import { resolveRefs, clone } from '@json-layout/core'
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 clone = rfdc()
10
-
11
9
  const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
12
10
  if (!schema.layout) {
13
- schema.layout = {}
11
+ /** @type import('@json-layout/vocabulary').PartialCompObject */
12
+ const layout = {}
13
+
14
+ if (schema['x-display'] === 'icon' && (schema.enum || schema.items?.enum)) {
15
+ layout.getItems = { itemIcon: schema['x-itemIcon'] || 'data.value' }
16
+ delete schema['x-display']
17
+ }
18
+
14
19
  if (schema['x-display']) {
15
- schema.layout.comp = schema['x-display']
20
+ layout.comp = schema['x-display']
16
21
  delete schema['x-display']
17
22
  }
18
23
 
19
24
  if (schema.format === 'hexcolor') {
20
- schema.layout.comp = 'color-picker'
25
+ layout.comp = 'color-picker'
21
26
  delete schema.format
22
27
  }
23
28
 
24
29
  if (schema['x-props']) {
25
- schema.layout.props = schema['x-props']
30
+ layout.props = schema['x-props']
26
31
  delete schema['x-props']
27
32
  }
28
33
 
29
- if (Object.keys(schema.layout).length === 1 && 'comp' in schema.layout) {
30
- schema.layout = schema.layout.comp
34
+ if (schema['x-fromData']) {
35
+ layout.comp = 'select'
36
+ layout.getItems = { expr: schema['x-fromData'] }
37
+ delete schema['x-fromData']
38
+ }
39
+
40
+ if (schema['x-fromUrl']) {
41
+ /** @type string */
42
+ let url = schema['x-fromUrl']
43
+ for (const expressionMatch of url.matchAll(/\{(.*?)\}/g)) {
44
+ if (expressionMatch[1] !== 'q') url = url.replace(expressionMatch[0], '$' + expressionMatch[0])
45
+ }
46
+ layout.getItems = { url }
47
+ delete schema['x-fromUrl']
48
+ }
49
+ if (layout.getItems && isPartialGetItemsObj(layout.getItems)) {
50
+ if (schema['x-itemKey']) layout.getItems.itemKey = `data["${schema['x-itemKey']}"]`
51
+ if (schema['x-itemTitle']) layout.getItems.itemTitle = `data["${schema['x-itemTitle']}"]`
52
+ if (schema['x-itemValue']) layout.getItems.itemValue = `data["${schema['x-itemValue']}"]`
53
+ if (schema['x-itemIcon']) layout.getItems.itemIcon = `data["${schema['x-itemIcon']}"]`
54
+ if (schema['x-itemsProp']) layout.getItems.itemsResults = `data["${schema['x-itemsProp']}"]`
55
+ delete schema['x-itemKey']
56
+ delete schema['x-itemTitle']
57
+ delete schema['x-itemValue']
58
+ delete schema['x-itemsProp']
59
+ }
60
+
61
+ // compact the layout keyword if possible
62
+ if (Object.keys(layout).length === 1 && 'comp' in layout) {
63
+ schema.layout = layout.comp
64
+ } else if (Object.keys(layout).length > 0) {
65
+ schema.layout = layout
31
66
  }
32
67
  }
33
68
 
@@ -4,29 +4,62 @@ import path from 'path'
4
4
  import ejs from 'ejs'
5
5
  import { compile as compileLayout } from '@json-layout/core'
6
6
  import { serialize as serializeCompiledLayout } from '@json-layout/core/src/compile/serialize'
7
- import { isCompObject, isSwitchStruct } from '@json-layout/vocabulary'
7
+ import { childIsCompObject, isCompObject, isSwitchStruct } from '@json-layout/vocabulary'
8
+ import { getFullOptions } from './options.js'
8
9
 
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
11
 
11
12
  const template = readFileSync(path.join(__dirname, 'v-jsf-compiled.vue.ejs'), 'utf8')
12
13
 
14
+ /**
15
+ *
16
+ * @param {Set<string>} comps
17
+ * @param {import('@json-layout/vocabulary').NormalizedLayout} layout
18
+ */
19
+ function listComps (comps, layout) {
20
+ if (isCompObject(layout)) {
21
+ comps.add(layout.comp)
22
+ if (layout.children) {
23
+ for (const child of /** @type {import('@json-layout/vocabulary').Children} */(layout.children)) {
24
+ if (childIsCompObject(child)) {
25
+ listComps(comps, child)
26
+ }
27
+ }
28
+ }
29
+ } else if (isSwitchStruct(layout)) {
30
+ for (const switchCase of layout.switch) {
31
+ listComps(comps, switchCase)
32
+ }
33
+ }
34
+ }
35
+
13
36
  /**
14
37
  * @param {object} schema
15
- * @param {string} baseImport
38
+ * @param {import('../types.js').PartialVjsfCompileOptions} [options]
39
+ * @param {string} [baseImport]
16
40
  * @returns {string}
17
41
  */
18
- export function compile (schema, baseImport = '@koumoul/vjsf/components') {
19
- const compiledLayout = compileLayout(schema, { code: true })
42
+ export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
43
+ const fullOptions = getFullOptions(options)
44
+ const compiledLayout = compileLayout(schema, { ...fullOptions, code: true })
20
45
  const compiledLayoutCode = serializeCompiledLayout(compiledLayout)
21
46
  /** @type Set<string> */
22
47
  const comps = new Set([])
23
48
  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
- }
49
+ listComps(comps, layout)
28
50
  }
29
51
  comps.delete('none')
30
- const code = ejs.render(template, { compiledLayoutCode, comps, baseImport })
52
+
53
+ const compImports = [...comps].map(comp => {
54
+ const compName = comp.replace(/-/g, '') + 'Node'
55
+ const compImport = fullOptions.nodeComponentImports[comp] ?? `${baseImport}/components/nodes/${comp}.vue`
56
+ return {
57
+ comp,
58
+ compName,
59
+ compImport
60
+ }
61
+ })
62
+
63
+ const code = ejs.render(template, { compiledLayoutCode, compImports, baseImport })
31
64
  return code
32
65
  }
@@ -0,0 +1,19 @@
1
+ /** @type import("../types.js").PartialVjsfCompileOptions */
2
+ export const defaultOptions = {
3
+ nodeComponentImports: {
4
+ markdown: '@koumoul/vjsf-markdown/components/nodes/markdown.vue'
5
+ }
6
+ }
7
+
8
+ /**
9
+ *
10
+ * @param {import("../types.js").PartialVjsfCompileOptions} options
11
+ * @returns
12
+ */
13
+ export const getFullOptions = (options) => {
14
+ const fullOptions = {
15
+ ...defaultOptions,
16
+ nodeComponentImports: { ...defaultOptions.nodeComponentImports, ...options.nodeComponentImports }
17
+ }
18
+ return /** @type import('../types.js').VjsfCompileOptions */ (fullOptions)
19
+ }
@@ -5,75 +5,53 @@ import { StatefulLayout } from '@json-layout/core'
5
5
  import { ref, shallowRef, getCurrentInstance, useSlots } from 'vue'
6
6
  import { useElementSize } from '@vueuse/core'
7
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 %>
8
+ import { defaultOptions } from '<%- baseImport %>/components/options.js'
9
+ import Tree from '<%- baseImport %>/components/tree.vue'
10
+ import { useVjsf, emits } from '<%- baseImport %>/composables/use-vjsf.js'
11
+ import '<%- baseImport %>/styles/vjsf.css'
15
12
 
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
- }
13
+ <% compImports.forEach(function({compName, compImport}){ %>
14
+ import <%= compName %> from '<%- compImport %>'
21
15
  <% }); %>
22
16
 
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
- })
17
+ <%- compiledLayoutCode %>
44
18
 
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)
19
+ const nodeComponents = {
20
+ <% compImports.forEach(function({comp, compName}){ %>
21
+ "<%= comp %>": <%= compName %>,
22
+ <% }); %>
56
23
  }
57
24
 
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()
25
+ const props = defineProps({
26
+ modelValue: {
27
+ type: null,
28
+ default: null
29
+ },
30
+ options: {
31
+ /** @type import('vue').PropType<import('<%- baseImport %>/types.js').PartialVjsfOptions | null> */
32
+ type: Object,
33
+ default: null
65
34
  }
66
35
  })
67
36
 
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
-
37
+ const emit = defineEmits(emits)
38
+
39
+ const { el, statefulLayout, stateTree } = useVjsf(
40
+ null,
41
+ computed(() => props.modelValue),
42
+ computed(() => props.options),
43
+ nodeComponents,
44
+ emit,
45
+ null,
46
+ computed(() => compiledLayout)
47
+ )
73
48
  </script>
74
49
 
75
50
  <template>
76
- <div ref="el">
51
+ <div
52
+ ref="el"
53
+ class="vjsf"
54
+ >
77
55
  <tree
78
56
  v-if="statefulLayout && stateTree"
79
57
  :model-value="stateTree"
@@ -0,0 +1,49 @@
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, VSlideXReverseTransition } 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>
49
+ ../../../types.js
@@ -12,12 +12,12 @@ export default {
12
12
  required: true
13
13
  },
14
14
  node: {
15
- /** @type import('vue').PropType<import('../types.js').VjsfNode> */
15
+ /** @type import('vue').PropType<import('../../types.js').VjsfNode> */
16
16
  type: Object,
17
17
  required: true
18
18
  },
19
19
  statefulLayout: {
20
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
20
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
21
21
  type: Object,
22
22
  required: true
23
23
  },
@@ -38,7 +38,7 @@ export default {
38
38
  }
39
39
  if (isNameSlot(this.layoutSlot)) {
40
40
  if (!this.statefulLayout.options.vjsfSlots[this.layoutSlot.name]) {
41
- console.error(`layout references a code slot "${this.layoutSlot.name}" that was not provided.`)
41
+ console.error(`vjsf: layout references a code slot "${this.layoutSlot.name}" that was not provided.`)
42
42
  } else {
43
43
  return h(renderTag, this.statefulLayout.options.vjsfSlots[this.layoutSlot.name]({ node: this.node, statefulLayout: this.statefulLayout }))
44
44
  }
@@ -4,7 +4,7 @@ import { computed } from 'vue'
4
4
 
5
5
  const props = defineProps({
6
6
  node: {
7
- /** @type import('vue').PropType<import('../types.js').VjsfNode> */
7
+ /** @type import('vue').PropType<import('../../types.js').VjsfNode> */
8
8
  type: Object,
9
9
  required: true
10
10
  }
@@ -26,7 +26,10 @@ const titleClass = computed(() => {
26
26
  </script>
27
27
 
28
28
  <template>
29
- <div :class="`mb-${titleDepthBase - node.options.titleDepth} mt-${titleDepthBase - node.options.titleDepth}`">
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
+ >
30
33
  <component
31
34
  :is="`h${node.options.titleDepth}`"
32
35
  v-if="node.layout.title"
@@ -50,3 +53,4 @@ const titleClass = computed(() => {
50
53
  </v-alert>
51
54
  </div>
52
55
  </template>
56
+ ../../../types.js
@@ -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>
@@ -1,16 +1,16 @@
1
1
  <script setup>
2
2
  import { VMenu, VTextField } from 'vuetify/components'
3
3
  import { computed, ref } from 'vue'
4
- import { getCompProps, getInputProps } from '../../utils/props.js'
4
+ import { getCompProps, getInputProps } from '../../utils/index.js'
5
5
 
6
6
  const props = defineProps({
7
7
  modelValue: {
8
- /** @type import('vue').PropType<import('../types.js').VjsfNode> */
8
+ /** @type import('vue').PropType<import('../../types.js').VjsfNode> */
9
9
  type: Object,
10
10
  required: true
11
11
  },
12
12
  statefulLayout: {
13
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
13
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
14
14
  type: Object,
15
15
  required: true
16
16
  },
@@ -22,7 +22,7 @@ const props = defineProps({
22
22
  })
23
23
 
24
24
  const fieldProps = computed(() => {
25
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout, false)
25
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout, [], false)
26
26
  fieldProps.readonly = true
27
27
  return fieldProps
28
28
  })