File size: 3,311 Bytes
aa3b624
 
66a740c
9db3a38
82a88c5
9bde4cc
aa3b624
edd7dca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa3b624
 
edd7dca
aa3b624
 
edd7dca
aa3b624
 
9bde4cc
 
 
 
 
 
aa3b624
9bde4cc
 
aa3b624
 
 
9bde4cc
aa3b624
 
 
 
 
 
75e70b4
aa3b624
 
 
 
 
66a740c
9db3a38
 
 
9bde4cc
9db3a38
 
 
aa3b624
9bde4cc
 
4c98241
 
 
9bde4cc
aa3b624
 
 
 
 
66a740c
aa3b624
 
 
8480986
9db3a38
 
 
 
9bde4cc
aa3b624
 
 
 
 
9bde4cc
aa3b624
 
82a88c5
9db3a38
 
 
82a88c5
aa3b624
 
 
 
 
9bde4cc
 
 
 
 
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
117
118
119
120
121
122
import { DriveStep } from "./driver";
import { refreshStage, trackActiveElement, transitionStage } from "./stage";
import { getConfig } from "./config";
import { repositionPopover, renderPopover, hidePopover } from "./popover";
import { bringInView } from "./utils";
import { getState, setState } from "./state";

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();
  }

  const previousHighlight = getState("activeHighlight");

  const transferHighlightFrom = previousHighlight || elemObj;
  const transferHighlightTo = elemObj;

  transferHighlight(transferHighlightFrom, transferHighlightTo);

  setState("previousHighlight", transferHighlightFrom);
  setState("activeHighlight", transferHighlightTo);
}

export function refreshActiveHighlight() {
  const activeHighlight = getState("activeHighlight");
  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 = to && (!from || from !== to);

  hidePopover();

  const animate = () => {
    const transitionCallback = getState("transitionCallback");

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

    const elapsed = Date.now() - start;

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

      if (hasDelayedPopover) {
        renderPopover(to);
      }

      setState("transitionCallback", undefined);
    }

    window.requestAnimationFrame(animate);
  };

  setState("transitionCallback", 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() {
  setState("activeHighlight", undefined);
  setState("previousHighlight", undefined);

  setState("transitionCallback", undefined);

  document.getElementById("driver-dummy-element")?.remove();

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