You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1500 lines
79 KiB

import { Overlay, CdkConnectedOverlay, OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { InjectionToken, Directive, EventEmitter, ChangeDetectorRef, NgZone, ElementRef, Optional, Inject, Self, Attribute, ViewChild, Input, Output, Component, ViewEncapsulation, ChangeDetectionStrategy, ContentChildren, ContentChild, NgModule } from '@angular/core';
import { mixinDisableRipple, mixinTabIndex, mixinDisabled, mixinErrorState, ErrorStateMatcher, _countGroupLabelsBeforeOption, _getOptionScrollPosition, MAT_OPTION_PARENT_COMPONENT, MatOption, MAT_OPTGROUP, MatOptionModule, MatCommonModule } from '@angular/material/core';
import { MatFormField, MAT_FORM_FIELD, MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field';
import { ViewportRuler, CdkScrollableModule } from '@angular/cdk/scrolling';
import { ActiveDescendantKeyManager, LiveAnnouncer } from '@angular/cdk/a11y';
import { Directionality } from '@angular/cdk/bidi';
import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
import { SelectionModel } from '@angular/cdk/collections';
import { DOWN_ARROW, UP_ARROW, LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE, hasModifierKey, A } from '@angular/cdk/keycodes';
import { NgForm, FormGroupDirective, NgControl } from '@angular/forms';
import { Subject, defer, merge } from 'rxjs';
import { startWith, switchMap, take, filter, map, distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { trigger, transition, query, animateChild, state, style, animate } from '@angular/animations';
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* The following are all the animations for the mat-select component, with each
* const containing the metadata for one animation.
*
* The values below match the implementation of the AngularJS Material mat-select animation.
* @docs-private
*/
import * as ɵngcc0 from '@angular/core';
import * as ɵngcc1 from '@angular/cdk/scrolling';
import * as ɵngcc2 from '@angular/material/core';
import * as ɵngcc3 from '@angular/cdk/bidi';
import * as ɵngcc4 from '@angular/forms';
import * as ɵngcc5 from '@angular/cdk/a11y';
import * as ɵngcc6 from '@angular/material/form-field';
import * as ɵngcc7 from '@angular/cdk/overlay';
import * as ɵngcc8 from '@angular/common';
const _c0 = ["trigger"];
const _c1 = ["panel"];
function MatSelect_span_4_Template(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵelementStart(0, "span", 8);
ɵngcc0.ɵɵtext(1);
ɵngcc0.ɵɵelementEnd();
} if (rf & 2) {
const ctx_r2 = ɵngcc0.ɵɵnextContext();
ɵngcc0.ɵɵadvance(1);
ɵngcc0.ɵɵtextInterpolate(ctx_r2.placeholder);
} }
function MatSelect_span_5_span_1_Template(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵelementStart(0, "span", 12);
ɵngcc0.ɵɵtext(1);
ɵngcc0.ɵɵelementEnd();
} if (rf & 2) {
const ctx_r5 = ɵngcc0.ɵɵnextContext(2);
ɵngcc0.ɵɵadvance(1);
ɵngcc0.ɵɵtextInterpolate(ctx_r5.triggerValue);
} }
function MatSelect_span_5_ng_content_2_Template(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵprojection(0, 0, ["*ngSwitchCase", "true"]);
} }
function MatSelect_span_5_Template(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵelementStart(0, "span", 9);
ɵngcc0.ɵɵtemplate(1, MatSelect_span_5_span_1_Template, 2, 1, "span", 10);
ɵngcc0.ɵɵtemplate(2, MatSelect_span_5_ng_content_2_Template, 1, 0, "ng-content", 11);
ɵngcc0.ɵɵelementEnd();
} if (rf & 2) {
const ctx_r3 = ɵngcc0.ɵɵnextContext();
ɵngcc0.ɵɵproperty("ngSwitch", !!ctx_r3.customTrigger);
ɵngcc0.ɵɵadvance(2);
ɵngcc0.ɵɵproperty("ngSwitchCase", true);
} }
function MatSelect_ng_template_8_Template(rf, ctx) { if (rf & 1) {
const _r9 = ɵngcc0.ɵɵgetCurrentView();
ɵngcc0.ɵɵelementStart(0, "div", 13);
ɵngcc0.ɵɵelementStart(1, "div", 14, 15);
ɵngcc0.ɵɵlistener("@transformPanel.done", function MatSelect_ng_template_8_Template_div_animation_transformPanel_done_1_listener($event) { ɵngcc0.ɵɵrestoreView(_r9); const ctx_r8 = ɵngcc0.ɵɵnextContext(); return ctx_r8._panelDoneAnimatingStream.next($event.toState); })("keydown", function MatSelect_ng_template_8_Template_div_keydown_1_listener($event) { ɵngcc0.ɵɵrestoreView(_r9); const ctx_r10 = ɵngcc0.ɵɵnextContext(); return ctx_r10._handleKeydown($event); });
ɵngcc0.ɵɵprojection(3, 1);
ɵngcc0.ɵɵelementEnd();
ɵngcc0.ɵɵelementEnd();
} if (rf & 2) {
const ctx_r4 = ɵngcc0.ɵɵnextContext();
ɵngcc0.ɵɵproperty("@transformPanelWrap", undefined);
ɵngcc0.ɵɵadvance(1);
ɵngcc0.ɵɵclassMapInterpolate1("mat-select-panel ", ctx_r4._getPanelTheme(), "");
ɵngcc0.ɵɵstyleProp("transform-origin", ctx_r4._transformOrigin)("font-size", ctx_r4._triggerFontSize, "px");
ɵngcc0.ɵɵproperty("ngClass", ctx_r4.panelClass)("@transformPanel", ctx_r4.multiple ? "showing-multiple" : "showing");
ɵngcc0.ɵɵattribute("id", ctx_r4.id + "-panel")("aria-multiselectable", ctx_r4.multiple)("aria-label", ctx_r4.ariaLabel || null)("aria-labelledby", ctx_r4._getPanelAriaLabelledby());
} }
const _c2 = [[["mat-select-trigger"]], "*"];
const _c3 = ["mat-select-trigger", "*"];
const matSelectAnimations = {
/**
* This animation ensures the select's overlay panel animation (transformPanel) is called when
* closing the select.
* This is needed due to https://github.com/angular/angular/issues/23302
*/
transformPanelWrap: trigger('transformPanelWrap', [
transition('* => void', query('@transformPanel', [animateChild()], { optional: true }))
]),
/**
* This animation transforms the select's overlay panel on and off the page.
*
* When the panel is attached to the DOM, it expands its width by the amount of padding, scales it
* up to 100% on the Y axis, fades in its border, and translates slightly up and to the
* side to ensure the option text correctly overlaps the trigger text.
*
* When the panel is removed from the DOM, it simply fades out linearly.
*/
transformPanel: trigger('transformPanel', [
state('void', style({
transform: 'scaleY(0.8)',
minWidth: '100%',
opacity: 0
})),
state('showing', style({
opacity: 1,
minWidth: 'calc(100% + 32px)',
transform: 'scaleY(1)'
})),
state('showing-multiple', style({
opacity: 1,
minWidth: 'calc(100% + 64px)',
transform: 'scaleY(1)'
})),
transition('void => *', animate('120ms cubic-bezier(0, 0, 0.2, 1)')),
transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 })))
])
};
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Returns an exception to be thrown when attempting to change a select's `multiple` option
* after initialization.
* @docs-private
*/
function getMatSelectDynamicMultipleError() {
return Error('Cannot change `multiple` mode of select after initialization.');
}
/**
* Returns an exception to be thrown when attempting to assign a non-array value to a select
* in `multiple` mode. Note that `undefined` and `null` are still valid values to allow for
* resetting the value.
* @docs-private
*/
function getMatSelectNonArrayValueError() {
return Error('Value must be an array in multiple-selection mode.');
}
/**
* Returns an exception to be thrown when assigning a non-function value to the comparator
* used to determine if a value corresponds to an option. Note that whether the function
* actually takes two values and returns a boolean is not checked.
*/
function getMatSelectNonFunctionValueError() {
return Error('`compareWith` must be a function.');
}
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
let nextUniqueId = 0;
/**
* The following style constants are necessary to save here in order
* to properly calculate the alignment of the selected option over
* the trigger element.
*/
/** The max height of the select's overlay panel. */
const SELECT_PANEL_MAX_HEIGHT = 256;
/** The panel's padding on the x-axis. */
const SELECT_PANEL_PADDING_X = 16;
/** The panel's x axis padding if it is indented (e.g. there is an option group). */
const SELECT_PANEL_INDENT_PADDING_X = SELECT_PANEL_PADDING_X * 2;
/** The height of the select items in `em` units. */
const SELECT_ITEM_HEIGHT_EM = 3;
// TODO(josephperrott): Revert to a constant after 2018 spec updates are fully merged.
/**
* Distance between the panel edge and the option text in
* multi-selection mode.
*
* Calculated as:
* (SELECT_PANEL_PADDING_X * 1.5) + 16 = 40
* The padding is multiplied by 1.5 because the checkbox's margin is half the padding.
* The checkbox width is 16px.
*/
const SELECT_MULTIPLE_PANEL_PADDING_X = SELECT_PANEL_PADDING_X * 1.5 + 16;
/**
* The select panel will only "fit" inside the viewport if it is positioned at
* this value or more away from the viewport boundary.
*/
const SELECT_PANEL_VIEWPORT_PADDING = 8;
/** Injection token that determines the scroll handling while a select is open. */
const MAT_SELECT_SCROLL_STRATEGY = new InjectionToken('mat-select-scroll-strategy');
/** @docs-private */
function MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) {
return () => overlay.scrollStrategies.reposition();
}
/** Injection token that can be used to provide the default options the select module. */
const MAT_SELECT_CONFIG = new InjectionToken('MAT_SELECT_CONFIG');
/** @docs-private */
const MAT_SELECT_SCROLL_STRATEGY_PROVIDER = {
provide: MAT_SELECT_SCROLL_STRATEGY,
deps: [Overlay],
useFactory: MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY,
};
/** Change event object that is emitted when the select value has changed. */
class MatSelectChange {
constructor(
/** Reference to the select that emitted the change event. */
source,
/** Current value of the select that emitted the event. */
value) {
this.source = source;
this.value = value;
}
}
// Boilerplate for applying mixins to MatSelect.
/** @docs-private */
const _MatSelectMixinBase = mixinDisableRipple(mixinTabIndex(mixinDisabled(mixinErrorState(class {
constructor(_elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl) {
this._elementRef = _elementRef;
this._defaultErrorStateMatcher = _defaultErrorStateMatcher;
this._parentForm = _parentForm;
this._parentFormGroup = _parentFormGroup;
this.ngControl = ngControl;
}
}))));
/**
* Injection token that can be used to reference instances of `MatSelectTrigger`. It serves as
* alternative token to the actual `MatSelectTrigger` class which could cause unnecessary
* retention of the class and its directive metadata.
*/
const MAT_SELECT_TRIGGER = new InjectionToken('MatSelectTrigger');
/**
* Allows the user to customize the trigger that is displayed when the select has a value.
*/
class MatSelectTrigger {
}
MatSelectTrigger.ɵfac = function MatSelectTrigger_Factory(t) { return new (t || MatSelectTrigger)(); };
MatSelectTrigger.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: MatSelectTrigger, selectors: [["mat-select-trigger"]], features: [ɵngcc0.ɵɵProvidersFeature([{ provide: MAT_SELECT_TRIGGER, useExisting: MatSelectTrigger }])] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(MatSelectTrigger, [{
type: Directive,
args: [{
selector: 'mat-select-trigger',
providers: [{ provide: MAT_SELECT_TRIGGER, useExisting: MatSelectTrigger }]
}]
}], null, null); })();
/** Base class with all of the `MatSelect` functionality. */
class _MatSelectBase extends _MatSelectMixinBase {
constructor(_viewportRuler, _changeDetectorRef, _ngZone, _defaultErrorStateMatcher, elementRef, _dir, _parentForm, _parentFormGroup, _parentFormField, ngControl, tabIndex, scrollStrategyFactory, _liveAnnouncer, _defaultOptions) {
var _a, _b, _c;
super(elementRef, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
this._viewportRuler = _viewportRuler;
this._changeDetectorRef = _changeDetectorRef;
this._ngZone = _ngZone;
this._dir = _dir;
this._parentFormField = _parentFormField;
this._liveAnnouncer = _liveAnnouncer;
this._defaultOptions = _defaultOptions;
/** Whether or not the overlay panel is open. */
this._panelOpen = false;
/** Comparison function to specify which option is displayed. Defaults to object equality. */
this._compareWith = (o1, o2) => o1 === o2;
/** Unique id for this input. */
this._uid = `mat-select-${nextUniqueId++}`;
/** Current `ariar-labelledby` value for the select trigger. */
this._triggerAriaLabelledBy = null;
/** Emits whenever the component is destroyed. */
this._destroy = new Subject();
/** `View -> model callback called when value changes` */
this._onChange = () => { };
/** `View -> model callback called when select has been touched` */
this._onTouched = () => { };
/** ID for the DOM node containing the select's value. */
this._valueId = `mat-select-value-${nextUniqueId++}`;
/** Emits when the panel element is finished transforming in. */
this._panelDoneAnimatingStream = new Subject();
this._overlayPanelClass = ((_a = this._defaultOptions) === null || _a === void 0 ? void 0 : _a.overlayPanelClass) || '';
this._focused = false;
/** A name for this control that can be used by `mat-form-field`. */
this.controlType = 'mat-select';
this._required = false;
this._multiple = false;
this._disableOptionCentering = (_c = (_b = this._defaultOptions) === null || _b === void 0 ? void 0 : _b.disableOptionCentering) !== null && _c !== void 0 ? _c : false;
/** Aria label of the select. */
this.ariaLabel = '';
/** Combined stream of all of the child options' change events. */
this.optionSelectionChanges = defer(() => {
const options = this.options;
if (options) {
return options.changes.pipe(startWith(options), switchMap(() => merge(...options.map(option => option.onSelectionChange))));
}
return this._ngZone.onStable
.pipe(take(1), switchMap(() => this.optionSelectionChanges));
});
/** Event emitted when the select panel has been toggled. */
this.openedChange = new EventEmitter();
/** Event emitted when the select has been opened. */
this._openedStream = this.openedChange.pipe(filter(o => o), map(() => { }));
/** Event emitted when the select has been closed. */
this._closedStream = this.openedChange.pipe(filter(o => !o), map(() => { }));
/** Event emitted when the selected value has been changed by the user. */
this.selectionChange = new EventEmitter();
/**
* Event that emits whenever the raw value of the select changes. This is here primarily
* to facilitate the two-way binding for the `value` input.
* @docs-private
*/
this.valueChange = new EventEmitter();
if (this.ngControl) {
// Note: we provide the value accessor through here, instead of
// the `providers` to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
// Note that we only want to set this when the defaults pass it in, otherwise it should
// stay as `undefined` so that it falls back to the default in the key manager.
if ((_defaultOptions === null || _defaultOptions === void 0 ? void 0 : _defaultOptions.typeaheadDebounceInterval) != null) {
this._typeaheadDebounceInterval = _defaultOptions.typeaheadDebounceInterval;
}
this._scrollStrategyFactory = scrollStrategyFactory;
this._scrollStrategy = this._scrollStrategyFactory();
this.tabIndex = parseInt(tabIndex) || 0;
// Force setter to be called in case id was not specified.
this.id = this.id;
}
/** Whether the select is focused. */
get focused() {
return this._focused || this._panelOpen;
}
/** Placeholder to be shown if no value has been selected. */
get placeholder() { return this._placeholder; }
set placeholder(value) {
this._placeholder = value;
this.stateChanges.next();
}
/** Whether the component is required. */
get required() { return this._required; }
set required(value) {
this._required = coerceBooleanProperty(value);
this.stateChanges.next();
}
/** Whether the user should be allowed to select multiple options. */
get multiple() { return this._multiple; }
set multiple(value) {
if (this._selectionModel && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getMatSelectDynamicMultipleError();
}
this._multiple = coerceBooleanProperty(value);
}
/** Whether to center the active option over the trigger. */
get disableOptionCentering() { return this._disableOptionCentering; }
set disableOptionCentering(value) {
this._disableOptionCentering = coerceBooleanProperty(value);
}
/**
* Function to compare the option values with the selected values. The first argument
* is a value from an option. The second is a value from the selection. A boolean
* should be returned.
*/
get compareWith() { return this._compareWith; }
set compareWith(fn) {
if (typeof fn !== 'function' && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getMatSelectNonFunctionValueError();
}
this._compareWith = fn;
if (this._selectionModel) {
// A different comparator means the selection could change.
this._initializeSelection();
}
}
/** Value of the select control. */
get value() { return this._value; }
set value(newValue) {
// Always re-assign an array, because it might have been mutated.
if (newValue !== this._value || (this._multiple && Array.isArray(newValue))) {
if (this.options) {
this._setSelectionByValue(newValue);
}
this._value = newValue;
}
}
/** Time to wait in milliseconds after the last keystroke before moving focus to an item. */
get typeaheadDebounceInterval() { return this._typeaheadDebounceInterval; }
set typeaheadDebounceInterval(value) {
this._typeaheadDebounceInterval = coerceNumberProperty(value);
}
/** Unique id of the element. */
get id() { return this._id; }
set id(value) {
this._id = value || this._uid;
this.stateChanges.next();
}
ngOnInit() {
this._selectionModel = new SelectionModel(this.multiple);
this.stateChanges.next();
// We need `distinctUntilChanged` here, because some browsers will
// fire the animation end event twice for the same animation. See:
// https://github.com/angular/angular/issues/24084
this._panelDoneAnimatingStream
.pipe(distinctUntilChanged(), takeUntil(this._destroy))
.subscribe(() => this._panelDoneAnimating(this.panelOpen));
}
ngAfterContentInit() {
this._initKeyManager();
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
event.added.forEach(option => option.select());
event.removed.forEach(option => option.deselect());
});
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
this._resetOptions();
this._initializeSelection();
});
}
ngDoCheck() {
const newAriaLabelledby = this._getTriggerAriaLabelledby();
// We have to manage setting the `aria-labelledby` ourselves, because part of its value
// is computed as a result of a content query which can cause this binding to trigger a
// "changed after checked" error.
if (newAriaLabelledby !== this._triggerAriaLabelledBy) {
const element = this._elementRef.nativeElement;
this._triggerAriaLabelledBy = newAriaLabelledby;
if (newAriaLabelledby) {
element.setAttribute('aria-labelledby', newAriaLabelledby);
}
else {
element.removeAttribute('aria-labelledby');
}
}
if (this.ngControl) {
this.updateErrorState();
}
}
ngOnChanges(changes) {
// Updating the disabled state is handled by `mixinDisabled`, but we need to additionally let
// the parent form field know to run change detection when the disabled state changes.
if (changes['disabled']) {
this.stateChanges.next();
}
if (changes['typeaheadDebounceInterval'] && this._keyManager) {
this._keyManager.withTypeAhead(this._typeaheadDebounceInterval);
}
}
ngOnDestroy() {
this._destroy.next();
this._destroy.complete();
this.stateChanges.complete();
}
/** Toggles the overlay panel open or closed. */
toggle() {
this.panelOpen ? this.close() : this.open();
}
/** Opens the overlay panel. */
open() {
if (this._canOpen()) {
this._panelOpen = true;
this._keyManager.withHorizontalOrientation(null);
this._highlightCorrectOption();
this._changeDetectorRef.markForCheck();
}
}
/** Closes the overlay panel and focuses the host element. */
close() {
if (this._panelOpen) {
this._panelOpen = false;
this._keyManager.withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr');
this._changeDetectorRef.markForCheck();
this._onTouched();
}
}
/**
* Sets the select's value. Part of the ControlValueAccessor interface
* required to integrate with Angular's core forms API.
*
* @param value New value to be written to the model.
*/
writeValue(value) {
this.value = value;
}
/**
* Saves a callback function to be invoked when the select's value
* changes from user input. Part of the ControlValueAccessor interface
* required to integrate with Angular's core forms API.
*
* @param fn Callback to be triggered when the value changes.
*/
registerOnChange(fn) {
this._onChange = fn;
}
/**
* Saves a callback function to be invoked when the select is blurred
* by the user. Part of the ControlValueAccessor interface required
* to integrate with Angular's core forms API.
*
* @param fn Callback to be triggered when the component has been touched.
*/
registerOnTouched(fn) {
this._onTouched = fn;
}
/**
* Disables the select. Part of the ControlValueAccessor interface required
* to integrate with Angular's core forms API.
*
* @param isDisabled Sets whether the component is disabled.
*/
setDisabledState(isDisabled) {
this.disabled = isDisabled;
this._changeDetectorRef.markForCheck();
this.stateChanges.next();
}
/** Whether or not the overlay panel is open. */
get panelOpen() {
return this._panelOpen;
}
/** The currently selected option. */
get selected() {
var _a, _b;
return this.multiple ? (((_a = this._selectionModel) === null || _a === void 0 ? void 0 : _a.selected) || []) :
(_b = this._selectionModel) === null || _b === void 0 ? void 0 : _b.selected[0];
}
/** The value displayed in the trigger. */
get triggerValue() {
if (this.empty) {
return '';
}
if (this._multiple) {
const selectedOptions = this._selectionModel.selected.map(option => option.viewValue);
if (this._isRtl()) {
selectedOptions.reverse();
}
// TODO(crisbeto): delimiter should be configurable for proper localization.
return selectedOptions.join(', ');
}
return this._selectionModel.selected[0].viewValue;
}
/** Whether the element is in RTL mode. */
_isRtl() {
return this._dir ? this._dir.value === 'rtl' : false;
}
/** Handles all keydown events on the select. */
_handleKeydown(event) {
if (!this.disabled) {
this.panelOpen ? this._handleOpenKeydown(event) : this._handleClosedKeydown(event);
}
}
/** Handles keyboard events while the select is closed. */
_handleClosedKeydown(event) {
const keyCode = event.keyCode;
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW ||
keyCode === LEFT_ARROW || keyCode === RIGHT_ARROW;
const isOpenKey = keyCode === ENTER || keyCode === SPACE;
const manager = this._keyManager;
// Open the select on ALT + arrow key to match the native <select>
if (!manager.isTyping() && (isOpenKey && !hasModifierKey(event)) ||
((this.multiple || event.altKey) && isArrowKey)) {
event.preventDefault(); // prevents the page from scrolling down when pressing space
this.open();
}
else if (!this.multiple) {
const previouslySelectedOption = this.selected;
manager.onKeydown(event);
const selectedOption = this.selected;
// Since the value has changed, we need to announce it ourselves.
if (selectedOption && previouslySelectedOption !== selectedOption) {
// We set a duration on the live announcement, because we want the live element to be
// cleared after a while so that users can't navigate to it using the arrow keys.
this._liveAnnouncer.announce(selectedOption.viewValue, 10000);
}
}
}
/** Handles keyboard events when the selected is open. */
_handleOpenKeydown(event) {
const manager = this._keyManager;
const keyCode = event.keyCode;
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
const isTyping = manager.isTyping();
if (isArrowKey && event.altKey) {
// Close the select on ALT + arrow key to match the native <select>
event.preventDefault();
this.close();
// Don't do anything in this case if the user is typing,
// because the typing sequence can include the space key.
}
else if (!isTyping && (keyCode === ENTER || keyCode === SPACE) && manager.activeItem &&
!hasModifierKey(event)) {
event.preventDefault();
manager.activeItem._selectViaInteraction();
}
else if (!isTyping && this._multiple && keyCode === A && event.ctrlKey) {
event.preventDefault();
const hasDeselectedOptions = this.options.some(opt => !opt.disabled && !opt.selected);
this.options.forEach(option => {
if (!option.disabled) {
hasDeselectedOptions ? option.select() : option.deselect();
}
});
}
else {
const previouslyFocusedIndex = manager.activeItemIndex;
manager.onKeydown(event);
if (this._multiple && isArrowKey && event.shiftKey && manager.activeItem &&
manager.activeItemIndex !== previouslyFocusedIndex) {
manager.activeItem._selectViaInteraction();
}
}
}
_onFocus() {
if (!this.disabled) {
this._focused = true;
this.stateChanges.next();
}
}
/**
* Calls the touched callback only if the panel is closed. Otherwise, the trigger will
* "blur" to the panel when it opens, causing a false positive.
*/
_onBlur() {
this._focused = false;
if (!this.disabled && !this.panelOpen) {
this._onTouched();
this._changeDetectorRef.markForCheck();
this.stateChanges.next();
}
}
/**
* Callback that is invoked when the overlay panel has been attached.
*/
_onAttached() {
this._overlayDir.positionChange.pipe(take(1)).subscribe(() => {
this._changeDetectorRef.detectChanges();
this._positioningSettled();
});
}
/** Returns the theme to be used on the panel. */
_getPanelTheme() {
return this._parentFormField ? `mat-${this._parentFormField.color}` : '';
}
/** Whether the select has a value. */
get empty() {
return !this._selectionModel || this._selectionModel.isEmpty();
}
_initializeSelection() {
// Defer setting the value in order to avoid the "Expression
// has changed after it was checked" errors from Angular.
Promise.resolve().then(() => {
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
this.stateChanges.next();
});
}
/**
* Sets the selected option based on a value. If no option can be
* found with the designated value, the select trigger is cleared.
*/
_setSelectionByValue(value) {
this._selectionModel.selected.forEach(option => option.setInactiveStyles());
this._selectionModel.clear();
if (this.multiple && value) {
if (!Array.isArray(value) && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getMatSelectNonArrayValueError();
}
value.forEach((currentValue) => this._selectValue(currentValue));
this._sortValues();
}
else {
const correspondingOption = this._selectValue(value);
// Shift focus to the active item. Note that we shouldn't do this in multiple
// mode, because we don't know what option the user interacted with last.
if (correspondingOption) {
this._keyManager.updateActiveItem(correspondingOption);
}
else if (!this.panelOpen) {
// Otherwise reset the highlighted option. Note that we only want to do this while
// closed, because doing it while open can shift the user's focus unnecessarily.
this._keyManager.updateActiveItem(-1);
}
}
this._changeDetectorRef.markForCheck();
}
/**
* Finds and selects and option based on its value.
* @returns Option that has the corresponding value.
*/
_selectValue(value) {
const correspondingOption = this.options.find((option) => {
// Skip options that are already in the model. This allows us to handle cases
// where the same primitive value is selected multiple times.
if (this._selectionModel.isSelected(option)) {
return false;
}
try {
// Treat null as a special reset value.
return option.value != null && this._compareWith(option.value, value);
}
catch (error) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
// Notify developers of errors in their comparator.
console.warn(error);
}
return false;
}
});
if (correspondingOption) {
this._selectionModel.select(correspondingOption);
}
return correspondingOption;
}
/** Sets up a key manager to listen to keyboard events on the overlay panel. */
_initKeyManager() {
this._keyManager = new ActiveDescendantKeyManager(this.options)
.withTypeAhead(this._typeaheadDebounceInterval)
.withVerticalOrientation()
.withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr')
.withHomeAndEnd()
.withAllowedModifierKeys(['shiftKey']);
this._keyManager.tabOut.pipe(takeUntil(this._destroy)).subscribe(() => {
if (this.panelOpen) {
// Select the active item when tabbing away. This is consistent with how the native
// select behaves. Note that we only want to do this in single selection mode.
if (!this.multiple && this._keyManager.activeItem) {
this._keyManager.activeItem._selectViaInteraction();
}
// Restore focus to the trigger before closing. Ensures that the focus
// position won't be lost if the user got focus into the overlay.
this.focus();
this.close();
}
});
this._keyManager.change.pipe(takeUntil(this._destroy)).subscribe(() => {
if (this._panelOpen && this.panel) {
this._scrollOptionIntoView(this._keyManager.activeItemIndex || 0);
}
else if (!this._panelOpen && !this.multiple && this._keyManager.activeItem) {
this._keyManager.activeItem._selectViaInteraction();
}
});
}
/** Drops current option subscriptions and IDs and resets from scratch. */
_resetOptions() {
const changedOrDestroyed = merge(this.options.changes, this._destroy);
this.optionSelectionChanges.pipe(takeUntil(changedOrDestroyed)).subscribe(event => {
this._onSelect(event.source, event.isUserInput);
if (event.isUserInput && !this.multiple && this._panelOpen) {
this.close();
this.focus();
}
});
// Listen to changes in the internal state of the options and react accordingly.
// Handles cases like the labels of the selected options changing.
merge(...this.options.map(option => option._stateChanges))
.pipe(takeUntil(changedOrDestroyed))
.subscribe(() => {
this._changeDetectorRef.markForCheck();
this.stateChanges.next();
});
}
/** Invoked when an option is clicked. */
_onSelect(option, isUserInput) {
const wasSelected = this._selectionModel.isSelected(option);
if (option.value == null && !this._multiple) {
option.deselect();
this._selectionModel.clear();
if (this.value != null) {
this._propagateChanges(option.value);
}
}
else {
if (wasSelected !== option.selected) {
option.selected ? this._selectionModel.select(option) :
this._selectionModel.deselect(option);
}
if (isUserInput) {
this._keyManager.setActiveItem(option);
}
if (this.multiple) {
this._sortValues();
if (isUserInput) {
// In case the user selected the option with their mouse, we
// want to restore focus back to the trigger, in order to
// prevent the select keyboard controls from clashing with
// the ones from `mat-option`.
this.focus();
}
}
}
if (wasSelected !== this._selectionModel.isSelected(option)) {
this._propagateChanges();
}
this.stateChanges.next();
}
/** Sorts the selected values in the selected based on their order in the panel. */
_sortValues() {
if (this.multiple) {
const options = this.options.toArray();
this._selectionModel.sort((a, b) => {
return this.sortComparator ? this.sortComparator(a, b, options) :
options.indexOf(a) - options.indexOf(b);
});
this.stateChanges.next();
}
}
/** Emits change event to set the model value. */
_propagateChanges(fallbackValue) {
let valueToEmit = null;
if (this.multiple) {
valueToEmit = this.selected.map(option => option.value);
}
else {
valueToEmit = this.selected ? this.selected.value : fallbackValue;
}
this._value = valueToEmit;
this.valueChange.emit(valueToEmit);
this._onChange(valueToEmit);
this.selectionChange.emit(this._getChangeEvent(valueToEmit));
this._changeDetectorRef.markForCheck();
}
/**
* Highlights the selected item. If no option is selected, it will highlight
* the first item instead.
*/
_highlightCorrectOption() {
if (this._keyManager) {
if (this.empty) {
this._keyManager.setFirstItemActive();
}
else {
this._keyManager.setActiveItem(this._selectionModel.selected[0]);
}
}
}
/** Whether the panel is allowed to open. */
_canOpen() {
var _a;
return !this._panelOpen && !this.disabled && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.length) > 0;
}
/** Focuses the select element. */
focus(options) {
this._elementRef.nativeElement.focus(options);
}
/** Gets the aria-labelledby for the select panel. */
_getPanelAriaLabelledby() {
var _a;
if (this.ariaLabel) {
return null;
}
const labelId = (_a = this._parentFormField) === null || _a === void 0 ? void 0 : _a.getLabelId();
const labelExpression = (labelId ? labelId + ' ' : '');
return this.ariaLabelledby ? labelExpression + this.ariaLabelledby : labelId;
}
/** Determines the `aria-activedescendant` to be set on the host. */
_getAriaActiveDescendant() {
if (this.panelOpen && this._keyManager && this._keyManager.activeItem) {
return this._keyManager.activeItem.id;
}
return null;
}
/** Gets the aria-labelledby of the select component trigger. */
_getTriggerAriaLabelledby() {
var _a;
if (this.ariaLabel) {
return null;
}
const labelId = (_a = this._parentFormField) === null || _a === void 0 ? void 0 : _a.getLabelId();
let value = (labelId ? labelId + ' ' : '') + this._valueId;
if (this.ariaLabelledby) {
value += ' ' + this.ariaLabelledby;
}
return value;
}
/** Called when the overlay panel is done animating. */
_panelDoneAnimating(isOpen) {
this.openedChange.emit(isOpen);
}
/**
* Implemented as part of MatFormFieldControl.
* @docs-private
*/
setDescribedByIds(ids) {
this._ariaDescribedby = ids.join(' ');
}
/**
* Implemented as part of MatFormFieldControl.
* @docs-private
*/
onContainerClick() {
this.focus();
this.open();
}
/**
* Implemented as part of MatFormFieldControl.
* @docs-private
*/
get shouldLabelFloat() {
return this._panelOpen || !this.empty || (this._focused && !!this._placeholder);
}
}
_MatSelectBase.ɵfac = function _MatSelectBase_Factory(t) { return new (t || _MatSelectBase)(ɵngcc0.ɵɵdirectiveInject(ɵngcc1.ViewportRuler), ɵngcc0.ɵɵdirectiveInject(ɵngcc0.ChangeDetectorRef), ɵngcc0.ɵɵdirectiveInject(ɵngcc0.NgZone), ɵngcc0.ɵɵdirectiveInject(ɵngcc2.ErrorStateMatcher), ɵngcc0.ɵɵdirectiveInject(ɵngcc0.ElementRef), ɵngcc0.ɵɵdirectiveInject(ɵngcc3.Directionality, 8), ɵngcc0.ɵɵdirectiveInject(ɵngcc4.NgForm, 8), ɵngcc0.ɵɵdirectiveInject(ɵngcc4.FormGroupDirective, 8), ɵngcc0.ɵɵdirectiveInject(MAT_FORM_FIELD, 8), ɵngcc0.ɵɵdirectiveInject(ɵngcc4.NgControl, 10), ɵngcc0.ɵɵinjectAttribute('tabindex'), ɵngcc0.ɵɵdirectiveInject(MAT_SELECT_SCROLL_STRATEGY), ɵngcc0.ɵɵdirectiveInject(ɵngcc5.LiveAnnouncer), ɵngcc0.ɵɵdirectiveInject(MAT_SELECT_CONFIG, 8)); };
_MatSelectBase.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: _MatSelectBase, viewQuery: function _MatSelectBase_Query(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵviewQuery(_c0, 5);
ɵngcc0.ɵɵviewQuery(_c1, 5);
ɵngcc0.ɵɵviewQuery(CdkConnectedOverlay, 5);
} if (rf & 2) {
let _t;
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.trigger = _t.first);
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.panel = _t.first);
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx._overlayDir = _t.first);
} }, inputs: { ariaLabel: ["aria-label", "ariaLabel"], id: "id", placeholder: "placeholder", required: "required", multiple: "multiple", disableOptionCentering: "disableOptionCentering", compareWith: "compareWith", value: "value", typeaheadDebounceInterval: "typeaheadDebounceInterval", panelClass: "panelClass", ariaLabelledby: ["aria-labelledby", "ariaLabelledby"], errorStateMatcher: "errorStateMatcher", sortComparator: "sortComparator" }, outputs: { openedChange: "openedChange", _openedStream: "opened", _closedStream: "closed", selectionChange: "selectionChange", valueChange: "valueChange" }, features: [ɵngcc0.ɵɵInheritDefinitionFeature, ɵngcc0.ɵɵNgOnChangesFeature] });
_MatSelectBase.ctorParameters = () => [
{ type: ViewportRuler },
{ type: ChangeDetectorRef },
{ type: NgZone },
{ type: ErrorStateMatcher },
{ type: ElementRef },
{ type: Directionality, decorators: [{ type: Optional }] },
{ type: NgForm, decorators: [{ type: Optional }] },
{ type: FormGroupDirective, decorators: [{ type: Optional }] },
{ type: MatFormField, decorators: [{ type: Optional }, { type: Inject, args: [MAT_FORM_FIELD,] }] },
{ type: NgControl, decorators: [{ type: Self }, { type: Optional }] },
{ type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] },
{ type: undefined, decorators: [{ type: Inject, args: [MAT_SELECT_SCROLL_STRATEGY,] }] },
{ type: LiveAnnouncer },
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_SELECT_CONFIG,] }] }
];
_MatSelectBase.propDecorators = {
trigger: [{ type: ViewChild, args: ['trigger',] }],
panel: [{ type: ViewChild, args: ['panel',] }],
_overlayDir: [{ type: ViewChild, args: [CdkConnectedOverlay,] }],
panelClass: [{ type: Input }],
placeholder: [{ type: Input }],
required: [{ type: Input }],
multiple: [{ type: Input }],
disableOptionCentering: [{ type: Input }],
compareWith: [{ type: Input }],
value: [{ type: Input }],
ariaLabel: [{ type: Input, args: ['aria-label',] }],
ariaLabelledby: [{ type: Input, args: ['aria-labelledby',] }],
errorStateMatcher: [{ type: Input }],
typeaheadDebounceInterval: [{ type: Input }],
sortComparator: [{ type: Input }],
id: [{ type: Input }],
openedChange: [{ type: Output }],
_openedStream: [{ type: Output, args: ['opened',] }],
_closedStream: [{ type: Output, args: ['closed',] }],
selectionChange: [{ type: Output }],
valueChange: [{ type: Output }]
};
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(_MatSelectBase, [{
type: Directive
}], function () { return [{ type: ɵngcc1.ViewportRuler }, { type: ɵngcc0.ChangeDetectorRef }, { type: ɵngcc0.NgZone }, { type: ɵngcc2.ErrorStateMatcher }, { type: ɵngcc0.ElementRef }, { type: ɵngcc3.Directionality, decorators: [{
type: Optional
}] }, { type: ɵngcc4.NgForm, decorators: [{
type: Optional
}] }, { type: ɵngcc4.FormGroupDirective, decorators: [{
type: Optional
}] }, { type: ɵngcc6.MatFormField, decorators: [{
type: Optional
}, {
type: Inject,
args: [MAT_FORM_FIELD]
}] }, { type: ɵngcc4.NgControl, decorators: [{
type: Self
}, {
type: Optional
}] }, { type: String, decorators: [{
type: Attribute,
args: ['tabindex']
}] }, { type: undefined, decorators: [{
type: Inject,
args: [MAT_SELECT_SCROLL_STRATEGY]
}] }, { type: ɵngcc5.LiveAnnouncer }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [MAT_SELECT_CONFIG]
}] }]; }, { ariaLabel: [{
type: Input,
args: ['aria-label']
}], openedChange: [{
type: Output
}], _openedStream: [{
type: Output,
args: ['opened']
}], _closedStream: [{
type: Output,
args: ['closed']
}], selectionChange: [{
type: Output
}], valueChange: [{
type: Output
}], id: [{
type: Input
}], placeholder: [{
type: Input
}], required: [{
type: Input
}], multiple: [{
type: Input
}], disableOptionCentering: [{
type: Input
}], compareWith: [{
type: Input
}], value: [{
type: Input
}], typeaheadDebounceInterval: [{
type: Input
}], trigger: [{
type: ViewChild,
args: ['trigger']
}], panel: [{
type: ViewChild,
args: ['panel']
}], _overlayDir: [{
type: ViewChild,
args: [CdkConnectedOverlay]
}], panelClass: [{
type: Input
}], ariaLabelledby: [{
type: Input,
args: ['aria-labelledby']
}], errorStateMatcher: [{
type: Input
}], sortComparator: [{
type: Input
}] }); })();
class MatSelect extends _MatSelectBase {
constructor() {
super(...arguments);
/** The scroll position of the overlay panel, calculated to center the selected option. */
this._scrollTop = 0;
/** The cached font-size of the trigger element. */
this._triggerFontSize = 0;
/** The value of the select panel's transform-origin property. */
this._transformOrigin = 'top';
/**
* The y-offset of the overlay panel in relation to the trigger's top start corner.
* This must be adjusted to align the selected option text over the trigger text.
* when the panel opens. Will change based on the y-position of the selected option.
*/
this._offsetY = 0;
this._positions = [
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'top',
},
{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'bottom',
},
];
}
/**
* Calculates the scroll position of the select's overlay panel.
*
* Attempts to center the selected option in the panel. If the option is
* too high or too low in the panel to be scrolled to the center, it clamps the
* scroll position to the min or max scroll positions respectively.
*/
_calculateOverlayScroll(selectedIndex, scrollBuffer, maxScroll) {
const itemHeight = this._getItemHeight();
const optionOffsetFromScrollTop = itemHeight * selectedIndex;
const halfOptionHeight = itemHeight / 2;
// Starts at the optionOffsetFromScrollTop, which scrolls the option to the top of the
// scroll container, then subtracts the scroll buffer to scroll the option down to
// the center of the overlay panel. Half the option height must be re-added to the
// scrollTop so the option is centered based on its middle, not its top edge.
const optimalScrollPosition = optionOffsetFromScrollTop - scrollBuffer + halfOptionHeight;
return Math.min(Math.max(0, optimalScrollPosition), maxScroll);
}
ngOnInit() {
super.ngOnInit();
this._viewportRuler.change().pipe(takeUntil(this._destroy)).subscribe(() => {
if (this.panelOpen) {
this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();
this._changeDetectorRef.markForCheck();
}
});
}
open() {
if (super._canOpen()) {
super.open();
this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();
// Note: The computed font-size will be a string pixel value (e.g. "16px").
// `parseInt` ignores the trailing 'px' and converts this to a number.
this._triggerFontSize =
parseInt(getComputedStyle(this.trigger.nativeElement).fontSize || '0');
this._calculateOverlayPosition();
// Set the font size on the panel element once it exists.
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
if (this._triggerFontSize && this._overlayDir.overlayRef &&
this._overlayDir.overlayRef.overlayElement) {
this._overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`;
}
});
}
}
/** Scrolls the active option into view. */
_scrollOptionIntoView(index) {
const labelCount = _countGroupLabelsBeforeOption(index, this.options, this.optionGroups);
const itemHeight = this._getItemHeight();
if (index === 0 && labelCount === 1) {
// If we've got one group label before the option and we're at the top option,
// scroll the list to the top. This is better UX than scrolling the list to the
// top of the option, because it allows the user to read the top group's label.
this.panel.nativeElement.scrollTop = 0;
}
else {
this.panel.nativeElement.scrollTop = _getOptionScrollPosition((index + labelCount) * itemHeight, itemHeight, this.panel.nativeElement.scrollTop, SELECT_PANEL_MAX_HEIGHT);
}
}
_positioningSettled() {
this._calculateOverlayOffsetX();
this.panel.nativeElement.scrollTop = this._scrollTop;
}
_panelDoneAnimating(isOpen) {
if (this.panelOpen) {
this._scrollTop = 0;
}
else {
this._overlayDir.offsetX = 0;
this._changeDetectorRef.markForCheck();
}
super._panelDoneAnimating(isOpen);
}
_getChangeEvent(value) {
return new MatSelectChange(this, value);
}
/**
* Sets the x-offset of the overlay panel in relation to the trigger's top start corner.
* This must be adjusted to align the selected option text over the trigger text when
* the panel opens. Will change based on LTR or RTL text direction. Note that the offset
* can't be calculated until the panel has been attached, because we need to know the
* content width in order to constrain the panel within the viewport.
*/
_calculateOverlayOffsetX() {
const overlayRect = this._overlayDir.overlayRef.overlayElement.getBoundingClientRect();
const viewportSize = this._viewportRuler.getViewportSize();
const isRtl = this._isRtl();
const paddingWidth = this.multiple ? SELECT_MULTIPLE_PANEL_PADDING_X + SELECT_PANEL_PADDING_X :
SELECT_PANEL_PADDING_X * 2;
let offsetX;
// Adjust the offset, depending on the option padding.
if (this.multiple) {
offsetX = SELECT_MULTIPLE_PANEL_PADDING_X;
}
else if (this.disableOptionCentering) {
offsetX = SELECT_PANEL_PADDING_X;
}
else {
let selected = this._selectionModel.selected[0] || this.options.first;
offsetX = selected && selected.group ? SELECT_PANEL_INDENT_PADDING_X : SELECT_PANEL_PADDING_X;
}
// Invert the offset in LTR.
if (!isRtl) {
offsetX *= -1;
}
// Determine how much the select overflows on each side.
const leftOverflow = 0 - (overlayRect.left + offsetX - (isRtl ? paddingWidth : 0));
const rightOverflow = overlayRect.right + offsetX - viewportSize.width
+ (isRtl ? 0 : paddingWidth);
// If the element overflows on either side, reduce the offset to allow it to fit.
if (leftOverflow > 0) {
offsetX += leftOverflow + SELECT_PANEL_VIEWPORT_PADDING;
}
else if (rightOverflow > 0) {
offsetX -= rightOverflow + SELECT_PANEL_VIEWPORT_PADDING;
}
// Set the offset directly in order to avoid having to go through change detection and
// potentially triggering "changed after it was checked" errors. Round the value to avoid
// blurry content in some browsers.
this._overlayDir.offsetX = Math.round(offsetX);
this._overlayDir.overlayRef.updatePosition();
}
/**
* Calculates the y-offset of the select's overlay panel in relation to the
* top start corner of the trigger. It has to be adjusted in order for the
* selected option to be aligned over the trigger when the panel opens.
*/
_calculateOverlayOffsetY(selectedIndex, scrollBuffer, maxScroll) {
const itemHeight = this._getItemHeight();
const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);
let optionOffsetFromPanelTop;
// Disable offset if requested by user by returning 0 as value to offset
if (this.disableOptionCentering) {
return 0;
}
if (this._scrollTop === 0) {
optionOffsetFromPanelTop = selectedIndex * itemHeight;
}
else if (this._scrollTop === maxScroll) {
const firstDisplayedIndex = this._getItemCount() - maxOptionsDisplayed;
const selectedDisplayIndex = selectedIndex - firstDisplayedIndex;
// The first item is partially out of the viewport. Therefore we need to calculate what
// portion of it is shown in the viewport and account for it in our offset.
let partialItemHeight = itemHeight - (this._getItemCount() * itemHeight - SELECT_PANEL_MAX_HEIGHT) % itemHeight;
// Because the panel height is longer than the height of the options alone,
// there is always extra padding at the top or bottom of the panel. When
// scrolled to the very bottom, this padding is at the top of the panel and
// must be added to the offset.
optionOffsetFromPanelTop = selectedDisplayIndex * itemHeight + partialItemHeight;
}
else {
// If the option was scrolled to the middle of the panel using a scroll buffer,
// its offset will be the scroll buffer minus the half height that was added to
// center it.
optionOffsetFromPanelTop = scrollBuffer - itemHeight / 2;
}
// The final offset is the option's offset from the top, adjusted for the height difference,
// multiplied by -1 to ensure that the overlay moves in the correct direction up the page.
// The value is rounded to prevent some browsers from blurring the content.
return Math.round(optionOffsetFromPanelTop * -1 - optionHeightAdjustment);
}
/**
* Checks that the attempted overlay position will fit within the viewport.
* If it will not fit, tries to adjust the scroll position and the associated
* y-offset so the panel can open fully on-screen. If it still won't fit,
* sets the offset back to 0 to allow the fallback position to take over.
*/
_checkOverlayWithinViewport(maxScroll) {
const itemHeight = this._getItemHeight();
const viewportSize = this._viewportRuler.getViewportSize();
const topSpaceAvailable = this._triggerRect.top - SELECT_PANEL_VIEWPORT_PADDING;
const bottomSpaceAvailable = viewportSize.height - this._triggerRect.bottom - SELECT_PANEL_VIEWPORT_PADDING;
const panelHeightTop = Math.abs(this._offsetY);
const totalPanelHeight = Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);
const panelHeightBottom = totalPanelHeight - panelHeightTop - this._triggerRect.height;
if (panelHeightBottom > bottomSpaceAvailable) {
this._adjustPanelUp(panelHeightBottom, bottomSpaceAvailable);
}
else if (panelHeightTop > topSpaceAvailable) {
this._adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll);
}
else {
this._transformOrigin = this._getOriginBasedOnOption();
}
}
/** Adjusts the overlay panel up to fit in the viewport. */
_adjustPanelUp(panelHeightBottom, bottomSpaceAvailable) {
// Browsers ignore fractional scroll offsets, so we need to round.
const distanceBelowViewport = Math.round(panelHeightBottom - bottomSpaceAvailable);
// Scrolls the panel up by the distance it was extending past the boundary, then
// adjusts the offset by that amount to move the panel up into the viewport.
this._scrollTop -= distanceBelowViewport;
this._offsetY -= distanceBelowViewport;
this._transformOrigin = this._getOriginBasedOnOption();
// If the panel is scrolled to the very top, it won't be able to fit the panel
// by scrolling, so set the offset to 0 to allow the fallback position to take
// effect.
if (this._scrollTop <= 0) {
this._scrollTop = 0;
this._offsetY = 0;
this._transformOrigin = `50% bottom 0px`;
}
}
/** Adjusts the overlay panel down to fit in the viewport. */
_adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll) {
// Browsers ignore fractional scroll offsets, so we need to round.
const distanceAboveViewport = Math.round(panelHeightTop - topSpaceAvailable);
// Scrolls the panel down by the distance it was extending past the boundary, then
// adjusts the offset by that amount to move the panel down into the viewport.
this._scrollTop += distanceAboveViewport;
this._offsetY += distanceAboveViewport;
this._transformOrigin = this._getOriginBasedOnOption();
// If the panel is scrolled to the very bottom, it won't be able to fit the
// panel by scrolling, so set the offset to 0 to allow the fallback position
// to take effect.
if (this._scrollTop >= maxScroll) {
this._scrollTop = maxScroll;
this._offsetY = 0;
this._transformOrigin = `50% top 0px`;
return;
}
}
/** Calculates the scroll position and x- and y-offsets of the overlay panel. */
_calculateOverlayPosition() {
const itemHeight = this._getItemHeight();
const items = this._getItemCount();
const panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);
const scrollContainerHeight = items * itemHeight;
// The farthest the panel can be scrolled before it hits the bottom
const maxScroll = scrollContainerHeight - panelHeight;
// If no value is selected we open the popup to the first item.
let selectedOptionOffset;
if (this.empty) {
selectedOptionOffset = 0;
}
else {
selectedOptionOffset =
Math.max(this.options.toArray().indexOf(this._selectionModel.selected[0]), 0);
}
selectedOptionOffset += _countGroupLabelsBeforeOption(selectedOptionOffset, this.options, this.optionGroups);
// We must maintain a scroll buffer so the selected option will be scrolled to the
// center of the overlay panel rather than the top.
const scrollBuffer = panelHeight / 2;
this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
this._checkOverlayWithinViewport(maxScroll);
}
/** Sets the transform origin point based on the selected option. */
_getOriginBasedOnOption() {
const itemHeight = this._getItemHeight();
const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
const originY = Math.abs(this._offsetY) - optionHeightAdjustment + itemHeight / 2;
return `50% ${originY}px 0px`;
}
/** Calculates the height of the select's options. */
_getItemHeight() {
return this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
}
/** Calculates the amount of items in the select. This includes options and group labels. */
_getItemCount() {
return this.options.length + this.optionGroups.length;
}
}
MatSelect.ɵfac = /*@__PURE__*/ function () { let ɵMatSelect_BaseFactory; return function MatSelect_Factory(t) { return (ɵMatSelect_BaseFactory || (ɵMatSelect_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatSelect)))(t || MatSelect); }; }();
MatSelect.ɵcmp = /*@__PURE__*/ ɵngcc0.ɵɵdefineComponent({ type: MatSelect, selectors: [["mat-select"]], contentQueries: function MatSelect_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) {
ɵngcc0.ɵɵcontentQuery(dirIndex, MAT_SELECT_TRIGGER, 5);
ɵngcc0.ɵɵcontentQuery(dirIndex, MatOption, 5);
ɵngcc0.ɵɵcontentQuery(dirIndex, MAT_OPTGROUP, 5);
} if (rf & 2) {
let _t;
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.customTrigger = _t.first);
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.options = _t);
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.optionGroups = _t);
} }, hostAttrs: ["role", "combobox", "aria-autocomplete", "none", "aria-haspopup", "true", 1, "mat-select"], hostVars: 20, hostBindings: function MatSelect_HostBindings(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵlistener("keydown", function MatSelect_keydown_HostBindingHandler($event) { return ctx._handleKeydown($event); })("focus", function MatSelect_focus_HostBindingHandler() { return ctx._onFocus(); })("blur", function MatSelect_blur_HostBindingHandler() { return ctx._onBlur(); });
} if (rf & 2) {
ɵngcc0.ɵɵattribute("id", ctx.id)("tabindex", ctx.tabIndex)("aria-controls", ctx.panelOpen ? ctx.id + "-panel" : null)("aria-expanded", ctx.panelOpen)("aria-label", ctx.ariaLabel || null)("aria-required", ctx.required.toString())("aria-disabled", ctx.disabled.toString())("aria-invalid", ctx.errorState)("aria-describedby", ctx._ariaDescribedby || null)("aria-activedescendant", ctx._getAriaActiveDescendant());
ɵngcc0.ɵɵclassProp("mat-select-disabled", ctx.disabled)("mat-select-invalid", ctx.errorState)("mat-select-required", ctx.required)("mat-select-empty", ctx.empty)("mat-select-multiple", ctx.multiple);
} }, inputs: { disabled: "disabled", disableRipple: "disableRipple", tabIndex: "tabIndex" }, exportAs: ["matSelect"], features: [ɵngcc0.ɵɵProvidersFeature([
{ provide: MatFormFieldControl, useExisting: MatSelect },
{ provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatSelect }
]), ɵngcc0.ɵɵInheritDefinitionFeature], ngContentSelectors: _c3, decls: 9, vars: 12, consts: [["cdk-overlay-origin", "", 1, "mat-select-trigger", 3, "click"], ["origin", "cdkOverlayOrigin", "trigger", ""], [1, "mat-select-value", 3, "ngSwitch"], ["class", "mat-select-placeholder mat-select-min-line", 4, "ngSwitchCase"], ["class", "mat-select-value-text", 3, "ngSwitch", 4, "ngSwitchCase"], [1, "mat-select-arrow-wrapper"], [1, "mat-select-arrow"], ["cdk-connected-overlay", "", "cdkConnectedOverlayLockPosition", "", "cdkConnectedOverlayHasBackdrop", "", "cdkConnectedOverlayBackdropClass", "cdk-overlay-transparent-backdrop", 3, "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayOffsetY", "backdropClick", "attach", "detach"], [1, "mat-select-placeholder", "mat-select-min-line"], [1, "mat-select-value-text", 3, "ngSwitch"], ["class", "mat-select-min-line", 4, "ngSwitchDefault"], [4, "ngSwitchCase"], [1, "mat-select-min-line"], [1, "mat-select-panel-wrap"], ["role", "listbox", "tabindex", "-1", 3, "ngClass", "keydown"], ["panel", ""]], template: function MatSelect_Template(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵprojectionDef(_c2);
ɵngcc0.ɵɵelementStart(0, "div", 0, 1);
ɵngcc0.ɵɵlistener("click", function MatSelect_Template_div_click_0_listener() { return ctx.toggle(); });
ɵngcc0.ɵɵelementStart(3, "div", 2);
ɵngcc0.ɵɵtemplate(4, MatSelect_span_4_Template, 2, 1, "span", 3);
ɵngcc0.ɵɵtemplate(5, MatSelect_span_5_Template, 3, 2, "span", 4);
ɵngcc0.ɵɵelementEnd();
ɵngcc0.ɵɵelementStart(6, "div", 5);
ɵngcc0.ɵɵelement(7, "div", 6);
ɵngcc0.ɵɵelementEnd();
ɵngcc0.ɵɵelementEnd();
ɵngcc0.ɵɵtemplate(8, MatSelect_ng_template_8_Template, 4, 14, "ng-template", 7);
ɵngcc0.ɵɵlistener("backdropClick", function MatSelect_Template_ng_template_backdropClick_8_listener() { return ctx.close(); })("attach", function MatSelect_Template_ng_template_attach_8_listener() { return ctx._onAttached(); })("detach", function MatSelect_Template_ng_template_detach_8_listener() { return ctx.close(); });
} if (rf & 2) {
const _r0 = ɵngcc0.ɵɵreference(1);
ɵngcc0.ɵɵattribute("aria-owns", ctx.panelOpen ? ctx.id + "-panel" : null);
ɵngcc0.ɵɵadvance(3);
ɵngcc0.ɵɵproperty("ngSwitch", ctx.empty);
ɵngcc0.ɵɵattribute("id", ctx._valueId);
ɵngcc0.ɵɵadvance(1);
ɵngcc0.ɵɵproperty("ngSwitchCase", true);
ɵngcc0.ɵɵadvance(1);
ɵngcc0.ɵɵproperty("ngSwitchCase", false);
ɵngcc0.ɵɵadvance(3);
ɵngcc0.ɵɵproperty("cdkConnectedOverlayPanelClass", ctx._overlayPanelClass)("cdkConnectedOverlayScrollStrategy", ctx._scrollStrategy)("cdkConnectedOverlayOrigin", _r0)("cdkConnectedOverlayOpen", ctx.panelOpen)("cdkConnectedOverlayPositions", ctx._positions)("cdkConnectedOverlayMinWidth", ctx._triggerRect == null ? null : ctx._triggerRect.width)("cdkConnectedOverlayOffsetY", ctx._offsetY);
} }, directives: [ɵngcc7.CdkOverlayOrigin, ɵngcc8.NgSwitch, ɵngcc8.NgSwitchCase, ɵngcc7.CdkConnectedOverlay, ɵngcc8.NgSwitchDefault, ɵngcc8.NgClass], styles: [".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-table;cursor:pointer;position:relative;box-sizing:border-box}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.mat-select-value{display:table-cell;max-width:0;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{display:table-cell;vertical-align:middle}.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 transparent;border-right:5px solid transparent;border-top:5px solid;margin:0 4px}.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:transparent;-webkit-text-fill-color:transparent;transition:none;display:block}.mat-select-min-line:empty::before{content:\" \";white-space:pre;width:1px;display:inline-block;opacity:0}\n"], encapsulation: 2, data: { animation: [
matSelectAnimations.transformPanelWrap,
matSelectAnimations.transformPanel
] }, changeDetection: 0 });
MatSelect.propDecorators = {
options: [{ type: ContentChildren, args: [MatOption, { descendants: true },] }],
optionGroups: [{ type: ContentChildren, args: [MAT_OPTGROUP, { descendants: true },] }],
customTrigger: [{ type: ContentChild, args: [MAT_SELECT_TRIGGER,] }]
};
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(MatSelect, [{
type: Component,
args: [{
selector: 'mat-select',
exportAs: 'matSelect',
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",
inputs: ['disabled', 'disableRipple', 'tabIndex'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'role': 'combobox',
'aria-autocomplete': 'none',
// TODO(crisbeto): the value for aria-haspopup should be `listbox`, but currently it's difficult
// to sync into Google, because of an outdated automated a11y check which flags it as an invalid
// value. At some point we should try to switch it back to being `listbox`.
'aria-haspopup': 'true',
'class': 'mat-select',
'[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-describedby]': '_ariaDescribedby || null',
'[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',
'(keydown)': '_handleKeydown($event)',
'(focus)': '_onFocus()',
'(blur)': '_onBlur()'
},
animations: [
matSelectAnimations.transformPanelWrap,
matSelectAnimations.transformPanel
],
providers: [
{ provide: MatFormFieldControl, useExisting: MatSelect },
{ provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatSelect }
],
styles: [".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-table;cursor:pointer;position:relative;box-sizing:border-box}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.mat-select-value{display:table-cell;max-width:0;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{display:table-cell;vertical-align:middle}.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 transparent;border-right:5px solid transparent;border-top:5px solid;margin:0 4px}.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:transparent;-webkit-text-fill-color:transparent;transition:none;display:block}.mat-select-min-line:empty::before{content:\" \";white-space:pre;width:1px;display:inline-block;opacity:0}\n"]
}]
}], null, { options: [{
type: ContentChildren,
args: [MatOption, { descendants: true }]
}], optionGroups: [{
type: ContentChildren,
args: [MAT_OPTGROUP, { descendants: true }]
}], customTrigger: [{
type: ContentChild,
args: [MAT_SELECT_TRIGGER]
}] }); })();
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
class MatSelectModule {
}
MatSelectModule.ɵfac = function MatSelectModule_Factory(t) { return new (t || MatSelectModule)(); };
MatSelectModule.ɵmod = /*@__PURE__*/ ɵngcc0.ɵɵdefineNgModule({ type: MatSelectModule });
MatSelectModule.ɵinj = /*@__PURE__*/ ɵngcc0.ɵɵdefineInjector({ providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER], imports: [[
CommonModule,
OverlayModule,
MatOptionModule,
MatCommonModule,
], CdkScrollableModule,
MatFormFieldModule,
MatOptionModule,
MatCommonModule] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(MatSelectModule, [{
type: NgModule,
args: [{
imports: [
CommonModule,
OverlayModule,
MatOptionModule,
MatCommonModule,
],
exports: [
CdkScrollableModule,
MatFormFieldModule,
MatSelect,
MatSelectTrigger,
MatOptionModule,
MatCommonModule
],
declarations: [MatSelect, MatSelectTrigger],
providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER]
}]
}], null, null); })();
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && ɵngcc0.ɵɵsetNgModuleScope(MatSelectModule, { declarations: function () { return [MatSelect, MatSelectTrigger]; }, imports: function () { return [CommonModule,
OverlayModule,
MatOptionModule,
MatCommonModule]; }, exports: function () { return [CdkScrollableModule,
MatFormFieldModule, MatSelect, MatSelectTrigger, MatOptionModule,
MatCommonModule]; } }); })();
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Generated bundle index. Do not edit.
*/
export { 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 };
//# sourceMappingURL=select.js.map