@onemrvapublic/design-system-demos 22.4.0-develop.2 → 22.4.0-develop.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"demo-button.component.html":"<p>Contained / Flat:</p>\n<button\n mat-flat-button\n color=\"accent\"\n class=\"{{ size() }}\"\n aria-label=\"Action\"\n [disabled]=\"disabled()\"\n>\n @if (icon() !== '') {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n Action\n @if (iconEnd() !== '') {\n <mat-icon iconPositionEnd>{{ iconEnd() }}</mat-icon>\n }\n</button>\n<p>Outlined / stroked:</p>\n<button\n mat-stroked-button\n color=\"primary\"\n class=\"{{ size() }}\"\n aria-label=\"Normal\"\n [disabled]=\"disabled()\"\n>\n @if (icon() !== '') {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n Normal\n\n @if (iconEnd() !== '') {\n <mat-icon iconPositionEnd>{{ iconEnd() }}</mat-icon>\n }\n</button>\n<p>Text:</p>\n<button\n mat-button\n color=\"primary\"\n class=\"{{ size() }}\"\n aria-label=\"Basic\"\n [disabled]=\"disabled()\"\n>\n Basic\n</button>\n","demo-button.component.ts":"import { Component, input, ViewEncapsulation } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\n\nimport { OnemrvaSharedModule } from '@onemrvapublic/design-system/shared';\nimport { DemoComponentBase } from '../../_demo/demo-component-base';\n\n@Component({\n selector: 'app-demo-button',\n templateUrl: './demo-button.component.html',\n\n standalone: true,\n imports: [MatIconModule, MatButtonModule, OnemrvaSharedModule],\n encapsulation: ViewEncapsulation.None,\n})\nexport class DemoButtonComponent extends DemoComponentBase {\n icon = input('');\n iconEnd = input('');\n size = input('');\n disabled = input(false);\n}\n"}
1
+ {"demo-button.component.html":"<p>Contained / Flat:</p>\n<button\n mat-flat-button\n color=\"accent\"\n class=\"{{ size() }}\"\n aria-label=\"Action\"\n [disabled]=\"disabled()\"\n>\n @if (icon() !== '') {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n Action\n @if (iconEnd() !== '') {\n <mat-icon iconPositionEnd>{{ iconEnd() }}</mat-icon>\n }\n</button>\n<p>Outlined / stroked:</p>\n<button\n mat-stroked-button\n color=\"primary\"\n class=\"{{ size() }}\"\n aria-label=\"Normal\"\n [disabled]=\"disabled()\"\n>\n @if (icon() !== '') {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n Normal\n\n @if (iconEnd() !== '') {\n <mat-icon iconPositionEnd>{{ iconEnd() }}</mat-icon>\n }\n</button>\n<p>Text:</p>\n<button\n mat-button\n color=\"primary\"\n class=\"{{ size() }}\"\n aria-label=\"Basic\"\n [disabled]=\"disabled()\"\n>\n Basic\n</button>\n<p>Loading:</p>\n<button\n mat-flat-button\n color=\"accent\"\n [disabled]=\"disabled()\"\n [loading]=\"loading()\"\n>\n @if (loading()) {\n <ng-container>Loading ...</ng-container>\n } @else {\n <ng-container>Send</ng-container>\n }\n</button>\n","demo-button.component.ts":"import { Component, input, ViewEncapsulation, signal } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\n\nimport { OnemrvaSharedModule } from '@onemrvapublic/design-system/shared';\nimport { DemoComponentBase } from '../../_demo/demo-component-base';\nimport { OnemrvaMatSpinnerModule } from '@onemrvapublic/design-system/mat-spinner';\n\n@Component({\n selector: 'app-demo-button',\n templateUrl: './demo-button.component.html',\n\n standalone: true,\n imports: [\n MatIconModule,\n MatButtonModule,\n OnemrvaSharedModule,\n OnemrvaMatSpinnerModule,\n ],\n encapsulation: ViewEncapsulation.None,\n})\nexport class DemoButtonComponent extends DemoComponentBase {\n icon = input('');\n iconEnd = input('');\n size = input('');\n disabled = input(false);\n loading = signal(true);\n}\n"}
@@ -1 +1 @@
1
- {"demo-table-datasource.ts":"import { DataSource } from '@angular/cdk/collections';\nimport { MatSort } from '@angular/material/sort';\nimport { map, tap } from 'rxjs/operators';\nimport {\n BehaviorSubject,\n merge,\n Observable,\n startWith,\n Subject,\n switchMap,\n} from 'rxjs';\nimport { OnemrvaMatPaginatorComponent } from '@onemrvapublic/design-system/mat-paginator';\nimport { FormControl } from '@angular/forms';\nimport { ElementsService, ListItem } from '../../_services/elements.service';\n\n/**\n * Data source for the List view. This class should\n * encapsulate all logic for fetching and manipulating the displayed data\n * (including sorting, pagination, and filtering).\n */\nexport class ListDataSource extends DataSource<ListItem> {\n paginator!: OnemrvaMatPaginatorComponent;\n search!: FormControl;\n sort: MatSort | undefined;\n data$: BehaviorSubject<ListItem[]> = new BehaviorSubject<ListItem[]>([]);\n prevSearch = '';\n refresh$ = new Subject();\n\n constructor(private elementsService: ElementsService) {\n super();\n }\n\n /**\n * Connect this data source to the table. The table will only update when\n * the returned stream emits new items.\n * @returns A stream of the items to be rendered.\n */\n connect(): Observable<ListItem[]> {\n if (this.sort) {\n // Combine everything that affects the rendered data into one update\n // stream for the data-table to consume.\n return merge(\n this.sort.sortChange, // we listen to sort change\n this.search.valueChanges, // we listen to filter change\n this.paginator.pageSize$, // we listen to page size change\n this.paginator.pageIndex$, // we listen to page index change\n this.refresh$, // used to force a refresh\n ).pipe(\n startWith({}),\n tap(() => {\n // if search changed, we reset the pageIndex\n if (\n this.search.getRawValue().trim() !== '' &&\n this.search.getRawValue() !== this.prevSearch\n ) {\n this.paginator.resetPageIndex();\n this.prevSearch = this.search.getRawValue();\n }\n }),\n switchMap(() => {\n const active: keyof ListItem =\n (this.sort?.active as keyof ListItem) || 'id';\n const direction = this.sort?.direction || 'asc';\n // We return the api call observable called with the correct parameters\n return this.elementsService\n .getElementsList(\n active,\n direction,\n this.paginator.pageIndex,\n this.paginator.pageSize,\n this.search.getRawValue(),\n )\n .pipe(\n // We map the response to the Interface we need/want\n map(response => {\n this.paginator.length = response.length;\n this.paginator.pageIndex = response.page;\n this.paginator.pageSize = response.pageSize;\n return response.list;\n }),\n );\n }),\n );\n } else {\n throw Error('Please set the sort on the data source before connecting.');\n }\n }\n\n /**\n * Called when the table is being destroyed. Use this function, to clean up\n * any open connections or free any held resources that were set up during connect.\n */\n disconnect(): void {\n console.log('disconnect');\n }\n\n /**\n * If user click on select all items (outside current page),\n * this service functions returns the IDs of all items in datasource\n */\n all() {\n return this.elementsService.getAllElementsIds(this.search.getRawValue());\n }\n\n /**\n * If user click to delete an item, we tell the api and then refresh the list.\n * @param id\n */\n delete(id: number) {\n this.elementsService.deleteElement(id);\n this.refresh$.next('');\n }\n}\n","demo-table.component.html":"<h1>Table</h1>\n<mat-form-field class=\"search\">\n <mat-label>Search</mat-label>\n <input matInput [formControl]=\"search\" />\n @if (search.getRawValue()) {\n <button matSuffix mat-icon-button aria-label=\"Clear\" (click)=\"clear()\">\n <mat-icon>close</mat-icon>\n </button>\n }\n</mat-form-field>\n<p><a tabindex=\"0\" (click)=\"showHideColumns()\">Hide/Show column</a></p>\n<table mat-table class=\"full-width-table\" matSort aria-label=\"Elements\">\n <tr mat-header-row *matHeaderRowDef=\"allColumns\"></tr>\n <tr mat-header-row class=\"compact\" *matHeaderRowDef=\"['delay', 'days']\"></tr>\n\n <!-- Notice Row -->\n <tr\n mat-header-row\n class=\"onemrva-notice-row\"\n [class]=\"showNotice() ? '' : 'hidden'\"\n *matHeaderRowDef=\"showNotice() ? ['notice'] : []\"\n ></tr>\n @if (showNotice()) {\n <ng-container matColumnDef=\"notice\">\n <th *matHeaderCellDef colspan=\"7\">\n You have selected {{ checkboxes.length }} items.&nbsp;\n @if (checkboxes.length < paginator.length) {\n <a tabindex=\"0\" (click)=\"selectAll()\"\n >Select {{ paginator.length }} items</a\n >&nbsp;\n }\n @if (checkboxes.length >= 0) {\n <a tabindex=\"0\" (click)=\"deSelectAll()\"\n >Deselect {{ checkboxes.length }} items</a\n >\n }\n </th>\n </ng-container>\n }\n <!-- Notice Row -->\n\n <tr mat-row *matRowDef=\"let _; columns: allSubColumns\"></tr>\n\n <tr *matNoDataRow class=\"noresult\">\n <td colspan=\"7\">\n <onemrva-mat-empty-row (clear)=\"clear()\" />\n </td>\n </tr>\n\n <!-- checkbox Column -->\n <ng-container matColumnDef=\"check\">\n <th mat-header-cell *matHeaderCellDef [attr.rowspan]=\"2\">\n <div>\n <mat-checkbox\n aria-label=\"Check All\"\n value=\"toggleAll\"\n [checked]=\"checked\"\n [indeterminate]=\"undetermined\"\n (change)=\"toggleAllVisible($event)\"\n />\n </div>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <mat-checkbox\n #checkboxes\n aria-label=\"Check row {{ row.id }}\"\n [value]=\"row.id\"\n [checked]=\"isChecked(row.id)\"\n (change)=\"toggleOne($event, row.id)\"\n />\n </td>\n </ng-container>\n\n <!-- Id Column -->\n @if (columnIsVisible('id')) {\n <ng-container matColumnDef=\"id\" sticky>\n <th\n mat-header-cell\n class=\"compact\"\n *matHeaderCellDef\n mat-sort-header\n [attr.rowspan]=\"2\"\n >\n Id\n </th>\n <td mat-cell class=\"compact\" *matCellDef=\"let row\">\n {{ row.id }}\n </td>\n </ng-container>\n }\n\n <!-- Name Column -->\n @if (columnIsVisible('name')) {\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header [attr.rowspan]=\"2\">\n Name\n </th>\n <td id=\"{{ row.name }}\" mat-cell *matCellDef=\"let row\">\n <a href=\"/#table\">{{ row.name }}</a>\n </td>\n </ng-container>\n }\n\n <!-- Description Column -->\n @if (columnIsVisible('description')) {\n <ng-container matColumnDef=\"description\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header=\"description\"\n [attr.rowspan]=\"2\"\n class=\"d-table-cell-large description-cell\"\n >\n Description\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n class=\"d-table-cell-large description-cell\"\n >\n <span></span>\n {{ row.description }}\n </td></ng-container\n >\n }\n @if (columnIsVisible('suspension')) {\n <!-- Suspension Column -->\n <ng-container matColumnDef=\"suspension\">\n <th\n mat-header-cell\n *matHeaderCellDef\n [attr.colspan]=\"2\"\n class=\"align-center compact\"\n >\n Suspension\n </th>\n <!-- This column doesn't generate <td> items, so no need to add a definition for them -->\n </ng-container>\n }\n\n @if (subColumnIsVisible('delay')) {\n <!-- Delay Column -->\n <ng-container matColumnDef=\"delay\">\n <th mat-header-cell *matHeaderCellDef class=\"compact align-center\">\n Delay\n </th>\n <td mat-cell *matCellDef=\"let _\" class=\"align-center\">1</td>\n </ng-container>\n }\n\n @if (subColumnIsVisible('days')) {\n <!-- Days Column -->\n <ng-container matColumnDef=\"days\">\n <th mat-header-cell *matHeaderCellDef class=\"compact align-center\">\n Days\n </th>\n <td mat-cell *matCellDef=\"let _\" class=\"align-center\">2</td>\n </ng-container>\n }\n\n @if (columnIsVisible('more')) {\n <!-- Menu Column -->\n <ng-container matColumnDef=\"more\">\n <th mat-header-cell class=\"compact\" *matHeaderCellDef [attr.rowspan]=\"2\">\n Menu\n </th>\n <td mat-cell class=\"compact align-right\" *matCellDef=\"let row\">\n <button mat-icon-button [matMenuTriggerFor]=\"menu\" aria-label=\"More\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"edit(row)\" aria-label=\"Edit\">\n <mat-icon>edit</mat-icon>\n Edit\n </button>\n <button mat-menu-item (click)=\"delete(row)\" aria-label=\"Delete\">\n <mat-icon>delete</mat-icon>\n Delete\n </button>\n <button mat-menu-item (click)=\"share(row)\" aria-label=\"Share\">\n <mat-icon>share</mat-icon>\n Share\n </button>\n </mat-menu>\n </td>\n </ng-container>\n }\n</table>\n<div class=\"my-m\">\n <onemrva-mat-paginator\n [pageSizeSelector]=\"true\"\n [pageSizeOptions]=\"[5, 10, 20, 50]\"\n [pageSizeDefaultOption]=\"5\"\n [pageSize]=\"5\"\n previousKey=\"Previous\"\n nextKey=\"Next\"\n (page)=\"changePage($event)\"\n #paginator\n />\n</div>\n","demo-table.component.scss":"table {\n // Medium media query of material\n // the initial is display: table, but it doesn't show responsive on larger screens\n @media (max-width: 1279.98px) {\n // Block is required for the scroll overflow!\n display: block;\n overflow: auto;\n width: 100%;\n }\n\n .description-cell {\n min-width: 500px;\n }\n}\n","demo-table.component.ts":"import {\n AfterViewInit,\n Component,\n inject,\n ViewEncapsulation,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { MatSort, MatSortModule } from '@angular/material/sort';\nimport { MatTable, MatTableModule } from '@angular/material/table';\nimport { ListDataSource } from './demo-table-datasource';\nimport { PageEvent } from '@angular/material/paginator';\nimport { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCheckbox, MatCheckboxChange } from '@angular/material/checkbox';\n\nimport { MatChipsModule } from '@angular/material/chips';\nimport { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';\nimport { MatIconButton } from '@angular/material/button';\nimport {\n OnemrvaMatPaginatorComponent,\n OnemrvaMatPaginatorModule,\n} from '@onemrvapublic/design-system/mat-paginator';\nimport {\n MatFormField,\n MatLabel,\n MatSuffix,\n} from '@angular/material/form-field';\nimport { MatInput } from '@angular/material/input';\nimport { OnemrvaMatEmptyRowComponent } from '@onemrvapublic/design-system/mat-empty-row';\nimport { ElementsService, ListItem } from '../../_services/elements.service';\nimport { OnemrvaMatMessageBoxModule } from '@onemrvapublic/design-system/mat-message-box';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { take } from 'rxjs';\nimport { MatDialog, MatDialogModule } from '@angular/material/dialog';\nimport { DemoTableDialogContentComponent } from '../table-dialog-content';\nimport { MatIcon } from '@angular/material/icon';\nimport { DemoComponentBase } from '../../_demo/demo-component-base';\n\n@Component({\n selector: 'app-demo-table',\n templateUrl: './demo-table.component.html',\n styleUrls: ['./demo-table.component.scss'],\n\n standalone: true,\n imports: [\n ReactiveFormsModule,\n FormsModule,\n MatCheckbox,\n MatChipsModule,\n MatFormField,\n MatIconButton,\n MatInput,\n MatLabel,\n MatMenu,\n MatMenuTrigger,\n MatSortModule,\n MatSuffix,\n MatTableModule,\n MatDialogModule,\n MatIcon,\n OnemrvaMatPaginatorModule,\n OnemrvaMatEmptyRowComponent,\n OnemrvaMatMessageBoxModule,\n OnemrvaMatPaginatorComponent,\n MatMenuItem,\n ],\n encapsulation: ViewEncapsulation.None,\n})\nexport class DemoTableComponent\n extends DemoComponentBase\n implements AfterViewInit\n{\n readonly paginator = viewChild.required(OnemrvaMatPaginatorComponent);\n\n readonly sort = viewChild.required(MatSort);\n\n readonly table = viewChild.required(MatTable);\n\n readonly matCheckboxes = viewChildren<MatCheckbox>('checkboxes');\n\n dataSource: ListDataSource;\n\n // Checked checkbox values' list\n public checkboxes: number[] = [];\n\n // Check all checkbox statuses\n public undetermined = false;\n public checked = false;\n\n // all columns first level\n allColumns = ['check', 'id', 'name', 'description', 'suspension', 'more'];\n // all columns second level\n allSubColumns = [\n 'check',\n 'id',\n 'name',\n 'description',\n 'delay',\n 'days',\n 'more',\n ];\n // removable columns\n columns = ['id', 'name', 'description'];\n\n // Filter form control\n search: FormControl = new FormControl('');\n\n dialog = inject(MatDialog);\n elementsService = inject(ElementsService);\n\n constructor() {\n super();\n // Create a new DataSource\n this.dataSource = new ListDataSource(this.elementsService);\n\n // When user start filtering, we need to update the master checkbox\n this.search.valueChanges.pipe(takeUntilDestroyed()).subscribe(search => {\n console.log(search);\n setTimeout(() => {\n this.updateToggleAllStatus();\n }, 100);\n });\n }\n\n /**\n *\n */\n showHideColumns() {\n const panelClass: string[] = ['large'];\n const dialogRef = this.dialog.open(DemoTableDialogContentComponent, {\n data: {\n allColumns: this.allColumns,\n allSubColumns: this.allSubColumns,\n columns: this.columns,\n },\n panelClass,\n });\n dialogRef\n .afterClosed()\n .pipe(take(1))\n .subscribe((values: boolean[]) => {\n if (values !== undefined) {\n this.allColumns = ['check'];\n this.allSubColumns = ['check'];\n values.forEach((value, index) => {\n if (value) {\n this.allColumns.push(this.columns[index]);\n this.allSubColumns.push(this.columns[index]);\n }\n });\n this.allColumns.push('suspension');\n this.allColumns.push('more');\n this.allSubColumns.push('delay');\n this.allSubColumns.push('days');\n this.allSubColumns.push('more');\n }\n });\n }\n\n /**\n * We set the datasource and paginator\n */\n ngAfterViewInit(): void {\n this.dataSource.sort = this.sort();\n this.dataSource.paginator = this.paginator();\n this.dataSource.search = this.search;\n this.table().dataSource = this.dataSource;\n }\n\n /**\n * returns the column visible state\n * @param column\n */\n columnIsVisible(column: string) {\n return this.allColumns.includes(column);\n }\n /**\n * returns the column visible state\n * @param column\n */\n subColumnIsVisible(column: string) {\n return this.allSubColumns.includes(column);\n }\n\n /**\n * Edit row action\n * @param row\n */\n edit(row: ListItem) {\n alert(`Edit row n°${row.id}: ${row.name}`);\n }\n\n /**\n * Delete row action\n * @param row\n */\n delete(row: ListItem) {\n this.dataSource.delete(row.id);\n }\n\n /**\n * Share row action\n * @param row\n */\n share(row: ListItem) {\n alert(`Share row n°${row.id}: ${row.name}`);\n }\n\n /**\n * clear search\n */\n clear() {\n this.search.setValue('');\n }\n\n /**\n * paginator changes page\n * @param event\n */\n changePage(event: PageEvent) {\n console.log(event);\n // timeout necessary to take the changes in account\n setTimeout(() => {\n this.updateToggleAllStatus();\n }, 100);\n }\n\n /**\n * Is the notice visible\n */\n showNotice() {\n return this.checkboxes.length > 0;\n }\n\n /**\n * is a checkbox checked\n * @param id\n */\n isChecked(id: number) {\n return this.checkboxes.includes(id);\n }\n\n /**\n * Toggle One checkbox\n * @param $event\n * @param id\n */\n toggleOne($event: MatCheckboxChange, id: number) {\n const checked = $event.checked;\n this.toggleCheckBox(id, checked);\n this.updateToggleAllStatus();\n }\n\n /**\n * toggle the checkbox with checks\n * @param id\n * @param checked\n * @private\n */\n private toggleCheckBox(id: number, checked: boolean) {\n if (checked) {\n if (!this.checkboxes.includes(id)) {\n this.checkboxes.push(id);\n }\n } else {\n this.checkboxes.splice(this.checkboxes.indexOf(id), 1);\n }\n }\n\n /**\n * Toggle all visible checkboxes\n * @param $event\n */\n toggleAllVisible($event: MatCheckboxChange) {\n const checked = $event.checked;\n this.checked = checked;\n\n this.matCheckboxes().forEach(matCheckbox => {\n const id = parseInt(matCheckbox.value);\n this.toggleCheckBox(id, checked);\n });\n this.updateToggleAllStatus();\n }\n\n /**\n * Update the status of the toggle all checkbox to reflect on what is checked (checked, unchecked, undetermined)\n */\n updateToggleAllStatus() {\n this.undetermined = false;\n this.checked = false;\n let cnt = 0;\n\n this.matCheckboxes().forEach(matCheckbox => {\n const id = parseInt(matCheckbox.value);\n if (this.checkboxes.includes(id)) {\n cnt++;\n }\n });\n if (cnt > 0) {\n if (cnt === this.matCheckboxes().length) {\n this.checked = true;\n } else {\n this.undetermined = true;\n }\n }\n }\n\n /**\n * Select all checkbox in the full datasource's list\n */\n selectAll() {\n this.dataSource\n .all()\n .pipe(take(1))\n .subscribe(ids => {\n this.checkboxes = ids;\n this.updateToggleAllStatus();\n });\n }\n\n /**\n * Deselect all checkboxes\n */\n deSelectAll() {\n this.checkboxes = [];\n this.updateToggleAllStatus();\n }\n}\n"}
1
+ {"demo-table-datasource.ts":"import { DataSource } from '@angular/cdk/collections';\nimport { MatSort } from '@angular/material/sort';\nimport { map, tap } from 'rxjs/operators';\nimport {\n BehaviorSubject,\n merge,\n Observable,\n startWith,\n Subject,\n switchMap,\n} from 'rxjs';\nimport { OnemrvaMatPaginatorComponent } from '@onemrvapublic/design-system/mat-paginator';\nimport { FormControl } from '@angular/forms';\nimport { ElementsService, ListItem } from '../../_services/elements.service';\n\n/**\n * Data source for the List view. This class should\n * encapsulate all logic for fetching and manipulating the displayed data\n * (including sorting, pagination, and filtering).\n */\nexport class ListDataSource extends DataSource<ListItem> {\n paginator!: OnemrvaMatPaginatorComponent;\n search!: FormControl;\n sort: MatSort | undefined;\n data$: BehaviorSubject<ListItem[]> = new BehaviorSubject<ListItem[]>([]);\n prevSearch = '';\n refresh$ = new Subject();\n\n constructor(private elementsService: ElementsService) {\n super();\n }\n\n /**\n * Connect this data source to the table. The table will only update when\n * the returned stream emits new items.\n * @returns A stream of the items to be rendered.\n */\n connect(): Observable<ListItem[]> {\n if (this.sort) {\n // Combine everything that affects the rendered data into one update\n // stream for the data-table to consume.\n return merge(\n this.sort.sortChange, // we listen to sort change\n this.search.valueChanges, // we listen to filter change\n this.paginator.pageSize$, // we listen to page size change\n this.paginator.pageIndex$, // we listen to page index change\n this.refresh$, // used to force a refresh\n ).pipe(\n startWith({}),\n tap(() => {\n // if search changed, we reset the pageIndex\n if (\n this.search.getRawValue().trim() !== '' &&\n this.search.getRawValue() !== this.prevSearch\n ) {\n this.paginator.resetPageIndex();\n this.prevSearch = this.search.getRawValue();\n }\n }),\n switchMap(() => {\n const active: keyof ListItem =\n (this.sort?.active as keyof ListItem) || 'id';\n const direction = this.sort?.direction || 'asc';\n // We return the api call observable called with the correct parameters\n return this.elementsService\n .getElementsList(\n active,\n direction,\n this.paginator.pageIndex,\n this.paginator.pageSize,\n this.search.getRawValue(),\n )\n .pipe(\n // We map the response to the Interface we need/want\n map(response => {\n this.paginator.length = response.length;\n this.paginator.pageIndex = response.page;\n this.paginator.pageSize = response.pageSize;\n return response.list;\n }),\n );\n }),\n );\n } else {\n throw Error('Please set the sort on the data source before connecting.');\n }\n }\n\n /**\n * Called when the table is being destroyed. Use this function, to clean up\n * any open connections or free any held resources that were set up during connect.\n */\n disconnect(): void {\n console.log('disconnect');\n }\n\n /**\n * If user click on select all items (outside current page),\n * this service functions returns the IDs of all items in datasource\n */\n all() {\n return this.elementsService.getAllElementsIds(this.search.getRawValue());\n }\n\n /**\n * If user click to delete an item, we tell the api and then refresh the list.\n * @param id\n */\n delete(id: number) {\n this.elementsService.deleteElement(id);\n this.refresh$.next('');\n }\n}\n","demo-table.component.html":"<h1>Table</h1>\n<mat-form-field class=\"search\">\n <mat-label>Search</mat-label>\n <input matInput [formControl]=\"search\" />\n @if (search.getRawValue()) {\n <button matSuffix mat-icon-button aria-label=\"Clear\" (click)=\"clear()\">\n <mat-icon>close</mat-icon>\n </button>\n }\n</mat-form-field>\n<p><a tabindex=\"0\" (click)=\"showHideColumns()\">Hide/Show column</a></p>\n<table\n mat-table\n class=\"full-width-table multi-table-row-header\"\n matSort\n aria-label=\"Elements\"\n>\n <tr mat-header-row *matHeaderRowDef=\"allColumns\"></tr>\n <tr mat-header-row class=\"compact\" *matHeaderRowDef=\"['delay', 'days']\"></tr>\n\n <!-- Notice Row -->\n <tr\n mat-header-row\n class=\"onemrva-notice-row\"\n [class]=\"showNotice() ? '' : 'hidden'\"\n *matHeaderRowDef=\"showNotice() ? ['notice'] : []\"\n ></tr>\n @if (showNotice()) {\n <ng-container matColumnDef=\"notice\">\n <th *matHeaderCellDef colspan=\"7\">\n You have selected {{ checkboxes.length }} items.&nbsp;\n @if (checkboxes.length < paginator.length) {\n <a tabindex=\"0\" (click)=\"selectAll()\"\n >Select {{ paginator.length }} items</a\n >&nbsp;\n }\n @if (checkboxes.length >= 0) {\n <a tabindex=\"0\" (click)=\"deSelectAll()\"\n >Deselect {{ checkboxes.length }} items</a\n >\n }\n </th>\n </ng-container>\n }\n <!-- Notice Row -->\n\n <tr mat-row *matRowDef=\"let _; columns: allSubColumns\"></tr>\n\n <tr *matNoDataRow class=\"noresult\">\n <td colspan=\"7\">\n <onemrva-mat-empty-row (clear)=\"clear()\" />\n </td>\n </tr>\n\n <!-- checkbox Column -->\n <ng-container matColumnDef=\"check\">\n <th mat-header-cell *matHeaderCellDef [attr.rowspan]=\"2\">\n <div>\n <mat-checkbox\n aria-label=\"Check All\"\n value=\"toggleAll\"\n [checked]=\"checked\"\n [indeterminate]=\"undetermined\"\n (change)=\"toggleAllVisible($event)\"\n />\n </div>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <mat-checkbox\n #checkboxes\n aria-label=\"Check row {{ row.id }}\"\n [value]=\"row.id\"\n [checked]=\"isChecked(row.id)\"\n (change)=\"toggleOne($event, row.id)\"\n />\n </td>\n </ng-container>\n\n <!-- Id Column -->\n @if (columnIsVisible('id')) {\n <ng-container matColumnDef=\"id\" sticky>\n <th\n mat-header-cell\n class=\"compact\"\n *matHeaderCellDef\n mat-sort-header\n [attr.rowspan]=\"2\"\n >\n Id\n </th>\n <td mat-cell class=\"compact\" *matCellDef=\"let row\">\n {{ row.id }}\n </td>\n </ng-container>\n }\n\n <!-- Name Column -->\n @if (columnIsVisible('name')) {\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header [attr.rowspan]=\"2\">\n Name\n </th>\n <td id=\"{{ row.name }}\" mat-cell *matCellDef=\"let row\">\n <a href=\"/#table\">{{ row.name }}</a>\n </td>\n </ng-container>\n }\n\n <!-- Description Column -->\n @if (columnIsVisible('description')) {\n <ng-container matColumnDef=\"description\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header=\"description\"\n [attr.rowspan]=\"2\"\n class=\"d-table-cell-large description-cell\"\n >\n Description\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n class=\"d-table-cell-large description-cell\"\n >\n <span></span>\n {{ row.description }}\n </td></ng-container\n >\n }\n @if (columnIsVisible('suspension')) {\n <!-- Suspension Column -->\n <ng-container matColumnDef=\"suspension\">\n <th\n mat-header-cell\n *matHeaderCellDef\n [attr.colspan]=\"2\"\n class=\"align-center compact table__border-left table__border-right\"\n >\n Suspension\n </th>\n <!-- This column doesn't generate <td> items, so no need to add a definition for them -->\n </ng-container>\n }\n\n @if (subColumnIsVisible('delay')) {\n <!-- Delay Column -->\n <ng-container matColumnDef=\"delay\">\n <th\n mat-header-cell\n *matHeaderCellDef\n class=\"compact align-center table__border-left\"\n >\n Delay\n </th>\n <td mat-cell *matCellDef=\"let _\" class=\"align-center table__border-left\">\n 1\n </td>\n </ng-container>\n }\n\n @if (subColumnIsVisible('days')) {\n <!-- Days Column -->\n <ng-container matColumnDef=\"days\">\n <th\n mat-header-cell\n *matHeaderCellDef\n class=\"compact align-center table__border-right\"\n >\n Days\n </th>\n <td mat-cell *matCellDef=\"let _\" class=\"align-center table__border-right\">\n 2\n </td>\n </ng-container>\n }\n\n @if (columnIsVisible('more')) {\n <!-- Menu Column -->\n <ng-container matColumnDef=\"more\">\n <th mat-header-cell class=\"compact\" *matHeaderCellDef [attr.rowspan]=\"2\">\n Menu\n </th>\n <td mat-cell class=\"compact align-right\" *matCellDef=\"let row\">\n <button mat-icon-button [matMenuTriggerFor]=\"menu\" aria-label=\"More\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"edit(row)\" aria-label=\"Edit\">\n <mat-icon>edit</mat-icon>\n Edit\n </button>\n <button mat-menu-item (click)=\"delete(row)\" aria-label=\"Delete\">\n <mat-icon>delete</mat-icon>\n Delete\n </button>\n <button mat-menu-item (click)=\"share(row)\" aria-label=\"Share\">\n <mat-icon>share</mat-icon>\n Share\n </button>\n </mat-menu>\n </td>\n </ng-container>\n }\n</table>\n<div class=\"my-m\">\n <onemrva-mat-paginator\n [pageSizeSelector]=\"true\"\n [pageSizeOptions]=\"[5, 10, 20, 50]\"\n [pageSizeDefaultOption]=\"5\"\n [pageSize]=\"5\"\n previousKey=\"Previous\"\n nextKey=\"Next\"\n (page)=\"changePage($event)\"\n #paginator\n />\n</div>\n\n<h2>Expandable row</h2>\n<p>\n A row can expand to reveal its own sub-rows. Several rows can be open at the\n same time, so two payments can be compared side by side. A row without\n sub-rows has no toggle to press and no detail row of its own.\n</p>\n<p>\n <button\n mat-button\n type=\"button\"\n aria-label=\"Collapse all expanded rows\"\n [disabled]=\"expandedCount() === 0\"\n (click)=\"collapseAll()\"\n >\n Collapse all ({{ expandedCount() }})\n </button>\n</p>\n\n<!--\n The table is kept a real `display: table` (see the .scss) and scrolls from\n this wrapper on narrow screens instead of from the table element itself.\n-->\n<div class=\"demo-expandable-table-scroll\">\n <table\n mat-table\n multiTemplateDataRows\n class=\"full-width-table demo-expandable-table\"\n aria-label=\"Benefit payments per beneficiary\"\n [dataSource]=\"benefitRows\"\n >\n <tr mat-header-row *matHeaderRowDef=\"benefitColumns\"></tr>\n\n <!--\n Data row. The stripe comes from `dataIndex` and not from the row's position\n in the DOM, because the detail rows shift that position — see the .scss.\n -->\n <tr\n mat-row\n *matRowDef=\"let row; columns: benefitColumns; let i = dataIndex\"\n class=\"demo-expandable-row\"\n [class.demo-expandable-row--striped]=\"i % 2 === 0\"\n [class.demo-expandable-row--open]=\"isExpanded(row.id)\"\n ></tr>\n\n <!--\n Detail row. Rendered only for rows that actually have sub-rows — `when:`\n keeps an empty detail row out of the DOM entirely instead of rendering a\n blank one. For rows that do have sub-rows it stays rendered while closed,\n so its wrapper has something to run the open/close transition on; the .scss\n collapses it to zero height.\n -->\n <tr\n mat-row\n *matRowDef=\"let row; columns: detailColumns; when: hasDetailRow\"\n class=\"demo-expandable-detail-row\"\n [attr.id]=\"detailRowId(row.id)\"\n ></tr>\n\n <!-- Expand toggle Column -->\n <ng-container matColumnDef=\"expand\">\n <th mat-header-cell *matHeaderCellDef class=\"demo-expandable-toggle-cell\">\n Details\n </th>\n <td mat-cell *matCellDef=\"let row\" class=\"demo-expandable-toggle-cell\">\n <!-- A row without sub-rows has nothing to reveal, so it gets no toggle. -->\n @if (hasDetails(row)) {\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-expanded]=\"isExpanded(row.id)\"\n [attr.aria-controls]=\"detailRowId(row.id)\"\n [attr.aria-label]=\"\n (isExpanded(row.id) ? 'Collapse' : 'Expand') +\n ' the payment details of ' +\n row.name\n \"\n (click)=\"toggleExpanded(row.id)\"\n >\n <mat-icon>{{\n isExpanded(row.id) ? 'expand_less' : 'expand_more'\n }}</mat-icon>\n </button>\n }\n </td>\n </ng-container>\n\n <!-- Id Column -->\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef class=\"compact\">Id</th>\n <td mat-cell *matCellDef=\"let row\" class=\"compact\">{{ row.id }}</td>\n </ng-container>\n\n <!-- Name Column -->\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef>Beneficiary</th>\n <td mat-cell *matCellDef=\"let row\">{{ row.name }}</td>\n </ng-container>\n\n <!-- Office Column -->\n <ng-container matColumnDef=\"office\">\n <th mat-header-cell *matHeaderCellDef>Office</th>\n <td mat-cell *matCellDef=\"let row\">{{ row.office }}</td>\n </ng-container>\n\n <!-- Period Column -->\n <ng-container matColumnDef=\"period\">\n <th mat-header-cell *matHeaderCellDef>Period</th>\n <td mat-cell *matCellDef=\"let row\">{{ row.period }}</td>\n </ng-container>\n\n <!-- Total Column -->\n <ng-container matColumnDef=\"total\">\n <th mat-header-cell *matHeaderCellDef class=\"align-right\">Total</th>\n <td mat-cell *matCellDef=\"let row\" class=\"align-right\">\n {{ row.total }}\n </td>\n </ng-container>\n\n <!--\n Detail Column. A plain grid of divs rather than `onemrva-mat-panel` (its\n chrome is wrong inside a row) or a nested `mat-table`.\n -->\n <ng-container matColumnDef=\"detail\">\n <td\n mat-cell\n *matCellDef=\"let row\"\n class=\"demo-expandable-detail-cell\"\n [class.demo-expandable-detail-cell--open]=\"isExpanded(row.id)\"\n [attr.colspan]=\"benefitColumns.length\"\n >\n <div class=\"demo-expandable-detail\">\n <div class=\"demo-expandable-detail__inner\">\n <div\n class=\"demo-expandable-detail__grid\"\n role=\"table\"\n [attr.aria-hidden]=\"isExpanded(row.id) ? null : 'true'\"\n [attr.aria-label]=\"'Payment details of ' + row.name\"\n >\n <div class=\"demo-expandable-detail__row\" role=\"row\">\n <div class=\"demo-expandable-detail__header\" role=\"columnheader\">\n Code\n </div>\n <div class=\"demo-expandable-detail__header\" role=\"columnheader\">\n Benefit\n </div>\n <div\n class=\"demo-expandable-detail__header demo-expandable-detail__numeric\"\n role=\"columnheader\"\n >\n Days\n </div>\n <div\n class=\"demo-expandable-detail__header demo-expandable-detail__numeric\"\n role=\"columnheader\"\n >\n Amount\n </div>\n </div>\n @for (detail of detailsFor(row.id); track detail.code) {\n <div class=\"demo-expandable-detail__row\" role=\"row\">\n <div role=\"cell\">{{ detail.code }}</div>\n <div role=\"cell\">{{ detail.label }}</div>\n <div class=\"demo-expandable-detail__numeric\" role=\"cell\">\n {{ detail.days }}\n </div>\n <div class=\"demo-expandable-detail__numeric\" role=\"cell\">\n {{ detail.amount }}\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </td>\n </ng-container>\n </table>\n</div>\n","demo-table.component.scss":"table {\n // Medium media query of material\n // the initial is display: table, but it doesn't show responsive on larger screens\n // Block is required for the scroll overflow!\n display: block;\n overflow: auto;\n width: 100%;\n\n .description-cell {\n min-width: 500px;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Expandable row example (second table)\n//\n// This component sets `ViewEncapsulation.None`, so every selector here leaks\n// globally: all of them are scoped under `.demo-expandable-table`.\n// ---------------------------------------------------------------------------\n// --- Width ------------------------------------------------------------------\n//\n// The `table` rule at the top of this file makes every table its own scroll\n// container with `display: block; overflow: auto`. A block-level <table> wraps\n// its rows in an *anonymous table box*, and that box's width is `auto`, i.e.\n// shrink-to-fit: `width: 100%` then sizes the outer block, not the anonymous\n// table inside it, so a table whose content is narrower than its container\n// stops filling that container. The first table hides this because its\n// `min-width: 500px` description cell already makes it wider than the viewport;\n// this one is narrow enough to visibly shrink.\n//\n// So: scroll from a wrapper, and let the table stay a table.\n.demo-expandable-table-scroll {\n width: 100%;\n overflow: auto;\n}\n\n.demo-expandable-table {\n &.mat-mdc-table {\n display: table;\n overflow: visible;\n }\n\n // --- Striping ------------------------------------------------------------\n //\n // `theme/overrides/_table.scss:22` stripes rows positionally, with\n // `tbody tr:nth-child(odd)`. `multiTemplateDataRows` inserts a detail <tr>\n // after every data <tr>, so a data row's DOM position is no longer its\n // position in the data: every data row lands on an odd child (all striped,\n // no alternation) and the detail rows take over the even ones.\n //\n // Approach taken: neutralise the positional rule for this table only, and\n // re-apply the stripe from a class bound to `dataIndex` in the template.\n // The theme override is shared by the whole library, so it is left alone.\n // `:nth-child(4n + 1)` would only work while every data row is followed by a\n // detail row, which is already not the case — rows without sub-rows get no\n // detail row. The index-based class cannot drift from the data.\n //\n // Both selectors below outrank the theme override on specificity alone, so\n // they do not depend on stylesheet order.\n &.mat-mdc-table:not(.mat-card-table) tbody tr:nth-child(odd) {\n background-color: transparent;\n }\n\n &.mat-mdc-table:not(.mat-card-table) tbody tr.demo-expandable-row--striped {\n background-color: var(--mat-sys-primary-container);\n\n & + tr {\n background-color: var(--mat-sys-primary-container);\n }\n }\n\n // --- Rows ----------------------------------------------------------------\n .demo-expandable-toggle-cell {\n width: 4rem;\n // A row without sub-rows carries no toggle button. On a cell `height` is a\n // minimum, so reserving the button's own size here keeps that row exactly\n // as tall as the rows that do have one.\n height: var(--mdc-icon-button-state-layer-size, 40px);\n }\n\n // An open data row and its detail row read as a single block.\n &.mat-mdc-table tbody tr.demo-expandable-row--open td.mat-mdc-cell {\n border-bottom: none;\n }\n\n // A closed detail row has to take up no vertical space at all, and two\n // things stand in the way of that.\n //\n // First, Material puts `height: var(--mat-table-row-item-container-height,\n // 52px)` on `.mat-mdc-row`. On a <tr> that height is a *minimum*, not a\n // target, so a closed detail row still reserved a full, empty row under its\n // data row. Size the row from its cell instead.\n &.mat-mdc-table tbody tr.demo-expandable-detail-row {\n height: auto;\n }\n\n // Second, the theme pads and underlines every cell.\n &.mat-mdc-table tbody td.demo-expandable-detail-cell.mat-mdc-cell {\n padding: 0;\n border-bottom: none;\n }\n\n &.mat-mdc-table tbody td.demo-expandable-detail-cell--open.mat-mdc-cell {\n border-bottom: 1px solid var(--mat-sys-outline-variant);\n }\n\n // --- Open / close transition ---------------------------------------------\n //\n // A `grid-template-rows: 0fr -> 1fr` transition on the wrapper, so the\n // content keeps its natural height without it being measured in TypeScript.\n // No `@angular/animations`: there are no animation triggers in this project.\n .demo-expandable-detail {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 220ms ease-in-out;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n .demo-expandable-detail-cell--open .demo-expandable-detail {\n grid-template-rows: 1fr;\n }\n\n .demo-expandable-detail__inner {\n min-height: 0;\n overflow: hidden;\n }\n\n // --- Sub-rows ------------------------------------------------------------\n .demo-expandable-detail__grid {\n padding: var(--spacer) var(--spacer-and-half) var(--spacer-and-half);\n }\n\n .demo-expandable-detail__row {\n display: grid;\n grid-template-columns: 6rem minmax(12rem, 1fr) 5rem 8rem;\n gap: var(--spacer);\n padding: var(--half-spacer) 0;\n\n &:not(:last-child) {\n border-bottom: 1px solid var(--mat-sys-outline-variant);\n }\n }\n\n .demo-expandable-detail__header {\n font-weight: var(--mat-sys-title-small-weight, 500);\n color: var(--mat-sys-on-surface-variant);\n }\n\n .demo-expandable-detail__numeric {\n text-align: right;\n }\n}\n","demo-table.component.ts":"import {\n AfterViewInit,\n Component,\n computed,\n inject,\n signal,\n ViewEncapsulation,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { MatSort, MatSortModule } from '@angular/material/sort';\nimport { MatTable, MatTableModule } from '@angular/material/table';\nimport { ListDataSource } from './demo-table-datasource';\nimport { PageEvent } from '@angular/material/paginator';\nimport { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCheckbox, MatCheckboxChange } from '@angular/material/checkbox';\n\nimport { MatChipsModule } from '@angular/material/chips';\nimport { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';\nimport { MatButton, MatIconButton } from '@angular/material/button';\nimport {\n OnemrvaMatPaginatorComponent,\n OnemrvaMatPaginatorModule,\n} from '@onemrvapublic/design-system/mat-paginator';\nimport {\n MatFormField,\n MatLabel,\n MatSuffix,\n} from '@angular/material/form-field';\nimport { MatInput } from '@angular/material/input';\nimport { OnemrvaMatEmptyRowComponent } from '@onemrvapublic/design-system/mat-empty-row';\nimport { ElementsService, ListItem } from '../../_services/elements.service';\nimport { OnemrvaMatMessageBoxModule } from '@onemrvapublic/design-system/mat-message-box';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { take } from 'rxjs';\nimport { MatDialog, MatDialogModule } from '@angular/material/dialog';\nimport { DemoTableDialogContentComponent } from '../table-dialog-content';\nimport { MatIcon } from '@angular/material/icon';\nimport { DemoComponentBase } from '../../_demo/demo-component-base';\n\n/**\n * A row of the \"expandable row\" example.\n *\n * `ListItem` (from `ElementsService`) carries no nested data, so the second\n * example uses its own local shape instead of extending the service.\n */\ninterface BenefitRow {\n id: number;\n name: string;\n office: string;\n period: string;\n total: string;\n}\n\n/** One sub-row revealed when a {@link BenefitRow} is expanded. */\ninterface BenefitDetailRow {\n code: string;\n label: string;\n days: number;\n amount: string;\n}\n\n/**\n * Static data for the expandable-row example.\n *\n * Deliberately not a `ListDataSource`: that class throws when `sort` is unset\n * before `connect()` and expects a paginator, so reusing it would mean a second\n * sort and paginator instance for an example that has neither.\n */\nconst BENEFIT_ROWS: readonly BenefitRow[] = [\n {\n id: 1,\n name: 'Janssens, Marie',\n office: 'Brussel',\n period: 'January 2026',\n total: '€ 1.284,60',\n },\n {\n id: 2,\n name: 'Peeters, Tom',\n office: 'Antwerpen',\n period: 'January 2026',\n total: '€ 962,00',\n },\n {\n id: 3,\n name: 'Dubois, Claire',\n office: 'Liège',\n period: 'January 2026',\n total: '€ 1.431,25',\n },\n {\n id: 4,\n name: 'Van Damme, Lars',\n office: 'Gent',\n period: 'January 2026',\n total: '€ 744,15',\n },\n // No sub-rows: this row has no detail row at all and its toggle is disabled.\n {\n id: 5,\n name: 'Mertens, Hind',\n office: 'Brugge',\n period: 'January 2026',\n total: '€ 0,00',\n },\n];\n\n/** Sub-rows per row id. */\nconst BENEFIT_DETAILS: Readonly<Record<number, readonly BenefitDetailRow[]>> = {\n 1: [\n {\n code: 'A01',\n label: 'Full unemployment benefit',\n days: 18,\n amount: '€ 902,40',\n },\n {\n code: 'A07',\n label: 'Seniority supplement',\n days: 18,\n amount: '€ 241,20',\n },\n { code: 'C12', label: 'Commuting allowance', days: 12, amount: '€ 141,00' },\n ],\n 2: [\n {\n code: 'A01',\n label: 'Full unemployment benefit',\n days: 14,\n amount: '€ 728,00',\n },\n {\n code: 'B03',\n label: 'Temporary unemployment — economic',\n days: 4,\n amount: '€ 234,00',\n },\n ],\n 3: [\n {\n code: 'A01',\n label: 'Full unemployment benefit',\n days: 20,\n amount: '€ 1.002,00',\n },\n {\n code: 'A07',\n label: 'Seniority supplement',\n days: 20,\n amount: '€ 268,00',\n },\n { code: 'C12', label: 'Commuting allowance', days: 15, amount: '€ 161,25' },\n ],\n 4: [\n {\n code: 'B03',\n label: 'Temporary unemployment — economic',\n days: 11,\n amount: '€ 643,50',\n },\n { code: 'C12', label: 'Commuting allowance', days: 9, amount: '€ 100,65' },\n ],\n};\n\n@Component({\n selector: 'app-demo-table',\n templateUrl: './demo-table.component.html',\n styleUrls: ['./demo-table.component.scss'],\n\n standalone: true,\n imports: [\n ReactiveFormsModule,\n FormsModule,\n MatCheckbox,\n MatChipsModule,\n MatFormField,\n MatIconButton,\n MatInput,\n MatLabel,\n MatMenu,\n MatMenuTrigger,\n MatSortModule,\n MatSuffix,\n MatTableModule,\n MatDialogModule,\n MatIcon,\n OnemrvaMatPaginatorModule,\n OnemrvaMatEmptyRowComponent,\n OnemrvaMatMessageBoxModule,\n OnemrvaMatPaginatorComponent,\n MatMenuItem,\n MatButton,\n ],\n encapsulation: ViewEncapsulation.None,\n})\nexport class DemoTableComponent\n extends DemoComponentBase\n implements AfterViewInit\n{\n readonly paginator = viewChild.required(OnemrvaMatPaginatorComponent);\n\n readonly sort = viewChild.required(MatSort);\n\n readonly table = viewChild.required(MatTable);\n\n readonly matCheckboxes = viewChildren<MatCheckbox>('checkboxes');\n\n dataSource: ListDataSource;\n\n // Checked checkbox values' list\n public checkboxes: number[] = [];\n\n // Check all checkbox statuses\n public undetermined = false;\n public checked = false;\n\n // all columns first level\n allColumns = ['check', 'id', 'name', 'description', 'suspension', 'more'];\n // all columns second level\n allSubColumns = [\n 'check',\n 'id',\n 'name',\n 'description',\n 'delay',\n 'days',\n 'more',\n ];\n // removable columns\n columns = ['id', 'name', 'description'];\n\n // Filter form control\n search: FormControl = new FormControl('');\n\n // ---------------------------------------------------------------------------\n // Expandable row example (second table)\n //\n // New state is signal-based per `.junie/guidelines.md`; the plain mutable\n // fields above belong to the first table and are left as they are.\n // ---------------------------------------------------------------------------\n\n /** Rows of the expandable-row example. */\n readonly benefitRows = BENEFIT_ROWS;\n\n /** Columns of the expandable-row example; its length is the detail colspan. */\n readonly benefitColumns = [\n 'id',\n 'name',\n 'office',\n 'period',\n 'total',\n 'expand',\n ];\n\n /** The single column of the detail row, which spans the whole table. */\n readonly detailColumns = ['detail'];\n\n /**\n * Ids of every open row.\n *\n * A set rather than a single id: several rows may be open at the same time,\n * so two rows can be compared without collapsing one of them.\n */\n private readonly expandedIds = signal<ReadonlySet<number>>(new Set<number>());\n\n /** How many rows are open; drives the \"Collapse all\" control. */\n readonly expandedCount = computed(() => this.expandedIds().size);\n\n /**\n * Row predicate for the detail row definition.\n *\n * A detail row exists only for rows that have sub-rows — an empty one would\n * otherwise be rendered (and, without the height reset in the .scss, be\n * visible) under every row. For rows that do have sub-rows the detail row\n * stays in the DOM while closed, so that its wrapper has something to run the\n * open/close CSS transition on.\n * @param _index unused row index required by the `when` signature\n * @param row\n */\n readonly hasDetailRow = (_index: number, row: BenefitRow): boolean =>\n this.hasDetails(row);\n\n /**\n * Does this row have sub-rows to reveal?\n * @param row\n */\n hasDetails(row: BenefitRow): boolean {\n return this.detailsFor(row.id).length > 0;\n }\n\n /**\n * Is the row with this id expanded?\n * @param id\n */\n isExpanded(id: number): boolean {\n return this.expandedIds().has(id);\n }\n\n /**\n * Open a collapsed row, or collapse an open one.\n * @param id\n */\n toggleExpanded(id: number): void {\n this.expandedIds.update(ids => {\n const next = new Set(ids);\n if (!next.delete(id)) {\n next.add(id);\n }\n return next;\n });\n }\n\n /**\n * Collapse every open row.\n */\n collapseAll(): void {\n this.expandedIds.set(new Set<number>());\n }\n\n /**\n * Sub-rows of the row with this id.\n * @param id\n */\n detailsFor(id: number): readonly BenefitDetailRow[] {\n return BENEFIT_DETAILS[id] ?? [];\n }\n\n /**\n * DOM id of a detail row, so the toggle button can point `aria-controls` at it.\n * @param id\n */\n detailRowId(id: number): string {\n return `demo-table-benefit-detail-${id}`;\n }\n\n dialog = inject(MatDialog);\n elementsService = inject(ElementsService);\n\n constructor() {\n super();\n // Create a new DataSource\n this.dataSource = new ListDataSource(this.elementsService);\n\n // When user start filtering, we need to update the master checkbox\n this.search.valueChanges.pipe(takeUntilDestroyed()).subscribe(search => {\n console.log(search);\n setTimeout(() => {\n this.updateToggleAllStatus();\n }, 100);\n });\n }\n\n /**\n *\n */\n showHideColumns() {\n const panelClass: string[] = ['large'];\n const dialogRef = this.dialog.open(DemoTableDialogContentComponent, {\n data: {\n allColumns: this.allColumns,\n allSubColumns: this.allSubColumns,\n columns: this.columns,\n },\n panelClass,\n });\n dialogRef\n .afterClosed()\n .pipe(take(1))\n .subscribe((values: boolean[]) => {\n if (values !== undefined) {\n this.allColumns = ['check'];\n this.allSubColumns = ['check'];\n values.forEach((value, index) => {\n if (value) {\n this.allColumns.push(this.columns[index]);\n this.allSubColumns.push(this.columns[index]);\n }\n });\n this.allColumns.push('suspension');\n this.allColumns.push('more');\n this.allSubColumns.push('delay');\n this.allSubColumns.push('days');\n this.allSubColumns.push('more');\n }\n });\n }\n\n /**\n * We set the datasource and paginator\n */\n ngAfterViewInit(): void {\n this.dataSource.sort = this.sort();\n this.dataSource.paginator = this.paginator();\n this.dataSource.search = this.search;\n this.table().dataSource = this.dataSource;\n }\n\n /**\n * returns the column visible state\n * @param column\n */\n columnIsVisible(column: string) {\n return this.allColumns.includes(column);\n }\n /**\n * returns the column visible state\n * @param column\n */\n subColumnIsVisible(column: string) {\n return this.allSubColumns.includes(column);\n }\n\n /**\n * Edit row action\n * @param row\n */\n edit(row: ListItem) {\n alert(`Edit row n°${row.id}: ${row.name}`);\n }\n\n /**\n * Delete row action\n * @param row\n */\n delete(row: ListItem) {\n this.dataSource.delete(row.id);\n }\n\n /**\n * Share row action\n * @param row\n */\n share(row: ListItem) {\n alert(`Share row n°${row.id}: ${row.name}`);\n }\n\n /**\n * clear search\n */\n clear() {\n this.search.setValue('');\n }\n\n /**\n * paginator changes page\n * @param event\n */\n changePage(event: PageEvent) {\n console.log(event);\n // timeout necessary to take the changes in account\n setTimeout(() => {\n this.updateToggleAllStatus();\n }, 100);\n }\n\n /**\n * Is the notice visible\n */\n showNotice() {\n return this.checkboxes.length > 0;\n }\n\n /**\n * is a checkbox checked\n * @param id\n */\n isChecked(id: number) {\n return this.checkboxes.includes(id);\n }\n\n /**\n * Toggle One checkbox\n * @param $event\n * @param id\n */\n toggleOne($event: MatCheckboxChange, id: number) {\n const checked = $event.checked;\n this.toggleCheckBox(id, checked);\n this.updateToggleAllStatus();\n }\n\n /**\n * toggle the checkbox with checks\n * @param id\n * @param checked\n * @private\n */\n private toggleCheckBox(id: number, checked: boolean) {\n if (checked) {\n if (!this.checkboxes.includes(id)) {\n this.checkboxes.push(id);\n }\n } else {\n this.checkboxes.splice(this.checkboxes.indexOf(id), 1);\n }\n }\n\n /**\n * Toggle all visible checkboxes\n * @param $event\n */\n toggleAllVisible($event: MatCheckboxChange) {\n const checked = $event.checked;\n this.checked = checked;\n\n this.matCheckboxes().forEach(matCheckbox => {\n const id = parseInt(matCheckbox.value);\n this.toggleCheckBox(id, checked);\n });\n this.updateToggleAllStatus();\n }\n\n /**\n * Update the status of the toggle all checkbox to reflect on what is checked (checked, unchecked, undetermined)\n */\n updateToggleAllStatus() {\n this.undetermined = false;\n this.checked = false;\n let cnt = 0;\n\n this.matCheckboxes().forEach(matCheckbox => {\n const id = parseInt(matCheckbox.value);\n if (this.checkboxes.includes(id)) {\n cnt++;\n }\n });\n if (cnt > 0) {\n if (cnt === this.matCheckboxes().length) {\n this.checked = true;\n } else {\n this.undetermined = true;\n }\n }\n }\n\n /**\n * Select all checkbox in the full datasource's list\n */\n selectAll() {\n this.dataSource\n .all()\n .pipe(take(1))\n .subscribe(ids => {\n this.checkboxes = ids;\n this.updateToggleAllStatus();\n });\n }\n\n /**\n * Deselect all checkboxes\n */\n deSelectAll() {\n this.checkboxes = [];\n this.updateToggleAllStatus();\n }\n}\n"}