UNPKG

tau-tooltip

Version:
714 lines (644 loc) 18.4 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof define === 'function' && define.amd) define([], factory); else if(typeof module === "object" && module.exports) module.exports = factory(); else if(typeof exports === 'object') exports["Tooltip"] = factory(); else root["Tooltip"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var classes = function classes(el) { return { add: function add(name) { el.classList.add(name); }, remove: function remove(name) { el.classList.remove(name); } }; }; var indexOf = function indexOf(arr, obj) { return arr.indexOf(obj); }; /** * Globals. */ var win = window; var doc = win.document; var docEl = doc.documentElement; var verticalPlaces = ['top', 'bottom']; /** * Poor man's shallow object extend. * * @param {Object} a * @param {Object} b * * @return {Object} */ function extend(a, b) { for (var key in b) { // jshint ignore:line a[key] = b[key]; } return a; } /** * Checks whether object is window. * * @param {Object} obj * * @return {Boolean} */ function isWin(obj) { return obj && obj.setInterval != null; } /** * Returns element's object with `left`, `top`, `bottom`, `right`, `width`, and `height` * properties indicating the position and dimensions of element on a page. * * @param {Element} element * * @return {Object} */ function position(element) { var winTop = win.pageYOffset || docEl.scrollTop; var winLeft = win.pageXOffset || docEl.scrollLeft; var box = { left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0 }; if (isWin(element)) { box.width = win.innerWidth || docEl.clientWidth; box.height = win.innerHeight || docEl.clientHeight; } else if (docEl.contains(element) && element.getBoundingClientRect != null) { extend(box, element.getBoundingClientRect()); // width & height don't exist in <IE9 box.width = box.right - box.left; box.height = box.bottom - box.top; } else { return box; } box.top = box.top + winTop - docEl.clientTop; box.left = box.left + winLeft - docEl.clientLeft; box.right = box.left + box.width; box.bottom = box.top + box.height; return box; } /** * Parse integer from strings like '-50px'. * * @param {Mixed} value * * @return {Integer} */ function parsePx(value) { return 0 | Math.round(String(value).replace(/[^\-0-9.]/g, '')); } /** * Get computed style of element. * * @param {Element} element * * @type {String} */ var style = function style(element, property) { return win.getComputedStyle(element)[property]; }; /** * Returns transition duration of element in ms. * * @param {Element} element * * @return {Integer} */ function transitionDuration(element) { var duration = String(style(element, transitionDuration.propName)); var match = duration.match(/([0-9.]+)([ms]{1,2})/); if (match) { duration = Number(match[1]); if (match[2] === 's') { duration *= 1000; } } return 0 | duration; } transitionDuration.propName = (function () { var element = doc.createElement('div'); var names = ['transitionDuration', 'webkitTransitionDuration']; var value = '1s'; for (var i = 0; i < names.length; i++) { element.style[names[i]] = value; if (element.style[names[i]] === value) { return names[i]; } } })(); var objectCreate = Object.create; /** * Tooltip construnctor. * * @param {String|Element} content * @param {Object} options * * @return {Tooltip} */ function Tooltip(content, options) { if (!(this instanceof Tooltip)) { return new Tooltip(content, options); } this.hidden = 1; this.options = extend(objectCreate(Tooltip.defaults), options); this._createElement(); if (content) { this.content(content); } } /** * Creates a tooltip element. * * @return {Void} */ Tooltip.prototype._createElement = function () { this.element = doc.createElement('div'); this.classes = classes(this.element); this.classes.add(this.options.baseClass); var propName; for (var i = 0; i < Tooltip.classTypes.length; i++) { propName = Tooltip.classTypes[i] + 'Class'; if (this.options[propName]) { this.classes.add(this.options[propName]); } } }; /** * Changes tooltip's type class type. * * @param {String} name * * @return {Tooltip} */ Tooltip.prototype.type = function (name) { return this.changeClassType('type', name); }; /** * Changes tooltip's effect class type. * * @param {String} name * * @return {Tooltip} */ Tooltip.prototype.effect = function (name) { return this.changeClassType('effect', name); }; /** * Changes class type. * * @param {String} propName * @param {String} newClass * * @return {Tooltip} */ Tooltip.prototype.changeClassType = function (propName, newClass) { propName += 'Class'; if (this.options[propName]) { this.classes.remove(this.options[propName]); } this.options[propName] = newClass; if (newClass) { this.classes.add(newClass); } return this; }; /** * Updates tooltip's dimensions. * * @return {Tooltip} */ Tooltip.prototype.updateSize = function () { if (this.hidden) { this.element.style.visibility = 'hidden'; doc.body.appendChild(this.element); } this.width = this.element.offsetWidth; this.height = this.element.offsetHeight; if (this.spacing == null) { this.spacing = this.options.spacing != null ? this.options.spacing : parsePx(style(this.element, 'top')); } if (this.hidden) { doc.body.removeChild(this.element); this.element.style.visibility = ''; } else { this.position(); } return this; }; /** * Change tooltip content. * * When tooltip is visible, its size is automatically * synced and tooltip correctly repositioned. * * @param {String|Element} content * * @return {Tooltip} */ Tooltip.prototype.content = function (content) { if (typeof content === 'object') { this.element.innerHTML = ''; this.element.appendChild(content); } else { this.element.innerHTML = content; } this.updateSize(); return this; }; /** * Pick new place tooltip should be displayed at. * * When the tooltip is visible, it is automatically positioned there. * * @param {String} place * * @return {Tooltip} */ Tooltip.prototype.place = function (place) { this.options.place = place; if (!this.hidden) { this.position(); } return this; }; /** * Attach tooltip to an element. * * @param {Element} element * * @return {Tooltip} */ Tooltip.prototype.attach = function (element) { this.attachedTo = element; if (!this.hidden) { this.position(); } return this; }; /** * Detach tooltip from element. * * @return {Tooltip} */ Tooltip.prototype.detach = function () { this.hide(); this.attachedTo = null; return this; }; /** * Pick the most reasonable place for target position. * * @param {Object} target * * @return {Tooltip} */ Tooltip.prototype._pickPlace = function (target) { if (!this.options.auto) { return this.options.place; } var winPos = position(win); var place = this.options.place.split('-'); var spacing = this.spacing; if (indexOf(verticalPlaces, place[0]) !== -1) { if (target.top - this.height - spacing <= winPos.top) { place[0] = 'bottom'; } else if (target.bottom + this.height + spacing >= winPos.bottom) { place[0] = 'top'; } switch (place[1]) { case 'left': if (target.right - this.width <= winPos.left) { place[1] = 'right'; } break; case 'right': if (target.left + this.width >= winPos.right) { place[1] = 'left'; } break; default: if (target.left + target.width / 2 + this.width / 2 >= winPos.right) { place[1] = 'left'; } else if (target.right - target.width / 2 - this.width / 2 <= winPos.left) { place[1] = 'right'; } } } else { if (target.left - this.width - spacing <= winPos.left) { place[0] = 'right'; } else if (target.right + this.width + spacing >= winPos.right) { place[0] = 'left'; } switch (place[1]) { case 'top': if (target.bottom - this.height <= winPos.top) { place[1] = 'bottom'; } break; case 'bottom': if (target.top + this.height >= winPos.bottom) { place[1] = 'top'; } break; default: if (target.top + target.height / 2 + this.height / 2 >= winPos.bottom) { place[1] = 'top'; } else if (target.bottom - target.height / 2 - this.height / 2 <= winPos.top) { place[1] = 'bottom'; } } } return place.join('-'); }; /** * Position the element to an element or a specific coordinates. * * @param {Integer|Element} x * @param {Integer} y * * @return {Tooltip} */ Tooltip.prototype.position = function (x, y) { if (this.attachedTo) { x = this.attachedTo; } if (x == null && this._p) { x = this._p[0]; y = this._p[1]; } else { this._p = arguments; } var target = typeof x === 'number' ? { left: 0 | x, right: 0 | x, top: 0 | y, bottom: 0 | y, width: 0, height: 0 } : position(x); var spacing = this.spacing; var newPlace = this._pickPlace(target); // Add/Change place class when necessary if (newPlace !== this.curPlace) { if (this.curPlace) { this.classes.remove(this.curPlace); } this.classes.add(newPlace); this.curPlace = newPlace; } // Position the tip var top, left; switch (this.curPlace) { case 'top': top = target.top - this.height - spacing; left = target.left + target.width / 2 - this.width / 2; break; case 'top-left': top = target.top - this.height - spacing; left = target.right - this.width; break; case 'top-right': top = target.top - this.height - spacing; left = target.left; break; case 'bottom': top = target.bottom + spacing; left = target.left + target.width / 2 - this.width / 2; break; case 'bottom-left': top = target.bottom + spacing; left = target.right - this.width; break; case 'bottom-right': top = target.bottom + spacing; left = target.left; break; case 'left': top = target.top + target.height / 2 - this.height / 2; left = target.left - this.width - spacing; break; case 'left-top': top = target.bottom - this.height; left = target.left - this.width - spacing; break; case 'left-bottom': top = target.top; left = target.left - this.width - spacing; break; case 'right': top = target.top + target.height / 2 - this.height / 2; left = target.right + spacing; break; case 'right-top': top = target.bottom - this.height; left = target.right + spacing; break; case 'right-bottom': top = target.top; left = target.right + spacing; break; } // Set tip position & class this.element.style.top = Math.round(top) + 'px'; this.element.style.left = Math.round(left) + 'px'; return this; }; /** * Show the tooltip. * * @param {Integer|Element} x * @param {Integer} y * * @return {Tooltip} */ Tooltip.prototype.show = function (x, y) { x = this.attachedTo ? this.attachedTo : x; // Clear potential ongoing animation clearTimeout(this.aIndex); // Position the element when requested if (x != null) { this.position(x, y); } // Stop here if tip is already visible if (this.hidden) { this.hidden = 0; doc.body.appendChild(this.element); } // Make tooltip aware of window resize if (this.attachedTo) { this._aware(); } // Trigger layout and kick in the transition if (this.options.inClass) { if (this.options.effectClass) { void this.element.clientHeight; } this.classes.add(this.options.inClass); } return this; }; Tooltip.prototype.getElement = function () { return this.element; }; /** * Hide the tooltip. * * @return {Tooltip} */ Tooltip.prototype.hide = function () { if (this.hidden) { return; } var self = this; var duration = 0; // Remove .in class and calculate transition duration if any if (this.options.inClass) { this.classes.remove(this.options.inClass); if (this.options.effectClass) { duration = transitionDuration(this.element); } } // Remove tip from window resize awareness if (this.attachedTo) { this._unaware(); } // Remove the tip from the DOM when transition is done clearTimeout(this.aIndex); this.aIndex = setTimeout(function () { self.aIndex = 0; doc.body.removeChild(self.element); self.hidden = 1; }, duration); return this; }; Tooltip.prototype.toggle = function (x, y) { return this[this.hidden ? 'show' : 'hide'](x, y); }; Tooltip.prototype.destroy = function () { clearTimeout(this.aIndex); this._unaware(); if (!this.hidden) { doc.body.removeChild(this.element); } this.element = this.options = null; }; /** * git remote add origin https://github.com/TargetProcess/tau-tooltip.git. * * @return {Void} */ Tooltip.prototype._aware = function () { var index = indexOf(Tooltip.winAware, this); if (index === -1) { Tooltip.winAware.push(this); } }; /** * Remove the window resize awareness. * * @return {Void} */ Tooltip.prototype._unaware = function () { var index = indexOf(Tooltip.winAware, this); if (index !== -1) { Tooltip.winAware.splice(index, 1); } }; /** * Handles repositioning of tooltips on window resize. * * @return {Void} */ Tooltip.reposition = (function () { var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (fn) { return setTimeout(fn, 17); }; var rIndex; function requestReposition() { if (rIndex || !Tooltip.winAware.length) { return; } rIndex = rAF(reposition); } function reposition() { rIndex = 0; var tip; for (var i = 0, l = Tooltip.winAware.length; i < l; i++) { tip = Tooltip.winAware[i]; tip.position(); } } return requestReposition; })(); Tooltip.winAware = []; // Bind winAware repositioning to window resize event window.addEventListener('resize', Tooltip.reposition); window.addEventListener('scroll', Tooltip.reposition); /** * Array with dynamic class types. * * @type {Array} */ Tooltip.classTypes = ['type', 'effect']; /** * Default options for Tooltip constructor. * * @type {Object} */ Tooltip.defaults = { baseClass: 'tooltip', // Base tooltip class name. typeClass: null, // Type tooltip class name. effectClass: null, // Effect tooltip class name. inClass: 'in', // Class used to transition stuff in. place: 'top', // Default place. spacing: null, // Gap between target and tooltip. auto: 0 // Whether to automatically adjust place to fit into window. }; exports.Tooltip = Tooltip; /***/ }) /******/ ]) }); ;