@koumoul/vjsf 3.0.0-beta.3 → 3.0.0-beta.30

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 (82) hide show
  1. package/README.md +21 -0
  2. package/package.json +5 -4
  3. package/src/compat/v2.js +127 -27
  4. package/src/compile/index.js +18 -4
  5. package/src/compile/options.js +3 -7
  6. package/src/compile/v-jsf-compiled.vue.ejs +1 -1
  7. package/src/components/fragments/help-message.vue +11 -1
  8. package/src/components/fragments/section-header.vue +1 -2
  9. package/src/components/fragments/select-item-icon.vue +1 -1
  10. package/src/components/fragments/select-selection.vue +2 -1
  11. package/src/components/fragments/selection-group.vue +101 -0
  12. package/src/components/fragments/text-field-menu.vue +5 -1
  13. package/src/components/node.vue +4 -3
  14. package/src/components/nodes/autocomplete.vue +9 -35
  15. package/src/components/nodes/checkbox-group.vue +36 -0
  16. package/src/components/nodes/checkbox.vue +6 -1
  17. package/src/components/nodes/color-picker.vue +2 -1
  18. package/src/components/nodes/date-picker.vue +1 -1
  19. package/src/components/nodes/expansion-panels.vue +22 -12
  20. package/src/components/nodes/list.vue +143 -88
  21. package/src/components/nodes/one-of-select.vue +42 -24
  22. package/src/components/nodes/radio-group.vue +50 -0
  23. package/src/components/nodes/select.vue +7 -27
  24. package/src/components/nodes/switch-group.vue +36 -0
  25. package/src/components/nodes/switch.vue +6 -3
  26. package/src/components/options.js +21 -5
  27. package/src/components/vjsf.vue +6 -0
  28. package/src/composables/use-dnd.js +1 -0
  29. package/src/composables/use-get-items.js +48 -0
  30. package/src/composables/use-vjsf.js +76 -40
  31. package/src/index.js +3 -0
  32. package/src/types.ts +21 -6
  33. package/src/utils/build.js +1 -1
  34. package/src/utils/index.js +0 -1
  35. package/src/utils/props.js +9 -25
  36. package/types/compat/v2.d.ts.map +1 -1
  37. package/types/compile/index.d.ts +2 -2
  38. package/types/compile/index.d.ts.map +1 -1
  39. package/types/compile/options.d.ts.map +1 -1
  40. package/types/components/fragments/selection-group.vue.d.ts +35 -0
  41. package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
  42. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
  43. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
  44. package/types/components/nodes/checkbox-group copy.vue.d.ts +27 -0
  45. package/types/components/nodes/checkbox-group copy.vue.d.ts.map +1 -0
  46. package/types/components/nodes/checkbox-group.vue.d.ts +27 -0
  47. package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
  48. package/types/components/nodes/radio-group.vue.d.ts +27 -0
  49. package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
  50. package/types/components/nodes/radio.vue.d.ts +10 -0
  51. package/types/components/nodes/radio.vue.d.ts.map +1 -0
  52. package/types/components/nodes/select.vue.d.ts.map +1 -1
  53. package/types/components/nodes/switch-group.vue.d.ts +27 -0
  54. package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
  55. package/types/components/options.d.ts +1 -1
  56. package/types/components/options.d.ts.map +1 -1
  57. package/types/components/vjsf.vue.d.ts +2 -2
  58. package/types/composables/use-dnd.d.ts.map +1 -1
  59. package/types/composables/use-get-items.d.ts +13 -0
  60. package/types/composables/use-get-items.d.ts.map +1 -0
  61. package/types/composables/use-vjsf.d.ts.map +1 -1
  62. package/types/index.d.ts +2 -0
  63. package/types/index.d.ts.map +1 -1
  64. package/types/types.d.ts +20 -8
  65. package/types/types.d.ts.map +1 -1
  66. package/types/utils/build.d.ts +1 -1
  67. package/types/utils/index.d.ts +0 -1
  68. package/types/utils/props.d.ts +3 -4
  69. package/types/utils/props.d.ts.map +1 -1
  70. package/src/utils/global-register.js +0 -13
  71. package/types/components/global-register.d.ts +0 -8
  72. package/types/components/global-register.d.ts.map +0 -1
  73. package/types/components/nodes/markdown.vue.d.ts +0 -27
  74. package/types/components/nodes/markdown.vue.d.ts.map +0 -1
  75. package/types/components/nodes/text-field copy.vue.d.ts +0 -10
  76. package/types/components/nodes/text-field copy.vue.d.ts.map +0 -1
  77. package/types/components/types.d.ts +0 -91
  78. package/types/components/types.d.ts.map +0 -1
  79. package/types/components/v-jsf.vue.d.ts +0 -13
  80. package/types/components/v-jsf.vue.d.ts.map +0 -1
  81. package/types/utils/clone.d.ts +0 -3
  82. package/types/utils/clone.d.ts.map +0 -1
