UNPKG

dfx-bootstrap-table

Version:

Angular table CDK implementation for Bootstrap with filtering, sorting and pagination.

1 lines 105 kB
{"version":3,"file":"dfx-bootstrap-table.mjs","sources":["../../../libs/dfx-bootstrap-table/src/lib/sort/sort-errors.ts","../../../libs/dfx-bootstrap-table/src/lib/sort/sort.ts","../../../libs/dfx-bootstrap-table/src/lib/sort/sort-direction.ts","../../../libs/dfx-bootstrap-table/src/lib/table/cell.ts","../../../libs/dfx-bootstrap-table/src/lib/sort/sort-header.ts","../../../libs/dfx-bootstrap-table/src/lib/sort/sort-header.html","../../../libs/dfx-bootstrap-table/src/lib/sort/sort-module.ts","../../../libs/dfx-bootstrap-table/src/lib/paginator/paginator-intl.service.ts","../../../libs/dfx-bootstrap-table/src/lib/paginator/paginator.ts","../../../libs/dfx-bootstrap-table/src/lib/paginator/paginator.html","../../../libs/dfx-bootstrap-table/src/lib/paginator/paginator.module.ts","../../../libs/dfx-bootstrap-table/src/lib/table/data-source.ts","../../../libs/dfx-bootstrap-table/src/lib/table/row.ts","../../../libs/dfx-bootstrap-table/src/lib/table/table.ts","../../../libs/dfx-bootstrap-table/src/lib/table/text-column.ts","../../../libs/dfx-bootstrap-table/src/lib/table/table-module.ts","../../../libs/dfx-bootstrap-table/src/public-api.ts","../../../libs/dfx-bootstrap-table/src/dfx-bootstrap-table.ts"],"sourcesContent":["/** @docs-private */\nexport function getSortDuplicateSortableIdError(id: string): Error {\n return Error(`Cannot have two NgbSortables with the same id (${id}).`);\n}\n\n/** @docs-private */\nexport function getSortHeaderNotContainedWithinSortError(): Error {\n return Error(`NgbSortHeader must be placed within a parent element with the NgbSort directive.`);\n}\n\n/** @docs-private */\nexport function getSortHeaderMissingIdError(): Error {\n return Error(`NgbSortHeader must be provided with a unique id.`);\n}\n\n/** @docs-private */\nexport function getSortInvalidDirectionError(direction: string): Error {\n return Error(`${direction} is not a valid sort direction ('asc' or 'desc').`);\n}\n","/**\n * @license\n * Original work Copyright Google LLC All Rights Reserved.\n * Modified work Copyright DatePoll-Systems\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n Directive,\n EventEmitter,\n InjectionToken,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n booleanAttribute,\n inject,\n} from '@angular/core';\n\nimport { Observable, ReplaySubject, Subject } from 'rxjs';\n\nimport { SortDirection } from './sort-direction';\nimport { getSortDuplicateSortableIdError, getSortHeaderMissingIdError, getSortInvalidDirectionError } from './sort-errors';\n\n/** Position of the arrow that displays when sorted. */\nexport type SortHeaderArrowPosition = 'before' | 'after';\n\n/** Interface for a directive that holds sorting state consumed by `NgbSortHeader`. */\nexport interface NgbSortable {\n /** The id of the column being sorted. */\n id: string;\n\n /** Starting sort direction. */\n start: SortDirection;\n\n /** Whether to disable clearing the sorting state. */\n disableClear: boolean;\n}\n\n/** The current sort state. */\nexport interface Sort {\n /** The id of the column being sorted. */\n active: string;\n\n /** The sort direction. */\n direction: SortDirection;\n}\n\n/** Default options for `ngb-sort`. */\nexport interface NgbSortDefaultOptions {\n /** Whether to disable clearing the sorting state. */\n disableClear?: boolean;\n /** Position of the arrow that displays when sorted. */\n arrowPosition?: SortHeaderArrowPosition;\n}\n\n/** Injection token to be used to override the default options for `ngb-sort`. */\nexport const NGB_SORT_DEFAULT_OPTIONS = new InjectionToken<NgbSortDefaultOptions>('NGB_SORT_DEFAULT_OPTIONS');\n\n/** Container for NgbSortable to manage the sort state and provide default sort parameters. */\n@Directive({\n selector: '[ngb-sort]',\n exportAs: 'ngbSort',\n host: { class: 'ngb-sort' },\n})\nexport class NgbSort implements OnChanges, OnDestroy, OnInit {\n private _initializedStream = new ReplaySubject<void>(1);\n\n /** Collection of all registered sortables that this directive manages. */\n sortables = new Map<string, NgbSortable>();\n\n /** Used to notify any child components listening to state changes. */\n readonly _stateChanges = new Subject<void>();\n\n /** The id of the most recently sorted NgbSortable. */\n @Input({ alias: 'ngbSortActive' }) active = '';\n\n /**\n * The direction to set when an NgbSortable is initially sorted.\n * May be overridden by the NgbSortable's sort start.\n */\n @Input('ngbSortStart') start: SortDirection = 'asc';\n\n /** The sort direction of the currently active NgbSortable. */\n @Input('ngbSortDirection')\n get direction(): SortDirection {\n return this._direction;\n }\n set direction(direction: SortDirection) {\n if (direction && direction !== 'asc' && direction !== 'desc' && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw getSortInvalidDirectionError(direction);\n }\n this._direction = direction;\n }\n private _direction: SortDirection = '';\n\n /**\n * Whether to disable the user from clearing the sort by finishing the sort direction cycle.\n * May be overridden by the NgbSortable's disable clear input.\n */\n @Input({ alias: 'ngbSortDisableClear', transform: booleanAttribute })\n disableClear = false;\n\n /** Whether the sortable is disabled. */\n @Input({ alias: 'ngbSortDisabled', transform: booleanAttribute })\n disabled = false;\n\n /** Event emitted when the user changes either the active sort or sort direction. */\n @Output('ngbSortChange') readonly sortChange: EventEmitter<Sort> = new EventEmitter<Sort>();\n\n /** Emits when the paginator is initialized. */\n initialized: Observable<void> = this._initializedStream;\n\n private _defaultOptions: NgbSortDefaultOptions | null = inject(NGB_SORT_DEFAULT_OPTIONS, { optional: true });\n\n /**\n * Register function to be used by the contained NgbSortables. Adds the NgbSortable to the\n * collection of NgbSortables.\n */\n register(sortable: NgbSortable): void {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!sortable.id) {\n throw getSortHeaderMissingIdError();\n }\n\n if (this.sortables.has(sortable.id)) {\n throw getSortDuplicateSortableIdError(sortable.id);\n }\n }\n\n this.sortables.set(sortable.id, sortable);\n }\n\n /**\n * Unregister function to be used by the contained NgbSortable. Removes the NgbSortable from the\n * collection of contained NgbSortable.\n */\n deregister(sortable: NgbSortable): void {\n this.sortables.delete(sortable.id);\n }\n\n /** Sets the active sort id and determines the new sort direction. */\n sort(sortable: NgbSortable): void {\n if (this.active != sortable.id) {\n this.active = sortable.id;\n this.direction = sortable.start ?? this.start;\n } else {\n this.direction = this.getNextSortDirection(sortable);\n }\n\n this.sortChange.emit({ active: this.active, direction: this.direction });\n }\n\n /** Returns the next sort direction of the active sortable, checking for potential overrides. */\n getNextSortDirection(sortable: NgbSortable): SortDirection {\n if (!sortable) {\n return '';\n }\n\n // Get the sort direction cycle with the potential sortable overrides.\n const disableClear = sortable?.disableClear ?? this.disableClear ?? !!this._defaultOptions?.disableClear;\n const sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear);\n\n // Get and return the next direction in the cycle\n let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1;\n if (nextDirectionIndex >= sortDirectionCycle.length) {\n nextDirectionIndex = 0;\n }\n return sortDirectionCycle[nextDirectionIndex];\n }\n\n ngOnInit(): void {\n this._initializedStream.next();\n }\n\n ngOnChanges(): void {\n this._stateChanges.next();\n }\n\n ngOnDestroy(): void {\n this._stateChanges.complete();\n }\n}\n\n/** Returns the sort direction cycle to use given the provided parameters of order and clear. */\nfunction getSortDirectionCycle(start: SortDirection, disableClear: boolean): SortDirection[] {\n const sortOrder: SortDirection[] = ['asc', 'desc'];\n if (start == 'desc') {\n sortOrder.reverse();\n }\n if (!disableClear) {\n sortOrder.push('');\n }\n\n return sortOrder;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\n\nexport type SortDirection = 'asc' | 'desc' | '';\n","/**\n * @license\n * Original work Copyright Google LLC All Rights Reserved.\n * Modified work Copyright DatePoll-Systems\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n CdkCell,\n CdkCellDef,\n CdkColumnDef,\n CdkFooterCell,\n CdkFooterCellDef,\n CdkHeaderCell,\n CdkHeaderCellDef,\n CdkTable,\n} from '@angular/cdk/table';\nimport { Directive, HostBinding, InjectionToken, Input } from '@angular/core';\n\n/**\n * Cell definition for the ngb-table.\n * Captures the template of a column's data row cell as well as cell-specific properties.\n */\n@Directive({\n selector: '[ngbCellDef]',\n providers: [{ provide: CdkCellDef, useExisting: NgbCellDef }],\n standalone: true,\n})\nexport class NgbCellDef<T> extends CdkCellDef {\n // leveraging syntactic-sugar syntax when we use *ngbCellDef\n @Input() ngbCellDefTable?: CdkTable<T>;\n\n // ngTemplateContextGuard flag to help with the Language Service\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n static ngTemplateContextGuard<T>(dir: NgbCellDef<T>, ctx: unknown): ctx is { $implicit: T; index: number } {\n return true;\n }\n}\n\n/**\n * Header cell definition for the ngb-table.\n * Captures the template of a column's header cell and as well as cell-specific properties.\n */\n@Directive({\n selector: '[ngbHeaderCellDef]',\n providers: [{ provide: CdkHeaderCellDef, useExisting: NgbHeaderCellDef }],\n standalone: true,\n})\nexport class NgbHeaderCellDef extends CdkHeaderCellDef {\n @HostBinding('style.white-space')\n @Input()\n whiteSpace = 'nowrap';\n}\n\n/**\n * Footer cell definition for the ngb-table.\n * Captures the template of a column's footer cell and as well as cell-specific properties.\n */\n@Directive({\n selector: '[ngbFooterCellDef]',\n providers: [{ provide: CdkFooterCellDef, useExisting: NgbFooterCellDef }],\n standalone: true,\n})\nexport class NgbFooterCellDef extends CdkFooterCellDef {\n @HostBinding('style.white-space')\n @Input()\n whiteSpace = 'nowrap';\n}\n\nexport const ngbSortHeaderColumnDef = new InjectionToken('NGB_SORT_HEADER_COLUMN_DEF');\n\n/**\n * Column definition for the ngb-table.\n * Defines a set of cells available for a table column.\n */\n@Directive({\n selector: '[ngbColumnDef]',\n providers: [\n { provide: CdkColumnDef, useExisting: NgbColumnDef },\n { provide: ngbSortHeaderColumnDef, useExisting: NgbColumnDef },\n ],\n standalone: true,\n})\nexport class NgbColumnDef extends CdkColumnDef {\n /** Unique name for this column. */\n @Input('ngbColumnDef')\n override get name(): string {\n return this._name;\n }\n\n override set name(name: string) {\n this._setNameInput(name);\n }\n}\n\n/** Header cell template container that adds the right classes and role. */\n@Directive({\n selector: 'ngb-header-cell, th[ngb-header-cell]',\n host: {\n role: 'columnheader',\n },\n standalone: true,\n})\nexport class NgbHeaderCell extends CdkHeaderCell {}\n\n/** Footer cell template container that adds the right classes and role. */\n@Directive({\n selector: 'ngb-footer-cell, td[ngb-footer-cell]',\n standalone: true,\n})\nexport class NgbFooterCell extends CdkFooterCell {}\n\n/** Cell template container that adds the right classes and role. */\n@Directive({\n selector: 'ngb-cell, td[ngb-cell]',\n standalone: true,\n})\nexport class NgbCell extends CdkCell {\n @HostBinding('style.white-space')\n @Input()\n whiteSpace = 'nowrap';\n}\n","/**\n * @license\n * Original work Copyright Google LLC All Rights Reserved.\n * Modified work Copyright DatePoll-Systems\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport { AriaDescriber, FocusMonitor } from '@angular/cdk/a11y';\nimport { ENTER, SPACE } from '@angular/cdk/keycodes';\nimport {\n ANIMATION_MODULE_TYPE,\n AfterViewInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n Input,\n OnDestroy,\n OnInit,\n ViewEncapsulation,\n booleanAttribute,\n inject,\n signal,\n} from '@angular/core';\n\nimport { Subscription, merge } from 'rxjs';\n\nimport { ngbSortHeaderColumnDef } from '../table/cell';\nimport { NGB_SORT_DEFAULT_OPTIONS, NgbSort, NgbSortDefaultOptions, NgbSortable, SortHeaderArrowPosition } from './sort';\nimport { SortDirection } from './sort-direction';\nimport { getSortHeaderNotContainedWithinSortError } from './sort-errors';\n\n/** Column definition associated with a `NgbSortHeader`. */\ninterface NgbSortHeaderColumnDef {\n name: string;\n}\n\n/**\n * Applies sorting behavior (click to change sort) and styles to an element, including an\n * arrow to display the current sort direction.\n *\n * Must be provided with an id and contained within a parent NgbSort directive.\n *\n * If used on header cells in a CdkTable, it will automatically default its id from its containing\n * column definition.\n */\n@Component({\n selector: '[ngb-sort-header]',\n exportAs: 'ngbSortHeader',\n templateUrl: 'sort-header.html',\n styleUrls: ['sort-header.scss'],\n host: {\n class: 'ngb-sort-header',\n '(click)': '_toggleOnInteraction()',\n '(keydown)': '_handleKeydown($event)',\n '(mouseleave)': '_recentlyCleared.set(null)',\n '[attr.aria-sort]': '_getAriaSortAttribute()',\n '[class.ngb-sort-header-disabled]': '_isDisabled()',\n },\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class NgbSortHeader implements NgbSortable, OnDestroy, OnInit, AfterViewInit {\n _sort = inject(NgbSort, { optional: true })!;\n _columnDef = inject<NgbSortHeaderColumnDef>(ngbSortHeaderColumnDef, {\n optional: true,\n });\n private _changeDetectorRef = inject(ChangeDetectorRef);\n private _focusMonitor = inject(FocusMonitor);\n private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n private _ariaDescriber = inject(AriaDescriber, { optional: true });\n private _renderChanges: Subscription | undefined;\n protected _animationModule = inject(ANIMATION_MODULE_TYPE, { optional: true });\n\n /**\n * Indicates which state was just cleared from the sort header.\n * Will be reset on the next interaction. Used for coordinating animations.\n */\n protected _recentlyCleared = signal<SortDirection | null>(null);\n\n /**\n * The element with role=\"button\" inside this component's view. We need this\n * in order to apply a description with AriaDescriber.\n */\n private _sortButton!: HTMLElement;\n\n /**\n * ID of this sort header. If used within the context of a CdkColumnDef, this will default to\n * the column's name.\n */\n @Input('ngb-sort-header') id!: string;\n\n /** Sets the position of the arrow that displays when sorted. */\n @Input() arrowPosition: SortHeaderArrowPosition = 'after';\n\n /** Overrides the sort start value of the containing NgbSort for this NgbSortable. */\n @Input() start!: SortDirection;\n\n /** whether the sort header is disabled. */\n @Input({ transform: booleanAttribute })\n disabled = false;\n\n /**\n * Description applied to NgbSortHeader's button element with aria-describedby. This text should\n * describe the action that will occur when the user clicks the sort header.\n */\n @Input()\n get sortActionDescription(): string {\n return this._sortActionDescription;\n }\n set sortActionDescription(value: string) {\n this._updateSortActionDescription(value);\n }\n // Default the action description to \"Sort\" because it's better than nothing.\n // Without a description, the button's label comes from the sort header text content,\n // which doesn't give any indication that it performs a sorting operation.\n private _sortActionDescription = 'Sort';\n\n /** Overrides the disable clear value of the containing NgbSort for this NgbSortable. */\n @Input({ transform: booleanAttribute })\n disableClear!: boolean;\n\n // eslint-disable-next-line @angular-eslint/prefer-inject\n constructor(...args: unknown[]);\n\n constructor() {\n const defaultOptions = inject<NgbSortDefaultOptions>(NGB_SORT_DEFAULT_OPTIONS, {\n optional: true,\n });\n\n if (!this._sort && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw getSortHeaderNotContainedWithinSortError();\n }\n\n if (defaultOptions?.arrowPosition) {\n this.arrowPosition = defaultOptions?.arrowPosition;\n }\n }\n\n ngOnInit() {\n if (!this.id && this._columnDef) {\n this.id = this._columnDef.name;\n }\n\n this._sort.register(this);\n this._renderChanges = merge(this._sort._stateChanges, this._sort.sortChange).subscribe(() => this._changeDetectorRef.markForCheck());\n this._sortButton = this._elementRef.nativeElement.querySelector('.ngb-sort-header-container')!;\n this._updateSortActionDescription(this._sortActionDescription);\n }\n\n ngAfterViewInit() {\n // We use the focus monitor because we also want to style\n // things differently based on the focus origin.\n this._focusMonitor.monitor(this._elementRef, true).subscribe(() => this._recentlyCleared.set(null));\n }\n\n ngOnDestroy() {\n this._focusMonitor.stopMonitoring(this._elementRef);\n this._sort.deregister(this);\n this._renderChanges?.unsubscribe();\n\n if (this._sortButton) {\n this._ariaDescriber?.removeDescription(this._sortButton, this._sortActionDescription);\n }\n }\n\n /** Triggers the sort on this sort header and removes the indicator hint. */\n _toggleOnInteraction() {\n if (!this._isDisabled()) {\n const wasSorted = this._isSorted();\n const prevDirection = this._sort.direction;\n this._sort.sort(this);\n this._recentlyCleared.set(wasSorted && !this._isSorted() ? prevDirection : null);\n }\n }\n\n _handleKeydown(event: KeyboardEvent) {\n if (event.keyCode === SPACE || event.keyCode === ENTER) {\n event.preventDefault();\n this._toggleOnInteraction();\n }\n }\n\n /** Whether this NgbSortHeader is currently sorted in either ascending or descending order. */\n _isSorted() {\n return this._sort.active == this.id && (this._sort.direction === 'asc' || this._sort.direction === 'desc');\n }\n\n _isDisabled() {\n return this._sort.disabled || this.disabled;\n }\n\n /**\n * Gets the aria-sort attribute that should be applied to this sort header. If this header\n * is not sorted, returns null so that the attribute is removed from the host element. Aria spec\n * says that the aria-sort property should only be present on one header at a time, so removing\n * ensures this is true.\n */\n _getAriaSortAttribute() {\n if (!this._isSorted()) {\n return 'none';\n }\n\n return this._sort.direction == 'asc' ? 'ascending' : 'descending';\n }\n\n /** Whether the arrow inside the sort header should be rendered. */\n _renderArrow() {\n return !this._isDisabled() || this._isSorted();\n }\n\n private _updateSortActionDescription(newDescription: string) {\n // We use AriaDescriber for the sort button instead of setting an `aria-label` because some\n // screen readers (notably VoiceOver) will read both the column header *and* the button's label\n // for every *cell* in the table, creating a lot of unnecessary noise.\n\n // If _sortButton is undefined, the component hasn't been initialized yet so there's\n // nothing to update in the DOM.\n if (this._sortButton) {\n // removeDescription will no-op if there is no existing message.\n // TODO(jelbourn): remove optional chaining when AriaDescriber is required.\n this._ariaDescriber?.removeDescription(this._sortButton, this._sortActionDescription);\n this._ariaDescriber?.describe(this._sortButton, newDescription);\n }\n\n this._sortActionDescription = newDescription;\n }\n}\n","<!--\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","/**\n * @license\n * Original work Copyright Google LLC All Rights Reserved.\n * Modified work Copyright DatePoll-Systems\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport { NgModule } from '@angular/core';\n\nimport { NgbSort } from './sort';\nimport { NgbSortHeader } from './sort-header';\n\nconst EXPORTED_DECLARATIONS = [NgbSort, NgbSortHeader];\n\n@NgModule({\n imports: EXPORTED_DECLARATIONS,\n exports: EXPORTED_DECLARATIONS,\n})\nexport class DfxSortModule {}\n","import { Injectable, Optional, SkipSelf } from '@angular/core';\n\nimport { Subject } from 'rxjs';\n\n/**\n * To modify the labels and text displayed, create a new instance of NgbPaginatorIntl and\n * include it in a custom provider\n */\n@Injectable({ providedIn: 'root' })\nexport class NgbPaginatorIntl {\n /**\n * Stream to emit from when labels are changed. Use this to notify components when the labels have\n * changed after initialization.\n */\n readonly changes: Subject<void> = new Subject<void>();\n\n /** A label for the page size selector. */\n itemsPerPageLabel = 'Items per page:';\n\n /** A label for the button that increments the current page. */\n nextPageLabel = 'Next page';\n\n /** A label for the button that decrements the current page. */\n previousPageLabel = 'Previous page';\n\n /** A label for the button that moves to the first page. */\n firstPageLabel = 'First page';\n\n /** A label for the button that moves to the last page. */\n lastPageLabel = 'Last page';\n\n /** A label for the range of items within the current page and the length of the whole list. */\n getRangeLabel: (page: number, pageSize: number, length: number) => string = (page: number, pageSize: number, length: number) => {\n if (length == 0 || pageSize == 0) {\n return `0 of ${length}`;\n }\n\n length = Math.max(length, 0);\n\n const startIndex = page * pageSize;\n\n // If the start index exceeds the list length, do not try and fix the end index to the end.\n const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;\n\n return `${startIndex + 1} – ${endIndex} of ${length}`;\n };\n}\n\n/** @docs-private */\nexport function NGB_PAGINATOR_INTL_PROVIDER_FACTORY(parentIntl: NgbPaginatorIntl): NgbPaginatorIntl {\n return parentIntl || new NgbPaginatorIntl();\n}\n\n/** @docs-private */\nexport const NGB_PAGINATOR_INTL_PROVIDER = {\n // If there is already an NgbPaginatorIntl available, use that. Otherwise, provide a new one.\n provide: NgbPaginatorIntl,\n deps: [[new Optional(), new SkipSelf(), NgbPaginatorIntl]],\n useFactory: NGB_PAGINATOR_INTL_PROVIDER_FACTORY,\n};\n","import { _IdGenerator } from '@angular/cdk/a11y';\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Inject,\n InjectionToken,\n Input,\n OnDestroy,\n OnInit,\n Optional,\n Output,\n ViewEncapsulation,\n booleanAttribute,\n inject,\n numberAttribute,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { Observable, ReplaySubject, Subscription } from 'rxjs';\n\nimport { NgbPaginatorIntl } from './paginator-intl.service';\n\n/** The default page size if there is no page size and there are no provided page size options. */\nconst DEFAULT_PAGE_SIZE = 50;\n\n/**\n * Change event object that is emitted when the user selects a\n * different page size or navigates to another page.\n */\nexport interface PageEvent {\n /** The current page index. */\n pageIndex: number;\n\n /** Index of the page that was selected previously. */\n previousPageIndex: number;\n\n /** The current page size. */\n pageSize: number;\n\n /** The current total number of items being paged. */\n length: number;\n}\n\n/** Object that can be used to configure the default options for the paginator module. */\nexport interface NgbPaginatorDefaultOptions {\n /** Number of items to display on a page. By default set to 50. */\n pageSize?: number;\n\n /** The set of provided page size options to display to the user. */\n pageSizeOptions?: number[];\n\n /** Whether to hide the page size selection UI from the user. */\n hidePageSize?: boolean;\n\n /** Whether to show the first/last buttons UI to the user. */\n showFirstLastButtons?: boolean;\n}\n\n/** Injection token that can be used to provide the default options for the paginator module. */\nexport const NGB_PAGINATOR_DEFAULT_OPTIONS = new InjectionToken<NgbPaginatorDefaultOptions>('NGB_PAGINATOR_DEFAULT_OPTIONS');\n\n@Component({\n selector: 'ngb-paginator',\n exportAs: 'ngbPaginator',\n templateUrl: './paginator.html',\n styles: `\n .ws-nowrap {\n white-space: nowrap;\n }\n `,\n host: {\n role: 'group',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [FormsModule],\n})\nexport class NgbPaginator implements OnInit, OnDestroy {\n /** ID for the DOM node containing the paginator's items per page label. */\n readonly _pageSizeLabelId = inject(_IdGenerator).getId('ngb-paginator-page-size-label-');\n\n private _intlChanges: Subscription;\n private _isInitialized = false;\n private _initializedStream = new ReplaySubject<void>(1);\n\n /** The zero-based page index of the displayed list of items. Defaulted to 0. */\n @Input({ transform: numberAttribute })\n get pageIndex(): number {\n return this._pageIndex;\n }\n set pageIndex(value: number) {\n this._pageIndex = Math.max(value || 0, 0);\n this._changeDetectorRef.markForCheck();\n }\n private _pageIndex = 0;\n\n /** The length of the total number of items that are being paginated. Defaulted to 0. */\n @Input({ transform: numberAttribute })\n get length(): number {\n return this._length;\n }\n set length(value: number) {\n this._length = value || 0;\n this._changeDetectorRef.markForCheck();\n }\n private _length = 0;\n\n /** Number of items to display on a page. By default set to 50. */\n @Input({ transform: numberAttribute })\n get pageSize(): number {\n return this._pageSize;\n }\n set pageSize(value: number) {\n this._pageSize = Math.max(value || 0, 0);\n this._updateDisplayedPageSizeOptions();\n }\n private _pageSize!: number;\n\n /** The set of provided page size options to display to the user. */\n @Input()\n get pageSizeOptions(): number[] {\n return this._pageSizeOptions;\n }\n set pageSizeOptions(value: number[] | readonly number[]) {\n this._pageSizeOptions = (value || ([] as number[])).map((p) => numberAttribute(p, 0));\n this._updateDisplayedPageSizeOptions();\n }\n private _pageSizeOptions: number[] = [];\n\n /** Whether to hide the page size selection UI from the user. */\n @Input({ transform: booleanAttribute }) hidePageSize = false;\n\n /** Whether to show the first/last buttons UI to the user. */\n @Input({ transform: booleanAttribute }) showFirstLastButtons = false;\n\n /** Whether the paginator is disabled. */\n @Input({ transform: booleanAttribute }) disabled = false;\n\n /**\n * The paginator display size.\n *\n * Bootstrap currently supports small and large sizes.\n */\n @Input() size?: 'sm' | 'lg' | null;\n\n /** Event emitted when the paginator changes the page size or page index. */\n @Output() readonly page: EventEmitter<PageEvent> = new EventEmitter<PageEvent>();\n\n /** Displayed set of page size options. Will be sorted and include current page size. */\n _displayedPageSizeOptions!: number[];\n\n /** Emits when the paginator is initialized. */\n initialized: Observable<void> = this._initializedStream;\n\n constructor(\n // eslint-disable-next-line @angular-eslint/prefer-inject\n public _intl: NgbPaginatorIntl,\n // eslint-disable-next-line @angular-eslint/prefer-inject\n private _changeDetectorRef: ChangeDetectorRef,\n // eslint-disable-next-line @angular-eslint/prefer-inject\n @Optional() @Inject(NGB_PAGINATOR_DEFAULT_OPTIONS) defaults?: NgbPaginatorDefaultOptions,\n ) {\n this._intlChanges = _intl.changes.subscribe(() => this._changeDetectorRef.markForCheck());\n\n if (defaults) {\n const { pageSize, pageSizeOptions, hidePageSize, showFirstLastButtons } = defaults;\n\n if (pageSize != null) {\n this.pageSize = pageSize;\n }\n\n if (pageSizeOptions != null) {\n this.pageSizeOptions = pageSizeOptions;\n }\n\n if (hidePageSize != null) {\n this.hidePageSize = hidePageSize;\n }\n\n if (showFirstLastButtons != null) {\n this.showFirstLastButtons = showFirstLastButtons;\n }\n }\n }\n\n ngOnInit(): void {\n this._isInitialized = true;\n this._updateDisplayedPageSizeOptions();\n this._initializedStream.next();\n }\n\n ngOnDestroy(): void {\n this._initializedStream.complete();\n this._intlChanges.unsubscribe();\n }\n\n /** Advances to the next page if it exists. */\n nextPage(): void {\n if (!this.hasNextPage()) {\n return;\n }\n\n const previousPageIndex = this.pageIndex;\n this.pageIndex = this.pageIndex + 1;\n this._emitPageEvent(previousPageIndex);\n }\n\n /** Move back to the previous page if it exists. */\n previousPage(): void {\n if (!this.hasPreviousPage()) {\n return;\n }\n\n const previousPageIndex = this.pageIndex;\n this.pageIndex = this.pageIndex - 1;\n this._emitPageEvent(previousPageIndex);\n }\n\n /** Move to the first page if not already there. */\n firstPage(): void {\n // hasPreviousPage being false implies at the start\n if (!this.hasPreviousPage()) {\n return;\n }\n\n const previousPageIndex = this.pageIndex;\n this.pageIndex = 0;\n this._emitPageEvent(previousPageIndex);\n }\n\n /** Move to the last page if not already there. */\n lastPage(): void {\n // hasNextPage being false implies at the end\n if (!this.hasNextPage()) {\n return;\n }\n\n const previousPageIndex = this.pageIndex;\n this.pageIndex = this.getNumberOfPages() - 1;\n this._emitPageEvent(previousPageIndex);\n }\n\n /** Whether there is a previous page. */\n hasPreviousPage(): boolean {\n return this.pageIndex >= 1 && this.pageSize !== 0;\n }\n\n /** Whether there is a next page. */\n hasNextPage(): boolean {\n const maxPageIndex = this.getNumberOfPages() - 1;\n return this.pageIndex < maxPageIndex && this.pageSize !== 0;\n }\n\n /** Calculate the number of pages */\n getNumberOfPages(): number {\n if (!this.pageSize) {\n return 0;\n }\n\n return Math.ceil(this.length / this.pageSize);\n }\n\n /**\n * Changes the page size so that the first item displayed on the page will still be\n * displayed using the new page size.\n *\n * For example, if the page size is 10 and on the second page (items indexed 10-19) then\n * switching so that the page size is 5 will set the third page as the current page so\n * that the 10th item will still be displayed.\n */\n _changePageSize(pageSize: number): void {\n // Current page needs to be updated to reflect the new page size. Navigate to the page\n // containing the previous page's first item.\n const startIndex = this.pageIndex * this.pageSize;\n const previousPageIndex = this.pageIndex;\n\n this.pageIndex = Math.floor(startIndex / pageSize) || 0;\n this.pageSize = pageSize;\n this._emitPageEvent(previousPageIndex);\n }\n\n /** Checks whether the buttons for going forwards should be disabled. */\n _nextButtonsDisabled(): boolean {\n return this.disabled || !this.hasNextPage();\n }\n\n /** Checks whether the buttons for going backwards should be disabled. */\n _previousButtonsDisabled(): boolean {\n return this.disabled || !this.hasPreviousPage();\n }\n\n /**\n * Updates the list of page size options to display to the user. Includes making sure that\n * the page size is an option and that the list is sorted.\n */\n private _updateDisplayedPageSizeOptions() {\n if (!this._isInitialized) {\n return;\n }\n\n // If no page size is provided, use the first page size option or the default page size.\n if (!this.pageSize) {\n this._pageSize = this.pageSizeOptions.length != 0 ? this.pageSizeOptions[0] : DEFAULT_PAGE_SIZE;\n }\n\n this._displayedPageSizeOptions = this.pageSizeOptions.slice();\n\n if (this._displayedPageSizeOptions.indexOf(this.pageSize) === -1) {\n this._displayedPageSizeOptions.push(this.pageSize);\n }\n\n // Sort the numbers using a number-specific sort function.\n this._displayedPageSizeOptions.sort((a, b) => a - b);\n this._changeDetectorRef.markForCheck();\n }\n\n /** Emits an event notifying that a change of the paginator's properties has been triggered. */\n private _emitPageEvent(previousPageIndex: number) {\n this.page.emit({\n previousPageIndex,\n pageIndex: this.pageIndex,\n pageSize: this.pageSize,\n length: this.length,\n });\n }\n}\n","<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\">&laquo;&laquo;</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\">&laquo;</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\">&raquo;</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()\"\n [disabled]=\"_nextButtonsDisabled()\"\n [attr.aria-label]=\"_intl.lastPageLabel\"\n [attr.tabindex]=\"_nextButtonsDisabled() ? '-1' : null\">\n <span aria-hidden=\"true\">&raquo;&raquo;</span>\n </button>\n </li>\n }\n </ul>\n </div>\n</div>\n","import { NgModule } from '@angular/core';\n\nimport { NgbPaginator } from './paginator';\nimport { NGB_PAGINATOR_INTL_PROVIDER } from './paginator-intl.service';\n\n@NgModule({\n imports: [NgbPaginator],\n exports: [NgbPaginator],\n providers: [NGB_PAGINATOR_INTL_PROVIDER],\n})\nexport class DfxPaginationModule {}\n","/**\n * @license\n * Original work Copyright Google LLC All Rights Reserved.\n * Modified work Copyright DatePoll-Systems\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport { _isNumberValue } from '@angular/cdk/coercion';\nimport { DataSource } from '@angular/cdk/table';\n\nimport { BehaviorSubject, Observable, Subject, Subscription, combineLatest, merge, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { NgbPaginator, PageEvent } from '../paginator/paginator';\nimport { NgbSort, Sort } from '../sort/sort';\n\n/**\n * Corresponds to `Number.MAX_SAFE_INTEGER`. Moved out into a variable here due to\n * flaky browser support and the value not being defined in Closure's typings.\n */\nconst MAX_SAFE_INTEGER = 9007199254740991;\n\n/**\n * Data source that accepts a client-side data array and includes native support of filtering,\n * sorting (using NgbSort), and pagination (using NgbPaginator).\n *\n * Allows for sort customization by overriding sortingDataAccessor, which defines how data\n * properties are accessed. Also allows for filter customization by overriding filterPredicate,\n * which defines how row data is converted to a string for filter matching.\n *\n * **Note:** This class is meant to be a simple data source to help you get started. As such\n * it isn't equipped to handle some more advanced cases like robust i18n support or server-side\n * interactions. If your app needs to support more advanced use cases, consider implementing your\n * own `DataSource`.\n */\nexport class NgbTableDataSource<T, P extends NgbPaginator = NgbPaginator> extends DataSource<T> {\n /** Stream that emits when a new data array is set on the data source. */\n private readonly _data: BehaviorSubject<T[]>;\n\n /** Stream emitting render data to the table (depends on ordered data changes). */\n private readonly _renderData = new BehaviorSubject<T[]>([]);\n\n /** Stream that emits when a new filter string is set on the data source. */\n private readonly _filter = new BehaviorSubject<string>('');\n\n /** Used to react to internal changes of the paginator that are made by the data source itself. */\n private readonly _internalPageChanges = new Subject<void>();\n\n /**\n * Subscription to the changes that should trigger an update to the table's rendered rows, such\n * as filtering, sorting, pagination, or base data changes.\n */\n _renderChangesSubscription: Subscription | null = null;\n\n /**\n * The filtered set of data that has been matched by the filter string, or all the data if there\n * is no filter. Useful for knowing the set of data the table represents.\n * For example, a 'selectAll()' function would likely want to select the set of filtered data\n * shown to the user rather than all the data.\n */\n filteredData!: T[];\n\n /** Array of data that should be rendered by the table, where each object represents one row. */\n get data() {\n return this._data.value;\n }\n\n set data(data: T[]) {\n data = Array.isArray(data) ? data : [];\n this._data.next(data);\n // Normally the `filteredData` is updated by the re-render\n // subscription, but that won't happen if it's inactive.\n if (!this._renderChangesSubscription) {\n this._filterData(data);\n }\n }\n\n /**\n * Filter term that should be used to filter out objects from the data array. To override how\n * data objects match to this filter string, provide a custom function for filterPredicate.\n */\n get filter(): string {\n return this._filter.value;\n }\n\n set filter(filter: string) {\n this._filter.next(filter);\n // Normally the `filteredData` is updated by the re-render\n // subscription, but that won't happen if it's inactive.\n if (!this._renderChangesSubscription) {\n this._filterData(this.data);\n }\n }\n\n /**\n * Instance of the NgbSort directive used by the table to control its sorting. Sort changes\n * emitted by the NgbSort will trigger an update to the table's rendered data.\n */\n get sort(): NgbSort | null | undefined {\n return this._sort;\n }\n\n set sort(sort: NgbSort | null | undefined) {\n this._sort = sort;\n this._updateChangeSubscription();\n }\n\n private _sort?: NgbSort | null;\n\n /**\n * Instance of the paginator component used by the table to control what page of the data is\n * displayed. Page changes emitted by the paginator will trigger an update to the\n * table's rendered data.\n *\n * Note that the data source uses the paginator's properties to calculate which page of data\n * should be displayed. If the paginator receives its properties as template inputs,\n * e.g. `[pageLength]=100` or `[pageIndex]=1`, then be sure that the paginator's view has been\n * initialized before assigning it to this data source.\n */\n get paginator(): P | null | undefined {\n return this._paginator;\n }\n\n set paginator(paginator: P | null | undefined) {\n this._paginator = paginator;\n this._updateChangeSubscription();\n }\n\n private _paginator?: P | null;\n\n /**\n * Data accessor function that is used for accessing data properties for sorting through\n * the default sortData function.\n * This default function assumes that the sort header IDs (which defaults to the column name)\n * matches the data's properties (e.g. column Xyz represents data['Xyz']).\n * May be set to a custom function for different behavior.\n * @param data Data object that is being accessed.\n * @param sortHeaderId The name of the column that represents the data.\n */\n sortingDataAccessor: (data: T, sortHeaderId: string) => string | number = (data: T, sortHeaderId: string): string | number => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const value = (data as unknown as Record<string, any>)[sortHeaderId];\n\n if (_isNumberValue(value)) {\n const numberValue = Number(value);\n\n // Numbers beyond `MAX_SAFE_INTEGER` can't be compared reliably so we\n // leave them as strings. For more info: https://goo.gl/y5vbSg\n return numberValue < MAX_SAFE_INTEGER ? numberValue : value;\n }\n\n return value;\n };\n\n /**\n * Gets a sorted copy of the data array based on the state of the NgbSort. Called\n * after changes are made to the filtered data or when sort changes are emitted from NgbSort.\n * By default, the function retrieves the active sort and its direction and compares data\n * by retrieving data using the sortingDataAccessor. May be overridden for a custom implementation\n * of data ordering.\n * @param data The array of data that should be sorted.\n * @param sort The connected NgbSort that holds the current sort state.\n */\n sortData: (data: T[], sort: NgbSort) => T[] = (data: T[], sort: NgbSort): T[] => {\n const active = sort.active;\n const direction = sort.direction;\n if (!active || direction == '') {\n return data;\n }\n\n return data.sort((a, b) => {\n let valueA = this.sortingDataAccessor(a, active);\n let valueB = this.sortingDataAccessor(b, active);\n\n // If there are data in the column that can be converted to a number,\n // it must be ensured that the rest of the data\n // is of the same type so as not to order incorrectly.\n const valueAType = typeof valueA;\n const valueBType = typeof valueB;\n\n if (valueAType !== valueBType) {\n if (valueAType === 'number') {\n valueA += '';\n }\n if (valueBType === 'number') {\n valueB += '';\n }\n }\n\n // If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if\n // one value exists while the other doesn't. In this case, existing value should come last.\n // This avoids inconsistent results when comparing values to undefined/null.\n // If neither value exists, return 0 (equal).\n let comparatorResult = 0;\n if (valueA != null && valueB != null) {\n // Check if one value is greater than the other; if equal, comparatorResult should remain 0.\n if (valueA > valueB) {\n comparatorResult = 1;\n } else if (valueA < valueB) {\n comparatorResult = -1;\n }\n } else if (valueA != null) {\n comparatorResult = 1;\n } else if (valueB != null) {\n comparatorResult = -1;\n }\n\n return comparatorResult * (direction == 'asc' ? 1 : -1);\n });\n };\n\n /**\n * Checks if a data object matches the data source's filter string. By default, each data object\n * is converted to a string of its properties and returns true if the filter has\n * at least one occurrence in that string. By default, the filter string has its whitespace\n * trimmed and the match is case-insensitive. May be overridden for a custom implementation of\n * filter matching.\n * @param data Data object used to check against the filter.\n * @param filter Filter string that has been set on the data source.\n * @returns Whether the filter matches against the data\n */\n filterPredicate: (data: T, filter: string) => boolean = (data: T, filter: string): boolean => {\n // Transform the data into a lowercase string of all property values.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const dataStr = Object.keys(data as unknown as Record<string, any>)\n .reduce((currentTerm: string, key: string) => {\n // Use an obscure Unicode character to delimit the words in the concatenated string.\n // This avoids matches where the values of two columns combined will match the user's query\n // (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something\n // that has a very low chance of being typed in by somebody in a text field. This one in\n // particular is \"White up-pointing triangle with dot\" from\n // https://en.wikipedia.org/wiki/List_of_Unicode_characters\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return currentTerm + (data as unknown as Record<string, any>)[key] + '◬';\n }, '')\n .toLowerCase();\n\n // Transform the filter by converting it to lowercase and removing whitespace.\n const transformedFilter = filter.trim().toLowerCase();\n\n return dataStr.indexOf(transformedFilter) != -1;\n };\n\n constructor(initialData: T[] = []) {\n super();\n this._data = new BehaviorSubject<T[]>(initialData);\n this._updateChangeSubscription();\n }\n\n /**\n *