quasar-ui-danx 0.4.32 → 0.4.35

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.32",
3
+ "version": "0.4.35",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div class="inline-block relative">
3
3
  <div
4
- contenteditable
5
- class="relative inline-block transition duration-300 outline-none outline-offset-0 border-none focus:outline-4 hover:outline-4 rounded-sm z-10 min-w-10 min-h-10"
4
+ :contenteditable="readonly ? 'false' : 'true'"
5
+ class="relative inline-block transition duration-300 outline-none outline-offset-0 border-none rounded-sm z-10 min-w-10 min-h-10"
6
6
  :style="{minWidth, minHeight}"
7
7
  :class="contentClass"
8
8
  @input="onInput"
@@ -12,7 +12,7 @@
12
12
  {{ text }}
13
13
  </div>
14
14
  <div
15
- v-if="!text && placeholder && !hasFocus"
15
+ v-if="!text && placeholder && !hasFocus && !readonly"
16
16
  ref="placeholderDiv"
17
17
  class="text-gray-600 absolute-top-left whitespace-nowrap z-1 pointer-events-none"
18
18
  >
@@ -31,6 +31,7 @@ const props = withDefaults(defineProps<{
31
31
  color?: string;
32
32
  textColor?: string;
33
33
  debounceDelay?: number;
34
+ readonly?: boolean;
34
35
  placeholder?: string;
35
36
  }>(), {
36
37
  modelValue: "",
@@ -43,16 +44,16 @@ const props = withDefaults(defineProps<{
43
44
  });
44
45
 
45
46
  const text = ref(props.modelValue);
46
- const placeholderDiv = ref(null);
47
- const minWidth = ref(0);
48
- const minHeight = ref(0);
47
+ const placeholderDiv = ref<Element | null>(null);
48
+ const minWidth = ref<string>("0");
49
+ const minHeight = ref<string>("0");
49
50
  const hasFocus = ref(false);
50
51
 
51
52
  onMounted(() => {
52
53
  // Set the min-width to the width of the placeholder
53
54
  if (placeholderDiv.value) {
54
- minWidth.value = placeholderDiv.value.offsetWidth + "px";
55
- minHeight.value = placeholderDiv.value.offsetHeight + "px";
55
+ minWidth.value = placeholderDiv.value?.offsetWidth + "px";
56
+ minHeight.value = placeholderDiv.value?.offsetHeight + "px";
56
57
  }
57
58
  });
58
59
 
@@ -72,9 +73,12 @@ function onInput(e) {
72
73
  }
73
74
 
74
75
  const contentClass = computed(() => [
75
- `hover:bg-${props.color} focus:bg-${props.color}`,
76
- `hover:text-${props.textColor} focus:text-${props.textColor}`,
77
- `hover:outline-${props.color} focus:outline-${props.color}`,
76
+ ...(props.readonly ? [] : [
77
+ `hover:bg-${props.color} focus:bg-${props.color}`,
78
+ `hover:text-${props.textColor} focus:text-${props.textColor}`,
79
+ `hover:outline-${props.color} focus:outline-${props.color}`,
80
+ "focus:outline-4 hover:outline-4"
81
+ ]),
78
82
  text.value ? "" : "!bg-none"
79
83
  ]);
80
84
  </script>
@@ -22,6 +22,7 @@
22
22
  label=""
23
23
  :input-class="{'is-hidden': !isShowing, [inputClass]: true}"
24
24
  class="max-w-full dx-select-field"
25
+ :class="selectClass"
25
26
  @filter="onFilter"
26
27
  @clear="onClear"
27
28
  @popup-show="onShow"
@@ -82,6 +83,7 @@ export interface Props extends QSelectProps {
82
83
  selectionLabel?: string | ((option) => string);
83
84
  chipLimit?: number;
84
85
  inputClass?: string;
86
+ selectClass?: string;
85
87
  selectionClass?: string;
86
88
  options?: unknown[];
87
89
  filterable?: boolean;
@@ -97,6 +99,7 @@ const props = withDefaults(defineProps<Props>(), {
97
99
  selectionLabel: null,
98
100
  chipLimit: 3,
99
101
  inputClass: "",
102
+ selectClass: "",
100
103
  selectionClass: "",
101
104
  options: () => [],
102
105
  filterFn: null,
@@ -151,7 +154,7 @@ const selectedValue = computed(() => {
151
154
  } else {
152
155
  if (props.modelValue === null) return "__null__";
153
156
 
154
- if (props.selectByObject) return props.modelValue.value || props.modelValue.id;
157
+ if (props.selectByObject) return props.modelValue?.value || props.modelValue?.id;
155
158
 
156
159
  return props.modelValue;
157
160
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="flex items-stretch flex-nowrap gap-x-4">
3
3
  <QBtn
4
- class="bg-green-900 px-4"
4
+ :class="createClass"
5
5
  :loading="loading"
6
6
  @click="$emit('create')"
7
7
  >
@@ -11,6 +11,14 @@
11
11
  />
12
12
  {{ createText }}
13
13
  </QBtn>
14
+ <ShowHideButton
15
+ v-if="showEdit"
16
+ v-model="editing"
17
+ :disable="!canEdit"
18
+ :label="editText"
19
+ :class="editClass"
20
+ :show-icon="EditIcon"
21
+ />
14
22
  <SelectField
15
23
  v-model="selected"
16
24
  class="flex-grow"
@@ -19,17 +27,10 @@
19
27
  :select-by-object="selectByObject"
20
28
  :option-label="optionLabel"
21
29
  />
22
- <ShowHideButton
23
- v-if="showEdit"
24
- v-model="editing"
25
- :disable="!canEdit"
26
- :label="editText"
27
- class="bg-sky-800 w-1/5"
28
- />
29
30
  </div>
30
31
  </template>
31
32
  <script setup lang="ts">
32
- import { FaSolidPlus as CreateIcon } from "danx-icon";
33
+ import { FaSolidPencil as EditIcon, FaSolidPlus as CreateIcon } from "danx-icon";
33
34
  import { QSelectOption } from "quasar";
34
35
  import { ActionTargetItem } from "../../../../types";
35
36
  import { ShowHideButton } from "../../../Utility/Buttons";
@@ -47,10 +48,14 @@ withDefaults(defineProps<{
47
48
  optionLabel?: string;
48
49
  createText?: string;
49
50
  editText?: string;
51
+ createClass?: string;
52
+ editClass?: string;
50
53
  clearable?: boolean;
51
54
  }>(), {
52
55
  optionLabel: "label",
53
- createText: "Create",
54
- editText: "Edit"
56
+ createText: "",
57
+ editText: "",
58
+ createClass: "bg-green-900 px-4",
59
+ editClass: "bg-sky-800 px-4"
55
60
  });
56
61
  </script>
@@ -73,7 +73,7 @@ export function useControls(name: string, options: ListControlsOptions): ListCon
73
73
 
74
74
  async function loadList() {
75
75
  if (!isInitialized || options.isListEnabled === false) return;
76
- // isLoadingList.value = true;
76
+ isLoadingList.value = true;
77
77
  try {
78
78
  setPagedItems(await options.routes.list(pager.value));
79
79
  } catch (e) {
@@ -1,19 +1,23 @@
1
1
  <template>
2
2
  <div
3
- :class="{'cursor-move': !showHandle}"
4
- draggable="true"
5
- @dragstart.stop="dragAndDrop.dragStart"
6
- @dragend="dragAndDrop.dragEnd"
3
+ :class="{'cursor-move': !showHandle && !disabled}"
4
+ :draggable="disabled ? undefined : 'true'"
5
+ @dragstart.stop="disabled ? null : dragAndDrop.dragStart"
6
+ @dragend="disabled ? null : dragAndDrop.dragEnd"
7
7
  >
8
8
  <div :class="contentClass">
9
9
  <div
10
10
  v-if="showHandle"
11
- class="cursor-move"
12
- :class="handleClass"
11
+ :class="resolvedHandleClass"
13
12
  >
13
+ <div
14
+ v-if="disabled"
15
+ :class="handleSize"
16
+ />
14
17
  <SvgImg
18
+ v-else
15
19
  :svg="DragHandleIcon"
16
- class="w-4 h-4"
20
+ :class="handleSize"
17
21
  alt="drag-handle"
18
22
  />
19
23
  </div>
@@ -24,6 +28,7 @@
24
28
  </div>
25
29
  </template>
26
30
  <script setup lang="ts">
31
+ import { computed } from "vue";
27
32
  import { DragHandleDotsIcon as DragHandleIcon } from "../../svg";
28
33
  import { SvgImg } from "../Utility";
29
34
  import { ListDragAndDrop } from "./listDragAndDrop";
@@ -37,14 +42,21 @@ const props = withDefaults(defineProps<{
37
42
  changeDropZone?: boolean;
38
43
  contentClass?: string | object;
39
44
  handleClass?: string | object;
45
+ handleSize?: string;
40
46
  listItems?: any[];
47
+ disabled?: boolean;
41
48
  }>(), {
42
49
  direction: "vertical",
50
+ handleSize: "w-4 h-4",
43
51
  handleClass: "",
44
52
  contentClass: "flex flex-nowrap items-center",
45
53
  listItems: () => []
46
54
  });
47
55
 
56
+ const resolvedHandleClass = computed(() => ({
57
+ "cursor-move": !props.disabled,
58
+ ...(typeof props.handleClass === "string" ? { [props.handleClass]: true } : props.handleClass)
59
+ }));
48
60
  const dragAndDrop = new ListDragAndDrop()
49
61
  .setDropZone(props.dropZone)
50
62
  .setOptions({ showPlaceholder: true, allowDropZoneChange: props.changeDropZone, direction: props.direction })
@@ -1,7 +1,7 @@
1
1
  import { useDebounceFn } from "@vueuse/core";
2
2
  import { FaSolidCopy as CopyIcon, FaSolidPencil as EditIcon, FaSolidTrash as DeleteIcon } from "danx-icon";
3
3
  import { uid } from "quasar";
4
- import { h, isReactive, Ref, shallowRef } from "vue";
4
+ import { h, isReactive, Ref, shallowReactive, shallowRef } from "vue";
5
5
  import { ConfirmActionDialog, CreateNewWithNameDialog } from "../components";
6
6
  import type { ActionGlobalOptions, ActionOptions, ActionTarget, ListController, ResourceAction } from "../types";
7
7
  import { FlashMessages } from "./FlashMessages";
@@ -48,35 +48,39 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionGlobal
48
48
  */
49
49
  function getAction(actionName: string, actionOptions?: Partial<ActionOptions>): ResourceAction {
50
50
  /// Resolve the action options or resource action based on the provided input
51
- const baseOptions = actions.find(a => a.name === actionName) || { name: actionName };
51
+ let resourceAction: Partial<ResourceAction> = actions.find(a => a.name === actionName) || { name: actionName };
52
52
 
53
53
  if (actionOptions) {
54
- Object.assign(baseOptions, actionOptions);
54
+ Object.assign(resourceAction, actionOptions);
55
55
  }
56
56
 
57
57
  // If the action is already reactive, return it
58
- if (isReactive(baseOptions) && "__type" in baseOptions) return baseOptions as ResourceAction;
59
-
60
- const resourceAction: ResourceAction = storeObject({
61
- onAction: globalOptions?.routes?.applyAction,
62
- onBatchAction: globalOptions?.routes?.batchAction,
63
- onBatchSuccess: globalOptions?.controls?.clearSelectedRows,
64
- ...globalOptions,
65
- ...baseOptions,
66
- trigger: (target, input) => performAction(resourceAction, target, input),
67
- isApplying: false,
68
- __type: "__Action:" + namespace
69
- });
58
+ if (!isReactive(resourceAction) || !("__type" in resourceAction)) {
59
+ resourceAction = storeObject({
60
+ onAction: globalOptions?.routes?.applyAction,
61
+ onBatchAction: globalOptions?.routes?.batchAction,
62
+ onBatchSuccess: globalOptions?.controls?.clearSelectedRows,
63
+ ...globalOptions,
64
+ ...resourceAction,
65
+ isApplying: false,
66
+ __type: "__Action:" + namespace
67
+ });
70
68
 
71
- // Assign Trigger function if it doesn't exist
72
- if (baseOptions.debounce) {
73
- resourceAction.trigger = useDebounceFn((target, input) => performAction(resourceAction, target, input), baseOptions.debounce);
69
+ // Splice the resourceAction in place of the action in the actions list
70
+ actions.splice(actions.findIndex(a => a.name === actionName), 1, resourceAction as ResourceAction);
74
71
  }
75
72
 
76
- // Splice the resourceAction in place of the action in the actions list
77
- actions.splice(actions.findIndex(a => a.name === actionName), 1, resourceAction);
73
+ // Return a clone of the action so it can be modified without affecting the original
74
+ const clonedAction = shallowReactive({ ...resourceAction }) as ResourceAction;
75
+
76
+ // Assign Trigger function if it doesn't exist
77
+ if (clonedAction.debounce) {
78
+ clonedAction.trigger = useDebounceFn((target, input) => performAction(clonedAction, target, input), clonedAction.debounce);
79
+ } else {
80
+ clonedAction.trigger = (target, input) => performAction(clonedAction, target, input);
81
+ }
78
82
 
79
- return resourceAction;
83
+ return clonedAction;
80
84
  }
81
85
 
82
86
  /**
@@ -29,40 +29,6 @@ export function remoteDateTime(dateTimeString: string) {
29
29
  return DateTime.fromSQL(dateTimeString, { zone: "local" }).setZone(SERVER_TZ);
30
30
  }
31
31
 
32
- /**
33
- * Parses a date string into a Luxon DateTime object
34
- */
35
- export function parseDateTime(dateTime: string | DateTime | null): DateTime<boolean> | null {
36
- if (typeof dateTime === "string") {
37
- return parseSqlDateTime(dateTime) || parseQDate(dateTime) || parseQDateTime(dateTime);
38
- }
39
- return dateTime || DateTime.fromSQL("0000-00-00 00:00:00");
40
- }
41
-
42
- /**
43
- * Parses a SQL formatted date string into a Luxon DateTime object
44
- */
45
- export function parseSqlDateTime(dateTime: string) {
46
- const parsed = DateTime.fromSQL(dateTime.replace("T", " ").replace(/\//g, "-"));
47
- return parsed.isValid ? parsed : null;
48
- }
49
-
50
- /**
51
- * Parses a Quasar formatted date string into a Luxon DateTime object
52
- */
53
- export function parseQDate(date: string, format = "yyyy/MM/dd"): DateTime<boolean> | null {
54
- const parsed = DateTime.fromFormat(date, format);
55
- return parsed.isValid ? parsed : null;
56
- }
57
-
58
- /**
59
- * Parses a Quasar formatted date/time string into a Luxon DateTime object
60
- */
61
- export function parseQDateTime(date: string, format = "yyyy/MM/dd HH:mm:ss"): DateTime<boolean> | null {
62
- const parsed = DateTime.fromFormat(date, format);
63
- return parsed.isValid ? parsed : null;
64
- }
65
-
66
32
  /**
67
33
  * Formats a Luxon DateTime object into a Quasar formatted date string
68
34
  * @param date
@@ -119,6 +85,89 @@ export function fDate(dateTime: string | DateTime | null, { empty = "--", format
119
85
  return formatted || empty;
120
86
  }
121
87
 
88
+
89
+ /**
90
+ * Parses a date string into a Luxon DateTime object
91
+ */
92
+ export function parseDateTime(dateTime: string | DateTime | null): DateTime<boolean> | null {
93
+ if (typeof dateTime === "string") {
94
+ return parseGenericDateTime(dateTime);
95
+ }
96
+ return dateTime || DateTime.fromSQL("0000-00-00 00:00:00");
97
+ }
98
+
99
+ /**
100
+ * Parses a SQL formatted date string into a Luxon DateTime object
101
+ */
102
+ export function parseSqlDateTime(dateTime: string) {
103
+ const parsed = DateTime.fromSQL(dateTime.replace("T", " ").replace(/\//g, "-"));
104
+ return parsed.isValid ? parsed : null;
105
+ }
106
+
107
+ /**
108
+ * Parses a Quasar formatted date string into a Luxon DateTime object
109
+ */
110
+ export function parseQDate(date: string, format = "yyyy/MM/dd"): DateTime<boolean> | null {
111
+ const parsed = DateTime.fromFormat(date, format);
112
+ return parsed.isValid ? parsed : null;
113
+ }
114
+
115
+ /**
116
+ * Parses a Quasar formatted date/time string into a Luxon DateTime object
117
+ */
118
+ export function parseQDateTime(date: string, format = "yyyy/MM/dd HH:mm:ss"): DateTime<boolean> | null {
119
+ const parsed = DateTime.fromFormat(date, format);
120
+ return parsed.isValid ? parsed : null;
121
+ }
122
+
123
+ /**
124
+ * Parses a date string in various formats into a Luxon DateTime object.
125
+ * Tries a list of common formats until one works.
126
+ *
127
+ * @param {string} dateTimeString - The date string to parse.
128
+ * @param {string} [defaultZone="local"] - The default time zone to use if not specified.
129
+ * @returns {DateTime | null} - A Luxon DateTime object if parsing succeeds, otherwise null.
130
+ */
131
+ export function parseGenericDateTime(dateTimeString: string, defaultZone = "local"): DateTime | null {
132
+ if (!dateTimeString) return null;
133
+
134
+ const formats = [
135
+ "yyyy-MM-dd", // ISO date
136
+ "yyyy-MM-dd HH:mm:ss", // ISO date with time
137
+ "MM/dd/yyyy", // US-style date
138
+ "dd/MM/yyyy", // European-style date
139
+ "MM/dd/yy", // US short date
140
+ "dd/MM/yy", // European short date
141
+ "yyyy/MM/dd", // Alternative ISO
142
+ "MM-dd-yyyy", // US with dashes
143
+ "dd-MM-yyyy", // European with dashes
144
+ "M/d/yyyy", // US date without leading zeros
145
+ "d/M/yyyy", // European date without leading zeros
146
+ "yyyyMMdd" // Compact ISO
147
+ ];
148
+
149
+ for (const format of formats) {
150
+ const parsed = DateTime.fromFormat(dateTimeString, format, { zone: defaultZone });
151
+ if (parsed.isValid) {
152
+ return parsed;
153
+ }
154
+ }
155
+
156
+ // Fallback to ISO parsing for strings like "2022-11-18T10:10:10Z"
157
+ const isoParsed = DateTime.fromISO(dateTimeString, { zone: defaultZone });
158
+ if (isoParsed.isValid) {
159
+ return isoParsed;
160
+ }
161
+
162
+ // Fallback to SQL parsing for strings like "2022-11-18 10:10:10"
163
+ const sqlParsed = DateTime.fromSQL(dateTimeString, { zone: defaultZone });
164
+ if (sqlParsed.isValid) {
165
+ return sqlParsed;
166
+ }
167
+
168
+ return null;
169
+ }
170
+
122
171
  /**
123
172
  * Formats a number of seconds into Hours / Minutes / Seconds or just Minutes and Seconds
124
173
  *
@@ -62,6 +62,11 @@ export interface ListControlsPagination {
62
62
  rowsPerPage?: number;
63
63
  perPage?: number;
64
64
  filter?: ListControlsFilter;
65
+ fields?: ControlsFieldsList;
66
+ }
67
+
68
+ export interface ControlsFieldsList {
69
+ [key: string]: boolean | ControlsFieldsList;
65
70
  }
66
71
 
67
72
  export interface PagedItems<T = ActionTargetItem> {