The tooltip is visible when the button is hovered, focused, or touched.
+ Hover over the Refresh icon in the above toolbar.
-
-
-
-
- Insert Drive
-
-
-
-
- Photos
-
-
-
+
+
+ The Tooltip's md-z-index attribute can be used to change
+ the tooltip's visual level in comparison with the other elements of
+ the application.
+ Note: the z-index default is 100.
+
-
-
-
md-direction attribute can used to dynamically change the direction of the tooltip.
- Note: the direction default value is 'bottom'.
-
-
- Left
- Top
- Bottom
- Right
-
+
+
+ The Tooltip's md-direction attribute can be used to
+ dynamically change the direction of the tooltip.
+ Note: the direction default value is
+ 'bottom'.
+
+
+
+ Top
+ Right
+ Bottom
+ Left
+
+
+
+ Insert Drive
+
+
+
+
-
-
+
- Additionally, the Tooltip's md-visible attribute can use data-binding to
+ The Tooltip's md-visible attribute can be used to
programmatically show/hide itself. Toggle the checkbox below...
-
-
- Insert Drive
-
+
+
+
+ Insert Drive
+
+
+
+ Photos
+
+
-
+
- Additionally, the Tooltip's md-delay attribute can use to delay the
- show animation. The default values is 0 mSecs...
+ The Tooltip's md-delay attribute can be used to delay
+ the show animation.
+ Note: the delay default value is
+ 0 milliseconds.
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
- Photos with Tooltip Delay {{demo.delayTooltip}} msecs
+ Menu with Tooltip Delay {{demo.delayTooltip}} msecs
+
-
+
diff --git a/src/components/tooltip/demoBasicUsage/script.js b/src/components/tooltip/demoBasicUsage/script.js
index 8059b4ea7c..92b15cca7c 100644
--- a/src/components/tooltip/demoBasicUsage/script.js
+++ b/src/components/tooltip/demoBasicUsage/script.js
@@ -1,18 +1,14 @@
-angular.module('tooltipDemo1', ['ngMaterial'])
-.controller('AppCtrl', function($scope) {
+angular.module('tooltipDemo', ['ngMaterial'])
+ .controller('AppCtrl', AppCtrl);
+
+function AppCtrl($scope) {
$scope.demo = {
- showTooltip : false,
- tipDirection : ''
+ showTooltip: false,
+ tipDirection: 'bottom'
};
$scope.demo.delayTooltip = undefined;
- $scope.$watch('demo.delayTooltip',function(val) {
+ $scope.$watch('demo.delayTooltip', function(val) {
$scope.demo.delayTooltip = parseInt(val, 10) || 0;
});
-
- $scope.$watch('demo.tipDirection',function(val) {
- if (val && val.length ) {
- $scope.demo.showTooltip = true;
- }
- });
-});
+}
diff --git a/src/components/tooltip/demoBasicUsage/style.css b/src/components/tooltip/demoBasicUsage/style.css
deleted file mode 100644
index 0f65234fd0..0000000000
--- a/src/components/tooltip/demoBasicUsage/style.css
+++ /dev/null
@@ -1,8 +0,0 @@
-
-md-toolbar .md-toolbar-tools .md-button,
-md-toolbar .md-toolbar-tools .md-button:hover {
- box-shadow: none;
- border: none;
- transform: none;
- -webkit-transform: none;
-}
\ No newline at end of file
diff --git a/src/components/tooltip/tooltip-theme.scss b/src/components/tooltip/tooltip-theme.scss
index 4a04f43b06..84b777a092 100644
--- a/src/components/tooltip/tooltip-theme.scss
+++ b/src/components/tooltip/tooltip-theme.scss
@@ -1,6 +1,6 @@
-md-tooltip.md-THEME_NAME-theme {
+.md-tooltip.md-THEME_NAME-theme {
color: '{{background-700-contrast}}';
- .md-content {
+ &.md-panel {
background-color: '{{background-700}}';
}
}
diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js
index abebd504c0..86db40fae1 100644
--- a/src/components/tooltip/tooltip.js
+++ b/src/components/tooltip/tooltip.js
@@ -3,392 +3,402 @@
* @name material.components.tooltip
*/
angular
- .module('material.components.tooltip', [ 'material.core' ])
- .directive('mdTooltip', MdTooltipDirective)
- .service('$$mdTooltipRegistry', MdTooltipRegistry);
+ .module('material.components.tooltip', [
+ 'material.core',
+ 'material.components.panel'
+ ])
+ .directive('mdTooltip', MdTooltipDirective)
+ .service('$$mdTooltipRegistry', MdTooltipRegistry);
+
/**
* @ngdoc directive
* @name mdTooltip
* @module material.components.tooltip
* @description
- * Tooltips are used to describe elements that are interactive and primarily graphical (not textual).
+ * Tooltips are used to describe elements that are interactive and primarily
+ * graphical (not textual).
*
* Place a `` as a child of the element it describes.
*
- * A tooltip will activate when the user focuses, hovers over, or touches the parent.
+ * A tooltip will activate when the user hovers over, focuses, or touches the
+ * parent element.
*
* @usage
*
- *
- *
- * Play Music
- *
- *
- *
+ *
+ * Play Music
+ *
+ *
*
*
- * @param {expression=} md-visible Boolean bound to whether the tooltip is currently visible.
- * @param {number=} md-delay How many milliseconds to wait to show the tooltip after the user focuses, hovers, or touches the
- * parent. Defaults to 0ms on non-touch devices and 75ms on touch.
- * @param {boolean=} md-autohide If present or provided with a boolean value, the tooltip will hide on mouse leave, regardless of focus
- * @param {string=} md-direction Which direction would you like the tooltip to go? Supports left, right, top, and bottom. Defaults to bottom.
+ * @param {number=} md-z-index The visual level that the tooltip will appear
+ * in comparison with the rest of the elements of the application.
+ * @param {expression=} md-visible Boolean bound to whether the tooltip is
+ * currently visible.
+ * @param {number=} md-delay How many milliseconds to wait to show the tooltip
+ * after the user hovers over, focuses, or touches the parent element.
+ * Defaults to 0ms on non-touch devices and 75ms on touch.
+ * @param {boolean=} md-autohide If present or provided with a boolean value,
+ * the tooltip will hide on mouse leave, regardless of focus.
+ * @param {string=} md-direction The direction that the tooltip is shown,
+ * relative to the parent element. Supports top, right, bottom, and left.
+ * Defaults to bottom.
*/
-function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdTheming, $animate,
- $interpolate, $mdConstant, $$mdTooltipRegistry) {
+function MdTooltipDirective($timeout, $window, $$rAF, $document, $interpolate,
+ $mdUtil, $mdTheming, $mdPanel, $$mdTooltipRegistry) {
var ENTER_EVENTS = 'focus touchstart mouseenter';
var LEAVE_EVENTS = 'blur touchcancel mouseleave';
- var SHOW_CLASS = 'md-show';
- var TOOLTIP_SHOW_DELAY = 0;
- var TOOLTIP_WINDOW_EDGE_SPACE = 8;
+ var TOOLTIP_DEFAULT_Z_INDEX = 100;
+ var TOOLTIP_DEFAULT_SHOW_DELAY = 0;
+ var TOOLTIP_DEFAULT_DIRECTION = 'bottom';
+ var TOOLTIP_DIRECTIONS = {
+ top: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.ABOVE },
+ right: { x: $mdPanel.xPosition.OFFSET_END, y: $mdPanel.yPosition.CENTER },
+ bottom: { x: $mdPanel.xPosition.CENTER, y: $mdPanel.yPosition.BELOW },
+ left: { x: $mdPanel.xPosition.OFFSET_START, y: $mdPanel.yPosition.CENTER }
+ };
return {
restrict: 'E',
- transclude: true,
- priority: $mdConstant.BEFORE_NG_ARIA,
- template: '',
+ priority: 210, // Before ngAria
scope: {
- delay: '=?mdDelay',
- visible: '=?mdVisible',
- autohide: '=?mdAutohide',
- direction: '@?mdDirection' // only expect raw or interpolated string value; not expression
+ mdZIndex: '=?mdZIndex',
+ mdDelay: '=?mdDelay',
+ mdVisible: '=?mdVisible',
+ mdAutohide: '=?mdAutohide',
+ mdDirection: '@?mdDirection' // Do not expect expressions.
},
- compile: function(tElement, tAttr) {
- if (!tAttr.mdDirection) {
- tAttr.$set('mdDirection', 'bottom');
- }
-
- return postLink;
- }
+ link: linkFunc
};
- function postLink(scope, element, attr) {
+ function linkFunc(scope, element, attr) {
+ // Set constants.
+ var parent = $mdUtil.getParentWithPointerEvents(element);
+ var debouncedOnResize = $$rAF.throttle(updatePosition);
+ var mouseActive = false;
+ var origin, position, panelPosition, panelRef, autohide, showTimeout,
+ elementFocusedOnWindowBlur = null;
+ // Initialize the theming of the tooltip.
$mdTheming(element);
- var parent = $mdUtil.getParentWithPointerEvents(element),
- content = angular.element(element[0].querySelector('.md-content')),
- tooltipParent = angular.element(document.body),
- showTimeout = null,
- debouncedOnResize = $$rAF.throttle(function () { updatePosition(); });
-
- if ($animate.pin) $animate.pin(element, parent);
-
- // Initialize element
-
+ // Set defaults
setDefaults();
- manipulateElement();
- bindEvents();
- // Default origin transform point is 'center top'
- // positionTooltip() is always relative to center top
- updateContentOrigin();
-
- configureWatchers();
+ // Set parent aria-label.
addAriaLabel();
+ // Remove the element from its current DOM position.
+ element.detach();
+ element.attr('role', 'tooltip');
- function setDefaults () {
- scope.delay = scope.delay || TOOLTIP_SHOW_DELAY;
- }
+ updatePosition();
+ bindEvents();
+ configureWatchers();
- function updateContentOrigin() {
- var origin = 'center top';
- switch (scope.direction) {
- case 'left' : origin = 'right center'; break;
- case 'right' : origin = 'left center'; break;
- case 'top' : origin = 'center bottom'; break;
- case 'bottom': origin = 'center top'; break;
+ function setDefaults() {
+ scope.mdZIndex = scope.mdZIndex || TOOLTIP_DEFAULT_Z_INDEX;
+ scope.mdDelay = scope.mdDelay || TOOLTIP_DEFAULT_SHOW_DELAY;
+ if (!TOOLTIP_DIRECTIONS[scope.mdDirection]) {
+ scope.mdDirection = TOOLTIP_DEFAULT_DIRECTION;
}
- content.css('transform-origin', origin);
}
- function onVisibleChanged (isVisible) {
- if (isVisible) showTooltip();
- else hideTooltip();
+ function addAriaLabel(override) {
+ if (override || !parent.attr('aria-label')) {
+ var rawText = override || element.text().trim();
+ var interpolatedText = $interpolate(rawText)(parent.scope());
+ parent.attr('aria-label', interpolatedText);
+ }
}
- function configureWatchers () {
- if (element[0] && 'MutationObserver' in $window) {
- var attributeObserver = new MutationObserver(function(mutations) {
- mutations
- .forEach(function (mutation) {
- if (mutation.attributeName === 'md-visible') {
- if (!scope.visibleWatcher)
- scope.visibleWatcher = scope.$watch('visible', onVisibleChanged );
- }
- if (mutation.attributeName === 'md-direction') {
- updatePosition(scope.direction);
- }
- });
- });
-
- attributeObserver.observe(element[0], { attributes: true });
+ function updatePosition() {
+ setDefaults();
- // build watcher only if mdVisible is being used
- if (attr.hasOwnProperty('mdVisible')) {
- scope.visibleWatcher = scope.$watch('visible', onVisibleChanged );
- }
- } else { // MutationObserver not supported
- scope.visibleWatcher = scope.$watch('visible', onVisibleChanged );
- scope.$watch('direction', updatePosition );
+ // If the panel has already been created, remove the current origin
+ // class from the panel element.
+ if (panelRef && panelRef.panelEl) {
+ panelRef.panelEl.removeClass(origin);
}
- var onElementDestroy = function() {
- scope.$destroy();
- };
+ // Set the panel element origin class based off of the current
+ // mdDirection.
+ origin = 'md-origin-' + scope.mdDirection;
- // Clean up if the element or parent was removed via jqLite's .remove.
- // A couple of notes:
- // - In these cases the scope might not have been destroyed, which is why we
- // destroy it manually. An example of this can be having `md-visible="false"` and
- // adding tooltips while they're invisible. If `md-visible` becomes true, at some
- // point, you'd usually get a lot of tooltips.
- // - We use `.one`, not `.on`, because this only needs to fire once. If we were
- // using `.on`, it would get thrown into an infinite loop.
- // - This kicks off the scope's `$destroy` event which finishes the cleanup.
- element.one('$destroy', onElementDestroy);
- parent.one('$destroy', onElementDestroy);
- scope.$on('$destroy', function() {
- setVisible(false);
- element.remove();
- attributeObserver && attributeObserver.disconnect();
- });
+ // Create the position of the panel based off of the mdDirection.
+ position = TOOLTIP_DIRECTIONS[scope.mdDirection];
- // Updates the aria-label when the element text changes. This watch
- // doesn't need to be set up if the element doesn't have any data
- // bindings.
- if (element.text().indexOf($interpolate.startSymbol()) > -1) {
- scope.$watch(function() {
- return element.text().trim();
- }, addAriaLabel);
- }
- }
+ // Using the newly created position object, use the MdPanel
+ // panelPosition API to build the panel's position.
+ panelPosition = $mdPanel.newPanelPosition()
+ .relativeTo(parent)
+ .addPanelPosition(position.x, position.y);
- function addAriaLabel (override) {
- if ((override || !parent.attr('aria-label')) && !parent.text().trim()) {
- var rawText = override || element.text().trim();
- var interpolatedText = $interpolate(rawText)(parent.scope());
- parent.attr('aria-label', interpolatedText);
+ // If the panel has already been created, add the new origin class to
+ // the panel element and update it's position with the panelPosition.
+ if (panelRef && panelRef.panelEl) {
+ panelRef.panelEl.addClass(origin);
+ panelRef.updatePosition(panelPosition);
}
}
- function manipulateElement () {
- element.detach();
- element.attr('role', 'tooltip');
- }
-
- function bindEvents () {
- var mouseActive = false;
-
- // add an mutationObserver when there is support for it
- // and the need for it in the form of viable host(parent[0])
+ function bindEvents() {
+ // Add a mutationObserver where there is support for it and the need
+ // for it in the form of viable host(parent[0]).
if (parent[0] && 'MutationObserver' in $window) {
- // use an mutationObserver to tackle #2602
+ // Use a mutationObserver to tackle #2602.
var attributeObserver = new MutationObserver(function(mutations) {
- if (mutations.some(function (mutation) {
- return (mutation.attributeName === 'disabled' && parent[0].disabled);
- })) {
- $mdUtil.nextTick(function() {
- setVisible(false);
- });
+ if (isDisabledMutation(mutations)) {
+ $mdUtil.nextTick(function() {
+ setVisible(false);
+ });
}
});
- attributeObserver.observe(parent[0], { attributes: true});
+ attributeObserver.observe(parent[0], {
+ attributes: true
+ });
}
- // Store whether the element was focused when the window loses focus.
- var windowBlurHandler = function() {
- elementFocusedOnWindowBlur = document.activeElement === parent[0];
- };
+ elementFocusedOnWindowBlur = false;
- var elementFocusedOnWindowBlur = false;
+ $$mdTooltipRegistry.register('scroll', windowScrollEventHandler, true);
+ $$mdTooltipRegistry.register('blur', windowBlurEventHandler);
+ $$mdTooltipRegistry.register('resize', debouncedOnResize);
- function windowScrollHandler() {
- setVisible(false);
- }
+ scope.$on('$destroy', onDestroy);
- $$mdTooltipRegistry.register('scroll', windowScrollHandler, true);
- $$mdTooltipRegistry.register('blur', windowBlurHandler);
- $$mdTooltipRegistry.register('resize', debouncedOnResize);
+ // To avoid 'synthetic clicks', we listen to mousedown instead of
+ // 'click'.
+ parent.on('mousedown', mousedownEventHandler);
+ parent.on(ENTER_EVENTS, enterEventHandler);
- scope.$on('$destroy', function() {
- $$mdTooltipRegistry.deregister('scroll', windowScrollHandler, true);
- $$mdTooltipRegistry.deregister('blur', windowBlurHandler);
- $$mdTooltipRegistry.deregister('resize', debouncedOnResize);
+ function isDisabledMutation(mutations) {
+ mutations.some(function(mutation) {
+ return mutation.attributeName === 'disabled' && parent[0].disabled;
+ });
+ return false;
+ }
- parent
- .off(ENTER_EVENTS, enterHandler)
- .off(LEAVE_EVENTS, leaveHandler)
- .off('mousedown', mousedownHandler);
+ function windowScrollEventHandler() {
+ setVisible(false);
+ }
- // Trigger the handler in case any the tooltip was still visible.
- leaveHandler();
- attributeObserver && attributeObserver.disconnect();
- });
+ function windowBlurEventHandler() {
+ elementFocusedOnWindowBlur = document.activeElement === parent[0];
+ }
- var enterHandler = function(e) {
- // Prevent the tooltip from showing when the window is receiving focus.
- if (e.type === 'focus' && elementFocusedOnWindowBlur) {
+ function enterEventHandler($event) {
+ // Prevent the tooltip from showing when the window is receiving
+ // focus.
+ if ($event.type === 'focus' && elementFocusedOnWindowBlur) {
elementFocusedOnWindowBlur = false;
- } else if (!scope.visible) {
- parent.on(LEAVE_EVENTS, leaveHandler);
+ } else if (!scope.mdVisible) {
+ parent.on(LEAVE_EVENTS, leaveEventHandler);
setVisible(true);
- // If the user is on a touch device, we should bind the tap away after
- // the `touched` in order to prevent the tooltip being removed immediately.
- if (e.type === 'touchstart') {
+ // If the user is on a touch device, we should bind the tap away
+ // after the 'touched' in order to prevent the tooltip being
+ // removed immediately.
+ if ($event.type === 'touchstart') {
parent.one('touchend', function() {
$mdUtil.nextTick(function() {
- $document.one('touchend', leaveHandler);
+ $document.one('touchend', leaveEventHandler);
}, false);
});
}
}
- };
+ }
- var leaveHandler = function () {
- var autohide = scope.hasOwnProperty('autohide') ? scope.autohide : attr.hasOwnProperty('mdAutohide');
+ function leaveEventHandler() {
+ autohide = scope.hasOwnProperty('mdAutohide') ?
+ scope.mdAutohide :
+ attr.hasOwnProperty('mdAutohide');
- if (autohide || mouseActive || $document[0].activeElement !== parent[0]) {
- // When a show timeout is currently in progress, then we have to cancel it.
- // Otherwise the tooltip will remain showing without focus or hover.
+ if (autohide || mouseActive ||
+ $document[0].activeElement !== parent[0]) {
+ // When a show timeout is currently in progress, then we have
+ // to cancel it, otherwise the tooltip will remain showing
+ // without focus or hover.
if (showTimeout) {
$timeout.cancel(showTimeout);
setVisible.queued = false;
showTimeout = null;
}
- parent.off(LEAVE_EVENTS, leaveHandler);
+ parent.off(LEAVE_EVENTS, leaveEventHandler);
parent.triggerHandler('blur');
setVisible(false);
}
mouseActive = false;
- };
+ }
- var mousedownHandler = function() {
+ function mousedownEventHandler() {
mouseActive = true;
- };
+ }
- // to avoid `synthetic clicks` we listen to mousedown instead of `click`
- parent.on('mousedown', mousedownHandler);
- parent.on(ENTER_EVENTS, enterHandler);
+ function onDestroy() {
+ $$mdTooltipRegistry.deregister('scroll', windowScrollEventHandler, true);
+ $$mdTooltipRegistry.deregister('blur', windowBlurEventHandler);
+ $$mdTooltipRegistry.deregister('resize', debouncedOnResize);
+
+ parent
+ .off(ENTER_EVENTS, enterEventHandler)
+ .off(LEAVE_EVENTS, leaveEventHandler)
+ .off('mousedown', mousedownEventHandler);
+
+ // Trigger the handler in case any of the tooltips are
+ // still visible.
+ leaveEventHandler();
+ attributeObserver && attributeObserver.disconnect();
+ }
+ }
+
+ function configureWatchers() {
+ if (element[0] && 'MutationObserver' in $window) {
+ var attributeObserver = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ if (mutation.attributeName === 'md-visible' &&
+ !scope.visibleWatcher ) {
+ scope.visibleWatcher = scope.$watch('mdVisible',
+ onVisibleChanged);
+ }
+ });
+ });
+
+ attributeObserver.observe(element[0], {
+ attributes: true
+ });
+
+ // Build watcher only if mdVisible is being used.
+ if (attr.hasOwnProperty('mdVisible')) {
+ scope.visibleWatcher = scope.$watch('mdVisible',
+ onVisibleChanged);
+ }
+ } else {
+ // MutationObserver not supported
+ scope.visibleWatcher = scope.$watch('mdVisible', onVisibleChanged);
+ }
+
+ // Direction watcher
+ scope.$watch('mdDirection', updatePosition);
+
+ // Clean up if the element or parent was removed via jqLite's .remove.
+ // A couple of notes:
+ // - In these cases the scope might not have been destroyed, which
+ // is why we destroy it manually. An example of this can be having
+ // `md-visible="false"` and adding tooltips while they're
+ // invisible. If `md-visible` becomes true, at some point, you'd
+ // usually get a lot of tooltips.
+ // - We use `.one`, not `.on`, because this only needs to fire once.
+ // If we were using `.on`, it would get thrown into an infinite
+ // loop.
+ // - This kicks off the scope's `$destroy` event which finishes the
+ // cleanup.
+ element.one('$destroy', onElementDestroy);
+ parent.one('$destroy', onElementDestroy);
+ scope.$on('$destroy', function() {
+ setVisible(false);
+ element.remove();
+ attributeObserver && attributeObserver.disconnect();
+ });
+
+ // Updates the aria-label when the element text changes. This watch
+ // doesn't need to be set up if the element doesn't have any data
+ // bindings.
+ if (element.text().indexOf($interpolate.startSymbol()) > -1) {
+ scope.$watch(function() {
+ return element.text().trim();
+ }, addAriaLabel);
+ }
+
+ function onElementDestroy() {
+ scope.$destroy();
+ }
}
- function setVisible (value) {
- // break if passed value is already in queue or there is no queue and passed value is current in the scope
- if (setVisible.queued && setVisible.value === !!value || !setVisible.queued && scope.visible === !!value) return;
+ function setVisible(value) {
+ // Break if passed value is already in queue or there is no queue and
+ // passed value is current in the controller.
+ if (setVisible.queued && setVisible.value === !!value ||
+ !setVisible.queued && scope.mdVisible === !!value) {
+ return;
+ }
setVisible.value = !!value;
if (!setVisible.queued) {
if (value) {
setVisible.queued = true;
showTimeout = $timeout(function() {
- scope.visible = setVisible.value;
+ scope.mdVisible = setVisible.value;
setVisible.queued = false;
showTimeout = null;
-
if (!scope.visibleWatcher) {
- onVisibleChanged(scope.visible);
+ onVisibleChanged(scope.mdVisible);
}
- }, scope.delay);
+ }, scope.mdDelay);
} else {
$mdUtil.nextTick(function() {
- scope.visible = false;
- if (!scope.visibleWatcher)
+ scope.mdVisible = false;
+ if (!scope.visibleWatcher) {
onVisibleChanged(false);
+ }
});
}
}
}
- function showTooltip() {
- // Do not show the tooltip if the text is empty.
- if (!element[0].textContent.trim()) return;
-
- // Insert the element and position at top left, so we can get the position
- // and check if we should display it
- element.css({top: 0, left: 0});
- tooltipParent.append(element);
-
- // Check if we should display it or not.
- // This handles hide-* and show-* along with any user defined css
- if ( $mdUtil.hasComputedStyle(element, 'display', 'none')) {
- scope.visible = false;
- element.detach();
- return;
- }
-
- updatePosition();
-
- $animate.addClass(content, SHOW_CLASS).then(function() {
- element.addClass(SHOW_CLASS);
- });
- }
-
- function hideTooltip() {
- $animate.removeClass(content, SHOW_CLASS).then(function(){
- element.removeClass(SHOW_CLASS);
- if (!scope.visible) element.detach();
- });
+ function onVisibleChanged(isVisible) {
+ isVisible ? showTooltip() : hideTooltip();
}
- function updatePosition() {
- if ( !scope.visible ) return;
-
- updateContentOrigin();
- positionTooltip();
- }
-
- function positionTooltip() {
- var tipRect = $mdUtil.offsetRect(element, tooltipParent);
- var parentRect = $mdUtil.offsetRect(parent, tooltipParent);
- var newPosition = getPosition(scope.direction);
- var offsetParent = element.prop('offsetParent');
-
- // If the user provided a direction, just nudge the tooltip onto the screen
- // Otherwise, recalculate based on 'top' since default is 'bottom'
- if (scope.direction) {
- newPosition = fitInParent(newPosition);
- } else if (offsetParent && newPosition.top > offsetParent.scrollHeight - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE) {
- newPosition = fitInParent(getPosition('top'));
+ function showTooltip() {
+ // Do not show the tooltip if the text is empty.
+ if (!element[0].textContent.trim()) {
+ throw new Error('Text for the tooltip has not been provided. ' +
+ 'Please include text within the mdTooltip element.');
}
- element.css({
- left: newPosition.left + 'px',
- top: newPosition.top + 'px'
- });
+ if (!panelRef) {
+ var id = 'tooltip-' + $mdUtil.nextUid();
+ var attachTo = angular.element(document.body);
+ var content = element.html().trim();
+ var panelAnimation = $mdPanel.newPanelAnimation()
+ .openFrom(parent)
+ .closeTo(parent)
+ .withAnimation({
+ open: 'md-show',
+ close: 'md-hide'
+ });
- function fitInParent (pos) {
- var newPosition = { left: pos.left, top: pos.top };
- newPosition.left = Math.min( newPosition.left, tooltipParent.prop('scrollWidth') - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE );
- newPosition.left = Math.max( newPosition.left, TOOLTIP_WINDOW_EDGE_SPACE );
- newPosition.top = Math.min( newPosition.top, tooltipParent.prop('scrollHeight') - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE );
- newPosition.top = Math.max( newPosition.top, TOOLTIP_WINDOW_EDGE_SPACE );
- return newPosition;
+ var panelConfig = {
+ id: id,
+ attachTo: attachTo,
+ template: content,
+ propagateContainerEvents: true,
+ panelClass: 'md-tooltip ' + origin,
+ animation: panelAnimation,
+ position: panelPosition,
+ zIndex: scope.mdZIndex,
+ focusOnOpen: false
+ };
+
+ panelRef = $mdPanel.create(panelConfig);
}
- function getPosition (dir) {
- return dir === 'left'
- ? { left: parentRect.left - tipRect.width - TOOLTIP_WINDOW_EDGE_SPACE,
- top: parentRect.top + parentRect.height / 2 - tipRect.height / 2 }
- : dir === 'right'
- ? { left: parentRect.left + parentRect.width + TOOLTIP_WINDOW_EDGE_SPACE,
- top: parentRect.top + parentRect.height / 2 - tipRect.height / 2 }
- : dir === 'top'
- ? { left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
- top: parentRect.top - tipRect.height - TOOLTIP_WINDOW_EDGE_SPACE }
- : { left: parentRect.left + parentRect.width / 2 - tipRect.width / 2,
- top: parentRect.top + parentRect.height + TOOLTIP_WINDOW_EDGE_SPACE };
- }
+ panelRef.open();
}
+ function hideTooltip() {
+ panelRef && panelRef.close();
+ }
}
}
+
/**
* Service that is used to reduce the amount of listeners that are being
* registered on the `window` by the tooltip component. Works by collecting
@@ -406,9 +416,9 @@ function MdTooltipRegistry() {
};
/**
- * Global event handler that dispatches the registered
- * handlers in the service.
- * @param {Event} event Event object passed in by the browser.
+ * Global event handler that dispatches the registered handlers in the
+ * service.
+ * @param {!Event} event Event object passed in by the browser
*/
function globalEventHandler(event) {
if (listeners[event.type]) {
@@ -420,45 +430,39 @@ function MdTooltipRegistry() {
/**
* Registers a new handler with the service.
- * @param {String} type Type of event to be registered.
- * @param {Function} handler Event handler
- * @param {Boolean} useCapture Whether to use event capturing.
+ * @param {string} type Type of event to be registered.
+ * @param {!Function} handler Event handler.
+ * @param {boolean} useCapture Whether to use event capturing.
*/
function register(type, handler, useCapture) {
- var array = listeners[type] = listeners[type] || [];
+ var handlers = listeners[type] = listeners[type] || [];
- if (!array.length) {
- if (useCapture) {
- window.addEventListener(type, globalEventHandler, true);
- } else {
- ngWindow.on(type, globalEventHandler);
- }
+ if (!handlers.length) {
+ useCapture ? window.addEventListener(type, globalEventHandler, true) :
+ ngWindow.on(type, globalEventHandler);
}
- if (array.indexOf(handler) === -1) {
- array.push(handler);
+ if (handlers.indexOf(handler) === -1) {
+ handlers.push(handler);
}
}
/**
* Removes an event handler from the service.
- * @param {String} type Type of event handler.
- * @param {Function} handler The event handler itself.
- * @param {Boolean} useCapture Whether the event handler used event capturing.
+ * @param {string} type Type of event handler.
+ * @param {!Function} handler The event handler itself.
+ * @param {boolean} useCapture Whether the event handler used event capturing.
*/
function deregister(type, handler, useCapture) {
- var array = listeners[type];
- var index = array ? array.indexOf(handler) : -1;
+ var handlers = listeners[type];
+ var index = handlers ? handlers.indexOf(handler) : -1;
if (index > -1) {
- array.splice(index, 1);
+ handlers.splice(index, 1);
- if (array.length === 0) {
- if (useCapture) {
- window.removeEventListener(type, globalEventHandler, true);
- } else {
- ngWindow.off(type, globalEventHandler);
- }
+ if (handlers.length === 0) {
+ useCapture ? window.removeEventListener(type, globalEventHandler, true) :
+ ngWindow.off(type, globalEventHandler);
}
}
}
diff --git a/src/components/tooltip/tooltip.scss b/src/components/tooltip/tooltip.scss
index 7bfe98f6bf..ab87b5d0af 100644
--- a/src/components/tooltip/tooltip.scss
+++ b/src/components/tooltip/tooltip.scss
@@ -1,71 +1,67 @@
-$tooltip-fontsize-lg: rem(1) !default;
-$tooltip-fontsize-sm: rem(1.4) !default;
-$tooltip-height-lg: rem(2.2) !default;
-$tooltip-height-sm: rem(3.2) !default;
-$tooltip-top-margin-lg: rem(1.4) !default;
-$tooltip-top-margin-sm: rem(2.4) !default;
-$tooltip-lr-padding-lg: rem(0.8) !default;
-$tooltip-lr-padding-sm: rem(1.6) !default;
-$tooltip-max-width: rem(3.20) !default;
+$tooltip-fontsize-lg: 10px !default;
+$tooltip-fontsize-sm: 14px !default;
+$tooltip-height-lg: 22px !default;
+$tooltip-height-sm: 32px !default;
+$tooltip-top-margin-lg: 14px !default;
+$tooltip-top-margin-sm: 24px !default;
+$tooltip-lr-padding-lg: 8px !default;
+$tooltip-lr-padding-sm: 16px !default;
+$tooltip-max-width: 32px !default;
-md-tooltip {
- position: absolute;
- z-index: $z-index-tooltip;
- overflow: hidden;
+.md-tooltip {
pointer-events: none;
border-radius: 4px;
-
+ overflow: hidden;
+ opacity: 0;
font-weight: 500;
font-size: $tooltip-fontsize-sm;
- @media (min-width: $layout-breakpoint-sm) {
- font-size: $tooltip-fontsize-lg;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ height: $tooltip-height-sm;
+ line-height: $tooltip-height-sm;
+ padding-right: $tooltip-lr-padding-sm;
+ padding-left: $tooltip-lr-padding-sm;
+ &.md-origin-top {
+ transform-origin: center bottom;
+ margin-top: -$tooltip-top-margin-sm;
}
-
- .md-content {
- position: relative;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ &.md-origin-right {
+ transform-origin: left center;
+ margin-left: $tooltip-top-margin-sm;
+ }
+ &.md-origin-bottom {
transform-origin: center top;
- transform: scale(0);
- opacity: 0;
- height: $tooltip-height-sm;
- line-height: $tooltip-height-sm;
- padding-left: $tooltip-lr-padding-sm;
- padding-right: $tooltip-lr-padding-sm;
- @media (min-width: $layout-breakpoint-sm) {
- height: $tooltip-height-lg;
- line-height: $tooltip-height-lg;
- padding-left: $tooltip-lr-padding-lg;
- padding-right: $tooltip-lr-padding-lg;
- }
- &.md-show-add {
- transition: $swift-ease-out;
- transition-duration: .2s;
- transform: scale(0);
- opacity: 0;
- }
- &.md-show, &.md-show-add-active {
- transform: scale(1);
- opacity: 0.9;
- transform-origin: center top;
- }
- &.md-show-remove {
- transition: $swift-ease-out;
- transition-duration: .2s;
- &.md-show-remove-active {
- transform: scale(0);
- opacity: 0;
- }
- }
+ margin-top: $tooltip-top-margin-sm;
+ }
+ &.md-origin-left {
+ transform-origin: right center;
+ margin-left: -$tooltip-top-margin-sm;
}
- &.md-hide {
- transition: $swift-ease-in;
+ @media (min-width: $layout-breakpoint-sm) {
+ font-size: $tooltip-fontsize-lg;
+ height: $tooltip-height-lg;
+ line-height: $tooltip-height-lg;
+ padding-right: $tooltip-lr-padding-lg;
+ padding-left: $tooltip-lr-padding-lg;
+ &.md-origin-top { margin-top: -$tooltip-top-margin-lg; }
+ &.md-origin-right { margin-left: $tooltip-top-margin-lg; }
+ &.md-origin-bottom { margin-top: $tooltip-top-margin-lg; }
+ &.md-origin-left { margin-left: -$tooltip-top-margin-lg; }
}
+ &.md-show-add {
+ transform: scale(0);
+ }
&.md-show {
transition: $swift-ease-out;
- pointer-events: auto;
+ transform: scale(1);
+ opacity: 0.9;
+ }
+ &.md-hide {
+ transition: $swift-ease-in;
+ transition-duration: .1s;
+ transform: scale(0);
+ opacity: 0;
}
}
diff --git a/src/components/tooltip/tooltip.spec.js b/src/components/tooltip/tooltip.spec.js
index c1427d843c..6b9cf6bf0d 100644
--- a/src/components/tooltip/tooltip.spec.js
+++ b/src/components/tooltip/tooltip.spec.js
@@ -1,20 +1,30 @@
-describe(' directive', function() {
- var $compile, $rootScope, $material, $timeout, $$mdTooltipRegistry;
+describe('MdTooltip Component', function() {
+ var $compile, $rootScope, $material, $timeout, $mdPanel, $$mdTooltipRegistry;
var element;
- beforeEach(module('material.components.tooltip'));
- beforeEach(module('material.components.button'));
- beforeEach(inject(function(_$compile_, _$rootScope_, _$material_, _$timeout_, _$$mdTooltipRegistry_){
- $compile = _$compile_;
- $rootScope = _$rootScope_;
- $material = _$material_;
- $timeout = _$timeout_;
- $$mdTooltipRegistry = _$$mdTooltipRegistry_;
- }));
+ var injectLocals = function($injector) {
+ $compile = $injector.get('$compile');
+ $rootScope = $injector.get('$rootScope');
+ $material = $injector.get('$material');
+ $timeout = $injector.get('$timeout');
+ $mdPanel = $injector.get('$mdPanel');
+ $$mdTooltipRegistry = $injector.get('$$mdTooltipRegistry');
+ };
+
+ beforeEach(function() {
+ module(
+ 'material.components.tooltip',
+ 'material.components.button'
+ );
+
+ inject(injectLocals);
+ });
+
afterEach(function() {
- // Make sure to remove/cleanup after each test
+ // Make sure to remove/cleanup after each test.
+ element.remove();
var scope = element && element.scope();
- scope && scope.$destroy();
+ scope && scope.$destroy;
element = undefined;
});
@@ -29,96 +39,97 @@ describe(' directive', function() {
}).not.toThrow();
});
- it('should set the position to "bottom", if it is undefined', function() {
+ it('should set the position to "bottom" if it is undefined', function() {
buildTooltip(
'' +
- 'Tooltip' +
+ 'Tooltip' +
''
);
- expect(findTooltip().attr('md-direction')).toBe('bottom');
+ expect(findTooltip()).toHaveClass('md-origin-bottom');
});
- it('should preserve parent text', function(){
- buildTooltip(
- '' +
- 'Hello' +
- 'Tooltip' +
- ''
- );
+ it('should preserve parent text', function() {
+ buildTooltip(
+ '' +
+ 'Hello' +
+ 'Tooltip' +
+ ''
+ );
- expect(element.text()).toBe("Hello");
+ expect(element.text()).toBe('Hello');
});
- it('should label parent', function(){
- buildTooltip(
- '' +
- '' +
- 'Tooltip' +
- ''+
- ''
- );
+ it('should label parent', function() {
+ buildTooltip(
+ '' +
+ '' +
+ 'Tooltip' +
+ '' +
+ ''
+ );
- expect(element.attr('aria-label')).toEqual('Tooltip');
+ expect(element.attr('aria-label')).toEqual('Tooltip');
});
- it('should interpolate the aria-label', function(){
- buildTooltip(
- '' +
- '{{ "hello" | uppercase }}' +
- ''
- );
+ it('should interpolate the aria-label', function() {
+ buildTooltip(
+ '' +
+ '{{ "hello" | uppercase }}' +
+ ''
+ );
- expect(element.attr('aria-label')).toBe('HELLO');
+ expect(element.attr('aria-label')).toBe('HELLO');
});
- it('should update the aria-label when the interpolated value changes', function(){
- buildTooltip(
- '' +
- '{{ testModel.ariaTest }}' +
- ''
- );
+ it('should update the aria-label when the interpolated value changes',
+ function() {
+ buildTooltip(
+ '' +
+ '{{ testModel.ariaText }}' +
+ ''
+ );
- $rootScope.$apply(function() {
- $rootScope.testModel.ariaTest = 'test 1';
- });
+ $rootScope.$apply(function() {
+ $rootScope.testModel.ariaText = 'test 1';
+ });
- expect(element.attr('aria-label')).toBe('test 1');
+ expect(element.attr('aria-label')).toBe('test 1');
- $rootScope.$apply(function() {
- $rootScope.testModel.ariaTest = 'test 2';
- });
+ $rootScope.$apply(function() {
+ $rootScope.testModel.ariaText = 'test 2';
+ });
- expect(element.attr('aria-label')).toBe('test 2');
- });
+ expect(element.attr('aria-label')).toBe('test 2');
+ });
- it('should not set parent to items with no pointer events', inject(function($window){
- spyOn($window, 'getComputedStyle').and.callFake(function(el) {
- return { 'pointer-events': el ? 'none' : '' };
- });
+ it('should not set parent to items with no pointer events',
+ inject(function($window) {
+ spyOn($window, 'getComputedStyle').and.callFake(function(el) {
+ return { 'pointer-events': el ? 'none' : '' };
+ });
- buildTooltip(
- '' +
- '' +
- '' +
- 'Hello world' +
- '' +
- '' +
- ''
- );
-
- triggerEvent('mouseenter', true);
- expect($rootScope.testModel.isVisible).toBeUndefined();
+ buildTooltip(
+ '' +
+ '' +
+ '' +
+ 'Hello world' +
+ '' +
+ '' +
+ ''
+ );
- }));
+ triggerEvent('mouseenter', true);
+ expect($rootScope.testModel.isVisible).toBeUndefined();
+ }));
it('should show after tooltipDelay ms', function() {
buildTooltip(
'' +
- 'Hello' +
- '' +
- 'Tooltip' +
- '' +
+ 'Hello' +
+ '' +
+ 'Tooltip' +
+ '' +
''
);
@@ -132,7 +143,6 @@ describe(' directive', function() {
// Total 300 == tooltipDelay
$timeout.flush(1);
expect($rootScope.testModel.isVisible).toBe(true);
-
});
it('should register itself with the $$mdTooltipRegistry', function() {
@@ -148,9 +158,7 @@ describe(' directive', function() {
});
describe('show and hide', function() {
-
- it('should show and hide when visible is set', function() {
-
+ it('should show and hide when visible is set', function() {
expect(findTooltip().length).toBe(0);
buildTooltip(
@@ -169,56 +177,60 @@ describe(' directive', function() {
showTooltip(false);
- expect(findTooltip().length).toBe(0);
+ expect(findTooltip().length).toBe(1);
+ expect(findTooltip().hasClass('md-hide')).toBe(true);
});
it('should set visible on mouseenter and mouseleave', function() {
- buildTooltip(
- '' +
- 'Hello' +
- '' +
- 'Tooltip' +
- '' +
- ''
- );
+ buildTooltip(
+ '' +
+ 'Hello' +
+ '' +
+ 'Tooltip' +
+ '' +
+ ''
+ );
- triggerEvent('mouseenter');
- expect($rootScope.testModel.isVisible).toBe(true);
+ triggerEvent('mouseenter');
+ expect($rootScope.testModel.isVisible).toBe(true);
- triggerEvent('mouseleave');
- expect($rootScope.testModel.isVisible).toBe(false);
+ triggerEvent('mouseleave');
+ expect($rootScope.testModel.isVisible).toBe(false);
});
- it('should should toggle visibility on the next touch', inject(function($document) {
- buildTooltip(
- '' +
- 'Hello' +
- '' +
- 'Tooltip' +
- '' +
- ''
- );
-
- triggerEvent('touchstart');
- expect($rootScope.testModel.isVisible).toBe(true);
- triggerEvent('touchend');
-
- $document.triggerHandler('touchend');
- $timeout.flush();
- expect($rootScope.testModel.isVisible).toBe(false);
- }));
+ it('should toggle visibility on the next touch',
+ inject(function($document) {
+ buildTooltip(
+ '' +
+ 'Hello' +
+ '' +
+ 'Tooltip' +
+ '' +
+ ''
+ );
+
+ triggerEvent('touchstart');
+ expect($rootScope.testModel.isVisible).toBe(true);
+ triggerEvent('touchend');
+
+ $document.triggerHandler('touchend');
+ $timeout.flush();
+ expect($rootScope.testModel.isVisible).toBe(false);
+ }));
it('should cancel when mouseleave was before the delay', function() {
buildTooltip(
'' +
'Hello' +
- '' +
+ '' +
'Tooltip' +
'' +
''
);
-
triggerEvent('mouseenter', true);
expect($rootScope.testModel.isVisible).toBeFalsy();
@@ -231,39 +243,27 @@ describe(' directive', function() {
expect($rootScope.testModel.isVisible).toBe(false);
});
- it('should not show when the text is empty', function() {
-
- expect(findTooltip().length).toBe(0);
-
+ it('should throw when the tooltip text is empty', function() {
buildTooltip(
'' +
'Hello' +
- '{{ textContent }} ' +
+ '' +
+ '{{ textContent }}' +
+ '' +
''
);
- showTooltip(true);
-
- expect(findTooltip().length).toBe(0);
-
- $rootScope.textContent = 'Tooltip';
- $rootScope.$apply();
-
- // Trigger a change on the model, otherwise the tooltip component can't detect the
- // change.
- showTooltip(false);
- showTooltip(true);
-
- expect(findTooltip().length).toBe(1);
- expect(findTooltip().hasClass('md-show')).toBe(true);
+ expect(function() {
+ showTooltip(true);
+ }).toThrow();
});
it('should set visible on focus and blur', function() {
buildTooltip(
'' +
- 'Hello' +
- '' +
- 'Tooltip' +
+ 'Hello' +
+ '' +
+ 'Tooltip' +
'' +
''
);
@@ -275,83 +275,68 @@ describe(' directive', function() {
expect($rootScope.testModel.isVisible).toBe(false);
});
- it('should not be visible on mousedown and then mouseleave', inject(function($document) {
- buildTooltip(
- '' +
- 'Hello' +
- '' +
- 'Tooltip' +
- '' +
- ''
- );
-
- // Append element to DOM so it can be set as activeElement.
- $document[0].body.appendChild(element[0]);
- element[0].focus();
- triggerEvent('focus,mousedown');
-
- expect($document[0].activeElement).toBe(element[0]);
- expect($rootScope.testModel.isVisible).toBe(true);
-
- triggerEvent('mouseleave');
- expect($rootScope.testModel.isVisible).toBe(false);
-
- // Clean up document.body.
- $document[0].body.removeChild(element[0]);
- }));
-
- it('should not be visible when the window is refocused', inject(function($window, $document) {
- buildTooltip(
- '' +
- 'Hello' +
- '' +
- 'Tooltip' +
- '' +
- ''
- );
-
- // Append element to DOM so it can be set as activeElement.
- $document[0].body.appendChild(element[0]);
- element[0].focus();
- triggerEvent('focus,mousedown');
- expect(document.activeElement).toBe(element[0]);
-
- triggerEvent('mouseleave');
-
- // Simulate tabbing away.
- angular.element($window).triggerHandler('blur');
-
- // Simulate focus event that occurs when tabbing back to the window.
- triggerEvent('focus');
- expect($rootScope.testModel.isVisible).toBe(false);
-
- // Clean up document.body.
- $document[0].body.removeChild(element[0]);
- }));
-
+ it('should not be visible on mousedown and then mouseleave',
+ inject(function($document) {
+ buildTooltip(
+ '' +
+ 'Hello' +
+ '' +
+ 'Tooltip' +
+ '' +
+ ''
+ );
+
+ // Append element to DOM so it can be set as activeElement.
+ $document[0].body.appendChild(element[0]);
+ element[0].focus();
+ triggerEvent('focus,mousedown');
+
+ expect($document[0].activeElement).toBe(element[0]);
+ expect($rootScope.testModel.isVisible).toBe(true);
+
+ triggerEvent('mouseleave');
+ expect($rootScope.testModel.isVisible).toBe(false);
+
+ // Clean up document.body.
+ // element.remove();
+ }));
+
+ it('should not be visible when the window is refocused',
+ inject(function($window, $document) {
+ buildTooltip(
+ '' +
+ 'Hello' +
+ '' +
+ 'Tooltip' +
+ '' +
+ ''
+ );
+
+ // Append element to DOM so it can be set as activeElement.
+ $document[0].body.appendChild(element[0]);
+ element[0].focus();
+ triggerEvent('focus,mousedown');
+ expect(document.activeElement).toBe(element[0]);
+
+ triggerEvent('mouseleave');
+
+ // Simulate tabbing away.
+ angular.element($window).triggerHandler('blur');
+
+ // Simulate focus event that occurs when tabbing back to the window.
+ triggerEvent('focus');
+ expect($rootScope.testModel.isVisible).toBe(false);
+
+ // Clean up document.body.
+ $document[0].body.removeChild(element[0]);
+ }));
});
describe('cleanup', function() {
- it('should clean up the scope if the parent was removed from the DOM', function() {
- buildTooltip(
- '' +
- 'Tooltip' +
- ''
- );
- var tooltip = findTooltip();
-
- expect(tooltip.length).toBe(1);
- expect(tooltip.scope()).toBeTruthy();
-
- element.remove();
- expect(tooltip.scope()).toBeUndefined();
- expect(findTooltip().length).toBe(0);
- });
-
it('should clean up if the parent scope was destroyed', function() {
buildTooltip(
'' +
- 'Tooltip' +
+ 'Tooltip' +
''
);
var tooltip = findTooltip();
@@ -367,7 +352,7 @@ describe(' directive', function() {
it('should remove the tooltip when its own scope is destroyed', function() {
buildTooltip(
'' +
- 'Tooltip' +
+ 'Tooltip' +
''
);
var tooltip = findTooltip();
@@ -377,32 +362,36 @@ describe(' directive', function() {
expect(findTooltip().length).toBe(0);
});
- it('should remove itself from the $$mdTooltipRegistry when it is destroyed', function() {
- buildTooltip(
- '' +
- 'Tooltip' +
- ''
- );
-
- spyOn($$mdTooltipRegistry, 'deregister');
- findTooltip().scope().$destroy();
- expect($$mdTooltipRegistry.deregister).toHaveBeenCalled();
- });
-
- it('should not re-appear if it was outside the DOM when the parent was removed', function() {
- buildTooltip(
- '' +
- 'Tooltip' +
- ''
- );
-
- showTooltip(false);
- expect(findTooltip().length).toBe(0);
-
- element.remove();
- showTooltip(true);
- expect(findTooltip().length).toBe(0);
- });
+ it('should remove itself from the $$mdTooltipRegistry when the parent ' +
+ 'scope is destroyed', function() {
+ buildTooltip(
+ '' +
+ 'Tooltip' +
+ ''
+ );
+
+ spyOn($$mdTooltipRegistry, 'deregister');
+ element.scope().$destroy();
+ expect($$mdTooltipRegistry.deregister).toHaveBeenCalled();
+ });
+
+ it('should not re-appear if it was outside the DOM when the parent was ' +
+ 'removed', function() {
+ buildTooltip(
+ '' +
+ '' +
+ 'Tooltip' +
+ '' +
+ ''
+ );
+
+ showTooltip(false);
+ expect(findTooltip().length).toBe(0);
+
+ element.remove();
+ showTooltip(true);
+ expect(findTooltip().length).toBe(0);
+ });
it('should unbind the parent listeners when it gets destroyed', function() {
buildTooltip(
@@ -425,7 +414,6 @@ describe(' directive', function() {
// ******************************************************
function buildTooltip(markup) {
-
element = $compile(markup)($rootScope);
$rootScope.testModel = {};
@@ -436,26 +424,31 @@ describe(' directive', function() {
}
function showTooltip(isVisible) {
- if (angular.isUndefined(isVisible)) isVisible = true;
-
- $rootScope.$apply('testModel.isVisible = ' + (isVisible ? 'true' : 'false') );
+ if (angular.isUndefined(isVisible)) {
+ isVisible = true;
+ }
+ $rootScope.testModel.isVisible = !!isVisible;
+ $rootScope.$apply();
$material.flushOutstandingAnimations();
}
function findTooltip() {
- return angular.element(document.body).find('md-tooltip');
+ return angular.element(document.querySelector('.md-tooltip'));
}
-
function triggerEvent(eventType, skipFlush) {
- angular.forEach(eventType.split(','),function(name) {
+ angular.forEach(eventType.split(','), function(name) {
element.triggerHandler(name);
});
!skipFlush && $timeout.flush();
}
-
});
+
+// ******************************************************
+// mdTooltipRegistry Testing
+// ******************************************************
+
describe('$$mdTooltipRegistry service', function() {
var tooltipRegistry, ngWindow;