adminforth 2.17.0-next.10 → 2.17.0-next.12

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.
@@ -0,0 +1,235 @@
1
+ <template>
2
+ <div class="relative inline-block">
3
+ <div
4
+ ref="triggerRef"
5
+ class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
6
+ @click="toggleMenu"
7
+ >
8
+ <IconDotsHorizontalOutline class="w-6 h-6 text-lightPrimary dark:text-darkPrimary" />
9
+ </div>
10
+ <teleport to="body">
11
+ <div
12
+ v-if="showMenu"
13
+ ref="menuRef"
14
+ class="z-50 bg-white dark:bg-gray-900 rounded-md shadow-lg border dark:border-gray-700 py-1"
15
+ :style="menuStyles"
16
+ >
17
+ <template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('show'))">
18
+ <RouterLink
19
+ v-if="resourceOptions?.allowedActions?.show"
20
+ class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300"
21
+ :to="{
22
+ name: 'resource-show',
23
+ params: {
24
+ resourceId: props.resourceId,
25
+ primaryKey: record._primaryKeyValue,
26
+ }
27
+ }"
28
+
29
+ >
30
+ <IconEyeSolid class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"/>
31
+ {{ $t('Show item') }}
32
+ </RouterLink>
33
+ </template>
34
+ <template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('edit'))">
35
+ <RouterLink
36
+ v-if="resourceOptions?.allowedActions?.edit"
37
+ class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300"
38
+ :to="{
39
+ name: 'resource-edit',
40
+ params: {
41
+ resourceId: props.resourceId,
42
+ primaryKey: record._primaryKeyValue,
43
+ }
44
+ }"
45
+ >
46
+ <IconPenSolid class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"/>
47
+ {{ $t('Edit item') }}
48
+ </RouterLink>
49
+ </template>
50
+ <template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('delete'))">
51
+ <button
52
+ v-if="resourceOptions?.allowedActions?.delete"
53
+ class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300"
54
+ @click="deleteRecord(record)"
55
+ >
56
+ <IconTrashBinSolid class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"/>
57
+ {{ $t('Delete item') }}
58
+ </button>
59
+ </template>
60
+ <div v-for="action in (resourceOptions.actions ?? []).filter(a => a.showIn?.listThreeDotsMenu)" :key="action.id" >
61
+ <button class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300" @click="() => { startCustomAction(action.id, record); showMenu = false; }">
62
+ <component
63
+ :is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
64
+ :meta="action.customComponent?.meta"
65
+ :row="record"
66
+ :resource="resource"
67
+ :adminUser="adminUser"
68
+ @callAction="(payload? : Object) => startCustomAction(action.id, record, payload)"
69
+ >
70
+ <component
71
+ v-if="action.icon"
72
+ :is="getIcon(action.icon)"
73
+ class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"
74
+ />
75
+ {{ $t(action.name) }}
76
+ </component>
77
+ </button>
78
+ </div>
79
+ <template v-if="customActionIconsThreeDotsMenuItems">
80
+ <component
81
+ v-for="c in customActionIconsThreeDotsMenuItems"
82
+ :is="getCustomComponent(c)"
83
+ :meta="c.meta"
84
+ :resource="coreStore.resource"
85
+ :adminUser="coreStore.adminUser"
86
+ :record="record"
87
+ :updateRecords="props.updateRecords"
88
+ />
89
+ </template>
90
+ </div>
91
+ </teleport>
92
+ </div>
93
+ </template>
94
+
95
+ <script lang="ts" setup>
96
+ import {
97
+ IconEyeSolid,
98
+ IconPenSolid,
99
+ IconTrashBinSolid,
100
+ IconDotsHorizontalOutline
101
+ } from '@iconify-prerendered/vue-flowbite';
102
+ import { onMounted, onBeforeUnmount, ref, nextTick, watch } from 'vue';
103
+ import { getIcon, getCustomComponent } from '@/utils';
104
+ import { useCoreStore } from '@/stores/core';
105
+ import CallActionWrapper from '@/components/CallActionWrapper.vue'
106
+
107
+ const coreStore = useCoreStore();
108
+ const showMenu = ref(false);
109
+ const triggerRef = ref<HTMLElement | null>(null);
110
+ const menuRef = ref<HTMLElement | null>(null);
111
+ const menuStyles = ref<Record<string, string>>({});
112
+
113
+ const props = defineProps<{
114
+ resourceOptions: any;
115
+ record: any;
116
+ customActionIconsThreeDotsMenuItems: any[];
117
+ resourceId: string;
118
+ deleteRecord: (record: any) => void;
119
+ updateRecords: () => void;
120
+ startCustomAction: (actionId: string, record: any) => void;
121
+ }>();
122
+
123
+ onMounted(() => {
124
+ window.addEventListener('scroll', handleScrollOrResize, true);
125
+ window.addEventListener('resize', handleScrollOrResize);
126
+ document.addEventListener('click', handleOutsideClick, true);
127
+ });
128
+
129
+ onBeforeUnmount(() => {
130
+ window.removeEventListener('scroll', handleScrollOrResize, true);
131
+ window.removeEventListener('resize', handleScrollOrResize);
132
+ document.removeEventListener('click', handleOutsideClick, true);
133
+ });
134
+
135
+ watch(showMenu, async (isOpen) => {
136
+ if (isOpen) {
137
+ await nextTick();
138
+ // First pass: after DOM mount
139
+ updateMenuPosition();
140
+ // Second pass: after layout/paint to catch width changes (fonts/icons)
141
+ requestAnimationFrame(() => {
142
+ updateMenuPosition();
143
+ // Final safety: one micro-delay retry if width was still 0
144
+ setTimeout(() => updateMenuPosition(), 0);
145
+ });
146
+ }
147
+ });
148
+
149
+ function toggleMenu() {
150
+ if (!showMenu.value) {
151
+ // Provisional position to avoid flashing at left:0 on first open
152
+ const el = triggerRef.value;
153
+ if (el) {
154
+ const rect = el.getBoundingClientRect();
155
+ const gap = 8;
156
+ menuStyles.value = {
157
+ position: 'fixed',
158
+ top: `${Math.round(rect.bottom)}px`,
159
+ left: `${Math.round(rect.left)}px`,
160
+ };
161
+ }
162
+ }
163
+ showMenu.value = !showMenu.value;
164
+ }
165
+
166
+ function updateMenuPosition() {
167
+ const el = triggerRef.value;
168
+ if (!el) return;
169
+ const rect = el.getBoundingClientRect();
170
+ const margin = 8; // gap around the trigger/menu
171
+ const menuEl = menuRef.value;
172
+ // Measure current menu size to align and decide flipping
173
+ let menuWidth = rect.width; // fallback to trigger width
174
+ let menuHeight = 0;
175
+ if (menuEl) {
176
+ const menuRect = menuEl.getBoundingClientRect();
177
+ // Prefer bounding rect; fallback to offset/scroll width if needed
178
+ const measuredW = menuRect.width || menuEl.offsetWidth || menuEl.scrollWidth;
179
+ if (measuredW > 0) menuWidth = measuredW;
180
+ const measuredH = menuRect.height || menuEl.offsetHeight || menuEl.scrollHeight;
181
+ if (measuredH > 0) menuHeight = measuredH;
182
+ }
183
+ // Right-align: right edge of menu == right edge of trigger
184
+ let left = rect.right - menuWidth;
185
+ // Clamp within viewport with small margin so it doesn't render off-screen
186
+ const minLeft = margin;
187
+ const maxLeft = Math.max(minLeft, window.innerWidth - margin - menuWidth);
188
+ left = Math.min(Math.max(left, minLeft), maxLeft);
189
+
190
+ // Determine whether to place above or below based on available space
191
+ const spaceBelow = window.innerHeight - rect.bottom - margin;
192
+ const spaceAbove = rect.top - margin;
193
+ const maxMenuHeight = Math.max(0, window.innerHeight - 2 * margin);
194
+
195
+ let top: number;
196
+ if (menuHeight === 0) {
197
+ // Unknown height yet (first pass). Prefer placing below; a subsequent pass will correct if needed.
198
+ top = rect.bottom + margin;
199
+ } else if (menuHeight <= spaceBelow) {
200
+ // Enough space below
201
+ top = rect.bottom + margin;
202
+ } else if (menuHeight <= spaceAbove) {
203
+ // Not enough below but enough above -> flip
204
+ top = rect.top - margin - menuHeight;
205
+ } else {
206
+ // Not enough space on either side: pick the side with more room and clamp within viewport
207
+ if (spaceBelow >= spaceAbove) {
208
+ top = Math.min(rect.bottom + margin, window.innerHeight - margin - menuHeight);
209
+ } else {
210
+ top = Math.max(margin, rect.top - margin - menuHeight);
211
+ }
212
+ }
213
+
214
+ menuStyles.value = {
215
+ position: 'fixed',
216
+ top: `${Math.round(top)}px`,
217
+ left: `${Math.round(left)}px`,
218
+ maxHeight: `${Math.round(maxMenuHeight)}px`,
219
+ overflowY: 'auto',
220
+ };
221
+ }
222
+
223
+ function handleScrollOrResize() {
224
+ showMenu.value = false;
225
+ }
226
+
227
+ function handleOutsideClick(e: MouseEvent) {
228
+ const target = e.target as Node | null;
229
+ if (!target) return;
230
+ if (menuRef.value?.contains(target)) return;
231
+ if (triggerRef.value?.contains(target)) return;
232
+ showMenu.value = false;
233
+ }
234
+
235
+ </script>
@@ -120,7 +120,7 @@
120
120
  </td>
