@elasticias/ui 1.0.49 → 1.0.50

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,6 +1,6 @@
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, isDevMode, model, InjectionToken, afterNextRender, TemplateRef, ContentChildren, viewChild, untracked, contentChild, ViewEncapsulation, ViewChildren, PLATFORM_ID } from '@angular/core';
3
- import { NumberUtils, UuidUtils } from '@elasticias/utils';
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, isDevMode, model, InjectionToken, afterNextRender, TemplateRef, ContentChildren, viewChild, untracked, contentChild, ViewEncapsulation, linkedSignal, ViewChildren, PLATFORM_ID } from '@angular/core';
3
+ import { NumberUtils, UuidUtils, PhoneUtils } from '@elasticias/utils';
4
4
  import * as i1 from 'primeng/button';
5
5
  import { ButtonModule } from 'primeng/button';
6
6
  import * as i1$3 from '@angular/common';
@@ -223,6 +223,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
223
223
  }]
224
224
  }], propDecorators: { quantity: [{ type: i0.Input, args: [{ isSignal: true, alias: "quantity", required: false }] }], quantityChange: [{ type: i0.Output, args: ["quantityChange"] }] } });
225
225
 
226
+ /** A positive number, or null: the shop treats a missing, zero or non-numeric price as unpriced. */
227
+ function positivePrice(value) {
228
+ if (typeof value !== 'number' && typeof value !== 'string')
229
+ return null;
230
+ if (typeof value === 'string' && value.trim() === '')
231
+ return null;
232
+ const n = Number(value);
233
+ return Number.isFinite(n) && n > 0 ? n : null;
234
+ }
235
+ /** The unit price a new line for `product` (and its chosen `variant`) starts at under `priceList`. */
236
+ function defaultUnitPrice(product, variant, priceList = 'wholesale') {
237
+ if (priceList === 'retail') {
238
+ return (positivePrice(variant?.['salePrice']) ??
239
+ positivePrice(product['salePrice']) ??
240
+ 0);
241
+ }
242
+ return (variant?.['price'] ??
243
+ product['unitPrice'] ??
244
+ product['salePrice'] ??
245
+ 0);
246
+ }
247
+
226
248
  /**
227
249
  * Helper functions for order line items
228
250
  */
@@ -294,14 +316,15 @@ class OrderLineItemHelper {
294
316
  /**
295
317
  * Create a line item from product selection
296
318
  * @param keyField - Field name used to identify the product (default: 'id')
319
+ * @param priceList - Which catalogue price the line starts at (see `EfOrderPriceList`)
297
320
  */
298
- static createFromProduct(product, quantity = 1, keyField = 'id', variantId) {
321
+ static createFromProduct(product, quantity = 1, keyField = 'id', variantId, priceList = 'wholesale') {
299
322
  // Resolve variant data if variantId is provided and product has variants
300
323
  const variants = product['variants'];
301
324
  const variant = variantId && variants?.length
302
325
  ? variants.find((v) => v['variantId'] === variantId)
303
326
  : undefined;
304
- const unitPrice = variant?.['price'] ?? product.unitPrice ?? product.salePrice ?? 0;
327
+ const unitPrice = defaultUnitPrice(product, variant, priceList);
305
328
  const taxRate = product.taxRate || 0;
306
329
  const countGroup = (variant?.['countGroup'] ?? product['countGroup'] ?? '');
307
330
  const item = {
@@ -847,6 +870,9 @@ class AbstractEfFormControl {
847
870
  }
848
871
  return this.ariaLabel() || null;
849
872
  }
873
+ /** Ids of elements outside the control that describe it too (a character
874
+ * counter, a help block), appended to `aria-describedby`. */
875
+ describedByExtra = input('', ...(ngDevMode ? [{ debugName: "describedByExtra" }] : /* istanbul ignore next */ []));
850
876
  /** Translated hint (or empty string). */
851
877
  get effectiveHint() {
852
878
  if (this.hintKey()) {
@@ -868,6 +894,7 @@ class AbstractEfFormControl {
868
894
  const ids = [
869
895
  this.effectiveHint ? this.hintId : null,
870
896
  showErrors ? this.errorsId : null,
897
+ this.describedByExtra() || null,
871
898
  ...extra,
872
899
  ].filter((id) => !!id);
873
900
  return ids.length ? ids.join(' ') : null;
@@ -879,12 +906,12 @@ class AbstractEfFormControl {
879
906
  return `${this.effectiveId}-errors`;
880
907
  }
881
908
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: AbstractEfFormControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
882
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", type: AbstractEfFormControl, isStandalone: true, selector: "ng-component", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, hintKey: { classPropertyName: "hintKey", publicName: "hintKey", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true });
909
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", type: AbstractEfFormControl, isStandalone: true, selector: "ng-component", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, hintKey: { classPropertyName: "hintKey", publicName: "hintKey", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, describedByExtra: { classPropertyName: "describedByExtra", publicName: "describedByExtra", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true });
883
910
  }
884
911
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: AbstractEfFormControl, decorators: [{
885
912
  type: Component,
886
913
  args: [{ template: '', standalone: true }]
887
- }], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], placeholderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKey", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], hintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKey", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }] } });
914
+ }], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], placeholderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKey", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], hintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKey", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], describedByExtra: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedByExtra", required: false }] }] } });
888
915
 
889
916
  /**
890
917
  * Comptoir select — wraps PrimeNG `p-select` (single) or `p-multiselect`
@@ -2805,11 +2832,11 @@ class EfInputNumberComponent extends AbstractEfFormControl {
2805
2832
  this.handleClear();
2806
2833
  }
2807
2834
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfInputNumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2808
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfInputNumberComponent, isStandalone: true, selector: "ef-inputnumber", inputs: { value: "value", invalid: "invalid", format: "format", showButtons: ["showButtons", "showButtons", booleanAttribute], buttonLayout: "buttonLayout", incrementButtonClass: "incrementButtonClass", decrementButtonClass: "decrementButtonClass", incrementButtonIcon: "incrementButtonIcon", decrementButtonIcon: "decrementButtonIcon", prefix: "prefix", suffix: "suffix", currency: "currency", currencyDisplay: "currencyDisplay", locale: "locale", localeMatcher: "localeMatcher", mode: "mode", useGrouping: "useGrouping", minFractionDigits: "minFractionDigits", maxFractionDigits: "maxFractionDigits", min: "min", max: "max", step: "step", allowEmpty: "allowEmpty", inputSize: "inputSize", size: "size", maxlength: "maxlength", minlength: "minlength", pattern: "pattern", tabindex: "tabindex", title: "title", ariaLabelledBy: "ariaLabelledBy", inputStyle: "inputStyle", inputStyleClass: "inputStyleClass", style: "style", styleClass: "styleClass", primeNgVariant: "primeNgVariant", autofocus: ["autofocus", "autofocus", booleanAttribute], autocomplete: "autocomplete", fluid: "fluid", inline: ["inline", "inline", booleanAttribute], variant: "variant", pt: "pt", ptOptions: "ptOptions", dt: "dt", unstyled: "unstyled" }, outputs: { inputEvent: "inputEvent", valueChangeEvent: "valueChangeEvent", focusEvent: "focusEvent", blurEvent: "blurEvent", keyDownEvent: "keyDownEvent", clearEvent: "clearEvent" }, host: { properties: { "class.ef-inline": "this.isInline" } }, viewQueries: [{ propertyName: "inputNumber", first: true, predicate: ["inputNumber"], descendants: true }], usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix || showButtons || currencyAddon) {\n <!-- Comptoir variant \u2014 native text input (inputmode=decimal, accepts\n \",\" or \".\" as decimal separator) wrapped in .ef-input-group when\n there's a prefix / suffix / stepper. min/max are enforced by\n blur-time clamping in the component (text inputs have no native\n range semantics). -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (min !== undefined && (value ?? 0) <= min)\"\n (click)=\"step$(-1)\"\n [attr.aria-label]=\"('ef_input_decrement' | translate) || 'Decrease'\"\n >\n <i class=\"pi pi-minus\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n @if (currencyAddon?.side === 'prefix') {\n <span class=\"ef-input-addon is-prefix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n <input\n type=\"text\"\n inputmode=\"decimal\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors, currencyAddon ? currencyId : null)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n @if (currencyAddon?.side === 'suffix') {\n <span class=\"ef-input-addon is-suffix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (max !== undefined && (value ?? 0) >= max)\"\n (click)=\"step$(1)\"\n [attr.aria-label]=\"('ef_input_increment' | translate) || 'Increase'\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native text input (inputmode=decimal,\n accepts \",\" or \".\" as decimal separator; optional inline clear).\n min/max are enforced by blur-time clamping in the component. -->\n <div class=\"ef-input-wrap\">\n <input\n type=\"text\"\n inputmode=\"decimal\"\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 full locale-aware formatting. -->\n <p-inputnumber\n #inputNumber\n [(ngModel)]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n [invalid]=\"isInvalid\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [format]=\"format\"\n [showButtons]=\"showButtons\"\n [buttonLayout]=\"buttonLayout\"\n [incrementButtonClass]=\"incrementButtonClass\"\n [decrementButtonClass]=\"decrementButtonClass\"\n [incrementButtonIcon]=\"incrementButtonIcon\"\n [decrementButtonIcon]=\"decrementButtonIcon\"\n [prefix]=\"prefix\"\n [suffix]=\"suffix\"\n [currency]=\"currency\"\n [currencyDisplay]=\"currencyDisplay\"\n [locale]=\"locale\"\n [localeMatcher]=\"localeMatcher\"\n [mode]=\"mode\"\n [useGrouping]=\"useGrouping\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [allowEmpty]=\"allowEmpty\"\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [inputSize]=\"inputSize\"\n [size]=\"size\"\n [maxlength]=\"maxlength\"\n [minlength]=\"minlength\"\n [pattern]=\"pattern\"\n [tabindex]=\"tabindex\"\n [title]=\"title\"\n [ariaLabel]=\"effectiveAriaLabel\"\n [ariaLabelledBy]=\"ariaLabelledBy\"\n [ariaDescribedBy]=\"describedBy(showRequired || !!serverErrors)\"\n [ariaRequired]=\"required()\"\n [inputStyle]=\"inputStyle\"\n [inputStyleClass]=\"inputStyleClass\"\n [style]=\"style\"\n [styleClass]=\"styleClass\"\n [showClear]=\"showClear()\"\n [variant]=\"primeNgVariant\"\n [autofocus]=\"autofocus\"\n [autocomplete]=\"autocomplete\"\n [fluid]=\"fluid\"\n [pt]=\"pt\"\n [ptOptions]=\"ptOptions\"\n [dt]=\"dt\"\n [unstyled]=\"unstyled\"\n (onInput)=\"handleInput($event)\"\n (onFocus)=\"handleFocus($event)\"\n (onBlur)=\"handleBlur($event)\"\n (onKeyDown)=\"handleKeyDown($event)\"\n (onClear)=\"handleClear()\"\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block;width:100%}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}:host(.ef-inputnumber-small) ::ng-deep .p-inputnumber-input{font-size:.875rem;padding:.375rem .5rem}:host(.ef-inputnumber-large) ::ng-deep .p-inputnumber-input{font-size:1.125rem;padding:.75rem 1rem}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input{border-color:var(--red-500, #ef4444)}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input:focus,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input:focus{box-shadow:0 0 0 .2rem #ef444440}\n"], dependencies: [{ kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i1$4.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2$1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { 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: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
2835
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfInputNumberComponent, isStandalone: true, selector: "ef-inputnumber", inputs: { value: "value", invalid: "invalid", format: "format", showButtons: ["showButtons", "showButtons", booleanAttribute], buttonLayout: "buttonLayout", incrementButtonClass: "incrementButtonClass", decrementButtonClass: "decrementButtonClass", incrementButtonIcon: "incrementButtonIcon", decrementButtonIcon: "decrementButtonIcon", prefix: "prefix", suffix: "suffix", currency: "currency", currencyDisplay: "currencyDisplay", locale: "locale", localeMatcher: "localeMatcher", mode: "mode", useGrouping: "useGrouping", minFractionDigits: "minFractionDigits", maxFractionDigits: "maxFractionDigits", min: "min", max: "max", step: "step", allowEmpty: "allowEmpty", inputSize: "inputSize", size: "size", maxlength: "maxlength", minlength: "minlength", pattern: "pattern", tabindex: "tabindex", title: "title", ariaLabelledBy: "ariaLabelledBy", inputStyle: "inputStyle", inputStyleClass: "inputStyleClass", style: "style", styleClass: "styleClass", primeNgVariant: "primeNgVariant", autofocus: ["autofocus", "autofocus", booleanAttribute], autocomplete: "autocomplete", fluid: "fluid", inline: ["inline", "inline", booleanAttribute], variant: "variant", pt: "pt", ptOptions: "ptOptions", dt: "dt", unstyled: "unstyled" }, outputs: { inputEvent: "inputEvent", valueChangeEvent: "valueChangeEvent", focusEvent: "focusEvent", blurEvent: "blurEvent", keyDownEvent: "keyDownEvent", clearEvent: "clearEvent" }, host: { properties: { "class.ef-inline": "this.isInline" } }, viewQueries: [{ propertyName: "inputNumber", first: true, predicate: ["inputNumber"], descendants: true }], usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix || showButtons || currencyAddon) {\n <!-- Comptoir variant \u2014 native text input (inputmode=decimal, accepts\n \",\" or \".\" as decimal separator) wrapped in .ef-input-group when\n there's a prefix / suffix / stepper. min/max are enforced by\n blur-time clamping in the component (text inputs have no native\n range semantics). -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (min !== undefined && (value ?? 0) <= min)\"\n (click)=\"step$(-1)\"\n [attr.aria-label]=\"('ef_input_decrement' | translate) || 'Decrease'\"\n >\n <i class=\"pi pi-minus\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n @if (currencyAddon?.side === 'prefix') {\n <span class=\"ef-input-addon is-prefix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n <input\n type=\"text\"\n inputmode=\"decimal\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors, currencyAddon ? currencyId : null)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n @if (currencyAddon?.side === 'suffix') {\n <span class=\"ef-input-addon is-suffix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (max !== undefined && (value ?? 0) >= max)\"\n (click)=\"step$(1)\"\n [attr.aria-label]=\"('ef_input_increment' | translate) || 'Increase'\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native text input (inputmode=decimal,\n accepts \",\" or \".\" as decimal separator; optional inline clear).\n min/max are enforced by blur-time clamping in the component. -->\n <div class=\"ef-input-wrap\">\n <input\n type=\"text\"\n inputmode=\"decimal\"\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 full locale-aware formatting. -->\n <p-inputnumber\n #inputNumber\n [(ngModel)]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n [invalid]=\"isInvalid\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [format]=\"format\"\n [showButtons]=\"showButtons\"\n [buttonLayout]=\"buttonLayout\"\n [incrementButtonClass]=\"incrementButtonClass\"\n [decrementButtonClass]=\"decrementButtonClass\"\n [incrementButtonIcon]=\"incrementButtonIcon\"\n [decrementButtonIcon]=\"decrementButtonIcon\"\n [prefix]=\"prefix\"\n [suffix]=\"suffix\"\n [currency]=\"currency\"\n [currencyDisplay]=\"currencyDisplay\"\n [locale]=\"locale\"\n [localeMatcher]=\"localeMatcher\"\n [mode]=\"mode\"\n [useGrouping]=\"useGrouping\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [allowEmpty]=\"allowEmpty\"\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [inputSize]=\"inputSize\"\n [size]=\"size\"\n [maxlength]=\"maxlength ?? null\"\n [minlength]=\"minlength ?? null\"\n [pattern]=\"$any(pattern)\"\n [tabindex]=\"tabindex\"\n [title]=\"title\"\n [ariaLabel]=\"effectiveAriaLabel ?? undefined\"\n [ariaLabelledBy]=\"ariaLabelledBy\"\n [ariaDescribedBy]=\"describedBy(showRequired || !!serverErrors) ?? undefined\"\n [ariaRequired]=\"required()\"\n [inputStyle]=\"inputStyle\"\n [inputStyleClass]=\"inputStyleClass\"\n [style]=\"style\"\n [styleClass]=\"styleClass\"\n [showClear]=\"showClear()\"\n [variant]=\"primeNgVariant\"\n [autofocus]=\"autofocus\"\n [autocomplete]=\"autocomplete\"\n [fluid]=\"fluid\"\n [pt]=\"pt\"\n [ptOptions]=\"ptOptions\"\n [dt]=\"dt\"\n [unstyled]=\"unstyled\"\n (onInput)=\"handleInput($event)\"\n (onFocus)=\"handleFocus($event)\"\n (onBlur)=\"handleBlur($event)\"\n (onKeyDown)=\"handleKeyDown($event)\"\n (onClear)=\"handleClear()\"\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block;width:100%}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}:host(.ef-inputnumber-small) ::ng-deep .p-inputnumber-input{font-size:.875rem;padding:.375rem .5rem}:host(.ef-inputnumber-large) ::ng-deep .p-inputnumber-input{font-size:1.125rem;padding:.75rem 1rem}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input{border-color:var(--red-500, #ef4444)}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input:focus,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input:focus{box-shadow:0 0 0 .2rem #ef444440}\n"], dependencies: [{ kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i1$4.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "placeholder", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "autocomplete", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2$1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { 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: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
2809
2836
  }
2810
2837
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfInputNumberComponent, decorators: [{
2811
2838
  type: Component,
2812
- args: [{ selector: 'ef-inputnumber', standalone: true, imports: [InputNumberModule, FormsModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix || showButtons || currencyAddon) {\n <!-- Comptoir variant \u2014 native text input (inputmode=decimal, accepts\n \",\" or \".\" as decimal separator) wrapped in .ef-input-group when\n there's a prefix / suffix / stepper. min/max are enforced by\n blur-time clamping in the component (text inputs have no native\n range semantics). -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (min !== undefined && (value ?? 0) <= min)\"\n (click)=\"step$(-1)\"\n [attr.aria-label]=\"('ef_input_decrement' | translate) || 'Decrease'\"\n >\n <i class=\"pi pi-minus\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n @if (currencyAddon?.side === 'prefix') {\n <span class=\"ef-input-addon is-prefix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n <input\n type=\"text\"\n inputmode=\"decimal\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors, currencyAddon ? currencyId : null)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n @if (currencyAddon?.side === 'suffix') {\n <span class=\"ef-input-addon is-suffix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (max !== undefined && (value ?? 0) >= max)\"\n (click)=\"step$(1)\"\n [attr.aria-label]=\"('ef_input_increment' | translate) || 'Increase'\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native text input (inputmode=decimal,\n accepts \",\" or \".\" as decimal separator; optional inline clear).\n min/max are enforced by blur-time clamping in the component. -->\n <div class=\"ef-input-wrap\">\n <input\n type=\"text\"\n inputmode=\"decimal\"\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 full locale-aware formatting. -->\n <p-inputnumber\n #inputNumber\n [(ngModel)]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n [invalid]=\"isInvalid\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [format]=\"format\"\n [showButtons]=\"showButtons\"\n [buttonLayout]=\"buttonLayout\"\n [incrementButtonClass]=\"incrementButtonClass\"\n [decrementButtonClass]=\"decrementButtonClass\"\n [incrementButtonIcon]=\"incrementButtonIcon\"\n [decrementButtonIcon]=\"decrementButtonIcon\"\n [prefix]=\"prefix\"\n [suffix]=\"suffix\"\n [currency]=\"currency\"\n [currencyDisplay]=\"currencyDisplay\"\n [locale]=\"locale\"\n [localeMatcher]=\"localeMatcher\"\n [mode]=\"mode\"\n [useGrouping]=\"useGrouping\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [allowEmpty]=\"allowEmpty\"\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [inputSize]=\"inputSize\"\n [size]=\"size\"\n [maxlength]=\"maxlength\"\n [minlength]=\"minlength\"\n [pattern]=\"pattern\"\n [tabindex]=\"tabindex\"\n [title]=\"title\"\n [ariaLabel]=\"effectiveAriaLabel\"\n [ariaLabelledBy]=\"ariaLabelledBy\"\n [ariaDescribedBy]=\"describedBy(showRequired || !!serverErrors)\"\n [ariaRequired]=\"required()\"\n [inputStyle]=\"inputStyle\"\n [inputStyleClass]=\"inputStyleClass\"\n [style]=\"style\"\n [styleClass]=\"styleClass\"\n [showClear]=\"showClear()\"\n [variant]=\"primeNgVariant\"\n [autofocus]=\"autofocus\"\n [autocomplete]=\"autocomplete\"\n [fluid]=\"fluid\"\n [pt]=\"pt\"\n [ptOptions]=\"ptOptions\"\n [dt]=\"dt\"\n [unstyled]=\"unstyled\"\n (onInput)=\"handleInput($event)\"\n (onFocus)=\"handleFocus($event)\"\n (onBlur)=\"handleBlur($event)\"\n (onKeyDown)=\"handleKeyDown($event)\"\n (onClear)=\"handleClear()\"\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block;width:100%}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}:host(.ef-inputnumber-small) ::ng-deep .p-inputnumber-input{font-size:.875rem;padding:.375rem .5rem}:host(.ef-inputnumber-large) ::ng-deep .p-inputnumber-input{font-size:1.125rem;padding:.75rem 1rem}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input{border-color:var(--red-500, #ef4444)}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input:focus,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input:focus{box-shadow:0 0 0 .2rem #ef444440}\n"] }]
2839
+ args: [{ selector: 'ef-inputnumber', standalone: true, imports: [InputNumberModule, FormsModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix || showButtons || currencyAddon) {\n <!-- Comptoir variant \u2014 native text input (inputmode=decimal, accepts\n \",\" or \".\" as decimal separator) wrapped in .ef-input-group when\n there's a prefix / suffix / stepper. min/max are enforced by\n blur-time clamping in the component (text inputs have no native\n range semantics). -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (min !== undefined && (value ?? 0) <= min)\"\n (click)=\"step$(-1)\"\n [attr.aria-label]=\"('ef_input_decrement' | translate) || 'Decrease'\"\n >\n <i class=\"pi pi-minus\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n @if (currencyAddon?.side === 'prefix') {\n <span class=\"ef-input-addon is-prefix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n <input\n type=\"text\"\n inputmode=\"decimal\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors, currencyAddon ? currencyId : null)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n @if (currencyAddon?.side === 'suffix') {\n <span class=\"ef-input-addon is-suffix is-currency\" [id]=\"currencyId\">{{ currencyAddon?.symbol }}</span>\n }\n @if (showButtons) {\n <button\n type=\"button\"\n class=\"ef-input-addon is-stepper\"\n [disabled]=\"isDisabled() || (max !== undefined && (value ?? 0) >= max)\"\n (click)=\"step$(1)\"\n [attr.aria-label]=\"('ef_input_increment' | translate) || 'Increase'\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native text input (inputmode=decimal,\n accepts \",\" or \".\" as decimal separator; optional inline clear).\n min/max are enforced by blur-time clamping in the component. -->\n <div class=\"ef-input-wrap\">\n <input\n type=\"text\"\n inputmode=\"decimal\"\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [value]=\"displayText\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? 'off'\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleNativeInput($any($event.target).value)\"\n (focus)=\"handleFocus($event)\"\n (blur)=\"handleBlur($event)\"\n (keydown)=\"handleKeyDown($event)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 full locale-aware formatting. -->\n <p-inputnumber\n #inputNumber\n [(ngModel)]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n [invalid]=\"isInvalid\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [format]=\"format\"\n [showButtons]=\"showButtons\"\n [buttonLayout]=\"buttonLayout\"\n [incrementButtonClass]=\"incrementButtonClass\"\n [decrementButtonClass]=\"decrementButtonClass\"\n [incrementButtonIcon]=\"incrementButtonIcon\"\n [decrementButtonIcon]=\"decrementButtonIcon\"\n [prefix]=\"prefix\"\n [suffix]=\"suffix\"\n [currency]=\"currency\"\n [currencyDisplay]=\"currencyDisplay\"\n [locale]=\"locale\"\n [localeMatcher]=\"localeMatcher\"\n [mode]=\"mode\"\n [useGrouping]=\"useGrouping\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [allowEmpty]=\"allowEmpty\"\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [inputSize]=\"inputSize\"\n [size]=\"size\"\n [maxlength]=\"maxlength ?? null\"\n [minlength]=\"minlength ?? null\"\n [pattern]=\"$any(pattern)\"\n [tabindex]=\"tabindex\"\n [title]=\"title\"\n [ariaLabel]=\"effectiveAriaLabel ?? undefined\"\n [ariaLabelledBy]=\"ariaLabelledBy\"\n [ariaDescribedBy]=\"describedBy(showRequired || !!serverErrors) ?? undefined\"\n [ariaRequired]=\"required()\"\n [inputStyle]=\"inputStyle\"\n [inputStyleClass]=\"inputStyleClass\"\n [style]=\"style\"\n [styleClass]=\"styleClass\"\n [showClear]=\"showClear()\"\n [variant]=\"primeNgVariant\"\n [autofocus]=\"autofocus\"\n [autocomplete]=\"autocomplete\"\n [fluid]=\"fluid\"\n [pt]=\"pt\"\n [ptOptions]=\"ptOptions\"\n [dt]=\"dt\"\n [unstyled]=\"unstyled\"\n (onInput)=\"handleInput($event)\"\n (onFocus)=\"handleFocus($event)\"\n (onBlur)=\"handleBlur($event)\"\n (onKeyDown)=\"handleKeyDown($event)\"\n (onClear)=\"handleClear()\"\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block;width:100%}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}:host(.ef-inputnumber-small) ::ng-deep .p-inputnumber-input{font-size:.875rem;padding:.375rem .5rem}:host(.ef-inputnumber-large) ::ng-deep .p-inputnumber-input{font-size:1.125rem;padding:.75rem 1rem}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input{border-color:var(--red-500, #ef4444)}:host(.ng-invalid.ng-touched) ::ng-deep .p-inputnumber-input:focus,:host(.ng-invalid.ng-dirty) ::ng-deep .p-inputnumber-input:focus{box-shadow:0 0 0 .2rem #ef444440}\n"] }]
2813
2840
  }], ctorParameters: () => [], propDecorators: { inputNumber: [{
2814
2841
  type: ViewChild,
2815
2842
  args: ['inputNumber', { static: false }]
@@ -3194,7 +3221,7 @@ class EfButtonComponent {
3194
3221
  * `'base'` maps to `null` rather than being forwarded verbatim.
3195
3222
  */
