Skip to content

Combobox base #20211

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

Merged
merged 26 commits into from
Aug 12, 2020
Merged

Combobox base #20211

merged 26 commits into from
Aug 12, 2020

Conversation

nielsr98
Copy link
Contributor

@nielsr98 nielsr98 commented Aug 6, 2020

Added most base features of combobox and panel. By default this combobox will always trigger on click. More unit tests are required. Only basic unit tests are implemented thus far.

@nielsr98 nielsr98 requested a review from jelbourn as a code owner August 6, 2020 05:37
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Aug 6, 2020
@nielsr98 nielsr98 added the target: minor This PR is targeted for the next minor release label Aug 6, 2020
@jelbourn jelbourn requested a review from crisbeto August 7, 2020 15:52
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.

Got most of the way through before my next meeting- will finish looking later today!


@Directive({
selector: 'ng-template[cdkComboboxPanel]',
exportAs: 'cdkComboboxPanel',
host: {
'aria-controls': 'contentId',
Copy link
Member

Choose a reason for hiding this comment

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

I don't think these aria attributes would go on the panel, they would go on the combobox trigger. The values are only necessary in the panel so that it can pass them back to the trigger

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I guess using a Subject for these two as well would be appropriate? Then on register of the content the panel updated the Subject and the trigger can grab the new value.

})
export class CdkComboboxPanel<T = unknown> {

valueUpdated: Subject<T> = new Subject<T>();
contentId: string = '';
contentType: string = '';
Copy link
Member

Choose a reason for hiding this comment

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

Rather than being string, this could be the allowed set of values for aria-haspopup

contentType: 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';


constructor(readonly _templateRef: TemplateRef<unknown>) {}

closePanel(data?: T) {
Copy link
Member

Choose a reason for hiding this comment

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

Add JsDoc for public methods
(here and below)

_registerContent(contentId: string, contentType: string) {
this.contentId = contentId;
if (contentType !== 'listbox' && contentType !== 'dialog') {
throw Error('CdkComboboxPanel content must be either a listbox or dialog');
Copy link
Member

Choose a reason for hiding this comment

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

I might tweak the wording here that this only currently supports "listbox" or "dialog"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha will do.

'role': 'combobox'
'role': 'combobox',
'(click)': 'toggle()',
'[attr.aria-disabled]': 'disabled'
Copy link
Member

Choose a reason for hiding this comment

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

The aria-haspopup and aria-controls should go here.

I also realized just now that there's a mode of interaction where this element controls a listbox while focus remains on the combobox element and it uses aria-activedescendant to indicate which listbox option is active. Seems like something to consider later on

Comment on lines 46 to 53
@Input('triggerFor')
get comboboxPanel(): CdkComboboxPanel<T> | undefined {
return this._comboboxPanel;
}
set comboboxPanel(panel: CdkComboboxPanel<T> | undefined) {
this._comboboxPanel = panel;
}
private _comboboxPanel: CdkComboboxPanel<T> | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
@Input('triggerFor')
get comboboxPanel(): CdkComboboxPanel<T> | undefined {
return this._comboboxPanel;
}
set comboboxPanel(panel: CdkComboboxPanel<T> | undefined) {
this._comboboxPanel = panel;
}
private _comboboxPanel: CdkComboboxPanel<T> | undefined;
@Input('cdkComboboxTriggerFor')
get panel(): CdkComboboxPanel<T> | undefined {
return this._panel;
}
set panel(panel: CdkComboboxPanel<T> | undefined) {
this._panel= panel;
}
private _panel: CdkComboboxPanel<T> | undefined;

Comment on lines 55 to 62
@Input()
get value(): T {
return this._value;
}
set value(val: T) {
this._value = val;
}
private _value: T;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
@Input()
get value(): T {
return this._value;
}
set value(val: T) {
this._value = val;
}
private _value: T;
@Input()
value: T;

I'd avoid adding the getter/setter until you need it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah okay.

Comment on lines 65 to 71
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
}
private _disabled: boolean = false;
Copy link
Member

Choose a reason for hiding this comment

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

optional: you can condense simple getter/setters like this

Suggested change
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
}
private _disabled: boolean = false;
get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); }
private _disabled: boolean = false;

];
}

private _getPortal() {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private _getPortal() {
private _getPanelContent() {

@@ -6,12 +6,40 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive} from '@angular/core';
export type ContentType = 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export type ContentType = 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
export type AriaHasPopupValue = 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';

Comment on lines 48 to 49
get comboboxPanel(): CdkComboboxPanel<T> | undefined { return this._panel; }
set comboboxPanel(panel: CdkComboboxPanel<T> | undefined) { this._panel = panel; }
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
get comboboxPanel(): CdkComboboxPanel<T> | undefined { return this._panel; }
set comboboxPanel(panel: CdkComboboxPanel<T> | undefined) { this._panel = panel; }
get panel(): CdkComboboxPanel<T> | undefined { return this._panel; }
set panel(panel: CdkComboboxPanel<T> | undefined) { this._panel = panel; }

return this._panelContent;
}

private _coerceOpenActionProperty(input: any): OpenAction[] {
Copy link
Member

Choose a reason for hiding this comment

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

Rather than any, this can be the union of all the types this can handle

Comment on lines 183 to 194
const actions: OpenAction[] = [];

const inputArray = coerceArray(input);
for (const action of inputArray) {
if (action !== 'focus' && action !== 'click' && action !== 'downKey' && action !== 'toggle') {
throw Error('Not a valid open action.');
} else {
actions.push(action);
}
}

return actions;
Copy link
Member

Choose a reason for hiding this comment

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

I would just have this whole function be:

Suggested change
const actions: OpenAction[] = [];
const inputArray = coerceArray(input);
for (const action of inputArray) {
if (action !== 'focus' && action !== 'click' && action !== 'downKey' && action !== 'toggle') {
throw Error('Not a valid open action.');
} else {
actions.push(action);
}
}
return actions;
return input;

And add the rest of the behavior when you add support for splitting a string

Comment on lines +42 to +43
'[attr.aria-controls]': 'contentId',
'[attr.aria-haspopup]': 'contentType'
Copy link
Member

Choose a reason for hiding this comment

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

Add unit tests for these attributes?

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

@jelbourn jelbourn added action: merge The PR is ready for merge by the caretaker merge safe labels Aug 10, 2020
@andrewseguin andrewseguin added the merge: fix commit message When the PR is merged, rewrites/fixups of the commit messages are needed label Aug 12, 2020
@andrewseguin andrewseguin merged commit 6184328 into angular:master Aug 12, 2020
@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 12, 2020
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 merge: fix commit message When the PR is merged, rewrites/fixups of the commit messages are needed target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants