wave-ui 1.45.15 → 1.49.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 +415 -382
- 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 +117 -34
- package/src/wave-ui/components/w-menu.vue +69 -275
- package/src/wave-ui/components/w-notification-manager.vue +9 -2
- 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 +106 -220
- package/src/wave-ui/mixins/detachable.js +321 -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.49.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,37 +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)
|
|
264
335
|
this.$emit('input', this.inputFiles)
|
|
265
|
-
this.$emit('change', this.inputFiles)
|
|
266
336
|
},
|
|
267
337
|
|
|
268
338
|
// For file input.
|
|
269
|
-
|
|
339
|
+
readFile (original, file) {
|
|
270
340
|
const reader = new FileReader()
|
|
271
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/')
|
|
272
346
|
// Check if the file is an image and set a preview image.
|
|
273
|
-
if (
|
|
347
|
+
if (this.preview && !isPreviewAnIcon && isFileAnImage) {
|
|
274
348
|
reader.addEventListener('load', e => {
|
|
275
349
|
this.$set(file, 'preview', e.target.result)
|
|
276
350
|
})
|
|
277
351
|
}
|
|
352
|
+
else delete file.preview
|
|
278
353
|
|
|
279
354
|
// Used to display a spinner while the file is loading.
|
|
280
355
|
reader.addEventListener('progress', event => {
|
|
@@ -319,6 +394,8 @@ $inactive-color: #777;
|
|
|
319
394
|
&--file {
|
|
320
395
|
flex-wrap: nowrap;
|
|
321
396
|
align-items: flex-end;
|
|
397
|
+
|
|
398
|
+
span.fade-leave-to {position: absolute;}
|
|
322
399
|
}
|
|
323
400
|
|
|
324
401
|
// Input field wrapper.
|
|
@@ -348,7 +425,10 @@ $inactive-color: #777;
|
|
|
348
425
|
&--round {border-radius: 99em;}
|
|
349
426
|
&--tile {border-radius: initial;}
|
|
350
427
|
&--shadow {box-shadow: $box-shadow;}
|
|
351
|
-
&--loading
|
|
428
|
+
&--loading, &--upload-complete {
|
|
429
|
+
border-bottom-color: transparent;
|
|
430
|
+
flex-wrap: wrap;
|
|
431
|
+
}
|
|
352
432
|
&--loading ~ .w-progress {
|
|
353
433
|
height: 2px;
|
|
354
434
|
position: absolute;
|
|
@@ -357,7 +437,8 @@ $inactive-color: #777;
|
|
|
357
437
|
}
|
|
358
438
|
|
|
359
439
|
.w-input--focused & {border-color: currentColor;}
|
|
360
|
-
.w-input--focused &--loading
|
|
440
|
+
.w-input--focused &--loading,
|
|
441
|
+
.w-input--focused &--upload-complete {border-bottom-color: transparent;}
|
|
361
442
|
|
|
362
443
|
// Underline.
|
|
363
444
|
&--underline:after {
|
|
@@ -468,6 +549,8 @@ $inactive-color: #777;
|
|
|
468
549
|
margin-left: 4px;
|
|
469
550
|
max-height: 2em;
|
|
470
551
|
align-self: flex-end;
|
|
552
|
+
|
|
553
|
+
&.w-icon {margin-bottom: 4px;}
|
|
471
554
|
}
|
|
472
555
|
|
|
473
556
|
// Icons inside.
|