@drax/crud-vue 3.25.1 → 3.29.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.25.1",
6
+ "version": "3.29.0",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "module": "./src/index.ts",
@@ -24,10 +24,10 @@
24
24
  "format": "prettier --write src/"
25
25
  },
26
26
  "dependencies": {
27
- "@drax/common-front": "^3.25.1",
27
+ "@drax/common-front": "^3.29.0",
28
28
  "@drax/crud-front": "^3.21.0",
29
- "@drax/crud-share": "^3.21.0",
30
- "@drax/media-vue": "^3.25.1"
29
+ "@drax/crud-share": "^3.29.0",
30
+ "@drax/media-vue": "^3.29.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "pinia": "^3.0.4",
@@ -50,5 +50,5 @@
50
50
  "vue-tsc": "^3.2.4",
51
51
  "vuetify": "^3.11.8"
52
52
  },
53
- "gitHead": "3a0fbd6094e1869b18c6c09a1d7bbdc0aee63288"
53
+ "gitHead": "4f97968e53b812a348aec9f09e59bff69e71ca9e"
54
54
  }
@@ -10,7 +10,7 @@ import {useDisplay} from "vuetify"
10
10
  const {t, te} = useI18n()
11
11
  const valueModel = defineModel({type: Array, default: () => []});
12
12
 
