@elasticias/ui 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Pipe, input, Component, output, booleanAttribute, Input, Directive, signal, computed, inject, EventEmitter, Injector, forwardRef, Output, ContentChild, HostBinding, ChangeDetectionStrategy, ElementRef, LOCALE_ID, ViewContainerRef, DestroyRef, ChangeDetectorRef, effect, HostListener, ViewChild, model, viewChild, contentChild, ViewEncapsulation, TemplateRef, ContentChildren, ViewChildren, untracked, PLATFORM_ID } from '@angular/core';
2
+ import { Pipe, input, Component, output, booleanAttribute, Input, Directive, signal, computed, inject, EventEmitter, Injector, forwardRef, Output, ContentChild, HostBinding, ChangeDetectionStrategy, ElementRef, LOCALE_ID, ViewContainerRef, DestroyRef, ChangeDetectorRef, effect, HostListener, ViewChild, model, viewChild, contentChild, ViewEncapsulation, TemplateRef, ContentChildren, ViewChildren, untracked, InjectionToken, PLATFORM_ID } from '@angular/core';
3
3
  import { NumberUtils, UuidUtils, StorageUtils } from '@elasticias/utils';
4
4
  import * as i1 from 'primeng/button';
5
5
  import { ButtonModule } from 'primeng/button';
@@ -3029,7 +3029,7 @@ class EfCardComponent {
3029
3029
  <ng-content select="[foot]"></ng-content>
3030
3030
  </div>
3031
3031
  }
3032
- `, isInline: true, styles: [":host{display:block}:host{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-2xl);overflow:hidden}:host(.is-overflow-visible){overflow:visible}.card-head-actions{display:inline-flex;align-items:center;gap:8px}.card-head.is-collapsible{cursor:pointer;-webkit-user-select:none;user-select:none}.card-collapse-btn{border:0;background:transparent;cursor:pointer;padding:4px;display:inline-flex;align-items:center;justify-content:center;color:var(--ink-500, #64748b);border-radius:var(--r-md, 8px)}.card-collapse-btn:hover{background:var(--paper, #f1f5f9);color:var(--ink-700, #334155)}.card-collapse-btn .pi{font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSkeletonComponent, selector: "ef-skeleton", inputs: ["variant", "rows", "height"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3032
+ `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:block}:host{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-2xl);overflow:hidden}:host(.is-overflow-visible){overflow:visible}.card-head-actions{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}@media(max-width:767px){.card-head{flex-direction:column;align-items:stretch}.card-head-actions{justify-content:flex-start}}.card-head.is-collapsible{cursor:pointer;-webkit-user-select:none;user-select:none}.card-collapse-btn{border:0;background:transparent;cursor:pointer;padding:4px;display:inline-flex;align-items:center;justify-content:center;color:var(--ink-500, #64748b);border-radius:var(--r-md, 8px)}.card-collapse-btn:hover{background:var(--paper, #f1f5f9);color:var(--ink-700, #334155)}.card-collapse-btn .pi{font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSkeletonComponent, selector: "ef-skeleton", inputs: ["variant", "rows", "height"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3033
3033
  }
3034
3034
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfCardComponent, decorators: [{
3035
3035
  type: Component,
@@ -3103,7 +3103,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
3103
3103
  <ng-content select="[foot]"></ng-content>
3104
3104
  </div>
3105
3105
  }
3106
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}:host{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-2xl);overflow:hidden}:host(.is-overflow-visible){overflow:visible}.card-head-actions{display:inline-flex;align-items:center;gap:8px}.card-head.is-collapsible{cursor:pointer;-webkit-user-select:none;user-select:none}.card-collapse-btn{border:0;background:transparent;cursor:pointer;padding:4px;display:inline-flex;align-items:center;justify-content:center;color:var(--ink-500, #64748b);border-radius:var(--r-md, 8px)}.card-collapse-btn:hover{background:var(--paper, #f1f5f9);color:var(--ink-700, #334155)}.card-collapse-btn .pi{font-size:12px}\n"] }]
3106
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:block}:host{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-2xl);overflow:hidden}:host(.is-overflow-visible){overflow:visible}.card-head-actions{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}@media(max-width:767px){.card-head{flex-direction:column;align-items:stretch}.card-head-actions{justify-content:flex-start}}.card-head.is-collapsible{cursor:pointer;-webkit-user-select:none;user-select:none}.card-collapse-btn{border:0;background:transparent;cursor:pointer;padding:4px;display:inline-flex;align-items:center;justify-content:center;color:var(--ink-500, #64748b);border-radius:var(--r-md, 8px)}.card-collapse-btn:hover{background:var(--paper, #f1f5f9);color:var(--ink-700, #334155)}.card-collapse-btn .pi{font-size:12px}\n"] }]
3107
3107
  }], propDecorators: { titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], metaKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "metaKey", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], forceHead: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceHead", required: false }] }], hasFoot: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasFoot", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], overflowVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "overflowVisible", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], skeleton: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeleton", required: false }] }], skeletonRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonRows", required: false }] }], skeletonHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonHeight", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }] } });
3108
3108
 
3109
3109
  /**
@@ -4086,7 +4086,7 @@ class EfOrderBuilderComponent {
4086
4086
  return OrderEntityHelper.calculateTotals(this.order());
4087
4087
  }
4088
4088
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4089
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderBuilderComponent, isStandalone: true, selector: "ef-order-builder", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: true, transformFunction: null }, products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, customers: { classPropertyName: "customers", publicName: "customers", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, auditEntries: { classPropertyName: "auditEntries", publicName: "auditEntries", isSignal: true, isRequired: false, transformFunction: null }, focusMode: { classPropertyName: "focusMode", publicName: "focusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { order: "orderChange", openCatalogue: "openCatalogue", orderChanged: "orderChanged", orderValidated: "orderValidated", productsAdded: "productsAdded", focusMode: "focusModeChange" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: EfProductTypeaheadComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: [":host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:760px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:JetBrains Mono,monospace;font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:760px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:JetBrains Mono,monospace;font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:JetBrains Mono,monospace;font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:JetBrains Mono,monospace}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:JetBrains Mono,monospace;font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:JetBrains Mono,monospace;font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:JetBrains Mono,monospace;font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSelectComponent, selector: "ef-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "group", "optionGroupLabel", "optionGroupChildren", "size", "fluid", "inline", "appendTo", "multiple", "filter", "filterBy", "filterPlaceholder", "filterPlaceholderKey", "autofocusFilter", "editable", "loading", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "multiDisplay", "showToggleAll", "emptyMessage", "emptyMessageKey", "emptyFilterMessage", "emptyFilterMessageKey", "styleClass", "panelStyleClass", "filterByKeys", "variant", "value"], outputs: ["selectionChangeEvent", "filterChangeEvent", "panelShowEvent", "panelHideEvent"] }, { kind: "component", type: EfDatepickerAdvancedComponent, selector: "ef-datepicker-advanced", inputs: ["triggerLabelKey", "triggerLabel", "presets", "activePreset", "start", "end", "allowCustomRange", "mode", "selectionMode", "variant", "showTime"], outputs: ["rangeChange", "opened", "closed"] }, { kind: "component", type: EfInputNumberComponent, selector: "ef-inputnumber", inputs: ["value", "invalid", "format", "showButtons", "buttonLayout", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "prefix", "suffix", "currency", "currencyDisplay", "locale", "localeMatcher", "mode", "useGrouping", "minFractionDigits", "maxFractionDigits", "min", "max", "step", "allowEmpty", "inputSize", "size", "maxlength", "minlength", "pattern", "tabindex", "title", "ariaLabelledBy", "inputStyle", "inputStyleClass", "style", "styleClass", "primeNgVariant", "autofocus", "autocomplete", "fluid", "inline", "variant", "pt", "ptOptions", "dt", "unstyled"], outputs: ["inputEvent", "valueChangeEvent", "focusEvent", "blurEvent", "keyDownEvent", "clearEvent"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition"], outputs: ["clickEvent"] }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfChangeHistoryComponent, selector: "ef-change-history", inputs: ["entries", "titleKey", "emptyKey", "limit", "previewLimit", "collapsible", "startCollapsed"] }, { kind: "component", type: EfProductTypeaheadComponent, selector: "ef-product-typeahead", inputs: ["products", "productKeyField", "placeholderKey", "disabled", "currency", "locale", "showVariants"], outputs: ["productSelect"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
4089
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderBuilderComponent, isStandalone: true, selector: "ef-order-builder", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: true, transformFunction: null }, products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, customers: { classPropertyName: "customers", publicName: "customers", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, auditEntries: { classPropertyName: "auditEntries", publicName: "auditEntries", isSignal: true, isRequired: false, transformFunction: null }, focusMode: { classPropertyName: "focusMode", publicName: "focusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { order: "orderChange", openCatalogue: "openCatalogue", orderChanged: "orderChanged", orderValidated: "orderValidated", productsAdded: "productsAdded", focusMode: "focusModeChange" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: EfProductTypeaheadComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: [":host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:JetBrains Mono,monospace;font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:767px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:JetBrains Mono,monospace;font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:JetBrains Mono,monospace;font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:JetBrains Mono,monospace}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:JetBrains Mono,monospace;font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:JetBrains Mono,monospace;font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:JetBrains Mono,monospace;font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSelectComponent, selector: "ef-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "group", "optionGroupLabel", "optionGroupChildren", "size", "fluid", "inline", "appendTo", "multiple", "filter", "filterBy", "filterPlaceholder", "filterPlaceholderKey", "autofocusFilter", "editable", "loading", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "multiDisplay", "showToggleAll", "emptyMessage", "emptyMessageKey", "emptyFilterMessage", "emptyFilterMessageKey", "styleClass", "panelStyleClass", "filterByKeys", "variant", "value"], outputs: ["selectionChangeEvent", "filterChangeEvent", "panelShowEvent", "panelHideEvent"] }, { kind: "component", type: EfDatepickerAdvancedComponent, selector: "ef-datepicker-advanced", inputs: ["triggerLabelKey", "triggerLabel", "presets", "activePreset", "start", "end", "allowCustomRange", "mode", "selectionMode", "variant", "showTime"], outputs: ["rangeChange", "opened", "closed"] }, { kind: "component", type: EfInputNumberComponent, selector: "ef-inputnumber", inputs: ["value", "invalid", "format", "showButtons", "buttonLayout", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "prefix", "suffix", "currency", "currencyDisplay", "locale", "localeMatcher", "mode", "useGrouping", "minFractionDigits", "maxFractionDigits", "min", "max", "step", "allowEmpty", "inputSize", "size", "maxlength", "minlength", "pattern", "tabindex", "title", "ariaLabelledBy", "inputStyle", "inputStyleClass", "style", "styleClass", "primeNgVariant", "autofocus", "autocomplete", "fluid", "inline", "variant", "pt", "ptOptions", "dt", "unstyled"], outputs: ["inputEvent", "valueChangeEvent", "focusEvent", "blurEvent", "keyDownEvent", "clearEvent"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition"], outputs: ["clickEvent"] }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfChangeHistoryComponent, selector: "ef-change-history", inputs: ["entries", "titleKey", "emptyKey", "limit", "previewLimit", "collapsible", "startCollapsed"] }, { kind: "component", type: EfProductTypeaheadComponent, selector: "ef-product-typeahead", inputs: ["products", "productKeyField", "placeholderKey", "disabled", "currency", "locale", "showVariants"], outputs: ["productSelect"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
4090
4090
  }
4091
4091
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, decorators: [{
4092
4092
  type: Component,
@@ -4103,7 +4103,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
4103
4103
  EfCardComponent,
4104
4104
  EfChangeHistoryComponent,
4105
4105
  EfProductTypeaheadComponent,
4106
- ], template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: [":host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:760px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:JetBrains Mono,monospace;font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:760px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:JetBrains Mono,monospace;font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:JetBrains Mono,monospace;font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:JetBrains Mono,monospace}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:JetBrains Mono,monospace;font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:JetBrains Mono,monospace;font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:JetBrains Mono,monospace;font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}\n"] }]
4106
+ ], template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: [":host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:JetBrains Mono,monospace;font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:767px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:JetBrains Mono,monospace;font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:JetBrains Mono,monospace;font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:JetBrains Mono,monospace}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:JetBrains Mono,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:JetBrains Mono,monospace;font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:JetBrains Mono,monospace;font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:Bricolage Grotesque,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:JetBrains Mono,monospace;font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}\n"] }]
4107
4107
  }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], order: [{ type: i0.Input, args: [{ isSignal: true, alias: "order", required: true }] }, { type: i0.Output, args: ["orderChange"] }], products: [{ type: i0.Input, args: [{ isSignal: true, alias: "products", required: true }] }], customers: [{ type: i0.Input, args: [{ isSignal: true, alias: "customers", required: true }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], countGroupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "countGroupLabels", required: false }] }], productKeyField: [{ type: i0.Input, args: [{ isSignal: true, alias: "productKeyField", required: false }] }], backendErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "backendErrors", required: false }] }], auditEntries: [{ type: i0.Input, args: [{ isSignal: true, alias: "auditEntries", required: false }] }], openCatalogue: [{ type: i0.Output, args: ["openCatalogue"] }], orderChanged: [{ type: i0.Output, args: ["orderChanged"] }], orderValidated: [{ type: i0.Output, args: ["orderValidated"] }], productsAdded: [{ type: i0.Output, args: ["productsAdded"] }], focusMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusMode", required: false }] }, { type: i0.Output, args: ["focusModeChange"] }], typeahead: [{ type: i0.ViewChild, args: [i0.forwardRef(() => EfProductTypeaheadComponent), { isSignal: true }] }] } });
4108
4108
 
4109
4109
  /**
@@ -5481,11 +5481,11 @@ class EfAppTopComponent {
5481
5481
  navItem = this.active.activeNavItem;
5482
5482
  hasBreadcrumb = computed(() => this.module() != null, ...(ngDevMode ? [{ debugName: "hasBreadcrumb" }] : /* istanbul ignore next */ []));
