@a-vision-software/vue-input-components 1.4.21 → 1.4.23
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/dist/src/index.d.ts +8 -10
- package/dist/src/types/index.d.ts +1 -3
- package/dist/src/types/list.d.ts +2 -2
- package/dist/src/types/textinput.d.ts +2 -1
- package/dist/vue-input-components.cjs.js +2 -2
- package/dist/vue-input-components.css +1 -1
- package/dist/vue-input-components.es.js +3024 -3007
- package/dist/vue-input-components.umd.js +2 -2
- package/package.json +1 -1
- package/src/components/FileUpload.vue +1 -1
- package/src/components/List.vue +2 -2
- package/src/components/Navigation.vue +162 -110
- package/src/components/TextInput.vue +88 -28
- package/src/index.ts +8 -10
- package/src/types/index.ts +1 -3
- package/src/types/list.ts +2 -2
- package/src/types/textinput.ts +3 -3
- package/src/views/TextInputTestView.vue +11 -0
package/package.json
CHANGED
|
@@ -106,7 +106,7 @@ const formatFileSize = (bytes: number): string => {
|
|
|
106
106
|
const k = 1024
|
|
107
107
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
108
108
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
109
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
109
|
+
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
const validateFileSize = (file: File): boolean => {
|
package/src/components/List.vue
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
35
|
<!-- Actions -->
|
|
36
|
-
<div v-if="actions?.length" class="list__actions">
|
|
36
|
+
<div v-if="actions?.length || props.CSVDownload" class="list__actions">
|
|
37
37
|
<Action
|
|
38
38
|
v-for="(action, actionIndex) in [
|
|
39
39
|
...(props.CSVDownload !== undefined
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
]
|
|
49
49
|
: []),
|
|
50
|
-
...actions,
|
|
50
|
+
...(actions || []),
|
|
51
51
|
]"
|
|
52
52
|
:key="actionIndex"
|
|
53
53
|
v-bind="action"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<nav
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
<nav
|
|
3
|
+
:class="[
|
|
4
|
+
'navigation',
|
|
5
|
+
`navigation--${type}`,
|
|
6
|
+
`navigation--${orientation}`,
|
|
7
|
+
{ 'navigation--large-icons': iconSize === 'large' },
|
|
8
|
+
]"
|
|
9
|
+
:style="{
|
|
8
10
|
'--navigation-color': color,
|
|
9
11
|
'--navigation-hover-color': hoverColor,
|
|
10
12
|
'--navigation-active-color': activeColor,
|
|
@@ -21,61 +23,101 @@
|
|
|
21
23
|
: 'none',
|
|
22
24
|
'--navigation-tiles-grid': navigationGrid,
|
|
23
25
|
'max-height': height,
|
|
24
|
-
}"
|
|
26
|
+
}"
|
|
27
|
+
>
|
|
25
28
|
<template v-if="type === 'tiles'">
|
|
26
29
|
<div class="navigation__tiles">
|
|
27
|
-
<div
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
|
|
30
|
+
<div
|
|
31
|
+
v-for="(item, index) in sortedItems"
|
|
32
|
+
:key="item.id"
|
|
33
|
+
class="navigation__tile"
|
|
34
|
+
:class="[
|
|
35
|
+
{ 'navigation__tile--active': item.id === activeItem },
|
|
36
|
+
{ 'navigation__tile--disabled': item.disabled },
|
|
37
|
+
{ 'navigation__tile--right': item.alignment === 'right' },
|
|
38
|
+
{ 'navigation__tile--open': isDropdownOpen(item.id) },
|
|
39
|
+
{ 'navigation__tile--spacer': item.id.includes('spacer') },
|
|
40
|
+
]"
|
|
41
|
+
:style="{
|
|
42
|
+
'--item-alignment': item.alignment || activeItemAlignment,
|
|
43
|
+
width: item.width || '150px',
|
|
44
|
+
'min-width': item.width || '150px',
|
|
45
|
+
'max-width': item.width || '150px',
|
|
46
|
+
'grid-column': item.alignment === 'right' ? `${index - items.length}` : `auto`,
|
|
47
|
+
}"
|
|
48
|
+
@click="(e) => !item.id.includes('spacer') && handleItemClick(item, e)"
|
|
49
|
+
>
|
|
50
|
+
<div
|
|
51
|
+
class="navigation__tile-content"
|
|
52
|
+
:class="{
|
|
53
|
+
'navigation__tile-content--icon-only': !item.label,
|
|
54
|
+
'navigation__tile-content--large-icon': iconSize === 'large' && item.icon,
|
|
55
|
+
}"
|
|
56
|
+
>
|
|
44
57
|
<div v-if="item.icon" class="navigation__icon">
|
|
45
|
-
<img
|
|
46
|
-
|
|
58
|
+
<img
|
|
59
|
+
v-if="item.icon.startsWith('img:')"
|
|
60
|
+
:src="item.icon.substring(4)"
|
|
61
|
+
:alt="item.label || 'Icon'"
|
|
62
|
+
class="navigation__icon-image"
|
|
63
|
+
/>
|
|
47
64
|
<font-awesome-icon v-else :icon="item.icon" />
|
|
48
65
|
</div>
|
|
49
|
-
<div
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
<div
|
|
67
|
+
v-if="item.label"
|
|
68
|
+
class="navigation__label"
|
|
69
|
+
:class="{
|
|
70
|
+
'navigation__label--small': item.labelSize === 'small',
|
|
71
|
+
'navigation__label--large': item.labelSize === 'large',
|
|
72
|
+
}"
|
|
73
|
+
>
|
|
53
74
|
<span>{{ item.label }}</span>
|
|
54
75
|
<div v-if="item.children" class="navigation__dropdown-arrow">
|
|
55
76
|
<font-awesome-icon icon="chevron-down" />
|
|
56
77
|
</div>
|
|
57
78
|
</div>
|
|
58
79
|
</div>
|
|
59
|
-
<div
|
|
60
|
-
|
|
80
|
+
<div
|
|
81
|
+
v-if="item.url && parseInt(height || '0') >= 80 && !item.hideExternalOpen"
|
|
82
|
+
class="navigation__external-link"
|
|
83
|
+
@click.stop="openUrl(item.url)"
|
|
84
|
+
>
|
|
61
85
|
<font-awesome-icon icon="square-up-right" />
|
|
62
86
|
</div>
|
|
63
|
-
<div
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'navigation__dropdown-
|
|
69
|
-
}"
|
|
87
|
+
<div
|
|
88
|
+
v-if="item.children && isDropdownOpen(item.id)"
|
|
89
|
+
class="navigation__dropdown-content"
|
|
90
|
+
:class="{
|
|
91
|
+
'navigation__dropdown-content--start': item.alignment === 'start',
|
|
92
|
+
'navigation__dropdown-content--end': item.alignment === 'end',
|
|
93
|
+
}"
|
|
94
|
+
>
|
|
95
|
+
<div
|
|
96
|
+
v-for="child in item.children"
|
|
97
|
+
:key="child.id"
|
|
98
|
+
class="navigation__dropdown-item"
|
|
99
|
+
:class="{
|
|
100
|
+
'navigation__dropdown-item--disabled': child.disabled,
|
|
101
|
+
}"
|
|
102
|
+
@click="(e) => handleItemClick(child, e)"
|
|
103
|
+
>
|
|
70
104
|
<div v-if="child.icon" class="navigation__icon">
|
|
71
|
-
<img
|
|
72
|
-
|
|
105
|
+
<img
|
|
106
|
+
v-if="child.icon.startsWith('img:')"
|
|
107
|
+
:src="child.icon.substring(4)"
|
|
108
|
+
:alt="child.label || 'Icon'"
|
|
109
|
+
class="navigation__icon-image"
|
|
110
|
+
/>
|
|
73
111
|
<font-awesome-icon v-else :icon="child.icon" />
|
|
74
112
|
</div>
|
|
75
|
-
<div
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
<div
|
|
114
|
+
v-if="child.label"
|
|
115
|
+
class="navigation__label"
|
|
116
|
+
:class="{
|
|
117
|
+
'navigation__label--small': child.labelSize === 'small',
|
|
118
|
+
'navigation__label--large': child.labelSize === 'large',
|
|
119
|
+
}"
|
|
120
|
+
>
|
|
79
121
|
{{ child.label }}
|
|
80
122
|
</div>
|
|
81
123
|
</div>
|
|
@@ -86,54 +128,93 @@
|
|
|
86
128
|
|
|
87
129
|
<template v-else>
|
|
88
130
|
<div class="navigation__dropdowns">
|
|
89
|
-
<div
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
131
|
+
<div
|
|
132
|
+
v-for="item in items"
|
|
133
|
+
:key="item.id"
|
|
134
|
+
class="navigation__dropdown"
|
|
135
|
+
:class="[
|
|
136
|
+
{ 'navigation__dropdown--active': item.id === activeItem },
|
|
137
|
+
{ 'navigation__dropdown--disabled': item.disabled },
|
|
138
|
+
{ 'navigation__dropdown--start': item.alignment === 'start' },
|
|
139
|
+
{ 'navigation__dropdown--end': item.alignment === 'end' },
|
|
140
|
+
{ 'navigation__dropdown--open': isDropdownOpen(item.id) },
|
|
141
|
+
]"
|
|
142
|
+
:style="{
|
|
143
|
+
'--item-alignment': item.alignment || activeItemAlignment,
|
|
144
|
+
}"
|
|
145
|
+
>
|
|
146
|
+
<div
|
|
147
|
+
class="navigation__dropdown-header"
|
|
148
|
+
:class="{
|
|
149
|
+
'navigation__dropdown-header--icon-only': !item.label,
|
|
150
|
+
'navigation__dropdown-header--large-icon': iconSize === 'large' && item.icon,
|
|
151
|
+
}"
|
|
152
|
+
@click="(e) => handleItemClick(item, e)"
|
|
153
|
+
>
|
|
102
154
|
<div v-if="item.icon" class="navigation__icon">
|
|
103
|
-
<img
|
|
104
|
-
|
|
155
|
+
<img
|
|
156
|
+
v-if="item.icon.startsWith('img:')"
|
|
157
|
+
:src="item.icon.substring(4)"
|
|
158
|
+
:alt="item.label || 'Icon'"
|
|
159
|
+
class="navigation__icon-image"
|
|
160
|
+
/>
|
|
105
161
|
<font-awesome-icon v-else :icon="item.icon" />
|
|
106
162
|
</div>
|
|
107
|
-
<div
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
163
|
+
<div
|
|
164
|
+
v-if="item.label"
|
|
165
|
+
class="navigation__label"
|
|
166
|
+
:class="{
|
|
167
|
+
'navigation__label--small': item.labelSize === 'small',
|
|
168
|
+
'navigation__label--large': item.labelSize === 'large',
|
|
169
|
+
}"
|
|
170
|
+
>
|
|
111
171
|
<span>{{ item.label }}</span>
|
|
112
172
|
<div v-if="item.children" class="navigation__dropdown-arrow">
|
|
113
173
|
<font-awesome-icon icon="chevron-down" />
|
|
114
174
|
</div>
|
|
115
175
|
</div>
|
|
116
176
|
</div>
|
|
117
|
-
<div
|
|
118
|
-
|
|
177
|
+
<div
|
|
178
|
+
v-if="item.url && parseInt(height || '0') >= 80 && !item.hideExternalOpen"
|
|
179
|
+
class="navigation__external-link"
|
|
180
|
+
@click.stop="openUrl(item.url)"
|
|
181
|
+
>
|
|
119
182
|
<font-awesome-icon icon="square-up-right" />
|
|
120
183
|
</div>
|
|
121
|
-
<div
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
'navigation__dropdown-
|
|
127
|
-
}"
|
|
184
|
+
<div
|
|
185
|
+
v-if="item.children && isDropdownOpen(item.id)"
|
|
186
|
+
class="navigation__dropdown-content"
|
|
187
|
+
:class="{
|
|
188
|
+
'navigation__dropdown-content--start': item.alignment === 'start',
|
|
189
|
+
'navigation__dropdown-content--end': item.alignment === 'end',
|
|
190
|
+
}"
|
|
191
|
+
>
|
|
192
|
+
<div
|
|
193
|
+
v-for="child in item.children"
|
|
194
|
+
:key="child.id"
|
|
195
|
+
class="navigation__dropdown-item"
|
|
196
|
+
:class="{
|
|
197
|
+
'navigation__dropdown-item--disabled': child.disabled,
|
|
198
|
+
}"
|
|
199
|
+
@click="(e) => handleItemClick(child, e)"
|
|
200
|
+
>
|
|
128
201
|
<div v-if="child.icon" class="navigation__icon">
|
|
129
|
-
<img
|
|
130
|
-
|
|
202
|
+
<img
|
|
203
|
+
v-if="child.icon.startsWith('img:')"
|
|
204
|
+
:src="child.icon.substring(4)"
|
|
205
|
+
:alt="child.label || 'Icon'"
|
|
206
|
+
class="navigation__icon-image"
|
|
207
|
+
/>
|
|
131
208
|
<font-awesome-icon v-else :icon="child.icon" />
|
|
132
209
|
</div>
|
|
133
|
-
<div
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
210
|
+
<div
|
|
211
|
+
v-if="child.label"
|
|
212
|
+
class="navigation__label"
|
|
213
|
+
:class="{
|
|
214
|
+
'navigation__label--small': child.labelSize === 'small',
|
|
215
|
+
'navigation__label--large': child.labelSize === 'large',
|
|
216
|
+
}"
|
|
217
|
+
>
|
|
137
218
|
{{ child.label }}
|
|
138
219
|
</div>
|
|
139
220
|
</div>
|
|
@@ -319,6 +400,7 @@ onUnmounted(() => {
|
|
|
319
400
|
.navigation__dropdown-header {
|
|
320
401
|
display: flex;
|
|
321
402
|
align-items: center;
|
|
403
|
+
width: 100%;
|
|
322
404
|
padding: var(--navigation-padding);
|
|
323
405
|
cursor: pointer;
|
|
324
406
|
transition: all 0.2s ease;
|
|
@@ -392,7 +474,6 @@ onUnmounted(() => {
|
|
|
392
474
|
}
|
|
393
475
|
|
|
394
476
|
.navigation__dropdown-item .navigation__icon {
|
|
395
|
-
margin-left: 0;
|
|
396
477
|
margin: 0.5rem !important;
|
|
397
478
|
font-size: 1.2rem !important;
|
|
398
479
|
}
|
|
@@ -468,26 +549,6 @@ onUnmounted(() => {
|
|
|
468
549
|
}
|
|
469
550
|
|
|
470
551
|
/* Update content layouts */
|
|
471
|
-
.navigation__tile-content,
|
|
472
|
-
.navigation__dropdown-header {
|
|
473
|
-
display: flex;
|
|
474
|
-
align-items: center;
|
|
475
|
-
width: 100%;
|
|
476
|
-
justify-content: flex-start;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
.navigation__tile-content {
|
|
480
|
-
display: flex;
|
|
481
|
-
align-items: center;
|
|
482
|
-
width: 100%;
|
|
483
|
-
flex-direction: row;
|
|
484
|
-
justify-content: center;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
.navigation__dropdown-header {
|
|
488
|
-
flex-direction: row;
|
|
489
|
-
justify-content: flex-start;
|
|
490
|
-
}
|
|
491
552
|
|
|
492
553
|
.navigation__tile--spacer {
|
|
493
554
|
cursor: default;
|
|
@@ -538,11 +599,6 @@ onUnmounted(() => {
|
|
|
538
599
|
text-align: center;
|
|
539
600
|
}
|
|
540
601
|
|
|
541
|
-
.navigation__dropdown-item .navigation__label {
|
|
542
|
-
text-align: center;
|
|
543
|
-
justify-content: center;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
602
|
.navigation__tile--open .navigation__dropdown-arrow,
|
|
547
603
|
.navigation__dropdown--open .navigation__dropdown-arrow {
|
|
548
604
|
transform: rotate(180deg);
|
|
@@ -560,6 +616,7 @@ onUnmounted(() => {
|
|
|
560
616
|
min-width: 100%;
|
|
561
617
|
background: white;
|
|
562
618
|
border: 1px solid var(--navigation-color);
|
|
619
|
+
border-radius: var(--navigation-border-radius);
|
|
563
620
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
564
621
|
z-index: 10;
|
|
565
622
|
}
|
|
@@ -569,11 +626,6 @@ onUnmounted(() => {
|
|
|
569
626
|
right: 0;
|
|
570
627
|
}
|
|
571
628
|
|
|
572
|
-
.navigation__tile--open .navigation__dropdown-content,
|
|
573
|
-
.navigation__dropdown--open .navigation__dropdown-content {
|
|
574
|
-
border-radius: var(--navigation-border-radius);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
629
|
.navigation__tile .navigation__dropdown-item {
|
|
578
630
|
min-height: 3rem;
|
|
579
631
|
display: flex;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:class="{
|
|
5
5
|
'text-input--disabled': disabled,
|
|
6
6
|
'text-input--has-error': error,
|
|
7
|
-
'text-input--date': type === 'date',
|
|
7
|
+
'text-input--date': type === 'date' || type === 'datetime',
|
|
8
8
|
[`label-${labelPosition}`]: label,
|
|
9
9
|
[`label-align-${labelAlign}`]: label,
|
|
10
10
|
'text-input--has-icon': icon,
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
<font-awesome-icon :icon="icon" />
|
|
35
35
|
</div>
|
|
36
36
|
<Datepicker
|
|
37
|
-
v-if="type === 'date'"
|
|
37
|
+
v-if="type === 'date' || type === 'datetime'"
|
|
38
38
|
:id="id"
|
|
39
39
|
v-model="dateValue"
|
|
40
40
|
:placeholder="placeholder"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
:min-date="min"
|
|
44
44
|
:max-date="max"
|
|
45
45
|
:format="dateFormat"
|
|
46
|
-
:enable-time-picker="
|
|
46
|
+
:enable-time-picker="type === 'datetime'"
|
|
47
47
|
:auto-apply="true"
|
|
48
48
|
:close-on-auto-apply="true"
|
|
49
49
|
:clearable="true"
|
|
@@ -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',
|
|
@@ -194,14 +199,18 @@ const currencyFormatter = computed(() => {
|
|
|
194
199
|
const formattedMoney = computed(() => {
|
|
195
200
|
if (props.type !== 'money') return ''
|
|
196
201
|
const numericValue =
|
|
197
|
-
typeof props.modelValue === 'number'
|
|
202
|
+
typeof props.modelValue === 'number'
|
|
203
|
+
? props.modelValue
|
|
204
|
+
: Number.parseFloat(String(props.modelValue))
|
|
198
205
|
if (!Number.isFinite(numericValue)) {
|
|
199
206
|
return ''
|
|
200
207
|
}
|
|
201
208
|
return currencyFormatter.value.format(numericValue)
|
|
202
209
|
})
|
|
203
210
|
|
|
204
|
-
const dateFormat =
|
|
211
|
+
const dateFormat = computed(() => {
|
|
212
|
+
return props.type === 'datetime' ? 'dd/MM/yyyy HH:mm' : 'dd/MM/yyyy'
|
|
213
|
+
})
|
|
205
214
|
|
|
206
215
|
const labelStyle = computed(() => {
|
|
207
216
|
if (!props.label) return {}
|
|
@@ -218,12 +227,35 @@ const formatDateForModel = (date: Date | null): string => {
|
|
|
218
227
|
const day = String(date.getDate()).padStart(2, '0')
|
|
219
228
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
220
229
|
const year = date.getFullYear()
|
|
230
|
+
|
|
231
|
+
if (props.type === 'datetime') {
|
|
232
|
+
const hours = String(date.getHours()).padStart(2, '0')
|
|
233
|
+
const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
234
|
+
return `${year}-${month}-${day}T${hours}:${minutes}`
|
|
235
|
+
}
|
|
236
|
+
|
|
221
237
|
return `${year}-${month}-${day}`
|
|
222
238
|
}
|
|
223
239
|
|
|
224
240
|
const parseDateFromModel = (dateStr: string): Date | null => {
|
|
225
241
|
if (!dateStr) return null
|
|
226
|
-
|
|
242
|
+
|
|
243
|
+
// Handle ISO format with T separator (e.g., "2023-06-22T13:29:00")
|
|
244
|
+
if (dateStr.includes('T')) {
|
|
245
|
+
const [datePart, timePart] = dateStr.split('T')
|
|
246
|
+
const [year, month, day] = datePart.split('-').map(Number)
|
|
247
|
+
const [hours, minutes] = timePart.split(':').map(Number)
|
|
248
|
+
return new Date(year, month - 1, day, hours, minutes)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Handle space-separated format from backend (e.g., "2023-06-22 13:29:00")
|
|
252
|
+
if (dateStr.includes(' ')) {
|
|
253
|
+
const [datePart, timePart] = dateStr.split(' ')
|
|
254
|
+
const [year, month, day] = datePart.split('-').map(Number)
|
|
255
|
+
const [hours, minutes] = timePart.split(':').map(Number)
|
|
256
|
+
return new Date(year, month - 1, day, hours, minutes)
|
|
257
|
+
}
|
|
258
|
+
|
|
227
259
|
const [year, month, day] = dateStr.split('-').map(Number)
|
|
228
260
|
return new Date(year, month - 1, day)
|
|
229
261
|
}
|
|
@@ -246,10 +278,7 @@ const handleAutosave = async (value: string) => {
|
|
|
246
278
|
}
|
|
247
279
|
}
|
|
248
280
|
|
|
249
|
-
const
|
|
250
|
-
if (debounceTimer.value) {
|
|
251
|
-
clearTimeout(debounceTimer.value)
|
|
252
|
-
}
|
|
281
|
+
const showChangedIndicator = () => {
|
|
253
282
|
if (changedTimer.value) {
|
|
254
283
|
clearTimeout(changedTimer.value)
|
|
255
284
|
}
|
|
@@ -262,10 +291,6 @@ const debounceAutosave = (value: string) => {
|
|
|
262
291
|
emit('changed')
|
|
263
292
|
isChanged.value = true
|
|
264
293
|
}, 500)
|
|
265
|
-
|
|
266
|
-
debounceTimer.value = window.setTimeout(() => {
|
|
267
|
-
handleAutosave(value)
|
|
268
|
-
}, 1500)
|
|
269
294
|
}
|
|
270
295
|
|
|
271
296
|
const focusInput = () => {
|
|
@@ -287,7 +312,9 @@ const adjustHeight = (element: HTMLTextAreaElement) => {
|
|
|
287
312
|
const inputValue = computed(() => {
|
|
288
313
|
if (props.type === 'money') {
|
|
289
314
|
const numericValue =
|
|
290
|
-
typeof props.modelValue === 'number'
|
|
315
|
+
typeof props.modelValue === 'number'
|
|
316
|
+
? props.modelValue
|
|
317
|
+
: Number.parseFloat(String(props.modelValue))
|
|
291
318
|
return Number.isFinite(numericValue) ? numericValue.toString() : ''
|
|
292
319
|
}
|
|
293
320
|
return String(props.modelValue)
|
|
@@ -297,14 +324,14 @@ const handleInput = (event: Event) => {
|
|
|
297
324
|
const value = (event.target as HTMLTextAreaElement).value
|
|
298
325
|
|
|
299
326
|
if (props.type === 'money') {
|
|
300
|
-
const parsed = parseFloat(value)
|
|
327
|
+
const parsed = Number.parseFloat(value)
|
|
301
328
|
if (!Number.isNaN(parsed)) {
|
|
302
329
|
emit('update:modelValue', parsed)
|
|
303
|
-
|
|
330
|
+
showChangedIndicator()
|
|
304
331
|
}
|
|
305
332
|
} else {
|
|
306
333
|
emit('update:modelValue', value)
|
|
307
|
-
|
|
334
|
+
showChangedIndicator()
|
|
308
335
|
if (props.type === 'textarea' && (event.target as HTMLTextAreaElement).tagName === 'TEXTAREA') {
|
|
309
336
|
adjustHeight(event.target as HTMLTextAreaElement)
|
|
310
337
|
}
|
|
@@ -313,6 +340,10 @@ const handleInput = (event: Event) => {
|
|
|
313
340
|
|
|
314
341
|
const handleFocus = () => {
|
|
315
342
|
isFocused.value = true
|
|
343
|
+
originalValue.value = props.modelValue
|
|
344
|
+
if (props.type === 'date' || props.type === 'datetime') {
|
|
345
|
+
originalDateValue.value = dateValue.value
|
|
346
|
+
}
|
|
316
347
|
emit('focus')
|
|
317
348
|
}
|
|
318
349
|
|
|
@@ -320,7 +351,7 @@ const handleBlur = (event?: Event) => {
|
|
|
320
351
|
if (props.type === 'money') {
|
|
321
352
|
const target = event && event.target instanceof HTMLInputElement ? event.target : inputRef.value
|
|
322
353
|
if (target) {
|
|
323
|
-
const parsed = parseFloat(target.value.replace(/,/g, ''))
|
|
354
|
+
const parsed = Number.parseFloat(target.value.replace(/,/g, ''))
|
|
324
355
|
if (!Number.isNaN(parsed)) {
|
|
325
356
|
emit('update:modelValue', parsed)
|
|
326
357
|
}
|
|
@@ -328,9 +359,41 @@ const handleBlur = (event?: Event) => {
|
|
|
328
359
|
}
|
|
329
360
|
isFocused.value = false
|
|
330
361
|
emit('blur')
|
|
362
|
+
|
|
363
|
+
if (props.autosaveOnBlur && props.autosave) {
|
|
364
|
+
const value = String(props.modelValue)
|
|
365
|
+
handleAutosave(value)
|
|
366
|
+
}
|
|
331
367
|
}
|
|
332
368
|
|
|
333
369
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
370
|
+
if (event.key === 'Escape') {
|
|
371
|
+
event.preventDefault()
|
|
372
|
+
emit('update:modelValue', originalValue.value)
|
|
373
|
+
if (props.type === 'date' || props.type === 'datetime') {
|
|
374
|
+
dateValue.value = originalDateValue.value
|
|
375
|
+
}
|
|
376
|
+
if (changedTimer.value) {
|
|
377
|
+
clearTimeout(changedTimer.value)
|
|
378
|
+
changedTimer.value = null
|
|
379
|
+
}
|
|
380
|
+
showSaved.value = false
|
|
381
|
+
showChanged.value = false
|
|
382
|
+
inputRef.value?.blur()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (event.key === 'Enter') {
|
|
386
|
+
if (props.type === 'textarea') {
|
|
387
|
+
if (event.ctrlKey || event.shiftKey) {
|
|
388
|
+
event.preventDefault()
|
|
389
|
+
inputRef.value?.blur()
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
event.preventDefault()
|
|
393
|
+
inputRef.value?.blur()
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
334
397
|
emit('keydown', event)
|
|
335
398
|
}
|
|
336
399
|
|
|
@@ -339,7 +402,7 @@ const handlePaste = (event: ClipboardEvent) => {
|
|
|
339
402
|
event.preventDefault()
|
|
340
403
|
const text = event.clipboardData?.getData('text') || ''
|
|
341
404
|
const cleaned = text.replace(/[^0-9+\-\.]/g, '')
|
|
342
|
-
const parsed = parseFloat(cleaned)
|
|
405
|
+
const parsed = Number.parseFloat(cleaned)
|
|
343
406
|
if (!Number.isNaN(parsed) && inputRef.value) {
|
|
344
407
|
inputRef.value.value = parsed.toString()
|
|
345
408
|
emit('update:modelValue', parsed)
|
|
@@ -349,13 +412,10 @@ const handlePaste = (event: ClipboardEvent) => {
|
|
|
349
412
|
const handleDateChange = (date: Date | null) => {
|
|
350
413
|
const formattedDate = formatDateForModel(date)
|
|
351
414
|
emit('update:modelValue', formattedDate)
|
|
352
|
-
|
|
415
|
+
showChangedIndicator()
|
|
353
416
|
}
|
|
354
417
|
|
|
355
418
|
onUnmounted(() => {
|
|
356
|
-
if (debounceTimer.value) {
|
|
357
|
-
clearTimeout(debounceTimer.value)
|
|
358
|
-
}
|
|
359
419
|
if (changedTimer.value) {
|
|
360
420
|
clearTimeout(changedTimer.value)
|
|
361
421
|
}
|
|
@@ -363,15 +423,15 @@ onUnmounted(() => {
|
|
|
363
423
|
|
|
364
424
|
onMounted(() => {
|
|
365
425
|
id.value = `text-input-${Math.random().toString(36).substring(2, 9)}`
|
|
366
|
-
if (props.type === 'date' && props.modelValue) {
|
|
367
|
-
dateValue.value = parseDateFromModel(props.modelValue)
|
|
426
|
+
if ((props.type === 'date' || props.type === 'datetime') && props.modelValue) {
|
|
427
|
+
dateValue.value = parseDateFromModel(props.modelValue as string)
|
|
368
428
|
}
|
|
369
429
|
})
|
|
370
430
|
|
|
371
431
|
watch(
|
|
372
432
|
() => props.modelValue,
|
|
373
433
|
(newValue) => {
|
|
374
|
-
if (props.type === 'date' && newValue) {
|
|
434
|
+
if ((props.type === 'date' || props.type === 'datetime') && newValue) {
|
|
375
435
|
dateValue.value = parseDateFromModel(newValue as string)
|
|
376
436
|
}
|
|
377
437
|
},
|