| import { FocusMonitor } from '@angular/cdk/a11y'; |
| import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
| import { SelectionModel } from '@angular/cdk/collections'; |
| import { InjectionToken, forwardRef, EventEmitter, Directive, ChangeDetectorRef, Optional, Inject, ContentChildren, Input, Output, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, Attribute, ViewChild, NgModule } from '@angular/core'; |
| import { NG_VALUE_ACCESSOR } from '@angular/forms'; |
| import { mixinDisableRipple, MatCommonModule, MatRippleModule } from '@angular/material/core'; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Injection token that can be used to configure the |
| * default options for all button toggles within an app. |
| */ |
| const MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS = new InjectionToken('MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS'); |
| /** |
| * Injection token that can be used to reference instances of `MatButtonToggleGroup`. |
| * It serves as alternative token to the actual `MatButtonToggleGroup` class which |
| * could cause unnecessary retention of the class and its component metadata. |
| */ |
| const MAT_BUTTON_TOGGLE_GROUP = new InjectionToken('MatButtonToggleGroup'); |
| /** |
| * Provider Expression that allows mat-button-toggle-group to register as a ControlValueAccessor. |
| * This allows it to support [(ngModel)]. |
| * @docs-private |
| */ |
| const MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR = { |
| provide: NG_VALUE_ACCESSOR, |
| useExisting: forwardRef(() => MatButtonToggleGroup), |
| multi: true |
| }; |
| // Counter used to generate unique IDs. |
| let uniqueIdCounter = 0; |
| /** Change event object emitted by MatButtonToggle. */ |
| class MatButtonToggleChange { |
| constructor( |
| /** The MatButtonToggle that emits the event. */ |
| source, |
| /** The value assigned to the MatButtonToggle. */ |
| value) { |
| this.source = source; |
| this.value = value; |
| } |
| } |
| /** Exclusive selection button toggle group that behaves like a radio-button group. */ |
| class MatButtonToggleGroup { |
| constructor(_changeDetector, defaultOptions) { |
| this._changeDetector = _changeDetector; |
| this._vertical = false; |
| this._multiple = false; |
| this._disabled = false; |
| /** |
| * The method to be called in order to update ngModel. |
| * Now `ngModel` binding is not supported in multiple selection mode. |
| */ |
| this._controlValueAccessorChangeFn = () => { }; |
| /** onTouch function registered via registerOnTouch (ControlValueAccessor). */ |
| this._onTouched = () => { }; |
| this._name = `mat-button-toggle-group-${uniqueIdCounter++}`; |
| /** |
| * Event that emits whenever the value of the group changes. |
| * Used to facilitate two-way data binding. |
| * @docs-private |
| */ |
| this.valueChange = new EventEmitter(); |
| /** Event emitted when the group's value changes. */ |
| this.change = new EventEmitter(); |
| this.appearance = |
| defaultOptions && defaultOptions.appearance ? defaultOptions.appearance : 'standard'; |
| } |
| /** `name` attribute for the underlying `input` element. */ |
| get name() { return this._name; } |
| set name(value) { |
| this._name = value; |
| if (this._buttonToggles) { |
| this._buttonToggles.forEach(toggle => { |
| toggle.name = this._name; |
| toggle._markForCheck(); |
| }); |
| } |
| } |
| /** Whether the toggle group is vertical. */ |
| get vertical() { return this._vertical; } |
| set vertical(value) { |
| this._vertical = coerceBooleanProperty(value); |
| } |
| /** Value of the toggle group. */ |
| get value() { |
| const selected = this._selectionModel ? this._selectionModel.selected : []; |
| if (this.multiple) { |
| return selected.map(toggle => toggle.value); |
| } |
| return selected[0] ? selected[0].value : undefined; |
| } |
| set value(newValue) { |
| this._setSelectionByValue(newValue); |
| this.valueChange.emit(this.value); |
| } |
| /** Selected button toggles in the group. */ |
| get selected() { |
| const selected = this._selectionModel ? this._selectionModel.selected : []; |
| return this.multiple ? selected : (selected[0] || null); |
| } |
| /** Whether multiple button toggles can be selected. */ |
| get multiple() { return this._multiple; } |
| set multiple(value) { |
| this._multiple = coerceBooleanProperty(value); |
| } |
| /** Whether multiple button toggle group is disabled. */ |
| get disabled() { return this._disabled; } |
| set disabled(value) { |
| this._disabled = coerceBooleanProperty(value); |
| if (this._buttonToggles) { |
| this._buttonToggles.forEach(toggle => toggle._markForCheck()); |
| } |
| } |
| ngOnInit() { |
| this._selectionModel = new SelectionModel(this.multiple, undefined, false); |
| } |
| ngAfterContentInit() { |
| this._selectionModel.select(...this._buttonToggles.filter(toggle => toggle.checked)); |
| } |
| /** |
| * Sets the model value. Implemented as part of ControlValueAccessor. |
| * @param value Value to be set to the model. |
| */ |
| writeValue(value) { |
| this.value = value; |
| this._changeDetector.markForCheck(); |
| } |
| // Implemented as part of ControlValueAccessor. |
| registerOnChange(fn) { |
| this._controlValueAccessorChangeFn = fn; |
| } |
| // Implemented as part of ControlValueAccessor. |
| registerOnTouched(fn) { |
| this._onTouched = fn; |
| } |
| // Implemented as part of ControlValueAccessor. |
| setDisabledState(isDisabled) { |
| this.disabled = isDisabled; |
| } |
| /** Dispatch change event with current selection and group value. */ |
| _emitChangeEvent() { |
| const selected = this.selected; |
| const source = Array.isArray(selected) ? selected[selected.length - 1] : selected; |
| const event = new MatButtonToggleChange(source, this.value); |
| this._controlValueAccessorChangeFn(event.value); |
| this.change.emit(event); |
| } |
| /** |
| * Syncs a button toggle's selected state with the model value. |
| * @param toggle Toggle to be synced. |
| * @param select Whether the toggle should be selected. |
| * @param isUserInput Whether the change was a result of a user interaction. |
| * @param deferEvents Whether to defer emitting the change events. |
| */ |
| _syncButtonToggle(toggle, select, isUserInput = false, deferEvents = false) { |
| // Deselect the currently-selected toggle, if we're in single-selection |
| // mode and the button being toggled isn't selected at the moment. |
| if (!this.multiple && this.selected && !toggle.checked) { |
| this.selected.checked = false; |
| } |
| if (this._selectionModel) { |
| if (select) { |
| this._selectionModel.select(toggle); |
| } |
| else { |
| this._selectionModel.deselect(toggle); |
| } |
| } |
| else { |
| deferEvents = true; |
| } |
| // We need to defer in some cases in order to avoid "changed after checked errors", however |
| // the side-effect is that we may end up updating the model value out of sequence in others |
| // The `deferEvents` flag allows us to decide whether to do it on a case-by-case basis. |
| if (deferEvents) { |
| Promise.resolve().then(() => this._updateModelValue(isUserInput)); |
| } |
| else { |
| this._updateModelValue(isUserInput); |
| } |
| } |
| /** Checks whether a button toggle is selected. */ |
| _isSelected(toggle) { |
| return this._selectionModel && this._selectionModel.isSelected(toggle); |
| } |
| /** Determines whether a button toggle should be checked on init. */ |
| _isPrechecked(toggle) { |
| if (typeof this._rawValue === 'undefined') { |
| return false; |
| } |
| if (this.multiple && Array.isArray(this._rawValue)) { |
| return this._rawValue.some(value => toggle.value != null && value === toggle.value); |
| } |
| return toggle.value === this._rawValue; |
| } |
| /** Updates the selection state of the toggles in the group based on a value. */ |
| _setSelectionByValue(value) { |
| this._rawValue = value; |
| if (!this._buttonToggles) { |
| return; |
| } |
| if (this.multiple && value) { |
| if (!Array.isArray(value) && (typeof ngDevMode === 'undefined' || ngDevMode)) { |
| throw Error('Value must be an array in multiple-selection mode.'); |
| } |
| this._clearSelection(); |
| value.forEach((currentValue) => this._selectValue(currentValue)); |
| } |
| else { |
| this._clearSelection(); |
| this._selectValue(value); |
| } |
| } |
| /** Clears the selected toggles. */ |
| _clearSelection() { |
| this._selectionModel.clear(); |
| this._buttonToggles.forEach(toggle => toggle.checked = false); |
| } |
| /** Selects a value if there's a toggle that corresponds to it. */ |
| _selectValue(value) { |
| const correspondingOption = this._buttonToggles.find(toggle => { |
| return toggle.value != null && toggle.value === value; |
| }); |
| if (correspondingOption) { |
| correspondingOption.checked = true; |
| this._selectionModel.select(correspondingOption); |
| } |
| } |
| /** Syncs up the group's value with the model and emits the change event. */ |
| _updateModelValue(isUserInput) { |
| // Only emit the change event for user input. |
| if (isUserInput) { |
| this._emitChangeEvent(); |
| } |
| // Note: we emit this one no matter whether it was a user interaction, because |
| // it is used by Angular to sync up the two-way data binding. |
| this.valueChange.emit(this.value); |
| } |
| } |
| MatButtonToggleGroup.decorators = [ |
| { type: Directive, args: [{ |
| selector: 'mat-button-toggle-group', |
| providers: [ |
| MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR, |
| { provide: MAT_BUTTON_TOGGLE_GROUP, useExisting: MatButtonToggleGroup }, |
| ], |
| host: { |
| 'role': 'group', |
| 'class': 'mat-button-toggle-group', |
| '[attr.aria-disabled]': 'disabled', |
| '[class.mat-button-toggle-vertical]': 'vertical', |
| '[class.mat-button-toggle-group-appearance-standard]': 'appearance === "standard"', |
| }, |
| exportAs: 'matButtonToggleGroup', |
| },] } |
| ]; |
| MatButtonToggleGroup.ctorParameters = () => [ |
| { type: ChangeDetectorRef }, |
| { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS,] }] } |
| ]; |
| MatButtonToggleGroup.propDecorators = { |
| _buttonToggles: [{ type: ContentChildren, args: [forwardRef(() => MatButtonToggle), { |
| // Note that this would technically pick up toggles |
| // from nested groups, but that's not a case that we support. |
| descendants: true |
| },] }], |
| appearance: [{ type: Input }], |
| name: [{ type: Input }], |
| vertical: [{ type: Input }], |
| value: [{ type: Input }], |
| valueChange: [{ type: Output }], |
| multiple: [{ type: Input }], |
| disabled: [{ type: Input }], |
| change: [{ type: Output }] |
| }; |
| // Boilerplate for applying mixins to the MatButtonToggle class. |
| /** @docs-private */ |
| class MatButtonToggleBase { |
| } |
| const _MatButtonToggleMixinBase = mixinDisableRipple(MatButtonToggleBase); |
| /** Single button inside of a toggle group. */ |
| class MatButtonToggle extends _MatButtonToggleMixinBase { |
| constructor(toggleGroup, _changeDetectorRef, _elementRef, _focusMonitor, defaultTabIndex, defaultOptions) { |
| super(); |
| this._changeDetectorRef = _changeDetectorRef; |
| this._elementRef = _elementRef; |
| this._focusMonitor = _focusMonitor; |
| this._isSingleSelector = false; |
| this._checked = false; |
| /** |
| * Users can specify the `aria-labelledby` attribute which will be forwarded to the input element |
| */ |
| this.ariaLabelledby = null; |
| this._disabled = false; |
| /** Event emitted when the group value changes. */ |
| this.change = new EventEmitter(); |
| const parsedTabIndex = Number(defaultTabIndex); |
| this.tabIndex = (parsedTabIndex || parsedTabIndex === 0) ? parsedTabIndex : null; |
| this.buttonToggleGroup = toggleGroup; |
| this.appearance = |
| defaultOptions && defaultOptions.appearance ? defaultOptions.appearance : 'standard'; |
| } |
| /** Unique ID for the underlying `button` element. */ |
| get buttonId() { return `${this.id}-button`; } |
| /** The appearance style of the button. */ |
| get appearance() { |
| return this.buttonToggleGroup ? this.buttonToggleGroup.appearance : this._appearance; |
| } |
| set appearance(value) { |
| this._appearance = value; |
| } |
| /** Whether the button is checked. */ |
| get checked() { |
| return this.buttonToggleGroup ? this.buttonToggleGroup._isSelected(this) : this._checked; |
| } |
| set checked(value) { |
| const newValue = coerceBooleanProperty(value); |
| if (newValue !== this._checked) { |
| this._checked = newValue; |
| if (this.buttonToggleGroup) { |
| this.buttonToggleGroup._syncButtonToggle(this, this._checked); |
| } |
| this._changeDetectorRef.markForCheck(); |
| } |
| } |
| /** Whether the button is disabled. */ |
| get disabled() { |
| return this._disabled || (this.buttonToggleGroup && this.buttonToggleGroup.disabled); |
| } |
| set disabled(value) { this._disabled = coerceBooleanProperty(value); } |
| ngOnInit() { |
| const group = this.buttonToggleGroup; |
| this._isSingleSelector = group && !group.multiple; |
| this.id = this.id || `mat-button-toggle-${uniqueIdCounter++}`; |
| if (this._isSingleSelector) { |
| this.name = group.name; |
| } |
| if (group) { |
| if (group._isPrechecked(this)) { |
| this.checked = true; |
| } |
| else if (group._isSelected(this) !== this._checked) { |
| // As as side effect of the circular dependency between the toggle group and the button, |
| // we may end up in a state where the button is supposed to be checked on init, but it |
| // isn't, because the checked value was assigned too early. This can happen when Ivy |
| // assigns the static input value before the `ngOnInit` has run. |
| group._syncButtonToggle(this, this._checked); |
| } |
| } |
| } |
| ngAfterViewInit() { |
| this._focusMonitor.monitor(this._elementRef, true); |
| } |
| ngOnDestroy() { |
| const group = this.buttonToggleGroup; |
| this._focusMonitor.stopMonitoring(this._elementRef); |
| // Remove the toggle from the selection once it's destroyed. Needs to happen |
| // on the next tick in order to avoid "changed after checked" errors. |
| if (group && group._isSelected(this)) { |
| group._syncButtonToggle(this, false, false, true); |
| } |
| } |
| /** Focuses the button. */ |
| focus(options) { |
| this._buttonElement.nativeElement.focus(options); |
| } |
| /** Checks the button toggle due to an interaction with the underlying native button. */ |
| _onButtonClick() { |
| const newChecked = this._isSingleSelector ? true : !this._checked; |
| if (newChecked !== this._checked) { |
| this._checked = newChecked; |
| if (this.buttonToggleGroup) { |
| this.buttonToggleGroup._syncButtonToggle(this, this._checked, true); |
| this.buttonToggleGroup._onTouched(); |
| } |
| } |
| // Emit a change event when it's the single selector |
| this.change.emit(new MatButtonToggleChange(this, this.value)); |
| } |
| /** |
| * Marks the button toggle as needing checking for change detection. |
| * This method is exposed because the parent button toggle group will directly |
| * update bound properties of the radio button. |
| */ |
| _markForCheck() { |
| // When the group value changes, the button will not be notified. |
| // Use `markForCheck` to explicit update button toggle's status. |
| this._changeDetectorRef.markForCheck(); |
| } |
| } |
| MatButtonToggle.decorators = [ |
| { type: Component, args: [{ |
| selector: 'mat-button-toggle', |
| template: "<button #button class=\"mat-button-toggle-button mat-focus-indicator\"\n type=\"button\"\n [id]=\"buttonId\"\n [attr.tabindex]=\"disabled ? -1 : tabIndex\"\n [attr.aria-pressed]=\"checked\"\n [disabled]=\"disabled || null\"\n [attr.name]=\"name || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-labelledby]=\"ariaLabelledby\"\n (click)=\"_onButtonClick()\">\n <span class=\"mat-button-toggle-label-content\">\n <ng-content></ng-content>\n </span>\n</button>\n\n<span class=\"mat-button-toggle-focus-overlay\"></span>\n<span class=\"mat-button-toggle-ripple\" matRipple\n [matRippleTrigger]=\"button\"\n [matRippleDisabled]=\"this.disableRipple || this.disabled\">\n</span>\n", |
| encapsulation: ViewEncapsulation.None, |
| exportAs: 'matButtonToggle', |
| changeDetection: ChangeDetectionStrategy.OnPush, |
| inputs: ['disableRipple'], |
| host: { |
| '[class.mat-button-toggle-standalone]': '!buttonToggleGroup', |
| '[class.mat-button-toggle-checked]': 'checked', |
| '[class.mat-button-toggle-disabled]': 'disabled', |
| '[class.mat-button-toggle-appearance-standard]': 'appearance === "standard"', |
| 'class': 'mat-button-toggle', |
| '[attr.aria-label]': 'null', |
| '[attr.aria-labelledby]': 'null', |
| '[attr.id]': 'id', |
| '[attr.name]': 'null', |
| '(focus)': 'focus()', |
| 'role': 'presentation', |
| }, |
| styles: [".mat-button-toggle-standalone,.mat-button-toggle-group{position:relative;display:inline-flex;flex-direction:row;white-space:nowrap;overflow:hidden;border-radius:2px;-webkit-tap-highlight-color:transparent}.cdk-high-contrast-active .mat-button-toggle-standalone,.cdk-high-contrast-active .mat-button-toggle-group{outline:solid 1px}.mat-button-toggle-standalone.mat-button-toggle-appearance-standard,.mat-button-toggle-group-appearance-standard{border-radius:4px}.cdk-high-contrast-active .mat-button-toggle-standalone.mat-button-toggle-appearance-standard,.cdk-high-contrast-active .mat-button-toggle-group-appearance-standard{outline:0}.mat-button-toggle-vertical{flex-direction:column}.mat-button-toggle-vertical .mat-button-toggle-label-content{display:block}.mat-button-toggle{white-space:nowrap;position:relative}.mat-button-toggle .mat-icon svg{vertical-align:top}.mat-button-toggle.cdk-keyboard-focused .mat-button-toggle-focus-overlay{opacity:1}.cdk-high-contrast-active .mat-button-toggle.cdk-keyboard-focused .mat-button-toggle-focus-overlay{opacity:.5}.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay{opacity:.04}.mat-button-toggle-appearance-standard.cdk-keyboard-focused:not(.mat-button-toggle-disabled) .mat-button-toggle-focus-overlay{opacity:.12}.cdk-high-contrast-active .mat-button-toggle-appearance-standard.cdk-keyboard-focused:not(.mat-button-toggle-disabled) .mat-button-toggle-focus-overlay{opacity:.5}@media(hover: none){.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay{display:none}}.mat-button-toggle-label-content{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;line-height:36px;padding:0 16px;position:relative}.mat-button-toggle-appearance-standard .mat-button-toggle-label-content{padding:0 12px}.mat-button-toggle-label-content>*{vertical-align:middle}.mat-button-toggle-focus-overlay{border-radius:inherit;pointer-events:none;opacity:0;top:0;left:0;right:0;bottom:0;position:absolute}.mat-button-toggle-checked .mat-button-toggle-focus-overlay{border-bottom:solid 36px}.cdk-high-contrast-active .mat-button-toggle-checked .mat-button-toggle-focus-overlay{opacity:.5;height:0}.cdk-high-contrast-active .mat-button-toggle-checked.mat-button-toggle-appearance-standard .mat-button-toggle-focus-overlay{border-bottom:solid 500px}.mat-button-toggle .mat-button-toggle-ripple{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none}.mat-button-toggle-button{border:0;background:none;color:inherit;padding:0;margin:0;font:inherit;outline:none;width:100%;cursor:pointer}.mat-button-toggle-disabled .mat-button-toggle-button{cursor:default}.mat-button-toggle-button::-moz-focus-inner{border:0}\n"] |
| },] } |
| ]; |
| MatButtonToggle.ctorParameters = () => [ |
| { type: MatButtonToggleGroup, decorators: [{ type: Optional }, { type: Inject, args: [MAT_BUTTON_TOGGLE_GROUP,] }] }, |
| { type: ChangeDetectorRef }, |
| { type: ElementRef }, |
| { type: FocusMonitor }, |
| { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] }, |
| { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS,] }] } |
| ]; |
| MatButtonToggle.propDecorators = { |
| ariaLabel: [{ type: Input, args: ['aria-label',] }], |
| ariaLabelledby: [{ type: Input, args: ['aria-labelledby',] }], |
| _buttonElement: [{ type: ViewChild, args: ['button',] }], |
| id: [{ type: Input }], |
| name: [{ type: Input }], |
| value: [{ type: Input }], |
| tabIndex: [{ type: Input }], |
| appearance: [{ type: Input }], |
| checked: [{ type: Input }], |
| disabled: [{ type: Input }], |
| change: [{ type: Output }] |
| }; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class MatButtonToggleModule { |
| } |
| MatButtonToggleModule.decorators = [ |
| { type: NgModule, args: [{ |
| imports: [MatCommonModule, MatRippleModule], |
| exports: [MatCommonModule, MatButtonToggleGroup, MatButtonToggle], |
| declarations: [MatButtonToggleGroup, MatButtonToggle], |
| },] } |
| ]; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| |
| /** |
| * Generated bundle index. Do not edit. |
| */ |
| |
| export { MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS, MAT_BUTTON_TOGGLE_GROUP, MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR, MatButtonToggle, MatButtonToggleChange, MatButtonToggleGroup, MatButtonToggleModule }; |
| //# sourceMappingURL=button-toggle.js.map |