@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
@@ -1,112 +1,198 @@
1
1
  <script setup>
2
- import { VList, VListItem, VListItemAction, VBtn, VMenu, VIcon } from 'vuetify/components'
3
- import { isSection } from '@json-layout/core'
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'
4
5
  import Node from '../node.vue'
5
- import clone from '../../utils/clone.js'
6
+ import { moveArrayItem } from '../../utils/index.js'
7
+ import useDnd from '../../composables/use-dnd.js'
6
8
 
7
- defineProps({
9
+ const props = defineProps({
8
10
  modelValue: {
9
- /** @type import('vue').PropType<import('../types.js').VjsfListNode> */
11
+ /** @type import('vue').PropType<import('../../types.js').VjsfListNode> */
10
12
  type: Object,
11
13
  required: true
12
14
  },
13
15
  statefulLayout: {
14
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
16
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
15
17
  type: Object,
16
18
  required: true
17
19
  }
18
20
  })
19
21
 
22
+ /* use composable for drag and drop */
23
+ const { activeDnd, sortableArray, draggable, hovered, dragging, itemBind, handleBind } = useDnd(props.modelValue.children, () => {
24
+ props.statefulLayout.input(props.modelValue, sortableArray.value.map((child) => child.data))
25
+ })
26
+ watch(() => props.modelValue.children, (array) => { sortableArray.value = array })
27
+
28
+ /* manage hovered and edited items */
29
+ const editedItem = computed(() => {
30
+ return props.statefulLayout.activeItems[props.modelValue.fullKey]
31
+ })
32
+ const menuOpened = ref(-1)
33
+ const activeItem = computed(() => {
34
+ if (
35
+ props.modelValue.layout.listActions.includes('edit') &&
36
+ props.modelValue.layout.listEditMode === 'inline-single' &&
37
+ editedItem.value !== undefined
38
+ ) {
39
+ return editedItem.value
40
+ }
41
+ if (dragging.value !== -1) return -1
42
+ if (menuOpened.value !== -1) return menuOpened.value
43
+ return hovered.value
44
+ })
45
+
46
+ const buttonDensity = computed(() => {
47
+ if (props.modelValue.options.density === 'default') return 'comfortable'
48
+ return props.modelValue.options.density
49
+ })
50
+
20
51
  </script>
21
52
 
22
53
  <template>
23
- <v-list :density="modelValue.options.density">
24
- <v-list-subheader v-if="modelValue.layout.title">
25
- {{ modelValue.layout.title }}
26
- </v-list-subheader>
27
- <v-list-item
28
- v-for="(child, childIndex) of modelValue.children"
29
- :key="child.key"
30
- >
31
- <v-row>
32
- <node
33
- v-for="grandChild of isSection(child) ? child.children : [child]"
34
- :key="grandChild.fullKey"
35
- :model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
36
- :stateful-layout="statefulLayout"
37
- />
38
- </v-row>
39
- <template #append>
40
- <v-list-item-action
41
- v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
42
- class="ml-1"
43
- >
44
- <v-btn
45
- v-if="child.options.readOnly"
46
- :title="modelValue.messages.edit"
47
- icon="mdi-pencil"
48
- variant="text"
49
- color="primary"
50
- :density="modelValue.options.density"
51
- @click="statefulLayout.activateItem(modelValue, childIndex)"
52
- />
53
- <v-btn
54
- v-else
55
- :title="modelValue.messages.edit"
56
- icon="mdi-pencil"
57
- variant="flat"
58
- color="primary"
59
- :density="modelValue.options.density"
60
- @click="statefulLayout.deactivateItem(modelValue)"
61
- />
62
- </v-list-item-action>
63
- <v-list-item-action
64
- v-if="modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate')"
65
- class="ml-1"
54
+ <v-sheet :elevation="1">
55
+ <v-list :density="modelValue.options.density">
56
+ <v-list-subheader v-if="modelValue.layout.title">
57
+ {{ modelValue.layout.title }}
58
+ </v-list-subheader>
59
+ <template
60
+ v-for="(child, childIndex) of sortableArray"
61
+ :key="props.modelValue.children.findIndex(c => c === child)"
62
+ >
63
+ <v-list-item
64
+ v-bind="itemBind(childIndex)"
65
+ :density="modelValue.options.density"
66
+ :draggable="draggable === childIndex"
67
+ :variant="editedItem === childIndex ? 'outlined' : 'flat'"
68
+ class="pa-1 vjsf-list-item"
66
69
  >
67
- <v-menu location="bottom end">
68
- <template #activator="{props: activatorProps}">
69
- <v-btn
70
- v-bind="activatorProps"
71
- icon="mdi-dots-vertical"
72
- variant="plain"
73
- :density="modelValue.options.density"
74
- />
75
- </template>
76
- <v-list :density="modelValue.options.density">
77
- <v-list-item
78
- v-if="modelValue.layout.listActions.includes('delete')"
79
- base-color="warning"
80
- @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), ...modelValue.data.slice(childIndex + 1)])"
70
+ <v-row class="ma-0">
71
+ <node
72
+ v-for="grandChild of isSection(child) ? child.children : [child]"
73
+ :key="grandChild.fullKey"
74
+ :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
75
+ :stateful-layout="statefulLayout"
76
+ />
77
+ </v-row>
78
+ <template
79
+ v-if="activeItem === childIndex"
80
+ #append
81
+ >
82
+ <div>
83
+ <v-list-item-action
84
+ v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
81
85
  >
