@@ -13,18 +13,19 @@ import {
1313 contentChildren ,
1414 Directive ,
1515 ElementRef ,
16- forwardRef ,
1716 inject ,
1817 input ,
1918 model ,
2019 signal ,
2120 untracked ,
2221} from '@angular/core' ;
23- import { ComboboxListboxPattern , ListboxPattern , OptionPattern } from '@angular/aria/private' ;
22+ import { ComboboxListboxPattern , ListboxPattern } from '@angular/aria/private' ;
2423import { Directionality } from '@angular/cdk/bidi' ;
2524import { toSignal } from '@angular/core/rxjs-interop' ;
2625import { _IdGenerator } from '@angular/cdk/a11y' ;
2726import { ComboboxPopup } from '../combobox' ;
27+ import { Option } from './option' ;
28+ import { LISTBOX } from './tokens' ;
2829
2930/**
3031 * Represents a container used to display a list of items for a user to select from.
@@ -62,6 +63,7 @@ import {ComboboxPopup} from '../combobox';
6263 '(focusin)' : '_onFocus()' ,
6364 } ,
6465 hostDirectives : [ ComboboxPopup ] ,
66+ providers : [ { provide : LISTBOX , useExisting : Listbox } ] ,
6567} )
6668export class Listbox < V > {
6769 /** A unique identifier for the listbox. */
@@ -82,13 +84,7 @@ export class Listbox<V> {
8284 private readonly _directionality = inject ( Directionality ) ;
8385
8486 /** The Options nested inside of the Listbox. */
85- private readonly _options = contentChildren (
86- // We need a `forwardRef` here, because the option class is declared further down
87- // in the same file. When the reference is written to Angular's metadata this can
88- // cause an attempt to access the class before it's defined.
89- forwardRef ( ( ) => Option ) ,
90- { descendants : true } ,
91- ) ;
87+ private readonly _options = contentChildren ( Option , { descendants : true } ) ;
9288
9389 /** A signal wrapper for directionality. */
9490 protected textDirection = toSignal ( this . _directionality . change , {
@@ -214,77 +210,3 @@ export class Listbox<V> {
214210 this . _pattern . listBehavior . first ( ) ;
215211 }
216212}
217-
218- /**
219- * A selectable option in an `ngListbox`.
220- *
221- * This directive should be applied to an element (e.g., `<li>`, `<div>`) within an
222- * `ngListbox`. The `value` input is used to identify the option, and the `label` input provides
223- * the accessible name for the option.
224- *
225- * ```html
226- * <li ngOption value="item-id" label="Item Name">
227- * Item Name
228- * </li>
229- * ```
230- *
231- * @developerPreview 21.0
232- */
233- @Directive ( {
234- selector : '[ngOption]' ,
235- exportAs : 'ngOption' ,
236- host : {
237- 'role' : 'option' ,
238- '[attr.data-active]' : 'active()' ,
239- '[attr.id]' : '_pattern.id()' ,
240- '[attr.tabindex]' : '_pattern.tabIndex()' ,
241- '[attr.aria-selected]' : '_pattern.selected()' ,
242- '[attr.aria-disabled]' : '_pattern.disabled()' ,
243- } ,
244- } )
245- export class Option < V > {
246- /** A reference to the host element. */
247- private readonly _elementRef = inject ( ElementRef ) ;
248-
249- /** A reference to the host element. */
250- readonly element = this . _elementRef . nativeElement as HTMLElement ;
251-
252- /** Whether the option is currently active (focused). */
253- active = computed ( ( ) => this . _pattern . active ( ) ) ;
254-
255- /** The parent Listbox. */
256- private readonly _listbox = inject ( Listbox ) ;
257-
258- /** A unique identifier for the option. */
259- readonly id = input ( inject ( _IdGenerator ) . getId ( 'ng-option-' , true ) ) ;
260-
261- // TODO(wagnermaciel): See if we want to change how we handle this since textContent is not
262- // reactive. See https://github.com/angular/components/pull/30495#discussion_r1961260216.
263- /** The text used by the typeahead search. */
264- protected searchTerm = computed ( ( ) => this . label ( ) ?? this . element . textContent ) ;
265-
266- /** The parent Listbox UIPattern. */
267- private readonly _listboxPattern = computed ( ( ) => this . _listbox . _pattern ) ;
268-
269- /** The value of the option. */
270- value = input . required < V > ( ) ;
271-
272- /** Whether an item is disabled. */
273- disabled = input ( false , { transform : booleanAttribute } ) ;
274-
275- /** The text used by the typeahead search. */
276- label = input < string > ( ) ;
277-
278- /** Whether the option is selected. */
279- readonly selected = computed ( ( ) => this . _pattern . selected ( ) ) ;
280-
281- /** The Option UIPattern. */
282- readonly _pattern = new OptionPattern < V > ( {
283- ...this ,
284- id : this . id ,
285- value : this . value ,
286- listbox : this . _listboxPattern ,
287- element : ( ) => this . element ,
288- searchTerm : ( ) => this . searchTerm ( ) ?? '' ,
289- } ) ;
290- }
0 commit comments