feat(tooltip): Tooltip use MdPanel API #9742
Conversation
$material.flushOutstandingAnimations(); | ||
} | ||
|
||
function findTooltip() { | ||
return angular.element(document.body).find('md-tooltip'); | ||
// return angular.element(document.body).find('.md-tooltip'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary comment.
isVisible = true; | ||
} | ||
$rootScope.$apply( | ||
'testModel.isVisible = ' + (isVisible ? 'true' : 'false') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a bit ugly.
$rootScope.testModel.isVisible = !!isVisible;
$rootScope.$apply();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively it can also be done as:
$rootScope.$apply(function() {
$rootScope.testModel.isVisible = !!isVisible;
});
expect($rootScope.testModel.isVisible).toBe(false); | ||
|
||
// Clean up document.body. | ||
$document[0].body.removeChild(element[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be simplified to
element.remove();
module( | ||
'material.components.tooltip', | ||
'material.components.button', | ||
'material.components.panel' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Panel should be a dependency on the tooltip itself (so you can remove it here).
This is important for material-tools for example or general the modules we provide (closure or classic)
@@ -6,66 +6,69 @@ $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-max-width: rem(3.2) !default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had this comment on the previous PR as well - Why do we use rem
here? rem should be for font sizes.
// 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.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be marked as a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devversion Not necessarily. In the previous version of the tooltip, the tooltip would not show if there was no text assigned. This only adds the throw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why it should be marked as an breaking change. We intentionally added this behavior (enhancement) on request of users.
See:: #8021
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devversion I didn't realize. What is the necessary action to take for a breaking change? Just want to make sure that I handle it properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally you just amend the commit and add to the description
Breaking Change: Tooltips no longer show when the content is empty.
showTooltip(); | ||
} else { | ||
hideTooltip(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be simplified with a ternary operator.
isVisible ? showTooltip() : hideTooltip();
if ( | ||
autohide || | ||
mouseActive || | ||
$document[0].activeElement !== parent[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not look very good.
As per the Code guidelines you should start the autohide directly after the if (
.
if (autohide || mouseActive ..... // TO LONG
$document[0].activeElement !== parent[0]) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devversion This is something that I have been struggling with in my own code as well. Where in the Code Guidelines is this reference? I was unable to find it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich The specs are not anchor able and not very specific about things, but most of the time the code-sample explains a lot. (We also follow this kind of pattern in all other components - Provide consistency)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devversion I understand. I have found that the four-space continuation makes things slightly more unreadable compared to the opening of the parenthesis, but this is nothing that I will debate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich Yeah, I agree four spaces is a pain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helpful advice via Clang format (I ran it on the entire file - here's the result for this method):
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 (showTimeout) {
$timeout.cancel(showTimeout);
setVisible.queued = false;
showTimeout = null;
}
parent.off(LEAVE_EVENTS, leaveEventHandler);
parent.triggerHandler('blur');
setVisible(false);
}
mouseActive = false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So they do the four space continuation on the if
statement header, but after that, the inner functionality drops back to a single indentation. @ErinCoughlan, is this formatter something that I might have access to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the if statement is a continuation. The method itself isn't, so the 4 indentation doesn't apply.
npm install -g clang-format
will install it, but I don't know if we have special flags to format like the style guide.
restrict: 'E', | ||
// transclude: true, | ||
priority: 210, // Before ngAria | ||
// template: '<div class="md-content" ng-transclude></div>', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comments (above as well) if those are not valid anymore.
.module('material.components.tooltip', [ 'material.core' ]) | ||
.directive('mdTooltip', MdTooltipDirective) | ||
.service('$$mdTooltipRegistry', MdTooltipRegistry); | ||
.module('material.components.tooltip', ['material.core']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in another comment, this should depend on the panel module as well.
.md-content { | ||
background-color: '{{background-700}}'; | ||
.md-tooltip.md-THEME_NAME-theme { | ||
color: '{{background-A100}}'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why were the colors changed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@crisbeto That is a good question. I am not sure, but the result was the same. I will have to look into why I changed them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The colors shouldn't be changed here. It seems you didn't rebase properly here after this PR:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@clshortfuse The result of the tooltip using the colors that I have specified is the same exact color scheme of the tooltips that are already present in the material demos. Changing them back to the background-700
color doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich I don't know what you mean by "have the same color scheme", but the material design spec says background-700
. https://material.google.com/components/tooltips.html#tooltips-tooltips-desktop-
By having these colors changed back, you'd be reintroducing old issues with dark themes and non-gray background palettes.
var attachTo = angular.element(document.body); | ||
var origin = null; | ||
var positions = ['top', 'right', 'bottom', 'left']; | ||
var position = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could combine all of these variables that are initialized to null
on one line, e.g. var origin, position, autohide...
. Should shorten it down a bit.
origin = 'md-origin-' + scope.mdDirection; | ||
|
||
// Create the position of the panel based off of the mdDirection. | ||
switch (scope.mdDirection) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a little more concise if we used an enum for the mappings, e.g.:
var POSITIONS = {
top: { x: 'CENTER', y: 'ABOVE' }
...
};
position = POSITIONS[scope.mdDirection];
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've got a typo here. It's supposed to be 'MutationObserver' in $window
, instead of 'mutationObserver' in $window
.
} | ||
panelConfig = { | ||
attachTo: attachTo, | ||
template: element.html().trim(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to trim the HTML here?
isVisible = true; | ||
} | ||
$rootScope.$apply( | ||
'testModel.isVisible = ' + (isVisible ? 'true' : 'false') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively it can also be done as:
$rootScope.$apply(function() {
$rootScope.testModel.isVisible = !!isVisible;
});
function linkFunc(scope, element, attr) { | ||
// Set constants. | ||
var parent = $mdUtil.getParentWithPointerEvents(element); | ||
var attachTo = angular.element(document.body); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I'm seeing, attachTo
is only being used once in the mdPanel
config. It might be more appropriate to inline it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@crisbeto Good catch. I have removed some others that were also only used in one place.
c9f476a
to
608bb4e
Compare
I have updated everything requested, but GitHub must have changed something. Normally, the inline comments would disappear after the commented line was changed in the next pushed commit. Now, all of your comments are sticking around... |
Overall LGTM on the second look. I'll try to do some manual testing and final review tomorrow. |
$tooltip-height-sm: 32px !default; | ||
$tooltip-top-margin-lg: 14px !default; | ||
$tooltip-top-margin-sm: 24px !default; | ||
$tooltip-lr-padding-lg: 08px !default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be 8px
(without the leading zero).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good. A lot of the code disappeared, which is cool.
I would love to see Material binary size change as all the components get converted.
* 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 {expression=} md-visible Boolean bound to whether the tooltip is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
boolean=
$mdUtil.nextTick(function() { | ||
setVisible(false); | ||
}); | ||
if (mutations.some(function(mutation) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a tad confusing in an if statement. Consider making a function (isDisabled or something) to check this.
var panelAnimation = $mdPanel.newPanelAnimation() | ||
.openFrom(parent) | ||
.closeTo(parent) | ||
.withAnimation({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would using the built in scale animation with a custom duration work in place of this?
Ideally, I'd want to have to use custom classes rarely, so if there's a way to make panel smarter to cover tooltips, I think it should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ErinCoughlan I haven't had a chance to study the new animation work that you and Kristiyan have put together. Can we add a future issue to Github to go back and add the new animation handlers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich I had meant the animations from 6 months ago when the component was initially created, but I'm okay with deferring.
panelClass: 'md-tooltip ' + origin, | ||
animation: panelAnimation, | ||
position: panelPosition, | ||
zIndex: 100, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point, consider making this customizable for uses of the tooltip. It's a problem my team has run into several times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I am adding this now. This actually came up in a demo with Topher.
if ( !scope.visible ) return; | ||
var panelConfig = { | ||
attachTo: attachTo, | ||
template: element.html(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we implement the content element in panel to make this easier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ErinCoughlan In this case, I am grabbing the internal content of the <md-tooltip></md-tooltip>
element and transposing it into the panel element. I think that it would complicate things to introduce the content element when all you want is the tooltip text here.
* @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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!Event
* @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] || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a horrible variable name. Variable names should be descriptive of what it contains, not it's type. (I know if was here before, but consider changing to handlers or listenersForType.)
pointer-events: none; | ||
border-radius: 4px; | ||
overflow: hidden; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's with all the new lines between "sections"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and removed 90% of them.
608bb4e
to
21678ad
Compare
$$mdTooltipRegistry.deregister('blur', windowBlurHandler); | ||
$$mdTooltipRegistry.deregister('resize', debouncedOnResize); | ||
function isDisabledMutation() { | ||
mutations.some(function(mutation) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich This throws an error. Seems like you forgot to pass in the mutations
.
|
||
updateContentOrigin(); | ||
positionTooltip(); | ||
panelRef = $mdPanel.create(panelConfig); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems very inefficient, because it creates a new panel ref on every open. I think we should create one only after the first open and then re-use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct about the inefficiency. I forgot to add the ID parameter to the panel configuration. That will make it only create the panel once for each of the tooltips.
@bradrich it seems like the margins on the tooltip has changed. Here's a side-by-side (left is what the current tooltip looks like): |
@crisbeto You are correct about the new spacing. According to the Material Guidelines, there is supposed to be 14 pixels between the element and the tooltip on a desktop. 24 pixels if you are on a mobile device. I have positioned the tooltip according to those requirements. |
21678ad
to
238b5a1
Compare
@crisbeto @ErinCoughlan Please take a look at the final revisions and provide an LGTM if everything is GTG. Thank you. |
@crisbeto , @ErinCoughlan - ping request for lgtm. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did another review and code-wise it LGTM, apart from the minor comment about the id generation, but that isn't something blocking.
Functionality-wise, I noticed that doing a lot of enter/leave actions causes unexpected behavior. E.g. in this case once my mouse stops on the trigger, the tooltip is supposed to stay visible:
For reference, here's what happens with the current implementation:
I don't know whether this isn't an mdPanel
issue where the animations get stacked up.
|
||
// Convert the content to camelCase to create the tooltip panel id. | ||
// Note: This will also remove any non-alpha characters. | ||
var id = content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich Why do we have to use the content to generate an id for the panel? This is seems like a lot of trouble for something that could easily be done with "tooltip-" + $mdUtil.nextUid()
, in addition to potentially having multiple tooltips with the same id, if they have the same content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@crisbeto This is a very good point. I have made this alteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradrich - please debounce the showing/hiding of the tooltip.
238b5a1
to
9ee73b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM/Approval after the comments are addressed.
visible: '=?mdVisible', | ||
autohide: '=?mdAutohide', | ||
direction: '@?mdDirection' // only expect raw or interpolated string value; not expression | ||
mdZIndex: '=?mdZIndex', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional: since you are no longer renaming these, you don't need to write the attribute name again:
mdZIndex: '=?',
mdDelay: '=?',
...
var TOOLTIP_DEFAULT_SHOW_DELAY = 0; | ||
var TOOLTIP_DEFAULT_DIRECTION = 'bottom'; | ||
var TOOLTIP_DIRECTIONS = { | ||
top: { x: 'CENTER', y: 'ABOVE' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is be more robust if you use the enum defined in mdPanel instead of writing them here. If, for instance, I decide that the text shouldn't be capitalized and change it, this entire component will break and it shouldn't.
expect($rootScope.testModel.isVisible).toBe(false); | ||
|
||
// Clean up document.body. | ||
element.remove(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to do something like this in the afterEach
to be extra safe from someone forgetting to clean up?
1f1aa13
to
2350930
Compare
@bradrich - ping regard last requests from @ErinCoughlan . |
@ThomasBurleson All clear! |
2350930
to
4ca0d64
Compare
@ThomasBurleson This one is all clear and good to go. Conflicts have been resolved. |
&.md-show { | ||
transition: $swift-ease-out; | ||
pointer-events: auto; | ||
transform: scale(1); | ||
opacity: 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opacity should be 0.9
(90%) as in master
4ca0d64
to
1bc01da
Compare
Tooltip has been refactored to use the MdPanel API to position, animate open and closed, and show the tooltip as a result of user interaction. Breaking Change: Tooltips with no content will no longer show and result in an error. Fixes angular#9563
1bc01da
to
ffa32b1
Compare
@ThomasBurleson Checking on the status of this PR. |
Tooltip has been refactored to use the MdPanel API to position, animate open and closed, and show the tooltip as a result of user interaction.
Breaking Change: Tooltips with not content will no longer show and result in an error.
Fixes #9563