-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
feat(chips): Add remove functionality/styling. #2476
feat(chips): Add remove functionality/styling. #2476
Conversation
src/demo-app/chips/chips-demo.ts
Outdated
@@ -36,6 +38,9 @@ export class ChipsDemo { | |||
{ name: 'Warn', color: 'warn' } | |||
]; | |||
|
|||
constructor() { | |||
} |
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 constructor unnecessary?
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; I think it got added by accident or something. I'll remove it.
src/demo-app/chips/chips-demo.ts
Outdated
@@ -47,6 +52,16 @@ export class ChipsDemo { | |||
} | |||
} | |||
|
|||
remove(person: Person): void { | |||
if (person) { |
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 check can be dropped, because TypeScript already validates the person is not undefined.
Also the indexOf
would just return -1
if it's really undefined.
src/lib/chips/chip-list.spec.ts
Outdated
// Focus the middle item | ||
midItem.focus(); | ||
it('focuses the first chip on focus', () => { | ||
let FOCUS_EVENT: Event = {} as 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.
Looks like you are casting it twice.
|
||
it('BACKSPACE focuses the last chip', () => { | ||
let nativeInput = chipListNativeElement.querySelector('input'); | ||
let BACKSPACE_EVENT: KeyboardEvent = new FakeKeyboardEvent(BACKSPACE, nativeInput) as any; |
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.
Same as above. The cast to any
seems unnecessary 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.
The first one is declaring the type; not casting it. When attempting to cast it to KeyboardEvent
it requires me to override everything in KeyboardEvent
so @jelbourn told me to just us any
for now.
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.
Hm, you should be able to do.
let backspaceEvent = new FakeKeyboardEvent(BACKSPACE, nativeInput) as KeyboardEvent;
as in this example
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 So, it looks like you are correct and this does indeed compile/run correctly, it's just that Webstorm is showing it as an error. I guess I need to play with my settings to figure out why.
40ff7d9
to
95316e9
Compare
Please hold off on reviewing this; I accidentally pushed some focus code into this branch that needs to be removed. Will update when done, but I'm working on some md1 issues at the moment. |
95316e9
to
47f528e
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.
I started reviewing this before I saw your last comment 😫 Some of the comments might be useful, though
src/lib/chips/chip-list.ts
Outdated
@@ -49,69 +49,125 @@ import {SPACE, LEFT_ARROW, RIGHT_ARROW} from '../core/keyboard/keycodes'; | |||
}) | |||
export class MdChipList implements AfterContentInit { | |||
|
|||
/** Track which chips we're listening to for focus/destruction. */ | |||
private _subscribed: WeakMap<MdChip, boolean> = new WeakMap(); | |||
/** Whether or not the chip list is currently focusable via keyboard interaction. */ |
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 isn't a boolean, so Whether...
isn't really the right way to phrase it. I think it's fine to say "tabIndex of the chip-list element."
src/lib/chips/chip-list.ts
Outdated
protected _tabIndex = -1; | ||
|
||
/** When a chip is destroyed, we track the index so we can focus the appropriate next chip. */ | ||
protected _destroyedIndex: number = 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.
Why store this instead of moving the focus immediately?
*/ | ||
protected _checkTabIndex(): void { | ||
// If we have 0 chips, we should not allow keyboard focus | ||
this._tabIndex = (this.chips.length == 0 ? -1 : 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.
Instead of manually calling _checkTabIndex
and updating _tabIndex
, why not just create a host binding like
host: {
'[attr.tabindex]': 'chips.length ? 0 : -1'
}
However, this should also be overridden by any user-provided tabIndex
value, so that makes it a bit more complex.
src/lib/chips/chip-list.ts
Outdated
* | ||
* TODO: Remove when md-input-container allows md-empty configuration. | ||
* */ | ||
protected _hackLabelElement() { |
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.
cc @mmalerba
What is it that you need the input to do here? I'd rather avoid merging something like this.
|
||
@Component({ | ||
template: ` | ||
<md-input-container> |
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.
cc @mmalerba - thoughts?
I'm not sure putting the chip-list inside of the input-container is the approach we ultimately want to take. Right now I don't think the input-container is in itself designed to have any content other than the input/textarea and friends.
I know we want to make the chips be part of the input's underline, but perhaps there's another way we could approach that.
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 do want to support this sort of thing in the future (the mdInput
directive would go on the md-chip-list
though and md-chips-list
would have to provide some input-like interface for mdInput
to work with). For now though what about something like this:
<md-input-container>
<md-chip-list md-prefix>
<md-chip>Chip 1</md-chip>
...
</md-chip-list>
<input mdInput>
</md-input-container>
src/lib/chips/chip-list.ts
Outdated
|
||
/** The ListKeyManager which handles focus. */ | ||
_keyManager: ListKeyManager; | ||
|
||
/** The chip components contained within this chip list. */ | ||
chips: QueryList<MdChip>; | ||
|
||
constructor(private _elementRef: ElementRef) { } | ||
constructor(protected _renderer: Renderer, protected _elementRef: ElementRef, | ||
@Optional() protected _mdInputContainer: MdInputContainer) { |
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 far we've been trying to avoid introducing any inter-component dependencies. In this case, I don't think we want to tightly couple the md-input with the chip-list, since people may use chips in combination with their own custom input (which is something Jen's team wants to do).
A less-coupled approach would be to create an interface like ChipInput
, and accept that as an @Input
(and making sure that either MdInput
or MdInputContainer
conforms to that interface. Then we could connect the two without introducing a dependency.
47f528e
to
68aa7b5
Compare
Is there any progress on chips? Now that autocomplete is more or less done, it'd be nice to start working on https://material.angularjs.org/latest/demo/chips Is it assigned to someone? I already have my own version working, I could try and make a PR.. |
@fxck I am the developer assigned and am indeed working on the chips. I just took some time off to have a baby and am getting back to it :-) I pushed some updates yesterday and should have more today. Hoping to have a mostly final version by this weekend, but it will need to go through the review process before being merged to master. |
5794658
to
f81b595
Compare
src/lib/chips/_chips-theme.scss
Outdated
$unselected-foreground: if($is-dark-theme, mat-color($foreground, text), $light-foreground); | ||
|
||
$selected-background: if($is-dark-theme, mat-color($background, app-bar), #808080); | ||
$selected-foreground: if($is-dark-theme, mat-color($foreground, text), $light-selected-foreground); | ||
|
||
$focus-color: mat-color($foreground, text); |
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 about using mat-color($foreground, secondary-text)
instead? It would match the borders of the checkbox and radio components.
1bc5658
to
44a73d2
Compare
@kara @devversion Can you guys please review this updated PR at your convenience? It adds the |
|
||
// Fix the label offset | ||
.mat-input-container mat-chip-list ~ label.mat-empty { | ||
transform: translateY(22px); |
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.
Where are the 22px
coming from?
.mat-input-container .mat-chip-list-wrapper input { | ||
width: auto; | ||
height: 38px; | ||
margin-left: 8px; |
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 way we can move those hard-coded values to variables?
And above for .mat-chip-remove
, couldn't we calculate those left
, right
values?
src/lib/chips/chip.ts
Outdated
@@ -145,6 +246,7 @@ export class MdChip implements Focusable, OnInit, OnDestroy { | |||
if (el.nodeName.toLowerCase() == 'mat-basic-chip' || el.hasAttribute('mat-basic-chip') || | |||
el.nodeName.toLowerCase() == 'md-basic-chip' || el.hasAttribute('md-basic-chip')) { | |||
el.classList.add('mat-basic-chip'); | |||
this._renderer.setElementClass(this._elementRef.nativeElement, `md-chip`, 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.
Is this correct? Do we really need to have the md-chip
class on the host-element?
And also above with classList.add('mat-chip')
, I think this is obsolete since we already set the class in the host metadata.
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.
Good catch; this was leftover and I'm removing.
src/lib/chips/chip.ts
Outdated
/** Emitted when the chip is focused. */ | ||
onFocus = new EventEmitter<MdChipEvent>(); | ||
|
||
/** Emitted when the removable property changes. */ | ||
onRemovableChange = new EventEmitter<boolean>(); |
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 not expose the EventEmitter
.
class X {
private _onRemovableChange = new EventEmitter<boolean>();
onRemovableChange = this._onRemovableChange.asObservable();
}
src/lib/chips/chip.spec.ts
Outdated
}); | ||
|
||
it('DELETE does not emit the (remove) event', () => { | ||
const DELETE_EVENT: KeyboardEvent = new FakeEvent(DELETE) as KeyboardEvent; |
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.
double cast shouldn't be necessary (also above).
TypeScript automatically interferes the types when doing const event = new FakeEvent(DELETE) as KeyboardEvent;
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 really a double cast (it's just an explicit type declaration), but it is indeed extra code. I'll remove.
44a73d2
to
a0ecad2
Compare
Add events, styling and keyboard controls to allow removable chips. - Add basic styling for a user-provided remove icon. - Add keyboard controls for backspace/delete. - Add `(remove)` event which is emitted when the remove icon or one of the delete keys is pressed. - Add `md-chip-remove` directive which can be applied to `<md-icon>` (or others) to inform the chip of the remove request. Add new directive `mdChipInput` for controlling: - `(chipAdded)` - Event fired when a chip should be added. - `[separatorKeys]` - The list of keycodes that will fire the `(chipAdded)` event. - `[addOnBlur]` - Whether or not to fire the `(chipAdded)` event when the input is blurred. Additionally, fix some issues with dark theme and add styling/support for usage inside the `md-input-container` and add styling for focused chips. BREAKING CHANGE - The `selectable` property of the `md-chip-list` has now been moved to `md-chip` to maintain consistency with the new `removable` option. If you used the following code, ```html <md-chip-list [selectable]="selectable"> <md-chip>My Chip</md-chip> </md-chip-list> ``` you should switch it to ```html <md-chip-list> <md-chip [selectable]="selectable">My Chip</md-chip> </md-chip-list> ``` References angular#120. Closes angular#3143. Hat tip to @willshowell for suggestion to use secondary-text for focus color :-)
a0ecad2
to
2a5819f
Compare
Works great. Looking forward to this being merged. |
@topherfangio is this waiting for further review? Don't want it to get stuck in limbo! |
@JacobBrandt Thanks for the kind words! I'm glad you like it! @willshowell It is indeed waiting for a final review and I am nagging the appropriate people to get it done ;-) |
<md-chip color="warn" selected="true" *ngIf="visible" | ||
(destroy)="alert('chip destroyed')" (click)="toggleVisible()"> | ||
(destroy)="alert('chip destroyed')" (remove)="toggleVisible()"> | ||
<md-icon md-chip-remove>cancel</md-icon> |
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.
Using the md-icon
element directly here feels a little weird to me. Couple of thoughts:
- I bet we can draw an
x
with css and avoid the need for the icon entirely. md-icon
has its own associatedrole
and aria attributes, which may interfere with the chip'soption
-ness- I originally thought this should be a
button
element (since it's an action), but now I'm thinking that's probably not the case, since a screen-reader user will probably want to see the chip as an atomicoption
.
So, what do you think of changing this so that there's two directives:
<md-chip-remove>
, which has its own x icon (potentially drawn with css)[mdChipRemove]
as an attribute that you apply when you want to do your own thing (no styles, only behavior)
?
@@ -16,34 +16,90 @@ | |||
|
|||
// The spec only provides guidance for light-themed chips. When inside of a dark theme, fall back | |||
// to standard background and foreground colors. | |||
$unselected-background: if($is-dark-theme, mat-color($background, card), #e0e0e0); | |||
$unselected-background: if($is-dark-theme, #656565, #e0e0e0); |
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 a comment for the origin of #656565
?
|
||
&:hover { | ||
opacity: 0.54; | ||
} |
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.
Any way to generally reduce the nesting in this file?
* to properly center the icon within the chip. | ||
*/ | ||
@Directive({ | ||
selector: '[md-chip-remove], [mat-chip-remove], [mdChipRemove], [matChipRemove]', |
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 for the dash-case attribute selectors any more.
/** Subscription for our onRemoveChange Observable */ | ||
_onRemoveChangeSubscription: Subscription; | ||
|
||
constructor(protected _renderer: Renderer, protected _elementRef: ElementRef, |
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 updated to Renderer2
now
|
||
set selectable(value: boolean) { | ||
this._selectable = coerceBooleanProperty(value); | ||
registerInput(inputElement: HTMLInputElement) { |
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 being an HTMLInputElement
, can we make this an interface that only defines the specific properties / methods the chip-list needs to interact with?
* Programmatically focus the chip list. This in turn focuses the first non-disabled chip in this | ||
* chip list, or the input if available and there are 0 chips. | ||
* | ||
* TODO: ARIA says this should focus the first `selected` chip if any are selected. |
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 move the TODO
to a separate //
comment so that it doesn't appear in the API docs?
@@ -148,11 +175,19 @@ export class MdChipList implements AfterContentInit { | |||
* | |||
* @param chips The list of chips to be subscribed. | |||
*/ | |||
protected _subscribeChips(chips: QueryList<MdChip>): void { | |||
protected _subscribeChips(chips: QueryList < MdChip >): 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.
You can omit the spaces inside the < >
* one. | ||
*/ | ||
protected _checkDestroyedFocus() { | ||
let chipsArray = this.chips.toArray(); |
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 toArray
shouldn't be necessary; QueryList
has a length
property
fixture.detectChanges(); | ||
}); | ||
|
||
it('SPACE selects/deselects the currently focused 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.
Tests should start with the word should
, e.g.,
it('should toggle selection with the space key', ...
Any progress on this? |
Are there any versions with |
May I ask if there is any progress on this? |
This PR is being absorbed into #4912 |
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 events, styling and keyboard controls to allow removable chips.
(remove)
event which is emitted when the remove icon orone of the delete keys is pressed.
md-chip-remove
directive which can be applied to<md-icon>
(or others) to inform the chip of the remove request.
Add new directive
mdChipInput
for controlling:(chipAdded)
- Event fired when a chip should be added.[separatorKeys]
- The list of keycodes that will fire the(chipAdded)
event.[addOnBlur]
- Whether or not to fire the(chipAdded)
eventwhen the input is blurred.
Additionally, fix some issues with dark theme and add styling/support
for usage inside the
md-input-container
and add styling for focusedchips.
BREAKING CHANGE - The
selectable
property of themd-chip-list
hasnow been moved to
md-chip
to maintain consistency with the newremovable
option.If you used the following code,
you should switch it to
References #120. Closes #3143.
Hat tip to @willshowell for suggestion to use secondary-text
for focus color :-)