wave-ui 2.29.0 → 2.31.2

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