@elasticias/ui 1.0.39 → 1.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Pipe, input, Component, output, booleanAttribute, Input, Directive, signal, computed, inject, EventEmitter, Injector, forwardRef, Output, ContentChild, HostBinding, ChangeDetectionStrategy, ElementRef, LOCALE_ID, ViewContainerRef, DestroyRef, ChangeDetectorRef, effect, HostListener, ViewChild, model, InjectionToken, TemplateRef, ContentChildren, viewChild, contentChild, ViewEncapsulation, afterNextRender, ViewChildren, untracked, PLATFORM_ID } from '@angular/core';
3
- import { NumberUtils, UuidUtils, StorageUtils } from '@elasticias/utils';
3
+ import { NumberUtils, UuidUtils } 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';
@@ -22,7 +22,7 @@ import * as i1$6 from 'primeng/dialog';
22
22
  import { DialogModule } from 'primeng/dialog';
23
23
  import { SCREEN_REF_DATA_SERVICE, ScreenStateEnum } from '@elasticias/screens';
24
24
  import { Permissions } from '@elasticias/types';
25
- import { EfShortcutService, EfViewportService, formatShortcut, EfThemeConfigService, EF_BUILD_INFO, EfPermissionService, EfActiveModuleService, EF_SESSION, EfToastService } from '@elasticias/core';
25
+ import { EfShortcutService, EfViewportService, EF_PREFERENCE_STORE, formatShortcut, efTablePrefsKey, EfThemeConfigService, EF_BUILD_INFO, EfPermissionService, EfActiveModuleService, EF_SESSION, EF_SIDE_COLLAPSED_KEY, EfToastService } from '@elasticias/core';
26
26
  import * as i1$7 from 'primeng/fieldset';
27
27
  import { FieldsetModule } from 'primeng/fieldset';
28
28
  import * as i2$2 from 'primeng/accordion';
@@ -570,6 +570,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
570
570
  args: [{ transform: booleanAttribute }]
571
571
  }] } });
572
572
 
573
+ /**
574
+ * Scopes a read-only state to every descendant `ef-*` form control.
575
+ *
576
+ * Drop it on the container that wraps a form and bind whether the current
577
+ * user lacks write access; descendant controls fold it into their effective
578
+ * disabled state, so no per-field `[disabled]` binding is needed:
579
+ *
580
+ * ```html
581
+ * <div class="detail-grid" [efReadOnly]="!canWrite()">
582
+ * <ef-input-text name="companyName" ... />
583
+ * <ef-select name="clientType" ... />
584
+ * </div>
585
+ * ```
586
+ *
587
+ * The input is a signal, so toggling it re-enables the controls under
588
+ * zoneless change detection (controls read it through `isDisabled`).
589
+ */
590
+ class EfReadOnlyDirective {
591
+ /** When true, every descendant ef-* control renders disabled. */
592
+ readOnly = input(false, { ...(ngDevMode ? { debugName: "readOnly" } : /* istanbul ignore next */ {}), alias: 'efReadOnly', transform: booleanAttribute });
593
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfReadOnlyDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
594
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: EfReadOnlyDirective, isStandalone: true, selector: "[efReadOnly]", inputs: { readOnly: { classPropertyName: "readOnly", publicName: "efReadOnly", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
595
+ }
596
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfReadOnlyDirective, decorators: [{
597
+ type: Directive,
598
+ args: [{
599
+ selector: '[efReadOnly]',
600
+ standalone: true,
601
+ }]
602
+ }], propDecorators: { readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "efReadOnly", required: false }] }] } });
603
+
573
604
  /**
574
605
  * Scopes a map of server/validation errors to its descendant `ef-*` form
575
606
  * controls, which auto-link by their `name` — no per-field `[errors]`
@@ -702,14 +733,16 @@ class AbstractEfFormControl {
702
733
  required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
703
734
  readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
704
735
  /** The `[disabled]` input. The **effective** disabled (input OR
705
- * form-driven via CVA) is {@link isDisabled} — bind that in templates. */
736
+ * form-driven via CVA, or an enclosing `[efReadOnly]` scope) is {@link isDisabled} — bind that in templates. */
706
737
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
707
738
  /** Form-driven disabled set by CVA `setDisabledState`, merged with the
708
739
  * `[disabled]` input. Writable signal (the input itself is read-only). */
709
740
  _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
710
741
  /** Effective disabled state — the `[disabled]` input OR a reactive-form
711
742
  * `disable()`. Templates/logic should use this, not `disabled()`. */