121
121
  <td class=" items-center px-2 md:px-3 lg:px-6 py-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
122
122
  <div class="flex text-lightPrimary dark:text-darkPrimary items-center">
123
- <Tooltip>
123
+ <Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('show')">
124
124
  <RouterLink
125
125
  v-if="resource.options?.allowedActions?.show"
126
126
  :to="{
@@ -139,8 +139,7 @@
139
139
  {{ $t('Show item') }}
140
140
  </template>
141
141
  </Tooltip>
142
-
143
- <Tooltip>
142
+ <Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('edit')" >
144
143
  <RouterLink
145
144
  v-if="resource.options?.allowedActions?.edit"
146
145
  :to="{
@@ -157,8 +156,7 @@
157
156
  {{ $t('Edit item') }}
158
157
  </template>
159
158
  </Tooltip>
160
-
161
- <Tooltip>
159
+ <Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('delete')">
162
160
  <button
163
161
  v-if="resource.options?.allowedActions?.delete"
164
162
  @click="deleteRecord(row)"
@@ -170,7 +168,6 @@
170
168
  {{ $t('Delete item') }}
171
169
  </template>
172
170
  </Tooltip>
173
-
174
171
  <template v-if="customActionsInjection">
175
172
  <component
176
173
  v-for="c in customActionsInjection"
