wave-ui 1.45.15 → 1.46.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 +237 -274
- 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-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 +121 -182
- 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,8 +1,8 @@
|
|
|
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
|
-
.w-tooltip(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
detachableVisible: false,
|
|
60
|
+
hoveringActivator: false,
|
|
52
61
|
// The activator coordinates.
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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}`]:
|
|
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.
|
|
163
|
-
left: (this.
|
|
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:
|
|
176
|
-
|
|
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.
|
|
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.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
190
|
+
async open (e) {
|
|
191
|
+
this.detachableVisible = true
|
|
192
|
+
await this.insertInDOM()
|
|
222
193
|
|
|
223
|
-
if (
|
|
224
|
-
const { top: targetTop, left: targetLeft } = this.tooltipParentEl.getBoundingClientRect()
|
|
225
|
-
coords = { ...coords, top: top - targetTop, left: left - targetLeft }
|
|
226
|
-
}
|
|
194
|
+
if (this.minWidth === 'activator') this.activatorWidth = this.activatorEl.offsetWidth
|
|
227
195
|
|
|
228
|
-
|
|
196
|
+
if (!this.noPosition) this.computeDetachableCoords(e)
|
|
229
197
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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)
|
|
234
206
|
|
|
235
|
-
|
|
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'
|
|
271
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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.
|
|
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.
|
|
259
|
+
if (bool !== this.detachableVisible) this.toggle({ type: 'click', target: this.activatorEl })
|
|
309
260
|
},
|
|
310
261
|
detachTo () {
|
|
311
|
-
this.
|
|
312
|
-
this.
|
|
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,22 +299,10 @@ export default {
|
|
|
348
299
|
&--shadow {box-shadow: $box-shadow;}
|
|
349
300
|
&--no-border {border: none;}
|
|
350
301
|
|
|
351
|
-
&--top {
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
|
@@ -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;
|