Skip to content

Commit 6b857e0

Browse files
committed
feat(cdk-experimental/ui-patterns): listbox ui pattern
1 parent 48058ff commit 6b857e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2894
-1
lines changed

.ng-dev/commit-message.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export const commitMessage: CommitMessageConfig = {
1111
'multiple', // For when a commit applies to multiple components.
1212
'cdk-experimental/column-resize',
1313
'cdk-experimental/combobox',
14+
'cdk-experimental/listbox',
1415
'cdk-experimental/popover-edit',
1516
'cdk-experimental/scrolling',
1617
'cdk-experimental/selection',
1718
'cdk-experimental/table-scroll-container',
19+
'cdk-experimental/ui-patterns',
1820
'cdk/a11y',
1921
'cdk/accordion',
2022
'cdk/bidi',

src/cdk-experimental/config.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
CDK_EXPERIMENTAL_ENTRYPOINTS = [
33
"column-resize",
44
"combobox",
5+
"listbox",
56
"popover-edit",
67
"scrolling",
78
"selection",
89
"table-scroll-container",
10+
"ui-patterns",
911
]
1012

1113
# List of all entry-point targets of the Angular cdk-experimental package.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
load("//tools:defaults.bzl", "ng_module")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ng_module(
6+
name = "listbox",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
deps = [
12+
"//src/cdk-experimental/ui-patterns",
13+
"//src/cdk/bidi",
14+
],
15+
)

src/cdk-experimental/listbox/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
export * from './public-api';
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {
10+
booleanAttribute,
11+
computed,
12+
contentChildren,
13+
Directive,
14+
ElementRef,
15+
inject,
16+
input,
17+
model,
18+
OnDestroy,
19+
signal,
20+
} from '@angular/core';
21+
import {ListboxPattern, OptionPattern} from '@angular/cdk-experimental/ui-patterns';
22+
import {Directionality} from '@angular/cdk/bidi';
23+
import {toSignal} from '@angular/core/rxjs-interop';
24+
25+
/**
26+
* A listbox container.
27+
*
28+
* Listboxes are used to display a list of items for a user to select from. The CdkListbox is meant
29+
* to be used in conjunction with CdkOption as follows:
30+
*
31+
* ```html
32+
* <ul cdkListbox>
33+
* <li cdkOption>Item 1</li>
34+
* <li cdkOption>Item 2</li>
35+
* <li cdkOption>Item 3</li>
36+
* </ul>
37+
* ```
38+
*/
39+
@Directive({
40+
selector: '[cdkListbox]',
41+
exportAs: 'cdkListbox',
42+
host: {
43+
'role': 'listbox',
44+
'class': 'cdk-listbox',
45+
'[attr.tabindex]': 'pattern.tabindex()',
46+
'[attr.aria-disabled]': 'pattern.disabled()',
47+
'[attr.aria-orientation]': 'pattern.orientation()',
48+
'[attr.aria-multiselectable]': 'pattern.multiselectable()',
49+
'[attr.aria-activedescendant]': 'pattern.activedescendant()',
50+
'(focusin)': 'pattern.onFocus()',
51+
'(keydown)': 'pattern.onKeydown($event)',
52+
'(mousedown)': 'pattern.onMousedown($event)',
53+
},
54+
})
55+
export class CdkListbox {
56+
/** The directionality (LTR / RTL) context for the application (or a subtree of it). */
57+
private _dir = inject(Directionality);
58+
59+
/** The CdkOptions nested inside of the CdkListbox. */
60+
private _cdkOptions = contentChildren(CdkOption, {descendants: true});
61+
62+
/** A signal wrapper for directionality. */
63+
protected directionality = toSignal(this._dir.change, {
64+
initialValue: 'ltr',
65+
});
66+
67+
/** The Option UIPatterns of the child CdkOptions. */
68+
protected items = computed(() => this._cdkOptions().map(option => option.pattern));
69+
70+
/** Whether the list is vertically or horizontally oriented. */
71+
orientation = input<'vertical' | 'horizontal'>('vertical');
72+
73+
/** Whether multiple items in the list can be selected at once. */
74+
multiselectable = input(false, {transform: booleanAttribute});
75+
76+
/** Whether focus should wrap when navigating. */
77+
wrap = input(true, {transform: booleanAttribute});
78+
79+
/** Whether disabled items in the list should be skipped when navigating. */
80+
skipDisabled = input(true, {transform: booleanAttribute});
81+
82+
/** The focus strategy used by the list. */
83+
focusMode = input<'roving' | 'activedescendant'>('roving');
84+
85+
/** The selection strategy used by the list. */
86+
selectionMode = input<'follow' | 'explicit'>('follow');
87+
88+
/** The amount of time before the typeahead search is reset. */
89+
typeaheadDelay = input<number>(0.5); // Picked arbitrarily.
90+
91+
/** Whether the listbox is disabled. */
92+
disabled = input(false, {transform: booleanAttribute});
93+
94+
/** The ids of the current selected items. */
95+
selectedIds = model<string[]>([]);
96+
97+
/** The current index that has been navigated to. */
98+
activeIndex = model<number>(0);
99+
100+
/** The Listbox UIPattern. */
101+
pattern: ListboxPattern = new ListboxPattern({
102+
...this,
103+
items: this.items,
104+
directionality: this.directionality,
105+
});
106+
}
107+
108+
// TODO(wagnermaciel): Figure out how we actually want to do this.
109+
let count = 0;
110+
111+
/** A selectable option in a CdkListbox. */
112+
@Directive({
113+
selector: '[cdkOption]',
114+
exportAs: 'cdkOption',
115+
host: {
116+
'role': 'option',
117+
'class': 'cdk-option',
118+
'[attr.tabindex]': 'pattern.tabindex()',
119+
'[attr.aria-selected]': 'pattern.selected()',
120+
'[attr.aria-disabled]': 'pattern.disabled()',
121+
},
122+
})
123+
export class CdkOption {
124+
/** A reference to the option element. */
125+
private _elementRef = inject(ElementRef);
126+
127+
/** The parent CdkListbox. */
128+
private _cdkListbox = inject(CdkListbox);
129+
130+
/** A unique identifier for the option. */
131+
protected id = computed(() => `${count++}`);
132+
133+
/** The text used by the typeahead search. */
134+
protected searchTerm = computed(() => this.label() ?? this.element().textContent);
135+
136+
/** The parent Listbox UIPattern. */
137+
protected listbox = computed(() => this._cdkListbox.pattern);
138+
139+
/** A reference to the option element to be focused on navigation. */
140+
protected element = computed(() => this._elementRef.nativeElement);
141+
142+
/** Whether an item is disabled. */
143+
disabled = input(false, {transform: booleanAttribute});
144+
145+
/** The text used by the typeahead search. */
146+
label = input<string>();
147+
148+
/** The Option UIPattern. */
149+
pattern = new OptionPattern({
150+
...this,
151+
id: this.id,
152+
listbox: this.listbox,
153+
element: this.element,
154+
searchTerm: this.searchTerm,
155+
});
156+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
export {CdkListbox, CdkOption} from './listbox';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
load("//tools:defaults.bzl", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "ui-patterns",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
deps = [
12+
"//src/cdk-experimental/ui-patterns/listbox",
13+
"@npm//@angular/core",
14+
],
15+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load("//tools:defaults.bzl", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "event-manager",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
deps = ["@npm//@angular/core"],
12+
)

0 commit comments

Comments
 (0)