wave-ui 1.45.14 → 1.48.0
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 +333 -304
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +4 -2
- package/src/wave-ui/components/w-alert.vue +0 -1
- package/src/wave-ui/components/w-confirm.vue +35 -10
- package/src/wave-ui/components/w-input.vue +119 -34
- package/src/wave-ui/components/w-menu.vue +67 -214
- package/src/wave-ui/components/w-select.vue +1 -1
- package/src/wave-ui/components/w-tag.vue +17 -6
- package/src/wave-ui/components/w-tooltip.vue +123 -265
- package/src/wave-ui/mixins/detachable.js +189 -0
- package/src/wave-ui/scss/_mixins.scss +18 -4
- package/src/wave-ui/scss/_variables.scss +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
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",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"@babel/core": "^7.16.0",
|
|
47
47
|
"@babel/eslint-parser": "^7.16.3",
|
|
48
48
|
"@babel/plugin-proposal-class-properties": "^7.16.0",
|
|
49
|
-
"@mdi/font": "^5.9.55",
|
|
50
49
|
"@vitejs/plugin-vue": "^1.10.1",
|
|
51
50
|
"autoprefixer": "^10.4.0",
|
|
52
51
|
"axios": "^0.21.4",
|
|
@@ -75,5 +74,8 @@
|
|
|
75
74
|
"vue-template-compiler": "^2.6.14",
|
|
76
75
|
"vueperslides": "^2.15.2",
|
|
77
76
|
"vuex": "^3.6.2"
|
|
77
|
+
},
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"@mdi/font": "^6.5.95"
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -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-on="on" v-bind="buttonProps")
|
|
5
|
+
w-button.w-confirm__button(v-on="{ ...$listeners, ...on }" v-bind="buttonProps")
|
|
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,
|
|
@@ -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")
|
|
@@ -58,47 +65,67 @@ component(
|
|
|
58
65
|
@blur="onBlur"
|
|
59
66
|
@change="onFileChange"
|
|
60
67
|
:multiple="multiple || null"
|
|
61
|
-
v-bind="attrs"
|
|
62
|
-
|
|
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}`")
|
|
63
74
|
span.w-input__no-file(v-if="!inputFiles.length && isFocused" key="no-file")
|
|
64
75
|
slot(name="no-file")
|
|
65
76
|
template(v-if="$slots['no-file'] === undefined") No file
|
|
66
77
|
span(v-for="(file, i) in inputFiles" :key="file.lastModified")
|
|
67
78
|
| {{ i ? ', ': '' }}
|
|
68
79
|
span.filename(:key="`${i}b`") {{ file.base }}
|
|
69
|
-
| {{ file.extension }}
|
|
80
|
+
| {{ file.extension ? `.${file.extension}` : '' }}
|
|
70
81
|
|
|
71
82
|
template(v-if="labelPosition === 'inside' && showLabelInside")
|
|
72
83
|
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
73
84
|
v-if="$slots.default"
|
|
74
85
|
:for="`w-input--${_uid}`"
|
|
75
|
-
:class="
|
|
86
|
+
:class="validationClasses")
|
|
76
87
|
slot
|
|
77
88
|
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
78
89
|
v-else-if="label"
|
|
79
90
|
:for="`w-input--${_uid}`"
|
|
80
91
|
v-html="label"
|
|
81
|
-
:class="
|
|
92
|
+
:class="validationClasses")
|
|
82
93
|
w-icon.w-input__icon.w-input__icon--inner-right(
|
|
83
94
|
v-if="innerIconRight"
|
|
84
95
|
tag="label"
|
|
85
96
|
:for="`w-input--${_uid}`"
|
|
86
97
|
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
87
98
|
|
|
99
|
+
w-progress.fill-width(
|
|
100
|
+
v-if="hasLoading || (showProgress && (uploadInProgress || uploadComplete))"
|
|
101
|
+
size="2"
|
|
102
|
+
:color="progressColor || color"
|
|
103
|
+
:value="showProgress ? (uploadInProgress || uploadComplete) && overallFilesProgress : loadingValue")
|
|
104
|
+
|
|
88
105
|
//- Files preview.
|
|
89
|
-
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}`")
|
|
90
107
|
template(v-for="(file, i) in inputFiles")
|
|
91
|
-
i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
|
|
108
|
+
i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
|
|
109
|
+
v-if="file.progress < 100"
|
|
110
|
+
:key="i")
|
|
92
111
|
img.w-input__file-preview(v-else-if="file.preview" :key="i" :src="file.preview" alt="")
|
|
93
|
-
i.w-icon.
|
|
112
|
+
i.w-icon.w-input__file-preview.primary.size--md(
|
|
113
|
+
v-else
|
|
114
|
+
:key="i"
|
|
115
|
+
:class="preview && typeof preview === 'string' ? preview : 'wi-file'")
|
|
94
116
|
|
|
95
117
|
//- Right label.
|
|
96
118
|
template(v-if="labelPosition === 'right'")
|
|
97
|
-
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
119
|
+
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
120
|
+
v-if="$slots.default"
|
|
121
|
+
:for="`w-input--${_uid}`"
|
|
122
|
+
:class="validationClasses")
|
|
98
123
|
slot
|
|
99
|
-
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
100
|
-
|
|
101
|
-
|
|
124
|
+
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
125
|
+
v-else-if="label"
|
|
126
|
+
:for="`w-input--${_uid}`"
|
|
127
|
+
:class="validationClasses"
|
|
128
|
+
v-html="label")
|
|
102
129
|
</template>
|
|
103
130
|
|
|
104
131
|
<script>
|
|
@@ -136,13 +163,17 @@ export default {
|
|
|
136
163
|
shadow: { type: Boolean },
|
|
137
164
|
tile: { type: Boolean },
|
|
138
165
|
multiple: { type: Boolean }, // Only for file uploads.
|
|
139
|
-
preview: { type: Boolean }, // Only for file uploads.
|
|
140
|
-
loading: { type: Boolean }
|
|
166
|
+
preview: { type: [Boolean, String], default: true }, // Only for file uploads.
|
|
167
|
+
loading: { type: [Boolean, Number], default: false }, // If a number is given, it will be the value of the progress.
|
|
168
|
+
showProgress: { type: [Boolean] }, // Only for file uploads.
|
|
169
|
+
// Allow syncing the files 1 way: prefilling a file is not possible.
|
|
170
|
+
// https://stackoverflow.com/questions/16365668/pre-populate-html-form-file-input
|
|
171
|
+
files: { type: Array }
|
|
141
172
|
// Props from mixin: name, disabled, readonly, required, tabindex, validators.
|
|
142
173
|
// Computed from mixin: inputName, isDisabled & isReadonly.
|
|
143
174
|
},
|
|
144
175
|
|
|
145
|
-
emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right'],
|
|
176
|
+
emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right', 'update:overallProgress'],
|
|
146
177
|
|
|
147
178
|
data () {
|
|
148
179
|
return {
|
|
@@ -173,23 +204,54 @@ export default {
|
|
|
173
204
|
},
|
|
174
205
|
|
|
175
206
|
hasValue () {
|
|
176
|
-
|
|
177
|
-
this.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
207
|
+
switch (this.type) {
|
|
208
|
+
case 'file': return !!this.inputFiles.length
|
|
209
|
+
case 'number': return this.inputNumberError
|
|
210
|
+
case 'date':
|
|
211
|
+
case 'time':
|
|
212
|
+
return true
|
|
213
|
+
default:
|
|
214
|
+
return this.inputValue || this.inputValue === 0
|
|
215
|
+
}
|
|
183
216
|
},
|
|
184
217
|
|
|
185
218
|
hasLabel () {
|
|
186
219
|
return this.label || this.$slots.default
|
|
187
220
|
},
|
|
188
221
|
|
|
222
|
+
hasLoading () {
|
|
223
|
+
return ![undefined, false].includes(this.loading)
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
loadingValue () {
|
|
227
|
+
let value
|
|
228
|
+
if (typeof this.loading === 'number') value = this.loading
|
|
229
|
+
else if (this.loading) {
|
|
230
|
+
value = this.type === 'file' && this.overallFilesProgress ? this.overallFilesProgress : undefined
|
|
231
|
+
}
|
|
232
|
+
return value
|
|
233
|
+
},
|
|
234
|
+
|
|
189
235
|
showLabelInside () {
|
|
190
236
|
return !this.staticLabel || (!this.hasValue && !this.placeholder)
|
|
191
237
|
},
|
|
192
238
|
|
|
239
|
+
overallFilesProgress () {
|
|
240
|
+
const progress = this.inputFiles.reduce((total, file) => total + file.progress, 0)
|
|
241
|
+
const total = progress / this.inputFiles.length
|
|
242
|
+
this.$emit('update:overallProgress', this.inputFiles.length ? total : undefined)
|
|
243
|
+
|
|
244
|
+
return total
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
uploadInProgress () {
|
|
248
|
+
return this.overallFilesProgress > 0 && this.overallFilesProgress < 100
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
uploadComplete () {
|
|
252
|
+
return this.overallFilesProgress === 100
|
|
253
|
+
},
|
|
254
|
+
|
|
193
255
|
classes () {
|
|
194
256
|
return {
|
|
195
257
|
'w-input': true,
|
|
@@ -207,6 +269,12 @@ export default {
|
|
|
207
269
|
}
|
|
208
270
|
},
|
|
209
271
|
|
|
272
|
+
validationClasses () {
|
|
273
|
+
return this.isFocused && {
|
|
274
|
+
[this.valid === false ? 'error' : this.color]: this.color || this.valid === false
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
210
278
|
inputWrapClasses () {
|
|
211
279
|
return {
|
|
212
280
|
[this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
|
|
@@ -219,7 +287,8 @@ export default {
|
|
|
219
287
|
'w-input__input-wrap--underline': !this.outline,
|
|
220
288
|
'w-input__input-wrap--shadow': this.shadow,
|
|
221
289
|
'w-input__input-wrap--no-padding': !this.outline && !this.bgColor && !this.shadow && !this.round,
|
|
222
|
-
'w-input__input-wrap--loading': this.loading
|
|
290
|
+
'w-input__input-wrap--loading': this.loading || (this.showProgress && this.uploadInProgress),
|
|
291
|
+
'w-input__input-wrap--upload-complete': this.uploadComplete
|
|
223
292
|
}
|
|
224
293
|
}
|
|
225
294
|
},
|
|
@@ -244,35 +313,43 @@ export default {
|
|
|
244
313
|
// For file input.
|
|
245
314
|
onFileChange (e) {
|
|
246
315
|
this.$set(this, 'inputFiles', [...e.target.files].map(original => {
|
|
247
|
-
|
|
316
|
+
// `full` if there is no filename but only an extension.
|
|
317
|
+
const [, base = '', extension = '', full = ''] = original.name.match(/^(.*?)\.([^.]*)$|(.*)/)
|
|
248
318
|
const file = Object.assign({}, {
|
|
249
319
|
name: original.name,
|
|
250
|
-
base,
|
|
320
|
+
base: base || full,
|
|
251
321
|
extension,
|
|
252
322
|
type: original.type,
|
|
253
323
|
size: original.size,
|
|
254
324
|
lastModified: original.lastModified,
|
|
255
325
|
preview: null,
|
|
256
|
-
progress: 0
|
|
326
|
+
progress: 0,
|
|
327
|
+
file: original
|
|
257
328
|
})
|
|
258
329
|
|
|
259
|
-
this.
|
|
330
|
+
this.readFile(original, file)
|
|
260
331
|
|
|
261
332
|
return file
|
|
262
333
|
}))
|
|
263
334
|
this.$emit('update:modelValue', this.inputFiles)
|
|
335
|
+
this.$emit('input', this.inputFiles)
|
|
264
336
|
},
|
|
265
337
|
|
|
266
338
|
// For file input.
|
|
267
|
-
|
|
339
|
+
readFile (original, file) {
|
|
268
340
|
const reader = new FileReader()
|
|
269
341
|
|
|
342
|
+
// If the preview prop is a string, the user is setting the preview to an icon and
|
|
343
|
+
// don't need the actual file preview.
|
|
344
|
+
const isPreviewAnIcon = typeof this.preview === 'string'
|
|
345
|
+
const isFileAnImage = original.type && original.type.startsWith('image/')
|
|
270
346
|
// Check if the file is an image and set a preview image.
|
|
271
|
-
if (
|
|
347
|
+
if (this.preview && !isPreviewAnIcon && isFileAnImage) {
|
|
272
348
|
reader.addEventListener('load', e => {
|
|
273
349
|
this.$set(file, 'preview', e.target.result)
|
|
274
350
|
})
|
|
275
351
|
}
|
|
352
|
+
else delete file.preview
|
|
276
353
|
|
|
277
354
|
// Used to display a spinner while the file is loading.
|
|
278
355
|
reader.addEventListener('progress', event => {
|
|
@@ -289,7 +366,7 @@ export default {
|
|
|
289
366
|
// On page load, check if the field is autofilled by the browser.
|
|
290
367
|
// 20211229. Only a problem on Chrome. Firefox ok, Safari always prompts before filling up.
|
|
291
368
|
setTimeout(() => {
|
|
292
|
-
if (this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
|
|
369
|
+
if (this.$refs.input && this.$refs.input.matches(':-webkit-autofill')) this.isAutofilled = true
|
|
293
370
|
}, 400) // Can't be less than 350: time for the browser to autofill.
|
|
294
371
|
},
|
|
295
372
|
|
|
@@ -317,6 +394,8 @@ $inactive-color: #777;
|
|
|
317
394
|
&--file {
|
|
318
395
|
flex-wrap: nowrap;
|
|
319
396
|
align-items: flex-end;
|
|
397
|
+
|
|
398
|
+
span.fade-leave-to {position: absolute;}
|
|
320
399
|
}
|
|
321
400
|
|
|
322
401
|
// Input field wrapper.
|
|
@@ -346,7 +425,10 @@ $inactive-color: #777;
|
|
|
346
425
|
&--round {border-radius: 99em;}
|
|
347
426
|
&--tile {border-radius: initial;}
|
|
348
427
|
&--shadow {box-shadow: $box-shadow;}
|
|
349
|
-
&--loading
|
|
428
|
+
&--loading, &--upload-complete {
|
|
429
|
+
border-bottom-color: transparent;
|
|
430
|
+
flex-wrap: wrap;
|
|
431
|
+
}
|
|
350
432
|
&--loading ~ .w-progress {
|
|
351
433
|
height: 2px;
|
|
352
434
|
position: absolute;
|
|
@@ -355,7 +437,8 @@ $inactive-color: #777;
|
|
|
355
437
|
}
|
|
356
438
|
|
|
357
439
|
.w-input--focused & {border-color: currentColor;}
|
|
358
|
-
.w-input--focused &--loading
|
|
440
|
+
.w-input--focused &--loading,
|
|
441
|
+
.w-input--focused &--upload-complete {border-bottom-color: transparent;}
|
|
359
442
|
|
|
360
443
|
// Underline.
|
|
361
444
|
&--underline:after {
|
|
@@ -466,6 +549,8 @@ $inactive-color: #777;
|
|
|
466
549
|
margin-left: 4px;
|
|
467
550
|
max-height: 2em;
|
|
468
551
|
align-self: flex-end;
|
|
552
|
+
|
|
553
|
+
&.w-icon {margin-bottom: 4px;}
|
|
469
554
|
}
|
|
470
555
|
|
|
471
556
|
// Icons inside.
|