Skip to content
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

Merged

Conversation

topherfangio
Copy link
Contributor

@topherfangio topherfangio commented Dec 1, 2016

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

screen shot 2016-11-30 at 10 12 00 am

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Dec 1, 2016
@crisbeto
Copy link
Member

crisbeto commented Dec 1, 2016

FYI the ListKeyManager fix was already merged in with #2009.

@jelbourn
Copy link
Member

jelbourn commented Dec 1, 2016

Looks like you have one failure on IE11

@topherfangio
Copy link
Contributor Author

@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);
Copy link
Member

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)

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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 = {
Copy link
Member

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. With inputs and outputs, 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.
*/
Copy link
Member

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
********************/
Copy link
Member

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}
Copy link
Member

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();
Copy link
Member

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();
Copy link
Member

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 {
Copy link
Member

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;
Copy link
Member

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) {
Copy link
Member

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

@topherfangio topherfangio force-pushed the team/topher/chips-keyboard-focus-120 branch 2 times, most recently from 1d96636 to 9183484 Compare December 1, 2016 22:17
Copy link
Member

@jelbourn jelbourn left a 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[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs field description

Copy link
Member

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).

@topherfangio topherfangio force-pushed the team/topher/chips-keyboard-focus-120 branch 3 times, most recently from 4041670 to b551fa8 Compare December 9, 2016 17:51
/** Track which chips we're listening to for focus/destruction. */
private _subscribed: WeakMap<MdChip, Boolean> = new WeakMap();

/** INTERNAL - The ListKeyManager which handles focus. */
Copy link
Member

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
Copy link
Member

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>();
Copy link
Member

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')) {
Copy link
Member

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 {
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

@topherfangio topherfangio force-pushed the team/topher/chips-keyboard-focus-120 branch from b551fa8 to 7ee819c Compare December 13, 2016 16:30
@topherfangio
Copy link
Contributor Author

@jelbourn Requested updates have been made. Let me know if you see anything else!

Copy link
Member

@jelbourn jelbourn left a 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();
Copy link
Member

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;
Copy link
Member

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.

Copy link
Contributor Author

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);
}
Copy link
Member

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.

Copy link
Contributor Author

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) => {
Copy link
Member

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
Copy link
Member

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';
Copy link
Member

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.

@topherfangio topherfangio force-pushed the team/topher/chips-keyboard-focus-120 branch 2 times, most recently from 6ed7552 to 3a58643 Compare December 13, 2016 18:17
@topherfangio topherfangio added the action: merge The PR is ready for merge by the caretaker label Dec 13, 2016
@topherfangio topherfangio force-pushed the team/topher/chips-keyboard-focus-120 branch from 3a58643 to d0c0d65 Compare December 13, 2016 20:34
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.
@topherfangio topherfangio force-pushed the team/topher/chips-keyboard-focus-120 branch from d0c0d65 to b138e66 Compare December 13, 2016 23:03
@jelbourn jelbourn merged commit ba85883 into angular:master Dec 14, 2016
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants