@praxisui/table 1.0.0-beta.10 → 1.0.0-beta.12

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.
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @praxisui/table
2
2
 
3
+ ## 🔰 Exemplos / Quickstart
4
+
5
+ Para ver esta biblioteca em funcionamento em uma aplicação completa, utilize o projeto de exemplo (Quickstart):
6
+
7
+ - Repositório: https://github.com/codexrodrigues/praxis-ui-quickstart
8
+ - O Quickstart demonstra a integração das bibliotecas `@praxisui/*` em um app Angular, incluindo instalação, configuração e uso em telas reais.
9
+
3
10
  > Componente de tabela empresarial avançado com arquitetura unificada
4
11
 
5
12
  ## 🌟 Visão Geral
@@ -569,8 +576,11 @@ sequenceDiagram
569
576
  - `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
570
577
  - `snoozeMs: number = 86400000` (24h)
571
578
  - `autoOpenSettingsOnOutdated: boolean = false`
572
- - Output:
579
+ - Outputs:
573
580
  - `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; resourcePath?: string }`
581
+ - Emitido tanto na verificação leve (304/200) quanto no bootstrap do schema (primeira carga via `loadSchema()`).
582
+ - `metadataChange: { meta: any; reason: 'bootstrap'|'verification'|'applied' }`
583
+ - Emitido quando `config.meta` é atualizado (ex.: após bootstrap ou verificação ETag). Útil para sincronizar sidebars/bridges de metadados.
574
584
 
575
585
  ### Fallback Global (opcional)
576
586
 
@@ -21622,6 +21622,8 @@ class PraxisTable {
21622
21622
  rowDoubleClick = new EventEmitter();
21623
21623
  /** Emits whenever schema outdated state changes (can be used by host) */
21624
21624
  schemaStatusChange = new EventEmitter();
21625
+ /** Emits when metadata (config.meta) changes for hosts interested in schema/config changes */
21626
+ metadataChange = new EventEmitter();
21625
21627
  beforeDelete = new EventEmitter();
21626
21628
  afterDelete = new EventEmitter();
21627
21629
  deleteError = new EventEmitter();
@@ -21645,6 +21647,36 @@ class PraxisTable {
21645
21647
  measuredInline = 0;
21646
21648
  resizeObserver;
21647
21649
  getActionId = getActionId;
21650
+ // Centralized schema status state for internal use/testing; hosts should use outputs
21651
+ schemaStatusSubject = new BehaviorSubject(null);
21652
+ emitSchemaStatus(status, reason) {
21653
+ try {
21654
+ this.schemaStatusSubject.next(status);
21655
+ }
21656
+ catch { }
21657
+ try {
21658
+ this.schemaStatusChange.emit(status);
21659
+ }
21660
+ catch { }
21661
+ if (this.isDebug()) {
21662
+ try {
21663
+ console.debug('[PraxisTable] schema:status', { ...status, reason });
21664
+ }
21665
+ catch { }
21666
+ }
21667
+ }
21668
+ emitMetadataChange(reason) {
21669
+ try {
21670
+ this.metadataChange.emit({ meta: this.config?.meta ?? null, reason });
21671
+ }
21672
+ catch { }
21673
+ if (this.isDebug()) {
21674
+ try {
21675
+ console.debug('[PraxisTable] schema:metadata', { reason, meta: this.config?.meta });
21676
+ }
21677
+ catch { }
21678
+ }
21679
+ }
21648
21680
  // Row menu icon helpers (customizable via config.actions.row.menuIcon and optional menuButtonColor)
21649
21681
  getRowMenuIcon() {
21650
21682
  try {
@@ -21861,7 +21893,7 @@ class PraxisTable {
21861
21893
  const hash = this.config?.meta?.serverHash;
21862
21894
  this.setOutdatedSnooze(hash, until);
21863
21895
  this.schemaOutdated = false;
21864
- this.schemaStatusChange.emit({ outdated: false, serverHash: hash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath });
21896
+ this.emitSchemaStatus({ outdated: false, serverHash: hash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath }, 'snackbar-dismiss');
21865
21897
  this.cdr.markForCheck();
21866
21898
  });
21867
21899
  this.setOutdatedNotified(serverHash);
@@ -21870,7 +21902,7 @@ class PraxisTable {
21870
21902
  }
21871
21903
  // Inline banner handlers
21872
21904
  onReconcileRequested() {
21873
- this.schemaStatusChange.emit({ outdated: true, serverHash: this.config?.meta?.serverHash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath });
21905
+ this.emitSchemaStatus({ outdated: true, serverHash: this.config?.meta?.serverHash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath }, 'reconcile');
21874
21906
  // Optional auto open settings first; otherwise just open settings
21875
21907
  this.openTableSettings();
21876
21908
  }
@@ -21878,7 +21910,7 @@ class PraxisTable {
21878
21910
  const hash = this.config?.meta?.serverHash;
21879
21911
  this.setOutdatedIgnore(hash, true);
21880
21912
  this.schemaOutdated = false;
21881
- this.schemaStatusChange.emit({ outdated: false, serverHash: hash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath });
21913
+ this.emitSchemaStatus({ outdated: false, serverHash: hash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath }, 'inline-ignore');
21882
21914
  this.cdr.markForCheck();
21883
21915
  }
21884
21916
  onSnoozeOutdated() {
@@ -21886,7 +21918,7 @@ class PraxisTable {
21886
21918
  const until = new Date(Date.now() + Math.max(0, this.snoozeMs || 0));
21887
21919
  this.setOutdatedSnooze(hash, until);
21888
21920
  this.schemaOutdated = false; // hide inline banner for now
21889
- this.schemaStatusChange.emit({ outdated: false, serverHash: hash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath });
21921
+ this.emitSchemaStatus({ outdated: false, serverHash: hash, lastVerifiedAt: this.config?.meta?.lastVerifiedAt, resourcePath: this.resourcePath }, 'inline-snooze');
21890
21922
  this.cdr.markForCheck();
21891
21923
  }
21892
21924
  toggleRow(row) {
@@ -22015,6 +22047,26 @@ class PraxisTable {
22015
22047
  }
22016
22048
  }
22017
22049
  subscriptions = [];
22050
+ updateTableMetaFromServerInfo(info, opts) {
22051
+ try {
22052
+ const nowIso = new Date().toISOString();
22053
+ const meta = { ...(this.config?.meta || {}) };
22054
+ if (info?.schemaId)
22055
+ meta.schemaId = info.schemaId;
22056
+ if (info?.schemaHash != null)
22057
+ meta.serverHash = info.schemaHash;
22058
+ if (!meta.version)
22059
+ meta.version = '2.0.0';
22060
+ if (!meta.createdAt)
22061
+ meta.createdAt = nowIso;
22062
+ if (opts?.touchUpdatedAt !== false)
22063
+ meta.updatedAt = nowIso;
22064
+ if (opts?.updateVerifiedAt)
22065
+ meta.lastVerifiedAt = nowIso;
22066
+ this.config.meta = meta;
22067
+ }
22068
+ catch { }
22069
+ }
22018
22070
  getIdField() {
22019
22071
  // Precedence: @Input → crudContext.idField → config.meta.idField → service.getResourceIdField() → 'id'
22020
22072
  const fromInput = this.idField || this.crudContext?.idField;
@@ -22869,7 +22921,8 @@ class PraxisTable {
22869
22921
  if (this.schemaOutdated) {
22870
22922
  this.schemaOutdated = false;
22871
22923
  }
22872
- this.schemaStatusChange.emit({ outdated: false, serverHash: this.config?.meta?.serverHash, lastVerifiedAt: meta.lastVerifiedAt, resourcePath: this.resourcePath });
22924
+ this.emitMetadataChange('verification');
22925
+ this.emitSchemaStatus({ outdated: false, serverHash: this.config?.meta?.serverHash, lastVerifiedAt: meta.lastVerifiedAt, resourcePath: this.resourcePath }, 'verification-304');
22873
22926
  return;
22874
22927
  }
22875
22928
  // status === 200: server hash changed (or first verification without hash). Do not apply columns here.
@@ -22880,7 +22933,8 @@ class PraxisTable {
22880
22933
  this.configStorage.saveConfig(`table-config:${this.tableId}`, this.config);
22881
22934
  }
22882
22935
  catch { }
22883
- this.schemaStatusChange.emit({ outdated: this.schemaOutdated, serverHash: meta.serverHash, lastVerifiedAt: meta.lastVerifiedAt, resourcePath: this.resourcePath });
22936
+ this.emitMetadataChange('verification');
22937
+ this.emitSchemaStatus({ outdated: this.schemaOutdated, serverHash: meta.serverHash, lastVerifiedAt: meta.lastVerifiedAt, resourcePath: this.resourcePath }, 'verification-200');
22884
22938
  // Notifications only if edit mode
22885
22939
  if (this.schemaOutdated) {
22886
22940
  if (this._resolvedPrefs.autoOpenSettingsOnOutdated) {
@@ -22923,18 +22977,7 @@ class PraxisTable {
22923
22977
  // Atualizar metadados de schema (schemaId/serverHash) e idField no TableConfig (em memória)
22924
22978
  try {
22925
22979
  const info = this.crudService.getLastSchemaInfo();
22926
- const meta = { ...(this.config?.meta || {}) };
22927
- if (info?.schemaId)
22928
- meta.schemaId = info.schemaId;
22929
- if (info?.schemaHash)
22930
- meta.serverHash = info.schemaHash;
22931
- if (!meta.version)
22932
- meta.version = '2.0.0';
22933
- if (!meta.createdAt)
22934
- meta.createdAt = new Date().toISOString();
22935
- meta.updatedAt = new Date().toISOString();
22936
- meta.lastVerifiedAt = new Date().toISOString();
22937
- this.config.meta = meta;
22980
+ this.updateTableMetaFromServerInfo({ schemaId: info?.schemaId, schemaHash: info?.schemaHash }, { updateVerifiedAt: true, touchUpdatedAt: true });
22938
22981
  }
22939
22982
  catch { }
22940
22983
  const existing = this.config?.columns ?? [];
@@ -22944,6 +22987,20 @@ class PraxisTable {
22944
22987
  .map((f) => this.convertFieldToColumn(f));
22945
22988
  }
22946
22989
  this.setupColumns();
22990
+ // Notificar hosts sobre atualização de metadados de schema ao concluir o loadSchema
22991
+ try {
22992
+ const meta = this.config?.meta || {};
22993
+ // Ao carregar o schema com sucesso, consideramos que não há desatualização pendente
22994
+ this.schemaOutdated = false;
22995
+ this.emitMetadataChange('bootstrap');
22996
+ this.emitSchemaStatus({
22997
+ outdated: false,
22998
+ serverHash: meta.serverHash,
22999
+ lastVerifiedAt: meta.lastVerifiedAt,
23000
+ resourcePath: this.resourcePath,
23001
+ }, 'bootstrap');
23002
+ }
23003
+ catch { }
22947
23004
  this.cdr.detectChanges();
22948
23005
  },
22949
23006
  error: (err) => {
@@ -24005,7 +24062,7 @@ class PraxisTable {
24005
24062
  this.dataSubject.complete();
24006
24063
  }
24007
24064
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTable, deps: [{ token: i1$3.GenericCrudService }, { token: i0.ChangeDetectorRef }, { token: i3$2.SettingsPanelService }, { token: DataFormattingService }, { token: CONFIG_STORAGE }, { token: CONNECTION_STORAGE }, { token: TableDefaultsProvider }, { token: i2$1.MatSnackBar }, { token: FilterConfigService }, { token: i7$2.PraxisDialog }, { token: i0.ElementRef }, { token: i1$3.GlobalConfigService }], target: i0.ɵɵFactoryTarget.Component });
24008
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTable, isStandalone: true, selector: "praxis-table", inputs: { config: "config", resourcePath: "resourcePath", filterCriteria: "filterCriteria", notifyIfOutdated: "notifyIfOutdated", snoozeMs: "snoozeMs", autoOpenSettingsOnOutdated: "autoOpenSettingsOnOutdated", showToolbar: "showToolbar", toolbarV2: "toolbarV2", autoDelete: "autoDelete", editModeEnabled: "editModeEnabled", dense: "dense", tableId: "tableId", debugLayout: "debugLayout", horizontalScroll: "horizontalScroll", crudContext: "crudContext", idField: "idField" }, outputs: { rowClick: "rowClick", rowAction: "rowAction", toolbarAction: "toolbarAction", bulkAction: "bulkAction", rowDoubleClick: "rowDoubleClick", schemaStatusChange: "schemaStatusChange", beforeDelete: "beforeDelete", afterDelete: "afterDelete", deleteError: "deleteError", beforeBulkDelete: "beforeBulkDelete", afterBulkDelete: "afterBulkDelete", bulkDeleteError: "bulkDeleteError" }, host: { properties: { "class.debug-layout": "debugLayout", "class.density-compact": "config?.appearance?.density === 'compact'", "class.density-comfortable": "config?.appearance?.density === 'comfortable'", "class.density-spacious": "config?.appearance?.density === 'spacious'", "class.row-borders": "config?.appearance?.borders?.showRowBorders !== false", "class.col-borders": "!!config?.appearance?.borders?.showColumnBorders" } }, queries: [{ propertyName: "projectedFilter", first: true, predicate: PraxisFilter, descendants: true }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "actionsHeaderCell", first: true, predicate: ["actionsHeaderCell"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<praxis-empty-state-card\n *ngIf=\"!resourcePath\"\n icon=\"link\"\n [title]=\"'Conecte a tabela \u00E0 fonte de dados'\"\n [description]=\"'Informe a rota do recurso da API para carregar colunas e dados automaticamente.'\"\n [primaryAction]=\"{ label: 'Conectar \u00E0 fonte de dados', icon: 'bolt', action: openQuickConnect.bind(this) }\"\n></praxis-empty-state-card>\n\n<!-- Error State with Quick Connect CTA -->\n<div class=\"ptable-error\" *ngIf=\"resourcePath && (schemaError || dataError)\" style=\"display:flex; align-items:center; gap:12px; padding:12px; border:1px solid var(--md-sys-color-error, #b00020); border-radius:8px; margin: 8px 0;\">\n <mat-icon color=\"warn\" aria-hidden=\"true\">error</mat-icon>\n <div style=\"flex:1\">\n <div style=\"font-weight:600\">Erro</div>\n <div>{{ errorMessage || 'Ocorreu um erro ao carregar a tabela.' }}</div>\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"openQuickConnect()\">\n <mat-icon>bolt</mat-icon>\n Conectar a recurso\n </button>\n <button mat-stroked-button (click)=\"retryData()\" *ngIf=\"!schemaError\">Tentar novamente</button>\n <button mat-stroked-button (click)=\"reloadSchema()\" *ngIf=\"schemaError\">Recarregar colunas</button>\n </div>\n\n<!-- Inline banner for schema change (only in edit mode) -->\n<div *ngIf=\"shouldShowOutdatedInline()\" class=\"ptable-info-banner\" role=\"status\" aria-live=\"polite\">\n <div class=\"text\">O schema do servidor mudou. Reconciliar agora?</div>\n <div class=\"actions\">\n <button mat-stroked-button color=\"primary\" (click)=\"onReconcileRequested()\">\n <mat-icon>sync</mat-icon>\n Reconciliar\n </button>\n <button mat-button (click)=\"onSnoozeOutdated()\">Lembrar depois</button>\n <button mat-button (click)=\"onIgnoreOutdated()\">Ignorar</button>\n </div>\n </div>\n\n <ng-container *ngIf=\"resourcePath && !schemaError && !dataError && toolbarV2; else legacyHeader\">\n <div class=\"praxis-table-header\" [class.debug-layout]=\"debugLayout\" [class.edit-mode]=\"editModeEnabled\" *ngIf=\"showToolbar || editModeEnabled\">\n <praxis-table-toolbar\n *ngIf=\"showToolbar\"\n [config]=\"config\"\n [debugLayout]=\"debugLayout\"\n (toolbarAction)=\"onToolbarAction($event)\"\n (reset)=\"onResetPreferences()\"\n >\n <praxis-filter\n *ngIf=\"\n resourcePath &&\n config.behavior?.filtering?.advancedFilters?.enabled &&\n !projectedFilter\n \"\n advancedFilter\n [resourcePath]=\"resourcePath\"\n [formId]=\"tableId + '-filter'\"\n [editModeEnabled]=\"editModeEnabled\"\n [quickField]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.quickField\n \"\n [alwaysVisibleFields]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.alwaysVisibleFields\n \"\n [allowSaveTags]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.allowSaveTags\n \"\n [changeDebounceMs]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.changeDebounceMs ?? 300\n \"\n [i18n]=\"getFilterI18n()\"\n [mode]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.mode ??\n 'auto'\n \"\n [debugLayout]=\"debugLayout\"\n [showFilterSettings]=\"!editModeEnabled\"\n (submit)=\"onAdvancedFilterSubmit($event)\"\n (clear)=\"onAdvancedFilterClear()\"\n ></praxis-filter>\n <ng-content select=\"[advancedFilter]\" />\n <ng-content select=\"[toolbar]\" />\n <button end-actions mat-icon-button color=\"primary\" data-role=\"table-settings\" *ngIf=\"editModeEnabled\"\n (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n </praxis-table-toolbar>\n <!-- Render a minimal settings button when toolbar is hidden but edit mode is enabled -->\n <div class=\"ptable-header-actions\" *ngIf=\"!showToolbar && editModeEnabled\">\n <button mat-icon-button color=\"primary\" (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n <button mat-icon-button (click)=\"disconnect()\" aria-label=\"Desconectar\" matTooltip=\"Desconectar da fonte de dados\">\n <mat-icon>link_off</mat-icon>\n </button>\n </div>\n \n </div>\n</ng-container>\n<ng-template #legacyHeader>\n <ng-container *ngIf=\"resourcePath && !schemaError && !dataError\">\n <praxis-table-toolbar\n *ngIf=\"showToolbar\"\n [config]=\"config\"\n [debugLayout]=\"debugLayout\"\n (toolbarAction)=\"onToolbarAction($event)\"\n (reset)=\"onResetPreferences()\"\n >\n <praxis-filter\n *ngIf=\"\n resourcePath &&\n config.behavior?.filtering?.advancedFilters?.enabled &&\n !projectedFilter\n \"\n advancedFilter\n [resourcePath]=\"resourcePath\"\n [formId]=\"tableId + '-filter'\"\n [editModeEnabled]=\"editModeEnabled\"\n [quickField]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.quickField\n \"\n [alwaysVisibleFields]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.alwaysVisibleFields\n \"\n [allowSaveTags]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.allowSaveTags\n \"\n [changeDebounceMs]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.changeDebounceMs ?? 300\n \"\n [i18n]=\"getFilterI18n()\"\n [mode]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.mode ??\n 'auto'\n \"\n [debugLayout]=\"debugLayout\"\n [showFilterSettings]=\"!editModeEnabled\"\n (submit)=\"onAdvancedFilterSubmit($event)\"\n (clear)=\"onAdvancedFilterClear()\"\n ></praxis-filter>\n <ng-content select=\"[advancedFilter]\" />\n <ng-content select=\"[toolbar]\" />\n <button end-actions mat-icon-button color=\"primary\" *ngIf=\"editModeEnabled\"\n (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n </praxis-table-toolbar>\n <!-- Legacy header: settings button when toolbar hidden -->\n <div class=\"ptable-header-actions\" *ngIf=\"!showToolbar && editModeEnabled\">\n <button mat-icon-button color=\"primary\" data-role=\"table-settings\" (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n </div>\n </ng-container>\n \n</ng-template>\n<div class=\"px-scroll-viewport\"\n [class.scroll-auto]=\"horizontalScroll === 'auto'\"\n [class.scroll-wrap]=\"horizontalScroll === 'wrap'\"\n [class.scroll-none]=\"horizontalScroll === 'none'\">\n\n<table\n *ngIf=\"resourcePath && !schemaError && !dataError\"\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"onSortChange($event)\"\n [matSortDisabled]=\"!getSortingEnabled()\"\n class=\"mat-elevation-z8\"\n>\n <ng-container\n *ngIf=\"config.behavior?.selection?.enabled\"\n matColumnDef=\"_select\"\n >\n <th mat-header-cell *matHeaderCellDef>\n <mat-checkbox\n (change)=\"masterToggle()\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\"\n ></mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(row)\"\n [checked]=\"selection.isSelected(row)\"\n ></mat-checkbox>\n </td>\n </ng-container>\n <ng-container\n *ngFor=\"let column of visibleColumns\"\n [matColumnDef]=\"column.field\"\n [sticky]=\"column.sticky === true || column.sticky === 'start'\"\n [stickyEnd]=\"column.sticky === 'end'\"\n >\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n [disabled]=\"!getSortingEnabled() || column.sortable === false\"\n [style.text-align]=\"column.align\"\n [style.width]=\"column.width\"\n [attr.style]=\"column.headerStyle\"\n >\n {{ column.header }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n [style.text-align]=\"column.align\"\n [style.width]=\"column.width\"\n [attr.style]=\"column.style\"\n [ngClass]=\"getCellClasses(element, column)\"\n [ngStyle]=\"getCellNgStyle(element, column)\"\n >\n <ng-container [ngSwitch]=\"getEffectiveRendererType(element, column)\">\n <!-- Icon renderer -->\n <ng-container *ngSwitchCase=\"'icon'\">\n <mat-icon\n [color]=\"getIconColor(element, column) || null\"\n [ngStyle]=\"getIconStyle(element, column)\"\n [attr.aria-label]=\"getIconAriaLabel(element, column) || null\"\n >{{ getIconName(element, column) }}</mat-icon\n >\n </ng-container>\n\n <!-- Image renderer -->\n <ng-container *ngSwitchCase=\"'image'\">\n <img\n class=\"pfx-cell-image\"\n [src]=\"getImageSrc(element, column)\"\n [attr.alt]=\"getImageAlt(element, column) || ''\"\n [attr.loading]=\"getImageLazy(element, column) ? 'lazy' : null\"\n [style.width.px]=\"getImageWidth(element, column)\"\n [style.height.px]=\"getImageHeight(element, column)\"\n [class.shape-rounded]=\"getImageShape(element, column) === 'rounded'\"\n [class.shape-circle]=\"getImageShape(element, column) === 'circle'\"\n [style.object-fit]=\"getImageFit(element, column)\"\n />\n </ng-container>\n\n <!-- Badge renderer -->\n <ng-container *ngSwitchCase=\"'badge'\">\n <span class=\"pfx-badge\" [ngClass]=\"getBadgeClasses(element, column)\">\n <mat-icon *ngIf=\"getBadgeIcon(element, column) as bi\" class=\"pfx-badge-icon\">{{ bi }}</mat-icon>\n <span class=\"pfx-badge-text\">{{ getBadgeText(element, column) }}</span>\n </span>\n </ng-container>\n\n <!-- Link renderer -->\n <ng-container *ngSwitchCase=\"'link'\">\n <a\n class=\"pfx-link\"\n [attr.href]=\"getLinkHref(element, column) || null\"\n [attr.target]=\"getLinkTarget(element, column) || null\"\n [attr.rel]=\"getLinkRel(element, column) || null\"\n (click)=\"$event.stopPropagation()\"\n >{{ getLinkText(element, column) }}</a\n >\n </ng-container>\n\n <!-- Button renderer -->\n <ng-container *ngSwitchCase=\"'button'\">\n <ng-container [ngSwitch]=\"getButtonVariant(element, column)\">\n <button\n *ngSwitchCase=\"'outlined'\"\n mat-stroked-button\n [color]=\"getButtonColor(element, column) || null\"\n [disabled]=\"isButtonDisabled(element, column)\"\n [attr.aria-label]=\"getButtonAriaLabel(element, column) || getButtonLabel(element, column)\"\n (click)=\"onButtonClick(element, column, $event)\"\n >\n <mat-icon *ngIf=\"getButtonIcon(element, column) as bi\">{{ bi }}</mat-icon>\n {{ getButtonLabel(element, column) }}\n </button>\n <button\n *ngSwitchCase=\"'text'\"\n mat-button\n [color]=\"getButtonColor(element, column) || null\"\n [disabled]=\"isButtonDisabled(element, column)\"\n [attr.aria-label]=\"getButtonAriaLabel(element, column) || getButtonLabel(element, column)\"\n (click)=\"onButtonClick(element, column, $event)\"\n >\n <mat-icon *ngIf=\"getButtonIcon(element, column) as bi\">{{ bi }}</mat-icon>\n {{ getButtonLabel(element, column) }}\n </button>\n <button\n *ngSwitchDefault\n mat-flat-button\n [color]=\"getButtonColor(element, column) || null\"\n [disabled]=\"isButtonDisabled(element, column)\"\n [attr.aria-label]=\"getButtonAriaLabel(element, column) || getButtonLabel(element, column)\"\n (click)=\"onButtonClick(element, column, $event)\"\n >\n <mat-icon *ngIf=\"getButtonIcon(element, column) as bi\">{{ bi }}</mat-icon>\n {{ getButtonLabel(element, column) }}\n </button>\n </ng-container>\n </ng-container>\n\n <!-- Chip renderer -->\n <ng-container *ngSwitchCase=\"'chip'\">\n <span class=\"pfx-chip\" [ngClass]=\"getChipClasses(element, column)\">\n <mat-icon *ngIf=\"getChipIcon(element, column) as ci\" class=\"pfx-chip-icon\">{{ ci }}</mat-icon>\n <span class=\"pfx-chip-text\">{{ getChipText(element, column) }}</span>\n </span>\n </ng-container>\n\n <!-- Progress renderer -->\n <ng-container *ngSwitchCase=\"'progress'\">\n <div class=\"pfx-progress\">\n <div class=\"pfx-progress-bar\" [style.width.%]=\"getProgressValue(element, column)\" [style.background]=\"getProgressColor(element, column) || null\"></div>\n <div class=\"pfx-progress-label\" *ngIf=\"getProgressShowLabel(element, column)\">{{ getProgressValue(element, column) }}%</div>\n </div>\n </ng-container>\n\n <!-- Avatar renderer -->\n <ng-container *ngSwitchCase=\"'avatar'\">\n <ng-container *ngIf=\"getAvatarSrc(element, column) as asrc; else initials\">\n <img class=\"pfx-avatar\" [src]=\"asrc\" [attr.alt]=\"getAvatarAlt(element, column) || ''\" [ngStyle]=\"getAvatarStyle(element, column)\" [class.shape-rounded]=\"getAvatarShape(element, column) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, column) === 'circle'\" loading=\"lazy\" />\n </ng-container>\n <ng-template #initials>\n <span class=\"pfx-avatar pfx-avatar--initials\" [ngStyle]=\"getAvatarStyle(element, column)\" [class.shape-rounded]=\"getAvatarShape(element, column) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, column) === 'circle'\">{{ getAvatarInitials(element, column) }}</span>\n </ng-template>\n </ng-container>\n\n <!-- Toggle renderer -->\n <ng-container *ngSwitchCase=\"'toggle'\">\n <mat-slide-toggle\n [checked]=\"getToggleState(element, column)\"\n [disabled]=\"isToggleDisabled(element, column)\"\n [attr.aria-label]=\"getToggleAriaLabel(element, column) || 'Alternar'\"\n (change)=\"onToggleChange(element, column, $event)\"\n (click)=\"$event.stopPropagation()\"\n ></mat-slide-toggle>\n </ng-container>\n\n <!-- Menu renderer -->\n <ng-container *ngSwitchCase=\"'menu'\">\n <button mat-icon-button [matMenuTriggerFor]=\"menuRef\" (click)=\"$event.stopPropagation()\" [attr.aria-label]=\"getMenuAriaLabel(element, column) || 'Menu'\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #menuRef=\"matMenu\">\n <button mat-menu-item *ngFor=\"let it of getMenuItems(element, column)\" (click)=\"onMenuItemClick(it.id, element, $event)\" [disabled]=\"!it.__visible\" >\n <mat-icon *ngIf=\"it.icon\">{{ it.icon }}</mat-icon>\n <span>{{ it.label }}</span>\n </button>\n </mat-menu>\n </ng-container>\n\n <!-- HTML renderer (sanitizado) -->\n <ng-container *ngSwitchCase=\"'html'\">\n <span [innerHTML]=\"getSafeHtml(element, column)\"></span>\n </ng-container>\n\n <!-- Compose renderer -->\n <ng-container *ngSwitchCase=\"'compose'\">\n <span class=\"pfx-cell-compose\" [ngClass]=\"getComposeClasses(element, column)\" [ngStyle]=\"getComposeGapStyle(element, column)\">\n <ng-container *ngFor=\"let it of getComposeItems(element, column)\">\n <ng-container [ngSwitch]=\"getItemEffectiveType(element, column, it)\">\n <!-- Reuse helpers by projecting item as faux column -->\n <ng-container *ngSwitchCase=\"'icon'\">\n <mat-icon [color]=\"getIconColor(element, asItemColumn(column, it)) || null\" [ngStyle]=\"getIconStyle(element, asItemColumn(column, it))\" [attr.aria-label]=\"getIconAriaLabel(element, asItemColumn(column, it)) || null\">{{ getIconName(element, asItemColumn(column, it)) }}</mat-icon>\n </ng-container>\n <ng-container *ngSwitchCase=\"'image'\">\n <img class=\"pfx-cell-image\" [src]=\"getImageSrc(element, asItemColumn(column, it))\" [attr.alt]=\"getImageAlt(element, asItemColumn(column, it)) || ''\" [attr.loading]=\"getImageLazy(element, asItemColumn(column, it)) ? 'lazy' : null\" [style.width.px]=\"getImageWidth(element, asItemColumn(column, it))\" [style.height.px]=\"getImageHeight(element, asItemColumn(column, it))\" [class.shape-rounded]=\"getImageShape(element, asItemColumn(column, it)) === 'rounded'\" [class.shape-circle]=\"getImageShape(element, asItemColumn(column, it)) === 'circle'\" [style.object-fit]=\"getImageFit(element, asItemColumn(column, it))\" />\n </ng-container>\n <ng-container *ngSwitchCase=\"'badge'\">\n <span class=\"pfx-badge\" [ngClass]=\"getBadgeClasses(element, asItemColumn(column, it))\"><mat-icon *ngIf=\"getBadgeIcon(element, asItemColumn(column, it)) as bi\" class=\"pfx-badge-icon\">{{ bi }}</mat-icon><span class=\"pfx-badge-text\">{{ getBadgeText(element, asItemColumn(column, it)) }}</span></span>\n </ng-container>\n <ng-container *ngSwitchCase=\"'link'\">\n <a class=\"pfx-link\" [attr.href]=\"getLinkHref(element, asItemColumn(column, it)) || null\" [attr.target]=\"getLinkTarget(element, asItemColumn(column, it)) || null\" [attr.rel]=\"getLinkRel(element, asItemColumn(column, it)) || null\" (click)=\"$event.stopPropagation()\">{{ getLinkText(element, asItemColumn(column, it)) }}</a>\n </ng-container>\n <ng-container *ngSwitchCase=\"'button'\">\n <ng-container [ngSwitch]=\"getButtonVariant(element, asItemColumn(column, it))\">\n <button *ngSwitchCase=\"'outlined'\" mat-stroked-button [color]=\"getButtonColor(element, asItemColumn(column, it)) || null\" [disabled]=\"isButtonDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getButtonAriaLabel(element, asItemColumn(column, it)) || getButtonLabel(element, asItemColumn(column, it))\" (click)=\"onButtonClick(element, asItemColumn(column, it), $event)\"><mat-icon *ngIf=\"getButtonIcon(element, asItemColumn(column, it)) as bi\">{{ bi }}</mat-icon>{{ getButtonLabel(element, asItemColumn(column, it)) }}</button>\n <button *ngSwitchCase=\"'text'\" mat-button [color]=\"getButtonColor(element, asItemColumn(column, it)) || null\" [disabled]=\"isButtonDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getButtonAriaLabel(element, asItemColumn(column, it)) || getButtonLabel(element, asItemColumn(column, it))\" (click)=\"onButtonClick(element, asItemColumn(column, it), $event)\"><mat-icon *ngIf=\"getButtonIcon(element, asItemColumn(column, it)) as bi\">{{ bi }}</mat-icon>{{ getButtonLabel(element, asItemColumn(column, it)) }}</button>\n <button *ngSwitchDefault mat-flat-button [color]=\"getButtonColor(element, asItemColumn(column, it)) || null\" [disabled]=\"isButtonDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getButtonAriaLabel(element, asItemColumn(column, it)) || getButtonLabel(element, asItemColumn(column, it))\" (click)=\"onButtonClick(element, asItemColumn(column, it), $event)\"><mat-icon *ngIf=\"getButtonIcon(element, asItemColumn(column, it)) as bi\">{{ bi }}</mat-icon>{{ getButtonLabel(element, asItemColumn(column, it)) }}</button>\n </ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'chip'\">\n <span class=\"pfx-chip\" [ngClass]=\"getChipClasses(element, asItemColumn(column, it))\"><mat-icon *ngIf=\"getChipIcon(element, asItemColumn(column, it)) as ci\" class=\"pfx-chip-icon\">{{ ci }}</mat-icon><span class=\"pfx-chip-text\">{{ getChipText(element, asItemColumn(column, it)) }}</span></span>\n </ng-container>\n <ng-container *ngSwitchCase=\"'progress'\">\n <div class=\"pfx-progress\"><div class=\"pfx-progress-bar\" [style.width.%]=\"getProgressValue(element, asItemColumn(column, it))\" [style.background]=\"getProgressColor(element, asItemColumn(column, it)) || null\"></div><div class=\"pfx-progress-label\" *ngIf=\"getProgressShowLabel(element, asItemColumn(column, it))\">{{ getProgressValue(element, asItemColumn(column, it)) }}%</div></div>\n </ng-container>\n <ng-container *ngSwitchCase=\"'avatar'\">\n <ng-container *ngIf=\"getAvatarSrc(element, asItemColumn(column, it)) as asrc; else initials_comp\">\n <img class=\"pfx-avatar\" [src]=\"asrc\" [attr.alt]=\"getAvatarAlt(element, asItemColumn(column, it)) || ''\" [ngStyle]=\"getAvatarStyle(element, asItemColumn(column, it))\" [class.shape-rounded]=\"getAvatarShape(element, asItemColumn(column, it)) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, asItemColumn(column, it)) === 'circle'\" loading=\"lazy\" />\n </ng-container>\n <ng-template #initials_comp>\n <span class=\"pfx-avatar pfx-avatar--initials\" [ngStyle]=\"getAvatarStyle(element, asItemColumn(column, it))\" [class.shape-rounded]=\"getAvatarShape(element, asItemColumn(column, it)) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, asItemColumn(column, it)) === 'circle'\">{{ getAvatarInitials(element, asItemColumn(column, it)) }}</span>\n </ng-template>\n </ng-container>\n <ng-container *ngSwitchCase=\"'toggle'\">\n <mat-slide-toggle [checked]=\"getToggleState(element, asItemColumn(column, it))\" [disabled]=\"isToggleDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getToggleAriaLabel(element, asItemColumn(column, it)) || 'Alternar'\" (change)=\"onToggleChange(element, asItemColumn(column, it), $event)\" (click)=\"$event.stopPropagation()\"></mat-slide-toggle>\n </ng-container>\n <ng-container *ngSwitchCase=\"'menu'\">\n <button mat-icon-button [matMenuTriggerFor]=\"menuRef\" (click)=\"$event.stopPropagation()\" [attr.aria-label]=\"getMenuAriaLabel(element, asItemColumn(column, it)) || 'Menu'\"><mat-icon>more_vert</mat-icon></button>\n <mat-menu #menuRef=\"matMenu\">\n <button mat-menu-item *ngFor=\"let mi of getMenuItems(element, asItemColumn(column, it))\" (click)=\"onMenuItemClick(mi.id, element, $event)\" [disabled]=\"!mi.__visible\"><mat-icon *ngIf=\"mi.icon\">{{ mi.icon }}</mat-icon><span>{{ mi.label }}</span></button>\n </mat-menu>\n </ng-container>\n <ng-container *ngSwitchCase=\"'html'\">\n <span [innerHTML]=\"getSafeHtml(element, asItemColumn(column, it))\"></span>\n </ng-container>\n <!-- Value item: render base cell text alongside visuals -->\n <ng-container *ngSwitchCase=\"'value'\">\n <span class=\"pfx-cell-value\">{{ getCellValue(element, column) }}</span>\n </ng-container>\n </ng-container>\n </ng-container>\n </span>\n </ng-container>\n\n <!-- Default text rendering -->\n <ng-container *ngSwitchDefault>\n {{ getCellValue(element, column) }}\n </ng-container>\n </ng-container>\n </td>\n </ng-container>\n <ng-container *ngIf=\"config.actions?.row?.enabled\" matColumnDef=\"_actions\" [sticky]=\"config.actions?.row?.sticky === true || config.actions?.row?.sticky === 'start'\" [stickyEnd]=\"config.actions?.row?.sticky === 'end'\">\n <th mat-header-cell *matHeaderCellDef #actionsHeaderCell [style.width]=\"config.actions?.row?.width\" class=\"praxis-actions-header\" [class.align-start]=\"getActionsHeaderAlign() === 'start'\" [class.align-center]=\"getActionsHeaderAlign() === 'center'\" [class.align-end]=\"getActionsHeaderAlign() === 'end'\">\n <div class=\"praxis-actions-header__content\" [matTooltip]=\"getActionsHeaderTooltip() || ''\" [matTooltipDisabled]=\"!getActionsHeaderTooltip()\">\n <mat-icon *ngIf=\"getActionsHeaderIcon() as hi\" [praxisIcon]=\"hi\"></mat-icon>\n <span class=\"label\" *ngIf=\"getActionsHeaderLabel() as hl\">{{ hl }}</span>\n </div>\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n class=\"praxis-actions-cell\"\n [class.dense]=\"dense\"\n [style.width]=\"config.actions?.row?.width\"\n >\n <div class=\"praxis-actions-cell__content\">\n <!-- A\u00E7\u00F5es inline -->\n <!-- Inline actions: icons mode -->\n <ng-container *ngIf=\"config.actions?.row?.display === 'icons' || !config.actions?.row?.display\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button\n mat-icon-button\n class=\"praxis-icon-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [matTooltip]=\"a.label || getActionId(a)\"\n matTooltipPosition=\"above\"\n matTooltipClass=\"praxis-tooltip\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\"\n >\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n </button>\n </ng-container>\n </ng-container>\n\n <!-- Inline actions: buttons mode (show label + icon) -->\n <ng-container *ngIf=\"config.actions?.row?.display === 'buttons'\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button\n mat-flat-button\n class=\"praxis-row-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\"\n >\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </ng-container>\n\n <!-- Menu de overflow -->\n <button\n mat-icon-button\n class=\"praxis-icon-btn praxis-more-btn\"\n *ngIf=\"hasOverflowRowActions(row)\"\n [matMenuTriggerFor]=\"rowMoreMenu\"\n [color]=\"getRowMenuButtonColor() || null\"\n aria-label=\"Mais a\u00E7\u00F5es\"\n >\n <mat-icon [praxisIcon]=\"getRowMenuIcon()\"></mat-icon>\n </button>\n <mat-menu #rowMoreMenu=\"matMenu\" xPosition=\"before\">\n <ng-container\n *ngFor=\"let a of getOverflowRowActions(row); trackBy: trackAction\"\n >\n <button\n mat-menu-item\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [disabled]=\"isActionDisabled(a, row)\"\n >\n <mat-icon [color]=\"a.color || null\" [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </mat-menu>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <ng-container *ngIf=\"!isVirtualized()\">\n <tr\n mat-row\n *matRowDef=\"let row; let i = index; columns: displayedColumns\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowNgStyle(row)\"\n [matTooltip]=\"getRowTooltip(row) || null\"\n [matTooltipDisabled]=\"!getRowTooltip(row)\"\n [matTooltipPosition]=\"getRowTooltipPosition(row)\"\n [matTooltipShowDelay]=\"getRowTooltipShowDelay(row)\"\n (click)=\"onRowClicked(row, i)\"\n (dblclick)=\"onRowDoubleClicked(row, i)\"\n ></tr>\n </ng-container>\n</table>\n\n<!-- Virtual rows path (header preserved above) -->\n<ng-container *ngIf=\"resourcePath && !schemaError && !dataError && isVirtualized()\">\n <cdk-virtual-scroll-viewport\n class=\"ptable-viewport\"\n [itemSize]=\"getVirtItemHeight()\"\n [minBufferPx]=\"getVirtBufferSize() * getVirtItemHeight()\"\n [maxBufferPx]=\"getVirtBufferSize() * getVirtItemHeight() * 2\"\n [style.minHeight]=\"getVirtMinHeightStyle()\"\n >\n <table class=\"mat-mdc-table mat-elevation-z8\" [style.width]=\"horizontalScroll === 'auto' ? 'max-content' : '100%'\">\n <tbody>\n <tr class=\"mat-mdc-row\"\n *cdkVirtualFor=\"let row of dataSource.data; let i = index; trackBy: trackByRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowNgStyle(row)\"\n [matTooltip]=\"getRowTooltip(row) || null\"\n [matTooltipDisabled]=\"!getRowTooltip(row)\"\n [matTooltipPosition]=\"getRowTooltipPosition(row)\"\n [matTooltipShowDelay]=\"getRowTooltipShowDelay(row)\"\n (click)=\"onRowClicked(row, i)\"\n (dblclick)=\"onRowDoubleClicked(row, i)\">\n <!-- Selection column -->\n <td class=\"mat-mdc-cell\" *ngIf=\"config.behavior?.selection?.enabled\">\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(row)\"\n [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </td>\n <!-- Data columns -->\n <td class=\"mat-mdc-cell\"\n *ngFor=\"let column of visibleColumns\"\n [style.text-align]=\"column.align\"\n [style.width]=\"column.width\"\n [attr.style]=\"column.style\"\n [ngClass]=\"getCellClasses(row, column)\"\n [ngStyle]=\"getCellNgStyle(row, column)\">\n <ng-container [ngSwitch]=\"getEffectiveRendererType(row, column)\">\n <ng-container *ngSwitchCase=\"'icon'\">\n <mat-icon [color]=\"getIconColor(row, column) || null\"\n [ngStyle]=\"getIconStyle(row, column)\"\n [attr.aria-label]=\"getIconAriaLabel(row, column) || null\">\n {{ getIconName(row, column) }}\n </mat-icon>\n </ng-container>\n <ng-container *ngSwitchCase=\"'image'\">\n <img class=\"pfx-cell-image\"\n [src]=\"getImageSrc(row, column)\"\n [attr.alt]=\"getImageAlt(row, column) || ''\"\n [attr.loading]=\"getImageLazy(row, column) ? 'lazy' : null\"\n [style.width.px]=\"getImageWidth(row, column)\"\n [style.height.px]=\"getImageHeight(row, column)\"\n [class.shape-rounded]=\"getImageShape(row, column) === 'rounded'\"\n [class.shape-circle]=\"getImageShape(row, column) === 'circle'\"\n [style.object-fit]=\"getImageFit(row, column)\" />\n </ng-container>\n <ng-container *ngSwitchCase=\"'badge'\">\n <span class=\"pfx-badge\" [ngClass]=\"getBadgeClasses(row, column)\">\n <mat-icon *ngIf=\"getBadgeIcon(row, column) as bi\" class=\"pfx-badge-icon\">{{ bi }}</mat-icon>\n <span class=\"pfx-badge-text\">{{ getBadgeText(row, column) }}</span>\n </span>\n </ng-container>\n <ng-container *ngSwitchDefault>\n {{ getCellValue(row, column) }}\n </ng-container>\n </ng-container>\n </td>\n\n <!-- Actions column -->\n <td class=\"mat-mdc-cell praxis-actions-cell\" *ngIf=\"config.actions?.row?.enabled\" [class.dense]=\"dense\" [style.width]=\"config.actions?.row?.width\">\n <div class=\"praxis-actions-cell__content\">\n <ng-container *ngIf=\"config.actions?.row?.display === 'icons' || !config.actions?.row?.display\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button mat-icon-button class=\"praxis-icon-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [matTooltip]=\"a.label || getActionId(a)\"\n matTooltipPosition=\"above\"\n matTooltipClass=\"praxis-tooltip\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\">\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n </button>\n </ng-container>\n </ng-container>\n <ng-container *ngIf=\"config.actions?.row?.display === 'buttons'\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button mat-flat-button class=\"praxis-row-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\">\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </ng-container>\n <button mat-icon-button class=\"praxis-icon-btn praxis-more-btn\"\n *ngIf=\"hasOverflowRowActions(row)\"\n [matMenuTriggerFor]=\"rowMoreMenuV\"\n [color]=\"getRowMenuButtonColor() || null\"\n aria-label=\"Mais a\u00E7\u00F5es\">\n <mat-icon [praxisIcon]=\"getRowMenuIcon()\"></mat-icon>\n </button>\n <mat-menu #rowMoreMenuV=\"matMenu\" xPosition=\"before\">\n <ng-container *ngFor=\"let a of getOverflowRowActions(row); trackBy: trackAction\">\n <button mat-menu-item (click)=\"onRowAction(getActionId(a), row, $event)\" [disabled]=\"isActionDisabled(a, row)\">\n <mat-icon [color]=\"a.color || null\" [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </mat-menu>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cdk-virtual-scroll-viewport>\n</ng-container>\n\n</div>\n<!-- Paginadores (top/bottom) -->\n<mat-paginator\n *ngIf=\"resourcePath && !schemaError && !dataError && getPaginationEnabled() && (config.behavior?.pagination?.position === 'top' || config.behavior?.pagination?.position === 'both')\"\n [length]=\"getPaginationLength()\"\n [pageSize]=\"getPaginationPageSize()\"\n [pageSizeOptions]=\"getPaginationPageSizeOptions()\"\n [showFirstLastButtons]=\"getPaginationShowFirstLast()\"\n (page)=\"onPageChange($event)\"\n [class.compact]=\"config.behavior?.pagination?.style === 'compact'\"\n>\n</mat-paginator>\n\n<mat-paginator\n *ngIf=\"resourcePath && !schemaError && !dataError && getPaginationEnabled() && (config.behavior?.pagination?.position === 'bottom' || config.behavior?.pagination?.position === 'both' || !config.behavior?.pagination?.position)\"\n [length]=\"getPaginationLength()\"\n [pageSize]=\"getPaginationPageSize()\"\n [pageSizeOptions]=\"getPaginationPageSizeOptions()\"\n [showFirstLastButtons]=\"getPaginationShowFirstLast()\"\n (page)=\"onPageChange($event)\"\n [class.compact]=\"config.behavior?.pagination?.style === 'compact'\"\n>\n</mat-paginator>\n", styles: ["@charset \"UTF-8\";table{width:100%}.praxis-actions-cell{height:100%;padding-inline:12px;white-space:nowrap}:host.density-compact{--p-header-padding: 8px 12px;--p-actions-btn-size: 32px;--p-actions-icon-size: 18px}:host.density-comfortable{--p-header-padding: 12px 16px;--p-actions-btn-size: 40px;--p-actions-icon-size: 22px}:host.density-spacious{--p-header-padding: 16px 20px;--p-actions-btn-size: 44px;--p-actions-icon-size: 24px}:host.density-compact ::ng-deep .mat-mdc-cell{padding:8px 12px}:host.density-comfortable ::ng-deep .mat-mdc-cell{padding:12px 16px}:host.density-spacious ::ng-deep .mat-mdc-cell{padding:16px 20px}:host.density-compact .praxis-actions-cell{padding-inline:8px}:host.density-spacious .praxis-actions-cell{padding-inline:16px}.praxis-actions-cell__content{display:flex;align-items:center;justify-content:flex-end;gap:8px;width:100%}.praxis-actions-cell.dense .praxis-actions-cell__content{gap:6px}.praxis-icon-btn{width:var(--p-actions-btn-size, 40px);height:var(--p-actions-btn-size, 40px);border:0;background:transparent;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;cursor:pointer;--mat-icon-button-state-layer-size: var(--p-actions-btn-size, 40px)}.praxis-icon-btn:hover{background:var(--md-sys-color-surface-variant, rgba(255, 255, 255, .06))}.praxis-icon-btn:focus-visible{outline:2px solid var(--primary, #48a1ff);outline-offset:2px}.praxis-icon-btn mat-icon,.praxis-icon-btn .mat-icon{font-size:var(--p-actions-icon-size, 22px);width:var(--p-actions-icon-size, 22px);height:var(--p-actions-icon-size, 22px);line-height:var(--p-actions-icon-size, 22px)}.praxis-more-btn{width:var(--p-actions-more-btn-size, var(--p-actions-btn-size, 40px));height:var(--p-actions-more-btn-size, var(--p-actions-btn-size, 40px));--mat-icon-button-state-layer-size: var(--p-actions-more-btn-size, var(--p-actions-btn-size, 40px));background-image:var(--p-actions-more-btn-gradient, none);background-size:100% 100%;background-repeat:no-repeat}.praxis-more-btn mat-icon,.praxis-more-btn .mat-icon{font-size:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));width:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));height:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));line-height:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));color:var(--p-actions-more-icon-color);background-image:var(--p-actions-more-icon-gradient, none);-webkit-background-clip:text;background-clip:text}.praxis-icon-btn.destructive mat-icon{color:#ff6b6b}.mat-mdc-tooltip.praxis-tooltip{margin-top:-8px;margin-bottom:8px}.spacer{flex:1 1 auto}.praxis-table-header{display:flex;flex-wrap:wrap;align-items:flex-start;gap:8px;margin:16px 0 12px;width:100%;clear:both;position:relative}.praxis-table-header.debug-layout,:host.debug-layout .praxis-table-header{outline:2px dashed #ff4d4f;border-radius:8px}:host.debug-layout ::ng-deep .praxis-filter-bar{outline:1px dashed #f59e0b}:host.debug-layout ::ng-deep .quick-shell{outline:1px dashed #3b82f6}:host.debug-layout ::ng-deep .always-fields{outline:1px dashed #22c55e}:host.debug-layout ::ng-deep .praxis-filter-overlay .praxis-filter-advanced{outline:2px solid #a855f7}:host.debug-layout ::ng-deep .mat-mdc-table{outline:1px dashed #94a3b8}.praxis-table-header>praxis-table-toolbar{flex:1 0 100%}:host{--p-table-header-bg: var(--md-sys-color-surface-container-highest, #1d1d1f);--p-table-header-fg: var(--md-sys-color-on-surface, #e8f3f1);--p-table-border-color: var(--md-sys-color-outline-variant, rgba(255, 255, 255, .12));--p-table-row-even-bg: var(--md-sys-color-surface-container, rgba(255, 255, 255, .04));--p-table-row-hover-bg: color-mix(in srgb, var(--md-sys-color-primary, #3f51b5) 10%, transparent);--p-table-row-selected-bg: var(--md-sys-color-primary-container, rgba(63,81,181,.14));--p-table-badge-soft-primary-bg: color-mix(in srgb, var(--mat-sys-primary, var(--md-sys-color-primary)) 16%, transparent);--p-table-badge-soft-primary-fg: var(--mat-sys-primary, var(--md-sys-color-primary));--p-table-badge-soft-accent-bg: color-mix(in srgb, var(--mat-sys-secondary, var(--md-sys-color-secondary, #ff4081)) 14%, transparent);--p-table-badge-soft-accent-fg: var(--mat-sys-secondary, var(--md-sys-color-secondary, #ff4081));--p-table-badge-soft-warn-bg: color-mix(in srgb, var(--mat-sys-error, var(--md-sys-color-error, #f44336)) 14%, transparent);--p-table-badge-soft-warn-fg: var(--mat-sys-error, var(--md-sys-color-error, #f44336));--p-table-state-success-bg: color-mix(in srgb, var(--mat-sys-tertiary, var(--md-sys-color-tertiary, #388E3C)) 16%, transparent);--p-table-state-success-fg: var(--md-sys-color-on-surface, #c8e6c9);--p-table-state-warning-bg: color-mix(in srgb, var(--md-sys-color-secondary, #FFA000) 18%, transparent);--p-table-state-warning-fg: var(--md-sys-color-on-surface, #ffe0b2);--p-table-state-danger-bg: color-mix(in srgb, var(--md-sys-color-error, #e53935) 18%, transparent);--p-table-state-danger-fg: var(--md-sys-color-on-surface, #ffcdd2);--p-table-state-highlight-bg: color-mix(in srgb, var(--md-sys-color-primary, #2196f3) 16%, transparent);--p-table-state-highlight-fg: var(--md-sys-color-on-surface, #bbdefb)}:host ::ng-deep .mat-mdc-table{background:var(--md-sys-color-surface-container-highest, #1d1d1f);border-radius:12px;width:100%}:host [data-role=table-settings].mat-mdc-icon-button{--mdc-icon-button-icon-color: var(--mat-sys-primary, var(--md-sys-color-primary, #3f51b5));color:var(--mat-sys-primary, var(--md-sys-color-primary, #3f51b5))}.pfx-link{color:var(--mat-sys-primary, #3f51b5);text-decoration:underline;cursor:pointer}.pfx-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:12px;line-height:1;border:1px solid transparent}.pfx-chip-icon{font-size:14px;width:14px;height:14px}.pfx-chip--outlined{background:transparent;border-color:var(--mat-sys-outline-variant);color:var(--mat-sys-on-surface)}.pfx-chip--filled-primary{background:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}.pfx-chip--filled-accent{background:var(--mat-sys-secondary, #ff4081);color:#fff}.pfx-chip--filled-warn{background:var(--mat-sys-error, #f44336);color:#fff}.pfx-chip--soft-primary{background:var(--p-table-badge-soft-primary-bg);color:var(--p-table-badge-soft-primary-fg)}.pfx-chip--soft-accent{background:var(--p-table-badge-soft-accent-bg);color:var(--p-table-badge-soft-accent-fg)}.pfx-chip--soft-warn{background:var(--p-table-badge-soft-warn-bg);color:var(--p-table-badge-soft-warn-fg)}.pfx-progress{position:relative;width:100%;max-width:140px;height:8px;background:var(--mat-sys-surface-container-highest, rgba(255, 255, 255, .08));border-radius:4px;overflow:hidden;display:inline-block;vertical-align:middle}.pfx-progress-bar{height:100%;background:var(--mat-sys-primary);transition:width .2s ease}.pfx-progress-label{margin-left:8px;font-size:12px;opacity:.8;display:inline-block;vertical-align:middle}.pfx-avatar{display:inline-flex;align-items:center;justify-content:center;background:var(--mat-sys-surface-container, #2a2a2e);color:var(--mat-sys-on-surface, #eee);border-radius:4px;overflow:hidden;font-weight:600}.pfx-avatar.shape-rounded{border-radius:8px}.pfx-avatar.shape-circle{border-radius:999px}.pfx-avatar--initials{text-transform:uppercase;font-size:12px}.pfx-cell-compose{display:inline-flex;align-items:center;gap:6px}.pfx-cell-compose.dir-col{flex-direction:column;align-items:stretch}.pfx-cell-compose.align-start{justify-content:flex-start}.pfx-cell-compose.align-center{justify-content:center}.pfx-cell-compose.align-end{justify-content:flex-end}.pfx-cell-compose.wrap{flex-wrap:wrap}.pfx-cell-compose.ellipsis{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.px-scroll-viewport{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;scrollbar-gutter:stable}.px-scroll-viewport.scroll-none{overflow-x:visible}.px-scroll-viewport.scroll-auto ::ng-deep .mat-mdc-table{width:max-content;min-width:100%}.px-scroll-viewport.scroll-wrap ::ng-deep .mat-mdc-header-cell,.px-scroll-viewport.scroll-wrap ::ng-deep .mat-mdc-cell{white-space:normal;text-overflow:initial}.px-scroll-viewport.scroll-wrap ::ng-deep .mat-mdc-table{width:100%}:host ::ng-deep .mat-mdc-header-row{position:sticky;top:0;z-index:1;background:var(--p-table-header-bg);color:var(--p-table-header-fg);box-shadow:var(--p-table-header-shadow, 0 1px 0 var(--p-table-border-color));border-bottom:1px solid var(--p-table-border-color)}:host ::ng-deep .mat-mdc-header-cell,:host ::ng-deep .mat-sort-header-content,:host ::ng-deep .mat-mdc-header-row .mat-icon{color:var(--p-table-header-fg)!important;font-weight:600}:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow,:host ::ng-deep .mat-mdc-header-cell:hover .mat-sort-header-arrow{color:var(--p-table-header-fg)!important}:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-indicator,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-stem,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-pointer,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-pointer-left,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-pointer-right{border-color:var(--p-table-header-fg)!important;background:var(--p-table-header-fg)!important}:host ::ng-deep .mat-mdc-header-cell{padding:var(--p-header-padding, 12px 16px)!important;font-size:var(--p-header-font-size, inherit);font-weight:var(--p-header-font-weight, 600);letter-spacing:var(--p-header-letter-spacing, normal);text-transform:var(--p-header-text-transform, none);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.praxis-actions-header{text-align:right}.praxis-actions-header.align-start{text-align:left}.praxis-actions-header.align-center{text-align:center}.praxis-actions-header.align-end{text-align:right}.praxis-actions-header .praxis-actions-header__content{display:inline-flex;align-items:center;gap:var(--p-actions-header-gap, 6px);color:var(--p-actions-header-color, inherit)}.praxis-actions-header .praxis-actions-header__content .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}:host ::ng-deep .mat-mdc-header-cell .mat-sort-header-container{padding-right:20px}:host ::ng-deep .mat-mdc-cell{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}:host ::ng-deep .mat-mdc-row:hover{background:var(--p-table-row-hover-bg)}:host ::ng-deep .mat-mdc-row:nth-child(2n){background:var(--p-table-row-even-bg)}:host.row-borders ::ng-deep .mat-mdc-row .mat-mdc-cell{border-bottom:1px solid var(--p-table-border-color)}:host.row-borders ::ng-deep .mat-mdc-header-row .mat-mdc-header-cell{border-bottom:none}:host.col-borders ::ng-deep .mat-mdc-header-row .mat-mdc-header-cell,:host.col-borders ::ng-deep .mat-mdc-row .mat-mdc-cell{border-right:1px solid var(--p-table-border-color)}:host.col-borders ::ng-deep .mat-mdc-header-row .mat-mdc-header-cell:last-child,:host.col-borders ::ng-deep .mat-mdc-row .mat-mdc-cell:last-child{border-right:none}.ptable-info-banner{display:flex;gap:12px;align-items:center;padding:8px 12px;margin:8px 0;border-radius:8px;border:1px solid var(--mat-sys-primary, var(--md-sys-color-primary, #1a73e8));background:color-mix(in srgb,var(--mat-sys-primary, var(--md-sys-color-primary, #1a73e8)) 10%,transparent)}.ptable-info-banner .text{flex:1;font-weight:600}.ptable-info-banner .actions{display:flex;gap:8px}.pfx-cell-image{display:inline-block;vertical-align:middle;background:var(--md-sys-color-surface-variant, #2a2a2a);border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .08))}.pfx-cell-image.shape-rounded{border-radius:8px}.pfx-cell-image.shape-circle{border-radius:9999px}.pfx-badge{display:inline-flex;align-items:center;gap:6px;line-height:1;padding:4px 8px;border-radius:9999px;font-size:12px;font-weight:600;border:1px solid transparent}.pfx-badge .pfx-badge-icon{font-size:16px;width:16px;height:16px}.pfx-badge--filled-primary{background:var(--mat-sys-primary, #3f51b5);color:var(--mat-sys-on-primary, #fff)}.pfx-badge--filled-accent{background:var(--mat-sys-secondary, #ff4081);color:var(--mat-sys-on-secondary, #fff)}.pfx-badge--filled-warn{background:var(--mat-sys-error, #f44336);color:var(--mat-sys-on-error, #fff)}.pfx-badge--outlined{background:transparent;border-color:var(--mat-sys-outline-variant, rgba(255, 255, 255, .24));color:inherit}.pfx-badge--soft-primary{background:var(--p-table-badge-soft-primary-bg);color:var(--p-table-badge-soft-primary-fg)}.pfx-badge--soft-accent{background:var(--p-table-badge-soft-accent-bg);color:var(--p-table-badge-soft-accent-fg)}.pfx-badge--soft-warn{background:var(--p-table-badge-soft-warn-bg);color:var(--p-table-badge-soft-warn-fg)}.row--success,.row--success td,td.row--success{background-color:var(--p-table-state-success-bg)!important;color:var(--p-table-state-success-fg)!important}.row--warning,.row--warning td,td.row--warning{background-color:var(--p-table-state-warning-bg)!important;color:var(--p-table-state-warning-fg)!important}.row--danger,.row--danger td,td.row--danger{background-color:var(--p-table-state-danger-bg)!important;color:var(--p-table-state-danger-fg)!important}.row--highlight,.row--highlight td,td.row--highlight{background-color:var(--p-table-state-highlight-bg)!important;color:var(--p-table-state-highlight-fg)!important;font-weight:600}.row--muted,.row--muted td,td.row--muted{opacity:.7;filter:saturate(.6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i8.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i8.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i8.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i8.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i8.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i8.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i8.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i8.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i8.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i8.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i11$1.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i12.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i12.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i9.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i15.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i19.ɵɵCdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i19.ɵɵCdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i19.ɵɵCdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: PraxisTableToolbar, selector: "praxis-table-toolbar", inputs: ["config", "debugLayout"], outputs: ["toolbarAction", "reset"] }, { kind: "component", type: PraxisFilter, selector: "praxis-filter", inputs: ["resourcePath", "formId", "mode", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "editModeEnabled", "value", "quickField", "alwaysVisibleFields", "tags", "allowSaveTags", "persistenceKey", "i18n", "changeDebounceMs", "showFilterSettings", "summary", "summaryTemplate", "summaryMap", "autoSummary", "confirmTagDelete", "debugLayout", "placeBooleansInActions", "showToggleLabels", "alwaysMinWidth", "alwaysColsMd", "alwaysColsLg", "tagColor", "tagVariant", "tagButtonColor", "actionsButtonColor", "actionsVariant", "overlayVariant", "overlayBackdrop", "advancedOpenMode"], outputs: ["submit", "change", "clear", "modeChange", "requestSearch", "tagsChange", "metaChanged", "schemaStatusChange"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline"] }] });
24065
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTable, isStandalone: true, selector: "praxis-table", inputs: { config: "config", resourcePath: "resourcePath", filterCriteria: "filterCriteria", notifyIfOutdated: "notifyIfOutdated", snoozeMs: "snoozeMs", autoOpenSettingsOnOutdated: "autoOpenSettingsOnOutdated", showToolbar: "showToolbar", toolbarV2: "toolbarV2", autoDelete: "autoDelete", editModeEnabled: "editModeEnabled", dense: "dense", tableId: "tableId", debugLayout: "debugLayout", horizontalScroll: "horizontalScroll", crudContext: "crudContext", idField: "idField" }, outputs: { rowClick: "rowClick", rowAction: "rowAction", toolbarAction: "toolbarAction", bulkAction: "bulkAction", rowDoubleClick: "rowDoubleClick", schemaStatusChange: "schemaStatusChange", metadataChange: "metadataChange", beforeDelete: "beforeDelete", afterDelete: "afterDelete", deleteError: "deleteError", beforeBulkDelete: "beforeBulkDelete", afterBulkDelete: "afterBulkDelete", bulkDeleteError: "bulkDeleteError" }, host: { properties: { "class.debug-layout": "debugLayout", "class.density-compact": "config?.appearance?.density === 'compact'", "class.density-comfortable": "config?.appearance?.density === 'comfortable'", "class.density-spacious": "config?.appearance?.density === 'spacious'", "class.row-borders": "config?.appearance?.borders?.showRowBorders !== false", "class.col-borders": "!!config?.appearance?.borders?.showColumnBorders" } }, queries: [{ propertyName: "projectedFilter", first: true, predicate: PraxisFilter, descendants: true }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "actionsHeaderCell", first: true, predicate: ["actionsHeaderCell"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<praxis-empty-state-card\n *ngIf=\"!resourcePath\"\n icon=\"link\"\n [title]=\"'Conecte a tabela \u00E0 fonte de dados'\"\n [description]=\"'Informe a rota do recurso da API para carregar colunas e dados automaticamente.'\"\n [primaryAction]=\"{ label: 'Conectar \u00E0 fonte de dados', icon: 'bolt', action: openQuickConnect.bind(this) }\"\n></praxis-empty-state-card>\n\n<!-- Error State with Quick Connect CTA -->\n<div class=\"ptable-error\" *ngIf=\"resourcePath && (schemaError || dataError)\" style=\"display:flex; align-items:center; gap:12px; padding:12px; border:1px solid var(--md-sys-color-error, #b00020); border-radius:8px; margin: 8px 0;\">\n <mat-icon color=\"warn\" aria-hidden=\"true\">error</mat-icon>\n <div style=\"flex:1\">\n <div style=\"font-weight:600\">Erro</div>\n <div>{{ errorMessage || 'Ocorreu um erro ao carregar a tabela.' }}</div>\n </div>\n <button mat-flat-button color=\"primary\" (click)=\"openQuickConnect()\">\n <mat-icon>bolt</mat-icon>\n Conectar a recurso\n </button>\n <button mat-stroked-button (click)=\"retryData()\" *ngIf=\"!schemaError\">Tentar novamente</button>\n <button mat-stroked-button (click)=\"reloadSchema()\" *ngIf=\"schemaError\">Recarregar colunas</button>\n </div>\n\n<!-- Inline banner for schema change (only in edit mode) -->\n<div *ngIf=\"shouldShowOutdatedInline()\" class=\"ptable-info-banner\" role=\"status\" aria-live=\"polite\">\n <div class=\"text\">O schema do servidor mudou. Reconciliar agora?</div>\n <div class=\"actions\">\n <button mat-stroked-button color=\"primary\" (click)=\"onReconcileRequested()\">\n <mat-icon>sync</mat-icon>\n Reconciliar\n </button>\n <button mat-button (click)=\"onSnoozeOutdated()\">Lembrar depois</button>\n <button mat-button (click)=\"onIgnoreOutdated()\">Ignorar</button>\n </div>\n </div>\n\n <ng-container *ngIf=\"resourcePath && !schemaError && !dataError && toolbarV2; else legacyHeader\">\n <div class=\"praxis-table-header\" [class.debug-layout]=\"debugLayout\" [class.edit-mode]=\"editModeEnabled\" *ngIf=\"showToolbar || editModeEnabled\">\n <praxis-table-toolbar\n *ngIf=\"showToolbar\"\n [config]=\"config\"\n [debugLayout]=\"debugLayout\"\n (toolbarAction)=\"onToolbarAction($event)\"\n (reset)=\"onResetPreferences()\"\n >\n <praxis-filter\n *ngIf=\"\n resourcePath &&\n config.behavior?.filtering?.advancedFilters?.enabled &&\n !projectedFilter\n \"\n advancedFilter\n [resourcePath]=\"resourcePath\"\n [formId]=\"tableId + '-filter'\"\n [editModeEnabled]=\"editModeEnabled\"\n [quickField]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.quickField\n \"\n [alwaysVisibleFields]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.alwaysVisibleFields\n \"\n [allowSaveTags]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.allowSaveTags\n \"\n [changeDebounceMs]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.changeDebounceMs ?? 300\n \"\n [i18n]=\"getFilterI18n()\"\n [mode]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.mode ??\n 'auto'\n \"\n [debugLayout]=\"debugLayout\"\n [showFilterSettings]=\"!editModeEnabled\"\n (submit)=\"onAdvancedFilterSubmit($event)\"\n (clear)=\"onAdvancedFilterClear()\"\n ></praxis-filter>\n <ng-content select=\"[advancedFilter]\" />\n <ng-content select=\"[toolbar]\" />\n <button end-actions mat-icon-button color=\"primary\" data-role=\"table-settings\" *ngIf=\"editModeEnabled\"\n (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n </praxis-table-toolbar>\n <!-- Render a minimal settings button when toolbar is hidden but edit mode is enabled -->\n <div class=\"ptable-header-actions\" *ngIf=\"!showToolbar && editModeEnabled\">\n <button mat-icon-button color=\"primary\" (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n <button mat-icon-button (click)=\"disconnect()\" aria-label=\"Desconectar\" matTooltip=\"Desconectar da fonte de dados\">\n <mat-icon>link_off</mat-icon>\n </button>\n </div>\n \n </div>\n</ng-container>\n<ng-template #legacyHeader>\n <ng-container *ngIf=\"resourcePath && !schemaError && !dataError\">\n <praxis-table-toolbar\n *ngIf=\"showToolbar\"\n [config]=\"config\"\n [debugLayout]=\"debugLayout\"\n (toolbarAction)=\"onToolbarAction($event)\"\n (reset)=\"onResetPreferences()\"\n >\n <praxis-filter\n *ngIf=\"\n resourcePath &&\n config.behavior?.filtering?.advancedFilters?.enabled &&\n !projectedFilter\n \"\n advancedFilter\n [resourcePath]=\"resourcePath\"\n [formId]=\"tableId + '-filter'\"\n [editModeEnabled]=\"editModeEnabled\"\n [quickField]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.quickField\n \"\n [alwaysVisibleFields]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.alwaysVisibleFields\n \"\n [allowSaveTags]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.allowSaveTags\n \"\n [changeDebounceMs]=\"\n config.behavior?.filtering?.advancedFilters?.settings\n ?.changeDebounceMs ?? 300\n \"\n [i18n]=\"getFilterI18n()\"\n [mode]=\"\n config.behavior?.filtering?.advancedFilters?.settings?.mode ??\n 'auto'\n \"\n [debugLayout]=\"debugLayout\"\n [showFilterSettings]=\"!editModeEnabled\"\n (submit)=\"onAdvancedFilterSubmit($event)\"\n (clear)=\"onAdvancedFilterClear()\"\n ></praxis-filter>\n <ng-content select=\"[advancedFilter]\" />\n <ng-content select=\"[toolbar]\" />\n <button end-actions mat-icon-button color=\"primary\" *ngIf=\"editModeEnabled\"\n (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n </praxis-table-toolbar>\n <!-- Legacy header: settings button when toolbar hidden -->\n <div class=\"ptable-header-actions\" *ngIf=\"!showToolbar && editModeEnabled\">\n <button mat-icon-button color=\"primary\" data-role=\"table-settings\" (click)=\"openTableSettings()\" aria-label=\"Configura\u00E7\u00F5es\"\n [matBadge]=\"schemaOutdated ? '!' : ''\"\n [matBadgeHidden]=\"!schemaOutdated\"\n matBadgeColor=\"warn\" matBadgeSize=\"small\" matBadgePosition=\"above after\"\n [matTooltip]=\"schemaOutdated ? 'Schema do servidor mudou \u2014 Reconciliar' : 'Configura\u00E7\u00F5es'\"\n matTooltipPosition=\"below\">\n <mat-icon>settings</mat-icon>\n </button>\n </div>\n </ng-container>\n \n</ng-template>\n<div class=\"px-scroll-viewport\"\n [class.scroll-auto]=\"horizontalScroll === 'auto'\"\n [class.scroll-wrap]=\"horizontalScroll === 'wrap'\"\n [class.scroll-none]=\"horizontalScroll === 'none'\">\n\n<table\n *ngIf=\"resourcePath && !schemaError && !dataError\"\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"onSortChange($event)\"\n [matSortDisabled]=\"!getSortingEnabled()\"\n class=\"mat-elevation-z8\"\n>\n <ng-container\n *ngIf=\"config.behavior?.selection?.enabled\"\n matColumnDef=\"_select\"\n >\n <th mat-header-cell *matHeaderCellDef>\n <mat-checkbox\n (change)=\"masterToggle()\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\"\n ></mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\">\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(row)\"\n [checked]=\"selection.isSelected(row)\"\n ></mat-checkbox>\n </td>\n </ng-container>\n <ng-container\n *ngFor=\"let column of visibleColumns\"\n [matColumnDef]=\"column.field\"\n [sticky]=\"column.sticky === true || column.sticky === 'start'\"\n [stickyEnd]=\"column.sticky === 'end'\"\n >\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n [disabled]=\"!getSortingEnabled() || column.sortable === false\"\n [style.text-align]=\"column.align\"\n [style.width]=\"column.width\"\n [attr.style]=\"column.headerStyle\"\n >\n {{ column.header }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n [style.text-align]=\"column.align\"\n [style.width]=\"column.width\"\n [attr.style]=\"column.style\"\n [ngClass]=\"getCellClasses(element, column)\"\n [ngStyle]=\"getCellNgStyle(element, column)\"\n >\n <ng-container [ngSwitch]=\"getEffectiveRendererType(element, column)\">\n <!-- Icon renderer -->\n <ng-container *ngSwitchCase=\"'icon'\">\n <mat-icon\n [color]=\"getIconColor(element, column) || null\"\n [ngStyle]=\"getIconStyle(element, column)\"\n [attr.aria-label]=\"getIconAriaLabel(element, column) || null\"\n >{{ getIconName(element, column) }}</mat-icon\n >\n </ng-container>\n\n <!-- Image renderer -->\n <ng-container *ngSwitchCase=\"'image'\">\n <img\n class=\"pfx-cell-image\"\n [src]=\"getImageSrc(element, column)\"\n [attr.alt]=\"getImageAlt(element, column) || ''\"\n [attr.loading]=\"getImageLazy(element, column) ? 'lazy' : null\"\n [style.width.px]=\"getImageWidth(element, column)\"\n [style.height.px]=\"getImageHeight(element, column)\"\n [class.shape-rounded]=\"getImageShape(element, column) === 'rounded'\"\n [class.shape-circle]=\"getImageShape(element, column) === 'circle'\"\n [style.object-fit]=\"getImageFit(element, column)\"\n />\n </ng-container>\n\n <!-- Badge renderer -->\n <ng-container *ngSwitchCase=\"'badge'\">\n <span class=\"pfx-badge\" [ngClass]=\"getBadgeClasses(element, column)\">\n <mat-icon *ngIf=\"getBadgeIcon(element, column) as bi\" class=\"pfx-badge-icon\">{{ bi }}</mat-icon>\n <span class=\"pfx-badge-text\">{{ getBadgeText(element, column) }}</span>\n </span>\n </ng-container>\n\n <!-- Link renderer -->\n <ng-container *ngSwitchCase=\"'link'\">\n <a\n class=\"pfx-link\"\n [attr.href]=\"getLinkHref(element, column) || null\"\n [attr.target]=\"getLinkTarget(element, column) || null\"\n [attr.rel]=\"getLinkRel(element, column) || null\"\n (click)=\"$event.stopPropagation()\"\n >{{ getLinkText(element, column) }}</a\n >\n </ng-container>\n\n <!-- Button renderer -->\n <ng-container *ngSwitchCase=\"'button'\">\n <ng-container [ngSwitch]=\"getButtonVariant(element, column)\">\n <button\n *ngSwitchCase=\"'outlined'\"\n mat-stroked-button\n [color]=\"getButtonColor(element, column) || null\"\n [disabled]=\"isButtonDisabled(element, column)\"\n [attr.aria-label]=\"getButtonAriaLabel(element, column) || getButtonLabel(element, column)\"\n (click)=\"onButtonClick(element, column, $event)\"\n >\n <mat-icon *ngIf=\"getButtonIcon(element, column) as bi\">{{ bi }}</mat-icon>\n {{ getButtonLabel(element, column) }}\n </button>\n <button\n *ngSwitchCase=\"'text'\"\n mat-button\n [color]=\"getButtonColor(element, column) || null\"\n [disabled]=\"isButtonDisabled(element, column)\"\n [attr.aria-label]=\"getButtonAriaLabel(element, column) || getButtonLabel(element, column)\"\n (click)=\"onButtonClick(element, column, $event)\"\n >\n <mat-icon *ngIf=\"getButtonIcon(element, column) as bi\">{{ bi }}</mat-icon>\n {{ getButtonLabel(element, column) }}\n </button>\n <button\n *ngSwitchDefault\n mat-flat-button\n [color]=\"getButtonColor(element, column) || null\"\n [disabled]=\"isButtonDisabled(element, column)\"\n [attr.aria-label]=\"getButtonAriaLabel(element, column) || getButtonLabel(element, column)\"\n (click)=\"onButtonClick(element, column, $event)\"\n >\n <mat-icon *ngIf=\"getButtonIcon(element, column) as bi\">{{ bi }}</mat-icon>\n {{ getButtonLabel(element, column) }}\n </button>\n </ng-container>\n </ng-container>\n\n <!-- Chip renderer -->\n <ng-container *ngSwitchCase=\"'chip'\">\n <span class=\"pfx-chip\" [ngClass]=\"getChipClasses(element, column)\">\n <mat-icon *ngIf=\"getChipIcon(element, column) as ci\" class=\"pfx-chip-icon\">{{ ci }}</mat-icon>\n <span class=\"pfx-chip-text\">{{ getChipText(element, column) }}</span>\n </span>\n </ng-container>\n\n <!-- Progress renderer -->\n <ng-container *ngSwitchCase=\"'progress'\">\n <div class=\"pfx-progress\">\n <div class=\"pfx-progress-bar\" [style.width.%]=\"getProgressValue(element, column)\" [style.background]=\"getProgressColor(element, column) || null\"></div>\n <div class=\"pfx-progress-label\" *ngIf=\"getProgressShowLabel(element, column)\">{{ getProgressValue(element, column) }}%</div>\n </div>\n </ng-container>\n\n <!-- Avatar renderer -->\n <ng-container *ngSwitchCase=\"'avatar'\">\n <ng-container *ngIf=\"getAvatarSrc(element, column) as asrc; else initials\">\n <img class=\"pfx-avatar\" [src]=\"asrc\" [attr.alt]=\"getAvatarAlt(element, column) || ''\" [ngStyle]=\"getAvatarStyle(element, column)\" [class.shape-rounded]=\"getAvatarShape(element, column) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, column) === 'circle'\" loading=\"lazy\" />\n </ng-container>\n <ng-template #initials>\n <span class=\"pfx-avatar pfx-avatar--initials\" [ngStyle]=\"getAvatarStyle(element, column)\" [class.shape-rounded]=\"getAvatarShape(element, column) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, column) === 'circle'\">{{ getAvatarInitials(element, column) }}</span>\n </ng-template>\n </ng-container>\n\n <!-- Toggle renderer -->\n <ng-container *ngSwitchCase=\"'toggle'\">\n <mat-slide-toggle\n [checked]=\"getToggleState(element, column)\"\n [disabled]=\"isToggleDisabled(element, column)\"\n [attr.aria-label]=\"getToggleAriaLabel(element, column) || 'Alternar'\"\n (change)=\"onToggleChange(element, column, $event)\"\n (click)=\"$event.stopPropagation()\"\n ></mat-slide-toggle>\n </ng-container>\n\n <!-- Menu renderer -->\n <ng-container *ngSwitchCase=\"'menu'\">\n <button mat-icon-button [matMenuTriggerFor]=\"menuRef\" (click)=\"$event.stopPropagation()\" [attr.aria-label]=\"getMenuAriaLabel(element, column) || 'Menu'\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #menuRef=\"matMenu\">\n <button mat-menu-item *ngFor=\"let it of getMenuItems(element, column)\" (click)=\"onMenuItemClick(it.id, element, $event)\" [disabled]=\"!it.__visible\" >\n <mat-icon *ngIf=\"it.icon\">{{ it.icon }}</mat-icon>\n <span>{{ it.label }}</span>\n </button>\n </mat-menu>\n </ng-container>\n\n <!-- HTML renderer (sanitizado) -->\n <ng-container *ngSwitchCase=\"'html'\">\n <span [innerHTML]=\"getSafeHtml(element, column)\"></span>\n </ng-container>\n\n <!-- Compose renderer -->\n <ng-container *ngSwitchCase=\"'compose'\">\n <span class=\"pfx-cell-compose\" [ngClass]=\"getComposeClasses(element, column)\" [ngStyle]=\"getComposeGapStyle(element, column)\">\n <ng-container *ngFor=\"let it of getComposeItems(element, column)\">\n <ng-container [ngSwitch]=\"getItemEffectiveType(element, column, it)\">\n <!-- Reuse helpers by projecting item as faux column -->\n <ng-container *ngSwitchCase=\"'icon'\">\n <mat-icon [color]=\"getIconColor(element, asItemColumn(column, it)) || null\" [ngStyle]=\"getIconStyle(element, asItemColumn(column, it))\" [attr.aria-label]=\"getIconAriaLabel(element, asItemColumn(column, it)) || null\">{{ getIconName(element, asItemColumn(column, it)) }}</mat-icon>\n </ng-container>\n <ng-container *ngSwitchCase=\"'image'\">\n <img class=\"pfx-cell-image\" [src]=\"getImageSrc(element, asItemColumn(column, it))\" [attr.alt]=\"getImageAlt(element, asItemColumn(column, it)) || ''\" [attr.loading]=\"getImageLazy(element, asItemColumn(column, it)) ? 'lazy' : null\" [style.width.px]=\"getImageWidth(element, asItemColumn(column, it))\" [style.height.px]=\"getImageHeight(element, asItemColumn(column, it))\" [class.shape-rounded]=\"getImageShape(element, asItemColumn(column, it)) === 'rounded'\" [class.shape-circle]=\"getImageShape(element, asItemColumn(column, it)) === 'circle'\" [style.object-fit]=\"getImageFit(element, asItemColumn(column, it))\" />\n </ng-container>\n <ng-container *ngSwitchCase=\"'badge'\">\n <span class=\"pfx-badge\" [ngClass]=\"getBadgeClasses(element, asItemColumn(column, it))\"><mat-icon *ngIf=\"getBadgeIcon(element, asItemColumn(column, it)) as bi\" class=\"pfx-badge-icon\">{{ bi }}</mat-icon><span class=\"pfx-badge-text\">{{ getBadgeText(element, asItemColumn(column, it)) }}</span></span>\n </ng-container>\n <ng-container *ngSwitchCase=\"'link'\">\n <a class=\"pfx-link\" [attr.href]=\"getLinkHref(element, asItemColumn(column, it)) || null\" [attr.target]=\"getLinkTarget(element, asItemColumn(column, it)) || null\" [attr.rel]=\"getLinkRel(element, asItemColumn(column, it)) || null\" (click)=\"$event.stopPropagation()\">{{ getLinkText(element, asItemColumn(column, it)) }}</a>\n </ng-container>\n <ng-container *ngSwitchCase=\"'button'\">\n <ng-container [ngSwitch]=\"getButtonVariant(element, asItemColumn(column, it))\">\n <button *ngSwitchCase=\"'outlined'\" mat-stroked-button [color]=\"getButtonColor(element, asItemColumn(column, it)) || null\" [disabled]=\"isButtonDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getButtonAriaLabel(element, asItemColumn(column, it)) || getButtonLabel(element, asItemColumn(column, it))\" (click)=\"onButtonClick(element, asItemColumn(column, it), $event)\"><mat-icon *ngIf=\"getButtonIcon(element, asItemColumn(column, it)) as bi\">{{ bi }}</mat-icon>{{ getButtonLabel(element, asItemColumn(column, it)) }}</button>\n <button *ngSwitchCase=\"'text'\" mat-button [color]=\"getButtonColor(element, asItemColumn(column, it)) || null\" [disabled]=\"isButtonDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getButtonAriaLabel(element, asItemColumn(column, it)) || getButtonLabel(element, asItemColumn(column, it))\" (click)=\"onButtonClick(element, asItemColumn(column, it), $event)\"><mat-icon *ngIf=\"getButtonIcon(element, asItemColumn(column, it)) as bi\">{{ bi }}</mat-icon>{{ getButtonLabel(element, asItemColumn(column, it)) }}</button>\n <button *ngSwitchDefault mat-flat-button [color]=\"getButtonColor(element, asItemColumn(column, it)) || null\" [disabled]=\"isButtonDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getButtonAriaLabel(element, asItemColumn(column, it)) || getButtonLabel(element, asItemColumn(column, it))\" (click)=\"onButtonClick(element, asItemColumn(column, it), $event)\"><mat-icon *ngIf=\"getButtonIcon(element, asItemColumn(column, it)) as bi\">{{ bi }}</mat-icon>{{ getButtonLabel(element, asItemColumn(column, it)) }}</button>\n </ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'chip'\">\n <span class=\"pfx-chip\" [ngClass]=\"getChipClasses(element, asItemColumn(column, it))\"><mat-icon *ngIf=\"getChipIcon(element, asItemColumn(column, it)) as ci\" class=\"pfx-chip-icon\">{{ ci }}</mat-icon><span class=\"pfx-chip-text\">{{ getChipText(element, asItemColumn(column, it)) }}</span></span>\n </ng-container>\n <ng-container *ngSwitchCase=\"'progress'\">\n <div class=\"pfx-progress\"><div class=\"pfx-progress-bar\" [style.width.%]=\"getProgressValue(element, asItemColumn(column, it))\" [style.background]=\"getProgressColor(element, asItemColumn(column, it)) || null\"></div><div class=\"pfx-progress-label\" *ngIf=\"getProgressShowLabel(element, asItemColumn(column, it))\">{{ getProgressValue(element, asItemColumn(column, it)) }}%</div></div>\n </ng-container>\n <ng-container *ngSwitchCase=\"'avatar'\">\n <ng-container *ngIf=\"getAvatarSrc(element, asItemColumn(column, it)) as asrc; else initials_comp\">\n <img class=\"pfx-avatar\" [src]=\"asrc\" [attr.alt]=\"getAvatarAlt(element, asItemColumn(column, it)) || ''\" [ngStyle]=\"getAvatarStyle(element, asItemColumn(column, it))\" [class.shape-rounded]=\"getAvatarShape(element, asItemColumn(column, it)) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, asItemColumn(column, it)) === 'circle'\" loading=\"lazy\" />\n </ng-container>\n <ng-template #initials_comp>\n <span class=\"pfx-avatar pfx-avatar--initials\" [ngStyle]=\"getAvatarStyle(element, asItemColumn(column, it))\" [class.shape-rounded]=\"getAvatarShape(element, asItemColumn(column, it)) === 'rounded'\" [class.shape-circle]=\"getAvatarShape(element, asItemColumn(column, it)) === 'circle'\">{{ getAvatarInitials(element, asItemColumn(column, it)) }}</span>\n </ng-template>\n </ng-container>\n <ng-container *ngSwitchCase=\"'toggle'\">\n <mat-slide-toggle [checked]=\"getToggleState(element, asItemColumn(column, it))\" [disabled]=\"isToggleDisabled(element, asItemColumn(column, it))\" [attr.aria-label]=\"getToggleAriaLabel(element, asItemColumn(column, it)) || 'Alternar'\" (change)=\"onToggleChange(element, asItemColumn(column, it), $event)\" (click)=\"$event.stopPropagation()\"></mat-slide-toggle>\n </ng-container>\n <ng-container *ngSwitchCase=\"'menu'\">\n <button mat-icon-button [matMenuTriggerFor]=\"menuRef\" (click)=\"$event.stopPropagation()\" [attr.aria-label]=\"getMenuAriaLabel(element, asItemColumn(column, it)) || 'Menu'\"><mat-icon>more_vert</mat-icon></button>\n <mat-menu #menuRef=\"matMenu\">\n <button mat-menu-item *ngFor=\"let mi of getMenuItems(element, asItemColumn(column, it))\" (click)=\"onMenuItemClick(mi.id, element, $event)\" [disabled]=\"!mi.__visible\"><mat-icon *ngIf=\"mi.icon\">{{ mi.icon }}</mat-icon><span>{{ mi.label }}</span></button>\n </mat-menu>\n </ng-container>\n <ng-container *ngSwitchCase=\"'html'\">\n <span [innerHTML]=\"getSafeHtml(element, asItemColumn(column, it))\"></span>\n </ng-container>\n <!-- Value item: render base cell text alongside visuals -->\n <ng-container *ngSwitchCase=\"'value'\">\n <span class=\"pfx-cell-value\">{{ getCellValue(element, column) }}</span>\n </ng-container>\n </ng-container>\n </ng-container>\n </span>\n </ng-container>\n\n <!-- Default text rendering -->\n <ng-container *ngSwitchDefault>\n {{ getCellValue(element, column) }}\n </ng-container>\n </ng-container>\n </td>\n </ng-container>\n <ng-container *ngIf=\"config.actions?.row?.enabled\" matColumnDef=\"_actions\" [sticky]=\"config.actions?.row?.sticky === true || config.actions?.row?.sticky === 'start'\" [stickyEnd]=\"config.actions?.row?.sticky === 'end'\">\n <th mat-header-cell *matHeaderCellDef #actionsHeaderCell [style.width]=\"config.actions?.row?.width\" class=\"praxis-actions-header\" [class.align-start]=\"getActionsHeaderAlign() === 'start'\" [class.align-center]=\"getActionsHeaderAlign() === 'center'\" [class.align-end]=\"getActionsHeaderAlign() === 'end'\">\n <div class=\"praxis-actions-header__content\" [matTooltip]=\"getActionsHeaderTooltip() || ''\" [matTooltipDisabled]=\"!getActionsHeaderTooltip()\">\n <mat-icon *ngIf=\"getActionsHeaderIcon() as hi\" [praxisIcon]=\"hi\"></mat-icon>\n <span class=\"label\" *ngIf=\"getActionsHeaderLabel() as hl\">{{ hl }}</span>\n </div>\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n class=\"praxis-actions-cell\"\n [class.dense]=\"dense\"\n [style.width]=\"config.actions?.row?.width\"\n >\n <div class=\"praxis-actions-cell__content\">\n <!-- A\u00E7\u00F5es inline -->\n <!-- Inline actions: icons mode -->\n <ng-container *ngIf=\"config.actions?.row?.display === 'icons' || !config.actions?.row?.display\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button\n mat-icon-button\n class=\"praxis-icon-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [matTooltip]=\"a.label || getActionId(a)\"\n matTooltipPosition=\"above\"\n matTooltipClass=\"praxis-tooltip\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\"\n >\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n </button>\n </ng-container>\n </ng-container>\n\n <!-- Inline actions: buttons mode (show label + icon) -->\n <ng-container *ngIf=\"config.actions?.row?.display === 'buttons'\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button\n mat-flat-button\n class=\"praxis-row-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\"\n >\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </ng-container>\n\n <!-- Menu de overflow -->\n <button\n mat-icon-button\n class=\"praxis-icon-btn praxis-more-btn\"\n *ngIf=\"hasOverflowRowActions(row)\"\n [matMenuTriggerFor]=\"rowMoreMenu\"\n [color]=\"getRowMenuButtonColor() || null\"\n aria-label=\"Mais a\u00E7\u00F5es\"\n >\n <mat-icon [praxisIcon]=\"getRowMenuIcon()\"></mat-icon>\n </button>\n <mat-menu #rowMoreMenu=\"matMenu\" xPosition=\"before\">\n <ng-container\n *ngFor=\"let a of getOverflowRowActions(row); trackBy: trackAction\"\n >\n <button\n mat-menu-item\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [disabled]=\"isActionDisabled(a, row)\"\n >\n <mat-icon [color]=\"a.color || null\" [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </mat-menu>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <ng-container *ngIf=\"!isVirtualized()\">\n <tr\n mat-row\n *matRowDef=\"let row; let i = index; columns: displayedColumns\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowNgStyle(row)\"\n [matTooltip]=\"getRowTooltip(row) || null\"\n [matTooltipDisabled]=\"!getRowTooltip(row)\"\n [matTooltipPosition]=\"getRowTooltipPosition(row)\"\n [matTooltipShowDelay]=\"getRowTooltipShowDelay(row)\"\n (click)=\"onRowClicked(row, i)\"\n (dblclick)=\"onRowDoubleClicked(row, i)\"\n ></tr>\n </ng-container>\n</table>\n\n<!-- Virtual rows path (header preserved above) -->\n<ng-container *ngIf=\"resourcePath && !schemaError && !dataError && isVirtualized()\">\n <cdk-virtual-scroll-viewport\n class=\"ptable-viewport\"\n [itemSize]=\"getVirtItemHeight()\"\n [minBufferPx]=\"getVirtBufferSize() * getVirtItemHeight()\"\n [maxBufferPx]=\"getVirtBufferSize() * getVirtItemHeight() * 2\"\n [style.minHeight]=\"getVirtMinHeightStyle()\"\n >\n <table class=\"mat-mdc-table mat-elevation-z8\" [style.width]=\"horizontalScroll === 'auto' ? 'max-content' : '100%'\">\n <tbody>\n <tr class=\"mat-mdc-row\"\n *cdkVirtualFor=\"let row of dataSource.data; let i = index; trackBy: trackByRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowNgStyle(row)\"\n [matTooltip]=\"getRowTooltip(row) || null\"\n [matTooltipDisabled]=\"!getRowTooltip(row)\"\n [matTooltipPosition]=\"getRowTooltipPosition(row)\"\n [matTooltipShowDelay]=\"getRowTooltipShowDelay(row)\"\n (click)=\"onRowClicked(row, i)\"\n (dblclick)=\"onRowDoubleClicked(row, i)\">\n <!-- Selection column -->\n <td class=\"mat-mdc-cell\" *ngIf=\"config.behavior?.selection?.enabled\">\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(row)\"\n [checked]=\"selection.isSelected(row)\">\n </mat-checkbox>\n </td>\n <!-- Data columns -->\n <td class=\"mat-mdc-cell\"\n *ngFor=\"let column of visibleColumns\"\n [style.text-align]=\"column.align\"\n [style.width]=\"column.width\"\n [attr.style]=\"column.style\"\n [ngClass]=\"getCellClasses(row, column)\"\n [ngStyle]=\"getCellNgStyle(row, column)\">\n <ng-container [ngSwitch]=\"getEffectiveRendererType(row, column)\">\n <ng-container *ngSwitchCase=\"'icon'\">\n <mat-icon [color]=\"getIconColor(row, column) || null\"\n [ngStyle]=\"getIconStyle(row, column)\"\n [attr.aria-label]=\"getIconAriaLabel(row, column) || null\">\n {{ getIconName(row, column) }}\n </mat-icon>\n </ng-container>\n <ng-container *ngSwitchCase=\"'image'\">\n <img class=\"pfx-cell-image\"\n [src]=\"getImageSrc(row, column)\"\n [attr.alt]=\"getImageAlt(row, column) || ''\"\n [attr.loading]=\"getImageLazy(row, column) ? 'lazy' : null\"\n [style.width.px]=\"getImageWidth(row, column)\"\n [style.height.px]=\"getImageHeight(row, column)\"\n [class.shape-rounded]=\"getImageShape(row, column) === 'rounded'\"\n [class.shape-circle]=\"getImageShape(row, column) === 'circle'\"\n [style.object-fit]=\"getImageFit(row, column)\" />\n </ng-container>\n <ng-container *ngSwitchCase=\"'badge'\">\n <span class=\"pfx-badge\" [ngClass]=\"getBadgeClasses(row, column)\">\n <mat-icon *ngIf=\"getBadgeIcon(row, column) as bi\" class=\"pfx-badge-icon\">{{ bi }}</mat-icon>\n <span class=\"pfx-badge-text\">{{ getBadgeText(row, column) }}</span>\n </span>\n </ng-container>\n <ng-container *ngSwitchDefault>\n {{ getCellValue(row, column) }}\n </ng-container>\n </ng-container>\n </td>\n\n <!-- Actions column -->\n <td class=\"mat-mdc-cell praxis-actions-cell\" *ngIf=\"config.actions?.row?.enabled\" [class.dense]=\"dense\" [style.width]=\"config.actions?.row?.width\">\n <div class=\"praxis-actions-cell__content\">\n <ng-container *ngIf=\"config.actions?.row?.display === 'icons' || !config.actions?.row?.display\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button mat-icon-button class=\"praxis-icon-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [matTooltip]=\"a.label || getActionId(a)\"\n matTooltipPosition=\"above\"\n matTooltipClass=\"praxis-tooltip\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\">\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n </button>\n </ng-container>\n </ng-container>\n <ng-container *ngIf=\"config.actions?.row?.display === 'buttons'\">\n <ng-container *ngFor=\"let a of getInlineRowActions(row); trackBy: trackAction\">\n <button mat-flat-button class=\"praxis-row-btn\"\n [disabled]=\"isActionDisabled(a, row)\"\n (click)=\"onRowAction(getActionId(a), row, $event)\"\n [attr.aria-label]=\"a.label || getActionId(a)\"\n [color]=\"a.color || null\">\n <mat-icon [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </ng-container>\n <button mat-icon-button class=\"praxis-icon-btn praxis-more-btn\"\n *ngIf=\"hasOverflowRowActions(row)\"\n [matMenuTriggerFor]=\"rowMoreMenuV\"\n [color]=\"getRowMenuButtonColor() || null\"\n aria-label=\"Mais a\u00E7\u00F5es\">\n <mat-icon [praxisIcon]=\"getRowMenuIcon()\"></mat-icon>\n </button>\n <mat-menu #rowMoreMenuV=\"matMenu\" xPosition=\"before\">\n <ng-container *ngFor=\"let a of getOverflowRowActions(row); trackBy: trackAction\">\n <button mat-menu-item (click)=\"onRowAction(getActionId(a), row, $event)\" [disabled]=\"isActionDisabled(a, row)\">\n <mat-icon [color]=\"a.color || null\" [praxisIcon]=\"a.icon\"></mat-icon>\n <span>{{ a.label || getActionId(a) }}</span>\n </button>\n </ng-container>\n </mat-menu>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cdk-virtual-scroll-viewport>\n</ng-container>\n\n</div>\n<!-- Paginadores (top/bottom) -->\n<mat-paginator\n *ngIf=\"resourcePath && !schemaError && !dataError && getPaginationEnabled() && (config.behavior?.pagination?.position === 'top' || config.behavior?.pagination?.position === 'both')\"\n [length]=\"getPaginationLength()\"\n [pageSize]=\"getPaginationPageSize()\"\n [pageSizeOptions]=\"getPaginationPageSizeOptions()\"\n [showFirstLastButtons]=\"getPaginationShowFirstLast()\"\n (page)=\"onPageChange($event)\"\n [class.compact]=\"config.behavior?.pagination?.style === 'compact'\"\n>\n</mat-paginator>\n\n<mat-paginator\n *ngIf=\"resourcePath && !schemaError && !dataError && getPaginationEnabled() && (config.behavior?.pagination?.position === 'bottom' || config.behavior?.pagination?.position === 'both' || !config.behavior?.pagination?.position)\"\n [length]=\"getPaginationLength()\"\n [pageSize]=\"getPaginationPageSize()\"\n [pageSizeOptions]=\"getPaginationPageSizeOptions()\"\n [showFirstLastButtons]=\"getPaginationShowFirstLast()\"\n (page)=\"onPageChange($event)\"\n [class.compact]=\"config.behavior?.pagination?.style === 'compact'\"\n>\n</mat-paginator>\n", styles: ["@charset \"UTF-8\";table{width:100%}.praxis-actions-cell{height:100%;padding-inline:12px;white-space:nowrap}:host.density-compact{--p-header-padding: 8px 12px;--p-actions-btn-size: 32px;--p-actions-icon-size: 18px}:host.density-comfortable{--p-header-padding: 12px 16px;--p-actions-btn-size: 40px;--p-actions-icon-size: 22px}:host.density-spacious{--p-header-padding: 16px 20px;--p-actions-btn-size: 44px;--p-actions-icon-size: 24px}:host.density-compact ::ng-deep .mat-mdc-cell{padding:8px 12px}:host.density-comfortable ::ng-deep .mat-mdc-cell{padding:12px 16px}:host.density-spacious ::ng-deep .mat-mdc-cell{padding:16px 20px}:host.density-compact .praxis-actions-cell{padding-inline:8px}:host.density-spacious .praxis-actions-cell{padding-inline:16px}.praxis-actions-cell__content{display:flex;align-items:center;justify-content:flex-end;gap:8px;width:100%}.praxis-actions-cell.dense .praxis-actions-cell__content{gap:6px}.praxis-icon-btn{width:var(--p-actions-btn-size, 40px);height:var(--p-actions-btn-size, 40px);border:0;background:transparent;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;cursor:pointer;--mat-icon-button-state-layer-size: var(--p-actions-btn-size, 40px)}.praxis-icon-btn:hover{background:var(--md-sys-color-surface-variant, rgba(255, 255, 255, .06))}.praxis-icon-btn:focus-visible{outline:2px solid var(--primary, #48a1ff);outline-offset:2px}.praxis-icon-btn mat-icon,.praxis-icon-btn .mat-icon{font-size:var(--p-actions-icon-size, 22px);width:var(--p-actions-icon-size, 22px);height:var(--p-actions-icon-size, 22px);line-height:var(--p-actions-icon-size, 22px)}.praxis-more-btn{width:var(--p-actions-more-btn-size, var(--p-actions-btn-size, 40px));height:var(--p-actions-more-btn-size, var(--p-actions-btn-size, 40px));--mat-icon-button-state-layer-size: var(--p-actions-more-btn-size, var(--p-actions-btn-size, 40px));background-image:var(--p-actions-more-btn-gradient, none);background-size:100% 100%;background-repeat:no-repeat}.praxis-more-btn mat-icon,.praxis-more-btn .mat-icon{font-size:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));width:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));height:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));line-height:var(--p-actions-more-icon-size, var(--p-actions-icon-size, 22px));color:var(--p-actions-more-icon-color);background-image:var(--p-actions-more-icon-gradient, none);-webkit-background-clip:text;background-clip:text}.praxis-icon-btn.destructive mat-icon{color:#ff6b6b}.mat-mdc-tooltip.praxis-tooltip{margin-top:-8px;margin-bottom:8px}.spacer{flex:1 1 auto}.praxis-table-header{display:flex;flex-wrap:wrap;align-items:flex-start;gap:8px;margin:16px 0 12px;width:100%;clear:both;position:relative}.praxis-table-header.debug-layout,:host.debug-layout .praxis-table-header{outline:2px dashed #ff4d4f;border-radius:8px}:host.debug-layout ::ng-deep .praxis-filter-bar{outline:1px dashed #f59e0b}:host.debug-layout ::ng-deep .quick-shell{outline:1px dashed #3b82f6}:host.debug-layout ::ng-deep .always-fields{outline:1px dashed #22c55e}:host.debug-layout ::ng-deep .praxis-filter-overlay .praxis-filter-advanced{outline:2px solid #a855f7}:host.debug-layout ::ng-deep .mat-mdc-table{outline:1px dashed #94a3b8}.praxis-table-header>praxis-table-toolbar{flex:1 0 100%}:host{--p-table-header-bg: var(--md-sys-color-surface-container-highest, #1d1d1f);--p-table-header-fg: var(--md-sys-color-on-surface, #e8f3f1);--p-table-border-color: var(--md-sys-color-outline-variant, rgba(255, 255, 255, .12));--p-table-row-even-bg: var(--md-sys-color-surface-container, rgba(255, 255, 255, .04));--p-table-row-hover-bg: color-mix(in srgb, var(--md-sys-color-primary, #3f51b5) 10%, transparent);--p-table-row-selected-bg: var(--md-sys-color-primary-container, rgba(63,81,181,.14));--p-table-badge-soft-primary-bg: color-mix(in srgb, var(--mat-sys-primary, var(--md-sys-color-primary)) 16%, transparent);--p-table-badge-soft-primary-fg: var(--mat-sys-primary, var(--md-sys-color-primary));--p-table-badge-soft-accent-bg: color-mix(in srgb, var(--mat-sys-secondary, var(--md-sys-color-secondary, #ff4081)) 14%, transparent);--p-table-badge-soft-accent-fg: var(--mat-sys-secondary, var(--md-sys-color-secondary, #ff4081));--p-table-badge-soft-warn-bg: color-mix(in srgb, var(--mat-sys-error, var(--md-sys-color-error, #f44336)) 14%, transparent);--p-table-badge-soft-warn-fg: var(--mat-sys-error, var(--md-sys-color-error, #f44336));--p-table-state-success-bg: color-mix(in srgb, var(--mat-sys-tertiary, var(--md-sys-color-tertiary, #388E3C)) 16%, transparent);--p-table-state-success-fg: var(--md-sys-color-on-surface, #c8e6c9);--p-table-state-warning-bg: color-mix(in srgb, var(--md-sys-color-secondary, #FFA000) 18%, transparent);--p-table-state-warning-fg: var(--md-sys-color-on-surface, #ffe0b2);--p-table-state-danger-bg: color-mix(in srgb, var(--md-sys-color-error, #e53935) 18%, transparent);--p-table-state-danger-fg: var(--md-sys-color-on-surface, #ffcdd2);--p-table-state-highlight-bg: color-mix(in srgb, var(--md-sys-color-primary, #2196f3) 16%, transparent);--p-table-state-highlight-fg: var(--md-sys-color-on-surface, #bbdefb)}:host ::ng-deep .mat-mdc-table{background:var(--md-sys-color-surface-container-highest, #1d1d1f);border-radius:12px;width:100%}:host [data-role=table-settings].mat-mdc-icon-button{--mdc-icon-button-icon-color: var(--mat-sys-primary, var(--md-sys-color-primary, #3f51b5));color:var(--mat-sys-primary, var(--md-sys-color-primary, #3f51b5))}.pfx-link{color:var(--mat-sys-primary, #3f51b5);text-decoration:underline;cursor:pointer}.pfx-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:12px;line-height:1;border:1px solid transparent}.pfx-chip-icon{font-size:14px;width:14px;height:14px}.pfx-chip--outlined{background:transparent;border-color:var(--mat-sys-outline-variant);color:var(--mat-sys-on-surface)}.pfx-chip--filled-primary{background:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}.pfx-chip--filled-accent{background:var(--mat-sys-secondary, #ff4081);color:#fff}.pfx-chip--filled-warn{background:var(--mat-sys-error, #f44336);color:#fff}.pfx-chip--soft-primary{background:var(--p-table-badge-soft-primary-bg);color:var(--p-table-badge-soft-primary-fg)}.pfx-chip--soft-accent{background:var(--p-table-badge-soft-accent-bg);color:var(--p-table-badge-soft-accent-fg)}.pfx-chip--soft-warn{background:var(--p-table-badge-soft-warn-bg);color:var(--p-table-badge-soft-warn-fg)}.pfx-progress{position:relative;width:100%;max-width:140px;height:8px;background:var(--mat-sys-surface-container-highest, rgba(255, 255, 255, .08));border-radius:4px;overflow:hidden;display:inline-block;vertical-align:middle}.pfx-progress-bar{height:100%;background:var(--mat-sys-primary);transition:width .2s ease}.pfx-progress-label{margin-left:8px;font-size:12px;opacity:.8;display:inline-block;vertical-align:middle}.pfx-avatar{display:inline-flex;align-items:center;justify-content:center;background:var(--mat-sys-surface-container, #2a2a2e);color:var(--mat-sys-on-surface, #eee);border-radius:4px;overflow:hidden;font-weight:600}.pfx-avatar.shape-rounded{border-radius:8px}.pfx-avatar.shape-circle{border-radius:999px}.pfx-avatar--initials{text-transform:uppercase;font-size:12px}.pfx-cell-compose{display:inline-flex;align-items:center;gap:6px}.pfx-cell-compose.dir-col{flex-direction:column;align-items:stretch}.pfx-cell-compose.align-start{justify-content:flex-start}.pfx-cell-compose.align-center{justify-content:center}.pfx-cell-compose.align-end{justify-content:flex-end}.pfx-cell-compose.wrap{flex-wrap:wrap}.pfx-cell-compose.ellipsis{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.px-scroll-viewport{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;scrollbar-gutter:stable}.px-scroll-viewport.scroll-none{overflow-x:visible}.px-scroll-viewport.scroll-auto ::ng-deep .mat-mdc-table{width:max-content;min-width:100%}.px-scroll-viewport.scroll-wrap ::ng-deep .mat-mdc-header-cell,.px-scroll-viewport.scroll-wrap ::ng-deep .mat-mdc-cell{white-space:normal;text-overflow:initial}.px-scroll-viewport.scroll-wrap ::ng-deep .mat-mdc-table{width:100%}:host ::ng-deep .mat-mdc-header-row{position:sticky;top:0;z-index:1;background:var(--p-table-header-bg);color:var(--p-table-header-fg);box-shadow:var(--p-table-header-shadow, 0 1px 0 var(--p-table-border-color));border-bottom:1px solid var(--p-table-border-color)}:host ::ng-deep .mat-mdc-header-cell,:host ::ng-deep .mat-sort-header-content,:host ::ng-deep .mat-mdc-header-row .mat-icon{color:var(--p-table-header-fg)!important;font-weight:600}:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow,:host ::ng-deep .mat-mdc-header-cell:hover .mat-sort-header-arrow{color:var(--p-table-header-fg)!important}:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-indicator,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-stem,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-pointer,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-pointer-left,:host ::ng-deep .mat-mdc-header-cell.mat-sort-header-sorted .mat-sort-header-arrow .mat-sort-header-pointer-right{border-color:var(--p-table-header-fg)!important;background:var(--p-table-header-fg)!important}:host ::ng-deep .mat-mdc-header-cell{padding:var(--p-header-padding, 12px 16px)!important;font-size:var(--p-header-font-size, inherit);font-weight:var(--p-header-font-weight, 600);letter-spacing:var(--p-header-letter-spacing, normal);text-transform:var(--p-header-text-transform, none);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.praxis-actions-header{text-align:right}.praxis-actions-header.align-start{text-align:left}.praxis-actions-header.align-center{text-align:center}.praxis-actions-header.align-end{text-align:right}.praxis-actions-header .praxis-actions-header__content{display:inline-flex;align-items:center;gap:var(--p-actions-header-gap, 6px);color:var(--p-actions-header-color, inherit)}.praxis-actions-header .praxis-actions-header__content .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}:host ::ng-deep .mat-mdc-header-cell .mat-sort-header-container{padding-right:20px}:host ::ng-deep .mat-mdc-cell{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}:host ::ng-deep .mat-mdc-row:hover{background:var(--p-table-row-hover-bg)}:host ::ng-deep .mat-mdc-row:nth-child(2n){background:var(--p-table-row-even-bg)}:host.row-borders ::ng-deep .mat-mdc-row .mat-mdc-cell{border-bottom:1px solid var(--p-table-border-color)}:host.row-borders ::ng-deep .mat-mdc-header-row .mat-mdc-header-cell{border-bottom:none}:host.col-borders ::ng-deep .mat-mdc-header-row .mat-mdc-header-cell,:host.col-borders ::ng-deep .mat-mdc-row .mat-mdc-cell{border-right:1px solid var(--p-table-border-color)}:host.col-borders ::ng-deep .mat-mdc-header-row .mat-mdc-header-cell:last-child,:host.col-borders ::ng-deep .mat-mdc-row .mat-mdc-cell:last-child{border-right:none}.ptable-info-banner{display:flex;gap:12px;align-items:center;padding:8px 12px;margin:8px 0;border-radius:8px;border:1px solid var(--mat-sys-primary, var(--md-sys-color-primary, #1a73e8));background:color-mix(in srgb,var(--mat-sys-primary, var(--md-sys-color-primary, #1a73e8)) 10%,transparent)}.ptable-info-banner .text{flex:1;font-weight:600}.ptable-info-banner .actions{display:flex;gap:8px}.pfx-cell-image{display:inline-block;vertical-align:middle;background:var(--md-sys-color-surface-variant, #2a2a2a);border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .08))}.pfx-cell-image.shape-rounded{border-radius:8px}.pfx-cell-image.shape-circle{border-radius:9999px}.pfx-badge{display:inline-flex;align-items:center;gap:6px;line-height:1;padding:4px 8px;border-radius:9999px;font-size:12px;font-weight:600;border:1px solid transparent}.pfx-badge .pfx-badge-icon{font-size:16px;width:16px;height:16px}.pfx-badge--filled-primary{background:var(--mat-sys-primary, #3f51b5);color:var(--mat-sys-on-primary, #fff)}.pfx-badge--filled-accent{background:var(--mat-sys-secondary, #ff4081);color:var(--mat-sys-on-secondary, #fff)}.pfx-badge--filled-warn{background:var(--mat-sys-error, #f44336);color:var(--mat-sys-on-error, #fff)}.pfx-badge--outlined{background:transparent;border-color:var(--mat-sys-outline-variant, rgba(255, 255, 255, .24));color:inherit}.pfx-badge--soft-primary{background:var(--p-table-badge-soft-primary-bg);color:var(--p-table-badge-soft-primary-fg)}.pfx-badge--soft-accent{background:var(--p-table-badge-soft-accent-bg);color:var(--p-table-badge-soft-accent-fg)}.pfx-badge--soft-warn{background:var(--p-table-badge-soft-warn-bg);color:var(--p-table-badge-soft-warn-fg)}.row--success,.row--success td,td.row--success{background-color:var(--p-table-state-success-bg)!important;color:var(--p-table-state-success-fg)!important}.row--warning,.row--warning td,td.row--warning{background-color:var(--p-table-state-warning-bg)!important;color:var(--p-table-state-warning-fg)!important}.row--danger,.row--danger td,td.row--danger{background-color:var(--p-table-state-danger-bg)!important;color:var(--p-table-state-danger-fg)!important}.row--highlight,.row--highlight td,td.row--highlight{background-color:var(--p-table-state-highlight-bg)!important;color:var(--p-table-state-highlight-fg)!important;font-weight:600}.row--muted,.row--muted td,td.row--muted{opacity:.7;filter:saturate(.6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i8.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i8.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i8.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i8.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i8.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i8.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i8.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i8.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i8.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i8.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i11$1.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i12.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i12.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i5.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i9.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i15.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i19.ɵɵCdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i19.ɵɵCdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i19.ɵɵCdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: PraxisTableToolbar, selector: "praxis-table-toolbar", inputs: ["config", "debugLayout"], outputs: ["toolbarAction", "reset"] }, { kind: "component", type: PraxisFilter, selector: "praxis-filter", inputs: ["resourcePath", "formId", "mode", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "editModeEnabled", "value", "quickField", "alwaysVisibleFields", "tags", "allowSaveTags", "persistenceKey", "i18n", "changeDebounceMs", "showFilterSettings", "summary", "summaryTemplate", "summaryMap", "autoSummary", "confirmTagDelete", "debugLayout", "placeBooleansInActions", "showToggleLabels", "alwaysMinWidth", "alwaysColsMd", "alwaysColsLg", "tagColor", "tagVariant", "tagButtonColor", "actionsButtonColor", "actionsVariant", "overlayVariant", "overlayBackdrop", "advancedOpenMode"], outputs: ["submit", "change", "clear", "modeChange", "requestSearch", "tagsChange", "metaChanged", "schemaStatusChange"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline"] }] });
24009
24066
  }
24010
24067
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTable, decorators: [{
24011
24068
  type: Component,
@@ -24087,6 +24144,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
24087
24144
  type: Output
24088
24145
  }], schemaStatusChange: [{
24089
24146
  type: Output
24147
+ }], metadataChange: [{
24148
+ type: Output
24090
24149
  }], beforeDelete: [{
24091
24150
  type: Output
24092
24151
  }], afterDelete: [{