Skip to content

Cdk listbox dev app #20010

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 24 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d3fbbfc
build: Added required files to listbox directory.
nielsr98 Jun 11, 2020
5066335
build: added listbox option directive and renamed listbox directive f…
nielsr98 Jun 11, 2020
d0133cd
build: Added required files to listbox directory.
nielsr98 Jun 11, 2020
1e84c2d
build: added listbox option directive and renamed listbox directive f…
nielsr98 Jun 11, 2020
e22b3f2
build: Added required files to listbox directory.
nielsr98 Jun 11, 2020
a9b2519
build: added listbox option directive and renamed listbox directive f…
nielsr98 Jun 11, 2020
b1da11c
build: Added required files to listbox directory.
nielsr98 Jun 11, 2020
0af787c
build: added listbox option directive and renamed listbox directive f…
nielsr98 Jun 11, 2020
7ad3615
feat(listbox): added support for non-multiple listbox and aria active…
nielsr98 Jul 9, 2020
86510e4
fix(listbox): formatted BUILD.bazel.
nielsr98 Jul 9, 2020
41b62da
feat(dev-app/listbox): added cdk listbox example to the dev-app.
nielsr98 Jul 15, 2020
83db6b5
feat(listbox): added shift key selection.
nielsr98 Jul 15, 2020
71d9806
test(listbox): added test for selection via shift key.
nielsr98 Jul 16, 2020
e30d8e5
fix(listbox): formatted dev app build file.
nielsr98 Jul 16, 2020
169ee9c
fix(listbox): added codeowners for dev-app listbox example.
nielsr98 Jul 16, 2020
817c6df
fix(listbox): removed width css property.
nielsr98 Jul 16, 2020
f7454da
fix(listbox): removed private properties from host bindings to fix vi…
nielsr98 Jul 16, 2020
127355d
nit(listbox): changed double quote to single quote.
nielsr98 Jul 16, 2020
72287d4
refactor(listbox): fixed some styling errors and added modifier key t…
nielsr98 Jul 17, 2020
1b24b51
nit(listbox): changed spacing.
nielsr98 Jul 17, 2020
5278f78
nit(listbox): spacing.:
nielsr98 Jul 17, 2020
d765094
nit(listbox): removed extra blank line.
nielsr98 Jul 20, 2020
033f30a
refactor(listbox): changed test to have a stronger check on selected.
nielsr98 Jul 20, 2020
02dc398
refactor(listbox): changed css classes to use demo- prefix.
nielsr98 Jul 20, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
/src/dev-app/button-toggle/** @jelbourn
/src/dev-app/button/** @jelbourn
/src/dev-app/card/** @jelbourn
/src/dev-app/cdk-experimental-listbox/** @jelbourn @nielsr98
/src/dev-app/cdk-experimental-menu/** @jelbourn @andy9775
/src/dev-app/checkbox/** @jelbourn @devversion
/src/dev-app/chips/** @jelbourn
Expand Down
34 changes: 29 additions & 5 deletions src/cdk-experimental/listbox/listbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
CdkOption,
CdkListboxModule, ListboxSelectionChangeEvent, CdkListbox
} from './index';
import {dispatchKeyboardEvent, dispatchMouseEvent} from '@angular/cdk/testing/private';
import {
createKeyboardEvent,
dispatchKeyboardEvent,
dispatchMouseEvent
} from '@angular/cdk/testing/private';
import {A, DOWN_ARROW, END, HOME, SPACE} from '@angular/cdk/keycodes';

describe('CdkOption', () => {
Expand Down Expand Up @@ -60,8 +64,8 @@ describe('CdkOption', () => {
});

it('should have set the selected input of the options to null by default', () => {
for (const instance of optionInstances) {
expect(instance.selected).toBeFalse();
for (const option of optionElements) {
expect(option.hasAttribute('aria-selected')).toBeFalse();
}
});

Expand Down Expand Up @@ -327,13 +331,34 @@ describe('CdkOption', () => {
expect(fixture.componentInstance.changedOption).toBeDefined();
expect(fixture.componentInstance.changedOption.id).toBe(optionInstances[0].id);
});

it('should focus and toggle the next item when pressing SHIFT + DOWN_ARROW', () => {
let selectedOptions = optionInstances.filter(option => option.selected);
const downKeyEvent =
createKeyboardEvent('keydown', DOWN_ARROW, undefined, undefined, {shift: true});

expect(selectedOptions.length).toBe(0);
expect(optionElements[0].hasAttribute('aria-selected')).toBeFalse();
expect(optionInstances[0].selected).toBeFalse();
expect(fixture.componentInstance.changedOption).toBeUndefined();

listboxInstance.setActiveOption(optionInstances[0]);
listboxInstance._keydown(downKeyEvent);
fixture.detectChanges();

selectedOptions = optionInstances.filter(option => option.selected);
expect(selectedOptions.length).toBe(1);
expect(optionElements[1].getAttribute('aria-selected')).toBe('true');
expect(optionInstances[1].selected).toBeTrue();
expect(fixture.componentInstance.changedOption).toBeDefined();
expect(fixture.componentInstance.changedOption.id).toBe(optionInstances[1].id);
});
});

describe('with multiple selection', () => {
let fixture: ComponentFixture<ListboxMultiselect>;

let testComponent: ListboxMultiselect;

let listbox: DebugElement;
let listboxInstance: CdkListbox;

Expand All @@ -353,7 +378,6 @@ describe('CdkOption', () => {
fixture.detectChanges();

testComponent = fixture.debugElement.componentInstance;

listbox = fixture.debugElement.query(By.directive(CdkListbox));
listboxInstance = listbox.injector.get<CdkListbox>(CdkListbox);

Expand Down
36 changes: 24 additions & 12 deletions src/cdk-experimental/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
QueryList
} from '@angular/core';
import {ActiveDescendantKeyManager, Highlightable, ListKeyManagerOption} from '@angular/cdk/a11y';
import {END, ENTER, HOME, SPACE} from '@angular/cdk/keycodes';
import {DOWN_ARROW, END, ENTER, HOME, SPACE, UP_ARROW} from '@angular/cdk/keycodes';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {SelectionChange, SelectionModel} from '@angular/cdk/collections';
import {defer, merge, Observable, Subject} from 'rxjs';
Expand All @@ -33,11 +33,12 @@ let nextId = 0;
'(focus)': 'activate()',
'(blur)': 'deactivate()',
'[id]': 'id',
'[attr.aria-selected]': '_selected || null',
'[attr.aria-selected]': 'selected || null',
'[attr.tabindex]': '_getTabIndex()',
'[attr.aria-disabled]': '_isInteractionDisabled()',
'[class.cdk-option-disabled]': '_isInteractionDisabled()',
'[class.cdk-option-active]': '_active'
'[class.cdk-option-active]': '_active',
'[class.cdk-option-selected]': 'selected'
}
})
export class CdkOption implements ListKeyManagerOption, Highlightable {
Expand Down Expand Up @@ -171,26 +172,27 @@ export class CdkOption implements ListKeyManagerOption, Highlightable {
host: {
'role': 'listbox',
'(keydown)': '_keydown($event)',
'[attr.aria-disabled]': '_disabled',
'[attr.aria-multiselectable]': '_multiple',
'[attr.tabindex]': '_tabIndex',
'[attr.aria-disabled]': 'disabled',
'[attr.aria-multiselectable]': 'multiple',
'[attr.aria-activedescendant]': '_getAriaActiveDescendant()'
}
})
export class CdkListbox implements AfterContentInit, OnDestroy, OnInit {

_listKeyManager: ActiveDescendantKeyManager<CdkOption>;
_selectionModel: SelectionModel<CdkOption>;
_tabIndex = 0;

readonly optionSelectionChanges: Observable<OptionSelectionChangeEvent> = defer(() => {
const options = this._options;
const options = this._options;

return options.changes.pipe(
startWith(options),
switchMap(() => merge(...options.map(option => option.selectionChange)))
);
return options.changes.pipe(
startWith(options),
switchMap(() => merge(...options.map(option => option.selectionChange)))
);
}) as Observable<OptionSelectionChangeEvent>;


private _disabled: boolean = false;
private _multiple: boolean = false;
private _useActiveDescendant: boolean = true;
Expand Down Expand Up @@ -256,7 +258,10 @@ export class CdkListbox implements AfterContentInit, OnDestroy, OnInit {

private _initKeyManager() {
this._listKeyManager = new ActiveDescendantKeyManager(this._options)
.withWrap().withVerticalOrientation().withTypeAhead();
.withWrap()
.withVerticalOrientation()
.withTypeAhead()
.withAllowedModifierKeys(['shiftKey']);

this._listKeyManager.change.pipe(takeUntil(this._destroyed)).subscribe(() => {
this._updateActiveOption();
Expand Down Expand Up @@ -284,6 +289,7 @@ export class CdkListbox implements AfterContentInit, OnDestroy, OnInit {

const manager = this._listKeyManager;
const {keyCode} = event;
const previousActiveIndex = manager.activeItemIndex;

if (keyCode === HOME || keyCode === END) {
event.preventDefault();
Expand All @@ -297,6 +303,12 @@ export class CdkListbox implements AfterContentInit, OnDestroy, OnInit {
} else {
manager.onKeydown(event);
}

/** Will select an option if shift was pressed while navigating to the option */
const isArrow = (keyCode === UP_ARROW || keyCode === DOWN_ARROW);
if (isArrow && event.shiftKey && previousActiveIndex !== this._listKeyManager.activeItemIndex) {
Copy link
Member

Choose a reason for hiding this comment

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

What's the thinking behind toggling options based on arrow keys with shift rather than requiring a separate space/enter keypress? Was this from the aria authoring practices?

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 it would work with a separate space/enter keypress. In the authoring practices it says that selection via SHIFT + arrow key is an optional way to do selection.

this._toggleActiveOption();
}
}

