@elasticias/ui 1.0.53 → 1.0.54

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,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Pipe, input, Component, output, booleanAttribute, Input, Directive, signal, computed, inject, EventEmitter, Injector, forwardRef, Output, ContentChild, HostBinding, ChangeDetectionStrategy, ElementRef, LOCALE_ID, ViewContainerRef, DestroyRef, ChangeDetectorRef, effect, HostListener, ViewChild, isDevMode, model, InjectionToken, afterNextRender, TemplateRef, ContentChildren, viewChild, untracked, contentChild, ViewEncapsulation, linkedSignal, ViewChildren, afterEveryRender, PLATFORM_ID } from '@angular/core';
2
+ import { Pipe, input, Component, output, booleanAttribute, Input, Directive, signal, computed, inject, EventEmitter, Injector, forwardRef, Output, ContentChild, HostBinding, ChangeDetectionStrategy, ElementRef, LOCALE_ID, ViewContainerRef, DestroyRef, ChangeDetectorRef, effect, HostListener, ViewChild, isDevMode, model, InjectionToken, afterNextRender, TemplateRef, ContentChildren, viewChild, untracked, contentChild, ViewEncapsulation, linkedSignal, ViewChildren, afterEveryRender, inputBinding, outputBinding, PLATFORM_ID } from '@angular/core';
3
3
  import { NumberUtils, UuidUtils, PhoneUtils } from '@elasticias/utils';
4
4
  import * as i1 from 'primeng/button';
5
5
  import { ButtonModule } from 'primeng/button';
6
6
  import * as i1$3 from '@angular/common';
7
7
  import { CommonModule, formatDate, CurrencyPipe, DatePipe, DecimalPipe } from '@angular/common';
8
8
  import * as i2$1 from '@angular/forms';
