@koumoul/vjsf 3.0.0-beta.2 → 3.0.0-beta.21

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 (52) hide show
  1. package/package.json +2 -2
  2. package/src/compat/v2.js +56 -14
  3. package/src/compile/index.js +18 -4
  4. package/src/compile/options.js +3 -7
  5. package/src/compile/v-jsf-compiled.vue.ejs +1 -1
  6. package/src/components/fragments/help-message.vue +11 -1
  7. package/src/components/fragments/section-header.vue +1 -2
  8. package/src/components/fragments/text-field-menu.vue +1 -1
  9. package/src/components/node.vue +4 -3
  10. package/src/components/nodes/checkbox.vue +6 -1
  11. package/src/components/nodes/color-picker.vue +2 -1
  12. package/src/components/nodes/date-picker.vue +1 -1
  13. package/src/components/nodes/expansion-panels.vue +22 -12
  14. package/src/components/nodes/list.vue +136 -87
  15. package/src/components/nodes/one-of-select.vue +26 -18
  16. package/src/components/nodes/switch.vue +6 -3
  17. package/src/components/options.js +15 -5
  18. package/src/composables/use-dnd.js +1 -0
  19. package/src/composables/use-vjsf.js +32 -9
  20. package/src/types.ts +15 -6
  21. package/src/utils/build.js +1 -1
  22. package/src/utils/index.js +0 -1
  23. package/src/utils/props.js +9 -25
  24. package/types/compat/v2.d.ts.map +1 -1
  25. package/types/compile/index.d.ts +2 -2
  26. package/types/compile/index.d.ts.map +1 -1
  27. package/types/compile/options.d.ts.map +1 -1
  28. package/types/components/options.d.ts +2 -2
  29. package/types/components/options.d.ts.map +1 -1
  30. package/types/components/vjsf.vue.d.ts +2 -2
  31. package/types/composables/use-dnd.d.ts.map +1 -1
  32. package/types/composables/use-vjsf.d.ts +1 -1
  33. package/types/composables/use-vjsf.d.ts.map +1 -1
  34. package/types/types.d.ts +11 -23
  35. package/types/types.d.ts.map +1 -1
  36. package/types/utils/build.d.ts +1 -1
  37. package/types/utils/index.d.ts +0 -1
  38. package/types/utils/props.d.ts +3 -4
  39. package/types/utils/props.d.ts.map +1 -1
  40. package/src/utils/global-register.js +0 -13
  41. package/types/components/global-register.d.ts +0 -8
  42. package/types/components/global-register.d.ts.map +0 -1
  43. package/types/components/nodes/markdown.vue.d.ts +0 -27
  44. package/types/components/nodes/markdown.vue.d.ts.map +0 -1
  45. package/types/components/nodes/text-field copy.vue.d.ts +0 -10
  46. package/types/components/nodes/text-field copy.vue.d.ts.map +0 -1
  47. package/types/components/types.d.ts +0 -91
  48. package/types/components/types.d.ts.map +0 -1
  49. package/types/components/v-jsf.vue.d.ts +0 -13
  50. package/types/components/v-jsf.vue.d.ts.map +0 -1
  51. package/types/utils/clone.d.ts +0 -3
  52. package/types/utils/clone.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koumoul/vjsf",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0-beta.21",
4
4
  "description": "Generate forms for the vuetify UI library (vuejs) based on annotated JSON schemas.",
5
5
  "scripts": {
6
6
  "test": "vitest",
@@ -75,7 +75,7 @@
75
75
  "vuetify": "^3.4.9"
76
76
  },
