@a-vision-software/vue-input-components 1.4.17 → 1.4.19

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": "@a-vision-software/vue-input-components",
3
- "version": "1.4.17",
3
+ "version": "1.4.19",
4
4
  "description": "A collection of reusable Vue 3 input components with TypeScript support",
5
5
  "author": "A-Vision Software",
6
6
  "license": "MIT",
@@ -1,33 +1,46 @@
1
1
  <template>
2
- <div class="dropdown" :class="{
3
- 'dropdown--open': isOpen,
4
- 'dropdown--disabled': disabled,
5
- 'dropdown--multiple': multiple,
6
- 'dropdown--large-icon': iconSize === 'large',
7
- 'dropdown--has-error': error,
8
- [`label-${labelPosition}`]: label,
9
- [`label-align-${labelAlign}`]: label,
10
- }" :style="[
11
- { width: width || '100%' },
12
- labelStyle,
13
- {
14
- '--dropdown-color': error ? 'var(--danger-color)' : color,
15
- '--dropdown-hover-color': hoverColor ? hoverColor : 'var(--dropdown-color)',
16
- '--dropdown-active-color': activeColor ? activeColor : 'var(--dropdown-color)',
17
- '--dropdown-disabled-color': disabledColor,
18
- '--dropdown-background-color': backgroundColor,
19
- '--dropdown-border-radius': borderRadius,
20
- '--dropdown-padding': padding,
21
- '--dropdown-max-height': maxHeight,
22
- }
23
- ]" @click="toggleDropdown" @keydown.esc="closeDropdown" @keydown.space.prevent="toggleDropdown"
24
- @keydown.enter.prevent="toggleDropdown" tabindex="0">
2
+ <div
3
+ class="dropdown"
4
+ :class="{
5
+ 'dropdown--open': isOpen,
6
+ 'dropdown--disabled': disabled,
7
+ 'dropdown--multiple': multiple,
8
+ 'dropdown--large-icon': iconSize === 'large',
9
+ 'dropdown--has-error': error,
10
+ [`label-${labelPosition}`]: label,
11
+ [`label-align-${labelAlign}`]: label,
12
+ }"
13
+ :style="[
14
+ { width: width || '100%' },
15
+ labelStyle,
16
+ {
17
+ '--dropdown-color': error ? 'var(--danger-color)' : color,
18
+ '--dropdown-hover-color': hoverColor ? hoverColor : 'var(--dropdown-color)',
19
+ '--dropdown-active-color': activeColor ? activeColor : 'var(--dropdown-color)',
20
+ '--dropdown-disabled-color': disabledColor,
21
+ '--dropdown-background-color': backgroundColor,
22
+ '--dropdown-border-radius': borderRadius,
23
+ '--dropdown-padding': padding,
24
+ '--dropdown-max-height': maxHeight,
25
+ },
26
+ ]"
27
+ @click="toggleDropdown"
28
+ @keydown.esc="closeDropdown"
29
+ @keydown.space.prevent="toggleDropdown"
30
+ @keydown.enter.prevent="toggleDropdown"
31
+ tabindex="0"
32
+ >
25
33
  <label v-if="label" :for="id" class="label">
26
34
  {{ label }}
27
35
  </label>
28
36
  <div class="dropdown__selected">
29
37
  <div v-if="icon" class="dropdown__icon">
30
- <img v-if="icon.startsWith('img:')" :src="icon.substring(4)" :alt="placeholder" class="dropdown__icon-image" />
38
+ <img
39
+ v-if="icon.startsWith('img:')"
40
+ :src="icon.substring(4)"
41
+ :alt="placeholder"
42
+ class="dropdown__icon-image"
43
+ />
31
44
  <font-awesome-icon v-else :icon="icon" />
32
45
  </div>
33
46
  <div v-if="!hasSelection" class="dropdown__placeholder">
@@ -35,8 +48,12 @@
35
48
  </div>
