@elasticias/ui 1.0.48 → 1.0.49

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,8 +1,10 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { PipeTransform, OnInit, OnChanges, TemplateRef, EventEmitter, AfterContentInit, OnDestroy, SimpleChanges, InjectionToken, AfterViewInit } from '@angular/core';
2
+ import { PipeTransform, OnInit, EventEmitter, TemplateRef, OnChanges, AfterContentInit, OnDestroy, SimpleChanges, InjectionToken, AfterViewInit } from '@angular/core';
3
3
  import * as _elasticias_screens from '@elasticias/screens';
4
4
  import { EfDataCardColumn, ScreenContext, EfDetailToolbarAction, EfSearchToolbarAction, EfDataCardSort, EfDataCardExportRequest, EfDateRange, EfDatePresetKey } from '@elasticias/screens';
5
5
  export { EfBoundedDateRange, EfDataCardColumn, EfDataCardColumnAlign, EfDataCardColumnMobileRole, EfDataCardColumnType, EfDataCardSort, EfDataCardSortDirection, EfDatePresetKey, EfDateRange, EfDetailToolbarAction, EfSearchToolbarAction } from '@elasticias/screens';
6
+ import { Observable } from 'rxjs';
7
+ import { EfPage, Permissions, EmailMetrics, EmailEventRecord, EmailEventKind } from '@elasticias/types';
6
8
  import { Menu } from 'primeng/menu';
7
9
  import * as _elasticias_core from '@elasticias/core';
8
10
  import { EfBuildPackage, EfSessionIdleService, EfModule, EfNavSection, EfNavItem, EfModuleId, EfToast, EfToastAction, EfThemeConfigService } from '@elasticias/core';
@@ -16,7 +18,6 @@ import { InputNumber, InputNumberInputEvent } from 'primeng/inputnumber';
16
18
  import { Nullable } from 'primeng/ts-helpers';
17
19
  import * as _elasticias_ui from '@elasticias/ui';
18
20
  import { Table, TableLazyLoadEvent } from 'primeng/table';
19
- import { Permissions, EmailMetrics, EmailEventRecord, EmailEventKind } from '@elasticias/types';
20
21
  import { PrimeNG } from 'primeng/config';
21
22
 
22
23
  /**
@@ -124,6 +125,8 @@ interface OrderEntity {
124
125
  grossTotal: number;
125
126
  discountTotal: number;
126
127
  taxTotal: number;
128
+ /** Delivery amount (TTC). Absent or 0 for orders without one (B2B, POS). */
129
+ shippingTotal?: number;
127
130
  finalTotal: number;
128
131
  notes?: string;
129
132
  internalNotes?: string;
