wave-ui 1.48.0 → 1.49.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "1.48.0",
3
+ "version": "1.49.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",
@@ -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
  },
@@ -1,6 +1,6 @@
1
1
  <template lang="pug">
2
2
  .w-tooltip-wrap
3
- slot(name="activator" :on="eventHandlers")
3
+ slot(name="activator" :on="activatorEventHandlers")
4
4
  transition(:name="transitionName" appear)
5
5
  .w-tooltip(v-if="detachableVisible" ref="detachable" :class="classes" :style="styles")
6
6
  slot
@@ -36,21 +36,11 @@ export default {
36
36
  round: { type: Boolean },
37
37
  transition: { type: String },
38
38
  tooltipClass: { type: [String, Object, Array] },
39
- // Position.
40
- detachTo: { type: [String, Boolean, Object], deprecated: true },
41
- appendTo: { type: [String, Boolean, Object] },
42
- fixed: { type: Boolean },
43
- top: { type: Boolean },
44
- bottom: { type: Boolean },
45
- left: { type: Boolean },
46
- right: { type: Boolean },
47
- alignTop: { type: Boolean },
48
- alignBottom: { type: Boolean },
49
- alignLeft: { type: Boolean },
50
- alignRight: { type: Boolean },
51
- zIndex: { type: [Number, String, Boolean] },
52
39
  persistent: { type: Boolean },
53
- noPosition: { type: Boolean }
40
+ delay: { type: Number }
41
+ // Other props in the detachable mixin:
42
+ // detachTo, appendTo, fixed, top, bottom, left, right, alignTop, alignBottom, alignLeft,
43
+ // alignRight, noPosition, zIndex, activator.
54
44
  },
55
45
 
56
46
  emits: ['input', 'update:modelValue', 'open', 'close'],
