wave-ui 2.28.1 → 2.31.1
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/wave-ui.cjs.js +1 -1
- package/dist/wave-ui.css +1 -1
- package/dist/wave-ui.es.js +567 -520
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +14 -11
- package/src/wave-ui/components/w-accordion.vue +6 -4
- package/src/wave-ui/components/w-alert.vue +0 -1
- package/src/wave-ui/components/w-app.vue +1 -1
- package/src/wave-ui/components/w-confirm.vue +35 -10
- package/src/wave-ui/components/w-flex.vue +2 -0
- package/src/wave-ui/components/w-input.vue +130 -33
- package/src/wave-ui/components/w-menu.vue +76 -270
- package/src/wave-ui/components/w-notification-manager.vue +9 -2
- package/src/wave-ui/components/w-progress.vue +4 -1
- package/src/wave-ui/components/w-rating.vue +1 -1
- package/src/wave-ui/components/w-select.vue +2 -2
- package/src/wave-ui/components/w-spinner.vue +2 -2
- package/src/wave-ui/components/w-table.vue +2 -2
- package/src/wave-ui/components/w-tag.vue +17 -6
- package/src/wave-ui/components/w-textarea.vue +1 -1
- package/src/wave-ui/components/w-tooltip.vue +121 -308
- package/src/wave-ui/core.js +0 -8
- package/src/wave-ui/mixins/detachable.js +321 -0
- package/src/wave-ui/scss/_mixins.scss +23 -12
- package/src/wave-ui/scss/_variables.scss +1 -1
- package/src/wave-ui/utils/index.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.1",
|
|
4
4
|
"description": "An emerging UI framework for Vue.js & Vue 3 with only the bright side. :sunny:",
|
|
5
5
|
"author": "Antoni Andre <antoniandre.web@gmail.com>",
|
|
6
6
|
"main": "./dist/wave-ui.umd.js",
|
|
@@ -43,29 +43,32 @@
|
|
|
43
43
|
"*.vue"
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@babel/core": "^7.
|
|
47
|
-
"@babel/eslint-parser": "^7.
|
|
48
|
-
"@babel/plugin-proposal-class-properties": "^7.16.
|
|
46
|
+
"@babel/core": "^7.17.0",
|
|
47
|
+
"@babel/eslint-parser": "^7.17.0",
|
|
48
|
+
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
|
49
49
|
"@mdi/font": "^5.9.55",
|
|
50
|
-
"@vitejs/plugin-vue": "^1.
|
|
50
|
+
"@vitejs/plugin-vue": "^1.10.2",
|
|
51
51
|
"@vue/compiler-sfc": "3.1.5",
|
|
52
|
-
"autoprefixer": "^10.4.
|
|
53
|
-
"axios": "^0.
|
|
52
|
+
"autoprefixer": "^10.4.2",
|
|
53
|
+
"axios": "^0.25.0",
|
|
54
54
|
"eslint": "^7.32.0",
|
|
55
55
|
"font-awesome": "^4.7.0",
|
|
56
|
-
"gsap": "^3.
|
|
56
|
+
"gsap": "^3.9.1",
|
|
57
57
|
"ionicons": "^4.6.3",
|
|
58
58
|
"material-design-icons": "^3.0.1",
|
|
59
59
|
"rollup-plugin-delete": "^2.0.0",
|
|
60
|
-
"sass": "^1.
|
|
60
|
+
"sass": "^1.49.7",
|
|
61
61
|
"simple-syntax-highlighter": "^2.2.0",
|
|
62
62
|
"splitpanes": "^3.0.6",
|
|
63
|
-
"vite": "^2.
|
|
63
|
+
"vite": "^2.7.13",
|
|
64
64
|
"vite-plugin-pug": "^0.3.0",
|
|
65
|
-
"vue": "^3.2.
|
|
65
|
+
"vue": "^3.2.31",
|
|
66
66
|
"vue-cal": "^4.2.0",
|
|
67
67
|
"vue-router": "^4.0.12",
|
|
68
68
|
"vueperslides": "^3.3.2",
|
|
69
69
|
"vuex": "^4.0.2"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"postcss": "^8.4.6"
|
|
70
73
|
}
|
|
71
74
|
}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
:tabindex="-1"
|
|
20
20
|
text
|
|
21
21
|
@keypress.stop
|
|
22
|
-
@click.stop="!item._disabled && toggleItem(item, $event)"
|
|
22
|
+
@click.stop="!item._disabled && toggleItem(item, $event)"
|
|
23
|
+
:class="{ 'w-accordion__expand-icon--expanded': item._expanded }")
|
|
23
24
|
//- Title.
|
|
24
25
|
slot(
|
|
25
26
|
v-if="$slots[`item-title.${item.id || i + 1}`]"
|
|
@@ -34,7 +35,8 @@
|
|
|
34
35
|
:icon="(item._expanded && collapseIcon) || expandIcon"
|
|
35
36
|
text
|
|
36
37
|
@keypress.stop
|
|
37
|
-
@click.stop="!item._disabled && toggleItem(item, $event)"
|
|
38
|
+
@click.stop="!item._disabled && toggleItem(item, $event)"
|
|
39
|
+
:class="{ 'w-accordion__expand-icon--expanded': item._expanded }")
|
|
38
40
|
//- Content.
|
|
39
41
|
w-transition-expand(y)
|
|
40
42
|
.w-accordion__item-content(v-if="item._expanded" :class="contentClass")
|
|
@@ -148,8 +150,8 @@ export default {
|
|
|
148
150
|
margin-right: $base-increment;
|
|
149
151
|
|
|
150
152
|
.w-accordion--rotate-icon & {@include default-transition;}
|
|
151
|
-
|
|
152
|
-
.w-accordion--
|
|
153
|
+
&--expanded {transform: rotate(-180deg);}
|
|
154
|
+
.w-accordion--icon-right &--expanded {transform: rotate(180deg);}
|
|
153
155
|
|
|
154
156
|
.w-icon:before {font-size: 1.1em;}
|
|
155
157
|
}
|
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
.w-confirm
|
|
3
3
|
w-menu(v-model="showPopup" v-bind="wMenuProps")
|
|
4
4
|
template(#activator="{ on }")
|
|
5
|
-
w-button.w-confirm__button(v-
|
|
5
|
+
w-button.w-confirm__button(v-bind="{ ...$attrs, ...buttonProps, ...on }")
|
|
6
6
|
slot
|
|
7
7
|
w-flex(:column="!inline" align-center)
|
|
8
8
|
div
|
|
9
|
-
slot(name="question")
|
|
9
|
+
slot(name="question") {{ question }}
|
|
10
10
|
.w-flex.justify-end(:class="inline ? 'ml2' : 'mt2'")
|
|
11
11
|
w-button.mr2(
|
|
12
|
-
v-if="
|
|
13
|
-
v-bind="
|
|
12
|
+
v-if="cancel !== false"
|
|
13
|
+
v-bind="cancelButtonProps"
|
|
14
14
|
:bg-color="(cancelButton || {}).bgColor || 'error'"
|
|
15
15
|
@click="onCancel")
|
|
16
|
-
slot(name="cancel")
|
|
16
|
+
slot(name="cancel") {{ cancelButton.label }}
|
|
17
17
|
w-button(
|
|
18
|
-
v-bind="
|
|
18
|
+
v-bind="confirmButtonProps"
|
|
19
19
|
:bg-color="(confirmButton || {}).bgColor || 'success'"
|
|
20
20
|
@click="onConfirm")
|
|
21
|
-
slot(name="confirm")
|
|
21
|
+
slot(name="confirm") {{ confirmButton.label }}
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script>
|
|
@@ -30,11 +30,16 @@ export default {
|
|
|
30
30
|
color: { type: String },
|
|
31
31
|
icon: { type: String },
|
|
32
32
|
mainButton: { type: Object }, // Allow passing down an object of props to the w-button component.
|
|
33
|
+
question: { type: String, default: 'Are you sure?' },
|
|
33
34
|
|
|
34
35
|
// Cancel & confirm buttons props.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// Allow passing down an object of props to the w-button component.
|
|
37
|
+
// If a string is given, that will be the label of the button.
|
|
38
|
+
// If false, no cancel button.
|
|
39
|
+
cancel: { type: [Boolean, Object, String], default: undefined },
|
|
40
|
+
// Allow passing down an object of props to the w-button component.
|
|
41
|
+
// If a string is given, that will be the label of the button.
|
|
42
|
+
confirm: { type: [Object, String] },
|
|
38
43
|
|
|
39
44
|
// global menu props.
|
|
40
45
|
inline: { type: Boolean }, // The layout inside the menu.
|
|
@@ -63,6 +68,26 @@ export default {
|
|
|
63
68
|
}),
|
|
64
69
|
|
|
65
70
|
computed: {
|
|
71
|
+
cancelButton () {
|
|
72
|
+
let button = { label: typeof this.cancel === 'string' ? this.cancel : 'Cancel' }
|
|
73
|
+
if (typeof this.cancel === 'object') button = Object.assign({}, button, this.cancel)
|
|
74
|
+
return button
|
|
75
|
+
},
|
|
76
|
+
// Props to pass down to the w-button component.
|
|
77
|
+
cancelButtonProps () {
|
|
78
|
+
const { label, ...props } = this.cancelButton // Everything except label.
|
|
79
|
+
return props
|
|
80
|
+
},
|
|
81
|
+
confirmButton () {
|
|
82
|
+
let button = { label: typeof this.confirm === 'string' ? this.confirm : 'Confirm' }
|
|
83
|
+
if (typeof this.confirm === 'object') button = Object.assign({}, button, this.confirm)
|
|
84
|
+
return button
|
|
85
|
+
},
|
|
86
|
+
// Props to pass down to the w-button component.
|
|
87
|
+
confirmButtonProps () {
|
|
88
|
+
const { label, ...props } = this.confirmButton // Everything except label.
|
|
89
|
+
return props
|
|
90
|
+
},
|
|
66
91
|
wMenuProps () {
|
|
67
92
|
return {
|
|
68
93
|
top: this.top,
|
|
@@ -26,6 +26,7 @@ export default {
|
|
|
26
26
|
justifyEnd: { type: Boolean },
|
|
27
27
|
justifySpaceBetween: { type: Boolean },
|
|
28
28
|
justifySpaceAround: { type: Boolean },
|
|
29
|
+
justifySpaceEvenly: { type: Boolean },
|
|
29
30
|
basisZero: { type: Boolean },
|
|
30
31
|
gap: { type: Number, default: 0 }
|
|
31
32
|
},
|
|
@@ -48,6 +49,7 @@ export default {
|
|
|
48
49
|
'justify-end': this.justifyEnd,
|
|
49
50
|
'justify-space-between': this.justifySpaceBetween,
|
|
50
51
|
'justify-space-around': this.justifySpaceAround,
|
|
52
|
+
'justify-space-evenly': this.justifySpaceEvenly,
|
|
51
53
|
'basis-zero': this.basisZero,
|
|
52
54
|
[`w-flex--gap${this.gap}`]: this.gap
|
|
53
55
|
}
|
|
@@ -12,9 +12,16 @@ component(
|
|
|
12
12
|
template(v-else)
|
|
13
13
|
//- Left label.
|
|
14
14
|
template(v-if="labelPosition === 'left'")
|
|
15
|
-
label.w-input__label.w-input__label--left.w-form-el-shakable(
|
|
15
|
+
label.w-input__label.w-input__label--left.w-form-el-shakable(
|
|
16
|
+
v-if="$slots.default"
|
|
17
|
+
:for="`w-input--${_.uid}`"
|
|
18
|
+
:class="validationClasses")
|
|
16
19
|
slot
|
|
17
|
-
label.w-input__label.w-input__label--left.w-form-el-shakable(
|
|
20
|
+
label.w-input__label.w-input__label--left.w-form-el-shakable(
|
|
21
|
+
v-else-if="label"
|
|
22
|
+
:for="`w-input--${_.uid}`"
|
|
23
|
+
:class="validationClasses"
|
|
24
|
+
v-html="label")
|
|
18
25
|
|
|
19
26
|
//- Input wrapper.
|
|
20
27
|
.w-input__input-wrap(:class="inputWrapClasses")
|
|
@@ -23,8 +30,10 @@ component(
|
|
|
23
30
|
tag="label"
|
|
24
31
|
:for="`w-input--${_.uid}`"
|
|
25
32
|
@click="$emit('click:inner-icon-left', $event)") {{ innerIconLeft }}
|
|
33
|
+
//- All types of input except file.
|
|
26
34
|
input.w-input__input(
|
|
27
35
|
v-if="type !== 'file'"
|
|
36
|
+
ref="input"
|
|
28
37
|
v-model="inputValue"
|
|
29
38
|
v-on="listeners"
|
|
30
39
|
@input="onInput"
|
|
@@ -45,8 +54,10 @@ component(
|
|
|
45
54
|
:required="required || null"
|
|
46
55
|
:tabindex="tabindex || null"
|
|
47
56
|
v-bind="attrs")
|
|
48
|
-
|
|
57
|
+
//- Input type file.
|
|
58
|
+
template(v-else)
|
|
49
59
|
input(
|
|
60
|
+
ref="input"
|
|
50
61
|
:id="`w-input--${_.uid}`"
|
|
51
62
|
type="file"
|
|
52
63
|
:name="name || null"
|
|
@@ -54,35 +65,45 @@ component(
|
|
|
54
65
|
@blur="onBlur"
|
|
55
66
|
@change="onFileChange"
|
|
56
67
|
:multiple="multiple || null"
|
|
57
|
-
v-bind="attrs"
|
|
58
|
-
|
|
68
|
+
v-bind="attrs"
|
|
69
|
+
:data-progress="overallFilesProgress /* Needed to emit the overallProgress. */")
|
|
70
|
+
transition-group.w-input__input.w-input__input--file(
|
|
71
|
+
tag="label"
|
|
72
|
+
name="fade"
|
|
73
|
+
:for="`w-input--${_.uid}`")
|
|
59
74
|
span.w-input__no-file(v-if="!inputFiles.length && isFocused" key="no-file")
|
|
60
75
|
slot(name="no-file")
|
|
61
76
|
template(v-if="$slots['no-file'] === undefined") No file
|
|
62
77
|
span(v-for="(file, i) in inputFiles" :key="file.lastModified")
|
|
63
78
|
| {{ i ? ', ': '' }}
|
|
64
79
|
span.filename(:key="`${i}b`") {{ file.base }}
|
|
65
|
-
| {{ file.extension }}
|
|
80
|
+
| {{ file.extension ? `.${file.extension}` : '' }}
|
|
66
81
|
|
|
67
82
|
template(v-if="labelPosition === 'inside' && showLabelInside")
|
|
68
83
|
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
69
84
|
v-if="$slots.default"
|
|
70
85
|
:for="`w-input--${_.uid}`"
|
|
71
|
-
:class="
|
|
86
|
+
:class="validationClasses")
|
|
72
87
|
slot
|
|
73
88
|
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
74
89
|
v-else-if="label"
|
|
75
90
|
:for="`w-input--${_.uid}`"
|
|
76
91
|
v-html="label"
|
|
77
|
-
:class="
|
|
92
|
+
:class="validationClasses")
|
|
78
93
|
w-icon.w-input__icon.w-input__icon--inner-right(
|
|
79
94
|
v-if="innerIconRight"
|
|
80
95
|
tag="label"
|
|
81
96
|
:for="`w-input--${_.uid}`"
|
|
82
97
|
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
83
98
|
|
|
99
|
+
w-progress.fill-width(
|
|
100
|
+
v-if="hasLoading || (showProgress && (uploadInProgress || uploadComplete))"
|
|
101
|
+
size="2"
|
|
102
|
+
:color="progressColor || color"
|
|
103
|
+
:model-value="showProgress ? (uploadInProgress || uploadComplete) && overallFilesProgress : loadingValue")
|
|
104
|
+
|
|
84
105
|
//- Files preview.
|
|
85
|
-
label.d-flex(v-if="type === 'file' && inputFiles.length" :for="`w-input--${_.uid}`")
|
|
106
|
+
label.d-flex(v-if="type === 'file' && preview && inputFiles.length" :for="`w-input--${_.uid}`")
|
|
86
107
|
template(v-for="(file, i) in inputFiles")
|
|
87
108
|
i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
|
|
88
109
|
v-if="file.progress < 100"
|
|
@@ -92,20 +113,23 @@ component(
|
|
|
92
113
|
:key="`${i}b`"
|
|
93
114
|
:src="file.preview"
|
|
94
115
|
alt="")
|
|
95
|
-
i.w-icon.
|
|
116
|
+
i.w-icon.w-input__file-preview.primary.size--md(
|
|
117
|
+
v-else
|
|
118
|
+
:key="`${i}c`"
|
|
119
|
+
:class="preview && typeof preview === 'string' ? preview : 'wi-file'")
|
|
96
120
|
|
|
97
121
|
//- Right label.
|
|
98
122
|
template(v-if="labelPosition === 'right'")
|
|
99
123
|
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
100
124
|
v-if="$slots.default"
|
|
101
|
-
:for="`w-input--${_.uid}`"
|
|
125
|
+
:for="`w-input--${_.uid}`"
|
|
126
|
+
:class="validationClasses")
|
|
102
127
|
slot
|
|
103
128
|
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
104
129
|
v-else-if="label"
|
|
105
130
|
:for="`w-input--${_.uid}`"
|
|
131
|
+
:class="validationClasses"
|
|
106
132
|
v-html="label")
|
|
107
|
-
|
|
108
|
-
w-progress.fill-width(v-if="loading" size="2" :color="progressColor || color")
|
|
109
133
|
</template>
|
|
110
134
|
|
|
111
135
|
<script>
|
|
@@ -144,13 +168,17 @@ export default {
|
|
|
144
168
|
shadow: { type: Boolean },
|
|
145
169
|
tile: { type: Boolean },
|
|
146
170
|
multiple: { type: Boolean }, // Only for file uploads.
|
|
147
|
-
preview: { type: Boolean }, // Only for file uploads.
|
|
148
|
-
loading: { type: Boolean }
|
|
171
|
+
preview: { type: [Boolean, String], default: true }, // Only for file uploads.
|
|
172
|
+
loading: { type: [Boolean, Number], default: false }, // If a number is given, it will be the value of the progress.
|
|
173
|
+
showProgress: { type: [Boolean] }, // Only for file uploads.
|
|
174
|
+
// Allow syncing the files 1 way: prefilling a file is not possible.
|
|
175
|
+
// https://stackoverflow.com/questions/16365668/pre-populate-html-form-file-input
|
|
176
|
+
files: { type: Array }
|
|
149
177
|
// Props from mixin: name, disabled, readonly, required, tabindex, validators.
|
|
150
178
|
// Computed from mixin: inputName, isDisabled & isReadonly.
|
|
151
179
|
},
|
|
152
180
|
|
|
153
|
-
emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right'],
|
|
181
|
+
emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right', 'update:overallProgress'],
|
|
154
182
|
|
|
155
183
|
data () {
|
|
156
184
|
return {
|
|
@@ -160,7 +188,8 @@ export default {
|
|
|
160
188
|
inputNumberError: false,
|
|
161
189
|
isFocused: false,
|
|
162
190
|
inputFiles: [], // For input type file.
|
|
163
|
-
fileReader: null // For input type file.
|
|
191
|
+
fileReader: null, // For input type file.
|
|
192
|
+
isAutofilled: false
|
|
164
193
|
}
|
|
165
194
|
},
|
|
166
195
|
|
|
@@ -178,35 +207,69 @@ export default {
|
|
|
178
207
|
const { input, focus, blur, ...listeners } = this.$attrs
|
|
179
208
|
return listeners
|
|
180
209
|
},
|
|
210
|
+
|
|
181
211
|
attrs () {
|
|
182
212
|
// eslint-disable-next-line no-unused-vars
|
|
183
213
|
const { class: Class, ...htmlAttrs } = this.$attrs
|
|
184
214
|
return htmlAttrs
|
|
185
215
|
},
|
|
216
|
+
|
|
186
217
|
hasValue () {
|
|
187
|
-
|
|
188
|
-
this.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
218
|
+
switch (this.type) {
|
|
219
|
+
case 'file': return !!this.inputFiles.length
|
|
220
|
+
case 'number': return this.inputValue || this.inputValue === 0 || this.inputNumberError
|
|
221
|
+
case 'date':
|
|
222
|
+
case 'time':
|
|
223
|
+
return true
|
|
224
|
+
default:
|
|
225
|
+
return this.inputValue || this.inputValue === 0
|
|
226
|
+
}
|
|
193
227
|
},
|
|
194
228
|
|
|
195
229
|
hasLabel () {
|
|
196
230
|
return this.label || this.$slots.default
|
|
197
231
|
},
|
|
198
232
|
|
|
233
|
+
hasLoading () {
|
|
234
|
+
return ![undefined, false].includes(this.loading)
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
loadingValue () {
|
|
238
|
+
let value
|
|
239
|
+
if (typeof this.loading === 'number') value = this.loading
|
|
240
|
+
else if (this.loading) {
|
|
241
|
+
value = this.type === 'file' && this.overallFilesProgress ? this.overallFilesProgress : undefined
|
|
242
|
+
}
|
|
243
|
+
return value
|
|
244
|
+
},
|
|
245
|
+
|
|
199
246
|
showLabelInside () {
|
|
200
247
|
return !this.staticLabel || (!this.hasValue && !this.placeholder)
|
|
201
248
|
},
|
|
202
249
|
|
|
250
|
+
overallFilesProgress () {
|
|
251
|
+
const progress = this.inputFiles.reduce((total, file) => total + file.progress, 0)
|
|
252
|
+
const total = progress / this.inputFiles.length
|
|
253
|
+
this.$emit('update:overallProgress', this.inputFiles.length ? total : undefined)
|
|
254
|
+
|
|
255
|
+
return total
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
uploadInProgress () {
|
|
259
|
+
return this.overallFilesProgress > 0 && this.overallFilesProgress < 100
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
uploadComplete () {
|
|
263
|
+
return this.overallFilesProgress === 100
|
|
264
|
+
},
|
|
265
|
+
|
|
203
266
|
classes () {
|
|
204
267
|
return {
|
|
205
268
|
'w-input': true,
|
|
206
269
|
'w-input--file': this.type === 'file',
|
|
207
270
|
'w-input--disabled': this.isDisabled,
|
|
208
271
|
'w-input--readonly': this.isReadonly,
|
|
209
|
-
[`w-input--${this.hasValue ? 'filled' : 'empty'}`]: true,
|
|
272
|
+
[`w-input--${this.hasValue || this.isAutofilled ? 'filled' : 'empty'}`]: true,
|
|
210
273
|
'w-input--focused': this.isFocused && !this.isReadonly,
|
|
211
274
|
'w-input--dark': this.dark,
|
|
212
275
|
'w-input--floating-label': this.hasLabel && this.labelPosition === 'inside' && !this.staticLabel,
|
|
@@ -217,6 +280,12 @@ export default {
|
|
|
217
280
|
}
|
|
218
281
|
},
|
|
219
282
|
|
|
283
|
+
validationClasses () {
|
|
284
|
+
return this.isFocused && {
|
|
285
|
+
[this.valid === false ? 'error' : this.color]: this.color || this.valid === false
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
|
|
220
289
|
inputWrapClasses () {
|
|
221
290
|
return {
|
|
222
291
|
[this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
|
|
@@ -229,7 +298,8 @@ export default {
|
|
|
229
298
|
'w-input__input-wrap--underline': !this.outline,
|
|
230
299
|
'w-input__input-wrap--shadow': this.shadow,
|
|
231
300
|
'w-input__input-wrap--no-padding': !this.outline && !this.bgColor && !this.shadow && !this.round,
|
|
232
|
-
'w-input__input-wrap--loading': this.loading
|
|
301
|
+
'w-input__input-wrap--loading': this.loading || (this.showProgress && this.uploadInProgress),
|
|
302
|
+
'w-input__input-wrap--upload-complete': this.uploadComplete
|
|
233
303
|
}
|
|
234
304
|
}
|
|
235
305
|
},
|
|
@@ -254,35 +324,44 @@ export default {
|
|
|
254
324
|
// For file input.
|
|
255
325
|
onFileChange (e) {
|
|
256
326
|
this.inputFiles = [...e.target.files].map(original => {
|
|
257
|
-
|
|
327
|
+
// `full` if there is no filename but only an extension.
|
|
328
|
+
const [, base = '', extension = '', full = ''] = original.name.match(/^(.*?)\.([^.]*)$|(.*)/)
|
|
329
|
+
|
|
258
330
|
const file = reactive({
|
|
259
331
|
name: original.name,
|
|
260
|
-
base,
|
|
332
|
+
base: base || full,
|
|
261
333
|
extension,
|
|
262
334
|
type: original.type,
|
|
263
335
|
size: original.size,
|
|
264
336
|
lastModified: original.lastModified,
|
|
265
337
|
preview: null,
|
|
266
|
-
progress: 0
|
|
338
|
+
progress: 0,
|
|
339
|
+
file: original
|
|
267
340
|
})
|
|
268
341
|
|
|
269
|
-
this.
|
|
342
|
+
this.readFile(original, file)
|
|
270
343
|
|
|
271
344
|
return file
|
|
272
345
|
})
|
|
273
346
|
this.$emit('update:modelValue', this.inputFiles)
|
|
347
|
+
this.$emit('input', this.inputFiles)
|
|
274
348
|
},
|
|
275
349
|
|
|
276
350
|
// For file input.
|
|
277
|
-
|
|
351
|
+
readFile (original, file) {
|
|
278
352
|
const reader = new FileReader()
|
|
279
353
|
|
|
354
|
+
// If the preview prop is a string, the user is setting the preview to an icon and
|
|
355
|
+
// don't need the actual file preview.
|
|
356
|
+
const isPreviewAnIcon = typeof this.preview === 'string'
|
|
357
|
+
const isFileAnImage = original.type && original.type.startsWith('image/')
|
|
280
358
|
// Check if the file is an image and set a preview image.
|
|
281
|
-
if (
|
|
359
|
+
if (this.preview && !isPreviewAnIcon && isFileAnImage) {
|
|
282
360
|
reader.addEventListener('load', e => {
|
|
283
361
|
file.preview = e.target.result
|
|
284
362
|
})
|
|
285
363
|
}
|
|
364
|
+
else delete file.preview
|
|
286
365
|
|
|
287
366
|
// Used to display a spinner while the file is loading.
|
|
288
367
|
reader.addEventListener('progress', e => {
|
|
@@ -293,9 +372,19 @@ export default {
|
|
|
293
372
|
}
|
|
294
373
|
},
|
|
295
374
|
|
|
375
|
+
mounted () {
|
|
376
|
+
// On page load, check if the field is autofilled by the browser.
|
|
377
|
+
// 20211229. Only a problem on Chrome. Firefox ok, Safari always prompts before filling up.
|
|
378
|
+
setTimeout(() => {
|
|
379
|
+
if (this.$refs.input && this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
|
|
380
|
+
}, 400) // Can't be less than 350: time for the browser to autofill.
|
|
381
|
+
},
|
|
382
|
+
|
|
296
383
|
watch: {
|
|
297
384
|
modelValue (value) {
|
|
298
385
|
this.inputValue = value
|
|
386
|
+
// When clearing the field value, also reset the isAutofilled var for the CSS class.
|
|
387
|
+
if (!value && value !== 0) this.isAutofilled = false
|
|
299
388
|
}
|
|
300
389
|
}
|
|
301
390
|
}
|
|
@@ -315,6 +404,8 @@ $inactive-color: #777;
|
|
|
315
404
|
&--file {
|
|
316
405
|
flex-wrap: nowrap;
|
|
317
406
|
align-items: flex-end;
|
|
407
|
+
|
|
408
|
+
span.fade-leave-to {position: absolute;}
|
|
318
409
|
}
|
|
319
410
|
|
|
320
411
|
// Input field wrapper.
|
|
@@ -344,7 +435,10 @@ $inactive-color: #777;
|
|
|
344
435
|
&--round {border-radius: 99em;}
|
|
345
436
|
&--tile {border-radius: initial;}
|
|
346
437
|
&--shadow {box-shadow: $box-shadow;}
|
|
347
|
-
&--loading
|
|
438
|
+
&--loading, &--upload-complete {
|
|
439
|
+
border-bottom-color: transparent;
|
|
440
|
+
flex-wrap: wrap;
|
|
441
|
+
}
|
|
348
442
|
&--loading ~ .w-progress {
|
|
349
443
|
height: 2px;
|
|
350
444
|
position: absolute;
|
|
@@ -353,7 +447,8 @@ $inactive-color: #777;
|
|
|
353
447
|
}
|
|
354
448
|
|
|
355
449
|
.w-input--focused & {border-color: currentColor;}
|
|
356
|
-
.w-input--focused &--loading
|
|
450
|
+
.w-input--focused &--loading,
|
|
451
|
+
.w-input--focused &--upload-complete {border-bottom-color: transparent;}
|
|
357
452
|
|
|
358
453
|
// Underline.
|
|
359
454
|
&--underline:after {
|
|
@@ -464,6 +559,8 @@ $inactive-color: #777;
|
|
|
464
559
|
margin-left: 4px;
|
|
465
560
|
max-height: 2em;
|
|
466
561
|
align-self: flex-end;
|
|
562
|
+
|
|
563
|
+
&.w-icon {margin-bottom: 4px;}
|
|
467
564
|
}
|
|
468
565
|
|
|
469
566
|
// Icons inside.
|