5483
5483
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppTopComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5484
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppTopComponent, isStandalone: true, selector: "ef-app-top", inputs: { themeToggle: { classPropertyName: "themeToggle", publicName: "themeToggle", isSignal: true, isRequired: false, transformFunction: null }, logout: { classPropertyName: "logout", publicName: "logout", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"ef-app-top__slot ef-app-top__slot--breadcrumb\">\n <ng-content select=\"[breadcrumb]\"></ng-content>\n @if (hasBreadcrumb()) {\n <nav class=\"ef-app-top__crumb\" aria-label=\"breadcrumb\">\n <span class=\"ef-app-top__crumb-dot\" [attr.data-module]=\"module()!.id\" aria-hidden=\"true\"></span>\n <span class=\"ef-app-top__crumb-root\">{{ module()!.labelKey | translate }}</span>\n @if (navItem(); as item) {\n <span class=\"ef-app-top__crumb-sep\" aria-hidden=\"true\">/</span>\n <span class=\"ef-app-top__crumb-leaf\">{{ item.labelKey | translate }}</span>\n }\n </nav>\n }\n</div>\n\n<div class=\"ef-app-top__slot ef-app-top__slot--actions\">\n <ng-content select=\"[actions]\"></ng-content>\n @if (themeToggle()) {\n <ef-theme-toggle />\n }\n @if (logout() && session) {\n <button\n type=\"button\"\n class=\"ef-app-top__logout\"\n [attr.aria-label]=\"'common_logout' | translate\"\n [attr.title]=\"'common_logout' | translate\"\n (click)=\"signOut()\"\n >\n <i class=\"pi pi-sign-out\" aria-hidden=\"true\"></i>\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr);align-items:center;gap:16px;flex:0 0 auto;height:48px;padding:0 20px;background:var(--paper-alt);border-bottom:1px solid var(--rule);color:var(--text)}.ef-app-top__slot{display:flex;align-items:center;gap:8px;min-width:0}.ef-app-top__slot--actions{justify-content:flex-end;gap:12px}.ef-app-top__crumb{display:inline-flex;align-items:center;gap:8px;font-family:Manrope,system-ui,sans-serif;font-size:13px;letter-spacing:.01em;line-height:1;color:var(--text-mute)}.ef-app-top__crumb-dot{width:8px;height:8px;border-radius:999px;background:var(--m-admin);flex-shrink:0}.ef-app-top__crumb-dot[data-module=sales]{background:var(--m-sales)}.ef-app-top__crumb-dot[data-module=purchase]{background:var(--m-purchase)}.ef-app-top__crumb-dot[data-module=stock]{background:var(--m-stock)}.ef-app-top__crumb-dot[data-module=pos]{background:var(--m-pos)}.ef-app-top__crumb-dot[data-module=marketing]{background:var(--m-marketing)}.ef-app-top__crumb-dot[data-module=store]{background:var(--m-store)}.ef-app-top__crumb-dot[data-module=finance]{background:var(--m-finance)}.ef-app-top__crumb-dot[data-module=admin]{background:var(--m-admin)}.ef-app-top__crumb-root{font-weight:600;color:var(--text)}.ef-app-top__crumb-sep{color:var(--text-soft)}.ef-app-top__crumb-leaf{color:var(--text-mute)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfThemeToggleComponent, selector: "ef-theme-toggle", inputs: ["darkIcon", "lightIcon", "ariaLabelKey"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5484
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppTopComponent, isStandalone: true, selector: "ef-app-top", inputs: { themeToggle: { classPropertyName: "themeToggle", publicName: "themeToggle", isSignal: true, isRequired: false, transformFunction: null }, logout: { classPropertyName: "logout", publicName: "logout", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"ef-app-top__slot ef-app-top__slot--breadcrumb\">\n <ng-content select=\"[breadcrumb]\"></ng-content>\n @if (hasBreadcrumb()) {\n <nav class=\"ef-app-top__crumb\" aria-label=\"breadcrumb\">\n <span class=\"ef-app-top__crumb-dot\" [attr.data-module]=\"module()!.id\" aria-hidden=\"true\"></span>\n <span class=\"ef-app-top__crumb-root\">{{ module()!.labelKey | translate }}</span>\n @if (navItem(); as item) {\n <span class=\"ef-app-top__crumb-sep\" aria-hidden=\"true\">/</span>\n <span class=\"ef-app-top__crumb-leaf\">{{ item.labelKey | translate }}</span>\n }\n </nav>\n }\n</div>\n\n<div class=\"ef-app-top__slot ef-app-top__slot--actions\">\n <ng-content select=\"[actions]\"></ng-content>\n @if (themeToggle()) {\n <ef-theme-toggle />\n }\n @if (logout() && session) {\n <button\n type=\"button\"\n class=\"ef-app-top__logout\"\n [attr.aria-label]=\"'common_logout' | translate\"\n [attr.title]=\"'common_logout' | translate\"\n (click)=\"signOut()\"\n >\n <i class=\"pi pi-sign-out\" aria-hidden=\"true\"></i>\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr);align-items:center;gap:16px;flex:0 0 auto;height:48px;padding:0 20px;background:var(--paper-alt);border-bottom:1px solid var(--rule);color:var(--text)}.ef-app-top__slot{display:flex;align-items:center;gap:8px;min-width:0}.ef-app-top__slot--actions{justify-content:flex-end;gap:12px}.ef-app-top__crumb{display:inline-flex;align-items:center;gap:8px;font-family:Manrope,system-ui,sans-serif;font-size:13px;letter-spacing:.01em;line-height:1;color:var(--text-mute)}.ef-app-top__crumb-dot{width:8px;height:8px;border-radius:999px;background:var(--m-admin);flex-shrink:0}.ef-app-top__crumb-dot[data-module=sales]{background:var(--m-sales)}.ef-app-top__crumb-dot[data-module=purchase]{background:var(--m-purchase)}.ef-app-top__crumb-dot[data-module=stock]{background:var(--m-stock)}.ef-app-top__crumb-dot[data-module=pos]{background:var(--m-pos)}.ef-app-top__crumb-dot[data-module=marketing]{background:var(--m-marketing)}.ef-app-top__crumb-dot[data-module=store]{background:var(--m-store)}.ef-app-top__crumb-dot[data-module=finance]{background:var(--m-finance)}.ef-app-top__crumb-dot[data-module=admin]{background:var(--m-admin)}.ef-app-top__crumb-root{font-weight:600;color:var(--text)}.ef-app-top__crumb-sep{color:var(--text-soft)}.ef-app-top__crumb-leaf{color:var(--text-mute)}@media(max-width:767px){:host{grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:0 12px}.ef-app-top__crumb{min-width:0;overflow:hidden}.ef-app-top__crumb-leaf,.ef-app-top__crumb-root{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfThemeToggleComponent, selector: "ef-theme-toggle", inputs: ["darkIcon", "lightIcon", "ariaLabelKey"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5485
5485
  }
5486
5486
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppTopComponent, decorators: [{
5487
5487
  type: Component,
5488
- args: [{ selector: 'ef-app-top', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule, EfThemeToggleComponent], template: "<div class=\"ef-app-top__slot ef-app-top__slot--breadcrumb\">\n <ng-content select=\"[breadcrumb]\"></ng-content>\n @if (hasBreadcrumb()) {\n <nav class=\"ef-app-top__crumb\" aria-label=\"breadcrumb\">\n <span class=\"ef-app-top__crumb-dot\" [attr.data-module]=\"module()!.id\" aria-hidden=\"true\"></span>\n <span class=\"ef-app-top__crumb-root\">{{ module()!.labelKey | translate }}</span>\n @if (navItem(); as item) {\n <span class=\"ef-app-top__crumb-sep\" aria-hidden=\"true\">/</span>\n <span class=\"ef-app-top__crumb-leaf\">{{ item.labelKey | translate }}</span>\n }\n </nav>\n }\n</div>\n\n<div class=\"ef-app-top__slot ef-app-top__slot--actions\">\n <ng-content select=\"[actions]\"></ng-content>\n @if (themeToggle()) {\n <ef-theme-toggle />\n }\n @if (logout() && session) {\n <button\n type=\"button\"\n class=\"ef-app-top__logout\"\n [attr.aria-label]=\"'common_logout' | translate\"\n [attr.title]=\"'common_logout' | translate\"\n (click)=\"signOut()\"\n >\n <i class=\"pi pi-sign-out\" aria-hidden=\"true\"></i>\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr);align-items:center;gap:16px;flex:0 0 auto;height:48px;padding:0 20px;background:var(--paper-alt);border-bottom:1px solid var(--rule);color:var(--text)}.ef-app-top__slot{display:flex;align-items:center;gap:8px;min-width:0}.ef-app-top__slot--actions{justify-content:flex-end;gap:12px}.ef-app-top__crumb{display:inline-flex;align-items:center;gap:8px;font-family:Manrope,system-ui,sans-serif;font-size:13px;letter-spacing:.01em;line-height:1;color:var(--text-mute)}.ef-app-top__crumb-dot{width:8px;height:8px;border-radius:999px;background:var(--m-admin);flex-shrink:0}.ef-app-top__crumb-dot[data-module=sales]{background:var(--m-sales)}.ef-app-top__crumb-dot[data-module=purchase]{background:var(--m-purchase)}.ef-app-top__crumb-dot[data-module=stock]{background:var(--m-stock)}.ef-app-top__crumb-dot[data-module=pos]{background:var(--m-pos)}.ef-app-top__crumb-dot[data-module=marketing]{background:var(--m-marketing)}.ef-app-top__crumb-dot[data-module=store]{background:var(--m-store)}.ef-app-top__crumb-dot[data-module=finance]{background:var(--m-finance)}.ef-app-top__crumb-dot[data-module=admin]{background:var(--m-admin)}.ef-app-top__crumb-root{font-weight:600;color:var(--text)}.ef-app-top__crumb-sep{color:var(--text-soft)}.ef-app-top__crumb-leaf{color:var(--text-mute)}\n"] }]
5488
+ args: [{ selector: 'ef-app-top', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule, EfThemeToggleComponent], template: "<div class=\"ef-app-top__slot ef-app-top__slot--breadcrumb\">\n <ng-content select=\"[breadcrumb]\"></ng-content>\n @if (hasBreadcrumb()) {\n <nav class=\"ef-app-top__crumb\" aria-label=\"breadcrumb\">\n <span class=\"ef-app-top__crumb-dot\" [attr.data-module]=\"module()!.id\" aria-hidden=\"true\"></span>\n <span class=\"ef-app-top__crumb-root\">{{ module()!.labelKey | translate }}</span>\n @if (navItem(); as item) {\n <span class=\"ef-app-top__crumb-sep\" aria-hidden=\"true\">/</span>\n <span class=\"ef-app-top__crumb-leaf\">{{ item.labelKey | translate }}</span>\n }\n </nav>\n }\n</div>\n\n<div class=\"ef-app-top__slot ef-app-top__slot--actions\">\n <ng-content select=\"[actions]\"></ng-content>\n @if (themeToggle()) {\n <ef-theme-toggle />\n }\n @if (logout() && session) {\n <button\n type=\"button\"\n class=\"ef-app-top__logout\"\n [attr.aria-label]=\"'common_logout' | translate\"\n [attr.title]=\"'common_logout' | translate\"\n (click)=\"signOut()\"\n >\n <i class=\"pi pi-sign-out\" aria-hidden=\"true\"></i>\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr);align-items:center;gap:16px;flex:0 0 auto;height:48px;padding:0 20px;background:var(--paper-alt);border-bottom:1px solid var(--rule);color:var(--text)}.ef-app-top__slot{display:flex;align-items:center;gap:8px;min-width:0}.ef-app-top__slot--actions{justify-content:flex-end;gap:12px}.ef-app-top__crumb{display:inline-flex;align-items:center;gap:8px;font-family:Manrope,system-ui,sans-serif;font-size:13px;letter-spacing:.01em;line-height:1;color:var(--text-mute)}.ef-app-top__crumb-dot{width:8px;height:8px;border-radius:999px;background:var(--m-admin);flex-shrink:0}.ef-app-top__crumb-dot[data-module=sales]{background:var(--m-sales)}.ef-app-top__crumb-dot[data-module=purchase]{background:var(--m-purchase)}.ef-app-top__crumb-dot[data-module=stock]{background:var(--m-stock)}.ef-app-top__crumb-dot[data-module=pos]{background:var(--m-pos)}.ef-app-top__crumb-dot[data-module=marketing]{background:var(--m-marketing)}.ef-app-top__crumb-dot[data-module=store]{background:var(--m-store)}.ef-app-top__crumb-dot[data-module=finance]{background:var(--m-finance)}.ef-app-top__crumb-dot[data-module=admin]{background:var(--m-admin)}.ef-app-top__crumb-root{font-weight:600;color:var(--text)}.ef-app-top__crumb-sep{color:var(--text-soft)}.ef-app-top__crumb-leaf{color:var(--text-mute)}@media(max-width:767px){:host{grid-template-columns:minmax(0,1fr) auto;gap:8px;padding:0 12px}.ef-app-top__crumb{min-width:0;overflow:hidden}.ef-app-top__crumb-leaf,.ef-app-top__crumb-root{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}\n"] }]
5489
5489
  }], propDecorators: { themeToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeToggle", required: false }] }], logout: [{ type: i0.Input, args: [{ isSignal: true, alias: "logout", required: false }] }] } });
5490
5490
 
5491
5491
  /**
@@ -5511,13 +5511,13 @@ class EfAppMainComponent {
5511
5511
  /** `data-module` value bound to the host. Null when no module is active. */
5512
5512
  moduleId = computed(() => this.activeModule.activeModuleId(), ...(ngDevMode ? [{ debugName: "moduleId" }] : /* istanbul ignore next */ []));
5513
5513
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppMainComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5514
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppMainComponent, isStandalone: true, selector: "ef-app-main", inputs: { stripe: ["stripe", "stripe", booleanAttribute] }, host: { properties: { "attr.data-module": "moduleId()" } }, ngImport: i0, template: "@if (stripe) {\n <div class=\"ef-app-main__stripe\" aria-hidden=\"true\"></div>\n}\n<div class=\"ef-app-main__content\">\n <ng-content></ng-content>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden;background:var(--paper);color:var(--text)}.ef-app-main__stripe{flex:0 0 auto;height:3px;background:var(--module)}.ef-app-main__content{flex:1 1 auto;overflow:auto;padding:var(--s-5) var(--s-6) var(--s-7);display:grid;align-content:start}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5514
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppMainComponent, isStandalone: true, selector: "ef-app-main", inputs: { stripe: ["stripe", "stripe", booleanAttribute] }, host: { properties: { "attr.data-module": "moduleId()" } }, ngImport: i0, template: "@if (stripe) {\n <div class=\"ef-app-main__stripe\" aria-hidden=\"true\"></div>\n}\n<div class=\"ef-app-main__content\">\n <ng-content></ng-content>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden;background:var(--paper);color:var(--text)}.ef-app-main__stripe{flex:0 0 auto;height:3px;background:var(--module)}.ef-app-main__content{flex:1 1 auto;overflow:auto;padding:var(--s-5) var(--s-6) var(--s-7);display:grid;grid-template-columns:minmax(0,1fr);align-content:start}@media(max-width:767px){.ef-app-main__content{padding:var(--s-4) var(--s-4) var(--s-7)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5515
5515
  }
