@masterteam/client-components 0.0.81 → 0.0.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, signal, computed, input, output, Component, viewChild, effect, untracked, ChangeDetectionStrategy } from '@angular/core';
2
+ import { inject, Injectable, signal, computed, input, output, Component, viewChild, effect, untracked, ChangeDetectionStrategy, ElementRef } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import * as i4 from '@jsverse/transloco';
@@ -8,7 +8,7 @@ import { Button } from '@masterteam/components/button';
8
8
  import { RuntimeActionRunner, resolveActionLabel } from '@masterteam/components/runtime-action';
9
9
  import { HttpClient } from '@angular/common/http';
10
10
  import { switchMap, of, map, Observable, share, take } from 'rxjs';
11
- import { resolveLocalizedLabel, isSectionKey, EntitiesPreview } from '@masterteam/components/entities';
11
+ import { resolveLocalizedLabel, serializeEntityRawValue, isSectionKey, EntitiesPreview } from '@masterteam/components/entities';
12
12
  import { Table, TableExportService, TableGroupingController, TableValueResolver, TableCaption } from '@masterteam/components/table';
13
13
  import * as i2 from 'primeng/skeleton';
14
14
  import { SkeletonModule } from 'primeng/skeleton';
@@ -22,6 +22,9 @@ import { MTDateFormatPipe, readableTextOn } from '@masterteam/components';
22
22
  import * as i3 from 'primeng/popover';
23
23
  import { PopoverModule } from 'primeng/popover';
24
24
  import { DashboardViewer } from '@masterteam/dashboard-builder';
25
+ import * as i1$1 from '@angular/forms';
26
+ import { FormsModule } from '@angular/forms';
27
+ import { TextField } from '@masterteam/components/text-field';
25
28
 
26
29
  const DEFAULT_INCLUDE_STATE = ['escalation'];
27
30
  class ClientListApiService {
@@ -484,9 +487,7 @@ class ClientListStateService {
484
487
  key: property?.key ?? projectionKey,
485
488
  normalizedKey: property?.normalizedKey ?? projectionKey,
486
489
  name: customLabel || label || property?.label || projectionKey,
487
- rawValue: cell?.raw === null || cell?.raw === undefined
488
- ? undefined
489
- : String(cell.raw),
490
+ rawValue: serializeEntityRawValue(cell?.raw),
490
491
  value: (cell?.value ?? ''),
491
492
  viewType: resolvedViewType,
492
493
  type: property?.source,
@@ -1673,13 +1674,115 @@ class ClientListInformativeView {
1673
1674
  };
1674
1675
  }
1675
1676
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListInformativeView, deps: [], target: i0.ɵɵFactoryTarget.Component });
1676
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListInformativeView, isStandalone: true, selector: "mt-client-list-informative-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (informativeState().config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.startSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.tableSpan)\r\n \"\r\n >\r\n <mt-card>\r\n @if (\r\n informativeState().loading &&\r\n !informativeState().dashboardData?.charts?.length\r\n ) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"6rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n </div>\r\n } @else if (informativeState().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ informativeState().error }}\r\n </div>\r\n } @else if (!informativeState().dashboardData?.charts?.length) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{\r\n \"components.clientComponents.list.noInformativeDashboard\"\r\n | transloco\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"min-h-[420px]\">\r\n <mt-dashboard-viewer\r\n [isPage]=\"false\"\r\n [showFilters]=\"false\"\r\n [dashboardData]=\"informativeState().dashboardData\"\r\n [extraFilters]=\"dashboardExtraFilters()\"\r\n />\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (informativeState().config.contentEnd) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.endSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: DashboardViewer, selector: "mt-dashboard-viewer", inputs: ["isPage", "pageTitle", "backButton", "pageId", "dashboardData", "chartsData", "dialogsData", "filtersData", "extraFilters", "lookups", "statuses", "showFilters", "showFilterTrigger", "ignoreQueryFilter"], outputs: ["pageLoaded", "onBack", "chartClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
1677
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListInformativeView, isStandalone: true, selector: "mt-client-list-informative-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (informativeState().config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.startSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.tableSpan)\r\n \"\r\n >\r\n <mt-card>\r\n @if (\r\n informativeState().loading &&\r\n !informativeState().dashboardData?.charts?.length\r\n ) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"6rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n </div>\r\n } @else if (informativeState().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ informativeState().error }}\r\n </div>\r\n } @else if (!informativeState().dashboardData?.charts?.length) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{\r\n \"components.clientComponents.list.noInformativeDashboard\"\r\n | transloco\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"min-h-[420px]\">\r\n <mt-dashboard-viewer\r\n [isPage]=\"false\"\r\n [showFilters]=\"false\"\r\n [dashboardData]=\"informativeState().dashboardData\"\r\n [extraFilters]=\"dashboardExtraFilters()\"\r\n />\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (informativeState().config.contentEnd) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.endSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: DashboardViewer, selector: "mt-dashboard-viewer", inputs: ["isPage", "pageTitle", "backButton", "pageId", "dashboardData", "chartsData", "dialogsData", "filtersData", "extraFilters", "lookups", "statuses", "levelsSchema", "levelLogsResolver", "showFilters", "showFilterTrigger", "ignoreQueryFilter"], outputs: ["pageLoaded", "onBack", "chartClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
1677
1678
  }
1678
1679
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListInformativeView, decorators: [{
1679
1680
  type: Component,
1680
1681
  args: [{ selector: 'mt-client-list-informative-view', standalone: true, imports: [CommonModule, Card, DashboardViewer, SkeletonModule, TranslocoPipe], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (informativeState().config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.startSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.tableSpan)\r\n \"\r\n >\r\n <mt-card>\r\n @if (\r\n informativeState().loading &&\r\n !informativeState().dashboardData?.charts?.length\r\n ) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"6rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n </div>\r\n } @else if (informativeState().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ informativeState().error }}\r\n </div>\r\n } @else if (!informativeState().dashboardData?.charts?.length) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{\r\n \"components.clientComponents.list.noInformativeDashboard\"\r\n | transloco\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"min-h-[420px]\">\r\n <mt-dashboard-viewer\r\n [isPage]=\"false\"\r\n [showFilters]=\"false\"\r\n [dashboardData]=\"informativeState().dashboardData\"\r\n [extraFilters]=\"dashboardExtraFilters()\"\r\n />\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (informativeState().config.contentEnd) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.endSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n" }]
