= ref(null);
const {
horizontal: desiredHorizontalPosition,
vertical: desiredVerticalPosition,
} = parsePosition(props.position);
const horizontalPosition = ref(desiredHorizontalPosition);
const verticalPosition = ref(desiredVerticalPosition);
onMounted(async () => {
// We need to wait until children components will be mounted
await nextTick();
const adjustedPosition = getAdjustedPosition(
containerRef,
contentRef,
width,
height,
desiredHorizontalPosition,
desiredVerticalPosition
);
horizontalPosition.value = adjustedPosition.horizontal;
verticalPosition.value = adjustedPosition.vertical;
});
// adjust position when something changes
const watchableList = [
scrollOffsetX,
scrollOffsetY,
width,
height,
props, // TODO: use more specific ones
];
watchableList.forEach((watchable) =>
watch(watchable, () => {
const adjustedPosition = getAdjustedPosition(
containerRef,
contentRef,
width,
height,
desiredHorizontalPosition,
desiredVerticalPosition
);
horizontalPosition.value = adjustedPosition.horizontal;
verticalPosition.value = adjustedPosition.vertical;
})
);
watch(
() => props.forceShow,
(show) => {
isShown.value = show;
}
);
return {
tooltipId,
isShown,
[config.containerRef]: containerRef,
[config.contentRef]: contentRef,
horizontalPosition,
verticalPosition,
};
},
computed: {
containerClasses(): string[] {
const classes = [config.class];
if (this.isShown) {
classes.push(styles.isShown);
}
if (this.hasArrow) {
classes.push(styles.hasArrow);
}
if (this.horizontalPosition) {
classes.push(styles[this.horizontalPosition]);
}
if (this.verticalPosition) {
classes.push(styles[this.verticalPosition]);
}
return classes;
},
contentOffsetStyles(): string | undefined {
if (!this.offset[0] && !this.offset[1]) {
return undefined;
}
return `margin: ${this.offset[0] || 0}px ${this.offset[1] || 0}px;`;
},
renderTarget(): VNode {
const bindings = {
tabindex: this.tabindex,
"aria-describedby": this.tooltipId,
class: [
config.targetClass,
this.targetClass,
generateClass.font(this.targetFont || this.size),
generateClass.outline(COLOR_SCHEME.SECONDARY, this.size), // TODO: config or props
],
onMouseenter: () => this.hoverHandler(true),
onMouseleave: () => this.hoverHandler(false),
onFocusin: () => this.hoverHandler(true),
onFocusout: () => this.hoverHandler(false),
onClick: () => this.clickHandler(),
onKeyup: this.keyupHandler,
};
return {this.$slots.target?.() || this.target}
;
},
renderContent(): VNode {
// TODO: move to getters
const bindings = {
ref: config.contentRef,
id: this.tooltipId,
"aria-hidden": !this.isShown,
role: "tooltip",
class: [
config.contentClass,
this.contentClass,
generateClass.font(this.contentFont || this.size),
...generateClass.padding(this.padding, this.size),
generateClass.rounding(this.rounding),
generateClass.size(this.size),
generateClass.transition(this.transition),
],
style: this.contentOffsetStyles,
};
return {this.$slots.content?.() || this.content}
;
},
},
methods: {
emitShown(): void {
/**
* Emits current tooltip state on change.
*
* @event change
* @type {isShown: boolean}
*/
this.$emit(EVENT_NAME.CHANGE, this.isShown);
/**
* Emits current tooltip state on change.
*
* @event update:show
* @type {isShown: boolean}
*/
this.$emit(EVENT_NAME.UPDATE_SHOW, this.isShown);
this.whenChange?.(this.isShown);
},
// TODO: combine change and toggle !!!
hoverHandler(isShown = true): void {
if (this.trigger === TRIGGER.HOVER && this.isShown !== isShown) {
this.isShown = isShown;
this.emitShown();
}
},
clickHandler(): void {
if (this.trigger === TRIGGER.CLICK) {
this.isShown = !this.isShown;
this.emitShown();
}
},
keyupHandler(event: KeyboardEvent): void {
if (this.trigger === TRIGGER.MANUAL) {
return;
}
if (event.key === EVENT_KEY.Enter || event.key === EVENT_KEY.Space) {
this.isShown = true;
this.emitShown();
} else if (event.key === EVENT_KEY.Escape) {
this.isShown = false;
this.emitShown();
}
},
},
/*TODO: why vue-docgen cant' detect not default slots ???*/
/**
* @slot $slots.target
* Use instead of props.target to fully customize target
* */
/**
* @slot $slots.content
* Use instead of props.error to fully customize content
* */
render(): VNode {
const Tag = this.tag;
return (
{this.renderTarget}
{this.renderContent}
);
},
});