5516
5516
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppMainComponent, decorators: [{
5517
5517
  type: Component,
5518
5518
  args: [{ selector: 'ef-app-main', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
5519
5519
  '[attr.data-module]': 'moduleId()',
5520
- }, template: "@if (stripe) {\n <div class=\"ef-app-main__stripe\" aria-hidden=\"true\"></div>\n}\n<div class=\"ef-app-main__content\">\n <ng-content></ng-content>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden;background:var(--paper);color:var(--text)}.ef-app-main__stripe{flex:0 0 auto;height:3px;background:var(--module)}.ef-app-main__content{flex:1 1 auto;overflow:auto;padding:var(--s-5) var(--s-6) var(--s-7);display:grid;align-content:start}\n"] }]
5520
+ }, template: "@if (stripe) {\n <div class=\"ef-app-main__stripe\" aria-hidden=\"true\"></div>\n}\n<div class=\"ef-app-main__content\">\n <ng-content></ng-content>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden;background:var(--paper);color:var(--text)}.ef-app-main__stripe{flex:0 0 auto;height:3px;background:var(--module)}.ef-app-main__content{flex:1 1 auto;overflow:auto;padding:var(--s-5) var(--s-6) var(--s-7);display:grid;grid-template-columns:minmax(0,1fr);align-content:start}@media(max-width:767px){.ef-app-main__content{padding:var(--s-4) var(--s-4) var(--s-7)}}\n"] }]
5521
5521
  }], propDecorators: { stripe: [{
5522
5522
  type: Input,
5523
5523
  args: [{ transform: booleanAttribute }]
@@ -5575,50 +5575,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5575
5575
  args: ['document:keydown.escape']
5576
5576
  }] } });
5577
5577
 
