mirror of
https://github.com/directus/directus.git
synced 2026-02-15 11:35:12 -05:00
* Remove unused nested folders from components * Remove nested folders * Standardize composables output * Fix import inconsistencies * Same trick for directives * Same for routes * Replace reliance root grouped export in favor of explicit imports * Replace reliance on implicit imports * Remove nested folder structure * Consistent use of non-default exports in utils * Remove nested folder structure from private components * Fix test mock * Remove extraneous component registration for valuenull * Fix stores provider * Fix logo sprite
216 lines
5.8 KiB
TypeScript
216 lines
5.8 KiB
TypeScript
import { nanoid } from 'nanoid';
|
|
import { Directive, DirectiveBinding } from 'vue';
|
|
|
|
const tooltipDelay = 300;
|
|
|
|
const handlers: Record<string, () => void> = {};
|
|
|
|
function beforeMount(element: HTMLElement, binding: DirectiveBinding): void {
|
|
if (binding.value) {
|
|
element.dataset.tooltip = nanoid();
|
|
handlers[element.dataset.tooltip] = createEnterHandler(element, binding);
|
|
element.addEventListener('mouseenter', handlers[element.dataset.tooltip]);
|
|
element.addEventListener('mouseleave', onLeaveTooltip);
|
|
}
|
|
}
|
|
|
|
function unmounted(element: HTMLElement): void {
|
|
element.removeEventListener('mouseenter', handlers[element.dataset.tooltip as string]);
|
|
element.removeEventListener('mouseleave', onLeaveTooltip);
|
|
clearTimeout(tooltipTimer);
|
|
const tooltip = getTooltip();
|
|
tooltip.classList.remove('visible');
|
|
delete handlers[element.dataset.tooltip as string];
|
|
}
|
|
|
|
const Tooltip: Directive = {
|
|
beforeMount,
|
|
unmounted,
|
|
updated(element, binding) {
|
|
if (binding.value && !binding.oldValue) {
|
|
beforeMount(element, binding);
|
|
} else if (!binding.value && binding.oldValue) {
|
|
unmounted(element);
|
|
} else {
|
|
unmounted(element);
|
|
beforeMount(element, binding);
|
|
}
|
|
},
|
|
};
|
|
|
|
export default Tooltip;
|
|
|
|
let tooltipTimer: number;
|
|
|
|
export function createEnterHandler(element: HTMLElement, binding: DirectiveBinding) {
|
|
return (): void => {
|
|
const tooltip = getTooltip();
|
|
|
|
if (binding.modifiers.instant) {
|
|
animateIn(tooltip);
|
|
updateTooltip(element, binding, tooltip);
|
|
} else {
|
|
clearTimeout(tooltipTimer);
|
|
tooltipTimer = window.setTimeout(() => {
|
|
animateIn(tooltip);
|
|
updateTooltip(element, binding, tooltip);
|
|
}, tooltipDelay);
|
|
}
|
|
};
|
|
}
|
|
|
|
export function onLeaveTooltip(): void {
|
|
const tooltip = getTooltip();
|
|
|
|
clearTimeout(tooltipTimer);
|
|
animateOut(tooltip);
|
|
}
|
|
|
|
export function updateTooltip(element: HTMLElement, binding: DirectiveBinding, tooltip: HTMLElement): void {
|
|
const offset = 10;
|
|
const arrowAlign = 20;
|
|
|
|
const bounds = element.getBoundingClientRect();
|
|
let top = bounds.top + pageYOffset;
|
|
let left = bounds.left + pageXOffset;
|
|
let transformPos;
|
|
|
|
tooltip.innerText = binding.value;
|
|
tooltip.classList.remove('top', 'bottom', 'left', 'right', 'start', 'end');
|
|
|
|
let placement = binding.arg ?? 'top';
|
|
|
|
if ('top' in binding.modifiers) placement = 'top';
|
|
if ('right' in binding.modifiers) placement = 'right';
|
|
if ('bottom' in binding.modifiers) placement = 'bottom';
|
|
if ('left' in binding.modifiers) placement = 'left';
|
|
|
|
if (binding.modifiers.inverted) {
|
|
tooltip.classList.add('inverted');
|
|
} else {
|
|
tooltip.classList.remove('inverted');
|
|
}
|
|
|
|
if (binding.modifiers.monospace) {
|
|
tooltip.classList.add('monospace');
|
|
}
|
|
|
|
if (placement === 'bottom') {
|
|
if (binding.modifiers.start) {
|
|
left += arrowAlign;
|
|
transformPos = 100;
|
|
tooltip.classList.add('start');
|
|
} else if (binding.modifiers.end) {
|
|
left += bounds.width - arrowAlign;
|
|
transformPos = 0;
|
|
tooltip.classList.add('end');
|
|
} else {
|
|
left += bounds.width / 2;
|
|
transformPos = 50;
|
|
}
|
|
|
|
top += bounds.height + offset;
|
|
tooltip.style.transform = `translate(calc(${left}px - ${transformPos}%), ${top}px)`;
|
|
tooltip.classList.add('bottom');
|
|
} else if (placement === 'left') {
|
|
if (binding.modifiers.start) {
|
|
top += arrowAlign;
|
|
transformPos = 100;
|
|
tooltip.classList.add('start');
|
|
} else if (binding.modifiers.end) {
|
|
top += bounds.height - arrowAlign;
|
|
transformPos = 0;
|
|
tooltip.classList.add('end');
|
|
} else {
|
|
top += bounds.height / 2;
|
|
transformPos = 50;
|
|
}
|
|
|
|
left -= offset;
|
|
tooltip.style.transform = `translate(calc(${left}px - 100%), calc(${top}px - ${transformPos}%))`;
|
|
tooltip.classList.add('left');
|
|
} else if (placement === 'right') {
|
|
if (binding.modifiers.start) {
|
|
top += arrowAlign;
|
|
transformPos = 100;
|
|
tooltip.classList.add('start');
|
|
} else if (binding.modifiers.end) {
|
|
top += bounds.height - arrowAlign;
|
|
transformPos = 0;
|
|
tooltip.classList.add('end');
|
|
} else {
|
|
top += bounds.height / 2;
|
|
transformPos = 50;
|
|
}
|
|
|
|
left += bounds.width + offset;
|
|
tooltip.style.transform = `translate(${left}px, calc(${top}px - ${transformPos}%))`;
|
|
tooltip.classList.add('right');
|
|
} else {
|
|
if (binding.modifiers.start) {
|
|
left += arrowAlign;
|
|
transformPos = 100;
|
|
tooltip.classList.add('start');
|
|
} else if (binding.modifiers.end) {
|
|
left += bounds.width - arrowAlign;
|
|
transformPos = 0;
|
|
tooltip.classList.add('end');
|
|
} else {
|
|
left += bounds.width / 2;
|
|
transformPos = 50;
|
|
}
|
|
|
|
top -= offset;
|
|
tooltip.style.transform = `translate(calc(${left}px - ${transformPos}%), calc(${top}px - 100%))`;
|
|
tooltip.classList.add('top');
|
|
}
|
|
}
|
|
|
|
export function animateIn(tooltip: HTMLElement): void {
|
|
tooltip.classList.add('visible', 'enter');
|
|
tooltip.classList.remove('leave', 'leave-active');
|
|
|
|
setTimeout(() => {
|
|
if (tooltip.classList.contains('enter') === false) return;
|
|
tooltip.classList.add('enter-active');
|
|
tooltip.classList.remove('enter');
|
|
}, 1);
|
|
|
|
setTimeout(() => {
|
|
tooltip.classList.remove('enter-active');
|
|
}, 200);
|
|
}
|
|
|
|
export function animateOut(tooltip: HTMLElement): void {
|
|
if (tooltip.classList.contains('visible') === false) return;
|
|
|
|
tooltip.classList.add('visible', 'leave');
|
|
tooltip.classList.remove('enter', 'enter-active');
|
|
|
|
setTimeout(() => {
|
|
if (tooltip.classList.contains('leave') === false) return;
|
|
tooltip.classList.add('leave-active');
|
|
tooltip.classList.remove('leave');
|
|
}, 1);
|
|
|
|
setTimeout(() => {
|
|
if (tooltip.classList.contains('leave-active') === false) return;
|
|
tooltip.classList.remove('leave-active');
|
|
tooltip.classList.remove('visible');
|
|
}, 200);
|
|
}
|
|
|
|
export function getTooltip(): HTMLElement {
|
|
let tooltip = document.getElementById('tooltip');
|
|
|
|
if (tooltip instanceof HTMLElement) {
|
|
return tooltip;
|
|
}
|
|
|
|
tooltip = document.createElement('div');
|
|
tooltip.id = 'tooltip';
|
|
document.body.appendChild(tooltip);
|
|
|
|
return tooltip;
|
|
}
|