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

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 (69) hide show
  1. package/package.json +10 -4
  2. package/src/compat/v2.js +44 -6
  3. package/src/compile/index.js +25 -6
  4. package/src/compile/v-jsf-compiled.vue.ejs +14 -52
  5. package/src/components/fragments/help-message.vue +48 -0
  6. package/src/components/fragments/section-header.vue +4 -1
  7. package/src/components/fragments/select-item-icon.vue +28 -0
  8. package/src/components/fragments/select-item.vue +43 -0
  9. package/src/components/fragments/select-selection.vue +35 -0
  10. package/src/components/fragments/text-field-menu.vue +1 -1
  11. package/src/components/node.vue +10 -1
  12. package/src/components/nodes/autocomplete.vue +95 -0
  13. package/src/components/nodes/combobox.vue +73 -0
  14. package/src/components/nodes/date-picker.vue +2 -2
  15. package/src/components/nodes/list.vue +170 -86
  16. package/src/components/nodes/markdown.vue +233 -0
  17. package/src/components/nodes/number-combobox.vue +73 -0
  18. package/src/components/nodes/number-field.vue +1 -2
  19. package/src/components/nodes/select.vue +70 -50
  20. package/src/components/nodes/stepper.vue +98 -0
  21. package/src/components/nodes/text-field.vue +1 -1
  22. package/src/components/nodes/textarea.vue +1 -1
  23. package/src/components/options.js +23 -2
  24. package/src/components/tree.vue +2 -1
  25. package/src/components/types.ts +8 -0
  26. package/src/components/vjsf.vue +38 -94
  27. package/src/composables/use-dnd.js +54 -0
  28. package/src/composables/use-vjsf.js +115 -0
  29. package/src/index.js +2 -1
  30. package/src/styles/vjsf.css +10 -0
  31. package/src/utils/arrays.js +15 -0
  32. package/src/utils/props.js +25 -6
  33. package/types/compat/v2.d.ts.map +1 -1
  34. package/types/compile/index.d.ts.map +1 -1
  35. package/types/components/fragments/help-message.vue.d.ts +8 -0
  36. package/types/components/fragments/help-message.vue.d.ts.map +1 -0
  37. package/types/components/fragments/select-item-icon.vue.d.ts +15 -0
  38. package/types/components/fragments/select-item-icon.vue.d.ts.map +1 -0
  39. package/types/components/fragments/select-item.vue.d.ts +12 -0
  40. package/types/components/fragments/select-item.vue.d.ts.map +1 -0
  41. package/types/components/fragments/select-selection.vue.d.ts +12 -0
  42. package/types/components/fragments/select-selection.vue.d.ts.map +1 -0
  43. package/types/components/nodes/autocomplete.vue.d.ts +27 -0
  44. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -0
  45. package/types/components/nodes/combobox.vue.d.ts +27 -0
  46. package/types/components/nodes/combobox.vue.d.ts.map +1 -0
  47. package/types/components/nodes/markdown.vue.d.ts +27 -0
  48. package/types/components/nodes/markdown.vue.d.ts.map +1 -0
  49. package/types/components/nodes/number-combobox.vue.d.ts +27 -0
  50. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -0
  51. package/types/components/nodes/select.vue.d.ts +25 -8
  52. package/types/components/nodes/select.vue.d.ts.map +1 -1
  53. package/types/components/nodes/stepper.vue.d.ts +10 -0
  54. package/types/components/nodes/stepper.vue.d.ts.map +1 -0
  55. package/types/components/options.d.ts +3 -2
  56. package/types/components/options.d.ts.map +1 -1
  57. package/types/components/types.d.ts +9 -1
  58. package/types/components/types.d.ts.map +1 -1
  59. package/types/components/vjsf.vue.d.ts +5 -3
  60. package/types/composables/use-dnd.d.ts +21 -0
  61. package/types/composables/use-dnd.d.ts.map +1 -0
  62. package/types/composables/use-vjsf.d.ts +17 -0
  63. package/types/composables/use-vjsf.d.ts.map +1 -0
  64. package/types/index.d.ts +2 -1
  65. package/types/index.d.ts.map +1 -1
  66. package/types/utils/arrays.d.ts +9 -0
  67. package/types/utils/arrays.d.ts.map +1 -0
  68. package/types/utils/props.d.ts +6 -3
  69. package/types/utils/props.d.ts.map +1 -1