5578
- /**
5579
- * Mobile-only shell. Picks up when `ef-app-shell` detects a mobile
5580
- * viewport. Composition:
5581
- *
5582
- * - Top bar (`ef-app-top`) with a menu trigger that opens the module
5583
- * switcher in a bottom sheet.
5584
- * - Scrollable content (`ef-app-main`) — projects `<router-outlet />`.
5585
- * - 5-tab bottom bar — first 5 visible modules from
5586
- * `EfPermissionService.visibleModules()`. Apps that want a usage-
5587
- * ranked tab order can replace the registry via `EF_MODULES_TOKEN`.
5588
- * - FAB slot (`[fab]`) — apps project a `<ef-fab />` here.
5589
- *
5590
- * The module switcher (bottom sheet) lists all visible modules so
5591
- * users can reach modules outside the top-5 tabs.
5592
- */
5593
- class EfAppShellMobileComponent {
5594
- perms = inject(EfPermissionService);
5595
- active = inject(EfActiveModuleService);
5596
- router = inject(Router);
5597
- activeId = this.active.activeModuleId;
5598
- switcherOpen = signal(false, ...(ngDevMode ? [{ debugName: "switcherOpen" }] : /* istanbul ignore next */ []));
5599
- tabs = computed(() => this.perms.visibleModules().slice(0, 5), ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
5600
- allVisible = this.perms.visibleModules;
5601
- openSwitcher() { this.switcherOpen.set(true); }
5602
- onSwitcherChange(open) { this.switcherOpen.set(open); }
5603
- onSelect(module) {
5604
- this.router.navigateByUrl(module.defaultRoute);
5605
- this.switcherOpen.set(false);
5606
- }
5607
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppShellMobileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5608
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppShellMobileComponent, isStandalone: true, selector: "ef-app-shell-mobile", ngImport: i0, template: "<ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n <button\n actions\n type=\"button\"\n class=\"ef-app-shell-mobile__menu\"\n (click)=\"openSwitcher()\"\n aria-label=\"Modules\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </button>\n <ng-content select=\"[actions]\" actions></ng-content>\n</ef-app-top>\n\n<ef-app-main [stripe]=\"true\">\n <ng-content></ng-content>\n</ef-app-main>\n\n<nav class=\"ef-app-shell-mobile__tabs\" aria-label=\"Modules\">\n @for (module of tabs(); track module.id) {\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__tab\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n [attr.aria-current]=\"activeId() === module.id ? 'page' : null\"\n (click)=\"onSelect(module)\"\n >\n <i class=\"ef-app-shell-mobile__tab-icon\" [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span class=\"ef-app-shell-mobile__tab-label\">{{ module.labelKey | translate }}</span>\n </button>\n }\n</nav>\n\n<ng-content select=\"[fab]\"></ng-content>\n\n<ef-bottom-sheet [open]=\"switcherOpen()\" (openChange)=\"onSwitcherChange($event)\">\n <h2 class=\"ef-app-shell-mobile__sheet-title\">{{ 'modules.all' | translate }}</h2>\n <ul class=\"ef-app-shell-mobile__sheet-list\">\n @for (module of allVisible(); track module.id) {\n <li>\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__sheet-item\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n (click)=\"onSelect(module)\"\n >\n <i [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span>{{ module.labelKey | translate }}</span>\n </button>\n </li>\n }\n </ul>\n</ef-bottom-sheet>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell-mobile__menu{display:inline-flex;align-items:center;justify-content:center;width:var(--hit-touch);height:var(--hit-touch);background:transparent;border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-size:18px}.ef-app-shell-mobile__menu:hover{background:var(--paper-alt)}.ef-app-shell-mobile__tabs{flex:0 0 auto;display:flex;align-items:stretch;height:64px;padding-bottom:env(safe-area-inset-bottom);background:var(--paper);border-top:1px solid var(--rule)}.ef-app-shell-mobile__tab{position:relative;flex:1 1 0;min-width:0;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:transparent;border:0;color:var(--text-mute);cursor:pointer;padding:6px 4px;transition:color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__tab.is-active{color:var(--text)}.ef-app-shell-mobile__tab.is-active:after{content:\"\";position:absolute;top:0;left:50%;transform:translate(-50%);width:28px;height:3px;background:var(--module);border-radius:0 0 var(--r-sm) var(--r-sm)}.ef-app-shell-mobile__tab-icon{font-size:18px;line-height:1}.ef-app-shell-mobile__tab-label{font-family:Manrope,system-ui,sans-serif;font-size:10.5px;font-weight:600;letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.ef-app-shell-mobile__sheet-title{font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 600,\"wdth\" 95;font-size:22px;letter-spacing:-.025em;margin:0 0 12px;color:var(--text)}.ef-app-shell-mobile__sheet-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.ef-app-shell-mobile__sheet-item{display:flex;align-items:center;gap:12px;width:100%;height:var(--hit-touch);padding:0 16px;background:var(--paper-alt);border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-family:Manrope,system-ui,sans-serif;font-size:14px;font-weight:500;text-align:left;transition:background-color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__sheet-item:hover{background:var(--rule)}.ef-app-shell-mobile__sheet-item.is-active{border-left:3px solid var(--module);padding-left:13px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "component", type: EfAppTopComponent, selector: "ef-app-top", inputs: ["themeToggle", "logout"] }, { kind: "component", type: EfAppMainComponent, selector: "ef-app-main", inputs: ["stripe"] }, { kind: "component", type: EfBottomSheetComponent, selector: "ef-bottom-sheet", inputs: ["open", "dismissOnBackdrop"], outputs: ["openChange"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5609
- }
5610
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppShellMobileComponent, decorators: [{
5611
- type: Component,
5612
- args: [{ selector: 'ef-app-shell-mobile', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
5613
- CommonModule,
5614
- TranslateModule,
5615
- TooltipModule,
5616
- EfAppTopComponent,
5617
- EfAppMainComponent,
5618
- EfBottomSheetComponent,
5619
- ], template: "<ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n <button\n actions\n type=\"button\"\n class=\"ef-app-shell-mobile__menu\"\n (click)=\"openSwitcher()\"\n aria-label=\"Modules\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </button>\n <ng-content select=\"[actions]\" actions></ng-content>\n</ef-app-top>\n\n<ef-app-main [stripe]=\"true\">\n <ng-content></ng-content>\n</ef-app-main>\n\n<nav class=\"ef-app-shell-mobile__tabs\" aria-label=\"Modules\">\n @for (module of tabs(); track module.id) {\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__tab\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n [attr.aria-current]=\"activeId() === module.id ? 'page' : null\"\n (click)=\"onSelect(module)\"\n >\n <i class=\"ef-app-shell-mobile__tab-icon\" [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span class=\"ef-app-shell-mobile__tab-label\">{{ module.labelKey | translate }}</span>\n </button>\n }\n</nav>\n\n<ng-content select=\"[fab]\"></ng-content>\n\n<ef-bottom-sheet [open]=\"switcherOpen()\" (openChange)=\"onSwitcherChange($event)\">\n <h2 class=\"ef-app-shell-mobile__sheet-title\">{{ 'modules.all' | translate }}</h2>\n <ul class=\"ef-app-shell-mobile__sheet-list\">\n @for (module of allVisible(); track module.id) {\n <li>\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__sheet-item\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n (click)=\"onSelect(module)\"\n >\n <i [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span>{{ module.labelKey | translate }}</span>\n </button>\n </li>\n }\n </ul>\n</ef-bottom-sheet>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell-mobile__menu{display:inline-flex;align-items:center;justify-content:center;width:var(--hit-touch);height:var(--hit-touch);background:transparent;border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-size:18px}.ef-app-shell-mobile__menu:hover{background:var(--paper-alt)}.ef-app-shell-mobile__tabs{flex:0 0 auto;display:flex;align-items:stretch;height:64px;padding-bottom:env(safe-area-inset-bottom);background:var(--paper);border-top:1px solid var(--rule)}.ef-app-shell-mobile__tab{position:relative;flex:1 1 0;min-width:0;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:transparent;border:0;color:var(--text-mute);cursor:pointer;padding:6px 4px;transition:color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__tab.is-active{color:var(--text)}.ef-app-shell-mobile__tab.is-active:after{content:\"\";position:absolute;top:0;left:50%;transform:translate(-50%);width:28px;height:3px;background:var(--module);border-radius:0 0 var(--r-sm) var(--r-sm)}.ef-app-shell-mobile__tab-icon{font-size:18px;line-height:1}.ef-app-shell-mobile__tab-label{font-family:Manrope,system-ui,sans-serif;font-size:10.5px;font-weight:600;letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.ef-app-shell-mobile__sheet-title{font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 600,\"wdth\" 95;font-size:22px;letter-spacing:-.025em;margin:0 0 12px;color:var(--text)}.ef-app-shell-mobile__sheet-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.ef-app-shell-mobile__sheet-item{display:flex;align-items:center;gap:12px;width:100%;height:var(--hit-touch);padding:0 16px;background:var(--paper-alt);border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-family:Manrope,system-ui,sans-serif;font-size:14px;font-weight:500;text-align:left;transition:background-color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__sheet-item:hover{background:var(--rule)}.ef-app-shell-mobile__sheet-item.is-active{border-left:3px solid var(--module);padding-left:13px}\n"] }]
5620
- }] });
5621
-
5622
5578
  /**
5623
5579
  * Top-level Comptoir shell.
5624
5580
  *
@@ -5649,13 +5605,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5649
5605
  */
