-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
Add chips keyboard support. #2046
Add chips keyboard support. #2046
Conversation
FYI the ListKeyManager fix was already merged in with #2009. |
Looks like you have one failure on IE11 |
@jelbourn I'll look into the failure tonight. In the meantime, can you do a code review so I can make any additional changes you find? |
.md-chip { | ||
background-color: #e0e0e0; | ||
color: rgba(0, 0, 0, 0.87); | ||
} | ||
|
||
.md-chip.selected { | ||
// TODO: Based on spec, this should be #808080, but we can only use md-contrast with a palette | ||
background-color: md-color($md-grey, 600); | ||
color: md-contrast($md-grey, 600); |
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 fine to use a different color so long as it's handled for both light and dark theme.
(given it's not affected by primary/accent)
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 can use a custom background color, but is there a good way to do the md-contrast()
with a standard color instead of the palette color? If not, I guess I can manually calculate it based on the spec.
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 contrast colors aren't calculated, it's just whatever a human designer thought looked "right". Where in the spec does it say #808080
?
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 guess it's never "stated" in the spec; I just used a color picker and pulled it from the spec images (which all appear to be consistent).
* | ||
* @type Component | ||
*/ | ||
export const MD_CHIP_LIST_COMPONENT_CONFIG: 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.
Kara and I chatted about this for a bit and decided that we'd rather duplicate the component metadata for now rather than breaking it out like this. Couple of reasons for this:
- It adds indirection to where the actual setup for the component lives
- The
host
object is effectively static; you'd have to duplicate the whole thing anyway if you wanted to add a property. Withinputs
andoutputs
, the metadata extractor does support concatenation, but there's no equivalent for objects yet (though that is in the pipeline). - Doing
outputs: ['focus: onFocus']
doesn't seem to work (it should work like@Output('focus') onFocus
, possibly a bug in core).
|
||
/** | ||
* The chip components contained within this chip list. | ||
*/ |
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 can smush short description like this like:
/** The chip components contained within this chip list. */
|
||
/******************** | ||
* EVENTS | ||
********************/ |
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 consider section delimiting comments like this to be a code smell. In general, if the file is big enough to need them, you should refactor. (this file isn't big enough to need them)
|
||
/** | ||
* Emitted when the chip receives focus. | ||
* @type {EventEmitter} |
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.
Omit all types from JsDoc. These are captured by TypeScript.
* Emitted when the chip is destroyed. | ||
* @type {EventEmitter} | ||
*/ | ||
public destroy = new EventEmitter(); |
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.
No need to include the public
keyword; we always omit it since it's the default
* Emitted when the chip receives focus. | ||
* @type {EventEmitter} | ||
*/ | ||
public didfocus = new EventEmitter(); |
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.
EventEmitter
needs a generic type. If you're never passing any data to it, use void
. If you're passing data, create a type to serve as the event object.
* | ||
* @returns {String} | ||
*/ | ||
get isAriaDisabled(): string { |
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 internal (start with an underscore but not use the private
keyword)
* @param value `true` to disable, `false` to enable. | ||
*/ | ||
set disabled(value: boolean) { | ||
this._disabled = (value === false || value === undefined) ? null : 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.
Use coerceBooleanProperty
Don't need @param
JsDoc for setters
* | ||
* @param event | ||
*/ | ||
click(event: Event) { |
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 something like
_handleClick() {
if (!this.disabled) {
this.focus();
}
}
focus() {
this._renderer.invokeElementMethod(this._elementRef.nativeElement, 'focus');
}
You shouldn't need the focus
Output. The native focus event on the chip element should work fine.
http://plnkr.co/edit/wW5Ux0yc3JlgTZZATGrQ?p=preview
1d96636
to
9183484
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.
FYI, probably obsolete comments
* keyboard navigation are properly handled. | ||
*/ | ||
export class ChipListKeyManager extends ListKeyManager { | ||
private _subscribed: MdBasicChip[] = []; |
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.
Needs field description
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 also think a WeakMap
would be better for what you're doing (WeakSet
would be best, but that's not supported in IE11).
4041670
to
b551fa8
Compare
/** Track which chips we're listening to for focus/destruction. */ | ||
private _subscribed: WeakMap<MdChip, Boolean> = new WeakMap(); | ||
|
||
/** INTERNAL - The ListKeyManager which handles focus. */ |
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.
Don't need INTERNAL
- it's implied by the underscore
@@ -1,47 +1,120 @@ | |||
// http://plnkr.co/edit/6Nnren92D7C0zvRf0mwY?p=preview |
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.
Omit this plunker link
/** | ||
* Emitted when the chip is destroyed. | ||
*/ | ||
@Output('destroy') destroy = new EventEmitter<MdChipEvent>(); |
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.
Don't need to give a string name if it has the same name as the property
ngOnInit(): void { | ||
let el: HTMLElement = this._elementRef.nativeElement; | ||
|
||
if (el.tagName == 'MD-CHIP' || el.hasAttribute('md-chip')) { |
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.
Prefer nodeName
to tagName
and use toLowerCase()
* @param index The index of the item to be focused. | ||
* @param focusElement Whether or not to focus the element as well. Defaults to `true`. | ||
*/ | ||
setFocus(index: number, focusElement = true): void { |
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 we break this into two methods? I think it would be better to have focusItem(index)
and updateFocusedIndex(index)
where the former sets focus and the latter only updates the index.
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 this just be set focusedItemIndex()
?
I REALLY don't like doing manager.focusedItemIndex = ...
, so I would prefer your suggestion of updateFocusedItemIndex(index)
, but I was putting it directly below the getter and it felt a bit weird.
Thoughts?
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.
Actually, focus
should be a no-op if the item is already focused. Why not just let it run again?
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'm not sure it is a no-op. I think it was emitting the events twice.
Also, now that I dug into the code, I'm not sure we want to change this. The majority of places assume it does both; my chips is a special case where we don't want to focus it (because it happens on click; so it already has focus).
Separating this forces users to call two functions the majority of the time which feels odd.
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.
Calling this in order to perform the focus should still update the index, I was just proposing we add a new method that only updates the index.
b551fa8
to
7ee819c
Compare
@jelbourn Requested updates have been made. Let me know if you see anything else! |
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, just a last few style fixes I missed last time. Add the merge_ready
label when you're done.
export class MdChipList implements AfterContentInit { | ||
|
||
/** Track which chips we're listening to for focus/destruction. */ | ||
private _subscribed: WeakMap<MdChip, Boolean> = new WeakMap(); |
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
lower case
@@ -13,6 +13,11 @@ | |||
} | |||
|
|||
.md-chip.selected { | |||
background-color: #808080; |
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.
Add comment explaining that this is the same color in both light and dark themes.
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 can definitely do that; but do we have any specs for a dark theme? I think it would be nice to change it as the current chips in dark mode do look a bit out of place.
/** Pass relevant key presses to our key manager. */ | ||
keydown(event: KeyboardEvent) { | ||
this._keyManager.onKeydown(event); | ||
} |
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 don't actually need these two events: you can call _keyManager.focusFirstItem()
and _keyManager.onKeydown($event)
directly in the host binding.
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'll change the focus, but I do need the onKeydown for the selection/deletion support that is coming soon in the next PR.
* @param chips The list of chips to be subscribed. | ||
*/ | ||
protected subscribeChips(chips: QueryList<MdChip>): void { | ||
chips.forEach((chip: MdChip) => { |
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 shouldn't need the type here, it should be inferred. Also can be condensed to
chips.forEach(chip => this.addChip(chip));
@@ -1,17 +1,96 @@ | |||
import { Component, ElementRef, Renderer } from '@angular/core'; | |||
import { | |||
// Classes |
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.
Don't need comments like this in imports.
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { Component, DebugElement, QueryList } from '@angular/core'; | ||
import { By } from '@angular/platform-browser'; | ||
import { MdChip, MdChipList, MdChipsModule } from './index'; |
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.
No spaces inside { }
like this; there's a WebStorm setting for this.
6ed7552
to
3a58643
Compare
3a58643
to
d0c0d65
Compare
Add basic focus/keyboard support for chips. - Up/down arrows navigate chips. - Clicking a chip properly focuses it for subsequent keyboard navigation. - More demos. Confirmed AoT compatibility. References angular#120.
d0c0d65
to
b138e66
Compare
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. |
Add basic focus/keyboard support for chips.
Up/down arrows navigate chips.
Clicking a chip properly focuses it for subsequent keyboard
navigation.
More demos.
Linter passes
Tests pass
Confirmed AoT compatibility
References #120.
Demo Page Screen Shot