@elasticias/ui 1.0.29 → 1.0.31

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.
@@ -4544,13 +4544,28 @@ class EfDataCardComponent {
4544
4544
  * not have.
4545
4545
  */
4546
4546
  onRowHeadActivate(row, index, event) {
4547
- if (!this.hasDetail())
4548
- return;
4549
4547
  const target = event.target;
4550
- if (target?.closest('.mrow__actions'))
4548
+ if (target?.closest('.mrow__actions, .mrow__disclosure'))
4549
+ return;
4550
+ // A phone has no double-click, so before this the head could only ever
4551
+ // expand: the record itself was reachable only through the row-actions
4552
+ // menu. Where the screen wires navigation, the head now opens the row
4553
+ // and the chevron beside it owns expansion — the chevron was already
4554
+ // drawn, just decorative. Where it does not, the head keeps expanding.
4555
+ if (this.rowNavigable()) {
4556
+ event.preventDefault();
4557
+ this.rowDoubleClick.emit(row);
4558
+ return;
4559
+ }
4560
+ if (!this.hasDetail())
4551
4561
  return;
4552
4562
  this.toggleExpanded(row, index);
4553
4563
  }
4564
+ /** The chevron owns expansion once the head is busy navigating. */
4565
+ onDisclosureToggle(row, index, event) {
4566
+ event.stopPropagation();
4567
+ this.toggleExpanded(row, index);
4568
+ }
4554
4569
  toggleExpanded(row, index) {
4555
4570
  const key = this.rowKey(row, index);
4556
4571
  const next = new Set(this.expandedRows());
@@ -4614,6 +4629,10 @@ class EfDataCardComponent {
4614
4629
  /** Fields a collapsed card shows. A row shows none until it opens. */
4615
4630
  cardPreviewColumns = computed(() => this.detailColumns().slice(0, EfDataCardComponent.CARD_PREVIEW_FIELDS), ...(ngDevMode ? [{ debugName: "cardPreviewColumns" }] : /* istanbul ignore next */ []));
4616
4631
  hasDetail = computed(() => this.detailColumns().length > 0, ...(ngDevMode ? [{ debugName: "hasDetail" }] : /* istanbul ignore next */ []));
4632
+ /** Whether the screen wired an open-the-record action to this table. */
4633
+ rowNavigable = computed(() => this.rowDoubleClickable(), ...(ngDevMode ? [{ debugName: "rowNavigable" }] : /* istanbul ignore next */ []));
4634
+ /** The head is a control when it either opens the row or expands it. */
4635
+ headIsControl = computed(() => this.rowNavigable() || this.hasDetail(), ...(ngDevMode ? [{ debugName: "headIsControl" }] : /* istanbul ignore next */ []));
4617
4636
  /** Only worth a toggle when opening actually reveals something. */
4618
4637
  hasMoreThanPreview = computed(() => this.detailColumns().length > EfDataCardComponent.CARD_PREVIEW_FIELDS, ...(ngDevMode ? [{ debugName: "hasMoreThanPreview" }] : /* istanbul ignore next */ []));
4619
4638
  /** Fields visible for a row right now, given layout and open state. */
@@ -4655,9 +4674,12 @@ class EfDataCardComponent {
4655
4674
  * state in the body.
4656
4675
  *
4657
4676
  * It is the *trigger*, not the copy: screens set it from
4658
- * `err?.message`, which is whatever the HTTP layer threw. The body
4659
- * state words itself from translated keys instead, so a failed list
4660
- * never shows an operator a status line or an exception string.
4677
+ * `err?.message`, which is whatever the HTTP layer threw. Neither the
4678
+ * chip nor the body state prints it — both word themselves from
4679
+ * translated keys, so a failed list never shows an operator a status
4680
+ * line or an exception string. The raw value is kept on the chip's
4681
+ * `data-error` attribute, which devtools can read and no reader can
4682
+ * see.
4661
4683
  */
4662
4684
  errorMsg = input('', ...(ngDevMode ? [{ debugName: "errorMsg" }] : /* istanbul ignore next */ []));
4663
4685
  loadingKey = input('common_loading_msg', ...(ngDevMode ? [{ debugName: "loadingKey" }] : /* istanbul ignore next */ []));
@@ -5164,13 +5186,51 @@ class EfDataCardComponent {
5164
5186
  const detail = event.detail;
5165
5187
  this.openMenuCount.update(c => Math.max(0, c + (detail?.open ? 1 : -1)));
5166
5188
  }
5167
- /** Fires `rowDoubleClick` unless the dblclick originated on an
5168
- * interactive child (checkbox, button, link, form control). */
5169
- onRowDoubleClick(row, event) {
5189
+ /**
5190
+ * Children that own their own click. Opening the row on top of them would
5191
+ * run two things from one gesture.
5192
+ */
5193
+ static INTERACTIVE_CHILD = 'button, a, input, select, textarea, label, [role="button"], [role="menu"], [role="menuitem"]';
5194
+ /**
5195
+ * Bounded by the row on purpose. The row itself is given `role="button"`
5196
+ * when it is openable, so an unbounded `closest()` matches the row as its
5197
+ * own ancestor and suppresses every click on it — the interaction looks
5198
+ * wired and does nothing. Only a control *inside* the row counts.
5199
+ */
5200
+ isInteractiveTarget(target, boundary) {
5201
+ const row = boundary;
5202
+ const hit = target?.closest(EfDataCardComponent.INTERACTIVE_CHILD);
5203
+ return !!hit && hit !== row && !!row?.contains(hit);
5204
+ }
5205
+ /**
5206
+ * A drag that selects text ends in a click. Opening the row on that click
5207
+ * throws the selection away at the instant it was made, which is worse
5208
+ * than not opening, because the work is lost rather than merely delayed.
5209
+ */
5210
+ hasTextSelection() {
5211
+ const sel = typeof window === 'undefined' ? null : window.getSelection();
5212
+ return !!sel && !sel.isCollapsed;
5213
+ }
5214
+ /**
5215
+ * Open a row with one click.
5216
+ *
5217
+ * This used to take two. The tell was not the pointer but the keyboard:
5218
+ * Enter on a focused row already opened it with a single press, because a
5219
+ * keyboard cannot produce a double-click at all. So the component was
5220
+ * already committed to single activation and only the mouse was asking
5221
+ * twice — and the row paints `cursor: pointer` the whole time, which says
5222
+ * a click does something.
5223
+ *
5224
+ * The output is still named `rowDoubleClick` so the twelve screens bound
5225
+ * to it keep working untouched. The name is now wrong; renaming it is a
5226
+ * breaking change to a published component and belongs in its own commit.
5227
+ */
5228
+ onRowClick(row, event) {
5170
5229
  if (!this.rowDoubleClickable())
5171
5230
  return;
5172
- const target = event.target;
5173
- if (target?.closest('button, a, input, select, textarea, label'))
5231
+ if (this.isInteractiveTarget(event.target, event.currentTarget))
5232
+ return;
5233
+ if (this.hasTextSelection())
5174
5234
  return;
5175
5235
  this.rowDoubleClick.emit(row);
5176
5236
  }
@@ -5214,7 +5274,7 @@ class EfDataCardComponent {
5214
5274
  };
5215
5275
  trackByColumn = (_, col) => col.id;
5216
5276
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDataCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5217
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDataCardComponent, isStandalone: true, selector: "ef-data-card", inputs: { mobileLayout: { classPropertyName: "mobileLayout", publicName: "mobileLayout", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, trackByField: { classPropertyName: "trackByField", publicName: "trackByField", isSignal: true, isRequired: false, transformFunction: null }, pageNumber: { classPropertyName: "pageNumber", publicName: "pageNumber", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, totalCount: { classPropertyName: "totalCount", publicName: "totalCount", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, hidePager: { classPropertyName: "hidePager", publicName: "hidePager", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, errorMsg: { classPropertyName: "errorMsg", publicName: "errorMsg", isSignal: true, isRequired: false, transformFunction: null }, loadingKey: { classPropertyName: "loadingKey", publicName: "loadingKey", isSignal: true, isRequired: false, transformFunction: null }, rowDoubleClickable: { classPropertyName: "rowDoubleClickable", publicName: "rowDoubleClickable", isSignal: true, isRequired: false, transformFunction: null }, hasActionsColumn: { classPropertyName: "hasActionsColumn", publicName: "hasActionsColumn", isSignal: true, isRequired: false, transformFunction: null }, rowActionsContext: { classPropertyName: "rowActionsContext", publicName: "rowActionsContext", isSignal: true, isRequired: false, transformFunction: null }, showViewAction: { classPropertyName: "showViewAction", publicName: "showViewAction", isSignal: true, isRequired: false, transformFunction: null }, showEditAction: { classPropertyName: "showEditAction", publicName: "showEditAction", isSignal: true, isRequired: false, transformFunction: null }, showDuplicateAction: { classPropertyName: "showDuplicateAction", publicName: "showDuplicateAction", isSignal: true, isRequired: false, transformFunction: null }, showDeleteAction: { classPropertyName: "showDeleteAction", publicName: "showDeleteAction", isSignal: true, isRequired: false, transformFunction: null }, showDensityControl: { classPropertyName: "showDensityControl", publicName: "showDensityControl", isSignal: true, isRequired: false, transformFunction: null }, showColumnPicker: { classPropertyName: "showColumnPicker", publicName: "showColumnPicker", isSignal: true, isRequired: false, transformFunction: null }, showExportControl: { classPropertyName: "showExportControl", publicName: "showExportControl", isSignal: true, isRequired: false, transformFunction: null }, exporting: { classPropertyName: "exporting", publicName: "exporting", isSignal: true, isRequired: false, transformFunction: null }, screenContext: { classPropertyName: "screenContext", publicName: "screenContext", isSignal: true, isRequired: false, transformFunction: null }, searchTerm: { classPropertyName: "searchTerm", publicName: "searchTerm", isSignal: true, isRequired: false, transformFunction: null }, tableKey: { classPropertyName: "tableKey", publicName: "tableKey", isSignal: true, isRequired: false, transformFunction: null }, defaultDensity: { classPropertyName: "defaultDensity", publicName: "defaultDensity", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, fixedLayout: { classPropertyName: "fixedLayout", publicName: "fixedLayout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", rowDoubleClick: "rowDoubleClick", rowAction: "rowAction", exportRequest: "exportRequest", clearSearch: "clearSearch", retry: "retry", rowReorder: "rowReorder" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "ef-row-actions-toggle": "onRowActionsToggle($event)" } }, queries: [{ propertyName: "bodyTemplates", predicate: EfColumnTemplateDirective }, { propertyName: "headerTemplates", predicate: EfColumnHeaderTemplateDirective }], ngImport: i0, template: "<!-- One cell renderer, shared by the table, the mobile row and the mobile\n detail. A new column type is added here once, not three times. -->\n<ng-template #cellTpl let-row let-col=\"col\" let-index=\"index\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col, index: index }\"></ng-container>\n } @else {\n @switch (col.type) {\n @case ('number') {\n {{ $any(cellValue(row, col)) | number:numberFormat(col):col.locale }}\n }\n @case ('money') {\n {{ $any(cellValue(row, col)) | currency:(col.currencyCode || 'EUR'):(col.currencyDisplay || 'symbol'):numberFormat(col):col.locale }}\n }\n @case ('date') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy'):undefined:col.locale }}\n }\n @case ('datetime') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy HH:mm'):undefined:col.locale }}\n }\n @case ('boolean') {\n @if (cellValue(row, col)) {\n <span class=\"bool yes\"><span class=\"dot\"></span> \u2713</span>\n } @else {\n <span class=\"bool no\"><span class=\"dot\"></span> \u2014</span>\n }\n }\n @case ('mono') {\n <span class=\"ref\">{{ cellValue(row, col) }}</span>\n }\n @case ('chip') {\n <span [class]=\"'chip ' + (col.chipPrefix || 'chip-') + cellValue(row, col)\">\n \u25CF {{ cellValue(row, col) }}\n </span>\n }\n @case ('status') {\n <ef-status-chip\n [referenceKey]=\"col.referenceKey || ''\"\n [code]=\"$any(cellValue(row, col))\"\n />\n }\n @case ('reference') {\n {{ resolveReference(cellValue(row, col), col) }}\n }\n @default {\n {{ cellValue(row, col) }}\n }\n }\n }\n</ng-template>\n\n<div class=\"tbl-wrap\" [class.has-open-menu]=\"openMenuCount() > 0 || openTool() !== null\" [attr.data-density]=\"density()\">\n <!-- \u2500\u2500 HEAD ROW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"tbl-head\">\n <div class=\"count small\">\n @if (loading()) {\n <span>{{ loadingKey() | translate }}</span>\n } @else if (errorMsg()) {\n <span style=\"color: var(--st-cancelled-fg); font-weight: 600;\">\u26A0 {{ errorMsg() }}</span>\n } @else {\n <ng-content select=\"[tbl-head-info]\"></ng-content>\n }\n </div>\n <div class=\"tbl-tools\">\n <ng-content select=\"[tbl-head-actions]\"></ng-content>\n\n @if (canExport()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [disabled]=\"exporting()\"\n [attr.aria-busy]=\"exporting()\"\n (click)=\"requestExport()\"\n >\n <i\n class=\"pi\"\n [class.pi-download]=\"!exporting()\"\n [class.pi-spinner]=\"exporting()\"\n [class.is-spinning]=\"exporting()\"\n aria-hidden=\"true\"\n ></i>\n {{ (exporting() ? 'common_export_running' : 'common_export') | translate }}\n </button>\n </div>\n }\n\n @if (showDensityControl()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'density'\"\n [attr.aria-expanded]=\"openTool() === 'density'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('density')\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n {{ 'common_density' | translate }}\n </button>\n @if (openTool() === 'density') {\n <div class=\"tbl-tool__menu\" role=\"menu\">\n @for (opt of densityOptions; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"density() === opt.value\"\n [class.is-active]=\"density() === opt.value\"\n (click)=\"setDensity(opt.value)\"\n >\n <i class=\"pi\" [ngClass]=\"opt.icon\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ opt.labelKey | translate }}</span>\n @if (density() === opt.value) {\n <i class=\"pi pi-check tbl-tool__check\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showColumnPicker()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'columns'\"\n [attr.aria-expanded]=\"openTool() === 'columns'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('columns')\"\n >\n <i class=\"pi pi-th-large\" aria-hidden=\"true\"></i>\n {{ 'common_columns' | translate }}\n </button>\n @if (openTool() === 'columns') {\n <div class=\"tbl-tool__menu tbl-tool__menu--wide\" role=\"menu\">\n @for (col of hideableColumns(); track col.id) {\n <button\n type=\"button\"\n role=\"menuitemcheckbox\"\n [attr.aria-checked]=\"!isColumnHidden(col.id)\"\n [class.is-active]=\"!isColumnHidden(col.id)\"\n (click)=\"toggleColumn(col.id)\"\n >\n <i class=\"pi\" [class.pi-check]=\"!isColumnHidden(col.id)\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{\n col.headerKey ? (col.headerKey | translate) : (col.header || col.id)\n }}</span>\n </button>\n }\n <div class=\"tbl-tool__sep\"></div>\n <button type=\"button\" role=\"menuitem\" (click)=\"resetColumns()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ 'common_columns_reset' | translate }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 TABLE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (isMobile()) {\n <!-- \u2500\u2500 MOBILE LIST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A six-column table on a 390px screen either overflows or\n squeezes every column to nothing, so the rows become a list.\n Same columns, same cell renderers; each column just declares\n (or is given) a role. Both shapes expand to the same detail \u2014\n they differ only in how much they show collapsed. -->\n <ul\n class=\"mlist\"\n [attr.data-layout]=\"effectiveMobileLayout()\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n >\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <li class=\"mrow\" [class.is-open]=\"isExpanded(row, i)\">\n <div\n class=\"mrow__head\"\n [class.is-tappable]=\"hasDetail()\"\n [attr.role]=\"hasDetail() ? 'button' : null\"\n [attr.tabindex]=\"hasDetail() ? 0 : null\"\n [attr.aria-expanded]=\"hasDetail() ? isExpanded(row, i) : null\"\n (click)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.enter)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.space)=\"onRowHeadActivate(row, i, $event)\"\n >\n <div class=\"mrow__lead\">\n @if (primaryColumn(); as pc) {\n <span class=\"mrow__primary\" efOverflowTooltip efTooltip efTooltipPosition=\"top\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: pc, index: i }\"></ng-container>\n </span>\n }\n @if (effectiveMobileLayout() === 'row' && secondaryColumns().length) {\n <span class=\"mrow__secondary\">\n @for (sc of secondaryColumns(); track sc.id) {\n <span class=\"mrow__sec-part\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: sc, index: i }\"></ng-container>\n </span>\n }\n </span>\n }\n </div>\n\n <div class=\"mrow__trail\">\n @if (statusColumn(); as st) {\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: st, index: i }\"></ng-container>\n }\n @if (hasActionsColumn()) {\n <span class=\"mrow__actions\">\n <ef-row-actions [items]=\"defaultRowActions(row)\" [context]=\"rowActionsContext()\" />\n </span>\n }\n @if (hasDetail() && effectiveMobileLayout() === 'row') {\n <!-- Says the row opens, and which way it is now. Decorative:\n the whole head is the control, and it carries the state. -->\n <i\n class=\"pi pi-chevron-down mrow__chevron\"\n [class.is-open]=\"isExpanded(row, i)\"\n aria-hidden=\"true\"\n ></i>\n }\n </div>\n </div>\n\n @if (visibleDetail(row, i).length) {\n <dl class=\"mrow__detail\">\n @for (col of visibleDetail(row, i); track col.id) {\n <div class=\"mrow__field\">\n <dt>{{ $any(col).headerKey ? ($any(col).headerKey | translate) : $any(col).header }}</dt>\n <dd [class]=\"cellAlignClass($any(col))\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"></ng-container>\n </dd>\n </div>\n }\n </dl>\n }\n\n @if (hasMoreThanPreview() && effectiveMobileLayout() === 'card') {\n <button class=\"mrow__more\" type=\"button\" (click)=\"toggleExpanded(row, i)\">\n <span>{{ (isExpanded(row, i) ? 'common_view_less' : 'common_view_more') | translate }}</span>\n <i class=\"pi pi-chevron-down mrow__chevron\" [class.is-open]=\"isExpanded(row, i)\" aria-hidden=\"true\"></i>\n </button>\n }\n </li>\n } @empty {\n <!-- No rows has three meanings and they must not share a screen:\n still loading is a skeleton, the request failed is the error\n state, finished-and-empty is the empty state. Saying \"nothing\n here yet\" to someone whose data is still in flight \u2014 or whose\n request just failed \u2014 is a lie the whole time it shows. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <li class=\"mrow mrow--skeleton\">\n <ef-skeleton variant=\"text\" [rows]=\"2\" />\n </li>\n }\n } @else if (showError()) {\n <li class=\"mrow mrow--empty\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </li>\n } @else {\n <li class=\"mrow mrow--empty\">\n <ng-content select=\"[empty-state-mobile]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </li>\n }\n }\n </ul>\n } @else {\n <table class=\"tbl\" [class.tbl--fixed]=\"fixedLayout()\">\n <thead>\n <tr>\n @if (reorderable()) {\n <th class=\"tbl-drag-h\" aria-hidden=\"true\"></th>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <!-- A sortable header is a real <button> inside the <th>, not a\n click handler on the cell. The cell carries aria-sort so the\n current order is announced; the arrow stays decorative.\n Sorting was previously mouse-only and silent to assistive\n technology, on every list screen in the product. -->\n <th\n [class]=\"headerAlignClass(col)\"\n [class.is-sortable]=\"col.sortable\"\n [style.width]=\"col.width || null\"\n [attr.data-col]=\"col.id\"\n [attr.aria-sort]=\"ariaSort(col)\"\n >\n @if (col.sortable) {\n <button type=\"button\" class=\"tbl-sort\" (click)=\"onHeaderClick(col)\">\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n @if (sortIndicator(col)) {\n <span aria-hidden=\"true\" class=\"tbl-sort__arrow\">{{ sortIndicator(col) }}</span>\n }\n </button>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody [attr.aria-busy]=\"loading() ? 'true' : null\">\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n [class.is-dragging]=\"dragFrom() === i\"\n [class.is-drop-target]=\"dragFrom() !== null && dragFrom() !== i && dragTo() === i\"\n [attr.tabindex]=\"rowDoubleClickable() ? 0 : null\"\n [attr.role]=\"rowDoubleClickable() ? 'button' : null\"\n (dblclick)=\"onRowDoubleClick(row, $event)\"\n (keydown.enter)=\"onRowKeydown(row, $event)\"\n >\n @if (reorderable()) {\n <td class=\"tbl-drag\">\n <!-- The row is draggable only by this handle, so a drag never\n starts from a cell the operator meant to click or select.\n Arrow keys move the row for anyone not using a pointer. -->\n <button\n type=\"button\"\n class=\"tbl-drag__grip\"\n [attr.aria-label]=\"'common_reorder_row' | translate\"\n (pointerdown)=\"onDragPointerDown(i, $event)\"\n (pointermove)=\"onDragPointerMove($event)\"\n (pointerup)=\"onDragPointerUp()\"\n (pointercancel)=\"onDragPointerUp()\"\n (keydown)=\"onDragKeydown(i, $event)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n </td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td efOverflowTooltip efTooltip efTooltipPosition=\"top\" [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n @if (col.id === 'actions' && hasActionsColumn()) {\n <!-- Auto row-actions cell: View / Edit / Duplicate / Delete,\n gated by [show*Action] flags + ScreenContext perms. -->\n <ef-row-actions\n [items]=\"defaultRowActions(row)\"\n [context]=\"rowActionsContext()\"\n />\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"\n ></ng-container>\n }\n </td>\n }\n </tr>\n } @empty {\n <!-- Placeholder rows keep the real columns, so the table that\n arrives lands where the shimmer already was instead of\n reflowing the page under the reader's eyes. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <tr>\n @if (reorderable()) {\n <td class=\"tbl-drag\"></td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <td [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n <ef-skeleton variant=\"text\" [rows]=\"1\" />\n </td>\n }\n </tr>\n }\n } @else if (showError()) {\n <!-- The failure replaces the body, so it spans the full row \u2014\n crammed into column one it reads as a value, not a state. -->\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </td>\n </tr>\n } @else {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-content select=\"[empty-state]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n }\n\n <!-- \u2500\u2500 PAGER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Paging controls for an empty result set are chrome with nothing to\n page, so they stay out of the way until there is something to move\n through. -->\n @if (!hidePager() && rows().length > 0) {\n <ef-pager\n [pageNumber]=\"pageNumber()\"\n [pageSize]=\"pageSize()\"\n [totalCount]=\"totalCount()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n (pageChange)=\"pageChange.emit($event)\"\n (pageSizeChange)=\"pageSizeChange.emit($event)\"\n />\n }\n</div>\n\n<!-- \u2500\u2500 DEFAULT EMPTY STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Rendered when a screen projects nothing into [empty-state] /\n [empty-state-mobile]. It words itself from `searchTerm`, because\n \"nothing matched what you typed\" and \"there is nothing here yet\"\n are different situations needing different ways out. -->\n<ng-template #defaultEmptyTpl>\n <div class=\"tbl-empty\">\n <i\n class=\"pi tbl-empty__icon\"\n [class.pi-search]=\"!!searchTerm()\"\n [class.pi-inbox]=\"!searchTerm()\"\n aria-hidden=\"true\"\n ></i>\n\n @if (searchTerm()) {\n <p class=\"tbl-empty__title\">\n {{ 'common_empty_no_results' | translate: { term: searchTerm() } }}\n </p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_no_results_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"clearSearch.emit()\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n {{ 'common_empty_clear_filters' | translate }}\n </button>\n } @else {\n <p class=\"tbl-empty__title\">{{ 'common_empty_none_yet' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_none_yet_hint' | translate }}</p>\n }\n </div>\n</ng-template>\n\n<!-- \u2500\u2500 ERROR STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A request that failed and came back with nothing is not an empty\n collection, and must never borrow its words: \"nothing here yet\" is\n a confident false statement about the operator's data, and they may\n act on it. So this replaces the empty state outright, projected\n [empty-state] included \u2014 a screen's own \"create your first client\"\n copy is just as wrong here as the default.\n\n It says nothing about *what* failed. `errorMsg` is `err.message`\n from the HTTP layer, and a status line or an exception string tells\n an operator nothing they can act on; the head chip still carries it\n for anyone looking. This carries the two things that help: their\n data is intact, and the way to try again.\n\n role=\"alert\" goes on this div, never on the <tr> / <td> / <li> that\n hosts it \u2014 a role on a row or cell takes the table's semantics down\n with it. It fires on insertion, which is exactly when @if adds it. -->\n<ng-template #errorStateTpl>\n <div class=\"tbl-empty tbl-empty--error\" role=\"alert\">\n <i class=\"pi pi-exclamation-triangle tbl-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"tbl-empty__title\">{{ 'common_error_load_failed' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_error_load_failed_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"retry.emit()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n {{ 'common_retry' | translate }}\n </button>\n </div>\n</ng-template>\n\n<!-- Header label, shared by the sortable and plain header cells so the\n projection rules stay in one place. -->\n<ng-template #headerLabelTpl let-col=\"col\">\n @if (headerTemplate(col.id); as headerTpl) {\n <ng-container *ngTemplateOutlet=\"headerTpl; context: { $implicit: col, col: col }\"></ng-container>\n } @else if (col.headerKey) {\n <span>{{ col.headerKey | translate }}</span>\n } @else if (col.header) {\n <span>{{ col.header }}</span>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";.tbl thead th.is-sortable{cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl thead th.is-sortable:hover{color:var(--text)}.tbl tbody tr.is-row-clickable{cursor:pointer}.tbl-tools{display:flex;gap:6px;align-items:center}.tbl-tool{position:relative}.tbl-tool>.btn.open{color:var(--text);background:var(--paper)}.tbl-tool__menu{position:absolute;top:calc(100% + 8px);inset-inline-end:0;z-index:40;min-width:212px;max-height:340px;overflow-y:auto;padding:6px;display:grid;gap:1px;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);animation:row-actions-menu-in var(--t-base) var(--ease-spring)}.tbl-tool__menu--wide{min-width:248px}.tbl-tool__menu button{appearance:none;background:transparent;border:0;width:100%;text-align:start;font:inherit;font-size:13.5px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:var(--r-md);cursor:pointer;transition:background-color var(--t-fast) var(--ease-out)}.tbl-tool__menu button:hover,.tbl-tool__menu button:focus-visible{background:var(--paper);outline:0}.tbl-tool__menu button>.pi{width:16px;flex:none;font-size:14px;color:var(--muted)}.tbl-tool__menu button.is-active>.pi{color:var(--module, var(--text))}.tbl-tool__label{flex:1 1 auto;min-width:0}.tbl-tool__check{color:var(--module, var(--text))}.tbl-tool__sep{margin:5px 2px;border-top:1px solid var(--rule)}.tbl-wrap[data-density=compact] .tbl tbody td{padding-top:4px;padding-bottom:4px}.tbl-wrap[data-density=comfortable] .tbl tbody td{padding-top:16px;padding-bottom:16px}.mlist{list-style:none;margin:0;padding:0;display:grid}.mrow{border-top:1px solid var(--rule)}.mrow:first-child{border-top:0}.mrow__head{display:flex;align-items:flex-start;gap:var(--s-3);padding:12px var(--s-4);min-height:var(--hit-touch)}.mrow__head.is-tappable{cursor:pointer}.mrow__head.is-tappable:active{background:var(--paper)}.mrow__lead{flex:1;min-width:0;display:grid;gap:2px}.mrow__primary{font-weight:700;font-size:14px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mrow__secondary{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px;font-size:12.5px;color:var(--text-mute);min-width:0}.mrow__secondary .mrow__sec-part+.mrow__sec-part:before{content:\"\\b7\";margin-inline-end:6px;color:var(--text-soft)}.mrow__trail{display:flex;align-items:center;justify-content:flex-end;gap:var(--s-2);flex-shrink:0;min-height:20px}.mrow__actions{display:inline-flex}.mrow__detail{margin:0;padding:0 var(--s-4) 12px;display:grid;gap:1px}.mrow__field{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-3);padding:7px 0;border-top:1px solid var(--rule-soft)}.mrow__field dt{font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-mute);flex-shrink:0}.mrow__field dd{margin:0;font-size:13.5px;color:var(--text);text-align:end;min-width:0;overflow-wrap:anywhere}.mrow__more{appearance:none;width:100%;background:transparent;border:0;border-top:1px solid var(--rule-soft);padding:10px;font:inherit;font-size:13px;font-weight:600;color:var(--tenant-500);cursor:pointer}.mlist[data-layout=card]{gap:var(--s-3);padding:var(--s-3) var(--s-3) 0}.mlist[data-layout=card] .mrow{border:1px solid var(--rule);border-radius:var(--r-lg);background:var(--paper-alt);overflow:hidden}.mlist[data-layout=card] .mrow__head{border-bottom:1px solid var(--rule)}.mrow__chevron{font-size:11px;color:var(--text-soft);transition:transform var(--t-fast) var(--ease-out);flex-shrink:0}.mrow__chevron.is-open{transform:rotate(180deg);color:var(--text-mute)}.mrow__head.is-tappable:focus-visible{outline:2px solid var(--tenant-400);outline-offset:-2px}.mrow__more{display:flex;align-items:center;justify-content:center;gap:6px}.tbl-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-2);padding:var(--s-9) var(--s-4);text-align:center}.tbl-empty__icon{font-size:28px;line-height:1;color:var(--ink-400);margin-block-end:var(--s-1)}.tbl-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--text);max-inline-size:46ch;overflow-wrap:break-word}.tbl-empty__hint{margin:0;font-size:13px;color:var(--ink-600);max-inline-size:52ch;text-wrap:pretty}.tbl-empty__action{margin-block-start:var(--s-3);min-block-size:var(--hit-base)}.tbl-empty--error .tbl-empty__icon{color:var(--st-cancelled-fg)}.mrow--empty{padding:0}.mrow--skeleton{padding:12px var(--s-4)}@media(max-width:767px){.tbl-empty{padding:var(--s-7) var(--s-4)}.tbl-empty__action{min-block-size:var(--hit-touch)}}.pi.is-spinning{animation:ef-export-spin .9s linear infinite}@keyframes ef-export-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.pi.is-spinning{animation:none}}.tbl th.tbl-drag-h,.tbl td.tbl-drag{width:28px;padding-inline:4px 0;text-align:center}.tbl td.tbl-drag{cursor:default}.tbl .tbl-drag__grip{display:inline-flex;align-items:center;justify-content:center;width:22px;height:26px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--text-soft);cursor:grab;touch-action:none;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl .tbl-drag__grip:hover{background:var(--paper);color:var(--text)}.tbl .tbl-drag__grip:focus-visible{outline:2px solid var(--tenant, var(--rule));outline-offset:1px}.tbl .tbl-drag__grip:active{cursor:grabbing}.tbl .tbl-drag__grip i{font-size:12px}.tbl tbody tr.is-dragging>td{background:var(--paper);opacity:.55}.tbl tbody tr.is-drop-target>td{box-shadow:inset 0 2px 0 var(--tenant, var(--rule))}.tbl.tbl--fixed{table-layout:fixed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfPagerComponent, selector: "ef-pager", inputs: ["pageNumber", "pageSize", "totalCount", "pageSizeOptions", "linesPerPageLabelKey", "ofLabelKey", "prevLabelKey", "nextLabelKey", "hideSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: EfStatusChipComponent, selector: "ef-status-chip", inputs: ["referenceKey", "code", "color", "label", "labelKey", "hideDot", "showCode"] }, { kind: "component", type: EfSkeletonComponent, selector: "ef-skeleton", inputs: ["variant", "rows", "height"] }, { kind: "component", type: EfRowActionsComponent, selector: "ef-row-actions", inputs: ["items", "context", "triggerIcon", "triggerAriaKey", "disabled", "menuLeft"], outputs: ["opened", "closed"] }, { kind: "directive", type: EfOverflowTooltipDirective, selector: "[efOverflowTooltip]" }, { kind: "directive", type: EfTooltipDirective, selector: "[efTooltip]" }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$3.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i1$3.DatePipe, name: "date" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5277
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDataCardComponent, isStandalone: true, selector: "ef-data-card", inputs: { mobileLayout: { classPropertyName: "mobileLayout", publicName: "mobileLayout", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, trackByField: { classPropertyName: "trackByField", publicName: "trackByField", isSignal: true, isRequired: false, transformFunction: null }, pageNumber: { classPropertyName: "pageNumber", publicName: "pageNumber", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, totalCount: { classPropertyName: "totalCount", publicName: "totalCount", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, hidePager: { classPropertyName: "hidePager", publicName: "hidePager", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, errorMsg: { classPropertyName: "errorMsg", publicName: "errorMsg", isSignal: true, isRequired: false, transformFunction: null }, loadingKey: { classPropertyName: "loadingKey", publicName: "loadingKey", isSignal: true, isRequired: false, transformFunction: null }, rowDoubleClickable: { classPropertyName: "rowDoubleClickable", publicName: "rowDoubleClickable", isSignal: true, isRequired: false, transformFunction: null }, hasActionsColumn: { classPropertyName: "hasActionsColumn", publicName: "hasActionsColumn", isSignal: true, isRequired: false, transformFunction: null }, rowActionsContext: { classPropertyName: "rowActionsContext", publicName: "rowActionsContext", isSignal: true, isRequired: false, transformFunction: null }, showViewAction: { classPropertyName: "showViewAction", publicName: "showViewAction", isSignal: true, isRequired: false, transformFunction: null }, showEditAction: { classPropertyName: "showEditAction", publicName: "showEditAction", isSignal: true, isRequired: false, transformFunction: null }, showDuplicateAction: { classPropertyName: "showDuplicateAction", publicName: "showDuplicateAction", isSignal: true, isRequired: false, transformFunction: null }, showDeleteAction: { classPropertyName: "showDeleteAction", publicName: "showDeleteAction", isSignal: true, isRequired: false, transformFunction: null }, showDensityControl: { classPropertyName: "showDensityControl", publicName: "showDensityControl", isSignal: true, isRequired: false, transformFunction: null }, showColumnPicker: { classPropertyName: "showColumnPicker", publicName: "showColumnPicker", isSignal: true, isRequired: false, transformFunction: null }, showExportControl: { classPropertyName: "showExportControl", publicName: "showExportControl", isSignal: true, isRequired: false, transformFunction: null }, exporting: { classPropertyName: "exporting", publicName: "exporting", isSignal: true, isRequired: false, transformFunction: null }, screenContext: { classPropertyName: "screenContext", publicName: "screenContext", isSignal: true, isRequired: false, transformFunction: null }, searchTerm: { classPropertyName: "searchTerm", publicName: "searchTerm", isSignal: true, isRequired: false, transformFunction: null }, tableKey: { classPropertyName: "tableKey", publicName: "tableKey", isSignal: true, isRequired: false, transformFunction: null }, defaultDensity: { classPropertyName: "defaultDensity", publicName: "defaultDensity", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, fixedLayout: { classPropertyName: "fixedLayout", publicName: "fixedLayout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", rowDoubleClick: "rowDoubleClick", rowAction: "rowAction", exportRequest: "exportRequest", clearSearch: "clearSearch", retry: "retry", rowReorder: "rowReorder" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "ef-row-actions-toggle": "onRowActionsToggle($event)" } }, queries: [{ propertyName: "bodyTemplates", predicate: EfColumnTemplateDirective }, { propertyName: "headerTemplates", predicate: EfColumnHeaderTemplateDirective }], ngImport: i0, template: "<!-- One cell renderer, shared by the table, the mobile row and the mobile\n detail. A new column type is added here once, not three times. -->\n<ng-template #cellTpl let-row let-col=\"col\" let-index=\"index\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col, index: index }\"></ng-container>\n } @else {\n @switch (col.type) {\n @case ('number') {\n {{ $any(cellValue(row, col)) | number:numberFormat(col):col.locale }}\n }\n @case ('money') {\n {{ $any(cellValue(row, col)) | currency:(col.currencyCode || 'EUR'):(col.currencyDisplay || 'symbol'):numberFormat(col):col.locale }}\n }\n @case ('date') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy'):undefined:col.locale }}\n }\n @case ('datetime') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy HH:mm'):undefined:col.locale }}\n }\n @case ('boolean') {\n @if (cellValue(row, col)) {\n <span class=\"bool yes\"><span class=\"dot\"></span> \u2713</span>\n } @else {\n <span class=\"bool no\"><span class=\"dot\"></span> \u2014</span>\n }\n }\n @case ('mono') {\n <span class=\"ref\">{{ cellValue(row, col) }}</span>\n }\n @case ('chip') {\n <span [class]=\"'chip ' + (col.chipPrefix || 'chip-') + cellValue(row, col)\">\n \u25CF {{ cellValue(row, col) }}\n </span>\n }\n @case ('status') {\n <ef-status-chip\n [referenceKey]=\"col.referenceKey || ''\"\n [code]=\"$any(cellValue(row, col))\"\n />\n }\n @case ('reference') {\n {{ resolveReference(cellValue(row, col), col) }}\n }\n @default {\n {{ cellValue(row, col) }}\n }\n }\n }\n</ng-template>\n\n<div class=\"tbl-wrap\" [class.has-open-menu]=\"openMenuCount() > 0 || openTool() !== null\" [attr.data-density]=\"density()\">\n <!-- \u2500\u2500 HEAD ROW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"tbl-head\">\n <div class=\"count small\">\n @if (loading()) {\n <span>{{ loadingKey() | translate }}</span>\n } @else if (errorMsg()) {\n <!-- A refresh that failed on top of rows already on screen keeps the\n rows, so this chip is the only sign anything went wrong \u2014 and the\n rows it sits above may now be stale. It has to say so, and say it\n in the product's words: `errorMsg` is `err.message` from the HTTP\n layer, and \"500 Internal Server Error\" tells an operator nothing\n they can act on.\n\n The raw string stays on `data-error`, where devtools can read it\n and no reader ever meets it. `title` was the obvious place and is\n the wrong one: it renders the exception in a tooltip, which is\n precisely what this change exists to stop, and it is unreachable\n on touch besides.\n\n role=\"status\", not \"alert\": polite is right for a signal beside\n rows that are still usable, and when the body is showing the\n error state it already carries the alert \u2014 one failure must not\n interrupt a screen-reader twice. -->\n <span\n role=\"status\"\n [attr.data-error]=\"errorMsg()\"\n style=\"color: var(--st-cancelled-fg); font-weight: 600;\"\n >\u26A0 {{ 'common_error_refresh_failed' | translate }}</span\n >\n } @else {\n <ng-content select=\"[tbl-head-info]\"></ng-content>\n }\n </div>\n <div class=\"tbl-tools\">\n <ng-content select=\"[tbl-head-actions]\"></ng-content>\n\n @if (canExport()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [disabled]=\"exporting()\"\n [attr.aria-busy]=\"exporting()\"\n (click)=\"requestExport()\"\n >\n <i\n class=\"pi\"\n [class.pi-download]=\"!exporting()\"\n [class.pi-spinner]=\"exporting()\"\n [class.is-spinning]=\"exporting()\"\n aria-hidden=\"true\"\n ></i>\n {{ (exporting() ? 'common_export_running' : 'common_export') | translate }}\n </button>\n </div>\n }\n\n @if (showDensityControl()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'density'\"\n [attr.aria-expanded]=\"openTool() === 'density'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('density')\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n {{ 'common_density' | translate }}\n </button>\n @if (openTool() === 'density') {\n <div class=\"tbl-tool__menu\" role=\"menu\">\n @for (opt of densityOptions; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"density() === opt.value\"\n [class.is-active]=\"density() === opt.value\"\n (click)=\"setDensity(opt.value)\"\n >\n <i class=\"pi\" [ngClass]=\"opt.icon\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ opt.labelKey | translate }}</span>\n @if (density() === opt.value) {\n <i class=\"pi pi-check tbl-tool__check\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showColumnPicker()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'columns'\"\n [attr.aria-expanded]=\"openTool() === 'columns'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('columns')\"\n >\n <i class=\"pi pi-th-large\" aria-hidden=\"true\"></i>\n {{ 'common_columns' | translate }}\n </button>\n @if (openTool() === 'columns') {\n <div class=\"tbl-tool__menu tbl-tool__menu--wide\" role=\"menu\">\n @for (col of hideableColumns(); track col.id) {\n <button\n type=\"button\"\n role=\"menuitemcheckbox\"\n [attr.aria-checked]=\"!isColumnHidden(col.id)\"\n [class.is-active]=\"!isColumnHidden(col.id)\"\n (click)=\"toggleColumn(col.id)\"\n >\n <i class=\"pi\" [class.pi-check]=\"!isColumnHidden(col.id)\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{\n col.headerKey ? (col.headerKey | translate) : (col.header || col.id)\n }}</span>\n </button>\n }\n <div class=\"tbl-tool__sep\"></div>\n <button type=\"button\" role=\"menuitem\" (click)=\"resetColumns()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ 'common_columns_reset' | translate }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 TABLE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (isMobile()) {\n <!-- \u2500\u2500 MOBILE LIST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A six-column table on a 390px screen either overflows or\n squeezes every column to nothing, so the rows become a list.\n Same columns, same cell renderers; each column just declares\n (or is given) a role. Both shapes expand to the same detail \u2014\n they differ only in how much they show collapsed. -->\n <ul\n class=\"mlist\"\n [attr.data-layout]=\"effectiveMobileLayout()\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n >\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <li class=\"mrow\" [class.is-open]=\"isExpanded(row, i)\">\n <div\n class=\"mrow__head\"\n [class.is-tappable]=\"headIsControl()\"\n [attr.role]=\"headIsControl() ? 'button' : null\"\n [attr.tabindex]=\"headIsControl() ? 0 : null\"\n [attr.aria-expanded]=\"!rowNavigable() && hasDetail() ? isExpanded(row, i) : null\"\n (click)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.enter)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.space)=\"onRowHeadActivate(row, i, $event)\"\n >\n <div class=\"mrow__lead\">\n @if (primaryColumn(); as pc) {\n <span class=\"mrow__primary\" efOverflowTooltip efTooltip efTooltipPosition=\"top\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: pc, index: i }\"></ng-container>\n </span>\n }\n @if (effectiveMobileLayout() === 'row' && secondaryColumns().length) {\n <span class=\"mrow__secondary\">\n @for (sc of secondaryColumns(); track sc.id) {\n <span class=\"mrow__sec-part\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: sc, index: i }\"></ng-container>\n </span>\n }\n </span>\n }\n </div>\n\n <div class=\"mrow__trail\">\n @if (statusColumn(); as st) {\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: st, index: i }\"></ng-container>\n }\n @if (hasActionsColumn()) {\n <span class=\"mrow__actions\">\n <ef-row-actions [items]=\"defaultRowActions(row)\" [context]=\"rowActionsContext()\" />\n </span>\n }\n @if (hasDetail() && effectiveMobileLayout() === 'row') {\n @if (rowNavigable()) {\n <!-- The head opens the record, so expansion needs a control\n of its own. Same glyph, promoted from decoration to a\n button that carries the state it was already drawing. -->\n <button\n type=\"button\"\n class=\"mrow__disclosure\"\n [attr.aria-expanded]=\"isExpanded(row, i)\"\n [attr.aria-label]=\"(isExpanded(row, i) ? 'common_view_less' : 'common_view_more') | translate\"\n (click)=\"onDisclosureToggle(row, i, $event)\"\n >\n <i\n class=\"pi pi-chevron-down mrow__chevron\"\n [class.is-open]=\"isExpanded(row, i)\"\n aria-hidden=\"true\"\n ></i>\n </button>\n } @else {\n <!-- Says the row opens, and which way it is now. Decorative:\n the whole head is the control, and it carries the state. -->\n <i\n class=\"pi pi-chevron-down mrow__chevron\"\n [class.is-open]=\"isExpanded(row, i)\"\n aria-hidden=\"true\"\n ></i>\n }\n }\n </div>\n </div>\n\n @if (visibleDetail(row, i).length) {\n <dl class=\"mrow__detail\">\n @for (col of visibleDetail(row, i); track col.id) {\n <div class=\"mrow__field\">\n <dt>{{ $any(col).headerKey ? ($any(col).headerKey | translate) : $any(col).header }}</dt>\n <dd [class]=\"cellAlignClass($any(col))\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"></ng-container>\n </dd>\n </div>\n }\n </dl>\n }\n\n @if (hasMoreThanPreview() && effectiveMobileLayout() === 'card') {\n <button class=\"mrow__more\" type=\"button\" (click)=\"toggleExpanded(row, i)\">\n <span>{{ (isExpanded(row, i) ? 'common_view_less' : 'common_view_more') | translate }}</span>\n <i class=\"pi pi-chevron-down mrow__chevron\" [class.is-open]=\"isExpanded(row, i)\" aria-hidden=\"true\"></i>\n </button>\n }\n </li>\n } @empty {\n <!-- No rows has three meanings and they must not share a screen:\n still loading is a skeleton, the request failed is the error\n state, finished-and-empty is the empty state. Saying \"nothing\n here yet\" to someone whose data is still in flight \u2014 or whose\n request just failed \u2014 is a lie the whole time it shows. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <li class=\"mrow mrow--skeleton\">\n <ef-skeleton variant=\"text\" [rows]=\"2\" />\n </li>\n }\n } @else if (showError()) {\n <li class=\"mrow mrow--empty\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </li>\n } @else {\n <li class=\"mrow mrow--empty\">\n <ng-content select=\"[empty-state-mobile]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </li>\n }\n }\n </ul>\n } @else {\n <table class=\"tbl\" [class.tbl--fixed]=\"fixedLayout()\">\n <thead>\n <tr>\n @if (reorderable()) {\n <th class=\"tbl-drag-h\" aria-hidden=\"true\"></th>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <!-- A sortable header is a real <button> inside the <th>, not a\n click handler on the cell. The cell carries aria-sort so the\n current order is announced; the arrow stays decorative.\n Sorting was previously mouse-only and silent to assistive\n technology, on every list screen in the product. -->\n <th\n [class]=\"headerAlignClass(col)\"\n [class.is-sortable]=\"col.sortable\"\n [style.width]=\"col.width || null\"\n [attr.data-col]=\"col.id\"\n [attr.aria-sort]=\"ariaSort(col)\"\n >\n @if (col.sortable) {\n <button type=\"button\" class=\"tbl-sort\" (click)=\"onHeaderClick(col)\">\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n @if (sortIndicator(col)) {\n <span aria-hidden=\"true\" class=\"tbl-sort__arrow\">{{ sortIndicator(col) }}</span>\n }\n </button>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody [attr.aria-busy]=\"loading() ? 'true' : null\">\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n [class.is-dragging]=\"dragFrom() === i\"\n [class.is-drop-target]=\"dragFrom() !== null && dragFrom() !== i && dragTo() === i\"\n [attr.tabindex]=\"rowDoubleClickable() ? 0 : null\"\n [attr.role]=\"rowDoubleClickable() ? 'button' : null\"\n (click)=\"onRowClick(row, $event)\"\n (keydown.enter)=\"onRowKeydown(row, $event)\"\n >\n @if (reorderable()) {\n <td class=\"tbl-drag\">\n <!-- The row is draggable only by this handle, so a drag never\n starts from a cell the operator meant to click or select.\n Arrow keys move the row for anyone not using a pointer. -->\n <button\n type=\"button\"\n class=\"tbl-drag__grip\"\n [attr.aria-label]=\"'common_reorder_row' | translate\"\n (pointerdown)=\"onDragPointerDown(i, $event)\"\n (pointermove)=\"onDragPointerMove($event)\"\n (pointerup)=\"onDragPointerUp()\"\n (pointercancel)=\"onDragPointerUp()\"\n (keydown)=\"onDragKeydown(i, $event)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n </td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td efOverflowTooltip efTooltip efTooltipPosition=\"top\" [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n @if (col.id === 'actions' && hasActionsColumn()) {\n <!-- Auto row-actions cell: View / Edit / Duplicate / Delete,\n gated by [show*Action] flags + ScreenContext perms. -->\n <ef-row-actions\n [items]=\"defaultRowActions(row)\"\n [context]=\"rowActionsContext()\"\n />\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"\n ></ng-container>\n }\n </td>\n }\n </tr>\n } @empty {\n <!-- Placeholder rows keep the real columns, so the table that\n arrives lands where the shimmer already was instead of\n reflowing the page under the reader's eyes. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <tr>\n @if (reorderable()) {\n <td class=\"tbl-drag\"></td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <td [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n <ef-skeleton variant=\"text\" [rows]=\"1\" />\n </td>\n }\n </tr>\n }\n } @else if (showError()) {\n <!-- The failure replaces the body, so it spans the full row \u2014\n crammed into column one it reads as a value, not a state. -->\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </td>\n </tr>\n } @else {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-content select=\"[empty-state]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n }\n\n <!-- \u2500\u2500 PAGER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Paging controls for an empty result set are chrome with nothing to\n page, so they stay out of the way until there is something to move\n through. -->\n @if (!hidePager() && rows().length > 0) {\n <ef-pager\n [pageNumber]=\"pageNumber()\"\n [pageSize]=\"pageSize()\"\n [totalCount]=\"totalCount()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n (pageChange)=\"pageChange.emit($event)\"\n (pageSizeChange)=\"pageSizeChange.emit($event)\"\n />\n }\n</div>\n\n<!-- \u2500\u2500 DEFAULT EMPTY STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Rendered when a screen projects nothing into [empty-state] /\n [empty-state-mobile]. It words itself from `searchTerm`, because\n \"nothing matched what you typed\" and \"there is nothing here yet\"\n are different situations needing different ways out. -->\n<ng-template #defaultEmptyTpl>\n <div class=\"tbl-empty\">\n <i\n class=\"pi tbl-empty__icon\"\n [class.pi-search]=\"!!searchTerm()\"\n [class.pi-inbox]=\"!searchTerm()\"\n aria-hidden=\"true\"\n ></i>\n\n @if (searchTerm()) {\n <p class=\"tbl-empty__title\">\n {{ 'common_empty_no_results' | translate: { term: searchTerm() } }}\n </p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_no_results_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"clearSearch.emit()\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n {{ 'common_empty_clear_filters' | translate }}\n </button>\n } @else {\n <p class=\"tbl-empty__title\">{{ 'common_empty_none_yet' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_none_yet_hint' | translate }}</p>\n }\n </div>\n</ng-template>\n\n<!-- \u2500\u2500 ERROR STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A request that failed and came back with nothing is not an empty\n collection, and must never borrow its words: \"nothing here yet\" is\n a confident false statement about the operator's data, and they may\n act on it. So this replaces the empty state outright, projected\n [empty-state] included \u2014 a screen's own \"create your first client\"\n copy is just as wrong here as the default.\n\n It says nothing about *what* failed. `errorMsg` is `err.message`\n from the HTTP layer, and a status line or an exception string tells\n an operator nothing they can act on; it survives on the head chip's\n `data-error` attribute, for whoever is reading the DOM. This carries\n the two things that help: their data is intact, and the way to try\n again.\n\n role=\"alert\" goes on this div, never on the <tr> / <td> / <li> that\n hosts it \u2014 a role on a row or cell takes the table's semantics down\n with it. It fires on insertion, which is exactly when @if adds it. -->\n<ng-template #errorStateTpl>\n <div class=\"tbl-empty tbl-empty--error\" role=\"alert\">\n <i class=\"pi pi-exclamation-triangle tbl-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"tbl-empty__title\">{{ 'common_error_load_failed' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_error_load_failed_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"retry.emit()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n {{ 'common_retry' | translate }}\n </button>\n </div>\n</ng-template>\n\n<!-- Header label, shared by the sortable and plain header cells so the\n projection rules stay in one place. -->\n<ng-template #headerLabelTpl let-col=\"col\">\n @if (headerTemplate(col.id); as headerTpl) {\n <ng-container *ngTemplateOutlet=\"headerTpl; context: { $implicit: col, col: col }\"></ng-container>\n } @else if (col.headerKey) {\n <span>{{ col.headerKey | translate }}</span>\n } @else if (col.header) {\n <span>{{ col.header }}</span>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";.tbl thead th.is-sortable{cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl thead th.is-sortable:hover{color:var(--text)}.tbl tbody tr.is-row-clickable{cursor:pointer}.tbl-tools{display:flex;gap:6px;align-items:center}.tbl-tool{position:relative}.tbl-tool>.btn.open{color:var(--text);background:var(--paper)}.tbl-tool__menu{position:absolute;top:calc(100% + 8px);inset-inline-end:0;z-index:40;min-width:212px;max-height:340px;overflow-y:auto;padding:6px;display:grid;gap:1px;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);animation:row-actions-menu-in var(--t-base) var(--ease-spring)}.tbl-tool__menu--wide{min-width:248px}.tbl-tool__menu button{appearance:none;background:transparent;border:0;width:100%;text-align:start;font:inherit;font-size:13.5px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:var(--r-md);cursor:pointer;transition:background-color var(--t-fast) var(--ease-out)}.tbl-tool__menu button:hover,.tbl-tool__menu button:focus-visible{background:var(--paper);outline:0}.tbl-tool__menu button>.pi{width:16px;flex:none;font-size:14px;color:var(--muted)}.tbl-tool__menu button.is-active>.pi{color:var(--module, var(--text))}.tbl-tool__label{flex:1 1 auto;min-width:0}.tbl-tool__check{color:var(--module, var(--text))}.tbl-tool__sep{margin:5px 2px;border-top:1px solid var(--rule)}.tbl-wrap[data-density=compact] .tbl tbody td{padding-top:4px;padding-bottom:4px}.tbl-wrap[data-density=comfortable] .tbl tbody td{padding-top:16px;padding-bottom:16px}.mlist{list-style:none;margin:0;padding:0;display:grid}.mrow{border-top:1px solid var(--rule)}.mrow:first-child{border-top:0}.mrow__head{display:flex;align-items:flex-start;gap:var(--s-3);padding:12px var(--s-4);min-height:var(--hit-touch)}.mrow__head.is-tappable{cursor:pointer}.mrow__head.is-tappable:active{background:var(--paper)}.mrow__lead{flex:1;min-width:0;display:grid;gap:2px}.mrow__primary{font-weight:700;font-size:14px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mrow__secondary{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px;font-size:12.5px;color:var(--text-mute);min-width:0}.mrow__secondary .mrow__sec-part+.mrow__sec-part:before{content:\"\\b7\";margin-inline-end:6px;color:var(--text-soft)}.mrow__trail{display:flex;align-items:center;justify-content:flex-end;gap:var(--s-2);flex-shrink:0;min-height:20px}.mrow__actions{display:inline-flex}.mrow__detail{margin:0;padding:0 var(--s-4) 12px;display:grid;gap:1px}.mrow__field{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-3);padding:7px 0;border-top:1px solid var(--rule-soft)}.mrow__field dt{font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-mute);flex-shrink:0}.mrow__field dd{margin:0;font-size:13.5px;color:var(--text);text-align:end;min-width:0;overflow-wrap:anywhere}.mrow__more{appearance:none;width:100%;background:transparent;border:0;border-top:1px solid var(--rule-soft);padding:10px;font:inherit;font-size:13px;font-weight:600;color:var(--tenant-500);cursor:pointer}.mlist[data-layout=card]{gap:var(--s-3);padding:var(--s-3) var(--s-3) 0}.mlist[data-layout=card] .mrow{border:1px solid var(--rule);border-radius:var(--r-lg);background:var(--paper-alt);overflow:hidden}.mlist[data-layout=card] .mrow__head{border-bottom:1px solid var(--rule)}.mrow__chevron{font-size:11px;color:var(--text-soft);transition:transform var(--t-fast) var(--ease-out);flex-shrink:0}.mrow__chevron.is-open{transform:rotate(180deg);color:var(--text-mute)}.mrow__head.is-tappable:focus-visible{outline:2px solid var(--tenant-400);outline-offset:-2px}.mrow__more{display:flex;align-items:center;justify-content:center;gap:6px}.tbl-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-2);padding:var(--s-9) var(--s-4);text-align:center}.tbl-empty__icon{font-size:28px;line-height:1;color:var(--ink-400);margin-block-end:var(--s-1)}.tbl-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--text);max-inline-size:46ch;overflow-wrap:break-word}.tbl-empty__hint{margin:0;font-size:13px;color:var(--ink-600);max-inline-size:52ch;text-wrap:pretty}.tbl-empty__action{margin-block-start:var(--s-3);min-block-size:var(--hit-base)}.tbl-empty--error .tbl-empty__icon{color:var(--st-cancelled-fg)}.mrow--empty{padding:0}.mrow--skeleton{padding:12px var(--s-4)}@media(max-width:767px){.tbl-empty{padding:var(--s-7) var(--s-4)}.tbl-empty__action{min-block-size:var(--hit-touch)}}.pi.is-spinning{animation:ef-export-spin .9s linear infinite}@keyframes ef-export-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.pi.is-spinning{animation:none}}.tbl th.tbl-drag-h,.tbl td.tbl-drag{width:28px;padding-inline:4px 0;text-align:center}.tbl td.tbl-drag{cursor:default}.tbl .tbl-drag__grip{display:inline-flex;align-items:center;justify-content:center;width:22px;height:26px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--text-soft);cursor:grab;touch-action:none;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl .tbl-drag__grip:hover{background:var(--paper);color:var(--text)}.tbl .tbl-drag__grip:focus-visible{outline:2px solid var(--tenant, var(--rule));outline-offset:1px}.tbl .tbl-drag__grip:active{cursor:grabbing}.tbl .tbl-drag__grip i{font-size:12px}.tbl tbody tr.is-dragging>td{background:var(--paper);opacity:.55}.tbl tbody tr.is-drop-target>td{box-shadow:inset 0 2px 0 var(--tenant, var(--rule))}.tbl.tbl--fixed{table-layout:fixed}.mrow__disclosure{appearance:none;background:transparent;border:0;padding:0;margin:0;display:inline-grid;place-items:center;color:var(--text-mute);cursor:pointer;position:relative}.mrow__disclosure:after{content:\"\";position:absolute;top:50%;left:50%;width:var(--hit-touch);height:var(--hit-touch);transform:translate(-50%,-50%)}.mrow__disclosure:focus-visible{outline:2px solid color-mix(in srgb,var(--ink-active) 40%,transparent);outline-offset:2px;border-radius:var(--r-pill)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfPagerComponent, selector: "ef-pager", inputs: ["pageNumber", "pageSize", "totalCount", "pageSizeOptions", "linesPerPageLabelKey", "ofLabelKey", "prevLabelKey", "nextLabelKey", "hideSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: EfStatusChipComponent, selector: "ef-status-chip", inputs: ["referenceKey", "code", "color", "label", "labelKey", "hideDot", "showCode"] }, { kind: "component", type: EfSkeletonComponent, selector: "ef-skeleton", inputs: ["variant", "rows", "height"] }, { kind: "component", type: EfRowActionsComponent, selector: "ef-row-actions", inputs: ["items", "context", "triggerIcon", "triggerAriaKey", "disabled", "menuLeft"], outputs: ["opened", "closed"] }, { kind: "directive", type: EfOverflowTooltipDirective, selector: "[efOverflowTooltip]" }, { kind: "directive", type: EfTooltipDirective, selector: "[efTooltip]" }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$3.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i1$3.DatePipe, name: "date" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5218
5278
  }
5219
5279
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDataCardComponent, decorators: [{
5220
5280
  type: Component,
@@ -5227,7 +5287,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5227
5287
  EfRowActionsComponent,
5228
5288
  EfOverflowTooltipDirective,
5229
5289
  EfTooltipDirective,
5230
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- One cell renderer, shared by the table, the mobile row and the mobile\n detail. A new column type is added here once, not three times. -->\n<ng-template #cellTpl let-row let-col=\"col\" let-index=\"index\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col, index: index }\"></ng-container>\n } @else {\n @switch (col.type) {\n @case ('number') {\n {{ $any(cellValue(row, col)) | number:numberFormat(col):col.locale }}\n }\n @case ('money') {\n {{ $any(cellValue(row, col)) | currency:(col.currencyCode || 'EUR'):(col.currencyDisplay || 'symbol'):numberFormat(col):col.locale }}\n }\n @case ('date') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy'):undefined:col.locale }}\n }\n @case ('datetime') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy HH:mm'):undefined:col.locale }}\n }\n @case ('boolean') {\n @if (cellValue(row, col)) {\n <span class=\"bool yes\"><span class=\"dot\"></span> \u2713</span>\n } @else {\n <span class=\"bool no\"><span class=\"dot\"></span> \u2014</span>\n }\n }\n @case ('mono') {\n <span class=\"ref\">{{ cellValue(row, col) }}</span>\n }\n @case ('chip') {\n <span [class]=\"'chip ' + (col.chipPrefix || 'chip-') + cellValue(row, col)\">\n \u25CF {{ cellValue(row, col) }}\n </span>\n }\n @case ('status') {\n <ef-status-chip\n [referenceKey]=\"col.referenceKey || ''\"\n [code]=\"$any(cellValue(row, col))\"\n />\n }\n @case ('reference') {\n {{ resolveReference(cellValue(row, col), col) }}\n }\n @default {\n {{ cellValue(row, col) }}\n }\n }\n }\n</ng-template>\n\n<div class=\"tbl-wrap\" [class.has-open-menu]=\"openMenuCount() > 0 || openTool() !== null\" [attr.data-density]=\"density()\">\n <!-- \u2500\u2500 HEAD ROW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"tbl-head\">\n <div class=\"count small\">\n @if (loading()) {\n <span>{{ loadingKey() | translate }}</span>\n } @else if (errorMsg()) {\n <span style=\"color: var(--st-cancelled-fg); font-weight: 600;\">\u26A0 {{ errorMsg() }}</span>\n } @else {\n <ng-content select=\"[tbl-head-info]\"></ng-content>\n }\n </div>\n <div class=\"tbl-tools\">\n <ng-content select=\"[tbl-head-actions]\"></ng-content>\n\n @if (canExport()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [disabled]=\"exporting()\"\n [attr.aria-busy]=\"exporting()\"\n (click)=\"requestExport()\"\n >\n <i\n class=\"pi\"\n [class.pi-download]=\"!exporting()\"\n [class.pi-spinner]=\"exporting()\"\n [class.is-spinning]=\"exporting()\"\n aria-hidden=\"true\"\n ></i>\n {{ (exporting() ? 'common_export_running' : 'common_export') | translate }}\n </button>\n </div>\n }\n\n @if (showDensityControl()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'density'\"\n [attr.aria-expanded]=\"openTool() === 'density'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('density')\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n {{ 'common_density' | translate }}\n </button>\n @if (openTool() === 'density') {\n <div class=\"tbl-tool__menu\" role=\"menu\">\n @for (opt of densityOptions; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"density() === opt.value\"\n [class.is-active]=\"density() === opt.value\"\n (click)=\"setDensity(opt.value)\"\n >\n <i class=\"pi\" [ngClass]=\"opt.icon\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ opt.labelKey | translate }}</span>\n @if (density() === opt.value) {\n <i class=\"pi pi-check tbl-tool__check\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showColumnPicker()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'columns'\"\n [attr.aria-expanded]=\"openTool() === 'columns'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('columns')\"\n >\n <i class=\"pi pi-th-large\" aria-hidden=\"true\"></i>\n {{ 'common_columns' | translate }}\n </button>\n @if (openTool() === 'columns') {\n <div class=\"tbl-tool__menu tbl-tool__menu--wide\" role=\"menu\">\n @for (col of hideableColumns(); track col.id) {\n <button\n type=\"button\"\n role=\"menuitemcheckbox\"\n [attr.aria-checked]=\"!isColumnHidden(col.id)\"\n [class.is-active]=\"!isColumnHidden(col.id)\"\n (click)=\"toggleColumn(col.id)\"\n >\n <i class=\"pi\" [class.pi-check]=\"!isColumnHidden(col.id)\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{\n col.headerKey ? (col.headerKey | translate) : (col.header || col.id)\n }}</span>\n </button>\n }\n <div class=\"tbl-tool__sep\"></div>\n <button type=\"button\" role=\"menuitem\" (click)=\"resetColumns()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ 'common_columns_reset' | translate }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 TABLE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (isMobile()) {\n <!-- \u2500\u2500 MOBILE LIST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A six-column table on a 390px screen either overflows or\n squeezes every column to nothing, so the rows become a list.\n Same columns, same cell renderers; each column just declares\n (or is given) a role. Both shapes expand to the same detail \u2014\n they differ only in how much they show collapsed. -->\n <ul\n class=\"mlist\"\n [attr.data-layout]=\"effectiveMobileLayout()\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n >\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <li class=\"mrow\" [class.is-open]=\"isExpanded(row, i)\">\n <div\n class=\"mrow__head\"\n [class.is-tappable]=\"hasDetail()\"\n [attr.role]=\"hasDetail() ? 'button' : null\"\n [attr.tabindex]=\"hasDetail() ? 0 : null\"\n [attr.aria-expanded]=\"hasDetail() ? isExpanded(row, i) : null\"\n (click)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.enter)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.space)=\"onRowHeadActivate(row, i, $event)\"\n >\n <div class=\"mrow__lead\">\n @if (primaryColumn(); as pc) {\n <span class=\"mrow__primary\" efOverflowTooltip efTooltip efTooltipPosition=\"top\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: pc, index: i }\"></ng-container>\n </span>\n }\n @if (effectiveMobileLayout() === 'row' && secondaryColumns().length) {\n <span class=\"mrow__secondary\">\n @for (sc of secondaryColumns(); track sc.id) {\n <span class=\"mrow__sec-part\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: sc, index: i }\"></ng-container>\n </span>\n }\n </span>\n }\n </div>\n\n <div class=\"mrow__trail\">\n @if (statusColumn(); as st) {\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: st, index: i }\"></ng-container>\n }\n @if (hasActionsColumn()) {\n <span class=\"mrow__actions\">\n <ef-row-actions [items]=\"defaultRowActions(row)\" [context]=\"rowActionsContext()\" />\n </span>\n }\n @if (hasDetail() && effectiveMobileLayout() === 'row') {\n <!-- Says the row opens, and which way it is now. Decorative:\n the whole head is the control, and it carries the state. -->\n <i\n class=\"pi pi-chevron-down mrow__chevron\"\n [class.is-open]=\"isExpanded(row, i)\"\n aria-hidden=\"true\"\n ></i>\n }\n </div>\n </div>\n\n @if (visibleDetail(row, i).length) {\n <dl class=\"mrow__detail\">\n @for (col of visibleDetail(row, i); track col.id) {\n <div class=\"mrow__field\">\n <dt>{{ $any(col).headerKey ? ($any(col).headerKey | translate) : $any(col).header }}</dt>\n <dd [class]=\"cellAlignClass($any(col))\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"></ng-container>\n </dd>\n </div>\n }\n </dl>\n }\n\n @if (hasMoreThanPreview() && effectiveMobileLayout() === 'card') {\n <button class=\"mrow__more\" type=\"button\" (click)=\"toggleExpanded(row, i)\">\n <span>{{ (isExpanded(row, i) ? 'common_view_less' : 'common_view_more') | translate }}</span>\n <i class=\"pi pi-chevron-down mrow__chevron\" [class.is-open]=\"isExpanded(row, i)\" aria-hidden=\"true\"></i>\n </button>\n }\n </li>\n } @empty {\n <!-- No rows has three meanings and they must not share a screen:\n still loading is a skeleton, the request failed is the error\n state, finished-and-empty is the empty state. Saying \"nothing\n here yet\" to someone whose data is still in flight \u2014 or whose\n request just failed \u2014 is a lie the whole time it shows. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <li class=\"mrow mrow--skeleton\">\n <ef-skeleton variant=\"text\" [rows]=\"2\" />\n </li>\n }\n } @else if (showError()) {\n <li class=\"mrow mrow--empty\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </li>\n } @else {\n <li class=\"mrow mrow--empty\">\n <ng-content select=\"[empty-state-mobile]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </li>\n }\n }\n </ul>\n } @else {\n <table class=\"tbl\" [class.tbl--fixed]=\"fixedLayout()\">\n <thead>\n <tr>\n @if (reorderable()) {\n <th class=\"tbl-drag-h\" aria-hidden=\"true\"></th>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <!-- A sortable header is a real <button> inside the <th>, not a\n click handler on the cell. The cell carries aria-sort so the\n current order is announced; the arrow stays decorative.\n Sorting was previously mouse-only and silent to assistive\n technology, on every list screen in the product. -->\n <th\n [class]=\"headerAlignClass(col)\"\n [class.is-sortable]=\"col.sortable\"\n [style.width]=\"col.width || null\"\n [attr.data-col]=\"col.id\"\n [attr.aria-sort]=\"ariaSort(col)\"\n >\n @if (col.sortable) {\n <button type=\"button\" class=\"tbl-sort\" (click)=\"onHeaderClick(col)\">\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n @if (sortIndicator(col)) {\n <span aria-hidden=\"true\" class=\"tbl-sort__arrow\">{{ sortIndicator(col) }}</span>\n }\n </button>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody [attr.aria-busy]=\"loading() ? 'true' : null\">\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n [class.is-dragging]=\"dragFrom() === i\"\n [class.is-drop-target]=\"dragFrom() !== null && dragFrom() !== i && dragTo() === i\"\n [attr.tabindex]=\"rowDoubleClickable() ? 0 : null\"\n [attr.role]=\"rowDoubleClickable() ? 'button' : null\"\n (dblclick)=\"onRowDoubleClick(row, $event)\"\n (keydown.enter)=\"onRowKeydown(row, $event)\"\n >\n @if (reorderable()) {\n <td class=\"tbl-drag\">\n <!-- The row is draggable only by this handle, so a drag never\n starts from a cell the operator meant to click or select.\n Arrow keys move the row for anyone not using a pointer. -->\n <button\n type=\"button\"\n class=\"tbl-drag__grip\"\n [attr.aria-label]=\"'common_reorder_row' | translate\"\n (pointerdown)=\"onDragPointerDown(i, $event)\"\n (pointermove)=\"onDragPointerMove($event)\"\n (pointerup)=\"onDragPointerUp()\"\n (pointercancel)=\"onDragPointerUp()\"\n (keydown)=\"onDragKeydown(i, $event)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n </td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td efOverflowTooltip efTooltip efTooltipPosition=\"top\" [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n @if (col.id === 'actions' && hasActionsColumn()) {\n <!-- Auto row-actions cell: View / Edit / Duplicate / Delete,\n gated by [show*Action] flags + ScreenContext perms. -->\n <ef-row-actions\n [items]=\"defaultRowActions(row)\"\n [context]=\"rowActionsContext()\"\n />\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"\n ></ng-container>\n }\n </td>\n }\n </tr>\n } @empty {\n <!-- Placeholder rows keep the real columns, so the table that\n arrives lands where the shimmer already was instead of\n reflowing the page under the reader's eyes. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <tr>\n @if (reorderable()) {\n <td class=\"tbl-drag\"></td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <td [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n <ef-skeleton variant=\"text\" [rows]=\"1\" />\n </td>\n }\n </tr>\n }\n } @else if (showError()) {\n <!-- The failure replaces the body, so it spans the full row \u2014\n crammed into column one it reads as a value, not a state. -->\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </td>\n </tr>\n } @else {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-content select=\"[empty-state]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n }\n\n <!-- \u2500\u2500 PAGER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Paging controls for an empty result set are chrome with nothing to\n page, so they stay out of the way until there is something to move\n through. -->\n @if (!hidePager() && rows().length > 0) {\n <ef-pager\n [pageNumber]=\"pageNumber()\"\n [pageSize]=\"pageSize()\"\n [totalCount]=\"totalCount()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n (pageChange)=\"pageChange.emit($event)\"\n (pageSizeChange)=\"pageSizeChange.emit($event)\"\n />\n }\n</div>\n\n<!-- \u2500\u2500 DEFAULT EMPTY STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Rendered when a screen projects nothing into [empty-state] /\n [empty-state-mobile]. It words itself from `searchTerm`, because\n \"nothing matched what you typed\" and \"there is nothing here yet\"\n are different situations needing different ways out. -->\n<ng-template #defaultEmptyTpl>\n <div class=\"tbl-empty\">\n <i\n class=\"pi tbl-empty__icon\"\n [class.pi-search]=\"!!searchTerm()\"\n [class.pi-inbox]=\"!searchTerm()\"\n aria-hidden=\"true\"\n ></i>\n\n @if (searchTerm()) {\n <p class=\"tbl-empty__title\">\n {{ 'common_empty_no_results' | translate: { term: searchTerm() } }}\n </p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_no_results_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"clearSearch.emit()\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n {{ 'common_empty_clear_filters' | translate }}\n </button>\n } @else {\n <p class=\"tbl-empty__title\">{{ 'common_empty_none_yet' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_none_yet_hint' | translate }}</p>\n }\n </div>\n</ng-template>\n\n<!-- \u2500\u2500 ERROR STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A request that failed and came back with nothing is not an empty\n collection, and must never borrow its words: \"nothing here yet\" is\n a confident false statement about the operator's data, and they may\n act on it. So this replaces the empty state outright, projected\n [empty-state] included \u2014 a screen's own \"create your first client\"\n copy is just as wrong here as the default.\n\n It says nothing about *what* failed. `errorMsg` is `err.message`\n from the HTTP layer, and a status line or an exception string tells\n an operator nothing they can act on; the head chip still carries it\n for anyone looking. This carries the two things that help: their\n data is intact, and the way to try again.\n\n role=\"alert\" goes on this div, never on the <tr> / <td> / <li> that\n hosts it \u2014 a role on a row or cell takes the table's semantics down\n with it. It fires on insertion, which is exactly when @if adds it. -->\n<ng-template #errorStateTpl>\n <div class=\"tbl-empty tbl-empty--error\" role=\"alert\">\n <i class=\"pi pi-exclamation-triangle tbl-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"tbl-empty__title\">{{ 'common_error_load_failed' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_error_load_failed_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"retry.emit()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n {{ 'common_retry' | translate }}\n </button>\n </div>\n</ng-template>\n\n<!-- Header label, shared by the sortable and plain header cells so the\n projection rules stay in one place. -->\n<ng-template #headerLabelTpl let-col=\"col\">\n @if (headerTemplate(col.id); as headerTpl) {\n <ng-container *ngTemplateOutlet=\"headerTpl; context: { $implicit: col, col: col }\"></ng-container>\n } @else if (col.headerKey) {\n <span>{{ col.headerKey | translate }}</span>\n } @else if (col.header) {\n <span>{{ col.header }}</span>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";.tbl thead th.is-sortable{cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl thead th.is-sortable:hover{color:var(--text)}.tbl tbody tr.is-row-clickable{cursor:pointer}.tbl-tools{display:flex;gap:6px;align-items:center}.tbl-tool{position:relative}.tbl-tool>.btn.open{color:var(--text);background:var(--paper)}.tbl-tool__menu{position:absolute;top:calc(100% + 8px);inset-inline-end:0;z-index:40;min-width:212px;max-height:340px;overflow-y:auto;padding:6px;display:grid;gap:1px;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);animation:row-actions-menu-in var(--t-base) var(--ease-spring)}.tbl-tool__menu--wide{min-width:248px}.tbl-tool__menu button{appearance:none;background:transparent;border:0;width:100%;text-align:start;font:inherit;font-size:13.5px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:var(--r-md);cursor:pointer;transition:background-color var(--t-fast) var(--ease-out)}.tbl-tool__menu button:hover,.tbl-tool__menu button:focus-visible{background:var(--paper);outline:0}.tbl-tool__menu button>.pi{width:16px;flex:none;font-size:14px;color:var(--muted)}.tbl-tool__menu button.is-active>.pi{color:var(--module, var(--text))}.tbl-tool__label{flex:1 1 auto;min-width:0}.tbl-tool__check{color:var(--module, var(--text))}.tbl-tool__sep{margin:5px 2px;border-top:1px solid var(--rule)}.tbl-wrap[data-density=compact] .tbl tbody td{padding-top:4px;padding-bottom:4px}.tbl-wrap[data-density=comfortable] .tbl tbody td{padding-top:16px;padding-bottom:16px}.mlist{list-style:none;margin:0;padding:0;display:grid}.mrow{border-top:1px solid var(--rule)}.mrow:first-child{border-top:0}.mrow__head{display:flex;align-items:flex-start;gap:var(--s-3);padding:12px var(--s-4);min-height:var(--hit-touch)}.mrow__head.is-tappable{cursor:pointer}.mrow__head.is-tappable:active{background:var(--paper)}.mrow__lead{flex:1;min-width:0;display:grid;gap:2px}.mrow__primary{font-weight:700;font-size:14px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mrow__secondary{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px;font-size:12.5px;color:var(--text-mute);min-width:0}.mrow__secondary .mrow__sec-part+.mrow__sec-part:before{content:\"\\b7\";margin-inline-end:6px;color:var(--text-soft)}.mrow__trail{display:flex;align-items:center;justify-content:flex-end;gap:var(--s-2);flex-shrink:0;min-height:20px}.mrow__actions{display:inline-flex}.mrow__detail{margin:0;padding:0 var(--s-4) 12px;display:grid;gap:1px}.mrow__field{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-3);padding:7px 0;border-top:1px solid var(--rule-soft)}.mrow__field dt{font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-mute);flex-shrink:0}.mrow__field dd{margin:0;font-size:13.5px;color:var(--text);text-align:end;min-width:0;overflow-wrap:anywhere}.mrow__more{appearance:none;width:100%;background:transparent;border:0;border-top:1px solid var(--rule-soft);padding:10px;font:inherit;font-size:13px;font-weight:600;color:var(--tenant-500);cursor:pointer}.mlist[data-layout=card]{gap:var(--s-3);padding:var(--s-3) var(--s-3) 0}.mlist[data-layout=card] .mrow{border:1px solid var(--rule);border-radius:var(--r-lg);background:var(--paper-alt);overflow:hidden}.mlist[data-layout=card] .mrow__head{border-bottom:1px solid var(--rule)}.mrow__chevron{font-size:11px;color:var(--text-soft);transition:transform var(--t-fast) var(--ease-out);flex-shrink:0}.mrow__chevron.is-open{transform:rotate(180deg);color:var(--text-mute)}.mrow__head.is-tappable:focus-visible{outline:2px solid var(--tenant-400);outline-offset:-2px}.mrow__more{display:flex;align-items:center;justify-content:center;gap:6px}.tbl-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-2);padding:var(--s-9) var(--s-4);text-align:center}.tbl-empty__icon{font-size:28px;line-height:1;color:var(--ink-400);margin-block-end:var(--s-1)}.tbl-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--text);max-inline-size:46ch;overflow-wrap:break-word}.tbl-empty__hint{margin:0;font-size:13px;color:var(--ink-600);max-inline-size:52ch;text-wrap:pretty}.tbl-empty__action{margin-block-start:var(--s-3);min-block-size:var(--hit-base)}.tbl-empty--error .tbl-empty__icon{color:var(--st-cancelled-fg)}.mrow--empty{padding:0}.mrow--skeleton{padding:12px var(--s-4)}@media(max-width:767px){.tbl-empty{padding:var(--s-7) var(--s-4)}.tbl-empty__action{min-block-size:var(--hit-touch)}}.pi.is-spinning{animation:ef-export-spin .9s linear infinite}@keyframes ef-export-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.pi.is-spinning{animation:none}}.tbl th.tbl-drag-h,.tbl td.tbl-drag{width:28px;padding-inline:4px 0;text-align:center}.tbl td.tbl-drag{cursor:default}.tbl .tbl-drag__grip{display:inline-flex;align-items:center;justify-content:center;width:22px;height:26px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--text-soft);cursor:grab;touch-action:none;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl .tbl-drag__grip:hover{background:var(--paper);color:var(--text)}.tbl .tbl-drag__grip:focus-visible{outline:2px solid var(--tenant, var(--rule));outline-offset:1px}.tbl .tbl-drag__grip:active{cursor:grabbing}.tbl .tbl-drag__grip i{font-size:12px}.tbl tbody tr.is-dragging>td{background:var(--paper);opacity:.55}.tbl tbody tr.is-drop-target>td{box-shadow:inset 0 2px 0 var(--tenant, var(--rule))}.tbl.tbl--fixed{table-layout:fixed}\n"] }]
5290
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- One cell renderer, shared by the table, the mobile row and the mobile\n detail. A new column type is added here once, not three times. -->\n<ng-template #cellTpl let-row let-col=\"col\" let-index=\"index\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col, index: index }\"></ng-container>\n } @else {\n @switch (col.type) {\n @case ('number') {\n {{ $any(cellValue(row, col)) | number:numberFormat(col):col.locale }}\n }\n @case ('money') {\n {{ $any(cellValue(row, col)) | currency:(col.currencyCode || 'EUR'):(col.currencyDisplay || 'symbol'):numberFormat(col):col.locale }}\n }\n @case ('date') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy'):undefined:col.locale }}\n }\n @case ('datetime') {\n {{ $any(cellValue(row, col)) | date:(col.dateFormat || 'dd/MM/yyyy HH:mm'):undefined:col.locale }}\n }\n @case ('boolean') {\n @if (cellValue(row, col)) {\n <span class=\"bool yes\"><span class=\"dot\"></span> \u2713</span>\n } @else {\n <span class=\"bool no\"><span class=\"dot\"></span> \u2014</span>\n }\n }\n @case ('mono') {\n <span class=\"ref\">{{ cellValue(row, col) }}</span>\n }\n @case ('chip') {\n <span [class]=\"'chip ' + (col.chipPrefix || 'chip-') + cellValue(row, col)\">\n \u25CF {{ cellValue(row, col) }}\n </span>\n }\n @case ('status') {\n <ef-status-chip\n [referenceKey]=\"col.referenceKey || ''\"\n [code]=\"$any(cellValue(row, col))\"\n />\n }\n @case ('reference') {\n {{ resolveReference(cellValue(row, col), col) }}\n }\n @default {\n {{ cellValue(row, col) }}\n }\n }\n }\n</ng-template>\n\n<div class=\"tbl-wrap\" [class.has-open-menu]=\"openMenuCount() > 0 || openTool() !== null\" [attr.data-density]=\"density()\">\n <!-- \u2500\u2500 HEAD ROW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"tbl-head\">\n <div class=\"count small\">\n @if (loading()) {\n <span>{{ loadingKey() | translate }}</span>\n } @else if (errorMsg()) {\n <!-- A refresh that failed on top of rows already on screen keeps the\n rows, so this chip is the only sign anything went wrong \u2014 and the\n rows it sits above may now be stale. It has to say so, and say it\n in the product's words: `errorMsg` is `err.message` from the HTTP\n layer, and \"500 Internal Server Error\" tells an operator nothing\n they can act on.\n\n The raw string stays on `data-error`, where devtools can read it\n and no reader ever meets it. `title` was the obvious place and is\n the wrong one: it renders the exception in a tooltip, which is\n precisely what this change exists to stop, and it is unreachable\n on touch besides.\n\n role=\"status\", not \"alert\": polite is right for a signal beside\n rows that are still usable, and when the body is showing the\n error state it already carries the alert \u2014 one failure must not\n interrupt a screen-reader twice. -->\n <span\n role=\"status\"\n [attr.data-error]=\"errorMsg()\"\n style=\"color: var(--st-cancelled-fg); font-weight: 600;\"\n >\u26A0 {{ 'common_error_refresh_failed' | translate }}</span\n >\n } @else {\n <ng-content select=\"[tbl-head-info]\"></ng-content>\n }\n </div>\n <div class=\"tbl-tools\">\n <ng-content select=\"[tbl-head-actions]\"></ng-content>\n\n @if (canExport()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [disabled]=\"exporting()\"\n [attr.aria-busy]=\"exporting()\"\n (click)=\"requestExport()\"\n >\n <i\n class=\"pi\"\n [class.pi-download]=\"!exporting()\"\n [class.pi-spinner]=\"exporting()\"\n [class.is-spinning]=\"exporting()\"\n aria-hidden=\"true\"\n ></i>\n {{ (exporting() ? 'common_export_running' : 'common_export') | translate }}\n </button>\n </div>\n }\n\n @if (showDensityControl()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'density'\"\n [attr.aria-expanded]=\"openTool() === 'density'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('density')\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n {{ 'common_density' | translate }}\n </button>\n @if (openTool() === 'density') {\n <div class=\"tbl-tool__menu\" role=\"menu\">\n @for (opt of densityOptions; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"density() === opt.value\"\n [class.is-active]=\"density() === opt.value\"\n (click)=\"setDensity(opt.value)\"\n >\n <i class=\"pi\" [ngClass]=\"opt.icon\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ opt.labelKey | translate }}</span>\n @if (density() === opt.value) {\n <i class=\"pi pi-check tbl-tool__check\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showColumnPicker()) {\n <div class=\"tbl-tool\">\n <button\n class=\"btn btn-ghost btn-sm\"\n type=\"button\"\n [class.open]=\"openTool() === 'columns'\"\n [attr.aria-expanded]=\"openTool() === 'columns'\"\n aria-haspopup=\"menu\"\n (click)=\"toggleTool('columns')\"\n >\n <i class=\"pi pi-th-large\" aria-hidden=\"true\"></i>\n {{ 'common_columns' | translate }}\n </button>\n @if (openTool() === 'columns') {\n <div class=\"tbl-tool__menu tbl-tool__menu--wide\" role=\"menu\">\n @for (col of hideableColumns(); track col.id) {\n <button\n type=\"button\"\n role=\"menuitemcheckbox\"\n [attr.aria-checked]=\"!isColumnHidden(col.id)\"\n [class.is-active]=\"!isColumnHidden(col.id)\"\n (click)=\"toggleColumn(col.id)\"\n >\n <i class=\"pi\" [class.pi-check]=\"!isColumnHidden(col.id)\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{\n col.headerKey ? (col.headerKey | translate) : (col.header || col.id)\n }}</span>\n </button>\n }\n <div class=\"tbl-tool__sep\"></div>\n <button type=\"button\" role=\"menuitem\" (click)=\"resetColumns()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n <span class=\"tbl-tool__label\">{{ 'common_columns_reset' | translate }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 TABLE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (isMobile()) {\n <!-- \u2500\u2500 MOBILE LIST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A six-column table on a 390px screen either overflows or\n squeezes every column to nothing, so the rows become a list.\n Same columns, same cell renderers; each column just declares\n (or is given) a role. Both shapes expand to the same detail \u2014\n they differ only in how much they show collapsed. -->\n <ul\n class=\"mlist\"\n [attr.data-layout]=\"effectiveMobileLayout()\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n >\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <li class=\"mrow\" [class.is-open]=\"isExpanded(row, i)\">\n <div\n class=\"mrow__head\"\n [class.is-tappable]=\"headIsControl()\"\n [attr.role]=\"headIsControl() ? 'button' : null\"\n [attr.tabindex]=\"headIsControl() ? 0 : null\"\n [attr.aria-expanded]=\"!rowNavigable() && hasDetail() ? isExpanded(row, i) : null\"\n (click)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.enter)=\"onRowHeadActivate(row, i, $event)\"\n (keydown.space)=\"onRowHeadActivate(row, i, $event)\"\n >\n <div class=\"mrow__lead\">\n @if (primaryColumn(); as pc) {\n <span class=\"mrow__primary\" efOverflowTooltip efTooltip efTooltipPosition=\"top\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: pc, index: i }\"></ng-container>\n </span>\n }\n @if (effectiveMobileLayout() === 'row' && secondaryColumns().length) {\n <span class=\"mrow__secondary\">\n @for (sc of secondaryColumns(); track sc.id) {\n <span class=\"mrow__sec-part\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: sc, index: i }\"></ng-container>\n </span>\n }\n </span>\n }\n </div>\n\n <div class=\"mrow__trail\">\n @if (statusColumn(); as st) {\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: st, index: i }\"></ng-container>\n }\n @if (hasActionsColumn()) {\n <span class=\"mrow__actions\">\n <ef-row-actions [items]=\"defaultRowActions(row)\" [context]=\"rowActionsContext()\" />\n </span>\n }\n @if (hasDetail() && effectiveMobileLayout() === 'row') {\n @if (rowNavigable()) {\n <!-- The head opens the record, so expansion needs a control\n of its own. Same glyph, promoted from decoration to a\n button that carries the state it was already drawing. -->\n <button\n type=\"button\"\n class=\"mrow__disclosure\"\n [attr.aria-expanded]=\"isExpanded(row, i)\"\n [attr.aria-label]=\"(isExpanded(row, i) ? 'common_view_less' : 'common_view_more') | translate\"\n (click)=\"onDisclosureToggle(row, i, $event)\"\n >\n <i\n class=\"pi pi-chevron-down mrow__chevron\"\n [class.is-open]=\"isExpanded(row, i)\"\n aria-hidden=\"true\"\n ></i>\n </button>\n } @else {\n <!-- Says the row opens, and which way it is now. Decorative:\n the whole head is the control, and it carries the state. -->\n <i\n class=\"pi pi-chevron-down mrow__chevron\"\n [class.is-open]=\"isExpanded(row, i)\"\n aria-hidden=\"true\"\n ></i>\n }\n }\n </div>\n </div>\n\n @if (visibleDetail(row, i).length) {\n <dl class=\"mrow__detail\">\n @for (col of visibleDetail(row, i); track col.id) {\n <div class=\"mrow__field\">\n <dt>{{ $any(col).headerKey ? ($any(col).headerKey | translate) : $any(col).header }}</dt>\n <dd [class]=\"cellAlignClass($any(col))\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"></ng-container>\n </dd>\n </div>\n }\n </dl>\n }\n\n @if (hasMoreThanPreview() && effectiveMobileLayout() === 'card') {\n <button class=\"mrow__more\" type=\"button\" (click)=\"toggleExpanded(row, i)\">\n <span>{{ (isExpanded(row, i) ? 'common_view_less' : 'common_view_more') | translate }}</span>\n <i class=\"pi pi-chevron-down mrow__chevron\" [class.is-open]=\"isExpanded(row, i)\" aria-hidden=\"true\"></i>\n </button>\n }\n </li>\n } @empty {\n <!-- No rows has three meanings and they must not share a screen:\n still loading is a skeleton, the request failed is the error\n state, finished-and-empty is the empty state. Saying \"nothing\n here yet\" to someone whose data is still in flight \u2014 or whose\n request just failed \u2014 is a lie the whole time it shows. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <li class=\"mrow mrow--skeleton\">\n <ef-skeleton variant=\"text\" [rows]=\"2\" />\n </li>\n }\n } @else if (showError()) {\n <li class=\"mrow mrow--empty\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </li>\n } @else {\n <li class=\"mrow mrow--empty\">\n <ng-content select=\"[empty-state-mobile]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </li>\n }\n }\n </ul>\n } @else {\n <table class=\"tbl\" [class.tbl--fixed]=\"fixedLayout()\">\n <thead>\n <tr>\n @if (reorderable()) {\n <th class=\"tbl-drag-h\" aria-hidden=\"true\"></th>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <!-- A sortable header is a real <button> inside the <th>, not a\n click handler on the cell. The cell carries aria-sort so the\n current order is announced; the arrow stays decorative.\n Sorting was previously mouse-only and silent to assistive\n technology, on every list screen in the product. -->\n <th\n [class]=\"headerAlignClass(col)\"\n [class.is-sortable]=\"col.sortable\"\n [style.width]=\"col.width || null\"\n [attr.data-col]=\"col.id\"\n [attr.aria-sort]=\"ariaSort(col)\"\n >\n @if (col.sortable) {\n <button type=\"button\" class=\"tbl-sort\" (click)=\"onHeaderClick(col)\">\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n @if (sortIndicator(col)) {\n <span aria-hidden=\"true\" class=\"tbl-sort__arrow\">{{ sortIndicator(col) }}</span>\n }\n </button>\n } @else {\n <ng-container *ngTemplateOutlet=\"headerLabelTpl; context: { col: col }\"></ng-container>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody [attr.aria-busy]=\"loading() ? 'true' : null\">\n @for (row of rows(); track trackByRow($index, row); let i = $index) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n [class.is-dragging]=\"dragFrom() === i\"\n [class.is-drop-target]=\"dragFrom() !== null && dragFrom() !== i && dragTo() === i\"\n [attr.tabindex]=\"rowDoubleClickable() ? 0 : null\"\n [attr.role]=\"rowDoubleClickable() ? 'button' : null\"\n (click)=\"onRowClick(row, $event)\"\n (keydown.enter)=\"onRowKeydown(row, $event)\"\n >\n @if (reorderable()) {\n <td class=\"tbl-drag\">\n <!-- The row is draggable only by this handle, so a drag never\n starts from a cell the operator meant to click or select.\n Arrow keys move the row for anyone not using a pointer. -->\n <button\n type=\"button\"\n class=\"tbl-drag__grip\"\n [attr.aria-label]=\"'common_reorder_row' | translate\"\n (pointerdown)=\"onDragPointerDown(i, $event)\"\n (pointermove)=\"onDragPointerMove($event)\"\n (pointerup)=\"onDragPointerUp()\"\n (pointercancel)=\"onDragPointerUp()\"\n (keydown)=\"onDragKeydown(i, $event)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n </td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td efOverflowTooltip efTooltip efTooltipPosition=\"top\" [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n @if (col.id === 'actions' && hasActionsColumn()) {\n <!-- Auto row-actions cell: View / Edit / Duplicate / Delete,\n gated by [show*Action] flags + ScreenContext perms. -->\n <ef-row-actions\n [items]=\"defaultRowActions(row)\"\n [context]=\"rowActionsContext()\"\n />\n } @else {\n <ng-container\n *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: col, index: i }\"\n ></ng-container>\n }\n </td>\n }\n </tr>\n } @empty {\n <!-- Placeholder rows keep the real columns, so the table that\n arrives lands where the shimmer already was instead of\n reflowing the page under the reader's eyes. -->\n @if (showSkeleton()) {\n @for (line of skeletonLines(); track line) {\n <tr>\n @if (reorderable()) {\n <td class=\"tbl-drag\"></td>\n }\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\n <td [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n <ef-skeleton variant=\"text\" [rows]=\"1\" />\n </td>\n }\n </tr>\n }\n } @else if (showError()) {\n <!-- The failure replaces the body, so it spans the full row \u2014\n crammed into column one it reads as a value, not a state. -->\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-container *ngTemplateOutlet=\"errorStateTpl\"></ng-container>\n </td>\n </tr>\n } @else {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length + (reorderable() ? 1 : 0)\">\n <ng-content select=\"[empty-state]\">\n <ng-container *ngTemplateOutlet=\"defaultEmptyTpl\"></ng-container>\n </ng-content>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n }\n\n <!-- \u2500\u2500 PAGER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Paging controls for an empty result set are chrome with nothing to\n page, so they stay out of the way until there is something to move\n through. -->\n @if (!hidePager() && rows().length > 0) {\n <ef-pager\n [pageNumber]=\"pageNumber()\"\n [pageSize]=\"pageSize()\"\n [totalCount]=\"totalCount()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n (pageChange)=\"pageChange.emit($event)\"\n (pageSizeChange)=\"pageSizeChange.emit($event)\"\n />\n }\n</div>\n\n<!-- \u2500\u2500 DEFAULT EMPTY STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Rendered when a screen projects nothing into [empty-state] /\n [empty-state-mobile]. It words itself from `searchTerm`, because\n \"nothing matched what you typed\" and \"there is nothing here yet\"\n are different situations needing different ways out. -->\n<ng-template #defaultEmptyTpl>\n <div class=\"tbl-empty\">\n <i\n class=\"pi tbl-empty__icon\"\n [class.pi-search]=\"!!searchTerm()\"\n [class.pi-inbox]=\"!searchTerm()\"\n aria-hidden=\"true\"\n ></i>\n\n @if (searchTerm()) {\n <p class=\"tbl-empty__title\">\n {{ 'common_empty_no_results' | translate: { term: searchTerm() } }}\n </p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_no_results_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"clearSearch.emit()\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n {{ 'common_empty_clear_filters' | translate }}\n </button>\n } @else {\n <p class=\"tbl-empty__title\">{{ 'common_empty_none_yet' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_empty_none_yet_hint' | translate }}</p>\n }\n </div>\n</ng-template>\n\n<!-- \u2500\u2500 ERROR STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A request that failed and came back with nothing is not an empty\n collection, and must never borrow its words: \"nothing here yet\" is\n a confident false statement about the operator's data, and they may\n act on it. So this replaces the empty state outright, projected\n [empty-state] included \u2014 a screen's own \"create your first client\"\n copy is just as wrong here as the default.\n\n It says nothing about *what* failed. `errorMsg` is `err.message`\n from the HTTP layer, and a status line or an exception string tells\n an operator nothing they can act on; it survives on the head chip's\n `data-error` attribute, for whoever is reading the DOM. This carries\n the two things that help: their data is intact, and the way to try\n again.\n\n role=\"alert\" goes on this div, never on the <tr> / <td> / <li> that\n hosts it \u2014 a role on a row or cell takes the table's semantics down\n with it. It fires on insertion, which is exactly when @if adds it. -->\n<ng-template #errorStateTpl>\n <div class=\"tbl-empty tbl-empty--error\" role=\"alert\">\n <i class=\"pi pi-exclamation-triangle tbl-empty__icon\" aria-hidden=\"true\"></i>\n <p class=\"tbl-empty__title\">{{ 'common_error_load_failed' | translate }}</p>\n <p class=\"tbl-empty__hint\">{{ 'common_error_load_failed_hint' | translate }}</p>\n <button class=\"btn btn-sm tbl-empty__action\" type=\"button\" (click)=\"retry.emit()\">\n <i class=\"pi pi-refresh\" aria-hidden=\"true\"></i>\n {{ 'common_retry' | translate }}\n </button>\n </div>\n</ng-template>\n\n<!-- Header label, shared by the sortable and plain header cells so the\n projection rules stay in one place. -->\n<ng-template #headerLabelTpl let-col=\"col\">\n @if (headerTemplate(col.id); as headerTpl) {\n <ng-container *ngTemplateOutlet=\"headerTpl; context: { $implicit: col, col: col }\"></ng-container>\n } @else if (col.headerKey) {\n <span>{{ col.headerKey | translate }}</span>\n } @else if (col.header) {\n <span>{{ col.header }}</span>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";.tbl thead th.is-sortable{cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl thead th.is-sortable:hover{color:var(--text)}.tbl tbody tr.is-row-clickable{cursor:pointer}.tbl-tools{display:flex;gap:6px;align-items:center}.tbl-tool{position:relative}.tbl-tool>.btn.open{color:var(--text);background:var(--paper)}.tbl-tool__menu{position:absolute;top:calc(100% + 8px);inset-inline-end:0;z-index:40;min-width:212px;max-height:340px;overflow-y:auto;padding:6px;display:grid;gap:1px;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);animation:row-actions-menu-in var(--t-base) var(--ease-spring)}.tbl-tool__menu--wide{min-width:248px}.tbl-tool__menu button{appearance:none;background:transparent;border:0;width:100%;text-align:start;font:inherit;font-size:13.5px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:var(--r-md);cursor:pointer;transition:background-color var(--t-fast) var(--ease-out)}.tbl-tool__menu button:hover,.tbl-tool__menu button:focus-visible{background:var(--paper);outline:0}.tbl-tool__menu button>.pi{width:16px;flex:none;font-size:14px;color:var(--muted)}.tbl-tool__menu button.is-active>.pi{color:var(--module, var(--text))}.tbl-tool__label{flex:1 1 auto;min-width:0}.tbl-tool__check{color:var(--module, var(--text))}.tbl-tool__sep{margin:5px 2px;border-top:1px solid var(--rule)}.tbl-wrap[data-density=compact] .tbl tbody td{padding-top:4px;padding-bottom:4px}.tbl-wrap[data-density=comfortable] .tbl tbody td{padding-top:16px;padding-bottom:16px}.mlist{list-style:none;margin:0;padding:0;display:grid}.mrow{border-top:1px solid var(--rule)}.mrow:first-child{border-top:0}.mrow__head{display:flex;align-items:flex-start;gap:var(--s-3);padding:12px var(--s-4);min-height:var(--hit-touch)}.mrow__head.is-tappable{cursor:pointer}.mrow__head.is-tappable:active{background:var(--paper)}.mrow__lead{flex:1;min-width:0;display:grid;gap:2px}.mrow__primary{font-weight:700;font-size:14px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mrow__secondary{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px;font-size:12.5px;color:var(--text-mute);min-width:0}.mrow__secondary .mrow__sec-part+.mrow__sec-part:before{content:\"\\b7\";margin-inline-end:6px;color:var(--text-soft)}.mrow__trail{display:flex;align-items:center;justify-content:flex-end;gap:var(--s-2);flex-shrink:0;min-height:20px}.mrow__actions{display:inline-flex}.mrow__detail{margin:0;padding:0 var(--s-4) 12px;display:grid;gap:1px}.mrow__field{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-3);padding:7px 0;border-top:1px solid var(--rule-soft)}.mrow__field dt{font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-mute);flex-shrink:0}.mrow__field dd{margin:0;font-size:13.5px;color:var(--text);text-align:end;min-width:0;overflow-wrap:anywhere}.mrow__more{appearance:none;width:100%;background:transparent;border:0;border-top:1px solid var(--rule-soft);padding:10px;font:inherit;font-size:13px;font-weight:600;color:var(--tenant-500);cursor:pointer}.mlist[data-layout=card]{gap:var(--s-3);padding:var(--s-3) var(--s-3) 0}.mlist[data-layout=card] .mrow{border:1px solid var(--rule);border-radius:var(--r-lg);background:var(--paper-alt);overflow:hidden}.mlist[data-layout=card] .mrow__head{border-bottom:1px solid var(--rule)}.mrow__chevron{font-size:11px;color:var(--text-soft);transition:transform var(--t-fast) var(--ease-out);flex-shrink:0}.mrow__chevron.is-open{transform:rotate(180deg);color:var(--text-mute)}.mrow__head.is-tappable:focus-visible{outline:2px solid var(--tenant-400);outline-offset:-2px}.mrow__more{display:flex;align-items:center;justify-content:center;gap:6px}.tbl-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-2);padding:var(--s-9) var(--s-4);text-align:center}.tbl-empty__icon{font-size:28px;line-height:1;color:var(--ink-400);margin-block-end:var(--s-1)}.tbl-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--text);max-inline-size:46ch;overflow-wrap:break-word}.tbl-empty__hint{margin:0;font-size:13px;color:var(--ink-600);max-inline-size:52ch;text-wrap:pretty}.tbl-empty__action{margin-block-start:var(--s-3);min-block-size:var(--hit-base)}.tbl-empty--error .tbl-empty__icon{color:var(--st-cancelled-fg)}.mrow--empty{padding:0}.mrow--skeleton{padding:12px var(--s-4)}@media(max-width:767px){.tbl-empty{padding:var(--s-7) var(--s-4)}.tbl-empty__action{min-block-size:var(--hit-touch)}}.pi.is-spinning{animation:ef-export-spin .9s linear infinite}@keyframes ef-export-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.pi.is-spinning{animation:none}}.tbl th.tbl-drag-h,.tbl td.tbl-drag{width:28px;padding-inline:4px 0;text-align:center}.tbl td.tbl-drag{cursor:default}.tbl .tbl-drag__grip{display:inline-flex;align-items:center;justify-content:center;width:22px;height:26px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--text-soft);cursor:grab;touch-action:none;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.tbl .tbl-drag__grip:hover{background:var(--paper);color:var(--text)}.tbl .tbl-drag__grip:focus-visible{outline:2px solid var(--tenant, var(--rule));outline-offset:1px}.tbl .tbl-drag__grip:active{cursor:grabbing}.tbl .tbl-drag__grip i{font-size:12px}.tbl tbody tr.is-dragging>td{background:var(--paper);opacity:.55}.tbl tbody tr.is-drop-target>td{box-shadow:inset 0 2px 0 var(--tenant, var(--rule))}.tbl.tbl--fixed{table-layout:fixed}.mrow__disclosure{appearance:none;background:transparent;border:0;padding:0;margin:0;display:inline-grid;place-items:center;color:var(--text-mute);cursor:pointer;position:relative}.mrow__disclosure:after{content:\"\";position:absolute;top:50%;left:50%;width:var(--hit-touch);height:var(--hit-touch);transform:translate(-50%,-50%)}.mrow__disclosure:focus-visible{outline:2px solid color-mix(in srgb,var(--ink-active) 40%,transparent);outline-offset:2px;border-radius:var(--r-pill)}\n"] }]
5231
5291
  }], propDecorators: { mobileLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileLayout", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], trackByField: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByField", required: false }] }], pageNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageNumber", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], totalCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalCount", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], hidePager: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePager", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], errorMsg: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMsg", required: false }] }], loadingKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingKey", required: false }] }], rowDoubleClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowDoubleClickable", required: false }] }], hasActionsColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasActionsColumn", required: false }] }], rowActionsContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActionsContext", required: false }] }], showViewAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showViewAction", required: false }] }], showEditAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditAction", required: false }] }], showDuplicateAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDuplicateAction", required: false }] }], showDeleteAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDeleteAction", required: false }] }], showDensityControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDensityControl", required: false }] }], showColumnPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnPicker", required: false }] }], showExportControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExportControl", required: false }] }], exporting: [{ type: i0.Input, args: [{ isSignal: true, alias: "exporting", required: false }] }], screenContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "screenContext", required: false }] }], searchTerm: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchTerm", required: false }] }], tableKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableKey", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], rowDoubleClick: [{ type: i0.Output, args: ["rowDoubleClick"] }], rowAction: [{ type: i0.Output, args: ["rowAction"] }], exportRequest: [{ type: i0.Output, args: ["exportRequest"] }], clearSearch: [{ type: i0.Output, args: ["clearSearch"] }], retry: [{ type: i0.Output, args: ["retry"] }], bodyTemplates: [{