3196
3223
  get pnSize() {
3197
- return this.size === 'base' ? null : this.size;
3224
+ return this.size === 'base' ? undefined : this.size;
3198
3225
  }
3199
3226
  /** Composed class string for the Comptoir variant —
3200
3227
  * `btn btn-{severity} btn-sm` plus any caller-supplied `styleClass`. */
@@ -3821,11 +3848,11 @@ class EfDialogComponent {
3821
3848
  this.saveAction.emit();
3822
3849
  }
3823
3850
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3824
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDialogComponent, isStandalone: true, selector: "ef-dialog", inputs: { header: "header", headerKey: "headerKey", subtitle: "subtitle", subtitleKey: "subtitleKey", visible: ["visible", "visible", booleanAttribute], modal: ["modal", "modal", booleanAttribute], closable: ["closable", "closable", booleanAttribute], draggable: ["draggable", "draggable", booleanAttribute], resizable: ["resizable", "resizable", booleanAttribute], dismissableMask: ["dismissableMask", "dismissableMask", booleanAttribute], closeOnEscape: ["closeOnEscape", "closeOnEscape", booleanAttribute], severity: "severity", icon: "icon", size: "size", style: "style", styleClass: "styleClass", position: "position", appendTo: "appendTo", variant: "variant", actions: "actions", defaultActions: ["defaultActions", "defaultActions", booleanAttribute], saveLabelKey: "saveLabelKey", cancelLabelKey: "cancelLabelKey", saveLabel: "saveLabel", cancelLabel: "cancelLabel", saveIcon: "saveIcon", cancelIcon: "cancelIcon", saveDisabled: ["saveDisabled", "saveDisabled", booleanAttribute], saveStyleClass: "saveStyleClass", cancelStyleClass: "cancelStyleClass", saveSeverity: "saveSeverity", cancelSeverity: "cancelSeverity" }, outputs: { visibleChange: "visibleChange", showEvent: "showEvent", hideEvent: "hideEvent", saveAction: "saveAction", cancelAction: "cancelAction", actionClick: "actionClick" }, viewQueries: [{ propertyName: "defaultFooterTpl", first: true, predicate: ["defaultFooter"], descendants: true, static: true }, { propertyName: "dialog", first: true, predicate: Dialog, descendants: true }], ngImport: i0, template: "<p-dialog\n [visible]=\"visible\"\n (visibleChange)=\"handleVisibleChange($event)\"\n [modal]=\"modal\"\n [draggable]=\"draggable\"\n [resizable]=\"resizable\"\n [dismissableMask]=\"dismissableMask\"\n [closeOnEscape]=\"closeOnEscape\"\n [focusOnShow]=\"!hasAutofocusAction\"\n [style]=\"style\"\n [styleClass]=\"effectiveStyleClass\"\n [position]=\"position\"\n [appendTo]=\"appendTo\"\n [header]=\"variant === 'primeng' ? effectiveTitle : null\"\n [closable]=\"variant === 'primeng' ? closable : false\"\n [footerTemplate]=\"variant === 'primeng' && !useDynamicActions && defaultActions ? defaultFooterTpl : null\"\n (onShow)=\"handleShow()\"\n (onHide)=\"handleHide()\"\n>\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR HEADER (content-child #header) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Replaces PrimeNG's default header strip with our icon + title\n + subtitle + close X structure. Skipped in 'primeng' variant\n (which uses the [header] string input + native close). -->\n @if (variant === 'comptoir') {\n <!-- PrimeNG points the dialog's aria-labelledby at an id it passes to\n this template; the title takes it, so the dialog is named. -->\n <ng-template #header let-labelledBy=\"ariaLabelledBy\">\n @if (effectiveIcon) {\n <span class=\"es-dialog__icon\" aria-hidden=\"true\">\n <i [class]=\"effectiveIcon\"></i>\n </span>\n }\n <div class=\"es-dialog__title-block\">\n <div class=\"es-dialog__title\" [attr.id]=\"labelledBy\">{{ effectiveTitle }}</div>\n @if (effectiveSubtitle) {\n <div class=\"es-dialog__sub\">{{ effectiveSubtitle }}</div>\n }\n </div>\n @if (closable) {\n <button\n type=\"button\"\n class=\"es-dialog__close\"\n [attr.aria-label]=\"'common_close' | translate\"\n (click)=\"handleVisibleChange(false)\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n }\n </ng-template>\n }\n\n <!-- Body \u2014 projected from <ef-dialog>\u2026</ef-dialog> -->\n <ng-content />\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR FOOTER (content-child #footer) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Dynamic [actions] path takes precedence; falls back to the\n backward-compat save+cancel pair when defaultActions is on. -->\n @if (variant === 'comptoir' && hasFooter) {\n <ng-template #footer>\n @if (useDynamicActions) {\n @for (a of actions; track $index) {\n <button\n type=\"button\"\n [class]=\"actionClass(a)\"\n [disabled]=\"a.disabled || a.loading\"\n [attr.data-ef-autofocus]=\"a.autofocus ? '' : null\"\n (click)=\"handleActionClick(a, $index)\"\n >\n @if (a.icon) {\n <i [class]=\"a.icon\"></i>\n }\n {{ actionLabel(a) }}\n </button>\n }\n } @else {\n <ef-button\n variant=\"comptoir\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"comptoir\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n }\n </ng-template>\n }\n</p-dialog>\n\n<!-- Default-action footer template for the 'primeng' variant (legacy path).\n Declared outside <p-dialog> because the comptoir branch above already\n provides its own content-child footer; this template is only consumed\n when variant === 'primeng' via [footerTemplate] input binding. -->\n<ng-template #defaultFooter>\n <ef-button\n variant=\"primeng\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"primeng\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$6.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
3851
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDialogComponent, isStandalone: true, selector: "ef-dialog", inputs: { header: "header", headerKey: "headerKey", subtitle: "subtitle", subtitleKey: "subtitleKey", visible: ["visible", "visible", booleanAttribute], modal: ["modal", "modal", booleanAttribute], closable: ["closable", "closable", booleanAttribute], draggable: ["draggable", "draggable", booleanAttribute], resizable: ["resizable", "resizable", booleanAttribute], dismissableMask: ["dismissableMask", "dismissableMask", booleanAttribute], closeOnEscape: ["closeOnEscape", "closeOnEscape", booleanAttribute], severity: "severity", icon: "icon", size: "size", style: "style", styleClass: "styleClass", position: "position", appendTo: "appendTo", variant: "variant", actions: "actions", defaultActions: ["defaultActions", "defaultActions", booleanAttribute], saveLabelKey: "saveLabelKey", cancelLabelKey: "cancelLabelKey", saveLabel: "saveLabel", cancelLabel: "cancelLabel", saveIcon: "saveIcon", cancelIcon: "cancelIcon", saveDisabled: ["saveDisabled", "saveDisabled", booleanAttribute], saveStyleClass: "saveStyleClass", cancelStyleClass: "cancelStyleClass", saveSeverity: "saveSeverity", cancelSeverity: "cancelSeverity" }, outputs: { visibleChange: "visibleChange", showEvent: "showEvent", hideEvent: "hideEvent", saveAction: "saveAction", cancelAction: "cancelAction", actionClick: "actionClick" }, viewQueries: [{ propertyName: "defaultFooterTpl", first: true, predicate: ["defaultFooter"], descendants: true, static: true }, { propertyName: "dialog", first: true, predicate: Dialog, descendants: true }], ngImport: i0, template: "<p-dialog\n [visible]=\"visible\"\n (visibleChange)=\"handleVisibleChange($event)\"\n [modal]=\"modal\"\n [draggable]=\"draggable\"\n [resizable]=\"resizable\"\n [dismissableMask]=\"dismissableMask\"\n [closeOnEscape]=\"closeOnEscape\"\n [focusOnShow]=\"!hasAutofocusAction\"\n [style]=\"style\"\n [styleClass]=\"effectiveStyleClass\"\n [position]=\"position\"\n [appendTo]=\"appendTo\"\n [header]=\"variant === 'primeng' ? effectiveTitle : undefined\"\n [closable]=\"variant === 'primeng' ? closable : false\"\n [footerTemplate]=\"variant === 'primeng' && !useDynamicActions && defaultActions ? defaultFooterTpl : undefined\"\n (onShow)=\"handleShow()\"\n (onHide)=\"handleHide()\"\n>\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR HEADER (content-child #header) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Replaces PrimeNG's default header strip with our icon + title\n + subtitle + close X structure. Skipped in 'primeng' variant\n (which uses the [header] string input + native close). -->\n @if (variant === 'comptoir') {\n <!-- PrimeNG points the dialog's aria-labelledby at an id it passes to\n this template; the title takes it, so the dialog is named. -->\n <ng-template #header let-labelledBy=\"ariaLabelledBy\">\n @if (effectiveIcon) {\n <span class=\"es-dialog__icon\" aria-hidden=\"true\">\n <i [class]=\"effectiveIcon\"></i>\n </span>\n }\n <div class=\"es-dialog__title-block\">\n <div class=\"es-dialog__title\" [attr.id]=\"labelledBy\">{{ effectiveTitle }}</div>\n @if (effectiveSubtitle) {\n <div class=\"es-dialog__sub\">{{ effectiveSubtitle }}</div>\n }\n </div>\n @if (closable) {\n <button\n type=\"button\"\n class=\"es-dialog__close\"\n [attr.aria-label]=\"'common_close' | translate\"\n (click)=\"handleVisibleChange(false)\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n }\n </ng-template>\n }\n\n <!-- Body \u2014 projected from <ef-dialog>\u2026</ef-dialog> -->\n <ng-content />\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR FOOTER (content-child #footer) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Dynamic [actions] path takes precedence; falls back to the\n backward-compat save+cancel pair when defaultActions is on. -->\n @if (variant === 'comptoir' && hasFooter) {\n <ng-template #footer>\n @if (useDynamicActions) {\n @for (a of actions; track $index) {\n <button\n type=\"button\"\n [class]=\"actionClass(a)\"\n [disabled]=\"a.disabled || a.loading\"\n [attr.data-ef-autofocus]=\"a.autofocus ? '' : null\"\n (click)=\"handleActionClick(a, $index)\"\n >\n @if (a.icon) {\n <i [class]=\"a.icon\"></i>\n }\n {{ actionLabel(a) }}\n </button>\n }\n } @else {\n <ef-button\n variant=\"comptoir\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"comptoir\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n }\n </ng-template>\n }\n</p-dialog>\n\n<!-- Default-action footer template for the 'primeng' variant (legacy path).\n Declared outside <p-dialog> because the comptoir branch above already\n provides its own content-child footer; this template is only consumed\n when variant === 'primeng' via [footerTemplate] input binding. -->\n<ng-template #defaultFooter>\n <ef-button\n variant=\"primeng\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"primeng\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$6.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
3825
3852
  }