36
49
  <div v-else class="dropdown__selected-items">
37
50
  <template v-if="multiple">
38
- <div v-for="option in selectedOptions" :key="option.id" class="dropdown__selected-tag"
39
- @click.stop="deselectOption(option)">
51
+ <div
52
+ v-for="option in selectedOptions"
53
+ :key="option.id"
54
+ class="dropdown__selected-tag"
55
+ @click.stop="deselectOption(option)"
56
+ >
40
57
  {{ option.label }}
41
58
  <font-awesome-icon icon="xmark" class="dropdown__selected-tag-remove" />
42
59
  </div>
@@ -46,12 +63,23 @@
46
63
  </template>
47
64
  </div>
48
65
  <div class="dropdown__icons">
49
- <font-awesome-icon v-if="hasSelection && !multiple" icon="xmark" class="dropdown__clear"
50
- @click.stop="clearSelection" />
51
- <font-awesome-icon icon="chevron-down" class="dropdown__arrow" :class="{ 'dropdown__arrow--open': isOpen }" />
66
+ <font-awesome-icon
67
+ v-if="hasSelection && !multiple"
68
+ icon="xmark"
69
+ class="dropdown__clear"
70
+ @click.stop="clearSelection"
71
+ />
72
+ <font-awesome-icon
73
+ icon="chevron-down"
74
+ class="dropdown__arrow"
75
+ :class="{ 'dropdown__arrow--open': isOpen && !readonly }"
76
+ />
52
77
  </div>
53
- <span v-if="required && !showSaved && !showChanged && !error"
54
- class="status-indicator required-indicator">required</span>
78
+ <span
79
+ v-if="required && !showSaved && !showChanged && !error"
80
+ class="status-indicator required-indicator"
81
+ >required</span
82
+ >
55
83
  <transition name="fade">
56
84
  <span v-if="showSaved && !error" class="status-indicator saved-indicator">saved</span>
57
85
  </transition>
@@ -63,17 +91,34 @@
63
91
  </transition>
64
92
  </div>
65
93
 
66
- <div v-if="isOpen" class="dropdown__content">
94
+ <div v-if="isOpen && !readonly" class="dropdown__content">
67
95
  <div v-if="filterable" class="dropdown__filter">
68
- <input ref="filterInput" v-model="filterText" type="text" class="dropdown__filter-input"
69
- placeholder="Type to filter..." @click.stop @keydown.stop />
96
+ <input
97
+ ref="filterInput"
98
+ v-model="filterText"
99
+ type="text"
100
+ class="dropdown__filter-input"
101
+ placeholder="Type to filter..."
102
+ @click.stop
103
+ @keydown.stop
104
+ />
70
105
  </div>
71
106
  <div class="dropdown__options">
72
- <div v-for="option in filteredOptions" :key="option.id" class="dropdown__option" :class="{
73
- 'dropdown__option--selected': isSelected(option),
74
- 'dropdown__option--disabled': option.disabled,
75
- }" @click.stop="toggleOption(option)">
76
- <font-awesome-icon v-if="multiple && isSelected(option)" icon="check" class="dropdown__option-check" />
107
+ <div
108
+ v-for="option in filteredOptions"
109
+ :key="option.id"
110
+ class="dropdown__option"
111
+ :class="{
112
+ 'dropdown__option--selected': isSelected(option),
113
+ 'dropdown__option--disabled': option.disabled,
114
+ }"
115
+ @click.stop="toggleOption(option)"
116
+ >
117
+ <font-awesome-icon
118
+ v-if="multiple && isSelected(option)"
119
+ icon="check"
120
+ class="dropdown__option-check"
121
+ />
77
122
  {{ option.label }}
78
123
  </div>
79
124
  </div>
@@ -107,6 +152,7 @@ const props = withDefaults(defineProps<DropdownProps>(), {
107
152
  labelPosition: 'top',
108
153
  labelAlign: 'left',
109
154
  labelWidth: '',
155
+ readonly: false,
110
156
  })