5650
5606
  class EfAppShellComponent {
5651
5607
  vp = inject(EfViewportService);
5608
+ perms = inject(EfPermissionService);
5609
+ active = inject(EfActiveModuleService);
5610
+ router = inject(Router);
5652
5611
  /** Show the 3px module-color stripe at the top of the content area. */
5653
5612
  stripe = true;
5654
5613
  viewport = this.vp.current;
5655
5614
  isMobile = this.vp.isMobile;
5656
5615
  showSide = computed(() => !this.vp.isMobile(), ...(ngDevMode ? [{ debugName: "showSide" }] : /* istanbul ignore next */ []));
5616
+ /* ── Mobile chrome ───────────────────────────────────────────
5617
+ Tabs and the module sheet live here rather than in a child
5618
+ component: content has to be declared once, in one template,
5619
+ and a child shell would mean declaring the slots twice. */
5620
+ activeId = this.active.activeModuleId;
5621
+ switcherOpen = signal(false, ...(ngDevMode ? [{ debugName: "switcherOpen" }] : /* istanbul ignore next */ []));
5622
+ /** Five tabs is what fits a phone; the rest live in the sheet. */
5623
+ tabs = computed(() => this.perms.visibleModules().slice(0, 5), ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
5624
+ allVisible = this.perms.visibleModules;
5625
+ /**
5626
+ * The screens inside the active module, for the mobile sheet.
5627
+ *
5628
+ * On a phone the side nav is not rendered, and the tab bar switches
5629
+ * modules — so without this there was no way to reach any screen except
5630
+ * the module's default one. Same filtering as `ef-module-side`, because
5631
+ * it answers the same question.
5632
+ */
5633
+ moduleSections = computed(() => {
5634
+ const m = this.active.activeModule();
5635
+ if (!m)
5636
+ return [];
5637
+ return m.navSections
5638
+ .map(section => ({
5639
+ ...section,
5640
+ items: section.items.filter(item => this.perms.can(m.id, item.requiredAction ?? 'read')),
5641
+ }))
5642
+ .filter(section => section.items.length > 0);
5643
+ }, ...(ngDevMode ? [{ debugName: "moduleSections" }] : /* istanbul ignore next */ []));
5644
+ onNavigate(item) {
5645
+ this.router.navigateByUrl(item.route);
5646
+ this.switcherOpen.set(false);
5647
+ }
5648
+ openSwitcher() {
5649
+ this.switcherOpen.set(true);
5650
+ }
5651
+ onSwitcherChange(open) {
5652
+ this.switcherOpen.set(open);
5653
+ }
5654
+ onSelect(module) {
5655
+ this.router.navigateByUrl(module.defaultRoute);
5656
+ this.switcherOpen.set(false);
5657
+ }
5657
5658
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5658
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppShellComponent, isStandalone: true, selector: "ef-app-shell", inputs: { stripe: "stripe" }, host: { properties: { "attr.data-viewport": "viewport()" } }, ngImport: i0, template: "@if (isMobile()) {\n <ef-app-shell-mobile>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n <ng-content select=\"[actions]\" actions></ng-content>\n <ng-content select=\"[fab]\" fab></ng-content>\n <ng-content></ng-content>\n </ef-app-shell-mobile>\n} @else {\n <ef-module-rail>\n <ng-content select=\"[logo]\" logo></ng-content>\n </ef-module-rail>\n @if (showSide()) {\n <ef-module-side>\n <ng-content select=\"[side-header-meta]\" side-header-meta></ng-content>\n <ng-content select=\"[side-footer]\" side-footer></ng-content>\n </ef-module-side>\n }\n <div class=\"ef-app-shell__column\">\n <ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[actions]\" actions></ng-content>\n </ef-app-top>\n <ef-app-main [stripe]=\"stripe\">\n <ng-content></ng-content>\n </ef-app-main>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:row;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell__column{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: EfModuleRailComponent, selector: "ef-module-rail", inputs: ["badges"] }, { kind: "component", type: EfModuleSideComponent, selector: "ef-module-side" }, { kind: "component", type: EfAppTopComponent, selector: "ef-app-top", inputs: ["themeToggle", "logout"] }, { kind: "component", type: EfAppMainComponent, selector: "ef-app-main", inputs: ["stripe"] }, { kind: "component", type: EfAppShellMobileComponent, selector: "ef-app-shell-mobile" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5659
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppShellComponent, isStandalone: true, selector: "ef-app-shell", inputs: { stripe: "stripe" }, host: { properties: { "attr.data-viewport": "viewport()" } }, ngImport: i0, template: "<!-- One layout, one declaration per slot.\n Angular binds projected content to the LAST matching <ng-content> in a\n template, so declaring the same selector once per viewport branch meant the\n branch that was actually rendered on a phone received nothing \u2014 every\n screen came up blank inside working chrome. The layouts differ by CSS and\n by which chrome is rendered, never by where the content is declared. -->\n\n<ef-module-rail class=\"ef-app-shell__rail\">\n <ng-content select=\"[logo]\" logo></ng-content>\n</ef-module-rail>\n\n@if (showSide()) {\n <ef-module-side class=\"ef-app-shell__side\">\n <ng-content select=\"[side-header-meta]\" side-header-meta></ng-content>\n <ng-content select=\"[side-footer]\" side-footer></ng-content>\n </ef-module-side>\n}\n\n<div class=\"ef-app-shell__column\">\n <ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n @if (isMobile()) {\n <button\n actions\n type=\"button\"\n class=\"ef-app-shell__menu\"\n (click)=\"openSwitcher()\"\n [attr.aria-label]=\"'common_modules' | translate\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </button>\n }\n <ng-content select=\"[actions]\" actions></ng-content>\n </ef-app-top>\n\n <ef-app-main [stripe]=\"stripe\">\n <ng-content></ng-content>\n </ef-app-main>\n\n @if (isMobile()) {\n <nav class=\"ef-app-shell__tabs\" [attr.aria-label]=\"'common_modules' | translate\">\n @for (module of tabs(); track module.id) {\n <button\n type=\"button\"\n class=\"ef-app-shell__tab\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n [attr.aria-current]=\"activeId() === module.id ? 'page' : null\"\n (click)=\"onSelect(module)\"\n >\n <i class=\"ef-app-shell__tab-icon\" [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span class=\"ef-app-shell__tab-label\">{{ module.labelKey | translate }}</span>\n </button>\n }\n </nav>\n }\n</div>\n\n<ng-content select=\"[fab]\"></ng-content>\n\n@if (isMobile()) {\n <ef-bottom-sheet [open]=\"switcherOpen()\" (openChange)=\"onSwitcherChange($event)\">\n <!-- The screens first: the tab bar already switches modules, so this is\n the only way to reach anything but a module's default screen. -->\n @for (section of moduleSections(); track section.id) {\n @if (section.labelKey) {\n <h2 class=\"ef-app-shell__sheet-title\">{{ section.labelKey | translate }}</h2>\n }\n <ul class=\"ef-app-shell__sheet-list\">\n @for (item of section.items; track item.id) {\n <li>\n <a\n class=\"ef-app-shell__sheet-item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"is-active\"\n (click)=\"onNavigate(item)\"\n >\n @if (item.icon) {\n <i [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span>{{ item.labelKey | translate }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <span class=\"ef-app-shell__sheet-badge\">{{ item.badge }}</span>\n }\n </a>\n </li>\n }\n </ul>\n }\n\n <h2 class=\"ef-app-shell__sheet-title\">{{ 'common_modules' | translate }}</h2>\n <ul class=\"ef-app-shell__sheet-list\">\n @for (module of allVisible(); track module.id) {\n <li>\n <button\n type=\"button\"\n class=\"ef-app-shell__sheet-item\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n (click)=\"onSelect(module)\"\n >\n <i [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span>{{ module.labelKey | translate }}</span>\n </button>\n </li>\n }\n </ul>\n </ef-bottom-sheet>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:row;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell__column{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.ef-app-shell__menu{display:inline-flex;align-items:center;justify-content:center;width:var(--hit-touch);height:var(--hit-touch);background:transparent;border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-size:18px}.ef-app-shell__menu:hover{background:var(--paper-alt)}.ef-app-shell__tabs{flex:0 0 auto;display:flex;align-items:stretch;height:64px;padding-bottom:env(safe-area-inset-bottom);background:var(--paper);border-top:1px solid var(--rule)}.ef-app-shell__tab{position:relative;flex:1 1 0;min-width:0;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:transparent;border:0;color:var(--text-mute);cursor:pointer;padding:6px 4px;transition:color var(--t-fast) var(--ease-out)}.ef-app-shell__tab.is-active{color:var(--text)}.ef-app-shell__tab.is-active:after{content:\"\";position:absolute;top:0;left:50%;transform:translate(-50%);width:28px;height:3px;background:var(--module);border-radius:0 0 var(--r-sm) var(--r-sm)}.ef-app-shell__tab-icon{font-size:18px;line-height:1}.ef-app-shell__tab-label{font-family:Manrope,system-ui,sans-serif;font-size:10.5px;font-weight:600;letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.ef-app-shell__sheet-title{font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 600,\"wdth\" 95;font-size:22px;letter-spacing:-.025em;margin:0 0 12px;color:var(--text)}.ef-app-shell__sheet-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.ef-app-shell__sheet-item{display:flex;align-items:center;gap:12px;width:100%;height:var(--hit-touch);padding:0 16px;background:var(--paper-alt);border:0;border-radius:var(--r-md);text-decoration:none;color:var(--text);cursor:pointer;font-family:Manrope,system-ui,sans-serif;font-size:14px;font-weight:500;text-align:left;transition:background-color var(--t-fast) var(--ease-out)}.ef-app-shell__sheet-item:hover{background:var(--rule)}.ef-app-shell__sheet-item.is-active{border-left:3px solid var(--module);padding-left:13px}@media(max-width:767px){.ef-app-shell__rail{display:none}}.ef-app-shell__sheet-badge{margin-inline-start:auto;font-size:11px;font-weight:700;color:var(--text-mute)}.ef-app-shell__sheet-item.is-active{background:var(--paper);color:var(--text);font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: EfModuleRailComponent, selector: "ef-module-rail", inputs: ["badges"] }, { kind: "component", type: EfModuleSideComponent, selector: "ef-module-side" }, { kind: "component", type: EfAppTopComponent, selector: "ef-app-top", inputs: ["themeToggle", "logout"] }, { kind: "component", type: EfAppMainComponent, selector: "ef-app-main", inputs: ["stripe"] }, { kind: "component", type: EfBottomSheetComponent, selector: "ef-bottom-sheet", inputs: ["open", "dismissOnBackdrop"], outputs: ["openChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5659
5660
  }
5660
5661
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppShellComponent, decorators: [{
5661
5662
  type: Component,
@@ -5665,14 +5666,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5665
5666
  EfModuleSideComponent,
5666
5667
  EfAppTopComponent,
5667
5668
  EfAppMainComponent,
5668
- EfAppShellMobileComponent,
5669
+ EfBottomSheetComponent,
5670
+ TranslateModule,
5671
+ RouterLink,
5669
5672
  ], host: {
5670
5673
  '[attr.data-viewport]': 'viewport()',
5671
- }, template: "@if (isMobile()) {\n <ef-app-shell-mobile>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n <ng-content select=\"[actions]\" actions></ng-content>\n <ng-content select=\"[fab]\" fab></ng-content>\n <ng-content></ng-content>\n </ef-app-shell-mobile>\n} @else {\n <ef-module-rail>\n <ng-content select=\"[logo]\" logo></ng-content>\n </ef-module-rail>\n @if (showSide()) {\n <ef-module-side>\n <ng-content select=\"[side-header-meta]\" side-header-meta></ng-content>\n <ng-content select=\"[side-footer]\" side-footer></ng-content>\n </ef-module-side>\n }\n <div class=\"ef-app-shell__column\">\n <ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[actions]\" actions></ng-content>\n </ef-app-top>\n <ef-app-main [stripe]=\"stripe\">\n <ng-content></ng-content>\n </ef-app-main>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:row;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell__column{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}\n"] }]
5674
+ }, template: "<!-- One layout, one declaration per slot.\n Angular binds projected content to the LAST matching <ng-content> in a\n template, so declaring the same selector once per viewport branch meant the\n branch that was actually rendered on a phone received nothing \u2014 every\n screen came up blank inside working chrome. The layouts differ by CSS and\n by which chrome is rendered, never by where the content is declared. -->\n\n<ef-module-rail class=\"ef-app-shell__rail\">\n <ng-content select=\"[logo]\" logo></ng-content>\n</ef-module-rail>\n\n@if (showSide()) {\n <ef-module-side class=\"ef-app-shell__side\">\n <ng-content select=\"[side-header-meta]\" side-header-meta></ng-content>\n <ng-content select=\"[side-footer]\" side-footer></ng-content>\n </ef-module-side>\n}\n\n<div class=\"ef-app-shell__column\">\n <ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n @if (isMobile()) {\n <button\n actions\n type=\"button\"\n class=\"ef-app-shell__menu\"\n (click)=\"openSwitcher()\"\n [attr.aria-label]=\"'common_modules' | translate\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </button>\n }\n <ng-content select=\"[actions]\" actions></ng-content>\n </ef-app-top>\n\n <ef-app-main [stripe]=\"stripe\">\n <ng-content></ng-content>\n </ef-app-main>\n\n @if (isMobile()) {\n <nav class=\"ef-app-shell__tabs\" [attr.aria-label]=\"'common_modules' | translate\">\n @for (module of tabs(); track module.id) {\n <button\n type=\"button\"\n class=\"ef-app-shell__tab\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n [attr.aria-current]=\"activeId() === module.id ? 'page' : null\"\n (click)=\"onSelect(module)\"\n >\n <i class=\"ef-app-shell__tab-icon\" [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span class=\"ef-app-shell__tab-label\">{{ module.labelKey | translate }}</span>\n </button>\n }\n </nav>\n }\n</div>\n\n<ng-content select=\"[fab]\"></ng-content>\n\n@if (isMobile()) {\n <ef-bottom-sheet [open]=\"switcherOpen()\" (openChange)=\"onSwitcherChange($event)\">\n <!-- The screens first: the tab bar already switches modules, so this is\n the only way to reach anything but a module's default screen. -->\n @for (section of moduleSections(); track section.id) {\n @if (section.labelKey) {\n <h2 class=\"ef-app-shell__sheet-title\">{{ section.labelKey | translate }}</h2>\n }\n <ul class=\"ef-app-shell__sheet-list\">\n @for (item of section.items; track item.id) {\n <li>\n <a\n class=\"ef-app-shell__sheet-item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"is-active\"\n (click)=\"onNavigate(item)\"\n >\n @if (item.icon) {\n <i [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span>{{ item.labelKey | translate }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <span class=\"ef-app-shell__sheet-badge\">{{ item.badge }}</span>\n }\n </a>\n </li>\n }\n </ul>\n }\n\n <h2 class=\"ef-app-shell__sheet-title\">{{ 'common_modules' | translate }}</h2>\n <ul class=\"ef-app-shell__sheet-list\">\n @for (module of allVisible(); track module.id) {\n <li>\n <button\n type=\"button\"\n class=\"ef-app-shell__sheet-item\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n (click)=\"onSelect(module)\"\n >\n <i [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span>{{ module.labelKey | translate }}</span>\n </button>\n </li>\n }\n </ul>\n </ef-bottom-sheet>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:row;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell__column{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.ef-app-shell__menu{display:inline-flex;align-items:center;justify-content:center;width:var(--hit-touch);height:var(--hit-touch);background:transparent;border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-size:18px}.ef-app-shell__menu:hover{background:var(--paper-alt)}.ef-app-shell__tabs{flex:0 0 auto;display:flex;align-items:stretch;height:64px;padding-bottom:env(safe-area-inset-bottom);background:var(--paper);border-top:1px solid var(--rule)}.ef-app-shell__tab{position:relative;flex:1 1 0;min-width:0;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:transparent;border:0;color:var(--text-mute);cursor:pointer;padding:6px 4px;transition:color var(--t-fast) var(--ease-out)}.ef-app-shell__tab.is-active{color:var(--text)}.ef-app-shell__tab.is-active:after{content:\"\";position:absolute;top:0;left:50%;transform:translate(-50%);width:28px;height:3px;background:var(--module);border-radius:0 0 var(--r-sm) var(--r-sm)}.ef-app-shell__tab-icon{font-size:18px;line-height:1}.ef-app-shell__tab-label{font-family:Manrope,system-ui,sans-serif;font-size:10.5px;font-weight:600;letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.ef-app-shell__sheet-title{font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 600,\"wdth\" 95;font-size:22px;letter-spacing:-.025em;margin:0 0 12px;color:var(--text)}.ef-app-shell__sheet-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.ef-app-shell__sheet-item{display:flex;align-items:center;gap:12px;width:100%;height:var(--hit-touch);padding:0 16px;background:var(--paper-alt);border:0;border-radius:var(--r-md);text-decoration:none;color:var(--text);cursor:pointer;font-family:Manrope,system-ui,sans-serif;font-size:14px;font-weight:500;text-align:left;transition:background-color var(--t-fast) var(--ease-out)}.ef-app-shell__sheet-item:hover{background:var(--rule)}.ef-app-shell__sheet-item.is-active{border-left:3px solid var(--module);padding-left:13px}@media(max-width:767px){.ef-app-shell__rail{display:none}}.ef-app-shell__sheet-badge{margin-inline-start:auto;font-size:11px;font-weight:700;color:var(--text-mute)}.ef-app-shell__sheet-item.is-active{background:var(--paper);color:var(--text);font-weight:700}\n"] }]
5672
5675
  }], propDecorators: { stripe: [{
5673
5676
  type: Input
5674
5677
  }] } });
