quasar-ui-danx 0.3.22 → 0.4.2

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.
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";