712
- isDisabled = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
743
+ isDisabled = computed(() => this.disabled() || this._cvaDisabled() || (this.readOnlyScope?.readOnly() ?? false), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
744
+ /** Optional enclosing `[efReadOnly]` scope — disables the control when set. */
745
+ readOnlyScope = inject(EfReadOnlyDirective, { optional: true });
713
746
  /** CVA hook for subclasses: `setDisabledState(d) { this.updateDisabledState(d); }`. */
714
747
  updateDisabledState(disabled) {
715
748
  this._cvaDisabled.set(disabled);
@@ -2604,6 +2637,12 @@ class EfQuantityStepperComponent {
2604
2637
  * When present, it specifies that the component should be disabled
2605
2638
  */
2606
2639
  disabled = false;
2640
+ /** Optional enclosing `[efReadOnly]` scope. */
2641
+ readOnlyScope = inject(EfReadOnlyDirective, { optional: true });
2642
+ /** Effective disabled: the input / CVA state OR a read-only scope. */
2643
+ get isDisabled() {
2644
+ return this.disabled || (this.readOnlyScope?.readOnly() ?? false);
2645
+ }
2607
2646
  /**
2608
2647
  * Callback to invoke when the value changes
2609
2648
  */
@@ -2675,7 +2714,7 @@ class EfQuantityStepperComponent {
2675
2714
  useExisting: forwardRef(() => EfQuantityStepperComponent),
2676
2715
  multi: true,
2677
2716
  },
2678
- ], ngImport: i0, template: "<div\n class=\"flex h-12 items-center text-center justify-center gap-2 rounded-full !border !border-gray-200 bg-gray-100 dark:bg-slate-500 p-2\"\n>\n <ef-inputnumber\n [(ngModel)]=\"value\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [locale]=\"locale\"\n [disabled]=\"disabled\"\n [showButtons]=\"true\"\n buttonLayout=\"horizontal\"\n decrementButtonIcon=\"-\"\n incrementButtonIcon=\"+\"\n [pt]=\"ptConfig\"\n (ngModelChange)=\"handleValueChange($event)\"\n (blurEvent)=\"handleBlur()\"\n >\n </ef-inputnumber>\n</div>\n", styles: [""], dependencies: [{ 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: "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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2717
+ ], ngImport: i0, template: "<div\n class=\"flex h-12 items-center text-center justify-center gap-2 rounded-full !border !border-gray-200 bg-gray-100 dark:bg-slate-500 p-2\"\n>\n <ef-inputnumber\n [(ngModel)]=\"value\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [locale]=\"locale\"\n [disabled]=\"isDisabled\"\n [showButtons]=\"true\"\n buttonLayout=\"horizontal\"\n decrementButtonIcon=\"-\"\n incrementButtonIcon=\"+\"\n [pt]=\"ptConfig\"\n (ngModelChange)=\"handleValueChange($event)\"\n (blurEvent)=\"handleBlur()\"\n >\n </ef-inputnumber>\n</div>\n", styles: [""], dependencies: [{ 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: "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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2679
2718
  }
2680
2719
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfQuantityStepperComponent, decorators: [{
2681
2720
  type: Component,
@@ -2685,7 +2724,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
2685
2724
  useExisting: forwardRef(() => EfQuantityStepperComponent),
2686
2725
  multi: true,
2687
2726
  },
2688
- ], template: "<div\n class=\"flex h-12 items-center text-center justify-center gap-2 rounded-full !border !border-gray-200 bg-gray-100 dark:bg-slate-500 p-2\"\n>\n <ef-inputnumber\n [(ngModel)]=\"value\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [locale]=\"locale\"\n [disabled]=\"disabled\"\n [showButtons]=\"true\"\n buttonLayout=\"horizontal\"\n decrementButtonIcon=\"-\"\n incrementButtonIcon=\"+\"\n [pt]=\"ptConfig\"\n (ngModelChange)=\"handleValueChange($event)\"\n (blurEvent)=\"handleBlur()\"\n >\n </ef-inputnumber>\n</div>\n" }]
2727
+ ], template: "<div\n class=\"flex h-12 items-center text-center justify-center gap-2 rounded-full !border !border-gray-200 bg-gray-100 dark:bg-slate-500 p-2\"\n>\n <ef-inputnumber\n [(ngModel)]=\"value\"\n [min]=\"min\"\n [max]=\"max\"\n [step]=\"step\"\n [minFractionDigits]=\"minFractionDigits\"\n [maxFractionDigits]=\"maxFractionDigits\"\n [locale]=\"locale\"\n [disabled]=\"isDisabled\"\n [showButtons]=\"true\"\n buttonLayout=\"horizontal\"\n decrementButtonIcon=\"-\"\n incrementButtonIcon=\"+\"\n [pt]=\"ptConfig\"\n (ngModelChange)=\"handleValueChange($event)\"\n (blurEvent)=\"handleBlur()\"\n >\n </ef-inputnumber>\n</div>\n" }]
2689
2728
  }], propDecorators: { value: [{
2690
2729
  type: Input
2691
2730
  }], min: [{
@@ -4280,8 +4319,12 @@ class EfRowActionsComponent {
4280
4319
  return true;
4281
4320
  if (action.visible === false)
4282
4321
  return false;
4283
- if (action.permission && ctx && !ctx.isGranted(action.permission))
4284
- return false;
4322
+ if (action.permission && ctx) {
4323
+ // Array.isArray does not narrow a ReadonlyArray out of the union.
4324
+ const required = typeof action.permission === 'string' ? [action.permission] : action.permission;
4325
+ if (!required.every(p => ctx.isGranted(p)))
4326
+ return false;
4327
+ }
4285
4328
  return true;
4286
4329
  }
4287
4330
  /** Stable trackBy across renders. */
@@ -4528,6 +4571,7 @@ class EfDataCardComponent {
4528
4571
  become a list instead, driven by the same column config —
4529
4572
  each column declares (or is given) a role. */
4530
4573
  viewport = inject(EfViewportService);
4574
+ prefs = inject(EF_PREFERENCE_STORE);
4531
4575
  configuredMobileLayout = inject(EF_DATA_CARD_MOBILE_LAYOUT, { optional: true });
4532
4576
  isMobile = this.viewport.isMobile;
4533
4577
  /** Per-screen override of the app-wide default. */
@@ -4881,7 +4925,10 @@ class EfDataCardComponent {
4881
4925
  labelKey: 'common_duplicate',
4882
4926
  icon: 'pi pi-copy',
4883
4927
  kbd: formatShortcut('mod+d'),
4884
- permission: Permissions.Duplicate,
4928
+ // Saved through create: without Create the row would open
4929
+ // a duplicate form the user cannot save. Hiding the item
4930
+ // also disarms mod+d, which only runs visible items.
4931
+ permission: [Permissions.Duplicate, Permissions.Create],
4885
4932
  command: () => this.rowAction.emit({ action: 'duplicate', row }),
4886
4933
  });
4887
4934
  }
@@ -5145,14 +5192,17 @@ class EfDataCardComponent {
5145
5192
  }
5146
5193
  storageKey() {
5147
5194
  const key = this.tableKey();
5148
- return key ? `TABLE_PREFS_${key}` : null;
5195
+ return key ? efTablePrefsKey(key) : null;
5149
5196
  }
5150
5197
  loadPreferences() {
5151
5198
  const key = this.storageKey();
5152
5199
  if (!key)
5153
5200
  return;
5154
5201
  try {
5155
- const saved = StorageUtils.getLocal(key);
5202
+ const raw = this.prefs.get(key);
5203
+ if (!raw)
5204
+ return;
5205
+ const saved = JSON.parse(raw);
5156
5206
  if (!saved)
5157
5207
  return;
5158
5208
  if (saved.density)
@@ -5170,10 +5220,10 @@ class EfDataCardComponent {
5170
5220
  if (!key)
5171
5221
  return;
5172
5222
  try {
5173
- StorageUtils.setLocal(key, {
5223
+ this.prefs.set(key, JSON.stringify({
5174
5224
  density: this.density(),
5175
5225
  hiddenColumnIds: this.hiddenColumnIds(),
5176
- });
5226
+ }));
5177
5227
  }
5178
5228
  catch {
5179
5229
  // Preferences are a convenience; losing them must never break
@@ -7273,7 +7323,8 @@ class EfModuleSideComponent {
7273
7323
  return m.navSections
7274
7324
  .map(section => ({
7275
7325
  ...section,
7276
- items: section.items.filter(item => this.perms.can(m.id, item.requiredAction ?? 'read')),
7326
+ items: section.items.filter(item => this.perms.can(m.id, item.requiredAction ?? 'read') &&
7327
+ (!item.screenCode || this.perms.canScreen(item.screenCode))),
7277
7328
  }))
7278
7329
  .filter(section => section.items.length > 0);
7279
7330
  }, ...(ngDevMode ? [{ debugName: "visibleSections" }] : /* istanbul ignore next */ []));
@@ -7425,15 +7476,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
7425
7476
  args: ['document:keydown.escape']
7426
7477
  }] } });
