@koumoul/vjsf 3.0.0-beta.4 → 3.0.0-beta.41

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 (164) hide show
  1. package/README.md +21 -0
  2. package/package.json +5 -4
  3. package/src/compat/v2.js +132 -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/child-subtitle.vue +25 -0
  8. package/src/components/fragments/help-message.vue +33 -8
  9. package/src/components/fragments/section-header.vue +9 -7
  10. package/src/components/fragments/select-item-icon.vue +2 -2
  11. package/src/components/fragments/select-item.vue +2 -1
  12. package/src/components/fragments/select-selection.vue +2 -1
  13. package/src/components/fragments/selection-group.vue +104 -0
  14. package/src/components/fragments/text-field-menu.vue +8 -3
  15. package/src/components/node.vue +58 -41
  16. package/src/components/nodes/autocomplete.vue +14 -60
  17. package/src/components/nodes/card.vue +39 -0
  18. package/src/components/nodes/checkbox-group.vue +39 -0
  19. package/src/components/nodes/checkbox.vue +4 -1
  20. package/src/components/nodes/color-picker.vue +6 -2
  21. package/src/components/nodes/combobox.vue +4 -1
  22. package/src/components/nodes/date-picker.vue +19 -10
  23. package/src/components/nodes/date-time-picker.vue +80 -3
  24. package/src/components/nodes/expansion-panels.vue +28 -12
  25. package/src/components/nodes/file-input.vue +4 -1
  26. package/src/components/nodes/list.vue +239 -104
  27. package/src/components/nodes/number-combobox.vue +4 -1
  28. package/src/components/nodes/number-field.vue +4 -1
  29. package/src/components/nodes/one-of-select.vue +46 -24
  30. package/src/components/nodes/radio-group.vue +55 -0
  31. package/src/components/nodes/section.vue +4 -1
  32. package/src/components/nodes/select.vue +13 -52
  33. package/src/components/nodes/slider.vue +4 -1
  34. package/src/components/nodes/stepper.vue +10 -2
  35. package/src/components/nodes/switch-group.vue +39 -0
  36. package/src/components/nodes/switch.vue +4 -1
  37. package/src/components/nodes/tabs.vue +18 -5
  38. package/src/components/nodes/text-field.vue +4 -1
  39. package/src/components/nodes/textarea.vue +4 -1
  40. package/src/components/nodes/time-picker.vue +38 -1
  41. package/src/components/nodes/vertical-tabs.vue +14 -3
  42. package/src/components/options.js +21 -4
  43. package/src/components/tree.vue +1 -1
  44. package/src/components/vjsf.vue +11 -1
  45. package/src/composables/use-comp-defaults.js +19 -0
  46. package/src/composables/use-dnd.js +1 -0
  47. package/src/composables/use-get-items.js +48 -0
  48. package/src/composables/use-vjsf.js +76 -40
  49. package/src/index.js +3 -0
  50. package/src/types.ts +21 -7
  51. package/src/utils/arrays.js +37 -6
  52. package/src/utils/build.js +1 -1
  53. package/src/utils/index.js +0 -1
  54. package/src/utils/props.js +36 -12
  55. package/src/utils/slots.js +28 -0
  56. package/types/compat/v2.d.ts +10 -0
  57. package/types/compat/v2.d.ts.map +1 -0
  58. package/types/compile/index.d.ts +8 -0
  59. package/types/compile/index.d.ts.map +1 -0
  60. package/types/compile/options.d.ts +4 -0
  61. package/types/compile/options.d.ts.map +1 -0
  62. package/types/components/fragments/child-subtitle.vue.d.ts +8 -0
  63. package/types/components/fragments/child-subtitle.vue.d.ts.map +1 -0
  64. package/types/components/fragments/help-message.vue.d.ts +8 -0
  65. package/types/components/fragments/help-message.vue.d.ts.map +1 -0
  66. package/types/components/fragments/node-slot.vue.d.ts +47 -0
  67. package/types/components/fragments/node-slot.vue.d.ts.map +1 -0
  68. package/types/components/fragments/section-header.vue.d.ts +10 -0
  69. package/types/components/fragments/section-header.vue.d.ts.map +1 -0
  70. package/types/components/fragments/select-item-icon.vue.d.ts +15 -0
  71. package/types/components/fragments/select-item-icon.vue.d.ts.map +1 -0
  72. package/types/components/fragments/select-item.vue.d.ts +12 -0
  73. package/types/components/fragments/select-item.vue.d.ts.map +1 -0
  74. package/types/components/fragments/select-selection.vue.d.ts +12 -0
  75. package/types/components/fragments/select-selection.vue.d.ts.map +1 -0
  76. package/types/components/fragments/selection-group.vue.d.ts +35 -0
  77. package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
  78. package/types/components/fragments/text-field-menu.vue.d.ts +20 -0
  79. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -0
  80. package/types/components/node.vue.d.ts +10 -0
  81. package/types/components/node.vue.d.ts.map +1 -0
  82. package/types/components/nodes/autocomplete.vue.d.ts +27 -0
  83. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -0
  84. package/types/components/nodes/card.vue.d.ts +10 -0
  85. package/types/components/nodes/card.vue.d.ts.map +1 -0
  86. package/types/components/nodes/checkbox-group.vue.d.ts +27 -0
  87. package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
  88. package/types/components/nodes/checkbox.vue.d.ts +10 -0
  89. package/types/components/nodes/checkbox.vue.d.ts.map +1 -0
  90. package/types/components/nodes/color-picker.vue.d.ts +10 -0
  91. package/types/components/nodes/color-picker.vue.d.ts.map +1 -0
  92. package/types/components/nodes/combobox.vue.d.ts +27 -0
  93. package/types/components/nodes/combobox.vue.d.ts.map +1 -0
  94. package/types/components/nodes/date-picker.vue.d.ts +10 -0
  95. package/types/components/nodes/date-picker.vue.d.ts.map +1 -0
  96. package/types/components/nodes/date-time-picker.vue.d.ts +10 -0
  97. package/types/components/nodes/date-time-picker.vue.d.ts.map +1 -0
  98. package/types/components/nodes/expansion-panels.vue.d.ts +10 -0
  99. package/types/components/nodes/expansion-panels.vue.d.ts.map +1 -0
  100. package/types/components/nodes/file-input.vue.d.ts +27 -0
  101. package/types/components/nodes/file-input.vue.d.ts.map +1 -0
  102. package/types/components/nodes/list.vue.d.ts +10 -0
  103. package/types/components/nodes/list.vue.d.ts.map +1 -0
  104. package/types/components/nodes/number-combobox.vue.d.ts +27 -0
  105. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -0
  106. package/types/components/nodes/number-field.vue.d.ts +27 -0
  107. package/types/components/nodes/number-field.vue.d.ts.map +1 -0
  108. package/types/components/nodes/one-of-select.vue.d.ts +10 -0
  109. package/types/components/nodes/one-of-select.vue.d.ts.map +1 -0
  110. package/types/components/nodes/radio-group.vue.d.ts +27 -0
  111. package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
  112. package/types/components/nodes/section.vue.d.ts +10 -0
  113. package/types/components/nodes/section.vue.d.ts.map +1 -0
  114. package/types/components/nodes/select.vue.d.ts +27 -0
  115. package/types/components/nodes/select.vue.d.ts.map +1 -0
  116. package/types/components/nodes/slider.vue.d.ts +10 -0
  117. package/types/components/nodes/slider.vue.d.ts.map +1 -0
  118. package/types/components/nodes/stepper.vue.d.ts +10 -0
  119. package/types/components/nodes/stepper.vue.d.ts.map +1 -0
  120. package/types/components/nodes/switch-group.vue.d.ts +27 -0
  121. package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
  122. package/types/components/nodes/switch.vue.d.ts +10 -0
  123. package/types/components/nodes/switch.vue.d.ts.map +1 -0
  124. package/types/components/nodes/tabs.vue.d.ts +10 -0
  125. package/types/components/nodes/tabs.vue.d.ts.map +1 -0
  126. package/types/components/nodes/text-field.vue.d.ts +27 -0
  127. package/types/components/nodes/text-field.vue.d.ts.map +1 -0
  128. package/types/components/nodes/textarea.vue.d.ts +27 -0
  129. package/types/components/nodes/textarea.vue.d.ts.map +1 -0
  130. package/types/components/nodes/time-picker.vue.d.ts +10 -0
  131. package/types/components/nodes/time-picker.vue.d.ts.map +1 -0
  132. package/types/components/nodes/vertical-tabs.vue.d.ts +10 -0
  133. package/types/components/nodes/vertical-tabs.vue.d.ts.map +1 -0
  134. package/types/components/options.d.ts +4 -0
  135. package/types/components/options.d.ts.map +1 -0
  136. package/types/components/tree.vue.d.ts +10 -0
  137. package/types/components/tree.vue.d.ts.map +1 -0
  138. package/types/components/vjsf.vue.d.ts +15 -0
  139. package/types/components/vjsf.vue.d.ts.map +1 -0
  140. package/types/composables/use-comp-defaults.d.ts +8 -0
  141. package/types/composables/use-comp-defaults.d.ts.map +1 -0
  142. package/types/composables/use-dnd.d.ts +25 -0
  143. package/types/composables/use-dnd.d.ts.map +1 -0
  144. package/types/composables/use-get-items.d.ts +13 -0
  145. package/types/composables/use-get-items.d.ts.map +1 -0
  146. package/types/composables/use-vjsf.d.ts +16 -0
  147. package/types/composables/use-vjsf.d.ts.map +1 -0
  148. package/types/index.d.ts +7 -0
  149. package/types/index.d.ts.map +1 -0
  150. package/types/types.d.ts +96 -0
  151. package/types/types.d.ts.map +1 -0
  152. package/types/utils/arrays.d.ts +22 -0
  153. package/types/utils/arrays.d.ts.map +1 -0
  154. package/types/utils/build.d.ts +2 -0
  155. package/types/utils/build.d.ts.map +1 -0
  156. package/types/utils/dates.d.ts +7 -0
  157. package/types/utils/dates.d.ts.map +1 -0
  158. package/types/utils/index.d.ts +5 -0
  159. package/types/utils/index.d.ts.map +1 -0
  160. package/types/utils/props.d.ts +29 -0
  161. package/types/utils/props.d.ts.map +1 -0
  162. package/types/utils/slots.d.ts +15 -0
  163. package/types/utils/slots.d.ts.map +1 -0
  164. package/src/utils/global-register.js +0 -13
