@wishbone-media/spark 0.40.0 → 0.41.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/index.js +1402 -1386
- package/package.json +1 -1
- package/src/assets/css/spark-tooltip.css +18 -8
- package/src/components/SparkTooltip.vue +32 -7
- package/src/directives/sparkTooltip.js +17 -2
package/package.json
CHANGED
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
white-space: pre-line;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/* Interactive tooltips: allow hovering/clicking inside the tooltip */
|
|
26
|
+
.spark-tooltip-interactive {
|
|
27
|
+
pointer-events: auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.spark-tooltip-interactive a {
|
|
31
|
+
color: #93c5fd; /* blue-300 */
|
|
32
|
+
text-decoration: underline;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.spark-tooltip-interactive a:hover {
|
|
36
|
+
color: #bfdbfe; /* blue-200 */
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
.spark-tooltip-arrow {
|
|
26
40
|
position: absolute;
|
|
27
41
|
width: 8px;
|
|
@@ -30,21 +44,17 @@
|
|
|
30
44
|
transform: rotate(45deg);
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
/* Transitions (for SparkTooltip component)
|
|
47
|
+
/* Transitions (for SparkTooltip component) — opacity only to avoid
|
|
48
|
+
conflicting with @floating-ui's transform: translate() positioning */
|
|
34
49
|
.spark-tooltip-enter-active {
|
|
35
|
-
transition:
|
|
36
|
-
opacity 150ms ease,
|
|
37
|
-
transform 150ms ease;
|
|
50
|
+
transition: opacity 150ms ease;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
53
|
.spark-tooltip-leave-active {
|
|
41
|
-
transition:
|
|
42
|
-
opacity 100ms ease,
|
|
43
|
-
transform 100ms ease;
|
|
54
|
+
transition: opacity 100ms ease;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
.spark-tooltip-enter-from,
|
|
47
58
|
.spark-tooltip-leave-to {
|
|
48
59
|
opacity: 0;
|
|
49
|
-
transform: scale(0.95);
|
|
50
60
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
ref="referenceRef"
|
|
4
4
|
class="inline-flex"
|
|
5
5
|
@mouseenter="show"
|
|
6
|
-
@mouseleave="
|
|
6
|
+
@mouseleave="startHide"
|
|
7
7
|
@focusin="show"
|
|
8
|
-
@focusout="
|
|
8
|
+
@focusout="startHide"
|
|
9
9
|
>
|
|
10
10
|
<slot />
|
|
11
11
|
</div>
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
v-if="isVisible"
|
|
17
17
|
ref="floatingRef"
|
|
18
18
|
:style="floatingStyles"
|
|
19
|
-
class="spark-tooltip"
|
|
19
|
+
:class="['spark-tooltip', { 'spark-tooltip-interactive': interactive }]"
|
|
20
20
|
role="tooltip"
|
|
21
|
+
@mouseenter="onTooltipMouseenter"
|
|
22
|
+
@mouseleave="onTooltipMouseleave"
|
|
21
23
|
>
|
|
22
24
|
<slot name="content">
|
|
23
25
|
<!-- eslint-disable-next-line vue/no-v-html -- html prop is opt-in, consumer responsibility -->
|
|
@@ -36,7 +38,8 @@
|
|
|
36
38
|
* Wraps a trigger element (default slot) and displays a floating tooltip on hover/focus.
|
|
37
39
|
*
|
|
38
40
|
* For simple text tooltips, prefer the v-spark-tooltip directive.
|
|
39
|
-
* Use this component when you need slot-based HTML content
|
|
41
|
+
* Use this component when you need slot-based HTML content, interactive tooltips
|
|
42
|
+
* with clickable links, or complex tooltip layouts.
|
|
40
43
|
*/
|
|
41
44
|
import { ref, computed, useSlots, onUnmounted } from 'vue'
|
|
42
45
|
import {
|
|
@@ -84,6 +87,11 @@ const props = defineProps({
|
|
|
84
87
|
type: Boolean,
|
|
85
88
|
default: true,
|
|
86
89
|
},
|
|
90
|
+
/** Allow hovering over the tooltip content (for clickable links, buttons, etc.) */
|
|
91
|
+
interactive: {
|
|
92
|
+
type: Boolean,
|
|
93
|
+
default: false,
|
|
94
|
+
},
|
|
87
95
|
})
|
|
88
96
|
|
|
89
97
|
const slots = useSlots()
|
|
@@ -96,6 +104,12 @@ const isVisible = ref(false)
|
|
|
96
104
|
let showTimeout = null
|
|
97
105
|
let hideTimeout = null
|
|
98
106
|
|
|
107
|
+
const hideDelay = computed(() => {
|
|
108
|
+
if (props.interactive)
|
|
109
|
+
return typeof props.delay === 'number' ? 150 : Math.max(props.delay.hide ?? 0, 150)
|
|
110
|
+
return typeof props.delay === 'number' ? 0 : (props.delay.hide ?? 0)
|
|
111
|
+
})
|
|
112
|
+
|
|
99
113
|
const middleware = computed(() => {
|
|
100
114
|
const mw = [offsetMiddleware(props.offset), flip(), shift({ padding: 8 })]
|
|
101
115
|
if (props.showArrow) {
|
|
@@ -139,12 +153,23 @@ function show() {
|
|
|
139
153
|
}, delay)
|
|
140
154
|
}
|
|
141
155
|
|
|
142
|
-
function
|
|
156
|
+
function startHide() {
|
|
143
157
|
clearTimeout(showTimeout)
|
|
144
|
-
const delay = typeof props.delay === 'number' ? 0 : (props.delay.hide ?? 0)
|
|
145
158
|
hideTimeout = setTimeout(() => {
|
|
146
159
|
isVisible.value = false
|
|
147
|
-
},
|
|
160
|
+
}, hideDelay.value)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function onTooltipMouseenter() {
|
|
164
|
+
if (props.interactive) {
|
|
165
|
+
clearTimeout(hideTimeout)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function onTooltipMouseleave() {
|
|
170
|
+
if (props.interactive) {
|
|
171
|
+
startHide()
|
|
172
|
+
}
|
|
148
173
|
}
|
|
149
174
|
|
|
150
175
|
onUnmounted(() => {
|
|
@@ -24,13 +24,16 @@ function normalizeConfig(value) {
|
|
|
24
24
|
html: false,
|
|
25
25
|
delay: { show: 200, hide: 0 },
|
|
26
26
|
arrow: true,
|
|
27
|
+
interactive: false,
|
|
27
28
|
...value,
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function createTooltipElement(config) {
|
|
32
33
|
const tooltip = document.createElement('div')
|
|
33
|
-
tooltip.className =
|
|
34
|
+
tooltip.className = config.interactive
|
|
35
|
+
? `${TOOLTIP_CLASS} spark-tooltip-interactive`
|
|
36
|
+
: TOOLTIP_CLASS
|
|
34
37
|
tooltip.setAttribute('role', 'tooltip')
|
|
35
38
|
|
|
36
39
|
if (config.html) {
|
|
@@ -103,6 +106,16 @@ function showTooltip(el) {
|
|
|
103
106
|
document.body.appendChild(tooltip)
|
|
104
107
|
state.tooltipEl = tooltip
|
|
105
108
|
|
|
109
|
+
// Interactive: keep tooltip visible while hovering over it
|
|
110
|
+
if (state.config.interactive) {
|
|
111
|
+
tooltip.addEventListener('mouseenter', () => {
|
|
112
|
+
clearTimeout(state.hideTimeout)
|
|
113
|
+
})
|
|
114
|
+
tooltip.addEventListener('mouseleave', () => {
|
|
115
|
+
hideTooltip(el)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
updatePosition(el, tooltip, state.config)
|
|
107
120
|
}, delay)
|
|
108
121
|
}
|
|
@@ -113,7 +126,9 @@ function hideTooltip(el) {
|
|
|
113
126
|
|
|
114
127
|
clearTimeout(state.showTimeout)
|
|
115
128
|
|
|
116
|
-
|
|
129
|
+
let delay = typeof state.config.delay === 'number' ? 0 : (state.config.delay?.hide ?? 0)
|
|
130
|
+
// Interactive tooltips need minimum delay so user can move mouse to the tooltip
|
|
131
|
+
if (state.config.interactive) delay = Math.max(delay, 150)
|
|
117
132
|
|
|
118
133
|
state.hideTimeout = setTimeout(() => {
|
|
119
134
|
if (state.tooltipEl) {
|