quasar-ui-danx 0.3.22 → 0.4.2

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