123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925 |
- import * as i0 from '@angular/core';
- import { inject, signal, ElementRef, booleanAttribute, Directive, Input, NgZone, ChangeDetectorRef, Renderer2, forwardRef, Output, ContentChildren, NgModule } from '@angular/core';
- import { NG_VALUE_ACCESSOR } from '@angular/forms';
- import { Subject, defer, merge } from 'rxjs';
- import { startWith, switchMap, map, takeUntil, filter } from 'rxjs/operators';
- import { A, S as SPACE, c as ENTER, H as HOME, E as END, U as UP_ARROW, D as DOWN_ARROW, L as LEFT_ARROW, R as RIGHT_ARROW } from './keycodes-CpHkExLC.mjs';
- import { A as ActiveDescendantKeyManager } from './activedescendant-key-manager-DC3-fwQI.mjs';
- import { S as SelectionModel } from './selection-model-CeeHVIcP.mjs';
- import { _ as _IdGenerator } from './id-generator-Dw_9dSDu.mjs';
- import { D as Directionality } from './directionality-CBXD4hga.mjs';
- import { P as Platform } from './platform-DmdVEw_C.mjs';
- import { hasModifierKey } from './keycodes.mjs';
- import { c as coerceArray } from './array-I1yfCXUO.mjs';
- import './list-key-manager-CyOIXo8P.mjs';
- import './typeahead-9ZW4Dtsf.mjs';
- import '@angular/common';
- /**
- * An implementation of SelectionModel that internally always represents the selection as a
- * multi-selection. This is necessary so that we can recover the full selection if the user
- * switches the listbox from single-selection to multi-selection after initialization.
- *
- * This selection model may report multiple selected values, even if it is in single-selection
- * mode. It is up to the user (CdkListbox) to check for invalid selections.
- */
- class ListboxSelectionModel extends SelectionModel {
- multiple;
- constructor(multiple = false, initiallySelectedValues, emitChanges = true, compareWith) {
- super(true, initiallySelectedValues, emitChanges, compareWith);
- this.multiple = multiple;
- }
- isMultipleSelection() {
- return this.multiple;
- }
- select(...values) {
- // The super class is always in multi-selection mode, so we need to override the behavior if
- // this selection model actually belongs to a single-selection listbox.
- if (this.multiple) {
- return super.select(...values);
- }
- else {
- return super.setSelection(...values);
- }
- }
- }
- /** A selectable option in a listbox. */
- class CdkOption {
- /** The id of the option's host element. */
- get id() {
- return this._id || this._generatedId;
- }
- set id(value) {
- this._id = value;
- }
- _id;
- _generatedId = inject(_IdGenerator).getId('cdk-option-');
- /** The value of this option. */
- value;
- /**
- * The text used to locate this item during listbox typeahead. If not specified,
- * the `textContent` of the item will be used.
- */
- typeaheadLabel;
- /** Whether this option is disabled. */
- get disabled() {
- return this.listbox.disabled || this._disabled();
- }
- set disabled(value) {
- this._disabled.set(value);
- }
- _disabled = signal(false);
- /** The tabindex of the option when it is enabled. */
- get enabledTabIndex() {
- return this._enabledTabIndex() === undefined
- ? this.listbox.enabledTabIndex
- : this._enabledTabIndex();
- }
- set enabledTabIndex(value) {
- this._enabledTabIndex.set(value);
- }
- _enabledTabIndex = signal(undefined);
- /** The option's host element */
- element = inject(ElementRef).nativeElement;
- /** The parent listbox this option belongs to. */
- listbox = inject(CdkListbox);
- /** Emits when the option is destroyed. */
- destroyed = new Subject();
- /** Emits when the option is clicked. */
- _clicked = new Subject();
- ngOnDestroy() {
- this.destroyed.next();
- this.destroyed.complete();
- }
- /** Whether this option is selected. */
- isSelected() {
- return this.listbox.isSelected(this);
- }
- /** Whether this option is active. */
- isActive() {
- return this.listbox.isActive(this);
- }
- /** Toggle the selected state of this option. */
- toggle() {
- this.listbox.toggle(this);
- }
- /** Select this option if it is not selected. */
- select() {
- this.listbox.select(this);
- }
- /** Deselect this option if it is selected. */
- deselect() {
- this.listbox.deselect(this);
- }
- /** Focus this option. */
- focus() {
- this.element.focus();
- }
- /** Get the label for this element which is required by the FocusableOption interface. */
- getLabel() {
- return (this.typeaheadLabel ?? this.element.textContent?.trim()) || '';
- }
- /**
- * No-op implemented as a part of `Highlightable`.
- * @docs-private
- */
- setActiveStyles() {
- // If the listbox is using `aria-activedescendant` the option won't have focus so the
- // browser won't scroll them into view automatically so we need to do it ourselves.
- if (this.listbox.useActiveDescendant) {
- this.element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
- }
- }
- /**
- * No-op implemented as a part of `Highlightable`.
- * @docs-private
- */
- setInactiveStyles() { }
- /** Handle focus events on the option. */
- _handleFocus() {
- // Options can wind up getting focused in active descendant mode if the user clicks on them.
- // In this case, we push focus back to the parent listbox to prevent an extra tab stop when
- // the user performs a shift+tab.
- if (this.listbox.useActiveDescendant) {
- this.listbox._setActiveOption(this);
- this.listbox.focus();
- }
- }
- /** Get the tabindex for this option. */
- _getTabIndex() {
- if (this.listbox.useActiveDescendant || this.disabled) {
- return -1;
- }
- return this.isActive() ? this.enabledTabIndex : -1;
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkOption, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkOption, isStandalone: true, selector: "[cdkOption]", inputs: { id: "id", value: ["cdkOption", "value"], typeaheadLabel: ["cdkOptionTypeaheadLabel", "typeaheadLabel"], disabled: ["cdkOptionDisabled", "disabled", booleanAttribute], enabledTabIndex: ["tabindex", "enabledTabIndex"] }, host: { attributes: { "role": "option" }, listeners: { "click": "_clicked.next($event)", "focus": "_handleFocus()" }, properties: { "id": "id", "attr.aria-selected": "isSelected()", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled", "class.cdk-option-active": "isActive()" }, classAttribute: "cdk-option" }, exportAs: ["cdkOption"], ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkOption, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkOption]',
- exportAs: 'cdkOption',
- host: {
- 'role': 'option',
- 'class': 'cdk-option',
- '[id]': 'id',
- '[attr.aria-selected]': 'isSelected()',
- '[attr.tabindex]': '_getTabIndex()',
- '[attr.aria-disabled]': 'disabled',
- '[class.cdk-option-active]': 'isActive()',
- '(click)': '_clicked.next($event)',
- '(focus)': '_handleFocus()',
- },
- }]
- }], propDecorators: { id: [{
- type: Input
- }], value: [{
- type: Input,
- args: ['cdkOption']
- }], typeaheadLabel: [{
- type: Input,
- args: ['cdkOptionTypeaheadLabel']
- }], disabled: [{
- type: Input,
- args: [{ alias: 'cdkOptionDisabled', transform: booleanAttribute }]
- }], enabledTabIndex: [{
- type: Input,
- args: ['tabindex']
- }] } });
- class CdkListbox {
- _cleanupWindowBlur;
- /** The id of the option's host element. */
- get id() {
- return this._id || this._generatedId;
- }
- set id(value) {
- this._id = value;
- }
- _id;
- _generatedId = inject(_IdGenerator).getId('cdk-listbox-');
- /** The tabindex to use when the listbox is enabled. */
- get enabledTabIndex() {
- return this._enabledTabIndex() === undefined ? 0 : this._enabledTabIndex();
- }
- set enabledTabIndex(value) {
- this._enabledTabIndex.set(value);
- }
- _enabledTabIndex = signal(undefined);
- /** The value selected in the listbox, represented as an array of option values. */
- get value() {
- return this._invalid ? [] : this.selectionModel.selected;
- }
- set value(value) {
- this._setSelection(value);
- }
- /**
- * Whether the listbox allows multiple options to be selected. If the value switches from `true`
- * to `false`, and more than one option is selected, all options are deselected.
- */
- get multiple() {
- return this.selectionModel.multiple;
- }
- set multiple(value) {
- this.selectionModel.multiple = value;
- if (this.options) {
- this._updateInternalValue();
- }
- }
- /** Whether the listbox is disabled. */
- get disabled() {
- return this._disabled();
- }
- set disabled(value) {
- this._disabled.set(value);
- }
- _disabled = signal(false);
- /** Whether the listbox will use active descendant or will move focus onto the options. */
- get useActiveDescendant() {
- return this._useActiveDescendant();
- }
- set useActiveDescendant(value) {
- this._useActiveDescendant.set(value);
- }
- _useActiveDescendant = signal(false);
- /** The orientation of the listbox. Only affects keyboard interaction, not visual layout. */
- get orientation() {
- return this._orientation;
- }
- set orientation(value) {
- this._orientation = value === 'horizontal' ? 'horizontal' : 'vertical';
- if (value === 'horizontal') {
- this.listKeyManager?.withHorizontalOrientation(this._dir?.value || 'ltr');
- }
- else {
- this.listKeyManager?.withVerticalOrientation();
- }
- }
- _orientation = 'vertical';
- /** The function used to compare option values. */
- get compareWith() {
- return this.selectionModel.compareWith;
- }
- set compareWith(fn) {
- this.selectionModel.compareWith = fn;
- }
- /**
- * Whether the keyboard navigation should wrap when the user presses arrow down on the last item
- * or arrow up on the first item.
- */
- get navigationWrapDisabled() {
- return this._navigationWrapDisabled;
- }
- set navigationWrapDisabled(wrap) {
- this._navigationWrapDisabled = wrap;
- this.listKeyManager?.withWrap(!this._navigationWrapDisabled);
- }
- _navigationWrapDisabled = false;
- /** Whether keyboard navigation should skip over disabled items. */
- get navigateDisabledOptions() {
- return this._navigateDisabledOptions;
- }
- set navigateDisabledOptions(skip) {
- this._navigateDisabledOptions = skip;
- this.listKeyManager?.skipPredicate(this._navigateDisabledOptions ? this._skipNonePredicate : this._skipDisabledPredicate);
- }
- _navigateDisabledOptions = false;
- /** Emits when the selected value(s) in the listbox change. */
- valueChange = new Subject();
- /** The child options in this listbox. */
- options;
- /** The selection model used by the listbox. */
- selectionModel = new ListboxSelectionModel();
- /** The key manager that manages keyboard navigation for this listbox. */
- listKeyManager;
- /** Emits when the listbox is destroyed. */
- destroyed = new Subject();
- /** The host element of the listbox. */
- element = inject(ElementRef).nativeElement;
- /** The Angular zone. */
- ngZone = inject(NgZone);
- /** The change detector for this listbox. */
- changeDetectorRef = inject(ChangeDetectorRef);
- /** Whether the currently selected value in the selection model is invalid. */
- _invalid = false;
- /** The last user-triggered option. */
- _lastTriggered = null;
- /** Callback called when the listbox has been touched */
- _onTouched = () => { };
- /** Callback called when the listbox value changes */
- _onChange = () => { };
- /** Emits when an option has been clicked. */
- _optionClicked = defer(() => this.options.changes.pipe(startWith(this.options), switchMap(options => merge(...options.map(option => option._clicked.pipe(map(event => ({ option, event }))))))));
- /** The directionality of the page. */
- _dir = inject(Directionality, { optional: true });
- /** Whether the component is being rendered in the browser. */
- _isBrowser = inject(Platform).isBrowser;
- /** A predicate that skips disabled options. */
- _skipDisabledPredicate = (option) => option.disabled;
- /** A predicate that does not skip any options. */
- _skipNonePredicate = () => false;
- /** Whether the listbox currently has focus. */
- _hasFocus = false;
- /** A reference to the option that was active before the listbox lost focus. */
- _previousActiveOption = null;
- constructor() {
- if (this._isBrowser) {
- const renderer = inject(Renderer2);
- this._cleanupWindowBlur = this.ngZone.runOutsideAngular(() => {
- return renderer.listen('window', 'blur', () => {
- if (this.element.contains(document.activeElement) && this._previousActiveOption) {
- this._setActiveOption(this._previousActiveOption);
- this._previousActiveOption = null;
- }
- });
- });
- }
- }
- ngAfterContentInit() {
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- this._verifyNoOptionValueCollisions();
- this._verifyOptionValues();
- }
- this._initKeyManager();
- // Update the internal value whenever the options or the model value changes.
- merge(this.selectionModel.changed, this.options.changes)
- .pipe(startWith(null), takeUntil(this.destroyed))
- .subscribe(() => this._updateInternalValue());
- this._optionClicked
- .pipe(filter(({ option }) => !option.disabled), takeUntil(this.destroyed))
- .subscribe(({ option, event }) => this._handleOptionClicked(option, event));
- }
- ngOnDestroy() {
- this._cleanupWindowBlur?.();
- this.listKeyManager?.destroy();
- this.destroyed.next();
- this.destroyed.complete();
- }
- /**
- * Toggle the selected state of the given option.
- * @param option The option to toggle
- */
- toggle(option) {
- this.toggleValue(option.value);
- }
- /**
- * Toggle the selected state of the given value.
- * @param value The value to toggle
- */
- toggleValue(value) {
- if (this._invalid) {
- this.selectionModel.clear(false);
- }
- this.selectionModel.toggle(value);
- }
- /**
- * Select the given option.
- * @param option The option to select
- */
- select(option) {
- this.selectValue(option.value);
- }
- /**
- * Select the given value.
- * @param value The value to select
- */
- selectValue(value) {
- if (this._invalid) {
- this.selectionModel.clear(false);
- }
- this.selectionModel.select(value);
- }
- /**
- * Deselect the given option.
- * @param option The option to deselect
- */
- deselect(option) {
- this.deselectValue(option.value);
- }
- /**
- * Deselect the given value.
- * @param value The value to deselect
- */
- deselectValue(value) {
- if (this._invalid) {
- this.selectionModel.clear(false);
- }
- this.selectionModel.deselect(value);
- }
- /**
- * Set the selected state of all options.
- * @param isSelected The new selected state to set
- */
- setAllSelected(isSelected) {
- if (!isSelected) {
- this.selectionModel.clear();
- }
- else {
- if (this._invalid) {
- this.selectionModel.clear(false);
- }
- this.selectionModel.select(...this.options.map(option => option.value));
- }
- }
- /**
- * Get whether the given option is selected.
- * @param option The option to get the selected state of
- */
- isSelected(option) {
- return this.isValueSelected(option.value);
- }
- /**
- * Get whether the given option is active.
- * @param option The option to get the active state of
- */
- isActive(option) {
- return !!(this.listKeyManager?.activeItem === option);
- }
- /**
- * Get whether the given value is selected.
- * @param value The value to get the selected state of
- */
- isValueSelected(value) {
- if (this._invalid) {
- return false;
- }
- return this.selectionModel.isSelected(value);
- }
- /**
- * Registers a callback to be invoked when the listbox's value changes from user input.
- * @param fn The callback to register
- * @docs-private
- */
- registerOnChange(fn) {
- this._onChange = fn;
- }
- /**
- * Registers a callback to be invoked when the listbox is blurred by the user.
- * @param fn The callback to register
- * @docs-private
- */
- registerOnTouched(fn) {
- this._onTouched = fn;
- }
- /**
- * Sets the listbox's value.
- * @param value The new value of the listbox
- * @docs-private
- */
- writeValue(value) {
- this._setSelection(value);
- this._verifyOptionValues();
- }
- /**
- * Sets the disabled state of the listbox.
- * @param isDisabled The new disabled state
- * @docs-private
- */
- setDisabledState(isDisabled) {
- this.disabled = isDisabled;
- this.changeDetectorRef.markForCheck();
- }
- /** Focus the listbox's host element. */
- focus() {
- this.element.focus();
- }
- /**
- * Triggers the given option in response to user interaction.
- * - In single selection mode: selects the option and deselects any other selected option.
- * - In multi selection mode: toggles the selected state of the option.
- * @param option The option to trigger
- */
- triggerOption(option) {
- if (option && !option.disabled) {
- this._lastTriggered = option;
- const changed = this.multiple
- ? this.selectionModel.toggle(option.value)
- : this.selectionModel.select(option.value);
- if (changed) {
- this._onChange(this.value);
- this.valueChange.next({
- value: this.value,
- listbox: this,
- option: option,
- });
- }
- }
- }
- /**
- * Trigger the given range of options in response to user interaction.
- * Should only be called in multi-selection mode.
- * @param trigger The option that was triggered
- * @param from The start index of the options to toggle
- * @param to The end index of the options to toggle
- * @param on Whether to toggle the option range on
- */
- triggerRange(trigger, from, to, on) {
- if (this.disabled || (trigger && trigger.disabled)) {
- return;
- }
- this._lastTriggered = trigger;
- const isEqual = this.compareWith ?? Object.is;
- const updateValues = [...this.options]
- .slice(Math.max(0, Math.min(from, to)), Math.min(this.options.length, Math.max(from, to) + 1))
- .filter(option => !option.disabled)
- .map(option => option.value);
- const selected = [...this.value];
- for (const updateValue of updateValues) {
- const selectedIndex = selected.findIndex(selectedValue => isEqual(selectedValue, updateValue));
- if (on && selectedIndex === -1) {
- selected.push(updateValue);
- }
- else if (!on && selectedIndex !== -1) {
- selected.splice(selectedIndex, 1);
- }
- }
- let changed = this.selectionModel.setSelection(...selected);
- if (changed) {
- this._onChange(this.value);
- this.valueChange.next({
- value: this.value,
- listbox: this,
- option: trigger,
- });
- }
- }
- /**
- * Sets the given option as active.
- * @param option The option to make active
- */
- _setActiveOption(option) {
- this.listKeyManager.setActiveItem(option);
- }
- /** Called when the listbox receives focus. */
- _handleFocus() {
- if (!this.useActiveDescendant) {
- if (this.selectionModel.selected.length > 0) {
- this._setNextFocusToSelectedOption();
- }
- else {
- this.listKeyManager.setNextItemActive();
- }
- this._focusActiveOption();
- }
- }
- /** Called when the user presses keydown on the listbox. */
- _handleKeydown(event) {
- if (this.disabled) {
- return;
- }
- const { keyCode } = event;
- const previousActiveIndex = this.listKeyManager.activeItemIndex;
- const ctrlKeys = ['ctrlKey', 'metaKey'];
- if (this.multiple && keyCode === A && hasModifierKey(event, ...ctrlKeys)) {
- // Toggle all options off if they're all selected, otherwise toggle them all on.
- this.triggerRange(null, 0, this.options.length - 1, this.options.length !== this.value.length);
- event.preventDefault();
- return;
- }
- if (this.multiple &&
- (keyCode === SPACE || keyCode === ENTER) &&
- hasModifierKey(event, 'shiftKey')) {
- if (this.listKeyManager.activeItem && this.listKeyManager.activeItemIndex != null) {
- this.triggerRange(this.listKeyManager.activeItem, this._getLastTriggeredIndex() ?? this.listKeyManager.activeItemIndex, this.listKeyManager.activeItemIndex, !this.listKeyManager.activeItem.isSelected());
- }
- event.preventDefault();
- return;
- }
- if (this.multiple &&
- keyCode === HOME &&
- hasModifierKey(event, ...ctrlKeys) &&
- hasModifierKey(event, 'shiftKey')) {
- const trigger = this.listKeyManager.activeItem;
- if (trigger) {
- const from = this.listKeyManager.activeItemIndex;
- this.listKeyManager.setFirstItemActive();
- this.triggerRange(trigger, from, this.listKeyManager.activeItemIndex, !trigger.isSelected());
- }
- event.preventDefault();
- return;
- }
- if (this.multiple &&
- keyCode === END &&
- hasModifierKey(event, ...ctrlKeys) &&
- hasModifierKey(event, 'shiftKey')) {
- const trigger = this.listKeyManager.activeItem;
- if (trigger) {
- const from = this.listKeyManager.activeItemIndex;
- this.listKeyManager.setLastItemActive();
- this.triggerRange(trigger, from, this.listKeyManager.activeItemIndex, !trigger.isSelected());
- }
- event.preventDefault();
- return;
- }
- if (keyCode === SPACE || keyCode === ENTER) {
- this.triggerOption(this.listKeyManager.activeItem);
- event.preventDefault();
- return;
- }
- const isNavKey = keyCode === UP_ARROW ||
- keyCode === DOWN_ARROW ||
- keyCode === LEFT_ARROW ||
- keyCode === RIGHT_ARROW ||
- keyCode === HOME ||
- keyCode === END;
- this.listKeyManager.onKeydown(event);
- // Will select an option if shift was pressed while navigating to the option
- if (isNavKey && event.shiftKey && previousActiveIndex !== this.listKeyManager.activeItemIndex) {
- this.triggerOption(this.listKeyManager.activeItem);
- }
- }
- /** Called when a focus moves into the listbox. */
- _handleFocusIn() {
- // Note that we use a `focusin` handler for this instead of the existing `focus` handler,
- // because focus won't land on the listbox if `useActiveDescendant` is enabled.
- this._hasFocus = true;
- }
- /**
- * Called when the focus leaves an element in the listbox.
- * @param event The focusout event
- */
- _handleFocusOut(event) {
- // Some browsers (e.g. Chrome and Firefox) trigger the focusout event when the user returns back to the document.
- // To prevent losing the active option in this case, we store it in `_previousActiveOption` and restore it on the window `blur` event
- // This ensures that the `activeItem` matches the actual focused element when the user returns to the document.
- this._previousActiveOption = this.listKeyManager.activeItem;
- const otherElement = event.relatedTarget;
- if (this.element !== otherElement && !this.element.contains(otherElement)) {
- this._onTouched();
- this._hasFocus = false;
- this._setNextFocusToSelectedOption();
- }
- }
- /** Get the id of the active option if active descendant is being used. */
- _getAriaActiveDescendant() {
- return this.useActiveDescendant ? this.listKeyManager?.activeItem?.id : null;
- }
- /** Get the tabindex for the listbox. */
- _getTabIndex() {
- if (this.disabled) {
- return -1;
- }
- return this.useActiveDescendant || !this.listKeyManager.activeItem ? this.enabledTabIndex : -1;
- }
- /** Initialize the key manager. */
- _initKeyManager() {
- this.listKeyManager = new ActiveDescendantKeyManager(this.options)
- .withWrap(!this._navigationWrapDisabled)
- .withTypeAhead()
- .withHomeAndEnd()
- .withAllowedModifierKeys(['shiftKey'])
- .skipPredicate(this._navigateDisabledOptions ? this._skipNonePredicate : this._skipDisabledPredicate);
- if (this.orientation === 'vertical') {
- this.listKeyManager.withVerticalOrientation();
- }
- else {
- this.listKeyManager.withHorizontalOrientation(this._dir?.value || 'ltr');
- }
- if (this.selectionModel.selected.length) {
- Promise.resolve().then(() => this._setNextFocusToSelectedOption());
- }
- this.listKeyManager.change.subscribe(() => this._focusActiveOption());
- this.options.changes.pipe(takeUntil(this.destroyed)).subscribe(() => {
- const activeOption = this.listKeyManager.activeItem;
- // If the active option was deleted, we need to reset
- // the key manager so it can allow focus back in.
- if (activeOption && !this.options.find(option => option === activeOption)) {
- this.listKeyManager.setActiveItem(-1);
- this.changeDetectorRef.markForCheck();
- }
- });
- }
- /** Focus the active option. */
- _focusActiveOption() {
- if (!this.useActiveDescendant) {
- this.listKeyManager.activeItem?.focus();
- }
- this.changeDetectorRef.markForCheck();
- }
- /**
- * Set the selected values.
- * @param value The list of new selected values.
- */
- _setSelection(value) {
- if (this._invalid) {
- this.selectionModel.clear(false);
- }
- this.selectionModel.setSelection(...this._coerceValue(value));
- if (!this._hasFocus) {
- this._setNextFocusToSelectedOption();
- }
- }
- /** Sets the first selected option as first in the keyboard focus order. */
- _setNextFocusToSelectedOption() {
- // Null check the options since they only get defined after `ngAfterContentInit`.
- const selected = this.options?.find(option => option.isSelected());
- if (selected) {
- this.listKeyManager.updateActiveItem(selected);
- }
- }
- /** Update the internal value of the listbox based on the selection model. */
- _updateInternalValue() {
- const indexCache = new Map();
- this.selectionModel.sort((a, b) => {
- const aIndex = this._getIndexForValue(indexCache, a);
- const bIndex = this._getIndexForValue(indexCache, b);
- return aIndex - bIndex;
- });
- const selected = this.selectionModel.selected;
- this._invalid =
- (!this.multiple && selected.length > 1) || !!this._getInvalidOptionValues(selected).length;
- this.changeDetectorRef.markForCheck();
- }
- /**
- * Gets the index of the given value in the given list of options.
- * @param cache The cache of indices found so far
- * @param value The value to find
- * @return The index of the value in the options list
- */
- _getIndexForValue(cache, value) {
- const isEqual = this.compareWith || Object.is;
- if (!cache.has(value)) {
- let index = -1;
- for (let i = 0; i < this.options.length; i++) {
- if (isEqual(value, this.options.get(i).value)) {
- index = i;
- break;
- }
- }
- cache.set(value, index);
- }
- return cache.get(value);
- }
- /**
- * Handle the user clicking an option.
- * @param option The option that was clicked.
- */
- _handleOptionClicked(option, event) {
- event.preventDefault();
- this.listKeyManager.setActiveItem(option);
- if (event.shiftKey && this.multiple) {
- this.triggerRange(option, this._getLastTriggeredIndex() ?? this.listKeyManager.activeItemIndex, this.listKeyManager.activeItemIndex, !option.isSelected());
- }
- else {
- this.triggerOption(option);
- }
- }
- /** Verifies that no two options represent the same value under the compareWith function. */
- _verifyNoOptionValueCollisions() {
- this.options.changes.pipe(startWith(this.options), takeUntil(this.destroyed)).subscribe(() => {
- const isEqual = this.compareWith ?? Object.is;
- for (let i = 0; i < this.options.length; i++) {
- const option = this.options.get(i);
- let duplicate = null;
- for (let j = i + 1; j < this.options.length; j++) {
- const other = this.options.get(j);
- if (isEqual(option.value, other.value)) {
- duplicate = other;
- break;
- }
- }
- if (duplicate) {
- // TODO(mmalerba): Link to docs about this.
- if (this.compareWith) {
- console.warn(`Found multiple CdkOption representing the same value under the given compareWith function`, {
- option1: option.element,
- option2: duplicate.element,
- compareWith: this.compareWith,
- });
- }
- else {
- console.warn(`Found multiple CdkOption with the same value`, {
- option1: option.element,
- option2: duplicate.element,
- });
- }
- return;
- }
- }
- });
- }
- /** Verifies that the option values are valid. */
- _verifyOptionValues() {
- if (this.options && (typeof ngDevMode === 'undefined' || ngDevMode)) {
- const selected = this.selectionModel.selected;
- const invalidValues = this._getInvalidOptionValues(selected);
- if (!this.multiple && selected.length > 1) {
- throw Error('Listbox cannot have more than one selected value in multi-selection mode.');
- }
- if (invalidValues.length) {
- throw Error('Listbox has selected values that do not match any of its options.');
- }
- }
- }
- /**
- * Coerces a value into an array representing a listbox selection.
- * @param value The value to coerce
- * @return An array
- */
- _coerceValue(value) {
- return value == null ? [] : coerceArray(value);
- }
- /**
- * Get the sublist of values that do not represent valid option values in this listbox.
- * @param values The list of values
- * @return The sublist of values that are not valid option values
- */
- _getInvalidOptionValues(values) {
- const isEqual = this.compareWith || Object.is;
- const validValues = (this.options || []).map(option => option.value);
- return values.filter(value => !validValues.some(validValue => isEqual(value, validValue)));
- }
- /** Get the index of the last triggered option. */
- _getLastTriggeredIndex() {
- const index = this.options.toArray().indexOf(this._lastTriggered);
- return index === -1 ? null : index;
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkListbox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkListbox, isStandalone: true, selector: "[cdkListbox]", inputs: { id: "id", enabledTabIndex: ["tabindex", "enabledTabIndex"], value: ["cdkListboxValue", "value"], multiple: ["cdkListboxMultiple", "multiple", booleanAttribute], disabled: ["cdkListboxDisabled", "disabled", booleanAttribute], useActiveDescendant: ["cdkListboxUseActiveDescendant", "useActiveDescendant", booleanAttribute], orientation: ["cdkListboxOrientation", "orientation"], compareWith: ["cdkListboxCompareWith", "compareWith"], navigationWrapDisabled: ["cdkListboxNavigationWrapDisabled", "navigationWrapDisabled", booleanAttribute], navigateDisabledOptions: ["cdkListboxNavigatesDisabledOptions", "navigateDisabledOptions", booleanAttribute] }, outputs: { valueChange: "cdkListboxValueChange" }, host: { attributes: { "role": "listbox" }, listeners: { "focus": "_handleFocus()", "keydown": "_handleKeydown($event)", "focusout": "_handleFocusOut($event)", "focusin": "_handleFocusIn()" }, properties: { "id": "id", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled", "attr.aria-multiselectable": "multiple", "attr.aria-activedescendant": "_getAriaActiveDescendant()", "attr.aria-orientation": "orientation" }, classAttribute: "cdk-listbox" }, providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => CdkListbox),
- multi: true,
- },
- ], queries: [{ propertyName: "options", predicate: CdkOption, descendants: true }], exportAs: ["cdkListbox"], ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkListbox, decorators: [{
- type: Directive,
- args: [{
- selector: '[cdkListbox]',
- exportAs: 'cdkListbox',
- host: {
- 'role': 'listbox',
- 'class': 'cdk-listbox',
- '[id]': 'id',
- '[attr.tabindex]': '_getTabIndex()',
- '[attr.aria-disabled]': 'disabled',
- '[attr.aria-multiselectable]': 'multiple',
- '[attr.aria-activedescendant]': '_getAriaActiveDescendant()',
- '[attr.aria-orientation]': 'orientation',
- '(focus)': '_handleFocus()',
- '(keydown)': '_handleKeydown($event)',
- '(focusout)': '_handleFocusOut($event)',
- '(focusin)': '_handleFocusIn()',
- },
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => CdkListbox),
- multi: true,
- },
- ],
- }]
- }], ctorParameters: () => [], propDecorators: { id: [{
- type: Input
- }], enabledTabIndex: [{
- type: Input,
- args: ['tabindex']
- }], value: [{
- type: Input,
- args: ['cdkListboxValue']
- }], multiple: [{
- type: Input,
- args: [{ alias: 'cdkListboxMultiple', transform: booleanAttribute }]
- }], disabled: [{
- type: Input,
- args: [{ alias: 'cdkListboxDisabled', transform: booleanAttribute }]
- }], useActiveDescendant: [{
- type: Input,
- args: [{ alias: 'cdkListboxUseActiveDescendant', transform: booleanAttribute }]
- }], orientation: [{
- type: Input,
- args: ['cdkListboxOrientation']
- }], compareWith: [{
- type: Input,
- args: ['cdkListboxCompareWith']
- }], navigationWrapDisabled: [{
- type: Input,
- args: [{ alias: 'cdkListboxNavigationWrapDisabled', transform: booleanAttribute }]
- }], navigateDisabledOptions: [{
- type: Input,
- args: [{ alias: 'cdkListboxNavigatesDisabledOptions', transform: booleanAttribute }]
- }], valueChange: [{
- type: Output,
- args: ['cdkListboxValueChange']
- }], options: [{
- type: ContentChildren,
- args: [CdkOption, { descendants: true }]
- }] } });
- const EXPORTED_DECLARATIONS = [CdkListbox, CdkOption];
- class CdkListboxModule {
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkListboxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: CdkListboxModule, imports: [CdkListbox, CdkOption], exports: [CdkListbox, CdkOption] });
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkListboxModule });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkListboxModule, decorators: [{
- type: NgModule,
- args: [{
- imports: [...EXPORTED_DECLARATIONS],
- exports: [...EXPORTED_DECLARATIONS],
- }]
- }] });
- export { CdkListbox, CdkListboxModule, CdkOption };
- //# sourceMappingURL=listbox.mjs.map
|