import angular from 'angular' import $ from 'jquery' import _ from 'lodash' angular.module('app').directive('mflyHoverTooltip', [ '$rootScope', '$sanitize', '$timeout', '$window', function mflyHoverTooltip( $rootScope, $sanitize, $timeout, $window, ) { return { link($scope, element, attrs) { let tipElement let parent let timeoutPromise let deregisterChangeWatcher = _.noop const options = { delay: Number.isFinite(attrs.mflyHoverTooltipDelay) ? attrs.mflyHoverTooltipDelay : 500, flushArrow: attrs.mflyHoverTooltipFlushArrow === 'true', maxWidth: Number.isFinite(attrs.mflyHoverTooltipMaxWidth) ? attrs.mflyHoverTooltipMaxWidth : 300, position: attrs.mflyHoverTooltipPosition || 'top left', } const getTooltipTemplate = () => { return '
' } const showTooltip = () => { tipElement.addClass('c-hover-tooltip--visible') } const positionTooltip = () => { const target = { left: 0, top: 0 } const tipWidth = tipElement.outerWidth() const tipHeight = tipElement.outerHeight() const tipMargin = 2 // px // Vertical position... if (_.includes(options.position, 'top')) { target.top = (parent.top - tipHeight - tipMargin) } else if (_.includes(options.position, 'bottom')) { target.top = (parent.top + parent.height + tipMargin) } // Horizontal position... if (_.includes(options.position, 'left')) { target.left = parent.left } else if (_.includes(options.position, 'right')) { target.left = parent.right - tipWidth } else if (_.includes(options.position, 'center')) { target.left = parent.left + (parent.width / 2) - (tipWidth / 2) } // Account for potential scrolling target.left += $window.pageXOffset target.top += $window.pageYOffset // Actually set the calculated position tipElement .css('left', target.left + 'px') .css('top', target.top + 'px') } const createHoverTooltip = (event) => { const canHover = $window.matchMedia(`(hover)`).matches if (!canHover) { return } // Create the tooltip and add it to the DOM tipElement = $('body') .append(getTooltipTemplate()) // Only one tip can be active at a time: .find('.js-active-hover-tooltip') // Add max-width here so wrapping is included in height calculations: .css('max-width', options.maxWidth + 'px') parent = event.target.getBoundingClientRect() positionTooltip() timeoutPromise = $timeout(showTooltip, options.delay) // Watch for changes only while this tooltip is being displayed deregisterChangeWatcher = $scope.$watch( () => { return attrs.mflyHoverTooltip }, () => { // Don't repopulate with unchanged content - that would cause it to measure briefly as 0x0, messing up the positioning const sanitized = $sanitize(attrs.mflyHoverTooltip) if (tipElement.html() !== sanitized) { tipElement.html(sanitized) positionTooltip() } } ) } const destroyHoverTooltip = () => { if (tipElement) { tipElement.remove() $timeout.cancel(timeoutPromise) } deregisterChangeWatcher() deregisterRootScopeWatcher() } element.on('mouseenter', createHoverTooltip) element.on('mouseleave', destroyHoverTooltip) $scope.$on('$destroy', destroyHoverTooltip) const deregisterRootScopeWatcher = $rootScope.$on('$stateChangeStart', destroyHoverTooltip) }, restrict: 'A', scope: {}, } }])