3826
3853
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDialogComponent, decorators: [{
3827
3854
  type: Component,
3828
- args: [{ selector: 'ef-dialog', standalone: true, imports: [CommonModule, DialogModule, TranslateModule, EfButtonComponent], template: "<p-dialog\n [visible]=\"visible\"\n (visibleChange)=\"handleVisibleChange($event)\"\n [modal]=\"modal\"\n [draggable]=\"draggable\"\n [resizable]=\"resizable\"\n [dismissableMask]=\"dismissableMask\"\n [closeOnEscape]=\"closeOnEscape\"\n [focusOnShow]=\"!hasAutofocusAction\"\n [style]=\"style\"\n [styleClass]=\"effectiveStyleClass\"\n [position]=\"position\"\n [appendTo]=\"appendTo\"\n [header]=\"variant === 'primeng' ? effectiveTitle : null\"\n [closable]=\"variant === 'primeng' ? closable : false\"\n [footerTemplate]=\"variant === 'primeng' && !useDynamicActions && defaultActions ? defaultFooterTpl : null\"\n (onShow)=\"handleShow()\"\n (onHide)=\"handleHide()\"\n>\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR HEADER (content-child #header) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Replaces PrimeNG's default header strip with our icon + title\n + subtitle + close X structure. Skipped in 'primeng' variant\n (which uses the [header] string input + native close). -->\n @if (variant === 'comptoir') {\n <!-- PrimeNG points the dialog's aria-labelledby at an id it passes to\n this template; the title takes it, so the dialog is named. -->\n <ng-template #header let-labelledBy=\"ariaLabelledBy\">\n @if (effectiveIcon) {\n <span class=\"es-dialog__icon\" aria-hidden=\"true\">\n <i [class]=\"effectiveIcon\"></i>\n </span>\n }\n <div class=\"es-dialog__title-block\">\n <div class=\"es-dialog__title\" [attr.id]=\"labelledBy\">{{ effectiveTitle }}</div>\n @if (effectiveSubtitle) {\n <div class=\"es-dialog__sub\">{{ effectiveSubtitle }}</div>\n }\n </div>\n @if (closable) {\n <button\n type=\"button\"\n class=\"es-dialog__close\"\n [attr.aria-label]=\"'common_close' | translate\"\n (click)=\"handleVisibleChange(false)\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n }\n </ng-template>\n }\n\n <!-- Body \u2014 projected from <ef-dialog>\u2026</ef-dialog> -->\n <ng-content />\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR FOOTER (content-child #footer) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Dynamic [actions] path takes precedence; falls back to the\n backward-compat save+cancel pair when defaultActions is on. -->\n @if (variant === 'comptoir' && hasFooter) {\n <ng-template #footer>\n @if (useDynamicActions) {\n @for (a of actions; track $index) {\n <button\n type=\"button\"\n [class]=\"actionClass(a)\"\n [disabled]=\"a.disabled || a.loading\"\n [attr.data-ef-autofocus]=\"a.autofocus ? '' : null\"\n (click)=\"handleActionClick(a, $index)\"\n >\n @if (a.icon) {\n <i [class]=\"a.icon\"></i>\n }\n {{ actionLabel(a) }}\n </button>\n }\n } @else {\n <ef-button\n variant=\"comptoir\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"comptoir\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n }\n </ng-template>\n }\n</p-dialog>\n\n<!-- Default-action footer template for the 'primeng' variant (legacy path).\n Declared outside <p-dialog> because the comptoir branch above already\n provides its own content-child footer; this template is only consumed\n when variant === 'primeng' via [footerTemplate] input binding. -->\n<ng-template #defaultFooter>\n <ef-button\n variant=\"primeng\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"primeng\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n</ng-template>\n" }]
3855
+ args: [{ selector: 'ef-dialog', standalone: true, imports: [CommonModule, DialogModule, TranslateModule, EfButtonComponent], template: "<p-dialog\n [visible]=\"visible\"\n (visibleChange)=\"handleVisibleChange($event)\"\n [modal]=\"modal\"\n [draggable]=\"draggable\"\n [resizable]=\"resizable\"\n [dismissableMask]=\"dismissableMask\"\n [closeOnEscape]=\"closeOnEscape\"\n [focusOnShow]=\"!hasAutofocusAction\"\n [style]=\"style\"\n [styleClass]=\"effectiveStyleClass\"\n [position]=\"position\"\n [appendTo]=\"appendTo\"\n [header]=\"variant === 'primeng' ? effectiveTitle : undefined\"\n [closable]=\"variant === 'primeng' ? closable : false\"\n [footerTemplate]=\"variant === 'primeng' && !useDynamicActions && defaultActions ? defaultFooterTpl : undefined\"\n (onShow)=\"handleShow()\"\n (onHide)=\"handleHide()\"\n>\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR HEADER (content-child #header) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Replaces PrimeNG's default header strip with our icon + title\n + subtitle + close X structure. Skipped in 'primeng' variant\n (which uses the [header] string input + native close). -->\n @if (variant === 'comptoir') {\n <!-- PrimeNG points the dialog's aria-labelledby at an id it passes to\n this template; the title takes it, so the dialog is named. -->\n <ng-template #header let-labelledBy=\"ariaLabelledBy\">\n @if (effectiveIcon) {\n <span class=\"es-dialog__icon\" aria-hidden=\"true\">\n <i [class]=\"effectiveIcon\"></i>\n </span>\n }\n <div class=\"es-dialog__title-block\">\n <div class=\"es-dialog__title\" [attr.id]=\"labelledBy\">{{ effectiveTitle }}</div>\n @if (effectiveSubtitle) {\n <div class=\"es-dialog__sub\">{{ effectiveSubtitle }}</div>\n }\n </div>\n @if (closable) {\n <button\n type=\"button\"\n class=\"es-dialog__close\"\n [attr.aria-label]=\"'common_close' | translate\"\n (click)=\"handleVisibleChange(false)\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n }\n </ng-template>\n }\n\n <!-- Body \u2014 projected from <ef-dialog>\u2026</ef-dialog> -->\n <ng-content />\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 COMPTOIR FOOTER (content-child #footer) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Dynamic [actions] path takes precedence; falls back to the\n backward-compat save+cancel pair when defaultActions is on. -->\n @if (variant === 'comptoir' && hasFooter) {\n <ng-template #footer>\n @if (useDynamicActions) {\n @for (a of actions; track $index) {\n <button\n type=\"button\"\n [class]=\"actionClass(a)\"\n [disabled]=\"a.disabled || a.loading\"\n [attr.data-ef-autofocus]=\"a.autofocus ? '' : null\"\n (click)=\"handleActionClick(a, $index)\"\n >\n @if (a.icon) {\n <i [class]=\"a.icon\"></i>\n }\n {{ actionLabel(a) }}\n </button>\n }\n } @else {\n <ef-button\n variant=\"comptoir\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"comptoir\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n }\n </ng-template>\n }\n</p-dialog>\n\n<!-- Default-action footer template for the 'primeng' variant (legacy path).\n Declared outside <p-dialog> because the comptoir branch above already\n provides its own content-child footer; this template is only consumed\n when variant === 'primeng' via [footerTemplate] input binding. -->\n<ng-template #defaultFooter>\n <ef-button\n variant=\"primeng\"\n [severity]=\"cancelSeverity\"\n [labelKey]=\"cancelLabelKey\"\n [label]=\"cancelLabel\"\n [icon]=\"cancelIcon\"\n [styleClass]=\"cancelStyleClass\"\n (clickEvent)=\"onDefaultCancel()\"\n />\n <ef-button\n variant=\"primeng\"\n [severity]=\"saveSeverity\"\n [labelKey]=\"saveLabelKey\"\n [label]=\"saveLabel\"\n [icon]=\"saveIcon\"\n [styleClass]=\"saveStyleClass\"\n [disabled]=\"saveDisabled\"\n (clickEvent)=\"onDefaultSave()\"\n />\n</ng-template>\n" }]
3829
3856
  }], propDecorators: { header: [{
3830
3857
  type: Input
3831
3858
  }], headerKey: [{
@@ -4064,6 +4091,11 @@ class EfProductTypeaheadComponent {
4064
4091
  * variant + unit price) is identical either way.
4065
4092
  */
4066
4093
  showVariants = input(true, { ...(ngDevMode ? { debugName: "showVariants" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4094
+ /**
4095
+ * Which catalogue price each row shows and its selection carries: the
4096
+ * wholesale price (default) or the shop price for a Web order.
4097
+ */
4098
+ priceList = input('wholesale', ...(ngDevMode ? [{ debugName: "priceList" }] : /* istanbul ignore next */ []));
4067
4099
  productSelect = output();
4068
4100
  query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
4069
4101
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
@@ -4170,11 +4202,7 @@ class EfProductTypeaheadComponent {
4170
4202
  return row.variants[this.variantIndexOf(row)] ?? null;
4171
4203
  }
4172
4204
  priceOf(row) {
4173
- const variant = this.variantOf(row);
4174
- return (variant?.['price'] ??
4175
- row.product['unitPrice'] ??
4176
- row.product['salePrice'] ??
4177
- 0);
4205
+ return defaultUnitPrice(row.product, this.variantOf(row), this.priceList());
4178
4206
  }
4179
4207
  setActiveVariant(row, index) {
4180
4208
  this.activeVariant.update((m) => ({ ...m, [row.key]: index }));
@@ -4203,12 +4231,12 @@ class EfProductTypeaheadComponent {
4203
4231
  this.activeVariant.set({});
4204
4232
  }
4205
4233
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfProductTypeaheadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4206
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfProductTypeaheadComponent, isStandalone: true, selector: "ef-product-typeahead", inputs: { products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, showVariants: { classPropertyName: "showVariants", publicName: "showVariants", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { productSelect: "productSelect" }, ngImport: i0, template: "<div class=\"qa-combo\">\n <svg class=\"ic\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" /><path d=\"m20 20-3-3\" />\n </svg>\n <input\n class=\"input\"\n type=\"text\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-controls=\"qa-listbox\"\n [attr.aria-expanded]=\"open() && rows().length > 0\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholderKey() ? (placeholderKey() | translate) : ''\"\n [ngModel]=\"query()\"\n (ngModelChange)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (focus)=\"open.set(true)\"\n (blur)=\"open.set(false)\" />\n\n @if (query() && !disabled()) {\n <button\n type=\"button\"\n class=\"qa-clear\"\n [attr.aria-label]=\"'action.clear' | translate\"\n (mousedown)=\"$event.preventDefault(); reset()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n }\n\n @if (open() && rows().length > 0) {\n <div id=\"qa-listbox\" class=\"qa-menu\" role=\"listbox\">\n <div class=\"grp\">{{ rows().length }} \u00B7 \u00AB {{ query() }} \u00BB</div>\n @for (row of rows(); track row.key; let i = $index) {\n <div\n class=\"qa-opt\"\n role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n [class.hl]=\"i === highlightedIndex()\"\n (mouseenter)=\"highlightedIndex.set(i)\"\n (mousedown)=\"$event.preventDefault(); selectRow(row)\">\n <span class=\"nm\">\n @for (seg of row.segments; track $index) {\n @if (seg.mark) { <mark>{{ seg.text }}</mark> } @else { {{ seg.text }} }\n }\n </span>\n <span class=\"var\">\n @if (showVariants()) {\n @for (variant of row.variants; track variant['variantId']; let vi = $index) {\n <span\n class=\"qa-chip\"\n [class.on]=\"vi === variantIndexOf(row)\"\n (mousedown)=\"$event.preventDefault(); $event.stopPropagation(); setActiveVariant(row, vi)\">\n {{ variant['title'] }}\n </span>\n }\n }\n </span>\n <span class=\"px\">{{ priceOf(row) | number: '1.2-2' : locale() }}</span>\n </div>\n }\n <div class=\"qa-foot\">\n <span class=\"qa-hint\">\n <span class=\"kbd\">\u2191</span><span class=\"kbd\">\u2193</span>\n {{ 'order_builder.qa_navigate' | translate }} \u00B7\n <span class=\"kbd\">\u21B5</span> {{ 'order_builder.qa_add' | translate }}\n </span>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;position:relative}.qa-combo{position:relative}.qa-combo .input{width:100%;box-sizing:border-box;height:var(--hit-field);padding:0 36px 0 38px;border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper-alt);color:var(--text);font:inherit;font-size:14px;line-height:1.2;outline:0}.qa-combo .input:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}.qa-combo .ic{position:absolute;inset-inline-start:13px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--text-soft);pointer-events:none}.qa-combo .qa-clear{position:absolute;inset-inline-end:8px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:0;border-radius:var(--r-pill);background:transparent;color:var(--text-soft);cursor:pointer}.qa-combo .qa-clear svg{width:14px;height:14px}.qa-combo .qa-clear:hover{background:var(--paper);color:var(--text)}.qa-menu{position:absolute;top:calc(100% + 6px);inset-inline:0;z-index:40;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);padding:6px;max-height:320px;overflow-y:auto;overflow-x:hidden}.grp{font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft);padding:8px 10px 4px}.qa-opt{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px;align-items:center;padding:8px 10px;border-radius:var(--r-md);cursor:pointer}.qa-opt:hover,.qa-opt.hl{background:color-mix(in srgb,var(--tenant-50) 75%,var(--paper-alt))}.qa-opt .nm{font-size:13px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qa-opt .nm mark{background:var(--tenant-100);color:var(--tenant-700);border-radius:3px;padding:0 1px;font-weight:800}.qa-opt .var{display:inline-flex;gap:4px;flex-shrink:0}.qa-opt .px{font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--text)}.qa-chip{font-family:var(--font-mono);font-size:10.5px;font-weight:700;padding:3px 7px;border-radius:var(--r-pill);border:1px solid var(--rule);color:var(--text-mute);background:var(--paper);cursor:pointer}.qa-chip.on{background:var(--tenant-500);border-color:var(--tenant-500);color:#fff}.qa-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px 4px;border-top:1px solid var(--rule-soft);margin-top:4px}.qa-hint{font-size:11px;color:var(--text-mute);display:inline-flex;align-items:center;gap:6px}.kbd{font-family:var(--font-mono);font-size:10px;font-weight:700;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:5px;padding:2px 6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$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: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4234
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfProductTypeaheadComponent, isStandalone: true, selector: "ef-product-typeahead", inputs: { products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, showVariants: { classPropertyName: "showVariants", publicName: "showVariants", isSignal: true, isRequired: false, transformFunction: null }, priceList: { classPropertyName: "priceList", publicName: "priceList", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { productSelect: "productSelect" }, ngImport: i0, template: "<div class=\"qa-combo\">\n <svg class=\"ic\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" /><path d=\"m20 20-3-3\" />\n </svg>\n <input\n class=\"input\"\n type=\"text\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-controls=\"qa-listbox\"\n [attr.aria-expanded]=\"open() && rows().length > 0\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholderKey() ? (placeholderKey() | translate) : ''\"\n [ngModel]=\"query()\"\n (ngModelChange)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (focus)=\"open.set(true)\"\n (blur)=\"open.set(false)\" />\n\n @if (query() && !disabled()) {\n <button\n type=\"button\"\n class=\"qa-clear\"\n [attr.aria-label]=\"'action.clear' | translate\"\n (mousedown)=\"$event.preventDefault(); reset()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n }\n\n @if (open() && rows().length > 0) {\n <div id=\"qa-listbox\" class=\"qa-menu\" role=\"listbox\">\n <div class=\"grp\">{{ rows().length }} \u00B7 \u00AB {{ query() }} \u00BB</div>\n @for (row of rows(); track row.key; let i = $index) {\n <div\n class=\"qa-opt\"\n role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n [class.hl]=\"i === highlightedIndex()\"\n (mouseenter)=\"highlightedIndex.set(i)\"\n (mousedown)=\"$event.preventDefault(); selectRow(row)\">\n <span class=\"nm\">\n @for (seg of row.segments; track $index) {\n @if (seg.mark) { <mark>{{ seg.text }}</mark> } @else { {{ seg.text }} }\n }\n </span>\n <span class=\"var\">\n @if (showVariants()) {\n @for (variant of row.variants; track variant['variantId']; let vi = $index) {\n <span\n class=\"qa-chip\"\n [class.on]=\"vi === variantIndexOf(row)\"\n (mousedown)=\"$event.preventDefault(); $event.stopPropagation(); setActiveVariant(row, vi)\">\n {{ variant['title'] }}\n </span>\n }\n }\n </span>\n <span class=\"px\">{{ priceOf(row) | number: '1.2-2' : locale() }}</span>\n </div>\n }\n <div class=\"qa-foot\">\n <span class=\"qa-hint\">\n <span class=\"kbd\">\u2191</span><span class=\"kbd\">\u2193</span>\n {{ 'order_builder.qa_navigate' | translate }} \u00B7\n <span class=\"kbd\">\u21B5</span> {{ 'order_builder.qa_add' | translate }}\n </span>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;position:relative}.qa-combo{position:relative}.qa-combo .input{width:100%;box-sizing:border-box;height:var(--hit-field);padding:0 36px 0 38px;border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper-alt);color:var(--text);font:inherit;font-size:14px;line-height:1.2;outline:0}.qa-combo .input:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}.qa-combo .ic{position:absolute;inset-inline-start:13px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--text-soft);pointer-events:none}.qa-combo .qa-clear{position:absolute;inset-inline-end:8px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:0;border-radius:var(--r-pill);background:transparent;color:var(--text-soft);cursor:pointer}.qa-combo .qa-clear svg{width:14px;height:14px}.qa-combo .qa-clear:hover{background:var(--paper);color:var(--text)}.qa-menu{position:absolute;top:calc(100% + 6px);inset-inline:0;z-index:40;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);padding:6px;max-height:320px;overflow-y:auto;overflow-x:hidden}.grp{font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft);padding:8px 10px 4px}.qa-opt{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px;align-items:center;padding:8px 10px;border-radius:var(--r-md);cursor:pointer}.qa-opt:hover,.qa-opt.hl{background:color-mix(in srgb,var(--tenant-50) 75%,var(--paper-alt))}.qa-opt .nm{font-size:13px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qa-opt .nm mark{background:var(--tenant-100);color:var(--tenant-700);border-radius:3px;padding:0 1px;font-weight:800}.qa-opt .var{display:inline-flex;gap:4px;flex-shrink:0}.qa-opt .px{font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--text)}.qa-chip{font-family:var(--font-mono);font-size:10.5px;font-weight:700;padding:3px 7px;border-radius:var(--r-pill);border:1px solid var(--rule);color:var(--text-mute);background:var(--paper);cursor:pointer}.qa-chip.on{background:var(--tenant-500);border-color:var(--tenant-500);color:#fff}.qa-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px 4px;border-top:1px solid var(--rule-soft);margin-top:4px}.qa-hint{font-size:11px;color:var(--text-mute);display:inline-flex;align-items:center;gap:6px}.kbd{font-family:var(--font-mono);font-size:10px;font-weight:700;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:5px;padding:2px 6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$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: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4207
4235
  }
4208
4236
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfProductTypeaheadComponent, decorators: [{
4209
4237
  type: Component,
4210
4238
  args: [{ selector: 'ef-product-typeahead', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, TranslateModule], template: "<div class=\"qa-combo\">\n <svg class=\"ic\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" /><path d=\"m20 20-3-3\" />\n </svg>\n <input\n class=\"input\"\n type=\"text\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-controls=\"qa-listbox\"\n [attr.aria-expanded]=\"open() && rows().length > 0\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholderKey() ? (placeholderKey() | translate) : ''\"\n [ngModel]=\"query()\"\n (ngModelChange)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (focus)=\"open.set(true)\"\n (blur)=\"open.set(false)\" />\n\n @if (query() && !disabled()) {\n <button\n type=\"button\"\n class=\"qa-clear\"\n [attr.aria-label]=\"'action.clear' | translate\"\n (mousedown)=\"$event.preventDefault(); reset()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n }\n\n @if (open() && rows().length > 0) {\n <div id=\"qa-listbox\" class=\"qa-menu\" role=\"listbox\">\n <div class=\"grp\">{{ rows().length }} \u00B7 \u00AB {{ query() }} \u00BB</div>\n @for (row of rows(); track row.key; let i = $index) {\n <div\n class=\"qa-opt\"\n role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n [class.hl]=\"i === highlightedIndex()\"\n (mouseenter)=\"highlightedIndex.set(i)\"\n (mousedown)=\"$event.preventDefault(); selectRow(row)\">\n <span class=\"nm\">\n @for (seg of row.segments; track $index) {\n @if (seg.mark) { <mark>{{ seg.text }}</mark> } @else { {{ seg.text }} }\n }\n </span>\n <span class=\"var\">\n @if (showVariants()) {\n @for (variant of row.variants; track variant['variantId']; let vi = $index) {\n <span\n class=\"qa-chip\"\n [class.on]=\"vi === variantIndexOf(row)\"\n (mousedown)=\"$event.preventDefault(); $event.stopPropagation(); setActiveVariant(row, vi)\">\n {{ variant['title'] }}\n </span>\n }\n }\n </span>\n <span class=\"px\">{{ priceOf(row) | number: '1.2-2' : locale() }}</span>\n </div>\n }\n <div class=\"qa-foot\">\n <span class=\"qa-hint\">\n <span class=\"kbd\">\u2191</span><span class=\"kbd\">\u2193</span>\n {{ 'order_builder.qa_navigate' | translate }} \u00B7\n <span class=\"kbd\">\u21B5</span> {{ 'order_builder.qa_add' | translate }}\n </span>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;position:relative}.qa-combo{position:relative}.qa-combo .input{width:100%;box-sizing:border-box;height:var(--hit-field);padding:0 36px 0 38px;border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper-alt);color:var(--text);font:inherit;font-size:14px;line-height:1.2;outline:0}.qa-combo .input:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}.qa-combo .ic{position:absolute;inset-inline-start:13px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--text-soft);pointer-events:none}.qa-combo .qa-clear{position:absolute;inset-inline-end:8px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:0;border-radius:var(--r-pill);background:transparent;color:var(--text-soft);cursor:pointer}.qa-combo .qa-clear svg{width:14px;height:14px}.qa-combo .qa-clear:hover{background:var(--paper);color:var(--text)}.qa-menu{position:absolute;top:calc(100% + 6px);inset-inline:0;z-index:40;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl);box-shadow:var(--shadow-3);padding:6px;max-height:320px;overflow-y:auto;overflow-x:hidden}.grp{font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--text-soft);padding:8px 10px 4px}.qa-opt{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px;align-items:center;padding:8px 10px;border-radius:var(--r-md);cursor:pointer}.qa-opt:hover,.qa-opt.hl{background:color-mix(in srgb,var(--tenant-50) 75%,var(--paper-alt))}.qa-opt .nm{font-size:13px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.qa-opt .nm mark{background:var(--tenant-100);color:var(--tenant-700);border-radius:3px;padding:0 1px;font-weight:800}.qa-opt .var{display:inline-flex;gap:4px;flex-shrink:0}.qa-opt .px{font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--text)}.qa-chip{font-family:var(--font-mono);font-size:10.5px;font-weight:700;padding:3px 7px;border-radius:var(--r-pill);border:1px solid var(--rule);color:var(--text-mute);background:var(--paper);cursor:pointer}.qa-chip.on{background:var(--tenant-500);border-color:var(--tenant-500);color:#fff}.qa-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px 4px;border-top:1px solid var(--rule-soft);margin-top:4px}.qa-hint{font-size:11px;color:var(--text-mute);display:inline-flex;align-items:center;gap:6px}.kbd{font-family:var(--font-mono);font-size:10px;font-weight:700;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:5px;padding:2px 6px}\n"] }]
4211
- }], propDecorators: { products: [{ type: i0.Input, args: [{ isSignal: true, alias: "products", required: true }] }], productKeyField: [{ type: i0.Input, args: [{ isSignal: true, alias: "productKeyField", required: false }] }], placeholderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKey", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], showVariants: [{ type: i0.Input, args: [{ isSignal: true, alias: "showVariants", required: false }] }], productSelect: [{ type: i0.Output, args: ["productSelect"] }] } });
4239
+ }], propDecorators: { products: [{ type: i0.Input, args: [{ isSignal: true, alias: "products", required: true }] }], productKeyField: [{ type: i0.Input, args: [{ isSignal: true, alias: "productKeyField", required: false }] }], placeholderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKey", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], showVariants: [{ type: i0.Input, args: [{ isSignal: true, alias: "showVariants", required: false }] }], priceList: [{ type: i0.Input, args: [{ isSignal: true, alias: "priceList", required: false }] }], productSelect: [{ type: i0.Output, args: ["productSelect"] }] } });
4212
4240
 
4213
4241
  /**
4214
4242
  * App-wide default, so screens don't drift apart. Provide it once at the
@@ -6117,10 +6145,40 @@ class EfOrderBuilderComponent {
6117
6145
  order = model.required(...(ngDevMode ? [{ debugName: "order" }] : /* istanbul ignore next */ []));
6118
6146
  products = input.required(...(ngDevMode ? [{ debugName: "products" }] : /* istanbul ignore next */ []));
6119
6147
  customers = input.required(...(ngDevMode ? [{ debugName: "customers" }] : /* istanbul ignore next */ []));
6148
+ /**
6149
+ * Who the order goes to when it has no client to pick (a storefront order).
6150
+ * When set, the header shows it read-only instead of the customer select,
6151
+ * `customers` may be `[]` and the order validates without a `customerId`.
6152
+ * Null keeps the customer select and its required rule.
6153
+ */
6154
+ recipient = input(null, ...(ngDevMode ? [{ debugName: "recipient" }] : /* istanbul ignore next */ []));
6155
+ /**
6156
+ * Calling code (`'212'` or `'+212'`) for a national recipient phone, so the
6157
+ * `tel:` link dials `+212612345678` for `0612345678` from anywhere.
6158
+ */
6159
+ defaultCallingCode = input('', ...(ngDevMode ? [{ debugName: "defaultCallingCode" }] : /* istanbul ignore next */ []));
6160
+ /**
6161
+ * `tel:` target for the recipient's phone: E.164 when it can be made, else
6162
+ * the digits as typed (a national number still dials inside the country).
6163
+ */
6164
+ recipientTel = computed(() => {
6165
+ const phone = this.recipient()?.phone ?? '';
6166
+ const e164 = PhoneUtils.toE164(phone, this.defaultCallingCode());
6167
+ if (e164)
6168
+ return `tel:${e164}`;
6169
+ const digits = phone.replace(/\D/g, '');
6170
+ return digits ? `tel:${digits}` : null;
6171
+ }, ...(ngDevMode ? [{ debugName: "recipientTel" }] : /* istanbul ignore next */ []));
6120
6172
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
6121
6173
  countGroupLabels = input([], ...(ngDevMode ? [{ debugName: "countGroupLabels" }] : /* istanbul ignore next */ []));
6122
6174
  /** Field name used to identify products (must match catalogue's trackByField) */
6123
6175
  productKeyField = input('id', ...(ngDevMode ? [{ debugName: "productKeyField" }] : /* istanbul ignore next */ []));
6176
+ /**
6177
+ * Which catalogue price a new line starts at, in quick-add, its typeahead
6178
+ * and the catalogue: `'wholesale'` (default, B2B sales) or `'retail'`, the
6179
+ * shop price a Web order is charged. Lines already on the order keep theirs.
6180
+ */
6181
+ priceList = input('wholesale', ...(ngDevMode ? [{ debugName: "priceList" }] : /* istanbul ignore next */ []));
6124
6182
  backendErrors = input({}, ...(ngDevMode ? [{ debugName: "backendErrors" }] : /* istanbul ignore next */ []));
6125
6183
  /**
6126
6184
  * Delivery amount (TTC) the host owns. It is the builder's only fee source:
@@ -6130,7 +6188,14 @@ class EfOrderBuilderComponent {
6130
6188
  * non-numeric fee is treated as 0.
6131
6189
  */
6132
6190
  shippingTotal = input(0, { ...(ngDevMode ? { debugName: "shippingTotal" } : /* istanbul ignore next */ {}), transform: toFee });
6133
- /** Optional change-history entries; when non-empty the Historique rail card renders. */
6191
+ /**
6192
+ * Optional change-history entries; when non-empty the Historique rail card renders.
6193
+ *
6194
+ * Host cards that belong in the rail (a delivery card, internal notes) are
6195
+ * projected with the `obRail` attribute: `<ef-card obRail ...>`. They sit
6196
+ * after the totals and the recap and before the history, as a column at
6197
+ * desktop width and inside the drawer below that or in focus mode.
6198
+ */
6134
6199
  auditEntries = input([], ...(ngDevMode ? [{ debugName: "auditEntries" }] : /* istanbul ignore next */ []));
6135
6200
  openCatalogue = output();
6136
6201
  orderChanged = output();
@@ -6216,7 +6281,13 @@ class EfOrderBuilderComponent {
6216
6281
  return { items, totalCount };
6217
6282
  }, ...(ngDevMode ? [{ debugName: "productsSummary" }] : /* istanbul ignore next */ []));
6218
6283
  validationResult = computed(() => {
6219
- return OrderEntityHelper.validate(this.order());
6284
+ const result = OrderEntityHelper.validate(this.order());
6285
+ if (!this.recipient() || !result.errors['customerId'])
6286
+ return result;
6287
+ // An order with a recipient has no client to pick: drop that one rule.
6288
+ const errors = { ...result.errors };
6289
+ delete errors['customerId'];
6290
+ return { ...result, errors, isValid: Object.keys(errors).length === 0 };
6220
6291
  }, ...(ngDevMode ? [{ debugName: "validationResult" }] : /* istanbul ignore next */ []));
6221
6292
  combinedErrors = computed(() => {
6222
6293
  const clientErrors = this.validationResult().errors;
@@ -6443,7 +6514,7 @@ class EfOrderBuilderComponent {
6443
6514
  return;
6444
6515
  }
6445
6516
  const variantId = selection.variant?.['variantId'] ?? undefined;
6446
- const newItem = OrderLineItemHelper.createFromProduct(selection.product, this.quickAddQuantity(), this.productKeyField(), variantId);
6517
+ const newItem = OrderLineItemHelper.createFromProduct(selection.product, this.quickAddQuantity(), this.productKeyField(), variantId, this.priceList());
6447
6518
  const overridden = OrderLineItemHelper.updateCalculations({
6448
6519
  ...newItem,
6449
6520
  productUnitPrice: this.quickAddPrice(),
@@ -6481,7 +6552,7 @@ class EfOrderBuilderComponent {
6481
6552
  currentItems[existingIndex] = updated;
6482
6553
  }
6483
6554
  else {
6484
- const newItem = OrderLineItemHelper.createFromProduct(product, quantity, keyField, variantId);
6555
+ const newItem = OrderLineItemHelper.createFromProduct(product, quantity, keyField, variantId, this.priceList());
6485
6556
  currentItems.push(newItem);
6486
6557
  addedItems.push(newItem);
6487
6558
  }
@@ -6603,7 +6674,7 @@ class EfOrderBuilderComponent {
6603
6674
  return OrderEntityHelper.calculateTotals(fee > 0 ? { ...order, shippingTotal: fee } : order);
6604
6675
  }
6605
6676
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6606
- 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 }, shippingTotal: { classPropertyName: "shippingTotal", publicName: "shippingTotal", 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 <!-- Ghost while the preconditions are unmet, tenant once the\n line is addable. A disabled control painted in a faded\n tenant fill reads as an enabled button that lost its\n colour, which is the greyed-out accent the design brief\n bans; ghost reads as \"not yet\". -->\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n size=\"base\"\n [severity]=\"quickAddDisabled() ? 'ghost' : 'tenant'\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines.\n\n ef-data-card owns the panel here instead of sitting inside an\n ef-card: `.tbl-wrap` already draws a bordered, rounded surface, so\n nesting it in another one would be a card inside a card. The title\n and the card's actions move into the head slots it provides.\n\n The columns are declared in TS (`lineColumns`) so discount and tax\n follow the document config, and so one definition drives both the\n desktop table and the mobile cards. Only the article column is\n elastic; every other column holds a number of known magnitude and\n gets a fixed track, which keeps the decimals aligned down the\n table rather than shifting with the longest description. -->\n <ef-data-card\n [columns]=\"lineColumns()\"\n [rows]=\"orderLines()\"\n trackByField=\"id\"\n [hidePager]=\"true\"\n defaultDensity=\"compact\"\n [reorderable]=\"!readonly()\"\n [fixedLayout]=\"true\"\n (rowReorder)=\"reorderLine($event)\"\n mobileLayout=\"card\">\n\n <div tbl-head-info class=\"ob-lines-head\">\n <span class=\"strong\">{{ 'order_builder.lines' | translate }}</span>\n <span class=\"meta\">\u00B7 {{ orderLines().length }}</span>\n </div>\n\n <div tbl-head-actions 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 <span empty-state>{{ 'order_builder.empty' | translate }}</span>\n <span empty-state-mobile>{{ 'order_builder.empty' | translate }}</span>\n\n <ng-template efColumnTemplate=\"product\" let-item let-i=\"index\">\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 </ng-template>\n\n <ng-template efColumnTemplate=\"price\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"qty\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"discount\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"tax\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"subtotal\" let-item>\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </ng-template>\n\n <ng-template efColumnTemplate=\"remove\" let-item>\n <ef-button\n icon=\"pi pi-trash\"\n ariaLabelKey=\"common_delete\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n </ng-template>\n </ef-data-card>\n\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 @if (shippingTotal() > 0) {\n <div class=\"row shipping\">\n <span class=\"lbl\">{{ 'order_builder.shipping' | translate }}</span>\n <span class=\"v\">{{ shippingTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n }\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: ["@charset \"UTF-8\";: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 .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 .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 ef-data-card{font-size:13px}:host .order-builder ef-data-card .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder ef-data-card .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder ef-data-card .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder ef-data-card .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder ef-data-card .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder ef-data-card ef-inputnumber,:host .order-builder ef-data-card ef-inputnumber .ef-field-control,:host .order-builder ef-data-card ef-inputnumber input{width:100%;min-width:0}:host .order-builder ef-data-card .num,:host .order-builder ef-data-card .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder ef-data-card .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);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 ef-data-card .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder ef-data-card .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder ef-data-card .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder ef-data-card .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder ef-data-card .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-lines-head{display:flex;align-items:baseline;gap:6px;min-width:0}:host .order-builder .ob-lines-head .strong{font-weight:700;color:var(--text)}:host .order-builder .ob-lines-head .meta{color:var(--text-soft)}: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:var(--font-mono);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:var(--font-display);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:var(--font-display);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:var(--font-mono);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:var(--font-mono);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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:calc(var(--app-bottom-bar, 0px) + env(safe-area-inset-bottom,0px) + 20px);z-index:30;align-items:center;gap:10px;box-sizing:border-box;block-size:var(--ef-order-fab-h);--ob-fab-ground: var(--ink-active);--ob-fab-on: var(--paper);background:var(--ob-fab-ground);color:var(--ob-fab-on);border-radius:var(--r-pill);padding-block:0;padding-inline:16px 10px;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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:color-mix(in srgb,var(--ob-fab-on) 14%,transparent);color:var(--ob-fab-on);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:color-mix(in srgb,var(--ob-fab-on) 24%,transparent)}:host .order-builder .ob-fab .open:focus-visible{outline:2px solid var(--ob-fab-on);outline-offset:2px}: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)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}: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)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host ef-data-card .nm,:host ef-data-card .num,:host ef-data-card .ln-val,:host .ob-totals .v{unicode-bidi:plaintext}@media(pointer:coarse){:host .order-builder .ob-fab .open{min-block-size:var(--hit-touch)}:host ef-data-card .ln-val{min-block-size:var(--hit-field)}}\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", "autofocus", "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", "allowAllTime", "mode", "selectionMode", "variant", "showTime", "valueFormat"], 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", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], 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: "component", type: EfDataCardComponent, selector: "ef-data-card", inputs: ["mobileLayout", "columns", "rows", "trackByField", "pageNumber", "pageSize", "totalCount", "pageSizeOptions", "hidePager", "sort", "loading", "errorMsg", "loadingKey", "rowDoubleClickable", "rowPressed", "rowDisabled", "hasActionsColumn", "rowActionsContext", "showViewAction", "showEditAction", "showDuplicateAction", "showDeleteAction", "rowActionState", "showDensityControl", "showColumnPicker", "showExportControl", "exporting", "screenContext", "searchTerm", "tableKey", "defaultDensity", "reorderable", "fixedLayout"], outputs: ["pageChange", "pageSizeChange", "sortChange", "rowDoubleClick", "rowAction", "exportRequest", "clearSearch", "retry", "rowReorder"] }, { kind: "directive", type: EfColumnTemplateDirective, selector: "[efColumnTemplate]", inputs: ["efColumnTemplate"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
6677
+ 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 }, recipient: { classPropertyName: "recipient", publicName: "recipient", isSignal: true, isRequired: false, transformFunction: null }, defaultCallingCode: { classPropertyName: "defaultCallingCode", publicName: "defaultCallingCode", isSignal: true, isRequired: false, 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 }, priceList: { classPropertyName: "priceList", publicName: "priceList", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, shippingTotal: { classPropertyName: "shippingTotal", publicName: "shippingTotal", 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 @if (recipient(); as r) {\n <!-- No client to pick (a storefront order): who it goes to,\n read-only, in the customer's place. -->\n <div class=\"field col-6\">\n <ef-label labelKey=\"order_builder.recipient\" />\n <div\n class=\"ob-recipient\"\n role=\"group\"\n [attr.aria-label]=\"'order_builder.recipient' | translate\">\n <span class=\"ob-recipient-name\">{{ r.name }}</span>\n @if (recipientTel(); as tel) {\n <a class=\"ob-recipient-phone\" [href]=\"tel\" dir=\"ltr\">{{ r.phone }}</a>\n }\n @if (r.city) {\n <span class=\"ob-recipient-city\">{{ r.city }}</span>\n }\n @if (r.address) {\n <span class=\"ob-recipient-address\" [title]=\"r.address\">{{ r.address }}</span>\n }\n </div>\n </div>\n } @else {\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 }\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 [priceList]=\"priceList()\"\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 <!-- Ghost while the preconditions are unmet, tenant once the\n line is addable. A disabled control painted in a faded\n tenant fill reads as an enabled button that lost its\n colour, which is the greyed-out accent the design brief\n bans; ghost reads as \"not yet\". -->\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n size=\"base\"\n [severity]=\"quickAddDisabled() ? 'ghost' : 'tenant'\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines.\n\n ef-data-card owns the panel here instead of sitting inside an\n ef-card: `.tbl-wrap` already draws a bordered, rounded surface, so\n nesting it in another one would be a card inside a card. The title\n and the card's actions move into the head slots it provides.\n\n The columns are declared in TS (`lineColumns`) so discount and tax\n follow the document config, and so one definition drives both the\n desktop table and the mobile cards. Only the article column is\n elastic; every other column holds a number of known magnitude and\n gets a fixed track, which keeps the decimals aligned down the\n table rather than shifting with the longest description. -->\n <ef-data-card\n [columns]=\"lineColumns()\"\n [rows]=\"orderLines()\"\n trackByField=\"id\"\n [hidePager]=\"true\"\n defaultDensity=\"compact\"\n [reorderable]=\"!readonly()\"\n [fixedLayout]=\"true\"\n (rowReorder)=\"reorderLine($event)\"\n mobileLayout=\"card\">\n\n <div tbl-head-info class=\"ob-lines-head\">\n <span class=\"strong\">{{ 'order_builder.lines' | translate }}</span>\n <span class=\"meta\">\u00B7 {{ orderLines().length }}</span>\n </div>\n\n <div tbl-head-actions 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 <span empty-state>{{ 'order_builder.empty' | translate }}</span>\n <span empty-state-mobile>{{ 'order_builder.empty' | translate }}</span>\n\n <ng-template efColumnTemplate=\"product\" let-item let-i=\"index\">\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 </ng-template>\n\n <ng-template efColumnTemplate=\"price\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"qty\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"discount\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"tax\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"subtotal\" let-item>\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </ng-template>\n\n <ng-template efColumnTemplate=\"remove\" let-item>\n <ef-button\n icon=\"pi pi-trash\"\n ariaLabelKey=\"common_delete\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n </ng-template>\n </ef-data-card>\n\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 @if (shippingTotal() > 0) {\n <div class=\"row shipping\">\n <span class=\"lbl\">{{ 'order_builder.shipping' | translate }}</span>\n <span class=\"v\">{{ shippingTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n }\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 <!-- Host cards that belong beside the totals (delivery, notes): a column\n at desktop width, below the totals in the drawer otherwise. -->\n <ng-content select=\"[obRail]\" />\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: ["@charset \"UTF-8\";: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 .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 .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 .ob-recipient{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--s-3);row-gap:2px;min-width:0;font-size:13px;line-height:1.45;color:var(--text-soft)}:host .order-builder .ob-recipient>*{flex:0 1 auto;min-width:0;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;unicode-bidi:plaintext}:host .order-builder .ob-recipient-name{font-weight:600;color:var(--text)}:host .order-builder .ob-recipient-phone{color:var(--tenant-700);text-decoration:none;font-variant-numeric:tabular-nums}:host .order-builder .ob-recipient-phone:hover{text-decoration:underline}:host .order-builder .ob-recipient-address{flex-basis:100%;color:var(--text-mute)}: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 ef-data-card{font-size:13px}:host .order-builder ef-data-card .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder ef-data-card .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder ef-data-card .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder ef-data-card .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder ef-data-card .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder ef-data-card ef-inputnumber,:host .order-builder ef-data-card ef-inputnumber .ef-field-control,:host .order-builder ef-data-card ef-inputnumber input{width:100%;min-width:0}:host .order-builder ef-data-card .num,:host .order-builder ef-data-card .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder ef-data-card .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);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 ef-data-card .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder ef-data-card .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder ef-data-card .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder ef-data-card .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder ef-data-card .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-lines-head{display:flex;align-items:baseline;gap:6px;min-width:0}:host .order-builder .ob-lines-head .strong{font-weight:700;color:var(--text)}:host .order-builder .ob-lines-head .meta{color:var(--text-soft)}: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:var(--font-mono);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:var(--font-display);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:var(--font-display);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:var(--font-mono);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:var(--font-mono);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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:calc(var(--app-bottom-bar, 0px) + env(safe-area-inset-bottom,0px) + 20px);z-index:30;align-items:center;gap:10px;box-sizing:border-box;block-size:var(--ef-order-fab-h);--ob-fab-ground: var(--ink-active);--ob-fab-on: var(--paper);background:var(--ob-fab-ground);color:var(--ob-fab-on);border-radius:var(--r-pill);padding-block:0;padding-inline:16px 10px;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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:color-mix(in srgb,var(--ob-fab-on) 14%,transparent);color:var(--ob-fab-on);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:color-mix(in srgb,var(--ob-fab-on) 24%,transparent)}:host .order-builder .ob-fab .open:focus-visible{outline:2px solid var(--ob-fab-on);outline-offset:2px}: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)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}: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)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host ef-data-card .nm,:host ef-data-card .num,:host ef-data-card .ln-val,:host .ob-totals .v{unicode-bidi:plaintext}@media(pointer:coarse){:host .order-builder .ob-fab .open{min-block-size:var(--hit-touch)}:host ef-data-card .ln-val{min-block-size:var(--hit-field)}:host .order-builder .ob-recipient-phone{line-height:var(--hit-touch)}}\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", "autofocus", "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", "allowAllTime", "mode", "selectionMode", "variant", "showTime", "valueFormat"], 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", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], 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", "priceList"], outputs: ["productSelect"] }, { kind: "component", type: EfDataCardComponent, selector: "ef-data-card", inputs: ["mobileLayout", "columns", "rows", "trackByField", "pageNumber", "pageSize", "totalCount", "pageSizeOptions", "hidePager", "sort", "loading", "errorMsg", "loadingKey", "rowDoubleClickable", "rowPressed", "rowDisabled", "hasActionsColumn", "rowActionsContext", "showViewAction", "showEditAction", "showDuplicateAction", "showDeleteAction", "rowActionState", "showDensityControl", "showColumnPicker", "showExportControl", "exporting", "screenContext", "searchTerm", "tableKey", "defaultDensity", "reorderable", "fixedLayout"], outputs: ["pageChange", "pageSizeChange", "sortChange", "rowDoubleClick", "rowAction", "exportRequest", "clearSearch", "retry", "rowReorder"] }, { kind: "directive", type: EfColumnTemplateDirective, selector: "[efColumnTemplate]", inputs: ["efColumnTemplate"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
6607
6678
  }
6608
6679
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, decorators: [{
6609
6680
  type: Component,
@@ -6622,8 +6693,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6622
6693
  EfProductTypeaheadComponent,
6623
6694
  EfDataCardComponent,
6624
6695
  EfColumnTemplateDirective,
6625
- ], 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 <!-- Ghost while the preconditions are unmet, tenant once the\n line is addable. A disabled control painted in a faded\n tenant fill reads as an enabled button that lost its\n colour, which is the greyed-out accent the design brief\n bans; ghost reads as \"not yet\". -->\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n size=\"base\"\n [severity]=\"quickAddDisabled() ? 'ghost' : 'tenant'\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines.\n\n ef-data-card owns the panel here instead of sitting inside an\n ef-card: `.tbl-wrap` already draws a bordered, rounded surface, so\n nesting it in another one would be a card inside a card. The title\n and the card's actions move into the head slots it provides.\n\n The columns are declared in TS (`lineColumns`) so discount and tax\n follow the document config, and so one definition drives both the\n desktop table and the mobile cards. Only the article column is\n elastic; every other column holds a number of known magnitude and\n gets a fixed track, which keeps the decimals aligned down the\n table rather than shifting with the longest description. -->\n <ef-data-card\n [columns]=\"lineColumns()\"\n [rows]=\"orderLines()\"\n trackByField=\"id\"\n [hidePager]=\"true\"\n defaultDensity=\"compact\"\n [reorderable]=\"!readonly()\"\n [fixedLayout]=\"true\"\n (rowReorder)=\"reorderLine($event)\"\n mobileLayout=\"card\">\n\n <div tbl-head-info class=\"ob-lines-head\">\n <span class=\"strong\">{{ 'order_builder.lines' | translate }}</span>\n <span class=\"meta\">\u00B7 {{ orderLines().length }}</span>\n </div>\n\n <div tbl-head-actions 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 <span empty-state>{{ 'order_builder.empty' | translate }}</span>\n <span empty-state-mobile>{{ 'order_builder.empty' | translate }}</span>\n\n <ng-template efColumnTemplate=\"product\" let-item let-i=\"index\">\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 </ng-template>\n\n <ng-template efColumnTemplate=\"price\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"qty\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"discount\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"tax\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"subtotal\" let-item>\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </ng-template>\n\n <ng-template efColumnTemplate=\"remove\" let-item>\n <ef-button\n icon=\"pi pi-trash\"\n ariaLabelKey=\"common_delete\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n </ng-template>\n </ef-data-card>\n\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 @if (shippingTotal() > 0) {\n <div class=\"row shipping\">\n <span class=\"lbl\">{{ 'order_builder.shipping' | translate }}</span>\n <span class=\"v\">{{ shippingTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n }\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: ["@charset \"UTF-8\";: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 .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 .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 ef-data-card{font-size:13px}:host .order-builder ef-data-card .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder ef-data-card .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder ef-data-card .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder ef-data-card .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder ef-data-card .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder ef-data-card ef-inputnumber,:host .order-builder ef-data-card ef-inputnumber .ef-field-control,:host .order-builder ef-data-card ef-inputnumber input{width:100%;min-width:0}:host .order-builder ef-data-card .num,:host .order-builder ef-data-card .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder ef-data-card .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);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 ef-data-card .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder ef-data-card .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder ef-data-card .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder ef-data-card .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder ef-data-card .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-lines-head{display:flex;align-items:baseline;gap:6px;min-width:0}:host .order-builder .ob-lines-head .strong{font-weight:700;color:var(--text)}:host .order-builder .ob-lines-head .meta{color:var(--text-soft)}: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:var(--font-mono);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:var(--font-display);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:var(--font-display);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:var(--font-mono);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:var(--font-mono);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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:calc(var(--app-bottom-bar, 0px) + env(safe-area-inset-bottom,0px) + 20px);z-index:30;align-items:center;gap:10px;box-sizing:border-box;block-size:var(--ef-order-fab-h);--ob-fab-ground: var(--ink-active);--ob-fab-on: var(--paper);background:var(--ob-fab-ground);color:var(--ob-fab-on);border-radius:var(--r-pill);padding-block:0;padding-inline:16px 10px;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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:color-mix(in srgb,var(--ob-fab-on) 14%,transparent);color:var(--ob-fab-on);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:color-mix(in srgb,var(--ob-fab-on) 24%,transparent)}:host .order-builder .ob-fab .open:focus-visible{outline:2px solid var(--ob-fab-on);outline-offset:2px}: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)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}: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)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host ef-data-card .nm,:host ef-data-card .num,:host ef-data-card .ln-val,:host .ob-totals .v{unicode-bidi:plaintext}@media(pointer:coarse){:host .order-builder .ob-fab .open{min-block-size:var(--hit-touch)}:host ef-data-card .ln-val{min-block-size:var(--hit-field)}}\n"] }]
6626
- }], 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 }] }], shippingTotal: [{ type: i0.Input, args: [{ isSignal: true, alias: "shippingTotal", 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 }] }] } });
6696
+ ], 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 @if (recipient(); as r) {\n <!-- No client to pick (a storefront order): who it goes to,\n read-only, in the customer's place. -->\n <div class=\"field col-6\">\n <ef-label labelKey=\"order_builder.recipient\" />\n <div\n class=\"ob-recipient\"\n role=\"group\"\n [attr.aria-label]=\"'order_builder.recipient' | translate\">\n <span class=\"ob-recipient-name\">{{ r.name }}</span>\n @if (recipientTel(); as tel) {\n <a class=\"ob-recipient-phone\" [href]=\"tel\" dir=\"ltr\">{{ r.phone }}</a>\n }\n @if (r.city) {\n <span class=\"ob-recipient-city\">{{ r.city }}</span>\n }\n @if (r.address) {\n <span class=\"ob-recipient-address\" [title]=\"r.address\">{{ r.address }}</span>\n }\n </div>\n </div>\n } @else {\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 }\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 [priceList]=\"priceList()\"\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 <!-- Ghost while the preconditions are unmet, tenant once the\n line is addable. A disabled control painted in a faded\n tenant fill reads as an enabled button that lost its\n colour, which is the greyed-out accent the design brief\n bans; ghost reads as \"not yet\". -->\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n size=\"base\"\n [severity]=\"quickAddDisabled() ? 'ghost' : 'tenant'\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines.\n\n ef-data-card owns the panel here instead of sitting inside an\n ef-card: `.tbl-wrap` already draws a bordered, rounded surface, so\n nesting it in another one would be a card inside a card. The title\n and the card's actions move into the head slots it provides.\n\n The columns are declared in TS (`lineColumns`) so discount and tax\n follow the document config, and so one definition drives both the\n desktop table and the mobile cards. Only the article column is\n elastic; every other column holds a number of known magnitude and\n gets a fixed track, which keeps the decimals aligned down the\n table rather than shifting with the longest description. -->\n <ef-data-card\n [columns]=\"lineColumns()\"\n [rows]=\"orderLines()\"\n trackByField=\"id\"\n [hidePager]=\"true\"\n defaultDensity=\"compact\"\n [reorderable]=\"!readonly()\"\n [fixedLayout]=\"true\"\n (rowReorder)=\"reorderLine($event)\"\n mobileLayout=\"card\">\n\n <div tbl-head-info class=\"ob-lines-head\">\n <span class=\"strong\">{{ 'order_builder.lines' | translate }}</span>\n <span class=\"meta\">\u00B7 {{ orderLines().length }}</span>\n </div>\n\n <div tbl-head-actions 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 <span empty-state>{{ 'order_builder.empty' | translate }}</span>\n <span empty-state-mobile>{{ 'order_builder.empty' | translate }}</span>\n\n <ng-template efColumnTemplate=\"product\" let-item let-i=\"index\">\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 </ng-template>\n\n <ng-template efColumnTemplate=\"price\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"qty\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"discount\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"tax\" let-item>\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 type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"subtotal\" let-item>\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </ng-template>\n\n <ng-template efColumnTemplate=\"remove\" let-item>\n <ef-button\n icon=\"pi pi-trash\"\n ariaLabelKey=\"common_delete\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n </ng-template>\n </ef-data-card>\n\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 @if (shippingTotal() > 0) {\n <div class=\"row shipping\">\n <span class=\"lbl\">{{ 'order_builder.shipping' | translate }}</span>\n <span class=\"v\">{{ shippingTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n }\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 <!-- Host cards that belong beside the totals (delivery, notes): a column\n at desktop width, below the totals in the drawer otherwise. -->\n <ng-content select=\"[obRail]\" />\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: ["@charset \"UTF-8\";: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 .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 .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 .ob-recipient{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--s-3);row-gap:2px;min-width:0;font-size:13px;line-height:1.45;color:var(--text-soft)}:host .order-builder .ob-recipient>*{flex:0 1 auto;min-width:0;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;unicode-bidi:plaintext}:host .order-builder .ob-recipient-name{font-weight:600;color:var(--text)}:host .order-builder .ob-recipient-phone{color:var(--tenant-700);text-decoration:none;font-variant-numeric:tabular-nums}:host .order-builder .ob-recipient-phone:hover{text-decoration:underline}:host .order-builder .ob-recipient-address{flex-basis:100%;color:var(--text-mute)}: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 ef-data-card{font-size:13px}:host .order-builder ef-data-card .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder ef-data-card .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder ef-data-card .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder ef-data-card .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder ef-data-card .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder ef-data-card ef-inputnumber,:host .order-builder ef-data-card ef-inputnumber .ef-field-control,:host .order-builder ef-data-card ef-inputnumber input{width:100%;min-width:0}:host .order-builder ef-data-card .num,:host .order-builder ef-data-card .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder ef-data-card .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);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 ef-data-card .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder ef-data-card .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder ef-data-card .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder ef-data-card .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder ef-data-card .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-lines-head{display:flex;align-items:baseline;gap:6px;min-width:0}:host .order-builder .ob-lines-head .strong{font-weight:700;color:var(--text)}:host .order-builder .ob-lines-head .meta{color:var(--text-soft)}: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:var(--font-mono);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:var(--font-display);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:var(--font-display);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:var(--font-mono);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:var(--font-mono);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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:calc(var(--app-bottom-bar, 0px) + env(safe-area-inset-bottom,0px) + 20px);z-index:30;align-items:center;gap:10px;box-sizing:border-box;block-size:var(--ef-order-fab-h);--ob-fab-ground: var(--ink-active);--ob-fab-on: var(--paper);background:var(--ob-fab-ground);color:var(--ob-fab-on);border-radius:var(--r-pill);padding-block:0;padding-inline:16px 10px;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:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:color-mix(in srgb,var(--ob-fab-on) 14%,transparent);color:var(--ob-fab-on);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:color-mix(in srgb,var(--ob-fab-on) 24%,transparent)}:host .order-builder .ob-fab .open:focus-visible{outline:2px solid var(--ob-fab-on);outline-offset:2px}: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)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}: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)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host ef-data-card .nm,:host ef-data-card .num,:host ef-data-card .ln-val,:host .ob-totals .v{unicode-bidi:plaintext}@media(pointer:coarse){:host .order-builder .ob-fab .open{min-block-size:var(--hit-touch)}:host ef-data-card .ln-val{min-block-size:var(--hit-field)}:host .order-builder .ob-recipient-phone{line-height:var(--hit-touch)}}\n"] }]
6697
+ }], 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 }] }], recipient: [{ type: i0.Input, args: [{ isSignal: true, alias: "recipient", required: false }] }], defaultCallingCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCallingCode", required: false }] }], 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 }] }], priceList: [{ type: i0.Input, args: [{ isSignal: true, alias: "priceList", required: false }] }], backendErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "backendErrors", required: false }] }], shippingTotal: [{ type: i0.Input, args: [{ isSignal: true, alias: "shippingTotal", 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 }] }] } });
6627
6698
 