/** Emits a selection change event, called when an option has its selected state changed. */
Expand Down
1 change: 1 addition & 0 deletions src/dev-app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ng_module(
"//src/dev-app/button",
"//src/dev-app/button-toggle",
"//src/dev-app/card",
"//src/dev-app/cdk-experimental-listbox",
"//src/dev-app/cdk-experimental-menu",
"//src/dev-app/checkbox",
"//src/dev-app/chips",
Expand Down
16 changes: 16 additions & 0 deletions src/dev-app/cdk-experimental-listbox/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("//tools:defaults.bzl", "ng_module")

package(default_visibility = ["//visibility:public"])

ng_module(
name = "cdk-experimental-listbox",
srcs = glob(["**/*.ts"]),
assets = [
"cdk-listbox-demo.html",
"cdk-listbox-demo.css",
],
deps = [
"//src/cdk-experimental/listbox",
"@npm//@angular/router",
],
)
24 changes: 24 additions & 0 deletions src/dev-app/cdk-experimental-listbox/cdk-listbox-demo-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {CdkListboxModule} from '@angular/cdk-experimental/listbox';

import {CdkListboxDemo} from './cdk-listbox-demo';

@NgModule({
imports: [
CdkListboxModule,
CommonModule,
RouterModule.forChild([{path: '', component: CdkListboxDemo}]),
],
declarations: [CdkListboxDemo],
})
export class CdkListboxDemoModule {}
14 changes: 14 additions & 0 deletions src/dev-app/cdk-experimental-listbox/cdk-listbox-demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.demo-listbox {
list-style-type: none;
border: 1px solid black;
cursor: default;
padding: 0;
}

.demo-listbox .cdk-option-active {
background: lightgrey;
}

.demo-listbox .cdk-option-selected {
background: cornflowerblue;
}
10 changes: 10 additions & 0 deletions src/dev-app/cdk-experimental-listbox/cdk-listbox-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<ul cdkListbox class="demo-listbox" [multiple]="multiSelectable" [useActiveDescendant]="activeDescendant">
<li cdkOption>Apple</li>
<li cdkOption>Orange</li>
<li cdkOption>Grapefruit</li>
<li cdkOption>Peach</li>
</ul>

<button (click)="toggleMultiple()">Toggle Multiple</button>
<br>
<button (click)="toggleActiveDescendant()">Toggle Active Descendant</button>
26 changes: 26 additions & 0 deletions src/dev-app/cdk-experimental-listbox/cdk-listbox-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Component} from '@angular/core';

