UNPKG

acontplus-ui-components

Version:

Angular Material UI component library with dynamic tables, theming support, dialog wrappers, customer management components, and comprehensive styling utilities

1 lines 266 kB
{"version":3,"file":"acontplus-ui-components.mjs","sources":["../../../projects/acontplus-ui-components/src/lib/constants/snackbar.constants.ts","../../../projects/acontplus-ui-components/src/lib/components/cards/mat-dynamic-card/mat-dynamic-card.component.ts","../../../projects/acontplus-ui-components/src/lib/components/cards/mat-dynamic-card/mat-dynamic-card.component.html","../../../projects/acontplus-ui-components/src/lib/components/dialog-wrapper/dialog-wrapper.component.ts","../../../projects/acontplus-ui-components/src/lib/components/dialog-wrapper/dialog-wrapper.component.html","../../../projects/acontplus-ui-components/src/lib/components/icons/icon-user/icon-user.component.ts","../../../projects/acontplus-ui-components/src/lib/components/icons/icon-user/icon-user.component.html","../../../projects/acontplus-ui-components/src/lib/components/icons/svg-icon/svg-icon.component.ts","../../../projects/acontplus-ui-components/src/lib/components/icons/svg-icon/svg-icon.component.html","../../../projects/acontplus-ui-components/src/lib/components/mat-input-chip/mat-input-chip.component.ts","../../../projects/acontplus-ui-components/src/lib/components/mat-input-chip/mat-input-chip.component.html","../../../projects/acontplus-ui-components/src/lib/components/mat-theme-button/mat-theme-button.component.ts","../../../projects/acontplus-ui-components/src/lib/components/mat-theme-button/mat-theme-button.component.html","../../../projects/acontplus-ui-components/src/lib/components/spinner/spinner.component.ts","../../../projects/acontplus-ui-components/src/lib/components/spinner/spinner.component.html","../../../projects/acontplus-ui-components/src/lib/services/snackbar/snackbar.config.ts","../../../projects/acontplus-ui-components/src/lib/services/snackbar/snackbar.service.ts","../../../projects/acontplus-ui-components/src/lib/components/snackbar/snackbar-notification/snackbar-notification.component.ts","../../../projects/acontplus-ui-components/src/lib/components/snackbar/snackbar-notification/snackbar-notification.component.html","../../../projects/acontplus-ui-components/src/lib/components/tables/custom-tabulator/custom-tabulator.component.ts","../../../projects/acontplus-ui-components/src/lib/components/tables/custom-tabulator/custom-tabulator.component.html","../../../projects/acontplus-ui-components/src/lib/pipes/get-total.pipe.ts","../../../projects/acontplus-ui-components/src/lib/pipes/status-display.pipe.ts","../../../projects/acontplus-ui-components/src/lib/components/tables/mat-dynamic-table/mat-dynamic-table.component.ts","../../../projects/acontplus-ui-components/src/lib/components/tables/mat-dynamic-table/mat-dynamic-table.component.html","../../../projects/acontplus-ui-components/src/lib/services/dialog/advanced-dialog.service.ts","../../../projects/acontplus-ui-components/src/lib/services/overlay.service.ts","../../../projects/acontplus-ui-components/src/lib/services/theme.service.ts","../../../projects/acontplus-ui-components/src/lib/models/autocomplete-wrapper.model.ts","../../../projects/acontplus-ui-components/src/lib/services/autocomplete-wrapper.service.ts","../../../projects/acontplus-ui-components/src/lib/components/theme-toggle/theme-toggle.component.ts","../../../projects/acontplus-ui-components/src/lib/components/theme-toggle/theme-toggle.component.html","../../../projects/acontplus-ui-components/src/lib/models/mat-table-models/field-definition.model.ts","../../../projects/acontplus-ui-components/src/lib/models/mat-table-models/column-definition.model.ts","../../../projects/acontplus-ui-components/src/lib/models/mat-table-models/table-cell-index.model.ts","../../../projects/acontplus-ui-components/src/lib/models/pagination.model.ts","../../../projects/acontplus-ui-components/src/lib/components/autocomplete-wrapper/autocomplete-wrapper.component.ts","../../../projects/acontplus-ui-components/src/lib/components/autocomplete-wrapper/autocomplete-wrapper.component.html","../../../projects/acontplus-ui-components/src/lib/directives/to-upper-case.directive.ts","../../../projects/acontplus-ui-components/src/lib/inputs/dynamic-input.ts","../../../projects/acontplus-ui-components/src/lib/interceptors/spinner.interceptor.ts","../../../projects/acontplus-ui-components/src/lib/customers-ui/customer-add-edit/customer-add-edit.component.ts","../../../projects/acontplus-ui-components/src/lib/customers-ui/customer-add-edit/customer-add-edit.component.html","../../../projects/acontplus-ui-components/src/lib/customers-ui/customer-card/customer-card.component.ts","../../../projects/acontplus-ui-components/src/lib/customers-ui/customer-card/customer-card.component.html","../../../projects/acontplus-ui-components/src/public-api.ts","../../../projects/acontplus-ui-components/src/acontplus-ui-components.ts"],"sourcesContent":["import { SnackbarType } from '../types';\n\nexport const SNACKBAR_MESSAGES = {\n SUCCESS: {\n SAVE: 'Data saved successfully',\n DELETE: 'Item deleted successfully',\n UPDATE: 'Data updated successfully',\n UPLOAD: 'File uploaded successfully',\n },\n ERROR: {\n SAVE: 'Failed to save data',\n DELETE: 'Failed to delete item',\n UPDATE: 'Failed to update data',\n UPLOAD: 'Failed to upload file',\n NETWORK: 'Network error occurred',\n UNKNOWN: 'An unexpected error occurred',\n },\n WARNING: {\n UNSAVED_CHANGES: 'You have unsaved changes',\n SESSION_EXPIRING: 'Your session is about to expire',\n STORAGE_FULL: 'Storage is running low',\n },\n INFO: {\n LOADING: 'Loading data...',\n PROCESSING: 'Processing request...',\n MAINTENANCE: 'System maintenance scheduled',\n },\n} as const;\n\nexport const SNACKBAR_DURATIONS = {\n SHORT: 3000,\n MEDIUM: 5000,\n LONG: 8000,\n PERSISTENT: 0,\n} as const;\n\nexport const SNACKBAR_ICONS: Record<SnackbarType, string> = {\n success: 'check_circle',\n error: 'error',\n warning: 'warning',\n info: 'info',\n} as const;\n","import { Component, input, output, booleanAttribute } from '@angular/core';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\n\n/**\n * A versatile card component that wraps Angular Material's mat-card with additional functionality\n * and customization options. This component provides a consistent card layout with configurable\n * header, content, and action areas.\n *\n * @example\n * <acp-mat-dynamic-card\n * [cardTitle]=\"'Card Title'\"\n * [cardSubtitle]=\"'Card Subtitle'\"\n * [isHeaderVisible]=\"true\"\n * [areActionsVisible]=\"true\"\n * (primaryButtonClicked)=\"onPrimaryAction()\">\n * Card content goes here\n * </acp-mat-dynamic-card>\n */\n@Component({\n selector: 'acp-mat-dynamic-card',\n standalone: true,\n imports: [MatCardModule, MatButtonModule, MatIconModule],\n templateUrl: './mat-dynamic-card.component.html',\n styleUrl: './mat-dynamic-card.component.scss',\n})\nexport class MatDynamicCardComponent {\n // Header inputs\n /**\n * The title text to display in the card header.\n * @default null\n */\n cardTitle = input<string | null>(null);\n\n /**\n * The subtitle text to display in the card header.\n * @default null\n */\n cardSubtitle = input<string | null>(null);\n\n /**\n * URL for the avatar image to display in the card header.\n * @default null\n */\n avatarImageUrl = input<string | null>(null);\n\n /**\n * Whether to show the card header section.\n * @default false\n */\n isHeaderVisible = input(false, { transform: booleanAttribute });\n\n // Content inputs\n /**\n * CSS padding value for the card content area.\n * @default '1rem'\n */\n contentPadding = input<string>('1rem');\n\n /**\n * Whether to show a divider between the header and content sections.\n * @default false\n */\n hasDivider = input(false, { transform: booleanAttribute });\n\n // Action inputs\n /**\n * Whether to show the action buttons section.\n * @default false\n */\n areActionsVisible = input(false, { transform: booleanAttribute });\n\n /**\n * Text for the primary action button.\n * @default 'Confirm'\n */\n primaryButtonText = input('Confirm');\n\n /**\n * Text for the secondary action button.\n * @default 'Cancel'\n */\n secondaryButtonText = input('Cancel');\n\n /**\n * Material icon name for the primary button.\n * @default null\n */\n primaryButtonIcon = input<string | null>(null);\n\n /**\n * Material icon name for the secondary button.\n * @default null\n */\n secondaryButtonIcon = input<string | null>(null);\n\n /**\n * Alignment of the action buttons.\n * @default 'end'\n */\n buttonsPosition = input<'start' | 'end'>('end');\n\n // Output events\n /**\n * Event emitted when the primary button is clicked.\n */\n primaryButtonClicked = output<void>();\n\n /**\n * Event emitted when the secondary button is clicked.\n */\n secondaryButtonClicked = output<void>();\n\n /**\n * Event emitted when the card is clicked.\n */\n cardClicked = output<Event>();\n\n // Methods\n /**\n * Handles the primary button click event.\n * Stops event propagation and emits the primaryButtonClicked event.\n * @param event The click event\n */\n handlePrimaryButtonClick(event: Event): void {\n event.stopPropagation();\n this.primaryButtonClicked.emit();\n }\n\n /**\n * Handles the secondary button click event.\n * Stops event propagation and emits the secondaryButtonClicked event.\n * @param event The click event\n */\n handleSecondaryButtonClick(event: Event): void {\n event.stopPropagation();\n this.secondaryButtonClicked.emit();\n }\n\n /**\n * Handles the card click event.\n * Emits the cardClicked event with the original event.\n * @param event The click event\n */\n handleCardClick(event: Event): void {\n this.cardClicked.emit(event);\n }\n}\n","<mat-card\n appearance=\"outlined\"\n (click)=\"handleCardClick($event)\"\n [class.mat-card-divider]=\"hasDivider()\"\n>\n @if (isHeaderVisible()) {\n <mat-card-header>\n @if (avatarImageUrl()) {\n <img mat-card-avatar [src]=\"avatarImageUrl()\" alt=\"Card avatar\" />\n }\n @if (cardTitle()) {\n <mat-card-title>{{ cardTitle() }}</mat-card-title>\n }\n @if (cardSubtitle()) {\n <mat-card-subtitle>{{ cardSubtitle() }}</mat-card-subtitle>\n }\n </mat-card-header>\n }\n\n <mat-card-content [style.padding]=\"contentPadding()\">\n <ng-content></ng-content>\n </mat-card-content>\n\n @if (areActionsVisible()) {\n <mat-card-actions [align]=\"buttonsPosition()\">\n @if (secondaryButtonText()) {\n <button mat-button color=\"warn\" (click)=\"handleSecondaryButtonClick($event)\">\n @if (secondaryButtonIcon()) {\n <mat-icon>{{ secondaryButtonIcon() }}</mat-icon>\n }\n {{ secondaryButtonText() }}\n </button>\n }\n\n @if (primaryButtonText()) {\n <button mat-button color=\"primary\" (click)=\"handlePrimaryButtonClick($event)\">\n @if (primaryButtonIcon()) {\n <mat-icon>{{ primaryButtonIcon() }}</mat-icon>\n }\n {{ primaryButtonText() }}\n </button>\n }\n </mat-card-actions>\n }\n</mat-card>\n","import {\n Component,\n ViewChild,\n ViewContainerRef,\n AfterViewInit,\n ElementRef,\n ChangeDetectionStrategy,\n inject,\n} from '@angular/core';\n\nimport { CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';\nimport { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\nimport { DialogWrapperConfig } from '../../services';\n\n/**\n * A wrapper component for Angular Material dialogs that provides a consistent look and feel,\n * including a draggable header and the ability to dynamically create components inside the dialog.\n *\n * This component is typically used with the AdvancedDialogService's openInWrapper method.\n *\n * @example\n * // In your service or component:\n * this.dialogService.openInWrapper({\n * component: YourDialogContentComponent,\n * title: 'Dialog Title',\n * icon: 'info',\n * data: { message: 'This is some data passed to the dialog content component' }\n * });\n */\n@Component({\n selector: 'acp-dialog-wrapper',\n standalone: true,\n imports: [CdkDrag, CdkDragHandle, MatDialogModule, MatIconModule, MatButtonModule],\n templateUrl: './dialog-wrapper.component.html',\n styleUrls: ['./dialog-wrapper.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DialogWrapperComponent implements AfterViewInit {\n dialogRef = inject<MatDialogRef<DialogWrapperComponent>>(MatDialogRef);\n config = inject<DialogWrapperConfig>(MAT_DIALOG_DATA);\n\n /**\n * A template reference that acts as an anchor for dynamic content.\n * This is where the component specified in the config will be rendered.\n */\n @ViewChild('contentHost', { read: ViewContainerRef, static: true })\n contentHost!: ViewContainerRef;\n\n /**\n * A reference to the header element for the z-index focus logic.\n * Used to bring the dialog to the front when clicked.\n */\n @ViewChild('dialogHeader', { static: false }) header?: ElementRef;\n\n /**\n * Static counter to track the highest z-index for multiple dialogs.\n * Ensures that the most recently clicked dialog appears on top.\n */\n private static lastZIndex = 1000;\n\n /** Inserted by Angular inject() migration for backwards compatibility */\n constructor(...args: unknown[]);\n\n /**\n * Creates an instance of DialogWrapperComponent.\n *\n * @param dialogRef Reference to the dialog opened via the Material Dialog service\n * @param config Configuration for the dialog wrapper, injected from MAT_DIALOG_DATA\n */\n constructor() {}\n\n /**\n * Lifecycle hook that initializes the dynamic content after the view is ready.\n * Creates the component specified in the config and passes data to it.\n */\n ngAfterViewInit(): void {\n // Dynamically create the content component after the view is ready.\n this.contentHost.clear();\n const componentRef = this.contentHost.createComponent(this.config.component);\n\n // Pass the provided data directly to the new component's instance.\n // This requires the content component to have an @Input() property named 'data'.\n if (this.config.data && componentRef.instance) {\n (componentRef.instance as any).data = this.config.data;\n }\n }\n\n /**\n * Closes the dialog.\n * Called when the close button in the header is clicked.\n */\n onClose(): void {\n this.dialogRef.close();\n }\n\n /**\n * Brings the dialog to the front by adjusting its z-index.\n * Called when the dialog header is clicked.\n */\n bringToFront(): void {\n const pane = this.header?.nativeElement.closest('.cdk-overlay-pane') as HTMLElement;\n if (pane) {\n pane.style.zIndex = (++DialogWrapperComponent.lastZIndex).toString();\n }\n }\n}\n","@if (!config.hideHeader) {\r\n <div\r\n class=\"dialog-header\"\r\n cdkDrag\r\n cdkDragRootElement=\".cdk-overlay-pane\"\r\n cdkDragHandle\r\n #dialogHeader\r\n (mousedown)=\"bringToFront()\"\r\n >\r\n <h6 mat-dialog-title>\r\n @if (config.icon) {\r\n <mat-icon class=\"title-icon\" aria-hidden=\"true\">{{ config.icon }}</mat-icon>\r\n }\r\n <span>{{ config.title }}</span>\r\n <button mat-icon-button class=\"close-button\" (click)=\"onClose()\" aria-label=\"Cerrar diálogo\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </h6>\r\n </div>\r\n}\r\n\r\n<ng-template #contentHost></ng-template>\r\n","import { Component, input } from '@angular/core';\n\n@Component({\n selector: 'acp-icon-user',\n imports: [],\n templateUrl: './icon-user.component.html',\n styleUrl: './icon-user.component.css',\n})\nexport class IconUserComponent {\n size = input('35');\n}\n","<svg\n [attr.width]=\"size()\"\n [attr.height]=\"size()\"\n version=\"1.1\"\n id=\"Capa_1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n viewBox=\"0 0 480 480\"\n xml:space=\"preserve\"\n fill=\"#000000\"\n>\n <g id=\"SVGRepo_bgCarrier\" stroke-width=\"0\"></g>\n <g id=\"SVGRepo_tracerCarrier\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></g>\n <g id=\"SVGRepo_iconCarrier\">\n <g>\n <g>\n <circle style=\"fill: #b8bac0\" cx=\"240\" cy=\"240\" r=\"240\"></circle>\n </g>\n <g>\n <g>\n <path\n style=\"fill: #ffffff\"\n d=\"M240,360.07c-27.944,0-53.297-11.991-72.003-31.372c-0.014,11.615-0.436,28.379-3.516,40.611 c2.02,1.235,3.588,3.262,3.894,5.784c3.992,32.484,34.781,56.977,71.625,56.977c36.836,0,67.625-24.496,71.625-56.977 c0.31-2.525,1.844-4.549,3.895-5.78c-3.08-12.233-3.503-28.999-3.517-40.615C293.297,348.079,267.944,360.07,240,360.07z\"\n ></path>\n </g>\n </g>\n <g>\n <g>\n <path\n style=\"fill: #d7dbe0\"\n d=\"M310.44,330.174c-18.549,18.477-43.242,29.896-70.44,29.896 c-27.944,0-53.297-11.991-72.003-31.372c-0.014,11.615-0.436,28.379-3.516,40.611c2.02,1.235,3.588,3.262,3.894,5.784 c1.765,14.359,8.778,27.144,19.223,36.954C235.766,405.265,290.437,357.702,310.44,330.174z\"\n ></path>\n </g>\n </g>\n <g>\n <g>\n <path\n style=\"fill: #ffffff\"\n d=\"M312,160.07H176c-22.055,0-40,17.945-40,40v48c0,61.758,46.656,112,104,112s104-50.242,104-112 v-56C344,174.426,329.648,160.07,312,160.07z\"\n ></path>\n </g>\n </g>\n <g>\n <g>\n <path\n style=\"fill: #5c546a\"\n d=\"M296,72.07H192c-15.047,0-27.695,10.438-31.102,24.449C133.359,100.02,112,123.598,112,152.07v40 c0,20.617,8.752,39.851,24,53.52v-45.52c0-22.055,17.945-40,40-40h136c17.648,0,32,14.355,32,32v53.511 c15.251-13.667,24-32.899,24-53.511v-48C368,104.371,335.703,72.07,296,72.07z\"\n ></path>\n </g>\n </g>\n <g>\n <path\n style=\"fill: #5c546a\"\n d=\"M61.632,400.544C105.562,449.319,169.191,480,240,480s134.438-30.681,178.368-79.456 c-7.66-10.356-18.462-18.77-32.352-22.659c-0.32-0.09-0.641-0.16-0.969-0.207l-63.859-9.582c-0.391-0.059-1.227-0.09-1.625-0.09 c-4.039,0-7.445,3.012-7.938,7.023c-4,32.48-34.789,56.977-71.625,56.977c-36.844,0-67.633-24.492-71.625-56.977 c-0.5-4.129-4.219-7.234-8.141-7.02c-0.469-0.027-0.93,0.012-1.422,0.086l-63.859,9.582c-0.328,0.047-0.648,0.117-0.969,0.207 C80.094,381.775,69.292,390.188,61.632,400.544z\"\n ></path>\n </g>\n </g>\n </g>\n</svg>\n","import { Component, input } from '@angular/core';\n\n@Component({\n selector: 'acp-svg-icon',\n imports: [],\n templateUrl: './svg-icon.component.html',\n styleUrl: './svg-icon.component.css',\n})\nexport class SvgIconComponent {\n id = input('');\n width = input('40px');\n height = input('40px');\n color = input('#414141');\n}\n","<svg [attr.width]=\"width()\" [attr.height]=\"height()\" [style.color]=\"color()\">\n <use [attr.xlink:href]=\"'./svg-sprite.svg#' + id()\"></use>\n</svg>\n","import { Component, inject, input } from '@angular/core';\nimport {\n MatChipEditedEvent,\n MatChipGrid,\n MatChipInput,\n MatChipInputEvent,\n MatChipRow,\n} from '@angular/material/chips';\nimport { MatFormField } from '@angular/material/form-field';\nimport { MatIcon } from '@angular/material/icon';\nimport { MatLabel } from '@angular/material/form-field';\nimport { MatHint } from '@angular/material/form-field';\nimport { LiveAnnouncer } from '@angular/cdk/a11y';\nimport { COMMA, ENTER } from '@angular/cdk/keycodes';\n\n@Component({\n selector: 'acp-mat-input-chip',\n imports: [MatFormField, MatLabel, MatChipRow, MatIcon, MatHint, MatChipGrid, MatChipInput],\n templateUrl: './mat-input-chip.component.html',\n styleUrl: './mat-input-chip.component.css',\n})\nexport class MatInputChipComponent {\n chips = input.required<string[]>();\n labelText = input.required<string>();\n placelholder = input<string>('');\n addOnBlur = true;\n readonly separatorKeysCodes = [ENTER, COMMA] as const;\n\n announcer = inject(LiveAnnouncer);\n\n add(event: MatChipInputEvent): void {\n const value = (event.value || '').trim();\n\n // Add our fruit\n if (value) {\n this.chips().push(value);\n }\n\n // Clear the input value\n event.chipInput!.clear();\n }\n\n remove(value: string): void {\n const index = this.chips().indexOf(value);\n\n if (index >= 0) {\n this.chips().splice(index, 1);\n\n this.announcer.announce(`Removed ${value}`);\n }\n }\n\n edit(inputRaw: string, event: MatChipEditedEvent) {\n const value = event.value.trim();\n\n // Remove fruit if it no longer has a name\n if (!value) {\n this.remove(inputRaw);\n return;\n }\n\n // Edit existing fruit\n const index = this.chips().indexOf(inputRaw);\n if (index >= 0) {\n this.chips()[index] = value.replace(/\\s+/g, '');\n }\n }\n}\n","<mat-form-field class=\"w-100\" appearance=\"outline\">\n <mat-label>{{ labelText() }}</mat-label>\n <mat-chip-grid #chipGrid aria-label=\"Enter fruits\">\n @for (chip of chips(); track $index) {\n <mat-chip-row\n (removed)=\"remove(chip)\"\n [editable]=\"true\"\n (edited)=\"edit(chip, $event)\"\n [aria-description]=\"'press enter to edit ' + chip\"\n >\n {{ chip }}\n <button matChipRemove [attr.aria-label]=\"'remove ' + chip\">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n [placeholder]=\"placelholder()\"\n [matChipInputFor]=\"chipGrid\"\n [matChipInputSeparatorKeyCodes]=\"separatorKeysCodes\"\n [matChipInputAddOnBlur]=\"addOnBlur\"\n (matChipInputTokenEnd)=\"add($event)\"\n />\n </mat-chip-grid>\n <mat-hint align=\"start\"\n ><strong>Ingrese {{ labelText() }} y dale \"Enter\"</strong>\n </mat-hint>\n</mat-form-field>\n","import { Component, input, output } from '@angular/core';\nimport { ThemePalette } from '@angular/material/core';\nimport { MatButton, MatFabButton, MatIconButton, MatMiniFabButton } from '@angular/material/button';\nimport { NgClass } from '@angular/common';\nimport { MatIcon } from '@angular/material/icon';\n\nexport type ButtonVariant =\n | 'primary'\n | 'secondary'\n | 'success'\n | 'danger'\n | 'warning'\n | 'info'\n | 'light'\n | 'dark';\n\nexport type ButtonType = 'button' | 'submit' | 'reset';\n\nexport type MaterialButtonStyle =\n | 'basic' // mat-button\n | 'raised' // mat-raised-button\n | 'flat' // mat-flat-button\n | 'stroked' // mat-stroked-button\n | 'icon' // mat-icon-button\n | 'fab' // mat-fab\n | 'mini-fab'; // mat-mini-fab\n\n@Component({\n selector: 'acp-mat-theme-button',\n imports: [MatButton, NgClass, MatIcon, MatMiniFabButton, MatIconButton, MatFabButton],\n templateUrl: './mat-theme-button.component.html',\n styleUrl: './mat-theme-button.component.css',\n})\nexport class MatThemeButtonComponent {\n variant = input<ButtonVariant>('primary');\n text = input<string>('');\n icon = input<string>('');\n outlined = input<boolean>(false);\n disabled = input<boolean>(false);\n useThemeColor = input<boolean>(false); // Use theme color based on variant\n type = input<ButtonType>('button');\n matStyle = input<MaterialButtonStyle>('raised');\n\n // Additional common button properties\n title = input<string>(''); // Tooltip text shown on hover\n ariaLabel = input<string>(''); // Accessibility label\n name = input<string>(''); // Name attribute for form submission\n id = input<string>(''); // ID for element reference\n form = input<string>(''); // Associated form ID\n tabIndex = input<number>(0); // Tab order\n testId = input<string>(''); // For testing purposes\n\n handleClick = output<unknown>();\n\n getButtonClasses(): Record<string, boolean> {\n return {\n [`btn-${this.variant()}`]: true,\n 'btn-outlined': this.outlined() && this.matStyle() !== 'stroked', // Stroked buttons are already outlined\n };\n }\n\n getThemeColor(): ThemePalette | null {\n if (!this.useThemeColor) return null;\n\n switch (this.variant()) {\n case 'primary':\n return 'primary';\n case 'secondary':\n return 'accent';\n case 'danger':\n return 'warn';\n default:\n return null;\n }\n }\n}\n","@if (matStyle() === 'basic') {\n <button\n mat-button\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n @if (icon()) {\n <mat-icon class=\"button-icon\">{{ icon() }}</mat-icon>\n }\n @if (text()) {\n <span class=\"button-text\">{{ text() }}</span>\n }\n <ng-content></ng-content>\n </button>\n} @else if (matStyle() === 'raised') {\n <button\n mat-raised-button\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n <ng-content select=\"svgIcon\"></ng-content>\n @if (icon()) {\n <mat-icon class=\"button-icon\">{{ icon() }}</mat-icon>\n }\n @if (text()) {\n <span class=\"button-text\">{{ text() }}</span>\n }\n <ng-content></ng-content>\n </button>\n} @else if (matStyle() === 'flat') {\n <button\n mat-flat-button\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n @if (icon()) {\n <mat-icon class=\"button-icon\">{{ icon() }}</mat-icon>\n }\n @if (text()) {\n <span class=\"button-text\">{{ text() }}</span>\n }\n <ng-content></ng-content>\n </button>\n} @else if (matStyle() === 'stroked') {\n <button\n mat-stroked-button\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n @if (icon()) {\n <mat-icon class=\"button-icon\">{{ icon() }}</mat-icon>\n }\n @if (text()) {\n <span class=\"button-text\">{{ text() }}</span>\n }\n <ng-content></ng-content>\n </button>\n} @else if (matStyle() === 'icon') {\n <button\n mat-icon-button\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n @if (icon()) {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n <ng-content></ng-content>\n </button>\n} @else if (matStyle() === 'fab') {\n <button\n mat-fab\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n @if (icon()) {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n @if (text()) {\n <span class=\"button-text\">{{ text() }}</span>\n }\n <ng-content></ng-content>\n </button>\n} @else if (matStyle() === 'mini-fab') {\n <button\n mat-mini-fab\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n @if (icon()) {\n <mat-icon>{{ icon() }}</mat-icon>\n }\n <ng-content></ng-content>\n </button>\n} @else {\n <button\n mat-raised-button\n [ngClass]=\"getButtonClasses()\"\n [disabled]=\"disabled()\"\n [color]=\"getThemeColor()\"\n [type]=\"type()\"\n [title]=\"title()\"\n [attr.aria-label]=\"ariaLabel() || title()\"\n [attr.name]=\"name()\"\n [attr.id]=\"id()\"\n [attr.form]=\"form()\"\n [attr.tabindex]=\"tabIndex()\"\n [attr.data-testid]=\"testId()\"\n (click)=\"handleClick.emit($event)\"\n >\n <ng-content select=\"svgIcon\"></ng-content>\n\n @if (icon()) {\n <mat-icon class=\"button-icon\">{{ icon() }}</mat-icon>\n }\n @if (text()) {\n <span class=\"button-text\">{{ text() }}</span>\n }\n <ng-content></ng-content>\n </button>\n}\n","import { Component } from '@angular/core';\nimport { MatProgressSpinner } from '@angular/material/progress-spinner';\n\n@Component({\n selector: 'acp-spinner',\n imports: [MatProgressSpinner],\n templateUrl: './spinner.component.html',\n styleUrl: './spinner.component.css',\n})\nexport class SpinnerComponent {}\n","<mat-spinner></mat-spinner>\n","import { InjectionToken } from '@angular/core';\nimport { MatSnackBarConfig } from '@angular/material/snack-bar';\n\nexport interface SnackbarConfig extends MatSnackBarConfig {\n readonly defaultAction?: string;\n readonly iconEnabled?: boolean;\n readonly titleEnabled?: boolean;\n}\n\nexport const DEFAULT_SNACKBAR_CONFIG: SnackbarConfig = {\n duration: 5000,\n horizontalPosition: 'center',\n verticalPosition: 'bottom',\n panelClass: [],\n defaultAction: 'Close',\n iconEnabled: true,\n titleEnabled: true,\n};\n\nexport const SNACKBAR_CONFIG = new InjectionToken<SnackbarConfig>('acontplus-snackbar-config', {\n providedIn: 'root',\n factory: () => DEFAULT_SNACKBAR_CONFIG,\n});\n","import { inject, Injectable } from '@angular/core';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { SNACKBAR_CONFIG } from './snackbar.config';\nimport { SNACKBAR_DURATIONS } from '../../constants/snackbar.constants';\nimport { NotificationCallProps, SnackbarProps } from '../../models/snackbar.model';\n@Injectable({\n providedIn: 'root',\n})\nexport class SnackbarService {\n private readonly snackBar = inject(MatSnackBar);\n private readonly config = inject(SNACKBAR_CONFIG);\n\n /**\n * Display a snackbar with specific type and configuration\n */\n show(props: SnackbarProps): void {\n const {\n type,\n message,\n title,\n action = this.config.defaultAction,\n config: userConfig = {},\n } = props;\n\n const typeClass = `acontplus-snackbar-${type}`;\n const panelClasses = this.buildPanelClasses(typeClass, userConfig.panelClass);\n\n const finalConfig = {\n ...this.config,\n ...userConfig,\n panelClass: panelClasses,\n };\n\n const displayMessage = this.buildMessage(message, title);\n this.snackBar.open(displayMessage, action, finalConfig);\n }\n\n /**\n * Quick success notification\n */\n success(props: NotificationCallProps): void {\n this.show({\n type: 'success',\n ...props,\n config: {\n duration: SNACKBAR_DURATIONS.MEDIUM,\n ...props.config,\n },\n });\n }\n\n /**\n * Quick error notification with longer duration\n */\n error(props: NotificationCallProps): void {\n this.show({\n type: 'error',\n ...props,\n config: {\n duration: SNACKBAR_DURATIONS.LONG,\n ...props.config,\n },\n });\n }\n\n /**\n * Quick warning notification\n */\n warning(props: NotificationCallProps): void {\n this.show({\n type: 'warning',\n ...props,\n config: {\n duration: SNACKBAR_DURATIONS.MEDIUM,\n ...props.config,\n },\n });\n }\n\n /**\n * Quick info notification\n */\n info(props: NotificationCallProps): void {\n this.show({\n type: 'info',\n ...props,\n config: {\n duration: SNACKBAR_DURATIONS.MEDIUM,\n ...props.config,\n },\n });\n }\n\n private buildPanelClasses(typeClass: string, userClasses?: string | string[]): string[] {\n const classes = ['acontplus-snackbar', typeClass];\n\n if (userClasses) {\n const normalizedClasses = Array.isArray(userClasses) ? userClasses : [userClasses];\n classes.push(...normalizedClasses);\n }\n\n return classes;\n }\n\n private buildMessage(message: string, title?: string): string {\n if (!this.config.titleEnabled || !title) {\n return message;\n }\n return `${title}: ${message}`;\n }\n}\n","import { Component, inject, signal } from '@angular/core';\nimport { SnackbarService } from '../../../services/snackbar/snackbar.service';\nimport { NotificationDemo } from '../../../models/snackbar.model';\nimport { SnackbarType } from '../../../types';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIcon } from '@angular/material/icon';\nimport { MatButton } from '@angular/material/button';\nimport { MatFormField, MatInput, MatLabel } from '@angular/material/input';\nimport { FormsModule } from '@angular/forms';\nimport { MatOption } from '@angular/material/core';\nimport { MatSelect } from '@angular/material/select';\nimport { SNACKBAR_MESSAGES } from '../../../constants/snackbar.constants';\n\n@Component({\n selector: 'acp-snackbar-notification',\n imports: [\n MatCardModule,\n MatButton,\n MatFormField,\n MatInput,\n MatLabel,\n FormsModule,\n MatOption,\n MatSelect,\n MatIcon,\n ],\n templateUrl: './snackbar-notification.component.html',\n styleUrl: './snackbar-notification.component.css',\n})\nexport class SnackbarNotificationComponent {\n private readonly snackbarService = inject(SnackbarService);\n\n protected readonly customNotification = signal<NotificationDemo>({\n type: 'info',\n message: '',\n title: '',\n duration: 5000,\n });\n\n protected readonly quickMessages = {\n success: SNACKBAR_MESSAGES.SUCCESS.SAVE,\n info: SNACKBAR_MESSAGES.INFO.LOADING,\n warning: SNACKBAR_MESSAGES.WARNING.UNSAVED_CHANGES,\n error: SNACKBAR_MESSAGES.ERROR.NETWORK,\n } as const;\n\n protected showQuickNotification(type: SnackbarType): void {\n this.snackbarService[type]({\n message: this.quickMessages[type],\n title: type.charAt(0).toUpperCase() + type.slice(1),\n });\n }\n\n protected showCustomNotification(): void {\n if (!this.isFormValid()) return;\n\n const notification = this.customNotification();\n this.snackbarService.show({\n type: notification.type,\n message: notification.message,\n title: notification.title || undefined,\n config: { duration: notification.duration || 5000 },\n });\n }\n\n protected isFormValid(): boolean {\n return !!this.customNotification().message.trim();\n }\n\n protected resetForm(): void {\n this.customNotification.set({\n type: 'info',\n message: '',\n title: '',\n duration: 5000,\n });\n }\n}\n","<div class=\"snackbar-demo-container\">\n <!-- Quick Action Buttons -->\n <mat-card class=\"demo-card\">\n <mat-card-header>\n <mat-card-title>\n <mat-icon>notifications</mat-icon>\n Quick Notifications\n </mat-card-title>\n <mat-card-subtitle> Test common notification scenarios </mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <div class=\"quick-buttons\">\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"showQuickNotification('success')\"\n class=\"notification-btn success-btn\"\n >\n <mat-icon>check_circle</mat-icon>\n Success\n </button>\n\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"showQuickNotification('info')\"\n class=\"notification-btn info-btn\"\n >\n <mat-icon>info</mat-icon>\n Info\n </button>\n\n <button\n mat-raised-button\n color=\"warn\"\n (click)=\"showQuickNotification('warning')\"\n class=\"notification-btn warning-btn\"\n >\n <mat-icon>warning</mat-icon>\n Warning\n </button>\n\n <button\n mat-raised-button\n color=\"warn\"\n (click)=\"showQuickNotification('error')\"\n class=\"notification-btn error-btn\"\n >\n <mat-icon>error</mat-icon>\n Error\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n\n <!-- Custom Notification Builder -->\n <mat-card class=\"demo-card\">\n <mat-card-header>\n <mat-card-title>\n <mat-icon>build</mat-icon>\n Custom Notification Builder\n </mat-card-title>\n <mat-card-subtitle> Create and test custom notifications </mat-card-subtitle>\n </mat-card-header>\n\n <mat-card-content>\n <form class=\"notification-form\" (ngSubmit)=\"showCustomNotification()\">\n <div class=\"form-row\">\n <mat-form-field appearance=\"outline\" class=\"form-field\">\n <mat-label>Type</mat-label>\n <mat-select [(ngModel)]=\"customNotification().type\" name=\"type\" required>\n <mat-option value=\"success\">Success</mat-option>\n <mat-option value=\"info\">Info</mat-option>\n <mat-option value=\"warning\">Warning</mat-option>\n <mat-option value=\"error\">Error</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"form-field\">\n <mat-label>Duration (ms)</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"customNotification().duration\"\n name=\"duration\"\n placeholder=\"5000\"\n min=\"1000\"\n max=\"30000\"\n />\n </mat-form-field>\n </div>\n\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Title (optional)</mat-label>\n <input\n matInput\n [(ngModel)]=\"customNotification().title\"\n name=\"title\"\n placeholder=\"Notification title\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Message</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"customNotification().message\"\n name=\"message\"\n placeholder=\"Your notification message...\"\n rows=\"3\"\n required\n ></textarea>\n </mat-form-field>\n\n <div class=\"form-actions\">\n <button\n mat-raised-button\n color=\"primary\"\n type=\"submit\"\n [disabled]=\"!isFormValid()\"\n class=\"send-btn\"\n >\n <mat-icon>send</mat-icon>\n Send Notification\n </button>\n\n <button mat-button type=\"button\" (click)=\"resetForm()\" class=\"reset-btn\">\n <mat-icon>refresh</mat-icon>\n Reset\n </button>\n </div>\n </form>\n </mat-card-content>\n </mat-card>\n\n <!-- Usage Examples -->\n <!-- <mat-card class=\"demo-card\">-->\n <!-- <mat-card-header>-->\n <!-- <mat-card-title>-->\n <!-- <mat-icon>code</mat-icon>-->\n <!-- Usage Examples-->\n <!-- </mat-card-title>-->\n <!-- </mat-card-header>-->\n\n <!-- <mat-card-content>-->\n <!-- <div class=\"code-examples\">-->\n <!-- <h4>Basic Usage:</h4>-->\n <!-- <pre><code>{{ basicUsageExample() }}</code></pre>-->\n\n <!-- <h4>With Configuration:</h4>-->\n <!-- <pre><code>{{ advancedUsageExample() }}</code></pre>-->\n <!-- </div>-->\n <!-- </mat-card-content>-->\n <!-- </mat-card>-->\n</div>\n","import { Component } from '@angular/core';\n\n@Component({\n selector: 'lib-custom-tabulator',\n imports: [],\n templateUrl: './custom-tabulator.component.html',\n styleUrl: './custom-tabulator.component.css',\n})\nexport class CustomTabulatorComponent {}\n","<p>custom-tabulator works!</p>\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'getTotal',\n standalone: true,\n})\nexport class GetTotalPipe implements PipeTransform {\n transform(colName: string, dataSource: any[]): any {\n return this.getTotal(colName, dataSource) || '';\n }\n\n /**\n * Calculate and return the total (sum) of all the column --> the column must be number\n */\n getTotal(colName: string, dataSource: any[]): number {\n const total = dataSource\n .map(row => row[colName])\n .reduce((acc, value) => (value ? acc + Number(value) : acc), 0);\n return total?.toFixed(2);\n }\n}\n","import { inject, Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { TranslocoService } from '@jsverse/transloco';\n\ntype StatusGender = 'male' | 'female' | 'neutral';\n\ninterface StatusOptions {\n gender?: StatusGender;\n showIcon?: boolean;\n customActiveText?: string;\n customInactiveText?: string;\n textClass?: string;\n iconClass?: string;\n}\n\ntype TranslationKey =\n | 'status.active'\n | 'status.inactive'\n | 'status.active.male'\n | 'status.active.female'\n | 'status.inactive.male'\n | 'status.inactive.female';\n\n@Pipe({\n name: 'statusDisplay',\n})\nexport class StatusDisplayPipe implements PipeTransform {\n private sanitizer = inject(DomSanitizer);\n private transloco = inject(TranslocoService, { optional: true });\n\n transform(isActive: boolean, options: StatusOptions = {}): SafeHtml {\n const {\n gender = 'neutral',\n showIcon = true,\n customActiveText,\n customInactiveText,\n textClass = '',\n iconClass = '',\n } = options;\n\n const text = this.getStatusText(isActive, gender, customActiveText, customInactiveText);\n const icon = isActive ? 'check_circle' : 'cancel';\n const colorClass = isActive ? 'text-green-500' : 'text-red-500';\n const combinedIconClass = `${colorClass} align-middle mr-1 ${iconClass}`.trim();\n const combinedTextClass = `align-middle ${textClass}`.trim();\n\n let html = '';\n\n if (showIcon) {\n html += `<mat-icon class=\"${combinedIconClass}\" fontIcon=\"${icon}\"></mat-icon>`;\n }\n\n html += `<span class=\"${combinedTextClass}\">${text}</span>`;\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n\n private getStatusText(\n isActive: boolean,\n gender: StatusGender,\n customActive?: string,\n customInactive?: string,\n ): string {\n if (customActive && isActive) return customActive;\n if (customInactive && !isActive) return customInactive;\n if (!this.transloco) return this.getFallbackText(isActive, gender);\n\n const translationKey = this.getTranslationKey(isActive, gender);\n const translation = this.transloco.translate(translationKey);\n\n return translation !== translationKey ? translation : this.getFallbackText(isActive, gender);\n }\n\n private getTranslationKey(isActive: boolean, gender: StatusGender): TranslationKey {\n const base = isActive ? 'status.active' : 'status.inactive';\n return gender !== 'neutral'\n ? (`${base}.${gender}` as TranslationKey)\n : (base as TranslationKey);\n }\n\n private getFallbackText(isActive: boolean, gender: StatusGender): string {\n const lang = this.transloco?.getActiveLang() || 'en';\n const isSpanish = lang.startsWith('es');\n\n if (!isSpanish) {\n return isActive ? 'Active' : 'Inactive';\n }\n\n if (isActive) {\n return gender === 'female' ? 'Activa' : 'Activo';\n } else {\n return gender === 'female' ? 'Inactiva' : 'Inactivo';\n }\n }\n}\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ContentChild,\n ContentChildren,\n EventEmitter,\n inject,\n Injector,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewContainerRef,\n ComponentRef,\n EmbeddedViewRef,\n} from '@angular/core';\nimport {\n MatColumnDef,\n MatFooterRowDef,\n MatHeaderRowDef,\n MatNoDataRow,\n MatRowDef,\n MatTable,\n MatTableDataSource,\n MatTableModule,\n} from '@angular/material/table';\nimport { SelectionModel } from '@angular/cdk/collections';\nimport { MatPaginatorModule, PageEvent } from '@angular/material/paginator';\nimport { animate, state, style, transition, trigger } from '@angular/animations';\nimport { DatePipe, DecimalPipe, NgClass, NgTemplateOutlet } from '@angular/common';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatSortModule } from '@angular/material/sort';\nimport { GetTotalPipe } from '../../../pipes';\nimport { ColumnDefinition, Pagination, TableContext, TableRow } from '../../../models';\n\n@Component({\n selector: 'acp-mat-dynamic-table',\n standalone: true,\n imports: [\n MatTableModule,\n MatCheckboxModule,\n MatSortModule,\n MatIconModule,\n MatButtonModule,\n MatPaginatorModule,\n NgClass,\n GetTotalPipe,\n DatePipe,\n DecimalPipe,\n NgTemplateOutlet,\n ],\n templateUrl: './mat-dynamic-table.component.html',\n styleUrl: './mat-dynamic-table.component.css',\n animations: [\n trigger('detailExpand', [\n state('collapsed,void', style({ height: '0px', minHeight: '0' })),\n state('expanded', style({ height: '*' })),\n transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),\n ]),\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class MatDynamicTableComponent<T extends TableRow>\n implements AfterContentInit, OnChanges, OnInit, OnDestroy\n{\n private componentRefs: ComponentRef<any>[] = [];\n private embeddedViews: EmbeddedViewRef<any>[] = [];\n private cdr = inject(ChangeDetectorRef);\n\n @Input() showExpand = false;\n @Input() showFooter = false;\n @Input() locale = 'en-US';\n @Input() highlightRowIndex = 0;\n @Input() visibleColumns: string[] = [];\n @Input() columnDefinitions: ColumnDefinition<T>[] = [];\n @Input() showSelectBox = false;\n @Input() tableData: T[] = [];\n @Input() rowTemplate: TemplateRef<TableContext<T>> | null = null;\n @Input() expandedDetail: TemplateRef<TableContext<T>> | null = null;\n @Input() enablePagination = false;\n @Input() paginationConfig: Pagination | null = null;\n @Input() isLoadingData = false;\n\n @Output() rowSelected = new EventEmitter<T[]>();\n @Output() copyRow = new EventEmitter<T>();\n @Output() showExpanded = new EventEmitter<T>();\n @Output() hideExpanded = new EventEmitter<T>();\n @Output() pageEvent = new EventEmitter<PageEvent>();\n\n isNormalRow = (_: number, row: T) => this.expandedElement !== row;\n isExpandedRow = (_: number, row: T) => this.expandedElement === row;\n\n dataSource = new MatTableDataSource<T>([]);\n selection = new SelectionModel<T>(true, []);\n expandedElement: T | null = null;\n columnsToDisplayWithExpand: string[] = [];\n\n @ContentChildren(MatHeaderRowDef) headerRowDefs!: QueryList<MatHeaderRowDef>;\n @ContentChildren(MatRowDef) rowDefs!: QueryList<MatRowDef<T>>;\n @ContentChildren(MatFooterRowDef) footerRowDefs!: QueryList<MatFooterRowDef>;\n @ContentChildren(MatColumnDef) columnDefs!: QueryList<MatColumnDef>;\n @ContentChild(MatNoDataRow) noDataRow!: MatNoDataRow;\n\n @ViewChild(MatTable, { static: true }) table!: MatTable<T>;\n @ContentChildren(ViewContainerRef) rows!: QueryList<ViewContainerRef>;\n\n ngOnInit(): void {\n this.updateColumnsToDisplay();\n this.initializeSelection();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['tableData']) {\n this.updateTableData();\n }\n\n if (changes['showExpand'] || changes['visibleColumns'] || changes['columnDefinitions']) {\n this.updateColumnsToDisplay();\n }\n\n // Trigger change detection for OnPush strategy\n this.cdr.markForCheck();\n }\n\n ngAfterContentInit(): void {\n this.registerTableContent();\n this.initializeTable();\n this.cdr.markForCheck();\n }\n\n ngOnDestroy(): void {\n this.cleanupDynamicComponents();\n }\n\n private updateTableData(): void {\n // Clear selection when new data arrives\n this.selection.clear();\n\n // Update data source\n this.dataSource.data = this.tableData || [];\n\n // Reset expanded ele