quasar-ui-danx 0.4.36 → 0.4.38

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.36",
3
+ "version": "0.4.38",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -6,9 +6,8 @@
6
6
  @drop.prevent="onDrop"
7
7
  >
8
8
  <FieldLabel
9
- :field="field"
9
+ v-if="label"
10
10
  :label="label"
11
- :show-name="showName"
12
11
  />
13
12
 
14
13
  <input
@@ -0,0 +1,155 @@
1
+ <template>
2
+ <div class="group flex items-center flex-nowrap gap-x-1 relative">
3
+ <QInnerLoading
4
+ v-if="loading"
5
+ showing
6
+ class="bg-sky-900 opacity-50 z-10 rounded"
7
+ color="teal"
8
+ />
9
+ <ShowHideButton
10
+ v-if="selectable"
11
+ v-model="isSelecting"
12
+ :disable="disable"
13
+ :label="selectText"
14
+ :class="selectClass"
15
+ :show-icon="selectIcon || DefaultSelectIcon"
16
+ class="mr-2"
17
+ >
18
+ <template #label>
19
+ <QMenu
20
+ :model-value="isSelecting"
21
+ @before-hide="isSelecting = false"
22
+ @hide="isSelecting = false"
23
+ >
24
+ <div>
25
+ <div
26
+ v-for="option in options"
27
+ :key="option.id"
28
+ v-ripple
29
+ class="cursor-pointer flex items-center relative"
30
+ :class="{'bg-sky-900 hover:bg-sky-800': selected?.id === option.id, 'hover:bg-slate-600': selected?.id !== option.id}"
31
+ @click="selected = option"
32
+ >
33
+ <div class="flex-grow px-4 py-2">
34
+ {{ option.name }}
35
+ </div>
36
+ <ActionButton
37
+ v-if="deletable"
38
+ type="trash"
39
+ class="ml-4 mr-2"
40
+ @click.stop.prevent="$emit('delete', option)"
41
+ />
42
+ </div>
43
+ <QSeparator class="bg-slate-400 my-2" />
44
+ <div class="px-4 mb-2">
45
+ <QBtn
46
+ :class="createClass"
47
+ :loading="loading"
48
+ @click="$emit('create')"
49
+ >
50
+ <CreateIcon
51
+ class="w-3"
52
+ :class="createText ? 'mr-2' : ''"
53
+ />
54
+ {{ createText }}
55
+ </QBtn>
56
+ </div>
57
+ </div>
58
+ </QMenu>
59
+ </template>
60
+ </ShowHideButton>
61
+
62
+ <div :class="labelClass">
63
+ <template v-if="selected">
64
+ <EditableDiv
65
+ v-if="nameEditable"
66
+ :model-value="selected.name"
67
+ color="slate-600"
68
+ @update:model-value="name => $emit('update', {name})"
69
+ />
70
+ <template v-else>
71
+ {{ selected.name }}
72
+ </template>
73
+ </template>
74
+ <template v-else>
75
+ <slot name="no-selection">
76
+ (No selection)
77
+ </slot>
78
+ </template>
79
+ </div>
80
+
81
+
82
+ <ShowHideButton
83
+ v-if="editable && selected"
84
+ v-model="editing"
85
+ :label="editText"
86
+ :class="editClass"
87
+ class="ml-1"
88
+ :show-icon="EditIcon"
89
+ :hide-icon="DoneEditingIcon"
90
+ />
91
+
92
+ <QBtn
93
+ v-if="clearable && selected"
94
+ :label="clearText"
95
+ :class="clearClass"
96
+ class="opacity-0 group-hover:opacity-100 transition-all"
97
+ @click="selected = null"
98
+ >
99
+ <ClearIcon class="w-4" />
100
+ </QBtn>
101
+ </div>
102
+ </template>
103
+ <script setup lang="ts">
104
+ import {
105
+ FaSolidCheck as DoneEditingIcon,
106
+ FaSolidCircleXmark as ClearIcon,
107
+ FaSolidListCheck as DefaultSelectIcon,
108
+ FaSolidPencil as EditIcon,
109
+ FaSolidPlus as CreateIcon
110
+ } from "danx-icon";
111
+ import { ref } from "vue";
112
+ import { ActionTargetItem } from "../../../../types";
113
+ import { ShowHideButton } from "../../../Utility/Buttons";
114
+ import ActionButton from "../../../Utility/Buttons/ActionButton";
115
+ import EditableDiv from "./EditableDiv";
116
+
117
+ defineEmits(["create", "update", "delete"]);
118
+ const selected = defineModel<ActionTargetItem | null>("selected");
119
+ const editing = defineModel<boolean>("editing");
120
+ withDefaults(defineProps<{
121
+ options: ActionTargetItem[];
122
+ showEdit?: boolean;
123
+ loading?: boolean;
124
+ selectText?: string;
125
+ createText?: string;
126
+ editText?: string;
127
+ clearText?: string;
128
+ selectClass?: string;
129
+ createClass?: string;
130
+ editClass?: string;
131
+ clearClass?: string;
132
+ labelClass?: string;
133
+ selectIcon?: object | string;
134
+ selectable?: boolean;
135
+ creatable?: boolean;
136
+ editable?: boolean;
137
+ deletable?: boolean;
138
+ nameEditable?: boolean;
139
+ clearable?: boolean;
140
+ disable?: boolean;
141
+ }>(), {
142
+ selectText: "",
143
+ createText: "",
144
+ editText: "",
145
+ clearText: "",
146
+ selectClass: "bg-sky-800",
147
+ createClass: "bg-green-900",
148
+ editClass: "",
149
+ clearClass: "rounded-full",
150
+ labelClass: "text-slate-600",
151
+ selectIcon: null
152
+ });
153
+
154
+ const isSelecting = ref(false);
155
+ </script>
@@ -30,6 +30,7 @@
30
30
  :model-value="modelValue"