@@ -221,6 +224,13 @@ declare class OrderEntityHelper {
221
224
  * Create a new empty order entity
222
225
  */
223
226
  static createEmpty(documentType?: 'order' | 'invoice' | 'quote'): OrderEntity;
227
+ /**
228
+ * Round to the centime, half away from zero, as the backend does
229
+ * (`Math.Round(x, 2, MidpointRounding.AwayFromZero)` on a decimal). The
230
+ * product goes through 15 significant digits first so a binary value such
231
+ * as 1.00499999... rounds as the decimal 1.005 would.
232
+ */
233
+ static roundMoney(value: number): number;
224
234
  /**
225
235
  * Calculate all totals for an order
226
236
  */
@@ -399,6 +409,14 @@ declare class EfOrderBuilderComponent implements OnInit {
399
409
  backendErrors: _angular_core.InputSignal<{
400
410
  [key: string]: string[];
401
411
  }>;
412
+ /**
413
+ * Delivery amount (TTC) the host owns. It is the builder's only fee source:
414
+ * the rail, the bound order and `exportOrder()` all total it, so the saved
415
+ * total is the one shown. A host without a fee (B2B) leaves it at 0, and
416
+ * the bound order then carries no `shippingTotal` at all. A negative or
417
+ * non-numeric fee is treated as 0.
418
+ */
419
+ shippingTotal: _angular_core.InputSignalWithTransform<number, number>;
402
420
  /** Optional change-history entries; when non-empty the Historique rail card renders. */
403
421
  auditEntries: _angular_core.InputSignal<EfChangeHistoryEntry[]>;
404
422
  openCatalogue: _angular_core.OutputEmitterRef<void>;
@@ -506,11 +524,18 @@ declare class EfOrderBuilderComponent implements OnInit {
506
524
  grossTotal: number;
507
525
  discountTotal: number;
508
526
  tvaTotal: number;
527
+ shippingTotal: number;
509
528
  finalTotal: number;
510
529
  };
511
530
  exportOrder(): OrderEntity;
531
+ /**
532
+ * The bound order with its totals, the fee taken from the `shippingTotal`
533
+ * input. Without a fee the order carries no `shippingTotal`, so a B2B order
534
+ * stays exactly as it came in.
535
+ */
536
+ private withTotals;
512
537
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfOrderBuilderComponent, never>;
513
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfOrderBuilderComponent, "ef-order-builder", never, { "config": { "alias": "config"; "required": false; "isSignal": true; }; "order": { "alias": "order"; "required": true; "isSignal": true; }; "products": { "alias": "products"; "required": true; "isSignal": true; }; "customers": { "alias": "customers"; "required": true; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "countGroupLabels": { "alias": "countGroupLabels"; "required": false; "isSignal": true; }; "productKeyField": { "alias": "productKeyField"; "required": false; "isSignal": true; }; "backendErrors": { "alias": "backendErrors"; "required": false; "isSignal": true; }; "auditEntries": { "alias": "auditEntries"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; }, { "order": "orderChange"; "openCatalogue": "openCatalogue"; "orderChanged": "orderChanged"; "orderValidated": "orderValidated"; "productsAdded": "productsAdded"; "focusMode": "focusModeChange"; }, never, never, true, never>;
538
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfOrderBuilderComponent, "ef-order-builder", never, { "config": { "alias": "config"; "required": false; "isSignal": true; }; "order": { "alias": "order"; "required": true; "isSignal": true; }; "products": { "alias": "products"; "required": true; "isSignal": true; }; "customers": { "alias": "customers"; "required": true; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "countGroupLabels": { "alias": "countGroupLabels"; "required": false; "isSignal": true; }; "productKeyField": { "alias": "productKeyField"; "required": false; "isSignal": true; }; "backendErrors": { "alias": "backendErrors"; "required": false; "isSignal": true; }; "shippingTotal": { "alias": "shippingTotal"; "required": false; "isSignal": true; }; "auditEntries": { "alias": "auditEntries"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; }, { "order": "orderChange"; "openCatalogue": "openCatalogue"; "orderChanged": "orderChanged"; "orderValidated": "orderValidated"; "productsAdded": "productsAdded"; "focusMode": "focusModeChange"; }, never, never, true, never>;
514
539
  }
515
540
 
516
541
  /**
@@ -569,194 +594,6 @@ declare class EfProductTypeaheadComponent {
569
594
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfProductTypeaheadComponent, "ef-product-typeahead", never, { "products": { "alias": "products"; "required": true; "isSignal": true; }; "productKeyField": { "alias": "productKeyField"; "required": false; "isSignal": true; }; "placeholderKey": { "alias": "placeholderKey"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "currency": { "alias": "currency"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "showVariants": { "alias": "showVariants"; "required": false; "isSignal": true; }; }, { "productSelect": "productSelect"; }, never, never, true, never>;
570
595
  }
571
596
 
572
- interface CategoryItem {
573
- code: string;
574
- label: string;
575
- description?: string | null;
576
- isActive: boolean;
577
- sortOrder: number;
578
- metadata?: Record<string, unknown> | null;
579
- children?: CategoryItem[];
580
- }
581
- interface CategoryGroup {
582
- code: string;
583
- label: string;
584
- description?: string | null;
585
- isActive: boolean;
586
- sortOrder: number;
587
- metadata?: Record<string, unknown> | null;
588
- children?: CategoryItem[];
589
- }
590
- interface CategoryAssignment {
591
- type: string;
592
- code: string;
593
- }
594
- interface SelectedFilter {
595
- type: string;
596
- code: string;
597
- label: string;
598
- }
599
- declare class EfProductCatalogueFilterComponent {
600
- /**
601
- * PrimeNG's Button reads `autofocus || buttonProps?.autofocus`, so a bare
602
- * `false` collapses to `undefined` and its AutoFocus directive writes the
603
- * attribute anyway. Passing the flag here too makes it resolve to `false`.
604
- */
605
- protected readonly noAutofocus: {
606
- autofocus: boolean;
607
- };
608
- /** Category groups from reference_data (product_categories items with children) */
609
- categoryGroups: _angular_core.InputSignal<CategoryGroup[]>;
610
- /** Products to calculate counts */
611
- products: _angular_core.InputSignal<{
612
- categoryAssignments?: CategoryAssignment[];
613
- }[]>;
614
- /** Selected filters (two-way binding) */
615
- selectedFilters: _angular_core.ModelSignal<SelectedFilter[]>;
616
- /** Emitted when filters change */
617
- filtersChanged: _angular_core.OutputEmitterRef<SelectedFilter[]>;
618
- /** Search terms for each category group */
619
- searchTerms: _angular_core.WritableSignal<Record<string, string>>;
620
- /** Accordion panel values (expanded panels) */
621
- expandedPanels: _angular_core.Signal<string[]>;
622
- /** Get filtered children for a category group based on search */
623
- getFilteredChildren(group: CategoryGroup): CategoryItem[];
624
- /** Get product count for a specific category item */
625
- getItemCount(groupCode: string, itemCode: string): number;
626
- /** Check if a filter is selected */
627
- isFilterSelected(groupCode: string, itemCode: string): boolean;
628
- /** Toggle a filter selection */
629
- toggleFilter(group: CategoryGroup, item: CategoryItem): void;
630
- /** Remove a specific filter */
631
- removeFilter(filter: SelectedFilter): void;
632
- /** Clear all filters */
633
- clearAllFilters(): void;
634
- /** Update search term for a category group */
635
- updateSearchTerm(groupCode: string, term: string): void;
636
- /** Get color class from metadata if available */
637
- getColorClass(item: CategoryItem): string | null;
638
- /** Check if group should show search input (more than 5 items) */
639
- shouldShowSearch(group: CategoryGroup): boolean;
640
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfProductCatalogueFilterComponent, never>;
641
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfProductCatalogueFilterComponent, "ef-product-catalogue-filter", never, { "categoryGroups": { "alias": "categoryGroups"; "required": true; "isSignal": true; }; "products": { "alias": "products"; "required": true; "isSignal": true; }; "selectedFilters": { "alias": "selectedFilters"; "required": false; "isSignal": true; }; }, { "selectedFilters": "selectedFiltersChange"; "filtersChanged": "filtersChanged"; }, never, never, true, never>;
642
- }
643
-
644
- interface CatalogueProductVariant {
645
- variantId: string;
646
- title: string;
647
- sku?: string | null;
648
- price: number;
649
- compareAtPrice?: number | null;
650
- optionValues?: Record<string, string>;
651
- isActive?: boolean;
652
- countGroup?: string | null;
653
- [key: string]: unknown;
654
- }
655
- interface CatalogueProduct {
656
- id: number;
657
- productCode: string;
658
- name: string;
659
- displayName: string;
660
- unitPrice?: number | null;
661
- packagingVolume?: number | null;
662
- categoryAssignments?: CategoryAssignment[];
663
- productType?: string | null;
664
- variants?: CatalogueProductVariant[];
665
- defaultVariantId?: string | null;
666
- [key: string]: unknown;
667
- }
668
- declare class EfProductCatalogueComponent implements OnChanges {
669
- private readonly locale;
670
- private readonly currencyPipe;
671
- /** Products to display */
672
- products: _angular_core.InputSignal<CatalogueProduct[]>;
673
- /** Category groups for filtering */
674
- categoryGroups: _angular_core.InputSignal<CategoryGroup[]>;
675
- /** Initial selected items (for editing existing orders) */
676
- initialSelectedItems: _angular_core.InputSignal<{
677
- productId?: unknown;
678
- quantity: number;
679
- variantId?: string | null;
680
- }[]>;
681
- /** Default filters to apply on init */
682
- defaultFilters: _angular_core.InputSignal<SelectedFilter[]>;
683
- /** Property name used as unique key for tracking products in the list */
684
- trackByField: _angular_core.InputSignal<string>;
685
- /** Currency code for price formatting */
686
- currencyCode: _angular_core.InputSignal<string>;
687
- /** Locale used for number/currency formatting and the quantity stepper */
688
- localeCode: _angular_core.InputSignal<string>;
689
- /**
690
- * Custom product thumbnail template.
691
- * Context: { $implicit: CatalogueProduct }
692
- *
693
- * @example
694
- * <ef-product-catalogue [products]="products()">
695
- * <ng-template #productThumbnail let-product>
696
- * <my-custom-thumbnail [product]="product" />
697
- * </ng-template>
698
- * </ef-product-catalogue>
699
- */
700
- productThumbnailTpl: _angular_core.Signal<TemplateRef<unknown>>;
701
- sortMenu: Menu;
702
- /** Emitted when products are validated */
703
- productsValidated: _angular_core.OutputEmitterRef<{
704
- product: CatalogueProduct;
705
- quantity: number;
706
- variantId?: string;
707
- }[]>;
708
- productSearch: _angular_core.WritableSignal<string>;
709
- selectedFilters: _angular_core.WritableSignal<SelectedFilter[]>;
710
- selectedSort: _angular_core.WritableSignal<string>;
711
- selectedProducts: _angular_core.WritableSignal<{
712
- product: CatalogueProduct;
713
- quantity: number;
714
- variantId?: string;
715
- }[]>;
716
- /** Tracks the currently selected variantId per product key */
717
- selectedVariants: _angular_core.WritableSignal<Record<string, string>>;
718
- filteredProducts: _angular_core.Signal<CatalogueProduct[]>;
719
- sortOptions: {
720
- label: string;
721
- icon: string;
722
- command: () => void;
723
- }[];
724
- onFiltersChanged(filters: SelectedFilter[]): void;
725
- ngOnChanges(): void;
726
- productKey(product: CatalogueProduct): unknown;
727
- private initializeSelectedProducts;
728
- private initializeDefaultFilters;
729
- toggleSortMenu(event: Event): void;
730
- /** Composite key for tracking product+variant selections */
731
- private selectionKey;
732
- /** Get the selected variantId for a product, or its defaultVariantId */
733
- getSelectedVariantId(product: CatalogueProduct): string | undefined;
734
- /** Set the selected variant for a product */
735
- setSelectedVariant(product: CatalogueProduct, variantId: string): void;
736
- /** Check if product is a VARIABLE product with variants */
737
- isVariableProduct(product: CatalogueProduct): boolean;
738
- /** Get active variants for a product */
739
- getActiveVariants(product: CatalogueProduct): CatalogueProductVariant[];
740
- /** Get total quantity for a product across all its variants */
741
- getProductQuantity(product: CatalogueProduct): number;
742
- /** Get quantity for a specific product+variant combination */
743
- getVariantQuantity(product: CatalogueProduct, variantId: string): number;
744
- addProduct(product: CatalogueProduct): void;
745
- removeProduct(product: CatalogueProduct): void;
746
- removeVariantSelection(product: CatalogueProduct, variantId: string): void;
747
- updateProductQuantity(product: CatalogueProduct, quantity: number): void;
748
- updateVariantQuantity(product: CatalogueProduct, variantId: string, quantity: number): void;
749
- validateSelection(): void;
750
- getPackagingLabel(product: CatalogueProduct): string;
751
- getVariantTitle(product: CatalogueProduct, variantId: string): string;
752
- trackKey(product: CatalogueProduct): unknown;
753
- formatPrice(product: CatalogueProduct): string;
754
- formatVariantPrice(variant: CatalogueProductVariant): string;
755
- private formatCurrency;
756
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfProductCatalogueComponent, never>;
757
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfProductCatalogueComponent, "ef-product-catalogue", never, { "products": { "alias": "products"; "required": false; "isSignal": true; }; "categoryGroups": { "alias": "categoryGroups"; "required": false; "isSignal": true; }; "initialSelectedItems": { "alias": "initialSelectedItems"; "required": false; "isSignal": true; }; "defaultFilters": { "alias": "defaultFilters"; "required": false; "isSignal": true; }; "trackByField": { "alias": "trackByField"; "required": false; "isSignal": true; }; "currencyCode": { "alias": "currencyCode"; "required": false; "isSignal": true; }; "localeCode": { "alias": "localeCode"; "required": false; "isSignal": true; }; }, { "productsValidated": "productsValidated"; }, ["productThumbnailTpl"], never, true, never>;
758
- }
759
-
760
597
  /**
761
598
  * Dynamic action descriptor for `ef-dialog`. The dialog renders one
762
599
  * `<button class="btn …">` per entry, in array order (left → right);
@@ -952,6 +789,297 @@ declare class EfDialogComponent {
952
789
  static ngAcceptInputType_saveDisabled: unknown;
953
790
  }
954
791
 
792
+ /** A product as the picker shows it. The caller maps its own DTO to this. */
793
+ interface EfProductPickerItem {
794
+ id: string;
795
+ /** Internal reference, shown in a mono column. */
796
+ reference?: string;
797
+ title: string;
798
+ /** Public image url, displayed as given (it comes from the backend). */
799
+ thumbnailUrl?: string;
800
+ /** One muted line of context ("Femme - publie"), already translated. */
801
+ meta?: string;
802
+ /** Shown but not pickable, for a product that is already in. */
803
+ disabled?: boolean;
804
+ }
805
+ /** What the picker asks the caller's search function for. */
806
+ interface EfProductPickerQuery {
807
+ text: string;
808
+ pageNumber: number;
809
+ pageSize: number;
810
+ }
811
+
812
+ /**
813
+ * Multi-select product picker: a dialog that searches the caller's catalogue
814
+ * page by page and hands back the ids the operator ticked. Data-agnostic: it
815
+ * knows nothing of collections or promotions, only a search function.
816
+ *
817
+ * Built from `ef-dialog` + `ef-smart-bar` + `ef-data-card` (select column,
818
+ * image column). The selection survives paging and new searches. The whole
819
+ * row is the toggle, 48px tall. On a phone (`EfViewportService`, <= 767px)
820
+ * the dialog is full screen and the table becomes a list whose row head is
821
+ * the toggle.
822
+ *
823
+ * `[(open)]` is two-way. The picker closes itself on X, Escape, Cancel and
824
+ * after a confirm, sets `open` back to false and emits `(closed)` each time.
825
+ * Each open starts over: empty selection, empty search, page one.
826
+ *
827
+ * Rows are toggle buttons (`ef-data-card` `rowPressed`): one tab stop each,
828
+ * Enter and Space toggle, `aria-pressed` says the state. The tick in the
829
+ * row is a picture of that state, not a second control. A `disabled` item is
830
+ * dimmed, `aria-disabled`, and cannot be picked.
831
+ *
832
+ * ```html
833
+ * <ef-product-picker
834
+ * [(open)]="pickerOpen"
835
+ * [search]="searchCandidates"
836
+ * titleKey="collections_products_picker_title"
837
+ * (confirm)="addProducts($event)"
838
+ * />
839
+ * ```
840
+ */
841
+ declare class EfProductPickerComponent {
842
+ private readonly viewport;
843
+ private readonly destroyRef;
844
+ /** Two-way: `[(open)]="pickerOpen"`. The picker sets it back to false
845
+ * on every close path, so the next open always opens. */
846
+ readonly open: _angular_core.ModelSignal<boolean>;
847
+ /** Called for every page and search. Map the caller's DTO to items. */
848
+ readonly search: _angular_core.InputSignal<(q: EfProductPickerQuery) => Observable<EfPage<EfProductPickerItem>>>;
849
+ readonly titleKey: _angular_core.InputSignal<string>;
850
+ readonly confirmLabelKey: _angular_core.InputSignal<string>;
851
+ readonly searchPlaceholderKey: _angular_core.InputSignal<string>;
852
+ readonly pageSize: _angular_core.InputSignal<number>;
853
+ /** The ticked ids, in the order they were ticked. */
854
+ readonly confirm: _angular_core.OutputEmitterRef<string[]>;
855
+ readonly closed: _angular_core.OutputEmitterRef<void>;
856
+ /** The list screen behind the dialog already owns the search shortcut. */
857
+ readonly searchShortcut: any;
858
+ protected readonly isMobile: _angular_core.Signal<boolean>;
859
+ private readonly selected;
860
+ readonly selectedIds: _angular_core.Signal<string[]>;
861
+ protected readonly selectedCount: _angular_core.Signal<number>;
862
+ protected readonly searchText: _angular_core.WritableSignal<string>;
863
+ protected readonly appliedText: _angular_core.WritableSignal<string>;
864
+ protected readonly pageNumber: _angular_core.WritableSignal<number>;
865
+ protected readonly rows: _angular_core.WritableSignal<readonly EfProductPickerItem[]>;
866
+ protected readonly totalCount: _angular_core.WritableSignal<number>;
867
+ protected readonly loading: _angular_core.WritableSignal<boolean>;
868
+ protected readonly errorMsg: _angular_core.WritableSignal<string>;
869
+ private readonly queries;
870
+ private lastQuery;
871
+ protected readonly pageSizeOptions: _angular_core.Signal<number[]>;
872
+ protected readonly dialogSize: _angular_core.Signal<"lg" | "full">;
873
+ protected readonly dialogClass: _angular_core.Signal<"ef-product-picker-dialog ef-product-picker-dialog--fullscreen" | "ef-product-picker-dialog">;
874
+ /** Desktop: a table. Phone: one composed cell, so the list row carries
875
+ * the tick, the thumb, the reference and the title without an expander. */
876
+ protected readonly columns: _angular_core.Signal<EfDataCardColumn[]>;
877
+ protected readonly actions: _angular_core.Signal<EfDialogAction[]>;
878
+ constructor();
879
+ onSearch(text: string): void;
880
+ onClear(): void;
881
+ onPageChange(page: number): void;
882
+ retry(): void;
883
+ isSelected(id: string): boolean;
884
+ /** Row state for ef-data-card. Arrow functions: data-card calls them. */
885
+ protected readonly rowPressed: (row: EfProductPickerItem) => boolean;
886
+ protected readonly rowDisabled: (row: EfProductPickerItem) => boolean;
887
+ toggle(item: EfProductPickerItem | null | undefined): void;
888
+ onVisibleChange(visible: boolean): void;
889
+ private startOver;
890
+ private run;
891
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfProductPickerComponent, never>;
892
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfProductPickerComponent, "ef-product-picker", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": true; "isSignal": true; }; "titleKey": { "alias": "titleKey"; "required": false; "isSignal": true; }; "confirmLabelKey": { "alias": "confirmLabelKey"; "required": false; "isSignal": true; }; "searchPlaceholderKey": { "alias": "searchPlaceholderKey"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "confirm": "confirm"; "closed": "closed"; }, never, never, true, never>;
893
+ }
894
+
895
+ interface CategoryItem {
896
+ code: string;
897
+ label: string;
898
+ description?: string | null;
899
+ isActive: boolean;
900
+ sortOrder: number;
901
+ metadata?: Record<string, unknown> | null;
902
+ children?: CategoryItem[];
903
+ }
904
+ interface CategoryGroup {
905
+ code: string;
906
+ label: string;
907
+ description?: string | null;
908
+ isActive: boolean;
909
+ sortOrder: number;
910
+ metadata?: Record<string, unknown> | null;
911
+ children?: CategoryItem[];
912
+ }
913
+ interface CategoryAssignment {
914
+ type: string;
915
+ code: string;
916
+ }
917
+ interface SelectedFilter {
918
+ type: string;
919
+ code: string;
920
+ label: string;
921
+ }
922
+ declare class EfProductCatalogueFilterComponent {
923
+ /**
924
+ * PrimeNG's Button reads `autofocus || buttonProps?.autofocus`, so a bare
925
+ * `false` collapses to `undefined` and its AutoFocus directive writes the
926
+ * attribute anyway. Passing the flag here too makes it resolve to `false`.
927
+ */
928
+ protected readonly noAutofocus: {
929
+ autofocus: boolean;
930
+ };
931
+ /** Category groups from reference_data (product_categories items with children) */
932
+ categoryGroups: _angular_core.InputSignal<CategoryGroup[]>;
933
+ /** Products to calculate counts */
934
+ products: _angular_core.InputSignal<{
935
+ categoryAssignments?: CategoryAssignment[];
936
+ }[]>;
937
+ /** Selected filters (two-way binding) */
938
+ selectedFilters: _angular_core.ModelSignal<SelectedFilter[]>;
939
+ /** Emitted when filters change */
940
+ filtersChanged: _angular_core.OutputEmitterRef<SelectedFilter[]>;
941
+ /** Search terms for each category group */
942
+ searchTerms: _angular_core.WritableSignal<Record<string, string>>;
943
+ /** Accordion panel values (expanded panels) */
944
+ expandedPanels: _angular_core.Signal<string[]>;
945
+ /** Get filtered children for a category group based on search */
946
+ getFilteredChildren(group: CategoryGroup): CategoryItem[];
947
+ /** Get product count for a specific category item */
948
+ getItemCount(groupCode: string, itemCode: string): number;
949
+ /** Check if a filter is selected */
950
+ isFilterSelected(groupCode: string, itemCode: string): boolean;
951
+ /** Toggle a filter selection */
952
+ toggleFilter(group: CategoryGroup, item: CategoryItem): void;
953
+ /** Remove a specific filter */
954
+ removeFilter(filter: SelectedFilter): void;
955
+ /** Clear all filters */
956
+ clearAllFilters(): void;
957
+ /** Update search term for a category group */
958
+ updateSearchTerm(groupCode: string, term: string): void;
959
+ /** Get color class from metadata if available */
960
+ getColorClass(item: CategoryItem): string | null;
961
+ /** Check if group should show search input (more than 5 items) */
962
+ shouldShowSearch(group: CategoryGroup): boolean;
963
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfProductCatalogueFilterComponent, never>;
964
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfProductCatalogueFilterComponent, "ef-product-catalogue-filter", never, { "categoryGroups": { "alias": "categoryGroups"; "required": true; "isSignal": true; }; "products": { "alias": "products"; "required": true; "isSignal": true; }; "selectedFilters": { "alias": "selectedFilters"; "required": false; "isSignal": true; }; }, { "selectedFilters": "selectedFiltersChange"; "filtersChanged": "filtersChanged"; }, never, never, true, never>;
965
+ }
966
+
967
+ interface CatalogueProductVariant {
968
+ variantId: string;
969
+ title: string;
970
+ sku?: string | null;
971
+ price: number;
972
+ compareAtPrice?: number | null;
973
+ optionValues?: Record<string, string>;
974
+ isActive?: boolean;
975
+ countGroup?: string | null;
976
+ [key: string]: unknown;
977
+ }
978
+ interface CatalogueProduct {
979
+ id: number;
980
+ productCode: string;
981
+ name: string;
982
+ displayName: string;
983
+ unitPrice?: number | null;
984
+ packagingVolume?: number | null;
985
+ categoryAssignments?: CategoryAssignment[];
986
+ productType?: string | null;
987
+ variants?: CatalogueProductVariant[];
988
+ defaultVariantId?: string | null;
989
+ [key: string]: unknown;
990
+ }
991
+ declare class EfProductCatalogueComponent implements OnChanges {
992
+ private readonly locale;
993
+ private readonly currencyPipe;
994
+ /** Products to display */
995
+ products: _angular_core.InputSignal<CatalogueProduct[]>;
996
+ /** Category groups for filtering */
997
+ categoryGroups: _angular_core.InputSignal<CategoryGroup[]>;
998
+ /** Initial selected items (for editing existing orders) */
999
+ initialSelectedItems: _angular_core.InputSignal<{
1000
+ productId?: unknown;
1001
+ quantity: number;
1002
+ variantId?: string | null;
1003
+ }[]>;
1004
+ /** Default filters to apply on init */
1005
+ defaultFilters: _angular_core.InputSignal<SelectedFilter[]>;
1006
+ /** Property name used as unique key for tracking products in the list */
1007
+ trackByField: _angular_core.InputSignal<string>;
1008
+ /** Currency code for price formatting */
1009
+ currencyCode: _angular_core.InputSignal<string>;
1010
+ /** Locale used for number/currency formatting and the quantity stepper */
1011
+ localeCode: _angular_core.InputSignal<string>;
1012
+ /**
1013
+ * Custom product thumbnail template.
1014
+ * Context: { $implicit: CatalogueProduct }
1015
+ *
1016
+ * @example
1017
+ * <ef-product-catalogue [products]="products()">
1018
+ * <ng-template #productThumbnail let-product>
1019
+ * <my-custom-thumbnail [product]="product" />
1020
+ * </ng-template>
1021
+ * </ef-product-catalogue>
1022
+ */
1023
+ productThumbnailTpl: _angular_core.Signal<TemplateRef<unknown>>;
1024
+ sortMenu: Menu;
1025
+ /** Emitted when products are validated */
1026
+ productsValidated: _angular_core.OutputEmitterRef<{
1027
+ product: CatalogueProduct;
1028
+ quantity: number;
1029
+ variantId?: string;
1030
+ }[]>;
1031
+ productSearch: _angular_core.WritableSignal<string>;
1032
+ selectedFilters: _angular_core.WritableSignal<SelectedFilter[]>;
1033
+ selectedSort: _angular_core.WritableSignal<string>;
1034
+ selectedProducts: _angular_core.WritableSignal<{
1035
+ product: CatalogueProduct;
1036
+ quantity: number;
1037
+ variantId?: string;
1038
+ }[]>;
1039
+ /** Tracks the currently selected variantId per product key */
1040
+ selectedVariants: _angular_core.WritableSignal<Record<string, string>>;
1041
+ filteredProducts: _angular_core.Signal<CatalogueProduct[]>;
1042
+ sortOptions: {
1043
+ label: string;
1044
+ icon: string;
1045
+ command: () => void;
1046
+ }[];
1047
+ onFiltersChanged(filters: SelectedFilter[]): void;
1048
+ ngOnChanges(): void;
1049
+ productKey(product: CatalogueProduct): unknown;
1050
+ private initializeSelectedProducts;
1051
+ private initializeDefaultFilters;
1052
+ toggleSortMenu(event: Event): void;
1053
+ /** Composite key for tracking product+variant selections */
1054
+ private selectionKey;
1055
+ /** Get the selected variantId for a product, or its defaultVariantId */
1056
+ getSelectedVariantId(product: CatalogueProduct): string | undefined;
1057
+ /** Set the selected variant for a product */
1058
+ setSelectedVariant(product: CatalogueProduct, variantId: string): void;
1059
+ /** Check if product is a VARIABLE product with variants */
1060
+ isVariableProduct(product: CatalogueProduct): boolean;
1061
+ /** Get active variants for a product */
1062
+ getActiveVariants(product: CatalogueProduct): CatalogueProductVariant[];
1063
+ /** Get total quantity for a product across all its variants */
1064
+ getProductQuantity(product: CatalogueProduct): number;
1065
+ /** Get quantity for a specific product+variant combination */
1066
+ getVariantQuantity(product: CatalogueProduct, variantId: string): number;
1067
+ addProduct(product: CatalogueProduct): void;
1068
+ removeProduct(product: CatalogueProduct): void;
1069
+ removeVariantSelection(product: CatalogueProduct, variantId: string): void;
1070
+ updateProductQuantity(product: CatalogueProduct, quantity: number): void;
1071
+ updateVariantQuantity(product: CatalogueProduct, variantId: string, quantity: number): void;
1072
+ validateSelection(): void;
1073
+ getPackagingLabel(product: CatalogueProduct): string;
1074
+ getVariantTitle(product: CatalogueProduct, variantId: string): string;
1075
+ trackKey(product: CatalogueProduct): unknown;
1076
+ formatPrice(product: CatalogueProduct): string;
1077
+ formatVariantPrice(variant: CatalogueProductVariant): string;
1078
+ private formatCurrency;
1079
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfProductCatalogueComponent, never>;
1080
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfProductCatalogueComponent, "ef-product-catalogue", never, { "products": { "alias": "products"; "required": false; "isSignal": true; }; "categoryGroups": { "alias": "categoryGroups"; "required": false; "isSignal": true; }; "initialSelectedItems": { "alias": "initialSelectedItems"; "required": false; "isSignal": true; }; "defaultFilters": { "alias": "defaultFilters"; "required": false; "isSignal": true; }; "trackByField": { "alias": "trackByField"; "required": false; "isSignal": true; }; "currencyCode": { "alias": "currencyCode"; "required": false; "isSignal": true; }; "localeCode": { "alias": "localeCode"; "required": false; "isSignal": true; }; }, { "productsValidated": "productsValidated"; }, ["productThumbnailTpl"], never, true, never>;
1081
+ }
1082
+
955
1083
  /**
956
1084
  * The app-wide confirmation modal, wearing the same skin as `ef-dialog`.
957
1085
  *
@@ -1233,11 +1361,18 @@ declare class EfFieldsetComponent {
1233
1361
  legend?: string;
1234
1362
  /** Translation key — takes priority over legend */
1235
1363
  legendKey?: string;
1364
+ /**
1365
+ * `framed` (default) is the bordered PrimeNG fieldset. `plain` drops the
1366
+ * frame and padding and sets the legend as a section heading: use it inside
1367
+ * an `ef-card`, so fields keep their full width at 390px. It stays a real
1368
+ * fieldset/legend, and it is never toggleable.
1369
+ */
1370
+ variant: 'framed' | 'plain';
1236
1371
  toggleable: boolean;
1237
1372
  collapsed: boolean;
1238
1373
  styleClass?: string;
1239
1374
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfFieldsetComponent, never>;
1240
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfFieldsetComponent, "ef-fieldset", never, { "legend": { "alias": "legend"; "required": false; }; "legendKey": { "alias": "legendKey"; "required": false; }; "toggleable": { "alias": "toggleable"; "required": false; }; "collapsed": { "alias": "collapsed"; "required": false; }; "styleClass": { "alias": "styleClass"; "required": false; }; }, {}, never, ["*"], true, never>;
1375
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfFieldsetComponent, "ef-fieldset", never, { "legend": { "alias": "legend"; "required": false; }; "legendKey": { "alias": "legendKey"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "toggleable": { "alias": "toggleable"; "required": false; }; "collapsed": { "alias": "collapsed"; "required": false; }; "styleClass": { "alias": "styleClass"; "required": false; }; }, {}, never, ["*"], true, never>;
1241
1376
  static ngAcceptInputType_toggleable: unknown;
1242
1377
  static ngAcceptInputType_collapsed: unknown;
1243
1378
  }
@@ -1267,12 +1402,19 @@ type EfButtonSeverity = EfButtonPrimeNGSeverity | EfButtonComptoirSeverity;
1267
1402
  * `disabled`, `loading`, `pTooltip` (via `tooltipKey`), and emit the
1268
1403
  * same `clickEvent`.
1269
1404
  */
1270
- declare class EfButtonComponent {
1405
+ declare class EfButtonComponent implements OnInit {
1271
1406
  /** Direct label text (not translated). */
1272
1407
  label?: string;
1273
1408
  /** Translation key — takes priority over `label`. */
1274
1409
  labelKey?: string;
1275
1410
  icon?: string;
1411
+ /**
1412
+ * Translation key for the button's accessible name (`aria-label`). Required
1413
+ * in practice for an icon-only button: a tooltip is not read as the name.
1414
+ * A button with a visible label does not need it. An icon-only button with
1415
+ * neither a label nor this key logs a dev-mode warning.
1416
+ */
1417
+ ariaLabelKey?: string;
1276
1418
  /**
1277
1419
  * Which rendering surface to use. Default `'primeng'` keeps v1
1278
1420
  * consumers unchanged. V2 detail/list screens pass `'comptoir'`
@@ -1354,9 +1496,10 @@ declare class EfButtonComponent {
1354
1496
  get comptoirClass(): string;
1355
1497
  /** Inline danger tint for the `severity="danger"` Comptoir variant. */
1356
1498
  get comptoirInlineColor(): string | null;
1499
+ ngOnInit(): void;
1357
1500
  handleClick(event: Event): void;
1358
1501
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfButtonComponent, never>;
1359
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfButtonComponent, "ef-button", never, { "label": { "alias": "label"; "required": false; }; "labelKey": { "alias": "labelKey"; "required": false; }; "icon": { "alias": "icon"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "severity": { "alias": "severity"; "required": false; }; "size": { "alias": "size"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "rounded": { "alias": "rounded"; "required": false; }; "outlined": { "alias": "outlined"; "required": false; }; "raised": { "alias": "raised"; "required": false; }; "text": { "alias": "text"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "styleClass": { "alias": "styleClass"; "required": false; }; "type": { "alias": "type"; "required": false; }; "badge": { "alias": "badge"; "required": false; }; "badgeSeverity": { "alias": "badgeSeverity"; "required": false; }; "tooltip": { "alias": "tooltip"; "required": false; }; "tooltipKey": { "alias": "tooltipKey"; "required": false; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; }, { "clickEvent": "clickEvent"; }, never, never, true, never>;
1502
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfButtonComponent, "ef-button", never, { "label": { "alias": "label"; "required": false; }; "labelKey": { "alias": "labelKey"; "required": false; }; "icon": { "alias": "icon"; "required": false; }; "ariaLabelKey": { "alias": "ariaLabelKey"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "severity": { "alias": "severity"; "required": false; }; "size": { "alias": "size"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "rounded": { "alias": "rounded"; "required": false; }; "outlined": { "alias": "outlined"; "required": false; }; "raised": { "alias": "raised"; "required": false; }; "text": { "alias": "text"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "styleClass": { "alias": "styleClass"; "required": false; }; "type": { "alias": "type"; "required": false; }; "badge": { "alias": "badge"; "required": false; }; "badgeSeverity": { "alias": "badgeSeverity"; "required": false; }; "tooltip": { "alias": "tooltip"; "required": false; }; "tooltipKey": { "alias": "tooltipKey"; "required": false; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; }, { "clickEvent": "clickEvent"; }, never, never, true, never>;
1360
1503
  static ngAcceptInputType_disabled: unknown;
1361
1504
  static ngAcceptInputType_rounded: unknown;
1362
1505
  static ngAcceptInputType_outlined: unknown;
@@ -1514,6 +1657,15 @@ declare class EfBadgeComponent {
1514
1657
  * Badge label text
1515
1658
  */
1516
1659
  label: _angular_core.InputSignal<string>;
1660
+ /**
1661
+ * Translation key for the label; wins over `label`.
1662
+ */
1663
+ labelKey: _angular_core.InputSignal<string>;
1664
+ /**
1665
+ * Opt in to the pointer cursor and hover lift. Off by default so a
1666
+ * read-only badge does not look like a button.
1667
+ */
1668
+ clickable: _angular_core.InputSignalWithTransform<boolean, unknown>;
1517
1669
  /**
1518
1670
  * Badge color (Tailwind color name)
1519
1671
  */
@@ -1555,7 +1707,7 @@ declare class EfBadgeComponent {
1555
1707
  */
1556
1708
  iconClasses: _angular_core.Signal<string>;
1557
1709
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfBadgeComponent, never>;
1558
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfBadgeComponent, "ef-badge", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "styleClass": { "alias": "styleClass"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "showDot": { "alias": "showDot"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1710
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfBadgeComponent, "ef-badge", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "styleClass": { "alias": "styleClass"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "showDot": { "alias": "showDot"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1559
1711
  }
1560
1712
 
1561
1713
  declare class EfToolbarComponent {
@@ -2649,6 +2801,52 @@ declare class EfFormGridComponent {
2649
2801
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfFormGridComponent, "ef-form-grid", never, {}, {}, never, ["*"], true, never>;
2650
2802
  }
2651
2803
 
2804
+ /**
2805
+ * Declarative row-action item consumed by `ef-row-actions`.
2806
+ *
2807
+ * The component renders a native Comptoir-styled popup (no PrimeNG
2808
+ * Menu) so the design — kbd hint, danger tone, divider, ink-active
2809
+ * trigger when open — matches the prototype 1:1.
2810
+ *
2811
+ * Use `command` for actions, `separator: true` for a divider line.
2812
+ */
2813
+ interface EfRowAction {
2814
+ /** Stable identifier for trackBy / tests. */
2815
+ id?: string;
2816
+ /** Translation key — preferred. */
2817
+ labelKey?: string;
2818
+ /** Direct label — used only when `labelKey` is empty. */
2819
+ label?: string;
2820
+ /** PrimeIcons class (e.g. `'pi pi-pencil'`). */
2821
+ icon?: string;
2822
+ /** Optional keyboard-shortcut hint shown right-aligned (e.g. `'↵'`, `'E'`, `'⌘D'`). */
2823
+ kbd?: string;
2824
+ /** Tone: `'danger'` styles destructive items in the cancelled palette. */
2825
+ severity?: 'default' | 'danger';
2826
+ /** Render a divider in this slot — all other fields ignored. */
2827
+ separator?: boolean;
2828
+ /** Disable without removing. The item stays focusable and is exposed
2829
+ * as `aria-disabled`, so a keyboard user can land on it and read why. */
2830
+ disabled?: boolean;
2831
+ /** Translation key saying why the item is disabled. Shown under the
2832
+ * label, as its tooltip, and as its `aria-describedby`. */
2833
+ reasonKey?: string;
2834
+ /**
2835
+ * Hide the item explicitly. Combined with `permission` and any
2836
+ * caller-supplied filter; defaults to `true` (visible).
2837
+ */
2838
+ visible?: boolean;
2839
+ /**
2840
+ * If set, the item is hidden unless the bound `ScreenContext`
2841
+ * grants this permission, or every permission in the array (e.g.
2842
+ * Duplicate needs `[Duplicate, Create]`, since a duplicate is saved
2843
+ * through create). Items with no `permission` always show.
2844
+ */
2845
+ permission?: Permissions | ReadonlyArray<Permissions>;
2846
+ /** Click handler. The container handles closing the popup. */
2847
+ command?: () => void;
2848
+ }
2849
+
2652
2850
  /**
2653
2851
  * Sticky detail-screen toolbar. Two slots and an optional declarative
2654
2852
  * action API:
@@ -2685,6 +2883,14 @@ declare class EfFormGridComponent {
2685
2883
  * </ef-detail-toolbar>
2686
2884
  * ```
2687
2885
  *
2886
+ * Phone overflow: on the mobile tier, when the visible actions (custom and
2887
+ * standard, Save included) outnumber `[overflowAfter]`, Save stays in view
2888
+ * and every other action moves into a kebab menu (`ef-row-actions`, so it is
2889
+ * a `role="menu"` with Escape, outside-click and RTL placement). The split
2890
+ * happens after permission and `requiresWrite` filtering, so a hidden action
2891
+ * is hidden in the menu too. The projected `[actions]` slot is not moved.
2892
+ * Buttons are 48px on a coarse pointer (see `.detail-toolbar` in patterns).
2893
+ *
2688
2894
  * Custom actions can declare `permission` and `visible` like
2689
2895
  * `EfRowAction`. With `[context]` bound, the toolbar filters them
2690
2896
  * against `ScreenContext.isGranted()` automatically.
@@ -2718,6 +2924,11 @@ declare class EfDetailToolbarComponent {
2718
2924
  /** i18n key for the Save button label. Screens typically pass
2719
2925
  * `'common_create'` in create mode and `'common_save'` in edit. */
2720
2926
  readonly saveLabelKey: _angular_core.InputSignal<string>;
2927
+ /** Most actions (Save included) kept inline on a phone; more than this
2928
+ * moves all but Save into the overflow menu. Desktop and tablet never
2929
+ * overflow. */
2930
+ readonly overflowAfter: _angular_core.InputSignalWithTransform<number, string | number>;
2931
+ private readonly viewport;
2721
2932
  /** Fired when a `customActions` button is clicked. The toast
2722
2933
  * service or screen handler should consume `id`. The `command`
2723
2934
  * callback on the action is also invoked. */
@@ -2738,6 +2949,10 @@ declare class EfDetailToolbarComponent {
2738
2949
  readonly canSave: _angular_core.Signal<boolean>;
2739
2950
  readonly hasStandardActions: _angular_core.Signal<boolean>;
2740
2951
  readonly hasCustomActions: _angular_core.Signal<boolean>;
2952
+ /** The phone is showing the overflow menu instead of inline buttons. */
2953
+ readonly overflowing: _angular_core.Signal<boolean>;
2954
+ /** Everything but Save, as menu items, in toolbar order. */
2955
+ readonly overflowItems: _angular_core.Signal<readonly EfRowAction[]>;
2741
2956
  /** Whether the `|` separator should render — both groups present. */
2742
2957
  readonly showSeparator: _angular_core.Signal<boolean>;
2743
2958
  onCustomClick(action: EfDetailToolbarAction): void;
@@ -2748,7 +2963,7 @@ declare class EfDetailToolbarComponent {
2748
2963
  /** Stable trackBy across renders. */
2749
2964
  trackByAction: (i: number, a: EfDetailToolbarAction) => unknown;
2750
2965
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfDetailToolbarComponent, never>;
2751
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfDetailToolbarComponent, "ef-detail-toolbar", never, { "customActions": { "alias": "customActions"; "required": false; "isSignal": true; }; "context": { "alias": "context"; "required": false; "isSignal": true; }; "showPrintAction": { "alias": "showPrintAction"; "required": false; "isSignal": true; }; "showDuplicateAction": { "alias": "showDuplicateAction"; "required": false; "isSignal": true; }; "showDeleteAction": { "alias": "showDeleteAction"; "required": false; "isSignal": true; }; "showSaveAction": { "alias": "showSaveAction"; "required": false; "isSignal": true; }; "saving": { "alias": "saving"; "required": false; "isSignal": true; }; "saveLabelKey": { "alias": "saveLabelKey"; "required": false; "isSignal": true; }; }, { "customAction": "customAction"; "print": "print"; "duplicate": "duplicate"; "delete": "delete"; "save": "save"; }, never, ["*", "[actions]"], true, never>;
2966
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfDetailToolbarComponent, "ef-detail-toolbar", never, { "customActions": { "alias": "customActions"; "required": false; "isSignal": true; }; "context": { "alias": "context"; "required": false; "isSignal": true; }; "showPrintAction": { "alias": "showPrintAction"; "required": false; "isSignal": true; }; "showDuplicateAction": { "alias": "showDuplicateAction"; "required": false; "isSignal": true; }; "showDeleteAction": { "alias": "showDeleteAction"; "required": false; "isSignal": true; }; "showSaveAction": { "alias": "showSaveAction"; "required": false; "isSignal": true; }; "saving": { "alias": "saving"; "required": false; "isSignal": true; }; "saveLabelKey": { "alias": "saveLabelKey"; "required": false; "isSignal": true; }; "overflowAfter": { "alias": "overflowAfter"; "required": false; "isSignal": true; }; }, { "customAction": "customAction"; "print": "print"; "duplicate": "duplicate"; "delete": "delete"; "save": "save"; }, never, ["*", "[actions]"], true, never>;
2752
2967
  }
2753
2968
 
2754
2969
  /**
@@ -3092,6 +3307,10 @@ declare class EfKpiCardComponent {
3092
3307
  readonly delta: _angular_core.InputSignal<string>;
3093
3308
  /** Delta tone — drives the arrow color. */
3094
3309
  readonly deltaTone: _angular_core.InputSignal<EfKpiDeltaTone>;
3310
+ /** Translation key of a secondary line under the value (e.g. "of which delivery: 120 MAD"). */
3311
+ readonly hintKey: _angular_core.InputSignal<string>;
3312
+ /** Interpolation params for `hintKey`. */
3313
+ readonly hintParams: _angular_core.InputSignal<Record<string, unknown>>;
3095
3314
  /** Raw series for the sparkline. */
3096
3315
  readonly spark: _angular_core.InputSignal<EfKpiSparkline>;
3097
3316
  /** Stroke color for the sparkline polyline. Defaults to the active
@@ -3103,7 +3322,7 @@ declare class EfKpiCardComponent {
3103
3322
  * when the series is empty. */
3104
3323
  readonly sparkPoints: _angular_core.Signal<string>;
3105
3324
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfKpiCardComponent, never>;
3106
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfKpiCardComponent, "ef-kpi-card", never, { "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "unit": { "alias": "unit"; "required": false; "isSignal": true; }; "delta": { "alias": "delta"; "required": false; "isSignal": true; }; "deltaTone": { "alias": "deltaTone"; "required": false; "isSignal": true; }; "spark": { "alias": "spark"; "required": false; "isSignal": true; }; "sparkColor": { "alias": "sparkColor"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3325
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfKpiCardComponent, "ef-kpi-card", never, { "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "unit": { "alias": "unit"; "required": false; "isSignal": true; }; "delta": { "alias": "delta"; "required": false; "isSignal": true; }; "deltaTone": { "alias": "deltaTone"; "required": false; "isSignal": true; }; "hintKey": { "alias": "hintKey"; "required": false; "isSignal": true; }; "hintParams": { "alias": "hintParams"; "required": false; "isSignal": true; }; "spark": { "alias": "spark"; "required": false; "isSignal": true; }; "sparkColor": { "alias": "sparkColor"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3107
3326
  }
3108
3327
 
3109
3328
  type EfChartType = 'line' | 'bar' | 'donut';
@@ -3166,6 +3385,78 @@ declare class EfRankListComponent {
3166
3385
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfRankListComponent, "ef-rank-list", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3167
3386
  }
3168
3387
 
3388
+ type EfIdentityThumbSize = 'sm' | 'md';
3389
+ /**
3390
+ * The square that stands for a record: a collection in its detail toolbar, a
3391
+ * product in a picker row. Shows the record's image when it has one and two
3392
+ * initials of its name when it does not (or when the image fails to load).
3393
+ *
3394
+ * Built on the `.thumb` pattern from `_patterns.scss`; the size is a host
3395
+ * class (`sm` 32px, `md` 40px), never an inline style. The image is
3396
+ * decorative (`alt=""`): the name always sits next to it.
3397
+ *
3398
+ * ```html
3399
+ * <ef-identity-thumb [imageUrl]="imageUrl()" [name]="title()" size="sm" />
3400
+ * ```
3401
+ */
3402
+ declare class EfIdentityThumbComponent {
3403
+ readonly imageUrl: _angular_core.InputSignal<string>;
3404
+ readonly name: _angular_core.InputSignal<string>;
3405
+ readonly size: _angular_core.InputSignal<EfIdentityThumbSize>;
3406
+ /** Set when the image fails; reset whenever the url changes. */
3407
+ protected readonly failed: _angular_core.WritableSignal<boolean>;
3408
+ protected readonly showImage: _angular_core.Signal<boolean>;
3409
+ /**
3410
+ * First letter of the first two words, or the first two letters of a
3411
+ * single word. `Array.from` splits by code point, so a letter outside the
3412
+ * BMP is never cut in half.
3413
+ */
3414
+ protected readonly initials: _angular_core.Signal<string>;
3415
+ constructor();
3416
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfIdentityThumbComponent, never>;
3417
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfIdentityThumbComponent, "ef-identity-thumb", never, { "imageUrl": { "alias": "imageUrl"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3418
+ }
3419
+
3420
+ /**
3421
+ * What a search engine would show for a page: the title, the address and the
3422
+ * description, cut where the engine cuts them (60 and 160 characters, the
3423
+ * same figures the SEO counters advise). One preview for every SEO card.
3424
+ *
3425
+ * It depicts a search result, so it keeps its own light surface and the
3426
+ * familiar link colours in both themes. The address is always left to right;
3427
+ * the title and description follow the direction of their own text, so an
3428
+ * Arabic page previews right to left inside an LTR or RTL app.
3429
+ *
3430
+ * ```html
3431
+ * <ef-seo-preview
3432
+ * [pageTitle]="seoTitle() || title()"
3433
+ * [url]="'shop.example/collections/' + slug()"
3434
+ * [description]="seoDescription() || description()"
3435
+ * contentLang="fr" />
3436
+ * ```
3437
+ */
3438
+ declare class EfSeoPreviewComponent {
3439
+ /** The page title. Named `pageTitle` so it does not become the host's tooltip. */
3440
+ readonly pageTitle: _angular_core.InputSignal<string>;
3441
+ /** The address as shown (no scheme needed), always rendered left to right. */
3442
+ readonly url: _angular_core.InputSignal<string>;
3443
+ readonly description: _angular_core.InputSignal<string>;
3444
+ /** i18n key of the small heading above the preview. */
3445
+ readonly labelKey: _angular_core.InputSignal<string>;
3446
+ /** Characters kept of the title; `null` shows it whole. */
3447
+ readonly titleMaxLength: _angular_core.InputSignal<number>;
3448
+ /** Characters kept of the description; `null` shows it whole. */
3449
+ readonly descriptionMaxLength: _angular_core.InputSignal<number>;
3450
+ /** Language of the previewed text (`fr`, `ar`, `en`), for screen readers and hyphenation. */
3451
+ readonly contentLang: _angular_core.InputSignal<string>;
3452
+ protected readonly labelId: string;
3453
+ protected readonly shownTitle: _angular_core.Signal<string>;
3454
+ protected readonly shownUrl: _angular_core.Signal<string>;
3455
+ protected readonly shownDescription: _angular_core.Signal<string>;
3456
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfSeoPreviewComponent, never>;
3457
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfSeoPreviewComponent, "ef-seo-preview", never, { "pageTitle": { "alias": "pageTitle"; "required": false; "isSignal": true; }; "url": { "alias": "url"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "titleMaxLength": { "alias": "titleMaxLength"; "required": false; "isSignal": true; }; "descriptionMaxLength": { "alias": "descriptionMaxLength"; "required": false; "isSignal": true; }; "contentLang": { "alias": "contentLang"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3458
+ }
3459
+
3169
3460
  /**
3170
3461
  * Shared base for every Comptoir form control (`ef-input-text`,
3171
3462
  * `ef-textarea`, `ef-select`, `ef-input-number`, `ef-checkbox`, …).
@@ -3186,8 +3477,9 @@ declare class EfRankListComponent {
3186
3477
  * - **ARIA** — `ariaLabelKey` / `ariaLabel` for screen-reader-only
3187
3478
  * labels; `effectiveAriaLabel` returns `null` when a visible
3188
3479
  * label is rendered (which already serves as the ARIA name).
3189
- * `errorsId` provides a stable id for the error/hint container
3190
- * so subclasses can wire `aria-describedby`.
3480
+ * `errorsId` and `hintId` give the error container and the hint
3481
+ * stable ids, and `describedBy()` joins them into the value
3482
+ * subclasses bind to `aria-describedby`.
3191
3483
  * - **State** — `required` / `readonly` / `disabled` shared inputs,
3192
3484
  * used directly in subclass templates and toggled by CVA's
3193
3485
  * `setDisabledState` in subclasses.
@@ -3224,6 +3516,14 @@ declare abstract class AbstractEfFormControl {
3224
3516
  readonly labelKey: _angular_core.InputSignal<string>;
3225
3517
  readonly placeholder: _angular_core.InputSignal<string>;
3226
3518
  readonly placeholderKey: _angular_core.InputSignal<string>;
3519
+ /** Short help text shown under the control. Use `hintKey` for i18n. */
3520
+ readonly hint: _angular_core.InputSignal<string>;
3521
+ /** Translation key for the hint, preferred over `hint`. The hint is
3522
+ * linked to the control through `aria-describedby` (see
3523
+ * {@link describedBy}), so a screen reader reads it with the field.
3524
+ * Rendered by ef-input-text, ef-textarea, ef-inputnumber and ef-select;
3525
+ * the other controls inherit the input but do not render it yet. */
3526
+ readonly hintKey: _angular_core.InputSignal<string>;
3227
3527
  readonly ariaLabel: _angular_core.InputSignal<string>;
3228
3528
  readonly ariaLabelKey: _angular_core.InputSignal<string>;
3229
3529
  readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
@@ -3298,12 +3598,23 @@ declare abstract class AbstractEfFormControl {
3298
3598
  * accessible name and duplicate `aria-label` would override it.
3299
3599
  */
3300
3600
  get effectiveAriaLabel(): string | null;
3301
- /** Stable id for the error / hint container — wired through
3601
+ /** Translated hint (or empty string). */
3602
+ get effectiveHint(): string;
3603
+ /** Stable id of the hint element, `<id>-hint`. */
3604
+ get hintId(): string;
3605
+ /**
3606
+ * The control's `aria-describedby`: the hint id when a hint is shown,
3607
+ * then the errors id when `showErrors` (the subclass knows when its
3608
+ * error container renders), then any `extra` ids. `null` (no
3609
+ * attribute) when there is nothing to point at.
3610
+ */
3611
+ describedBy(showErrors: boolean, ...extra: (string | null | undefined)[]): string | null;
3612
+ /** Stable id for the error container, wired through
3302
3613
  * `aria-describedby` on the input so screen readers announce
3303
3614
  * the validation message together with the field name. */
3304
3615
  get errorsId(): string;
3305
3616
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<AbstractEfFormControl, never>;
3306
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractEfFormControl, "ng-component", never, { "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "placeholderKey": { "alias": "placeholderKey"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelKey": { "alias": "ariaLabelKey"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "showClear": { "alias": "showClear"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3617
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AbstractEfFormControl, "ng-component", never, { "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "placeholderKey": { "alias": "placeholderKey"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "hintKey": { "alias": "hintKey"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaLabelKey": { "alias": "ariaLabelKey"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "showClear": { "alias": "showClear"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3307
3618
  }
3308
3619
 
3309
3620
  declare class EfInputTextComponent extends AbstractEfFormControl implements ControlValueAccessor {
@@ -3321,7 +3632,7 @@ declare class EfInputTextComponent extends AbstractEfFormControl implements Cont
3321
3632
  * Rendering variant — same dual-mode pattern as `ef-button`.
3322
3633
  * - `'comptoir'` (default) — native `<input class="ef-input">` against
3323
3634
  * the Comptoir pattern styles. Renders at the
3324
- * canonical `--hit-base` (40px) control height.
3635
+ * canonical `--hit-field` height (40px, 48px on touch).
3325
3636
  * Required for `prefix` / `suffix`.
3326
3637
  * - `'primeng'` — wraps `<input pInputText>` (v1). Opt in when
3327
3638
  * you specifically need PrimeNG input behaviour.
@@ -3408,7 +3719,7 @@ declare class EfSelectComponent extends AbstractEfFormControl implements Control
3408
3719
  group: boolean;
3409
3720
  optionGroupLabel: string;
3410
3721
  optionGroupChildren: string;
3411
- /** Control height. Default (undefined) = the canonical `--hit-base` (40px),
3722
+ /** Control height. Default (undefined) = the canonical `--hit-field` (40px, 48px on touch),
3412
3723
  * matching native inputs (ADR-009). `'small'` opts into the dense 32px
3413
3724
  * variant for compact contexts (e.g. table-row editors). */
3414
3725
  size?: 'small' | 'large';
@@ -3469,8 +3780,8 @@ declare class EfSelectComponent extends AbstractEfFormControl implements Control
3469
3780
  * the `.es` / `.es__panel` patterns (the custom
3470
3781
  * dropdown: filter, styled options, animated panel
3471
3782
  * — see `components.html` Section 04). Also pinned
3472
- * to the canonical `--hit-base` (40px) trigger
3473
- * height (`.es.p-select { min-height: --hit-base }`),
3783
+ * to the canonical `--hit-field` (40px, 48px on touch) trigger
3784
+ * height (`.es.p-select { min-height: --hit-field }`),
3474
3785
  * so it aligns with inputs/buttons in a row.
3475
3786
  * - `'comptoir'` — lite native `<select class="ef-select-native">`.
3476
3787
  * No filter / panel / multi / templates; use it for
@@ -3505,11 +3816,28 @@ declare class EfSelectComponent extends AbstractEfFormControl implements Control
3505
3816
  ngControl: NgControl | null;
3506
3817
  private readonly injector;
3507
3818
  ngOnInit(): void;
3819
+ /**
3820
+ * The text an unset select shows: the placeholder, or nothing. A blank or
3821
+ * dash-only placeholder (a translation of " " or "—") counts as nothing, so
3822
+ * the unset state can never read as "— —". The native variant used to wrap
3823
+ * any truthy placeholder in dashes, which turned " " into exactly that.
3824
+ */
3825
+ get placeholderText(): string;
3508
3826
  /** Translated filter placeholder. Falls back to a generic key, then
3509
3827
  * to "Search…" so it always renders something readable. */
3510
3828
  get effectiveFilterPlaceholder(): string;
3511
3829
  get effectiveEmptyMessage(): string;
3512
3830
  get effectiveEmptyFilterMessage(): string;
3831
+ private _describedByPt?;
3832
+ /**
3833
+ * PrimeNG pass-through that puts `aria-describedby` (hint, then errors)
3834
+ * on the element the `<label for>` points at: `p-select`'s
3835
+ * `span[role=combobox]` (`label`) and `p-multiselect`'s hidden focus
3836
+ * input (`hiddenInput`). Neither component has an input for it. Cached
3837
+ * per id list so change detection does not hand PrimeNG a new object
3838
+ * every pass.
3839
+ */
3840
+ get describedByPt(): Record<string, Record<string, string | null>>;
3513
3841
  /** Always include `.es` so the global Comptoir skin in
3514
3842
  * `_patterns.scss` always finds a hook, regardless of what the
3515
3843
  * consumer passes via `styleClass`. */
@@ -3643,6 +3971,11 @@ declare class EfCheckboxComponent extends AbstractEfFormControl implements Contr
3643
3971
  * ariaLabelKey="sales_clients_toggle_active_aria"
3644
3972
  * (checkedChange)="setActive(row, $event)" />
3645
3973
  * ```
3974
+ *
3975
+ * With a label, the host is a flex row: the switch keeps its size and a
3976
+ * sentence-long label wraps beside it, not under it, at phone width.
3977
+ * Screens no longer restyle `ef-toggleswitch` for this. Without a label
3978
+ * (a table row's switch) the host keeps its inline box.
3646
3979
  */
3647
3980
  declare class EfToggleSwitchComponent extends AbstractEfFormControl implements ControlValueAccessor {
3648
3981
  /**
@@ -3746,38 +4079,6 @@ declare class EfDatepickerComponent implements ControlValueAccessor {
3746
4079
  static ngAcceptInputType_autofocus: unknown;
3747
4080
  }
3748
4081
 
3749
- /**
3750
- * Comptoir number input.
3751
- *
3752
- * Two render variants share the same API:
3753
- * - `'comptoir'` (default) — native `<input type="text" inputmode="decimal">`
3754
- * styled against `.ef-input` / `.ef-input-group`. Lean, no locale
3755
- * formatting on display, but data entry accepts both `,` and `.` as
3756
- * the decimal separator (fr-MA users type commas) and ignores
3757
- * space group separators; pair with `suffix="MAD"` for unit display
3758
- * in dense grids (variants table, inventory adjustments, etc.).
3759
- * See ADR-009.
3760
- * - `'primeng'` — wraps `p-inputnumber`. Full locale-aware
3761
- * currency / decimal formatting, stepper buttons, prefix / suffix.
3762
- *
3763
- * Identity / label / placeholder / aria / required / readonly /
3764
- * disabled are inherited from `AbstractEfFormControl`.
3765
- *
3766
- * ```html
3767
- * <ef-inputnumber
3768
- * labelKey="catalog.price"
3769
- * variant="primeng"
3770
- * mode="currency" currency="MAD" locale="fr-MA"
3771
- * [(value)]="variant.price" />
3772
- *
3773
- * <ef-inputnumber
3774
- * labelKey="catalog.inventory"
3775
- * variant="comptoir"
3776
- * [showButtons]="true" buttonLayout="horizontal"
3777
- * [min]="0" [step]="1"
3778
- * [(value)]="variant.inventoryQuantity" />
3779
- * ```
3780
- */
3781
4082
  declare class EfInputNumberComponent extends AbstractEfFormControl implements ControlValueAccessor {
3782
4083
  inputNumber?: InputNumber;
3783
4084
  value?: Nullable<number>;
@@ -3804,7 +4105,7 @@ declare class EfInputNumberComponent extends AbstractEfFormControl implements Co
3804
4105
  step: number;
3805
4106
  allowEmpty: boolean;
3806
4107
  inputSize?: number;
3807
- /** Default (undefined) = canonical `--hit-base` (40px); `'small'` = dense 32px. */
4108
+ /** PrimeNG variant only: `'small'` = dense 32px. The comptoir variant ignores it and is always `--hit-field` (40px, 48px on touch). */
3808
4109
  size?: 'small' | 'large';
3809
4110
  maxlength?: number;
3810
4111
  minlength?: number;
@@ -3827,7 +4128,7 @@ declare class EfInputNumberComponent extends AbstractEfFormControl implements Co
3827
4128
  * Render variant.
3828
4129
  * - `'comptoir'` (default) — native `<input type="text" inputmode="decimal">`
3829
4130
  * styled against `.ef-input` / `.ef-input-group`,
3830
- * at the canonical `--hit-base` (40px) height.
4131
+ * at the canonical `--hit-field` height (40px, 48px on touch).
3831
4132
  * Accepts `,` and `.` as decimal separator.
3832
4133
  * - `'primeng'` — wraps `p-inputnumber`. Opt in for PrimeNG's
3833
4134
  * formatted stepper behaviour.
@@ -3848,9 +4149,54 @@ declare class EfInputNumberComponent extends AbstractEfFormControl implements Co
3848
4149
  /** A number field is clearable when it holds any value (including 0). */
3849
4150
  protected get hasValue(): boolean;
3850
4151
  clearValue(): void;
4152
+ /** True while the comptoir input has focus in currency mode. */
4153
+ private focused;
4154
+ /** What the user typed since focus, shown as is (never reformatted). */
4155
+ private editText;
4156
+ /** A change that did not come from typing (clear, stepper, form write)
4157
+ * replaces the edit text while focused, or the box would keep the old
4158
+ * amount and the next keystroke would append to it. Marks the view,
4159
+ * since a programmatic call (zoneless) would not. */
4160
+ private syncEditText;
4161
+ /** Comptoir currency formatting applies: `mode="currency"` and a currency code. */
4162
+ get isCurrency(): boolean;
4163
+ private get effectiveLocale();
4164
+ /** Decimals: explicit inputs, else the currency's own (2 for MAD). */
4165
+ private get fractionDigits();
4166
+ /**
4167
+ * The amount as text, in Latin digits and without bidi marks: grouped
4168
+ * for display, ungrouped for editing (so the text always parses back
4169
+ * through {@link parseComptoirValue}).
4170
+ */
4171
+ private formatAmount;
4172
+ /**
4173
+ * The currency addon: the locale's symbol and the side it goes on, or
4174
+ * `null` outside currency mode or when an explicit prefix / suffix
4175
+ * already shows the unit.
4176
+ */
4177
+ get currencyAddon(): {
4178
+ symbol: string;
4179
+ side: 'prefix' | 'suffix';
4180
+ } | null;
4181
+ /**
4182
+ * Currency mode, before the generic parse: drops the currency's names (a
4183
+ * pasted "1.234,50 MAD" or "1.234,50 د.م."), then, when the currency has
4184
+ * at most 2 decimals, reads the locale's own group separator followed by
4185
+ * groups of exactly 3 digits as grouping. "1.500" in a fr-MA or ar-MA MAD
4186
+ * field is 1 500, not 1,50: a 3-digit tail cannot be a decimal there.
4187
+ * Only the locale's group character counts, so a French "1,500" stays
4188
+ * 1,5, and "0.500" or "12.50" stay decimals.
4189
+ */
4190
+ private stripCurrencyGrouping;
4191
+ /** Id of the currency addon, referenced by the input's aria-describedby. */
4192
+ get currencyId(): string;
4193
+ /** Text bound to the comptoir input. */
4194
+ get displayText(): string | number;
3851
4195
  private onChange;
3852
4196
  private onTouched;
3853
4197
  readonly ngControl: NgControl;
4198
+ private readonly localeId;
4199
+ private readonly cdr;
3854
4200
  constructor();
3855
4201
  get showRequired(): boolean;
3856
4202
  get serverErrors(): string[] | null;
@@ -3871,6 +4217,8 @@ declare class EfInputNumberComponent extends AbstractEfFormControl implements Co
3871
4217
  * dot before parsing, and spaces (JS `\s` covers NBSP / narrow-NBSP
3872
4218
  * group separators) are stripped. Both `23,50` and `23.50` parse to
3873
4219
  * 23.5; unparseable text yields `null` (existing empty semantics).
4220
+ * Bidi marks (an Arabic formatted amount carries U+200F) are dropped,
4221
+ * and when both `.` and `,` appear the last one is the decimal.
3874
4222
  */
3875
4223
  private parseComptoirValue;
3876
4224
  /**
@@ -4471,50 +4819,16 @@ type EfDataCardMobileLayout = 'row' | 'card';
4471
4819
  */
4472
4820
  declare const EF_DATA_CARD_MOBILE_LAYOUT: InjectionToken<EfDataCardMobileLayout>;
4473
4821
 
4474
- /**
4475
- * Declarative row-action item consumed by `ef-row-actions`.
4476
- *
4477
- * The component renders a native Comptoir-styled popup (no PrimeNG
4478
- * Menu) so the design — kbd hint, danger tone, divider, ink-active
4479
- * trigger when open — matches the prototype 1:1.
4480
- *
4481
- * Use `command` for actions, `separator: true` for a divider line.
4482
- */
4483
- interface EfRowAction {
4484
- /** Stable identifier for trackBy / tests. */
4485
- id?: string;
4486
- /** Translation key — preferred. */
4487
- labelKey?: string;
4488
- /** Direct label — used only when `labelKey` is empty. */
4489
- label?: string;
4490
- /** PrimeIcons class (e.g. `'pi pi-pencil'`). */
4491
- icon?: string;
4492
- /** Optional keyboard-shortcut hint shown right-aligned (e.g. `'↵'`, `'E'`, `'⌘D'`). */
4493
- kbd?: string;
4494
- /** Tone: `'danger'` styles destructive items in the cancelled palette. */
4495
- severity?: 'default' | 'danger';
4496
- /** Render a divider in this slot — all other fields ignored. */
4497
- separator?: boolean;
4498
- /** Disable without removing. */
4499
- disabled?: boolean;
4500
- /**
4501
- * Hide the item explicitly. Combined with `permission` and any
4502
- * caller-supplied filter; defaults to `true` (visible).
4503
- */
4504
- visible?: boolean;
4505
- /**
4506
- * If set, the item is hidden unless the bound `ScreenContext`
4507
- * grants this permission, or every permission in the array (e.g.
4508
- * Duplicate needs `[Duplicate, Create]`, since a duplicate is saved
4509
- * through create). Items with no `permission` always show.
4510
- */
4511
- permission?: Permissions | ReadonlyArray<Permissions>;
4512
- /** Click handler. The container handles closing the popup. */
4513
- command?: () => void;
4514
- }
4515
-
4516
4822
  /** Action identifier emitted by `ef-data-card`'s auto row-actions cell. */
4517
4823
  type EfDataCardRowAction = 'view' | 'edit' | 'duplicate' | 'delete';
4824
+ /** What `[rowActionState]` answers for one action on one row: `'hidden'`
4825
+ * leaves it out of the menu, `'disabled'` (or `{ state: 'disabled', reasonKey }`, which also says why, announced
4826
+ * and shown under the item) shows it greyed and inert, and
4827
+ * `null` / `undefined` leaves it as it is. */
4828
+ type EfDataCardRowActionState = 'hidden' | 'disabled' | {
4829
+ state: 'disabled';
4830
+ reasonKey?: string;
4831
+ } | null | undefined;
4518
4832
  /** Row height presets offered by the Density control. */
4519
4833
  type EfTableDensity = 'compact' | 'default' | 'comfortable';
4520
4834
  /**
@@ -4694,12 +5008,48 @@ declare class EfDataCardComponent<TRow = any> implements AfterContentInit {
4694
5008
  * their native behaviour (toggle selection, run an action, …).
4695
5009
  */
4696
5010
  readonly rowDoubleClickable: _angular_core.InputSignalWithTransform<boolean, unknown>;
5011
+ /**
5012
+ * Turns an activatable row into a toggle button (APG button pattern):
5013
+ * the row, already `role="button"` under `[rowDoubleClickable]`, gets
5014
+ * `aria-pressed` from this function, on the desktop `<tr>` and on the
5015
+ * phone row head alike. Enter and Space both activate. For a
5016
+ * multi-select picker whose rows are the selection.
5017
+ *
5018
+ * `aria-pressed` rather than `aria-selected`: selection belongs to a
5019
+ * grid or listbox, which own arrow-key focus management this table does
5020
+ * not have, and `aria-selected` is not allowed on a button. Unset, no
5021
+ * attribute is written.
5022
+ */
5023
+ readonly rowPressed: _angular_core.InputSignal<(row: TRow) => boolean>;
5024
+ /**
5025
+ * A row that is shown but cannot be activated (a product already in the
5026
+ * collection): `.is-disabled` dims it, `aria-disabled` says so, and
5027
+ * click, Enter and Space are ignored. It stays focusable, so a screen
5028
+ * reader user can still find it and hear why nothing happens.
5029
+ */
5030
+ readonly rowDisabled: _angular_core.InputSignal<(row: TRow) => boolean>;
5031
+ protected pressedAttr(row: TRow): 'true' | 'false' | null;
5032
+ protected isRowDisabled(row: TRow): boolean;
4697
5033
  readonly hasActionsColumn: _angular_core.InputSignalWithTransform<boolean, unknown>;
4698
5034
  readonly rowActionsContext: _angular_core.InputSignal<ScreenContext>;
4699
5035
  readonly showViewAction: _angular_core.InputSignalWithTransform<boolean, unknown>;
4700
5036
  readonly showEditAction: _angular_core.InputSignalWithTransform<boolean, unknown>;
4701
5037
  readonly showDuplicateAction: _angular_core.InputSignalWithTransform<boolean, unknown>;
4702
5038
  readonly showDeleteAction: _angular_core.InputSignalWithTransform<boolean, unknown>;
5039
+ /**
5040
+ * Per-row visibility of the built-in row actions, e.g. no Delete on a
5041
+ * row that is not a draft:
5042
+ *
5043
+ * ```ts
5044
+ * rowActionState = (action, row) =>
5045
+ * action === 'delete' && row.status !== 'draft' ? 'hidden' : null;
5046
+ * ```
5047
+ *
5048
+ * Applied on top of the `show*Action` flags and the permission filter,
5049
+ * on desktop rows and phone cards alike. A row whose actions are all
5050
+ * hidden gets a disabled trigger. Unset, every row keeps every action.
5051
+ */
5052
+ readonly rowActionState: _angular_core.InputSignal<(action: EfDataCardRowAction, row: TRow) => EfDataCardRowActionState>;
4703
5053
  /** Render the Density control in the head row. */
4704
5054
  readonly showDensityControl: _angular_core.InputSignalWithTransform<boolean, unknown>;
4705
5055
  /** Render the Columns picker in the head row. */
@@ -4739,9 +5089,9 @@ declare class EfDataCardComponent<TRow = any> implements AfterContentInit {
4739
5089
  readonly searchTerm: _angular_core.InputSignal<string>;
4740
5090
  /**
4741
5091
  * Stable key used to remember density and hidden columns for this
4742
- * table. Preferences are a per-viewer convenience, so they live in
4743
- * localStorage and are read defensively -- a private window or
4744
- * cleared site data simply falls back to the defaults.
5092
+ * table, through `EF_PREFERENCE_STORE`. They are read defensively: a
5093
+ * store that throws or holds nothing falls back to the defaults
5094
+ * (`defaultDensity`, and each column's `defaultHidden`).
4745
5095
  */
4746
5096
  readonly tableKey: _angular_core.InputSignal<string>;
4747
5097
  readonly pageChange: _angular_core.OutputEmitterRef<number>;
@@ -4856,8 +5206,17 @@ declare class EfDataCardComponent<TRow = any> implements AfterContentInit {
4856
5206
  readonly fixedLayout: _angular_core.InputSignalWithTransform<boolean, unknown>;
4857
5207
  /** Row density. Applied as a class on `.tbl-wrap`. */
4858
5208
  readonly density: _angular_core.WritableSignal<EfTableDensity>;
4859
- /** Column ids the viewer has hidden. */
4860
- readonly hiddenColumnIds: _angular_core.WritableSignal<readonly string[]>;
5209
+ /**
5210
+ * The viewer's own column choice: from the saved preferences, or from
5211
+ * the Columns picker. `null` until there is one, so the columns'
5212
+ * `defaultHidden` flags decide.
5213
+ */
5214
+ private readonly chosenHiddenColumnIds;
5215
+ /** Columns hidden until the viewer says otherwise. Only ones the
5216
+ * picker lists, so a default can always be undone. */
5217
+ private readonly defaultHiddenColumnIds;
5218
+ /** Column ids hidden right now: the viewer's choice, else the defaults. */
5219
+ readonly hiddenColumnIds: _angular_core.Signal<readonly string[]>;
4861
5220
  readonly densityOptions: ReadonlyArray<{
4862
5221
  value: EfTableDensity;
4863
5222
  labelKey: string;
@@ -4894,6 +5253,7 @@ declare class EfDataCardComponent<TRow = any> implements AfterContentInit {
4894
5253
  setDensity(value: EfTableDensity): void;
4895
5254
  isColumnHidden(id: string): boolean;
4896
5255
  toggleColumn(id: string): void;
5256
+ /** "Show all columns": every column, the `defaultHidden` ones included. */
4897
5257
  resetColumns(): void;
4898
5258
  /** Close an open panel on an outside click or Escape. */
4899
5259
  onDocumentClick(event: MouseEvent): void;
@@ -4960,11 +5320,17 @@ declare class EfDataCardComponent<TRow = any> implements AfterContentInit {
4960
5320
  * only missing half of the interaction.
4961
5321
  */
4962
5322
  onRowKeydown(row: TRow, event: Event): void;
5323
+ /**
5324
+ * Space activates a button as Enter does (APG). Only when the row itself
5325
+ * has focus: Space on a checkbox or button inside the row is theirs.
5326
+ * preventDefault keeps the page (or a dialog body) from scrolling.
5327
+ */
5328
+ onRowSpace(row: TRow, event: Event): void;
4963
5329
  onHeaderClick(col: EfDataCardColumn): void;
4964
5330
  trackByRow: (_: number, row: TRow) => unknown;
4965
5331
  trackByColumn: (_: number, col: EfDataCardColumn) => string;
4966
5332
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfDataCardComponent<any>, never>;
4967
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfDataCardComponent<any>, "ef-data-card", never, { "mobileLayout": { "alias": "mobileLayout"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "trackByField": { "alias": "trackByField"; "required": false; "isSignal": true; }; "pageNumber": { "alias": "pageNumber"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "totalCount": { "alias": "totalCount"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "hidePager": { "alias": "hidePager"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "errorMsg": { "alias": "errorMsg"; "required": false; "isSignal": true; }; "loadingKey": { "alias": "loadingKey"; "required": false; "isSignal": true; }; "rowDoubleClickable": { "alias": "rowDoubleClickable"; "required": false; "isSignal": true; }; "hasActionsColumn": { "alias": "hasActionsColumn"; "required": false; "isSignal": true; }; "rowActionsContext": { "alias": "rowActionsContext"; "required": false; "isSignal": true; }; "showViewAction": { "alias": "showViewAction"; "required": false; "isSignal": true; }; "showEditAction": { "alias": "showEditAction"; "required": false; "isSignal": true; }; "showDuplicateAction": { "alias": "showDuplicateAction"; "required": false; "isSignal": true; }; "showDeleteAction": { "alias": "showDeleteAction"; "required": false; "isSignal": true; }; "showDensityControl": { "alias": "showDensityControl"; "required": false; "isSignal": true; }; "showColumnPicker": { "alias": "showColumnPicker"; "required": false; "isSignal": true; }; "showExportControl": { "alias": "showExportControl"; "required": false; "isSignal": true; }; "exporting": { "alias": "exporting"; "required": false; "isSignal": true; }; "screenContext": { "alias": "screenContext"; "required": false; "isSignal": true; }; "searchTerm": { "alias": "searchTerm"; "required": false; "isSignal": true; }; "tableKey": { "alias": "tableKey"; "required": false; "isSignal": true; }; "defaultDensity": { "alias": "defaultDensity"; "required": false; "isSignal": true; }; "reorderable": { "alias": "reorderable"; "required": false; "isSignal": true; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; "isSignal": true; }; }, { "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; "sortChange": "sortChange"; "rowDoubleClick": "rowDoubleClick"; "rowAction": "rowAction"; "exportRequest": "exportRequest"; "clearSearch": "clearSearch"; "retry": "retry"; "rowReorder": "rowReorder"; }, ["bodyTemplates", "headerTemplates"], ["[tbl-head-info]", "[tbl-head-actions]", "[empty-state-mobile]", "[empty-state]"], true, never>;
5333
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfDataCardComponent<any>, "ef-data-card", never, { "mobileLayout": { "alias": "mobileLayout"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "trackByField": { "alias": "trackByField"; "required": false; "isSignal": true; }; "pageNumber": { "alias": "pageNumber"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "totalCount": { "alias": "totalCount"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "hidePager": { "alias": "hidePager"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "errorMsg": { "alias": "errorMsg"; "required": false; "isSignal": true; }; "loadingKey": { "alias": "loadingKey"; "required": false; "isSignal": true; }; "rowDoubleClickable": { "alias": "rowDoubleClickable"; "required": false; "isSignal": true; }; "rowPressed": { "alias": "rowPressed"; "required": false; "isSignal": true; }; "rowDisabled": { "alias": "rowDisabled"; "required": false; "isSignal": true; }; "hasActionsColumn": { "alias": "hasActionsColumn"; "required": false; "isSignal": true; }; "rowActionsContext": { "alias": "rowActionsContext"; "required": false; "isSignal": true; }; "showViewAction": { "alias": "showViewAction"; "required": false; "isSignal": true; }; "showEditAction": { "alias": "showEditAction"; "required": false; "isSignal": true; }; "showDuplicateAction": { "alias": "showDuplicateAction"; "required": false; "isSignal": true; }; "showDeleteAction": { "alias": "showDeleteAction"; "required": false; "isSignal": true; }; "rowActionState": { "alias": "rowActionState"; "required": false; "isSignal": true; }; "showDensityControl": { "alias": "showDensityControl"; "required": false; "isSignal": true; }; "showColumnPicker": { "alias": "showColumnPicker"; "required": false; "isSignal": true; }; "showExportControl": { "alias": "showExportControl"; "required": false; "isSignal": true; }; "exporting": { "alias": "exporting"; "required": false; "isSignal": true; }; "screenContext": { "alias": "screenContext"; "required": false; "isSignal": true; }; "searchTerm": { "alias": "searchTerm"; "required": false; "isSignal": true; }; "tableKey": { "alias": "tableKey"; "required": false; "isSignal": true; }; "defaultDensity": { "alias": "defaultDensity"; "required": false; "isSignal": true; }; "reorderable": { "alias": "reorderable"; "required": false; "isSignal": true; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; "isSignal": true; }; }, { "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; "sortChange": "sortChange"; "rowDoubleClick": "rowDoubleClick"; "rowAction": "rowAction"; "exportRequest": "exportRequest"; "clearSearch": "clearSearch"; "retry": "retry"; "rowReorder": "rowReorder"; }, ["bodyTemplates", "headerTemplates"], ["[tbl-head-info]", "[tbl-head-actions]", "[empty-state-mobile]", "[empty-state]"], true, never>;
4968
5334
  }
4969
5335
 
4970
5336
  /**
@@ -5095,6 +5461,7 @@ declare class EfOverflowTooltipDirective {
5095
5461
  declare class EfRowActionsComponent {
5096
5462
  private readonly host;
5097
5463
  private readonly shortcuts;
5464
+ private readonly injector;
5098
5465
  /** Distinguishes this instance's registrations from every other row's:
5099
5466
  * each row on screen constructs its own component, and ids must not
5100
5467
  * collide or one row's disposer (and its `when`) would clobber
@@ -5140,6 +5507,17 @@ declare class EfRowActionsComponent {
5140
5507
  private static openInstance;
5141
5508
  toggle(event: MouseEvent): void;
5142
5509
  runCommand(action: EfRowAction, event: MouseEvent): void;
5510
+ /** Id of the visible reason a disabled item carries. */
5511
+ reasonId(index: number): string;
5512
+ /** ArrowDown on the closed trigger opens the menu (APG menu button). */
5513
+ openFromKeyboard(event: Event): void;
5514
+ /** Arrow Up/Down (wrapping), Home and End move between items; Escape
5515
+ * closes and hands focus back to the trigger. */
5516
+ onMenuKeydown(event: KeyboardEvent): void;
5517
+ private menuButtons;
5518
+ private focusTrigger;
5519
+ /** Once the menu is rendered, focus its first enabled item (or the first). */
5520
+ private focusItem;
5143
5521
  close(): void;
5144
5522
  /** Gap between the trigger and the menu, plus a little breathing room
5145
5523
  * against the viewport edge. Matches `top: calc(100% + 8px)`. */
@@ -5685,12 +6063,16 @@ declare class EfRadioComponent extends AbstractEfFormControl implements ControlV
5685
6063
  * [rows]="4"
5686
6064
  * [(value)]="description"
5687
6065
  * />
6066
+ * <ef-textarea labelKey="store_pages_details_body_fr" mono [rows]="12" [(value)]="bodyFr" />
5688
6067
  * ```
5689
6068
  */
5690
6069
  declare class EfTextareaComponent extends AbstractEfFormControl implements ControlValueAccessor {
5691
6070
  rows: number;
5692
6071
  maxlength?: number;
5693
6072
  autofocus: boolean;
6073
+ /** Monospace face for source text (an HTML body, a JSON snippet), where
6074
+ * tags and indentation must stay readable. */
6075
+ mono: boolean;
5694
6076
  valueChangeEvent: EventEmitter<string>;
5695
6077
  /** Two-way-bindable as `[value]` (one-way) or
5696
6078
  * `[(value)]="signal()"` (set + listen via `valueChangeEvent`). */
@@ -5712,8 +6094,64 @@ declare class EfTextareaComponent extends AbstractEfFormControl implements Contr
5712
6094
  handleInput(value: string): void;
5713
6095
  handleBlur(): void;
5714
6096
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfTextareaComponent, never>;
5715
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfTextareaComponent, "ef-textarea", never, { "rows": { "alias": "rows"; "required": false; }; "maxlength": { "alias": "maxlength"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; "value": { "alias": "value"; "required": false; }; }, { "valueChangeEvent": "valueChangeEvent"; }, never, never, true, never>;
6097
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfTextareaComponent, "ef-textarea", never, { "rows": { "alias": "rows"; "required": false; }; "maxlength": { "alias": "maxlength"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; "mono": { "alias": "mono"; "required": false; }; "value": { "alias": "value"; "required": false; }; }, { "valueChangeEvent": "valueChangeEvent"; }, never, never, true, never>;
5716
6098
  static ngAcceptInputType_autofocus: unknown;
6099
+ static ngAcceptInputType_mono: unknown;
6100
+ }
6101
+
6102
+ type EfFileRejectReason = 'size' | 'type';
6103
+ interface EfFileRejection {
6104
+ file: File;
6105
+ reason: EfFileRejectReason;
6106
+ }
6107
+ /**
6108
+ * A button that opens the native file picker and emits the chosen `File`.
6109
+ * It never talks to the network: the app uploads the emitted file through its
6110
+ * NSwag client (`client.uploadLogo({ data: file, fileName: file.name })`).
6111
+ *
6112
+ * - The button is a real `<button>`, so Enter/Space work and a screen reader
6113
+ * announces it by its label (`labelKey`) or `ariaLabelKey` when icon-only.
6114
+ * The native input is hidden from the tab order and the accessibility tree.
6115
+ * - `accept` is the usual `<input accept>` list (MIME types, `image/*`,
6116
+ * `.ext`); it is checked again on the chosen file, because the picker's
6117
+ * filter can be bypassed.
6118
+ * - `maxSizeBytes` rejects larger files. A rejected file is not emitted;
6119
+ * `(fileRejected)` fires and a live region shows `sizeErrorKey` or
6120
+ * `typeErrorKey` (translated).
6121
+ * - On a coarse pointer (touch phone, tablet, touch laptop) the painted button
6122
+ * is `--hit-touch` (48px), as `ef-pill-group` does.
6123
+ *
6124
+ * ```html
6125
+ * <ef-file-upload labelKey="tenant_logo_upload" accept="image/png,image/jpeg"
6126
+ * [maxSizeBytes]="2097152" sizeErrorKey="file_too_large"
6127
+ * (fileSelected)="upload($event)" />
6128
+ * ```
6129
+ */
6130
+ declare class EfFileUploadComponent {
6131
+ /** Visible label (translation key). Omit for an icon-only button and set `ariaLabelKey`. */
6132
+ readonly labelKey: _angular_core.InputSignal<string>;
6133
+ readonly ariaLabelKey: _angular_core.InputSignal<string>;
6134
+ readonly icon: _angular_core.InputSignal<string>;
6135
+ readonly severity: _angular_core.InputSignal<"primary" | "danger" | "tenant" | "ghost">;
6136
+ /** The `<input accept>` list: MIME types, `type/*` and `.ext`, comma separated. */
6137
+ readonly accept: _angular_core.InputSignal<string>;
6138
+ readonly maxSizeBytes: _angular_core.InputSignal<number>;
6139
+ /** Translation key shown when the file is over `maxSizeBytes`. */
6140
+ readonly sizeErrorKey: _angular_core.InputSignal<string>;
6141
+ /** Translation key shown when the file does not match `accept`. */
6142
+ readonly typeErrorKey: _angular_core.InputSignal<string>;
6143
+ readonly disabled: _angular_core.InputSignal<boolean>;
6144
+ readonly fileSelected: _angular_core.OutputEmitterRef<File>;
6145
+ readonly fileRejected: _angular_core.OutputEmitterRef<EfFileRejection>;
6146
+ private readonly picker;
6147
+ private readonly reason;
6148
+ protected readonly errorKey: _angular_core.Signal<string>;
6149
+ protected open(): void;
6150
+ protected onChange(event: Event): void;
6151
+ private check;
6152
+ private matchesAccept;
6153
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfFileUploadComponent, never>;
6154
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfFileUploadComponent, "ef-file-upload", never, { "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "ariaLabelKey": { "alias": "ariaLabelKey"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "severity": { "alias": "severity"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSizeBytes": { "alias": "maxSizeBytes"; "required": false; "isSignal": true; }; "sizeErrorKey": { "alias": "sizeErrorKey"; "required": false; "isSignal": true; }; "typeErrorKey": { "alias": "typeErrorKey"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "fileSelected": "fileSelected"; "fileRejected": "fileRejected"; }, never, never, true, never>;
5717
6155
  }
5718
6156
 
5719
6157
  interface EfPresetItem<T = string> {
@@ -5797,6 +6235,8 @@ interface EfDatePreset {
5797
6235
  * a `[start, end]` tuple for a range — so `ef-datepicker-advanced` is a
5798
6236
  * drop-in replacement for the legacy `ef-datepicker` in forms. */
5799
6237
  type EfDateFieldValue = Date | [Date, Date] | null;
6238
+ /** What the form receives: a Date shape, or with `valueFormat="local-datetime"` wall-time strings. */
6239
+ type EfDateFieldOutput = EfDateFieldValue | string | [string, string];
5800
6240
  /**
5801
6241
  * Comptoir period selector for smart-bars — a pill trigger ("Date — 30
5802
6242
  * derniers jours v") that opens a popover with a preset list and an
@@ -5891,6 +6331,17 @@ declare class EfDatepickerAdvancedComponent extends AbstractEfFormControl implem
5891
6331
  * start-of-day) and the trigger renders `DD/MM/YYYY HH:mm`.
5892
6332
  */
5893
6333
  readonly showTime: _angular_core.InputSignalWithTransform<boolean, unknown>;
6334
+ /**
6335
+ * Field mode only: the value shape the form sees.
6336
+ * - `'date'` (default): a `Date` (or `[start, end]`).
6337
+ * - `'local-datetime'`: wall time as a `yyyy-MM-ddTHH:mm` string (or a
6338
+ * pair), read and written from the date's local components, never
6339
+ * through UTC. For times that belong to another zone than the
6340
+ * browser's (a shop's opening time, an offer's start), where the
6341
+ * server converts and the browser must not. A string in any other
6342
+ * shape (seconds, `Z`, an offset) is ignored rather than guessed.
6343
+ */
6344
+ readonly valueFormat: _angular_core.InputSignal<"date" | "local-datetime">;
5894
6345
  /** Emitted whenever a preset or applied custom range changes. */
5895
6346
  readonly rangeChange: _angular_core.OutputEmitterRef<EfDateRange>;
5896
6347
  /** Forwarded when the popover opens / closes (mirrors ef-row-actions). */
@@ -5980,8 +6431,8 @@ declare class EfDatepickerAdvancedComponent extends AbstractEfFormControl implem
5980
6431
  get serverErrors(): string[] | null;
5981
6432
  /** Invalid + interacted — drives the red border on the field box. */
5982
6433
  get isInvalid(): boolean;
5983
- writeValue(value: Date | Date[] | string | number | null | undefined): void;
5984
- registerOnChange(fn: (value: EfDateFieldValue) => void): void;
6434
+ writeValue(value: Date | Date[] | string | string[] | number | null | undefined): void;
6435
+ registerOnChange(fn: (value: EfDateFieldOutput) => void): void;
5985
6436
  registerOnTouched(fn: () => void): void;
5986
6437
  setDisabledState(isDisabled: boolean): void;
5987
6438
  protected get hasValue(): boolean;
@@ -5990,6 +6441,13 @@ declare class EfDatepickerAdvancedComponent extends AbstractEfFormControl implem
5990
6441
  * Tolerates `null`, `[start, null]`, strings/epochs, and stray
5991
6442
  * multi-element arrays (keeps the first two). */
5992
6443
  private coerceValue;
6444
+ /** `local-datetime` in: `yyyy-MM-ddTHH:mm` built into a Date from its
6445
+ * parts (local time), never through `Date` string parsing. */
6446
+ private coerceLocal;
6447
+ private parseLocalDateTime;
6448
+ /** `local-datetime` out: the Date's local components, zero-padded. */
6449
+ private formatLocalDateTime;
6450
+ private toOutput;
5993
6451
  private toDate;
5994
6452
  /** Commit a field-mode value: update internal state, notify the form,
5995
6453
  * and close the popover. `single` → `Date`, `range` → `[start, end]`. */
@@ -6076,7 +6534,7 @@ declare class EfDatepickerAdvancedComponent extends AbstractEfFormControl implem
6076
6534
  }) => number;
6077
6535
  trackByDow: (_: number, label: string) => string;
6078
6536
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EfDatepickerAdvancedComponent, never>;
6079
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfDatepickerAdvancedComponent, "ef-datepicker-advanced", never, { "triggerLabelKey": { "alias": "triggerLabelKey"; "required": false; "isSignal": true; }; "triggerLabel": { "alias": "triggerLabel"; "required": false; "isSignal": true; }; "presets": { "alias": "presets"; "required": false; "isSignal": true; }; "activePreset": { "alias": "activePreset"; "required": false; "isSignal": true; }; "start": { "alias": "start"; "required": false; "isSignal": true; }; "end": { "alias": "end"; "required": false; "isSignal": true; }; "allowCustomRange": { "alias": "allowCustomRange"; "required": false; "isSignal": true; }; "allowAllTime": { "alias": "allowAllTime"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "showTime": { "alias": "showTime"; "required": false; "isSignal": true; }; }, { "rangeChange": "rangeChange"; "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
6537
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfDatepickerAdvancedComponent, "ef-datepicker-advanced", never, { "triggerLabelKey": { "alias": "triggerLabelKey"; "required": false; "isSignal": true; }; "triggerLabel": { "alias": "triggerLabel"; "required": false; "isSignal": true; }; "presets": { "alias": "presets"; "required": false; "isSignal": true; }; "activePreset": { "alias": "activePreset"; "required": false; "isSignal": true; }; "start": { "alias": "start"; "required": false; "isSignal": true; }; "end": { "alias": "end"; "required": false; "isSignal": true; }; "allowCustomRange": { "alias": "allowCustomRange"; "required": false; "isSignal": true; }; "allowAllTime": { "alias": "allowAllTime"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "showTime": { "alias": "showTime"; "required": false; "isSignal": true; }; "valueFormat": { "alias": "valueFormat"; "required": false; "isSignal": true; }; }, { "rangeChange": "rangeChange"; "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
6080
6538
  }
6081
6539
 
6082
6540
  /**
@@ -6275,5 +6733,5 @@ declare class EfEmailMetricsCardComponent {
6275
6733
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<EfEmailMetricsCardComponent, "ef-email-metrics-card", never, { "metrics": { "alias": "metrics"; "required": false; "isSignal": true; }; "recentEvents": { "alias": "recentEvents"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, { "rangeChange": "rangeChange"; }, never, never, true, never>;
6276
6734
  }
6277
6735
 
6278
- 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, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfReadOnlyDirective, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, 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 };
6279
- export type { CatalogueProduct, CatalogueProductVariant, CategoryAssignment, CategoryGroup, CategoryItem, DatatableSearchEntity, DocumentConfig, EfAboutServer, EfBadgeColor, EfBadgeConfig, EfBadgeSize, EfBadgeVariant, EfButtonComptoirSeverity, EfButtonPrimeNGSeverity, EfButtonSeverity, EfChangeHistoryEntry, EfChangeHistoryFieldChange, EfChangePasswordSubmit, EfChartSeries, EfChartType, EfDataCardMobileLayout, EfDataCardRowAction, EfDatatableActions, EfDatatableColumn, EfDatatableColumnAlign, EfDatatableColumnType, EfDatatableConfig, EfDateFieldValue, EfDatePreset, EfDialogAction, EfDialogSeverity, EfDialogSize, EfFabPosition, EfKpiDeltaTone, EfKpiSparkline, EfPillItem, EfPresetItem, EfProfileMenuItem, EfRankRow, EfRowAction, EfSkeletonVariant, EfStatsRow, EfStatusColor, EfStatusReferenceItem, EfTabItem, EfTabsModule, EfTabsVariant, EfTimelineItem, EfTimelineState, EfToastRegionPosition, EfTotalsGrand, EfTotalsRow, HighlightSegment, OrderChangeInfo, OrderEntity, OrderLineItem, OrderProductsSummary, OrderSummaryItem, ProductCountGroup, ProductCountGroupLabel, ProductTypeaheadRow, ProductTypeaheadSelection, ProductWithCountGroup, SelectedFilter, ValidationResult };
6736
+ 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 };
6737
+ export type { CatalogueProduct, CatalogueProductVariant, CategoryAssignment, CategoryGroup, CategoryItem, DatatableSearchEntity, DocumentConfig, EfAboutServer, EfBadgeColor, EfBadgeConfig, EfBadgeSize, EfBadgeVariant, EfButtonComptoirSeverity, EfButtonPrimeNGSeverity, EfButtonSeverity, EfChangeHistoryEntry, EfChangeHistoryFieldChange, EfChangePasswordSubmit, EfChartSeries, EfChartType, EfDataCardMobileLayout, EfDataCardRowAction, EfDataCardRowActionState, EfDatatableActions, EfDatatableColumn, EfDatatableColumnAlign, EfDatatableColumnType, EfDatatableConfig, EfDateFieldOutput, EfDateFieldValue, EfDatePreset, EfDialogAction, EfDialogSeverity, EfDialogSize, EfFabPosition, EfFileRejectReason, EfFileRejection, EfIdentityThumbSize, EfKpiDeltaTone, EfKpiSparkline, EfPillItem, EfPresetItem, EfProductPickerItem, EfProductPickerQuery, EfProfileMenuItem, EfRankRow, EfRowAction, EfSkeletonVariant, EfStatsRow, EfStatusColor, EfStatusReferenceItem, EfTabItem, EfTabsModule, EfTabsVariant, EfTimelineItem, EfTimelineState, EfToastRegionPosition, EfTotalsGrand, EfTotalsRow, HighlightSegment, OrderChangeInfo, OrderEntity, OrderLineItem, OrderProductsSummary, OrderSummaryItem, ProductCountGroup, ProductCountGroupLabel, ProductTypeaheadRow, ProductTypeaheadSelection, ProductWithCountGroup, SelectedFilter, ValidationResult };