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.
@@ -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 this.inputValue || ['date', 'time'].includes(this.type) || (this.type === 'number' && this.inputNumberError)
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(ref="wrapper")
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="titleClass"
24
- :content-class="contentClass"
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="showMenu"
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="showMenu = false")
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
- showMenu: false,
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
- [this.menuClass]: this.menuClass,
169
- [`w-menu--${this.position}`]: true,
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.showMenu
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) this.openMenu(e)
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', (this.showMenu = true))
278
+ this.$emit('update:modelValue', true)
247
279
  this.$emit('input', true)
248
280
  this.$emit('open')
249
- }, 10)
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.showMenu) return
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.showMenu = false))
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.showMenu = false))
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.showMenu) this.menuEl.style.display = 'none'
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
- const wrapper = this.$refs.wrapper
407
- this.menuEl = this.$refs.menu.$el || this.$refs.menu
408
- // Unwrap the activator element.
409
- wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
410
-
411
- // Unwrap the overlay.
412
- if (this.overlay) wrapper.parentNode.insertBefore(this.overlayEl, wrapper)
413
-
414
- // Move the menu elsewhere in the DOM.
415
- // wrapper.parentNode.insertBefore(this.menuEl, wrapper)
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
- // el.remove() doesn't work on IE11.
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
- this.activatorEl = this.$refs.wrapper.firstElementChild
427
- this.overlayEl = this.overlay ? this.$refs.overlay.$el : null
428
- this.insertMenu()
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
- // el.remove() doesn't work on IE11.
436
- if (this.overlay && this.overlayEl.parentNode) this.overlayEl.parentNode.removeChild(this.overlayEl)
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.showMenu) this.toggleMenu({ type: 'click', target: this.activatorEl })
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>
@@ -62,7 +62,7 @@ export default {
62
62
  z-index: 1000;
63
63
  pointer-events: none;
64
64
  width: 280px;
65
- overflow: auto;
65
+ overflow-x: hidden;
66
66
 
67
67
  &--left {right: auto;left: 0;}
68
68
 
@@ -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}`")