31
31
  :maxlength="allowOverMax ? undefined : maxLength"
32
32
  :debounce="debounce"
33
+ :loading="loading"
33
34
  @keydown.enter="$emit('submit')"
34
35
  @update:model-value="$emit('update:model-value', $event)"
35
36
  >
@@ -19,6 +19,7 @@ export { default as NumberField } from "./NumberField.vue";
19
19
  export { default as NumberRangeField } from "./NumberRangeField.vue";
20
20
  export { default as SelectDrawer } from "./SelectDrawer.vue";
21
21
  export { default as SelectField } from "./SelectField.vue";
22
+ export { default as SelectionMenuField } from "./SelectionMenuField.vue";
22
23
  export { default as SelectOrCreateField } from "./SelectOrCreateField.vue";
23
24
  export { default as SelectWithChildrenField } from "./SelectWithChildrenField.vue";
24
25
  export { default as SingleFileField } from "./SingleFileField.vue";
@@ -2,8 +2,8 @@
2
2
  <div
3
3
  :class="{'cursor-move': !showHandle && !disabled}"
4
4
  :draggable="disabled ? undefined : 'true'"
5
- @dragstart.stop="disabled ? null : dragAndDrop.dragStart"
6
- @dragend="disabled ? null : dragAndDrop.dragEnd"
5
+ @dragstart.stop="dragAndDrop.dragStart"
6
+ @dragend="dragAndDrop.dragEnd"
7
7
  >
8
8
  <div :class="contentClass">
9
9
  <div
@@ -28,7 +28,8 @@
28
28
  </div>
29
29
  </template>
30
30
  <script setup lang="ts">
31
- import { computed } from "vue";
31
+ import { DropZoneResolver } from "src/components/DragAndDrop/dragAndDrop";
32
+ import { computed, watch } from "vue";
32
33
  import { DragHandleDotsIcon as DragHandleIcon } from "../../svg";
33
34
  import { SvgImg } from "../Utility";
