cerxos-ui 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, EventEmitter, ElementRef, HostListener, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Attribute, forwardRef, Directive, ContentChildren, inject, ChangeDetectorRef, ContentChild, HostBinding, signal, Injectable, Pipe } from '@angular/core';
2
+ import { Component, EventEmitter, ElementRef, HostListener, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Attribute, inject, ChangeDetectorRef, forwardRef, Directive, ContentChildren, ContentChild, HostBinding, signal, Injectable, Pipe } from '@angular/core';
3
3
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
4
  import * as i1 from '@angular/common';
5
5
  import { NgTemplateOutlet, CommonModule } from '@angular/common';
@@ -975,6 +975,7 @@ const MARK_SIZE_CLASSES = {
975
975
  lg: 'after:h-3 after:w-2 before:h-0.5 before:w-4'
976
976
  };
977
977
  class CxsCheckboxComponent {
978
+ changeDetector = inject(ChangeDetectorRef);
978
979
  checked = false;
979
980
  indeterminate = false;
980
981
  disabled = false;
@@ -1020,6 +1021,7 @@ class CxsCheckboxComponent {
1020
1021
  }
1021
1022
  writeValue(value) {
1022
1023
  this.checked = value ?? false;
1024
+ this.changeDetector.markForCheck();
1023
1025
  }
1024
1026
  registerOnChange(fn) {
1025
1027
  this.onChange = fn;
@@ -1029,6 +1031,7 @@ class CxsCheckboxComponent {
1029
1031
  }
1030
1032
  setDisabledState(isDisabled) {
1031
1033
  this.disabledFromControl = isDisabled;
1034
+ this.changeDetector.markForCheck();
1032
1035
  }
1033
1036
  onCheckboxChange(event) {
1034
1037
  const target = event.target;
@@ -1247,6 +1250,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1247
1250
  args: ['cxsDataTableCell']
1248
1251
  }] } });
1249
1252
 
1253
+ /** Prefix of the browser storage entry that holds a table's rows-per-page choice. */
1254
+ const CXS_DATA_TABLE_PAGE_SIZE_STORAGE_PREFIX = 'cxs-data-table:page-size:';
1255
+ /**
1256
+ * Reads the rows-per-page choice saved under `key`.
1257
+ * Returns `fallback` when nothing is saved, the saved value is not one of `options`,
1258
+ * or browser storage is unavailable.
1259
+ */
1260
+ function readCxsDataTablePageSize(key, options, fallback) {
1261
+ try {
1262
+ const raw = getLocalStorage()?.getItem(CXS_DATA_TABLE_PAGE_SIZE_STORAGE_PREFIX + key);
1263
+ if (raw === null || raw === undefined) {
1264
+ return fallback;
1265
+ }
1266
+ const parsed = raw === 'all' ? 'all' : Number(raw);
1267
+ return options.includes(parsed) ? parsed : fallback;
1268
+ }
1269
+ catch {
1270
+ return fallback;
1271
+ }
1272
+ }
1273
+ /** Saves the rows-per-page choice under `key`. Storage failures are ignored. */
1274
+ function writeCxsDataTablePageSize(key, size) {
1275
+ try {
1276
+ getLocalStorage()?.setItem(CXS_DATA_TABLE_PAGE_SIZE_STORAGE_PREFIX + key, String(size));
1277
+ }
1278
+ catch {
1279
+ // Storage can be blocked or full; the table still works without remembering.
1280
+ }
1281
+ }
1282
+ function getLocalStorage() {
1283
+ return typeof localStorage === 'undefined' ? null : localStorage;
1284
+ }
1285
+
1250
1286
  const WRAPPER_BASE_CLASSES$1 = 'w-full overflow-x-auto';
1251
1287
  const WRAPPER_OVERFLOW_HIDDEN_CLASSES = 'overflow-y-hidden';
1252
1288
  const WRAPPER_OVERFLOW_VISIBLE_CLASSES = 'overflow-y-visible';
@@ -1267,7 +1303,7 @@ const HEADER_FILTER_INPUT_CLASSES = 'w-full rounded-[var(--cxs-radius-md)] borde
1267
1303
  'focus-visible:outline-offset-2 focus-visible:outline-[var(--cxs-color-focus)]';
1268
1304
  const BODY_ROW_BASE_CLASSES$1 = 'border-b border-[var(--cxs-color-border)] last:border-b-0';
1269
1305
  const BODY_ROW_STRIPED_CLASSES$1 = 'odd:bg-[var(--cxs-color-surface)] even:bg-[var(--cxs-color-surface-hover)]';
1270
- const BODY_CELL_BASE_CLASSES$1 = 'px-3 py-2 align-top';
1306
+ const BODY_CELL_BASE_CLASSES$1 = 'px-3 py-2 align-middle';
1271
1307
  const BODY_CELL_COMPACT_CLASSES$1 = 'px-2 py-1.5';