6628
6699
  /**
6629
6700
  * Comptoir smart bar — the search/filter card every list screen
@@ -7438,6 +7509,12 @@ class EfProductCatalogueComponent {
7438
7509
  trackByField = input('id', ...(ngDevMode ? [{ debugName: "trackByField" }] : /* istanbul ignore next */ []));
7439
7510
  /** Currency code for price formatting */
7440
7511
  currencyCode = input('MAD', ...(ngDevMode ? [{ debugName: "currencyCode" }] : /* istanbul ignore next */ []));
7512
+ /**
7513
+ * Which price the cards show: `'wholesale'` (default) or `'retail'`, the shop price.
7514
+ * Bind the same value as the order builder's `priceList`, so a card shows the price
7515
+ * of the line it adds (the rule lives in `defaultUnitPrice`).
7516
+ */
7517
+ priceList = input('wholesale', ...(ngDevMode ? [{ debugName: "priceList" }] : /* istanbul ignore next */ []));
7441
7518
  /** Locale used for number/currency formatting and the quantity stepper */
7442
7519
  localeCode = input('fr-FR', ...(ngDevMode ? [{ debugName: "localeCode" }] : /* istanbul ignore next */ []));
7443
7520
  /**
@@ -7691,30 +7768,38 @@ class EfProductCatalogueComponent {
7691
7768
  trackKey(product) {
7692
7769
  return this.productKey(product);
7693
7770
  }
7771
+ /** The price a variant (or a simple product, `variant` null) is shown at under the active list. */
7772
+ priceOf(product, variant) {
7773
+ if (this.priceList() === 'retail') {
7774
+ return defaultUnitPrice(product, variant, 'retail');
7775
+ }
7776
+ return variant ? variant.price : product.unitPrice;
7777
+ }
7694
7778
  formatPrice(product) {
7695
7779
  if (this.isVariableProduct(product)) {
7696
7780
  const variantId = this.getSelectedVariantId(product);
7697
7781
  const variant = product.variants?.find((v) => v.variantId === variantId);
7698
7782
  if (variant) {
7699
- return this.formatCurrency(variant.price);
7783
+ return this.formatCurrency(this.priceOf(product, variant));
7700
7784
  }
7701
7785
  // Fallback: show price range
7702
7786
  const activeVariants = this.getActiveVariants(product);
7703
7787
  if (activeVariants.length > 0) {
7704
- const min = Math.min(...activeVariants.map((v) => v.price));
7705
- const max = Math.max(...activeVariants.map((v) => v.price));
7788
+ const prices = activeVariants.map((v) => this.priceOf(product, v) ?? 0);
7789
+ const min = Math.min(...prices);
7790
+ const max = Math.max(...prices);
7706
7791
  if (min === max)
7707
7792
  return this.formatCurrency(min);
7708
7793
  return `${this.formatCurrency(min)} - ${this.formatCurrency(max)}`;
7709
7794
  }
7710
7795
  }
7711
- const price = product.unitPrice;
7796
+ const price = this.priceOf(product, null);
7712
7797
  if (price == null)
7713
7798
  return '';
7714
7799
  return this.formatCurrency(price);
7715
7800
  }
