/** * @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 */ import { Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, forwardRef, Inject, Input, NgZone, Optional, Output, ViewChild, ViewEncapsulation, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatRipple, mixinColor, mixinDisabled, mixinDisableRipple, mixinTabIndex, } from '@angular/material/core'; import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { MAT_CHECKBOX_DEFAULT_OPTIONS, MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY, } from './checkbox-config'; import * as i0 from "@angular/core"; import * as i1 from "@angular/material/core"; export const MAT_CHECKBOX_CONTROL_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MatCheckbox), multi: true, }; /** Change event object emitted by checkbox. */ export class MatCheckboxChange { } // Increasing integer for generating unique ids for checkbox components. let nextUniqueId = 0; // Default checkbox configuration. const defaults = MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY(); // Boilerplate for applying mixins to MatCheckbox. /** @docs-private */ const _MatCheckboxMixinBase = mixinTabIndex(mixinColor(mixinDisableRipple(mixinDisabled(class { constructor(_elementRef) { this._elementRef = _elementRef; } })))); export class _MatCheckboxBase extends _MatCheckboxMixinBase { /** Returns the unique id for the visual hidden input. */ get inputId() { return `${this.id || this._uniqueId}-input`; } /** Whether the checkbox is required. */ get required() { return this._required; } set required(value) { this._required = coerceBooleanProperty(value); } constructor(idPrefix, elementRef, _changeDetectorRef, _ngZone, tabIndex, _animationMode, _options) { super(elementRef); this._changeDetectorRef = _changeDetectorRef; this._ngZone = _ngZone; this._animationMode = _animationMode; this._options = _options; /** * Attached to the aria-label attribute of the host element. In most cases, aria-labelledby will * take precedence so this may be omitted. */ this.ariaLabel = ''; /** * Users can specify the `aria-labelledby` attribute which will be forwarded to the input element */ this.ariaLabelledby = null; /** Whether the label should appear after or before the checkbox. Defaults to 'after' */ this.labelPosition = 'after'; /** Name value will be applied to the input element if present */ this.name = null; /** Event emitted when the checkbox's `checked` value changes. */ this.change = new EventEmitter(); /** Event emitted when the checkbox's `indeterminate` value changes. */ this.indeterminateChange = new EventEmitter(); /** * Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor. * @docs-private */ this._onTouched = () => { }; this._currentAnimationClass = ''; this._currentCheckState = 0 /* TransitionCheckState.Init */; this._controlValueAccessorChangeFn = () => { }; this._checked = false; this._disabled = false; this._indeterminate = false; this._options = this._options || defaults; this.color = this.defaultColor = this._options.color || defaults.color; this.tabIndex = parseInt(tabIndex) || 0; this.id = this._uniqueId = `${idPrefix}${++nextUniqueId}`; } ngAfterViewInit() { this._syncIndeterminate(this._indeterminate); } /** Whether the checkbox is checked. */ get checked() { return this._checked; } set checked(value) { const checked = coerceBooleanProperty(value); if (checked != this.checked) { this._checked = checked; this._changeDetectorRef.markForCheck(); } } /** * Whether the checkbox is disabled. This fully overrides the implementation provided by * mixinDisabled, but the mixin is still required because mixinTabIndex requires it. */ get disabled() { return this._disabled; } set disabled(value) { const newValue = coerceBooleanProperty(value); if (newValue !== this.disabled) { this._disabled = newValue; this._changeDetectorRef.markForCheck(); } } /** * Whether the checkbox is indeterminate. This is also known as "mixed" mode and can be used to * represent a checkbox with three states, e.g. a checkbox that represents a nested list of * checkable items. Note that whenever checkbox is manually clicked, indeterminate is immediately * set to false. */ get indeterminate() { return this._indeterminate; } set indeterminate(value) { const changed = value != this._indeterminate; this._indeterminate = coerceBooleanProperty(value); if (changed) { if (this._indeterminate) { this._transitionCheckState(3 /* TransitionCheckState.Indeterminate */); } else { this._transitionCheckState(this.checked ? 1 /* TransitionCheckState.Checked */ : 2 /* TransitionCheckState.Unchecked */); } this.indeterminateChange.emit(this._indeterminate); } this._syncIndeterminate(this._indeterminate); } _isRippleDisabled() { return this.disableRipple || this.disabled; } /** Method being called whenever the label text changes. */ _onLabelTextChange() { // Since the event of the `cdkObserveContent` directive runs outside of the zone, the checkbox // component will be only marked for check, but no actual change detection runs automatically. // Instead of going back into the zone in order to trigger a change detection which causes // *all* components to be checked (if explicitly marked or not using OnPush), we only trigger // an explicit change detection for the checkbox view and its children. this._changeDetectorRef.detectChanges(); } // Implemented as part of ControlValueAccessor. writeValue(value) { this.checked = !!value; } // 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; } _transitionCheckState(newState) { let oldState = this._currentCheckState; let element = this._getAnimationTargetElement(); if (oldState === newState || !element) { return; } if (this._currentAnimationClass) { element.classList.remove(this._currentAnimationClass); } this._currentAnimationClass = this._getAnimationClassForCheckStateTransition(oldState, newState); this._currentCheckState = newState; if (this._currentAnimationClass.length > 0) { element.classList.add(this._currentAnimationClass); // Remove the animation class to avoid animation when the checkbox is moved between containers const animationClass = this._currentAnimationClass; this._ngZone.runOutsideAngular(() => { setTimeout(() => { element.classList.remove(animationClass); }, 1000); }); } } _emitChangeEvent() { this._controlValueAccessorChangeFn(this.checked); this.change.emit(this._createChangeEvent(this.checked)); // Assigning the value again here is redundant, but we have to do it in case it was // changed inside the `change` listener which will cause the input to be out of sync. if (this._inputElement) { this._inputElement.nativeElement.checked = this.checked; } } /** Toggles the `checked` state of the checkbox. */ toggle() { this.checked = !this.checked; this._controlValueAccessorChangeFn(this.checked); } _handleInputClick() { const clickAction = this._options?.clickAction; // If resetIndeterminate is false, and the current state is indeterminate, do nothing on click if (!this.disabled && clickAction !== 'noop') { // When user manually click on the checkbox, `indeterminate` is set to false. if (this.indeterminate && clickAction !== 'check') { Promise.resolve().then(() => { this._indeterminate = false; this.indeterminateChange.emit(this._indeterminate); }); } this._checked = !this._checked; this._transitionCheckState(this._checked ? 1 /* TransitionCheckState.Checked */ : 2 /* TransitionCheckState.Unchecked */); // Emit our custom change event if the native input emitted one. // It is important to only emit it, if the native input triggered one, because // we don't want to trigger a change event, when the `checked` variable changes for example. this._emitChangeEvent(); } else if (!this.disabled && clickAction === 'noop') { // Reset native input when clicked with noop. The native checkbox becomes checked after // click, reset it to be align with `checked` value of `mat-checkbox`. this._inputElement.nativeElement.checked = this.checked; this._inputElement.nativeElement.indeterminate = this.indeterminate; } } _onInteractionEvent(event) { // We always have to stop propagation on the change event. // Otherwise the change event, from the input element, will bubble up and // emit its event object to the `change` output. event.stopPropagation(); } _onBlur() { // When a focused element becomes disabled, the browser *immediately* fires a blur event. // Angular does not expect events to be raised during change detection, so any state change // (such as a form control's 'ng-touched') will cause a changed-after-checked error. // See https://github.com/angular/angular/issues/17793. To work around this, we defer // telling the form control it has been touched until the next tick. Promise.resolve().then(() => { this._onTouched(); this._changeDetectorRef.markForCheck(); }); } _getAnimationClassForCheckStateTransition(oldState, newState) { // Don't transition if animations are disabled. if (this._animationMode === 'NoopAnimations') { return ''; } switch (oldState) { case 0 /* TransitionCheckState.Init */: // Handle edge case where user interacts with checkbox that does not have [(ngModel)] or // [checked] bound to it. if (newState === 1 /* TransitionCheckState.Checked */) { return this._animationClasses.uncheckedToChecked; } else if (newState == 3 /* TransitionCheckState.Indeterminate */) { return this._checked ? this._animationClasses.checkedToIndeterminate : this._animationClasses.uncheckedToIndeterminate; } break; case 2 /* TransitionCheckState.Unchecked */: return newState === 1 /* TransitionCheckState.Checked */ ? this._animationClasses.uncheckedToChecked : this._animationClasses.uncheckedToIndeterminate; case 1 /* TransitionCheckState.Checked */: return newState === 2 /* TransitionCheckState.Unchecked */ ? this._animationClasses.checkedToUnchecked : this._animationClasses.checkedToIndeterminate; case 3 /* TransitionCheckState.Indeterminate */: return newState === 1 /* TransitionCheckState.Checked */ ? this._animationClasses.indeterminateToChecked : this._animationClasses.indeterminateToUnchecked; } return ''; } /** * Syncs the indeterminate value with the checkbox DOM node. * * We sync `indeterminate` directly on the DOM node, because in Ivy the check for whether a * property is supported on an element boils down to `if (propName in element)`. Domino's * HTMLInputElement doesn't have an `indeterminate` property so Ivy will warn during * server-side rendering. */ _syncIndeterminate(value) { const nativeCheckbox = this._inputElement; if (nativeCheckbox) { nativeCheckbox.nativeElement.indeterminate = value; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: _MatCheckboxBase, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: _MatCheckboxBase, inputs: { ariaLabel: ["aria-label", "ariaLabel"], ariaLabelledby: ["aria-labelledby", "ariaLabelledby"], ariaDescribedby: ["aria-describedby", "ariaDescribedby"], id: "id", required: "required", labelPosition: "labelPosition", name: "name", value: "value", checked: "checked", disabled: "disabled", indeterminate: "indeterminate" }, outputs: { change: "change", indeterminateChange: "indeterminateChange" }, viewQueries: [{ propertyName: "_inputElement", first: true, predicate: ["input"], descendants: true }, { propertyName: "_labelElement", first: true, predicate: ["label"], descendants: true }, { propertyName: "ripple", first: true, predicate: MatRipple, descendants: true }], usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: _MatCheckboxBase, decorators: [{ type: Directive }], ctorParameters: function () { return [{ type: undefined }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: undefined }, { type: undefined }, { type: undefined }]; }, propDecorators: { ariaLabel: [{ type: Input, args: ['aria-label'] }], ariaLabelledby: [{ type: Input, args: ['aria-labelledby'] }], ariaDescribedby: [{ type: Input, args: ['aria-describedby'] }], id: [{ type: Input }], required: [{ type: Input }], labelPosition: [{ type: Input }], name: [{ type: Input }], change: [{ type: Output }], indeterminateChange: [{ type: Output }], value: [{ type: Input }], _inputElement: [{ type: ViewChild, args: ['input'] }], _labelElement: [{ type: ViewChild, args: ['label'] }], ripple: [{ type: ViewChild, args: [MatRipple] }], checked: [{ type: Input }], disabled: [{ type: Input }], indeterminate: [{ type: Input }] } }); export class MatCheckbox extends _MatCheckboxBase { constructor(elementRef, changeDetectorRef, ngZone, tabIndex, animationMode, options) { super('mat-mdc-checkbox-', elementRef, changeDetectorRef, ngZone, tabIndex, animationMode, options); this._animationClasses = { uncheckedToChecked: 'mdc-checkbox--anim-unchecked-checked', uncheckedToIndeterminate: 'mdc-checkbox--anim-unchecked-indeterminate', checkedToUnchecked: 'mdc-checkbox--anim-checked-unchecked', checkedToIndeterminate: 'mdc-checkbox--anim-checked-indeterminate', indeterminateToChecked: 'mdc-checkbox--anim-indeterminate-checked', indeterminateToUnchecked: 'mdc-checkbox--anim-indeterminate-unchecked', }; } /** Focuses the checkbox. */ focus() { this._inputElement.nativeElement.focus(); } _createChangeEvent(isChecked) { const event = new MatCheckboxChange(); event.source = this; event.checked = isChecked; return event; } _getAnimationTargetElement() { return this._inputElement?.nativeElement; } _onInputClick() { super._handleInputClick(); } _onTouchTargetClick() { super._handleInputClick(); if (!this.disabled) { // Normally the input should be focused already, but if the click // comes from the touch target, then we might have to focus it ourselves. this._inputElement.nativeElement.focus(); } } /** * Prevent click events that come from the `