djledda.de main
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

130 行
4.0 KiB

  1. import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
  2. import { addCSS, css, 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. const tooltipStyles = css`
  9. .tooltip-container {
  10. display: inline-block;
  11. }
  12. .tooltip-carrier {
  13. opacity: 0;
  14. display: block;
  15. pointer-events: none;
  16. background-color: black;
  17. border: white solid 1px;
  18. color: white;
  19. padding: 10px;
  20. position: absolute;
  21. z-index: 1;
  22. overflow: hidden;
  23. height: 0;
  24. width: 0;
  25. position: absolute;
  26. transition: opacity 200ms, height 200ms, width 200ms;
  27. .text-carrier {
  28. position: absolute;
  29. width: 350px;
  30. font-size: 16px;
  31. font-family: "Roboto", serif;
  32. display: block;
  33. overflow: hidden;
  34. }
  35. }`;
  36. export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
  37. const { carrier } = options;
  38. addCSS('tooltip-carrier', tooltipStyles);
  39. watchEffect(() => {
  40. if (carrier.value) {
  41. carrier.value.classList.add('tooltip-carrier');
  42. carrier.value.appendChild(djh('div', { className: 'text-carrier' }));
  43. }
  44. });
  45. const listener = (pos: { x: number, y: number }) => {
  46. if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
  47. if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
  48. carrier.value.style.left = (pos.x + 15) + "px";
  49. } else {
  50. carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px";
  51. }
  52. if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) {
  53. carrier.value.style.top = pos.y + "px";
  54. } else {
  55. carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
  56. }
  57. }
  58. };
  59. const active = ref(false);
  60. watch(active, async () => {
  61. const tooltipCarrier = carrier.value;
  62. if (tooltipCarrier) {
  63. tooltipCarrier.style.opacity = active.value ? '1' : '0';
  64. tooltipCarrier.style.width = active.value ? '350px' : '0';
  65. await nextTick();
  66. if (active.value) {
  67. if (tooltipCarrier.firstChild?.nodeType === Node.ELEMENT_NODE) {
  68. const computedHeight = getComputedStyle(tooltipCarrier.firstChild as Element).height;
  69. tooltipCarrier.style.height = computedHeight;
  70. }
  71. } else {
  72. tooltipCarrier.style.height = '0';
  73. }
  74. }
  75. });
  76. onMounted(() => document.addEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
  77. onBeforeUnmount(() => document.removeEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
  78. const ctx: TooltipContext = {
  79. show(tooltip, x, y) {
  80. if (carrier.value) {
  81. (carrier.value.firstChild as HTMLElement)!.innerHTML = tooltip;
  82. }
  83. active.value = true;
  84. listener({ x, y });
  85. },
  86. hide() {
  87. active.value = false;
  88. },
  89. };
  90. provide(tooltipContext, ctx);
  91. }
  92. export default defineComponent({
  93. name: "dj-tooltip",
  94. props: {
  95. tooltip: {
  96. type: String,
  97. required: true,
  98. },
  99. },
  100. setup(props, { slots, attrs }) {
  101. const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);
  102. onBeforeUnmount(() => tooltip.hide());
  103. return () => <>
  104. <div class="tooltip-container"
  105. {...attrs}
  106. onMouseenter={(e) => tooltip.show(props.tooltip, e.pageX, e.pageY)}
  107. onMouseleave={() => tooltip.hide()}>
  108. {slots.default && <slots.default />}
  109. </div>
  110. </>;
  111. },
  112. });