7716
- formatVariantPrice(variant) {
7717
- return this.formatCurrency(variant.price);
7801
+ formatVariantPrice(variant, product) {
7802
+ return this.formatCurrency(product ? this.priceOf(product, variant) : variant.price);
7718
7803
  }
7719
7804
  formatCurrency(value) {
7720
7805
  if (value == null)
@@ -7722,7 +7807,7 @@ class EfProductCatalogueComponent {
7722
7807
  return (this.currencyPipe.transform(value, this.currencyCode(), 'symbol-narrow', '1.2-2') || '');
7723
7808
  }
7724
7809
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfProductCatalogueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7725
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfProductCatalogueComponent, isStandalone: true, selector: "ef-product-catalogue", inputs: { products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: false, transformFunction: null }, categoryGroups: { classPropertyName: "categoryGroups", publicName: "categoryGroups", isSignal: true, isRequired: false, transformFunction: null }, initialSelectedItems: { classPropertyName: "initialSelectedItems", publicName: "initialSelectedItems", isSignal: true, isRequired: false, transformFunction: null }, defaultFilters: { classPropertyName: "defaultFilters", publicName: "defaultFilters", isSignal: true, isRequired: false, transformFunction: null }, trackByField: { classPropertyName: "trackByField", publicName: "trackByField", isSignal: true, isRequired: false, transformFunction: null }, currencyCode: { classPropertyName: "currencyCode", publicName: "currencyCode", isSignal: true, isRequired: false, transformFunction: null }, localeCode: { classPropertyName: "localeCode", publicName: "localeCode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { productsValidated: "productsValidated" }, queries: [{ propertyName: "productThumbnailTpl", first: true, predicate: ["productThumbnail"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "sortMenu", first: true, predicate: ["sortMenu"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"bg-surface-0 dark:bg-surface-950\">\n <div class=\"flex flex-row gap-4\">\n <!-- Filter Sidebar -->\n <ef-product-catalogue-filter\n [categoryGroups]=\"categoryGroups()\"\n [products]=\"products()\"\n [(selectedFilters)]=\"selectedFilters\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n />\n\n <!-- Product Grid -->\n <div\n class=\"min-h-96 flex-1 rounded-md border-2 border-dotted border-surface-200 bg-surface-50 p-4 dark:border-surface-700 dark:bg-surface-800\"\n >\n <div class=\"sticky top-0 z-10 w-full mb-4 bg-surface-50 dark:bg-surface-800 pb-2 -mt-4 pt-4 -mx-4 px-4\">\n <p-iconfield iconPosition=\"left\" class=\"w-full\">\n <p-inputicon class=\"pi pi-search\"></p-inputicon>\n <input\n pInputText\n size=\"small\"\n [(ngModel)]=\"productSearch\"\n [placeholder]=\"'placeholder.search_products' | translate\"\n class=\"w-full\"\n />\n </p-iconfield>\n </div>\n <div class=\"grid grid-cols-2 gap-4 lg:grid-cols-3 xl:grid-cols-4\">\n @for (product of filteredProducts(); track trackKey(product)) {\n <p-card\n [header]=\"''\"\n [class.border-2]=\"true\"\n [class.border-blue-100]=\"getProductQuantity(product) > 0\"\n [class.!bg-blue-100]=\"getProductQuantity(product) > 0\"\n [class.dark:!border-blue-900]=\"getProductQuantity(product) > 0\"\n [class.dark:!bg-blue-900]=\"getProductQuantity(product) > 0\"\n [class.border-transparent]=\"getProductQuantity(product) === 0\"\n class=\"cursor-pointer hover:shadow-md dark:hover:shadow-md dark:hover:shadow-gray-300/50\"\n >\n <ng-template pTemplate=\"header\">\n <div class=\"w-full\">\n @if (productThumbnailTpl()) {\n <ng-container *ngTemplateOutlet=\"productThumbnailTpl(); context: { $implicit: product }\"></ng-container>\n } @else {\n <!-- Default fallback thumbnail -->\n <div class=\"flex h-[13rem] items-center justify-center bg-surface-100 text-3xl text-surface-400 dark:bg-surface-800\">\n <i class=\"pi pi-box\"></i>\n </div>\n }\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <div [innerHTML]=\"product.displayName\" class=\"h-14 font-semibold\"></div>\n </ng-template>\n <ng-template pTemplate=\"footer\">\n @if (isVariableProduct(product)) {\n <!-- Variable product: variant selector + per-variant quantities -->\n <div class=\"flex items-center justify-between mb-2\">\n <p-badge\n severity=\"secondary\"\n [value]=\"getActiveVariants(product).length + ' ' + ('catalogue.variants' | translate)\"\n badgeSize=\"small\"\n />\n <div class=\"text-lg font-bold\">\n {{ formatPrice(product) }}\n </div>\n </div>\n\n <!-- Variant selector -->\n <p-select\n [autofocus]=\"false\"\n [options]=\"getActiveVariants(product)\"\n optionLabel=\"title\"\n optionValue=\"variantId\"\n [ngModel]=\"getSelectedVariantId(product)\"\n (ngModelChange)=\"setSelectedVariant(product, $event)\"\n size=\"small\"\n styleClass=\"w-full mb-3\"\n [placeholder]=\"'placeholder.select_variant' | translate\"\n />\n\n <!-- Add button or quantity stepper for selected variant -->\n <div class=\"mt-2\">\n @if (getProductQuantity(product) > 0) {\n <!-- Show per-variant breakdown -->\n @for (entry of selectedProducts(); track $index) {\n @if (productKey(entry.product) === productKey(product) && entry.variantId) {\n <div class=\"flex items-center justify-center gap-2 mb-2\">\n <span class=\"text-xs font-medium text-surface-600 dark:text-surface-400 min-w-12 text-center\">\n {{ getVariantTitle(product, entry.variantId) }}\n </span>\n <ef-quantity-stepper\n [ngModel]=\"entry.quantity\"\n (ngModelChange)=\"updateVariantQuantity(product, entry.variantId!, $event)\"\n [min]=\"0\"\n [max]=\"1000\"\n [maxFractionDigits]=\"4\"\n [locale]=\"localeCode()\"\n />\n <ef-button\n icon=\"pi pi-times\"\n severity=\"danger\"\n styleClass=\"!rounded-full !w-7 !h-7 !p-0\"\n size=\"small\"\n (clickEvent)=\"removeVariantSelection(product, entry.variantId!)\"\n />\n </div>\n }\n }\n <!-- Add another variant button -->\n <ef-button\n icon=\"pi pi-plus\"\n [label]=\"'button.add_variant' | translate\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-10 mt-1\"\n size=\"small\"\n (clickEvent)=\"addProduct(product)\"\n />\n } @else {\n <ef-button\n [label]=\"'button.add' | translate\"\n icon=\"pi pi-shopping-cart\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-12\"\n (clickEvent)=\"addProduct(product)\"\n />\n }\n </div>\n } @else {\n <!-- Simple product: original behavior -->\n <div class=\"flex items-center justify-between\">\n <p-badge\n severity=\"secondary\"\n [value]=\"getPackagingLabel(product)\"\n badgeSize=\"small\"\n />\n <div class=\"text-lg font-bold\">\n {{ formatPrice(product) }}\n </div>\n </div>\n <div class=\"mt-4\">\n @if (getProductQuantity(product) > 0) {\n <div class=\"flex items-center justify-between\">\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"primary\"\n styleClass=\"!rounded-full\"\n (clickEvent)=\"removeProduct(product)\"\n />\n <ef-quantity-stepper\n [ngModel]=\"getProductQuantity(product)\"\n (ngModelChange)=\"updateProductQuantity(product, $event)\"\n [min]=\"0\"\n [max]=\"1000\"\n [maxFractionDigits]=\"4\"\n [locale]=\"localeCode()\"\n />\n </div>\n } @else {\n <ef-button\n [label]=\"'button.add' | translate\"\n icon=\"pi pi-shopping-cart\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-12\"\n (clickEvent)=\"addProduct(product)\"\n />\n }\n </div>\n }\n </ng-template>\n </p-card>\n } @empty {\n <div class=\"p-2\">{{ 'message.no_products_found' | translate }}</div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".p-accordion-header-link{border:0!important;padding:.25rem 0!important;box-shadow:none!important}.p-accordion-panel{border:0!important;border-bottom:1px solid var(--rule)!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$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: BadgeModule }, { kind: "component", type: i3.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i5$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: IconFieldModule }, { kind: "component", type: i7.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "ngmodule", type: InputIconModule }, { kind: "component", type: i8.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i9.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i1$2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: EfQuantityStepperComponent, selector: "ef-quantity-stepper", inputs: ["value", "min", "max", "step", "minFractionDigits", "maxFractionDigits", "locale", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: EfProductCatalogueFilterComponent, selector: "ef-product-catalogue-filter", inputs: ["categoryGroups", "products", "selectedFilters"], outputs: ["selectedFiltersChange", "filtersChanged"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7810
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfProductCatalogueComponent, isStandalone: true, selector: "ef-product-catalogue", inputs: { products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: false, transformFunction: null }, categoryGroups: { classPropertyName: "categoryGroups", publicName: "categoryGroups", isSignal: true, isRequired: false, transformFunction: null }, initialSelectedItems: { classPropertyName: "initialSelectedItems", publicName: "initialSelectedItems", isSignal: true, isRequired: false, transformFunction: null }, defaultFilters: { classPropertyName: "defaultFilters", publicName: "defaultFilters", isSignal: true, isRequired: false, transformFunction: null }, trackByField: { classPropertyName: "trackByField", publicName: "trackByField", isSignal: true, isRequired: false, transformFunction: null }, currencyCode: { classPropertyName: "currencyCode", publicName: "currencyCode", isSignal: true, isRequired: false, transformFunction: null }, priceList: { classPropertyName: "priceList", publicName: "priceList", isSignal: true, isRequired: false, transformFunction: null }, localeCode: { classPropertyName: "localeCode", publicName: "localeCode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { productsValidated: "productsValidated" }, queries: [{ propertyName: "productThumbnailTpl", first: true, predicate: ["productThumbnail"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "sortMenu", first: true, predicate: ["sortMenu"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"bg-surface-0 dark:bg-surface-950\">\n <div class=\"flex flex-row gap-4\">\n <!-- Filter Sidebar -->\n <ef-product-catalogue-filter\n [categoryGroups]=\"categoryGroups()\"\n [products]=\"products()\"\n [(selectedFilters)]=\"selectedFilters\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n />\n\n <!-- Product Grid -->\n <div\n class=\"min-h-96 flex-1 rounded-md border-2 border-dotted border-surface-200 bg-surface-50 p-4 dark:border-surface-700 dark:bg-surface-800\"\n >\n <div class=\"sticky top-0 z-10 w-full mb-4 bg-surface-50 dark:bg-surface-800 pb-2 -mt-4 pt-4 -mx-4 px-4\">\n <p-iconfield iconPosition=\"left\" class=\"w-full\">\n <p-inputicon class=\"pi pi-search\"></p-inputicon>\n <input\n pInputText\n size=\"small\"\n [(ngModel)]=\"productSearch\"\n [placeholder]=\"'placeholder.search_products' | translate\"\n class=\"w-full\"\n />\n </p-iconfield>\n </div>\n <div class=\"grid grid-cols-2 gap-4 lg:grid-cols-3 xl:grid-cols-4\">\n @for (product of filteredProducts(); track trackKey(product)) {\n <p-card\n [header]=\"''\"\n [class.border-2]=\"true\"\n [class.border-blue-100]=\"getProductQuantity(product) > 0\"\n [class.!bg-blue-100]=\"getProductQuantity(product) > 0\"\n [class.dark:!border-blue-900]=\"getProductQuantity(product) > 0\"\n [class.dark:!bg-blue-900]=\"getProductQuantity(product) > 0\"\n [class.border-transparent]=\"getProductQuantity(product) === 0\"\n class=\"cursor-pointer hover:shadow-md dark:hover:shadow-md dark:hover:shadow-gray-300/50\"\n >\n <ng-template pTemplate=\"header\">\n <div class=\"w-full\">\n @if (productThumbnailTpl()) {\n <ng-container *ngTemplateOutlet=\"productThumbnailTpl(); context: { $implicit: product }\"></ng-container>\n } @else {\n <!-- Default fallback thumbnail -->\n <div class=\"flex h-[13rem] items-center justify-center bg-surface-100 text-3xl text-surface-400 dark:bg-surface-800\">\n <i class=\"pi pi-box\"></i>\n </div>\n }\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <div [innerHTML]=\"product.displayName\" class=\"h-14 font-semibold\"></div>\n </ng-template>\n <ng-template pTemplate=\"footer\">\n @if (isVariableProduct(product)) {\n <!-- Variable product: variant selector + per-variant quantities -->\n <div class=\"flex items-center justify-between mb-2\">\n <p-badge\n severity=\"secondary\"\n [value]=\"getActiveVariants(product).length + ' ' + ('catalogue.variants' | translate)\"\n badgeSize=\"small\"\n />\n <div class=\"text-lg font-bold\">\n {{ formatPrice(product) }}\n </div>\n </div>\n\n <!-- Variant selector -->\n <p-select\n [autofocus]=\"false\"\n [options]=\"getActiveVariants(product)\"\n optionLabel=\"title\"\n optionValue=\"variantId\"\n [ngModel]=\"getSelectedVariantId(product)\"\n (ngModelChange)=\"setSelectedVariant(product, $event)\"\n size=\"small\"\n styleClass=\"w-full mb-3\"\n [placeholder]=\"'placeholder.select_variant' | translate\"\n />\n\n <!-- Add button or quantity stepper for selected variant -->\n <div class=\"mt-2\">\n @if (getProductQuantity(product) > 0) {\n <!-- Show per-variant breakdown -->\n @for (entry of selectedProducts(); track $index) {\n @if (productKey(entry.product) === productKey(product) && entry.variantId) {\n <div class=\"flex items-center justify-center gap-2 mb-2\">\n <span class=\"text-xs font-medium text-surface-600 dark:text-surface-400 min-w-12 text-center\">\n {{ getVariantTitle(product, entry.variantId) }}\n </span>\n <ef-quantity-stepper\n [ngModel]=\"entry.quantity\"\n (ngModelChange)=\"updateVariantQuantity(product, entry.variantId!, $event)\"\n [min]=\"0\"\n [max]=\"1000\"\n [maxFractionDigits]=\"4\"\n [locale]=\"localeCode()\"\n />\n <ef-button\n icon=\"pi pi-times\"\n severity=\"danger\"\n styleClass=\"!rounded-full !w-7 !h-7 !p-0\"\n size=\"small\"\n (clickEvent)=\"removeVariantSelection(product, entry.variantId!)\"\n />\n </div>\n }\n }\n <!-- Add another variant button -->\n <ef-button\n icon=\"pi pi-plus\"\n [label]=\"'button.add_variant' | translate\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-10 mt-1\"\n size=\"small\"\n (clickEvent)=\"addProduct(product)\"\n />\n } @else {\n <ef-button\n [label]=\"'button.add' | translate\"\n icon=\"pi pi-shopping-cart\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-12\"\n (clickEvent)=\"addProduct(product)\"\n />\n }\n </div>\n } @else {\n <!-- Simple product: original behavior -->\n <div class=\"flex items-center justify-between\">\n <p-badge\n severity=\"secondary\"\n [value]=\"getPackagingLabel(product)\"\n badgeSize=\"small\"\n />\n <div class=\"text-lg font-bold\">\n {{ formatPrice(product) }}\n </div>\n </div>\n <div class=\"mt-4\">\n @if (getProductQuantity(product) > 0) {\n <div class=\"flex items-center justify-between\">\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"primary\"\n styleClass=\"!rounded-full\"\n (clickEvent)=\"removeProduct(product)\"\n />\n <ef-quantity-stepper\n [ngModel]=\"getProductQuantity(product)\"\n (ngModelChange)=\"updateProductQuantity(product, $event)\"\n [min]=\"0\"\n [max]=\"1000\"\n [maxFractionDigits]=\"4\"\n [locale]=\"localeCode()\"\n />\n </div>\n } @else {\n <ef-button\n [label]=\"'button.add' | translate\"\n icon=\"pi pi-shopping-cart\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-12\"\n (clickEvent)=\"addProduct(product)\"\n />\n }\n </div>\n }\n </ng-template>\n </p-card>\n } @empty {\n <div class=\"p-2\">{{ 'message.no_products_found' | translate }}</div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".p-accordion-header-link{border:0!important;padding:.25rem 0!important;box-shadow:none!important}.p-accordion-panel{border:0!important;border-bottom:1px solid var(--rule)!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$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: BadgeModule }, { kind: "component", type: i3.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i5$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: IconFieldModule }, { kind: "component", type: i7.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "ngmodule", type: InputIconModule }, { kind: "component", type: i8.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i9.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i1$2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: EfQuantityStepperComponent, selector: "ef-quantity-stepper", inputs: ["value", "min", "max", "step", "minFractionDigits", "maxFractionDigits", "locale", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: EfProductCatalogueFilterComponent, selector: "ef-product-catalogue-filter", inputs: ["categoryGroups", "products", "selectedFilters"], outputs: ["selectedFiltersChange", "filtersChanged"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7726
7811
  }
7727
7812
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfProductCatalogueComponent, decorators: [{
7728
7813
  type: Component,
@@ -7741,7 +7826,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
7741
7826
  EfProductCatalogueFilterComponent,
7742
7827
  TranslateModule,
7743
7828
  ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"bg-surface-0 dark:bg-surface-950\">\n <div class=\"flex flex-row gap-4\">\n <!-- Filter Sidebar -->\n <ef-product-catalogue-filter\n [categoryGroups]=\"categoryGroups()\"\n [products]=\"products()\"\n [(selectedFilters)]=\"selectedFilters\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n />\n\n <!-- Product Grid -->\n <div\n class=\"min-h-96 flex-1 rounded-md border-2 border-dotted border-surface-200 bg-surface-50 p-4 dark:border-surface-700 dark:bg-surface-800\"\n >\n <div class=\"sticky top-0 z-10 w-full mb-4 bg-surface-50 dark:bg-surface-800 pb-2 -mt-4 pt-4 -mx-4 px-4\">\n <p-iconfield iconPosition=\"left\" class=\"w-full\">\n <p-inputicon class=\"pi pi-search\"></p-inputicon>\n <input\n pInputText\n size=\"small\"\n [(ngModel)]=\"productSearch\"\n [placeholder]=\"'placeholder.search_products' | translate\"\n class=\"w-full\"\n />\n </p-iconfield>\n </div>\n <div class=\"grid grid-cols-2 gap-4 lg:grid-cols-3 xl:grid-cols-4\">\n @for (product of filteredProducts(); track trackKey(product)) {\n <p-card\n [header]=\"''\"\n [class.border-2]=\"true\"\n [class.border-blue-100]=\"getProductQuantity(product) > 0\"\n [class.!bg-blue-100]=\"getProductQuantity(product) > 0\"\n [class.dark:!border-blue-900]=\"getProductQuantity(product) > 0\"\n [class.dark:!bg-blue-900]=\"getProductQuantity(product) > 0\"\n [class.border-transparent]=\"getProductQuantity(product) === 0\"\n class=\"cursor-pointer hover:shadow-md dark:hover:shadow-md dark:hover:shadow-gray-300/50\"\n >\n <ng-template pTemplate=\"header\">\n <div class=\"w-full\">\n @if (productThumbnailTpl()) {\n <ng-container *ngTemplateOutlet=\"productThumbnailTpl(); context: { $implicit: product }\"></ng-container>\n } @else {\n <!-- Default fallback thumbnail -->\n <div class=\"flex h-[13rem] items-center justify-center bg-surface-100 text-3xl text-surface-400 dark:bg-surface-800\">\n <i class=\"pi pi-box\"></i>\n </div>\n }\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <div [innerHTML]=\"product.displayName\" class=\"h-14 font-semibold\"></div>\n </ng-template>\n <ng-template pTemplate=\"footer\">\n @if (isVariableProduct(product)) {\n <!-- Variable product: variant selector + per-variant quantities -->\n <div class=\"flex items-center justify-between mb-2\">\n <p-badge\n severity=\"secondary\"\n [value]=\"getActiveVariants(product).length + ' ' + ('catalogue.variants' | translate)\"\n badgeSize=\"small\"\n />\n <div class=\"text-lg font-bold\">\n {{ formatPrice(product) }}\n </div>\n </div>\n\n <!-- Variant selector -->\n <p-select\n [autofocus]=\"false\"\n [options]=\"getActiveVariants(product)\"\n optionLabel=\"title\"\n optionValue=\"variantId\"\n [ngModel]=\"getSelectedVariantId(product)\"\n (ngModelChange)=\"setSelectedVariant(product, $event)\"\n size=\"small\"\n styleClass=\"w-full mb-3\"\n [placeholder]=\"'placeholder.select_variant' | translate\"\n />\n\n <!-- Add button or quantity stepper for selected variant -->\n <div class=\"mt-2\">\n @if (getProductQuantity(product) > 0) {\n <!-- Show per-variant breakdown -->\n @for (entry of selectedProducts(); track $index) {\n @if (productKey(entry.product) === productKey(product) && entry.variantId) {\n <div class=\"flex items-center justify-center gap-2 mb-2\">\n <span class=\"text-xs font-medium text-surface-600 dark:text-surface-400 min-w-12 text-center\">\n {{ getVariantTitle(product, entry.variantId) }}\n </span>\n <ef-quantity-stepper\n [ngModel]=\"entry.quantity\"\n (ngModelChange)=\"updateVariantQuantity(product, entry.variantId!, $event)\"\n [min]=\"0\"\n [max]=\"1000\"\n [maxFractionDigits]=\"4\"\n [locale]=\"localeCode()\"\n />\n <ef-button\n icon=\"pi pi-times\"\n severity=\"danger\"\n styleClass=\"!rounded-full !w-7 !h-7 !p-0\"\n size=\"small\"\n (clickEvent)=\"removeVariantSelection(product, entry.variantId!)\"\n />\n </div>\n }\n }\n <!-- Add another variant button -->\n <ef-button\n icon=\"pi pi-plus\"\n [label]=\"'button.add_variant' | translate\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-10 mt-1\"\n size=\"small\"\n (clickEvent)=\"addProduct(product)\"\n />\n } @else {\n <ef-button\n [label]=\"'button.add' | translate\"\n icon=\"pi pi-shopping-cart\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-12\"\n (clickEvent)=\"addProduct(product)\"\n />\n }\n </div>\n } @else {\n <!-- Simple product: original behavior -->\n <div class=\"flex items-center justify-between\">\n <p-badge\n severity=\"secondary\"\n [value]=\"getPackagingLabel(product)\"\n badgeSize=\"small\"\n />\n <div class=\"text-lg font-bold\">\n {{ formatPrice(product) }}\n </div>\n </div>\n <div class=\"mt-4\">\n @if (getProductQuantity(product) > 0) {\n <div class=\"flex items-center justify-between\">\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"primary\"\n styleClass=\"!rounded-full\"\n (clickEvent)=\"removeProduct(product)\"\n />\n <ef-quantity-stepper\n [ngModel]=\"getProductQuantity(product)\"\n (ngModelChange)=\"updateProductQuantity(product, $event)\"\n [min]=\"0\"\n [max]=\"1000\"\n [maxFractionDigits]=\"4\"\n [locale]=\"localeCode()\"\n />\n </div>\n } @else {\n <ef-button\n [label]=\"'button.add' | translate\"\n icon=\"pi pi-shopping-cart\"\n severity=\"primary\"\n styleClass=\"w-full !rounded-full !h-12\"\n (clickEvent)=\"addProduct(product)\"\n />\n }\n </div>\n }\n </ng-template>\n </p-card>\n } @empty {\n <div class=\"p-2\">{{ 'message.no_products_found' | translate }}</div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".p-accordion-header-link{border:0!important;padding:.25rem 0!important;box-shadow:none!important}.p-accordion-panel{border:0!important;border-bottom:1px solid var(--rule)!important}\n"] }]
7744
- }], propDecorators: { products: [{ type: i0.Input, args: [{ isSignal: true, alias: "products", required: false }] }], categoryGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "categoryGroups", required: false }] }], initialSelectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelectedItems", required: false }] }], defaultFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultFilters", required: false }] }], trackByField: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByField", required: false }] }], currencyCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencyCode", required: false }] }], localeCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeCode", required: false }] }], productThumbnailTpl: [{ type: i0.ContentChild, args: ['productThumbnail', { isSignal: true }] }], sortMenu: [{
7829
+ }], propDecorators: { products: [{ type: i0.Input, args: [{ isSignal: true, alias: "products", required: false }] }], categoryGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "categoryGroups", required: false }] }], initialSelectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelectedItems", required: false }] }], defaultFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultFilters", required: false }] }], trackByField: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByField", required: false }] }], currencyCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencyCode", required: false }] }], priceList: [{ type: i0.Input, args: [{ isSignal: true, alias: "priceList", required: false }] }], localeCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeCode", required: false }] }], productThumbnailTpl: [{ type: i0.ContentChild, args: ['productThumbnail', { isSignal: true }] }], sortMenu: [{
7745
7830
  type: ViewChild,
7746
7831
  args: ['sortMenu']
7747
7832
  }], productsValidated: [{ type: i0.Output, args: ["productsValidated"] }] } });
@@ -9263,8 +9348,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9263
9348
  */
