@ngrithms/hotkeys 0.1.0

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.
@@ -0,0 +1,317 @@
1
+ import { NgClass } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, input, signal, computed, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { HotkeysService, HOTKEY_CONFIG, isMacPlatform, comboToDisplay } from '@ngrithms/hotkeys';
5
+
6
+ /**
7
+ * Drop-in cheatsheet overlay listing every registered hotkey that has a
8
+ * `description`. Toggle with the `cheatsheetHotkey` config combo (default `'?'`)
9
+ * or by setting `[open]` programmatically.
10
+ *
11
+ * ```html
12
+ * <ngr-hotkey-cheatsheet /> <!-- toggled by '?' -->
13
+ * <ngr-hotkey-cheatsheet [open]="show()" /> <!-- controlled -->
14
+ * <ngr-hotkey-cheatsheet [searchable]="true" /> <!-- adds a filter input -->
15
+ * ```
16
+ *
17
+ * Bindings with the same `category` (set via `hotkeyCategory="..."` on the
18
+ * directive, or `category` on the service) appear under a shared subheader
19
+ * within their scope.
20
+ *
21
+ * Themable via CSS custom properties — see the component styles for the
22
+ * available `--ngr-hotkey-*` vars.
23
+ */
24
+ class HotkeyCheatsheetComponent {
25
+ hotkeys = inject(HotkeysService);
26
+ config = inject(HOTKEY_CONFIG);
27
+ /** Cheatsheet title. */
28
+ title = input('Keyboard shortcuts', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
29
+ /** Externally control visibility. When `undefined`, the component manages its own state. */
30
+ open = input(undefined, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
31
+ /** Show a search input that filters bindings by description / combo. Default `false`. */
32
+ searchable = input(false, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
33
+ mac = isMacPlatform();
34
+ internalOpen = signal(false, ...(ngDevMode ? [{ debugName: "internalOpen" }] : /* istanbul ignore next */ []));
35
+ query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
36
+ visible = computed(() => this.open() ?? this.internalOpen(), ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
37
+ groups = computed(() => {
38
+ const byScope = new Map();
39
+ for (const b of this.hotkeys.registered()) {
40
+ if (!b.description)
41
+ continue;
42
+ const scope = b.scope ?? 'global';
43
+ const combos = typeof b.keys === 'string' ? [b.keys] : [...b.keys];
44
+ if (combos.length === 0)
45
+ continue;
46
+ const [primary, ...rest] = combos;
47
+ const entry = {
48
+ primary,
49
+ primaryDisplay: safeDisplay(primary, this.mac),
50
+ aliases: rest.map((combo) => ({ combo, display: safeDisplay(combo, this.mac) })),
51
+ scope,
52
+ category: b.category ?? null,
53
+ description: b.description,
54
+ };
55
+ const list = byScope.get(scope);
56
+ if (list === undefined)
57
+ byScope.set(scope, [entry]);
58
+ else
59
+ list.push(entry);
60
+ }
61
+ return [...byScope.entries()]
62
+ .sort(([a], [b]) => (a === 'global' ? -1 : b === 'global' ? 1 : a.localeCompare(b)))
63
+ .map(([scope, entries]) => ({ scope, subgroups: groupByCategory(entries) }));
64
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
65
+ filteredGroups = computed(() => {
66
+ const q = this.query().trim().toLowerCase();
67
+ if (q === '')
68
+ return this.groups();
69
+ return this.groups()
70
+ .map((g) => ({
71
+ scope: g.scope,
72
+ subgroups: g.subgroups
73
+ .map((sub) => ({
74
+ category: sub.category,
75
+ entries: sub.entries.filter((e) => entryMatches(e, q)),
76
+ }))
77
+ .filter((sub) => sub.entries.length > 0),
78
+ }))
79
+ .filter((g) => g.subgroups.length > 0);
80
+ }, ...(ngDevMode ? [{ debugName: "filteredGroups" }] : /* istanbul ignore next */ []));
81
+ onKey(e) {
82
+ if (this.open() !== undefined)
83
+ return;
84
+ const trigger = this.config.cheatsheetHotkey;
85
+ // Ignore the trigger key when typed inside the search field — let the user type a `?`.
86
+ const inSearch = e.target instanceof HTMLElement &&
87
+ e.target.classList.contains('ngr-hotkey-cheatsheet__search-input');
88
+ if (!inSearch && trigger !== null && e.key === trigger && !isModified(e)) {
89
+ e.preventDefault();
90
+ this.internalOpen.update((v) => !v);
91
+ if (!this.internalOpen())
92
+ this.query.set('');
93
+ return;
94
+ }
95
+ if (e.key === 'Escape' && this.internalOpen()) {
96
+ this.internalOpen.set(false);
97
+ this.query.set('');
98
+ }
99
+ }
100
+ onSearch(e) {
101
+ const target = e.target;
102
+ this.query.set(target.value);
103
+ }
104
+ close() {
105
+ if (this.open() === undefined)
106
+ this.internalOpen.set(false);
107
+ this.query.set('');
108
+ }
109
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: HotkeyCheatsheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
110
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: HotkeyCheatsheetComponent, isStandalone: true, selector: "ngr-hotkey-cheatsheet", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown": "onKey($event)" } }, ngImport: i0, template: `
111
+ @if (visible()) {
112
+ <div
113
+ class="ngr-hotkey-cheatsheet"
114
+ [ngClass]="'ngr-hotkey-cheatsheet--' + (mac ? 'mac' : 'pc')"
115
+ role="dialog"
116
+ aria-label="Keyboard shortcuts"
117
+ >
118
+ <div class="ngr-hotkey-cheatsheet__backdrop" (click)="close()"></div>
119
+ <div class="ngr-hotkey-cheatsheet__panel">
120
+ <header class="ngr-hotkey-cheatsheet__header">
121
+ <h2 class="ngr-hotkey-cheatsheet__title">{{ title() }}</h2>
122
+ <button
123
+ type="button"
124
+ class="ngr-hotkey-cheatsheet__close"
125
+ aria-label="Close"
126
+ (click)="close()"
127
+ >
128
+ ×
129
+ </button>
130
+ </header>
131
+ @if (searchable()) {
132
+ <div class="ngr-hotkey-cheatsheet__search">
133
+ <input
134
+ type="text"
135
+ class="ngr-hotkey-cheatsheet__search-input"
136
+ placeholder="Search shortcuts…"
137
+ aria-label="Search shortcuts"
138
+ [value]="query()"
139
+ (input)="onSearch($event)"
140
+ #searchInput
141
+ />
142
+ </div>
143
+ }
144
+ <div class="ngr-hotkey-cheatsheet__body">
145
+ @if (filteredGroups().length === 0) {
146
+ <p class="ngr-hotkey-cheatsheet__empty">
147
+ @if (query()) {
148
+ No shortcuts match "{{ query() }}".
149
+ } @else {
150
+ No shortcuts registered yet.
151
+ }
152
+ </p>
153
+ }
154
+ @for (group of filteredGroups(); track group.scope) {
155
+ <section class="ngr-hotkey-cheatsheet__group">
156
+ <h3 class="ngr-hotkey-cheatsheet__group-title">{{ group.scope }}</h3>
157
+ @for (sub of group.subgroups; track sub.category) {
158
+ @if (sub.category !== null) {
159
+ <h4 class="ngr-hotkey-cheatsheet__category">{{ sub.category }}</h4>
160
+ }
161
+ <dl class="ngr-hotkey-cheatsheet__list">
162
+ @for (entry of sub.entries; track entry.primary + entry.description) {
163
+ <div class="ngr-hotkey-cheatsheet__row">
164
+ <dt class="ngr-hotkey-cheatsheet__keys">
165
+ <kbd>{{ entry.primaryDisplay }}</kbd>
166
+ @for (alias of entry.aliases; track alias.combo) {
167
+ <span class="ngr-hotkey-cheatsheet__alias">
168
+ <kbd>{{ alias.display }}</kbd>
169
+ </span>
170
+ }
171
+ </dt>
172
+ <dd class="ngr-hotkey-cheatsheet__desc">{{ entry.description }}</dd>
173
+ </div>
174
+ }
175
+ </dl>
176
+ }
177
+ </section>
178
+ }
179
+ </div>
180
+ </div>
181
+ </div>
182
+ }
183
+ `, isInline: true, styles: [":host{--ngr-hotkey-backdrop: rgba(15, 23, 42, .45);--ngr-hotkey-bg: #ffffff;--ngr-hotkey-fg: #0f172a;--ngr-hotkey-muted: rgba(15, 23, 42, .6);--ngr-hotkey-border: rgba(15, 23, 42, .08);--ngr-hotkey-kbd-bg: #f1f5f9;--ngr-hotkey-kbd-fg: #0f172a;--ngr-hotkey-input-bg: #ffffff;--ngr-hotkey-input-border: rgba(15, 23, 42, .16);--ngr-hotkey-radius: 12px}.ngr-hotkey-cheatsheet{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;color:var(--ngr-hotkey-fg);font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}.ngr-hotkey-cheatsheet__backdrop{position:absolute;inset:0;background:var(--ngr-hotkey-backdrop)}.ngr-hotkey-cheatsheet__panel{position:relative;background:var(--ngr-hotkey-bg);border-radius:var(--ngr-hotkey-radius);box-shadow:0 20px 50px #02061740;max-width:520px;width:calc(100% - 32px);max-height:calc(100vh - 64px);overflow:hidden;display:flex;flex-direction:column}.ngr-hotkey-cheatsheet__header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--ngr-hotkey-border)}.ngr-hotkey-cheatsheet__title{margin:0;font-size:16px;font-weight:600}.ngr-hotkey-cheatsheet__close{background:none;border:0;font-size:22px;line-height:1;cursor:pointer;color:var(--ngr-hotkey-muted);padding:4px 8px;border-radius:6px}.ngr-hotkey-cheatsheet__close:hover{background:var(--ngr-hotkey-kbd-bg)}.ngr-hotkey-cheatsheet__search{padding:12px 18px;border-bottom:1px solid var(--ngr-hotkey-border)}.ngr-hotkey-cheatsheet__search-input{width:100%;font:inherit;padding:8px 10px;border-radius:8px;border:1px solid var(--ngr-hotkey-input-border);background:var(--ngr-hotkey-input-bg);color:inherit;box-sizing:border-box}.ngr-hotkey-cheatsheet__search-input:focus{outline:2px solid rgba(37,99,235,.45);outline-offset:-1px;border-color:#2563eb99}.ngr-hotkey-cheatsheet__body{padding:8px 18px 18px;overflow-y:auto}.ngr-hotkey-cheatsheet__group+.ngr-hotkey-cheatsheet__group{margin-top:18px}.ngr-hotkey-cheatsheet__group-title{margin:12px 0 8px;font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ngr-hotkey-muted)}.ngr-hotkey-cheatsheet__category{margin:10px 0 4px;font-size:12px;font-weight:600;color:var(--ngr-hotkey-fg)}.ngr-hotkey-cheatsheet__list{margin:0}.ngr-hotkey-cheatsheet__row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:6px 0}.ngr-hotkey-cheatsheet__keys{margin:0;flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}.ngr-hotkey-cheatsheet__keys kbd{background:var(--ngr-hotkey-kbd-bg);color:var(--ngr-hotkey-kbd-fg);border-radius:6px;padding:3px 8px;font-family:inherit;font-size:12px;font-weight:600;white-space:nowrap}.ngr-hotkey-cheatsheet__alias{display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--ngr-hotkey-muted)}.ngr-hotkey-cheatsheet__alias:before{content:\"or\";opacity:.7}.ngr-hotkey-cheatsheet__alias kbd{opacity:.8}.ngr-hotkey-cheatsheet__desc{margin:0;text-align:right;color:var(--ngr-hotkey-fg)}.ngr-hotkey-cheatsheet__empty{margin:12px 0;color:var(--ngr-hotkey-muted)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
184
+ }
185
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: HotkeyCheatsheetComponent, decorators: [{
186
+ type: Component,
187
+ args: [{ selector: 'ngr-hotkey-cheatsheet', standalone: true, imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: `
188
+ @if (visible()) {
189
+ <div
190
+ class="ngr-hotkey-cheatsheet"
191
+ [ngClass]="'ngr-hotkey-cheatsheet--' + (mac ? 'mac' : 'pc')"
192
+ role="dialog"
193
+ aria-label="Keyboard shortcuts"
194
+ >
195
+ <div class="ngr-hotkey-cheatsheet__backdrop" (click)="close()"></div>
196
+ <div class="ngr-hotkey-cheatsheet__panel">
197
+ <header class="ngr-hotkey-cheatsheet__header">
198
+ <h2 class="ngr-hotkey-cheatsheet__title">{{ title() }}</h2>
199
+ <button
200
+ type="button"
201
+ class="ngr-hotkey-cheatsheet__close"
202
+ aria-label="Close"
203
+ (click)="close()"
204
+ >
205
+ ×
206
+ </button>
207
+ </header>
208
+ @if (searchable()) {
209
+ <div class="ngr-hotkey-cheatsheet__search">
210
+ <input
211
+ type="text"
212
+ class="ngr-hotkey-cheatsheet__search-input"
213
+ placeholder="Search shortcuts…"
214
+ aria-label="Search shortcuts"
215
+ [value]="query()"
216
+ (input)="onSearch($event)"
217
+ #searchInput
218
+ />
219
+ </div>
220
+ }
221
+ <div class="ngr-hotkey-cheatsheet__body">
222
+ @if (filteredGroups().length === 0) {
223
+ <p class="ngr-hotkey-cheatsheet__empty">
224
+ @if (query()) {
225
+ No shortcuts match "{{ query() }}".
226
+ } @else {
227
+ No shortcuts registered yet.
228
+ }
229
+ </p>
230
+ }
231
+ @for (group of filteredGroups(); track group.scope) {
232
+ <section class="ngr-hotkey-cheatsheet__group">
233
+ <h3 class="ngr-hotkey-cheatsheet__group-title">{{ group.scope }}</h3>
234
+ @for (sub of group.subgroups; track sub.category) {
235
+ @if (sub.category !== null) {
236
+ <h4 class="ngr-hotkey-cheatsheet__category">{{ sub.category }}</h4>
237
+ }
238
+ <dl class="ngr-hotkey-cheatsheet__list">
239
+ @for (entry of sub.entries; track entry.primary + entry.description) {
240
+ <div class="ngr-hotkey-cheatsheet__row">
241
+ <dt class="ngr-hotkey-cheatsheet__keys">
242
+ <kbd>{{ entry.primaryDisplay }}</kbd>
243
+ @for (alias of entry.aliases; track alias.combo) {
244
+ <span class="ngr-hotkey-cheatsheet__alias">
245
+ <kbd>{{ alias.display }}</kbd>
246
+ </span>
247
+ }
248
+ </dt>
249
+ <dd class="ngr-hotkey-cheatsheet__desc">{{ entry.description }}</dd>
250
+ </div>
251
+ }
252
+ </dl>
253
+ }
254
+ </section>
255
+ }
256
+ </div>
257
+ </div>
258
+ </div>
259
+ }
260
+ `, styles: [":host{--ngr-hotkey-backdrop: rgba(15, 23, 42, .45);--ngr-hotkey-bg: #ffffff;--ngr-hotkey-fg: #0f172a;--ngr-hotkey-muted: rgba(15, 23, 42, .6);--ngr-hotkey-border: rgba(15, 23, 42, .08);--ngr-hotkey-kbd-bg: #f1f5f9;--ngr-hotkey-kbd-fg: #0f172a;--ngr-hotkey-input-bg: #ffffff;--ngr-hotkey-input-border: rgba(15, 23, 42, .16);--ngr-hotkey-radius: 12px}.ngr-hotkey-cheatsheet{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;color:var(--ngr-hotkey-fg);font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}.ngr-hotkey-cheatsheet__backdrop{position:absolute;inset:0;background:var(--ngr-hotkey-backdrop)}.ngr-hotkey-cheatsheet__panel{position:relative;background:var(--ngr-hotkey-bg);border-radius:var(--ngr-hotkey-radius);box-shadow:0 20px 50px #02061740;max-width:520px;width:calc(100% - 32px);max-height:calc(100vh - 64px);overflow:hidden;display:flex;flex-direction:column}.ngr-hotkey-cheatsheet__header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--ngr-hotkey-border)}.ngr-hotkey-cheatsheet__title{margin:0;font-size:16px;font-weight:600}.ngr-hotkey-cheatsheet__close{background:none;border:0;font-size:22px;line-height:1;cursor:pointer;color:var(--ngr-hotkey-muted);padding:4px 8px;border-radius:6px}.ngr-hotkey-cheatsheet__close:hover{background:var(--ngr-hotkey-kbd-bg)}.ngr-hotkey-cheatsheet__search{padding:12px 18px;border-bottom:1px solid var(--ngr-hotkey-border)}.ngr-hotkey-cheatsheet__search-input{width:100%;font:inherit;padding:8px 10px;border-radius:8px;border:1px solid var(--ngr-hotkey-input-border);background:var(--ngr-hotkey-input-bg);color:inherit;box-sizing:border-box}.ngr-hotkey-cheatsheet__search-input:focus{outline:2px solid rgba(37,99,235,.45);outline-offset:-1px;border-color:#2563eb99}.ngr-hotkey-cheatsheet__body{padding:8px 18px 18px;overflow-y:auto}.ngr-hotkey-cheatsheet__group+.ngr-hotkey-cheatsheet__group{margin-top:18px}.ngr-hotkey-cheatsheet__group-title{margin:12px 0 8px;font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ngr-hotkey-muted)}.ngr-hotkey-cheatsheet__category{margin:10px 0 4px;font-size:12px;font-weight:600;color:var(--ngr-hotkey-fg)}.ngr-hotkey-cheatsheet__list{margin:0}.ngr-hotkey-cheatsheet__row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:6px 0}.ngr-hotkey-cheatsheet__keys{margin:0;flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}.ngr-hotkey-cheatsheet__keys kbd{background:var(--ngr-hotkey-kbd-bg);color:var(--ngr-hotkey-kbd-fg);border-radius:6px;padding:3px 8px;font-family:inherit;font-size:12px;font-weight:600;white-space:nowrap}.ngr-hotkey-cheatsheet__alias{display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--ngr-hotkey-muted)}.ngr-hotkey-cheatsheet__alias:before{content:\"or\";opacity:.7}.ngr-hotkey-cheatsheet__alias kbd{opacity:.8}.ngr-hotkey-cheatsheet__desc{margin:0;text-align:right;color:var(--ngr-hotkey-fg)}.ngr-hotkey-cheatsheet__empty{margin:12px 0;color:var(--ngr-hotkey-muted)}\n"] }]
261
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], onKey: [{
262
+ type: HostListener,
263
+ args: ['document:keydown', ['$event']]
264
+ }] } });
265
+ function isModified(e) {
266
+ return e.metaKey || e.ctrlKey || e.altKey;
267
+ }
268
+ function safeDisplay(combo, mac) {
269
+ try {
270
+ return comboToDisplay(combo, mac);
271
+ }
272
+ catch {
273
+ return combo;
274
+ }
275
+ }
276
+ function entryMatches(entry, q) {
277
+ if (entry.description.toLowerCase().includes(q))
278
+ return true;
279
+ if (entry.primary.toLowerCase().includes(q))
280
+ return true;
281
+ if (entry.primaryDisplay.toLowerCase().includes(q))
282
+ return true;
283
+ return entry.aliases.some((a) => a.combo.toLowerCase().includes(q) || a.display.toLowerCase().includes(q));
284
+ }
285
+ function groupByCategory(entries) {
286
+ const byCategory = new Map();
287
+ for (const entry of entries) {
288
+ const list = byCategory.get(entry.category);
289
+ if (list === undefined)
290
+ byCategory.set(entry.category, [entry]);
291
+ else
292
+ list.push(entry);
293
+ }
294
+ // Uncategorized first, then categorized alphabetically.
295
+ const uncategorized = byCategory.get(null);
296
+ const result = [];
297
+ if (uncategorized !== undefined) {
298
+ result.push({ category: null, entries: uncategorized });
299
+ }
300
+ for (const [category, list] of [...byCategory.entries()]
301
+ .filter(([c]) => c !== null)
302
+ .sort(([a], [b]) => a.localeCompare(b))) {
303
+ result.push({ category, entries: list });
304
+ }
305
+ return result;
306
+ }
307
+
308
+ /*
309
+ * Public API surface of @ngrithms/hotkeys/cheatsheet
310
+ */
311
+
312
+ /**
313
+ * Generated bundle index. Do not edit.
314
+ */
315
+
316
+ export { HotkeyCheatsheetComponent };
317
+ //# sourceMappingURL=ngrithms-hotkeys-cheatsheet.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngrithms-hotkeys-cheatsheet.mjs","sources":["../../../projects/hotkeys/cheatsheet/src/lib/hotkey-cheatsheet.component.ts","../../../projects/hotkeys/cheatsheet/src/public-api.ts","../../../projects/hotkeys/cheatsheet/src/ngrithms-hotkeys-cheatsheet.ts"],"sourcesContent":["import { NgClass } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n HostListener,\n computed,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport {\n HOTKEY_CONFIG,\n HotkeysService,\n comboToDisplay,\n isMacPlatform,\n type HotkeyBinding,\n} from '@ngrithms/hotkeys';\n\ninterface CheatsheetEntry {\n /** First / primary combo, shown prominently. */\n primary: string;\n /** Display string for the primary combo. */\n primaryDisplay: string;\n /** Additional combos that fire the same handler — shown as muted \"also\" badges. */\n aliases: ReadonlyArray<{ combo: string; display: string }>;\n scope: string;\n category: string | null;\n description: string;\n}\n\ninterface CheatsheetSubgroup {\n /** Sub-grouping label within a scope. `null` = no category (rendered before categorized rows). */\n category: string | null;\n entries: CheatsheetEntry[];\n}\n\ninterface CheatsheetGroup {\n scope: string;\n subgroups: CheatsheetSubgroup[];\n}\n\n/**\n * Drop-in cheatsheet overlay listing every registered hotkey that has a\n * `description`. Toggle with the `cheatsheetHotkey` config combo (default `'?'`)\n * or by setting `[open]` programmatically.\n *\n * ```html\n * <ngr-hotkey-cheatsheet /> <!-- toggled by '?' -->\n * <ngr-hotkey-cheatsheet [open]=\"show()\" /> <!-- controlled -->\n * <ngr-hotkey-cheatsheet [searchable]=\"true\" /> <!-- adds a filter input -->\n * ```\n *\n * Bindings with the same `category` (set via `hotkeyCategory=\"...\"` on the\n * directive, or `category` on the service) appear under a shared subheader\n * within their scope.\n *\n * Themable via CSS custom properties — see the component styles for the\n * available `--ngr-hotkey-*` vars.\n */\n@Component({\n selector: 'ngr-hotkey-cheatsheet',\n standalone: true,\n imports: [NgClass],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (visible()) {\n <div\n class=\"ngr-hotkey-cheatsheet\"\n [ngClass]=\"'ngr-hotkey-cheatsheet--' + (mac ? 'mac' : 'pc')\"\n role=\"dialog\"\n aria-label=\"Keyboard shortcuts\"\n >\n <div class=\"ngr-hotkey-cheatsheet__backdrop\" (click)=\"close()\"></div>\n <div class=\"ngr-hotkey-cheatsheet__panel\">\n <header class=\"ngr-hotkey-cheatsheet__header\">\n <h2 class=\"ngr-hotkey-cheatsheet__title\">{{ title() }}</h2>\n <button\n type=\"button\"\n class=\"ngr-hotkey-cheatsheet__close\"\n aria-label=\"Close\"\n (click)=\"close()\"\n >\n ×\n </button>\n </header>\n @if (searchable()) {\n <div class=\"ngr-hotkey-cheatsheet__search\">\n <input\n type=\"text\"\n class=\"ngr-hotkey-cheatsheet__search-input\"\n placeholder=\"Search shortcuts…\"\n aria-label=\"Search shortcuts\"\n [value]=\"query()\"\n (input)=\"onSearch($event)\"\n #searchInput\n />\n </div>\n }\n <div class=\"ngr-hotkey-cheatsheet__body\">\n @if (filteredGroups().length === 0) {\n <p class=\"ngr-hotkey-cheatsheet__empty\">\n @if (query()) {\n No shortcuts match \"{{ query() }}\".\n } @else {\n No shortcuts registered yet.\n }\n </p>\n }\n @for (group of filteredGroups(); track group.scope) {\n <section class=\"ngr-hotkey-cheatsheet__group\">\n <h3 class=\"ngr-hotkey-cheatsheet__group-title\">{{ group.scope }}</h3>\n @for (sub of group.subgroups; track sub.category) {\n @if (sub.category !== null) {\n <h4 class=\"ngr-hotkey-cheatsheet__category\">{{ sub.category }}</h4>\n }\n <dl class=\"ngr-hotkey-cheatsheet__list\">\n @for (entry of sub.entries; track entry.primary + entry.description) {\n <div class=\"ngr-hotkey-cheatsheet__row\">\n <dt class=\"ngr-hotkey-cheatsheet__keys\">\n <kbd>{{ entry.primaryDisplay }}</kbd>\n @for (alias of entry.aliases; track alias.combo) {\n <span class=\"ngr-hotkey-cheatsheet__alias\">\n <kbd>{{ alias.display }}</kbd>\n </span>\n }\n </dt>\n <dd class=\"ngr-hotkey-cheatsheet__desc\">{{ entry.description }}</dd>\n </div>\n }\n </dl>\n }\n </section>\n }\n </div>\n </div>\n </div>\n }\n `,\n styles: [\n `\n :host {\n --ngr-hotkey-backdrop: rgba(15, 23, 42, 0.45);\n --ngr-hotkey-bg: #ffffff;\n --ngr-hotkey-fg: #0f172a;\n --ngr-hotkey-muted: rgba(15, 23, 42, 0.6);\n --ngr-hotkey-border: rgba(15, 23, 42, 0.08);\n --ngr-hotkey-kbd-bg: #f1f5f9;\n --ngr-hotkey-kbd-fg: #0f172a;\n --ngr-hotkey-input-bg: #ffffff;\n --ngr-hotkey-input-border: rgba(15, 23, 42, 0.16);\n --ngr-hotkey-radius: 12px;\n }\n .ngr-hotkey-cheatsheet {\n position: fixed;\n inset: 0;\n z-index: 9999;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--ngr-hotkey-fg);\n font: 14px/1.4 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n }\n .ngr-hotkey-cheatsheet__backdrop {\n position: absolute;\n inset: 0;\n background: var(--ngr-hotkey-backdrop);\n }\n .ngr-hotkey-cheatsheet__panel {\n position: relative;\n background: var(--ngr-hotkey-bg);\n border-radius: var(--ngr-hotkey-radius);\n box-shadow: 0 20px 50px rgba(2, 6, 23, 0.25);\n max-width: 520px;\n width: calc(100% - 32px);\n max-height: calc(100vh - 64px);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n .ngr-hotkey-cheatsheet__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--ngr-hotkey-border);\n }\n .ngr-hotkey-cheatsheet__title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n }\n .ngr-hotkey-cheatsheet__close {\n background: none;\n border: 0;\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n color: var(--ngr-hotkey-muted);\n padding: 4px 8px;\n border-radius: 6px;\n }\n .ngr-hotkey-cheatsheet__close:hover {\n background: var(--ngr-hotkey-kbd-bg);\n }\n .ngr-hotkey-cheatsheet__search {\n padding: 12px 18px;\n border-bottom: 1px solid var(--ngr-hotkey-border);\n }\n .ngr-hotkey-cheatsheet__search-input {\n width: 100%;\n font: inherit;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--ngr-hotkey-input-border);\n background: var(--ngr-hotkey-input-bg);\n color: inherit;\n box-sizing: border-box;\n }\n .ngr-hotkey-cheatsheet__search-input:focus {\n outline: 2px solid rgba(37, 99, 235, 0.45);\n outline-offset: -1px;\n border-color: rgba(37, 99, 235, 0.6);\n }\n .ngr-hotkey-cheatsheet__body {\n padding: 8px 18px 18px;\n overflow-y: auto;\n }\n .ngr-hotkey-cheatsheet__group + .ngr-hotkey-cheatsheet__group {\n margin-top: 18px;\n }\n .ngr-hotkey-cheatsheet__group-title {\n margin: 12px 0 8px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--ngr-hotkey-muted);\n }\n .ngr-hotkey-cheatsheet__category {\n margin: 10px 0 4px;\n font-size: 12px;\n font-weight: 600;\n color: var(--ngr-hotkey-fg);\n }\n .ngr-hotkey-cheatsheet__list {\n margin: 0;\n }\n .ngr-hotkey-cheatsheet__row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 6px 0;\n }\n .ngr-hotkey-cheatsheet__keys {\n margin: 0;\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n }\n .ngr-hotkey-cheatsheet__keys kbd {\n background: var(--ngr-hotkey-kbd-bg);\n color: var(--ngr-hotkey-kbd-fg);\n border-radius: 6px;\n padding: 3px 8px;\n font-family: inherit;\n font-size: 12px;\n font-weight: 600;\n white-space: nowrap;\n }\n .ngr-hotkey-cheatsheet__alias {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n color: var(--ngr-hotkey-muted);\n }\n .ngr-hotkey-cheatsheet__alias::before {\n content: 'or';\n opacity: 0.7;\n }\n .ngr-hotkey-cheatsheet__alias kbd {\n opacity: 0.8;\n }\n .ngr-hotkey-cheatsheet__desc {\n margin: 0;\n text-align: right;\n color: var(--ngr-hotkey-fg);\n }\n .ngr-hotkey-cheatsheet__empty {\n margin: 12px 0;\n color: var(--ngr-hotkey-muted);\n }\n `,\n ],\n})\nexport class HotkeyCheatsheetComponent {\n private readonly hotkeys = inject(HotkeysService);\n private readonly config = inject(HOTKEY_CONFIG);\n\n /** Cheatsheet title. */\n readonly title = input<string>('Keyboard shortcuts');\n /** Externally control visibility. When `undefined`, the component manages its own state. */\n readonly open = input<boolean | undefined>(undefined);\n /** Show a search input that filters bindings by description / combo. Default `false`. */\n readonly searchable = input<boolean>(false);\n\n protected readonly mac = isMacPlatform();\n private readonly internalOpen = signal(false);\n protected readonly query = signal('');\n\n protected readonly visible = computed(() => this.open() ?? this.internalOpen());\n\n protected readonly groups = computed<CheatsheetGroup[]>(() => {\n const byScope = new Map<string, CheatsheetEntry[]>();\n for (const b of this.hotkeys.registered() as readonly HotkeyBinding[]) {\n if (!b.description) continue;\n const scope = b.scope ?? 'global';\n const combos = typeof b.keys === 'string' ? [b.keys] : [...b.keys];\n if (combos.length === 0) continue;\n const [primary, ...rest] = combos;\n const entry: CheatsheetEntry = {\n primary,\n primaryDisplay: safeDisplay(primary, this.mac),\n aliases: rest.map((combo) => ({ combo, display: safeDisplay(combo, this.mac) })),\n scope,\n category: b.category ?? null,\n description: b.description,\n };\n const list = byScope.get(scope);\n if (list === undefined) byScope.set(scope, [entry]);\n else list.push(entry);\n }\n return [...byScope.entries()]\n .sort(([a], [b]) => (a === 'global' ? -1 : b === 'global' ? 1 : a.localeCompare(b)))\n .map(([scope, entries]) => ({ scope, subgroups: groupByCategory(entries) }));\n });\n\n protected readonly filteredGroups = computed<CheatsheetGroup[]>(() => {\n const q = this.query().trim().toLowerCase();\n if (q === '') return this.groups();\n return this.groups()\n .map((g) => ({\n scope: g.scope,\n subgroups: g.subgroups\n .map((sub) => ({\n category: sub.category,\n entries: sub.entries.filter((e) => entryMatches(e, q)),\n }))\n .filter((sub) => sub.entries.length > 0),\n }))\n .filter((g) => g.subgroups.length > 0);\n });\n\n @HostListener('document:keydown', ['$event'])\n protected onKey(e: KeyboardEvent): void {\n if (this.open() !== undefined) return;\n const trigger = this.config.cheatsheetHotkey;\n // Ignore the trigger key when typed inside the search field — let the user type a `?`.\n const inSearch =\n e.target instanceof HTMLElement &&\n e.target.classList.contains('ngr-hotkey-cheatsheet__search-input');\n if (!inSearch && trigger !== null && e.key === trigger && !isModified(e)) {\n e.preventDefault();\n this.internalOpen.update((v) => !v);\n if (!this.internalOpen()) this.query.set('');\n return;\n }\n if (e.key === 'Escape' && this.internalOpen()) {\n this.internalOpen.set(false);\n this.query.set('');\n }\n }\n\n protected onSearch(e: Event): void {\n const target = e.target as HTMLInputElement;\n this.query.set(target.value);\n }\n\n protected close(): void {\n if (this.open() === undefined) this.internalOpen.set(false);\n this.query.set('');\n }\n}\n\nfunction isModified(e: KeyboardEvent): boolean {\n return e.metaKey || e.ctrlKey || e.altKey;\n}\n\nfunction safeDisplay(combo: string, mac: boolean): string {\n try {\n return comboToDisplay(combo, mac);\n } catch {\n return combo;\n }\n}\n\nfunction entryMatches(entry: CheatsheetEntry, q: string): boolean {\n if (entry.description.toLowerCase().includes(q)) return true;\n if (entry.primary.toLowerCase().includes(q)) return true;\n if (entry.primaryDisplay.toLowerCase().includes(q)) return true;\n return entry.aliases.some(\n (a) => a.combo.toLowerCase().includes(q) || a.display.toLowerCase().includes(q),\n );\n}\n\nfunction groupByCategory(entries: CheatsheetEntry[]): CheatsheetSubgroup[] {\n const byCategory = new Map<string | null, CheatsheetEntry[]>();\n for (const entry of entries) {\n const list = byCategory.get(entry.category);\n if (list === undefined) byCategory.set(entry.category, [entry]);\n else list.push(entry);\n }\n // Uncategorized first, then categorized alphabetically.\n const uncategorized = byCategory.get(null);\n const result: CheatsheetSubgroup[] = [];\n if (uncategorized !== undefined) {\n result.push({ category: null, entries: uncategorized });\n }\n for (const [category, list] of [...byCategory.entries()]\n .filter(([c]) => c !== null)\n .sort(([a], [b]) => (a as string).localeCompare(b as string))) {\n result.push({ category, entries: list });\n }\n return result;\n}\n","/*\n * Public API surface of @ngrithms/hotkeys/cheatsheet\n */\n\nexport { HotkeyCheatsheetComponent } from './lib/hotkey-cheatsheet.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}