tooltipDirective.ts
121 lines
| 1 | import { DirectiveBinding } from "vue"; |
| 2 | |
| 3 | import { MouseEvent } from "@/types/enums"; |
| 4 | |
| 5 | const TOOLTIP_ATTR = "tooltip"; |
| 6 | |
| 7 | interface ComplexTooltip { |
| 8 | content: string; |
| 9 | autoWidth?: boolean; |
| 10 | } |
| 11 | |
| 12 | type TooltipBinding = |
| 13 | | DirectiveBinding<string> |
| 14 | | DirectiveBinding<ComplexTooltip>; |
| 15 | |
| 16 | const isString = (value: unknown) => typeof value === "string"; |
| 17 | |
| 18 | const getCurrentRotation = (el: HTMLElement) => { |
| 19 | const computedStyles = window.getComputedStyle(el, null); |
| 20 | const transformProp = |
| 21 | computedStyles.getPropertyValue("-webkit-transform") || |
| 22 | computedStyles.getPropertyValue("-moz-transform") || |
| 23 | computedStyles.getPropertyValue("-ms-transform") || |
| 24 | computedStyles.getPropertyValue("-o-transform") || |
| 25 | computedStyles.getPropertyValue("transform") || |
| 26 | "none"; |
| 27 | if (transformProp != "none") { |
| 28 | const values = transformProp.split("(")[1].split(")")[0].split(","); |
| 29 | |
| 30 | const angle = Math.round( |
| 31 | Math.atan2(Number(values[1]), Number(values[0])) * (180 / Math.PI) |
| 32 | ); |
| 33 | |
| 34 | return angle < 0 ? angle + 360 : angle; |
| 35 | } |
| 36 | |
| 37 | return 0; |
| 38 | }; |
| 39 | |
| 40 | const getTooltipClass = ( |
| 41 | { modifiers }: DirectiveBinding, |
| 42 | isRotated: boolean |
| 43 | ) => { |
| 44 | const position = Object.keys(modifiers)[0]; |
| 45 | |
| 46 | return `has-tooltip--${position || "bottom"}${isRotated ? "-rotated" : ""}`; |
| 47 | }; |
| 48 | |
| 49 | const getTooltipContent = (binding: TooltipBinding) => { |
| 50 | if (!binding.value) return null; |
| 51 | |
| 52 | return isString(binding.value) |
| 53 | ? binding.value |
| 54 | : (binding.value as ComplexTooltip).content; |
| 55 | }; |
| 56 | |
| 57 | const addTooltip = (el: HTMLElement, binding: TooltipBinding) => { |
| 58 | const content = getTooltipContent(binding); |
| 59 | |
| 60 | if (!content) return removeTooltip(el, binding); |
| 61 | |
| 62 | const rotation = getCurrentRotation(el); |
| 63 | |
| 64 | el.setAttribute(TOOLTIP_ATTR, content as string); |
| 65 | |
| 66 | document.documentElement.style.setProperty( |
| 67 | "--tooltip-rotation", |
| 68 | `-${rotation}deg` |
| 69 | ); |
| 70 | |
| 71 | if ( |
| 72 | !isString(binding.value) && |
| 73 | (binding.value as ComplexTooltip).autoWidth |
| 74 | ) { |
| 75 | document.documentElement.style.setProperty("--tooltip-width", "auto"); |
| 76 | } |
| 77 | |
| 78 | el.style.transition = "0s"; |
| 79 | |
| 80 | const zIndex = getComputedStyle(document.documentElement).getPropertyValue( |
| 81 | "--z-index-child-2" |
| 82 | ); |
| 83 | el.style.zIndex = zIndex; |
| 84 | el.style.position = "relative"; |
| 85 | |
| 86 | el.classList.add(getTooltipClass(binding, rotation === 180)); |
| 87 | }; |
| 88 | |
| 89 | const removeTooltip = (el: HTMLElement, binding: TooltipBinding) => { |
| 90 | el.removeAttribute(TOOLTIP_ATTR); |
| 91 | |
| 92 | el.style.zIndex = ""; |
| 93 | el.style.position = ""; |
| 94 | |
| 95 | el.classList.remove(getTooltipClass(binding, true)); |
| 96 | el.classList.remove(getTooltipClass(binding, false)); |
| 97 | }; |
| 98 | |
| 99 | const unbind = (el: HTMLElement, binding: TooltipBinding) => { |
| 100 | el.removeEventListener(MouseEvent.MouseOver, () => addTooltip(el, binding)); |
| 101 | el.removeEventListener(MouseEvent.MouseLeave, () => |
| 102 | removeTooltip(el, binding) |
| 103 | ); |
| 104 | el.removeEventListener(MouseEvent.Click, () => removeTooltip(el, binding)); |
| 105 | }; |
| 106 | |
| 107 | const bind = (el: HTMLElement, binding: TooltipBinding) => { |
| 108 | el.addEventListener(MouseEvent.MouseOver, () => addTooltip(el, binding)); |
| 109 | el.addEventListener(MouseEvent.MouseLeave, () => |
| 110 | removeTooltip(el, binding) |
| 111 | ); |
| 112 | el.addEventListener(MouseEvent.Click, () => removeTooltip(el, binding)); |
| 113 | }; |
| 114 | |
| 115 | export const vTooltip = { |
| 116 | mounted: (el: HTMLElement, binding: TooltipBinding) => bind(el, binding), |
| 117 | updated: (el: HTMLElement, binding: TooltipBinding) => bind(el, binding), |
| 118 | beforeUnmount: (el: HTMLElement, binding: TooltipBinding) => |
| 119 | unbind(el, binding) |
| 120 | }; |
| 121 |