9
- import { NgControl, Validators, FormControl, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
9
+ import { NgControl, Validators, FormControl, FormsModule, NG_VALUE_ACCESSOR, FormGroupDirective, NgForm, NG_VALIDATORS } from '@angular/forms';
10
10
  import * as i1$1 from '@ngx-translate/core';
11
11
  import { TranslateModule, TranslateService } from '@ngx-translate/core';
12
12
  import * as i1$2 from 'primeng/select';
@@ -16,7 +16,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
16
16
  import { ObjectUtils, ZIndexUtils } from 'primeng/utils';
17
17
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
18
18
  import { merge, Subject, tap, switchMap, catchError, EMPTY } from 'rxjs';
19
- import { EfViewportService, EfShortcutService, EF_PREFERENCE_STORE, formatShortcut, efTablePrefsKey, EfThemeConfigService, EF_BUILD_INFO, EfSessionIdleService, EF_SESSION, EfPermissionService, EfActiveModuleService, EF_SIDE_COLLAPSED_KEY, EfToastService } from '@elasticias/core';
19
+ import { EfViewportService, EfShortcutService, EF_PREFERENCE_STORE, formatShortcut, efTablePrefsKey, EfThemeConfigService, EF_BUILD_INFO, EfSessionIdleService, EF_SESSION, EfPermissionService, EfActiveModuleService, EF_SIDE_COLLAPSED_KEY, EfToastService, EF_CONTENT_CULTURES, cleanLocalizedText, EF_DEFAULT_CONTENT_CULTURES } from '@elasticias/core';
20
20
  import * as i1$4 from 'primeng/inputnumber';
21
21
  import { InputNumberModule } from 'primeng/inputnumber';
22
22
  import * as i1$5 from 'primeng/tooltip';
@@ -674,6 +674,24 @@ class EfServerErrorsDirective {
674
674
  return null;
675
675
  return map[name] ?? null;
676
676
  }
677
+ /**
678
+ * Messages for every key that starts with `prefix`, keyed by what follows
679
+ * it: `errorsWithPrefix('title.')` turns `title.ar` into `ar`. For a control
680
+ * that owns several backend keys, such as a text per language. Empty lists
681
+ * are left out.
682
+ */
683
+ errorsWithPrefix(prefix) {
684
+ const out = {};
685
+ const map = this.errors();
686
+ if (!prefix || !map)
687
+ return out;
688
+ for (const [key, messages] of Object.entries(map)) {
689
+ if (key.length > prefix.length && key.startsWith(prefix) && messages?.length) {
690
+ out[key.slice(prefix.length)] = messages;
691
+ }
692
+ }
693
+ return out;
694
+ }
677
695
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfServerErrorsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
678
696
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: EfServerErrorsDirective, isStandalone: true, selector: "[efServerErrors]", inputs: { errors: { classPropertyName: "errors", publicName: "efServerErrors", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
679
697
  }
@@ -701,6 +719,9 @@ function resolveExternalErrors(explicit, scope, name) {
701
719
  }
702
720
 
703
721
  /**
722
+ * Exported for `@elasticias/ui/rich-text` only, not a supported extension
723
+ * point: it may change without a major version.
724
+ *
704
725
  * Shared base for every Comptoir form control (`ef-input-text`,
705
726
  * `ef-textarea`, `ef-select`, `ef-input-number`, `ef-checkbox`, …).
706
727
  *
@@ -9906,6 +9927,7 @@ class EfTabsComponent {
9906
9927
  * `fr.json` / `ar.json`. */
9907
9928
  dirtyLabelKey = 'common.dirty';
9908
9929
  valueChange = new EventEmitter();
9930
+ host = inject(ElementRef);
9909
9931
  tabEls;
9910
9932
  /* ── Content children (optional projected panels) ────────────── */
9911
9933
  panels;
@@ -9967,6 +9989,13 @@ class EfTabsComponent {
9967
9989
  default:
9968
9990
  return;
9969
9991
  }
9992
+ // In a right-to-left tablist the next tab sits to the left, so the
9993
+ // arrows swap (WAI-ARIA tabs pattern); Home and End keep their meaning.
9994
+ if ((event.key === 'ArrowRight' || event.key === 'ArrowLeft') && this.isRtl(event.currentTarget)) {
9995
+ nextPos = event.key === 'ArrowRight'
9996
+ ? enabledPositions[(here - 1 + enabledPositions.length) % enabledPositions.length]
9997
+ : enabledPositions[(here + 1) % enabledPositions.length];
9998
+ }
9970
9999
  event.preventDefault();
9971
10000
  const target = all[nextPos];
9972
10001
  this.select(target);
@@ -9975,11 +10004,19 @@ class EfTabsComponent {
9975
10004
  const el = this.tabEls?.toArray()[nextPos]?.nativeElement;
9976
10005
  el?.focus();
9977
10006
  }
10007
+ isRtl(target) {
10008
+ const el = target instanceof HTMLElement ? target : this.host.nativeElement;
10009
+ if (getComputedStyle(el).direction === 'rtl')
10010
+ return true;
10011
+ // Only reached in jsdom, which does not inherit `direction`; a browser
10012
+ // answers above.
10013
+ return el.closest('[dir]')?.getAttribute('dir') === 'rtl';
10014
+ }
9978
10015
  trackValue(_, item) {
9979
10016
  return item.value;
9980
10017
  }
9981
10018
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9982
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfTabsComponent, isStandalone: true, selector: "ef-tabs", inputs: { items: "items", value: "value", variant: "variant", module: "module", overflow: ["overflow", "overflow", booleanAttribute], hideCounts: ["hideCounts", "hideCounts", booleanAttribute], ariaLabel: "ariaLabel", ariaLabelKey: "ariaLabelKey", dirtyLabelKey: "dirtyLabelKey" }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.ef-tabs--pill": "variant === 'pill'", "class.ef-tabs--underline": "variant === 'underline'", "class.ef-tabs--overflow": "overflow", "attr.data-module": "module ?? null" } }, queries: [{ propertyName: "panels", predicate: EfTabPanelDirective }], viewQueries: [{ propertyName: "tabEls", predicate: ["tabEl"], descendants: true }], ngImport: i0, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabelKey ? (ariaLabelKey | translate) : ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:flex;align-items:stretch;gap:2px;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__list>*{flex:0 0 auto}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:var(--font-mono);font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}@media(pointer:coarse){:host(.ef-tabs--pill) .ef-tabs__tab,:host(.ef-tabs--underline) .ef-tabs__tab{min-height:var(--hit-touch)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10019
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfTabsComponent, isStandalone: true, selector: "ef-tabs", inputs: { items: "items", value: "value", variant: "variant", module: "module", overflow: ["overflow", "overflow", booleanAttribute], hideCounts: ["hideCounts", "hideCounts", booleanAttribute], ariaLabel: "ariaLabel", ariaLabelKey: "ariaLabelKey", dirtyLabelKey: "dirtyLabelKey" }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.ef-tabs--pill": "variant === 'pill'", "class.ef-tabs--underline": "variant === 'underline'", "class.ef-tabs--overflow": "overflow", "attr.data-module": "module ?? null" } }, queries: [{ propertyName: "panels", predicate: EfTabPanelDirective }], viewQueries: [{ propertyName: "tabEls", predicate: ["tabEl"], descendants: true }], ngImport: i0, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabelKey ? (ariaLabelKey | translate) : ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [class.is-danger]=\"item.tone === 'danger'\"\n [attr.id]=\"item.id || null\"\n [attr.aria-controls]=\"item.panelId || null\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.noteKey) {\n <span class=\"ef-tabs__note\">{{ item.noteKey | translate }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:flex;align-items:stretch;gap:2px;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__list>*{flex:0 0 auto}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:var(--font-mono);font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}.ef-tabs__note{font-size:11px;font-weight:500}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}.ef-tabs__tab.is-danger,:host(.ef-tabs--pill) .ef-tabs__tab.is-danger:hover:not(.is-active):not(.is-disabled),:host(.ef-tabs--underline) .ef-tabs__tab.is-danger:hover:not(.is-active):not(.is-disabled){color:var(--st-cancelled-fg)}:host(.ef-tabs--pill) .ef-tabs__tab.is-danger.is-active{color:var(--paper);box-shadow:inset 0 0 0 2px var(--st-cancelled-fg)}:host(.ef-tabs--underline) .ef-tabs__tab.is-danger.is-active{color:var(--st-cancelled-fg);border-bottom-color:var(--st-cancelled-fg)}:host(.ef-tabs--pill) .ef-tabs__tab.is-danger.is-active:focus-visible{box-shadow:inset 0 0 0 2px var(--st-cancelled-fg),0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}@media(pointer:coarse){:host(.ef-tabs--pill) .ef-tabs__tab,:host(.ef-tabs--underline) .ef-tabs__tab{min-height:var(--hit-touch)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9983
10020
  }
9984
10021
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, decorators: [{
9985
10022
  type: Component,
@@ -9988,7 +10025,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9988
10025
  '[class.ef-tabs--underline]': "variant === 'underline'",
9989
10026
  '[class.ef-tabs--overflow]': 'overflow',
9990
10027
  '[attr.data-module]': 'module ?? null',
9991
- }, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabelKey ? (ariaLabelKey | translate) : ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:flex;align-items:stretch;gap:2px;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__list>*{flex:0 0 auto}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:var(--font-mono);font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}@media(pointer:coarse){:host(.ef-tabs--pill) .ef-tabs__tab,:host(.ef-tabs--underline) .ef-tabs__tab{min-height:var(--hit-touch)}}\n"] }]
10028
+ }, template: "<div class=\"ef-tabs__list\" role=\"tablist\" [attr.aria-label]=\"ariaLabelKey ? (ariaLabelKey | translate) : ariaLabel\">\n @for (item of items; track trackValue($index, item); let i = $index) {\n <button\n #tabEl\n type=\"button\"\n class=\"ef-tabs__tab\"\n role=\"tab\"\n [class.is-active]=\"item.value === value\"\n [class.is-disabled]=\"!!item.disabled\"\n [class.is-danger]=\"item.tone === 'danger'\"\n [attr.id]=\"item.id || null\"\n [attr.aria-controls]=\"item.panelId || null\"\n [attr.aria-selected]=\"item.value === value\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [disabled]=\"item.disabled\"\n [tabindex]=\"item.value === value ? 0 : -1\"\n (click)=\"select(item)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n @if (item.icon) {\n <i class=\"ef-tabs__ico\" [class]=\"item.icon\" aria-hidden=\"true\"></i>\n }\n <span class=\"ef-tabs__label\">\n @if (item.labelKey) {\n {{ item.labelKey | translate }}\n } @else {\n {{ item.label }}\n }\n </span>\n @if (!hideCounts && item.count !== undefined && item.count !== null) {\n <span class=\"ef-tabs__count\">{{ item.count }}</span>\n }\n @if (item.noteKey) {\n <span class=\"ef-tabs__note\">{{ item.noteKey | translate }}</span>\n }\n @if (item.dirty) {\n <span class=\"ef-tabs__dot\" [attr.aria-label]=\"dirtyLabelKey | translate\"></span>\n }\n </button>\n }\n</div>\n\n@if (hasPanels()) {\n <div class=\"ef-tabs__panels\" role=\"tabpanel\">\n @if (activePanel(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\"></ng-container>\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0}.ef-tabs__list{position:relative;display:flex;align-items:stretch;gap:2px;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.ef-tabs__list>*{flex:0 0 auto}.ef-tabs__panels{display:block;padding-top:var(--s-5);min-width:0;animation:ef-tabs-panel-in var(--t-base, .26s) var(--ease-out, ease-out)}@keyframes ef-tabs-panel-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.ef-tabs__list::-webkit-scrollbar{display:none}.ef-tabs__tab{appearance:none;background:transparent;border:0;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.005em;color:var(--text-mute);cursor:pointer;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;transition:color var(--t-fast, .14s) var(--ease-out, ease-out),background-color var(--t-fast, .14s) var(--ease-out, ease-out),border-color var(--t-base, .26s) var(--ease-spring, cubic-bezier(.34, 1.56, .64, 1))}.ef-tabs__tab:focus-visible{outline:0}.ef-tabs__tab.is-disabled,.ef-tabs__tab[disabled]{opacity:.4;cursor:not-allowed}.ef-tabs__ico{font-size:13px;opacity:.75;flex-shrink:0}.ef-tabs__tab.is-active .ef-tabs__ico{opacity:1}.ef-tabs__count{font-family:var(--font-mono);font-size:10.5px;font-weight:700;background:var(--paper);border:1px solid var(--rule);color:var(--text-mute);padding:1px 7px;border-radius:var(--r-pill);min-width:20px;text-align:center;line-height:1.5;font-variant-numeric:tabular-nums}.ef-tabs__dot{width:6px;height:6px;border-radius:50%;background:var(--tenant-500);flex-shrink:0}.ef-tabs__note{font-size:11px;font-weight:500}:host(.ef-tabs--pill) .ef-tabs__list{background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-pill);padding:4px;gap:2px}:host(.ef-tabs--pill) .ef-tabs__tab{min-height:var(--hit, 32px);padding:7px 14px;border-radius:var(--r-pill)}:host(.ef-tabs--pill) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text)}:host(.ef-tabs--pill) .ef-tabs__tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active{background:var(--ink-active, #0f1115);color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__count{background:#ffffff2e;border-color:transparent;color:var(--paper)}:host(.ef-tabs--pill) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__list{display:flex;border-bottom:1px solid var(--rule);padding:0;gap:2px}:host(.ef-tabs--underline) .ef-tabs__tab{min-height:42px;padding:11px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:var(--r-sm) var(--r-sm) 0 0;font-size:13.5px}:host(.ef-tabs--underline) .ef-tabs__tab:hover:not(.is-active):not(.is-disabled){color:var(--text);background:var(--paper)}:host(.ef-tabs--underline) .ef-tabs__tab:focus-visible{background:var(--paper-alt);box-shadow:inset 0 0 0 2px var(--tenant-200)}:host(.ef-tabs--underline) .ef-tabs__tab.is-active{color:var(--text);border-bottom-color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 14%,var(--paper-alt));border-color:transparent;color:var(--module, var(--ink-active))}:host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__dot{background:var(--module, var(--tenant-500))}.ef-tabs__tab.is-danger,:host(.ef-tabs--pill) .ef-tabs__tab.is-danger:hover:not(.is-active):not(.is-disabled),:host(.ef-tabs--underline) .ef-tabs__tab.is-danger:hover:not(.is-active):not(.is-disabled){color:var(--st-cancelled-fg)}:host(.ef-tabs--pill) .ef-tabs__tab.is-danger.is-active{color:var(--paper);box-shadow:inset 0 0 0 2px var(--st-cancelled-fg)}:host(.ef-tabs--underline) .ef-tabs__tab.is-danger.is-active{color:var(--st-cancelled-fg);border-bottom-color:var(--st-cancelled-fg)}:host(.ef-tabs--pill) .ef-tabs__tab.is-danger.is-active:focus-visible{box-shadow:inset 0 0 0 2px var(--st-cancelled-fg),0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}:host[data-module=sales]{--module: var(--m-sales)}:host[data-module=purchase]{--module: var(--m-purchase)}:host[data-module=stock]{--module: var(--m-stock)}:host[data-module=pos]{--module: var(--m-pos)}:host[data-module=marketing]{--module: var(--m-marketing)}:host[data-module=store]{--module: var(--m-store)}:host[data-module=finance]{--module: var(--m-finance)}:host[data-module=admin]{--module: var(--m-admin)}:host(.ef-tabs--overflow){position:relative;display:block}:host(.ef-tabs--overflow) .ef-tabs__list{display:flex;width:100%}:host(.ef-tabs--overflow):before,:host(.ef-tabs--overflow):after{content:\"\";position:absolute;top:0;bottom:0;width:36px;pointer-events:none;z-index:2}:host(.ef-tabs--overflow):before{inset-inline-start:0;background:linear-gradient(to right,var(--paper-alt),transparent)}:host(.ef-tabs--overflow):after{inset-inline-end:0;background:linear-gradient(to left,var(--paper-alt),transparent)}[data-theme=dark] :host(.ef-tabs--underline) .ef-tabs__tab.is-active .ef-tabs__count{background:color-mix(in srgb,var(--module, var(--ink-active)) 28%,transparent)}@media(pointer:coarse){:host(.ef-tabs--pill) .ef-tabs__tab,:host(.ef-tabs--underline) .ef-tabs__tab{min-height:var(--hit-touch)}}\n"] }]
9992
10029
  }], propDecorators: { items: [{
9993
10030
  type: Input,
9994
10031
  args: [{ required: true }]
@@ -11644,6 +11681,10 @@ class EfInputTextComponent extends AbstractEfFormControl {
11644
11681
  * follow the value's first strong character. The label and messages keep
11645
11682
  * the page direction. Null or absent leaves the input unchanged. */
11646
11683
  dir = null;
11684
+ /** Language of the typed value (`'ar'` for an Arabic title on a French
11685
+ * screen), applied to the native input only: the label and hint are page
11686
+ * text and keep the page language. Null or absent renders no attribute. */
11687
+ lang = null;
11647
11688
  /** Clearable (showClear / canClear) is inherited from AbstractEfFormControl;
11648
11689
  * we only supply the value predicate + the reset. */
11649
11690
  get hasValue() {
@@ -11755,11 +11796,11 @@ class EfInputTextComponent extends AbstractEfFormControl {
11755
11796
  this.onTouched();
11756
11797
  }
11757
11798
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfInputTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11758
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfInputTextComponent, isStandalone: true, selector: "ef-input-text", inputs: { size: "size", type: "type", maxlength: "maxlength", fluid: ["fluid", "fluid", booleanAttribute], inline: ["inline", "inline", booleanAttribute], autocomplete: "autocomplete", dir: "dir", variant: "variant", prefix: "prefix", suffix: "suffix", value: "value" }, outputs: { valueChangeEvent: "valueChangeEvent" }, host: { listeners: { "focusin": "markFocused($event)" }, properties: { "class.ef-inline": "this.isInline" } }, usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix) {\n <!-- Comptoir variant with prefix/suffix \u2014 wraps the input in\n an .ef-input-group so the addons share the border + focus\n ring with the input. -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native input (+ optional inline clear). -->\n <div class=\"ef-input-wrap\">\n <input\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 v1 styling. -->\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [type]=\"type\"\n pInputText\n [pSize]=\"size\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n fluid\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i9.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
11799
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfInputTextComponent, isStandalone: true, selector: "ef-input-text", inputs: { size: "size", type: "type", maxlength: "maxlength", fluid: ["fluid", "fluid", booleanAttribute], inline: ["inline", "inline", booleanAttribute], autocomplete: "autocomplete", dir: "dir", lang: "lang", variant: "variant", prefix: "prefix", suffix: "suffix", value: "value" }, outputs: { valueChangeEvent: "valueChangeEvent" }, host: { listeners: { "focusin": "markFocused($event)" }, properties: { "attr.lang": "null", "class.ef-inline": "this.isInline" } }, usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix) {\n <!-- Comptoir variant with prefix/suffix \u2014 wraps the input in\n an .ef-input-group so the addons share the border + focus\n ring with the input. -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [attr.lang]=\"lang || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native input (+ optional inline clear). -->\n <div class=\"ef-input-wrap\">\n <input\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [attr.lang]=\"lang || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 v1 styling. -->\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [attr.lang]=\"lang || null\"\n [type]=\"type\"\n pInputText\n [pSize]=\"size\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n fluid\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i9.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
11759
11800
  }
11760
11801
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfInputTextComponent, decorators: [{
11761
11802
  type: Component,
11762
- args: [{ selector: 'ef-input-text', standalone: true, imports: [InputTextModule, FormsModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix) {\n <!-- Comptoir variant with prefix/suffix \u2014 wraps the input in\n an .ef-input-group so the addons share the border + focus\n ring with the input. -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native input (+ optional inline clear). -->\n <div class=\"ef-input-wrap\">\n <input\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 v1 styling. -->\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [type]=\"type\"\n pInputText\n [pSize]=\"size\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n fluid\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"] }]
11803
+ args: [{ selector: 'ef-input-text', standalone: true, imports: [InputTextModule, FormsModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], host: { '[attr.lang]': 'null' }, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n @if (variant === 'comptoir') {\n @if (prefix || suffix) {\n <!-- Comptoir variant with prefix/suffix \u2014 wraps the input in\n an .ef-input-group so the addons share the border + focus\n ring with the input. -->\n <div class=\"ef-input-group\" [class.is-invalid]=\"isInvalid\">\n @if (prefix) {\n <span class=\"ef-input-addon is-prefix\" aria-hidden=\"true\">{{ prefix }}</span>\n }\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [attr.lang]=\"lang || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-group-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (suffix) {\n <span class=\"ef-input-addon is-suffix\" aria-hidden=\"true\">{{ suffix }}</span>\n }\n </div>\n } @else {\n <!-- Comptoir variant \u2014 bare native input (+ optional inline clear). -->\n <div class=\"ef-input-wrap\">\n <input\n class=\"ef-input\"\n [class.has-clear]=\"canClear\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [attr.lang]=\"lang || null\"\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n />\n @if (canClear) {\n <ef-clear-button\n class=\"ef-input-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n }\n } @else {\n <!-- PrimeNG variant (default) \u2014 v1 styling. -->\n <input\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.dir]=\"dir || null\"\n [attr.lang]=\"lang || null\"\n [type]=\"type\"\n pInputText\n [pSize]=\"size\"\n [value]=\"value\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [attr.autocomplete]=\"autocomplete ?? null\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n fluid\n />\n }\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"] }]
11763
11804
  }], ctorParameters: () => [], propDecorators: { size: [{
11764
11805
  type: Input
11765
11806
  }], type: [{
@@ -11776,6 +11817,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
11776
11817
  type: Input
11777
11818
  }], dir: [{
11778
11819
  type: Input
11820
+ }], lang: [{
11821
+ type: Input
11779
11822
  }], variant: [{
11780
11823
  type: Input
11781
11824
  }], prefix: [{
@@ -12349,6 +12392,19 @@ class EfMultiSelectComponent extends AbstractEfFormControl {
12349
12392
  selectionLimit;
12350
12393
  filters;
12351
12394
  inline = false;
12395
+ /**
12396
+ * Rendering variant, as on `ef-select`.
12397
+ * - `'primeng'` (default): PrimeNG's own multiselect skin, unchanged.
12398
+ * - `'comptoir'`: the shared `.es` skin with `.es--comptoir`, the same hook
12399
+ * `ef-select variant="comptoir"` uses: a trigger on
12400
+ * `--hit-field` (`--hit-base` 40px, `--hit-touch` 48px on a
12401
+ * coarse pointer) and no minimum width, so a select and a
12402
+ * multi-select in one row align and both shrink with it.
12403
+ */
12404
+ variant = 'primeng';
12405
+ get effectiveStyleClass() {
12406
+ return this.variant === 'comptoir' ? 'es es--comptoir' : undefined;
12407
+ }
12352
12408
  get isInline() { return this.inline; }
12353
12409
  get isRequired() { return this.required(); }
12354
12410
  selectedValues = [];
@@ -12413,11 +12469,11 @@ class EfMultiSelectComponent extends AbstractEfFormControl {
12413
12469
  this.selectedValues = [];
12414
12470
  }
12415
12471
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12416
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfMultiSelectComponent, isStandalone: true, selector: "ef-multi-select", inputs: { optionLabel: "optionLabel", optionValue: "optionValue", options: "options", filteredOptions: "filteredOptions", maxSelectedLabels: "maxSelectedLabels", display: "display", size: "size", selectionLimit: "selectionLimit", filters: "filters", inline: ["inline", "inline", booleanAttribute] }, outputs: { changeEvent: "changeEvent" }, host: { properties: { "class.ef-inline": "this.isInline", "class.ef-required": "this.isRequired" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <p-multiSelect\n [id]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [options]=\"filteredOptions\"\n [optionLabel]=\"optionLabel\"\n [optionValue]=\"optionValue\"\n [maxSelectedLabels]=\"maxSelectedLabels\"\n [ngModel]=\"selectedValues\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"onInternalModelChange($event)\"\n (onClear)=\"handleClear()\"\n [disabled]=\"isDisabled()\"\n [display]=\"display\"\n [size]=\"size\"\n [selectionLimit]=\"selectionLimit\"\n [showClear]=\"showClear()\"\n fluid\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host:not(.ef-required) ::ng-deep .p-multiselect.ng-invalid.ng-dirty{border-color:var(--p-multiselect-border-color, #d1d5db)}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i2.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
12472
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfMultiSelectComponent, isStandalone: true, selector: "ef-multi-select", inputs: { optionLabel: "optionLabel", optionValue: "optionValue", options: "options", filteredOptions: "filteredOptions", maxSelectedLabels: "maxSelectedLabels", display: "display", size: "size", selectionLimit: "selectionLimit", filters: "filters", inline: ["inline", "inline", booleanAttribute], variant: "variant" }, outputs: { changeEvent: "changeEvent" }, host: { properties: { "class.ef-inline": "this.isInline", "class.ef-required": "this.isRequired" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <p-multiSelect\n [id]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [options]=\"filteredOptions\"\n [optionLabel]=\"optionLabel\"\n [optionValue]=\"optionValue\"\n [maxSelectedLabels]=\"maxSelectedLabels\"\n [ngModel]=\"selectedValues\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"onInternalModelChange($event)\"\n (onClear)=\"handleClear()\"\n [disabled]=\"isDisabled()\"\n [display]=\"display\"\n [size]=\"size\"\n [selectionLimit]=\"selectionLimit\"\n [showClear]=\"showClear()\"\n [styleClass]=\"effectiveStyleClass\"\n fluid\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host:not(.ef-required) ::ng-deep .p-multiselect.ng-invalid.ng-dirty{border-color:var(--p-multiselect-border-color, #d1d5db)}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i2.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
12417
12473
  }
12418
12474
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfMultiSelectComponent, decorators: [{
12419
12475
  type: Component,
12420
- args: [{ selector: 'ef-multi-select', standalone: true, imports: [FormsModule, MultiSelectModule, TranslateModule, EfLabelComponent], template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <p-multiSelect\n [id]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [options]=\"filteredOptions\"\n [optionLabel]=\"optionLabel\"\n [optionValue]=\"optionValue\"\n [maxSelectedLabels]=\"maxSelectedLabels\"\n [ngModel]=\"selectedValues\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"onInternalModelChange($event)\"\n (onClear)=\"handleClear()\"\n [disabled]=\"isDisabled()\"\n [display]=\"display\"\n [size]=\"size\"\n [selectionLimit]=\"selectionLimit\"\n [showClear]=\"showClear()\"\n fluid\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host:not(.ef-required) ::ng-deep .p-multiselect.ng-invalid.ng-dirty{border-color:var(--p-multiselect-border-color, #d1d5db)}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"] }]
12476
+ args: [{ selector: 'ef-multi-select', standalone: true, imports: [FormsModule, MultiSelectModule, TranslateModule, EfLabelComponent], template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <p-multiSelect\n [id]=\"effectiveId\"\n [name]=\"name()\"\n [placeholder]=\"effectivePlaceholder\"\n [options]=\"filteredOptions\"\n [optionLabel]=\"optionLabel\"\n [optionValue]=\"optionValue\"\n [maxSelectedLabels]=\"maxSelectedLabels\"\n [ngModel]=\"selectedValues\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"onInternalModelChange($event)\"\n (onClear)=\"handleClear()\"\n [disabled]=\"isDisabled()\"\n [display]=\"display\"\n [size]=\"size\"\n [selectionLimit]=\"selectionLimit\"\n [showClear]=\"showClear()\"\n [styleClass]=\"effectiveStyleClass\"\n fluid\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}:host{display:block}:host:not(.ef-required) ::ng-deep .p-multiselect.ng-invalid.ng-dirty{border-color:var(--p-multiselect-border-color, #d1d5db)}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"] }]
12421
12477
  }], ctorParameters: () => [], propDecorators: { changeEvent: [{
12422
12478
  type: Output
12423
12479
  }], optionLabel: [{
@@ -12441,6 +12497,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
12441
12497
  }], inline: [{
12442
12498
  type: Input,
12443
12499
  args: [{ transform: booleanAttribute }]
12500
+ }], variant: [{
12501
+ type: Input
12444
12502
  }], isInline: [{
12445
12503
  type: HostBinding,
12446
12504
  args: ['class.ef-inline']
@@ -13330,6 +13388,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
13330
13388
  class EfTextareaComponent extends AbstractEfFormControl {
13331
13389
  rows = 4;
13332
13390
  maxlength;
13391
+ /** Language of the typed value, applied to the native textarea only (the
13392
+ * label and hint keep the page language). Null or absent renders none. */
13393
+ lang = null;
13333
13394
  autofocus = false;
13334
13395
  /** Monospace face for source text (an HTML body, a JSON snippet), where
13335
13396
  * tags and indentation must stay readable. */
@@ -13430,15 +13491,17 @@ class EfTextareaComponent extends AbstractEfFormControl {
13430
13491
  this.onTouched();
13431
13492
  }
13432
13493
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13433
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfTextareaComponent, isStandalone: true, selector: "ef-textarea", inputs: { rows: "rows", maxlength: "maxlength", autofocus: ["autofocus", "autofocus", booleanAttribute], mono: ["mono", "mono", booleanAttribute], value: "value" }, outputs: { valueChangeEvent: "valueChangeEvent" }, host: { listeners: { "focusin": "markFocused($event)" } }, usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <div class=\"ef-textarea-wrap\">\n <textarea\n class=\"ef-textarea\"\n [class.has-clear]=\"canClear\"\n [class.ef-textarea--mono]=\"mono\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [rows]=\"rows\"\n [value]=\"value\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n ></textarea>\n @if (canClear) {\n <ef-clear-button\n class=\"ef-textarea-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}.ef-textarea.ef-textarea--mono{font-family:var(--font-mono);font-size:12.5px;line-height:1.55}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
13494
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfTextareaComponent, isStandalone: true, selector: "ef-textarea", inputs: { rows: "rows", maxlength: "maxlength", lang: "lang", autofocus: ["autofocus", "autofocus", booleanAttribute], mono: ["mono", "mono", booleanAttribute], value: "value" }, outputs: { valueChangeEvent: "valueChangeEvent" }, host: { listeners: { "focusin": "markFocused($event)" }, properties: { "attr.lang": "null" } }, usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <div class=\"ef-textarea-wrap\">\n <textarea\n class=\"ef-textarea\"\n [class.has-clear]=\"canClear\"\n [class.ef-textarea--mono]=\"mono\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.lang]=\"lang || null\"\n [rows]=\"rows\"\n [value]=\"value\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n ></textarea>\n @if (canClear) {\n <ef-clear-button\n class=\"ef-textarea-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}.ef-textarea.ef-textarea--mono{font-family:var(--font-mono);font-size:12.5px;line-height:1.55}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
13434
13495
  }
13435
13496
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTextareaComponent, decorators: [{
13436
13497
  type: Component,
13437
- args: [{ selector: 'ef-textarea', standalone: true, imports: [FormsModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <div class=\"ef-textarea-wrap\">\n <textarea\n class=\"ef-textarea\"\n [class.has-clear]=\"canClear\"\n [class.ef-textarea--mono]=\"mono\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [rows]=\"rows\"\n [value]=\"value\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n ></textarea>\n @if (canClear) {\n <ef-clear-button\n class=\"ef-textarea-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}.ef-textarea.ef-textarea--mono{font-family:var(--font-mono);font-size:12.5px;line-height:1.55}\n"] }]
13498
+ args: [{ selector: 'ef-textarea', standalone: true, imports: [FormsModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], host: { '[attr.lang]': 'null' }, template: "@if (labelKey() || label()) {\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-field-control\">\n <div class=\"ef-textarea-wrap\">\n <textarea\n class=\"ef-textarea\"\n [class.has-clear]=\"canClear\"\n [class.ef-textarea--mono]=\"mono\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [attr.lang]=\"lang || null\"\n [rows]=\"rows\"\n [value]=\"value\"\n [attr.placeholder]=\"effectivePlaceholder || null\"\n [attr.maxlength]=\"maxlength ?? null\"\n [readonly]=\"readonly()\"\n [disabled]=\"isDisabled()\"\n [class.is-invalid]=\"isInvalid\"\n [attr.aria-label]=\"effectiveAriaLabel\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"describedBy(showRequired || !!serverErrors)\"\n (input)=\"handleInput($any($event.target).value)\"\n (blur)=\"handleBlur()\"\n ></textarea>\n @if (canClear) {\n <ef-clear-button\n class=\"ef-textarea-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: [":host([dir]){direction:inherit}:host([dir=rtl]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:rtl}:host([dir=ltr]) .ef-field-control>:not([role=alert]):not(.ef-field-hint){direction:ltr}:host([dir=auto]) .ef-field-control input,:host([dir=auto]) .ef-field-control textarea{unicode-bidi:plaintext}.ef-textarea.ef-textarea--mono{font-family:var(--font-mono);font-size:12.5px;line-height:1.55}\n"] }]
13438
13499
  }], ctorParameters: () => [], propDecorators: { rows: [{
13439
13500
  type: Input
13440
13501
  }], maxlength: [{
13441
13502
  type: Input
13503
+ }], lang: [{
13504
+ type: Input
13442
13505
  }], autofocus: [{
13443
13506
  type: Input,
13444
13507
  args: [{ transform: booleanAttribute }]
@@ -13454,82 +13517,503 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
13454
13517
  args: ['focusin', ['$event']]
13455
13518
  }] } });
13456
13519
 
13520
+ /**
13521
+ * Loads the rich text editor component that `ef-localized-input`'s `rich`
13522
+ * mode renders. The editor lives in the `@elasticias/ui/rich-text` entry
13523
+ * point so an app that never edits rich text neither bundles PrimeNG's Editor
13524
+ * nor needs `quill` installed. Provide it with `provideEfRichText()` from that
13525
+ * entry (the editor code is in the app's main bundle, Quill itself still loads
13526
+ * lazily the first time an editor renders), or with your own loader to defer
13527
+ * the editor code too:
13528
+ *
13529
+ * ```ts
13530
+ * { provide: EF_RICH_TEXT, useValue: () => import('@elasticias/ui/rich-text').then(m => m.EfRichTextComponent) }
13531
+ * ```
13532
+ */
13533
+ const EF_RICH_TEXT = new InjectionToken('EF_RICH_TEXT');
13534
+ /** Length of the text a reader sees in an HTML fragment, tags not counted. Browser only: it parses with `document`. */
13535
+ function plainTextLength(html) {
13536
+ if (!html)
13537
+ return 0;
13538
+ const tpl = document.createElement('template');
13539
+ tpl.innerHTML = html;
13540
+ return (tpl.content.textContent ?? '').length;
13541
+ }
13542
+
13457
13543
  let nextId = 0;
13458
- const EF_SEO_DEFAULT_LOCALES = [
13459
- { code: 'fr', labelKey: 'ef_seo_fields_locale_fr', dir: 'auto' },
13460
- { code: 'ar', labelKey: 'ef_seo_fields_locale_ar', dir: 'rtl' },
13461
- { code: 'en', labelKey: 'ef_seo_fields_locale_en', dir: 'ltr' },
13462
- ];
13544
+ /** Same texts once blanks are dropped. */
13545
+ function sameText(a, b) {
13546
+ const ka = Object.keys(a);
13547
+ return ka.length === Object.keys(b).length && ka.every(k => a[k] === b[k]);
13548
+ }
13463
13549
  /**
13464
- * SEO title and description per language, each with a counter against what a
13465
- * search engine shows (60 / 160), capped at the server's limits (256 / 512),
13466
- * and the shared `ef-seo-preview` of the French result with the entity's own
13467
- * title and description as the fallback the shop uses. Card-less: each screen
13468
- * wraps it in its own `ef-card`.
13550
+ * One owner-written text (a title, a description, an HTML body) in every
13551
+ * content language: a pill tab per culture, the active culture's field below
13552
+ * it, and the whole object out on every edit with blank cultures removed
13553
+ * (ADR-050 section 7).
13554
+ *
13555
+ * - **Cultures** come from `EF_CONTENT_CULTURES` (the ERP provides them from
13556
+ * the tenant profile) unless `cultures` is set. The active tab is local and
13557
+ * starts on the default culture.
13558
+ * - **Tabs** carry the `ef_localized_input_missing` note on every culture with
13559
+ * no text, the error tone on the default culture of a `required` field left
13560
+ * empty once the field is touched, changed or its form submitted, and the
13561
+ * dirty dot on cultures edited since the value arrived. Dirty detection
13562
+ * relies on the parent storing the emitted object by reference (the V2
13563
+ * signal pattern): a parent that clones the value resets the dots on every
13564
+ * edit, though it never loses text.
13565
+ * - **Direction.** Each field gets its culture's `dir` and `lang`. In `html`
13566
+ * mode the source stays `ltr` for every culture (raw HTML in an `rtl`
13567
+ * textarea reads reversed) and the sanitised preview takes the culture's
13568
+ * direction.
13569
+ * - **Rich mode** (`mode="rich"`) edits each culture with `ef-rich-text`, the
13570
+ * shared editor in `@elasticias/ui/rich-text`. The app turns it on once with
13571
+ * `provideEfRichText()` (and installs `quill`); without it the field falls
13572
+ * back to the `html` source and logs why in development, as it does when
13573
+ * the editor's chunk fails to load. With `advisedLength` the counter
13574
+ * counts the text a reader sees; with only `maxLength` it counts the
13575
+ * stored HTML, which is what the server measures. Neither caps typing in
13576
+ * this mode.
13577
+ * - **Read-only cultures** are shown read-only (still focusable, so the hint
13578
+ * is read and the text can be copied) with
13579
+ * `ef_localized_input_read_only_hint` and the projected `[read-only-link]`;
13580
+ * their text still travels in the emitted object. `[efReadOnly]`, `[disabled]`
13581
+ * and a form's `disable()` disable every culture; the tabs stay usable so the
13582
+ * other languages can still be read.
13583
+ * - **Forms.** A ControlValueAccessor (`[formControl]`, `formControlName`,
13584
+ * `[(ngModel)]`) as well as `[value]` + `(valueChangeEvent)`. With
13585
+ * `required`, the form control reports `required` while the default culture
13586
+ * is empty.
13587
+ * - **Server errors** (ADR-010) come from the enclosing `[efServerErrors]`
13588
+ * scope. `<name>.<culture>` errors tint that culture's tab, show under its
13589
+ * field while it is active (the field links itself by name) and, from another
13590
+ * tab, show as a hint naming the languages in error; a new set of errors
13591
+ * moves to the first of them. `<name>` errors, or an explicit `[errors]`,
13592
+ * show under the field on every tab, as do errors for a language the field
13593
+ * does not list (`title.xx`). Messages are translated as keys. Keys: `ef_localized_input_errors_in_other`.
13594
+ * - **Spoken limit.** `counterHintKey` is read with the counter (through
13595
+ * `aria-describedby`) and `counterOverKey` is announced politely when the
13596
+ * text passes the limit; both take `{{max}}`.
13597
+ * - **Names.** Each culture's field is named `<name>.<culture>` (`title.ar`),
13598
+ * the key the backend reports a per-culture error under.
13599
+ *
13600
+ * i18n keys the app's bundles must define (the libs ship no JSON):
13601
+ *
13602
+ * | key | fr | ar | en |
13603
+ * |---|---|---|---|
13604
+ * | `ef_localized_input_culture_fr` | Français | الفرنسية | French |
13605
+ * | `ef_localized_input_culture_ar` | Arabe | العربية | Arabic |
13606
+ * | `ef_localized_input_culture_en` | Anglais | الإنجليزية | English |
13607
+ * | `ef_localized_input_missing` | manquant | غير مكتوب | missing |
13608
+ * | `ef_localized_input_cultures` | Langues du texte | لغات النص | Text languages |
13609
+ * | `ef_localized_input_errors_in_other` | Erreur dans : | خطأ في: | Error in: |
13610
+ * | `ef_localized_input_read_only_hint` | Ce texte se modifie ailleurs. | يُعدَّل هذا النص في مكان آخر. | This text is edited elsewhere. |
13469
13611
  *
13470
- * The French controls carry the backend names (`seoTitle`, `seoDescription`)
13471
- * so `[efServerErrors]` links their errors; the others (`seoTitleAr`, ...) are
13472
- * dictionary entries whose errors are toasted (ADR-010).
13612
+ * ```html
13613
+ * <ef-localized-input name="title" labelKey="product_title" required
13614
+ * [value]="product().title" (valueChangeEvent)="patch({ title: $event })"
13615
+ * [advisedLength]="60" [maxLength]="256" />
13616
+ *
13617
+ * <ef-localized-input name="title" [readOnlyCultures]="['fr']" ...>
13618
+ * <a read-only-link [routerLink]="catalogLink()">{{ 'store_open_catalog' | translate }}</a>
13619
+ * </ef-localized-input>
13620
+ * ```
13621
+ */
13622
+ class EfLocalizedInputComponent {
13623
+ /** The camelCase backend property (`title`); each field is `<name>.<culture>`. */
13624
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
13625
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
13626
+ mode = input('text', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
13627
+ rows = input(4, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
13628
+ /** Hard cap, the server's limit. */
13629
+ maxLength = input(undefined, ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
13630
+ /** What the counter measures against (what a search engine shows, say). */
13631
+ advisedLength = input(undefined, ...(ngDevMode ? [{ debugName: "advisedLength" }] : /* istanbul ignore next */ []));
13632
+ /** The default culture must be filled. */
13633
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
13634
+ /** Cultures shown but not editable here, with the `[read-only-link]` slot. */
13635
+ readOnlyCultures = input([], ...(ngDevMode ? [{ debugName: "readOnlyCultures" }] : /* istanbul ignore next */ []));
13636
+ /** Labels the field and names its tablist; without it the tablist is
13637
+ * named `ef_localized_input_cultures`. */
13638
+ labelKey = input('', ...(ngDevMode ? [{ debugName: "labelKey" }] : /* istanbul ignore next */ []));
13639
+ placeholderKey = input('', ...(ngDevMode ? [{ debugName: "placeholderKey" }] : /* istanbul ignore next */ []));
13640
+ hintKey = input('', ...(ngDevMode ? [{ debugName: "hintKey" }] : /* istanbul ignore next */ []));
13641
+ /** i18n key (with `{{max}}`) read out with the counter, for a limit that is advice rather than a cap. */
13642
+ counterHintKey = input('', ...(ngDevMode ? [{ debugName: "counterHintKey" }] : /* istanbul ignore next */ []));
13643
+ /** i18n key (with `{{max}}`) announced politely when the text goes past the limit. */
13644
+ counterOverKey = input('', ...(ngDevMode ? [{ debugName: "counterOverKey" }] : /* istanbul ignore next */ []));
13645
+ /** Field style in `text` mode only, named like ef-input-text's (`primeng`
13646
+ * or `comptoir`); the textarea, html and rich modes have one style.
13647
+ * `default` is the former name of `primeng`, kept for one release. */
13648
+ variant = input('comptoir', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
13649
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
13650
+ /**
13651
+ * Messages for the whole field. Without it they come from the enclosing
13652
+ * `[efServerErrors]` scope under `name`; an explicit list wins. The
13653
+ * per-language messages (`<name>.<culture>`) always come from the scope.
13654
+ */
13655
+ errors = input(undefined, ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
13656
+ /** Overrides the `EF_CONTENT_CULTURES` list for this field. */
13657
+ cultures = input(undefined, ...(ngDevMode ? [{ debugName: "cultures" }] : /* istanbul ignore next */ []));
13658
+ /** The whole new object, trimmed, with blank cultures removed. */
13659
+ valueChangeEvent = output();
13660
+ contentCultures = inject(EF_CONTENT_CULTURES);
13661
+ readOnlyScope = inject(EfReadOnlyDirective, { optional: true });
13662
+ ngControl = inject(NgControl, { self: true, optional: true });
13663
+ serverErrorsScope = inject(EfServerErrorsDirective, { optional: true });
13664
+ richLoader = inject(EF_RICH_TEXT, { optional: true });
13665
+ richType = signal(null, ...(ngDevMode ? [{ debugName: "richType" }] : /* istanbul ignore next */ []));
13666
+ /** The editor's chunk failed to load (offline, a stale deploy). */
13667
+ richFailed = signal(false, ...(ngDevMode ? [{ debugName: "richFailed" }] : /* istanbul ignore next */ []));
13668
+ richHost = viewChild('richHost', { ...(ngDevMode ? { debugName: "richHost" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
13669
+ /** `rich` without a provided editor renders the html source instead. */
13670
+ effectiveMode = computed(() => this.mode() === 'rich' && (!this.richLoader || this.richFailed()) ? 'html' : this.mode(), ...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
13671
+ parentForm = inject(FormGroupDirective, { optional: true }) ?? inject(NgForm, { optional: true });
13672
+ host = inject(ElementRef);
13673
+ translateService = inject(TranslateService);
13674
+ /** The user typed in, or left, the field. */
13675
+ interacted = signal(false, ...(ngDevMode ? [{ debugName: "interacted" }] : /* istanbul ignore next */ []));
13676
+ /** The form control is touched or dirty, or its form was submitted; read on every check. */
13677
+ formTouched = signal(false, ...(ngDevMode ? [{ debugName: "formTouched" }] : /* istanbul ignore next */ []));
13678
+ /** Errors wait for touch, a change or a submit, like every ef-* control. */
13679
+ showErrors = computed(() => this.interacted() || this.formTouched(), ...(ngDevMode ? [{ debugName: "showErrors" }] : /* istanbul ignore next */ []));
13680
+ uid = `ef-localized-input-${nextId++}`;
13681
+ /** The object this component last emitted; an echo of it is not new data. */
13682
+ lastEmitted = null;
13683
+ /** The text as typed, blanks and spaces kept, so an echo never rewrites a field mid-word. */
13684
+ raw = linkedSignal({ ...(ngDevMode ? { debugName: "raw" } : /* istanbul ignore next */ {}), source: this.value,
13685
+ computation: (v, prev) => {
13686
+ if (prev && (v === this.lastEmitted || sameText(cleanLocalizedText(v), cleanLocalizedText(prev.value)))) {
13687
+ return prev.value;
13688
+ }
13689
+ return { ...(v ?? {}) };
13690
+ } });
13691
+ /** What the dirty dots compare against: the last value that came from outside. */
13692
+ baseline = linkedSignal({ ...(ngDevMode ? { debugName: "baseline" } : /* istanbul ignore next */ {}), source: this.value,
13693
+ computation: (v, prev) => (prev && v === this.lastEmitted ? prev.value : cleanLocalizedText(v)) });
13694
+ cleaned = computed(() => cleanLocalizedText(this.raw()), ...(ngDevMode ? [{ debugName: "cleaned" }] : /* istanbul ignore next */ []));
13695
+ list = computed(() => this.cultures() ?? this.contentCultures().cultures, ...(ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []));
13696
+ defaultCode = computed(() => {
13697
+ const preferred = this.contentCultures().defaultCulture;
13698
+ const list = this.list();
13699
+ return list.some(c => c.code === preferred) ? preferred : (list[0]?.code ?? preferred);
13700
+ }, ...(ngDevMode ? [{ debugName: "defaultCode" }] : /* istanbul ignore next */ []));
13701
+ selected = signal(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
13702
+ active = computed(() => {
13703
+ const list = this.list();
13704
+ const code = this.selected();
13705
+ return list.find(c => c.code === code) ?? list.find(c => c.code === this.defaultCode()) ?? list[0];
13706
+ }, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
13707
+ /** The active culture's code: unlike active(), equal when an equal culture list arrives. */
13708
+ activeCode = computed(() => this.active()?.code, ...(ngDevMode ? [{ debugName: "activeCode" }] : /* istanbul ignore next */ []));
13709
+ /** Server messages for the whole field (`title`). */
13710
+ fieldErrors = computed(() => {
13711
+ const messages = [...(resolveExternalErrors(this.errors(), this.serverErrorsScope, this.name()) ?? []), ...this.unlistedErrors()];
13712
+ return messages.length ? messages : null;
13713
+ }, ...(ngDevMode ? [{ debugName: "fieldErrors" }] : /* istanbul ignore next */ []));
13714
+ /** Every `<name>.<culture>` message the scope holds. */
13715
+ allCultureErrors = computed(() => this.serverErrorsScope?.errorsWithPrefix(`${this.name()}.`) ?? {}, ...(ngDevMode ? [{ debugName: "allCultureErrors" }] : /* istanbul ignore next */ []));
13716
+ /** Server messages per language (`title.ar` gives `ar`), only for listed cultures. */
13717
+ cultureErrors = computed(() => {
13718
+ const all = this.allCultureErrors();
13719
+ const out = {};
13720
+ for (const c of this.list())
13721
+ if (all[c.code])
13722
+ out[c.code] = all[c.code];
13723
+ return out;
13724
+ }, ...(ngDevMode ? [{ debugName: "cultureErrors" }] : /* istanbul ignore next */ []));
13725
+ /** Messages for a language this field does not list (`title.xx`): shown with the whole field, never dropped. */
13726
+ unlistedErrors = computed(() => {
13727
+ const listed = new Set(this.list().map(c => c.code));
13728
+ return Object.entries(this.allCultureErrors()).flatMap(([code, messages]) => (listed.has(code) ? [] : messages));
13729
+ }, ...(ngDevMode ? [{ debugName: "unlistedErrors" }] : /* istanbul ignore next */ []));
13730
+ /** Bumps when texts change, so translated messages follow the language. */
13731
+ textsVersion = signal(0, ...(ngDevMode ? [{ debugName: "textsVersion" }] : /* istanbul ignore next */ []));
13732
+ /** The active language's messages, translated once here for the field below. */
13733
+ activeErrors = computed(() => {
13734
+ this.textsVersion();
13735
+ const messages = this.cultureErrors()[this.active()?.code ?? ''];
13736
+ return messages?.length ? messages.map(m => this.translateService.instant(m)) : null;
13737
+ }, ...(ngDevMode ? [{ debugName: "activeErrors" }] : /* istanbul ignore next */ []));
13738
+ /** Cultures with a server error, in tab order. */
13739
+ culturesInError = computed(() => this.list().filter(c => this.cultureErrors()[c.code]), ...(ngDevMode ? [{ debugName: "culturesInError" }] : /* istanbul ignore next */ []));
13740
+ /** Cultures in error other than the one on show, for the hint under the tabs. */
13741
+ errorsElsewhere = computed(() => this.culturesInError().filter(c => c.code !== this.active()?.code), ...(ngDevMode ? [{ debugName: "errorsElsewhere" }] : /* istanbul ignore next */ []));
13742
+ /** Changes only when the set of errors does, so a tab the user picked stays picked. */
13743
+ errorSignature = computed(() => JSON.stringify(this.cultureErrors()), ...(ngDevMode ? [{ debugName: "errorSignature" }] : /* istanbul ignore next */ []));
13744
+ tabItems = computed(() => {
13745
+ const text = this.cleaned();
13746
+ const base = this.baseline();
13747
+ return this.list().map(c => {
13748
+ const missing = !text[c.code];
13749
+ return {
13750
+ value: c.code,
13751
+ labelKey: c.labelKey,
13752
+ noteKey: missing ? 'ef_localized_input_missing' : undefined,
13753
+ tone: this.cultureErrors()[c.code] ||
13754
+ (missing && this.required() && c.code === this.defaultCode() && this.showErrors())
13755
+ ? 'danger'
13756
+ : undefined,
13757
+ dirty: (text[c.code] ?? '') !== (base[c.code] ?? ''),
13758
+ id: this.tabId(c.code),
13759
+ panelId: this.panelId,
13760
+ };
13761
+ });
13762
+ }, ...(ngDevMode ? [{ debugName: "tabItems" }] : /* istanbul ignore next */ []));
13763
+ cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
13764
+ /** Every culture is disabled: `[disabled]`, a form's `disable()`, or `[efReadOnly]`. */
13765
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled() || (this.readOnlyScope?.readOnly() ?? false), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
13766
+ activeReadOnly = computed(() => {
13767
+ const code = this.active()?.code;
13768
+ return !!code && this.readOnlyCultures().includes(code);
13769
+ }, ...(ngDevMode ? [{ debugName: "activeReadOnly" }] : /* istanbul ignore next */ []));
13770
+ activeText = computed(() => this.raw()[this.active()?.code ?? ''] ?? '', ...(ngDevMode ? [{ debugName: "activeText" }] : /* istanbul ignore next */ []));
13771
+ /**
13772
+ * What the counter counts. Rich text against `advisedLength` counts the
13773
+ * characters a reader sees; against `maxLength` alone it counts the stored
13774
+ * HTML, because that is what the server's limit measures.
13775
+ */
13776
+ activeLength = computed(() => this.effectiveMode() === 'rich' && this.advisedLength() != null
13777
+ ? plainTextLength(this.activeText())
13778
+ : this.activeText().length, ...(ngDevMode ? [{ debugName: "activeLength" }] : /* istanbul ignore next */ []));
13779
+ labelId = `${this.uid}-label`;
13780
+ counterLimit = computed(() => this.advisedLength() ?? this.maxLength(), ...(ngDevMode ? [{ debugName: "counterLimit" }] : /* istanbul ignore next */ []));
13781
+ panelId = `${this.uid}-panel`;
13782
+ counterId = `${this.uid}-counter`;
13783
+ onChange = () => { };
13784
+ touched = () => { };
13785
+ constructor() {
13786
+ if (this.ngControl)
13787
+ this.ngControl.valueAccessor = this;
13788
+ merge(this.translateService.onLangChange, this.translateService.onTranslationChange)
13789
+ .pipe(takeUntilDestroyed())
13790
+ .subscribe(() => this.textsVersion.update(v => v + 1));
13791
+ // The validator reads required and the default culture, which can
13792
+ // change without the value changing (tenant cultures arrive late).
13793
+ effect(() => {
13794
+ this.required();
13795
+ this.defaultCode();
13796
+ untracked(() => this.ngControl?.control?.updateValueAndValidity({ emitEvent: false }));
13797
+ });
13798
+ // A new set of server errors brings the first language in error into view.
13799
+ effect(() => {
13800
+ this.errorSignature();
13801
+ untracked(() => {
13802
+ const first = this.culturesInError()[0];
13803
+ if (first)
13804
+ this.selected.set(first.code);
13805
+ });
13806
+ });
13807
+ effect(() => {
13808
+ if (this.mode() !== 'rich')
13809
+ return;
13810
+ if (!this.richLoader) {
13811
+ if (isDevMode()) {
13812
+ console.error(`ef-localized-input "${untracked(this.name)}": mode="rich" needs provideEfRichText() from @elasticias/ui/rich-text; showing the html source instead.`);
13813
+ }
13814
+ return;
13815
+ }
13816
+ if (untracked(this.richType) || untracked(this.richFailed))
13817
+ return;
13818
+ this.richLoader()
13819
+ .then(t => this.richType.set(t))
13820
+ .catch((err) => {
13821
+ if (isDevMode()) {
13822
+ console.error(`ef-localized-input "${untracked(this.name)}": could not load the rich text editor; showing the html source instead.`, err);
13823
+ }
13824
+ this.richFailed.set(true);
13825
+ });
13826
+ });
13827
+ // One editor per active culture: the @for recreates the host on a
13828
+ // culture switch, and the editor is created in it with live bindings.
13829
+ effect(() => {
13830
+ const vcr = this.richHost();
13831
+ const type = this.richType();
13832
+ const code = this.activeCode();
13833
+ if (!vcr || !type || !code || this.effectiveMode() !== 'rich')
13834
+ return;
13835
+ untracked(() => this.createRich(vcr, type, code));
13836
+ });
13837
+ }
13838
+ createRich(vcr, type, code) {
13839
+ vcr.clear();
13840
+ const culture = () => this.list().find(c => c.code === code);
13841
+ const readOnly = () => this.readOnlyCultures().includes(code);
13842
+ vcr.createComponent(type, {
13843
+ bindings: [
13844
+ inputBinding('value', () => this.raw()[code] ?? ''),
13845
+ inputBinding('name', () => `${this.name()}.${code}`),
13846
+ inputBinding('inputId', () => this.controlId(code)),
13847
+ inputBinding('dir', () => culture()?.dir ?? null),
13848
+ inputBinding('lang', () => code),
13849
+ inputBinding('rows', () => this.rows()),
13850
+ inputBinding('labelledBy', () => (this.labelKey() ? this.labelId : null)),
13851
+ inputBinding('required', () => this.required() && code === this.defaultCode()),
13852
+ inputBinding('errors', () => this.activeErrors()),
13853
+ inputBinding('disabled', () => this.isDisabled()),
13854
+ inputBinding('readonly', readOnly),
13855
+ inputBinding('placeholderKey', () => this.placeholderKey()),
13856
+ inputBinding('hintKey', () => (readOnly() ? 'ef_localized_input_read_only_hint' : this.hintKey())),
13857
+ inputBinding('describedByExtra', () => (this.counterLimit() ? this.counterId : '')),
13858
+ outputBinding('valueChangeEvent', v => this.setText(code, v)),
13859
+ ],
13860
+ });
13861
+ }
13862
+ tabId(code) {
13863
+ return `${this.uid}-tab-${code}`;
13864
+ }
13865
+ controlId(code) {
13866
+ return `${this.uid}-${code}`;
13867
+ }
13868
+ select(code) {
13869
+ this.selected.set(code);
13870
+ }
13871
+ setText(code, text) {
13872
+ this.raw.update(r => ({ ...r, [code]: text }));
13873
+ const out = this.cleaned();
13874
+ this.lastEmitted = out;
13875
+ this.interacted.set(true);
13876
+ this.onChange(out);
13877
+ this.valueChangeEvent.emit(out);
13878
+ }
13879
+ /** Touched when focus leaves the whole field, not when it moves from a tab to the text. */
13880
+ onFocusOut(event) {
13881
+ const next = event.relatedTarget;
13882
+ if (next instanceof Node && this.host.nativeElement.contains(next))
13883
+ return;
13884
+ this.interacted.set(true);
13885
+ this.touched();
13886
+ }
13887
+ ngDoCheck() {
13888
+ // A form's markAllAsTouched() or submit does not reach the accessor,
13889
+ // so the control's state is read each time the parent is checked.
13890
+ const c = this.ngControl;
13891
+ this.formTouched.set(!!(c?.touched || c?.dirty || this.parentForm?.submitted));
13892
+ }
13893
+ /** `{ required: true }` while a required field's default culture has no text. */
13894
+ validateRequired(control) {
13895
+ if (!this.required())
13896
+ return null;
13897
+ return cleanLocalizedText(control.value)[this.defaultCode()] ? null : { required: true };
13898
+ }
13899
+ writeValue(v) {
13900
+ this.raw.set({ ...(v ?? {}) });
13901
+ this.baseline.set(cleanLocalizedText(v));
13902
+ }
13903
+ registerOnChange(fn) {
13904
+ this.onChange = fn;
13905
+ }
13906
+ registerOnTouched(fn) {
13907
+ this.touched = fn;
13908
+ }
13909
+ setDisabledState(disabled) {
13910
+ this.cvaDisabled.set(disabled);
13911
+ }
13912
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfLocalizedInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13913
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfLocalizedInputComponent, isStandalone: true, selector: "ef-localized-input", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, advisedLength: { classPropertyName: "advisedLength", publicName: "advisedLength", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, readOnlyCultures: { classPropertyName: "readOnlyCultures", publicName: "readOnlyCultures", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, hintKey: { classPropertyName: "hintKey", publicName: "hintKey", isSignal: true, isRequired: false, transformFunction: null }, counterHintKey: { classPropertyName: "counterHintKey", publicName: "counterHintKey", isSignal: true, isRequired: false, transformFunction: null }, counterOverKey: { classPropertyName: "counterOverKey", publicName: "counterOverKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, cultures: { classPropertyName: "cultures", publicName: "cultures", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChangeEvent: "valueChangeEvent" }, host: { listeners: { "focusout": "onFocusOut($event)" } }, providers: [
13914
+ {
13915
+ // A form's required validator reads an empty object as filled (it
13916
+ // checks length), so the field validates its default culture
13917
+ // itself. The component is looked up when the form validates, not
13918
+ // here: it injects NgControl, which injects these validators.
13919
+ provide: NG_VALIDATORS,
13920
+ multi: true,
13921
+ useFactory: () => {
13922
+ const injector = inject(Injector);
13923
+ return (control) => injector.get(EfLocalizedInputComponent).validateRequired(control);
13924
+ },
13925
+ },
13926
+ ], viewQueries: [{ propertyName: "richHost", first: true, predicate: ["richHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<div class=\"eli-head\">\n @if (labelKey()) {\n <span class=\"eli-label\" [id]=\"labelId\">\n <ef-label [labelKey]=\"labelKey()\" [for]=\"controlId(active()?.code ?? '')\" [required]=\"required()\" />\n </span>\n }\n <div class=\"eli-tabs\">\n <ef-tabs\n variant=\"pill\"\n [items]=\"tabItems()\"\n [value]=\"active()?.code ?? null\"\n [ariaLabelKey]=\"labelKey() || 'ef_localized_input_cultures'\"\n (valueChange)=\"select($event)\"\n />\n </div>\n</div>\n\n<div class=\"eli-panel\" role=\"tabpanel\" [id]=\"panelId\" [attr.aria-labelledby]=\"tabId(active()?.code ?? '')\">\n @for (c of active() ? [active()!] : []; track c.code) {\n @switch (effectiveMode()) {\n @case ('text') {\n <ef-input-text\n [variant]=\"variant() === 'comptoir' ? 'comptoir' : 'primeng'\"\n [inputId]=\"controlId(c.code)\"\n [name]=\"name() + '.' + c.code\"\n [dir]=\"c.dir\"\n [lang]=\"c.code\"\n [value]=\"activeText()\"\n [maxlength]=\"maxLength()\"\n [required]=\"required() && c.code === defaultCode()\"\n [errors]=\"activeErrors()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"activeReadOnly()\"\n [placeholderKey]=\"placeholderKey()\"\n [hintKey]=\"activeReadOnly() ? 'ef_localized_input_read_only_hint' : hintKey()\"\n [describedByExtra]=\"counterLimit() ? counterId : ''\"\n (valueChangeEvent)=\"setText(c.code, $event)\"\n />\n }\n @case ('textarea') {\n <ef-textarea\n [inputId]=\"controlId(c.code)\"\n [name]=\"name() + '.' + c.code\"\n [dir]=\"c.dir\"\n [lang]=\"c.code\"\n [rows]=\"rows()\"\n [value]=\"activeText()\"\n [maxlength]=\"maxLength()\"\n [required]=\"required() && c.code === defaultCode()\"\n [errors]=\"activeErrors()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"activeReadOnly()\"\n [placeholderKey]=\"placeholderKey()\"\n [hintKey]=\"activeReadOnly() ? 'ef_localized_input_read_only_hint' : hintKey()\"\n [describedByExtra]=\"counterLimit() ? counterId : ''\"\n (valueChangeEvent)=\"setText(c.code, $event)\"\n />\n }\n @case ('rich') {\n <!-- ef-rich-text, created from EF_RICH_TEXT (see createRich). -->\n <ng-container #richHost />\n }\n @case ('html') {\n <!-- The source stays ltr for every culture: raw HTML in an rtl\n textarea reads reversed (ADR-036). The preview takes the\n culture's direction. -->\n <ef-textarea\n mono\n dir=\"ltr\"\n [inputId]=\"controlId(c.code)\"\n [name]=\"name() + '.' + c.code\"\n [lang]=\"c.code\"\n [rows]=\"rows()\"\n [value]=\"activeText()\"\n [maxlength]=\"maxLength()\"\n [required]=\"required() && c.code === defaultCode()\"\n [errors]=\"activeErrors()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"activeReadOnly()\"\n [placeholderKey]=\"placeholderKey()\"\n [hintKey]=\"activeReadOnly() ? 'ef_localized_input_read_only_hint' : hintKey()\"\n [describedByExtra]=\"counterLimit() ? counterId : ''\"\n (valueChangeEvent)=\"setText(c.code, $event)\"\n />\n }\n }\n }\n\n @if (counterLimit(); as limit) {\n <span class=\"eli-counter\" data-counter [id]=\"counterId\" [class.is-over]=\"activeLength() > limit\">\n <bdi dir=\"ltr\">{{ activeLength() }} / {{ limit }}</bdi>\n @if (counterHintKey()) {\n <span class=\"eli-counter__sr\">{{ counterHintKey() | translate: { max: limit } }}</span>\n }\n </span>\n @if (counterOverKey()) {\n <!-- Always present, so a screen reader hears the text appear. -->\n <span class=\"eli-counter__sr\" data-over-message aria-live=\"polite\">\n @if (activeLength() > limit) {\n {{ counterOverKey() | translate: { max: limit } }}\n }\n </span>\n }\n }\n\n @if (effectiveMode() === 'html' && activeText().trim()) {\n @if (active(); as c) {\n <!-- Angular's default [innerHTML] sanitising strips scripts and handlers. -->\n <div class=\"eli-preview\" data-preview [attr.dir]=\"c.dir\" [attr.lang]=\"c.code\" [innerHTML]=\"activeText()\"></div>\n }\n }\n\n @if (fieldErrors(); as messages) {\n <div class=\"eli-errors\" data-field-errors role=\"alert\" aria-live=\"polite\">\n @for (message of messages; track message) {\n <small class=\"text-danger block\">{{ message | translate }}</small>\n }\n </div>\n }\n\n @if (errorsElsewhere(); as others) {\n @if (others.length) {\n <small class=\"eli-elsewhere text-danger block\" data-errors-elsewhere role=\"status\">\n {{ 'ef_localized_input_errors_in_other' | translate }}\n @for (c of others; track c.code; let last = $last) {\n {{ c.labelKey | translate }}{{ last ? '' : ',' }}\n }\n </small>\n }\n }\n\n @if (activeReadOnly()) {\n <div class=\"eli-link\">\n <ng-content select=\"[read-only-link]\" />\n </div>\n }\n</div>\n", styles: [":host{position:relative;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-2);min-width:0}.eli-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s-2);min-width:0}.eli-label{min-width:0}.eli-tabs{min-width:0;max-width:100%;overflow-x:auto}.eli-panel{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-2);min-width:0}.eli-counter{position:relative;font-size:12px;color:var(--text-mute);text-align:end;font-variant-numeric:tabular-nums}.eli-counter.is-over{color:var(--st-pending-fg);font-weight:600}.eli-counter__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.eli-preview{min-width:0;overflow-x:auto;overflow-wrap:anywhere;padding:var(--s-3);border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);font-size:13.5px;line-height:1.55}.eli-link{font-size:12px}@media(max-width:767px){.eli-tabs{--hit: var(--hit-touch);flex:1 1 100%}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfTabsComponent, selector: "ef-tabs", inputs: ["items", "value", "variant", "module", "overflow", "hideCounts", "ariaLabel", "ariaLabelKey", "dirtyLabelKey"], outputs: ["valueChange"] }, { kind: "component", type: EfInputTextComponent, selector: "ef-input-text", inputs: ["size", "type", "maxlength", "fluid", "inline", "autocomplete", "dir", "lang", "variant", "prefix", "suffix", "value"], outputs: ["valueChangeEvent"] }, { kind: "component", type: EfTextareaComponent, selector: "ef-textarea", inputs: ["rows", "maxlength", "lang", "autofocus", "mono", "value"], outputs: ["valueChangeEvent"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13927
+ }
13928
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfLocalizedInputComponent, decorators: [{
13929
+ type: Component,
13930
+ args: [{ selector: 'ef-localized-input', standalone: true, imports: [TranslateModule, EfLabelComponent, EfTabsComponent, EfInputTextComponent, EfTextareaComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(focusout)': 'onFocusOut($event)' }, providers: [
13931
+ {
13932
+ // A form's required validator reads an empty object as filled (it
13933
+ // checks length), so the field validates its default culture
13934
+ // itself. The component is looked up when the form validates, not
13935
+ // here: it injects NgControl, which injects these validators.
13936
+ provide: NG_VALIDATORS,
13937
+ multi: true,
13938
+ useFactory: () => {
13939
+ const injector = inject(Injector);
13940
+ return (control) => injector.get(EfLocalizedInputComponent).validateRequired(control);
13941
+ },
13942
+ },
13943
+ ], template: "<div class=\"eli-head\">\n @if (labelKey()) {\n <span class=\"eli-label\" [id]=\"labelId\">\n <ef-label [labelKey]=\"labelKey()\" [for]=\"controlId(active()?.code ?? '')\" [required]=\"required()\" />\n </span>\n }\n <div class=\"eli-tabs\">\n <ef-tabs\n variant=\"pill\"\n [items]=\"tabItems()\"\n [value]=\"active()?.code ?? null\"\n [ariaLabelKey]=\"labelKey() || 'ef_localized_input_cultures'\"\n (valueChange)=\"select($event)\"\n />\n </div>\n</div>\n\n<div class=\"eli-panel\" role=\"tabpanel\" [id]=\"panelId\" [attr.aria-labelledby]=\"tabId(active()?.code ?? '')\">\n @for (c of active() ? [active()!] : []; track c.code) {\n @switch (effectiveMode()) {\n @case ('text') {\n <ef-input-text\n [variant]=\"variant() === 'comptoir' ? 'comptoir' : 'primeng'\"\n [inputId]=\"controlId(c.code)\"\n [name]=\"name() + '.' + c.code\"\n [dir]=\"c.dir\"\n [lang]=\"c.code\"\n [value]=\"activeText()\"\n [maxlength]=\"maxLength()\"\n [required]=\"required() && c.code === defaultCode()\"\n [errors]=\"activeErrors()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"activeReadOnly()\"\n [placeholderKey]=\"placeholderKey()\"\n [hintKey]=\"activeReadOnly() ? 'ef_localized_input_read_only_hint' : hintKey()\"\n [describedByExtra]=\"counterLimit() ? counterId : ''\"\n (valueChangeEvent)=\"setText(c.code, $event)\"\n />\n }\n @case ('textarea') {\n <ef-textarea\n [inputId]=\"controlId(c.code)\"\n [name]=\"name() + '.' + c.code\"\n [dir]=\"c.dir\"\n [lang]=\"c.code\"\n [rows]=\"rows()\"\n [value]=\"activeText()\"\n [maxlength]=\"maxLength()\"\n [required]=\"required() && c.code === defaultCode()\"\n [errors]=\"activeErrors()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"activeReadOnly()\"\n [placeholderKey]=\"placeholderKey()\"\n [hintKey]=\"activeReadOnly() ? 'ef_localized_input_read_only_hint' : hintKey()\"\n [describedByExtra]=\"counterLimit() ? counterId : ''\"\n (valueChangeEvent)=\"setText(c.code, $event)\"\n />\n }\n @case ('rich') {\n <!-- ef-rich-text, created from EF_RICH_TEXT (see createRich). -->\n <ng-container #richHost />\n }\n @case ('html') {\n <!-- The source stays ltr for every culture: raw HTML in an rtl\n textarea reads reversed (ADR-036). The preview takes the\n culture's direction. -->\n <ef-textarea\n mono\n dir=\"ltr\"\n [inputId]=\"controlId(c.code)\"\n [name]=\"name() + '.' + c.code\"\n [lang]=\"c.code\"\n [rows]=\"rows()\"\n [value]=\"activeText()\"\n [maxlength]=\"maxLength()\"\n [required]=\"required() && c.code === defaultCode()\"\n [errors]=\"activeErrors()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"activeReadOnly()\"\n [placeholderKey]=\"placeholderKey()\"\n [hintKey]=\"activeReadOnly() ? 'ef_localized_input_read_only_hint' : hintKey()\"\n [describedByExtra]=\"counterLimit() ? counterId : ''\"\n (valueChangeEvent)=\"setText(c.code, $event)\"\n />\n }\n }\n }\n\n @if (counterLimit(); as limit) {\n <span class=\"eli-counter\" data-counter [id]=\"counterId\" [class.is-over]=\"activeLength() > limit\">\n <bdi dir=\"ltr\">{{ activeLength() }} / {{ limit }}</bdi>\n @if (counterHintKey()) {\n <span class=\"eli-counter__sr\">{{ counterHintKey() | translate: { max: limit } }}</span>\n }\n </span>\n @if (counterOverKey()) {\n <!-- Always present, so a screen reader hears the text appear. -->\n <span class=\"eli-counter__sr\" data-over-message aria-live=\"polite\">\n @if (activeLength() > limit) {\n {{ counterOverKey() | translate: { max: limit } }}\n }\n </span>\n }\n }\n\n @if (effectiveMode() === 'html' && activeText().trim()) {\n @if (active(); as c) {\n <!-- Angular's default [innerHTML] sanitising strips scripts and handlers. -->\n <div class=\"eli-preview\" data-preview [attr.dir]=\"c.dir\" [attr.lang]=\"c.code\" [innerHTML]=\"activeText()\"></div>\n }\n }\n\n @if (fieldErrors(); as messages) {\n <div class=\"eli-errors\" data-field-errors role=\"alert\" aria-live=\"polite\">\n @for (message of messages; track message) {\n <small class=\"text-danger block\">{{ message | translate }}</small>\n }\n </div>\n }\n\n @if (errorsElsewhere(); as others) {\n @if (others.length) {\n <small class=\"eli-elsewhere text-danger block\" data-errors-elsewhere role=\"status\">\n {{ 'ef_localized_input_errors_in_other' | translate }}\n @for (c of others; track c.code; let last = $last) {\n {{ c.labelKey | translate }}{{ last ? '' : ',' }}\n }\n </small>\n }\n }\n\n @if (activeReadOnly()) {\n <div class=\"eli-link\">\n <ng-content select=\"[read-only-link]\" />\n </div>\n }\n</div>\n", styles: [":host{position:relative;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-2);min-width:0}.eli-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s-2);min-width:0}.eli-label{min-width:0}.eli-tabs{min-width:0;max-width:100%;overflow-x:auto}.eli-panel{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-2);min-width:0}.eli-counter{position:relative;font-size:12px;color:var(--text-mute);text-align:end;font-variant-numeric:tabular-nums}.eli-counter.is-over{color:var(--st-pending-fg);font-weight:600}.eli-counter__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.eli-preview{min-width:0;overflow-x:auto;overflow-wrap:anywhere;padding:var(--s-3);border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);font-size:13.5px;line-height:1.55}.eli-link{font-size:12px}@media(max-width:767px){.eli-tabs{--hit: var(--hit-touch);flex:1 1 100%}}\n"] }]
13944
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], advisedLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "advisedLength", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], readOnlyCultures: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnlyCultures", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], placeholderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKey", required: false }] }], hintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKey", required: false }] }], counterHintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "counterHintKey", required: false }] }], counterOverKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "counterOverKey", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], cultures: [{ type: i0.Input, args: [{ isSignal: true, alias: "cultures", required: false }] }], valueChangeEvent: [{ type: i0.Output, args: ["valueChangeEvent"] }], richHost: [{ type: i0.ViewChild, args: ['richHost', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
13945
+
13946
+ /**
13947
+ * The content cultures the SEO fields start from, shared with `ef-localized-input`.
13948
+ * @deprecated use `EF_DEFAULT_CONTENT_CULTURES` from `@elasticias/core`.
13949
+ */
13950
+ const EF_SEO_DEFAULT_LOCALES = EF_DEFAULT_CONTENT_CULTURES;
13951
+ /**
13952
+ * SEO title and description in every content language, each an
13953
+ * `ef-localized-input` with a counter against what a search engine shows
13954
+ * (60 / 160, read out to screen readers), capped at the server's limits
13955
+ * (256 / 512), and the shared `ef-seo-preview` of the default language's
13956
+ * result with the entity's own title and description as the fallback the shop
13957
+ * uses. Card-less: each screen wraps it in its own `ef-card`.
13958
+ *
13959
+ * The controls are named `seoTitle` and `seoDescription`, so under
13960
+ * `[efServerErrors]` a `seoTitle.ar` error lands on the Arabic tab and shows
13961
+ * under its field. Languages come from `EF_CONTENT_CULTURES` unless `cultures`
13962
+ * is set.
13963
+ *
13964
+ * i18n keys the app's bundles must define, besides those of
13965
+ * `ef-localized-input`:
13966
+ *
13967
+ * | key | fr | ar | en |
13968
+ * |---|---|---|---|
13969
+ * | `ef_seo_fields_title_label` | Titre SEO | عنوان SEO | SEO title |
13970
+ * | `ef_seo_fields_description_label` | Description SEO | وصف SEO | SEO description |
13971
+ * | `ef_seo_fields_counter_hint` | Au-delà de {{max}} caractères, Google coupe le texte. | بعد {{max}} حرفا، يقتطع Google النص. | Past {{max}} characters, Google cuts the text. |
13972
+ * | `ef_seo_fields_over_hint` | Le texte dépasse {{max}} caractères. | النص يتجاوز {{max}} حرفا. | The text is over {{max}} characters. |
13973
+ * | `ef_seo_fields_preview` | Aperçu dans Google | المعاينة في Google | Google preview |
13974
+ * | `ef_seo_fields_fallback_hint` | Vide : la boutique reprend le titre et la description. | فارغ: يستخدم المتجر العنوان والوصف. | Empty: the shop uses the title and description. |
13473
13975
  *
13474
13976
  * ```html
13475
- * <ef-seo-fields [seoTitle]="p.seoTitle" ... [previewUrl]="url()" (seoChange)="patch($event)" />
13977
+ * <ef-seo-fields [seoTitle]="p.seoTitle" [seoDescription]="p.seoDescription"
13978
+ * [fallbackTitle]="p.title" [fallbackDescription]="p.description"
13979
+ * [previewUrl]="url()" (seoChange)="patch($event)" />
13476
13980
  * ```
13477
13981
  */
13478
13982
  class EfSeoFieldsComponent {
13479
- seoTitle = input('', ...(ngDevMode ? [{ debugName: "seoTitle" }] : /* istanbul ignore next */ []));
13480
- seoDescription = input('', ...(ngDevMode ? [{ debugName: "seoDescription" }] : /* istanbul ignore next */ []));
13481
- seoTitleTranslations = input({}, ...(ngDevMode ? [{ debugName: "seoTitleTranslations" }] : /* istanbul ignore next */ []));
13482
- seoDescriptionTranslations = input({}, ...(ngDevMode ? [{ debugName: "seoDescriptionTranslations" }] : /* istanbul ignore next */ []));
13983
+ seoTitle = input(null, ...(ngDevMode ? [{ debugName: "seoTitle" }] : /* istanbul ignore next */ []));
13984
+ seoDescription = input(null, ...(ngDevMode ? [{ debugName: "seoDescription" }] : /* istanbul ignore next */ []));
13483
13985
  /** The entity's own texts, which the shop uses when SEO is empty. */
13484
- fallbackTitle = input('', ...(ngDevMode ? [{ debugName: "fallbackTitle" }] : /* istanbul ignore next */ []));
13485
- fallbackDescription = input('', ...(ngDevMode ? [{ debugName: "fallbackDescription" }] : /* istanbul ignore next */ []));
13986
+ fallbackTitle = input(null, ...(ngDevMode ? [{ debugName: "fallbackTitle" }] : /* istanbul ignore next */ []));
13987
+ fallbackDescription = input(null, ...(ngDevMode ? [{ debugName: "fallbackDescription" }] : /* istanbul ignore next */ []));
13486
13988
  /** The page address shown in the preview, computed by the app. */
13487
13989
  previewUrl = input('', ...(ngDevMode ? [{ debugName: "previewUrl" }] : /* istanbul ignore next */ []));
13488
13990
  titleMax = input(256, ...(ngDevMode ? [{ debugName: "titleMax" }] : /* istanbul ignore next */ []));
13489
13991
  descriptionMax = input(512, ...(ngDevMode ? [{ debugName: "descriptionMax" }] : /* istanbul ignore next */ []));
13490
13992
  titleAdvised = input(60, ...(ngDevMode ? [{ debugName: "titleAdvised" }] : /* istanbul ignore next */ []));
13491
13993
  descriptionAdvised = input(160, ...(ngDevMode ? [{ debugName: "descriptionAdvised" }] : /* istanbul ignore next */ []));
13492
- locales = input(EF_SEO_DEFAULT_LOCALES, ...(ngDevMode ? [{ debugName: "locales" }] : /* istanbul ignore next */ []));
13994
+ /** Overrides the `EF_CONTENT_CULTURES` list for both fields. */
13995
+ cultures = input(undefined, ...(ngDevMode ? [{ debugName: "cultures" }] : /* istanbul ignore next */ []));
13493
13996
  seoChange = output();
13494
- text(locale, field) {
13495
- if (locale === 'fr')
13496
- return (field === 'title' ? this.seoTitle() : this.seoDescription()) ?? '';
13497
- const dict = field === 'title' ? this.seoTitleTranslations() : this.seoDescriptionTranslations();
13498
- return dict?.[locale] ?? '';
13499
- }
13500
- /** Id of a counter, which the field it counts points at with `aria-describedby`. */
13501
- counterId(locale, field) {
13502
- return `${this.uid}-${this.name(locale, field)}-counter`;
13503
- }
13504
- uid = `ef-seo-fields-${nextId++}`;
13505
- /** French matches the backend property; the others are unique for the label link. */
13506
- name(locale, field) {
13507
- const base = field === 'title' ? 'seoTitle' : 'seoDescription';
13508
- return locale === 'fr' ? base : `${base}${locale.charAt(0).toUpperCase()}${locale.slice(1)}`;
13509
- }
13510
- setText(locale, field, value) {
13511
- if (locale === 'fr') {
13512
- this.seoChange.emit(field === 'title' ? { seoTitle: value } : { seoDescription: value });
13513
- return;
13514
- }
13515
- const key = field === 'title' ? 'seoTitleTranslations' : 'seoDescriptionTranslations';
13516
- const next = { ...((field === 'title' ? this.seoTitleTranslations() : this.seoDescriptionTranslations()) ?? {}) };
13517
- // A cleared field removes the entry, so the shop falls back instead of showing nothing.
13518
- if (value)
13519
- next[locale] = value;
13520
- else
13521
- delete next[locale];
13522
- this.seoChange.emit({ [key]: next });
13997
+ contentCultures = inject(EF_CONTENT_CULTURES);
13998
+ /** The language the preview shows: the tenant's default if listed, else the first listed. */
13999
+ previewCode = computed(() => {
14000
+ const list = this.cultures() ?? this.contentCultures().cultures;
14001
+ const preferred = this.contentCultures().defaultCulture;
14002
+ return list.some(c => c.code === preferred) ? preferred : (list[0]?.code ?? preferred);
14003
+ }, ...(ngDevMode ? [{ debugName: "previewCode" }] : /* istanbul ignore next */ []));
14004
+ previewTitle = computed(() => this.shown(this.seoTitle(), this.fallbackTitle()), ...(ngDevMode ? [{ debugName: "previewTitle" }] : /* istanbul ignore next */ []));
14005
+ previewDescription = computed(() => this.shown(this.seoDescription(), this.fallbackDescription()), ...(ngDevMode ? [{ debugName: "previewDescription" }] : /* istanbul ignore next */ []));
14006
+ shown(seo, fallback) {
14007
+ const code = this.previewCode();
14008
+ return (seo?.[code]?.trim() || fallback?.[code]?.trim() || '');
13523
14009
  }
13524
- previewTitle = computed(() => (this.seoTitle() || this.fallbackTitle() || '').trim(), ...(ngDevMode ? [{ debugName: "previewTitle" }] : /* istanbul ignore next */ []));
13525
- previewDescription = computed(() => (this.seoDescription() || this.fallbackDescription() || '').trim(), ...(ngDevMode ? [{ debugName: "previewDescription" }] : /* istanbul ignore next */ []));
13526
14010
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSeoFieldsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13527
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfSeoFieldsComponent, isStandalone: true, selector: "ef-seo-fields", inputs: { seoTitle: { classPropertyName: "seoTitle", publicName: "seoTitle", isSignal: true, isRequired: false, transformFunction: null }, seoDescription: { classPropertyName: "seoDescription", publicName: "seoDescription", isSignal: true, isRequired: false, transformFunction: null }, seoTitleTranslations: { classPropertyName: "seoTitleTranslations", publicName: "seoTitleTranslations", isSignal: true, isRequired: false, transformFunction: null }, seoDescriptionTranslations: { classPropertyName: "seoDescriptionTranslations", publicName: "seoDescriptionTranslations", isSignal: true, isRequired: false, transformFunction: null }, fallbackTitle: { classPropertyName: "fallbackTitle", publicName: "fallbackTitle", isSignal: true, isRequired: false, transformFunction: null }, fallbackDescription: { classPropertyName: "fallbackDescription", publicName: "fallbackDescription", isSignal: true, isRequired: false, transformFunction: null }, previewUrl: { classPropertyName: "previewUrl", publicName: "previewUrl", isSignal: true, isRequired: false, transformFunction: null }, titleMax: { classPropertyName: "titleMax", publicName: "titleMax", isSignal: true, isRequired: false, transformFunction: null }, descriptionMax: { classPropertyName: "descriptionMax", publicName: "descriptionMax", isSignal: true, isRequired: false, transformFunction: null }, titleAdvised: { classPropertyName: "titleAdvised", publicName: "titleAdvised", isSignal: true, isRequired: false, transformFunction: null }, descriptionAdvised: { classPropertyName: "descriptionAdvised", publicName: "descriptionAdvised", isSignal: true, isRequired: false, transformFunction: null }, locales: { classPropertyName: "locales", publicName: "locales", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { seoChange: "seoChange" }, ngImport: i0, template: "<div class=\"seo-body\">\n <p class=\"seo-note text-mute\">{{ 'ef_seo_fields_fallback_hint' | translate }}</p>\n\n @for (locale of locales(); track locale.code) {\n <ef-fieldset variant=\"plain\" [legendKey]=\"locale.labelKey\" [attr.lang]=\"locale.code\">\n <ef-form-grid>\n <ef-input-text\n class=\"col-12\"\n variant=\"comptoir\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'title')\"\n [describedByExtra]=\"counterId(locale.code, 'title')\"\n labelKey=\"ef_seo_fields_title_label\"\n [maxlength]=\"titleMax()\"\n [value]=\"text(locale.code, 'title')\"\n (valueChangeEvent)=\"setText(locale.code, 'title', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'title')\"\n [attr.data-counter]=\"locale.code + '-title'\"\n [class.is-over]=\"text(locale.code, 'title').length > titleAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'title').length }} / {{ titleAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: titleAdvised() } }}\n @if (text(locale.code, 'title').length > titleAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: titleAdvised() } }}\n }\n </span>\n </span>\n\n <ef-textarea\n class=\"col-12\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'description')\"\n [describedByExtra]=\"counterId(locale.code, 'description')\"\n labelKey=\"ef_seo_fields_description_label\"\n [rows]=\"3\"\n [maxlength]=\"descriptionMax()\"\n [value]=\"text(locale.code, 'description')\"\n (valueChangeEvent)=\"setText(locale.code, 'description', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'description')\"\n [attr.data-counter]=\"locale.code + '-description'\"\n [class.is-over]=\"text(locale.code, 'description').length > descriptionAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'description').length }} / {{ descriptionAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: descriptionAdvised() } }}\n @if (text(locale.code, 'description').length > descriptionAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: descriptionAdvised() } }}\n }\n </span>\n </span>\n </ef-form-grid>\n </ef-fieldset>\n }\n\n <ef-seo-preview\n labelKey=\"ef_seo_fields_preview\"\n contentLang=\"fr\"\n [pageTitle]=\"previewTitle()\"\n [url]=\"previewUrl()\"\n [description]=\"previewDescription()\"\n />\n</div>\n", styles: [":host{position:relative;display:block;min-width:0}.seo-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);min-width:0}.seo-note{margin:0;font-size:12px}.seo-counter{margin-top:calc(-1 * var(--s-2));font-size:12px;color:var(--text-mute);text-align:end;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.seo-counter.is-over{color:var(--st-pending-fg);font-weight:600}.seo-counter__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfFieldsetComponent, selector: "ef-fieldset", inputs: ["legend", "legendKey", "variant", "toggleable", "collapsed", "styleClass"] }, { kind: "component", type: EfFormGridComponent, selector: "ef-form-grid" }, { kind: "component", type: EfInputTextComponent, selector: "ef-input-text", inputs: ["size", "type", "maxlength", "fluid", "inline", "autocomplete", "dir", "variant", "prefix", "suffix", "value"], outputs: ["valueChangeEvent"] }, { kind: "component", type: EfTextareaComponent, selector: "ef-textarea", inputs: ["rows", "maxlength", "autofocus", "mono", "value"], outputs: ["valueChangeEvent"] }, { kind: "component", type: EfSeoPreviewComponent, selector: "ef-seo-preview", inputs: ["pageTitle", "url", "description", "labelKey", "titleMaxLength", "descriptionMaxLength", "contentLang"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14011
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", type: EfSeoFieldsComponent, isStandalone: true, selector: "ef-seo-fields", inputs: { seoTitle: { classPropertyName: "seoTitle", publicName: "seoTitle", isSignal: true, isRequired: false, transformFunction: null }, seoDescription: { classPropertyName: "seoDescription", publicName: "seoDescription", isSignal: true, isRequired: false, transformFunction: null }, fallbackTitle: { classPropertyName: "fallbackTitle", publicName: "fallbackTitle", isSignal: true, isRequired: false, transformFunction: null }, fallbackDescription: { classPropertyName: "fallbackDescription", publicName: "fallbackDescription", isSignal: true, isRequired: false, transformFunction: null }, previewUrl: { classPropertyName: "previewUrl", publicName: "previewUrl", isSignal: true, isRequired: false, transformFunction: null }, titleMax: { classPropertyName: "titleMax", publicName: "titleMax", isSignal: true, isRequired: false, transformFunction: null }, descriptionMax: { classPropertyName: "descriptionMax", publicName: "descriptionMax", isSignal: true, isRequired: false, transformFunction: null }, titleAdvised: { classPropertyName: "titleAdvised", publicName: "titleAdvised", isSignal: true, isRequired: false, transformFunction: null }, descriptionAdvised: { classPropertyName: "descriptionAdvised", publicName: "descriptionAdvised", isSignal: true, isRequired: false, transformFunction: null }, cultures: { classPropertyName: "cultures", publicName: "cultures", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { seoChange: "seoChange" }, ngImport: i0, template: "<div class=\"seo-body\">\n <p class=\"seo-note text-mute\">{{ 'ef_seo_fields_fallback_hint' | translate }}</p>\n\n <ef-localized-input\n name=\"seoTitle\"\n labelKey=\"ef_seo_fields_title_label\"\n [value]=\"seoTitle()\"\n [cultures]=\"cultures()\"\n [maxLength]=\"titleMax()\"\n [advisedLength]=\"titleAdvised()\"\n counterHintKey=\"ef_seo_fields_counter_hint\"\n counterOverKey=\"ef_seo_fields_over_hint\"\n (valueChangeEvent)=\"seoChange.emit({ seoTitle: $event })\"\n />\n\n <ef-localized-input\n name=\"seoDescription\"\n mode=\"textarea\"\n [rows]=\"3\"\n labelKey=\"ef_seo_fields_description_label\"\n [value]=\"seoDescription()\"\n [cultures]=\"cultures()\"\n [maxLength]=\"descriptionMax()\"\n [advisedLength]=\"descriptionAdvised()\"\n counterHintKey=\"ef_seo_fields_counter_hint\"\n counterOverKey=\"ef_seo_fields_over_hint\"\n (valueChangeEvent)=\"seoChange.emit({ seoDescription: $event })\"\n />\n\n <ef-seo-preview\n labelKey=\"ef_seo_fields_preview\"\n [contentLang]=\"previewCode()\"\n [pageTitle]=\"previewTitle()\"\n [url]=\"previewUrl()\"\n [description]=\"previewDescription()\"\n />\n</div>\n", styles: [":host{display:block;min-width:0}.seo-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);min-width:0}.seo-note{margin:0;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLocalizedInputComponent, selector: "ef-localized-input", inputs: ["name", "value", "mode", "rows", "maxLength", "advisedLength", "required", "readOnlyCultures", "labelKey", "placeholderKey", "hintKey", "counterHintKey", "counterOverKey", "variant", "disabled", "errors", "cultures"], outputs: ["valueChangeEvent"] }, { kind: "component", type: EfSeoPreviewComponent, selector: "ef-seo-preview", inputs: ["pageTitle", "url", "description", "labelKey", "titleMaxLength", "descriptionMaxLength", "contentLang"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13528
14012
  }
13529
14013
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSeoFieldsComponent, decorators: [{
13530
14014
  type: Component,
13531
- args: [{ selector: 'ef-seo-fields', standalone: true, imports: [TranslateModule, EfFieldsetComponent, EfFormGridComponent, EfInputTextComponent, EfTextareaComponent, EfSeoPreviewComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"seo-body\">\n <p class=\"seo-note text-mute\">{{ 'ef_seo_fields_fallback_hint' | translate }}</p>\n\n @for (locale of locales(); track locale.code) {\n <ef-fieldset variant=\"plain\" [legendKey]=\"locale.labelKey\" [attr.lang]=\"locale.code\">\n <ef-form-grid>\n <ef-input-text\n class=\"col-12\"\n variant=\"comptoir\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'title')\"\n [describedByExtra]=\"counterId(locale.code, 'title')\"\n labelKey=\"ef_seo_fields_title_label\"\n [maxlength]=\"titleMax()\"\n [value]=\"text(locale.code, 'title')\"\n (valueChangeEvent)=\"setText(locale.code, 'title', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'title')\"\n [attr.data-counter]=\"locale.code + '-title'\"\n [class.is-over]=\"text(locale.code, 'title').length > titleAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'title').length }} / {{ titleAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: titleAdvised() } }}\n @if (text(locale.code, 'title').length > titleAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: titleAdvised() } }}\n }\n </span>\n </span>\n\n <ef-textarea\n class=\"col-12\"\n [dir]=\"locale.dir\"\n [name]=\"name(locale.code, 'description')\"\n [describedByExtra]=\"counterId(locale.code, 'description')\"\n labelKey=\"ef_seo_fields_description_label\"\n [rows]=\"3\"\n [maxlength]=\"descriptionMax()\"\n [value]=\"text(locale.code, 'description')\"\n (valueChangeEvent)=\"setText(locale.code, 'description', $event)\"\n />\n <span\n class=\"col-12 seo-counter\"\n [id]=\"counterId(locale.code, 'description')\"\n [attr.data-counter]=\"locale.code + '-description'\"\n [class.is-over]=\"text(locale.code, 'description').length > descriptionAdvised()\"\n >\n <bdi dir=\"ltr\">{{ text(locale.code, 'description').length }} / {{ descriptionAdvised() }}</bdi>\n <span class=\"seo-counter__sr\">\n {{ 'ef_seo_fields_counter_hint' | translate: { max: descriptionAdvised() } }}\n @if (text(locale.code, 'description').length > descriptionAdvised()) {\n {{ 'ef_seo_fields_over_hint' | translate: { max: descriptionAdvised() } }}\n }\n </span>\n </span>\n </ef-form-grid>\n </ef-fieldset>\n }\n\n <ef-seo-preview\n labelKey=\"ef_seo_fields_preview\"\n contentLang=\"fr\"\n [pageTitle]=\"previewTitle()\"\n [url]=\"previewUrl()\"\n [description]=\"previewDescription()\"\n />\n</div>\n", styles: [":host{position:relative;display:block;min-width:0}.seo-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);min-width:0}.seo-note{margin:0;font-size:12px}.seo-counter{margin-top:calc(-1 * var(--s-2));font-size:12px;color:var(--text-mute);text-align:end;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.seo-counter.is-over{color:var(--st-pending-fg);font-weight:600}.seo-counter__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"] }]
13532
- }], propDecorators: { seoTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoTitle", required: false }] }], seoDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoDescription", required: false }] }], seoTitleTranslations: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoTitleTranslations", required: false }] }], seoDescriptionTranslations: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoDescriptionTranslations", required: false }] }], fallbackTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackTitle", required: false }] }], fallbackDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackDescription", required: false }] }], previewUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewUrl", required: false }] }], titleMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleMax", required: false }] }], descriptionMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionMax", required: false }] }], titleAdvised: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleAdvised", required: false }] }], descriptionAdvised: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionAdvised", required: false }] }], locales: [{ type: i0.Input, args: [{ isSignal: true, alias: "locales", required: false }] }], seoChange: [{ type: i0.Output, args: ["seoChange"] }] } });
14015
+ args: [{ selector: 'ef-seo-fields', standalone: true, imports: [TranslateModule, EfLocalizedInputComponent, EfSeoPreviewComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"seo-body\">\n <p class=\"seo-note text-mute\">{{ 'ef_seo_fields_fallback_hint' | translate }}</p>\n\n <ef-localized-input\n name=\"seoTitle\"\n labelKey=\"ef_seo_fields_title_label\"\n [value]=\"seoTitle()\"\n [cultures]=\"cultures()\"\n [maxLength]=\"titleMax()\"\n [advisedLength]=\"titleAdvised()\"\n counterHintKey=\"ef_seo_fields_counter_hint\"\n counterOverKey=\"ef_seo_fields_over_hint\"\n (valueChangeEvent)=\"seoChange.emit({ seoTitle: $event })\"\n />\n\n <ef-localized-input\n name=\"seoDescription\"\n mode=\"textarea\"\n [rows]=\"3\"\n labelKey=\"ef_seo_fields_description_label\"\n [value]=\"seoDescription()\"\n [cultures]=\"cultures()\"\n [maxLength]=\"descriptionMax()\"\n [advisedLength]=\"descriptionAdvised()\"\n counterHintKey=\"ef_seo_fields_counter_hint\"\n counterOverKey=\"ef_seo_fields_over_hint\"\n (valueChangeEvent)=\"seoChange.emit({ seoDescription: $event })\"\n />\n\n <ef-seo-preview\n labelKey=\"ef_seo_fields_preview\"\n [contentLang]=\"previewCode()\"\n [pageTitle]=\"previewTitle()\"\n [url]=\"previewUrl()\"\n [description]=\"previewDescription()\"\n />\n</div>\n", styles: [":host{display:block;min-width:0}.seo-body{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);min-width:0}.seo-note{margin:0;font-size:12px}\n"] }]
14016
+ }], propDecorators: { seoTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoTitle", required: false }] }], seoDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoDescription", required: false }] }], fallbackTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackTitle", required: false }] }], fallbackDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallbackDescription", required: false }] }], previewUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewUrl", required: false }] }], titleMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleMax", required: false }] }], descriptionMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionMax", required: false }] }], titleAdvised: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleAdvised", required: false }] }], descriptionAdvised: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionAdvised", required: false }] }], cultures: [{ type: i0.Input, args: [{ isSignal: true, alias: "cultures", required: false }] }], seoChange: [{ type: i0.Output, args: ["seoChange"] }] } });
13533
14017
 
