Skip to content

Commit 2883415

Browse files
committed
BsTooltip:
- Add properties `activator` and `arrowOff`. - Improve css variables.
1 parent 4ce95e8 commit 2883415

File tree

4 files changed

+145
-83
lines changed

4 files changed

+145
-83
lines changed

src/components/Tooltip/BsTooltip.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ComponentInternalInstance, Prop } from 'vue';
1+
import type { ComponentInternalInstance, ComponentPublicInstance, Prop } from 'vue';
22
import {
33
computed,
44
createCommentVNode,
@@ -34,6 +34,11 @@ import {
3434
export default defineComponent<TBsTooltip>({
3535
name: 'BsTooltip',
3636
props: {
37+
activator: {
38+
type: [String, Object],
39+
default: undefined,
40+
} as Prop<string | Element | ComponentPublicInstance>,
41+
arrowOff: booleanProp,
3742
content: stringProp,
3843
disabled: booleanProp,
3944
show: booleanProp,
@@ -59,36 +64,43 @@ export default defineComponent<TBsTooltip>({
5964
setup(props, { slots }) {
6065
const thisProps = props as Readonly<TTooltipOptionProps>;
6166
const tooltip = ref<Element | null>(null);
67+
const activator = ref<Element | null>(null);
6268
const active = ref<boolean>(false);
69+
const isDisabled = ref<boolean>(thisProps.disabled ?? false);
6370
const isActive = computed(() => active.value || thisProps.show);
64-
const setPosition = () => {
65-
nextTick().then(() =>
66-
useSetTooltipPosition(tooltip, instance, thisProps.placement, isActive.value)
67-
);
68-
};
6971
const transitionName = computed(() => `${cssPrefix}tooltip-${thisProps.placement}`);
7072
const classNames = computed(() => [`${cssPrefix}tooltip`, transitionName.value]);
7173
const styles = computed(() => ({
7274
width: thisProps.width === 'auto' ? undefined : Helper.cssUnit(thisProps.width),
7375
'max-width': Helper.cssUnit(thisProps.maxWidth),
7476
'z-index': thisProps.zIndex,
77+
[`--${cssPrefix}tooltip-arrow-height`]: thisProps.arrowOff ? 0 : undefined,
78+
[`--${cssPrefix}tooltip-arrow-width`]: thisProps.arrowOff ? 0 : undefined,
7579
}));
80+
const setPosition = () => {
81+
nextTick().then(() => useSetTooltipPosition(activator, tooltip, thisProps.placement));
82+
};
83+
7684
let instance: ComponentInternalInstance | null;
7785

7886
watch(
79-
() => isActive.value,
80-
(value) => {
81-
nextTick().then(() =>
82-
useSetTooltipPosition(tooltip, instance, thisProps.placement, value)
83-
);
84-
}
87+
() => thisProps.disabled as boolean,
88+
(value) => (isDisabled.value = value)
8589
);
90+
8691
onMounted(() => {
8792
instance = getCurrentInstance();
88-
useAddTooltipListener(instance, active);
89-
useSetTooltipPosition(tooltip, instance, thisProps.placement, isActive.value);
93+
useAddTooltipListener(
94+
tooltip,
95+
activator,
96+
active,
97+
isDisabled,
98+
instance,
99+
thisProps.activator,
100+
thisProps.placement
101+
);
90102
});
91-
onBeforeUnmount(() => useRemoveTooltipListener(instance));
103+
onBeforeUnmount(() => useRemoveTooltipListener(activator));
92104

93105
return () =>
94106
h(Fragment, null, [
@@ -104,14 +116,14 @@ export default defineComponent<TBsTooltip>({
104116
class: classNames.value,
105117
style: styles.value,
106118
ref: tooltip,
107-
role: 'tooltip',
108119
},
109120
[
110-
h('div', { class: 'tooltip-arrow' }),
121+
!thisProps.arrowOff && h('div', { class: 'tooltip-arrow' }),
111122
h(
112123
'div',
113124
{
114125
class: `${cssPrefix}tooltip-inner`,
126+
role: 'tooltip',
115127
},
116128
toDisplayString(thisProps.content)
117129
),
Lines changed: 88 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import type { ComponentInternalInstance, Ref, VNode, VNodeArrayChildren } from 'vue';
1+
import type {
2+
ComponentInternalInstance,
3+
ComponentPublicInstance,
4+
Ref,
5+
VNode,
6+
VNodeArrayChildren,
7+
} from 'vue';
8+
import { unref } from 'vue';
29
import { cssPrefix } from '../../../mixins/CommonApi';
310
import { EventListener } from '../../../mixins/DomHelper';
411
import type {
@@ -14,64 +21,87 @@ const SPACE = 4;
1421
/**
1522
* Calculate Tooltip left offset.
1623
*
17-
* @param activatorEl Activator Element
18-
* @param width Element width
19-
* @param placement Tooltip placement.
24+
* @param activatorEl Activator Element
25+
* @param tooltipWidth Tooltip element width
26+
* @param placement Tooltip placement.
2027
* @returns Tooltip left offset
2128
*/
2229
function getTooltipLeftPosition(
2330
activatorEl: Element,
24-
width: number,
31+
tooltipWidth: number,
2532
placement?: TPlacementPosition
2633
) {
27-
const offset = activatorEl.getBoundingClientRect();
34+
const domRect = activatorEl.getBoundingClientRect();
35+
const parentRect = activatorEl.parentElement?.getBoundingClientRect();
2836

2937
switch (placement) {
3038
case 'left':
31-
return offset.left - width - SPACE;
39+
return domRect.left - tooltipWidth - SPACE;
3240
case 'right':
33-
return offset.left + offset.width + SPACE;
41+
return domRect.left + domRect.width + SPACE;
3442
case 'top':
3543
case 'bottom':
3644
default:
37-
return offset.left + offset.width / 2 - width / 2;
45+
return (
46+
domRect.left +
47+
Math.min(domRect.width / 2, (parentRect?.width ?? domRect.width) / 2) -
48+
tooltipWidth / 2
49+
);
3850
}
3951
}
4052

4153
/**
4254
* Calculate Tooltip top offset.
4355
*
44-
* @param activatorEl Activator Element
45-
* @param height Element height
46-
* @param placement Tooltip placement.
56+
* @param activatorEl Activator Element
57+
* @param tooltipHeight Tooltip element height
58+
* @param placement Tooltip placement.
4759
* @returns Tooltip top offset
4860
*/
4961
function getTooltipTopPosition(
5062
activatorEl: Element,
51-
height: number,
63+
tooltipHeight: number,
5264
placement?: TPlacementPosition
5365
) {
5466
const rect = activatorEl.getBoundingClientRect();
5567

5668
switch (placement) {
5769
case 'top':
58-
return rect.top - height - SPACE;
70+
return rect.top - tooltipHeight - SPACE;
5971
case 'bottom':
6072
return rect.top + rect.height + SPACE;
6173
case 'left':
6274
case 'right':
6375
default:
64-
return rect.top + rect.height / 2 - height / 2;
76+
return rect.top + rect.height / 2 - tooltipHeight / 2;
6577
}
6678
}
6779

6880
/**
6981
* Find first `VNode` within the `BsTooltip` virtual-node subtree.
7082
*
71-
* @param instance Component instance search starting point.
83+
* @param instance The tooltip component instance.
84+
* @param triggerEl An element ID or element instance that can trigger the appearance of the tooltip.
7285
* @returns The DOM Element if found.
7386
*/
74-
function findActivatorElement(instance: ComponentInternalInstance): Element | null {
87+
function findActivatorElement(
88+
instance: ComponentInternalInstance,
89+
triggerEl?: string | Element | ComponentPublicInstance
90+
): Element | null {
91+
if (triggerEl) {
92+
if (triggerEl instanceof Element) {
93+
return triggerEl;
94+
} else if (Helper.isObject(triggerEl) && '$el' in triggerEl) {
95+
return triggerEl.$el;
96+
} else if (Helper.isString(triggerEl)) {
97+
const element = document.getElementById(triggerEl);
98+
if (element) {
99+
return element;
100+
}
101+
}
102+
}
103+
104+
// Fallback to component instance
75105
const sibling = (instance.vnode.el as Element).nextElementSibling;
76106
if (sibling && !sibling.classList.contains(`${cssPrefix}tooltip`)) {
77107
// The child-element on "slot.default"
@@ -93,55 +123,63 @@ function findActivatorElement(instance: ComponentInternalInstance): Element | nu
93123
}
94124

95125
export function useSetTooltipPosition(
126+
activatorRef: Ref<Element | null>,
96127
tooltipRef: Ref<Element | null>,
97-
instance?: ComponentInternalInstance | null,
98-
placement?: TPlacementPosition,
99-
isActive?: boolean
100-
) {
101-
if (!tooltipRef.value || !instance || !isActive) {
128+
placement?: TPlacementPosition
129+
): void {
130+
const activatorEl = unref(activatorRef) as Element | null;
131+
const tooltipEl = unref(tooltipRef) as HTMLElement | null;
132+
133+
if (!activatorEl || !tooltipEl) {
102134
return;
103135
}
104136

105-
const tooltipEl = tooltipRef.value as HTMLElement;
106-
107137
if (tooltipEl && Helper.isFunction(tooltipEl.getBoundingClientRect)) {
108-
const elRect = tooltipEl.getBoundingClientRect();
109-
const activatorEl = findActivatorElement(instance);
110-
111-
if (activatorEl) {
112-
tooltipEl.style.top =
113-
getTooltipTopPosition(activatorEl, elRect.height, placement) + 'px';
114-
tooltipEl.style.left =
115-
getTooltipLeftPosition(activatorEl, elRect.width, placement) + 'px';
116-
}
138+
const tooltipRect = tooltipEl.getBoundingClientRect();
139+
140+
tooltipEl.style.top =
141+
getTooltipTopPosition(activatorEl, tooltipRect.height, placement) + 'px';
142+
tooltipEl.style.left =
143+
getTooltipLeftPosition(activatorEl, tooltipRect.width, placement) + 'px';
117144
}
118145
}
119146

120147
export function useAddTooltipListener(
148+
tooltipRef: Ref<Element | null>,
149+
activatorRef: Ref<Element | null>,
150+
active: Ref<boolean>,
151+
disabled: Ref<boolean>,
121152
instance: ComponentInternalInstance | null,
122-
active: Ref<boolean>
153+
trigger?: string | Element | ComponentPublicInstance,
154+
placement?: TPlacementPosition
123155
) {
124156
if (!instance) {
125157
return;
126158
}
127159

128-
const showTooltip = (_e: Event) => {
160+
const showTooltip = () => {
161+
if (unref(disabled)) {
162+
return;
163+
}
164+
129165
window.requestAnimationFrame(() => {
166+
useSetTooltipPosition(activatorRef, tooltipRef, placement);
130167
instance.emit('update:show', true);
131168
active.value = true;
132-
})
133-
169+
});
134170
// preventEventTarget(e);
135171
};
136172
const hideTooltip = () => {
137173
instance.emit('update:show', false);
138174
active.value = false;
139175
};
140176

141-
const activatorEl = findActivatorElement(instance) as IHTMLElement | null;
177+
const activatorEl = findActivatorElement(instance, trigger) as IHTMLElement | null;
178+
activatorRef.value = activatorEl;
142179

143180
if (activatorEl) {
144-
const options = {capture: true, passive: false};
181+
const options = { capture: true, passive: false };
182+
145183
(activatorEl as IBindingElement).__mouseEvents = {
146184
mouseEnter: EventListener.listen(activatorEl, 'mouseenter', showTooltip, options),
147185
mouseLeave: EventListener.listen(activatorEl, 'mouseleave', hideTooltip, options),
@@ -151,18 +189,16 @@ export function useAddTooltipListener(
151189
}
152190
}
153191

154-
export function useRemoveTooltipListener(instance?: ComponentInternalInstance | null) {
155-
if (instance) {
156-
const activatorEl = findActivatorElement(instance) as IBindingElement | null;
157-
158-
if (activatorEl) {
159-
// @ts-ignore
160-
const { mouseEnter, mouseLeave, focus, blur } = activatorEl.__mouseEvents;
161-
(mouseEnter as IEventResult).remove();
162-
(mouseLeave as IEventResult).remove();
163-
(focus as IEventResult).remove();
164-
(blur as IEventResult).remove();
165-
activatorEl.__mouseEvents = undefined;
166-
}
192+
export function useRemoveTooltipListener(activatorRef: Ref<Element | null>) {
193+
const activatorEl = unref(activatorRef) as IBindingElement | null;
194+
195+
if (activatorEl) {
196+
// @ts-ignore
197+
const { mouseEnter, mouseLeave, focus, blur } = activatorEl.__mouseEvents;
198+
(mouseEnter as IEventResult).remove();
199+
(mouseLeave as IEventResult).remove();
200+
(focus as IEventResult).remove();
201+
(blur as IEventResult).remove();
202+
activatorEl.__mouseEvents = undefined;
167203
}
168204
}

src/components/Tooltip/tooltip.scss

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
line-height: 18px;
4343
padding: 4px 10px;
4444
pointer-events: none;
45+
width: max-content;
4546
}
4647
}
4748

@@ -53,9 +54,10 @@
5354

5455
&:before {
5556
top: 0;
56-
//border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0;
57-
border-width: $tooltip-arrow-height (math.div($tooltip-arrow-width, 2)) 0;
58-
border-top-color: rgba($tooltip-bg, $tooltip-opacity);
57+
border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0;
58+
border-top-color: rgba(var(--#{$prefix}tooltip-bg), var(--#{$prefix}tooltip-opacity));
59+
//border-width: $tooltip-arrow-height (math.div($tooltip-arrow-width, 2)) 0;
60+
//border-top-color: rgba($tooltip-bg, $tooltip-opacity);
5961
}
6062
}
6163
}
@@ -68,9 +70,10 @@
6870

6971
&::before {
7072
bottom: 0;
71-
//border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
72-
border-width: 0 math.div($tooltip-arrow-width, 2) $tooltip-arrow-height;
73-
border-bottom-color: rgba($tooltip-bg, $tooltip-opacity);
73+
border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
74+
border-bottom-color: rgba(var(--#{$prefix}tooltip-bg), var(--#{$prefix}tooltip-opacity));
75+
//border-width: 0 math.div($tooltip-arrow-width, 2) $tooltip-arrow-height;
76+
//border-bottom-color: rgba($tooltip-bg, $tooltip-opacity);
7477
}
7578
}
7679
}
@@ -85,9 +88,10 @@
8588

8689
&::before {
8790
left: 0;
88-
//border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
89-
border-width: math.div($tooltip-arrow-width, 2) 0 math.div($tooltip-arrow-width, 2) $tooltip-arrow-height;
90-
border-left-color: rgba($tooltip-bg, $tooltip-opacity);
91+
border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
92+
border-left-color: rgba(var(--#{$prefix}tooltip-bg), var(--#{$prefix}tooltip-opacity));
93+
//border-width: math.div($tooltip-arrow-width, 2) 0 math.div($tooltip-arrow-width, 2) $tooltip-arrow-height;
94+
//border-left-color: rgba($tooltip-bg, $tooltip-opacity);
9195
}
9296
}
9397
}
@@ -102,9 +106,10 @@
102106

103107
&:before {
104108
right: 0;
105-
//border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0;
106-
border-width: math.div($tooltip-arrow-width, 2) $tooltip-arrow-height math.div($tooltip-arrow-width, 2) 0;
107-
border-right-color: rgba($tooltip-bg, $tooltip-opacity);
109+
border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0;
110+
border-right-color: rgba(var(--#{$prefix}tooltip-bg), var(--#{$prefix}tooltip-opacity));
111+
//border-width: math.div($tooltip-arrow-width, 2) $tooltip-arrow-height math.div($tooltip-arrow-width, 2) 0;
112+
//border-right-color: rgba($tooltip-bg, $tooltip-opacity);
108113
}
109114
}
110115
}

0 commit comments

Comments
 (0)