quasar-ui-danx 0.4.32 → 0.4.35

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.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> {