5675
5678
 
5679
+ /**
5680
+ * Mobile-only shell. Picks up when `ef-app-shell` detects a mobile
5681
+ * viewport. Composition:
5682
+ *
5683
+ * - Top bar (`ef-app-top`) with a menu trigger that opens the module
5684
+ * switcher in a bottom sheet.
5685
+ * - Scrollable content (`ef-app-main`) — projects `<router-outlet />`.
5686
+ * - 5-tab bottom bar — first 5 visible modules from
5687
+ * `EfPermissionService.visibleModules()`. Apps that want a usage-
5688
+ * ranked tab order can replace the registry via `EF_MODULES_TOKEN`.
5689
+ * - FAB slot (`[fab]`) — apps project a `<ef-fab />` here.
5690
+ *
5691
+ * The module switcher (bottom sheet) lists all visible modules so
5692
+ * users can reach modules outside the top-5 tabs.
5693
+ */
5694
+ class EfAppShellMobileComponent {
5695
+ perms = inject(EfPermissionService);
5696
+ active = inject(EfActiveModuleService);
5697
+ router = inject(Router);
5698
+ activeId = this.active.activeModuleId;
5699
+ switcherOpen = signal(false, ...(ngDevMode ? [{ debugName: "switcherOpen" }] : /* istanbul ignore next */ []));
5700
+ tabs = computed(() => this.perms.visibleModules().slice(0, 5), ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
5701
+ allVisible = this.perms.visibleModules;
5702
+ openSwitcher() { this.switcherOpen.set(true); }
5703
+ onSwitcherChange(open) { this.switcherOpen.set(open); }
5704
+ onSelect(module) {
5705
+ this.router.navigateByUrl(module.defaultRoute);
5706
+ this.switcherOpen.set(false);
5707
+ }
5708
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppShellMobileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5709
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfAppShellMobileComponent, isStandalone: true, selector: "ef-app-shell-mobile", ngImport: i0, template: "<ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n <button\n actions\n type=\"button\"\n class=\"ef-app-shell-mobile__menu\"\n (click)=\"openSwitcher()\"\n aria-label=\"Modules\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </button>\n <ng-content select=\"[actions]\" actions></ng-content>\n</ef-app-top>\n\n<ef-app-main [stripe]=\"true\">\n <ng-content></ng-content>\n</ef-app-main>\n\n<nav class=\"ef-app-shell-mobile__tabs\" aria-label=\"Modules\">\n @for (module of tabs(); track module.id) {\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__tab\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n [attr.aria-current]=\"activeId() === module.id ? 'page' : null\"\n (click)=\"onSelect(module)\"\n >\n <i class=\"ef-app-shell-mobile__tab-icon\" [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span class=\"ef-app-shell-mobile__tab-label\">{{ module.labelKey | translate }}</span>\n </button>\n }\n</nav>\n\n<ng-content select=\"[fab]\"></ng-content>\n\n<ef-bottom-sheet [open]=\"switcherOpen()\" (openChange)=\"onSwitcherChange($event)\">\n <h2 class=\"ef-app-shell-mobile__sheet-title\">{{ 'modules.all' | translate }}</h2>\n <ul class=\"ef-app-shell-mobile__sheet-list\">\n @for (module of allVisible(); track module.id) {\n <li>\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__sheet-item\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n (click)=\"onSelect(module)\"\n >\n <i [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span>{{ module.labelKey | translate }}</span>\n </button>\n </li>\n }\n </ul>\n</ef-bottom-sheet>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell-mobile__menu{display:inline-flex;align-items:center;justify-content:center;width:var(--hit-touch);height:var(--hit-touch);background:transparent;border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-size:18px}.ef-app-shell-mobile__menu:hover{background:var(--paper-alt)}.ef-app-shell-mobile__tabs{flex:0 0 auto;display:flex;align-items:stretch;height:64px;padding-bottom:env(safe-area-inset-bottom);background:var(--paper);border-top:1px solid var(--rule)}.ef-app-shell-mobile__tab{position:relative;flex:1 1 0;min-width:0;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:transparent;border:0;color:var(--text-mute);cursor:pointer;padding:6px 4px;transition:color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__tab.is-active{color:var(--text)}.ef-app-shell-mobile__tab.is-active:after{content:\"\";position:absolute;top:0;left:50%;transform:translate(-50%);width:28px;height:3px;background:var(--module);border-radius:0 0 var(--r-sm) var(--r-sm)}.ef-app-shell-mobile__tab-icon{font-size:18px;line-height:1}.ef-app-shell-mobile__tab-label{font-family:Manrope,system-ui,sans-serif;font-size:10.5px;font-weight:600;letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.ef-app-shell-mobile__sheet-title{font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 600,\"wdth\" 95;font-size:22px;letter-spacing:-.025em;margin:0 0 12px;color:var(--text)}.ef-app-shell-mobile__sheet-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.ef-app-shell-mobile__sheet-item{display:flex;align-items:center;gap:12px;width:100%;height:var(--hit-touch);padding:0 16px;background:var(--paper-alt);border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-family:Manrope,system-ui,sans-serif;font-size:14px;font-weight:500;text-align:left;transition:background-color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__sheet-item:hover{background:var(--rule)}.ef-app-shell-mobile__sheet-item.is-active{border-left:3px solid var(--module);padding-left:13px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "component", type: EfAppTopComponent, selector: "ef-app-top", inputs: ["themeToggle", "logout"] }, { kind: "component", type: EfAppMainComponent, selector: "ef-app-main", inputs: ["stripe"] }, { kind: "component", type: EfBottomSheetComponent, selector: "ef-bottom-sheet", inputs: ["open", "dismissOnBackdrop"], outputs: ["openChange"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5710
+ }
5711
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfAppShellMobileComponent, decorators: [{
5712
+ type: Component,
5713
+ args: [{ selector: 'ef-app-shell-mobile', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
5714
+ CommonModule,
5715
+ TranslateModule,
5716
+ TooltipModule,
5717
+ EfAppTopComponent,
5718
+ EfAppMainComponent,
5719
+ EfBottomSheetComponent,
5720
+ ], template: "<ef-app-top>\n <ng-content select=\"[breadcrumb]\" breadcrumb></ng-content>\n <ng-content select=\"[search]\" search></ng-content>\n <button\n actions\n type=\"button\"\n class=\"ef-app-shell-mobile__menu\"\n (click)=\"openSwitcher()\"\n aria-label=\"Modules\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </button>\n <ng-content select=\"[actions]\" actions></ng-content>\n</ef-app-top>\n\n<ef-app-main [stripe]=\"true\">\n <ng-content></ng-content>\n</ef-app-main>\n\n<nav class=\"ef-app-shell-mobile__tabs\" aria-label=\"Modules\">\n @for (module of tabs(); track module.id) {\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__tab\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n [attr.aria-current]=\"activeId() === module.id ? 'page' : null\"\n (click)=\"onSelect(module)\"\n >\n <i class=\"ef-app-shell-mobile__tab-icon\" [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span class=\"ef-app-shell-mobile__tab-label\">{{ module.labelKey | translate }}</span>\n </button>\n }\n</nav>\n\n<ng-content select=\"[fab]\"></ng-content>\n\n<ef-bottom-sheet [open]=\"switcherOpen()\" (openChange)=\"onSwitcherChange($event)\">\n <h2 class=\"ef-app-shell-mobile__sheet-title\">{{ 'modules.all' | translate }}</h2>\n <ul class=\"ef-app-shell-mobile__sheet-list\">\n @for (module of allVisible(); track module.id) {\n <li>\n <button\n type=\"button\"\n class=\"ef-app-shell-mobile__sheet-item\"\n [class.is-active]=\"activeId() === module.id\"\n [attr.data-module]=\"module.id\"\n (click)=\"onSelect(module)\"\n >\n <i [class]=\"module.icon\" aria-hidden=\"true\"></i>\n <span>{{ module.labelKey | translate }}</span>\n </button>\n </li>\n }\n </ul>\n</ef-bottom-sheet>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100vh;background:var(--paper);color:var(--text);overflow:hidden}.ef-app-shell-mobile__menu{display:inline-flex;align-items:center;justify-content:center;width:var(--hit-touch);height:var(--hit-touch);background:transparent;border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-size:18px}.ef-app-shell-mobile__menu:hover{background:var(--paper-alt)}.ef-app-shell-mobile__tabs{flex:0 0 auto;display:flex;align-items:stretch;height:64px;padding-bottom:env(safe-area-inset-bottom);background:var(--paper);border-top:1px solid var(--rule)}.ef-app-shell-mobile__tab{position:relative;flex:1 1 0;min-width:0;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:transparent;border:0;color:var(--text-mute);cursor:pointer;padding:6px 4px;transition:color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__tab.is-active{color:var(--text)}.ef-app-shell-mobile__tab.is-active:after{content:\"\";position:absolute;top:0;left:50%;transform:translate(-50%);width:28px;height:3px;background:var(--module);border-radius:0 0 var(--r-sm) var(--r-sm)}.ef-app-shell-mobile__tab-icon{font-size:18px;line-height:1}.ef-app-shell-mobile__tab-label{font-family:Manrope,system-ui,sans-serif;font-size:10.5px;font-weight:600;letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.ef-app-shell-mobile__sheet-title{font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 96,\"wght\" 600,\"wdth\" 95;font-size:22px;letter-spacing:-.025em;margin:0 0 12px;color:var(--text)}.ef-app-shell-mobile__sheet-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.ef-app-shell-mobile__sheet-item{display:flex;align-items:center;gap:12px;width:100%;height:var(--hit-touch);padding:0 16px;background:var(--paper-alt);border:0;border-radius:var(--r-md);color:var(--text);cursor:pointer;font-family:Manrope,system-ui,sans-serif;font-size:14px;font-weight:500;text-align:left;transition:background-color var(--t-fast) var(--ease-out)}.ef-app-shell-mobile__sheet-item:hover{background:var(--rule)}.ef-app-shell-mobile__sheet-item.is-active{border-left:3px solid var(--module);padding-left:13px}\n"] }]
5721
+ }] });
5722
+
5676
5723
  /**
5677
5724
  * Floating action button — mobile primary action surface.
5678
5725
  *
@@ -5950,7 +5997,7 @@ class EfTabsComponent {
5950
5997
  return item.value;
5951
5998
  }
5952
5999
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5953
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfTabsComponent, isStandalone: true, selector: "ef-tabs", inputs: { items: "items", value: "value", variant: "variant", module: "module", overflow: ["overflow", "overflow", booleanAttribute], hideCounts: ["hideCounts", "hideCounts", booleanAttribute], ariaLabel: "ariaLabel", dirtyLabelKey: "dirtyLabelKey" }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.ef-tabs--pill": "variant === 'pill'", "class.ef-tabs--underline": "variant === 'underline'", "class.ef-tabs--overflow": "overflow", "attr.data-module": "module ?? null" } }, queries: [{ propertyName: "panels", predicate: EfTabPanelDirective }], viewQueries: [{ propertyName: "tabEls", predicate: ["tabEl"], descendants: true }], ngImport: i0, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:inline-flex;align-items:stretch;gap:2px;min-width:0;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:JetBrains Mono,ui-monospace,monospace;font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}: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)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6000
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfTabsComponent, isStandalone: true, selector: "ef-tabs", inputs: { items: "items", value: "value", variant: "variant", module: "module", overflow: ["overflow", "overflow", booleanAttribute], hideCounts: ["hideCounts", "hideCounts", booleanAttribute], ariaLabel: "ariaLabel", dirtyLabelKey: "dirtyLabelKey" }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.ef-tabs--pill": "variant === 'pill'", "class.ef-tabs--underline": "variant === 'underline'", "class.ef-tabs--overflow": "overflow", "attr.data-module": "module ?? null" } }, queries: [{ propertyName: "panels", predicate: EfTabPanelDirective }], viewQueries: [{ propertyName: "tabEls", predicate: ["tabEl"], descendants: true }], ngImport: i0, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:flex;align-items:stretch;gap:2px;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__list>*{flex:0 0 auto}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:JetBrains Mono,ui-monospace,monospace;font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}: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)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5954
6001
  }
5955
6002
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, decorators: [{
5956
6003
  type: Component,
@@ -5959,7 +6006,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5959
6006
  '[class.ef-tabs--underline]': "variant === 'underline'",
5960
6007
  '[class.ef-tabs--overflow]': 'overflow',
5961
6008
  '[attr.data-module]': 'module ?? null',
5962
- }, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:inline-flex;align-items:stretch;gap:2px;min-width:0;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:JetBrains Mono,ui-monospace,monospace;font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}: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)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}\n"] }]
6009
+ }, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:flex;align-items:stretch;gap:2px;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__list>*{flex:0 0 auto}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:JetBrains Mono,ui-monospace,monospace;font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}: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)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}\n"] }]
5963
6010
  }], propDecorators: { items: [{
5964
6011
  type: Input,
5965
6012
  args: [{ required: true }]
@@ -8555,6 +8602,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
8555
8602
  args: ['pTable']
8556
8603
  }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], searchEntity: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEntity", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], pt: [{ type: i0.Input, args: [{ isSignal: true, alias: "pt", required: false }] }], screenStateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "screenStateKey", required: false }] }], editRow: [{ type: i0.Output, args: ["editRow"] }], deleteRow: [{ type: i0.Output, args: ["deleteRow"] }], duplicateRow: [{ type: i0.Output, args: ["duplicateRow"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }] } });
8557
8604
 
8605
+ /**
8606
+ * App-wide default, so screens don't drift apart. Provide it once at the
8607
+ * application root; a screen that genuinely needs the other shape overrides
8608
+ * it with `[mobileLayout]`.
8609
+ *
8610
+ * ```ts
8611
+ * { provide: EF_DATA_CARD_MOBILE_LAYOUT, useValue: 'card' }
8612
+ * ```
8613
+ */
8614
+ const EF_DATA_CARD_MOBILE_LAYOUT = new InjectionToken('EF_DATA_CARD_MOBILE_LAYOUT');
8615
+
8558
8616
  /**
8559
8617
  * Pagination control matching the Comptoir prototype's `.pager`
8560
8618
  * shape (lines-per-page selector · range · page buttons).
@@ -8956,6 +9014,110 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
8956
9014
  * ```
8957
9015
  */