@@ -1,10 +1,23 @@
1
1
  <script setup>
2
2
  import { watch, computed, ref } from 'vue'
3
- import { VList, VListItem, VListItemAction, VBtn, VMenu, VIcon, VSheet, VSpacer, VDivider, VRow, VListSubheader } from 'vuetify/components'
4
- import { isSection, clone } from '@json-layout/core'
3
+ import { useDefaults, useTheme } from 'vuetify'
4
+ import { VList, VListItem, VListItemAction, VListItemTitle, VListSubheader } from 'vuetify/components/VList'
5
+ import { VRow } from 'vuetify/components/VGrid'
6
+ import { VTextField } from 'vuetify/components/VTextField'
7
+ import { VSheet } from 'vuetify/components/VSheet'
8
+ import { VDivider } from 'vuetify/components/VDivider'
9
+ import { VIcon } from 'vuetify/components/VIcon'
10
+ import { VBtn } from 'vuetify/components/VBtn'
11
+ import { VMenu } from 'vuetify/components/VMenu'
12
+ import { VForm } from 'vuetify/components/VForm'
13
+ import { isSection, clone, getRegexp } from '@json-layout/core'
5
14
  import Node from '../node.vue'
6
- import { moveArrayItem } from '../../utils/index.js'
15
+ import { moveDataItem } from '../../utils/index.js'
7
16
  import useDnd from '../../composables/use-dnd.js'
