|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
- import { addCSS, css, 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>;
-
- const tooltipStyles = css`
- .tooltip-container {
- display: inline-block;
- }
-
- .tooltip-carrier {
- opacity: 0;
- display: block;
- pointer-events: none;
- background-color: black;
- border: white solid 1px;
- color: white;
- padding: 10px;
- position: absolute;
- z-index: 1;
- overflow: hidden;
- height: 0;
- width: 0;
- position: absolute;
- transition: opacity 200ms, height 200ms, width 200ms;
-
- .text-carrier {
- position: absolute;
- width: 350px;
- font-size: 16px;
- font-family: "Roboto", serif;
- display: block;
- overflow: hidden;
- }
- }`;
-
- export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
- const { carrier } = options;
-
- addCSS('tooltip-carrier', tooltipStyles);
-
- 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 as HTMLElement)!.innerHTML = 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);
-
- onBeforeUnmount(() => tooltip.hide());
-
- return () => <>
- <div class="tooltip-container"
- {...attrs}
- onMouseenter={(e) => tooltip.show(props.tooltip, e.pageX, e.pageY)}
- onMouseleave={() => tooltip.hide()}>
- {slots.default && <slots.default />}
- </div>
- </>;
- },
- });
|