1272
1308
  const ALIGN_CLASSES$2 = {
1273
1309
  left: 'text-left',
@@ -1320,6 +1356,7 @@ class CxsDataTableComponent {
1320
1356
  pageSize = 10;
1321
1357
  pageIndex = 1;
1322
1358
  pageSizeOptions = [10, 20, 50];
1359
+ pageSizeStorageKey;
1323
1360
  showPagination = true;
1324
1361
  showPageSize = true;
1325
1362
  manualPagination = false;
@@ -1343,8 +1380,10 @@ class CxsDataTableComponent {
1343
1380
  selectionChange = new EventEmitter();
1344
1381
  bulkAction = new EventEmitter();
1345
1382
  cellTemplates;
1383
+ columnsPanel;
1384
+ columnsPanelSummary;
1346
1385
  currentPage = 1;
1347
- currentPageSize = 10;
1386
+ selectedPageSize = 10;
1348
1387
  globalFilter = '';
1349
1388
  columnFilters = {};
1350
1389
  columnVisibility = {};
@@ -1354,6 +1393,7 @@ class CxsDataTableComponent {
1354
1393
  pinnedOffsetsLeft = {};
1355
1394
  pinnedOffsetsRight = {};
1356
1395
  filtersOpen = true;
1396
+ columnsPanelOpen = false;
1357
1397
  resizeStartX = 0;
1358
1398
  resizeStartWidth = 0;
1359
1399
  constructor(hostClass) {
@@ -1364,7 +1404,10 @@ class CxsDataTableComponent {
1364
1404
  this.currentPage = Math.max(1, this.pageIndex);
1365
1405
  }
1366
1406
  if (changes['pageSize']) {
1367
- this.currentPageSize = Math.max(1, this.pageSize);
1407
+ this.selectedPageSize = this.pageSize === 0 ? 'all' : Math.max(1, this.pageSize);
1408
+ }
1409
+ if ((changes['pageSizeStorageKey'] || changes['pageSizeOptions']) && this.pageSizeStorageKey) {
1410
+ this.selectedPageSize = readCxsDataTablePageSize(this.pageSizeStorageKey, this.pageSizeOptions, this.selectedPageSize);
1368
1411
  }
1369
1412
  if (changes['columns']) {
1370
1413
  this.initializeVisibility();
@@ -1462,7 +1505,13 @@ class CxsDataTableComponent {
1462
1505
  return this.data.length;
1463
1506
  }
1464
1507
  get totalPages() {
1465
- return Math.max(1, Math.ceil(this.totalItems / this.currentPageSize));
1508
+ if (this.selectedPageSize === 'all') {
1509
+ return 1;
1510
+ }
1511
+ return Math.max(1, Math.ceil(this.totalItems / this.selectedPageSize));
1512
+ }
1513
+ get pageSizeValue() {
1514
+ return String(this.selectedPageSize);
1466
1515
  }
1467
1516
  get pageLabel() {
1468
1517
  return `Page ${this.currentPage} of ${this.totalPages}`;
@@ -1470,11 +1519,11 @@ class CxsDataTableComponent {
1470
1519
  get displayRows() {
1471
1520
  const filtered = this.manualFilter ? this.data : this.getFilteredData(this.data);
1472
1521
  const sorted = this.manualSort ? filtered : this.getSortedData(filtered);
1473
- if (this.manualPagination) {
1522
+ if (this.manualPagination || this.selectedPageSize === 'all') {
1474
1523
  return sorted;
1475
1524
  }
1476
- const start = (this.currentPage - 1) * this.currentPageSize;
1477
- return sorted.slice(start, start + this.currentPageSize);
1525
+ const start = (this.currentPage - 1) * this.selectedPageSize;
1526
+ return sorted.slice(start, start + this.selectedPageSize);
1478
1527
  }
1479
1528
  get visibleColumns() {
1480
1529
  return this.columns.filter((column) => this.columnVisibility[column.key] !== false);
@@ -1605,13 +1654,28 @@ class CxsDataTableComponent {
1605
1654
  previousPage() {
1606
1655
  this.goToPage(this.currentPage - 1);
1607
1656
  }
1657
+ pageSizeLabel(size) {
1658
+ return size === 'all' ? 'All' : String(size);
1659
+ }
1660
+ isPageSizeSelected(size) {
1661
+ return size === this.selectedPageSize;
1662
+ }
1608
1663
  onPageSizeChange(value) {
1609
- const nextSize = Number(value);
1610
- if (!Number.isFinite(nextSize) || nextSize <= 0) {
1611
- return;
1664
+ let nextSize;
1665
+ if (value === 'all') {
1666
+ nextSize = 'all';
1612
1667
  }
1613
- this.currentPageSize = nextSize;
1614
- this.pageSizeChange.emit(nextSize);
1668
+ else {
1669
+ nextSize = Number(value);
1670
+ if (!Number.isFinite(nextSize) || nextSize <= 0) {
1671
+ return;
1672
+ }
1673
+ }
1674
+ this.selectedPageSize = nextSize;
1675
+ if (this.pageSizeStorageKey) {
1676
+ writeCxsDataTablePageSize(this.pageSizeStorageKey, nextSize);
1677
+ }
1678
+ this.pageSizeChange.emit(nextSize === 'all' ? 0 : nextSize);
1615
1679
  this.goToPage(1);
1616
1680
  }
1617
1681
  onGlobalFilterChange(value) {
@@ -1627,6 +1691,27 @@ class CxsDataTableComponent {
1627
1691
  columnFilterValue(columnKey) {
1628
1692
  return this.columnFilters[columnKey] || '';
1629
1693
  }
1694
+ onColumnsPanelToggle(event) {
1695
+ this.columnsPanelOpen = event.target.open;
1696
+ }
1697
+ closeColumnsPanel(restoreFocus) {
1698
+ if (!this.columnsPanelOpen) {
1699
+ return;
1700
+ }
1701
+ this.columnsPanelOpen = false;
1702
+ if (restoreFocus) {
1703
+ this.columnsPanelSummary?.nativeElement.focus();
1704
+ }
1705
+ }
1706
+ onDocumentClick(event) {
1707
+ if (!this.columnsPanelOpen) {
1708
+ return;
1709
+ }
1710
+ const target = event.target;
1711
+ if (target && !this.columnsPanel?.nativeElement.contains(target)) {
1712
+ this.closeColumnsPanel(false);
1713
+ }
1714
+ }
1630
1715
  toggleColumnVisibility(columnKey) {
1631
1716
  this.columnVisibility[columnKey] = !this.columnVisibility[columnKey];
1632
1717
  this.updatePinnedOffsets();
@@ -1797,11 +1882,11 @@ class CxsDataTableComponent {
1797
1882
  return value;
1798
1883
  }
1799
1884
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: CxsDataTableComponent, deps: [{ token: 'class', attribute: true }], target: i0.ɵɵFactoryTarget.Component });
1800
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: CxsDataTableComponent, isStandalone: true, selector: "cxs-data-table", inputs: { columns: "columns", data: "data", caption: "caption", title: "title", subtitle: "subtitle", ariaLabel: "ariaLabel", striped: "striped", bordered: "bordered", compact: "compact", loading: "loading", emptyMessage: "emptyMessage", emptyCell: "emptyCell", allowOverflow: "allowOverflow", columnBorders: "columnBorders", pageSize: "pageSize", pageIndex: "pageIndex", pageSizeOptions: "pageSizeOptions", showPagination: "showPagination", showPageSize: "showPageSize", manualPagination: "manualPagination", total: "total", sortKey: "sortKey", sortDirection: "sortDirection", manualSort: "manualSort", showToolbar: "showToolbar", showFilters: "showFilters", showColumnVisibility: "showColumnVisibility", showGlobalSearch: "showGlobalSearch", manualFilter: "manualFilter", selectable: "selectable", rowKey: "rowKey", selectedKeys: "selectedKeys", bulkActions: "bulkActions" }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", filterChange: "filterChange", selectionChange: "selectionChange", bulkAction: "bulkAction" }, queries: [{ propertyName: "cellTemplates", predicate: CxsDataTableCellDirective }], usesOnChanges: true, ngImport: i0, template: "@if (hasTitleBlock) {\n <div [class]=\"titleBarClass\">\n <div class=\"min-w-0\">\n @if (title) {\n <div [class]=\"titleTextClass\">{{ title }}</div>\n }\n @if (subtitle) {\n <div [class]=\"subtitleTextClass\">{{ subtitle }}</div>\n }\n </div>\n @if (showToolbar) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputInlineClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <details class=\"relative\">\n <summary [class]=\"toolbarButtonClass\">Columns</summary>\n <div\n class=\"absolute right-0 mt-2 w-48 rounded-(--cxs-radius-md) border border-(--cxs-color-border) bg-(--cxs-color-surface) p-2 shadow-(--cxs-shadow-sm)\"\n >\n @for (column of columns; track column) {\n <label class=\"flex items-center gap-2 px-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n [checked]=\"columnVisibility[column.key]\"\n (change)=\"toggleColumnVisibility(column.key)\"\n />\n <span>{{ column.label }}</span>\n </label>\n }\n </div>\n </details>\n }\n </div>\n }\n </div>\n} @else if (showToolbar) {\n <div [class]=\"toolbarClass\">\n <div [class]=\"toolbarGroupClass\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n </div>\n <div [class]=\"toolbarGroupClass\">\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <details class=\"relative\">\n <summary [class]=\"toolbarButtonClass\">Columns</summary>\n <div\n class=\"absolute right-0 mt-2 w-48 rounded-(--cxs-radius-md) border border-(--cxs-color-border) bg-(--cxs-color-surface) p-2 shadow-(--cxs-shadow-sm)\"\n >\n @for (column of columns; track column) {\n <label class=\"flex items-center gap-2 px-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n [checked]=\"columnVisibility[column.key]\"\n (change)=\"toggleColumnVisibility(column.key)\"\n />\n <span>{{ column.label }}</span>\n </label>\n }\n </div>\n </details>\n }\n </div>\n </div>\n}\n\n@if (selectable && selectedCount > 0 && bulkActions.length > 0) {\n <div [class]=\"bulkBarClass\">\n <span>{{ selectedCount }} selected</span>\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (action of bulkActions; track action) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"runBulkAction(action.id)\">\n {{ action.label }}\n </button>\n }\n </div>\n </div>\n}\n\n<div [class]=\"wrapperClass\">\n <table [class]=\"tableClass\" [attr.aria-label]=\"ariaLabel\">\n @if (caption) {\n <caption class=\"sr-only\">{{ caption }}</caption>\n }\n <thead>\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\">\n <input\n type=\"checkbox\"\n [checked]=\"allVisibleSelected\"\n [indeterminate]=\"someVisibleSelected\"\n (change)=\"toggleSelectAll()\"\n aria-label=\"Select all rows\"\n />\n </th>\n }\n @for (column of visibleColumns; track column) {\n <th\n class=\"relative\"\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.width]=\"columnWidth(column)\"\n [style.minWidth]=\"columnMinWidth(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n >\n @if (column.sortable) {\n <button\n type=\"button\"\n [class]=\"headerButtonClass()\"\n (click)=\"onSort(column)\"\n >\n <span>{{ column.label }}</span>\n @if (sortKey === column.key) {\n @if (sortDirection === 'asc') {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18\"\n />\n </svg>\n } @else {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3\"\n />\n </svg>\n }\n }\n </button>\n } @else {\n {{ column.label }}\n }\n @if (column.resizable !== false) {\n <span\n [class]=\"resizeHandleClass\"\n (mousedown)=\"onResizeStart($event, column)\"\n ></span>\n }\n </th>\n }\n </tr>\n @if (showFilters && filtersOpen) {\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\"></th>\n }\n @for (column of visibleColumns; track column) {\n <th\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (column.filterable) {\n @if (column.filterType === 'select') {\n <select\n [class]=\"headerFilterInputClass()\"\n [value]=\"columnFilterValue(column.key)\"\n (change)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n >\n <option value=\"\">All</option>\n @for (option of column.filterOptions ?? []; track option) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n } @else {\n <input\n [class]=\"headerFilterInputClass()\"\n [attr.type]=\"column.filterType === 'number' ? 'number' : 'text'\"\n [value]=\"columnFilterValue(column.key)\"\n (input)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n />\n }\n }\n </th>\n }\n </tr>\n }\n </thead>\n <tbody>\n @if (loading) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">Loading...</span>\n </td>\n </tr>\n } @else if (displayRows.length === 0) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">{{ emptyMessage }}</span>\n </td>\n </tr>\n } @else {\n @for (row of displayRows; track row) {\n <tr [class]=\"bodyRowClass\">\n @if (selectable) {\n <td class=\"px-3 py-2\">\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRowSelection(row)\"\n aria-label=\"Select row\"\n />\n </td>\n }\n @for (column of visibleColumns; track column) {\n <td\n [class]=\"bodyCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (cellTemplate(column.key); as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, value: row[column.key], column }\"\n ></ng-container>\n } @else {\n {{ cellValue(row, column) }}\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n\n@if (showPagination) {\n <div [class]=\"paginationClass\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"previousPage()\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"nextPage()\"\n >\n Next\n </button>\n <span class=\"text-(--cxs-color-on-surface-muted)\">{{ pageLabel }}</span>\n </div>\n @if (showPageSize) {\n <label class=\"flex items-center gap-2 text-(--cxs-color-on-surface-muted)\">\n <span>Rows</span>\n <select\n #pageSizeSelect\n [class]=\"pageSelectClass\"\n [value]=\"currentPageSize\"\n (change)=\"onPageSizeChange(pageSizeSelect.value)\"\n >\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\">{{ size }}</option>\n }\n </select>\n </label>\n }\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1885
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: CxsDataTableComponent, isStandalone: true, selector: "cxs-data-table", inputs: { columns: "columns", data: "data", caption: "caption", title: "title", subtitle: "subtitle", ariaLabel: "ariaLabel", striped: "striped", bordered: "bordered", compact: "compact", loading: "loading", emptyMessage: "emptyMessage", emptyCell: "emptyCell", allowOverflow: "allowOverflow", columnBorders: "columnBorders", pageSize: "pageSize", pageIndex: "pageIndex", pageSizeOptions: "pageSizeOptions", pageSizeStorageKey: "pageSizeStorageKey", showPagination: "showPagination", showPageSize: "showPageSize", manualPagination: "manualPagination", total: "total", sortKey: "sortKey", sortDirection: "sortDirection", manualSort: "manualSort", showToolbar: "showToolbar", showFilters: "showFilters", showColumnVisibility: "showColumnVisibility", showGlobalSearch: "showGlobalSearch", manualFilter: "manualFilter", selectable: "selectable", rowKey: "rowKey", selectedKeys: "selectedKeys", bulkActions: "bulkActions" }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", filterChange: "filterChange", selectionChange: "selectionChange", bulkAction: "bulkAction" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, queries: [{ propertyName: "cellTemplates", predicate: CxsDataTableCellDirective }], viewQueries: [{ propertyName: "columnsPanel", first: true, predicate: ["columnsPanel"], descendants: true, read: ElementRef }, { propertyName: "columnsPanelSummary", first: true, predicate: ["columnsPanelSummary"], descendants: true, read: ElementRef }], usesOnChanges: true, ngImport: i0, template: "<ng-template #columnsPanelTemplate>\n <details\n #columnsPanel\n class=\"relative\"\n [open]=\"columnsPanelOpen\"\n (toggle)=\"onColumnsPanelToggle($event)\"\n (keydown.escape)=\"closeColumnsPanel(true)\"\n >\n <summary #columnsPanelSummary [class]=\"toolbarButtonClass\">Columns</summary>\n <div\n class=\"absolute right-0 z-40 mt-2 w-48 rounded-(--cxs-radius-md) border border-(--cxs-color-border) bg-(--cxs-color-surface) p-2 shadow-(--cxs-shadow-sm)\"\n >\n @for (column of columns; track column) {\n <label class=\"flex items-center gap-2 px-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n [checked]=\"columnVisibility[column.key]\"\n (change)=\"toggleColumnVisibility(column.key)\"\n />\n <span>{{ column.label }}</span>\n </label>\n }\n </div>\n </details>\n</ng-template>\n\n@if (hasTitleBlock) {\n <div [class]=\"titleBarClass\">\n <div class=\"min-w-0\">\n @if (title) {\n <div [class]=\"titleTextClass\">{{ title }}</div>\n }\n @if (subtitle) {\n <div [class]=\"subtitleTextClass\">{{ subtitle }}</div>\n }\n </div>\n @if (showToolbar) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputInlineClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <ng-container [ngTemplateOutlet]=\"columnsPanelTemplate\"></ng-container>\n }\n </div>\n }\n </div>\n} @else if (showToolbar) {\n <div [class]=\"toolbarClass\">\n <div [class]=\"toolbarGroupClass\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n </div>\n <div [class]=\"toolbarGroupClass\">\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <ng-container [ngTemplateOutlet]=\"columnsPanelTemplate\"></ng-container>\n }\n </div>\n </div>\n}\n\n@if (selectable && selectedCount > 0 && bulkActions.length > 0) {\n <div [class]=\"bulkBarClass\">\n <span>{{ selectedCount }} selected</span>\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (action of bulkActions; track action) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"runBulkAction(action.id)\">\n {{ action.label }}\n </button>\n }\n </div>\n </div>\n}\n\n<div [class]=\"wrapperClass\">\n <table [class]=\"tableClass\" [attr.aria-label]=\"ariaLabel\">\n @if (caption) {\n <caption class=\"sr-only\">{{ caption }}</caption>\n }\n <thead>\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\">\n <input\n type=\"checkbox\"\n [checked]=\"allVisibleSelected\"\n [indeterminate]=\"someVisibleSelected\"\n (change)=\"toggleSelectAll()\"\n aria-label=\"Select all rows\"\n />\n </th>\n }\n @for (column of visibleColumns; track column) {\n <th\n class=\"relative\"\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.width]=\"columnWidth(column)\"\n [style.minWidth]=\"columnMinWidth(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n >\n @if (column.sortable) {\n <button\n type=\"button\"\n [class]=\"headerButtonClass()\"\n (click)=\"onSort(column)\"\n >\n <span>{{ column.label }}</span>\n @if (sortKey === column.key) {\n @if (sortDirection === 'asc') {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18\"\n />\n </svg>\n } @else {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3\"\n />\n </svg>\n }\n }\n </button>\n } @else {\n {{ column.label }}\n }\n @if (column.resizable !== false) {\n <span\n [class]=\"resizeHandleClass\"\n (mousedown)=\"onResizeStart($event, column)\"\n ></span>\n }\n </th>\n }\n </tr>\n @if (showFilters && filtersOpen) {\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\"></th>\n }\n @for (column of visibleColumns; track column) {\n <th\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (column.filterable) {\n @if (column.filterType === 'select') {\n <select\n [class]=\"headerFilterInputClass()\"\n [value]=\"columnFilterValue(column.key)\"\n (change)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n >\n <option value=\"\">All</option>\n @for (option of column.filterOptions ?? []; track option) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n } @else {\n <input\n [class]=\"headerFilterInputClass()\"\n [attr.type]=\"column.filterType === 'number' ? 'number' : 'text'\"\n [value]=\"columnFilterValue(column.key)\"\n (input)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n />\n }\n }\n </th>\n }\n </tr>\n }\n </thead>\n <tbody>\n @if (loading) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">Loading...</span>\n </td>\n </tr>\n } @else if (displayRows.length === 0) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">{{ emptyMessage }}</span>\n </td>\n </tr>\n } @else {\n @for (row of displayRows; track row) {\n <tr [class]=\"bodyRowClass\">\n @if (selectable) {\n <td class=\"px-3 py-2\">\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRowSelection(row)\"\n aria-label=\"Select row\"\n />\n </td>\n }\n @for (column of visibleColumns; track column) {\n <td\n [class]=\"bodyCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (cellTemplate(column.key); as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, value: row[column.key], column }\"\n ></ng-container>\n } @else {\n {{ cellValue(row, column) }}\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n\n@if (showPagination) {\n <div [class]=\"paginationClass\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"previousPage()\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"nextPage()\"\n >\n Next\n </button>\n <span class=\"text-(--cxs-color-on-surface-muted)\">{{ pageLabel }}</span>\n </div>\n @if (showPageSize) {\n <label class=\"flex items-center gap-2 text-(--cxs-color-on-surface-muted)\">\n <span>Rows</span>\n <select\n #pageSizeSelect\n [class]=\"pageSelectClass\"\n [value]=\"pageSizeValue\"\n (change)=\"onPageSizeChange(pageSizeSelect.value)\"\n >\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\" [selected]=\"isPageSizeSelected(size)\">{{ pageSizeLabel(size) }}</option>\n }\n </select>\n </label>\n }\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1801
1886
  }
1802
1887
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: CxsDataTableComponent, decorators: [{
1803
1888
  type: Component,
1804
- args: [{ selector: 'cxs-data-table', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (hasTitleBlock) {\n <div [class]=\"titleBarClass\">\n <div class=\"min-w-0\">\n @if (title) {\n <div [class]=\"titleTextClass\">{{ title }}</div>\n }\n @if (subtitle) {\n <div [class]=\"subtitleTextClass\">{{ subtitle }}</div>\n }\n </div>\n @if (showToolbar) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputInlineClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <details class=\"relative\">\n <summary [class]=\"toolbarButtonClass\">Columns</summary>\n <div\n class=\"absolute right-0 mt-2 w-48 rounded-(--cxs-radius-md) border border-(--cxs-color-border) bg-(--cxs-color-surface) p-2 shadow-(--cxs-shadow-sm)\"\n >\n @for (column of columns; track column) {\n <label class=\"flex items-center gap-2 px-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n [checked]=\"columnVisibility[column.key]\"\n (change)=\"toggleColumnVisibility(column.key)\"\n />\n <span>{{ column.label }}</span>\n </label>\n }\n </div>\n </details>\n }\n </div>\n }\n </div>\n} @else if (showToolbar) {\n <div [class]=\"toolbarClass\">\n <div [class]=\"toolbarGroupClass\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n </div>\n <div [class]=\"toolbarGroupClass\">\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <details class=\"relative\">\n <summary [class]=\"toolbarButtonClass\">Columns</summary>\n <div\n class=\"absolute right-0 mt-2 w-48 rounded-(--cxs-radius-md) border border-(--cxs-color-border) bg-(--cxs-color-surface) p-2 shadow-(--cxs-shadow-sm)\"\n >\n @for (column of columns; track column) {\n <label class=\"flex items-center gap-2 px-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n [checked]=\"columnVisibility[column.key]\"\n (change)=\"toggleColumnVisibility(column.key)\"\n />\n <span>{{ column.label }}</span>\n </label>\n }\n </div>\n </details>\n }\n </div>\n </div>\n}\n\n@if (selectable && selectedCount > 0 && bulkActions.length > 0) {\n <div [class]=\"bulkBarClass\">\n <span>{{ selectedCount }} selected</span>\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (action of bulkActions; track action) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"runBulkAction(action.id)\">\n {{ action.label }}\n </button>\n }\n </div>\n </div>\n}\n\n<div [class]=\"wrapperClass\">\n <table [class]=\"tableClass\" [attr.aria-label]=\"ariaLabel\">\n @if (caption) {\n <caption class=\"sr-only\">{{ caption }}</caption>\n }\n <thead>\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\">\n <input\n type=\"checkbox\"\n [checked]=\"allVisibleSelected\"\n [indeterminate]=\"someVisibleSelected\"\n (change)=\"toggleSelectAll()\"\n aria-label=\"Select all rows\"\n />\n </th>\n }\n @for (column of visibleColumns; track column) {\n <th\n class=\"relative\"\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.width]=\"columnWidth(column)\"\n [style.minWidth]=\"columnMinWidth(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n >\n @if (column.sortable) {\n <button\n type=\"button\"\n [class]=\"headerButtonClass()\"\n (click)=\"onSort(column)\"\n >\n <span>{{ column.label }}</span>\n @if (sortKey === column.key) {\n @if (sortDirection === 'asc') {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18\"\n />\n </svg>\n } @else {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3\"\n />\n </svg>\n }\n }\n </button>\n } @else {\n {{ column.label }}\n }\n @if (column.resizable !== false) {\n <span\n [class]=\"resizeHandleClass\"\n (mousedown)=\"onResizeStart($event, column)\"\n ></span>\n }\n </th>\n }\n </tr>\n @if (showFilters && filtersOpen) {\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\"></th>\n }\n @for (column of visibleColumns; track column) {\n <th\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (column.filterable) {\n @if (column.filterType === 'select') {\n <select\n [class]=\"headerFilterInputClass()\"\n [value]=\"columnFilterValue(column.key)\"\n (change)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n >\n <option value=\"\">All</option>\n @for (option of column.filterOptions ?? []; track option) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n } @else {\n <input\n [class]=\"headerFilterInputClass()\"\n [attr.type]=\"column.filterType === 'number' ? 'number' : 'text'\"\n [value]=\"columnFilterValue(column.key)\"\n (input)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n />\n }\n }\n </th>\n }\n </tr>\n }\n </thead>\n <tbody>\n @if (loading) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">Loading...</span>\n </td>\n </tr>\n } @else if (displayRows.length === 0) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">{{ emptyMessage }}</span>\n </td>\n </tr>\n } @else {\n @for (row of displayRows; track row) {\n <tr [class]=\"bodyRowClass\">\n @if (selectable) {\n <td class=\"px-3 py-2\">\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRowSelection(row)\"\n aria-label=\"Select row\"\n />\n </td>\n }\n @for (column of visibleColumns; track column) {\n <td\n [class]=\"bodyCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (cellTemplate(column.key); as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, value: row[column.key], column }\"\n ></ng-container>\n } @else {\n {{ cellValue(row, column) }}\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n\n@if (showPagination) {\n <div [class]=\"paginationClass\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"previousPage()\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"nextPage()\"\n >\n Next\n </button>\n <span class=\"text-(--cxs-color-on-surface-muted)\">{{ pageLabel }}</span>\n </div>\n @if (showPageSize) {\n <label class=\"flex items-center gap-2 text-(--cxs-color-on-surface-muted)\">\n <span>Rows</span>\n <select\n #pageSizeSelect\n [class]=\"pageSelectClass\"\n [value]=\"currentPageSize\"\n (change)=\"onPageSizeChange(pageSizeSelect.value)\"\n >\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\">{{ size }}</option>\n }\n </select>\n </label>\n }\n </div>\n}\n" }]
1889
+ args: [{ selector: 'cxs-data-table', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #columnsPanelTemplate>\n <details\n #columnsPanel\n class=\"relative\"\n [open]=\"columnsPanelOpen\"\n (toggle)=\"onColumnsPanelToggle($event)\"\n (keydown.escape)=\"closeColumnsPanel(true)\"\n >\n <summary #columnsPanelSummary [class]=\"toolbarButtonClass\">Columns</summary>\n <div\n class=\"absolute right-0 z-40 mt-2 w-48 rounded-(--cxs-radius-md) border border-(--cxs-color-border) bg-(--cxs-color-surface) p-2 shadow-(--cxs-shadow-sm)\"\n >\n @for (column of columns; track column) {\n <label class=\"flex items-center gap-2 px-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n [checked]=\"columnVisibility[column.key]\"\n (change)=\"toggleColumnVisibility(column.key)\"\n />\n <span>{{ column.label }}</span>\n </label>\n }\n </div>\n </details>\n</ng-template>\n\n@if (hasTitleBlock) {\n <div [class]=\"titleBarClass\">\n <div class=\"min-w-0\">\n @if (title) {\n <div [class]=\"titleTextClass\">{{ title }}</div>\n }\n @if (subtitle) {\n <div [class]=\"subtitleTextClass\">{{ subtitle }}</div>\n }\n </div>\n @if (showToolbar) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputInlineClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <ng-container [ngTemplateOutlet]=\"columnsPanelTemplate\"></ng-container>\n }\n </div>\n }\n </div>\n} @else if (showToolbar) {\n <div [class]=\"toolbarClass\">\n <div [class]=\"toolbarGroupClass\">\n @if (showGlobalSearch) {\n <div [class]=\"toolbarInputWrapperClass\">\n <svg\n [class]=\"toolbarInputIconClass\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"m21 21-4.35-4.35m1.85-5.65a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z\"\n />\n </svg>\n <input\n type=\"search\"\n [class]=\"toolbarInputClass\"\n placeholder=\"Search...\"\n [value]=\"globalFilter\"\n (input)=\"onGlobalFilterChange($any($event.target).value)\"\n />\n </div>\n }\n </div>\n <div [class]=\"toolbarGroupClass\">\n @if (showFilters) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"filtersOpen = !filtersOpen\">\n Filters\n </button>\n }\n @if (showColumnVisibility) {\n <ng-container [ngTemplateOutlet]=\"columnsPanelTemplate\"></ng-container>\n }\n </div>\n </div>\n}\n\n@if (selectable && selectedCount > 0 && bulkActions.length > 0) {\n <div [class]=\"bulkBarClass\">\n <span>{{ selectedCount }} selected</span>\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (action of bulkActions; track action) {\n <button type=\"button\" [class]=\"toolbarButtonClass\" (click)=\"runBulkAction(action.id)\">\n {{ action.label }}\n </button>\n }\n </div>\n </div>\n}\n\n<div [class]=\"wrapperClass\">\n <table [class]=\"tableClass\" [attr.aria-label]=\"ariaLabel\">\n @if (caption) {\n <caption class=\"sr-only\">{{ caption }}</caption>\n }\n <thead>\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\">\n <input\n type=\"checkbox\"\n [checked]=\"allVisibleSelected\"\n [indeterminate]=\"someVisibleSelected\"\n (change)=\"toggleSelectAll()\"\n aria-label=\"Select all rows\"\n />\n </th>\n }\n @for (column of visibleColumns; track column) {\n <th\n class=\"relative\"\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.width]=\"columnWidth(column)\"\n [style.minWidth]=\"columnMinWidth(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n scope=\"col\"\n [attr.aria-sort]=\"ariaSort(column)\"\n >\n @if (column.sortable) {\n <button\n type=\"button\"\n [class]=\"headerButtonClass()\"\n (click)=\"onSort(column)\"\n >\n <span>{{ column.label }}</span>\n @if (sortKey === column.key) {\n @if (sortDirection === 'asc') {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18\"\n />\n </svg>\n } @else {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n class=\"h-4 w-4 text-(--cxs-color-on-surface-muted)\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3\"\n />\n </svg>\n }\n }\n </button>\n } @else {\n {{ column.label }}\n }\n @if (column.resizable !== false) {\n <span\n [class]=\"resizeHandleClass\"\n (mousedown)=\"onResizeStart($event, column)\"\n ></span>\n }\n </th>\n }\n </tr>\n @if (showFilters && filtersOpen) {\n <tr [class]=\"headerRowClass\">\n @if (selectable) {\n <th class=\"px-3 py-2 text-left\"></th>\n }\n @for (column of visibleColumns; track column) {\n <th\n [class]=\"headerCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (column.filterable) {\n @if (column.filterType === 'select') {\n <select\n [class]=\"headerFilterInputClass()\"\n [value]=\"columnFilterValue(column.key)\"\n (change)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n >\n <option value=\"\">All</option>\n @for (option of column.filterOptions ?? []; track option) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n } @else {\n <input\n [class]=\"headerFilterInputClass()\"\n [attr.type]=\"column.filterType === 'number' ? 'number' : 'text'\"\n [value]=\"columnFilterValue(column.key)\"\n (input)=\"onColumnFilterChange(column.key, $any($event.target).value)\"\n />\n }\n }\n </th>\n }\n </tr>\n }\n </thead>\n <tbody>\n @if (loading) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">Loading...</span>\n </td>\n </tr>\n } @else if (displayRows.length === 0) {\n <tr [class]=\"bodyRowClass\">\n <td [class]=\"bodyCellClass({ key: '', label: '' })\" [attr.colspan]=\"colSpan\">\n <span [class]=\"emptyRowClass\">{{ emptyMessage }}</span>\n </td>\n </tr>\n } @else {\n @for (row of displayRows; track row) {\n <tr [class]=\"bodyRowClass\">\n @if (selectable) {\n <td class=\"px-3 py-2\">\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRowSelection(row)\"\n aria-label=\"Select row\"\n />\n </td>\n }\n @for (column of visibleColumns; track column) {\n <td\n [class]=\"bodyCellClass(column) + ' ' + pinnedClass(column)\"\n [style.left]=\"pinnedLeft(column)\"\n [style.right]=\"pinnedRight(column)\"\n >\n @if (cellTemplate(column.key); as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, value: row[column.key], column }\"\n ></ng-container>\n } @else {\n {{ cellValue(row, column) }}\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n\n@if (showPagination) {\n <div [class]=\"paginationClass\">\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"previousPage()\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n [class]=\"pageButtonClass\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"nextPage()\"\n >\n Next\n </button>\n <span class=\"text-(--cxs-color-on-surface-muted)\">{{ pageLabel }}</span>\n </div>\n @if (showPageSize) {\n <label class=\"flex items-center gap-2 text-(--cxs-color-on-surface-muted)\">\n <span>Rows</span>\n <select\n #pageSizeSelect\n [class]=\"pageSelectClass\"\n [value]=\"pageSizeValue\"\n (change)=\"onPageSizeChange(pageSizeSelect.value)\"\n >\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\" [selected]=\"isPageSizeSelected(size)\">{{ pageSizeLabel(size) }}</option>\n }\n </select>\n </label>\n }\n </div>\n}\n" }]
1805
1890
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1806
1891
  type: Attribute,
1807
1892
  args: ['class']
@@ -1839,6 +1924,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1839
1924
  type: Input
1840
1925
  }], pageSizeOptions: [{
1841
1926
  type: Input
1927
+ }], pageSizeStorageKey: [{
1928
+ type: Input
1842
1929
  }], showPagination: [{
1843
1930
  type: Input
1844
1931
  }], showPageSize: [{
@@ -1886,6 +1973,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1886
1973
  }], cellTemplates: [{
1887
1974
  type: ContentChildren,
1888
1975
  args: [CxsDataTableCellDirective]
1976
+ }], columnsPanel: [{
1977
+ type: ViewChild,
1978
+ args: ['columnsPanel', { read: ElementRef }]
1979
+ }], columnsPanelSummary: [{
1980
+ type: ViewChild,
1981
+ args: ['columnsPanelSummary', { read: ElementRef }]
1982
+ }], onDocumentClick: [{
1983
+ type: HostListener,
1984
+ args: ['document:click', ['$event']]
1889
1985
  }] } });
1890
1986
 
1891
1987
  const OVERLAY_CLASSES = 'cxs-dialog-overlay fixed inset-0 z-50 flex items-center justify-center bg-[rgba(15,23,42,0.45)] p-4';
@@ -2075,6 +2171,7 @@ const DAY_TODAY = 'border border-[var(--cxs-color-primary)]';
2075
2171
  const DAY_SELECTED = 'bg-[var(--cxs-color-primary)] text-[var(--cxs-color-on-primary)]';
2076
2172
  const DAY_HOVER = 'hover:bg-[var(--cxs-color-surface-hover)]';
2077
2173
  class CxsDatepickerComponent {
2174
+ changeDetector = inject(ChangeDetectorRef);
2078
2175
  static nextId = 0;
2079
2176
  value = '';
2080
2177
  variant = 'outline';
@@ -2175,6 +2272,7 @@ class CxsDatepickerComponent {
2175
2272
  writeValue(value) {
2176
2273
  this.value = value ?? '';
2177
2274
  this.syncDisplayMonthWithValue();
2275
+ this.changeDetector.markForCheck();
2178
2276
  }
2179
2277
  registerOnChange(fn) {
2180
2278
  this.onChange = fn;
@@ -2184,6 +2282,7 @@ class CxsDatepickerComponent {
2184
2282
  }
2185
2283
  setDisabledState(isDisabled) {
2186
2284
  this.disabledFromControl = isDisabled;
2285
+ this.changeDetector.markForCheck();
2187
2286
  }
2188
2287
  onInput(event) {
2189
2288
  const target = event.target;
@@ -2862,6 +2961,7 @@ const ERROR_SIZE_CLASSES = {
2862
2961
  const INVALID_CLASSES$3 = 'border-[var(--cxs-color-danger)] focus-visible:border-[var(--cxs-color-danger)] ' +
2863
2962
  'focus-visible:outline-[var(--cxs-color-danger)]';
2864
2963
  class CxsInputComponent {
2964
+ changeDetector = inject(ChangeDetectorRef);
2865
2965
  static nextId = 0;
2866
2966
  value = '';
2867
2967
  type = 'text';
@@ -2944,6 +3044,7 @@ class CxsInputComponent {
2944
3044
  }
2945
3045
  writeValue(value) {
2946
3046
  this.value = value ?? '';
3047
+ this.changeDetector.markForCheck();
2947
3048
  }
2948
3049
  registerOnChange(fn) {
2949
3050
  this.onChange = fn;
@@ -2953,6 +3054,7 @@ class CxsInputComponent {
2953
3054
  }
2954
3055
  setDisabledState(isDisabled) {
2955
3056
  this.disabledFromControl = isDisabled;
3057
+ this.changeDetector.markForCheck();
2956
3058
  }
2957
3059
  onInput(event) {
2958
3060
  const target = event.target;
@@ -3205,6 +3307,7 @@ const INVALID_CLASSES$2 = 'border-[var(--cxs-color-danger)] focus-within:border-
3205
3307
  'focus-within:outline-[var(--cxs-color-danger)]';
3206
3308
  class CxsMultiSelectComponent {
3207
3309
  elementRef;
3310
+ changeDetector = inject(ChangeDetectorRef);
3208
3311
  static nextId = 0;
3209
3312
  options = [];
3210
3313
  value = [];
@@ -3321,6 +3424,7 @@ class CxsMultiSelectComponent {
3321
3424
  }
3322
3425
  writeValue(value) {
3323
3426
  this.value = value ? [...value] : [];
3427
+ this.changeDetector.markForCheck();
3324
3428
  }
3325
3429
  registerOnChange(fn) {
3326
3430
  this.onChange = fn;
@@ -3330,6 +3434,7 @@ class CxsMultiSelectComponent {
3330
3434
  }
3331
3435
  setDisabledState(isDisabled) {
3332
3436
  this.disabledFromControl = isDisabled;
3437
+ this.changeDetector.markForCheck();
3333
3438
  }
3334
3439
  focusInput() {
3335
3440
  if (this.isDisabled || this.readonly) {
@@ -3751,6 +3856,7 @@ const DOT_SIZE_CLASSES = {
3751
3856
  const INVALID_CLASSES$1 = 'border-[var(--cxs-color-danger)] peer-checked:border-[var(--cxs-color-danger)] ' +
3752
3857
  'after:bg-[var(--cxs-color-danger)]';
3753
3858
  class CxsRadioComponent {
3859
+ changeDetector = inject(ChangeDetectorRef);
3754
3860
  value = '';
3755
3861
  name;
3756
3862
  variant = 'primary';
@@ -3801,6 +3907,7 @@ class CxsRadioComponent {
3801
3907
  }
3802
3908
  writeValue(value) {
3803
3909
  this.selectedValue = value ?? null;
3910
+ this.changeDetector.markForCheck();
3804
3911
  }
3805
3912
  registerOnChange(fn) {
3806
3913
  this.onChange = fn;
@@ -3810,6 +3917,7 @@ class CxsRadioComponent {
3810
3917
  }
3811
3918
  setDisabledState(isDisabled) {
3812
3919
  this.disabledFromControl = isDisabled;
3920
+ this.changeDetector.markForCheck();
3813
3921
  }
3814
3922
  onRadioChange(event) {
3815
3923
  const target = event.target;
@@ -4002,6 +4110,7 @@ const LABEL_SIZE_CLASSES$1 = {
4002
4110
  const INVALID_CLASSES = 'border-[var(--cxs-color-danger)] focus-visible:border-[var(--cxs-color-danger)] ' +
4003
4111
  'focus-visible:outline-[var(--cxs-color-danger)]';
4004
4112
  class CxsSelectComponent {
4113
+ changeDetector = inject(ChangeDetectorRef);
4005
4114
  static nextId = 0;
4006
4115
  value = '';
4007
4116
  variant = 'outline';
@@ -4072,6 +4181,7 @@ class CxsSelectComponent {
4072
4181
  }
4073
4182
  writeValue(value) {
4074
4183
  this.value = value ?? '';
4184
+ this.changeDetector.markForCheck();
4075
4185
  }
4076
4186
  registerOnChange(fn) {
4077
4187
  this.onChange = fn;
@@ -4081,6 +4191,7 @@ class CxsSelectComponent {
4081
4191
  }
4082
4192
  setDisabledState(isDisabled) {
4083
4193
  this.disabledFromControl = isDisabled;
4194
+ this.changeDetector.markForCheck();
4084
4195
  }
4085
4196
  onSelectChange(event) {
4086
4197
  const target = event.target;
@@ -4241,6 +4352,7 @@ const INPUT_VARIANT_CLASSES = {
4241
4352
  danger: 'cxs-slider--danger'
4242
4353
  };
4243
4354
  class CxsSliderComponent {
4355
+ changeDetector = inject(ChangeDetectorRef);
4244
4356
  static nextId = 0;
4245
4357
  value = 0;
4246
4358
  min = 0;
@@ -4337,6 +4449,7 @@ class CxsSliderComponent {
4337
4449
  }
4338
4450
  writeValue(value) {
4339
4451
  this.value = value ?? this.normalizedMin;
4452
+ this.changeDetector.markForCheck();
4340
4453
  }
4341
4454
  registerOnChange(fn) {
4342
4455
  this.onChange = fn;
@@ -4346,6 +4459,7 @@ class CxsSliderComponent {
4346
4459
  }
4347
4460
  setDisabledState(isDisabled) {
4348
4461
  this.disabledFromControl = isDisabled;
4462
+ this.changeDetector.markForCheck();
4349
4463
  }
4350
4464
  onInput(event) {
4351
4465
  const target = event.target;
@@ -5179,5 +5293,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
5179
5293
  * Generated bundle index. Do not edit.
5180
5294
  */
5181
5295
 
5182
- export { CXS_USER_PREFERENCE_CHANGED_EVENT, CXS_USER_SESSION_STORAGE_KEY, CerxosUi, CxsAccordionComponent, CxsAccordionItemComponent, CxsActionMenuComponent, CxsAlertComponent, CxsAvatarComponent, CxsBadgeComponent, CxsBreadcrumbComponent, CxsButtonComponent, CxsCardComponent, CxsCarouselComponent, CxsCheckboxComponent, CxsCurrencyPipe, CxsDataTableCellDirective, CxsDataTableComponent, CxsDatePipe, CxsDatepickerComponent, CxsDialogComponent, CxsHabitGridComponent, CxsHoverCardComponent, CxsHoverCardContentDirective, CxsHoverCardTriggerDirective, CxsIconComponent, CxsInputComponent, CxsMenubarComponent, CxsMultiSelectComponent, CxsPreferenceFormattingService, CxsProgressBarComponent, CxsRadioChipGroupComponent, CxsRadioComponent, CxsSelectComponent, CxsSkeletonComponent, CxsSliderComponent, CxsTabLabelDirective, CxsTabPanelComponent, CxsTableComponent, CxsTabsComponent, CxsThemeDirective, CxsToastComponent, CxsToggleComponent, CxsTooltipComponent };
5296
+ export { CXS_DATA_TABLE_PAGE_SIZE_STORAGE_PREFIX, CXS_USER_PREFERENCE_CHANGED_EVENT, CXS_USER_SESSION_STORAGE_KEY, CerxosUi, CxsAccordionComponent, CxsAccordionItemComponent, CxsActionMenuComponent, CxsAlertComponent, CxsAvatarComponent, CxsBadgeComponent, CxsBreadcrumbComponent, CxsButtonComponent, CxsCardComponent, CxsCarouselComponent, CxsCheckboxComponent, CxsCurrencyPipe, CxsDataTableCellDirective, CxsDataTableComponent, CxsDatePipe, CxsDatepickerComponent, CxsDialogComponent, CxsHabitGridComponent, CxsHoverCardComponent, CxsHoverCardContentDirective, CxsHoverCardTriggerDirective, CxsIconComponent, CxsInputComponent, CxsMenubarComponent, CxsMultiSelectComponent, CxsPreferenceFormattingService, CxsProgressBarComponent, CxsRadioChipGroupComponent, CxsRadioComponent, CxsSelectComponent, CxsSkeletonComponent, CxsSliderComponent, CxsTabLabelDirective, CxsTabPanelComponent, CxsTableComponent, CxsTabsComponent, CxsThemeDirective, CxsToastComponent, CxsToggleComponent, CxsTooltipComponent, readCxsDataTablePageSize, writeCxsDataTablePageSize };
5183
5297
  //# sourceMappingURL=cerxos-ui.mjs.map