File size: 3,079 Bytes
aa3b624
 
66a740c
9db3a38
82a88c5
aa3b624
 
 
4c98241
aa3b624
edd7dca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa3b624
 
edd7dca
aa3b624
 
edd7dca
aa3b624
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75e70b4
aa3b624
 
 
 
 
66a740c
9db3a38
 
 
 
 
 
 
aa3b624
4c98241
 
 
 
aa3b624
 
 
 
 
66a740c
aa3b624
 
 
8480986
9db3a38
 
 
 
4c98241
aa3b624
 
 
 
 
4c98241
aa3b624
 
82a88c5
9db3a38
 
 
82a88c5
aa3b624
 
 
 
 
 
4c98241
aa3b624
 
edd7dca
aa3b624
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { DriveStep } from "./driver";
import { refreshStage, trackActiveElement, transitionStage } from "./stage";
import { getConfig } from "./config";
import { repositionPopover, renderPopover, hidePopover } from "./popover";
import { bringInView } from "./utils";

let previousHighlight: Element | undefined;
let activeHighlight: Element | undefined;
let currentTransitionCallback: undefined | (() => void);

function mountDummyElement(): Element {
  const existingDummy = document.getElementById("driver-dummy-element");
  if (existingDummy) {
    return existingDummy;
  }

  let element = document.createElement("div");

  element.id = "driver-dummy-element";
  element.style.width = "0";
  element.style.height = "0";
  element.style.pointerEvents = "none";
  element.style.opacity = "0";
  element.style.position = "fixed";
  element.style.top = "50%";
  element.style.left = "50%";

  document.body.appendChild(element);

  return element;
}

export function highlight(step: DriveStep) {
  const { element } = step;
  let elemObj = typeof element === "string" ? document.querySelector(element) : element;

  if (!elemObj) {
    elemObj = mountDummyElement();
  }

  previousHighlight = activeHighlight;
  activeHighlight = elemObj;

  transferHighlight(previousHighlight || elemObj, elemObj);
}

export function refreshActiveHighlight() {
  if (!activeHighlight) {
    return;
  }

  trackActiveElement(activeHighlight);
  refreshStage();
  repositionPopover(activeHighlight);
}

function transferHighlight(from: Element, to: Element) {
  const duration = 400;
  const start = Date.now();

  // If it's the first time we're highlighting an element, we show
  // the popover immediately. Otherwise, we wait for the animation
  // to finish before showing the popover.
  const hasDelayedPopover = !from || from !== to;

  hidePopover();

  const animate = () => {
    // This makes sure that the repeated calls to transferHighlight
    // don't interfere with each other. Only the last call will be
    // executed.
    if (currentTransitionCallback !== animate) {
      return;
    }

    const elapsed = Date.now() - start;

    if (getConfig("animate") && elapsed < duration) {
      transitionStage(elapsed, duration, from, to);
    } else {
      trackActiveElement(to);

      if (hasDelayedPopover) {
        renderPopover(to);
      }

      currentTransitionCallback = undefined;
    }

    window.requestAnimationFrame(animate);
  };

  currentTransitionCallback = animate;
  window.requestAnimationFrame(animate);

  bringInView(to);
  if (!hasDelayedPopover) {
    renderPopover(to);
  }

  from.classList.remove("driver-active-element");
  to.classList.add("driver-active-element");
}

export function destroyHighlight() {
  activeHighlight = undefined;
  currentTransitionCallback = undefined;
  previousHighlight = undefined;
  activeHighlight = undefined;
  document.getElementById("driver-dummy-element")?.remove();

  document.querySelectorAll(".driver-active-element").forEach(element => {
    element.classList.remove("driver-active-element");
  });
}