@koumoul/vjsf 3.0.0-beta.3 → 3.0.0-beta.31
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/README.md +21 -0
- package/package.json +5 -4
- package/src/compat/v2.js +132 -27
- package/src/compile/index.js +18 -4
- package/src/compile/options.js +3 -7
- package/src/compile/v-jsf-compiled.vue.ejs +1 -1
- package/src/components/fragments/help-message.vue +10 -1
- package/src/components/fragments/section-header.vue +1 -3
- package/src/components/fragments/select-item-icon.vue +1 -1
- package/src/components/fragments/select-selection.vue +2 -1
- package/src/components/fragments/selection-group.vue +100 -0
- package/src/components/fragments/text-field-menu.vue +5 -1
- package/src/components/node.vue +47 -42
- package/src/components/nodes/autocomplete.vue +11 -35
- package/src/components/nodes/checkbox-group.vue +39 -0
- package/src/components/nodes/checkbox.vue +9 -1
- package/src/components/nodes/color-picker.vue +5 -1
- package/src/components/nodes/combobox.vue +3 -0
- package/src/components/nodes/date-picker.vue +4 -2
- package/src/components/nodes/date-time-picker.vue +3 -0
- package/src/components/nodes/expansion-panels.vue +24 -12
- package/src/components/nodes/file-input.vue +3 -0
- package/src/components/nodes/list.vue +145 -91
- package/src/components/nodes/number-combobox.vue +3 -0
- package/src/components/nodes/number-field.vue +3 -0
- package/src/components/nodes/one-of-select.vue +45 -24
- package/src/components/nodes/radio-group.vue +53 -0
- package/src/components/nodes/section.vue +3 -0
- package/src/components/nodes/select.vue +10 -27
- package/src/components/nodes/slider.vue +3 -0
- package/src/components/nodes/stepper.vue +3 -0
- package/src/components/nodes/switch-group.vue +39 -0
- package/src/components/nodes/switch.vue +9 -3
- package/src/components/nodes/tabs.vue +10 -3
- package/src/components/nodes/text-field.vue +3 -0
- package/src/components/nodes/textarea.vue +3 -0
- package/src/components/nodes/vertical-tabs.vue +6 -1
- package/src/components/options.js +21 -5
- package/src/components/vjsf.vue +6 -0
- package/src/composables/use-comp-defaults.js +19 -0
- package/src/composables/use-dnd.js +1 -0
- package/src/composables/use-get-items.js +48 -0
- package/src/composables/use-vjsf.js +76 -40
- package/src/index.js +3 -0
- package/src/types.ts +21 -6
- package/src/utils/build.js +1 -1
- package/src/utils/index.js +0 -1
- package/src/utils/props.js +9 -30
- package/types/compat/v2.d.ts.map +1 -1
- package/types/compile/index.d.ts +2 -2
- package/types/compile/index.d.ts.map +1 -1
- package/types/compile/options.d.ts.map +1 -1
- package/types/components/fragments/selection-group.vue.d.ts +35 -0
- package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
- package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
- package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
- package/types/components/nodes/checkbox-group copy.vue.d.ts +27 -0
- package/types/components/nodes/checkbox-group copy.vue.d.ts.map +1 -0
- package/types/components/nodes/checkbox-group.vue.d.ts +27 -0
- package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
- package/types/components/nodes/combobox.vue.d.ts.map +1 -1
- package/types/components/nodes/file-input.vue.d.ts.map +1 -1
- package/types/components/nodes/number-combobox.vue.d.ts.map +1 -1
- package/types/components/nodes/number-field.vue.d.ts.map +1 -1
- package/types/components/nodes/radio-group.vue.d.ts +27 -0
- package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
- package/types/components/nodes/radio.vue.d.ts +10 -0
- package/types/components/nodes/radio.vue.d.ts.map +1 -0
- package/types/components/nodes/select.vue.d.ts.map +1 -1
- package/types/components/nodes/switch-group.vue.d.ts +27 -0
- package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
- package/types/components/nodes/text-field.vue.d.ts.map +1 -1
- package/types/components/nodes/textarea.vue.d.ts.map +1 -1
- package/types/components/options.d.ts +1 -1
- package/types/components/options.d.ts.map +1 -1
- package/types/components/vjsf.vue.d.ts +2 -2
- package/types/composables/use-comp-defaults.d.ts +8 -0
- package/types/composables/use-comp-defaults.d.ts.map +1 -0
- package/types/composables/use-dnd.d.ts.map +1 -1
- package/types/composables/use-get-items.d.ts +13 -0
- package/types/composables/use-get-items.d.ts.map +1 -0
- package/types/composables/use-vjsf.d.ts.map +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/types.d.ts +20 -8
- package/types/types.d.ts.map +1 -1
- package/types/utils/build.d.ts +1 -1
- package/types/utils/index.d.ts +0 -1
- package/types/utils/props.d.ts +3 -4
- package/types/utils/props.d.ts.map +1 -1
- package/src/utils/global-register.js +0 -13
- package/types/components/global-register.d.ts +0 -8
- package/types/components/global-register.d.ts.map +0 -1
- package/types/components/nodes/markdown.vue.d.ts +0 -27
- package/types/components/nodes/markdown.vue.d.ts.map +0 -1
- package/types/components/nodes/text-field copy.vue.d.ts +0 -10
- package/types/components/nodes/text-field copy.vue.d.ts.map +0 -1
- package/types/components/types.d.ts +0 -91
- package/types/components/types.d.ts.map +0 -1
- package/types/components/v-jsf.vue.d.ts +0 -13
- package/types/components/v-jsf.vue.d.ts.map +0 -1
- package/types/utils/clone.d.ts +0 -3
- package/types/utils/clone.d.ts.map +0 -1
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { VSelect, VRow } from 'vuetify/components'
|
|
3
|
-
import {
|
|
2
|
+
import { VSelect, VRow, VCol } from 'vuetify/components'
|
|
3
|
+
import { ref, watch, computed, h } from 'vue'
|
|
4
4
|
import { isSection } from '@json-layout/core'
|
|
5
|
+
import { isCompObject } from '@json-layout/vocabulary'
|
|
6
|
+
import { getInputProps } from '../../utils/index.js'
|
|
5
7
|
import Node from '../node.vue'
|
|
8
|
+
import { useDefaults } from 'vuetify'
|
|
9
|
+
|
|
10
|
+
useDefaults({}, 'VjsfOneOfSelect')
|
|
6
11
|
|
|
7
12
|
const props = defineProps({
|
|
8
13
|
modelValue: {
|
|
@@ -17,40 +22,56 @@ const props = defineProps({
|
|
|
17
22
|
}
|
|
18
23
|
})
|
|
19
24
|
|
|
20
|
-
/** @type import('vue').
|
|
21
|
-
const activeChildTree =
|
|
25
|
+
/** @type import('vue').Ref<string | undefined> */
|
|
26
|
+
const activeChildTree = ref(undefined)
|
|
22
27
|
watch(() => props.modelValue, () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
if (props.modelValue.children?.length === 1) {
|
|
29
|
+
if (typeof props.modelValue.children[0].key === 'number') {
|
|
30
|
+
activeChildTree.value = props.modelValue.skeleton.childrenTrees?.[props.modelValue.children[0].key]
|
|
31
|
+
}
|
|
26
32
|
} else {
|
|
27
33
|
activeChildTree.value = undefined
|
|
28
34
|
}
|
|
29
35
|
}, { immediate: true })
|
|
30
36
|
|
|
31
|
-
const onChange = (/** @type
|
|
37
|
+
const onChange = (/** @type {string} */childTree) => {
|
|
32
38
|
if (!props.modelValue.skeleton.childrenTrees) return
|
|
33
39
|
props.statefulLayout.activateItem(props.modelValue, props.modelValue.skeleton.childrenTrees.indexOf(childTree))
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
const fieldProps = computed(() => {
|
|
43
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
44
|
+
fieldProps.modelValue = activeChildTree.value
|
|
45
|
+
fieldProps['onUpdate:modelValue'] = onChange
|
|
46
|
+
const items = []
|
|
47
|
+
for (const childTreePointer of props.modelValue.skeleton.childrenTrees || []) {
|
|
48
|
+
const childTree = props.statefulLayout.compiledLayout.skeletonTrees[childTreePointer]
|
|
49
|
+
const childLayout = props.statefulLayout.compiledLayout.normalizedLayouts[childTree.root]
|
|
50
|
+
if (!isCompObject(childLayout) || !childLayout.if || !!props.statefulLayout.evalNodeExpression(props.modelValue, childLayout.if, props.modelValue.data)) {
|
|
51
|
+
items.push(childTree)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
fieldProps.items = items
|
|
55
|
+
fieldProps.itemTitle = 'title'
|
|
56
|
+
fieldProps.itemValue = (/** @type {import('@json-layout/core').SkeletonTree} */childTree) => childTree.root
|
|
57
|
+
return fieldProps
|
|
58
|
+
})
|
|
36
59
|
</script>
|
|
37
60
|
|
|
38
61
|
<template>
|
|
39
|
-
<v-
|
|
40
|
-
v-if="modelValue.skeleton.childrenTrees"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
:stateful-layout="statefulLayout"
|
|
54
|
-
/>
|
|
62
|
+
<v-row>
|
|
63
|
+
<v-col v-if="modelValue.skeleton.childrenTrees">
|
|
64
|
+
<v-select
|
|
65
|
+
v-bind="fieldProps"
|
|
66
|
+
/>
|
|
67
|
+
</v-col>
|
|
68
|
+
<template v-if="modelValue.children?.[0]">
|
|
69
|
+
<node
|
|
70
|
+
v-for="grandChild of isSection(modelValue.children?.[0]) ? modelValue.children?.[0].children : modelValue.children"
|
|
71
|
+
:key="grandChild.fullKey"
|
|
72
|
+
:model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
|
|
73
|
+
:stateful-layout="statefulLayout"
|
|
74
|
+
/>
|
|
75
|
+
</template>
|
|
55
76
|
</v-row>
|
|
56
77
|
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { VRadioGroup, VRadio, VSkeletonLoader } from 'vuetify/components'
|
|
3
|
+
import { defineComponent, h, computed } from 'vue'
|
|
4
|
+
import { getInputProps, getCompSlots } from '../../utils/index.js'
|
|
5
|
+
import useGetItems from '../../composables/use-get-items.js'
|
|
6
|
+
import { useDefaults } from 'vuetify'
|
|
7
|
+
|
|
8
|
+
export default defineComponent({
|
|
9
|
+
props: {
|
|
10
|
+
modelValue: {
|
|
11
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfRadioGroupNode> */
|
|
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
|
+
}
|
|
20
|
+
},
|
|
21
|
+
setup (props) {
|
|
22
|
+
useDefaults({}, 'VjsfRadioGroup')
|
|
23
|
+
|
|
24
|
+
const getItems = useGetItems(props)
|
|
25
|
+
|
|
26
|
+
const fieldProps = computed(() => {
|
|
27
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
28
|
+
return fieldProps
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const fieldSlots = computed(() => {
|
|
32
|
+
const slots = getCompSlots(props.modelValue, props.statefulLayout)
|
|
33
|
+
/** @type {import('vue').VNode[]} */
|
|
34
|
+
const children = []
|
|
35
|
+
if (getItems.loading.value) {
|
|
36
|
+
children.push(h(VSkeletonLoader, { type: 'chip' }))
|
|
37
|
+
} else {
|
|
38
|
+
for (const item of getItems.items.value) {
|
|
39
|
+
children.push(h(VRadio, { label: item.title, value: item.value }))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
slots.default = () => children
|
|
43
|
+
return slots
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
return () => {
|
|
48
|
+
return h(VRadioGroup, fieldProps.value, fieldSlots.value)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
</script>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { VSelect } from 'vuetify/components'
|
|
3
|
-
import { defineComponent, h, computed
|
|
3
|
+
import { defineComponent, h, computed } from 'vue'
|
|
4
4
|
import { getInputProps, getCompSlots } from '../../utils/index.js'
|
|
5
|
+
import useGetItems from '../../composables/use-get-items.js'
|
|
5
6
|
import SelectItem from '../fragments/select-item.vue'
|
|
6
7
|
import SelectSelection from '../fragments/select-selection.vue'
|
|
8
|
+
import { useDefaults } from 'vuetify'
|
|
7
9
|
|
|
8
10
|
export default defineComponent({
|
|
9
11
|
props: {
|
|
@@ -19,38 +21,19 @@ export default defineComponent({
|
|
|
19
21
|
}
|
|
20
22
|
},
|
|
21
23
|
setup (props) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const loading = ref(false)
|
|
24
|
+
useDefaults({}, 'VjsfSelect')
|
|
25
|
+
|
|
26
|
+
const getItems = useGetItems(props)
|
|
26
27
|
|
|
27
28
|
const fieldProps = computed(() => {
|
|
28
29
|
const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['multiple'])
|
|
29
30
|
if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
|
|
30
|
-
fieldProps.loading = loading.value
|
|
31
|
-
fieldProps.items = items.value
|
|
32
|
-
fieldProps
|
|
31
|
+
fieldProps.loading = getItems.loading.value
|
|
32
|
+
fieldProps.items = getItems.items.value
|
|
33
|
+
fieldProps.clearable = fieldProps.clearable ?? !props.modelValue.skeleton.required
|
|
33
34
|
return fieldProps
|
|
34
35
|
})
|
|
35
36
|
|
|
36
|
-
/** @type import('@json-layout/core').StateTree | null */
|
|
37
|
-
let lastStateTree = null
|
|
38
|
-
/** @type Record<string, any> | null */
|
|
39
|
-
let lastContext = null
|
|
40
|
-
|
|
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
|
-
}
|
|
53
|
-
|
|
54
37
|
const fieldSlots = computed(() => {
|
|
55
38
|
const slots = getCompSlots(props.modelValue, props.statefulLayout)
|
|
56
39
|
if (!slots.item) {
|
|
@@ -64,7 +47,7 @@ export default defineComponent({
|
|
|
64
47
|
slots.selection = (/** @type {any} */ context) => h(SelectSelection, {
|
|
65
48
|
multiple: props.modelValue.layout.multiple,
|
|
66
49
|
last: props.modelValue.layout.multiple && context.index === props.modelValue.data.length - 1,
|
|
67
|
-
item: context.item.raw
|
|
50
|
+
item: getItems.prepareSelectedItem(context.item.raw, context.item.value)
|
|
68
51
|
})
|
|
69
52
|
}
|
|
70
53
|
return slots
|
|
@@ -4,6 +4,9 @@ import { VStepper, VStepperHeader, VStepperItem, VStepperWindow, VStepperWindowI
|
|
|
4
4
|
import { isSection } from '@json-layout/core'
|
|
5
5
|
import Node from '../node.vue'
|
|
6
6
|
import SectionHeader from '../fragments/section-header.vue'
|
|
7
|
+
import { useDefaults } from 'vuetify'
|
|
8
|
+
|
|
9
|
+
useDefaults({}, 'VjsfStepper')
|
|
7
10
|
|
|
8
11
|
const props = defineProps({
|
|
9
12
|
modelValue: {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { useDefaults } from 'vuetify'
|
|
3
|
+
import SelectionGroup from '../fragments/selection-group.vue'
|
|
4
|
+
import { defineComponent, h } from 'vue'
|
|
5
|
+
|
|
6
|
+
export default defineComponent({
|
|
7
|
+
props: {
|
|
8
|
+
modelValue: {
|
|
9
|
+
/** @type import('vue').PropType<import('../../types.js').VjsfCheckboxGroupNode> */
|
|
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
|
+
useDefaults({}, 'VjsfSwitchGroup')
|
|
21
|
+
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
return () => {
|
|
24
|
+
return h(SelectionGroup, {
|
|
25
|
+
modelValue: props.modelValue,
|
|
26
|
+
statefulLayout: props.statefulLayout,
|
|
27
|
+
type: 'switch'
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.vjsf-node-checkbox-group .v-selection-control-group .v-checkbox .v-selection-control {
|
|
37
|
+
min-height: auto;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { VSwitch } from 'vuetify/components'
|
|
3
3
|
import { computed } from 'vue'
|
|
4
4
|
import { getInputProps } from '../../utils/index.js'
|
|
5
|
+
import { useDefaults } from 'vuetify'
|
|
6
|
+
|
|
7
|
+
useDefaults({}, 'VjsfSwitch')
|
|
5
8
|
|
|
6
9
|
const props = defineProps({
|
|
7
10
|
modelValue: {
|
|
@@ -16,9 +19,12 @@ const props = defineProps({
|
|
|
16
19
|
}
|
|
17
20
|
})
|
|
18
21
|
|
|
19
|
-
const fieldProps = computed(() =>
|
|
20
|
-
getInputProps(props.modelValue, props.statefulLayout)
|
|
21
|
-
|
|
22
|
+
const fieldProps = computed(() => {
|
|
23
|
+
const inputProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
24
|
+
// it is not very common to show an error below checkboxes and switches and without hide-details=auto they take a lot of space
|
|
25
|
+
if (!('hideDetails' in inputProps)) inputProps.hideDetails = 'auto'
|
|
26
|
+
return inputProps
|
|
27
|
+
})
|
|
22
28
|
</script>
|
|
23
29
|
|
|
24
30
|
<template>
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { VTabs, VTab, VContainer, VSheet, VWindow, VWindowItem, VRow, VIcon } from 'vuetify/components'
|
|
3
|
+
import { useDefaults } from 'vuetify'
|
|
3
4
|
import { ref } from 'vue'
|
|
4
5
|
import { isSection } from '@json-layout/core'
|
|
5
6
|
import Node from '../node.vue'
|
|
6
7
|
import SectionHeader from '../fragments/section-header.vue'
|
|
8
|
+
import useCompDefaults from '../../composables/use-comp-defaults.js'
|
|
9
|
+
|
|
10
|
+
useDefaults({}, 'VjsfTabs')
|
|
11
|
+
const vSheetProps = useCompDefaults('VjsfTabs-VSheet', { border: true })
|
|
7
12
|
|
|
8
13
|
defineProps({
|
|
9
14
|
modelValue: {
|
|
@@ -23,13 +28,15 @@ const tab = ref(0)
|
|
|
23
28
|
|
|
24
29
|
<template>
|
|
25
30
|
<section-header :node="modelValue" />
|
|
26
|
-
<v-sheet
|
|
27
|
-
<v-tabs
|
|
31
|
+
<v-sheet v-bind="vSheetProps">
|
|
32
|
+
<v-tabs
|
|
33
|
+
v-model="tab"
|
|
34
|
+
direction="horizontal"
|
|
35
|
+
>
|
|
28
36
|
<v-tab
|
|
29
37
|
v-for="(child, i) of modelValue.children"
|
|
30
38
|
:key="child.key"
|
|
31
39
|
:value="i"
|
|
32
|
-
:density="modelValue.options.density"
|
|
33
40
|
:color="child.validated && (child.error || child.childError) ? 'error' : undefined"
|
|
34
41
|
>
|
|
35
42
|
<v-icon
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { defineComponent, h, computed } from 'vue'
|
|
3
3
|
import { VTextField } from 'vuetify/components'
|
|
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({}, 'VjsfTextField')
|
|
22
|
+
|
|
20
23
|
const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout, ['placeholder']))
|
|
21
24
|
const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
|
|
22
25
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { defineComponent, h, computed, ref, watch } from 'vue'
|
|
3
3
|
import { VTextarea } from 'vuetify/components'
|
|
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({}, 'VjsfTextArea')
|
|
22
|
+
|
|
20
23
|
/** @type {import('vue').Ref<null | HTMLElement>} */
|
|
21
24
|
const textarea = ref(null)
|
|
22
25
|
|
|
@@ -4,6 +4,11 @@ import { VTabs, VTab, VContainer, VSheet, VWindow, VWindowItem, VRow, VIcon } fr
|
|
|
4
4
|
import { ref } from 'vue'
|
|
5
5
|
import Node from '../node.vue'
|
|
6
6
|
import SectionHeader from '../fragments/section-header.vue'
|
|
7
|
+
import { useDefaults } from 'vuetify'
|
|
8
|
+
import useCompDefaults from '../../composables/use-comp-defaults.js'
|
|
9
|
+
|
|
10
|
+
useDefaults({}, 'VjsfVerticalTabs')
|
|
11
|
+
const vSheetProps = useCompDefaults('VjsfVerticalTabs-VSheet', { border: true })
|
|
7
12
|
|
|
8
13
|
defineProps({
|
|
9
14
|
modelValue: {
|
|
@@ -23,7 +28,7 @@ const tab = ref(0)
|
|
|
23
28
|
|
|
24
29
|
<template>
|
|
25
30
|
<section-header :node="modelValue" />
|
|
26
|
-
<v-sheet
|
|
31
|
+
<v-sheet v-bind="vSheetProps">
|
|
27
32
|
<div class="d-flex flex-row">
|
|
28
33
|
<v-tabs
|
|
29
34
|
v-model="tab"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** @type {import("../types.js").PartialVjsfOptions} */
|
|
2
2
|
export const defaultOptions = {
|
|
3
|
-
errorAlertProps: { type: 'error', variant: 'tonal' },
|
|
4
3
|
nodeComponents: {},
|
|
5
|
-
plugins:
|
|
4
|
+
plugins: [],
|
|
5
|
+
pluginsOptions: {}
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -11,18 +11,34 @@ export const defaultOptions = {
|
|
|
11
11
|
* @param {any} form
|
|
12
12
|
* @param {number} width
|
|
13
13
|
* @param {import("vue").Slots} slots
|
|
14
|
-
* @param {Record<string, import('vue').Component>}
|
|
14
|
+
* @param {Record<string, import('vue').Component>} defaultNodeComponents
|
|
15
|
+
* @param {(data: any) => void} onData
|
|
16
|
+
* @param {(statefulLayout: import('@json-layout/core').StatefulLayout) => void} onUpdate
|
|
17
|
+
* @param {(key: string) => void} onAutofocus
|
|
15
18
|
* @returns
|
|
16
19
|
*/
|
|
17
|
-
export const getFullOptions = (options, form, width, slots,
|
|
20
|
+
export const getFullOptions = (options, form, width, slots, defaultNodeComponents, onData, onUpdate, onAutofocus) => {
|
|
21
|
+
const components = { ...options?.components }
|
|
22
|
+
const nodeComponents = { ...defaultNodeComponents, ...options?.nodeComponents }
|
|
23
|
+
if (options?.plugins) {
|
|
24
|
+
for (const plugin of options.plugins) {
|
|
25
|
+
components[plugin.info.name] = plugin.info
|
|
26
|
+
nodeComponents[plugin.info.name] = plugin.nodeComponent
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
const fullOptions = {
|
|
19
31
|
...defaultOptions,
|
|
20
32
|
readOnly: !!(form && (form.isDisabled.value || form.isReadonly.value)),
|
|
21
33
|
...options,
|
|
34
|
+
onData,
|
|
35
|
+
onUpdate,
|
|
36
|
+
onAutofocus,
|
|
22
37
|
context: options?.context ? JSON.parse(JSON.stringify(options.context)) : {},
|
|
23
38
|
width: Math.round(width ?? 0),
|
|
24
39
|
vjsfSlots: { ...slots },
|
|
25
|
-
|
|
40
|
+
components,
|
|
41
|
+
nodeComponents
|
|
26
42
|
}
|
|
27
43
|
return /** @type import('../types.js').VjsfOptions */ (fullOptions)
|
|
28
44
|
}
|
package/src/components/vjsf.vue
CHANGED
|
@@ -18,6 +18,9 @@ import NodeDateTimePicker from './nodes/date-time-picker.vue'
|
|
|
18
18
|
import NodeColorPicker from './nodes/color-picker.vue'
|
|
19
19
|
import NodeSelect from './nodes/select.vue'
|
|
20
20
|
import NodeAutocomplete from './nodes/autocomplete.vue'
|
|
21
|
+
import NodeRadioGroup from './nodes/radio-group.vue'
|
|
22
|
+
import NodeCheckboxGroup from './nodes/checkbox-group.vue'
|
|
23
|
+
import NodeSwitchGroup from './nodes/switch-group.vue'
|
|
21
24
|
import NodeOneOfSelect from './nodes/one-of-select.vue'
|
|
22
25
|
import NodeTabs from './nodes/tabs.vue'
|
|
23
26
|
import NodeVerticalTabs from './nodes/vertical-tabs.vue'
|
|
@@ -42,6 +45,9 @@ const nodeComponents = {
|
|
|
42
45
|
'color-picker': NodeColorPicker,
|
|
43
46
|
select: NodeSelect,
|
|
44
47
|
autocomplete: NodeAutocomplete,
|
|
48
|
+
'radio-group': NodeRadioGroup,
|
|
49
|
+
'checkbox-group': NodeCheckboxGroup,
|
|
50
|
+
'switch-group': NodeSwitchGroup,
|
|
45
51
|
'one-of-select': NodeOneOfSelect,
|
|
46
52
|
tabs: NodeTabs,
|
|
47
53
|
'vertical-tabs': NodeVerticalTabs,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { inject, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
// inspired by https://github.com/vuetifyjs/vuetify/blob/27b4b1e52060b6bee13a290a4829f935f1bd9c05/packages/vuetify/src/composables/defaults.ts#L92
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {string} name
|
|
7
|
+
* @param {Record<string, any> | null} [localDefaults]
|
|
8
|
+
* @returns {import('vue').ComputedRef<Record<string, any>>}
|
|
9
|
+
*/
|
|
10
|
+
export default function useCompDefaultProps (name, localDefaults = null) {
|
|
11
|
+
/** @type {import('vue').Ref<import('vuetify').DefaultsInstance> | undefined} */
|
|
12
|
+
const defaults = inject(Symbol.for('vuetify:defaults'))
|
|
13
|
+
if (!defaults) throw new Error('[vjsf] Could not find defaults instance')
|
|
14
|
+
return computed(() => {
|
|
15
|
+
const componentDefaults = defaults.value?.[name] ?? {}
|
|
16
|
+
if (!localDefaults) return componentDefaults
|
|
17
|
+
return { ...componentDefaults, ...localDefaults }
|
|
18
|
+
})
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { watch, shallowRef, ref, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {{modelValue: import('@json-layout/core').StateNode, statefulLayout: import('../types.js').VjsfStatefulLayout}} props
|
|
5
|
+
*/
|
|
6
|
+
export default function (props) {
|
|
7
|
+
/** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
|
|
8
|
+
const items = shallowRef([])
|
|
9
|
+
/** @type import('vue').Ref<boolean> */
|
|
10
|
+
const loading = ref(false)
|
|
11
|
+
/** @type import('vue').Ref<string> */
|
|
12
|
+
const search = ref('')
|
|
13
|
+
|
|
14
|
+
const fetchItems = async () => {
|
|
15
|
+
loading.value = true
|
|
16
|
+
items.value = await props.statefulLayout.getItems(props.modelValue, search.value)
|
|
17
|
+
loading.value = false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
watch(() => props.modelValue.itemsCacheKey, (newValue, oldValue) => {
|
|
21
|
+
if (newValue === oldValue) return
|
|
22
|
+
fetchItems()
|
|
23
|
+
}, { immediate: true })
|
|
24
|
+
|
|
25
|
+
watch(search, () => {
|
|
26
|
+
fetchItems()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {any} selectedItem
|
|
31
|
+
* @param {any} itemValue
|
|
32
|
+
*/
|
|
33
|
+
const prepareSelectedItem = (selectedItem, itemValue) => {
|
|
34
|
+
let item = selectedItem
|
|
35
|
+
// item and value are the same when the selection is not found in items list
|
|
36
|
+
if (selectedItem === itemValue) {
|
|
37
|
+
try {
|
|
38
|
+
item = props.statefulLayout.prepareSelectItem(props.modelValue, selectedItem)
|
|
39
|
+
if (item.value === undefined) item.value = itemValue
|
|
40
|
+
} catch (e) {
|
|
41
|
+
item = { value: itemValue }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return item
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { items, loading, search, prepareSelectedItem }
|
|
48
|
+
}
|