|
import Overlay from './core/overlay'; |
|
import Element from './core/element'; |
|
import Popover from './core/popover'; |
|
import './common/polyfill'; |
|
import { |
|
CLASS_CLOSE_BTN, |
|
CLASS_NEXT_STEP_BTN, |
|
CLASS_PREV_STEP_BTN, |
|
ESC_KEY_CODE, |
|
ID_POPOVER, |
|
LEFT_KEY_CODE, |
|
OVERLAY_OPACITY, |
|
OVERLAY_PADDING, |
|
RIGHT_KEY_CODE, |
|
SHOULD_ANIMATE_OVERLAY, |
|
SHOULD_OUTSIDE_CLICK_CLOSE, |
|
} from './common/constants'; |
|
import Stage from './core/stage'; |
|
|
|
|
|
|
|
|
|
export default class Driver { |
|
|
|
|
|
|
|
constructor(options = {}) { |
|
this.options = { |
|
animate: SHOULD_ANIMATE_OVERLAY, |
|
opacity: OVERLAY_OPACITY, |
|
padding: OVERLAY_PADDING, |
|
scrollIntoViewOptions: null, |
|
allowClose: SHOULD_OUTSIDE_CLICK_CLOSE, |
|
stageBackground: '#ffffff', |
|
onHighlightStarted: () => { |
|
}, |
|
onHighlighted: () => { |
|
}, |
|
onDeselected: () => { |
|
}, |
|
onReset: () => { |
|
}, |
|
...options, |
|
}; |
|
|
|
this.document = document; |
|
this.window = window; |
|
this.isActivated = false; |
|
this.steps = []; |
|
this.currentStep = 0; |
|
|
|
this.overlay = new Overlay(this.options, window, document); |
|
|
|
this.onResize = this.onResize.bind(this); |
|
this.onKeyUp = this.onKeyUp.bind(this); |
|
this.onClick = this.onClick.bind(this); |
|
|
|
|
|
this.bind(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
bind() { |
|
this.window.addEventListener('resize', this.onResize, false); |
|
this.window.addEventListener('keyup', this.onKeyUp, false); |
|
this.window.addEventListener('click', this.onClick, false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onClick(e) { |
|
if (!this.isActivated || !this.hasHighlightedElement()) { |
|
return; |
|
} |
|
|
|
const highlightedElement = this.overlay.getHighlightedElement(); |
|
const popover = this.document.getElementById(ID_POPOVER); |
|
|
|
const clickedHighlightedElement = highlightedElement.node.contains(e.target); |
|
const clickedPopover = popover && popover.contains(e.target); |
|
|
|
|
|
if (!clickedHighlightedElement && !clickedPopover && this.options.allowClose) { |
|
this.reset(); |
|
return; |
|
} |
|
|
|
const nextClicked = e.target.classList.contains(CLASS_NEXT_STEP_BTN); |
|
const prevClicked = e.target.classList.contains(CLASS_PREV_STEP_BTN); |
|
const closeClicked = e.target.classList.contains(CLASS_CLOSE_BTN); |
|
|
|
if (closeClicked) { |
|
this.reset(); |
|
return; |
|
} |
|
|
|
if (nextClicked) { |
|
this.moveNext(); |
|
} else if (prevClicked) { |
|
this.movePrevious(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onResize() { |
|
if (!this.isActivated) { |
|
return; |
|
} |
|
|
|
this.overlay.refresh(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
onKeyUp(event) { |
|
if (!this.isActivated) { |
|
return; |
|
} |
|
|
|
|
|
if (event.keyCode === ESC_KEY_CODE && this.options.allowClose) { |
|
this.reset(); |
|
return; |
|
} |
|
|
|
|
|
if (this.steps.length !== 0) { |
|
if (event.keyCode === RIGHT_KEY_CODE) { |
|
this.moveNext(); |
|
} else if (event.keyCode === LEFT_KEY_CODE) { |
|
this.movePrevious(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
movePrevious() { |
|
this.currentStep -= 1; |
|
if (this.steps[this.currentStep]) { |
|
this.overlay.highlight(this.steps[this.currentStep]); |
|
} else { |
|
this.reset(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
moveNext() { |
|
this.currentStep += 1; |
|
if (this.steps[this.currentStep]) { |
|
this.overlay.highlight(this.steps[this.currentStep]); |
|
} else { |
|
this.reset(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
hasNextStep() { |
|
return !!this.steps[this.currentStep + 1]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
hasPreviousStep() { |
|
return !!this.steps[this.currentStep - 1]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
reset(immediate = false) { |
|
this.currentStep = 0; |
|
this.isActivated = false; |
|
this.overlay.clear(immediate); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
hasHighlightedElement() { |
|
const highlightedElement = this.overlay.getHighlightedElement(); |
|
return highlightedElement && highlightedElement.node; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
getHighlightedElement() { |
|
return this.overlay.getHighlightedElement(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
getLastHighlightedElement() { |
|
return this.overlay.getLastHighlightedElement(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
defineSteps(steps) { |
|
this.steps = []; |
|
|
|
steps.forEach((step, index) => { |
|
if (!step.element || typeof step.element !== 'string') { |
|
throw new Error(`Element (query selector string) missing in step ${index}`); |
|
} |
|
|
|
const element = this.prepareElementFromStep(step, steps, index); |
|
if (!element) { |
|
return; |
|
} |
|
|
|
this.steps.push(element); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prepareElementFromStep(currentStep, allSteps = [], index = 0) { |
|
let querySelector = ''; |
|
let elementOptions = {}; |
|
|
|
|
|
if (typeof currentStep === 'string') { |
|
querySelector = currentStep; |
|
} else { |
|
querySelector = currentStep.element; |
|
elementOptions = { |
|
...this.options, |
|
...currentStep, |
|
}; |
|
} |
|
|
|
const domElement = this.document.querySelector(querySelector); |
|
if (!domElement) { |
|
console.warn(`Element to highlight ${querySelector} not found`); |
|
return null; |
|
} |
|
|
|
let popover = null; |
|
if (elementOptions.popover && elementOptions.popover.description) { |
|
const popoverOptions = { |
|
...this.options, |
|
...elementOptions.popover, |
|
totalCount: allSteps.length, |
|
currentIndex: index, |
|
isFirst: index === 0, |
|
isLast: index === allSteps.length - 1, |
|
}; |
|
|
|
popover = new Popover(popoverOptions, this.window, this.document); |
|
} |
|
|
|
const stageOptions = { |
|
...this.options, |
|
...elementOptions, |
|
}; |
|
|
|
const stage = new Stage(stageOptions, this.window, this.document); |
|
|
|
return new Element({ |
|
node: domElement, |
|
options: elementOptions, |
|
popover, |
|
stage, |
|
overlay: this.overlay, |
|
window: this.window, |
|
document: this.document, |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
start(index = 0) { |
|
if (!this.steps || this.steps.length === 0) { |
|
throw new Error('There are no steps defined to iterate'); |
|
} |
|
|
|
this.isActivated = true; |
|
|
|
this.currentStep = index; |
|
this.overlay.highlight(this.steps[index]); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
highlight(selector) { |
|
this.isActivated = true; |
|
|
|
const element = this.prepareElementFromStep(selector); |
|
if (!element) { |
|
return; |
|
} |
|
|
|
this.overlay.highlight(element); |
|
} |
|
} |
|
|