@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.
Files changed (103) hide show
  1. package/README.md +21 -0
  2. package/package.json +5 -4
  3. package/src/compat/v2.js +132 -27
  4. package/src/compile/index.js +18 -4
  5. package/src/compile/options.js +3 -7
  6. package/src/compile/v-jsf-compiled.vue.ejs +1 -1
  7. package/src/components/fragments/help-message.vue +10 -1
  8. package/src/components/fragments/section-header.vue +1 -3
  9. package/src/components/fragments/select-item-icon.vue +1 -1
  10. package/src/components/fragments/select-selection.vue +2 -1
  11. package/src/components/fragments/selection-group.vue +100 -0
  12. package/src/components/fragments/text-field-menu.vue +5 -1
  13. package/src/components/node.vue +47 -42
  14. package/src/components/nodes/autocomplete.vue +11 -35
  15. package/src/components/nodes/checkbox-group.vue +39 -0
  16. package/src/components/nodes/checkbox.vue +9 -1
  17. package/src/components/nodes/color-picker.vue +5 -1
  18. package/src/components/nodes/combobox.vue +3 -0
  19. package/src/components/nodes/date-picker.vue +4 -2
  20. package/src/components/nodes/date-time-picker.vue +3 -0
  21. package/src/components/nodes/expansion-panels.vue +24 -12
  22. package/src/components/nodes/file-input.vue +3 -0
  23. package/src/components/nodes/list.vue +145 -91
  24. package/src/components/nodes/number-combobox.vue +3 -0
  25. package/src/components/nodes/number-field.vue +3 -0
  26. package/src/components/nodes/one-of-select.vue +45 -24
  27. package/src/components/nodes/radio-group.vue +53 -0
  28. package/src/components/nodes/section.vue +3 -0
  29. package/src/components/nodes/select.vue +10 -27
  30. package/src/components/nodes/slider.vue +3 -0
  31. package/src/components/nodes/stepper.vue +3 -0
  32. package/src/components/nodes/switch-group.vue +39 -0
  33. package/src/components/nodes/switch.vue +9 -3
  34. package/src/components/nodes/tabs.vue +10 -3
  35. package/src/components/nodes/text-field.vue +3 -0
  36. package/src/components/nodes/textarea.vue +3 -0
  37. package/src/components/nodes/vertical-tabs.vue +6 -1
  38. package/src/components/options.js +21 -5
  39. package/src/components/vjsf.vue +6 -0
  40. package/src/composables/use-comp-defaults.js +19 -0
  41. package/src/composables/use-dnd.js +1 -0
  42. package/src/composables/use-get-items.js +48 -0
  43. package/src/composables/use-vjsf.js +76 -40
  44. package/src/index.js +3 -0
  45. package/src/types.ts +21 -6
  46. package/src/utils/build.js +1 -1
  47. package/src/utils/index.js +0 -1
  48. package/src/utils/props.js +9 -30
  49. package/types/compat/v2.d.ts.map +1 -1
  50. package/types/compile/index.d.ts +2 -2
  51. package/types/compile/index.d.ts.map +1 -1
  52. package/types/compile/options.d.ts.map +1 -1
  53. package/types/components/fragments/selection-group.vue.d.ts +35 -0
  54. package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
  55. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
  56. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
  57. package/types/components/nodes/checkbox-group copy.vue.d.ts +27 -0
  58. package/types/components/nodes/checkbox-group copy.vue.d.ts.map +1 -0
  59. package/types/components/nodes/checkbox-group.vue.d.ts +27 -0
  60. package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
  61. package/types/components/nodes/combobox.vue.d.ts.map +1 -1
  62. package/types/components/nodes/file-input.vue.d.ts.map +1 -1
  63. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -1
  64. package/types/components/nodes/number-field.vue.d.ts.map +1 -1
  65. package/types/components/nodes/radio-group.vue.d.ts +27 -0
  66. package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
  67. package/types/components/nodes/radio.vue.d.ts +10 -0
  68. package/types/components/nodes/radio.vue.d.ts.map +1 -0
  69. package/types/components/nodes/select.vue.d.ts.map +1 -1
  70. package/types/components/nodes/switch-group.vue.d.ts +27 -0
  71. package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
  72. package/types/components/nodes/text-field.vue.d.ts.map +1 -1
  73. package/types/components/nodes/textarea.vue.d.ts.map +1 -1
  74. package/types/components/options.d.ts +1 -1
  75. package/types/components/options.d.ts.map +1 -1
  76. package/types/components/vjsf.vue.d.ts +2 -2
  77. package/types/composables/use-comp-defaults.d.ts +8 -0
  78. package/types/composables/use-comp-defaults.d.ts.map +1 -0
  79. package/types/composables/use-dnd.d.ts.map +1 -1
  80. package/types/composables/use-get-items.d.ts +13 -0
  81. package/types/composables/use-get-items.d.ts.map +1 -0
  82. package/types/composables/use-vjsf.d.ts.map +1 -1
  83. package/types/index.d.ts +2 -0
  84. package/types/index.d.ts.map +1 -1
  85. package/types/types.d.ts +20 -8
  86. package/types/types.d.ts.map +1 -1
  87. package/types/utils/build.d.ts +1 -1
  88. package/types/utils/index.d.ts +0 -1
  89. package/types/utils/props.d.ts +3 -4
  90. package/types/utils/props.d.ts.map +1 -1
  91. package/src/utils/global-register.js +0 -13
  92. package/types/components/global-register.d.ts +0 -8
  93. package/types/components/global-register.d.ts.map +0 -1
  94. package/types/components/nodes/markdown.vue.d.ts +0 -27
  95. package/types/components/nodes/markdown.vue.d.ts.map +0 -1
  96. package/types/components/nodes/text-field copy.vue.d.ts +0 -10
  97. package/types/components/nodes/text-field copy.vue.d.ts.map +0 -1
  98. package/types/components/types.d.ts +0 -91
  99. package/types/components/types.d.ts.map +0 -1
  100. package/types/components/v-jsf.vue.d.ts +0 -13
  101. package/types/components/v-jsf.vue.d.ts.map +0 -1
  102. package/types/utils/clone.d.ts +0 -3
  103. 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 { shallowRef, watch } from 'vue'
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').ShallowRef<import('@json-layout/core').SkeletonTree | undefined> */
21
- const activeChildTree = shallowRef(undefined)
25
+ /** @type import('vue').Ref<string | undefined> */
26
+ const activeChildTree = ref(undefined)
22
27
  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]]
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 import('@json-layout/core').SkeletonTree */childTree) => {
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-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
- />
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>
@@ -2,6 +2,9 @@
2
2
  import { VRow } from 'vuetify/components'
3
3
  import Node from '../node.vue'
4
4
  import SectionHeader from '../fragments/section-header.vue'
5
+ import { useDefaults } from 'vuetify'
6
+
7
+ useDefaults({}, 'VjsfSection')
5
8
 
6
9
  defineProps({
7
10
  modelValue: {
@@ -1,9 +1,11 @@
1
1
  <script>
2
2
  import { VSelect } from 'vuetify/components'
3
- import { defineComponent, h, computed, ref, shallowRef } from 'vue'
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
- /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
23
- const items = shallowRef([])
24
- /** @type import('vue').Ref<boolean> */
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['onUpdate:menu'] = refresh
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
@@ -2,6 +2,9 @@
2
2
  import { VSlider } 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({}, 'VjsfSlider')
5
8
 
6
9
  const props = defineProps({
7
10
  modelValue: {
@@ -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 border>
27
- <v-tabs v-model="tab">
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 border>
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>} nodeComponents
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, nodeComponents) => {
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
- nodeComponents: { ...nodeComponents, ...options?.nodeComponents }
40
+ components,
41
+ nodeComponents
26
42
  }
27
43
  return /** @type import('../types.js').VjsfOptions */ (fullOptions)
28
44
  }
@@ -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
+ }
@@ -42,6 +42,7 @@ export default function useDnd (array, callback) {
42
42
  dragging.value = itemIndex
43
43
  },
44
44
  onDragend: () => {
45
+ hovered.value = itemIndex
45
46
  dragging.value = -1
46
47
  callback()
47
48
  }
@@ -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
+ }