@elasticias/ui 1.0.13 → 1.0.15

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.
@@ -42,7 +42,7 @@ import { MenuModule } from 'primeng/menu';
42
42
  import * as i2$3 from 'primeng/api';
43
43
  import * as i1$8 from 'primeng/confirmdialog';
44
44
  import { ConfirmDialogModule } from 'primeng/confirmdialog';
45
- import { EfThemeConfigService, EF_BUILD_INFO, EfShortcutService, isMacPlatform, formatShortcut, EfPermissionService, EfActiveModuleService, EF_SESSION, EfViewportService, EfToastService } from '@elasticias/core';
45
+ import { EfThemeConfigService, EF_BUILD_INFO, EfShortcutService, formatShortcut, EfPermissionService, EfActiveModuleService, EF_SESSION, EfViewportService, EfToastService } from '@elasticias/core';
46
46
  import * as i1$9 from 'primeng/buttongroup';
47
47
  import { ButtonGroupModule } from 'primeng/buttongroup';
48
48
  import * as i1$a from 'primeng/toolbar';
@@ -4934,6 +4934,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
4934
4934
  args: [{ selector: 'ef-about-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslateModule, EfDialogComponent], template: "<!-- appendTo=\"body\": ef-app-main is `overflow: hidden`, so a dialog left in\n place inside the content column is clipped by it. -->\n<ef-dialog\n [visible]=\"visible()\"\n (visibleChange)=\"visible.set($event)\"\n headerKey=\"common_about\"\n size=\"sm\"\n appendTo=\"body\"\n dismissableMask\n [actions]=\"closeAction\"\n>\n <div class=\"ef-about\">\n <span class=\"ef-about__mark\" aria-hidden=\"true\">\n @if (logoUrl()) {\n <img [src]=\"logoUrl()\" alt=\"\" />\n } @else {\n {{ initial() }}\n }\n </span>\n\n <p class=\"ef-about__product\">{{ product() }}</p>\n @if (edition()) {\n <p class=\"ef-about__edition\">{{ edition() }}</p>\n }\n\n @if (rows().length) {\n <dl class=\"ef-about__rows\">\n @for (row of rows(); track row.labelKey) {\n <div class=\"ef-about__row\">\n <dt>{{ row.labelKey | translate }}</dt>\n <dd>{{ row.value }}</dd>\n </div>\n }\n </dl>\n }\n\n @if (packages().length) {\n <h3 class=\"ef-about__group\">{{ 'common_about_packages' | translate }}</h3>\n <dl class=\"ef-about__rows ef-about__rows--tight\">\n @for (pkg of packages(); track pkg.name) {\n <div class=\"ef-about__row\">\n <dt>{{ pkg.name }}</dt>\n <dd>{{ pkg.version }}</dd>\n </div>\n }\n </dl>\n }\n\n <!-- The API's half of the answer. An app that exposes no such endpoint\n passes no [server] and this whole block collapses. -->\n @if (hasServer()) {\n <h3 class=\"ef-about__group\">{{ 'common_about_server' | translate }}</h3>\n <dl class=\"ef-about__rows ef-about__rows--tight\">\n @for (row of serverRows(); track row.labelKey) {\n <div class=\"ef-about__row\">\n <dt>{{ row.labelKey | translate }}</dt>\n <dd>{{ row.value }}</dd>\n </div>\n }\n @for (pkg of serverPackages(); track pkg.name) {\n <div class=\"ef-about__row\">\n <dt>{{ pkg.name }}</dt>\n <dd>{{ pkg.version }}</dd>\n </div>\n }\n </dl>\n }\n\n @if (websiteUrl()) {\n <a\n class=\"ef-about__link\"\n [href]=\"websiteUrl()\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span>{{ websiteLabel() || websiteUrl() }}</span>\n <i class=\"pi pi-external-link\" aria-hidden=\"true\"></i>\n </a>\n }\n\n <p class=\"ef-about__owner\">\u00A9 {{ year }} {{ owner() }}</p>\n </div>\n</ef-dialog>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.ef-about{display:grid;justify-items:center;gap:10px;text-align:center}.ef-about__mark{display:grid;place-items:center;width:56px;height:56px;border-radius:var(--r-pill, 999px);background:var(--tenant-500);color:#fff;font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 80;font-size:22px;line-height:1;overflow:hidden}.ef-about__mark img{width:100%;height:100%;object-fit:cover}.ef-about__product{margin:2px 0 0;font-family:Bricolage Grotesque,system-ui,sans-serif;font-variation-settings:\"opsz\" 14,\"wght\" 700,\"wdth\" 90;font-size:20px;letter-spacing:-.02em;color:var(--text)}.ef-about__edition{margin:-6px 0 0;max-width:100%;font-size:12px;color:var(--text-mute);overflow-wrap:anywhere}.ef-about__rows{width:100%;min-width:0;margin:0;padding:12px 0 0;display:grid;gap:7px;border-top:1px solid var(--rule);text-align:start}.ef-about__row{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:baseline}.ef-about__row dt{min-width:0;font-size:12px;color:var(--text-mute);overflow-wrap:anywhere}.ef-about__row dd{min-width:0;margin:0;font-family:JetBrains Mono,ui-monospace,monospace;font-size:11.5px;color:var(--text);text-align:end;overflow-wrap:anywhere}.ef-about__rows--tight{padding-top:0;border-top:0}.ef-about__group{width:100%;margin:4px 0 0;padding-top:12px;border-top:1px solid var(--rule);font-family:JetBrains Mono,ui-monospace,monospace;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft);text-align:start}.ef-about__link{display:inline-flex;align-items:center;gap:6px;min-height:40px;margin-top:2px;padding:0 10px;border-radius:var(--r-pill, 999px);color:var(--tenant-600, var(--tenant-500));font-size:12.5px;font-weight:700;text-decoration:none}.ef-about__link i{font-size:11px;opacity:.75}.ef-about__link:hover{background:color-mix(in srgb,var(--tenant-500) 10%,transparent);text-decoration:underline}.ef-about__link:focus-visible{outline:2px solid var(--tenant-400);outline-offset:2px}.ef-about__owner{margin:6px 0 0;font-size:11px;color:var(--text-soft)}@media(max-width:767px){.ef-about__mark{width:48px;height:48px;font-size:19px}.ef-about__link{min-height:var(--hit-touch, 48px)}}\n"] }]
