@elasticias/ui 1.0.53 → 1.0.55
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.
- package/fesm2022/elasticias-ui-rich-text.mjs +336 -0
- package/fesm2022/elasticias-ui-rich-text.mjs.map +1 -0
- package/fesm2022/elasticias-ui.mjs +839 -79
- package/fesm2022/elasticias-ui.mjs.map +1 -1
- package/package.json +12 -2
- package/types/elasticias-ui-rich-text.d.ts +151 -0
- package/types/elasticias-ui.d.ts +484 -52
|
@@ -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
|
*
|
|
@@ -4260,12 +4281,55 @@ const OP_META = {
|
|
|
4260
4281
|
note: { cls: 'op-note' },
|
|
4261
4282
|
};
|
|
4262
4283
|
const DEFAULT_META = { cls: 'op-default' };
|
|
4284
|
+
/** A path whose last (`notes.ar`) or first (`fr.subject`) segment is two
|
|
4285
|
+
* lower-case letters; a culture only when the bundle names it. */
|
|
4286
|
+
const TRAILING_CULTURE = /^(.+)\.([a-z]{2})$/;
|
|
4287
|
+
const LEADING_CULTURE = /^([a-z]{2})\.(.+)$/;
|
|
4288
|
+
/**
|
|
4289
|
+
* The i18n key segment of a backend entity type: PascalCase to snake_case,
|
|
4290
|
+
* an acronym kept as one word. `Product` gives `product`, `EmailTemplate`
|
|
4291
|
+
* gives `email_template`, `POSSession` gives `pos_session`; a segment
|
|
4292
|
+
* already in snake_case is kept.
|
|
4293
|
+
*/
|
|
4294
|
+
function auditEntityKeySegment(entityType) {
|
|
4295
|
+
return entityType
|
|
4296
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
4297
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
4298
|
+
.replace(/[\s.-]+/g, '_')
|
|
4299
|
+
.toLowerCase();
|
|
4300
|
+
}
|
|
4263
4301
|
/**
|
|
4264
4302
|
* Standardized change-history box. Drop it on any detail screen and feed it the
|
|
4265
4303
|
* record's audit entries (from `GET /api/audit/{entityType}/{id}` via the NSwag
|
|
4266
4304
|
* `AuditClient`). Renders a vertical timeline inside an {@link EfCardComponent},
|
|
4267
4305
|
* resolving operation labels through `audit_op_*` i18n keys and field labels
|
|
4268
|
-
* through `audit_field_*` (falling back to the
|
|
4306
|
+
* through `audit_field_*` (falling back to the humanized field path when
|
|
4307
|
+
* unmapped).
|
|
4308
|
+
*
|
|
4309
|
+
* **Field labels.** For a field path (dots become `_` in the key) the box
|
|
4310
|
+
* tries, in order:
|
|
4311
|
+
*
|
|
4312
|
+
* 1. `audit_field_<entity>_<field>`, when `entityType` is set, with the type
|
|
4313
|
+
* converted by {@link auditEntityKeySegment} (`EmailTemplate` gives
|
|
4314
|
+
* `email_template`); the field keeps its camelCase (`htmlBody`), as
|
|
4315
|
+
* `ChangeDiff` records it;
|
|
4316
|
+
* 2. `audit_field_<field>`;
|
|
4317
|
+
* 3. when the last segment (`notes.ar`, as ChangeDiff writes a
|
|
4318
|
+
* LocalizedText) or, for older entries, the first segment (`fr.subject`)
|
|
4319
|
+
* is a known culture: the label of the rest of the path, found by steps 1
|
|
4320
|
+
* and 2 or humanized, then the culture's name in brackets: "Notes
|
|
4321
|
+
* olfactives (Arabe)". A culture is known when the app's bundle has
|
|
4322
|
+
* `ef_localized_input_culture_<xx>`, the key that also names it. The
|
|
4323
|
+
* bundle, not `EF_CONTENT_CULTURES`, decides, because a history outlives
|
|
4324
|
+
* the tenant's current culture list: a language the tenant dropped still
|
|
4325
|
+
* labels its old entries. Any other two-letter segment (`dimensions.cm`)
|
|
4326
|
+
* is part of the path;
|
|
4327
|
+
* 4. the humanized path (`clientCode` gives "Client code").
|
|
4328
|
+
*
|
|
4329
|
+
* So one `audit_field_product_notes` key labels every language of
|
|
4330
|
+
* `Product.Notes`, and an entity can name a field differently from the
|
|
4331
|
+
* global key used by others. On a V2 detail screen bind
|
|
4332
|
+
* `[entityType]="auditEntityType()"`.
|
|
4269
4333
|
*
|
|
4270
4334
|
* ```html
|
|
4271
4335
|
* <ef-change-history [entries]="auditEntries()" />
|
|
@@ -4306,6 +4370,10 @@ class EfChangeHistoryComponent {
|
|
|
4306
4370
|
* or null to keep the raw note. Rendered as plain text (never HTML). A
|
|
4307
4371
|
* screen uses it to translate coded notes such as `order_email_resent:order_confirmed`. */
|
|
4308
4372
|
noteFormatter = input(null, ...(ngDevMode ? [{ debugName: "noteFormatter" }] : /* istanbul ignore next */ []));
|
|
4373
|
+
/** The backend entity type (`ScreenConfig.AUDIT_ENTITY_TYPE`, e.g.
|
|
4374
|
+
* `EmailTemplate`), for the entity's own `audit_field_<entity>_<field>`
|
|
4375
|
+
* labels. Unset uses the global keys only. */
|
|
4376
|
+
entityType = input(undefined, ...(ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []));
|
|
4309
4377
|
/** "Show more" dialog visibility. */
|
|
4310
4378
|
dialogVisible = signal(false, ...(ngDevMode ? [{ debugName: "dialogVisible" }] : /* istanbul ignore next */ []));
|
|
4311
4379
|
/** First `previewLimit` rows shown inline in the card. */
|
|
@@ -4332,17 +4400,50 @@ class EfChangeHistoryComponent {
|
|
|
4332
4400
|
};
|
|
4333
4401
|
});
|
|
4334
4402
|
}, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
4335
|
-
/** Resolve a field
|
|
4336
|
-
* OPTIONAL
|
|
4337
|
-
*
|
|
4338
|
-
*
|
|
4403
|
+
/** Resolve a field path to its label (order in the class docs). Keys are
|
|
4404
|
+
* OPTIONAL: without one the path is humanized (`clientCode` gives
|
|
4405
|
+
* "Client code"), so new fields work with zero i18n maintenance. Add a
|
|
4406
|
+
* key only for a properly localized (fr/ar) term. */
|
|
4339
4407
|
fieldLabel(field) {
|
|
4340
4408
|
if (!field)
|
|
4341
4409
|
return '';
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4410
|
+
const exact = this.lookupField(field);
|
|
4411
|
+
if (exact !== null)
|
|
4412
|
+
return exact;
|
|
4413
|
+
// [base, culture code] for a trailing, then a leading, culture segment.
|
|
4414
|
+
const trailing = TRAILING_CULTURE.exec(field);
|
|
4415
|
+
const leading = LEADING_CULTURE.exec(field);
|
|
4416
|
+
const candidates = [];
|
|
4417
|
+
if (trailing)
|
|
4418
|
+
candidates.push([trailing[1], trailing[2]]);
|
|
4419
|
+
if (leading)
|
|
4420
|
+
candidates.push([leading[2], leading[1]]);
|
|
4421
|
+
for (const [base, code] of candidates) {
|
|
4422
|
+
const name = this.cultureName(code);
|
|
4423
|
+
if (name)
|
|
4424
|
+
return `${this.lookupField(base) ?? this.humanize(base)} (${name})`;
|
|
4425
|
+
}
|
|
4426
|
+
return this.humanize(field);
|
|
4427
|
+
}
|
|
4428
|
+
/** The culture's name from `ef_localized_input_culture_<code>`, or null when the bundle does not know it. */
|
|
4429
|
+
cultureName(code) {
|
|
4430
|
+
const key = `ef_localized_input_culture_${code}`;
|
|
4431
|
+
const name = this.translate.instant(key);
|
|
4432
|
+
return name && name !== key ? name : null;
|
|
4433
|
+
}
|
|
4434
|
+
/** The entity key, then the global key, for a path; null when neither exists. */
|
|
4435
|
+
lookupField(path) {
|
|
4436
|
+
// Dotted paths (address.city, contacts.added) become an underscore
|
|
4437
|
+
// key, since ngx-translate treats dots as nested lookups.
|
|
4438
|
+
const field = path.replace(/\./g, '_');
|
|
4439
|
+
const type = this.entityType();
|
|
4440
|
+
const entity = type ? auditEntityKeySegment(type) : '';
|
|
4441
|
+
for (const key of entity ? [`audit_field_${entity}_${field}`, `audit_field_${field}`] : [`audit_field_${field}`]) {
|
|
4442
|
+
const translated = this.translate.instant(key);
|
|
4443
|
+
if (translated && translated !== key)
|
|
4444
|
+
return translated;
|
|
4445
|
+
}
|
|
4446
|
+
return null;
|
|
4346
4447
|
}
|
|
4347
4448
|
/** The note as shown: the formatter's text, else the raw note. */
|
|
4348
4449
|
noteText(note) {
|
|
@@ -4395,7 +4496,7 @@ class EfChangeHistoryComponent {
|
|
|
4395
4496
|
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
4396
4497
|
}
|
|
4397
4498
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfChangeHistoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4398
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfChangeHistoryComponent, isStandalone: true, selector: "ef-change-history", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, emptyKey: { classPropertyName: "emptyKey", publicName: "emptyKey", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, previewLimit: { classPropertyName: "previewLimit", publicName: "previewLimit", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, startCollapsed: { classPropertyName: "startCollapsed", publicName: "startCollapsed", isSignal: true, isRequired: false, transformFunction: null }, valueLists: { classPropertyName: "valueLists", publicName: "valueLists", isSignal: true, isRequired: false, transformFunction: null }, noteFormatter: { classPropertyName: "noteFormatter", publicName: "noteFormatter", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ef-card\n [titleKey]=\"titleKey()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"startCollapsed()\"\n>\n <ng-container head-extra>\n @if (hasMore()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-window-maximize\"\n tooltipKey=\"audit_view_all\"\n (clickEvent)=\"openDialog($event)\"\n />\n }\n </ng-container>\n\n <ng-container\n *ngTemplateOutlet=\"timeline; context: { $implicit: previewRows(), compact: true }\"\n ></ng-container>\n\n @if (hasMore()) {\n <button type=\"button\" class=\"ch-more\" (click)=\"openDialog()\">\n {{ 'audit_view_all' | translate }} ({{ rows().length }})\n </button>\n }\n</ef-card>\n\n<!-- \"Show more\" dialog \u2014 full timeline, roomy + scrollable -->\n<ef-dialog\n [visible]=\"dialogVisible()\"\n (visibleChange)=\"dialogVisible.set($event)\"\n [headerKey]=\"titleKey()\"\n [defaultActions]=\"false\"\n size=\"lg\"\n [modal]=\"true\"\n position=\"center\"\n>\n <ng-container\n *ngTemplateOutlet=\"timeline; context: { $implicit: rows(), compact: false }\"\n ></ng-container>\n</ef-dialog>\n\n<ng-template #timeline let-items let-compact=\"compact\">\n @if (items.length === 0) {\n <p class=\"text-mute small\">{{ emptyKey() | translate }}</p>\n } @else {\n <div class=\"timeline\" [class.timeline--compact]=\"compact\">\n @for (row of items; track $index) {\n <div class=\"item\">\n <span class=\"dot\" [class]=\"row.meta.cls\" aria-hidden=\"true\"></span>\n <div class=\"body\">\n <div class=\"label\">{{ row.operationLabel }}</div>\n <div class=\"meta\">\n @if (row.entry.userName) {\n <span class=\"strong\">{{ row.entry.userName }}</span> \u00B7\n }\n {{ formatWhen(row.entry.occurredAt) }}\n </div>\n\n @if (row.entry.operation === 'status_changed' && (row.entry.fromStatus || row.entry.toStatus)) {\n <div class=\"ch-status small\">\n <span class=\"text-mute\" dir=\"auto\">{{ valueLabel('status', row.entry.fromStatus) || '\u2014' }}</span>\n <i class=\"pi pi-arrow-right\"></i>\n <span class=\"strong\" dir=\"auto\">{{ valueLabel('status', row.entry.toStatus) || '\u2014' }}</span>\n </div>\n }\n\n @if (row.entry.changes?.length) {\n <ul class=\"ch-changes small\">\n @for (c of row.entry.changes; track c.field) {\n <li>\n <span class=\"ch-field\">{{ fieldLabel(c.field) }}</span>\n <span class=\"text-mute\" dir=\"auto\">{{ valueLabel(c.field, c.oldValue) || '\u2014' }}</span>\n <i class=\"pi pi-arrow-right\"></i>\n <span class=\"strong\" dir=\"auto\">{{ valueLabel(c.field, c.newValue) || '\u2014' }}</span>\n </li>\n }\n </ul>\n }\n\n @if (row.entry.note) {\n <div class=\"ch-note small\">{{ noteText(row.entry.note) }}</div>\n }\n </div>\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: [":host{display:block}.dot.op-created{background:var(--st-delivered-fg, #16a34a);border-color:var(--st-delivered-fg, #16a34a)}.dot.op-updated{background:#2563eb;border-color:#2563eb}.dot.op-status{background:#f59e0b;border-color:#f59e0b}.dot.op-deleted{background:#dc2626;border-color:#dc2626}.dot.op-note{background:#64748b;border-color:#64748b}.dot.op-default{background:var(--ink-300, #94a3b8);border-color:var(--ink-300, #94a3b8)}.ch-status{display:flex;align-items:center;gap:6px;margin-top:4px}.item .body{min-width:0}.ch-changes{list-style:none;padding:0;margin:4px 0 0}.ch-changes li{display:flex;align-items:center;gap:6px;padding:1px 0;min-width:0}.ch-field{font-weight:600;min-width:120px}.timeline--compact .ch-changes .ch-field{flex:0 0 auto}.timeline--compact .ch-changes .text-mute,.timeline--compact .ch-changes .strong{flex:1 1 0;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.timeline:not(.timeline--compact) .ch-changes li{flex-wrap:wrap}.timeline:not(.timeline--compact) .ch-changes .text-mute,.timeline:not(.timeline--compact) .ch-changes .strong{overflow-wrap:anywhere}.ch-changes .pi,.ch-status .pi{font-size:10px;opacity:.6}.ch-note{margin-top:4px;padding-left:8px;border-left:2px solid var(--rule-soft, #e2e8f0);color:var(--ink-600, #475569)}.ch-more{margin-top:8px;border:0;background:transparent;cursor:pointer;padding:2px 0;font-size:12px;font-weight:600;color:var(--module, #2563eb)}.ch-more:hover{text-decoration:underline}.ch-changes .text-mute,.ch-changes .strong,.ch-status .text-mute,.ch-status .strong{text-align:start}:host-context([dir=rtl]) .ch-changes .pi-arrow-right,:host-context([dir=rtl]) .ch-status .pi-arrow-right{transform:scaleX(-1)}@media(max-width:767px){.timeline--compact .ch-changes li{flex-wrap:wrap}.timeline--compact .ch-changes .text-mute,.timeline--compact .ch-changes .strong{flex:1 1 auto;overflow:visible;white-space:normal;text-overflow:clip;overflow-wrap:anywhere}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfDialogComponent, selector: "ef-dialog", inputs: ["header", "headerKey", "subtitle", "subtitleKey", "visible", "modal", "closable", "draggable", "resizable", "dismissableMask", "closeOnEscape", "severity", "icon", "size", "style", "styleClass", "position", "appendTo", "variant", "actions", "defaultActions", "saveLabelKey", "cancelLabelKey", "saveLabel", "cancelLabel", "saveIcon", "cancelIcon", "saveDisabled", "saveStyleClass", "cancelStyleClass", "saveSeverity", "cancelSeverity"], outputs: ["visibleChange", "showEvent", "hideEvent", "saveAction", "cancelAction", "actionClick"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4499
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfChangeHistoryComponent, isStandalone: true, selector: "ef-change-history", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, emptyKey: { classPropertyName: "emptyKey", publicName: "emptyKey", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, previewLimit: { classPropertyName: "previewLimit", publicName: "previewLimit", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, startCollapsed: { classPropertyName: "startCollapsed", publicName: "startCollapsed", isSignal: true, isRequired: false, transformFunction: null }, valueLists: { classPropertyName: "valueLists", publicName: "valueLists", isSignal: true, isRequired: false, transformFunction: null }, noteFormatter: { classPropertyName: "noteFormatter", publicName: "noteFormatter", isSignal: true, isRequired: false, transformFunction: null }, entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ef-card\n [titleKey]=\"titleKey()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"startCollapsed()\"\n>\n <ng-container head-extra>\n @if (hasMore()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-window-maximize\"\n tooltipKey=\"audit_view_all\"\n (clickEvent)=\"openDialog($event)\"\n />\n }\n </ng-container>\n\n <ng-container\n *ngTemplateOutlet=\"timeline; context: { $implicit: previewRows(), compact: true }\"\n ></ng-container>\n\n @if (hasMore()) {\n <button type=\"button\" class=\"ch-more\" (click)=\"openDialog()\">\n {{ 'audit_view_all' | translate }} ({{ rows().length }})\n </button>\n }\n</ef-card>\n\n<!-- \"Show more\" dialog \u2014 full timeline, roomy + scrollable -->\n<ef-dialog\n [visible]=\"dialogVisible()\"\n (visibleChange)=\"dialogVisible.set($event)\"\n [headerKey]=\"titleKey()\"\n [defaultActions]=\"false\"\n size=\"lg\"\n [modal]=\"true\"\n position=\"center\"\n>\n <ng-container\n *ngTemplateOutlet=\"timeline; context: { $implicit: rows(), compact: false }\"\n ></ng-container>\n</ef-dialog>\n\n<ng-template #timeline let-items let-compact=\"compact\">\n @if (items.length === 0) {\n <p class=\"text-mute small\">{{ emptyKey() | translate }}</p>\n } @else {\n <div class=\"timeline\" [class.timeline--compact]=\"compact\">\n @for (row of items; track $index) {\n <div class=\"item\">\n <span class=\"dot\" [class]=\"row.meta.cls\" aria-hidden=\"true\"></span>\n <div class=\"body\">\n <div class=\"label\">{{ row.operationLabel }}</div>\n <div class=\"meta\">\n @if (row.entry.userName) {\n <span class=\"strong\">{{ row.entry.userName }}</span> \u00B7\n }\n {{ formatWhen(row.entry.occurredAt) }}\n </div>\n\n @if (row.entry.operation === 'status_changed' && (row.entry.fromStatus || row.entry.toStatus)) {\n <div class=\"ch-status small\">\n <span class=\"text-mute\" dir=\"auto\">{{ valueLabel('status', row.entry.fromStatus) || '\u2014' }}</span>\n <i class=\"pi pi-arrow-right\"></i>\n <span class=\"strong\" dir=\"auto\">{{ valueLabel('status', row.entry.toStatus) || '\u2014' }}</span>\n </div>\n }\n\n @if (row.entry.changes?.length) {\n <ul class=\"ch-changes small\">\n @for (c of row.entry.changes; track c.field) {\n <li>\n <span class=\"ch-field\">{{ fieldLabel(c.field) }}</span>\n <span class=\"text-mute\" dir=\"auto\">{{ valueLabel(c.field, c.oldValue) || '\u2014' }}</span>\n <i class=\"pi pi-arrow-right\"></i>\n <span class=\"strong\" dir=\"auto\">{{ valueLabel(c.field, c.newValue) || '\u2014' }}</span>\n </li>\n }\n </ul>\n }\n\n @if (row.entry.note) {\n <div class=\"ch-note small\">{{ noteText(row.entry.note) }}</div>\n }\n </div>\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: [":host{display:block}.dot.op-created{background:var(--st-delivered-fg, #16a34a);border-color:var(--st-delivered-fg, #16a34a)}.dot.op-updated{background:#2563eb;border-color:#2563eb}.dot.op-status{background:#f59e0b;border-color:#f59e0b}.dot.op-deleted{background:#dc2626;border-color:#dc2626}.dot.op-note{background:#64748b;border-color:#64748b}.dot.op-default{background:var(--ink-300, #94a3b8);border-color:var(--ink-300, #94a3b8)}.ch-status{display:flex;align-items:center;gap:6px;margin-top:4px}.item .body{min-width:0}.ch-changes{list-style:none;padding:0;margin:4px 0 0}.ch-changes li{display:flex;align-items:center;gap:6px;padding:1px 0;min-width:0}.ch-field{font-weight:600;min-width:120px}.timeline--compact .ch-changes .ch-field{flex:0 0 auto}.timeline--compact .ch-changes .text-mute,.timeline--compact .ch-changes .strong{flex:1 1 0;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.timeline:not(.timeline--compact) .ch-changes li{flex-wrap:wrap}.timeline:not(.timeline--compact) .ch-changes .text-mute,.timeline:not(.timeline--compact) .ch-changes .strong{overflow-wrap:anywhere}.ch-changes .pi,.ch-status .pi{font-size:10px;opacity:.6}.ch-note{margin-top:4px;padding-left:8px;border-left:2px solid var(--rule-soft, #e2e8f0);color:var(--ink-600, #475569)}.ch-more{margin-top:8px;border:0;background:transparent;cursor:pointer;padding:2px 0;font-size:12px;font-weight:600;color:var(--module, #2563eb)}.ch-more:hover{text-decoration:underline}.ch-changes .text-mute,.ch-changes .strong,.ch-status .text-mute,.ch-status .strong{text-align:start}:host-context([dir=rtl]) .ch-changes .pi-arrow-right,:host-context([dir=rtl]) .ch-status .pi-arrow-right{transform:scaleX(-1)}@media(max-width:767px){.timeline--compact .ch-changes li{flex-wrap:wrap}.timeline--compact .ch-changes .text-mute,.timeline--compact .ch-changes .strong{flex:1 1 auto;overflow:visible;white-space:normal;text-overflow:clip;overflow-wrap:anywhere}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfDialogComponent, selector: "ef-dialog", inputs: ["header", "headerKey", "subtitle", "subtitleKey", "visible", "modal", "closable", "draggable", "resizable", "dismissableMask", "closeOnEscape", "severity", "icon", "size", "style", "styleClass", "position", "appendTo", "variant", "actions", "defaultActions", "saveLabelKey", "cancelLabelKey", "saveLabel", "cancelLabel", "saveIcon", "cancelIcon", "saveDisabled", "saveStyleClass", "cancelStyleClass", "saveSeverity", "cancelSeverity"], outputs: ["visibleChange", "showEvent", "hideEvent", "saveAction", "cancelAction", "actionClick"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4399
4500
|
}
|
|
4400
4501
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfChangeHistoryComponent, decorators: [{
|
|
4401
4502
|
type: Component,
|
|
@@ -4406,7 +4507,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
4406
4507
|
EfDialogComponent,
|
|
4407
4508
|
EfButtonComponent,
|
|
4408
4509
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ef-card\n [titleKey]=\"titleKey()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"startCollapsed()\"\n>\n <ng-container head-extra>\n @if (hasMore()) {\n <ef-button\n variant=\"comptoir\"\n severity=\"ghost\"\n size=\"small\"\n icon=\"pi pi-window-maximize\"\n tooltipKey=\"audit_view_all\"\n (clickEvent)=\"openDialog($event)\"\n />\n }\n </ng-container>\n\n <ng-container\n *ngTemplateOutlet=\"timeline; context: { $implicit: previewRows(), compact: true }\"\n ></ng-container>\n\n @if (hasMore()) {\n <button type=\"button\" class=\"ch-more\" (click)=\"openDialog()\">\n {{ 'audit_view_all' | translate }} ({{ rows().length }})\n </button>\n }\n</ef-card>\n\n<!-- \"Show more\" dialog \u2014 full timeline, roomy + scrollable -->\n<ef-dialog\n [visible]=\"dialogVisible()\"\n (visibleChange)=\"dialogVisible.set($event)\"\n [headerKey]=\"titleKey()\"\n [defaultActions]=\"false\"\n size=\"lg\"\n [modal]=\"true\"\n position=\"center\"\n>\n <ng-container\n *ngTemplateOutlet=\"timeline; context: { $implicit: rows(), compact: false }\"\n ></ng-container>\n</ef-dialog>\n\n<ng-template #timeline let-items let-compact=\"compact\">\n @if (items.length === 0) {\n <p class=\"text-mute small\">{{ emptyKey() | translate }}</p>\n } @else {\n <div class=\"timeline\" [class.timeline--compact]=\"compact\">\n @for (row of items; track $index) {\n <div class=\"item\">\n <span class=\"dot\" [class]=\"row.meta.cls\" aria-hidden=\"true\"></span>\n <div class=\"body\">\n <div class=\"label\">{{ row.operationLabel }}</div>\n <div class=\"meta\">\n @if (row.entry.userName) {\n <span class=\"strong\">{{ row.entry.userName }}</span> \u00B7\n }\n {{ formatWhen(row.entry.occurredAt) }}\n </div>\n\n @if (row.entry.operation === 'status_changed' && (row.entry.fromStatus || row.entry.toStatus)) {\n <div class=\"ch-status small\">\n <span class=\"text-mute\" dir=\"auto\">{{ valueLabel('status', row.entry.fromStatus) || '\u2014' }}</span>\n <i class=\"pi pi-arrow-right\"></i>\n <span class=\"strong\" dir=\"auto\">{{ valueLabel('status', row.entry.toStatus) || '\u2014' }}</span>\n </div>\n }\n\n @if (row.entry.changes?.length) {\n <ul class=\"ch-changes small\">\n @for (c of row.entry.changes; track c.field) {\n <li>\n <span class=\"ch-field\">{{ fieldLabel(c.field) }}</span>\n <span class=\"text-mute\" dir=\"auto\">{{ valueLabel(c.field, c.oldValue) || '\u2014' }}</span>\n <i class=\"pi pi-arrow-right\"></i>\n <span class=\"strong\" dir=\"auto\">{{ valueLabel(c.field, c.newValue) || '\u2014' }}</span>\n </li>\n }\n </ul>\n }\n\n @if (row.entry.note) {\n <div class=\"ch-note small\">{{ noteText(row.entry.note) }}</div>\n }\n </div>\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: [":host{display:block}.dot.op-created{background:var(--st-delivered-fg, #16a34a);border-color:var(--st-delivered-fg, #16a34a)}.dot.op-updated{background:#2563eb;border-color:#2563eb}.dot.op-status{background:#f59e0b;border-color:#f59e0b}.dot.op-deleted{background:#dc2626;border-color:#dc2626}.dot.op-note{background:#64748b;border-color:#64748b}.dot.op-default{background:var(--ink-300, #94a3b8);border-color:var(--ink-300, #94a3b8)}.ch-status{display:flex;align-items:center;gap:6px;margin-top:4px}.item .body{min-width:0}.ch-changes{list-style:none;padding:0;margin:4px 0 0}.ch-changes li{display:flex;align-items:center;gap:6px;padding:1px 0;min-width:0}.ch-field{font-weight:600;min-width:120px}.timeline--compact .ch-changes .ch-field{flex:0 0 auto}.timeline--compact .ch-changes .text-mute,.timeline--compact .ch-changes .strong{flex:1 1 0;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.timeline:not(.timeline--compact) .ch-changes li{flex-wrap:wrap}.timeline:not(.timeline--compact) .ch-changes .text-mute,.timeline:not(.timeline--compact) .ch-changes .strong{overflow-wrap:anywhere}.ch-changes .pi,.ch-status .pi{font-size:10px;opacity:.6}.ch-note{margin-top:4px;padding-left:8px;border-left:2px solid var(--rule-soft, #e2e8f0);color:var(--ink-600, #475569)}.ch-more{margin-top:8px;border:0;background:transparent;cursor:pointer;padding:2px 0;font-size:12px;font-weight:600;color:var(--module, #2563eb)}.ch-more:hover{text-decoration:underline}.ch-changes .text-mute,.ch-changes .strong,.ch-status .text-mute,.ch-status .strong{text-align:start}:host-context([dir=rtl]) .ch-changes .pi-arrow-right,:host-context([dir=rtl]) .ch-status .pi-arrow-right{transform:scaleX(-1)}@media(max-width:767px){.timeline--compact .ch-changes li{flex-wrap:wrap}.timeline--compact .ch-changes .text-mute,.timeline--compact .ch-changes .strong{flex:1 1 auto;overflow:visible;white-space:normal;text-overflow:clip;overflow-wrap:anywhere}}\n"] }]
|
|
4409
|
-
}], propDecorators: { entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], emptyKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyKey", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], previewLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewLimit", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], startCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "startCollapsed", required: false }] }], valueLists: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueLists", required: false }] }], noteFormatter: [{ type: i0.Input, args: [{ isSignal: true, alias: "noteFormatter", required: false }] }] } });
|
|
4510
|
+
}], propDecorators: { entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], emptyKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyKey", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], previewLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewLimit", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], startCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "startCollapsed", required: false }] }], valueLists: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueLists", required: false }] }], noteFormatter: [{ type: i0.Input, args: [{ isSignal: true, alias: "noteFormatter", required: false }] }], entityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityType", required: false }] }] } });
|
|
4410
4511
|
|
|
4411
4512
|
/**
|
|
4412
4513
|
* Keyboard-navigable product search. Filters products by display name,
|
|
@@ -7051,7 +7152,7 @@ class EfOrderBuilderComponent {
|
|
|
7051
7152
|
return OrderEntityHelper.calculateTotals(fee > 0 ? { ...order, shippingTotal: fee } : order);
|
|
7052
7153
|
}
|
|
7053
7154
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7054
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderBuilderComponent, isStandalone: true, selector: "ef-order-builder", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: true, transformFunction: null }, products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, customers: { classPropertyName: "customers", publicName: "customers", isSignal: true, isRequired: true, transformFunction: null }, recipient: { classPropertyName: "recipient", publicName: "recipient", isSignal: true, isRequired: false, transformFunction: null }, defaultCallingCode: { classPropertyName: "defaultCallingCode", publicName: "defaultCallingCode", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, priceList: { classPropertyName: "priceList", publicName: "priceList", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, shippingTotal: { classPropertyName: "shippingTotal", publicName: "shippingTotal", isSignal: true, isRequired: false, transformFunction: null }, auditEntries: { classPropertyName: "auditEntries", publicName: "auditEntries", isSignal: true, isRequired: false, transformFunction: null }, auditValueLists: { classPropertyName: "auditValueLists", publicName: "auditValueLists", isSignal: true, isRequired: false, transformFunction: null }, auditNoteFormatter: { classPropertyName: "auditNoteFormatter", publicName: "auditNoteFormatter", isSignal: true, isRequired: false, transformFunction: null }, focusMode: { classPropertyName: "focusMode", publicName: "focusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { order: "orderChange", openCatalogue: "openCatalogue", orderChanged: "orderChanged", orderValidated: "orderValidated", productsAdded: "productsAdded", focusMode: "focusModeChange" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: EfProductTypeaheadComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n @if (recipient(); as r) {\n <!-- No client to pick (a storefront order): who it goes to,\n read-only, in the customer's place. -->\n <div class=\"field col-6\">\n <ef-label labelKey=\"order_builder.recipient\" />\n <div\n class=\"ob-recipient\"\n role=\"group\"\n [attr.aria-label]=\"'order_builder.recipient' | translate\">\n <span class=\"ob-recipient-name\">{{ r.name }}</span>\n @if (recipientTel(); as tel) {\n <a class=\"ob-recipient-phone\" [href]=\"tel\" dir=\"ltr\">{{ r.phone }}</a>\n }\n @if (r.city) {\n <span class=\"ob-recipient-city\">{{ r.city }}</span>\n }\n @if (r.address) {\n <span class=\"ob-recipient-address\" [title]=\"r.address\">{{ r.address }}</span>\n }\n </div>\n </div>\n } @else {\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n }\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n [priceList]=\"priceList()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <!-- Ghost while the preconditions are unmet, tenant once the\n line is addable. A disabled control painted in a faded\n tenant fill reads as an enabled button that lost its\n colour, which is the greyed-out accent the design brief\n bans; ghost reads as \"not yet\". -->\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n size=\"base\"\n [severity]=\"quickAddDisabled() ? 'ghost' : 'tenant'\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines.\n\n ef-data-card owns the panel here instead of sitting inside an\n ef-card: `.tbl-wrap` already draws a bordered, rounded surface, so\n nesting it in another one would be a card inside a card. The title\n and the card's actions move into the head slots it provides.\n\n The columns are declared in TS (`lineColumns`) so discount and tax\n follow the document config, and so one definition drives both the\n desktop table and the mobile cards. Only the article column is\n elastic; every other column holds a number of known magnitude and\n gets a fixed track, which keeps the decimals aligned down the\n table rather than shifting with the longest description. -->\n <ef-data-card\n [columns]=\"lineColumns()\"\n [rows]=\"orderLines()\"\n trackByField=\"id\"\n [hidePager]=\"true\"\n defaultDensity=\"compact\"\n [reorderable]=\"!readonly()\"\n [fixedLayout]=\"true\"\n (rowReorder)=\"reorderLine($event)\"\n mobileLayout=\"card\">\n\n <div tbl-head-info class=\"ob-lines-head\">\n <span class=\"strong\">{{ 'order_builder.lines' | translate }}</span>\n <span class=\"meta\">\u00B7 {{ orderLines().length }}</span>\n </div>\n\n <div tbl-head-actions class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <span empty-state>{{ 'order_builder.empty' | translate }}</span>\n <span empty-state-mobile>{{ 'order_builder.empty' | translate }}</span>\n\n <ng-template efColumnTemplate=\"product\" let-item let-i=\"index\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </ng-template>\n\n <ng-template efColumnTemplate=\"price\" let-item>\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"qty\" let-item>\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"discount\" let-item>\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"tax\" let-item>\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"subtotal\" let-item>\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </ng-template>\n\n <ng-template efColumnTemplate=\"remove\" let-item>\n <ef-button\n icon=\"pi pi-trash\"\n ariaLabelKey=\"common_delete\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n </ng-template>\n </ef-data-card>\n\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n @if (shippingTotal() > 0) {\n <div class=\"row shipping\">\n <span class=\"lbl\">{{ 'order_builder.shipping' | translate }}</span>\n <span class=\"v\">{{ shippingTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n }\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Host cards that belong beside the totals (delivery, notes): a column\n at desktop width, below the totals in the drawer otherwise. -->\n <ng-content select=\"[obRail]\" />\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history\n [entries]=\"auditEntries()\"\n [valueLists]=\"auditValueLists()\"\n [noteFormatter]=\"auditNoteFormatter()\"\n />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class*=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .ob-recipient{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--s-3);row-gap:2px;min-width:0;font-size:13px;line-height:1.45;color:var(--text-soft)}:host .order-builder .ob-recipient>*{flex:0 1 auto;min-width:0;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;unicode-bidi:plaintext}:host .order-builder .ob-recipient-name{font-weight:600;color:var(--text)}:host .order-builder .ob-recipient-phone{color:var(--tenant-700);text-decoration:none;font-variant-numeric:tabular-nums}:host .order-builder .ob-recipient-phone:hover{text-decoration:underline}:host .order-builder .ob-recipient-address{flex-basis:100%;color:var(--text-mute)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder ef-data-card{font-size:13px}:host .order-builder ef-data-card .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder ef-data-card .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder ef-data-card .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder ef-data-card .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder ef-data-card .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder ef-data-card ef-inputnumber,:host .order-builder ef-data-card ef-inputnumber .ef-field-control,:host .order-builder ef-data-card ef-inputnumber input{width:100%;min-width:0}:host .order-builder ef-data-card .num,:host .order-builder ef-data-card .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder ef-data-card .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder ef-data-card .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder ef-data-card .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder ef-data-card .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder ef-data-card .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder ef-data-card .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-lines-head{display:flex;align-items:baseline;gap:6px;min-width:0}:host .order-builder .ob-lines-head .strong{font-weight:700;color:var(--text)}:host .order-builder .ob-lines-head .meta{color:var(--text-soft)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:var(--font-display);font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:var(--font-mono);font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:var(--font-mono);font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:calc(var(--app-bottom-bar, 0px) + env(safe-area-inset-bottom,0px) + 20px);z-index:30;align-items:center;gap:10px;box-sizing:border-box;block-size:var(--ef-order-fab-h);--ob-fab-ground: var(--ink-active);--ob-fab-on: var(--paper);background:var(--ob-fab-ground);color:var(--ob-fab-on);border-radius:var(--r-pill);padding-block:0;padding-inline:16px 10px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:color-mix(in srgb,var(--ob-fab-on) 14%,transparent);color:var(--ob-fab-on);font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:color-mix(in srgb,var(--ob-fab-on) 24%,transparent)}:host .order-builder .ob-fab .open:focus-visible{outline:2px solid var(--ob-fab-on);outline-offset:2px}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host ef-data-card .nm,:host ef-data-card .num,:host ef-data-card .ln-val,:host .ob-totals .v{unicode-bidi:plaintext}@media(pointer:coarse){:host .order-builder .ob-fab .open{min-block-size:var(--hit-touch)}:host ef-data-card .ln-val{min-block-size:var(--hit-field)}:host .order-builder .ob-recipient-phone{line-height:var(--hit-touch)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSelectComponent, selector: "ef-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "group", "optionGroupLabel", "optionGroupChildren", "size", "fluid", "inline", "appendTo", "multiple", "filter", "filterBy", "filterPlaceholder", "filterPlaceholderKey", "autofocusFilter", "autofocus", "editable", "loading", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "multiDisplay", "showToggleAll", "emptyMessage", "emptyMessageKey", "emptyFilterMessage", "emptyFilterMessageKey", "styleClass", "panelStyleClass", "filterByKeys", "variant", "value"], outputs: ["selectionChangeEvent", "filterChangeEvent", "panelShowEvent", "panelHideEvent"] }, { kind: "component", type: EfDatepickerAdvancedComponent, selector: "ef-datepicker-advanced", inputs: ["triggerLabelKey", "triggerLabel", "presets", "activePreset", "start", "end", "allowCustomRange", "allowAllTime", "mode", "selectionMode", "variant", "showTime", "valueFormat"], outputs: ["rangeChange", "opened", "closed"] }, { kind: "component", type: EfInputNumberComponent, selector: "ef-inputnumber", inputs: ["value", "invalid", "format", "showButtons", "buttonLayout", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "prefix", "suffix", "currency", "currencyDisplay", "locale", "localeMatcher", "mode", "useGrouping", "minFractionDigits", "maxFractionDigits", "min", "max", "step", "allowEmpty", "inputSize", "size", "maxlength", "minlength", "pattern", "tabindex", "title", "ariaLabelledBy", "inputStyle", "inputStyleClass", "style", "styleClass", "primeNgVariant", "autofocus", "autocomplete", "fluid", "inline", "variant", "pt", "ptOptions", "dt", "unstyled"], outputs: ["inputEvent", "valueChangeEvent", "focusEvent", "blurEvent", "keyDownEvent", "clearEvent"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfChangeHistoryComponent, selector: "ef-change-history", inputs: ["entries", "titleKey", "emptyKey", "limit", "previewLimit", "collapsible", "startCollapsed", "valueLists", "noteFormatter"] }, { kind: "component", type: EfProductTypeaheadComponent, selector: "ef-product-typeahead", inputs: ["products", "productKeyField", "placeholderKey", "disabled", "currency", "locale", "showVariants", "priceList"], outputs: ["productSelect"] }, { kind: "component", type: EfDataCardComponent, selector: "ef-data-card", inputs: ["mobileLayout", "columns", "rows", "trackByField", "pageNumber", "pageSize", "totalCount", "pageSizeOptions", "hidePager", "sort", "loading", "errorMsg", "loadingKey", "rowDoubleClickable", "rowPressed", "rowDisabled", "hasActionsColumn", "rowActionsContext", "showViewAction", "showEditAction", "showDuplicateAction", "showDeleteAction", "rowActionState", "showDensityControl", "showColumnPicker", "showExportControl", "exporting", "screenContext", "searchTerm", "tableKey", "defaultDensity", "reorderable", "fixedLayout"], outputs: ["pageChange", "pageSizeChange", "sortChange", "rowDoubleClick", "rowAction", "exportRequest", "clearSearch", "retry", "rowReorder"] }, { kind: "directive", type: EfColumnTemplateDirective, selector: "[efColumnTemplate]", inputs: ["efColumnTemplate"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
|
|
7155
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderBuilderComponent, isStandalone: true, selector: "ef-order-builder", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: true, transformFunction: null }, products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, customers: { classPropertyName: "customers", publicName: "customers", isSignal: true, isRequired: true, transformFunction: null }, recipient: { classPropertyName: "recipient", publicName: "recipient", isSignal: true, isRequired: false, transformFunction: null }, defaultCallingCode: { classPropertyName: "defaultCallingCode", publicName: "defaultCallingCode", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, priceList: { classPropertyName: "priceList", publicName: "priceList", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, shippingTotal: { classPropertyName: "shippingTotal", publicName: "shippingTotal", isSignal: true, isRequired: false, transformFunction: null }, auditEntries: { classPropertyName: "auditEntries", publicName: "auditEntries", isSignal: true, isRequired: false, transformFunction: null }, auditValueLists: { classPropertyName: "auditValueLists", publicName: "auditValueLists", isSignal: true, isRequired: false, transformFunction: null }, auditNoteFormatter: { classPropertyName: "auditNoteFormatter", publicName: "auditNoteFormatter", isSignal: true, isRequired: false, transformFunction: null }, focusMode: { classPropertyName: "focusMode", publicName: "focusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { order: "orderChange", openCatalogue: "openCatalogue", orderChanged: "orderChanged", orderValidated: "orderValidated", productsAdded: "productsAdded", focusMode: "focusModeChange" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: EfProductTypeaheadComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n @if (recipient(); as r) {\n <!-- No client to pick (a storefront order): who it goes to,\n read-only, in the customer's place. -->\n <div class=\"field col-6\">\n <ef-label labelKey=\"order_builder.recipient\" />\n <div\n class=\"ob-recipient\"\n role=\"group\"\n [attr.aria-label]=\"'order_builder.recipient' | translate\">\n <span class=\"ob-recipient-name\">{{ r.name }}</span>\n @if (recipientTel(); as tel) {\n <a class=\"ob-recipient-phone\" [href]=\"tel\" dir=\"ltr\">{{ r.phone }}</a>\n }\n @if (r.city) {\n <span class=\"ob-recipient-city\">{{ r.city }}</span>\n }\n @if (r.address) {\n <span class=\"ob-recipient-address\" [title]=\"r.address\">{{ r.address }}</span>\n }\n </div>\n </div>\n } @else {\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n }\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n [priceList]=\"priceList()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <!-- Ghost while the preconditions are unmet, tenant once the\n line is addable. A disabled control painted in a faded\n tenant fill reads as an enabled button that lost its\n colour, which is the greyed-out accent the design brief\n bans; ghost reads as \"not yet\". -->\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n size=\"base\"\n [severity]=\"quickAddDisabled() ? 'ghost' : 'tenant'\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines.\n\n ef-data-card owns the panel here instead of sitting inside an\n ef-card: `.tbl-wrap` already draws a bordered, rounded surface, so\n nesting it in another one would be a card inside a card. The title\n and the card's actions move into the head slots it provides.\n\n The columns are declared in TS (`lineColumns`) so discount and tax\n follow the document config, and so one definition drives both the\n desktop table and the mobile cards. Only the article column is\n elastic; every other column holds a number of known magnitude and\n gets a fixed track, which keeps the decimals aligned down the\n table rather than shifting with the longest description. -->\n <ef-data-card\n [columns]=\"lineColumns()\"\n [rows]=\"orderLines()\"\n trackByField=\"id\"\n [hidePager]=\"true\"\n defaultDensity=\"compact\"\n [reorderable]=\"!readonly()\"\n [fixedLayout]=\"true\"\n (rowReorder)=\"reorderLine($event)\"\n mobileLayout=\"card\">\n\n <div tbl-head-info class=\"ob-lines-head\">\n <span class=\"strong\">{{ 'order_builder.lines' | translate }}</span>\n <span class=\"meta\">\u00B7 {{ orderLines().length }}</span>\n </div>\n\n <div tbl-head-actions class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <span empty-state>{{ 'order_builder.empty' | translate }}</span>\n <span empty-state-mobile>{{ 'order_builder.empty' | translate }}</span>\n\n <ng-template efColumnTemplate=\"product\" let-item let-i=\"index\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </ng-template>\n\n <ng-template efColumnTemplate=\"price\" let-item>\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"qty\" let-item>\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"discount\" let-item>\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"tax\" let-item>\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button type=\"button\" class=\"ln-val\" [disabled]=\"readonly()\" (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </ng-template>\n\n <ng-template efColumnTemplate=\"subtotal\" let-item>\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </ng-template>\n\n <ng-template efColumnTemplate=\"remove\" let-item>\n <ef-button\n icon=\"pi pi-trash\"\n ariaLabelKey=\"common_delete\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n </ng-template>\n </ef-data-card>\n\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n @if (shippingTotal() > 0) {\n <div class=\"row shipping\">\n <span class=\"lbl\">{{ 'order_builder.shipping' | translate }}</span>\n <span class=\"v\">{{ shippingTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n }\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Host cards that belong beside the totals (delivery, notes): a column\n at desktop width, below the totals in the drawer otherwise. -->\n <ng-content select=\"[obRail]\" />\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history\n [entries]=\"auditEntries()\"\n [valueLists]=\"auditValueLists()\"\n [noteFormatter]=\"auditNoteFormatter()\"\n />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class*=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .ob-recipient{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--s-3);row-gap:2px;min-width:0;font-size:13px;line-height:1.45;color:var(--text-soft)}:host .order-builder .ob-recipient>*{flex:0 1 auto;min-width:0;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;unicode-bidi:plaintext}:host .order-builder .ob-recipient-name{font-weight:600;color:var(--text)}:host .order-builder .ob-recipient-phone{color:var(--tenant-700);text-decoration:none;font-variant-numeric:tabular-nums}:host .order-builder .ob-recipient-phone:hover{text-decoration:underline}:host .order-builder .ob-recipient-address{flex-basis:100%;color:var(--text-mute)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder ef-data-card{font-size:13px}:host .order-builder ef-data-card .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder ef-data-card .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder ef-data-card .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder ef-data-card .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder ef-data-card .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder ef-data-card ef-inputnumber,:host .order-builder ef-data-card ef-inputnumber .ef-field-control,:host .order-builder ef-data-card ef-inputnumber input{width:100%;min-width:0}:host .order-builder ef-data-card .num,:host .order-builder ef-data-card .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder ef-data-card .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder ef-data-card .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder ef-data-card .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder ef-data-card .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder ef-data-card .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder ef-data-card .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-lines-head{display:flex;align-items:baseline;gap:6px;min-width:0}:host .order-builder .ob-lines-head .strong{font-weight:700;color:var(--text)}:host .order-builder .ob-lines-head .meta{color:var(--text-soft)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:var(--font-display);font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:var(--font-mono);font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:var(--font-mono);font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:calc(var(--app-bottom-bar, 0px) + env(safe-area-inset-bottom,0px) + 20px);z-index:30;align-items:center;gap:10px;box-sizing:border-box;block-size:var(--ef-order-fab-h);--ob-fab-ground: var(--ink-active);--ob-fab-on: var(--paper);background:var(--ob-fab-ground);color:var(--ob-fab-on);border-radius:var(--r-pill);padding-block:0;padding-inline:16px 10px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:color-mix(in srgb,var(--ob-fab-on) 14%,transparent);color:var(--ob-fab-on);font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:color-mix(in srgb,var(--ob-fab-on) 24%,transparent)}:host .order-builder .ob-fab .open:focus-visible{outline:2px solid var(--ob-fab-on);outline-offset:2px}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host ef-data-card .nm,:host ef-data-card .num,:host ef-data-card .ln-val,:host .ob-totals .v{unicode-bidi:plaintext}@media(pointer:coarse){:host .order-builder .ob-fab .open{min-block-size:var(--hit-touch)}:host ef-data-card .ln-val{min-block-size:var(--hit-field)}:host .order-builder .ob-recipient-phone{line-height:var(--hit-touch)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSelectComponent, selector: "ef-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "group", "optionGroupLabel", "optionGroupChildren", "size", "fluid", "inline", "appendTo", "multiple", "filter", "filterBy", "filterPlaceholder", "filterPlaceholderKey", "autofocusFilter", "autofocus", "editable", "loading", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "multiDisplay", "showToggleAll", "emptyMessage", "emptyMessageKey", "emptyFilterMessage", "emptyFilterMessageKey", "styleClass", "panelStyleClass", "filterByKeys", "variant", "value"], outputs: ["selectionChangeEvent", "filterChangeEvent", "panelShowEvent", "panelHideEvent"] }, { kind: "component", type: EfDatepickerAdvancedComponent, selector: "ef-datepicker-advanced", inputs: ["triggerLabelKey", "triggerLabel", "presets", "activePreset", "start", "end", "allowCustomRange", "allowAllTime", "mode", "selectionMode", "variant", "showTime", "valueFormat"], outputs: ["rangeChange", "opened", "closed"] }, { kind: "component", type: EfInputNumberComponent, selector: "ef-inputnumber", inputs: ["value", "invalid", "format", "showButtons", "buttonLayout", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "prefix", "suffix", "currency", "currencyDisplay", "locale", "localeMatcher", "mode", "useGrouping", "minFractionDigits", "maxFractionDigits", "min", "max", "step", "allowEmpty", "inputSize", "size", "maxlength", "minlength", "pattern", "tabindex", "title", "ariaLabelledBy", "inputStyle", "inputStyleClass", "style", "styleClass", "primeNgVariant", "autofocus", "autocomplete", "fluid", "inline", "variant", "pt", "ptOptions", "dt", "unstyled"], outputs: ["inputEvent", "valueChangeEvent", "focusEvent", "blurEvent", "keyDownEvent", "clearEvent"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "ariaLabelKey", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition", "autofocus"], outputs: ["clickEvent"] }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfChangeHistoryComponent, selector: "ef-change-history", inputs: ["entries", "titleKey", "emptyKey", "limit", "previewLimit", "collapsible", "startCollapsed", "valueLists", "noteFormatter", "entityType"] }, { kind: "component", type: EfProductTypeaheadComponent, selector: "ef-product-typeahead", inputs: ["products", "productKeyField", "placeholderKey", "disabled", "currency", "locale", "showVariants", "priceList"], outputs: ["productSelect"] }, { kind: "component", type: EfDataCardComponent, selector: "ef-data-card", inputs: ["mobileLayout", "columns", "rows", "trackByField", "pageNumber", "pageSize", "totalCount", "pageSizeOptions", "hidePager", "sort", "loading", "errorMsg", "loadingKey", "rowDoubleClickable", "rowPressed", "rowDisabled", "hasActionsColumn", "rowActionsContext", "showViewAction", "showEditAction", "showDuplicateAction", "showDeleteAction", "rowActionState", "showDensityControl", "showColumnPicker", "showExportControl", "exporting", "screenContext", "searchTerm", "tableKey", "defaultDensity", "reorderable", "fixedLayout"], outputs: ["pageChange", "pageSizeChange", "sortChange", "rowDoubleClick", "rowAction", "exportRequest", "clearSearch", "retry", "rowReorder"] }, { kind: "directive", type: EfColumnTemplateDirective, selector: "[efColumnTemplate]", inputs: ["efColumnTemplate"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
|
|
7055
7156
|
}
|
|
7056
7157
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, decorators: [{
|
|
7057
7158
|
type: Component,
|
|
@@ -9906,6 +10007,7 @@ class EfTabsComponent {
|
|
|
9906
10007
|
* `fr.json` / `ar.json`. */
|
|
9907
10008
|
dirtyLabelKey = 'common.dirty';
|
|
9908
10009
|
valueChange = new EventEmitter();
|
|
10010
|
+
host = inject(ElementRef);
|
|
9909
10011
|
tabEls;
|
|
9910
10012
|
/* ── Content children (optional projected panels) ────────────── */
|
|
9911
10013
|
panels;
|
|
@@ -9967,6 +10069,13 @@ class EfTabsComponent {
|
|
|
9967
10069
|
default:
|
|
9968
10070
|
return;
|
|
9969
10071
|
}
|
|
10072
|
+
// In a right-to-left tablist the next tab sits to the left, so the
|
|
10073
|
+
// arrows swap (WAI-ARIA tabs pattern); Home and End keep their meaning.
|
|
10074
|
+
if ((event.key === 'ArrowRight' || event.key === 'ArrowLeft') && this.isRtl(event.currentTarget)) {
|
|
10075
|
+
nextPos = event.key === 'ArrowRight'
|
|
10076
|
+
? enabledPositions[(here - 1 + enabledPositions.length) % enabledPositions.length]
|
|
10077
|
+
: enabledPositions[(here + 1) % enabledPositions.length];
|
|
10078
|
+
}
|
|
9970
10079
|
event.preventDefault();
|
|
9971
10080
|
const target = all[nextPos];
|
|
9972
10081
|
this.select(target);
|
|
@@ -9975,11 +10084,19 @@ class EfTabsComponent {
|
|
|
9975
10084
|
const el = this.tabEls?.toArray()[nextPos]?.nativeElement;
|
|
9976
10085
|
el?.focus();
|
|
9977
10086
|
}
|
|
10087
|
+
isRtl(target) {
|
|
10088
|
+
const el = target instanceof HTMLElement ? target : this.host.nativeElement;
|
|
10089
|
+
if (getComputedStyle(el).direction === 'rtl')
|
|
10090
|
+
return true;
|
|
10091
|
+
// Only reached in jsdom, which does not inherit `direction`; a browser
|
|
10092
|
+
// answers above.
|
|
10093
|
+
return el.closest('[dir]')?.getAttribute('dir') === 'rtl';
|
|
10094
|
+
}
|
|
9978
10095
|
trackValue(_, item) {
|
|
9979
10096
|
return item.value;
|
|
9980
10097
|
}
|
|
9981
10098
|
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 });
|
|
10099
|
+
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.badgeKey) {\n <!-- Whitespace inside, as in the label, so the accessible name\n reads \"manquant personnalis\u00E9\", not one word. -->\n <span class=\"ef-tabs__badge\">\n {{ item.badgeKey | translate }}\n </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}.ef-tabs__badge{font-size:10.5px;font-weight:600;line-height:1.5;padding:0 6px;border:1px solid currentColor;border-radius:var(--r-pill);white-space:nowrap}: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
10100
|
}
|
|
9984
10101
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTabsComponent, decorators: [{
|
|
9985
10102
|
type: Component,
|
|
@@ -9988,7 +10105,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
9988
10105
|
'[class.ef-tabs--underline]': "variant === 'underline'",
|
|
9989
10106
|
'[class.ef-tabs--overflow]': 'overflow',
|
|
9990
10107
|
'[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"] }]
|
|
10108
|
+
}, 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.badgeKey) {\n <!-- Whitespace inside, as in the label, so the accessible name\n reads \"manquant personnalis\u00E9\", not one word. -->\n <span class=\"ef-tabs__badge\">\n {{ item.badgeKey | translate }}\n </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}.ef-tabs__badge{font-size:10.5px;font-weight:600;line-height:1.5;padding:0 6px;border:1px solid currentColor;border-radius:var(--r-pill);white-space:nowrap}: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
10109
|
}], propDecorators: { items: [{
|
|
9993
10110
|
type: Input,
|
|
9994
10111
|
args: [{ required: true }]
|
|
@@ -11644,6 +11761,10 @@ class EfInputTextComponent extends AbstractEfFormControl {
|
|
|
11644
11761
|
* follow the value's first strong character. The label and messages keep
|
|
11645
11762
|
* the page direction. Null or absent leaves the input unchanged. */
|
|
11646
11763
|
dir = null;
|
|
11764
|
+
/** Language of the typed value (`'ar'` for an Arabic title on a French
|
|
11765
|
+
* screen), applied to the native input only: the label and hint are page
|
|
11766
|
+
* text and keep the page language. Null or absent renders no attribute. */
|
|
11767
|
+
lang = null;
|
|
11647
11768
|
/** Clearable (showClear / canClear) is inherited from AbstractEfFormControl;
|
|
11648
11769
|
* we only supply the value predicate + the reset. */
|
|
11649
11770
|
get hasValue() {
|
|
@@ -11755,11 +11876,11 @@ class EfInputTextComponent extends AbstractEfFormControl {
|
|
|
11755
11876
|
this.onTouched();
|
|
11756
11877
|
}
|
|
11757
11878
|
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
|
|
11879
|
+
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
11880
|
}
|
|
11760
11881
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfInputTextComponent, decorators: [{
|
|
11761
11882
|
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
|
|
11883
|
+
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
11884
|
}], ctorParameters: () => [], propDecorators: { size: [{
|
|
11764
11885
|
type: Input
|
|
11765
11886
|
}], type: [{
|
|
@@ -11776,6 +11897,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
11776
11897
|
type: Input
|
|
11777
11898
|
}], dir: [{
|
|
11778
11899
|
type: Input
|
|
11900
|
+
}], lang: [{
|
|
11901
|
+
type: Input
|
|
11779
11902
|
}], variant: [{
|
|
11780
11903
|
type: Input
|
|
11781
11904
|
}], prefix: [{
|
|
@@ -12349,6 +12472,19 @@ class EfMultiSelectComponent extends AbstractEfFormControl {
|
|
|
12349
12472
|
selectionLimit;
|
|
12350
12473
|
filters;
|
|
12351
12474
|
inline = false;
|
|
12475
|
+
/**
|
|
12476
|
+
* Rendering variant, as on `ef-select`.
|
|
12477
|
+
* - `'primeng'` (default): PrimeNG's own multiselect skin, unchanged.
|
|
12478
|
+
* - `'comptoir'`: the shared `.es` skin with `.es--comptoir`, the same hook
|
|
12479
|
+
* `ef-select variant="comptoir"` uses: a trigger on
|
|
12480
|
+
* `--hit-field` (`--hit-base` 40px, `--hit-touch` 48px on a
|
|
12481
|
+
* coarse pointer) and no minimum width, so a select and a
|
|
12482
|
+
* multi-select in one row align and both shrink with it.
|
|
12483
|
+
*/
|
|
12484
|
+
variant = 'primeng';
|
|
12485
|
+
get effectiveStyleClass() {
|
|
12486
|
+
return this.variant === 'comptoir' ? 'es es--comptoir' : undefined;
|
|
12487
|
+
}
|
|
12352
12488
|
get isInline() { return this.inline; }
|
|
12353
12489
|
get isRequired() { return this.required(); }
|
|
12354
12490
|
selectedValues = [];
|
|
@@ -12413,11 +12549,11 @@ class EfMultiSelectComponent extends AbstractEfFormControl {
|
|
|
12413
12549
|
this.selectedValues = [];
|
|
12414
12550
|
}
|
|
12415
12551
|
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" }] });
|
|
12552
|
+
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
12553
|
}
|
|
12418
12554
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfMultiSelectComponent, decorators: [{
|
|
12419
12555
|
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"] }]
|
|
12556
|
+
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
12557
|
}], ctorParameters: () => [], propDecorators: { changeEvent: [{
|
|
12422
12558
|
type: Output
|
|
12423
12559
|
}], optionLabel: [{
|
|
@@ -12441,6 +12577,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
12441
12577
|
}], inline: [{
|
|
12442
12578
|
type: Input,
|
|
12443
12579
|
args: [{ transform: booleanAttribute }]
|
|
12580
|
+
}], variant: [{
|
|
12581
|
+
type: Input
|
|
12444
12582
|
}], isInline: [{
|
|
12445
12583
|
type: HostBinding,
|
|
12446
12584
|
args: ['class.ef-inline']
|
|
@@ -13330,6 +13468,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
13330
13468
|
class EfTextareaComponent extends AbstractEfFormControl {
|
|
13331
13469
|
rows = 4;
|
|
13332
13470
|
maxlength;
|
|
13471
|
+
/** Language of the typed value, applied to the native textarea only (the
|
|
13472
|
+
* label and hint keep the page language). Null or absent renders none. */
|
|
13473
|
+
lang = null;
|
|
13333
13474
|
autofocus = false;
|
|
13334
13475
|
/** Monospace face for source text (an HTML body, a JSON snippet), where
|
|
13335
13476
|
* tags and indentation must stay readable. */
|
|
@@ -13430,15 +13571,17 @@ class EfTextareaComponent extends AbstractEfFormControl {
|
|
|
13430
13571
|
this.onTouched();
|
|
13431
13572
|
}
|
|
13432
13573
|
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" }] });
|
|
13574
|
+
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
13575
|
}
|
|
13435
13576
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfTextareaComponent, decorators: [{
|
|
13436
13577
|
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"] }]
|
|
13578
|
+
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
13579
|
}], ctorParameters: () => [], propDecorators: { rows: [{
|
|
13439
13580
|
type: Input
|
|
13440
13581
|
}], maxlength: [{
|
|
13441
13582
|
type: Input
|
|
13583
|
+
}], lang: [{
|
|
13584
|
+
type: Input
|
|
13442
13585
|
}], autofocus: [{
|
|
13443
13586
|
type: Input,
|
|
13444
13587
|
args: [{ transform: booleanAttribute }]
|
|
@@ -13454,82 +13597,699 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
13454
13597
|
args: ['focusin', ['$event']]
|
|
13455
13598
|
}] } });
|
|
13456
13599
|
|
|
13600
|
+
/**
|
|
13601
|
+
* Loads the rich text editor component that `ef-localized-input`'s `rich`
|
|
13602
|
+
* mode renders. The editor lives in the `@elasticias/ui/rich-text` entry
|
|
13603
|
+
* point so an app that never edits rich text neither bundles PrimeNG's Editor
|
|
13604
|
+
* nor needs `quill` installed. Provide it with `provideEfRichText()` from that
|
|
13605
|
+
* entry (the editor code is in the app's main bundle, Quill itself still loads
|
|
13606
|
+
* lazily the first time an editor renders), or with your own loader to defer
|
|
13607
|
+
* the editor code too:
|
|
13608
|
+
*
|
|
13609
|
+
* ```ts
|
|
13610
|
+
* { provide: EF_RICH_TEXT, useValue: () => import('@elasticias/ui/rich-text').then(m => m.EfRichTextComponent) }
|
|
13611
|
+
* ```
|
|
13612
|
+
*/
|
|
13613
|
+
const EF_RICH_TEXT = new InjectionToken('EF_RICH_TEXT');
|
|
13614
|
+
/** Length of the text a reader sees in an HTML fragment, tags not counted. Browser only: it parses with `document`. */
|
|
13615
|
+
function plainTextLength(html) {
|
|
13616
|
+
if (!html)
|
|
13617
|
+
return 0;
|
|
13618
|
+
const tpl = document.createElement('template');
|
|
13619
|
+
tpl.innerHTML = html;
|
|
13620
|
+
return (tpl.content.textContent ?? '').length;
|
|
13621
|
+
}
|
|
13622
|
+
|
|
13457
13623
|
let nextId = 0;
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13624
|
+
/** Same texts once blanks are dropped. */
|
|
13625
|
+
function sameText(a, b) {
|
|
13626
|
+
const ka = Object.keys(a);
|
|
13627
|
+
return ka.length === Object.keys(b).length && ka.every(k => a[k] === b[k]);
|
|
13628
|
+
}
|
|
13463
13629
|
/**
|
|
13464
|
-
*
|
|
13465
|
-
*
|
|
13466
|
-
* and the
|
|
13467
|
-
*
|
|
13468
|
-
*
|
|
13630
|
+
* One owner-written text (a title, a description, an HTML body) in every
|
|
13631
|
+
* content language: a pill tab per culture, the active culture's field below
|
|
13632
|
+
* it, and the whole object out on every edit with blank cultures removed
|
|
13633
|
+
* (ADR-050 section 7).
|
|
13634
|
+
*
|
|
13635
|
+
* - **Cultures** come from `EF_CONTENT_CULTURES` (the ERP provides them from
|
|
13636
|
+
* the tenant profile) unless `cultures` is set. The active culture starts
|
|
13637
|
+
* on the default and is two-way through `[(activeCulture)]`.
|
|
13638
|
+
* - **One tab row for several texts** (an email's subject and body): bind the
|
|
13639
|
+
* same signal to `[(activeCulture)]` on each and set `[showTabs]="false"`
|
|
13640
|
+
* on all but one, and list those in the row owner's `[linkedInputs]` so
|
|
13641
|
+
* the row also shows their error tone, required tone and dirty dot. A new
|
|
13642
|
+
* set of server errors moves the shared culture to the first culture in
|
|
13643
|
+
* error in the row's order.
|
|
13644
|
+
* - **Tabs** show the language name only (no "missing" note: an empty culture
|
|
13645
|
+
* is visible from its empty field), the error tone on the default culture of a `required` field left
|
|
13646
|
+
* empty once the field is touched, changed or its form submitted, and the
|
|
13647
|
+
* dirty dot on cultures edited since the value arrived. Dirty detection
|
|
13648
|
+
* relies on the parent storing the emitted object by reference (the V2
|
|
13649
|
+
* signal pattern): a parent that clones the value resets the dots on every
|
|
13650
|
+
* edit, though it never loses text.
|
|
13651
|
+
* - **Direction.** Each field gets its culture's `dir` and `lang`. In `html`
|
|
13652
|
+
* mode the source stays `ltr` for every culture (raw HTML in an `rtl`
|
|
13653
|
+
* textarea reads reversed) and the sanitised preview takes the culture's
|
|
13654
|
+
* direction.
|
|
13655
|
+
* - **Rich mode** (`mode="rich"`) edits each culture with `ef-rich-text`, the
|
|
13656
|
+
* shared editor in `@elasticias/ui/rich-text`. The app turns it on once with
|
|
13657
|
+
* `provideEfRichText()` (and installs `quill`); without it the field falls
|
|
13658
|
+
* back to the `html` source and logs why in development, as it does when
|
|
13659
|
+
* the editor's chunk fails to load. With `advisedLength` the counter
|
|
13660
|
+
* counts the text a reader sees; with only `maxLength` it counts the
|
|
13661
|
+
* stored HTML, which is what the server measures. Neither caps typing in
|
|
13662
|
+
* this mode.
|
|
13663
|
+
* - **Read-only cultures** are shown read-only (still focusable, so the hint
|
|
13664
|
+
* is read and the text can be copied) with
|
|
13665
|
+
* `ef_localized_input_read_only_hint` and the projected `[read-only-link]`;
|
|
13666
|
+
* their text still travels in the emitted object. `[efReadOnly]`, `[disabled]`
|
|
13667
|
+
* and a form's `disable()` disable every culture; the tabs stay usable so the
|
|
13668
|
+
* other languages can still be read.
|
|
13669
|
+
* - **Forms.** A ControlValueAccessor (`[formControl]`, `formControlName`,
|
|
13670
|
+
* `[(ngModel)]`) as well as `[value]` + `(valueChangeEvent)`. With
|
|
13671
|
+
* `required`, the form control reports `required` while the default culture
|
|
13672
|
+
* is empty, and once the field is touched, changed or submitted the default
|
|
13673
|
+
* culture's field shows `validation.required` and is `aria-invalid`.
|
|
13674
|
+
* - **Server errors** (ADR-010) come from the enclosing `[efServerErrors]`
|
|
13675
|
+
* scope. `<name>.<culture>` errors tint that culture's tab, show under its
|
|
13676
|
+
* field while it is active (the field links itself by name) and, from another
|
|
13677
|
+
* tab, show as a hint naming the languages in error; a new set of errors
|
|
13678
|
+
* moves to the first of them. `<name>` errors, or an explicit `[errors]`,
|
|
13679
|
+
* show under the field on every tab, as do errors for a language the field
|
|
13680
|
+
* does not list (`title.xx`), prefixed with that language's name (or its
|
|
13681
|
+
* code) through `ef_localized_input_error_in_culture`. Messages are
|
|
13682
|
+
* translated as keys. Keys: `ef_localized_input_errors_in_other`,
|
|
13683
|
+
* `ef_localized_input_error_in_culture`.
|
|
13684
|
+
* - **Counter.** With `advisedLength` it reads as advice through
|
|
13685
|
+
* `ef_localized_input_counter_advised` (`{{count}}`, `{{max}}`), so 5123 of
|
|
13686
|
+
* 5000 does not look like a cap; with `maxLength` alone it stays `n / max`.
|
|
13687
|
+
* - **Spoken limit.** `counterHintKey` is read with the counter (through
|
|
13688
|
+
* `aria-describedby`) and `counterOverKey` is announced politely when the
|
|
13689
|
+
* text passes the limit; both take `{{max}}`. On a field with several
|
|
13690
|
+
* cultures the announcement is prefixed with the culture's name through
|
|
13691
|
+
* `ef_localized_input_error_in_culture`, so `counterOverKey` should not
|
|
13692
|
+
* name it.
|
|
13693
|
+
* - **Names.** Each culture's field is named `<name>.<culture>` (`title.ar`),
|
|
13694
|
+
* the key the backend reports a per-culture error under.
|
|
13695
|
+
*
|
|
13696
|
+
* i18n keys the app's bundles must define (the libs ship no JSON):
|
|
13697
|
+
*
|
|
13698
|
+
* | key | fr | ar | en |
|
|
13699
|
+
* |---|---|---|---|
|
|
13700
|
+
* | `ef_localized_input_culture_fr` | Français | الفرنسية | French |
|
|
13701
|
+
* | `ef_localized_input_culture_ar` | Arabe | العربية | Arabic |
|
|
13702
|
+
* | `ef_localized_input_culture_en` | Anglais | الإنجليزية | English |
|
|
13703
|
+
* | `ef_localized_input_cultures` | Langues du texte | لغات النص | Text languages |
|
|
13704
|
+
* | `ef_localized_input_errors_in_other` | Erreur dans : | خطأ في: | Error in: |
|
|
13705
|
+
* | `ef_localized_input_counter_advised` | {{count}} / {{max}} conseillés | {{count}} / {{max}} موصى بها | {{count}} / {{max}} advised |
|
|
13706
|
+
* | `ef_localized_input_error_in_culture` | {{culture}} : {{message}} | {{culture}}: {{message}} | {{culture}}: {{message}} |
|
|
13707
|
+
* | `ef_localized_input_read_only_hint` | Ce texte se modifie ailleurs. | يُعدَّل هذا النص في مكان آخر. | This text is edited elsewhere. |
|
|
13708
|
+
*
|
|
13709
|
+
* ```html
|
|
13710
|
+
* <ef-localized-input name="title" labelKey="product_title" required
|
|
13711
|
+
* [value]="product().title" (valueChangeEvent)="patch({ title: $event })"
|
|
13712
|
+
* [advisedLength]="60" [maxLength]="256" />
|
|
13469
13713
|
*
|
|
13470
|
-
*
|
|
13471
|
-
*
|
|
13472
|
-
*
|
|
13714
|
+
* <ef-localized-input name="subject" labelKey="email_subject" [maxLength]="200" [linkedInputs]="[body]" [(activeCulture)]="culture" ... />
|
|
13715
|
+
* <ef-localized-input #body name="htmlBody" labelKey="email_body" mode="html" [showTabs]="false" [(activeCulture)]="culture" ... />
|
|
13716
|
+
*
|
|
13717
|
+
* <ef-localized-input name="title" [readOnlyCultures]="['fr']" ...>
|
|
13718
|
+
* <a read-only-link [routerLink]="catalogLink()">{{ 'store_open_catalog' | translate }}</a>
|
|
13719
|
+
* </ef-localized-input>
|
|
13720
|
+
* ```
|
|
13721
|
+
*/
|
|
13722
|
+
class EfLocalizedInputComponent {
|
|
13723
|
+
/** The camelCase backend property (`title`); each field is `<name>.<culture>`. */
|
|
13724
|
+
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
13725
|
+
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
13726
|
+
mode = input('text', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
13727
|
+
rows = input(4, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
13728
|
+
/** Hard cap, the server's limit. */
|
|
13729
|
+
maxLength = input(undefined, ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
|
|
13730
|
+
/** What the counter measures against (what a search engine shows, say). */
|
|
13731
|
+
advisedLength = input(undefined, ...(ngDevMode ? [{ debugName: "advisedLength" }] : /* istanbul ignore next */ []));
|
|
13732
|
+
/** The default culture must be filled. */
|
|
13733
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13734
|
+
/** Cultures shown but not editable here, with the `[read-only-link]` slot. */
|
|
13735
|
+
readOnlyCultures = input([], ...(ngDevMode ? [{ debugName: "readOnlyCultures" }] : /* istanbul ignore next */ []));
|
|
13736
|
+
/** Labels the field and names its tablist; without it the tablist is
|
|
13737
|
+
* named `ef_localized_input_cultures`. */
|
|
13738
|
+
labelKey = input('', ...(ngDevMode ? [{ debugName: "labelKey" }] : /* istanbul ignore next */ []));
|
|
13739
|
+
placeholderKey = input('', ...(ngDevMode ? [{ debugName: "placeholderKey" }] : /* istanbul ignore next */ []));
|
|
13740
|
+
hintKey = input('', ...(ngDevMode ? [{ debugName: "hintKey" }] : /* istanbul ignore next */ []));
|
|
13741
|
+
/** i18n key (with `{{max}}`) read out with the counter, for a limit that is advice rather than a cap. */
|
|
13742
|
+
counterHintKey = input('', ...(ngDevMode ? [{ debugName: "counterHintKey" }] : /* istanbul ignore next */ []));
|
|
13743
|
+
/** i18n key (with `{{max}}`) announced politely when the text goes past the limit. */
|
|
13744
|
+
counterOverKey = input('', ...(ngDevMode ? [{ debugName: "counterOverKey" }] : /* istanbul ignore next */ []));
|
|
13745
|
+
/** Field style in `text` mode only, named like ef-input-text's (`primeng`
|
|
13746
|
+
* or `comptoir`); the textarea, html and rich modes have one style.
|
|
13747
|
+
* `default` is the former name of `primeng`, kept for one release. */
|
|
13748
|
+
variant = input('comptoir', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
13749
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13750
|
+
/**
|
|
13751
|
+
* Messages for the whole field. Without it they come from the enclosing
|
|
13752
|
+
* `[efServerErrors]` scope under `name`; an explicit list wins. The
|
|
13753
|
+
* per-language messages (`<name>.<culture>`) always come from the scope.
|
|
13754
|
+
*/
|
|
13755
|
+
errors = input(undefined, ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
13756
|
+
/** Overrides the `EF_CONTENT_CULTURES` list for this field. */
|
|
13757
|
+
cultures = input(undefined, ...(ngDevMode ? [{ debugName: "cultures" }] : /* istanbul ignore next */ []));
|
|
13758
|
+
/**
|
|
13759
|
+
* The culture on show, two-way (`[(activeCulture)]`). `null` or a culture
|
|
13760
|
+
* the field does not list means the default; the field then writes back
|
|
13761
|
+
* the culture it settled on, so the parent always reads a listed code.
|
|
13762
|
+
* Bind one signal to several inputs to give them one tab row.
|
|
13763
|
+
*/
|
|
13764
|
+
activeCulture = model(null, ...(ngDevMode ? [{ debugName: "activeCulture" }] : /* istanbul ignore next */ []));
|
|
13765
|
+
/**
|
|
13766
|
+
* Draws the culture tab row. Off for a field that follows another
|
|
13767
|
+
* field's row through a shared `activeCulture`: its culture is then
|
|
13768
|
+
* named to assistive tech through `aria-describedby` and the hint still
|
|
13769
|
+
* names the cultures in error. List it in the row owner's
|
|
13770
|
+
* `linkedInputs` so the row draws its error tone, required tone and
|
|
13771
|
+
* dirty dot too. Without tabs it never rewrites `activeCulture` to fit
|
|
13772
|
+
* its own culture list (it shows its own fallback instead), and it moves
|
|
13773
|
+
* the shared culture to its first culture in error only when no row
|
|
13774
|
+
* owner links it.
|
|
13775
|
+
*/
|
|
13776
|
+
showTabs = input(true, { ...(ngDevMode ? { debugName: "showTabs" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13777
|
+
/**
|
|
13778
|
+
* On the input that draws the shared tab row: the inputs that follow it
|
|
13779
|
+
* (`[showTabs]="false"`), whose `cultureFlags` the row merges into its
|
|
13780
|
+
* tones and dots. A new set of server errors on any of them moves the
|
|
13781
|
+
* shared culture to the first culture in error in this row's order.
|
|
13782
|
+
* Badges stay this input's own. A template reference works forward:
|
|
13783
|
+
* `<ef-localized-input [linkedInputs]="[body]" ... /> <ef-localized-input #body [showTabs]="false" ... />`.
|
|
13784
|
+
*/
|
|
13785
|
+
linkedInputs = input([], ...(ngDevMode ? [{ debugName: "linkedInputs" }] : /* istanbul ignore next */ []));
|
|
13786
|
+
/** `html` mode only: the sanitised preview under the source. Off when
|
|
13787
|
+
* the screen renders its own preview (an email rendered by the server,
|
|
13788
|
+
* with its tokens filled in). */
|
|
13789
|
+
preview = input(true, { ...(ngDevMode ? { debugName: "preview" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13790
|
+
/** A badge per culture tab, culture code to i18n key (`{ ar:
|
|
13791
|
+
* 'email_templates_customized' }`): a state of that language worth
|
|
13792
|
+
* seeing from the tab row. Drawn by the input that shows the tabs. */
|
|
13793
|
+
tabBadges = input({}, ...(ngDevMode ? [{ debugName: "tabBadges" }] : /* istanbul ignore next */ []));
|
|
13794
|
+
/** The whole new object, trimmed, with blank cultures removed. */
|
|
13795
|
+
valueChangeEvent = output();
|
|
13796
|
+
contentCultures = inject(EF_CONTENT_CULTURES);
|
|
13797
|
+
readOnlyScope = inject(EfReadOnlyDirective, { optional: true });
|
|
13798
|
+
ngControl = inject(NgControl, { self: true, optional: true });
|
|
13799
|
+
serverErrorsScope = inject(EfServerErrorsDirective, { optional: true });
|
|
13800
|
+
richLoader = inject(EF_RICH_TEXT, { optional: true });
|
|
13801
|
+
richType = signal(null, ...(ngDevMode ? [{ debugName: "richType" }] : /* istanbul ignore next */ []));
|
|
13802
|
+
/** The editor's chunk failed to load (offline, a stale deploy). */
|
|
13803
|
+
richFailed = signal(false, ...(ngDevMode ? [{ debugName: "richFailed" }] : /* istanbul ignore next */ []));
|
|
13804
|
+
richHost = viewChild('richHost', { ...(ngDevMode ? { debugName: "richHost" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
13805
|
+
richRef = null;
|
|
13806
|
+
textField = viewChild(EfInputTextComponent, ...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
|
|
13807
|
+
areaField = viewChild(EfTextareaComponent, ...(ngDevMode ? [{ debugName: "areaField" }] : /* istanbul ignore next */ []));
|
|
13808
|
+
/** `rich` without a provided editor renders the html source instead. */
|
|
13809
|
+
effectiveMode = computed(() => this.mode() === 'rich' && (!this.richLoader || this.richFailed()) ? 'html' : this.mode(), ...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
|
|
13810
|
+
parentForm = inject(FormGroupDirective, { optional: true }) ?? inject(NgForm, { optional: true });
|
|
13811
|
+
host = inject(ElementRef);
|
|
13812
|
+
translateService = inject(TranslateService);
|
|
13813
|
+
/** The user typed in, or left, the field. */
|
|
13814
|
+
interacted = signal(false, ...(ngDevMode ? [{ debugName: "interacted" }] : /* istanbul ignore next */ []));
|
|
13815
|
+
/** The form control is touched or dirty, or its form was submitted; read on every check. */
|
|
13816
|
+
formTouched = signal(false, ...(ngDevMode ? [{ debugName: "formTouched" }] : /* istanbul ignore next */ []));
|
|
13817
|
+
/** Errors wait for touch, a change or a submit, like every ef-* control. */
|
|
13818
|
+
showErrors = computed(() => this.interacted() || this.formTouched(), ...(ngDevMode ? [{ debugName: "showErrors" }] : /* istanbul ignore next */ []));
|
|
13819
|
+
uid = `ef-localized-input-${nextId++}`;
|
|
13820
|
+
/** The object this component last emitted; an echo of it is not new data. */
|
|
13821
|
+
lastEmitted = null;
|
|
13822
|
+
/** The text as typed, blanks and spaces kept, so an echo never rewrites a field mid-word. */
|
|
13823
|
+
raw = linkedSignal({ ...(ngDevMode ? { debugName: "raw" } : /* istanbul ignore next */ {}), source: this.value,
|
|
13824
|
+
computation: (v, prev) => {
|
|
13825
|
+
if (prev && (v === this.lastEmitted || sameText(cleanLocalizedText(v), cleanLocalizedText(prev.value)))) {
|
|
13826
|
+
return prev.value;
|
|
13827
|
+
}
|
|
13828
|
+
return { ...(v ?? {}) };
|
|
13829
|
+
} });
|
|
13830
|
+
/** What the dirty dots compare against: the last value that came from outside. */
|
|
13831
|
+
baseline = linkedSignal({ ...(ngDevMode ? { debugName: "baseline" } : /* istanbul ignore next */ {}), source: this.value,
|
|
13832
|
+
computation: (v, prev) => (prev && v === this.lastEmitted ? prev.value : cleanLocalizedText(v)) });
|
|
13833
|
+
cleaned = computed(() => cleanLocalizedText(this.raw()), ...(ngDevMode ? [{ debugName: "cleaned" }] : /* istanbul ignore next */ []));
|
|
13834
|
+
list = computed(() => this.cultures() ?? this.contentCultures().cultures, ...(ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []));
|
|
13835
|
+
defaultCode = computed(() => {
|
|
13836
|
+
const preferred = this.contentCultures().defaultCulture;
|
|
13837
|
+
const list = this.list();
|
|
13838
|
+
return list.some(c => c.code === preferred) ? preferred : (list[0]?.code ?? preferred);
|
|
13839
|
+
}, ...(ngDevMode ? [{ debugName: "defaultCode" }] : /* istanbul ignore next */ []));
|
|
13840
|
+
active = computed(() => {
|
|
13841
|
+
const list = this.list();
|
|
13842
|
+
const code = this.activeCulture();
|
|
13843
|
+
return list.find(c => c.code === code) ?? list.find(c => c.code === this.defaultCode()) ?? list[0];
|
|
13844
|
+
}, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
13845
|
+
/** The active culture's code: unlike active(), equal when an equal culture list arrives. */
|
|
13846
|
+
activeCode = computed(() => this.active()?.code, ...(ngDevMode ? [{ debugName: "activeCode" }] : /* istanbul ignore next */ []));
|
|
13847
|
+
/** Server messages for the whole field (`title`), and those of unlisted
|
|
13848
|
+
* cultures, translated here (never through the pipe a second time). */
|
|
13849
|
+
fieldErrors = computed(() => {
|
|
13850
|
+
this.textsVersion();
|
|
13851
|
+
const t = (key) => this.translateService.instant(key);
|
|
13852
|
+
const messages = [
|
|
13853
|
+
...(resolveExternalErrors(this.errors(), this.serverErrorsScope, this.name()) ?? []).map(t),
|
|
13854
|
+
...this.unlistedErrors(),
|
|
13855
|
+
];
|
|
13856
|
+
return messages.length ? messages : null;
|
|
13857
|
+
}, ...(ngDevMode ? [{ debugName: "fieldErrors" }] : /* istanbul ignore next */ []));
|
|
13858
|
+
/** Every `<name>.<culture>` message the scope holds. */
|
|
13859
|
+
allCultureErrors = computed(() => this.serverErrorsScope?.errorsWithPrefix(`${this.name()}.`) ?? {}, ...(ngDevMode ? [{ debugName: "allCultureErrors" }] : /* istanbul ignore next */ []));
|
|
13860
|
+
/** Server messages per language (`title.ar` gives `ar`), only for listed cultures. */
|
|
13861
|
+
cultureErrors = computed(() => {
|
|
13862
|
+
const all = this.allCultureErrors();
|
|
13863
|
+
const out = {};
|
|
13864
|
+
for (const c of this.list())
|
|
13865
|
+
if (all[c.code])
|
|
13866
|
+
out[c.code] = all[c.code];
|
|
13867
|
+
return out;
|
|
13868
|
+
}, ...(ngDevMode ? [{ debugName: "cultureErrors" }] : /* istanbul ignore next */ []));
|
|
13869
|
+
/** Messages for a language this field does not list (`title.xx`): shown
|
|
13870
|
+
* with the whole field, never dropped, prefixed with the culture
|
|
13871
|
+
* (`xx : message`) since no tab says which language they concern. */
|
|
13872
|
+
unlistedErrors = computed(() => {
|
|
13873
|
+
this.textsVersion();
|
|
13874
|
+
const t = this.translateService;
|
|
13875
|
+
const listed = new Set(this.list().map(c => c.code));
|
|
13876
|
+
return Object.entries(this.allCultureErrors()).flatMap(([code, messages]) => {
|
|
13877
|
+
if (listed.has(code))
|
|
13878
|
+
return [];
|
|
13879
|
+
// The culture's name when the bundle has one, else its code.
|
|
13880
|
+
const nameKey = `ef_localized_input_culture_${code}`;
|
|
13881
|
+
const name = t.instant(nameKey);
|
|
13882
|
+
const culture = name && name !== nameKey ? name : code;
|
|
13883
|
+
return messages.map(m => t.instant('ef_localized_input_error_in_culture', { culture, message: t.instant(m) }));
|
|
13884
|
+
});
|
|
13885
|
+
}, ...(ngDevMode ? [{ debugName: "unlistedErrors" }] : /* istanbul ignore next */ []));
|
|
13886
|
+
/** Bumps when texts change, so translated messages follow the language. */
|
|
13887
|
+
textsVersion = signal(0, ...(ngDevMode ? [{ debugName: "textsVersion" }] : /* istanbul ignore next */ []));
|
|
13888
|
+
/**
|
|
13889
|
+
* The active language's messages, translated once here for the field
|
|
13890
|
+
* below. Without one, a required default culture left empty says so
|
|
13891
|
+
* once the field is touched, changed or submitted (`validation.required`,
|
|
13892
|
+
* the family's message), which also sets `aria-invalid` on it.
|
|
13893
|
+
*/
|
|
13894
|
+
activeErrors = computed(() => {
|
|
13895
|
+
this.textsVersion();
|
|
13896
|
+
const code = this.active()?.code ?? '';
|
|
13897
|
+
const messages = this.cultureErrors()[code];
|
|
13898
|
+
if (messages?.length)
|
|
13899
|
+
return messages.map(m => this.translateService.instant(m));
|
|
13900
|
+
if (this.requiredTone(code)) {
|
|
13901
|
+
return [this.translateService.instant('validation.required')];
|
|
13902
|
+
}
|
|
13903
|
+
return null;
|
|
13904
|
+
}, ...(ngDevMode ? [{ debugName: "activeErrors" }] : /* istanbul ignore next */ []));
|
|
13905
|
+
/** Cultures with a server error, in tab order. */
|
|
13906
|
+
culturesInError = computed(() => this.list().filter(c => this.cultureErrors()[c.code]), ...(ngDevMode ? [{ debugName: "culturesInError" }] : /* istanbul ignore next */ []));
|
|
13907
|
+
/** Cultures in error other than the one on show, for the hint under the tabs. */
|
|
13908
|
+
errorsElsewhere = computed(() => this.culturesInError().filter(c => c.code !== this.active()?.code), ...(ngDevMode ? [{ debugName: "errorsElsewhere" }] : /* istanbul ignore next */ []));
|
|
13909
|
+
/** Changes only when the set of errors does, so a tab the user picked stays picked. */
|
|
13910
|
+
errorSignature = computed(() => {
|
|
13911
|
+
const own = JSON.stringify(this.cultureErrors());
|
|
13912
|
+
if (!this.showTabs())
|
|
13913
|
+
return own;
|
|
13914
|
+
const linked = this.linkedInputs().map(l => Object.entries(l.cultureFlags()).filter(([, f]) => f.serverError).map(([code]) => code));
|
|
13915
|
+
return JSON.stringify([own, linked]);
|
|
13916
|
+
}, ...(ngDevMode ? [{ debugName: "errorSignature" }] : /* istanbul ignore next */ []));
|
|
13917
|
+
/** How many row owners list this input in their `linkedInputs`. */
|
|
13918
|
+
linkedBy = signal(0, ...(ngDevMode ? [{ debugName: "linkedBy" }] : /* istanbul ignore next */ []));
|
|
13919
|
+
/** Danger tone on the default culture: required, empty, and touched, changed or submitted. */
|
|
13920
|
+
requiredTone(code) {
|
|
13921
|
+
return this.required() && code === this.defaultCode() && !this.cleaned()[code] && this.showErrors();
|
|
13922
|
+
}
|
|
13923
|
+
/**
|
|
13924
|
+
* What this input's tab would show for each culture it lists, read by
|
|
13925
|
+
* the input that draws a shared row (`linkedInputs`). Read-only.
|
|
13926
|
+
*/
|
|
13927
|
+
cultureFlags = computed(() => {
|
|
13928
|
+
const text = this.cleaned();
|
|
13929
|
+
const base = this.baseline();
|
|
13930
|
+
const errors = this.cultureErrors();
|
|
13931
|
+
const out = {};
|
|
13932
|
+
for (const c of this.list()) {
|
|
13933
|
+
const serverError = !!errors[c.code];
|
|
13934
|
+
out[c.code] = {
|
|
13935
|
+
error: serverError || this.requiredTone(c.code),
|
|
13936
|
+
serverError,
|
|
13937
|
+
dirty: (text[c.code] ?? '') !== (base[c.code] ?? ''),
|
|
13938
|
+
};
|
|
13939
|
+
}
|
|
13940
|
+
return out;
|
|
13941
|
+
}, ...(ngDevMode ? [{ debugName: "cultureFlags" }] : /* istanbul ignore next */ []));
|
|
13942
|
+
tabItems = computed(() => {
|
|
13943
|
+
const badges = this.tabBadges() ?? {};
|
|
13944
|
+
const flags = [this.cultureFlags(), ...this.linkedInputs().map(l => l.cultureFlags())];
|
|
13945
|
+
return this.list().map(c => {
|
|
13946
|
+
return {
|
|
13947
|
+
value: c.code,
|
|
13948
|
+
labelKey: c.labelKey,
|
|
13949
|
+
badgeKey: badges[c.code] || undefined,
|
|
13950
|
+
tone: flags.some(f => f[c.code]?.error) ? 'danger' : undefined,
|
|
13951
|
+
dirty: flags.some(f => f[c.code]?.dirty),
|
|
13952
|
+
id: this.tabId(c.code),
|
|
13953
|
+
panelId: this.panelId,
|
|
13954
|
+
};
|
|
13955
|
+
});
|
|
13956
|
+
}, ...(ngDevMode ? [{ debugName: "tabItems" }] : /* istanbul ignore next */ []));
|
|
13957
|
+
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
13958
|
+
/** Every culture is disabled: `[disabled]`, a form's `disable()`, or `[efReadOnly]`. */
|
|
13959
|
+
isDisabled = computed(() => this.disabled() || this.cvaDisabled() || (this.readOnlyScope?.readOnly() ?? false), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
13960
|
+
activeReadOnly = computed(() => {
|
|
13961
|
+
const code = this.active()?.code;
|
|
13962
|
+
return !!code && this.readOnlyCultures().includes(code);
|
|
13963
|
+
}, ...(ngDevMode ? [{ debugName: "activeReadOnly" }] : /* istanbul ignore next */ []));
|
|
13964
|
+
activeText = computed(() => this.raw()[this.active()?.code ?? ''] ?? '', ...(ngDevMode ? [{ debugName: "activeText" }] : /* istanbul ignore next */ []));
|
|
13965
|
+
/**
|
|
13966
|
+
* What the counter counts. Rich text against `advisedLength` counts the
|
|
13967
|
+
* characters a reader sees; against `maxLength` alone it counts the stored
|
|
13968
|
+
* HTML, because that is what the server's limit measures.
|
|
13969
|
+
*/
|
|
13970
|
+
activeLength = computed(() => this.effectiveMode() === 'rich' && this.advisedLength() != null
|
|
13971
|
+
? plainTextLength(this.activeText())
|
|
13972
|
+
: this.activeText().length, ...(ngDevMode ? [{ debugName: "activeLength" }] : /* istanbul ignore next */ []));
|
|
13973
|
+
labelId = `${this.uid}-label`;
|
|
13974
|
+
counterLimit = computed(() => this.advisedLength() ?? this.maxLength(), ...(ngDevMode ? [{ debugName: "counterLimit" }] : /* istanbul ignore next */ []));
|
|
13975
|
+
/**
|
|
13976
|
+
* The polite over-limit announcement: empty under the limit, else the
|
|
13977
|
+
* `counterOverKey` text, prefixed with the culture's name when the field
|
|
13978
|
+
* has more than one. Its text changes only with the (culture, over)
|
|
13979
|
+
* pair, so typing past the limit is announced once, and a switch to
|
|
13980
|
+
* another language that is also over is announced and names it.
|
|
13981
|
+
*/
|
|
13982
|
+
overMessage = computed(() => {
|
|
13983
|
+
this.textsVersion();
|
|
13984
|
+
const key = this.counterOverKey();
|
|
13985
|
+
const limit = this.counterLimit();
|
|
13986
|
+
const culture = this.active();
|
|
13987
|
+
if (!key || limit == null || !culture || this.activeLength() <= limit)
|
|
13988
|
+
return '';
|
|
13989
|
+
const message = this.translateService.instant(key, { max: limit });
|
|
13990
|
+
if (this.list().length < 2)
|
|
13991
|
+
return message;
|
|
13992
|
+
return this.translateService.instant('ef_localized_input_error_in_culture', {
|
|
13993
|
+
culture: this.translateService.instant(culture.labelKey),
|
|
13994
|
+
message,
|
|
13995
|
+
});
|
|
13996
|
+
}, ...(ngDevMode ? [{ debugName: "overMessage" }] : /* istanbul ignore next */ []));
|
|
13997
|
+
panelId = `${this.uid}-panel`;
|
|
13998
|
+
counterId = `${this.uid}-counter`;
|
|
13999
|
+
/** The culture's name, read with a field that has no tab row. */
|
|
14000
|
+
cultureNameId = `${this.uid}-culture`;
|
|
14001
|
+
/** Extra `aria-describedby` ids for the active field: the counter, and
|
|
14002
|
+
* the culture's name when no tab says it. */
|
|
14003
|
+
describedByExtra = computed(() => [this.counterLimit() ? this.counterId : null, this.showTabs() ? null : this.cultureNameId].filter(Boolean).join(' '), ...(ngDevMode ? [{ debugName: "describedByExtra" }] : /* istanbul ignore next */ []));
|
|
14004
|
+
onChange = () => { };
|
|
14005
|
+
touched = () => { };
|
|
14006
|
+
constructor() {
|
|
14007
|
+
if (this.ngControl)
|
|
14008
|
+
this.ngControl.valueAccessor = this;
|
|
14009
|
+
merge(this.translateService.onLangChange, this.translateService.onTranslationChange)
|
|
14010
|
+
.pipe(takeUntilDestroyed())
|
|
14011
|
+
.subscribe(() => this.textsVersion.update(v => v + 1));
|
|
14012
|
+
// The validator reads required and the default culture, which can
|
|
14013
|
+
// change without the value changing (tenant cultures arrive late).
|
|
14014
|
+
effect(() => {
|
|
14015
|
+
this.required();
|
|
14016
|
+
this.defaultCode();
|
|
14017
|
+
untracked(() => this.ngControl?.control?.updateValueAndValidity({ emitEvent: false }));
|
|
14018
|
+
});
|
|
14019
|
+
// The parent always reads the culture on show, never null or one the
|
|
14020
|
+
// row does not list. Only the input that draws tabs writes it back:
|
|
14021
|
+
// two inputs with different lists would otherwise keep overwriting
|
|
14022
|
+
// each other.
|
|
14023
|
+
effect(() => {
|
|
14024
|
+
const code = this.activeCode();
|
|
14025
|
+
if (this.showTabs() && code && this.activeCulture() !== code)
|
|
14026
|
+
this.activeCulture.set(code);
|
|
14027
|
+
});
|
|
14028
|
+
// A row owner tells the inputs it links, so they leave the error jump to it.
|
|
14029
|
+
effect(onCleanup => {
|
|
14030
|
+
const linked = this.showTabs() ? this.linkedInputs() : [];
|
|
14031
|
+
for (const l of linked)
|
|
14032
|
+
l.linkedBy.update(n => n + 1);
|
|
14033
|
+
onCleanup(() => {
|
|
14034
|
+
for (const l of linked)
|
|
14035
|
+
l.linkedBy.update(n => n - 1);
|
|
14036
|
+
});
|
|
14037
|
+
});
|
|
14038
|
+
// A new set of server errors brings the first language in error into
|
|
14039
|
+
// view, through activeCulture. The row owner picks it in its own tab
|
|
14040
|
+
// order across itself and its linked inputs; an input without tabs
|
|
14041
|
+
// that no owner links moves it itself.
|
|
14042
|
+
effect(() => {
|
|
14043
|
+
this.errorSignature();
|
|
14044
|
+
untracked(() => {
|
|
14045
|
+
if (!this.showTabs() && this.linkedBy() > 0)
|
|
14046
|
+
return;
|
|
14047
|
+
const linked = this.showTabs() ? this.linkedInputs() : [];
|
|
14048
|
+
const first = this.list().find(c => this.cultureErrors()[c.code] || linked.some(l => l.cultureFlags()[c.code]?.serverError));
|
|
14049
|
+
if (first)
|
|
14050
|
+
this.activeCulture.set(first.code);
|
|
14051
|
+
});
|
|
14052
|
+
});
|
|
14053
|
+
effect(() => {
|
|
14054
|
+
if (this.mode() !== 'rich')
|
|
14055
|
+
return;
|
|
14056
|
+
if (!this.richLoader) {
|
|
14057
|
+
if (isDevMode()) {
|
|
14058
|
+
console.error(`ef-localized-input "${untracked(this.name)}": mode="rich" needs provideEfRichText() from @elasticias/ui/rich-text; showing the html source instead.`);
|
|
14059
|
+
}
|
|
14060
|
+
return;
|
|
14061
|
+
}
|
|
14062
|
+
if (untracked(this.richType) || untracked(this.richFailed))
|
|
14063
|
+
return;
|
|
14064
|
+
this.richLoader()
|
|
14065
|
+
.then(t => this.richType.set(t))
|
|
14066
|
+
.catch((err) => {
|
|
14067
|
+
if (isDevMode()) {
|
|
14068
|
+
console.error(`ef-localized-input "${untracked(this.name)}": could not load the rich text editor; showing the html source instead.`, err);
|
|
14069
|
+
}
|
|
14070
|
+
this.richFailed.set(true);
|
|
14071
|
+
});
|
|
14072
|
+
});
|
|
14073
|
+
// One editor per active culture: the @for recreates the host on a
|
|
14074
|
+
// culture switch, and the editor is created in it with live bindings.
|
|
14075
|
+
effect(() => {
|
|
14076
|
+
const vcr = this.richHost();
|
|
14077
|
+
const type = this.richType();
|
|
14078
|
+
const code = this.activeCode();
|
|
14079
|
+
if (!vcr || !type || !code || this.effectiveMode() !== 'rich')
|
|
14080
|
+
return;
|
|
14081
|
+
untracked(() => this.createRich(vcr, type, code));
|
|
14082
|
+
});
|
|
14083
|
+
}
|
|
14084
|
+
createRich(vcr, type, code) {
|
|
14085
|
+
vcr.clear();
|
|
14086
|
+
this.richRef = null;
|
|
14087
|
+
const culture = () => this.list().find(c => c.code === code);
|
|
14088
|
+
const readOnly = () => this.readOnlyCultures().includes(code);
|
|
14089
|
+
this.richRef = vcr.createComponent(type, {
|
|
14090
|
+
bindings: [
|
|
14091
|
+
inputBinding('value', () => this.raw()[code] ?? ''),
|
|
14092
|
+
inputBinding('name', () => `${this.name()}.${code}`),
|
|
14093
|
+
inputBinding('inputId', () => this.controlId(code)),
|
|
14094
|
+
inputBinding('dir', () => culture()?.dir ?? null),
|
|
14095
|
+
inputBinding('lang', () => code),
|
|
14096
|
+
inputBinding('rows', () => this.rows()),
|
|
14097
|
+
inputBinding('labelledBy', () => (this.labelKey() ? this.labelId : null)),
|
|
14098
|
+
inputBinding('required', () => this.required() && code === this.defaultCode()),
|
|
14099
|
+
inputBinding('errors', () => this.activeErrors()),
|
|
14100
|
+
inputBinding('disabled', () => this.isDisabled()),
|
|
14101
|
+
inputBinding('readonly', readOnly),
|
|
14102
|
+
inputBinding('placeholderKey', () => this.placeholderKey()),
|
|
14103
|
+
inputBinding('hintKey', () => (readOnly() ? 'ef_localized_input_read_only_hint' : this.hintKey())),
|
|
14104
|
+
inputBinding('describedByExtra', () => this.describedByExtra()),
|
|
14105
|
+
outputBinding('valueChangeEvent', v => this.setText(code, v)),
|
|
14106
|
+
],
|
|
14107
|
+
});
|
|
14108
|
+
}
|
|
14109
|
+
/**
|
|
14110
|
+
* Inserts `text` (a `{{token}}`) into the culture on show, at the caret,
|
|
14111
|
+
* replacing a selection, or at the end when its field was never entered
|
|
14112
|
+
* or was just recreated by a culture switch; then emits the whole object
|
|
14113
|
+
* once, as typing would. Returns false, and changes nothing, when the
|
|
14114
|
+
* field is disabled, the culture is read-only, the result would pass
|
|
14115
|
+
* `maxLength`, there is no active field (no culture listed), or the
|
|
14116
|
+
* field is the rich editor (which has no insert yet). The field says
|
|
14117
|
+
* nothing itself on a refusal, so the caller should tell the user (a
|
|
14118
|
+
* toast such as "the variable would pass the length limit"); otherwise
|
|
14119
|
+
* a click on a variable silently does nothing.
|
|
14120
|
+
*
|
|
14121
|
+
* On a phone, the focus that follows the insert opens the keyboard.
|
|
14122
|
+
*
|
|
14123
|
+
* A caller tracking "the field last focused" should ignore focus on the
|
|
14124
|
+
* culture tabs (`[role="tab"]`): picking a language is not choosing where
|
|
14125
|
+
* the token goes.
|
|
14126
|
+
*/
|
|
14127
|
+
insertAtCaret(text) {
|
|
14128
|
+
const code = this.activeCode();
|
|
14129
|
+
if (!code || this.isDisabled() || this.activeReadOnly())
|
|
14130
|
+
return false;
|
|
14131
|
+
const target = (this.effectiveMode() === 'rich' ? this.richRef?.instance : (this.textField() ?? this.areaField()));
|
|
14132
|
+
if (typeof target?.insertAtCursor !== 'function')
|
|
14133
|
+
return false;
|
|
14134
|
+
const before = this.raw()[code] ?? '';
|
|
14135
|
+
target.insertAtCursor(text);
|
|
14136
|
+
return (this.raw()[code] ?? '') !== before;
|
|
14137
|
+
}
|
|
14138
|
+
tabId(code) {
|
|
14139
|
+
return `${this.uid}-tab-${code}`;
|
|
14140
|
+
}
|
|
14141
|
+
controlId(code) {
|
|
14142
|
+
return `${this.uid}-${code}`;
|
|
14143
|
+
}
|
|
14144
|
+
select(code) {
|
|
14145
|
+
this.activeCulture.set(code);
|
|
14146
|
+
}
|
|
14147
|
+
setText(code, text) {
|
|
14148
|
+
this.raw.update(r => ({ ...r, [code]: text }));
|
|
14149
|
+
const out = this.cleaned();
|
|
14150
|
+
this.lastEmitted = out;
|
|
14151
|
+
this.interacted.set(true);
|
|
14152
|
+
this.onChange(out);
|
|
14153
|
+
this.valueChangeEvent.emit(out);
|
|
14154
|
+
}
|
|
14155
|
+
/** Touched when focus leaves the whole field, not when it moves from a tab to the text. */
|
|
14156
|
+
onFocusOut(event) {
|
|
14157
|
+
const next = event.relatedTarget;
|
|
14158
|
+
if (next instanceof Node && this.host.nativeElement.contains(next))
|
|
14159
|
+
return;
|
|
14160
|
+
this.interacted.set(true);
|
|
14161
|
+
this.touched();
|
|
14162
|
+
}
|
|
14163
|
+
ngDoCheck() {
|
|
14164
|
+
// A form's markAllAsTouched() or submit does not reach the accessor,
|
|
14165
|
+
// so the control's state is read each time the parent is checked.
|
|
14166
|
+
const c = this.ngControl;
|
|
14167
|
+
this.formTouched.set(!!(c?.touched || c?.dirty || this.parentForm?.submitted));
|
|
14168
|
+
}
|
|
14169
|
+
/** `{ required: true }` while a required field's default culture has no text. */
|
|
14170
|
+
validateRequired(control) {
|
|
14171
|
+
if (!this.required())
|
|
14172
|
+
return null;
|
|
14173
|
+
return cleanLocalizedText(control.value)[this.defaultCode()] ? null : { required: true };
|
|
14174
|
+
}
|
|
14175
|
+
writeValue(v) {
|
|
14176
|
+
this.raw.set({ ...(v ?? {}) });
|
|
14177
|
+
this.baseline.set(cleanLocalizedText(v));
|
|
14178
|
+
}
|
|
14179
|
+
registerOnChange(fn) {
|
|
14180
|
+
this.onChange = fn;
|
|
14181
|
+
}
|
|
14182
|
+
registerOnTouched(fn) {
|
|
14183
|
+
this.touched = fn;
|
|
14184
|
+
}
|
|
14185
|
+
setDisabledState(disabled) {
|
|
14186
|
+
this.cvaDisabled.set(disabled);
|
|
14187
|
+
}
|
|
14188
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfLocalizedInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14189
|
+
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 }, activeCulture: { classPropertyName: "activeCulture", publicName: "activeCulture", isSignal: true, isRequired: false, transformFunction: null }, showTabs: { classPropertyName: "showTabs", publicName: "showTabs", isSignal: true, isRequired: false, transformFunction: null }, linkedInputs: { classPropertyName: "linkedInputs", publicName: "linkedInputs", isSignal: true, isRequired: false, transformFunction: null }, preview: { classPropertyName: "preview", publicName: "preview", isSignal: true, isRequired: false, transformFunction: null }, tabBadges: { classPropertyName: "tabBadges", publicName: "tabBadges", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeCulture: "activeCultureChange", valueChangeEvent: "valueChangeEvent" }, host: { listeners: { "focusout": "onFocusOut($event)" } }, providers: [
|
|
14190
|
+
{
|
|
14191
|
+
// A form's required validator reads an empty object as filled (it
|
|
14192
|
+
// checks length), so the field validates its default culture
|
|
14193
|
+
// itself. The component is looked up when the form validates, not
|
|
14194
|
+
// here: it injects NgControl, which injects these validators.
|
|
14195
|
+
provide: NG_VALIDATORS,
|
|
14196
|
+
multi: true,
|
|
14197
|
+
useFactory: () => {
|
|
14198
|
+
const injector = inject(Injector);
|
|
14199
|
+
return (control) => injector.get(EfLocalizedInputComponent).validateRequired(control);
|
|
14200
|
+
},
|
|
14201
|
+
},
|
|
14202
|
+
], viewQueries: [{ propertyName: "richHost", first: true, predicate: ["richHost"], descendants: true, read: ViewContainerRef, isSignal: true }, { propertyName: "textField", first: true, predicate: EfInputTextComponent, descendants: true, isSignal: true }, { propertyName: "areaField", first: true, predicate: EfTextareaComponent, descendants: true, 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 @if (showTabs()) {\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 }\n</div>\n\n<!-- Without a tab row the panel is a plain group: a tabpanel would point\n at a tab that does not exist. -->\n<div\n class=\"eli-panel\"\n [id]=\"panelId\"\n [attr.role]=\"showTabs() ? 'tabpanel' : null\"\n [attr.aria-labelledby]=\"showTabs() ? tabId(active()?.code ?? '') : null\"\n>\n @if (!showTabs()) {\n @if (active(); as c) {\n <span class=\"eli-sr\" [id]=\"cultureNameId\">{{ c.labelKey | translate }}</span>\n }\n }\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]=\"describedByExtra()\"\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]=\"describedByExtra()\"\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]=\"describedByExtra()\"\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 @if (advisedLength() !== null && advisedLength() !== undefined) {\n <!-- Advice, worded so (\"5123 / 5000 conseill\u00E9s\"), so it does not\n read as a cap. Words around the numbers: the text takes the\n page's direction, and an rtl reader meets the count first. -->\n <span data-counter-text>{{ 'ef_localized_input_counter_advised' | translate: { count: activeLength(), max: limit } }}</span>\n } @else {\n <bdi dir=\"ltr\">{{ activeLength() }} / {{ limit }}</bdi>\n }\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\">{{ overMessage() }}</span>\n }\n }\n\n @if (effectiveMode() === 'html' && preview() && 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 }}</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,.eli-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 });
|
|
14203
|
+
}
|
|
14204
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfLocalizedInputComponent, decorators: [{
|
|
14205
|
+
type: Component,
|
|
14206
|
+
args: [{ selector: 'ef-localized-input', standalone: true, imports: [TranslateModule, EfLabelComponent, EfTabsComponent, EfInputTextComponent, EfTextareaComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(focusout)': 'onFocusOut($event)' }, providers: [
|
|
14207
|
+
{
|
|
14208
|
+
// A form's required validator reads an empty object as filled (it
|
|
14209
|
+
// checks length), so the field validates its default culture
|
|
14210
|
+
// itself. The component is looked up when the form validates, not
|
|
14211
|
+
// here: it injects NgControl, which injects these validators.
|
|
14212
|
+
provide: NG_VALIDATORS,
|
|
14213
|
+
multi: true,
|
|
14214
|
+
useFactory: () => {
|
|
14215
|
+
const injector = inject(Injector);
|
|
14216
|
+
return (control) => injector.get(EfLocalizedInputComponent).validateRequired(control);
|
|
14217
|
+
},
|
|
14218
|
+
},
|
|
14219
|
+
], 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 @if (showTabs()) {\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 }\n</div>\n\n<!-- Without a tab row the panel is a plain group: a tabpanel would point\n at a tab that does not exist. -->\n<div\n class=\"eli-panel\"\n [id]=\"panelId\"\n [attr.role]=\"showTabs() ? 'tabpanel' : null\"\n [attr.aria-labelledby]=\"showTabs() ? tabId(active()?.code ?? '') : null\"\n>\n @if (!showTabs()) {\n @if (active(); as c) {\n <span class=\"eli-sr\" [id]=\"cultureNameId\">{{ c.labelKey | translate }}</span>\n }\n }\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]=\"describedByExtra()\"\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]=\"describedByExtra()\"\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]=\"describedByExtra()\"\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 @if (advisedLength() !== null && advisedLength() !== undefined) {\n <!-- Advice, worded so (\"5123 / 5000 conseill\u00E9s\"), so it does not\n read as a cap. Words around the numbers: the text takes the\n page's direction, and an rtl reader meets the count first. -->\n <span data-counter-text>{{ 'ef_localized_input_counter_advised' | translate: { count: activeLength(), max: limit } }}</span>\n } @else {\n <bdi dir=\"ltr\">{{ activeLength() }} / {{ limit }}</bdi>\n }\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\">{{ overMessage() }}</span>\n }\n }\n\n @if (effectiveMode() === 'html' && preview() && 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 }}</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,.eli-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"] }]
|
|
14220
|
+
}], 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 }] }], activeCulture: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeCulture", required: false }] }, { type: i0.Output, args: ["activeCultureChange"] }], showTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTabs", required: false }] }], linkedInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkedInputs", required: false }] }], preview: [{ type: i0.Input, args: [{ isSignal: true, alias: "preview", required: false }] }], tabBadges: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabBadges", required: false }] }], valueChangeEvent: [{ type: i0.Output, args: ["valueChangeEvent"] }], richHost: [{ type: i0.ViewChild, args: ['richHost', { ...{ read: ViewContainerRef }, isSignal: true }] }], textField: [{ type: i0.ViewChild, args: [i0.forwardRef(() => EfInputTextComponent), { isSignal: true }] }], areaField: [{ type: i0.ViewChild, args: [i0.forwardRef(() => EfTextareaComponent), { isSignal: true }] }] } });
|
|
14221
|
+
|
|
14222
|
+
/**
|
|
14223
|
+
* The content cultures the SEO fields start from, shared with `ef-localized-input`.
|
|
14224
|
+
* @deprecated use `EF_DEFAULT_CONTENT_CULTURES` from `@elasticias/core`.
|
|
14225
|
+
*/
|
|
14226
|
+
const EF_SEO_DEFAULT_LOCALES = EF_DEFAULT_CONTENT_CULTURES;
|
|
14227
|
+
/**
|
|
14228
|
+
* SEO title and description in every content language, each an
|
|
14229
|
+
* `ef-localized-input` with a counter against what a search engine shows
|
|
14230
|
+
* (60 / 160, read out to screen readers), capped at the server's limits
|
|
14231
|
+
* (256 / 512), and the shared `ef-seo-preview` of the default language's
|
|
14232
|
+
* result with the entity's own title and description as the fallback the shop
|
|
14233
|
+
* uses. Card-less: each screen wraps it in its own `ef-card`.
|
|
14234
|
+
*
|
|
14235
|
+
* The controls are named `seoTitle` and `seoDescription`, so under
|
|
14236
|
+
* `[efServerErrors]` a `seoTitle.ar` error lands on the Arabic tab and shows
|
|
14237
|
+
* under its field. Languages come from `EF_CONTENT_CULTURES` unless `cultures`
|
|
14238
|
+
* is set.
|
|
14239
|
+
*
|
|
14240
|
+
* i18n keys the app's bundles must define, besides those of
|
|
14241
|
+
* `ef-localized-input`:
|
|
14242
|
+
*
|
|
14243
|
+
* | key | fr | ar | en |
|
|
14244
|
+
* |---|---|---|---|
|
|
14245
|
+
* | `ef_seo_fields_title_label` | Titre SEO | عنوان SEO | SEO title |
|
|
14246
|
+
* | `ef_seo_fields_description_label` | Description SEO | وصف SEO | SEO description |
|
|
14247
|
+
* | `ef_seo_fields_counter_hint` | Au-delà de {{max}} caractères, Google coupe le texte. | بعد {{max}} حرفا، يقتطع Google النص. | Past {{max}} characters, Google cuts the text. |
|
|
14248
|
+
* | `ef_seo_fields_over_hint` | Le texte dépasse {{max}} caractères. | النص يتجاوز {{max}} حرفا. | The text is over {{max}} characters. |
|
|
14249
|
+
* | `ef_seo_fields_preview` | Aperçu dans Google | المعاينة في Google | Google preview |
|
|
14250
|
+
* | `ef_seo_fields_fallback_hint` | Vide : la boutique reprend le titre et la description. | فارغ: يستخدم المتجر العنوان والوصف. | Empty: the shop uses the title and description. |
|
|
13473
14251
|
*
|
|
13474
14252
|
* ```html
|
|
13475
|
-
* <ef-seo-fields [seoTitle]="p.seoTitle"
|
|
14253
|
+
* <ef-seo-fields [seoTitle]="p.seoTitle" [seoDescription]="p.seoDescription"
|
|
14254
|
+
* [fallbackTitle]="p.title" [fallbackDescription]="p.description"
|
|
14255
|
+
* [previewUrl]="url()" (seoChange)="patch($event)" />
|
|
13476
14256
|
* ```
|
|
13477
14257
|
*/
|
|
13478
14258
|
class EfSeoFieldsComponent {
|
|
13479
|
-
seoTitle = input(
|
|
13480
|
-
seoDescription = input(
|
|
13481
|
-
seoTitleTranslations = input({}, ...(ngDevMode ? [{ debugName: "seoTitleTranslations" }] : /* istanbul ignore next */ []));
|
|
13482
|
-
seoDescriptionTranslations = input({}, ...(ngDevMode ? [{ debugName: "seoDescriptionTranslations" }] : /* istanbul ignore next */ []));
|
|
14259
|
+
seoTitle = input(null, ...(ngDevMode ? [{ debugName: "seoTitle" }] : /* istanbul ignore next */ []));
|
|
14260
|
+
seoDescription = input(null, ...(ngDevMode ? [{ debugName: "seoDescription" }] : /* istanbul ignore next */ []));
|
|
13483
14261
|
/** The entity's own texts, which the shop uses when SEO is empty. */
|
|
13484
|
-
fallbackTitle = input(
|
|
13485
|
-
fallbackDescription = input(
|
|
14262
|
+
fallbackTitle = input(null, ...(ngDevMode ? [{ debugName: "fallbackTitle" }] : /* istanbul ignore next */ []));
|
|
14263
|
+
fallbackDescription = input(null, ...(ngDevMode ? [{ debugName: "fallbackDescription" }] : /* istanbul ignore next */ []));
|
|
13486
14264
|
/** The page address shown in the preview, computed by the app. */
|
|
13487
14265
|
previewUrl = input('', ...(ngDevMode ? [{ debugName: "previewUrl" }] : /* istanbul ignore next */ []));
|
|
13488
14266
|
titleMax = input(256, ...(ngDevMode ? [{ debugName: "titleMax" }] : /* istanbul ignore next */ []));
|
|
13489
14267
|
descriptionMax = input(512, ...(ngDevMode ? [{ debugName: "descriptionMax" }] : /* istanbul ignore next */ []));
|
|
13490
14268
|
titleAdvised = input(60, ...(ngDevMode ? [{ debugName: "titleAdvised" }] : /* istanbul ignore next */ []));
|
|
13491
14269
|
descriptionAdvised = input(160, ...(ngDevMode ? [{ debugName: "descriptionAdvised" }] : /* istanbul ignore next */ []));
|
|
13492
|
-
|
|
14270
|
+
/** Overrides the `EF_CONTENT_CULTURES` list for both fields. */
|
|
14271
|
+
cultures = input(undefined, ...(ngDevMode ? [{ debugName: "cultures" }] : /* istanbul ignore next */ []));
|
|
13493
14272
|
seoChange = output();
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
const
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
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 });
|
|
14273
|
+
contentCultures = inject(EF_CONTENT_CULTURES);
|
|
14274
|
+
/** The language the preview shows: the tenant's default if listed, else the first listed. */
|
|
14275
|
+
previewCode = computed(() => {
|
|
14276
|
+
const list = this.cultures() ?? this.contentCultures().cultures;
|
|
14277
|
+
const preferred = this.contentCultures().defaultCulture;
|
|
14278
|
+
return list.some(c => c.code === preferred) ? preferred : (list[0]?.code ?? preferred);
|
|
14279
|
+
}, ...(ngDevMode ? [{ debugName: "previewCode" }] : /* istanbul ignore next */ []));
|
|
14280
|
+
previewTitle = computed(() => this.shown(this.seoTitle(), this.fallbackTitle()), ...(ngDevMode ? [{ debugName: "previewTitle" }] : /* istanbul ignore next */ []));
|
|
14281
|
+
previewDescription = computed(() => this.shown(this.seoDescription(), this.fallbackDescription()), ...(ngDevMode ? [{ debugName: "previewDescription" }] : /* istanbul ignore next */ []));
|
|
14282
|
+
shown(seo, fallback) {
|
|
14283
|
+
const code = this.previewCode();
|
|
14284
|
+
return (seo?.[code]?.trim() || fallback?.[code]?.trim() || '');
|
|
13523
14285
|
}
|
|
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
14286
|
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.
|
|
14287
|
+
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", "activeCulture", "showTabs", "linkedInputs", "preview", "tabBadges"], outputs: ["activeCultureChange", "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
14288
|
}
|
|
13529
14289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSeoFieldsComponent, decorators: [{
|
|
13530
14290
|
type: Component,
|
|
13531
|
-
args: [{ selector: 'ef-seo-fields', standalone: true, imports: [TranslateModule,
|
|
13532
|
-
}], propDecorators: { seoTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoTitle", required: false }] }], seoDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "seoDescription", required: false }] }],
|
|
14291
|
+
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"] }]
|
|
14292
|
+
}], 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
14293
|
|
|
13534
14294
|
/**
|
|
13535
14295
|
* A button that opens the native file picker and emits the chosen `File`.
|
|
@@ -14439,5 +15199,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
14439
15199
|
* Generated bundle index. Do not edit.
|
|
14440
15200
|
*/
|
|
14441
15201
|
|
|
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 };
|
|
15202
|
+
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, auditEntityKeySegment, buildChartConfig, defaultUnitPrice, getColumnAlignment, mergeColumnDefaults, plainTextLength, resolveEfStatusColor };
|
|
14443
15203
|
//# sourceMappingURL=elasticias-ui.mjs.map
|