wave-ui 1.46.0 → 1.49.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "1.46.0",
3
+ "version": "1.49.1",
4
4
  "description": "An emerging UI framework for Vue.js & Vue 3 with only the bright side. :sunny:",
5
5
  "author": "Antoni Andre <antoniandre.web@gmail.com>",
6
6
  "main": "./dist/wave-ui.umd.js",
@@ -46,7 +46,6 @@
46
46
  "@babel/core": "^7.16.0",
47
47
  "@babel/eslint-parser": "^7.16.3",
48
48
  "@babel/plugin-proposal-class-properties": "^7.16.0",
49
- "@mdi/font": "^5.9.55",
50
49
  "@vitejs/plugin-vue": "^1.10.1",
51
50
  "autoprefixer": "^10.4.0",
52
51
  "axios": "^0.21.4",
@@ -75,5 +74,8 @@
75
74
  "vue-template-compiler": "^2.6.14",
76
75
  "vueperslides": "^2.15.2",
77
76
  "vuex": "^3.6.2"
77
+ },
78
+ "dependencies": {
79
+ "@mdi/font": "^6.5.95"
78
80
  }
79
81
  }
@@ -2,23 +2,23 @@
2
2
  .w-confirm
3
3
  w-menu(v-model="showPopup" v-bind="wMenuProps")
