@koumoul/vjsf 3.0.0-beta.9 → 3.0.1

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 (162) hide show
  1. package/README.md +21 -0
  2. package/package.json +13 -20
  3. package/src/compat/v2.js +126 -27
  4. package/src/components/fragments/child-subtitle.vue +25 -0
  5. package/src/components/fragments/help-message.vue +33 -8
  6. package/src/components/fragments/section-header.vue +9 -7
  7. package/src/components/fragments/select-item-icon.vue +2 -2
  8. package/src/components/fragments/select-item.vue +2 -1
  9. package/src/components/fragments/select-selection.vue +2 -1
  10. package/src/components/fragments/selection-group.vue +105 -0
  11. package/src/components/fragments/text-field-menu.vue +16 -7
  12. package/src/components/node.vue +58 -41
  13. package/src/components/nodes/autocomplete.vue +14 -60
  14. package/src/components/nodes/card.vue +39 -0
  15. package/src/components/nodes/checkbox-group.vue +39 -0
  16. package/src/components/nodes/checkbox.vue +31 -26
  17. package/src/components/nodes/color-picker.vue +10 -5
  18. package/src/components/nodes/combobox.vue +17 -40
  19. package/src/components/nodes/date-picker.vue +33 -12
  20. package/src/components/nodes/date-time-picker.vue +86 -3
  21. package/src/components/nodes/expansion-panels.vue +17 -9
  22. package/src/components/nodes/file-input.vue +15 -11
  23. package/src/components/nodes/list.vue +246 -112
  24. package/src/components/nodes/number-combobox.vue +18 -39
  25. package/src/components/nodes/number-field.vue +17 -11
  26. package/src/components/nodes/one-of-select.vue +53 -27
  27. package/src/components/nodes/radio-group.vue +58 -0
  28. package/src/components/nodes/section.vue +4 -1
  29. package/src/components/nodes/select.vue +15 -54
  30. package/src/components/nodes/slider.vue +32 -29
  31. package/src/components/nodes/stepper.vue +10 -2
  32. package/src/components/nodes/switch-group.vue +39 -0
  33. package/src/components/nodes/switch.vue +31 -26
  34. package/src/components/nodes/tabs.vue +20 -8
  35. package/src/components/nodes/text-field.vue +10 -7
  36. package/src/components/nodes/textarea.vue +20 -12
  37. package/src/components/nodes/time-picker.vue +45 -1
  38. package/src/components/nodes/vertical-tabs.vue +16 -6
  39. package/src/components/tree.vue +1 -1
  40. package/src/components/vjsf.vue +11 -1
  41. package/src/composables/use-comp-defaults.js +19 -0
  42. package/src/composables/use-dnd.js +2 -1
  43. package/src/composables/use-get-items.js +53 -0
  44. package/src/composables/use-node.js +136 -0
  45. package/src/composables/use-select-node.js +67 -0
  46. package/src/composables/use-vjsf.js +72 -51
  47. package/src/index.js +5 -2
  48. package/src/options.js +67 -0
  49. package/src/types.ts +64 -33
  50. package/src/utils/arrays.js +37 -6
  51. package/types/compat/v2.d.ts.map +1 -1
  52. package/types/compile/index.d.ts +2 -2
  53. package/types/compile/index.d.ts.map +1 -1
  54. package/types/compile/options.d.ts +3 -2
  55. package/types/compile/options.d.ts.map +1 -1
  56. package/types/components/fragments/child-subtitle.vue.d.ts +8 -0
  57. package/types/components/fragments/child-subtitle.vue.d.ts.map +1 -0
  58. package/types/components/fragments/help-message.vue.d.ts +2 -2
  59. package/types/components/fragments/node-slot.vue.d.ts +2 -44
  60. package/types/components/fragments/node-slot.vue.d.ts.map +1 -1
  61. package/types/components/fragments/section-header.vue.d.ts +4 -2
  62. package/types/components/fragments/select-item-icon.vue.d.ts +2 -12
  63. package/types/components/fragments/select-item.vue.d.ts +2 -2
  64. package/types/components/fragments/select-selection.vue.d.ts +2 -2
  65. package/types/components/fragments/selection-group.vue.d.ts +5 -0
  66. package/types/components/fragments/selection-group.vue.d.ts.map +1 -0
  67. package/types/components/fragments/text-field-menu.vue.d.ts +2 -2
  68. package/types/components/fragments/text-field-menu.vue.d.ts.map +1 -1
  69. package/types/components/node.vue.d.ts +2 -2
  70. package/types/components/nodes/autocomplete.vue.d.ts +2 -24
  71. package/types/components/nodes/autocomplete.vue.d.ts.map +1 -1
  72. package/types/components/nodes/card.vue.d.ts +10 -0
  73. package/types/components/nodes/card.vue.d.ts.map +1 -0
  74. package/types/components/nodes/checkbox-group.vue.d.ts +5 -0
  75. package/types/components/nodes/checkbox-group.vue.d.ts.map +1 -0
  76. package/types/components/nodes/checkbox.vue.d.ts +3 -8
  77. package/types/components/nodes/color-picker.vue.d.ts +2 -2
  78. package/types/components/nodes/combobox.vue.d.ts +2 -24
  79. package/types/components/nodes/combobox.vue.d.ts.map +1 -1
  80. package/types/components/nodes/date-picker.vue.d.ts +2 -2
  81. package/types/components/nodes/date-time-picker.vue.d.ts +4 -4
  82. package/types/components/nodes/expansion-panels.vue.d.ts +2 -2
  83. package/types/components/nodes/file-input.vue.d.ts +2 -24
  84. package/types/components/nodes/file-input.vue.d.ts.map +1 -1
  85. package/types/components/nodes/list.vue.d.ts +2 -2
  86. package/types/components/nodes/number-combobox.vue.d.ts +2 -24
  87. package/types/components/nodes/number-combobox.vue.d.ts.map +1 -1
  88. package/types/components/nodes/number-field.vue.d.ts +2 -24
  89. package/types/components/nodes/number-field.vue.d.ts.map +1 -1
  90. package/types/components/nodes/one-of-select.vue.d.ts +2 -2
  91. package/types/components/nodes/radio-group.vue.d.ts +5 -0
  92. package/types/components/nodes/radio-group.vue.d.ts.map +1 -0
  93. package/types/components/nodes/section.vue.d.ts +2 -2
  94. package/types/components/nodes/select.vue.d.ts +2 -24
  95. package/types/components/nodes/select.vue.d.ts.map +1 -1
  96. package/types/components/nodes/slider.vue.d.ts +3 -8
  97. package/types/components/nodes/stepper.vue.d.ts +2 -2
  98. package/types/components/nodes/switch-group.vue.d.ts +5 -0
  99. package/types/components/nodes/switch-group.vue.d.ts.map +1 -0
  100. package/types/components/nodes/switch.vue.d.ts +3 -8
  101. package/types/components/nodes/tabs.vue.d.ts +2 -2
  102. package/types/components/nodes/text-field.vue.d.ts +2 -24
  103. package/types/components/nodes/text-field.vue.d.ts.map +1 -1
  104. package/types/components/nodes/textarea.vue.d.ts +2 -24
  105. package/types/components/nodes/textarea.vue.d.ts.map +1 -1
  106. package/types/components/nodes/time-picker.vue.d.ts +8 -1
  107. package/types/components/nodes/vertical-tabs.vue.d.ts +2 -2
  108. package/types/components/options.d.ts +1 -1
  109. package/types/components/options.d.ts.map +1 -1
  110. package/types/components/tree.vue.d.ts +2 -2
  111. package/types/components/vjsf.vue.d.ts +5 -5
  112. package/types/composables/use-comp-defaults.d.ts +8 -0
  113. package/types/composables/use-comp-defaults.d.ts.map +1 -0
  114. package/types/composables/use-dnd.d.ts +3 -3
  115. package/types/composables/use-dnd.d.ts.map +1 -1
  116. package/types/composables/use-field-props.d.ts +30 -0
  117. package/types/composables/use-field-props.d.ts.map +1 -0
  118. package/types/composables/use-field.d.ts +31 -0
  119. package/types/composables/use-field.d.ts.map +1 -0
  120. package/types/composables/use-get-items.d.ts +12 -0
  121. package/types/composables/use-get-items.d.ts.map +1 -0
  122. package/types/composables/use-node.d.ts +32 -0
  123. package/types/composables/use-node.d.ts.map +1 -0
  124. package/types/composables/use-select-field.d.ts +21 -0
  125. package/types/composables/use-select-field.d.ts.map +1 -0
  126. package/types/composables/use-select-node.d.ts +27 -0
  127. package/types/composables/use-select-node.d.ts.map +1 -0
  128. package/types/composables/use-select-props.d.ts +21 -0
  129. package/types/composables/use-select-props.d.ts.map +1 -0
  130. package/types/composables/use-select.d.ts +21 -0
  131. package/types/composables/use-select.d.ts.map +1 -0
  132. package/types/composables/use-vjsf.d.ts +2 -2
  133. package/types/composables/use-vjsf.d.ts.map +1 -1
  134. package/types/iconsets/default-aliases.d.ts +10 -0
  135. package/types/iconsets/default-aliases.d.ts.map +1 -0
  136. package/types/iconsets/mdi-svg.d.ts +3 -0
  137. package/types/iconsets/mdi-svg.d.ts.map +1 -0
  138. package/types/iconsets/mdi.d.ts +3 -0
  139. package/types/iconsets/mdi.d.ts.map +1 -0
  140. package/types/index.d.ts +5 -2
  141. package/types/index.d.ts.map +1 -1
  142. package/types/options.d.ts +9 -0
  143. package/types/options.d.ts.map +1 -0
  144. package/types/types.d.ts +65 -33
  145. package/types/types.d.ts.map +1 -1
  146. package/types/utils/arrays.d.ts +17 -4
  147. package/types/utils/arrays.d.ts.map +1 -1
  148. package/types/utils/index.d.ts +0 -3
  149. package/types/utils/props.d.ts +7 -0
  150. package/types/utils/props.d.ts.map +1 -1
  151. package/types/utils/slots.d.ts +8 -0
  152. package/types/utils/slots.d.ts.map +1 -1
  153. package/src/compile/index.js +0 -65
  154. package/src/compile/options.js +0 -19
  155. package/src/compile/v-jsf-compiled.vue.ejs +0 -61
  156. package/src/components/options.js +0 -27
  157. package/src/utils/global-register.js +0 -13
  158. package/src/utils/index.js +0 -5
  159. package/src/utils/props.js +0 -107
  160. package/src/utils/slots.js +0 -18
  161. package/types/utils/global-register.d.ts +0 -8
  162. package/types/utils/global-register.d.ts.map +0 -1