7427
7478
 
7428
- const SIDE_COLLAPSED_KEY = 'ef.shell.side-collapsed';
7429
- function readSideCollapsed() {
7430
- try {
7431
- return localStorage.getItem(SIDE_COLLAPSED_KEY) === '1';
7432
- }
7433
- catch {
7434
- return false;
7435
- }
7436
- }
7437
7479
  /**
7438
7480
  * Top-level Comptoir shell.
7439
7481
  *
@@ -7481,20 +7523,22 @@ class EfAppShellComponent {
7481
7523
  inside the side, so it is still there to bring the side back
7482
7524
  once it is gone, and in the same spot. Remembered per browser:
7483
7525
  it is a reading preference, not something worth a round trip. */
7484
- sideCollapsed = signal(readSideCollapsed(), ...(ngDevMode ? [{ debugName: "sideCollapsed" }] : /* istanbul ignore next */ []));
7526
+ prefs = inject(EF_PREFERENCE_STORE);
7527
+ sideCollapsed = signal(this.prefs.get(EF_SIDE_COLLAPSED_KEY) === '1', ...(ngDevMode ? [{ debugName: "sideCollapsed" }] : /* istanbul ignore next */ []));
7485
7528
  /** Shown in the toggle's tooltip: the button is far from the content, the shortcut is not. */
7486
7529
  shortcutHint = formatShortcut('mod+b');
7487
7530
  toggleSide() {
7488
7531
  const collapsed = !this.sideCollapsed();
7489
7532
  this.sideCollapsed.set(collapsed);
7490
- try {
7491
- localStorage.setItem(SIDE_COLLAPSED_KEY, collapsed ? '1' : '0');
7492
- }
7493
- catch {
7494
- /* private window or blocked storage: the toggle still works for this visit */
7495
- }
7533
+ this.prefs.set(EF_SIDE_COLLAPSED_KEY, collapsed ? '1' : '0');
7496
7534
  }
