###* Tooltips ======== Unpoly comes with a basic tooltip implementation. Add an [`up-tooltip`](/up-tooltip) attribute to any HTML tag to show a tooltip whenever the user hovers over the element: Decks \#\#\# Styling The default styles render a tooltip with white text on a gray background. A gray triangle points to the element. To change the styling, simply override the [CSS rules](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/up/tooltip.css.sass) for the `.up-tooltip` selector and its `:after` selector that is used for the triangle. The HTML of a tooltip element is simply this:
Show all decks
The tooltip element is appended to the end of ``. @class up.tooltip ### up.tooltip = (($) -> u = up.util ###* Sets default options for future tooltips. @property up.tooltip.config @param {String} [config.position] The default position of tooltips relative to the element. Can be `'top'`, `'right'`, `'bottom'` or `'left'`. @param {String} [config.openAnimation='fade-in'] The animation used to open a tooltip. @param {String} [config.closeAnimation='fade-out'] The animation used to close a tooltip. @param {Number} [config.openDuration] The duration of the open animation (in milliseconds). @param {Number} [config.closeDuration] The duration of the close animation (in milliseconds). @param {String} [config.openEasing] The timing function controlling the acceleration of the opening animation. @param {String} [config.closeEasing] The timing function controlling the acceleration of the closing animation. @stable ### config = u.config position: 'top' openAnimation: 'fade-in' closeAnimation: 'fade-out' openDuration: 100 closeDuration: 50 openEasing: null closeEasing: null state = u.config phase: 'closed' # can be 'opening', 'opened', 'closing' and 'closed' $anchor: null # the element to which the tooltip is anchored $tooltip: null # the tooltiop element position: null # the position of the tooltip element relative to its anchor chain = new u.DivertibleChain() reset = -> # Destroy the tooltip container regardless whether it's currently in a closing animation state.$tooltip?.remove() state.reset() chain.reset() config.reset() align = -> css = {} tooltipBox = u.measure(state.$tooltip) if u.isFixed(state.$anchor) linkBox = state.$anchor.get(0).getBoundingClientRect() css['position'] = 'fixed' else linkBox = u.measure(state.$anchor) switch state.position when 'top' css['top'] = linkBox.top - tooltipBox.height css['left'] = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width) when 'left' css['top'] = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height) css['left'] = linkBox.left - tooltipBox.width when 'right' css['top'] = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height) css['left'] = linkBox.left + linkBox.width when 'bottom' css['top'] = linkBox.top + linkBox.height css['left'] = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width) else up.fail("Unknown position option '%s'", state.position) state.$tooltip.attr('up-position', state.position) state.$tooltip.css(css) createElement = (options) -> $element = u.$createElementFromSelector('.up-tooltip') if u.isGiven(options.text) $element.text(options.text) else $element.html(options.html) $element.appendTo(document.body) state.$tooltip = $element ###* Opens a tooltip over the given element. up.tooltip.attach('.help', { html: 'Enter multiple words or phrases' }); @function up.tooltip.attach @param {Element|jQuery|String} elementOrSelector @param {String} [options.text] The text to display in the tooltip. Any HTML control characters will be escaped. If you need to use HTML formatting in the tooltip, use `options.html` instead. @param {String} [options.html] The HTML to display in the tooltip unescaped. Make sure to escape any user-provided text before passing it as this option, or use `options.text` (which automatically escapes). @param {String} [options.position='top'] The position of the tooltip. Can be `'top'`, `'right'`, `'bottom'` or `'left'`. @param {String} [options.animation] The animation to use when opening the tooltip. @return {Promise} A promise that will be resolved when the tooltip's opening animation has finished. @stable ### attachAsap = (elementOrSelector, options = {}) -> curriedAttachNow = -> attachNow(elementOrSelector, options) if isOpen() chain.asap(closeNow, curriedAttachNow) else chain.asap(curriedAttachNow) chain.promise() attachNow = (elementOrSelector, options) -> $anchor = $(elementOrSelector) options = u.options(options) html = u.option(options.html, $anchor.attr('up-tooltip-html')) text = u.option(options.text, $anchor.attr('up-tooltip')) position = u.option(options.position, $anchor.attr('up-position'), config.position) animation = u.option(options.animation, u.castedAttr($anchor, 'up-animation'), config.openAnimation) animateOptions = up.motion.animateOptions(options, $anchor, duration: config.openDuration, easing: config.openEasing) state.phase = 'opening' state.$anchor = $anchor createElement(text: text, html: html) state.position = position align() up.animate(state.$tooltip, animation, animateOptions).then -> state.phase = 'opened' ###* Closes a currently shown tooltip. Does nothing if no tooltip is currently shown. @function up.tooltip.close @param {Object} options See options for [`up.animate`](/up.animate). @return {Promise} A promise for the end of the closing animation. @stable ### closeAsap = (options) -> if isOpen() chain.asap -> closeNow(options) chain.promise() closeNow = (options) -> unless isOpen() # this can happen when a request fails and the chain proceeds to the next task return u.resolvedPromise() options = u.options(options, animation: config.closeAnimation) animateOptions = up.motion.animateOptions(options, duration: config.closeDuration, easing: config.closeEasing) u.extend(options, animateOptions) state.phase = 'closing' up.destroy(state.$tooltip, options).then -> state.phase = 'closed' state.$tooltip = null state.$anchor = null ###* Returns whether a tooltip is currently showing. @function up.tooltip.isOpen @stable ### isOpen = -> state.phase == 'opening' || state.phase == 'opened' ###* Displays a tooltip with text content when hovering the mouse over this element: Decks To make the tooltip appear below the element instead of above the element, add an `up-position` attribute: Decks @selector [up-tooltip] @param {String} [up-animation] The animation used to open the tooltip. Defaults to [`up.tooltip.config.openAnimation`](/up.tooltip.config). @param {String} [up-position] The default position of tooltips relative to the element. Can be either `"top"` or `"bottom"`. Defaults to [`up.tooltip.config.position`](/up.tooltip.config). @stable ### ###* Displays a tooltip with HTML content when hovering the mouse over this element: Decks @selector [up-tooltip-html] @stable ### up.compiler '[up-tooltip], [up-tooltip-html]', ($opener) -> # Don't register these events on document since *every* # mouse move interaction bubbles up to the document. $opener.on('mouseenter', -> attachAsap($opener)) $opener.on('mouseleave', -> closeAsap()) # We close the tooltip when someone clicks on the document. # We also need to listen to up:action:consumed in case an [up-instant] link # was followed on mousedown. up.on 'click up:action:consumed', (event) -> closeAsap() # Do not halt the event chain here. The user is allowed to directly activate # a link in the background, even with a (now closing) tooltip open. # The framework is reset between tests, so also close a currently open tooltip. up.on 'up:framework:reset', reset # Close the tooltip when the user presses ESC. up.bus.onEscape(-> closeAsap()) config: config attach: attachAsap isOpen: isOpen close: closeAsap )(jQuery)