@@ -1,10 +1,13 @@
1
1
  <script setup>
2
2
  import { computed } from 'vue'
3
- import { useTheme } from 'vuetify'
4
- import { VCol } from 'vuetify/components'
3
+ import { useTheme, useDefaults } from 'vuetify'
4
+ import { VCol } from 'vuetify/components/VGrid'
5
+ import { VDefaultsProvider } from 'vuetify/components/VDefaultsProvider'
5
6
  import NodeSlot from './fragments/node-slot.vue'
6
7
  import HelpMessage from './fragments/help-message.vue'
7
8
 
9
+ useDefaults({}, 'VjsfNode')
10
+
8
11
  const props = defineProps({
9
12
  modelValue: {
10
13
  /** @type import('vue').PropType<import('../types.js').VjsfNode> */
@@ -27,11 +30,23 @@ const beforeAfterClasses = {
27
30
 
28
31
  const theme = useTheme()
29
32
 
33
+ const indent = computed(() => {
34
+ if (props.modelValue.parentFullKey === null) return 0
35
+ if (!props.modelValue.options.indent) return 0
36
+ if (props.modelValue.layout.comp !== 'section') return 0
37
+ if (!props.modelValue.layout.title) return 0
38
+ if (typeof props.modelValue.options.indent === 'number') return props.modelValue.options.indent
39
+ if (props.modelValue.options.density === 'compact') return 2
40
+ if (props.modelValue.options.density === 'comfortable') return 4
41
+ return 6
42
+ })
43
+
30
44
  const nodeClasses = computed(() => {
31
45
  let classes = `vjsf-node vjsf-node-${props.modelValue.layout.comp} vjsf-density-${props.modelValue.options.density}`
32
46
  if (props.modelValue.options.readOnly) classes += ' vjsf-readonly'
33
47
  if (props.modelValue.options.summary) classes += ' vjsf-summary'
34
48
  if (theme.current.value.dark) classes += ' vjsf-dark'
49
+ if (indent.value) classes += ' ml-' + indent.value
35
50
  return classes
36
51
  })
37
52
 
@@ -42,45 +57,47 @@ if (props.modelValue.layout.comp !== 'none' && !props.statefulLayout.options.nod
42
57
  </script>
43
58
 
44
59
  <template>
45
- <v-col
46
- :cols="modelValue.cols"
47
- :class="nodeClasses"
48
- >
49
- <node-slot
50
- v-if="modelValue.layout.slots?.before"
51
- key="before"
52
- :layout-slot="modelValue.layout.slots?.before"
53
- :node="modelValue"
54
- :stateful-layout="statefulLayout"
55
- :class="beforeAfterClasses[modelValue.options.density]"
56
- />
60
+ <v-defaults-provider :defaults="{global: { density: props.modelValue.options.density }}">
61
+ <v-col
62
+ v-if="modelValue.layout.comp !== 'none'"
63
+ :cols="modelValue.cols"
64
+ :class="nodeClasses"
65
+ >
66
+ <node-slot
67
+ v-if="modelValue.layout.slots?.before"
68
+ key="before"
69
+ :layout-slot="modelValue.layout.slots?.before"
70
+ :node="modelValue"
71
+ :stateful-layout="statefulLayout"
72
+ :class="beforeAfterClasses[modelValue.options.density]"
73
+ />
57
74
 
58
- <help-message
59
- v-if="modelValue.layout.help"
60
- :node="modelValue"
61
- :class="beforeAfterClasses[modelValue.options.density]"
62
- />
63
- <node-slot
64
- v-if="modelValue.layout.slots?.component"
65
- key="component"
66
- :layout-slot="modelValue.layout.slots?.component"
67
- :node="modelValue"
68
- :stateful-layout="statefulLayout"
69
- />
70
- <component
71
- :is="props.statefulLayout.options.nodeComponents[modelValue.layout.comp]"
72
- v-else-if="modelValue.layout.comp !== 'none' "
73
- :model-value="modelValue"
74
- :stateful-layout="statefulLayout"
75
- />
75
+ <help-message
76
+ v-if="modelValue.layout.help && !modelValue.options.summary"
77
+ :node="modelValue"
78
+ />
79
+ <node-slot
80
+ v-if="modelValue.layout.slots?.component"
81
+ key="component"
82
+ :layout-slot="modelValue.layout.slots?.component"
83
+ :node="modelValue"
84
+ :stateful-layout="statefulLayout"
85
+ />
86
+ <component
87
+ :is="props.statefulLayout.options.nodeComponents[modelValue.layout.comp]"
88
+ v-else
89
+ :model-value="modelValue"
90
+ :stateful-layout="statefulLayout"
91
+ />
76
92
 
77
- <node-slot
78
- v-if="modelValue.layout.slots?.after"
79
- key="after"
80
- :layout-slot="modelValue.layout.slots?.after"
81
- :node="modelValue"
82
- :stateful-layout="statefulLayout"
83
- :class="beforeAfterClasses[modelValue.options.density]"
84
- />
85
- </v-col>
93
+ <node-slot
94
+ v-if="modelValue.layout.slots?.after"
95
+ key="after"
96
+ :layout-slot="modelValue.layout.slots?.after"
97
+ :node="modelValue"
98
+ :stateful-layout="statefulLayout"
99
+ :class="beforeAfterClasses[modelValue.options.density]"
100
+ />
101
+ </v-col>
102
+ </v-defaults-provider>
86
103
  </template>
@@ -1,9 +1,8 @@
1
1
  <script>
2
- import { VAutocomplete } from 'vuetify/components'
3
- import { defineComponent, computed, ref, shallowRef, h } from 'vue'
4
- import { getInputProps, getCompSlots } from '../../utils/index.js'
5
- import SelectItem from '../fragments/select-item.vue'
6
- import SelectSelection from '../fragments/select-selection.vue'
2
+ import { VAutocomplete } from 'vuetify/components/VAutocomplete'
3
+ import { useDefaults } from 'vuetify'
4
+ import { defineComponent, computed, h, toRef } from 'vue'
5
+ import useSelectNode from '../../composables/use-select-node.js'
7
6
 
8
7
  export default defineComponent({
9
8
  props: {
@@ -13,75 +12,30 @@ export default defineComponent({
13
12
  required: true
14
13
  },
15
14
  statefulLayout: {
16
- /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
15
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
17
16
  type: Object,
18
17
  required: true
19
18
  }
20
19
  },
21
20
  setup (props) {
22
- /** @type import('vue').ShallowRef<import('@json-layout/vocabulary').SelectItems> */
23
- const items = shallowRef([])
24
- /** @type import('vue').Ref<boolean> */
25
- const loading = ref(false)
26
- /** @type import('vue').Ref<string> */
27
- const search = ref('')
21
+ useDefaults({}, 'VjsfAutocomplete')
22
+
23
+ const { getItems, selectProps, selectSlots, localData } = useSelectNode(toRef(props, 'modelValue'), props.statefulLayout)
28
24
 
29
25
  const fieldProps = computed(() => {
30
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout, ['multiple'])
31
- if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
26
+ const fieldProps = { ...selectProps.value }
32
27
  fieldProps.noFilter = true
33
28
  fieldProps['onUpdate:search'] = (/** @type string */searchValue) => {
34
- search.value = searchValue
35
- refresh()
29
+ getItems.search.value = searchValue
36
30
  }
37
- fieldProps['onUpdate:menu'] = refresh
38
- fieldProps.items = items.value
39
- fieldProps.loading = loading.value
31
+ fieldProps.items = getItems.items.value
32
+ fieldProps.loading = getItems.loading.value
33
+ fieldProps.modelValue = localData.value
40
34
  return fieldProps
41
35
  })
42
36
 
43
- /** @type import('@json-layout/core').StateTree | null */
44
- let lastStateTree = null
45
- /** @type Record<string, any> | null */
46
- let lastContext = null
47
- /** @type string */
48
- let lastSearch = ''
49
-
50
- const refresh = async () => {
51
- if (props.statefulLayout.stateTree === lastStateTree && props.statefulLayout.options.context === lastContext && search.value === lastSearch) return
52
- loading.value = true
53
- items.value = await props.statefulLayout.getItems(props.modelValue, search.value)
54
- lastStateTree = props.statefulLayout.stateTree
55
- lastContext = props.statefulLayout.options.context ?? null
56
- lastSearch = search.value
57
- loading.value = false
58
- }
59
-
60
- if (!props.modelValue.layout.items) {
61
- refresh()
62
- }
63
-
64
- const fieldSlots = computed(() => {
65
- const slots = getCompSlots(props.modelValue, props.statefulLayout)
66
- if (!slots.item) {
67
- slots.item = (/** @type {any} */ context) => h(SelectItem, {
68
- multiple: props.modelValue.layout.multiple,
69
- itemProps: context.props,
70
- item: context.item.raw
71
- })
72
- }
73
- if (!slots.selection) {
74
- slots.selection = (/** @type {any} */ context) => h(SelectSelection, {
75
- multiple: props.modelValue.layout.multiple,
76
- last: props.modelValue.layout.multiple && context.index === props.modelValue.data.length - 1,
77
- item: context.item.raw
78
- })
79
- }
80
- return slots
81
- })
82
-
83
37
  // @ts-ignore
84
- return () => h(VAutocomplete, fieldProps.value, fieldSlots.value)
38
+ return () => h(VAutocomplete, fieldProps.value, selectSlots.value)
85
39
  }
86
40
  })
87
41
 
@@ -0,0 +1,39 @@
1
+ <script setup>
2
+ import { VCard } from 'vuetify/components/VCard'
3
+ import { VContainer, VRow } from 'vuetify/components/VGrid'
4
+ import Node from '../node.vue'
5
+ import SectionHeader from '../fragments/section-header.vue'
6
+ import { useDefaults } from 'vuetify'
7
+
8
+ useDefaults({}, 'VjsfCard')
9
+
10
+ defineProps({
11
+ modelValue: {
12
+ /** @type import('vue').PropType<import('../../types.js').VjsfCardNode> */
13
+ type: Object,
14
+ required: true
15
+ },
16
+ statefulLayout: {
17
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
18
+ type: Object,
19
+ required: true
20
+ }
21
+ })
22
+
23
+ </script>
24
+
25
+ <template>
26
+ <v-card :title="modelValue.layout.title ?? ''">
27
+ <v-container fluid>
28
+ <section-header :node="modelValue" hide-title />
29
+ <v-row :dense="modelValue.options?.density === 'compact' || modelValue.options?.density === 'comfortable'">
30
+ <node
31
+ v-for="child of modelValue.children"
32
+ :key="child.fullKey"
33
+ :model-value="/** @type import('../../types.js').VjsfNode */(child)"
34
+ :stateful-layout="statefulLayout"
35
+ />
36
+ </v-row>
37
+ </v-container>
38
+ </v-card>
39
+ </template>
@@ -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({}, 'VjsfCheckboxGroup')
21
+
22
+ // @ts-ignore
23
+ return () => {
24
+ return h(SelectionGroup, {
25
+ modelValue: props.modelValue,
26
+ statefulLayout: props.statefulLayout,
27
+ type: 'checkbox'
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>
@@ -1,32 +1,37 @@
1
- <script setup>
2
- import { VCheckbox } from 'vuetify/components'
3
- import { computed } from 'vue'
4
- import { getInputProps } from '../../utils/index.js'
1
+ <script>
2
+ import { defineComponent, h, computed, toRef } from 'vue'
3
+ import { VCheckbox } from 'vuetify/components/VCheckbox'
4
+ import useNode from '../../composables/use-node.js'
5
+ import { useDefaults } from 'vuetify'
5
6
 
6
- const props = defineProps({
7
- modelValue: {
8
- /** @type import('vue').PropType<import('../../types.js').VjsfCheckboxNode> */
9
- type: Object,
10
- required: true
7
+ export default defineComponent({
8
+ props: {
9
+ modelValue: {
10
+ /** @type import('vue').PropType<import('../../types.js').VjsfCheckboxNode> */
11
+ type: Object,
12
+ required: true
13
+ },
14
+ statefulLayout: {
15
+ /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
16
+ type: Object,
17
+ required: true
18
+ }
11
19
  },
12
- statefulLayout: {
13
- /** @type import('vue').PropType<import('../../types.js').VjsfStatefulLayout> */
14
- type: Object,
15
- required: true
20
+ setup (props) {
21
+ useDefaults({}, 'VjsfCheckbox')
22
+
23
+ const { inputProps, localData, compSlots } = useNode(toRef(props, 'modelValue'), props.statefulLayout)
24
+
25
+ const fullProps = computed(() => {
26
+ const fullProps = { ...inputProps.value }
27
+ // it is not very common to show an error below checkboxes and switches and without hide-details=auto they take a lot of space
28
+ if (!('hideDetails' in inputProps)) fullProps.hideDetails = 'auto'
29
+ fullProps.modelValue = localData.value
30
+ return fullProps
31
+ })
32
+
33
+ return () => h(VCheckbox, fullProps.value, compSlots.value)
16
34
  }
17
35
  })
18
36
 
19
- const fieldProps = computed(() => {
20
- const inputProps = getInputProps(props.modelValue, props.statefulLayout)
21
- // it is not very common to show an error below checkboxes and switches and without hide-details=auto they take a lot of space
22
- if (!('hideDetails' in inputProps)) inputProps.hideDetails = 'auto'
23
- return inputProps
24
- })
25
37
  </script>
26
-
27
- <template>
28
- <v-checkbox
29
- v-bind="fieldProps"
30
- @update:model-value="value => statefulLayout.input(modelValue, value)"
31
- />
32
- </template>
@@ -1,8 +1,11 @@
1
1
  <script setup>
2
2
  import TextFieldMenu from '../fragments/text-field-menu.vue'
3
- import { VColorPicker } from 'vuetify/components'
4
- import { computed } from 'vue'
5
- import { getCompProps } from '../../utils/index.js'
3
+ import { VColorPicker } from 'vuetify/components/VColorPicker'
4
+ import { computed, toRef } from 'vue'
5
+ import useNode from '../../composables/use-node.js'
6
+ import { useDefaults } from 'vuetify'
7
+
8
+ useDefaults({}, 'VjsfColorPicker')
6
9
 
7
10
  const props = defineProps({
8
11
  modelValue: {
@@ -17,9 +20,11 @@ const props = defineProps({
17
20
  }
18
21
  })
19
22
 
23
+ const { compProps, localData } = useNode(toRef(props, 'modelValue'), props.statefulLayout)
24
+
20
25
  const colorPickerProps = computed(() => {
21
- const colorPickerProps = getCompProps(props.modelValue, true)
22
- colorPickerProps.modelValue = props.modelValue.data
26
+ const colorPickerProps = { ...compProps.value }
27
+ colorPickerProps.modelValue = localData.value
23
28
  return colorPickerProps
24
29
  })
25
30
  </script>
@@ -1,7 +1,9 @@
1
1
  <script>
2
- import { defineComponent, h, computed, shallowRef, ref } from 'vue'
3
- import { VCombobox } from 'vuetify/components'
4
- import { getInputProps, getCompSlots } from '../../utils/index.js'
2
+ import { defineComponent, h, computed, toRef } from 'vue'
3
+ import { VCombobox } from 'vuetify/components/VCombobox'
4
+ import useNode from '../../composables/use-node.js'
5
+ import useGetItems from '../../composables/use-get-items.js'
6
+ import { useDefaults } from 'vuetify'
5
7
 
6
8
  export default defineComponent({
7
9
  props: {
@@ -17,55 +19,30 @@ export default defineComponent({
17
19
  }
18
20
  },
19
21
  setup (props) {
20
- /** @type import('vue').Ref<import('@json-layout/vocabulary').SelectItems> */
21
- const items = shallowRef(props.modelValue.layout.items ?? [])
22
- /** @type import('vue').Ref<boolean> */
23
- const loading = ref(false)
22
+ useDefaults({}, 'VjsfCombobox')
24
23
 
25
- /** @type import('@json-layout/core').StateTree | null */
26
- let lastStateTree = null
27
- /** @type Record<string, any> | null */
28
- let lastContext = null
29
-
30
- const hasItems = computed(() => {
31
- return !!(props.modelValue.layout.items || props.modelValue.layout.getItems)
32
- })
33
-
34
- const refresh = async () => {
35
- if (props.modelValue.layout.items) return
36
- if (props.statefulLayout.stateTree === lastStateTree && props.statefulLayout.options.context === lastContext) return
37
- lastStateTree = props.statefulLayout.stateTree
38
- lastContext = props.statefulLayout.options.context ?? null
39
- if (hasItems.value) {
40
- loading.value = true
41
- items.value = await props.statefulLayout.getItems(props.modelValue)
42
- loading.value = false
43
- }
44
- }
45
-
46
- if (!props.modelValue.layout.items) {
47
- refresh()
48
- }
24
+ const nodeRef = toRef(props, 'modelValue')
25
+ const getItems = useGetItems(nodeRef, props.statefulLayout)
26
+ const { inputProps, compSlots, localData, layout, options } = useNode(nodeRef, props.statefulLayout)
49
27
 
50
28
  const fieldProps = computed(() => {
51
- const fieldProps = getInputProps(props.modelValue, props.statefulLayout)
52
- fieldProps.loading = loading.value
29
+ const fieldProps = { ...inputProps.value }
53
30
  fieldProps.returnObject = false
54
- if (hasItems.value) fieldProps.items = items.value
55
- if (props.modelValue.options.readOnly) fieldProps.menuProps = { modelValue: false }
56
- if (props.modelValue.layout.multiple) {
31
+ if (options.value.readOnly) fieldProps.menuProps = { modelValue: false }
32
+ if (getItems.hasItems.value) {
33
+ fieldProps.items = getItems.items.value
34
+ fieldProps.loading = getItems.loading.value
35
+ }
36
+ if (layout.value.multiple) {
57
37
  fieldProps.multiple = true
58
38
  fieldProps.chips = true
59
39
  fieldProps.closableChips = true
60
40
  }
61
- fieldProps['onUpdate:menu'] = () => refresh()
62
41
  return fieldProps
63
42
  })
64
43
 
65
- const fieldSlots = computed(() => getCompSlots(props.modelValue, props.statefulLayout))
66
-
67
44
  // @ts-ignore
68
- return () => h(VCombobox, fieldProps.value, fieldSlots.value)
45
+ return () => h(VCombobox, { ...fieldProps.value, modelValue: localData.value }, compSlots.value)
69
46
  }
70
47
  })
71
48
 
@@ -1,9 +1,13 @@
1
1
  <script setup>
2
2
  import TextFieldMenu from '../fragments/text-field-menu.vue'
3
+ import { VIcon } from 'vuetify/components/VIcon'
3
4
  import { VDatePicker } from 'vuetify/components/VDatePicker'
4
- import { useDate } from 'vuetify'
5
- import { computed } from 'vue'
6
- import { getCompProps, getDateTimeParts } from '../../utils/index.js'
5
+ import { useDate, useDefaults } from 'vuetify'
6
+ import { computed, ref, toRef } from 'vue'
7
+ import { getDateTimeParts, getDateTimeWithOffset } from '../../utils/dates.js'
8
+ import useNode from '../../composables/use-node.js'
9
+
10
+ useDefaults({}, 'VjsfDatePicker')
7
11
 
8
12
  const props = defineProps({
9
13
  modelValue: {
@@ -20,25 +24,42 @@ const props = defineProps({
20
24
 
21
25
  const vDate = useDate()
22
26
 
27
+ const menuOpened = ref(false)
28
+
29
+ const { compProps, localData } = useNode(toRef(props, 'modelValue'), props.statefulLayout)
30
+
23
31
  const datePickerProps = computed(() => {
24
- const datePickerProps = getCompProps(props.modelValue, true)
32
+ const datePickerProps = { ...compProps.value }
25
33
  datePickerProps.hideActions = true
26
- if (props.modelValue.data) datePickerProps.modelValue = new Date(props.modelValue.data)
34
+ if (localData.value) datePickerProps.modelValue = new Date(/** @type {string} */(localData.value))
35
+ datePickerProps['onUpdate:modelValue'] = (/** @type {Date} */value) => {
36
+ if (!value) return
37
+ if (props.modelValue.layout.format === 'date-time') {
38
+ props.statefulLayout.input(props.modelValue, getDateTimeWithOffset(value))
39
+ } else {
40
+ props.statefulLayout.input(props.modelValue, getDateTimeParts(/** @type Date */(/** @type unknown */(value)))[0])
41
+ }
42
+ menuOpened.value = false
43
+ }
27
44
  return datePickerProps
28
45
  })
46
+
47
+ const formattedValue = computed(() => {
48
+ return localData.value ? vDate.format(/** @type {string} */(localData.value), 'fullDateWithWeekday') : null
49
+ })
50
+
29
51
  </script>
30
52
 
31
53
  <template>
32
54
  <text-field-menu
33
- :model-value="modelValue"
55
+ v-model:menu-opened="menuOpened"
56
+ :model-value="props.modelValue"
34
57
  :stateful-layout="statefulLayout"
35
- :formatted-value="modelValue.data && vDate.format(modelValue.data, 'fullDateWithWeekday')"
58
+ :formatted-value="formattedValue"
36
59
  >
37
- <template #default="{close}">
38
- <v-date-picker
39
- v-bind="datePickerProps"
40
- @update:model-value="value => {statefulLayout.input(modelValue, value && getDateTimeParts(/** @type Date */(/** @type unknown */(value)))[0]); close()}"
41
- />
60
+ <template #prepend-inner>
61
+ <v-icon :icon="statefulLayout.options.icons.calendar" />
42
62
  </template>
63
+ <v-date-picker v-bind="datePickerProps" />
43
64
  </text-field-menu>
44
65
  </template>
@@ -1,8 +1,20 @@
1
1
  <script setup>
2
+ import TextFieldMenu from '../fragments/text-field-menu.vue'
3
+ import { VDatePicker } from 'vuetify/components/VDatePicker'
4
+ import { VTimePicker } from 'vuetify/labs/VTimePicker'
5
+ import { VTabs, VTab, VTabsWindow, VTabsWindowItem } from 'vuetify/components/VTabs'
6
+ import { VIcon } from 'vuetify/components/VIcon'
7
+ import { VSheet } from 'vuetify/components/VSheet'
8
+ import { useDate, useDefaults } from 'vuetify'
9
+ import { computed, ref, watch, toRef } from 'vue'
10
+ import { getDateTimeParts, getDateTimeWithOffset, getShortTime } from '../../utils/dates.js'
11
+ import useNode from '../../composables/use-node.js'
2
12
 
3
- defineProps({
13
+ useDefaults({}, 'VjsfDatePicker')
14
+
15
+ const props = defineProps({
4
16
  modelValue: {
5
- /** @type import('vue').PropType<import('../../types.js').VjsfDateTimePickerNode> */
17
+ /** @type import('vue').PropType<import('../../types.js').VjsfDatePickerNode> */
6
18
  type: Object,
7
19
  required: true
8
20
  },
@@ -13,8 +25,79 @@ defineProps({
13
25
  }
14
26
  })
15
27
 
28
+ const vDate = useDate()
29
+
30
+ const tab = ref('date')
31
+ const menuOpened = ref(false)
32
+ watch(menuOpened, () => { tab.value = 'date' })
33
+
34
+ const { compProps, localData } = useNode(toRef(props, 'modelValue'), props.statefulLayout)
35
+
36
+ const datePickerProps = computed(() => {
37
+ const datePickerProps = { ...compProps.value }
38
+ datePickerProps.hideActions = true
39
+ if (localData.value) datePickerProps.modelValue = new Date(localData.value)
40
+ datePickerProps['onUpdate:modelValue'] = (/** @type {Date} */value) => {
41
+ if (!value) return
42
+
43
+ if (localData.value) {
44
+ // replace date part of current value
45
+ const datePart = value && getDateTimeParts(/** @type Date */(/** @type unknown */(value)))[0]
46
+ props.statefulLayout.input(props.modelValue, datePart + localData.value.slice(10))
47
+ } else {
48
+ props.statefulLayout.input(props.modelValue, getDateTimeWithOffset(value))
49
+ }
50
+ tab.value = 'time'
51
+ }
52
+ return datePickerProps
53
+ })
54
+
55
+ const timePickerProps = computed(() => {
56
+ const timePickerProps = { ...compProps.value }
57
+ timePickerProps['ampm-in-title'] = true
58
+ if (localData.value) timePickerProps.modelValue = getShortTime(localData.value.slice(11))
59
+ timePickerProps['onUpdate:modelValue'] = (/** @type {string} */value) => {
60
+ if (!localData.value) return
61
+ console.log('set time', value, localData.value.slice(0, 10), localData.value.slice(15))
62
+ props.statefulLayout.input(props.modelValue, localData.value.slice(0, 11) + value + localData.value.slice(16))
63
+ }
64
+ return timePickerProps
65
+ })
16
66
  </script>
17
67
 
18
68
  <template>
19
- TODO date-time
69
+ <text-field-menu
70
+ v-model:menu-opened="menuOpened"
71
+ :model-value="modelValue"
72
+ :stateful-layout="statefulLayout"
73
+ :formatted-value="modelValue.data && vDate.format(modelValue.data, 'fullDateTime')"
74
+ >
75
+ <template #prepend-inner>
76
+ <v-icon :icon="statefulLayout.options.icons.calendar" />
77
+ </template>
78
+ <v-sheet style="width: 328px">
79
+ <v-tabs
80
+ v-model="tab"
81
+ align-tabs="center"
82
+ >
83
+ <v-tab value="date">
84
+ <v-icon :icon="statefulLayout.options.icons.calendar" />
85
+ </v-tab>
86
+ <v-tab
87
+ value="time"
88
+ :disabled="!modelValue.data"
89
+ >
90
+ <v-icon :icon="statefulLayout.options.icons.clock" />
91
+ </v-tab>
92
+ </v-tabs>
93
+ <v-tabs-window v-model="tab">
94
+ <v-tabs-window-item value="date">
95
+ <v-date-picker v-bind="datePickerProps" />
96
+ </v-tabs-window-item>
97
+ <v-tabs-window-item value="time">
98
+ <v-time-picker v-bind="timePickerProps" />
99
+ </v-tabs-window-item>
100
+ </v-tabs-window>
101
+ </v-sheet>
102
+ </text-field-menu>
20
103
  </template>