4935
4935
  }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], product: [{ type: i0.Input, args: [{ isSignal: true, alias: "product", required: false }] }], edition: [{ type: i0.Input, args: [{ isSignal: true, alias: "edition", required: false }] }], organisation: [{ type: i0.Input, args: [{ isSignal: true, alias: "organisation", required: false }] }], server: [{ type: i0.Input, args: [{ isSignal: true, alias: "server", required: false }] }], websiteUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "websiteUrl", required: false }] }], websiteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "websiteLabel", required: false }] }], logoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoUrl", required: false }] }], initial: [{ type: i0.Input, args: [{ isSignal: true, alias: "initial", required: false }] }], owner: [{ type: i0.Input, args: [{ isSignal: true, alias: "owner", required: false }] }] } });
4936
4936
 
4937
+ const GROUP_ORDER = [
4938
+ 'shortcut_group_general',
4939
+ 'shortcut_group_screen',
4940
+ 'shortcut_group_row_actions',
4941
+ ];
4937
4942
  /**
4938
4943
  * The keyboard-shortcuts reference: every registration currently sitting
4939
4944
  * in `EfShortcutService`, grouped and rendered with the same `.kbd` chip
@@ -4953,10 +4958,40 @@ class EfShortcutsDialogComponent {
4953
4958
  shortcutService = inject(EfShortcutService);
4954
4959
  /** Two-way open state. */