9264
9349
  class EfPillGroupComponent {
9265
9350
  items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
9266
- /** Two-way bound selection. */
9267
- value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
9351
+ /** Selected value; `null` when nothing is selected. Two-way bindable
9352
+ * with `[(value)]`. */
9353
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
9354
+ /** Emits the picked pill's value. Never `null`: a click always picks
9355
+ * a pill, so handlers can take `T` without a null check. */
9356
+ valueChange = output();
9357
+ /** What the pills render: the bound value, or the last pick when the
9358
+ * caller only listens to `valueChange`. */
9359
+ selected = linkedSignal(() => this.value(), ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
9268
9360
  /** Translation key for the group's accessible name. */
9269
9361
  ariaLabelKey = input('common_filters', ...(ngDevMode ? [{ debugName: "ariaLabelKey" }] : /* istanbul ignore next */ []));
9270
9362
  /** Hide counts even when items provide them. */
@@ -9285,7 +9377,7 @@ class EfPillGroupComponent {
9285
9377
  this.scrollActiveIntoView('auto');
9286
9378
  });
9287
9379
  effect(() => {
9288
- this.value();
9380
+ this.selected();
9289
9381
  this.items();
9290
9382
  queueMicrotask(() => {
9291
9383
  this.measure();
@@ -9294,8 +9386,10 @@ class EfPillGroupComponent {
9294
9386
  });
9295
9387
  }
9296
9388
  select(item) {
9297
- if (this.value() !== item.value)
9298
- this.value.set(item.value);
9389
+ if (this.selected() === item.value)
9390
+ return;
9391
+ this.selected.set(item.value);
9392
+ this.valueChange.emit(item.value);
9299
9393
  }
9300
9394
  onScroll() {
9301
9395
  this.measure();
@@ -9325,12 +9419,12 @@ class EfPillGroupComponent {
9325
9419
  active.scrollIntoView({ behavior, inline: 'center', block: 'nearest' });
9326
9420
  }
9327
9421
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfPillGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9328
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfPillGroupComponent, isStandalone: true, selector: "ef-pill-group", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, hideCounts: { classPropertyName: "hideCounts", publicName: "hideCounts", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"ef-pill-group\" [ngClass]=\"fadeClass()\">\n <div\n #scroller\n class=\"ef-pill-group__track\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabelKey() | translate\"\n (scroll)=\"onScroll()\"\n >\n @for (item of items(); track trackValue($index, item)) {\n <button\n type=\"button\"\n class=\"ef-pill\"\n role=\"tab\"\n [class.is-active]=\"item.value === value()\"\n [attr.aria-selected]=\"item.value === value()\"\n (click)=\"select(item)\"\n >\n <span class=\"ef-pill__label\">{{ item.labelKey | translate }}</span>\n @if (!hideCounts() && item.count !== undefined) {\n <span class=\"ef-pill__sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"ef-pill__count\">{{ item.count }}</span>\n }\n </button>\n }\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;min-width:0;max-width:100%;flex:1 1 auto}@media(max-width:767px){:host{flex-basis:100%}}.ef-pill-group{position:relative;display:flex;min-width:0;max-width:100%;inline-size:100%;border-radius:var(--r-pill)}.ef-pill-group__track{display:flex;align-items:center;gap:4px;padding:4px;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x proximity;border-radius:inherit;scrollbar-width:none;-ms-overflow-style:none}.ef-pill-group__track::-webkit-scrollbar{display:none}.ef-pill-group:before,.ef-pill-group:after{content:\"\";position:absolute;inset-block:0;inline-size:28px;pointer-events:none;opacity:0;transition:opacity var(--t-fast, .14s) var(--ease-out, ease-out);z-index:1}.ef-pill-group:before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group:after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:before{background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:after{background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group.has-fade-start:before,.ef-pill-group.has-fade-end:after{opacity:1}.ef-pill{flex:0 0 auto;scroll-snap-align:center;white-space:nowrap;display:inline-flex;align-items:center;gap:6px;block-size:var(--hit);padding-inline:14px;border:none;border-radius:var(--r-pill);background:transparent;color:var(--text-mute);font-family:inherit;font-size:12.5px;font-weight:500;letter-spacing:.01em;line-height:1;cursor:pointer;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-pill:hover:not(.is-active){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent);color:var(--text)}.ef-pill:focus-visible{outline:2px solid color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent);outline-offset:1px}.ef-pill.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}.ef-pill.is-active .ef-pill__sep,.ef-pill.is-active .ef-pill__count{color:color-mix(in srgb,var(--paper) 70%,transparent)}.ef-pill__sep{color:var(--text-soft)}.ef-pill__count{font-variant-numeric:tabular-nums;color:var(--text-mute)}@media(pointer:coarse){.ef-pill{block-size:var(--hit-touch);padding-inline:16px;font-size:13px}}@media(max-width:767px){.ef-pill-group__track{scroll-padding-inline:var(--s-2)}}@media(max-width:767px)and (pointer:fine){.ef-pill-group__track{padding-block:calc((var(--hit-touch) - var(--hit)) / 2)}.ef-pill{position:relative;min-inline-size:var(--hit-touch);justify-content:center}.ef-pill:after{content:\"\";position:absolute;inset-inline:0;inset-block:calc((var(--hit) - var(--hit-touch)) / 2)}}@media(prefers-reduced-motion:reduce){.ef-pill-group:before,.ef-pill-group:after,.ef-pill{transition:none}.ef-pill-group__track{scroll-behavior:auto}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9422
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfPillGroupComponent, isStandalone: true, selector: "ef-pill-group", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, hideCounts: { classPropertyName: "hideCounts", publicName: "hideCounts", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"ef-pill-group\" [ngClass]=\"fadeClass()\">\n <div\n #scroller\n class=\"ef-pill-group__track\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabelKey() | translate\"\n (scroll)=\"onScroll()\"\n >\n @for (item of items(); track trackValue($index, item)) {\n <button\n type=\"button\"\n class=\"ef-pill\"\n role=\"tab\"\n [class.is-active]=\"item.value === selected()\"\n [attr.aria-selected]=\"item.value === selected()\"\n (click)=\"select(item)\"\n >\n <span class=\"ef-pill__label\">{{ item.labelKey | translate }}</span>\n @if (!hideCounts() && item.count !== undefined) {\n <span class=\"ef-pill__sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"ef-pill__count\">{{ item.count }}</span>\n }\n </button>\n }\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;min-width:0;max-width:100%;flex:1 1 auto}@media(max-width:767px){:host{flex-basis:100%}}.ef-pill-group{position:relative;display:flex;min-width:0;max-width:100%;inline-size:100%;border-radius:var(--r-pill)}.ef-pill-group__track{display:flex;align-items:center;gap:4px;padding:4px;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x proximity;border-radius:inherit;scrollbar-width:none;-ms-overflow-style:none}.ef-pill-group__track::-webkit-scrollbar{display:none}.ef-pill-group:before,.ef-pill-group:after{content:\"\";position:absolute;inset-block:0;inline-size:28px;pointer-events:none;opacity:0;transition:opacity var(--t-fast, .14s) var(--ease-out, ease-out);z-index:1}.ef-pill-group:before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group:after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:before{background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:after{background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group.has-fade-start:before,.ef-pill-group.has-fade-end:after{opacity:1}.ef-pill{flex:0 0 auto;scroll-snap-align:center;white-space:nowrap;display:inline-flex;align-items:center;gap:6px;block-size:var(--hit);padding-inline:14px;border:none;border-radius:var(--r-pill);background:transparent;color:var(--text-mute);font-family:inherit;font-size:12.5px;font-weight:500;letter-spacing:.01em;line-height:1;cursor:pointer;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-pill:hover:not(.is-active){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent);color:var(--text)}.ef-pill:focus-visible{outline:2px solid color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent);outline-offset:1px}.ef-pill.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}.ef-pill.is-active .ef-pill__sep,.ef-pill.is-active .ef-pill__count{color:color-mix(in srgb,var(--paper) 70%,transparent)}.ef-pill__sep{color:var(--text-soft)}.ef-pill__count{font-variant-numeric:tabular-nums;color:var(--text-mute)}@media(pointer:coarse){.ef-pill{block-size:var(--hit-touch);padding-inline:16px;font-size:13px}}@media(max-width:767px){.ef-pill-group__track{scroll-padding-inline:var(--s-2)}}@media(max-width:767px)and (pointer:fine){.ef-pill-group__track{padding-block:calc((var(--hit-touch) - var(--hit)) / 2)}.ef-pill{position:relative;min-inline-size:var(--hit-touch);justify-content:center}.ef-pill:after{content:\"\";position:absolute;inset-inline:0;inset-block:calc((var(--hit) - var(--hit-touch)) / 2)}}@media(prefers-reduced-motion:reduce){.ef-pill-group:before,.ef-pill-group:after,.ef-pill{transition:none}.ef-pill-group__track{scroll-behavior:auto}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9329
9423
  }
9330
9424
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfPillGroupComponent, decorators: [{
9331
9425
  type: Component,
9332
- args: [{ selector: 'ef-pill-group', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule], template: "<div class=\"ef-pill-group\" [ngClass]=\"fadeClass()\">\n <div\n #scroller\n class=\"ef-pill-group__track\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabelKey() | translate\"\n (scroll)=\"onScroll()\"\n >\n @for (item of items(); track trackValue($index, item)) {\n <button\n type=\"button\"\n class=\"ef-pill\"\n role=\"tab\"\n [class.is-active]=\"item.value === value()\"\n [attr.aria-selected]=\"item.value === value()\"\n (click)=\"select(item)\"\n >\n <span class=\"ef-pill__label\">{{ item.labelKey | translate }}</span>\n @if (!hideCounts() && item.count !== undefined) {\n <span class=\"ef-pill__sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"ef-pill__count\">{{ item.count }}</span>\n }\n </button>\n }\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;min-width:0;max-width:100%;flex:1 1 auto}@media(max-width:767px){:host{flex-basis:100%}}.ef-pill-group{position:relative;display:flex;min-width:0;max-width:100%;inline-size:100%;border-radius:var(--r-pill)}.ef-pill-group__track{display:flex;align-items:center;gap:4px;padding:4px;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x proximity;border-radius:inherit;scrollbar-width:none;-ms-overflow-style:none}.ef-pill-group__track::-webkit-scrollbar{display:none}.ef-pill-group:before,.ef-pill-group:after{content:\"\";position:absolute;inset-block:0;inline-size:28px;pointer-events:none;opacity:0;transition:opacity var(--t-fast, .14s) var(--ease-out, ease-out);z-index:1}.ef-pill-group:before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group:after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:before{background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:after{background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group.has-fade-start:before,.ef-pill-group.has-fade-end:after{opacity:1}.ef-pill{flex:0 0 auto;scroll-snap-align:center;white-space:nowrap;display:inline-flex;align-items:center;gap:6px;block-size:var(--hit);padding-inline:14px;border:none;border-radius:var(--r-pill);background:transparent;color:var(--text-mute);font-family:inherit;font-size:12.5px;font-weight:500;letter-spacing:.01em;line-height:1;cursor:pointer;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-pill:hover:not(.is-active){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent);color:var(--text)}.ef-pill:focus-visible{outline:2px solid color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent);outline-offset:1px}.ef-pill.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}.ef-pill.is-active .ef-pill__sep,.ef-pill.is-active .ef-pill__count{color:color-mix(in srgb,var(--paper) 70%,transparent)}.ef-pill__sep{color:var(--text-soft)}.ef-pill__count{font-variant-numeric:tabular-nums;color:var(--text-mute)}@media(pointer:coarse){.ef-pill{block-size:var(--hit-touch);padding-inline:16px;font-size:13px}}@media(max-width:767px){.ef-pill-group__track{scroll-padding-inline:var(--s-2)}}@media(max-width:767px)and (pointer:fine){.ef-pill-group__track{padding-block:calc((var(--hit-touch) - var(--hit)) / 2)}.ef-pill{position:relative;min-inline-size:var(--hit-touch);justify-content:center}.ef-pill:after{content:\"\";position:absolute;inset-inline:0;inset-block:calc((var(--hit) - var(--hit-touch)) / 2)}}@media(prefers-reduced-motion:reduce){.ef-pill-group:before,.ef-pill-group:after,.ef-pill{transition:none}.ef-pill-group__track{scroll-behavior:auto}}\n"] }]
9333
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], hideCounts: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideCounts", required: false }] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }] } });
9426
+ args: [{ selector: 'ef-pill-group', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule], template: "<div class=\"ef-pill-group\" [ngClass]=\"fadeClass()\">\n <div\n #scroller\n class=\"ef-pill-group__track\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabelKey() | translate\"\n (scroll)=\"onScroll()\"\n >\n @for (item of items(); track trackValue($index, item)) {\n <button\n type=\"button\"\n class=\"ef-pill\"\n role=\"tab\"\n [class.is-active]=\"item.value === selected()\"\n [attr.aria-selected]=\"item.value === selected()\"\n (click)=\"select(item)\"\n >\n <span class=\"ef-pill__label\">{{ item.labelKey | translate }}</span>\n @if (!hideCounts() && item.count !== undefined) {\n <span class=\"ef-pill__sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"ef-pill__count\">{{ item.count }}</span>\n }\n </button>\n }\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;min-width:0;max-width:100%;flex:1 1 auto}@media(max-width:767px){:host{flex-basis:100%}}.ef-pill-group{position:relative;display:flex;min-width:0;max-width:100%;inline-size:100%;border-radius:var(--r-pill)}.ef-pill-group__track{display:flex;align-items:center;gap:4px;padding:4px;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x proximity;border-radius:inherit;scrollbar-width:none;-ms-overflow-style:none}.ef-pill-group__track::-webkit-scrollbar{display:none}.ef-pill-group:before,.ef-pill-group:after{content:\"\";position:absolute;inset-block:0;inline-size:28px;pointer-events:none;opacity:0;transition:opacity var(--t-fast, .14s) var(--ease-out, ease-out);z-index:1}.ef-pill-group:before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group:after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:before{background:linear-gradient(to left,var(--paper-alt) 20%,transparent)}[dir=rtl] .ef-pill-group:after{background:linear-gradient(to right,var(--paper-alt) 20%,transparent)}.ef-pill-group.has-fade-start:before,.ef-pill-group.has-fade-end:after{opacity:1}.ef-pill{flex:0 0 auto;scroll-snap-align:center;white-space:nowrap;display:inline-flex;align-items:center;gap:6px;block-size:var(--hit);padding-inline:14px;border:none;border-radius:var(--r-pill);background:transparent;color:var(--text-mute);font-family:inherit;font-size:12.5px;font-weight:500;letter-spacing:.01em;line-height:1;cursor:pointer;transition:background var(--t-fast, .14s) var(--ease-out, ease-out),color var(--t-fast, .14s) var(--ease-out, ease-out)}.ef-pill:hover:not(.is-active){background:color-mix(in srgb,var(--ink-active, #0f1115) 6%,transparent);color:var(--text)}.ef-pill:focus-visible{outline:2px solid color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent);outline-offset:1px}.ef-pill.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}.ef-pill.is-active .ef-pill__sep,.ef-pill.is-active .ef-pill__count{color:color-mix(in srgb,var(--paper) 70%,transparent)}.ef-pill__sep{color:var(--text-soft)}.ef-pill__count{font-variant-numeric:tabular-nums;color:var(--text-mute)}@media(pointer:coarse){.ef-pill{block-size:var(--hit-touch);padding-inline:16px;font-size:13px}}@media(max-width:767px){.ef-pill-group__track{scroll-padding-inline:var(--s-2)}}@media(max-width:767px)and (pointer:fine){.ef-pill-group__track{padding-block:calc((var(--hit-touch) - var(--hit)) / 2)}.ef-pill{position:relative;min-inline-size:var(--hit-touch);justify-content:center}.ef-pill:after{content:\"\";position:absolute;inset-inline:0;inset-block:calc((var(--hit) - var(--hit-touch)) / 2)}}@media(prefers-reduced-motion:reduce){.ef-pill-group:before,.ef-pill-group:after,.ef-pill{transition:none}.ef-pill-group__track{scroll-behavior:auto}}\n"] }]
9427
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], hideCounts: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideCounts", required: false }] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }] } });
9334
9428
 
9335
9429
  /**
9336
9430
  * Marks an `<ng-template>` as the panel renderer for a tab value on
@@ -9426,7 +9520,10 @@ class EfTabsComponent {
9426
9520
  overflow = false;
9427
9521
  /** Hide count badges even when items provide them. */
9428
9522
  hideCounts = false;
9523
+ /** Untranslated accessible name of the tablist. Prefer `ariaLabelKey`. */
9429
9524
  ariaLabel = 'tabs';
9525
+ /** Translation key for the tablist's accessible name; wins over `ariaLabel`. */
9526
+ ariaLabelKey;
9430
9527
  /** Translation key used as the `aria-label` of the dirty dot.
9431
9528
  * Defaults to `'common.dirty'` — apps add the matching entry to
9432
9529
  * `fr.json` / `ar.json`. */
@@ -9505,7 +9602,7 @@ class EfTabsComponent {
9505
9602
  return item.value;
9506
9603
  }
9507
9604
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9508
- 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:var(--font-mono);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 });
9605
+ 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", ariaLabelKey: "ariaLabelKey", 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]=\"ariaLabelKey ? (ariaLabelKey | translate) : 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:var(--font-mono);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 });
9509
9606
  }
9510
9607
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, decorators: [{
9511
9608
  type: Component,
@@ -9514,7 +9611,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9514
9611
  '[class.ef-tabs--underline]': "variant === 'underline'",
9515
9612
  '[class.ef-tabs--overflow]': 'overflow',
9516
9613
  '[attr.data-module]': 'module ?? null',
9517
- }, 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:var(--font-mono);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"] }]
9614
+ }, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabelKey ? (ariaLabelKey | translate) : 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:var(--font-mono);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"] }]
9518
9615
  }], propDecorators: { items: [{
9519
9616
  type: Input,
9520
9617
  args: [{ required: true }]
@@ -9532,6 +9629,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9532
9629
  args: [{ transform: booleanAttribute }]
9533
9630
  }], ariaLabel: [{
9534
9631
  type: Input
9632
+ }], ariaLabelKey: [{
9633
+ type: Input
9535
9634
  }], dirtyLabelKey: [{
9536
9635
  type: Input
9537
9636
  }], valueChange: [{
@@ -9815,8 +9914,13 @@ class EfPdfPreviewComponent {
9815
9914
  showOpenInNewTab = input(true, { ...(ngDevMode ? { debugName: "showOpenInNewTab" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9816
9915
  /** Show the WhatsApp share button (Web Share API → wa.me fallback). */
9817
9916
  showWhatsappShare = input(false, { ...(ngDevMode ? { debugName: "showWhatsappShare" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9818
- /** Optional recipient phone for the wa.me fallback (digits / +). */
9917
+ /** Optional recipient phone for the wa.me fallback, as typed: international
9918
+ * (`+`/`00`) or national with `defaultCallingCode`. */
9819
9919
  whatsappPhone = input('', ...(ngDevMode ? [{ debugName: "whatsappPhone" }] : /* istanbul ignore next */ []));
9920
+ /** Calling code (`'212'` or `'+212'`) that turns a national
9921
+ * `whatsappPhone` (`0612345678`) into the international number wa.me
9922
+ * needs. Without it a national number opens the contact picker. */
9923
+ defaultCallingCode = input('', ...(ngDevMode ? [{ debugName: "defaultCallingCode" }] : /* istanbul ignore next */ []));
9820
9924
  objectUrl = signal(null, ...(ngDevMode ? [{ debugName: "objectUrl" }] : /* istanbul ignore next */ []));
9821
9925
  currentUrl = null;
9822
9926
  safeUrl = computed(() => {
@@ -9926,7 +10030,10 @@ class EfPdfPreviewComponent {
9926
10030
  this.openWhatsappWeb();
9927
10031
  }
9928
10032
  openWhatsappWeb() {
9929
- const phone = (this.whatsappPhone() || '').replace(/[^0-9+]/g, '');
10033
+ // wa.me takes the international number as digits only: a leading "+"
10034
+ // or a national number lands on WhatsApp's error page, not the chat.
10035
+ const e164 = PhoneUtils.toE164(this.whatsappPhone(), this.defaultCallingCode());
10036
+ const phone = e164 ? e164.slice(1) : '';
9930
10037
  const msg = encodeURIComponent(this.fileName());
9931
10038
  const url = phone
9932
10039
  ? `https://wa.me/${phone}?text=${msg}`
@@ -9934,12 +10041,12 @@ class EfPdfPreviewComponent {
9934
10041
  window.open(url, '_blank');
9935
10042
  }
9936
10043
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfPdfPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9937
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfPdfPreviewComponent, isStandalone: true, selector: "ef-pdf-preview", inputs: { blob: { classPropertyName: "blob", publicName: "blob", isSignal: true, isRequired: false, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, showDownload: { classPropertyName: "showDownload", publicName: "showDownload", isSignal: true, isRequired: false, transformFunction: null }, showPrint: { classPropertyName: "showPrint", publicName: "showPrint", isSignal: true, isRequired: false, transformFunction: null }, showOpenInNewTab: { classPropertyName: "showOpenInNewTab", publicName: "showOpenInNewTab", isSignal: true, isRequired: false, transformFunction: null }, showWhatsappShare: { classPropertyName: "showWhatsappShare", publicName: "showWhatsappShare", isSignal: true, isRequired: false, transformFunction: null }, whatsappPhone: { classPropertyName: "whatsappPhone", publicName: "whatsappPhone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange" }, ngImport: i0, template: "<ef-dialog\n [(visible)]=\"visible\"\n [headerKey]=\"titleKey()\"\n [subtitle]=\"fileName()\"\n icon=\"pi pi-file-pdf\"\n [style]=\"{ width: '92vw', 'max-width': '1280px' }\"\n styleClass=\"ef-pdf-preview-dialog\"\n [actions]=\"actions()\"\n>\n <div class=\"pdf-frame-wrap\">\n @if (safeUrl()) {\n <iframe class=\"pdf-frame\" [src]=\"safeUrl()\" [title]=\"fileName()\"></iframe>\n } @else {\n <div class=\"pdf-empty\">{{ 'pdf_preview.empty' | translate }}</div>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.pdf-frame-wrap{height:75vh;min-height:320px}.pdf-frame{width:100%;height:100%;border:0;border-radius:var(--r-md);background:#525659}.pdf-empty{display:grid;place-items:center;height:100%;color:var(--text-mute);font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfDialogComponent, selector: "ef-dialog", inputs: ["header", "headerKey", "subtitle", "subtitleKey", "visible", "modal", "closable", "draggable", "resizable", "dismissableMask", "closeOnEscape", "severity", "icon", "size", "style", "styleClass", "position", "appendTo", "variant", "actions", "defaultActions", "saveLabelKey", "cancelLabelKey", "saveLabel", "cancelLabel", "saveIcon", "cancelIcon", "saveDisabled", "saveStyleClass", "cancelStyleClass", "saveSeverity", "cancelSeverity"], outputs: ["visibleChange", "showEvent", "hideEvent", "saveAction", "cancelAction", "actionClick"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10044
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfPdfPreviewComponent, isStandalone: true, selector: "ef-pdf-preview", inputs: { blob: { classPropertyName: "blob", publicName: "blob", isSignal: true, isRequired: false, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, showDownload: { classPropertyName: "showDownload", publicName: "showDownload", isSignal: true, isRequired: false, transformFunction: null }, showPrint: { classPropertyName: "showPrint", publicName: "showPrint", isSignal: true, isRequired: false, transformFunction: null }, showOpenInNewTab: { classPropertyName: "showOpenInNewTab", publicName: "showOpenInNewTab", isSignal: true, isRequired: false, transformFunction: null }, showWhatsappShare: { classPropertyName: "showWhatsappShare", publicName: "showWhatsappShare", isSignal: true, isRequired: false, transformFunction: null }, whatsappPhone: { classPropertyName: "whatsappPhone", publicName: "whatsappPhone", isSignal: true, isRequired: false, transformFunction: null }, defaultCallingCode: { classPropertyName: "defaultCallingCode", publicName: "defaultCallingCode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange" }, ngImport: i0, template: "<ef-dialog\n [(visible)]=\"visible\"\n [headerKey]=\"titleKey()\"\n [subtitle]=\"fileName()\"\n icon=\"pi pi-file-pdf\"\n [style]=\"{ width: '92vw', 'max-width': '1280px' }\"\n styleClass=\"ef-pdf-preview-dialog\"\n [actions]=\"actions()\"\n>\n <div class=\"pdf-frame-wrap\">\n @if (safeUrl()) {\n <iframe class=\"pdf-frame\" [src]=\"safeUrl()\" [title]=\"fileName()\"></iframe>\n } @else {\n <div class=\"pdf-empty\">{{ 'pdf_preview.empty' | translate }}</div>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.pdf-frame-wrap{height:75vh;min-height:320px}.pdf-frame{width:100%;height:100%;border:0;border-radius:var(--r-md);background:#525659}.pdf-empty{display:grid;place-items:center;height:100%;color:var(--text-mute);font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfDialogComponent, selector: "ef-dialog", inputs: ["header", "headerKey", "subtitle", "subtitleKey", "visible", "modal", "closable", "draggable", "resizable", "dismissableMask", "closeOnEscape", "severity", "icon", "size", "style", "styleClass", "position", "appendTo", "variant", "actions", "defaultActions", "saveLabelKey", "cancelLabelKey", "saveLabel", "cancelLabel", "saveIcon", "cancelIcon", "saveDisabled", "saveStyleClass", "cancelStyleClass", "saveSeverity", "cancelSeverity"], outputs: ["visibleChange", "showEvent", "hideEvent", "saveAction", "cancelAction", "actionClick"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9938
10045
  }
9939
10046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfPdfPreviewComponent, decorators: [{
9940
10047
  type: Component,
9941
10048
  args: [{ selector: 'ef-pdf-preview', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, TranslateModule, EfDialogComponent], template: "<ef-dialog\n [(visible)]=\"visible\"\n [headerKey]=\"titleKey()\"\n [subtitle]=\"fileName()\"\n icon=\"pi pi-file-pdf\"\n [style]=\"{ width: '92vw', 'max-width': '1280px' }\"\n styleClass=\"ef-pdf-preview-dialog\"\n [actions]=\"actions()\"\n>\n <div class=\"pdf-frame-wrap\">\n @if (safeUrl()) {\n <iframe class=\"pdf-frame\" [src]=\"safeUrl()\" [title]=\"fileName()\"></iframe>\n } @else {\n <div class=\"pdf-empty\">{{ 'pdf_preview.empty' | translate }}</div>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.pdf-frame-wrap{height:75vh;min-height:320px}.pdf-frame{width:100%;height:100%;border:0;border-radius:var(--r-md);background:#525659}.pdf-empty{display:grid;place-items:center;height:100%;color:var(--text-mute);font-size:13px}\n"] }]
9942
- }], ctorParameters: () => [], propDecorators: { blob: [{ type: i0.Input, args: [{ isSignal: true, alias: "blob", required: false }] }], fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], showDownload: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDownload", required: false }] }], showPrint: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPrint", required: false }] }], showOpenInNewTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOpenInNewTab", required: false }] }], showWhatsappShare: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWhatsappShare", required: false }] }], whatsappPhone: [{ type: i0.Input, args: [{ isSignal: true, alias: "whatsappPhone", required: false }] }] } });
10049
+ }], ctorParameters: () => [], propDecorators: { blob: [{ type: i0.Input, args: [{ isSignal: true, alias: "blob", required: false }] }], fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], showDownload: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDownload", required: false }] }], showPrint: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPrint", required: false }] }], showOpenInNewTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOpenInNewTab", required: false }] }], showWhatsappShare: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWhatsappShare", required: false }] }], whatsappPhone: [{ type: i0.Input, args: [{ isSignal: true, alias: "whatsappPhone", required: false }] }], defaultCallingCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCallingCode", required: false }] }] } });
9943
10050
 