@@ -185,7 +182,7 @@
185
182
 
186
183
  <template v-if="resource.options?.actions">
187
184
  <Tooltip
188
- v-for="action in resource.options.actions.filter(a => a.showIn?.list)"
185
+ v-for="action in resource.options.actions.filter(a => a.showIn?.list || a.showIn?.listQuickIcon)"
189
186
  :key="action.id"
190
187
  >
191
188
  <component
@@ -213,6 +210,16 @@
213
210
  </template>
214
211
  </Tooltip>
215
212
  </template>
213
+ <ListActionsThreeDots
214
+ v-if="showListActionsThreeDots"
215
+ :resourceOptions="resource?.options"
216
+ :record="row"
217
+ :updateRecords="()=>emits('update:records', true)"
218
+ :deleteRecord="deleteRecord"
219
+ :resourceId="resource.resourceId"
220
+ :startCustomAction="startCustomAction"
221
+ :customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
222
+ />
216
223
  </div>
217
224
 
218
225
  </td>
@@ -323,20 +330,18 @@ import { useCoreStore } from '@/stores/core';
323
330
  import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
324
331
  import SkeleteLoader from '@/components/SkeleteLoader.vue';
325
332
  import { getIcon } from '@/utils';
326
- import {
327
- IconInboxOutline,
328
- } from '@iconify-prerendered/vue-flowbite';
329
-
330
333
  import {
331
334
  IconEyeSolid,
332
335
  IconPenSolid,
333
336
  IconTrashBinSolid,
337
+ IconInboxOutline
334
338
  } from '@iconify-prerendered/vue-flowbite';
335
339
  import router from '@/router';
336
340
  import { Tooltip } from '@/afcl';
337
341
  import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
338
342
  import adminforth from '@/adminforth';
339
343
  import Checkbox from '@/afcl/Checkbox.vue';
344
+ import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
340
345
  import CallActionWrapper from '@/components/CallActionWrapper.vue'
341
346
 
342
347
  const coreStore = useCoreStore();
@@ -352,6 +357,7 @@ const props = defineProps<{
352
357
  noRoundings?: boolean,
353
358
  customActionsInjection?: any[],
354
359
  tableBodyStartInjection?: any[],
360
+ customActionIconsThreeDotsMenuItems?: any[]
355
361
  tableRowReplaceInjection?: AdminForthComponentDeclaration,
356
362
  }>();