4955
4960
  visible = model(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
4956
- groups = this.shortcutService.list;
4957
- /** Only macOS reads `⌘` at a glance. Everyone else needs the line
4958
- * spelling out that it means Ctrl. */
4959
- isMacPlatform = isMacPlatform;
4961
+ /** `list()`, reordered Global → Screen → Row, with the bindings for one
4962
+ * action collapsed onto a single row.
4963
+ *
4964
+ * An action can be reachable by more than one key: opening this dialog
4965
+ * answers to both `?` and `mod+/`. Listing registrations verbatim
4966
+ * printed that action's name twice, once per binding, which reads as a
4967
+ * duplicate rather than as a choice. An unrecognised group (a future
4968
+ * tier this component does not know about) sorts last rather than
4969
+ * being dropped. */
4970
+ groups = computed(() => {
4971
+ const rank = (group) => {
4972
+ const index = GROUP_ORDER.indexOf(group);
4973
+ return index === -1 ? GROUP_ORDER.length : index;
4974
+ };
4975
+ return [...this.shortcutService.list()]
4976
+ .sort((a, b) => rank(a.group) - rank(b.group))
4977
+ .map(group => {
4978
+ const byLabel = new Map();
4979
+ for (const shortcut of group.shortcuts) {
4980
+ const keys = byLabel.get(shortcut.labelKey);
4981
+ if (keys) {
4982
+ if (!keys.includes(shortcut.keys))
4983
+ keys.push(shortcut.keys);
4984
+ }
4985
+ else {
4986
+ byLabel.set(shortcut.labelKey, [shortcut.keys]);
4987
+ }
4988
+ }
4989
+ return {
4990
+ group: group.group,
4991
+ actions: [...byLabel].map(([labelKey, keys]) => ({ labelKey, keys })),
4992
+ };
4993
+ });
4994
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
4960
4995
  closeAction = [
4961
4996
  { labelKey: 'common_close', severity: 'ghost' },
4962
4997
  ];
@@ -4964,11 +4999,11 @@ class EfShortcutsDialogComponent {
4964
4999
  return formatShortcut(keys);
4965
5000
  }
4966
5001
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfShortcutsDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4967
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfShortcutsDialogComponent, isStandalone: true, selector: "ef-shortcuts-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange" }, ngImport: i0, template: "<!-- appendTo=\"body\": a dialog left inside the content column can be clipped\n by an ancestor's overflow rule (see ef-about-dialog). -->\n<ef-dialog\n [visible]=\"visible()\"\n (visibleChange)=\"visible.set($event)\"\n headerKey=\"shortcuts_dialog_title\"\n size=\"sm\"\n appendTo=\"body\"\n dismissableMask\n [actions]=\"closeAction\"\n>\n <div class=\"shortcuts-dialog\">\n @if (!isMacPlatform) {\n <p class=\"shortcuts-dialog__hint\">{{ 'shortcuts_dialog_mod_hint' | translate }}</p>\n }\n\n @if (groups().length) {\n @for (group of groups(); track group.group) {\n <section class=\"shortcuts-dialog__group\">\n <h3 class=\"shortcuts-dialog__group-title\">{{ group.group | translate }}</h3>\n <ul class=\"shortcuts-dialog__list\">\n @for (shortcut of group.shortcuts; track shortcut.id) {\n <li class=\"shortcuts-dialog__item\">\n <span class=\"lbl\">{{ shortcut.labelKey | translate }}</span>\n <!-- Not aria-hidden here: the key IS the content this\n dialog exists to say. Hiding it would leave a\n screen-reader user hearing the action names and\n never the shortcut that triggers them. -->\n <span class=\"kbd\">{{ formatKeys(shortcut.keys) }}</span>\n </li>\n }\n </ul>\n </section>\n }\n } @else {\n <p class=\"shortcuts-dialog__empty\">{{ 'shortcuts_dialog_empty' | translate }}</p>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.shortcuts-dialog{display:grid;gap:16px}.shortcuts-dialog__hint{margin:0;padding:8px 10px;border-radius:8px;background:var(--paper-soft, var(--paper));border:1px solid var(--rule);font-size:12px;color:var(--text-mute)}.shortcuts-dialog__empty{margin:0;padding:12px 0;font-size:13px;color:var(--text-mute);text-align:center}.shortcuts-dialog__group+.shortcuts-dialog__group{padding-top:12px;border-top:1px solid var(--rule)}.shortcuts-dialog__group-title{margin:0 0 8px;font-family:JetBrains Mono,ui-monospace,monospace;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft)}.shortcuts-dialog__list{display:grid;gap:6px;margin:0;padding:0;list-style:none}.shortcuts-dialog__item{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}.shortcuts-dialog__item .lbl{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--text)}.shortcuts-dialog__item .kbd{flex:none;font-family:JetBrains Mono,monospace;font-size:10px;font-weight:600;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:6px;padding:2px 6px;line-height:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfDialogComponent, selector: "ef-dialog", inputs: ["header", "headerKey", "subtitle", "subtitleKey", "visible", "modal", "closable", "draggable", "resizable", "dismissableMask", "closeOnEscape", "severity", "icon", "size", "style", "styleClass", "position", "appendTo", "variant", "actions", "defaultActions", "saveLabelKey", "cancelLabelKey", "saveLabel", "cancelLabel", "saveIcon", "cancelIcon", "saveDisabled", "saveStyleClass", "cancelStyleClass", "saveSeverity", "cancelSeverity"], outputs: ["visibleChange", "showEvent", "hideEvent", "saveAction", "cancelAction", "actionClick"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5002
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfShortcutsDialogComponent, isStandalone: true, selector: "ef-shortcuts-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange" }, ngImport: i0, template: "<!-- appendTo=\"body\": a dialog left inside the content column can be clipped\n by an ancestor's overflow rule (see ef-about-dialog). -->\n<ef-dialog\n [visible]=\"visible()\"\n (visibleChange)=\"visible.set($event)\"\n headerKey=\"shortcuts_dialog_title\"\n size=\"sm\"\n appendTo=\"body\"\n dismissableMask\n [actions]=\"closeAction\"\n>\n <div class=\"shortcuts-dialog\">\n @if (groups().length) {\n @for (group of groups(); track group.group) {\n <section class=\"shortcuts-dialog__group\">\n <h3 class=\"shortcuts-dialog__group-title\">{{ group.group | translate }}</h3>\n <ul class=\"shortcuts-dialog__list\">\n @for (action of group.actions; track action.labelKey) {\n <li class=\"shortcuts-dialog__item\">\n <span class=\"lbl\">{{ action.labelKey | translate }}</span>\n <!-- Not aria-hidden: the key IS the content this dialog\n exists to say. Hiding it would leave a screen-reader\n user hearing action names and never the shortcut.\n One action can carry several bindings, so they sit\n on one row rather than repeating the label. -->\n <span class=\"keys\">\n @for (keys of action.keys; track keys) {\n <span class=\"kbd\">{{ formatKeys(keys) }}</span>\n }\n </span>\n </li>\n }\n </ul>\n </section>\n }\n } @else {\n <p class=\"shortcuts-dialog__empty\">{{ 'shortcuts_dialog_empty' | translate }}</p>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.shortcuts-dialog{display:grid;gap:18px}.shortcuts-dialog__empty{margin:0;padding:12px 0;font-size:13px;color:var(--text-mute);text-align:center}.shortcuts-dialog__group-title{margin:0 0 4px;font-family:JetBrains Mono,ui-monospace,monospace;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft)}.shortcuts-dialog__list{display:grid;gap:1px;margin:0;padding:0;list-style:none}.shortcuts-dialog__item{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0;padding:7px 0}.shortcuts-dialog__item .lbl{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13.5px;font-weight:600;color:var(--text)}.shortcuts-dialog__item .keys{display:inline-flex;flex:none;align-items:center;gap:4px}.shortcuts-dialog__item .kbd{flex:none;font-family:JetBrains Mono,monospace;font-size:10px;font-weight:600;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:6px;padding:2px 6px;line-height:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfDialogComponent, selector: "ef-dialog", inputs: ["header", "headerKey", "subtitle", "subtitleKey", "visible", "modal", "closable", "draggable", "resizable", "dismissableMask", "closeOnEscape", "severity", "icon", "size", "style", "styleClass", "position", "appendTo", "variant", "actions", "defaultActions", "saveLabelKey", "cancelLabelKey", "saveLabel", "cancelLabel", "saveIcon", "cancelIcon", "saveDisabled", "saveStyleClass", "cancelStyleClass", "saveSeverity", "cancelSeverity"], outputs: ["visibleChange", "showEvent", "hideEvent", "saveAction", "cancelAction", "actionClick"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4968
5003
  }
