quasar-ui-danx 0.3.21 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/.eslintrc.cjs +32 -30
  2. package/danx-local.sh +1 -1
  3. package/dist/danx.es.js +7490 -7520
  4. package/dist/danx.es.js.map +1 -1
  5. package/dist/danx.umd.js +5 -5
  6. package/dist/danx.umd.js.map +1 -1
  7. package/dist/style.css +1 -1
  8. package/package.json +1 -1
  9. package/src/components/ActionTable/ActionMenu.vue +1 -1
  10. package/src/components/ActionTable/ActionTable.vue +64 -45
  11. package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
  12. package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
  13. package/src/components/ActionTable/Columns/index.ts +2 -0
  14. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
  15. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
  16. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -35
  17. package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
  18. package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
  19. package/src/components/ActionTable/Form/form.d.ts +31 -0
  20. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
  21. package/src/components/ActionTable/TableSummaryRow.vue +4 -4
  22. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
  23. package/src/components/ActionTable/Toolbars/index.ts +1 -0
  24. package/src/components/ActionTable/index.ts +1 -2
  25. package/src/components/ActionTable/listControls.ts +512 -385
  26. package/src/components/ActionTable/listHelpers.ts +46 -44
  27. package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
  28. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
  29. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
  30. package/src/components/Utility/Buttons/ExportButton.vue +1 -1
  31. package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
  32. package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
  33. package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
  34. package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
  35. package/src/helpers/actions.ts +197 -187
  36. package/src/styles/general.scss +12 -11
  37. package/src/styles/quasar-reset.scss +59 -11
  38. package/src/styles/themes/danx/action-table.scss +19 -0
  39. package/src/styles/themes/danx/buttons.scss +13 -0
  40. package/src/styles/themes/danx/forms.scss +5 -0
  41. package/src/styles/themes/danx/index.scss +3 -0
  42. package/src/styles/themes/danx/panels.scss +19 -0
  43. package/src/styles/themes/danx/sidebar.scss +3 -0
  44. package/src/styles/themes/danx/toolbar.scss +3 -0
  45. package/types/index.d.ts +1 -0
  46. 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
- BooleanField,
115
- DateField,
116
- DateRangeField,
117
- IntegerField,
118
- MultiFileField,
119
- NumberField,
120
- SingleFileField,
121
- TextField,
122
- WysiwygField
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
- const emit = defineEmits(["update:values"]);
126
- const props = defineProps({
127
- values: {
128
- type: Array,
129
- default: null
130
- },
131
- form: {
132
- type: Object,
133
- required: true
134
- },
135
- noLabel: Boolean,
136
- showName: Boolean,
137
- disable: Boolean,
138
- readonly: Boolean,
139
- saving: Boolean,
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
- BOOLEAN: BooleanField,
149
- DATE: DateField,
150
- DATE_RANGE: DateRangeField,
151
- INTEGER: IntegerField,
152
- NUMBER: NumberField,
153
- TEXT: TextField,
154
- SINGLE_FILE: SingleFileField,
155
- MULTI_FILE: MultiFileField,
156
- WYSIWYG: WysiwygField
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
- placeholder: `Enter ${field.label}`,
161
- ...field,
162
- component: FORM_FIELD_MAP[field.type],
163
- default: field.type === "BOOLEAN" ? false : ""
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
- const names = [...new Set(props.values.map(v => v.variation))].sort();
168
- // Always guarantee that we show the default variation
169
- if (names.length === 0) {
170
- names.push("");
171
- }
172
- return names;
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
- if (!props.values) return undefined;
183
- return props.values.find((v) => v.variation === (variation !== undefined ? variation : currentVariation.value) && v.name === name);
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
- return getFieldResponse(name)?.value;
199
+ return getFieldResponse(name)?.value;
187
200
  }
188
201
  function onInput(name, value) {
189
- const fieldResponse = getFieldResponse(name);
190
- const newFieldResponse = {
191
- name,
192
- variation: currentVariation.value || "",
193
- value: value === undefined ? props.emptyValue : value
194
- };
195
- const newValues = replace(props.values, fieldResponse, newFieldResponse, true);
196
- emit("update:values", newValues);
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
- return props.form.fields.map((field) => ({
201
- variation,
202
- name: field.name,
203
- value: field.type === "BOOLEAN" ? false : null
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
- if (props.saving) return;
209
- let newValues = [...props.values];
221
+ if (props.saving) return;
222
+ let newValues = [...fieldResponses.value];
210
223
 
211
- if (newValues.length === 0) {
212
- newValues = createVariation("");
213
- }
214
- const previousName = variationNames.value[variationNames.value.length - 1];
215
- const newName = incrementName(!previousName ? "1" : previousName);
216
- const newVariation = createVariation(newName);
217
- emit("update:values", [...newValues, ...newVariation]);
218
- currentVariation.value = newName;
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
- if (!newVariationName.value) return;
223
- if (variationNames.value.includes(newVariationName.value)) {
224
- FlashMessages.error("Variation name already exists");
225
- return;
226
- }
227
- const newValues = props.values.map((v) => {
228
- if (v.variation === variationToEdit.value) {
229
- return { ...v, variation: newVariationName.value };
230
- }
231
- return v;
232
- });
233
- emit("update:values", newValues);
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
- currentVariation.value = newVariationName.value;
236
- variationToEdit.value = false;
237
- newVariationName.value = "";
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
- if (!name) return;
265
+ function onRemoveVariation(name: string) {
266
+ if (!name) return;
242
267
 
243
- const newValues = props.values.filter((v) => v.variation !== name);
244
- emit("update:values", newValues);
268
+ const newValues = fieldResponses.value.filter((v) => v.variation !== name);
269
+ updateValues(newValues);
245
270
 
246
- if (currentVariation.value === name) {
247
- currentVariation.value = variationNames.value[0];
248
- }
249
- variationToDelete.value = "";
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
- const requiredGroups = {};
254
- return props.form.fields.filter(r => r.required || r.required_group).every((field) => {
255
- const fieldResponse = getFieldResponse(field.name, variation);
256
- const hasValue = !!fieldResponse && fieldResponse.value !== null;
257
- if (field.required_group) {
258
- // This required group has already been satisfied
259
- if (requiredGroups[field.required_group]) return true;
260
- return requiredGroups[field.required_group] = hasValue;
261
- } else {
262
- return hasValue;
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 bg-white">
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="filters" />
7
- <slot />
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="{'!bg-gray-100': !selectedCount, '!bg-blue-600 text-white selected': selectedCount, 'opacity-50': loading}"
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="font-bold transition-all"
9
- :class="{'!bg-gray-100 !pl-5': !selectedCount, '!bg-blue-600 text-white !pl-4': selectedCount}"
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";