@@ -0,0 +1,36 @@
1
+ <script>
2
+ import SelectionGroup from '../fragments/selection-group.vue'
3
+ import { defineComponent, h } from 'vue'
4
+
5
+ export default defineComponent({
6
+ props: {
7
+ modelValue: {
8
+ /** @type import('vue').PropType<import('../../types.js').VjsfCheckboxGroupNode> */
9
+ type: Object,
10
+ required: true
11
+ },
12
+ statefulLayout: {
13
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
14
+ type: Object,
15
+ required: true
16
+ }
17
+ },
18
+ setup (props) {
19
+ // @ts-ignore
20
+ return () => {
21
+ return h(SelectionGroup, {
22
+ modelValue: props.modelValue,
23
+ statefulLayout: props.statefulLayout,
24
+ type: 'checkbox'
25
+ })
26
+ }
27
+ }
28
+ })
29
+
30
+ </script>
31
+
32
+ <style>
33
+ .vjsf-node-checkbox-group .v-selection-control-group .v-checkbox .v-selection-control {
34
+ min-height: auto;
35
+ }
36
+ </style>
@@ -16,7 +16,12 @@ const props = defineProps({
16
16
  }
17
17
  })
18
18
 
19
- const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout))
19
+ const fieldProps = computed(() => {
20
+ const inputProps = getInputProps(props.modelValue, props.statefulLayout)
21
+ // it is not very common to show an error below checkboxes and switches and without hide-details=auto they take a lot of space
22
+ if (!('hideDetails' in inputProps)) inputProps.hideDetails = 'auto'
23
+ return inputProps
24
+ })
20
25
  </script>
21
26
 
22
27
  <template>
@@ -18,7 +18,8 @@ const props = defineProps({
18
18
  })
19
19
 
20
20
  const colorPickerProps = computed(() => {
21
- const colorPickerProps = getCompProps(props.modelValue, 'colorPicker', true)
21
+ const colorPickerProps = getCompProps(props.modelValue, true)
22
+ colorPickerProps.modelValue = props.modelValue.data
22
23
  return colorPickerProps
23
24
  })
24
25
  </script>
