@openziti/ziti-console-lib 1.6.0 → 1.7.1

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)));
1909
+ }
1910
+ catch {
1911
+ // ignore
1912
+ }
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));
1888
1932
  }
1889
1933
  catch {
1890
1934
  // ignore
1891
1935
  }
1892
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,
@@ -7419,6 +7570,23 @@ class IdentitiesPageService extends ListPageServiceClass {
7419
7570
  cellClass: 'nf-cell-vert-align',
7420
7571
  width: 100,
7421
7572
  },
7573
+ {
7574
+ key: 'externalId',
7575
+ field: 'externalId',
7576
+ label: 'External Id',
7577
+ filterType: 'TEXTINPUT',
7578
+ cellClass: 'nf-cell-vert-align',
7579
+ resizable: true,
7580
+ sortable: true,
7581
+ onSort: this.sort.bind(this),
7582
+ onCellClick: (ctx) => {
7583
+ if (this.hasSelectedText()) {
7584
+ return;
7585
+ }
7586
+ this.openEditForm(ctx.row?.id);
7587
+ },
7588
+ hidden: true,
7589
+ },
7422
7590
  {
7423
7591
  key: 'id',
7424
7592
  field: 'id',
@@ -10169,6 +10337,11 @@ class ListTableHeaderComponent {
10169
10337
  this.actionClicked = new EventEmitter();
10170
10338
  this.tabClicked = new EventEmitter();
10171
10339
  }
10340
+ /** Only show the tab strip when there is more than one visible tab; a lone tab
10341
+ * is just a redundant label, so the strip is hidden. */
10342
+ get showTabs() {
10343
+ return (this.tabs || []).filter((t) => !t?.hidden).length > 1;
10344
+ }
10172
10345
  clickAction(value) {
10173
10346
  this.actionClicked.emit(value);
10174
10347
  }
@@ -10210,11 +10383,11 @@ class ListTableHeaderComponent {
10210
10383
  return active === tab?.id || active === tab?.value || active === tab?.url || active === tab?.label;
10211
10384
  }
10212
10385
  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"] }] }); }
10386
+ 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
10387
  }
10215
10388
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableHeaderComponent, decorators: [{
10216
10389
  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"] }]
10390
+ 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
10391
  }], propDecorators: { title: [{
10219
10392
  type: Input
10220
10393
  }], subtitle: [{
@@ -11802,6 +11975,12 @@ class ListTableAutoRefreshComponent {
11802
11975
  this.refresh = new EventEmitter();
11803
11976
  this.secondsChange = new EventEmitter();
11804
11977
  this.open = false;
11978
+ /** Menu geometry, all measured when the menu opens: left offset so labels line up
11979
+ * under the button's value, width so the right edge lands on the button's right
11980
+ * edge, and right padding so the check centers under the chevron. */
11981
+ this.menuLeft = 0;
11982
+ this.menuWidth = 0;
11983
+ this.menuPadRight = 0;
11805
11984
  }
11806
11985
  /** Current interval in seconds; 0 means off. */
11807
11986
  set seconds(value) {
@@ -11823,7 +12002,37 @@ class ListTableAutoRefreshComponent {
11823
12002
  }
11824
12003
  toggleMenu(event) {
11825
12004
  event.stopPropagation();
11826
- this.open = !this.open;
12005
+ if (this.open) {
12006
+ this.open = false;
12007
+ return;
12008
+ }
12009
+ this.alignMenuToValue();
12010
+ this.open = true;
12011
+ }
12012
+ /** Position + size the menu so its labels sit directly under the button's value,
12013
+ * its right edge lands on the button's right edge (one shared border), and the
12014
+ * right-aligned check centers under the chevron. */
12015
+ alignMenuToValue() {
12016
+ const el = this.arEl?.nativeElement;
12017
+ const btn = el?.querySelector('.lt-ar-btn');
12018
+ const value = this.valueEl?.nativeElement?.getBoundingClientRect();
12019
+ const chevron = this.chevronEl?.nativeElement?.getBoundingClientRect();
12020
+ const container = el?.getBoundingClientRect();
12021
+ if (!btn || !value || !chevron || !container) {
12022
+ return;
12023
+ }
12024
+ const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
12025
+ // left padding we back out of the value position so labels sit under it
12026
+ const itemPadLeft = 0.875 * rem;
12027
+ // half the check's box, so we can land its center rather than its edge
12028
+ const tickHalf = 0.375 * rem;
12029
+ const borderWidth = parseFloat(getComputedStyle(btn).borderRightWidth) || 1;
12030
+ this.menuLeft = Math.max(0, value.left - container.left - itemPadLeft);
12031
+ this.menuWidth = container.width - this.menuLeft;
12032
+ // the check is right-aligned, so its right padding is the gap from the menu's
12033
+ // right edge to the chevron center, less the border and half the check box
12034
+ const chevronCenter = (chevron.left + chevron.right) / 2;
12035
+ this.menuPadRight = Math.max(0, container.right - chevronCenter - borderWidth - tickHalf);
11827
12036
  }
11828
12037
  closeMenu() {
11829
12038
  this.open = false;
@@ -11859,11 +12068,11 @@ class ListTableAutoRefreshComponent {
11859
12068
  }
11860
12069
  }
11861
12070
  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"] }] }); }
12071
+ 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
12072
  }
11864
12073
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableAutoRefreshComponent, decorators: [{
11865
12074
  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"] }]
12075
+ 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
12076
  }], propDecorators: { seconds: [{
11868
12077
  type: Input
11869
12078
  }], loading: [{
@@ -11874,6 +12083,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
11874
12083
  type: Output
11875
12084
  }], secondsChange: [{
11876
12085
  type: Output
12086
+ }], arEl: [{
12087
+ type: ViewChild,
12088
+ args: ['arEl']
12089
+ }], valueEl: [{
12090
+ type: ViewChild,
12091
+ args: ['valueEl']
12092
+ }], chevronEl: [{
12093
+ type: ViewChild,
12094
+ args: ['chevronEl']
11877
12095
  }] } });
11878
12096
 
11879
12097
  /**
@@ -11915,11 +12133,11 @@ class SelectCellComponent {
11915
12133
  return tooltip;
11916
12134
  }
11917
12135
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SelectCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11918
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SelectCellComponent, isStandalone: false, selector: "lib-select-cell", inputs: { row: "row", toggleItem: "toggleItem" }, ngImport: i0, template: "@if (!item.hideSelect) {\n <div\n #toggleButton\n (click)=\"toggle(item)\"\n [ngClass]=\"{ selected: item.selected, disabled: disableToggle }\"\n attr.name=\"RowSelect0_{{ item.name }}\"\n class=\"row-toggle tSelect\"\n id=\"RowSelect0_{{ item.id }}\"\n attr.data-id=\"{{item.id}}\"\n [matTooltip]=\"tooltip\"\n ></div>\n}\n", styles: [".row-toggle.tSelect{box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;cursor:pointer;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;transition:border-color .12s,background-color .12s}.row-toggle.tSelect:hover{border-color:var(--lt-link)}.row-toggle.tSelect.selected{border-color:var(--lt-link);background-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\")}.row-toggle.tSelect.disabled{opacity:.3;cursor:not-allowed;transform:translate(-50%,-50%)!important}.row-toggle.tSelect.disabled:hover{border-color:var(--lt-border);background-color:var(--lt-control-bg)}\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"] }] }); }
12136
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SelectCellComponent, isStandalone: false, selector: "lib-select-cell", inputs: { row: "row", toggleItem: "toggleItem" }, ngImport: i0, template: "@if (!item.hideSelect) {\n <div\n #toggleButton\n (click)=\"toggle(item)\"\n [ngClass]=\"{ selected: item.selected, indeterminate: item.indeterminate && !item.selected, disabled: disableToggle }\"\n attr.name=\"RowSelect0_{{ item.name }}\"\n class=\"row-toggle tSelect\"\n id=\"RowSelect0_{{ item.id }}\"\n attr.data-id=\"{{item.id}}\"\n [matTooltip]=\"tooltip\"\n ></div>\n}\n", styles: [".row-toggle.tSelect{box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;cursor:pointer;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;transition:border-color .12s,background-color .12s}.row-toggle.tSelect:hover{border-color:var(--lt-link)}.row-toggle.tSelect.selected{border-color:var(--lt-link);background-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\")}.row-toggle.tSelect.indeterminate{border-color:var(--lt-link);background-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' d='M3 7 L11 7'/%3E%3C/svg%3E\")}.row-toggle.tSelect.disabled{opacity:.3;cursor:not-allowed;transform:translate(-50%,-50%)!important}.row-toggle.tSelect.disabled:hover{border-color:var(--lt-border);background-color:var(--lt-control-bg)}\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"] }] }); }
11919
12137
  }
11920
12138
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SelectCellComponent, decorators: [{
11921
12139
  type: Component,
11922
- args: [{ selector: 'lib-select-cell', standalone: false, template: "@if (!item.hideSelect) {\n <div\n #toggleButton\n (click)=\"toggle(item)\"\n [ngClass]=\"{ selected: item.selected, disabled: disableToggle }\"\n attr.name=\"RowSelect0_{{ item.name }}\"\n class=\"row-toggle tSelect\"\n id=\"RowSelect0_{{ item.id }}\"\n attr.data-id=\"{{item.id}}\"\n [matTooltip]=\"tooltip\"\n ></div>\n}\n", styles: [".row-toggle.tSelect{box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;cursor:pointer;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;transition:border-color .12s,background-color .12s}.row-toggle.tSelect:hover{border-color:var(--lt-link)}.row-toggle.tSelect.selected{border-color:var(--lt-link);background-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\")}.row-toggle.tSelect.disabled{opacity:.3;cursor:not-allowed;transform:translate(-50%,-50%)!important}.row-toggle.tSelect.disabled:hover{border-color:var(--lt-border);background-color:var(--lt-control-bg)}\n"] }]
12140
+ args: [{ selector: 'lib-select-cell', standalone: false, template: "@if (!item.hideSelect) {\n <div\n #toggleButton\n (click)=\"toggle(item)\"\n [ngClass]=\"{ selected: item.selected, indeterminate: item.indeterminate && !item.selected, disabled: disableToggle }\"\n attr.name=\"RowSelect0_{{ item.name }}\"\n class=\"row-toggle tSelect\"\n id=\"RowSelect0_{{ item.id }}\"\n attr.data-id=\"{{item.id}}\"\n [matTooltip]=\"tooltip\"\n ></div>\n}\n", styles: [".row-toggle.tSelect{box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;cursor:pointer;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;transition:border-color .12s,background-color .12s}.row-toggle.tSelect:hover{border-color:var(--lt-link)}.row-toggle.tSelect.selected{border-color:var(--lt-link);background-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\")}.row-toggle.tSelect.indeterminate{border-color:var(--lt-link);background-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' d='M3 7 L11 7'/%3E%3C/svg%3E\")}.row-toggle.tSelect.disabled{opacity:.3;cursor:not-allowed;transform:translate(-50%,-50%)!important}.row-toggle.tSelect.disabled:hover{border-color:var(--lt-border);background-color:var(--lt-control-bg)}\n"] }]
11923
12141
  }], propDecorators: { row: [{
11924
12142
  type: Input
11925
12143
  }], toggleItem: [{
@@ -11941,11 +12159,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
11941
12159
  See the License for the specific language governing permissions and
11942
12160
  limitations under the License.
11943
12161
  */
11944
- /** Header cell for {@link ListTableComponent}: label, 3-state sort (asc/desc), and hide. */
12162
+ /** Header cell for {@link ListTableComponent}: label and 3-state sort (asc/desc). */
11945
12163
  class ListHeaderCellComponent {
11946
12164
  constructor() {
11947
12165
  this.sortChanged = new EventEmitter();
11948
- this.hideColumn = new EventEmitter();
11949
12166
  }
11950
12167
  get enableSorting() {
11951
12168
  return !!(this.column?.sortable || this.column?.enableSorting);
@@ -11959,24 +12176,18 @@ class ListHeaderCellComponent {
11959
12176
  this.column.onSort?.(this.column.key, dir);
11960
12177
  this.sortChanged.emit({ colId: this.column.key, dir });
11961
12178
  }
11962
- hide(event) {
11963
- event.stopPropagation();
11964
- this.hideColumn.emit(this.column);
11965
- }
11966
12179
  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"] }] }); }
12180
+ 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
12181
  }
11969
12182
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListHeaderCellComponent, decorators: [{
11970
12183
  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"] }]
12184
+ 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
12185
  }], propDecorators: { column: [{
11973
12186
  type: Input
11974
12187
  }], sortDir: [{
11975
12188
  type: Input
11976
12189
  }], sortChanged: [{
11977
12190
  type: Output
11978
- }], hideColumn: [{
11979
- type: Output
11980
12191
  }] } });
11981
12192
 
11982
12193
  /*
@@ -12018,6 +12229,8 @@ class ListFilterCellComponent {
12018
12229
  this.selectedValues = [];
12019
12230
  /** In-dropdown search text for the multi-select attribute combo. */
12020
12231
  this.attrSearch = '';
12232
+ /** Match any selected attribute (or) vs every one (and); attribute filters only. */
12233
+ this.attrSemantic = 'AnyOf';
12021
12234
  this.open = false;
12022
12235
  this.menuLeft = 0;
12023
12236
  this.menuTop = 0;
@@ -12217,10 +12430,19 @@ class ListFilterCellComponent {
12217
12430
  value: [...this.selectedValues],
12218
12431
  label: this.selectedValues.map((v) => this.labelForValue(v)).join(', '),
12219
12432
  type: this.isAttributeMode ? 'ATTRIBUTE' : 'MULTISELECT',
12220
- semantic: 'AnyOf',
12433
+ semantic: this.isAttributeMode ? this.attrSemantic : 'AnyOf',
12221
12434
  };
12222
12435
  this.filterService.updateFilter(filter);
12223
12436
  }
12437
+ setSemantic(semantic) {
12438
+ if (this.attrSemantic === semantic) {
12439
+ return;
12440
+ }
12441
+ this.attrSemantic = semantic;
12442
+ if (this.selectedValues.length) {
12443
+ this.emitMulti();
12444
+ }
12445
+ }
12224
12446
  // ------------------------------------------------------------------ date range
