Skip to content

Commit 0f4fb54

Browse files
committed
fixup! feat(cdk-experimental/ui-patterns): create combobox ui pattern
1 parent fdf0c1a commit 0f4fb54

File tree

5 files changed

+74
-56
lines changed

5 files changed

+74
-56
lines changed

src/cdk-experimental/listbox/listbox.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
model,
1919
signal,
2020
} from '@angular/core';
21-
import {ListboxPattern, OptionPattern} from '../ui-patterns';
21+
import {ComboboxListboxPattern, ListboxPattern, OptionPattern} from '../ui-patterns';
2222
import {Directionality} from '@angular/cdk/bidi';
2323
import {toSignal} from '@angular/core/rxjs-interop';
2424
import {_IdGenerator} from '@angular/cdk/a11y';
@@ -118,20 +118,28 @@ export class CdkListbox<V> {
118118
value = model<V[]>([]);
119119

120120
/** The Listbox UIPattern. */
121-
pattern: ListboxPattern<V> = new ListboxPattern<V>({
122-
...this,
123-
items: this.items,
124-
activeItem: signal(undefined),
125-
textDirection: this.textDirection,
126-
element: () => this._elementRef.nativeElement,
127-
combobox: () => this._popup?.combobox?.pattern,
128-
});
121+
pattern: ListboxPattern<V>;
129122

130123
/** Whether the listbox has received focus yet. */
131124
private _hasFocused = signal(false);
132125

133126
constructor() {
134-
this._popup?.actions?.set(this.pattern.comboboxActions);
127+
const inputs = {
128+
...this,
129+
items: this.items,
130+
activeItem: signal(undefined),
131+
textDirection: this.textDirection,
132+
element: () => this._elementRef.nativeElement,
133+
combobox: () => this._popup?.combobox?.pattern,
134+
};
135+
136+
this.pattern = this._popup?.combobox
137+
? new ComboboxListboxPattern<V>(inputs)
138+
: new ListboxPattern<V>(inputs);
139+
140+
if (this._popup) {
141+
this._popup.actions.set((this.pattern as ComboboxListboxPattern<V>).comboboxActions);
142+
}
135143

136144
afterRenderEffect(() => {
137145
if (typeof ngDevMode === 'undefined' || ngDevMode) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {computed} from '@angular/core';
2+
import {ListboxInputs, ListboxPattern} from './listbox';
3+
import {SignalLike} from '../behaviors/signal-like/signal-like';
4+
import {OptionPattern} from './option';
5+
import {ComboboxPattern, ComboboxPopupControls} from '../combobox/combobox';
6+
7+
export type ComboboxListboxInputs<V> = ListboxInputs<V> & {
8+
/** The combobox controlling the listbox. */
9+
combobox: SignalLike<ComboboxPattern<OptionPattern<V>, V> | undefined>;
10+
};
11+
12+
export class ComboboxListboxPattern<V> extends ListboxPattern<V> {
13+
override tabindex: SignalLike<-1 | 0> = () => -1;
14+
15+
constructor(override readonly inputs: ComboboxListboxInputs<V>) {
16+
if (inputs.combobox()) {
17+
inputs.multi = () => false;
18+
inputs.focusMode = () => 'activedescendant';
19+
inputs.element = inputs.combobox()!.inputs.inputEl;
20+
}
21+
22+
super(inputs);
23+
}
24+
25+
override onKeydown(_: KeyboardEvent): void {}
26+
override onPointerdown(_: PointerEvent): void {}
27+
override setDefaultState(): void {}
28+
29+
/** The actions that can be performed on a combobox popup listbox. */
30+
comboboxActions: ComboboxPopupControls<OptionPattern<V>, V> = {
31+
activeId: computed(() => this.listBehavior.activedescendant()),
32+
next: () => this.listBehavior.next(),
33+
prev: () => this.listBehavior.prev(),
34+
last: () => this.listBehavior.last(),
35+
first: () => this.listBehavior.first(),
36+
unfocus: () => this.listBehavior.unfocus(),
37+
select: item => this.listBehavior.select(item),
38+
clearSelection: () => this.listBehavior.deselectAll(),
39+
getItem: e => this._getItem(e),
40+
getSelectedItem: () => this.inputs.items().find(i => i.selected()),
41+
setValue: (value: V | undefined) => this.inputs.value.set(value ? [value] : []),
42+
filter: (text: string) => {
43+
const filterFn = this.inputs.combobox()!.inputs.filter();
44+
45+
this.inputs.items().forEach(i => {
46+
const isMatch = filterFn(text, i.searchTerm());
47+
i.inert.set(isMatch ? null : true);
48+
});
49+
},
50+
};
51+
}

src/cdk-experimental/ui-patterns/listbox/listbox.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ describe('Listbox Pattern', () => {
4646
orientation: inputs.orientation ?? signal('vertical'),
4747
selectionMode: inputs.selectionMode ?? signal('explicit'),
4848
element: signal(document.createElement('div')),
49-
combobox: signal(undefined),
5049
});
5150
}
5251

src/cdk-experimental/ui-patterns/listbox/listbox.ts

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,10 @@ import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/
1111
import {computed, signal} from '@angular/core';
1212
import {SignalLike} from '../behaviors/signal-like/signal-like';
1313
import {List, ListInputs} from '../behaviors/list/list';
14-
import {ComboboxPattern, ComboboxPopupControls} from '../combobox/combobox';
1514

1615
/** Represents the required inputs for a listbox. */
1716
export type ListboxInputs<V> = ListInputs<OptionPattern<V>, V> & {
1817
readonly: SignalLike<boolean>;
19-
20-
/** The combobox controlling the listbox. */
21-
combobox: SignalLike<ComboboxPattern<OptionPattern<V>, V> | undefined>;
2218
};
2319

2420
/** Controls the state of a listbox. */
@@ -35,7 +31,7 @@ export class ListboxPattern<V> {
3531
readonly: SignalLike<boolean>;
3632

3733
/** The tabindex of the listbox. */
38-
tabindex = computed(() => (this.inputs.combobox() ? -1 : this.listBehavior.tabindex()));
34+
tabindex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabindex());
3935

4036
/** The id of the current active item. */
4137
activedescendant = computed(() => this.listBehavior.activedescendant());
@@ -192,12 +188,6 @@ export class ListboxPattern<V> {
192188
this.readonly = inputs.readonly;
193189
this.orientation = inputs.orientation;
194190
this.multi = inputs.multi;
195-
196-
if (this.inputs.combobox()) {
197-
this.inputs.focusMode = () => 'activedescendant';
198-
this.inputs.element = this.inputs.combobox()!.inputs.inputEl;
199-
}
200-
201191
this.listBehavior = new List(inputs);
202192
}
203193

@@ -224,13 +214,13 @@ export class ListboxPattern<V> {
224214

225215
/** Handles keydown events for the listbox. */
226216
onKeydown(event: KeyboardEvent) {
227-
if (!this.disabled() && !this.inputs.combobox()) {
217+
if (!this.disabled()) {
228218
this.keydown().handle(event);
229219
}
230220
}
231221

232222
onPointerdown(event: PointerEvent) {
233-
if (!this.disabled() && !this.inputs.combobox()) {
223+
if (!this.disabled()) {
234224
this.pointerdown().handle(event);
235225
}
236226
}
@@ -246,10 +236,6 @@ export class ListboxPattern<V> {
246236
* is called.
247237
*/
248238
setDefaultState() {
249-
if (this.inputs.combobox()) {
250-
return;
251-
}
252-
253239
let firstItem: OptionPattern<V> | null = null;
254240

255241
for (const item of this.inputs.items()) {
@@ -269,39 +255,12 @@ export class ListboxPattern<V> {
269255
}
270256
}
271257

272-
private _getItem(e: PointerEvent) {
258+
protected _getItem(e: PointerEvent) {
273259
if (!(e.target instanceof HTMLElement)) {
274260
return;
275261
}
276262

277263
const element = e.target.closest('[role="option"]');
278264
return this.inputs.items().find(i => i.element() === element);
279265
}
280-
281-
/** The actions that can be performed on a combobox popup listbox. */
282-
comboboxActions: ComboboxPopupControls<OptionPattern<V>, V> = {
283-
activeId: computed(() => this.listBehavior.activedescendant()),
284-
285-
next: () => this.listBehavior.next(),
286-
prev: () => this.listBehavior.prev(),
287-
last: () => this.listBehavior.last(),
288-
first: () => this.listBehavior.first(),
289-
unfocus: () => this.listBehavior.unfocus(),
290-
select: item => this.listBehavior.select(item),
291-
clearSelection: () => this.listBehavior.deselectAll(),
292-
293-
getItem: e => this._getItem(e),
294-
getSelectedItem: () => this.inputs.items().find(i => i.selected()),
295-
296-
setValue: (value: V | undefined) => this.inputs.value.set(value ? [value] : []),
297-
298-
filter: (text: string) => {
299-
const filterFn = this.inputs.combobox()!.inputs.filter();
300-
301-
this.inputs.items().forEach(i => {
302-
const isMatch = filterFn(text, i.searchTerm());
303-
i.inert.set(isMatch ? null : true);
304-
});
305-
},
306-
};
307266
}

src/cdk-experimental/ui-patterns/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
export * from './combobox/combobox';
1010
export * from './listbox/listbox';
1111
export * from './listbox/option';
12+
export * from './listbox/combobox-listbox';
1213
export * from './radio-group/radio-group';
1314
export * from './radio-group/radio-button';
1415
export * from './behaviors/signal-like/signal-like';

0 commit comments

Comments
 (0)