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

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,91 +1,91 @@
1
1
  {
2
- "name": "@a-vision-software/vue-input-components",
3
- "version": "1.4.16",
4
- "description": "A collection of reusable Vue 3 input components with TypeScript support",
5
- "author": "A-Vision Software",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/a-vision/vue-input-components.git"
10
- },
11
- "homepage": "https://a-vision.github.io/vue-input-components/",
12
- "keywords": [
13
- "vue",
14
- "vue3",
15
- "components",
16
- "input",
17
- "form",
18
- "typescript",
19
- "file-upload",
20
- "text-input",
21
- "date-picker",
22
- "dropdown",
23
- "textarea"
24
- ],
25
- "type": "module",
26
- "files": [
27
- "src",
28
- "types",
29
- "dist"
30
- ],
31
- "main": "./dist/vue-input-components.cjs.js",
32
- "module": "./dist/vue-input-components.es.js",
33
- "types": "./dist/src/index.d.ts",
34
- "exports": {
35
- ".": {
36
- "types": "./dist/src/index.d.ts",
37
- "import": "./dist/vue-input-components.es.js",
38
- "require": "./dist/vue-input-components.cjs.js"
2
+ "name": "@a-vision-software/vue-input-components",
3
+ "version": "1.4.17",
4
+ "description": "A collection of reusable Vue 3 input components with TypeScript support",
5
+ "author": "A-Vision Software",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/a-vision/vue-input-components.git"
39
10
  },
40
- "./global": {
41
- "types": "./dist/src/global.d.ts"
11
+ "homepage": "https://a-vision.github.io/vue-input-components/",
12
+ "keywords": [
13
+ "vue",
14
+ "vue3",
15
+ "components",
16
+ "input",
17
+ "form",
18
+ "typescript",
19
+ "file-upload",
20
+ "text-input",
21
+ "date-picker",
22
+ "dropdown",
23
+ "textarea"
24
+ ],
25
+ "type": "module",
26
+ "files": [
27
+ "src",
28
+ "types",
29
+ "dist"
30
+ ],
31
+ "main": "./dist/vue-input-components.cjs.js",
32
+ "module": "./dist/vue-input-components.es.js",
33
+ "types": "./dist/src/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/src/index.d.ts",
37
+ "import": "./dist/vue-input-components.es.js",
38
+ "require": "./dist/vue-input-components.cjs.js"
39
+ },
40
+ "./global": {
41
+ "types": "./dist/src/global.d.ts"
42
+ },
43
+ "./dist/*": "./dist/*",
44
+ "./styles.css": "./dist/vue-input-components.css",
45
+ "./styles": "./dist/vue-input-components.css"
42
46
  },
43
- "./dist/*": "./dist/*",
44
- "./styles.css": "./dist/vue-input-components.css",
45
- "./styles": "./dist/vue-input-components.css"
46
- },
47
- "sideEffects": [
48
- "**/*.css"
49
- ],
50
- "scripts": {
51
- "dev": "vite",
52
- "build": "vite build",
53
- "preview": "vite preview",
54
- "test": "vitest"
55
- },
56
- "peerDependencies": {
57
- "vue": "^3.5.0"
58
- },
59
- "dependencies": {
60
- "@fortawesome/fontawesome-svg-core": "^6.7.0",
61
- "@fortawesome/free-regular-svg-icons": "^6.7.0",
62
- "@fortawesome/free-solid-svg-icons": "^6.7.0",
63
- "@fortawesome/vue-fontawesome": "^3.0.0",
64
- "@vuepic/vue-datepicker": "^11.0.2"
65
- },
66
- "devDependencies": {
67
- "@tsconfig/node20": "^20.1.2",
68
- "@types/node": "^20.11.0",
69
- "@vitejs/plugin-vue": "^5.0.0",
70
- "@vue/eslint-config-prettier": "^9.0.0",
71
- "@vue/eslint-config-typescript": "^12.0.0",
72
- "@vue/test-utils": "^2.4.0",
73
- "@vue/tsconfig": "^0.5.1",
74
- "eslint": "^8.56.0",
75
- "eslint-plugin-vue": "^9.21.0",
76
- "npm-run-all": "^4.1.5",
77
- "prettier": "^3.2.0",
78
- "typescript": "~5.3.0",
79
- "vite": "^5.0.0",
80
- "vite-plugin-dts": "^3.7.0",
81
- "vitest": "^1.2.0",
82
- "vue-router": "^4.2.0",
83
- "vue-tsc": "^1.8.0"
84
- },
85
- "engines": {
86
- "node": ">=18.0.0"
87
- },
88
- "publishConfig": {
89
- "access": "public"
90
- }
91
- }
47
+ "sideEffects": [
48
+ "**/*.css"
49
+ ],
50
+ "scripts": {
51
+ "dev": "vite",
52
+ "build": "vite build",
53
+ "preview": "vite preview",
54
+ "test": "vitest"
55
+ },
56
+ "peerDependencies": {
57
+ "vue": "^3.5.0"
58
+ },
59
+ "dependencies": {
60
+ "@fortawesome/fontawesome-svg-core": "^6.7.0",
61
+ "@fortawesome/free-regular-svg-icons": "^6.7.0",
62
+ "@fortawesome/free-solid-svg-icons": "^6.7.0",
63
+ "@fortawesome/vue-fontawesome": "^3.0.0",
64
+ "@vuepic/vue-datepicker": "^11.0.2"
65
+ },
66
+ "devDependencies": {
67
+ "@tsconfig/node20": "^20.1.2",
68
+ "@types/node": "^20.11.0",
69
+ "@vitejs/plugin-vue": "^5.0.0",
70
+ "@vue/eslint-config-prettier": "^9.0.0",
71
+ "@vue/eslint-config-typescript": "^12.0.0",
72
+ "@vue/test-utils": "^2.4.0",
73
+ "@vue/tsconfig": "^0.5.1",
74
+ "eslint": "^8.56.0",
75
+ "eslint-plugin-vue": "^9.21.0",
76
+ "npm-run-all": "^4.1.5",
77
+ "prettier": "^3.2.0",
78
+ "typescript": "~5.3.0",
79
+ "vite": "^5.0.0",
80
+ "vite-plugin-dts": "^3.7.0",
81
+ "vitest": "^1.2.0",
82
+ "vue-router": "^4.2.0",
83
+ "vue-tsc": "^1.8.0"
84
+ },
85
+ "engines": {
86
+ "node": ">=18.0.0"
87
+ },
88
+ "publishConfig": {
89
+ "access": "public"
90
+ }
91
+ }
@@ -22,7 +22,16 @@
22
22
  <!-- Actions -->
