@angular-mdc/web
Version:
724 lines (719 loc) • 22.2 kB
JavaScript
/**
* @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