wave-ui 1.45.14 → 1.48.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.
@@ -1,8 +1,8 @@
1
1
  <template lang="pug">
2
- .w-tooltip-wrap(:class="{ 'w-tooltip-wrap--attached': !detachTo }")
2
+ .w-tooltip-wrap
3
3
  slot(name="activator" :on="eventHandlers")
4
4
  transition(:name="transitionName" appear)
5
- .w-tooltip(ref="tooltip" v-show="showTooltip" :class="classes" :style="styles")
5
+ .w-tooltip(v-if="detachableVisible" ref="detachable" :class="classes" :style="styles")
6
6
  slot
7
7
  </template>
8
8
 
@@ -17,12 +17,13 @@
17
17
  */
18
18
 
19
19
  import { objectifyClasses } from '../utils/index'
20
- import { consoleWarn } from '../utils/console'
20
+ import DetachableMixin from '../mixins/detachable'
21
21
 
22
- const marginFromWindowSide = 4 // Amount of px from a window side, instead of overflowing.
22
+ // const marginFromWindowSide = 4 // Amount of px from a window side, instead of overflowing.
23
23
 
24
24
  export default {
25
25
  name: 'w-tooltip',
26
+ mixins: [DetachableMixin],
26
27
 
27
28
  props: {
28
29
  value: {},
@@ -36,32 +37,44 @@ export default {
36
37
  transition: { type: String },
37
38
  tooltipClass: { type: [String, Object, Array] },
38
39
  // Position.
39
- detachTo: {},
40
+ detachTo: { type: [String, Boolean, Object], deprecated: true },
41
+ appendTo: { type: [String, Boolean, Object] },
40
42
  fixed: { type: Boolean },
41
43
  top: { type: Boolean },
42
44
  bottom: { type: Boolean },
43
45
  left: { type: Boolean },
44
46
  right: { type: Boolean },
45
- zIndex: { type: [Number, String, Boolean] }
47
+ alignTop: { type: Boolean },
48
+ alignBottom: { type: Boolean },
49
+ alignLeft: { type: Boolean },
50
+ alignRight: { type: Boolean },
51
+ zIndex: { type: [Number, String, Boolean] },
52
+ persistent: { type: Boolean },
53
+ noPosition: { type: Boolean }
46
54
  },
47
55
 
48
56
  emits: ['input', 'update:modelValue', 'open', 'close'],
49
57
 
50
58
  data: () => ({
51
- showTooltip: false,
59
+ detachableVisible: false,
60
+ hoveringActivator: false,
52
61
  // The activator coordinates.
53
- coordinates: {
62
+ detachableCoords: {
54
63
  top: 0,
55
- left: 0,
56
- width: 0,
57
- height: 0
64
+ left: 0
58
65
  },
59
66
  activatorEl: null,
60
- tooltipEl: null,
67
+ detachableEl: null,
61
68
  timeoutId: null
62
69
  }),
63
70
 
64
71
  computed: {
72
+ /**
73
+ * Other computed in the detachable mixin:
74
+ * - `appendToTarget`
75
+ * - `detachableParentEl`
76
+ **/
77
+
65
78
  tooltipClasses () {
66
79
  return objectifyClasses(this.tooltipClass)
67
80
  },
@@ -71,34 +84,6 @@ export default {
71
84
  return this.transition || `w-tooltip-slide-fade-${direction}`
72
85
  },
73
86
 
74
- // DOM element to attach tooltip to.
75
- // ! \ This computed uses the DOM - NO SSR (only trigger from beforeMount and later).
76
- detachToTarget () {
77
- const defaultTarget = '.w-app'
78
-
79
- let target = this.detachTo || defaultTarget
80
- if (target === true) target = defaultTarget
81
- else if (target && !['object', 'string'].includes(typeof target)) target = defaultTarget
82
- else if (typeof target === 'object' && !target.nodeType) {
83
- target = defaultTarget
84
- consoleWarn('Invalid node provided in w-tooltip `attach-to`. Falling back to .w-app.', this)
85
- }
86
- if (typeof target === 'string') target = document.querySelector(target)
87
-
88
- if (!target) {
89
- consoleWarn(`Unable to locate ${this.detachTo ? `target ${this.detachTo}` : defaultTarget}`, this)
90
- target = document.querySelector(defaultTarget)
91
- }
92
-
93
- return target
94
- },
95
-
96
- // DOM element that will receive the tooltip.
97
- // ! \ This computed uses the DOM - NO SSR (only trigger from beforeMount and later).
98
- tooltipParentEl () {
99
- return this.detachTo ? this.detachToTarget : this.$el
100
- },
101
-
102
87
  position () {
103
88
  return (
104
89
  (this.top && 'top') ||
@@ -109,34 +94,14 @@ export default {
109
94
  )
110
95
  },
111
96
 
112
- tooltipCoordinates () {
113
- const coords = {}
114
- const { top, left, width, height } = this.coordinates
115
-
116
- switch (this.position) {
117
- case 'top': {
118
- coords.top = top
119
- coords.left = left + width / 2 // left: 50%.
120
- break
121
- }
122
- case 'bottom': {
123
- coords.top = top + height
124
- coords.left = left + width / 2 // left: 50%.
125
- break
126
- }
127
- case 'left': {
128
- coords.top = top + height / 2 // top: 50%.
129
- coords.left = left
130
- break
131
- }
132
- case 'right': {
133
- coords.top = top + height / 2 // top: 50%.
134
- coords.left = left + width
135
- break
136
- }
137
- }
138
-
139
- return coords
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
+ )
140
105
  },
141
106
 
142
107
  classes () {
@@ -144,12 +109,12 @@ export default {
144
109
  [this.color]: this.color,
145
110
  [`${this.bgColor}--bg`]: this.bgColor,
146
111
  ...this.tooltipClasses,
147
- [`w-tooltip--${this.position}`]: true,
112
+ [`w-tooltip--${this.position}`]: !this.noPosition,
113
+ [`w-tooltip--align-${this.alignment}`]: !this.noPosition && this.alignment,
148
114
  'w-tooltip--tile': this.tile,
149
115
  'w-tooltip--round': this.round,
150
116
  'w-tooltip--shadow': this.shadow,
151
117
  'w-tooltip--fixed': this.fixed,
152
- 'w-tooltip--active': this.showTooltip,
153
118
  'w-tooltip--no-border': this.noBorder || this.bgColor,
154
119
  'w-tooltip--custom-transition': this.transition
155
120
  }
@@ -159,8 +124,8 @@ export default {
159
124
  styles () {
160
125
  return {
161
126
  zIndex: this.zIndex || this.zIndex === 0 || null,
162
- top: (this.tooltipCoordinates.top && `${~~this.tooltipCoordinates.top}px`) || null,
163
- left: (this.tooltipCoordinates.left && `${~~this.tooltipCoordinates.left}px`) || null,
127
+ top: (this.detachableCoords.top && `${~~this.detachableCoords.top}px`) || null,
128
+ left: (this.detachableCoords.left && `${~~this.detachableCoords.left}px`) || null,
164
129
  '--w-tooltip-bg-color': this.$waveui.colors[this.bgColor || 'white']
165
130
  }
166
131
  },
@@ -172,8 +137,14 @@ export default {
172
137
  handlers = {
173
138
  focus: this.toggle,
174
139
  blur: this.toggle,
175
- mouseenter: this.toggle,
176
- mouseleave: this.toggle
140
+ mouseenter: e => {
141
+ this.hoveringActivator = true
142
+ this.open(e)
143
+ },
144
+ mouseleave: e => {
145
+ this.hoveringActivator = false
146
+ this.close()
147
+ }
177
148
  }
178
149
 
179
150
  // Check the window exists: SSR-proof.
@@ -184,9 +155,19 @@ export default {
184
155
  },
185
156
 
186
157
  methods: {
158
+ /**
159
+ * Other methods in the `detachable` mixin:
160
+ * - `getActivatorCoordinates`
161
+ * - `computeDetachableCoords`
162
+ * - `onResize`
163
+ * - `onOutsideMousedown`
164
+ * - `insertInDOM`
165
+ * - `removeFromDOM`
166
+ **/
167
+
187
168
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
188
169
  toggle (e) {
189
- let shouldShowTooltip = this.showTooltip
170
+ let shouldShowTooltip = this.detachableVisible
190
171
  if (typeof window !== 'undefined' && 'ontouchstart' in window) {
191
172
  if (e.type === 'click') shouldShowTooltip = !shouldShowTooltip
192
173
  }
@@ -196,131 +177,101 @@ export default {
196
177
 
197
178
  this.timeoutId = clearTimeout(this.timeoutId)
198
179
  if (shouldShowTooltip) {
199
- this.coordinates = this.getCoordinates(e)
200
- // In `getCoordinates` accessing the tooltip computed styles takes a few ms (less than 10ms),
201
- // if we don't postpone the tooltip apparition it will start transition from a visible tooltip and
202
- // thus will not transition.
203
- this.timeoutId = setTimeout(() => {
204
- this.showTooltip = true
205
- this.$emit('update:modelValue', true)
206
- this.$emit('input', true)
207
- this.$emit('open')
208
- }, 10)
209
- }
210
- else {
211
- this.showTooltip = false
212
- this.$emit('update:modelValue', false)
213
- this.$emit('input', false)
214
- this.$emit('close')
180
+ this.$emit('update:modelValue', (this.detachableVisible = true))
181
+ this.$emit('input', true)
182
+ this.$emit('open')
183
+
184
+ this.open(e)
215
185
  }
186
+ else this.close()
216
187
  },
217
188
 
218
189
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
219
- getCoordinates () {
220
- const { top, left, width, height } = this.activatorEl.getBoundingClientRect()
221
- let coords = { top, left, width, height }
222
-
223
- if (!this.fixed) {
224
- const { top: targetTop, left: targetLeft } = this.tooltipParentEl.getBoundingClientRect()
225
- coords = { ...coords, top: top - targetTop, left: left - targetLeft }
226
- }
190
+ async open (e) {
191
+ this.detachableVisible = true
192
+ await this.insertInDOM()
227
193
 
228
- const tooltipEl = this.$refs.tooltip
194
+ if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
229
195
 
230
- // 1. First display the tooltip but hide it (So we can get its dimension).
231
- tooltipEl.style.visibility = 'hidden'
232
- tooltipEl.style.display = 'table'
233
- const computedStyles = window.getComputedStyle(tooltipEl, null)
196
+ if (!this.noPosition) this.computeDetachableCoords(e)
234
197
 
235
- // Keep fully in viewport.
236
- // --------------------------------------------------
237
- if (this.position === 'top' && ((top - tooltipEl.offsetHeight) < 0)) {
238
- const margin = -parseInt(computedStyles.getPropertyValue('margin-top'))
239
- coords.top -= top - tooltipEl.offsetHeight - margin - marginFromWindowSide
240
- }
241
- else if (this.position === 'left' && left - tooltipEl.offsetWidth < 0) {
242
- const margin = -parseInt(computedStyles.getPropertyValue('margin-left'))
243
- coords.left -= left - tooltipEl.offsetWidth - margin - marginFromWindowSide
244
- }
245
- else if (this.position === 'right' && left + width + tooltipEl.offsetWidth > window.innerWidth) {
246
- const margin = parseInt(computedStyles.getPropertyValue('margin-left'))
247
- coords.left -= left + width + tooltipEl.offsetWidth - window.innerWidth + margin + marginFromWindowSide
248
- }
249
- else if (this.position === 'bottom' && top + height + tooltipEl.offsetHeight > window.innerHeight) {
250
- const margin = parseInt(computedStyles.getPropertyValue('margin-top'))
251
- coords.top -= top + height + tooltipEl.offsetHeight - window.innerHeight + margin + marginFromWindowSide
252
- }
253
- // --------------------------------------------------
254
-
255
- // 2. Update left & top if there is a custom transition.
256
- // Tooltip position relies on transform translate, the custom animation may override the transform
257
- // property so do without it and subtract half width or height manually.
258
- if (this.transition) {
259
- // If tooltip is on top or bottom.
260
- if (['top', 'bottom'].includes(this.position)) coords.left -= tooltipEl.offsetWidth / 2
261
- // If tooltip is on left or right.
262
- if (['left', 'right'].includes(this.position)) coords.top -= tooltipEl.offsetHeight / 2
263
-
264
- if (this.position === 'left') coords.left -= tooltipEl.offsetWidth
265
- if (this.position === 'top') coords.top -= tooltipEl.offsetHeight
266
- }
267
-
268
- // 3. Hide the tooltip again so the transition happens correctly.
269
- tooltipEl.style.visibility = null
270
- tooltipEl.style.display = 'none'
198
+ // In `getActivatorCoordinates` accessing the tooltip computed styles takes a few ms (less than 10ms),
199
+ // if we don't postpone the Tooltip apparition it will start transition from a visible tooltip and
200
+ // thus will not transition.
201
+ this.timeoutId = setTimeout(() => {
202
+ this.$emit('update:modelValue', true)
203
+ this.$emit('input', true)
204
+ this.$emit('open')
205
+ }, 0)
271
206
 
272
- return coords
207
+ if (!this.persistent) document.addEventListener('mousedown', this.onOutsideMousedown)
208
+ if (!this.noPosition) window.addEventListener('resize', this.onResize)
273
209
  },
274
210
 
275
- insertTooltip () {
276
- const wrapper = this.$el
277
- this.tooltipEl = this.$refs.tooltip.$el || this.$refs.tooltip
278
-
279
- // Unwrap the activator element.
280
- wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
281
-
282
- // Move the tooltip elsewhere in the DOM.
283
- // wrapper.parentNode.insertBefore(this.$refs.tooltip, wrapper)
284
- this.detachToTarget.appendChild(this.$refs.tooltip)
285
- },
211
+ /**
212
+ * Closes the tooltip. Can happen on:
213
+ * - click of activator
214
+ * - hover outside if showOnHover
215
+ * - click inside tooltip if hideOnTooltipClick.
216
+ * / ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
217
+ *
218
+ * @param {Boolean} force when showOnHover is set to true, hovering tooltip should keep it open.
219
+ * But if hideOnTooltipClick is also set to true, this should force close
220
+ * even while hovering the tooltip.
221
+ */
222
+ async close (force = false) {
223
+ // Might be already closed.
224
+ // E.g. showOnHover & hideOnTooltipClick: on click, force hide then mouseleave is also firing.
225
+ if (!this.detachableVisible) return
226
+
227
+ if (this.showOnHover && !force) {
228
+ await new Promise(resolve => setTimeout(resolve, 10))
229
+ if (this.showOnHover && this.hoveringActivator) return
230
+ }
286
231
 
287
- removeTooltip () {
288
- if (this.tooltipEl && this.tooltipEl.parentNode) this.tooltipEl.remove()
232
+ this.$emit('update:modelValue', (this.detachableVisible = false))
233
+ this.$emit('input', false)
234
+ this.$emit('close')
235
+ // Remove the mousedown listener if the tooltip got closed without a mousedown outside of the tooltip.
236
+ document.removeEventListener('mousedown', this.onOutsideMousedown)
237
+ window.removeEventListener('resize', this.onResize)
289
238
  }
290
-
291
239
  },
292
240
 
293
241
  mounted () {
294
- this.activatorEl = this.$el.firstElementChild
295
- if (this.detachTo) this.insertTooltip()
242
+ const wrapper = this.$el
243
+ this.activatorEl = wrapper.firstElementChild
244
+
245
+ // Unwrap the activator element.
246
+ wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
296
247
 
297
248
  if (this.value) this.toggle({ type: 'click', target: this.activatorEl })
298
249
  },
299
250
 
300
251
  beforeDestroy () {
301
- this.removeTooltip()
252
+ this.removeFromDOM()
302
253
 
303
254
  if (this.activatorEl && this.activatorEl.parentNode) this.activatorEl.remove()
304
255
  },
305
256
 
306
257
  watch: {
307
258
  value (bool) {
308
- if (bool !== this.showTooltip) this.toggle({ type: 'click', target: this.activatorEl })
259
+ if (bool !== this.detachableVisible) this.toggle({ type: 'click', target: this.activatorEl })
309
260
  },
310
261
  detachTo () {
311
- this.removeTooltip()
312
- this.insertTooltip()
262
+ this.removeFromDOM()
263
+ this.insertInDOM()
264
+ },
265
+ appendTo () {
266
+ this.removeFromDOM()
267
+ this.insertInDOM()
313
268
  }
314
269
  }
315
270
  }
316
271
  </script>
317
272
 
318
273
  <style lang="scss">
319
- .w-tooltip-wrap {
320
- display: none;
321
-
322
- &--attached {display: inline-block;position: relative;}
323
- }
274
+ .w-tooltip-wrap {display: none;}
324
275
 
325
276
  .w-tooltip {
326
277
  // Fix Safari where `width: max-content` does not take padding and border into consideration.
@@ -348,115 +299,22 @@ export default {
348
299
  &--shadow {box-shadow: $box-shadow;}
349
300
  &--no-border {border: none;}
350
301
 
351
- &--top {
352
- transform: translate(-50%, -100%);
353
- margin-top: -3 * $base-increment;
354
- }
355
- &--bottom {
356
- transform: translateX(-50%);
357
- margin-top: 3 * $base-increment;
358
- }
359
- &--left {
360
- transform: translate(-100%, -50%);
361
- margin-left: -3 * $base-increment;
362
- }
363
- &--right {
364
- transform: translateY(-50%);
365
- margin-left: 3 * $base-increment;
366
- }
302
+ &--top {margin-top: -3 * $base-increment;}
303
+ &--bottom {margin-top: 3 * $base-increment;}
304
+ &--left {margin-left: -3 * $base-increment;}
305
+ &--right {margin-left: 3 * $base-increment;}
367
306
 
368
307
  &--custom-transition {transform: none;}
369
308
 
370
- // &:after {
371
- // content: '';
372
- // position: absolute;
373
- // width: 0;
374
- // height: 0;
375
- // border: 6px solid transparent;
376
- // }
377
- // &--top:after {
378
- // top: 100%;
379
- // left: 50%;
380
- // border-top-color: $tooltip-bg-color;
381
- // transform: translateX(-50%);
382
- // margin-top: 1px;
383
- // }
384
- // &--bottom:after {
385
- // bottom: 100%;
386
- // left: 50%;
387
- // border-bottom-color: $tooltip-bg-color;
388
- // transform: translateX(-50%);
389
- // margin-bottom: 1px;
390
- // }
391
- // &--left:after {
392
- // left: 100%;
393
- // top: 50%;
394
- // border-left-color: $tooltip-bg-color;
395
- // transform: translateY(-50%);
396
- // margin-left: 1px;
397
- // }
398
- // &--right:after {
399
- // right: 100%;
400
- // top: 50%;
401
- // border-right-color: $tooltip-bg-color;
402
- // transform: translateY(-50%);
403
- // margin-right: 1px;
404
- // }
405
-
406
309
  // Tooltip without border.
407
- // --------------------------------------------------------
408
310
  &--no-border {
409
311
  @include triangle(var(--w-tooltip-bg-color), '.w-tooltip', 7px, 0);
410
312
  }
313
+
314
+ // Tooltip with border.
411
315
  &:not(&--no-border) {
412
316
  @include triangle(var(--w-tooltip-bg-color), '.w-tooltip', 7px);
413
317
  }
414
-
415
- // Tooltip with border.
416
- // --------------------------------------------------------
417
- // &:not(&--no-border).w-tooltip--top:after {margin-top: -1px;}
418
- // &:not(&--no-border).w-tooltip--bottom:after {margin-bottom: -1px;}
419
- // &:not(&--no-border).w-tooltip--left:after {margin-left: -1px;}
420
- // &:not(&--no-border).w-tooltip--right:after {margin-right: -1px;}
421
-
422
- // &:not(&--no-border) {
423
- // &:before {
424
- // content: '';
425
- // position: absolute;
426
- // width: 0;
427
- // height: 0;
428
- // border: 7px solid transparent;
429
- // }
430
- // &.w-tooltip--top:before {
431
- // top: 100%;
432
- // left: 50%;
433
- // border-top-color: inherit;
434
- // transform: translateX(-50%);
435
- // margin-top: 0;
436
- // }
437
- // &.w-tooltip--bottom:before {
438
- // bottom: 100%;
439
- // left: 50%;
440
- // border-bottom-color: inherit;
441
- // transform: translateX(-50%);
442
- // margin-bottom: 0;
443
- // }
444
- // &.w-tooltip--left:before {
445
- // left: 100%;
446
- // top: 50%;
447
- // border-left-color: inherit;
448
- // transform: translateY(-50%);
449
- // margin-left: 0;
450
- // }
451
- // &.w-tooltip--right:before {
452
- // right: 100%;
453
- // top: 50%;
454
- // border-right-color: inherit;
455
- // transform: translateY(-50%);
456
- // margin-right: 0;
457
- // }
458
- // }
459
- // --------------------------------------------------------
460
318
  }
461
319
 
462
320
  // Transitions.