-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feature(mat-experimental/mat-feature-highlight): Port Feature Highlight #18170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
to open source Angular from Google Domains code.
@jelbourn there seems to be some kind of lint check failing for a code owner for this. Who should I assign as the code owner? |
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.
First pass of comments
For the code owner, feel free to put yourself if you want and me
* refer to left and right in an LTR context and vice versa in an RTL context. | ||
*/ | ||
export type FeatureHighlightCalloutPosition = | ||
'top_start'|'top_end'|'bottom_start'|'bottom_end'; |
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 rest of Angular Material uses hyphens for word separators in string literal types, so we should be consistent here
(here and for other string literal types below)
import {BasePortalOutlet, CdkPortalOutlet, ComponentPortal, TemplatePortal} from '@angular/cdk/portal'; | ||
import {Component, ComponentRef, EmbeddedViewRef, OnDestroy, ViewChild} from '@angular/core'; | ||
|
||
/** Container for the callout component of feature highlight. */ |
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.
Could you expand this description a bit to explain how the component is used?
@@ -0,0 +1 @@ | |||
<ng-template cdkPortalOutlet></ng-template> |
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: could inline this since it's just one line
|
||
private _assertNotAttached() { | ||
if (this.portalOutlet.hasAttached()) { | ||
throw new Error( |
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.
nit: we omit the new
on Error since it does the same thing without it (which is different from the internal style guide)
(here and elsewhere)
* Left value of the callout, relative to the target element. Used only when | ||
* isOuterCircleBounded is not true. | ||
*/ | ||
readonly calloutLeft?: string|number; |
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.
Rather than left
/right
, we try to use start
/end
so that the name applies in both LTR and RTL. Would it make sense for this to be something like calloutStartCoordinate
?
* feature highlight elements. | ||
*/ | ||
@Directive({ | ||
selector: `[feature-highlight-target], |
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 selectors and class name here should start with Mat
, as well as any associated classes that apply to this class.
I also debated with myself about omitting "target" from the selector so it's more like [matTooltip]
, but then the class name would need to match, and then it would conflict with the service name. Another alternative would be something like [matHighlightedFeature]
, but I'm not sure if it's too similar and would be confusing. @angular/angular-components what do you all think?
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 wanted to get a better feel for how this component and related directives worked before providing feedback on this, but I couldn't find any examples in this PR.
@Directive({ | ||
selector: `[feature-highlight-target], | ||
[featureHighlightTarget], | ||
feature-highlight-target`, |
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.
Is there a specific case for the element selector, or could we exclusively use the attribute selector?
}, | ||
}) | ||
export class FeatureHighlightTarget { | ||
@Input() featureHighlightEnabled = true; |
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.
We do directive inputs with alises so that the class member stays short, but the template name is unambiguous:
@Input('matFeatureHighlightTargetDisabled')
disabled: boolean;
}, | ||
}) | ||
export class FeatureHighlightTarget { | ||
@Input() featureHighlightEnabled = true; |
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.
We prefer using disabled
that defaults to false
rather than enabled
that defaults to true
. For boolean inputs, we also always use a setter so we can use coerceBooleanProperty
from cdk/coercion
. This lets users apply the value like <button matFeatureHighlightTargetDisabled>
without a value, the same as the native disabled
, required
, etc.
*/ | ||
get zIndex(): string|null { | ||
return this.featureHighlightEnabled ? '1001' : 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.
We prefer using methods rather than getters for anything that doesn't need to be a bindable property since the getter generates a bit more es5 code.
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 have a link to an example where this component is used?
If not, is it possible to add an example and include it in the src/dev-app
?
Examples normally go in src/components-examples/material-experimental
and are later moved to src/components-examples/material
when the component moves out of experimental.
Some kind of basic explanation in a .md
file would be helpful as well since GitHub doesn't really organize these files in any kind of way to put the most helpful comments at the top. 😀
* feature highlight elements. | ||
*/ | ||
@Directive({ | ||
selector: `[feature-highlight-target], |
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 wanted to get a better feel for how this component and related directives worked before providing feedback on this, but I couldn't find any examples in this PR.
} | ||
|
||
/** | ||
* Open a feature highlight with callout as a component. |
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's not 100% clear to me what a "callout" is. Perhaps this wouldn't be an issue if there was a feature-highlight.md
file with some details about what the component does and how the different parts (and vocab) fit together.
}); | ||
|
||
TestBed.compileComponents(); | ||
fixture = TestBed.createComponent(CalloutHostComponent); |
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 you switch the tests so each individual one creates the component itself? Creating it like this in a beforeEach
makes it difficult to add new tests that use a different template. You can see an example in drag.spec.ts
.
`, | ||
}) | ||
class CalloutHostComponent { | ||
@ViewChild('calloutContainer', {static: true}) |
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.
We should avoid adding new code that depends on static queries. You can avoid it in this case by calling detectChanges
after creating the test component.
/** Container for the callout component of feature highlight. */ | ||
@Component({ | ||
selector: 'feature-highlight-callout-container', | ||
templateUrl: './feature-highlight-callout-container.ng.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.
We have a lint rule for this, but I think that it that previous failures meant that the CI never got far enough to log it.
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
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 mat-
prefix should be removed from the directory name.
/** Container for the callout component of feature highlight. */ | ||
@Component({ | ||
selector: 'feature-highlight-callout-container', | ||
templateUrl: './feature-highlight-callout-container.ng.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.
Remove the .ng
from the file name.
@Output() afterDismissed = new EventEmitter<void>(); | ||
|
||
@ViewChild('rootDiv', {static: true}) rootDiv!: ElementRef<HTMLElement>; | ||
@ViewChild('innerCircle', {static: true}) |
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.
We should avoid adding new code with static queries.
* is equal to the diagonal length of the box, should the diameter be auto | ||
* determined. | ||
*/ | ||
private _positionOuterCircle() { |
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 method is somewhat long. Can it be broken up into a couple of helpers?
private _changeAnimationState(newState: State) { | ||
requestAnimationFrame(() => { | ||
this.state = newState; | ||
this._changeDetectorRef.markForCheck(); |
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 this is redundant since requestAnimationFrame
triggers change detection.
|
||
// TODO(b/138400499) - Ideally we should emit afterOpened event when the | ||
// transitionend is triggered. However it doesn't look like catalyst flush() | ||
// triggers the transitionend event so there is no way to test the behavior. |
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 behavior can be tested with the CDK. You can check out some tests in drag.spec.ts
that flush transitionend
.
FeatureHighlightCalloutContainer, | ||
], | ||
}) | ||
export class FeatureHighlightModule { |
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 name should be prefixed with Mat
.
Closing due to inactivity after review was provided. If you'd like to work on landing this, feel free to re-open a PR with the comments addressed |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This PR adds the feature highlight component that Google Domains uses to angular/components.
This is a straight port with minimal changes made so that the current usages can be more easily migrated.
Initial design for this component was approved by @jelbourn internally.
Once this is submitted and synced I will migrate Google Domains usages to this component.