quasar-ui-danx 0.3.46 → 0.3.48

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.3.46",
3
+ "version": "0.3.48",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -11,7 +11,7 @@
11
11
  class="text-sm font-semibold"
12
12
  />
13
13
  <div
14
- v-if="!disable && !readonly"
14
+ v-if="!disable && !readonly && !hideControls"
15
15
  class="text-sm my-2"
16
16
  >
17
17
  <a
@@ -23,20 +23,21 @@
23
23
  class="ml-3 text-red-900"
24
24
  @click="clearUploadedFiles"
25
25
  >Clear</a>
26
- <input
27
- ref="file"
28
- class="hidden"
29
- type="file"
30
- multiple
31
- @change="onFilesSelected"
32
- >
33
26
  </div>
34
27
 
28
+ <input
29
+ ref="file"
30
+ class="hidden"
31
+ type="file"
32
+ multiple
33
+ @change="onFilesSelected"
34
+ >
35
+
35
36
  <div class="max-w-[50em] flex items-stretch justify-start">
36
37
  <FilePreview
37
38
  v-for="file in uploadedFiles"
38
39
  :key="'file-upload-' + file.id"
39
- class="w-32 m-2 cursor-pointer bg-gray-200"
40
+ class="w-32 h-32 m-2 cursor-pointer bg-gray-200"
40
41
  :class="{'border border-dashed border-blue-600': !uploadedFiles.length}"
41
42
  :image="file"
42
43
  :related-files="uploadedFiles"
@@ -46,7 +47,7 @@
46
47
  />
47
48
  <FilePreview
48
49
  v-if="!disable && !readonly"
49
- class="w-32 m-2 cursor-pointer border border-dashed border-blue-600"
50
+ class="w-32 h-32 m-2 cursor-pointer border border-dashed border-blue-600"
50
51
  disabled
51
52
  @click="$refs.file.click()"
52
53
  />
@@ -61,31 +62,33 @@
61
62
  </template>
62
63
 
63
64
  <script setup>
64
- import { onMounted } from "vue";
65
+ import { onMounted, watch } from "vue";
65
66
  import { useMultiFileUpload } from "../../../../helpers";
66
67
  import { FilePreview } from "../../../Utility";
67
68
  import FieldLabel from "./FieldLabel";
68
69
 
69
70
  const emit = defineEmits(["update:model-value"]);
70
71
  const props = defineProps({
71
- modelValue: {
72
- type: [Object, String],
73
- default: null
74
- },
75
- field: {
76
- type: Object,
77
- required: true
78
- },
79
- showName: Boolean,
80
- disable: Boolean,
81
- readonly: Boolean
72
+ modelValue: {
73
+ type: [Object, String],
74
+ default: null
75
+ },
76
+ field: {
77
+ type: Object,
78
+ required: true
79
+ },
80
+ showName: Boolean,
81
+ disable: Boolean,
82
+ readonly: Boolean,
83
+ hideControls: Boolean
82
84
  });
83
85
 
84
86
  const { onComplete, onDrop, onFilesSelected, uploadedFiles, clearUploadedFiles, onRemove } = useMultiFileUpload();
85
87
  onMounted(() => {
86
- if (props.modelValue) {
87
- uploadedFiles.value = props.modelValue;
88
- }
88
+ if (props.modelValue) {
89
+ uploadedFiles.value = props.modelValue;
90
+ }
89
91
  });
92
+ watch(() => props.modelValue, () => uploadedFiles.value = props.modelValue);
90
93
  onComplete(() => emit("update:model-value", uploadedFiles.value));
91
94
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div class="dx-text-field">
3
3
  <QInput
4
4
  v-if="!readonly"
5
5
  :data-dusk="'text-field-' + field?.id"
@@ -12,8 +12,9 @@
12
12
  :input-class="inputClass"
13
13
  :class="parentClass"
14
14
  stack-label
15
- :rows="rows"
16
- :type="type"
15
+ :autogrow="rows > 1"
16
+ :type="rows > 1 ? 'textarea' : type"
17
+ :input-style="rows ? {minHeight: rows * 1.5 + 'rem'} : {}"
17
18
  :model-value="modelValue"
18
19
  :debounce="debounce"
19
20
  @keydown.enter="$emit('submit')"
@@ -75,6 +75,7 @@
75
75
  :label="field.label || undefined"
76
76
  :no-label="noLabel"
77
77
  :show-name="showName"