5232
5292
  type: ContentChildren,
5233
5293
  args: [EfColumnTemplateDirective]
@@ -5254,9 +5314,6 @@ class EfOrderBuilderComponent {
5254
5314
  rowTrackBy = (_, item) => item.id;
5255
5315
  config = input({
5256
5316
  documentType: 'order',
5257
- headerLabel: 'Commande',
5258
- dateLabel: 'Date de commande',
5259
- customerLabel: 'Client',
5260
5317
  enableTax: false,
5261
5318
  enableDiscount: true,
5262
5319
  showCatalogue: false,
@@ -7208,13 +7265,13 @@ class EfModuleSideComponent {
7208
7265
  return i.id;
7209
7266
  }
7210
7267
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfModuleSideComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7211
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfModuleSideComponent, isStandalone: true, selector: "ef-module-side", host: { properties: { "attr.data-module": "moduleId()" } }, ngImport: i0, template: "@if (module(); as m) {\n <div class=\"ef-module-side__scroll\">\n <header class=\"ef-module-side__header\">\n <span\n class=\"ef-module-side__header-icon\"\n [style.--m-color]=\"'var(--' + m.accent + ')'\"\n aria-hidden=\"true\"\n >\n @if (m.icon) {\n <i [class]=\"m.icon\"></i>\n }\n </span>\n <span class=\"ef-module-side__header-text\">\n <span class=\"ef-module-side__name\">{{ m.labelKey | translate }}</span>\n <span class=\"ef-module-side__meta\">\n <ng-content select=\"[side-header-meta]\"></ng-content>\n </span>\n </span>\n </header>\n\n @for (section of visibleSections(); track section.id) {\n <section class=\"ef-module-side__section\">\n @if (section.labelKey) {\n <h3 class=\"ef-module-side__section-title\">{{ section.labelKey | translate }}</h3>\n }\n <ul class=\"ef-module-side__list\">\n @for (item of section.items; track item.id) {\n <li>\n <a\n class=\"ef-module-side__item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"is-active\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n >\n @if (item.icon) {\n <i class=\"ef-module-side__icon\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-module-side__label\">{{ item.labelKey | translate }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <span class=\"ef-module-side__badge\">{{ item.badge }}</span>\n }\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <div class=\"ef-module-side__footer\">\n <ng-content></ng-content>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:0 0 240px;width:240px;background:var(--paper-alt);border-right:1px solid var(--rule)}:host:empty{display:none}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}.ef-module-side__scroll{flex:1 1 auto;min-height:0;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px}.ef-module-side__footer{flex:0 0 auto;padding:8px;border-top:1px solid var(--rule)}.ef-module-side__footer:empty{display:none}.ef-module-side__header{display:flex;align-items:center;gap:10px;padding:4px 4px 12px;border-bottom:1px solid var(--rule)}.ef-module-side__header-icon{display:grid;place-items:center;flex-shrink:0;width:28px;height:28px;border-radius:var(--r-md);background:var(--m-color, var(--module));color:#fff;font-size:14px;line-height:1}.ef-module-side__header-text{display:flex;flex-direction:column;min-width:0;line-height:1.2}.ef-module-side__name{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 90;font-size:15px;letter-spacing:-.01em;color:var(--text)}.ef-module-side__meta{font-size:10.5px;font-weight:600;color:var(--text-mute);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__meta:empty{display:none}.ef-module-side__section{display:grid;gap:2px}.ef-module-side__section-title{display:flex;align-items:center;gap:9px;margin:0 0 8px;padding:0 12px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft)}.ef-module-side__section-title:before{content:\"\";flex:0 0 auto;width:13px;height:2px;border-radius:var(--r-pill);background:var(--module, var(--tenant-500))}.ef-module-side__section-title:after{content:\"\";flex:1 1 auto;height:1px;background:linear-gradient(90deg,var(--rule),transparent)}.ef-module-side__list{list-style:none;margin:0;padding:0;display:grid;gap:2px}.ef-module-side__item{position:relative;display:flex;align-items:center;gap:12px;min-height:36px;padding:7px 12px;border-radius:var(--r-pill);color:var(--text-mute);text-decoration:none;font-size:13.5px;font-weight:600;letter-spacing:-.005em;cursor:pointer;transition:color var(--t-fast) var(--ease-out),background-color var(--t-fast) var(--ease-out)}.ef-module-side__item:hover{color:var(--text);background:var(--paper)}.ef-module-side__item.is-active{color:var(--paper);background:var(--ink-active)}.ef-module-side__item:focus-visible{outline:2px solid var(--tenant-400);outline-offset:2px}.ef-module-side__icon{font-size:16px;line-height:1;width:16px;text-align:center;flex-shrink:0;opacity:.85}.ef-module-side__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__badge{flex:0 0 auto;padding:2px 7px;border-radius:var(--r-pill);background:var(--tenant-100);color:var(--tenant-700);font-family:var(--font-mono);font-size:10px;font-weight:700;line-height:1.2;letter-spacing:0}.ef-module-side__item.is-active .ef-module-side__badge{background:#ffffff2e;color:var(--paper)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7268
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfModuleSideComponent, isStandalone: true, selector: "ef-module-side", host: { properties: { "attr.data-module": "moduleId()" } }, ngImport: i0, template: "@if (module(); as m) {\n <div class=\"ef-module-side__scroll\">\n <header class=\"ef-module-side__header\">\n <span\n class=\"ef-module-side__header-icon\"\n [style.--m-color]=\"'var(--' + m.accent + ')'\"\n aria-hidden=\"true\"\n >\n @if (m.icon) {\n <i [class]=\"m.icon\"></i>\n }\n </span>\n <span class=\"ef-module-side__header-text\">\n <span class=\"ef-module-side__name\">{{ m.labelKey | translate }}</span>\n <span class=\"ef-module-side__meta\">\n <ng-content select=\"[side-header-meta]\"></ng-content>\n </span>\n </span>\n </header>\n\n @for (section of visibleSections(); track section.id) {\n <section class=\"ef-module-side__section\">\n @if (section.labelKey) {\n <h3 class=\"ef-module-side__section-title\">{{ section.labelKey | translate }}</h3>\n }\n <ul class=\"ef-module-side__list\">\n @for (item of section.items; track item.id) {\n <li>\n <a\n class=\"ef-module-side__item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"is-active\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n >\n @if (item.icon) {\n <i class=\"ef-module-side__icon\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-module-side__label\">{{ item.labelKey | translate }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <span class=\"ef-module-side__badge\">{{ item.badge }}</span>\n }\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <div class=\"ef-module-side__footer\">\n <ng-content></ng-content>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:0 0 240px;width:240px;background:var(--paper-alt);border-right:1px solid var(--rule)}:host:empty{display:none}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}.ef-module-side__scroll{flex:1 1 auto;min-height:0;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px}.ef-module-side__footer{flex:0 0 auto;padding:8px;border-top:1px solid var(--rule)}.ef-module-side__footer:empty{display:none}.ef-module-side__header{display:flex;align-items:center;gap:10px;padding:4px 4px 12px;border-bottom:1px solid var(--rule)}.ef-module-side__header-icon{display:grid;place-items:center;flex-shrink:0;width:28px;height:28px;border-radius:var(--r-md);background:var(--m-color, var(--module));color:#fff;font-size:14px;line-height:1}.ef-module-side__header-text{display:flex;flex-direction:column;min-width:0;line-height:1.2}.ef-module-side__name{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 90;font-size:15px;letter-spacing:-.01em;color:var(--text)}.ef-module-side__meta{font-size:10.5px;font-weight:600;color:var(--text-mute);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__meta:empty{display:none}.ef-module-side__section{display:grid;gap:2px}.ef-module-side__section-title{display:flex;align-items:center;gap:9px;margin:0 0 8px;padding:0 12px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-mute)}.ef-module-side__section-title:before{content:\"\";flex:0 0 auto;width:13px;height:2px;border-radius:var(--r-pill);background:var(--module, var(--tenant-500))}.ef-module-side__section-title:after{content:\"\";flex:1 1 auto;height:1px;background:linear-gradient(90deg,var(--rule),transparent)}.ef-module-side__list{list-style:none;margin:0;padding:0;display:grid;gap:2px}.ef-module-side__item{position:relative;display:flex;align-items:center;gap:12px;min-height:36px;padding:7px 12px;border-radius:var(--r-pill);color:var(--text-mute);text-decoration:none;font-size:13.5px;font-weight:600;letter-spacing:-.005em;cursor:pointer;transition:color var(--t-fast) var(--ease-out),background-color var(--t-fast) var(--ease-out)}.ef-module-side__item:hover{color:var(--text);background:var(--paper)}.ef-module-side__item.is-active{color:var(--paper);background:var(--ink-active)}.ef-module-side__item:focus-visible{outline:2px solid var(--tenant-400);outline-offset:2px}.ef-module-side__icon{font-size:16px;line-height:1;width:16px;text-align:center;flex-shrink:0;opacity:.85}.ef-module-side__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__badge{flex:0 0 auto;padding:2px 7px;border-radius:var(--r-pill);background:var(--tenant-100);color:var(--tenant-700);font-family:var(--font-mono);font-size:10px;font-weight:700;line-height:1.2;letter-spacing:0}.ef-module-side__item.is-active .ef-module-side__badge{background:#ffffff2e;color:var(--paper)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7212
7269
  }
7213
7270
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfModuleSideComponent, decorators: [{
7214
7271
  type: Component,
7215
7272
  args: [{ selector: 'ef-module-side', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, RouterLink, RouterLinkActive, TranslateModule], host: {
7216
7273
  '[attr.data-module]': 'moduleId()',
7217
- }, template: "@if (module(); as m) {\n <div class=\"ef-module-side__scroll\">\n <header class=\"ef-module-side__header\">\n <span\n class=\"ef-module-side__header-icon\"\n [style.--m-color]=\"'var(--' + m.accent + ')'\"\n aria-hidden=\"true\"\n >\n @if (m.icon) {\n <i [class]=\"m.icon\"></i>\n }\n </span>\n <span class=\"ef-module-side__header-text\">\n <span class=\"ef-module-side__name\">{{ m.labelKey | translate }}</span>\n <span class=\"ef-module-side__meta\">\n <ng-content select=\"[side-header-meta]\"></ng-content>\n </span>\n </span>\n </header>\n\n @for (section of visibleSections(); track section.id) {\n <section class=\"ef-module-side__section\">\n @if (section.labelKey) {\n <h3 class=\"ef-module-side__section-title\">{{ section.labelKey | translate }}</h3>\n }\n <ul class=\"ef-module-side__list\">\n @for (item of section.items; track item.id) {\n <li>\n <a\n class=\"ef-module-side__item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"is-active\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n >\n @if (item.icon) {\n <i class=\"ef-module-side__icon\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-module-side__label\">{{ item.labelKey | translate }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <span class=\"ef-module-side__badge\">{{ item.badge }}</span>\n }\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <div class=\"ef-module-side__footer\">\n <ng-content></ng-content>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:0 0 240px;width:240px;background:var(--paper-alt);border-right:1px solid var(--rule)}:host:empty{display:none}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}.ef-module-side__scroll{flex:1 1 auto;min-height:0;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px}.ef-module-side__footer{flex:0 0 auto;padding:8px;border-top:1px solid var(--rule)}.ef-module-side__footer:empty{display:none}.ef-module-side__header{display:flex;align-items:center;gap:10px;padding:4px 4px 12px;border-bottom:1px solid var(--rule)}.ef-module-side__header-icon{display:grid;place-items:center;flex-shrink:0;width:28px;height:28px;border-radius:var(--r-md);background:var(--m-color, var(--module));color:#fff;font-size:14px;line-height:1}.ef-module-side__header-text{display:flex;flex-direction:column;min-width:0;line-height:1.2}.ef-module-side__name{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 90;font-size:15px;letter-spacing:-.01em;color:var(--text)}.ef-module-side__meta{font-size:10.5px;font-weight:600;color:var(--text-mute);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__meta:empty{display:none}.ef-module-side__section{display:grid;gap:2px}.ef-module-side__section-title{display:flex;align-items:center;gap:9px;margin:0 0 8px;padding:0 12px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft)}.ef-module-side__section-title:before{content:\"\";flex:0 0 auto;width:13px;height:2px;border-radius:var(--r-pill);background:var(--module, var(--tenant-500))}.ef-module-side__section-title:after{content:\"\";flex:1 1 auto;height:1px;background:linear-gradient(90deg,var(--rule),transparent)}.ef-module-side__list{list-style:none;margin:0;padding:0;display:grid;gap:2px}.ef-module-side__item{position:relative;display:flex;align-items:center;gap:12px;min-height:36px;padding:7px 12px;border-radius:var(--r-pill);color:var(--text-mute);text-decoration:none;font-size:13.5px;font-weight:600;letter-spacing:-.005em;cursor:pointer;transition:color var(--t-fast) var(--ease-out),background-color var(--t-fast) var(--ease-out)}.ef-module-side__item:hover{color:var(--text);background:var(--paper)}.ef-module-side__item.is-active{color:var(--paper);background:var(--ink-active)}.ef-module-side__item:focus-visible{outline:2px solid var(--tenant-400);outline-offset:2px}.ef-module-side__icon{font-size:16px;line-height:1;width:16px;text-align:center;flex-shrink:0;opacity:.85}.ef-module-side__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__badge{flex:0 0 auto;padding:2px 7px;border-radius:var(--r-pill);background:var(--tenant-100);color:var(--tenant-700);font-family:var(--font-mono);font-size:10px;font-weight:700;line-height:1.2;letter-spacing:0}.ef-module-side__item.is-active .ef-module-side__badge{background:#ffffff2e;color:var(--paper)}\n"] }]
7274
+ }, template: "@if (module(); as m) {\n <div class=\"ef-module-side__scroll\">\n <header class=\"ef-module-side__header\">\n <span\n class=\"ef-module-side__header-icon\"\n [style.--m-color]=\"'var(--' + m.accent + ')'\"\n aria-hidden=\"true\"\n >\n @if (m.icon) {\n <i [class]=\"m.icon\"></i>\n }\n </span>\n <span class=\"ef-module-side__header-text\">\n <span class=\"ef-module-side__name\">{{ m.labelKey | translate }}</span>\n <span class=\"ef-module-side__meta\">\n <ng-content select=\"[side-header-meta]\"></ng-content>\n </span>\n </span>\n </header>\n\n @for (section of visibleSections(); track section.id) {\n <section class=\"ef-module-side__section\">\n @if (section.labelKey) {\n <h3 class=\"ef-module-side__section-title\">{{ section.labelKey | translate }}</h3>\n }\n <ul class=\"ef-module-side__list\">\n @for (item of section.items; track item.id) {\n <li>\n <a\n class=\"ef-module-side__item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"is-active\"\n [routerLinkActiveOptions]=\"{ exact: false }\"\n >\n @if (item.icon) {\n <i class=\"ef-module-side__icon\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-module-side__label\">{{ item.labelKey | translate }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <span class=\"ef-module-side__badge\">{{ item.badge }}</span>\n }\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n\n <div class=\"ef-module-side__footer\">\n <ng-content></ng-content>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:0 0 240px;width:240px;background:var(--paper-alt);border-right:1px solid var(--rule)}:host:empty{display:none}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}.ef-module-side__scroll{flex:1 1 auto;min-height:0;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px}.ef-module-side__footer{flex:0 0 auto;padding:8px;border-top:1px solid var(--rule)}.ef-module-side__footer:empty{display:none}.ef-module-side__header{display:flex;align-items:center;gap:10px;padding:4px 4px 12px;border-bottom:1px solid var(--rule)}.ef-module-side__header-icon{display:grid;place-items:center;flex-shrink:0;width:28px;height:28px;border-radius:var(--r-md);background:var(--m-color, var(--module));color:#fff;font-size:14px;line-height:1}.ef-module-side__header-text{display:flex;flex-direction:column;min-width:0;line-height:1.2}.ef-module-side__name{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 90;font-size:15px;letter-spacing:-.01em;color:var(--text)}.ef-module-side__meta{font-size:10.5px;font-weight:600;color:var(--text-mute);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__meta:empty{display:none}.ef-module-side__section{display:grid;gap:2px}.ef-module-side__section-title{display:flex;align-items:center;gap:9px;margin:0 0 8px;padding:0 12px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-mute)}.ef-module-side__section-title:before{content:\"\";flex:0 0 auto;width:13px;height:2px;border-radius:var(--r-pill);background:var(--module, var(--tenant-500))}.ef-module-side__section-title:after{content:\"\";flex:1 1 auto;height:1px;background:linear-gradient(90deg,var(--rule),transparent)}.ef-module-side__list{list-style:none;margin:0;padding:0;display:grid;gap:2px}.ef-module-side__item{position:relative;display:flex;align-items:center;gap:12px;min-height:36px;padding:7px 12px;border-radius:var(--r-pill);color:var(--text-mute);text-decoration:none;font-size:13.5px;font-weight:600;letter-spacing:-.005em;cursor:pointer;transition:color var(--t-fast) var(--ease-out),background-color var(--t-fast) var(--ease-out)}.ef-module-side__item:hover{color:var(--text);background:var(--paper)}.ef-module-side__item.is-active{color:var(--paper);background:var(--ink-active)}.ef-module-side__item:focus-visible{outline:2px solid var(--tenant-400);outline-offset:2px}.ef-module-side__icon{font-size:16px;line-height:1;width:16px;text-align:center;flex-shrink:0;opacity:.85}.ef-module-side__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-module-side__badge{flex:0 0 auto;padding:2px 7px;border-radius:var(--r-pill);background:var(--tenant-100);color:var(--tenant-700);font-family:var(--font-mono);font-size:10px;font-weight:700;line-height:1.2;letter-spacing:0}.ef-module-side__item.is-active .ef-module-side__badge{background:#ffffff2e;color:var(--paper)}\n"] }]
7218
7275
  }] });