111
157
 
112
158
  const emit = defineEmits<{
@@ -136,9 +182,7 @@ const hasSelection = computed(() => {
136
182
  const filteredOptions = computed(() => {
137
183
  if (!filterText.value) return props.options
138
184
  const searchText = filterText.value.toLowerCase()
139
- return props.options.filter((option) =>
140
- option.label.toLowerCase().includes(searchText)
141
- )
185
+ return props.options.filter((option) => option.label.toLowerCase().includes(searchText))
142
186
  })
143
187
 
144
188
  const isSelected = (option: DropdownOption) => {
@@ -1,28 +1,53 @@
1
1
  <template>
2
- <div class="file-upload"
3
- :class="{ 'label-position-top': labelPosition === 'top', 'label-position-left': labelPosition === 'left' }" :style="{
2
+ <div
3
+ class="file-upload"
4
+ :class="{
5
+ 'label-position-top': labelPosition === 'top',
6
+ 'label-position-left': labelPosition === 'left',
7
+ }"
8
+ :style="{
4
9
  '--file-upload-width': width || 'auto',
5
10
  '--file-upload-height': height || 'auto',
6
- '--file-upload-color': error ? 'var(--ui-error-text-color)' : (color || 'var(--ui-upload-text-color, #888888)'),
11
+ '--file-upload-color': error
12
+ ? 'var(--ui-error-text-color)'
13
+ : color || 'var(--ui-upload-text-color, #888888)',
7
14
  '--file-upload-disabled-color': 'color-mix(in srgb, var(--file-upload-color), white 70%)',
8
15
  '--file-upload-background-color': bgColor || 'var(--ui-upload-bg-color, #ffffff)',
9
- '--file-upload-background-active-color': activeColor || 'var(--ui-upload-active-bg-color, #cccccc)',
16
+ '--file-upload-background-active-color':
17
+ activeColor || 'var(--ui-upload-active-bg-color, #cccccc)',
10
18
  '--file-upload-border-color': borderColor || 'var(--ui-upload-border-color, #888888)',
11
19
  '--file-upload-icon-color': iconColor || 'var(--file-upload-color, #888888)',
12
20
  '--file-upload-progress-color': progressColor || 'var(--ui-progress-color, #4444aa)',
13
21
  '--file-upload-error-color': 'var(--ui-error-text-color, #aa0000)',
14
22
  '--file-upload-success-color': 'var(--ui-success-text-color, #00aa00)',
15
- }">
23
+ }"
24
+ >
16
25
  <label class="file-upload-label" v-if="label" for="fileInput">{{ label }}</label>
17
- <div class="upload-area"
18
- :class="{ 'is-dragging': isDragging, 'has-files': files.length > 0, 'is-disabled': disabled }">
19
- <input ref="fileInput" type="file" :multiple="multiple" class="file-input" @change="handleFileSelect"
20
- :accept="accept" :required="required" :disabled="disabled" :placeholder="placeholder"
21
- @dragenter.prevent="handleDragEnter" @dragleave.prevent="handleDragLeave" @dragover.prevent
22
- @drop.prevent="handleDrop" />
26
+ <div
27
+ class="upload-area"
28
+ :class="{ 'is-dragging': isDragging, 'has-files': files.length > 0, 'is-disabled': disabled }"
29
+ >
30
+ <input
31
+ ref="fileInput"
32
+ type="file"
33
+ :multiple="multiple"
34
+ class="file-input"
35
+ @change="handleFileSelect"
36
+ :accept="accept"
37
+ :required="required"
38
+ :disabled="disabled"
39
+ :placeholder="placeholder"
40
+ @dragenter.prevent="handleDragEnter"
41
+ @dragleave.prevent="handleDragLeave"
42
+ @dragover.prevent
43
+ @drop.prevent="handleDrop"
44
+ />
23
45
  <div class="upload-content">
24
46
  <font-awesome-icon v-if="icon" :icon="['fas', icon]" />
25
- <p v-if="files.length === 0" v-text="placeholder || 'Drag & drop files here or click to select'"></p>
47
+ <p v-if="files.length === 0">
48
+ {{ placeholder || 'Drag & drop files here or click to select' }}
49
+ <div v-if="maxSize" class="max-size">(max size: {{ maxSize }}MB)</div>
50
+ </p>
26
51
  <div v-else class="selected-files">
27
52
  <p>{{ files.length }} file(s) selected</p>
28
53
  <div v-for="(file, index) in files" :key="index" class="file-info">
@@ -39,8 +64,11 @@
39
64
  <div class="progress" :style="{ width: `${uploadProgress}%` }"></div>
40
65
  </div>
41
66
  </div>
42
- <button v-if="files.length > 0 && !uploadUrl && !disabled && showUploadButton" class="upload-button"
43
- @click="handleStartUpload">
67
+ <button
68
+ v-if="files.length > 0 && !uploadUrl && !disabled && showUploadButton"
69
+ class="upload-button"
70
+ @click="handleStartUpload"
71
+ >
44
72
  Upload Files
45
73
  </button>
46
74
  </div>
@@ -48,7 +76,12 @@
48
76
 
49
77
  <script setup lang="ts">
50
78
  import { ref, watch, onMounted } from 'vue'
51
- import type { FileUploadProps, FileUploadEmits, UploadStatus, UploadCallbackResponse } from '@/types/fileupload'
79
+ import type {
80
+ FileUploadProps,
81
+ FileUploadEmits,
82
+ UploadStatus,
83
+ UploadCallbackResponse,
84
+ } from '@/types/fileupload'
52
85
 
53
86
  const props = defineProps<FileUploadProps>()
54
87
  const emit = defineEmits<FileUploadEmits>()
@@ -65,7 +98,7 @@ const uploadStatus = ref<UploadStatus>({
65
98
  })
66
99
 
67
100
  onMounted(() => {
68
- startUpload.value = props.doUpload;
101
+ startUpload.value = props.doUpload
69
102
  })
70
103
 
71
104
  const formatFileSize = (bytes: number): string => {
@@ -77,7 +110,7 @@ const formatFileSize = (bytes: number): string => {
77
110
  }
78
111
 
79
112
  const validateFileSize = (file: File): boolean => {
80
- if (props.maxSize && file.size > props.maxSize) {
113
+ if (props.maxSize && file.size > props.maxSize * 1024 * 1024) {
81
114
  error.value = `File "${file.name}" exceeds the maximum size of ${props.maxSize}MB`
82
115
  return false
83
116
  }
@@ -129,12 +162,13 @@ const uploadFiles = () => {
129
162
  const formData = new FormData()
130
163
  files.value.forEach((file) => {
131
164
  formData.append('files', file)
132
- });
165
+ })
133
166
 
134
167
  if (props.uploadCallback) {
135
- props.uploadCallback(formData)
168
+ props
169
+ .uploadCallback(formData)
136
170
  .then((response: UploadCallbackResponse) => {
137
- const { formData, headers } = response;
171
+ const { formData, headers } = response
138
172
  doUploadFiles(formData, headers)
139
173
  })
140
174
  .catch((err) => {
@@ -143,10 +177,11 @@ const uploadFiles = () => {
143
177
  uploadStatus.value = {
144
178
  type: 'error',
145
179
  message: errorMessage,
180
+ response: err,
146
181
  }
147
182
  emit('upload-complete', files.value)
148
183
  emit('upload-error', uploadStatus.value)
149
- });
184
+ })
150
185
  } else {
151
186
  doUploadFiles(formData, {})
152
187
  }
@@ -172,24 +207,43 @@ const doUploadFiles = (formData: FormData, headers: Record<string, string>) => {
172
207
 
173
208
  xhr.addEventListener('load', () => {
174
209
  if (xhr.status >= 200 && xhr.status < 300) {
210
+ const response = JSON.parse(xhr.response)
175
211
  uploadStatus.value = {
176
212
  type: 'success',
177
213
  message: 'Upload completed successfully',
214
+ response: response,
178
215
  }
179
216
 
180
- const response = JSON.parse(xhr.response)
181
- emit('upload-complete', response)
217
+ emit('upload-complete', files.value)
182
218
  emit('upload-success', uploadStatus.value)
183
219
  files.value = []
184
220
  uploadProgress.value = 0
185
- startUpload.value = false;
221
+ startUpload.value = false
186
222
  } else {
187
- throw new Error(`Upload failed with status ${xhr.status}`)
223
+ const errorMessage = xhr.statusText || 'Upload failed'
224
+ error.value = errorMessage
225
+ uploadStatus.value = {
226
+ type: 'error',
227
+ message: errorMessage,
228
+ response: null,
229
+ }
230
+ emit('upload-complete', files.value)
231
+ emit('upload-error', uploadStatus.value)
232
+ files.value = []
233
+ uploadProgress.value = 0
234
+ startUpload.value = false
188
235
  }
189
236
  })
190
237
 
191
238
  xhr.addEventListener('error', () => {
192
- throw new Error('Upload failed')
239
+ const errorMessage = xhr.statusText || 'Upload error'
240
+ uploadStatus.value = {
241
+ type: 'error',
242
+ message: errorMessage,
243
+ response: null,
244
+ }
245
+ emit('upload-complete', files.value)
246
+ emit('upload-error', uploadStatus.value)
193
247
  })
194
248
 
195
249
  xhr.open('POST', props.uploadUrl || '')
@@ -205,6 +259,7 @@ const doUploadFiles = (formData: FormData, headers: Record<string, string>) => {
205
259
  uploadStatus.value = {
206
260
  type: 'error',
207
261
  message: errorMessage,
262
+ response: err,
208
263
  }
209
264
  emit('upload-complete', files.value)
210
265
  emit('upload-error', uploadStatus.value)
@@ -226,20 +281,26 @@ watch(files, (newFiles) => {
226
281
  }
227
282
  })
228
283
 
229
- watch(() => props.doUpload, (newDoUpload) => {
230
- if (files.value && newDoUpload) {
231
- startUpload.value = newDoUpload;
232
- } else {
233
- emit('files-selected', files.value)
234
- startUpload.value = false;
235
- }
236
- })
284
+ watch(
285
+ () => props.doUpload,
286
+ (newDoUpload) => {
287
+ if (files.value && newDoUpload) {
288
+ startUpload.value = newDoUpload
289
+ } else {
290
+ emit('files-selected', files.value)
291
+ startUpload.value = false
292
+ }
293
+ },
294
+ )
237
295
 
238
- watch(() => startUpload.value, (newStartUpload) => {
239
- if (newStartUpload) {
240
- uploadFiles()
241
- }
242
- })
296
+ watch(
297
+ () => startUpload.value,
298
+ (newStartUpload) => {
299
+ if (newStartUpload) {
300
+ uploadFiles()
301
+ }
302
+ },
303
+ )
243
304
  </script>
244
305
 
245
306
  <style scoped>
@@ -253,7 +314,6 @@ watch(() => startUpload.value, (newStartUpload) => {
253
314
  grid-template-columns: 10em calc(100% - 10em);
254
315
  gap: 1rem;
255
316
  }
256
-
257
317
  }
258
318
 
259
319
  .upload-area {
@@ -299,6 +359,11 @@ watch(() => startUpload.value, (newStartUpload) => {
299
359
  grid-template-rows: 1fr 1fr;
300
360
  text-align: center;
301
361
  gap: 1rem;
362
+
363
+ .max-size {
364
+ font-size: 0.75rem;
365
+ color: var(--file-upload-color);
366
+ }
302
367
  }
303
368
 
304
369
  .upload-content :deep(svg) {