@koumoul/vjsf 2.22.0 → 3.0.0-alpha.0
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 +50 -86
- package/src/compat/v2.js +82 -0
- package/src/compile/index.js +32 -0
- package/src/compile/v-jsf-compiled.vue.ejs +83 -0
- package/src/components/fragments/node-slot.vue +49 -0
- package/src/components/fragments/section-header.vue +52 -0
- package/src/components/fragments/text-field-menu.vue +68 -0
- package/src/components/node.vue +60 -0
- package/src/components/nodes/checkbox.vue +27 -0
- package/src/components/nodes/color-picker.vue +43 -0
- package/src/components/nodes/date-picker.vue +45 -0
- package/src/components/nodes/date-time-picker.vue +20 -0
- package/src/components/nodes/expansion-panels.vue +52 -0
- package/src/components/nodes/list.vue +112 -0
- package/src/components/nodes/number-field.vue +35 -0
- package/src/components/nodes/one-of-select.vue +56 -0
- package/src/components/nodes/section.vue +30 -0
- package/src/components/nodes/select.vue +59 -0
- package/src/components/nodes/slider.vue +34 -0
- package/src/components/nodes/switch.vue +29 -0
- package/src/components/nodes/tabs.vue +63 -0
- package/src/components/nodes/text-field.vue +29 -0
- package/src/components/nodes/textarea.vue +29 -0
- package/src/components/nodes/time-picker.vue +7 -0
- package/src/components/nodes/vertical-tabs.vue +70 -0
- package/src/components/options.js +25 -0
- package/src/components/tree.vue +25 -0
- package/src/components/types.ts +59 -0
- package/src/components/vjsf.vue +168 -0
- package/src/index.js +2 -0
- package/src/utils/clone.js +3 -0
- package/src/utils/dates.js +52 -0
- package/src/utils/props.js +79 -0
- package/src/utils/slots.js +19 -0
- package/types/compat/v2.d.ts +10 -0
- package/types/compat/v2.d.ts.map +1 -0
- package/types/compile/index.d.ts +7 -0
- package/types/compile/index.d.ts.map +1 -0
- package/types/components/fragments/node-slot.vue.d.ts +47 -0
- package/types/components/fragments/node-slot.vue.d.ts.map +1 -0
- package/types/components/fragments/section-header.vue.d.ts +8 -0
- package/types/components/fragments/section-header.vue.d.ts.map +1 -0
- package/types/components/fragments/text-field-menu.vue.d.ts +20 -0
- package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -0
- package/types/components/node.vue.d.ts +10 -0
- package/types/components/node.vue.d.ts.map +1 -0
- package/types/components/nodes/checkbox.vue.d.ts +10 -0
- package/types/components/nodes/checkbox.vue.d.ts.map +1 -0
- package/types/components/nodes/color-picker.vue.d.ts +10 -0
- package/types/components/nodes/color-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/date-picker.vue.d.ts +10 -0
- package/types/components/nodes/date-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/date-time-picker.vue.d.ts +10 -0
- package/types/components/nodes/date-time-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/expansion-panels.vue.d.ts +10 -0
- package/types/components/nodes/expansion-panels.vue.d.ts.map +1 -0
- package/types/components/nodes/list.vue.d.ts +10 -0
- package/types/components/nodes/list.vue.d.ts.map +1 -0
- package/types/components/nodes/number-field.vue.d.ts +27 -0
- package/types/components/nodes/number-field.vue.d.ts.map +1 -0
- package/types/components/nodes/one-of-select.vue.d.ts +10 -0
- package/types/components/nodes/one-of-select.vue.d.ts.map +1 -0
- package/types/components/nodes/section.vue.d.ts +10 -0
- package/types/components/nodes/section.vue.d.ts.map +1 -0
- package/types/components/nodes/select.vue.d.ts +10 -0
- package/types/components/nodes/select.vue.d.ts.map +1 -0
- package/types/components/nodes/slider.vue.d.ts +10 -0
- package/types/components/nodes/slider.vue.d.ts.map +1 -0
- package/types/components/nodes/switch.vue.d.ts +10 -0
- package/types/components/nodes/switch.vue.d.ts.map +1 -0
- package/types/components/nodes/tabs.vue.d.ts +10 -0
- package/types/components/nodes/tabs.vue.d.ts.map +1 -0
- package/types/components/nodes/text-field copy.vue.d.ts +10 -0
- package/types/components/nodes/text-field copy.vue.d.ts.map +1 -0
- package/types/components/nodes/text-field.vue.d.ts +27 -0
- package/types/components/nodes/text-field.vue.d.ts.map +1 -0
- package/types/components/nodes/textarea.vue.d.ts +27 -0
- package/types/components/nodes/textarea.vue.d.ts.map +1 -0
- package/types/components/nodes/time-picker.vue.d.ts +3 -0
- package/types/components/nodes/time-picker.vue.d.ts.map +1 -0
- package/types/components/nodes/vertical-tabs.vue.d.ts +10 -0
- package/types/components/nodes/vertical-tabs.vue.d.ts.map +1 -0
- package/types/components/options.d.ts +3 -0
- package/types/components/options.d.ts.map +1 -0
- package/types/components/tree.vue.d.ts +10 -0
- package/types/components/tree.vue.d.ts.map +1 -0
- package/types/components/types.d.ts +71 -0
- package/types/components/types.d.ts.map +1 -0
- package/types/components/v-jsf.vue.d.ts +13 -0
- package/types/components/v-jsf.vue.d.ts.map +1 -0
- package/types/components/vjsf.vue.d.ts +13 -0
- package/types/components/vjsf.vue.d.ts.map +1 -0
- package/types/index.d.ts +3 -0
- package/types/index.d.ts.map +1 -0
- package/types/utils/clone.d.ts +3 -0
- package/types/utils/clone.d.ts.map +1 -0
- package/types/utils/dates.d.ts +7 -0
- package/types/utils/dates.d.ts.map +1 -0
- package/types/utils/props.d.ts +20 -0
- package/types/utils/props.d.ts.map +1 -0
- package/types/utils/slots.d.ts +7 -0
- package/types/utils/slots.d.ts.map +1 -0
- package/.eslintignore +0 -9
- package/.eslintrc.js +0 -38
- package/.github/workflows/scrape-doc.yml +0 -14
- package/.nvmrc +0 -1
- package/CONTRIBUTE.md +0 -61
- package/FUNDING.yml +0 -1
- package/README.md +0 -19
- package/babel.config.js +0 -4
- package/dist/main.css +0 -63
- package/dist/main.js +0 -2
- package/dist/main.js.LICENSE.txt +0 -10
- package/dist/third-party.js +0 -8
- package/dist/third-party.js.LICENSE.txt +0 -8
- package/lib/VJsf.css +0 -63
- package/lib/VJsf.js +0 -117
- package/lib/VJsfNoDeps.js +0 -517
- package/lib/deps/third-party.js +0 -16
- package/lib/mixins/ColorProperty.js +0 -45
- package/lib/mixins/DateProperty.js +0 -170
- package/lib/mixins/Dependent.js +0 -69
- package/lib/mixins/EditableArray.js +0 -418
- package/lib/mixins/FileProperty.js +0 -81
- package/lib/mixins/MarkdownEditor.js +0 -183
- package/lib/mixins/ObjectContainer.js +0 -351
- package/lib/mixins/SelectProperty.js +0 -400
- package/lib/mixins/SimpleProperty.js +0 -165
- package/lib/mixins/Tooltip.js +0 -42
- package/lib/mixins/Validatable.js +0 -119
- package/lib/utils/expr-eval-parser.js +0 -21
- package/lib/utils/is-cyclic.js +0 -34
- package/lib/utils/json-refs.js +0 -209
- package/lib/utils/options.js +0 -328
- package/lib/utils/rules.js +0 -81
- package/lib/utils/schema.js +0 -100
- package/lib/utils/select.js +0 -141
- package/webpack.config.js +0 -46
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import TextFieldMenu from '../fragments/text-field-menu.vue'
|
|
3
|
+
import { VDatePicker } from 'vuetify/labs/VDatePicker'
|
|
4
|
+
import { useDate } from 'vuetify/labs/date'
|
|
5
|
+
import { computed } from 'vue'
|
|
6
|
+
import { getCompProps } from '../../utils/props.js'
|
|
7
|
+
import { getDateTimeParts } from '../../utils/dates.js'
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
modelValue: {
|
|
11
|
+
/** @type import('vue').PropType<import('../types.js').VjsfDatePickerNode> */
|
|
12
|
+
type: Object,
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
statefulLayout: {
|
|
16
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
17
|
+
type: Object,
|
|
18
|
+
required: true
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const vDate = useDate()
|
|
23
|
+
|
|
24
|
+
const datePickerProps = computed(() => {
|
|
25
|
+
const datePickerProps = getCompProps(props.modelValue, 'datePicker', true)
|
|
26
|
+
datePickerProps.hideActions = true
|
|
27
|
+
if (props.modelValue.data) datePickerProps.modelValue = new Date(props.modelValue.data)
|
|
28
|
+
return datePickerProps
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<text-field-menu
|
|
34
|
+
:model-value="modelValue"
|
|
35
|
+
:stateful-layout="statefulLayout"
|
|
36
|
+
:formatted-value="modelValue.data && vDate.format(modelValue.data, 'fullDateWithWeekday')"
|
|
37
|
+
>
|
|
38
|
+
<template #default="{close}">
|
|
39
|
+
<v-date-picker
|
|
40
|
+
v-bind="datePickerProps"
|
|
41
|
+
@update:model-value="value => {statefulLayout.input(modelValue, value && getDateTimeParts(/** @type Date */(/** @type unknown */(value)))[0]); close()}"
|
|
42
|
+
/>
|
|
43
|
+
</template>
|
|
44
|
+
</text-field-menu>
|
|
45
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
defineProps({
|
|
4
|
+
modelValue: {
|
|
5
|
+
/** @type import('vue').PropType<import('../types.js').VjsfDateTimePickerNode> */
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true
|
|
8
|
+
},
|
|
9
|
+
statefulLayout: {
|
|
10
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
11
|
+
type: Object,
|
|
12
|
+
required: true
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
TODO date-time
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VContainer } from 'vuetify/components'
|
|
3
|
+
import { isSection } from '@json-layout/core'
|
|
4
|
+
import Node from '../node.vue'
|
|
5
|
+
import SectionHeader from '../fragments/section-header.vue'
|
|
6
|
+
|
|
7
|
+
defineProps({
|
|
8
|
+
modelValue: {
|
|
9
|
+
/** @type import('vue').PropType<import('../types.js').VjsfExpansionPanelsNode> */
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
statefulLayout: {
|
|
14
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<section-header :node="modelValue" />
|
|
24
|
+
<v-expansion-panels>
|
|
25
|
+
<v-expansion-panel
|
|
26
|
+
v-for="(child, i) of modelValue.children"
|
|
27
|
+
:key="child.key"
|
|
28
|
+
:value="i"
|
|
29
|
+
>
|
|
30
|
+
<v-expansion-panel-title>
|
|
31
|
+
<v-icon
|
|
32
|
+
v-if="child.validated && (child.error || child.childError)"
|
|
33
|
+
color="error"
|
|
34
|
+
class="mr-2"
|
|
35
|
+
>
|
|
36
|
+
mdi-alert
|
|
37
|
+
</v-icon>
|
|
38
|
+
{{ child.layout.title ?? child.layout.label }}
|
|
39
|
+
</v-expansion-panel-title>
|
|
40
|
+
<v-container fluid>
|
|
41
|
+
<v-row>
|
|
42
|
+
<node
|
|
43
|
+
v-for="grandChild of isSection(child) ? child.children : [child]"
|
|
44
|
+
:key="grandChild.fullKey"
|
|
45
|
+
:model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
|
|
46
|
+
:stateful-layout="statefulLayout"
|
|
47
|
+
/>
|
|
48
|
+
</v-row>
|
|
49
|
+
</v-container>
|
|
50
|
+
</v-expansion-panel>
|
|
51
|
+
</v-expansion-panels>
|
|
52
|
+
</template>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VList, VListItem, VListItemAction, VBtn, VMenu, VIcon } from 'vuetify/components'
|
|
3
|
+
import { isSection } from '@json-layout/core'
|
|
4
|
+
import Node from '../node.vue'
|
|
5
|
+
import clone from '../../utils/clone.js'
|
|
6
|
+
|
|
7
|
+
defineProps({
|
|
8
|
+
modelValue: {
|
|
9
|
+
/** @type import('vue').PropType<import('../types.js').VjsfListNode> */
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
statefulLayout: {
|
|
14
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<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"
|
|
66
|
+
>
|
|
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)])"
|
|
81
|
+
>
|
|
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)])"
|
|
91
|
+
>
|
|
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>
|
|
100
|
+
</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>
|
|
112
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defineComponent, h, computed } from 'vue'
|
|
3
|
+
import { VTextField } 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').VjsfNumberFieldNode> */
|
|
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
|
+
const fieldProps = computed(() => {
|
|
22
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
23
|
+
fieldProps.type = 'number'
|
|
24
|
+
if ('step' in props.modelValue.layout) fieldProps.step = props.modelValue.layout.step
|
|
25
|
+
fieldProps['onUpdate:modelValue'] = (/** @type string */value) => props.statefulLayout.input(props.modelValue, value && Number(value))
|
|
26
|
+
return fieldProps
|
|
27
|
+
})
|
|
28
|
+
const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
|
|
29
|
+
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
return () => h(VTextField, fieldProps.value, fieldSlots.value)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VSelect } from 'vuetify/components'
|
|
3
|
+
import { shallowRef, watch } from 'vue'
|
|
4
|
+
import { isSection } from '@json-layout/core'
|
|
5
|
+
import Node from '../node.vue'
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
modelValue: {
|
|
9
|
+
/** @type import('vue').PropType<import('../types.js').VjsfOneOfSelectNode> */
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
statefulLayout: {
|
|
14
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/** @type import('vue').ShallowRef<import('@json-layout/core').SkeletonTree | undefined> */
|
|
21
|
+
const activeChildTree = shallowRef(undefined)
|
|
22
|
+
watch(() => props.modelValue, () => {
|
|
23
|
+
// we set the active oneOf child as the one whose schema validates on the current data
|
|
24
|
+
if (props.modelValue.fullKey in props.statefulLayout.activeItems) {
|
|
25
|
+
activeChildTree.value = props.modelValue.skeleton.childrenTrees?.[props.statefulLayout.activeItems[props.modelValue.fullKey]]
|
|
26
|
+
} else {
|
|
27
|
+
activeChildTree.value = undefined
|
|
28
|
+
}
|
|
29
|
+
}, { immediate: true })
|
|
30
|
+
|
|
31
|
+
const onChange = (/** @type import('@json-layout/core').SkeletonTree */childTree) => {
|
|
32
|
+
if (!props.modelValue.skeleton.childrenTrees) return
|
|
33
|
+
props.statefulLayout.activateItem(props.modelValue, props.modelValue.skeleton.childrenTrees.indexOf(childTree))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<v-select
|
|
40
|
+
v-if="modelValue.skeleton.childrenTrees"
|
|
41
|
+
v-model="activeChildTree"
|
|
42
|
+
:items="modelValue.skeleton.childrenTrees"
|
|
43
|
+
item-title="title"
|
|
44
|
+
return-object
|
|
45
|
+
:error-messages="modelValue.validated ? modelValue.error : null"
|
|
46
|
+
@update:model-value="onChange"
|
|
47
|
+
/>
|
|
48
|
+
<v-row v-if="modelValue.children?.[0]">
|
|
49
|
+
<node
|
|
50
|
+
v-for="grandChild of isSection(modelValue.children?.[0]) ? modelValue.children?.[0].children : modelValue.children"
|
|
51
|
+
:key="grandChild.fullKey"
|
|
52
|
+
:model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
|
|
53
|
+
:stateful-layout="statefulLayout"
|
|
54
|
+
/>
|
|
55
|
+
</v-row>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Node from '../node.vue'
|
|
3
|
+
import SectionHeader from '../fragments/section-header.vue'
|
|
4
|
+
|
|
5
|
+
defineProps({
|
|
6
|
+
modelValue: {
|
|
7
|
+
/** @type import('vue').PropType<import('../types.js').VjsfSectionNode> */
|
|
8
|
+
type: Object,
|
|
9
|
+
required: true
|
|
10
|
+
},
|
|
11
|
+
statefulLayout: {
|
|
12
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
13
|
+
type: Object,
|
|
14
|
+
required: true
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<section-header :node="modelValue" />
|
|
22
|
+
<v-row :dense="modelValue.options?.density === 'compact' || modelValue.options?.density === 'comfortable'">
|
|
23
|
+
<node
|
|
24
|
+
v-for="child of modelValue.children"
|
|
25
|
+
:key="child.fullKey"
|
|
26
|
+
:model-value="/** @type import('../types.js').VjsfNode */(child)"
|
|
27
|
+
:stateful-layout="statefulLayout"
|
|
28
|
+
/>
|
|
29
|
+
</v-row>
|
|
30
|
+
</template>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup>
|
|
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
|
|
11
|
+
},
|
|
12
|
+
statefulLayout: {
|
|
13
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
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
|
+
})
|
|
24
|
+
|
|
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
|
+
}
|
|
48
|
+
|
|
49
|
+
</script>
|
|
50
|
+
|
|
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>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VSlider } from 'vuetify/components'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { getInputProps } from '../../utils/props.js'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
modelValue: {
|
|
8
|
+
/** @type import('vue').PropType<import('../types.js').VjsfSliderNode> */
|
|
9
|
+
type: Object,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
statefulLayout: {
|
|
13
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const fieldProps = computed(() => {
|
|
20
|
+
const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
|
|
21
|
+
if ('step' in props.modelValue.layout) fieldProps.step = props.modelValue.layout.step
|
|
22
|
+
fieldProps.min = props.modelValue.layout.min
|
|
23
|
+
fieldProps.max = props.modelValue.layout.max
|
|
24
|
+
return fieldProps
|
|
25
|
+
})
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<v-slider
|
|
30
|
+
type="number"
|
|
31
|
+
v-bind="fieldProps"
|
|
32
|
+
@update:model-value="value => statefulLayout.input(modelValue, value && Number(value))"
|
|
33
|
+
/>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VSwitch } from 'vuetify/components'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { getInputProps } from '../../utils/props.js'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
modelValue: {
|
|
8
|
+
/** @type import('vue').PropType<import('../types.js').VjsfSwitchNode> */
|
|
9
|
+
type: Object,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
statefulLayout: {
|
|
13
|
+
/** @type import('vue').PropType<import('@json-layout/core').StatefulLayout> */
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const fieldProps = computed(() =>
|
|
20
|
+
getInputProps(props.modelValue, props.statefulLayout)
|
|
21
|
+
)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<v-switch
|
|
26
|
+
v-bind="fieldProps"
|
|
27
|
+
@update:model-value="value => statefulLayout.input(modelValue, value)"
|
|
28
|
+
/>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VTabs, VTab, VContainer } from 'vuetify/components'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import { isSection } from '@json-layout/core'
|
|
5
|
+
import Node from '../node.vue'
|
|
6
|
+
import SectionHeader from '../fragments/section-header.vue'
|
|
7
|
+
|
|
8
|
+
defineProps({
|
|
9
|
+
modelValue: {
|
|
10
|
+
/** @type import('vue').PropType<import('../types.js').VjsfTabsNode> */
|
|
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
|
+
|
|
21
|
+
const tab = ref(0)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<section-header :node="modelValue" />
|
|
26
|
+
<v-sheet border>
|
|
27
|
+
<v-tabs v-model="tab">
|
|
28
|
+
<v-tab
|
|
29
|
+
v-for="(child, i) of modelValue.children"
|
|
30
|
+
:key="child.key"
|
|
31
|
+
:value="i"
|
|
32
|
+
:density="modelValue.options.density"
|
|
33
|
+
:color="child.validated && (child.error || child.childError) ? 'error' : undefined"
|
|
34
|
+
>
|
|
35
|
+
<v-icon
|
|
36
|
+
v-if="child.validated && (child.error || child.childError)"
|
|
37
|
+
color="error"
|
|
38
|
+
>
|
|
39
|
+
mdi-alert
|
|
40
|
+
</v-icon>
|
|
41
|
+
{{ child.layout.title ?? child.layout.label }}
|
|
42
|
+
</v-tab>
|
|
43
|
+
</v-tabs>
|
|
44
|
+
<v-window v-model="tab">
|
|
45
|
+
<v-window-item
|
|
46
|
+
v-for="(child, i) of modelValue.children"
|
|
47
|
+
:key="child.key"
|
|
48
|
+
:value="i"
|
|
49
|
+
>
|
|
50
|
+
<v-container fluid>
|
|
51
|
+
<v-row>
|
|
52
|
+
<node
|
|
53
|
+
v-for="grandChild of isSection(child) ? child.children : [child]"
|
|
54
|
+
:key="grandChild.fullKey"
|
|
55
|
+
:model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
|
|
56
|
+
:stateful-layout="statefulLayout"
|
|
57
|
+
/>
|
|
58
|
+
</v-row>
|
|
59
|
+
</v-container>
|
|
60
|
+
</v-window-item>
|
|
61
|
+
</v-window>
|
|
62
|
+
</v-sheet>
|
|
63
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defineComponent, h, computed } from 'vue'
|
|
3
|
+
import { VTextField } 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').VjsfTextFieldNode> */
|
|
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
|
+
const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout))
|
|
22
|
+
const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
|
|
23
|
+
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
return () => h(VTextField, fieldProps.value, fieldSlots.value)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defineComponent, h, computed } from 'vue'
|
|
3
|
+
import { VTextarea } 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').VjsfTextareaNode> */
|
|
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
|
+
const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout))
|
|
22
|
+
const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
|
|
23
|
+
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
return () => h(VTextarea, fieldProps.value, fieldSlots.value)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { isSection } from '@json-layout/core'
|
|
3
|
+
import { VTabs, VTab, VContainer } from 'vuetify/components'
|
|
4
|
+
import { ref } from 'vue'
|
|
5
|
+
import Node from '../node.vue'
|
|
6
|
+
import SectionHeader from '../fragments/section-header.vue'
|
|
7
|
+
|
|
8
|
+
defineProps({
|
|
9
|
+
modelValue: {
|
|
10
|
+
/** @type import('vue').PropType<import('../types.js').VjsfVerticalTabsNode> */
|
|
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
|
+
|
|
21
|
+
const tab = ref(0)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<section-header :node="modelValue" />
|
|
26
|
+
<v-sheet border>
|
|
27
|
+
<div class="d-flex flex-row">
|
|
28
|
+
<v-tabs
|
|
29
|
+
v-model="tab"
|
|
30
|
+
direction="vertical"
|
|
31
|
+
>
|
|
32
|
+
<v-tab
|
|
33
|
+
v-for="(child, i) of modelValue.children"
|
|
34
|
+
:key="child.key"
|
|
35
|
+
:value="i"
|
|
36
|
+
:color="child.validated && (child.error || child.childError) ? 'error' : undefined"
|
|
37
|
+
>
|
|
38
|
+
<v-icon
|
|
39
|
+
v-if="child.validated && (child.error || child.childError)"
|
|
40
|
+
color="error"
|
|
41
|
+
>
|
|
42
|
+
mdi-alert
|
|
43
|
+
</v-icon>
|
|
44
|
+
{{ child.layout.title ?? child.layout.label }}
|
|
45
|
+
</v-tab>
|
|
46
|
+
</v-tabs>
|
|
47
|
+
<v-window
|
|
48
|
+
v-model="tab"
|
|
49
|
+
class="flex-fill"
|
|
50
|
+
>
|
|
51
|
+
<v-window-item
|
|
52
|
+
v-for="(child, i) of modelValue.children"
|
|
53
|
+
:key="child.key"
|
|
54
|
+
:value="i"
|
|
55
|
+
>
|
|
56
|
+
<v-container fluid>
|
|
57
|
+
<v-row>
|
|
58
|
+
<node
|
|
59
|
+
v-for="grandChild of isSection(child) ? child.children : [child]"
|
|
60
|
+
:key="grandChild.fullKey"
|
|
61
|
+
:model-value="/** @type import('../types.js').VjsfNode */(grandChild)"
|
|
62
|
+
:stateful-layout="statefulLayout"
|
|
63
|
+
/>
|
|
64
|
+
</v-row>
|
|
65
|
+
</v-container>
|
|
66
|
+
</v-window-item>
|
|
67
|
+
</v-window>
|
|
68
|
+
</div>
|
|
69
|
+
</v-sheet>
|
|
70
|
+
</template>
|