@clr/angular
Version:
Angular components for Clarity
1,279 lines (1,256 loc) • 373 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Directive, ViewChild, Component, PLATFORM_ID, Inject, DOCUMENT, EventEmitter, ElementRef, booleanAttribute, Input, Output, Optional, ContentChild, ChangeDetectionStrategy, ContentChildren, forwardRef, HostListener, ViewContainerRef, runInInjectionContext, Injector, ChangeDetectorRef, NgZone, Renderer2, inject, EnvironmentInjector, TemplateRef, IterableDiffers, afterNextRender, ViewChildren, InjectionToken, NgModule } from '@angular/core';
import * as i2 from '@clr/angular/utils';
import { uniqueIdFactory, Keys, HostWrapper, IfExpandService, ClrLoadingState, ClrExpandableAnimationDirective, LoadingListener, WillyWonka, OompaLoompa, ClrKeyFocus, DomAdapter, ClrIfExpanded, CdkDragModule, CdkTrapFocusModule, ClrLoadingModule, ClrConditionalModule, ClrOutsideClickModule, ClrExpandableAnimationModule, ClrKeyFocusModule } from '@clr/angular/utils';
import * as i2$2 from 'rxjs';
import { Subject, BehaviorSubject, fromEvent, ReplaySubject, combineLatest, merge, of } from 'rxjs';
import { filter, takeUntil, delay, debounceTime, map, switchMap } from 'rxjs/operators';
import * as i3 from '@clr/angular/popover/common';
import { ClrPopoverPosition, ClrPopoverType, ClrPopoverHostDirective, mapPopoverKeyToPosition, ClrPopoverModuleNext } from '@clr/angular/popover/common';
import * as i1 from '@clr/angular/modal';
import * as i9 from '@angular/common';
import { isPlatformBrowser, NgForOf, CommonModule } from '@angular/common';
import * as i5 from '@clr/angular/icon';
import { ClarityIcons, ellipsisVerticalIcon, viewColumnsIcon, windowCloseIcon, arrowIcon, timesIcon, twoWayArrowsIcon, stepForward2Icon, angleDoubleIcon, filterGridCircleIcon, filterGridIcon, ClrIcon } from '@clr/angular/icon';
import * as i4 from '@clr/angular/forms/common';
import { ClrControlLabel } from '@clr/angular/forms/common';
import * as i5$1 from '@clr/angular/forms/number-input';
import { ClrNumberInputModule } from '@clr/angular/forms/number-input';
import * as i13 from '@angular/forms';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import * as i5$2 from '@clr/angular/forms/input';
import { ClrInputModule } from '@clr/angular/forms/input';
import { ClrSignpost } from '@clr/angular/popover/signpost';
import * as i12 from '@clr/angular/forms/radio';
import { ClrRadioModule } from '@clr/angular/forms/radio';
import * as i14 from '@clr/angular/progress/spinner';
import { ClrSpinnerModule } from '@clr/angular/progress/spinner';
import * as i2$1 from '@angular/cdk/bidi';
import { Directionality } from '@angular/cdk/bidi';
import { coerceNumberProperty } from '@angular/cdk/coercion';
import * as i3$1 from '@angular/cdk/scrolling';
import { FixedSizeVirtualScrollStrategy, VIRTUAL_SCROLL_STRATEGY, ScrollDispatcher, ViewportRuler, CdkVirtualScrollable, CdkVirtualScrollViewport, CDK_VIRTUAL_SCROLL_VIEWPORT, CdkVirtualForOf } from '@angular/cdk/scrolling';
import * as i7 from '@clr/angular/forms/checkbox';
import { ClrCheckboxModule } from '@clr/angular/forms/checkbox';
import { ClrSelectModule } from '@clr/angular/forms/select';
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
/**
* Generic accessor for deep object properties
* that can be specified as simple dot-separated strings.
*/
class NestedProperty {
constructor(prop) {
this.prop = prop;
if (prop.indexOf('.') >= 0) {
this.splitProp = prop.split('.');
}
}
// Safe getter for a deep object property, will not throw an error but return
// undefined if one of the intermediate properties is null or undefined.
getPropValue(item) {
if (this.splitProp) {
let value = item;
for (const nestedProp of this.splitProp) {
if (value === null ||
typeof value === 'undefined' ||
typeof value[nestedProp] === 'undefined') {
return undefined;
}
value = value[nestedProp];
}
return value;
}
else {
return item[this.prop];
}
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridPropertyComparator {
constructor(prop) {
this.prop = prop;
this.nestedProp = new NestedProperty(prop);
}
compare(a, b) {
let propA = this.nestedProp.getPropValue(a);
let propB = this.nestedProp.getPropValue(b);
if (typeof propA === 'string') {
propA = propA.toLowerCase();
}
if (typeof propB === 'string') {
propB = propB.toLowerCase();
}
if (typeof propA === 'undefined' || propA === null) {
if (typeof propB === 'undefined' || propB === null) {
return 0;
}
else {
return 1;
}
}
else {
if (typeof propB === 'undefined' || propB === null) {
return -1;
}
else if (propA < propB) {
return -1;
}
else if (propA > propB) {
return 1;
}
else {
return 0;
}
}
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridPropertyNumericFilter {
constructor(prop, exact = false) {
this.prop = prop;
this.exact = exact;
this.nestedProp = new NestedProperty(prop);
}
accepts(item, low, high) {
const propValue = this.nestedProp.getPropValue(item);
if (propValue === undefined) {
return false;
}
if (low !== null && (typeof propValue !== 'number' || propValue < low)) {
return false;
}
if (high !== null && (typeof propValue !== 'number' || propValue > high)) {
return false;
}
return true;
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridNumericFilterImpl {
constructor(filterFn) {
this.filterFn = filterFn;
/**
* The Observable required as part of the Filter interface
*/
this._changes = new Subject();
/**
* Internal values and accessor
*/
this._low = null;
this._high = null;
}
// We do not want to expose the Subject itself, but the Observable which is read-only
get changes() {
return this._changes.asObservable();
}
get value() {
return [this._low, this._high];
}
set value(vals) {
const low = vals[0];
const high = vals[1];
if (low !== this._low || high !== this._high) {
this._low = low;
this._high = high;
this._changes.next([this._low, this._high]);
}
}
get low() {
return this._low;
}
set low(low) {
if (low !== this._low) {
this._low = low;
this._changes.next([this._low, this._high]);
}
}
get high() {
return this._high;
}
set high(high) {
if (high !== this._high) {
this._high = high;
this._changes.next([this._low, this._high]);
}
}
get state() {
if (this.filterFn instanceof DatagridPropertyNumericFilter) {
return {
property: this.filterFn.prop,
low: this._low,
high: this._high,
};
}
return this;
}
/**
* Indicates if the filter is currently active, (at least one input is set)
*/
isActive() {
return this._low !== null || this.high !== null;
}
/**
* Tests if an item matches a search text
*/
accepts(item) {
// We have a filter function in case someone wants to implement a numeric
// filter that always passes nulls or similar
return this.filterFn.accepts(item, this._low, this._high);
}
equals(other) {
if (other instanceof DatagridNumericFilterImpl) {
if (other.filterFn instanceof DatagridPropertyNumericFilter) {
return (this.filterFn instanceof DatagridPropertyNumericFilter &&
other.filterFn.prop === this.filterFn.prop &&
other.low === this._low &&
other.high === this._high);
}
return other === this;
}
return false;
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridPropertyStringFilter {
constructor(prop, exact = false) {
this.prop = prop;
this.exact = exact;
this.nestedProp = new NestedProperty(prop);
}
accepts(item, search) {
const propValue = this.nestedProp.getPropValue(item);
if (typeof propValue === 'undefined') {
return false;
}
else if (this.exact) {
return ('' + propValue).toLowerCase() === search;
}
else {
return ('' + propValue).toLowerCase().indexOf(search) >= 0;
}
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridStringFilterImpl {
constructor(filterFn) {
this.filterFn = filterFn;
/**
* The Observable required as part of the Filter interface
*/
this._changes = new Subject();
/**
* Input value converted to lowercase
*/
this._lowerCaseValue = '';
/**
* Raw input value
*/
this._rawValue = '';
}
// We do not want to expose the Subject itself, but the Observable which is read-only
get changes() {
return this._changes.asObservable();
}
get lowerCaseValue() {
return this._lowerCaseValue;
}
get state() {
if (this.filterFn instanceof DatagridPropertyStringFilter) {
return {
property: this.filterFn.prop,
value: this.value,
};
}
return this;
}
get value() {
return this._rawValue;
}
/**
* Common setter for the input value
*/
set value(value) {
if (!value) {
value = '';
}
if (value !== this._rawValue) {
this._rawValue = value;
this._lowerCaseValue = value.toLowerCase().trim();
this._changes.next(value);
}
}
/**
* Indicates if the filter is currently active, meaning the input is not empty
*/
isActive() {
return !!this.value;
}
/**
* Tests if an item matches a search text
*/
accepts(item) {
// We always test with the lowercase value of the input, to stay case insensitive
return this.filterFn.accepts(item, this.lowerCaseValue);
}
equals(other) {
if (other instanceof DatagridStringFilterImpl) {
if (other.filterFn instanceof DatagridPropertyStringFilter) {
return (this.filterFn instanceof DatagridPropertyStringFilter &&
other.filterFn.prop === this.filterFn.prop &&
other.value === this.value);
}
return other === this;
}
return false;
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
/**
* Enumeration representing the sorting order of a datagrid column. It is a constant Enum,
* i.e. each value needs to be treated as a `number`, starting at index 0.
*
* @export
* @enum {number}
*/
var ClrDatagridSortOrder;
(function (ClrDatagridSortOrder) {
ClrDatagridSortOrder[ClrDatagridSortOrder["UNSORTED"] = 0] = "UNSORTED";
ClrDatagridSortOrder[ClrDatagridSortOrder["ASC"] = 1] = "ASC";
ClrDatagridSortOrder[ClrDatagridSortOrder["DESC"] = -1] = "DESC";
})(ClrDatagridSortOrder || (ClrDatagridSortOrder = {}));
var ClrDatagridAriaSortOrder;
(function (ClrDatagridAriaSortOrder) {
ClrDatagridAriaSortOrder["UNSORTED"] = "none";
ClrDatagridAriaSortOrder["ASC"] = "ascending";
ClrDatagridAriaSortOrder["DESC"] = "descending";
})(ClrDatagridAriaSortOrder || (ClrDatagridAriaSortOrder = {}));
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class CustomFilter {
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
const STRICT_WIDTH_CLASS = 'datagrid-fixed-width';
const HIDDEN_COLUMN_CLASS = 'datagrid-hidden-column';
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
/*
* This provider implements some form of synchronous debouncing through a lock pattern
* to avoid emitting multiple state changes for a single user action.
*/
class StateDebouncer {
constructor() {
/*
* This is the lock, to only emit once all the changes have finished processing
*/
this.nbChanges = 0;
/**
* The Observable that lets other classes subscribe to global state changes
*/
this._change = new Subject();
}
// We do not want to expose the Subject itself, but the Observable which is read-only
get change() {
return this._change.asObservable();
}
changeStart() {
this.nbChanges++;
}
changeDone() {
if (--this.nbChanges === 0) {
this._change.next();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StateDebouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StateDebouncer }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StateDebouncer, decorators: [{
type: Injectable
}] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class Page {
constructor(stateDebouncer) {
this.stateDebouncer = stateDebouncer;
this.activated = false;
/**
* Page size, a value of 0 means no pagination
*/
this._size = 0;
/**
* Current page
*/
this._current = 1;
/**
* The Observable that lets other classes subscribe to page changes
*/
this._change = new Subject();
this.preventEmit = false;
this._sizeChange = new Subject();
}
get size() {
return this._size;
}
set size(size) {
const oldSize = this._size;
if (size !== oldSize) {
if (!this.preventEmit) {
this.stateDebouncer.changeStart();
}
this._size = size;
if (size === 0) {
this._current = 1;
}
else {
// Yeap. That's the formula to keep the first item from the old page still
// displayed in the new one.
this._current = Math.floor((oldSize / size) * (this._current - 1)) + 1;
}
// We always emit an event even if the current page index didn't change, because
// the size changing means the items inside the page are different
if (!this.preventEmit) {
this._change.next(this._current);
this._sizeChange.next(this._size);
this.stateDebouncer.changeDone();
}
}
this.preventEmit = false;
}
get totalItems() {
return this._totalItems || 0; // remains 0 if not set to avoid breaking change
}
set totalItems(total) {
this._totalItems = total;
// If we have less items than before, we might need to change the current page
if (this.current > this.last) {
this.current = this.last;
}
}
get last() {
if (this._last) {
return this._last;
}
// If the last page isn't known, we compute it from the last item's index
if (this.size > 0 && this.totalItems) {
return Math.ceil(this.totalItems / this.size);
}
return 1;
}
set last(page) {
this._last = page;
}
// We do not want to expose the Subject itself, but the Observable which is read-only
get change() {
return this._change.asObservable();
}
get sizeChange() {
return this._sizeChange.asObservable();
}
get current() {
return this._current;
}
set current(page) {
if (page !== this._current) {
this.stateDebouncer.changeStart();
this._current = page;
this._change.next(page);
this.stateDebouncer.changeDone();
}
}
/**
* Index of the first item displayed on the current page, starting at 0, -1 if none displayed
*/
get firstItem() {
if (this._totalItems === 0) {
return -1;
}
if (this.size === 0) {
return 0;
}
return (this.current - 1) * this.size;
}
/**
* Index of the last item displayed on the current page, starting at 0, -1 if none displayed
*/
get lastItem() {
if (this._totalItems === 0) {
return -1;
}
if (this.size === 0) {
return this.totalItems - 1;
}
let lastInPage = this.current * this.size - 1;
if (this.totalItems) {
lastInPage = Math.min(lastInPage, this.totalItems - 1);
}
return lastInPage;
}
/**
* Moves to the previous page if it exists
*/
previous() {
if (this.current > 1) {
this.current--;
}
}
/**
* Moves to the next page if it exists
*/
next() {
if (this.current < this.last) {
this.current++;
}
}
/**
* Resets the page size to 0
*/
resetPageSize(preventEmit = false) {
this.preventEmit = preventEmit;
this.size = 0;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: Page, deps: [{ token: StateDebouncer }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: Page }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: Page, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: StateDebouncer }] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class FiltersProvider {
constructor(_page, stateDebouncer) {
this._page = _page;
this.stateDebouncer = stateDebouncer;
/**
* This subject is the list of filters that changed last, not the whole list.
* We emit a list rather than just one filter to allow batch changes to several at once.
*/
this._change = new Subject();
/**
* List of all filters, whether they're active or not
*/
this._all = [];
}
// We do not want to expose the Subject itself, but the Observable which is read-only
get change() {
return this._change.asObservable();
}
/**
* Tests if at least one filter is currently active
*/
hasActiveFilters() {
// We do not use getActiveFilters() because this function will be called much more often
// and stopping the loop early might be relevant.
for (const { filter } of this._all) {
if (filter && filter.isActive()) {
return true;
}
}
return false;
}
/**
* Returns a list of all currently active filters
*/
getActiveFilters() {
const ret = [];
for (const { filter } of this._all) {
if (filter && filter.isActive()) {
ret.push(filter);
}
}
return ret;
}
/**
* Registers a filter, and returns a deregistration function
*/
add(filter) {
const subscription = filter.changes.subscribe(() => this.resetPageAndEmitFilterChange([filter]));
let hasUnregistered = false;
const registered = new RegisteredFilter(filter, () => {
if (hasUnregistered) {
return;
}
subscription.unsubscribe();
const matchIndex = this._all.findIndex(item => item.filter === filter);
if (matchIndex >= 0) {
this._all.splice(matchIndex, 1);
}
if (filter.isActive()) {
this.resetPageAndEmitFilterChange([]);
}
hasUnregistered = true;
});
this._all.push(registered);
if (filter.isActive()) {
this.resetPageAndEmitFilterChange([filter]);
}
return registered;
}
/**
* Accepts an item if it is accepted by all currently active filters
*/
accepts(item) {
for (const { filter } of this._all) {
if (filter && filter.isActive() && !filter.accepts(item)) {
return false;
}
}
return true;
}
resetPageAndEmitFilterChange(filters) {
this.stateDebouncer.changeStart();
// filtering may change the page number such that current page number doesn't exist in the filtered dataset.
// So here we always set the current page to 1 so that it'll fetch first page's data with the given filter.
this._page.current = 1;
this._change.next(filters);
this.stateDebouncer.changeDone();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FiltersProvider, deps: [{ token: Page }, { token: StateDebouncer }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FiltersProvider }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FiltersProvider, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: Page }, { type: StateDebouncer }] });
class RegisteredFilter {
constructor(filter, unregister) {
this.filter = filter;
this.unregister = unregister;
}
}
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridFilterRegistrar {
constructor(filters) {
this.filters = filters;
}
get filter() {
return this.registered && this.registered.filter;
}
ngOnDestroy() {
this.deleteFilter();
}
setFilter(filter) {
// If we previously had another filter, we unregister it
this.deleteFilter();
if (filter instanceof RegisteredFilter) {
this.registered = filter;
}
else if (filter) {
this.registered = this.filters.add(filter);
}
}
deleteFilter() {
if (this.registered) {
this.registered.unregister();
delete this.registered;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DatagridFilterRegistrar, deps: [{ token: FiltersProvider }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: DatagridFilterRegistrar, isStandalone: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DatagridFilterRegistrar, decorators: [{
type: Directive
}], ctorParameters: () => [{ type: FiltersProvider }] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class WrappedColumn {
ngAfterViewInit() {
// Create the cells view in memory, not the DOM.
this.columnView = this.templateRef.createEmbeddedView(null);
}
ngOnDestroy() {
this.columnView.destroy();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: WrappedColumn, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: WrappedColumn, isStandalone: false, selector: "dg-wrapped-column", viewQueries: [{ propertyName: "templateRef", first: true, predicate: ["columnPortal"], descendants: true }], ngImport: i0, template: `
<ng-template #columnPortal>
<ng-content></ng-content>
</ng-template>
`, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: WrappedColumn, decorators: [{
type: Component,
args: [{
selector: 'dg-wrapped-column',
template: `
<ng-template #columnPortal>
<ng-content></ng-content>
</ng-template>
`,
standalone: false,
}]
}], propDecorators: { templateRef: [{
type: ViewChild,
args: ['columnPortal']
}] } });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class Sort {
constructor(stateDebouncer) {
this.stateDebouncer = stateDebouncer;
/**
* Ascending order if false, descending if true
*/
this._reverse = false;
/**
* The Observable that lets other classes subscribe to sort changes
*/
this._change = new Subject();
}
get comparator() {
return this._comparator;
}
set comparator(value) {
this.stateDebouncer.changeStart();
this._comparator = value;
this.emitChange();
this.stateDebouncer.changeDone();
}
get reverse() {
return this._reverse;
}
set reverse(value) {
this.stateDebouncer.changeStart();
this._reverse = value;
this.emitChange();
this.stateDebouncer.changeDone();
}
// We do not want to expose the Subject itself, but the Observable which is read-only
get change() {
return this._change.asObservable();
}
/**
* Sets a comparator as the current one, or toggles reverse if the comparator is already used. The
* optional forceReverse input parameter allows to override that toggling behavior by sorting in
* reverse order if `true`.
*
* @memberof Sort
*/
toggle(sortBy, forceReverse) {
this.stateDebouncer.changeStart();
// We modify private properties directly, to batch the change event
if (this.comparator === sortBy) {
this._reverse = typeof forceReverse !== 'undefined' ? forceReverse || !this._reverse : !this._reverse;
}
else {
this._comparator = sortBy;
this._reverse = typeof forceReverse !== 'undefined' ? forceReverse : false;
}
this.emitChange();
this.stateDebouncer.changeDone();
}
/**
* Clears the current sorting order
*/
clear() {
this.comparator = null;
}
/**
* Compares two objects according to the current comparator
*/
compare(a, b) {
return (this.reverse ? -1 : 1) * this.comparator.compare(a, b);
}
emitChange() {
this._change.next(this);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: Sort, deps: [{ token: StateDebouncer }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: Sort }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: Sort, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: StateDebouncer }] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DetailService {
constructor(modalStackService) {
this.modalStackService = modalStackService;
this.preventScroll = false;
this.toggleState = false;
this._enabled = false;
this._state = new BehaviorSubject(this.toggleState);
}
get enabled() {
return this._enabled;
}
set enabled(state) {
this._enabled = state;
}
get preventFocusScroll() {
return this.preventScroll;
}
set preventFocusScroll(preventScroll) {
this.preventScroll = preventScroll;
}
get state() {
return this.cache;
}
get stateChange() {
return this._state.asObservable();
}
get isOpen() {
return this.toggleState === true;
}
open(item, button) {
this.cache = item;
this.button = button;
this.toggleState = true;
this._state.next(this.toggleState);
this.modalStackService.trackModalOpen(this);
}
close() {
this.toggleState = false;
this.returnFocus();
this._state.next(this.toggleState);
this.modalStackService.trackModalClose(this);
}
returnFocus() {
if (this.button) {
this.button.focus({ preventScroll: this.preventFocusScroll });
this.button = null;
}
}
toggle(item, button) {
if (this.isRowOpen(item) || !item) {
this.close();
}
else {
this.open(item, button);
}
}
isRowOpen(item) {
return !!(this.toggleState && this.cache === item);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DetailService, deps: [{ token: i1.ModalStackService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DetailService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DetailService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.ModalStackService }] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
var DatagridRenderStep;
(function (DatagridRenderStep) {
DatagridRenderStep[DatagridRenderStep["ALIGN_COLUMNS"] = 0] = "ALIGN_COLUMNS";
DatagridRenderStep[DatagridRenderStep["CALCULATE_MODE_ON"] = 1] = "CALCULATE_MODE_ON";
DatagridRenderStep[DatagridRenderStep["CALCULATE_MODE_OFF"] = 2] = "CALCULATE_MODE_OFF";
DatagridRenderStep[DatagridRenderStep["CLEAR_WIDTHS"] = 3] = "CLEAR_WIDTHS";
DatagridRenderStep[DatagridRenderStep["COMPUTE_COLUMN_WIDTHS"] = 4] = "COMPUTE_COLUMN_WIDTHS";
})(DatagridRenderStep || (DatagridRenderStep = {}));
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DatagridRenderOrganizer {
constructor() {
this._renderStep = new Subject();
this.alreadySized = false;
}
get renderStep() {
return this._renderStep.asObservable();
}
filterRenderSteps(step) {
return this.renderStep.pipe(filter(testStep => step === testStep));
}
resize() {
this._renderStep.next(DatagridRenderStep.CALCULATE_MODE_ON);
if (this.alreadySized) {
this._renderStep.next(DatagridRenderStep.CLEAR_WIDTHS);
}
this._renderStep.next(DatagridRenderStep.COMPUTE_COLUMN_WIDTHS);
this._renderStep.next(DatagridRenderStep.ALIGN_COLUMNS); // NOT USED
this.alreadySized = true;
this._renderStep.next(DatagridRenderStep.CALCULATE_MODE_OFF);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DatagridRenderOrganizer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DatagridRenderOrganizer }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DatagridRenderOrganizer, decorators: [{
type: Injectable
}] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
const MIN_COLUMN_WIDTH = 96;
// This service allows DatagridHeaderRenderer and ClrDatagridColumnSeparator
// to share column resize data with each other.
class ColumnResizerService {
constructor(el, domAdapter, organizer) {
this.el = el;
this.domAdapter = domAdapter;
this.organizer = organizer;
this._resizedBy = 0;
}
get resizedBy() {
return this._resizedBy;
}
get minColumnWidth() {
return this.domAdapter.minWidth(this.el.nativeElement) || MIN_COLUMN_WIDTH;
}
get maxResizeRange() {
return this.widthBeforeResize - this.minColumnWidth;
}
get widthAfterResize() {
return this.widthBeforeResize + this._resizedBy;
}
startResize() {
this._resizedBy = 0;
this.isWithinMaxResizeRange = true;
this.widthBeforeResize = this.domAdapter.clientRect(this.el.nativeElement).width;
}
endResize() {
this.organizer.resize();
}
calculateResize(resizedBy) {
// calculates the resize amount within the allowed range
if (resizedBy < -this.maxResizeRange) {
this._resizedBy = -this.maxResizeRange;
this.isWithinMaxResizeRange = false;
}
else {
this._resizedBy = resizedBy;
this.isWithinMaxResizeRange = true;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ColumnResizerService, deps: [{ token: i0.ElementRef }, { token: i2.DomAdapter }, { token: DatagridRenderOrganizer }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ColumnResizerService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ColumnResizerService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i2.DomAdapter }, { type: DatagridRenderOrganizer }] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
/**
* @description
* Internal datagrid service that holds a reference to the clr-dg-table element and exposes a method to get height.
*/
class TableSizeService {
constructor(platformId) {
this.platformId = platformId;
}
get tableRef() {
return this._tableRef;
}
set tableRef(element) {
this._tableRef = element;
}
set table(table) {
if (isPlatformBrowser(this.platformId) && table.nativeElement) {
this.tableRef = table.nativeElement.querySelector('.datagrid-table');
}
}
// Used when resizing columns to show the column border being dragged.
getColumnDragHeight() {
if (!this.tableRef) {
return null;
}
return `${this.tableRef.clientHeight}px`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TableSizeService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TableSizeService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TableSizeService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }] });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
// Default resize length on each keyboard move event
const KEYBOARD_RESIZE_LENGTH = 12;
class ClrDatagridColumnSeparator {
constructor(columnResizerService, renderer, ngZone, tableSizeService, commonString, document) {
this.columnResizerService = columnResizerService;
this.renderer = renderer;
this.ngZone = ngZone;
this.tableSizeService = tableSizeService;
this.commonString = commonString;
this.document = document;
this.columnSeparatorId = uniqueIdFactory();
this.resizeStartedOnKeyDown = false;
this.unlisteners = [];
}
get descriptionId() {
return `${this.columnSeparatorId}-aria-describedby`;
}
get resizeTrackerEl() {
return this.resizeTrackerRef.nativeElement;
}
get columnHandleEl() {
return this.columnHandleRef.nativeElement;
}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
this.unlisteners.push(this.renderer.listen(this.columnHandleEl, 'keydown', event => {
this.showTrackerOnFirstKeyDown(event);
this.moveTrackerOnKeyDown(event);
}));
this.unlisteners.push(this.renderer.listen(this.columnHandleEl, 'keyup', event => {
this.hideTrackerOnKeyUp(event);
}));
});
}
ngOnDestroy() {
this.unlisteners.forEach(unlistener => unlistener());
}
showTracker() {
this.columnResizerService.startResize();
const tableHeight = this.tableSizeService.getColumnDragHeight();
this.renderer.setStyle(this.resizeTrackerEl, 'height', tableHeight);
this.renderer.setStyle(this.resizeTrackerEl, 'display', 'block');
}
moveTracker(movedBy) {
this.columnResizerService.calculateResize(movedBy);
this.renderer.setStyle(this.resizeTrackerEl, 'transform', `translateX(${this.columnResizerService.resizedBy}px)`);
this.renderer.setStyle(this.document.body, 'cursor', 'col-resize');
this.redFlagTracker();
}
hideTracker() {
this.columnResizerService.endResize();
this.renderer.setStyle(this.resizeTrackerEl, 'display', 'none');
this.renderer.setStyle(this.resizeTrackerEl, 'transform', `translateX(0px)`);
this.renderer.setStyle(this.columnHandleEl, 'transform', `translateX(0px)`);
this.renderer.setStyle(this.document.body, 'cursor', 'auto');
}
showTrackerOnFirstKeyDown(event) {
if (!this.resizeStartedOnKeyDown && (this.isArrowLeftKeyEvent(event) || this.isArrowRightKeyEvent(event))) {
this.resizeStartedOnKeyDown = true;
this.renderer.addClass(this.resizeTrackerEl, 'on-arrow-key-resize');
this.showTracker();
}
}
moveTrackerOnKeyDown(event) {
if (this.isArrowLeftKeyEvent(event)) {
event.stopPropagation();
this.moveTracker(this.columnResizerService.resizedBy - KEYBOARD_RESIZE_LENGTH);
}
else if (this.isArrowRightKeyEvent(event)) {
event.stopPropagation();
this.moveTracker(this.columnResizerService.resizedBy + KEYBOARD_RESIZE_LENGTH);
}
}
hideTrackerOnKeyUp(event) {
if (this.resizeStartedOnKeyDown && (this.isArrowLeftKeyEvent(event) || this.isArrowRightKeyEvent(event))) {
this.resizeStartedOnKeyDown = false;
this.renderer.removeClass(this.resizeTrackerEl, 'on-arrow-key-resize');
this.hideTracker();
this.columnHandleEl.focus();
}
}
redFlagTracker() {
if (this.isWithinMaxResizeRange !== this.columnResizerService.isWithinMaxResizeRange) {
this.isWithinMaxResizeRange = this.columnResizerService.isWithinMaxResizeRange;
if (!this.isWithinMaxResizeRange) {
this.renderer.addClass(this.resizeTrackerEl, 'exceeded-max');
}
else {
this.renderer.removeClass(this.resizeTrackerEl, 'exceeded-max');
}
}
}
isArrowLeftKeyEvent(event) {
return event.key === Keys.ArrowLeft;
}
isArrowRightKeyEvent(event) {
return event.key === Keys.ArrowRight;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ClrDatagridColumnSeparator, deps: [{ token: ColumnResizerService }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: TableSizeService }, { token: i2.ClrCommonStringsService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: ClrDatagridColumnSeparator, isStandalone: false, selector: "clr-dg-column-separator", host: { properties: { "class.datagrid-column-separator": "true" } }, viewQueries: [{ propertyName: "resizeTrackerRef", first: true, predicate: ["resizeTracker"], descendants: true }, { propertyName: "columnHandleRef", first: true, predicate: ["columnHandle"], descendants: true }], ngImport: i0, template: `
<button
type="button"
class="datagrid-column-handle"
[attr.aria-label]="commonString.keys.columnSeparatorAriaLabel"
[attr.aria-describedby]="descriptionId"
cdkDrag
cdkDragLockAxis="x"
(cdkDragStarted)="showTracker()"
(cdkDragMoved)="moveTracker($event.distance.x)"
(cdkDragEnded)="hideTracker(); $event.source._dragRef.reset()"
#columnHandle
></button>
<span class="clr-sr-only" [attr.id]="descriptionId">
{{ commonString.keys.columnSeparatorDescription }}
</span>
<div class="datagrid-column-resize-tracker" #resizeTracker></div>
`, isInline: true, dependencies: [{ kind: "directive", type: i2.CdkDragModule_CdkDrag, selector: "[cdkDrag]" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ClrDatagridColumnSeparator, decorators: [{
type: Component,
args: [{
selector: 'clr-dg-column-separator',
template: `
<button
type="button"
class="datagrid-column-handle"
[attr.aria-label]="commonString.keys.columnSeparatorAriaLabel"
[attr.aria-describedby]="descriptionId"
cdkDrag
cdkDragLockAxis="x"
(cdkDragStarted)="showTracker()"
(cdkDragMoved)="moveTracker($event.distance.x)"
(cdkDragEnded)="hideTracker(); $event.source._dragRef.reset()"
#columnHandle
></button>
<span class="clr-sr-only" [attr.id]="descriptionId">
{{ commonString.keys.columnSeparatorDescription }}
</span>
<div class="datagrid-column-resize-tracker" #resizeTracker></div>
`,
host: {
'[class.datagrid-column-separator]': 'true',
},
standalone: false,
}]
}], ctorParameters: () => [{ type: ColumnResizerService }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: TableSizeService }, { type: i2.ClrCommonStringsService }, { type: undefined, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }], propDecorators: { resizeTrackerRef: [{
type: ViewChild,
args: ['resizeTracker']
}], columnHandleRef: [{
type: ViewChild,
args: ['columnHandle']
}] } });
/*
* Copyright (c) 2016-2026 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
class DefaultKeyNavigationStrategy {
constructor(utils) {
this.utils = utils;
}
keyUp(currentCellCoords) {
const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);
if (currentCellCoords.y === 0) {
return nextCellCoords;
}
nextCellCoords.y = currentCellCoords.y - 1;
const isActionCell = this.utils.isActionCell(currentCellCoords);
if (this.utils.isSingleCellExpandedRow(nextCellCoords.y) &&
!isActionCell &&
this.utils.isDetailsRow(nextCellCoords.y)) {
nextCellCoords.x = 0;
}
else if (this.utils.isDetailsRow(nextCellCoords.y)) {
if (isActionCell) {
nextCellCoords.y = nextCellCoords.y - 1;
}
else {
nextCellCoords.x = nextCellCoords.x - this.utils.actionCellCount(currentCellCoords.y);
}
}
return nextCellCoords;
}
keyDown(currentCellCoords) {
const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);
const numOfRows = this.utils.rows ? this.utils.rows.length - 1 : 0;
if (currentCellCoords.y >= numOfRows) {
return nextCellCoords;
}
const isActionCell = this.utils.isActionCell(currentCellCoords);
nextCellCoords.y = currentCellCoords.y + 1;
if (!isActionCell && this.utils.isRowReplaced(nextCellCoords.y)) {
nextCellCoords.y = nextCellCoords.y + 1;
nextCellCoords.x = this.utils.isSingleCellExpandedRow(nextCellCoords.y)
? 0
: nextCellCoords.x - this.utils.actionCellCount(currentCellCoords.y);
}
return nextCellCoords;
}
keyLeft(currentCellCoords) {
const nextCellCoords = this.utils.createNextCellCoords(currentCellCoords);
if (currentCellCoords.x === 0) {
return nextCellCoords;
}
nextCellCoords.x = currentCellCoords.x - 1;
return nextCellCoords;