34
35
  import { ListDragAndDrop } from "./listDragAndDrop";
@@ -36,7 +37,7 @@ import { ListDragAndDrop } from "./listDragAndDrop";
36
37
  const emit = defineEmits(["position", "update:list-items", "drop-zone"]);
37
38
  const dragging = defineModel<boolean>();
38
39
  const props = withDefaults(defineProps<{
39
- dropZone: string | (() => string);
40
+ dropZone: DropZoneResolver;
40
41
  direction?: "vertical" | "horizontal";
41
42
  showHandle?: boolean;
42
43
  changeDropZone?: boolean;
@@ -53,13 +54,21 @@ const props = withDefaults(defineProps<{
53
54
  listItems: () => []
54
55
  });
55
56
 
57
+ watch(() => props.disabled, (value) => dragAndDrop.setOptions({ disabled: value }));
58
+
56
59
  const resolvedHandleClass = computed(() => ({
57
60
  "cursor-move": !props.disabled,
58
61
  ...(typeof props.handleClass === "string" ? { [props.handleClass]: true } : props.handleClass)
59
62
  }));
63
+
60
64
  const dragAndDrop = new ListDragAndDrop()
61
65
  .setDropZone(props.dropZone)
62
- .setOptions({ showPlaceholder: true, allowDropZoneChange: props.changeDropZone, direction: props.direction })
66
+ .setOptions({
67
+ showPlaceholder: true,
68
+ allowDropZoneChange: props.changeDropZone,
69
+ direction: props.direction,
70
+ disabled: props.disabled
71
+ })
63
72
  .onStart(() => dragging.value = true)
64
73
  .onEnd(() => dragging.value = false)
65
74
  .onDropZoneChange((target, dropZone, newPosition, oldPosition, data) => {
@@ -1,3 +1,9 @@
1
+ import { AnyObject } from "src/types";
2
+
3
+ export type DropZoneResolver = ((e: DragEvent) => HTMLElement) | string | null;
4
+ export type DragAndDropCallback = ((e: DragEvent, data: DraggableData) => void) | null;
5
+ export type DraggableData = AnyObject | string | number | null;
6
+
1
7
  /**
2
8
  * Drag and Drop basic functionality for dragging elements and firing events on drag start, drag over and drag end
3
9
  */
@@ -7,6 +13,7 @@ export class DragAndDrop {
7
13
  hideDragImage?: boolean,
8
14
  showPlaceholder?: boolean,
9
15
  allowDropZoneChange?: boolean,
16
+ disabled?: boolean,
10
17
  } = { direction: "vertical", hideDragImage: false };
11
18
 
12
19
  // State
@@ -15,19 +22,20 @@ export class DragAndDrop {
15
22
  startSize = 0;
16
23
  cursorY = 0;
17
24
  cursorX = 0;
18
- onStartCb = null;
19
- onEndCb = null;
20
- onDropCb = null;
21
- onDraggingCb = null;
22
- dropZoneResolver = null;
25
+ onStartCb: DragAndDropCallback = null;
26
+ onEndCb: DragAndDropCallback = null;
27
+ onDropCb: DragAndDropCallback = null;
28
+ onDraggingCb: DragAndDropCallback = null;
29
+ dropZoneResolver: DropZoneResolver = null;
23
30
  currentDropZone: HTMLElement | null = null;
24
- draggableData = null;
31
+ draggableData: DraggableData = null;
25
32
  // Used to abort dragging event listeners on the element
26
- abortController = null;
33
+ abortController: AbortController | null = null;
27
34
 
28
35
  constructor(options = {}) {
29
36
  // Options
30
37
  options = {
38
+ disabled: false,
31
39
  direction: "vertical",
32
40
  hideDragImage: false,
33
41
  ...options
@@ -38,8 +46,6 @@ export class DragAndDrop {
38
46
 
39
47
  /**
40
48
  * Set the options for the drag and drop instance
41
- * @param options
42
- * @returns {DragAndDrop}
43
49
  */
44
50
  setOptions(options = {}) {
45
51
  this.options = { ...this.options, ...options };
@@ -48,7 +54,6 @@ export class DragAndDrop {
48
54
 
49
55
  /**
50
56
  * Returns if the list is stacked vertically or horizontally
51
- * @returns {boolean}
52
57
  */
53
58
  isVertical() {
54
59
  return this.options.direction === "vertical";
@@ -56,77 +61,68 @@ export class DragAndDrop {
56
61
 
57
62
  /**
58
63
  * Set the target drop zone for draggable elements
59
- * @param dropZone
60
- * @returns {DragAndDrop}
61
64
  */
62
- setDropZone(dropZone) {
65
+ setDropZone(dropZone: DropZoneResolver) {
63
66
  this.dropZoneResolver = dropZone;
64
67
  return this;
65
68
  }
66
69
 
67
70
  /**
68
71
  * Callback that fires when an element has started dragging
69
- * @param cb
70
- * @returns {DragAndDrop}
71
72
  */
72
- onStart(cb) {
73
+ onStart(cb: DragAndDropCallback) {
73
74
  this.onStartCb = cb;
74
75
  return this;
75
76
  }
76
77
 
77
78
  /**
78
79
  * Callback that fires when an element has stopped dragging
79
- * @param cb
80
- * @returns {DragAndDrop}
81
80
  */
82
- onEnd(cb) {
81
+ onEnd(cb: DragAndDropCallback) {
83
82
  this.onEndCb = cb;
84
83
  return this;
85
84
  }
86
85
 
87
86
  /**
88
87
  * Callback that fires when the dragging element is moved
89
- * @param cb
90
- * @returns {DragAndDrop}
91
88
  */
92
- onDragging(cb) {
89
+ onDragging(cb: DragAndDropCallback) {
93
90
  this.onDraggingCb = cb;
94
91
  return this;
95
92
  }
96
93
 
97
94
  /**
98
95
  * Callback that fires when the dragging element has been dropped
99
- * @param cb
100
- * @returns {DragAndDrop}
101
96
  */
102
- onDrop(cb) {
97
+ onDrop(cb: DragAndDropCallback) {
103
98
  this.onDropCb = cb;
104
99
  return this;
105
100
  }
106
101
 
107
102
  /**
108
103
  * Start listening for drag events and prepare an element for drag/drop
109
- * @param e
110
- * @param data
111
104
  */
112
- dragStart(e, data) {
105
+ dragStart(e: DragEvent, data: DraggableData) {
106
+ if (this.options.disabled) return;
113
107
  this.currentDropZone = this.getDropZone(e);
114
108
 
115
109
  if (this.currentDropZone) {
116
110
  this.startY = e.clientY;
117
111
  this.startX = e.clientX;
118
112
  this.startSize = this.getDropZoneSize();
119
- e.dataTransfer.effectAllowed = "move";
120
- e.dataTransfer.dropEffect = "move";
113
+ if (e.dataTransfer) {
114
+ e.dataTransfer.effectAllowed = "move";
115
+ e.dataTransfer.dropEffect = "move";
116
+ }
121
117
  this.draggableData = data;
122
118
  this.abortController = new AbortController();
123
119
  const options = { signal: this.abortController.signal };
124
120
  document.addEventListener("dragenter", (e) => this.dragEnter(e), options);
125
121
  document.addEventListener("dragover", (e) => this.dragOver(e), options);
126
122
  document.addEventListener("drop", (e) => this.drop(e), options);
127
- this.onStartCb && this.onStartCb(e);
123
+ this.onStartCb && this.onStartCb(e, this.draggableData);
128
124
 
129
- if (this.options.hideDragImage) {
125
+ if (e.dataTransfer && this.options.hideDragImage) {
130
126
  e.dataTransfer.setDragImage(new Image(), 0, 0);
131
127
  }
132
128
  } else {
@@ -137,55 +133,53 @@ export class DragAndDrop {
137
133
  /**
138
134
  * Clean up event listeners after dragging is done
139
135
  */
140
- dragEnd(e) {
136
+ dragEnd(e: DragEvent) {
137
+ if (this.options.disabled) return;
141
138
  this.currentDropZone = null;
142
139
  this.abortController?.abort();
140
+ this.onEndCb && this.onEndCb(e, this.draggableData);
143
141
  this.draggableData = null;
144
- this.onEndCb && this.onEndCb(e);
145
142
  }
146
143
 
147
144
  /**
148
145
  * The dragging element has entered a new target
149
- * @param e
150
146
  */
151
- dragEnter(e) {
147
+ dragEnter(e: DragEvent) {
152
148
  e.preventDefault();
153
149
  }
154
150
 
155
151
  /**
156
152
  * The dragging element is moving
157
- * @param e
158
153
  */
159
- dragOver(e) {
154
+ dragOver(e: DragEvent) {
155
+ if (this.options.disabled) return;
160
156
  e.preventDefault();
161
157
  this.cursorY = e.clientY;
162
158
  this.cursorX = e.clientX;
163
- this.onDraggingCb && this.onDraggingCb(e);
159
+ this.onDraggingCb && this.onDraggingCb(e, this.draggableData);
164
160
  }
165
161
 
166
162
  /**
167
163
  * Handle dropping the element into its correct position
168
- * @param e
169
164
  */
170
- drop(e) {
171
- e.dataTransfer.dropEffect = "move";
165
+ drop(e: DragEvent) {
166
+ if (this.options.disabled) return;
167
+ e.dataTransfer && (e.dataTransfer.dropEffect = "move");
172
168
  e.preventDefault();
173
169
  this.onDropCb && this.onDropCb(e, this.draggableData);
174
170
  }
175
171
 
176
172
  /**
177
173
  * Returns the drop zone if the current target element is or is inside the drop zone
178
- * @param e
179
- * @returns {HTMLElement|null}
180
174
  */
181
- getDropZone(e) {
175
+ getDropZone(e: DragEvent): HTMLElement | null {
182
176
  if (typeof this.dropZoneResolver === "string") {
183
- let target = e.target;
177
+ let target = e.target as HTMLElement;
184
178
  while (target) {
185
179
  if (target.dataset?.dropZone === this.dropZoneResolver) {
186
180
  return target;
187
181
  }
188
- target = target.parentNode;
182
+ target = target.parentNode as HTMLElement;
189
183
  }
190
184
  return null;
191
185
  } else if (typeof this.dropZoneResolver === "function") {
@@ -197,9 +191,8 @@ export class DragAndDrop {
197
191
 
198
192
  /**
199
193
  * Returns the distance between the start and current cursor position
200
- * @returns {number}
201
194
  */
202
- getDistance() {
195
+ getDistance(): number {
203
196
  return this.isVertical()
204
197
  ? this.cursorY - this.startY
205
198
  : this.cursorX - this.startX;
@@ -208,19 +201,17 @@ export class DragAndDrop {
208
201
  /**
209
202
  * Returns the size of the drop zone
210
203
  */
211
- getDropZoneSize() {
212
- return this.isVertical()
204
+ getDropZoneSize(): number {
205
+ return (this.isVertical()
213
206
  ? this.currentDropZone?.offsetHeight
214
- : this.currentDropZone?.offsetWidth;
207
+ : this.currentDropZone?.offsetWidth) || 0;
215
208
  }
216
209
 
217
210
  /**
218
211
  * Returns the percent change between the start and current cursor position relative to the drop zone size
219
- *
220
- * @returns {number}
221
212
  */
222
213
  getPercentChange() {
223
214
  const distance = this.getDistance();
224
- return (distance / this.startSize) * 100;
215
+ return (distance / (this.startSize || 1)) * 100;
225
216
  }
226
217
  }