357
363
 
@@ -369,6 +375,12 @@ const pageInput = ref('1');
369
375
  const page = ref(1);
370
376
  const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
371
377
 
378
+ const showListActionsThreeDots = computed(() => {
379
+ return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
380
+ || (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
381
+ || !props.resource?.options.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
382
+ || (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
383
+ })
372
384
 
373
385
  const from = computed(() => ((page.value || 1) - 1) * props.pageSize + 1);
374
386
  const to = computed(() => Math.min((page.value || 1) * props.pageSize, props.totalRows));
@@ -130,7 +130,7 @@
130
130
  </td>
131
131
  <td class=" items-center px-2 md:px-3 lg:px-6 py-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
132
132
  <div class="flex text-lightPrimary dark:text-darkPrimary items-center">
133
- <Tooltip>
133
+ <Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('show')">
134
134
  <RouterLink
135
135
  v-if="resource.options?.allowedActions?.show"
136
136
  :to="{
@@ -142,15 +142,14 @@
142
142
  }"
143
143
 
144
144
  >
145
- <IconEyeSolid class="w-5 h-5 me-2"/>
145
+ <IconEyeSolid class="af-show-icon w-5 h-5 me-2"/>
146
146
  </RouterLink>
147
147
 
148
148
  <template v-slot:tooltip>
149
149
  {{ $t('Show item') }}
150
150
  </template>
151
151
  </Tooltip>
152
-
153
- <Tooltip>
152
+ <Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('edit')" >
154
153
  <RouterLink
155
154
  v-if="resource.options?.allowedActions?.edit"
156
155
  :to="{
@@ -161,26 +160,24 @@
161
160
  }
162
161
  }"
163
162
  >
164
- <IconPenSolid class="w-5 h-5 me-2"/>
163
+ <IconPenSolid class="af-edit-icon w-5 h-5 me-2"/>
165
164
  </RouterLink>
166
165
  <template v-slot:tooltip>
167
166
  {{ $t('Edit item') }}
168
167
  </template>
169
168
  </Tooltip>
170
-
171
- <Tooltip>
169
+ <Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('delete')">
172
170
  <button
173
171
  v-if="resource.options?.allowedActions?.delete"
174
172
  @click="deleteRecord(row)"
175
173
  >
176
- <IconTrashBinSolid class="w-5 h-5 me-2"/>
174
+ <IconTrashBinSolid class="af-delete-icon w-5 h-5 me-2"/>
177
175
  </button>
178
176
 
179
177
  <template v-slot:tooltip>
180
178
  {{ $t('Delete item') }}
181
179
  </template>
182
- </Tooltip>
183
-
180
+ </Tooltip>
184
181
  <template v-if="customActionsInjection">
185
182
  <component
186
183
  v-for="c in customActionsInjection"
@@ -192,10 +189,9 @@
192
189
  :updateRecords="()=>emits('update:records', true)"
193
190
  />
194
191
  </template>
195
-
196
- <template v-if="resource.options?.actions">
192
+ <template v-if="resource.options?.actions">
197
193
  <Tooltip
198
- v-for="action in resource.options.actions.filter(a => a.showIn?.list)"
194
+ v-for="action in resource.options.actions.filter(a => a.showIn?.list || a.showIn?.listQuickIcon)"
199
195
  :key="action.id"
200
196
  >
201
197
  <CallActionWrapper
@@ -228,6 +224,16 @@
228
224
  </template>
229
225
  </Tooltip>
230
226
  </template>
227
+ <ListActionsThreeDots
228
+ v-if="showListActionsThreeDots"
229
+ :resourceOptions="resource?.options"
230
+ :record="row"
231
+ :updateRecords="()=>emits('update:records', true)"
232
+ :deleteRecord="deleteRecord"
233
+ :resourceId="resource.resourceId"
234
+ :startCustomAction="startCustomAction"
235
+ :customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
236
+ />
231
237
  </div>
232
238
  </td>
233
239
  </component>
@@ -359,6 +365,7 @@ import { Tooltip } from '@/afcl';
359
365
  import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
360
366
  import adminforth from '@/adminforth';
361
367
  import Checkbox from '@/afcl/Checkbox.vue';
368
+ import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
362
369
  import CallActionWrapper from '@/components/CallActionWrapper.vue'
363
370
 
364
371
  const coreStore = useCoreStore();
