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