@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 +2 -2
- package/src/components/nodes/list.vue +95 -20
- package/src/utils/arrays.js +37 -6
- package/types/utils/arrays.d.ts +17 -4
- package/types/utils/arrays.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koumoul/vjsf",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
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.
|
|
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
|
|
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 {
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
77
|
-
|
|
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,
|
|
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,
|
|
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
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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>
|
package/src/utils/arrays.js
CHANGED
|
@@ -1,15 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* @param {T[]} array
|
|
2
|
+
* @param {any[] | Record<string, any>} data
|
|
4
3
|
* @param {number} fromIndex
|
|
5
4
|
* @param {number} toIndex
|
|
6
|
-
* @return {
|
|
5
|
+
* @return {any[] | Record<string, any>}
|
|
7
6
|
*/
|
|
8
|
-
export function
|
|
9
|
-
if (fromIndex === toIndex || fromIndex === -1 || toIndex === -1) return
|
|
10
|
-
|
|
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
|
+
}
|
package/types/utils/arrays.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* @param {T[]} array
|
|
2
|
+
* @param {any[] | Record<string, any>} data
|
|
4
3
|
* @param {number} fromIndex
|
|
5
4
|
* @param {number} toIndex
|
|
6
|
-
* @return {
|
|
5
|
+
* @return {any[] | Record<string, any>}
|
|
7
6
|
*/
|
|
8
|
-
export function
|
|
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
|
|
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"}
|