@koumoul/vjsf 3.0.0-beta.39 → 3.0.0-beta.40

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koumoul/vjsf",
3
- "version": "3.0.0-beta.39",
3
+ "version": "3.0.0-beta.40",
4
4
  "description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
5
5
  "scripts": {
6
6
  "test": "vitest",
@@ -76,7 +76,7 @@
76
76
  "vuetify": "^3.6.13"
77
77
  },
78
78
  "dependencies": {
79
- "@json-layout/core": "0.30.1",
79
+ "@json-layout/core": "0.31.0",
80
80
  "@vueuse/core": "^10.5.0",
81
81
  "debug": "^4.3.4",
82
82
  "ejs": "^3.1.9"
@@ -1,16 +1,18 @@
1
1
  <script setup>
2
2
  import { watch, computed, ref } from 'vue'
3
3
  import { useDefaults, useTheme } from 'vuetify'
4
- import { VList, VListItem, VListItemAction, VListSubheader } from 'vuetify/components/VList'
5
- import { VRow, VSpacer } from 'vuetify/components/VGrid'
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'
6
7
  import { VSheet } from 'vuetify/components/VSheet'
7
8
  import { VDivider } from 'vuetify/components/VDivider'
8
9
  import { VIcon } from 'vuetify/components/VIcon'
9
10
  import { VBtn } from 'vuetify/components/VBtn'
10
11
  import { VMenu } from 'vuetify/components/VMenu'
11
- import { isSection, clone } from '@json-layout/core'
12
+ import { VForm } from 'vuetify/components/VForm'
13
+ import { isSection, clone, getRegexp } from '@json-layout/core'
12
14
  import Node from '../node.vue'
13
- import { moveArrayItem } from '../../utils/index.js'
15
+ import { moveDataItem } from '../../utils/index.js'
14
16
  import useDnd from '../../composables/use-dnd.js'
15
17
  import useCompDefaults from '../../composables/use-comp-defaults.js'
16
18
 
@@ -34,7 +36,11 @@ const theme = useTheme()
34
36
 
35
37
  /* use composable for drag and drop */
36
38
  const { activeDnd, sortableArray, draggable, hovered, dragging, itemBind, handleBind } = useDnd(props.modelValue.children, () => {
37
- 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)
38
44
  })
39
45
  watch(() => props.modelValue.children, (array) => { sortableArray.value = array })
40
46
 
@@ -69,12 +75,40 @@ const pushEmptyItem = () => {
69
75
  }
70
76
  }
71
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
+
72
94
  /**
73
95
  * @param {number} childIndex
74
96
  */
75
97
  const deleteItem = (childIndex) => {
76
- const newData = [...props.modelValue.data.slice(0, childIndex), ...props.modelValue.data.slice(childIndex + 1)]
77
- props.statefulLayout.input(props.modelValue, newData)
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
+
78
112
  menuOpened.value = -1
79
113
  }
80
114
 
@@ -103,7 +137,7 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
103
137
 
104
138
  <template>
105
139
  <v-sheet v-bind="vSheetProps">
106
- <v-list>
140
+ <v-list class="py-0">
107
141
  <v-list-subheader v-if="modelValue.layout.title">
108
142
  {{ modelValue.layout.title }}
109
143
  </v-list-subheader>
@@ -118,6 +152,12 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
118
152
  :style="`border: 1px solid ${itemBorderColor(child, childIndex)}`"
119
153
  class="pa-1 vjsf-list-item"
120
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>
121
161
  <v-row class="ma-0">
122
162
  <node
123
163
  v-for="grandChild of isSection(child) ? child.children : [child]"
@@ -130,7 +170,7 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
130
170
  v-if="!modelValue.options.readOnly && modelValue.layout.listActions.length"
131
171
  #append
132
172
  >
133
- <div>
173
+ <div class="vjsf-list-item-actions-wrapper">
134
174
  <v-list-item-action v-if="activeItem !== childIndex">
135
175
  <v-btn
136
176
  style="visibility:hidden"
@@ -211,7 +251,7 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
211
251
  </v-list-item>
212
252
  <v-list-item
213
253
  v-if="modelValue.layout.listActions.includes('sort')"
214
- @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex - 1))"
254
+ @click="statefulLayout.input(modelValue, moveDataItem(modelValue.data, childIndex, childIndex - 1))"
215
255
  >
216
256
  <template #prepend>
217
257
  <v-icon icon="mdi-arrow-up" />
@@ -220,7 +260,7 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
220
260
  </v-list-item>
221
261
  <v-list-item
222
262
  v-if="modelValue.layout.listActions.includes('sort')"
223
- @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex + 1))"
263
+ @click="statefulLayout.input(modelValue, moveDataItem(modelValue.data, childIndex, childIndex + 1))"
224
264
  >
225
265
  <template #prepend>
226
266
  <v-icon icon="mdi-arrow-down" />
@@ -236,15 +276,44 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
236
276
  </v-list-item>
237
277
  <v-divider v-if="childIndex < modelValue.children.length - 1" />
238
278
  </template>
239
- <v-list-item v-if="!modelValue.options.readOnly && modelValue.layout.listActions.includes('add')">
240
- <v-spacer />
241
- <v-btn
242
- color="primary"
243
- @click="pushEmptyItem"
244
- >
245
- {{ modelValue.messages.addItem }}
246
- </v-btn>
247
- <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>
248
317
  </v-list-item>