@@ -377,6 +384,7 @@ const props = defineProps<{
377
384
  containerHeight?: number,
378
385
  itemHeight?: number,
379
386
  bufferSize?: number,
387
+ customActionIconsThreeDotsMenuItems?: any[]
380
388
  tableRowReplaceInjection?: AdminForthComponentDeclaration
381
389
  }>();
382
390
 
@@ -394,6 +402,12 @@ const pageInput = ref('1');
394
402
  const page = ref(1);
395
403
  const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
396
404
 
405
+ const showListActionsThreeDots = computed(() => {
406
+ return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
407
+ || (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
408
+ || !props.resource?.options.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
409
+ || (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
410
+ })
397
411
 
398
412
  const from = computed(() => ((page.value || 1) - 1) * props.pageSize + 1);
399
413
  const to = computed(() => Math.min((page.value || 1) * props.pageSize, props.totalRows));
@@ -848,6 +848,7 @@ export interface AdminForthActionInput {
848
848
  name: string;
849
849
  showIn?: {
850
850
  list?: boolean,
851
+ listThreeDotsMenu?: boolean,
851
852
  showButton?: boolean,
852
853
  showThreeDotsMenu?: boolean,
853
854
  };
@@ -861,6 +862,7 @@ export interface AdminForthActionInput {
861
862
  resource: AdminForthResource;
862
863
  recordId: string;
863
864
  adminUser: AdminUser;
865
+ response: IAdminForthHttpResponse;
864
866
  extra?: HttpExtra;
865
867
  tr: Function;
866
868
  }) => Promise<{
@@ -1358,6 +1360,7 @@ export type AllowedActions = {
1358
1360
  */
1359
1361
  export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
1360
1362
 
1363
+ baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[],
1361
1364
  /**
1362
1365
  * Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
1363
1366
  * using checkboxes.
@@ -503,6 +503,7 @@ export interface AdminForthResourceInputCommon {
503
503
  bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
504
504
  threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
505
505
  customActionIcons?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
506
+ customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
506
507
  tableBodyStart?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
507
508
  tableRowReplace?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
508
509
  },
@@ -119,7 +119,7 @@
119
119
  :adminUser="coreStore.adminUser"
120
120
  />
121
121
  <ResourceListTableVirtual
122
- v-if="isVirtualScrollEnabled"
122
+ v-if="isVirtualScrollEnabled && !coreStore.isResourceFetching"
123
123
  :resource="coreStore.resource"
124
124
  :rows="rows"
125
125
  :page="page"
@@ -143,6 +143,11 @@
143
143
  ? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
144
144
  : []
145
145
  "
146
+ :customActionIconsThreeDotsMenuItems="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems)
147
+ ? coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems
148
+ : coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems
149
+ ? [coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems]
150
+ : []"
146
151
  :tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
147
152
  ? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
148
153
  : coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
@@ -152,7 +157,7 @@
152
157
  />
153
158
 
154
159
  <ResourceListTable
155
- v-else
160
+ v-else-if="!coreStore.isResourceFetching"
156
161
  :resource="coreStore.resource"
157
162
  :rows="rows"
158
163
  :page="page"
@@ -176,6 +181,11 @@
176
181
  ? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
177
182
  : []
178
183
  "
184
+ :customActionIconsThreeDotsMenuItems="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems)
185
+ ? coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems
186
+ : coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems
187
+ ? [coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems]
188
+ : []"
179
189
  :tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
180
190
  ? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
181
191
  : coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
@@ -830,6 +830,7 @@ export interface AdminForthActionInput {
830
830
  name: string;
831
831
  showIn?: {
832
832
  list?: boolean;
833
+ listThreeDotsMenu?: boolean;
833
834
  showButton?: boolean;
834
835
  showThreeDotsMenu?: boolean;
835
836
  };
@@ -843,6 +844,7 @@ export interface AdminForthActionInput {
843
844
  resource: AdminForthResource;
844
845
  recordId: string;
845
846
  adminUser: AdminUser;
847
+ response: IAdminForthHttpResponse;
846
848
  extra?: HttpExtra;
847
849
  tr: Function;
848
850
  }) => Promise<{
@@ -1208,6 +1210,7 @@ export type AllowedActions = {
1208
1210
  * General options for resource.
1209
1211
  */
1210
1212
  export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
1213
+ baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[];
1211
1214
  /**
1212
1215
  * Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
1213
1216
  * using checkboxes.