quasar-ui-danx 0.4.13 → 0.4.15
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 +5394 -5322
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +80 -80
- 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 +65 -63
- package/src/components/ActionTable/Form/ActionForm.vue +18 -12
- package/src/components/ActionTable/Form/RenderedForm.vue +39 -19
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +80 -70
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +29 -29
- package/src/components/ActionTable/listControls.ts +32 -31
- package/src/components/Navigation/NavigationMenu.vue +83 -40
- package/src/components/PanelsDrawer/PanelsDrawer.vue +5 -3
- package/src/components/Utility/Transitions/AutoHeightTransition.vue +21 -0
- package/src/components/Utility/Transitions/index.ts +1 -0
- package/src/config/index.ts +1 -0
- package/src/helpers/actions.ts +31 -20
- package/src/helpers/formats.ts +8 -5
- package/src/helpers/objectStore.ts +9 -2
- package/src/helpers/request.ts +1 -1
- package/src/helpers/routes.ts +5 -0
- package/src/types/actions.d.ts +1 -4
- package/src/types/config.d.ts +3 -1
- package/src/types/controls.d.ts +5 -7
- package/src/types/forms.d.ts +1 -0
- package/src/types/shared.d.ts +1 -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">
|
@@ -89,6 +89,7 @@ export interface Props {
|
|
89
89
|
columns: TableColumn[];
|
90
90
|
rowsPerPageOptions?: number[];
|
91
91
|
summaryColSpan?: number;
|
92
|
+
selection: "multiple" | "single";
|
92
93
|
}
|
93
94
|
|
94
95
|
const props = withDefaults(defineProps<Props>(), {
|
@@ -97,7 +98,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
97
98
|
summary: null,
|
98
99
|
loadingSummary: false,
|
99
100
|
rowsPerPageOptions: () => [10, 25, 50, 100],
|
100
|
-
summaryColSpan: null
|
101
|
+
summaryColSpan: null,
|
102
|
+
selection: "multiple"
|
101
103
|
});
|
102
104
|
|
103
105
|
const actionTable = ref(null);
|
@@ -1,16 +1,19 @@
|
|
1
1
|
<template>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
2
|
+
<div>
|
3
|
+
<RenderedForm
|
4
|
+
v-bind="renderedFormProps"
|
5
|
+
v-model:values="input"
|
6
|
+
empty-value=""
|
7
|
+
:saved-at="target.updated_at"
|
8
|
+
:saving="action.isApplying"
|
9
|
+
@update:values="action.trigger(target, input)"
|
10
|
+
>
|
11
|
+
<slot />
|
12
|
+
</RenderedForm>
|
13
|
+
</div>
|
11
14
|
</template>
|
12
15
|
<script setup lang="ts">
|
13
|
-
import { ref, watch } from "vue";
|
16
|
+
import { Ref, ref, watch } from "vue";
|
14
17
|
import { ActionTargetItem, AnyObject, Form, ResourceAction } from "../../../types";
|
15
18
|
import RenderedForm from "./RenderedForm.vue";
|
16
19
|
|
@@ -27,7 +30,10 @@ interface ActionFormProps {
|
|
27
30
|
savingClass?: string;
|
28
31
|
}
|
29
32
|
|
30
|
-
const props = defineProps<ActionFormProps>()
|
33
|
+
const props = withDefaults(defineProps<ActionFormProps>(), {
|
34
|
+
fieldClass: "",
|
35
|
+
savingClass: undefined
|
36
|
+
});
|
31
37
|
const renderedFormProps = {
|
32
38
|
form: props.form,
|
33
39
|
noLabel: props.noLabel,
|
@@ -39,7 +45,7 @@ const renderedFormProps = {
|
|
39
45
|
savingClass: props.savingClass
|
40
46
|
};
|
41
47
|
|
42
|
-
const input: AnyObject = ref({ ...props.target });
|
48
|
+
const input: Ref<AnyObject> = ref({ ...props.target });
|
43
49
|
const fieldStatus: AnyObject = {};
|
44
50
|
|
45
51
|
// Only update field values from target changes when the field is not already being saved
|
@@ -92,6 +92,7 @@
|
|
92
92
|
/>
|
93
93
|
</div>
|
94
94
|
</template>
|
95
|
+
<slot />
|
95
96
|
<div
|
96
97
|
v-if="savedAt"
|
97
98
|
:class="savingClass"
|
@@ -137,7 +138,7 @@
|
|
137
138
|
</template>
|
138
139
|
<script setup lang="ts">
|
139
140
|
import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@heroicons/vue/solid";
|
140
|
-
import { computed, ref } from "vue";
|
141
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
141
142
|
import { fDateTime, FlashMessages, incrementName, replace } from "../../../helpers";
|
142
143
|
import { TrashIcon as RemoveIcon } from "../../../svg";
|
143
144
|
import { AnyObject, FormFieldValue, RenderedFormProps } from "../../../types";
|
@@ -178,15 +179,20 @@ const FORM_FIELD_MAP = {
|
|
178
179
|
|
179
180
|
const mappedFields = props.form.fields.map((field) => ({
|
180
181
|
placeholder: `Enter ${field.label}`,
|
181
|
-
default: field.type === "BOOLEAN" ? false : "",
|
182
182
|
...field,
|
183
183
|
component: field.component || FORM_FIELD_MAP[field.type]
|
184
184
|
}));
|
185
185
|
|
186
186
|
const fieldResponses = computed(() => {
|
187
|
-
|
188
|
-
if (
|
189
|
-
|
187
|
+
const values = props.values;
|
188
|
+
if (!values) return [];
|
189
|
+
if (Array.isArray(values)) return values;
|
190
|
+
|
191
|
+
return Object.entries(values).filter((entry) => mappedFields.find(mf => mf.name === entry[0])).map(([name, value]) => ({
|
192
|
+
name,
|
193
|
+
value,
|
194
|
+
variation: ""
|
195
|
+
}));
|
190
196
|
});
|
191
197
|
|
192
198
|
const fieldInputs = computed(() => {
|
@@ -252,11 +258,38 @@ function onInput(name: string, value: any) {
|
|
252
258
|
updateValues(newValues);
|
253
259
|
}
|
254
260
|
|
261
|
+
function updateValues(values: FormFieldValue[]) {
|
262
|
+
let updatedValues: FormFieldValue[] | object = values;
|
263
|
+
|
264
|
+
if (!Array.isArray(props.values)) {
|
265
|
+
updatedValues = values.reduce((acc: AnyObject, v) => {
|
266
|
+
acc[v.name] = v.value;
|
267
|
+
return acc;
|
268
|
+
}, {});
|
269
|
+
}
|
270
|
+
|
271
|
+
emit("update:values", updatedValues);
|
272
|
+
}
|
273
|
+
|
274
|
+
onMounted(() => {
|
275
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
276
|
+
});
|
277
|
+
onBeforeUnmount(() => {
|
278
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
279
|
+
});
|
280
|
+
|
281
|
+
|
282
|
+
function handleBeforeUnload(event: BeforeUnloadEvent) {
|
283
|
+
if (props.saving) {
|
284
|
+
return event.returnValue = "Changes are currently being saved. If you leave now, you might lose unsaved changes.";
|
285
|
+
}
|
286
|
+
}
|
287
|
+
|
255
288
|
function createVariation(variation) {
|
256
289
|
return props.form.fields.map((field) => ({
|
257
290
|
variation,
|
258
291
|
name: field.name,
|
259
|
-
value: field.
|
292
|
+
value: field.default_value === undefined ? null : field.default_value
|
260
293
|
}));
|
261
294
|
}
|
262
295
|
|
@@ -293,19 +326,6 @@ function onChangeVariationName() {
|
|
293
326
|
newVariationName.value = "";
|
294
327
|
}
|
295
328
|
|
296
|
-
function updateValues(values: FormFieldValue[]) {
|
297
|
-
let updatedValues: FormFieldValue[] | object = values;
|
298
|
-
|
299
|
-
if (!Array.isArray(props.values)) {
|
300
|
-
updatedValues = values.reduce((acc: AnyObject, v) => {
|
301
|
-
acc[v.name] = v.value;
|
302
|
-
return acc;
|
303
|
-
}, {});
|
304
|
-
}
|
305
|
-
|
306
|
-
emit("update:values", updatedValues);
|
307
|
-
}
|
308
|
-
|
309
329
|
function onRemoveVariation(name: string) {
|
310
330
|
if (!name) return;
|
311
331
|
|
@@ -1,70 +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
|
-
|
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>
|
68
70
|
</template>
|
69
71
|
<script setup lang="ts">
|
70
72
|
import { computed } from "vue";
|
@@ -75,10 +77,8 @@ import ActionTable from "../ActionTable.vue";
|
|
75
77
|
import { CollapsableFiltersSidebar } from "../Filters";
|
76
78
|
import { ActionToolbar } from "../Toolbars";
|
77
79
|
|
78
|
-
|
80
|
+
export interface ActionTableLayoutProps {
|
79
81
|
title?: string;
|
80
|
-
refreshButton: boolean;
|
81
|
-
showFilters: boolean;
|
82
82
|
controller: ActionController;
|
83
83
|
columns: TableColumn[];
|
84
84
|
filters?: FilterGroup[];
|
@@ -87,7 +87,17 @@ const props = defineProps<{
|
|
87
87
|
exporter?: () => Promise<void>;
|
88
88
|
panelTitleField?: string;
|
89
89
|
tableClass?: string;
|
90
|
-
|
90
|
+
refreshButton?: boolean;
|
91
|
+
showFilters?: boolean;
|
92
|
+
hideToolbar?: boolean;
|
93
|
+
selection?: "multiple" | "single";
|
94
|
+
}
|
95
|
+
|
96
|
+
const props = withDefaults(defineProps<ActionTableLayoutProps>(), {
|
97
|
+
title: "",
|
98
|
+
tableClass: "",
|
99
|
+
selection: "multiple"
|
100
|
+
});
|
91
101
|
|
92
102
|
const activeFilter = computed(() => props.controller.activeFilter.value);
|
93
103
|
const activeItem = computed(() => props.controller.activeItem.value);
|
@@ -1,33 +1,33 @@
|
|
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
|
-
|
2
|
+
<div class="dx-action-toolbar flex items-center">
|
3
|
+
<div class="flex-grow px-6">
|
4
|
+
<slot name="title">
|
5
|
+
<h4 v-if="title">
|
6
|
+
{{ title }}
|
7
|
+
</h4>
|
8
|
+
</slot>
|
9
|
+
</div>
|
10
|
+
<div class="py-3 flex items-center flex-nowrap">
|
11
|
+
<slot />
|
12
|
+
<RefreshButton
|
13
|
+
v-if="refreshButton"
|
14
|
+
:loading="loading"
|
15
|
+
@click="$emit('refresh')"
|
16
|
+
/>
|
17
|
+
<ExportButton
|
18
|
+
v-if="exporter"
|
19
|
+
:exporter="exporter"
|
20
|
+
class="ml-4"
|
21
|
+
/>
|
22
|
+
<ActionMenu
|
23
|
+
v-if="actions && actions.length > 0"
|
24
|
+
class="ml-4 dx-batch-actions"
|
25
|
+
:target="actionTarget"
|
26
|
+
:actions="actions"
|
27
|
+
/>
|
28
|
+
<slot name="after" />
|
29
|
+
</div>
|
30
|
+
</div>
|
31
31
|
</template>
|
32
32
|
<script setup lang="ts">
|
33
33
|
import { ActionTargetItem, AnyObject, ResourceAction } from "../../../types";
|