UNPKG

@angular-mdc/web

Version:
724 lines (719 loc) 22.2 kB
/** * @license * Copyright (c) Dominic Carretto * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/trimox/angular-mdc-web/blob/master/LICENSE */ import { InjectionToken, EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef, Optional, Inject, Input, Output, ViewChild, forwardRef, ContentChildren, NgModule } from '@angular/core'; import { MdcFormFieldControl, MdcFormField, MdcFormFieldModule } from '@angular-mdc/web/form-field'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { UniqueSelectionDispatcher } from '@angular/cdk/collections'; import { supportsPassiveEventListeners } from '@angular/cdk/platform'; import { MDCRippleFoundation } from '@material/ripple'; import { MDCRadioFoundation } from '@material/radio'; import { MdcRipple } from '@angular-mdc/web/ripple'; import { MDCComponent } from '@angular-mdc/web/base'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; /** * @fileoverview added by tsickle * Generated from: radio/radio.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Injection token used to provide the parent MdcRadioGroup component to MdcRadio. * @type {?} */ const MDC_RADIO_GROUP_PARENT_COMPONENT = new InjectionToken('MDC_RADIO_GROUP_PARENT_COMPONENT'); /** * Change event object emitted by MdcRadio. */ class MdcRadioChange { /** * @param {?} source * @param {?} value */ constructor(source, value) { this.source = source; this.value = value; } } /** @type {?} */ let nextUniqueId = 0; class MdcRadio extends MDCComponent { /** * @param {?} _changeDetectorRef * @param {?} elementRef * @param {?} ripple * @param {?} _radioDispatcher * @param {?} radioGroup * @param {?} _parentFormField */ constructor(_changeDetectorRef, elementRef, ripple, _radioDispatcher, radioGroup, _parentFormField) { super(elementRef); this._changeDetectorRef = _changeDetectorRef; this.elementRef = elementRef; this.ripple = ripple; this._radioDispatcher = _radioDispatcher; this.radioGroup = radioGroup; this._parentFormField = _parentFormField; this._uniqueId = `mdc-radio-${++nextUniqueId}`; this._initialized = false; /** * The unique ID for the radio button. */ this.id = this._uniqueId; this.tabIndex = 0; this._touch = false; this._checked = false; this._disabled = false; this._required = false; this.change = new EventEmitter(); /** * Unregister function for _radioDispatcher */ this._removeUniqueSelectionListener = (/** * @return {?} */ () => { }); if (this._parentFormField) { _parentFormField.elementRef.nativeElement.classList.add('mdc-form-field'); } this._root = elementRef.nativeElement; this.ripple = this._createRipple(); this._removeUniqueSelectionListener = _radioDispatcher.listen((/** * @param {?} id * @param {?} name * @return {?} */ (id, name) => { if (id !== this.id && name === this.name) { // Get the checked state from native radio button. The native radio buttons with the same // name have separate unique selection in different form containers. this.checked = this.input.nativeElement.checked; } })); } /** * @return {?} */ get inputId() { return `${this.id || this._uniqueId}-input`; } /** * @return {?} */ get touch() { return this._touch; } /** * @param {?} value * @return {?} */ set touch(value) { this._touch = coerceBooleanProperty(value); } /** * @return {?} */ get value() { return this._value; } /** * @param {?} newValue * @return {?} */ set value(newValue) { this.setValue(newValue); } /** * @return {?} */ get checked() { return this._checked; } /** * @param {?} value * @return {?} */ set checked(value) { this.setChecked(value); } /** * @return {?} */ get disabled() { return this._disabled || (this.radioGroup !== null && this.radioGroup.disabled); } /** * @param {?} value * @return {?} */ set disabled(value) { /** @type {?} */ const newDisabledState = coerceBooleanProperty(value); if (this._disabled !== newDisabledState) { this._disabled = newDisabledState; this._foundation.setDisabled(this._disabled); this._changeDetectorRef.markForCheck(); } } /** * @return {?} */ get required() { return this._required || (this.radioGroup && this.radioGroup.required); } /** * @param {?} value * @return {?} */ set required(value) { this._required = coerceBooleanProperty(value); } /** * @return {?} */ getDefaultFoundation() { /** @type {?} */ const adapter = { addClass: (/** * @param {?} className * @return {?} */ (className) => this._root.classList.add(className)), removeClass: (/** * @param {?} className * @return {?} */ (className) => this._root.classList.remove(className)), setNativeControlDisabled: (/** * @param {?} disabled * @return {?} */ (disabled) => this.disabled = disabled) }; return new MDCRadioFoundation(adapter); } /** * @return {?} */ ngAfterViewInit() { this._initialized = true; this._foundation.init(); if (this.radioGroup) { Promise.resolve().then((/** * @return {?} */ () => { // If the radio is inside a radio group, determine if it should be checked this.checked = this.radioGroup.value === this._value; // Copy name from parent radio group this.name = this.radioGroup.name; this.setChecked(this.checked); this._changeDetectorRef.markForCheck(); })); } } /** * @return {?} */ ngOnDestroy() { this._removeUniqueSelectionListener(); this.ripple.destroy(); this._foundation.destroy(); } /** * @param {?} event * @return {?} */ onInputClick(event) { // Preventing bubbling for the second event will solve that issue. event.stopPropagation(); } /** * @param {?} event * @return {?} */ onInputChange(event) { this.ripple.init(); event.stopPropagation(); /** @type {?} */ const groupValueChanged = this.radioGroup && this.value !== this.radioGroup.value; this.checked = true; this._emitChangeEvent(); if (this.radioGroup) { this.radioGroup._controlValueAccessorChangeFn(this.value); if (groupValueChanged) { this.radioGroup.emitChangeEvent(); } } } /** * @param {?} checked * @return {?} */ setChecked(checked) { if (!this._initialized) { return; } /** @type {?} */ const newCheckedState = coerceBooleanProperty(checked); if (this._checked !== newCheckedState) { this._checked = newCheckedState; this.input.nativeElement.checked = newCheckedState; if (newCheckedState && this.radioGroup && this.radioGroup.value !== this.value) { this.radioGroup.selected = this; } else if (!newCheckedState && this.radioGroup && this.radioGroup.value === this.value) { // When unchecking the selected radio button, update the selected radio // property on the group. this.radioGroup.selected = null; } this._changeDetectorRef.markForCheck(); if (newCheckedState) { // Notify all radio buttons with the same name to un-check. this._radioDispatcher.notify(this.id, this.name); } } } /** * @param {?} value * @return {?} */ setValue(value) { this._value = value; this.input.nativeElement.value = this._value; if (this.radioGroup !== null) { if (!this.checked) { // Update checked when the value changed to match the radio group's value this.checked = this.radioGroup.value === value; } if (this.checked) { this.radioGroup.selected = this; } } } /** * @return {?} */ focus() { this.input.nativeElement.focus(); } /** * @return {?} */ markForCheck() { this._changeDetectorRef.markForCheck(); } /** * @private * @return {?} */ _createRipple() { /** @type {?} */ const adapter = Object.assign(Object.assign({}, MdcRipple.createAdapter(this)), { isSurfaceActive: (/** * @return {?} */ () => false), isUnbounded: (/** * @return {?} */ () => true), deregisterInteractionHandler: (/** * @param {?} evtType * @param {?} handler * @return {?} */ (evtType, handler) => this.input.nativeElement.removeEventListener(evtType, handler, supportsPassiveEventListeners())), registerInteractionHandler: (/** * @param {?} evtType * @param {?} handler * @return {?} */ (evtType, handler) => this.input.nativeElement.addEventListener(evtType, handler, supportsPassiveEventListeners())) }); return new MdcRipple(this.elementRef, new MDCRippleFoundation(adapter)); } /** * Dispatch change event with current value. * @private * @return {?} */ _emitChangeEvent() { this.change.emit(new MdcRadioChange(this, this._value)); } } MdcRadio.decorators = [ { type: Component, args: [{selector: 'mdc-radio', exportAs: 'mdcRadio', host: { '[id]': 'id', 'class': 'mdc-radio', '(focus)': 'input.nativeElement.focus()', '[attr.tabindex]': '-1', '[attr.name]': 'null', '[class.mdc-radio--touch]': 'touch', }, template: ` <input type="radio" #input class="mdc-radio__native-control" [id]="inputId" [attr.name]="name" [tabIndex]="tabIndex" [attr.aria-label]="ariaLabel" [attr.aria-labelledby]="ariaLabelledby" [attr.aria-describedby]="ariaDescribedby" [disabled]="disabled" [required]="required" [checked]="checked" (click)="onInputClick($event)" (change)="onInputChange($event)" /> <div class="mdc-radio__background"> <div class="mdc-radio__outer-circle"></div> <div class="mdc-radio__inner-circle"></div> </div> <div class="mdc-radio__ripple"></div> `, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [ MdcRipple, { provide: MdcFormFieldControl, useExisting: MdcRadio } ] },] }, ]; /** @nocollapse */ MdcRadio.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: ElementRef }, { type: MdcRipple }, { type: UniqueSelectionDispatcher }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MDC_RADIO_GROUP_PARENT_COMPONENT,] }] }, { type: MdcFormField, decorators: [{ type: Optional }] } ]; MdcRadio.propDecorators = { id: [{ type: Input }], name: [{ type: Input }], tabIndex: [{ type: Input }], ariaLabel: [{ type: Input, args: ['aria-label',] }], ariaLabelledby: [{ type: Input, args: ['aria-labelledby',] }], ariaDescribedby: [{ type: Input, args: ['aria-describedby',] }], touch: [{ type: Input }], value: [{ type: Input }], checked: [{ type: Input }], disabled: [{ type: Input }], required: [{ type: Input }], change: [{ type: Output }], input: [{ type: ViewChild, args: ['input', { static: true },] }] }; /** * @fileoverview added by tsickle * Generated from: radio/radio-group.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Provider Expression that allows mdc-radio-group to register as a ControlValueAccessor. This * allows it to support [(ngModel)] and ngControl. * @type {?} */ const MDC_RADIO_GROUP_CONTROL_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef((/** * @return {?} */ () => MdcRadioGroup)), multi: true }; /** @type {?} */ let nextUniqueId$1 = 0; class MdcRadioGroup { /** * @param {?} _changeDetectorRef * @param {?} elementRef */ constructor(_changeDetectorRef, elementRef) { this._changeDetectorRef = _changeDetectorRef; this.elementRef = elementRef; this._name = `mdc-radio-group-${nextUniqueId$1++}`; /** * Selected value for the radio group. */ this._value = null; /** * Whether the `value` has been set to its initial value. */ this._isInitialized = false; this._selected = null; this._required = false; this._disabled = false; /** * The method to be called in order to update ngModel */ this._controlValueAccessorChangeFn = (/** * @return {?} */ () => { }); /** * onTouch function registered via registerOnTouch (ControlValueAccessor). */ this.onTouched = (/** * @return {?} */ () => { }); this.change = new EventEmitter(); } /** * Name of the radio button group. All radio buttons inside this group will use this name. * @return {?} */ get name() { return this._name; } /** * @param {?} value * @return {?} */ set name(value) { this._name = value; this._updateRadioButtonNames(); } /** * Value for the radio-group. Should equal the value of the selected radio button if there is * a corresponding radio button with a matching value. If there is not such a corresponding * radio button, this value persists to be applied in case a new radio button is added with a * matching value. * @return {?} */ get value() { return this._value; } /** * @param {?} newValue * @return {?} */ set value(newValue) { if (this._value !== newValue) { // Set this before proceeding to ensure no circular loop occurs with selection. this._value = newValue; this._updateSelectedRadioFromValue(); this._checkSelectedRadioButton(); } } /** * The currently selected radio button. If set to a new radio button, the radio group value * will be updated to match the new selected button. * @return {?} */ get selected() { return this._selected; } /** * @param {?} selected * @return {?} */ set selected(selected) { this._selected = selected; this.value = selected ? selected.value : null; this._checkSelectedRadioButton(); } /** * @return {?} */ get required() { return this._required; } /** * @param {?} value * @return {?} */ set required(value) { this._required = coerceBooleanProperty(value); this._markRadiosForCheck(); } /** * @return {?} */ get disabled() { return this._disabled; } /** * @param {?} value * @return {?} */ set disabled(value) { this._disabled = coerceBooleanProperty(value); this._updateDisableRadioState(this._disabled); this._markRadiosForCheck(); } /** * @return {?} */ _checkSelectedRadioButton() { if (this._selected && !this._selected.checked) { this._selected.checked = true; } } /** * @return {?} */ ngAfterContentInit() { this._isInitialized = true; } /** * @return {?} */ _touch() { if (this.onTouched) { this.onTouched(); } } /** * @private * @return {?} */ _updateRadioButtonNames() { if (this._radios) { this._radios.forEach((/** * @param {?} radio * @return {?} */ radio => { radio.name = this.name; radio.markForCheck(); })); } } /** * Updates the `selected` radio button from the internal _value state. * @private * @return {?} */ _updateSelectedRadioFromValue() { // If the value already matches the selected radio, do nothing. /** @type {?} */ const isAlreadySelected = this._selected !== null && this._selected.value === this._value; if (this._radios && !isAlreadySelected) { this._selected = null; this._radios.forEach((/** * @param {?} radio * @return {?} */ radio => { radio.checked = this.value === radio.value; if (radio.checked) { this._selected = radio; } })); } } /** * @private * @return {?} */ _markRadiosForCheck() { if (this._radios) { this._radios.forEach((/** * @param {?} radio * @return {?} */ radio => radio.markForCheck())); } } /** * @private * @param {?} disabled * @return {?} */ _updateDisableRadioState(disabled) { if (this._radios) { this._radios.forEach((/** * @param {?} radio * @return {?} */ radio => radio.disabled = disabled)); } } /** * Sets the model value. Implemented as part of ControlValueAccessor. * @param {?} value * @return {?} */ writeValue(value) { this.value = value; this._changeDetectorRef.markForCheck(); } /** * Registers a callback to be triggered when the model value changes. * Implemented as part of ControlValueAccessor. * @param {?} fn Callback to be registered. * @return {?} */ registerOnChange(fn) { this._controlValueAccessorChangeFn = fn; } /** * Registers a callback to be triggered when the control is touched. * Implemented as part of ControlValueAccessor. * @param {?} fn Callback to be registered. * @return {?} */ registerOnTouched(fn) { this.onTouched = fn; } /** * Sets the disabled state of the control. Implemented as a part of ControlValueAccessor. * @param {?} isDisabled Whether the control should be disabled. * @return {?} */ setDisabledState(isDisabled) { this.disabled = coerceBooleanProperty(isDisabled); this._changeDetectorRef.markForCheck(); } /** * Dispatch change event with current selection and group value. * @return {?} */ emitChangeEvent() { if (this._isInitialized) { this.change.emit(new MdcRadioChange((/** @type {?} */ (this._selected)), this._value)); } } } MdcRadioGroup.decorators = [ { type: Component, args: [{ selector: 'mdc-radio-group, [mdcRadioGroup]', exportAs: 'mdcRadioGroup', host: { 'role': 'radiogroup', '[attr.name]': 'null' }, template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [MDC_RADIO_GROUP_CONTROL_VALUE_ACCESSOR, { provide: MDC_RADIO_GROUP_PARENT_COMPONENT, useExisting: MdcRadioGroup } ] },] }, ]; /** @nocollapse */ MdcRadioGroup.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: ElementRef } ]; MdcRadioGroup.propDecorators = { _radios: [{ type: ContentChildren, args: [forwardRef((/** * @return {?} */ () => MdcRadio)), { descendants: true },] }], name: [{ type: Input }], value: [{ type: Input }], selected: [{ type: Input }], required: [{ type: Input }], disabled: [{ type: Input }], change: [{ type: Output }] }; /** * @fileoverview added by tsickle * Generated from: radio/module.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class MdcRadioModule { } MdcRadioModule.decorators = [ { type: NgModule, args: [{ imports: [MdcFormFieldModule], exports: [MdcRadioGroup, MdcRadio], declarations: [MdcRadioGroup, MdcRadio] },] }, ]; export { MDC_RADIO_GROUP_CONTROL_VALUE_ACCESSOR, MDC_RADIO_GROUP_PARENT_COMPONENT, MdcRadio, MdcRadioChange, MdcRadioGroup, MdcRadioModule }; //# sourceMappingURL=radio.js.map