1681
1682
  }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }] } });
1682
1683
 
1684
+ /**
1685
+ * Below this many lists the filter box is noise — the whole rail fits on
1686
+ * screen and scanning it is faster than typing.
1687
+ */
1688
+ const FILTER_VISIBLE_FROM = 6;
1689
+ /**
1690
+ * The master rail of the client list's master/detail layout: one row per
1691
+ * configured list, with its record count, and the selected row highlighted.
1692
+ *
1693
+ * Purely presentational — it renders the entries it is handed and emits the
1694
+ * key that was picked. Selection, persistence and loading all stay in
1695
+ * `ClientList`.
1696
+ *
1697
+ * Two shapes, picked by `ClientList` from the width it measures on itself and
1698
+ * handed down as `.mt-cl-nav--rail`: the vertical rail when there is room for
1699
+ * a second column, a horizontally scrolling strip of chips above the content
1700
+ * when there is not. Never keyed off the viewport — see the stylesheet.
1701
+ */
1702
+ class ClientListNavigator {
1703
+ host = inject((ElementRef));
1704
+ entries = input.required(...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
1705
+ activeKey = input(null, ...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
1706
+ /** Id of the content pane, for each tab's `aria-controls`. */
1707
+ panelId = input('', ...(ngDevMode ? [{ debugName: "panelId" }] : /* istanbul ignore next */ []));
1708
+ selected = output();
1709
+ filterTerm = signal('', ...(ngDevMode ? [{ debugName: "filterTerm" }] : /* istanbul ignore next */ []));
1710
+ showFilter = computed(() => this.entries().length >= FILTER_VISIBLE_FROM, ...(ngDevMode ? [{ debugName: "showFilter" }] : /* istanbul ignore next */ []));
1711
+ visibleEntries = computed(() => {
1712
+ const term = this.filterTerm().trim().toLocaleLowerCase();
1713
+ const entries = this.entries();
1714
+ if (!term || !this.showFilter())
1715
+ return entries;
1716
+ return entries.filter((entry) => entry.label.toLocaleLowerCase().includes(term));
1717
+ }, ...(ngDevMode ? [{ debugName: "visibleEntries" }] : /* istanbul ignore next */ []));
1718
+ onFilterChange(value) {
1719
+ this.filterTerm.set(value ?? '');
1720
+ }
1721
+ /**
1722
+ * Roving arrow-key navigation across the rail, per the ARIA tabs pattern.
1723
+ * Up/Down always work; Left/Right too (the rail is horizontal in a narrow
1724
+ * container) and swap meaning under RTL, where "next" is to the left.
1725
+ */
1726
+ onKeydown(event) {
1727
+ const entries = this.visibleEntries();
1728
+ if (entries.length === 0)
1729
+ return;
1730
+ const rtl = this.isRtl();
1731
+ let step;
1732
+ let absolute = null;
1733
+ switch (event.key) {
1734
+ case 'ArrowDown':
1735
+ step = 1;
1736
+ break;
1737
+ case 'ArrowUp':
1738
+ step = -1;
1739
+ break;
1740
+ case 'ArrowRight':
1741
+ step = rtl ? -1 : 1;
1742
+ break;
1743
+ case 'ArrowLeft':
1744
+ step = rtl ? 1 : -1;
1745
+ break;
1746
+ case 'Home':
1747
+ step = 0;
1748
+ absolute = 0;
1749
+ break;
1750
+ case 'End':
1751
+ step = 0;
1752
+ absolute = entries.length - 1;
1753
+ break;
1754
+ default:
1755
+ return;
1756
+ }
1757
+ const current = entries.findIndex((entry) => entry.key === this.activeKey());
1758
+ const from = current === -1 ? 0 : current;
1759
+ const next = absolute ?? (from + step + entries.length) % entries.length;
1760
+ event.preventDefault();
1761
+ this.selected.emit(entries[next].key);
1762
+ this.focusEntry(next);
1763
+ }
1764
+ focusEntry(index) {
1765
+ // The freshly selected row is re-rendered before it can take focus, so
1766
+ // hand the browser one frame to swap the roving tabindex over.
1767
+ requestAnimationFrame(() => {
1768
+ const element = this.host.nativeElement.querySelector(`[data-nav-index="${index}"]`);
1769
+ element?.focus();
1770
+ });
1771
+ }
1772
+ isRtl() {
1773
+ if (typeof getComputedStyle !== 'function')
1774
+ return false;
1775
+ return (getComputedStyle(this.host.nativeElement).direction ===
1776
+ 'rtl');
1777
+ }
1778
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListNavigator, deps: [], target: i0.ɵɵFactoryTarget.Component });
1779
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListNavigator, isStandalone: true, selector: "mt-client-list-navigator", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, panelId: { classPropertyName: "panelId", publicName: "panelId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: "@if (showFilter()) {\n <div class=\"mt-cl-nav__filter\">\n <mt-text-field\n [field]=\"false\"\n [ngModel]=\"filterTerm()\"\n (ngModelChange)=\"onFilterChange($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"\n 'components.clientComponents.list.navigator.filter' | transloco\n \"\n />\n </div>\n}\n\n<div\n class=\"mt-cl-nav__list\"\n role=\"tablist\"\n [attr.aria-label]=\"\n 'components.clientComponents.list.navigator.label' | transloco\n \"\n (keydown)=\"onKeydown($event)\"\n>\n @for (entry of visibleEntries(); track entry.key; let i = $index) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"mt-cl-nav__item\"\n [class.mt-cl-nav__item--active]=\"entry.key === activeKey()\"\n [attr.aria-selected]=\"entry.key === activeKey()\"\n [attr.tabindex]=\"entry.key === activeKey() ? 0 : -1\"\n [attr.aria-controls]=\"panelId() || null\"\n [attr.data-nav-index]=\"i\"\n [title]=\"entry.label\"\n (click)=\"selected.emit(entry.key)\"\n >\n <span class=\"mt-cl-nav__label\">{{ entry.label }}</span>\n\n @if (entry.hasError) {\n <mt-icon\n class=\"mt-cl-nav__error\"\n icon=\"alert.alert-circle\"\n role=\"img\"\n [attr.aria-label]=\"\n 'components.clientComponents.list.navigator.loadFailed' | transloco\n \"\n />\n } @else if (entry.loading && entry.count === null) {\n <span\n class=\"mt-cl-nav__count mt-cl-nav__count--loading\"\n aria-hidden=\"true\"\n ></span>\n } @else if (entry.count !== null) {\n <span\n class=\"mt-cl-nav__count\"\n [class.mt-cl-nav__count--empty]=\"entry.count === 0\"\n >\n {{ entry.count === 0 ? \"\u2013\" : entry.count }}\n </span>\n }\n </button>\n } @empty {\n <p class=\"mt-cl-nav__empty\">\n {{ \"components.clientComponents.list.navigator.noMatches\" | transloco }}\n </p>\n }\n</div>\n\n<p class=\"mt-cl-nav__summary\">\n {{\n \"components.clientComponents.list.navigator.summary\"\n | transloco: { count: entries().length }\n }}\n</p>\n", styles: [":host{display:block;min-width:0}.mt-cl-nav__filter{display:none;margin-block-end:.5rem}.mt-cl-nav__filter mt-text-field{grid-template-columns:minmax(0,1fr)}.mt-cl-nav__list{display:flex;flex-direction:row;gap:.375rem;overflow-x:auto;overscroll-behavior-inline:contain;scrollbar-width:thin;padding-block-end:.375rem}.mt-cl-nav__item{display:flex;flex:0 0 auto;align-items:center;gap:.5rem;min-width:0;padding:.4375rem .75rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:999px;background:var(--p-content-background, #ffffff);color:var(--p-text-muted-color, #6b7280);font-size:.875rem;font-weight:500;line-height:1.25rem;text-align:start;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.mt-cl-nav__item:hover{color:var(--p-text-color, #111827);background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 8%,var(--p-content-background, #ffffff))}.mt-cl-nav__item:focus-visible{outline:2px solid var(--p-primary-color, #2aaec0);outline-offset:2px}.mt-cl-nav__item--active,.mt-cl-nav__item--active:hover{border-color:color-mix(in srgb,var(--p-primary-color, #2aaec0) 45%,transparent);background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 12%,var(--p-content-background, #ffffff));color:var(--p-primary-color, #2aaec0);font-weight:600}.mt-cl-nav__label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mt-cl-nav__count{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;min-width:1.75rem;height:1.25rem;padding-inline:.375rem;border-radius:999px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-muted-color, #6b7280);font-size:.75rem;font-weight:600;font-variant-numeric:tabular-nums}.mt-cl-nav__count--empty{background:transparent;color:var(--p-text-muted-color, #9ca3af);font-weight:500}.mt-cl-nav__count--loading{animation:mt-cl-nav-pulse 1.4s ease-in-out infinite}.mt-cl-nav__item--active .mt-cl-nav__count{background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 20%,transparent);color:var(--p-primary-color, #2aaec0)}.mt-cl-nav__error{flex:0 0 auto;color:var(--p-red-500, #ef4444)}.mt-cl-nav__empty,.mt-cl-nav__summary{margin:0;color:var(--p-text-muted-color, #6b7280);font-size:.75rem}.mt-cl-nav__empty{padding:.75rem .25rem}.mt-cl-nav__summary{display:none}@keyframes mt-cl-nav-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.mt-cl-nav__item{transition:none}.mt-cl-nav__count--loading{animation:none}}:host(.mt-cl-nav--rail) .mt-cl-nav__filter{display:block}:host(.mt-cl-nav--rail) .mt-cl-nav__list{flex-direction:column;gap:.125rem;overflow-x:visible;overflow-y:auto;max-height:32rem;padding-block-end:0}:host(.mt-cl-nav--rail) .mt-cl-nav__item{flex:0 0 auto;width:100%;padding:.625rem .75rem;border:0;border-inline-start:3px solid transparent;border-radius:0;border-start-end-radius:.375rem;border-end-end-radius:.375rem;background:transparent}:host(.mt-cl-nav--rail) .mt-cl-nav__item:hover{background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 6%,transparent)}:host(.mt-cl-nav--rail) .mt-cl-nav__item--active,:host(.mt-cl-nav--rail) .mt-cl-nav__item--active:hover{border-inline-start-color:var(--p-primary-color, #2aaec0);background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 10%,transparent)}:host(.mt-cl-nav--rail) .mt-cl-nav__label{flex:1 1 auto}:host(.mt-cl-nav--rail) .mt-cl-nav__summary{display:block;margin-block-start:.75rem;padding-block-start:.75rem;padding-inline:.75rem;border-block-start:1px solid var(--p-content-border-color, #e5e7eb)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "pipe", type: i4.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1780
+ }
1781
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListNavigator, decorators: [{
1782
+ type: Component,
1783
+ args: [{ selector: 'mt-client-list-navigator', standalone: true, imports: [FormsModule, TranslocoModule, Icon, TextField], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (showFilter()) {\n <div class=\"mt-cl-nav__filter\">\n <mt-text-field\n [field]=\"false\"\n [ngModel]=\"filterTerm()\"\n (ngModelChange)=\"onFilterChange($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"\n 'components.clientComponents.list.navigator.filter' | transloco\n \"\n />\n </div>\n}\n\n<div\n class=\"mt-cl-nav__list\"\n role=\"tablist\"\n [attr.aria-label]=\"\n 'components.clientComponents.list.navigator.label' | transloco\n \"\n (keydown)=\"onKeydown($event)\"\n>\n @for (entry of visibleEntries(); track entry.key; let i = $index) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"mt-cl-nav__item\"\n [class.mt-cl-nav__item--active]=\"entry.key === activeKey()\"\n [attr.aria-selected]=\"entry.key === activeKey()\"\n [attr.tabindex]=\"entry.key === activeKey() ? 0 : -1\"\n [attr.aria-controls]=\"panelId() || null\"\n [attr.data-nav-index]=\"i\"\n [title]=\"entry.label\"\n (click)=\"selected.emit(entry.key)\"\n >\n <span class=\"mt-cl-nav__label\">{{ entry.label }}</span>\n\n @if (entry.hasError) {\n <mt-icon\n class=\"mt-cl-nav__error\"\n icon=\"alert.alert-circle\"\n role=\"img\"\n [attr.aria-label]=\"\n 'components.clientComponents.list.navigator.loadFailed' | transloco\n \"\n />\n } @else if (entry.loading && entry.count === null) {\n <span\n class=\"mt-cl-nav__count mt-cl-nav__count--loading\"\n aria-hidden=\"true\"\n ></span>\n } @else if (entry.count !== null) {\n <span\n class=\"mt-cl-nav__count\"\n [class.mt-cl-nav__count--empty]=\"entry.count === 0\"\n >\n {{ entry.count === 0 ? \"\u2013\" : entry.count }}\n </span>\n }\n </button>\n } @empty {\n <p class=\"mt-cl-nav__empty\">\n {{ \"components.clientComponents.list.navigator.noMatches\" | transloco }}\n </p>\n }\n</div>\n\n<p class=\"mt-cl-nav__summary\">\n {{\n \"components.clientComponents.list.navigator.summary\"\n | transloco: { count: entries().length }\n }}\n</p>\n", styles: [":host{display:block;min-width:0}.mt-cl-nav__filter{display:none;margin-block-end:.5rem}.mt-cl-nav__filter mt-text-field{grid-template-columns:minmax(0,1fr)}.mt-cl-nav__list{display:flex;flex-direction:row;gap:.375rem;overflow-x:auto;overscroll-behavior-inline:contain;scrollbar-width:thin;padding-block-end:.375rem}.mt-cl-nav__item{display:flex;flex:0 0 auto;align-items:center;gap:.5rem;min-width:0;padding:.4375rem .75rem;border:1px solid var(--p-content-border-color, #e5e7eb);border-radius:999px;background:var(--p-content-background, #ffffff);color:var(--p-text-muted-color, #6b7280);font-size:.875rem;font-weight:500;line-height:1.25rem;text-align:start;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.mt-cl-nav__item:hover{color:var(--p-text-color, #111827);background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 8%,var(--p-content-background, #ffffff))}.mt-cl-nav__item:focus-visible{outline:2px solid var(--p-primary-color, #2aaec0);outline-offset:2px}.mt-cl-nav__item--active,.mt-cl-nav__item--active:hover{border-color:color-mix(in srgb,var(--p-primary-color, #2aaec0) 45%,transparent);background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 12%,var(--p-content-background, #ffffff));color:var(--p-primary-color, #2aaec0);font-weight:600}.mt-cl-nav__label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mt-cl-nav__count{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;min-width:1.75rem;height:1.25rem;padding-inline:.375rem;border-radius:999px;background:var(--p-surface-100, #f3f4f6);color:var(--p-text-muted-color, #6b7280);font-size:.75rem;font-weight:600;font-variant-numeric:tabular-nums}.mt-cl-nav__count--empty{background:transparent;color:var(--p-text-muted-color, #9ca3af);font-weight:500}.mt-cl-nav__count--loading{animation:mt-cl-nav-pulse 1.4s ease-in-out infinite}.mt-cl-nav__item--active .mt-cl-nav__count{background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 20%,transparent);color:var(--p-primary-color, #2aaec0)}.mt-cl-nav__error{flex:0 0 auto;color:var(--p-red-500, #ef4444)}.mt-cl-nav__empty,.mt-cl-nav__summary{margin:0;color:var(--p-text-muted-color, #6b7280);font-size:.75rem}.mt-cl-nav__empty{padding:.75rem .25rem}.mt-cl-nav__summary{display:none}@keyframes mt-cl-nav-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.mt-cl-nav__item{transition:none}.mt-cl-nav__count--loading{animation:none}}:host(.mt-cl-nav--rail) .mt-cl-nav__filter{display:block}:host(.mt-cl-nav--rail) .mt-cl-nav__list{flex-direction:column;gap:.125rem;overflow-x:visible;overflow-y:auto;max-height:32rem;padding-block-end:0}:host(.mt-cl-nav--rail) .mt-cl-nav__item{flex:0 0 auto;width:100%;padding:.625rem .75rem;border:0;border-inline-start:3px solid transparent;border-radius:0;border-start-end-radius:.375rem;border-end-end-radius:.375rem;background:transparent}:host(.mt-cl-nav--rail) .mt-cl-nav__item:hover{background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 6%,transparent)}:host(.mt-cl-nav--rail) .mt-cl-nav__item--active,:host(.mt-cl-nav--rail) .mt-cl-nav__item--active:hover{border-inline-start-color:var(--p-primary-color, #2aaec0);background:color-mix(in srgb,var(--p-primary-color, #2aaec0) 10%,transparent)}:host(.mt-cl-nav--rail) .mt-cl-nav__label{flex:1 1 auto}:host(.mt-cl-nav--rail) .mt-cl-nav__summary{display:block;margin-block-start:.75rem;padding-block-start:.75rem;padding-inline:.75rem;border-block-start:1px solid var(--p-content-border-color, #e5e7eb)}\n"] }]
1784
+ }], propDecorators: { entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], panelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelId", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
1785
+
1683
1786
  function mergeRuntimeFilters(configured = [], runtime = {}, columns = []) {
1684
1787
  const filters = [...configured];
1685
1788
  for (const [rawKey, rawValue] of Object.entries(runtime)) {
@@ -1774,21 +1877,86 @@ const DEFAULT_COLLAPSE_ICON = 'arrow.chevron-up';
1774
1877
  const DEFAULT_EXPAND_ICON = 'arrow.chevron-down';
1775
1878
  const DEFAULT_SIDE_CONTENT_SPAN = 3;
1776
1879
  const DEFAULT_GRID_COLUMNS = 12;
1880
+ /**
1881
+ * Host width, in px, from which the navigator switches from a chip strip
1882
+ * above the content to a rail beside it. Below this a 16.5rem rail plus a
1883
+ * table leaves the table too narrow to be worth the split.
1884
+ */
1885
+ const RAIL_LAYOUT_MIN_WIDTH = 896;
1886
+ /** localStorage prefix for the per-group remembered selection. */
1887
+ const ACTIVE_KEY_STORAGE_PREFIX = 'mt-client-list:active:';
1777
1888
  const ESCALATION_STATE_KEY = 'escalation';
1778
1889
  const VIEW_ESCALATION_ACTION_KEY = 'viewEscalation';
1779
1890
  const VIEW_ESCALATION_OPERATION_KEY = 'ViewEscalation';
1891
+ /** Makes each instance's `aria-controls` target unique on the page. */
1892
+ let panelIdCounter = 0;
1780
1893
  class ClientList {
1781
1894
  api = inject(ClientListApiService);
1782
1895
  state = inject(ClientListStateService);
1783
1896
  runtimeActions = inject(ClientListRuntimeActionsService);
1784
1897
  runtimeRunner = inject(RuntimeActionRunner);
1785
1898
  transloco = inject(TranslocoService);
1899
+ host = inject(ElementRef);
1786
1900
  configurations = input.required(...(ngDevMode ? [{ debugName: "configurations" }] : /* istanbul ignore next */ []));
1787
1901
  defaultTake = input(DEFAULT_SERVER_PAGE_SIZE, ...(ngDevMode ? [{ debugName: "defaultTake" }] : /* istanbul ignore next */ []));
1902
+ /**
1903
+ * Arrangement for MORE THAN ONE configuration — see
1904
+ * {@link ClientListMultiLayout}. A single configuration ignores it and
1905
+ * renders exactly as it always has.
1906
+ */
1907
+ multiLayout = input('navigator', ...(ngDevMode ? [{ debugName: "multiLayout" }] : /* istanbul ignore next */ []));
1788
1908
  loaded = output();
1789
1909
  errored = output();
1790
1910
  itemClicked = output();
1911
+ /** The list the navigator switched to. Never fires in `stacked`. */
1912
+ activeListChanged = output();
1791
1913
  items = this.state.items;
1914
+ /** Ties each rail tab to the pane it controls, for screen readers. */
1915
+ panelId = `mt-client-list-panel-${++panelIdCounter}`;
1916
+ activeKeyOverride = signal(null, ...(ngDevMode ? [{ debugName: "activeKeyOverride" }] : /* istanbul ignore next */ []));
1917
+ railLayout = signal(false, ...(ngDevMode ? [{ debugName: "railLayout" }] : /* istanbul ignore next */ []));
1918
+ resizeObserver = null;
1919
+ useNavigator = computed(() => this.multiLayout() === 'navigator' && this.items().length > 1, ...(ngDevMode ? [{ debugName: "useNavigator" }] : /* istanbul ignore next */ []));
1920
+ isRailLayout = this.railLayout.asReadonly();
1921
+ /**
1922
+ * The selected list. Falls back to the first one whenever the override is
1923
+ * unset or points at a list the host has since removed — so a config change
1924
+ * can never leave the pane blank.
1925
+ */
1926
+ activeKey = computed(() => {
1927
+ const items = this.items();
1928
+ if (items.length === 0)
1929
+ return null;
1930
+ const override = this.activeKeyOverride();
1931
+ return override && items.some((item) => item.key === override)
1932
+ ? override
1933
+ : items[0].key;
1934
+ }, ...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
1935
+ /**
1936
+ * Identity of the current SET of lists. A string so it settles: every load
1937
+ * tick rebuilds the items array, but the signature only changes when the
1938
+ * host adds, removes or re-keys a list.
1939
+ */
1940
+ groupSignature = computed(() => this.items()
1941
+ .map((item) => item.key)
1942
+ .join(''), ...(ngDevMode ? [{ debugName: "groupSignature" }] : /* istanbul ignore next */ []));
1943
+ activeItem = computed(() => {
1944
+ const key = this.activeKey();
1945
+ return key ? (this.state.itemsByKey()[key] ?? null) : null;
1946
+ }, ...(ngDevMode ? [{ debugName: "activeItem" }] : /* istanbul ignore next */ []));
1947
+ navigatorEntries = computed(() => this.items().map((item) => ({
1948
+ key: item.key,
1949
+ label: item.title || item.moduleKey || this.defaultTitle(item),
1950
+ // Informative lists count charts, not records — a number there would
1951
+ // read as a record count next to every other row in the rail. A list
1952
+ // still loading its first page has no count yet either; `0` would
1953
+ // claim it is empty.
1954
+ count: item.type === 'informative' || (item.loading && item.totalCount === 0)
1955
+ ? null
1956
+ : item.totalCount,
1957
+ loading: item.loading,
1958
+ hasError: !!item.error,
1959
+ })), ...(ngDevMode ? [{ debugName: "navigatorEntries" }] : /* istanbul ignore next */ []));
1792
1960
  subscriptions = new Map();
1793
1961
  inFlightRequestSignatures = new Map();
1794
1962
  fulfilledRequestSignatures = new Map();
@@ -1803,6 +1971,122 @@ class ClientList {
1803
1971
  });
1804
1972
  });
1805
1973
  });
1974
+ effect(() => {
1975
+ // Depends on the signature, not on `items()` itself: every load tick
1976
+ // produces a fresh items array, and re-reading storage on each one
1977
+ // would be pure waste. The string only changes when the set of lists
1978
+ // does, which is exactly when the remembered selection is worth
1979
+ // consulting again.
1980
+ this.groupSignature();
1981
+ untracked(() => this.restoreActiveKey(this.items().map((item) => item.key)));
1982
+ });
1983
+ this.observeHostWidth();
1984
+ }
1985
+ /**
1986
+ * Switches the pane. Remembered per list group, so coming back to a
1987
+ * workspace tab lands on the module you were last reading rather than
1988
+ * resetting to the first one.
1989
+ */
1990
+ setActiveKey(key) {
1991
+ if (key === this.activeKey())
1992
+ return;
1993
+ if (!this.state.itemsByKey()[key])
1994
+ return;
1995
+ this.activeKeyOverride.set(key);
1996
+ this.persistActiveKey(key);
1997
+ this.activeListChanged.emit(key);
1998
+ }
1999
+ /**
2000
+ * Subtitle under the pane title: the load failure if there is one, the
2001
+ * record count once it is known, nothing while the first page is still in
2002
+ * flight (a "0 records" that flips to "12 records" reads as a bug).
2003
+ */
2004
+ paneMeta(item) {
2005
+ if (item.error)
2006
+ return item.error;
2007
+ if (item.type === 'informative')
2008
+ return '';
2009
+ if (item.loading && item.totalCount === 0)
2010
+ return '';
2011
+ if (item.totalCount === 0) {
2012
+ return this.transloco.translate('components.clientComponents.list.navigator.noRecords');
2013
+ }
2014
+ return this.transloco.translate(item.totalCount === 1
2015
+ ? 'components.clientComponents.list.navigator.oneRecord'
2016
+ : 'components.clientComponents.list.navigator.records', { count: item.totalCount });
2017
+ }
2018
+ /**
2019
+ * Both layouts render a list body through one `ng-template`, whose context
2020
+ * is untyped. Re-narrowing here keeps strict template checking on every
2021
+ * binding inside it instead of silently degrading them to `any`.
2022
+ */
2023
+ asItem(value) {
2024
+ return value;
2025
+ }
2026
+ /**
2027
+ * Rail beside the content, or chip strip above it. Measured from the host
2028
+ * rather than the viewport: this component renders inside an app shell, so
2029
+ * viewport width says nothing about the room it actually has.
2030
+ */
2031
+ observeHostWidth() {
2032
+ if (typeof ResizeObserver === 'undefined') {
2033
+ // No measurement available (SSR/tests): the single-column chip strip is
2034
+ // the layout that works at any width.
2035
+ return;
2036
+ }
2037
+ const element = this.host.nativeElement;
2038
+ this.resizeObserver = new ResizeObserver((entries) => {
2039
+ const width = entries[0]?.contentRect.width ?? 0;
2040
+ // Only internal layout changes, so this can't feed its own next
2041
+ // notification — the host's width stays whatever its parent gives it.
2042
+ this.railLayout.set(width >= RAIL_LAYOUT_MIN_WIDTH);
2043
+ });
2044
+ this.resizeObserver.observe(element);
2045
+ }
2046
+ restoreActiveKey(keys) {
2047
+ if (keys.length < 2)
2048
+ return;
2049
+ const override = this.activeKeyOverride();
2050
+ if (override && keys.includes(override))
2051
+ return;
2052
+ const stored = this.readStoredActiveKey(this.groupStorageKey(keys));
2053
+ if (stored && keys.includes(stored)) {
2054
+ this.activeKeyOverride.set(stored);
2055
+ }
2056
+ }
2057
+ /**
2058
+ * Identifies this exact set of lists, so the remembered selection follows
2059
+ * the workspace tab it belongs to and a different tab starts fresh. Hashed
2060
+ * because the keys themselves carry ids and filters and get long.
2061
+ */
2062
+ groupStorageKey(keys) {
2063
+ const source = [...keys].sort().join('|');
2064
+ let hash = 5381;
2065
+ for (let i = 0; i < source.length; i++) {
2066
+ hash = ((hash << 5) + hash + source.charCodeAt(i)) | 0;
2067
+ }
2068
+ return `${ACTIVE_KEY_STORAGE_PREFIX}${(hash >>> 0).toString(36)}`;
2069
+ }
2070
+ readStoredActiveKey(storageKey) {
2071
+ try {
2072
+ return globalThis.localStorage?.getItem(storageKey) ?? null;
2073
+ }
2074
+ catch {
2075
+ // Storage disabled (private mode, blocked cookies) — selection just
2076
+ // won't survive a reload.
2077
+ return null;
2078
+ }
2079
+ }
2080
+ persistActiveKey(key) {
2081
+ const keys = this.items().map((item) => item.key);
2082
+ if (keys.length < 2)
2083
+ return;
2084
+ try {
2085
+ globalThis.localStorage?.setItem(this.groupStorageKey(keys), key);
2086
+ }
2087
+ catch {
2088
+ /* see readStoredActiveKey */
2089
+ }
1806
2090
  }
1807
2091
  onLazyLoad(itemKey, event) {
1808
2092
  const item = this.state.itemsByKey()[itemKey];
@@ -2147,6 +2431,8 @@ class ClientList {
2147
2431
  : 'components.clientComponents.list.table');
2148
2432
  }
2149
2433
  ngOnDestroy() {
2434
+ this.resizeObserver?.disconnect();
2435
+ this.resizeObserver = null;
2150
2436
  this.subscriptions.forEach((sub) => sub.unsubscribe());
2151
2437
  this.subscriptions.clear();
2152
2438
  this.rowActionsLoadingFnCache.clear();
@@ -2669,11 +2955,11 @@ class ClientList {
2669
2955
  return mergeRuntimeFilters(config.filters, this.runtimeFilters.get(key), this.state.itemsByKey()[key]?.columns);
2670
2956
  }
2671
2957
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, deps: [], target: i0.ɵɵFactoryTarget.Component });
2672
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientList, isStandalone: true, selector: "mt-client-list", inputs: { configurations: { classPropertyName: "configurations", publicName: "configurations", isSignal: true, isRequired: true, transformFunction: null }, defaultTake: { classPropertyName: "defaultTake", publicName: "defaultTake", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored", itemClicked: "itemClicked" }, providers: [
2958
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientList, isStandalone: true, selector: "mt-client-list", inputs: { configurations: { classPropertyName: "configurations", publicName: "configurations", isSignal: true, isRequired: true, transformFunction: null }, defaultTake: { classPropertyName: "defaultTake", publicName: "defaultTake", isSignal: true, isRequired: false, transformFunction: null }, multiLayout: { classPropertyName: "multiLayout", publicName: "multiLayout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored", itemClicked: "itemClicked", activeListChanged: "activeListChanged" }, providers: [
2673
2959
  ClientListStateService,
2674
2960
  ClientListRuntimeActionsService,
2675
2961
  ClientListToolbarService,
2676
- ], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n @if (item.config.showHeader) {\r\n <div class=\"flex w-full items-center gap-2\">\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (item.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n item.expanded\r\n ? item.config.collapse.collapseIcon\r\n : item.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(item.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-semibold\">\r\n {{ item.title || item.moduleKey || defaultTitle(item) }}\r\n </h3>\r\n @if (item.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (item.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.config.templateContent) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.templateContent\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n } @else if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"rowActionsFor(item)\"\r\n [actionShape]=\"item.config.actionShape\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (rowClick)=\"onTableRowClick(item, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view\r\n [state]=\"item\"\r\n [rowActions]=\"rowActionsFor(item)\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (loadMore)=\"loadMoreCards(item.key)\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ClientListTableView, selector: "mt-client-list-table-view", inputs: ["state", "rowActions", "actionShape", "rowActionsLoadingFn"], outputs: ["lazyLoad", "rowClick", "rowActionsRequested"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state", "rowActions", "rowActionsLoadingFn"], outputs: ["lazyLoad", "loadMore", "cardClick", "rowActionsRequested"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }] });
2962
+ ], ngImport: i0, template: "@if (useNavigator()) {\n <!--\n Master/detail: the rail lists every configured module, the pane renders\n only the selected one. Deliberately NOT a hidden-but-mounted pane per\n module \u2014 a seven-module workspace tab would otherwise mount seven tables,\n each with its own virtual scroller and paginator, to show one.\n -->\n <div class=\"mt-cl-shell\" [class.mt-cl-shell--rail]=\"isRailLayout()\">\n <mt-client-list-navigator\n class=\"mt-cl-shell__rail\"\n [class.mt-cl-nav--rail]=\"isRailLayout()\"\n [entries]=\"navigatorEntries()\"\n [activeKey]=\"activeKey()\"\n [panelId]=\"panelId\"\n (selected)=\"setActiveKey($event)\"\n />\n\n <div class=\"mt-cl-shell__pane\" role=\"tabpanel\" [id]=\"panelId\">\n @if (activeItem(); as item) {\n @if (item.config.showHeader) {\n <div class=\"mt-cl-pane__header\">\n <div class=\"mt-cl-pane__heading\">\n <h3 class=\"mt-cl-pane__title\">\n {{ item.title || item.moduleKey || defaultTitle(item) }}\n </h3>\n @if (paneMeta(item); as meta) {\n <p\n class=\"mt-cl-pane__meta\"\n [class.mt-cl-pane__meta--error]=\"!!item.error\"\n >\n {{ meta }}\n </p>\n }\n </div>\n\n @if (item.config.headerStart) {\n <div class=\"mt-cl-pane__header-start\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerStart\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n\n @if (item.config.headerEnd) {\n <div class=\"mt-cl-pane__actions\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerEnd\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n }\n\n <ng-container\n [ngTemplateOutlet]=\"itemContent\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n }\n </div>\n </div>\n} @else {\n <div class=\"flex flex-col gap-4\">\n @for (item of items(); track item.key) {\n <section class=\"flex flex-col gap-4\">\n @if (item.config.showHeader) {\n <div class=\"flex w-full items-center gap-2\">\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (item.config.collapse.enabled) {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\n [icon]=\"\n item.expanded\n ? item.config.collapse.collapseIcon\n : item.config.collapse.expandIcon\n \"\n (onClick)=\"toggleExpanded(item.key)\"\n />\n }\n <h3 class=\"m-0 text-lg font-semibold\">\n {{ item.title || item.moduleKey || defaultTitle(item) }}\n </h3>\n @if (item.config.headerStart) {\n <div class=\"flex items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerStart\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n @if (item.config.headerEnd) {\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerEnd\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n }\n\n @if (item.expanded || !item.config.collapse.enabled) {\n <ng-container\n [ngTemplateOutlet]=\"itemContent\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n }\n </section>\n }\n </div>\n}\n\n<!--\n The one place a list's body is described. Both layouts render through it so\n the navigator pane and the stacked sections can never drift apart.\n-->\n<ng-template #itemContent let-rawItem>\n @let item = asItem(rawItem);\n\n @if (item.config.templateContent) {\n <ng-container\n [ngTemplateOutlet]=\"item.config.templateContent\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n } @else if (item.type === \"informative\") {\n <mt-client-list-informative-view [state]=\"item\" />\n } @else if (item.areaType === \"table\") {\n <mt-client-list-table-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [actionShape]=\"item.config.actionShape\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (rowClick)=\"onTableRowClick(item, $event)\"\n />\n } @else {\n <mt-client-list-cards-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (loadMore)=\"loadMoreCards(item.key)\"\n (cardClick)=\"onCardClick(item, $event)\"\n />\n }\n</ng-template>\n", styles: [".mt-cl-shell{display:grid;grid-template-columns:minmax(0,1fr);gap:1rem;align-items:start}.mt-cl-shell--rail{grid-template-columns:16.5rem minmax(0,1fr);gap:1.5rem}.mt-cl-shell--rail .mt-cl-shell__rail{padding-inline-end:1.25rem;border-inline-end:1px solid var(--p-content-border-color, #e5e7eb)}.mt-cl-shell__pane{display:flex;flex-direction:column;gap:1rem;min-width:0}.mt-cl-pane__header{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.75rem 1rem}.mt-cl-pane__heading{display:flex;flex-direction:column;gap:.125rem;min-width:0;flex:1 1 auto}.mt-cl-pane__title{margin:0;color:var(--p-text-color, #111827);font-size:1.25rem;font-weight:600;line-height:1.75rem;overflow-wrap:anywhere}.mt-cl-pane__meta{margin:0;color:var(--p-text-muted-color, #6b7280);font-size:.8125rem;line-height:1.25rem}.mt-cl-pane__meta--error{color:var(--p-red-500, #ef4444)}.mt-cl-pane__actions{display:flex;flex:0 0 auto;align-items:center;gap:.5rem;margin-inline-start:auto}.mt-cl-pane__header-start{display:flex;flex:0 0 auto;align-items:center;gap:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ClientListTableView, selector: "mt-client-list-table-view", inputs: ["state", "rowActions", "actionShape", "rowActionsLoadingFn"], outputs: ["lazyLoad", "rowClick", "rowActionsRequested"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state", "rowActions", "rowActionsLoadingFn"], outputs: ["lazyLoad", "loadMore", "cardClick", "rowActionsRequested"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }, { kind: "component", type: ClientListNavigator, selector: "mt-client-list-navigator", inputs: ["entries", "activeKey", "panelId"], outputs: ["selected"] }] });
2677
2963
  }