13
- const {field, entity} = defineProps({
13
+ const {field, entity, readonly} = defineProps({
14
14
  entity: {type: Object as PropType<IEntityCrud>, required: true},
15
15
  field: {type: Object as PropType<IEntityCrudField>, required: true},
16
16
  readonly: {type: Boolean, default: false},
@@ -45,15 +45,22 @@ function addItem() {
45
45
  const item = newItem()
46
46
  valueModel.value.push(item);
47
47
  menuSelect(item, valueModel.value.length - 1)
48
+ emit('updateValue')
48
49
  }
49
50
 
50
51
  function removeItem(index: number) {
52
+ const removedItem = valueModel.value[index]
53
+
51
54
  if (indexSelected.value === index) {
52
- itemSelected.value = undefined
53
- indexSelected.value = undefined
55
+ valueModel.value.splice(index, 1);
56
+ syncSelectedItem(undefined)
57
+ emit('updateValue')
58
+ return
54
59
  }
55
- valueModel.value.splice(index, 1);
56
60
 
61
+ valueModel.value.splice(index, 1);
62
+ syncSelectedItem(itemSelected.value === removedItem ? undefined : itemSelected.value)
63
+ emit('updateValue')
57
64
  }
58
65
 
59
66
  const label = computed(() => {
@@ -81,10 +88,87 @@ const menuMaxHeight = computed(() => {
81
88
  return field.menuMaxHeight || '300px'
82
89
  })
83
90
 
84
- defineEmits(['updateValue'])
91
+ const emit = defineEmits(['updateValue'])
85
92
 
86
93
  const {xs} = useDisplay()
87
94
 
95
+ const dragIndex = ref<number | null>(null)
96
+ const dragOverIndex = ref<number | null>(null)
97
+
98
+ const isSortable = computed(() => {
99
+ return !readonly
100
+ })
101
+
102
+ function getItemTitle(index: number) {
103
+ //@ts-ignore
104
+ return valueModel.value[index]?.[field?.arrayObjectShowField ?? Object.keys(valueModel.value[index] as any)[0]]
105
+ }
106
+
107
+ function syncSelectedItem(item?: any) {
108
+ if (!item) {
109
+ itemSelected.value = undefined
110
+ indexSelected.value = undefined
111
+ return
112
+ }
113
+
114
+ const nextIndex = valueModel.value.findIndex(currentItem => currentItem === item)
115
+
116
+ if (nextIndex === -1) {
117
+ itemSelected.value = undefined
118
+ indexSelected.value = undefined
119
+ return
120
+ }
121
+
122
+ itemSelected.value = item
123
+ indexSelected.value = nextIndex
124
+ }
125
+
126
+ function reorderItems(fromIndex: number, toIndex: number) {
127
+ if (fromIndex === toIndex) {
128
+ return
129
+ }
130
+
131
+ const movedItem = valueModel.value[fromIndex]
132
+ const selectedItem = itemSelected.value
133
+
134
+ valueModel.value.splice(fromIndex, 1)
135
+ valueModel.value.splice(toIndex, 0, movedItem)
136
+
137
+ syncSelectedItem(selectedItem ?? movedItem)
138
+ emit('updateValue')
139
+ }
140
+
141
+ function onDragStart(index: number) {
142
+ if (!isSortable.value) {
143
+ return
144
+ }
145
+
146
+ dragIndex.value = index
147
+ dragOverIndex.value = index
148
+ }
149
+
150
+ function onDragEnter(index: number) {
151
+ if (!isSortable.value || dragIndex.value === null) {
152
+ return
153
+ }
154
+
155
+ dragOverIndex.value = index
156
+ }
157
+
158
+ function onDrop(index: number) {
159
+ if (!isSortable.value || dragIndex.value === null) {
160
+ return
161
+ }
162
+
163
+ reorderItems(dragIndex.value, index)
164
+ clearDragState()
165
+ }
166
+
167
+ function clearDragState() {
168
+ dragIndex.value = null
169
+ dragOverIndex.value = null
170
+ }
171
+
88
172
  </script>
89
173
 
90
174
  <template>
@@ -95,14 +179,19 @@ const {xs} = useDisplay()
95
179
  <!--ACCORDION-->
96
180
  <v-card-text v-if="field.arrayObjectUI === 'accordion' || xs">
97
181
  <v-expansion-panels>
98
- <v-expansion-panel v-for="(item,index) in valueModel" :key="index">
182
+ <v-expansion-panel v-for="(item,index) in valueModel" :key="index"
183
+ :class="{'crud-form-list--drag-over': dragOverIndex === index}"
184
+ :draggable="isSortable"
185
+ @dragstart="onDragStart(index)"
186
+ @dragenter.prevent="onDragEnter(index)"
187
+ @dragover.prevent
188
+ @drop.prevent="onDrop(index)"
189
+ @dragend="clearDragState">
99
190
 
100
191
  <v-expansion-panel-title>
192
+ <v-icon v-if="isSortable" class="mr-2" size="small">mdi-drag</v-icon>
101
193
  <v-chip class="mr-2" :color="hasError(index) ? 'red':'teal'">{{ index }}</v-chip>
102
- {{
103
- //@ts-ignore
104
- valueModel[index][field?.arrayObjectShowField ?? Object.keys(valueModel[index] as any)[0]]
105
- }}
194
+ {{ getItemTitle(index) }}
106
195
 
107
196
  <template v-slot:actions="{expanded}">
108
197
  <v-icon>{{ expanded ? "mdi-menu-down" : "mdi-menu-up" }}</v-icon>
@@ -169,12 +258,16 @@ const {xs} = useDisplay()
169
258
 
170
259
  <v-chip v-for="(item,index) in valueModel" :key="index"
171
260
  :value="index" @click="menuSelect(item, index)"
261
+ :draggable="isSortable"
262
+ :class="{'crud-form-list--drag-over': dragOverIndex === index}"
263
+ @dragstart="onDragStart(index)"
264
+ @dragenter.prevent="onDragEnter(index)"
265
+ @dragover.prevent
266
+ @drop.prevent="onDrop(index)"
267
+ @dragend="clearDragState"
172
268
  label class="pr-0" :color="indexSelected === index ? 'primary' : ''"
173
269
  >
174
- {{
175
- //@ts-ignore
176
- valueModel[index][field?.arrayObjectShowField ?? Object.keys(valueModel[index] as any)[0]] || (index)
177
- }}
270
+ {{ getItemTitle(index) || (index) }}
178
271
 
179
272
  <template v-slot:append>
180
273
  <v-btn variant="text" class="ml-2" density="compact"
@@ -227,9 +320,17 @@ const {xs} = useDisplay()
227
320
  <v-card-text>
228
321
  <v-list v-model="itemSelected" :style="{ maxHeight: menuMaxHeight, overflowY: 'auto' }">
229
322
  <v-list-item v-for="(item,index) in valueModel" :key="index" rounded="shaped"
323
+ :class="{'crud-form-list--drag-over': dragOverIndex === index}"
230
324
  :value="item" @click="menuSelect(item, index)"
325
+ :draggable="isSortable"
326
+ @dragstart="onDragStart(index)"
327
+ @dragenter.prevent="onDragEnter(index)"
328
+ @dragover.prevent
329
+ @drop.prevent="onDrop(index)"
330
+ @dragend="clearDragState"
231
331
  >
232
332
  <template v-slot:append>
333
+ <v-icon v-if="isSortable" size="small" class="mr-2">mdi-drag</v-icon>
233
334
  <v-btn size="x-small" variant="text" color="red" icon="mdi-delete"
234
335
  @click="removeItem(index)"
235
336
 
@@ -237,10 +338,7 @@ const {xs} = useDisplay()
237
338
  </template>
238
339
  <v-list-item-title>
239
340
  <v-chip class="mr-2" :color="hasError(index) ? 'red':'teal'">{{ index }}</v-chip>
240
- {{
241
- //@ts-ignore
242
- valueModel[index][field?.arrayObjectShowField ?? Object.keys(valueModel[index] as any)[0]]
243
- }}
341
+ {{ getItemTitle(index) }}
244
342
  </v-list-item-title>
245
343
  </v-list-item>
246
344
 
@@ -287,5 +385,8 @@ const {xs} = useDisplay()
287
385
  </template>
288
386
 
289
387
  <style scoped>
290
-
388
+ .crud-form-list--drag-over {
389
+ outline: 2px dashed rgb(var(--v-theme-primary));
390
+ outline-offset: 2px;
391
+ }
291
392
  </style>
@@ -22,6 +22,7 @@ import CrudFiltersDynamic from "./CrudFiltersDynamic.vue";
22
22
  import CrudFiltersAction from "./CrudFiltersAction.vue";
23
23
  import CrudFilterButton from "./buttons/CrudFilterButton.vue";
24
24
  import CrudSavedQueriesButton from "./buttons/CrudSavedQueriesButton.vue";
25
+ import CrudRefreshButton from "./buttons/CrudRefreshButton.vue";
25
26
 
26
27
  const {t, te} = useI18n()
27
28
  const {hasPermission} = useAuth()
@@ -98,6 +99,11 @@ onMounted(() => {
98
99
  <slot name="toolbar">
99
100
  </slot>
100
101
 
102
+ <crud-refresh-button
103
+ v-if="entity.isRefreshable !== false"
104
+ @click="doPaginate"
105
+ />
106
+
101
107
  <crud-create-button
102
108
  v-if="entity.isCreatable"
103
109
  :entity="entity"
@@ -22,6 +22,7 @@ import CrudFiltersAction from "./CrudFiltersAction.vue";
22
22
  import CrudFilterButton from "./buttons/CrudFilterButton.vue";
23
23
  import CrudSavedQueriesButton from "./buttons/CrudSavedQueriesButton.vue";
24
24
  import CrudRowValue from "./CrudRowValue.vue";
25
+ import CrudRefreshButton from "./buttons/CrudRefreshButton.vue";
25
26
 
26
27
  const {t, te} = useI18n()
27
28
  const {hasPermission} = useAuth()
@@ -131,6 +132,11 @@ defineEmits(['import', 'export', 'create', 'update', 'delete', 'view', 'edit'])
131
132
 
132
133
  </slot>
133
134
 
135
+ <crud-refresh-button
136
+ v-if="entity.isRefreshable !== false"
137
+ @click="doPaginate"
138
+ />
139
+
134
140
  <crud-create-button
135
141
  v-if="entity.isCreatable"
136
142
  :entity="entity"
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import {useI18n} from "vue-i18n";
3
+
4
+ const {t} = useI18n()
5
+ </script>
6
+
7
+ <template>
8
+ <v-tooltip location="top">
9
+ <template v-slot:activator="{ props }">
10
+ <v-btn
11
+ v-bind="{ ...$attrs, ...props }"
12
+ icon="mdi-refresh"
13
+ class="mr-1"
14
+ variant="text"
15
+ >
16
+ </v-btn>
17
+ </template>
18
+ {{ t('action.refresh')}}
19
+ </v-tooltip>
20
+ </template>
21
+
22
+ <style scoped>
23
+
24
+ </style>
@@ -225,6 +225,10 @@ class EntityCrud implements IEntityCrud {
225
225
  return true
226
226
  }
227
227
 
228
+ get isRefreshable() {
229
+ return true
230
+ }
231
+
228
232
  get isSavedQueriesEnabled(){
229
233
  return false
230
234
  }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import CrudNotify from "./components/CrudNotify.vue";
14
14
  import CrudSearch from "./components/CrudSearch.vue";
15
15
  import CrudAutocomplete from "./components/CrudAutocomplete.vue";
16
16
  import CrudSavedQueriesButton from "./components/buttons/CrudSavedQueriesButton.vue";
17
+ import CrudRefreshButton from "./components/buttons/CrudRefreshButton.vue";
17
18
  import EntityCombobox from "./components/combobox/EntityCombobox.vue";
18
19
  import {useCrudStore} from "./stores/UseCrudStore";
19
20
  import {useEntityStore} from "./stores/UseEntityStore";
@@ -39,6 +40,7 @@ export {
39
40
  CrudSearch,
40
41
  CrudAutocomplete,
41
42
  CrudSavedQueriesButton,
43
+ CrudRefreshButton,
42
44
  CrudFilters,
43
45
  CrudFiltersAction,
44
46
  useCrud,