9944
10051
  /**
9945
10052
  * Comptoir toast region — V2 successor to PrimeNG `<p-toast>`.
@@ -10158,13 +10265,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10158
10265
  * ```
10159
10266
  *
10160
10267
  * Phone overflow: on the mobile tier, when the visible actions (custom and
10161
- * standard, Save included) outnumber `[overflowAfter]`, Save stays in view
10162
- * and every other action moves into a kebab menu (`ef-row-actions`, so it is
10268
+ * standard, Save included) outnumber `[overflowAfter]`, Save and any custom
10269
+ * action flagged `keepInView` stay in view and every other action moves
10270
+ * into a kebab menu (`ef-row-actions`, so it is
10163
10271
  * a `role="menu"` with Escape, outside-click and RTL placement). The split
10164
10272
  * happens after permission and `requiresWrite` filtering, so a hidden action
10165
10273
  * is hidden in the menu too. The projected `[actions]` slot is not moved.
10166
10274
  * Buttons are 48px on a coarse pointer (see `.detail-toolbar` in patterns).
10167
10275
  *
10276
+ * Tablet and desktop reflow: the identity has a basis
10277
+ * (`--detail-toolbar-identity-min`, 22rem), so when it and the actions
10278
+ * cannot share a row the actions wrap below it, rather than squeezing the
10279
+ * identity until its notices are cut.
10280
+ *
10168
10281
  * Custom actions can declare `permission` and `visible` like
10169
10282
  * `EfRowAction`. With `[context]` bound, the toolbar filters them
10170
10283
  * against `ScreenContext.isGranted()` automatically.
@@ -10241,9 +10354,11 @@ class EfDetailToolbarComponent {
10241
10354
  [this.canPrint(), this.canDuplicate(), this.canDelete(), this.canSave()].filter(Boolean).length;
10242
10355
  return total > this.overflowAfter();
10243
10356
  }, ...(ngDevMode ? [{ debugName: "overflowing" }] : /* istanbul ignore next */ []));
10244
- /** Everything but Save, as menu items, in toolbar order. */
10357
+ /** Visible custom actions that stay inline on a phone even when the rest overflow. */
10358
+ keptInView = computed(() => this.visibleCustomActions().filter((a) => a.keepInView), ...(ngDevMode ? [{ debugName: "keptInView" }] : /* istanbul ignore next */ []));
10359
+ /** Everything but Save and the keepInView actions, as menu items, in toolbar order. */
10245
10360
  overflowItems = computed(() => {
10246
- const items = this.visibleCustomActions().map((a, i) => ({
10361
+ const items = this.visibleCustomActions().filter((a) => !a.keepInView).map((a, i) => ({
10247
10362
  id: `custom-${a.id ?? i}`,
10248
10363
  labelKey: a.labelKey,
10249
10364
  label: a.label,
@@ -10266,6 +10381,34 @@ class EfDetailToolbarComponent {
10266
10381
  }, ...(ngDevMode ? [{ debugName: "overflowItems" }] : /* istanbul ignore next */ []));
10267
10382
  /** Whether the `|` separator should render — both groups present. */
10268
10383
  showSeparator = computed(() => this.hasCustomActions() && this.hasStandardActions(), ...(ngDevMode ? [{ debugName: "showSeparator" }] : /* istanbul ignore next */ []));
10384
+ /* ── Wrap of the standard group ─────────────────────────────── */
10385
+ host = inject(ElementRef);
10386
+ destroyRef = inject(DestroyRef);
10387
+ /** The standard group (separator, Print … Save) starts a row of its own,
10388
+ * below the custom actions: its leading separator would separate nothing. */
10389
+ standardWrapped = signal(false, ...(ngDevMode ? [{ debugName: "standardWrapped" }] : /* istanbul ignore next */ []));
10390
+ constructor() {
10391
+ afterNextRender(() => {
10392
+ if (typeof ResizeObserver === 'undefined')
10393
+ return;
10394
+ const right = this.host.nativeElement.querySelector('.right');
10395
+ if (!right)
10396
+ return;
10397
+ const observer = new ResizeObserver(() => this.measureWrap());
10398
+ observer.observe(right);
10399
+ this.destroyRef.onDestroy(() => observer.disconnect());
10400
+ });
10401
+ }
10402
+ /** Compares the standard group's row with the first item of the right
10403
+ * group. Public for specs: jsdom lays nothing out. */
10404
+ measureWrap() {
10405
+ const right = this.host.nativeElement.querySelector('.right');
10406
+ const std = right?.querySelector(':scope > .std');
10407
+ const first = right?.firstElementChild;
10408
+ const wrapped = !!std && !!first && first !== std && std.getBoundingClientRect().top > first.getBoundingClientRect().top + 1;
10409
+ if (wrapped !== this.standardWrapped())
10410
+ this.standardWrapped.set(wrapped);
10411
+ }
10269
10412
  /* ── Click bridge ───────────────────────────────────────────── */
10270
10413
  onCustomClick(action) {
10271
10414
  if (action.disabled)
@@ -10305,12 +10448,12 @@ class EfDetailToolbarComponent {
10305
10448
  /** Stable trackBy across renders. */
10306
10449
  trackByAction = (i, a) => a.id ?? a.labelKey ?? a.label ?? i;
10307
10450
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDetailToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10308
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDetailToolbarComponent, isStandalone: true, selector: "ef-detail-toolbar", inputs: { customActions: { classPropertyName: "customActions", publicName: "customActions", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, showPrintAction: { classPropertyName: "showPrintAction", publicName: "showPrintAction", 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 }, showSaveAction: { classPropertyName: "showSaveAction", publicName: "showSaveAction", isSignal: true, isRequired: false, transformFunction: null }, saving: { classPropertyName: "saving", publicName: "saving", isSignal: true, isRequired: false, transformFunction: null }, saveLabelKey: { classPropertyName: "saveLabelKey", publicName: "saveLabelKey", isSignal: true, isRequired: false, transformFunction: null }, overflowAfter: { classPropertyName: "overflowAfter", publicName: "overflowAfter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { customAction: "customAction", print: "print", duplicate: "duplicate", delete: "delete", save: "save" }, host: { classAttribute: "detail-toolbar" }, ngImport: i0, template: "<div class=\"left\">\n <ng-content></ng-content>\n</div>\n\n<div class=\"right\">\n @if (overflowing()) {\n <!-- Phone, too many actions: everything but Save sits in a menu. -->\n <ef-row-actions\n class=\"overflow\"\n [items]=\"overflowItems()\"\n triggerIcon=\"pi pi-ellipsis-v\"\n triggerAriaKey=\"common_actions\"\n />\n }\n\n <!-- Custom screen actions (before the standard group). -->\n @for (action of (overflowing() ? [] : visibleCustomActions()); track trackByAction($index, action)) {\n <ef-button\n variant=\"comptoir\"\n size=\"small\"\n [severity]=\"action.severity ?? 'ghost'\"\n [icon]=\"action.icon\"\n [label]=\"action.label\"\n [labelKey]=\"action.labelKey\"\n [disabled]=\"!!action.disabled\"\n (clickEvent)=\"onCustomClick(action)\"\n />\n }\n\n <!-- Full-template escape hatch \u2014 anything projected here renders\n after the customActions array, before the standard group. -->\n <ng-content select=\"[actions]\"></ng-content>\n\n <!-- Separator between custom and standard groups. -->\n @if (showSeparator() && !overflowing()) {\n <span class=\"sep\" aria-hidden=\"true\">|</span>\n }\n\n <!-- Standard actions: Print \u2192 Duplicate \u2192 Delete \u2192 Save. -->\n @if (canPrint() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-print\"\n labelKey=\"common_print\"\n (clickEvent)=\"print.emit()\"\n />\n }\n\n @if (canDuplicate() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-clone\"\n labelKey=\"common_duplicate\"\n (clickEvent)=\"duplicate.emit()\"\n />\n }\n\n @if (canDelete() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"danger\"\n size=\"small\"\n icon=\"pi pi-trash\"\n labelKey=\"common_delete\"\n (clickEvent)=\"delete.emit()\"\n />\n }\n\n @if (canSave()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"tenant\"\n size=\"small\"\n icon=\"pi pi-save\"\n [labelKey]=\"saveLabelKey()\"\n [disabled]=\"saving()\"\n [loading]=\"saving()\"\n (clickEvent)=\"save.emit()\"\n />\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "component", type: EfRowActionsComponent, selector: "ef-row-actions", inputs: ["items", "context", "triggerIcon", "triggerAriaKey", "disabled", "menuLeft"], outputs: ["opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10451
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDetailToolbarComponent, isStandalone: true, selector: "ef-detail-toolbar", inputs: { customActions: { classPropertyName: "customActions", publicName: "customActions", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, showPrintAction: { classPropertyName: "showPrintAction", publicName: "showPrintAction", 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 }, showSaveAction: { classPropertyName: "showSaveAction", publicName: "showSaveAction", isSignal: true, isRequired: false, transformFunction: null }, saving: { classPropertyName: "saving", publicName: "saving", isSignal: true, isRequired: false, transformFunction: null }, saveLabelKey: { classPropertyName: "saveLabelKey", publicName: "saveLabelKey", isSignal: true, isRequired: false, transformFunction: null }, overflowAfter: { classPropertyName: "overflowAfter", publicName: "overflowAfter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { customAction: "customAction", print: "print", duplicate: "duplicate", delete: "delete", save: "save" }, host: { classAttribute: "detail-toolbar" }, ngImport: i0, template: "<div class=\"left\">\n <ng-content></ng-content>\n</div>\n\n<div class=\"right\">\n @if (overflowing()) {\n <!-- Phone, too many actions: everything but Save and the keepInView actions sits in a menu. -->\n <ef-row-actions\n class=\"overflow\"\n [items]=\"overflowItems()\"\n triggerIcon=\"pi pi-ellipsis-v\"\n triggerAriaKey=\"common_actions\"\n />\n }\n\n <!-- Custom screen actions (before the standard group). Each is named by\n its label key, so it keeps a name when a phone shows only its icon;\n a keepInView action keeps its visible label there too. -->\n @for (action of (overflowing() ? keptInView() : visibleCustomActions()); track trackByAction($index, action)) {\n <ef-button\n variant=\"comptoir\"\n size=\"small\"\n [severity]=\"action.severity ?? 'ghost'\"\n [icon]=\"action.icon\"\n [label]=\"action.label\"\n [labelKey]=\"action.labelKey\"\n [ariaLabelKey]=\"action.labelKey || action.label\"\n [styleClass]=\"action.keepInView ? 'btn--keep-label' : undefined\"\n [disabled]=\"!!action.disabled\"\n (clickEvent)=\"onCustomClick(action)\"\n />\n }\n\n <!-- Full-template escape hatch \u2014 anything projected here renders\n after the customActions array, before the standard group. -->\n <ng-content select=\"[actions]\"></ng-content>\n\n <!-- Standard actions: Print \u2192 Duplicate \u2192 Delete \u2192 Save, in one group\n that never wraps inside itself, with the separator at its head. When\n the group drops to a row of its own the separator turns invisible but\n keeps its width, so the wrap state cannot flip back and forth. -->\n @if (hasStandardActions()) {\n <span class=\"std\">\n @if (showSeparator() && !overflowing()) {\n <span class=\"sep\" [class.is-hidden]=\"standardWrapped()\" aria-hidden=\"true\">|</span>\n }\n\n @if (canPrint() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-print\"\n labelKey=\"common_print\"\n ariaLabelKey=\"common_print\"\n (clickEvent)=\"print.emit()\"\n />\n }\n\n @if (canDuplicate() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-clone\"\n labelKey=\"common_duplicate\"\n ariaLabelKey=\"common_duplicate\"\n (clickEvent)=\"duplicate.emit()\"\n />\n }\n\n @if (canDelete() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"danger\"\n size=\"small\"\n icon=\"pi pi-trash\"\n labelKey=\"common_delete\"\n ariaLabelKey=\"common_delete\"\n (clickEvent)=\"delete.emit()\"\n />\n }\n\n @if (canSave()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"tenant\"\n size=\"small\"\n icon=\"pi pi-save\"\n [labelKey]=\"saveLabelKey()\"\n [ariaLabelKey]=\"saveLabelKey()\"\n [disabled]=\"saving()\"\n [loading]=\"saving()\"\n (clickEvent)=\"save.emit()\"\n />\n }\n </span>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "component", type: EfRowActionsComponent, selector: "ef-row-actions", inputs: ["items", "context", "triggerIcon", "triggerAriaKey", "disabled", "menuLeft"], outputs: ["opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10309
10452
  }
10310
10453
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDetailToolbarComponent, decorators: [{
10311
10454
  type: Component,
10312
- args: [{ selector: 'ef-detail-toolbar', standalone: true, imports: [CommonModule, TranslateModule, EfButtonComponent, EfRowActionsComponent], host: { class: 'detail-toolbar' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"left\">\n <ng-content></ng-content>\n</div>\n\n<div class=\"right\">\n @if (overflowing()) {\n <!-- Phone, too many actions: everything but Save sits in a menu. -->\n <ef-row-actions\n class=\"overflow\"\n [items]=\"overflowItems()\"\n triggerIcon=\"pi pi-ellipsis-v\"\n triggerAriaKey=\"common_actions\"\n />\n }\n\n <!-- Custom screen actions (before the standard group). -->\n @for (action of (overflowing() ? [] : visibleCustomActions()); track trackByAction($index, action)) {\n <ef-button\n variant=\"comptoir\"\n size=\"small\"\n [severity]=\"action.severity ?? 'ghost'\"\n [icon]=\"action.icon\"\n [label]=\"action.label\"\n [labelKey]=\"action.labelKey\"\n [disabled]=\"!!action.disabled\"\n (clickEvent)=\"onCustomClick(action)\"\n />\n }\n\n <!-- Full-template escape hatch \u2014 anything projected here renders\n after the customActions array, before the standard group. -->\n <ng-content select=\"[actions]\"></ng-content>\n\n <!-- Separator between custom and standard groups. -->\n @if (showSeparator() && !overflowing()) {\n <span class=\"sep\" aria-hidden=\"true\">|</span>\n }\n\n <!-- Standard actions: Print \u2192 Duplicate \u2192 Delete \u2192 Save. -->\n @if (canPrint() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-print\"\n labelKey=\"common_print\"\n (clickEvent)=\"print.emit()\"\n />\n }\n\n @if (canDuplicate() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-clone\"\n labelKey=\"common_duplicate\"\n (clickEvent)=\"duplicate.emit()\"\n />\n }\n\n @if (canDelete() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"danger\"\n size=\"small\"\n icon=\"pi pi-trash\"\n labelKey=\"common_delete\"\n (clickEvent)=\"delete.emit()\"\n />\n }\n\n @if (canSave()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"tenant\"\n size=\"small\"\n icon=\"pi pi-save\"\n [labelKey]=\"saveLabelKey()\"\n [disabled]=\"saving()\"\n [loading]=\"saving()\"\n (clickEvent)=\"save.emit()\"\n />\n }\n</div>\n" }]
10313
- }], propDecorators: { customActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "customActions", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], showPrintAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPrintAction", required: false }] }], showDuplicateAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDuplicateAction", required: false }] }], showDeleteAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDeleteAction", required: false }] }], showSaveAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSaveAction", required: false }] }], saving: [{ type: i0.Input, args: [{ isSignal: true, alias: "saving", required: false }] }], saveLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "saveLabelKey", required: false }] }], overflowAfter: [{ type: i0.Input, args: [{ isSignal: true, alias: "overflowAfter", required: false }] }], customAction: [{ type: i0.Output, args: ["customAction"] }], print: [{ type: i0.Output, args: ["print"] }], duplicate: [{ type: i0.Output, args: ["duplicate"] }], delete: [{ type: i0.Output, args: ["delete"] }], save: [{ type: i0.Output, args: ["save"] }] } });
10455
+ args: [{ selector: 'ef-detail-toolbar', standalone: true, imports: [CommonModule, TranslateModule, EfButtonComponent, EfRowActionsComponent], host: { class: 'detail-toolbar' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"left\">\n <ng-content></ng-content>\n</div>\n\n<div class=\"right\">\n @if (overflowing()) {\n <!-- Phone, too many actions: everything but Save and the keepInView actions sits in a menu. -->\n <ef-row-actions\n class=\"overflow\"\n [items]=\"overflowItems()\"\n triggerIcon=\"pi pi-ellipsis-v\"\n triggerAriaKey=\"common_actions\"\n />\n }\n\n <!-- Custom screen actions (before the standard group). Each is named by\n its label key, so it keeps a name when a phone shows only its icon;\n a keepInView action keeps its visible label there too. -->\n @for (action of (overflowing() ? keptInView() : visibleCustomActions()); track trackByAction($index, action)) {\n <ef-button\n variant=\"comptoir\"\n size=\"small\"\n [severity]=\"action.severity ?? 'ghost'\"\n [icon]=\"action.icon\"\n [label]=\"action.label\"\n [labelKey]=\"action.labelKey\"\n [ariaLabelKey]=\"action.labelKey || action.label\"\n [styleClass]=\"action.keepInView ? 'btn--keep-label' : undefined\"\n [disabled]=\"!!action.disabled\"\n (clickEvent)=\"onCustomClick(action)\"\n />\n }\n\n <!-- Full-template escape hatch \u2014 anything projected here renders\n after the customActions array, before the standard group. -->\n <ng-content select=\"[actions]\"></ng-content>\n\n <!-- Standard actions: Print \u2192 Duplicate \u2192 Delete \u2192 Save, in one group\n that never wraps inside itself, with the separator at its head. When\n the group drops to a row of its own the separator turns invisible but\n keeps its width, so the wrap state cannot flip back and forth. -->\n @if (hasStandardActions()) {\n <span class=\"std\">\n @if (showSeparator() && !overflowing()) {\n <span class=\"sep\" [class.is-hidden]=\"standardWrapped()\" aria-hidden=\"true\">|</span>\n }\n\n @if (canPrint() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-print\"\n labelKey=\"common_print\"\n ariaLabelKey=\"common_print\"\n (clickEvent)=\"print.emit()\"\n />\n }\n\n @if (canDuplicate() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-clone\"\n labelKey=\"common_duplicate\"\n ariaLabelKey=\"common_duplicate\"\n (clickEvent)=\"duplicate.emit()\"\n />\n }\n\n @if (canDelete() && !overflowing()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"danger\"\n size=\"small\"\n icon=\"pi pi-trash\"\n labelKey=\"common_delete\"\n ariaLabelKey=\"common_delete\"\n (clickEvent)=\"delete.emit()\"\n />\n }\n\n @if (canSave()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"tenant\"\n size=\"small\"\n icon=\"pi pi-save\"\n [labelKey]=\"saveLabelKey()\"\n [ariaLabelKey]=\"saveLabelKey()\"\n [disabled]=\"saving()\"\n [loading]=\"saving()\"\n (clickEvent)=\"save.emit()\"\n />\n }\n </span>\n }\n</div>\n" }]
10456
+ }], ctorParameters: () => [], propDecorators: { customActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "customActions", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], showPrintAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPrintAction", required: false }] }], showDuplicateAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDuplicateAction", required: false }] }], showDeleteAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDeleteAction", required: false }] }], showSaveAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSaveAction", required: false }] }], saving: [{ type: i0.Input, args: [{ isSignal: true, alias: "saving", required: false }] }], saveLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "saveLabelKey", required: false }] }], overflowAfter: [{ type: i0.Input, args: [{ isSignal: true, alias: "overflowAfter", required: false }] }], customAction: [{ type: i0.Output, args: ["customAction"] }], print: [{ type: i0.Output, args: ["print"] }], duplicate: [{ type: i0.Output, args: ["duplicate"] }], delete: [{ type: i0.Output, args: ["delete"] }], save: [{ type: i0.Output, args: ["save"] }] } });
10314
10457
 
10315
10458
  /**
10316
10459
  * Page-level toolbar for list / search screens. Mirrors the
@@ -10947,7 +11090,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10947
11090
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.rank{list-style:none;margin:0;padding:0;display:grid;gap:10px}.rank-row{display:grid;grid-template-columns:22px 1fr auto;gap:10px;align-items:center}.rank-idx{font-size:12px;font-weight:700;color:var(--ink-soft, #64748b);text-align:center}.rank-main{display:grid;gap:3px;min-width:0}.rank-label{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.rank-secondary{font-size:11px;color:var(--ink-soft, #64748b)}.rank-bar{height:4px;border-radius:999px;background:var(--paper-alt, #f1f5f9);overflow:hidden}.rank-bar i{display:block;height:100%;border-radius:inherit;background:var(--tenant-500, #0d9488)}.rank-value{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}\n"] }]
10948
11091
  }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }] } });
10949
11092
 
10950
- let nextId = 0;
11093
+ let nextId$1 = 0;
10951
11094
  /** Cuts at a code point count (never inside a surrogate pair) and adds an ellipsis. */