23
23
  <div v-if="actions?.length" class="list__actions">
24
24
 
25
- <Action v-for="(action, actionIndex) in actions" :key="actionIndex" v-bind="action"
25
+ <Action v-for="(action, actionIndex) in [
26
+ ...(props.CSVDownload !== undefined ? [{
27
+ id: 'csv-download',
28
+ label: 'Download',
29
+ icon: 'file-csv',
30
+ color: 'var(--primary-color)',
31
+ onActionClick: handleCSVDownload
32
+ }] : []),
33
+ ...actions
34
+ ]" :key="actionIndex" v-bind="action"
26
35
  :href="presentation === 'minimal' && !action.href ? `#/${action.id ? action.id : action.label?.toLowerCase()}` : action.href"
27
36
  @click.prevent="action.onActionClick ? action.onActionClick(undefined, action) : null"
28
37
  :size="presentation === 'minimal' ? 'small' : 'regular'" />
@@ -85,6 +94,11 @@
85
94
  <template v-if="column.type === 'text'">
86
95
  {{ row[column.key] }}
87
96
  </template>
97
+ <template v-else-if="column.type === 'email'">
98
+ <a v-if="row['name']" :href="`mailto:${row['name']} <${row[column.key]}>`" @click.stop>{{
99
+ row[column.key] }}</a>
100
+ <a v-else :href="`mailto:${row[column.key]}`" @click.stop>{{ row[column.key] }}</a>
101
+ </template>
88
102
  <template v-else-if="column.type === 'number'">
89
103
  {{ row[column.key] }}
90
104
  </template>
@@ -154,6 +168,43 @@ watch(filterValue, (newValue) => {
154
168
  return () => clearTimeout(timeout)
155
169
  })
156
170
 
171
+ const handleCSVDownload = () => {
172
+ const csv = [];
173
+ const headerRow = [];
174
+ const headerKeys = []
175
+ for (const column of props.columns) {
176
+ const dataType = typeof props.data[0][column.key];
177
+ if (dataType !== 'object') {
178
+ const columnName = column.label || column.key;
179
+ headerRow.push(columnName);
180
+ headerKeys.push(column.key);
181
+ }
182
+ }
183
+ csv.push(headerRow.join(','));
184
+
185
+ for (const row of props.data) {
186
+ const rowData: string[] = [];
187
+ headerKeys.forEach(key => {
188
+ const value = row[key];
189
+ if (value == null) {
190
+ rowData.push('');
191
+ } else {
192
+ rowData.push(value);
193
+ }
194
+ })
195
+ csv.push('"' + rowData.join('","') + '"');
196
+ }
197
+
198
+ const blob = new Blob([csv.join('\n')], { type: 'text/csv;charset=utf-8;' })
199
+ const url = URL.createObjectURL(blob)
200
+ const a = document.createElement('a')
201
+ a.href = url
202
+ const downloadName = props.CSVDownload || 'data';
203
+ a.download = downloadName.toLowerCase().endsWith('.csv') ? downloadName : downloadName + '.csv'
204
+ a.click()
205
+ URL.revokeObjectURL(url)
206
+ }
207
+
157
208
  const handleFilter = () => {
158
209
  // No need to do anything here as the watch handles the filtering
159
210
  }