@@ -21,7 +21,7 @@ const props = defineProps({
21
21
  const vDate = useDate()
22
22
 
23
23
  const datePickerProps = computed(() => {
24
- const datePickerProps = getCompProps(props.modelValue, 'datePicker', true)
24
+ const datePickerProps = getCompProps(props.modelValue, true)
25
25
  datePickerProps.hideActions = true
26
26
  if (props.modelValue.data) datePickerProps.modelValue = new Date(props.modelValue.data)
27
27
  return datePickerProps
@@ -1,8 +1,10 @@
1
1
  <script setup>
2
- import { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VContainer, VRow, VIcon } from 'vuetify/components'
2
+ import { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VExpansionPanelText, VContainer, VRow, VIcon } from 'vuetify/components'
3
+ import { computed } from 'vue'
3
4
  import { isSection } from '@json-layout/core'
4
5
  import Node from '../node.vue'
5
6
  import SectionHeader from '../fragments/section-header.vue'
7
+ import { getCompProps } from '../../utils/index.js'
6
8
 
7
9
  defineProps({
8
10
  modelValue: {
@@ -21,7 +23,7 @@ defineProps({
21
23
 
22
24
  <template>
23
25
  <section-header :node="modelValue" />
24
- <v-expansion-panels>
26
+ <v-expansion-panels v-bind="getCompProps(modelValue, true)">
25
27
  <v-expansion-panel
26
28
  v-for="(child, i) of modelValue.children"
27
29
  :key="child.key"
@@ -37,16 +39,24 @@ defineProps({
37
39
  </v-icon>
38
40
  {{ child.layout.title ?? child.layout.label }}
39
41
  </v-expansion-panel-title>
40
- <v-container fluid>
41
- <v-row>
42
- <node
43
- v-for="grandChild of isSection(child) ? child.children : [child]"
44
- :key="grandChild.fullKey"
45
- :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
46
- :stateful-layout="statefulLayout"
47
- />
48
- </v-row>
49
- </v-container>
42
+ <v-expansion-panel-text>
43
+ <v-container fluid>
44
+ <v-row>
45
+ <node
46
+ v-for="grandChild of isSection(child) ? child.children : [child]"
47
+ :key="grandChild.fullKey"
48
+ :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
49
+ :stateful-layout="statefulLayout"
50
+ />
51
+ </v-row>
52
+ </v-container>
53
+ </v-expansion-panel-text>
50
54
  </v-expansion-panel>
51
55
  </v-expansion-panels>
52
56
  </template>
57
+
58
+ <style>
59
+ .vjsf-node-expansion-panels .v-expansion-panel-text__wrapper {
60
+ padding: 0;
61
+ }
62
+ </style>
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { watch, computed, ref } from 'vue'
3
+ import { useTheme } from 'vuetify'
3
4
  import { VList, VListItem, VListItemAction, VBtn, VMenu, VIcon, VSheet, VSpacer, VDivider, VRow, VListSubheader } from 'vuetify/components'
4
5
  import { isSection, clone } from '@json-layout/core'
5
6
  import Node from '../node.vue'
@@ -19,6 +20,8 @@ const props = defineProps({
19
20
  }
20
21
  })
21
22
 
23
+ const theme = useTheme()
24
+
22
25
  /* use composable for drag and drop */
23
26
  const { activeDnd, sortableArray, draggable, hovered, dragging, itemBind, handleBind } = useDnd(props.modelValue.children, () => {
24
27
  props.statefulLayout.input(props.modelValue, sortableArray.value.map((child) => child.data))
@@ -27,7 +30,7 @@ watch(() => props.modelValue.children, (array) => { sortableArray.value = array
27
30
 
28
31
  /* manage hovered and edited items */
29
32
  const editedItem = computed(() => {
30
- return props.statefulLayout.activeItems[props.modelValue.fullKey]
33
+ return props.statefulLayout.activatedItems[props.modelValue.fullKey]
31
34
  })
32
35
  const menuOpened = ref(-1)
33
36
  const activeItem = computed(() => {
@@ -48,10 +51,51 @@ const buttonDensity = computed(() => {
48
51
  return props.modelValue.options.density
49
52
  })
50
53
 
54
+ const pushEmptyItem = () => {
55
+ const newData = (props.modelValue.data ?? []).concat([undefined])
56
+ props.statefulLayout.input(props.modelValue, newData)
57
+ if (props.modelValue.layout.listEditMode === 'inline-single') {
58
+ props.statefulLayout.activateItem(props.modelValue, newData.length - 1)
59
+ }
60
+ }
61
+
62
+ /**
63
+ * @param {number} childIndex
64
+ */
65
+ const deleteItem = (childIndex) => {
66
+ const newData = [...props.modelValue.data.slice(0, childIndex), ...props.modelValue.data.slice(childIndex + 1)]
67
+ props.statefulLayout.input(props.modelValue, newData)
68
+ menuOpened.value = -1
69
+ }
70
+
71
+ /**
72
+ * @param {import('@json-layout/core').StateNode} child
73
+ * @param {number} childIndex
74
+ */
75
+ const duplicateItem = (child, childIndex) => {
76
+ const newData = [...props.modelValue.data.slice(0, childIndex), clone(child.data), ...props.modelValue.data.slice(childIndex)]
77
+ props.statefulLayout.input(props.modelValue, newData)
78
+ if (props.modelValue.layout.listEditMode === 'inline-single') {
79
+ props.statefulLayout.activateItem(props.modelValue, childIndex + 1)
80
+ }
81
+ menuOpened.value = -1
82
+ }
83
+
84
+ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').StateNode} */child, /** @type {number} */childIndex) => {
85
+ if (editedItem.value === childIndex) return theme.current.value.colors.primary
86
+ if (child.validated && (child.error || child.childError)) return theme.current.value.colors.error
87
+ if (props.modelValue.options.readOnly) return 'transparent'
88
+ if (activeItem.value === childIndex) return theme.current.value.colors.primary
89
+ return 'transparent'
90
+ })
91
+
51
92
  </script>
52
93
 
53
94
  <template>
54
- <v-sheet :elevation="1">
95
+ <v-sheet
96
+ :elevation="2"
97
+ rounded
98
+ >
55
99
  <v-list :density="modelValue.options.density">
56
100
  <v-list-subheader v-if="modelValue.layout.title">
57
101
  {{ modelValue.layout.title }}
@@ -64,7 +108,8 @@ const buttonDensity = computed(() => {
64
108
  v-bind="itemBind(childIndex)"
65
109
  :density="modelValue.options.density"
66
110
  :draggable="draggable === childIndex"
67
- :variant="editedItem === childIndex ? 'outlined' : 'flat'"
111
+ variant="flat"
112
+ :style="`border: 1px solid ${itemBorderColor(child, childIndex)}`"
68
113
  class="pa-1 vjsf-list-item"
69
114
  >
70
115
  <v-row class="ma-0">
@@ -76,100 +121,110 @@ const buttonDensity = computed(() => {
76
121
  />
77
122
  </v-row>
78
123
  <template
79
- v-if="activeItem === childIndex"
124
+ v-if="!modelValue.options.readOnly && modelValue.layout.listActions.length"
80
125
  #append
81
126
  >
82
127
  <div>
83
- <v-list-item-action
84
- v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
85
- >
128
+ <v-list-item-action v-if="activeItem !== childIndex">
86
129
  <v-btn
87
- v-if="editedItem !== childIndex"
88
- :title="modelValue.messages.edit"
89
- icon="mdi-pencil"
130
+ style="visibility:hidden"
90
131
  variant="text"
91
- color="primary"
92
132
  :density="buttonDensity"
93
- @click="statefulLayout.activateItem(modelValue, childIndex)"
94
- />
95
- <v-btn
96
- v-else
97
- :title="modelValue.messages.edit"
98
133
  icon="mdi-pencil"
99
- variant="flat"
100
- color="primary"
101
- :density="buttonDensity"
102
- @click="statefulLayout.deactivateItem(modelValue)"
103
134
  />
104
135
  </v-list-item-action>
105
- <v-list-item-action
106
- v-if="editedItem === undefined && modelValue.layout.listActions.includes('sort') && activeDnd"
107
- >
108
- <v-btn
109
- :title="modelValue.messages.sort"
110
- icon="mdi-arrow-up-down"
111
- variant="plain"
112
- :density="buttonDensity"
113
- v-bind="handleBind(childIndex)"
114
- />
115
- </v-list-item-action>
116
- <v-list-item-action
117
- v-if="editedItem === undefined && (modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate') || modelValue.layout.listActions.includes('sort'))"
118
- >
119
- <v-menu
120
- location="bottom end"
121
- @update:model-value="value => {menuOpened = value ? childIndex : -1}"
136
+ <template v-else>
137
+ <v-list-item-action
138
+ v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
122
139
  >
123
- <template #activator="{props: activatorProps}">
124
- <v-btn
125
- v-bind="activatorProps"
126
- icon="mdi-dots-vertical"
127
- variant="plain"
128
- slim
129
- :density="buttonDensity"
130
- />
131
- </template>
132
- <v-list :density="modelValue.options.density">
133
- <v-list-item
134
- v-if="modelValue.layout.listActions.includes('delete')"
135
- base-color="warning"
136
- @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), ...modelValue.data.slice(childIndex + 1)])"
137
- >
138
- <template #prepend>
139
- <v-icon icon="mdi-delete" />
140
- </template>
141
- {{ modelValue.messages.delete }}
142
- </v-list-item>
143
- <v-list-item
144
- v-if="modelValue.layout.listActions.includes('duplicate')"
145
- @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), clone(child.data), ...modelValue.data.slice(childIndex)])"
146
- >
147
- <template #prepend>
148
- <v-icon icon="mdi-content-duplicate" />
149
- </template>
150
- {{ modelValue.messages.duplicate }}
151
- </v-list-item>
152
- <v-list-item
153
- v-if="modelValue.layout.listActions.includes('sort')"
154
- @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex - 1))"
155
- >
156
- <template #prepend>
157
- <v-icon icon="mdi-arrow-up" />
158
- </template>
159
- {{ modelValue.messages.up }}
160
- </v-list-item>
161
- <v-list-item
162
- v-if="modelValue.layout.listActions.includes('sort')"
163
- @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex + 1))"
164
- >
165
- <template #prepend>
166
- <v-icon icon="mdi-arrow-down" />
167
- </template>
168
- {{ modelValue.messages.down }}
169
- </v-list-item>
170
- </v-list>
171
- </v-menu>
172
- </v-list-item-action>
140
+ <v-btn
141
+ v-if="editedItem !== childIndex"
142
+ :title="modelValue.messages.edit"
143
+ icon="mdi-pencil"
144
+ variant="text"
145
+ color="primary"
146
+ :density="buttonDensity"
147
+ @click="statefulLayout.activateItem(modelValue, childIndex)"
148
+ />
149
+ <v-btn
150
+ v-else
151
+ :title="modelValue.messages.close"
152
+ icon="mdi-close"
153
+ variant="flat"
154
+ color="primary"
155
+ :density="buttonDensity"
156
+ @click="statefulLayout.deactivateItem(modelValue)"
157
+ />
158
+ </v-list-item-action>
159
+ <v-list-item-action
160
+ v-if="editedItem === undefined && modelValue.layout.listActions.includes('sort') && activeDnd"
161
+ >
162
+ <v-btn
163
+ :title="modelValue.messages.sort"
164
+ icon="mdi-arrow-up-down"
165
+ variant="plain"
166
+ :density="buttonDensity"
167
+ v-bind="handleBind(childIndex)"
168
+ />
169
+ </v-list-item-action>
170
+ <v-list-item-action
171
+ v-if="editedItem === undefined && (modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate') || modelValue.layout.listActions.includes('sort'))"
172
+ >
173
+ <v-menu
174
+ location="bottom end"
175
+ @update:model-value="value => {menuOpened = value ? childIndex : -1}"
176
+ >
177
+ <template #activator="{props: activatorProps}">
178
+ <v-btn
179
+ v-bind="activatorProps"
180
+ icon="mdi-dots-vertical"
181
+ variant="plain"
182
+ slim
183
+ :density="buttonDensity"
184
+ />
185
+ </template>
186
+ <v-list :density="modelValue.options.density">
187
+ <v-list-item
188
+ v-if="modelValue.layout.listActions.includes('delete')"
189
+ base-color="warning"
190
+ @click="deleteItem(childIndex)"
191
+ >
192
+ <template #prepend>
193
+ <v-icon icon="mdi-delete" />
194
+ </template>
195
+ {{ modelValue.messages.delete }}
196
+ </v-list-item>
197
+ <v-list-item
198
+ v-if="modelValue.layout.listActions.includes('duplicate')"
199
+ @click="duplicateItem(child, childIndex)"
200
+ >
201
+ <template #prepend>
202
+ <v-icon icon="mdi-content-duplicate" />
203
+ </template>
204
+ {{ modelValue.messages.duplicate }}
205
+ </v-list-item>
206
+ <v-list-item
207
+ v-if="modelValue.layout.listActions.includes('sort')"
208
+ @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex - 1))"
209
+ >
210
+ <template #prepend>
211
+ <v-icon icon="mdi-arrow-up" />
212
+ </template>
213
+ {{ modelValue.messages.up }}
214
+ </v-list-item>
215
+ <v-list-item
216
+ v-if="modelValue.layout.listActions.includes('sort')"
217
+ @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex + 1))"
218
+ >
219
+ <template #prepend>
220
+ <v-icon icon="mdi-arrow-down" />
221
+ </template>
222
+ {{ modelValue.messages.down }}
223
+ </v-list-item>
224
+ </v-list>
225
+ </v-menu>
226
+ </v-list-item-action>
227
+ </template>
173
228
  </div>
174
229
  </template>
175
230
  </v-list-item>
@@ -180,7 +235,7 @@ const buttonDensity = computed(() => {
180
235
  <v-btn
181
236
  color="primary"
182
237
  :density="modelValue.options.density"
183
- @click="statefulLayout.input(modelValue, (modelValue.data ?? []).concat([undefined]))"
238
+ @click="pushEmptyItem"
184
239
  >
185
240
  {{ modelValue.messages.addItem }}
186
241
  </v-btn>
@@ -1,7 +1,9 @@
1
1
  <script setup>
2
- import { VSelect, VRow } from 'vuetify/components'
3
- import { shallowRef, watch } from 'vue'
2
+ import { VSelect, VRow, VCol } from 'vuetify/components'
3
+ import { ref, watch, computed, h } from 'vue'
4
4
  import { isSection } from '@json-layout/core'
5
+ import { isCompObject } from '@json-layout/vocabulary'
6
+ import { getInputProps } from '../../utils/index.js'
5
7
  import Node from '../node.vue'
6
8
 
7
9
  const props = defineProps({
@@ -17,40 +19,56 @@ const props = defineProps({
17
19
  }
18
20
  })
19
21
 
20
- /** @type import('vue').ShallowRef<import('@json-layout/core').SkeletonTree | undefined> */
21
- const activeChildTree = shallowRef(undefined)
22
+ /** @type import('vue').Ref<string | undefined> */
23
+ const activeChildTree = ref(undefined)
22
24
  watch(() => props.modelValue, () => {
23
- // we set the active oneOf child as the one whose schema validates on the current data
24
- if (props.modelValue.fullKey in props.statefulLayout.activeItems) {
25
- activeChildTree.value = props.modelValue.skeleton.childrenTrees?.[props.statefulLayout.activeItems[props.modelValue.fullKey]]
25
+ if (props.modelValue.children?.length === 1) {
26
+ if (typeof props.modelValue.children[0].key === 'number') {
27
+ activeChildTree.value = props.modelValue.skeleton.childrenTrees?.[props.modelValue.children[0].key]
28
+ }
26
29
  } else {
27
30
  activeChildTree.value = undefined
28
31
  }
29
32
  }, { immediate: true })
30
33
 
31
- const onChange = (/** @type import('@json-layout/core').SkeletonTree */childTree) => {
34
+ const onChange = (/** @type {string} */childTree) => {
32
35
  if (!props.modelValue.skeleton.childrenTrees) return
33
36
  props.statefulLayout.activateItem(props.modelValue, props.modelValue.skeleton.childrenTrees.indexOf(childTree))
34
37
  }
35
38
 
39
+ const fieldProps = computed(() => {
40
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
41
+ fieldProps.modelValue = activeChildTree.value
42
+ fieldProps['onUpdate:modelValue'] = onChange
43
+ const items = []
44
+ for (const childTreePointer of props.modelValue.skeleton.childrenTrees || []) {
45
+ const childTree = props.statefulLayout.compiledLayout.skeletonTrees[childTreePointer]
46
+ const childLayout = props.statefulLayout.compiledLayout.normalizedLayouts[childTree.root]
47
+ if (!isCompObject(childLayout) || !childLayout.if || !!props.statefulLayout.evalNodeExpression(props.modelValue, childLayout.if, props.modelValue.data)) {
48
+ items.push(childTree)
49
+ }
50
+ }
51
+ fieldProps.items = items
52
+ fieldProps.itemTitle = 'title'
53
+ fieldProps.itemValue = (/** @type {import('@json-layout/core').SkeletonTree} */childTree) => childTree.root
54
+ return fieldProps
55
+ })
36
56
  </script>
37
57
 
38
58
  <template>
39
- <v-select
40
- v-if="modelValue.skeleton.childrenTrees"
41
- v-model="activeChildTree"
42
- :items="modelValue.skeleton.childrenTrees"
43
- item-title="title"
44
- return-object
45
- :error-messages="modelValue.validated ? modelValue.error : null"
46
- @update:model-value="onChange"
47
- />
48
- <v-row v-if="modelValue.children?.[0]">
49
- <node
50
- v-for="grandChild of isSection(modelValue.children?.[0]) ? modelValue.children?.[0].children : modelValue.children"
51
- :key="grandChild.fullKey"
52
- :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
53
- :stateful-layout="statefulLayout"
54
- />
59
+ <v-row>
60
+ <v-col v-if="modelValue.skeleton.childrenTrees">
61
+ <v-select
62
+ v-bind="fieldProps"
63
+ />
64
+ </v-col>
65
+ <template v-if="modelValue.children?.[0]">
66
+ <node
67
+ v-for="grandChild of isSection(modelValue.children?.[0]) ? modelValue.children?.[0].children : modelValue.children"
68
+ :key="grandChild.fullKey"
69
+ :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
70
+ :stateful-layout="statefulLayout"
71
+ />
72
+ </template>
55
73
  </v-row>
56
74
  </template>
@@ -0,0 +1,50 @@
1
+ <script>
2
+ import { VRadioGroup, VRadio, VSkeletonLoader } from 'vuetify/components'
3
+ import { defineComponent, h, computed } from 'vue'
4
+ import { getInputProps, getCompSlots } from '../../utils/index.js'
5
+ import useGetItems from '../../composables/use-get-items.js'
6
+
7
+ export default defineComponent({
8
+ props: {
9
+ modelValue: {
10
+ /** @type import('vue').PropType<import('../../types.js').VjsfRadioGroupNode> */
11
+ type: Object,
12
+ required: true
13
+ },
14
+ statefulLayout: {
15
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
16
+ type: Object,
17
+ required: true
18
+ }
19
+ },
20
+ setup (props) {
21
+ const getItems = useGetItems(props)
22
+
23
+ const fieldProps = computed(() => {
24
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
25
+ return fieldProps
26
+ })
27
+
28
+ const fieldSlots = computed(() => {
29
+ const slots = getCompSlots(props.modelValue, props.statefulLayout)
30
+ /** @type {import('vue').VNode[]} */
31
+ const children = []
32
+ if (getItems.loading.value) {
33
+ children.push(h(VSkeletonLoader, { type: 'chip' }))
34
+ } else {
35
+ for (const item of getItems.items.value) {
36
+ children.push(h(VRadio, { label: item.title, value: item.value }))
37
+ }
38
+ }
39
+ slots.default = () => children
40
+ return slots
41
+ })
42
+
43
+ // @ts-ignore
44
+ return () => {
45
+ return h(VRadioGroup, fieldProps.value, fieldSlots.value)
46
+ }
47
+ }
48
+ })
49
+
50
+ </script>
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { VSelect } from 'vuetify/components'
3
- import { defineComponent, h, computed, ref, shallowRef } from 'vue'
3
+ import { defineComponent, h, computed } from 'vue'
4
4
  import { getInputProps, getCompSlots } from '../../utils/index.js'
5
+ import useGetItems from '../../composables/use-get-items.js'
5
6
  import SelectItem from '../fragments/select-item.vue'
6
7
  import SelectSelection from '../fragments/select-selection.vue'
7
8
 
@@ -19,38 +20,17 @@ export default defineComponent({
19
20
  }
20
21
  },
21
22
  setup (props) {
22
- /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
23
- const items = shallowRef([])
24
- /** @type import('vue').Ref<boolean> */
25
- const loading = ref(false)
23
+ const getItems = useGetItems(props)
26
24
 
27
25
  const fieldProps = computed(() => {
28
26
  const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['multiple'])
29
27
  if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
30
- fieldProps.loading = loading.value
31
- fieldProps.items = items.value
32
- fieldProps['onUpdate:menu'] = refresh
28
+ fieldProps.loading = getItems.loading.value
29
+ fieldProps.items = getItems.items.value
30
+ fieldProps.clearable = fieldProps.clearable ?? !props.modelValue.skeleton.required
33
31
  return fieldProps
34
32
  })
35
33
 
36
- /** @type import('@json-layout/core').StateTree | null */
37
- let lastStateTree = null
38
- /** @type Record<string, any> | null */
39
- let lastContext = null
40
-
41
- const refresh = async () => {
42
- if (props.statefulLayout.stateTree === lastStateTree && props.statefulLayout.options.context === lastContext) return
43
- lastStateTree = props.statefulLayout.stateTree
44
- lastContext = props.statefulLayout.options.context ?? null
45
- loading.value = true
46
- items.value = await props.statefulLayout.getItems(props.modelValue)
47
- loading.value = false
48
- }
49
-
50
- if (!props.modelValue.layout.items) {
51
- refresh()
52
- }
53
-
54
34
  const fieldSlots = computed(() => {
55
35
  const slots = getCompSlots(props.modelValue, props.statefulLayout)
56
36
  if (!slots.item) {
@@ -64,7 +44,7 @@ export default defineComponent({
64
44
  slots.selection = (/** @type {any} */ context) => h(SelectSelection, {
65
45
  multiple: props.modelValue.layout.multiple,
66
46
  last: props.modelValue.layout.multiple && context.index === props.modelValue.data.length - 1,
67
- item: context.item.raw
47
+ item: getItems.prepareSelectedItem(context.item.raw, context.item.value)
68
48
  })
69
49
  }
70
50
  return slots
@@ -0,0 +1,36 @@
1
+ <script>
2
+ import SelectionGroup from '../fragments/selection-group.vue'
3
+ import { defineComponent, h } from 'vue'
4
+
5
+ export default defineComponent({
6
+ props: {
7
+ modelValue: {
8
+ /** @type import('vue').PropType<import('../../types.js').VjsfCheckboxGroupNode> */
9
+ type: Object,
10
+ required: true
11
+ },
12
+ statefulLayout: {
13
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
14
+ type: Object,
15
+ required: true
16
+ }
17
+ },
18
+ setup (props) {
19
+ // @ts-ignore
20
+ return () => {
21
+ return h(SelectionGroup, {
22
+ modelValue: props.modelValue,
23
+ statefulLayout: props.statefulLayout,
24
+ type: 'switch'
25
+ })
26
+ }
27
+ }
28
+ })
29
+
30
+ </script>
31
+
32
+ <style>
33
+ .vjsf-node-checkbox-group .v-selection-control-group .v-checkbox .v-selection-control {
34
+ min-height: auto;
35
+ }
36
+ </style>