17
+ import useCompDefaults from '../../composables/use-comp-defaults.js'
18
+
19
+ useDefaults({}, 'VjsfList')
20
+ const vSheetProps = useCompDefaults('VjsfList-VSheet', { border: true })
8
21
 
9
22
  const props = defineProps({
10
23
  modelValue: {
@@ -19,15 +32,21 @@ const props = defineProps({
19
32
  }
20
33
  })
21
34
 
35
+ const theme = useTheme()
36
+
22
37
  /* use composable for drag and drop */
23
38
  const { activeDnd, sortableArray, draggable, hovered, dragging, itemBind, handleBind } = useDnd(props.modelValue.children, () => {
24
- props.statefulLayout.input(props.modelValue, sortableArray.value.map((child) => child.data))
39
+ const newData = props.modelValue.layout.indexed
40
+ ? sortableArray.value.reduce((a, child) => { a[child.key] = child.data; return a }, /** @type {Record<string, any>} */({}))
41
+ : sortableArray.value.map((child) => child.data)
42
+ console.log(newData)
43
+ props.statefulLayout.input(props.modelValue, newData)
25
44
  })
26
45
  watch(() => props.modelValue.children, (array) => { sortableArray.value = array })
27
46
 
28
47
  /* manage hovered and edited items */
29
48
  const editedItem = computed(() => {
30
- return props.statefulLayout.activeItems[props.modelValue.fullKey]
49
+ return props.statefulLayout.activatedItems[props.modelValue.fullKey]
31
50
  })
32
51
  const menuOpened = ref(-1)
33
52
  const activeItem = computed(() => {
@@ -48,11 +67,77 @@ const buttonDensity = computed(() => {
48
67
  return props.modelValue.options.density
49
68
  })
50
69
 
70
+ const pushEmptyItem = () => {
71
+ const newData = (props.modelValue.data ?? []).concat([undefined])
72
+ props.statefulLayout.input(props.modelValue, newData)
73
+ if (props.modelValue.layout.listEditMode === 'inline-single') {
74
+ props.statefulLayout.activateItem(props.modelValue, newData.length - 1)
75
+ }
76
+ }
77
+
78
+ const newKey = ref('')
79
+ /** @type {import('vue').Ref<InstanceType<typeof import('vuetify/components/VForm').VForm> | null>} */
80
+ const newKeyForm = ref(null)
81
+ const pushEmptyIndexedItem = () => {
82
+ if (!newKey.value) return
83
+ if (!newKeyForm.value) return
84
+ if (!newKeyForm.value.isValid) return
85
+ const newData = { ...(props.modelValue.data ?? {}), [newKey.value]: null }
86
+ props.statefulLayout.input(props.modelValue, newData)
87
+ if (props.modelValue.layout.listEditMode === 'inline-single') {
88
+ props.statefulLayout.activateItem(props.modelValue, Object.keys(newData).length - 1)
89
+ }
90
+ newKey.value = ''
91
+ newKeyForm.value?.reset()
92
+ }
93
+
94
+ /**
95
+ * @param {number} childIndex
96
+ */
97
+ const deleteItem = (childIndex) => {
98
+ if (props.modelValue.layout.indexed) {
99
+ const oldData = /** @type {Record<string, any>} */(props.modelValue.data)
100
+ const keys = Object.keys(props.modelValue.data)
101
+ /** @type {Record<string, any>} */
102
+ const newData = {}
103
+ for (let i = 0; i < keys.length; i++) {
104
+ if (i !== childIndex) newData[keys[i]] = oldData[keys[i]]
105
+ }
106
+ props.statefulLayout.input(props.modelValue, newData)
107
+ } else {
108
+ const newData = [...props.modelValue.data.slice(0, childIndex), ...props.modelValue.data.slice(childIndex + 1)]
109
+ props.statefulLayout.input(props.modelValue, newData)
110
+ }
111
+
112
+ menuOpened.value = -1
113
+ }
114
+
115
+ /**
116
+ * @param {import('@json-layout/core').StateNode} child
117
+ * @param {number} childIndex
118
+ */
119
+ const duplicateItem = (child, childIndex) => {
120
+ const newData = [...props.modelValue.data.slice(0, childIndex), clone(child.data), ...props.modelValue.data.slice(childIndex)]
121
+ props.statefulLayout.input(props.modelValue, newData)
122
+ if (props.modelValue.layout.listEditMode === 'inline-single') {
123
+ props.statefulLayout.activateItem(props.modelValue, childIndex + 1)
124
+ }
125
+ menuOpened.value = -1
126
+ }
127
+
128
+ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').StateNode} */child, /** @type {number} */childIndex) => {
129
+ if (editedItem.value === childIndex) return theme.current.value.colors.primary
130
+ if (child.validated && (child.error || child.childError)) return theme.current.value.colors.error
131
+ if (props.modelValue.options.readOnly) return 'transparent'
132
+ if (activeItem.value === childIndex) return theme.current.value.colors.primary
133
+ return 'transparent'
134
+ })
135
+
51
136
  </script>
52
137
 
53
138
  <template>
54
- <v-sheet :elevation="1">
55
- <v-list :density="modelValue.options.density">
139
+ <v-sheet v-bind="vSheetProps">
140
+ <v-list class="py-0">
56
141
  <v-list-subheader v-if="modelValue.layout.title">
57
142
  {{ modelValue.layout.title }}
58
143
  </v-list-subheader>
@@ -62,11 +147,17 @@ const buttonDensity = computed(() => {
62
147
  >
63
148
  <v-list-item
64
149
  v-bind="itemBind(childIndex)"
65
- :density="modelValue.options.density"
66
150
  :draggable="draggable === childIndex"
67
- :variant="editedItem === childIndex ? 'outlined' : 'flat'"
151
+ variant="flat"
152
+ :style="`border: 1px solid ${itemBorderColor(child, childIndex)}`"
68
153
  class="pa-1 vjsf-list-item"
69
154
  >
155
+ <v-list-item-title
156
+ v-if="modelValue.layout.indexed"
157
+ class="pl-4 pt-2"
158
+ >
159
+ {{ child.key }}
160
+ </v-list-item-title>
70
161
  <v-row class="ma-0">
71
162
  <node
72
163
  v-for="grandChild of isSection(child) ? child.children : [child]"
@@ -76,115 +167,153 @@ const buttonDensity = computed(() => {
76
167
  />
77
168
  </v-row>
78
169
  <template
79
- v-if="activeItem === childIndex"
170
+ v-if="!modelValue.options.readOnly && modelValue.layout.listActions.length"
80
171
  #append
81
172
  >
82
- <div>
83
- <v-list-item-action
84
- v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
85
- >
173
+ <div class="vjsf-list-item-actions-wrapper">
174
+ <v-list-item-action v-if="activeItem !== childIndex">
86
175
  <v-btn
87
- v-if="editedItem !== childIndex"
88
- :title="modelValue.messages.edit"
89
- icon="mdi-pencil"
176
+ style="visibility:hidden"
90
177
  variant="text"
91
- color="primary"
92
178
  :density="buttonDensity"
93
- @click="statefulLayout.activateItem(modelValue, childIndex)"
94
- />
95
- <v-btn
96
- v-else
97
- :title="modelValue.messages.edit"
98
179
  icon="mdi-pencil"
99
- variant="flat"
100
- color="primary"
101
- :density="buttonDensity"
102
- @click="statefulLayout.deactivateItem(modelValue)"
103
- />
104
- </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
180
  />
115
181
  </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}"
182
+ <template v-else>
183
+ <v-list-item-action
184
+ v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
122
185
  >
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>
186
+ <v-btn
187
+ v-if="editedItem !== childIndex"
188
+ :title="modelValue.messages.edit"
189
+ icon="mdi-pencil"
190
+ variant="text"
191
+ color="primary"
192
+ :density="buttonDensity"
193
+ @click="statefulLayout.activateItem(modelValue, childIndex)"
194
+ />
195
+ <v-btn
196
+ v-else
197
+ :title="modelValue.messages.close"
198
+ icon="mdi-close"
199
+ variant="flat"
200
+ color="primary"
201
+ :density="buttonDensity"
202
+ @click="statefulLayout.deactivateItem(modelValue)"
203
+ />
204
+ </v-list-item-action>
205
+ <v-list-item-action
206
+ v-if="editedItem === undefined && modelValue.layout.listActions.includes('sort') && activeDnd"
207
+ >
208
+ <v-btn
209
+ :title="modelValue.messages.sort"
210
+ icon="mdi-arrow-up-down"
211
+ variant="plain"
212
+ :density="buttonDensity"
213
+ v-bind="handleBind(childIndex)"
214
+ />
215
+ </v-list-item-action>
216
+ <v-list-item-action
217
+ v-if="editedItem === undefined && (modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate') || modelValue.layout.listActions.includes('sort'))"
218
+ >
219
+ <v-menu
220
+ location="bottom end"
221
+ @update:model-value="value => {menuOpened = value ? childIndex : -1}"
222
+ >
223
+ <template #activator="{props: activatorProps}">
224
+ <v-btn
225
+ v-bind="activatorProps"
226
+ icon="mdi-dots-vertical"
227
+ variant="plain"
228
+ slim
229
+ :density="buttonDensity"
230
+ />
231
+ </template>
232
+ <v-list>
233
+ <v-list-item
234
+ v-if="modelValue.layout.listActions.includes('delete')"
235
+ base-color="warning"
236
+ @click="deleteItem(childIndex)"
237
+ >
238
+ <template #prepend>
239
+ <v-icon icon="mdi-delete" />
240
+ </template>
241
+ {{ modelValue.messages.delete }}
242
+ </v-list-item>
243
+ <v-list-item
244
+ v-if="modelValue.layout.listActions.includes('duplicate')"
245
+ @click="duplicateItem(child, childIndex)"
246
+ >
247
+ <template #prepend>
248
+ <v-icon icon="mdi-content-duplicate" />
249
+ </template>
250
+ {{ modelValue.messages.duplicate }}
251
+ </v-list-item>
252
+ <v-list-item
253
+ v-if="modelValue.layout.listActions.includes('sort')"
254
+ @click="statefulLayout.input(modelValue, moveDataItem(modelValue.data, childIndex, childIndex - 1))"
255
+ >
256
+ <template #prepend>
257
+ <v-icon icon="mdi-arrow-up" />
258
+ </template>
259
+ {{ modelValue.messages.up }}
260
+ </v-list-item>
261
+ <v-list-item
262
+ v-if="modelValue.layout.listActions.includes('sort')"
263
+ @click="statefulLayout.input(modelValue, moveDataItem(modelValue.data, childIndex, childIndex + 1))"
264
+ >
265
+ <template #prepend>
266
+ <v-icon icon="mdi-arrow-down" />
267
+ </template>
268
+ {{ modelValue.messages.down }}
269
+ </v-list-item>
270
+ </v-list>
271
+ </v-menu>
272
+ </v-list-item-action>
273
+ </template>
173
274
  </div>
174
275
  </template>
175
276
  </v-list-item>
176
277
  <v-divider v-if="childIndex < modelValue.children.length - 1" />
177
278
  </template>
178
- <v-list-item v-if="!modelValue.options.readOnly && modelValue.layout.listActions.includes('add')">
179
- <v-spacer />
180
- <v-btn
181
- color="primary"
182
- :density="modelValue.options.density"
183
- @click="statefulLayout.input(modelValue, (modelValue.data ?? []).concat([undefined]))"
184
- >
185
- {{ modelValue.messages.addItem }}
186
- </v-btn>
187
- <v-spacer />
279
+ <v-list-item
280
+ v-if="!modelValue.options.readOnly && modelValue.layout.listActions.includes('add')"
281
+ class="py-2"
282
+ >
283
+ <template v-if="modelValue.layout.indexed">
284
+ <v-form
285
+ ref="newKeyForm"
286
+ style="max-width: 250px;"
287
+ @submit.prevent
288
+ >
289
+ <v-text-field
290
+ v-model="newKey"
291
+ variant="outlined"
292
+ :placeholder="modelValue.messages.addItem"
293
+ hide-details
294
+ :rules="[v => !modelValue.children.some(c => c.key === v), v => !v || modelValue.layout.indexed?.some(pattern => v.match(getRegexp(pattern)))]"
295
+ @keypress.enter="pushEmptyIndexedItem"
296
+ >
297
+ <template #append>
298
+ <v-icon
299
+ color="primary"
300
+ size="large"
301
+ @click="pushEmptyIndexedItem"
302
+ >
303
+ mdi-plus
304
+ </v-icon>
305
+ </template>
306
+ </v-text-field>
307
+ </v-form>
308
+ </template>
309
+ <template v-else>
310
+ <v-btn
311
+ color="primary"
312
+ @click="pushEmptyItem"
313
+ >
314
+ {{ modelValue.messages.addItem }}
315
+ </v-btn>
316
+ </template>
188
317
  </v-list-item>
189
318
  </v-list>
190
319
  </v-sheet>
@@ -195,4 +324,10 @@ const buttonDensity = computed(() => {
195
324
  height: 100%;
196
325
  align-items: start;
197
326
  }
327
+ .vjsf-list-item .v-list-item__content {
328
+ padding-right: 4px;
329
+ }
330
+ .vjsf-list-item-actions-wrapper {
331
+ /*margin: -4px;*/
332
+ }
198
333
  </style>
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { defineComponent, h, computed, shallowRef, ref } from 'vue'
3
- import { VCombobox } from 'vuetify/components'
3
+ import { VCombobox } from 'vuetify/components/VCombobox'
4
4
  import { getInputProps, getCompSlots } from '../../utils/index.js'
5
+ import { useDefaults } from 'vuetify'
5
6
 
6
7
  export default defineComponent({
7
8
  props: {
@@ -17,6 +18,8 @@ export default defineComponent({
17
18
  }
18
19
  },
19
20
  setup (props) {
21
+ useDefaults({}, 'VjsfNumberCombobox')
22
+
20
23
  /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
21
24
  const items = shallowRef(props.modelValue.layout.items ?? [])
22
25
  /** @type import('vue').Ref<boolean> */
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { defineComponent, h, computed } from 'vue'
3
- import { VTextField } from 'vuetify/components'
3
+ import { VTextField } from 'vuetify/components/VTextField'
4
4
  import { getInputProps, getCompSlots } from '../../utils/index.js'
5
+ import { useDefaults } from 'vuetify'
5
6
 
6
7
  export default defineComponent({
7
8
  props: {
@@ -17,6 +18,8 @@ export default defineComponent({
17
18
  }
18
19
  },
19
20
  setup (props) {
21
+ useDefaults({}, 'VjsfNumberField')
22
+
20
23
  const fieldProps = computed(() => {
21
24
  const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['step', 'min', 'max', 'placeholder'])
22
25
  fieldProps.type = 'number'
@@ -1,8 +1,14 @@
1
1
  <script setup>
2
- import { VSelect, VRow } from 'vuetify/components'
3
- import { shallowRef, watch } from 'vue'
2
+ import { VRow, VCol } from 'vuetify/components/VGrid'
3
+ import { VSelect } from 'vuetify/components/VSelect'
4
+ import { ref, watch, computed, h } from 'vue'
4
5
  import { isSection } from '@json-layout/core'
6
+ import { isCompObject } from '@json-layout/vocabulary'
7
+ import { getInputProps } from '../../utils/index.js'
5
8
  import Node from '../node.vue'
9
+ import { useDefaults } from 'vuetify'
10
+
11
+ useDefaults({}, 'VjsfOneOfSelect')
6
12
 
7
13
  const props = defineProps({
8
14
  modelValue: {
@@ -17,40 +23,56 @@ const props = defineProps({
17
23
  }
18
24
  })
19
25
 
20
- /** @type import('vue').ShallowRef<import('@json-layout/core').SkeletonTree | undefined> */
21
- const activeChildTree = shallowRef(undefined)
26
+ /** @type import('vue').Ref<string | undefined> */
27
+ const activeChildTree = ref(undefined)
22
28
  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]]
29
+ if (props.modelValue.children?.length === 1) {
30
+ if (typeof props.modelValue.children[0].key === 'number') {
31
+ activeChildTree.value = props.modelValue.skeleton.childrenTrees?.[props.modelValue.children[0].key]
32
+ }
26
33
  } else {
27
34
  activeChildTree.value = undefined
28
35
  }
29
36
  }, { immediate: true })
30
37
 
31
- const onChange = (/** @type import('@json-layout/core').SkeletonTree */childTree) => {
38
+ const onChange = (/** @type {string} */childTree) => {
32
39
  if (!props.modelValue.skeleton.childrenTrees) return
33
40
  props.statefulLayout.activateItem(props.modelValue, props.modelValue.skeleton.childrenTrees.indexOf(childTree))
34
41
  }
35
42
 
43
+ const fieldProps = computed(() => {
44
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
45
+ fieldProps.modelValue = activeChildTree.value
46
+ fieldProps['onUpdate:modelValue'] = onChange
47
+ const items = []
48
+ for (const childTreePointer of props.modelValue.skeleton.childrenTrees || []) {
49
+ const childTree = props.statefulLayout.compiledLayout.skeletonTrees[childTreePointer]
50
+ const childLayout = props.statefulLayout.compiledLayout.normalizedLayouts[childTree.root]
51
+ if (!isCompObject(childLayout) || !childLayout.if || !!props.statefulLayout.evalNodeExpression(props.modelValue, childLayout.if, props.modelValue.data)) {
52
+ items.push(childTree)
53
+ }
54
+ }
55
+ fieldProps.items = items
56
+ fieldProps.itemTitle = 'title'
57
+ fieldProps.itemValue = (/** @type {import('@json-layout/core').SkeletonTree} */childTree) => childTree.root
58
+ return fieldProps
59
+ })
36
60
  </script>
37
61
 
38
62
  <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
- />
63
+ <v-row>
64
+ <v-col v-if="modelValue.skeleton.childrenTrees">
65
+ <v-select
66
+ v-bind="fieldProps"
67
+ />
68
+ </v-col>
69
+ <template v-if="modelValue.children?.[0]">
70
+ <node
71
+ v-for="grandChild of isSection(modelValue.children?.[0]) ? modelValue.children?.[0].children : modelValue.children"
72
+ :key="grandChild.fullKey"
73
+ :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
74
+ :stateful-layout="statefulLayout"
75
+ />
76
+ </template>
55
77
  </v-row>
56
78
  </template>
@@ -0,0 +1,55 @@
1
+ <script>
2
+ import { VRadio } from 'vuetify/components/VRadio'
3
+ import { VRadioGroup } from 'vuetify/components/VRadioGroup'
4
+ import { VSkeletonLoader } from 'vuetify/components/VSkeletonLoader'
5
+ import { defineComponent, h, computed } from 'vue'
6
+ import { getInputProps, getCompSlots } from '../../utils/index.js'
7
+ import useGetItems from '../../composables/use-get-items.js'
8
+ import { useDefaults } from 'vuetify'
9
+
10
+ export default defineComponent({
11
+ props: {
12
+ modelValue: {
13
+ /** @type import('vue').PropType<import('../../types.js').VjsfRadioGroupNode> */
14
+ type: Object,
15
+ required: true
16
+ },
17
+ statefulLayout: {
18
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
19
+ type: Object,
20
+ required: true
21
+ }
22
+ },
23
+ setup (props) {
24
+ useDefaults({}, 'VjsfRadioGroup')
25
+
26
+ const getItems = useGetItems(props)
27
+
28
+ const fieldProps = computed(() => {
29
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
30
+ return fieldProps
31
+ })
32
+
33
+ const fieldSlots = computed(() => {
34
+ const slots = getCompSlots(props.modelValue, props.statefulLayout)
35
+ /** @type {import('vue').VNode[]} */
36
+ const children = []
37
+ if (getItems.loading.value) {
38
+ children.push(h(VSkeletonLoader, { type: 'chip' }))
39
+ } else {
40
+ for (const item of getItems.items.value) {
41
+ children.push(h(VRadio, { label: item.title, value: item.value }))
42
+ }
43
+ }
44
+ slots.default = () => children
45
+ return slots
46
+ })
47
+
48
+ // @ts-ignore
49
+ return () => {
50
+ return h(VRadioGroup, fieldProps.value, fieldSlots.value)
51
+ }
52
+ }
53
+ })
54
+
55
+ </script>
@@ -1,7 +1,10 @@
1
1
  <script setup>
2
- import { VRow } from 'vuetify/components'
2
+ import { VRow } from 'vuetify/components/VGrid'
3
3
  import Node from '../node.vue'
4
4
  import SectionHeader from '../fragments/section-header.vue'
5
+ import { useDefaults } from 'vuetify'
6
+
7
+ useDefaults({}, 'VjsfSection')
5
8
 
6
9
  defineProps({
7
10
  modelValue: {