@openziti/ziti-console-lib 1.6.0 → 1.7.0

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.
@@ -1800,6 +1800,27 @@ class ListTableService {
1800
1800
  pageSizeKey() {
1801
1801
  return `lt_${this.tableId}_page_size`;
1802
1802
  }
1803
+ treeVariantKey() {
1804
+ return `lt_${this.tableId}_tree_variant`;
1805
+ }
1806
+ /** Returns the saved tree layout ('card' | 'flat'), or `fallback` if none. */
1807
+ restoreTreeVariant(fallback) {
1808
+ try {
1809
+ const raw = localStorage.getItem(this.treeVariantKey());
1810
+ return raw === 'card' || raw === 'flat' ? raw : fallback;
1811
+ }
1812
+ catch {
1813
+ return fallback;
1814
+ }
1815
+ }
1816
+ saveTreeVariant(variant) {
1817
+ try {
1818
+ localStorage.setItem(this.treeVariantKey(), variant);
1819
+ }
1820
+ catch {
1821
+ // ignore
1822
+ }
1823
+ }
1803
1824
  static pageSizeKeyFor(tableId) {
1804
1825
  return `lt_${tableId}_page_size`;
1805
1826
  }
@@ -1830,66 +1851,100 @@ class ListTableService {
1830
1851
  * and order applied. Columns not present in the saved state keep their declared
1831
1852
  * position (appended after the saved ones).
1832
1853
  */
1833
- applyPersistedState(columns) {
1834
- let cols = columns.map((c) => ({ ...c }));
1835
- // widths
1854
+ /** Parses a stored JSON value, or null when missing/unavailable/malformed. */
1855
+ readJson(key) {
1836
1856
  try {
1837
- const raw = localStorage.getItem(this.widthsKey());
1838
- if (raw) {
1839
- const widths = JSON.parse(raw) || {};
1840
- cols.forEach((c) => {
1841
- if (widths[c.key] != null) {
1842
- c.width = widths[c.key];
1843
- }
1844
- });
1845
- }
1857
+ const raw = localStorage.getItem(key);
1858
+ return raw ? JSON.parse(raw) : null;
1846
1859
  }
1847
1860
  catch {
1848
- // ignore malformed / unavailable storage
1861
+ return null;
1849
1862
  }
1850
- // order + visibility
1851
- try {
1852
- const raw = localStorage.getItem(this.stateKey());
1853
- if (raw) {
1854
- const state = JSON.parse(raw) || [];
1855
- const ordered = [];
1856
- state.forEach((s) => {
1857
- const found = cols.find((c) => c.key === s.colId);
1858
- if (found && !ordered.includes(found)) {
1859
- if (s.hide != null) {
1860
- found.hidden = s.hide;
1861
- }
1862
- if (s.width != null) {
1863
- found.width = s.width;
1864
- }
1865
- ordered.push(found);
1863
+ }
1864
+ /** Applies a widths map then a persisted order/visibility list onto a fresh copy of
1865
+ * `columns`. Shared by the top-level and per-depth restore paths. */
1866
+ mergePersistedColumns(columns, widths, state) {
1867
+ let cols = columns.map((c) => ({ ...c }));
1868
+ cols.forEach((c) => {
1869
+ if (widths[c.key] != null) {
1870
+ c.width = widths[c.key];
1871
+ }
1872
+ });
1873
+ if (state.length) {
1874
+ const ordered = [];
1875
+ state.forEach((s) => {
1876
+ const found = cols.find((c) => c.key === s.colId);
1877
+ if (found && !ordered.includes(found)) {
1878
+ if (s.hide != null) {
1879
+ found.hidden = s.hide;
1866
1880
  }
1867
- });
1868
- cols.forEach((c) => {
1869
- if (!ordered.includes(c)) {
1870
- ordered.push(c);
1881
+ if (s.width != null) {
1882
+ found.width = s.width;
1871
1883
  }
1872
- });
1873
- if (ordered.length) {
1874
- cols = ordered;
1884
+ ordered.push(found);
1875
1885
  }
1876
- }
1877
- }
1878
- catch {
1879
- // ignore malformed / unavailable storage
1886
+ });
1887
+ cols.forEach((c) => {
1888
+ if (!ordered.includes(c)) {
1889
+ ordered.push(c);
1890
+ }
1891
+ });
1892
+ cols = ordered;
1880
1893
  }
1881
1894
  return cols;
1882
1895
  }
1896
+ /** Order + visibility + width entries for one set of columns. */
1897
+ columnStateEntries(columns) {
1898
+ return columns.map((c) => ({ colId: c.key, hide: !!c.hidden, width: c.width }));
1899
+ }
1900
+ applyPersistedState(columns) {
1901
+ const widths = this.readJson(this.widthsKey()) || {};
1902
+ const state = this.readJson(this.stateKey()) || [];
1903
+ return this.mergePersistedColumns(columns, widths, state);
1904
+ }
1883
1905
  /** Persists the current order + visibility (and widths embedded in each entry). */
1884
1906
  saveState(columns) {
1885
- const state = columns.map((c) => ({ colId: c.key, hide: !!c.hidden, width: c.width }));
1886
1907
  try {
1887
- localStorage.setItem(this.stateKey(), JSON.stringify(state));
1908
+ localStorage.setItem(this.stateKey(), JSON.stringify(this.columnStateEntries(columns)));
1888
1909
  }
1889
1910
  catch {
1890
1911
  // ignore
1891
1912
  }
1892
1913
  }
1914
+ // ---- nested (per-depth) child columns: same contract as above, keyed by level ----
1915
+ childStateKey() {
1916
+ return `lt_${this.tableId}_child_state`;
1917
+ }
1918
+ childWidthsKey() {
1919
+ return `lt_${this.tableId}_child_widths`;
1920
+ }
1921
+ /** Applies persisted order/visibility/width for one nested depth to its columns. */
1922
+ applyPersistedChildState(level, columns) {
1923
+ const widths = (this.readJson(this.childWidthsKey()) || {})[level] || {};
1924
+ const state = (this.readJson(this.childStateKey()) || {})[level] || [];
1925
+ return this.mergePersistedColumns(columns, widths, state);
1926
+ }
1927
+ saveChildState(level, columns) {
1928
+ try {
1929
+ const byLevel = this.readJson(this.childStateKey()) || {};
1930
+ byLevel[level] = this.columnStateEntries(columns);
1931
+ localStorage.setItem(this.childStateKey(), JSON.stringify(byLevel));
1932
+ }
1933
+ catch {
1934
+ // ignore
1935
+ }
1936
+ }
1937
+ saveChildWidth(level, colId, width, columns) {
1938
+ try {
1939
+ const byLevel = this.readJson(this.childWidthsKey()) || {};
1940
+ byLevel[level] = { ...(byLevel[level] || {}), [colId]: width };
1941
+ localStorage.setItem(this.childWidthsKey(), JSON.stringify(byLevel));
1942
+ }
1943
+ catch {
1944
+ // ignore
1945
+ }
1946
+ this.saveChildState(level, columns);
1947
+ }
1893
1948
  /** Persists a single column's width, then rewrites the ordered state. */
1894
1949
  saveWidth(colId, width, columns) {
1895
1950
  try {
@@ -1909,6 +1964,9 @@ class ListTableService {
1909
1964
  localStorage.removeItem(this.stateKey());
1910
1965
  localStorage.removeItem(this.widthsKey());
1911
1966
  localStorage.removeItem(this.pageSizeKey());
1967
+ localStorage.removeItem(this.treeVariantKey());
1968
+ localStorage.removeItem(this.childStateKey());
1969
+ localStorage.removeItem(this.childWidthsKey());
1912
1970
  }
1913
1971
  catch {
1914
1972
  // ignore
@@ -6602,11 +6660,11 @@ class NameCellComponent {
6602
6660
  return val;
6603
6661
  }
6604
6662
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NameCellComponent, deps: [{ token: i4.Router }], target: i0.ɵɵFactoryTarget.Component }); }
6605
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: NameCellComponent, isStandalone: false, selector: "lib-name-cell", inputs: { row: "row", cellValue: "cellValue", cellConfig: "cellConfig" }, ngImport: i0, template: "<span class=\"cell-name-container\">\n <a href=\"./{{cellParams.pathRoot}}/{{item.id}}\" class=\"cell-name-link\" (click)=\"linkClicked($event)\">\n <div class=\"col cell-name-renderer\" data-id=\"{{item.id}}\">\n @if (cellParams.showIdentityIcons && item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.edgeRouterConnectionStatus}}\" title=\"Edge Router Connection Status\"></span>\n </div>\n }\n @if (cellParams.showIdentityIcons && !item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.hasApiSession}}\" title=\"Api Session\"></span>\n <span class=\"circle {{item.hasEdgeRouterConnection}}\" title=\"Edge Router Connected\"></span>\n </div>\n }\n @if (cellParams.showRouterIcons) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.isVerified}}\" title=\"{{item.isVerified ? 'Verified' : 'Unverified'}}\"></span>\n <span class=\"circle {{item.isOnline}}\" title=\"{{item.isOnline ? 'Online' : 'Offline'}}\"></span>\n </div>\n }\n @if (namePrefix) {\n <div class=\"cell-name-prefix-wrap\" [matTooltip]=\"namePrefix.tooltip\" [matTooltipShowDelay]=\"0\">\n <span class=\"cell-name-prefix-icon\" [ngClass]=\"namePrefix.iconClass\"></span>\n </div>\n }\n <strong>{{cellText}}</strong>\n </div>\n </a>\n</span>\n", styles: [".cell-name-renderer{display:flex;flex-direction:row;align-items:center;gap:.3125rem}.cell-name-container{display:flex;flex-direction:row;flex-wrap:nowrap}.cell-name-link{display:inline-flex;align-items:center}.cell-status-container{position:relative;display:flex;align-items:center;justify-content:center;gap:.25rem}.cell-name-prefix-wrap{display:flex;align-items:center;justify-content:center}.cell-name-prefix-icon{color:var(--highlighted);font-size:1rem}.circle.online{background-color:var(--green)}.circle.unknown{background-color:var(--highlighted)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
6663
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: NameCellComponent, isStandalone: false, selector: "lib-name-cell", inputs: { row: "row", cellValue: "cellValue", cellConfig: "cellConfig" }, ngImport: i0, template: "<span class=\"cell-name-container\">\n <a href=\"./{{cellParams.pathRoot}}/{{item.id}}\" class=\"cell-name-link\" (click)=\"linkClicked($event)\">\n <div class=\"col cell-name-renderer\" data-id=\"{{item.id}}\">\n @if (cellParams.showIdentityIcons && item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.edgeRouterConnectionStatus}}\" title=\"Edge Router Connection Status\"></span>\n </div>\n }\n @if (cellParams.showIdentityIcons && !item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.hasApiSession}}\" title=\"Api Session\"></span>\n <span class=\"circle {{item.hasEdgeRouterConnection}}\" title=\"Edge Router Connected\"></span>\n </div>\n }\n @if (cellParams.showRouterIcons) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.isVerified}}\" title=\"{{item.isVerified ? 'Verified' : 'Unverified'}}\"></span>\n <span class=\"circle {{item.isOnline}}\" title=\"{{item.isOnline ? 'Online' : 'Offline'}}\"></span>\n </div>\n }\n @if (namePrefix) {\n <div class=\"cell-name-prefix-wrap\" [matTooltip]=\"namePrefix.tooltip\" [matTooltipShowDelay]=\"0\">\n <span class=\"cell-name-prefix-icon\" [ngClass]=\"namePrefix.iconClass\"></span>\n </div>\n }\n <strong>{{cellText}}</strong>\n </div>\n </a>\n</span>\n", styles: [".cell-name-renderer{display:flex;flex-direction:row;align-items:center;gap:.3125rem}.cell-name-renderer strong{font-weight:600}.cell-name-container{display:flex;flex-direction:row;flex-wrap:nowrap}.cell-name-link{display:inline-flex;align-items:center}.cell-status-container{position:relative;display:flex;align-items:center;justify-content:center;gap:.25rem}.cell-name-prefix-wrap{display:flex;align-items:center;justify-content:center}.cell-name-prefix-icon{color:var(--lt-warning, #f5a93d);font-size:1rem}.circle.online{background-color:var(--lt-positive, #16a34a)}.circle.unknown{background-color:var(--lt-warning, #f5a93d)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
6606
6664
  }
6607
6665
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NameCellComponent, decorators: [{
6608
6666
  type: Component,
6609
- args: [{ selector: 'lib-name-cell', standalone: false, template: "<span class=\"cell-name-container\">\n <a href=\"./{{cellParams.pathRoot}}/{{item.id}}\" class=\"cell-name-link\" (click)=\"linkClicked($event)\">\n <div class=\"col cell-name-renderer\" data-id=\"{{item.id}}\">\n @if (cellParams.showIdentityIcons && item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.edgeRouterConnectionStatus}}\" title=\"Edge Router Connection Status\"></span>\n </div>\n }\n @if (cellParams.showIdentityIcons && !item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.hasApiSession}}\" title=\"Api Session\"></span>\n <span class=\"circle {{item.hasEdgeRouterConnection}}\" title=\"Edge Router Connected\"></span>\n </div>\n }\n @if (cellParams.showRouterIcons) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.isVerified}}\" title=\"{{item.isVerified ? 'Verified' : 'Unverified'}}\"></span>\n <span class=\"circle {{item.isOnline}}\" title=\"{{item.isOnline ? 'Online' : 'Offline'}}\"></span>\n </div>\n }\n @if (namePrefix) {\n <div class=\"cell-name-prefix-wrap\" [matTooltip]=\"namePrefix.tooltip\" [matTooltipShowDelay]=\"0\">\n <span class=\"cell-name-prefix-icon\" [ngClass]=\"namePrefix.iconClass\"></span>\n </div>\n }\n <strong>{{cellText}}</strong>\n </div>\n </a>\n</span>\n", styles: [".cell-name-renderer{display:flex;flex-direction:row;align-items:center;gap:.3125rem}.cell-name-container{display:flex;flex-direction:row;flex-wrap:nowrap}.cell-name-link{display:inline-flex;align-items:center}.cell-status-container{position:relative;display:flex;align-items:center;justify-content:center;gap:.25rem}.cell-name-prefix-wrap{display:flex;align-items:center;justify-content:center}.cell-name-prefix-icon{color:var(--highlighted);font-size:1rem}.circle.online{background-color:var(--green)}.circle.unknown{background-color:var(--highlighted)}\n"] }]
6667
+ args: [{ selector: 'lib-name-cell', standalone: false, template: "<span class=\"cell-name-container\">\n <a href=\"./{{cellParams.pathRoot}}/{{item.id}}\" class=\"cell-name-link\" (click)=\"linkClicked($event)\">\n <div class=\"col cell-name-renderer\" data-id=\"{{item.id}}\">\n @if (cellParams.showIdentityIcons && item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.edgeRouterConnectionStatus}}\" title=\"Edge Router Connection Status\"></span>\n </div>\n }\n @if (cellParams.showIdentityIcons && !item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.hasApiSession}}\" title=\"Api Session\"></span>\n <span class=\"circle {{item.hasEdgeRouterConnection}}\" title=\"Edge Router Connected\"></span>\n </div>\n }\n @if (cellParams.showRouterIcons) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.isVerified}}\" title=\"{{item.isVerified ? 'Verified' : 'Unverified'}}\"></span>\n <span class=\"circle {{item.isOnline}}\" title=\"{{item.isOnline ? 'Online' : 'Offline'}}\"></span>\n </div>\n }\n @if (namePrefix) {\n <div class=\"cell-name-prefix-wrap\" [matTooltip]=\"namePrefix.tooltip\" [matTooltipShowDelay]=\"0\">\n <span class=\"cell-name-prefix-icon\" [ngClass]=\"namePrefix.iconClass\"></span>\n </div>\n }\n <strong>{{cellText}}</strong>\n </div>\n </a>\n</span>\n", styles: [".cell-name-renderer{display:flex;flex-direction:row;align-items:center;gap:.3125rem}.cell-name-renderer strong{font-weight:600}.cell-name-container{display:flex;flex-direction:row;flex-wrap:nowrap}.cell-name-link{display:inline-flex;align-items:center}.cell-status-container{position:relative;display:flex;align-items:center;justify-content:center;gap:.25rem}.cell-name-prefix-wrap{display:flex;align-items:center;justify-content:center}.cell-name-prefix-icon{color:var(--lt-warning, #f5a93d);font-size:1rem}.circle.online{background-color:var(--lt-positive, #16a34a)}.circle.unknown{background-color:var(--lt-warning, #f5a93d)}\n"] }]
6610
6668
  }], ctorParameters: () => [{ type: i4.Router }], propDecorators: { row: [{
6611
6669
  type: Input
6612
6670
  }], cellValue: [{
@@ -6750,11 +6808,11 @@ class BooleanCellComponent {
6750
6808
  return this.value === true || this.value === 'true';
6751
6809
  }
6752
6810
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: BooleanCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6753
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: BooleanCellComponent, isStandalone: false, selector: "lib-boolean-cell", inputs: { value: "value", trueLabel: "trueLabel" }, ngImport: i0, template: "@if (isTrue) {\n <span class=\"bool bool-true\">\n <svg class=\"bool-check\" viewBox=\"0 0 14 14\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.5 7.6 5.7 10.8 11.5 3.6\"/>\n </svg>\n <span class=\"bool-label\">{{ trueLabel }}</span>\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.bool-true{display:inline-flex;align-items:center;gap:.3125rem;padding:.1875rem .5rem .1875rem .4375rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;line-height:1.2;color:#15803d;background:color-mix(in srgb,#16a34a 12%,transparent);border-radius:999px}.bool-check{flex:0 0 auto;display:block;color:#16a34a}\n"] }); }
6811
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: BooleanCellComponent, isStandalone: false, selector: "lib-boolean-cell", inputs: { value: "value", trueLabel: "trueLabel" }, ngImport: i0, template: "@if (isTrue) {\n <span class=\"bool bool-true\">\n <svg class=\"bool-check\" viewBox=\"0 0 14 14\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.5 7.6 5.7 10.8 11.5 3.6\"/>\n </svg>\n <span class=\"bool-label\">{{ trueLabel }}</span>\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.bool-true{display:inline-flex;align-items:center;gap:.3375rem;min-height:1.575rem;box-sizing:border-box;padding:.140625rem .5625rem .140625rem .421875rem;font-family:var(--lt-font);font-size:.73125rem;font-weight:500;line-height:1.2;color:var(--lt-positive-text, #166534);background:color-mix(in srgb,var(--lt-positive, #16a34a) 14%,transparent);border:1px solid color-mix(in srgb,var(--lt-positive, #16a34a) 42%,var(--background));border-radius:999px}.bool-check{flex:0 0 auto;display:block;width:.75rem;height:.75rem;color:var(--lt-positive, #16a34a)}\n"] }); }
6754
6812
  }
6755
6813
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: BooleanCellComponent, decorators: [{
6756
6814
  type: Component,
6757
- args: [{ selector: 'lib-boolean-cell', standalone: false, template: "@if (isTrue) {\n <span class=\"bool bool-true\">\n <svg class=\"bool-check\" viewBox=\"0 0 14 14\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.5 7.6 5.7 10.8 11.5 3.6\"/>\n </svg>\n <span class=\"bool-label\">{{ trueLabel }}</span>\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.bool-true{display:inline-flex;align-items:center;gap:.3125rem;padding:.1875rem .5rem .1875rem .4375rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;line-height:1.2;color:#15803d;background:color-mix(in srgb,#16a34a 12%,transparent);border-radius:999px}.bool-check{flex:0 0 auto;display:block;color:#16a34a}\n"] }]
6815
+ args: [{ selector: 'lib-boolean-cell', standalone: false, template: "@if (isTrue) {\n <span class=\"bool bool-true\">\n <svg class=\"bool-check\" viewBox=\"0 0 14 14\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.5 7.6 5.7 10.8 11.5 3.6\"/>\n </svg>\n <span class=\"bool-label\">{{ trueLabel }}</span>\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.bool-true{display:inline-flex;align-items:center;gap:.3375rem;min-height:1.575rem;box-sizing:border-box;padding:.140625rem .5625rem .140625rem .421875rem;font-family:var(--lt-font);font-size:.73125rem;font-weight:500;line-height:1.2;color:var(--lt-positive-text, #166534);background:color-mix(in srgb,var(--lt-positive, #16a34a) 14%,transparent);border:1px solid color-mix(in srgb,var(--lt-positive, #16a34a) 42%,var(--background));border-radius:999px}.bool-check{flex:0 0 auto;display:block;width:.75rem;height:.75rem;color:var(--lt-positive, #16a34a)}\n"] }]
6758
6816
  }], propDecorators: { value: [{
6759
6817
  type: Input
6760
6818
  }], trueLabel: [{
@@ -6777,7 +6835,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6777
6835
  limitations under the License.
6778
6836
  */
6779
6837
  /**
6780
- * Enrollment-token cell for the identities list. Shows a status chip (Available /
6838
+ * Enrollment-token cell for the identities list. Shows a status chip (Token /
6781
6839
  * Expired / —) that doubles as a dropdown of enrollment actions (Download JWT, Show
6782
6840
  * QR, Re-issue). Actions are emitted via the cell context's `emitAction`, so they run
6783
6841
  * through the same page handlers as the row ⋯ menu.
@@ -6795,7 +6853,14 @@ class TokenCellComponent {
6795
6853
  return this.tokenExpired ? 'expired' : 'available';
6796
6854
  }
6797
6855
  get statusLabel() {
6798
- return this.status === 'expired' ? 'Expired' : 'Available';
6856
+ return this.status === 'expired' ? 'Expired' : 'Token';
6857
+ }
6858
+ /** A cert-enrolled identity with no pending token can have its enrollment
6859
+ * re-issued. Reset enrollment acts on the cert authenticator, so this is gated
6860
+ * on cert specifically: password (updb) and external-IdP/JWT-signer identities
6861
+ * have no cert to reset and are left as the empty state. */
6862
+ get canReissue() {
6863
+ return this.status === 'none' && !!this.row?.authenticators?.cert?.id;
6799
6864
  }
6800
6865
  get actions() {
6801
6866
  if (this.status === 'available') {
@@ -6845,11 +6910,11 @@ class TokenCellComponent {
6845
6910
  return expiration ? moment$1(expiration).isBefore() : false;
6846
6911
  }
6847
6912
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6848
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TokenCellComponent, isStandalone: false, selector: "lib-token-cell", inputs: { row: "row", emitAction: "emitAction" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }], ngImport: i0, template: "@if (status !== 'none') {\n <button\n #trigger\n type=\"button\"\n class=\"token-chip {{ status }}\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"token-chip-label\">{{ statusLabel }}</span>\n <svg class=\"token-caret\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"token-menu\" [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px' }\" (clickOutside)=\"closeMenu()\">\n @for (a of actions; track a.action) {\n <button type=\"button\" class=\"token-menu-item\" (click)=\"runAction(a.action, $event)\">\n <span class=\"token-menu-icon {{ a.icon }}\" aria-hidden=\"true\"></span>\n <span class=\"token-menu-label\">{{ a.label }}</span>\n </button>\n }\n </div>\n }\n} @else {\n <span class=\"token-empty\">&#8212;</span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.token-chip{display:inline-flex;align-items:center;gap:.3125rem;height:1.5rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;border:none;border-radius:.75rem;cursor:pointer;white-space:nowrap;transition:filter .15s}.token-chip .token-caret{display:block;opacity:.75}.token-chip:hover,.token-chip.is-open{filter:brightness(.96)}.token-chip.available{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,var(--lt-control-bg))}.token-chip.expired{color:color-mix(in srgb,var(--highlighted) 62%,#000);background:color-mix(in srgb,var(--highlighted) 26%,var(--lt-control-bg))}.token-empty{color:var(--groupText)}.token-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:token-in .1s ease-out}@keyframes token-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.token-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.token-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.token-menu-item:hover .token-menu-icon{color:var(--lt-link)}.token-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
6913
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TokenCellComponent, isStandalone: false, selector: "lib-token-cell", inputs: { row: "row", emitAction: "emitAction" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }], ngImport: i0, template: "@if (status !== 'none') {\n <button\n #trigger\n type=\"button\"\n class=\"token-chip {{ status }}\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"token-chip-label\">{{ statusLabel }}</span>\n <svg class=\"token-caret\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M1 1.5 5 5 9 1.5\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"token-menu\" [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px' }\" (clickOutside)=\"closeMenu()\">\n @for (a of actions; track a.action) {\n <button type=\"button\" class=\"token-menu-item\" (click)=\"runAction(a.action, $event)\">\n <span class=\"token-menu-icon {{ a.icon }}\" aria-hidden=\"true\"></span>\n <span class=\"token-menu-label\">{{ a.label }}</span>\n </button>\n }\n </div>\n }\n} @else if (canReissue) {\n <button\n type=\"button\"\n class=\"token-chip reissue\"\n (click)=\"runAction('reset-enrollment', $event)\"\n aria-label=\"Reissue enrollment\"\n >\n <svg class=\"token-reissue-icon\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12.5 5A5 5 0 0 0 3.5 6.5\"/>\n <path d=\"M3.5 11A5 5 0 0 0 12.5 9.5\"/>\n <path d=\"M12.5 2.5V5H10\"/>\n <path d=\"M3.5 13.5V11H6\"/>\n </svg>\n <span class=\"token-chip-label\">Reissue</span>\n </button>\n} @else {\n <span class=\"token-empty\">&#8212;</span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.token-chip{display:inline-flex;align-items:center;gap:.3375rem;min-height:1.575rem;box-sizing:border-box;padding:.140625rem .5625rem;font-family:var(--lt-font);font-size:.73125rem;font-weight:500;line-height:1.2;border:1px solid transparent;border-radius:999px;cursor:pointer;white-space:nowrap;transition:filter .15s}.token-chip .token-caret{display:block;width:.5625rem;height:auto;opacity:.75}.token-chip:hover,.token-chip.is-open{filter:brightness(.96)}.token-chip.available{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 14%,var(--lt-control-bg));border-color:color-mix(in srgb,var(--lt-link) 42%,var(--background))}.token-chip.expired,.token-chip.reissue{color:var(--lt-warning-text, #92400e);background:color-mix(in srgb,var(--lt-warning, #f5a93d) 20%,var(--lt-control-bg));border-color:color-mix(in srgb,var(--lt-warning, #f5a93d) 55%,var(--background))}.token-reissue-icon{flex:0 0 auto;display:block;width:.75rem;height:.75rem}.token-empty{color:var(--groupText)}.token-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:token-in .1s ease-out}@keyframes token-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.token-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.token-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.token-menu-item:hover .token-menu-icon{color:var(--lt-link)}.token-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
6849
6914
  }
6850
6915
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenCellComponent, decorators: [{
6851
6916
  type: Component,
6852
- args: [{ selector: 'lib-token-cell', standalone: false, template: "@if (status !== 'none') {\n <button\n #trigger\n type=\"button\"\n class=\"token-chip {{ status }}\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"token-chip-label\">{{ statusLabel }}</span>\n <svg class=\"token-caret\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"token-menu\" [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px' }\" (clickOutside)=\"closeMenu()\">\n @for (a of actions; track a.action) {\n <button type=\"button\" class=\"token-menu-item\" (click)=\"runAction(a.action, $event)\">\n <span class=\"token-menu-icon {{ a.icon }}\" aria-hidden=\"true\"></span>\n <span class=\"token-menu-label\">{{ a.label }}</span>\n </button>\n }\n </div>\n }\n} @else {\n <span class=\"token-empty\">&#8212;</span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.token-chip{display:inline-flex;align-items:center;gap:.3125rem;height:1.5rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;border:none;border-radius:.75rem;cursor:pointer;white-space:nowrap;transition:filter .15s}.token-chip .token-caret{display:block;opacity:.75}.token-chip:hover,.token-chip.is-open{filter:brightness(.96)}.token-chip.available{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,var(--lt-control-bg))}.token-chip.expired{color:color-mix(in srgb,var(--highlighted) 62%,#000);background:color-mix(in srgb,var(--highlighted) 26%,var(--lt-control-bg))}.token-empty{color:var(--groupText)}.token-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:token-in .1s ease-out}@keyframes token-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.token-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.token-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.token-menu-item:hover .token-menu-icon{color:var(--lt-link)}.token-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}\n"] }]
6917
+ args: [{ selector: 'lib-token-cell', standalone: false, template: "@if (status !== 'none') {\n <button\n #trigger\n type=\"button\"\n class=\"token-chip {{ status }}\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"token-chip-label\">{{ statusLabel }}</span>\n <svg class=\"token-caret\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M1 1.5 5 5 9 1.5\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"token-menu\" [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px' }\" (clickOutside)=\"closeMenu()\">\n @for (a of actions; track a.action) {\n <button type=\"button\" class=\"token-menu-item\" (click)=\"runAction(a.action, $event)\">\n <span class=\"token-menu-icon {{ a.icon }}\" aria-hidden=\"true\"></span>\n <span class=\"token-menu-label\">{{ a.label }}</span>\n </button>\n }\n </div>\n }\n} @else if (canReissue) {\n <button\n type=\"button\"\n class=\"token-chip reissue\"\n (click)=\"runAction('reset-enrollment', $event)\"\n aria-label=\"Reissue enrollment\"\n >\n <svg class=\"token-reissue-icon\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12.5 5A5 5 0 0 0 3.5 6.5\"/>\n <path d=\"M3.5 11A5 5 0 0 0 12.5 9.5\"/>\n <path d=\"M12.5 2.5V5H10\"/>\n <path d=\"M3.5 13.5V11H6\"/>\n </svg>\n <span class=\"token-chip-label\">Reissue</span>\n </button>\n} @else {\n <span class=\"token-empty\">&#8212;</span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.token-chip{display:inline-flex;align-items:center;gap:.3375rem;min-height:1.575rem;box-sizing:border-box;padding:.140625rem .5625rem;font-family:var(--lt-font);font-size:.73125rem;font-weight:500;line-height:1.2;border:1px solid transparent;border-radius:999px;cursor:pointer;white-space:nowrap;transition:filter .15s}.token-chip .token-caret{display:block;width:.5625rem;height:auto;opacity:.75}.token-chip:hover,.token-chip.is-open{filter:brightness(.96)}.token-chip.available{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 14%,var(--lt-control-bg));border-color:color-mix(in srgb,var(--lt-link) 42%,var(--background))}.token-chip.expired,.token-chip.reissue{color:var(--lt-warning-text, #92400e);background:color-mix(in srgb,var(--lt-warning, #f5a93d) 20%,var(--lt-control-bg));border-color:color-mix(in srgb,var(--lt-warning, #f5a93d) 55%,var(--background))}.token-reissue-icon{flex:0 0 auto;display:block;width:.75rem;height:.75rem}.token-empty{color:var(--groupText)}.token-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:token-in .1s ease-out}@keyframes token-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.token-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.token-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.token-menu-item:hover .token-menu-icon{color:var(--lt-link)}.token-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}\n"] }]
6853
6918
  }], propDecorators: { row: [{
6854
6919
  type: Input
6855
6920
  }], emitAction: [{
@@ -6859,6 +6924,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6859
6924
  args: ['trigger']
6860
6925
  }] } });
6861
6926
 
6927
+ /*
6928
+ Copyright NetFoundry Inc.
6929
+
6930
+ Licensed under the Apache License, Version 2.0 (the "License");
6931
+ you may not use this file except in compliance with the License.
6932
+ You may obtain a copy of the License at
6933
+
6934
+ https://www.apache.org/licenses/LICENSE-2.0
6935
+
6936
+ Unless required by applicable law or agreed to in writing, software
6937
+ distributed under the License is distributed on an "AS IS" BASIS,
6938
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6939
+ See the License for the specific language governing permissions and
6940
+ limitations under the License.
6941
+ */
6942
+ /**
6943
+ * Role/attribute chips for list-table attribute columns. Each chip reveals two
6944
+ * controls on hover: copy (puts the bare name on the clipboard) and filter (adds
6945
+ * the attribute to this column's filter). Filtering goes through the shared
6946
+ * DataTableFilterService so it stacks with, and stays in sync with, the column
6947
+ * header's filter dropdown.
6948
+ */
6949
+ class AttributeCellComponent {
6950
+ set attributes(value) {
6951
+ this.chips = this.toChips(value);
6952
+ }
6953
+ constructor(filterService) {
6954
+ this.filterService = filterService;
6955
+ /** Column field; used as the filter's columnId so the filter icon drives the
6956
+ * same filter as the column header dropdown. */
6957
+ this.field = '';
6958
+ /** Column label, shown on the applied-filter tag. */
6959
+ this.filterLabel = '';
6960
+ this.chips = [];
6961
+ }
6962
+ toChips(value) {
6963
+ const list = Array.isArray(value) ? value : (value ? [value] : []);
6964
+ return list.reduce((chips, attr) => {
6965
+ const raw = String(attr?.name ?? attr ?? '').trim();
6966
+ if (raw) {
6967
+ const first = raw.charAt(0);
6968
+ const isNamed = first === '@';
6969
+ const bare = first === '#' || first === '@' ? raw.slice(1) : raw;
6970
+ const label = isNamed || first === '#' ? raw : '#' + raw;
6971
+ chips.push({ label, raw, bare, isNamed });
6972
+ }
6973
+ return chips;
6974
+ }, []);
6975
+ }
6976
+ async copy(chip, event) {
6977
+ event.stopPropagation();
6978
+ try {
6979
+ await navigator.clipboard.writeText(chip.bare);
6980
+ chip.copied = true;
6981
+ setTimeout(() => (chip.copied = false), 1200);
6982
+ }
6983
+ catch {
6984
+ // clipboard blocked (older browser or insecure context); nothing to do
6985
+ }
6986
+ }
6987
+ filter(chip, event) {
6988
+ event.stopPropagation();
6989
+ if (!this.field) {
6990
+ return;
6991
+ }
6992
+ const existing = this.filterService.filters.find((f) => f.columnId === this.field);
6993
+ const values = Array.isArray(existing?.value)
6994
+ ? [...existing.value]
6995
+ : (existing?.value ? [existing.value] : []);
6996
+ // filter on the source value (prefix intact) so a named @entity does not match a
6997
+ // role of the same bare name, and so it stacks with the header dropdown's values
6998
+ if (values.includes(chip.raw)) {
6999
+ return;
7000
+ }
7001
+ values.push(chip.raw);
7002
+ // keep whatever all-of/any-of the filter already uses so adding a chip does
7003
+ // not quietly change the match mode
7004
+ const semantic = existing?.semantic === 'AllOf' ? 'AllOf' : 'AnyOf';
7005
+ const filter = {
7006
+ filterName: this.filterLabel,
7007
+ columnId: this.field,
7008
+ value: values,
7009
+ label: values.join(', '),
7010
+ type: 'ATTRIBUTE',
7011
+ semantic,
7012
+ };
7013
+ this.filterService.updateFilter(filter);
7014
+ }
7015
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AttributeCellComponent, deps: [{ token: DataTableFilterService }], target: i0.ɵɵFactoryTarget.Component }); }
7016
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: AttributeCellComponent, isStandalone: false, selector: "lib-attribute-cell", inputs: { attributes: "attributes", field: "field", filterLabel: "filterLabel" }, ngImport: i0, template: "@for (chip of chips; track chip.raw) {\n <span class=\"lt-chip\" [class.lt-chip-at]=\"chip.isNamed\">\n <span class=\"lt-chip-label\">{{ chip.label }}</span>\n <span class=\"lt-chip-actions\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"lt-chip-action\"\n [class.is-done]=\"chip.copied\"\n (click)=\"copy(chip, $event)\"\n [attr.aria-label]=\"'Copy ' + chip.bare\"\n title=\"Copy\"\n >\n @if (chip.copied) {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3.5 8.5 7 12l5.5-7\"/>\n </svg>\n } @else {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1.3\"/>\n <path d=\"M10.5 5.5V3.3A1.3 1.3 0 0 0 9.2 2H3.3A1.3 1.3 0 0 0 2 3.3v5.9A1.3 1.3 0 0 0 3.3 10.5h2.2\"/>\n </svg>\n }\n </button>\n <button\n type=\"button\"\n class=\"lt-chip-action\"\n (click)=\"filter(chip, $event)\"\n [attr.aria-label]=\"'Filter by ' + chip.bare\"\n title=\"Filter\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M2 3.5h12l-4.6 5.4v3.6l-2.8 1.4V8.9z\"/>\n </svg>\n </button>\n </span>\n </span>\n}\n", styles: [":host{display:inline}.lt-chip{position:relative;--lt-chip-cover: color-mix(in srgb, var(--lt-link) 16%, var(--lt-row-bg, #f6f7fb))}.lt-chip.lt-chip-at{--lt-chip-cover: color-mix(in srgb, var(--secondary) 18%, var(--lt-row-bg, #f6f7fb))}.lt-chip-actions{position:absolute;top:0;right:0;bottom:0;display:flex;align-items:center;gap:.1875rem;padding-left:.875rem;padding-right:.3125rem;border-radius:inherit;opacity:0;pointer-events:none;transition:opacity .12s ease;background:linear-gradient(to right,transparent,var(--lt-chip-cover) 38%)}.lt-chip:hover .lt-chip-actions,.lt-chip:focus-within .lt-chip-actions{opacity:1;pointer-events:auto}.lt-chip-action{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;padding:0;color:var(--lt-link);background:var(--lt-control-bg);border:1px solid color-mix(in srgb,var(--lt-link) 40%,transparent);border-radius:.3125rem;cursor:pointer;transition:color .12s,background .12s,border-color .12s}.lt-chip-action svg{display:block;width:.875rem;height:.875rem}.lt-chip-action:hover{color:var(--lt-control-bg);background:var(--lt-link);border-color:var(--lt-link)}.lt-chip-action.is-done{color:var(--lt-positive, #16a34a);border-color:color-mix(in srgb,var(--lt-positive, #16a34a) 45%,transparent)}\n"] }); }
7017
+ }
7018
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AttributeCellComponent, decorators: [{
7019
+ type: Component,
7020
+ args: [{ selector: 'lib-attribute-cell', standalone: false, template: "@for (chip of chips; track chip.raw) {\n <span class=\"lt-chip\" [class.lt-chip-at]=\"chip.isNamed\">\n <span class=\"lt-chip-label\">{{ chip.label }}</span>\n <span class=\"lt-chip-actions\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"lt-chip-action\"\n [class.is-done]=\"chip.copied\"\n (click)=\"copy(chip, $event)\"\n [attr.aria-label]=\"'Copy ' + chip.bare\"\n title=\"Copy\"\n >\n @if (chip.copied) {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3.5 8.5 7 12l5.5-7\"/>\n </svg>\n } @else {\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1.3\"/>\n <path d=\"M10.5 5.5V3.3A1.3 1.3 0 0 0 9.2 2H3.3A1.3 1.3 0 0 0 2 3.3v5.9A1.3 1.3 0 0 0 3.3 10.5h2.2\"/>\n </svg>\n }\n </button>\n <button\n type=\"button\"\n class=\"lt-chip-action\"\n (click)=\"filter(chip, $event)\"\n [attr.aria-label]=\"'Filter by ' + chip.bare\"\n title=\"Filter\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M2 3.5h12l-4.6 5.4v3.6l-2.8 1.4V8.9z\"/>\n </svg>\n </button>\n </span>\n </span>\n}\n", styles: [":host{display:inline}.lt-chip{position:relative;--lt-chip-cover: color-mix(in srgb, var(--lt-link) 16%, var(--lt-row-bg, #f6f7fb))}.lt-chip.lt-chip-at{--lt-chip-cover: color-mix(in srgb, var(--secondary) 18%, var(--lt-row-bg, #f6f7fb))}.lt-chip-actions{position:absolute;top:0;right:0;bottom:0;display:flex;align-items:center;gap:.1875rem;padding-left:.875rem;padding-right:.3125rem;border-radius:inherit;opacity:0;pointer-events:none;transition:opacity .12s ease;background:linear-gradient(to right,transparent,var(--lt-chip-cover) 38%)}.lt-chip:hover .lt-chip-actions,.lt-chip:focus-within .lt-chip-actions{opacity:1;pointer-events:auto}.lt-chip-action{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;padding:0;color:var(--lt-link);background:var(--lt-control-bg);border:1px solid color-mix(in srgb,var(--lt-link) 40%,transparent);border-radius:.3125rem;cursor:pointer;transition:color .12s,background .12s,border-color .12s}.lt-chip-action svg{display:block;width:.875rem;height:.875rem}.lt-chip-action:hover{color:var(--lt-control-bg);background:var(--lt-link);border-color:var(--lt-link)}.lt-chip-action.is-done{color:var(--lt-positive, #16a34a);border-color:color-mix(in srgb,var(--lt-positive, #16a34a) 45%,transparent)}\n"] }]
7021
+ }], ctorParameters: () => [{ type: DataTableFilterService }], propDecorators: { attributes: [{
7022
+ type: Input
7023
+ }], field: [{
7024
+ type: Input
7025
+ }], filterLabel: [{
7026
+ type: Input
7027
+ }] } });
7028
+
6862
7029
  /*
6863
7030
  Copyright NetFoundry Inc.
6864
7031
 
@@ -7253,27 +7420,6 @@ class IdentitiesPageService extends ListPageServiceClass {
7253
7420
  : 'lt-type-other';
7254
7421
  return `<span class="lt-type-badge ${cls}">${t}</span>`;
7255
7422
  };
7256
- // Light skinned role chips (the legacy rolesRenderer paints
7257
- // heavy dark overlap chips; this stays scoped to the beta page).
7258
- const rolesChipRenderer = (row) => {
7259
- const colId = row?.column?.colId;
7260
- const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
7261
- if (!attrs?.length) {
7262
- return '';
7263
- }
7264
- return attrs.map((attr) => {
7265
- const raw = String(attr?.name ?? attr ?? '').trim();
7266
- if (!raw) {
7267
- return '';
7268
- }
7269
- // named attributes (@) use the secondary color; role attributes (#)
7270
- // the primary color, matching the tag selector's distinction
7271
- const isNamed = raw.charAt(0) === '@';
7272
- const label = isNamed ? raw : (raw.charAt(0) === '#' ? raw : '#' + raw);
7273
- const cls = isNamed ? 'lt-chip lt-chip-at' : 'lt-chip';
7274
- return `<span class="${cls}">${label}</span>`;
7275
- }).join('');
7276
- };
7277
7423
  let tableColumns = [
7278
7424
  {
7279
7425
  key: 'name',
@@ -7316,7 +7462,12 @@ class IdentitiesPageService extends ListPageServiceClass {
7316
7462
  label: 'Roles',
7317
7463
  filterType: 'ATTRIBUTE',
7318
7464
  headerParams: identityRolesHeaderComponentParams,
7319
- cellHtml: rolesChipRenderer,
7465
+ cell: AttributeCellComponent,
7466
+ cellInputs: (ctx) => ({
7467
+ attributes: ctx.row?.roleAttributesDisplay ?? ctx.row?.roleAttributes,
7468
+ field: ctx.colId,
7469
+ filterLabel: 'Roles',
7470
+ }),
7320
7471
  onCellClick: (ctx) => {
7321
7472
  if (this.hasSelectedText()) {
7322
7473
  return;
@@ -7401,7 +7552,7 @@ class IdentitiesPageService extends ListPageServiceClass {
7401
7552
  {
7402
7553
  key: 'token',
7403
7554
  field: 'token',
7404
- label: 'Token',
7555
+ label: 'Enrollment',
7405
7556
  cell: TokenCellComponent,
7406
7557
  cellInputs: (ctx) => ({ row: ctx.row, emitAction: ctx.emitAction }),
7407
7558
  resizable: true,
@@ -10169,6 +10320,11 @@ class ListTableHeaderComponent {
10169
10320
  this.actionClicked = new EventEmitter();
10170
10321
  this.tabClicked = new EventEmitter();
10171
10322
  }
10323
+ /** Only show the tab strip when there is more than one visible tab; a lone tab
10324
+ * is just a redundant label, so the strip is hidden. */
10325
+ get showTabs() {
10326
+ return (this.tabs || []).filter((t) => !t?.hidden).length > 1;
10327
+ }
10172
10328
  clickAction(value) {
10173
10329
  this.actionClicked.emit(value);
10174
10330
  }
@@ -10210,11 +10366,11 @@ class ListTableHeaderComponent {
10210
10366
  return active === tab?.id || active === tab?.value || active === tab?.url || active === tab?.label;
10211
10367
  }
10212
10368
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10213
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableHeaderComponent, isStandalone: false, selector: "lib-list-table-header", inputs: { title: "title", subtitle: "subtitle", createLabel: "createLabel", infoText: "infoText", tabs: "tabs", showAdd: "showAdd", blockCreate: "blockCreate", blockDelete: "blockDelete", tabsUseRouter: "tabsUseRouter", activeTab: "activeTab" }, outputs: { actionClicked: "actionClicked", tabClicked: "tabClicked" }, ngImport: i0, template: "<div class=\"lth\">\n <div class=\"lth-main\">\n <div class=\"lth-icon\" aria-hidden=\"true\">\n <ng-content select=\"[header-icon]\"></ng-content>\n </div>\n <div class=\"lth-titles\">\n <div class=\"lth-title-row\">\n <h1 class=\"lth-title\">{{ title }}</h1>\n @if (infoText) {\n <span class=\"lth-info\" [title]=\"infoText\" tabindex=\"0\" role=\"img\" [attr.aria-label]=\"infoText\">\n <span class=\"lth-info-badge\" aria-hidden=\"true\">i</span>\n </span>\n }\n </div>\n @if (subtitle) {\n <p class=\"lth-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <div class=\"lth-actions\">\n @if (showAdd && !blockCreate) {\n <button type=\"button\" class=\"lth-create\" (click)=\"clickAction('add')\" data-action=\"add\" id=\"CreateButton\">\n <span class=\"lth-create-plus\" aria-hidden=\"true\">+</span>\n <span>{{ createLabel }}</span>\n </button>\n }\n @if (!showAdd && !blockDelete) {\n <button type=\"button\" class=\"lth-delete\" (click)=\"clickAction('delete')\" data-action=\"delete\" id=\"DeleteButton\">\n <span class=\"lth-delete-icon icon-minus\" aria-hidden=\"true\"></span>\n <span>Delete</span>\n </button>\n }\n </div>\n </div>\n @if (tabs?.length) {\n <div class=\"lth-tabs\">\n @for (tab of tabs; track tab) {\n <a\n class=\"lth-tab\"\n [ngClass]=\"{ selected: tabSelected(tab) }\"\n [routerLink]=\"tabsUseRouter ? tab.url : null\"\n [hidden]=\"tab.hidden\"\n [attr.role]=\"tabsUseRouter ? null : 'button'\"\n [attr.tabindex]=\"tabsUseRouter ? null : 0\"\n (click)=\"onTabClick(tab)\"\n (keydown.enter)=\"tabsUseRouter ? null : onTabClick(tab)\"\n >{{ tab.label }}</a>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.lth{display:flex;flex-direction:column;gap:.75rem;padding-bottom:.5rem}.lth-main{display:flex;align-items:center;gap:.875rem}.lth-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:.75rem;color:var(--primary);background:color-mix(in srgb,var(--primary) 12%,var(--background));border:1px solid color-mix(in srgb,var(--primary) 22%,var(--background))}.lth-icon svg,.lth-icon img{display:block;width:1.5rem;height:1.5rem}:host ::ng-deep .lth-icon [class^=icon-],:host ::ng-deep .lth-icon [class*=\" icon-\"]{font-size:1.5rem;line-height:1}:host ::ng-deep .lth-icon [class^=icon-]:before,:host ::ng-deep .lth-icon [class*=\" icon-\"]:before{color:var(--primary)}.lth-titles{flex:1 1 auto;min-width:0}.lth-title-row{display:flex;align-items:center;gap:.5rem}.lth-title{margin:0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:1.5rem;font-weight:700;line-height:1.15;color:var(--tableText, var(--text))}.lth-info{display:inline-flex;align-items:center;justify-content:center;padding:.25rem;background:var(--background);border:1px solid var(--stroke);border-radius:.375rem;box-shadow:0 1px 2px #0000000f;cursor:help}.lth-info:hover{border-color:var(--groupText)}.lth-info-badge{display:inline-flex;align-items:center;justify-content:center;width:.9375rem;height:.9375rem;font-size:.625rem;font-weight:700;font-style:italic;font-family:Georgia,Times New Roman,serif;line-height:1;color:#fff;background:var(--dark);border-radius:50%}.lth-subtitle{margin:.125rem 0 0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;color:var(--groupText)}.lth-actions{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}.lth-create{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:#fff;background:var(--primary);border:none;border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:filter .15s}.lth-create:hover{filter:brightness(1.06)}.lth-create-plus{font-size:1.125rem;line-height:1}.lth-delete{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--redText, #9c0027);background:transparent;border:1px solid var(--redText, #9c0027);border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:background .15s}.lth-delete:hover{background:color-mix(in srgb,var(--redText, #9c0027) 8%,transparent)}.lth-tabs{display:flex;align-items:center;gap:1.25rem;border-bottom:1px solid var(--stroke)}.lth-tab{padding:.4375rem .125rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--groupText);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;transition:color .15s,border-color .15s}.lth-tab:hover{color:var(--text)}.lth-tab.selected{color:var(--primary);border-bottom-color:var(--primary)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
10369
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableHeaderComponent, isStandalone: false, selector: "lib-list-table-header", inputs: { title: "title", subtitle: "subtitle", createLabel: "createLabel", infoText: "infoText", tabs: "tabs", showAdd: "showAdd", blockCreate: "blockCreate", blockDelete: "blockDelete", tabsUseRouter: "tabsUseRouter", activeTab: "activeTab" }, outputs: { actionClicked: "actionClicked", tabClicked: "tabClicked" }, ngImport: i0, template: "<div class=\"lth\">\n <div class=\"lth-main\">\n <div class=\"lth-icon\" aria-hidden=\"true\">\n <ng-content select=\"[header-icon]\"></ng-content>\n </div>\n <div class=\"lth-titles\">\n <div class=\"lth-title-row\">\n <h1 class=\"lth-title\">{{ title }}</h1>\n @if (infoText) {\n <span class=\"lth-info\" [title]=\"infoText\" tabindex=\"0\" role=\"img\" [attr.aria-label]=\"infoText\">\n <span class=\"lth-info-badge\" aria-hidden=\"true\">i</span>\n </span>\n }\n </div>\n @if (subtitle) {\n <p class=\"lth-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <div class=\"lth-actions\">\n @if (showAdd && !blockCreate) {\n <button type=\"button\" class=\"lth-create\" (click)=\"clickAction('add')\" data-action=\"add\" id=\"CreateButton\">\n <span class=\"lth-create-plus\" aria-hidden=\"true\">+</span>\n <span>{{ createLabel }}</span>\n </button>\n }\n @if (!showAdd && !blockDelete) {\n <button type=\"button\" class=\"lth-delete\" (click)=\"clickAction('delete')\" data-action=\"delete\" id=\"DeleteButton\">\n <span class=\"lth-delete-icon icon-minus\" aria-hidden=\"true\"></span>\n <span>Delete</span>\n </button>\n }\n </div>\n </div>\n @if (showTabs) {\n <div class=\"lth-tabs\">\n @for (tab of tabs; track tab) {\n <a\n class=\"lth-tab\"\n [ngClass]=\"{ selected: tabSelected(tab) }\"\n [routerLink]=\"tabsUseRouter ? tab.url : null\"\n [hidden]=\"tab.hidden\"\n [attr.role]=\"tabsUseRouter ? null : 'button'\"\n [attr.tabindex]=\"tabsUseRouter ? null : 0\"\n (click)=\"onTabClick(tab)\"\n (keydown.enter)=\"tabsUseRouter ? null : onTabClick(tab)\"\n >{{ tab.label }}</a>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.lth{display:flex;flex-direction:column;gap:.875rem;padding-bottom:.625rem}.lth-main{display:flex;align-items:center;gap:.875rem}.lth-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:.75rem;color:var(--lt-header-icon, var(--primary));background:color-mix(in srgb,var(--lt-header-icon, var(--primary)) 12%,var(--background));border:1px solid color-mix(in srgb,var(--lt-header-icon, var(--primary)) 22%,var(--background))}.lth-icon svg,.lth-icon img{display:block;width:1.5rem;height:1.5rem}:host ::ng-deep .lth-icon [class^=icon-],:host ::ng-deep .lth-icon [class*=\" icon-\"]{font-size:1.5rem;line-height:1}:host ::ng-deep .lth-icon [class^=icon-]:before,:host ::ng-deep .lth-icon [class*=\" icon-\"]:before{color:var(--lt-header-icon, var(--primary))}.lth-titles{flex:1 1 auto;min-width:0}.lth-title-row{display:flex;align-items:center;gap:.5rem}.lth-title{margin:0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:1.5rem;font-weight:700;line-height:1.15;color:var(--tableText, var(--text))}.lth-info{display:inline-flex;align-items:center;justify-content:center;padding:.25rem;background:var(--background);border:1px solid var(--stroke);border-radius:.375rem;box-shadow:0 1px 2px #0000000f;cursor:help}.lth-info:hover{border-color:var(--groupText)}.lth-info-badge{display:inline-flex;align-items:center;justify-content:center;width:.9375rem;height:.9375rem;font-size:.625rem;font-weight:700;font-style:italic;font-family:Georgia,Times New Roman,serif;line-height:1;color:#fff;background:var(--dark);border-radius:50%}.lth-subtitle{margin:.125rem 0 0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;color:var(--groupText)}.lth-actions{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}.lth-create{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:#fff;background:var(--primary);border:none;border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:filter .15s}.lth-create:hover{filter:brightness(1.06)}.lth-create-plus{font-size:1.125rem;line-height:1}.lth-delete{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--redText, #9c0027);background:transparent;border:1px solid var(--redText, #9c0027);border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:background .15s}.lth-delete:hover{background:color-mix(in srgb,var(--redText, #9c0027) 8%,transparent)}.lth-tabs{display:flex;align-items:center;gap:1.25rem;border-bottom:1px solid var(--stroke)}.lth-tab{padding:.4375rem .125rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--groupText);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;transition:color .15s,border-color .15s}.lth-tab:hover{color:var(--text)}.lth-tab.selected{color:var(--primary);border-bottom-color:var(--primary)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
10214
10370
  }
10215
10371
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableHeaderComponent, decorators: [{
10216
10372
  type: Component,
10217
- args: [{ selector: 'lib-list-table-header', standalone: false, template: "<div class=\"lth\">\n <div class=\"lth-main\">\n <div class=\"lth-icon\" aria-hidden=\"true\">\n <ng-content select=\"[header-icon]\"></ng-content>\n </div>\n <div class=\"lth-titles\">\n <div class=\"lth-title-row\">\n <h1 class=\"lth-title\">{{ title }}</h1>\n @if (infoText) {\n <span class=\"lth-info\" [title]=\"infoText\" tabindex=\"0\" role=\"img\" [attr.aria-label]=\"infoText\">\n <span class=\"lth-info-badge\" aria-hidden=\"true\">i</span>\n </span>\n }\n </div>\n @if (subtitle) {\n <p class=\"lth-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <div class=\"lth-actions\">\n @if (showAdd && !blockCreate) {\n <button type=\"button\" class=\"lth-create\" (click)=\"clickAction('add')\" data-action=\"add\" id=\"CreateButton\">\n <span class=\"lth-create-plus\" aria-hidden=\"true\">+</span>\n <span>{{ createLabel }}</span>\n </button>\n }\n @if (!showAdd && !blockDelete) {\n <button type=\"button\" class=\"lth-delete\" (click)=\"clickAction('delete')\" data-action=\"delete\" id=\"DeleteButton\">\n <span class=\"lth-delete-icon icon-minus\" aria-hidden=\"true\"></span>\n <span>Delete</span>\n </button>\n }\n </div>\n </div>\n @if (tabs?.length) {\n <div class=\"lth-tabs\">\n @for (tab of tabs; track tab) {\n <a\n class=\"lth-tab\"\n [ngClass]=\"{ selected: tabSelected(tab) }\"\n [routerLink]=\"tabsUseRouter ? tab.url : null\"\n [hidden]=\"tab.hidden\"\n [attr.role]=\"tabsUseRouter ? null : 'button'\"\n [attr.tabindex]=\"tabsUseRouter ? null : 0\"\n (click)=\"onTabClick(tab)\"\n (keydown.enter)=\"tabsUseRouter ? null : onTabClick(tab)\"\n >{{ tab.label }}</a>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.lth{display:flex;flex-direction:column;gap:.75rem;padding-bottom:.5rem}.lth-main{display:flex;align-items:center;gap:.875rem}.lth-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:.75rem;color:var(--primary);background:color-mix(in srgb,var(--primary) 12%,var(--background));border:1px solid color-mix(in srgb,var(--primary) 22%,var(--background))}.lth-icon svg,.lth-icon img{display:block;width:1.5rem;height:1.5rem}:host ::ng-deep .lth-icon [class^=icon-],:host ::ng-deep .lth-icon [class*=\" icon-\"]{font-size:1.5rem;line-height:1}:host ::ng-deep .lth-icon [class^=icon-]:before,:host ::ng-deep .lth-icon [class*=\" icon-\"]:before{color:var(--primary)}.lth-titles{flex:1 1 auto;min-width:0}.lth-title-row{display:flex;align-items:center;gap:.5rem}.lth-title{margin:0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:1.5rem;font-weight:700;line-height:1.15;color:var(--tableText, var(--text))}.lth-info{display:inline-flex;align-items:center;justify-content:center;padding:.25rem;background:var(--background);border:1px solid var(--stroke);border-radius:.375rem;box-shadow:0 1px 2px #0000000f;cursor:help}.lth-info:hover{border-color:var(--groupText)}.lth-info-badge{display:inline-flex;align-items:center;justify-content:center;width:.9375rem;height:.9375rem;font-size:.625rem;font-weight:700;font-style:italic;font-family:Georgia,Times New Roman,serif;line-height:1;color:#fff;background:var(--dark);border-radius:50%}.lth-subtitle{margin:.125rem 0 0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;color:var(--groupText)}.lth-actions{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}.lth-create{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:#fff;background:var(--primary);border:none;border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:filter .15s}.lth-create:hover{filter:brightness(1.06)}.lth-create-plus{font-size:1.125rem;line-height:1}.lth-delete{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--redText, #9c0027);background:transparent;border:1px solid var(--redText, #9c0027);border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:background .15s}.lth-delete:hover{background:color-mix(in srgb,var(--redText, #9c0027) 8%,transparent)}.lth-tabs{display:flex;align-items:center;gap:1.25rem;border-bottom:1px solid var(--stroke)}.lth-tab{padding:.4375rem .125rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--groupText);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;transition:color .15s,border-color .15s}.lth-tab:hover{color:var(--text)}.lth-tab.selected{color:var(--primary);border-bottom-color:var(--primary)}\n"] }]
10373
+ args: [{ selector: 'lib-list-table-header', standalone: false, template: "<div class=\"lth\">\n <div class=\"lth-main\">\n <div class=\"lth-icon\" aria-hidden=\"true\">\n <ng-content select=\"[header-icon]\"></ng-content>\n </div>\n <div class=\"lth-titles\">\n <div class=\"lth-title-row\">\n <h1 class=\"lth-title\">{{ title }}</h1>\n @if (infoText) {\n <span class=\"lth-info\" [title]=\"infoText\" tabindex=\"0\" role=\"img\" [attr.aria-label]=\"infoText\">\n <span class=\"lth-info-badge\" aria-hidden=\"true\">i</span>\n </span>\n }\n </div>\n @if (subtitle) {\n <p class=\"lth-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <div class=\"lth-actions\">\n @if (showAdd && !blockCreate) {\n <button type=\"button\" class=\"lth-create\" (click)=\"clickAction('add')\" data-action=\"add\" id=\"CreateButton\">\n <span class=\"lth-create-plus\" aria-hidden=\"true\">+</span>\n <span>{{ createLabel }}</span>\n </button>\n }\n @if (!showAdd && !blockDelete) {\n <button type=\"button\" class=\"lth-delete\" (click)=\"clickAction('delete')\" data-action=\"delete\" id=\"DeleteButton\">\n <span class=\"lth-delete-icon icon-minus\" aria-hidden=\"true\"></span>\n <span>Delete</span>\n </button>\n }\n </div>\n </div>\n @if (showTabs) {\n <div class=\"lth-tabs\">\n @for (tab of tabs; track tab) {\n <a\n class=\"lth-tab\"\n [ngClass]=\"{ selected: tabSelected(tab) }\"\n [routerLink]=\"tabsUseRouter ? tab.url : null\"\n [hidden]=\"tab.hidden\"\n [attr.role]=\"tabsUseRouter ? null : 'button'\"\n [attr.tabindex]=\"tabsUseRouter ? null : 0\"\n (click)=\"onTabClick(tab)\"\n (keydown.enter)=\"tabsUseRouter ? null : onTabClick(tab)\"\n >{{ tab.label }}</a>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.lth{display:flex;flex-direction:column;gap:.875rem;padding-bottom:.625rem}.lth-main{display:flex;align-items:center;gap:.875rem}.lth-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:.75rem;color:var(--lt-header-icon, var(--primary));background:color-mix(in srgb,var(--lt-header-icon, var(--primary)) 12%,var(--background));border:1px solid color-mix(in srgb,var(--lt-header-icon, var(--primary)) 22%,var(--background))}.lth-icon svg,.lth-icon img{display:block;width:1.5rem;height:1.5rem}:host ::ng-deep .lth-icon [class^=icon-],:host ::ng-deep .lth-icon [class*=\" icon-\"]{font-size:1.5rem;line-height:1}:host ::ng-deep .lth-icon [class^=icon-]:before,:host ::ng-deep .lth-icon [class*=\" icon-\"]:before{color:var(--lt-header-icon, var(--primary))}.lth-titles{flex:1 1 auto;min-width:0}.lth-title-row{display:flex;align-items:center;gap:.5rem}.lth-title{margin:0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:1.5rem;font-weight:700;line-height:1.15;color:var(--tableText, var(--text))}.lth-info{display:inline-flex;align-items:center;justify-content:center;padding:.25rem;background:var(--background);border:1px solid var(--stroke);border-radius:.375rem;box-shadow:0 1px 2px #0000000f;cursor:help}.lth-info:hover{border-color:var(--groupText)}.lth-info-badge{display:inline-flex;align-items:center;justify-content:center;width:.9375rem;height:.9375rem;font-size:.625rem;font-weight:700;font-style:italic;font-family:Georgia,Times New Roman,serif;line-height:1;color:#fff;background:var(--dark);border-radius:50%}.lth-subtitle{margin:.125rem 0 0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;color:var(--groupText)}.lth-actions{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}.lth-create{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:#fff;background:var(--primary);border:none;border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:filter .15s}.lth-create:hover{filter:brightness(1.06)}.lth-create-plus{font-size:1.125rem;line-height:1}.lth-delete{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--redText, #9c0027);background:transparent;border:1px solid var(--redText, #9c0027);border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:background .15s}.lth-delete:hover{background:color-mix(in srgb,var(--redText, #9c0027) 8%,transparent)}.lth-tabs{display:flex;align-items:center;gap:1.25rem;border-bottom:1px solid var(--stroke)}.lth-tab{padding:.4375rem .125rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--groupText);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;transition:color .15s,border-color .15s}.lth-tab:hover{color:var(--text)}.lth-tab.selected{color:var(--primary);border-bottom-color:var(--primary)}\n"] }]
10218
10374
  }], propDecorators: { title: [{
10219
10375
  type: Input
10220
10376
  }], subtitle: [{
@@ -11802,6 +11958,12 @@ class ListTableAutoRefreshComponent {
11802
11958
  this.refresh = new EventEmitter();
11803
11959
  this.secondsChange = new EventEmitter();
11804
11960
  this.open = false;
11961
+ /** Menu geometry, all measured when the menu opens: left offset so labels line up
11962
+ * under the button's value, width so the right edge lands on the button's right
11963
+ * edge, and right padding so the check centers under the chevron. */
11964
+ this.menuLeft = 0;
11965
+ this.menuWidth = 0;
11966
+ this.menuPadRight = 0;
11805
11967
  }
11806
11968
  /** Current interval in seconds; 0 means off. */
11807
11969
  set seconds(value) {
@@ -11823,7 +11985,37 @@ class ListTableAutoRefreshComponent {
11823
11985
  }
11824
11986
  toggleMenu(event) {
11825
11987
  event.stopPropagation();
11826
- this.open = !this.open;
11988
+ if (this.open) {
11989
+ this.open = false;
11990
+ return;
11991
+ }
11992
+ this.alignMenuToValue();
11993
+ this.open = true;
11994
+ }
11995
+ /** Position + size the menu so its labels sit directly under the button's value,
11996
+ * its right edge lands on the button's right edge (one shared border), and the
11997
+ * right-aligned check centers under the chevron. */
11998
+ alignMenuToValue() {
11999
+ const el = this.arEl?.nativeElement;
12000
+ const btn = el?.querySelector('.lt-ar-btn');
12001
+ const value = this.valueEl?.nativeElement?.getBoundingClientRect();
12002
+ const chevron = this.chevronEl?.nativeElement?.getBoundingClientRect();
12003
+ const container = el?.getBoundingClientRect();
12004
+ if (!btn || !value || !chevron || !container) {
12005
+ return;
12006
+ }
12007
+ const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
12008
+ // left padding we back out of the value position so labels sit under it
12009
+ const itemPadLeft = 0.875 * rem;
12010
+ // half the check's box, so we can land its center rather than its edge
12011
+ const tickHalf = 0.375 * rem;
12012
+ const borderWidth = parseFloat(getComputedStyle(btn).borderRightWidth) || 1;
12013
+ this.menuLeft = Math.max(0, value.left - container.left - itemPadLeft);
12014
+ this.menuWidth = container.width - this.menuLeft;
12015
+ // the check is right-aligned, so its right padding is the gap from the menu's
12016
+ // right edge to the chevron center, less the border and half the check box
12017
+ const chevronCenter = (chevron.left + chevron.right) / 2;
12018
+ this.menuPadRight = Math.max(0, container.right - chevronCenter - borderWidth - tickHalf);
11827
12019
  }
11828
12020
  closeMenu() {
11829
12021
  this.open = false;
@@ -11859,11 +12051,11 @@ class ListTableAutoRefreshComponent {
11859
12051
  }
11860
12052
  }
11861
12053
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableAutoRefreshComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11862
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableAutoRefreshComponent, isStandalone: false, selector: "lib-list-table-auto-refresh", inputs: { seconds: "seconds", loading: "loading", intervals: "intervals" }, outputs: { refresh: "refresh", secondsChange: "secondsChange" }, ngImport: i0, template: "<div class=\"lt-ar\" [class.is-open]=\"open\">\n <button\n type=\"button\"\n class=\"lt-ar-btn\"\n [class.is-on]=\"enabled\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n [attr.aria-label]=\"'Auto-refresh: ' + valueLabel\"\n id=\"AutoRefreshButton\"\n >\n <svg class=\"lt-ar-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 6.4A5.6 5.6 0 1 1 2.4 9\"/>\n <path fill=\"currentColor\" d=\"M1.4 3.1l.7 3.3 3.2-.9z\"/>\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5.3V8.2l2 1.3\"/>\n </svg>\n <span class=\"lt-ar-prefix\">Auto-refresh:</span>\n <span class=\"lt-ar-value\">{{ valueLabel }}</span>\n <svg class=\"lt-ar-chevron\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\"><path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/></svg>\n </button>\n\n @if (open) {\n <div class=\"lt-ar-menu\" (clickOutside)=\"closeMenu()\" role=\"menu\">\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"!enabled\" (click)=\"select(0)\" role=\"menuitemradio\" [attr.aria-checked]=\"!enabled\">\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (!enabled) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n <span>Off</span>\n </button>\n @for (interval of intervals; track interval.seconds) {\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"isSelected(interval.seconds)\" (click)=\"select(interval.seconds)\" role=\"menuitemradio\" [attr.aria-checked]=\"isSelected(interval.seconds)\">\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (isSelected(interval.seconds)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n <span>{{ interval.label }}</span>\n </button>\n }\n </div>\n }\n</div>\n", styles: [":host{display:inline-flex}.lt-ar{position:relative;display:inline-flex}.lt-ar-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-ar-btn svg{display:block}.lt-ar-btn:hover,.lt-ar-btn.is-on{border-color:var(--lt-link);color:var(--lt-link)}.lt-ar-btn.is-open{border-color:var(--lt-link);color:var(--lt-link);border-bottom-left-radius:0;border-bottom-right-radius:0}.lt-ar-value{font-weight:700}.lt-ar-chevron{opacity:.7}.lt-ar-menu{position:absolute;top:100%;right:0;z-index:1000;min-width:100%;display:flex;flex-direction:column;padding:.25rem 0;background:var(--lt-control-bg);border:1px solid var(--lt-link);border-top:none;border-bottom-left-radius:var(--lt-radius);border-bottom-right-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-ar-in .12s ease-out}@keyframes lt-ar-in{0%{opacity:0;transform:translateY(-.125rem)}to{opacity:1;transform:translateY(0)}}.lt-ar-item{display:inline-flex;align-items:center;gap:.375rem;justify-content:flex-end;min-height:2rem;padding:0 .75rem 0 .625rem;font-family:var(--lt-font);font-size:var(--lt-control-font-size);color:var(--lt-cell-text);background:transparent;border:none;cursor:pointer;text-align:right;transition:background .1s,color .1s}.lt-ar-item:hover{background:var(--lt-row-hover)}.lt-ar-item.is-selected{color:var(--lt-link);font-weight:var(--lt-control-font-weight)}.lt-ar-tick{display:inline-flex;justify-content:center;width:.625rem;color:var(--lt-link)}.lt-ar-tick svg{display:block}\n"], dependencies: [{ kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
12054
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableAutoRefreshComponent, isStandalone: false, selector: "lib-list-table-auto-refresh", inputs: { seconds: "seconds", loading: "loading", intervals: "intervals" }, outputs: { refresh: "refresh", secondsChange: "secondsChange" }, viewQueries: [{ propertyName: "arEl", first: true, predicate: ["arEl"], descendants: true }, { propertyName: "valueEl", first: true, predicate: ["valueEl"], descendants: true }, { propertyName: "chevronEl", first: true, predicate: ["chevronEl"], descendants: true }], ngImport: i0, template: "<div class=\"lt-ar\" #arEl [class.is-open]=\"open\">\n <button\n type=\"button\"\n class=\"lt-ar-btn\"\n [class.is-on]=\"enabled\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n [attr.aria-label]=\"'Auto-refresh: ' + valueLabel\"\n id=\"AutoRefreshButton\"\n >\n <svg class=\"lt-ar-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 6.4A5.6 5.6 0 1 1 2.4 9\"/>\n <path fill=\"currentColor\" d=\"M1.4 3.1l.7 3.3 3.2-.9z\"/>\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5.3V8.2l2 1.3\"/>\n </svg>\n <span class=\"lt-ar-prefix\">Auto-refresh:</span>\n <span class=\"lt-ar-value\" #valueEl>{{ valueLabel }}</span>\n <svg class=\"lt-ar-chevron\" #chevronEl viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M1 1.5 5 5 9 1.5\"/></svg>\n </button>\n\n @if (open) {\n <div class=\"lt-ar-menu\" [style.left.px]=\"menuLeft\" [style.width.px]=\"menuWidth\" [style.--lt-ar-pad-right]=\"menuPadRight + 'px'\" (clickOutside)=\"closeMenu()\" role=\"menu\">\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"!enabled\" (click)=\"select(0)\" role=\"menuitemradio\" [attr.aria-checked]=\"!enabled\">\n <span class=\"lt-ar-label\">Off</span>\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (!enabled) {\n <svg viewBox=\"0 0 12 12\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n </button>\n @for (interval of intervals; track interval.seconds) {\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"isSelected(interval.seconds)\" (click)=\"select(interval.seconds)\" role=\"menuitemradio\" [attr.aria-checked]=\"isSelected(interval.seconds)\">\n <span class=\"lt-ar-label\">{{ interval.label }}</span>\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (isSelected(interval.seconds)) {\n <svg viewBox=\"0 0 12 12\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n </button>\n }\n </div>\n }\n</div>\n", styles: [":host{display:inline-flex}.lt-ar{position:relative;display:inline-flex}.lt-ar-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-ar-btn svg{display:block}.lt-ar-btn:hover,.lt-ar-btn.is-on{border-color:var(--lt-link);color:var(--lt-link)}.lt-ar-btn.is-on{background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.lt-ar-btn.is-open{border-color:var(--lt-link);color:var(--lt-link);border-bottom-right-radius:0}.lt-ar-value{font-weight:700}.lt-ar-chevron{opacity:.7}.lt-ar-menu{position:absolute;top:100%;left:0;z-index:1000;min-width:0;margin-top:calc(var(--lt-control-border-width) * -1);display:flex;flex-direction:column;padding:.25rem 0;background:var(--lt-control-bg);border:var(--lt-control-border-width) solid var(--lt-link);border-radius:0 0 var(--lt-radius) var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-ar-in .12s ease-out}@keyframes lt-ar-in{0%{opacity:0;transform:translateY(-.125rem)}to{opacity:1;transform:translateY(0)}}.lt-ar-item{display:flex;align-items:center;justify-content:space-between;gap:0;min-height:2rem;padding:0 var(--lt-ar-pad-right, .875rem) 0 .875rem;font-family:var(--lt-font);font-size:var(--lt-control-font-size);color:var(--lt-cell-text);background:transparent;border:none;cursor:pointer;text-align:left;transition:background .1s,color .1s}.lt-ar-item:hover{background:var(--lt-row-hover)}.lt-ar-item.is-selected{color:var(--lt-link);font-weight:700}.lt-ar-tick{display:inline-flex;flex:0 0 auto;justify-content:center;width:.75rem;color:var(--lt-link)}.lt-ar-tick svg{display:block}\n"], dependencies: [{ kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
11863
12055
  }
11864
12056
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableAutoRefreshComponent, decorators: [{
11865
12057
  type: Component,
11866
- args: [{ selector: 'lib-list-table-auto-refresh', standalone: false, template: "<div class=\"lt-ar\" [class.is-open]=\"open\">\n <button\n type=\"button\"\n class=\"lt-ar-btn\"\n [class.is-on]=\"enabled\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n [attr.aria-label]=\"'Auto-refresh: ' + valueLabel\"\n id=\"AutoRefreshButton\"\n >\n <svg class=\"lt-ar-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 6.4A5.6 5.6 0 1 1 2.4 9\"/>\n <path fill=\"currentColor\" d=\"M1.4 3.1l.7 3.3 3.2-.9z\"/>\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5.3V8.2l2 1.3\"/>\n </svg>\n <span class=\"lt-ar-prefix\">Auto-refresh:</span>\n <span class=\"lt-ar-value\">{{ valueLabel }}</span>\n <svg class=\"lt-ar-chevron\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\"><path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/></svg>\n </button>\n\n @if (open) {\n <div class=\"lt-ar-menu\" (clickOutside)=\"closeMenu()\" role=\"menu\">\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"!enabled\" (click)=\"select(0)\" role=\"menuitemradio\" [attr.aria-checked]=\"!enabled\">\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (!enabled) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n <span>Off</span>\n </button>\n @for (interval of intervals; track interval.seconds) {\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"isSelected(interval.seconds)\" (click)=\"select(interval.seconds)\" role=\"menuitemradio\" [attr.aria-checked]=\"isSelected(interval.seconds)\">\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (isSelected(interval.seconds)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n <span>{{ interval.label }}</span>\n </button>\n }\n </div>\n }\n</div>\n", styles: [":host{display:inline-flex}.lt-ar{position:relative;display:inline-flex}.lt-ar-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-ar-btn svg{display:block}.lt-ar-btn:hover,.lt-ar-btn.is-on{border-color:var(--lt-link);color:var(--lt-link)}.lt-ar-btn.is-open{border-color:var(--lt-link);color:var(--lt-link);border-bottom-left-radius:0;border-bottom-right-radius:0}.lt-ar-value{font-weight:700}.lt-ar-chevron{opacity:.7}.lt-ar-menu{position:absolute;top:100%;right:0;z-index:1000;min-width:100%;display:flex;flex-direction:column;padding:.25rem 0;background:var(--lt-control-bg);border:1px solid var(--lt-link);border-top:none;border-bottom-left-radius:var(--lt-radius);border-bottom-right-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-ar-in .12s ease-out}@keyframes lt-ar-in{0%{opacity:0;transform:translateY(-.125rem)}to{opacity:1;transform:translateY(0)}}.lt-ar-item{display:inline-flex;align-items:center;gap:.375rem;justify-content:flex-end;min-height:2rem;padding:0 .75rem 0 .625rem;font-family:var(--lt-font);font-size:var(--lt-control-font-size);color:var(--lt-cell-text);background:transparent;border:none;cursor:pointer;text-align:right;transition:background .1s,color .1s}.lt-ar-item:hover{background:var(--lt-row-hover)}.lt-ar-item.is-selected{color:var(--lt-link);font-weight:var(--lt-control-font-weight)}.lt-ar-tick{display:inline-flex;justify-content:center;width:.625rem;color:var(--lt-link)}.lt-ar-tick svg{display:block}\n"] }]
12058
+ args: [{ selector: 'lib-list-table-auto-refresh', standalone: false, template: "<div class=\"lt-ar\" #arEl [class.is-open]=\"open\">\n <button\n type=\"button\"\n class=\"lt-ar-btn\"\n [class.is-on]=\"enabled\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n [attr.aria-label]=\"'Auto-refresh: ' + valueLabel\"\n id=\"AutoRefreshButton\"\n >\n <svg class=\"lt-ar-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 6.4A5.6 5.6 0 1 1 2.4 9\"/>\n <path fill=\"currentColor\" d=\"M1.4 3.1l.7 3.3 3.2-.9z\"/>\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5.3V8.2l2 1.3\"/>\n </svg>\n <span class=\"lt-ar-prefix\">Auto-refresh:</span>\n <span class=\"lt-ar-value\" #valueEl>{{ valueLabel }}</span>\n <svg class=\"lt-ar-chevron\" #chevronEl viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M1 1.5 5 5 9 1.5\"/></svg>\n </button>\n\n @if (open) {\n <div class=\"lt-ar-menu\" [style.left.px]=\"menuLeft\" [style.width.px]=\"menuWidth\" [style.--lt-ar-pad-right]=\"menuPadRight + 'px'\" (clickOutside)=\"closeMenu()\" role=\"menu\">\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"!enabled\" (click)=\"select(0)\" role=\"menuitemradio\" [attr.aria-checked]=\"!enabled\">\n <span class=\"lt-ar-label\">Off</span>\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (!enabled) {\n <svg viewBox=\"0 0 12 12\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n </button>\n @for (interval of intervals; track interval.seconds) {\n <button type=\"button\" class=\"lt-ar-item\" [class.is-selected]=\"isSelected(interval.seconds)\" (click)=\"select(interval.seconds)\" role=\"menuitemradio\" [attr.aria-checked]=\"isSelected(interval.seconds)\">\n <span class=\"lt-ar-label\">{{ interval.label }}</span>\n <span class=\"lt-ar-tick\" aria-hidden=\"true\">\n @if (isSelected(interval.seconds)) {\n <svg viewBox=\"0 0 12 12\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n </button>\n }\n </div>\n }\n</div>\n", styles: [":host{display:inline-flex}.lt-ar{position:relative;display:inline-flex}.lt-ar-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-ar-btn svg{display:block}.lt-ar-btn:hover,.lt-ar-btn.is-on{border-color:var(--lt-link);color:var(--lt-link)}.lt-ar-btn.is-on{background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.lt-ar-btn.is-open{border-color:var(--lt-link);color:var(--lt-link);border-bottom-right-radius:0}.lt-ar-value{font-weight:700}.lt-ar-chevron{opacity:.7}.lt-ar-menu{position:absolute;top:100%;left:0;z-index:1000;min-width:0;margin-top:calc(var(--lt-control-border-width) * -1);display:flex;flex-direction:column;padding:.25rem 0;background:var(--lt-control-bg);border:var(--lt-control-border-width) solid var(--lt-link);border-radius:0 0 var(--lt-radius) var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-ar-in .12s ease-out}@keyframes lt-ar-in{0%{opacity:0;transform:translateY(-.125rem)}to{opacity:1;transform:translateY(0)}}.lt-ar-item{display:flex;align-items:center;justify-content:space-between;gap:0;min-height:2rem;padding:0 var(--lt-ar-pad-right, .875rem) 0 .875rem;font-family:var(--lt-font);font-size:var(--lt-control-font-size);color:var(--lt-cell-text);background:transparent;border:none;cursor:pointer;text-align:left;transition:background .1s,color .1s}.lt-ar-item:hover{background:var(--lt-row-hover)}.lt-ar-item.is-selected{color:var(--lt-link);font-weight:700}.lt-ar-tick{display:inline-flex;flex:0 0 auto;justify-content:center;width:.75rem;color:var(--lt-link)}.lt-ar-tick svg{display:block}\n"] }]
11867
12059
  }], propDecorators: { seconds: [{
11868
12060
  type: Input
11869
12061
  }], loading: [{
@@ -11874,6 +12066,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
11874
12066
  type: Output
11875
12067
  }], secondsChange: [{
11876
12068
  type: Output
12069
+ }], arEl: [{
12070
+ type: ViewChild,
12071
+ args: ['arEl']
12072
+ }], valueEl: [{
12073
+ type: ViewChild,
12074
+ args: ['valueEl']
12075
+ }], chevronEl: [{
12076
+ type: ViewChild,
12077
+ args: ['chevronEl']
11877
12078
  }] } });
11878
12079
 
11879
12080
  /**
@@ -11941,11 +12142,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
11941
12142
  See the License for the specific language governing permissions and
11942
12143
  limitations under the License.
11943
12144
  */
11944
- /** Header cell for {@link ListTableComponent}: label, 3-state sort (asc/desc), and hide. */
12145
+ /** Header cell for {@link ListTableComponent}: label and 3-state sort (asc/desc). */
11945
12146
  class ListHeaderCellComponent {
11946
12147
  constructor() {
11947
12148
  this.sortChanged = new EventEmitter();
11948
- this.hideColumn = new EventEmitter();
11949
12149
  }
11950
12150
  get enableSorting() {
11951
12151
  return !!(this.column?.sortable || this.column?.enableSorting);
@@ -11959,24 +12159,18 @@ class ListHeaderCellComponent {
11959
12159
  this.column.onSort?.(this.column.key, dir);
11960
12160
  this.sortChanged.emit({ colId: this.column.key, dir });
11961
12161
  }
11962
- hide(event) {
11963
- event.stopPropagation();
11964
- this.hideColumn.emit(this.column);
11965
- }
11966
12162
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListHeaderCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11967
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListHeaderCellComponent, isStandalone: false, selector: "lib-list-header-cell", inputs: { column: "column", sortDir: "sortDir" }, outputs: { sortChanged: "sortChanged", hideColumn: "hideColumn" }, ngImport: i0, template: "<div\n (click)=\"sort()\"\n [ngClass]=\"{ 'nf-can-sort': enableSorting }\"\n class=\"lt-header-container\"\n id=\"TableHeaderContainer_{{ column.key }}\"\n>\n @if (column.hideable !== false) {\n <span\n (click)=\"hide($event)\"\n class=\"lt-header-icon lt-col-hide-button\"\n id=\"HeaderHide_{{ column.key }}\"\n title=\"Hide Column\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"\n d=\"M1 8s2.7-4.5 7-4.5S15 8 15 8s-2.7 4.5-7 4.5S1 8 1 8Z\"/>\n <circle cx=\"8\" cy=\"8\" r=\"1.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n <line x1=\"2.5\" y1=\"13.5\" x2=\"13.5\" y2=\"2.5\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n </svg>\n </span>\n }\n <div class=\"lt-header-label\">\n <span\n [title]=\"column.label || null\"\n class=\"lt-header-text\"\n [class.is-sorted]=\"sortDir === 'asc' || sortDir === 'desc'\"\n id=\"HeaderName_{{ column.key }}\"\n unselectable=\"on\"\n >{{ column.label }}</span>\n @if (enableSorting) {\n <span class=\"lt-sort\" [class.asc]=\"sortDir === 'asc'\" [class.desc]=\"sortDir === 'desc'\">\n <svg viewBox=\"0 0 8 14\" width=\"8\" height=\"13\" aria-hidden=\"true\">\n <path class=\"lt-sort-up\" d=\"M4 0 7.5 4.5H0.5z\"/>\n <path class=\"lt-sort-down\" d=\"M4 14 0.5 9.5H7.5z\"/>\n </svg>\n </span>\n }\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;align-items:center}.lt-header-container{display:flex;align-items:center;width:100%;height:100%;cursor:default;gap:.25rem}.lt-header-container.nf-can-sort{cursor:pointer}.lt-header-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:.25rem}.lt-header-text{color:var(--lt-header-text);font-family:var(--lt-font);font-size:.6875rem;font-weight:600;letter-spacing:.045em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .15s}.lt-header-text.is-sorted{color:var(--lt-header-sorted-text, var(--lt-icon-active));font-weight:var(--lt-header-sorted-weight, 700)}.lt-header-text-container{display:flex;align-items:center;min-width:0;flex:1 1 auto}.lt-header-text-container.show-input-filter{width:100%}.lt-header-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--lt-icon);cursor:pointer;transition:opacity .2s,color .2s}.lt-header-icon svg{display:block}.lt-header-icon:hover{color:var(--lt-icon-active)}.lt-col-hide-button,.lt-col-filter-button{opacity:0}.lt-header-container:hover .lt-col-hide-button,.lt-header-container:hover .lt-col-filter-button{opacity:1}.lt-col-filter-button.nf-filter-applied{opacity:1;color:var(--lt-icon-active)}.lt-sort{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:auto;margin-right:.375rem}.lt-sort svg{display:block}.lt-sort .lt-sort-up,.lt-sort .lt-sort-down{fill:var(--lt-icon);opacity:.35;transition:opacity .15s,fill .15s}.lt-sort.asc .lt-sort-up,.lt-sort.desc .lt-sort-down{fill:var(--lt-icon-active);opacity:1}.nf-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
12163
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListHeaderCellComponent, isStandalone: false, selector: "lib-list-header-cell", inputs: { column: "column", sortDir: "sortDir" }, outputs: { sortChanged: "sortChanged" }, ngImport: i0, template: "<div\n (click)=\"sort()\"\n [ngClass]=\"{ 'nf-can-sort': enableSorting }\"\n class=\"lt-header-container\"\n id=\"TableHeaderContainer_{{ column.key }}\"\n>\n <div class=\"lt-header-label\">\n <span\n [title]=\"column.label || null\"\n class=\"lt-header-text\"\n [class.is-sorted]=\"sortDir === 'asc' || sortDir === 'desc'\"\n id=\"HeaderName_{{ column.key }}\"\n unselectable=\"on\"\n >{{ column.label }}</span>\n @if (enableSorting) {\n <span class=\"lt-sort\" [class.asc]=\"sortDir === 'asc'\" [class.desc]=\"sortDir === 'desc'\">\n <svg viewBox=\"0 0 8 14\" width=\"8\" height=\"13\" aria-hidden=\"true\">\n <path class=\"lt-sort-up\" d=\"M4 0 7.5 4.5H0.5z\"/>\n <path class=\"lt-sort-down\" d=\"M4 14 0.5 9.5H7.5z\"/>\n </svg>\n </span>\n }\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;align-items:center}.lt-header-container{display:flex;align-items:center;width:100%;height:100%;cursor:default;gap:.25rem;position:relative}.lt-header-container.nf-can-sort{cursor:pointer}.lt-header-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:.25rem}.lt-header-text{color:var(--lt-header-text);font-family:var(--lt-font);font-size:.6875rem;font-weight:600;letter-spacing:.045em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .15s}.lt-header-text.is-sorted{color:var(--lt-header-sorted-text, var(--lt-icon-active));font-weight:var(--lt-header-sorted-weight, 700)}.lt-header-text-container{display:flex;align-items:center;min-width:0;flex:1 1 auto}.lt-header-text-container.show-input-filter{width:100%}.lt-sort{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:auto;margin-right:.375rem}.lt-sort svg{display:block}.lt-sort .lt-sort-up,.lt-sort .lt-sort-down{fill:var(--lt-icon);opacity:.35;transition:opacity .15s,fill .15s}.lt-sort.asc .lt-sort-up,.lt-sort.desc .lt-sort-down{fill:var(--lt-icon-active);opacity:1}.nf-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
11968
12164
  }
11969
12165
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListHeaderCellComponent, decorators: [{
11970
12166
  type: Component,
11971
- args: [{ selector: 'lib-list-header-cell', standalone: false, template: "<div\n (click)=\"sort()\"\n [ngClass]=\"{ 'nf-can-sort': enableSorting }\"\n class=\"lt-header-container\"\n id=\"TableHeaderContainer_{{ column.key }}\"\n>\n @if (column.hideable !== false) {\n <span\n (click)=\"hide($event)\"\n class=\"lt-header-icon lt-col-hide-button\"\n id=\"HeaderHide_{{ column.key }}\"\n title=\"Hide Column\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"\n d=\"M1 8s2.7-4.5 7-4.5S15 8 15 8s-2.7 4.5-7 4.5S1 8 1 8Z\"/>\n <circle cx=\"8\" cy=\"8\" r=\"1.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n <line x1=\"2.5\" y1=\"13.5\" x2=\"13.5\" y2=\"2.5\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n </svg>\n </span>\n }\n <div class=\"lt-header-label\">\n <span\n [title]=\"column.label || null\"\n class=\"lt-header-text\"\n [class.is-sorted]=\"sortDir === 'asc' || sortDir === 'desc'\"\n id=\"HeaderName_{{ column.key }}\"\n unselectable=\"on\"\n >{{ column.label }}</span>\n @if (enableSorting) {\n <span class=\"lt-sort\" [class.asc]=\"sortDir === 'asc'\" [class.desc]=\"sortDir === 'desc'\">\n <svg viewBox=\"0 0 8 14\" width=\"8\" height=\"13\" aria-hidden=\"true\">\n <path class=\"lt-sort-up\" d=\"M4 0 7.5 4.5H0.5z\"/>\n <path class=\"lt-sort-down\" d=\"M4 14 0.5 9.5H7.5z\"/>\n </svg>\n </span>\n }\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;align-items:center}.lt-header-container{display:flex;align-items:center;width:100%;height:100%;cursor:default;gap:.25rem}.lt-header-container.nf-can-sort{cursor:pointer}.lt-header-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:.25rem}.lt-header-text{color:var(--lt-header-text);font-family:var(--lt-font);font-size:.6875rem;font-weight:600;letter-spacing:.045em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .15s}.lt-header-text.is-sorted{color:var(--lt-header-sorted-text, var(--lt-icon-active));font-weight:var(--lt-header-sorted-weight, 700)}.lt-header-text-container{display:flex;align-items:center;min-width:0;flex:1 1 auto}.lt-header-text-container.show-input-filter{width:100%}.lt-header-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--lt-icon);cursor:pointer;transition:opacity .2s,color .2s}.lt-header-icon svg{display:block}.lt-header-icon:hover{color:var(--lt-icon-active)}.lt-col-hide-button,.lt-col-filter-button{opacity:0}.lt-header-container:hover .lt-col-hide-button,.lt-header-container:hover .lt-col-filter-button{opacity:1}.lt-col-filter-button.nf-filter-applied{opacity:1;color:var(--lt-icon-active)}.lt-sort{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:auto;margin-right:.375rem}.lt-sort svg{display:block}.lt-sort .lt-sort-up,.lt-sort .lt-sort-down{fill:var(--lt-icon);opacity:.35;transition:opacity .15s,fill .15s}.lt-sort.asc .lt-sort-up,.lt-sort.desc .lt-sort-down{fill:var(--lt-icon-active);opacity:1}.nf-hidden{display:none}\n"] }]
12167
+ args: [{ selector: 'lib-list-header-cell', standalone: false, template: "<div\n (click)=\"sort()\"\n [ngClass]=\"{ 'nf-can-sort': enableSorting }\"\n class=\"lt-header-container\"\n id=\"TableHeaderContainer_{{ column.key }}\"\n>\n <div class=\"lt-header-label\">\n <span\n [title]=\"column.label || null\"\n class=\"lt-header-text\"\n [class.is-sorted]=\"sortDir === 'asc' || sortDir === 'desc'\"\n id=\"HeaderName_{{ column.key }}\"\n unselectable=\"on\"\n >{{ column.label }}</span>\n @if (enableSorting) {\n <span class=\"lt-sort\" [class.asc]=\"sortDir === 'asc'\" [class.desc]=\"sortDir === 'desc'\">\n <svg viewBox=\"0 0 8 14\" width=\"8\" height=\"13\" aria-hidden=\"true\">\n <path class=\"lt-sort-up\" d=\"M4 0 7.5 4.5H0.5z\"/>\n <path class=\"lt-sort-down\" d=\"M4 14 0.5 9.5H7.5z\"/>\n </svg>\n </span>\n }\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;align-items:center}.lt-header-container{display:flex;align-items:center;width:100%;height:100%;cursor:default;gap:.25rem;position:relative}.lt-header-container.nf-can-sort{cursor:pointer}.lt-header-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:.25rem}.lt-header-text{color:var(--lt-header-text);font-family:var(--lt-font);font-size:.6875rem;font-weight:600;letter-spacing:.045em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .15s}.lt-header-text.is-sorted{color:var(--lt-header-sorted-text, var(--lt-icon-active));font-weight:var(--lt-header-sorted-weight, 700)}.lt-header-text-container{display:flex;align-items:center;min-width:0;flex:1 1 auto}.lt-header-text-container.show-input-filter{width:100%}.lt-sort{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:auto;margin-right:.375rem}.lt-sort svg{display:block}.lt-sort .lt-sort-up,.lt-sort .lt-sort-down{fill:var(--lt-icon);opacity:.35;transition:opacity .15s,fill .15s}.lt-sort.asc .lt-sort-up,.lt-sort.desc .lt-sort-down{fill:var(--lt-icon-active);opacity:1}.nf-hidden{display:none}\n"] }]
11972
12168
  }], propDecorators: { column: [{
11973
12169
  type: Input
11974
12170
  }], sortDir: [{
11975
12171
  type: Input
11976
12172
  }], sortChanged: [{
11977
12173
  type: Output
11978
- }], hideColumn: [{
11979
- type: Output
11980
12174
  }] } });
11981
12175
 
11982
12176
  /*
@@ -12018,6 +12212,8 @@ class ListFilterCellComponent {
12018
12212
  this.selectedValues = [];
12019
12213
  /** In-dropdown search text for the multi-select attribute combo. */
12020
12214
  this.attrSearch = '';
12215
+ /** Match any selected attribute (or) vs every one (and); attribute filters only. */
12216
+ this.attrSemantic = 'AnyOf';
12021
12217
  this.open = false;
12022
12218
  this.menuLeft = 0;
12023
12219
  this.menuTop = 0;
@@ -12217,10 +12413,19 @@ class ListFilterCellComponent {
12217
12413
  value: [...this.selectedValues],
12218
12414
  label: this.selectedValues.map((v) => this.labelForValue(v)).join(', '),
12219
12415
  type: this.isAttributeMode ? 'ATTRIBUTE' : 'MULTISELECT',
12220
- semantic: 'AnyOf',
12416
+ semantic: this.isAttributeMode ? this.attrSemantic : 'AnyOf',
12221
12417
  };
12222
12418
  this.filterService.updateFilter(filter);
12223
12419
  }
12420
+ setSemantic(semantic) {
12421
+ if (this.attrSemantic === semantic) {
12422
+ return;
12423
+ }
12424
+ this.attrSemantic = semantic;
12425
+ if (this.selectedValues.length) {
12426
+ this.emitMulti();
12427
+ }
12428
+ }
12224
12429
  // ------------------------------------------------------------------ date range
12225
12430
  selectDatePreset(opt) {
12226
12431
  this.selectedValue = opt.value;
@@ -12293,6 +12498,7 @@ class ListFilterCellComponent {
12293
12498
  this.textValue = '';
12294
12499
  this.dateRangeStart = null;
12295
12500
  this.dateRangeEnd = null;
12501
+ this.attrSemantic = 'AnyOf';
12296
12502
  this.removeThis();
12297
12503
  }
12298
12504
  removeThis() {
@@ -12319,6 +12525,9 @@ class ListFilterCellComponent {
12319
12525
  }
12320
12526
  else if (this.mode === 'multi') {
12321
12527
  this.selectedValues = _.isArray(match.value) ? [...match.value] : [match.value];
12528
+ if (this.isAttributeMode) {
12529
+ this.attrSemantic = match.semantic === 'AllOf' ? 'AllOf' : 'AnyOf';
12530
+ }
12322
12531
  }
12323
12532
  else if (this.mode === 'daterange') {
12324
12533
  // keep the locally chosen preset label; match.value is the raw [start,end] range
@@ -12328,11 +12537,11 @@ class ListFilterCellComponent {
12328
12537
  }
12329
12538
  }
12330
12539
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListFilterCellComponent, deps: [{ token: DataTableFilterService }, { token: ListTableService }], target: i0.ɵɵFactoryTarget.Component }); }
12331
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListFilterCellComponent, isStandalone: false, selector: "lib-list-filter-cell", inputs: { column: "column" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "rangePicker", first: true, predicate: ["rangePicker"], descendants: true }], ngImport: i0, template: "@switch (mode) {\n @case ('text') {\n <div class=\"fc-text\">\n <input\n class=\"fc-input\"\n type=\"text\"\n [placeholder]=\"column.filterPlaceholder || 'Filter'\"\n [(ngModel)]=\"textValue\"\n (keyup)=\"onTextInput()\"\n />\n @if (textValue) {\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n }\n </div>\n }\n @default {\n <button\n #trigger\n type=\"button\"\n class=\"fc-pill\"\n [class.has-value]=\"hasValue\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"fc-pill-field\">{{ column.label }}</span>\n @if (hasValue) {\n <span class=\"fc-pill-value\" [title]=\"valueLabel\">{{ valueLabel }}</span>\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n } @else {\n <span class=\"fc-caret\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 6\" width=\"9\" height=\"6\"><path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/></svg>\n </span>\n }\n </button>\n\n @if (open) {\n <div\n class=\"fc-menu\"\n (clickOutside)=\"closeMenu()\"\n [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px', 'min-width.px': menuWidth }\"\n >\n @if (mode === 'multi') {\n <div class=\"fc-search\">\n <svg class=\"fc-search-icon\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"fc-search-input\"\n type=\"text\"\n [(ngModel)]=\"attrSearch\"\n [placeholder]=\"isAttributeMode ? 'Search attributes' : 'Search'\"\n (click)=\"$event.stopPropagation()\"\n autocomplete=\"off\"\n />\n </div>\n @if (!filteredOptions.length) {\n <div class=\"fc-empty\">{{ options.length ? 'No matches' : (isAttributeMode ? 'No attributes available' : 'No options available') }}</div>\n }\n <div class=\"fc-opt-list\">\n @for (opt of filteredOptions; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"toggleOption(opt)\">\n <span class=\"fc-check\" aria-hidden=\"true\">\n @if (isOptionSelected(opt)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n @if (isAttributeMode) {\n <span class=\"fc-attr\" [class.at]=\"isNamedOption(opt)\">{{ optionLabel(opt) }}</span>\n } @else if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ optionLabel(opt) }}</span>\n }\n </button>\n }\n </div>\n } @else if (mode === 'daterange') {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectDatePreset(opt)\">\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n </button>\n }\n <div class=\"fc-divider\"></div>\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"selectedValue === 'custom'\" (click)=\"openCustomRange($event)\">\n <span class=\"fc-opt-label\">Custom range\u2026</span>\n </button>\n } @else {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectOption(opt)\">\n @if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n }\n </button>\n }\n }\n </div>\n }\n }\n}\n\n@if (mode === 'daterange') {\n <!-- hidden Material range input; the calendar is opened from the \"Custom range\u2026\" option -->\n <mat-date-range-input class=\"fc-hidden-range\" [rangePicker]=\"rangePicker\">\n <input matStartDate [(ngModel)]=\"dateRangeStart\" (dateChange)=\"onRangeStartChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n <input matEndDate [(ngModel)]=\"dateRangeEnd\" (dateChange)=\"onRangeEndChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n </mat-date-range-input>\n <mat-date-range-picker #rangePicker xPosition=\"start\" yPosition=\"below\"></mat-date-range-picker>\n}\n", styles: [":host{display:inline-flex;position:relative}.fc-hidden-range{position:absolute;left:0;top:100%;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden}.fc-divider{height:1px;margin:.25rem;background:var(--lt-border)}.fc-text{position:relative;display:flex;align-items:center}.fc-input{width:100%;height:1.875rem;padding:0 1.5rem 0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-input::placeholder{color:var(--placeholder)}.fc-input:focus{border-color:var(--lt-link)}.fc-pill{display:inline-flex;align-items:center;gap:.375rem;height:var(--lt-toolbar-height);max-width:14rem;padding:0 .75rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.fc-pill:hover,.fc-pill.is-open{border-color:var(--lt-link)}.fc-pill.has-value{border-color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.fc-pill-field{color:var(--lt-cell-text)}.fc-pill.has-value .fc-pill-field{color:var(--groupText)}.fc-pill.has-value .fc-pill-field:after{content:\":\"}.fc-pill-value{color:var(--lt-link);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-caret{flex:0 0 auto;display:inline-flex;color:var(--lt-icon)}.fc-clear{flex:0 0 auto;font-size:1rem;line-height:1;color:var(--lt-icon);cursor:pointer}.fc-clear:hover{color:var(--lt-icon-active)}.fc-text .fc-clear{position:absolute;right:.5rem}.fc-menu{position:fixed;z-index:1000;max-height:18rem;overflow-y:auto;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:fc-in .1s ease-out}@keyframes fc-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.fc-opt{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.fc-opt:hover{background:var(--lt-row-hover)}.fc-opt.selected{color:var(--lt-link);font-weight:600}.fc-opt-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-check{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;box-sizing:border-box;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg)}.fc-opt.selected .fc-check{background:var(--lt-link);border-color:var(--lt-link)}.fc-empty{padding:.5rem;font-size:.75rem;color:var(--groupText)}.fc-search{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:.375rem;padding:.125rem .25rem .375rem;background:var(--lt-control-bg)}.fc-search-icon{flex:0 0 auto;margin-left:.25rem;color:var(--lt-icon)}.fc-search-input{flex:1 1 auto;min-width:0;height:1.75rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-search-input::placeholder{color:var(--placeholder)}.fc-search-input:focus{border-color:var(--lt-link)}.fc-opt-list{display:flex;flex-direction:column}.fc-attr{display:inline-flex;align-items:center;padding:.0625rem .375rem;font-size:.75rem;font-weight:600;line-height:1.4;border-radius:.25rem;color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-attr.at{color:var(--secondary);background:color-mix(in srgb,var(--secondary) 14%,transparent)}.fc-opt.selected .fc-attr{color:var(--lt-link)}.fc-opt.selected .fc-attr.at{color:var(--secondary)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
12540
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListFilterCellComponent, isStandalone: false, selector: "lib-list-filter-cell", inputs: { column: "column" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "rangePicker", first: true, predicate: ["rangePicker"], descendants: true }], ngImport: i0, template: "@switch (mode) {\n @case ('text') {\n <div class=\"fc-text\">\n <input\n class=\"fc-input\"\n type=\"text\"\n [placeholder]=\"column.filterPlaceholder || 'Filter'\"\n [(ngModel)]=\"textValue\"\n (keyup)=\"onTextInput()\"\n />\n @if (textValue) {\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n }\n </div>\n }\n @default {\n <button\n #trigger\n type=\"button\"\n class=\"fc-pill\"\n [class.has-value]=\"hasValue\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"fc-pill-field\">{{ column.label }}</span>\n @if (hasValue) {\n <span class=\"fc-pill-value\" [title]=\"valueLabel\">{{ valueLabel }}</span>\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n } @else {\n <span class=\"fc-caret\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 6\" width=\"9\" height=\"6\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M1 1.5 5 5 9 1.5\"/></svg>\n </span>\n }\n </button>\n\n @if (open) {\n <div\n class=\"fc-menu\"\n (clickOutside)=\"closeMenu()\"\n [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px', 'min-width.px': menuWidth }\"\n >\n @if (mode === 'multi') {\n <div class=\"fc-search\">\n <svg class=\"fc-search-icon\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"fc-search-input\"\n type=\"text\"\n [(ngModel)]=\"attrSearch\"\n [placeholder]=\"isAttributeMode ? 'Search attributes' : 'Search'\"\n (click)=\"$event.stopPropagation()\"\n autocomplete=\"off\"\n />\n </div>\n @if (isAttributeMode) {\n <div class=\"fc-semantic\" role=\"group\" aria-label=\"Match\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"fc-semantic-opt\" [class.active]=\"attrSemantic === 'AnyOf'\" (click)=\"setSemantic('AnyOf')\">Any of</button>\n <button type=\"button\" class=\"fc-semantic-opt\" [class.active]=\"attrSemantic === 'AllOf'\" (click)=\"setSemantic('AllOf')\">All of</button>\n </div>\n }\n @if (!filteredOptions.length) {\n <div class=\"fc-empty\">{{ options.length ? 'No matches' : (isAttributeMode ? 'No attributes available' : 'No options available') }}</div>\n }\n <div class=\"fc-opt-list\">\n @for (opt of filteredOptions; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"toggleOption(opt)\">\n <span class=\"fc-check\" aria-hidden=\"true\">\n @if (isOptionSelected(opt)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n @if (isAttributeMode) {\n <span class=\"fc-attr\" [class.at]=\"isNamedOption(opt)\">{{ optionLabel(opt) }}</span>\n } @else if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ optionLabel(opt) }}</span>\n }\n </button>\n }\n </div>\n } @else if (mode === 'daterange') {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectDatePreset(opt)\">\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n </button>\n }\n <div class=\"fc-divider\"></div>\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"selectedValue === 'custom'\" (click)=\"openCustomRange($event)\">\n <span class=\"fc-opt-label\">Custom range\u2026</span>\n </button>\n } @else {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectOption(opt)\">\n @if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n }\n </button>\n }\n }\n </div>\n }\n }\n}\n\n@if (mode === 'daterange') {\n <!-- hidden Material range input; the calendar is opened from the \"Custom range\u2026\" option -->\n <mat-date-range-input class=\"fc-hidden-range\" [rangePicker]=\"rangePicker\">\n <input matStartDate [(ngModel)]=\"dateRangeStart\" (dateChange)=\"onRangeStartChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n <input matEndDate [(ngModel)]=\"dateRangeEnd\" (dateChange)=\"onRangeEndChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n </mat-date-range-input>\n <mat-date-range-picker #rangePicker xPosition=\"start\" yPosition=\"below\"></mat-date-range-picker>\n}\n", styles: [":host{display:inline-flex;position:relative}.fc-hidden-range{position:absolute;left:0;top:100%;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden}.fc-divider{height:1px;margin:.25rem;background:var(--lt-border)}.fc-text{position:relative;display:flex;align-items:center}.fc-input{width:100%;height:1.875rem;padding:0 1.5rem 0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-input::placeholder{color:var(--placeholder)}.fc-input:focus{border-color:var(--lt-link)}.fc-pill{display:inline-flex;align-items:center;gap:.375rem;height:var(--lt-toolbar-height);max-width:14rem;padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:.8125rem;font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.fc-pill:hover,.fc-pill.is-open{border-color:var(--lt-link)}.fc-pill.has-value{border-color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.fc-pill-field{color:var(--lt-cell-text)}.fc-pill.has-value .fc-pill-field{color:var(--groupText)}.fc-pill.has-value .fc-pill-field:after{content:\":\"}.fc-pill-value{color:var(--lt-link);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-caret{flex:0 0 auto;display:inline-flex;color:var(--lt-icon)}.fc-clear{flex:0 0 auto;font-size:1rem;line-height:1;color:var(--lt-icon);cursor:pointer}.fc-clear:hover{color:var(--lt-icon-active)}.fc-text .fc-clear{position:absolute;right:.5rem}.fc-menu{position:fixed;z-index:1000;max-height:20rem;overflow-y:auto;padding:.375rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:fc-in .1s ease-out}@keyframes fc-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.fc-semantic{display:flex;gap:.1875rem;margin:.125rem 0 .4375rem;padding:.1875rem;background:color-mix(in srgb,var(--lt-border) 30%,var(--lt-control-bg));border:1px solid var(--lt-border);border-radius:var(--lt-radius)}.fc-semantic-opt{flex:1 1 0;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:500;color:var(--lt-cell-text);background:transparent;border:none;border-radius:calc(var(--lt-radius) - .0625rem);cursor:pointer}.fc-semantic-opt:hover:not(.active){color:var(--lt-link)}.fc-semantic-opt.active{color:var(--lt-link);background:var(--lt-control-bg);box-shadow:0 1px 2px color-mix(in srgb,var(--lt-cell-text) 14%,transparent)}.fc-opt{display:flex;align-items:center;gap:.5625rem;width:100%;padding:.5rem;font-family:var(--lt-font);font-size:.875rem;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.fc-opt:hover{background:var(--lt-row-hover)}.fc-opt.selected{color:var(--lt-link);font-weight:600}.fc-opt-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-check{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1.125rem;height:1.125rem;box-sizing:border-box;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg)}.fc-opt.selected .fc-check{background:var(--lt-link);border-color:var(--lt-link)}.fc-empty{padding:.5rem;font-size:.75rem;color:var(--groupText)}.fc-search{position:sticky;top:0;z-index:1;padding:.125rem 0 .4375rem;background:var(--lt-control-bg)}.fc-search-icon{position:absolute;left:.625rem;top:.125rem;display:flex;align-items:center;height:2.125rem;color:var(--lt-icon);pointer-events:none}.fc-search-input{width:100%;box-sizing:border-box;height:2.125rem;padding:0 .625rem 0 2rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-search-input::placeholder{color:var(--placeholder)}.fc-search-input:focus{border-color:var(--lt-link)}.fc-opt-list{display:flex;flex-direction:column}.fc-attr{display:inline-flex;align-items:center;padding:.1875rem .5rem;font-size:.8125rem;font-weight:600;line-height:1.4;border-radius:.25rem;color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-attr.at{color:var(--secondary);background:color-mix(in srgb,var(--secondary) 14%,transparent)}.fc-opt.selected .fc-attr{color:var(--lt-link)}.fc-opt.selected .fc-attr.at{color:var(--secondary)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
12332
12541
  }
12333
12542
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListFilterCellComponent, decorators: [{
12334
12543
  type: Component,
12335
- args: [{ selector: 'lib-list-filter-cell', standalone: false, template: "@switch (mode) {\n @case ('text') {\n <div class=\"fc-text\">\n <input\n class=\"fc-input\"\n type=\"text\"\n [placeholder]=\"column.filterPlaceholder || 'Filter'\"\n [(ngModel)]=\"textValue\"\n (keyup)=\"onTextInput()\"\n />\n @if (textValue) {\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n }\n </div>\n }\n @default {\n <button\n #trigger\n type=\"button\"\n class=\"fc-pill\"\n [class.has-value]=\"hasValue\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"fc-pill-field\">{{ column.label }}</span>\n @if (hasValue) {\n <span class=\"fc-pill-value\" [title]=\"valueLabel\">{{ valueLabel }}</span>\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n } @else {\n <span class=\"fc-caret\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 6\" width=\"9\" height=\"6\"><path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/></svg>\n </span>\n }\n </button>\n\n @if (open) {\n <div\n class=\"fc-menu\"\n (clickOutside)=\"closeMenu()\"\n [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px', 'min-width.px': menuWidth }\"\n >\n @if (mode === 'multi') {\n <div class=\"fc-search\">\n <svg class=\"fc-search-icon\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"fc-search-input\"\n type=\"text\"\n [(ngModel)]=\"attrSearch\"\n [placeholder]=\"isAttributeMode ? 'Search attributes' : 'Search'\"\n (click)=\"$event.stopPropagation()\"\n autocomplete=\"off\"\n />\n </div>\n @if (!filteredOptions.length) {\n <div class=\"fc-empty\">{{ options.length ? 'No matches' : (isAttributeMode ? 'No attributes available' : 'No options available') }}</div>\n }\n <div class=\"fc-opt-list\">\n @for (opt of filteredOptions; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"toggleOption(opt)\">\n <span class=\"fc-check\" aria-hidden=\"true\">\n @if (isOptionSelected(opt)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n @if (isAttributeMode) {\n <span class=\"fc-attr\" [class.at]=\"isNamedOption(opt)\">{{ optionLabel(opt) }}</span>\n } @else if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ optionLabel(opt) }}</span>\n }\n </button>\n }\n </div>\n } @else if (mode === 'daterange') {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectDatePreset(opt)\">\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n </button>\n }\n <div class=\"fc-divider\"></div>\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"selectedValue === 'custom'\" (click)=\"openCustomRange($event)\">\n <span class=\"fc-opt-label\">Custom range\u2026</span>\n </button>\n } @else {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectOption(opt)\">\n @if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n }\n </button>\n }\n }\n </div>\n }\n }\n}\n\n@if (mode === 'daterange') {\n <!-- hidden Material range input; the calendar is opened from the \"Custom range\u2026\" option -->\n <mat-date-range-input class=\"fc-hidden-range\" [rangePicker]=\"rangePicker\">\n <input matStartDate [(ngModel)]=\"dateRangeStart\" (dateChange)=\"onRangeStartChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n <input matEndDate [(ngModel)]=\"dateRangeEnd\" (dateChange)=\"onRangeEndChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n </mat-date-range-input>\n <mat-date-range-picker #rangePicker xPosition=\"start\" yPosition=\"below\"></mat-date-range-picker>\n}\n", styles: [":host{display:inline-flex;position:relative}.fc-hidden-range{position:absolute;left:0;top:100%;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden}.fc-divider{height:1px;margin:.25rem;background:var(--lt-border)}.fc-text{position:relative;display:flex;align-items:center}.fc-input{width:100%;height:1.875rem;padding:0 1.5rem 0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-input::placeholder{color:var(--placeholder)}.fc-input:focus{border-color:var(--lt-link)}.fc-pill{display:inline-flex;align-items:center;gap:.375rem;height:var(--lt-toolbar-height);max-width:14rem;padding:0 .75rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.fc-pill:hover,.fc-pill.is-open{border-color:var(--lt-link)}.fc-pill.has-value{border-color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.fc-pill-field{color:var(--lt-cell-text)}.fc-pill.has-value .fc-pill-field{color:var(--groupText)}.fc-pill.has-value .fc-pill-field:after{content:\":\"}.fc-pill-value{color:var(--lt-link);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-caret{flex:0 0 auto;display:inline-flex;color:var(--lt-icon)}.fc-clear{flex:0 0 auto;font-size:1rem;line-height:1;color:var(--lt-icon);cursor:pointer}.fc-clear:hover{color:var(--lt-icon-active)}.fc-text .fc-clear{position:absolute;right:.5rem}.fc-menu{position:fixed;z-index:1000;max-height:18rem;overflow-y:auto;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:fc-in .1s ease-out}@keyframes fc-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.fc-opt{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.fc-opt:hover{background:var(--lt-row-hover)}.fc-opt.selected{color:var(--lt-link);font-weight:600}.fc-opt-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-check{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;box-sizing:border-box;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg)}.fc-opt.selected .fc-check{background:var(--lt-link);border-color:var(--lt-link)}.fc-empty{padding:.5rem;font-size:.75rem;color:var(--groupText)}.fc-search{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:.375rem;padding:.125rem .25rem .375rem;background:var(--lt-control-bg)}.fc-search-icon{flex:0 0 auto;margin-left:.25rem;color:var(--lt-icon)}.fc-search-input{flex:1 1 auto;min-width:0;height:1.75rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-search-input::placeholder{color:var(--placeholder)}.fc-search-input:focus{border-color:var(--lt-link)}.fc-opt-list{display:flex;flex-direction:column}.fc-attr{display:inline-flex;align-items:center;padding:.0625rem .375rem;font-size:.75rem;font-weight:600;line-height:1.4;border-radius:.25rem;color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-attr.at{color:var(--secondary);background:color-mix(in srgb,var(--secondary) 14%,transparent)}.fc-opt.selected .fc-attr{color:var(--lt-link)}.fc-opt.selected .fc-attr.at{color:var(--secondary)}\n"] }]
12544
+ args: [{ selector: 'lib-list-filter-cell', standalone: false, template: "@switch (mode) {\n @case ('text') {\n <div class=\"fc-text\">\n <input\n class=\"fc-input\"\n type=\"text\"\n [placeholder]=\"column.filterPlaceholder || 'Filter'\"\n [(ngModel)]=\"textValue\"\n (keyup)=\"onTextInput()\"\n />\n @if (textValue) {\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n }\n </div>\n }\n @default {\n <button\n #trigger\n type=\"button\"\n class=\"fc-pill\"\n [class.has-value]=\"hasValue\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"fc-pill-field\">{{ column.label }}</span>\n @if (hasValue) {\n <span class=\"fc-pill-value\" [title]=\"valueLabel\">{{ valueLabel }}</span>\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n } @else {\n <span class=\"fc-caret\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 6\" width=\"9\" height=\"6\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M1 1.5 5 5 9 1.5\"/></svg>\n </span>\n }\n </button>\n\n @if (open) {\n <div\n class=\"fc-menu\"\n (clickOutside)=\"closeMenu()\"\n [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px', 'min-width.px': menuWidth }\"\n >\n @if (mode === 'multi') {\n <div class=\"fc-search\">\n <svg class=\"fc-search-icon\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"fc-search-input\"\n type=\"text\"\n [(ngModel)]=\"attrSearch\"\n [placeholder]=\"isAttributeMode ? 'Search attributes' : 'Search'\"\n (click)=\"$event.stopPropagation()\"\n autocomplete=\"off\"\n />\n </div>\n @if (isAttributeMode) {\n <div class=\"fc-semantic\" role=\"group\" aria-label=\"Match\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"fc-semantic-opt\" [class.active]=\"attrSemantic === 'AnyOf'\" (click)=\"setSemantic('AnyOf')\">Any of</button>\n <button type=\"button\" class=\"fc-semantic-opt\" [class.active]=\"attrSemantic === 'AllOf'\" (click)=\"setSemantic('AllOf')\">All of</button>\n </div>\n }\n @if (!filteredOptions.length) {\n <div class=\"fc-empty\">{{ options.length ? 'No matches' : (isAttributeMode ? 'No attributes available' : 'No options available') }}</div>\n }\n <div class=\"fc-opt-list\">\n @for (opt of filteredOptions; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"toggleOption(opt)\">\n <span class=\"fc-check\" aria-hidden=\"true\">\n @if (isOptionSelected(opt)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n @if (isAttributeMode) {\n <span class=\"fc-attr\" [class.at]=\"isNamedOption(opt)\">{{ optionLabel(opt) }}</span>\n } @else if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ optionLabel(opt) }}</span>\n }\n </button>\n }\n </div>\n } @else if (mode === 'daterange') {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectDatePreset(opt)\">\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n </button>\n }\n <div class=\"fc-divider\"></div>\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"selectedValue === 'custom'\" (click)=\"openCustomRange($event)\">\n <span class=\"fc-opt-label\">Custom range\u2026</span>\n </button>\n } @else {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectOption(opt)\">\n @if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n }\n </button>\n }\n }\n </div>\n }\n }\n}\n\n@if (mode === 'daterange') {\n <!-- hidden Material range input; the calendar is opened from the \"Custom range\u2026\" option -->\n <mat-date-range-input class=\"fc-hidden-range\" [rangePicker]=\"rangePicker\">\n <input matStartDate [(ngModel)]=\"dateRangeStart\" (dateChange)=\"onRangeStartChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n <input matEndDate [(ngModel)]=\"dateRangeEnd\" (dateChange)=\"onRangeEndChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n </mat-date-range-input>\n <mat-date-range-picker #rangePicker xPosition=\"start\" yPosition=\"below\"></mat-date-range-picker>\n}\n", styles: [":host{display:inline-flex;position:relative}.fc-hidden-range{position:absolute;left:0;top:100%;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden}.fc-divider{height:1px;margin:.25rem;background:var(--lt-border)}.fc-text{position:relative;display:flex;align-items:center}.fc-input{width:100%;height:1.875rem;padding:0 1.5rem 0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-input::placeholder{color:var(--placeholder)}.fc-input:focus{border-color:var(--lt-link)}.fc-pill{display:inline-flex;align-items:center;gap:.375rem;height:var(--lt-toolbar-height);max-width:14rem;padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:.8125rem;font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.fc-pill:hover,.fc-pill.is-open{border-color:var(--lt-link)}.fc-pill.has-value{border-color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.fc-pill-field{color:var(--lt-cell-text)}.fc-pill.has-value .fc-pill-field{color:var(--groupText)}.fc-pill.has-value .fc-pill-field:after{content:\":\"}.fc-pill-value{color:var(--lt-link);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-caret{flex:0 0 auto;display:inline-flex;color:var(--lt-icon)}.fc-clear{flex:0 0 auto;font-size:1rem;line-height:1;color:var(--lt-icon);cursor:pointer}.fc-clear:hover{color:var(--lt-icon-active)}.fc-text .fc-clear{position:absolute;right:.5rem}.fc-menu{position:fixed;z-index:1000;max-height:20rem;overflow-y:auto;padding:.375rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:fc-in .1s ease-out}@keyframes fc-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.fc-semantic{display:flex;gap:.1875rem;margin:.125rem 0 .4375rem;padding:.1875rem;background:color-mix(in srgb,var(--lt-border) 30%,var(--lt-control-bg));border:1px solid var(--lt-border);border-radius:var(--lt-radius)}.fc-semantic-opt{flex:1 1 0;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:500;color:var(--lt-cell-text);background:transparent;border:none;border-radius:calc(var(--lt-radius) - .0625rem);cursor:pointer}.fc-semantic-opt:hover:not(.active){color:var(--lt-link)}.fc-semantic-opt.active{color:var(--lt-link);background:var(--lt-control-bg);box-shadow:0 1px 2px color-mix(in srgb,var(--lt-cell-text) 14%,transparent)}.fc-opt{display:flex;align-items:center;gap:.5625rem;width:100%;padding:.5rem;font-family:var(--lt-font);font-size:.875rem;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.fc-opt:hover{background:var(--lt-row-hover)}.fc-opt.selected{color:var(--lt-link);font-weight:600}.fc-opt-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-check{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1.125rem;height:1.125rem;box-sizing:border-box;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg)}.fc-opt.selected .fc-check{background:var(--lt-link);border-color:var(--lt-link)}.fc-empty{padding:.5rem;font-size:.75rem;color:var(--groupText)}.fc-search{position:sticky;top:0;z-index:1;padding:.125rem 0 .4375rem;background:var(--lt-control-bg)}.fc-search-icon{position:absolute;left:.625rem;top:.125rem;display:flex;align-items:center;height:2.125rem;color:var(--lt-icon);pointer-events:none}.fc-search-input{width:100%;box-sizing:border-box;height:2.125rem;padding:0 .625rem 0 2rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-search-input::placeholder{color:var(--placeholder)}.fc-search-input:focus{border-color:var(--lt-link)}.fc-opt-list{display:flex;flex-direction:column}.fc-attr{display:inline-flex;align-items:center;padding:.1875rem .5rem;font-size:.8125rem;font-weight:600;line-height:1.4;border-radius:.25rem;color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-attr.at{color:var(--secondary);background:color-mix(in srgb,var(--secondary) 14%,transparent)}.fc-opt.selected .fc-attr{color:var(--lt-link)}.fc-opt.selected .fc-attr.at{color:var(--secondary)}\n"] }]
12336
12545
  }], ctorParameters: () => [{ type: DataTableFilterService }, { type: ListTableService }], propDecorators: { column: [{
12337
12546
  type: Input
12338
12547
  }], trigger: [{
@@ -12376,6 +12585,87 @@ class ListTableComponent {
12376
12585
  this.buildColumns();
12377
12586
  }
12378
12587
  }
12588
+ set rowData(value) {
12589
+ this._rowData = value || [];
12590
+ this.rebuildFlatRows();
12591
+ }
12592
+ get rowData() {
12593
+ return this._rowData;
12594
+ }
12595
+ // fitColumns: keep the table within its visible width when resizing - a column can only grow until
12596
+ // the other columns reach their set or minimum width, then the drag stops, so nothing is pushed
12597
+ // off screen into the horizontal scroll. Off by default (columns can grow past the view and scroll).
12598
+ set options(value) {
12599
+ const wasTree = !!this._options?.tree;
12600
+ this._options = value || {};
12601
+ // rebuild the render list if tree mode was flipped after init, otherwise flatRows
12602
+ // keeps the pre-toggle shape
12603
+ if (this._initialized && !!this._options.tree !== wasTree) {
12604
+ this.rebuildFlatRows();
12605
+ }
12606
+ }
12607
+ get options() {
12608
+ return this._options;
12609
+ }
12610
+ /** Tree mode: columns per depth level (index = level; level 0 uses `columns`).
12611
+ * Nested rows render in their own grid with a per-level sub-header, so children
12612
+ * need not match the parent. Unset renders children with the parent's columns.
12613
+ * The bound value is the default; users can show/hide/reorder/resize child columns
12614
+ * per depth from the Columns menu, persisted per table. */
12615
+ set childColumnsByLevel(v) {
12616
+ this._declaredChildColumnsByLevel = v;
12617
+ this.buildChildColumns();
12618
+ }
12619
+ get childColumnsByLevel() {
12620
+ return this._childColumnsByLevel;
12621
+ }
12622
+ invalidateColumnCache() {
12623
+ this._childColsVersion++;
12624
+ }
12625
+ syncTreeMemo() {
12626
+ if (this._treeMemoVersion !== this._childColsVersion) {
12627
+ this._treeKeyCache.clear();
12628
+ this._leadOffsetCache.clear();
12629
+ this._treeMemoVersion = this._childColsVersion;
12630
+ }
12631
+ }
12632
+ /** Rebuilds the effective per-level child columns from the declared defaults with
12633
+ * any persisted order/visibility/width applied. */
12634
+ buildChildColumns() {
12635
+ const declared = this._declaredChildColumnsByLevel;
12636
+ if (!declared) {
12637
+ this._childColumnsByLevel = undefined;
12638
+ this.invalidateColumnCache();
12639
+ return;
12640
+ }
12641
+ this._childColumnsByLevel = declared.map((set, level) => set && set.length ? this.svc.applyPersistedChildState(level, set) : (set ? [...set] : set));
12642
+ this.invalidateColumnCache();
12643
+ }
12644
+ /** Tree look: 'card' floats each nested level as a bordered card; 'flat' renders
12645
+ * nested rows inline with the table, sub-headers as plain strips, no card chrome.
12646
+ * The bound value is the default; the user can switch it from the Columns menu and
12647
+ * the choice is remembered per table. */
12648
+ set treeVariant(v) {
12649
+ this._declaredTreeVariant = v;
12650
+ this._treeVariant = v;
12651
+ }
12652
+ get treeVariant() {
12653
+ return this._treeVariant;
12654
+ }
12655
+ get isFlatTree() {
12656
+ return this.isTreeMode && this.treeVariant === 'flat';
12657
+ }
12658
+ /** Show the flat/nested switch only when the table actually renders nested rows. */
12659
+ get canSwitchTreeVariant() {
12660
+ return this.isTreeMode && this.hasChildColumns;
12661
+ }
12662
+ setTreeVariant(variant) {
12663
+ if (this._treeVariant === variant) {
12664
+ return;
12665
+ }
12666
+ this._treeVariant = variant;
12667
+ this.svc.saveTreeVariant(variant);
12668
+ }
12379
12669
  constructor(svc, tableFilterService, el, ngZone, sanitizer) {
12380
12670
  this.svc = svc;
12381
12671
  this.tableFilterService = tableFilterService;
@@ -12383,9 +12673,19 @@ class ListTableComponent {
12383
12673
  this.ngZone = ngZone;
12384
12674
  this.sanitizer = sanitizer;
12385
12675
  this._tableId = '';
12386
- this.rowData = [];
12676
+ this._rowData = [];
12387
12677
  this.isLoading = false;
12388
- this.options = { noSelect: false, noMenu: false };
12678
+ this._options = { noSelect: false, noMenu: false };
12679
+ // Cached per level; a width change mutates the same column objects, so no rebuild is needed.
12680
+ this._childLevelCache = new Map();
12681
+ this._childColsVersion = 0;
12682
+ this._childLevelCacheVersion = -1;
12683
+ // per-level derived values, recomputed only when the column set changes, so the
12684
+ // template's per-cell/per-row calls stay cheap on every change-detection pass
12685
+ this._treeKeyCache = new Map();
12686
+ this._leadOffsetCache = new Map();
12687
+ this._treeMemoVersion = -1;
12688
+ this.nodeToggled = new EventEmitter();
12389
12689
  this.hasNext = false;
12390
12690
  this.hasPrev = false;
12391
12691
  this.startCount = '-';
@@ -12403,6 +12703,8 @@ class ListTableComponent {
12403
12703
  this.filterColumn = 'name';
12404
12704
  this.actionRequested = new EventEmitter();
12405
12705
  this.tableReady = new EventEmitter();
12706
+ /** Full set of selected rows (all depths) whenever the selection changes. */
12707
+ this.selectionChanged = new EventEmitter();
12406
12708
  /** Full column list (includes hidden), after persisted state. */
12407
12709
  this.allColumns = [];
12408
12710
  /** Currently rendered columns (visible + ordered). */
@@ -12418,6 +12720,28 @@ class ListTableComponent {
12418
12720
  this.selectedItem = { actionList: [] };
12419
12721
  // ---- selection ----
12420
12722
  this.allToggled = false;
12723
+ /** Rows actually rendered: in tree mode this is the roots plus the expanded
12724
+ * descendants in order; otherwise it is `rowData` unchanged. */
12725
+ this.flatRows = [];
12726
+ this._treeVariant = 'card';
12727
+ this._declaredTreeVariant = 'card';
12728
+ /** Show the flat/nested row-layout switch in the Columns menu. Hidden for now;
12729
+ * the variant still comes from the `treeVariant` input. */
12730
+ this.showTreeVariantToggle = false;
12731
+ /** Flat variant only: draw a running guide rail down the left of each nested level. */
12732
+ this.treeRail = false;
12733
+ this.treeIndentRem = 1.25;
12734
+ // nudges cards past the first nested level right so their edge lands on the
12735
+ // parent chevron (a nested chevron sits one grid-padding deeper than the root's)
12736
+ this.treeDeepAlignRem = 0.625;
12737
+ // flat first-column indent: base clears the select gutter, then one chevron-width
12738
+ // per level so each sub-header lines up under its parent row's name
12739
+ this.treeFlatBaseRem = 3.75;
12740
+ this.treeFlatStepRem = 2.125;
12741
+ // nested card's left inset before per-depth indent; must match the --lt-card-inset token
12742
+ this.treeCardBaseRem = 4;
12743
+ // width of the leading checkbox column injected into nested grids when selectChildren is on
12744
+ this.treeSelectColRem = 2.25;
12421
12745
  // ---- column chooser (skinned show/hide + reorder panel) ----
12422
12746
  this.showColumnChooser = false;
12423
12747
  // ---- integrated toolbar: search + filter chips + pagination ----
@@ -12440,16 +12764,28 @@ class ListTableComponent {
12440
12764
  this._initialColumns = [];
12441
12765
  this._initialized = false;
12442
12766
  this.subscription = new Subscription();
12443
- this.trackRow = (index, row) => row?.id ?? row?.name ?? index;
12767
+ // expansion + lazily-loaded children keyed by __treeRowKey, so a rowData refresh
12768
+ // (auto-refresh, paging, filter) keeps the tree open instead of collapsing it
12769
+ this._expandedKeys = new Set();
12770
+ this._childrenCache = new Map();
12771
+ this._destroyed = false;
12772
+ this.trackRow = (index, row) => row?.__treeRowKey ?? row?.id ?? row?.name ?? index;
12444
12773
  this.trackCol = (index, col) => col.key;
12445
12774
  // ---------------------------------------------------------------- hover tooltip
12446
12775
  this.hoverTooltip = null;
12447
- // ---------------------------------------------------------------- selection
12448
12776
  this.toggleItem = (item) => {
12449
12777
  item.selected = !item.selected;
12450
- this.allToggled = !_.isEmpty(this.rowData) && _.every(this.rowData, { selected: true });
12778
+ this.syncSelectAll();
12451
12779
  this.actionRequested.emit({ action: 'toggleItem', item });
12452
- };
12780
+ this.emitSelection();
12781
+ };
12782
+ this._railCache = new Map();
12783
+ // The whole chooser row is draggable, and the label sits inside the visibility
12784
+ // toggle button, so a reorder drag ends with a click on that button. This guard
12785
+ // swallows that one click so dragging a row never also hides its column.
12786
+ this._chooserDragged = false;
12787
+ // ---- nested-column chooser: one expandable group per depth level ----
12788
+ this.nestedChooserOpen = new Set();
12453
12789
  // ---------------------------------------------------------------- action menus
12454
12790
  this.openActionMenu = (event, item) => {
12455
12791
  this.selectedItem = item;
@@ -12485,7 +12821,12 @@ class ListTableComponent {
12485
12821
  this._initialized = true;
12486
12822
  // set directly (no emit) to avoid forcing a second fetch when a host pre-seeded it
12487
12823
  this.tableFilterService.pageSize = this.svc.restorePageSize(this.initialPageSize);
12824
+ this._treeVariant = this.svc.restoreTreeVariant(this._declaredTreeVariant);
12825
+ this.buildChildColumns();
12488
12826
  this.buildColumns();
12827
+ // all inputs are bound by now, so settle the render list against the final
12828
+ // tree flag (the rowData setter may have run before `options` arrived)
12829
+ this.rebuildFlatRows();
12489
12830
  this.updateEntityTypeLabel();
12490
12831
  this.subscription.add(this.tableFilterService.filtersChanged.subscribe((filters) => {
12491
12832
  // chips: every non-hidden filter except the free-text search (shown in the box)
@@ -12514,6 +12855,7 @@ class ListTableComponent {
12514
12855
  this.ensureFilterResizeObserver();
12515
12856
  }
12516
12857
  ngOnDestroy() {
12858
+ this._destroyed = true;
12517
12859
  this.subscription.unsubscribe();
12518
12860
  this.detachResizeListeners();
12519
12861
  this.filterRO?.disconnect();
@@ -12683,6 +13025,7 @@ class ListTableComponent {
12683
13025
  this.hiddenColumns = this.allColumns
12684
13026
  .filter((c) => c.hidden)
12685
13027
  .map((c) => ({ ...c, headerName: c.label }));
13028
+ this.invalidateColumnCache();
12686
13029
  }
12687
13030
  colWidth(col) {
12688
13031
  return col.width != null ? `${col.width}px` : null;
@@ -12751,17 +13094,325 @@ class ListTableComponent {
12751
13094
  onCellLeave() {
12752
13095
  this.hoverTooltip = null;
12753
13096
  }
13097
+ // ---------------------------------------------------------------- selection
13098
+ /** True when nested rows get checkboxes and join the selection (opt-in). */
13099
+ get childSelectEnabled() {
13100
+ return !!this.options?.selectChildren && !this.options?.noSelect;
13101
+ }
13102
+ /** Rows the checkboxes and select-all act on: top-level only, unless selectChildren
13103
+ * is set. Keeps select-all from marking nested rows a host that reads rowData never sees. */
13104
+ get selectableRows() {
13105
+ if (!this.isTreeMode || this.childSelectEnabled) {
13106
+ return this.flatRows;
13107
+ }
13108
+ return this.flatRows.filter((r) => (r.__treeLevel ?? 0) === 0);
13109
+ }
12754
13110
  /** Some rows selected but the header select-all isn't fully on (indeterminate). */
12755
13111
  get someSelected() {
12756
- return !this.allToggled && _.some(this.rowData, { selected: true });
13112
+ return !this.allToggled && this.selectableRows.some((r) => r.selected);
12757
13113
  }
12758
13114
  toggleAll() {
12759
13115
  const newState = !this.allToggled;
12760
13116
  this.allToggled = newState;
12761
- _.forEach(this.rowData, (row) => {
12762
- row.selected = newState;
12763
- });
13117
+ this.selectableRows.forEach((row) => (row.selected = newState));
12764
13118
  this.actionRequested.emit({ action: 'toggleAll' });
13119
+ this.emitSelection();
13120
+ }
13121
+ syncSelectAll() {
13122
+ const rows = this.selectableRows;
13123
+ this.allToggled = rows.length > 0 && rows.every((r) => r.selected);
13124
+ }
13125
+ /** Emit every selected row across all depths. Walks the full data, not just the rendered
13126
+ * rows, so a collapsed parent's selected children are still reported. */
13127
+ emitSelection() {
13128
+ const out = [];
13129
+ const walk = (rows) => {
13130
+ (rows || []).forEach((r) => {
13131
+ if (r.selected) {
13132
+ out.push(r);
13133
+ }
13134
+ if (Array.isArray(r.children)) {
13135
+ walk(r.children);
13136
+ }
13137
+ });
13138
+ };
13139
+ walk(this._rowData);
13140
+ this.selectionChanged.emit(out);
13141
+ }
13142
+ // ---------------------------------------------------------------- tree rows
13143
+ get isTreeMode() {
13144
+ return !!this.options?.tree;
13145
+ }
13146
+ /** True when per-level child columns are configured (children render in their own
13147
+ * grid with a sub-header rather than the parent's columns). */
13148
+ get hasChildColumns() {
13149
+ return !!(this.childColumnsByLevel && this.childColumnsByLevel.length);
13150
+ }
13151
+ /** Columns used at a given depth: level 0 is the main (visible) columns; deeper
13152
+ * levels use `childColumnsByLevel`, falling back to the deepest set defined. */
13153
+ columnsForLevel(level) {
13154
+ if (level <= 0 || !this.hasChildColumns) {
13155
+ return this.visibleColumns;
13156
+ }
13157
+ if (this._childLevelCacheVersion !== this._childColsVersion) {
13158
+ this._childLevelCache.clear();
13159
+ this._childLevelCacheVersion = this._childColsVersion;
13160
+ }
13161
+ const cached = this._childLevelCache.get(level);
13162
+ if (cached) {
13163
+ return cached;
13164
+ }
13165
+ const sets = this.childColumnsByLevel;
13166
+ const set = sets[level] ?? sets[sets.length - 1];
13167
+ const cols = (set ?? this.visibleColumns).filter((c) => !c.hidden);
13168
+ this._childLevelCache.set(level, cols);
13169
+ return cols;
13170
+ }
13171
+ /** Child columns the chooser lists at a depth: all declared ones (including hidden),
13172
+ * minus structural columns like the select checkbox or row-actions gutter. */
13173
+ allColumnsForLevel(level) {
13174
+ return (this._childColumnsByLevel?.[level] ?? []).filter((c) => !c.excludeFromChooser);
13175
+ }
13176
+ /** Depths that offer configurable columns (skips the level-0 placeholder and any
13177
+ * level that only holds structural columns), one chooser group per level. */
13178
+ get childLevels() {
13179
+ const sets = this._childColumnsByLevel;
13180
+ if (!sets) {
13181
+ return [];
13182
+ }
13183
+ const levels = [];
13184
+ sets.forEach((_set, level) => {
13185
+ if (level > 0 && this.allColumnsForLevel(level).length) {
13186
+ levels.push(level);
13187
+ }
13188
+ });
13189
+ return levels;
13190
+ }
13191
+ /** Chooser hint for a nested level: its first few column labels, then "+N". */
13192
+ childLevelLabel(level) {
13193
+ const labels = this.allColumnsForLevel(level)
13194
+ .map((c) => c.label)
13195
+ .filter(Boolean);
13196
+ const max = 3;
13197
+ if (labels.length <= max) {
13198
+ return labels.join(' / ');
13199
+ }
13200
+ return labels.slice(0, max).join(' / ') + ' +' + (labels.length - max);
13201
+ }
13202
+ /** Key of the column that carries the expand control + indentation for a column set:
13203
+ * the flagged one, else the first real data column (skipping a leading structural
13204
+ * column such as a select checkbox the host put at the start). */
13205
+ treeColumnKeyFor(cols) {
13206
+ return (cols.find((c) => c.treeColumn) || cols.find((c) => !c.excludeFromChooser) || cols[0])?.key;
13207
+ }
13208
+ treeColumnKeyForLevel(level) {
13209
+ this.syncTreeMemo();
13210
+ if (!this._treeKeyCache.has(level)) {
13211
+ this._treeKeyCache.set(level, this.treeColumnKeyFor(this.columnsForLevel(level)));
13212
+ }
13213
+ return this._treeKeyCache.get(level);
13214
+ }
13215
+ /** Width of the columns before the tree column, used to pull the flat first-column
13216
+ * indent back by whatever structural columns (like a select checkbox) sit ahead of
13217
+ * it, so its content still lands under the parent row's name. */
13218
+ treeLeadOffset(level) {
13219
+ this.syncTreeMemo();
13220
+ const cacheKey = `${level}|${this.childSelectEnabled}`;
13221
+ const hit = this._leadOffsetCache.get(cacheKey);
13222
+ if (hit !== undefined) {
13223
+ return hit;
13224
+ }
13225
+ const cols = this.columnsForLevel(level);
13226
+ const treeKey = this.treeColumnKeyForLevel(level);
13227
+ let px = 0;
13228
+ let count = 0;
13229
+ for (const c of cols) {
13230
+ if (c.key === treeKey) {
13231
+ break;
13232
+ }
13233
+ px += c.width ?? 0;
13234
+ count++;
13235
+ }
13236
+ const gap = 'var(--lt-tree-col-gap, 0.5rem)';
13237
+ // the injected select column (when on) sits before every data column
13238
+ const selRem = this.childSelectEnabled ? this.treeSelectColRem : 0;
13239
+ const gaps = count + (this.childSelectEnabled ? 1 : 0);
13240
+ const result = (!gaps && !selRem && !px) ? '0px' : `calc(${selRem}rem + ${px}px + ${gaps} * ${gap})`;
13241
+ this._leadOffsetCache.set(cacheKey, result);
13242
+ return result;
13243
+ }
13244
+ isTreeColumn(col, level) {
13245
+ return this.isTreeMode && col.key === this.treeColumnKeyForLevel(level);
13246
+ }
13247
+ isTreeCell(col) {
13248
+ return this.isTreeColumn(col, 0);
13249
+ }
13250
+ /** Columns a full-width nested row spans: the data columns plus the select and
13251
+ * menu gutters, matching the header's cell count. */
13252
+ get treeSpanCount() {
13253
+ return this.visibleColumns.length + (this.options?.noSelect ? 0 : 1) + (this.options?.noMenu ? 0 : 1);
13254
+ }
13255
+ /** Per-level indent (rem); the card variant nudges levels past the first so the edge meets the parent chevron. */
13256
+ treeIndentFor(level) {
13257
+ const align = this.treeVariant === 'card' && level >= 2 ? this.treeDeepAlignRem : 0;
13258
+ return level * this.treeIndentRem + align;
13259
+ }
13260
+ /** Flat variant: left pad (rem) of a nested row's first column (sub-header + data),
13261
+ * one chevron-width per level so it lines up under the parent row's name. */
13262
+ treeFlatFirstIndentRem(level) {
13263
+ return this.treeFlatBaseRem + level * this.treeFlatStepRem;
13264
+ }
13265
+ /** Flat variant: drop the chevron slot for a leaf-only level so values sit under their header. */
13266
+ showTreeLead(row) {
13267
+ return !(this.isFlatTree && row?.__treeSiblingsCanExpand === false);
13268
+ }
13269
+ /** Left inset (rem) of a nested card at a given depth, matching the td padding. */
13270
+ treeCardLeftRem(level) {
13271
+ return this.treeCardBaseRem + this.treeIndentFor(level);
13272
+ }
13273
+ /** Vertical guide lines redrawing each ancestor card's left edge at this row's
13274
+ * position, so a card's left border stays continuous where a nested card is
13275
+ * inserted between its rows. Returns a stack of 1px background gradients, or null
13276
+ * when there is no ancestor to redraw (roots and the first nested level). */
13277
+ treeAncestorRails(level) {
13278
+ const key = `${level}|${this._treeVariant}|${this.treeRail}|${this.isTreeMode}`;
13279
+ const cached = this._railCache.get(key);
13280
+ if (cached !== undefined) {
13281
+ return cached;
13282
+ }
13283
+ const rails = this.computeAncestorRails(level);
13284
+ this._railCache.set(key, rails);
13285
+ return rails;
13286
+ }
13287
+ computeAncestorRails(level) {
13288
+ if (!this.isTreeMode || level < 2) {
13289
+ return null;
13290
+ }
13291
+ const flat = this.treeVariant === 'flat';
13292
+ // card mode always redraws ancestor card borders; flat mode draws the rail
13293
+ // only when the optional running rail is turned on
13294
+ if (flat && !this.treeRail) {
13295
+ return null;
13296
+ }
13297
+ const color = flat
13298
+ ? 'var(--lt-tree-rail, color-mix(in srgb, var(--lt-border) 55%, var(--lt-icon)))'
13299
+ : 'var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)))';
13300
+ const rails = [];
13301
+ for (let k = 1; k < level; k++) {
13302
+ const x = this.treeCardLeftRem(k);
13303
+ rails.push(`linear-gradient(90deg, transparent ${x}rem, ${color} ${x}rem, ${color} calc(${x}rem + 1px), transparent calc(${x}rem + 1px))`);
13304
+ }
13305
+ return rails.join(', ');
13306
+ }
13307
+ gridTemplate(level) {
13308
+ const tracks = this.columnsForLevel(level)
13309
+ .map((c) => (c.width != null ? `${c.width}px` : 'minmax(0, 1fr)'));
13310
+ if (this.childSelectEnabled) {
13311
+ tracks.unshift(`${this.treeSelectColRem}rem`);
13312
+ }
13313
+ return tracks.join(' ');
13314
+ }
13315
+ hasChildren(row) {
13316
+ return Array.isArray(row?.children) && row.children.length > 0;
13317
+ }
13318
+ /** A row can be expanded unless it is a leaf, has no loaded children, and has no
13319
+ * loader left to try. */
13320
+ canExpand(row) {
13321
+ if (!this.isTreeMode || row?.leaf === true) {
13322
+ return false;
13323
+ }
13324
+ if (this.hasChildren(row)) {
13325
+ return true;
13326
+ }
13327
+ const key = row?.__treeRowKey;
13328
+ if (key && this._childrenCache.has(key)) {
13329
+ return true;
13330
+ }
13331
+ return !!this.loadChildren && row?.__treeLoaded !== true;
13332
+ }
13333
+ toggleNode(row) {
13334
+ if (!this.isTreeMode || row?.__treeLoading) {
13335
+ return;
13336
+ }
13337
+ const key = row.__treeRowKey;
13338
+ if (this._expandedKeys.has(key)) {
13339
+ this._expandedKeys.delete(key);
13340
+ this.rebuildFlatRows();
13341
+ this.nodeToggled.emit({ row, expanded: false });
13342
+ return;
13343
+ }
13344
+ if (this.loadChildren && !this.hasChildren(row) && !this._childrenCache.has(key)) {
13345
+ row.__treeLoading = true;
13346
+ row.__treeLoadError = false; // clear any previous failure on retry
13347
+ this.rebuildFlatRows(); // surface the spinner while children load
13348
+ // run the loader inside the executor so a synchronous throw rejects the
13349
+ // promise instead of escaping and leaving __treeLoading stuck on forever
13350
+ new Promise((resolve) => resolve(this.loadChildren(row))).then((children) => this.onChildrenLoaded(row, children), () => this.onChildrenLoaded(row, [], true));
13351
+ return;
13352
+ }
13353
+ this._expandedKeys.add(key);
13354
+ this.rebuildFlatRows();
13355
+ this.nodeToggled.emit({ row, expanded: true });
13356
+ }
13357
+ onChildrenLoaded(row, children, failed = false) {
13358
+ if (this._destroyed) {
13359
+ return;
13360
+ }
13361
+ row.__treeLoading = false;
13362
+ if (failed) {
13363
+ // flag the failure so the toggle shows a retry affordance, and leave it collapsed
13364
+ row.__treeLoadError = true;
13365
+ this.rebuildFlatRows();
13366
+ return;
13367
+ }
13368
+ const list = Array.isArray(children) ? children : [];
13369
+ this._childrenCache.set(row.__treeRowKey, list);
13370
+ row.children = list;
13371
+ row.__treeLoaded = true;
13372
+ if (!list.length) {
13373
+ row.leaf = true; // nothing under it, so stop offering the control
13374
+ }
13375
+ this._expandedKeys.add(row.__treeRowKey);
13376
+ this.rebuildFlatRows();
13377
+ this.nodeToggled.emit({ row, expanded: true });
13378
+ }
13379
+ /** Flatten the tree into the ordered render list, tagging each row with its depth.
13380
+ * Outside tree mode the render list is `rowData` as-is. */
13381
+ rebuildFlatRows() {
13382
+ if (!this.isTreeMode) {
13383
+ this.flatRows = this._rowData;
13384
+ return;
13385
+ }
13386
+ const out = [];
13387
+ const walk = (rows, level, parentKey) => {
13388
+ const list = rows || [];
13389
+ // when no sibling can expand, the level needs no chevron slot
13390
+ const siblingsCanExpand = list.some((r) => this.canExpand(r));
13391
+ list.forEach((row, idx) => {
13392
+ row.__treeLevel = level;
13393
+ row.__treeSiblingsCanExpand = siblingsCanExpand;
13394
+ // first child of a group drives the per-expansion sub-header;
13395
+ // first and last mark the top and bottom of the nested card
13396
+ row.__treeFirstChild = level > 0 && idx === 0;
13397
+ row.__treeLastChild = level > 0 && idx === list.length - 1;
13398
+ // branch-unique track key: the same entity can appear under two parents
13399
+ // in a flat tree, so fold the parent path in to keep @for keys distinct
13400
+ row.__treeRowKey = parentKey + '/' + (row?.id ?? row?.name ?? idx);
13401
+ // a rowData refresh replaces the row object; re-attach children we loaded
13402
+ // earlier so an expanded lazy node stays populated instead of emptying out
13403
+ if (!this.hasChildren(row) && this._childrenCache.has(row.__treeRowKey)) {
13404
+ row.children = this._childrenCache.get(row.__treeRowKey);
13405
+ row.__treeLoaded = true;
13406
+ }
13407
+ row.__treeExpanded = this._expandedKeys.has(row.__treeRowKey);
13408
+ out.push(row);
13409
+ if (row.__treeExpanded && this.hasChildren(row)) {
13410
+ walk(row.children, level + 1, row.__treeRowKey);
13411
+ }
13412
+ });
13413
+ };
13414
+ walk(this._rowData, 0, '');
13415
+ this.flatRows = out;
12765
13416
  }
12766
13417
  // ---------------------------------------------------------------- sort
12767
13418
  onHeaderSort(event) {
@@ -12769,16 +13420,7 @@ class ListTableComponent {
12769
13420
  c.sortDir = c.key === event.colId ? event.dir : undefined;
12770
13421
  });
12771
13422
  }
12772
- // ---------------------------------------------------------------- hide / show / reorder
12773
- hideColumn(col) {
12774
- const target = this.allColumns.find((c) => c.key === col.key);
12775
- if (!target) {
12776
- return;
12777
- }
12778
- target.hidden = true;
12779
- this.updateVisible();
12780
- this.svc.saveState(this.allColumns);
12781
- }
13423
+ // ---------------------------------------------------------------- show / reorder
12782
13424
  showColumn(event) {
12783
13425
  const target = this.allColumns.find((c) => c.key === event.column?.key);
12784
13426
  if (!target) {
@@ -12801,7 +13443,9 @@ class ListTableComponent {
12801
13443
  }
12802
13444
  resetTableColumns() {
12803
13445
  this.svc.reset();
13446
+ this._treeVariant = this._declaredTreeVariant;
12804
13447
  this.allColumns = this._initialColumns.map((c) => ({ ...c }));
13448
+ this._childColumnsByLevel = this._declaredChildColumnsByLevel?.map((set) => set ? set.map((c) => ({ ...c })) : set);
12805
13449
  this.updateVisible();
12806
13450
  this.closeActionMenu();
12807
13451
  }
@@ -12811,14 +13455,45 @@ class ListTableComponent {
12811
13455
  get hiddenColumnCount() {
12812
13456
  return this.allColumns.filter((c) => c.hidden && !c.excludeFromChooser && !this.isDefaultHidden(c)).length;
12813
13457
  }
12814
- /** True when visibility + order match the originally declared columns. */
13458
+ /** True when visibility, order, and width all match the originally declared
13459
+ * columns. Width is included so resizing a column alone still enables the
13460
+ * reset button; a column with no declared width uses null on both sides so
13461
+ * it only counts as changed once the user drags it to a concrete width. */
12815
13462
  get columnsAtDefault() {
13463
+ if (this.canSwitchTreeVariant && this._treeVariant !== this._declaredTreeVariant) {
13464
+ return false;
13465
+ }
12816
13466
  if (this.allColumns.length !== this._initialColumns.length) {
12817
13467
  return false;
12818
13468
  }
12819
- return this.allColumns.every((c, i) => {
13469
+ const parentAtDefault = this.allColumns.every((c, i) => {
12820
13470
  const init = this._initialColumns[i];
12821
- return init && init.key === c.key && !!init.hidden === !!c.hidden;
13471
+ return init
13472
+ && init.key === c.key
13473
+ && !!init.hidden === !!c.hidden
13474
+ && (init.width ?? null) === (c.width ?? null);
13475
+ });
13476
+ return parentAtDefault && this.childColumnsAtDefault;
13477
+ }
13478
+ /** True when every nested level's columns match their declared order/visibility/width. */
13479
+ get childColumnsAtDefault() {
13480
+ const declared = this._declaredChildColumnsByLevel;
13481
+ const current = this._childColumnsByLevel;
13482
+ if (!declared || !current) {
13483
+ return true;
13484
+ }
13485
+ return declared.every((set, level) => {
13486
+ const cur = current[level] ?? [];
13487
+ if (!set || set.length !== cur.length) {
13488
+ return set ? set.length === cur.length : true;
13489
+ }
13490
+ return set.every((d, i) => {
13491
+ const c = cur[i];
13492
+ return c
13493
+ && d.key === c.key
13494
+ && !!d.hidden === !!c.hidden
13495
+ && (d.width ?? null) === (c.width ?? null);
13496
+ });
12822
13497
  });
12823
13498
  }
12824
13499
  /** True when the column is hidden in the originally declared defaults. */
@@ -12836,36 +13511,82 @@ class ListTableComponent {
12836
13511
  closeColumnChooser() {
12837
13512
  this.showColumnChooser = false;
12838
13513
  }
12839
- toggleColumnVisible(col) {
12840
- if (col.hideable === false) {
13514
+ onChooserDragStarted() {
13515
+ this._chooserDragged = true;
13516
+ }
13517
+ onChooserDragEnded() {
13518
+ // clear after the post-drag click has been handled (and skipped) below
13519
+ setTimeout(() => (this._chooserDragged = false));
13520
+ }
13521
+ /** Toggle a column's visibility. `level` undefined targets the top-level columns;
13522
+ * a number targets that nested depth's columns. */
13523
+ toggleColumnVisible(col, level) {
13524
+ if (this._chooserDragged || col.hideable === false) {
12841
13525
  return;
12842
13526
  }
12843
- const target = this.allColumns.find((c) => c.key === col.key);
13527
+ const set = level == null ? this.allColumns : this._childColumnsByLevel?.[level];
13528
+ const target = set?.find((c) => c.key === col.key);
12844
13529
  if (!target) {
12845
13530
  return;
12846
13531
  }
13532
+ if (!target.hidden && this.isLastVisibleColumn(target, level)) {
13533
+ return;
13534
+ }
12847
13535
  target.hidden = !target.hidden;
12848
- this.updateVisible();
12849
- this.svc.saveState(this.allColumns);
13536
+ this.afterColumnChange(level, set);
12850
13537
  }
12851
- /** Columns shown in the Columns menu. */
13538
+ /** True when this is the only visible column at its level, so its toggle is disabled
13539
+ * to keep at least one column on screen. */
13540
+ isLastVisibleColumn(col, level) {
13541
+ const list = level == null ? this.chooserColumns : this.allColumnsForLevel(level);
13542
+ return !col.hidden && list.filter((c) => !c.hidden).length <= 1;
13543
+ }
13544
+ /** Columns shown in the top-level Columns menu. */
12852
13545
  get chooserColumns() {
12853
13546
  return this.allColumns.filter((c) => !c.excludeFromChooser);
12854
13547
  }
12855
- chooserReorder(event) {
13548
+ /** Reorder columns from a chooser drag. `level` undefined targets the top-level
13549
+ * columns; a number targets that nested depth's columns. */
13550
+ chooserReorder(event, level) {
12856
13551
  if (event.previousIndex === event.currentIndex) {
12857
13552
  return;
12858
13553
  }
12859
- // menu lists a subset, so map the drag indices back onto allColumns
12860
- const list = this.chooserColumns;
12861
- const from = this.allColumns.indexOf(list[event.previousIndex]);
12862
- const to = this.allColumns.indexOf(list[event.currentIndex]);
13554
+ // the menu lists a chooser subset, so map the drag indices back onto the full set
13555
+ const full = level == null ? this.allColumns : this._childColumnsByLevel?.[level];
13556
+ const list = level == null ? this.chooserColumns : this.allColumnsForLevel(level);
13557
+ if (!full) {
13558
+ return;
13559
+ }
13560
+ const from = full.indexOf(list[event.previousIndex]);
13561
+ const to = full.indexOf(list[event.currentIndex]);
12863
13562
  if (from < 0 || to < 0) {
12864
13563
  return;
12865
13564
  }
12866
- moveItemInArray(this.allColumns, from, to);
12867
- this.updateVisible();
12868
- this.svc.saveState(this.allColumns);
13565
+ moveItemInArray(full, from, to);
13566
+ this.afterColumnChange(level, full);
13567
+ }
13568
+ /** Refresh and persist after a column visibility/order change, routing to the
13569
+ * top-level or nested-level persistence as appropriate. */
13570
+ afterColumnChange(level, set) {
13571
+ if (level == null) {
13572
+ this.updateVisible();
13573
+ this.svc.saveState(this.allColumns);
13574
+ }
13575
+ else {
13576
+ this.invalidateColumnCache();
13577
+ this.svc.saveChildState(level, set);
13578
+ }
13579
+ }
13580
+ isNestedGroupOpen(id) {
13581
+ return this.nestedChooserOpen.has(id);
13582
+ }
13583
+ toggleNestedGroup(id) {
13584
+ if (this.nestedChooserOpen.has(id)) {
13585
+ this.nestedChooserOpen.delete(id);
13586
+ }
13587
+ else {
13588
+ this.nestedChooserOpen.add(id);
13589
+ }
12869
13590
  }
12870
13591
  // ---------------------------------------------------------------- resize
12871
13592
  startResize(col, event) {
@@ -12876,7 +13597,25 @@ class ListTableComponent {
12876
13597
  event.stopPropagation();
12877
13598
  const th = event.target?.closest('th');
12878
13599
  const startWidth = col.width ?? th?.offsetWidth ?? 150;
12879
- this.resizing = { col, startX: event.clientX, startWidth };
13600
+ this.resizing = { col, startX: event.clientX, startWidth, persist: true };
13601
+ this.setupFitCompensation(col, startWidth, this.visibleColumns, (key) => this.measuredHeaderWidth(key));
13602
+ document.addEventListener('pointermove', this.onPointerMove);
13603
+ document.addEventListener('pointerup', this.onPointerUp);
13604
+ }
13605
+ /** Resize a nested row's column. Measures the sub-header cell's current rendered
13606
+ * width so a flexible (1fr) column converts to a concrete width before dragging;
13607
+ * the grid reflows live because its template reads each column's width. */
13608
+ startChildResize(col, level, event) {
13609
+ if (col.resizable === false) {
13610
+ return;
13611
+ }
13612
+ event.preventDefault();
13613
+ event.stopPropagation();
13614
+ const cell = event.target?.closest('.lt-tree-subhead-cell');
13615
+ const startWidth = col.width ?? Math.round(cell?.getBoundingClientRect().width ?? 150);
13616
+ const grid = cell?.closest('.lt-tree-grid');
13617
+ this.resizing = { col, startX: event.clientX, startWidth, persist: true, level };
13618
+ this.setupFitCompensation(col, startWidth, this.columnsForLevel(level), (key) => this.measuredCellWidth(grid, level, key));
12880
13619
  document.addEventListener('pointermove', this.onPointerMove);
12881
13620
  document.addEventListener('pointerup', this.onPointerUp);
12882
13621
  }
@@ -12884,13 +13623,94 @@ class ListTableComponent {
12884
13623
  if (!this.resizing) {
12885
13624
  return;
12886
13625
  }
12887
- const delta = event.clientX - this.resizing.startX;
12888
- const min = this.resizing.col.minWidth ?? 40;
12889
- this.resizing.col.width = Math.max(min, Math.round(this.resizing.startWidth + delta));
13626
+ const r = this.resizing;
13627
+ const delta = event.clientX - r.startX;
13628
+ if (r.sink && r.sinkStart != null) {
13629
+ // fitColumns: the column grows and the sink shrinks by the same amount, so the table stays
13630
+ // the same width and no other column (notably the tree column) shifts. The drag stops once
13631
+ // the column or the sink reaches its minimum.
13632
+ const d = Math.min(r.maxDelta ?? delta, Math.max(r.minDelta ?? delta, delta));
13633
+ r.col.width = Math.round(r.startWidth + d);
13634
+ r.sink.width = Math.round(r.sinkStart - d);
13635
+ }
13636
+ else {
13637
+ const min = r.col.minWidth ?? 40;
13638
+ r.col.width = Math.max(min, Math.round(r.startWidth + delta));
13639
+ }
13640
+ }
13641
+ /** fitColumns: pick the sink column and the range the drag may travel, so the column and the sink
13642
+ * trade width while every other column stays fixed. No-op (plain resize) when fitColumns is off. */
13643
+ setupFitCompensation(col, startWidth, columns, measure) {
13644
+ if (!this.options?.fitColumns || !this.resizing) {
13645
+ return;
13646
+ }
13647
+ const sink = this.pickSink(col, columns);
13648
+ if (!sink) {
13649
+ return;
13650
+ }
13651
+ const FLOOR = 60;
13652
+ // measure() returns 0 when the element isn't found; fall back so a width of 0
13653
+ // never gets frozen in (and later persisted) for the sink or a flex column
13654
+ const sinkStart = sink.width ?? (measure(sink.key) || startWidth);
13655
+ // Freeze any other flexible column to its rendered width so only the column and the sink move
13656
+ // (otherwise the browser would still redistribute slack among the flexible columns). These are
13657
+ // restored to flexible when the drag ends, so one resize doesn't pin the whole table to px.
13658
+ const frozen = [];
13659
+ columns.forEach((c) => {
13660
+ if (c.key !== col.key && c.key !== sink.key && c.width == null) {
13661
+ const w = measure(c.key);
13662
+ if (w > 0) {
13663
+ c.width = w;
13664
+ frozen.push(c);
13665
+ }
13666
+ }
13667
+ });
13668
+ this.resizing.sink = sink;
13669
+ this.resizing.sinkStart = sinkStart;
13670
+ this.resizing.frozen = frozen;
13671
+ this.resizing.maxDelta = Math.max(0, sinkStart - (sink.minWidth ?? FLOOR)); // grow until the sink hits its min
13672
+ this.resizing.minDelta = Math.min(0, (col.minWidth ?? 40) - startWidth); // shrink until the column hits its min
13673
+ }
13674
+ /** The column that absorbs a resize: a flexible column first (it is meant to take up slack), else
13675
+ * the nearest resizable column that is not the tree column, so indentation never shifts. */
13676
+ pickSink(col, columns) {
13677
+ // never pick the tree column (explicit or implicit) as the sink, or resizing
13678
+ // would move the expand chevrons
13679
+ const treeKey = this.treeColumnKeyFor(columns);
13680
+ const usable = columns.filter((c) => c.key !== col.key && c.resizable !== false && c.key !== treeKey);
13681
+ const flex = usable.find((c) => c.width == null);
13682
+ if (flex) {
13683
+ return flex;
13684
+ }
13685
+ const idx = columns.findIndex((c) => c.key === col.key);
13686
+ return usable.find((c) => columns.indexOf(c) > idx) ?? usable[usable.length - 1];
13687
+ }
13688
+ measuredHeaderWidth(key) {
13689
+ const host = this.el?.nativeElement;
13690
+ const th = host?.querySelector(`[id="Header_${key}"]`);
13691
+ return th ? Math.round(th.getBoundingClientRect().width) : 0;
13692
+ }
13693
+ measuredCellWidth(grid, level, key) {
13694
+ const idx = this.columnsForLevel(level).findIndex((c) => c.key === key);
13695
+ const cells = grid?.querySelectorAll('.lt-tree-subhead-cell');
13696
+ const cell = cells ? cells[idx] : undefined;
13697
+ return cell ? Math.round(cell.getBoundingClientRect().width) : 0;
12890
13698
  }
12891
13699
  handleResizeEnd() {
12892
- if (this.resizing) {
12893
- this.svc.saveWidth(this.resizing.col.key, this.resizing.col.width, this.allColumns);
13700
+ const r = this.resizing;
13701
+ // columns frozen only to steady the drag go back to flexible, so the table keeps
13702
+ // filling the viewport and their temporary px widths are not persisted by saveState
13703
+ r?.frozen?.forEach((c) => (c.width = undefined));
13704
+ if (r?.persist) {
13705
+ if (r.level != null) {
13706
+ const set = this._childColumnsByLevel?.[r.level];
13707
+ if (set) {
13708
+ this.svc.saveChildWidth(r.level, r.col.key, r.col.width, set);
13709
+ }
13710
+ }
13711
+ else {
13712
+ this.svc.saveWidth(r.col.key, r.col.width, this.allColumns);
13713
+ }
12894
13714
  }
12895
13715
  this.resizing = undefined;
12896
13716
  this.detachResizeListeners();
@@ -13095,11 +13915,11 @@ class ListTableComponent {
13095
13915
  this.entityTypeLabel = map[this._tableId] || this.entityTypeName || '';
13096
13916
  }
13097
13917
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableComponent, deps: [{ token: ListTableService }, { token: DataTableFilterService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
13098
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableComponent, isStandalone: false, selector: "lib-list-table", inputs: { tableId: "tableId", columns: "columns", rowData: "rowData", isLoading: "isLoading", options: "options", hasNext: "hasNext", hasPrev: "hasPrev", startCount: "startCount", endCount: "endCount", totalCount: "totalCount", filterApplied: "filterApplied", menuItems: "menuItems", headerActions: "headerActions", noItemsImage: "noItemsImage", showNoItemsAdd: "showNoItemsAdd", noItemsReadonly: "noItemsReadonly", accessDenied: "accessDenied", entityTypeName: "entityTypeName", filterName: "filterName", filterColumn: "filterColumn", initialPageSize: "initialPageSize" }, outputs: { actionRequested: "actionRequested", tableReady: "tableReady" }, providers: [ListTableService], viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true }, { propertyName: "filterZone", first: true, predicate: ["filterZone"], descendants: true }, { propertyName: "filtersRow", first: true, predicate: ["filtersRow"], descendants: true }], ngImport: i0, template: "@if (showTable && !accessDenied) {\n <div class=\"lt-container\">\n <div class=\"lt-toolbar\">\n <div class=\"lt-toolbar-left\">\n <div class=\"lt-search\">\n <svg class=\"lt-search-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"lt-search-input\"\n id=\"SearchFilter\"\n type=\"text\"\n [placeholder]=\"'Search ' + (entityTypeLabel || 'items')\"\n [(ngModel)]=\"searchString\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n @if (filterableColumns.length) {\n <div class=\"lt-filter-zone\" #filterZone>\n <div class=\"lt-filters\" #filtersRow>\n @for (col of visibleFilterColumns; track col.key) {\n <span class=\"lt-filter-item\" [attr.data-filter-key]=\"col.key\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </span>\n }\n </div>\n @if (overflowFilterColumns.length) {\n <div class=\"lt-filter-more\">\n <button\n type=\"button\"\n class=\"lt-filter-more-btn\"\n [class.is-on]=\"hiddenActiveCount > 0\"\n [class.is-open]=\"showFilterOverflow\"\n (click)=\"toggleFilterOverflow($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showFilterOverflow\"\n [attr.aria-label]=\"overflowFilterColumns.length + ' more filters'\"\n id=\"FilterOverflowButton\"\n >\n <span class=\"lt-filter-more-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"><path fill=\"currentColor\" d=\"M2 3h12l-4.6 5.6v4l-2.8 1.4v-5.4z\"/></svg>\n </span>\n <span class=\"lt-filter-more-label\">More</span>\n <span class=\"lt-filter-more-count\">{{ overflowFilterColumns.length }}</span>\n </button>\n @if (showFilterOverflow) {\n <div class=\"lt-filter-overflow\" (clickOutside)=\"closeFilterOverflow($event)\">\n <div class=\"lt-filter-overflow-title\">Filters</div>\n @for (col of overflowFilterColumns; track col.key) {\n <div class=\"lt-filter-overflow-row\" [class.is-active]=\"isFilterActive(col)\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <div class=\"lt-toolbar-right\">\n <ng-template #refreshIcon>\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" d=\"M13.4 8a5.4 5.4 0 1 1-1.5-3.8\"/>\n <path fill=\"currentColor\" d=\"M13.9 2.1l.5 3.2-3.2-.5z\"/>\n </svg>\n </ng-template>\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n <lib-list-table-auto-refresh [loading]=\"filtering\" (refresh)=\"refresh()\"></lib-list-table-auto-refresh>\n <div class=\"lt-columns\">\n <button\n type=\"button\"\n class=\"lt-columns-btn\"\n [class.is-on]=\"hiddenColumnCount > 0\"\n [class.is-open]=\"showColumnChooser\"\n (click)=\"toggleColumnChooser($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showColumnChooser\"\n id=\"ColumnChooserButton\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M2 3.5h9v1.4H2zM2 7.3h6v1.4H2zM2 11.1h11v1.4H2z\"/>\n <circle cx=\"13\" cy=\"4.2\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"10\" cy=\"8\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"14.4\" cy=\"11.8\" r=\"1.7\" fill=\"currentColor\"/>\n </svg>\n <span>Columns</span>\n @if (hiddenColumnCount > 0) {\n <span class=\"lt-columns-count\">{{ hiddenColumnCount }}</span>\n }\n </button>\n @if (showColumnChooser) {\n <div class=\"lt-column-chooser\" (clickOutside)=\"closeColumnChooser()\">\n <div class=\"lt-chooser-header\">\n <span class=\"lt-chooser-title\">Columns</span>\n <span class=\"lt-chooser-subtitle\">Customize order and visibility</span>\n </div>\n <ul class=\"lt-chooser-list\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event)\">\n @for (col of chooserColumns; track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\">\n <button\n type=\"button\"\n class=\"lt-chooser-toggle\"\n [class.checked]=\"!col.hidden\"\n [disabled]=\"col.hideable === false\"\n (click)=\"toggleColumnVisible(col)\"\n [attr.aria-pressed]=\"!col.hidden\"\n >\n <span class=\"lt-chooser-box\" aria-hidden=\"true\">\n @if (!col.hidden) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/>\n </svg>\n }\n </span>\n <span class=\"lt-chooser-text\">{{ col.label }}</span>\n @if (isDefaultHidden(col)) {\n <span class=\"lt-chooser-default-tag\">hidden by default</span>\n }\n </button>\n @if (col.reorderable !== false) {\n <span class=\"lt-chooser-handle\" cdkDragHandle aria-label=\"Drag to reorder\" title=\"Drag to reorder\">\n <svg viewBox=\"0 0 10 16\" width=\"10\" height=\"15\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.25\"/><circle cx=\"7\" cy=\"3\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"8\" r=\"1.25\"/><circle cx=\"7\" cy=\"8\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"13\" r=\"1.25\"/><circle cx=\"7\" cy=\"13\" r=\"1.25\"/>\n </svg>\n </span>\n }\n </li>\n }\n </ul>\n <div class=\"lt-chooser-footer\">\n <button\n type=\"button\"\n class=\"lt-chooser-reset\"\n [disabled]=\"columnsAtDefault\"\n (click)=\"resetTableColumns()\"\n id=\"ResetColumnsButton\"\n >\n Reset to default\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div class=\"lt-scroll\" id=\"ItemTable\">\n <table class=\"lt-table\">\n <colgroup>\n @if (!options.noSelect) { <col class=\"lt-col-select\" /> }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <col [style.width]=\"colWidth(col)\" />\n }\n @if (!options.noMenu) { <col class=\"lt-col-menu\" /> }\n </colgroup>\n <thead class=\"lt-head\">\n <tr cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onReorder($event)\">\n @if (!options.noSelect) {\n <th class=\"lt-select-cell\">\n <div\n class=\"lt-select-all\"\n [class.checked]=\"allToggled\"\n [class.indeterminate]=\"someSelected\"\n (click)=\"toggleAll()\"\n id=\"SelectAllRows\"\n ></div>\n </th>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <th\n cdkDrag\n [cdkDragDisabled]=\"col.reorderable === false\"\n class=\"lt-th\"\n [ngClass]=\"col.cellClass\"\n id=\"Header_{{ col.key }}\"\n >\n <lib-list-header-cell\n [column]=\"col\"\n [sortDir]=\"col.sortDir\"\n (sortChanged)=\"onHeaderSort($event)\"\n (hideColumn)=\"hideColumn($event)\"\n ></lib-list-header-cell>\n @if (col.resizable !== false) {\n <span class=\"lt-resize\" (pointerdown)=\"startResize(col, $event)\"></span>\n }\n </th>\n }\n @if (!options.noMenu) {\n <th class=\"lt-menu-cell\">\n <div class=\"lt-header-menu-trigger\" (click)=\"openHeaderActionMenu($event)\" id=\"ColumnMenuHeader\">\n &#8942;\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rowData; track trackRow($index, row); let i = $index) {\n <tr\n class=\"lt-row\"\n [class.lt-row-odd]=\"i % 2 === 1\"\n [class.lt-row-selected]=\"row.selected\"\n [class.row-invalid]=\"row.invalid\"\n [class.row-copied]=\"row.copied\"\n >\n @if (!options.noSelect) {\n <td class=\"lt-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </td>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <td\n class=\"lt-cell\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @switch (renderMode(col)) {\n @case ('component') {\n <ng-container *ngComponentOutlet=\"col.cell; inputs: cellInputs(col, row, i)\"></ng-container>\n }\n @case ('html') {\n <span class=\"lt-cell-html\" [innerHTML]=\"cellHtml(col, row, i)\"></span>\n }\n @default {\n <span class=\"lt-cell-text\">{{ cellText(col, row, i) }}</span>\n }\n }\n </td>\n }\n @if (!options.noMenu) {\n <td class=\"lt-menu-cell\">\n @if (row.actionList?.length) {\n <button\n type=\"button\"\n class=\"lt-row-menu-trigger\"\n (click)=\"$event.stopPropagation(); openActionMenu($event, row)\"\n attr.id=\"RowMenu_{{ row.id }}\"\n aria-label=\"Row actions\"\n >&#8942;</button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n @if (!isLoading && filterApplied && (!rowData || rowData.length === 0)) {\n <div class=\"no-rows-message\">No Rows To Show</div>\n }\n </div>\n\n <div class=\"lt-footer\">\n <div class=\"lt-footer-count\">\n Showing <span id=\"Start\">{{ startCount }}</span>&#8211;<span id=\"End\">{{ endCount }}</span>\n of <span id=\"Total\">{{ totalCount }}</span>\n </div>\n <div class=\"lt-footer-controls\">\n <div class=\"lt-rpp\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Rows per page\"\n >\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"lt-pager\">\n <button type=\"button\" class=\"lt-page-btn icon-prev\" [disabled]=\"prevDisabled\" (click)=\"prevPage()\" aria-label=\"Previous page\"></button>\n @if (!cursorPager) {\n @for (p of pageWindow; track $index) {\n @if (p === 0) {\n <span class=\"lt-page-gap\">&#8230;</span>\n } @else {\n <button\n type=\"button\"\n class=\"lt-page-num\"\n [class.is-current]=\"p === currentPageNum\"\n (click)=\"goToPage(p)\"\n >{{ p }}</button>\n }\n }\n }\n <button type=\"button\" class=\"lt-page-btn icon-next\" [disabled]=\"nextDisabled\" (click)=\"nextPage()\" aria-label=\"Next page\"></button>\n </div>\n </div>\n </div>\n\n <!-- header action menu -->\n <lib-list-menu\n menuId=\"HeaderActionMenu\"\n [open]=\"openHeaderMenu\"\n [items]=\"headerMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onHeaderMenuClick($event)\"\n (closed)=\"closeHeaderActionMenu()\"\n ></lib-list-menu>\n\n <!-- row action menu -->\n <lib-list-menu\n menuId=\"RowActionMenu\"\n [open]=\"openMenu\"\n [items]=\"rowMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onRowMenuClick($event)\"\n (closed)=\"closeActionMenu()\"\n ></lib-list-menu>\n </div>\n} @else {\n <lib-no-items\n (clickEmit)=\"openCreate()\"\n [image]=\"noItemsImage\"\n [isEmpty]=\"!rowData || rowData.length === 0\"\n [isLoading]=\"isLoading\"\n [typeName]=\"entityTypeLabel\"\n [hasAdd]=\"showNoItemsAdd\"\n [readonly]=\"noItemsReadonly\"\n [accessDenied]=\"accessDenied\"\n ></lib-no-items>\n}\n\n@if (hoverTooltip) {\n <div class=\"lt-tooltip\" [ngStyle]=\"{ left: hoverTooltip.left + 'px', top: hoverTooltip.top + 'px' }\">\n <ng-container *ngComponentOutlet=\"hoverTooltip.component; inputs: hoverTooltip.inputs\"></ng-container>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.lt-columns-btn,.lt-refresh-btn,.lt-filter-more-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-columns-btn svg,.lt-refresh-btn svg,.lt-filter-more-btn svg{display:block}.lt-columns-btn:hover,.lt-refresh-btn:hover,.lt-filter-more-btn:hover,.is-open.lt-columns-btn,.is-open.lt-refresh-btn,.is-open.lt-filter-more-btn,.is-on.lt-columns-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{border-color:var(--lt-link);color:var(--lt-link)}.lt-columns-count,.lt-filter-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.125rem;height:1.125rem;padding:0 .3125rem;font-size:.6875rem;font-weight:700;color:var(--lt-control-bg);background:var(--lt-link);border-radius:.5625rem}.lt-container{display:flex;position:relative;height:calc(100% - var(--lt-container-reserve, 5.625rem));width:100%;flex-direction:column}.lt-scroll{position:relative;z-index:1;width:100%;flex:0 1 auto;min-height:0;overflow:auto;background-color:var(--lt-bg)}.lt-scroll .no-rows-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--lt-cell-text);font-size:.875rem;pointer-events:none}.lt-table{width:100%;border-collapse:collapse;table-layout:fixed;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text)}.lt-head{position:sticky;top:0;z-index:10}.lt-head th{position:relative;box-sizing:border-box;height:2.75rem;padding:0 .3125rem 0 .625rem;text-align:left;vertical-align:middle;background-color:var(--lt-header-bg);border-bottom:1px solid var(--lt-header-underline);white-space:nowrap;-webkit-user-select:none;user-select:none}.lt-head th.lt-th{border-right:1px solid var(--lt-border)}.lt-head .lt-select-cell,.lt-head .lt-menu-cell{width:3.75rem;padding:0}.lt-resize{position:absolute;right:0;top:50%;transform:translateY(-50%);width:.625rem;height:.875rem;cursor:col-resize}.lt-resize:before,.lt-resize:after{content:\"\";position:absolute;top:0;height:100%;width:1px;background:transparent;transition:background .15s}.lt-resize:before{right:.25rem}.lt-resize:after{right:.125rem}.lt-resize:hover:before,.lt-resize:hover:after{background:var(--lt-icon)}.lt-head th:hover .lt-resize:before,.lt-head th:hover .lt-resize:after{background:var(--lt-border)}.lt-select-all{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;background:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;cursor:pointer;transition:border-color .12s,background-color .12s}.lt-select-all:hover{border-color:var(--lt-link)}.lt-select-all.checked{background-color:var(--lt-link);border-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.lt-select-all.indeterminate{background-color:var(--lt-link);border-color:var(--lt-link);background-image:linear-gradient(#fff,#fff);background-size:.5rem .125rem}.lt-header-menu-trigger{display:flex;align-items:center;justify-content:center;height:100%;font-size:1.25rem;color:var(--lt-icon);cursor:pointer}.lt-header-menu-trigger:hover{color:var(--lt-icon-active)}.lt-row-menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;font-size:1.25rem;line-height:1;color:var(--lt-icon);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;transition:color .15s,background .15s}.lt-row-menu-trigger:hover{color:var(--lt-icon-active);background:var(--lt-row-hover)}.cdk-drag-preview{box-sizing:border-box;background-color:var(--lt-header-bg);color:var(--lt-header-text);box-shadow:var(--shadowLarge);padding:0 .625rem;display:flex;align-items:center;border-radius:var(--lt-radius)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.lt-row{height:var(--lt-row-height);background-color:var(--lt-row-bg);color:var(--lt-cell-text)}.lt-row td{border-bottom:1px solid var(--lt-border)}.lt-row.lt-row-odd{background-color:var(--lt-row-alt-bg)}.lt-row:hover{background-color:var(--lt-row-hover)}.lt-row.lt-row-selected{background-color:var(--lt-row-selected)}.lt-row.lt-row-selected:hover{background-color:var(--lt-row-hover)}.lt-row.row-invalid{background-color:var(--invalid)!important}.lt-row.row-copied{background-color:var(--copiedRow)!important}.lt-cell{height:var(--lt-row-height);padding:0 .625rem 0 1.25rem;text-align:left;vertical-align:middle;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-user-select:text;user-select:text}.lt-cell-text{display:inline-block;overflow:hidden;text-overflow:ellipsis;max-width:100%;vertical-align:middle}.lt-cell.lt-cell-center{text-align:center}.lt-badge-cell .lt-cell-text{padding:.1875rem .5rem;font-size:.75rem;font-weight:600;line-height:1.2;color:var(--lt-cell-text);background:color-mix(in srgb,var(--lt-border) 45%,transparent);border:1px solid var(--lt-border);border-radius:999px}.lt-badge-cell .lt-cell-text:empty{display:none}.lt-select-cell,.lt-menu-cell{width:3.75rem;padding:0;text-align:center;vertical-align:middle;overflow:visible;position:relative}.lt-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin:var(--lt-toolbar-margin-y, 0) 0;padding:0 0 var(--lt-toolbar-pad-bottom, .625rem);min-height:var(--lt-toolbar-height);position:relative;z-index:30}.lt-toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1 1 auto;min-width:0;flex-wrap:nowrap}.lt-filter-zone{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;flex:0 1 auto;min-width:0}.lt-filters{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;min-width:0;flex:0 1 auto;overflow:hidden}.lt-filter-item{display:inline-flex;flex:0 0 auto}.lt-filter-more{position:relative;flex:0 0 auto}.lt-filter-more-btn{gap:.375rem;padding:0 .625rem}.lt-filter-more-btn .lt-filter-more-icon{display:inline-flex}.lt-filter-overflow{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;min-width:15rem;max-width:min(22rem,90vw);display:flex;flex-direction:column;gap:.5rem;padding:.625rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-chooser-in .12s ease-out}.lt-filter-overflow-title{font-family:var(--lt-font);font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--groupText)}.lt-filter-overflow-row{display:flex}.lt-filter-overflow-row lib-list-filter-cell,.lt-filter-overflow-row .lt-filter-item{flex:1 1 auto;min-width:0}.lt-toolbar-right{display:flex;align-items:center;gap:.75rem;flex:0 0 auto}.lt-divider{flex:0 0 auto;width:1px;height:1.5rem;background:var(--lt-border)}.lt-search{position:relative;display:flex;align-items:center;width:18rem;max-width:40vw;flex:0 1 18rem}.lt-search-icon{position:absolute;left:.625rem;display:block;color:var(--lt-icon);pointer-events:none}.lt-search-input{width:100%;height:var(--lt-toolbar-height);padding:0 .75rem 0 1.75rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);outline:none;transition:border-color .15s,box-shadow .15s}.lt-search-input::placeholder{color:var(--placeholder)}.lt-search-input:focus{border-color:var(--lt-link);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-count{font-size:.8125rem;color:var(--groupText);white-space:nowrap}.lt-count .lt-count-of{color:var(--groupText)}.lt-pager{display:flex;align-items:center;gap:.25rem}.lt-page-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);color:var(--lt-icon);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;font-size:.9375rem;transition:border-color .15s,color .15s}.lt-page-btn:not(:disabled):hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-btn:disabled{opacity:.4;cursor:default}.lt-refresh-btn{padding:0;width:var(--lt-toolbar-height);justify-content:center}.lt-refresh-btn:disabled{opacity:.5;cursor:default;border-color:var(--lt-border);color:var(--lt-cell-text)}.lt-columns{position:relative;flex:0 0 auto}.lt-column-chooser{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;width:15rem;max-height:calc(100vh - 9rem);display:flex;flex-direction:column;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);overflow:hidden;animation:lt-chooser-in .12s ease-out}@keyframes lt-chooser-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-chooser-header{flex:0 0 auto;display:flex;flex-direction:column;padding:.625rem .75rem .5rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-title{font-size:.8125rem;font-weight:700;color:var(--lt-cell-text)}.lt-chooser-subtitle{font-size:.6875rem;color:var(--groupText)}.lt-chooser-list{flex:1 1 auto;list-style:none;margin:0;padding:.375rem .5rem;min-height:0;overflow-y:auto}.lt-chooser-row{display:flex;align-items:center;justify-content:space-between;min-height:2.5rem;padding:.5rem;border-radius:var(--lt-radius);background:var(--lt-control-bg)}.lt-chooser-row:hover{background:var(--lt-row-hover)}.lt-chooser-row:hover .lt-chooser-handle{opacity:1}.lt-chooser-toggle{display:flex;align-items:center;gap:.625rem;flex:1 1 auto;min-width:0;padding:0;background:transparent;border:none;font:inherit;font-size:.875rem;color:var(--lt-cell-text);text-align:left;cursor:pointer}.lt-chooser-toggle:disabled{cursor:default;opacity:.5}.lt-chooser-box{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:1rem;height:1rem;border:.09375rem solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg);transition:background .12s,border-color .12s}.lt-chooser-toggle.checked .lt-chooser-box{background:var(--lt-link);border-color:var(--lt-link)}.lt-chooser-toggle:not(:disabled):hover .lt-chooser-box{border-color:var(--lt-link)}.lt-chooser-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-chooser-default-tag{flex:0 0 auto;margin-left:.375rem;font-size:.6875rem;font-weight:400;font-style:italic;color:var(--groupText);white-space:nowrap}.lt-chooser-handle{flex:0 0 auto;display:inline-flex;align-items:center;color:var(--lt-icon);cursor:grab;padding-left:.5rem;-webkit-user-select:none;user-select:none}.lt-chooser-handle:active{cursor:grabbing}.lt-chooser-footer{flex:0 0 auto;padding:.5rem .75rem;border-top:1px solid var(--lt-border)}.lt-chooser-reset{width:100%;height:1.875rem;font-size:.75rem;font-weight:600;color:var(--lt-link);background:transparent;border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer}.lt-chooser-reset:disabled{color:var(--groupText);cursor:default;opacity:.6}.lt-chooser-reset:not(:disabled):hover{border-color:var(--lt-link)}.lt-footer{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.625rem .125rem 0;font-family:var(--lt-font);font-size:.8125rem;color:var(--groupText)}.lt-footer-count{white-space:nowrap}.lt-footer-count span{color:var(--lt-cell-text);font-weight:600}.lt-footer-controls{display:flex;align-items:center;gap:1.25rem;flex-wrap:nowrap}.lt-rpp{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}.lt-rpp-label{text-transform:none;font-weight:400;color:var(--groupText)}.lt-rpp-select{width:auto;min-width:4rem;height:var(--lt-toolbar-height);padding:0 1.75rem 0 .625rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background-color:var(--lt-control-bg);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .5rem center;border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);appearance:none;-webkit-appearance:none;cursor:pointer}.lt-rpp-select:focus{outline:none;border-color:var(--lt-link)}.lt-page-num{display:inline-flex;align-items:center;justify-content:center;min-width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);padding:0 .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.lt-page-num:hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-num.is-current{color:#fff;background:var(--lt-link);border-color:var(--lt-link);cursor:default}.lt-page-gap{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;color:var(--groupText)}.lt-tooltip{position:fixed;z-index:1100;pointer-events:none;animation:lt-tooltip-in .12s ease-out}@keyframes lt-tooltip-in{0%{opacity:0;transform:translateY(-.1875rem)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i6$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }, { kind: "component", type: ListMenuComponent, selector: "lib-list-menu", inputs: ["open", "items", "left", "top", "menuId"], outputs: ["itemClick", "closed"] }, { kind: "component", type: ListTableAutoRefreshComponent, selector: "lib-list-table-auto-refresh", inputs: ["seconds", "loading", "intervals"], outputs: ["refresh", "secondsChange"] }, { kind: "component", type: SelectCellComponent, selector: "lib-select-cell", inputs: ["row", "toggleItem"] }, { kind: "component", type: ListHeaderCellComponent, selector: "lib-list-header-cell", inputs: ["column", "sortDir"], outputs: ["sortChanged", "hideColumn"] }, { kind: "component", type: ListFilterCellComponent, selector: "lib-list-filter-cell", inputs: ["column"] }, { kind: "component", type: NoItemsComponent, selector: "lib-no-items", inputs: ["image", "typeName", "isEmpty", "hasAdd", "hiddenResults", "isLoading", "readonly", "accessDenied"], outputs: ["clickEmit", "refresh"] }] }); }
13918
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableComponent, isStandalone: false, selector: "lib-list-table", inputs: { tableId: "tableId", columns: "columns", rowData: "rowData", isLoading: "isLoading", options: "options", loadChildren: "loadChildren", childColumnsByLevel: "childColumnsByLevel", hasNext: "hasNext", hasPrev: "hasPrev", startCount: "startCount", endCount: "endCount", totalCount: "totalCount", filterApplied: "filterApplied", menuItems: "menuItems", headerActions: "headerActions", noItemsImage: "noItemsImage", showNoItemsAdd: "showNoItemsAdd", noItemsReadonly: "noItemsReadonly", accessDenied: "accessDenied", entityTypeName: "entityTypeName", filterName: "filterName", filterColumn: "filterColumn", treeVariant: "treeVariant", showTreeVariantToggle: "showTreeVariantToggle", treeRail: "treeRail", initialPageSize: "initialPageSize" }, outputs: { nodeToggled: "nodeToggled", actionRequested: "actionRequested", tableReady: "tableReady", selectionChanged: "selectionChanged" }, host: { properties: { "class.lt-tree-flat": "this.isFlatTree" } }, providers: [ListTableService], viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true }, { propertyName: "filterZone", first: true, predicate: ["filterZone"], descendants: true }, { propertyName: "filtersRow", first: true, predicate: ["filtersRow"], descendants: true }], ngImport: i0, template: "@if (showTable && !accessDenied) {\n <div class=\"lt-container\">\n <div class=\"lt-toolbar\">\n <div class=\"lt-toolbar-left\">\n <div class=\"lt-search\">\n <svg class=\"lt-search-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"lt-search-input\"\n id=\"SearchFilter\"\n type=\"text\"\n [placeholder]=\"'Search ' + (entityTypeLabel || 'items')\"\n [(ngModel)]=\"searchString\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n @if (filterableColumns.length) {\n <div class=\"lt-filter-zone\" #filterZone>\n <div class=\"lt-filters\" #filtersRow>\n @for (col of visibleFilterColumns; track col.key) {\n <span class=\"lt-filter-item\" [attr.data-filter-key]=\"col.key\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </span>\n }\n </div>\n @if (overflowFilterColumns.length) {\n <div class=\"lt-filter-more\">\n <button\n type=\"button\"\n class=\"lt-filter-more-btn\"\n [class.is-on]=\"hiddenActiveCount > 0\"\n [class.is-open]=\"showFilterOverflow\"\n (click)=\"toggleFilterOverflow($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showFilterOverflow\"\n [attr.aria-label]=\"overflowFilterColumns.length + ' more filters'\"\n id=\"FilterOverflowButton\"\n >\n <span class=\"lt-filter-more-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"><path fill=\"currentColor\" d=\"M2 3h12l-4.6 5.6v4l-2.8 1.4v-5.4z\"/></svg>\n </span>\n <span class=\"lt-filter-more-label\">More</span>\n <span class=\"lt-filter-more-count\">{{ overflowFilterColumns.length }}</span>\n </button>\n @if (showFilterOverflow) {\n <div class=\"lt-filter-overflow\" (clickOutside)=\"closeFilterOverflow($event)\">\n <div class=\"lt-filter-overflow-title\">Filters</div>\n @for (col of overflowFilterColumns; track col.key) {\n <div class=\"lt-filter-overflow-row\" [class.is-active]=\"isFilterActive(col)\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n <!-- Consumer-supplied toolbar controls (e.g. page-specific filters), rendered inline beside search. -->\n <ng-content select=\"[toolbar-filters]\"></ng-content>\n </div>\n <div class=\"lt-toolbar-right\">\n <ng-template #refreshIcon>\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" d=\"M13.4 8a5.4 5.4 0 1 1-1.5-3.8\"/>\n <path fill=\"currentColor\" d=\"M13.9 2.1l.5 3.2-3.2-.5z\"/>\n </svg>\n </ng-template>\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n <lib-list-table-auto-refresh [loading]=\"filtering\" (refresh)=\"refresh()\"></lib-list-table-auto-refresh>\n <div class=\"lt-columns\">\n <button\n type=\"button\"\n class=\"lt-columns-btn\"\n [class.is-on]=\"hiddenColumnCount > 0\"\n [class.is-open]=\"showColumnChooser\"\n (click)=\"toggleColumnChooser($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showColumnChooser\"\n id=\"ColumnChooserButton\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M2 3.5h9v1.4H2zM2 7.3h6v1.4H2zM2 11.1h11v1.4H2z\"/>\n <circle cx=\"13\" cy=\"4.2\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"10\" cy=\"8\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"14.4\" cy=\"11.8\" r=\"1.7\" fill=\"currentColor\"/>\n </svg>\n <span>Columns</span>\n @if (hiddenColumnCount > 0) {\n <span class=\"lt-columns-count\">{{ hiddenColumnCount }}</span>\n }\n </button>\n @if (showColumnChooser) {\n <div class=\"lt-column-chooser\" (clickOutside)=\"closeColumnChooser()\">\n <div class=\"lt-chooser-header\">\n <span class=\"lt-chooser-title\">Columns</span>\n <span class=\"lt-chooser-subtitle\">Customize order and visibility</span>\n </div>\n @if (canSwitchTreeVariant && showTreeVariantToggle) {\n <div class=\"lt-chooser-view\" role=\"group\" aria-label=\"Expanded row layout\">\n <span class=\"lt-chooser-view-label\">Expanded rows</span>\n <div class=\"lt-chooser-view-toggle\">\n <button type=\"button\" class=\"lt-chooser-view-opt\" [class.active]=\"treeVariant === 'flat'\" (click)=\"setTreeVariant('flat')\">Flat</button>\n <button type=\"button\" class=\"lt-chooser-view-opt\" [class.active]=\"treeVariant === 'card'\" (click)=\"setTreeVariant('card')\">Nested</button>\n </div>\n </div>\n }\n <div class=\"lt-chooser-scroll\">\n <ul class=\"lt-chooser-list\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event)\">\n @for (col of chooserColumns; track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\"\n (cdkDragStarted)=\"onChooserDragStarted()\" (cdkDragEnded)=\"onChooserDragEnded()\">\n <ng-container *ngTemplateOutlet=\"chooserRowBody; context: { col: col }\"></ng-container>\n </li>\n }\n </ul>\n @if (canSwitchTreeVariant && childLevels.length) {\n <div class=\"lt-chooser-nested\">\n <button\n type=\"button\"\n class=\"lt-chooser-group\"\n (click)=\"toggleNestedGroup('__root')\"\n [attr.aria-expanded]=\"isNestedGroupOpen('__root')\"\n >\n <span class=\"lt-chooser-chevron\" [class.open]=\"isNestedGroupOpen('__root')\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 4l4 4-4 4\"/></svg>\n </span>\n <span class=\"lt-chooser-group-label\">Nested rows</span>\n </button>\n @if (isNestedGroupOpen('__root')) {\n @for (level of childLevels; track level) {\n <button\n type=\"button\"\n class=\"lt-chooser-group lt-chooser-group-sub\"\n (click)=\"toggleNestedGroup('lvl' + level)\"\n [attr.aria-expanded]=\"isNestedGroupOpen('lvl' + level)\"\n >\n <span class=\"lt-chooser-chevron\" [class.open]=\"isNestedGroupOpen('lvl' + level)\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 4l4 4-4 4\"/></svg>\n </span>\n <span class=\"lt-chooser-group-label\">Level {{ level }}</span>\n <span class=\"lt-chooser-group-hint\">{{ childLevelLabel(level) }}</span>\n </button>\n @if (isNestedGroupOpen('lvl' + level)) {\n <ul class=\"lt-chooser-list lt-chooser-sublist\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event, level)\">\n @for (col of allColumnsForLevel(level); track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\"\n (cdkDragStarted)=\"onChooserDragStarted()\" (cdkDragEnded)=\"onChooserDragEnded()\">\n <ng-container *ngTemplateOutlet=\"chooserRowBody; context: { col: col, level: level }\"></ng-container>\n </li>\n }\n </ul>\n }\n }\n }\n </div>\n }\n </div>\n <div class=\"lt-chooser-footer\">\n <button\n type=\"button\"\n class=\"lt-chooser-reset\"\n [disabled]=\"columnsAtDefault\"\n (click)=\"resetTableColumns()\"\n id=\"ResetColumnsButton\"\n >\n Reset to default\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- inner content of a Columns-chooser row, shared by the top-level and nested lists.\n The <li cdkDrag> wrapper stays inline in each list (above) so cdkDropList's content\n query still registers it as a draggable; only the body is shared here.\n `level` undefined targets the top-level columns, a number targets that nested depth. -->\n <ng-template #chooserRowBody let-col=\"col\" let-level=\"level\">\n <button\n type=\"button\"\n class=\"lt-chooser-toggle\"\n [class.checked]=\"!col.hidden\"\n [disabled]=\"col.hideable === false || isLastVisibleColumn(col, level)\"\n [attr.title]=\"isLastVisibleColumn(col, level) ? 'At least one column must stay visible' : null\"\n (click)=\"toggleColumnVisible(col, level)\"\n [attr.aria-pressed]=\"!col.hidden\"\n >\n <span class=\"lt-chooser-box\" aria-hidden=\"true\">\n <!-- always rendered: the check is white, so it stays invisible until the box\n turns blue when checked. Keeping it in the DOM means clicking to hide a\n column never detaches the click target, which would otherwise make the\n click-outside handler think the click was outside and close the menu. -->\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/>\n </svg>\n </span>\n <span class=\"lt-chooser-text\">{{ col.label }}</span>\n @if (level == null && isDefaultHidden(col)) {\n <span class=\"lt-chooser-default-tag\">hidden by default</span>\n }\n </button>\n @if (col.reorderable !== false) {\n <!-- the whole row is the drag target (cdkDrag on the <li>); this is just the grip affordance -->\n <span class=\"lt-chooser-handle\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 16\" width=\"10\" height=\"15\" fill=\"currentColor\">\n <circle cx=\"3\" cy=\"3\" r=\"1.25\"/><circle cx=\"7\" cy=\"3\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"8\" r=\"1.25\"/><circle cx=\"7\" cy=\"8\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"13\" r=\"1.25\"/><circle cx=\"7\" cy=\"13\" r=\"1.25\"/>\n </svg>\n </span>\n }\n </ng-template>\n\n <div class=\"lt-scroll\" id=\"ItemTable\">\n <!-- expand/collapse control, reused by root rows and nested child rows -->\n <ng-template #treeToggle let-row=\"row\">\n @if (canExpand(row)) {\n <button\n type=\"button\"\n class=\"lt-tree-toggle\"\n [class.is-expanded]=\"row.__treeExpanded\"\n [class.is-loading]=\"row.__treeLoading\"\n [class.is-error]=\"row.__treeLoadError\"\n (click)=\"$event.stopPropagation(); toggleNode(row)\"\n [attr.aria-expanded]=\"!!row.__treeExpanded\"\n [attr.aria-busy]=\"!!row.__treeLoading\"\n [attr.title]=\"row.__treeLoadError ? 'Could not load children - click to retry' : null\"\n [attr.aria-label]=\"row.__treeExpanded ? 'Collapse row' : 'Expand row'\"\n >\n <svg class=\"lt-tree-chevron\" viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 4l4 4-4 4\"/>\n </svg>\n @if (row.__treeLoading) {\n <span class=\"lt-tree-spinner\" aria-hidden=\"true\"></span>\n }\n </button>\n } @else {\n <span class=\"lt-tree-spacer\" aria-hidden=\"true\"></span>\n }\n </ng-template>\n\n <!-- a single cell's content, reused across the fixed grid and the nested grid -->\n <ng-template #cellBody let-col=\"col\" let-row=\"row\" let-i=\"i\">\n @switch (renderMode(col)) {\n @case ('component') {\n <ng-container *ngComponentOutlet=\"col.cell; inputs: cellInputs(col, row, i)\"></ng-container>\n }\n @case ('html') {\n <span class=\"lt-cell-html\" [innerHTML]=\"cellHtml(col, row, i)\"></span>\n }\n @default {\n <span class=\"lt-cell-text\">{{ cellText(col, row, i) }}</span>\n }\n }\n </ng-template>\n\n <table class=\"lt-table\">\n <colgroup>\n @if (!options.noSelect) { <col class=\"lt-col-select\" /> }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <col [style.width]=\"colWidth(col)\" />\n }\n @if (!options.noMenu) { <col class=\"lt-col-menu\" /> }\n </colgroup>\n <thead class=\"lt-head\">\n <tr cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onReorder($event)\">\n @if (!options.noSelect) {\n <th class=\"lt-select-cell\">\n <div\n class=\"lt-select-all\"\n [class.checked]=\"allToggled\"\n [class.indeterminate]=\"someSelected\"\n (click)=\"toggleAll()\"\n id=\"SelectAllRows\"\n ></div>\n </th>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <th\n cdkDrag\n [cdkDragDisabled]=\"col.reorderable === false\"\n class=\"lt-th\"\n [class.lt-th-tree]=\"isTreeCell(col)\"\n [ngClass]=\"col.cellClass\"\n id=\"Header_{{ col.key }}\"\n >\n <lib-list-header-cell\n [column]=\"col\"\n [sortDir]=\"col.sortDir\"\n (sortChanged)=\"onHeaderSort($event)\"\n ></lib-list-header-cell>\n @if (col.resizable !== false) {\n <span class=\"lt-resize\" (pointerdown)=\"startResize(col, $event)\"></span>\n }\n </th>\n }\n @if (!options.noMenu) {\n <th class=\"lt-menu-cell\">\n <div class=\"lt-header-menu-trigger\" (click)=\"openHeaderActionMenu($event)\" id=\"ColumnMenuHeader\">\n &#8942;\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of flatRows; track trackRow($index, row); let i = $index) {\n @if (isTreeMode && row.__treeLevel > 0 && hasChildColumns) {\n <!-- nested rows with their own per-level columns, laid out in a grid\n that spans the full table width (so they need not match the parent) -->\n @if (row.__treeFirstChild) {\n <tr class=\"lt-tree-subhead-row\">\n <td\n [attr.colspan]=\"treeSpanCount\"\n [style.--lt-tree-indent.rem]=\"treeIndentFor(row.__treeLevel)\"\n [style.--lt-tree-first-indent.rem]=\"treeFlatFirstIndentRem(row.__treeLevel)\"\n [style.--lt-tree-lead-offset]=\"treeLeadOffset(row.__treeLevel)\"\n [style.background-image]=\"treeAncestorRails(row.__treeLevel)\"\n >\n <div\n class=\"lt-tree-grid\"\n [style.grid-template-columns]=\"gridTemplate(row.__treeLevel)\"\n >\n @if (childSelectEnabled) {\n <span class=\"lt-tree-subhead-cell lt-tree-select-cell\"></span>\n }\n @for (col of columnsForLevel(row.__treeLevel); track col.key; let last = $last) {\n <span class=\"lt-tree-subhead-cell\" [class.lt-tree-indent-col]=\"isTreeColumn(col, row.__treeLevel)\" [ngClass]=\"cellClass(col)\">\n {{ col.label }}\n @if (!last && col.resizable !== false) {\n <span class=\"lt-resize lt-tree-resize\" (pointerdown)=\"startChildResize(col, row.__treeLevel, $event)\"></span>\n }\n </span>\n }\n </div>\n </td>\n </tr>\n }\n <tr\n class=\"lt-row lt-tree-child-row\"\n [class.lt-tree-last-child]=\"row.__treeLastChild\"\n [class.lt-row-selected]=\"row.selected\"\n [class.lt-row-expanded]=\"row.__treeExpanded\"\n >\n <td\n [attr.colspan]=\"treeSpanCount\"\n [style.--lt-tree-indent.rem]=\"treeIndentFor(row.__treeLevel)\"\n [style.--lt-tree-first-indent.rem]=\"treeFlatFirstIndentRem(row.__treeLevel)\"\n [style.--lt-tree-lead-offset]=\"treeLeadOffset(row.__treeLevel)\"\n [style.background-image]=\"treeAncestorRails(row.__treeLevel)\"\n >\n <div\n class=\"lt-tree-grid\"\n [style.grid-template-columns]=\"gridTemplate(row.__treeLevel)\"\n >\n @if (childSelectEnabled) {\n <div class=\"lt-tree-cellbox lt-tree-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </div>\n }\n @for (col of columnsForLevel(row.__treeLevel); track col.key) {\n <div\n class=\"lt-tree-cellbox\"\n [class.lt-tree-indent-col]=\"isTreeColumn(col, row.__treeLevel)\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @if (isTreeColumn(col, row.__treeLevel) && showTreeLead(row)) {\n <span class=\"lt-tree-lead\">\n <ng-container *ngTemplateOutlet=\"treeToggle; context: {row: row}\"></ng-container>\n </span>\n }\n <ng-container *ngTemplateOutlet=\"cellBody; context: {col: col, row: row, i: i}\"></ng-container>\n </div>\n }\n </div>\n </td>\n </tr>\n } @else {\n <tr\n class=\"lt-row\"\n [class.lt-row-odd]=\"i % 2 === 1\"\n [class.lt-row-selected]=\"row.selected\"\n [class.lt-row-expanded]=\"row.__treeExpanded\"\n [class.row-invalid]=\"row.invalid\"\n [class.row-copied]=\"row.copied\"\n >\n @if (!options.noSelect) {\n <td class=\"lt-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </td>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <td\n class=\"lt-cell\"\n [class.lt-tree-cell]=\"isTreeCell(col)\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @if (isTreeCell(col)) {\n <span class=\"lt-tree-cell-inner\">\n <span class=\"lt-tree-lead\" [style.margin-left.rem]=\"(row.__treeLevel || 0) * treeIndentRem\">\n <ng-container *ngTemplateOutlet=\"treeToggle; context: {row: row}\"></ng-container>\n </span>\n <ng-container *ngTemplateOutlet=\"cellBody; context: {col: col, row: row, i: i}\"></ng-container>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellBody; context: {col: col, row: row, i: i}\"></ng-container>\n }\n </td>\n }\n @if (!options.noMenu) {\n <td class=\"lt-menu-cell\">\n @if (row.actionList?.length) {\n <button\n type=\"button\"\n class=\"lt-row-menu-trigger\"\n (click)=\"$event.stopPropagation(); openActionMenu($event, row)\"\n attr.id=\"RowMenu_{{ row.id }}\"\n aria-label=\"Row actions\"\n >&#8942;</button>\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n @if (!isLoading && filterApplied && (!rowData || rowData.length === 0)) {\n <div class=\"no-rows-message\">No Rows To Show</div>\n }\n </div>\n\n <div class=\"lt-footer\">\n <div class=\"lt-footer-count\">\n Showing <span id=\"Start\">{{ startCount }}</span>&#8211;<span id=\"End\">{{ endCount }}</span>\n of <span id=\"Total\">{{ totalCount }}</span>\n </div>\n <div class=\"lt-footer-controls\">\n <div class=\"lt-rpp\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Rows per page\"\n >\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"lt-pager\">\n <button type=\"button\" class=\"lt-page-btn icon-prev\" [disabled]=\"prevDisabled\" (click)=\"prevPage()\" aria-label=\"Previous page\"></button>\n @if (!cursorPager) {\n @for (p of pageWindow; track $index) {\n @if (p === 0) {\n <span class=\"lt-page-gap\">&#8230;</span>\n } @else {\n <button\n type=\"button\"\n class=\"lt-page-num\"\n [class.is-current]=\"p === currentPageNum\"\n (click)=\"goToPage(p)\"\n >{{ p }}</button>\n }\n }\n }\n <button type=\"button\" class=\"lt-page-btn icon-next\" [disabled]=\"nextDisabled\" (click)=\"nextPage()\" aria-label=\"Next page\"></button>\n </div>\n </div>\n </div>\n\n <!-- header action menu -->\n <lib-list-menu\n menuId=\"HeaderActionMenu\"\n [open]=\"openHeaderMenu\"\n [items]=\"headerMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onHeaderMenuClick($event)\"\n (closed)=\"closeHeaderActionMenu()\"\n ></lib-list-menu>\n\n <!-- row action menu -->\n <lib-list-menu\n menuId=\"RowActionMenu\"\n [open]=\"openMenu\"\n [items]=\"rowMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onRowMenuClick($event)\"\n (closed)=\"closeActionMenu()\"\n ></lib-list-menu>\n </div>\n} @else {\n <lib-no-items\n (clickEmit)=\"openCreate()\"\n [image]=\"noItemsImage\"\n [isEmpty]=\"!rowData || rowData.length === 0\"\n [isLoading]=\"isLoading\"\n [typeName]=\"entityTypeLabel\"\n [hasAdd]=\"showNoItemsAdd\"\n [readonly]=\"noItemsReadonly\"\n [accessDenied]=\"accessDenied\"\n ></lib-no-items>\n}\n\n@if (hoverTooltip) {\n <div class=\"lt-tooltip\" [ngStyle]=\"{ left: hoverTooltip.left + 'px', top: hoverTooltip.top + 'px' }\">\n <ng-container *ngComponentOutlet=\"hoverTooltip.component; inputs: hoverTooltip.inputs\"></ng-container>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.lt-columns-btn,.lt-refresh-btn,.lt-filter-more-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-columns-btn svg,.lt-refresh-btn svg,.lt-filter-more-btn svg{display:block}.lt-columns-btn:hover,.lt-refresh-btn:hover,.lt-filter-more-btn:hover,.is-open.lt-columns-btn,.is-open.lt-refresh-btn,.is-open.lt-filter-more-btn,.is-on.lt-columns-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{border-color:var(--lt-link);color:var(--lt-link)}.is-on.lt-columns-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.lt-columns-count,.lt-filter-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.125rem;height:1.125rem;padding:0 .3125rem;font-size:.6875rem;font-weight:700;color:var(--lt-control-bg);background:var(--lt-link);border-radius:.5625rem}.lt-container{display:flex;position:relative;height:calc(100% - var(--lt-container-reserve, 5.625rem));width:100%;flex-direction:column}.lt-scroll{position:relative;z-index:1;width:100%;flex:0 1 auto;min-height:0;overflow:auto;background-color:var(--lt-bg)}.lt-scroll .no-rows-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--lt-cell-text);font-size:.875rem;pointer-events:none}.lt-table{width:100%;border-collapse:collapse;table-layout:fixed;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text)}.lt-head{position:sticky;top:0;z-index:10}.lt-head th{position:relative;box-sizing:border-box;height:2.6rem;padding:0 .3125rem 0 1.25rem;text-align:left;vertical-align:middle;background-color:var(--lt-header-bg);border-bottom:1px solid var(--lt-header-underline);white-space:nowrap;-webkit-user-select:none;user-select:none}.lt-head th.lt-th-tree{padding-left:2.75rem}.lt-head .lt-select-cell,.lt-head .lt-menu-cell{width:3.75rem;padding:0}.lt-resize{position:absolute;right:0;top:50%;transform:translateY(-50%);width:.75rem;height:2.25rem;cursor:col-resize}.lt-resize:before,.lt-resize:after{content:\"\";position:absolute;top:50%;height:1.25rem;transform:translateY(-50%);width:1px;background:color-mix(in srgb,var(--lt-border) 50%,var(--lt-icon));transition:background .15s}.lt-resize:before{right:.3125rem}.lt-resize:after{right:.125rem}.lt-resize:hover:before,.lt-resize:hover:after{background:color-mix(in srgb,var(--lt-icon) 72%,var(--lt-cell-text))}.lt-select-all{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;background:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;cursor:pointer;transition:border-color .12s,background-color .12s}.lt-select-all:hover{border-color:var(--lt-link)}.lt-select-all.checked{background-color:var(--lt-link);border-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.lt-select-all.indeterminate{background-color:var(--lt-link);border-color:var(--lt-link);background-image:linear-gradient(#fff,#fff);background-size:.5rem .125rem}.lt-header-menu-trigger{display:flex;align-items:center;justify-content:center;height:100%;font-size:1.25rem;color:var(--lt-icon);cursor:pointer}.lt-header-menu-trigger:hover{color:var(--lt-icon-active)}.lt-row-menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;font-size:1.25rem;line-height:1;color:var(--lt-icon);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;transition:color .15s,background .15s}.lt-row-menu-trigger:hover{color:var(--lt-icon-active);background:var(--lt-row-hover)}.cdk-drag-preview{box-sizing:border-box;background-color:var(--lt-header-bg);color:var(--lt-header-text);box-shadow:var(--shadowLarge);padding:0 .625rem;display:flex;align-items:center;border-radius:var(--lt-radius)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.lt-row{height:var(--lt-row-height);background-color:var(--lt-row-bg);color:var(--lt-cell-text)}.lt-row td{border-bottom:1px solid var(--lt-border)}.lt-row.lt-row-odd{background-color:var(--lt-row-alt-bg)}.lt-row:hover{background-color:var(--lt-row-hover)}.lt-row.lt-row-selected{background-color:var(--lt-row-selected)}.lt-row.lt-row-selected:hover{background-color:var(--lt-row-hover)}.lt-row.row-invalid{background-color:var(--invalid)!important}.lt-row.row-copied{background-color:var(--copiedRow)!important}.lt-cell{height:var(--lt-row-height);padding:0 .625rem 0 1.25rem;text-align:left;vertical-align:middle;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-user-select:text;user-select:text}.lt-cell-text{display:inline-block;overflow:hidden;text-overflow:ellipsis;max-width:100%;vertical-align:middle}.lt-cell.lt-cell-center{text-align:center}.lt-badge-cell .lt-cell-text{padding:.1875rem .5rem;font-size:.75rem;font-weight:600;line-height:1.2;color:var(--lt-cell-text);background:color-mix(in srgb,var(--lt-border) 45%,transparent);border:1px solid var(--lt-border);border-radius:999px}.lt-badge-cell .lt-cell-text:empty{display:none}.lt-select-cell,.lt-menu-cell{width:3.75rem;padding:0;text-align:center;vertical-align:middle;overflow:visible;position:relative}.lt-tree-cell{padding-left:.625rem}.lt-tree-cell-inner{display:flex;align-items:center}.lt-tree-lead{display:inline-flex;align-items:center;flex:0 0 auto;margin-right:.375rem}.lt-tree-toggle{position:relative;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;color:var(--lt-icon);background:transparent;border:none;border-radius:50%;cursor:pointer;transition:color .15s,background .15s}.lt-tree-toggle:hover{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-tree-chevron{display:block;width:13.8px;height:13.8px;transition:transform .15s ease}.lt-tree-toggle.is-expanded .lt-tree-chevron{transform:rotate(90deg)}.lt-tree-toggle.is-error .lt-tree-chevron{color:var(--lt-negative, #dc2626)}.lt-tree-spacer{display:inline-block;width:1.75rem;height:1.75rem}.lt-tree-spinner{position:absolute;top:50%;left:50%;box-sizing:border-box;width:1.875rem;height:1.875rem;margin:-.9375rem 0 0 -.9375rem;border:2px solid color-mix(in srgb,var(--lt-link) 25%,transparent);border-top-color:var(--lt-link);border-radius:50%;animation:loading .6s linear infinite;pointer-events:none}.lt-row-expanded>.lt-tree-cell .lt-cell-text{font-weight:600}.lt-tree-subhead-row>td,.lt-tree-child-row>td{padding:0 1rem 0 calc(var(--lt-card-inset, 4rem) + var(--lt-tree-indent, 0rem));background:transparent;border-bottom:none}.lt-tree-subhead-row,.lt-tree-child-row,.lt-tree-child-row:hover{background-color:var(--lt-tree-well, color-mix(in srgb, var(--lt-border) 22%, var(--lt-row-bg)))}.lt-tree-subhead-row>td{padding-top:1rem}.lt-tree-child-row.lt-tree-last-child>td{padding-bottom:1rem}.lt-tree-grid{display:grid;align-items:center;--lt-tree-col-gap: .5rem;column-gap:var(--lt-tree-col-gap);min-height:var(--lt-row-height);padding-left:.625rem;padding-right:.625rem;background:var(--lt-row-bg);border-left:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)));border-right:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)))}.lt-tree-child-row:hover .lt-tree-grid{background:var(--lt-row-hover)}.lt-tree-subhead-row .lt-tree-grid{min-height:2rem;background:var(--lt-header-bg);border-top:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)));border-top-left-radius:var(--lt-radius, 8px);border-top-right-radius:var(--lt-radius, 8px)}.lt-tree-child-row.lt-tree-last-child .lt-tree-grid{border-bottom:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)));border-bottom-left-radius:var(--lt-radius, 8px);border-bottom-right-radius:var(--lt-radius, 8px);box-shadow:var(--lt-tree-card-shadow, 0 2px 4px color-mix(in srgb, #000 10%, transparent))}.lt-tree-child-row:not(.lt-tree-last-child) .lt-tree-grid{border-bottom:1px solid color-mix(in srgb,var(--lt-border) 55%,transparent)}.lt-tree-subhead-cell{position:relative;padding:0 .3125rem;font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--lt-header-text);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.lt-tree-cellbox{display:flex;align-items:center;min-width:0;padding:0 .3125rem;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.lt-tree-cellbox .lt-cell-text{overflow:hidden;text-overflow:ellipsis}.lt-tree-grid>.lt-tree-select-cell{display:flex;align-items:center;justify-content:center;padding:0;overflow:visible}:host(.lt-tree-flat) .lt-tree-subhead-row,:host(.lt-tree-flat) .lt-tree-child-row,:host(.lt-tree-flat) .lt-tree-child-row:hover{background-color:transparent}:host(.lt-tree-flat) .lt-tree-subhead-row>td,:host(.lt-tree-flat) .lt-tree-child-row>td{padding:0}:host(.lt-tree-flat) .lt-tree-grid>.lt-tree-indent-col{padding-left:calc(var(--lt-tree-first-indent, 5.875rem) - var(--lt-tree-lead-offset, 0px))}:host(.lt-tree-flat) .lt-tree-grid{background:transparent;border-left:none;border-right:none;border-radius:0}:host(.lt-tree-flat) .lt-tree-subhead-row .lt-tree-grid{background:var(--lt-header-bg);border:none;border-bottom:1px solid var(--lt-border);border-radius:0}:host(.lt-tree-flat) .lt-tree-child-row .lt-tree-grid,:host(.lt-tree-flat) .lt-tree-child-row.lt-tree-last-child .lt-tree-grid{border-left:none;border-right:none;border-radius:0;box-shadow:none;border-bottom:1px solid color-mix(in srgb,var(--lt-border) 55%,transparent)}.lt-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin:var(--lt-toolbar-margin-top, .75rem) 0 var(--lt-toolbar-margin-bottom, 0);padding:0 0 var(--lt-toolbar-pad-bottom, .625rem);min-height:var(--lt-toolbar-height);position:relative;z-index:30}.lt-toolbar-left{display:flex;align-items:center;gap:var(--lt-control-row-gap);flex:1 1 auto;min-width:0;flex-wrap:nowrap}.lt-filter-zone{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--lt-control-row-gap);flex:0 1 auto;min-width:0}.lt-filters{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--lt-control-row-gap);min-width:0;flex:0 1 auto;overflow:hidden}.lt-filter-item{display:inline-flex;flex:0 0 auto}.lt-filter-more{position:relative;flex:0 0 auto}.lt-filter-more-btn{gap:.375rem;padding:0 .625rem}.lt-filter-more-btn .lt-filter-more-icon{display:inline-flex}.lt-filter-overflow{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;min-width:15rem;max-width:min(22rem,90vw);display:flex;flex-direction:column;gap:.5rem;padding:.625rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-chooser-in .12s ease-out}.lt-filter-overflow-title{font-family:var(--lt-font);font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--groupText)}.lt-filter-overflow-row{display:flex}.lt-filter-overflow-row lib-list-filter-cell,.lt-filter-overflow-row .lt-filter-item{flex:1 1 auto;min-width:0}.lt-toolbar-right{display:flex;align-items:center;gap:var(--lt-control-row-gap);flex:0 0 auto}.lt-divider{flex:0 0 auto;width:1px;height:1.5rem;background:var(--lt-border)}.lt-search{position:relative;display:flex;align-items:center;width:18rem;max-width:40vw;flex:0 1 18rem}.lt-search-icon{position:absolute;left:.625rem;display:block;color:var(--lt-icon);pointer-events:none}.lt-search-input{width:100%;height:var(--lt-toolbar-height);padding:0 .75rem 0 1.75rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);outline:none;transition:border-color .15s,box-shadow .15s}.lt-search-input::placeholder{color:var(--placeholder)}.lt-search-input:focus{border-color:var(--lt-link);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-count{font-size:.8125rem;color:var(--groupText);white-space:nowrap}.lt-count .lt-count-of{color:var(--groupText)}.lt-pager{display:flex;align-items:center;gap:.25rem}.lt-page-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);color:var(--lt-icon);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;font-size:.9375rem;transition:border-color .15s,color .15s}.lt-page-btn:not(:disabled):hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-btn:disabled{opacity:.4;cursor:default}.lt-refresh-btn{padding:0;width:var(--lt-toolbar-height);justify-content:center}.lt-refresh-btn:disabled{opacity:.5;cursor:default;border-color:var(--lt-border);color:var(--lt-cell-text)}.lt-columns{position:relative;flex:0 0 auto}.lt-column-chooser{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;width:15rem;max-height:calc(100vh - 9rem);display:flex;flex-direction:column;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);overflow:hidden;animation:lt-chooser-in .12s ease-out}@keyframes lt-chooser-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-chooser-header{flex:0 0 auto;display:flex;flex-direction:column;padding:.625rem .75rem .5rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-title{font-size:.8125rem;font-weight:700;color:var(--lt-cell-text)}.lt-chooser-subtitle{font-size:.6875rem;color:var(--groupText)}.lt-chooser-view{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-view-label{font-size:.75rem;font-weight:600;color:var(--lt-cell-text)}.lt-chooser-view-toggle{display:flex;gap:.1875rem;padding:.1875rem;background:color-mix(in srgb,var(--lt-border) 30%,var(--lt-control-bg));border:1px solid var(--lt-border);border-radius:var(--lt-radius)}.lt-chooser-view-opt{padding:.25rem .625rem;font-family:var(--lt-font);font-size:.75rem;font-weight:500;color:var(--lt-cell-text);background:transparent;border:none;border-radius:calc(var(--lt-radius) - .0625rem);cursor:pointer}.lt-chooser-view-opt:hover:not(.active){color:var(--lt-link)}.lt-chooser-view-opt.active{color:var(--lt-link);background:var(--lt-control-bg);box-shadow:0 1px 2px color-mix(in srgb,var(--lt-cell-text) 14%,transparent)}.lt-chooser-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.lt-chooser-list{list-style:none;margin:0;padding:.375rem .5rem}.lt-chooser-nested{padding:0 .5rem .375rem;border-top:1px solid var(--lt-border)}.lt-chooser-group{display:flex;align-items:center;gap:.375rem;width:100%;min-height:2.25rem;padding:.375rem .25rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.lt-chooser-group:hover{background:var(--lt-row-hover)}.lt-chooser-group-sub{padding-left:1.25rem;font-weight:500}.lt-chooser-group-label{flex:0 0 auto}.lt-chooser-chevron{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:.875rem;color:var(--lt-icon);transition:transform .15s ease}.lt-chooser-chevron svg{display:block}.lt-chooser-chevron.open{transform:rotate(90deg)}.lt-chooser-group-hint{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.6875rem;font-weight:400;color:var(--groupText)}.lt-chooser-sublist{padding-left:1.75rem}.lt-chooser-row{display:flex;align-items:center;justify-content:space-between;min-height:2.5rem;padding:.5rem;border-radius:var(--lt-radius);background:var(--lt-control-bg);cursor:grab}.lt-chooser-row:active{cursor:grabbing}.lt-chooser-row.cdk-drag-disabled{cursor:default}.lt-chooser-row:hover{background:var(--lt-row-hover)}.lt-chooser-row:hover .lt-chooser-handle{opacity:1}.lt-chooser-toggle{display:flex;align-items:center;gap:.625rem;flex:1 1 auto;min-width:0;padding:0;background:transparent;border:none;font:inherit;font-size:.875rem;color:var(--lt-cell-text);text-align:left;cursor:pointer}.lt-chooser-toggle:disabled{cursor:default;opacity:.5}.lt-chooser-box{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:1rem;height:1rem;border:.09375rem solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg);transition:background .12s,border-color .12s}.lt-chooser-toggle.checked .lt-chooser-box{background:var(--lt-link);border-color:var(--lt-link)}.lt-chooser-toggle:not(:disabled):hover .lt-chooser-box{border-color:var(--lt-link)}.lt-chooser-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-chooser-default-tag{flex:0 0 auto;margin-left:.375rem;font-size:.6875rem;font-weight:400;font-style:italic;color:var(--groupText);white-space:nowrap}.lt-chooser-handle{flex:0 0 auto;display:inline-flex;align-items:center;color:var(--lt-icon);cursor:grab;padding-left:.5rem;-webkit-user-select:none;user-select:none}.lt-chooser-handle:active{cursor:grabbing}.lt-chooser-footer{flex:0 0 auto;padding:.5rem .75rem;border-top:1px solid var(--lt-border)}.lt-chooser-reset{width:100%;height:1.875rem;font-size:.75rem;font-weight:600;color:var(--lt-link);background:transparent;border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer}.lt-chooser-reset:disabled{color:var(--groupText);cursor:default;opacity:.6}.lt-chooser-reset:not(:disabled):hover{border-color:var(--lt-link)}.lt-footer{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.625rem .125rem 0;font-family:var(--lt-font);font-size:.8125rem;color:var(--groupText)}.lt-footer-count{white-space:nowrap}.lt-footer-count span{color:var(--lt-cell-text);font-weight:600}.lt-footer-controls{display:flex;align-items:center;gap:1.25rem;flex-wrap:nowrap}.lt-rpp{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}.lt-rpp-label{text-transform:none;font-weight:400;color:var(--groupText)}.lt-rpp-select{width:auto;min-width:4rem;height:var(--lt-toolbar-height);padding:0 1.75rem 0 .625rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background-color:var(--lt-control-bg);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .5rem center;border:var(--lt-control-border-full);border-radius:var(--lt-radius);appearance:none;-webkit-appearance:none;cursor:pointer}.lt-rpp-select:focus{outline:none;border-color:var(--lt-link)}.lt-page-num{display:inline-flex;align-items:center;justify-content:center;min-width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);padding:0 .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.lt-page-num:hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-num.is-current{color:#fff;background:var(--lt-link);border-color:var(--lt-link);cursor:default}.lt-page-gap{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;color:var(--groupText)}.lt-tooltip{position:fixed;z-index:1100;pointer-events:none;animation:lt-tooltip-in .12s ease-out}@keyframes lt-tooltip-in{0%{opacity:0;transform:translateY(-.1875rem)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i6$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$4.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }, { kind: "component", type: ListMenuComponent, selector: "lib-list-menu", inputs: ["open", "items", "left", "top", "menuId"], outputs: ["itemClick", "closed"] }, { kind: "component", type: ListTableAutoRefreshComponent, selector: "lib-list-table-auto-refresh", inputs: ["seconds", "loading", "intervals"], outputs: ["refresh", "secondsChange"] }, { kind: "component", type: SelectCellComponent, selector: "lib-select-cell", inputs: ["row", "toggleItem"] }, { kind: "component", type: ListHeaderCellComponent, selector: "lib-list-header-cell", inputs: ["column", "sortDir"], outputs: ["sortChanged"] }, { kind: "component", type: ListFilterCellComponent, selector: "lib-list-filter-cell", inputs: ["column"] }, { kind: "component", type: NoItemsComponent, selector: "lib-no-items", inputs: ["image", "typeName", "isEmpty", "hasAdd", "hiddenResults", "isLoading", "readonly", "accessDenied"], outputs: ["clickEmit", "refresh"] }] }); }
13099
13919
  }
13100
13920
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableComponent, decorators: [{
13101
13921
  type: Component,
13102
- args: [{ selector: 'lib-list-table', standalone: false, providers: [ListTableService], template: "@if (showTable && !accessDenied) {\n <div class=\"lt-container\">\n <div class=\"lt-toolbar\">\n <div class=\"lt-toolbar-left\">\n <div class=\"lt-search\">\n <svg class=\"lt-search-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"lt-search-input\"\n id=\"SearchFilter\"\n type=\"text\"\n [placeholder]=\"'Search ' + (entityTypeLabel || 'items')\"\n [(ngModel)]=\"searchString\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n @if (filterableColumns.length) {\n <div class=\"lt-filter-zone\" #filterZone>\n <div class=\"lt-filters\" #filtersRow>\n @for (col of visibleFilterColumns; track col.key) {\n <span class=\"lt-filter-item\" [attr.data-filter-key]=\"col.key\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </span>\n }\n </div>\n @if (overflowFilterColumns.length) {\n <div class=\"lt-filter-more\">\n <button\n type=\"button\"\n class=\"lt-filter-more-btn\"\n [class.is-on]=\"hiddenActiveCount > 0\"\n [class.is-open]=\"showFilterOverflow\"\n (click)=\"toggleFilterOverflow($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showFilterOverflow\"\n [attr.aria-label]=\"overflowFilterColumns.length + ' more filters'\"\n id=\"FilterOverflowButton\"\n >\n <span class=\"lt-filter-more-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"><path fill=\"currentColor\" d=\"M2 3h12l-4.6 5.6v4l-2.8 1.4v-5.4z\"/></svg>\n </span>\n <span class=\"lt-filter-more-label\">More</span>\n <span class=\"lt-filter-more-count\">{{ overflowFilterColumns.length }}</span>\n </button>\n @if (showFilterOverflow) {\n <div class=\"lt-filter-overflow\" (clickOutside)=\"closeFilterOverflow($event)\">\n <div class=\"lt-filter-overflow-title\">Filters</div>\n @for (col of overflowFilterColumns; track col.key) {\n <div class=\"lt-filter-overflow-row\" [class.is-active]=\"isFilterActive(col)\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <div class=\"lt-toolbar-right\">\n <ng-template #refreshIcon>\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" d=\"M13.4 8a5.4 5.4 0 1 1-1.5-3.8\"/>\n <path fill=\"currentColor\" d=\"M13.9 2.1l.5 3.2-3.2-.5z\"/>\n </svg>\n </ng-template>\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n <lib-list-table-auto-refresh [loading]=\"filtering\" (refresh)=\"refresh()\"></lib-list-table-auto-refresh>\n <div class=\"lt-columns\">\n <button\n type=\"button\"\n class=\"lt-columns-btn\"\n [class.is-on]=\"hiddenColumnCount > 0\"\n [class.is-open]=\"showColumnChooser\"\n (click)=\"toggleColumnChooser($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showColumnChooser\"\n id=\"ColumnChooserButton\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M2 3.5h9v1.4H2zM2 7.3h6v1.4H2zM2 11.1h11v1.4H2z\"/>\n <circle cx=\"13\" cy=\"4.2\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"10\" cy=\"8\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"14.4\" cy=\"11.8\" r=\"1.7\" fill=\"currentColor\"/>\n </svg>\n <span>Columns</span>\n @if (hiddenColumnCount > 0) {\n <span class=\"lt-columns-count\">{{ hiddenColumnCount }}</span>\n }\n </button>\n @if (showColumnChooser) {\n <div class=\"lt-column-chooser\" (clickOutside)=\"closeColumnChooser()\">\n <div class=\"lt-chooser-header\">\n <span class=\"lt-chooser-title\">Columns</span>\n <span class=\"lt-chooser-subtitle\">Customize order and visibility</span>\n </div>\n <ul class=\"lt-chooser-list\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event)\">\n @for (col of chooserColumns; track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\">\n <button\n type=\"button\"\n class=\"lt-chooser-toggle\"\n [class.checked]=\"!col.hidden\"\n [disabled]=\"col.hideable === false\"\n (click)=\"toggleColumnVisible(col)\"\n [attr.aria-pressed]=\"!col.hidden\"\n >\n <span class=\"lt-chooser-box\" aria-hidden=\"true\">\n @if (!col.hidden) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/>\n </svg>\n }\n </span>\n <span class=\"lt-chooser-text\">{{ col.label }}</span>\n @if (isDefaultHidden(col)) {\n <span class=\"lt-chooser-default-tag\">hidden by default</span>\n }\n </button>\n @if (col.reorderable !== false) {\n <span class=\"lt-chooser-handle\" cdkDragHandle aria-label=\"Drag to reorder\" title=\"Drag to reorder\">\n <svg viewBox=\"0 0 10 16\" width=\"10\" height=\"15\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.25\"/><circle cx=\"7\" cy=\"3\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"8\" r=\"1.25\"/><circle cx=\"7\" cy=\"8\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"13\" r=\"1.25\"/><circle cx=\"7\" cy=\"13\" r=\"1.25\"/>\n </svg>\n </span>\n }\n </li>\n }\n </ul>\n <div class=\"lt-chooser-footer\">\n <button\n type=\"button\"\n class=\"lt-chooser-reset\"\n [disabled]=\"columnsAtDefault\"\n (click)=\"resetTableColumns()\"\n id=\"ResetColumnsButton\"\n >\n Reset to default\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div class=\"lt-scroll\" id=\"ItemTable\">\n <table class=\"lt-table\">\n <colgroup>\n @if (!options.noSelect) { <col class=\"lt-col-select\" /> }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <col [style.width]=\"colWidth(col)\" />\n }\n @if (!options.noMenu) { <col class=\"lt-col-menu\" /> }\n </colgroup>\n <thead class=\"lt-head\">\n <tr cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onReorder($event)\">\n @if (!options.noSelect) {\n <th class=\"lt-select-cell\">\n <div\n class=\"lt-select-all\"\n [class.checked]=\"allToggled\"\n [class.indeterminate]=\"someSelected\"\n (click)=\"toggleAll()\"\n id=\"SelectAllRows\"\n ></div>\n </th>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <th\n cdkDrag\n [cdkDragDisabled]=\"col.reorderable === false\"\n class=\"lt-th\"\n [ngClass]=\"col.cellClass\"\n id=\"Header_{{ col.key }}\"\n >\n <lib-list-header-cell\n [column]=\"col\"\n [sortDir]=\"col.sortDir\"\n (sortChanged)=\"onHeaderSort($event)\"\n (hideColumn)=\"hideColumn($event)\"\n ></lib-list-header-cell>\n @if (col.resizable !== false) {\n <span class=\"lt-resize\" (pointerdown)=\"startResize(col, $event)\"></span>\n }\n </th>\n }\n @if (!options.noMenu) {\n <th class=\"lt-menu-cell\">\n <div class=\"lt-header-menu-trigger\" (click)=\"openHeaderActionMenu($event)\" id=\"ColumnMenuHeader\">\n &#8942;\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rowData; track trackRow($index, row); let i = $index) {\n <tr\n class=\"lt-row\"\n [class.lt-row-odd]=\"i % 2 === 1\"\n [class.lt-row-selected]=\"row.selected\"\n [class.row-invalid]=\"row.invalid\"\n [class.row-copied]=\"row.copied\"\n >\n @if (!options.noSelect) {\n <td class=\"lt-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </td>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <td\n class=\"lt-cell\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @switch (renderMode(col)) {\n @case ('component') {\n <ng-container *ngComponentOutlet=\"col.cell; inputs: cellInputs(col, row, i)\"></ng-container>\n }\n @case ('html') {\n <span class=\"lt-cell-html\" [innerHTML]=\"cellHtml(col, row, i)\"></span>\n }\n @default {\n <span class=\"lt-cell-text\">{{ cellText(col, row, i) }}</span>\n }\n }\n </td>\n }\n @if (!options.noMenu) {\n <td class=\"lt-menu-cell\">\n @if (row.actionList?.length) {\n <button\n type=\"button\"\n class=\"lt-row-menu-trigger\"\n (click)=\"$event.stopPropagation(); openActionMenu($event, row)\"\n attr.id=\"RowMenu_{{ row.id }}\"\n aria-label=\"Row actions\"\n >&#8942;</button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n @if (!isLoading && filterApplied && (!rowData || rowData.length === 0)) {\n <div class=\"no-rows-message\">No Rows To Show</div>\n }\n </div>\n\n <div class=\"lt-footer\">\n <div class=\"lt-footer-count\">\n Showing <span id=\"Start\">{{ startCount }}</span>&#8211;<span id=\"End\">{{ endCount }}</span>\n of <span id=\"Total\">{{ totalCount }}</span>\n </div>\n <div class=\"lt-footer-controls\">\n <div class=\"lt-rpp\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Rows per page\"\n >\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"lt-pager\">\n <button type=\"button\" class=\"lt-page-btn icon-prev\" [disabled]=\"prevDisabled\" (click)=\"prevPage()\" aria-label=\"Previous page\"></button>\n @if (!cursorPager) {\n @for (p of pageWindow; track $index) {\n @if (p === 0) {\n <span class=\"lt-page-gap\">&#8230;</span>\n } @else {\n <button\n type=\"button\"\n class=\"lt-page-num\"\n [class.is-current]=\"p === currentPageNum\"\n (click)=\"goToPage(p)\"\n >{{ p }}</button>\n }\n }\n }\n <button type=\"button\" class=\"lt-page-btn icon-next\" [disabled]=\"nextDisabled\" (click)=\"nextPage()\" aria-label=\"Next page\"></button>\n </div>\n </div>\n </div>\n\n <!-- header action menu -->\n <lib-list-menu\n menuId=\"HeaderActionMenu\"\n [open]=\"openHeaderMenu\"\n [items]=\"headerMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onHeaderMenuClick($event)\"\n (closed)=\"closeHeaderActionMenu()\"\n ></lib-list-menu>\n\n <!-- row action menu -->\n <lib-list-menu\n menuId=\"RowActionMenu\"\n [open]=\"openMenu\"\n [items]=\"rowMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onRowMenuClick($event)\"\n (closed)=\"closeActionMenu()\"\n ></lib-list-menu>\n </div>\n} @else {\n <lib-no-items\n (clickEmit)=\"openCreate()\"\n [image]=\"noItemsImage\"\n [isEmpty]=\"!rowData || rowData.length === 0\"\n [isLoading]=\"isLoading\"\n [typeName]=\"entityTypeLabel\"\n [hasAdd]=\"showNoItemsAdd\"\n [readonly]=\"noItemsReadonly\"\n [accessDenied]=\"accessDenied\"\n ></lib-no-items>\n}\n\n@if (hoverTooltip) {\n <div class=\"lt-tooltip\" [ngStyle]=\"{ left: hoverTooltip.left + 'px', top: hoverTooltip.top + 'px' }\">\n <ng-container *ngComponentOutlet=\"hoverTooltip.component; inputs: hoverTooltip.inputs\"></ng-container>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.lt-columns-btn,.lt-refresh-btn,.lt-filter-more-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-columns-btn svg,.lt-refresh-btn svg,.lt-filter-more-btn svg{display:block}.lt-columns-btn:hover,.lt-refresh-btn:hover,.lt-filter-more-btn:hover,.is-open.lt-columns-btn,.is-open.lt-refresh-btn,.is-open.lt-filter-more-btn,.is-on.lt-columns-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{border-color:var(--lt-link);color:var(--lt-link)}.lt-columns-count,.lt-filter-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.125rem;height:1.125rem;padding:0 .3125rem;font-size:.6875rem;font-weight:700;color:var(--lt-control-bg);background:var(--lt-link);border-radius:.5625rem}.lt-container{display:flex;position:relative;height:calc(100% - var(--lt-container-reserve, 5.625rem));width:100%;flex-direction:column}.lt-scroll{position:relative;z-index:1;width:100%;flex:0 1 auto;min-height:0;overflow:auto;background-color:var(--lt-bg)}.lt-scroll .no-rows-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--lt-cell-text);font-size:.875rem;pointer-events:none}.lt-table{width:100%;border-collapse:collapse;table-layout:fixed;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text)}.lt-head{position:sticky;top:0;z-index:10}.lt-head th{position:relative;box-sizing:border-box;height:2.75rem;padding:0 .3125rem 0 .625rem;text-align:left;vertical-align:middle;background-color:var(--lt-header-bg);border-bottom:1px solid var(--lt-header-underline);white-space:nowrap;-webkit-user-select:none;user-select:none}.lt-head th.lt-th{border-right:1px solid var(--lt-border)}.lt-head .lt-select-cell,.lt-head .lt-menu-cell{width:3.75rem;padding:0}.lt-resize{position:absolute;right:0;top:50%;transform:translateY(-50%);width:.625rem;height:.875rem;cursor:col-resize}.lt-resize:before,.lt-resize:after{content:\"\";position:absolute;top:0;height:100%;width:1px;background:transparent;transition:background .15s}.lt-resize:before{right:.25rem}.lt-resize:after{right:.125rem}.lt-resize:hover:before,.lt-resize:hover:after{background:var(--lt-icon)}.lt-head th:hover .lt-resize:before,.lt-head th:hover .lt-resize:after{background:var(--lt-border)}.lt-select-all{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;background:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;cursor:pointer;transition:border-color .12s,background-color .12s}.lt-select-all:hover{border-color:var(--lt-link)}.lt-select-all.checked{background-color:var(--lt-link);border-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.lt-select-all.indeterminate{background-color:var(--lt-link);border-color:var(--lt-link);background-image:linear-gradient(#fff,#fff);background-size:.5rem .125rem}.lt-header-menu-trigger{display:flex;align-items:center;justify-content:center;height:100%;font-size:1.25rem;color:var(--lt-icon);cursor:pointer}.lt-header-menu-trigger:hover{color:var(--lt-icon-active)}.lt-row-menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;font-size:1.25rem;line-height:1;color:var(--lt-icon);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;transition:color .15s,background .15s}.lt-row-menu-trigger:hover{color:var(--lt-icon-active);background:var(--lt-row-hover)}.cdk-drag-preview{box-sizing:border-box;background-color:var(--lt-header-bg);color:var(--lt-header-text);box-shadow:var(--shadowLarge);padding:0 .625rem;display:flex;align-items:center;border-radius:var(--lt-radius)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.lt-row{height:var(--lt-row-height);background-color:var(--lt-row-bg);color:var(--lt-cell-text)}.lt-row td{border-bottom:1px solid var(--lt-border)}.lt-row.lt-row-odd{background-color:var(--lt-row-alt-bg)}.lt-row:hover{background-color:var(--lt-row-hover)}.lt-row.lt-row-selected{background-color:var(--lt-row-selected)}.lt-row.lt-row-selected:hover{background-color:var(--lt-row-hover)}.lt-row.row-invalid{background-color:var(--invalid)!important}.lt-row.row-copied{background-color:var(--copiedRow)!important}.lt-cell{height:var(--lt-row-height);padding:0 .625rem 0 1.25rem;text-align:left;vertical-align:middle;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-user-select:text;user-select:text}.lt-cell-text{display:inline-block;overflow:hidden;text-overflow:ellipsis;max-width:100%;vertical-align:middle}.lt-cell.lt-cell-center{text-align:center}.lt-badge-cell .lt-cell-text{padding:.1875rem .5rem;font-size:.75rem;font-weight:600;line-height:1.2;color:var(--lt-cell-text);background:color-mix(in srgb,var(--lt-border) 45%,transparent);border:1px solid var(--lt-border);border-radius:999px}.lt-badge-cell .lt-cell-text:empty{display:none}.lt-select-cell,.lt-menu-cell{width:3.75rem;padding:0;text-align:center;vertical-align:middle;overflow:visible;position:relative}.lt-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin:var(--lt-toolbar-margin-y, 0) 0;padding:0 0 var(--lt-toolbar-pad-bottom, .625rem);min-height:var(--lt-toolbar-height);position:relative;z-index:30}.lt-toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1 1 auto;min-width:0;flex-wrap:nowrap}.lt-filter-zone{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;flex:0 1 auto;min-width:0}.lt-filters{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;min-width:0;flex:0 1 auto;overflow:hidden}.lt-filter-item{display:inline-flex;flex:0 0 auto}.lt-filter-more{position:relative;flex:0 0 auto}.lt-filter-more-btn{gap:.375rem;padding:0 .625rem}.lt-filter-more-btn .lt-filter-more-icon{display:inline-flex}.lt-filter-overflow{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;min-width:15rem;max-width:min(22rem,90vw);display:flex;flex-direction:column;gap:.5rem;padding:.625rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-chooser-in .12s ease-out}.lt-filter-overflow-title{font-family:var(--lt-font);font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--groupText)}.lt-filter-overflow-row{display:flex}.lt-filter-overflow-row lib-list-filter-cell,.lt-filter-overflow-row .lt-filter-item{flex:1 1 auto;min-width:0}.lt-toolbar-right{display:flex;align-items:center;gap:.75rem;flex:0 0 auto}.lt-divider{flex:0 0 auto;width:1px;height:1.5rem;background:var(--lt-border)}.lt-search{position:relative;display:flex;align-items:center;width:18rem;max-width:40vw;flex:0 1 18rem}.lt-search-icon{position:absolute;left:.625rem;display:block;color:var(--lt-icon);pointer-events:none}.lt-search-input{width:100%;height:var(--lt-toolbar-height);padding:0 .75rem 0 1.75rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);outline:none;transition:border-color .15s,box-shadow .15s}.lt-search-input::placeholder{color:var(--placeholder)}.lt-search-input:focus{border-color:var(--lt-link);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-count{font-size:.8125rem;color:var(--groupText);white-space:nowrap}.lt-count .lt-count-of{color:var(--groupText)}.lt-pager{display:flex;align-items:center;gap:.25rem}.lt-page-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);color:var(--lt-icon);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;font-size:.9375rem;transition:border-color .15s,color .15s}.lt-page-btn:not(:disabled):hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-btn:disabled{opacity:.4;cursor:default}.lt-refresh-btn{padding:0;width:var(--lt-toolbar-height);justify-content:center}.lt-refresh-btn:disabled{opacity:.5;cursor:default;border-color:var(--lt-border);color:var(--lt-cell-text)}.lt-columns{position:relative;flex:0 0 auto}.lt-column-chooser{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;width:15rem;max-height:calc(100vh - 9rem);display:flex;flex-direction:column;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);overflow:hidden;animation:lt-chooser-in .12s ease-out}@keyframes lt-chooser-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-chooser-header{flex:0 0 auto;display:flex;flex-direction:column;padding:.625rem .75rem .5rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-title{font-size:.8125rem;font-weight:700;color:var(--lt-cell-text)}.lt-chooser-subtitle{font-size:.6875rem;color:var(--groupText)}.lt-chooser-list{flex:1 1 auto;list-style:none;margin:0;padding:.375rem .5rem;min-height:0;overflow-y:auto}.lt-chooser-row{display:flex;align-items:center;justify-content:space-between;min-height:2.5rem;padding:.5rem;border-radius:var(--lt-radius);background:var(--lt-control-bg)}.lt-chooser-row:hover{background:var(--lt-row-hover)}.lt-chooser-row:hover .lt-chooser-handle{opacity:1}.lt-chooser-toggle{display:flex;align-items:center;gap:.625rem;flex:1 1 auto;min-width:0;padding:0;background:transparent;border:none;font:inherit;font-size:.875rem;color:var(--lt-cell-text);text-align:left;cursor:pointer}.lt-chooser-toggle:disabled{cursor:default;opacity:.5}.lt-chooser-box{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:1rem;height:1rem;border:.09375rem solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg);transition:background .12s,border-color .12s}.lt-chooser-toggle.checked .lt-chooser-box{background:var(--lt-link);border-color:var(--lt-link)}.lt-chooser-toggle:not(:disabled):hover .lt-chooser-box{border-color:var(--lt-link)}.lt-chooser-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-chooser-default-tag{flex:0 0 auto;margin-left:.375rem;font-size:.6875rem;font-weight:400;font-style:italic;color:var(--groupText);white-space:nowrap}.lt-chooser-handle{flex:0 0 auto;display:inline-flex;align-items:center;color:var(--lt-icon);cursor:grab;padding-left:.5rem;-webkit-user-select:none;user-select:none}.lt-chooser-handle:active{cursor:grabbing}.lt-chooser-footer{flex:0 0 auto;padding:.5rem .75rem;border-top:1px solid var(--lt-border)}.lt-chooser-reset{width:100%;height:1.875rem;font-size:.75rem;font-weight:600;color:var(--lt-link);background:transparent;border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer}.lt-chooser-reset:disabled{color:var(--groupText);cursor:default;opacity:.6}.lt-chooser-reset:not(:disabled):hover{border-color:var(--lt-link)}.lt-footer{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.625rem .125rem 0;font-family:var(--lt-font);font-size:.8125rem;color:var(--groupText)}.lt-footer-count{white-space:nowrap}.lt-footer-count span{color:var(--lt-cell-text);font-weight:600}.lt-footer-controls{display:flex;align-items:center;gap:1.25rem;flex-wrap:nowrap}.lt-rpp{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}.lt-rpp-label{text-transform:none;font-weight:400;color:var(--groupText)}.lt-rpp-select{width:auto;min-width:4rem;height:var(--lt-toolbar-height);padding:0 1.75rem 0 .625rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background-color:var(--lt-control-bg);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .5rem center;border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);appearance:none;-webkit-appearance:none;cursor:pointer}.lt-rpp-select:focus{outline:none;border-color:var(--lt-link)}.lt-page-num{display:inline-flex;align-items:center;justify-content:center;min-width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);padding:0 .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-control-border, var(--lt-border));border-radius:var(--lt-radius);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.lt-page-num:hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-num.is-current{color:#fff;background:var(--lt-link);border-color:var(--lt-link);cursor:default}.lt-page-gap{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;color:var(--groupText)}.lt-tooltip{position:fixed;z-index:1100;pointer-events:none;animation:lt-tooltip-in .12s ease-out}@keyframes lt-tooltip-in{0%{opacity:0;transform:translateY(-.1875rem)}to{opacity:1;transform:translateY(0)}}\n"] }]
13922
+ args: [{ selector: 'lib-list-table', standalone: false, providers: [ListTableService], template: "@if (showTable && !accessDenied) {\n <div class=\"lt-container\">\n <div class=\"lt-toolbar\">\n <div class=\"lt-toolbar-left\">\n <div class=\"lt-search\">\n <svg class=\"lt-search-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"lt-search-input\"\n id=\"SearchFilter\"\n type=\"text\"\n [placeholder]=\"'Search ' + (entityTypeLabel || 'items')\"\n [(ngModel)]=\"searchString\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n @if (filterableColumns.length) {\n <div class=\"lt-filter-zone\" #filterZone>\n <div class=\"lt-filters\" #filtersRow>\n @for (col of visibleFilterColumns; track col.key) {\n <span class=\"lt-filter-item\" [attr.data-filter-key]=\"col.key\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </span>\n }\n </div>\n @if (overflowFilterColumns.length) {\n <div class=\"lt-filter-more\">\n <button\n type=\"button\"\n class=\"lt-filter-more-btn\"\n [class.is-on]=\"hiddenActiveCount > 0\"\n [class.is-open]=\"showFilterOverflow\"\n (click)=\"toggleFilterOverflow($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showFilterOverflow\"\n [attr.aria-label]=\"overflowFilterColumns.length + ' more filters'\"\n id=\"FilterOverflowButton\"\n >\n <span class=\"lt-filter-more-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"><path fill=\"currentColor\" d=\"M2 3h12l-4.6 5.6v4l-2.8 1.4v-5.4z\"/></svg>\n </span>\n <span class=\"lt-filter-more-label\">More</span>\n <span class=\"lt-filter-more-count\">{{ overflowFilterColumns.length }}</span>\n </button>\n @if (showFilterOverflow) {\n <div class=\"lt-filter-overflow\" (clickOutside)=\"closeFilterOverflow($event)\">\n <div class=\"lt-filter-overflow-title\">Filters</div>\n @for (col of overflowFilterColumns; track col.key) {\n <div class=\"lt-filter-overflow-row\" [class.is-active]=\"isFilterActive(col)\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n <!-- Consumer-supplied toolbar controls (e.g. page-specific filters), rendered inline beside search. -->\n <ng-content select=\"[toolbar-filters]\"></ng-content>\n </div>\n <div class=\"lt-toolbar-right\">\n <ng-template #refreshIcon>\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" d=\"M13.4 8a5.4 5.4 0 1 1-1.5-3.8\"/>\n <path fill=\"currentColor\" d=\"M13.9 2.1l.5 3.2-3.2-.5z\"/>\n </svg>\n </ng-template>\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n <lib-list-table-auto-refresh [loading]=\"filtering\" (refresh)=\"refresh()\"></lib-list-table-auto-refresh>\n <div class=\"lt-columns\">\n <button\n type=\"button\"\n class=\"lt-columns-btn\"\n [class.is-on]=\"hiddenColumnCount > 0\"\n [class.is-open]=\"showColumnChooser\"\n (click)=\"toggleColumnChooser($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showColumnChooser\"\n id=\"ColumnChooserButton\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M2 3.5h9v1.4H2zM2 7.3h6v1.4H2zM2 11.1h11v1.4H2z\"/>\n <circle cx=\"13\" cy=\"4.2\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"10\" cy=\"8\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"14.4\" cy=\"11.8\" r=\"1.7\" fill=\"currentColor\"/>\n </svg>\n <span>Columns</span>\n @if (hiddenColumnCount > 0) {\n <span class=\"lt-columns-count\">{{ hiddenColumnCount }}</span>\n }\n </button>\n @if (showColumnChooser) {\n <div class=\"lt-column-chooser\" (clickOutside)=\"closeColumnChooser()\">\n <div class=\"lt-chooser-header\">\n <span class=\"lt-chooser-title\">Columns</span>\n <span class=\"lt-chooser-subtitle\">Customize order and visibility</span>\n </div>\n @if (canSwitchTreeVariant && showTreeVariantToggle) {\n <div class=\"lt-chooser-view\" role=\"group\" aria-label=\"Expanded row layout\">\n <span class=\"lt-chooser-view-label\">Expanded rows</span>\n <div class=\"lt-chooser-view-toggle\">\n <button type=\"button\" class=\"lt-chooser-view-opt\" [class.active]=\"treeVariant === 'flat'\" (click)=\"setTreeVariant('flat')\">Flat</button>\n <button type=\"button\" class=\"lt-chooser-view-opt\" [class.active]=\"treeVariant === 'card'\" (click)=\"setTreeVariant('card')\">Nested</button>\n </div>\n </div>\n }\n <div class=\"lt-chooser-scroll\">\n <ul class=\"lt-chooser-list\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event)\">\n @for (col of chooserColumns; track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\"\n (cdkDragStarted)=\"onChooserDragStarted()\" (cdkDragEnded)=\"onChooserDragEnded()\">\n <ng-container *ngTemplateOutlet=\"chooserRowBody; context: { col: col }\"></ng-container>\n </li>\n }\n </ul>\n @if (canSwitchTreeVariant && childLevels.length) {\n <div class=\"lt-chooser-nested\">\n <button\n type=\"button\"\n class=\"lt-chooser-group\"\n (click)=\"toggleNestedGroup('__root')\"\n [attr.aria-expanded]=\"isNestedGroupOpen('__root')\"\n >\n <span class=\"lt-chooser-chevron\" [class.open]=\"isNestedGroupOpen('__root')\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 4l4 4-4 4\"/></svg>\n </span>\n <span class=\"lt-chooser-group-label\">Nested rows</span>\n </button>\n @if (isNestedGroupOpen('__root')) {\n @for (level of childLevels; track level) {\n <button\n type=\"button\"\n class=\"lt-chooser-group lt-chooser-group-sub\"\n (click)=\"toggleNestedGroup('lvl' + level)\"\n [attr.aria-expanded]=\"isNestedGroupOpen('lvl' + level)\"\n >\n <span class=\"lt-chooser-chevron\" [class.open]=\"isNestedGroupOpen('lvl' + level)\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"11\" height=\"11\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 4l4 4-4 4\"/></svg>\n </span>\n <span class=\"lt-chooser-group-label\">Level {{ level }}</span>\n <span class=\"lt-chooser-group-hint\">{{ childLevelLabel(level) }}</span>\n </button>\n @if (isNestedGroupOpen('lvl' + level)) {\n <ul class=\"lt-chooser-list lt-chooser-sublist\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event, level)\">\n @for (col of allColumnsForLevel(level); track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\"\n (cdkDragStarted)=\"onChooserDragStarted()\" (cdkDragEnded)=\"onChooserDragEnded()\">\n <ng-container *ngTemplateOutlet=\"chooserRowBody; context: { col: col, level: level }\"></ng-container>\n </li>\n }\n </ul>\n }\n }\n }\n </div>\n }\n </div>\n <div class=\"lt-chooser-footer\">\n <button\n type=\"button\"\n class=\"lt-chooser-reset\"\n [disabled]=\"columnsAtDefault\"\n (click)=\"resetTableColumns()\"\n id=\"ResetColumnsButton\"\n >\n Reset to default\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- inner content of a Columns-chooser row, shared by the top-level and nested lists.\n The <li cdkDrag> wrapper stays inline in each list (above) so cdkDropList's content\n query still registers it as a draggable; only the body is shared here.\n `level` undefined targets the top-level columns, a number targets that nested depth. -->\n <ng-template #chooserRowBody let-col=\"col\" let-level=\"level\">\n <button\n type=\"button\"\n class=\"lt-chooser-toggle\"\n [class.checked]=\"!col.hidden\"\n [disabled]=\"col.hideable === false || isLastVisibleColumn(col, level)\"\n [attr.title]=\"isLastVisibleColumn(col, level) ? 'At least one column must stay visible' : null\"\n (click)=\"toggleColumnVisible(col, level)\"\n [attr.aria-pressed]=\"!col.hidden\"\n >\n <span class=\"lt-chooser-box\" aria-hidden=\"true\">\n <!-- always rendered: the check is white, so it stays invisible until the box\n turns blue when checked. Keeping it in the DOM means clicking to hide a\n column never detaches the click target, which would otherwise make the\n click-outside handler think the click was outside and close the menu. -->\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/>\n </svg>\n </span>\n <span class=\"lt-chooser-text\">{{ col.label }}</span>\n @if (level == null && isDefaultHidden(col)) {\n <span class=\"lt-chooser-default-tag\">hidden by default</span>\n }\n </button>\n @if (col.reorderable !== false) {\n <!-- the whole row is the drag target (cdkDrag on the <li>); this is just the grip affordance -->\n <span class=\"lt-chooser-handle\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 16\" width=\"10\" height=\"15\" fill=\"currentColor\">\n <circle cx=\"3\" cy=\"3\" r=\"1.25\"/><circle cx=\"7\" cy=\"3\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"8\" r=\"1.25\"/><circle cx=\"7\" cy=\"8\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"13\" r=\"1.25\"/><circle cx=\"7\" cy=\"13\" r=\"1.25\"/>\n </svg>\n </span>\n }\n </ng-template>\n\n <div class=\"lt-scroll\" id=\"ItemTable\">\n <!-- expand/collapse control, reused by root rows and nested child rows -->\n <ng-template #treeToggle let-row=\"row\">\n @if (canExpand(row)) {\n <button\n type=\"button\"\n class=\"lt-tree-toggle\"\n [class.is-expanded]=\"row.__treeExpanded\"\n [class.is-loading]=\"row.__treeLoading\"\n [class.is-error]=\"row.__treeLoadError\"\n (click)=\"$event.stopPropagation(); toggleNode(row)\"\n [attr.aria-expanded]=\"!!row.__treeExpanded\"\n [attr.aria-busy]=\"!!row.__treeLoading\"\n [attr.title]=\"row.__treeLoadError ? 'Could not load children - click to retry' : null\"\n [attr.aria-label]=\"row.__treeExpanded ? 'Collapse row' : 'Expand row'\"\n >\n <svg class=\"lt-tree-chevron\" viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 4l4 4-4 4\"/>\n </svg>\n @if (row.__treeLoading) {\n <span class=\"lt-tree-spinner\" aria-hidden=\"true\"></span>\n }\n </button>\n } @else {\n <span class=\"lt-tree-spacer\" aria-hidden=\"true\"></span>\n }\n </ng-template>\n\n <!-- a single cell's content, reused across the fixed grid and the nested grid -->\n <ng-template #cellBody let-col=\"col\" let-row=\"row\" let-i=\"i\">\n @switch (renderMode(col)) {\n @case ('component') {\n <ng-container *ngComponentOutlet=\"col.cell; inputs: cellInputs(col, row, i)\"></ng-container>\n }\n @case ('html') {\n <span class=\"lt-cell-html\" [innerHTML]=\"cellHtml(col, row, i)\"></span>\n }\n @default {\n <span class=\"lt-cell-text\">{{ cellText(col, row, i) }}</span>\n }\n }\n </ng-template>\n\n <table class=\"lt-table\">\n <colgroup>\n @if (!options.noSelect) { <col class=\"lt-col-select\" /> }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <col [style.width]=\"colWidth(col)\" />\n }\n @if (!options.noMenu) { <col class=\"lt-col-menu\" /> }\n </colgroup>\n <thead class=\"lt-head\">\n <tr cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onReorder($event)\">\n @if (!options.noSelect) {\n <th class=\"lt-select-cell\">\n <div\n class=\"lt-select-all\"\n [class.checked]=\"allToggled\"\n [class.indeterminate]=\"someSelected\"\n (click)=\"toggleAll()\"\n id=\"SelectAllRows\"\n ></div>\n </th>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <th\n cdkDrag\n [cdkDragDisabled]=\"col.reorderable === false\"\n class=\"lt-th\"\n [class.lt-th-tree]=\"isTreeCell(col)\"\n [ngClass]=\"col.cellClass\"\n id=\"Header_{{ col.key }}\"\n >\n <lib-list-header-cell\n [column]=\"col\"\n [sortDir]=\"col.sortDir\"\n (sortChanged)=\"onHeaderSort($event)\"\n ></lib-list-header-cell>\n @if (col.resizable !== false) {\n <span class=\"lt-resize\" (pointerdown)=\"startResize(col, $event)\"></span>\n }\n </th>\n }\n @if (!options.noMenu) {\n <th class=\"lt-menu-cell\">\n <div class=\"lt-header-menu-trigger\" (click)=\"openHeaderActionMenu($event)\" id=\"ColumnMenuHeader\">\n &#8942;\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of flatRows; track trackRow($index, row); let i = $index) {\n @if (isTreeMode && row.__treeLevel > 0 && hasChildColumns) {\n <!-- nested rows with their own per-level columns, laid out in a grid\n that spans the full table width (so they need not match the parent) -->\n @if (row.__treeFirstChild) {\n <tr class=\"lt-tree-subhead-row\">\n <td\n [attr.colspan]=\"treeSpanCount\"\n [style.--lt-tree-indent.rem]=\"treeIndentFor(row.__treeLevel)\"\n [style.--lt-tree-first-indent.rem]=\"treeFlatFirstIndentRem(row.__treeLevel)\"\n [style.--lt-tree-lead-offset]=\"treeLeadOffset(row.__treeLevel)\"\n [style.background-image]=\"treeAncestorRails(row.__treeLevel)\"\n >\n <div\n class=\"lt-tree-grid\"\n [style.grid-template-columns]=\"gridTemplate(row.__treeLevel)\"\n >\n @if (childSelectEnabled) {\n <span class=\"lt-tree-subhead-cell lt-tree-select-cell\"></span>\n }\n @for (col of columnsForLevel(row.__treeLevel); track col.key; let last = $last) {\n <span class=\"lt-tree-subhead-cell\" [class.lt-tree-indent-col]=\"isTreeColumn(col, row.__treeLevel)\" [ngClass]=\"cellClass(col)\">\n {{ col.label }}\n @if (!last && col.resizable !== false) {\n <span class=\"lt-resize lt-tree-resize\" (pointerdown)=\"startChildResize(col, row.__treeLevel, $event)\"></span>\n }\n </span>\n }\n </div>\n </td>\n </tr>\n }\n <tr\n class=\"lt-row lt-tree-child-row\"\n [class.lt-tree-last-child]=\"row.__treeLastChild\"\n [class.lt-row-selected]=\"row.selected\"\n [class.lt-row-expanded]=\"row.__treeExpanded\"\n >\n <td\n [attr.colspan]=\"treeSpanCount\"\n [style.--lt-tree-indent.rem]=\"treeIndentFor(row.__treeLevel)\"\n [style.--lt-tree-first-indent.rem]=\"treeFlatFirstIndentRem(row.__treeLevel)\"\n [style.--lt-tree-lead-offset]=\"treeLeadOffset(row.__treeLevel)\"\n [style.background-image]=\"treeAncestorRails(row.__treeLevel)\"\n >\n <div\n class=\"lt-tree-grid\"\n [style.grid-template-columns]=\"gridTemplate(row.__treeLevel)\"\n >\n @if (childSelectEnabled) {\n <div class=\"lt-tree-cellbox lt-tree-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </div>\n }\n @for (col of columnsForLevel(row.__treeLevel); track col.key) {\n <div\n class=\"lt-tree-cellbox\"\n [class.lt-tree-indent-col]=\"isTreeColumn(col, row.__treeLevel)\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @if (isTreeColumn(col, row.__treeLevel) && showTreeLead(row)) {\n <span class=\"lt-tree-lead\">\n <ng-container *ngTemplateOutlet=\"treeToggle; context: {row: row}\"></ng-container>\n </span>\n }\n <ng-container *ngTemplateOutlet=\"cellBody; context: {col: col, row: row, i: i}\"></ng-container>\n </div>\n }\n </div>\n </td>\n </tr>\n } @else {\n <tr\n class=\"lt-row\"\n [class.lt-row-odd]=\"i % 2 === 1\"\n [class.lt-row-selected]=\"row.selected\"\n [class.lt-row-expanded]=\"row.__treeExpanded\"\n [class.row-invalid]=\"row.invalid\"\n [class.row-copied]=\"row.copied\"\n >\n @if (!options.noSelect) {\n <td class=\"lt-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </td>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <td\n class=\"lt-cell\"\n [class.lt-tree-cell]=\"isTreeCell(col)\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @if (isTreeCell(col)) {\n <span class=\"lt-tree-cell-inner\">\n <span class=\"lt-tree-lead\" [style.margin-left.rem]=\"(row.__treeLevel || 0) * treeIndentRem\">\n <ng-container *ngTemplateOutlet=\"treeToggle; context: {row: row}\"></ng-container>\n </span>\n <ng-container *ngTemplateOutlet=\"cellBody; context: {col: col, row: row, i: i}\"></ng-container>\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellBody; context: {col: col, row: row, i: i}\"></ng-container>\n }\n </td>\n }\n @if (!options.noMenu) {\n <td class=\"lt-menu-cell\">\n @if (row.actionList?.length) {\n <button\n type=\"button\"\n class=\"lt-row-menu-trigger\"\n (click)=\"$event.stopPropagation(); openActionMenu($event, row)\"\n attr.id=\"RowMenu_{{ row.id }}\"\n aria-label=\"Row actions\"\n >&#8942;</button>\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n @if (!isLoading && filterApplied && (!rowData || rowData.length === 0)) {\n <div class=\"no-rows-message\">No Rows To Show</div>\n }\n </div>\n\n <div class=\"lt-footer\">\n <div class=\"lt-footer-count\">\n Showing <span id=\"Start\">{{ startCount }}</span>&#8211;<span id=\"End\">{{ endCount }}</span>\n of <span id=\"Total\">{{ totalCount }}</span>\n </div>\n <div class=\"lt-footer-controls\">\n <div class=\"lt-rpp\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Rows per page\"\n >\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"lt-pager\">\n <button type=\"button\" class=\"lt-page-btn icon-prev\" [disabled]=\"prevDisabled\" (click)=\"prevPage()\" aria-label=\"Previous page\"></button>\n @if (!cursorPager) {\n @for (p of pageWindow; track $index) {\n @if (p === 0) {\n <span class=\"lt-page-gap\">&#8230;</span>\n } @else {\n <button\n type=\"button\"\n class=\"lt-page-num\"\n [class.is-current]=\"p === currentPageNum\"\n (click)=\"goToPage(p)\"\n >{{ p }}</button>\n }\n }\n }\n <button type=\"button\" class=\"lt-page-btn icon-next\" [disabled]=\"nextDisabled\" (click)=\"nextPage()\" aria-label=\"Next page\"></button>\n </div>\n </div>\n </div>\n\n <!-- header action menu -->\n <lib-list-menu\n menuId=\"HeaderActionMenu\"\n [open]=\"openHeaderMenu\"\n [items]=\"headerMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onHeaderMenuClick($event)\"\n (closed)=\"closeHeaderActionMenu()\"\n ></lib-list-menu>\n\n <!-- row action menu -->\n <lib-list-menu\n menuId=\"RowActionMenu\"\n [open]=\"openMenu\"\n [items]=\"rowMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onRowMenuClick($event)\"\n (closed)=\"closeActionMenu()\"\n ></lib-list-menu>\n </div>\n} @else {\n <lib-no-items\n (clickEmit)=\"openCreate()\"\n [image]=\"noItemsImage\"\n [isEmpty]=\"!rowData || rowData.length === 0\"\n [isLoading]=\"isLoading\"\n [typeName]=\"entityTypeLabel\"\n [hasAdd]=\"showNoItemsAdd\"\n [readonly]=\"noItemsReadonly\"\n [accessDenied]=\"accessDenied\"\n ></lib-no-items>\n}\n\n@if (hoverTooltip) {\n <div class=\"lt-tooltip\" [ngStyle]=\"{ left: hoverTooltip.left + 'px', top: hoverTooltip.top + 'px' }\">\n <ng-container *ngComponentOutlet=\"hoverTooltip.component; inputs: hoverTooltip.inputs\"></ng-container>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.lt-columns-btn,.lt-refresh-btn,.lt-filter-more-btn{display:inline-flex;align-items:center;gap:var(--lt-control-gap);height:var(--lt-toolbar-height);padding:0 var(--lt-control-padding-x);font-family:var(--lt-font);font-size:var(--lt-control-font-size);font-weight:var(--lt-control-font-weight);color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-columns-btn svg,.lt-refresh-btn svg,.lt-filter-more-btn svg{display:block}.lt-columns-btn:hover,.lt-refresh-btn:hover,.lt-filter-more-btn:hover,.is-open.lt-columns-btn,.is-open.lt-refresh-btn,.is-open.lt-filter-more-btn,.is-on.lt-columns-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{border-color:var(--lt-link);color:var(--lt-link)}.is-on.lt-columns-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.lt-columns-count,.lt-filter-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.125rem;height:1.125rem;padding:0 .3125rem;font-size:.6875rem;font-weight:700;color:var(--lt-control-bg);background:var(--lt-link);border-radius:.5625rem}.lt-container{display:flex;position:relative;height:calc(100% - var(--lt-container-reserve, 5.625rem));width:100%;flex-direction:column}.lt-scroll{position:relative;z-index:1;width:100%;flex:0 1 auto;min-height:0;overflow:auto;background-color:var(--lt-bg)}.lt-scroll .no-rows-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--lt-cell-text);font-size:.875rem;pointer-events:none}.lt-table{width:100%;border-collapse:collapse;table-layout:fixed;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text)}.lt-head{position:sticky;top:0;z-index:10}.lt-head th{position:relative;box-sizing:border-box;height:2.6rem;padding:0 .3125rem 0 1.25rem;text-align:left;vertical-align:middle;background-color:var(--lt-header-bg);border-bottom:1px solid var(--lt-header-underline);white-space:nowrap;-webkit-user-select:none;user-select:none}.lt-head th.lt-th-tree{padding-left:2.75rem}.lt-head .lt-select-cell,.lt-head .lt-menu-cell{width:3.75rem;padding:0}.lt-resize{position:absolute;right:0;top:50%;transform:translateY(-50%);width:.75rem;height:2.25rem;cursor:col-resize}.lt-resize:before,.lt-resize:after{content:\"\";position:absolute;top:50%;height:1.25rem;transform:translateY(-50%);width:1px;background:color-mix(in srgb,var(--lt-border) 50%,var(--lt-icon));transition:background .15s}.lt-resize:before{right:.3125rem}.lt-resize:after{right:.125rem}.lt-resize:hover:before,.lt-resize:hover:after{background:color-mix(in srgb,var(--lt-icon) 72%,var(--lt-cell-text))}.lt-select-all{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;background:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;cursor:pointer;transition:border-color .12s,background-color .12s}.lt-select-all:hover{border-color:var(--lt-link)}.lt-select-all.checked{background-color:var(--lt-link);border-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.lt-select-all.indeterminate{background-color:var(--lt-link);border-color:var(--lt-link);background-image:linear-gradient(#fff,#fff);background-size:.5rem .125rem}.lt-header-menu-trigger{display:flex;align-items:center;justify-content:center;height:100%;font-size:1.25rem;color:var(--lt-icon);cursor:pointer}.lt-header-menu-trigger:hover{color:var(--lt-icon-active)}.lt-row-menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;font-size:1.25rem;line-height:1;color:var(--lt-icon);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;transition:color .15s,background .15s}.lt-row-menu-trigger:hover{color:var(--lt-icon-active);background:var(--lt-row-hover)}.cdk-drag-preview{box-sizing:border-box;background-color:var(--lt-header-bg);color:var(--lt-header-text);box-shadow:var(--shadowLarge);padding:0 .625rem;display:flex;align-items:center;border-radius:var(--lt-radius)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.lt-row{height:var(--lt-row-height);background-color:var(--lt-row-bg);color:var(--lt-cell-text)}.lt-row td{border-bottom:1px solid var(--lt-border)}.lt-row.lt-row-odd{background-color:var(--lt-row-alt-bg)}.lt-row:hover{background-color:var(--lt-row-hover)}.lt-row.lt-row-selected{background-color:var(--lt-row-selected)}.lt-row.lt-row-selected:hover{background-color:var(--lt-row-hover)}.lt-row.row-invalid{background-color:var(--invalid)!important}.lt-row.row-copied{background-color:var(--copiedRow)!important}.lt-cell{height:var(--lt-row-height);padding:0 .625rem 0 1.25rem;text-align:left;vertical-align:middle;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-user-select:text;user-select:text}.lt-cell-text{display:inline-block;overflow:hidden;text-overflow:ellipsis;max-width:100%;vertical-align:middle}.lt-cell.lt-cell-center{text-align:center}.lt-badge-cell .lt-cell-text{padding:.1875rem .5rem;font-size:.75rem;font-weight:600;line-height:1.2;color:var(--lt-cell-text);background:color-mix(in srgb,var(--lt-border) 45%,transparent);border:1px solid var(--lt-border);border-radius:999px}.lt-badge-cell .lt-cell-text:empty{display:none}.lt-select-cell,.lt-menu-cell{width:3.75rem;padding:0;text-align:center;vertical-align:middle;overflow:visible;position:relative}.lt-tree-cell{padding-left:.625rem}.lt-tree-cell-inner{display:flex;align-items:center}.lt-tree-lead{display:inline-flex;align-items:center;flex:0 0 auto;margin-right:.375rem}.lt-tree-toggle{position:relative;display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;color:var(--lt-icon);background:transparent;border:none;border-radius:50%;cursor:pointer;transition:color .15s,background .15s}.lt-tree-toggle:hover{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-tree-chevron{display:block;width:13.8px;height:13.8px;transition:transform .15s ease}.lt-tree-toggle.is-expanded .lt-tree-chevron{transform:rotate(90deg)}.lt-tree-toggle.is-error .lt-tree-chevron{color:var(--lt-negative, #dc2626)}.lt-tree-spacer{display:inline-block;width:1.75rem;height:1.75rem}.lt-tree-spinner{position:absolute;top:50%;left:50%;box-sizing:border-box;width:1.875rem;height:1.875rem;margin:-.9375rem 0 0 -.9375rem;border:2px solid color-mix(in srgb,var(--lt-link) 25%,transparent);border-top-color:var(--lt-link);border-radius:50%;animation:loading .6s linear infinite;pointer-events:none}.lt-row-expanded>.lt-tree-cell .lt-cell-text{font-weight:600}.lt-tree-subhead-row>td,.lt-tree-child-row>td{padding:0 1rem 0 calc(var(--lt-card-inset, 4rem) + var(--lt-tree-indent, 0rem));background:transparent;border-bottom:none}.lt-tree-subhead-row,.lt-tree-child-row,.lt-tree-child-row:hover{background-color:var(--lt-tree-well, color-mix(in srgb, var(--lt-border) 22%, var(--lt-row-bg)))}.lt-tree-subhead-row>td{padding-top:1rem}.lt-tree-child-row.lt-tree-last-child>td{padding-bottom:1rem}.lt-tree-grid{display:grid;align-items:center;--lt-tree-col-gap: .5rem;column-gap:var(--lt-tree-col-gap);min-height:var(--lt-row-height);padding-left:.625rem;padding-right:.625rem;background:var(--lt-row-bg);border-left:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)));border-right:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)))}.lt-tree-child-row:hover .lt-tree-grid{background:var(--lt-row-hover)}.lt-tree-subhead-row .lt-tree-grid{min-height:2rem;background:var(--lt-header-bg);border-top:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)));border-top-left-radius:var(--lt-radius, 8px);border-top-right-radius:var(--lt-radius, 8px)}.lt-tree-child-row.lt-tree-last-child .lt-tree-grid{border-bottom:1px solid var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)));border-bottom-left-radius:var(--lt-radius, 8px);border-bottom-right-radius:var(--lt-radius, 8px);box-shadow:var(--lt-tree-card-shadow, 0 2px 4px color-mix(in srgb, #000 10%, transparent))}.lt-tree-child-row:not(.lt-tree-last-child) .lt-tree-grid{border-bottom:1px solid color-mix(in srgb,var(--lt-border) 55%,transparent)}.lt-tree-subhead-cell{position:relative;padding:0 .3125rem;font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--lt-header-text);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.lt-tree-cellbox{display:flex;align-items:center;min-width:0;padding:0 .3125rem;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.lt-tree-cellbox .lt-cell-text{overflow:hidden;text-overflow:ellipsis}.lt-tree-grid>.lt-tree-select-cell{display:flex;align-items:center;justify-content:center;padding:0;overflow:visible}:host(.lt-tree-flat) .lt-tree-subhead-row,:host(.lt-tree-flat) .lt-tree-child-row,:host(.lt-tree-flat) .lt-tree-child-row:hover{background-color:transparent}:host(.lt-tree-flat) .lt-tree-subhead-row>td,:host(.lt-tree-flat) .lt-tree-child-row>td{padding:0}:host(.lt-tree-flat) .lt-tree-grid>.lt-tree-indent-col{padding-left:calc(var(--lt-tree-first-indent, 5.875rem) - var(--lt-tree-lead-offset, 0px))}:host(.lt-tree-flat) .lt-tree-grid{background:transparent;border-left:none;border-right:none;border-radius:0}:host(.lt-tree-flat) .lt-tree-subhead-row .lt-tree-grid{background:var(--lt-header-bg);border:none;border-bottom:1px solid var(--lt-border);border-radius:0}:host(.lt-tree-flat) .lt-tree-child-row .lt-tree-grid,:host(.lt-tree-flat) .lt-tree-child-row.lt-tree-last-child .lt-tree-grid{border-left:none;border-right:none;border-radius:0;box-shadow:none;border-bottom:1px solid color-mix(in srgb,var(--lt-border) 55%,transparent)}.lt-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin:var(--lt-toolbar-margin-top, .75rem) 0 var(--lt-toolbar-margin-bottom, 0);padding:0 0 var(--lt-toolbar-pad-bottom, .625rem);min-height:var(--lt-toolbar-height);position:relative;z-index:30}.lt-toolbar-left{display:flex;align-items:center;gap:var(--lt-control-row-gap);flex:1 1 auto;min-width:0;flex-wrap:nowrap}.lt-filter-zone{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--lt-control-row-gap);flex:0 1 auto;min-width:0}.lt-filters{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--lt-control-row-gap);min-width:0;flex:0 1 auto;overflow:hidden}.lt-filter-item{display:inline-flex;flex:0 0 auto}.lt-filter-more{position:relative;flex:0 0 auto}.lt-filter-more-btn{gap:.375rem;padding:0 .625rem}.lt-filter-more-btn .lt-filter-more-icon{display:inline-flex}.lt-filter-overflow{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;min-width:15rem;max-width:min(22rem,90vw);display:flex;flex-direction:column;gap:.5rem;padding:.625rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-chooser-in .12s ease-out}.lt-filter-overflow-title{font-family:var(--lt-font);font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--groupText)}.lt-filter-overflow-row{display:flex}.lt-filter-overflow-row lib-list-filter-cell,.lt-filter-overflow-row .lt-filter-item{flex:1 1 auto;min-width:0}.lt-toolbar-right{display:flex;align-items:center;gap:var(--lt-control-row-gap);flex:0 0 auto}.lt-divider{flex:0 0 auto;width:1px;height:1.5rem;background:var(--lt-border)}.lt-search{position:relative;display:flex;align-items:center;width:18rem;max-width:40vw;flex:0 1 18rem}.lt-search-icon{position:absolute;left:.625rem;display:block;color:var(--lt-icon);pointer-events:none}.lt-search-input{width:100%;height:var(--lt-toolbar-height);padding:0 .75rem 0 1.75rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);outline:none;transition:border-color .15s,box-shadow .15s}.lt-search-input::placeholder{color:var(--placeholder)}.lt-search-input:focus{border-color:var(--lt-link);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-count{font-size:.8125rem;color:var(--groupText);white-space:nowrap}.lt-count .lt-count-of{color:var(--groupText)}.lt-pager{display:flex;align-items:center;gap:.25rem}.lt-page-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);color:var(--lt-icon);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;font-size:.9375rem;transition:border-color .15s,color .15s}.lt-page-btn:not(:disabled):hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-btn:disabled{opacity:.4;cursor:default}.lt-refresh-btn{padding:0;width:var(--lt-toolbar-height);justify-content:center}.lt-refresh-btn:disabled{opacity:.5;cursor:default;border-color:var(--lt-border);color:var(--lt-cell-text)}.lt-columns{position:relative;flex:0 0 auto}.lt-column-chooser{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;width:15rem;max-height:calc(100vh - 9rem);display:flex;flex-direction:column;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);overflow:hidden;animation:lt-chooser-in .12s ease-out}@keyframes lt-chooser-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-chooser-header{flex:0 0 auto;display:flex;flex-direction:column;padding:.625rem .75rem .5rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-title{font-size:.8125rem;font-weight:700;color:var(--lt-cell-text)}.lt-chooser-subtitle{font-size:.6875rem;color:var(--groupText)}.lt-chooser-view{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-view-label{font-size:.75rem;font-weight:600;color:var(--lt-cell-text)}.lt-chooser-view-toggle{display:flex;gap:.1875rem;padding:.1875rem;background:color-mix(in srgb,var(--lt-border) 30%,var(--lt-control-bg));border:1px solid var(--lt-border);border-radius:var(--lt-radius)}.lt-chooser-view-opt{padding:.25rem .625rem;font-family:var(--lt-font);font-size:.75rem;font-weight:500;color:var(--lt-cell-text);background:transparent;border:none;border-radius:calc(var(--lt-radius) - .0625rem);cursor:pointer}.lt-chooser-view-opt:hover:not(.active){color:var(--lt-link)}.lt-chooser-view-opt.active{color:var(--lt-link);background:var(--lt-control-bg);box-shadow:0 1px 2px color-mix(in srgb,var(--lt-cell-text) 14%,transparent)}.lt-chooser-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.lt-chooser-list{list-style:none;margin:0;padding:.375rem .5rem}.lt-chooser-nested{padding:0 .5rem .375rem;border-top:1px solid var(--lt-border)}.lt-chooser-group{display:flex;align-items:center;gap:.375rem;width:100%;min-height:2.25rem;padding:.375rem .25rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.lt-chooser-group:hover{background:var(--lt-row-hover)}.lt-chooser-group-sub{padding-left:1.25rem;font-weight:500}.lt-chooser-group-label{flex:0 0 auto}.lt-chooser-chevron{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:.875rem;color:var(--lt-icon);transition:transform .15s ease}.lt-chooser-chevron svg{display:block}.lt-chooser-chevron.open{transform:rotate(90deg)}.lt-chooser-group-hint{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.6875rem;font-weight:400;color:var(--groupText)}.lt-chooser-sublist{padding-left:1.75rem}.lt-chooser-row{display:flex;align-items:center;justify-content:space-between;min-height:2.5rem;padding:.5rem;border-radius:var(--lt-radius);background:var(--lt-control-bg);cursor:grab}.lt-chooser-row:active{cursor:grabbing}.lt-chooser-row.cdk-drag-disabled{cursor:default}.lt-chooser-row:hover{background:var(--lt-row-hover)}.lt-chooser-row:hover .lt-chooser-handle{opacity:1}.lt-chooser-toggle{display:flex;align-items:center;gap:.625rem;flex:1 1 auto;min-width:0;padding:0;background:transparent;border:none;font:inherit;font-size:.875rem;color:var(--lt-cell-text);text-align:left;cursor:pointer}.lt-chooser-toggle:disabled{cursor:default;opacity:.5}.lt-chooser-box{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:1rem;height:1rem;border:.09375rem solid var(--lt-control-border, var(--lt-border));border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg);transition:background .12s,border-color .12s}.lt-chooser-toggle.checked .lt-chooser-box{background:var(--lt-link);border-color:var(--lt-link)}.lt-chooser-toggle:not(:disabled):hover .lt-chooser-box{border-color:var(--lt-link)}.lt-chooser-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-chooser-default-tag{flex:0 0 auto;margin-left:.375rem;font-size:.6875rem;font-weight:400;font-style:italic;color:var(--groupText);white-space:nowrap}.lt-chooser-handle{flex:0 0 auto;display:inline-flex;align-items:center;color:var(--lt-icon);cursor:grab;padding-left:.5rem;-webkit-user-select:none;user-select:none}.lt-chooser-handle:active{cursor:grabbing}.lt-chooser-footer{flex:0 0 auto;padding:.5rem .75rem;border-top:1px solid var(--lt-border)}.lt-chooser-reset{width:100%;height:1.875rem;font-size:.75rem;font-weight:600;color:var(--lt-link);background:transparent;border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer}.lt-chooser-reset:disabled{color:var(--groupText);cursor:default;opacity:.6}.lt-chooser-reset:not(:disabled):hover{border-color:var(--lt-link)}.lt-footer{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.625rem .125rem 0;font-family:var(--lt-font);font-size:.8125rem;color:var(--groupText)}.lt-footer-count{white-space:nowrap}.lt-footer-count span{color:var(--lt-cell-text);font-weight:600}.lt-footer-controls{display:flex;align-items:center;gap:1.25rem;flex-wrap:nowrap}.lt-rpp{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}.lt-rpp-label{text-transform:none;font-weight:400;color:var(--groupText)}.lt-rpp-select{width:auto;min-width:4rem;height:var(--lt-toolbar-height);padding:0 1.75rem 0 .625rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background-color:var(--lt-control-bg);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .5rem center;border:var(--lt-control-border-full);border-radius:var(--lt-radius);appearance:none;-webkit-appearance:none;cursor:pointer}.lt-rpp-select:focus{outline:none;border-color:var(--lt-link)}.lt-page-num{display:inline-flex;align-items:center;justify-content:center;min-width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);padding:0 .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--lt-control-border-full);border-radius:var(--lt-radius);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.lt-page-num:hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-num.is-current{color:#fff;background:var(--lt-link);border-color:var(--lt-link);cursor:default}.lt-page-gap{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;color:var(--groupText)}.lt-tooltip{position:fixed;z-index:1100;pointer-events:none;animation:lt-tooltip-in .12s ease-out}@keyframes lt-tooltip-in{0%{opacity:0;transform:translateY(-.1875rem)}to{opacity:1;transform:translateY(0)}}\n"] }]
13103
13923
  }], ctorParameters: () => [{ type: ListTableService }, { type: DataTableFilterService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i1.DomSanitizer }], propDecorators: { tableId: [{
13104
13924
  type: Input
13105
13925
  }], columns: [{
@@ -13110,6 +13930,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13110
13930
  type: Input
13111
13931
  }], options: [{
13112
13932
  type: Input
13933
+ }], loadChildren: [{
13934
+ type: Input
13935
+ }], childColumnsByLevel: [{
13936
+ type: Input
13937
+ }], nodeToggled: [{
13938
+ type: Output
13113
13939
  }], hasNext: [{
13114
13940
  type: Input
13115
13941
  }], hasPrev: [{
@@ -13144,6 +13970,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13144
13970
  type: Output
13145
13971
  }], tableReady: [{
13146
13972
  type: Output
13973
+ }], selectionChanged: [{
13974
+ type: Output
13147
13975
  }], contextMenu: [{
13148
13976
  type: ViewChild,
13149
13977
  args: ['contextMenu']
@@ -13153,6 +13981,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13153
13981
  }], filtersRow: [{
13154
13982
  type: ViewChild,
13155
13983
  args: ['filtersRow']
13984
+ }], treeVariant: [{
13985
+ type: Input
13986
+ }], showTreeVariantToggle: [{
13987
+ type: Input
13988
+ }], treeRail: [{
13989
+ type: Input
13990
+ }], isFlatTree: [{
13991
+ type: HostBinding,
13992
+ args: ['class.lt-tree-flat']
13156
13993
  }], initialPageSize: [{
13157
13994
  type: Input
13158
13995
  }] } });
@@ -16122,7 +16959,7 @@ class IdentitiesListPageComponent extends ListPageComponent {
16122
16959
  return true;
16123
16960
  }
16124
16961
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentitiesListPageComponent, deps: [{ token: IdentitiesPageService }, { token: DataTableFilterService }, { token: i5.MatDialog }, { token: TabNameService }, { token: i4.Router }, { token: ConsoleEventsService }, { token: IDENTITY_EXTENSION_SERVICE }], target: i0.ɵɵFactoryTarget.Component }); }
16125
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: IdentitiesListPageComponent, isStandalone: false, selector: "lib-identities-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-identities-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Manage identities across the network\"\n createLabel=\"Create Identity\"\n infoText=\"Identities are the clients, services, and routers enrolled in this network.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-identity\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n<lib-side-modal [(open)]=\"svc.sideModalOpen\" [showClose]=\"false\">\n @if (svc.modalType === 'overrides' && svc.sideModalOpen) {\n <lib-overrides\n [identity]=\"svc.selectedIdentity\"\n (close)=\"closeModal($event)\"\n ></lib-overrides>\n }\n</lib-side-modal>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [".ziti-page-container .action{line-height:3.75rem;top:1.5625rem;right:1.5625rem;z-index:999}.ziti-identities-container ::ng-deep .zac-wrapper-container{height:0rem;left:-62499.9375rem;position:fixed}.ziti-identities-container ::ng-deep .zac-wrapper-container .action{display:none}.ziti-ag-grid{height:100%;position:relative;width:100%}.ziti-ag-grid .ziti-ag-grid-table{height:100%;width:100%}.ziti-ag-grid .ziti-ag-grid-table .ag-root-wrapper{border-bottom-style:none}.ziti-ag-grid .ziti-ag-grid-table .ag-cell{align-items:center;display:flex}.system-policies-toggle,.hidden-results-container{position:relative;right:0;display:flex;flex-direction:row;justify-content:center;align-items:flex-start;margin-top:.625rem;padding-left:1.25rem;border-radius:var(--inputBorderRadius);padding-bottom:0;padding-right:0;padding-top:0;flex-wrap:nowrap;flex:1 0 auto}.system-policies-toggle .toggle-text,.hidden-results-container .toggle-text{font-weight:600;font-size:.75rem}.system-policies-toggle .toggle-text a,.hidden-results-container .toggle-text a{cursor:pointer}.system-policies-toggle .toggle,.hidden-results-container .toggle{width:4.6875rem;margin-left:.625rem}.system-policies-toggle .toggle.on .switch,.hidden-results-container .toggle.on .switch{left:3.25rem}.system-policies-toggle .spinner,.hidden-results-container .spinner{display:inline-block;width:.625rem;height:.625rem;border-radius:50%;background:transparent;border-top:.125rem solid var(--tableText);border-right:.125rem solid var(--tableText);border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;animation:loading .5s infinite linear;margin-left:.3125rem}.hidden-results-container.loading-results{opacity:.7}.nf-cell-vert-align,.ag-row .ag-cell{align-items:center;display:flex}.hidden-columns-list{align-items:center;display:flex;flex-direction:row;margin-left:.4375rem;min-height:1.875rem;flex-wrap:wrap;flex:0 1 auto}.hidden-columns-list.hidden-results{flex-wrap:nowrap;overflow:auto}.hidden-columns-list.hidden-results .hidden-column-item{flex:1 0 auto}.hidden-columns-list::-webkit-scrollbar{height:.875rem}.hidden-columns-list .hidden-column-item{align-items:center;background-color:var(--background);border:.125rem solid var(--background);border-radius:.75rem;cursor:pointer;color:var(----stroke);font-weight:600;font-family:Open Sans,sans-serif;display:flex;flex:0 1 auto;height:1.5625rem!important;margin-left:.625rem;margin-top:.3125rem;padding:.125rem .625rem .125rem .3125rem;position:relative;text-align:center;box-shadow:unset;transform:translateY(0)}.hidden-columns-list .ag-dnd-ghost:hover{border:.125rem solid var(--primaryColor)!important}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-label,.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash{color:var(--ag-alpine-active-color, var(--primaryColor))}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash:before{content:\"\\f113\"}.hidden-columns-list .ag-dnd-ghost-label{line-height:initial}::ng-deep .darkmode .ag-dnd-ghost-icon .ag-icon-eye-slash{color:#fff!important}.tMenu{transition-property:opacity;z-index:99999999999;height:fit-content}.tMenu.action-menu{position:fixed;width:10.9375rem}.tMenu .tActionRow{transition-property:opacity}.tMenu .tActionRow.has-icon{display:flex;flex-direction:row;align-items:center}.tMenu .tActionRow .v7Offline{background-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline{width:.875rem;height:.875rem;border-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline:hover{border-color:var(--placeholder)}.hidden-items-container{display:flex;flex-direction:row;width:100%;flex-wrap:nowrap;justify-content:space-between}.os-icon{width:1.4375rem;height:1.4375rem;background-size:1.4375rem 1.4375rem;display:inline-flex;margin-right:.5rem;margin-top:.3125rem;margin-bottom:.3125rem}::ng-deep .date-time-container{position:fixed}::ng-deep .date-time-container .p-calendar .p-inputtext{height:.0625rem;width:.0625rem;padding:0}::ng-deep .date-time-container .filter-button-container{height:3.125rem;display:flex;flex-direction:row;justify-content:center;align-items:center}::ng-deep .date-time-container .filter-button-container .toggleButton{padding-left:.3125rem;padding-right:.3125rem;width:unset}::ng-deep .date-time-container .p-calendar{height:1.875rem}::ng-deep .date-time-container .p-calendar .p-inputtext{height:1.875rem;border-top-left-radius:1.25rem;border-bottom-left-radius:1.25rem;text-indent:.3125rem}::ng-deep .date-time-container .p-calendar .p-button-icon-only{border-top-right-radius:1.25rem;border-bottom-right-radius:1.25rem}#TimeSearch{position:relative;float:left;display:inline-block;margin-left:.625rem;width:3.125rem;height:2.5rem;background-color:var(--primary)}#TimeSearch #TimeSearchList{width:2.75rem;height:0;position:absolute;top:0;left:-.125rem;overflow:hidden;transition:var(--transition);background-color:var(--menu);border-radius:.5rem;z-index:30}#TimeSearch #TimeSearchList .time{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;font-size:.75rem;font-weight:700;text-align:center;margin-top:.125rem;margin-left:.125rem;left:-.125rem;top:-3.1875rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSearchList.open{top:-3.1875rem;width:2.75rem;height:14.125rem}#TimeSearch #TimeSelectButton{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;text-align:center;font-size:.75rem;font-weight:700;margin-top:.4375rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSelectButton:hover,#TimeSearch #TimeSearchList .time:hover{background-color:var(--primaryColor);color:var(--white)}::ng-deep p-togglebutton .p-togglebutton.p-button{border-radius:var(--inputBorderRadius)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;margin-top:-.125rem!important}::ng-deep p-togglebutton .p-togglebutton.p-button:hover{filter:brightness(99%)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;transition:all .3s ease}::ng-deep p-togglebutton .p-togglebutton.p-button:active{filter:brightness(95%)!important;box-shadow:none!important;transform:translateY(.0625rem)!important;transition:all .3s ease}#TimeSearch #TimeSelectButton{margin-top:.25rem!important}::ng-deep .p-calendar .p-inputtext{height:2.3125rem}::ng-deep .p-inputtext{border-radius:var(--inputBorderRadius)}.calendar{display:inline-block;padding-left:.3125rem;float:left;padding-right:.625rem;margin-top:-.125rem}::ng-deep .p-datepicker:not(.p-datepicker-inline){background:var(--table);border:0 none;box-shadow:#091e4226 0 1.25rem 2rem -.5rem;border-radius:.75rem}::ng-deep .p-button.p-button-text{background-color:transparent;color:var(--primaryColor);border-color:transparent}::ng-deep .p-button.p-button-text:enabled:hover{background:var(--primaryColorShadowLight);color:var(--primaryColor);border-color:transparent}::ng-deep .p-datepicker table td>span.p-highlight{color:var(--tableText);background:var(--primaryColorShadowLight)}::ng-deep .p-inputtext:enabled:hover{border-color:var(--primaryColor)}::ng-deep .p-datepicker{padding:.5rem;padding-bottom:0;background:var(--table);color:var(--tableText);border:.0625rem solid var(--inputBorder);border-radius:.1875rem}::ng-deep .p-inputtext:enabled:focus{outline:0 none;outline-offset:0;box-shadow:0 0 0 1 var(--primaryColorShadowLight);border-color:var(--primaryColor)}::ng-deep .p-inputtext{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol;font-size:1rem;color:var(--tableText);background:var(--table);padding:.5rem;border:.0625rem solid var(--inputBorder);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--inputBorderRadius)}::ng-deep .p-datepicker:not(.p-datepicker-inline) .p-datepicker-header{background:var(--table)}::ng-deep .p-datepicker .p-datepicker-header{padding:.5rem;color:var(--tableText);background:var(--table);font-weight:600;margin:0;border-bottom:.0625rem solid var(--inputBorder);border-top-right-radius:.75rem;border-top-left-radius:.75rem}::ng-deep .p-datepicker:not(.p-disabled) table td span:not(.p-highlight):not(.p-disabled):hover{background:#e9ecef;color:var(--table)}::ng-deep .p-datepicker .p-timepicker{border-top:.0625rem solid var(--background);padding:.5rem}::ng-deep .p-togglebutton.p-button{background:var(--background);border:.0625rem solid var(--inputBorder);color:var(--tableText);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s}::ng-deep .p-datepicker .p-datepicker-buttonbar{padding:1rem 0;border-top:.0625rem solid var(--inputBorder)}::ng-deep .p-button{color:#fff;background:var(--primaryColor);border:.0625rem solid var(--primaryColor);padding:.495rem 1rem;font-size:1rem;transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;border-radius:var(--inputBorderRadius)}::ng-deep .p-togglebutton.p-button.p-highlight{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}::ng-deep .p-togglebutton.p-button.p-highlight:hover{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}app-zac-wrapper{z-index:999999}\n"], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete", "tabsUseRouter", "activeTab"], outputs: ["actionClicked", "tabClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "hasNext", "hasPrev", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn", "initialPageSize"], outputs: ["actionRequested", "tableReady"] }, { kind: "component", type: SideModalComponent, selector: "lib-side-modal", inputs: ["showClose", "open"], outputs: ["openChange"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: OverridesComponent, selector: "lib-overrides", inputs: ["identity"], outputs: ["close"] }] }); }
16962
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: IdentitiesListPageComponent, isStandalone: false, selector: "lib-identities-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-identities-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Manage identities across the network\"\n createLabel=\"Create Identity\"\n infoText=\"Identities are the clients, services, and routers enrolled in this network.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-identity\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n<lib-side-modal [(open)]=\"svc.sideModalOpen\" [showClose]=\"false\">\n @if (svc.modalType === 'overrides' && svc.sideModalOpen) {\n <lib-overrides\n [identity]=\"svc.selectedIdentity\"\n (close)=\"closeModal($event)\"\n ></lib-overrides>\n }\n</lib-side-modal>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [".ziti-page-container .action{line-height:3.75rem;top:1.5625rem;right:1.5625rem;z-index:999}.ziti-identities-container ::ng-deep .zac-wrapper-container{height:0rem;left:-62499.9375rem;position:fixed}.ziti-identities-container ::ng-deep .zac-wrapper-container .action{display:none}.ziti-ag-grid{height:100%;position:relative;width:100%}.ziti-ag-grid .ziti-ag-grid-table{height:100%;width:100%}.ziti-ag-grid .ziti-ag-grid-table .ag-root-wrapper{border-bottom-style:none}.ziti-ag-grid .ziti-ag-grid-table .ag-cell{align-items:center;display:flex}.system-policies-toggle,.hidden-results-container{position:relative;right:0;display:flex;flex-direction:row;justify-content:center;align-items:flex-start;margin-top:.625rem;padding-left:1.25rem;border-radius:var(--inputBorderRadius);padding-bottom:0;padding-right:0;padding-top:0;flex-wrap:nowrap;flex:1 0 auto}.system-policies-toggle .toggle-text,.hidden-results-container .toggle-text{font-weight:600;font-size:.75rem}.system-policies-toggle .toggle-text a,.hidden-results-container .toggle-text a{cursor:pointer}.system-policies-toggle .toggle,.hidden-results-container .toggle{width:4.6875rem;margin-left:.625rem}.system-policies-toggle .toggle.on .switch,.hidden-results-container .toggle.on .switch{left:3.25rem}.system-policies-toggle .spinner,.hidden-results-container .spinner{display:inline-block;width:.625rem;height:.625rem;border-radius:50%;background:transparent;border-top:.125rem solid var(--tableText);border-right:.125rem solid var(--tableText);border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;animation:loading .5s infinite linear;margin-left:.3125rem}.hidden-results-container.loading-results{opacity:.7}.nf-cell-vert-align,.ag-row .ag-cell{align-items:center;display:flex}.hidden-columns-list{align-items:center;display:flex;flex-direction:row;margin-left:.4375rem;min-height:1.875rem;flex-wrap:wrap;flex:0 1 auto}.hidden-columns-list.hidden-results{flex-wrap:nowrap;overflow:auto}.hidden-columns-list.hidden-results .hidden-column-item{flex:1 0 auto}.hidden-columns-list::-webkit-scrollbar{height:.875rem}.hidden-columns-list .hidden-column-item{align-items:center;background-color:var(--background);border:.125rem solid var(--background);border-radius:.75rem;cursor:pointer;color:var(----stroke);font-weight:600;font-family:Open Sans,sans-serif;display:flex;flex:0 1 auto;height:1.5625rem!important;margin-left:.625rem;margin-top:.3125rem;padding:.125rem .625rem .125rem .3125rem;position:relative;text-align:center;box-shadow:unset;transform:translateY(0)}.hidden-columns-list .ag-dnd-ghost:hover{border:.125rem solid var(--primaryColor)!important}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-label,.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash{color:var(--ag-alpine-active-color, var(--primaryColor))}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash:before{content:\"\\f113\"}.hidden-columns-list .ag-dnd-ghost-label{line-height:initial}::ng-deep .darkmode .ag-dnd-ghost-icon .ag-icon-eye-slash{color:#fff!important}.tMenu{transition-property:opacity;z-index:99999999999;height:fit-content}.tMenu.action-menu{position:fixed;width:10.9375rem}.tMenu .tActionRow{transition-property:opacity}.tMenu .tActionRow.has-icon{display:flex;flex-direction:row;align-items:center}.tMenu .tActionRow .v7Offline{background-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline{width:.875rem;height:.875rem;border-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline:hover{border-color:var(--placeholder)}.hidden-items-container{display:flex;flex-direction:row;width:100%;flex-wrap:nowrap;justify-content:space-between}.os-icon{width:1.4375rem;height:1.4375rem;background-size:1.4375rem 1.4375rem;display:inline-flex;margin-right:.5rem;margin-top:.3125rem;margin-bottom:.3125rem}::ng-deep .date-time-container{position:fixed}::ng-deep .date-time-container .p-calendar .p-inputtext{height:.0625rem;width:.0625rem;padding:0}::ng-deep .date-time-container .filter-button-container{height:3.125rem;display:flex;flex-direction:row;justify-content:center;align-items:center}::ng-deep .date-time-container .filter-button-container .toggleButton{padding-left:.3125rem;padding-right:.3125rem;width:unset}::ng-deep .date-time-container .p-calendar{height:1.875rem}::ng-deep .date-time-container .p-calendar .p-inputtext{height:1.875rem;border-top-left-radius:1.25rem;border-bottom-left-radius:1.25rem;text-indent:.3125rem}::ng-deep .date-time-container .p-calendar .p-button-icon-only{border-top-right-radius:1.25rem;border-bottom-right-radius:1.25rem}#TimeSearch{position:relative;float:left;display:inline-block;margin-left:.625rem;width:3.125rem;height:2.5rem;background-color:var(--primary)}#TimeSearch #TimeSearchList{width:2.75rem;height:0;position:absolute;top:0;left:-.125rem;overflow:hidden;transition:var(--transition);background-color:var(--menu);border-radius:.5rem;z-index:30}#TimeSearch #TimeSearchList .time{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;font-size:.75rem;font-weight:700;text-align:center;margin-top:.125rem;margin-left:.125rem;left:-.125rem;top:-3.1875rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSearchList.open{top:-3.1875rem;width:2.75rem;height:14.125rem}#TimeSearch #TimeSelectButton{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;text-align:center;font-size:.75rem;font-weight:700;margin-top:.4375rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSelectButton:hover,#TimeSearch #TimeSearchList .time:hover{background-color:var(--primaryColor);color:var(--white)}::ng-deep p-togglebutton .p-togglebutton.p-button{border-radius:var(--inputBorderRadius)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;margin-top:-.125rem!important}::ng-deep p-togglebutton .p-togglebutton.p-button:hover{filter:brightness(99%)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;transition:all .3s ease}::ng-deep p-togglebutton .p-togglebutton.p-button:active{filter:brightness(95%)!important;box-shadow:none!important;transform:translateY(.0625rem)!important;transition:all .3s ease}#TimeSearch #TimeSelectButton{margin-top:.25rem!important}::ng-deep .p-calendar .p-inputtext{height:2.3125rem}::ng-deep .p-inputtext{border-radius:var(--inputBorderRadius)}.calendar{display:inline-block;padding-left:.3125rem;float:left;padding-right:.625rem;margin-top:-.125rem}::ng-deep .p-datepicker:not(.p-datepicker-inline){background:var(--table);border:0 none;box-shadow:#091e4226 0 1.25rem 2rem -.5rem;border-radius:.75rem}::ng-deep .p-button.p-button-text{background-color:transparent;color:var(--primaryColor);border-color:transparent}::ng-deep .p-button.p-button-text:enabled:hover{background:var(--primaryColorShadowLight);color:var(--primaryColor);border-color:transparent}::ng-deep .p-datepicker table td>span.p-highlight{color:var(--tableText);background:var(--primaryColorShadowLight)}::ng-deep .p-inputtext:enabled:hover{border-color:var(--primaryColor)}::ng-deep .p-datepicker{padding:.5rem;padding-bottom:0;background:var(--table);color:var(--tableText);border:.0625rem solid var(--inputBorder);border-radius:.1875rem}::ng-deep .p-inputtext:enabled:focus{outline:0 none;outline-offset:0;box-shadow:0 0 0 1 var(--primaryColorShadowLight);border-color:var(--primaryColor)}::ng-deep .p-inputtext{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol;font-size:1rem;color:var(--tableText);background:var(--table);padding:.5rem;border:.0625rem solid var(--inputBorder);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--inputBorderRadius)}::ng-deep .p-datepicker:not(.p-datepicker-inline) .p-datepicker-header{background:var(--table)}::ng-deep .p-datepicker .p-datepicker-header{padding:.5rem;color:var(--tableText);background:var(--table);font-weight:600;margin:0;border-bottom:.0625rem solid var(--inputBorder);border-top-right-radius:.75rem;border-top-left-radius:.75rem}::ng-deep .p-datepicker:not(.p-disabled) table td span:not(.p-highlight):not(.p-disabled):hover{background:#e9ecef;color:var(--table)}::ng-deep .p-datepicker .p-timepicker{border-top:.0625rem solid var(--background);padding:.5rem}::ng-deep .p-togglebutton.p-button{background:var(--background);border:.0625rem solid var(--inputBorder);color:var(--tableText);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s}::ng-deep .p-datepicker .p-datepicker-buttonbar{padding:1rem 0;border-top:.0625rem solid var(--inputBorder)}::ng-deep .p-button{color:#fff;background:var(--primaryColor);border:.0625rem solid var(--primaryColor);padding:.495rem 1rem;font-size:1rem;transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;border-radius:var(--inputBorderRadius)}::ng-deep .p-togglebutton.p-button.p-highlight{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}::ng-deep .p-togglebutton.p-button.p-highlight:hover{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}app-zac-wrapper{z-index:999999}\n"], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete", "tabsUseRouter", "activeTab"], outputs: ["actionClicked", "tabClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "loadChildren", "childColumnsByLevel", "hasNext", "hasPrev", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn", "treeVariant", "showTreeVariantToggle", "treeRail", "initialPageSize"], outputs: ["nodeToggled", "actionRequested", "tableReady", "selectionChanged"] }, { kind: "component", type: SideModalComponent, selector: "lib-side-modal", inputs: ["showClose", "open"], outputs: ["openChange"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: OverridesComponent, selector: "lib-overrides", inputs: ["identity"], outputs: ["close"] }] }); }
16126
16963
  }
16127
16964
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentitiesListPageComponent, decorators: [{
16128
16965
  type: Component,
@@ -25067,12 +25904,12 @@ class EdgeRouterPoliciesPageService extends ListPageServiceClass {
25067
25904
  createdAt: '',
25068
25905
  };
25069
25906
  this.menuItems = [
25070
- { name: 'Edit', action: 'update' },
25071
- { name: 'Delete', action: 'delete' },
25907
+ { name: 'Edit', action: 'update', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M11.2 2.6 13.4 4.8 6 12.2l-2.8.6.6-2.8z"/></svg>' },
25908
+ { name: 'Delete', action: 'delete', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M3.4 4.6h9.2M6.4 4.6V3.2h3.2v1.4M4.6 4.6 5 13h6l.4-8.4"/></svg>' },
25072
25909
  ];
25073
25910
  this.tableHeaderActions = [
25074
- { name: 'Download All', action: 'download-all' },
25075
- { name: 'Download Selected', action: 'download-selected' },
25911
+ { name: 'Download All', action: 'download-all', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
25912
+ { name: 'Download Selected', action: 'download-selected', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
25076
25913
  ];
25077
25914
  this.resourceType = 'edge-router-policies';
25078
25915
  this.validate = (formData) => {
@@ -25310,9 +26147,163 @@ class EdgeRouterPoliciesPageService extends ListPageServiceClass {
25310
26147
  this.ID_COLUMN_DEF
25311
26148
  ];
25312
26149
  }
25313
- getData(filters, sort, page) {
26150
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
26151
+ initListTableColumns() {
26152
+ this.initMenuActions();
26153
+ const routerRolesHeaderComponentParams = {
26154
+ filterType: 'ATTRIBUTE',
26155
+ enableSorting: true,
26156
+ getRoleAttributes: () => this.routerRoleAttributes,
26157
+ getNamedAttributes: () => this.routerNamedAttributes,
26158
+ getSelectedRoleAttributes: () => this.selectedRouterRoleAttributes,
26159
+ getSelectedNamedAttributes: () => this.selectedRouterNamedAttributes,
26160
+ setSelectedRoleAttributes: (attributes) => {
26161
+ this.selectedRouterRoleAttributes = attributes;
26162
+ },
26163
+ setSelectedNamedAttributes: (attributes) => {
26164
+ this.selectedRouterNamedAttributes = attributes;
26165
+ },
26166
+ getNamedAttributesMap: () => this.routerNamedAttributesMap,
26167
+ };
26168
+ const identityRolesHeaderComponentParams = {
26169
+ filterType: 'ATTRIBUTE',
26170
+ enableSorting: true,
26171
+ getRoleAttributes: () => this.identityRoleAttributes,
26172
+ getNamedAttributes: () => this.identityNamedAttributes,
26173
+ getSelectedRoleAttributes: () => this.selectedIdentityRoleAttributes,
26174
+ getSelectedNamedAttributes: () => this.selectedIdentityNamedAttributes,
26175
+ setSelectedRoleAttributes: (attributes) => {
26176
+ this.selectedIdentityRoleAttributes = attributes;
26177
+ },
26178
+ setSelectedNamedAttributes: (attributes) => {
26179
+ this.selectedIdentityNamedAttributes = attributes;
26180
+ },
26181
+ getNamedAttributesMap: () => this.identityNamedAttributesMap,
26182
+ };
26183
+ // @ marks a named attribute, # a role attribute (matches the tag selector)
26184
+ const rolesChipRenderer = (row) => {
26185
+ const colId = row?.column?.colId;
26186
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
26187
+ return attrs.map((attr) => {
26188
+ const raw = String(attr?.name ?? attr ?? '').trim();
26189
+ if (!raw) {
26190
+ return '';
26191
+ }
26192
+ const isNamed = raw.charAt(0) === '@';
26193
+ const label = isNamed || raw.charAt(0) === '#' ? raw : '#' + raw;
26194
+ return `<span class="${isNamed ? 'lt-chip lt-chip-at' : 'lt-chip'}">${label}</span>`;
26195
+ }).join('');
26196
+ };
26197
+ const semanticFilterOptions = [
26198
+ { label: 'Show All', value: '' },
26199
+ { label: 'Any Of', value: 'AnyOf' },
26200
+ { label: 'All Of', value: 'AllOf' },
26201
+ ];
26202
+ const semanticRenderer = (row) => row?.data?.semantic === 'AnyOf' ? 'Any Of' : 'All Of';
26203
+ const openForm = (ctx) => {
26204
+ if (this.hasSelectedText()) {
26205
+ return;
26206
+ }
26207
+ this.serviceType = 'advanced';
26208
+ this.openEditForm(ctx.row?.id);
26209
+ };
26210
+ let tableColumns = [
26211
+ {
26212
+ key: 'name',
26213
+ field: 'name',
26214
+ label: 'Name',
26215
+ filterType: 'TEXTINPUT',
26216
+ cell: NameCellComponent,
26217
+ cellInputs: (ctx) => ({
26218
+ row: ctx.row,
26219
+ cellValue: ctx.value,
26220
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
26221
+ }),
26222
+ onCellClick: openForm,
26223
+ resizable: true,
26224
+ cellClass: 'nf-cell-vert-align',
26225
+ sortable: true,
26226
+ onSort: this.sort.bind(this),
26227
+ sortDir: 'asc',
26228
+ width: 300,
26229
+ },
26230
+ {
26231
+ key: 'edgeRouterRoles',
26232
+ field: 'edgeRouterRoles',
26233
+ label: 'Router Attributes',
26234
+ filterType: 'ATTRIBUTE',
26235
+ headerParams: routerRolesHeaderComponentParams,
26236
+ cellHtml: rolesChipRenderer,
26237
+ onCellClick: openForm,
26238
+ resizable: true,
26239
+ cellClass: 'nf-cell-vert-align',
26240
+ sortable: false,
26241
+ },
26242
+ {
26243
+ key: 'identityRoles',
26244
+ field: 'identityRoles',
26245
+ label: 'Identity Attributes',
26246
+ filterType: 'ATTRIBUTE',
26247
+ headerParams: identityRolesHeaderComponentParams,
26248
+ cellHtml: rolesChipRenderer,
26249
+ onCellClick: openForm,
26250
+ resizable: true,
26251
+ cellClass: 'nf-cell-vert-align',
26252
+ sortable: false,
26253
+ },
26254
+ {
26255
+ key: 'semantic',
26256
+ field: 'semantic',
26257
+ label: 'Semantic',
26258
+ filterType: 'SELECT',
26259
+ filterPlaceholder: 'Any semantic',
26260
+ filterOptions: semanticFilterOptions,
26261
+ cellHtml: semanticRenderer,
26262
+ onCellClick: openForm,
26263
+ resizable: true,
26264
+ sortable: true,
26265
+ onSort: this.sort.bind(this),
26266
+ cellClass: 'nf-cell-vert-align',
26267
+ width: 120,
26268
+ },
26269
+ {
26270
+ key: 'createdAt',
26271
+ field: 'createdAt',
26272
+ label: 'Created At',
26273
+ valueFormatter: this.createdAtFormatter,
26274
+ filterType: 'DATETIME',
26275
+ filterPlaceholder: 'Any time',
26276
+ onCellClick: openForm,
26277
+ resizable: true,
26278
+ sortable: true,
26279
+ onSort: this.sort.bind(this),
26280
+ cellClass: 'nf-cell-vert-align',
26281
+ hidden: true,
26282
+ },
26283
+ {
26284
+ key: 'id',
26285
+ field: 'id',
26286
+ label: 'ID',
26287
+ filterType: 'TEXTINPUT',
26288
+ onCellClick: openForm,
26289
+ resizable: true,
26290
+ sortable: true,
26291
+ onSort: this.sort.bind(this),
26292
+ cellClass: 'nf-cell-vert-align',
26293
+ hidden: true,
26294
+ },
26295
+ ];
26296
+ if (this.extService?.processListTableColumns) {
26297
+ tableColumns = this.extService.processListTableColumns(tableColumns);
26298
+ }
26299
+ return tableColumns;
26300
+ }
26301
+ getData(filters, sort, page, pageSize) {
25314
26302
  // we can customize filters or sorting here before moving on...
25315
26303
  this.paging.page = page || this.paging.page;
26304
+ if (pageSize) {
26305
+ this.paging.total = pageSize;
26306
+ }
25316
26307
  return super.getTableData('edge-router-policies', this.paging, filters, sort)
25317
26308
  .then((results) => {
25318
26309
  return this.processData(results);
@@ -25536,6 +26527,135 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
25536
26527
  args: [SHAREDZ_EXTENSION]
25537
26528
  }] }] });
25538
26529
 
26530
+ /*
26531
+ Copyright NetFoundry Inc.
26532
+
26533
+ Licensed under the Apache License, Version 2.0 (the "License");
26534
+ you may not use this file except in compliance with the License.
26535
+ You may obtain a copy of the License at
26536
+
26537
+ https://www.apache.org/licenses/LICENSE-2.0
26538
+
26539
+ Unless required by applicable law or agreed to in writing, software
26540
+ distributed under the License is distributed on an "AS IS" BASIS,
26541
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26542
+ See the License for the specific language governing permissions and
26543
+ limitations under the License.
26544
+ */
26545
+ // Edge Router Policies page on `lib-list-table`, selected by the route when Beta Features is on.
26546
+ class EdgeRouterPoliciesListPageComponent extends ListPageComponent {
26547
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
26548
+ super(filterService, svc, consoleEvents, dialogForm, extService);
26549
+ this.svc = svc;
26550
+ this.tabNames = tabNames;
26551
+ this.extService = extService;
26552
+ this.title = 'Edge Router Policies';
26553
+ this.isLoading = false;
26554
+ this.formDataChanged = false;
26555
+ this.tableId = 'edge-router-policies';
26556
+ this.initialPageSize = 50;
26557
+ }
26558
+ loadTableColumns() {
26559
+ return this.svc.initListTableColumns();
26560
+ }
26561
+ ngOnInit() {
26562
+ this.tabs = this.tabNames.getTabs('policies');
26563
+ this.svc.refreshData = this.refreshData;
26564
+ this.getRoleAttributes();
26565
+ super.ngOnInit();
26566
+ }
26567
+ headerActionClicked(action) {
26568
+ switch (action) {
26569
+ case 'add':
26570
+ this.svc.serviceType = '';
26571
+ this.svc.openEditForm();
26572
+ break;
26573
+ case 'edit':
26574
+ this.svc.openEditForm();
26575
+ break;
26576
+ case 'delete':
26577
+ const selectedItems = this.rowData.filter((row) => {
26578
+ return row.selected;
26579
+ });
26580
+ const label = selectedItems.length > 1 ? 'edge router policies' : 'edge router policy';
26581
+ this.openBulkDelete(selectedItems, label);
26582
+ break;
26583
+ default:
26584
+ }
26585
+ }
26586
+ tableAction(event) {
26587
+ this.trackMenuAction(event?.action, event?.item);
26588
+ switch (event?.action) {
26589
+ case 'toggleAll':
26590
+ case 'toggleItem':
26591
+ this.itemToggled(event.item);
26592
+ this.rowData.forEach((item) => {
26593
+ if (item.isSystem) {
26594
+ item.selected = false;
26595
+ }
26596
+ });
26597
+ break;
26598
+ case 'update':
26599
+ this.svc.serviceType = 'advanced';
26600
+ this.svc.openEditForm(event.item?.id);
26601
+ break;
26602
+ case 'create':
26603
+ this.svc.openEditForm();
26604
+ break;
26605
+ case 'delete':
26606
+ this.deleteItem(event.item);
26607
+ break;
26608
+ case 'download-all':
26609
+ this.downloadAllItems();
26610
+ break;
26611
+ case 'download-selected':
26612
+ this.svc.downloadItems(this.selectedItems);
26613
+ break;
26614
+ default:
26615
+ break;
26616
+ }
26617
+ }
26618
+ trackMenuAction(action, item) {
26619
+ this.extService?.consoleActionTriggered?.emit({
26620
+ action,
26621
+ page: 'edge-router-policy',
26622
+ item,
26623
+ type: 'menu-action'
26624
+ });
26625
+ }
26626
+ getRoleAttributes() {
26627
+ this.svc.getEdgeRouterRoleAttributes();
26628
+ this.svc.getIdentityRoleAttributes();
26629
+ this.svc.getServiceNamedAttributes();
26630
+ this.svc.getIdentityNamedAttributes();
26631
+ this.svc.getPostureNamedAttributes();
26632
+ }
26633
+ deleteItem(item) {
26634
+ this.openBulkDelete([item], 'edge-router-policies');
26635
+ }
26636
+ closeModal(event) {
26637
+ this.svc.sideModalOpen = false;
26638
+ if (event?.refresh) {
26639
+ this.refreshData();
26640
+ }
26641
+ }
26642
+ dataChanged(event) {
26643
+ this.formDataChanged = event;
26644
+ }
26645
+ ngOnDestroy() {
26646
+ super.ngOnDestroy();
26647
+ }
26648
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EdgeRouterPoliciesListPageComponent, deps: [{ token: EdgeRouterPoliciesPageService }, { token: DataTableFilterService }, { token: i5.MatDialog }, { token: TabNameService }, { token: ConsoleEventsService }, { token: SHAREDZ_EXTENSION }], target: i0.ɵɵFactoryTarget.Component }); }
26649
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: EdgeRouterPoliciesListPageComponent, isStandalone: false, selector: "lib-edge-router-policies-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-edge-router-policies-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Control which identities can use which edge routers\"\n createLabel=\"Create Edge Router Policy\"\n infoText=\"Edge router policies grant identities access to edge routers.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-routerpolicy\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [""], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete", "tabsUseRouter", "activeTab"], outputs: ["actionClicked", "tabClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "loadChildren", "childColumnsByLevel", "hasNext", "hasPrev", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn", "treeVariant", "showTreeVariantToggle", "treeRail", "initialPageSize"], outputs: ["nodeToggled", "actionRequested", "tableReady", "selectionChanged"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
26650
+ }
26651
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EdgeRouterPoliciesListPageComponent, decorators: [{
26652
+ type: Component,
26653
+ args: [{ selector: 'lib-edge-router-policies-list', standalone: false, template: "<div class=\"ziti-page-container ziti-edge-router-policies-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Control which identities can use which edge routers\"\n createLabel=\"Create Edge Router Policy\"\n infoText=\"Edge router policies grant identities access to edge routers.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-routerpolicy\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n" }]
26654
+ }], ctorParameters: () => [{ type: EdgeRouterPoliciesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
26655
+ type: Inject,
26656
+ args: [SHAREDZ_EXTENSION]
26657
+ }] }] });
26658
+
25539
26659
  /*
25540
26660
  Copyright NetFoundry Inc.
25541
26661
 
@@ -25584,13 +26704,14 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25584
26704
  os: '',
25585
26705
  createdAt: '',
25586
26706
  };
26707
+ // `icon` is rendered by the new list-table's menus; the legacy ag-grid menu ignores it.
25587
26708
  this.menuItems = [
25588
- { name: 'Edit', action: 'update' },
25589
- { name: 'Delete', action: 'delete' },
26709
+ { name: 'Edit', action: 'update', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M11.2 2.6 13.4 4.8 6 12.2l-2.8.6.6-2.8z"/></svg>' },
26710
+ { name: 'Delete', action: 'delete', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M3.4 4.6h9.2M6.4 4.6V3.2h3.2v1.4M4.6 4.6 5 13h6l.4-8.4"/></svg>' },
25590
26711
  ];
25591
26712
  this.tableHeaderActions = [
25592
- { name: 'Download All', action: 'download-all' },
25593
- { name: 'Download Selected', action: 'download-selected' },
26713
+ { name: 'Download All', action: 'download-all', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
26714
+ { name: 'Download Selected', action: 'download-selected', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
25594
26715
  ];
25595
26716
  this.resourceType = 'service-policies';
25596
26717
  this.validate = (formData) => {
@@ -25607,6 +26728,7 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25607
26728
  identityFilter = true;
25608
26729
  break;
25609
26730
  case 'postureRoles':
26731
+ case 'postureCheckRoles':
25610
26732
  postureFilter = true;
25611
26733
  break;
25612
26734
  }
@@ -25619,6 +26741,10 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25619
26741
  this.selectedIdentityRoleAttributes = [];
25620
26742
  this.selectedIdentityNamedAttributes = [];
25621
26743
  }
26744
+ if (!postureFilter) {
26745
+ this.selectedPostureRoleAttributes = [];
26746
+ this.selectedPostureNamedAttributes = [];
26747
+ }
25622
26748
  });
25623
26749
  }
25624
26750
  initTableColumns() {
@@ -25872,9 +26998,209 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25872
26998
  }
25873
26999
  return tableColumns;
25874
27000
  }
25875
- getData(filters, sort, page) {
27001
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
27002
+ initListTableColumns() {
27003
+ this.initMenuActions();
27004
+ const serviceRolesHeaderComponentParams = {
27005
+ filterType: 'ATTRIBUTE',
27006
+ enableSorting: true,
27007
+ getRoleAttributes: () => this.serviceRoleAttributes,
27008
+ getNamedAttributes: () => this.serviceNamedAttributes,
27009
+ getSelectedRoleAttributes: () => this.selectedServiceRoleAttributes,
27010
+ getSelectedNamedAttributes: () => this.selectedServiceNamedAttributes,
27011
+ setSelectedRoleAttributes: (attributes) => {
27012
+ this.selectedServiceRoleAttributes = attributes;
27013
+ },
27014
+ setSelectedNamedAttributes: (attributes) => {
27015
+ this.selectedServiceNamedAttributes = attributes;
27016
+ },
27017
+ getNamedAttributesMap: () => this.serviceNamedAttributesMap,
27018
+ };
27019
+ const identityRolesHeaderComponentParams = {
27020
+ filterType: 'ATTRIBUTE',
27021
+ enableSorting: true,
27022
+ getRoleAttributes: () => this.identityRoleAttributes,
27023
+ getNamedAttributes: () => this.identityNamedAttributes,
27024
+ getSelectedRoleAttributes: () => this.selectedIdentityRoleAttributes,
27025
+ getSelectedNamedAttributes: () => this.selectedIdentityNamedAttributes,
27026
+ setSelectedRoleAttributes: (attributes) => {
27027
+ this.selectedIdentityRoleAttributes = attributes;
27028
+ },
27029
+ setSelectedNamedAttributes: (attributes) => {
27030
+ this.selectedIdentityNamedAttributes = attributes;
27031
+ },
27032
+ getNamedAttributesMap: () => this.identityNamedAttributesMap,
27033
+ };
27034
+ const postureRolesHeaderComponentParams = {
27035
+ filterType: 'ATTRIBUTE',
27036
+ enableSorting: true,
27037
+ getRoleAttributes: () => this.postureRoleAttributes,
27038
+ getNamedAttributes: () => this.postureNamedAttributes,
27039
+ getSelectedRoleAttributes: () => this.selectedPostureRoleAttributes,
27040
+ getSelectedNamedAttributes: () => this.selectedPostureNamedAttributes,
27041
+ setSelectedRoleAttributes: (attributes) => {
27042
+ this.selectedPostureRoleAttributes = attributes;
27043
+ },
27044
+ setSelectedNamedAttributes: (attributes) => {
27045
+ this.selectedPostureNamedAttributes = attributes;
27046
+ },
27047
+ getNamedAttributesMap: () => this.postureNamedAttributesMap,
27048
+ };
27049
+ // @ marks a named attribute, # a role attribute (matches the tag selector)
27050
+ const rolesChipRenderer = (row) => {
27051
+ const colId = row?.column?.colId;
27052
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
27053
+ return attrs.map((attr) => {
27054
+ const raw = String(attr?.name ?? attr ?? '').trim();
27055
+ if (!raw) {
27056
+ return '';
27057
+ }
27058
+ const isNamed = raw.charAt(0) === '@';
27059
+ const label = isNamed || raw.charAt(0) === '#' ? raw : '#' + raw;
27060
+ return `<span class="${isNamed ? 'lt-chip lt-chip-at' : 'lt-chip'}">${label}</span>`;
27061
+ }).join('');
27062
+ };
27063
+ const semanticFilterOptions = [
27064
+ { label: 'Show All', value: '' },
27065
+ { label: 'Any Of', value: 'AnyOf' },
27066
+ { label: 'All Of', value: 'AllOf' },
27067
+ ];
27068
+ const semanticRenderer = (row) => row?.data?.semantic === 'AnyOf' ? 'Any Of' : 'All Of';
27069
+ const openForm = (ctx) => {
27070
+ if (this.hasSelectedText()) {
27071
+ return;
27072
+ }
27073
+ this.serviceType = 'advanced';
27074
+ this.openEditForm(ctx.row?.id);
27075
+ };
27076
+ let tableColumns = [
27077
+ {
27078
+ key: 'name',
27079
+ field: 'name',
27080
+ label: 'Name',
27081
+ filterType: 'TEXTINPUT',
27082
+ cell: NameCellComponent,
27083
+ cellInputs: (ctx) => ({
27084
+ row: ctx.row,
27085
+ cellValue: ctx.value,
27086
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
27087
+ }),
27088
+ onCellClick: openForm,
27089
+ resizable: true,
27090
+ cellClass: 'nf-cell-vert-align',
27091
+ sortable: true,
27092
+ onSort: this.sort.bind(this),
27093
+ sortDir: 'asc',
27094
+ width: 300,
27095
+ },
27096
+ {
27097
+ key: 'serviceRoles',
27098
+ field: 'serviceRoles',
27099
+ label: 'Service Attributes',
27100
+ filterType: 'ATTRIBUTE',
27101
+ headerParams: serviceRolesHeaderComponentParams,
27102
+ cellHtml: rolesChipRenderer,
27103
+ onCellClick: openForm,
27104
+ resizable: true,
27105
+ cellClass: 'nf-cell-vert-align',
27106
+ sortable: false,
27107
+ },
27108
+ {
27109
+ key: 'identityRoles',
27110
+ field: 'identityRoles',
27111
+ label: 'Identity Attributes',
27112
+ filterType: 'ATTRIBUTE',
27113
+ headerParams: identityRolesHeaderComponentParams,
27114
+ cellHtml: rolesChipRenderer,
27115
+ onCellClick: openForm,
27116
+ resizable: true,
27117
+ cellClass: 'nf-cell-vert-align',
27118
+ sortable: false,
27119
+ },
27120
+ {
27121
+ key: 'postureCheckRoles',
27122
+ field: 'postureCheckRoles',
27123
+ label: 'Posture Check Attributes',
27124
+ filterType: 'ATTRIBUTE',
27125
+ headerParams: postureRolesHeaderComponentParams,
27126
+ cellHtml: rolesChipRenderer,
27127
+ onCellClick: openForm,
27128
+ resizable: true,
27129
+ cellClass: 'nf-cell-vert-align',
27130
+ sortable: false,
27131
+ },
27132
+ {
27133
+ key: 'semantic',
27134
+ field: 'semantic',
27135
+ label: 'Semantic',
27136
+ filterType: 'SELECT',
27137
+ filterPlaceholder: 'Any semantic',
27138
+ filterOptions: semanticFilterOptions,
27139
+ cellHtml: semanticRenderer,
27140
+ onCellClick: openForm,
27141
+ resizable: true,
27142
+ sortable: true,
27143
+ onSort: this.sort.bind(this),
27144
+ cellClass: 'nf-cell-vert-align',
27145
+ width: 120,
27146
+ },
27147
+ {
27148
+ key: 'type',
27149
+ field: 'type',
27150
+ label: 'Type',
27151
+ filterType: 'SELECT',
27152
+ filterPlaceholder: 'Any type',
27153
+ filterOptions: [
27154
+ { label: 'Show All', value: '' },
27155
+ { label: 'Dial', value: 'Dial' },
27156
+ { label: 'Bind', value: 'Bind' },
27157
+ ],
27158
+ cellHtml: (row) => row?.data?.type ?? '',
27159
+ onCellClick: openForm,
27160
+ resizable: true,
27161
+ sortable: true,
27162
+ onSort: this.sort.bind(this),
27163
+ cellClass: 'nf-cell-vert-align',
27164
+ width: 100,
27165
+ },
27166
+ {
27167
+ key: 'createdAt',
27168
+ field: 'createdAt',
27169
+ label: 'Created At',
27170
+ valueFormatter: this.createdAtFormatter,
27171
+ filterType: 'DATETIME',
27172
+ filterPlaceholder: 'Any time',
27173
+ onCellClick: openForm,
27174
+ resizable: true,
27175
+ sortable: true,
27176
+ onSort: this.sort.bind(this),
27177
+ cellClass: 'nf-cell-vert-align',
27178
+ hidden: true,
27179
+ },
27180
+ {
27181
+ key: 'id',
27182
+ field: 'id',
27183
+ label: 'ID',
27184
+ filterType: 'TEXTINPUT',
27185
+ onCellClick: openForm,
27186
+ resizable: true,
27187
+ sortable: true,
27188
+ onSort: this.sort.bind(this),
27189
+ cellClass: 'nf-cell-vert-align',
27190
+ hidden: true,
27191
+ },
27192
+ ];
27193
+ if (this.extService?.processListTableColumns) {
27194
+ tableColumns = this.extService.processListTableColumns(tableColumns);
27195
+ }
27196
+ return tableColumns;
27197
+ }
27198
+ getData(filters, sort, page, pageSize) {
25876
27199
  // we can customize filters or sorting here before moving on...
25877
27200
  this.paging.page = page || this.paging.page;
27201
+ if (pageSize) {
27202
+ this.paging.total = pageSize;
27203
+ }
25878
27204
  return super.getTableData('service-policies', this.paging, filters, sort)
25879
27205
  .then((results) => {
25880
27206
  return this.processData(results);
@@ -26113,6 +27439,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
26113
27439
  args: [SERVICE_POLICY_EXTENSION_SERVICE]
26114
27440
  }] }] });
26115
27441
 
27442
+ /*
27443
+ Copyright NetFoundry Inc.
27444
+
27445
+ Licensed under the Apache License, Version 2.0 (the "License");
27446
+ you may not use this file except in compliance with the License.
27447
+ You may obtain a copy of the License at
27448
+
27449
+ https://www.apache.org/licenses/LICENSE-2.0
27450
+
27451
+ Unless required by applicable law or agreed to in writing, software
27452
+ distributed under the License is distributed on an "AS IS" BASIS,
27453
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27454
+ See the License for the specific language governing permissions and
27455
+ limitations under the License.
27456
+ */
27457
+ // Service Policies page on `lib-list-table`, selected by the route when Beta Features is on.
27458
+ class ServicePoliciesListPageComponent extends ListPageComponent {
27459
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
27460
+ super(filterService, svc, consoleEvents, dialogForm, extService);
27461
+ this.svc = svc;
27462
+ this.tabNames = tabNames;
27463
+ this.extService = extService;
27464
+ this.title = 'Service Policies';
27465
+ this.isLoading = false;
27466
+ this.formDataChanged = false;
27467
+ this.tableId = 'service-policies';
27468
+ this.initialPageSize = 50;
27469
+ }
27470
+ loadTableColumns() {
27471
+ return this.svc.initListTableColumns();
27472
+ }
27473
+ ngOnInit() {
27474
+ this.tabs = this.tabNames.getTabs('policies');
27475
+ this.svc.refreshData = this.refreshData;
27476
+ this.getRoleAttributes();
27477
+ super.ngOnInit();
27478
+ }
27479
+ headerActionClicked(action) {
27480
+ switch (action) {
27481
+ case 'add':
27482
+ this.svc.serviceType = '';
27483
+ this.svc.openEditForm();
27484
+ break;
27485
+ case 'edit':
27486
+ this.svc.openEditForm();
27487
+ break;
27488
+ case 'delete':
27489
+ const selectedItems = this.rowData.filter((row) => {
27490
+ return row.selected;
27491
+ });
27492
+ const label = selectedItems.length > 1 ? 'service policies' : 'service policy';
27493
+ this.openBulkDelete(selectedItems, label);
27494
+ break;
27495
+ default:
27496
+ }
27497
+ }
27498
+ tableAction(event) {
27499
+ const extensionActionFound = this.handleExtensionActions(event);
27500
+ this.trackMenuAction(event?.action, event?.item);
27501
+ if (extensionActionFound) {
27502
+ return;
27503
+ }
27504
+ switch (event?.action) {
27505
+ case 'toggleAll':
27506
+ case 'toggleItem':
27507
+ this.itemToggled(event.item);
27508
+ break;
27509
+ case 'update':
27510
+ this.svc.serviceType = 'advanced';
27511
+ this.svc.openEditForm(event.item.id);
27512
+ break;
27513
+ case 'create':
27514
+ this.svc.openEditForm();
27515
+ break;
27516
+ case 'delete':
27517
+ this.deleteItem(event.item);
27518
+ break;
27519
+ case 'download-all':
27520
+ this.downloadAllItems();
27521
+ break;
27522
+ case 'download-selected':
27523
+ this.svc.downloadItems(this.selectedItems);
27524
+ break;
27525
+ default:
27526
+ break;
27527
+ }
27528
+ }
27529
+ trackMenuAction(action, item) {
27530
+ this.extService?.consoleActionTriggered?.emit({
27531
+ action,
27532
+ page: 'service-policy',
27533
+ item,
27534
+ type: 'menu-action'
27535
+ });
27536
+ }
27537
+ getRoleAttributes() {
27538
+ this.svc.getServiceRoleAttributes();
27539
+ this.svc.getIdentityRoleAttributes();
27540
+ this.svc.getServiceNamedAttributes();
27541
+ this.svc.getIdentityNamedAttributes();
27542
+ this.svc.getPostureNamedAttributes();
27543
+ }
27544
+ deleteItem(item) {
27545
+ this.openBulkDelete([item], 'service-policies');
27546
+ }
27547
+ closeModal(event) {
27548
+ this.svc.sideModalOpen = false;
27549
+ if (event?.refresh) {
27550
+ this.refreshData();
27551
+ }
27552
+ }
27553
+ dataChanged(event) {
27554
+ this.formDataChanged = event;
27555
+ }
27556
+ ngOnDestroy() {
27557
+ super.ngOnDestroy();
27558
+ }
27559
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ServicePoliciesListPageComponent, deps: [{ token: ServicePoliciesPageService }, { token: DataTableFilterService }, { token: i5.MatDialog }, { token: TabNameService }, { token: ConsoleEventsService }, { token: SERVICE_POLICY_EXTENSION_SERVICE }], target: i0.ɵɵFactoryTarget.Component }); }
27560
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ServicePoliciesListPageComponent, isStandalone: false, selector: "lib-service-policies-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-service-policies-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Control which identities can dial or bind which services\"\n createLabel=\"Create Service Policy\"\n infoText=\"Service policies grant identities access to dial or bind services.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-policies\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [""], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete", "tabsUseRouter", "activeTab"], outputs: ["actionClicked", "tabClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "loadChildren", "childColumnsByLevel", "hasNext", "hasPrev", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn", "treeVariant", "showTreeVariantToggle", "treeRail", "initialPageSize"], outputs: ["nodeToggled", "actionRequested", "tableReady", "selectionChanged"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
27561
+ }
27562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ServicePoliciesListPageComponent, decorators: [{
27563
+ type: Component,
27564
+ args: [{ selector: 'lib-service-policies-list', standalone: false, template: "<div class=\"ziti-page-container ziti-service-policies-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Control which identities can dial or bind which services\"\n createLabel=\"Create Service Policy\"\n infoText=\"Service policies grant identities access to dial or bind services.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-policies\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n" }]
27565
+ }], ctorParameters: () => [{ type: ServicePoliciesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
27566
+ type: Inject,
27567
+ args: [SERVICE_POLICY_EXTENSION_SERVICE]
27568
+ }] }] });
27569
+
26116
27570
  class ServicePolicyFormComponent extends ProjectableForm {
26117
27571
  constructor(settingsService, svc, zitiService, growlerService, extService, router, route, location, dialogForm) {
26118
27572
  super(growlerService, extService, zitiService, router, route, location, settingsService, dialogForm);
@@ -26985,12 +28439,12 @@ class ServiceEdgeRouterPoliciesPageService extends ListPageServiceClass {
26985
28439
  createdAt: '',
26986
28440
  };
26987
28441
  this.menuItems = [
26988
- { name: 'Edit', action: 'update' },
26989
- { name: 'Delete', action: 'delete' },
28442
+ { name: 'Edit', action: 'update', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M11.2 2.6 13.4 4.8 6 12.2l-2.8.6.6-2.8z"/></svg>' },
28443
+ { name: 'Delete', action: 'delete', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M3.4 4.6h9.2M6.4 4.6V3.2h3.2v1.4M4.6 4.6 5 13h6l.4-8.4"/></svg>' },
26990
28444
  ];
26991
28445
  this.tableHeaderActions = [
26992
- { name: 'Download All', action: 'download-all' },
26993
- { name: 'Download Selected', action: 'download-selected' },
28446
+ { name: 'Download All', action: 'download-all', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
28447
+ { name: 'Download Selected', action: 'download-selected', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
26994
28448
  ];
26995
28449
  this.resourceType = 'service-edge-router-policies';
26996
28450
  this.validate = (formData) => {
@@ -27203,9 +28657,163 @@ class ServiceEdgeRouterPoliciesPageService extends ListPageServiceClass {
27203
28657
  this.ID_COLUMN_DEF
27204
28658
  ];
27205
28659
  }
27206
- getData(filters, sort, page) {
28660
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
28661
+ initListTableColumns() {
28662
+ this.initMenuActions();
28663
+ const serviceRolesHeaderComponentParams = {
28664
+ filterType: 'ATTRIBUTE',
28665
+ enableSorting: true,
28666
+ getRoleAttributes: () => this.serviceRoleAttributes,
28667
+ getNamedAttributes: () => this.serviceNamedAttributes,
28668
+ getSelectedRoleAttributes: () => this.selectedServiceRoleAttributes,
28669
+ getSelectedNamedAttributes: () => this.selectedServiceNamedAttributes,
28670
+ setSelectedRoleAttributes: (attributes) => {
28671
+ this.selectedServiceRoleAttributes = attributes;
28672
+ },
28673
+ setSelectedNamedAttributes: (attributes) => {
28674
+ this.selectedServiceNamedAttributes = attributes;
28675
+ },
28676
+ getNamedAttributesMap: () => this.serviceNamedAttributesMap,
28677
+ };
28678
+ const routerRolesHeaderComponentParams = {
28679
+ filterType: 'ATTRIBUTE',
28680
+ enableSorting: true,
28681
+ getRoleAttributes: () => this.routerRoleAttributes,
28682
+ getNamedAttributes: () => this.routerNamedAttributes,
28683
+ getSelectedRoleAttributes: () => this.selectedRouterRoleAttributes,
28684
+ getSelectedNamedAttributes: () => this.selectedRouterNamedAttributes,
28685
+ setSelectedRoleAttributes: (attributes) => {
28686
+ this.selectedRouterRoleAttributes = attributes;
28687
+ },
28688
+ setSelectedNamedAttributes: (attributes) => {
28689
+ this.selectedRouterNamedAttributes = attributes;
28690
+ },
28691
+ getNamedAttributesMap: () => this.routerNamedAttributesMap,
28692
+ };
28693
+ // @ marks a named attribute, # a role attribute (matches the tag selector)
28694
+ const rolesChipRenderer = (row) => {
28695
+ const colId = row?.column?.colId;
28696
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
28697
+ return attrs.map((attr) => {
28698
+ const raw = String(attr?.name ?? attr ?? '').trim();
28699
+ if (!raw) {
28700
+ return '';
28701
+ }
28702
+ const isNamed = raw.charAt(0) === '@';
28703
+ const label = isNamed || raw.charAt(0) === '#' ? raw : '#' + raw;
28704
+ return `<span class="${isNamed ? 'lt-chip lt-chip-at' : 'lt-chip'}">${label}</span>`;
28705
+ }).join('');
28706
+ };
28707
+ const semanticFilterOptions = [
28708
+ { label: 'Show All', value: '' },
28709
+ { label: 'Any Of', value: 'AnyOf' },
28710
+ { label: 'All Of', value: 'AllOf' },
28711
+ ];
28712
+ const semanticRenderer = (row) => row?.data?.semantic === 'AnyOf' ? 'Any Of' : 'All Of';
28713
+ const openForm = (ctx) => {
28714
+ if (this.hasSelectedText()) {
28715
+ return;
28716
+ }
28717
+ this.serviceType = 'advanced';
28718
+ this.openEditForm(ctx.row?.id);
28719
+ };
28720
+ let tableColumns = [
28721
+ {
28722
+ key: 'name',
28723
+ field: 'name',
28724
+ label: 'Name',
28725
+ filterType: 'TEXTINPUT',
28726
+ cell: NameCellComponent,
28727
+ cellInputs: (ctx) => ({
28728
+ row: ctx.row,
28729
+ cellValue: ctx.value,
28730
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
28731
+ }),
28732
+ onCellClick: openForm,
28733
+ resizable: true,
28734
+ cellClass: 'nf-cell-vert-align',
28735
+ sortable: true,
28736
+ onSort: this.sort.bind(this),
28737
+ sortDir: 'asc',
28738
+ width: 300,
28739
+ },
28740
+ {
28741
+ key: 'serviceRoles',
28742
+ field: 'serviceRoles',
28743
+ label: 'Service Attributes',
28744
+ filterType: 'ATTRIBUTE',
28745
+ headerParams: serviceRolesHeaderComponentParams,
28746
+ cellHtml: rolesChipRenderer,
28747
+ onCellClick: openForm,
28748
+ resizable: true,
28749
+ cellClass: 'nf-cell-vert-align',
28750
+ sortable: false,
28751
+ },
28752
+ {
28753
+ key: 'edgeRouterRoles',
28754
+ field: 'edgeRouterRoles',
28755
+ label: 'Router Attributes',
28756
+ filterType: 'ATTRIBUTE',
28757
+ headerParams: routerRolesHeaderComponentParams,
28758
+ cellHtml: rolesChipRenderer,
28759
+ onCellClick: openForm,
28760
+ resizable: true,
28761
+ cellClass: 'nf-cell-vert-align',
28762
+ sortable: false,
28763
+ },
28764
+ {
28765
+ key: 'semantic',
28766
+ field: 'semantic',
28767
+ label: 'Semantic',
28768
+ filterType: 'SELECT',
28769
+ filterPlaceholder: 'Any semantic',
28770
+ filterOptions: semanticFilterOptions,
28771
+ cellHtml: semanticRenderer,
28772
+ onCellClick: openForm,
28773
+ resizable: true,
28774
+ sortable: true,
28775
+ onSort: this.sort.bind(this),
28776
+ cellClass: 'nf-cell-vert-align',
28777
+ width: 120,
28778
+ },
28779
+ {
28780
+ key: 'createdAt',
28781
+ field: 'createdAt',
28782
+ label: 'Created At',
28783
+ valueFormatter: this.createdAtFormatter,
28784
+ filterType: 'DATETIME',
28785
+ filterPlaceholder: 'Any time',
28786
+ onCellClick: openForm,
28787
+ resizable: true,
28788
+ sortable: true,
28789
+ onSort: this.sort.bind(this),
28790
+ cellClass: 'nf-cell-vert-align',
28791
+ hidden: true,
28792
+ },
28793
+ {
28794
+ key: 'id',
28795
+ field: 'id',
28796
+ label: 'ID',
28797
+ filterType: 'TEXTINPUT',
28798
+ onCellClick: openForm,
28799
+ resizable: true,
28800
+ sortable: true,
28801
+ onSort: this.sort.bind(this),
28802
+ cellClass: 'nf-cell-vert-align',
28803
+ hidden: true,
28804
+ },
28805
+ ];
28806
+ if (this.extService?.processListTableColumns) {
28807
+ tableColumns = this.extService.processListTableColumns(tableColumns);
28808
+ }
28809
+ return tableColumns;
28810
+ }
28811
+ getData(filters, sort, page, pageSize) {
27207
28812
  // we can customize filters or sorting here before moving on...
27208
28813
  this.paging.page = page || this.paging.page;
28814
+ if (pageSize) {
28815
+ this.paging.total = pageSize;
28816
+ }
27209
28817
  return super.getTableData('service-edge-router-policies', this.paging, filters, sort)
27210
28818
  .then((results) => {
27211
28819
  return this.processData(results);
@@ -27413,6 +29021,129 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
27413
29021
  args: [SHAREDZ_EXTENSION]
27414
29022
  }] }] });
27415
29023
 
29024
+ /*
29025
+ Copyright NetFoundry Inc.
29026
+
29027
+ Licensed under the Apache License, Version 2.0 (the "License");
29028
+ you may not use this file except in compliance with the License.
29029
+ You may obtain a copy of the License at
29030
+
29031
+ https://www.apache.org/licenses/LICENSE-2.0
29032
+
29033
+ Unless required by applicable law or agreed to in writing, software
29034
+ distributed under the License is distributed on an "AS IS" BASIS,
29035
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29036
+ See the License for the specific language governing permissions and
29037
+ limitations under the License.
29038
+ */
29039
+ // Service Edge Router Policies page on `lib-list-table`, selected by the route when Beta Features is on.
29040
+ class ServiceEdgeRouterPoliciesListPageComponent extends ListPageComponent {
29041
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
29042
+ super(filterService, svc, consoleEvents, dialogForm, extService);
29043
+ this.svc = svc;
29044
+ this.tabNames = tabNames;
29045
+ this.extService = extService;
29046
+ this.title = 'Service Edge Router Policies';
29047
+ this.isLoading = false;
29048
+ this.formDataChanged = false;
29049
+ this.tableId = 'service-edge-router-policies';
29050
+ this.initialPageSize = 50;
29051
+ }
29052
+ loadTableColumns() {
29053
+ return this.svc.initListTableColumns();
29054
+ }
29055
+ ngOnInit() {
29056
+ this.tabs = this.tabNames.getTabs('policies');
29057
+ this.svc.refreshData = this.refreshData;
29058
+ this.getRoleAttributes();
29059
+ super.ngOnInit();
29060
+ }
29061
+ headerActionClicked(action) {
29062
+ switch (action) {
29063
+ case 'add':
29064
+ this.svc.serviceType = '';
29065
+ this.svc.openEditForm();
29066
+ break;
29067
+ case 'edit':
29068
+ this.svc.openEditForm();
29069
+ break;
29070
+ case 'delete':
29071
+ const selectedItems = this.rowData.filter((row) => {
29072
+ return row.selected;
29073
+ });
29074
+ const label = selectedItems.length > 1 ? 'service edge router policies' : 'service edge router policy';
29075
+ this.openBulkDelete(selectedItems, label);
29076
+ break;
29077
+ default:
29078
+ }
29079
+ }
29080
+ tableAction(event) {
29081
+ this.trackMenuAction(event?.action, event?.item);
29082
+ switch (event?.action) {
29083
+ case 'toggleAll':
29084
+ case 'toggleItem':
29085
+ this.itemToggled(event.item);
29086
+ break;
29087
+ case 'update':
29088
+ this.svc.serviceType = 'advanced';
29089
+ this.svc.openEditForm(event.item?.id);
29090
+ break;
29091
+ case 'create':
29092
+ this.svc.openEditForm();
29093
+ break;
29094
+ case 'delete':
29095
+ this.deleteItem(event.item);
29096
+ break;
29097
+ case 'download-all':
29098
+ this.downloadAllItems();
29099
+ break;
29100
+ case 'download-selected':
29101
+ this.svc.downloadItems(this.selectedItems);
29102
+ break;
29103
+ default:
29104
+ break;
29105
+ }
29106
+ }
29107
+ trackMenuAction(action, item) {
29108
+ this.extService?.consoleActionTriggered?.emit({
29109
+ action,
29110
+ page: 'service-edge-router-policy',
29111
+ item,
29112
+ type: 'menu-action'
29113
+ });
29114
+ }
29115
+ getRoleAttributes() {
29116
+ this.svc.getEdgeRouterRoleAttributes();
29117
+ this.svc.getServiceRoleAttributes();
29118
+ this.svc.getServiceNamedAttributes();
29119
+ this.svc.getRouterNamedAttributes();
29120
+ }
29121
+ deleteItem(item) {
29122
+ this.openBulkDelete([item], 'service-edge-router-policies');
29123
+ }
29124
+ closeModal(event) {
29125
+ this.svc.sideModalOpen = false;
29126
+ if (event?.refresh) {
29127
+ this.refreshData();
29128
+ }
29129
+ }
29130
+ dataChanged(event) {
29131
+ this.formDataChanged = event;
29132
+ }
29133
+ ngOnDestroy() {
29134
+ super.ngOnDestroy();
29135
+ }
29136
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ServiceEdgeRouterPoliciesListPageComponent, deps: [{ token: ServiceEdgeRouterPoliciesPageService }, { token: DataTableFilterService }, { token: i5.MatDialog }, { token: TabNameService }, { token: ConsoleEventsService }, { token: SHAREDZ_EXTENSION }], target: i0.ɵɵFactoryTarget.Component }); }
29137
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ServiceEdgeRouterPoliciesListPageComponent, isStandalone: false, selector: "lib-service-edge-router-policies-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-service-edge-router-policies-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Control which edge routers can carry which services\"\n createLabel=\"Create Service Edge Router Policy\"\n infoText=\"Service edge router policies define which edge routers can carry traffic for which services.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-routerpolicy\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [""], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete", "tabsUseRouter", "activeTab"], outputs: ["actionClicked", "tabClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "loadChildren", "childColumnsByLevel", "hasNext", "hasPrev", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn", "treeVariant", "showTreeVariantToggle", "treeRail", "initialPageSize"], outputs: ["nodeToggled", "actionRequested", "tableReady", "selectionChanged"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
29138
+ }
29139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ServiceEdgeRouterPoliciesListPageComponent, decorators: [{
29140
+ type: Component,
29141
+ args: [{ selector: 'lib-service-edge-router-policies-list', standalone: false, template: "<div class=\"ziti-page-container ziti-service-edge-router-policies-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Control which edge routers can carry which services\"\n createLabel=\"Create Service Edge Router Policy\"\n infoText=\"Service edge router policies define which edge routers can carry traffic for which services.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-routerpolicy\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n" }]
29142
+ }], ctorParameters: () => [{ type: ServiceEdgeRouterPoliciesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
29143
+ type: Inject,
29144
+ args: [SHAREDZ_EXTENSION]
29145
+ }] }] });
29146
+
27416
29147
  class ServiceEdgeRouterPolicy {
27417
29148
  constructor() {
27418
29149
  this.name = '';
@@ -32331,32 +34062,20 @@ class PostureChecksPageService extends ListPageServiceClass {
32331
34062
  constructor(schemaSvc, settings, filterService, csvDownloadService, extService, router) {
32332
34063
  super(settings, filterService, csvDownloadService, extService, router);
32333
34064
  this.schemaSvc = schemaSvc;
34065
+ this.extService = extService;
32334
34066
  this.router = router;
32335
34067
  this.paging = this.DEFAULT_PAGING;
32336
34068
  this.resourceType = 'posture-checks';
32337
34069
  this.modalType = '';
32338
34070
  this.menuItems = [
32339
- { name: 'Edit', action: 'update' },
32340
- { name: 'Delete', action: 'delete' },
34071
+ { name: 'Edit', action: 'update', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M11.2 2.6 13.4 4.8 6 12.2l-2.8.6.6-2.8z"/></svg>' },
34072
+ { name: 'Delete', action: 'delete', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M3.4 4.6h9.2M6.4 4.6V3.2h3.2v1.4M4.6 4.6 5 13h6l.4-8.4"/></svg>' },
32341
34073
  ];
32342
- }
32343
- initTableColumns() {
32344
- const osTypeFilterOptions = {
32345
- filterType: 'SELECT',
32346
- enableSorting: true,
32347
- filterOptions: [
32348
- { label: 'Domain', value: 'DOMAIN' },
32349
- { label: 'MAC', value: 'MAC' },
32350
- { label: 'MFA', value: 'MFA' },
32351
- { label: 'OS', value: 'OS' },
32352
- { label: 'Process', value: 'PROCESS' },
32353
- { label: 'Process Multi', value: 'MULTI_PROCESS' },
32354
- ]
32355
- };
32356
- const createdAtHeaderComponentParams = {
32357
- filterType: 'DATETIME',
32358
- };
32359
- const detailsRenderer = (row) => {
34074
+ this.tableHeaderActions = [
34075
+ { name: 'Download All', action: 'download-all', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
34076
+ { name: 'Download Selected', action: 'download-selected', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
34077
+ ];
34078
+ this.detailsRenderer = (row) => {
32360
34079
  const data = row?.data || '';
32361
34080
  let details = '';
32362
34081
  if (data.typeId == "DOMAIN") {
@@ -32387,6 +34106,23 @@ class PostureChecksPageService extends ListPageServiceClass {
32387
34106
  }
32388
34107
  return details;
32389
34108
  };
34109
+ }
34110
+ initTableColumns() {
34111
+ const osTypeFilterOptions = {
34112
+ filterType: 'SELECT',
34113
+ enableSorting: true,
34114
+ filterOptions: [
34115
+ { label: 'Domain', value: 'DOMAIN' },
34116
+ { label: 'MAC', value: 'MAC' },
34117
+ { label: 'MFA', value: 'MFA' },
34118
+ { label: 'OS', value: 'OS' },
34119
+ { label: 'Process', value: 'PROCESS' },
34120
+ { label: 'Process Multi', value: 'MULTI_PROCESS' },
34121
+ ]
34122
+ };
34123
+ const createdAtHeaderComponentParams = {
34124
+ filterType: 'DATETIME',
34125
+ };
32390
34126
  return [
32391
34127
  {
32392
34128
  colId: 'name',
@@ -32432,7 +34168,7 @@ class PostureChecksPageService extends ListPageServiceClass {
32432
34168
  field: 'details',
32433
34169
  headerName: 'Details',
32434
34170
  headerComponent: TableColumnDefaultComponent,
32435
- cellRenderer: detailsRenderer,
34171
+ cellRenderer: this.detailsRenderer,
32436
34172
  cellRendererParams: { pathRoot: this.basePath },
32437
34173
  onCellClicked: (data) => {
32438
34174
  if (this.hasSelectedText()) {
@@ -32460,8 +34196,102 @@ class PostureChecksPageService extends ListPageServiceClass {
32460
34196
  this.ID_COLUMN_DEF
32461
34197
  ];
32462
34198
  }
32463
- getData(filters, sort) {
34199
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
34200
+ initListTableColumns() {
34201
+ this.initMenuActions();
34202
+ const escapeHtml = (v) => String(v ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
34203
+ const openForm = (ctx) => {
34204
+ if (this.hasSelectedText()) {
34205
+ return;
34206
+ }
34207
+ this.openEditForm(ctx.row?.id);
34208
+ };
34209
+ let tableColumns = [
34210
+ {
34211
+ key: 'name',
34212
+ field: 'name',
34213
+ label: 'Name',
34214
+ filterType: 'TEXTINPUT',
34215
+ cell: NameCellComponent,
34216
+ cellInputs: (ctx) => ({
34217
+ row: ctx.row,
34218
+ cellValue: ctx.value,
34219
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
34220
+ }),
34221
+ onCellClick: openForm,
34222
+ resizable: true,
34223
+ cellClass: 'nf-cell-vert-align',
34224
+ sortable: true,
34225
+ onSort: this.sort.bind(this),
34226
+ width: 300,
34227
+ },
34228
+ {
34229
+ key: 'typeId',
34230
+ field: 'typeId',
34231
+ label: 'Type',
34232
+ filterType: 'SELECT',
34233
+ filterPlaceholder: 'Any type',
34234
+ filterOptions: [
34235
+ { label: 'Show All', value: '' },
34236
+ { label: 'Domain', value: 'DOMAIN' },
34237
+ { label: 'MAC', value: 'MAC' },
34238
+ { label: 'MFA', value: 'MFA' },
34239
+ { label: 'OS', value: 'OS' },
34240
+ { label: 'Process', value: 'PROCESS' },
34241
+ { label: 'Process Multi', value: 'MULTI_PROCESS' },
34242
+ ],
34243
+ onCellClick: openForm,
34244
+ resizable: true,
34245
+ cellClass: 'nf-cell-vert-align',
34246
+ width: 140,
34247
+ },
34248
+ {
34249
+ key: 'details',
34250
+ field: 'details',
34251
+ label: 'Details',
34252
+ cellHtml: (row) => escapeHtml(this.detailsRenderer(row)),
34253
+ onCellClick: openForm,
34254
+ resizable: true,
34255
+ cellClass: 'nf-cell-vert-align',
34256
+ },
34257
+ {
34258
+ key: 'createdAt',
34259
+ field: 'createdAt',
34260
+ label: 'Created At',
34261
+ valueFormatter: this.createdAtFormatter,
34262
+ filterType: 'DATETIME',
34263
+ filterPlaceholder: 'Any time',
34264
+ onCellClick: openForm,
34265
+ resizable: true,
34266
+ sortable: true,
34267
+ onSort: this.sort.bind(this),
34268
+ cellClass: 'nf-cell-vert-align',
34269
+ width: this.remToPx(10),
34270
+ },
34271
+ {
34272
+ key: 'id',
34273
+ field: 'id',
34274
+ label: 'ID',
34275
+ filterType: 'TEXTINPUT',
34276
+ onCellClick: openForm,
34277
+ resizable: true,
34278
+ sortable: true,
34279
+ onSort: this.sort.bind(this),
34280
+ cellClass: 'nf-cell-vert-align',
34281
+ hidden: true,
34282
+ },
34283
+ ];
34284
+ if (this.extService?.processListTableColumns) {
34285
+ tableColumns = this.extService.processListTableColumns(tableColumns);
34286
+ }
34287
+ return tableColumns;
34288
+ }
34289
+ getData(filters, sort, page, pageSize) {
32464
34290
  // we can customize filters or sorting here before moving on...
34291
+ this.paging.page = page || this.paging.page;
34292
+ if (pageSize) {
34293
+ this.paging.total = pageSize;
34294
+ }
32465
34295
  return super.getTableData('posture-checks', this.paging, filters, sort)
32466
34296
  .then((results) => {
32467
34297
  return this.processData(results);
@@ -32603,6 +34433,120 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
32603
34433
  args: [SHAREDZ_EXTENSION]
32604
34434
  }] }] });
32605
34435
 
34436
+ /*
34437
+ Copyright NetFoundry Inc.
34438
+
34439
+ Licensed under the Apache License, Version 2.0 (the "License");
34440
+ you may not use this file except in compliance with the License.
34441
+ You may obtain a copy of the License at
34442
+
34443
+ https://www.apache.org/licenses/LICENSE-2.0
34444
+
34445
+ Unless required by applicable law or agreed to in writing, software
34446
+ distributed under the License is distributed on an "AS IS" BASIS,
34447
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34448
+ See the License for the specific language governing permissions and
34449
+ limitations under the License.
34450
+ */
34451
+ // Posture Checks page on `lib-list-table`, selected by the route when Beta Features is on.
34452
+ class PostureChecksListPageComponent extends ListPageComponent {
34453
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
34454
+ super(filterService, svc, consoleEvents, dialogForm, extService);
34455
+ this.svc = svc;
34456
+ this.tabNames = tabNames;
34457
+ this.extService = extService;
34458
+ this.title = 'Posture Checks';
34459
+ this.isLoading = false;
34460
+ this.formDataChanged = false;
34461
+ this.tableId = 'posture-checks';
34462
+ this.initialPageSize = 50;
34463
+ }
34464
+ loadTableColumns() {
34465
+ return this.svc.initListTableColumns();
34466
+ }
34467
+ ngOnInit() {
34468
+ this.tabs = this.tabNames.getTabs('policies');
34469
+ this.svc.refreshData = this.refreshData;
34470
+ super.ngOnInit();
34471
+ }
34472
+ headerActionClicked(action) {
34473
+ switch (action) {
34474
+ case 'add':
34475
+ this.svc.openEditForm();
34476
+ break;
34477
+ case 'edit':
34478
+ this.svc.openEditForm();
34479
+ break;
34480
+ case 'delete':
34481
+ const selectedItems = this.rowData.filter((row) => {
34482
+ return row.selected;
34483
+ });
34484
+ const label = selectedItems.length > 1 ? 'posture checks' : 'posture check';
34485
+ this.openBulkDelete(selectedItems, label);
34486
+ break;
34487
+ default:
34488
+ }
34489
+ }
34490
+ tableAction(event) {
34491
+ this.trackMenuAction(event?.action, event?.item);
34492
+ switch (event?.action) {
34493
+ case 'toggleAll':
34494
+ case 'toggleItem':
34495
+ this.itemToggled(event.item);
34496
+ break;
34497
+ case 'update':
34498
+ this.svc.openEditForm(event.item?.id);
34499
+ break;
34500
+ case 'create':
34501
+ this.svc.openEditForm();
34502
+ break;
34503
+ case 'delete':
34504
+ this.deleteItem(event.item);
34505
+ break;
34506
+ case 'download-all':
34507
+ this.downloadAllItems();
34508
+ break;
34509
+ case 'download-selected':
34510
+ this.svc.downloadItems(this.selectedItems);
34511
+ break;
34512
+ default:
34513
+ break;
34514
+ }
34515
+ }
34516
+ trackMenuAction(action, item) {
34517
+ this.extService?.consoleActionTriggered?.emit({
34518
+ action,
34519
+ page: 'posture-check',
34520
+ item,
34521
+ type: 'menu-action'
34522
+ });
34523
+ }
34524
+ deleteItem(item) {
34525
+ this.openBulkDelete([item], 'posture-checks');
34526
+ }
34527
+ closeModal(event) {
34528
+ this.svc.sideModalOpen = false;
34529
+ if (event?.refresh) {
34530
+ this.refreshData();
34531
+ }
34532
+ }
34533
+ dataChanged(event) {
34534
+ this.formDataChanged = event;
34535
+ }
34536
+ ngOnDestroy() {
34537
+ super.ngOnDestroy();
34538
+ }
34539
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PostureChecksListPageComponent, deps: [{ token: PostureChecksPageService }, { token: DataTableFilterService }, { token: i5.MatDialog }, { token: TabNameService }, { token: ConsoleEventsService }, { token: SHAREDZ_EXTENSION }], target: i0.ɵɵFactoryTarget.Component }); }
34540
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PostureChecksListPageComponent, isStandalone: false, selector: "lib-posture-checks-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-posture-checks-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Verify the state of identities before granting access\"\n createLabel=\"Create Posture Check\"\n infoText=\"Posture checks verify attributes of an identity, such as its OS or MFA status, before access is granted.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-posturechecks\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [""], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete", "tabsUseRouter", "activeTab"], outputs: ["actionClicked", "tabClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "loadChildren", "childColumnsByLevel", "hasNext", "hasPrev", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn", "treeVariant", "showTreeVariantToggle", "treeRail", "initialPageSize"], outputs: ["nodeToggled", "actionRequested", "tableReady", "selectionChanged"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
34541
+ }
34542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PostureChecksListPageComponent, decorators: [{
34543
+ type: Component,
34544
+ args: [{ selector: 'lib-posture-checks-list', standalone: false, template: "<div class=\"ziti-page-container ziti-posture-checks-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Verify the state of identities before granting access\"\n createLabel=\"Create Posture Check\"\n infoText=\"Posture checks verify attributes of an identity, such as its OS or MFA status, before access is granted.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-posturechecks\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"tableId\" [initialPageSize]=\"initialPageSize\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n" }]
34545
+ }], ctorParameters: () => [{ type: PostureChecksPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
34546
+ type: Inject,
34547
+ args: [SHAREDZ_EXTENSION]
34548
+ }] }] });
34549
+
32606
34550
  class PostureCheck {
32607
34551
  constructor() {
32608
34552
  this.name = '';
@@ -44280,6 +46224,7 @@ class OpenZitiConsoleLibModule {
44280
46224
  SdkInfoTooltipComponent,
44281
46225
  BooleanCellComponent,
44282
46226
  TokenCellComponent,
46227
+ AttributeCellComponent,
44283
46228
  TableCellSelectComponent,
44284
46229
  TableColumnSelectComponent,
44285
46230
  TableCellMenuComponent,
@@ -44319,12 +46264,15 @@ class OpenZitiConsoleLibModule {
44319
46264
  CreationSummaryDialogComponent,
44320
46265
  ConfigEditorComponent,
44321
46266
  ServicePoliciesPageComponent,
46267
+ ServicePoliciesListPageComponent,
44322
46268
  ServicePolicyFormComponent,
44323
46269
  EdgeRouterPoliciesPageComponent,
46270
+ EdgeRouterPoliciesListPageComponent,
44324
46271
  EdgeRouterPolicyFormComponent,
44325
46272
  MultiActionButtonComponent,
44326
46273
  TableCellNameComponent,
44327
46274
  ServiceEdgeRouterPoliciesPageComponent,
46275
+ ServiceEdgeRouterPoliciesListPageComponent,
44328
46276
  ServiceEdgeRouterPolicyFormComponent,
44329
46277
  TerminatorsPageComponent,
44330
46278
  ConfigurationFormComponent,
@@ -44338,6 +46286,7 @@ class OpenZitiConsoleLibModule {
44338
46286
  VerifyCertificateComponent,
44339
46287
  LoginDialogComponent,
44340
46288
  PostureChecksPageComponent,
46289
+ PostureChecksListPageComponent,
44341
46290
  PostureCheckFormComponent,
44342
46291
  SessionsPageComponent,
44343
46292
  SessionFormComponent,
@@ -44425,8 +46374,10 @@ class OpenZitiConsoleLibModule {
44425
46374
  FormFieldToggleComponent,
44426
46375
  ServiceFormComponent,
44427
46376
  ServicePoliciesPageComponent,
46377
+ ServicePoliciesListPageComponent,
44428
46378
  ServicePolicyFormComponent,
44429
46379
  EdgeRouterPoliciesPageComponent,
46380
+ EdgeRouterPoliciesListPageComponent,
44430
46381
  TerminatorsPageComponent,
44431
46382
  TerminatorFormComponent,
44432
46383
  JwtSignersPageComponent,
@@ -44435,6 +46386,7 @@ class OpenZitiConsoleLibModule {
44435
46386
  AuthPolicyFormComponent,
44436
46387
  LoginDialogComponent,
44437
46388
  PostureChecksPageComponent,
46389
+ PostureChecksListPageComponent,
44438
46390
  PostureCheckFormComponent,
44439
46391
  SessionsPageComponent,
44440
46392
  SessionFormComponent,
@@ -44536,6 +46488,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44536
46488
  SdkInfoTooltipComponent,
44537
46489
  BooleanCellComponent,
44538
46490
  TokenCellComponent,
46491
+ AttributeCellComponent,
44539
46492
  TableCellSelectComponent,
44540
46493
  TableColumnSelectComponent,
44541
46494
  TableCellMenuComponent,
@@ -44575,12 +46528,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44575
46528
  CreationSummaryDialogComponent,
44576
46529
  ConfigEditorComponent,
44577
46530
  ServicePoliciesPageComponent,
46531
+ ServicePoliciesListPageComponent,
44578
46532
  ServicePolicyFormComponent,
44579
46533
  EdgeRouterPoliciesPageComponent,
46534
+ EdgeRouterPoliciesListPageComponent,
44580
46535
  EdgeRouterPolicyFormComponent,
44581
46536
  MultiActionButtonComponent,
44582
46537
  TableCellNameComponent,
44583
46538
  ServiceEdgeRouterPoliciesPageComponent,
46539
+ ServiceEdgeRouterPoliciesListPageComponent,
44584
46540
  ServiceEdgeRouterPolicyFormComponent,
44585
46541
  TerminatorsPageComponent,
44586
46542
  ConfigurationFormComponent,
@@ -44594,6 +46550,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44594
46550
  VerifyCertificateComponent,
44595
46551
  LoginDialogComponent,
44596
46552
  PostureChecksPageComponent,
46553
+ PostureChecksListPageComponent,
44597
46554
  PostureCheckFormComponent,
44598
46555
  SessionsPageComponent,
44599
46556
  SessionFormComponent,
@@ -44662,8 +46619,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44662
46619
  FormFieldToggleComponent,
44663
46620
  ServiceFormComponent,
44664
46621
  ServicePoliciesPageComponent,
46622
+ ServicePoliciesListPageComponent,
44665
46623
  ServicePolicyFormComponent,
44666
46624
  EdgeRouterPoliciesPageComponent,
46625
+ EdgeRouterPoliciesListPageComponent,
44667
46626
  TerminatorsPageComponent,
44668
46627
  TerminatorFormComponent,
44669
46628
  JwtSignersPageComponent,
@@ -44672,6 +46631,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44672
46631
  AuthPolicyFormComponent,
44673
46632
  LoginDialogComponent,
44674
46633
  PostureChecksPageComponent,
46634
+ PostureChecksListPageComponent,
44675
46635
  PostureCheckFormComponent,
44676
46636
  SessionsPageComponent,
44677
46637
  SessionFormComponent,
@@ -46197,7 +48157,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
46197
48157
  */
46198
48158
 
46199
48159
  const ZAC_VERSION = {
46200
- "version": "4.6.0"
48160
+ "version": "4.7.0"
46201
48161
  };
46202
48162
 
46203
48163
  /*
@@ -46208,5 +48168,5 @@ const ZAC_VERSION = {
46208
48168
  * Generated bundle index. Do not edit.
46209
48169
  */
46210
48170
 
46211
- export { APISessionFormComponent, APISessionsPageComponent, API_RESOURCE_TO_ENTITY_PERMISSION, ApiSessionsPageService, AttributesComponent, AuthPoliciesPageComponent, AuthPoliciesPageService, AuthPolicyFormComponent, AuthPolicyFormService, AuthService, BooleanCellComponent, BooleanToggleInputComponent, CallbackComponent, CardListComponent, CertificateAuthoritiesPageComponent, CertificateAuthoritiesPageService, CertificateAuthorityFormComponent, CertificateAuthorityFormService, CheckboxListInputComponent, ChipsInputComponent, ConfigBuilderComponent, ConfigBuilderModule, ConfigEditorComponent, ConfigTypeFormComponent, ConfigTypesPageComponent, ConfigurationFormComponent, ConfigurationService, ConfigurationsPageComponent, CsvDownloadService, DEACTIVATE_GUARD, DEFAULT_APP_CONFIG, DataTableFilterService, DeactivateGuardService, EDGE_ROUTER_EXTENSION_SERVICE, EDGE_ROUTER_POLICY_EXTENSION_SERVICE, EdgeRouter, EdgeRouterFormComponent, EdgeRouterFormService, EdgeRouterPoliciesPageComponent, EdgeRouterPoliciesPageService, EdgeRouterPolicyFormComponent, EdgeRouterPolicyFormService, EdgeRoutersPageComponent, EdgeRoutersPageService, Entity, ExtendableComponent, ExtensionsNoopService, FilterSelectComponent, FilterSelectOptionTemplateDirective, FilterSelectTriggerTemplateDirective, FormFieldContainerComponent, FormFieldToggleComponent, FormHeaderComponent, GeolocateComponent, GrowlerComponent, GrowlerModel, GrowlerModule, GrowlerService, HAControllerService, HaStatusIndicatorComponent, HeaderBarComponent, IDENTITY_EXTENSION_SERVICE, IdentitiesListPageComponent, IdentitiesPageComponent, IdentitiesPageService, Identity$1 as Identity, IdentityFormComponent, IdentityFormService, IdentityServicePathComponent, InsecureContextError, JsonViewComponent, JwtSignerFormComponent, JwtSignerFormService, JwtSignersPageComponent, JwtSignersPageService, KEY_CODES, ListFilterCellComponent, ListHeaderCellComponent, ListMenuComponent, ListPageComponent, ListPageServiceClass, ListTableAutoRefreshComponent, ListTableComponent, ListTableHeaderComponent, ListTableService, LoadingIndicatorComponent, LoggerService, LoginDialogComponent, LoginServiceClass, ManagementPermissionsService, NameCellComponent, NetworkVisualizerComponent, NodeDataService, NoopLoginService, NoopTabInterceptorService, NumberInputComponent, OIDC_CLIENT_ID, OIDC_CLIENT_ID_LEGACY, ObjectComponent, OidcEnvironmentalError, OidcSessionUnrecoverableError, OpenZitiConsoleLibModule, OsInfoTooltipComponent, PasswordInputComponent, PostureCheckFormComponent, PostureChecksPageComponent, PostureChecksPageService, PreviewListComponent, ProfileComponent, ProgressMeterComponent, ProgressMeterStep, ProgressWizardComponent, ProgressWizardStep, ProjectableForm, ProtocolAddressPortInputComponent, QuickAddComponent, ResetEnrollmentService, SAVE_DENIED_BY_PERMISSION_TOOLTIP, SERVICE_EDGE_ROUTER_POLICY_EXTENSION_SERVICE, SERVICE_EXTENSION_SERVICE, SERVICE_POLICY_EXTENSION_SERVICE, SETTINGS_SERVICE, SHAREDZ_EXTENSION, SdkInfoTooltipComponent, SelectCellComponent, SelectorInputComponent, ServiceEdgeRouterPoliciesPageComponent, ServiceEdgeRouterPoliciesPageService, ServiceEdgeRouterPolicyFormComponent, ServiceEdgeRouterPolicyFormService, ServiceFormComponent, ServiceFormService, ServicePoliciesPageComponent, ServicePoliciesPageService, ServicePolicyFormComponent, ServicePolicyFormService, ServicesPageComponent, ServicesPageService, Session, SessionFormComponent, SessionRefreshService, SessionsPageComponent, SessionsPageService, SettingsComponent, SettingsService, SettingsServiceClass, SideModalComponent, SideNavbarComponent, SideNavigatorComponent, SimpleServiceComponent, StringInputComponent, TabNameService, TagSelectorComponent, TerminatorFormComponent, TerminatorFormService, TerminatorsPageComponent, TerminatorsPageService, TextListInputComponent, TokenCellComponent, TransitRouterFormComponent, TransitRouterFormService, TransitRoutersPageComponent, TransitRoutersPageService, ValidationService, VerifyCertificateComponent, VisualizerServiceClass, ZAC_LOGIN_SERVICE, ZAC_VERSION, ZITI_DATA_SERVICE, ZITI_DOMAIN_CONTROLLER, ZITI_NAVIGATOR, ZITI_TAB_OVERRIDES, ZITI_URLS, ZacRoutingModule, ZitiControllerDataService, ZitiDataService, ZitiOidcService, decodeJwtPayload, jwtExpMs, jwtIatMs, pkceChallengeS256, playerFactory, randomUrlSafeString };
48171
+ export { APISessionFormComponent, APISessionsPageComponent, API_RESOURCE_TO_ENTITY_PERMISSION, ApiSessionsPageService, AttributesComponent, AuthPoliciesPageComponent, AuthPoliciesPageService, AuthPolicyFormComponent, AuthPolicyFormService, AuthService, BooleanCellComponent, BooleanToggleInputComponent, CallbackComponent, CardListComponent, CertificateAuthoritiesPageComponent, CertificateAuthoritiesPageService, CertificateAuthorityFormComponent, CertificateAuthorityFormService, CheckboxListInputComponent, ChipsInputComponent, ConfigBuilderComponent, ConfigBuilderModule, ConfigEditorComponent, ConfigTypeFormComponent, ConfigTypesPageComponent, ConfigurationFormComponent, ConfigurationService, ConfigurationsPageComponent, CsvDownloadService, DEACTIVATE_GUARD, DEFAULT_APP_CONFIG, DataTableFilterService, DeactivateGuardService, EDGE_ROUTER_EXTENSION_SERVICE, EDGE_ROUTER_POLICY_EXTENSION_SERVICE, EdgeRouter, EdgeRouterFormComponent, EdgeRouterFormService, EdgeRouterPoliciesListPageComponent, EdgeRouterPoliciesPageComponent, EdgeRouterPoliciesPageService, EdgeRouterPolicyFormComponent, EdgeRouterPolicyFormService, EdgeRoutersPageComponent, EdgeRoutersPageService, Entity, ExtendableComponent, ExtensionsNoopService, FilterSelectComponent, FilterSelectOptionTemplateDirective, FilterSelectTriggerTemplateDirective, FormFieldContainerComponent, FormFieldToggleComponent, FormHeaderComponent, GeolocateComponent, GrowlerComponent, GrowlerModel, GrowlerModule, GrowlerService, HAControllerService, HaStatusIndicatorComponent, HeaderBarComponent, IDENTITY_EXTENSION_SERVICE, IdentitiesListPageComponent, IdentitiesPageComponent, IdentitiesPageService, Identity$1 as Identity, IdentityFormComponent, IdentityFormService, IdentityServicePathComponent, InsecureContextError, JsonViewComponent, JwtSignerFormComponent, JwtSignerFormService, JwtSignersPageComponent, JwtSignersPageService, KEY_CODES, ListFilterCellComponent, ListHeaderCellComponent, ListMenuComponent, ListPageComponent, ListPageServiceClass, ListTableAutoRefreshComponent, ListTableComponent, ListTableHeaderComponent, ListTableService, LoadingIndicatorComponent, LoggerService, LoginDialogComponent, LoginServiceClass, ManagementPermissionsService, NameCellComponent, NetworkVisualizerComponent, NodeDataService, NoopLoginService, NoopTabInterceptorService, NumberInputComponent, OIDC_CLIENT_ID, OIDC_CLIENT_ID_LEGACY, ObjectComponent, OidcEnvironmentalError, OidcSessionUnrecoverableError, OpenZitiConsoleLibModule, OsInfoTooltipComponent, PasswordInputComponent, PostureCheckFormComponent, PostureChecksListPageComponent, PostureChecksPageComponent, PostureChecksPageService, PreviewListComponent, ProfileComponent, ProgressMeterComponent, ProgressMeterStep, ProgressWizardComponent, ProgressWizardStep, ProjectableForm, ProtocolAddressPortInputComponent, QuickAddComponent, ResetEnrollmentService, SAVE_DENIED_BY_PERMISSION_TOOLTIP, SERVICE_EDGE_ROUTER_POLICY_EXTENSION_SERVICE, SERVICE_EXTENSION_SERVICE, SERVICE_POLICY_EXTENSION_SERVICE, SETTINGS_SERVICE, SHAREDZ_EXTENSION, SdkInfoTooltipComponent, SelectCellComponent, SelectorInputComponent, ServiceEdgeRouterPoliciesListPageComponent, ServiceEdgeRouterPoliciesPageComponent, ServiceEdgeRouterPoliciesPageService, ServiceEdgeRouterPolicyFormComponent, ServiceEdgeRouterPolicyFormService, ServiceFormComponent, ServiceFormService, ServicePoliciesListPageComponent, ServicePoliciesPageComponent, ServicePoliciesPageService, ServicePolicyFormComponent, ServicePolicyFormService, ServicesPageComponent, ServicesPageService, Session, SessionFormComponent, SessionRefreshService, SessionsPageComponent, SessionsPageService, SettingsComponent, SettingsService, SettingsServiceClass, SideModalComponent, SideNavbarComponent, SideNavigatorComponent, SimpleServiceComponent, StringInputComponent, TabNameService, TagSelectorComponent, TerminatorFormComponent, TerminatorFormService, TerminatorsPageComponent, TerminatorsPageService, TextListInputComponent, TokenCellComponent, TransitRouterFormComponent, TransitRouterFormService, TransitRoutersPageComponent, TransitRoutersPageService, ValidationService, VerifyCertificateComponent, VisualizerServiceClass, ZAC_LOGIN_SERVICE, ZAC_VERSION, ZITI_DATA_SERVICE, ZITI_DOMAIN_CONTROLLER, ZITI_NAVIGATOR, ZITI_TAB_OVERRIDES, ZITI_URLS, ZacRoutingModule, ZitiControllerDataService, ZitiDataService, ZitiOidcService, decodeJwtPayload, jwtExpMs, jwtIatMs, pkceChallengeS256, playerFactory, randomUrlSafeString };
46212
48172
  //# sourceMappingURL=openziti-ziti-console-lib.mjs.map