File size: 4,317 Bytes
aa3b624
 
66a740c
9db3a38
82a88c5
9bde4cc
aa3b624
edd7dca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa3b624
 
edd7dca
aa3b624
836d49f
 
 
 
aa3b624
edd7dca
aa3b624
 
836d49f
 
 
 
9bde4cc
836d49f
9bde4cc
 
836d49f
aa3b624
836d49f
 
 
 
aa3b624
 
 
836d49f
aa3b624
 
 
 
 
 
75e70b4
aa3b624
 
836d49f
aa3b624
 
66a740c
836d49f
 
9db3a38
 
 
836d49f
 
 
 
 
 
 
 
 
 
 
 
 
9db3a38
 
 
aa3b624
9bde4cc
 
4c98241
 
 
9bde4cc
aa3b624
 
 
 
 
66a740c
aa3b624
 
 
8480986
836d49f
9db3a38
 
 
9bde4cc
aa3b624
 
 
 
 
9bde4cc
aa3b624
 
82a88c5
836d49f
9db3a38
 
82a88c5
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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 the element is not found, we mount a 1px div
  // at the center of the screen to highlight and show
  // the popover on top of that. This is to show a
  // modal-like highlight.
  if (!elemObj) {
    elemObj = mountDummyElement();
  }

  // Keep track of the previous step so that we can
  // animate the transition between the two steps.
  setState("previousStep", getState("activeStep"));
  setState("activeStep", step);

  const transferHighlightFrom = getState("activeElement") || elemObj;
  const transferHighlightTo = elemObj;

  transferHighlight(transferHighlightFrom, transferHighlightTo, step);

  // Keep track of the previous element so that we can
  // animate the transition between the two elements.
  setState("previousElement", transferHighlightFrom);
  setState("activeElement", transferHighlightTo);
}

export function refreshActiveHighlight() {
  const activeHighlight = getState("activeElement");
  if (!activeHighlight) {
    return;
  }

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

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

  const previousStep = getState("previousStep");

  // 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 isFirstHighlight = !from || from === to;
  const hasNoPreviousPopover = previousStep && !previousStep.popover;
  const isNextOrPrevDummyElement = to.id === "driver-dummy-element" || from.id === "driver-dummy-element";

  const hasDelayedPopover = !isFirstHighlight && (hasNoPreviousPopover || isNextOrPrevDummyElement);

  console.log("--------------------");
  console.log("from", from);
  console.log("to", to);
  console.log("hasDelayedPopover", hasDelayedPopover);
  console.log("isFirstHighlight", isFirstHighlight);
  console.log("hasNoPreviousPopover", hasNoPreviousPopover);
  console.log("isNextOrPrevDummyElement", isNextOrPrevDummyElement);

  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 && toStep.popover) {
        renderPopover(to);
      }

      setState("transitionCallback", undefined);
    }

    window.requestAnimationFrame(animate);
  };

  setState("transitionCallback", animate);
  window.requestAnimationFrame(animate);

  bringInView(to);
  if (!hasDelayedPopover && toStep.popover) {
    renderPopover(to);
  }

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

export function destroyHighlight() {
  document.getElementById("driver-dummy-element")?.remove();
  document.querySelectorAll(".driver-active-element").forEach(element => {
    element.classList.remove("driver-active-element");
  });
}