7219
7276
 
7220
7277
  /**
@@ -7922,11 +7979,11 @@ class EfProfileChipComponent {
7922
7979
  }
7923
7980
  }
7924
7981
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfProfileChipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7925
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfProfileChipComponent, isStandalone: true, selector: "ef-profile-chip", inputs: { name: "name", role: "role", roleKey: "roleKey", initial: "initial", tone: "tone", readonly: ["readonly", "readonly", booleanAttribute] }, outputs: { clickEvent: "clickEvent" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"ef-profile-chip\"\n [class.is-tenant]=\"tone === 'tenant'\"\n [class.is-readonly]=\"readonly\"\n [attr.aria-label]=\"name || null\"\n [attr.tabindex]=\"readonly ? -1 : null\"\n (click)=\"handleClick($event)\"\n>\n <span class=\"ef-profile-chip__avatar\" aria-hidden=\"true\">{{ computedInitial }}</span>\n <span class=\"ef-profile-chip__text\">\n @if (name) {\n <span class=\"ef-profile-chip__name\">{{ name }}</span>\n }\n @if (roleKey || role) {\n <span class=\"ef-profile-chip__role\">\n @if (roleKey) {\n {{ roleKey | translate }}\n } @else {\n {{ role }}\n }\n </span>\n }\n </span>\n</button>\n", styles: [":host{display:block}.ef-profile-chip{display:inline-flex;align-items:center;gap:8px;width:100%;padding:8px 10px;background:transparent;border:0;border-radius:var(--r-md, 8px);color:var(--text);cursor:pointer;text-align:start;font-family:inherit;transition:background var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-profile-chip:hover:not(.is-readonly){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent)}.ef-profile-chip:focus-visible{outline:2px solid color-mix(in srgb,var(--tenant-400, #6b7280) 60%,transparent);outline-offset:1px}.ef-profile-chip.is-readonly{cursor:default}.ef-profile-chip__avatar{display:inline-flex;align-items:center;justify-content:center;flex:0 0 32px;width:32px;height:32px;border-radius:var(--r-pill, 999px);background:var(--ink-active, #0f1115);color:var(--paper, #fff);font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 80;font-size:12px;line-height:1;letter-spacing:-.01em}.is-tenant .ef-profile-chip__avatar{background:var(--tenant-500)}.ef-profile-chip__text{display:flex;flex-direction:column;min-width:0;font-size:12px;line-height:1.3}.ef-profile-chip__name{font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-profile-chip__role{color:var(--text-mute);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7982
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfProfileChipComponent, isStandalone: true, selector: "ef-profile-chip", inputs: { name: "name", role: "role", roleKey: "roleKey", initial: "initial", tone: "tone", readonly: ["readonly", "readonly", booleanAttribute] }, outputs: { clickEvent: "clickEvent" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"ef-profile-chip\"\n [class.is-tenant]=\"tone === 'tenant'\"\n [class.is-readonly]=\"readonly\"\n [attr.aria-label]=\"name || null\"\n [attr.tabindex]=\"readonly ? -1 : null\"\n (click)=\"handleClick($event)\"\n>\n <span class=\"ef-profile-chip__avatar\" aria-hidden=\"true\">{{ computedInitial }}</span>\n <span class=\"ef-profile-chip__text\">\n @if (name) {\n <span class=\"ef-profile-chip__name\">{{ name }}</span>\n }\n @if (roleKey || role) {\n <span class=\"ef-profile-chip__role\">\n @if (roleKey) {\n {{ roleKey | translate }}\n } @else {\n {{ role }}\n }\n </span>\n }\n </span>\n</button>\n", styles: ["@charset \"UTF-8\";:host{display:block}.ef-profile-chip{display:inline-flex;align-items:center;gap:8px;width:100%;padding:8px 10px;background:transparent;border:0;border-radius:var(--r-md, 8px);color:var(--text);cursor:pointer;text-align:start;font-family:inherit;transition:background var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-profile-chip:hover:not(.is-readonly){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent)}.ef-profile-chip:focus-visible{outline:2px solid color-mix(in srgb,var(--tenant-400, #6b7280) 60%,transparent);outline-offset:1px}.ef-profile-chip.is-readonly{cursor:default}.ef-profile-chip__avatar{display:inline-flex;align-items:center;justify-content:center;flex:0 0 32px;width:32px;height:32px;border-radius:var(--r-pill, 999px);background:var(--ink-active, #0f1115);color:var(--paper, #fff);font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 80;font-size:12px;line-height:1;letter-spacing:-.01em}.is-tenant .ef-profile-chip__avatar{background:var(--tenant-500);color:var(--on-tenant)}.ef-profile-chip__text{display:flex;flex-direction:column;min-width:0;font-size:12px;line-height:1.3}.ef-profile-chip__name{font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-profile-chip__role{color:var(--text-mute);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7926
7983
  }
7927
7984
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfProfileChipComponent, decorators: [{
7928
7985
  type: Component,
7929
- args: [{ selector: 'ef-profile-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule], template: "<button\n type=\"button\"\n class=\"ef-profile-chip\"\n [class.is-tenant]=\"tone === 'tenant'\"\n [class.is-readonly]=\"readonly\"\n [attr.aria-label]=\"name || null\"\n [attr.tabindex]=\"readonly ? -1 : null\"\n (click)=\"handleClick($event)\"\n>\n <span class=\"ef-profile-chip__avatar\" aria-hidden=\"true\">{{ computedInitial }}</span>\n <span class=\"ef-profile-chip__text\">\n @if (name) {\n <span class=\"ef-profile-chip__name\">{{ name }}</span>\n }\n @if (roleKey || role) {\n <span class=\"ef-profile-chip__role\">\n @if (roleKey) {\n {{ roleKey | translate }}\n } @else {\n {{ role }}\n }\n </span>\n }\n </span>\n</button>\n", styles: [":host{display:block}.ef-profile-chip{display:inline-flex;align-items:center;gap:8px;width:100%;padding:8px 10px;background:transparent;border:0;border-radius:var(--r-md, 8px);color:var(--text);cursor:pointer;text-align:start;font-family:inherit;transition:background var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-profile-chip:hover:not(.is-readonly){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent)}.ef-profile-chip:focus-visible{outline:2px solid color-mix(in srgb,var(--tenant-400, #6b7280) 60%,transparent);outline-offset:1px}.ef-profile-chip.is-readonly{cursor:default}.ef-profile-chip__avatar{display:inline-flex;align-items:center;justify-content:center;flex:0 0 32px;width:32px;height:32px;border-radius:var(--r-pill, 999px);background:var(--ink-active, #0f1115);color:var(--paper, #fff);font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 80;font-size:12px;line-height:1;letter-spacing:-.01em}.is-tenant .ef-profile-chip__avatar{background:var(--tenant-500)}.ef-profile-chip__text{display:flex;flex-direction:column;min-width:0;font-size:12px;line-height:1.3}.ef-profile-chip__name{font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-profile-chip__role{color:var(--text-mute);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
7986
+ args: [{ selector: 'ef-profile-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule], template: "<button\n type=\"button\"\n class=\"ef-profile-chip\"\n [class.is-tenant]=\"tone === 'tenant'\"\n [class.is-readonly]=\"readonly\"\n [attr.aria-label]=\"name || null\"\n [attr.tabindex]=\"readonly ? -1 : null\"\n (click)=\"handleClick($event)\"\n>\n <span class=\"ef-profile-chip__avatar\" aria-hidden=\"true\">{{ computedInitial }}</span>\n <span class=\"ef-profile-chip__text\">\n @if (name) {\n <span class=\"ef-profile-chip__name\">{{ name }}</span>\n }\n @if (roleKey || role) {\n <span class=\"ef-profile-chip__role\">\n @if (roleKey) {\n {{ roleKey | translate }}\n } @else {\n {{ role }}\n }\n </span>\n }\n </span>\n</button>\n", styles: ["@charset \"UTF-8\";:host{display:block}.ef-profile-chip{display:inline-flex;align-items:center;gap:8px;width:100%;padding:8px 10px;background:transparent;border:0;border-radius:var(--r-md, 8px);color:var(--text);cursor:pointer;text-align:start;font-family:inherit;transition:background var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-profile-chip:hover:not(.is-readonly){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent)}.ef-profile-chip:focus-visible{outline:2px solid color-mix(in srgb,var(--tenant-400, #6b7280) 60%,transparent);outline-offset:1px}.ef-profile-chip.is-readonly{cursor:default}.ef-profile-chip__avatar{display:inline-flex;align-items:center;justify-content:center;flex:0 0 32px;width:32px;height:32px;border-radius:var(--r-pill, 999px);background:var(--ink-active, #0f1115);color:var(--paper, #fff);font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 80;font-size:12px;line-height:1;letter-spacing:-.01em}.is-tenant .ef-profile-chip__avatar{background:var(--tenant-500);color:var(--on-tenant)}.ef-profile-chip__text{display:flex;flex-direction:column;min-width:0;font-size:12px;line-height:1.3}.ef-profile-chip__name{font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ef-profile-chip__role{color:var(--text-mute);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
7930
7987
  }], propDecorators: { name: [{
7931
7988
  type: Input
7932
7989
  }], role: [{