@elasticias/ui 1.0.13 → 1.0.14

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.
@@ -4934,6 +4934,19 @@ 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
+ /**
4938
+ * The three tiers a shortcut can belong to, outer to inner: a Global
4939
+ * shortcut works anywhere in the app, a Screen shortcut works anywhere on
4940
+ * the current screen, a Row shortcut only while a row's menu is open.
4941
+ * `list()` returns groups in registration order, which is really "whatever
4942
+ * happened to construct first." This fixes the section order so it
4943
+ * always reads outer to inner regardless of what rendered when.
4944
+ */
4945
+ const GROUP_ORDER = [
4946
+ 'shortcut_group_general',
4947
+ 'shortcut_group_screen',
4948
+ 'shortcut_group_row_actions',
4949
+ ];
4937
4950
  /**
4938
4951
  * The keyboard-shortcuts reference: every registration currently sitting
4939
4952
  * in `EfShortcutService`, grouped and rendered with the same `.kbd` chip
@@ -4953,9 +4966,19 @@ class EfShortcutsDialogComponent {
4953
4966
  shortcutService = inject(EfShortcutService);
4954
4967
  /** Two-way open state. */
4955
4968
  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. */
4969
+ /** `list()`, reordered Global → Screen → Row. An unrecognised group
4970
+ * (a future tier this component doesn't know about yet) sorts last
4971
+ * rather than being dropped. */
4972
+ groups = computed(() => {
4973
+ const rank = (group) => {
4974
+ const index = GROUP_ORDER.indexOf(group);
4975
+ return index === -1 ? GROUP_ORDER.length : index;
4976
+ };
4977
+ return [...this.shortcutService.list()].sort((a, b) => rank(a.group) - rank(b.group));
4978
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
4979
+ /** Only macOS shows every chip already resolved to its own glyphs.
4980
+ * Everyone else gets a line saying the chips already match their
4981
+ * system, since nothing here prints a Mac glyph for them to decode. */
4959
4982
  isMacPlatform = isMacPlatform;
4960
4983
  closeAction = [
4961
4984
  { labelKey: 'common_close', severity: 'ghost' },
@@ -4964,11 +4987,11 @@ class EfShortcutsDialogComponent {
4964
4987
  return formatShortcut(keys);
4965
4988
  }
4966
4989
  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 });
4990
+ 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:1px;margin:0;padding:6px;list-style:none;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl)}.shortcuts-dialog__item{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0;padding:9px 12px;border-radius:var(--r-md)}.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 .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
4991
  }
4969
4992
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfShortcutsDialogComponent, decorators: [{
4970
4993
  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"] }]
4994
+ 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:1px;margin:0;padding:6px;list-style:none;background:var(--paper-alt);border:1px solid var(--rule);border-radius:var(--r-xl)}.shortcuts-dialog__item{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0;padding:9px 12px;border-radius:var(--r-md)}.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 .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
4995
  }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }] } });
4973
4996
 
