@a-vision-software/vue-input-components 1.4.20 → 1.4.22
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/README.md +3 -1
- package/dist/src/types/list.d.ts +11 -2
- package/dist/src/types/textinput.d.ts +2 -0
- package/dist/vue-input-components.cjs.js +2 -2
- package/dist/vue-input-components.css +1 -1
- package/dist/vue-input-components.es.js +2833 -2814
- package/dist/vue-input-components.umd.js +2 -2
- package/package.json +1 -1
- package/src/components/List.vue +52 -28
- package/src/components/TextInput.vue +46 -15
- package/src/types/list.ts +12 -1
- package/src/types/textinput.ts +1 -0
- package/src/views/ListTestView.vue +79 -2
package/package.json
CHANGED
package/src/components/List.vue
CHANGED
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
}"
|
|
97
97
|
@click="handleSort(column)"
|
|
98
98
|
>
|
|
99
|
-
<div class="list__column-header">
|
|
99
|
+
<div class="list__column-header" :title="column.headerTooltip">
|
|
100
100
|
<span>{{ column.label }}</span>
|
|
101
101
|
<span
|
|
102
102
|
v-if="column.sortable"
|
|
@@ -219,7 +219,7 @@ import { ref, computed, watch } from 'vue'
|
|
|
219
219
|
import TextInput from './TextInput.vue'
|
|
220
220
|
import Action from './Action.vue'
|
|
221
221
|
import Checkbox from './Checkbox.vue'
|
|
222
|
-
import type { ListProps, ListEmits, ListColumn, ListAction } from '../types/list'
|
|
222
|
+
import type { ListProps, ListEmits, ListColumn, ListAction, ListRowData } from '../types/list'
|
|
223
223
|
import { config } from '../config'
|
|
224
224
|
|
|
225
225
|
const props = withDefaults(defineProps<ListProps>(), {
|
|
@@ -341,6 +341,9 @@ const filteredData = computed(() => {
|
|
|
341
341
|
if (filterableColumns.length === 0) return props.data
|
|
342
342
|
|
|
343
343
|
return props.data.filter((row) => {
|
|
344
|
+
// Always include rows excluded from filtering
|
|
345
|
+
if (row.excludeFromFilter) return true
|
|
346
|
+
|
|
344
347
|
return filterableColumns.some((column) => {
|
|
345
348
|
const value = row[column.key]
|
|
346
349
|
if (value == null) return false
|
|
@@ -364,36 +367,57 @@ const filteredData = computed(() => {
|
|
|
364
367
|
})
|
|
365
368
|
|
|
366
369
|
const sortedAndFilteredData = computed(() => {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (column.type === 'date') {
|
|
377
|
-
const dateA = new Date(aValue).getTime()
|
|
378
|
-
const dateB = new Date(bValue).getTime()
|
|
379
|
-
return (dateA - dateB) * sortOrder
|
|
370
|
+
// Separate fixed rows (excluded from sorting) from sortable rows
|
|
371
|
+
const fixedRows: ListRowData[] = []
|
|
372
|
+
const sortableRows: ListRowData[] = []
|
|
373
|
+
|
|
374
|
+
filteredData.value.forEach((row) => {
|
|
375
|
+
if (row.excludeFromSort) {
|
|
376
|
+
fixedRows.push(row)
|
|
377
|
+
} else {
|
|
378
|
+
sortableRows.push(row)
|
|
380
379
|
}
|
|
380
|
+
})
|
|
381
381
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
382
|
+
// Sort only the sortable rows if sorting is active
|
|
383
|
+
let sortedRows = sortableRows
|
|
384
|
+
if (sortColumn.value) {
|
|
385
|
+
sortedRows = [...sortableRows].sort((a, b) => {
|
|
386
|
+
const column = sortColumn.value!
|
|
387
|
+
const aValue = a[column.key]
|
|
388
|
+
const bValue = b[column.key]
|
|
389
|
+
const sortOrder = sortDirection.value === 'asc' ? 1 : -1
|
|
390
|
+
|
|
391
|
+
// Handle different data types
|
|
392
|
+
if (column.type === 'date') {
|
|
393
|
+
const dateA = new Date(aValue).getTime()
|
|
394
|
+
const dateB = new Date(bValue).getTime()
|
|
395
|
+
return (dateA - dateB) * sortOrder
|
|
396
|
+
}
|
|
385
397
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return (aChecked - bChecked) * sortOrder
|
|
390
|
-
}
|
|
398
|
+
if (column.type === 'number') {
|
|
399
|
+
return (aValue - bValue) * sortOrder
|
|
400
|
+
}
|
|
391
401
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
if (column.type === 'checkbox') {
|
|
403
|
+
const aChecked = aValue?.modelValue || false
|
|
404
|
+
const bChecked = bValue?.modelValue || false
|
|
405
|
+
return (aChecked - bChecked) * sortOrder
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Default string comparison for text and other types
|
|
409
|
+
const stringA = String(aValue || '').toLowerCase()
|
|
410
|
+
const stringB = String(bValue || '').toLowerCase()
|
|
411
|
+
return stringA.localeCompare(stringB) * sortOrder
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Separate fixed rows by their fixed position
|
|
416
|
+
const topFixedRows = fixedRows.filter((row) => row.fixed === 'top')
|
|
417
|
+
const bottomFixedRows = fixedRows.filter((row) => row.fixed === 'bottom')
|
|
418
|
+
|
|
419
|
+
// Return rows in order: top fixed, sorted, bottom fixed
|
|
420
|
+
return [...topFixedRows, ...sortedRows, ...bottomFixedRows]
|
|
397
421
|
})
|
|
398
422
|
|
|
399
423
|
defineExpose({
|
|
@@ -104,6 +104,9 @@
|
|
|
104
104
|
:disabled="disabled"
|
|
105
105
|
class="text-input__input"
|
|
106
106
|
@input="handleInput"
|
|
107
|
+
@focus="handleFocus"
|
|
108
|
+
@blur="handleBlur"
|
|
109
|
+
@keydown="handleKeydown"
|
|
107
110
|
ref="inputRef"
|
|
108
111
|
:readonly="readonly"
|
|
109
112
|
></textarea>
|
|
@@ -156,6 +159,7 @@ const props = withDefaults(defineProps<TextInputProps>(), {
|
|
|
156
159
|
maxHeight: '14rem',
|
|
157
160
|
bgColor: 'var(--input-color, #ffffffee)',
|
|
158
161
|
width: '100%',
|
|
162
|
+
autosaveOnBlur: true,
|
|
159
163
|
})
|
|
160
164
|
|
|
161
165
|
const emit = defineEmits<{
|
|
@@ -171,11 +175,12 @@ const id = ref<string>('')
|
|
|
171
175
|
const showSaved = ref(false)
|
|
172
176
|
const showChanged = ref(false)
|
|
173
177
|
const isChanged = ref(false)
|
|
174
|
-
const debounceTimer = ref<number | null>(null)
|
|
175
178
|
const changedTimer = ref<number | null>(null)
|
|
176
179
|
const inputRef = ref<HTMLInputElement | null>(null)
|
|
177
180
|
const dateValue = ref<Date | null>(null)
|
|
178
181
|
const isFocused = ref(false)
|
|
182
|
+
const originalValue = ref<string | number>('')
|
|
183
|
+
const originalDateValue = ref<Date | null>(null)
|
|
179
184
|
|
|
180
185
|
const defaultCurrencyFormatter = new Intl.NumberFormat('en-NZ', {
|
|
181
186
|
style: 'currency',
|
|
@@ -246,10 +251,7 @@ const handleAutosave = async (value: string) => {
|
|
|
246
251
|
}
|
|
247
252
|
}
|
|
248
253
|
|
|
249
|
-
const
|
|
250
|
-
if (debounceTimer.value) {
|
|
251
|
-
clearTimeout(debounceTimer.value)
|
|
252
|
-
}
|
|
254
|
+
const showChangedIndicator = () => {
|
|
253
255
|
if (changedTimer.value) {
|
|
254
256
|
clearTimeout(changedTimer.value)
|
|
255
257
|
}
|
|
@@ -262,10 +264,6 @@ const debounceAutosave = (value: string) => {
|
|
|
262
264
|
emit('changed')
|
|
263
265
|
isChanged.value = true
|
|
264
266
|
}, 500)
|
|
265
|
-
|
|
266
|
-
debounceTimer.value = window.setTimeout(() => {
|
|
267
|
-
handleAutosave(value)
|
|
268
|
-
}, 1500)
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
const focusInput = () => {
|
|
@@ -300,11 +298,11 @@ const handleInput = (event: Event) => {
|
|
|
300
298
|
const parsed = parseFloat(value)
|
|
301
299
|
if (!Number.isNaN(parsed)) {
|
|
302
300
|
emit('update:modelValue', parsed)
|
|
303
|
-
|
|
301
|
+
showChangedIndicator()
|
|
304
302
|
}
|
|
305
303
|
} else {
|
|
306
304
|
emit('update:modelValue', value)
|
|
307
|
-
|
|
305
|
+
showChangedIndicator()
|
|
308
306
|
if (props.type === 'textarea' && (event.target as HTMLTextAreaElement).tagName === 'TEXTAREA') {
|
|
309
307
|
adjustHeight(event.target as HTMLTextAreaElement)
|
|
310
308
|
}
|
|
@@ -313,6 +311,10 @@ const handleInput = (event: Event) => {
|
|
|
313
311
|
|
|
314
312
|
const handleFocus = () => {
|
|
315
313
|
isFocused.value = true
|
|
314
|
+
originalValue.value = props.modelValue
|
|
315
|
+
if (props.type === 'date') {
|
|
316
|
+
originalDateValue.value = dateValue.value
|
|
317
|
+
}
|
|
316
318
|
emit('focus')
|
|
317
319
|
}
|
|
318
320
|
|
|
@@ -328,9 +330,41 @@ const handleBlur = (event?: Event) => {
|
|
|
328
330
|
}
|
|
329
331
|
isFocused.value = false
|
|
330
332
|
emit('blur')
|
|
333
|
+
|
|
334
|
+
if (props.autosaveOnBlur && props.autosave) {
|
|
335
|
+
const value = String(props.modelValue)
|
|
336
|
+
handleAutosave(value)
|
|
337
|
+
}
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
341
|
+
if (event.key === 'Escape') {
|
|
342
|
+
event.preventDefault()
|
|
343
|
+
emit('update:modelValue', originalValue.value)
|
|
344
|
+
if (props.type === 'date') {
|
|
345
|
+
dateValue.value = originalDateValue.value
|
|
346
|
+
}
|
|
347
|
+
if (changedTimer.value) {
|
|
348
|
+
clearTimeout(changedTimer.value)
|
|
349
|
+
changedTimer.value = null
|
|
350
|
+
}
|
|
351
|
+
showSaved.value = false
|
|
352
|
+
showChanged.value = false
|
|
353
|
+
inputRef.value?.blur()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (event.key === 'Enter') {
|
|
357
|
+
if (props.type === 'textarea') {
|
|
358
|
+
if (event.ctrlKey || event.shiftKey) {
|
|
359
|
+
event.preventDefault()
|
|
360
|
+
inputRef.value?.blur()
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
event.preventDefault()
|
|
364
|
+
inputRef.value?.blur()
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
334
368
|
emit('keydown', event)
|
|
335
369
|
}
|
|
336
370
|
|
|
@@ -349,13 +383,10 @@ const handlePaste = (event: ClipboardEvent) => {
|
|
|
349
383
|
const handleDateChange = (date: Date | null) => {
|
|
350
384
|
const formattedDate = formatDateForModel(date)
|
|
351
385
|
emit('update:modelValue', formattedDate)
|
|
352
|
-
|
|
386
|
+
showChangedIndicator()
|
|
353
387
|
}
|
|
354
388
|
|
|
355
389
|
onUnmounted(() => {
|
|
356
|
-
if (debounceTimer.value) {
|
|
357
|
-
clearTimeout(debounceTimer.value)
|
|
358
|
-
}
|
|
359
390
|
if (changedTimer.value) {
|
|
360
391
|
clearTimeout(changedTimer.value)
|
|
361
392
|
}
|
package/src/types/list.ts
CHANGED
|
@@ -38,6 +38,7 @@ interface ListColumn {
|
|
|
38
38
|
minWidth?: string
|
|
39
39
|
maxWidth?: string
|
|
40
40
|
cellClasses?: conditionalClassList
|
|
41
|
+
headerTooltip?: string
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
interface ListFilter {
|
|
@@ -47,7 +48,7 @@ interface ListFilter {
|
|
|
47
48
|
|
|
48
49
|
interface ListProps {
|
|
49
50
|
columns: ListColumn[]
|
|
50
|
-
data:
|
|
51
|
+
data: ListRowData[]
|
|
51
52
|
actions?: ListActionProps[]
|
|
52
53
|
CSVDownload?: string
|
|
53
54
|
filter?: {
|
|
@@ -76,6 +77,15 @@ interface ListIconProps {
|
|
|
76
77
|
color?: string
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
interface ListRowData {
|
|
81
|
+
excludeFromSort?: boolean
|
|
82
|
+
excludeFromFilter?: boolean
|
|
83
|
+
fixed?: 'top' | 'bottom'
|
|
84
|
+
selected?: boolean
|
|
85
|
+
class?: string
|
|
86
|
+
[key: string]: any
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
export type {
|
|
80
90
|
ListPresentation,
|
|
81
91
|
ListProps,
|
|
@@ -85,4 +95,5 @@ export type {
|
|
|
85
95
|
ListIconProps,
|
|
86
96
|
ListColumn,
|
|
87
97
|
ListFilter,
|
|
98
|
+
ListRowData,
|
|
88
99
|
}
|
package/src/types/textinput.ts
CHANGED
|
@@ -65,7 +65,13 @@
|
|
|
65
65
|
<script setup lang="ts">
|
|
66
66
|
import { ref } from 'vue'
|
|
67
67
|
import List from '@/components/List.vue'
|
|
68
|
-
import type {
|
|
68
|
+
import type {
|
|
69
|
+
ListColumn,
|
|
70
|
+
ListActionProps,
|
|
71
|
+
ListCheckboxProps,
|
|
72
|
+
ListIconProps,
|
|
73
|
+
ListRowData,
|
|
74
|
+
} from '@/types'
|
|
69
75
|
|
|
70
76
|
const autosaveActive = (info: any) => {
|
|
71
77
|
console.log('Autosave active', info)
|
|
@@ -130,6 +136,7 @@ const columns: ListColumn[] = [
|
|
|
130
136
|
filterable: false,
|
|
131
137
|
align: 'center',
|
|
132
138
|
width: '4rem',
|
|
139
|
+
headerTooltip: 'User status indicator',
|
|
133
140
|
},
|
|
134
141
|
{
|
|
135
142
|
key: 'name',
|
|
@@ -137,6 +144,7 @@ const columns: ListColumn[] = [
|
|
|
137
144
|
type: 'text',
|
|
138
145
|
sortable: true,
|
|
139
146
|
filterable: true,
|
|
147
|
+
headerTooltip: 'The full name of the user',
|
|
140
148
|
},
|
|
141
149
|
{
|
|
142
150
|
key: 'email',
|
|
@@ -144,6 +152,7 @@ const columns: ListColumn[] = [
|
|
|
144
152
|
type: 'email',
|
|
145
153
|
sortable: true,
|
|
146
154
|
filterable: true,
|
|
155
|
+
headerTooltip: 'Contact email address',
|
|
147
156
|
cellClasses: {
|
|
148
157
|
lightredEmail: (value: any) => value.includes('bob'),
|
|
149
158
|
},
|
|
@@ -154,6 +163,7 @@ const columns: ListColumn[] = [
|
|
|
154
163
|
type: 'date',
|
|
155
164
|
sortable: true,
|
|
156
165
|
width: '6rem',
|
|
166
|
+
headerTooltip: 'Account creation date',
|
|
157
167
|
},
|
|
158
168
|
{
|
|
159
169
|
key: 'active',
|
|
@@ -162,6 +172,7 @@ const columns: ListColumn[] = [
|
|
|
162
172
|
align: 'center',
|
|
163
173
|
sortable: true,
|
|
164
174
|
width: '4rem',
|
|
175
|
+
headerTooltip: 'Account active status',
|
|
165
176
|
},
|
|
166
177
|
{
|
|
167
178
|
key: 'actions',
|
|
@@ -172,7 +183,39 @@ const columns: ListColumn[] = [
|
|
|
172
183
|
},
|
|
173
184
|
]
|
|
174
185
|
|
|
175
|
-
const data = ref([
|
|
186
|
+
const data = ref<ListRowData[]>([
|
|
187
|
+
{
|
|
188
|
+
// This row will always appear at the top when sorting
|
|
189
|
+
excludeFromSort: true,
|
|
190
|
+
fixed: 'top',
|
|
191
|
+
selected: false,
|
|
192
|
+
select: <ListCheckboxProps>{
|
|
193
|
+
modelValue: false,
|
|
194
|
+
disabled: false,
|
|
195
|
+
onCheckboxClick: selectClick,
|
|
196
|
+
},
|
|
197
|
+
status: <ListIconProps>{
|
|
198
|
+
icon: 'crown',
|
|
199
|
+
color: 'gold',
|
|
200
|
+
},
|
|
201
|
+
name: 'Current User (Fixed at Top)',
|
|
202
|
+
email: 'current@example.com',
|
|
203
|
+
joined: '2024-01-01',
|
|
204
|
+
active: {
|
|
205
|
+
modelValue: true,
|
|
206
|
+
disabled: false,
|
|
207
|
+
autosave: autosaveActive,
|
|
208
|
+
onCheckboxClick: selectClick,
|
|
209
|
+
},
|
|
210
|
+
actions: <ListActionProps[]>[
|
|
211
|
+
{
|
|
212
|
+
id: 'edit',
|
|
213
|
+
label: 'Edit',
|
|
214
|
+
icon: 'edit',
|
|
215
|
+
onActionClick: rowActionClick,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
176
219
|
{
|
|
177
220
|
class: 'testclass',
|
|
178
221
|
selected: false,
|
|
@@ -390,6 +433,36 @@ const data = ref([
|
|
|
390
433
|
},
|
|
391
434
|
],
|
|
392
435
|
},
|
|
436
|
+
{
|
|
437
|
+
// This row will always appear at the bottom when sorting and is excluded from filtering
|
|
438
|
+
excludeFromSort: true,
|
|
439
|
+
excludeFromFilter: true,
|
|
440
|
+
fixed: 'bottom',
|
|
441
|
+
select: <ListCheckboxProps>{
|
|
442
|
+
modelValue: false,
|
|
443
|
+
disabled: false,
|
|
444
|
+
onCheckboxClick: selectClick,
|
|
445
|
+
},
|
|
446
|
+
status: <ListIconProps>{
|
|
447
|
+
icon: 'info-circle',
|
|
448
|
+
color: 'blue',
|
|
449
|
+
},
|
|
450
|
+
name: 'Footer Row (Fixed at Bottom)',
|
|
451
|
+
email: 'footer@example.com',
|
|
452
|
+
joined: '2024-12-31',
|
|
453
|
+
active: {
|
|
454
|
+
modelValue: true,
|
|
455
|
+
disabled: false,
|
|
456
|
+
},
|
|
457
|
+
actions: <ListActionProps[]>[
|
|
458
|
+
{
|
|
459
|
+
id: 'info',
|
|
460
|
+
label: 'Info',
|
|
461
|
+
icon: 'info',
|
|
462
|
+
onActionClick: rowActionClick,
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
},
|
|
393
466
|
])
|
|
394
467
|
</script>
|
|
395
468
|
|
|
@@ -431,4 +504,8 @@ const data = ref([
|
|
|
431
504
|
:deep(.list-test__section .list__cell.lightredEmail) {
|
|
432
505
|
background-color: rgba(255, 0, 0, 0.1);
|
|
433
506
|
}
|
|
507
|
+
|
|
508
|
+
:deep(.list-test__section .list__row--testclass) {
|
|
509
|
+
background-color: rgba(155, 155, 0, 0.1);
|
|
510
|
+
}
|
|
434
511
|
</style>
|