UNPKG

keyboardly

Version:
1,712 lines (1,408 loc) 64.7 kB
/*! * Keyboardly v0.2.1 (https://github.com/victornpb/keyboardly) * Copyright (c) victornpb * @license MIT */ 'use strict'; var DEBUG = false; var PREFIX = '[Keyboardly]'; var setDebug = function setDebug(bool) { return DEBUG = bool; }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function () {}; return { s: F, n: function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function (e) { throw e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function () { it = it.call(o); }, n: function () { var step = it.next(); normalCompletion = step.done; return step; }, e: function (e) { didErr = true; err = e; }, f: function () { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); } function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; } function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); } function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } } function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } } function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); } var isMacLike = !!navigator.platform.match(/Mac|iOS|iPod|iPad|Apple/i); function testElement(elm, event, attribute) { var isEnabled = !elm.disabled; var keyBinding = elm.getAttribute(attribute); return isEnabled && keyBinding && testKeybinding(keyBinding, event); } /** * Test if a keyboard event matches a keybinding string like ctrl-c * @param {string} keyBinding A key binding string e.g. 'crtl-shift-a', 'shift-x', 'enter' * @param {KeyboardEvent} event A keyboard event * @return {boolean} Return true when the event matches the keybiding string */ function testKeybinding(keyBinding, event) { function test(keyBinding, event) { var keys = keyBinding.split(/-|\+/); var k = keys[keys.length - 1]; return event.ctrlKey === keys.includes('ctrl') && event.shiftKey === keys.includes('shift') && event.altKey === keys.includes('alt') && event.metaKey === keys.includes('meta') && (event.key === k || event.code === k) //TODO: keycodes are its own nightmare ; } // Mac if (isMacLike) { //TODO: make this a setting keyBinding = keyBinding.replace(/cmd|command/g, 'meta'); keyBinding = keyBinding.replace(/ctrl/g, 'meta'); // Use Coommand instead of commoon ctrl shortcuts } if (keyBinding.includes('|')) { // has alternative keybidings var aliases = keyBinding.split('|'); var _iterator = _createForOfIteratorHelper(aliases), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var kb = _step.value; if (test(kb, event)) return true; } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return false; } else { // single keybiding return test(keyBinding, event); } } // const keyCodes = { // esc: 27, // tab: 9, // enter: 13, // space: 32, // up: 38, // left: 37, // right: 39, // down: 40, // 'delete': [8, 46] // }; var ATTR_OVERRIDE_DEFAULT = 'data-prevent'; function shortcutDelegator(container, event, keybindingAttr) { var dispatcherFn = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _defaultAction; if (container) { // focused on input var activeElm = document.activeElement; var isFocusedOnInput = activeElm.nodeName === 'INPUT' || activeElm.nodeName === 'TEXTAREA'; // find elements with shortcuts // TODO: cache oportunity var matchedElements = Array.from(container.querySelectorAll("[".concat(keybindingAttr, "]"))).filter(function (elm) { return testElement(elm, event, keybindingAttr); }); if (matchedElements.length) { if (isFocusedOnInput && matchedElements.every(function (elm) { return !elm.hasAttribute(ATTR_OVERRIDE_DEFAULT); })) return; //ignore event unless a hotkey is set to prevent // trigger clicks matchedElements.forEach(function (element) { if (typeof dispatcherFn === 'function') { if (DEBUG) console.log(PREFIX, 'dispatching shortcut...', container, element, event, dispatcherFn); dispatcherFn({ container: container, element: element, event: event }); } }); } } } function _defaultAction(_ref) { _ref.container; var element = _ref.element, event = _ref.event; event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); try { element.focus(); } catch (_) { /* ignore */ } try { element.click(); } catch (_) { /* ignore */ } } function findNextItem(elements, current, e) { var rects = elements.map(getRect); var focused = getRect(current || elements[0]); var dryRun = e.shiftKey; var next; if (e.key === 'ArrowUp') { next = findAbove(focused, rects, dryRun); } else if (e.key === 'ArrowDown') { next = findBelow(focused, rects, dryRun); } else if (e.key === 'ArrowLeft') { next = findLeft(focused, rects, dryRun); } else if (e.key === 'ArrowRight') { next = findRight(focused, rects, dryRun); } if (dryRun) { //debug //DEBUG_TARGET(current); return; } if (next) { e.preventDefault(); if (dryRun) DEBUG_TARGET(next); return next.element; } return null; } function getRect(element) { var r = element.getBoundingClientRect(); var rect = { element: element, left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height, x: r.left + Math.floor(r.width / 2), y: r.top + Math.floor(r.height / 2) }; return rect; } // function inViewport(el) { // let rect = el.getBoundingClientRect(); // return ( // rect.top >= 0 && // rect.left >= 0 && // rect.bottom <= window.innerHeight && // rect.right <= window.innerWidth // ); // } /** * return direction of angle * @param {number} angle Degrees in range 0-360 * 45 up 135 * \ / * left \/ right * /\ * / \ * 315 down 225 */ function direction(angle) { if (angle >= 45 && angle < 135) return 'up'; if (angle >= 135 && angle < 225) return 'right'; if (angle >= 225 && angle < 315) return 'down'; if (angle >= 315 || angle < 45) return 'left'; } function distance(fromItem, toItem) { var dx = toItem.x - fromItem.x; var dy = toItem.y - fromItem.y; return Math.sqrt(dx * dx + dy * dy); } function findAngle(fromItem, toItem) { var dx = fromItem.x - toItem.x; var dy = fromItem.y - toItem.y; var angle = Math.atan2(dy, dx); var deg = angle * 180 / Math.PI; // radians to degrees return deg % 360 + (deg < 0 ? 360 : 0); // normalize angles to 0-360 } function findBelow(fromItem, rects, dryRun) { var dest; var _iterator = _createForOfIteratorHelper(rects), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var item = _step.value; if (dryRun) DEBUG_TARGET(item, 0); if (item.element === fromItem.element) continue; var angle = findAngle(fromItem, item); // item.element.innerHTML = `${angle}º`; if (direction(angle) === 'down') { if (!dest) dest = item; if (dryRun) DEBUG_TARGET(item, 1); if (distance(fromItem, item) < distance(fromItem, dest)) { dest = item; } } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return dest; } function findAbove(fromItem, rects, dryRun) { var dest; var _iterator2 = _createForOfIteratorHelper(rects), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var item = _step2.value; if (dryRun) DEBUG_TARGET(item, 0); if (item.element === fromItem.element) continue; var angle = findAngle(fromItem, item); // item.element.innerHTML = `${angle}º`; if (direction(angle) === 'up') { if (!dest) dest = item; if (dryRun) DEBUG_TARGET(item, 1); if (distance(fromItem, item) < distance(fromItem, dest)) { dest = item; } } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return dest; } function findRight(fromItem, rects, dryRun) { var dest; var _iterator3 = _createForOfIteratorHelper(rects), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var item = _step3.value; if (dryRun) DEBUG_TARGET(item, 0); if (item.element === fromItem.element) continue; var angle = findAngle(fromItem, item); // item.element.innerHTML = `${angle}º`; if (direction(angle) === 'right') { if (!dest) dest = item; if (dryRun) DEBUG_TARGET(item, 1); if (distance(fromItem, item) < distance(fromItem, dest)) { dest = item; } } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } return dest; } function findLeft(fromItem, rects, dryRun) { var dest; var _iterator4 = _createForOfIteratorHelper(rects), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var item = _step4.value; if (dryRun) DEBUG_TARGET(item, 0); if (item.element === fromItem.element) continue; var angle = findAngle(fromItem, item); // item.element.innerHTML = `${angle}º`; if (direction(angle) === 'left') { if (!dest) dest = item; if (dryRun) DEBUG_TARGET(item, 1); if (distance(fromItem, item) < distance(fromItem, dest)) { dest = item; } } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } return dest; } //// function DEBUG_TARGET(item, x) { if (DEBUG) item.element.style.backgroundColor = x ? '#FF0' : '#333'; setTimeout(function () { return item.element.style.backgroundColor = ''; }, 1000 * 3); } function isEnabled(elm) { return !(elm.disabled !== undefined ? elm.disabled : elm.hasAttribute('disabled')); } function isFocusedOnInput() { var elm = document.activeElement; return elm && (elm.nodeName === 'INPUT' || elm.nodeName === 'TEXTAREA'); } var _focusPanelOnClickHandler = /*#__PURE__*/new WeakMap(); var _tabFocusFollowerHandler = /*#__PURE__*/new WeakMap(); var _navigationHandler = /*#__PURE__*/new WeakMap(); var ComponentFocusManager = /*#__PURE__*/function () { function ComponentFocusManager() { _classCallCheck(this, ComponentFocusManager); _defineProperty(this, "ACTIVE_CLASS", "focus"); _defineProperty(this, "COMPONENT_SELECTOR", "[data-shortcut-component]"); _defineProperty(this, "activeComponent", null); _classPrivateFieldInitSpec(this, _focusPanelOnClickHandler, { writable: true, value: null }); _classPrivateFieldInitSpec(this, _tabFocusFollowerHandler, { writable: true, value: null }); _classPrivateFieldInitSpec(this, _navigationHandler, { writable: true, value: null }); _classPrivateFieldSet(this, _focusPanelOnClickHandler, this._focusPanelOnClickHandler.bind(this)); _classPrivateFieldSet(this, _tabFocusFollowerHandler, this._tabFocusFollowerHandler.bind(this)); _classPrivateFieldSet(this, _navigationHandler, this._navigationHandler.bind(this)); // this.init(); } _createClass(ComponentFocusManager, [{ key: "init", value: function init() { document.addEventListener("click", _classPrivateFieldGet(this, _focusPanelOnClickHandler)); document.addEventListener("focusin", _classPrivateFieldGet(this, _tabFocusFollowerHandler)); document.addEventListener("keydown", _classPrivateFieldGet(this, _navigationHandler)); } }, { key: "destroy", value: function destroy() { document.removeEventListener('click', _classPrivateFieldGet(this, _focusPanelOnClickHandler)); document.removeEventListener('focusin', _classPrivateFieldGet(this, _tabFocusFollowerHandler)); document.removeEventListener('keydown', _classPrivateFieldGet(this, _navigationHandler)); } /** Focus a element when clicked */ }, { key: "_focusPanelOnClickHandler", value: function _focusPanelOnClickHandler(event) { if (DEBUG) console.log(PREFIX, 'focusPanelOnClickHandler', event); var clickedElm = event.target; this.focusComponentOfTargetElm(clickedElm); } /** Watch for focus changing via Tab */ }, { key: "_tabFocusFollowerHandler", value: function _tabFocusFollowerHandler(event) { if (DEBUG) console.log(PREFIX, 'tabFocusFollowerHandler', event); var focusedElm = document.activeElement; this.focusComponentOfTargetElm(focusedElm); } }, { key: "focusComponentOfTargetElm", value: function focusComponentOfTargetElm(targetElm) { var component = targetElm && targetElm.closest(this.COMPONENT_SELECTOR); if (component && isEnabled(component)) { if (component !== this.activeComponent) { this.setCurrentComponent(component); } } else { this.unselectCurrentComponent(); } return component; } }, { key: "unselectCurrentComponent", value: function unselectCurrentComponent() { if (this.activeComponent) { if (document.activeElement === this.activeComponent) this.activeComponent.blur(); this.blurElm(this.activeComponent); this.activeComponent = null; } } }, { key: "setCurrentComponent", value: function setCurrentComponent(elm) { if (this.activeComponent && this.activeComponent !== elm) this.unselectCurrentComponent(); this.activeComponent = elm; this.focusElm(this.activeComponent); this.activeComponent.setAttribute("tabindex", 0); // make focusable // focus the actual panel, unless there's something focused inside it if (!this.activeComponent.contains(document.activeElement)) { // TODO: optimize? this.activeComponent.focus(); } } }, { key: "focusElm", value: function focusElm(elm) { elm.classList.add(this.ACTIVE_CLASS); } }, { key: "blurElm", value: function blurElm(elm) { elm.classList.remove(this.ACTIVE_CLASS); } }, { key: "getActiveComponent", value: function getActiveComponent() { return this.activeComponent; } }, { key: "_navigationHandler", value: function _navigationHandler(event) { if (DEBUG) console.log(PREFIX, 'navigationHandler', event); if (isFocusedOnInput()) return; // do not navigate if focused on input var components = Array.from(document.querySelectorAll(this.COMPONENT_SELECTOR)); components = components.filter(isEnabled); var nextPanel; var currentPanel = this.getActiveComponent(); // TODO: handle which key moves in which direction should be handled outside the findNextItem function nextPanel = findNextItem(components, currentPanel, event); if (nextPanel && nextPanel !== currentPanel) { event.preventDefault(); event.stopPropagation(); this.setCurrentComponent(nextPanel); // nextPanel.scrollIntoView(); } } }]); return ComponentFocusManager; }(); /// --- shortcut handling var _isInit = /*#__PURE__*/new WeakMap(); var _shortcutDelegatorHandler = /*#__PURE__*/new WeakMap(); var Shortcuts = /*#__PURE__*/function () { function Shortcuts() { _classCallCheck(this, Shortcuts); _defineProperty(this, "KEYBIDING_ATTR", 'data-shortcut'); _defineProperty(this, "mngr", null); _classPrivateFieldInitSpec(this, _isInit, { writable: true, value: false }); _classPrivateFieldInitSpec(this, _shortcutDelegatorHandler, { writable: true, value: null }); _classPrivateFieldSet(this, _shortcutDelegatorHandler, this._shortcutDelegatorHandler.bind(this)); this.mngr = new ComponentFocusManager(); this.init(); } _createClass(Shortcuts, [{ key: "init", value: function init() { if (!_classPrivateFieldGet(this, _isInit)) { this.mngr.init(); // TODO: add support for holding keys keyup/keydown document.addEventListener('keydown', _classPrivateFieldGet(this, _shortcutDelegatorHandler)); _classPrivateFieldSet(this, _isInit, true); } else throw new Error("Already initialized!"); } }, { key: "destroy", value: function destroy() { this.mngr.destroy(); document.removeEventListener('keydown', _classPrivateFieldGet(this, _shortcutDelegatorHandler)); _classPrivateFieldSet(this, _isInit, false); } }, { key: "_shortcutDelegatorHandler", value: function _shortcutDelegatorHandler(event) { if (DEBUG) console.log(PREFIX, "_shortcutDelegatorHandler", event); var currentPanel = this.mngr.getActiveComponent(); shortcutDelegator(currentPanel, event, this.KEYBIDING_ATTR); } }, { key: "getShortcuts", value: function getShortcuts() { var _this = this; var currentPanel = this.mngr.getActiveComponent(); var elms = Array.from(currentPanel.querySelectorAll("[".concat(this.KEYBIDING_ATTR, "]"))).filter(function (elm) { return !elm.disabled; }); var shortcuts = elms.map(function (elm) { return { keyBinding: elm.getAttribute(_this.KEYBIDING_ATTR), text: elm.innerText, title: elm.title, elm: elm }; }); return shortcuts; } }, { key: "getAllShortcuts", value: function getAllShortcuts() { var _this2 = this; var elms = Array.from(document.querySelectorAll("[".concat(this.KEYBIDING_ATTR, "]"))).filter(function (elm) { return !elm.disabled; }); var shortcuts = elms.map(function (elm) { return { keyBinding: elm.getAttribute(_this2.KEYBIDING_ATTR), text: elm.innerText, title: elm.title, elm: elm }; }); return shortcuts; } }]); return Shortcuts; }(); /** * A hot key is a key combination that the user can press to perform an action quickly. * Global to the application, examples: save, undo, bring a menu on a game etc... */ var _handler = /*#__PURE__*/new WeakMap(); var Hotkeys = /*#__PURE__*/function () { function Hotkeys() { _classCallCheck(this, Hotkeys); _classPrivateFieldInitSpec(this, _handler, { writable: true, value: null }); this.init(); } _createClass(Hotkeys, [{ key: "init", value: function init() { if (_classPrivateFieldGet(this, _handler)) throw new Error("Already initialized!"); _classPrivateFieldSet(this, _handler, this._hotkeysDelegatorHandler.bind(this)); document.addEventListener('keydown', _classPrivateFieldGet(this, _handler)); } }, { key: "destroy", value: function destroy() { document.removeEventListener('keydown', _classPrivateFieldGet(this, _handler)); _classPrivateFieldSet(this, _handler, null); } }, { key: "_hotkeysDelegatorHandler", value: function _hotkeysDelegatorHandler(event) { if (DEBUG) console.log(PREFIX, "hotkeysDelegatorHandler", event); shortcutDelegator(document, event, Hotkeys.KEYBIDING_ATTR); } }]); return Hotkeys; }(); _defineProperty(Hotkeys, "KEYBIDING_ATTR", 'data-hotkey'); function announceHotkeys() { var elms = Array.from(document.querySelectorAll("[".concat(Hotkeys.KEYBIDING_ATTR, "]"))).filter(function (elm) { return !elm.disabled; }); var hotkeys = elms.map(function (elm) { return { keyBinding: elm.getAttribute(Hotkeys.KEYBIDING_ATTR), text: elm.innerText, title: elm.title, elm: elm }; }); return hotkeys; } var DIRECTIVE = 'tooltip'; /** * Creates the actual tooltip element, appends it to the document * at this point it is not visible or positioned. */ function createTooltipElm() { var doc = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; var style = "\n position: fixed;\n z-index: 9999999;\n pointer-events: none;\n will-change: top, left, opacity;\n --transition: opacity 250ms ease 100ms;\n font-size: 18px;\n font-family: sans-serif;\n padding: 5px 8px;\n border-radius: 2px;\n background: rgba(218,218,218, 0.85);\n color: rgb(0 0 0);\n box-shadow: rgb(0 0 0 / 20%) 0px 3px 1px -2px, rgb(0 0 0 / 14%) 0px 2px 2px 0px, rgb(0 0 0 / 12%) 0px 1px 5px 0px;\n font-weight: bold;\n min-width: 32px;\n text-align: center;\n text-transform: capitalize;\n top: 0;\n left: 0;\n opacity: 0;\n display: none;\n "; var tooltipElm = doc.createElement('div'); tooltipElm.setAttribute('class', "v-".concat(DIRECTIVE, "-directive")); tooltipElm.style.cssText = style; // ARIA https://www.w3.org/TR/wai-aria-practices-1.1/#tooltip tooltipElm.id = "id_".concat(Math.random()); tooltipElm.setAttribute('role', 'tooltip'); tooltipElm.setAttribute('aria-hidden', 'true'); return tooltipElm; } function positionTooltip(tooltipElement, targetElement) { var margin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; var doc = arguments.length > 3 ? arguments[3] : undefined; // The tooltip is already in the document, but not visible. // It needs to be be a block, and be inside the screen for it to have dimensions. // Since we reuse tooltips, if the content changes, and it overflows off-screen, // the computed styles will not match when placed in the the real posision. if (tooltipElement.style.display === 'none') { tooltipElement.style.opacity = 0; tooltipElement.style.display = 'block'; tooltipElement.style.top = "".concat(0, "px"); tooltipElement.style.left = "".concat(0, "px"); } // Get dimensions and positioning of target and tooltip var tooltipRect = tooltipElement.getBoundingClientRect(); var targetRect = targetElement.getBoundingClientRect(); // set tooltip on top center of target var y = targetRect.top - margin - tooltipRect.height; var x = targetRect.left + targetRect.width / 2 - tooltipRect.width / 2; // is tooltip above screen? move it below the target if (y < 0) y = targetRect.bottom + margin; // is tooltip-left-side off screen? align with target-left-side if (x < 0) x = targetRect.left; // is tooltip-right-side off screen? align with target-right-side var vw = Math.max(doc.documentElement.clientWidth || 0, window.innerWidth || 0); if (x + tooltipRect.width > vw) x = targetRect.right - tooltipRect.width; // place the tooltip position to the x,y calculated tooltipElement.style.left = "".concat(x, "px"); tooltipElement.style.top = "".concat(y, "px"); } var Tooltip = /*#__PURE__*/function () { function Tooltip(_ref) { var target = _ref.target, html = _ref.html, _ref$margin = _ref.margin, margin = _ref$margin === void 0 ? 5 : _ref$margin, _ref$root = _ref.root, root = _ref$root === void 0 ? document.body : _ref$root; _classCallCheck(this, Tooltip); this.margin = margin; this.target = target; this.el = createTooltipElm(this.target.ownerDocument); if (typeof root === 'string') root = document.querySelector(root); root.appendChild(this.el); // ARIA https://www.w3.org/TR/wai-aria-practices-1.1/#tooltip this.target.setAttribute('aria-describedby', this.el.id); this.el.setAttribute('aria-hidden', 'false'); this.update(html); } _createClass(Tooltip, [{ key: "update", value: function update(html) { this.el.innerHTML = html; if (this.target && this.target.parentNode) { positionTooltip(this.el, this.target, this.margin, this.el.ownerDocument); this.el.style.opacity = 1; } } }, { key: "destroy", value: function destroy() { if (this.target && this.target.parentNode) { this.target.removeAttribute('aria-describedby'); } if (this.el.parentElement) this.el.parentElement.removeChild(this.el); } }]); return Tooltip; }(); /*! * FooBar v0.0.0 (https://github.com/username/foo-bar) * Copyright (c) username * @license MIT */ function findHighestZIndex() { var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '*'; var max = null; var elems = document.getElementsByTagName(tagName); var _iterator = _createForOfIteratorHelper(elems), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var elm = _step.value; var z = document.defaultView.getComputedStyle(elm, null).getPropertyValue('z-index'); if (z !== 'auto') { z = parseInt(z, 10); if (z > max || max === null) max = z; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return max; } var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var penner = createCommonjsModule(function (module, exports) { /* Copyright © 2001 Robert Penner All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ (function () { var penner, umd; umd = function umd(factory) { { return module.exports = factory; } }; penner = { linear: function linear(t, b, c, d) { return c * t / d + b; }, easeInQuad: function easeInQuad(t, b, c, d) { return c * (t /= d) * t + b; }, easeOutQuad: function easeOutQuad(t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, easeInOutQuad: function easeInOutQuad(t, b, c, d) { if ((t /= d / 2) < 1) { return c / 2 * t * t + b; } else { return -c / 2 * (--t * (t - 2) - 1) + b; } }, easeInCubic: function easeInCubic(t, b, c, d) { return c * (t /= d) * t * t + b; }, easeOutCubic: function easeOutCubic(t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; }, easeInOutCubic: function easeInOutCubic(t, b, c, d) { if ((t /= d / 2) < 1) { return c / 2 * t * t * t + b; } else { return c / 2 * ((t -= 2) * t * t + 2) + b; } }, easeInQuart: function easeInQuart(t, b, c, d) { return c * (t /= d) * t * t * t + b; }, easeOutQuart: function easeOutQuart(t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeInOutQuart: function easeInOutQuart(t, b, c, d) { if ((t /= d / 2) < 1) { return c / 2 * t * t * t * t + b; } else { return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } }, easeInQuint: function easeInQuint(t, b, c, d) { return c * (t /= d) * t * t * t * t + b; }, easeOutQuint: function easeOutQuint(t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; }, easeInOutQuint: function easeInOutQuint(t, b, c, d) { if ((t /= d / 2) < 1) { return c / 2 * t * t * t * t * t + b; } else { return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } }, easeInSine: function easeInSine(t, b, c, d) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; }, easeOutSine: function easeOutSine(t, b, c, d) { return c * Math.sin(t / d * (Math.PI / 2)) + b; }, easeInOutSine: function easeInOutSine(t, b, c, d) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; }, easeInExpo: function easeInExpo(t, b, c, d) { if (t === 0) { return b; } else { return c * Math.pow(2, 10 * (t / d - 1)) + b; } }, easeOutExpo: function easeOutExpo(t, b, c, d) { if (t === d) { return b + c; } else { return c * (-Math.pow(2, -10 * t / d) + 1) + b; } }, easeInOutExpo: function easeInOutExpo(t, b, c, d) { if ((t /= d / 2) < 1) { return c / 2 * Math.pow(2, 10 * (t - 1)) + b; } else { return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } }, easeInCirc: function easeInCirc(t, b, c, d) { return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; }, easeOutCirc: function easeOutCirc(t, b, c, d) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; }, easeInOutCirc: function easeInOutCirc(t, b, c, d) { if ((t /= d / 2) < 1) { return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; } else { return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } }, easeInElastic: function easeInElastic(t, b, c, d) { var a, p, s; s = 1.70158; p = 0; a = c; if (t === 0) ;else if ((t /= d) === 1) ; if (!p) { p = d * .3; } if (a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; }, easeOutElastic: function easeOutElastic(t, b, c, d) { var a, p, s; s = 1.70158; p = 0; a = c; if (t === 0) ;else if ((t /= d) === 1) ; if (!p) { p = d * .3; } if (a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; }, easeInOutElastic: function easeInOutElastic(t, b, c, d) { var a, p, s; s = 1.70158; p = 0; a = c; if (t === 0) ;else if ((t /= d / 2) === 2) ; if (!p) { p = d * (.3 * 1.5); } if (a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } if (t < 1) { return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; } else { return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b; } }, easeInBack: function easeInBack(t, b, c, d, s) { if (s === void 0) { s = 1.70158; } return c * (t /= d) * t * ((s + 1) * t - s) + b; }, easeOutBack: function easeOutBack(t, b, c, d, s) { if (s === void 0) { s = 1.70158; } return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; }, easeInOutBack: function easeInOutBack(t, b, c, d, s) { if (s === void 0) { s = 1.70158; } if ((t /= d / 2) < 1) { return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b; } else { return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; } }, easeInBounce: function easeInBounce(t, b, c, d) { var v; v = penner.easeOutBounce(d - t, 0, c, d); return c - v + b; }, easeOutBounce: function easeOutBounce(t, b, c, d) { if ((t /= d) < 1 / 2.75) { return c * (7.5625 * t * t) + b; } else if (t < 2 / 2.75) { return c * (7.5625 * (t -= 1.5 / 2.75) * t + .75) + b; } else if (t < 2.5 / 2.75) { return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b; } else { return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b; } }, easeInOutBounce: function easeInOutBounce(t, b, c, d) { var v; if (t < d / 2) { v = penner.easeInBounce(t * 2, 0, c, d); return v * .5 + b; } else { v = penner.easeOutBounce(t * 2 - d, 0, c, d); return v * .5 + c * .5 + b; } } }; umd(penner); }).call(commonjsGlobal); }); /** * @function shorthand * @param {Number|Number[]} values A number, or Array of 4, 3, 2 or 1 Values * @return {[Number, Number, Number, Number]} Array of values [T, R, B, L] */ function shorthand() { var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var b = arguments.length > 1 ? arguments[1] : undefined; var c = arguments.length > 2 ? arguments[2] : undefined; var d = arguments.length > 3 ? arguments[3] : undefined; var l = arguments.length; if (Array.isArray(a)) { l = a.length; var _a = a; var _a2 = _slicedToArray(_a, 4); a = _a2[0]; b = _a2[1]; c = _a2[2]; d = _a2[3]; } if (l === 1) return [a, a, a, a]; if (l === 2) return [a, b, a, b]; if (l === 3) return [a, b, c, b]; if (l === 4) return [a, b, c, d]; return [0, 0, 0, 0]; } /** * Draws a rounded rectangle on a CanvasRenderingContext2D * @param {CanvasRenderingContext2D} context * @param {Number} x The x-axis coordinate of the rectangle's starting point. * @param {Number} y The y-axis coordinate of the rectangle's starting point. * @param {Number} width The rectangle's width. Positive values are to the right, and negative to the left. * @param {Number} height The rectangle's height. Positive values are down, and negative are up. * @param {Number|Number[]} [radius=5] Border radius; You can specify borders indivudially by passing an array containing 4 values [TL, TR, BR, BL] or 3 Values [TL, TR_BL, BR] or 2 Values [TL_BR, TR_BL]. */ function roundRect(context, x, y, width, height) { var radius = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 5; function constraintRadius(radius, width, height) { var limit = (width < height ? width : height) / 2; radius[0] = radius[0] > limit ? limit : radius[0]; radius[1] = radius[1] > limit ? limit : radius[1]; radius[2] = radius[2] > limit ? limit : radius[2]; radius[3] = radius[3] > limit ? limit : radius[3]; return radius; } radius = shorthand(radius); radius = constraintRadius(radius, width, height); context.beginPath(); context.moveTo(x + radius[0], y); context.lineTo(x + width - radius[1], y); context.quadraticCurveTo(x + width, y, x + width, y + radius[1]); context.lineTo(x + width, y + height - radius[2]); context.quadraticCurveTo(x + width, y + height, x + width - radius[2], y + height); context.lineTo(x + radius[3], y + height); context.quadraticCurveTo(x, y + height, x, y + height - radius[3]); context.lineTo(x, y + radius[0]); context.quadraticCurveTo(x, y, x + radius[0], y); context.closePath(); } var Spot = /*#__PURE__*/function () { function Spot(stage) { _classCallCheck(this, Spot); _defineProperty(this, "type", ''); _defineProperty(this, "_stage", null); this._stage = stage; } _createClass(Spot, [{ key: "draw", value: function draw(ctx) { throw 'Not implemented'; } }, { key: "remove", value: function remove() { if (this._stage) { this._stage.removeSpot(this); this._stage = null; } } }]); return Spot; }(); var SpotCircle = /*#__PURE__*/function (_Spot) { _inherits(SpotCircle, _Spot); var _super = _createSuper(SpotCircle); function SpotCircle(stage, x, y, radius) { var _this2; _classCallCheck(this, SpotCircle); _this2 = _super.call(this, stage); _defineProperty(_assertThisInitialized(_this2), "type", 'circle'); _defineProperty(_assertThisInitialized(_this2), "x", 0); _defineProperty(_assertThisInitialized(_this2), "y", 0); _defineProperty(_assertThisInitialized(_this2), "radius", 0); _this2.x = x; _this2.y = y; _this2.radius = radius; return _this2; } _createClass(SpotCircle, [{ key: "draw", value: function draw(ctx) { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fill(); } }]); return SpotCircle; }(Spot); var SpotRetangle = /*#__PURE__*/function (_Spot2) { _inherits(SpotRetangle, _Spot2); var _super2 = _createSuper(SpotRetangle); function SpotRetangle(stage, x, y, width, height) { var _this3; _classCallCheck(this, SpotRetangle); _this3 = _super2.call(this, stage); _defineProperty(_assertThisInitialized(_this3), "type", 'retangle'); _defineProperty(_assertThisInitialized(_this3), "x", 0); _defineProperty(_assertThisInitialized(_this3), "y", 0); _defineProperty(_assertThisInitialized(_this3), "width", 0); _defineProperty(_assertThisInitialized(_this3), "height", 0); _this3.x = x; _this3.y = y; _this3.width = width; _this3.height = height; return _this3; } _createClass(SpotRetangle, [{ key: "draw", value: function draw(ctx) { ctx.beginPath(); ctx.fillRect(this.x, this.y, this.width, this.height); } }]); return SpotRetangle; }(Spot); var SpotRoundRetangle = /*#__PURE__*/function (_Spot3) { _inherits(SpotRoundRetangle, _Spot3); var _super3 = _createSuper(SpotRoundRetangle); function SpotRoundRetangle(stage, x, y, width, height, radius) { var _this4; _classCallCheck(this, SpotRoundRetangle); _this4 = _super3.call(this, stage); _defineProperty(_assertThisInitialized(_this4), "type", 'round-retangle'); _defineProperty(_assertThisInitialized(_this4), "x", 0); _defineProperty(_assertThisInitialized(_this4), "y", 0); _defineProperty(_assertThisInitialized(_this4), "width", 0); _defineProperty(_assertThisInitialized(_this4), "height", 0); _defineProperty(_assertThisInitialized(_this4), "radius", 5); _this4.x = x; _this4.y = y; _this4.width = width; _this4.height = height; _this4.radius = radius; return _this4; } _createClass(SpotRoundRetangle, [{ key: "draw", value: function draw(ctx) { ctx.beginPath(); roundRect(ctx, this.x, this.y, this.width, this.height, this.radius); ctx.fill(); } }]); return SpotRoundRetangle; }(Spot); var SpotPolygon = /*#__PURE__*/function (_Spot4) { _inherits(SpotPolygon, _Spot4); var _super4 = _createSuper(SpotPolygon); function SpotPolygon(stage, points) { var _this5; _classCallCheck(this, SpotPolygon); _this5 = _super4.call(this, stage); _defineProperty(_assertThisInitialized(_this5), "type", 'polygon'); _defineProperty(_assertThisInitialized(_this5), "points", []); _this5.points = points; return _this5; } _createClass(SpotPolygon, [{ key: "draw", value: function draw(ctx) { ctx.beginPath(); ctx.moveTo(this.points[0], this.points[1]); for (var i = 2; i < this.points.length; i += 2) { ctx.lineTo(this.points[i], this.points[i + 1]); } ctx.closePath(); ctx.fill(); } }]); return SpotPolygon; }(Spot); var SpotElement = /*#__PURE__*/function (_Spot5) { _inherits(SpotElement, _Spot5); var _super5 = _createSuper(SpotElement); function SpotElement(stage, element, options) { var _this6; _classCallCheck(this, SpotElement); _this6 = _super5.call(this, stage); _defineProperty(_assertThisInitialized(_this6), "type", 'element'); _defineProperty(_assertThisInitialized(_this6), "elm", HTMLElement); _defineProperty(_assertThisInitialized(_this6), "radius", 0); _defineProperty(_assertThisInitialized(_this6), "padding", 0); _this6.elm = element; _this6.radius = options.radius; _this6.padding = options.padding; return _this6; } _createClass(SpotElement, [{ key: "draw", value: function draw(ctx) { var rect = this.elm.getBoundingClientRect(); var padding = shorthand(this.padding); var x = rect.left - padding[3]; var y = rect.top - padding[0]; var h = rect.height + padding[0] + padding[2]; var w = rect.width + padding[3] + padding[1]; ctx.beginPath(); if (this.radius) { roundRect(ctx, x, y, w, h, this.radius); ctx.fill(); } else { ctx.fillRect(x, y, w, h); } } }]); return SpotElement; }(Spot); /** * spotlight-canvas: a canvas element that dims the screen except for spotlight locations formed by circles or polygons */ var _resizeHandler = /*#__PURE__*/new WeakMap(); var _scrollHandler = /*#__PURE__*/new WeakMap(); var _nextTick = /*#__PURE__*/new WeakMap(); var SpotlightStage = /*#__PURE__*/function () { /** * create a spotlight div * @param {object} [options] * @param {number} [options.x=0] use to place layer on creation * @param {number} [options.y=0] * @param {number} [options.width=window.innerWidth] * @param {number} [options.height=window.innerHeight] * @param {number} [options.color=black] color of under layer * @param {number} [options.alpha=0.8] alpha of under layer * @param {number} [options.zIndex] zIndex of under layer * @param {number} [options.forceSyncRedraw] force redrawing after each syncronous operation * @param {HTMLElement} [options.parent=document.body] parent of spotlight layer */ function SpotlightStage(options) { var _this7 = this; _classCallCheck(this, SpotlightStage); _defineProperty(this, "options", {}); _defineProperty(this, "canvas", undefined); _defineProperty(this, "spots", []); _defineProperty(this, "looping", false); _classPrivateFieldInitSpec(this, _resizeHandler, { writable: true, value: undefined }); _classPrivateFieldInitSpec(this, _scrollHandler, { writable: true, value: undefined }); _classPrivateFieldInitSpec(this, _nextTick, { writable: true, value: undefined }); this.options = options || {}; /** * @type