UNPKG

jspanel4

Version:

A JavaScript library to create highly configurable multifunctional floating panels that can also be used as modal, tooltip, hint or contextmenu

330 lines (287 loc) 14.7 kB
/** * jsPanel - A JavaScript library to create highly configurable multifunctional floating panels that can also be used as modal, tooltip, hint or contextmenu * @version v4.16.1 * @homepage https://jspanel.de/ * @license MIT * @author Stefan Sträßer - info@jspanel.de * @author of dialog extension: Michael Daumling - michael@terrapinlogo.com * @github https://github.com/Flyer53/jsPanel4.git */ import {jsPanel} from '../../jspanel.js'; // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent#Polyfill - needed for IE11 (function (window) { try { new MouseEvent('test'); return false; // No need to polyfill } catch (e) { // Need to polyfill - fall through } // Polyfills DOM4 MouseEvent var MouseEvent = function (eventType, params) { params = params || { bubbles: false, cancelable: false }; var mouseEvent = document.createEvent('MouseEvent'); mouseEvent.initMouseEvent(eventType, params.bubbles, params.cancelable, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); return mouseEvent; }; MouseEvent.prototype = Event.prototype; window.MouseEvent = MouseEvent; })(window); // ----------------------------------------------------------- if (!jsPanel.tooltip) { jsPanel.tooltip = { version: '1.4.0', date: '2021-03-13 11:20', defaults: { //tip.options.position: is set in jsPanel.tooltip.create() border: '1px', dragit: false, resizeit: false, headerControls: 'none', delay: 400, ttipEvent: 'mouseenter', ttipName: 'default', parentPanelFront: false }, create(options = {}, callback) { options.paneltype = 'tooltip'; if (!options.id) { options.id = `jsPanel-${jsPanel.idCounter += 1}`; } else if (typeof options.id === 'function') { options.id = options.id(); } let target = options.target, mode = options.mode || 'default', timer; if (typeof target === 'string') { target = document.querySelector(target); } if (!target) { try {throw new jsPanel.jsPanelError('TOOLTIP SETUP FAILED!<br>Either option target is missing in the tooltip configuration or the target does nor exist in the document!');} catch (e) {jsPanel.error(e);} return false; } // don't close tooltip or contextmenu on mousedown in target jsPanel.pointerdown.forEach(function (evt) { target.addEventListener(evt, e => { e.stopPropagation(); }, false); }); let opts = options; if (options.config) { opts = Object.assign({}, options.config, options); delete opts.config; } opts = Object.assign({}, jsPanel.tooltip.defaults, opts); opts.position = Object.assign({}, options.position); opts.position.of = options.position.of || target; target[opts.ttipName] = () => { timer = window.setTimeout(function () { // do nothing if id already exists in document if (document.getElementById(options.id)) { return false; } jsPanel.create(opts, function (panel) { const tipToClose = panel, closeTip = () => { tipToClose.close(); target.removeEventListener('mouseleave', closeTip); panel.removeEventListener('mouseleave', closeTip); }; // by default tooltip will close when mouse leaves trigger if (mode === 'default') { target.addEventListener('mouseleave', closeTip, false); } else if (mode === 'semisticky') { // close tooltip when mouse leaves tooltip panel.addEventListener('mouseleave', closeTip, false); } // some more tooltip specifics panel.classList.add('jsPanel-tooltip'); panel.style.overflow = 'visible'; panel.header.style.cursor = 'default'; panel.footer.style.cursor = 'default'; // check whether tooltip is triggered from within a modal panel or panel and if so update z-index if (target.closest('.jsPanel-modal')) { panel.style.zIndex = target.closest('.jsPanel-modal').style.zIndex; } else { if (target.closest('.jsPanel') && opts.parentPanelFront) { target.closest('.jsPanel').front(); } panel.style.zIndex = jsPanel.zi.next(); } // do not use 'click' instead of jsPanel.pointerdown jsPanel.pointerdown.forEach(function (evt) { panel.addEventListener(evt, (e) => { e.stopPropagation(); }, false); }); // add tooltip connector if (opts.connector) { let tipPos = jsPanel.tooltip.relativeTipPos(panel.options.position); if (tipPos !== 'over') { panel.append(jsPanel.tooltip.addConnector(panel, tipPos)); } } if (callback) {callback.call(panel, panel);} }); }, opts.delay); }; target.addEventListener(opts.ttipEvent, target[opts.ttipName], false); // immediately show tooltip if (opts.autoshow) { let event = new MouseEvent('mouseenter'); target.dispatchEvent(event); } // do not create tooltip if mouse leaves target before delay elapsed target.addEventListener('mouseleave', () => { window.clearTimeout(timer); }, false); }, relativeTipPos(position) { // returns the basic tip.options.position of the tooltip relative to option.tip.options.position.of (top, right, right-bottom etc.) let relPos; // TODO: relative positions leave out a few positions if (position.my === 'center-bottom' && position.at === 'center-top') { relPos = 'top'; } else if (position.my === 'left-center' && position.at === 'right-center') { relPos = 'right'; } else if (position.my === 'center-top' && position.at === 'center-bottom') { relPos = 'bottom'; } else if (position.my === 'right-center' && position.at === 'left-center') { relPos = 'left'; } else if (position.my === 'right-bottom' && position.at === 'left-top') { relPos = 'left-top-corner'; } else if (position.my === 'left-bottom' && position.at === 'right-right') { relPos = 'right-top-corner'; } else if (position.my === 'left-top' && position.at === 'right-bottom') { relPos = 'right-bottom-corner'; } else if (position.my === 'right-top' && position.at === 'left-bottom') { relPos = 'left-bottom-corner'; } else if (position.my === 'left-bottom' && position.at === 'left-top') { relPos = 'topleft'; } else if (position.my === 'right-bottom' && position.at === 'right-top') { relPos = 'topright'; } else if (position.my === 'left-top' && position.at === 'right-top') { relPos = 'righttop'; } else if (position.my === 'left-bottom' && position.at === 'right-bottom') { relPos = 'rightbottom'; } else if (position.my === 'right-top' && position.at === 'right-bottom') { relPos = 'bottomright'; } else if (position.my === 'left-top' && position.at === 'left-bottom') { relPos = 'bottomleft'; } else if (position.my === 'right-bottom' && position.at === 'left-bottom') { relPos = 'leftbottom'; } else if (position.my === 'right-top' && position.at === 'left-top') { relPos = 'lefttop'; } else {relPos = 'over';} return relPos; }, addConnector(tip, relposition) { let left, top, connCSS, connBg, conn = document.createElement('div'); conn.className = `jsPanel-connector jsPanel-connector-${relposition}`; // rest of tooltip positioning is in jspanel.sass if (relposition === 'top' || relposition === 'topleft' || relposition === 'topright') { tip.style.top = `calc(${tip.style.top} - 12px)`; } else if (relposition === 'right' || relposition === 'righttop' || relposition === 'rightbottom') { tip.style.left = `calc(${tip.style.left} + 12px)`; } else if (relposition === 'bottom' || relposition === 'bottomleft' || relposition === 'bottomright') { tip.style.top = `calc(${tip.style.top} + 12px)`; } else if (relposition === 'left' || relposition === 'lefttop' || relposition === 'leftbottom') { tip.style.left = `calc(${tip.style.left} - 12px)`; } if (typeof tip.options.connector === 'string') { connBg = tip.options.connector; } else { connBg = window.getComputedStyle(tip).borderBottomColor; } if (relposition.match(/-/)) { connCSS = { left: left, top: top, backgroundColor: connBg }; } else { let styles = window.getComputedStyle(tip); if (relposition === 'topleft' || relposition === 'topright') { if (relposition === 'topleft') { left = styles.borderBottomLeftRadius; } else { let corr = (24 + parseInt(styles.borderBottomLeftRadius)) + 'px'; left = `calc(100% - ${corr})`; } relposition = 'top'; } else if (relposition === 'bottomleft' || relposition === 'bottomright') { if (relposition === 'bottomleft') { left = styles.borderTopLeftRadius; } else { let corr = (24 + parseInt(styles.borderTopRightRadius)) + 'px'; left = `calc(100% - ${corr})`; } relposition = 'bottom'; } else if (relposition === 'lefttop' || relposition === 'leftbottom') { if (relposition === 'lefttop') { top = styles.borderTopRightRadius; } else { let corr = (24 + parseInt(styles.borderBottomRightRadius)) + 'px'; top = `calc(100% - ${corr})`; } relposition = 'left'; } else if (relposition === 'righttop' || relposition === 'rightbottom') { if (relposition === 'righttop') { top = styles.borderTopLeftRadius; } else { let corr = (24 + parseInt(styles.borderBottomLeftRadius)) + 'px'; top = `calc(100% - ${corr})`; } relposition = 'right'; } connCSS = { left: left, top: top, [`border-${relposition}-color`]: connBg }; } jsPanel.setStyle(conn, connCSS); return conn; }, // reposition is still experimental reposition(tip, newposition, cb) { setTimeout(function () { // switch of connector doesn't work properly without timeout // newposition must be an object // get option.tip.position.of newposition.of = tip.options.position.of; // reposition tooltip tip.reposition(newposition); // ... and add connector again if (tip.options.connector) { let connector = tip.querySelector('div.jsPanel-connector'); tip.removeChild(connector); let tipPos = jsPanel.tooltip.relativeTipPos(newposition); if (tipPos !== 'over') { // relative positions leave out a few positions -> connectors are not added for some positions tip.append(jsPanel.tooltip.addConnector(tip, tipPos)); } } if (cb) {cb.call(tip, tip);} return tip; },200); }, // removes specific tooltip from target remove(tgt, evt = 'mouseenter', tip = 'default') { let tooltipToRemove = tgt[tip]; tgt.removeEventListener(evt, tooltipToRemove); } }; // close tooltips on pointerdown in document jsPanel.pointerdown.forEach(function (evt) { document.addEventListener(evt, function (e) { document.querySelectorAll('.jsPanel-tooltip').forEach(function (item) { if (!e.target.closest('.jsPanel-tooltip')) { if (!item.options.autoshow) { item.close(); } } }); },false); }); }