2678
2964
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, decorators: [{
2679
2965
  type: Component,
@@ -2683,12 +2969,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2683
2969
  ClientListTableView,
2684
2970
  ClientListCardsView,
2685
2971
  ClientListInformativeView,
2972
+ ClientListNavigator,
2686
2973
  ], providers: [
2687
2974
  ClientListStateService,
2688
2975
  ClientListRuntimeActionsService,
2689
2976
  ClientListToolbarService,
2690
- ], template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n @if (item.config.showHeader) {\r\n <div class=\"flex w-full items-center gap-2\">\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (item.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n item.expanded\r\n ? item.config.collapse.collapseIcon\r\n : item.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(item.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-semibold\">\r\n {{ item.title || item.moduleKey || defaultTitle(item) }}\r\n </h3>\r\n @if (item.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (item.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.config.templateContent) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.templateContent\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n } @else if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"rowActionsFor(item)\"\r\n [actionShape]=\"item.config.actionShape\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (rowClick)=\"onTableRowClick(item, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view\r\n [state]=\"item\"\r\n [rowActions]=\"rowActionsFor(item)\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (loadMore)=\"loadMoreCards(item.key)\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n" }]
2691
- }], ctorParameters: () => [], propDecorators: { configurations: [{ type: i0.Input, args: [{ isSignal: true, alias: "configurations", required: true }] }], defaultTake: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultTake", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], errored: [{ type: i0.Output, args: ["errored"] }], itemClicked: [{ type: i0.Output, args: ["itemClicked"] }] } });
2977
+ ], template: "@if (useNavigator()) {\n <!--\n Master/detail: the rail lists every configured module, the pane renders\n only the selected one. Deliberately NOT a hidden-but-mounted pane per\n module \u2014 a seven-module workspace tab would otherwise mount seven tables,\n each with its own virtual scroller and paginator, to show one.\n -->\n <div class=\"mt-cl-shell\" [class.mt-cl-shell--rail]=\"isRailLayout()\">\n <mt-client-list-navigator\n class=\"mt-cl-shell__rail\"\n [class.mt-cl-nav--rail]=\"isRailLayout()\"\n [entries]=\"navigatorEntries()\"\n [activeKey]=\"activeKey()\"\n [panelId]=\"panelId\"\n (selected)=\"setActiveKey($event)\"\n />\n\n <div class=\"mt-cl-shell__pane\" role=\"tabpanel\" [id]=\"panelId\">\n @if (activeItem(); as item) {\n @if (item.config.showHeader) {\n <div class=\"mt-cl-pane__header\">\n <div class=\"mt-cl-pane__heading\">\n <h3 class=\"mt-cl-pane__title\">\n {{ item.title || item.moduleKey || defaultTitle(item) }}\n </h3>\n @if (paneMeta(item); as meta) {\n <p\n class=\"mt-cl-pane__meta\"\n [class.mt-cl-pane__meta--error]=\"!!item.error\"\n >\n {{ meta }}\n </p>\n }\n </div>\n\n @if (item.config.headerStart) {\n <div class=\"mt-cl-pane__header-start\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerStart\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n\n @if (item.config.headerEnd) {\n <div class=\"mt-cl-pane__actions\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerEnd\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n }\n\n <ng-container\n [ngTemplateOutlet]=\"itemContent\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n }\n </div>\n </div>\n} @else {\n <div class=\"flex flex-col gap-4\">\n @for (item of items(); track item.key) {\n <section class=\"flex flex-col gap-4\">\n @if (item.config.showHeader) {\n <div class=\"flex w-full items-center gap-2\">\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (item.config.collapse.enabled) {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\n [icon]=\"\n item.expanded\n ? item.config.collapse.collapseIcon\n : item.config.collapse.expandIcon\n \"\n (onClick)=\"toggleExpanded(item.key)\"\n />\n }\n <h3 class=\"m-0 text-lg font-semibold\">\n {{ item.title || item.moduleKey || defaultTitle(item) }}\n </h3>\n @if (item.config.headerStart) {\n <div class=\"flex items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerStart\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n @if (item.config.headerEnd) {\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerEnd\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n }\n\n @if (item.expanded || !item.config.collapse.enabled) {\n <ng-container\n [ngTemplateOutlet]=\"itemContent\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n }\n </section>\n }\n </div>\n}\n\n<!--\n The one place a list's body is described. Both layouts render through it so\n the navigator pane and the stacked sections can never drift apart.\n-->\n<ng-template #itemContent let-rawItem>\n @let item = asItem(rawItem);\n\n @if (item.config.templateContent) {\n <ng-container\n [ngTemplateOutlet]=\"item.config.templateContent\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n } @else if (item.type === \"informative\") {\n <mt-client-list-informative-view [state]=\"item\" />\n } @else if (item.areaType === \"table\") {\n <mt-client-list-table-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [actionShape]=\"item.config.actionShape\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (rowClick)=\"onTableRowClick(item, $event)\"\n />\n } @else {\n <mt-client-list-cards-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (loadMore)=\"loadMoreCards(item.key)\"\n (cardClick)=\"onCardClick(item, $event)\"\n />\n }\n</ng-template>\n", styles: [".mt-cl-shell{display:grid;grid-template-columns:minmax(0,1fr);gap:1rem;align-items:start}.mt-cl-shell--rail{grid-template-columns:16.5rem minmax(0,1fr);gap:1.5rem}.mt-cl-shell--rail .mt-cl-shell__rail{padding-inline-end:1.25rem;border-inline-end:1px solid var(--p-content-border-color, #e5e7eb)}.mt-cl-shell__pane{display:flex;flex-direction:column;gap:1rem;min-width:0}.mt-cl-pane__header{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.75rem 1rem}.mt-cl-pane__heading{display:flex;flex-direction:column;gap:.125rem;min-width:0;flex:1 1 auto}.mt-cl-pane__title{margin:0;color:var(--p-text-color, #111827);font-size:1.25rem;font-weight:600;line-height:1.75rem;overflow-wrap:anywhere}.mt-cl-pane__meta{margin:0;color:var(--p-text-muted-color, #6b7280);font-size:.8125rem;line-height:1.25rem}.mt-cl-pane__meta--error{color:var(--p-red-500, #ef4444)}.mt-cl-pane__actions{display:flex;flex:0 0 auto;align-items:center;gap:.5rem;margin-inline-start:auto}.mt-cl-pane__header-start{display:flex;flex:0 0 auto;align-items:center;gap:.5rem}\n"] }]
2978
+ }], ctorParameters: () => [], propDecorators: { configurations: [{ type: i0.Input, args: [{ isSignal: true, alias: "configurations", required: true }] }], defaultTake: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultTake", required: false }] }], multiLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiLayout", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], errored: [{ type: i0.Output, args: ["errored"] }], itemClicked: [{ type: i0.Output, args: ["itemClicked"] }], activeListChanged: [{ type: i0.Output, args: ["activeListChanged"] }] } });
2692
2979
 
2693
2980
  /**
2694
2981
  * Generated bundle index. Do not edit.