wave-ui 2.23.0 → 2.27.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 +7337 -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-button.vue +1 -0
- 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 +155 -2
- 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 +212 -15
- 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/_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 +3 -10
- package/src/wave-ui/scss/index.scss +1 -0
- package/src/wave-ui/utils/index.js +11 -0
|
@@ -8,6 +8,7 @@ component(
|
|
|
8
8
|
:wrap="hasLabel && labelPosition !== 'inside'"
|
|
9
9
|
:class="classes")
|
|
10
10
|
input(v-if="type === 'hidden'" type="hidden" :name="name || null" v-model="inputValue")
|
|
11
|
+
|
|
11
12
|
template(v-else)
|
|
12
13
|
//- Left label.
|
|
13
14
|
template(v-if="labelPosition === 'left'")
|
|
@@ -23,6 +24,7 @@ component(
|
|
|
23
24
|
:for="`w-input--${_.uid}`"
|
|
24
25
|
@click="$emit('click:inner-icon-left', $event)") {{ innerIconLeft }}
|
|
25
26
|
input.w-input__input(
|
|
27
|
+
v-if="type !== 'file'"
|
|
26
28
|
v-model="inputValue"
|
|
27
29
|
v-on="listeners"
|
|
28
30
|
@input="onInput"
|
|
@@ -43,6 +45,25 @@ component(
|
|
|
43
45
|
:required="required || null"
|
|
44
46
|
:tabindex="tabindex || null"
|
|
45
47
|
v-bind="attrs")
|
|
48
|
+
template(v-if="type === 'file'")
|
|
49
|
+
input(
|
|
50
|
+
:id="`w-input--${_.uid}`"
|
|
51
|
+
type="file"
|
|
52
|
+
:name="name || null"
|
|
53
|
+
@focus="onFocus"
|
|
54
|
+
@blur="onBlur"
|
|
55
|
+
@change="onFileChange"
|
|
56
|
+
:multiple="multiple || null"
|
|
57
|
+
v-bind="attrs")
|
|
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")
|
|
63
|
+
| {{ i ? ', ': '' }}
|
|
64
|
+
span.filename(:key="`${i}b`") {{ file.base }}
|
|
65
|
+
| {{ file.extension }}
|
|
66
|
+
|
|
46
67
|
template(v-if="labelPosition === 'inside' && showLabelInside")
|
|
47
68
|
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
48
69
|
v-if="$slots.default"
|
|
@@ -60,6 +81,19 @@ component(
|
|
|
60
81
|
:for="`w-input--${_.uid}`"
|
|
61
82
|
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
62
83
|
|
|
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`")
|
|
96
|
+
|
|
63
97
|
//- Right label.
|
|
64
98
|
template(v-if="labelPosition === 'right'")
|
|
65
99
|
label.w-input__label.w-input__label--right.w-form-el-shakable(
|
|
@@ -80,6 +114,7 @@ component(
|
|
|
80
114
|
**/
|
|
81
115
|
|
|
82
116
|
import FormElementMixin from '../mixins/form-elements'
|
|
117
|
+
import { reactive } from 'vue'
|
|
83
118
|
|
|
84
119
|
export default {
|
|
85
120
|
name: 'w-input',
|
|
@@ -108,6 +143,8 @@ export default {
|
|
|
108
143
|
round: { type: Boolean },
|
|
109
144
|
shadow: { type: Boolean },
|
|
110
145
|
tile: { type: Boolean },
|
|
146
|
+
multiple: { type: Boolean }, // Only for file uploads.
|
|
147
|
+
preview: { type: Boolean }, // Only for file uploads.
|
|
111
148
|
loading: { type: Boolean }
|
|
112
149
|
// Props from mixin: name, disabled, readonly, required, tabindex, validators.
|
|
113
150
|
// Computed from mixin: inputName, isDisabled & isReadonly.
|
|
@@ -121,11 +158,20 @@ export default {
|
|
|
121
158
|
// In case of incorrect input type="number", the inputValue gets emptied,
|
|
122
159
|
// and the label would come back on top of the input text.
|
|
123
160
|
inputNumberError: false,
|
|
124
|
-
isFocused: false
|
|
161
|
+
isFocused: false,
|
|
162
|
+
inputFiles: [], // For input type file.
|
|
163
|
+
fileReader: null // For input type file.
|
|
125
164
|
}
|
|
126
165
|
},
|
|
127
166
|
|
|
128
167
|
computed: {
|
|
168
|
+
attrs () {
|
|
169
|
+
// Keep the `class` attribute bound to the wrapper and not the input.
|
|
170
|
+
// eslint-disable-next-line no-unused-vars
|
|
171
|
+
const { class: classes, ...attrs } = this.$attrs
|
|
172
|
+
return attrs
|
|
173
|
+
},
|
|
174
|
+
|
|
129
175
|
listeners () {
|
|
130
176
|
// Remove the events that are fired separately, so they don't fire twice.
|
|
131
177
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -138,17 +184,26 @@ export default {
|
|
|
138
184
|
return htmlAttrs
|
|
139
185
|
},
|
|
140
186
|
hasValue () {
|
|
141
|
-
return
|
|
187
|
+
return (
|
|
188
|
+
this.inputValue ||
|
|
189
|
+
['date', 'time'].includes(this.type) ||
|
|
190
|
+
(this.type === 'number' && this.inputNumberError) ||
|
|
191
|
+
(this.type === 'file' && this.inputFiles.length)
|
|
192
|
+
)
|
|
142
193
|
},
|
|
194
|
+
|
|
143
195
|
hasLabel () {
|
|
144
196
|
return this.label || this.$slots.default
|
|
145
197
|
},
|
|
198
|
+
|
|
146
199
|
showLabelInside () {
|
|
147
200
|
return !this.staticLabel || (!this.hasValue && !this.placeholder)
|
|
148
201
|
},
|
|
202
|
+
|
|
149
203
|
classes () {
|
|
150
204
|
return {
|
|
151
205
|
'w-input': true,
|
|
206
|
+
'w-input--file': this.type === 'file',
|
|
152
207
|
'w-input--disabled': this.isDisabled,
|
|
153
208
|
'w-input--readonly': this.isReadonly,
|
|
154
209
|
[`w-input--${this.hasValue ? 'filled' : 'empty'}`]: true,
|
|
@@ -161,10 +216,12 @@ export default {
|
|
|
161
216
|
'w-input--inner-icon-right': this.innerIconRight
|
|
162
217
|
}
|
|
163
218
|
},
|
|
219
|
+
|
|
164
220
|
inputWrapClasses () {
|
|
165
221
|
return {
|
|
166
222
|
[this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
|
|
167
223
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
224
|
+
'w-input__input-wrap--file': this.type === 'file',
|
|
168
225
|
'w-input__input-wrap--round': this.round,
|
|
169
226
|
'w-input__input-wrap--tile': this.tile,
|
|
170
227
|
// Box adds a padding on input. If there is a bgColor or shadow, a padding is needed.
|
|
@@ -183,13 +240,56 @@ export default {
|
|
|
183
240
|
this.$emit('update:modelValue', this.inputValue)
|
|
184
241
|
this.$emit('input', this.inputValue)
|
|
185
242
|
},
|
|
243
|
+
|
|
186
244
|
onFocus (e) {
|
|
187
245
|
this.isFocused = true
|
|
188
246
|
this.$emit('focus', e)
|
|
189
247
|
},
|
|
248
|
+
|
|
190
249
|
onBlur (e) {
|
|
191
250
|
this.isFocused = false
|
|
192
251
|
this.$emit('blur', e)
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// For file input.
|
|
255
|
+
onFileChange (e) {
|
|
256
|
+
this.inputFiles = [...e.target.files].map(original => {
|
|
257
|
+
const [, base, extension] = original.name.match(/^(.*)(\..*?)$/)
|
|
258
|
+
const file = reactive({
|
|
259
|
+
name: original.name,
|
|
260
|
+
base,
|
|
261
|
+
extension,
|
|
262
|
+
type: original.type,
|
|
263
|
+
size: original.size,
|
|
264
|
+
lastModified: original.lastModified,
|
|
265
|
+
preview: null,
|
|
266
|
+
progress: 0
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
this.filePreview(original, file)
|
|
270
|
+
|
|
271
|
+
return file
|
|
272
|
+
})
|
|
273
|
+
this.$emit('update:modelValue', this.inputFiles)
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// For file input.
|
|
277
|
+
filePreview (original, file) {
|
|
278
|
+
const reader = new FileReader()
|
|
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
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
reader.readAsDataURL(original)
|
|
193
293
|
}
|
|
194
294
|
},
|
|
195
295
|
|
|
@@ -212,6 +312,11 @@ $inactive-color: #777;
|
|
|
212
312
|
align-items: center;
|
|
213
313
|
font-size: $base-font-size;
|
|
214
314
|
|
|
315
|
+
&--file {
|
|
316
|
+
flex-wrap: nowrap;
|
|
317
|
+
align-items: flex-end;
|
|
318
|
+
}
|
|
319
|
+
|
|
215
320
|
// Input field wrapper.
|
|
216
321
|
// ------------------------------------------------------
|
|
217
322
|
&__input-wrap {
|
|
@@ -227,6 +332,9 @@ $inactive-color: #777;
|
|
|
227
332
|
.w-input--floating-label & {margin-top: 3 * $base-increment;}
|
|
228
333
|
.w-input[class^="bdrs"] &, .w-input[class*=" bdrs"] & {border-radius: inherit;}
|
|
229
334
|
|
|
335
|
+
// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
|
|
336
|
+
&--file {min-width: 0;}
|
|
337
|
+
|
|
230
338
|
&--underline {
|
|
231
339
|
border-bottom-left-radius: initial;
|
|
232
340
|
border-bottom-right-radius: initial;
|
|
@@ -282,6 +390,8 @@ $inactive-color: #777;
|
|
|
282
390
|
font-size: inherit;
|
|
283
391
|
color: inherit;
|
|
284
392
|
text-align: inherit;
|
|
393
|
+
display: inline-flex;
|
|
394
|
+
align-items: center;
|
|
285
395
|
background: none;
|
|
286
396
|
border: none;
|
|
287
397
|
outline: none;
|
|
@@ -314,6 +424,48 @@ $inactive-color: #777;
|
|
|
314
424
|
|
|
315
425
|
&--disabled input::placeholder {color: inherit;}
|
|
316
426
|
|
|
427
|
+
// Upload field.
|
|
428
|
+
// ------------------------------------------------------
|
|
429
|
+
// Hides the built-in file input (replaced with a more stylable element).
|
|
430
|
+
input[type="file"] {
|
|
431
|
+
position: absolute;
|
|
432
|
+
z-index: -1;
|
|
433
|
+
pointer-events: none;
|
|
434
|
+
opacity: 0;
|
|
435
|
+
}
|
|
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
|
+
|
|
463
|
+
&__file-preview {
|
|
464
|
+
margin-left: 4px;
|
|
465
|
+
max-height: 2em;
|
|
466
|
+
align-self: flex-end;
|
|
467
|
+
}
|
|
468
|
+
|
|
317
469
|
// Icons inside.
|
|
318
470
|
// ------------------------------------------------------
|
|
319
471
|
&__icon {position: absolute;}
|
|
@@ -373,6 +525,7 @@ $inactive-color: #777;
|
|
|
373
525
|
.w-input--floating-label & {
|
|
374
526
|
transform-origin: 0 0;
|
|
375
527
|
transition: $transition-duration ease;
|
|
528
|
+
will-change: transform;
|
|
376
529
|
}
|
|
377
530
|
|
|
378
531
|
// move label with underline style.
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
.w-menu-wrap
|
|
2
|
+
.w-menu-wrap
|
|
3
3
|
slot(name="activator" :on="activatorEventHandlers")
|
|
4
|
-
transition(:name="transitionName")
|
|
4
|
+
transition(:name="transitionName" appear)
|
|
5
5
|
.w-menu(
|
|
6
|
-
v-if="custom"
|
|
6
|
+
v-if="custom && menuVisible"
|
|
7
7
|
ref="menu"
|
|
8
|
-
v-show="showMenu"
|
|
9
8
|
@click="hideOnMenuClick && closeMenu(true)"
|
|
10
9
|
@mouseenter="showOnHover && (hoveringMenu = true)"
|
|
11
10
|
@mouseleave="showOnHover && ((hoveringMenu = false), closeMenu())"
|
|
@@ -13,15 +12,14 @@
|
|
|
13
12
|
:style="styles")
|
|
14
13
|
slot
|
|
15
14
|
w-card.w-menu(
|
|
16
|
-
v-else
|
|
15
|
+
v-else-if="menuVisible"
|
|
17
16
|
ref="menu"
|
|
18
|
-
v-show="showMenu"
|
|
19
17
|
@click.native="hideOnMenuClick && closeMenu(true)"
|
|
20
18
|
@mouseenter.native="showOnHover && (hoveringMenu = true)"
|
|
21
19
|
@mouseleave.native="showOnHover && ((hoveringMenu = false), closeMenu())"
|
|
22
20
|
:tile="tile"
|
|
23
|
-
:title-class="
|
|
24
|
-
:content-class="
|
|
21
|
+
:title-class="titleClasses"
|
|
22
|
+
:content-class="contentClasses"
|
|
25
23
|
:shadow="shadow"
|
|
26
24
|
:no-border="noBorder"
|
|
27
25
|
:class="classes"
|
|
@@ -34,10 +32,12 @@
|
|
|
34
32
|
w-overlay(
|
|
35
33
|
v-if="overlay"
|
|
36
34
|
ref="overlay"
|
|
37
|
-
:model-value="
|
|
35
|
+
:model-value="menuVisible"
|
|
38
36
|
:persistent="persistent"
|
|
37
|
+
:class="overlayClasses"
|
|
38
|
+
v-bind="overlayProps"
|
|
39
39
|
:z-index="(zIndex || 200) - 1"
|
|
40
|
-
@update:model-value="
|
|
40
|
+
@update:model-value="menuVisible = false")
|
|
41
41
|
</template>
|
|
42
42
|
|
|
43
43
|
<script>
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* and move the menu elsewhere in the DOM.
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
|
+
import { objectifyClasses } from '../utils/index'
|
|
53
54
|
import { consoleWarn } from '../utils/console'
|
|
54
55
|
|
|
55
56
|
// const marginFromWindowSide = 4 // Amount of px from a window side, instead of overflowing.
|
|
@@ -69,10 +70,11 @@ export default {
|
|
|
69
70
|
round: { type: Boolean },
|
|
70
71
|
noBorder: { type: Boolean },
|
|
71
72
|
transition: { type: String },
|
|
72
|
-
menuClass: { type: String },
|
|
73
|
-
titleClass: { type: String },
|
|
74
|
-
contentClass: { type: String },
|
|
73
|
+
menuClass: { type: [String, Object, Array] },
|
|
74
|
+
titleClass: { type: [String, Object, Array] },
|
|
75
|
+
contentClass: { type: [String, Object, Array] },
|
|
75
76
|
// Position.
|
|
77
|
+
arrow: { type: Boolean }, // The small triangle pointing toward the activator.
|
|
76
78
|
detachTo: { type: [String, Boolean, Object] },
|
|
77
79
|
fixed: { type: Boolean },
|
|
78
80
|
top: { type: Boolean },
|
|
@@ -86,6 +88,8 @@ export default {
|
|
|
86
88
|
zIndex: { type: [Number, String, Boolean] },
|
|
87
89
|
minWidth: { type: [Number, String] }, // can be like: `40`, `5em`, `activator`.
|
|
88
90
|
overlay: { type: Boolean },
|
|
91
|
+
overlayClass: { type: [String, Object, Array] },
|
|
92
|
+
overlayProps: { type: Object }, // Allow passing down an object of props to the w-overlay component.
|
|
89
93
|
persistent: { type: Boolean },
|
|
90
94
|
noPosition: { type: Boolean }
|
|
91
95
|
},
|
|
@@ -93,7 +97,7 @@ export default {
|
|
|
93
97
|
emits: ['input', 'update:modelValue', 'open', 'close'],
|
|
94
98
|
|
|
95
99
|
data: () => ({
|
|
96
|
-
|
|
100
|
+
menuVisible: false,
|
|
97
101
|
hoveringActivator: false,
|
|
98
102
|
hoveringMenu: false,
|
|
99
103
|
// The menu computed top & left coordinates.
|
|
@@ -161,27 +165,46 @@ export default {
|
|
|
161
165
|
)
|
|
162
166
|
},
|
|
163
167
|
|
|
168
|
+
menuClasses () {
|
|
169
|
+
return objectifyClasses(this.menuClass)
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
titleClasses () {
|
|
173
|
+
return objectifyClasses(this.titleClass)
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
contentClasses () {
|
|
177
|
+
return objectifyClasses(this.contentClass)
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
overlayClasses () {
|
|
181
|
+
return objectifyClasses(this.overlayClass)
|
|
182
|
+
},
|
|
183
|
+
|
|
164
184
|
classes () {
|
|
165
185
|
return {
|
|
166
186
|
[this.color]: this.color,
|
|
167
187
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
168
|
-
|
|
169
|
-
[`w-menu--${this.position}`]:
|
|
170
|
-
[`w-menu--align-${this.alignment}`]: this.alignment,
|
|
188
|
+
...this.menuClasses,
|
|
189
|
+
[`w-menu--${this.position}`]: !this.noPosition,
|
|
190
|
+
[`w-menu--align-${this.alignment}`]: !this.noPosition && this.alignment,
|
|
171
191
|
'w-menu--tile': this.tile,
|
|
172
192
|
'w-menu--card': !this.custom,
|
|
173
193
|
'w-menu--round': this.round,
|
|
194
|
+
'w-menu--arrow': this.arrow,
|
|
174
195
|
'w-menu--shadow': this.shadow,
|
|
175
196
|
'w-menu--fixed': this.fixed
|
|
176
197
|
}
|
|
177
198
|
},
|
|
178
199
|
|
|
200
|
+
// The floatting menu styles.
|
|
179
201
|
styles () {
|
|
180
202
|
return {
|
|
181
203
|
zIndex: this.zIndex || this.zIndex === 0 || (this.overlay && !this.zIndex && 200) || null,
|
|
182
204
|
top: (this.menuCoordinates.top && `${~~this.menuCoordinates.top}px`) || null,
|
|
183
205
|
left: (this.menuCoordinates.left && `${~~this.menuCoordinates.left}px`) || null,
|
|
184
|
-
minWidth: (this.minWidth && this.menuMinWidth) || null
|
|
206
|
+
minWidth: (this.minWidth && this.menuMinWidth) || null,
|
|
207
|
+
'--w-menu-bg-color': this.arrow && this.$waveui.colors[this.bgColor || 'white']
|
|
185
208
|
}
|
|
186
209
|
},
|
|
187
210
|
|
|
@@ -214,7 +237,7 @@ export default {
|
|
|
214
237
|
|
|
215
238
|
methods: {
|
|
216
239
|
toggleMenu (e) {
|
|
217
|
-
let shouldShowMenu = this.
|
|
240
|
+
let shouldShowMenu = this.menuVisible
|
|
218
241
|
if ('ontouchstart' in window && this.showOnHover && e.type === 'click') {
|
|
219
242
|
shouldShowMenu = !shouldShowMenu
|
|
220
243
|
}
|
|
@@ -230,11 +253,20 @@ export default {
|
|
|
230
253
|
|
|
231
254
|
this.timeoutId = clearTimeout(this.timeoutId)
|
|
232
255
|
|
|
233
|
-
if (shouldShowMenu)
|
|
256
|
+
if (shouldShowMenu) {
|
|
257
|
+
this.$emit('update:modelValue', (this.menuVisible = true))
|
|
258
|
+
this.$emit('input', true)
|
|
259
|
+
this.$emit('open')
|
|
260
|
+
|
|
261
|
+
this.openMenu(e)
|
|
262
|
+
}
|
|
234
263
|
else this.closeMenu()
|
|
235
264
|
},
|
|
236
265
|
|
|
237
|
-
openMenu (e) {
|
|
266
|
+
async openMenu (e) {
|
|
267
|
+
this.menuVisible = true
|
|
268
|
+
await this.insertMenu()
|
|
269
|
+
|
|
238
270
|
if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
|
|
239
271
|
|
|
240
272
|
if (!this.noPosition) this.computeMenuPosition(e)
|
|
@@ -243,10 +275,10 @@ export default {
|
|
|
243
275
|
// if we don't postpone the Menu apparition it will start transition from a visible menu and
|
|
244
276
|
// thus will not transition.
|
|
245
277
|
this.timeoutId = setTimeout(() => {
|
|
246
|
-
this.$emit('update:modelValue',
|
|
278
|
+
this.$emit('update:modelValue', true)
|
|
247
279
|
this.$emit('input', true)
|
|
248
280
|
this.$emit('open')
|
|
249
|
-
},
|
|
281
|
+
}, 0)
|
|
250
282
|
|
|
251
283
|
if (!this.persistent) document.addEventListener('mousedown', this.onOutsideMousedown)
|
|
252
284
|
if (!this.noPosition) window.addEventListener('resize', this.onResize)
|
|
@@ -265,14 +297,14 @@ export default {
|
|
|
265
297
|
async closeMenu (force = false) {
|
|
266
298
|
// Might be already closed.
|
|
267
299
|
// E.g. showOnHover & hideOnMenuClick: on click, force hide then mouseleave is also firing.
|
|
268
|
-
if (!this.
|
|
300
|
+
if (!this.menuVisible) return
|
|
269
301
|
|
|
270
302
|
if (this.showOnHover && !force) {
|
|
271
303
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
272
304
|
if (this.showOnHover && (this.hoveringMenu || this.hoveringActivator)) return
|
|
273
305
|
}
|
|
274
306
|
|
|
275
|
-
this.$emit('update:modelValue', (this.
|
|
307
|
+
this.$emit('update:modelValue', (this.menuVisible = false))
|
|
276
308
|
this.$emit('input', false)
|
|
277
309
|
this.$emit('close')
|
|
278
310
|
// Remove the mousedown listener if the menu got closed without a mousedown outside of the menu.
|
|
@@ -282,7 +314,7 @@ export default {
|
|
|
282
314
|
|
|
283
315
|
onOutsideMousedown (e) {
|
|
284
316
|
if (!this.menuEl.contains(e.target) && !this.activatorEl.contains(e.target)) {
|
|
285
|
-
this.$emit('update:modelValue', (this.
|
|
317
|
+
this.$emit('update:modelValue', (this.menuVisible = false))
|
|
286
318
|
this.$emit('input', false)
|
|
287
319
|
this.$emit('close')
|
|
288
320
|
document.removeEventListener('mousedown', this.onOutsideMousedown)
|
|
@@ -397,49 +429,53 @@ export default {
|
|
|
397
429
|
this.menuEl.style.visibility = null
|
|
398
430
|
|
|
399
431
|
// The menu coordinates are also recalculated while resizing window with open menu: keep the menu visible.
|
|
400
|
-
if (!this.
|
|
432
|
+
if (!this.menuVisible) this.menuEl.style.display = 'none'
|
|
401
433
|
|
|
402
434
|
this.menuCoordinates = { top, left }
|
|
403
435
|
},
|
|
404
436
|
|
|
405
437
|
insertMenu () {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
this.detachToTarget.appendChild(this.menuEl)
|
|
438
|
+
return new Promise(resolve => {
|
|
439
|
+
this.$nextTick(() => {
|
|
440
|
+
this.menuEl = this.$refs.menu?.$el || this.$refs.menu
|
|
441
|
+
|
|
442
|
+
// Move the menu elsewhere in the DOM.
|
|
443
|
+
// wrapper.parentNode.insertBefore(this.menuEl, wrapper)
|
|
444
|
+
this.detachToTarget.appendChild(this.menuEl)
|
|
445
|
+
resolve()
|
|
446
|
+
})
|
|
447
|
+
})
|
|
417
448
|
},
|
|
418
449
|
|
|
419
450
|
removeMenu () {
|
|
420
|
-
|
|
421
|
-
if (this.menuEl && this.menuEl.parentNode) this.menuEl.parentNode.removeChild(this.menuEl)
|
|
451
|
+
if (this.menuEl && this.menuEl.parentNode) this.menuEl.remove()
|
|
422
452
|
}
|
|
423
453
|
},
|
|
424
454
|
|
|
425
455
|
mounted () {
|
|
426
|
-
|
|
427
|
-
this.
|
|
428
|
-
|
|
456
|
+
const wrapper = this.$el
|
|
457
|
+
this.activatorEl = wrapper.firstElementChild
|
|
458
|
+
// Unwrap the activator element.
|
|
459
|
+
wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
|
|
460
|
+
|
|
461
|
+
// Unwrap the overlay.
|
|
462
|
+
if (this.overlay) {
|
|
463
|
+
this.overlayEl = this.$refs.overlay?.$el
|
|
464
|
+
wrapper.parentNode.insertBefore(this.overlayEl, wrapper)
|
|
465
|
+
}
|
|
429
466
|
|
|
430
467
|
if (this.modelValue) this.toggleMenu({ type: 'click', target: this.activatorEl })
|
|
431
468
|
},
|
|
432
469
|
|
|
433
470
|
beforeUnmount () {
|
|
434
471
|
this.removeMenu()
|
|
435
|
-
|
|
436
|
-
if (this.
|
|
437
|
-
if (this.activatorEl && this.activatorEl.parentNode) this.activatorEl.parentNode.removeChild(this.activatorEl)
|
|
472
|
+
if (this.overlay && this.overlayEl.parentNode) this.overlayEl.remove()
|
|
473
|
+
if (this.activatorEl && this.activatorEl.parentNode) this.activatorEl.remove()
|
|
438
474
|
},
|
|
439
475
|
|
|
440
476
|
watch: {
|
|
441
477
|
modelValue (bool) {
|
|
442
|
-
if (!!bool !== this.
|
|
478
|
+
if (!!bool !== this.menuVisible) this.toggleMenu({ type: 'click', target: this.activatorEl })
|
|
443
479
|
},
|
|
444
480
|
detachTo () {
|
|
445
481
|
this.removeMenu()
|
|
@@ -469,5 +505,14 @@ export default {
|
|
|
469
505
|
&--bottom {margin-top: 3 * $base-increment;}
|
|
470
506
|
&--left {margin-left: -3 * $base-increment;}
|
|
471
507
|
&--right {margin-left: 3 * $base-increment;}
|
|
508
|
+
|
|
509
|
+
&--arrow {
|
|
510
|
+
&.w-menu--top {margin-top: -4 * $base-increment;}
|
|
511
|
+
&.w-menu--bottom {margin-top: 4 * $base-increment;}
|
|
512
|
+
&.w-menu--left {margin-left: -4 * $base-increment;}
|
|
513
|
+
&.w-menu--right {margin-left: 4 * $base-increment;}
|
|
514
|
+
|
|
515
|
+
@include triangle(var(--w-menu-bg-color), '.w-menu', 9px);
|
|
516
|
+
}
|
|
472
517
|
}
|
|
473
518
|
</style>
|
|
@@ -27,6 +27,7 @@ component(
|
|
|
27
27
|
label.w-switch__label.w-form-el-shakable(v-else-if="label" :for="`w-switch--${_.uid}`" v-html="label")
|
|
28
28
|
.w-switch__input(
|
|
29
29
|
@click="$refs.input.focus();$refs.input.click()"
|
|
30
|
+
v-on="$attrs"
|
|
30
31
|
:class="inputClasses")
|
|
31
32
|
template(v-if="hasLabel && !labelOnLeft")
|
|
32
33
|
label.w-switch__label.w-form-el-shakable(v-if="$slots.default" :for="`w-switch--${_.uid}`")
|