UNPKG

77.3 kBJavaScriptView Raw
1import * as i8 from '@angular/cdk/overlay';
2import { Overlay, CdkConnectedOverlay, OverlayModule } from '@angular/cdk/overlay';
3import * as i7 from '@angular/common';
4import { CommonModule } from '@angular/common';
5import * as i0 from '@angular/core';
6import { InjectionToken, Directive, EventEmitter, Optional, Inject, Self, Attribute, Input, ViewChild, Output, Component, ViewEncapsulation, ChangeDetectionStrategy, ContentChildren, ContentChild, NgModule } from '@angular/core';
7import * as i2 from '@angular/material/core';
8import { mixinDisableRipple, mixinTabIndex, mixinDisabled, mixinErrorState, _countGroupLabelsBeforeOption, _getOptionScrollPosition, MAT_OPTION_PARENT_COMPONENT, MatOption, MAT_OPTGROUP, MatOptionModule, MatCommonModule } from '@angular/material/core';
9import * as i6 from '@angular/material/form-field';
10import { MAT_FORM_FIELD, MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field';
11import * as i1 from '@angular/cdk/scrolling';
12import { CdkScrollableModule } from '@angular/cdk/scrolling';
13import * as i5 from '@angular/cdk/a11y';
14import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
15import * as i3 from '@angular/cdk/bidi';
16import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
17import { SelectionModel } from '@angular/cdk/collections';
18import { DOWN_ARROW, UP_ARROW, LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE, hasModifierKey, A } from '@angular/cdk/keycodes';
19import * as i4 from '@angular/forms';
20import { Validators } from '@angular/forms';
21import { Subject, defer, merge } from 'rxjs';
22import { startWith, switchMap, take, filter, map, distinctUntilChanged, takeUntil } from 'rxjs/operators';
23import { trigger, transition, query, animateChild, state, style, animate } from '@angular/animations';
24
25/**
26 * @license
27 * Copyright Google LLC All Rights Reserved.
28 *
29 * Use of this source code is governed by an MIT-style license that can be
30 * found in the LICENSE file at https://angular.io/license
31 */
32/**
33 * The following are all the animations for the mat-select component, with each
34 * const containing the metadata for one animation.
35 *
36 * The values below match the implementation of the AngularJS Material mat-select animation.
37 * @docs-private
38 */
39const matSelectAnimations = {
40 /**
41 * This animation ensures the select's overlay panel animation (transformPanel) is called when
42 * closing the select.
43 * This is needed due to https://github.com/angular/angular/issues/23302
44 */
45 transformPanelWrap: trigger('transformPanelWrap', [
46 transition('* => void', query('@transformPanel', [animateChild()], { optional: true })),
47 ]),
48 /**
49 * This animation transforms the select's overlay panel on and off the page.
50 *
51 * When the panel is attached to the DOM, it expands its width by the amount of padding, scales it
52 * up to 100% on the Y axis, fades in its border, and translates slightly up and to the
53 * side to ensure the option text correctly overlaps the trigger text.
54 *
55 * When the panel is removed from the DOM, it simply fades out linearly.
56 */
57 transformPanel: trigger('transformPanel', [
58 state('void', style({
59 transform: 'scaleY(0.8)',
60 minWidth: '100%',
61 opacity: 0,
62 })),
63 state('showing', style({
64 opacity: 1,
65 minWidth: 'calc(100% + 32px)',
66 transform: 'scaleY(1)',
67 })),
68 state('showing-multiple', style({
69 opacity: 1,
70 minWidth: 'calc(100% + 64px)',
71 transform: 'scaleY(1)',
72 })),
73 transition('void => *', animate('120ms cubic-bezier(0, 0, 0.2, 1)')),
74 transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 }))),
75 ]),
76};
77
78/**
79 * @license
80 * Copyright Google LLC All Rights Reserved.
81 *
82 * Use of this source code is governed by an MIT-style license that can be
83 * found in the LICENSE file at https://angular.io/license
84 */
85/**
86 * Returns an exception to be thrown when attempting to change a select's `multiple` option
87 * after initialization.
88 * @docs-private
89 */
90function getMatSelectDynamicMultipleError() {
91 return Error('Cannot change `multiple` mode of select after initialization.');
92}
93/**
94 * Returns an exception to be thrown when attempting to assign a non-array value to a select
95 * in `multiple` mode. Note that `undefined` and `null` are still valid values to allow for
96 * resetting the value.
97 * @docs-private
98 */
99function getMatSelectNonArrayValueError() {
100 return Error('Value must be an array in multiple-selection mode.');
101}
102/**
103 * Returns an exception to be thrown when assigning a non-function value to the comparator
104 * used to determine if a value corresponds to an option. Note that whether the function
105 * actually takes two values and returns a boolean is not checked.
106 */
107function getMatSelectNonFunctionValueError() {
108 return Error('`compareWith` must be a function.');
109}
110
111/**
112 * @license
113 * Copyright Google LLC All Rights Reserved.
114 *
115 * Use of this source code is governed by an MIT-style license that can be
116 * found in the LICENSE file at https://angular.io/license
117 */
118let nextUniqueId = 0;
119/**
120 * The following style constants are necessary to save here in order
121 * to properly calculate the alignment of the selected option over
122 * the trigger element.
123 */
124/** The max height of the select's overlay panel. */
125const SELECT_PANEL_MAX_HEIGHT = 256;
126/** The panel's padding on the x-axis. */
127const SELECT_PANEL_PADDING_X = 16;
128/** The panel's x axis padding if it is indented (e.g. there is an option group). */
129const SELECT_PANEL_INDENT_PADDING_X = SELECT_PANEL_PADDING_X * 2;
130/** The height of the select items in `em` units. */
131const SELECT_ITEM_HEIGHT_EM = 3;
132// TODO(josephperrott): Revert to a constant after 2018 spec updates are fully merged.
133/**
134 * Distance between the panel edge and the option text in
135 * multi-selection mode.
136 *
137 * Calculated as:
138 * (SELECT_PANEL_PADDING_X * 1.5) + 16 = 40
139 * The padding is multiplied by 1.5 because the checkbox's margin is half the padding.
140 * The checkbox width is 16px.
141 */
142const SELECT_MULTIPLE_PANEL_PADDING_X = SELECT_PANEL_PADDING_X * 1.5 + 16;
143/**
144 * The select panel will only "fit" inside the viewport if it is positioned at
145 * this value or more away from the viewport boundary.
146 */
147const SELECT_PANEL_VIEWPORT_PADDING = 8;
148/** Injection token that determines the scroll handling while a select is open. */
149const MAT_SELECT_SCROLL_STRATEGY = new InjectionToken('mat-select-scroll-strategy');
150/** @docs-private */
151function MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) {
152 return () => overlay.scrollStrategies.reposition();
153}
154/** Injection token that can be used to provide the default options the select module. */
155const MAT_SELECT_CONFIG = new InjectionToken('MAT_SELECT_CONFIG');
156/** @docs-private */
157const MAT_SELECT_SCROLL_STRATEGY_PROVIDER = {
158 provide: MAT_SELECT_SCROLL_STRATEGY,
159 deps: [Overlay],
160 useFactory: MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY,
161};
162/** Change event object that is emitted when the select value has changed. */
163class MatSelectChange {
164 constructor(
165 /** Reference to the select that emitted the change event. */
166 source,
167 /** Current value of the select that emitted the event. */
168 value) {
169 this.source = source;
170 this.value = value;
171 }
172}
173// Boilerplate for applying mixins to MatSelect.
174/** @docs-private */
175const _MatSelectMixinBase = mixinDisableRipple(mixinTabIndex(mixinDisabled(mixinErrorState(class {
176 constructor(_elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup,
177 /**
178 * Form control bound to the component.
179 * Implemented as part of `MatFormFieldControl`.
180 * @docs-private
181 */
182 ngControl) {
183 this._elementRef = _elementRef;
184 this._defaultErrorStateMatcher = _defaultErrorStateMatcher;
185 this._parentForm = _parentForm;
186 this._parentFormGroup = _parentFormGroup;
187 this.ngControl = ngControl;
188 /**
189 * Emits whenever the component state changes and should cause the parent
190 * form-field to update. Implemented as part of `MatFormFieldControl`.
191 * @docs-private
192 */
193 this.stateChanges = new Subject();
194 }
195}))));
196/**
197 * Injection token that can be used to reference instances of `MatSelectTrigger`. It serves as
198 * alternative token to the actual `MatSelectTrigger` class which could cause unnecessary
199 * retention of the class and its directive metadata.
200 */
201const MAT_SELECT_TRIGGER = new InjectionToken('MatSelectTrigger');
202/**
203 * Allows the user to customize the trigger that is displayed when the select has a value.
204 */
205class MatSelectTrigger {
206}
207MatSelectTrigger.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelectTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
208MatSelectTrigger.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: MatSelectTrigger, selector: "mat-select-trigger", providers: [{ provide: MAT_SELECT_TRIGGER, useExisting: MatSelectTrigger }], ngImport: i0 });
209i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelectTrigger, decorators: [{
210 type: Directive,
211 args: [{
212 selector: 'mat-select-trigger',
213 providers: [{ provide: MAT_SELECT_TRIGGER, useExisting: MatSelectTrigger }],
214 }]
215 }] });
216/** Base class with all of the `MatSelect` functionality. */
217class _MatSelectBase extends _MatSelectMixinBase {
218 constructor(_viewportRuler, _changeDetectorRef, _ngZone, _defaultErrorStateMatcher, elementRef, _dir, _parentForm, _parentFormGroup, _parentFormField, ngControl, tabIndex, scrollStrategyFactory, _liveAnnouncer, _defaultOptions) {
219 super(elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
220 this._viewportRuler = _viewportRuler;
221 this._changeDetectorRef = _changeDetectorRef;
222 this._ngZone = _ngZone;
223 this._dir = _dir;
224 this._parentFormField = _parentFormField;
225 this._liveAnnouncer = _liveAnnouncer;
226 this._defaultOptions = _defaultOptions;
227 /** Whether or not the overlay panel is open. */
228 this._panelOpen = false;
229 /** Comparison function to specify which option is displayed. Defaults to object equality. */
230 this._compareWith = (o1, o2) => o1 === o2;
231 /** Unique id for this input. */
232 this._uid = `mat-select-${nextUniqueId++}`;
233 /** Current `aria-labelledby` value for the select trigger. */
234 this._triggerAriaLabelledBy = null;
235 /** Emits whenever the component is destroyed. */
236 this._destroy = new Subject();
237 /** `View -> model callback called when value changes` */
238 this._onChange = () => { };
239 /** `View -> model callback called when select has been touched` */
240 this._onTouched = () => { };
241 /** ID for the DOM node containing the select's value. */
242 this._valueId = `mat-select-value-${nextUniqueId++}`;
243 /** Emits when the panel element is finished transforming in. */
244 this._panelDoneAnimatingStream = new Subject();
245 this._overlayPanelClass = this._defaultOptions?.overlayPanelClass || '';
246 this._focused = false;
247 /** A name for this control that can be used by `mat-form-field`. */
248 this.controlType = 'mat-select';
249 this._multiple = false;
250 this._disableOptionCentering = this._defaultOptions?.disableOptionCentering ?? false;
251 /** Aria label of the select. */
252 this.ariaLabel = '';
253 /** Combined stream of all of the child options' change events. */
254 this.optionSelectionChanges = defer(() => {
255 const options = this.options;
256 if (options) {
257 return options.changes.pipe(startWith(options), switchMap(() => merge(...options.map(option => option.onSelectionChange))));
258 }
259 return this._ngZone.onStable.pipe(take(1), switchMap(() => this.optionSelectionChanges));
260 });
261 /** Event emitted when the select panel has been toggled. */
262 this.openedChange = new EventEmitter();
263 /** Event emitted when the select has been opened. */
264 this._openedStream = this.openedChange.pipe(filter(o => o), map(() => { }));
265 /** Event emitted when the select has been closed. */
266 this._closedStream = this.openedChange.pipe(filter(o => !o), map(() => { }));
267 /** Event emitted when the selected value has been changed by the user. */
268 this.selectionChange = new EventEmitter();
269 /**
270 * Event that emits whenever the raw value of the select changes. This is here primarily
271 * to facilitate the two-way binding for the `value` input.
272 * @docs-private
273 */
274 this.valueChange = new EventEmitter();
275 if (this.ngControl) {
276 // Note: we provide the value accessor through here, instead of
277 // the `providers` to avoid running into a circular import.
278 this.ngControl.valueAccessor = this;
279 }
280 // Note that we only want to set this when the defaults pass it in, otherwise it should
281 // stay as `undefined` so that it falls back to the default in the key manager.
282 if (_defaultOptions?.typeaheadDebounceInterval != null) {
283 this._typeaheadDebounceInterval = _defaultOptions.typeaheadDebounceInterval;
284 }
285 this._scrollStrategyFactory = scrollStrategyFactory;
286 this._scrollStrategy = this._scrollStrategyFactory();
287 this.tabIndex = parseInt(tabIndex) || 0;
288 // Force setter to be called in case id was not specified.
289 this.id = this.id;
290 }
291 /** Whether the select is focused. */
292 get focused() {
293 return this._focused || this._panelOpen;
294 }
295 /** Placeholder to be shown if no value has been selected. */
296 get placeholder() {
297 return this._placeholder;
298 }
299 set placeholder(value) {
300 this._placeholder = value;
301 this.stateChanges.next();
302 }
303 /** Whether the component is required. */
304 get required() {
305 return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false;
306 }
307 set required(value) {
308 this._required = coerceBooleanProperty(value);
309 this.stateChanges.next();
310 }
311 /** Whether the user should be allowed to select multiple options. */
312 get multiple() {
313 return this._multiple;
314 }
315 set multiple(value) {
316 if (this._selectionModel && (typeof ngDevMode === 'undefined' || ngDevMode)) {
317 throw getMatSelectDynamicMultipleError();
318 }
319 this._multiple = coerceBooleanProperty(value);
320 }
321 /** Whether to center the active option over the trigger. */
322 get disableOptionCentering() {
323 return this._disableOptionCentering;
324 }
325 set disableOptionCentering(value) {
326 this._disableOptionCentering = coerceBooleanProperty(value);
327 }
328 /**
329 * Function to compare the option values with the selected values. The first argument
330 * is a value from an option. The second is a value from the selection. A boolean
331 * should be returned.
332 */
333 get compareWith() {
334 return this._compareWith;
335 }
336 set compareWith(fn) {
337 if (typeof fn !== 'function' && (typeof ngDevMode === 'undefined' || ngDevMode)) {
338 throw getMatSelectNonFunctionValueError();
339 }
340 this._compareWith = fn;
341 if (this._selectionModel) {
342 // A different comparator means the selection could change.
343 this._initializeSelection();
344 }
345 }
346 /** Value of the select control. */
347 get value() {
348 return this._value;
349 }
350 set value(newValue) {
351 const hasAssigned = this._assignValue(newValue);
352 if (hasAssigned) {
353 this._onChange(newValue);
354 }
355 }
356 /** Time to wait in milliseconds after the last keystroke before moving focus to an item. */
357 get typeaheadDebounceInterval() {
358 return this._typeaheadDebounceInterval;
359 }
360 set typeaheadDebounceInterval(value) {
361 this._typeaheadDebounceInterval = coerceNumberProperty(value);
362 }
363 /** Unique id of the element. */
364 get id() {
365 return this._id;
366 }
367 set id(value) {
368 this._id = value || this._uid;
369 this.stateChanges.next();
370 }
371 ngOnInit() {
372 this._selectionModel = new SelectionModel(this.multiple);
373 this.stateChanges.next();
374 // We need `distinctUntilChanged` here, because some browsers will
375 // fire the animation end event twice for the same animation. See:
376 // https://github.com/angular/angular/issues/24084
377 this._panelDoneAnimatingStream
378 .pipe(distinctUntilChanged(), takeUntil(this._destroy))
379 .subscribe(() => this._panelDoneAnimating(this.panelOpen));
380 }
381 ngAfterContentInit() {
382 this._initKeyManager();
383 this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
384 event.added.forEach(option => option.select());
385 event.removed.forEach(option => option.deselect());
386 });
387 this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
388 this._resetOptions();
389 this._initializeSelection();
390 });
391 }
392 ngDoCheck() {
393 const newAriaLabelledby = this._getTriggerAriaLabelledby();
394 const ngControl = this.ngControl;
395 // We have to manage setting the `aria-labelledby` ourselves, because part of its value
396 // is computed as a result of a content query which can cause this binding to trigger a
397 // "changed after checked" error.
398 if (newAriaLabelledby !== this._triggerAriaLabelledBy) {
399 const element = this._elementRef.nativeElement;
400 this._triggerAriaLabelledBy = newAriaLabelledby;
401 if (newAriaLabelledby) {
402 element.setAttribute('aria-labelledby', newAriaLabelledby);
403 }
404 else {
405 element.removeAttribute('aria-labelledby');
406 }
407 }
408 if (ngControl) {
409 // The disabled state might go out of sync if the form group is swapped out. See #17860.
410 if (this._previousControl !== ngControl.control) {
411 if (this._previousControl !== undefined &&
412 ngControl.disabled !== null &&
413 ngControl.disabled !== this.disabled) {
414 this.disabled = ngControl.disabled;
415 }
416 this._previousControl = ngControl.control;
417 }
418 this.updateErrorState();
419 }
420 }
421 ngOnChanges(changes) {
422 // Updating the disabled state is handled by `mixinDisabled`, but we need to additionally let
423 // the parent form field know to run change detection when the disabled state changes.
424 if (changes['disabled'] || changes['userAriaDescribedBy']) {
425 this.stateChanges.next();
426 }
427 if (changes['typeaheadDebounceInterval'] && this._keyManager) {
428 this._keyManager.withTypeAhead(this._typeaheadDebounceInterval);
429 }
430 }
431 ngOnDestroy() {
432 this._destroy.next();
433 this._destroy.complete();
434 this.stateChanges.complete();
435 }
436 /** Toggles the overlay panel open or closed. */
437 toggle() {
438 this.panelOpen ? this.close() : this.open();
439 }
440 /** Opens the overlay panel. */
441 open() {
442 if (this._canOpen()) {
443 this._panelOpen = true;
444 this._keyManager.withHorizontalOrientation(null);
445 this._highlightCorrectOption();
446 this._changeDetectorRef.markForCheck();
447 }
448 }
449 /** Closes the overlay panel and focuses the host element. */
450 close() {
451 if (this._panelOpen) {
452 this._panelOpen = false;
453 this._keyManager.withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr');
454 this._changeDetectorRef.markForCheck();
455 this._onTouched();
456 }
457 }
458 /**
459 * Sets the select's value. Part of the ControlValueAccessor interface
460 * required to integrate with Angular's core forms API.
461 *
462 * @param value New value to be written to the model.
463 */
464 writeValue(value) {
465 this._assignValue(value);
466 }
467 /**
468 * Saves a callback function to be invoked when the select's value
469 * changes from user input. Part of the ControlValueAccessor interface
470 * required to integrate with Angular's core forms API.
471 *
472 * @param fn Callback to be triggered when the value changes.
473 */
474 registerOnChange(fn) {
475 this._onChange = fn;
476 }
477 /**
478 * Saves a callback function to be invoked when the select is blurred
479 * by the user. Part of the ControlValueAccessor interface required
480 * to integrate with Angular's core forms API.
481 *
482 * @param fn Callback to be triggered when the component has been touched.
483 */
484 registerOnTouched(fn) {
485 this._onTouched = fn;
486 }
487 /**
488 * Disables the select. Part of the ControlValueAccessor interface required
489 * to integrate with Angular's core forms API.
490 *
491 * @param isDisabled Sets whether the component is disabled.
492 */
493 setDisabledState(isDisabled) {
494 this.disabled = isDisabled;
495 this._changeDetectorRef.markForCheck();
496 this.stateChanges.next();
497 }
498 /** Whether or not the overlay panel is open. */
499 get panelOpen() {
500 return this._panelOpen;
501 }
502 /** The currently selected option. */
503 get selected() {
504 return this.multiple ? this._selectionModel?.selected || [] : this._selectionModel?.selected[0];
505 }
506 /** The value displayed in the trigger. */
507 get triggerValue() {
508 if (this.empty) {
509 return '';
510 }
511 if (this._multiple) {
512 const selectedOptions = this._selectionModel.selected.map(option => option.viewValue);
513 if (this._isRtl()) {
514 selectedOptions.reverse();
515 }
516 // TODO(crisbeto): delimiter should be configurable for proper localization.
517 return selectedOptions.join(', ');
518 }
519 return this._selectionModel.selected[0].viewValue;
520 }
521 /** Whether the element is in RTL mode. */
522 _isRtl() {
523 return this._dir ? this._dir.value === 'rtl' : false;
524 }
525 /** Handles all keydown events on the select. */
526 _handleKeydown(event) {
527 if (!this.disabled) {
528 this.panelOpen ? this._handleOpenKeydown(event) : this._handleClosedKeydown(event);
529 }
530 }
531 /** Handles keyboard events while the select is closed. */
532 _handleClosedKeydown(event) {
533 const keyCode = event.keyCode;
534 const isArrowKey = keyCode === DOWN_ARROW ||
535 keyCode === UP_ARROW ||
536 keyCode === LEFT_ARROW ||
537 keyCode === RIGHT_ARROW;
538 const isOpenKey = keyCode === ENTER || keyCode === SPACE;
539 const manager = this._keyManager;
540 // Open the select on ALT + arrow key to match the native <select>
541 if ((!manager.isTyping() && isOpenKey && !hasModifierKey(event)) ||
542 ((this.multiple || event.altKey) && isArrowKey)) {
543 event.preventDefault(); // prevents the page from scrolling down when pressing space
544 this.open();
545 }
546 else if (!this.multiple) {
547 const previouslySelectedOption = this.selected;
548 manager.onKeydown(event);
549 const selectedOption = this.selected;
550 // Since the value has changed, we need to announce it ourselves.
551 if (selectedOption && previouslySelectedOption !== selectedOption) {
552 // We set a duration on the live announcement, because we want the live element to be
553 // cleared after a while so that users can't navigate to it using the arrow keys.
554 this._liveAnnouncer.announce(selectedOption.viewValue, 10000);
555 }
556 }
557 }
558 /** Handles keyboard events when the selected is open. */
559 _handleOpenKeydown(event) {
560 const manager = this._keyManager;
561 const keyCode = event.keyCode;
562 const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
563 const isTyping = manager.isTyping();
564 if (isArrowKey && event.altKey) {
565 // Close the select on ALT + arrow key to match the native <select>
566 event.preventDefault();
567 this.close();
568 // Don't do anything in this case if the user is typing,
569 // because the typing sequence can include the space key.
570 }
571 else if (!isTyping &&
572 (keyCode === ENTER || keyCode === SPACE) &&
573 manager.activeItem &&
574 !hasModifierKey(event)) {
575 event.preventDefault();
576 manager.activeItem._selectViaInteraction();
577 }
578 else if (!isTyping && this._multiple && keyCode === A && event.ctrlKey) {
579 event.preventDefault();
580 const hasDeselectedOptions = this.options.some(opt => !opt.disabled && !opt.selected);
581 this.options.forEach(option => {
582 if (!option.disabled) {
583 hasDeselectedOptions ? option.select() : option.deselect();
584 }
585 });
586 }
587 else {
588 const previouslyFocusedIndex = manager.activeItemIndex;
589 manager.onKeydown(event);
590 if (this._multiple &&
591 isArrowKey &&
592 event.shiftKey &&
593 manager.activeItem &&
594 manager.activeItemIndex !== previouslyFocusedIndex) {
595 manager.activeItem._selectViaInteraction();
596 }
597 }
598 }
599 _onFocus() {
600 if (!this.disabled) {
601 this._focused = true;
602 this.stateChanges.next();
603 }
604 }
605 /**
606 * Calls the touched callback only if the panel is closed. Otherwise, the trigger will
607 * "blur" to the panel when it opens, causing a false positive.
608 */
609 _onBlur() {
610 this._focused = false;
611 if (!this.disabled && !this.panelOpen) {
612 this._onTouched();
613 this._changeDetectorRef.markForCheck();
614 this.stateChanges.next();
615 }
616 }
617 /**
618 * Callback that is invoked when the overlay panel has been attached.
619 */
620 _onAttached() {
621 this._overlayDir.positionChange.pipe(take(1)).subscribe(() => {
622 this._changeDetectorRef.detectChanges();
623 this._positioningSettled();
624 });
625 }
626 /** Returns the theme to be used on the panel. */
627 _getPanelTheme() {
628 return this._parentFormField ? `mat-${this._parentFormField.color}` : '';
629 }
630 /** Whether the select has a value. */
631 get empty() {
632 return !this._selectionModel || this._selectionModel.isEmpty();
633 }
634 _initializeSelection() {
635 // Defer setting the value in order to avoid the "Expression
636 // has changed after it was checked" errors from Angular.
637 Promise.resolve().then(() => {
638 if (this.ngControl) {
639 this._value = this.ngControl.value;
640 }
641 this._setSelectionByValue(this._value);
642 this.stateChanges.next();
643 });
644 }
645 /**
646 * Sets the selected option based on a value. If no option can be
647 * found with the designated value, the select trigger is cleared.
648 */
649 _setSelectionByValue(value) {
650 this._selectionModel.selected.forEach(option => option.setInactiveStyles());
651 this._selectionModel.clear();
652 if (this.multiple && value) {
653 if (!Array.isArray(value) && (typeof ngDevMode === 'undefined' || ngDevMode)) {
654 throw getMatSelectNonArrayValueError();
655 }
656 value.forEach((currentValue) => this._selectOptionByValue(currentValue));
657 this._sortValues();
658 }
659 else {
660 const correspondingOption = this._selectOptionByValue(value);
661 // Shift focus to the active item. Note that we shouldn't do this in multiple
662 // mode, because we don't know what option the user interacted with last.
663 if (correspondingOption) {
664 this._keyManager.updateActiveItem(correspondingOption);
665 }
666 else if (!this.panelOpen) {
667 // Otherwise reset the highlighted option. Note that we only want to do this while
668 // closed, because doing it while open can shift the user's focus unnecessarily.
669 this._keyManager.updateActiveItem(-1);
670 }
671 }
672 this._changeDetectorRef.markForCheck();
673 }
674 /**
675 * Finds and selects and option based on its value.
676 * @returns Option that has the corresponding value.
677 */
678 _selectOptionByValue(value) {
679 const correspondingOption = this.options.find((option) => {
680 // Skip options that are already in the model. This allows us to handle cases
681 // where the same primitive value is selected multiple times.
682 if (this._selectionModel.isSelected(option)) {
683 return false;
684 }
685 try {
686 // Treat null as a special reset value.
687 return option.value != null && this._compareWith(option.value, value);
688 }
689 catch (error) {
690 if (typeof ngDevMode === 'undefined' || ngDevMode) {
691 // Notify developers of errors in their comparator.
692 console.warn(error);
693 }
694 return false;
695 }
696 });
697 if (correspondingOption) {
698 this._selectionModel.select(correspondingOption);
699 }
700 return correspondingOption;
701 }
702 /** Assigns a specific value to the select. Returns whether the value has changed. */
703 _assignValue(newValue) {
704 // Always re-assign an array, because it might have been mutated.
705 if (newValue !== this._value || (this._multiple && Array.isArray(newValue))) {
706 if (this.options) {
707 this._setSelectionByValue(newValue);
708 }
709 this._value = newValue;
710 return true;
711 }
712 return false;
713 }
714 /** Sets up a key manager to listen to keyboard events on the overlay panel. */
715 _initKeyManager() {
716 this._keyManager = new ActiveDescendantKeyManager(this.options)
717 .withTypeAhead(this._typeaheadDebounceInterval)
718 .withVerticalOrientation()
719 .withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr')
720 .withHomeAndEnd()
721 .withAllowedModifierKeys(['shiftKey']);
722 this._keyManager.tabOut.pipe(takeUntil(this._destroy)).subscribe(() => {
723 if (this.panelOpen) {
724 // Select the active item when tabbing away. This is consistent with how the native
725 // select behaves. Note that we only want to do this in single selection mode.
726 if (!this.multiple && this._keyManager.activeItem) {
727 this._keyManager.activeItem._selectViaInteraction();
728 }
729 // Restore focus to the trigger before closing. Ensures that the focus
730 // position won't be lost if the user got focus into the overlay.
731 this.focus();
732 this.close();
733 }
734 });
735 this._keyManager.change.pipe(takeUntil(this._destroy)).subscribe(() => {
736 if (this._panelOpen && this.panel) {
737 this._scrollOptionIntoView(this._keyManager.activeItemIndex || 0);
738 }
739 else if (!this._panelOpen && !this.multiple && this._keyManager.activeItem) {
740 this._keyManager.activeItem._selectViaInteraction();
741 }
742 });
743 }
744 /** Drops current option subscriptions and IDs and resets from scratch. */
745 _resetOptions() {
746 const changedOrDestroyed = merge(this.options.changes, this._destroy);
747 this.optionSelectionChanges.pipe(takeUntil(changedOrDestroyed)).subscribe(event => {
748 this._onSelect(event.source, event.isUserInput);
749 if (event.isUserInput && !this.multiple && this._panelOpen) {
750 this.close();
751 this.focus();
752 }
753 });
754 // Listen to changes in the internal state of the options and react accordingly.
755 // Handles cases like the labels of the selected options changing.
756 merge(...this.options.map(option => option._stateChanges))
757 .pipe(takeUntil(changedOrDestroyed))
758 .subscribe(() => {
759 this._changeDetectorRef.markForCheck();
760 this.stateChanges.next();
761 });
762 }
763 /** Invoked when an option is clicked. */
764 _onSelect(option, isUserInput) {
765 const wasSelected = this._selectionModel.isSelected(option);
766 if (option.value == null && !this._multiple) {
767 option.deselect();
768 this._selectionModel.clear();
769 if (this.value != null) {
770 this._propagateChanges(option.value);
771 }
772 }
773 else {
774 if (wasSelected !== option.selected) {
775 option.selected
776 ? this._selectionModel.select(option)
777 : this._selectionModel.deselect(option);
778 }
779 if (isUserInput) {
780 this._keyManager.setActiveItem(option);
781 }
782 if (this.multiple) {
783 this._sortValues();
784 if (isUserInput) {
785 // In case the user selected the option with their mouse, we
786 // want to restore focus back to the trigger, in order to
787 // prevent the select keyboard controls from clashing with
788 // the ones from `mat-option`.
789 this.focus();
790 }
791 }
792 }
793 if (wasSelected !== this._selectionModel.isSelected(option)) {
794 this._propagateChanges();
795 }
796 this.stateChanges.next();
797 }
798 /** Sorts the selected values in the selected based on their order in the panel. */
799 _sortValues() {
800 if (this.multiple) {
801 const options = this.options.toArray();
802 this._selectionModel.sort((a, b) => {
803 return this.sortComparator
804 ? this.sortComparator(a, b, options)
805 : options.indexOf(a) - options.indexOf(b);
806 });
807 this.stateChanges.next();
808 }
809 }
810 /** Emits change event to set the model value. */
811 _propagateChanges(fallbackValue) {
812 let valueToEmit = null;
813 if (this.multiple) {
814 valueToEmit = this.selected.map(option => option.value);
815 }
816 else {
817 valueToEmit = this.selected ? this.selected.value : fallbackValue;
818 }
819 this._value = valueToEmit;
820 this.valueChange.emit(valueToEmit);
821 this._onChange(valueToEmit);
822 this.selectionChange.emit(this._getChangeEvent(valueToEmit));
823 this._changeDetectorRef.markForCheck();
824 }
825 /**
826 * Highlights the selected item. If no option is selected, it will highlight
827 * the first item instead.
828 */
829 _highlightCorrectOption() {
830 if (this._keyManager) {
831 if (this.empty) {
832 this._keyManager.setFirstItemActive();
833 }
834 else {
835 this._keyManager.setActiveItem(this._selectionModel.selected[0]);
836 }
837 }
838 }
839 /** Whether the panel is allowed to open. */
840 _canOpen() {
841 return !this._panelOpen && !this.disabled && this.options?.length > 0;
842 }
843 /** Focuses the select element. */
844 focus(options) {
845 this._elementRef.nativeElement.focus(options);
846 }
847 /** Gets the aria-labelledby for the select panel. */
848 _getPanelAriaLabelledby() {
849 if (this.ariaLabel) {
850 return null;
851 }
852 const labelId = this._parentFormField?.getLabelId();
853 const labelExpression = labelId ? labelId + ' ' : '';
854 return this.ariaLabelledby ? labelExpression + this.ariaLabelledby : labelId;
855 }
856 /** Determines the `aria-activedescendant` to be set on the host. */
857 _getAriaActiveDescendant() {
858 if (this.panelOpen && this._keyManager && this._keyManager.activeItem) {
859 return this._keyManager.activeItem.id;
860 }
861 return null;
862 }
863 /** Gets the aria-labelledby of the select component trigger. */
864 _getTriggerAriaLabelledby() {
865 if (this.ariaLabel) {
866 return null;
867 }
868 const labelId = this._parentFormField?.getLabelId();
869 let value = (labelId ? labelId + ' ' : '') + this._valueId;
870 if (this.ariaLabelledby) {
871 value += ' ' + this.ariaLabelledby;
872 }
873 return value;
874 }
875 /** Called when the overlay panel is done animating. */
876 _panelDoneAnimating(isOpen) {
877 this.openedChange.emit(isOpen);
878 }
879 /**
880 * Implemented as part of MatFormFieldControl.
881 * @docs-private
882 */
883 setDescribedByIds(ids) {
884 if (ids.length) {
885 this._elementRef.nativeElement.setAttribute('aria-describedby', ids.join(' '));
886 }
887 else {
888 this._elementRef.nativeElement.removeAttribute('aria-describedby');
889 }
890 }
891 /**
892 * Implemented as part of MatFormFieldControl.
893 * @docs-private
894 */
895 onContainerClick() {
896 this.focus();
897 this.open();
898 }
899 /**
900 * Implemented as part of MatFormFieldControl.
901 * @docs-private
902 */
903 get shouldLabelFloat() {
904 return this._panelOpen || !this.empty || (this._focused && !!this._placeholder);
905 }
906}
907_MatSelectBase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatSelectBase, deps: [{ token: i1.ViewportRuler }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i2.ErrorStateMatcher }, { token: i0.ElementRef }, { token: i3.Directionality, optional: true }, { token: i4.NgForm, optional: true }, { token: i4.FormGroupDirective, optional: true }, { token: MAT_FORM_FIELD, optional: true }, { token: i4.NgControl, optional: true, self: true }, { token: 'tabindex', attribute: true }, { token: MAT_SELECT_SCROLL_STRATEGY }, { token: i5.LiveAnnouncer }, { token: MAT_SELECT_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
908_MatSelectBase.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.1", type: _MatSelectBase, inputs: { userAriaDescribedBy: ["aria-describedby", "userAriaDescribedBy"], panelClass: "panelClass", placeholder: "placeholder", required: "required", multiple: "multiple", disableOptionCentering: "disableOptionCentering", compareWith: "compareWith", value: "value", ariaLabel: ["aria-label", "ariaLabel"], ariaLabelledby: ["aria-labelledby", "ariaLabelledby"], errorStateMatcher: "errorStateMatcher", typeaheadDebounceInterval: "typeaheadDebounceInterval", sortComparator: "sortComparator", id: "id" }, outputs: { openedChange: "openedChange", _openedStream: "opened", _closedStream: "closed", selectionChange: "selectionChange", valueChange: "valueChange" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true }, { propertyName: "_overlayDir", first: true, predicate: CdkConnectedOverlay, descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0 });
909i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: _MatSelectBase, decorators: [{
910 type: Directive
911 }], ctorParameters: function () { return [{ type: i1.ViewportRuler }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: i2.ErrorStateMatcher }, { type: i0.ElementRef }, { type: i3.Directionality, decorators: [{
912 type: Optional
913 }] }, { type: i4.NgForm, decorators: [{
914 type: Optional
915 }] }, { type: i4.FormGroupDirective, decorators: [{
916 type: Optional
917 }] }, { type: i6.MatFormField, decorators: [{
918 type: Optional
919 }, {
920 type: Inject,
921 args: [MAT_FORM_FIELD]
922 }] }, { type: i4.NgControl, decorators: [{
923 type: Self
924 }, {
925 type: Optional
926 }] }, { type: undefined, decorators: [{
927 type: Attribute,
928 args: ['tabindex']
929 }] }, { type: undefined, decorators: [{
930 type: Inject,
931 args: [MAT_SELECT_SCROLL_STRATEGY]
932 }] }, { type: i5.LiveAnnouncer }, { type: undefined, decorators: [{
933 type: Optional
934 }, {
935 type: Inject,
936 args: [MAT_SELECT_CONFIG]
937 }] }]; }, propDecorators: { userAriaDescribedBy: [{
938 type: Input,
939 args: ['aria-describedby']
940 }], trigger: [{
941 type: ViewChild,
942 args: ['trigger']
943 }], panel: [{
944 type: ViewChild,
945 args: ['panel']
946 }], _overlayDir: [{
947 type: ViewChild,
948 args: [CdkConnectedOverlay]
949 }], panelClass: [{
950 type: Input
951 }], placeholder: [{
952 type: Input
953 }], required: [{
954 type: Input
955 }], multiple: [{
956 type: Input
957 }], disableOptionCentering: [{
958 type: Input
959 }], compareWith: [{
960 type: Input
961 }], value: [{
962 type: Input
963 }], ariaLabel: [{
964 type: Input,
965 args: ['aria-label']
966 }], ariaLabelledby: [{
967 type: Input,
968 args: ['aria-labelledby']
969 }], errorStateMatcher: [{
970 type: Input
971 }], typeaheadDebounceInterval: [{
972 type: Input
973 }], sortComparator: [{
974 type: Input
975 }], id: [{
976 type: Input
977 }], openedChange: [{
978 type: Output
979 }], _openedStream: [{
980 type: Output,
981 args: ['opened']
982 }], _closedStream: [{
983 type: Output,
984 args: ['closed']
985 }], selectionChange: [{
986 type: Output
987 }], valueChange: [{
988 type: Output
989 }] } });
990class MatSelect extends _MatSelectBase {
991 constructor() {
992 super(...arguments);
993 /** The scroll position of the overlay panel, calculated to center the selected option. */
994 this._scrollTop = 0;
995 /** The cached font-size of the trigger element. */
996 this._triggerFontSize = 0;
997 /** The value of the select panel's transform-origin property. */
998 this._transformOrigin = 'top';
999 /**
1000 * The y-offset of the overlay panel in relation to the trigger's top start corner.
1001 * This must be adjusted to align the selected option text over the trigger text.
1002 * when the panel opens. Will change based on the y-position of the selected option.
1003 */
1004 this._offsetY = 0;
1005 this._positions = [
1006 {
1007 originX: 'start',
1008 originY: 'top',
1009 overlayX: 'start',
1010 overlayY: 'top',
1011 },
1012 {
1013 originX: 'start',
1014 originY: 'bottom',
1015 overlayX: 'start',
1016 overlayY: 'bottom',
1017 },
1018 ];
1019 }
1020 /**
1021 * Calculates the scroll position of the select's overlay panel.
1022 *
1023 * Attempts to center the selected option in the panel. If the option is
1024 * too high or too low in the panel to be scrolled to the center, it clamps the
1025 * scroll position to the min or max scroll positions respectively.
1026 */
1027 _calculateOverlayScroll(selectedIndex, scrollBuffer, maxScroll) {
1028 const itemHeight = this._getItemHeight();
1029 const optionOffsetFromScrollTop = itemHeight * selectedIndex;
1030 const halfOptionHeight = itemHeight / 2;
1031 // Starts at the optionOffsetFromScrollTop, which scrolls the option to the top of the
1032 // scroll container, then subtracts the scroll buffer to scroll the option down to
1033 // the center of the overlay panel. Half the option height must be re-added to the
1034 // scrollTop so the option is centered based on its middle, not its top edge.
1035 const optimalScrollPosition = optionOffsetFromScrollTop - scrollBuffer + halfOptionHeight;
1036 return Math.min(Math.max(0, optimalScrollPosition), maxScroll);
1037 }
1038 ngOnInit() {
1039 super.ngOnInit();
1040 this._viewportRuler
1041 .change()
1042 .pipe(takeUntil(this._destroy))
1043 .subscribe(() => {
1044 if (this.panelOpen) {
1045 this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();
1046 this._changeDetectorRef.markForCheck();
1047 }
1048 });
1049 }
1050 open() {
1051 if (super._canOpen()) {
1052 super.open();
1053 this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();
1054 // Note: The computed font-size will be a string pixel value (e.g. "16px").
1055 // `parseInt` ignores the trailing 'px' and converts this to a number.
1056 this._triggerFontSize = parseInt(getComputedStyle(this.trigger.nativeElement).fontSize || '0');
1057 this._calculateOverlayPosition();
1058 // Set the font size on the panel element once it exists.
1059 this._ngZone.onStable.pipe(take(1)).subscribe(() => {
1060 if (this._triggerFontSize &&
1061 this._overlayDir.overlayRef &&
1062 this._overlayDir.overlayRef.overlayElement) {
1063 this._overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`;
1064 }
1065 });
1066 }
1067 }
1068 /** Scrolls the active option into view. */
1069 _scrollOptionIntoView(index) {
1070 const labelCount = _countGroupLabelsBeforeOption(index, this.options, this.optionGroups);
1071 const itemHeight = this._getItemHeight();
1072 if (index === 0 && labelCount === 1) {
1073 // If we've got one group label before the option and we're at the top option,
1074 // scroll the list to the top. This is better UX than scrolling the list to the
1075 // top of the option, because it allows the user to read the top group's label.
1076 this.panel.nativeElement.scrollTop = 0;
1077 }
1078 else {
1079 this.panel.nativeElement.scrollTop = _getOptionScrollPosition((index + labelCount) * itemHeight, itemHeight, this.panel.nativeElement.scrollTop, SELECT_PANEL_MAX_HEIGHT);
1080 }
1081 }
1082 _positioningSettled() {
1083 this._calculateOverlayOffsetX();
1084 this.panel.nativeElement.scrollTop = this._scrollTop;
1085 }
1086 _panelDoneAnimating(isOpen) {
1087 if (this.panelOpen) {
1088 this._scrollTop = 0;
1089 }
1090 else {
1091 this._overlayDir.offsetX = 0;
1092 this._changeDetectorRef.markForCheck();
1093 }
1094 super._panelDoneAnimating(isOpen);
1095 }
1096 _getChangeEvent(value) {
1097 return new MatSelectChange(this, value);
1098 }
1099 /**
1100 * Sets the x-offset of the overlay panel in relation to the trigger's top start corner.
1101 * This must be adjusted to align the selected option text over the trigger text when
1102 * the panel opens. Will change based on LTR or RTL text direction. Note that the offset
1103 * can't be calculated until the panel has been attached, because we need to know the
1104 * content width in order to constrain the panel within the viewport.
1105 */
1106 _calculateOverlayOffsetX() {
1107 const overlayRect = this._overlayDir.overlayRef.overlayElement.getBoundingClientRect();
1108 const viewportSize = this._viewportRuler.getViewportSize();
1109 const isRtl = this._isRtl();
1110 const paddingWidth = this.multiple
1111 ? SELECT_MULTIPLE_PANEL_PADDING_X + SELECT_PANEL_PADDING_X
1112 : SELECT_PANEL_PADDING_X * 2;
1113 let offsetX;
1114 // Adjust the offset, depending on the option padding.
1115 if (this.multiple) {
1116 offsetX = SELECT_MULTIPLE_PANEL_PADDING_X;
1117 }
1118 else if (this.disableOptionCentering) {
1119 offsetX = SELECT_PANEL_PADDING_X;
1120 }
1121 else {
1122 let selected = this._selectionModel.selected[0] || this.options.first;
1123 offsetX = selected && selected.group ? SELECT_PANEL_INDENT_PADDING_X : SELECT_PANEL_PADDING_X;
1124 }
1125 // Invert the offset in LTR.
1126 if (!isRtl) {
1127 offsetX *= -1;
1128 }
1129 // Determine how much the select overflows on each side.
1130 const leftOverflow = 0 - (overlayRect.left + offsetX - (isRtl ? paddingWidth : 0));
1131 const rightOverflow = overlayRect.right + offsetX - viewportSize.width + (isRtl ? 0 : paddingWidth);
1132 // If the element overflows on either side, reduce the offset to allow it to fit.
1133 if (leftOverflow > 0) {
1134 offsetX += leftOverflow + SELECT_PANEL_VIEWPORT_PADDING;
1135 }
1136 else if (rightOverflow > 0) {
1137 offsetX -= rightOverflow + SELECT_PANEL_VIEWPORT_PADDING;
1138 }
1139 // Set the offset directly in order to avoid having to go through change detection and
1140 // potentially triggering "changed after it was checked" errors. Round the value to avoid
1141 // blurry content in some browsers.
1142 this._overlayDir.offsetX = Math.round(offsetX);
1143 this._overlayDir.overlayRef.updatePosition();
1144 }
1145 /**
1146 * Calculates the y-offset of the select's overlay panel in relation to the
1147 * top start corner of the trigger. It has to be adjusted in order for the
1148 * selected option to be aligned over the trigger when the panel opens.
1149 */
1150 _calculateOverlayOffsetY(selectedIndex, scrollBuffer, maxScroll) {
1151 const itemHeight = this._getItemHeight();
1152 const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
1153 const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);
1154 let optionOffsetFromPanelTop;
1155 // Disable offset if requested by user by returning 0 as value to offset
1156 if (this.disableOptionCentering) {
1157 return 0;
1158 }
1159 if (this._scrollTop === 0) {
1160 optionOffsetFromPanelTop = selectedIndex * itemHeight;
1161 }
1162 else if (this._scrollTop === maxScroll) {
1163 const firstDisplayedIndex = this._getItemCount() - maxOptionsDisplayed;
1164 const selectedDisplayIndex = selectedIndex - firstDisplayedIndex;
1165 // The first item is partially out of the viewport. Therefore we need to calculate what
1166 // portion of it is shown in the viewport and account for it in our offset.
1167 let partialItemHeight = itemHeight - ((this._getItemCount() * itemHeight - SELECT_PANEL_MAX_HEIGHT) % itemHeight);
1168 // Because the panel height is longer than the height of the options alone,
1169 // there is always extra padding at the top or bottom of the panel. When
1170 // scrolled to the very bottom, this padding is at the top of the panel and
1171 // must be added to the offset.
1172 optionOffsetFromPanelTop = selectedDisplayIndex * itemHeight + partialItemHeight;
1173 }
1174 else {
1175 // If the option was scrolled to the middle of the panel using a scroll buffer,
1176 // its offset will be the scroll buffer minus the half height that was added to
1177 // center it.
1178 optionOffsetFromPanelTop = scrollBuffer - itemHeight / 2;
1179 }
1180 // The final offset is the option's offset from the top, adjusted for the height difference,
1181 // multiplied by -1 to ensure that the overlay moves in the correct direction up the page.
1182 // The value is rounded to prevent some browsers from blurring the content.
1183 return Math.round(optionOffsetFromPanelTop * -1 - optionHeightAdjustment);
1184 }
1185 /**
1186 * Checks that the attempted overlay position will fit within the viewport.
1187 * If it will not fit, tries to adjust the scroll position and the associated
1188 * y-offset so the panel can open fully on-screen. If it still won't fit,
1189 * sets the offset back to 0 to allow the fallback position to take over.
1190 */
1191 _checkOverlayWithinViewport(maxScroll) {
1192 const itemHeight = this._getItemHeight();
1193 const viewportSize = this._viewportRuler.getViewportSize();
1194 const topSpaceAvailable = this._triggerRect.top - SELECT_PANEL_VIEWPORT_PADDING;
1195 const bottomSpaceAvailable = viewportSize.height - this._triggerRect.bottom - SELECT_PANEL_VIEWPORT_PADDING;
1196 const panelHeightTop = Math.abs(this._offsetY);
1197 const totalPanelHeight = Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);
1198 const panelHeightBottom = totalPanelHeight - panelHeightTop - this._triggerRect.height;
1199 if (panelHeightBottom > bottomSpaceAvailable) {
1200 this._adjustPanelUp(panelHeightBottom, bottomSpaceAvailable);
1201 }
1202 else if (panelHeightTop > topSpaceAvailable) {
1203 this._adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll);
1204 }
1205 else {
1206 this._transformOrigin = this._getOriginBasedOnOption();
1207 }
1208 }
1209 /** Adjusts the overlay panel up to fit in the viewport. */
1210 _adjustPanelUp(panelHeightBottom, bottomSpaceAvailable) {
1211 // Browsers ignore fractional scroll offsets, so we need to round.
1212 const distanceBelowViewport = Math.round(panelHeightBottom - bottomSpaceAvailable);
1213 // Scrolls the panel up by the distance it was extending past the boundary, then
1214 // adjusts the offset by that amount to move the panel up into the viewport.
1215 this._scrollTop -= distanceBelowViewport;
1216 this._offsetY -= distanceBelowViewport;
1217 this._transformOrigin = this._getOriginBasedOnOption();
1218 // If the panel is scrolled to the very top, it won't be able to fit the panel
1219 // by scrolling, so set the offset to 0 to allow the fallback position to take
1220 // effect.
1221 if (this._scrollTop <= 0) {
1222 this._scrollTop = 0;
1223 this._offsetY = 0;
1224 this._transformOrigin = `50% bottom 0px`;
1225 }
1226 }
1227 /** Adjusts the overlay panel down to fit in the viewport. */
1228 _adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll) {
1229 // Browsers ignore fractional scroll offsets, so we need to round.
1230 const distanceAboveViewport = Math.round(panelHeightTop - topSpaceAvailable);
1231 // Scrolls the panel down by the distance it was extending past the boundary, then
1232 // adjusts the offset by that amount to move the panel down into the viewport.
1233 this._scrollTop += distanceAboveViewport;
1234 this._offsetY += distanceAboveViewport;
1235 this._transformOrigin = this._getOriginBasedOnOption();
1236 // If the panel is scrolled to the very bottom, it won't be able to fit the
1237 // panel by scrolling, so set the offset to 0 to allow the fallback position
1238 // to take effect.
1239 if (this._scrollTop >= maxScroll) {
1240 this._scrollTop = maxScroll;
1241 this._offsetY = 0;
1242 this._transformOrigin = `50% top 0px`;
1243 return;
1244 }
1245 }
1246 /** Calculates the scroll position and x- and y-offsets of the overlay panel. */
1247 _calculateOverlayPosition() {
1248 const itemHeight = this._getItemHeight();
1249 const items = this._getItemCount();
1250 const panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);
1251 const scrollContainerHeight = items * itemHeight;
1252 // The farthest the panel can be scrolled before it hits the bottom
1253 const maxScroll = scrollContainerHeight - panelHeight;
1254 // If no value is selected we open the popup to the first item.
1255 let selectedOptionOffset;
1256 if (this.empty) {
1257 selectedOptionOffset = 0;
1258 }
1259 else {
1260 selectedOptionOffset = Math.max(this.options.toArray().indexOf(this._selectionModel.selected[0]), 0);
1261 }
1262 selectedOptionOffset += _countGroupLabelsBeforeOption(selectedOptionOffset, this.options, this.optionGroups);
1263 // We must maintain a scroll buffer so the selected option will be scrolled to the
1264 // center of the overlay panel rather than the top.
1265 const scrollBuffer = panelHeight / 2;
1266 this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
1267 this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
1268 this._checkOverlayWithinViewport(maxScroll);
1269 }
1270 /** Sets the transform origin point based on the selected option. */
1271 _getOriginBasedOnOption() {
1272 const itemHeight = this._getItemHeight();
1273 const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
1274 const originY = Math.abs(this._offsetY) - optionHeightAdjustment + itemHeight / 2;
1275 return `50% ${originY}px 0px`;
1276 }
1277 /** Calculates the height of the select's options. */
1278 _getItemHeight() {
1279 return this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
1280 }
1281 /** Calculates the amount of items in the select. This includes options and group labels. */
1282 _getItemCount() {
1283 return this.options.length + this.optionGroups.length;
1284 }
1285}
1286MatSelect.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelect, deps: null, target: i0.ɵɵFactoryTarget.Component });
1287MatSelect.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.1", type: MatSelect, selector: "mat-select", inputs: { disabled: "disabled", disableRipple: "disableRipple", tabIndex: "tabIndex" }, host: { attributes: { "role": "combobox", "aria-autocomplete": "none", "aria-haspopup": "true" }, listeners: { "keydown": "_handleKeydown($event)", "focus": "_onFocus()", "blur": "_onBlur()" }, properties: { "attr.id": "id", "attr.tabindex": "tabIndex", "attr.aria-controls": "panelOpen ? id + \"-panel\" : null", "attr.aria-expanded": "panelOpen", "attr.aria-label": "ariaLabel || null", "attr.aria-required": "required.toString()", "attr.aria-disabled": "disabled.toString()", "attr.aria-invalid": "errorState", "attr.aria-activedescendant": "_getAriaActiveDescendant()", "class.mat-select-disabled": "disabled", "class.mat-select-invalid": "errorState", "class.mat-select-required": "required", "class.mat-select-empty": "empty", "class.mat-select-multiple": "multiple" }, classAttribute: "mat-select" }, providers: [
1288 { provide: MatFormFieldControl, useExisting: MatSelect },
1289 { provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatSelect },
1290 ], queries: [{ propertyName: "customTrigger", first: true, predicate: MAT_SELECT_TRIGGER, descendants: true }, { propertyName: "options", predicate: MatOption, descendants: true }, { propertyName: "optionGroups", predicate: MAT_OPTGROUP, descendants: true }], exportAs: ["matSelect"], usesInheritance: true, ngImport: i0, template: "<!--\n Note that the select trigger element specifies `aria-owns` pointing to the listbox overlay.\n While aria-owns is not required for the ARIA 1.2 `role=\"combobox\"` interaction pattern,\n it fixes an issue with VoiceOver when the select appears inside of an `aria-model=\"true\"`\n element (e.g. a dialog). Without this `aria-owns`, the `aria-modal` on a dialog prevents\n VoiceOver from \"seeing\" the select's listbox overlay for aria-activedescendant.\n Using `aria-owns` re-parents the select overlay so that it works again.\n See https://github.com/angular/components/issues/20694\n-->\n<div cdk-overlay-origin\n [attr.aria-owns]=\"panelOpen ? id + '-panel' : null\"\n class=\"mat-select-trigger\"\n (click)=\"toggle()\"\n #origin=\"cdkOverlayOrigin\"\n #trigger>\n <div class=\"mat-select-value\" [ngSwitch]=\"empty\" [attr.id]=\"_valueId\">\n <span class=\"mat-select-placeholder mat-select-min-line\" *ngSwitchCase=\"true\">{{placeholder}}</span>\n <span class=\"mat-select-value-text\" *ngSwitchCase=\"false\" [ngSwitch]=\"!!customTrigger\">\n <span class=\"mat-select-min-line\" *ngSwitchDefault>{{triggerValue}}</span>\n <ng-content select=\"mat-select-trigger\" *ngSwitchCase=\"true\"></ng-content>\n </span>\n </div>\n\n <div class=\"mat-select-arrow-wrapper\"><div class=\"mat-select-arrow\"></div></div>\n</div>\n\n<ng-template\n cdk-connected-overlay\n cdkConnectedOverlayLockPosition\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPanelClass]=\"_overlayPanelClass\"\n [cdkConnectedOverlayScrollStrategy]=\"_scrollStrategy\"\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"panelOpen\"\n [cdkConnectedOverlayPositions]=\"_positions\"\n [cdkConnectedOverlayMinWidth]=\"_triggerRect?.width!\"\n [cdkConnectedOverlayOffsetY]=\"_offsetY\"\n (backdropClick)=\"close()\"\n (attach)=\"_onAttached()\"\n (detach)=\"close()\">\n <div class=\"mat-select-panel-wrap\" [@transformPanelWrap]>\n <div\n #panel\n role=\"listbox\"\n tabindex=\"-1\"\n class=\"mat-select-panel {{ _getPanelTheme() }}\"\n [attr.id]=\"id + '-panel'\"\n [attr.aria-multiselectable]=\"multiple\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"_getPanelAriaLabelledby()\"\n [ngClass]=\"panelClass\"\n [@transformPanel]=\"multiple ? 'showing-multiple' : 'showing'\"\n (@transformPanel.done)=\"_panelDoneAnimatingStream.next($event.toState)\"\n [style.transformOrigin]=\"_transformOrigin\"\n [style.font-size.px]=\"_triggerFontSize\"\n (keydown)=\"_handleKeydown($event)\">\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-flex;align-items:center;cursor:pointer;position:relative;box-sizing:border-box;width:100%}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;user-select:none;cursor:default}.mat-select-value{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-select-value-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mat-select-arrow-wrapper{height:16px;flex-shrink:0;display:inline-flex;align-items:center}.mat-form-field-appearance-fill .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-outline .mat-select-arrow-wrapper{transform:translateY(-25%)}.mat-form-field-appearance-standard.mat-form-field-has-label .mat-select:not(.mat-select-empty) .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:none}.mat-select-arrow{width:0;height:0;border-left:5px solid rgba(0,0,0,0);border-right:5px solid rgba(0,0,0,0);border-top:5px solid;margin:0 4px}.mat-form-field.mat-focused .mat-select-arrow{transform:translateX(0)}.mat-select-panel-wrap{flex-basis:100%}.mat-select-panel{min-width:112px;max-width:280px;overflow:auto;-webkit-overflow-scrolling:touch;padding-top:0;padding-bottom:0;max-height:256px;min-width:100%;border-radius:4px;outline:0}.cdk-high-contrast-active .mat-select-panel{outline:solid 1px}.mat-select-panel .mat-optgroup-label,.mat-select-panel .mat-option{font-size:inherit;line-height:3em;height:3em}.mat-form-field-type-mat-select:not(.mat-form-field-disabled) .mat-form-field-flex{cursor:pointer}.mat-form-field-type-mat-select .mat-form-field-label{width:calc(100% - 18px)}.mat-select-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable .mat-select-placeholder{transition:none}.mat-form-field-hide-placeholder .mat-select-placeholder{color:rgba(0,0,0,0);-webkit-text-fill-color:rgba(0,0,0,0);transition:none;display:block}.mat-select-min-line:empty::before{content:\" \";white-space:pre;width:1px;display:inline-block;visibility:hidden}"], dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i7.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i7.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "directive", type: i8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], animations: [matSelectAnimations.transformPanelWrap, matSelectAnimations.transformPanel], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1291i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelect, decorators: [{
1292 type: Component,
1293 args: [{ selector: 'mat-select', exportAs: 'matSelect', inputs: ['disabled', 'disableRipple', 'tabIndex'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1294 'role': 'combobox',
1295 'aria-autocomplete': 'none',
1296 // TODO(crisbeto): the value for aria-haspopup should be `listbox`, but currently it's difficult
1297 // to sync into Google, because of an outdated automated a11y check which flags it as an invalid
1298 // value. At some point we should try to switch it back to being `listbox`.
1299 'aria-haspopup': 'true',
1300 'class': 'mat-select',
1301 '[attr.id]': 'id',
1302 '[attr.tabindex]': 'tabIndex',
1303 '[attr.aria-controls]': 'panelOpen ? id + "-panel" : null',
1304 '[attr.aria-expanded]': 'panelOpen',
1305 '[attr.aria-label]': 'ariaLabel || null',
1306 '[attr.aria-required]': 'required.toString()',
1307 '[attr.aria-disabled]': 'disabled.toString()',
1308 '[attr.aria-invalid]': 'errorState',
1309 '[attr.aria-activedescendant]': '_getAriaActiveDescendant()',
1310 '[class.mat-select-disabled]': 'disabled',
1311 '[class.mat-select-invalid]': 'errorState',
1312 '[class.mat-select-required]': 'required',
1313 '[class.mat-select-empty]': 'empty',
1314 '[class.mat-select-multiple]': 'multiple',
1315 '(keydown)': '_handleKeydown($event)',
1316 '(focus)': '_onFocus()',
1317 '(blur)': '_onBlur()',
1318 }, animations: [matSelectAnimations.transformPanelWrap, matSelectAnimations.transformPanel], providers: [
1319 { provide: MatFormFieldControl, useExisting: MatSelect },
1320 { provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatSelect },
1321 ], template: "<!--\n Note that the select trigger element specifies `aria-owns` pointing to the listbox overlay.\n While aria-owns is not required for the ARIA 1.2 `role=\"combobox\"` interaction pattern,\n it fixes an issue with VoiceOver when the select appears inside of an `aria-model=\"true\"`\n element (e.g. a dialog). Without this `aria-owns`, the `aria-modal` on a dialog prevents\n VoiceOver from \"seeing\" the select's listbox overlay for aria-activedescendant.\n Using `aria-owns` re-parents the select overlay so that it works again.\n See https://github.com/angular/components/issues/20694\n-->\n<div cdk-overlay-origin\n [attr.aria-owns]=\"panelOpen ? id + '-panel' : null\"\n class=\"mat-select-trigger\"\n (click)=\"toggle()\"\n #origin=\"cdkOverlayOrigin\"\n #trigger>\n <div class=\"mat-select-value\" [ngSwitch]=\"empty\" [attr.id]=\"_valueId\">\n <span class=\"mat-select-placeholder mat-select-min-line\" *ngSwitchCase=\"true\">{{placeholder}}</span>\n <span class=\"mat-select-value-text\" *ngSwitchCase=\"false\" [ngSwitch]=\"!!customTrigger\">\n <span class=\"mat-select-min-line\" *ngSwitchDefault>{{triggerValue}}</span>\n <ng-content select=\"mat-select-trigger\" *ngSwitchCase=\"true\"></ng-content>\n </span>\n </div>\n\n <div class=\"mat-select-arrow-wrapper\"><div class=\"mat-select-arrow\"></div></div>\n</div>\n\n<ng-template\n cdk-connected-overlay\n cdkConnectedOverlayLockPosition\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPanelClass]=\"_overlayPanelClass\"\n [cdkConnectedOverlayScrollStrategy]=\"_scrollStrategy\"\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"panelOpen\"\n [cdkConnectedOverlayPositions]=\"_positions\"\n [cdkConnectedOverlayMinWidth]=\"_triggerRect?.width!\"\n [cdkConnectedOverlayOffsetY]=\"_offsetY\"\n (backdropClick)=\"close()\"\n (attach)=\"_onAttached()\"\n (detach)=\"close()\">\n <div class=\"mat-select-panel-wrap\" [@transformPanelWrap]>\n <div\n #panel\n role=\"listbox\"\n tabindex=\"-1\"\n class=\"mat-select-panel {{ _getPanelTheme() }}\"\n [attr.id]=\"id + '-panel'\"\n [attr.aria-multiselectable]=\"multiple\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"_getPanelAriaLabelledby()\"\n [ngClass]=\"panelClass\"\n [@transformPanel]=\"multiple ? 'showing-multiple' : 'showing'\"\n (@transformPanel.done)=\"_panelDoneAnimatingStream.next($event.toState)\"\n [style.transformOrigin]=\"_transformOrigin\"\n [style.font-size.px]=\"_triggerFontSize\"\n (keydown)=\"_handleKeydown($event)\">\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-flex;align-items:center;cursor:pointer;position:relative;box-sizing:border-box;width:100%}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;user-select:none;cursor:default}.mat-select-value{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-select-value-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mat-select-arrow-wrapper{height:16px;flex-shrink:0;display:inline-flex;align-items:center}.mat-form-field-appearance-fill .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-outline .mat-select-arrow-wrapper{transform:translateY(-25%)}.mat-form-field-appearance-standard.mat-form-field-has-label .mat-select:not(.mat-select-empty) .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:none}.mat-select-arrow{width:0;height:0;border-left:5px solid rgba(0,0,0,0);border-right:5px solid rgba(0,0,0,0);border-top:5px solid;margin:0 4px}.mat-form-field.mat-focused .mat-select-arrow{transform:translateX(0)}.mat-select-panel-wrap{flex-basis:100%}.mat-select-panel{min-width:112px;max-width:280px;overflow:auto;-webkit-overflow-scrolling:touch;padding-top:0;padding-bottom:0;max-height:256px;min-width:100%;border-radius:4px;outline:0}.cdk-high-contrast-active .mat-select-panel{outline:solid 1px}.mat-select-panel .mat-optgroup-label,.mat-select-panel .mat-option{font-size:inherit;line-height:3em;height:3em}.mat-form-field-type-mat-select:not(.mat-form-field-disabled) .mat-form-field-flex{cursor:pointer}.mat-form-field-type-mat-select .mat-form-field-label{width:calc(100% - 18px)}.mat-select-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable .mat-select-placeholder{transition:none}.mat-form-field-hide-placeholder .mat-select-placeholder{color:rgba(0,0,0,0);-webkit-text-fill-color:rgba(0,0,0,0);transition:none;display:block}.mat-select-min-line:empty::before{content:\" \";white-space:pre;width:1px;display:inline-block;visibility:hidden}"] }]
1322 }], propDecorators: { options: [{
1323 type: ContentChildren,
1324 args: [MatOption, { descendants: true }]
1325 }], optionGroups: [{
1326 type: ContentChildren,
1327 args: [MAT_OPTGROUP, { descendants: true }]
1328 }], customTrigger: [{
1329 type: ContentChild,
1330 args: [MAT_SELECT_TRIGGER]
1331 }] } });
1332
1333/**
1334 * @license
1335 * Copyright Google LLC All Rights Reserved.
1336 *
1337 * Use of this source code is governed by an MIT-style license that can be
1338 * found in the LICENSE file at https://angular.io/license
1339 */
1340class MatSelectModule {
1341}
1342MatSelectModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1343MatSelectModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.1", ngImport: i0, type: MatSelectModule, declarations: [MatSelect, MatSelectTrigger], imports: [CommonModule, OverlayModule, MatOptionModule, MatCommonModule], exports: [CdkScrollableModule,
1344 MatFormFieldModule,
1345 MatSelect,
1346 MatSelectTrigger,
1347 MatOptionModule,
1348 MatCommonModule] });
1349MatSelectModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelectModule, providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER], imports: [CommonModule, OverlayModule, MatOptionModule, MatCommonModule, CdkScrollableModule,
1350 MatFormFieldModule,
1351 MatOptionModule,
1352 MatCommonModule] });
1353i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.1", ngImport: i0, type: MatSelectModule, decorators: [{
1354 type: NgModule,
1355 args: [{
1356 imports: [CommonModule, OverlayModule, MatOptionModule, MatCommonModule],
1357 exports: [
1358 CdkScrollableModule,
1359 MatFormFieldModule,
1360 MatSelect,
1361 MatSelectTrigger,
1362 MatOptionModule,
1363 MatCommonModule,
1364 ],
1365 declarations: [MatSelect, MatSelectTrigger],
1366 providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER],
1367 }]
1368 }] });
1369
1370/**
1371 * @license
1372 * Copyright Google LLC All Rights Reserved.
1373 *
1374 * Use of this source code is governed by an MIT-style license that can be
1375 * found in the LICENSE file at https://angular.io/license
1376 */
1377
1378/**
1379 * @license
1380 * Copyright Google LLC All Rights Reserved.
1381 *
1382 * Use of this source code is governed by an MIT-style license that can be
1383 * found in the LICENSE file at https://angular.io/license
1384 */
1385
1386/**
1387 * Generated bundle index. Do not edit.
1388 */
1389
1390export { MAT_SELECT_CONFIG, MAT_SELECT_SCROLL_STRATEGY, MAT_SELECT_SCROLL_STRATEGY_PROVIDER, MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY, MAT_SELECT_TRIGGER, MatSelect, MatSelectChange, MatSelectModule, MatSelectTrigger, _MatSelectBase, matSelectAnimations };
1391//# sourceMappingURL=select.mjs.map