@@ -58,12 +48,11 @@ export default {
58
48
  data: () => ({
59
49
  detachableVisible: false,
60
50
  hoveringActivator: false,
61
- // The activator coordinates.
51
+ // The tooltip computed top & left coordinates.
62
52
  detachableCoords: {
63
53
  top: 0,
64
54
  left: 0
65
55
  },
66
- activatorEl: null,
67
56
  detachableEl: null,
68
57
  timeoutId: null
69
58
  }),
@@ -73,6 +62,9 @@ export default {
73
62
  * Other computed in the detachable mixin:
74
63
  * - `appendToTarget`
75
64
  * - `detachableParentEl`
65
+ * - `activatorEl`
66
+ * - `position`
67
+ * - `alignment`
76
68
  **/
77
69
 
78
70
  tooltipClasses () {
@@ -84,26 +76,6 @@ export default {
84
76
  return this.transition || `w-tooltip-slide-fade-${direction}`
85
77
  },
86
78
 
87
- position () {
88
- return (
89
- (this.top && 'top') ||
90
- (this.bottom && 'bottom') ||
91
- (this.left && 'left') ||
92
- (this.right && 'right') ||
93
- 'bottom'
94
- )
95
- },
96
-
97
- alignment () {
98
- return (
99
- (['top', 'bottom'].includes(this.position) && this.alignLeft && 'left') ||
100
- (['top', 'bottom'].includes(this.position) && this.alignRight && 'right') ||
101
- (['left', 'right'].includes(this.position) && this.alignTop && 'top') ||
102
- (['left', 'right'].includes(this.position) && this.alignBottom && 'bottom') ||
103
- ''
104
- )
105
- },
106
-
107
79
  classes () {
108
80
  return {
109
81
  [this.color]: this.color,
@@ -130,7 +102,7 @@ export default {
130
102
  }
131
103
  },
132
104
 
133
- eventHandlers () {
105
+ activatorEventHandlers () {
134
106
  let handlers = {}
135
107
  if (this.showOnClick) handlers = { click: this.toggle }
136
108
  else {
@@ -176,19 +148,22 @@ export default {
176
148
  else if (['mouseleave', 'blur'].includes(e.type) && !this.showOnClick) shouldShowTooltip = false
177
149
 
178
150
  this.timeoutId = clearTimeout(this.timeoutId)
179
- if (shouldShowTooltip) {
180
- this.$emit('update:modelValue', (this.detachableVisible = true))
181
- this.$emit('input', true)
182
- this.$emit('open')
183
-
184
- this.open(e)
185
- }
151
+ if (shouldShowTooltip) this.open(e)
186
152
  else this.close()
187
153
  },
188
154
 
189
155
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
190
156
  async open (e) {
157
+ // A tiny delay may help positioning the detachable correctly in case of multiple activators
158
+ // with different menu contents.
159
+ if (this.delay) await new Promise(resolve => setTimeout(resolve, this.delay))
160
+
191
161
  this.detachableVisible = true
162
+
163
+ // If the activator is external, there might be multiple,
164
+ // so on open, the activator will be set to the event target.
165
+ if (this.activator) this.activatorEl = e.target
166
+
192
167
  await this.insertInDOM()
193
168
 
194
169
  if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
@@ -236,37 +211,9 @@ export default {
236
211
  document.removeEventListener('mousedown', this.onOutsideMousedown)
237
212
  window.removeEventListener('resize', this.onResize)
238
213
  }
239
- },
240
-
241
- mounted () {
242
- const wrapper = this.$el
243
- this.activatorEl = wrapper.firstElementChild
244
-
245
- // Unwrap the activator element.
246
- wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
247
-
248
- if (this.value) this.toggle({ type: 'click', target: this.activatorEl })
249
- },
250
-
251
- beforeDestroy () {
252
- this.removeFromDOM()
253
-
254
- if (this.activatorEl && this.activatorEl.parentNode) this.activatorEl.remove()
255
- },
256
-
257
- watch: {
258
- value (bool) {
259
- if (bool !== this.detachableVisible) this.toggle({ type: 'click', target: this.activatorEl })
260
- },
261
- detachTo () {
262
- this.removeFromDOM()
263
- this.insertInDOM()
264
- },
265
- appendTo () {
266
- this.removeFromDOM()
267
- this.insertInDOM()
268
- }
269
214
  }
215
+
216
+ // watch, mounted & beforeDestroy hooks are set in the detachable.js mixin.
270
217
  }
271
218
  </script>
272
219
 
@@ -7,6 +7,33 @@
7
7
  import { consoleWarn } from '../utils/console'
8
8
 
9
9
  export default {
10
+ props: {
11
+ // Position.
12
+ detachTo: { type: [String, Boolean, Object], deprecated: true },
13
+ appendTo: { type: [String, Boolean, Object] },
14
+ fixed: { type: Boolean },
15
+ top: { type: Boolean },
16
+ bottom: { type: Boolean },
17
+ left: { type: Boolean },
18
+ right: { type: Boolean },
19
+ alignTop: { type: Boolean },
20
+ alignBottom: { type: Boolean },
21
+ alignLeft: { type: Boolean },
22
+ alignRight: { type: Boolean },
23
+ noPosition: { type: Boolean },
24
+ zIndex: { type: [Number, String, Boolean] },
25
+ activator: { type: String } // Optionally designate an external activator.
26
+ },
27
+
28
+ data: () => ({
29
+ // The event listeners handlers have to be removed the exact same way they have been attached.
30
+ // Since the handler functions have variables that change after hot-reload, keep them exactly
31
+ // as is in an array so we can delete them on destroy.
32
+ // This only applies to the activatorEventHandlers, the other events listeners can be removed
33
+ // normally.
34
+ docAEventListenersHandlers: []
35
+ }),
36
+
10
37
  computed: {
11
38
  // DOM element to attach tooltip/menu to.
12
39
  // ! \ This computed uses the DOM - NO SSR (only trigger from beforeMount and later).
@@ -40,6 +67,40 @@ export default {
40
67
  // ! \ This computed uses the DOM - NO SSR (only trigger from beforeMount and later).
41
68
  detachableParentEl () {
42
69
  return this.appendToTarget
70
+ },
71
+
72
+ hasSeparateActivator () {
73
+ return !this.$scopedSlots.activator && typeof this.activator === 'string'
74
+ },
75
+
76
+ activatorEl: {
77
+ get () {
78
+ if (this.hasSeparateActivator) return document.querySelector(this.activator)
79
+ return this.$el.firstElementChild
80
+ },
81
+ set () {
82
+
83
+ }
84
+ },
85
+
86
+ position () {
87
+ return (
88
+ (this.top && 'top') ||
89
+ (this.bottom && 'bottom') ||
90
+ (this.left && 'left') ||
91
+ (this.right && 'right') ||
92
+ 'bottom'
93
+ )
94
+ },
95
+
96
+ alignment () {
97
+ return (
98
+ (['top', 'bottom'].includes(this.position) && this.alignLeft && 'left') ||
99
+ (['top', 'bottom'].includes(this.position) && this.alignRight && 'right') ||
100
+ (['left', 'right'].includes(this.position) && this.alignTop && 'top') ||
101
+ (['left', 'right'].includes(this.position) && this.alignBottom && 'bottom') ||
102
+ ''
103
+ )
43
104
  }
44
105
  },
45
106
 
@@ -176,14 +237,85 @@ export default {
176
237
 
177
238
  // Move the tooltip/menu elsewhere in the DOM.
178
239
  // wrapper.parentNode.insertBefore(this.detachableEl, wrapper)
179
- this.appendToTarget.appendChild(this.detachableEl)
240
+ if (this.detachableEl) this.appendToTarget.appendChild(this.detachableEl)
180
241
  resolve()
181
242
  })
182
243
  })
183
244
  },
184
245
 
185
246
  removeFromDOM () {
186
- if (this.detachableEl && this.detachableEl.parentNode) this.detachableEl.remove()
247
+ document.removeEventListener('mousedown', this.onOutsideMousedown)
248
+ window.removeEventListener('resize', this.onResize)
249
+ if (this.detachableEl && this.detachableEl.parentNode) {
250
+ this.detachableVisible = false
251
+ this.detachableEl.remove()
252
+ this.detachableEl = null
253
+ }
254
+ }
255
+ },
256
+
257
+ mounted () {
258
+ const wrapper = this.$el
259
+
260
+ // Unwrap the activator element if the activator is in the activator slot.
261
+ if (this.$scopedSlots.activator) wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
262
+
263
+ // If the activator is external, add event listeners to the document and check the target is
264
+ // the activator when toggling.
265
+ // This way, the activator can be a future DOM element, that is not yet in the DOM.
266
+ else if (this.activator) {
267
+ Object.entries(this.activatorEventHandlers).forEach(([eventName, handler]) => {
268
+ // Convert mouseenter to mouseover & mouseleave to mouseout because we are attaching
269
+ // event to the document, so it can accept future nodes.
270
+ eventName = eventName.replace('mouseenter', 'mouseover').replace('mouseleave', 'mouseout')
271
+ const handlerWrap = e => {
272
+ if (e.target?.matches && e.target.matches(this.activator)) handler(e)
273
+ }
274
+ document.addEventListener(eventName, handlerWrap)
275
+ // The event listeners handlers have to be removed the exact same way they have been attached.
276
+ // Since the handler functions have variables that change after hot-reload, keep them exactly
277
+ // as is in an array so we can delete them on destroy.
278
+ this.docAEventListenersHandlers.push({ eventName, handler: handlerWrap })
279
+ })
280
+ }
281
+
282
+ // Unwrap the overlay if any.
283
+ if (this.overlay) {
284
+ this.overlayEl = this.$refs.overlay?.$el
285
+ wrapper.parentNode.insertBefore(this.overlayEl, wrapper)
286
+ }
287
+
288
+ if (this.value) this.toggleMenu({ type: 'click', target: this.activatorEl })
289
+ },
290
+
291
+ beforeDestroy () {
292
+ this.close()
293
+
294
+ this.removeFromDOM()
295
+
296
+ // Remove the event listeners the exact same way they have been defined.
297
+ // Fixes issues on hot-reloading.
298
+ if (this.docAEventListenersHandlers.length) {
299
+ this.docAEventListenersHandlers.forEach(({ eventName, handler }) => {
300
+ document.removeEventListener(eventName, handler)
301
+ })
302
+ }
303
+
304
+ if (this.overlay && this.overlayEl.parentNode) this.overlayEl.remove()
305
+ if (this.activatorEl?.parentNode && this.$scopedSlots.activator) this.activatorEl.remove()
306
+ },
307
+
308
+ watch: {
309
+ value (bool) {
310
+ if (!!bool !== this.detachableVisible) this.toggle({ type: 'click', target: this.activatorEl })
311
+ },
312
+ detachTo () {
313
+ this.removeFromDOM()
314
+ this.insertInDOM()
315
+ },
316
+ appendTo () {
317
+ this.removeFromDOM()
318
+ this.insertInDOM()
187
319
  }
188
320
  }
189
321
  }