@@ -1,10 +1,13 @@
1
1
  <script setup>
2
+ import { watch, computed, ref } from 'vue'
2
3
  import { VList, VListItem, VListItemAction, VBtn, VMenu, VIcon } from 'vuetify/components'
3
4
  import { isSection } from '@json-layout/core'
4
5
  import Node from '../node.vue'
5
6
  import clone from '../../utils/clone.js'
7
+ import { moveArrayItem } from '../../utils/arrays.js'
8
+ import useDnd from '../../composables/use-dnd.js'
6
9
 
7
- defineProps({
10
+ const props = defineProps({
8
11
  modelValue: {
9
12
  /** @type import('vue').PropType<import('../types.js').VjsfListNode> */
10
13
  type: Object,
@@ -17,96 +20,177 @@ defineProps({
17
20
  }
18
21
  })
19
22
 
23
+ /* use composable for drag and drop */
24
+ const { activeDnd, sortableArray, draggable, itemBind, handleBind } = useDnd(props.modelValue.children, () => {
25
+ props.statefulLayout.input(props.modelValue, sortableArray.value.map((child) => child.data))
26
+ })
27
+ watch(() => props.modelValue.children, (array) => { sortableArray.value = array })
28
+
29
+ /* manage hovered and edited items */
30
+ const editedItem = computed(() => {
31
+ return props.statefulLayout.activeItems[props.modelValue.fullKey]
32
+ })
33
+ const hoveredItem = ref(1)
34
+ const activeItem = computed(() => {
35
+ if (
36
+ props.modelValue.layout.listActions.includes('edit') &&
37
+ props.modelValue.layout.listEditMode === 'inline-single' &&
38
+ editedItem.value !== undefined
39
+ ) {
40
+ return editedItem.value
41
+ }
42
+ return hoveredItem.value
43
+ })
44
+
45
+ const buttonDensity = computed(() => {
46
+ if (props.modelValue.options.density === 'default') return 'comfortable'
47
+ return props.modelValue.options.density
48
+ })
49
+
20
50
  </script>
21
51
 
22
52
  <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"
53
+ <v-sheet :elevation="1">
54
+ <v-list :density="modelValue.options.density">
55
+ <v-list-subheader v-if="modelValue.layout.title">
56
+ {{ modelValue.layout.title }}
57
+ </v-list-subheader>
58
+ <template
59
+ v-for="(child, childIndex) of sortableArray"
60
+ :key="props.modelValue.children.findIndex(c => c === child)"
61
+ >
62
+ <v-list-item
63
+ v-bind="itemBind(childIndex)"
64
+ :density="modelValue.options.density"
65
+ :draggable="draggable === childIndex"
66
+ :variant="editedItem === childIndex ? 'outlined' : 'flat'"
67
+ class="pa-1 vjsf-list-item"
68
+ @mouseenter="hoveredItem = childIndex"
69
+ @mouseleave="hoveredItem = -1"
66
70
  >
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)])"
71
+ <v-row class="ma-0">
72
+ <node
73
+ v-for="grandChild of isSection(child) ? child.children : [child]"
74
+ :key="grandChild.fullKey"
75
+ :model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
76
+ :stateful-layout="statefulLayout"
77
+ />
78
+ </v-row>
79
+ <template
80
+ v-if="activeItem === childIndex"
81
+ #append
82
+ >
83
+ <div>
84
+ <v-list-item-action
85
+ v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
81
86
  >
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)])"
87
+ <v-btn
88
+ v-if="editedItem !== childIndex"
89
+ :title="modelValue.messages.edit"
90
+ icon="mdi-pencil"
91
+ variant="text"
92
+ color="primary"
93
+ :density="buttonDensity"
94
+ @click="statefulLayout.activateItem(modelValue, childIndex)"
95
+ />
96
+ <v-btn
97
+ v-else
98
+ :title="modelValue.messages.edit"
99
+ icon="mdi-pencil"
100
+ variant="flat"
101
+ color="primary"
102
+ :density="buttonDensity"
103
+ @click="statefulLayout.deactivateItem(modelValue)"
104
+ />
105
+ </v-list-item-action>
106
+ <v-list-item-action
107
+ v-if="editedItem === undefined && modelValue.layout.listActions.includes('sort') && activeDnd"
108
+ >
109
+ <v-btn
110
+ :title="modelValue.messages.sort"
111
+ icon="mdi-arrow-up-down"
112
+ variant="plain"
113
+ :density="buttonDensity"
114
+ v-bind="handleBind(childIndex)"
115
+ />
116
+ </v-list-item-action>
117
+ <v-list-item-action
118
+ v-if="editedItem === undefined && (modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate') || modelValue.layout.listActions.includes('sort'))"
91
119
  >
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>
120
+ <v-menu location="bottom end">
121
+ <template #activator="{props: activatorProps}">
122
+ <v-btn
123
+ v-bind="activatorProps"
124
+ icon="mdi-dots-vertical"
125
+ variant="plain"
126
+ slim
127
+ :density="buttonDensity"
128
+ />
129
+ </template>
130
+ <v-list :density="modelValue.options.density">
131
+ <v-list-item
132
+ v-if="modelValue.layout.listActions.includes('delete')"
133
+ base-color="warning"
134
+ @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), ...modelValue.data.slice(childIndex + 1)])"
135
+ >
136
+ <template #prepend>
137
+ <v-icon icon="mdi-delete" />
138
+ </template>
139
+ {{ modelValue.messages.delete }}
140
+ </v-list-item>
141
+ <v-list-item
142
+ v-if="modelValue.layout.listActions.includes('duplicate')"
143
+ @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), clone(child.data), ...modelValue.data.slice(childIndex)])"
144
+ >
145
+ <template #prepend>
146
+ <v-icon icon="mdi-content-duplicate" />
147
+ </template>
148
+ {{ modelValue.messages.duplicate }}
149
+ </v-list-item>
150
+ <v-list-item
151
+ v-if="modelValue.layout.listActions.includes('sort')"
152
+ @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex - 1))"
153
+ >
154
+ <template #prepend>
155
+ <v-icon icon="mdi-arrow-up" />
156
+ </template>
157
+ {{ modelValue.messages.up }}
158
+ </v-list-item>
159
+ <v-list-item
160
+ v-if="modelValue.layout.listActions.includes('sort')"
161
+ @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex + 1))"
162
+ >
163
+ <template #prepend>
164
+ <v-icon icon="mdi-arrow-down" />
165
+ </template>
166
+ {{ modelValue.messages.down }}
167
+ </v-list-item>
168
+ </v-list>
169
+ </v-menu>
170
+ </v-list-item-action>
171
+ </div>
172
+ </template>
173
+ </v-list-item>
174
+ <v-divider v-if="childIndex < modelValue.children.length - 1" />
100
175
  </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>