4969
5004
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfShortcutsDialogComponent, decorators: [{
4970
5005
  type: Component,
4971
- args: [{ selector: 'ef-shortcuts-dialog', standalone: true, imports: [CommonModule, TranslateModule, EfDialogComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- appendTo=\"body\": a dialog left inside the content column can be clipped\n by an ancestor's overflow rule (see ef-about-dialog). -->\n<ef-dialog\n [visible]=\"visible()\"\n (visibleChange)=\"visible.set($event)\"\n headerKey=\"shortcuts_dialog_title\"\n size=\"sm\"\n appendTo=\"body\"\n dismissableMask\n [actions]=\"closeAction\"\n>\n <div class=\"shortcuts-dialog\">\n @if (!isMacPlatform) {\n <p class=\"shortcuts-dialog__hint\">{{ 'shortcuts_dialog_mod_hint' | translate }}</p>\n }\n\n @if (groups().length) {\n @for (group of groups(); track group.group) {\n <section class=\"shortcuts-dialog__group\">\n <h3 class=\"shortcuts-dialog__group-title\">{{ group.group | translate }}</h3>\n <ul class=\"shortcuts-dialog__list\">\n @for (shortcut of group.shortcuts; track shortcut.id) {\n <li class=\"shortcuts-dialog__item\">\n <span class=\"lbl\">{{ shortcut.labelKey | translate }}</span>\n <!-- Not aria-hidden here: the key IS the content this\n dialog exists to say. Hiding it would leave a\n screen-reader user hearing the action names and\n never the shortcut that triggers them. -->\n <span class=\"kbd\">{{ formatKeys(shortcut.keys) }}</span>\n </li>\n }\n </ul>\n </section>\n }\n } @else {\n <p class=\"shortcuts-dialog__empty\">{{ 'shortcuts_dialog_empty' | translate }}</p>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.shortcuts-dialog{display:grid;gap:16px}.shortcuts-dialog__hint{margin:0;padding:8px 10px;border-radius:8px;background:var(--paper-soft, var(--paper));border:1px solid var(--rule);font-size:12px;color:var(--text-mute)}.shortcuts-dialog__empty{margin:0;padding:12px 0;font-size:13px;color:var(--text-mute);text-align:center}.shortcuts-dialog__group+.shortcuts-dialog__group{padding-top:12px;border-top:1px solid var(--rule)}.shortcuts-dialog__group-title{margin:0 0 8px;font-family:JetBrains Mono,ui-monospace,monospace;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft)}.shortcuts-dialog__list{display:grid;gap:6px;margin:0;padding:0;list-style:none}.shortcuts-dialog__item{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}.shortcuts-dialog__item .lbl{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--text)}.shortcuts-dialog__item .kbd{flex:none;font-family:JetBrains Mono,monospace;font-size:10px;font-weight:600;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:6px;padding:2px 6px;line-height:1}\n"] }]
5006
+ args: [{ selector: 'ef-shortcuts-dialog', standalone: true, imports: [CommonModule, TranslateModule, EfDialogComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- appendTo=\"body\": a dialog left inside the content column can be clipped\n by an ancestor's overflow rule (see ef-about-dialog). -->\n<ef-dialog\n [visible]=\"visible()\"\n (visibleChange)=\"visible.set($event)\"\n headerKey=\"shortcuts_dialog_title\"\n size=\"sm\"\n appendTo=\"body\"\n dismissableMask\n [actions]=\"closeAction\"\n>\n <div class=\"shortcuts-dialog\">\n @if (groups().length) {\n @for (group of groups(); track group.group) {\n <section class=\"shortcuts-dialog__group\">\n <h3 class=\"shortcuts-dialog__group-title\">{{ group.group | translate }}</h3>\n <ul class=\"shortcuts-dialog__list\">\n @for (action of group.actions; track action.labelKey) {\n <li class=\"shortcuts-dialog__item\">\n <span class=\"lbl\">{{ action.labelKey | translate }}</span>\n <!-- Not aria-hidden: the key IS the content this dialog\n exists to say. Hiding it would leave a screen-reader\n user hearing action names and never the shortcut.\n One action can carry several bindings, so they sit\n on one row rather than repeating the label. -->\n <span class=\"keys\">\n @for (keys of action.keys; track keys) {\n <span class=\"kbd\">{{ formatKeys(keys) }}</span>\n }\n </span>\n </li>\n }\n </ul>\n </section>\n }\n } @else {\n <p class=\"shortcuts-dialog__empty\">{{ 'shortcuts_dialog_empty' | translate }}</p>\n }\n </div>\n</ef-dialog>\n", styles: [":host{display:contents}.shortcuts-dialog{display:grid;gap:18px}.shortcuts-dialog__empty{margin:0;padding:12px 0;font-size:13px;color:var(--text-mute);text-align:center}.shortcuts-dialog__group-title{margin:0 0 4px;font-family:JetBrains Mono,ui-monospace,monospace;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.22em;color:var(--text-soft)}.shortcuts-dialog__list{display:grid;gap:1px;margin:0;padding:0;list-style:none}.shortcuts-dialog__item{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0;padding:7px 0}.shortcuts-dialog__item .lbl{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13.5px;font-weight:600;color:var(--text)}.shortcuts-dialog__item .keys{display:inline-flex;flex:none;align-items:center;gap:4px}.shortcuts-dialog__item .kbd{flex:none;font-family:JetBrains Mono,monospace;font-size:10px;font-weight:600;color:var(--text-mute);background:var(--paper);border:1px solid var(--rule);border-radius:6px;padding:2px 6px;line-height:1}\n"] }]
4972
5007
  }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }] } });