4974
4997
  class EfFieldsetComponent {
@@ -9882,8 +9905,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9882
9905
  /**
9883
9906
  * Comptoir smart bar — the search/filter card every list screen
9884
9907
  * starts with. Composes:
9885
- * 1. Search input (with `⌘K` kbd hint) + Effacer / Rechercher
9886
- * action buttons.
9908
+ * 1. Search input (with an optional keyboard-shortcut hint chip) +
9909
+ * Effacer / Rechercher action buttons.
9887
9910
  * 2. A filter-row slot for `[filters]` (pill-group + filter-pill +
9888
9911
  * preset-pill + "+ Plus de filtres").
9889
9912
  * 3. A drawer slot for `[drawer]` (advanced filters via
@@ -9893,6 +9916,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9893
9916
  * for two-way binding, or watch the (search) output for explicit
9894
9917
  * Rechercher clicks.
9895
9918
  *
9919
+ * `shortcut` defaults to `'mod+k'`: this component registers it with
9920
+ * `EfShortcutService` and focuses the search input when it fires (the
9921
+ * user still has to type and submit; the shortcut only gets them into
9922
+ * the field). Pass `[shortcut]="null"` on a screen with a second smart
9923
+ * bar so only one of them claims the binding.
9924
+ *
9896
9925
  * ```html
9897
9926
  * <ef-smart-bar
9898
9927
  * [(searchText)]="searchQuery"
@@ -9912,6 +9941,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9912
9941
  * ```
9913
9942
  */
9914
9943
  class EfSmartBarComponent {
9944
+ shortcuts = inject(EfShortcutService);
9945
+ /** Distinguishes this instance's registration from any other
9946
+ * ef-smart-bar on screen, the same convention ef-row-actions uses. */
9947
+ static nextInstanceId = 0;
9948
+ instanceId = EfSmartBarComponent.nextInstanceId++;
9949
+ searchInput = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
9915
9950
  /** Two-way bound search input. */
9916
9951
  searchText = model('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
9917
9952
  placeholderKey = input('', ...(ngDevMode ? [{ debugName: "placeholderKey" }] : /* istanbul ignore next */ []));
@@ -9923,12 +9958,44 @@ class EfSmartBarComponent {
9923
9958
  hideClear = input(false, { ...(ngDevMode ? { debugName: "hideClear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9924
9959
  hideSearch = input(false, { ...(ngDevMode ? { debugName: "hideSearch" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9925
9960
  loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
9961
+ /** Keyboard shortcut that focuses the search input. `null` opts out
9962
+ * (a screen with two smart bars registers the binding on only one
9963
+ * of them). Defaults to `'mod+k'`. */
9964
+ shortcut = input('mod+k', ...(ngDevMode ? [{ debugName: "shortcut" }] : /* istanbul ignore next */ []));
9965
+ kbdLabel = computed(() => {
9966
+ const keys = this.shortcut();
9967
+ return keys ? formatShortcut(keys) : '';
9968
+ }, ...(ngDevMode ? [{ debugName: "kbdLabel" }] : /* istanbul ignore next */ []));
9926
9969
  // `search` shadows a native DOM event name. Renaming this output would
9927
9970
  // churn every consumer template, so the name stays and the rule is
9928
9971
  // suppressed here deliberately.
9929
9972
  // eslint-disable-next-line @angular-eslint/no-output-native
9930
9973
  search = output();
9931
9974
  clear = output();
9975
+ constructor() {
9976
+ // An effect, not a constructor-time register(): `shortcut` is an
9977
+ // input, so its bound value isn't readable until after Angular has
9978
+ // finished construction. The effect also re-registers cleanly if a
9979
+ // caller ever changes `shortcut` at runtime, and its `onCleanup`
9980
+ // covers disposal since `EfShortcutService.register()` cannot pick
9981
+ // up a DestroyRef from inside an effect callback.
9982
+ effect(onCleanup => {
9983
+ const keys = this.shortcut();
9984
+ if (!keys)
9985
+ return;
9986
+ const dispose = this.shortcuts.register({
9987
+ id: `smart-bar.${this.instanceId}.focus`,
9988
+ keys,
9989
+ // Not 'common_search': this shortcut focuses the field, it
9990
+ // never submits, and the dialog is where a user learns
9991
+ // what a key actually does.
9992
+ labelKey: 'shortcut_focus_search',
9993
+ group: 'shortcut_group_screen',
9994
+ handler: () => this.searchInput()?.nativeElement.focus(),
9995
+ });
9996
+ onCleanup(dispose);
9997
+ });
9998
+ }
9932
9999
  onSubmit() {
9933
10000
  this.search.emit(this.searchText());
9934
10001
  }
@@ -9937,19 +10004,20 @@ class EfSmartBarComponent {
9937
10004
  this.clear.emit();
9938
10005
  }
9939
10006
  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: `
10007
+ 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
10008
  <div class="screen-search">
9942
10009
  <div class="smart-bar">
9943
10010
  <label class="search">
9944
10011
  <input
10012
+ #searchInput
9945
10013
  type="text"
9946
10014
  [placeholder]="(placeholderKey() ? (placeholderKey() | translate) : placeholder())"
9947
10015
  [ngModel]="searchText()"
9948
10016
  (ngModelChange)="searchText.set($event)"
9949
10017
  (keydown.enter)="onSubmit()"
9950
10018
  />
9951
- @if (showKbdHint()) {
9952
- <span class="kbd">⌘K</span>
10019
+ @if (showKbdHint() && kbdLabel()) {
10020
+ <span class="kbd">{{ kbdLabel() }}</span>
9953
10021
  }
9954
10022
  </label>
9955
10023
  <div class="actions">
@@ -9995,14 +10063,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
9995
10063
  <div class="smart-bar">
9996
10064
  <label class="search">
9997
10065
  <input
10066
+ #searchInput
9998
10067
  type="text"
9999
10068
  [placeholder]="(placeholderKey() ? (placeholderKey() | translate) : placeholder())"
10000
10069
  [ngModel]="searchText()"
10001
10070
  (ngModelChange)="searchText.set($event)"
10002
10071
  (keydown.enter)="onSubmit()"
10003
10072
  />
10004
- @if (showKbdHint()) {
10005
- <span class="kbd">⌘K</span>
10073
+ @if (showKbdHint() && kbdLabel()) {
10074
+ <span class="kbd">{{ kbdLabel() }}</span>
10006
10075
  }
10007
10076
  </label>
10008
10077
  <div class="actions">
@@ -10038,7 +10107,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
10038
10107
  `,
10039
10108
  changeDetection: ChangeDetectionStrategy.OnPush,
10040
10109
  }]
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"] }] } });
10110
+ }], 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
10111
 
10043
10112
  /**
10044
10113
  * Comptoir filter pill — value-bearing button used in the smart-bar's