176
+ <v-list-item v-if="!modelValue.options.readOnly && modelValue.layout.listActions.includes('add')">
177
+ <v-spacer />
178
+ <v-btn
179
+ color="primary"
180
+ :density="modelValue.options.density"
181
+ @click="statefulLayout.input(modelValue, (modelValue.data ?? []).concat([undefined]))"
182
+ >
183
+ {{ modelValue.messages.addItem }}
184
+ </v-btn>
185
+ <v-spacer />
186
+ </v-list-item>
187
+ </v-list>
188
+ </v-sheet>
112
189
  </template>
190
+
191
+ <style>
192
+ .vjsf-list-item .v-list-item__append {
193
+ height: 100%;
194
+ align-items: start;
195
+ }
196
+ </style>
@@ -0,0 +1,233 @@
1
+ <script>
2
+ import { defineComponent, h, computed, onMounted, ref, onUnmounted, watch } from 'vue'
3
+ import { VInput, VLabel } from 'vuetify/components'
4
+ import { getInputProps } from '../../utils/props.js'
5
+ import { getCompSlots } from '../../utils/slots.js'
6
+ // import 'easymde/dist/easymde.min.css'
7
+ // import EasyMDE from 'easymde'
8
+
9
+ export default defineComponent({
10
+ props: {
11
+ modelValue: {
12
+ /** @type import('vue').PropType<import('../types.js').VjsfTextareaNode> */
13
+ type: Object,
14
+ required: true
15
+ },
16
+ statefulLayout: {
17
+ /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
18
+ type: Object,
19
+ required: true
20
+ }
21
+ },
22
+ setup (props) {
23
+ /** @type {import('vue').Ref<null | HTMLElement>} */
24
+ const element = ref(null)
25
+
26
+ const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout))
27
+ const fieldSlots = computed(() => {
28
+ const fieldSlots = getCompSlots(props.modelValue, props.statefulLayout)
29
+ fieldSlots.default = () => [
30
+ h('div', { style: 'width:100%' }, [
31
+ h(VLabel, { text: fieldProps.value.label }),
32
+ h('textarea', { ref: element })
33
+ ])
34
+ ]
35
+ return fieldSlots
36
+ })
37
+
38
+ /** @type {EasyMDE | null} */
39
+ let easymde = null
40
+
41
+ const initEasyMDE = async () => {
42
+ if (!element.value) throw new Error('component was not mounted for markdown editor')
43
+
44
+ // @ts-ignore
45
+ await import('easymde/dist/easymde.min.css')
46
+ const EasyMDE = (await import('easymde')).default
47
+
48
+ const messages = props.modelValue.messages
49
+
50
+ const config = {
51
+ element: element.value,
52
+ initialValue: props.modelValue.data ?? '',
53
+ renderingConfig: {},
54
+ status: false,
55
+ autoDownloadFontAwesome: false,
56
+ spellChecker: false,
57
+ minHeight: '300px',
58
+ insertTexts: {
59
+ link: [messages.mdeLink1, messages.mdeLink2],
60
+ image: [messages.mdeImg1, messages.mdeImg2],
61
+ table: [messages.mdeTable1, messages.mdeTable2],
62
+ horizontalRule: ['', '\n\n-----\n\n']
63
+ },
64
+ // cf https://github.com/Ionaru/easy-markdown-editor/blob/master/src/js/easymde.js#L1380
65
+ toolbar: [{
66
+ name: 'bold',
67
+ action: EasyMDE.toggleBold,
68
+ className: 'mdi mdi-format-bold',
69
+ title: messages.bold
70
+ }, {
71
+ name: 'italic',
72
+ action: EasyMDE.toggleItalic,
73
+ className: 'mdi mdi-format-italic',
74
+ title: messages.italic
75
+ }, {
76
+ name: 'heading',
77
+ action: EasyMDE.toggleHeadingSmaller,
78
+ className: 'mdi mdi-format-title',
79
+ title: messages.heading
80
+ }, /* {
81
+ name: 'heading-1',
82
+ action: EasyMDE.toggleHeading1,
83
+ className: 'mdi mdi-format-title',
84
+ title: 'Titre 1'
85
+ }, {
86
+ name: 'heading-2',
87
+ action: EasyMDE.toggleHeading2,
88
+ className: 'mdi mdi-format-title',
89
+ title: 'Titre 2'
90
+ }, {
91
+ name: 'heading-3',
92
+ action: EasyMDE.toggleHeading3,
93
+ className: 'mdi mdi-format-title',
94
+ title: 'Titre 3'
95
+ }, */
96
+ '|',
97
+ {
98
+ name: 'quote',
99
+ action: EasyMDE.toggleBlockquote,
100
+ className: 'mdi mdi-format-quote-open',
101
+ title: messages.quote
102
+ },
103
+ {
104
+ name: 'unordered-list',
105
+ action: EasyMDE.toggleUnorderedList,
106
+ className: 'mdi mdi-format-list-bulleted',
107
+ title: messages.unorderedList
108
+ },
109
+ {
110
+ name: 'ordered-list',
111
+ action: EasyMDE.toggleOrderedList,
112
+ className: 'mdi mdi-format-list-numbered',
113
+ title: messages.orderedList
114
+ },
115
+ '|',
116
+ {
117
+ name: 'link',
118
+ action: EasyMDE.drawLink,
119
+ className: 'mdi mdi-link',
120
+ title: messages.createLink
121
+ },
122
+ {
123
+ name: 'image',
124
+ action: EasyMDE.drawImage,
125
+ className: 'mdi mdi-image',
126
+ title: messages.insertImage
127
+ },
128
+ {
129
+ name: 'table',
130
+ action: EasyMDE.drawTable,
131
+ className: 'mdi mdi-table',
132
+ title: messages.createTable
133
+ },
134
+ '|',
135
+ {
136
+ name: 'preview',
137
+ action: EasyMDE.togglePreview,
138
+ className: 'mdi mdi-eye text-accent',
139
+ title: messages.preview,
140
+ noDisable: true
141
+ },
142
+ '|',
143
+ {
144
+ name: 'undo',
145
+ action: EasyMDE.undo,
146
+ className: 'mdi mdi-undo',
147
+ title: messages.undo,
148
+ noDisable: true
149
+ },
150
+ {
151
+ name: 'redo',
152
+ action: EasyMDE.redo,
153
+ className: 'mdi mdi-redo',
154
+ title: messages.redo,
155
+ noDisable: true
156
+ },
157
+ '|',
158
+ {
159
+ name: 'guide',
160
+ action: 'https://simplemde.com/markdown-guide',
161
+ className: 'mdi mdi-help-circle text-success',
162
+ title: messages.mdeGuide,
163
+ noDisable: true
164
+ }
165
+ ],
166
+ ...props.modelValue.options.easyMDEOptions
167
+ }
168
+
169
+ if (easymde) easymde.toTextArea()
170
+ // @ts-ignore
171
+ easymde = new EasyMDE(config)
172
+
173
+ let changed = false
174
+ easymde.codemirror.on('change', () => {
175
+ changed = true
176
+ if (easymde) props.statefulLayout.input(props.modelValue, easymde.value())
177
+ })
178
+ /** @type {ReturnType<typeof setTimeout> | null} */
179
+ let blurTimeout = null
180
+ easymde.codemirror.on('blur', () => {
181
+ // timeout to prevent triggering save when clicking on a menu button
182
+ blurTimeout = setTimeout(() => {
183
+ if (changed) props.statefulLayout.blur(props.modelValue)
184
+ changed = false
185
+ }, 500)
186
+ })
187
+ easymde.codemirror.on('focus', () => {
188
+ if (blurTimeout) clearTimeout(blurTimeout)
189
+ })
190
+ }
191
+
192
+ onMounted(initEasyMDE)
193
+
194
+ onUnmounted(() => {
195
+ if (easymde) easymde.toTextArea()
196
+ })
197
+
198
+ // update data from outside
199
+ watch(() => props.modelValue, () => {
200
+ if (easymde && (easymde.value() !== props.modelValue.data ?? '')) {
201
+ easymde.value(props.modelValue.data ?? '')
202
+ }
203
+ })
204
+
205
+ // update easymde config from outside
206
+ watch(() => [props.modelValue.messages, props.modelValue.options.easyMDEOptions], () => {
207
+ initEasyMDE()
208
+ })
209
+
210
+ return () => h(VInput, fieldProps.value, fieldSlots.value)
211
+ }
212
+ })
213
+
214
+ </script>
215
+
216
+ <style>
217
+ .vjsf-node-markdown .v-input--density-compact .editor-toolbar {
218
+ padding: 0;
219
+ }
220
+ .vjsf-node-markdown .v-input--density-comfortable .editor-toolbar {
221
+ padding: 4px;
222
+ }
223
+
224
+ .vjsf-node-markdown .v-input--density-compact .CodeMirror-wrap {
225
+ padding-top: 2px;
226
+ padding-bottom: 2px;
227
+ }
228
+ .vjsf-node-markdown .v-input--density-comfortable .CodeMirror-wrap {
229
+ padding-top: 6px;
230
+ padding-bottom: 6px;
231
+ }
232
+
233
+ </style>
@@ -0,0 +1,73 @@
1
+ <script>
2
+ import { defineComponent, h, computed, shallowRef, ref } from 'vue'
3
+ import { VCombobox } from 'vuetify/components'
4
+ import { getInputProps } from '../../utils/props.js'
5
+ import { getCompSlots } from '../../utils/slots.js'
6
+
7
+ export default defineComponent({
8
+ props: {
9
+ modelValue: {
10
+ /** @type import('vue').PropType<import('../types.js').VjsfComboboxNode> */
11
+ type: Object,
12
+ required: true
13
+ },
14
+ statefulLayout: {
15
+ /** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
16
+ type: Object,
17
+ required: true
18
+ }
19
+ },
20
+ setup (props) {
21
+ /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
22
+ const items = shallowRef(props.modelValue.layout.items ?? [])
23
+ /** @type import('vue').Ref<boolean> */
24
+ const loading = ref(false)
25
+
26
+ /** @type import('@json-layout/core').StateTree | null */
27
+ let lastStateTree = null
28
+ /** @type Record<string, any> | null */
29
+ let lastContext = null
30
+
31
+ const hasItems = computed(() => {
32
+ return !!(props.modelValue.layout.items || props.modelValue.layout.getItems)
33
+ })
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
+ if (hasItems.value) {
41
+ loading.value = true
42
+ items.value = await props.statefulLayout.getItems(props.modelValue)
43
+ loading.value = false
44
+ }
45
+ }
46
+
47
+ if (!props.modelValue.layout.items) {
48
+ refresh()
49
+ }
50
+
51
+ const fieldProps = computed(() => {
52
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['step', 'min', 'max'])
53
+ fieldProps.type = 'number'
54
+ fieldProps.loading = loading.value
55
+ if (hasItems.value) fieldProps.items = items.value
56
+ if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
57
+ if (props.modelValue.layout.multiple) {
58
+ fieldProps.multiple = true
59
+ fieldProps.chips = true
60
+ fieldProps.closableChips = true
61
+ }
62
+ fieldProps['onUpdate:menu'] = () => refresh()
63
+ fieldProps['onUpdate:modelValue'] = (/** @type string[] */value) => props.statefulLayout.input(props.modelValue, value && value.map(Number))
64
+ return fieldProps
65
+ })
66
+ const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
67
+
68
+ // @ts-ignore
69
+ return () => h(VCombobox, fieldProps.value, fieldSlots.value)
70
+ }
71
+ })
72
+
73
+ </script>
@@ -19,9 +19,8 @@ export default defineComponent({
19
19
  },
20
20
  setup (props) {
21
21
  const fieldProps = computed(() => {
22
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
22
+ const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['step', 'min', 'max', 'placeholder'])
23
23
  fieldProps.type = 'number'
24
- if ('step' in props.modelValue.layout) fieldProps.step = props.modelValue.layout.step
25
24
  fieldProps['onUpdate:modelValue'] = (/** @type string */value) => props.statefulLayout.input(props.modelValue, value && Number(value))
26
25
  return fieldProps
27
26
  })