8958
9016
  class EfDataCardComponent {
9017
+ /* ── Mobile presentation ─────────────────────────────────────
9018
+ Under 768px a table stops being readable: six columns on a
9019
+ 390px screen either overflow or squeeze to nothing. The rows
9020
+ become a list instead, driven by the same column config —
9021
+ each column declares (or is given) a role. */
9022
+ viewport = inject(EfViewportService);
9023
+ configuredMobileLayout = inject(EF_DATA_CARD_MOBILE_LAYOUT, { optional: true });
9024
+ isMobile = this.viewport.isMobile;
9025
+ /** Per-screen override of the app-wide default. */
9026
+ mobileLayout = input(null, ...(ngDevMode ? [{ debugName: "mobileLayout" }] : /* istanbul ignore next */ []));
9027
+ effectiveMobileLayout = computed(() => this.mobileLayout() ?? this.configuredMobileLayout ?? 'row', ...(ngDevMode ? [{ debugName: "effectiveMobileLayout" }] : /* istanbul ignore next */ []));
9028
+ /** Rows currently showing their detail, keyed the same way as trackByRow. */
9029
+ expandedRows = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedRows" }] : /* istanbul ignore next */ []));
9030
+ isExpanded(row, index) {
9031
+ return this.expandedRows().has(this.rowKey(row, index));
9032
+ }
9033
+ /**
9034
+ * The row head is the expander, but the actions menu lives inside it.
9035
+ * Filtering here rather than stopping propagation on a wrapper keeps the
9036
+ * wrapper a plain span — a click handler on one would need a keyboard
9037
+ * equivalent and a tabindex to be usable, which a decorative box should
9038
+ * not have.
9039
+ */
9040
+ onRowHeadActivate(row, index, event) {
9041
+ if (!this.hasDetail())
9042
+ return;
9043
+ const target = event.target;
9044
+ if (target?.closest('.mrow__actions'))
9045
+ return;
9046
+ this.toggleExpanded(row, index);
9047
+ }
9048
+ toggleExpanded(row, index) {
9049
+ const key = this.rowKey(row, index);
9050
+ const next = new Set(this.expandedRows());
9051
+ if (!next.delete(key))
9052
+ next.add(key);
9053
+ this.expandedRows.set(next);
9054
+ }
9055
+ rowKey(row, index) {
9056
+ const id = row?.['id'];
9057
+ return id ?? index;
9058
+ }
9059
+ /**
9060
+ * Columns that carry data on mobile, in role order. Structural columns
9061
+ * (select, actions) are handled by the row chrome, not as fields.
9062
+ */
9063
+ mobileColumns = computed(() => this.effectiveColumns().filter(c => c.id !== 'select' && c.id !== 'actions'), ...(ngDevMode ? [{ debugName: "mobileColumns" }] : /* istanbul ignore next */ []));
9064
+ /**
9065
+ * The role a column plays on mobile. An explicit `mobile` wins; otherwise
9066
+ * it is derived so every existing screen gets a sensible list without
9067
+ * touching its column definitions: the first text-ish column is what you
9068
+ * scan for, a status chip is a badge, money and dates sit on the muted
9069
+ * line, and the rest waits behind the expander.
9070
+ */
9071
+ roleOf(col) {
9072
+ if (col.mobile)
9073
+ return col.mobile;
9074
+ if (col.type === 'status' || col.type === 'chip')
9075
+ return 'status';
9076
+ if (col.id === this.derivedPrimaryId())
9077
+ return 'primary';
9078
+ if (col.type === 'money' || col.type === 'date' || col.type === 'datetime')
9079
+ return 'secondary';
9080
+ return 'detail';
9081
+ }
9082
+ derivedPrimaryId = computed(() => {
9083
+ const cols = this.mobileColumns();
9084
+ // Screens already mark the row's identity column `cellClass: 'strong'`
9085
+ // so it reads as the row's title in the table — that is a better
9086
+ // signal than position. Products, for one, leads with an external
9087
+ // reference that is usually blank; its title is second.
9088
+ const strong = cols.find(c => (c.cellClass ?? '').split(/\s+/).includes('strong'));
9089
+ if (strong)
9090
+ return strong.id;
9091
+ const textish = cols.find(c => !c.type || c.type === 'text' || c.type === 'mono');
9092
+ return (textish ?? cols[0])?.id ?? '';
9093
+ }, ...(ngDevMode ? [{ debugName: "derivedPrimaryId" }] : /* istanbul ignore next */ []));
9094
+ primaryColumn = computed(() => this.mobileColumns().find(c => this.roleOf(c) === 'primary'), ...(ngDevMode ? [{ debugName: "primaryColumn" }] : /* istanbul ignore next */ []));
9095
+ statusColumn = computed(() => this.mobileColumns().find(c => this.roleOf(c) === 'status'), ...(ngDevMode ? [{ debugName: "statusColumn" }] : /* istanbul ignore next */ []));
9096
+ secondaryColumns = computed(() => this.mobileColumns().filter(c => this.roleOf(c) === 'secondary'), ...(ngDevMode ? [{ debugName: "secondaryColumns" }] : /* istanbul ignore next */ []));
9097
+ /**
9098
+ * Fields shown once a row opens. The primary column is excluded: the
9099
+ * header already carries it, and repeating it is the first thing you
9100
+ * notice. Status is excluded for the same reason.
9101
+ */
9102
+ detailColumns = computed(() => this.mobileColumns().filter(c => {
9103
+ const role = this.roleOf(c);
9104
+ return role === 'detail' || role === 'secondary';
9105
+ }), ...(ngDevMode ? [{ debugName: "detailColumns" }] : /* istanbul ignore next */ []));
9106
+ /** How many fields a card shows before "view more". */
9107
+ static CARD_PREVIEW_FIELDS = 4;
9108
+ /** Fields a collapsed card shows. A row shows none until it opens. */
9109
+ cardPreviewColumns = computed(() => this.detailColumns().slice(0, EfDataCardComponent.CARD_PREVIEW_FIELDS), ...(ngDevMode ? [{ debugName: "cardPreviewColumns" }] : /* istanbul ignore next */ []));
9110
+ hasDetail = computed(() => this.detailColumns().length > 0, ...(ngDevMode ? [{ debugName: "hasDetail" }] : /* istanbul ignore next */ []));
9111
+ /** Only worth a toggle when opening actually reveals something. */
9112
+ hasMoreThanPreview = computed(() => this.detailColumns().length > EfDataCardComponent.CARD_PREVIEW_FIELDS, ...(ngDevMode ? [{ debugName: "hasMoreThanPreview" }] : /* istanbul ignore next */ []));
9113
+ /** Fields visible for a row right now, given layout and open state. */
9114
+ visibleDetail(row, index) {
9115
+ const open = this.isExpanded(row, index);
9116
+ if (this.effectiveMobileLayout() === 'card') {
9117
+ return open ? this.detailColumns() : this.cardPreviewColumns();
9118
+ }
9119
+ return open ? this.detailColumns() : [];
9120
+ }
8959
9121
  refDataService = inject(SCREEN_REF_DATA_SERVICE, { optional: true });
8960
9122
  /* ── Data inputs ────────────────────────────────────────────── */
8961
9123
  columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
@@ -9293,7 +9455,7 @@ class EfDataCardComponent {
9293
9455
  };
9294
9456
  trackByColumn = (_, col) => col.id;
9295
9457
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDataCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9296
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDataCardComponent, isStandalone: true, selector: "ef-data-card", inputs: { 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 }, tableKey: { classPropertyName: "tableKey", publicName: "tableKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", rowDoubleClick: "rowDoubleClick", rowAction: "rowAction" }, 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: "<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 (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 <table class=\"tbl\">\n <thead>\n <tr>\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\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 (click)=\"onHeaderClick(col)\"\n [style.cursor]=\"col.sortable ? 'pointer' : null\"\n >\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 @if (col.sortable && sortIndicator(col)) {\n <span aria-hidden=\"true\" style=\"margin-inline-start: 4px; opacity: 0.7;\">{{ sortIndicator(col) }}</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody>\n @for (row of rows(); track trackByRow($index, row)) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n (dblclick)=\"onRowDoubleClick(row, $event)\"\n >\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container\n *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col, index: $index }\"\n ></ng-container>\n } @else 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 @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 </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length\">\n <ng-content select=\"[empty-state]\"></ng-content>\n </td>\n </tr>\n }\n </tbody>\n </table>\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 @if (!hidePager()) {\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", 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}\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: EfRowActionsComponent, selector: "ef-row-actions", inputs: ["items", "context", "triggerIcon", "triggerAriaKey", "disabled", "menuLeft"], outputs: ["opened", "closed"] }, { 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 });
9458
+ 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 }, tableKey: { classPropertyName: "tableKey", publicName: "tableKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", rowDoubleClick: "rowDoubleClick", rowAction: "rowAction" }, 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\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col }\"></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 (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 class=\"mlist\" [attr.data-layout]=\"effectiveMobileLayout()\">\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\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: pc }\"></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 }\"></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 }\"></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 }\"></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 <li class=\"mrow mrow--empty\">\n <ng-content select=\"[empty-state-mobile]\"></ng-content>\n </li>\n }\n </ul>\n } @else {\n <table class=\"tbl\">\n <thead>\n <tr>\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\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 (click)=\"onHeaderClick(col)\"\n [style.cursor]=\"col.sortable ? 'pointer' : null\"\n >\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 @if (col.sortable && sortIndicator(col)) {\n <span aria-hidden=\"true\" style=\"margin-inline-start: 4px; opacity: 0.7;\">{{ sortIndicator(col) }}</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody>\n @for (row of rows(); track trackByRow($index, row)) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n (dblclick)=\"onRowDoubleClick(row, $event)\"\n >\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td [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 }\"\n ></ng-container>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length\">\n <ng-content select=\"[empty-state]\"></ng-content>\n </td>\n </tr>\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 @if (!hidePager()) {\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", 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}\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: EfRowActionsComponent, selector: "ef-row-actions", inputs: ["items", "context", "triggerIcon", "triggerAriaKey", "disabled", "menuLeft"], outputs: ["opened", "closed"] }, { 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 });
9297
9459
  }
