quasar-ui-danx 0.4.15 → 0.4.17
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/dist/danx.es.js +4306 -4295
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +71 -71
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionTable.vue +62 -62
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +74 -69
- package/src/components/ActionTable/listControls.ts +38 -24
- package/src/helpers/actions.ts +21 -1
- package/src/helpers/objectStore.ts +4 -2
- package/src/helpers/utils.ts +16 -0
package/package.json
CHANGED
@@ -1,66 +1,66 @@
|
|
1
1
|
<template>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
2
|
+
<div
|
3
|
+
class="dx-action-table overflow-hidden"
|
4
|
+
:class="{'dx-no-data': !hasData, 'dx-is-loading': loadingList || loadingSummary, 'dx-is-loading-list': loadingList}"
|
5
|
+
>
|
6
|
+
<ActionVnode />
|
7
|
+
<QTable
|
8
|
+
ref="actionTable"
|
9
|
+
:selected="selectedRows"
|
10
|
+
:pagination="pagination"
|
11
|
+
:columns="tableColumns"
|
12
|
+
:loading="loadingList || loadingSummary"
|
13
|
+
:rows="pagedItems?.data || []"
|
14
|
+
:binary-state-sort="false"
|
15
|
+
:selection="selection"
|
16
|
+
:rows-per-page-options="rowsPerPageOptions"
|
17
|
+
class="sticky-column sticky-header w-full h-full !border-0"
|
18
|
+
:color="color"
|
19
|
+
@update:selected="$emit('update:selected-rows', $event)"
|
20
|
+
@update:pagination="() => {}"
|
21
|
+
@request="(e) => $emit('update:pagination', {...e.pagination, __sort: mapSortBy(e.pagination, tableColumns)})"
|
22
|
+
>
|
23
|
+
<template #no-data>
|
24
|
+
<slot name="empty">
|
25
|
+
<EmptyTableState :text="`There are no ${label.toLowerCase()} matching the applied filter`" />
|
26
|
+
</slot>
|
27
|
+
</template>
|
28
|
+
<template #top-row>
|
29
|
+
<TableSummaryRow
|
30
|
+
v-if="hasData"
|
31
|
+
:label="label"
|
32
|
+
:item-count="summary?.count || 0"
|
33
|
+
:selected-count="selectedRows.length"
|
34
|
+
:sticky-colspan="summaryColSpan"
|
35
|
+
:loading="loadingSummary"
|
36
|
+
:summary="summary"
|
37
|
+
:columns="tableColumns"
|
38
|
+
@clear="$emit('update:selected-rows', [])"
|
39
|
+
/>
|
40
|
+
</template>
|
41
|
+
<template #header-cell="rowProps">
|
42
|
+
<ActionTableHeaderColumn
|
43
|
+
v-model="columnSettings"
|
44
|
+
:row-props="rowProps"
|
45
|
+
:name="name"
|
46
|
+
@update:model-value="onUpdateColumnSettings"
|
47
|
+
/>
|
48
|
+
</template>
|
49
|
+
<template #body-cell="rowProps">
|
50
|
+
<ActionTableColumn
|
51
|
+
:key="rowProps.key"
|
52
|
+
:row-props="rowProps"
|
53
|
+
:settings="columnSettings[rowProps.col.name]"
|
54
|
+
>
|
55
|
+
<slot
|
56
|
+
:column-name="rowProps.col.name"
|
57
|
+
:row="rowProps.row"
|
58
|
+
:value="rowProps.value"
|
59
|
+
/>
|
60
|
+
</ActionTableColumn>
|
61
|
+
</template>
|
62
|
+
</QTable>
|
63
|
+
</div>
|
64
64
|
</template>
|
65
65
|
|
66
66
|
<script setup lang="ts">
|
@@ -1,72 +1,72 @@
|
|
1
1
|
<template>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
2
|
+
<div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full">
|
3
|
+
<slot name="top" />
|
4
|
+
<slot name="toolbar">
|
5
|
+
<ActionToolbar
|
6
|
+
v-if="!hideToolbar"
|
7
|
+
:title="title"
|
8
|
+
:refresh-button="refreshButton"
|
9
|
+
:actions="actions?.filter(a => a.batch)"
|
10
|
+
:action-target="controller.selectedRows.value"
|
11
|
+
:exporter="controller.exportList"
|
12
|
+
:loading="controller.isLoadingList.value || controller.isLoadingSummary.value"
|
13
|
+
@refresh="controller.refreshAll"
|
14
|
+
>
|
15
|
+
<template #default>
|
16
|
+
<slot name="action-toolbar" />
|
17
|
+
</template>
|
18
|
+
</ActionToolbar>
|
19
|
+
</slot>
|
20
|
+
<div class="flex flex-nowrap flex-grow overflow-hidden w-full">
|
21
|
+
<slot name="filters">
|
22
|
+
<CollapsableFiltersSidebar
|
23
|
+
v-if="activeFilter"
|
24
|
+
:name="controller.name"
|
25
|
+
:show-filters="showFilters"
|
26
|
+
:filters="filters"
|
27
|
+
:active-filter="activeFilter"
|
28
|
+
class="dx-action-table-filters"
|
29
|
+
@update:active-filter="controller.setActiveFilter"
|
30
|
+
/>
|
31
|
+
</slot>
|
32
|
+
<slot>
|
33
|
+
<ActionTable
|
34
|
+
class="flex-grow"
|
35
|
+
:pagination="controller.pagination.value"
|
36
|
+
:selected-rows="controller.selectedRows.value"
|
37
|
+
:label="controller.label"
|
38
|
+
:name="controller.name"
|
39
|
+
:class="tableClass"
|
40
|
+
:summary="controller.summary.value"
|
41
|
+
:loading-list="controller.isLoadingList.value"
|
42
|
+
:loading-summary="controller.isLoadingSummary.value"
|
43
|
+
:paged-items="controller.pagedItems.value"
|
44
|
+
:columns="columns"
|
45
|
+
:selection="selection"
|
46
|
+
@update:selected-rows="controller.setSelectedRows"
|
47
|
+
@update:pagination="controller.setPagination"
|
48
|
+
/>
|
49
|
+
</slot>
|
50
|
+
<slot name="panels">
|
51
|
+
<PanelsDrawer
|
52
|
+
v-if="activeItem && panels"
|
53
|
+
:title="panelTitle"
|
54
|
+
:model-value="activePanel"
|
55
|
+
:active-item="activeItem"
|
56
|
+
:panels="panels"
|
57
|
+
@update:model-value="panel => controller.activatePanel(activeItem, panel)"
|
58
|
+
@close="controller.setActiveItem(null)"
|
59
|
+
>
|
60
|
+
<template #controls>
|
61
|
+
<PreviousNextControls
|
62
|
+
:is-loading="controller.isLoadingList.value"
|
63
|
+
@next="controller.getNextItem"
|
64
|
+
/>
|
65
|
+
</template>
|
66
|
+
</PanelsDrawer>
|
67
|
+
</slot>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
70
|
</template>
|
71
71
|
<script setup lang="ts">
|
72
72
|
import { computed } from "vue";
|
@@ -96,7 +96,12 @@ export interface ActionTableLayoutProps {
|
|
96
96
|
const props = withDefaults(defineProps<ActionTableLayoutProps>(), {
|
97
97
|
title: "",
|
98
98
|
tableClass: "",
|
99
|
-
selection: "multiple"
|
99
|
+
selection: "multiple",
|
100
|
+
filters: undefined,
|
101
|
+
panels: undefined,
|
102
|
+
actions: undefined,
|
103
|
+
exporter: undefined,
|
104
|
+
panelTitleField: ""
|
100
105
|
});
|
101
106
|
|
102
107
|
const activeFilter = computed(() => props.controller.activeFilter.value);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { computed, Ref, ref, shallowRef, watch } from "vue";
|
2
2
|
import { RouteParams, Router } from "vue-router";
|
3
3
|
import { danxOptions } from "../../config";
|
4
|
-
import { getItem, setItem, storeObject, waitForRef } from "../../helpers";
|
4
|
+
import { getItem, latestCallOnly, setItem, storeObject, waitForRef } from "../../helpers";
|
5
5
|
import {
|
6
6
|
ActionController,
|
7
7
|
ActionTargetItem,
|
@@ -290,10 +290,16 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
290
290
|
* @returns {Promise<void>}
|
291
291
|
*/
|
292
292
|
async function getActiveItemDetails() {
|
293
|
-
if (!activeItem.value || !options.routes.details) return;
|
294
|
-
|
295
293
|
try {
|
296
|
-
const
|
294
|
+
const latestResult = latestCallOnly("active-item", async () => {
|
295
|
+
if (!activeItem.value || !options.routes.details) return undefined;
|
296
|
+
return await options.routes.details(activeItem.value);
|
297
|
+
});
|
298
|
+
|
299
|
+
const result = await latestResult();
|
300
|
+
|
301
|
+
// Undefined means we were not the latest, or the request was invalid (ie: activeItem was already cleared)
|
302
|
+
if (result === undefined) return;
|
297
303
|
|
298
304
|
if (!result || !result.__type || !result.id) {
|
299
305
|
return console.error("Invalid response from details route: All responses must include a __type and id field. result =", result);
|
@@ -354,33 +360,41 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
354
360
|
const index = pagedItems.value.data.findIndex((i: ActionTargetItem) => i.id === activeItem.value?.id);
|
355
361
|
if (index === undefined || index === null) return;
|
356
362
|
|
357
|
-
|
363
|
+
const nextIndex = index + offset;
|
364
|
+
|
365
|
+
const latestNextIndex = latestCallOnly("getNextItem", async () => {
|
366
|
+
// Load the previous page if the offset is before index 0
|
367
|
+
if (nextIndex < 0) {
|
368
|
+
if (pagination.value.page > 1) {
|
369
|
+
pagination.value = { ...pagination.value, page: pagination.value.page - 1 };
|
370
|
+
await waitForRef(isLoadingList, false);
|
371
|
+
return pagedItems.value.data.length - 1;
|
372
|
+
}
|
358
373
|
|
359
|
-
// Load the previous page if the offset is before index 0
|
360
|
-
if (nextIndex < 0) {
|
361
|
-
if (pagination.value.page > 1) {
|
362
|
-
pagination.value = { ...pagination.value, page: pagination.value.page - 1 };
|
363
|
-
await waitForRef(isLoadingList, false);
|
364
|
-
nextIndex = pagedItems.value.data.length - 1;
|
365
|
-
} else {
|
366
374
|
// There are no more previous pages
|
367
|
-
return;
|
375
|
+
return -1;
|
368
376
|
}
|
369
|
-
}
|
370
377
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
+
// Load the next page if the offset is past the last index
|
379
|
+
if (nextIndex >= pagedItems.value.data.length) {
|
380
|
+
if (pagination.value.page < (pagedItems.value?.meta?.last_page || 1)) {
|
381
|
+
pagination.value = { ...pagination.value, page: pagination.value.page + 1 };
|
382
|
+
await waitForRef(isLoadingList, false);
|
383
|
+
return 0;
|
384
|
+
}
|
385
|
+
|
378
386
|
// There are no more next pages
|
379
|
-
return;
|
387
|
+
return -1;
|
380
388
|
}
|
381
|
-
}
|
382
389
|
|
383
|
-
|
390
|
+
return nextIndex;
|
391
|
+
});
|
392
|
+
|
393
|
+
const resolvedNextIndex = await latestNextIndex();
|
394
|
+
|
395
|
+
if (resolvedNextIndex !== undefined && resolvedNextIndex >= 0) {
|
396
|
+
activeItem.value = pagedItems.value?.data[resolvedNextIndex];
|
397
|
+
}
|
384
398
|
}
|
385
399
|
|
386
400
|
/**
|
package/src/helpers/actions.ts
CHANGED
@@ -14,6 +14,25 @@ export const activeActionVnode: Ref = shallowRef(null);
|
|
14
14
|
export function useActions(actions: ActionOptions[], globalOptions: Partial<ActionOptions> | null = null) {
|
15
15
|
const namespace = uid();
|
16
16
|
|
17
|
+
/**
|
18
|
+
* Extend an action so the base action can be modified without affecting other places the action is used.
|
19
|
+
* This isolates the action to the provided id, so it is still re-usable across the system, but does not affect behavior elsewhere.
|
20
|
+
*
|
21
|
+
* For example, when you have a list of items and you want to perform a callback on the action on a single item, you can extend the action
|
22
|
+
* with the id of the item you want to perform the action on, allowing you to perform behavior on a single item, instead of all instances
|
23
|
+
* in the list receiving the same callback.
|
24
|
+
*/
|
25
|
+
function extendAction(actionName: string, extendedId: string | number, actionOptions: Partial<ActionOptions>): ResourceAction {
|
26
|
+
const action = getAction(actionName);
|
27
|
+
const extendedAction = { ...action, ...actionOptions, id: extendedId };
|
28
|
+
if (extendedAction.debounce) {
|
29
|
+
extendedAction.trigger = useDebounceFn((target, input) => performAction(extendedAction, target, input), extendedAction.debounce);
|
30
|
+
} else {
|
31
|
+
extendedAction.trigger = (target, input) => performAction(extendedAction, target, input);
|
32
|
+
}
|
33
|
+
return storeObject(extendedAction);
|
34
|
+
}
|
35
|
+
|
17
36
|
/**
|
18
37
|
* Resolve the action object based on the provided name (or return the object if the name is already an object)
|
19
38
|
*/
|
@@ -140,7 +159,8 @@ export function useActions(actions: ActionOptions[], globalOptions: Partial<Acti
|
|
140
159
|
|
141
160
|
return {
|
142
161
|
getAction,
|
143
|
-
getActions
|
162
|
+
getActions,
|
163
|
+
extendAction
|
144
164
|
};
|
145
165
|
}
|
146
166
|
|
@@ -34,9 +34,11 @@ export function storeObject<T extends TypedObject>(newObject: T): ShallowReactiv
|
|
34
34
|
// Recursively store all the children of the object as well
|
35
35
|
for (const key of Object.keys(newObject)) {
|
36
36
|
const value = newObject[key];
|
37
|
-
if (Array.isArray(value) && value.length > 0
|
37
|
+
if (Array.isArray(value) && value.length > 0) {
|
38
38
|
for (const index in value) {
|
39
|
-
|
39
|
+
if (value[index] && typeof value[index] === "object") {
|
40
|
+
newObject[key][index] = storeObject(value[index]);
|
41
|
+
}
|
40
42
|
}
|
41
43
|
} else if (value?.__type) {
|
42
44
|
// @ts-expect-error newObject[key] is guaranteed to be a TypedObject
|
package/src/helpers/utils.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { AnyObject } from "src/types";
|
1
2
|
import { Ref, watch } from "vue";
|
2
3
|
|
3
4
|
export { useDebounceFn } from "@vueuse/core";
|
@@ -33,6 +34,21 @@ export function waitForRef(ref: Ref, value: any) {
|
|
33
34
|
});
|
34
35
|
}
|
35
36
|
|
37
|
+
const currentCalls: AnyObject = {};
|
38
|
+
export function latestCallOnly<T extends any[], R>(type: string, fn: (...args: T) => Promise<R>) {
|
39
|
+
if (!currentCalls[type]) {
|
40
|
+
currentCalls[type] = 0;
|
41
|
+
}
|
42
|
+
return async function (...args: T) {
|
43
|
+
const callId = ++currentCalls[type];
|
44
|
+
const result = await fn(...args);
|
45
|
+
if (callId === currentCalls[type]) {
|
46
|
+
return result;
|
47
|
+
}
|
48
|
+
return undefined;
|
49
|
+
};
|
50
|
+
}
|
51
|
+
|
36
52
|
/**
|
37
53
|
* Returns a number that is constrained to the given range.
|
38
54
|
*/
|