10952
11095
  function clip(text, max) {
10953
11096
  const value = (text ?? '').trim();
@@ -10988,7 +11131,7 @@ class EfSeoPreviewComponent {
10988
11131
  descriptionMaxLength = input(160, ...(ngDevMode ? [{ debugName: "descriptionMaxLength" }] : /* istanbul ignore next */ []));
10989
11132
  /** Language of the previewed text (`fr`, `ar`, `en`), for screen readers and hyphenation. */
10990
11133
  contentLang = input(null, ...(ngDevMode ? [{ debugName: "contentLang" }] : /* istanbul ignore next */ []));
10991
- labelId = `ef-seo-preview-label-${++nextId}`;
11134
+ labelId = `ef-seo-preview-label-${++nextId$1}`;
10992
11135
  shownTitle = computed(() => clip(this.pageTitle(), this.titleMaxLength()), ...(ngDevMode ? [{ debugName: "shownTitle" }] : /* istanbul ignore next */ []));
10993
11136
  shownUrl = computed(() => (this.url() ?? '').trim(), ...(ngDevMode ? [{ debugName: "shownUrl" }] : /* istanbul ignore next */ []));
10994
11137
  shownDescription = computed(() => clip(this.description(), this.descriptionMaxLength()), ...(ngDevMode ? [{ debugName: "shownDescription" }] : /* istanbul ignore next */ []));
@@ -11318,7 +11461,7 @@ class EfToggleSwitchComponent extends AbstractEfFormControl {
11318
11461
  <p-toggleswitch
11319
11462
  [autofocus]="autofocus"
11320
11463
  [inputId]="effectiveId"
11321
- [name]="name() || undefined"
11464
+ [name]="$any(name() || undefined)"
11322
11465
  [ngModel]="checked"
11323
11466
  [ngModelOptions]="{ standalone: true }"
11324
11467
  [disabled]="isDisabled()"
@@ -11343,7 +11486,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
11343
11486
  <p-toggleswitch
11344
11487
  [autofocus]="autofocus"
11345
11488
  [inputId]="effectiveId"
11346
- [name]="name() || undefined"
11489
+ [name]="$any(name() || undefined)"
11347
11490
  [ngModel]="checked"
11348
11491
  [ngModelOptions]="{ standalone: true }"
11349
11492
  [disabled]="isDisabled()"
@@ -11433,14 +11576,14 @@ class EfDatepickerComponent {
11433
11576
  }
11434
11577
  get showRequired() {
11435
11578
  const ctrl = this.ngControl?.control;
11436
- return ctrl?.hasError('required') && ctrl?.touched;
11579
+ return !!(ctrl?.hasError('required') && ctrl?.touched);
11437
11580
  }
11438
11581
  get serverErrors() {
11439
11582
  return this.ngControl?.control?.errors?.serverError ?? null;
11440
11583
  }
11441
11584
  get isInvalid() {
11442
11585
  const ctrl = this.ngControl?.control;
11443
- return ctrl?.invalid && (ctrl?.touched || ctrl?.dirty);
11586
+ return !!(ctrl?.invalid && (ctrl?.touched || ctrl?.dirty));
11444
11587
  }
11445
11588
  writeValue(value) {
11446
11589
  this.value = value;
@@ -11463,11 +11606,11 @@ class EfDatepickerComponent {
11463
11606
  this.onTouched();
11464
11607
  }
11465
11608
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDatepickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11466
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDatepickerComponent, isStandalone: true, selector: "ef-datepicker", inputs: { label: "label", labelKey: "labelKey", inputId: "inputId", name: "name", placeholder: "placeholder", placeholderKey: "placeholderKey", size: "size", required: ["required", "required", booleanAttribute], fluid: ["fluid", "fluid", booleanAttribute], inline: ["inline", "inline", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], showIcon: ["showIcon", "showIcon", booleanAttribute], iconDisplay: "iconDisplay", selectionMode: "selectionMode", dateFormat: "dateFormat", appendTo: "appendTo", showTime: ["showTime", "showTime", booleanAttribute], showButtonBar: ["showButtonBar", "showButtonBar", booleanAttribute], minDate: "minDate", maxDate: "maxDate", autofocus: ["autofocus", "autofocus", booleanAttribute] }, outputs: { dateSelectEvent: "dateSelectEvent" }, host: { properties: { "class.ef-inline": "this.isInline" } }, ngImport: i0, template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"name\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"isDisabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i1$d.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { 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: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
11609
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDatepickerComponent, isStandalone: true, selector: "ef-datepicker", inputs: { label: "label", labelKey: "labelKey", inputId: "inputId", name: "name", placeholder: "placeholder", placeholderKey: "placeholderKey", size: "size", required: ["required", "required", booleanAttribute], fluid: ["fluid", "fluid", booleanAttribute], inline: ["inline", "inline", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], showIcon: ["showIcon", "showIcon", booleanAttribute], iconDisplay: "iconDisplay", selectionMode: "selectionMode", dateFormat: "dateFormat", appendTo: "appendTo", showTime: ["showTime", "showTime", booleanAttribute], showButtonBar: ["showButtonBar", "showButtonBar", booleanAttribute], minDate: "minDate", maxDate: "maxDate", autofocus: ["autofocus", "autofocus", booleanAttribute] }, outputs: { dateSelectEvent: "dateSelectEvent" }, host: { properties: { "class.ef-inline": "this.isInline" } }, ngImport: i0, template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"$any(name)\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"isDisabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i1$d.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { 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: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
11467
11610
  }
11468
11611
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDatepickerComponent, decorators: [{
11469
11612
  type: Component,
11470
- args: [{ selector: 'ef-datepicker', standalone: true, imports: [DatePickerModule, FormsModule, TranslateModule, EfLabelComponent], template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"name\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"isDisabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"] }]
11613
+ args: [{ selector: 'ef-datepicker', standalone: true, imports: [DatePickerModule, FormsModule, TranslateModule, EfLabelComponent], template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"$any(name)\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"isDisabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"] }]
11471
11614
  }], ctorParameters: () => [], propDecorators: { label: [{
11472
11615
  type: Input
11473
11616
  }], labelKey: [{
@@ -12710,6 +12853,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
12710
12853
  type: Input
12711
12854
  }] } });
12712
12855
 
12856
+ let nextId = 0;
12857
+ const EF_SEO_DEFAULT_LOCALES = [
12858
+ { code: 'fr', labelKey: 'ef_seo_fields_locale_fr', dir: 'auto' },
12859
+ { code: 'ar', labelKey: 'ef_seo_fields_locale_ar', dir: 'rtl' },
12860
+ { code: 'en', labelKey: 'ef_seo_fields_locale_en', dir: 'ltr' },
12861
+ ];
12862
+ /**
12863
+ * SEO title and description per language, each with a counter against what a
12864
+ * search engine shows (60 / 160), capped at the server's limits (256 / 512),
12865
+ * and the shared `ef-seo-preview` of the French result with the entity's own
12866
+ * title and description as the fallback the shop uses. Card-less: each screen
12867
+ * wraps it in its own `ef-card`.
12868
+ *
12869
+ * The French controls carry the backend names (`seoTitle`, `seoDescription`)
12870
+ * so `[efServerErrors]` links their errors; the others (`seoTitleAr`, ...) are
12871
+ * dictionary entries whose errors are toasted (ADR-010).
12872
+ *
12873
+ * ```html
12874
+ * <ef-seo-fields [seoTitle]="p.seoTitle" ... [previewUrl]="url()" (seoChange)="patch($event)" />
12875
+ * ```
12876
+ */
12877
+ class EfSeoFieldsComponent {
12878
+ seoTitle = input('', ...(ngDevMode ? [{ debugName: "seoTitle" }] : /* istanbul ignore next */ []));
12879
+ seoDescription = input('', ...(ngDevMode ? [{ debugName: "seoDescription" }] : /* istanbul ignore next */ []));
12880
+ seoTitleTranslations = input({}, ...(ngDevMode ? [{ debugName: "seoTitleTranslations" }] : /* istanbul ignore next */ []));
12881
+ seoDescriptionTranslations = input({}, ...(ngDevMode ? [{ debugName: "seoDescriptionTranslations" }] : /* istanbul ignore next */ []));
12882
+ /** The entity's own texts, which the shop uses when SEO is empty. */
12883
+ fallbackTitle = input('', ...(ngDevMode ? [{ debugName: "fallbackTitle" }] : /* istanbul ignore next */ []));
12884
+ fallbackDescription = input('', ...(ngDevMode ? [{ debugName: "fallbackDescription" }] : /* istanbul ignore next */ []));
12885
+ /** The page address shown in the preview, computed by the app. */
12886
+ previewUrl = input('', ...(ngDevMode ? [{ debugName: "previewUrl" }] : /* istanbul ignore next */ []));
12887
+ titleMax = input(256, ...(ngDevMode ? [{ debugName: "titleMax" }] : /* istanbul ignore next */ []));
12888
+ descriptionMax = input(512, ...(ngDevMode ? [{ debugName: "descriptionMax" }] : /* istanbul ignore next */ []));
12889
+ titleAdvised = input(60, ...(ngDevMode ? [{ debugName: "titleAdvised" }] : /* istanbul ignore next */ []));
12890
+ descriptionAdvised = input(160, ...(ngDevMode ? [{ debugName: "descriptionAdvised" }] : /* istanbul ignore next */ []));
12891
+ locales = input(EF_SEO_DEFAULT_LOCALES, ...(ngDevMode ? [{ debugName: "locales" }] : /* istanbul ignore next */ []));
12892
+ seoChange = output();
12893
+ text(locale, field) {
12894
+ if (locale === 'fr')
12895
+ return (field === 'title' ? this.seoTitle() : this.seoDescription()) ?? '';
12896
+ const dict = field === 'title' ? this.seoTitleTranslations() : this.seoDescriptionTranslations();
12897
+ return dict?.[locale] ?? '';
12898
+ }
12899
+ /** Id of a counter, which the field it counts points at with `aria-describedby`. */
12900
+ counterId(locale, field) {
12901
+ return `${this.uid}-${this.name(locale, field)}-counter`;
12902
+ }
12903
+ uid = `ef-seo-fields-${nextId++}`;
12904
+ /** French matches the backend property; the others are unique for the label link. */
12905
+ name(locale, field) {
12906
+ const base = field === 'title' ? 'seoTitle' : 'seoDescription';
12907
+ return locale === 'fr' ? base : `${base}${locale.charAt(0).toUpperCase()}${locale.slice(1)}`;
12908
+ }
12909
+ setText(locale, field, value) {
12910
+ if (locale === 'fr') {
12911
+ this.seoChange.emit(field === 'title' ? { seoTitle: value } : { seoDescription: value });
12912
+ return;
12913
+ }
12914
+ const key = field === 'title' ? 'seoTitleTranslations' : 'seoDescriptionTranslations';
12915
+ const next = { ...((field === 'title' ? this.seoTitleTranslations() : this.seoDescriptionTranslations()) ?? {}) };
12916
+ // A cleared field removes the entry, so the shop falls back instead of showing nothing.
12917
+ if (value)
12918
+ next[locale] = value;
12919
+ else
12920
+ delete next[locale];
12921
+ this.seoChange.emit({ [key]: next });
12922
+ }
12923
+ previewTitle = computed(() => (this.seoTitle() || this.fallbackTitle() || '').trim(), ...(ngDevMode ? [{ debugName: "previewTitle" }] : /* istanbul ignore next */ []));
12924
+ previewDescription = computed(() => (this.seoDescription() || this.fallbackDescription() || '').trim(), ...(ngDevMode ? [{ debugName: "previewDescription" }] : /* istanbul ignore next */ []));
12925
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSeoFieldsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12926
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfSeoFieldsComponent, isStandalone: true, selector: "ef-seo-fields", inputs: { seoTitle: { classPropertyName: "seoTitle", publicName: "seoTitle", isSignal: true, isRequired: false, transformFunction: null }, seoDescription: { classPropertyName: "seoDescription", publicName: "seoDescription", isSignal: true, isRequired: false, transformFunction: null }, seoTitleTranslations: { classPropertyName: "seoTitleTranslations", publicName: "seoTitleTranslations", isSignal: true, isRequired: false, transformFunction: null }, seoDescriptionTranslations: { classPropertyName: "seoDescriptionTranslations", publicName: "seoDescriptionTranslations", isSignal: true, isRequired: false, transformFunction: null }, fallbackTitle: { classPropertyName: "fallbackTitle", publicName: "fallbackTitle", isSignal: true, isRequired: false, transformFunction: null }, fallbackDescription: { classPropertyName: "fallbackDescription", publicName: "fallbackDescription", isSignal: true, isRequired: false, transformFunction: null }, previewUrl: { classPropertyName: "previewUrl", publicName: "previewUrl", isSignal: true, isRequired: false, transformFunction: null }, titleMax: { classPropertyName: "titleMax", publicName: "titleMax", isSignal: true, isRequired: false, transformFunction: null }, descriptionMax: { classPropertyName: "descriptionMax", publicName: "descriptionMax", isSignal: true, isRequired: false, transformFunction: null }, titleAdvised: { classPropertyName: "titleAdvised", publicName: "titleAdvised", isSignal: true, isRequired: false, transformFunction: null }, descriptionAdvised: { classPropertyName: "descriptionAdvised", publicName: "descriptionAdvised", isSignal: true, isRequired: false, transformFunction: null }, locales: { classPropertyName: "locales", publicName: "locales", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { seoChange: "seoChange" }, ngImport: i0, template: "<div class=\"seo-body\">\n <p class=\"seo-note text-mute\">{{ 'ef_seo_fields_fallback_hint' | translate }}</p>\n\n @for (locale of locales(); track locale.code) {\n <ef-fieldset variant=\"plain\" [legendKey]=\"locale.labelKey\" [attr.lang]=\"locale.code\">\n <ef-form-grid>\n <ef-input-text\n class=\"col-12\"\n variant=\"comptoir\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'title')\"\n [describedByExtra]=\"counterId(locale.code, 'title')\"\n labelKey=\"ef_seo_fields_title_label\"\n [maxlength]=\"titleMax()\"\n [value]=\"text(locale.code, 'title')\"\n (valueChangeEvent)=\"setText(locale.code, 'title', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'title')\"\n [attr.data-counter]=\"locale.code + '-title'\"\n [class.is-over]=\"text(locale.code, 'title').length > titleAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'title').length }} / {{ titleAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: titleAdvised() } }}\n @if (text(locale.code, 'title').length > titleAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: titleAdvised() } }}\n }\n </span>\n </span>\n\n <ef-textarea\n class=\"col-12\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'description')\"\n [describedByExtra]=\"counterId(locale.code, 'description')\"\n labelKey=\"ef_seo_fields_description_label\"\n [rows]=\"3\"\n [maxlength]=\"descriptionMax()\"\n [value]=\"text(locale.code, 'description')\"\n (valueChangeEvent)=\"setText(locale.code, 'description', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'description')\"\n [attr.data-counter]=\"locale.code + '-description'\"\n [class.is-over]=\"text(locale.code, 'description').length > descriptionAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'description').length }} / {{ descriptionAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: descriptionAdvised() } }}\n @if (text(locale.code, 'description').length > descriptionAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: descriptionAdvised() } }}\n }\n </span>\n </span>\n </ef-form-grid>\n </ef-fieldset>\n }\n\n <ef-seo-preview\n labelKey=\"ef_seo_fields_preview\"\n contentLang=\"fr\"\n [pageTitle]=\"previewTitle()\"\n [url]=\"previewUrl()\"\n [description]=\"previewDescription()\"\n />\n</div>\n", styles: [":host{display:block;min-width:0}.seo-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);min-width:0}.seo-note{margin:0;font-size:12px}.seo-counter{margin-top:calc(-1 * var(--s-2));font-size:12px;color:var(--text-mute);text-align:end;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.seo-counter.is-over{color:var(--st-pending-fg);font-weight:600}.seo-counter__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfFieldsetComponent, selector: "ef-fieldset", inputs: ["legend", "legendKey", "variant", "toggleable", "collapsed", "styleClass"] }, { kind: "component", type: EfFormGridComponent, selector: "ef-form-grid" }, { kind: "component", type: EfInputTextComponent, selector: "ef-input-text", inputs: ["size", "type", "maxlength", "fluid", "inline", "autocomplete", "variant", "prefix", "suffix", "value"], outputs: ["valueChangeEvent"] }, { kind: "component", type: EfTextareaComponent, selector: "ef-textarea", inputs: ["rows", "maxlength", "autofocus", "mono", "value"], outputs: ["valueChangeEvent"] }, { kind: "component", type: EfSeoPreviewComponent, selector: "ef-seo-preview", inputs: ["pageTitle", "url", "description", "labelKey", "titleMaxLength", "descriptionMaxLength", "contentLang"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12927
+ }
12928
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSeoFieldsComponent, decorators: [{
12929
+ type: Component,
12930
+ args: [{ selector: 'ef-seo-fields', standalone: true, imports: [TranslateModule, EfFieldsetComponent, EfFormGridComponent, EfInputTextComponent, EfTextareaComponent, EfSeoPreviewComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"seo-body\">\n <p class=\"seo-note text-mute\">{{ 'ef_seo_fields_fallback_hint' | translate }}</p>\n\n @for (locale of locales(); track locale.code) {\n <ef-fieldset variant=\"plain\" [legendKey]=\"locale.labelKey\" [attr.lang]=\"locale.code\">\n <ef-form-grid>\n <ef-input-text\n class=\"col-12\"\n variant=\"comptoir\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'title')\"\n [describedByExtra]=\"counterId(locale.code, 'title')\"\n labelKey=\"ef_seo_fields_title_label\"\n [maxlength]=\"titleMax()\"\n [value]=\"text(locale.code, 'title')\"\n (valueChangeEvent)=\"setText(locale.code, 'title', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'title')\"\n [attr.data-counter]=\"locale.code + '-title'\"\n [class.is-over]=\"text(locale.code, 'title').length > titleAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'title').length }} / {{ titleAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: titleAdvised() } }}\n @if (text(locale.code, 'title').length > titleAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: titleAdvised() } }}\n }\n </span>\n </span>\n\n <ef-textarea\n class=\"col-12\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'description')\"\n [describedByExtra]=\"counterId(locale.code, 'description')\"\n labelKey=\"ef_seo_fields_description_label\"\n [rows]=\"3\"\n [maxlength]=\"descriptionMax()\"\n [value]=\"text(locale.code, 'description')\"\n (valueChangeEvent)=\"setText(locale.code, 'description', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'description')\"\n [attr.data-counter]=\"locale.code + '-description'\"\n [class.is-over]=\"text(locale.code, 'description').length > descriptionAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'description').length }} / {{ descriptionAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: descriptionAdvised() } }}\n @if (text(locale.code, 'description').length > descriptionAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: descriptionAdvised() } }}\n }\n </span>\n </span>\n </ef-form-grid>\n </ef-fieldset>\n }\n\n <ef-seo-preview\n labelKey=\"ef_seo_fields_preview\"\n contentLang=\"fr\"\n [pageTitle]=\"previewTitle()\"\n [url]=\"previewUrl()\"\n [description]=\"previewDescription()\"\n />\n</div>\n", styles: [":host{display:block;min-width:0}.seo-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);min-width:0}.seo-note{margin:0;font-size:12px}.seo-counter{margin-top:calc(-1 * var(--s-2));font-size:12px;color:var(--text-mute);text-align:end;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.seo-counter.is-over{color:var(--st-pending-fg);font-weight:600}.seo-counter__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"] }]
12931
+ }], propDecorators: { seoTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoTitle", required: false }] }], seoDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoDescription", required: false }] }], seoTitleTranslations: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoTitleTranslations", required: false }] }], seoDescriptionTranslations: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoDescriptionTranslations", required: false }] }], fallbackTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackTitle", required: false }] }], fallbackDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackDescription", required: false }] }], previewUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewUrl", required: false }] }], titleMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleMax", required: false }] }], descriptionMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionMax", required: false }] }], titleAdvised: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleAdvised", required: false }] }], descriptionAdvised: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionAdvised", required: false }] }], locales: [{ type: i0.Input, args: [{ isSignal: true, alias: "locales", required: false }] }], seoChange: [{ type: i0.Output, args: ["seoChange"] }] } });
12932
+
12713
12933
  /**
12714
12934
  * A button that opens the native file picker and emits the chosen `File`.
12715
12935
  * It never talks to the network: the app uploads the emitted file through its
@@ -13212,7 +13432,7 @@ class EfThemeConfiguratorComponent {
13212
13432
  this.configService.appState.update((state) => ({ ...state, RTL: value }));
13213
13433
  }
13214
13434
  ngOnInit() {
13215
- this.onPresetChange(this.configService.appState().preset);
13435
+ this.onPresetChange(this.configService.appState().preset ?? 'Lara');
13216
13436
  }
13217
13437
  surfaces = [
13218
13438
  { name: 'slate', palette: { 0: '#ffffff', 50: '#f8fafc', 100: '#f1f5f9', 200: '#e2e8f0', 300: '#cbd5e1', 400: '#94a3b8', 500: '#64748b', 600: '#475569', 700: '#334155', 800: '#1e293b', 900: '#0f172a', 950: '#020617' } },
@@ -13369,7 +13589,7 @@ class EfThemeConfiguratorComponent {
13369
13589
  (click)="updateColors($event, 'primary', primaryColor)"
13370
13590
  [ngClass]="{ 'active-color': primaryColor.name === selectedPrimaryColor() }"
13371
13591
  [style]="{
13372
- 'background-color': primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette['500']
13592
+ 'background-color': primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor.palette?.['500']
13373
13593
  }"
13374
13594
  ></button>
13375
13595
  }
@@ -13386,7 +13606,7 @@ class EfThemeConfiguratorComponent {
13386
13606
  (click)="updateColors($event, 'surface', surface)"
13387
13607
  [ngClass]="{ 'active-color': selectedSurfaceColor() ? selectedSurfaceColor() === surface.name : configService.appState().darkTheme ? surface.name === 'zinc' : surface.name === 'slate' }"
13388
13608
  [style]="{
13389
- 'background-color': surface.name === 'noir' ? 'var(--text-color)' : surface?.palette['500']
13609
+ 'background-color': surface.name === 'noir' ? 'var(--text-color)' : surface.palette?.['500']
13390
13610
  }"
13391
13611
  ></button>
13392
13612
  }
@@ -13444,7 +13664,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
13444
13664
  (click)="updateColors($event, 'primary', primaryColor)"
13445
13665
  [ngClass]="{ 'active-color': primaryColor.name === selectedPrimaryColor() }"
13446
13666
  [style]="{
13447
- 'background-color': primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette['500']
13667
+ 'background-color': primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor.palette?.['500']
13448
13668
  }"
13449
13669
  ></button>
13450
13670
  }
@@ -13461,7 +13681,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
13461
13681
  (click)="updateColors($event, 'surface', surface)"
13462
13682
  [ngClass]="{ 'active-color': selectedSurfaceColor() ? selectedSurfaceColor() === surface.name : configService.appState().darkTheme ? surface.name === 'zinc' : surface.name === 'slate' }"
13463
13683
  [style]="{
13464
- 'background-color': surface.name === 'noir' ? 'var(--text-color)' : surface?.palette['500']
13684
+ 'background-color': surface.name === 'noir' ? 'var(--text-color)' : surface.palette?.['500']
13465
13685
  }"
13466
13686
  ></button>
13467
13687
  }
@@ -13618,5 +13838,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
13618
13838
  * Generated bundle index. Do not edit.
13619
13839
  */
13620
13840
 
13621
- 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, EfAboutDialogComponent, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChangePasswordComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFileUploadComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfIdentityThumbComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductPickerComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfReadOnlyDirective, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfSeoPreviewComponent, EfServerErrorsDirective, EfSessionIdleDialogComponent, EfShortcutsDialogComponent, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
13841
+ 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_SEO_DEFAULT_LOCALES, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfAboutDialogComponent, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChangePasswordComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFileUploadComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfIdentityThumbComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductPickerComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfReadOnlyDirective, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfSeoFieldsComponent, EfSeoPreviewComponent, EfServerErrorsDirective, EfSessionIdleDialogComponent, EfShortcutsDialogComponent, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, defaultUnitPrice, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
13622
13842
  //# sourceMappingURL=elasticias-ui.mjs.map