-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(chip-list): Implement FormFieldControl and ControlValueAccessor in chip list #6686
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
mmalerba
left a comment
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.
Still need to take a closer look at the code, but something I noticed in the demo: it's hard to focus the actual input sometimes. Clicking on the md-form-field / md-chip-list should focus the input rather than one of the chips
src/lib/chips/chip-input.ts
Outdated
| } | ||
| }) | ||
| export class MdChipInput { | ||
| export class MdChipInput extends MdInput { |
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 check for something like: <input mdInput mdChipInputFor="chips"> and throw an error? People might just be used to adding the mdInput and I'm not sure what would happen in this case if they did that... I assume it would break things.
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 it's okay to use something like <input mdInput mdChipInputFor="chips">.
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.
They won't conflict with each other since this extends MdInput? It seems like at the very least you would be doing twice the amount of work for no reason, since both directives would react to every change in value and try to do things.
Actually looking at the code a little more closely, I see that MdChipList implements MdFormFieldControl and handles communication with the MdFormField so why does this need to extend MdInput at all? If there's enough shared stuff between this and MdInput you could maybe factor it into a common base class, but I don't think it really makes sense for this to directly extend MdInput
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.
@tinayuangao still curious about why this needs to extend MdInput, seems unnecessary to me.
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.
@mmalerba Changed MdChipInput to not extends MdInput. Added focus(), focused and empty to work with chip list. Please take another look. Thanks!
8ae9963 to
eb74659
Compare
jelbourn
left a comment
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.
Are there breaking changes that should be marked in this PR?
| value: string; | ||
| } | ||
|
|
||
| @Directive({ |
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 add a class description for this directive that talks about how it operates as a control for md-form-field?
src/lib/chips/chip-list.ts
Outdated
| protected _ariaDescribedby: string; | ||
|
|
||
| /** Id of the chip list */ | ||
| protected _id: 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.
There's an extra space after protected on some of these properties
src/lib/chips/chip-list.ts
Outdated
|
|
||
| /** For FormFieldControl. If any of the chips has focus, or the chip input has focus */ | ||
| get focused() { | ||
| return !!this.chips.find((chip) => chip._hasFocus) || |
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 use some instead of find and you won't need the !!
src/lib/chips/chip-list.ts
Outdated
| return this._chipInput.placeholder; | ||
| } else { | ||
| return this._placeholder; | ||
| } |
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 a ternary?
return this._chipInput ? this._chipInput.placeholder : this._placeholder;| changeDetection: ChangeDetectionStrategy.OnPush | ||
| }) | ||
| export class MdChipList implements AfterContentInit, OnDestroy { | ||
| export class MdChipList implements MdFormFieldControl<any>, ControlValueAccessor, |
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 file an issue to track sharing common code for role="listbox" components in cdk/listbox? It would be good to capture what in particular would belong there
| } else if (chipIndex - 1 >= 0) { | ||
| this._keyManager.setActiveItem(chipIndex - 1); | ||
| } | ||
| protected _updateKeyManager(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.
Add function description?
src/lib/chips/chip-list.ts
Outdated
|
|
||
| _setSelectionByValue(value: any, isUserInput: boolean = true) { | ||
| this._clearSelection(); | ||
| this.chips.forEach((chip) => chip.deselect()); |
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: don't need parenthesis around chip
(in any arrow function where the type is implicit)
src/lib/chips/chip-list.ts
Outdated
| _setSelectionByValue(value: any, isUserInput: boolean = true) { | ||
| this._clearSelection(); | ||
| this.chips.forEach((chip) => chip.deselect()); | ||
| const isArray = Array.isArray(value); |
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 wouldn't add the extra isArray variable here and would just use isArray() in the if block
src/lib/chips/chip-list.ts
Outdated
| */ | ||
| private _selectValue(value: any, isUserInput: boolean = true): MdChip | undefined { | ||
|
|
||
| const correspondingChip = this.chips.find((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.
The MdChip type should be implicit
eb74659 to
c19c278
Compare
|
Comments addressed. Added breaking changes to description. For focus, user can set <md-chip-list [tabIndex]="-1" #chipList>
<md-chip>chip one</md-chip>
<input mdChipInputFor="chipList">
</md-chip-list>And user can use left and right arrow keys to navigate to chips when |
kara
left a comment
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.
Typos and a few questions
src/lib/chips/chip-input.ts
Outdated
|
|
||
| /** | ||
| * A material design chips input component working with chip list component. | ||
| * To be placed inside md-form-field. Can be placed inside <md-chip-list> or outside <md-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.
Nit: Use < > with md-form-field, since we are using them with md-chip-list later in the comment
src/lib/chips/chip.ts
Outdated
| } | ||
| protected _selected: boolean = false; | ||
|
|
||
| /** The value of the chip. Default to the content inside <md-chip> tags. */ |
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.
Typo: Default -> Defaults
| import {MdFormFieldControl} from '../form-field/form-field-control'; | ||
|
|
||
|
|
||
| let nextUniqueId = 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.
Comment for this?
src/lib/chips/chip-list.ts
Outdated
|
|
||
| protected _value: any; | ||
|
|
||
| /** Placeholder for the chip list. Alternatively, placeholder can set to MdChipInput */ |
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.
Typo: can set to -> can be set on
src/lib/chips/chips.scss
Outdated
| } | ||
|
|
||
| input.mat-chip-input { | ||
| width: 150px; |
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 be a Sass variable?
| if (this._isValidIndex(chipIndex)) { | ||
| if (chip._hasFocus) { | ||
| // Check whether the chip is the last item | ||
| if (chipIndex < this.chips.length - 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.
Nit: we could clarify this comment. The check is really for whether the chip is not the last item, right? I had to read it a few times before I realized it was doing the opposite
| const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) || | ||
| (this._parentForm && this._parentForm.submitted); | ||
|
|
||
| return !!(isInvalid && (isTouched || isSubmitted)); |
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 looks pretty similar to the select's default error state here: https://github.com/angular/material2/blob/master/src/lib/select/select.ts#L678
Could we share it somewhere? There is a default errorStateMatcher - could we update it to add the control existence checks?
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 #6741 to track sharing common 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.
Ah, good to know, thanks.
src/lib/chips/chip-list.ts
Outdated
|
|
||
| /** | ||
| * Whether or not this chip is selectable. When a chip is not selectable, | ||
| * it's selected state is always ignored. |
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.
Typo: it's -> its
src/lib/chips/chip-list.ts
Outdated
| @Output() change: EventEmitter<MdChipListChange> = new EventEmitter<MdChipListChange>(); | ||
|
|
||
| /** | ||
| * Event that emits whenever the raw value of the select changes. This is here primarily |
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.
Typo: select -> chip list
| // A different comparator means the selection could change. | ||
| this._initializeSelection(); | ||
| } | ||
| } |
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 we share more code with the select here? Maybe a mixin that adds compareWith and default compareWith?
|
Thanks for review. Comments addressed. Please take another look. Thanks! |
jelbourn
left a comment
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.
@tinayuangao can you add the deprecated message to the commit message? That's what the changelog script picks up
Might be easiest to squash
Does chips.md need to be updated at all for this change?
src/lib/chips/chip-input.ts
Outdated
| /** | ||
| * A material design chips input component working with chip list component. | ||
| * To be placed inside <md-form-field>. Can be placed inside <md-chip-list> or outside | ||
| * <md-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.
How about
Directive that adds chip-specific behaviors to an input element inside <md-form-field>.
May be placed inside or outside of an <md-chip-list>.
src/lib/chips/chip-input.ts
Outdated
| * <md-chip>Chip 2<md-chip> | ||
| * <input mdChipInputFor="chipList"> | ||
| * </md-chip-list> | ||
| * </md-form-field> |
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 it's better to leave the examples in the docs / live examples, since the generated API don't wont give this a nice visual treatment
| '[attr.aria-describedby]': '_ariaDescribedby || null', | ||
| '[attr.aria-required]': 'required.toString()', | ||
| '[attr.aria-disabled]': 'disabled.toString()', | ||
| '[attr.aria-invalid]': 'errorState', |
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.
Does the component support multiple selection? If not, it should have aria-multiselectable="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.
Now it only support multiple selection.
I think I should change it to support single selection so we can share more common code with select 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.
Added single selection support to chip list. Please take another look. Thanks!
src/lib/chips/chip-list.ts
Outdated
| /** Comparison function to specify which option is displayed. Defaults to object equality. */ | ||
| private _compareWith = (o1: any, o2: any) => o1 === o2; | ||
|
|
||
| get selected(): 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.
Add description?
src/lib/chips/chip-list.ts
Outdated
| return this._chipInput ? this._chipInput.placeholder : this._placeholder; | ||
| } | ||
|
|
||
| /** For FormFieldControl. If any of the chips has focus, or the chip input has 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.
/** Whether any chips or the mdChipInput inside of this chip-list has focus. */
src/lib/chips/chip-list.ts
Outdated
| return (!this._chipInput || this._chipInput.empty) && this.chips.length === 0; | ||
| } | ||
|
|
||
| /** For FormFieldControl. The disabled is not depend on chip input */ |
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.
/** Whether this chip-list is disabled. */| get disabled() { return this.ngControl ? this.ngControl.disabled : this._disabled; } | ||
| set disabled(value: any) { this._disabled = coerceBooleanProperty(value); } | ||
|
|
||
| get errorState(): 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.
Add description?
src/lib/chips/chip-list.ts
Outdated
| } | ||
| } | ||
|
|
||
| _onBlur() { |
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 weird that there's a _blur() and an _onBlur()- could the names be more specific? Function descriptions would also help
| if (!this.disabled) { | ||
| if (this._chipInput) { | ||
| // Check if focus moved to chip input. If not, do real on blur | ||
| setTimeout(() => { |
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 for why this needs to be a timeout?
| } | ||
|
|
||
| _blur() { | ||
| if (!this.disabled) { |
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 be good to have a comment that explains what this method is doing
dd2f0a0 to
234f0d2
Compare
|
Comments addressed. Updated |
0cccb44 to
96ca320
Compare
|
@tinayuangao prerender test looks to be failing now |
|
@jelbourn Fixed prerender test. Please take a look. Thanks! |
jelbourn
left a comment
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 needs rebase
8117d07 to
19f0842
Compare
|
Fixed focus & blur behavior for chip list. PTAL. Thanks! |
mmalerba
left a comment
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
jelbourn
left a comment
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
0416022 to
0d75c2a
Compare
0d75c2a to
1bfa13e
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. |
Closes #5999 #6077
md-chip's API similar tomd-optionmd-chip-list's API similar tomd-selectBreaking changes in
MdChip:@Output() select = EventEmitter<MdChipEvent>and@Output() deselect = EventEmitter<MdChipEvent>@Output() onSelectionChange = EventEmitter<MdChipSelectionChange>