@@ -203,16 +254,17 @@ const filteredData = computed(() => {
203
254
  const value = row[column.key]
204
255
  if (value == null) return false
205
256
 
257
+ const dateStr = formatDate(value)
258
+ const checkboxValue = value?.modelValue ? 'yes' : 'no'
259
+
206
260
  switch (column.type) {
207
261
  case 'text':
208
262
  return String(value).toLowerCase().includes(effectiveFilterValue.value)
209
263
  case 'number':
210
264
  return String(value).includes(effectiveFilterValue.value)
211
265
  case 'date':
212
- const dateStr = formatDate(value)
213
266
  return dateStr.toLowerCase().includes(effectiveFilterValue.value)
214
267
  case 'checkbox':
215
- const checkboxValue = value?.modelValue ? 'yes' : 'no'
216
268
  return checkboxValue.includes(effectiveFilterValue.value)
217
269
  default:
218
270
  return false
@@ -228,32 +280,29 @@ const sortedAndFilteredData = computed(() => {
228
280
  const column = sortColumn.value!
229
281
  const aValue = a[column.key]
230
282
  const bValue = b[column.key]
283
+ const sortOrder = sortDirection.value === 'asc' ? 1 : -1
231
284
 
232
285
  // Handle different data types
233
286
  if (column.type === 'date') {
234
287
  const dateA = new Date(aValue).getTime()
235
288
  const dateB = new Date(bValue).getTime()
236
- return sortDirection.value === 'asc' ? dateA - dateB : dateB - dateA
289
+ return (dateA - dateB) * sortOrder
237
290
  }
238
291
 
239
292
  if (column.type === 'number') {
240
- return sortDirection.value === 'asc' ? aValue - bValue : bValue - aValue
293
+ return (aValue - bValue) * sortOrder
241
294
  }
242
295
 
243
296
  if (column.type === 'checkbox') {
244
297
  const aChecked = aValue?.modelValue || false
245
298
  const bChecked = bValue?.modelValue || false
246
- return sortDirection.value === 'asc'
247
- ? (aChecked === bChecked ? 0 : aChecked ? 1 : -1)
248
- : (aChecked === bChecked ? 0 : aChecked ? -1 : 1)
299
+ return (aChecked - bChecked) * sortOrder
249
300
  }
250
301
 
251
302
  // Default string comparison for text and other types
252
303
  const stringA = String(aValue || '').toLowerCase()
253
304
  const stringB = String(bValue || '').toLowerCase()
254
- return sortDirection.value === 'asc'
255
- ? stringA.localeCompare(stringB)
256
- : stringB.localeCompare(stringA)
305
+ return stringA.localeCompare(stringB) * sortOrder
257
306
  })
258
307
  })
259
308
 
package/src/types/list.ts CHANGED
@@ -2,7 +2,7 @@ import { ListActionProps } from './action'
2
2
 
3
3
  type ListPresentation = 'default' | 'minimal'
4
4
 
5
- type ListDataType = 'text' | 'number' | 'date' | 'action' | 'checkbox' | 'icon'
5
+ type ListDataType = 'text' | 'number' | 'date' | 'action' | 'checkbox' | 'icon' | 'email'
6
6
 
7
7
  interface ListAction {
8
8
  id: string
@@ -35,6 +35,7 @@ interface ListProps {
35
35
  columns: ListColumn[]
36
36
  data: any[]
37
37
  actions?: ListActionProps[]
38
+ CSVDownload?: string
38
39
  filter?: {
39
40
  placeholder?: string
40
41
  }
@@ -12,14 +12,15 @@
12
12
  <section class="list-test__section">
13
13
  <h2>Default Presentation</h2>
14
14
  <List :actions="listActions" :columns="columns" :data="data" :filter="{ placeholder: 'Search users...' }"
15
- @row-click="handleRowClick" @row-dblclick="handleRowDblClick" />
15
+ @row-click="handleRowClick" @row-dblclick="handleRowDblClick" :CSVDownload="'users'" />
16
16
  </section>
17
17
 
18
18
  <!-- Minimal Presentation -->
19
19
  <section class="list-test__section">
20
20
  <h2>Minimal Presentation</h2>
21
21
  <List :actions="listActions" :columns="columns" :data="data" presentation="minimal" width="50em"
22
- :filter="{ placeholder: 'Search users...' }" @row-click="handleRowClick" @row-dblclick="handleRowDblClick" />
22
+ :filter="{ placeholder: 'Search users...' }" @row-click="handleRowClick" @row-dblclick="handleRowDblClick"
23
+ :CSVDownload="'user-list'" />
23
24
  </section>
24
25
 
25
26
  <!-- Loading State -->
@@ -115,7 +116,7 @@ const columns: ListColumn[] = [
115
116
  {
116
117
  key: 'email',
117
118
  label: 'Email',
118
- type: 'text',
119
+ type: 'email',
119
120
  sortable: true,
120
121
  filterable: true
121
122
  },
@@ -131,6 +132,7 @@ const columns: ListColumn[] = [
131
132
  label: 'Active',
132
133
  type: 'checkbox',
133
134
  align: 'center',
135
+ sortable: true,
134
136
  width: '4rem'
135
137
  },
136
138
  {