12225
12447
  selectDatePreset(opt) {
12226
12448
  this.selectedValue = opt.value;
@@ -12293,6 +12515,7 @@ class ListFilterCellComponent {
12293
12515
  this.textValue = '';
12294
12516
  this.dateRangeStart = null;
12295
12517
  this.dateRangeEnd = null;
12518
+ this.attrSemantic = 'AnyOf';
12296
12519
  this.removeThis();
12297
12520
  }
12298
12521
  removeThis() {
@@ -12319,6 +12542,9 @@ class ListFilterCellComponent {
12319
12542
  }
12320
12543
  else if (this.mode === 'multi') {
12321
12544
  this.selectedValues = _.isArray(match.value) ? [...match.value] : [match.value];
12545
+ if (this.isAttributeMode) {
12546
+ this.attrSemantic = match.semantic === 'AllOf' ? 'AllOf' : 'AnyOf';
12547
+ }
12322
12548
  }
12323
12549
  else if (this.mode === 'daterange') {
12324
12550
  // keep the locally chosen preset label; match.value is the raw [start,end] range
@@ -12328,11 +12554,11 @@ class ListFilterCellComponent {
12328
12554
  }
12329
12555
  }
12330
12556
  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"] }] }); }
12557
+ 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
12558
  }
12333
12559
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListFilterCellComponent, decorators: [{
12334
12560
  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"] }]
12561
+ 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
12562
  }], ctorParameters: () => [{ type: DataTableFilterService }, { type: ListTableService }], propDecorators: { column: [{
12337
12563
  type: Input
12338
12564
  }], trigger: [{
@@ -12376,6 +12602,103 @@ class ListTableComponent {
12376
12602
  this.buildColumns();
12377
12603
  }
12378
12604
  }
12605
+ set rowData(value) {
12606
+ this._rowData = value || [];
12607
+ this.rebuildFlatRows();
12608
+ }
12609
+ get rowData() {
12610
+ return this._rowData;
12611
+ }
12612
+ // fitColumns: keep the table within its visible width when resizing - a column can only grow until
12613
+ // the other columns reach their set or minimum width, then the drag stops, so nothing is pushed
12614
+ // off screen into the horizontal scroll. Off by default (columns can grow past the view and scroll).
12615
+ set options(value) {
12616
+ const wasTree = !!this._options?.tree;
12617
+ this._options = value || {};
12618
+ this.toolbarControls = this.normalizeToolbar(this._options.toolbar);
12619
+ // rebuild the render list if tree mode was flipped after init, otherwise flatRows
12620
+ // keeps the pre-toggle shape
12621
+ if (this._initialized && !!this._options.tree !== wasTree) {
12622
+ this.rebuildFlatRows();
12623
+ }
12624
+ }
12625
+ get options() {
12626
+ return this._options;
12627
+ }
12628
+ normalizeToolbar(toolbar) {
12629
+ const resolve = (c) => ({
12630
+ show: c?.show !== false,
12631
+ disabled: !!c?.disabled,
12632
+ });
12633
+ return {
12634
+ search: resolve(toolbar?.search),
12635
+ filters: resolve(toolbar?.filters),
12636
+ refresh: resolve(toolbar?.refresh),
12637
+ autoRefresh: resolve(toolbar?.autoRefresh),
12638
+ columns: resolve(toolbar?.columns),
12639
+ pageSize: resolve(toolbar?.pageSize),
12640
+ pagination: resolve(toolbar?.pagination),
12641
+ };
12642
+ }
12643
+ /** Tree mode: columns per depth level (index = level; level 0 uses `columns`).
12644
+ * Nested rows render in their own grid with a per-level sub-header, so children
12645
+ * need not match the parent. Unset renders children with the parent's columns.
12646
+ * The bound value is the default; users can show/hide/reorder/resize child columns
12647
+ * per depth from the Columns menu, persisted per table. */
12648
+ set childColumnsByLevel(v) {
12649
+ this._declaredChildColumnsByLevel = v;
12650
+ this.buildChildColumns();
12651
+ }
12652
+ get childColumnsByLevel() {
12653
+ return this._childColumnsByLevel;
12654
+ }
12655
+ invalidateColumnCache() {
12656
+ this._childColsVersion++;
12657
+ }
12658
+ syncTreeMemo() {
12659
+ if (this._treeMemoVersion !== this._childColsVersion) {
12660
+ this._treeKeyCache.clear();
12661
+ this._leadOffsetCache.clear();
12662
+ this._treeMemoVersion = this._childColsVersion;
12663
+ }
12664
+ }
12665
+ /** Rebuilds the effective per-level child columns from the declared defaults with
12666
+ * any persisted order/visibility/width applied. */
12667
+ buildChildColumns() {
12668
+ const declared = this._declaredChildColumnsByLevel;
12669
+ if (!declared) {
12670
+ this._childColumnsByLevel = undefined;
12671
+ this.invalidateColumnCache();
12672
+ return;
12673
+ }
12674
+ this._childColumnsByLevel = declared.map((set, level) => set && set.length ? this.svc.applyPersistedChildState(level, set) : (set ? [...set] : set));
12675
+ this.invalidateColumnCache();
12676
+ }
12677
+ /** Tree look: 'card' floats each nested level as a bordered card; 'flat' renders
12678
+ * nested rows inline with the table, sub-headers as plain strips, no card chrome.
12679
+ * The bound value is the default; the user can switch it from the Columns menu and
12680
+ * the choice is remembered per table. */
12681
+ set treeVariant(v) {
12682
+ this._declaredTreeVariant = v;
12683
+ this._treeVariant = v;
12684
+ }
12685
+ get treeVariant() {
12686
+ return this._treeVariant;
12687
+ }
12688
+ get isFlatTree() {
12689
+ return this.isTreeMode && this.treeVariant === 'flat';
12690
+ }
12691
+ /** Show the flat/nested switch only when the table actually renders nested rows. */
12692
+ get canSwitchTreeVariant() {
12693
+ return this.isTreeMode && this.hasChildColumns;
12694
+ }
12695
+ setTreeVariant(variant) {
12696
+ if (this._treeVariant === variant) {
12697
+ return;
12698
+ }
12699
+ this._treeVariant = variant;
12700
+ this.svc.saveTreeVariant(variant);
12701
+ }
12379
12702
  constructor(svc, tableFilterService, el, ngZone, sanitizer) {
12380
12703
  this.svc = svc;
12381
12704
  this.tableFilterService = tableFilterService;
@@ -12383,9 +12706,22 @@ class ListTableComponent {
12383
12706
  this.ngZone = ngZone;
12384
12707
  this.sanitizer = sanitizer;
12385
12708
  this._tableId = '';
12386
- this.rowData = [];
12709
+ this._rowData = [];
12387
12710
  this.isLoading = false;
12388
- this.options = { noSelect: false, noMenu: false };
12711
+ this._options = { noSelect: false, noMenu: false };
12712
+ /** Resolved show/disabled state for each toolbar control, read by the template.
12713
+ * Every control shows and is enabled unless the host's `options.toolbar` says otherwise. */
12714
+ this.toolbarControls = this.normalizeToolbar(undefined);
12715
+ // Cached per level; a width change mutates the same column objects, so no rebuild is needed.
12716
+ this._childLevelCache = new Map();
12717
+ this._childColsVersion = 0;
12718
+ this._childLevelCacheVersion = -1;
12719
+ // per-level derived values, recomputed only when the column set changes, so the
12720
+ // template's per-cell/per-row calls stay cheap on every change-detection pass
12721
+ this._treeKeyCache = new Map();
12722
+ this._leadOffsetCache = new Map();
12723
+ this._treeMemoVersion = -1;
12724
+ this.nodeToggled = new EventEmitter();
12389
12725
  this.hasNext = false;
12390
12726
  this.hasPrev = false;
12391
12727
  this.startCount = '-';
@@ -12403,6 +12739,12 @@ class ListTableComponent {
12403
12739
  this.filterColumn = 'name';
12404
12740
  this.actionRequested = new EventEmitter();
12405
12741
  this.tableReady = new EventEmitter();
12742
+ /** Full set of selected rows (all depths) whenever the selection changes. */
12743
+ this.selectionChanged = new EventEmitter();
12744
+ /** The row the pointer is over, or null when it leaves. Emitted once per row (at the
12745
+ * row level, not per cell) so consumers can react to the whole hovered row, for
12746
+ * example to highlight that row's path in a connected diagram. */
12747
+ this.rowHover = new EventEmitter();
12406
12748
  /** Full column list (includes hidden), after persisted state. */
12407
12749
  this.allColumns = [];
12408
12750
  /** Currently rendered columns (visible + ordered). */
@@ -12418,6 +12760,28 @@ class ListTableComponent {
12418
12760
  this.selectedItem = { actionList: [] };
12419
12761
  // ---- selection ----
12420
12762
  this.allToggled = false;
12763
+ /** Rows actually rendered: in tree mode this is the roots plus the expanded
12764
+ * descendants in order; otherwise it is `rowData` unchanged. */
12765
+ this.flatRows = [];
12766
+ this._treeVariant = 'card';
12767
+ this._declaredTreeVariant = 'card';
12768
+ /** Show the flat/nested row-layout switch in the Columns menu. Hidden for now;
12769
+ * the variant still comes from the `treeVariant` input. */
12770
+ this.showTreeVariantToggle = false;
12771
+ /** Flat variant only: draw a running guide rail down the left of each nested level. */
12772
+ this.treeRail = false;
12773
+ this.treeIndentRem = 1.25;
12774
+ // nudges cards past the first nested level right so their edge lands on the
12775
+ // parent chevron (a nested chevron sits one grid-padding deeper than the root's)
12776
+ this.treeDeepAlignRem = 0.625;
12777
+ // flat first-column indent: base clears the select gutter, then one chevron-width
12778
+ // per level so each sub-header lines up under its parent row's name
12779
+ this.treeFlatBaseRem = 3.75;
12780
+ this.treeFlatStepRem = 2.125;
12781
+ // nested card's left inset before per-depth indent; must match the --lt-card-inset token
12782
+ this.treeCardBaseRem = 4;
12783
+ // width of the leading checkbox column injected into nested grids when selectChildren is on
12784
+ this.treeSelectColRem = 2.25;
12421
12785
  // ---- column chooser (skinned show/hide + reorder panel) ----
12422
12786
  this.showColumnChooser = false;
12423
12787
  // ---- integrated toolbar: search + filter chips + pagination ----
@@ -12440,16 +12804,31 @@ class ListTableComponent {
12440
12804
  this._initialColumns = [];
12441
12805
  this._initialized = false;
12442
12806
  this.subscription = new Subscription();
12443
- this.trackRow = (index, row) => row?.id ?? row?.name ?? index;
12807
+ // expansion + lazily-loaded children keyed by __treeRowKey, so a rowData refresh
12808
+ // (auto-refresh, paging, filter) keeps the tree open instead of collapsing it
12809
+ this._expandedKeys = new Set();
12810
+ this._childrenCache = new Map();
12811
+ this._destroyed = false;
12812
+ this.trackRow = (index, row) => row?.__treeRowKey ?? row?.id ?? row?.name ?? index;
12444
12813
  this.trackCol = (index, col) => col.key;
12445
12814
  // ---------------------------------------------------------------- hover tooltip
12446
12815
  this.hoverTooltip = null;
12447
- // ---------------------------------------------------------------- selection
12816
+ // ---------------------------------------------------------------- row hover
12817
+ /** The row currently under the pointer, so we only emit when it actually changes. */
12818
+ this.hoveredRow = null;
12448
12819
  this.toggleItem = (item) => {
12449
12820
  item.selected = !item.selected;
12450
- this.allToggled = !_.isEmpty(this.rowData) && _.every(this.rowData, { selected: true });
12821
+ this.syncSelectAll();
12451
12822
  this.actionRequested.emit({ action: 'toggleItem', item });
12452
- };
12823
+ this.emitSelection();
12824
+ };
12825
+ this._railCache = new Map();
12826
+ // The whole chooser row is draggable, and the label sits inside the visibility
12827
+ // toggle button, so a reorder drag ends with a click on that button. This guard
12828
+ // swallows that one click so dragging a row never also hides its column.
12829
+ this._chooserDragged = false;
12830
+ // ---- nested-column chooser: one expandable group per depth level ----
12831
+ this.nestedChooserOpen = new Set();
12453
12832
  // ---------------------------------------------------------------- action menus
12454
12833
  this.openActionMenu = (event, item) => {
12455
12834
  this.selectedItem = item;
@@ -12485,7 +12864,12 @@ class ListTableComponent {
12485
12864
  this._initialized = true;
12486
12865
  // set directly (no emit) to avoid forcing a second fetch when a host pre-seeded it
12487
12866
  this.tableFilterService.pageSize = this.svc.restorePageSize(this.initialPageSize);
12867
+ this._treeVariant = this.svc.restoreTreeVariant(this._declaredTreeVariant);
12868
+ this.buildChildColumns();
12488
12869
  this.buildColumns();
12870
+ // all inputs are bound by now, so settle the render list against the final
12871
+ // tree flag (the rowData setter may have run before `options` arrived)
12872
+ this.rebuildFlatRows();
12489
12873
  this.updateEntityTypeLabel();
12490
12874
  this.subscription.add(this.tableFilterService.filtersChanged.subscribe((filters) => {
12491
12875
  // chips: every non-hidden filter except the free-text search (shown in the box)
@@ -12514,6 +12898,7 @@ class ListTableComponent {
12514
12898
  this.ensureFilterResizeObserver();
12515
12899
  }
12516
12900
  ngOnDestroy() {
12901
+ this._destroyed = true;
12517
12902
  this.subscription.unsubscribe();
12518
12903
  this.detachResizeListeners();
12519
12904
  this.filterRO?.disconnect();
@@ -12683,6 +13068,7 @@ class ListTableComponent {
12683
13068
  this.hiddenColumns = this.allColumns
12684
13069
  .filter((c) => c.hidden)
12685
13070
  .map((c) => ({ ...c, headerName: c.label }));
13071
+ this.invalidateColumnCache();
12686
13072
  }
12687
13073
  colWidth(col) {
12688
13074
  return col.width != null ? `${col.width}px` : null;
@@ -12751,17 +13137,339 @@ class ListTableComponent {
12751
13137
  onCellLeave() {
12752
13138
  this.hoverTooltip = null;
12753
13139
  }
13140
+ onRowHover(row) {
13141
+ if (this.hoveredRow === row) {
13142
+ return;
13143
+ }
13144
+ this.hoveredRow = row;
13145
+ this.rowHover.emit(row);
13146
+ }
13147
+ onRowLeave(row) {
13148
+ if (this.hoveredRow !== row) {
13149
+ return;
13150
+ }
13151
+ this.hoveredRow = null;
13152
+ this.rowHover.emit(null);
13153
+ }
13154
+ // ---------------------------------------------------------------- selection
13155
+ /** True when nested rows get checkboxes and join the selection (opt-in). */
13156
+ get childSelectEnabled() {
13157
+ return !!this.options?.selectChildren && !this.options?.noSelect;
13158
+ }
13159
+ /** Rows the checkboxes and select-all act on: top-level only, unless selectChildren
13160
+ * is set. Keeps select-all from marking nested rows a host that reads rowData never sees. */
13161
+ get selectableRows() {
13162
+ if (!this.isTreeMode || this.childSelectEnabled) {
13163
+ return this.flatRows;
13164
+ }
13165
+ return this.flatRows.filter((r) => (r.__treeLevel ?? 0) === 0);
13166
+ }
12754
13167
  /** Some rows selected but the header select-all isn't fully on (indeterminate). */
12755
13168
  get someSelected() {
12756
- return !this.allToggled && _.some(this.rowData, { selected: true });
13169
+ return !this.allToggled && this.selectableRows.some((r) => r.selected);
12757
13170
  }
12758
13171
  toggleAll() {
12759
13172
  const newState = !this.allToggled;
12760
13173
  this.allToggled = newState;
12761
- _.forEach(this.rowData, (row) => {
12762
- row.selected = newState;
12763
- });
13174
+ this.selectableRows.forEach((row) => (row.selected = newState));
12764
13175
  this.actionRequested.emit({ action: 'toggleAll' });
13176
+ this.emitSelection();
13177
+ }
13178
+ syncSelectAll() {
13179
+ const rows = this.selectableRows;
13180
+ this.allToggled = rows.length > 0 && rows.every((r) => r.selected);
13181
+ }
13182
+ /** Emit every selected row across all depths. Walks the full data, not just the rendered
13183
+ * rows, so a collapsed parent's selected children are still reported. */
13184
+ emitSelection() {
13185
+ const out = [];
13186
+ const walk = (rows) => {
13187
+ (rows || []).forEach((r) => {
13188
+ if (r.selected) {
13189
+ out.push(r);
13190
+ }
13191
+ if (Array.isArray(r.children)) {
13192
+ walk(r.children);
13193
+ }
13194
+ });
13195
+ };
13196
+ walk(this._rowData);
13197
+ this.selectionChanged.emit(out);
13198
+ }
13199
+ // ---------------------------------------------------------------- tree rows
13200
+ get isTreeMode() {
13201
+ return !!this.options?.tree;
13202
+ }
13203
+ /** True when per-level child columns are configured (children render in their own
13204
+ * grid with a sub-header rather than the parent's columns). */
13205
+ get hasChildColumns() {
13206
+ return !!(this.childColumnsByLevel && this.childColumnsByLevel.length);
13207
+ }
13208
+ /** Columns used at a given depth: level 0 is the main (visible) columns; deeper
13209
+ * levels use `childColumnsByLevel`, falling back to the deepest set defined. */
13210
+ columnsForLevel(level) {
13211
+ if (level <= 0 || !this.hasChildColumns) {
13212
+ return this.visibleColumns;
13213
+ }
13214
+ if (this._childLevelCacheVersion !== this._childColsVersion) {
13215
+ this._childLevelCache.clear();
13216
+ this._childLevelCacheVersion = this._childColsVersion;
13217
+ }
13218
+ const cached = this._childLevelCache.get(level);
13219
+ if (cached) {
13220
+ return cached;
13221
+ }
13222
+ const sets = this.childColumnsByLevel;
13223
+ const set = sets[level] ?? sets[sets.length - 1];
13224
+ const cols = (set ?? this.visibleColumns).filter((c) => !c.hidden);
13225
+ this._childLevelCache.set(level, cols);
13226
+ return cols;
13227
+ }
13228
+ /** Child columns the chooser lists at a depth: all declared ones (including hidden),
13229
+ * minus structural columns like the select checkbox or row-actions gutter. */
13230
+ allColumnsForLevel(level) {
13231
+ return (this._childColumnsByLevel?.[level] ?? []).filter((c) => !c.excludeFromChooser);
13232
+ }
13233
+ /** Depths that offer configurable columns (skips the level-0 placeholder and any
13234
+ * level that only holds structural columns), one chooser group per level. */
13235
+ get childLevels() {
13236
+ const sets = this._childColumnsByLevel;
13237
+ if (!sets) {
13238
+ return [];
13239
+ }
13240
+ const levels = [];
13241
+ sets.forEach((_set, level) => {
13242
+ if (level > 0 && this.allColumnsForLevel(level).length) {
13243
+ levels.push(level);
13244
+ }
13245
+ });
13246
+ return levels;
13247
+ }
13248
+ /** Chooser hint for a nested level: its first few column labels, then "+N". */
13249
+ childLevelLabel(level) {
13250
+ const labels = this.allColumnsForLevel(level)
13251
+ .map((c) => c.label)
13252
+ .filter(Boolean);
13253
+ const max = 3;
13254
+ if (labels.length <= max) {
13255
+ return labels.join(' / ');
13256
+ }
13257
+ return labels.slice(0, max).join(' / ') + ' +' + (labels.length - max);
13258
+ }
13259
+ /** Key of the column that carries the expand control + indentation for a column set:
13260
+ * the flagged one, else the first real data column (skipping a leading structural
13261
+ * column such as a select checkbox the host put at the start). */
13262
+ treeColumnKeyFor(cols) {
13263
+ return (cols.find((c) => c.treeColumn) || cols.find((c) => !c.excludeFromChooser) || cols[0])?.key;
13264
+ }
13265
+ treeColumnKeyForLevel(level) {
13266
+ this.syncTreeMemo();
13267
+ if (!this._treeKeyCache.has(level)) {
13268
+ this._treeKeyCache.set(level, this.treeColumnKeyFor(this.columnsForLevel(level)));
13269
+ }
13270
+ return this._treeKeyCache.get(level);
13271
+ }
13272
+ /** Width of the columns before the tree column, used to pull the flat first-column
13273
+ * indent back by whatever structural columns (like a select checkbox) sit ahead of
13274
+ * it, so its content still lands under the parent row's name. */
13275
+ treeLeadOffset(level) {
13276
+ this.syncTreeMemo();
13277
+ const cacheKey = `${level}|${this.childSelectEnabled}`;
13278
+ const hit = this._leadOffsetCache.get(cacheKey);
13279
+ if (hit !== undefined) {
13280
+ return hit;
13281
+ }
13282
+ const cols = this.columnsForLevel(level);
13283
+ const treeKey = this.treeColumnKeyForLevel(level);
13284
+ let px = 0;
13285
+ let count = 0;
13286
+ for (const c of cols) {
13287
+ if (c.key === treeKey) {
13288
+ break;
13289
+ }
13290
+ px += c.width ?? 0;
13291
+ count++;
13292
+ }
13293
+ const gap = 'var(--lt-tree-col-gap, 0.5rem)';
13294
+ // the injected select column (when on) sits before every data column
13295
+ const selRem = this.childSelectEnabled ? this.treeSelectColRem : 0;
13296
+ const gaps = count + (this.childSelectEnabled ? 1 : 0);
13297
+ const result = (!gaps && !selRem && !px) ? '0px' : `calc(${selRem}rem + ${px}px + ${gaps} * ${gap})`;
13298
+ this._leadOffsetCache.set(cacheKey, result);
13299
+ return result;
13300
+ }
13301
+ isTreeColumn(col, level) {
13302
+ return this.isTreeMode && col.key === this.treeColumnKeyForLevel(level);
13303
+ }
13304
+ isTreeCell(col) {
13305
+ return this.isTreeColumn(col, 0);
13306
+ }
13307
+ /** Columns a full-width nested row spans: the data columns plus the select and
13308
+ * menu gutters, matching the header's cell count. */
13309
+ get treeSpanCount() {
13310
+ return this.visibleColumns.length + (this.options?.noSelect ? 0 : 1) + (this.options?.noMenu ? 0 : 1);
13311
+ }
13312
+ /** Per-level indent (rem); the card variant nudges levels past the first so the edge meets the parent chevron. */
13313
+ treeIndentFor(level) {
13314
+ const align = this.treeVariant === 'card' && level >= 2 ? this.treeDeepAlignRem : 0;
13315
+ return level * this.treeIndentRem + align;
13316
+ }
13317
+ /** Flat variant: left pad (rem) of a nested row's first column (sub-header + data),
13318
+ * one chevron-width per level so it lines up under the parent row's name. */
13319
+ treeFlatFirstIndentRem(level) {
13320
+ return this.treeFlatBaseRem + level * this.treeFlatStepRem;
13321
+ }
13322
+ /** Flat variant: drop the chevron slot for a leaf-only level so values sit under their header. */
13323
+ showTreeLead(row) {
13324
+ return !(this.isFlatTree && row?.__treeSiblingsCanExpand === false);
13325
+ }
13326
+ /** Left inset (rem) of a nested card at a given depth, matching the td padding. */
13327
+ treeCardLeftRem(level) {
13328
+ return this.treeCardBaseRem + this.treeIndentFor(level);
13329
+ }
13330
+ /** Vertical guide lines redrawing each ancestor card's left edge at this row's
13331
+ * position, so a card's left border stays continuous where a nested card is
13332
+ * inserted between its rows. Returns a stack of 1px background gradients, or null
13333
+ * when there is no ancestor to redraw (roots and the first nested level). */
13334
+ treeAncestorRails(level) {
13335
+ const key = `${level}|${this._treeVariant}|${this.treeRail}|${this.isTreeMode}`;
13336
+ const cached = this._railCache.get(key);
13337
+ if (cached !== undefined) {
13338
+ return cached;
13339
+ }
13340
+ const rails = this.computeAncestorRails(level);
13341
+ this._railCache.set(key, rails);
13342
+ return rails;
13343
+ }
13344
+ computeAncestorRails(level) {
13345
+ if (!this.isTreeMode || level < 2) {
13346
+ return null;
13347
+ }
13348
+ const flat = this.treeVariant === 'flat';
13349
+ // card mode always redraws ancestor card borders; flat mode draws the rail
13350
+ // only when the optional running rail is turned on
13351
+ if (flat && !this.treeRail) {
13352
+ return null;
13353
+ }
13354
+ const color = flat
13355
+ ? 'var(--lt-tree-rail, color-mix(in srgb, var(--lt-border) 55%, var(--lt-icon)))'
13356
+ : 'var(--lt-tree-card-border, color-mix(in srgb, var(--lt-border) 70%, var(--lt-icon)))';
13357
+ const rails = [];
13358
+ for (let k = 1; k < level; k++) {
13359
+ const x = this.treeCardLeftRem(k);
13360
+ rails.push(`linear-gradient(90deg, transparent ${x}rem, ${color} ${x}rem, ${color} calc(${x}rem + 1px), transparent calc(${x}rem + 1px))`);
13361
+ }
13362
+ return rails.join(', ');
13363
+ }
13364
+ gridTemplate(level) {
13365
+ const tracks = this.columnsForLevel(level)
13366
+ .map((c) => (c.width != null ? `${c.width}px` : 'minmax(0, 1fr)'));
13367
+ if (this.childSelectEnabled) {
13368
+ tracks.unshift(`${this.treeSelectColRem}rem`);
13369
+ }
13370
+ return tracks.join(' ');
13371
+ }
13372
+ hasChildren(row) {
13373
+ return Array.isArray(row?.children) && row.children.length > 0;
13374
+ }
13375
+ /** A row can be expanded unless it is a leaf, has no loaded children, and has no
13376
+ * loader left to try. */
13377
+ canExpand(row) {
13378
+ if (!this.isTreeMode || row?.leaf === true) {
13379
+ return false;
13380
+ }
13381
+ if (this.hasChildren(row)) {
13382
+ return true;
13383
+ }
13384
+ const key = row?.__treeRowKey;
13385
+ if (key && this._childrenCache.has(key)) {
13386
+ return true;
13387
+ }
13388
+ return !!this.loadChildren && row?.__treeLoaded !== true;
13389
+ }
13390
+ toggleNode(row) {
13391
+ if (!this.isTreeMode || row?.__treeLoading) {
13392
+ return;
13393
+ }
13394
+ const key = row.__treeRowKey;
13395
+ if (this._expandedKeys.has(key)) {
13396
+ this._expandedKeys.delete(key);
13397
+ this.rebuildFlatRows();
13398
+ this.nodeToggled.emit({ row, expanded: false });
13399
+ return;
13400
+ }
13401
+ if (this.loadChildren && !this.hasChildren(row) && !this._childrenCache.has(key)) {
13402
+ row.__treeLoading = true;
13403
+ row.__treeLoadError = false; // clear any previous failure on retry
13404
+ this.rebuildFlatRows(); // surface the spinner while children load
13405
+ // run the loader inside the executor so a synchronous throw rejects the
13406
+ // promise instead of escaping and leaving __treeLoading stuck on forever
13407
+ new Promise((resolve) => resolve(this.loadChildren(row))).then((children) => this.onChildrenLoaded(row, children), () => this.onChildrenLoaded(row, [], true));
13408
+ return;
13409
+ }
13410
+ this._expandedKeys.add(key);
13411
+ this.rebuildFlatRows();
13412
+ this.nodeToggled.emit({ row, expanded: true });
13413
+ }
13414
+ onChildrenLoaded(row, children, failed = false) {
13415
+ if (this._destroyed) {
13416
+ return;
13417
+ }
13418
+ row.__treeLoading = false;
13419
+ if (failed) {
13420
+ // flag the failure so the toggle shows a retry affordance, and leave it collapsed
13421
+ row.__treeLoadError = true;
13422
+ this.rebuildFlatRows();
13423
+ return;
13424
+ }
13425
+ const list = Array.isArray(children) ? children : [];
13426
+ this._childrenCache.set(row.__treeRowKey, list);
13427
+ row.children = list;
13428
+ row.__treeLoaded = true;
13429
+ if (!list.length) {
13430
+ row.leaf = true; // nothing under it, so stop offering the control
13431
+ }
13432
+ this._expandedKeys.add(row.__treeRowKey);
13433
+ this.rebuildFlatRows();
13434
+ this.nodeToggled.emit({ row, expanded: true });
13435
+ }
13436
+ /** Flatten the tree into the ordered render list, tagging each row with its depth.
13437
+ * Outside tree mode the render list is `rowData` as-is. */
13438
+ rebuildFlatRows() {
13439
+ if (!this.isTreeMode) {
13440
+ this.flatRows = this._rowData;
13441
+ return;
13442
+ }
13443
+ const out = [];
13444
+ const walk = (rows, level, parentKey) => {
13445
+ const list = rows || [];
13446
+ // when no sibling can expand, the level needs no chevron slot
13447
+ const siblingsCanExpand = list.some((r) => this.canExpand(r));
13448
+ list.forEach((row, idx) => {
13449
+ row.__treeLevel = level;
13450
+ row.__treeSiblingsCanExpand = siblingsCanExpand;
13451
+ // first child of a group drives the per-expansion sub-header;
13452
+ // first and last mark the top and bottom of the nested card
13453
+ row.__treeFirstChild = level > 0 && idx === 0;
13454
+ row.__treeLastChild = level > 0 && idx === list.length - 1;
13455
+ // branch-unique track key: the same entity can appear under two parents
13456
+ // in a flat tree, so fold the parent path in to keep @for keys distinct
13457
+ row.__treeRowKey = parentKey + '/' + (row?.id ?? row?.name ?? idx);
13458
+ // a rowData refresh replaces the row object; re-attach children we loaded
13459
+ // earlier so an expanded lazy node stays populated instead of emptying out
13460
+ if (!this.hasChildren(row) && this._childrenCache.has(row.__treeRowKey)) {
13461
+ row.children = this._childrenCache.get(row.__treeRowKey);
13462
+ row.__treeLoaded = true;
13463
+ }
13464
+ row.__treeExpanded = this._expandedKeys.has(row.__treeRowKey);
13465
+ out.push(row);
13466
+ if (row.__treeExpanded && this.hasChildren(row)) {
13467
+ walk(row.children, level + 1, row.__treeRowKey);
13468
+ }
13469
+ });
13470
+ };
13471
+ walk(this._rowData, 0, '');
13472
+ this.flatRows = out;
12765
13473
  }
12766
13474
  // ---------------------------------------------------------------- sort
12767
13475
  onHeaderSort(event) {
@@ -12769,16 +13477,7 @@ class ListTableComponent {
12769
13477
  c.sortDir = c.key === event.colId ? event.dir : undefined;
12770
13478
  });
12771
13479
  }
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
- }
13480
+ // ---------------------------------------------------------------- show / reorder
12782
13481
  showColumn(event) {
12783
13482
  const target = this.allColumns.find((c) => c.key === event.column?.key);
12784
13483
  if (!target) {
@@ -12801,7 +13500,9 @@ class ListTableComponent {
12801
13500
  }
12802
13501
  resetTableColumns() {
12803
13502
  this.svc.reset();
13503
+ this._treeVariant = this._declaredTreeVariant;
12804
13504
  this.allColumns = this._initialColumns.map((c) => ({ ...c }));
13505
+ this._childColumnsByLevel = this._declaredChildColumnsByLevel?.map((set) => set ? set.map((c) => ({ ...c })) : set);
12805
13506
  this.updateVisible();
12806
13507
  this.closeActionMenu();
12807
13508
  }
@@ -12811,14 +13512,45 @@ class ListTableComponent {
12811
13512
  get hiddenColumnCount() {
12812
13513
  return this.allColumns.filter((c) => c.hidden && !c.excludeFromChooser && !this.isDefaultHidden(c)).length;
12813
13514
  }
12814
- /** True when visibility + order match the originally declared columns. */
13515
+ /** True when visibility, order, and width all match the originally declared
13516
+ * columns. Width is included so resizing a column alone still enables the
13517
+ * reset button; a column with no declared width uses null on both sides so
13518
+ * it only counts as changed once the user drags it to a concrete width. */
12815
13519
  get columnsAtDefault() {
13520
+ if (this.canSwitchTreeVariant && this._treeVariant !== this._declaredTreeVariant) {
13521
+ return false;
13522
+ }
12816
13523
  if (this.allColumns.length !== this._initialColumns.length) {
12817
13524
  return false;
12818
13525
  }
12819
- return this.allColumns.every((c, i) => {
13526
+ const parentAtDefault = this.allColumns.every((c, i) => {
12820
13527
  const init = this._initialColumns[i];
12821
- return init && init.key === c.key && !!init.hidden === !!c.hidden;
13528
+ return init
13529
+ && init.key === c.key
13530
+ && !!init.hidden === !!c.hidden
13531
+ && (init.width ?? null) === (c.width ?? null);
13532
+ });
13533
+ return parentAtDefault && this.childColumnsAtDefault;
13534
+ }
13535
+ /** True when every nested level's columns match their declared order/visibility/width. */
13536
+ get childColumnsAtDefault() {
13537
+ const declared = this._declaredChildColumnsByLevel;
13538
+ const current = this._childColumnsByLevel;
13539
+ if (!declared || !current) {
13540
+ return true;
13541
+ }
13542
+ return declared.every((set, level) => {
13543
+ const cur = current[level] ?? [];
13544
+ if (!set || set.length !== cur.length) {
13545
+ return set ? set.length === cur.length : true;
13546
+ }
13547
+ return set.every((d, i) => {
13548
+ const c = cur[i];
13549
+ return c
13550
+ && d.key === c.key
13551
+ && !!d.hidden === !!c.hidden
13552
+ && (d.width ?? null) === (c.width ?? null);
13553
+ });
12822
13554
  });
12823
13555
  }
12824
13556
  /** True when the column is hidden in the originally declared defaults. */
@@ -12836,36 +13568,82 @@ class ListTableComponent {
12836
13568
  closeColumnChooser() {
12837
13569
  this.showColumnChooser = false;
12838
13570
  }
12839
- toggleColumnVisible(col) {
12840
- if (col.hideable === false) {
13571
+ onChooserDragStarted() {
13572
+ this._chooserDragged = true;
13573
+ }
13574
+ onChooserDragEnded() {
13575
+ // clear after the post-drag click has been handled (and skipped) below
13576
+ setTimeout(() => (this._chooserDragged = false));
13577
+ }
13578
+ /** Toggle a column's visibility. `level` undefined targets the top-level columns;
13579
+ * a number targets that nested depth's columns. */
13580
+ toggleColumnVisible(col, level) {
13581
+ if (this._chooserDragged || col.hideable === false) {
12841
13582
  return;
12842
13583
  }
12843
- const target = this.allColumns.find((c) => c.key === col.key);
13584
+ const set = level == null ? this.allColumns : this._childColumnsByLevel?.[level];
13585
+ const target = set?.find((c) => c.key === col.key);
12844
13586
  if (!target) {
12845
13587
  return;
12846
13588
  }
13589
+ if (!target.hidden && this.isLastVisibleColumn(target, level)) {
13590
+ return;
13591
+ }
12847
13592
  target.hidden = !target.hidden;
12848
- this.updateVisible();
12849
- this.svc.saveState(this.allColumns);
13593
+ this.afterColumnChange(level, set);
13594
+ }
13595
+ /** True when this is the only visible column at its level, so its toggle is disabled
13596
+ * to keep at least one column on screen. */
13597
+ isLastVisibleColumn(col, level) {
13598
+ const list = level == null ? this.chooserColumns : this.allColumnsForLevel(level);
13599
+ return !col.hidden && list.filter((c) => !c.hidden).length <= 1;
12850
13600
  }
12851
- /** Columns shown in the Columns menu. */
13601
+ /** Columns shown in the top-level Columns menu. */
12852
13602
  get chooserColumns() {
12853
13603
  return this.allColumns.filter((c) => !c.excludeFromChooser);
12854
13604
  }
12855
- chooserReorder(event) {
13605
+ /** Reorder columns from a chooser drag. `level` undefined targets the top-level
13606
+ * columns; a number targets that nested depth's columns. */
13607
+ chooserReorder(event, level) {
12856
13608
  if (event.previousIndex === event.currentIndex) {
12857
13609
  return;
12858
13610
  }
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]);
13611
+ // the menu lists a chooser subset, so map the drag indices back onto the full set
13612
+ const full = level == null ? this.allColumns : this._childColumnsByLevel?.[level];
13613
+ const list = level == null ? this.chooserColumns : this.allColumnsForLevel(level);
13614
+ if (!full) {
13615
+ return;
13616
+ }
13617
+ const from = full.indexOf(list[event.previousIndex]);
13618
+ const to = full.indexOf(list[event.currentIndex]);
12863
13619
  if (from < 0 || to < 0) {
12864
13620
  return;
12865
13621
  }
12866
- moveItemInArray(this.allColumns, from, to);
12867
- this.updateVisible();
12868
- this.svc.saveState(this.allColumns);
13622
+ moveItemInArray(full, from, to);
13623
+ this.afterColumnChange(level, full);
13624
+ }
13625
+ /** Refresh and persist after a column visibility/order change, routing to the
13626
+ * top-level or nested-level persistence as appropriate. */
13627
+ afterColumnChange(level, set) {
13628
+ if (level == null) {
13629
+ this.updateVisible();
13630
+ this.svc.saveState(this.allColumns);
13631
+ }
13632
+ else {
13633
+ this.invalidateColumnCache();
13634
+ this.svc.saveChildState(level, set);
13635
+ }
13636
+ }
13637
+ isNestedGroupOpen(id) {
13638
+ return this.nestedChooserOpen.has(id);
13639
+ }
13640
+ toggleNestedGroup(id) {
13641
+ if (this.nestedChooserOpen.has(id)) {
13642
+ this.nestedChooserOpen.delete(id);
13643
+ }
13644
+ else {
13645
+ this.nestedChooserOpen.add(id);
13646
+ }
12869
13647
  }
12870
13648
  // ---------------------------------------------------------------- resize
12871
13649
  startResize(col, event) {
@@ -12876,7 +13654,25 @@ class ListTableComponent {
12876
13654
  event.stopPropagation();
12877
13655
  const th = event.target?.closest('th');
12878
13656
  const startWidth = col.width ?? th?.offsetWidth ?? 150;
12879
- this.resizing = { col, startX: event.clientX, startWidth };
13657
+ this.resizing = { col, startX: event.clientX, startWidth, persist: true };
13658
+ this.setupFitCompensation(col, startWidth, this.visibleColumns, (key) => this.measuredHeaderWidth(key));
13659
+ document.addEventListener('pointermove', this.onPointerMove);
13660
+ document.addEventListener('pointerup', this.onPointerUp);
13661
+ }
13662
+ /** Resize a nested row's column. Measures the sub-header cell's current rendered
13663
+ * width so a flexible (1fr) column converts to a concrete width before dragging;
13664
+ * the grid reflows live because its template reads each column's width. */
13665
+ startChildResize(col, level, event) {
13666
+ if (col.resizable === false) {
13667
+ return;
13668
+ }
13669
+ event.preventDefault();
13670
+ event.stopPropagation();
13671
+ const cell = event.target?.closest('.lt-tree-subhead-cell');
13672
+ const startWidth = col.width ?? Math.round(cell?.getBoundingClientRect().width ?? 150);
13673
+ const grid = cell?.closest('.lt-tree-grid');
13674
+ this.resizing = { col, startX: event.clientX, startWidth, persist: true, level };
13675
+ this.setupFitCompensation(col, startWidth, this.columnsForLevel(level), (key) => this.measuredCellWidth(grid, level, key));
12880
13676
  document.addEventListener('pointermove', this.onPointerMove);
12881
13677
  document.addEventListener('pointerup', this.onPointerUp);
12882
13678
  }
@@ -12884,13 +13680,94 @@ class ListTableComponent {
12884
13680
  if (!this.resizing) {
12885
13681
  return;
12886
13682
  }
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));
13683
+ const r = this.resizing;
13684
+ const delta = event.clientX - r.startX;
13685
+ if (r.sink && r.sinkStart != null) {
13686
+ // fitColumns: the column grows and the sink shrinks by the same amount, so the table stays
13687
+ // the same width and no other column (notably the tree column) shifts. The drag stops once
13688
+ // the column or the sink reaches its minimum.
13689
+ const d = Math.min(r.maxDelta ?? delta, Math.max(r.minDelta ?? delta, delta));
13690
+ r.col.width = Math.round(r.startWidth + d);
13691
+ r.sink.width = Math.round(r.sinkStart - d);
13692
+ }
13693
+ else {
13694
+ const min = r.col.minWidth ?? 40;
13695
+ r.col.width = Math.max(min, Math.round(r.startWidth + delta));
13696
+ }
13697
+ }
13698
+ /** fitColumns: pick the sink column and the range the drag may travel, so the column and the sink
13699
+ * trade width while every other column stays fixed. No-op (plain resize) when fitColumns is off. */
13700
+ setupFitCompensation(col, startWidth, columns, measure) {
13701
+ if (!this.options?.fitColumns || !this.resizing) {
13702
+ return;
13703
+ }
13704
+ const sink = this.pickSink(col, columns);
13705
+ if (!sink) {
13706
+ return;
13707
+ }
13708
+ const FLOOR = 60;
13709
+ // measure() returns 0 when the element isn't found; fall back so a width of 0
13710
+ // never gets frozen in (and later persisted) for the sink or a flex column
13711
+ const sinkStart = sink.width ?? (measure(sink.key) || startWidth);
13712
+ // Freeze any other flexible column to its rendered width so only the column and the sink move
13713
+ // (otherwise the browser would still redistribute slack among the flexible columns). These are
13714
+ // restored to flexible when the drag ends, so one resize doesn't pin the whole table to px.
13715
+ const frozen = [];
13716
+ columns.forEach((c) => {
13717
+ if (c.key !== col.key && c.key !== sink.key && c.width == null) {
13718
+ const w = measure(c.key);
13719
+ if (w > 0) {
13720
+ c.width = w;
13721
+ frozen.push(c);
13722
+ }
13723
+ }
13724
+ });
13725
+ this.resizing.sink = sink;
13726
+ this.resizing.sinkStart = sinkStart;
13727
+ this.resizing.frozen = frozen;
13728
+ this.resizing.maxDelta = Math.max(0, sinkStart - (sink.minWidth ?? FLOOR)); // grow until the sink hits its min
13729
+ this.resizing.minDelta = Math.min(0, (col.minWidth ?? 40) - startWidth); // shrink until the column hits its min
13730
+ }
13731
+ /** The column that absorbs a resize: a flexible column first (it is meant to take up slack), else
13732
+ * the nearest resizable column that is not the tree column, so indentation never shifts. */
13733
+ pickSink(col, columns) {
13734
+ // never pick the tree column (explicit or implicit) as the sink, or resizing
13735
+ // would move the expand chevrons
13736
+ const treeKey = this.treeColumnKeyFor(columns);
13737
+ const usable = columns.filter((c) => c.key !== col.key && c.resizable !== false && c.key !== treeKey);
13738
+ const flex = usable.find((c) => c.width == null);
13739
+ if (flex) {
13740
+ return flex;
13741
+ }
13742
+ const idx = columns.findIndex((c) => c.key === col.key);
13743
+ return usable.find((c) => columns.indexOf(c) > idx) ?? usable[usable.length - 1];
13744
+ }
13745
+ measuredHeaderWidth(key) {
13746
+ const host = this.el?.nativeElement;
13747
+ const th = host?.querySelector(`[id="Header_${key}"]`);
13748
+ return th ? Math.round(th.getBoundingClientRect().width) : 0;
13749
+ }
13750
+ measuredCellWidth(grid, level, key) {
13751
+ const idx = this.columnsForLevel(level).findIndex((c) => c.key === key);
13752
+ const cells = grid?.querySelectorAll('.lt-tree-subhead-cell');
13753
+ const cell = cells ? cells[idx] : undefined;
13754
+ return cell ? Math.round(cell.getBoundingClientRect().width) : 0;
12890
13755
  }
12891
13756
  handleResizeEnd() {
12892
- if (this.resizing) {
12893
- this.svc.saveWidth(this.resizing.col.key, this.resizing.col.width, this.allColumns);
13757
+ const r = this.resizing;
13758
+ // columns frozen only to steady the drag go back to flexible, so the table keeps
13759
+ // filling the viewport and their temporary px widths are not persisted by saveState
13760
+ r?.frozen?.forEach((c) => (c.width = undefined));
13761
+ if (r?.persist) {
13762
+ if (r.level != null) {
13763
+ const set = this._childColumnsByLevel?.[r.level];
13764
+ if (set) {
13765
+ this.svc.saveChildWidth(r.level, r.col.key, r.col.width, set);
13766
+ }
13767
+ }
13768
+ else {
13769
+ this.svc.saveWidth(r.col.key, r.col.width, this.allColumns);
13770
+ }
12894
13771
  }
12895
13772
  this.resizing = undefined;
12896
13773
  this.detachResizeListeners();
@@ -13095,11 +13972,11 @@ class ListTableComponent {
13095
13972
  this.entityTypeLabel = map[this._tableId] || this.entityTypeName || '';
13096
13973
  }
13097
13974
  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"] }] }); }
13975
+ 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", rowHover: "rowHover" }, 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 @if (toolbarControls.search.show) {\n <div class=\"lt-search\" [class.lt-ctl-disabled]=\"toolbarControls.search.disabled\">\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 [disabled]=\"toolbarControls.search.disabled\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n }\n @if (toolbarControls.filters.show && filterableColumns.length) {\n <div class=\"lt-filter-zone\" [class.lt-ctl-disabled]=\"toolbarControls.filters.disabled\" #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 @if (toolbarControls.refresh.show) {\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering || toolbarControls.refresh.disabled\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n }\n @if (toolbarControls.autoRefresh.show) {\n <span class=\"lt-autorefresh-wrap\" [class.lt-ctl-disabled]=\"toolbarControls.autoRefresh.disabled\">\n <lib-list-table-auto-refresh [loading]=\"filtering\" (refresh)=\"refresh()\"></lib-list-table-auto-refresh>\n </span>\n }\n @if (toolbarControls.columns.show) {\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 [disabled]=\"toolbarControls.columns.disabled\"\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 }\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 (mouseenter)=\"onRowHover(row)\"\n (mouseleave)=\"onRowLeave(row)\"\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 (mouseenter)=\"onRowHover(row)\"\n (mouseleave)=\"onRowLeave(row)\"\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 @if (toolbarControls.pageSize.show) {\n <div class=\"lt-rpp\" [class.lt-ctl-disabled]=\"toolbarControls.pageSize.disabled\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering || toolbarControls.pageSize.disabled\"\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 }\n @if (toolbarControls.pagination.show) {\n <div class=\"lt-pager\" [class.lt-ctl-disabled]=\"toolbarControls.pagination.disabled\">\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 }\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-ctl-disabled{opacity:.5;pointer-events:none}.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:18rem;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
13976
  }
13100
13977
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableComponent, decorators: [{
13101
13978
  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"] }]
13979
+ 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 @if (toolbarControls.search.show) {\n <div class=\"lt-search\" [class.lt-ctl-disabled]=\"toolbarControls.search.disabled\">\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 [disabled]=\"toolbarControls.search.disabled\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n }\n @if (toolbarControls.filters.show && filterableColumns.length) {\n <div class=\"lt-filter-zone\" [class.lt-ctl-disabled]=\"toolbarControls.filters.disabled\" #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 @if (toolbarControls.refresh.show) {\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering || toolbarControls.refresh.disabled\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n }\n @if (toolbarControls.autoRefresh.show) {\n <span class=\"lt-autorefresh-wrap\" [class.lt-ctl-disabled]=\"toolbarControls.autoRefresh.disabled\">\n <lib-list-table-auto-refresh [loading]=\"filtering\" (refresh)=\"refresh()\"></lib-list-table-auto-refresh>\n </span>\n }\n @if (toolbarControls.columns.show) {\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 [disabled]=\"toolbarControls.columns.disabled\"\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 }\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 (mouseenter)=\"onRowHover(row)\"\n (mouseleave)=\"onRowLeave(row)\"\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 (mouseenter)=\"onRowHover(row)\"\n (mouseleave)=\"onRowLeave(row)\"\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 @if (toolbarControls.pageSize.show) {\n <div class=\"lt-rpp\" [class.lt-ctl-disabled]=\"toolbarControls.pageSize.disabled\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering || toolbarControls.pageSize.disabled\"\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 }\n @if (toolbarControls.pagination.show) {\n <div class=\"lt-pager\" [class.lt-ctl-disabled]=\"toolbarControls.pagination.disabled\">\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 }\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-ctl-disabled{opacity:.5;pointer-events:none}.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:18rem;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
13980
  }], ctorParameters: () => [{ type: ListTableService }, { type: DataTableFilterService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i1.DomSanitizer }], propDecorators: { tableId: [{
13104
13981
  type: Input
13105
13982
  }], columns: [{
@@ -13110,6 +13987,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13110
13987
  type: Input
13111
13988
  }], options: [{
13112
13989
  type: Input
13990
+ }], loadChildren: [{
13991
+ type: Input
13992
+ }], childColumnsByLevel: [{
13993
+ type: Input
13994
+ }], nodeToggled: [{
13995
+ type: Output
13113
13996
  }], hasNext: [{
13114
13997
  type: Input
13115
13998
  }], hasPrev: [{
@@ -13144,6 +14027,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13144
14027
  type: Output
13145
14028
  }], tableReady: [{
13146
14029
  type: Output
14030
+ }], selectionChanged: [{
14031
+ type: Output
14032
+ }], rowHover: [{
14033
+ type: Output
13147
14034
  }], contextMenu: [{
13148
14035
  type: ViewChild,
13149
14036
  args: ['contextMenu']
@@ -13153,6 +14040,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13153
14040
  }], filtersRow: [{
13154
14041
  type: ViewChild,
13155
14042
  args: ['filtersRow']
14043
+ }], treeVariant: [{
14044
+ type: Input
14045
+ }], showTreeVariantToggle: [{
14046
+ type: Input
14047
+ }], treeRail: [{
14048
+ type: Input
14049
+ }], isFlatTree: [{
14050
+ type: HostBinding,
14051
+ args: ['class.lt-tree-flat']
13156
14052
  }], initialPageSize: [{
13157
14053
  type: Input
13158
14054
  }] } });
@@ -16122,7 +17018,7 @@ class IdentitiesListPageComponent extends ListPageComponent {
16122
17018
  return true;
16123
17019
  }
16124
17020
  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"] }] }); }
17021
+ 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", "rowHover"] }, { 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
17022
  }
16127
17023
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentitiesListPageComponent, decorators: [{
16128
17024
  type: Component,
@@ -25067,12 +25963,12 @@ class EdgeRouterPoliciesPageService extends ListPageServiceClass {
25067
25963
  createdAt: '',
25068
25964
  };
25069
25965
  this.menuItems = [
25070
- { name: 'Edit', action: 'update' },
25071
- { name: 'Delete', action: 'delete' },
25966
+ { 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>' },
25967
+ { 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
25968
  ];
25073
25969
  this.tableHeaderActions = [
25074
- { name: 'Download All', action: 'download-all' },
25075
- { name: 'Download Selected', action: 'download-selected' },
25970
+ { 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>' },
25971
+ { 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
25972
  ];
25077
25973
  this.resourceType = 'edge-router-policies';
25078
25974
  this.validate = (formData) => {
@@ -25310,9 +26206,163 @@ class EdgeRouterPoliciesPageService extends ListPageServiceClass {
25310
26206
  this.ID_COLUMN_DEF
25311
26207
  ];
25312
26208
  }
25313
- getData(filters, sort, page) {
26209
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
26210
+ initListTableColumns() {
26211
+ this.initMenuActions();
26212
+ const routerRolesHeaderComponentParams = {
26213
+ filterType: 'ATTRIBUTE',
26214
+ enableSorting: true,
26215
+ getRoleAttributes: () => this.routerRoleAttributes,
26216
+ getNamedAttributes: () => this.routerNamedAttributes,
26217
+ getSelectedRoleAttributes: () => this.selectedRouterRoleAttributes,
26218
+ getSelectedNamedAttributes: () => this.selectedRouterNamedAttributes,
26219
+ setSelectedRoleAttributes: (attributes) => {
26220
+ this.selectedRouterRoleAttributes = attributes;
26221
+ },
26222
+ setSelectedNamedAttributes: (attributes) => {
26223
+ this.selectedRouterNamedAttributes = attributes;
26224
+ },
26225
+ getNamedAttributesMap: () => this.routerNamedAttributesMap,
26226
+ };
26227
+ const identityRolesHeaderComponentParams = {
26228
+ filterType: 'ATTRIBUTE',
26229
+ enableSorting: true,
26230
+ getRoleAttributes: () => this.identityRoleAttributes,
26231
+ getNamedAttributes: () => this.identityNamedAttributes,
26232
+ getSelectedRoleAttributes: () => this.selectedIdentityRoleAttributes,
26233
+ getSelectedNamedAttributes: () => this.selectedIdentityNamedAttributes,
26234
+ setSelectedRoleAttributes: (attributes) => {
26235
+ this.selectedIdentityRoleAttributes = attributes;
26236
+ },
26237
+ setSelectedNamedAttributes: (attributes) => {
26238
+ this.selectedIdentityNamedAttributes = attributes;
26239
+ },
26240
+ getNamedAttributesMap: () => this.identityNamedAttributesMap,
26241
+ };
26242
+ // @ marks a named attribute, # a role attribute (matches the tag selector)
26243
+ const rolesChipRenderer = (row) => {
26244
+ const colId = row?.column?.colId;
26245
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
26246
+ return attrs.map((attr) => {
26247
+ const raw = String(attr?.name ?? attr ?? '').trim();
26248
+ if (!raw) {
26249
+ return '';
26250
+ }
26251
+ const isNamed = raw.charAt(0) === '@';
26252
+ const label = isNamed || raw.charAt(0) === '#' ? raw : '#' + raw;
26253
+ return `<span class="${isNamed ? 'lt-chip lt-chip-at' : 'lt-chip'}">${label}</span>`;
26254
+ }).join('');
26255
+ };
26256
+ const semanticFilterOptions = [
26257
+ { label: 'Show All', value: '' },
26258
+ { label: 'Any Of', value: 'AnyOf' },
26259
+ { label: 'All Of', value: 'AllOf' },
26260
+ ];
26261
+ const semanticRenderer = (row) => row?.data?.semantic === 'AnyOf' ? 'Any Of' : 'All Of';
26262
+ const openForm = (ctx) => {
26263
+ if (this.hasSelectedText()) {
26264
+ return;
26265
+ }
26266
+ this.serviceType = 'advanced';
26267
+ this.openEditForm(ctx.row?.id);
26268
+ };
26269
+ let tableColumns = [
26270
+ {
26271
+ key: 'name',
26272
+ field: 'name',
26273
+ label: 'Name',
26274
+ filterType: 'TEXTINPUT',
26275
+ cell: NameCellComponent,
26276
+ cellInputs: (ctx) => ({
26277
+ row: ctx.row,
26278
+ cellValue: ctx.value,
26279
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
26280
+ }),
26281
+ onCellClick: openForm,
26282
+ resizable: true,
26283
+ cellClass: 'nf-cell-vert-align',
26284
+ sortable: true,
26285
+ onSort: this.sort.bind(this),
26286
+ sortDir: 'asc',
26287
+ width: 300,
26288
+ },
26289
+ {
26290
+ key: 'edgeRouterRoles',
26291
+ field: 'edgeRouterRoles',
26292
+ label: 'Router Attributes',
26293
+ filterType: 'ATTRIBUTE',
26294
+ headerParams: routerRolesHeaderComponentParams,
26295
+ cellHtml: rolesChipRenderer,
26296
+ onCellClick: openForm,
26297
+ resizable: true,
26298
+ cellClass: 'nf-cell-vert-align',
26299
+ sortable: false,
26300
+ },
26301
+ {
26302
+ key: 'identityRoles',
26303
+ field: 'identityRoles',
26304
+ label: 'Identity Attributes',
26305
+ filterType: 'ATTRIBUTE',
26306
+ headerParams: identityRolesHeaderComponentParams,
26307
+ cellHtml: rolesChipRenderer,
26308
+ onCellClick: openForm,
26309
+ resizable: true,
26310
+ cellClass: 'nf-cell-vert-align',
26311
+ sortable: false,
26312
+ },
26313
+ {
26314
+ key: 'semantic',
26315
+ field: 'semantic',
26316
+ label: 'Semantic',
26317
+ filterType: 'SELECT',
26318
+ filterPlaceholder: 'Any semantic',
26319
+ filterOptions: semanticFilterOptions,
26320
+ cellHtml: semanticRenderer,
26321
+ onCellClick: openForm,
26322
+ resizable: true,
26323
+ sortable: true,
26324
+ onSort: this.sort.bind(this),
26325
+ cellClass: 'nf-cell-vert-align',
26326
+ width: 120,
26327
+ },
26328
+ {
26329
+ key: 'createdAt',
26330
+ field: 'createdAt',
26331
+ label: 'Created At',
26332
+ valueFormatter: this.createdAtFormatter,
26333
+ filterType: 'DATETIME',
26334
+ filterPlaceholder: 'Any time',
26335
+ onCellClick: openForm,
26336
+ resizable: true,
26337
+ sortable: true,
26338
+ onSort: this.sort.bind(this),
26339
+ cellClass: 'nf-cell-vert-align',
26340
+ hidden: true,
26341
+ },
26342
+ {
26343
+ key: 'id',
26344
+ field: 'id',
26345
+ label: 'ID',
26346
+ filterType: 'TEXTINPUT',
26347
+ onCellClick: openForm,
26348
+ resizable: true,
26349
+ sortable: true,
26350
+ onSort: this.sort.bind(this),
26351
+ cellClass: 'nf-cell-vert-align',
26352
+ hidden: true,
26353
+ },
26354
+ ];
26355
+ if (this.extService?.processListTableColumns) {
26356
+ tableColumns = this.extService.processListTableColumns(tableColumns);
26357
+ }
26358
+ return tableColumns;
26359
+ }
26360
+ getData(filters, sort, page, pageSize) {
25314
26361
  // we can customize filters or sorting here before moving on...
25315
26362
  this.paging.page = page || this.paging.page;
26363
+ if (pageSize) {
26364
+ this.paging.total = pageSize;
26365
+ }
25316
26366
  return super.getTableData('edge-router-policies', this.paging, filters, sort)
25317
26367
  .then((results) => {
25318
26368
  return this.processData(results);
@@ -25536,6 +26586,135 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
25536
26586
  args: [SHAREDZ_EXTENSION]
25537
26587
  }] }] });
25538
26588
 
26589
+ /*
26590
+ Copyright NetFoundry Inc.
26591
+
26592
+ Licensed under the Apache License, Version 2.0 (the "License");
26593
+ you may not use this file except in compliance with the License.
26594
+ You may obtain a copy of the License at
26595
+
26596
+ https://www.apache.org/licenses/LICENSE-2.0
26597
+
26598
+ Unless required by applicable law or agreed to in writing, software
26599
+ distributed under the License is distributed on an "AS IS" BASIS,
26600
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26601
+ See the License for the specific language governing permissions and
26602
+ limitations under the License.
26603
+ */
26604
+ // Edge Router Policies page on `lib-list-table`, selected by the route when Beta Features is on.
26605
+ class EdgeRouterPoliciesListPageComponent extends ListPageComponent {
26606
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
26607
+ super(filterService, svc, consoleEvents, dialogForm, extService);
26608
+ this.svc = svc;
26609
+ this.tabNames = tabNames;
26610
+ this.extService = extService;
26611
+ this.title = 'Edge Router Policies';
26612
+ this.isLoading = false;
26613
+ this.formDataChanged = false;
26614
+ this.tableId = 'edge-router-policies';
26615
+ this.initialPageSize = 50;
26616
+ }
26617
+ loadTableColumns() {
26618
+ return this.svc.initListTableColumns();
26619
+ }
26620
+ ngOnInit() {
26621
+ this.tabs = this.tabNames.getTabs('policies');
26622
+ this.svc.refreshData = this.refreshData;
26623
+ this.getRoleAttributes();
26624
+ super.ngOnInit();
26625
+ }
26626
+ headerActionClicked(action) {
26627
+ switch (action) {
26628
+ case 'add':
26629
+ this.svc.serviceType = '';
26630
+ this.svc.openEditForm();
26631
+ break;
26632
+ case 'edit':
26633
+ this.svc.openEditForm();
26634
+ break;
26635
+ case 'delete':
26636
+ const selectedItems = this.rowData.filter((row) => {
26637
+ return row.selected;
26638
+ });
26639
+ const label = selectedItems.length > 1 ? 'edge router policies' : 'edge router policy';
26640
+ this.openBulkDelete(selectedItems, label);
26641
+ break;
26642
+ default:
26643
+ }
26644
+ }
26645
+ tableAction(event) {
26646
+ this.trackMenuAction(event?.action, event?.item);
26647
+ switch (event?.action) {
26648
+ case 'toggleAll':
26649
+ case 'toggleItem':
26650
+ this.itemToggled(event.item);
26651
+ this.rowData.forEach((item) => {
26652
+ if (item.isSystem) {
26653
+ item.selected = false;
26654
+ }
26655
+ });
26656
+ break;
26657
+ case 'update':
26658
+ this.svc.serviceType = 'advanced';
26659
+ this.svc.openEditForm(event.item?.id);
26660
+ break;
26661
+ case 'create':
26662
+ this.svc.openEditForm();
26663
+ break;
26664
+ case 'delete':
26665
+ this.deleteItem(event.item);
26666
+ break;
26667
+ case 'download-all':
26668
+ this.downloadAllItems();
26669
+ break;
26670
+ case 'download-selected':
26671
+ this.svc.downloadItems(this.selectedItems);
26672
+ break;
26673
+ default:
26674
+ break;
26675
+ }
26676
+ }
26677
+ trackMenuAction(action, item) {
26678
+ this.extService?.consoleActionTriggered?.emit({
26679
+ action,
26680
+ page: 'edge-router-policy',
26681
+ item,
26682
+ type: 'menu-action'
26683
+ });
26684
+ }
26685
+ getRoleAttributes() {
26686
+ this.svc.getEdgeRouterRoleAttributes();
26687
+ this.svc.getIdentityRoleAttributes();
26688
+ this.svc.getServiceNamedAttributes();
26689
+ this.svc.getIdentityNamedAttributes();
26690
+ this.svc.getPostureNamedAttributes();
26691
+ }
26692
+ deleteItem(item) {
26693
+ this.openBulkDelete([item], 'edge-router-policies');
26694
+ }
26695
+ closeModal(event) {
26696
+ this.svc.sideModalOpen = false;
26697
+ if (event?.refresh) {
26698
+ this.refreshData();
26699
+ }
26700
+ }
26701
+ dataChanged(event) {
26702
+ this.formDataChanged = event;
26703
+ }
26704
+ ngOnDestroy() {
26705
+ super.ngOnDestroy();
26706
+ }
26707
+ 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 }); }
26708
+ 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", "rowHover"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
26709
+ }
26710
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EdgeRouterPoliciesListPageComponent, decorators: [{
26711
+ type: Component,
26712
+ 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" }]
26713
+ }], ctorParameters: () => [{ type: EdgeRouterPoliciesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
26714
+ type: Inject,
26715
+ args: [SHAREDZ_EXTENSION]
26716
+ }] }] });
26717
+
25539
26718
  /*
25540
26719
  Copyright NetFoundry Inc.
25541
26720
 
@@ -25584,13 +26763,14 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25584
26763
  os: '',
25585
26764
  createdAt: '',
25586
26765
  };
26766
+ // `icon` is rendered by the new list-table's menus; the legacy ag-grid menu ignores it.
25587
26767
  this.menuItems = [
25588
- { name: 'Edit', action: 'update' },
25589
- { name: 'Delete', action: 'delete' },
26768
+ { 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>' },
26769
+ { 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
26770
  ];
25591
26771
  this.tableHeaderActions = [
25592
- { name: 'Download All', action: 'download-all' },
25593
- { name: 'Download Selected', action: 'download-selected' },
26772
+ { 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>' },
26773
+ { 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
26774
  ];
25595
26775
  this.resourceType = 'service-policies';
25596
26776
  this.validate = (formData) => {
@@ -25607,6 +26787,7 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25607
26787
  identityFilter = true;
25608
26788
  break;
25609
26789
  case 'postureRoles':
26790
+ case 'postureCheckRoles':
25610
26791
  postureFilter = true;
25611
26792
  break;
25612
26793
  }
@@ -25619,6 +26800,10 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25619
26800
  this.selectedIdentityRoleAttributes = [];
25620
26801
  this.selectedIdentityNamedAttributes = [];
25621
26802
  }
26803
+ if (!postureFilter) {
26804
+ this.selectedPostureRoleAttributes = [];
26805
+ this.selectedPostureNamedAttributes = [];
26806
+ }
25622
26807
  });
25623
26808
  }
25624
26809
  initTableColumns() {
@@ -25872,9 +27057,209 @@ class ServicePoliciesPageService extends ListPageServiceClass {
25872
27057
  }
25873
27058
  return tableColumns;
25874
27059
  }
25875
- getData(filters, sort, page) {
27060
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
27061
+ initListTableColumns() {
27062
+ this.initMenuActions();
27063
+ const serviceRolesHeaderComponentParams = {
27064
+ filterType: 'ATTRIBUTE',
27065
+ enableSorting: true,
27066
+ getRoleAttributes: () => this.serviceRoleAttributes,
27067
+ getNamedAttributes: () => this.serviceNamedAttributes,
27068
+ getSelectedRoleAttributes: () => this.selectedServiceRoleAttributes,
27069
+ getSelectedNamedAttributes: () => this.selectedServiceNamedAttributes,
27070
+ setSelectedRoleAttributes: (attributes) => {
27071
+ this.selectedServiceRoleAttributes = attributes;
27072
+ },
27073
+ setSelectedNamedAttributes: (attributes) => {
27074
+ this.selectedServiceNamedAttributes = attributes;
27075
+ },
27076
+ getNamedAttributesMap: () => this.serviceNamedAttributesMap,
27077
+ };
27078
+ const identityRolesHeaderComponentParams = {
27079
+ filterType: 'ATTRIBUTE',
27080
+ enableSorting: true,
27081
+ getRoleAttributes: () => this.identityRoleAttributes,
27082
+ getNamedAttributes: () => this.identityNamedAttributes,
27083
+ getSelectedRoleAttributes: () => this.selectedIdentityRoleAttributes,
27084
+ getSelectedNamedAttributes: () => this.selectedIdentityNamedAttributes,
27085
+ setSelectedRoleAttributes: (attributes) => {
27086
+ this.selectedIdentityRoleAttributes = attributes;
27087
+ },
27088
+ setSelectedNamedAttributes: (attributes) => {
27089
+ this.selectedIdentityNamedAttributes = attributes;
27090
+ },
27091
+ getNamedAttributesMap: () => this.identityNamedAttributesMap,
27092
+ };
27093
+ const postureRolesHeaderComponentParams = {
27094
+ filterType: 'ATTRIBUTE',
27095
+ enableSorting: true,
27096
+ getRoleAttributes: () => this.postureRoleAttributes,
27097
+ getNamedAttributes: () => this.postureNamedAttributes,
27098
+ getSelectedRoleAttributes: () => this.selectedPostureRoleAttributes,
27099
+ getSelectedNamedAttributes: () => this.selectedPostureNamedAttributes,
27100
+ setSelectedRoleAttributes: (attributes) => {
27101
+ this.selectedPostureRoleAttributes = attributes;
27102
+ },
27103
+ setSelectedNamedAttributes: (attributes) => {
27104
+ this.selectedPostureNamedAttributes = attributes;
27105
+ },
27106
+ getNamedAttributesMap: () => this.postureNamedAttributesMap,
27107
+ };
27108
+ // @ marks a named attribute, # a role attribute (matches the tag selector)
27109
+ const rolesChipRenderer = (row) => {
27110
+ const colId = row?.column?.colId;
27111
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
27112
+ return attrs.map((attr) => {
27113
+ const raw = String(attr?.name ?? attr ?? '').trim();
27114
+ if (!raw) {
27115
+ return '';
27116
+ }
27117
+ const isNamed = raw.charAt(0) === '@';
27118
+ const label = isNamed || raw.charAt(0) === '#' ? raw : '#' + raw;
27119
+ return `<span class="${isNamed ? 'lt-chip lt-chip-at' : 'lt-chip'}">${label}</span>`;
27120
+ }).join('');
27121
+ };
27122
+ const semanticFilterOptions = [
27123
+ { label: 'Show All', value: '' },
27124
+ { label: 'Any Of', value: 'AnyOf' },
27125
+ { label: 'All Of', value: 'AllOf' },
27126
+ ];
27127
+ const semanticRenderer = (row) => row?.data?.semantic === 'AnyOf' ? 'Any Of' : 'All Of';
27128
+ const openForm = (ctx) => {
27129
+ if (this.hasSelectedText()) {
27130
+ return;
27131
+ }
27132
+ this.serviceType = 'advanced';
27133
+ this.openEditForm(ctx.row?.id);
27134
+ };
27135
+ let tableColumns = [
27136
+ {
27137
+ key: 'name',
27138
+ field: 'name',
27139
+ label: 'Name',
27140
+ filterType: 'TEXTINPUT',
27141
+ cell: NameCellComponent,
27142
+ cellInputs: (ctx) => ({
27143
+ row: ctx.row,
27144
+ cellValue: ctx.value,
27145
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
27146
+ }),
27147
+ onCellClick: openForm,
27148
+ resizable: true,
27149
+ cellClass: 'nf-cell-vert-align',
27150
+ sortable: true,
27151
+ onSort: this.sort.bind(this),
27152
+ sortDir: 'asc',
27153
+ width: 300,
27154
+ },
27155
+ {
27156
+ key: 'serviceRoles',
27157
+ field: 'serviceRoles',
27158
+ label: 'Service Attributes',
27159
+ filterType: 'ATTRIBUTE',
27160
+ headerParams: serviceRolesHeaderComponentParams,
27161
+ cellHtml: rolesChipRenderer,
27162
+ onCellClick: openForm,
27163
+ resizable: true,
27164
+ cellClass: 'nf-cell-vert-align',
27165
+ sortable: false,
27166
+ },
27167
+ {
27168
+ key: 'identityRoles',
27169
+ field: 'identityRoles',
27170
+ label: 'Identity Attributes',
27171
+ filterType: 'ATTRIBUTE',
27172
+ headerParams: identityRolesHeaderComponentParams,
27173
+ cellHtml: rolesChipRenderer,
27174
+ onCellClick: openForm,
27175
+ resizable: true,
27176
+ cellClass: 'nf-cell-vert-align',
27177
+ sortable: false,
27178
+ },
27179
+ {
27180
+ key: 'postureCheckRoles',
27181
+ field: 'postureCheckRoles',
27182
+ label: 'Posture Check Attributes',
27183
+ filterType: 'ATTRIBUTE',
27184
+ headerParams: postureRolesHeaderComponentParams,
27185
+ cellHtml: rolesChipRenderer,
27186
+ onCellClick: openForm,
27187
+ resizable: true,
27188
+ cellClass: 'nf-cell-vert-align',
27189
+ sortable: false,
27190
+ },
27191
+ {
27192
+ key: 'semantic',
27193
+ field: 'semantic',
27194
+ label: 'Semantic',
27195
+ filterType: 'SELECT',
27196
+ filterPlaceholder: 'Any semantic',
27197
+ filterOptions: semanticFilterOptions,
27198
+ cellHtml: semanticRenderer,
27199
+ onCellClick: openForm,
27200
+ resizable: true,
27201
+ sortable: true,
27202
+ onSort: this.sort.bind(this),
27203
+ cellClass: 'nf-cell-vert-align',
27204
+ width: 120,
27205
+ },
27206
+ {
27207
+ key: 'type',
27208
+ field: 'type',
27209
+ label: 'Type',
27210
+ filterType: 'SELECT',
27211
+ filterPlaceholder: 'Any type',
27212
+ filterOptions: [
27213
+ { label: 'Show All', value: '' },
27214
+ { label: 'Dial', value: 'Dial' },
27215
+ { label: 'Bind', value: 'Bind' },
27216
+ ],
27217
+ cellHtml: (row) => row?.data?.type ?? '',
27218
+ onCellClick: openForm,
27219
+ resizable: true,
27220
+ sortable: true,
27221
+ onSort: this.sort.bind(this),
27222
+ cellClass: 'nf-cell-vert-align',
27223
+ width: 100,
27224
+ },
27225
+ {
27226
+ key: 'createdAt',
27227
+ field: 'createdAt',
27228
+ label: 'Created At',
27229
+ valueFormatter: this.createdAtFormatter,
27230
+ filterType: 'DATETIME',
27231
+ filterPlaceholder: 'Any time',
27232
+ onCellClick: openForm,
27233
+ resizable: true,
27234
+ sortable: true,
27235
+ onSort: this.sort.bind(this),
27236
+ cellClass: 'nf-cell-vert-align',
27237
+ hidden: true,
27238
+ },
27239
+ {
27240
+ key: 'id',
27241
+ field: 'id',
27242
+ label: 'ID',
27243
+ filterType: 'TEXTINPUT',
27244
+ onCellClick: openForm,
27245
+ resizable: true,
27246
+ sortable: true,
27247
+ onSort: this.sort.bind(this),
27248
+ cellClass: 'nf-cell-vert-align',
27249
+ hidden: true,
27250
+ },
27251
+ ];
27252
+ if (this.extService?.processListTableColumns) {
27253
+ tableColumns = this.extService.processListTableColumns(tableColumns);
27254
+ }
27255
+ return tableColumns;
27256
+ }
27257
+ getData(filters, sort, page, pageSize) {
25876
27258
  // we can customize filters or sorting here before moving on...
25877
27259
  this.paging.page = page || this.paging.page;
27260
+ if (pageSize) {
27261
+ this.paging.total = pageSize;
27262
+ }
25878
27263
  return super.getTableData('service-policies', this.paging, filters, sort)
25879
27264
  .then((results) => {
25880
27265
  return this.processData(results);
@@ -26113,6 +27498,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
26113
27498
  args: [SERVICE_POLICY_EXTENSION_SERVICE]
26114
27499
  }] }] });
26115
27500
 
27501
+ /*
27502
+ Copyright NetFoundry Inc.
27503
+
27504
+ Licensed under the Apache License, Version 2.0 (the "License");
27505
+ you may not use this file except in compliance with the License.
27506
+ You may obtain a copy of the License at
27507
+
27508
+ https://www.apache.org/licenses/LICENSE-2.0
27509
+
27510
+ Unless required by applicable law or agreed to in writing, software
27511
+ distributed under the License is distributed on an "AS IS" BASIS,
27512
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27513
+ See the License for the specific language governing permissions and
27514
+ limitations under the License.
27515
+ */
27516
+ // Service Policies page on `lib-list-table`, selected by the route when Beta Features is on.
27517
+ class ServicePoliciesListPageComponent extends ListPageComponent {
27518
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
27519
+ super(filterService, svc, consoleEvents, dialogForm, extService);
27520
+ this.svc = svc;
27521
+ this.tabNames = tabNames;
27522
+ this.extService = extService;
27523
+ this.title = 'Service Policies';
27524
+ this.isLoading = false;
27525
+ this.formDataChanged = false;
27526
+ this.tableId = 'service-policies';
27527
+ this.initialPageSize = 50;
27528
+ }
27529
+ loadTableColumns() {
27530
+ return this.svc.initListTableColumns();
27531
+ }
27532
+ ngOnInit() {
27533
+ this.tabs = this.tabNames.getTabs('policies');
27534
+ this.svc.refreshData = this.refreshData;
27535
+ this.getRoleAttributes();
27536
+ super.ngOnInit();
27537
+ }
27538
+ headerActionClicked(action) {
27539
+ switch (action) {
27540
+ case 'add':
27541
+ this.svc.serviceType = '';
27542
+ this.svc.openEditForm();
27543
+ break;
27544
+ case 'edit':
27545
+ this.svc.openEditForm();
27546
+ break;
27547
+ case 'delete':
27548
+ const selectedItems = this.rowData.filter((row) => {
27549
+ return row.selected;
27550
+ });
27551
+ const label = selectedItems.length > 1 ? 'service policies' : 'service policy';
27552
+ this.openBulkDelete(selectedItems, label);
27553
+ break;
27554
+ default:
27555
+ }
27556
+ }
27557
+ tableAction(event) {
27558
+ const extensionActionFound = this.handleExtensionActions(event);
27559
+ this.trackMenuAction(event?.action, event?.item);
27560
+ if (extensionActionFound) {
27561
+ return;
27562
+ }
27563
+ switch (event?.action) {
27564
+ case 'toggleAll':
27565
+ case 'toggleItem':
27566
+ this.itemToggled(event.item);
27567
+ break;
27568
+ case 'update':
27569
+ this.svc.serviceType = 'advanced';
27570
+ this.svc.openEditForm(event.item.id);
27571
+ break;
27572
+ case 'create':
27573
+ this.svc.openEditForm();
27574
+ break;
27575
+ case 'delete':
27576
+ this.deleteItem(event.item);
27577
+ break;
27578
+ case 'download-all':
27579
+ this.downloadAllItems();
27580
+ break;
27581
+ case 'download-selected':
27582
+ this.svc.downloadItems(this.selectedItems);
27583
+ break;
27584
+ default:
27585
+ break;
27586
+ }
27587
+ }
27588
+ trackMenuAction(action, item) {
27589
+ this.extService?.consoleActionTriggered?.emit({
27590
+ action,
27591
+ page: 'service-policy',
27592
+ item,
27593
+ type: 'menu-action'
27594
+ });
27595
+ }
27596
+ getRoleAttributes() {
27597
+ this.svc.getServiceRoleAttributes();
27598
+ this.svc.getIdentityRoleAttributes();
27599
+ this.svc.getServiceNamedAttributes();
27600
+ this.svc.getIdentityNamedAttributes();
27601
+ this.svc.getPostureNamedAttributes();
27602
+ }
27603
+ deleteItem(item) {
27604
+ this.openBulkDelete([item], 'service-policies');
27605
+ }
27606
+ closeModal(event) {
27607
+ this.svc.sideModalOpen = false;
27608
+ if (event?.refresh) {
27609
+ this.refreshData();
27610
+ }
27611
+ }
27612
+ dataChanged(event) {
27613
+ this.formDataChanged = event;
27614
+ }
27615
+ ngOnDestroy() {
27616
+ super.ngOnDestroy();
27617
+ }
27618
+ 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 }); }
27619
+ 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", "rowHover"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
27620
+ }
27621
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ServicePoliciesListPageComponent, decorators: [{
27622
+ type: Component,
27623
+ 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" }]
27624
+ }], ctorParameters: () => [{ type: ServicePoliciesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
27625
+ type: Inject,
27626
+ args: [SERVICE_POLICY_EXTENSION_SERVICE]
27627
+ }] }] });
27628
+
26116
27629
  class ServicePolicyFormComponent extends ProjectableForm {
26117
27630
  constructor(settingsService, svc, zitiService, growlerService, extService, router, route, location, dialogForm) {
26118
27631
  super(growlerService, extService, zitiService, router, route, location, settingsService, dialogForm);
@@ -26985,12 +28498,12 @@ class ServiceEdgeRouterPoliciesPageService extends ListPageServiceClass {
26985
28498
  createdAt: '',
26986
28499
  };
26987
28500
  this.menuItems = [
26988
- { name: 'Edit', action: 'update' },
26989
- { name: 'Delete', action: 'delete' },
28501
+ { 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>' },
28502
+ { 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
28503
  ];
26991
28504
  this.tableHeaderActions = [
26992
- { name: 'Download All', action: 'download-all' },
26993
- { name: 'Download Selected', action: 'download-selected' },
28505
+ { 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>' },
28506
+ { 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
28507
  ];
26995
28508
  this.resourceType = 'service-edge-router-policies';
26996
28509
  this.validate = (formData) => {
@@ -27203,9 +28716,163 @@ class ServiceEdgeRouterPoliciesPageService extends ListPageServiceClass {
27203
28716
  this.ID_COLUMN_DEF
27204
28717
  ];
27205
28718
  }
27206
- getData(filters, sort, page) {
28719
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
28720
+ initListTableColumns() {
28721
+ this.initMenuActions();
28722
+ const serviceRolesHeaderComponentParams = {
28723
+ filterType: 'ATTRIBUTE',
28724
+ enableSorting: true,
28725
+ getRoleAttributes: () => this.serviceRoleAttributes,
28726
+ getNamedAttributes: () => this.serviceNamedAttributes,
28727
+ getSelectedRoleAttributes: () => this.selectedServiceRoleAttributes,
28728
+ getSelectedNamedAttributes: () => this.selectedServiceNamedAttributes,
28729
+ setSelectedRoleAttributes: (attributes) => {
28730
+ this.selectedServiceRoleAttributes = attributes;
28731
+ },
28732
+ setSelectedNamedAttributes: (attributes) => {
28733
+ this.selectedServiceNamedAttributes = attributes;
28734
+ },
28735
+ getNamedAttributesMap: () => this.serviceNamedAttributesMap,
28736
+ };
28737
+ const routerRolesHeaderComponentParams = {
28738
+ filterType: 'ATTRIBUTE',
28739
+ enableSorting: true,
28740
+ getRoleAttributes: () => this.routerRoleAttributes,
28741
+ getNamedAttributes: () => this.routerNamedAttributes,
28742
+ getSelectedRoleAttributes: () => this.selectedRouterRoleAttributes,
28743
+ getSelectedNamedAttributes: () => this.selectedRouterNamedAttributes,
28744
+ setSelectedRoleAttributes: (attributes) => {
28745
+ this.selectedRouterRoleAttributes = attributes;
28746
+ },
28747
+ setSelectedNamedAttributes: (attributes) => {
28748
+ this.selectedRouterNamedAttributes = attributes;
28749
+ },
28750
+ getNamedAttributesMap: () => this.routerNamedAttributesMap,
28751
+ };
28752
+ // @ marks a named attribute, # a role attribute (matches the tag selector)
28753
+ const rolesChipRenderer = (row) => {
28754
+ const colId = row?.column?.colId;
28755
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
28756
+ return attrs.map((attr) => {
28757
+ const raw = String(attr?.name ?? attr ?? '').trim();
28758
+ if (!raw) {
28759
+ return '';
28760
+ }
28761
+ const isNamed = raw.charAt(0) === '@';
28762
+ const label = isNamed || raw.charAt(0) === '#' ? raw : '#' + raw;
28763
+ return `<span class="${isNamed ? 'lt-chip lt-chip-at' : 'lt-chip'}">${label}</span>`;
28764
+ }).join('');
28765
+ };
28766
+ const semanticFilterOptions = [
28767
+ { label: 'Show All', value: '' },
28768
+ { label: 'Any Of', value: 'AnyOf' },
28769
+ { label: 'All Of', value: 'AllOf' },
28770
+ ];
28771
+ const semanticRenderer = (row) => row?.data?.semantic === 'AnyOf' ? 'Any Of' : 'All Of';
28772
+ const openForm = (ctx) => {
28773
+ if (this.hasSelectedText()) {
28774
+ return;
28775
+ }
28776
+ this.serviceType = 'advanced';
28777
+ this.openEditForm(ctx.row?.id);
28778
+ };
28779
+ let tableColumns = [
28780
+ {
28781
+ key: 'name',
28782
+ field: 'name',
28783
+ label: 'Name',
28784
+ filterType: 'TEXTINPUT',
28785
+ cell: NameCellComponent,
28786
+ cellInputs: (ctx) => ({
28787
+ row: ctx.row,
28788
+ cellValue: ctx.value,
28789
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
28790
+ }),
28791
+ onCellClick: openForm,
28792
+ resizable: true,
28793
+ cellClass: 'nf-cell-vert-align',
28794
+ sortable: true,
28795
+ onSort: this.sort.bind(this),
28796
+ sortDir: 'asc',
28797
+ width: 300,
28798
+ },
28799
+ {
28800
+ key: 'serviceRoles',
28801
+ field: 'serviceRoles',
28802
+ label: 'Service Attributes',
28803
+ filterType: 'ATTRIBUTE',
28804
+ headerParams: serviceRolesHeaderComponentParams,
28805
+ cellHtml: rolesChipRenderer,
28806
+ onCellClick: openForm,
28807
+ resizable: true,
28808
+ cellClass: 'nf-cell-vert-align',
28809
+ sortable: false,
28810
+ },
28811
+ {
28812
+ key: 'edgeRouterRoles',
28813
+ field: 'edgeRouterRoles',
28814
+ label: 'Router Attributes',
28815
+ filterType: 'ATTRIBUTE',
28816
+ headerParams: routerRolesHeaderComponentParams,
28817
+ cellHtml: rolesChipRenderer,
28818
+ onCellClick: openForm,
28819
+ resizable: true,
28820
+ cellClass: 'nf-cell-vert-align',
28821
+ sortable: false,
28822
+ },
28823
+ {
28824
+ key: 'semantic',
28825
+ field: 'semantic',
28826
+ label: 'Semantic',
28827
+ filterType: 'SELECT',
28828
+ filterPlaceholder: 'Any semantic',
28829
+ filterOptions: semanticFilterOptions,
28830
+ cellHtml: semanticRenderer,
28831
+ onCellClick: openForm,
28832
+ resizable: true,
28833
+ sortable: true,
28834
+ onSort: this.sort.bind(this),
28835
+ cellClass: 'nf-cell-vert-align',
28836
+ width: 120,
28837
+ },
28838
+ {
28839
+ key: 'createdAt',
28840
+ field: 'createdAt',
28841
+ label: 'Created At',
28842
+ valueFormatter: this.createdAtFormatter,
28843
+ filterType: 'DATETIME',
28844
+ filterPlaceholder: 'Any time',
28845
+ onCellClick: openForm,
28846
+ resizable: true,
28847
+ sortable: true,
28848
+ onSort: this.sort.bind(this),
28849
+ cellClass: 'nf-cell-vert-align',
28850
+ hidden: true,
28851
+ },
28852
+ {
28853
+ key: 'id',
28854
+ field: 'id',
28855
+ label: 'ID',
28856
+ filterType: 'TEXTINPUT',
28857
+ onCellClick: openForm,
28858
+ resizable: true,
28859
+ sortable: true,
28860
+ onSort: this.sort.bind(this),
28861
+ cellClass: 'nf-cell-vert-align',
28862
+ hidden: true,
28863
+ },
28864
+ ];
28865
+ if (this.extService?.processListTableColumns) {
28866
+ tableColumns = this.extService.processListTableColumns(tableColumns);
28867
+ }
28868
+ return tableColumns;
28869
+ }
28870
+ getData(filters, sort, page, pageSize) {
27207
28871
  // we can customize filters or sorting here before moving on...
27208
28872
  this.paging.page = page || this.paging.page;
28873
+ if (pageSize) {
28874
+ this.paging.total = pageSize;
28875
+ }
27209
28876
  return super.getTableData('service-edge-router-policies', this.paging, filters, sort)
27210
28877
  .then((results) => {
27211
28878
  return this.processData(results);
@@ -27413,6 +29080,129 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
27413
29080
  args: [SHAREDZ_EXTENSION]
27414
29081
  }] }] });
27415
29082
 
29083
+ /*
29084
+ Copyright NetFoundry Inc.
29085
+
29086
+ Licensed under the Apache License, Version 2.0 (the "License");
29087
+ you may not use this file except in compliance with the License.
29088
+ You may obtain a copy of the License at
29089
+
29090
+ https://www.apache.org/licenses/LICENSE-2.0
29091
+
29092
+ Unless required by applicable law or agreed to in writing, software
29093
+ distributed under the License is distributed on an "AS IS" BASIS,
29094
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29095
+ See the License for the specific language governing permissions and
29096
+ limitations under the License.
29097
+ */
29098
+ // Service Edge Router Policies page on `lib-list-table`, selected by the route when Beta Features is on.
29099
+ class ServiceEdgeRouterPoliciesListPageComponent extends ListPageComponent {
29100
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
29101
+ super(filterService, svc, consoleEvents, dialogForm, extService);
29102
+ this.svc = svc;
29103
+ this.tabNames = tabNames;
29104
+ this.extService = extService;
29105
+ this.title = 'Service Edge Router Policies';
29106
+ this.isLoading = false;
29107
+ this.formDataChanged = false;
29108
+ this.tableId = 'service-edge-router-policies';
29109
+ this.initialPageSize = 50;
29110
+ }
29111
+ loadTableColumns() {
29112
+ return this.svc.initListTableColumns();
29113
+ }
29114
+ ngOnInit() {
29115
+ this.tabs = this.tabNames.getTabs('policies');
29116
+ this.svc.refreshData = this.refreshData;
29117
+ this.getRoleAttributes();
29118
+ super.ngOnInit();
29119
+ }
29120
+ headerActionClicked(action) {
29121
+ switch (action) {
29122
+ case 'add':
29123
+ this.svc.serviceType = '';
29124
+ this.svc.openEditForm();
29125
+ break;
29126
+ case 'edit':
29127
+ this.svc.openEditForm();
29128
+ break;
29129
+ case 'delete':
29130
+ const selectedItems = this.rowData.filter((row) => {
29131
+ return row.selected;
29132
+ });
29133
+ const label = selectedItems.length > 1 ? 'service edge router policies' : 'service edge router policy';
29134
+ this.openBulkDelete(selectedItems, label);
29135
+ break;
29136
+ default:
29137
+ }
29138
+ }
29139
+ tableAction(event) {
29140
+ this.trackMenuAction(event?.action, event?.item);
29141
+ switch (event?.action) {
29142
+ case 'toggleAll':
29143
+ case 'toggleItem':
29144
+ this.itemToggled(event.item);
29145
+ break;
29146
+ case 'update':
29147
+ this.svc.serviceType = 'advanced';
29148
+ this.svc.openEditForm(event.item?.id);
29149
+ break;
29150
+ case 'create':
29151
+ this.svc.openEditForm();
29152
+ break;
29153
+ case 'delete':
29154
+ this.deleteItem(event.item);
29155
+ break;
29156
+ case 'download-all':
29157
+ this.downloadAllItems();
29158
+ break;
29159
+ case 'download-selected':
29160
+ this.svc.downloadItems(this.selectedItems);
29161
+ break;
29162
+ default:
29163
+ break;
29164
+ }
29165
+ }
29166
+ trackMenuAction(action, item) {
29167
+ this.extService?.consoleActionTriggered?.emit({
29168
+ action,
29169
+ page: 'service-edge-router-policy',
29170
+ item,
29171
+ type: 'menu-action'
29172
+ });
29173
+ }
29174
+ getRoleAttributes() {
29175
+ this.svc.getEdgeRouterRoleAttributes();
29176
+ this.svc.getServiceRoleAttributes();
29177
+ this.svc.getServiceNamedAttributes();
29178
+ this.svc.getRouterNamedAttributes();
29179
+ }
29180
+ deleteItem(item) {
29181
+ this.openBulkDelete([item], 'service-edge-router-policies');
29182
+ }
29183
+ closeModal(event) {
29184
+ this.svc.sideModalOpen = false;
29185
+ if (event?.refresh) {
29186
+ this.refreshData();
29187
+ }
29188
+ }
29189
+ dataChanged(event) {
29190
+ this.formDataChanged = event;
29191
+ }
29192
+ ngOnDestroy() {
29193
+ super.ngOnDestroy();
29194
+ }
29195
+ 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 }); }
29196
+ 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", "rowHover"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
29197
+ }
29198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ServiceEdgeRouterPoliciesListPageComponent, decorators: [{
29199
+ type: Component,
29200
+ 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" }]
29201
+ }], ctorParameters: () => [{ type: ServiceEdgeRouterPoliciesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
29202
+ type: Inject,
29203
+ args: [SHAREDZ_EXTENSION]
29204
+ }] }] });
29205
+
27416
29206
  class ServiceEdgeRouterPolicy {
27417
29207
  constructor() {
27418
29208
  this.name = '';
@@ -29911,7 +31701,7 @@ class AuthPoliciesPageService extends ListPageServiceClass {
29911
31701
  ];
29912
31702
  }
29913
31703
  checkDefaultAuthPolicy(authPolicy) {
29914
- if (authPolicy.id !== 'default') {
31704
+ if (authPolicy?.id !== 'default') {
29915
31705
  return Promise.resolve({ confirmed: true });
29916
31706
  }
29917
31707
  const confirmationMessage = `<p>WARNING: Do not change the default policy unless you're sure you know what you are doing.</p>
@@ -30045,7 +31835,7 @@ class AuthPoliciesPageComponent extends ListPageComponent {
30045
31835
  break;
30046
31836
  case 'update':
30047
31837
  this.svc.checkDefaultAuthPolicy(event.item).then((result) => {
30048
- if (!result) {
31838
+ if (!result?.confirmed) {
30049
31839
  return;
30050
31840
  }
30051
31841
  this.svc.openEditForm(event.item?.id);
@@ -30056,7 +31846,7 @@ class AuthPoliciesPageComponent extends ListPageComponent {
30056
31846
  break;
30057
31847
  case 'delete':
30058
31848
  this.svc.checkDefaultAuthPolicy(event.item).then((result) => {
30059
- if (!result) {
31849
+ if (!result?.confirmed) {
30060
31850
  return;
30061
31851
  }
30062
31852
  this.deleteItem(event.item);
@@ -32331,32 +34121,20 @@ class PostureChecksPageService extends ListPageServiceClass {
32331
34121
  constructor(schemaSvc, settings, filterService, csvDownloadService, extService, router) {
32332
34122
  super(settings, filterService, csvDownloadService, extService, router);
32333
34123
  this.schemaSvc = schemaSvc;
34124
+ this.extService = extService;
32334
34125
  this.router = router;
32335
34126
  this.paging = this.DEFAULT_PAGING;
32336
34127
  this.resourceType = 'posture-checks';
32337
34128
  this.modalType = '';
32338
34129
  this.menuItems = [
32339
- { name: 'Edit', action: 'update' },
32340
- { name: 'Delete', action: 'delete' },
34130
+ { 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>' },
34131
+ { 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
34132
  ];
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) => {
34133
+ this.tableHeaderActions = [
34134
+ { 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>' },
34135
+ { 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>' },
34136
+ ];
34137
+ this.detailsRenderer = (row) => {
32360
34138
  const data = row?.data || '';
32361
34139
  let details = '';
32362
34140
  if (data.typeId == "DOMAIN") {
@@ -32387,6 +34165,23 @@ class PostureChecksPageService extends ListPageServiceClass {
32387
34165
  }
32388
34166
  return details;
32389
34167
  };
34168
+ }
34169
+ initTableColumns() {
34170
+ const osTypeFilterOptions = {
34171
+ filterType: 'SELECT',
34172
+ enableSorting: true,
34173
+ filterOptions: [
34174
+ { label: 'Domain', value: 'DOMAIN' },
34175
+ { label: 'MAC', value: 'MAC' },
34176
+ { label: 'MFA', value: 'MFA' },
34177
+ { label: 'OS', value: 'OS' },
34178
+ { label: 'Process', value: 'PROCESS' },
34179
+ { label: 'Process Multi', value: 'MULTI_PROCESS' },
34180
+ ]
34181
+ };
34182
+ const createdAtHeaderComponentParams = {
34183
+ filterType: 'DATETIME',
34184
+ };
32390
34185
  return [
32391
34186
  {
32392
34187
  colId: 'name',
@@ -32432,7 +34227,7 @@ class PostureChecksPageService extends ListPageServiceClass {
32432
34227
  field: 'details',
32433
34228
  headerName: 'Details',
32434
34229
  headerComponent: TableColumnDefaultComponent,
32435
- cellRenderer: detailsRenderer,
34230
+ cellRenderer: this.detailsRenderer,
32436
34231
  cellRendererParams: { pathRoot: this.basePath },
32437
34232
  onCellClicked: (data) => {
32438
34233
  if (this.hasSelectedText()) {
@@ -32460,8 +34255,102 @@ class PostureChecksPageService extends ListPageServiceClass {
32460
34255
  this.ID_COLUMN_DEF
32461
34256
  ];
32462
34257
  }
32463
- getData(filters, sort) {
34258
+ // Declarative ListColumn set for the new `lib-list-table` (Beta Features on).
34259
+ initListTableColumns() {
34260
+ this.initMenuActions();
34261
+ const escapeHtml = (v) => String(v ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
34262
+ const openForm = (ctx) => {
34263
+ if (this.hasSelectedText()) {
34264
+ return;
34265
+ }
34266
+ this.openEditForm(ctx.row?.id);
34267
+ };
34268
+ let tableColumns = [
34269
+ {
34270
+ key: 'name',
34271
+ field: 'name',
34272
+ label: 'Name',
34273
+ filterType: 'TEXTINPUT',
34274
+ cell: NameCellComponent,
34275
+ cellInputs: (ctx) => ({
34276
+ row: ctx.row,
34277
+ cellValue: ctx.value,
34278
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
34279
+ }),
34280
+ onCellClick: openForm,
34281
+ resizable: true,
34282
+ cellClass: 'nf-cell-vert-align',
34283
+ sortable: true,
34284
+ onSort: this.sort.bind(this),
34285
+ width: 300,
34286
+ },
34287
+ {
34288
+ key: 'typeId',
34289
+ field: 'typeId',
34290
+ label: 'Type',
34291
+ filterType: 'SELECT',
34292
+ filterPlaceholder: 'Any type',
34293
+ filterOptions: [
34294
+ { label: 'Show All', value: '' },
34295
+ { label: 'Domain', value: 'DOMAIN' },
34296
+ { label: 'MAC', value: 'MAC' },
34297
+ { label: 'MFA', value: 'MFA' },
34298
+ { label: 'OS', value: 'OS' },
34299
+ { label: 'Process', value: 'PROCESS' },
34300
+ { label: 'Process Multi', value: 'MULTI_PROCESS' },
34301
+ ],
34302
+ onCellClick: openForm,
34303
+ resizable: true,
34304
+ cellClass: 'nf-cell-vert-align',
34305
+ width: 140,
34306
+ },
34307
+ {
34308
+ key: 'details',
34309
+ field: 'details',
34310
+ label: 'Details',
34311
+ cellHtml: (row) => escapeHtml(this.detailsRenderer(row)),
34312
+ onCellClick: openForm,
34313
+ resizable: true,
34314
+ cellClass: 'nf-cell-vert-align',
34315
+ },
34316
+ {
34317
+ key: 'createdAt',
34318
+ field: 'createdAt',
34319
+ label: 'Created At',
34320
+ valueFormatter: this.createdAtFormatter,
34321
+ filterType: 'DATETIME',
34322
+ filterPlaceholder: 'Any time',
34323
+ onCellClick: openForm,
34324
+ resizable: true,
34325
+ sortable: true,
34326
+ onSort: this.sort.bind(this),
34327
+ cellClass: 'nf-cell-vert-align',
34328
+ width: this.remToPx(10),
34329
+ },
34330
+ {
34331
+ key: 'id',
34332
+ field: 'id',
34333
+ label: 'ID',
34334
+ filterType: 'TEXTINPUT',
34335
+ onCellClick: openForm,
34336
+ resizable: true,
34337
+ sortable: true,
34338
+ onSort: this.sort.bind(this),
34339
+ cellClass: 'nf-cell-vert-align',
34340
+ hidden: true,
34341
+ },
34342
+ ];
34343
+ if (this.extService?.processListTableColumns) {
34344
+ tableColumns = this.extService.processListTableColumns(tableColumns);
34345
+ }
34346
+ return tableColumns;
34347
+ }
34348
+ getData(filters, sort, page, pageSize) {
32464
34349
  // we can customize filters or sorting here before moving on...
34350
+ this.paging.page = page || this.paging.page;
34351
+ if (pageSize) {
34352
+ this.paging.total = pageSize;
34353
+ }
32465
34354
  return super.getTableData('posture-checks', this.paging, filters, sort)
32466
34355
  .then((results) => {
32467
34356
  return this.processData(results);
@@ -32603,6 +34492,120 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
32603
34492
  args: [SHAREDZ_EXTENSION]
32604
34493
  }] }] });
32605
34494
 
34495
+ /*
34496
+ Copyright NetFoundry Inc.
34497
+
34498
+ Licensed under the Apache License, Version 2.0 (the "License");
34499
+ you may not use this file except in compliance with the License.
34500
+ You may obtain a copy of the License at
34501
+
34502
+ https://www.apache.org/licenses/LICENSE-2.0
34503
+
34504
+ Unless required by applicable law or agreed to in writing, software
34505
+ distributed under the License is distributed on an "AS IS" BASIS,
34506
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34507
+ See the License for the specific language governing permissions and
34508
+ limitations under the License.
34509
+ */
34510
+ // Posture Checks page on `lib-list-table`, selected by the route when Beta Features is on.
34511
+ class PostureChecksListPageComponent extends ListPageComponent {
34512
+ constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
34513
+ super(filterService, svc, consoleEvents, dialogForm, extService);
34514
+ this.svc = svc;
34515
+ this.tabNames = tabNames;
34516
+ this.extService = extService;
34517
+ this.title = 'Posture Checks';
34518
+ this.isLoading = false;
34519
+ this.formDataChanged = false;
34520
+ this.tableId = 'posture-checks';
34521
+ this.initialPageSize = 50;
34522
+ }
34523
+ loadTableColumns() {
34524
+ return this.svc.initListTableColumns();
34525
+ }
34526
+ ngOnInit() {
34527
+ this.tabs = this.tabNames.getTabs('policies');
34528
+ this.svc.refreshData = this.refreshData;
34529
+ super.ngOnInit();
34530
+ }
34531
+ headerActionClicked(action) {
34532
+ switch (action) {
34533
+ case 'add':
34534
+ this.svc.openEditForm();
34535
+ break;
34536
+ case 'edit':
34537
+ this.svc.openEditForm();
34538
+ break;
34539
+ case 'delete':
34540
+ const selectedItems = this.rowData.filter((row) => {
34541
+ return row.selected;
34542
+ });
34543
+ const label = selectedItems.length > 1 ? 'posture checks' : 'posture check';
34544
+ this.openBulkDelete(selectedItems, label);
34545
+ break;
34546
+ default:
34547
+ }
34548
+ }
34549
+ tableAction(event) {
34550
+ this.trackMenuAction(event?.action, event?.item);
34551
+ switch (event?.action) {
34552
+ case 'toggleAll':
34553
+ case 'toggleItem':
34554
+ this.itemToggled(event.item);
34555
+ break;
34556
+ case 'update':
34557
+ this.svc.openEditForm(event.item?.id);
34558
+ break;
34559
+ case 'create':
34560
+ this.svc.openEditForm();
34561
+ break;
34562
+ case 'delete':
34563
+ this.deleteItem(event.item);
34564
+ break;
34565
+ case 'download-all':
34566
+ this.downloadAllItems();
34567
+ break;
34568
+ case 'download-selected':
34569
+ this.svc.downloadItems(this.selectedItems);
34570
+ break;
34571
+ default:
34572
+ break;
34573
+ }
34574
+ }
34575
+ trackMenuAction(action, item) {
34576
+ this.extService?.consoleActionTriggered?.emit({
34577
+ action,
34578
+ page: 'posture-check',
34579
+ item,
34580
+ type: 'menu-action'
34581
+ });
34582
+ }
34583
+ deleteItem(item) {
34584
+ this.openBulkDelete([item], 'posture-checks');
34585
+ }
34586
+ closeModal(event) {
34587
+ this.svc.sideModalOpen = false;
34588
+ if (event?.refresh) {
34589
+ this.refreshData();
34590
+ }
34591
+ }
34592
+ dataChanged(event) {
34593
+ this.formDataChanged = event;
34594
+ }
34595
+ ngOnDestroy() {
34596
+ super.ngOnDestroy();
34597
+ }
34598
+ 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 }); }
34599
+ 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", "rowHover"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
34600
+ }
34601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PostureChecksListPageComponent, decorators: [{
34602
+ type: Component,
34603
+ 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" }]
34604
+ }], ctorParameters: () => [{ type: PostureChecksPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
34605
+ type: Inject,
34606
+ args: [SHAREDZ_EXTENSION]
34607
+ }] }] });
34608
+
32606
34609
  class PostureCheck {
32607
34610
  constructor() {
32608
34611
  this.name = '';
@@ -44280,6 +46283,7 @@ class OpenZitiConsoleLibModule {
44280
46283
  SdkInfoTooltipComponent,
44281
46284
  BooleanCellComponent,
44282
46285
  TokenCellComponent,
46286
+ AttributeCellComponent,
44283
46287
  TableCellSelectComponent,
44284
46288
  TableColumnSelectComponent,
44285
46289
  TableCellMenuComponent,
@@ -44319,12 +46323,15 @@ class OpenZitiConsoleLibModule {
44319
46323
  CreationSummaryDialogComponent,
44320
46324
  ConfigEditorComponent,
44321
46325
  ServicePoliciesPageComponent,
46326
+ ServicePoliciesListPageComponent,
44322
46327
  ServicePolicyFormComponent,
44323
46328
  EdgeRouterPoliciesPageComponent,
46329
+ EdgeRouterPoliciesListPageComponent,
44324
46330
  EdgeRouterPolicyFormComponent,
44325
46331
  MultiActionButtonComponent,
44326
46332
  TableCellNameComponent,
44327
46333
  ServiceEdgeRouterPoliciesPageComponent,
46334
+ ServiceEdgeRouterPoliciesListPageComponent,
44328
46335
  ServiceEdgeRouterPolicyFormComponent,
44329
46336
  TerminatorsPageComponent,
44330
46337
  ConfigurationFormComponent,
@@ -44338,6 +46345,7 @@ class OpenZitiConsoleLibModule {
44338
46345
  VerifyCertificateComponent,
44339
46346
  LoginDialogComponent,
44340
46347
  PostureChecksPageComponent,
46348
+ PostureChecksListPageComponent,
44341
46349
  PostureCheckFormComponent,
44342
46350
  SessionsPageComponent,
44343
46351
  SessionFormComponent,
@@ -44425,8 +46433,10 @@ class OpenZitiConsoleLibModule {
44425
46433
  FormFieldToggleComponent,
44426
46434
  ServiceFormComponent,
44427
46435
  ServicePoliciesPageComponent,
46436
+ ServicePoliciesListPageComponent,
44428
46437
  ServicePolicyFormComponent,
44429
46438
  EdgeRouterPoliciesPageComponent,
46439
+ EdgeRouterPoliciesListPageComponent,
44430
46440
  TerminatorsPageComponent,
44431
46441
  TerminatorFormComponent,
44432
46442
  JwtSignersPageComponent,
@@ -44435,6 +46445,7 @@ class OpenZitiConsoleLibModule {
44435
46445
  AuthPolicyFormComponent,
44436
46446
  LoginDialogComponent,
44437
46447
  PostureChecksPageComponent,
46448
+ PostureChecksListPageComponent,
44438
46449
  PostureCheckFormComponent,
44439
46450
  SessionsPageComponent,
44440
46451
  SessionFormComponent,
@@ -44536,6 +46547,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44536
46547
  SdkInfoTooltipComponent,
44537
46548
  BooleanCellComponent,
44538
46549
  TokenCellComponent,
46550
+ AttributeCellComponent,
44539
46551
  TableCellSelectComponent,
44540
46552
  TableColumnSelectComponent,
44541
46553
  TableCellMenuComponent,
@@ -44575,12 +46587,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44575
46587
  CreationSummaryDialogComponent,
44576
46588
  ConfigEditorComponent,
44577
46589
  ServicePoliciesPageComponent,
46590
+ ServicePoliciesListPageComponent,
44578
46591
  ServicePolicyFormComponent,
44579
46592
  EdgeRouterPoliciesPageComponent,
46593
+ EdgeRouterPoliciesListPageComponent,
44580
46594
  EdgeRouterPolicyFormComponent,
44581
46595
  MultiActionButtonComponent,
44582
46596
  TableCellNameComponent,
44583
46597
  ServiceEdgeRouterPoliciesPageComponent,
46598
+ ServiceEdgeRouterPoliciesListPageComponent,
44584
46599
  ServiceEdgeRouterPolicyFormComponent,
44585
46600
  TerminatorsPageComponent,
44586
46601
  ConfigurationFormComponent,
@@ -44594,6 +46609,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44594
46609
  VerifyCertificateComponent,
44595
46610
  LoginDialogComponent,
44596
46611
  PostureChecksPageComponent,
46612
+ PostureChecksListPageComponent,
44597
46613
  PostureCheckFormComponent,
44598
46614
  SessionsPageComponent,
44599
46615
  SessionFormComponent,
@@ -44662,8 +46678,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44662
46678
  FormFieldToggleComponent,
44663
46679
  ServiceFormComponent,
44664
46680
  ServicePoliciesPageComponent,
46681
+ ServicePoliciesListPageComponent,
44665
46682
  ServicePolicyFormComponent,
44666
46683
  EdgeRouterPoliciesPageComponent,
46684
+ EdgeRouterPoliciesListPageComponent,
44667
46685
  TerminatorsPageComponent,
44668
46686
  TerminatorFormComponent,
44669
46687
  JwtSignersPageComponent,
@@ -44672,6 +46690,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
44672
46690
  AuthPolicyFormComponent,
44673
46691
  LoginDialogComponent,
44674
46692
  PostureChecksPageComponent,
46693
+ PostureChecksListPageComponent,
44675
46694
  PostureCheckFormComponent,
44676
46695
  SessionsPageComponent,
44677
46696
  SessionFormComponent,
@@ -46197,7 +48216,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
46197
48216
  */
46198
48217
 
46199
48218
  const ZAC_VERSION = {
46200
- "version": "4.6.0"
48219
+ "version": "4.7.1"
46201
48220
  };
46202
48221
 
46203
48222
  /*
@@ -46208,5 +48227,5 @@ const ZAC_VERSION = {
46208
48227
  * Generated bundle index. Do not edit.
46209
48228
  */
46210
48229
 
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 };
48230
+ export { APISessionFormComponent, APISessionsPageComponent, API_RESOURCE_TO_ENTITY_PERMISSION, ApiSessionsPageService, AttributeCellComponent, 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
48231
  //# sourceMappingURL=openziti-ziti-console-lib.mjs.map