@ng-matero/extensions
Version:
Angular Material Extensions
265 lines (260 loc) • 15 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, ChangeDetectorRef, ElementRef, EventEmitter, forwardRef, booleanAttribute, Output, Input, ContentChildren, ChangeDetectionStrategy, ViewEncapsulation, Component, NgModule } from '@angular/core';
import { AsyncPipe, CommonModule } from '@angular/common';
import * as i1 from '@angular/forms';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatCheckbox, MatCheckboxModule } from '@angular/material/checkbox';
import { MtxToObservablePipe, MtxPipesModule } from '@ng-matero/extensions/core';
import { FocusMonitor } from '@angular/cdk/a11y';
class MtxCheckboxBase {
constructor(label, value) {
this.label = label;
this.value = value;
}
}
class MtxCheckboxGroup {
constructor() {
this._changeDetectorRef = inject(ChangeDetectorRef);
this._focusMonitor = inject(FocusMonitor);
this._elementRef = inject(ElementRef);
this._items = [];
this._originalItems = [];
this.bindLabel = 'label';
this.bindValue = 'value';
this.showSelectAll = false;
this.selectAllLabel = 'Select All';
this.disabled = false;
this.change = new EventEmitter();
this.selectAll = false;
this.selectAllIndeterminate = false;
this.selectedItems = [];
this._onChange = () => null;
this._onTouched = () => null;
this._trackBy = (index, item) => {
return this.trackBy ? this.trackBy(index, item) : item;
};
}
/**
* Tracking function that will be used to check the differences in data changes. Used similarly
* to `ngFor` `trackBy` function. Optimize row operations by identifying a row based on its data
* relative to the function to know if a row should be added/removed/moved.
* Accepts a function that takes two parameters, `index` and `item`.
*/
get trackBy() {
return this._trackByFn;
}
set trackBy(fn) {
if (fn != null && typeof fn !== 'function') {
console.warn(`trackBy must be a function, but received ${JSON.stringify(fn)}.`);
}
this._trackByFn = fn;
}
get items() {
return this._items;
}
set items(value) {
// store the original data with deep clone
this._originalItems = JSON.parse(JSON.stringify(value));
this._items = value.map(option => {
return option instanceof Object ? { ...option } : new MtxCheckboxBase(option, option);
});
// update the state of selectAll when items changed
this.writeValue(this.selectedItems);
}
get compareWith() {
return this._compareWith;
}
set compareWith(fn) {
if (fn != null && typeof fn !== 'function') {
throw Error('`compareWith` must be a function.');
}
this._compareWith = fn;
}
ngAfterViewInit() {
this._focusMonitor.monitor(this._elementRef, true).subscribe(focusOrigin => {
if (!focusOrigin) {
// 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();
});
}
});
}
ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
}
/**
* Finds and selects and option based on its value.
* @returns Option that has the corresponding value.
*/
_selectValue(value) {
const correspondingOption = this.items.find(option => {
try {
const compareValue = option[this.bindValue] === value;
return this._compareWith ? this._compareWith(option, value) : compareValue;
}
catch (error) {
console.warn(error);
return false;
}
});
if (correspondingOption) {
correspondingOption.checked = true;
}
return correspondingOption;
}
/**
* Sets the model value. Implemented as part of ControlValueAccessor.
* @param value New value to be written to the model.
*/
writeValue(value) {
this.items.forEach(item => (item.checked = false));
if (value) {
if (!Array.isArray(value)) {
throw Error('Value must be an array.');
}
value.forEach(currentValue => this._selectValue(currentValue));
this.selectedItems = value;
}
this._checkMasterCheckboxState();
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.
*/
registerOnChange(fn) {
this._onChange = fn;
}
/**
* Registers a callback to be triggered when the control is touched.
* Implemented as part of ControlValueAccessor.
* @param fn Callback to be registered.
*/
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.
*/
setDisabledState(isDisabled) {
this.disabled = isDisabled;
this._changeDetectorRef.markForCheck();
}
_checkMasterCheckboxState() {
if (this.items
.filter(option => option.checked || !option.disabled)
.every(option => !option.checked)) {
this.selectAll = false;
this.selectAllIndeterminate = false;
}
else if (this.items
.filter(option => option.checked || !option.disabled)
.every(option => option.checked)) {
this.selectAll = true;
this.selectAllIndeterminate = false;
}
else {
this.selectAllIndeterminate = true;
}
}
_getSelectedItems(index) {
this.selectedItems = this.items.filter(option => option.checked);
if (this._compareWith) {
this.selectedItems = this._originalItems.filter(option => this.selectedItems.find(selectedOption => this._compareWith(option, selectedOption)));
}
else {
this.selectedItems = this.selectedItems.map(option => option[this.bindValue]);
}
this._onChange(this.selectedItems);
this.change.emit({ model: this.selectedItems, index });
}
/** Handle normal checkbox toggle */
_updateNormalCheckboxState(e, index) {
this._checkMasterCheckboxState();
this._getSelectedItems(index);
}
/** Handle master checkbox toggle */
_updateMasterCheckboxState(e, index) {
this.selectAll = !this.selectAll;
this.selectAllIndeterminate = false;
if (this.selectAll) {
this.items
.filter(option => option.checked || !option.disabled)
.forEach(option => (option.checked = true));
}
else {
this.items
.filter(option => option.checked || !option.disabled)
.forEach(option => (option.checked = !!option.disabled));
}
this._getSelectedItems(index);
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MtxCheckboxGroup, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MtxCheckboxGroup, isStandalone: true, selector: "mtx-checkbox-group", inputs: { trackBy: "trackBy", items: "items", bindLabel: "bindLabel", bindValue: "bindValue", showSelectAll: ["showSelectAll", "showSelectAll", booleanAttribute], selectAllLabel: "selectAllLabel", compareWith: "compareWith", disabled: ["disabled", "disabled", booleanAttribute] }, outputs: { change: "change" }, host: { classAttribute: "mtx-checkbox-group" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef((() => MtxCheckboxGroup)),
multi: true,
},
], queries: [{ propertyName: "_checkboxes", predicate: i0.forwardRef(() => MatCheckbox), descendants: true }], exportAs: ["mtxCheckboxGroup"], ngImport: i0, template: "@if (showSelectAll) {\n <mat-checkbox\n class=\"mtx-checkbox-master\"\n [checked]=\"selectAll\"\n [(indeterminate)]=\"selectAllIndeterminate\"\n [disabled]=\"disabled\"\n (change)=\"_updateMasterCheckboxState($event, -1)\"\n >\n {{ selectAllLabel }}\n </mat-checkbox>\n}\n\n@for (option of items; track _trackBy(i, option); let i = $index) {\n <mat-checkbox\n class=\"mtx-checkbox-normal\"\n [(ngModel)]=\"option.checked\"\n [ngModelOptions]=\"{ standalone: true }\"\n [aria-describedby]=\"option.ariaDescribedby\"\n [aria-label]=\"option.ariaLabel\"\n [aria-labelledby]=\"option.ariaLabelledby\"\n [color]=\"option.color\"\n [disabled]=\"option.disabled || disabled\"\n [disableRipple]=\"option.disableRipple\"\n [labelPosition]=\"option.labelPosition\"\n [required]=\"option.required\"\n (change)=\"_updateNormalCheckboxState($event, i)\"\n >\n {{ option[bindLabel] | toObservable | async }}\n </mat-checkbox>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "pipe", type: MtxToObservablePipe, name: "toObservable" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MtxCheckboxGroup, decorators: [{
type: Component,
args: [{ selector: 'mtx-checkbox-group', exportAs: 'mtxCheckboxGroup', host: {
class: 'mtx-checkbox-group',
}, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef((() => MtxCheckboxGroup)),
multi: true,
},
], imports: [FormsModule, MatCheckbox, MtxToObservablePipe, AsyncPipe], template: "@if (showSelectAll) {\n <mat-checkbox\n class=\"mtx-checkbox-master\"\n [checked]=\"selectAll\"\n [(indeterminate)]=\"selectAllIndeterminate\"\n [disabled]=\"disabled\"\n (change)=\"_updateMasterCheckboxState($event, -1)\"\n >\n {{ selectAllLabel }}\n </mat-checkbox>\n}\n\n@for (option of items; track _trackBy(i, option); let i = $index) {\n <mat-checkbox\n class=\"mtx-checkbox-normal\"\n [(ngModel)]=\"option.checked\"\n [ngModelOptions]=\"{ standalone: true }\"\n [aria-describedby]=\"option.ariaDescribedby\"\n [aria-label]=\"option.ariaLabel\"\n [aria-labelledby]=\"option.ariaLabelledby\"\n [color]=\"option.color\"\n [disabled]=\"option.disabled || disabled\"\n [disableRipple]=\"option.disableRipple\"\n [labelPosition]=\"option.labelPosition\"\n [required]=\"option.required\"\n (change)=\"_updateNormalCheckboxState($event, i)\"\n >\n {{ option[bindLabel] | toObservable | async }}\n </mat-checkbox>\n}\n" }]
}], propDecorators: { _checkboxes: [{
type: ContentChildren,
args: [forwardRef(() => MatCheckbox), { descendants: true }]
}], trackBy: [{
type: Input
}], items: [{
type: Input
}], bindLabel: [{
type: Input
}], bindValue: [{
type: Input
}], showSelectAll: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], selectAllLabel: [{
type: Input
}], compareWith: [{
type: Input
}], disabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], change: [{
type: Output
}] } });
class MtxCheckboxGroupModule {
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MtxCheckboxGroupModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: MtxCheckboxGroupModule, imports: [CommonModule, FormsModule, MatCheckboxModule, MtxPipesModule, MtxCheckboxGroup], exports: [MtxCheckboxGroup, MtxPipesModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MtxCheckboxGroupModule, imports: [CommonModule, FormsModule, MatCheckboxModule, MtxPipesModule, MtxCheckboxGroup, MtxPipesModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MtxCheckboxGroupModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule, FormsModule, MatCheckboxModule, MtxPipesModule, MtxCheckboxGroup],
exports: [MtxCheckboxGroup, MtxPipesModule],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { MtxCheckboxBase, MtxCheckboxGroup, MtxCheckboxGroupModule };
//# sourceMappingURL=mtxCheckboxGroup.mjs.map