249
318
  </v-list>
250
319
  </v-sheet>
@@ -255,4 +324,10 @@ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').S
255
324
  height: 100%;
256
325
  align-items: start;
257
326
  }
327
+ .vjsf-list-item .v-list-item__content {
328
+ padding-right: 4px;
329
+ }
330
+ .vjsf-list-item-actions-wrapper {
331
+ /*margin: -4px;*/
332
+ }
258
333
  </style>
@@ -1,15 +1,46 @@
1
1
  /**
2
- * @template T
3
- * @param {T[]} array
2
+ * @param {any[] | Record<string, any>} data
4
3
  * @param {number} fromIndex
5
4
  * @param {number} toIndex
6
- * @return {T[]}
5
+ * @return {any[] | Record<string, any>}
7
6
  */
8
- export function moveArrayItem (array, fromIndex, toIndex) {
9
- if (fromIndex === toIndex || fromIndex === -1 || toIndex === -1) return array
10
- const newArray = [...array]
7
+ export function moveDataItem (data, fromIndex, toIndex) {
8
+ if (fromIndex === toIndex || fromIndex === -1 || toIndex === -1) return data
9
+ if (!Array.isArray(data) && typeof data === 'object') return moveObjectItem(data, fromIndex, toIndex)
10
+ return moveArrayItem(data, fromIndex, toIndex)
11
+ }
12
+
13
+ /**
14
+ * @param {any[]} data
15
+ * @param {number} fromIndex
16
+ * @param {number} toIndex
17
+ * @return {any[]}
18
+ */
19
+ export function moveArrayItem (data, fromIndex, toIndex) {
20
+ if (fromIndex === toIndex || fromIndex === -1 || toIndex === -1) return data
21
+ // @ts-ignore
22
+ if (!Array.isArray(data) && typeof data === 'object') return moveObjectItem(data, fromIndex, toIndex)
23
+ const newArray = [...data]
11
24
  const element = newArray[fromIndex]
12
25
  newArray.splice(fromIndex, 1)
13
26
  newArray.splice(toIndex, 0, element)
14
27
  return newArray
15
28
  }
29
+
30
+ /**
31
+ * @param {Record<string, any>} data
32
+ * @param {number} fromIndex
33
+ * @param {number} toIndex
34
+ * @return {Record<string, any>}
35
+ */
36
+ export function moveObjectItem (data, fromIndex, toIndex) {
37
+ if (fromIndex === toIndex || fromIndex === -1 || toIndex === -1) return data
38
+ const newKeys = /** @type {string[] } */(moveArrayItem(Object.keys(data), fromIndex, toIndex))
39
+ /** @type {Record<string, any>} */
40
+ const newData = {}
41
+ for (const key of newKeys) {
42
+ newData[key] = data[key]
43
+ }
44
+ console.log(newData)
45
+ return newData
46
+ }
@@ -1,9 +1,22 @@
1
1
  /**
2
- * @template T
3
- * @param {T[]} array
2
+ * @param {any[] | Record<string, any>} data
4
3
  * @param {number} fromIndex
5
4
  * @param {number} toIndex
6
- * @return {T[]}
5
+ * @return {any[] | Record<string, any>}
7
6
  */
8
- export function moveArrayItem<T>(array: T[], fromIndex: number, toIndex: number): T[];
7
+ export function moveDataItem(data: any[] | Record<string, any>, fromIndex: number, toIndex: number): any[] | Record<string, any>;
8
+ /**
9
+ * @param {any[]} data
10
+ * @param {number} fromIndex
11
+ * @param {number} toIndex
12
+ * @return {any[]}
13
+ */
14
+ export function moveArrayItem(data: any[], fromIndex: number, toIndex: number): any[];
15
+ /**
16
+ * @param {Record<string, any>} data
17
+ * @param {number} fromIndex
18
+ * @param {number} toIndex
19
+ * @return {Record<string, any>}
20
+ */
21
+ export function moveObjectItem(data: Record<string, any>, fromIndex: number, toIndex: number): Record<string, any>;
9
22
  //# sourceMappingURL=arrays.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"arrays.d.ts","sourceRoot":"","sources":["../../src/utils/arrays.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wDAJW,MAAM,WACN,MAAM,OAUhB"}
1
+ {"version":3,"file":"arrays.d.ts","sourceRoot":"","sources":["../../src/utils/arrays.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,mCALW,GAAG,EAAE,GAAG,OAAO,MAAM,EAAE,GAAG,CAAC,aAC3B,MAAM,WACN,MAAM,GACL,GAAG,EAAE,GAAG,OAAO,MAAM,EAAE,GAAG,CAAC,CAMtC;AAED;;;;;GAKG;AACH,oCALW,GAAG,EAAE,aACL,MAAM,WACN,MAAM,GACL,GAAG,EAAE,CAWhB;AAED;;;;;GAKG;AACH,qCALW,OAAO,MAAM,EAAE,GAAG,CAAC,aACnB,MAAM,WACN,MAAM,GACL,OAAO,MAAM,EAAE,GAAG,CAAC,CAY9B"}