wave-ui 2.24.0 → 2.28.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 +7346 -1
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +17 -16
- package/src/wave-ui/components/index.js +3 -2
- package/src/wave-ui/components/w-app.vue +42 -2
- package/src/wave-ui/components/w-badge.vue +19 -9
- package/src/wave-ui/components/w-card.vue +19 -5
- package/src/wave-ui/components/w-confirm.vue +103 -0
- package/src/wave-ui/components/w-icon.vue +3 -15
- package/src/wave-ui/components/w-input.vue +71 -19
- package/src/wave-ui/components/w-menu.vue +92 -47
- package/src/wave-ui/components/w-notification-manager.vue +1 -1
- package/src/wave-ui/components/w-switch.vue +1 -0
- package/src/wave-ui/components/w-table.vue +58 -29
- package/src/wave-ui/components/{w-tabs.vue → w-tabs/index.vue} +20 -14
- package/src/wave-ui/components/w-tabs/tab-content.vue +8 -0
- package/src/wave-ui/components/w-tooltip.vue +11 -8
- package/src/wave-ui/scss/_base.scss +0 -21
- package/src/wave-ui/scss/_icons.scss +4 -1
- package/src/wave-ui/scss/_layout.scss +202 -192
- package/src/wave-ui/scss/_mixins.scss +100 -0
- package/src/wave-ui/scss/_transitions.scss +4 -4
- package/src/wave-ui/scss/_variables.scss +7 -10
- package/src/wave-ui/utils/index.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.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",
|
|
@@ -43,28 +43,29 @@
|
|
|
43
43
|
"*.vue"
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@babel/core": "^7.
|
|
47
|
-
"@babel/eslint-parser": "^7.
|
|
48
|
-
"@babel/plugin-proposal-class-properties": "^7.
|
|
46
|
+
"@babel/core": "^7.16.0",
|
|
47
|
+
"@babel/eslint-parser": "^7.16.3",
|
|
48
|
+
"@babel/plugin-proposal-class-properties": "^7.16.0",
|
|
49
49
|
"@mdi/font": "^5.9.55",
|
|
50
|
-
"@vitejs/plugin-vue": "^1.
|
|
50
|
+
"@vitejs/plugin-vue": "^1.9.4",
|
|
51
51
|
"@vue/compiler-sfc": "3.1.5",
|
|
52
|
-
"autoprefixer": "^10.
|
|
53
|
-
"axios": "^0.21.
|
|
54
|
-
"eslint": "^7.
|
|
52
|
+
"autoprefixer": "^10.4.0",
|
|
53
|
+
"axios": "^0.21.4",
|
|
54
|
+
"eslint": "^7.32.0",
|
|
55
55
|
"font-awesome": "^4.7.0",
|
|
56
|
-
"gsap": "^3.
|
|
56
|
+
"gsap": "^3.8.0",
|
|
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.
|
|
61
|
-
"simple-syntax-highlighter": "^2.0
|
|
62
|
-
"splitpanes": "^3.0.
|
|
63
|
-
"vite": "^2.
|
|
60
|
+
"sass": "^1.43.4",
|
|
61
|
+
"simple-syntax-highlighter": "^2.2.0",
|
|
62
|
+
"splitpanes": "^3.0.6",
|
|
63
|
+
"vite": "^2.6.14",
|
|
64
64
|
"vite-plugin-pug": "^0.3.0",
|
|
65
|
-
"vue": "^3.2.
|
|
65
|
+
"vue": "^3.2.26",
|
|
66
66
|
"vue-cal": "^4.2.0",
|
|
67
|
-
"vue-router": "^4.0.
|
|
68
|
-
"vueperslides": "^3.3.
|
|
67
|
+
"vue-router": "^4.0.12",
|
|
68
|
+
"vueperslides": "^3.3.2",
|
|
69
|
+
"vuex": "^4.0.2"
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -5,9 +5,10 @@ export { default as WApp } from './w-app.vue'
|
|
|
5
5
|
export { default as WBadge } from './w-badge.vue'
|
|
6
6
|
export { default as WBreadcrumbs } from './w-breadcrumbs.vue'
|
|
7
7
|
export { default as WButton } from './w-button.vue'
|
|
8
|
+
export { default as WCard } from './w-card.vue'
|
|
8
9
|
export { default as WCheckbox } from './w-checkbox.vue'
|
|
9
10
|
export { default as WCheckboxes } from './w-checkboxes.vue'
|
|
10
|
-
export { default as
|
|
11
|
+
export { default as WConfirm } from './w-confirm.vue'
|
|
11
12
|
export { default as WDatePicker } from './w-date-picker.vue'
|
|
12
13
|
export { default as WDialog } from './w-dialog.vue'
|
|
13
14
|
export { default as WDivider } from './w-divider.vue'
|
|
@@ -32,7 +33,7 @@ export { default as WSlider } from './w-slider.vue'
|
|
|
32
33
|
export { default as WSpinner } from './w-spinner.vue'
|
|
33
34
|
export { default as WSteps } from './w-steps.vue'
|
|
34
35
|
export { default as WSwitch } from './w-switch.vue'
|
|
35
|
-
export { default as WTabs } from './w-tabs.vue'
|
|
36
|
+
export { default as WTabs } from './w-tabs/index.vue'
|
|
36
37
|
export { default as WTable } from './w-table.vue'
|
|
37
38
|
export { default as WTag } from './w-tag.vue'
|
|
38
39
|
export { default as WTextarea } from './w-textarea.vue'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
.w-app(:class="
|
|
2
|
+
.w-app(:class="classes")
|
|
3
3
|
slot
|
|
4
4
|
notification-manager
|
|
5
5
|
</template>
|
|
@@ -16,7 +16,17 @@ export default {
|
|
|
16
16
|
name: 'w-app',
|
|
17
17
|
props: {
|
|
18
18
|
dark: { type: Boolean },
|
|
19
|
-
block: { type: Boolean }
|
|
19
|
+
block: { type: Boolean },
|
|
20
|
+
row: { type: Boolean },
|
|
21
|
+
alignCenter: { type: Boolean },
|
|
22
|
+
alignEnd: { type: Boolean },
|
|
23
|
+
justifyCenter: { type: Boolean },
|
|
24
|
+
justifyEnd: { type: Boolean },
|
|
25
|
+
justifySpaceBetween: { type: Boolean },
|
|
26
|
+
justifySpaceAround: { type: Boolean },
|
|
27
|
+
justifySpaceEvenly: { type: Boolean },
|
|
28
|
+
textCenter: { type: Boolean },
|
|
29
|
+
textRight: { type: Boolean }
|
|
20
30
|
},
|
|
21
31
|
|
|
22
32
|
components: { NotificationManager },
|
|
@@ -26,6 +36,25 @@ export default {
|
|
|
26
36
|
notifManager: null
|
|
27
37
|
}),
|
|
28
38
|
|
|
39
|
+
computed: {
|
|
40
|
+
classes () {
|
|
41
|
+
return {
|
|
42
|
+
'd-block': this.block,
|
|
43
|
+
'row': this.row,
|
|
44
|
+
'align-center': this.alignCenter,
|
|
45
|
+
'align-end': this.alignEnd,
|
|
46
|
+
'justify-center': this.justifyCenter,
|
|
47
|
+
'justify-end': this.justifyEnd,
|
|
48
|
+
'justify-space-between': this.justifySpaceBetween,
|
|
49
|
+
'justify-space-around': this.justifySpaceAround,
|
|
50
|
+
'justify-space-evenly': this.justifySpaceEvenly,
|
|
51
|
+
'text-center': this.textCenter,
|
|
52
|
+
'text-right': this.textRight,
|
|
53
|
+
'theme--dark': this.dark
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
29
58
|
methods: {
|
|
30
59
|
getBreakpoint () {
|
|
31
60
|
const width = window.innerWidth
|
|
@@ -85,6 +114,17 @@ export default {
|
|
|
85
114
|
flex-direction: column;
|
|
86
115
|
min-height: 100vh;
|
|
87
116
|
|
|
117
|
+
&.row {flex-direction: row;}
|
|
88
118
|
&.d-block {display: block;}
|
|
119
|
+
&.align-center {align-items: center;}
|
|
120
|
+
&.align-end {align-items: flex-end;}
|
|
121
|
+
&.justify-center {justify-content: center;}
|
|
122
|
+
&.justify-end {justify-content: flex-end;}
|
|
123
|
+
&.justify-space-between {justify-content: space-between;}
|
|
124
|
+
&.justify-space-around {justify-content: space-around;}
|
|
125
|
+
&.justify-space-evenly {justify-content: space-evenly;}
|
|
126
|
+
&.text-center {text-align: center;}
|
|
127
|
+
&.text-right {text-align: right;}
|
|
128
|
+
|
|
89
129
|
}
|
|
90
130
|
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
.w-badge-wrap
|
|
2
|
+
.w-badge-wrap(v-on="$attrs")
|
|
3
3
|
slot
|
|
4
4
|
transition(:name="`${transition}`")
|
|
5
5
|
.w-badge(
|
|
@@ -104,6 +104,7 @@ export default {
|
|
|
104
104
|
border-radius: 99em;
|
|
105
105
|
// Always get an even number for better text vertical align.
|
|
106
106
|
height: round(1.1 * divide($base-font-size, 2)) * 2;
|
|
107
|
+
line-height: round(1.1 * divide($base-font-size, 2)) * 2;
|
|
107
108
|
min-width: round(1.1 * divide($base-font-size, 2)) * 2;
|
|
108
109
|
z-index: 1;
|
|
109
110
|
padding: 0 $base-increment;
|
|
@@ -121,29 +122,38 @@ export default {
|
|
|
121
122
|
$height: round(divide($base-font-size, 2)) * 2;
|
|
122
123
|
font-size: round(0.67 * divide($base-font-size, 2)) * 2;
|
|
123
124
|
height: $height;
|
|
125
|
+
line-height: $height;
|
|
124
126
|
min-width: $height;
|
|
125
127
|
|
|
126
128
|
&--round {width: $height;padding: 0 round(divide($height, 2));}
|
|
127
129
|
}
|
|
128
130
|
&.size--sm {
|
|
131
|
+
$height: round(1.1 * divide($base-font-size, 2)) * 2;
|
|
129
132
|
font-size: round(0.75 * divide($base-font-size, 2)) * 2;
|
|
130
|
-
height:
|
|
131
|
-
|
|
133
|
+
height: $height;
|
|
134
|
+
line-height: $height;
|
|
135
|
+
min-width: $height;
|
|
132
136
|
}
|
|
133
137
|
&.size--md {
|
|
138
|
+
$height: round(1.3 * divide($base-font-size, 2)) * 2;
|
|
134
139
|
font-size: round(0.9 * divide($base-font-size, 2)) * 2;
|
|
135
|
-
height:
|
|
136
|
-
|
|
140
|
+
height: $height;
|
|
141
|
+
line-height: $height;
|
|
142
|
+
min-width: $height;
|
|
137
143
|
}
|
|
138
144
|
&.size--lg {
|
|
145
|
+
$height: round(1.5 * divide($base-font-size, 2)) * 2;
|
|
139
146
|
font-size: round(1.05 * divide($base-font-size, 2)) * 2;
|
|
140
|
-
height:
|
|
141
|
-
|
|
147
|
+
height: $height;
|
|
148
|
+
line-height: $height;
|
|
149
|
+
min-width: $height;
|
|
142
150
|
}
|
|
143
151
|
&.size--xl {
|
|
152
|
+
$height: round(1.8 * divide($base-font-size, 2)) * 2;
|
|
144
153
|
font-size: round(1.2 * divide($base-font-size, 2)) * 2;
|
|
145
|
-
height:
|
|
146
|
-
|
|
154
|
+
height: $height;
|
|
155
|
+
line-height: $height;
|
|
156
|
+
min-width: $height;
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
// Position.
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
.w-card(:class="classes" :style="styles")
|
|
3
3
|
.w-card__title(
|
|
4
4
|
v-if="$slots.title"
|
|
5
|
-
:class="{ 'w-card__title--has-toolbar': titleHasToolbar,
|
|
5
|
+
:class="{ 'w-card__title--has-toolbar': titleHasToolbar, ...titleClasses }")
|
|
6
6
|
slot(name="title")
|
|
7
|
-
.w-card__title(v-else-if="title" :class="
|
|
7
|
+
.w-card__title(v-else-if="title" :class="titleClasses" v-html="title")
|
|
8
8
|
w-image.w-card__image(v-if="image" :src="image" v-bind="imgProps")
|
|
9
9
|
slot(name="image-content")
|
|
10
|
-
.w-card__content(:class="
|
|
10
|
+
.w-card__content(:class="contentClasses")
|
|
11
11
|
slot
|
|
12
12
|
.w-card__actions(
|
|
13
13
|
v-if="$slots.actions"
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<script>
|
|
19
|
+
import { objectifyClasses } from '../utils/index'
|
|
20
|
+
|
|
19
21
|
export default {
|
|
20
22
|
name: 'w-card',
|
|
21
23
|
|
|
@@ -28,21 +30,31 @@ export default {
|
|
|
28
30
|
title: { type: String },
|
|
29
31
|
image: { type: String },
|
|
30
32
|
imageProps: { type: Object },
|
|
31
|
-
titleClass: { type: String },
|
|
32
|
-
contentClass: { type: String }
|
|
33
|
+
titleClass: { type: [String, Object, Array] },
|
|
34
|
+
contentClass: { type: [String, Object, Array] }
|
|
33
35
|
},
|
|
34
36
|
|
|
35
37
|
emits: [],
|
|
36
38
|
|
|
37
39
|
computed: {
|
|
40
|
+
titleClasses () {
|
|
41
|
+
return objectifyClasses(this.titleClass)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
contentClasses () {
|
|
45
|
+
return objectifyClasses(this.contentClass)
|
|
46
|
+
},
|
|
47
|
+
|
|
38
48
|
titleHasToolbar () {
|
|
39
49
|
const { title } = this.$slots
|
|
40
50
|
return title && title().map(vnode => vnode.type.name).join('').includes('w-toolbar')
|
|
41
51
|
},
|
|
52
|
+
|
|
42
53
|
actionsHasToolbar () {
|
|
43
54
|
const { actions } = this.$slots
|
|
44
55
|
return actions && actions().map(vnode => vnode.type.name).join('').includes('w-toolbar')
|
|
45
56
|
},
|
|
57
|
+
|
|
46
58
|
imgProps () {
|
|
47
59
|
return {
|
|
48
60
|
tag: 'div',
|
|
@@ -50,6 +62,7 @@ export default {
|
|
|
50
62
|
...this.imageProps
|
|
51
63
|
}
|
|
52
64
|
},
|
|
65
|
+
|
|
53
66
|
classes () {
|
|
54
67
|
return {
|
|
55
68
|
[this.color]: this.color,
|
|
@@ -59,6 +72,7 @@ export default {
|
|
|
59
72
|
'w-card--shadow': this.shadow
|
|
60
73
|
}
|
|
61
74
|
},
|
|
75
|
+
|
|
62
76
|
styles () {
|
|
63
77
|
return false
|
|
64
78
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.w-confirm
|
|
3
|
+
w-menu(v-model="showPopup" v-bind="wMenuProps")
|
|
4
|
+
template(#activator="{ on }")
|
|
5
|
+
w-button.w-confirm__button(v-on="on" v-bind="buttonProps")
|
|
6
|
+
slot
|
|
7
|
+
w-flex(:column="!inline" align-center)
|
|
8
|
+
div
|
|
9
|
+
slot(name="question") Are you sure?
|
|
10
|
+
.w-flex.justify-end(:class="inline ? 'ml2' : 'mt2'")
|
|
11
|
+
w-button.mr2(
|
|
12
|
+
v-if="!noCancel"
|
|
13
|
+
v-bind="cancelButton"
|
|
14
|
+
:bg-color="(cancelButton || {}).bgColor || 'error'"
|
|
15
|
+
@click="onCancel")
|
|
16
|
+
slot(name="cancel") Cancel
|
|
17
|
+
w-button(
|
|
18
|
+
v-bind="confirmButton"
|
|
19
|
+
:bg-color="(confirmButton || {}).bgColor || 'success'"
|
|
20
|
+
@click="onConfirm")
|
|
21
|
+
slot(name="confirm") Confirm
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
export default {
|
|
26
|
+
name: 'w-confirm',
|
|
27
|
+
props: {
|
|
28
|
+
// Main button props.
|
|
29
|
+
bgColor: { type: String },
|
|
30
|
+
color: { type: String },
|
|
31
|
+
icon: { type: String },
|
|
32
|
+
mainButton: { type: Object }, // Allow passing down an object of props to the w-button component.
|
|
33
|
+
|
|
34
|
+
// Cancel & confirm buttons props.
|
|
35
|
+
noCancel: { type: Boolean }, // Removes the cancel button.
|
|
36
|
+
cancelButton: { type: [Boolean, Object] }, // Allow passing down an object of props to the w-button component.
|
|
37
|
+
confirmButton: { type: Object }, // Allow passing down an object of props to the w-button component.
|
|
38
|
+
|
|
39
|
+
// global menu props.
|
|
40
|
+
inline: { type: Boolean }, // The layout inside the menu.
|
|
41
|
+
|
|
42
|
+
// W-menu props.
|
|
43
|
+
menu: { type: Object }, // Allow passing down an object of props to the w-menu component.
|
|
44
|
+
// All the menu props shorthands, as long as they don't conflict with the button props.
|
|
45
|
+
noArrow: { type: Boolean }, // Adds a directional triangle to the edge of the menu, like a tooltip.
|
|
46
|
+
top: { type: Boolean },
|
|
47
|
+
bottom: { type: Boolean },
|
|
48
|
+
left: { type: Boolean },
|
|
49
|
+
right: { type: Boolean },
|
|
50
|
+
alignTop: { type: Boolean },
|
|
51
|
+
alignBottom: { type: Boolean },
|
|
52
|
+
alignLeft: { type: Boolean },
|
|
53
|
+
alignRight: { type: Boolean },
|
|
54
|
+
persistent: { type: Boolean },
|
|
55
|
+
transition: { type: String }
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
emits: ['cancel', 'confirm'],
|
|
59
|
+
|
|
60
|
+
data: () => ({
|
|
61
|
+
showPopup: false,
|
|
62
|
+
props: []
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
computed: {
|
|
66
|
+
wMenuProps () {
|
|
67
|
+
return {
|
|
68
|
+
top: this.top,
|
|
69
|
+
bottom: this.bottom,
|
|
70
|
+
left: this.left,
|
|
71
|
+
right: this.right,
|
|
72
|
+
arrow: !this.noArrow,
|
|
73
|
+
alignTop: this.alignTop,
|
|
74
|
+
alignBottom: this.alignBottom,
|
|
75
|
+
alignLeft: this.alignLeft,
|
|
76
|
+
alignRight: this.alignRight,
|
|
77
|
+
persistent: this.persistent,
|
|
78
|
+
transition: this.transition,
|
|
79
|
+
...this.menu
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
buttonProps () {
|
|
83
|
+
return {
|
|
84
|
+
bgColor: this.bgColor,
|
|
85
|
+
color: this.color,
|
|
86
|
+
icon: this.icon,
|
|
87
|
+
...this.mainButton
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
methods: {
|
|
93
|
+
onCancel () {
|
|
94
|
+
this.$emit('cancel')
|
|
95
|
+
this.showPopup = false
|
|
96
|
+
},
|
|
97
|
+
onConfirm () {
|
|
98
|
+
this.$emit('confirm')
|
|
99
|
+
this.showPopup = false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
@@ -48,7 +48,7 @@ export default {
|
|
|
48
48
|
ligature () {
|
|
49
49
|
if (!config.iconsLigature) return false
|
|
50
50
|
|
|
51
|
-
const [fontName, icon] = this.
|
|
51
|
+
const [fontName, icon] = this.$slots.default()[0].children
|
|
52
52
|
return fontName === config.iconsLigature && { fontName, icon }
|
|
53
53
|
},
|
|
54
54
|
forcedSize () {
|
|
@@ -66,7 +66,7 @@ export default {
|
|
|
66
66
|
},
|
|
67
67
|
classes () {
|
|
68
68
|
return {
|
|
69
|
-
[this.
|
|
69
|
+
[this.$slots.default()[0].children]: true,
|
|
70
70
|
[this.color]: this.color,
|
|
71
71
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
72
72
|
[`size--${this.presetSize}`]: this.presetSize && !this.forcedSize,
|
|
@@ -87,16 +87,6 @@ export default {
|
|
|
87
87
|
styles () {
|
|
88
88
|
return this.forcedSize && `font-size: ${this.forcedSize}`
|
|
89
89
|
}
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
created () {
|
|
93
|
-
const { default: slotContent } = this.$slots
|
|
94
|
-
this.icon = slotContent ? (slotContent()[0].children || '').trim() : ''
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
beforeUpdate () {
|
|
98
|
-
const { default: slotContent } = this.$slots
|
|
99
|
-
this.icon = slotContent ? (slotContent()[0].children || '').trim() : ''
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
92
|
</script>
|
|
@@ -121,9 +111,7 @@ export default {
|
|
|
121
111
|
&.size--lg {font-size: round(1.7 * $base-font-size);}
|
|
122
112
|
&.size--xl {font-size: 2 * $base-font-size;}
|
|
123
113
|
|
|
124
|
-
//
|
|
125
|
-
// .w-button &, .w-alert & {font-size: round(1.4 * $base-font-size);}
|
|
126
|
-
// Always an even number to vertical align well in button.
|
|
114
|
+
// Always an even number to align well vertically in a button.
|
|
127
115
|
.w-button.size--xs & {font-size: round(0.95 * divide($base-font-size, 2)) * 2;}
|
|
128
116
|
.w-alert.size--xs & {font-size: $base-font-size;}
|
|
129
117
|
.w-button.size--sm &, .w-alert.size--sm & {font-size: round(1.15 * $base-font-size);}
|
|
@@ -55,10 +55,13 @@ component(
|
|
|
55
55
|
@change="onFileChange"
|
|
56
56
|
:multiple="multiple || null"
|
|
57
57
|
v-bind="attrs")
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
transition-group.w-input__input.w-input__input--file(tag="label" name="fade" :for="`w-input--${_.uid}`")
|
|
59
|
+
span.w-input__no-file(v-if="!inputFiles.length && isFocused" key="no-file")
|
|
60
|
+
slot(name="no-file")
|
|
61
|
+
template(v-if="$slots['no-file'] === undefined") No file
|
|
62
|
+
span(v-for="(file, i) in inputFiles" :key="file.lastModified")
|
|
60
63
|
| {{ i ? ', ': '' }}
|
|
61
|
-
span {{ file.base }}
|
|
64
|
+
span.filename(:key="`${i}b`") {{ file.base }}
|
|
62
65
|
| {{ file.extension }}
|
|
63
66
|
|
|
64
67
|
template(v-if="labelPosition === 'inside' && showLabelInside")
|
|
@@ -78,7 +81,18 @@ component(
|
|
|
78
81
|
:for="`w-input--${_.uid}`"
|
|
79
82
|
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
//- Files preview.
|
|
85
|
+
label.d-flex(v-if="type === 'file' && inputFiles.length" :for="`w-input--${_.uid}`")
|
|
86
|
+
template(v-for="(file, i) in inputFiles")
|
|
87
|
+
i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
|
|
88
|
+
v-if="file.progress < 100"
|
|
89
|
+
:key="`${i}a`")
|
|
90
|
+
img.w-input__file-preview(
|
|
91
|
+
v-else-if="file.preview"
|
|
92
|
+
:key="`${i}b`"
|
|
93
|
+
:src="file.preview"
|
|
94
|
+
alt="")
|
|
95
|
+
i.w-icon.wi-file.w-input__file-preview.primary(v-else :key="`${i}c`")
|
|
82
96
|
|
|
83
97
|
//- Right label.
|
|
84
98
|
template(v-if="labelPosition === 'right'")
|
|
@@ -189,6 +203,7 @@ export default {
|
|
|
189
203
|
classes () {
|
|
190
204
|
return {
|
|
191
205
|
'w-input': true,
|
|
206
|
+
'w-input--file': this.type === 'file',
|
|
192
207
|
'w-input--disabled': this.isDisabled,
|
|
193
208
|
'w-input--readonly': this.isReadonly,
|
|
194
209
|
[`w-input--${this.hasValue ? 'filled' : 'empty'}`]: true,
|
|
@@ -206,6 +221,7 @@ export default {
|
|
|
206
221
|
return {
|
|
207
222
|
[this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
|
|
208
223
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
224
|
+
'w-input__input-wrap--file': this.type === 'file',
|
|
209
225
|
'w-input__input-wrap--round': this.round,
|
|
210
226
|
'w-input__input-wrap--tile': this.tile,
|
|
211
227
|
// Box adds a padding on input. If there is a bgColor or shadow, a padding is needed.
|
|
@@ -235,16 +251,8 @@ export default {
|
|
|
235
251
|
this.$emit('blur', e)
|
|
236
252
|
},
|
|
237
253
|
|
|
254
|
+
// For file input.
|
|
238
255
|
onFileChange (e) {
|
|
239
|
-
// [...e.target.files].forEach((file, i) => {
|
|
240
|
-
// this.inputFiles[i] = Object.assign({}, file)
|
|
241
|
-
// this.filePreview(file)
|
|
242
|
-
// })
|
|
243
|
-
|
|
244
|
-
// this.inputFiles = [...e.target.files].map(file => {
|
|
245
|
-
// this.filePreview(file)
|
|
246
|
-
// return file
|
|
247
|
-
// })
|
|
248
256
|
this.inputFiles = [...e.target.files].map(original => {
|
|
249
257
|
const [, base, extension] = original.name.match(/^(.*)(\..*?)$/)
|
|
250
258
|
const file = reactive({
|
|
@@ -254,7 +262,8 @@ export default {
|
|
|
254
262
|
type: original.type,
|
|
255
263
|
size: original.size,
|
|
256
264
|
lastModified: original.lastModified,
|
|
257
|
-
preview:
|
|
265
|
+
preview: null,
|
|
266
|
+
progress: 0
|
|
258
267
|
})
|
|
259
268
|
|
|
260
269
|
this.filePreview(original, file)
|
|
@@ -264,14 +273,22 @@ export default {
|
|
|
264
273
|
this.$emit('update:modelValue', this.inputFiles)
|
|
265
274
|
},
|
|
266
275
|
|
|
276
|
+
// For file input.
|
|
267
277
|
filePreview (original, file) {
|
|
268
|
-
// Check if the file is an image.
|
|
269
|
-
if (original.type && !original.type.startsWith('image/')) return
|
|
270
|
-
|
|
271
278
|
const reader = new FileReader()
|
|
272
|
-
|
|
273
|
-
|
|
279
|
+
|
|
280
|
+
// Check if the file is an image and set a preview image.
|
|
281
|
+
if (original.type && original.type.startsWith('image/')) {
|
|
282
|
+
reader.addEventListener('load', e => {
|
|
283
|
+
file.preview = e.target.result
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Used to display a spinner while the file is loading.
|
|
288
|
+
reader.addEventListener('progress', e => {
|
|
289
|
+
if (e.loaded && e.total) file.progress = e.loaded * 100 / e.total
|
|
274
290
|
})
|
|
291
|
+
|
|
275
292
|
reader.readAsDataURL(original)
|
|
276
293
|
}
|
|
277
294
|
},
|
|
@@ -295,6 +312,11 @@ $inactive-color: #777;
|
|
|
295
312
|
align-items: center;
|
|
296
313
|
font-size: $base-font-size;
|
|
297
314
|
|
|
315
|
+
&--file {
|
|
316
|
+
flex-wrap: nowrap;
|
|
317
|
+
align-items: flex-end;
|
|
318
|
+
}
|
|
319
|
+
|
|
298
320
|
// Input field wrapper.
|
|
299
321
|
// ------------------------------------------------------
|
|
300
322
|
&__input-wrap {
|
|
@@ -310,6 +332,9 @@ $inactive-color: #777;
|
|
|
310
332
|
.w-input--floating-label & {margin-top: 3 * $base-increment;}
|
|
311
333
|
.w-input[class^="bdrs"] &, .w-input[class*=" bdrs"] & {border-radius: inherit;}
|
|
312
334
|
|
|
335
|
+
// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
|
|
336
|
+
&--file {min-width: 0;}
|
|
337
|
+
|
|
313
338
|
&--underline {
|
|
314
339
|
border-bottom-left-radius: initial;
|
|
315
340
|
border-bottom-right-radius: initial;
|
|
@@ -409,6 +434,32 @@ $inactive-color: #777;
|
|
|
409
434
|
opacity: 0;
|
|
410
435
|
}
|
|
411
436
|
|
|
437
|
+
&__input--file {
|
|
438
|
+
> span {
|
|
439
|
+
display: inline-flex;
|
|
440
|
+
overflow: hidden;
|
|
441
|
+
white-space: nowrap;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.filename {
|
|
445
|
+
margin-left: 0.2em;
|
|
446
|
+
overflow: hidden;
|
|
447
|
+
text-overflow: ellipsis;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
> span:first-child .filename {margin-left: 0;}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
&__no-file {
|
|
454
|
+
position: absolute;
|
|
455
|
+
top: 0;
|
|
456
|
+
bottom: 0;
|
|
457
|
+
left: 0;
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
color: $disabled-color;
|
|
461
|
+
}
|
|
462
|
+
|
|
412
463
|
&__file-preview {
|
|
413
464
|
margin-left: 4px;
|
|
414
465
|
max-height: 2em;
|
|
@@ -474,6 +525,7 @@ $inactive-color: #777;
|
|
|
474
525
|
.w-input--floating-label & {
|
|
475
526
|
transform-origin: 0 0;
|
|
476
527
|
transition: $transition-duration ease;
|
|
528
|
+
will-change: transform;
|
|
477
529
|
}
|
|
478
530
|
|
|
479
531
|
// move label with underline style.
|