13534
14018
  /**
13535
14019
  * A button that opens the native file picker and emits the chosen `File`.
@@ -14439,5 +14923,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
14439
14923
  * Generated bundle index. Do not edit.
14440
14924
  */
14441
14925
 
14442
- 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_HTML_PREVIEW_DEFAULT_TITLE_KEY, EF_SEO_DEFAULT_LOCALES, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfAboutDialogComponent, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChangePasswordComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFileUploadComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfHtmlPreviewComponent, EfIdentityThumbComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductPickerComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfReadOnlyDirective, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfSeoFieldsComponent, EfSeoPreviewComponent, EfServerErrorsDirective, EfSessionIdleDialogComponent, EfShortcutsDialogComponent, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, defaultUnitPrice, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
14926
+ export { AbstractEfFormControl, 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_HTML_PREVIEW_DEFAULT_TITLE_KEY, EF_RICH_TEXT, EF_SEO_DEFAULT_LOCALES, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfAboutDialogComponent, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChangePasswordComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFileUploadComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfHtmlPreviewComponent, EfIdentityThumbComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfLocalizedInputComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductPickerComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfReadOnlyDirective, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfSeoFieldsComponent, EfSeoPreviewComponent, EfServerErrorsDirective, EfSessionIdleDialogComponent, EfShortcutsDialogComponent, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, defaultUnitPrice, getColumnAlignment, mergeColumnDefaults, plainTextLength, resolveEfStatusColor };
14443
14927
  //# sourceMappingURL=elasticias-ui.mjs.map