77
77
  "dependencies": {
78
- "@json-layout/core": "0.9.2",
78
+ "@json-layout/core": "0.16.4",
79
79
  "@vueuse/core": "^10.5.0",
80
80
  "debug": "^4.3.4",
81
81
  "ejs": "^3.1.9"
package/src/compat/v2.js CHANGED
@@ -6,11 +6,36 @@ import { isPartialGetItemsObj } from '@json-layout/vocabulary'
6
6
  // @ts-ignore
7
7
  const Ajv = /** @type {typeof ajvModule.default} */ (ajvModule)
8
8
 
9
+ /**
10
+ *
11
+ * @param {string} expression
12
+ * @returns {{type: 'js-eval' | undefined, expr: string, pure: boolean}}
13
+ */
14
+ const fiexEvalExpression = (expression) => {
15
+ let expr = expression
16
+ let pure = true
17
+ if (expr.includes('rootData')) {
18
+ pure = false
19
+ }
20
+ if (expr.includes('parent.value')) {
21
+ pure = false
22
+ expr = expr.replace(/parent\.value/g, 'parentData')
23
+ }
24
+
25
+ return { type: 'js-eval', expr, pure }
26
+ }
27
+
9
28
  const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
10
29
  if (!schema.layout) {
11
30
  /** @type import('@json-layout/vocabulary').PartialCompObject */
12
31
  const layout = {}
13
32
 
33
+ if (schema.separator || schema['x-separator']) {
34
+ layout.separator = schema.separator || schema['x-separator']
35
+ delete schema.separator
36
+ delete schema['x-separator']
37
+ }
38
+
14
39
  if (schema['x-display'] === 'icon' && (schema.enum || schema.items?.enum)) {
15
40
  layout.getItems = { itemIcon: schema['x-itemIcon'] || 'data.value' }
16
41
  delete schema['x-display']
@@ -33,10 +58,15 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
33
58
 
34
59
  if (schema['x-fromData']) {
35
60
  layout.comp = 'select'
36
- layout.getItems = { expr: schema['x-fromData'] }
61
+ layout.getItems = fiexEvalExpression(schema['x-fromData'])
37
62
  delete schema['x-fromData']
38
63
  }
39
64
 
65
+ if (schema['x-if']) {
66
+ layout.if = fiexEvalExpression(schema['x-if'])
67
+ delete schema['x-if']
68
+ }
69
+
40
70
  if (schema['x-fromUrl']) {
41
71
  /** @type string */
42
72
  let url = schema['x-fromUrl']
@@ -66,21 +96,33 @@ const processFragment = (/** @type {import("ajv").SchemaObject} */schema) => {
66
96
  }
67
97
  }
68
98
 
69
- if (schema.type === 'object') {
70
- if (schema.properties) {
71
- for (const propertyKey of Object.keys(schema.properties)) {
72
- processFragment(schema.properties[propertyKey])
73
- }
74
- }
75
- if (schema.allOf) {
76
- for (const item of schema.allOf) processFragment(item)
99
+ if (schema.properties) {
100
+ for (const propertyKey of Object.keys(schema.properties)) {
101
+ processFragment(schema.properties[propertyKey])
77
102
  }
78
- if (schema.oneOf) {
79
- for (const item of schema.oneOf) processFragment(item)
80
- }
81
- if (schema.anyOf) {
82
- for (const item of schema.anyOf) processFragment(item)
103
+ }
104
+
105
+ if (schema.allOf) {
106
+ for (const item of schema.allOf) processFragment(item)
107
+ }
108
+
109
+ if (schema.oneOf) {
110
+ if (!schema.oneOfLayout) {
111
+ const constPropertyKey = Object.keys(schema.oneOf[0]?.properties || {})
112
+ .find(key => !!schema.oneOf[0]?.properties[key].const)
113
+ if (constPropertyKey) {
114
+ const constProperty = schema.oneOf[0]?.properties[constPropertyKey]
115
+ if (constProperty?.title) schema.oneOfLayout = { label: constProperty.title }
116
+ if (schema.required && Array.isArray(schema.required)) {
117
+ schema.required = schema.required.filter(key => key !== constPropertyKey)
118
+ }
119
+ }
83
120
  }
121
+ for (const item of schema.oneOf) processFragment(item)
122
+ }
123
+
124
+ if (schema.anyOf) {
125
+ for (const item of schema.anyOf) processFragment(item)
84
126
  }
85
127
 
86
128
  if (schema.type === 'array' && schema.items) {
@@ -37,10 +37,17 @@ function listComps (comps, layout) {
37
37
  * @param {object} schema
38
38
  * @param {import('../types.js').PartialVjsfCompileOptions} [options]
39
39
  * @param {string} [baseImport]
40
- * @returns {string}
40
+ * @returns {Promise<string>}
41
41
  */
42
- export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
42
+ export async function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
43
43
  const fullOptions = getFullOptions(options)
44
+ /** @type {Record<string, string>} */
45
+ const pluginsImportsByName = {}
46
+ for (const pluginImport of fullOptions.pluginsImports) {
47
+ const componentInfo = /** @type {import('@json-layout/vocabulary').ComponentInfo} */((await import(pluginImport + '/info.js')).default)
48
+ fullOptions.components[componentInfo.name] = componentInfo
49
+ pluginsImportsByName[componentInfo.name] = pluginImport
50
+ }
44
51
  const compiledLayout = compileLayout(schema, { ...fullOptions, code: true })
45
52
  const compiledLayoutCode = serializeCompiledLayout(compiledLayout)
46
53
  /** @type Set<string> */
@@ -50,9 +57,16 @@ export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
50
57
  }
51
58
  comps.delete('none')
52
59
 
60
+ /** @type {Record<string, any>} */
61
+ const pluginsComponents = {}
62
+
53
63
  const compImports = [...comps].map(comp => {
54
64
  const compName = comp.replace(/-/g, '') + 'Node'
55
- const compImport = fullOptions.nodeComponentImports[comp] ?? `${baseImport}/components/nodes/${comp}.vue`
65
+ let compImport = `${baseImport}/components/nodes/${comp}.vue`
66
+ if (pluginsImportsByName[comp]) {
67
+ compImport = `${pluginsImportsByName[comp]}/node.vue`
68
+ pluginsComponents[comp] = fullOptions.components[comp]
69
+ }
56
70
  return {
57
71
  comp,
58
72
  compName,
@@ -60,6 +74,6 @@ export function compile (schema, options = {}, baseImport = '@koumoul/vjsf') {
60
74
  }
61
75
  })
62
76
 
63
- const code = ejs.render(template, { compiledLayoutCode, compImports, baseImport })
77
+ const code = ejs.render(template, { compiledLayoutCode, compImports, baseImport, pluginsComponents })
64
78
  return code
65
79
  }
@@ -1,8 +1,7 @@
1
1
  /** @type import("../types.js").PartialVjsfCompileOptions */
2
2
  export const defaultOptions = {
3
- nodeComponentImports: {
4
- markdown: '@koumoul/vjsf-markdown/components/nodes/markdown.vue'
5
- }
3
+ pluginsImports: ['@koumoul/vjsf-markdown'],
4
+ components: {}
6
5
  }
7
6
 
8
7
  /**
@@ -11,9 +10,6 @@ export const defaultOptions = {
11
10
  * @returns
12
11
  */
13
12
  export const getFullOptions = (options) => {
14
- const fullOptions = {
15
- ...defaultOptions,
16
- nodeComponentImports: { ...defaultOptions.nodeComponentImports, ...options.nodeComponentImports }
17
- }
13
+ const fullOptions = { ...defaultOptions }
18
14
  return /** @type import('../types.js').VjsfCompileOptions */ (fullOptions)
19
15
  }
@@ -39,7 +39,7 @@ const emit = defineEmits(emits)
39
39
  const { el, statefulLayout, stateTree } = useVjsf(
40
40
  null,
41
41
  computed(() => props.modelValue),
42
- computed(() => props.options),
42
+ computed(() => ({...props.options, components: <%- JSON.stringify(pluginsComponents) %>})),
43
43
  nodeComponents,
44
44
  emit,
45
45
  null,
@@ -4,15 +4,17 @@
4
4
  <v-alert
5
5
  v-show="show"
6
6
  color="info"
7
+ :density="node.options.density"
7
8
  >
8
9
  <div v-html="node.layout.help" />
9
10
  </v-alert>
10
11
  </v-slide-x-reverse-transition>
11
12
  <v-btn
12
13
  color="info"
13
- class="vjsf-help-message-toggle"
14
+ :class="`vjsf-help-message-toggle vjsf-help-message-toggle-${node.options.density}`"
14
15
  :icon="show ? 'mdi-close-circle' : 'mdi-information'"
15
16
  density="compact"
17
+ :size="node.options.density !== 'default' ? 'small' : 'default'"
16
18
  :title="show ? '' : node.messages.showHelp"
17
19
  @click="show = !show"
18
20
  />
@@ -45,5 +47,13 @@ const show = ref(false)
45
47
  right: -4px;
46
48
  z-index: 1;
47
49
  }
50
+ .vjsf-help-message-toggle-comfortable {
51
+ top: -4px;
52
+ right: -4px;
53
+ }
54
+ .vjsf-help-message-toggle-compact {
55
+ top: -4px;
56
+ right: -4px;
57
+ }
48
58
  </style>
49
59
  ../../../types.js
@@ -45,7 +45,7 @@ const titleClass = computed(() => {
45
45
  </p>
46
46
  <v-alert
47
47
  v-if="node.error && node.validated"
48
- v-bind="node.options.errorAlertProps"
48
+ type="error"
49
49
  :class="`mt-${titleDepthBase - node.options.titleDepth}`"
50
50
  :density="node.options.density"
51
51
  >
@@ -53,4 +53,3 @@ const titleClass = computed(() => {
53
53
  </v-alert>
54
54
  </div>
55
55
  </template>
56
- ../../../types.js
@@ -28,7 +28,7 @@ const fieldProps = computed(() => {
28
28
  })
29
29
 
30
30
  const menuProps = computed(() => {
31
- const menuProps = getCompProps(props.modelValue, 'menu', false)
31
+ const menuProps = getCompProps(props.modelValue)
32
32
  menuProps.closeOnContentClick = false
33
33
  menuProps.disabled = true
34
34
  return menuProps
@@ -35,7 +35,7 @@ const nodeClasses = computed(() => {
35
35
  return classes
36
36
  })
37
37
 
38
- if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp]) {
38
+ if (props.modelValue.layout.comp !== 'none' && !props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp]) {
39
39
  console.error(`vjsf: missing component to render vjsf node "${props.modelValue.layout.comp}", maybe you forgot to register a component from a plugin ?`)
40
40
  }
41
41
 
@@ -43,6 +43,7 @@ if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp])
43
43
 
44
44
  <template>
45
45
  <v-col
46
+ v-if="modelValue.layout.comp !== 'none'"
46
47
  :cols="modelValue.cols"
47
48
  :class="nodeClasses"
48
49
  >
@@ -56,7 +57,7 @@ if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp])
56
57
  />
57
58
 
58
59
  <help-message
59
- v-if="modelValue.layout.help"
60
+ v-if="modelValue.layout.help && !modelValue.options.summary"
60
61
  :node="modelValue"
61
62
  :class="beforeAfterClasses[modelValue.options.density]"
62
63
  />
@@ -69,7 +70,7 @@ if (!props.statefulLayout.options.nodeComponents[props.modelValue.layout.comp])
69
70
  />
70
71
  <component
71
72
  :is="props.statefulLayout.options.nodeComponents[modelValue.layout.comp]"
72
- v-else-if="modelValue.layout.comp !== 'none' "
73
+ v-else
73
74
  :model-value="modelValue"
74
75
  :stateful-layout="statefulLayout"
75
76
  />
@@ -16,7 +16,12 @@ const props = defineProps({
16
16
  }
17
17
  })
18
18
 
19
- const fieldProps = computed(() => getInputProps(props.modelValue, props.statefulLayout))
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
+ })
20
25
  </script>
21
26
 
22
27
  <template>
@@ -18,7 +18,8 @@ const props = defineProps({
18
18
  })
19
19
 
20
20
  const colorPickerProps = computed(() => {
21
- const colorPickerProps = getCompProps(props.modelValue, 'colorPicker', true)
21
+ const colorPickerProps = getCompProps(props.modelValue, true)
22
+ colorPickerProps.modelValue = props.modelValue.data
22
23
  return colorPickerProps
23
24
  })
24
25
  </script>
@@ -21,7 +21,7 @@ const props = defineProps({
21
21
  const vDate = useDate()
22
22
 
23
23
  const datePickerProps = computed(() => {
24
- const datePickerProps = getCompProps(props.modelValue, 'datePicker', true)
24
+ const datePickerProps = getCompProps(props.modelValue, true)
25
25
  datePickerProps.hideActions = true
26
26
  if (props.modelValue.data) datePickerProps.modelValue = new Date(props.modelValue.data)
27
27
  return datePickerProps
@@ -1,8 +1,10 @@
1
1
  <script setup>
2
- import { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VContainer, VRow, VIcon } from 'vuetify/components'
2
+ import { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VExpansionPanelText, VContainer, VRow, VIcon } from 'vuetify/components'
3
+ import { computed } from 'vue'
3
4
  import { isSection } from '@json-layout/core'
4
5
  import Node from '../node.vue'
5
6
  import SectionHeader from '../fragments/section-header.vue'
7
+ import { getCompProps } from '../../utils/index.js'
6
8
 
7
9
  defineProps({
8
10
  modelValue: {
@@ -21,7 +23,7 @@ defineProps({
21
23
 
22
24
  <template>
23
25
  <section-header :node="modelValue" />
24
- <v-expansion-panels>
26
+ <v-expansion-panels v-bind="getCompProps(modelValue, true)">
25
27
  <v-expansion-panel
26
28
  v-for="(child, i) of modelValue.children"
27
29
  :key="child.key"
@@ -37,16 +39,24 @@ defineProps({
37
39
  </v-icon>
38
40
  {{ child.layout.title ?? child.layout.label }}
39
41
  </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>
42
+ <v-expansion-panel-text>
43
+ <v-container fluid>
44
+ <v-row>
45
+ <node
46
+ v-for="grandChild of isSection(child) ? child.children : [child]"
47
+ :key="grandChild.fullKey"
48
+ :model-value="/** @type import('../../types.js').VjsfNode */(grandChild)"
49
+ :stateful-layout="statefulLayout"
50
+ />
51
+ </v-row>
52
+ </v-container>
53
+ </v-expansion-panel-text>
50
54
  </v-expansion-panel>
51
55
  </v-expansion-panels>
52
56
  </template>
57
+
58
+ <style>
59
+ .vjsf-node-expansion-panels .v-expansion-panel-text__wrapper {
60
+ padding: 0;
61
+ }
62
+ </style>
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { watch, computed, ref } from 'vue'
3
+ import { useTheme } from 'vuetify'
3
4
  import { VList, VListItem, VListItemAction, VBtn, VMenu, VIcon, VSheet, VSpacer, VDivider, VRow, VListSubheader } from 'vuetify/components'
4
5
  import { isSection, clone } from '@json-layout/core'
5
6
  import Node from '../node.vue'
@@ -19,6 +20,8 @@ const props = defineProps({
19
20
  }
20
21
  })
21
22
 
23
+ const theme = useTheme()
24
+
22
25
  /* use composable for drag and drop */
23
26
  const { activeDnd, sortableArray, draggable, hovered, dragging, itemBind, handleBind } = useDnd(props.modelValue.children, () => {
24
27
  props.statefulLayout.input(props.modelValue, sortableArray.value.map((child) => child.data))
@@ -48,10 +51,45 @@ const buttonDensity = computed(() => {
48
51
  return props.modelValue.options.density
49
52
  })
50
53
 
54
+ const pushEmptyItem = () => {
55
+ const newData = (props.modelValue.data ?? []).concat([undefined])
56
+ props.statefulLayout.input(props.modelValue, newData)
57
+ props.statefulLayout.activateItem(props.modelValue, newData.length - 1)
58
+ }
59
+
60
+ /**
61
+ * @param {number} childIndex
62
+ */
63
+ const deleteItem = (childIndex) => {
64
+ const newData = [...props.modelValue.data.slice(0, childIndex), ...props.modelValue.data.slice(childIndex + 1)]
65
+ props.statefulLayout.input(props.modelValue, newData)
66
+ menuOpened.value = -1
67
+ }
68
+
69
+ /**
70
+ * @param {import('@json-layout/core').StateNode} child
71
+ * @param {number} childIndex
72
+ */
73
+ const duplicateItem = (child, childIndex) => {
74
+ const newData = [...props.modelValue.data.slice(0, childIndex), clone(child.data), ...props.modelValue.data.slice(childIndex)]
75
+ props.statefulLayout.input(props.modelValue, newData)
76
+ props.statefulLayout.activateItem(props.modelValue, childIndex + 1)
77
+ menuOpened.value = -1
78
+ }
79
+
80
+ const itemBorderColor = computed(() => (/** @type {import('@json-layout/core').StateNode} */child, /** @type {number} */childIndex) => {
81
+ if (editedItem.value === childIndex) return theme.current.value.colors.primary
82
+ if (child.validated && (child.error || child.childError)) return theme.current.value.colors.error
83
+ return 'transparent'
84
+ })
85
+
51
86
  </script>
52
87
 
53
88
  <template>
54
- <v-sheet :elevation="1">
89
+ <v-sheet
90
+ :elevation="2"
91
+ rounded
92
+ >
55
93
  <v-list :density="modelValue.options.density">
56
94
  <v-list-subheader v-if="modelValue.layout.title">
57
95
  {{ modelValue.layout.title }}
@@ -64,7 +102,8 @@ const buttonDensity = computed(() => {
64
102
  v-bind="itemBind(childIndex)"
65
103
  :density="modelValue.options.density"
66
104
  :draggable="draggable === childIndex"
67
- :variant="editedItem === childIndex ? 'outlined' : 'flat'"
105
+ variant="flat"
106
+ :style="`border: 1px solid ${itemBorderColor(child, childIndex)}`"
68
107
  class="pa-1 vjsf-list-item"
69
108
  >
70
109
  <v-row class="ma-0">
@@ -76,100 +115,110 @@ const buttonDensity = computed(() => {
76
115
  />
77
116
  </v-row>
78
117
  <template
79
- v-if="activeItem === childIndex"
118
+ v-if="!modelValue.options.readOnly && modelValue.layout.listActions.length"
80
119
  #append
81
120
  >
82
121
  <div>
83
- <v-list-item-action
84
- v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
85
- >
122
+ <v-list-item-action v-if="activeItem !== childIndex">
86
123
  <v-btn
87
- v-if="editedItem !== childIndex"
88
- :title="modelValue.messages.edit"
89
- icon="mdi-pencil"
124
+ style="visibility:hidden"
90
125
  variant="text"
91
- color="primary"
92
126
  :density="buttonDensity"
93
- @click="statefulLayout.activateItem(modelValue, childIndex)"
94
- />
95
- <v-btn
96
- v-else
97
- :title="modelValue.messages.edit"
98
127
  icon="mdi-pencil"
99
- variant="flat"
100
- color="primary"
101
- :density="buttonDensity"
102
- @click="statefulLayout.deactivateItem(modelValue)"
103
- />
104
- </v-list-item-action>
105
- <v-list-item-action
106
- v-if="editedItem === undefined && modelValue.layout.listActions.includes('sort') && activeDnd"
107
- >
108
- <v-btn
109
- :title="modelValue.messages.sort"
110
- icon="mdi-arrow-up-down"
111
- variant="plain"
112
- :density="buttonDensity"
113
- v-bind="handleBind(childIndex)"
114
128
  />
115
129
  </v-list-item-action>
116
- <v-list-item-action
117
- v-if="editedItem === undefined && (modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate') || modelValue.layout.listActions.includes('sort'))"
118
- >
119
- <v-menu
120
- location="bottom end"
121
- @update:model-value="value => {menuOpened = value ? childIndex : -1}"
130
+ <template v-else>
131
+ <v-list-item-action
132
+ v-if="modelValue.layout.listActions.includes('edit') && modelValue.layout.listEditMode === 'inline-single'"
122
133
  >
123
- <template #activator="{props: activatorProps}">
124
- <v-btn
125
- v-bind="activatorProps"
126
- icon="mdi-dots-vertical"
127
- variant="plain"
128
- slim
129
- :density="buttonDensity"
130
- />
131
- </template>
132
- <v-list :density="modelValue.options.density">
133
- <v-list-item
134
- v-if="modelValue.layout.listActions.includes('delete')"
135
- base-color="warning"
136
- @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), ...modelValue.data.slice(childIndex + 1)])"
137
- >
138
- <template #prepend>
139
- <v-icon icon="mdi-delete" />
140
- </template>
141
- {{ modelValue.messages.delete }}
142
- </v-list-item>
143
- <v-list-item
144
- v-if="modelValue.layout.listActions.includes('duplicate')"
145
- @click="statefulLayout.input(modelValue, [...modelValue.data.slice(0, childIndex), clone(child.data), ...modelValue.data.slice(childIndex)])"
146
- >
147
- <template #prepend>
148
- <v-icon icon="mdi-content-duplicate" />
149
- </template>
150
- {{ modelValue.messages.duplicate }}
151
- </v-list-item>
152
- <v-list-item
153
- v-if="modelValue.layout.listActions.includes('sort')"
154
- @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex - 1))"
155
- >
156
- <template #prepend>
157
- <v-icon icon="mdi-arrow-up" />
158
- </template>
159
- {{ modelValue.messages.up }}
160
- </v-list-item>
161
- <v-list-item
162
- v-if="modelValue.layout.listActions.includes('sort')"
163
- @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex + 1))"
164
- >
165
- <template #prepend>
166
- <v-icon icon="mdi-arrow-down" />
167
- </template>
168
- {{ modelValue.messages.down }}
169
- </v-list-item>
170
- </v-list>
171
- </v-menu>
172
- </v-list-item-action>
134
+ <v-btn
135
+ v-if="editedItem !== childIndex"
136
+ :title="modelValue.messages.edit"
137
+ icon="mdi-pencil"
138
+ variant="text"
139
+ color="primary"
140
+ :density="buttonDensity"
141
+ @click="statefulLayout.activateItem(modelValue, childIndex)"
142
+ />
143
+ <v-btn
144
+ v-else
145
+ :title="modelValue.messages.edit"
146
+ icon="mdi-close"
147
+ variant="flat"
148
+ color="primary"
149
+ :density="buttonDensity"
150
+ @click="statefulLayout.deactivateItem(modelValue)"
151
+ />
152
+ </v-list-item-action>
153
+ <v-list-item-action
154
+ v-if="editedItem === undefined && modelValue.layout.listActions.includes('sort') && activeDnd"
155
+ >
156
+ <v-btn
157
+ :title="modelValue.messages.sort"
158
+ icon="mdi-arrow-up-down"
159
+ variant="plain"
160
+ :density="buttonDensity"
161
+ v-bind="handleBind(childIndex)"
162
+ />
163
+ </v-list-item-action>
164
+ <v-list-item-action
165
+ v-if="editedItem === undefined && (modelValue.layout.listActions.includes('delete') || modelValue.layout.listActions.includes('duplicate') || modelValue.layout.listActions.includes('sort'))"
166
+ >
167
+ <v-menu
168
+ location="bottom end"
169
+ @update:model-value="value => {menuOpened = value ? childIndex : -1}"
170
+ >
171
+ <template #activator="{props: activatorProps}">
172
+ <v-btn
173
+ v-bind="activatorProps"
174
+ icon="mdi-dots-vertical"
175
+ variant="plain"
176
+ slim
177
+ :density="buttonDensity"
178
+ />
179
+ </template>
180
+ <v-list :density="modelValue.options.density">
181
+ <v-list-item
182
+ v-if="modelValue.layout.listActions.includes('delete')"
183
+ base-color="warning"
184
+ @click="deleteItem(childIndex)"
185
+ >
186
+ <template #prepend>
187
+ <v-icon icon="mdi-delete" />
188
+ </template>
189
+ {{ modelValue.messages.delete }}
190
+ </v-list-item>
191
+ <v-list-item
192
+ v-if="modelValue.layout.listActions.includes('duplicate')"
193
+ @click="duplicateItem(child, childIndex)"
194
+ >
195
+ <template #prepend>
196
+ <v-icon icon="mdi-content-duplicate" />
197
+ </template>
198
+ {{ modelValue.messages.duplicate }}
199
+ </v-list-item>
200
+ <v-list-item
201
+ v-if="modelValue.layout.listActions.includes('sort')"
202
+ @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex - 1))"
203
+ >
204
+ <template #prepend>
205
+ <v-icon icon="mdi-arrow-up" />
206
+ </template>
207
+ {{ modelValue.messages.up }}
208
+ </v-list-item>
209
+ <v-list-item
210
+ v-if="modelValue.layout.listActions.includes('sort')"
211
+ @click="statefulLayout.input(modelValue, moveArrayItem(modelValue.data, childIndex, childIndex + 1))"
212
+ >
213
+ <template #prepend>
214
+ <v-icon icon="mdi-arrow-down" />
215
+ </template>
216
+ {{ modelValue.messages.down }}
217
+ </v-list-item>
218
+ </v-list>
219
+ </v-menu>
220
+ </v-list-item-action>
221
+ </template>
173
222
  </div>
174
223
  </template>
175
224
  </v-list-item>
@@ -180,7 +229,7 @@ const buttonDensity = computed(() => {
180
229
  <v-btn
181
230
  color="primary"
182
231
  :density="modelValue.options.density"
183
- @click="statefulLayout.input(modelValue, (modelValue.data ?? []).concat([undefined]))"
232
+ @click="pushEmptyItem"
184
233
  >
185
234
  {{ modelValue.messages.addItem }}
186
235
  </v-btn>