wave-ui 1.66.0 → 1.67.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 +398 -87
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +7 -5
- package/src/wave-ui/components/index.js +1 -1
- package/src/wave-ui/components/w-button/button.vue +14 -10
- package/src/wave-ui/components/w-card.vue +12 -4
- package/src/wave-ui/components/w-confirm.vue +7 -1
- package/src/wave-ui/components/w-progress.vue +1 -1
- package/src/wave-ui/components/w-rating.vue +2 -1
- package/src/wave-ui/components/w-scrollable.vue +243 -0
- package/src/wave-ui/components/w-select.vue +20 -10
- package/src/wave-ui/components/w-table.vue +214 -64
- package/src/wave-ui/components/w-tabs/index.vue +39 -34
- package/src/wave-ui/components/w-tag.vue +1 -0
- package/src/wave-ui/components/w-tree.vue +104 -29
- package/src/wave-ui/core.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.67.0",
|
|
4
4
|
"description": "An emerging UI framework for Vue.js (2 & 3) with only the bright side. :sunny:",
|
|
5
5
|
"author": "Antoni Andre <antoniandre.web@gmail.com>",
|
|
6
6
|
"homepage": "https://antoniandre.github.io/wave-ui",
|
|
@@ -46,12 +46,14 @@
|
|
|
46
46
|
"build": "vite build --base /wave-ui/",
|
|
47
47
|
"build-bundle": "BUNDLE=true vite build && mv ./dist/style.css ./dist/wave-ui.css",
|
|
48
48
|
"serve": "vite preview --base /wave-ui/",
|
|
49
|
-
"lint": "vite lint"
|
|
49
|
+
"lint": "vite lint",
|
|
50
|
+
"publish": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag && npm publish --tag latest"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@babel/core": "^7.
|
|
53
|
-
"@babel/eslint-parser": "^7.
|
|
53
|
+
"@babel/core": "^7.22.10",
|
|
54
|
+
"@babel/eslint-parser": "^7.22.10",
|
|
54
55
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
56
|
+
"@faker-js/faker": "^8.0.2",
|
|
55
57
|
"@mdi/font": "^6.9.96",
|
|
56
58
|
"@vitejs/plugin-vue": "^1.10.2",
|
|
57
59
|
"autoprefixer": "^10.4.13",
|
|
@@ -68,7 +70,7 @@
|
|
|
68
70
|
"gsap": "^3.11.3",
|
|
69
71
|
"ionicons": "^4.6.3",
|
|
70
72
|
"material-design-icons": "^3.0.1",
|
|
71
|
-
"postcss": "^8.4.
|
|
73
|
+
"postcss": "^8.4.27",
|
|
72
74
|
"pug": "^3.0.2",
|
|
73
75
|
"rollup-plugin-delete": "^2.0.0",
|
|
74
76
|
"sass": "^1.56.1",
|
|
@@ -28,7 +28,7 @@ export { default as WProgress } from './w-progress.vue'
|
|
|
28
28
|
export { default as WRadio } from './w-radio.vue'
|
|
29
29
|
export { default as WRadios } from './w-radios.vue'
|
|
30
30
|
export { default as WRating } from './w-rating.vue'
|
|
31
|
-
export { default as
|
|
31
|
+
export { default as WScrollable } from './w-scrollable.vue'
|
|
32
32
|
export { default as WSelect } from './w-select.vue'
|
|
33
33
|
export { default as WSlider } from './w-slider.vue'
|
|
34
34
|
export { default as WSpinner } from './w-spinner.vue'
|
|
@@ -163,7 +163,7 @@ $spinner-size: 40;
|
|
|
163
163
|
z-index: 1;
|
|
164
164
|
// Background-color must not transition to not affect the hover & focus states
|
|
165
165
|
// in :before & :after.
|
|
166
|
-
transition: $transition-duration, background-color 0s, padding 0s;
|
|
166
|
+
transition: all $transition-duration, background-color 0s, padding 0s;
|
|
167
167
|
-webkit-tap-highlight-color: transparent;
|
|
168
168
|
|
|
169
169
|
// In w-flex wrapper, allow overriding the default `align-self: center`.
|
|
@@ -252,10 +252,10 @@ $spinner-size: 40;
|
|
|
252
252
|
// Button states.
|
|
253
253
|
// ------------------------------------------------------
|
|
254
254
|
// Hover & focus states - inside button.
|
|
255
|
-
&:hover:before, &:focus:before {opacity: 0.
|
|
256
|
-
&--dark:hover:before, &--dark:focus:before {opacity: 0.
|
|
257
|
-
&--outline:hover:before, &--outline:focus:before,
|
|
258
|
-
&--text:hover:before, &--text:focus:before {opacity: 0.12;}
|
|
255
|
+
&:hover:before, &:focus-visible:before {opacity: 0.2;}
|
|
256
|
+
&--dark:hover:before, &--dark:focus-visible:before {opacity: 0.4;}
|
|
257
|
+
&--outline:hover:before, &--outline:focus-visible:before,
|
|
258
|
+
&--text:hover:before, &--text:focus-visible:before {opacity: 0.12;}
|
|
259
259
|
|
|
260
260
|
// Focus state - outside button.
|
|
261
261
|
&:after {
|
|
@@ -272,22 +272,25 @@ $spinner-size: 40;
|
|
|
272
272
|
transition: opacity 0.2s cubic-bezier(0.45, 0.05, 0.55, 0.95), transform 0.25s ease-in;
|
|
273
273
|
transform: scale(0.85, 0.7);
|
|
274
274
|
}
|
|
275
|
-
&:focus:after {
|
|
275
|
+
&:focus-visible:after {
|
|
276
276
|
opacity: 0.4;
|
|
277
277
|
transform: scale(1);
|
|
278
278
|
transition: opacity 0.2s cubic-bezier(0.45, 0.05, 0.55, 0.95), transform 0.25s ease-out;
|
|
279
279
|
}
|
|
280
|
-
&--dark:focus:after {opacity: 0.2;}
|
|
280
|
+
&--dark:focus-visible:after {opacity: 0.2;}
|
|
281
281
|
|
|
282
282
|
// Active state.
|
|
283
|
-
&:active
|
|
284
|
-
|
|
283
|
+
&:active {transform: scale(1.02);}
|
|
284
|
+
&:active:before {opacity: 0.3;}
|
|
285
|
+
&--dark:active:before, &.primary--bg:active:before {opacity: 0.35;}
|
|
285
286
|
|
|
286
287
|
// Disable visual feedback on loading and disabled buttons.
|
|
287
288
|
&--loading:hover:before,
|
|
288
|
-
&--loading:focus:before,
|
|
289
|
+
&--loading:focus-visible:before,
|
|
289
290
|
&--loading:active:before,
|
|
290
291
|
&[disabled]:before {opacity: 0;}
|
|
292
|
+
&--loading:active,
|
|
293
|
+
&[disabled] {transform: none;}
|
|
291
294
|
// ------------------------------------------------------
|
|
292
295
|
|
|
293
296
|
// Disable events binding on nested content.
|
|
@@ -303,6 +306,7 @@ $spinner-size: 40;
|
|
|
303
306
|
align-items: center;
|
|
304
307
|
justify-content: center;
|
|
305
308
|
background: inherit;
|
|
309
|
+
border-radius: inherit;
|
|
306
310
|
|
|
307
311
|
svg {height: 75%;}
|
|
308
312
|
circle {
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
.w-card(:class="classes")
|
|
3
3
|
.w-card__title(
|
|
4
4
|
v-if="$slots.title"
|
|
5
|
-
:class="{ 'w-card__title--has-toolbar': titleHasToolbar, ...titleClasses }")
|
|
5
|
+
:class="{ 'w-card__title--has-toolbar': $slots.title && titleHasToolbar, ...titleClasses }")
|
|
6
6
|
slot(name="title")
|
|
7
|
-
.w-card__title(
|
|
7
|
+
.w-card__title(
|
|
8
|
+
v-else-if="title"
|
|
9
|
+
v-html="title"
|
|
10
|
+
:class="{ 'w-card__title--has-toolbar': title && titleHasToolbar, ...titleClasses }")
|
|
8
11
|
w-image.w-card__image(v-if="image" :src="image" v-bind="imgProps")
|
|
9
12
|
slot(name="image-content")
|
|
10
13
|
.w-card__content(:class="contentClasses")
|
|
@@ -94,12 +97,17 @@ export default {
|
|
|
94
97
|
padding: (2 * $base-increment) (3 * $base-increment);
|
|
95
98
|
font-size: 1.3em;
|
|
96
99
|
border-bottom: $border;
|
|
97
|
-
border-top-left-radius: inherit;
|
|
98
|
-
border-top-right-radius: inherit;
|
|
99
100
|
|
|
100
101
|
&--has-toolbar {padding: 0;border-bottom: none;}
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
// When there is no title apply the border radius to the image.
|
|
105
|
+
&__image:first-child {
|
|
106
|
+
border-top-left-radius: inherit;
|
|
107
|
+
border-top-right-radius: inherit;
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
&__content {
|
|
104
112
|
padding: 3 * $base-increment;
|
|
105
113
|
flex-grow: 1;
|
|
@@ -128,7 +128,13 @@ export default {
|
|
|
128
128
|
icon: this.icon,
|
|
129
129
|
tooltip: label,
|
|
130
130
|
tooltipProps,
|
|
131
|
-
...this.mainButton
|
|
131
|
+
...this.mainButton,
|
|
132
|
+
...this.$attrs,
|
|
133
|
+
// Vue 2 specific:
|
|
134
|
+
// The classes and styles are not in $attrs. Add them from $vnode.data.staticClass, so
|
|
135
|
+
// the button gets these classes wen used with tooltip.
|
|
136
|
+
// https://v2.vuejs.org/v2/guide/components-props.html#Disabling-Attribute-Inheritance
|
|
137
|
+
class: [this.$vnode.data.staticClass, this.$vnode.data.class]
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
},
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.w-scrollable(
|
|
3
|
+
ref="scrollable"
|
|
4
|
+
@mouseenter="onMouseEnter"
|
|
5
|
+
@mouseleave="onMouseLeave"
|
|
6
|
+
@mousewheel="onMouseWheel"
|
|
7
|
+
:class="scrollableClasses"
|
|
8
|
+
v-bind="$attrs"
|
|
9
|
+
:style="scrollableStyles")
|
|
10
|
+
slot
|
|
11
|
+
.w-scrollbar(ref="track" @mousedown="onTrackMouseDown" :class="scrollbarClasses")
|
|
12
|
+
.w-scrollbar__thumb(ref="thumb" :style="thumbStyles")
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
const domProps = {
|
|
17
|
+
h: {
|
|
18
|
+
horizOrVert: 'horizontal',
|
|
19
|
+
topOrLeft: 'left',
|
|
20
|
+
widthOrHeight: 'width',
|
|
21
|
+
offsetWidthOrHeight: 'offsetWidth',
|
|
22
|
+
maxWidthOrHeight: 'max-width',
|
|
23
|
+
scrollWidthOrHeight: 'scrollWidth',
|
|
24
|
+
clientXorY: 'clientX',
|
|
25
|
+
deltaXorY: 'deltaX',
|
|
26
|
+
scrollTopOrLeft: 'scrollLeft'
|
|
27
|
+
},
|
|
28
|
+
v: {
|
|
29
|
+
horizOrVert: 'vertical',
|
|
30
|
+
topOrLeft: 'top',
|
|
31
|
+
widthOrHeight: 'height',
|
|
32
|
+
offsetWidthOrHeight: 'offsetHeight',
|
|
33
|
+
maxWidthOrHeight: 'max-height',
|
|
34
|
+
scrollWidthOrHeight: 'scrollHeight',
|
|
35
|
+
clientXorY: 'clientY',
|
|
36
|
+
deltaXorY: 'deltaY',
|
|
37
|
+
scrollTopOrLeft: 'scrollTop'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
name: 'w-scrollable',
|
|
43
|
+
props: {
|
|
44
|
+
color: { type: String, default: 'primary' },
|
|
45
|
+
bgColor: { type: String },
|
|
46
|
+
width: { type: [Number, String] },
|
|
47
|
+
height: { type: [Number, String] }
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
emits: [],
|
|
51
|
+
|
|
52
|
+
data: () => ({
|
|
53
|
+
mounted: false,
|
|
54
|
+
scrollable: {
|
|
55
|
+
top: null,
|
|
56
|
+
left: null,
|
|
57
|
+
hovered: false
|
|
58
|
+
},
|
|
59
|
+
scrollValuePercent: 0
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
computed: {
|
|
63
|
+
isHorizontal () {
|
|
64
|
+
if (!this.mounted) return false
|
|
65
|
+
console.log('💂♂️', this.$refs.scrollable?.scrollWidth, this.$refs.scrollable?.offsetWidth)
|
|
66
|
+
return (this.width && !this.height) || (this.$refs.scrollable?.scrollWidth > this.$refs.scrollable?.offsetWidth)
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
m () { // m = shorthand for map of DOM properties.
|
|
70
|
+
return domProps[this.isHorizontal ? 'h' : 'v']
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
scrollableClasses () {
|
|
74
|
+
return {
|
|
75
|
+
[`w-scrollable--${this.m.horizOrVert}`]: true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
scrollbarClasses () {
|
|
80
|
+
return {
|
|
81
|
+
[`w-scrollbar--${this.m.horizOrVert}`]: true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
thumbSizePercent () {
|
|
86
|
+
if (!this.mounted) return 0
|
|
87
|
+
console.log('😒', this[this.m.widthOrHeight], this.$refs.scrollable[[this.m.offsetWidthOrHeight]])
|
|
88
|
+
const widthOrHeight = this[this.m.widthOrHeight] ?? this.$refs.scrollable[[this.m.offsetWidthOrHeight]]
|
|
89
|
+
// if (widthOrHeight === undefined) widthOrHeight = this.$refs.scrollable.offsetWidthOrHeight
|
|
90
|
+
return (widthOrHeight * 100 / this.$refs.scrollable?.[this.m.scrollWidthOrHeight]) || 0
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
scrollableStyles () {
|
|
94
|
+
return {
|
|
95
|
+
[this.m.maxWidthOrHeight]: `${this[this.m.widthOrHeight]}px`
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
thumbStyles () {
|
|
100
|
+
let topOrLeftValue = this.scrollValuePercent
|
|
101
|
+
topOrLeftValue = Math.max(0, Math.min(topOrLeftValue, 100 - this.thumbSizePercent))
|
|
102
|
+
return {
|
|
103
|
+
[this.m.widthOrHeight]: `${this.thumbSizePercent}%`,
|
|
104
|
+
[this.m.topOrLeft]: `${topOrLeftValue}%`
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
methods: {
|
|
110
|
+
onTrackMouseDown (e) {
|
|
111
|
+
if (this.isDisabled || this.isReadonly) return
|
|
112
|
+
// On touch screen don't listen for both touchstart & mousedown.
|
|
113
|
+
if ('ontouchstart' in window && e.type === 'mousedown') return
|
|
114
|
+
|
|
115
|
+
const { top, left, width, height } = this.$refs.track.getBoundingClientRect()
|
|
116
|
+
if (this.isHorizontal) {
|
|
117
|
+
this.$refs.track.width = width
|
|
118
|
+
this.$refs.track.left = left
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.$refs.track.height = height
|
|
122
|
+
this.$refs.track.top = top
|
|
123
|
+
}
|
|
124
|
+
this.dragging = true
|
|
125
|
+
|
|
126
|
+
this.computeScroll(e.type === 'touchstart' ? e.touches[0][this.m.clientXorY] : e[this.m.clientXorY])
|
|
127
|
+
this.scroll()
|
|
128
|
+
|
|
129
|
+
document.addEventListener(e.type === 'touchstart' ? 'touchmove' : 'mousemove', this.onDrag)
|
|
130
|
+
document.addEventListener(e.type === 'touchstart' ? 'touchend' : 'mouseup', this.onMouseUp, { once: true })
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
onDrag (e) {
|
|
134
|
+
this.computeScroll((e.type === 'touchmove' ? e.touches[0][this.m.clientXorY] : e[this.m.clientXorY]))
|
|
135
|
+
this.scroll()
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
onMouseUp (e) {
|
|
139
|
+
this.dragging = false
|
|
140
|
+
document.removeEventListener(e.type === 'touchend' ? 'touchmove' : 'mousemove', this.onDrag)
|
|
141
|
+
if (this.$refs.thumb) this.$refs.thumb.focus()
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
onMouseEnter () {
|
|
145
|
+
this.scrollable.hovered = true
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
onMouseLeave () {
|
|
149
|
+
this.scrollable.hovered = false
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
onMouseWheel (e) {
|
|
153
|
+
if (!this.scrollable.hovered) return // Only scroll a w-scrollable element that is being hovered.
|
|
154
|
+
|
|
155
|
+
// When scrolling beyond limits, release the mousewheel and scroll the parent.
|
|
156
|
+
if (this.scrollValuePercent <= 0 && e[this.m.deltaXorY] < 0) return
|
|
157
|
+
else if (this.scrollValuePercent >= 100 - this.thumbSizePercent && e[this.m.deltaXorY] > 0) return
|
|
158
|
+
|
|
159
|
+
e.preventDefault() // Hold the scroll in the hovered w-scrollable element.
|
|
160
|
+
|
|
161
|
+
this.scrollValuePercent += e[this.m.deltaXorY] * 0.05
|
|
162
|
+
this.scrollValuePercent = Math.max(0, Math.min(this.scrollValuePercent, 100))
|
|
163
|
+
this.scroll()
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
computeScroll (cursorPositionXorY) {
|
|
167
|
+
const { top, left, width, height } = this.$refs.scrollable.getBoundingClientRect()
|
|
168
|
+
const topOrLeft = this.isHorizontal ? left : top
|
|
169
|
+
const widthOrHeight = this.isHorizontal ? width : height
|
|
170
|
+
this.scrollValuePercent = Math.max(0, Math.min(((cursorPositionXorY - topOrLeft) / widthOrHeight) * 100, 100))
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
scroll () {
|
|
174
|
+
this.$refs.scrollable[this.m.scrollTopOrLeft] = this.scrollValuePercent * this.$refs.scrollable?.[this.m.scrollWidthOrHeight] / 100
|
|
175
|
+
this.updateThumbPosition()
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
updateThumbPosition () {
|
|
179
|
+
this.$refs.thumb.style[this.m.topOrLeft] = this.scrollValuePercent
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
mounted () {
|
|
184
|
+
this.mounted = true
|
|
185
|
+
const { top, left } = this.$refs.scrollable.getBoundingClientRect()
|
|
186
|
+
this.scrollable.top = top
|
|
187
|
+
this.scrollable.left = left
|
|
188
|
+
|
|
189
|
+
this.$el.parentNode.style.position = 'relative'
|
|
190
|
+
this.$el.parentNode.style[this.m.maxWidthOrHeight] = `${this[this.m.widthOrHeight]}px`
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<style lang="scss">
|
|
196
|
+
.w-scrollable {
|
|
197
|
+
position: relative;
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.w-scrollbar {
|
|
202
|
+
position: absolute;
|
|
203
|
+
background: #000;
|
|
204
|
+
user-select: none;
|
|
205
|
+
|
|
206
|
+
&--horizontal {
|
|
207
|
+
left: 0;
|
|
208
|
+
right: 0;
|
|
209
|
+
bottom: 0;
|
|
210
|
+
height: 8px;
|
|
211
|
+
}
|
|
212
|
+
&--vertical {
|
|
213
|
+
top: 0;
|
|
214
|
+
bottom: 0;
|
|
215
|
+
right: 0;
|
|
216
|
+
width: 8px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
&__thumb {
|
|
220
|
+
position: absolute;
|
|
221
|
+
background: #333;
|
|
222
|
+
border-radius: $border-radius;
|
|
223
|
+
z-index: 1;
|
|
224
|
+
will-change: top left;
|
|
225
|
+
|
|
226
|
+
&:hover {background: #444;}
|
|
227
|
+
}
|
|
228
|
+
&--horizontal &__thumb {
|
|
229
|
+
height: 6px;
|
|
230
|
+
left: 0;
|
|
231
|
+
right: 0;
|
|
232
|
+
margin-top: 1px;
|
|
233
|
+
margin-bottom: 1px;
|
|
234
|
+
}
|
|
235
|
+
&--vertical &__thumb {
|
|
236
|
+
width: 6px;
|
|
237
|
+
top: 0;
|
|
238
|
+
bottom: 0;
|
|
239
|
+
margin-left: 1px;
|
|
240
|
+
margin-right: 1px;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
component(
|
|
3
3
|
ref="formEl"
|
|
4
4
|
:is="formRegister ? 'w-form-element' : 'div'"
|
|
5
|
-
v-bind="formRegister && { validators, inputValue: selectionString, disabled: isDisabled, readonly: isReadonly }"
|
|
5
|
+
v-bind="formRegister && { validators, inputValue: selectionString, disabled: isDisabled, readonly: isReadonly, isFocused }"
|
|
6
6
|
:valid.sync="valid"
|
|
7
7
|
@reset="onReset"
|
|
8
8
|
:wrap="hasLabel && labelPosition !== 'inside'"
|
|
@@ -16,6 +16,7 @@ component(
|
|
|
16
16
|
|
|
17
17
|
w-menu(
|
|
18
18
|
v-model="showMenu"
|
|
19
|
+
@close="!$event && closeMenu()"
|
|
19
20
|
:menu-class="`w-select__menu ${menuClass || ''}`"
|
|
20
21
|
transition="slide-fade-down"
|
|
21
22
|
:append-to="(menuProps || {}).appendTo !== undefined ? (menuProps || {}).appendTo : undefined"
|
|
@@ -26,7 +27,7 @@ component(
|
|
|
26
27
|
template(#activator="{ on }")
|
|
27
28
|
//- Input wrapper.
|
|
28
29
|
.w-select__selection-wrap(
|
|
29
|
-
@click="!isDisabled && !isReadonly && (
|
|
30
|
+
@click="!isDisabled && !isReadonly && onInputFieldClick()"
|
|
30
31
|
role="button"
|
|
31
32
|
aria-haspopup="listbox"
|
|
32
33
|
:aria-expanded="showMenu ? 'true' : 'false'"
|
|
@@ -76,7 +77,7 @@ component(
|
|
|
76
77
|
@item-click="$emit('item-click', $event)"
|
|
77
78
|
@item-select="onListItemSelect"
|
|
78
79
|
@keydown:enter="noUnselect && !multiple && closeMenu()"
|
|
79
|
-
@keydown:escape="closeMenu"
|
|
80
|
+
@keydown:escape="showMenu && (this.showMenu = false) /* Will call closeMenu() from w-menu(@close). */"
|
|
80
81
|
:value="inputValue"
|
|
81
82
|
:items="selectItems"
|
|
82
83
|
:multiple="multiple"
|
|
@@ -190,7 +191,7 @@ export default {
|
|
|
190
191
|
},
|
|
191
192
|
selectionString () {
|
|
192
193
|
return this.inputValue && this.inputValue.map(
|
|
193
|
-
item => item[this.itemValueKey] !== undefined ? item[this.itemLabelKey] : (item[this.itemLabelKey]
|
|
194
|
+
item => item[this.itemValueKey] !== undefined ? item[this.itemLabelKey] : (item[this.itemLabelKey] ?? item)
|
|
194
195
|
).join(', ')
|
|
195
196
|
},
|
|
196
197
|
classes () {
|
|
@@ -227,12 +228,18 @@ export default {
|
|
|
227
228
|
|
|
228
229
|
methods: {
|
|
229
230
|
onFocus (e) {
|
|
231
|
+
if (this.isFocused) return
|
|
232
|
+
|
|
230
233
|
this.isFocused = true
|
|
231
234
|
this.$emit('focus', e)
|
|
232
235
|
return false
|
|
233
236
|
},
|
|
234
237
|
|
|
235
238
|
onBlur (e) {
|
|
239
|
+
// As long as the menu is open the focus is still on.
|
|
240
|
+
// When closing the menu, the focus is given back to the input (not blur yet).
|
|
241
|
+
if (this.showMenu) return
|
|
242
|
+
|
|
236
243
|
this.isFocused = false
|
|
237
244
|
this.$emit('blur', e)
|
|
238
245
|
},
|
|
@@ -241,11 +248,10 @@ export default {
|
|
|
241
248
|
// Forbid typing in contenteditable element.
|
|
242
249
|
// Note: using contenteditable rather than input in order to be able to fit the select list
|
|
243
250
|
// to its content with CSS. Only contenteditable divs/non-interactive elements can react to focus/blur ).
|
|
244
|
-
|
|
251
|
+
// still allow meta key & ctrl key combinations and the tab key (9).
|
|
252
|
+
if (!e.metaKey && !e.ctrlKey && e.keyCode !== 9) e.preventDefault()
|
|
245
253
|
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
if (e.keyCode === 27) this.closeMenu() // Escape.
|
|
254
|
+
if (e.keyCode === 27 && this.showMenu) this.closeMenu() // Escape.
|
|
249
255
|
else if (e.keyCode === 13) this.openMenu() // Enter.
|
|
250
256
|
|
|
251
257
|
// Up & down arrows.
|
|
@@ -284,12 +290,16 @@ export default {
|
|
|
284
290
|
this.$emit('update:modelValue', selection)
|
|
285
291
|
this.$emit('input', selection)
|
|
286
292
|
},
|
|
293
|
+
onInputFieldClick () {
|
|
294
|
+
if (this.showMenu) this.showMenu = false // Will call `closeMenu()` from w-menu(@close).
|
|
295
|
+
else this.openMenu()
|
|
296
|
+
},
|
|
287
297
|
|
|
288
298
|
// Called on item selection: on click & `enter` keydown.
|
|
289
299
|
onListItemSelect (e) {
|
|
290
300
|
this.$emit('item-select', e)
|
|
291
301
|
// Close menu after selection on single select, but keep open if multiple.
|
|
292
|
-
if (!this.multiple) this.closeMenu()
|
|
302
|
+
if (!this.multiple) this.showMenu = false // Will call `closeMenu()` from w-menu(@close).
|
|
293
303
|
},
|
|
294
304
|
|
|
295
305
|
onReset () {
|
|
@@ -332,7 +342,7 @@ export default {
|
|
|
332
342
|
|
|
333
343
|
// Close the dropdown selection list.
|
|
334
344
|
closeMenu () {
|
|
335
|
-
if (
|
|
345
|
+
if (this.menuProps?.hideOnMenuClick === false) return
|
|
336
346
|
|
|
337
347
|
this.showMenu = false
|
|
338
348
|
// Set the focus back on the main w-select input.
|