95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
|
|
import { h as djh } from "@/util.ts";
|
|
|
|
type TooltipContext = {
|
|
show: (newText: string, x: number, y: number) => void,
|
|
hide: () => void,
|
|
};
|
|
|
|
const tooltipContext = Symbol('tooltip') as InjectionKey<TooltipContext>;
|
|
|
|
export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
|
|
const { carrier } = options;
|
|
|
|
watchEffect(() => {
|
|
if (carrier.value) {
|
|
carrier.value.classList.add('tooltip-carrier');
|
|
carrier.value.appendChild(djh('div', { className: 'text-carrier' }));
|
|
}
|
|
});
|
|
|
|
const listener = (pos: { x: number, y: number }) => {
|
|
if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
|
|
if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
|
|
carrier.value.style.left = (pos.x + 15) + "px";
|
|
} else {
|
|
carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px";
|
|
}
|
|
if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) {
|
|
carrier.value.style.top = pos.y + "px";
|
|
} else {
|
|
carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
|
|
}
|
|
}
|
|
};
|
|
|
|
const active = ref(false);
|
|
|
|
watch(active, async () => {
|
|
const tooltipCarrier = carrier.value;
|
|
if (tooltipCarrier) {
|
|
tooltipCarrier.style.opacity = active.value ? '1' : '0';
|
|
tooltipCarrier.style.width = active.value ? '350px' : '0';
|
|
await nextTick();
|
|
if (active.value) {
|
|
if (tooltipCarrier.firstChild?.nodeType === Node.ELEMENT_NODE) {
|
|
const computedHeight = getComputedStyle(tooltipCarrier.firstChild as Element).height;
|
|
tooltipCarrier.style.height = computedHeight;
|
|
}
|
|
} else {
|
|
tooltipCarrier.style.height = '0';
|
|
}
|
|
}
|
|
});
|
|
|
|
onMounted(() => document.addEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
|
|
onBeforeUnmount(() => document.removeEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
|
|
|
|
const ctx: TooltipContext = {
|
|
show(tooltip, x, y) {
|
|
if (carrier.value) {
|
|
carrier.value.firstChild!.textContent = tooltip;
|
|
}
|
|
active.value = true;
|
|
listener({ x, y });
|
|
},
|
|
hide() {
|
|
active.value = false;
|
|
},
|
|
};
|
|
|
|
provide(tooltipContext, ctx);
|
|
}
|
|
|
|
export default defineComponent({
|
|
name: "dj-tooltip",
|
|
props: {
|
|
tooltip: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
},
|
|
setup(props, { slots, attrs }) {
|
|
const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);
|
|
|
|
return () => <>
|
|
<div class="tooltip-container"
|
|
{...attrs}
|
|
onMouseenter={(e) => tooltip.show(props.tooltip, e.pageX, e.pageY)}
|
|
onMouseleave={() => tooltip.hide()}>
|
|
{slots.default && <slots.default />}
|
|
</div>
|
|
</>;
|
|
},
|
|
});
|