7497
7535
  constructor() {
7536
+ // A store that hydrates after sign in announces the key it filled.
7537
+ this.prefs.changes?.pipe(takeUntilDestroyed()).subscribe((key) => {
7538
+ if (key === EF_SIDE_COLLAPSED_KEY) {
7539
+ this.sideCollapsed.set(this.prefs.get(EF_SIDE_COLLAPSED_KEY) === '1');
7540
+ }
7541
+ });
7498
7542
  inject(EfShortcutService).register({
7499
7543
  id: 'shell.toggle-side',
7500
7544
  keys: 'mod+b',
@@ -7530,7 +7574,8 @@ class EfAppShellComponent {
7530
7574
  return m.navSections
7531
7575
  .map(section => ({
7532
7576
  ...section,
7533
- items: section.items.filter(item => this.perms.can(m.id, item.requiredAction ?? 'read')),
7577
+ items: section.items.filter(item => this.perms.can(m.id, item.requiredAction ?? 'read') &&
7578
+ (!item.screenCode || this.perms.canScreen(item.screenCode))),
7534
7579
  }))
7535
7580
  .filter(section => section.items.length > 0);
7536
7581
  }, ...(ngDevMode ? [{ debugName: "moduleSections" }] : /* istanbul ignore next */ []));
@@ -8601,6 +8646,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
8601
8646
  * Custom actions can declare `permission` and `visible` like
8602
8647
  * `EfRowAction`. With `[context]` bound, the toolbar filters them
8603
8648
  * against `ScreenContext.isGranted()` automatically.
8649
+ *
8650
+ * Save and actions flagged `requiresWrite` follow `context.canWrite`,
8651
+ * which `AbstractDetailScreenV2` sets from its own `canWrite()` (Edit on
8652
+ * an existing record, Create on a new one or a duplicate). Binding
8653
+ * `[context]="context"` is all a screen does; it does not pass
8654
+ * `[showSaveAction]` for permissions. Without a context, or with one that
8655
+ * carries no `canWrite`, Save behaves as `[showSaveAction]` alone says.
8604
8656
  */
8605
8657
  class EfDetailToolbarComponent {
8606
8658
  /* ── Inputs ─────────────────────────────────────────────────── */
@@ -8638,12 +8690,17 @@ class EfDetailToolbarComponent {
8638
8690
  /** Custom actions after permission + `visible` filtering. */
8639
8691
  visibleCustomActions = computed(() => {
8640
8692
  const ctx = this.context();
8641
- return this.customActions().filter((a) => this.isCustomVisible(a, ctx));
8693
+ const canWrite = this.canWrite();
8694
+ return this.customActions().filter((a) => this.isCustomVisible(a, ctx, canWrite));
8642
8695
  }, ...(ngDevMode ? [{ debugName: "visibleCustomActions" }] : /* istanbul ignore next */ []));
8643
8696
  canPrint = computed(() => this.checkStandard(this.showPrintAction(), Permissions.Print), ...(ngDevMode ? [{ debugName: "canPrint" }] : /* istanbul ignore next */ []));
8644
8697
  canDuplicate = computed(() => this.checkStandard(this.showDuplicateAction(), Permissions.Duplicate), ...(ngDevMode ? [{ debugName: "canDuplicate" }] : /* istanbul ignore next */ []));
8645
8698
  canDelete = computed(() => this.checkStandard(this.showDeleteAction(), Permissions.Delete), ...(ngDevMode ? [{ debugName: "canDelete" }] : /* istanbul ignore next */ []));
8646
- canSave = computed(() => this.showSaveAction(), ...(ngDevMode ? [{ debugName: "canSave" }] : /* istanbul ignore next */ []));
8699
+ /** The bound screen's write state; `true` when unknown (no context,
8700
+ * or a context no detail abstract has stamped), which keeps the
8701
+ * toolbar's behaviour for those callers what it always was. */
8702
+ canWrite = computed(() => this.context()?.canWrite?.() ?? true, ...(ngDevMode ? [{ debugName: "canWrite" }] : /* istanbul ignore next */ []));
8703
+ canSave = computed(() => this.showSaveAction() && this.canWrite(), ...(ngDevMode ? [{ debugName: "canSave" }] : /* istanbul ignore next */ []));
8647
8704
  hasStandardActions = computed(() => this.canPrint() ||
8648
8705
  this.canDuplicate() ||
8649
8706
  this.canDelete() ||
@@ -8659,9 +8716,11 @@ class EfDetailToolbarComponent {
8659
8716
  this.customAction.emit(action);
8660
8717
  }
8661
8718
  /* ── Internals ──────────────────────────────────────────────── */
8662
- isCustomVisible(action, ctx) {
8719
+ isCustomVisible(action, ctx, canWrite) {
8663
8720
  if (action.visible === false)
8664
8721
  return false;
8722
+ if (action.requiresWrite && !canWrite)
8723
+ return false;
8665
8724
  if (action.permission && ctx && !ctx.isGranted(action.permission))
8666
8725
  return false;
8667
8726
  return true;
@@ -8678,7 +8737,9 @@ class EfDetailToolbarComponent {
8678
8737
  case Permissions.Print:
8679
8738
  return ctx.hasPrintPermission;
8680
8739
  case Permissions.Duplicate:
8681
- return ctx.hasDuplicatePermission;
8740
+ // A duplicate is saved through create, and duplicate() refuses
8741
+ // without Create; showing it on Duplicate alone gave a dead button.
8742
+ return ctx.hasDuplicatePermission && ctx.hasCreatePermission;
8682
8743
  case Permissions.Delete:
8683
8744
  return ctx.hasDeletePermission;
8684
8745
  }
@@ -9541,11 +9602,11 @@ class EfCheckboxComponent extends AbstractEfFormControl {
9541
9602
  this.onTouched();
9542
9603
  }
9543
9604
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfCheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9544
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfCheckboxComponent, isStandalone: true, selector: "ef-checkbox", inputs: { value: "value", binary: ["binary", "binary", booleanAttribute], variant: "variant", inline: ["inline", "inline", booleanAttribute], checked: "checked" }, outputs: { checkedChange: "checkedChange" }, host: { properties: { "class.ef-stacked": "this.isStacked" } }, usesInheritance: true, ngImport: i0, template: "@if (!inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-checkbox-control\">\n @if (variant === 'comptoir') {\n <label class=\"ef-checkbox-native\" [class.is-disabled]=\"disabled\">\n <input\n type=\"checkbox\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [checked]=\"!!checked\"\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]=\"(showRequired || serverErrors) ? errorsId : null\"\n (change)=\"handleNativeChange($any($event.target).checked)\"\n />\n <span class=\"ef-checkbox-box\" aria-hidden=\"true\"></span>\n @if (inline && hasLabel) {\n <span class=\"ef-checkbox-label\">\n {{ labelKey() ? (labelKey() | translate) : label() }}\n @if (required()) {\n <span class=\"ef-checkbox-required\" aria-hidden=\"true\">*</span>\n }\n </span>\n }\n </label>\n } @else {\n <p-checkbox\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [ngModel]=\"checked\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"handleChange($event)\"\n [value]=\"value\"\n [binary]=\"binary\"\n [disabled]=\"isDisabled()\"\n [ariaLabel]=\"effectiveAriaLabel || undefined\"\n />\n @if (inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" styleClass=\"form-label-left cursor-pointer\" />\n }\n }\n</div>\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", styles: [":host{display:inline-flex;flex-wrap:wrap;align-items:center;gap:.5rem;min-width:0}.ef-checkbox-control{display:inline-flex;align-items:center;gap:.5rem;min-width:0}:host(.ef-stacked){display:block}:host(.ef-stacked)>.ef-checkbox-control{display:flex;min-height:var(--hit-base)}\n"], dependencies: [{ kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i5.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
9605
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfCheckboxComponent, isStandalone: true, selector: "ef-checkbox", inputs: { value: "value", binary: ["binary", "binary", booleanAttribute], variant: "variant", inline: ["inline", "inline", booleanAttribute], checked: "checked" }, outputs: { checkedChange: "checkedChange" }, host: { properties: { "class.ef-stacked": "this.isStacked" } }, usesInheritance: true, ngImport: i0, template: "@if (!inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-checkbox-control\">\n @if (variant === 'comptoir') {\n <label class=\"ef-checkbox-native\" [class.is-disabled]=\"isDisabled()\">\n <input\n type=\"checkbox\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [checked]=\"!!checked\"\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]=\"(showRequired || serverErrors) ? errorsId : null\"\n (change)=\"handleNativeChange($any($event.target).checked)\"\n />\n <span class=\"ef-checkbox-box\" aria-hidden=\"true\"></span>\n @if (inline && hasLabel) {\n <span class=\"ef-checkbox-label\">\n {{ labelKey() ? (labelKey() | translate) : label() }}\n @if (required()) {\n <span class=\"ef-checkbox-required\" aria-hidden=\"true\">*</span>\n }\n </span>\n }\n </label>\n } @else {\n <p-checkbox\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [ngModel]=\"checked\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"handleChange($event)\"\n [value]=\"value\"\n [binary]=\"binary\"\n [disabled]=\"isDisabled()\"\n [ariaLabel]=\"effectiveAriaLabel || undefined\"\n />\n @if (inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" styleClass=\"form-label-left cursor-pointer\" />\n }\n }\n</div>\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", styles: [":host{display:inline-flex;flex-wrap:wrap;align-items:center;gap:.5rem;min-width:0}.ef-checkbox-control{display:inline-flex;align-items:center;gap:.5rem;min-width:0}:host(.ef-stacked){display:block}:host(.ef-stacked)>.ef-checkbox-control{display:flex;min-height:var(--hit-base)}\n"], dependencies: [{ kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i5.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
9545
9606
  }
9546
9607
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfCheckboxComponent, decorators: [{
9547
9608
  type: Component,
9548
- args: [{ selector: 'ef-checkbox', standalone: true, imports: [CheckboxModule, FormsModule, TranslateModule, EfLabelComponent], template: "@if (!inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-checkbox-control\">\n @if (variant === 'comptoir') {\n <label class=\"ef-checkbox-native\" [class.is-disabled]=\"disabled\">\n <input\n type=\"checkbox\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [checked]=\"!!checked\"\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]=\"(showRequired || serverErrors) ? errorsId : null\"\n (change)=\"handleNativeChange($any($event.target).checked)\"\n />\n <span class=\"ef-checkbox-box\" aria-hidden=\"true\"></span>\n @if (inline && hasLabel) {\n <span class=\"ef-checkbox-label\">\n {{ labelKey() ? (labelKey() | translate) : label() }}\n @if (required()) {\n <span class=\"ef-checkbox-required\" aria-hidden=\"true\">*</span>\n }\n </span>\n }\n </label>\n } @else {\n <p-checkbox\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [ngModel]=\"checked\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"handleChange($event)\"\n [value]=\"value\"\n [binary]=\"binary\"\n [disabled]=\"isDisabled()\"\n [ariaLabel]=\"effectiveAriaLabel || undefined\"\n />\n @if (inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" styleClass=\"form-label-left cursor-pointer\" />\n }\n }\n</div>\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", styles: [":host{display:inline-flex;flex-wrap:wrap;align-items:center;gap:.5rem;min-width:0}.ef-checkbox-control{display:inline-flex;align-items:center;gap:.5rem;min-width:0}:host(.ef-stacked){display:block}:host(.ef-stacked)>.ef-checkbox-control{display:flex;min-height:var(--hit-base)}\n"] }]
9609
+ args: [{ selector: 'ef-checkbox', standalone: true, imports: [CheckboxModule, FormsModule, TranslateModule, EfLabelComponent], template: "@if (!inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" [required]=\"required()\" />\n}\n<div class=\"ef-checkbox-control\">\n @if (variant === 'comptoir') {\n <label class=\"ef-checkbox-native\" [class.is-disabled]=\"isDisabled()\">\n <input\n type=\"checkbox\"\n [id]=\"effectiveId\"\n [attr.name]=\"name() || null\"\n [checked]=\"!!checked\"\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]=\"(showRequired || serverErrors) ? errorsId : null\"\n (change)=\"handleNativeChange($any($event.target).checked)\"\n />\n <span class=\"ef-checkbox-box\" aria-hidden=\"true\"></span>\n @if (inline && hasLabel) {\n <span class=\"ef-checkbox-label\">\n {{ labelKey() ? (labelKey() | translate) : label() }}\n @if (required()) {\n <span class=\"ef-checkbox-required\" aria-hidden=\"true\">*</span>\n }\n </span>\n }\n </label>\n } @else {\n <p-checkbox\n [inputId]=\"effectiveId\"\n [name]=\"name()\"\n [ngModel]=\"checked\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"handleChange($event)\"\n [value]=\"value\"\n [binary]=\"binary\"\n [disabled]=\"isDisabled()\"\n [ariaLabel]=\"effectiveAriaLabel || undefined\"\n />\n @if (inline && hasLabel) {\n <ef-label [labelKey]=\"labelKey()\" [label]=\"label()\" [for]=\"effectiveId\" styleClass=\"form-label-left cursor-pointer\" />\n }\n }\n</div>\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", styles: [":host{display:inline-flex;flex-wrap:wrap;align-items:center;gap:.5rem;min-width:0}.ef-checkbox-control{display:inline-flex;align-items:center;gap:.5rem;min-width:0}:host(.ef-stacked){display:block}:host(.ef-stacked)>.ef-checkbox-control{display:flex;min-height:var(--hit-base)}\n"] }]
9549
9610
  }], ctorParameters: () => [], propDecorators: { value: [{
9550
9611
  type: Input
9551
9612
  }], binary: [{
@@ -9708,6 +9769,12 @@ class EfDatepickerComponent {
9708
9769
  showButtonBar = false;
9709
9770
  minDate;
9710
9771
  maxDate;
9772
+ /** Optional enclosing `[efReadOnly]` scope. */
9773
+ readOnlyScope = inject(EfReadOnlyDirective, { optional: true });
9774
+ /** Effective disabled: the input / CVA state OR a read-only scope. */
9775
+ get isDisabled() {
9776
+ return this.disabled || (this.readOnlyScope?.readOnly() ?? false);
9777
+ }
9711
9778
  /**
9712
9779
  * Take focus on load. Bound explicitly rather than left unset: PrimeNG's
9713
9780
  * AutoFocus directive tests `autofocus === false`, and the control declares
@@ -9767,11 +9834,11 @@ class EfDatepickerComponent {
9767
9834
  this.onTouched();
9768
9835
  }
9769
9836
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDatepickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9770
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDatepickerComponent, isStandalone: true, selector: "ef-datepicker", inputs: { label: "label", labelKey: "labelKey", inputId: "inputId", name: "name", placeholder: "placeholder", placeholderKey: "placeholderKey", size: "size", required: ["required", "required", booleanAttribute], fluid: ["fluid", "fluid", booleanAttribute], inline: ["inline", "inline", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], showIcon: ["showIcon", "showIcon", booleanAttribute], iconDisplay: "iconDisplay", selectionMode: "selectionMode", dateFormat: "dateFormat", appendTo: "appendTo", showTime: ["showTime", "showTime", booleanAttribute], showButtonBar: ["showButtonBar", "showButtonBar", booleanAttribute], minDate: "minDate", maxDate: "maxDate", autofocus: ["autofocus", "autofocus", booleanAttribute] }, outputs: { dateSelectEvent: "dateSelectEvent" }, host: { properties: { "class.ef-inline": "this.isInline" } }, ngImport: i0, template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"name\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"disabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i1$d.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
9837
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDatepickerComponent, isStandalone: true, selector: "ef-datepicker", inputs: { label: "label", labelKey: "labelKey", inputId: "inputId", name: "name", placeholder: "placeholder", placeholderKey: "placeholderKey", size: "size", required: ["required", "required", booleanAttribute], fluid: ["fluid", "fluid", booleanAttribute], inline: ["inline", "inline", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], showIcon: ["showIcon", "showIcon", booleanAttribute], iconDisplay: "iconDisplay", selectionMode: "selectionMode", dateFormat: "dateFormat", appendTo: "appendTo", showTime: ["showTime", "showTime", booleanAttribute], showButtonBar: ["showButtonBar", "showButtonBar", booleanAttribute], minDate: "minDate", maxDate: "maxDate", autofocus: ["autofocus", "autofocus", booleanAttribute] }, outputs: { dateSelectEvent: "dateSelectEvent" }, host: { properties: { "class.ef-inline": "this.isInline" } }, ngImport: i0, template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"name\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"isDisabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host{display:block}:host(.ef-inline){display:flex;align-items:baseline;gap:.5rem}:host(.ef-inline)>label{white-space:nowrap;flex-shrink:0}:host(.ef-inline)>.ef-field-control{flex:1;min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i1$d.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
9771
9838
  }
9772
9839
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDatepickerComponent, decorators: [{
9773
9840
  type: Component,
9774
- args: [{ selector: 'ef-datepicker', standalone: true, imports: [DatePickerModule, FormsModule, TranslateModule, EfLabelComponent], template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"name\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"disabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host{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"] }]
9841
+ args: [{ selector: 'ef-datepicker', standalone: true, imports: [DatePickerModule, FormsModule, TranslateModule, EfLabelComponent], template: "@if (labelKey || label) {\n <ef-label [labelKey]=\"labelKey\" [label]=\"label\" [for]=\"effectiveId\" [required]=\"required\" />\n}\n<div class=\"ef-field-control\">\n <p-datepicker\n [autofocus]=\"autofocus\"\n [inputId]=\"effectiveId\"\n [name]=\"name\"\n [ngModel]=\"value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"handleChange($event)\"\n (onBlur)=\"handleBlur()\"\n [placeholder]=\"effectivePlaceholder\"\n [size]=\"size\"\n [disabled]=\"isDisabled\"\n [showIcon]=\"showIcon\"\n [iconDisplay]=\"iconDisplay\"\n [selectionMode]=\"selectionMode\"\n [dateFormat]=\"dateFormat\"\n [appendTo]=\"appendTo\"\n [showTime]=\"showTime\"\n [showButtonBar]=\"showButtonBar\"\n [minDate]=\"minDate\"\n [maxDate]=\"maxDate\"\n [fluid]=\"fluid\"\n [class.ng-invalid]=\"isInvalid\"\n [class.ng-touched]=\"ngControl?.touched\"\n />\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n</div>\n", styles: [":host{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"] }]
9775
9842
  }], ctorParameters: () => [], propDecorators: { label: [{
9776
9843
  type: Input
9777
9844
  }], labelKey: [{
@@ -10818,14 +10885,22 @@ class EfSmartBarComponent {
10818
10885
  only text inside this label is the shortcut hint, so
10819
10886
  without it a screen reader announced the primary
10820
10887
  search field of every list screen as "Cmd K". -->
10888
+ <!-- type="search" fires a native, bubbling 'search' Event
10889
+ on Enter and on the browser's clear button (Chromium,
10890
+ WebKit). A host (search) binding hears that DOM event
10891
+ as well as the output below, so it would receive the
10892
+ Event as the search text. Stopped here; the output is
10893
+ the only 'search' that leaves this component. -->
10821
10894
  <input
10822
10895
  #searchInput
10823
10896
  type="search"
10897
+ enterkeyhint="search"
10824
10898
  [attr.aria-label]="accessibleLabel()"
10825
10899
  [placeholder]="(placeholderKey() ? (placeholderKey() | translate) : placeholder())"
10826
10900
  [ngModel]="searchText()"
10827
10901
  (ngModelChange)="searchText.set($event)"
10828
10902
  (keydown.enter)="onSubmit()"
10903
+ (search)="$event.stopPropagation()"
10829
10904
  />
10830
10905
  @if (showKbdHint() && kbdLabel()) {
10831
10906
  <!-- Decorative, and hidden from the accessibility tree:
@@ -10865,15 +10940,11 @@ class EfSmartBarComponent {
10865
10940
 
10866
10941
  <ng-content select="[drawer]"></ng-content>
10867
10942
  </div>
10868
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10943
+ `, isInline: true, styles: ["input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10869
10944
  }
10870
10945
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSmartBarComponent, decorators: [{
10871
10946
  type: Component,
10872
- args: [{
10873
- selector: 'ef-smart-bar',
10874
- standalone: true,
10875
- imports: [CommonModule, FormsModule, TranslateModule],
10876
- template: `
10947
+ args: [{ selector: 'ef-smart-bar', standalone: true, imports: [CommonModule, FormsModule, TranslateModule], template: `
10877
10948
  <div class="screen-search">
10878
10949
  <div class="smart-bar">
10879
10950
  <label class="search">
@@ -10881,14 +10952,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10881
10952
  only text inside this label is the shortcut hint, so
10882
10953
  without it a screen reader announced the primary
10883
10954
  search field of every list screen as "Cmd K". -->
10955
+ <!-- type="search" fires a native, bubbling 'search' Event
10956
+ on Enter and on the browser's clear button (Chromium,
10957
+ WebKit). A host (search) binding hears that DOM event
10958
+ as well as the output below, so it would receive the
10959
+ Event as the search text. Stopped here; the output is
10960
+ the only 'search' that leaves this component. -->
10884
10961
  <input
10885
10962
  #searchInput
10886
10963
  type="search"
10964
+ enterkeyhint="search"
10887
10965
  [attr.aria-label]="accessibleLabel()"
10888
10966
  [placeholder]="(placeholderKey() ? (placeholderKey() | translate) : placeholder())"
10889
10967
  [ngModel]="searchText()"
10890
10968
  (ngModelChange)="searchText.set($event)"
10891
10969
  (keydown.enter)="onSubmit()"
10970
+ (search)="$event.stopPropagation()"
10892
10971
  />
10893
10972
  @if (showKbdHint() && kbdLabel()) {
10894
10973
  <!-- Decorative, and hidden from the accessibility tree:
@@ -10928,9 +11007,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10928
11007
 
10929
11008
  <ng-content select="[drawer]"></ng-content>
10930
11009
  </div>
10931
- `,
10932
- changeDetection: ChangeDetectionStrategy.OnPush,
10933
- }]
11010
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;display:none}\n"] }]
10934
11011
  }], ctorParameters: () => [], propDecorators: { searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], searchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchText", required: false }] }, { type: i0.Output, args: ["searchTextChange"] }], placeholderKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], clearLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabelKey", required: false }] }], clearTitleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearTitleKey", required: false }] }], searchLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchLabelKey", required: false }] }], showKbdHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "showKbdHint", required: false }] }], hideClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideClear", required: false }] }], hideSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSearch", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], clear: [{ type: i0.Output, args: ["clear"] }] } });
10935
11012
 
10936
11013
  /**
@@ -12088,5 +12165,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
12088
12165
  * Generated bundle index. Do not edit.
12089
12166
  */
12090
12167
 
12091
- export { EF_BADGE_BG_COLORS, EF_BADGE_BG_LIGHT_COLORS, EF_BADGE_BORDER_COLORS, EF_BADGE_DEFAULTS, EF_BADGE_TEXT_COLORS, EF_CHART_PALETTE, EF_DATATABLE_ACTIONS_DEFAULTS, EF_DATATABLE_COLUMN_DEFAULTS, EF_DATATABLE_DEFAULTS, EF_DATA_CARD_MOBILE_LAYOUT, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfAboutDialogComponent, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChangePasswordComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfServerErrorsDirective, EfShortcutsDialogComponent, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
12168
+ export { EF_BADGE_BG_COLORS, EF_BADGE_BG_LIGHT_COLORS, EF_BADGE_BORDER_COLORS, EF_BADGE_DEFAULTS, EF_BADGE_TEXT_COLORS, EF_CHART_PALETTE, EF_DATATABLE_ACTIONS_DEFAULTS, EF_DATATABLE_COLUMN_DEFAULTS, EF_DATATABLE_DEFAULTS, EF_DATA_CARD_MOBILE_LAYOUT, EF_STATUS_COLORS, EF_STATUS_COLOR_ALIASES, EfAboutDialogComponent, EfActivationFilterComponent, EfAppMainComponent, EfAppShellComponent, EfAppShellMobileComponent, EfAppTopComponent, EfBadgeComponent, EfBlockUiComponent, EfBlockableDivComponent, EfBottomSheetComponent, EfBuildStampComponent, EfBulkBarComponent, EfButtonComponent, EfButtonGroupComponent, EfCardComponent, EfChangeHistoryComponent, EfChangePasswordComponent, EfChartComponent, EfCheckboxComponent, EfClearButtonComponent, EfColumnHeaderTemplateDirective, EfColumnTemplateDirective, EfCompactPipe, EfConfirmDialogComponent, EfCurrencyPipe, EfDataCardComponent, EfDatatableActionBarComponent, EfDatatableComponent, EfDatepickerAdvancedComponent, EfDatepickerComponent, EfDetailToolbarComponent, EfDialogComponent, EfEmailMetricsCardComponent, EfEmptyStateComponent, EfFabComponent, EfFieldsetComponent, EfFilterDrawerComponent, EfFilterPillComponent, EfFormGridComponent, EfInputNumberComponent, EfInputTextComponent, EfKpiCardComponent, EfLabelComponent, EfModuleRailComponent, EfModuleSideComponent, EfMultiSelectComponent, EfOrderBuilderComponent, EfOrderSummaryComponent, EfOverflowTooltipDirective, EfPagerComponent, EfPasswordComponent, EfPdfPreviewComponent, EfPillGroupComponent, EfPresetPillComponent, EfPriceDisplayComponent, EfProductCatalogueComponent, EfProductCatalogueFilterComponent, EfProductTypeaheadComponent, EfProfileChipComponent, EfProfileMenuComponent, EfPulseComponent, EfQuantitySelectorComponent, EfQuantityStepperComponent, EfRadioComponent, EfRankListComponent, EfReadOnlyDirective, EfRowActionsComponent, EfSearchToolbarComponent, EfSelectButtonComponent, EfSelectComponent, EfServerErrorsDirective, EfShortcutsDialogComponent, EfSkeletonComponent, EfSmartBarComponent, EfStatsComponent, EfStatusChipComponent, EfTabPanelDirective, EfTabsComponent, EfTextareaComponent, EfThemeConfiguratorComponent, EfThemeToggleComponent, EfTimelineComponent, EfToastRegionComponent, EfToggleSwitchComponent, EfToolbarComponent, EfTooltipDirective, EfTotalsComponent, OrderEntityHelper, OrderLineItemHelper, TruncatePipe, buildChartConfig, getColumnAlignment, mergeColumnDefaults, resolveEfStatusColor };
12092
12169
  //# sourceMappingURL=elasticias-ui.mjs.map