82
- <template #prepend>
83
- <v-icon icon="mdi-delete" />
84
- </template>
85
- {{ modelValue.messages.delete }}
86
- </v-list-item>
87
-
88
- <v-list-item
89
- v-if="modelValue.layout.listActions.includes('duplicate')"
90
- @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), clone(child.data), ...modelValue.data.slice(childIndex)])"
86
+ <v-btn
87
+ v-if="editedItem !== childIndex"
88
+ :title="modelValue.messages.edit"
89
+ icon="mdi-pencil"
90
+ variant="text"
91
+ color="primary"
92
+ :density="buttonDensity"
93
+ @click="statefulLayout.activateItem(modelValue, childIndex)"
94
+ />
95
+ <v-btn
96
+ v-else
97
+ :title="modelValue.messages.edit"
98
+ 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
+ />
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'))"
91
118
  >
92
- <template #prepend>
93
- <v-icon icon="mdi-content-duplicate" />
94
- </template>
95
- {{ modelValue.messages.duplicate }}
96
- </v-list-item>
97
- </v-list>
98
- </v-menu>
99
- </v-list-item-action>
119
+ <v-menu
120
+ location="bottom end"
121
+ @update:model-value="value => {menuOpened = value ? childIndex : -1}"
122
+ >
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>
173
+ </div>
174
+ </template>
175
+ </v-list-item>
176
+ <v-divider v-if="childIndex < modelValue.children.length - 1" />
100
177
  </template>
101
- </v-list-item>
102
- <v-list-item v-if="!modelValue.options.readOnly && modelValue.layout.listActions.includes('add')">
103
- <v-btn
104
- color="primary"
105
- :density="modelValue.options.density"
106
- @click="statefulLayout.input(modelValue, (modelValue.data || []).concat([clone(modelValue.skeleton.childrenTrees?.[0]?.root.defaultData)]), (modelValue.data || []).length)"
107
- >
108
- {{ modelValue.messages.addItem }}
109
- </v-btn>
110
- </v-list-item>
111
- </v-list>
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 />
188
+ </v-list-item>
189
+ </v-list>
190
+ </v-sheet>
112
191
  </template>
192
+
193
+ <style>
194
+ .vjsf-list-item .v-list-item__append {
195
+ height: 100%;
196
+ align-items: start;
197
+ }
198
+ </style>
@@ -0,0 +1,72 @@
1
+ <script>
2
+ import { defineComponent, h, computed, shallowRef, ref } from 'vue'
3
+ import { VCombobox } from 'vuetify/components'
4
+ import { getInputProps, getCompSlots } from '../../utils/index.js'
5
+
6
+ export default defineComponent({
7
+ props: {
8
+ modelValue: {
9
+ /** @type import('vue').PropType<import('../../types.js').VjsfComboboxNode> */
10
+ type: Object,
11
+ required: true
12
+ },
13
+ statefulLayout: {
14
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
15
+ type: Object,
16
+ required: true
17
+ }
18
+ },
19
+ setup (props) {
20
+ /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
21
+ const items = shallowRef(props.modelValue.layout.items ?? [])
22
+ /** @type import('vue').Ref<boolean> */
23
+ const loading = ref(false)
24
+
25
+ /** @type import('@json-layout/core').StateTree | null */
26
+ let lastStateTree = null
27
+ /** @type Record<string, any> | null */
28
+ let lastContext = null
29
+
30
+ const hasItems = computed(() => {
31
+ return !!(props.modelValue.layout.items || props.modelValue.layout.getItems)
32
+ })
33
+
34
+ const refresh = async () => {
35
+ if (props.modelValue.layout.items) return
36
+ if (props.statefulLayout.stateTree === lastStateTree && props.statefulLayout.options.context === lastContext) return
37
+ lastStateTree = props.statefulLayout.stateTree
38
+ lastContext = props.statefulLayout.options.context ?? null
39
+ if (hasItems.value) {
40
+ loading.value = true
41
+ items.value = await props.statefulLayout.getItems(props.modelValue)
42
+ loading.value = false
43
+ }
44
+ }
45
+
46
+ if (!props.modelValue.layout.items) {
47
+ refresh()
48
+ }
49
+
50
+ const fieldProps = computed(() => {
51
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['step', 'min', 'max'])
52
+ fieldProps.type = 'number'
53
+ fieldProps.loading = loading.value
54
+ if (hasItems.value) fieldProps.items = items.value
55
+ if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
56
+ if (props.modelValue.layout.multiple) {
57
+ fieldProps.multiple = true
58
+ fieldProps.chips = true
59
+ fieldProps.closableChips = true
60
+ }
61
+ fieldProps['onUpdate:menu'] = () => refresh()
62
+ fieldProps['onUpdate:modelValue'] = (/** @type string[] */value) => props.statefulLayout.input(props.modelValue, value && value.map(Number))
63
+ return fieldProps
64
+ })
65
+ const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
66
+
67
+ // @ts-ignore
68
+ return () => h(VCombobox, fieldProps.value, fieldSlots.value)
69
+ }
70
+ })
71
+
72
+ </script>
@@ -1,27 +1,25 @@
1
1
  <script>
