wave-ui 2.29.0 → 2.30.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/dist/wave-ui.cjs.js +1 -1
- package/dist/wave-ui.css +1 -1
- package/dist/wave-ui.es.js +353 -373
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +1 -1
- package/src/wave-ui/components/w-alert.vue +0 -1
- package/src/wave-ui/components/w-confirm.vue +35 -10
- package/src/wave-ui/components/w-input.vue +3 -1
- package/src/wave-ui/components/w-menu.vue +67 -214
- package/src/wave-ui/components/w-select.vue +1 -1
- package/src/wave-ui/components/w-tag.vue +17 -6
- package/src/wave-ui/components/w-tooltip.vue +122 -185
- package/src/wave-ui/mixins/detachable.js +189 -0
- package/src/wave-ui/scss/_mixins.scss +18 -4
- package/src/wave-ui/scss/_variables.scss +1 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
.w-tooltip-wrap
|
|
2
|
+
.w-tooltip-wrap
|
|
3
3
|
slot(name="activator" :on="eventHandlers")
|
|
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
|
-
|
|
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
|
|
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: {},
|
|
@@ -42,32 +42,44 @@ export default {
|
|
|
42
42
|
transition: { type: String },
|
|
43
43
|
tooltipClass: { type: [String, Object, Array] },
|
|
44
44
|
// Position.
|
|
45
|
-
detachTo: {},
|
|
45
|
+
detachTo: { type: [String, Boolean, Object], deprecated: true },
|
|
46
|
+
appendTo: { type: [String, Boolean, Object] },
|
|
46
47
|
fixed: { type: Boolean },
|
|
47
48
|
top: { type: Boolean },
|
|
48
49
|
bottom: { type: Boolean },
|
|
49
50
|
left: { type: Boolean },
|
|
50
51
|
right: { type: Boolean },
|
|
51
|
-
|
|
52
|
+
alignTop: { type: Boolean },
|
|
53
|
+
alignBottom: { type: Boolean },
|
|
54
|
+
alignLeft: { type: Boolean },
|
|
55
|
+
alignRight: { type: Boolean },
|
|
56
|
+
zIndex: { type: [Number, String, Boolean] },
|
|
57
|
+
persistent: { type: Boolean },
|
|
58
|
+
noPosition: { type: Boolean }
|
|
52
59
|
},
|
|
53
60
|
|
|
54
61
|
emits: ['input', 'update:modelValue', 'open', 'close'],
|
|
55
62
|
|
|
56
63
|
data: () => ({
|
|
57
|
-
|
|
64
|
+
detachableVisible: false,
|
|
65
|
+
hoveringActivator: false,
|
|
58
66
|
// The activator coordinates.
|
|
59
|
-
|
|
67
|
+
detachableCoords: {
|
|
60
68
|
top: 0,
|
|
61
|
-
left: 0
|
|
62
|
-
width: 0,
|
|
63
|
-
height: 0
|
|
69
|
+
left: 0
|
|
64
70
|
},
|
|
65
71
|
activatorEl: null,
|
|
66
|
-
|
|
72
|
+
detachableEl: null,
|
|
67
73
|
timeoutId: null
|
|
68
74
|
}),
|
|
69
75
|
|
|
70
76
|
computed: {
|
|
77
|
+
/**
|
|
78
|
+
* Other computed in the detachable mixin:
|
|
79
|
+
* - `appendToTarget`
|
|
80
|
+
* - `detachableParentEl`
|
|
81
|
+
**/
|
|
82
|
+
|
|
71
83
|
tooltipClasses () {
|
|
72
84
|
return objectifyClasses(this.tooltipClass)
|
|
73
85
|
},
|
|
@@ -77,34 +89,6 @@ export default {
|
|
|
77
89
|
return this.transition || `w-tooltip-slide-fade-${direction}`
|
|
78
90
|
},
|
|
79
91
|
|
|
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
92
|
position () {
|
|
109
93
|
return (
|
|
110
94
|
(this.top && 'top') ||
|
|
@@ -115,34 +99,14 @@ export default {
|
|
|
115
99
|
)
|
|
116
100
|
},
|
|
117
101
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
102
|
+
alignment () {
|
|
103
|
+
return (
|
|
104
|
+
(['top', 'bottom'].includes(this.position) && this.alignLeft && 'left') ||
|
|
105
|
+
(['top', 'bottom'].includes(this.position) && this.alignRight && 'right') ||
|
|
106
|
+
(['left', 'right'].includes(this.position) && this.alignTop && 'top') ||
|
|
107
|
+
(['left', 'right'].includes(this.position) && this.alignBottom && 'bottom') ||
|
|
108
|
+
''
|
|
109
|
+
)
|
|
146
110
|
},
|
|
147
111
|
|
|
148
112
|
classes () {
|
|
@@ -150,12 +114,12 @@ export default {
|
|
|
150
114
|
[this.color]: this.color,
|
|
151
115
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
152
116
|
...this.tooltipClasses,
|
|
153
|
-
[`w-tooltip--${this.position}`]:
|
|
117
|
+
[`w-tooltip--${this.position}`]: !this.noPosition,
|
|
118
|
+
[`w-tooltip--align-${this.alignment}`]: !this.noPosition && this.alignment,
|
|
154
119
|
'w-tooltip--tile': this.tile,
|
|
155
120
|
'w-tooltip--round': this.round,
|
|
156
121
|
'w-tooltip--shadow': this.shadow,
|
|
157
122
|
'w-tooltip--fixed': this.fixed,
|
|
158
|
-
'w-tooltip--active': this.showTooltip,
|
|
159
123
|
'w-tooltip--no-border': this.noBorder || this.bgColor,
|
|
160
124
|
'w-tooltip--custom-transition': this.transition
|
|
161
125
|
}
|
|
@@ -165,8 +129,8 @@ export default {
|
|
|
165
129
|
styles () {
|
|
166
130
|
return {
|
|
167
131
|
zIndex: this.zIndex || this.zIndex === 0 || null,
|
|
168
|
-
top: (this.
|
|
169
|
-
left: (this.
|
|
132
|
+
top: (this.detachableCoords.top && `${~~this.detachableCoords.top}px`) || null,
|
|
133
|
+
left: (this.detachableCoords.left && `${~~this.detachableCoords.left}px`) || null,
|
|
170
134
|
'--w-tooltip-bg-color': this.$waveui.colors[this.bgColor || 'white']
|
|
171
135
|
}
|
|
172
136
|
},
|
|
@@ -178,8 +142,14 @@ export default {
|
|
|
178
142
|
handlers = {
|
|
179
143
|
focus: this.toggle,
|
|
180
144
|
blur: this.toggle,
|
|
181
|
-
mouseenter:
|
|
182
|
-
|
|
145
|
+
mouseenter: e => {
|
|
146
|
+
this.hoveringActivator = true
|
|
147
|
+
this.open(e)
|
|
148
|
+
},
|
|
149
|
+
mouseleave: e => {
|
|
150
|
+
this.hoveringActivator = false
|
|
151
|
+
this.close()
|
|
152
|
+
}
|
|
183
153
|
}
|
|
184
154
|
|
|
185
155
|
// Check the window exists: SSR-proof.
|
|
@@ -190,9 +160,19 @@ export default {
|
|
|
190
160
|
},
|
|
191
161
|
|
|
192
162
|
methods: {
|
|
163
|
+
/**
|
|
164
|
+
* Other methods in the `detachable` mixin:
|
|
165
|
+
* - `getActivatorCoordinates`
|
|
166
|
+
* - `computeDetachableCoords`
|
|
167
|
+
* - `onResize`
|
|
168
|
+
* - `onOutsideMousedown`
|
|
169
|
+
* - `insertInDOM`
|
|
170
|
+
* - `removeFromDOM`
|
|
171
|
+
**/
|
|
172
|
+
|
|
193
173
|
// ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
194
174
|
toggle (e) {
|
|
195
|
-
let shouldShowTooltip = this.
|
|
175
|
+
let shouldShowTooltip = this.detachableVisible
|
|
196
176
|
if (typeof window !== 'undefined' && 'ontouchstart' in window) {
|
|
197
177
|
if (e.type === 'click') shouldShowTooltip = !shouldShowTooltip
|
|
198
178
|
}
|
|
@@ -202,130 +182,101 @@ export default {
|
|
|
202
182
|
|
|
203
183
|
this.timeoutId = clearTimeout(this.timeoutId)
|
|
204
184
|
if (shouldShowTooltip) {
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
this.
|
|
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')
|
|
185
|
+
this.$emit('update:modelValue', (this.detachableVisible = true))
|
|
186
|
+
this.$emit('input', true)
|
|
187
|
+
this.$emit('open')
|
|
188
|
+
|
|
189
|
+
this.open(e)
|
|
221
190
|
}
|
|
191
|
+
else this.close()
|
|
222
192
|
},
|
|
223
193
|
|
|
224
194
|
// ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
195
|
+
async open (e) {
|
|
196
|
+
this.detachableVisible = true
|
|
197
|
+
await this.insertInDOM()
|
|
228
198
|
|
|
229
|
-
if (
|
|
230
|
-
const { top: targetTop, left: targetLeft } = this.tooltipParentEl.getBoundingClientRect()
|
|
231
|
-
coords = { ...coords, top: top - targetTop, left: left - targetLeft }
|
|
232
|
-
}
|
|
199
|
+
if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
|
|
233
200
|
|
|
234
|
-
|
|
201
|
+
if (!this.noPosition) this.computeDetachableCoords(e)
|
|
235
202
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
203
|
+
// In `getActivatorCoordinates` accessing the tooltip computed styles takes a few ms (less than 10ms),
|
|
204
|
+
// if we don't postpone the Tooltip apparition it will start transition from a visible tooltip and
|
|
205
|
+
// thus will not transition.
|
|
206
|
+
this.timeoutId = setTimeout(() => {
|
|
207
|
+
this.$emit('update:modelValue', true)
|
|
208
|
+
this.$emit('input', true)
|
|
209
|
+
this.$emit('open')
|
|
210
|
+
}, 0)
|
|
240
211
|
|
|
241
|
-
|
|
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
|
-
}
|
|
273
|
-
|
|
274
|
-
// 3. Hide the tooltip again so the transition happens correctly.
|
|
275
|
-
tooltip.style.visibility = null
|
|
276
|
-
tooltip.style.display = 'none'
|
|
277
|
-
|
|
278
|
-
return coords
|
|
212
|
+
if (!this.persistent) document.addEventListener('mousedown', this.onOutsideMousedown)
|
|
213
|
+
if (!this.noPosition) window.addEventListener('resize', this.onResize)
|
|
279
214
|
},
|
|
280
215
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Closes the tooltip. Can happen on:
|
|
218
|
+
* - click of activator
|
|
219
|
+
* - hover outside if showOnHover
|
|
220
|
+
* - click inside tooltip if hideOnTooltipClick.
|
|
221
|
+
* / ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
222
|
+
*
|
|
223
|
+
* @param {Boolean} force when showOnHover is set to true, hovering tooltip should keep it open.
|
|
224
|
+
* But if hideOnTooltipClick is also set to true, this should force close
|
|
225
|
+
* even while hovering the tooltip.
|
|
226
|
+
*/
|
|
227
|
+
async close (force = false) {
|
|
228
|
+
// Might be already closed.
|
|
229
|
+
// E.g. showOnHover & hideOnTooltipClick: on click, force hide then mouseleave is also firing.
|
|
230
|
+
if (!this.detachableVisible) return
|
|
231
|
+
|
|
232
|
+
if (this.showOnHover && !force) {
|
|
233
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
234
|
+
if (this.showOnHover && this.hoveringActivator) return
|
|
235
|
+
}
|
|
292
236
|
|
|
293
|
-
|
|
294
|
-
|
|
237
|
+
this.$emit('update:modelValue', (this.detachableVisible = false))
|
|
238
|
+
this.$emit('input', false)
|
|
239
|
+
this.$emit('close')
|
|
240
|
+
// Remove the mousedown listener if the tooltip got closed without a mousedown outside of the tooltip.
|
|
241
|
+
document.removeEventListener('mousedown', this.onOutsideMousedown)
|
|
242
|
+
window.removeEventListener('resize', this.onResize)
|
|
295
243
|
}
|
|
296
244
|
},
|
|
297
245
|
|
|
298
246
|
mounted () {
|
|
299
|
-
|
|
300
|
-
|
|
247
|
+
const wrapper = this.$el
|
|
248
|
+
this.activatorEl = wrapper.firstElementChild
|
|
249
|
+
|
|
250
|
+
// Unwrap the activator element.
|
|
251
|
+
wrapper.parentNode.insertBefore(this.activatorEl, wrapper)
|
|
301
252
|
|
|
302
253
|
if (this.modelValue) this.toggle({ type: 'click', target: this.activatorEl })
|
|
303
254
|
},
|
|
304
255
|
|
|
305
256
|
beforeUnmount () {
|
|
306
|
-
this.
|
|
257
|
+
this.removeFromDOM()
|
|
307
258
|
|
|
308
259
|
if (this.activatorEl && this.activatorEl.parentNode) this.activatorEl.remove()
|
|
309
260
|
},
|
|
310
261
|
|
|
311
262
|
watch: {
|
|
312
263
|
modelValue (bool) {
|
|
313
|
-
if (bool !== this.
|
|
264
|
+
if (bool !== this.detachableVisible) this.toggle({ type: 'click', target: this.activatorEl })
|
|
314
265
|
},
|
|
315
266
|
detachTo () {
|
|
316
|
-
this.
|
|
317
|
-
this.
|
|
267
|
+
this.removeFromDOM()
|
|
268
|
+
this.insertInDOM()
|
|
269
|
+
},
|
|
270
|
+
appendTo () {
|
|
271
|
+
this.removeFromDOM()
|
|
272
|
+
this.insertInDOM()
|
|
318
273
|
}
|
|
319
274
|
}
|
|
320
275
|
}
|
|
321
276
|
</script>
|
|
322
277
|
|
|
323
278
|
<style lang="scss">
|
|
324
|
-
.w-tooltip-wrap {
|
|
325
|
-
display: none;
|
|
326
|
-
|
|
327
|
-
&--attached {display: inline-block;position: relative;}
|
|
328
|
-
}
|
|
279
|
+
.w-tooltip-wrap {display: none;}
|
|
329
280
|
|
|
330
281
|
.w-tooltip {
|
|
331
282
|
// Fix Safari where `width: max-content` does not take padding and border into consideration.
|
|
@@ -353,33 +304,19 @@ export default {
|
|
|
353
304
|
&--shadow {box-shadow: $box-shadow;}
|
|
354
305
|
&--no-border {border: none;}
|
|
355
306
|
|
|
356
|
-
&--top {
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
}
|
|
307
|
+
&--top {margin-top: -3 * $base-increment;}
|
|
308
|
+
&--bottom {margin-top: 3 * $base-increment;}
|
|
309
|
+
&--left {margin-left: -3 * $base-increment;}
|
|
310
|
+
&--right {margin-left: 3 * $base-increment;}
|
|
372
311
|
|
|
373
312
|
&--custom-transition {transform: none;}
|
|
374
313
|
|
|
375
314
|
// Tooltip without border.
|
|
376
|
-
// --------------------------------------------------------
|
|
377
315
|
&--no-border {
|
|
378
316
|
@include triangle(var(--w-tooltip-bg-color), '.w-tooltip', 7px, 0);
|
|
379
317
|
}
|
|
380
318
|
|
|
381
319
|
// Tooltip with border.
|
|
382
|
-
// --------------------------------------------------------
|
|
383
320
|
&:not(&--no-border) {
|
|
384
321
|
@include triangle(var(--w-tooltip-bg-color), '.w-tooltip', 7px);
|
|
385
322
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A detachable element is an element that can be appended to another DOM node
|
|
3
|
+
* (but keeping data-driven Vue DOM refreshes).
|
|
4
|
+
* This mixin is used by w-tooltip & w-menu.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { consoleWarn } from '../utils/console'
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
computed: {
|
|
11
|
+
// DOM element to attach tooltip/menu to.
|
|
12
|
+
// ! \ This computed uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
13
|
+
appendToTarget () {
|
|
14
|
+
const defaultTarget = '.w-app'
|
|
15
|
+
|
|
16
|
+
// Convert deprecated prop to renamed one.
|
|
17
|
+
if (this.detachTo && !this.appendTo) {
|
|
18
|
+
consoleWarn(`The ${this.$options.name} prop \`detach-to\` is deprecated. You can replace it with \`append-to\`.`, this)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let target = this.appendTo || this.detachTo || defaultTarget
|
|
22
|
+
if (target === true) target = defaultTarget
|
|
23
|
+
else if (this.appendTo === 'activator') target = this.$el.previousElementSibling
|
|
24
|
+
else if (target && !['object', 'string'].includes(typeof target)) target = defaultTarget
|
|
25
|
+
else if (typeof target === 'object' && !target.nodeType) {
|
|
26
|
+
target = defaultTarget
|
|
27
|
+
consoleWarn(`Invalid node provided in ${this.$options.name} \`append-to\`. Falling back to .w-app.`, this)
|
|
28
|
+
}
|
|
29
|
+
if (typeof target === 'string') target = document.querySelector(target)
|
|
30
|
+
|
|
31
|
+
if (!target) {
|
|
32
|
+
consoleWarn(`Unable to locate ${this.appendTo ? `target ${this.appendTo}` : defaultTarget}`, this)
|
|
33
|
+
target = document.querySelector(defaultTarget)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return target
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// DOM element that will receive the tooltip/menu.
|
|
40
|
+
// ! \ This computed uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
41
|
+
detachableParentEl () {
|
|
42
|
+
return this.appendToTarget
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
methods: {
|
|
47
|
+
// ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
48
|
+
getActivatorCoordinates (e) {
|
|
49
|
+
// Get the activator coordinates relative to window.
|
|
50
|
+
const { top, left, width, height } = (e ? e.target : this.activatorEl).getBoundingClientRect()
|
|
51
|
+
let coords = { top, left, width, height }
|
|
52
|
+
|
|
53
|
+
// If absolute position, adjust top & left.
|
|
54
|
+
if (!this.fixed) {
|
|
55
|
+
const { top: targetTop, left: targetLeft } = this.detachableParentEl.getBoundingClientRect()
|
|
56
|
+
const computedStyles = window.getComputedStyle(this.detachableParentEl, null)
|
|
57
|
+
coords = {
|
|
58
|
+
...coords,
|
|
59
|
+
top: top - targetTop + this.detachableParentEl.scrollTop - parseInt(computedStyles.getPropertyValue('border-top-width')),
|
|
60
|
+
left: left - targetLeft + this.detachableParentEl.scrollLeft - parseInt(computedStyles.getPropertyValue('border-left-width'))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return coords
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
68
|
+
computeDetachableCoords (e) {
|
|
69
|
+
// Get the activator coordinates.
|
|
70
|
+
let { top, left, width, height } = this.getActivatorCoordinates(e)
|
|
71
|
+
|
|
72
|
+
// 1. First display the menu but hide it (So we can get its dimension).
|
|
73
|
+
// --------------------------------------------------
|
|
74
|
+
this.detachableEl.style.visibility = 'hidden'
|
|
75
|
+
this.detachableEl.style.display = 'flex'
|
|
76
|
+
const computedStyles = window.getComputedStyle(this.detachableEl, null)
|
|
77
|
+
|
|
78
|
+
// 2. Position the menu top, left, right, bottom and apply chosen alignment.
|
|
79
|
+
// --------------------------------------------------
|
|
80
|
+
// Subtract half or full activator width or height and menu width or height according to the
|
|
81
|
+
// menu alignment.
|
|
82
|
+
// Note: the menu position relies on transform translate, the custom animation may override the
|
|
83
|
+
// css transform property so do without it i.e. no translateX(-50%), and recalculate top & left
|
|
84
|
+
// manually.
|
|
85
|
+
switch (this.position) {
|
|
86
|
+
case 'top': {
|
|
87
|
+
top -= this.detachableEl.offsetHeight
|
|
88
|
+
if (this.alignRight) {
|
|
89
|
+
// left: 100% of activator.
|
|
90
|
+
left += width - this.detachableEl.offsetWidth +
|
|
91
|
+
parseInt(computedStyles.getPropertyValue('border-right-width'))
|
|
92
|
+
}
|
|
93
|
+
else if (!this.alignLeft) left += (width - this.detachableEl.offsetWidth) / 2 // left: 50% of activator - half menu width.
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
case 'bottom': {
|
|
97
|
+
top += height
|
|
98
|
+
if (this.alignRight) {
|
|
99
|
+
// left: 100% of activator.
|
|
100
|
+
left += width - this.detachableEl.offsetWidth +
|
|
101
|
+
parseInt(computedStyles.getPropertyValue('border-right-width'))
|
|
102
|
+
}
|
|
103
|
+
else if (!this.alignLeft) left += (width - this.detachableEl.offsetWidth) / 2 // left: 50% of activator - half menu width.
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
case 'left': {
|
|
107
|
+
left -= this.detachableEl.offsetWidth
|
|
108
|
+
if (this.alignBottom) top += height - this.detachableEl.offsetHeight
|
|
109
|
+
else if (!this.alignTop) top += (height - this.detachableEl.offsetHeight) / 2 // top: 50% of activator - half menu height.
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
case 'right': {
|
|
113
|
+
left += width
|
|
114
|
+
if (this.alignBottom) {
|
|
115
|
+
top += height - this.detachableEl.offsetHeight +
|
|
116
|
+
parseInt(computedStyles.getPropertyValue('margin-top'))
|
|
117
|
+
}
|
|
118
|
+
else if (!this.alignTop) {
|
|
119
|
+
top += (height - this.detachableEl.offsetHeight) / 2 + // top: 50% of activator - half menu height.
|
|
120
|
+
parseInt(computedStyles.getPropertyValue('margin-top'))
|
|
121
|
+
}
|
|
122
|
+
break
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 3. Keep fully in viewport.
|
|
127
|
+
// @todo: do this.
|
|
128
|
+
// --------------------------------------------------
|
|
129
|
+
// if (this.position === 'top' && ((top - this.detachableEl.offsetHeight) < 0)) {
|
|
130
|
+
// const margin = - parseInt(computedStyles.getPropertyValue('margin-top'))
|
|
131
|
+
// top -= top - this.detachableEl.offsetHeight - margin - marginFromWindowSide
|
|
132
|
+
// }
|
|
133
|
+
// else if (this.position === 'left' && left - this.detachableEl.offsetWidth < 0) {
|
|
134
|
+
// const margin = - parseInt(computedStyles.getPropertyValue('margin-left'))
|
|
135
|
+
// left -= left - this.detachableEl.offsetWidth - margin - marginFromWindowSide
|
|
136
|
+
// }
|
|
137
|
+
// else if (this.position === 'right' && left + width + this.detachableEl.offsetWidth > window.innerWidth) {
|
|
138
|
+
// const margin = parseInt(computedStyles.getPropertyValue('margin-left'))
|
|
139
|
+
// left -= left + width + this.detachableEl.offsetWidth - window.innerWidth + margin + marginFromWindowSide
|
|
140
|
+
// }
|
|
141
|
+
// else if (this.position === 'bottom' && top + height + this.detachableEl.offsetHeight > window.innerHeight) {
|
|
142
|
+
// const margin = parseInt(computedStyles.getPropertyValue('margin-top'))
|
|
143
|
+
// top -= top + height + this.detachableEl.offsetHeight - window.innerHeight + margin + marginFromWindowSide
|
|
144
|
+
// }
|
|
145
|
+
|
|
146
|
+
// 4. Hide the menu again so the transition happens correctly.
|
|
147
|
+
// --------------------------------------------------
|
|
148
|
+
this.detachableEl.style.visibility = null
|
|
149
|
+
|
|
150
|
+
// The menu coordinates are also recalculated while resizing window with open menu: keep the menu visible.
|
|
151
|
+
if (!this.detachableVisible) this.detachableEl.style.display = 'none'
|
|
152
|
+
|
|
153
|
+
this.detachableCoords = { top, left }
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
onResize () {
|
|
157
|
+
if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
|
|
158
|
+
this.computeDetachableCoords()
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
|
|
162
|
+
onOutsideMousedown (e) {
|
|
163
|
+
if (!this.detachableEl.contains(e.target) && !this.activatorEl.contains(e.target)) {
|
|
164
|
+
this.$emit('update:modelValue', (this.detachableVisible = false))
|
|
165
|
+
this.$emit('input', false)
|
|
166
|
+
this.$emit('close')
|
|
167
|
+
document.removeEventListener('mousedown', this.onOutsideMousedown)
|
|
168
|
+
window.removeEventListener('resize', this.onResize)
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
insertInDOM () {
|
|
173
|
+
return new Promise(resolve => {
|
|
174
|
+
this.$nextTick(() => {
|
|
175
|
+
this.detachableEl = this.$refs.detachable?.$el || this.$refs.detachable
|
|
176
|
+
|
|
177
|
+
// Move the tooltip/menu elsewhere in the DOM.
|
|
178
|
+
// wrapper.parentNode.insertBefore(this.detachableEl, wrapper)
|
|
179
|
+
this.appendToTarget.appendChild(this.detachableEl)
|
|
180
|
+
resolve()
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
removeFromDOM () {
|
|
186
|
+
if (this.detachableEl && this.detachableEl.parentNode) this.detachableEl.remove()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -54,10 +54,24 @@
|
|
|
54
54
|
margin-right: 0;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
&#{$selector}--align-top:before {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
&#{$selector}--align-top:before {
|
|
58
|
+
transform: none;
|
|
59
|
+
top: (2 * $base-increment) - 1px;
|
|
60
|
+
}
|
|
61
|
+
&#{$selector}--align-bottom:before {
|
|
62
|
+
transform: none;
|
|
63
|
+
top: auto;
|
|
64
|
+
bottom: (2 * $base-increment) - 1px;
|
|
65
|
+
}
|
|
66
|
+
&#{$selector}--align-left:before {
|
|
67
|
+
transform: none;
|
|
68
|
+
left: (2 * $base-increment) - 1px;
|
|
69
|
+
}
|
|
70
|
+
&#{$selector}--align-right:before {
|
|
71
|
+
transform: none;
|
|
72
|
+
left: auto;
|
|
73
|
+
right: (2 * $base-increment) - 1px;
|
|
74
|
+
}
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
// The colored triangle on top of `:before`.
|
|
@@ -11,7 +11,7 @@ $css-scope: '.w-app' !default; // Allows control on CSS rules priority.
|
|
|
11
11
|
// True by default. False allows you to use an external CSS library (like Tailwind).
|
|
12
12
|
$use-layout-classes: true !default;
|
|
13
13
|
|
|
14
|
-
$base-font-size: 14px !default;
|
|
14
|
+
$base-font-size: 14px !default; // Must be a px unit.
|
|
15
15
|
$base-increment: round(divide($base-font-size, 4)) !default;
|
|
16
16
|
$layout-padding: $base-increment * 4 !default; // Applied on the .content-wrap tag.
|
|
17
17
|
$border-radius: 3px !default;
|