{"version":3,"file":"select.js","sources":["../../packages/material/select/select-animations.js","../../packages/material/select/select-errors.js","../../packages/material/select/select.js","../../packages/material/select/select-module.js","../../packages/material/select/index.js"],"sourcesContent":["/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport { animate, state, style, transition, trigger, } from '@angular/animations';\n/**\n * This animation transforms the select's overlay panel on and off the page.\n *\n * When the panel is attached to the DOM, it expands its width by the amount of padding, scales it\n * up to 100% on the Y axis, fades in its border, and translates slightly up and to the\n * side to ensure the option text correctly overlaps the trigger text.\n *\n * When the panel is removed from the DOM, it simply fades out linearly.\n */\nexport const transformPanel = trigger('transformPanel', [\n state('showing', style({\n opacity: 1,\n minWidth: 'calc(100% + 32px)',\n transform: 'scaleY(1)'\n })),\n state('showing-multiple', style({\n opacity: 1,\n minWidth: 'calc(100% + 64px)',\n transform: 'scaleY(1)'\n })),\n transition('void => *', [\n style({\n opacity: 0,\n minWidth: '100%',\n transform: 'scaleY(0)'\n }),\n animate('150ms cubic-bezier(0.25, 0.8, 0.25, 1)')\n ]),\n transition('* => void', [\n animate('250ms 100ms linear', style({ opacity: 0 }))\n ])\n]);\n/**\n * This animation fades in the background color and text content of the\n * select's options. It is time delayed to occur 100ms after the overlay\n * panel has transformed in.\n */\nexport const fadeInContent = trigger('fadeInContent', [\n state('showing', style({ opacity: 1 })),\n transition('void => showing', [\n style({ opacity: 0 }),\n animate('150ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)')\n ])\n]);\n//# sourceMappingURL=select-animations.js.map","/**\n * Returns an exception to be thrown when attempting to change a select's `multiple` option\n * after initialization.\n * \\@docs-private\n * @return {?}\n */\nexport function getMatSelectDynamicMultipleError() {\n return Error('Cannot change `multiple` mode of select after initialization.');\n}\n/**\n * Returns an exception to be thrown when attempting to assign a non-array value to a select\n * in `multiple` mode. Note that `undefined` and `null` are still valid values to allow for\n * resetting the value.\n * \\@docs-private\n * @return {?}\n */\nexport function getMatSelectNonArrayValueError() {\n return Error('Cannot assign truthy non-array value to select in `multiple` mode.');\n}\n/**\n * Returns an exception to be thrown when assigning a non-function value to the comparator\n * used to determine if a value corresponds to an option. Note that whether the function\n * actually takes two values and returns a boolean is not checked.\n * @return {?}\n */\nexport function getMatSelectNonFunctionValueError() {\n return Error('Cannot assign a non-function value to `compareWith`.');\n}\n//# sourceMappingURL=select-errors.js.map","/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport { ActiveDescendantKeyManager } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { SelectionModel } from '@angular/cdk/collections';\nimport { DOWN_ARROW, END, ENTER, HOME, SPACE, UP_ARROW } from '@angular/cdk/keycodes';\nimport { ConnectedOverlayDirective, Overlay, ViewportRuler, } from '@angular/cdk/overlay';\nimport { filter, first, startWith } from '@angular/cdk/rxjs';\nimport { Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, ElementRef, EventEmitter, Inject, InjectionToken, Input, isDevMode, NgZone, Optional, Output, Renderer2, Self, ViewChild, ViewEncapsulation, } from '@angular/core';\nimport { FormGroupDirective, NgControl, NgForm } from '@angular/forms';\nimport { MatOptgroup, MatOption, mixinDisabled, mixinTabIndex, ErrorStateMatcher, } from '@angular/material/core';\nimport { MatFormField, MatFormFieldControl } from '@angular/material/form-field';\nimport { merge } from 'rxjs/observable/merge';\nimport { Subject } from 'rxjs/Subject';\nimport { Subscription } from 'rxjs/Subscription';\nimport { fadeInContent, transformPanel } from './select-animations';\nimport { getMatSelectDynamicMultipleError, getMatSelectNonArrayValueError, getMatSelectNonFunctionValueError, } from './select-errors';\nlet /** @type {?} */ nextUniqueId = 0;\n/**\n * The max height of the select's overlay panel\n */\nexport const SELECT_PANEL_MAX_HEIGHT = 256;\n/**\n * The panel's padding on the x-axis\n */\nexport const SELECT_PANEL_PADDING_X = 16;\n/**\n * The panel's x axis padding if it is indented (e.g. there is an option group).\n */\nexport const SELECT_PANEL_INDENT_PADDING_X = SELECT_PANEL_PADDING_X * 2;\n/**\n * The height of the select items in `em` units.\n */\nexport const SELECT_ITEM_HEIGHT_EM = 3;\n/**\n * Distance between the panel edge and the option text in\n * multi-selection mode.\n *\n * (SELECT_PANEL_PADDING_X * 1.5) + 20 = 44\n * The padding is multiplied by 1.5 because the checkbox's margin is half the padding.\n * The checkbox width is 20px.\n */\nexport const SELECT_MULTIPLE_PANEL_PADDING_X = SELECT_PANEL_PADDING_X * 1.5 + 20;\n/**\n * The select panel will only \"fit\" inside the viewport if it is positioned at\n * this value or more away from the viewport boundary.\n */\nexport const SELECT_PANEL_VIEWPORT_PADDING = 8;\n/**\n * Injection token that determines the scroll handling while a select is open.\n */\nexport const MAT_SELECT_SCROLL_STRATEGY = new InjectionToken('mat-select-scroll-strategy');\n/**\n * \\@docs-private\n * @param {?} overlay\n * @return {?}\n */\nexport function MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) {\n return () => overlay.scrollStrategies.reposition();\n}\n/**\n * \\@docs-private\n */\nexport const MAT_SELECT_SCROLL_STRATEGY_PROVIDER = {\n provide: MAT_SELECT_SCROLL_STRATEGY,\n deps: [Overlay],\n useFactory: MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY,\n};\n/**\n * Change event object that is emitted when the select value has changed.\n */\nexport class MatSelectChange {\n /**\n * @param {?} source\n * @param {?} value\n */\n constructor(source, value) {\n this.source = source;\n this.value = value;\n }\n}\nfunction MatSelectChange_tsickle_Closure_declarations() {\n /** @type {?} */\n MatSelectChange.prototype.source;\n /** @type {?} */\n MatSelectChange.prototype.value;\n}\n/**\n * \\@docs-private\n */\nexport class MatSelectBase {\n /**\n * @param {?} _renderer\n * @param {?} _elementRef\n */\n constructor(_renderer, _elementRef) {\n this._renderer = _renderer;\n this._elementRef = _elementRef;\n }\n}\nfunction MatSelectBase_tsickle_Closure_declarations() {\n /** @type {?} */\n MatSelectBase.prototype._renderer;\n /** @type {?} */\n MatSelectBase.prototype._elementRef;\n}\nexport const /** @type {?} */ _MatSelectMixinBase = mixinTabIndex(mixinDisabled(MatSelectBase));\n/**\n * Allows the user to customize the trigger that is displayed when the select has a value.\n */\nexport class MatSelectTrigger {\n}\nMatSelectTrigger.decorators = [\n { type: Directive, args: [{\n selector: 'mat-select-trigger'\n },] },\n];\n/**\n * @nocollapse\n */\nMatSelectTrigger.ctorParameters = () => [];\nfunction MatSelectTrigger_tsickle_Closure_declarations() {\n /** @type {?} */\n MatSelectTrigger.decorators;\n /**\n * @nocollapse\n * @type {?}\n */\n MatSelectTrigger.ctorParameters;\n}\nexport class MatSelect extends _MatSelectMixinBase {\n /**\n * @param {?} _viewportRuler\n * @param {?} _changeDetectorRef\n * @param {?} _ngZone\n * @param {?} _defaultErrorStateMatcher\n * @param {?} renderer\n * @param {?} elementRef\n * @param {?} _dir\n * @param {?} _parentForm\n * @param {?} _parentFormGroup\n * @param {?} _parentFormField\n * @param {?} ngControl\n * @param {?} tabIndex\n * @param {?} _scrollStrategyFactory\n */\n constructor(_viewportRuler, _changeDetectorRef, _ngZone, _defaultErrorStateMatcher, renderer, elementRef, _dir, _parentForm, _parentFormGroup, _parentFormField, ngControl, tabIndex, _scrollStrategyFactory) {\n super(renderer, elementRef);\n this._viewportRuler = _viewportRuler;\n this._changeDetectorRef = _changeDetectorRef;\n this._ngZone = _ngZone;\n this._defaultErrorStateMatcher = _defaultErrorStateMatcher;\n this._dir = _dir;\n this._parentForm = _parentForm;\n this._parentFormGroup = _parentFormGroup;\n this._parentFormField = _parentFormField;\n this.ngControl = ngControl;\n this._scrollStrategyFactory = _scrollStrategyFactory;\n /**\n * Whether or not the overlay panel is open.\n */\n this._panelOpen = false;\n /**\n * Subscriptions to option events.\n */\n this._optionSubscription = Subscription.EMPTY;\n /**\n * Subscription to changes in the option list.\n */\n this._changeSubscription = Subscription.EMPTY;\n /**\n * Subscription to tab events while overlay is focused.\n */\n this._tabSubscription = Subscription.EMPTY;\n /**\n * Whether filling out the select is required in the form.\n */\n this._required = false;\n /**\n * The scroll position of the overlay panel, calculated to center the selected option.\n */\n this._scrollTop = 0;\n /**\n * Whether the component is in multiple selection mode.\n */\n this._multiple = false;\n /**\n * Comparison function to specify which option is displayed. Defaults to object equality.\n */\n this._compareWith = (o1, o2) => o1 === o2;\n /**\n * Unique id for this input.\n */\n this._uid = `mat-select-${nextUniqueId++}`;\n /**\n * The cached font-size of the trigger element.\n */\n this._triggerFontSize = 0;\n /**\n * View -> model callback called when value changes\n */\n this._onChange = () => { };\n /**\n * View -> model callback called when select has been touched\n */\n this._onTouched = () => { };\n /**\n * The IDs of child options to be passed to the aria-owns attribute.\n */\n this._optionIds = '';\n /**\n * The value of the select panel's transform-origin property.\n */\n this._transformOrigin = 'top';\n /**\n * Whether the panel's animation is done.\n */\n this._panelDoneAnimating = false;\n /**\n * Strategy that will be used to handle scrolling while the select panel is open.\n */\n this._scrollStrategy = this._scrollStrategyFactory();\n /**\n * The y-offset of the overlay panel in relation to the trigger's top start corner.\n * This must be adjusted to align the selected option text over the trigger text.\n * when the panel opens. Will change based on the y-position of the selected option.\n */\n this._offsetY = 0;\n /**\n * This position config ensures that the top \"start\" corner of the overlay\n * is aligned with with the top \"start\" of the origin by default (overlapping\n * the trigger completely). If the panel cannot fit below the trigger, it\n * will fall back to a position above the trigger.\n */\n this._positions = [\n {\n originX: 'start',\n originY: 'top',\n overlayX: 'start',\n overlayY: 'top',\n },\n {\n originX: 'start',\n originY: 'bottom',\n overlayX: 'start',\n overlayY: 'bottom',\n },\n ];\n /**\n * Stream that emits whenever the state of the select changes such that the wrapping\n * `MatFormField` needs to run change detection.\n */\n this.stateChanges = new Subject();\n /**\n * Whether the select is focused.\n */\n this.focused = false;\n /**\n * A name for this control that can be used by `mat-form-field`.\n */\n this.controlType = 'mat-select';\n this._disableRipple = false;\n /**\n * Aria label of the select. If not specified, the placeholder will be used as label.\n */\n this.ariaLabel = '';\n /**\n * Event emitted when the select has been opened.\n */\n this.onOpen = new EventEmitter();\n /**\n * Event emitted when the select has been closed.\n */\n this.onClose = new EventEmitter();\n /**\n * Event emitted when the selected value has been changed by the user.\n */\n this.change = new EventEmitter();\n /**\n * Event that emits whenever the raw value of the select changes. This is here primarily\n * to facilitate the two-way binding for the `value` input.\n * \\@docs-private\n */\n this.valueChange = new EventEmitter();\n if (this.ngControl) {\n this.ngControl.valueAccessor = this;\n }\n this.tabIndex = parseInt(tabIndex) || 0;\n // Force setter to be called in case id was not specified.\n this.id = this.id;\n }\n /**\n * Placeholder to be shown if no value has been selected.\n * @return {?}\n */\n get placeholder() { return this._placeholder; }\n /**\n * @param {?} value\n * @return {?}\n */\n set placeholder(value) {\n this._placeholder = value;\n this.stateChanges.next();\n }\n /**\n * Whether the component is required.\n * @return {?}\n */\n get required() { return this._required; }\n /**\n * @param {?} value\n * @return {?}\n */\n set required(value) {\n this._required = coerceBooleanProperty(value);\n this.stateChanges.next();\n }\n /**\n * Whether the user should be allowed to select multiple options.\n * @return {?}\n */\n get multiple() { return this._multiple; }\n /**\n * @param {?} value\n * @return {?}\n */\n set multiple(value) {\n if (this._selectionModel) {\n throw getMatSelectDynamicMultipleError();\n }\n this._multiple = coerceBooleanProperty(value);\n }\n /**\n * A function to compare the option values with the selected values. The first argument\n * is a value from an option. The second is a value from the selection. A boolean\n * should be returned.\n * @return {?}\n */\n get compareWith() { return this._compareWith; }\n /**\n * @param {?} fn\n * @return {?}\n */\n set compareWith(fn) {\n if (typeof fn !== 'function') {\n throw getMatSelectNonFunctionValueError();\n }\n this._compareWith = fn;\n if (this._selectionModel) {\n // A different comparator means the selection could change.\n this._initializeSelection();\n }\n }\n /**\n * Value of the select control.\n * @return {?}\n */\n get value() { return this._value; }\n /**\n * @param {?} newValue\n * @return {?}\n */\n set value(newValue) {\n if (newValue !== this._value) {\n this.writeValue(newValue);\n this._value = newValue;\n }\n }\n /**\n * Whether ripples for all options in the select are disabled.\n * @return {?}\n */\n get disableRipple() { return this._disableRipple; }\n /**\n * @param {?} value\n * @return {?}\n */\n set disableRipple(value) {\n this._disableRipple = coerceBooleanProperty(value);\n this._setOptionDisableRipple();\n }\n /**\n * Unique id of the element.\n * @return {?}\n */\n get id() { return this._id; }\n /**\n * @param {?} value\n * @return {?}\n */\n set id(value) {\n this._id = value || this._uid;\n this.stateChanges.next();\n }\n /**\n * Combined stream of all of the child options' change events.\n * @return {?}\n */\n get optionSelectionChanges() {\n return merge(...this.options.map(option => option.onSelectionChange));\n }\n /**\n * @return {?}\n */\n ngOnInit() {\n this._selectionModel = new SelectionModel(this.multiple, undefined, false);\n this.stateChanges.next();\n }\n /**\n * @return {?}\n */\n ngAfterContentInit() {\n this._initKeyManager();\n this._changeSubscription = startWith.call(this.options.changes, null).subscribe(() => {\n this._resetOptions();\n this._initializeSelection();\n });\n }\n /**\n * @return {?}\n */\n ngOnDestroy() {\n this._dropSubscriptions();\n this._changeSubscription.unsubscribe();\n this._tabSubscription.unsubscribe();\n }\n /**\n * Toggles the overlay panel open or closed.\n * @return {?}\n */\n toggle() {\n this.panelOpen ? this.close() : this.open();\n }\n /**\n * Opens the overlay panel.\n * @return {?}\n */\n open() {\n if (this.disabled || !this.options.length) {\n return;\n }\n this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();\n // Note: The computed font-size will be a string pixel value (e.g. \"16px\").\n // `parseInt` ignores the trailing 'px' and converts this to a number.\n this._triggerFontSize = parseInt(getComputedStyle(this.trigger.nativeElement)['font-size']);\n this._calculateOverlayPosition();\n this._highlightCorrectOption();\n this._panelOpen = true;\n this._changeDetectorRef.markForCheck();\n // Set the font size on the panel element once it exists.\n first.call(this._ngZone.onStable).subscribe(() => {\n if (this._triggerFontSize && this.overlayDir.overlayRef &&\n this.overlayDir.overlayRef.overlayElement) {\n this.overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`;\n }\n });\n }\n /**\n * Closes the overlay panel and focuses the host element.\n * @return {?}\n */\n close() {\n if (this._panelOpen) {\n this._panelOpen = false;\n this._changeDetectorRef.markForCheck();\n this.focus();\n }\n }\n /**\n * Sets the select's value. Part of the ControlValueAccessor interface\n * required to integrate with Angular's core forms API.\n *\n * @param {?} value New value to be written to the model.\n * @return {?}\n */\n writeValue(value) {\n if (this.options) {\n this._setSelectionByValue(value);\n }\n }\n /**\n * Saves a callback function to be invoked when the select's value\n * changes from user input. Part of the ControlValueAccessor interface\n * required to integrate with Angular's core forms API.\n *\n * @param {?} fn Callback to be triggered when the value changes.\n * @return {?}\n */\n registerOnChange(fn) {\n this._onChange = fn;\n }\n /**\n * Saves a callback function to be invoked when the select is blurred\n * by the user. Part of the ControlValueAccessor interface required\n * to integrate with Angular's core forms API.\n *\n * @param {?} fn Callback to be triggered when the component has been touched.\n * @return {?}\n */\n registerOnTouched(fn) {\n this._onTouched = fn;\n }\n /**\n * Disables the select. Part of the ControlValueAccessor interface required\n * to integrate with Angular's core forms API.\n *\n * @param {?} isDisabled Sets whether the component is disabled.\n * @return {?}\n */\n setDisabledState(isDisabled) {\n this.disabled = isDisabled;\n this._changeDetectorRef.markForCheck();\n this.stateChanges.next();\n }\n /**\n * Whether or not the overlay panel is open.\n * @return {?}\n */\n get panelOpen() {\n return this._panelOpen;\n }\n /**\n * The currently selected option.\n * @return {?}\n */\n get selected() {\n return this.multiple ? this._selectionModel.selected : this._selectionModel.selected[0];\n }\n /**\n * The value displayed in the trigger.\n * @return {?}\n */\n get triggerValue() {\n if (!this._selectionModel || this._selectionModel.isEmpty()) {\n return '';\n }\n if (this._multiple) {\n const /** @type {?} */ selectedOptions = this._selectionModel.selected.map(option => option.viewValue);\n if (this._isRtl()) {\n selectedOptions.reverse();\n }\n // TODO(crisbeto): delimiter should be configurable for proper localization.\n return selectedOptions.join(', ');\n }\n return this._selectionModel.selected[0].viewValue;\n }\n /**\n * Whether the element is in RTL mode.\n * @return {?}\n */\n _isRtl() {\n return this._dir ? this._dir.value === 'rtl' : false;\n }\n /**\n * Handles all keydown events on the select.\n * @param {?} event\n * @return {?}\n */\n _handleKeydown(event) {\n if (!this.disabled) {\n this.panelOpen ? this._handleOpenKeydown(event) : this._handleClosedKeydown(event);\n }\n }\n /**\n * Handles keyboard events while the select is closed.\n * @param {?} event\n * @return {?}\n */\n _handleClosedKeydown(event) {\n if (event.keyCode === ENTER || event.keyCode === SPACE) {\n event.preventDefault(); // prevents the page from scrolling down when pressing space\n this.open();\n }\n else if (event.keyCode === UP_ARROW || event.keyCode === DOWN_ARROW) {\n this._handleClosedArrowKey(event);\n }\n }\n /**\n * Handles keyboard events when the selected is open.\n * @param {?} event\n * @return {?}\n */\n _handleOpenKeydown(event) {\n const /** @type {?} */ keyCode = event.keyCode;\n if (keyCode === HOME || keyCode === END) {\n event.preventDefault();\n keyCode === HOME ? this._keyManager.setFirstItemActive() :\n this._keyManager.setLastItemActive();\n }\n else if ((keyCode === ENTER || keyCode === SPACE) && this._keyManager.activeItem) {\n event.preventDefault();\n this._keyManager.activeItem._selectViaInteraction();\n }\n else {\n this._keyManager.onKeydown(event);\n // TODO(crisbeto): get rid of the Promise.resolve when #6441 gets in.\n Promise.resolve().then(() => {\n if (this.panelOpen) {\n this._scrollActiveOptionIntoView();\n }\n });\n }\n }\n /**\n * When the panel element is finished transforming in (though not fading in), it\n * emits an event and focuses an option if the panel is open.\n * @return {?}\n */\n _onPanelDone() {\n if (this.panelOpen) {\n this._scrollTop = 0;\n this.onOpen.emit();\n }\n else {\n this.onClose.emit();\n this._panelDoneAnimating = false;\n this.overlayDir.offsetX = 0;\n this._changeDetectorRef.markForCheck();\n }\n }\n /**\n * When the panel content is done fading in, the _panelDoneAnimating property is\n * set so the proper class can be added to the panel.\n * @return {?}\n */\n _onFadeInDone() {\n this._panelDoneAnimating = this.panelOpen;\n this.panel.nativeElement.focus();\n this._changeDetectorRef.markForCheck();\n }\n /**\n * @return {?}\n */\n _onFocus() {\n if (!this.disabled) {\n this.focused = true;\n this.stateChanges.next();\n }\n }\n /**\n * Calls the touched callback only if the panel is closed. Otherwise, the trigger will\n * \"blur\" to the panel when it opens, causing a false positive.\n * @return {?}\n */\n _onBlur() {\n if (!this.disabled && !this.panelOpen) {\n this.focused = false;\n this._onTouched();\n this._changeDetectorRef.markForCheck();\n this.stateChanges.next();\n }\n }\n /**\n * Callback that is invoked when the overlay panel has been attached.\n * @return {?}\n */\n _onAttached() {\n this._changeDetectorRef.detectChanges();\n this._calculateOverlayOffsetX();\n this.panel.nativeElement.scrollTop = this._scrollTop;\n }\n /**\n * Returns the theme to be used on the panel.\n * @return {?}\n */\n _getPanelTheme() {\n return this._parentFormField ? `mat-${this._parentFormField.color}` : '';\n }\n /**\n * Whether the select has a value.\n * @return {?}\n */\n get empty() {\n return !this._selectionModel || this._selectionModel.isEmpty();\n }\n /**\n * Whether the select is in an error state.\n * @return {?}\n */\n get errorState() {\n const /** @type {?} */ parent = this._parentFormGroup || this._parentForm;\n const /** @type {?} */ matcher = this.errorStateMatcher || this._defaultErrorStateMatcher;\n const /** @type {?} */ control = this.ngControl ? (this.ngControl.control) : null;\n return matcher.isErrorState(control, parent);\n }\n /**\n * @return {?}\n */\n _initializeSelection() {\n // Defer setting the value in order to avoid the \"Expression\n // has changed after it was checked\" errors from Angular.\n Promise.resolve().then(() => {\n this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);\n });\n }\n /**\n * Sets the selected option based on a value. If no option can be\n * found with the designated value, the select trigger is cleared.\n * @param {?} value\n * @param {?=} isUserInput\n * @return {?}\n */\n _setSelectionByValue(value, isUserInput = false) {\n const /** @type {?} */ isArray = Array.isArray(value);\n if (this.multiple && value && !isArray) {\n throw getMatSelectNonArrayValueError();\n }\n this._clearSelection();\n if (isArray) {\n value.forEach((currentValue) => this._selectValue(currentValue, isUserInput));\n this._sortValues();\n }\n else {\n const /** @type {?} */ correspondingOption = this._selectValue(value, isUserInput);\n // Shift focus to the active item. Note that we shouldn't do this in multiple\n // mode, because we don't know what option the user interacted with last.\n if (correspondingOption) {\n this._keyManager.setActiveItem(this.options.toArray().indexOf(correspondingOption));\n }\n }\n this._changeDetectorRef.markForCheck();\n }\n /**\n * Finds and selects and option based on its value.\n * @param {?} value\n * @param {?=} isUserInput\n * @return {?} Option that has the corresponding value.\n */\n _selectValue(value, isUserInput = false) {\n const /** @type {?} */ correspondingOption = this.options.find((option) => {\n try {\n // Treat null as a special reset value.\n return option.value != null && this._compareWith(option.value, value);\n }\n catch (error) {\n if (isDevMode()) {\n // Notify developers of errors in their comparator.\n console.warn(error);\n }\n return false;\n }\n });\n if (correspondingOption) {\n isUserInput ? correspondingOption._selectViaInteraction() : correspondingOption.select();\n this._selectionModel.select(correspondingOption);\n this.stateChanges.next();\n }\n return correspondingOption;\n }\n /**\n * Clears the select trigger and deselects every option in the list.\n * @param {?=} skip Option that should not be deselected.\n * @return {?}\n */\n _clearSelection(skip) {\n this._selectionModel.clear();\n this.options.forEach(option => {\n if (option !== skip) {\n option.deselect();\n }\n });\n this.stateChanges.next();\n }\n /**\n * Sets up a key manager to listen to keyboard events on the overlay panel.\n * @return {?}\n */\n _initKeyManager() {\n this._keyManager = new ActiveDescendantKeyManager(this.options).withTypeAhead();\n this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close());\n }\n /**\n * Drops current option subscriptions and IDs and resets from scratch.\n * @return {?}\n */\n _resetOptions() {\n this._dropSubscriptions();\n this._listenToOptions();\n this._setOptionIds();\n this._setOptionMultiple();\n this._setOptionDisableRipple();\n }\n /**\n * Listens to user-generated selection events on each option.\n * @return {?}\n */\n _listenToOptions() {\n this._optionSubscription = filter.call(this.optionSelectionChanges, event => event.isUserInput).subscribe(event => {\n this._onSelect(event.source);\n if (!this.multiple) {\n this.close();\n }\n });\n }\n /**\n * Invoked when an option is clicked.\n * @param {?} option\n * @return {?}\n */\n _onSelect(option) {\n const /** @type {?} */ wasSelected = this._selectionModel.isSelected(option);\n // TODO(crisbeto): handle blank/null options inside multi-select.\n if (this.multiple) {\n this._selectionModel.toggle(option);\n this.stateChanges.next();\n wasSelected ? option.deselect() : option.select();\n this._sortValues();\n }\n else {\n this._clearSelection(option.value == null ? undefined : option);\n if (option.value == null) {\n this._propagateChanges(option.value);\n }\n else {\n this._selectionModel.select(option);\n this.stateChanges.next();\n }\n }\n if (wasSelected !== this._selectionModel.isSelected(option)) {\n this._propagateChanges();\n }\n }\n /**\n * Sorts the model values, ensuring that they keep the same\n * order that they have in the panel.\n * @return {?}\n */\n _sortValues() {\n if (this._multiple) {\n this._selectionModel.clear();\n this.options.forEach(option => {\n if (option.selected) {\n this._selectionModel.select(option);\n }\n });\n this.stateChanges.next();\n }\n }\n /**\n * Unsubscribes from all option subscriptions.\n * @return {?}\n */\n _dropSubscriptions() {\n this._optionSubscription.unsubscribe();\n }\n /**\n * Emits change event to set the model value.\n * @param {?=} fallbackValue\n * @return {?}\n */\n _propagateChanges(fallbackValue) {\n let /** @type {?} */ valueToEmit = null;\n if (Array.isArray(this.selected)) {\n valueToEmit = this.selected.map(option => option.value);\n }\n else {\n valueToEmit = this.selected ? this.selected.value : fallbackValue;\n }\n this._value = valueToEmit;\n this._onChange(valueToEmit);\n this.change.emit(new MatSelectChange(this, valueToEmit));\n this.valueChange.emit(valueToEmit);\n this._changeDetectorRef.markForCheck();\n }\n /**\n * Records option IDs to pass to the aria-owns property.\n * @return {?}\n */\n _setOptionIds() {\n this._optionIds = this.options.map(option => option.id).join(' ');\n }\n /**\n * Sets the `multiple` property on each option. The promise is necessary\n * in order to avoid Angular errors when modifying the property after init.\n * @return {?}\n */\n _setOptionMultiple() {\n if (this.multiple) {\n Promise.resolve(null).then(() => {\n this.options.forEach(option => option.multiple = this.multiple);\n });\n }\n }\n /**\n * Sets the `disableRipple` property on each option.\n * @return {?}\n */\n _setOptionDisableRipple() {\n if (this.options) {\n this.options.forEach(option => option.disableRipple = this.disableRipple);\n }\n }\n /**\n * Highlights the selected item. If no option is selected, it will highlight\n * the first item instead.\n * @return {?}\n */\n _highlightCorrectOption() {\n if (this._selectionModel.isEmpty()) {\n this._keyManager.setFirstItemActive();\n }\n else {\n this._keyManager.setActiveItem(/** @type {?} */ ((this._getOptionIndex(this._selectionModel.selected[0]))));\n }\n }\n /**\n * Scrolls the active option into view.\n * @return {?}\n */\n _scrollActiveOptionIntoView() {\n const /** @type {?} */ itemHeight = this._getItemHeight();\n const /** @type {?} */ activeOptionIndex = this._keyManager.activeItemIndex || 0;\n const /** @type {?} */ labelCount = MatOption.countGroupLabelsBeforeOption(activeOptionIndex, this.options, this.optionGroups);\n const /** @type {?} */ scrollOffset = (activeOptionIndex + labelCount) * itemHeight;\n const /** @type {?} */ panelTop = this.panel.nativeElement.scrollTop;\n if (scrollOffset < panelTop) {\n this.panel.nativeElement.scrollTop = scrollOffset;\n }\n else if (scrollOffset + itemHeight > panelTop + SELECT_PANEL_MAX_HEIGHT) {\n this.panel.nativeElement.scrollTop =\n Math.max(0, scrollOffset - SELECT_PANEL_MAX_HEIGHT + itemHeight);\n }\n }\n /**\n * Focuses the select element.\n * @return {?}\n */\n focus() {\n this._elementRef.nativeElement.focus();\n }\n /**\n * Gets the index of the provided option in the option list.\n * @param {?} option\n * @return {?}\n */\n _getOptionIndex(option) {\n return this.options.reduce((result, current, index) => {\n return result === undefined ? (option === current ? index : undefined) : result;\n }, undefined);\n }\n /**\n * Calculates the scroll position and x- and y-offsets of the overlay panel.\n * @return {?}\n */\n _calculateOverlayPosition() {\n const /** @type {?} */ itemHeight = this._getItemHeight();\n const /** @type {?} */ items = this._getItemCount();\n const /** @type {?} */ panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);\n const /** @type {?} */ scrollContainerHeight = items * itemHeight;\n // The farthest the panel can be scrolled before it hits the bottom\n const /** @type {?} */ maxScroll = scrollContainerHeight - panelHeight;\n // If no value is selected we open the popup to the first item.\n let /** @type {?} */ selectedOptionOffset = this.empty ? 0 : ((this._getOptionIndex(this._selectionModel.selected[0])));\n selectedOptionOffset += MatOption.countGroupLabelsBeforeOption(selectedOptionOffset, this.options, this.optionGroups);\n // We must maintain a scroll buffer so the selected option will be scrolled to the\n // center of the overlay panel rather than the top.\n const /** @type {?} */ scrollBuffer = panelHeight / 2;\n this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);\n this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);\n this._checkOverlayWithinViewport(maxScroll);\n }\n /**\n * Calculates the scroll position of the select's overlay panel.\n *\n * Attempts to center the selected option in the panel. If the option is\n * too high or too low in the panel to be scrolled to the center, it clamps the\n * scroll position to the min or max scroll positions respectively.\n * @param {?} selectedIndex\n * @param {?} scrollBuffer\n * @param {?} maxScroll\n * @return {?}\n */\n _calculateOverlayScroll(selectedIndex, scrollBuffer, maxScroll) {\n const /** @type {?} */ itemHeight = this._getItemHeight();\n const /** @type {?} */ optionOffsetFromScrollTop = itemHeight * selectedIndex;\n const /** @type {?} */ halfOptionHeight = itemHeight / 2;\n // Starts at the optionOffsetFromScrollTop, which scrolls the option to the top of the\n // scroll container, then subtracts the scroll buffer to scroll the option down to\n // the center of the overlay panel. Half the option height must be re-added to the\n // scrollTop so the option is centered based on its middle, not its top edge.\n const /** @type {?} */ optimalScrollPosition = optionOffsetFromScrollTop - scrollBuffer + halfOptionHeight;\n return Math.min(Math.max(0, optimalScrollPosition), maxScroll);\n }\n /**\n * Returns the aria-label of the select component.\n * @return {?}\n */\n get _ariaLabel() {\n // If an ariaLabelledby value has been set, the select should not overwrite the\n // `aria-labelledby` value by setting the ariaLabel to the placeholder.\n return this.ariaLabelledby ? null : this.ariaLabel || this.placeholder;\n }\n /**\n * Determines the `aria-activedescendant` to be set on the host.\n * @return {?}\n */\n _getAriaActiveDescendant() {\n if (this.panelOpen && this._keyManager && this._keyManager.activeItem) {\n return this._keyManager.activeItem.id;\n }\n return null;\n }\n /**\n * Sets the x-offset of the overlay panel in relation to the trigger's top start corner.\n * This must be adjusted to align the selected option text over the trigger text when\n * the panel opens. Will change based on LTR or RTL text direction. Note that the offset\n * can't be calculated until the panel has been attached, because we need to know the\n * content width in order to constrain the panel within the viewport.\n * @return {?}\n */\n _calculateOverlayOffsetX() {\n const /** @type {?} */ overlayRect = this.overlayDir.overlayRef.overlayElement.getBoundingClientRect();\n const /** @type {?} */ viewportRect = this._viewportRuler.getViewportRect();\n const /** @type {?} */ isRtl = this._isRtl();\n const /** @type {?} */ paddingWidth = this.multiple ? SELECT_MULTIPLE_PANEL_PADDING_X + SELECT_PANEL_PADDING_X :\n SELECT_PANEL_PADDING_X * 2;\n let /** @type {?} */ offsetX;\n // Adjust the offset, depending on the option padding.\n if (this.multiple) {\n offsetX = SELECT_MULTIPLE_PANEL_PADDING_X;\n }\n else {\n let /** @type {?} */ selected = this._selectionModel.selected[0] || this.options.first;\n offsetX = selected && selected.group ? SELECT_PANEL_INDENT_PADDING_X : SELECT_PANEL_PADDING_X;\n }\n // Invert the offset in LTR.\n if (!isRtl) {\n offsetX *= -1;\n }\n // Determine how much the select overflows on each side.\n const /** @type {?} */ leftOverflow = 0 - (overlayRect.left + offsetX - (isRtl ? paddingWidth : 0));\n const /** @type {?} */ rightOverflow = overlayRect.right + offsetX - viewportRect.width\n + (isRtl ? 0 : paddingWidth);\n // If the element overflows on either side, reduce the offset to allow it to fit.\n if (leftOverflow > 0) {\n offsetX += leftOverflow + SELECT_PANEL_VIEWPORT_PADDING;\n }\n else if (rightOverflow > 0) {\n offsetX -= rightOverflow + SELECT_PANEL_VIEWPORT_PADDING;\n }\n // Set the offset directly in order to avoid having to go through change detection and\n // potentially triggering \"changed after it was checked\" errors.\n this.overlayDir.offsetX = offsetX;\n this.overlayDir.overlayRef.updatePosition();\n }\n /**\n * Calculates the y-offset of the select's overlay panel in relation to the\n * top start corner of the trigger. It has to be adjusted in order for the\n * selected option to be aligned over the trigger when the panel opens.\n * @param {?} selectedIndex\n * @param {?} scrollBuffer\n * @param {?} maxScroll\n * @return {?}\n */\n _calculateOverlayOffsetY(selectedIndex, scrollBuffer, maxScroll) {\n const /** @type {?} */ itemHeight = this._getItemHeight();\n const /** @type {?} */ optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;\n const /** @type {?} */ maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);\n let /** @type {?} */ optionOffsetFromPanelTop;\n if (this._scrollTop === 0) {\n optionOffsetFromPanelTop = selectedIndex * itemHeight;\n }\n else if (this._scrollTop === maxScroll) {\n const /** @type {?} */ firstDisplayedIndex = this._getItemCount() - maxOptionsDisplayed;\n const /** @type {?} */ selectedDisplayIndex = selectedIndex - firstDisplayedIndex;\n // The first item is partially out of the viewport. Therefore we need to calculate what\n // portion of it is shown in the viewport and account for it in our offset.\n let /** @type {?} */ partialItemHeight = itemHeight - (this._getItemCount() * itemHeight - SELECT_PANEL_MAX_HEIGHT) % itemHeight;\n // Because the panel height is longer than the height of the options alone,\n // there is always extra padding at the top or bottom of the panel. When\n // scrolled to the very bottom, this padding is at the top of the panel and\n // must be added to the offset.\n optionOffsetFromPanelTop = selectedDisplayIndex * itemHeight + partialItemHeight;\n }\n else {\n // If the option was scrolled to the middle of the panel using a scroll buffer,\n // its offset will be the scroll buffer minus the half height that was added to\n // center it.\n optionOffsetFromPanelTop = scrollBuffer - itemHeight / 2;\n }\n // The final offset is the option's offset from the top, adjusted for the height\n // difference, multiplied by -1 to ensure that the overlay moves in the correct\n // direction up the page.\n return optionOffsetFromPanelTop * -1 - optionHeightAdjustment;\n }\n /**\n * Checks that the attempted overlay position will fit within the viewport.\n * If it will not fit, tries to adjust the scroll position and the associated\n * y-offset so the panel can open fully on-screen. If it still won't fit,\n * sets the offset back to 0 to allow the fallback position to take over.\n * @param {?} maxScroll\n * @return {?}\n */\n _checkOverlayWithinViewport(maxScroll) {\n const /** @type {?} */ itemHeight = this._getItemHeight();\n const /** @type {?} */ viewportRect = this._viewportRuler.getViewportRect();\n const /** @type {?} */ topSpaceAvailable = this._triggerRect.top - SELECT_PANEL_VIEWPORT_PADDING;\n const /** @type {?} */ bottomSpaceAvailable = viewportRect.height - this._triggerRect.bottom - SELECT_PANEL_VIEWPORT_PADDING;\n const /** @type {?} */ panelHeightTop = Math.abs(this._offsetY);\n const /** @type {?} */ totalPanelHeight = Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);\n const /** @type {?} */ panelHeightBottom = totalPanelHeight - panelHeightTop - this._triggerRect.height;\n if (panelHeightBottom > bottomSpaceAvailable) {\n this._adjustPanelUp(panelHeightBottom, bottomSpaceAvailable);\n }\n else if (panelHeightTop > topSpaceAvailable) {\n this._adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll);\n }\n else {\n this._transformOrigin = this._getOriginBasedOnOption();\n }\n }\n /**\n * Adjusts the overlay panel up to fit in the viewport.\n * @param {?} panelHeightBottom\n * @param {?} bottomSpaceAvailable\n * @return {?}\n */\n _adjustPanelUp(panelHeightBottom, bottomSpaceAvailable) {\n // Browsers ignore fractional scroll offsets, so we need to round.\n const /** @type {?} */ distanceBelowViewport = Math.round(panelHeightBottom - bottomSpaceAvailable);\n // Scrolls the panel up by the distance it was extending past the boundary, then\n // adjusts the offset by that amount to move the panel up into the viewport.\n this._scrollTop -= distanceBelowViewport;\n this._offsetY -= distanceBelowViewport;\n this._transformOrigin = this._getOriginBasedOnOption();\n // If the panel is scrolled to the very top, it won't be able to fit the panel\n // by scrolling, so set the offset to 0 to allow the fallback position to take\n // effect.\n if (this._scrollTop <= 0) {\n this._scrollTop = 0;\n this._offsetY = 0;\n this._transformOrigin = `50% bottom 0px`;\n }\n }\n /**\n * Adjusts the overlay panel down to fit in the viewport.\n * @param {?} panelHeightTop\n * @param {?} topSpaceAvailable\n * @param {?} maxScroll\n * @return {?}\n */\n _adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll) {\n // Browsers ignore fractional scroll offsets, so we need to round.\n const /** @type {?} */ distanceAboveViewport = Math.round(panelHeightTop - topSpaceAvailable);\n // Scrolls the panel down by the distance it was extending past the boundary, then\n // adjusts the offset by that amount to move the panel down into the viewport.\n this._scrollTop += distanceAboveViewport;\n this._offsetY += distanceAboveViewport;\n this._transformOrigin = this._getOriginBasedOnOption();\n // If the panel is scrolled to the very bottom, it won't be able to fit the\n // panel by scrolling, so set the offset to 0 to allow the fallback position\n // to take effect.\n if (this._scrollTop >= maxScroll) {\n this._scrollTop = maxScroll;\n this._offsetY = 0;\n this._transformOrigin = `50% top 0px`;\n return;\n }\n }\n /**\n * Sets the transform origin point based on the selected option.\n * @return {?}\n */\n _getOriginBasedOnOption() {\n const /** @type {?} */ itemHeight = this._getItemHeight();\n const /** @type {?} */ optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;\n const /** @type {?} */ originY = Math.abs(this._offsetY) - optionHeightAdjustment + itemHeight / 2;\n return `50% ${originY}px 0px`;\n }\n /**\n * Handles the user pressing the arrow keys on a closed select.\n * @param {?} event\n * @return {?}\n */\n _handleClosedArrowKey(event) {\n if (this._multiple) {\n event.preventDefault();\n this.open();\n }\n else {\n const /** @type {?} */ prevActiveItem = this._keyManager.activeItem;\n // Cycle though the select options even when the select is closed,\n // matching the behavior of the native select element.\n // TODO(crisbeto): native selects also cycle through the options with left/right arrows,\n // however the key manager only supports up/down at the moment.\n this._keyManager.onKeydown(event);\n // TODO(crisbeto): get rid of the Promise.resolve when #6441 gets in.\n Promise.resolve().then(() => {\n const /** @type {?} */ currentActiveItem = this._keyManager.activeItem;\n if (currentActiveItem && currentActiveItem !== prevActiveItem) {\n this._clearSelection();\n this._setSelectionByValue(currentActiveItem.value, true);\n }\n });\n }\n }\n /**\n * Calculates the amount of items in the select. This includes options and group labels.\n * @return {?}\n */\n _getItemCount() {\n return this.options.length + this.optionGroups.length;\n }\n /**\n * Calculates the height of the select's options.\n * @return {?}\n */\n _getItemHeight() {\n return this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;\n }\n /**\n * @param {?} ids\n * @return {?}\n */\n setDescribedByIds(ids) {\n this._ariaDescribedby = ids.join(' ');\n }\n /**\n * @return {?}\n */\n onContainerClick() {\n this.focus();\n this.open();\n }\n /**\n * @return {?}\n */\n get shouldPlaceholderFloat() { return this._panelOpen || !this.empty; }\n}\nMatSelect.decorators = [\n { type: Component, args: [{selector: 'mat-select',\n exportAs: 'matSelect',\n template: \"