4973
5008
 
4974
5009
  class EfFieldsetComponent {
@@ -9882,8 +9917,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9882
9917
  /**
9883
9918
  * Comptoir smart bar — the search/filter card every list screen
9884
9919
  * starts with. Composes:
9885
- * 1. Search input (with `⌘K` kbd hint) + Effacer / Rechercher
9886
- * action buttons.
9920
+ * 1. Search input (with an optional keyboard-shortcut hint chip) +
9921
+ * Effacer / Rechercher action buttons.
9887
9922
  * 2. A filter-row slot for `[filters]` (pill-group + filter-pill +
9888
9923
  * preset-pill + "+ Plus de filtres").
9889
9924
  * 3. A drawer slot for `[drawer]` (advanced filters via
@@ -9893,6 +9928,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9893
9928
  * for two-way binding, or watch the (search) output for explicit
9894
9929
  * Rechercher clicks.
9895
9930
  *
9931
+ * `shortcut` defaults to `'mod+k'`: this component registers it with
9932
+ * `EfShortcutService` and focuses the search input when it fires (the
9933
+ * user still has to type and submit; the shortcut only gets them into
9934
+ * the field). Pass `[shortcut]="null"` on a screen with a second smart
9935
+ * bar so only one of them claims the binding.
9936
+ *
9896
9937
  * ```html
9897
9938
  * <ef-smart-bar
9898
9939
  * [(searchText)]="searchQuery"
@@ -9912,6 +9953,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9912
9953
  * ```
9913
9954
  */
9914
9955
  class EfSmartBarComponent {
9956
+ shortcuts = inject(EfShortcutService);
9957
+ /** Distinguishes this instance's registration from any other
9958
+ * ef-smart-bar on screen, the same convention ef-row-actions uses. */
9959
+ static nextInstanceId = 0;
9960
+ instanceId = EfSmartBarComponent.nextInstanceId++;
9961
+ searchInput = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
9915
9962
  /** Two-way bound search input. */
9916
9963
  searchText = model('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
9917
9964
  placeholderKey = input('', ...(ngDevMode ? [{ debugName: "placeholderKey" }] : /* istanbul ignore next */ []));
@@ -9923,12 +9970,44 @@ class EfSmartBarComponent {
9923
9970
  hideClear = input(false, { ...(ngDevMode ? { debugName: "hideClear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9924
9971
  hideSearch = input(false, { ...(ngDevMode ? { debugName: "hideSearch" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9925
9972
  loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9973
+ /** Keyboard shortcut that focuses the search input. `null` opts out
9974
+ * (a screen with two smart bars registers the binding on only one
9975
+ * of them). Defaults to `'mod+k'`. */
9976
+ shortcut = input('mod+k', ...(ngDevMode ? [{ debugName: "shortcut" }] : /* istanbul ignore next */ []));
9977
+ kbdLabel = computed(() => {
9978
+ const keys = this.shortcut();
9979
+ return keys ? formatShortcut(keys) : '';
9980
+ }, ...(ngDevMode ? [{ debugName: "kbdLabel" }] : /* istanbul ignore next */ []));
9926
9981
  // `search` shadows a native DOM event name. Renaming this output would
9927
9982
  // churn every consumer template, so the name stays and the rule is
9928
9983
  // suppressed here deliberately.
9929
9984
  // eslint-disable-next-line @angular-eslint/no-output-native
9930
9985
  search = output();
9931
9986
  clear = output();
9987
+ constructor() {
9988
+ // An effect, not a constructor-time register(): `shortcut` is an
9989
+ // input, so its bound value isn't readable until after Angular has
9990
+ // finished construction. The effect also re-registers cleanly if a
9991
+ // caller ever changes `shortcut` at runtime, and its `onCleanup`
9992
+ // covers disposal since `EfShortcutService.register()` cannot pick
9993
+ // up a DestroyRef from inside an effect callback.
9994
+ effect(onCleanup => {
9995
+ const keys = this.shortcut();
9996
+ if (!keys)
9997
+ return;
9998
+ const dispose = this.shortcuts.register({
9999
+ id: `smart-bar.${this.instanceId}.focus`,
10000
+ keys,
10001
+ // Not 'common_search': this shortcut focuses the field, it
10002
+ // never submits, and the dialog is where a user learns
10003
+ // what a key actually does.
10004
+ labelKey: 'shortcut_focus_search',
10005
+ group: 'shortcut_group_screen',
10006
+ handler: () => this.searchInput()?.nativeElement.focus(),
10007
+ });
10008
+ onCleanup(dispose);
10009
+ });
10010
+ }
9932
10011
  onSubmit() {
9933
10012
  this.search.emit(this.searchText());
9934
10013
  }
@@ -9937,19 +10016,20 @@ class EfSmartBarComponent {
9937
10016
  this.clear.emit();
9938
10017
  }
9939
10018
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfSmartBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9940
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfSmartBarComponent, isStandalone: true, selector: "ef-smart-bar", inputs: { searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabelKey: { classPropertyName: "clearLabelKey", publicName: "clearLabelKey", isSignal: true, isRequired: false, transformFunction: null }, clearTitleKey: { classPropertyName: "clearTitleKey", publicName: "clearTitleKey", isSignal: true, isRequired: false, transformFunction: null }, searchLabelKey: { classPropertyName: "searchLabelKey", publicName: "searchLabelKey", isSignal: true, isRequired: false, transformFunction: null }, showKbdHint: { classPropertyName: "showKbdHint", publicName: "showKbdHint", isSignal: true, isRequired: false, transformFunction: null }, hideClear: { classPropertyName: "hideClear", publicName: "hideClear", isSignal: true, isRequired: false, transformFunction: null }, hideSearch: { classPropertyName: "hideSearch", publicName: "hideSearch", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchText: "searchTextChange", search: "search", clear: "clear" }, ngImport: i0, template: `
10019
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfSmartBarComponent, isStandalone: true, selector: "ef-smart-bar", inputs: { searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null }, placeholderKey: { classPropertyName: "placeholderKey", publicName: "placeholderKey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabelKey: { classPropertyName: "clearLabelKey", publicName: "clearLabelKey", isSignal: true, isRequired: false, transformFunction: null }, clearTitleKey: { classPropertyName: "clearTitleKey", publicName: "clearTitleKey", isSignal: true, isRequired: false, transformFunction: null }, searchLabelKey: { classPropertyName: "searchLabelKey", publicName: "searchLabelKey", isSignal: true, isRequired: false, transformFunction: null }, showKbdHint: { classPropertyName: "showKbdHint", publicName: "showKbdHint", isSignal: true, isRequired: false, transformFunction: null }, hideClear: { classPropertyName: "hideClear", publicName: "hideClear", isSignal: true, isRequired: false, transformFunction: null }, hideSearch: { classPropertyName: "hideSearch", publicName: "hideSearch", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchText: "searchTextChange", search: "search", clear: "clear" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
9941
10020
  <div class="screen-search">
9942
10021
  <div class="smart-bar">
9943
10022
  <label class="search">
9944
10023
  <input
10024
+ #searchInput
9945
10025
  type="text"
9946
10026
  [placeholder]="(placeholderKey() ? (placeholderKey() | translate) : placeholder())"
9947
10027
  [ngModel]="searchText()"
9948
10028
  (ngModelChange)="searchText.set($event)"
9949
10029
  (keydown.enter)="onSubmit()"
9950
10030
  />
9951
- @if (showKbdHint()) {
9952
- <span class="kbd">⌘K</span>
10031
+ @if (showKbdHint() && kbdLabel()) {
10032
+ <span class="kbd">{{ kbdLabel() }}</span>
9953
10033
  }
9954
10034
  </label>
9955
10035
  <div class="actions">
@@ -9995,14 +10075,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9995
10075
  <div class="smart-bar">
9996
10076
  <label class="search">
9997
10077
  <input
10078
+ #searchInput
9998
10079
  type="text"
9999
10080
  [placeholder]="(placeholderKey() ? (placeholderKey() | translate) : placeholder())"
10000
10081
  [ngModel]="searchText()"
10001
10082
  (ngModelChange)="searchText.set($event)"
10002
10083
  (keydown.enter)="onSubmit()"
10003
10084
  />
10004
- @if (showKbdHint()) {
10005
- <span class="kbd">⌘K</span>
10085
+ @if (showKbdHint() && kbdLabel()) {
10086
+ <span class="kbd">{{ kbdLabel() }}</span>
10006
10087
  }
10007
10088
  </label>
10008
10089
  <div class="actions">
@@ -10038,7 +10119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10038
10119
  `,
10039
10120
  changeDetection: ChangeDetectionStrategy.OnPush,
10040
10121
  }]
10041
- }], propDecorators: { 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 }] }], search: [{ type: i0.Output, args: ["search"] }], clear: [{ type: i0.Output, args: ["clear"] }] } });
10122
+ }], 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"] }] } });
10042
10123
 
10043
10124
  /**
10044
10125
  * Comptoir filter pill — value-bearing button used in the smart-bar's