9298
9460
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDataCardComponent, decorators: [{
9299
9461
  type: Component,
@@ -9303,8 +9465,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9303
9465
  EfPagerComponent,
9304
9466
  EfStatusChipComponent,
9305
9467
  EfRowActionsComponent,
9306
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<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 (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 <table class=\"tbl\">\n <thead>\n <tr>\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\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 (click)=\"onHeaderClick(col)\"\n [style.cursor]=\"col.sortable ? 'pointer' : null\"\n >\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 @if (col.sortable && sortIndicator(col)) {\n <span aria-hidden=\"true\" style=\"margin-inline-start: 4px; opacity: 0.7;\">{{ sortIndicator(col) }}</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody>\n @for (row of rows(); track trackByRow($index, row)) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n (dblclick)=\"onRowDoubleClick(row, $event)\"\n >\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td [class]=\"cellAlignClass(col)\" [attr.data-col]=\"col.id\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container\n *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col, index: $index }\"\n ></ng-container>\n } @else 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 @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 </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length\">\n <ng-content select=\"[empty-state]\"></ng-content>\n </td>\n </tr>\n }\n </tbody>\n </table>\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 @if (!hidePager()) {\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", 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}\n"] }]
9307
- }], propDecorators: { 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 }] }], 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"] }], bodyTemplates: [{
9468
+ ], 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\">\n @if (bodyTemplate(col.id); as bodyTpl) {\n <ng-container *ngTemplateOutlet=\"bodyTpl; context: { $implicit: row, col: col }\"></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 (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 class=\"mlist\" [attr.data-layout]=\"effectiveMobileLayout()\">\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\">\n <ng-container *ngTemplateOutlet=\"cellTpl; context: { $implicit: row, col: pc }\"></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 }\"></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 }\"></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 }\"></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 <li class=\"mrow mrow--empty\">\n <ng-content select=\"[empty-state-mobile]\"></ng-content>\n </li>\n }\n </ul>\n } @else {\n <table class=\"tbl\">\n <thead>\n <tr>\n @for (col of effectiveColumns(); track trackByColumn($index, col)) {\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 (click)=\"onHeaderClick(col)\"\n [style.cursor]=\"col.sortable ? 'pointer' : null\"\n >\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 @if (col.sortable && sortIndicator(col)) {\n <span aria-hidden=\"true\" style=\"margin-inline-start: 4px; opacity: 0.7;\">{{ sortIndicator(col) }}</span>\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody>\n @for (row of rows(); track trackByRow($index, row)) {\n <tr\n [class.is-row-clickable]=\"rowDoubleClickable()\"\n (dblclick)=\"onRowDoubleClick(row, $event)\"\n >\n @for (col of effectiveColumns(); track trackByColumn($index, col); let colIdx = $index) {\n <td [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 }\"\n ></ng-container>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"effectiveColumns().length\">\n <ng-content select=\"[empty-state]\"></ng-content>\n </td>\n </tr>\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 @if (!hidePager()) {\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", 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}\n"] }]
9469
+ }], 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 }] }], 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"] }], bodyTemplates: [{
9308
9470
  type: ContentChildren,
9309
9471
  args: [EfColumnTemplateDirective]
9310
9472
  }], headerTemplates: [{
@@ -10550,5 +10712,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10550
10712
  * Generated bundle index. Do not edit.
10551
10713
  */
10552
10714
 
10553
- export { EF_BADGE_BG_COLORS, EF_BADGE_BG_LIGHT_COLORS, EF_BADGE_BORDER_COLORS, EF_BADGE_DEFAULTS, EF_BADGE_TEXT_COLORS, EF_CHART_PALETTE, EF_DATATABLE_ACTIONS_DEFAULTS, EF_DATATABLE_COLUMN_DEFAULTS, EF_DATATABLE_DEFAULTS, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRankListComponent, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfServerErrorsDirective, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
10715
+ export { EF_BADGE_BG_COLORS, EF_BADGE_BG_LIGHT_COLORS, EF_BADGE_BORDER_COLORS, EF_BADGE_DEFAULTS, EF_BADGE_TEXT_COLORS, EF_CHART_PALETTE, EF_DATATABLE_ACTIONS_DEFAULTS, EF_DATATABLE_COLUMN_DEFAULTS, EF_DATATABLE_DEFAULTS, EF_DATA_CARD_MOBILE_LAYOUT, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRankListComponent, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfServerErrorsDirective, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
10554
10716
  //# sourceMappingURL=elasticias-ui.mjs.map