78
+ :rows="field.type === 'TEXTAREA' ? 5 : 1"
78
79
  :clearable="field.clearable || clearable"
79
80
  :disable="disable"
80
81
  :readonly="readonly"
@@ -154,6 +155,7 @@ const FORM_FIELD_MAP = {
154
155
  INTEGER: IntegerField,
155
156
  NUMBER: NumberField,
156
157
  TEXT: TextField,
158
+ TEXTAREA: TextField,
157
159
  NO_INPUT: NoInputField,
158
160
  SINGLE_FILE: SingleFileField,
159
161
  MULTI_FILE: MultiFileField,
@@ -156,7 +156,7 @@ export function useListControls({
156
156
  }
157
157
 
158
158
  // Add a reactive isSaving property to each item (for performance reasons in checking saving state)
159
- data = data.map((item: any) => {
159
+ data = data?.map((item: any) => {
160
160
  // We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
161
161
  // components, and therefore reactivity is not responding to the new isSaving state
162
162
  const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
@@ -182,7 +182,7 @@ export function useListControls({
182
182
  const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
183
183
  setPagedItems({
184
184
  data,
185
- meta: { total: pagedItems.value.meta.total }
185
+ meta: { total: pagedItems.value?.meta?.total || 0 }
186
186
  });
187
187
 
188
188
  // Update the active item as well if it is set
@@ -205,8 +205,8 @@ export function useListControls({
205
205
 
206
206
  if (newItems && newItems.length > 0) {
207
207
  setPagedItems({
208
- data: [...pagedItems.value.data, ...newItems],
209
- meta: { total: pagedItems.value.meta.total }
208
+ data: [...pagedItems.value?.data || [], ...newItems],
209
+ meta: { total: pagedItems.value?.meta?.total || 0 }
210
210
  });
211
211
  return true;
212
212
  }
@@ -34,6 +34,7 @@
34
34
  <slot>{{ content }}</slot>
35
35
  </QCardSection>
36
36
  <div
37
+ v-if="!hideButtons"
37
38
  class="flex items-center justify-center px-6 py-4 border-t border-gray-300"
38
39
  >
39
40
  <div class="flex-grow text-right">
@@ -61,35 +62,36 @@ import { XIcon as CloseIcon } from "@heroicons/vue/outline";
61
62
 
62
63
  const emit = defineEmits(["update:model-value", "close"]);
63
64
  defineProps({
64
- modelValue: { type: [Boolean, Object], default: true },
65
- title: {
66
- type: String,
67
- default: ""
68
- },
69
- titleClass: {
70
- type: String,
71
- default: ""
72
- },
73
- subtitle: {
74
- type: String,
75
- default: ""
76
- },
77
- content: {
78
- type: String,
79
- default: ""
80
- },
81
- backdropDismiss: Boolean,
82
- maximized: Boolean,
83
- fullWidth: Boolean,
84
- fullHeight: Boolean,
85
- doneText: {
86
- type: String,
87
- default: "Done"
88
- }
65
+ modelValue: { type: [Boolean, Object], default: true },
66
+ title: {
67
+ type: String,
68
+ default: ""
69
+ },
70
+ titleClass: {
71
+ type: String,
72
+ default: ""
73
+ },
74
+ subtitle: {
75
+ type: String,
76
+ default: ""
77
+ },
78
+ content: {
79
+ type: String,
80
+ default: ""
81
+ },
82
+ backdropDismiss: Boolean,
83
+ hideButtons: Boolean,
84
+ maximized: Boolean,
85
+ fullWidth: Boolean,
86
+ fullHeight: Boolean,
87
+ doneText: {
88
+ type: String,
89
+ default: "Done"
90
+ }
89
91
  });
90
92
 
91
93
  function onClose() {
92
- emit("update:model-value", false);
93
- emit("close");
94
+ emit("update:model-value", false);
95
+ emit("close");
94
96
  }
95
97
  </script>
@@ -3,31 +3,31 @@ import { Ref, shallowRef, VNode } from "vue";
3
3
  import { FlashMessages } from "./FlashMessages";
4
4
 
5
5
  export type ActionTargetItem = {
6
- id: number | string;
7
- isSaving: Ref<boolean>;
8
- [key: string]: any;
6
+ id: number | string;
7
+ isSaving: Ref<boolean>;
8
+ [key: string]: any;
9
9
  };
10
10
  export type ActionTarget = ActionTargetItem[] | ActionTargetItem;
11
11
 
12
12
  export interface ActionOptions {
13
- name?: string;
14
- label?: string;
15
- menu?: boolean;
16
- batch?: boolean;
17
- category?: string;
18
- class?: string;
19
- debounce?: number;
20
- trigger?: (target: ActionTarget, input: any) => Promise<any>;
21
- vnode?: (target: ActionTarget) => VNode;
22
- enabled?: (target: object) => boolean;
23
- batchEnabled?: (targets: object[]) => boolean;
24
- optimistic?: (action: ActionOptions, target: object, input: any) => void;
25
- onAction?: (action: string | null | undefined, target: object, input: any) => Promise<any>;
26
- onBatchAction?: (action: string | null | undefined, targets: object[], input: any) => Promise<any>;
27
- onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
28
- onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
29
- onError?: (result: any, targets: ActionTarget, input: any) => any;
30
- onFinish?: (result: any, targets: ActionTarget, input: any) => any;
13
+ name?: string;
14
+ label?: string;
15
+ menu?: boolean;
16
+ batch?: boolean;
17
+ category?: string;
18
+ class?: string;
19
+ debounce?: number;
20
+ trigger?: (target: ActionTarget, input: any) => Promise<any>;
21
+ vnode?: (target: ActionTarget) => VNode;
22
+ enabled?: (target: object) => boolean;
23
+ batchEnabled?: (targets: object[]) => boolean;
24
+ optimistic?: (action: ActionOptions, target: object, input: any) => void;
25
+ onAction?: (action: string | null | undefined, target: object, input: any) => Promise<any>;
26
+ onBatchAction?: (action: string | null | undefined, targets: object[], input: any) => Promise<any>;
27
+ onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
28
+ onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
29
+ onError?: (result: any, targets: ActionTarget, input: any) => any;
30
+ onFinish?: (result: any, targets: ActionTarget, input: any) => any;
31
31
  }
32
32
 
33
33
  export const activeActionVnode: Ref = shallowRef(null);
@@ -40,173 +40,173 @@ export const activeActionVnode: Ref = shallowRef(null);
40
40
  * @param {ActionOptions | null} globalOptions
41
41
  */
42
42
  export function useActions(actions: ActionOptions[], globalOptions: ActionOptions | null = null) {
43
- const mappedActions = actions.map(action => {
44
- const mappedAction: ActionOptions = { ...globalOptions, ...action };
45
- if (mappedAction.debounce) {
46
- mappedAction.trigger = useDebounceFn((target, input) => performAction(mappedAction, target, input, true), mappedAction.debounce);
47
- } else if (!mappedAction.trigger) {
48
- mappedAction.trigger = (target, input) => performAction(mappedAction, target, input, true);
49
- }
50
- return mappedAction;
51
- });
52
-
53
- /**
54
- * Set the reactive saving state of a target
55
- */
56
- function setTargetSavingState(target: ActionTarget, saving: boolean) {
57
- if (Array.isArray(target)) {
58
- for (const t of target) {
59
- t.isSaving.value = saving;
60
- }
61
- } else {
62
- target.isSaving.value = saving;
63
- }
64
- }
65
-
66
- /**
67
- * Perform an action on a set of targets
68
- *
69
- * @param {string} name - can either be a string or an action object
70
- * @param {object[]|object} target - an array of targets or a single target object
71
- * @param {any} input - The input data to pass to the action handler
72
- * @param isTriggered - Whether the action was triggered by a trigger function
73
- */
74
- async function performAction(name: string | object, target: ActionTarget, input: any = null, isTriggered = false) {
75
- const action: ActionOptions | null | undefined = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
76
- if (!action) {
77
- throw new Error(`Unknown action: ${name}`);
78
- }
79
-
80
- // We always want to call the trigger function if it exists, unless it's already been triggered
81
- // This provides behavior like debounce and custom action resolution
82
- if (action.trigger && !isTriggered) {
83
- return action.trigger(target, input);
84
- }
85
-
86
- const vnode = action.vnode && action.vnode(target);
87
- let result: any;
88
-
89
- // Run the onStart handler if it exists and quit the operation if it returns false
90
- if (action.onStart) {
91
- if (!action.onStart(action, target, input)) {
92
- return;
93
- }
94
- }
95
-
96
- setTargetSavingState(target, true);
97
-
98
- // If additional input is required, first render the vnode and wait for the confirm or cancel action
99
- if (vnode) {
100
- // If the action requires an input, we set the activeActionVnode to the input component.
101
- // This will tell the ActionVnode to render the input component, and confirm or cancel the
102
- // action The confirm function has the input from the component passed and will resolve the promise
103
- // with the result of the action
104
- result = await new Promise((resolve, reject) => {
105
- activeActionVnode.value = {
106
- vnode,
107
- confirm: async (confirmInput: any) => {
108
- const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
109
-
110
- // Only resolve when we have a non-error response, so we can show the error message w/o
111
- // hiding the dialog / vnode
112
- if (result === undefined || result === true || result?.success) {
113
- resolve(result);
114
- }
115
- },
116
- cancel: resolve
117
- };
118
- });
119
-
120
- activeActionVnode.value = null;
121
- } else {
122
- result = await onConfirmAction(action, target, input);
123
- }
124
-
125
- setTargetSavingState(target, false);
126
-
127
- return result;
128
- }
129
-
130
- /**
131
- * Filter the list of actions based on the provided filters in key-value pairs
132
- * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
133
- *
134
- * @param filters
135
- * @returns {ActionOptions[]}
136
- */
137
- function filterActions(filters: object): ActionOptions[] {
138
- let filteredActions = [...mappedActions];
139
-
140
- for (const filter of Object.keys(filters)) {
141
- const filterValue = filters[filter];
142
- filteredActions = filteredActions.filter((a: object) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
143
- }
144
- return filteredActions;
145
- }
146
-
147
- return {
148
- actions: mappedActions,
149
- filterActions,
150
- performAction
151
- };
43
+ const mappedActions = actions.map(action => {
44
+ const mappedAction: ActionOptions = { ...globalOptions, ...action };
45
+ if (mappedAction.debounce) {
46
+ mappedAction.trigger = useDebounceFn((target, input) => performAction(mappedAction, target, input, true), mappedAction.debounce);
47
+ } else if (!mappedAction.trigger) {
48
+ mappedAction.trigger = (target, input) => performAction(mappedAction, target, input, true);
49
+ }
50
+ return mappedAction;
51
+ });
52
+
53
+ /**
54
+ * Set the reactive saving state of a target
55
+ */
56
+ function setTargetSavingState(target: ActionTarget, saving: boolean) {
57
+ if (Array.isArray(target)) {
58
+ for (const t of target) {
59
+ t.isSaving.value = saving;
60
+ }
61
+ } else if (target.isSaving) {
62
+ target.isSaving.value = saving;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Perform an action on a set of targets
68
+ *
69
+ * @param {string} name - can either be a string or an action object
70
+ * @param {object[]|object} target - an array of targets or a single target object
71
+ * @param {any} input - The input data to pass to the action handler
72
+ * @param isTriggered - Whether the action was triggered by a trigger function
73
+ */
74
+ async function performAction(name: string | object, target: ActionTarget, input: any = null, isTriggered = false) {
75
+ const action: ActionOptions | null | undefined = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
76
+ if (!action) {
77
+ throw new Error(`Unknown action: ${name}`);
78
+ }
79
+
80
+ // We always want to call the trigger function if it exists, unless it's already been triggered
81
+ // This provides behavior like debounce and custom action resolution
82
+ if (action.trigger && !isTriggered) {
83
+ return action.trigger(target, input);
84
+ }
85
+
86
+ const vnode = action.vnode && action.vnode(target);
87
+ let result: any;
88
+
89
+ // Run the onStart handler if it exists and quit the operation if it returns false
90
+ if (action.onStart) {
91
+ if (!action.onStart(action, target, input)) {
92
+ return;
93
+ }
94
+ }
95
+
96
+ setTargetSavingState(target, true);
97
+
98
+ // If additional input is required, first render the vnode and wait for the confirm or cancel action
99
+ if (vnode) {
100
+ // If the action requires an input, we set the activeActionVnode to the input component.
101
+ // This will tell the ActionVnode to render the input component, and confirm or cancel the
102
+ // action The confirm function has the input from the component passed and will resolve the promise
103
+ // with the result of the action
104
+ result = await new Promise((resolve, reject) => {
105
+ activeActionVnode.value = {
106
+ vnode,
107
+ confirm: async (confirmInput: any) => {
108
+ const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
109
+
110
+ // Only resolve when we have a non-error response, so we can show the error message w/o
111
+ // hiding the dialog / vnode
112
+ if (result === undefined || result === true || result?.success) {
113
+ resolve(result);
114
+ }
115
+ },
116
+ cancel: resolve
117
+ };
118
+ });
119
+
120
+ activeActionVnode.value = null;
121
+ } else {
122
+ result = await onConfirmAction(action, target, input);
123
+ }
124
+
125
+ setTargetSavingState(target, false);
126
+
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Filter the list of actions based on the provided filters in key-value pairs
132
+ * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
133
+ *
134
+ * @param filters
135
+ * @returns {ActionOptions[]}
136
+ */
137
+ function filterActions(filters: object): ActionOptions[] {
138
+ let filteredActions = [...mappedActions];
139
+
140
+ for (const filter of Object.keys(filters)) {
141
+ const filterValue = filters[filter];
142
+ filteredActions = filteredActions.filter((a: object) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
143
+ }
144
+ return filteredActions;
145
+ }
146
+
147
+ return {
148
+ actions: mappedActions,
149
+ filterActions,
150
+ performAction
151
+ };
152
152
  }
153
153
 
154
154
  async function onConfirmAction(action: ActionOptions, target: ActionTarget, input: any = null) {
155
- if (!action.onAction) {
156
- throw new Error("No onAction handler found for the selected action:" + action.name);
157
- }
158
-
159
- let result: any;
160
- try {
161
- if (Array.isArray(target)) {
162
- if (action.onBatchAction) {
163
- result = await action.onBatchAction(action.name, target, input);
164
- } else {
165
- result = { error: `Action ${action.name} does not support batch actions` };
166
- }
167
- } else {
168
- // If the action has an optimistic callback, we call it before the actual action to immediately
169
- // update the UI
170
- if (action.optimistic) {
171
- action.optimistic(action, target, input);
172
- }
173
-
174
- result = await action.onAction(action.name, target, input);
175
- }
176
- } catch (e) {
177
- console.error(e);
178
- result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
179
- }
180
-
181
- // If there is no return value or the result marks it as successful, we show a success message
182
- if (result === undefined || result === true || result?.success) {
183
- if (result?.success && Array.isArray(target)) {
184
- FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
185
- }
186
-
187
- if (action.onSuccess) {
188
- action.onSuccess(result, target, input);
189
- }
190
- } else {
191
- const errors = [];
192
- if (result.errors) {
193
- errors.push(...result.errors);
194
- } else if (result.error) {
195
- errors.push(typeof result.error === "string" ? result.error : result.error.message);
196
- } else {
197
- errors.push("An unknown error occurred. Please try again later.");
198
- }
199
-
200
- FlashMessages.combine("error", errors);
201
-
202
- if (action.onError) {
203
- action.onError(result, target, input);
204
- }
205
- }
206
-
207
- if (action.onFinish) {
208
- action.onFinish(result, target, input);
209
- }
210
-
211
- return result;
155
+ if (!action.onAction) {
156
+ throw new Error("No onAction handler found for the selected action:" + action.name);
157
+ }
158
+
159
+ let result: any;
160
+ try {
161
+ if (Array.isArray(target)) {
162
+ if (action.onBatchAction) {
163
+ result = await action.onBatchAction(action.name, target, input);
164
+ } else {
165
+ result = { error: `Action ${action.name} does not support batch actions` };
166
+ }
167
+ } else {
168
+ // If the action has an optimistic callback, we call it before the actual action to immediately
169
+ // update the UI
170
+ if (action.optimistic) {
171
+ action.optimistic(action, target, input);
172
+ }
173
+
174
+ result = await action.onAction(action.name, target, input);
175
+ }
176
+ } catch (e) {
177
+ console.error(e);
178
+ result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
179
+ }
180
+
181
+ // If there is no return value or the result marks it as successful, we show a success message
182
+ if (result === undefined || result === true || result?.success) {
183
+ if (result?.success && Array.isArray(target)) {
184
+ FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
185
+ }
186
+
187
+ if (action.onSuccess) {
188
+ action.onSuccess(result, target, input);
189
+ }
190
+ } else {
191
+ const errors = [];
192
+ if (result.errors) {
193
+ errors.push(...result.errors);
194
+ } else if (result.error) {
195
+ errors.push(typeof result.error === "string" ? result.error : result.error.message);
196
+ } else {
197
+ errors.push("An unknown error occurred. Please try again later.");
198
+ }
199
+
200
+ FlashMessages.combine("error", errors);
201
+
202
+ if (action.onError) {
203
+ action.onError(result, target, input);
204
+ }
205
+ }
206
+
207
+ if (action.onFinish) {
208
+ action.onFinish(result, target, input);
209
+ }
210
+
211
+ return result;
212
212
  }