4
4
  template(#activator="{ on }")
5
- w-button.w-confirm__button(v-on="on" v-bind="buttonProps")
5
+ w-button.w-confirm__button(v-on="{ ...$listeners, ...on }" v-bind="buttonProps")
6
6
  slot
7
7
  w-flex(:column="!inline" align-center)
8
8
  div
9
- slot(name="question") Are you sure?
9
+ slot(name="question") {{ question }}
10
10
  .w-flex.justify-end(:class="inline ? 'ml2' : 'mt2'")
11
11
  w-button.mr2(
12
- v-if="!noCancel"
13
- v-bind="cancelButton"
12
+ v-if="cancel !== false"
13
+ v-bind="cancelButtonProps"
14
14
  :bg-color="(cancelButton || {}).bgColor || 'error'"
15
15
  @click="onCancel")
16
- slot(name="cancel") Cancel
16
+ slot(name="cancel") {{ cancelButton.label }}
17
17
  w-button(
18
- v-bind="confirmButton"
18
+ v-bind="confirmButtonProps"
19
19
  :bg-color="(confirmButton || {}).bgColor || 'success'"
20
20
  @click="onConfirm")
21
- slot(name="confirm") Confirm
21
+ slot(name="confirm") {{ confirmButton.label }}
22
22
  </template>
23
23
 
24
24
  <script>
@@ -30,11 +30,16 @@ export default {
30
30
  color: { type: String },
31
31
  icon: { type: String },
32
32
  mainButton: { type: Object }, // Allow passing down an object of props to the w-button component.
33
+ question: { type: String, default: 'Are you sure?' },
33
34
 
34
35
  // Cancel & confirm buttons props.
35
- 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.
36
+ // Allow passing down an object of props to the w-button component.
37
+ // If a string is given, that will be the label of the button.
38
+ // If false, no cancel button.
39
+ cancel: { type: [Boolean, Object, String], default: undefined },
40
+ // Allow passing down an object of props to the w-button component.
41
+ // If a string is given, that will be the label of the button.
42
+ confirm: { type: [Object, String] },
38
43
 
39
44
  // global menu props.
40
45
  inline: { type: Boolean }, // The layout inside the menu.
@@ -63,6 +68,26 @@ export default {
63
68
  }),
64
69
 
65
70
  computed: {
71
+ cancelButton () {
72
+ let button = { label: typeof this.cancel === 'string' ? this.cancel : 'Cancel' }
73
+ if (typeof this.cancel === 'object') button = Object.assign({}, button, this.cancel)
74
+ return button
75
+ },
76
+ // Props to pass down to the w-button component.
77
+ cancelButtonProps () {
78
+ const { label, ...props } = this.cancelButton // Everything except label.
79
+ return props
80
+ },
81
+ confirmButton () {
82
+ let button = { label: typeof this.confirm === 'string' ? this.confirm : 'Confirm' }
83
+ if (typeof this.confirm === 'object') button = Object.assign({}, button, this.confirm)
84
+ return button
85
+ },
86
+ // Props to pass down to the w-button component.
87
+ confirmButtonProps () {
88
+ const { label, ...props } = this.confirmButton // Everything except label.
89
+ return props
90
+ },
66
91
  wMenuProps () {
67
92
  return {
68
93
  top: this.top,
@@ -12,9 +12,16 @@ component(
12
12
  template(v-else)
13
13
  //- Left label.
14
14
  template(v-if="labelPosition === 'left'")
15
- label.w-input__label.w-input__label--left.w-form-el-shakable(v-if="$slots.default" :for="`w-input--${_uid}`")
15
+ label.w-input__label.w-input__label--left.w-form-el-shakable(
16
+ v-if="$slots.default"
17
+ :for="`w-input--${_uid}`"
18
+ :class="validationClasses")
16
19
  slot
17
- label.w-input__label.w-input__label--left.w-form-el-shakable(v-else-if="label" :for="`w-input--${_uid}`" v-html="label")
20
+ label.w-input__label.w-input__label--left.w-form-el-shakable(
21
+ v-else-if="label"
22
+ :for="`w-input--${_uid}`"
23
+ :class="validationClasses"
24
+ v-html="label")
18
25
 
19
26
  //- Input wrapper.
20
27
  .w-input__input-wrap(:class="inputWrapClasses")
@@ -58,47 +65,67 @@ component(
58
65
  @blur="onBlur"
59
66
  @change="onFileChange"
60
67
  :multiple="multiple || null"
61
- v-bind="attrs")
62
- transition-group.w-input__input.w-input__input--file(tag="label" name="fade" :for="`w-input--${_uid}`")
68
+ v-bind="attrs"
69
+ :data-progress="overallFilesProgress /* Needed to emit the overallProgress. */")
70
+ transition-group.w-input__input.w-input__input--file(
71
+ tag="label"
72
+ name="fade"
73
+ :for="`w-input--${_uid}`")
63
74
  span.w-input__no-file(v-if="!inputFiles.length && isFocused" key="no-file")
64
75
  slot(name="no-file")
65
76
  template(v-if="$slots['no-file'] === undefined") No file
66
77
  span(v-for="(file, i) in inputFiles" :key="file.lastModified")
67
78
  | {{ i ? ', ': '' }}
68
79
  span.filename(:key="`${i}b`") {{ file.base }}
69
- | {{ file.extension }}
80
+ | {{ file.extension ? `.${file.extension}` : '' }}
70
81
 
71
82
  template(v-if="labelPosition === 'inside' && showLabelInside")
72
83
  label.w-input__label.w-input__label--inside.w-form-el-shakable(
73
84
  v-if="$slots.default"
74
85
  :for="`w-input--${_uid}`"
75
- :class="isFocused && { [valid === false ? 'error' : color]: color || valid === false }")
86
+ :class="validationClasses")
76
87
  slot
77
88
  label.w-input__label.w-input__label--inside.w-form-el-shakable(
78
89
  v-else-if="label"
79
90
  :for="`w-input--${_uid}`"
80
91
  v-html="label"
81
- :class="isFocused && { [valid === false ? 'error' : color]: color || valid === false }")
92
+ :class="validationClasses")
82
93
  w-icon.w-input__icon.w-input__icon--inner-right(
83
94
  v-if="innerIconRight"
84
95
  tag="label"
85
96
  :for="`w-input--${_uid}`"
86
97
  @click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
87
98
 
99
+ w-progress.fill-width(
100
+ v-if="hasLoading || (showProgress && (uploadInProgress || uploadComplete))"
101
+ size="2"
102
+ :color="progressColor || color"
103
+ :value="showProgress ? (uploadInProgress || uploadComplete) && overallFilesProgress : loadingValue")
104
+
88
105
  //- Files preview.
89
- label.d-flex(v-if="type === 'file' && inputFiles.length" :for="`w-input--${_uid}`")
106
+ label.d-flex(v-if="type === 'file' && preview && inputFiles.length" :for="`w-input--${_uid}`")
90
107
  template(v-for="(file, i) in inputFiles")
91
- i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(v-if="file.progress < 100" :key="i")
108
+ i.w-icon.wi-spinner.w-icon--spin.size--sm.w-input__file-preview.primary(
109
+ v-if="file.progress < 100"
110
+ :key="i")
92
111
  img.w-input__file-preview(v-else-if="file.preview" :key="i" :src="file.preview" alt="")
93
- i.w-icon.wi-file.w-input__file-preview.primary(v-else :key="i")
112
+ i.w-icon.w-input__file-preview.primary.size--md(
113
+ v-else
114
+ :key="i"
115
+ :class="preview && typeof preview === 'string' ? preview : 'wi-file'")
94
116
 
95
117
  //- Right label.
96
118
  template(v-if="labelPosition === 'right'")
97
- label.w-input__label.w-input__label--right.w-form-el-shakable(v-if="$slots.default" :for="`w-input--${_uid}`")
119
+ label.w-input__label.w-input__label--right.w-form-el-shakable(
120
+ v-if="$slots.default"
121
+ :for="`w-input--${_uid}`"
122
+ :class="validationClasses")
98
123
  slot
99
- label.w-input__label.w-input__label--right.w-form-el-shakable(v-else-if="label" :for="`w-input--${_uid}`" v-html="label")
100
-
101
- w-progress.fill-width(v-if="loading" size="2" :color="progressColor || color")
124
+ label.w-input__label.w-input__label--right.w-form-el-shakable(
125
+ v-else-if="label"
126
+ :for="`w-input--${_uid}`"
127
+ :class="validationClasses"
128
+ v-html="label")
102
129
  </template>
103
130
 
104
131
  <script>
@@ -136,13 +163,17 @@ export default {
136
163
  shadow: { type: Boolean },
137
164
  tile: { type: Boolean },
138
165
  multiple: { type: Boolean }, // Only for file uploads.
139
- preview: { type: Boolean }, // Only for file uploads.
140
- loading: { type: Boolean }
166
+ preview: { type: [Boolean, String], default: true }, // Only for file uploads.
167
+ loading: { type: [Boolean, Number], default: false }, // If a number is given, it will be the value of the progress.
168
+ showProgress: { type: [Boolean] }, // Only for file uploads.
169
+ // Allow syncing the files 1 way: prefilling a file is not possible.
170
+ // https://stackoverflow.com/questions/16365668/pre-populate-html-form-file-input
171
+ files: { type: Array }
141
172
  // Props from mixin: name, disabled, readonly, required, tabindex, validators.
142
173
  // Computed from mixin: inputName, isDisabled & isReadonly.
143
174
  },
144
175
 
145
- emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right'],
176
+ emits: ['input', 'update:modelValue', 'focus', 'blur', 'click:inner-icon-left', 'click:inner-icon-right', 'update:overallProgress'],
146
177
 
147
178
  data () {
148
179
  return {
@@ -173,23 +204,54 @@ export default {
173
204
  },
174
205
 
175
206
  hasValue () {
176
- return (
177
- this.inputValue ||
178
- this.inputValue === 0 ||
179
- ['date', 'time'].includes(this.type) ||
180
- (this.type === 'number' && this.inputNumberError) ||
181
- (this.type === 'file' && this.inputFiles.length)
182
- )
207
+ switch (this.type) {
208
+ case 'file': return !!this.inputFiles.length
209
+ case 'number': return this.inputNumberError
210
+ case 'date':
211
+ case 'time':
212
+ return true
213
+ default:
214
+ return this.inputValue || this.inputValue === 0
215
+ }
183
216
  },
184
217
 
185
218
  hasLabel () {
186
219
  return this.label || this.$slots.default
187
220
  },
188
221
 
222
+ hasLoading () {
223
+ return ![undefined, false].includes(this.loading)
224
+ },
225
+
226
+ loadingValue () {
227
+ let value
228
+ if (typeof this.loading === 'number') value = this.loading
229
+ else if (this.loading) {
230
+ value = this.type === 'file' && this.overallFilesProgress ? this.overallFilesProgress : undefined
231
+ }
232
+ return value
233
+ },
234
+
189
235
  showLabelInside () {
190
236
  return !this.staticLabel || (!this.hasValue && !this.placeholder)
191
237
  },
192
238
 
239
+ overallFilesProgress () {
240
+ const progress = this.inputFiles.reduce((total, file) => total + file.progress, 0)
241
+ const total = progress / this.inputFiles.length
242
+ this.$emit('update:overallProgress', this.inputFiles.length ? total : undefined)
243
+
244
+ return total
245
+ },
246
+
247
+ uploadInProgress () {
248
+ return this.overallFilesProgress > 0 && this.overallFilesProgress < 100
249
+ },
250
+
251
+ uploadComplete () {
252
+ return this.overallFilesProgress === 100
253
+ },
254
+
193
255
  classes () {
194
256
  return {
195
257
  'w-input': true,
@@ -207,6 +269,12 @@ export default {
207
269
  }
208
270
  },
209
271
 
272
+ validationClasses () {
273
+ return this.isFocused && {
274
+ [this.valid === false ? 'error' : this.color]: this.color || this.valid === false
275
+ }
276
+ },
277
+
210
278
  inputWrapClasses () {
211
279
  return {
212
280
  [this.valid === false ? 'error' : this.color]: this.color || this.valid === false,
@@ -219,7 +287,8 @@ export default {
219
287
  'w-input__input-wrap--underline': !this.outline,
220
288
  'w-input__input-wrap--shadow': this.shadow,
221
289
  'w-input__input-wrap--no-padding': !this.outline && !this.bgColor && !this.shadow && !this.round,
222
- 'w-input__input-wrap--loading': this.loading
290
+ 'w-input__input-wrap--loading': this.loading || (this.showProgress && this.uploadInProgress),
291
+ 'w-input__input-wrap--upload-complete': this.uploadComplete
223
292
  }
224
293
  }
225
294
  },
@@ -244,37 +313,43 @@ export default {
244
313
  // For file input.
245
314
  onFileChange (e) {
246
315
  this.$set(this, 'inputFiles', [...e.target.files].map(original => {
247
- const [, base, extension] = original.name.match(/^(.*)(\..*?)$/)
316
+ // `full` if there is no filename but only an extension.
317
+ const [, base = '', extension = '', full = ''] = original.name.match(/^(.*?)\.([^.]*)$|(.*)/)
248
318
  const file = Object.assign({}, {
249
319
  name: original.name,
250
- base,
320
+ base: base || full,
251
321
  extension,
252
322
  type: original.type,
253
323
  size: original.size,
254
324
  lastModified: original.lastModified,
255
325
  preview: null,
256
- progress: 0
326
+ progress: 0,
327
+ file: original
257
328
  })
258
329
 
259
- this.filePreview(original, file)
330
+ this.readFile(original, file)
260
331
 
261
332
  return file
262
333
  }))
263
334
  this.$emit('update:modelValue', this.inputFiles)
264
335
  this.$emit('input', this.inputFiles)
265
- this.$emit('change', this.inputFiles)
266
336
  },
267
337
 
268
338
  // For file input.
269
- filePreview (original, file) {
339
+ readFile (original, file) {
270
340
  const reader = new FileReader()
271
341
 
342
+ // If the preview prop is a string, the user is setting the preview to an icon and
343
+ // don't need the actual file preview.
344
+ const isPreviewAnIcon = typeof this.preview === 'string'
345
+ const isFileAnImage = original.type && original.type.startsWith('image/')
272
346
  // Check if the file is an image and set a preview image.
273
- if (original.type && original.type.startsWith('image/')) {
347
+ if (this.preview && !isPreviewAnIcon && isFileAnImage) {
274
348
  reader.addEventListener('load', e => {
275
349
  this.$set(file, 'preview', e.target.result)
276
350
  })
277
351
  }
352
+ else delete file.preview
278
353
 
279
354
  // Used to display a spinner while the file is loading.
280
355
  reader.addEventListener('progress', event => {
@@ -319,6 +394,8 @@ $inactive-color: #777;
319
394
  &--file {
320
395
  flex-wrap: nowrap;
321
396
  align-items: flex-end;
397
+
398
+ span.fade-leave-to {position: absolute;}
322
399
  }
323
400
 
324
401
  // Input field wrapper.
@@ -348,7 +425,10 @@ $inactive-color: #777;
348
425
  &--round {border-radius: 99em;}
349
426
  &--tile {border-radius: initial;}
350
427
  &--shadow {box-shadow: $box-shadow;}
351
- &--loading {border-bottom-color: transparent;}
428
+ &--loading, &--upload-complete {
429
+ border-bottom-color: transparent;
430
+ flex-wrap: wrap;
431
+ }
352
432
  &--loading ~ .w-progress {
353
433
  height: 2px;
354
434
  position: absolute;
@@ -357,7 +437,8 @@ $inactive-color: #777;
357
437
  }
358
438
 
359
439
  .w-input--focused & {border-color: currentColor;}
360
- .w-input--focused &--loading {border-bottom-color: transparent;}
440
+ .w-input--focused &--loading,
441
+ .w-input--focused &--upload-complete {border-bottom-color: transparent;}
361
442
 
362
443
  // Underline.
363
444
  &--underline:after {
@@ -468,6 +549,8 @@ $inactive-color: #777;
468
549
  margin-left: 4px;
469
550
  max-height: 2em;
470
551
  align-self: flex-end;
552
+
553
+ &.w-icon {margin-bottom: 4px;}
471
554
  }
472
555
 
473
556
  // Icons inside.
@@ -74,26 +74,16 @@ export default {
74
74
  menuClass: { type: [String, Object, Array] },
75
75
  titleClass: { type: [String, Object, Array] },
76
76
  contentClass: { type: [String, Object, Array] },
77
- // Position.
78
77
  arrow: { type: Boolean }, // The small triangle pointing toward the activator.
79
- detachTo: { type: [String, Boolean, Object], deprecated: true },
80
- appendTo: { type: [String, Boolean, Object] },
81
- fixed: { type: Boolean },
82
- top: { type: Boolean },
83
- bottom: { type: Boolean },
84
- left: { type: Boolean },
85
- right: { type: Boolean },
86
- alignTop: { type: Boolean },
87
- alignBottom: { type: Boolean },
88
- alignLeft: { type: Boolean },
89
- alignRight: { type: Boolean },
90
- zIndex: { type: [Number, String, Boolean] },
91
78
  minWidth: { type: [Number, String] }, // can be like: `40`, `5em`, `activator`.
92
79
  overlay: { type: Boolean },
93
80
  overlayClass: { type: [String, Object, Array] },
94
81
  overlayProps: { type: Object }, // Allow passing down an object of props to the w-overlay component.
95
82
  persistent: { type: Boolean },
96
- noPosition: { type: Boolean }
83
+ delay: { type: Number }
84
+ // Other props in the detachable mixin:
85
+ // detachTo, appendTo, fixed, top, bottom, left, right, alignTop, alignBottom, alignLeft,
86
+ // alignRight, noPosition, zIndex, activator.
97
87
  },
98
88
 
99
89
  emits: ['input', 'update:modelValue', 'open', 'close'],
@@ -107,7 +97,6 @@ export default {
107
97
  top: 0,
108
98
  left: 0
109
99
  },
110
- activatorEl: null,
111
100
  activatorWidth: 0,
112
101
  detachableEl: null,
113
102
  timeoutId: null
@@ -118,32 +107,15 @@ export default {
118
107
  * Other computed in the detachable mixin:
119
108
  * - `appendToTarget`
120
109
  * - `detachableParentEl`
110
+ * - `activatorEl`
111
+ * - `position`
112
+ * - `alignment`
121
113
  **/
122
114
 
123
115
  transitionName () {
124
116
  return this.transition || 'scale-fade'
125
117
  },
126
118
 
127
- position () {
128
- return (
129
- (this.top && 'top') ||
130
- (this.bottom && 'bottom') ||
131
- (this.left && 'left') ||
132
- (this.right && 'right') ||
133
- 'bottom'
134
- )
135
- },
136
-
137
- alignment () {
138
- return (
139
- (['top', 'bottom'].includes(this.position) && this.alignLeft && 'left') ||
140
- (['top', 'bottom'].includes(this.position) && this.alignRight && 'right') ||
141
- (['left', 'right'].includes(this.position) && this.alignTop && 'top') ||
142
- (['left', 'right'].includes(this.position) && this.alignBottom && 'bottom') ||
143
- ''
144
- )
145
- },
146
-
147
119
  menuMinWidth () {
148
120
  if (this.minWidth === 'activator') return this.activatorWidth ? `${this.activatorWidth}px` : 0
149
121
  else return isNaN(this.minWidth) ? this.minWidth : (this.minWidth ? `${this.minWidth}px` : 0)
@@ -197,8 +169,8 @@ export default {
197
169
 
198
170
  if (this.showOnHover) {
199
171
  handlers = {
200
- focus: this.toggleMenu,
201
- blur: this.toggleMenu,
172
+ focus: this.toggle,
173
+ blur: this.toggle,
202
174
  mouseenter: e => {
203
175
  this.hoveringActivator = true
204
176
  this.open(e)
@@ -213,10 +185,10 @@ export default {
213
185
  }
214
186
  // Check the window exists: SSR-proof.
215
187
  if (typeof window !== 'undefined' && 'ontouchstart' in window) {
216
- handlers.click = this.toggleMenu
188
+ handlers.click = this.toggle
217
189
  }
218
190
  }
219
- else handlers = { click: this.toggleMenu }
191
+ else handlers = { click: this.toggle }
220
192
  return handlers
221
193
  }
222
194
  },
@@ -233,7 +205,7 @@ export default {
233
205
  **/
234
206
 
235
207
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
236
- toggleMenu (e) {
208
+ toggle (e) {
237
209
  let shouldShowMenu = this.detachableVisible
238
210
  if ('ontouchstart' in window && this.showOnHover && e.type === 'click') {
239
211
  shouldShowMenu = !shouldShowMenu
@@ -250,19 +222,22 @@ export default {
250
222
 
251
223
  this.timeoutId = clearTimeout(this.timeoutId)
252
224
 
253
- if (shouldShowMenu) {
254
- this.$emit('update:modelValue', (this.detachableVisible = true))
255
- this.$emit('input', true)
256
- this.$emit('open')
257
-
258
- this.open(e)
259
- }
225
+ if (shouldShowMenu) this.open(e)
260
226
  else this.close()
261
227
  },
262
228
 
263
229
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
264
230
  async open (e) {
231
+ // A tiny delay may help positioning the detachable correctly in case of multiple activators
232
+ // with different menu contents.
233
+ if (this.delay) await new Promise(resolve => setTimeout(resolve, this.delay))
234
+
265
235
  this.detachableVisible = true
236
+
237
+ // If the activator is external, there might be multiple,
238
+ // so on open, the activator will be set to the event target.
239
+ if (this.activator) this.activatorEl = e.target
240
+
266
241
  await this.insertInDOM()
267
242
 
268
243
  if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
@@ -310,43 +285,9 @@ export default {
310
285
  document.removeEventListener('mousedown', this.onOutsideMousedown)
311
286
  window.removeEventListener('resize', this.onResize)
312
287
  }
313
- },
314
-
315
- mounted () {
316
- const wrapper = this.$el
317
- this.activatorEl = wrapper.firstElementChild
318
-
319
- // Unwrap the activator element.
320
- wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
321
-
322
- // Unwrap the overlay.
323
- if (this.overlay) {
324
- this.overlayEl = this.$refs.overlay?.$el
325
- wrapper.parentNode.insertBefore(this.overlayEl, wrapper)
326
- }
327
-
328
- if (this.value) this.toggleMenu({ type: 'click', target: this.activatorEl })
329
- },
330
-
331
- beforeDestroy () {
332
- this.removeFromDOM()
333
- if (this.overlay && this.overlayEl.parentNode) this.overlayEl.remove()
334
- if (this.activatorEl && this.activatorEl.parentNode) this.activatorEl.remove()
335
- },
336
-
337
- watch: {
338
- value (bool) {
339
- if (!!bool !== this.detachableVisible) this.toggleMenu({ type: 'click', target: this.activatorEl })
340
- },
341
- detachTo () {
342
- this.removeFromDOM()
343
- this.insertInDOM()
344
- },
345
- appendTo () {
346
- this.removeFromDOM()
347
- this.insertInDOM()
348
- }
349
288
  }
289
+
290
+ // watch, mounted & beforeDestroy hooks are set in the detachable.js mixin.
350
291
  }
351
292
  </script>
352
293
 
@@ -11,8 +11,8 @@ transition-group(
11
11
  :key="notif._uid"
12
12
  v-model="notif._value"
13
13
  @close="notifManager.dismiss(notif._uid)"
14
- v-bind="notif")
15
- | {{ notif.message }}
14
+ v-bind="notifProps(notif)")
15
+ div(v-html="notif.message")
16
16
  </template>
17
17
 
18
18
  <script>
@@ -42,6 +42,13 @@ export default {
42
42
  }
43
43
  },
44
44
 
45
+ methods: {
46
+ notifProps (notif) {
47
+ const { _value, _uid, message, timeout, ...props } = notif
48
+ return props
49
+ }
50
+ },
51
+
45
52
  created () {
46
53
  this.notifManager = new NotificationManager()
47
54
  },
@@ -330,7 +330,7 @@ export default {
330
330
  setTimeout(() => {
331
331
  const itemIndex = this.inputValue.length ? this.inputValue[0].index : 0 // Real index starts at 0.
332
332
  // User visible index starts at 1.
333
- this.$refs['w-list'].$el.querySelector(`#w-select-menu--${this._uid}_item-${itemIndex + 1}`).focus()
333
+ this.$refs['w-list'].$el.querySelector(`#w-select-menu--${this._uid}_item-${itemIndex + 1}`)?.focus()
334
334
  }, 100)
335
335
  },
336
336