quasar-ui-danx 0.3.22 → 0.4.1
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/.eslintrc.cjs +32 -30
- package/danx-local.sh +1 -1
- package/dist/danx.es.js +7490 -7519
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +5 -5
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +1 -1
- package/src/components/ActionTable/ActionTable.vue +64 -45
- package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
- package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
- package/src/components/ActionTable/Columns/index.ts +2 -0
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -34
- package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
- package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
- package/src/components/ActionTable/Form/form.d.ts +31 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
- package/src/components/ActionTable/TableSummaryRow.vue +4 -4
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
- package/src/components/ActionTable/Toolbars/index.ts +1 -0
- package/src/components/ActionTable/index.ts +1 -2
- package/src/components/ActionTable/listControls.ts +512 -385
- package/src/components/ActionTable/listHelpers.ts +46 -44
- package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
- package/src/components/Utility/Buttons/ExportButton.vue +1 -1
- package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
- package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
- package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
- package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
- package/src/helpers/actions.ts +197 -187
- package/src/styles/general.scss +12 -11
- package/src/styles/quasar-reset.scss +59 -11
- package/src/styles/themes/danx/action-table.scss +19 -0
- package/src/styles/themes/danx/buttons.scss +13 -0
- package/src/styles/themes/danx/forms.scss +5 -0
- package/src/styles/themes/danx/index.scss +3 -0
- package/src/styles/themes/danx/panels.scss +19 -0
- package/src/styles/themes/danx/sidebar.scss +3 -0
- package/src/styles/themes/danx/toolbar.scss +3 -0
- package/types/index.d.ts +1 -0
- package/src/styles/actions.scss +0 -10
@@ -67,6 +67,16 @@
|
|
67
67
|
:key="field.id"
|
68
68
|
:class="{ 'mt-4': index > 0 }"
|
69
69
|
>
|
70
|
+
<RenderVnode
|
71
|
+
v-if="field.vnode"
|
72
|
+
:vnode="field.vnode"
|
73
|
+
:readonly="readonly"
|
74
|
+
:disable="disable"
|
75
|
+
:show-name="showName"
|
76
|
+
:no-label="noLabel"
|
77
|
+
:model-value="getFieldValue(field.name)"
|
78
|
+
@update:model-value="onInput(field.name, $event)"
|
79
|
+
/>
|
70
80
|
<Component
|
71
81
|
:is="field.component"
|
72
82
|
:key="field.name + '-' + currentVariation"
|
@@ -104,72 +114,75 @@
|
|
104
114
|
/>
|
105
115
|
</div>
|
106
116
|
</template>
|
107
|
-
<script setup>
|
117
|
+
<script setup lang="ts">
|
108
118
|
import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@heroicons/vue/solid";
|
109
119
|
import { computed, ref } from "vue";
|
110
120
|
import { FlashMessages, incrementName, replace } from "../../../helpers";
|
111
121
|
import { TrashIcon as RemoveIcon } from "../../../svg";
|
112
|
-
import { ConfirmDialog } from "../../Utility";
|
122
|
+
import { ConfirmDialog, RenderVnode } from "../../Utility";
|
113
123
|
import {
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
124
|
+
BooleanField,
|
125
|
+
DateField,
|
126
|
+
DateRangeField,
|
127
|
+
IntegerField,
|
128
|
+
MultiFileField,
|
129
|
+
NumberField,
|
130
|
+
SingleFileField,
|
131
|
+
TextField,
|
132
|
+
WysiwygField
|
123
133
|
} from "./Fields";
|
134
|
+
import { Form, FormFieldValue } from "./form.d.ts";
|
124
135
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
emptyValue: {
|
141
|
-
type: [String, Number, Boolean],
|
142
|
-
default: undefined
|
143
|
-
},
|
144
|
-
canModifyVariations: Boolean
|
136
|
+
export interface Props {
|
137
|
+
values?: FormFieldValue[] | object;
|
138
|
+
form: Form;
|
139
|
+
noLabel?: boolean;
|
140
|
+
showName?: boolean;
|
141
|
+
disable?: boolean;
|
142
|
+
readonly?: boolean;
|
143
|
+
saving?: boolean;
|
144
|
+
emptyValue?: string | number | boolean;
|
145
|
+
canModifyVariations?: boolean;
|
146
|
+
}
|
147
|
+
|
148
|
+
const props = withDefaults(defineProps<Props>(), {
|
149
|
+
values: null,
|
150
|
+
emptyValue: undefined
|
145
151
|
});
|
146
152
|
|
153
|
+
const emit = defineEmits(["update:values"]);
|
154
|
+
|
147
155
|
const FORM_FIELD_MAP = {
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
156
|
+
BOOLEAN: BooleanField,
|
157
|
+
DATE: DateField,
|
158
|
+
DATE_RANGE: DateRangeField,
|
159
|
+
INTEGER: IntegerField,
|
160
|
+
NUMBER: NumberField,
|
161
|
+
TEXT: TextField,
|
162
|
+
SINGLE_FILE: SingleFileField,
|
163
|
+
MULTI_FILE: MultiFileField,
|
164
|
+
WYSIWYG: WysiwygField
|
157
165
|
};
|
158
166
|
|
159
167
|
const mappedFields = props.form.fields.map((field) => ({
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
168
|
+
placeholder: `Enter ${field.label}`,
|
169
|
+
...field,
|
170
|
+
component: field.component || FORM_FIELD_MAP[field.type],
|
171
|
+
default: field.type === "BOOLEAN" ? false : ""
|
164
172
|
}));
|
165
173
|
|
174
|
+
const fieldResponses = computed(() => {
|
175
|
+
if (!props.values) return [];
|
176
|
+
if (Array.isArray(props.values)) return props.values;
|
177
|
+
return Object.entries(props.values).map(([name, value]) => ({ name, value, variation: "" }));
|
178
|
+
});
|
166
179
|
const variationNames = computed(() => {
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
180
|
+
const names = [...new Set(fieldResponses.value.map(v => v.variation))].sort();
|
181
|
+
// Always guarantee that we show the default variation
|
182
|
+
if (names.length === 0) {
|
183
|
+
names.push("");
|
184
|
+
}
|
185
|
+
return names;
|
173
186
|
});
|
174
187
|
|
175
188
|
const currentVariation = ref(variationNames.value[0] || "");
|
@@ -178,89 +191,101 @@ const variationToEdit = ref(false);
|
|
178
191
|
const variationToDelete = ref("");
|
179
192
|
const canAddVariation = computed(() => props.canModifyVariations && !props.readonly && !props.disable && variationNames.value.length < props.form.variations);
|
180
193
|
|
181
|
-
function getFieldResponse(name, variation) {
|
182
|
-
|
183
|
-
|
194
|
+
function getFieldResponse(name, variation: string = undefined) {
|
195
|
+
if (!fieldResponses.value) return undefined;
|
196
|
+
return fieldResponses.value.find((fr: FormFieldValue) => fr.variation === (variation !== undefined ? variation : currentVariation.value) && fr.name === name);
|
184
197
|
}
|
185
198
|
function getFieldValue(name) {
|
186
|
-
|
199
|
+
return getFieldResponse(name)?.value;
|
187
200
|
}
|
188
201
|
function onInput(name, value) {
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
202
|
+
const fieldResponse = getFieldResponse(name);
|
203
|
+
const newFieldResponse = {
|
204
|
+
name,
|
205
|
+
variation: currentVariation.value || "",
|
206
|
+
value: value === undefined ? props.emptyValue : value
|
207
|
+
};
|
208
|
+
const newValues = replace(fieldResponses.value, fieldResponse, newFieldResponse, true);
|
209
|
+
updateValues(newValues);
|
197
210
|
}
|
198
211
|
|
199
212
|
function createVariation(variation) {
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
213
|
+
return props.form.fields.map((field) => ({
|
214
|
+
variation,
|
215
|
+
name: field.name,
|
216
|
+
value: field.type === "BOOLEAN" ? false : null
|
217
|
+
}));
|
205
218
|
}
|
206
219
|
|
207
220
|
function onAddVariation() {
|
208
|
-
|
209
|
-
|
221
|
+
if (props.saving) return;
|
222
|
+
let newValues = [...fieldResponses.value];
|
210
223
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
224
|
+
if (newValues.length === 0) {
|
225
|
+
newValues = createVariation("");
|
226
|
+
}
|
227
|
+
const previousName = variationNames.value[variationNames.value.length - 1];
|
228
|
+
const newName = incrementName(!previousName ? "1" : previousName);
|
229
|
+
const newVariation = createVariation(newName);
|
230
|
+
updateValues([...newValues, ...newVariation]);
|
231
|
+
currentVariation.value = newName;
|
219
232
|
}
|
220
233
|
|
221
234
|
function onChangeVariationName() {
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
235
|
+
if (!newVariationName.value) return;
|
236
|
+
if (variationNames.value.includes(newVariationName.value)) {
|
237
|
+
FlashMessages.error("Variation name already exists");
|
238
|
+
return;
|
239
|
+
}
|
240
|
+
const newValues = fieldResponses.value.map((v) => {
|
241
|
+
if (v.variation === variationToEdit.value) {
|
242
|
+
return { ...v, variation: newVariationName.value };
|
243
|
+
}
|
244
|
+
return v;
|
245
|
+
});
|
246
|
+
updateValues(newValues);
|
247
|
+
|
248
|
+
currentVariation.value = newVariationName.value;
|
249
|
+
variationToEdit.value = false;
|
250
|
+
newVariationName.value = "";
|
251
|
+
}
|
252
|
+
|
253
|
+
function updateValues(values: FormFieldValue[]) {
|
254
|
+
let updatedValues: FormFieldValue[] | object = values;
|
234
255
|
|
235
|
-
|
236
|
-
|
237
|
-
|
256
|
+
if (!Array.isArray(props.values)) {
|
257
|
+
updatedValues = values.reduce((acc, v) => {
|
258
|
+
acc[v.name] = v.value;
|
259
|
+
return acc;
|
260
|
+
}, {});
|
261
|
+
}
|
262
|
+
emit("update:values", updatedValues);
|
238
263
|
}
|
239
264
|
|
240
|
-
function onRemoveVariation(name) {
|
241
|
-
|
265
|
+
function onRemoveVariation(name: string) {
|
266
|
+
if (!name) return;
|
242
267
|
|
243
|
-
|
244
|
-
|
268
|
+
const newValues = fieldResponses.value.filter((v) => v.variation !== name);
|
269
|
+
updateValues(newValues);
|
245
270
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
271
|
+
if (currentVariation.value === name) {
|
272
|
+
currentVariation.value = variationNames.value[0];
|
273
|
+
}
|
274
|
+
variationToDelete.value = "";
|
250
275
|
}
|
251
276
|
|
252
277
|
function isVariationFormComplete(variation) {
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
278
|
+
const requiredGroups = {};
|
279
|
+
return props.form.fields.filter(r => r.required || r.required_group).every((field) => {
|
280
|
+
const fieldResponse = getFieldResponse(field.name, variation);
|
281
|
+
const hasValue = !!fieldResponse && fieldResponse.value !== null;
|
282
|
+
if (field.required_group) {
|
283
|
+
// This required group has already been satisfied
|
284
|
+
if (requiredGroups[field.required_group]) return true;
|
285
|
+
return requiredGroups[field.required_group] = hasValue;
|
286
|
+
} else {
|
287
|
+
return hasValue;
|
288
|
+
}
|
289
|
+
});
|
265
290
|
}
|
266
291
|
</script>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { VNode } from "vue";
|
2
|
+
|
3
|
+
export interface FormFieldOption {
|
4
|
+
value: string;
|
5
|
+
label: string;
|
6
|
+
}
|
7
|
+
|
8
|
+
export interface FormField {
|
9
|
+
id?: string;
|
10
|
+
type?: string;
|
11
|
+
name: string;
|
12
|
+
label: string;
|
13
|
+
vnode?: ((props) => VNode | any);
|
14
|
+
component?: any;
|
15
|
+
required?: boolean;
|
16
|
+
required_group?: string;
|
17
|
+
options?: FormFieldOption[];
|
18
|
+
}
|
19
|
+
|
20
|
+
export interface Form {
|
21
|
+
id?: string;
|
22
|
+
name?: string;
|
23
|
+
variations?: number;
|
24
|
+
fields: FormField[];
|
25
|
+
}
|
26
|
+
|
27
|
+
export interface FormFieldValue {
|
28
|
+
name: string,
|
29
|
+
value: any,
|
30
|
+
variation?: string
|
31
|
+
}
|
@@ -1,10 +1,94 @@
|
|
1
1
|
<template>
|
2
|
-
<div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full
|
2
|
+
<div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full">
|
3
3
|
<slot name="top" />
|
4
|
-
<slot name="toolbar"
|
4
|
+
<slot name="toolbar">
|
5
|
+
<ActionToolbar
|
6
|
+
:refresh-button="refreshButton"
|
7
|
+
:actions="actions?.filter(a => a.batch)"
|
8
|
+
:action-target="controller.selectedRows.value"
|
9
|
+
:exporter="controller.exportList"
|
10
|
+
@refresh="controller.refreshAll"
|
11
|
+
/>
|
12
|
+
</slot>
|
5
13
|
<div class="flex flex-nowrap flex-grow overflow-hidden w-full">
|
6
|
-
<slot name="
|
7
|
-
|
14
|
+
<slot name="filter-fields">
|
15
|
+
<CollapsableFiltersSidebar
|
16
|
+
v-if="activeFilter"
|
17
|
+
:name="controller.name"
|
18
|
+
:show-filters="showFilters"
|
19
|
+
:filters="filters"
|
20
|
+
:active-filter="activeFilter"
|
21
|
+
@update:active-filter="controller.setActiveFilter"
|
22
|
+
/>
|
23
|
+
</slot>
|
24
|
+
<slot>
|
25
|
+
<ActionTable
|
26
|
+
class="flex-grow"
|
27
|
+
:pagination="controller.pagination.value"
|
28
|
+
:selected-rows="controller.selectedRows.value"
|
29
|
+
:label="controller.label"
|
30
|
+
:name="controller.name"
|
31
|
+
:class="tableClass"
|
32
|
+
:summary="controller.summary.value"
|
33
|
+
:loading-list="controller.isLoadingList.value"
|
34
|
+
:loading-summary="controller.isLoadingSummary.value"
|
35
|
+
:paged-items="controller.pagedItems.value"
|
36
|
+
:columns="columns"
|
37
|
+
@update:selected-rows="controller.setSelectedRows"
|
38
|
+
@update:pagination="controller.setPagination"
|
39
|
+
/>
|
40
|
+
</slot>
|
41
|
+
<slot name="panels">
|
42
|
+
<PanelsDrawer
|
43
|
+
v-if="activeItem && panels"
|
44
|
+
:title="panelTitle"
|
45
|
+
:model-value="activePanel"
|
46
|
+
:panels="panels"
|
47
|
+
@update:model-value="panel => controller.activatePanel(activeItem, panel)"
|
48
|
+
@close="controller.setActiveItem(null)"
|
49
|
+
>
|
50
|
+
<template #controls>
|
51
|
+
<PreviousNextControls
|
52
|
+
:is-loading="controller.isLoadingList.value"
|
53
|
+
@next="controller.getNextItem"
|
54
|
+
/>
|
55
|
+
</template>
|
56
|
+
</PanelsDrawer>
|
57
|
+
</slot>
|
8
58
|
</div>
|
9
59
|
</div>
|
10
60
|
</template>
|
61
|
+
<script setup lang="ts">
|
62
|
+
import { computed } from "vue";
|
63
|
+
import { ActionOptions } from "../../../helpers";
|
64
|
+
import { PanelsDrawer } from "../../PanelsDrawer";
|
65
|
+
import { PreviousNextControls } from "../../Utility";
|
66
|
+
import ActionTable from "../ActionTable";
|
67
|
+
import { CollapsableFiltersSidebar } from "../Filters";
|
68
|
+
import { ActionController, ActionPanel, FilterField } from "../listControls";
|
69
|
+
import { TableColumn } from "../tableColumns";
|
70
|
+
import { ActionToolbar } from "../Toolbars";
|
71
|
+
|
72
|
+
const props = defineProps<{
|
73
|
+
refreshButton: boolean,
|
74
|
+
showFilters: boolean,
|
75
|
+
controller: ActionController,
|
76
|
+
columns: TableColumn[],
|
77
|
+
filters?: FilterField[],
|
78
|
+
panels?: ActionPanel[],
|
79
|
+
actions?: ActionOptions[],
|
80
|
+
exporter?: () => Promise<void>,
|
81
|
+
titleField?: string,
|
82
|
+
tableClass?: string
|
83
|
+
}>();
|
84
|
+
|
85
|
+
const activeFilter = computed(() => props.controller.activeFilter.value);
|
86
|
+
const activeItem = computed(() => props.controller.activeItem.value);
|
87
|
+
const activePanel = computed(() => props.controller.activePanel.value || "");
|
88
|
+
const panelTitle = computed(() => {
|
89
|
+
if (activeItem.value) {
|
90
|
+
return activeItem.value[props.titleField || "title"] || activeItem.value.label || activeItem.value.name;
|
91
|
+
}
|
92
|
+
return null;
|
93
|
+
});
|
94
|
+
</script>
|
@@ -1,12 +1,12 @@
|
|
1
1
|
<template>
|
2
2
|
<QTr
|
3
|
-
class="sticky-column-1 transition-all sticky-row"
|
4
|
-
:class="{'
|
3
|
+
class="dx-table-summary-tr sticky-column-1 transition-all sticky-row"
|
4
|
+
:class="{'has-selection': selectedCount, 'is-loading': loading}"
|
5
5
|
>
|
6
6
|
<QTd
|
7
7
|
:colspan="stickyColspan"
|
8
|
-
class="
|
9
|
-
:class="{'
|
8
|
+
class="dx-table-summary-td transition-all"
|
9
|
+
:class="{'has-selection': selectedCount}"
|
10
10
|
>
|
11
11
|
<div class="flex flex-nowrap items-center">
|
12
12
|
<div
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="flex items-center">
|
3
|
+
<div class="flex-grow px-6">
|
4
|
+
<slot name="title">
|
5
|
+
<h2 v-if="title">
|
6
|
+
{{ title }}
|
7
|
+
</h2>
|
8
|
+
</slot>
|
9
|
+
</div>
|
10
|
+
<div class="py-3 px-6 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.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
|
+
</template>
|
32
|
+
<script setup lang="ts">
|
33
|
+
import { ActionOptions, ActionTargetItem } from "../../../helpers";
|
34
|
+
import { ExportButton, RefreshButton } from "../../Utility";
|
35
|
+
import ActionMenu from "../ActionMenu";
|
36
|
+
|
37
|
+
defineEmits(["refresh"]);
|
38
|
+
defineProps<{
|
39
|
+
title?: string,
|
40
|
+
actions: ActionOptions[],
|
41
|
+
actionTarget?: ActionTargetItem[],
|
42
|
+
refreshButton?: boolean,
|
43
|
+
loading?: boolean,
|
44
|
+
exporter?: () => void
|
45
|
+
}>();
|
46
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as ActionToolbar } from "./ActionToolbar.vue";
|
@@ -5,9 +5,8 @@ export * from "./Layouts";
|
|
5
5
|
export * from "./listControls";
|
6
6
|
export * from "./listHelpers";
|
7
7
|
export * from "./tableColumns";
|
8
|
+
export * from "./Toolbars";
|
8
9
|
export { default as ActionMenu } from "./ActionMenu.vue";
|
9
10
|
export { default as ActionTable } from "./ActionTable.vue";
|
10
|
-
export { default as ActionTableColumn } from "./ActionTableColumn.vue";
|
11
|
-
export { default as ActionTableHeaderColumn } from "./ActionTableHeaderColumn.vue";
|
12
11
|
export { default as EmptyTableState } from "./EmptyTableState.vue";
|
13
12
|
export { default as TableSummaryRow } from "./TableSummaryRow.vue";
|