dfx-bootstrap-table
Version:
Angular table CDK implementation for Bootstrap with filtering, sorting and pagination.
801 lines (793 loc) • 95.9 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, EventEmitter, inject, booleanAttribute, Output, Input, Directive, HostBinding, ChangeDetectorRef, ElementRef, ANIMATION_MODULE_TYPE, signal, ChangeDetectionStrategy, ViewEncapsulation, Component, NgModule, Injectable, Optional, SkipSelf, numberAttribute, Inject } from '@angular/core';
import { ReplaySubject, Subject, merge, BehaviorSubject, of, combineLatest } from 'rxjs';
import { FocusMonitor, AriaDescriber, _IdGenerator } from '@angular/cdk/a11y';
import { SPACE, ENTER } from '@angular/cdk/keycodes';
import { CdkCellDef, CdkHeaderCellDef, CdkFooterCellDef, CdkColumnDef, CdkHeaderCell, CdkFooterCell, CdkCell, DataSource, CdkHeaderRowDef, CdkFooterRowDef, CdkRowDef, CdkHeaderRow, CdkCellOutlet, CdkFooterRow, CdkRow, CdkNoDataRow, CdkTable, HeaderRowOutlet, DataRowOutlet, NoDataRowOutlet, FooterRowOutlet, CDK_TABLE, STICKY_POSITIONING_LISTENER, CdkTextColumn, CdkTableModule } from '@angular/cdk/table';
import * as i2 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import { _isNumberValue } from '@angular/cdk/coercion';
import { map } from 'rxjs/operators';
/** @docs-private */
function getSortDuplicateSortableIdError(id) {
return Error(`Cannot have two NgbSortables with the same id (${id}).`);
}
/** @docs-private */
function getSortHeaderNotContainedWithinSortError() {
return Error(`NgbSortHeader must be placed within a parent element with the NgbSort directive.`);
}
/** @docs-private */
function getSortHeaderMissingIdError() {
return Error(`NgbSortHeader must be provided with a unique id.`);
}
/** @docs-private */
function getSortInvalidDirectionError(direction) {
return Error(`${direction} is not a valid sort direction ('asc' or 'desc').`);
}
/**
* @license
* Original work Copyright Google LLC All Rights Reserved.
* Modified work Copyright DatePoll-Systems
*
* 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
*/
/** Injection token to be used to override the default options for `ngb-sort`. */
const NGB_SORT_DEFAULT_OPTIONS = new InjectionToken('NGB_SORT_DEFAULT_OPTIONS');
/** Container for NgbSortable to manage the sort state and provide default sort parameters. */
class NgbSort {
_initializedStream = new ReplaySubject(1);
/** Collection of all registered sortables that this directive manages. */
sortables = new Map();
/** Used to notify any child components listening to state changes. */
_stateChanges = new Subject();
/** The id of the most recently sorted NgbSortable. */
active = '';
/**
* The direction to set when an NgbSortable is initially sorted.
* May be overridden by the NgbSortable's sort start.
*/
start = 'asc';
/** The sort direction of the currently active NgbSortable. */
get direction() {
return this._direction;
}
set direction(direction) {
if (direction && direction !== 'asc' && direction !== 'desc' && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getSortInvalidDirectionError(direction);
}
this._direction = direction;
}
_direction = '';
/**
* Whether to disable the user from clearing the sort by finishing the sort direction cycle.
* May be overridden by the NgbSortable's disable clear input.
*/
disableClear = false;
/** Whether the sortable is disabled. */
disabled = false;
/** Event emitted when the user changes either the active sort or sort direction. */
sortChange = new EventEmitter();
/** Emits when the paginator is initialized. */
initialized = this._initializedStream;
_defaultOptions = inject(NGB_SORT_DEFAULT_OPTIONS, { optional: true });
/**
* Register function to be used by the contained NgbSortables. Adds the NgbSortable to the
* collection of NgbSortables.
*/
register(sortable) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!sortable.id) {
throw getSortHeaderMissingIdError();
}
if (this.sortables.has(sortable.id)) {
throw getSortDuplicateSortableIdError(sortable.id);
}
}
this.sortables.set(sortable.id, sortable);
}
/**
* Unregister function to be used by the contained NgbSortable. Removes the NgbSortable from the
* collection of contained NgbSortable.
*/
deregister(sortable) {
this.sortables.delete(sortable.id);
}
/** Sets the active sort id and determines the new sort direction. */
sort(sortable) {
if (this.active != sortable.id) {
this.active = sortable.id;
this.direction = sortable.start ?? this.start;
}
else {
this.direction = this.getNextSortDirection(sortable);
}
this.sortChange.emit({ active: this.active, direction: this.direction });
}
/** Returns the next sort direction of the active sortable, checking for potential overrides. */
getNextSortDirection(sortable) {
if (!sortable) {
return '';
}
// Get the sort direction cycle with the potential sortable overrides.
const disableClear = sortable?.disableClear ?? this.disableClear ?? !!this._defaultOptions?.disableClear;
const sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear);
// Get and return the next direction in the cycle
let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1;
if (nextDirectionIndex >= sortDirectionCycle.length) {
nextDirectionIndex = 0;
}
return sortDirectionCycle[nextDirectionIndex];
}
ngOnInit() {
this._initializedStream.next();
}
ngOnChanges() {
this._stateChanges.next();
}
ngOnDestroy() {
this._stateChanges.complete();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbSort, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "22.0.0", type: NgbSort, isStandalone: true, selector: "[ngb-sort]", inputs: { active: ["ngbSortActive", "active"], start: ["ngbSortStart", "start"], direction: ["ngbSortDirection", "direction"], disableClear: ["ngbSortDisableClear", "disableClear", booleanAttribute], disabled: ["ngbSortDisabled", "disabled", booleanAttribute] }, outputs: { sortChange: "ngbSortChange" }, host: { classAttribute: "ngb-sort" }, exportAs: ["ngbSort"], usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbSort, decorators: [{
type: Directive,
args: [{
selector: '[ngb-sort]',
exportAs: 'ngbSort',
host: { class: 'ngb-sort' },
}]
}], propDecorators: { active: [{
type: Input,
args: [{ alias: 'ngbSortActive' }]
}], start: [{
type: Input,
args: ['ngbSortStart']
}], direction: [{
type: Input,
args: ['ngbSortDirection']
}], disableClear: [{
type: Input,
args: [{ alias: 'ngbSortDisableClear', transform: booleanAttribute }]
}], disabled: [{
type: Input,
args: [{ alias: 'ngbSortDisabled', transform: booleanAttribute }]
}], sortChange: [{
type: Output,
args: ['ngbSortChange']
}] } });
/** Returns the sort direction cycle to use given the provided parameters of order and clear. */
function getSortDirectionCycle(start, disableClear) {
const sortOrder = ['asc', 'desc'];
if (start == 'desc') {
sortOrder.reverse();
}
if (!disableClear) {
sortOrder.push('');
}
return sortOrder;
}
/**
* @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
*/
/**
* @license
* Original work Copyright Google LLC All Rights Reserved.
* Modified work Copyright DatePoll-Systems
*
* 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
*/
/**
* Cell definition for the ngb-table.
* Captures the template of a column's data row cell as well as cell-specific properties.
*/
class NgbCellDef extends CdkCellDef {
// leveraging syntactic-sugar syntax when we use *ngbCellDef
ngbCellDefTable;
// ngTemplateContextGuard flag to help with the Language Service
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static ngTemplateContextGuard(dir, ctx) {
return true;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbCellDef, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbCellDef, isStandalone: true, selector: "[ngbCellDef]", inputs: { ngbCellDefTable: "ngbCellDefTable" }, providers: [{ provide: CdkCellDef, useExisting: NgbCellDef }], usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbCellDef, decorators: [{
type: Directive,
args: [{
selector: '[ngbCellDef]',
providers: [{ provide: CdkCellDef, useExisting: NgbCellDef }],
standalone: true,
}]
}], propDecorators: { ngbCellDefTable: [{
type: Input
}] } });
/**
* Header cell definition for the ngb-table.
* Captures the template of a column's header cell and as well as cell-specific properties.
*/
class NgbHeaderCellDef extends CdkHeaderCellDef {
whiteSpace = 'nowrap';
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbHeaderCellDef, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbHeaderCellDef, isStandalone: true, selector: "[ngbHeaderCellDef]", inputs: { whiteSpace: "whiteSpace" }, host: { properties: { "style.white-space": "this.whiteSpace" } }, providers: [{ provide: CdkHeaderCellDef, useExisting: NgbHeaderCellDef }], usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbHeaderCellDef, decorators: [{
type: Directive,
args: [{
selector: '[ngbHeaderCellDef]',
providers: [{ provide: CdkHeaderCellDef, useExisting: NgbHeaderCellDef }],
standalone: true,
}]
}], propDecorators: { whiteSpace: [{
type: HostBinding,
args: ['style.white-space']
}, {
type: Input
}] } });
/**
* Footer cell definition for the ngb-table.
* Captures the template of a column's footer cell and as well as cell-specific properties.
*/
class NgbFooterCellDef extends CdkFooterCellDef {
whiteSpace = 'nowrap';
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbFooterCellDef, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbFooterCellDef, isStandalone: true, selector: "[ngbFooterCellDef]", inputs: { whiteSpace: "whiteSpace" }, host: { properties: { "style.white-space": "this.whiteSpace" } }, providers: [{ provide: CdkFooterCellDef, useExisting: NgbFooterCellDef }], usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbFooterCellDef, decorators: [{
type: Directive,
args: [{
selector: '[ngbFooterCellDef]',
providers: [{ provide: CdkFooterCellDef, useExisting: NgbFooterCellDef }],
standalone: true,
}]
}], propDecorators: { whiteSpace: [{
type: HostBinding,
args: ['style.white-space']
}, {
type: Input
}] } });
const ngbSortHeaderColumnDef = new InjectionToken('NGB_SORT_HEADER_COLUMN_DEF');
/**
* Column definition for the ngb-table.
* Defines a set of cells available for a table column.
*/
class NgbColumnDef extends CdkColumnDef {
/** Unique name for this column. */
get name() {
return this._name;
}
set name(name) {
this._setNameInput(name);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbColumnDef, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbColumnDef, isStandalone: true, selector: "[ngbColumnDef]", inputs: { name: ["ngbColumnDef", "name"] }, providers: [
{ provide: CdkColumnDef, useExisting: NgbColumnDef },
{ provide: ngbSortHeaderColumnDef, useExisting: NgbColumnDef },
], usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbColumnDef, decorators: [{
type: Directive,
args: [{
selector: '[ngbColumnDef]',
providers: [
{ provide: CdkColumnDef, useExisting: NgbColumnDef },
{ provide: ngbSortHeaderColumnDef, useExisting: NgbColumnDef },
],
standalone: true,
}]
}], propDecorators: { name: [{
type: Input,
args: ['ngbColumnDef']
}] } });
/** Header cell template container that adds the right classes and role. */
class NgbHeaderCell extends CdkHeaderCell {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbHeaderCell, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbHeaderCell, isStandalone: true, selector: "ngb-header-cell, th[ngb-header-cell]", host: { attributes: { "role": "columnheader" } }, usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbHeaderCell, decorators: [{
type: Directive,
args: [{
selector: 'ngb-header-cell, th[ngb-header-cell]',
host: {
role: 'columnheader',
},
standalone: true,
}]
}] });
/** Footer cell template container that adds the right classes and role. */
class NgbFooterCell extends CdkFooterCell {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbFooterCell, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbFooterCell, isStandalone: true, selector: "ngb-footer-cell, td[ngb-footer-cell]", usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbFooterCell, decorators: [{
type: Directive,
args: [{
selector: 'ngb-footer-cell, td[ngb-footer-cell]',
standalone: true,
}]
}] });
/** Cell template container that adds the right classes and role. */
class NgbCell extends CdkCell {
whiteSpace = 'nowrap';
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbCell, deps: null, target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: NgbCell, isStandalone: true, selector: "ngb-cell, td[ngb-cell]", inputs: { whiteSpace: "whiteSpace" }, host: { properties: { "style.white-space": "this.whiteSpace" } }, usesInheritance: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbCell, decorators: [{
type: Directive,
args: [{
selector: 'ngb-cell, td[ngb-cell]',
standalone: true,
}]
}], propDecorators: { whiteSpace: [{
type: HostBinding,
args: ['style.white-space']
}, {
type: Input
}] } });
/**
* @license
* Original work Copyright Google LLC All Rights Reserved.
* Modified work Copyright DatePoll-Systems
*
* 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
*/
/**
* Applies sorting behavior (click to change sort) and styles to an element, including an
* arrow to display the current sort direction.
*
* Must be provided with an id and contained within a parent NgbSort directive.
*
* If used on header cells in a CdkTable, it will automatically default its id from its containing
* column definition.
*/
class NgbSortHeader {
_sort = inject(NgbSort, { optional: true });
_columnDef = inject(ngbSortHeaderColumnDef, {
optional: true,
});
_changeDetectorRef = inject(ChangeDetectorRef);
_focusMonitor = inject(FocusMonitor);
_elementRef = inject(ElementRef);
_ariaDescriber = inject(AriaDescriber, { optional: true });
_renderChanges;
_animationModule = inject(ANIMATION_MODULE_TYPE, { optional: true });
/**
* Indicates which state was just cleared from the sort header.
* Will be reset on the next interaction. Used for coordinating animations.
*/
_recentlyCleared = signal(null, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_recentlyCleared" }] : /* istanbul ignore next */ []));
/**
* The element with role="button" inside this component's view. We need this
* in order to apply a description with AriaDescriber.
*/
_sortButton;
/**
* ID of this sort header. If used within the context of a CdkColumnDef, this will default to
* the column's name.
*/
id;
/** Sets the position of the arrow that displays when sorted. */
arrowPosition = 'after';
/** Overrides the sort start value of the containing NgbSort for this NgbSortable. */
start;
/** whether the sort header is disabled. */
disabled = false;
/**
* Description applied to NgbSortHeader's button element with aria-describedby. This text should
* describe the action that will occur when the user clicks the sort header.
*/
get sortActionDescription() {
return this._sortActionDescription;
}
set sortActionDescription(value) {
this._updateSortActionDescription(value);
}
// Default the action description to "Sort" because it's better than nothing.
// Without a description, the button's label comes from the sort header text content,
// which doesn't give any indication that it performs a sorting operation.
_sortActionDescription = 'Sort';
/** Overrides the disable clear value of the containing NgbSort for this NgbSortable. */
disableClear;
constructor() {
const defaultOptions = inject(NGB_SORT_DEFAULT_OPTIONS, {
optional: true,
});
if (!this._sort && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getSortHeaderNotContainedWithinSortError();
}
if (defaultOptions?.arrowPosition) {
this.arrowPosition = defaultOptions?.arrowPosition;
}
}
ngOnInit() {
if (!this.id && this._columnDef) {
this.id = this._columnDef.name;
}
this._sort.register(this);
this._renderChanges = merge(this._sort._stateChanges, this._sort.sortChange).subscribe(() => this._changeDetectorRef.markForCheck());
this._sortButton = this._elementRef.nativeElement.querySelector('.ngb-sort-header-container');
this._updateSortActionDescription(this._sortActionDescription);
}
ngAfterViewInit() {
// We use the focus monitor because we also want to style
// things differently based on the focus origin.
this._focusMonitor.monitor(this._elementRef, true).subscribe(() => this._recentlyCleared.set(null));
}
ngOnDestroy() {
this._focusMonitor.stopMonitoring(this._elementRef);
this._sort.deregister(this);
this._renderChanges?.unsubscribe();
if (this._sortButton) {
this._ariaDescriber?.removeDescription(this._sortButton, this._sortActionDescription);
}
}
/** Triggers the sort on this sort header and removes the indicator hint. */
_toggleOnInteraction() {
if (!this._isDisabled()) {
const wasSorted = this._isSorted();
const prevDirection = this._sort.direction;
this._sort.sort(this);
this._recentlyCleared.set(wasSorted && !this._isSorted() ? prevDirection : null);
}
}
_handleKeydown(event) {
if (event.keyCode === SPACE || event.keyCode === ENTER) {
event.preventDefault();
this._toggleOnInteraction();
}
}
/** Whether this NgbSortHeader is currently sorted in either ascending or descending order. */
_isSorted() {
return this._sort.active == this.id && (this._sort.direction === 'asc' || this._sort.direction === 'desc');
}
_isDisabled() {
return this._sort.disabled || this.disabled;
}
/**
* Gets the aria-sort attribute that should be applied to this sort header. If this header
* is not sorted, returns null so that the attribute is removed from the host element. Aria spec
* says that the aria-sort property should only be present on one header at a time, so removing
* ensures this is true.
*/
_getAriaSortAttribute() {
if (!this._isSorted()) {
return 'none';
}
return this._sort.direction == 'asc' ? 'ascending' : 'descending';
}
/** Whether the arrow inside the sort header should be rendered. */
_renderArrow() {
return !this._isDisabled() || this._isSorted();
}
_updateSortActionDescription(newDescription) {
// We use AriaDescriber for the sort button instead of setting an `aria-label` because some
// screen readers (notably VoiceOver) will read both the column header *and* the button's label
// for every *cell* in the table, creating a lot of unnecessary noise.
// If _sortButton is undefined, the component hasn't been initialized yet so there's
// nothing to update in the DOM.
if (this._sortButton) {
// removeDescription will no-op if there is no existing message.
// TODO(jelbourn): remove optional chaining when AriaDescriber is required.
this._ariaDescriber?.removeDescription(this._sortButton, this._sortActionDescription);
this._ariaDescriber?.describe(this._sortButton, newDescription);
}
this._sortActionDescription = newDescription;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbSortHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: NgbSortHeader, isStandalone: true, selector: "[ngb-sort-header]", inputs: { id: ["ngb-sort-header", "id"], arrowPosition: "arrowPosition", start: "start", disabled: ["disabled", "disabled", booleanAttribute], sortActionDescription: "sortActionDescription", disableClear: ["disableClear", "disableClear", booleanAttribute] }, host: { listeners: { "click": "_toggleOnInteraction()", "keydown": "_handleKeydown($event)", "mouseleave": "_recentlyCleared.set(null)" }, properties: { "attr.aria-sort": "_getAriaSortAttribute()", "class.ngb-sort-header-disabled": "_isDisabled()" }, classAttribute: "ngb-sort-header" }, exportAs: ["ngbSortHeader"], ngImport: i0, template: "<!--\n We set the `tabindex` on an element inside the table header, rather than the header itself,\n because of a bug in NVDA where having a `tabindex` on a `th` breaks keyboard navigation in the\n table (see https://github.com/nvaccess/nvda/issues/7718). This allows for the header to both\n be focusable, and have screen readers read out its `aria-sort` state. We prefer this approach\n over having a button with an `aria-label` inside the header, because the button's `aria-label`\n will be read out as the user is navigating the table's cell (see #13012).\n\n The approach is based off of: https://dequeuniversity.com/library/aria/tables/sf-sortable-grid\n-->\n<div\n class=\"ngb-sort-header-container ngb-focus-indicator\"\n [class.ngb-sort-header-sorted]=\"_isSorted()\"\n [class.ngb-sort-header-position-before]=\"arrowPosition === 'before'\"\n [class.ngb-sort-header-descending]=\"this._sort.direction === 'desc'\"\n [class.ngb-sort-header-ascending]=\"this._sort.direction === 'asc'\"\n [class.ngb-sort-header-recently-cleared-ascending]=\"_recentlyCleared() === 'asc'\"\n [class.ngb-sort-header-recently-cleared-descending]=\"_recentlyCleared() === 'desc'\"\n [class.ngb-sort-header-animations-disabled]=\"_animationModule === 'NoopAnimations'\"\n [attr.tabindex]=\"_isDisabled() ? null : 0\"\n [attr.role]=\"_isDisabled() ? null : 'button'\">\n <!--\n TODO(crisbeto): this div isn't strictly necessary, but we have to keep it due to a large\n number of screenshot diff failures. It should be removed eventually. Note that the difference\n isn't visible with a shorter header, but once it breaks up into multiple lines, this element\n causes it to be center-aligned, whereas removing it will keep the text to the left.\n -->\n <div class=\"ngb-sort-header-content\">\n <ng-content></ng-content>\n </div>\n\n <!-- Disable animations while a current animation is running -->\n @if (_renderArrow()) {\n <div class=\"ngb-sort-header-arrow\">\n <svg viewBox=\"0 -960 960 960\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M440-240v-368L296-464l-56-56 240-240 240 240-56 56-144-144v368h-80Z\" />\n </svg>\n </div>\n }\n</div>\n", styles: [".ngb-sort-header-container{display:flex;cursor:pointer;align-items:center;letter-spacing:normal;outline:0}[ngb-sort-header].cdk-keyboard-focused .ngb-sort-header-container,[ngb-sort-header].cdk-program-focused .ngb-sort-header-container{border-bottom:solid 1px currentColor}.ngb-sort-header-disabled .ngb-sort-header-container{cursor:default}.ngb-sort-header-content{text-align:center;display:flex;align-items:center}.ngb-sort-header-position-before{flex-direction:row-reverse}@keyframes ngb-sort-header-recently-cleared-ascending{0%{transform:translateY(0);opacity:1}to{transform:translateY(-25%);opacity:0}}@keyframes ngb-sort-header-recently-cleared-descending{0%{transform:translateY(0) rotate(180deg);opacity:1}to{transform:translateY(25%) rotate(180deg);opacity:0}}.ngb-sort-header-arrow{height:12px;width:12px;position:relative;transition:transform 225ms cubic-bezier(.4,0,.2,1),opacity 225ms cubic-bezier(.4,0,.2,1);opacity:0;overflow:visible}.ngb-sort-header:hover .ngb-sort-header-arrow{opacity:.54}.ngb-sort-header .ngb-sort-header-sorted .ngb-sort-header-arrow{opacity:1}.ngb-sort-header-descending .ngb-sort-header-arrow{transform:rotate(180deg)}.ngb-sort-header-recently-cleared-ascending .ngb-sort-header-arrow{transform:translateY(-25%)}.ngb-sort-header-recently-cleared-ascending .ngb-sort-header-arrow{transition:none;animation:_ngb-sort-header-recently-cleared-ascending 225ms cubic-bezier(.4,0,.2,1) forwards}.ngb-sort-header-recently-cleared-descending .ngb-sort-header-arrow{transition:none;animation:_ngb-sort-header-recently-cleared-descending 225ms cubic-bezier(.4,0,.2,1) forwards}.ngb-sort-header-animations-disabled .ngb-sort-header-arrow{transition-duration:0ms;animation-duration:0ms}.ngb-sort-header-arrow svg{width:24px;height:24px;fill:currentColor;position:absolute;top:50%;left:50%;margin:-12px 0 0 -12px;transform:translateZ(0)}.ngb-sort-header-arrow,[dir=rtl] .ngb-sort-header-position-before .ngb-sort-header-arrow{margin:0 0 0 6px}.ngb-sort-header-position-before .ngb-sort-header-arrow,[dir=rtl] .ngb-sort-header-arrow{margin:0 6px 0 0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbSortHeader, decorators: [{
type: Component,
args: [{ selector: '[ngb-sort-header]', exportAs: 'ngbSortHeader', host: {
class: 'ngb-sort-header',
'(click)': '_toggleOnInteraction()',
'(keydown)': '_handleKeydown($event)',
'(mouseleave)': '_recentlyCleared.set(null)',
'[attr.aria-sort]': '_getAriaSortAttribute()',
'[class.ngb-sort-header-disabled]': '_isDisabled()',
}, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n We set the `tabindex` on an element inside the table header, rather than the header itself,\n because of a bug in NVDA where having a `tabindex` on a `th` breaks keyboard navigation in the\n table (see https://github.com/nvaccess/nvda/issues/7718). This allows for the header to both\n be focusable, and have screen readers read out its `aria-sort` state. We prefer this approach\n over having a button with an `aria-label` inside the header, because the button's `aria-label`\n will be read out as the user is navigating the table's cell (see #13012).\n\n The approach is based off of: https://dequeuniversity.com/library/aria/tables/sf-sortable-grid\n-->\n<div\n class=\"ngb-sort-header-container ngb-focus-indicator\"\n [class.ngb-sort-header-sorted]=\"_isSorted()\"\n [class.ngb-sort-header-position-before]=\"arrowPosition === 'before'\"\n [class.ngb-sort-header-descending]=\"this._sort.direction === 'desc'\"\n [class.ngb-sort-header-ascending]=\"this._sort.direction === 'asc'\"\n [class.ngb-sort-header-recently-cleared-ascending]=\"_recentlyCleared() === 'asc'\"\n [class.ngb-sort-header-recently-cleared-descending]=\"_recentlyCleared() === 'desc'\"\n [class.ngb-sort-header-animations-disabled]=\"_animationModule === 'NoopAnimations'\"\n [attr.tabindex]=\"_isDisabled() ? null : 0\"\n [attr.role]=\"_isDisabled() ? null : 'button'\">\n <!--\n TODO(crisbeto): this div isn't strictly necessary, but we have to keep it due to a large\n number of screenshot diff failures. It should be removed eventually. Note that the difference\n isn't visible with a shorter header, but once it breaks up into multiple lines, this element\n causes it to be center-aligned, whereas removing it will keep the text to the left.\n -->\n <div class=\"ngb-sort-header-content\">\n <ng-content></ng-content>\n </div>\n\n <!-- Disable animations while a current animation is running -->\n @if (_renderArrow()) {\n <div class=\"ngb-sort-header-arrow\">\n <svg viewBox=\"0 -960 960 960\" focusable=\"false\" aria-hidden=\"true\">\n <path d=\"M440-240v-368L296-464l-56-56 240-240 240 240-56 56-144-144v368h-80Z\" />\n </svg>\n </div>\n }\n</div>\n", styles: [".ngb-sort-header-container{display:flex;cursor:pointer;align-items:center;letter-spacing:normal;outline:0}[ngb-sort-header].cdk-keyboard-focused .ngb-sort-header-container,[ngb-sort-header].cdk-program-focused .ngb-sort-header-container{border-bottom:solid 1px currentColor}.ngb-sort-header-disabled .ngb-sort-header-container{cursor:default}.ngb-sort-header-content{text-align:center;display:flex;align-items:center}.ngb-sort-header-position-before{flex-direction:row-reverse}@keyframes ngb-sort-header-recently-cleared-ascending{0%{transform:translateY(0);opacity:1}to{transform:translateY(-25%);opacity:0}}@keyframes ngb-sort-header-recently-cleared-descending{0%{transform:translateY(0) rotate(180deg);opacity:1}to{transform:translateY(25%) rotate(180deg);opacity:0}}.ngb-sort-header-arrow{height:12px;width:12px;position:relative;transition:transform 225ms cubic-bezier(.4,0,.2,1),opacity 225ms cubic-bezier(.4,0,.2,1);opacity:0;overflow:visible}.ngb-sort-header:hover .ngb-sort-header-arrow{opacity:.54}.ngb-sort-header .ngb-sort-header-sorted .ngb-sort-header-arrow{opacity:1}.ngb-sort-header-descending .ngb-sort-header-arrow{transform:rotate(180deg)}.ngb-sort-header-recently-cleared-ascending .ngb-sort-header-arrow{transform:translateY(-25%)}.ngb-sort-header-recently-cleared-ascending .ngb-sort-header-arrow{transition:none;animation:_ngb-sort-header-recently-cleared-ascending 225ms cubic-bezier(.4,0,.2,1) forwards}.ngb-sort-header-recently-cleared-descending .ngb-sort-header-arrow{transition:none;animation:_ngb-sort-header-recently-cleared-descending 225ms cubic-bezier(.4,0,.2,1) forwards}.ngb-sort-header-animations-disabled .ngb-sort-header-arrow{transition-duration:0ms;animation-duration:0ms}.ngb-sort-header-arrow svg{width:24px;height:24px;fill:currentColor;position:absolute;top:50%;left:50%;margin:-12px 0 0 -12px;transform:translateZ(0)}.ngb-sort-header-arrow,[dir=rtl] .ngb-sort-header-position-before .ngb-sort-header-arrow{margin:0 0 0 6px}.ngb-sort-header-position-before .ngb-sort-header-arrow,[dir=rtl] .ngb-sort-header-arrow{margin:0 6px 0 0}\n"] }]
}], ctorParameters: () => [], propDecorators: { id: [{
type: Input,
args: ['ngb-sort-header']
}], arrowPosition: [{
type: Input
}], start: [{
type: Input
}], disabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], sortActionDescription: [{
type: Input
}], disableClear: [{
type: Input,
args: [{ transform: booleanAttribute }]
}] } });
/**
* @license
* Original work Copyright Google LLC All Rights Reserved.
* Modified work Copyright DatePoll-Systems
*
* 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
*/
const EXPORTED_DECLARATIONS$1 = [NgbSort, NgbSortHeader];
class DfxSortModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DfxSortModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.0", ngImport: i0, type: DfxSortModule, imports: [NgbSort, NgbSortHeader], exports: [NgbSort, NgbSortHeader] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DfxSortModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DfxSortModule, decorators: [{
type: NgModule,
args: [{
imports: EXPORTED_DECLARATIONS$1,
exports: EXPORTED_DECLARATIONS$1,
}]
}] });
/**
* To modify the labels and text displayed, create a new instance of NgbPaginatorIntl and
* include it in a custom provider
*/
class NgbPaginatorIntl {
/**
* Stream to emit from when labels are changed. Use this to notify components when the labels have
* changed after initialization.
*/
changes = new Subject();
/** A label for the page size selector. */
itemsPerPageLabel = 'Items per page:';
/** A label for the button that increments the current page. */
nextPageLabel = 'Next page';
/** A label for the button that decrements the current page. */
previousPageLabel = 'Previous page';
/** A label for the button that moves to the first page. */
firstPageLabel = 'First page';
/** A label for the button that moves to the last page. */
lastPageLabel = 'Last page';
/** A label for the range of items within the current page and the length of the whole list. */
getRangeLabel = (page, pageSize, length) => {
if (length == 0 || pageSize == 0) {
return `0 of ${length}`;
}
length = Math.max(length, 0);
const startIndex = page * pageSize;
// If the start index exceeds the list length, do not try and fix the end index to the end.
const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
return `${startIndex + 1} – ${endIndex} of ${length}`;
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbPaginatorIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbPaginatorIntl, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbPaginatorIntl, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/** @docs-private */
function NGB_PAGINATOR_INTL_PROVIDER_FACTORY(parentIntl) {
return parentIntl || new NgbPaginatorIntl();
}
/** @docs-private */
const NGB_PAGINATOR_INTL_PROVIDER = {
// If there is already an NgbPaginatorIntl available, use that. Otherwise, provide a new one.
provide: NgbPaginatorIntl,
deps: [[new Optional(), new SkipSelf(), NgbPaginatorIntl]],
useFactory: NGB_PAGINATOR_INTL_PROVIDER_FACTORY,
};
/** The default page size if there is no page size and there are no provided page size options. */
const DEFAULT_PAGE_SIZE = 50;
/** Injection token that can be used to provide the default options for the paginator module. */
const NGB_PAGINATOR_DEFAULT_OPTIONS = new InjectionToken('NGB_PAGINATOR_DEFAULT_OPTIONS');
class NgbPaginator {
_intl;
_changeDetectorRef;
/** ID for the DOM node containing the paginator's items per page label. */
_pageSizeLabelId = inject(_IdGenerator).getId('ngb-paginator-page-size-label-');
_intlChanges;
_isInitialized = false;
_initializedStream = new ReplaySubject(1);
/** The zero-based page index of the displayed list of items. Defaulted to 0. */
get pageIndex() {
return this._pageIndex;
}
set pageIndex(value) {
this._pageIndex = Math.max(value || 0, 0);
this._changeDetectorRef.markForCheck();
}
_pageIndex = 0;
/** The length of the total number of items that are being paginated. Defaulted to 0. */
get length() {
return this._length;
}
set length(value) {
this._length = value || 0;
this._changeDetectorRef.markForCheck();
}
_length = 0;
/** Number of items to display on a page. By default set to 50. */
get pageSize() {
return this._pageSize;
}
set pageSize(value) {
this._pageSize = Math.max(value || 0, 0);
this._updateDisplayedPageSizeOptions();
}
_pageSize;
/** The set of provided page size options to display to the user. */
get pageSizeOptions() {
return this._pageSizeOptions;
}
set pageSizeOptions(value) {
this._pageSizeOptions = (value || []).map((p) => numberAttribute(p, 0));
this._updateDisplayedPageSizeOptions();
}
_pageSizeOptions = [];
/** Whether to hide the page size selection UI from the user. */
hidePageSize = false;
/** Whether to show the first/last buttons UI to the user. */
showFirstLastButtons = false;
/** Whether the paginator is disabled. */
disabled = false;
/**
* The paginator display size.
*
* Bootstrap currently supports small and large sizes.
*/
size;
/** Event emitted when the paginator changes the page size or page index. */
page = new EventEmitter();
/** Displayed set of page size options. Will be sorted and include current page size. */
_displayedPageSizeOptions;
/** Emits when the paginator is initialized. */
initialized = this._initializedStream;
constructor(
// eslint-disable-next-line @angular-eslint/prefer-inject
_intl,
// eslint-disable-next-line @angular-eslint/prefer-inject
_changeDetectorRef,
// eslint-disable-next-line @angular-eslint/prefer-inject
defaults) {
this._intl = _intl;
this._changeDetectorRef = _changeDetectorRef;
this._intlChanges = _intl.changes.subscribe(() => this._changeDetectorRef.markForCheck());
if (defaults) {
const { pageSize, pageSizeOptions, hidePageSize, showFirstLastButtons } = defaults;
if (pageSize != null) {
this.pageSize = pageSize;
}
if (pageSizeOptions != null) {
this.pageSizeOptions = pageSizeOptions;
}
if (hidePageSize != null) {
this.hidePageSize = hidePageSize;
}
if (showFirstLastButtons != null) {
this.showFirstLastButtons = showFirstLastButtons;
}
}
}
ngOnInit() {
this._isInitialized = true;
this._updateDisplayedPageSizeOptions();
this._initializedStream.next();
}
ngOnDestroy() {
this._initializedStream.complete();
this._intlChanges.unsubscribe();
}
/** Advances to the next page if it exists. */
nextPage() {
if (!this.hasNextPage()) {
return;
}
const previousPageIndex = this.pageIndex;
this.pageIndex = this.pageIndex + 1;
this._emitPageEvent(previousPageIndex);
}
/** Move back to the previous page if it exists. */
previousPage() {
if (!this.hasPreviousPage()) {
return;
}
const previousPageIndex = this.pageIndex;
this.pageIndex = this.pageIndex - 1;
this._emitPageEvent(previousPageIndex);
}
/** Move to the first page if not already there. */
firstPage() {
// hasPreviousPage being false implies at the start
if (!this.hasPreviousPage()) {
return;
}
const previousPageIndex = this.pageIndex;
this.pageIndex = 0;
this._emitPageEvent(previousPageIndex);
}
/** Move to the last page if not already there. */
lastPage() {
// hasNextPage being false implies at the end
if (!this.hasNextPage()) {
return;
}
const previousPageIndex = this.pageIndex;
this.pageIndex = this.getNumberOfPages() - 1;
this._emitPageEvent(previousPageIndex);
}
/** Whether there is a previous page. */
hasPreviousPage() {
return this.pageIndex >= 1 && this.pageSize !== 0;
}
/** Whether there is a next page. */
hasNextPage() {
const maxPageIndex = this.getNumberOfPages() - 1;
return this.pageIndex < maxPageIndex && this.pageSize !== 0;
}
/** Calculate the number of pages */
getNumberOfPages() {
if (!this.pageSize) {
return 0;
}
return Math.ceil(this.length / this.pageSize);
}
/**
* Changes the page size so that the first item displayed on the page will still be
* displayed using the new page size.
*
* For example, if the page size is 10 and on the second page (items indexed 10-19) then
* switching so that the page size is 5 will set the third page as the current page so
* that the 10th item will still be displayed.
*/
_changePageSize(pageSize) {
// Current page needs to be updated to reflect the new page size. Navigate to the page
// containing the previous page's first item.
const startIndex = this.pageIndex * this.pageSize;
const previousPageIndex = this.pageIndex;
this.pageIndex = Math.floor(startIndex / pageSize) || 0;
this.pageSize = pageSize;
this._emitPageEvent(previousPageIndex);
}
/** Checks whether the buttons for going forwards should be disabled. */
_nextButtonsDisabled() {
return this.disabled || !this.hasNextPage();
}
/** Checks whether the buttons for going backwards should be disabled. */
_previousButtonsDisabled() {
return this.disabled || !this.hasPreviousPage();
}
/**
* Updates the list of page size options to display to the user. Includes making sure that
* the page size is an option and that the list is sorted.
*/
_updateDisplayedPageSizeOptions() {
if (!this._isInitialized) {
return;
}
// If no page size is provided, use the first page size option or the default page size.
if (!this.pageSize) {
this._pageSize = this.pageSizeOptions.length != 0 ? this.pageSizeOptions[0] : DEFAULT_PAGE_SIZE;
}
this._displayedPageSizeOptions = this.pageSizeOptions.slice();
if (this._displayedPageSizeOptions.indexOf(this.pageSize) === -1) {
this._displayedPageSizeOptions.push(this.pageSize);
}
// Sort the numbers using a number-specific sort function.
this._displayedPageSizeOptions.sort((a, b) => a - b);
this._changeDetectorRef.markForCheck();
}
/** Emits an event notifying that a change of the paginator's properties has been triggered. */
_emitPageEvent(previousPageIndex) {
this.page.emit({
previousPageIndex,
pageIndex: this.pageIndex,
pageSize: this.pageSize,
length: this.length,
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: NgbPaginator, deps: [{ token: NgbPaginatorIntl }, { token: i0.ChangeDetectorRef }, { token: NGB_PAGINATOR_DEFAULT_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: NgbPaginator, isStandalone: true, selector: "ngb-paginator", inputs: { pageIndex: ["pageIndex", "pageIndex", numberAttribute], length: ["length", "length", numberAttribute], pageSize: ["pageSize", "pageSize", numberAttribute], pageSizeOptions: "pageSizeOptions", hidePageSize: ["hidePageSize", "hidePageSize", booleanAttribute], showFirstLastButtons: ["showFirstLastButtons", "showFirstLastButtons", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], size: "size" }, outputs: { page: "page" }, host: { attributes: { "role": "group" } }, exportAs: ["ngbPaginator"], ngImport: i0, template: "<div\n class=\"d-flex flex-column-reverse flex-lg-row justify-content-center justify-content-lg-end align-items-end align-items-lg-center gap-lg-5 gap-3\">\n @if (!hidePageSize) {\n <div class=\"d-inline-flex align-items-center gap-2\">\n <small class=\"ws-nowrap\" [attr.id]=\"_pageSizeLabelId\">{{ _intl.itemsPerPageLabel }}</small>\n\n @if (_displayedPageSizeOptions.length > 1) {\n <select\n class=\"form-select form-select-sm\"\n name=\"pageSize\"\n [ngModel]=\"pageSize\"\n [disabled]=\"disabled\"\n [attr.aria-labelledby]=\"_pageSizeLabelId\"\n (ngModelChange)=\"_changePageSize($any($event))\">\n @for (pageSizeOption of _displayedPageSizeOptions; track pageSizeOption) {\n <option [ngValue]=\"pageSizeOption\">{{ pageSizeOption }}</option>\n }\n </select>\n } @if (_displayedPageSizeOptions.length <= 1) {\n <small>{{pageSize}}</small>\n }\n </div>\n }\n\n <div class=\"d-inline-flex align-items-center gap-lg-5 gap-3\">\n <small aria-live=\"polite\">{{ _intl.getRangeLabel(pageIndex, pageSize, length) }}</small>\n\n <ul [class]=\"'my-0 pagination' + (size ? ' pagination-' + size : '')\">\n @if (showFirstLastButtons) {\n <li class=\"page-item\" [class.disabled]=\"_previousButtonsDisabled()\">\n <button\n class=\"page-link\"\n type=\"button\"\n (click)=\"firstPage()\"\n [disabled]=\"_previousButtonsDisabled()\"\n [attr.aria-label]=\"_intl.firstPageLabel\"\n [attr.tabindex]=\"_previousButtonsDisabled() ? '-1' : null\">\n <span aria-hidden=\"true\">««</span>\n </button>\n </li>\n }\n\n <li class=\"page-item\" [class.disabled]=\"_previousButtonsDisabled()\">\n <button\n class=\"page-link\"\n type=\"button\"\n (click)=\"previousPage()\"\n [disabled]=\"_previousButtonsDisabled()\"\n [attr.aria-label]=\"_intl.previousPageLabel\"\n [attr.tabindex]=\"_previousButtonsDisabled() ? '-1' : null\">\n <span aria-hidden=\"true\">«</span>\n </button>\n </li>\n <li class=\"page-item\" [class.disabled]=\"_nextButtonsDisabled()\">\n <button\n class=\"page-link\"\n type=\"button\"\n (click)=\"nextPage()\"\n [disabled]=\"_nextButtonsDisabled()\"\n [attr.aria-label]=\"_intl.nextPageLabel\"\n [attr.tabindex]=\"_nextButtonsDisabled() ? '-1' : null\">\n <span aria-hidden=\"true\">»</span>\n </button>\n </li>\n\n @if(showFirstLastButtons) {\n <li class=\"page-item\" [class.disabled]=\"_nextButtonsDisabled()\">\n <button\n class=\"page-link\"\n type=\"button\"\n (click)=\"lastPage()\"\