@Component({
templateUrl: 'cdk-listbox-demo.html',
styleUrls: ['cdk-listbox-demo.css'],
})
export class CdkListboxDemo {
multiSelectable = false;
activeDescendant = true;

toggleMultiple() {
this.multiSelectable = !this.multiSelectable;
}

toggleActiveDescendant() {
this.activeDescendant = !this.activeDescendant;
}
}
1 change: 1 addition & 0 deletions src/dev-app/dev-app/dev-app-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class DevAppLayout {
{name: 'Button Toggle', route: '/button-toggle'},
{name: 'Button', route: '/button'},
{name: 'Card', route: '/card'},
{name: 'Cdk Experimental Listbox', route: '/cdk-experimental-listbox'},
{name: 'Cdk Experimental Menu', route: '/cdk-experimental-menu'},
{name: 'Checkbox', route: '/checkbox'},
{name: 'Chips', route: '/chips'},
Expand Down
4 changes: 4 additions & 0 deletions src/dev-app/dev-app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export const DEV_APP_ROUTES: Routes = [
loadChildren: 'button-toggle/button-toggle-demo-module#ButtonToggleDemoModule'
},
{path: 'card', loadChildren: 'card/card-demo-module#CardDemoModule'},
{
path: 'cdk-experimental-listbox',
loadChildren: 'cdk-experimental-listbox/cdk-listbox-demo-module#CdkListboxDemoModule'
},
{
path: 'cdk-experimental-menu',
loadChildren: 'cdk-experimental-menu/cdk-menu-demo-module#CdkMenuDemoModule'
Expand Down