source: trip-planner-front/node_modules/@angular/material/fesm2015/select.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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