2
2
  import { defineComponent, h, computed } from 'vue'
3
3
  import { VTextField } from 'vuetify/components'
4
- import { getInputProps } from '../../utils/props.js'
5
- import { getCompSlots } from '../../utils/slots.js'
4
+ import { getInputProps, getCompSlots } from '../../utils/index.js'
6
5
 
7
6
  export default defineComponent({
8
7
  props: {
9
8
  modelValue: {
10
- /** @type import('vue').PropType<import('../types.js').VjsfNumberFieldNode> */
9
+ /** @type import('vue').PropType<import('../../types.js').VjsfNumberFieldNode> */
11
10
  type: Object,
12
11
  required: true
13
12
  },
14
13
  statefulLayout: {
15
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
14
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
16
15
  type: Object,
17
16
  required: true
18
17
  }
19
18
  },
20
19
  setup (props) {
21
20
  const fieldProps = computed(() => {
22
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
21
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['step', 'min', 'max', 'placeholder'])
23
22
  fieldProps.type = 'number'
24
- if ('step' in props.modelValue.layout) fieldProps.step = props.modelValue.layout.step
25
23
  fieldProps['onUpdate:modelValue'] = (/** @type string */value) => props.statefulLayout.input(props.modelValue, value && Number(value))
26
24
  return fieldProps
27
25
  })
@@ -1,17 +1,17 @@
1
1
  <script setup>
2
- import { VSelect } from 'vuetify/components'
2
+ import { VSelect, VRow } from 'vuetify/components'
3
3
  import { shallowRef, watch } from 'vue'
4
4
  import { isSection } from '@json-layout/core'
5
5
  import Node from '../node.vue'
6
6
 
7
7
  const props = defineProps({
8
8
  modelValue: {
9
- /** @type import('vue').PropType<import('../types.js').VjsfOneOfSelectNode> */
9
+ /** @type import('vue').PropType<import('../../types.js').VjsfOneOfSelectNode> */
10
10
  type: Object,
11
11
  required: true
12
12
  },
13
13
  statefulLayout: {
14
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
14
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
15
15
  type: Object,
16
16
  required: true
17
17
  }
@@ -49,7 +49,7 @@ const onChange = (/** @type import('@json-layout/core').SkeletonTree */childTree
49
49
  <node
50
50
  v-for="grandChild of isSection(modelValue.children?.[0]) ? modelValue.children?.[0].children : modelValue.children"
51
51
  :key="grandChild.fullKey"
52
- :model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
52
+ :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
53
53
  :stateful-layout="statefulLayout"
54
54
  />
55
55
  </v-row>
@@ -1,15 +1,16 @@
1
1
  <script setup>
2
+ import { VRow } from 'vuetify/components'
2
3
  import Node from '../node.vue'
3
4
  import SectionHeader from '../fragments/section-header.vue'
4
5
 
5
6
  defineProps({
6
7
  modelValue: {
7
- /** @type import('vue').PropType<import('../types.js').VjsfSectionNode> */
8
+ /** @type import('vue').PropType<import('../../types.js').VjsfSectionNode> */
8
9
  type: Object,
9
10
  required: true
10
11
  },
11
12
  statefulLayout: {
12
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
13
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
13
14
  type: Object,
14
15
  required: true
15
16
  }
@@ -23,7 +24,7 @@ defineProps({
23
24
  <node
24
25
  v-for="child of modelValue.children"
25
26
  :key="child.fullKey"
26
- :model-value="/** @type import('../types.js').VjsfNode */(child)"
27
+ :model-value="/** @type import('../../types.js').VjsfNode */(child)"
27
28
  :stateful-layout="statefulLayout"
28
29
  />
29
30
  </v-row>
@@ -1,59 +1,78 @@
1
- <script setup>
1
+ <script>
2
2
  import { VSelect } from 'vuetify/components'
3
- import { computed, ref } from 'vue'
4
- import { getInputProps } from '../../utils/props.js'
5
-
6
- const props = defineProps({
7
- modelValue: {
8
- /** @type import('vue').PropType<import('../types.js').VjsfSelectNode> */
9
- type: Object,
10
- required: true
3
+ import { defineComponent, h, computed, ref, shallowRef } from 'vue'
4
+ import { getInputProps, getCompSlots } from '../../utils/index.js'
5
+ import SelectItem from '../fragments/select-item.vue'
6
+ import SelectSelection from '../fragments/select-selection.vue'
7
+
8
+ export default defineComponent({
9
+ props: {
10
+ modelValue: {
11
+ /** @type import('vue').PropType<import('../../types.js').VjsfSelectNode> */
12
+ type: Object,
13
+ required: true
14
+ },
15
+ statefulLayout: {
16
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
17
+ type: Object,
18
+ required: true
19
+ }
11
20
  },
12
- statefulLayout: {
13
- /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
14
- type: Object,
15
- required: true
16
- }
17
- })
21
+ 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)
18
26
 
19
- const fieldProps = computed(() => {
20
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
21
- if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
22
- return fieldProps
23
- })
27
+ const fieldProps = computed(() => {
28
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['multiple'])
29
+ if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
30
+ fieldProps.loading = loading.value
31
+ fieldProps.items = items.value
32
+ fieldProps['onUpdate:menu'] = refresh
33
+ return fieldProps
34
+ })
24
35
 
25
- /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
26
- const items = ref(props.modelValue.layout.items ?? [])
27
- /** @type import('vue').Ref<boolean> */
28
- const loading = ref(false)
29
-
30
- /** @type import('@json-layout/core').StateTree | null */
31
- let lastStateTree = null
32
- /** @type Record<string, any> | null */
33
- let lastContext = null
34
-
35
- const refresh = async () => {
36
- if (props.modelValue.layout.items) return
37
- if (props.statefulLayout.stateTree === lastStateTree && props.statefulLayout.options.context === lastContext) return
38
- lastStateTree = props.statefulLayout.stateTree
39
- lastContext = props.statefulLayout.options.context ?? null
40
- loading.value = true
41
- items.value = await props.statefulLayout.getSelectItems(props.modelValue)
42
- loading.value = false
43
- }
44
-
45
- if (!props.modelValue.layout.items) {
46
- refresh()
47
- }
36
+ /** @type import('@json-layout/core').StateTree | null */
37
+ let lastStateTree = null
38
+ /** @type Record<string, any> | null */
39
+ let lastContext = null
48
40
 
49
- </script>
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
+ }
50
53
 
51
- <template>
52
- <v-select
53
- v-bind="fieldProps"
54
- :loading="loading"
55
- :items="items"
56
- @update:model-value="value => statefulLayout.input(modelValue, value)"
57
- @update:menu="refresh"
58
- />
59
- </template>
54
+ const fieldSlots = computed(() => {
55
+ const slots = getCompSlots(props.modelValue, props.statefulLayout)
56
+ if (!slots.item) {
57
+ slots.item = (/** @type {any} */ context) => h(SelectItem, {
58
+ multiple: props.modelValue.layout.multiple,
59
+ itemProps: context.props,
60
+ item: context.item.raw
61
+ })
62
+ }
63
+ if (!slots.selection) {
64
+ slots.selection = (/** @type {any} */ context) => h(SelectSelection, {
65
+ multiple: props.modelValue.layout.multiple,
66
+ last: props.modelValue.layout.multiple && context.index === props.modelValue.data.length - 1,
67
+ item: context.item.raw
68
+ })
69
+ }
70
+ return slots
71
+ })
72
+
73
+ // @ts-ignore
74
+ return () => h(VSelect, fieldProps.value, fieldSlots.value)
75
+ }
76
+ })
77
+
78
+ </script>
@@ -1,16 +1,16 @@
1
1
  <script setup>
2
2
  import { VSlider } from 'vuetify/components'
3
3
  import { computed } from 'vue'
4
- import { getInputProps } from '../../utils/props.js'
4
+ import { getInputProps } from '../../utils/index.js'
5
5
 
6
6
  const props = defineProps({
7
7
  modelValue: {
8
- /** @type import('vue').PropType<import('../types.js').VjsfSliderNode> */
8
+ /** @type import('vue').PropType<import('../../types.js').VjsfSliderNode> */
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
  }