djledda.de main
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

95 lines
3.3 KiB

  1. import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
  2. import { h as djh } from "@/util.ts";
  3. type TooltipContext = {
  4. show: (newText: string, x: number, y: number) => void,
  5. hide: () => void,
  6. };
  7. const tooltipContext = Symbol('tooltip') as InjectionKey<TooltipContext>;
  8. export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
  9. const { carrier } = options;
  10. watchEffect(() => {
  11. if (carrier.value) {
  12. carrier.value.classList.add('tooltip-carrier');
  13. carrier.value.appendChild(djh('div', { className: 'text-carrier' }));
  14. }
  15. });
  16. const listener = (pos: { x: number, y: number }) => {
  17. if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
  18. if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
  19. carrier.value.style.left = (pos.x + 15) + "px";
  20. } else {
  21. carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px";
  22. }
  23. if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) {
  24. carrier.value.style.top = pos.y + "px";
  25. } else {
  26. carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
  27. }
  28. }
  29. };
  30. const active = ref(false);
  31. watch(active, async () => {
  32. const tooltipCarrier = carrier.value;
  33. if (tooltipCarrier) {
  34. tooltipCarrier.style.opacity = active.value ? '1' : '0';
  35. tooltipCarrier.style.width = active.value ? '350px' : '0';
  36. await nextTick();
  37. if (active.value) {
  38. if (tooltipCarrier.firstChild?.nodeType === Node.ELEMENT_NODE) {
  39. const computedHeight = getComputedStyle(tooltipCarrier.firstChild as Element).height;
  40. tooltipCarrier.style.height = computedHeight;
  41. }
  42. } else {
  43. tooltipCarrier.style.height = '0';
  44. }
  45. }
  46. });
  47. onMounted(() => document.addEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
  48. onBeforeUnmount(() => document.removeEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
  49. const ctx: TooltipContext = {
  50. show(tooltip, x, y) {
  51. if (carrier.value) {
  52. carrier.value.firstChild!.textContent = tooltip;
  53. }
  54. active.value = true;
  55. listener({ x, y });
  56. },
  57. hide() {
  58. active.value = false;
  59. },
  60. };
  61. provide(tooltipContext, ctx);
  62. }
  63. export default defineComponent({
  64. name: "dj-tooltip",
  65. props: {
  66. tooltip: {
  67. type: String,
  68. required: true,
  69. },
  70. },
  71. setup(props, { slots, attrs }) {
  72. const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);
  73. return () => <>
  74. <div class="tooltip-container"
  75. {...attrs}
  76. onMouseenter={(e) => tooltip.show(props.tooltip, e.pageX, e.pageY)}
  77. onMouseleave={() => tooltip.hide()}>
  78. {slots.default && <slots.default />}
  79. </div>
  80. </>;
  81. },
  82. });