@tocdk-oss/ng-spotlight 0.0.0-stage → 21.0.1

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,416 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injector, Signal, EnvironmentProviders, InjectionToken, Type } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+ import { Route, Routes } from '@angular/router';
5
+
6
+ /** Visual tone of a badge. The renderer maps it to its own colours. */
7
+ type SpotlightBadgeTone = 'info' | 'success' | 'warn' | 'danger';
8
+ interface SpotlightBadge {
9
+ label: string;
10
+ tone?: SpotlightBadgeTone;
11
+ }
12
+ /** What happens when an item is selected. */
13
+ type SpotlightAction = {
14
+ route: unknown[] | string;
15
+ } | {
16
+ url: string;
17
+ newTab?: boolean;
18
+ } | {
19
+ run: () => void | Promise<void>;
20
+ };
21
+ interface SpotlightItem {
22
+ id: string;
23
+ title: string;
24
+ subtitle?: string;
25
+ /** The renderer decides the meaning (CSS class name, image URL, …). */
26
+ icon?: string;
27
+ group: string;
28
+ keywords?: string[];
29
+ badges?: SpotlightBadge[];
30
+ /** Shown on an empty query. */
31
+ suggested?: boolean;
32
+ /** Free payload for custom templates. */
33
+ data?: unknown;
34
+ action: SpotlightAction;
35
+ /** Reserved for nested pages (v1.x). Must be absent in v1: items that set it are dropped. */
36
+ children?: never;
37
+ }
38
+ /** An item as written for staticSource(): `group` falls back to the source's group. */
39
+ type SpotlightItemInput = Omit<SpotlightItem, 'group'> & {
40
+ group?: string;
41
+ };
42
+ interface SpotlightGroup {
43
+ id: string;
44
+ label: string;
45
+ total?: number;
46
+ items: SpotlightItem[];
47
+ }
48
+ interface SpotlightResponse {
49
+ groups: SpotlightGroup[];
50
+ }
51
+ type SpotlightResult = SpotlightItem[] | SpotlightResponse;
52
+ interface SpotlightContext {
53
+ /** Aborted when the next keystroke supersedes this search. */
54
+ signal: AbortSignal;
55
+ /** Group id of the active chip, or null for "All". */
56
+ chip: string | null;
57
+ /** Environment injector of the provideSpotlight() call that registered the source. */
58
+ injector: Injector;
59
+ }
60
+ interface SpotlightSource {
61
+ id: string;
62
+ /** Default group for items that do not set one. */
63
+ group?: string;
64
+ /** e.g. '#', '@'. When the query starts with it, only this source runs. */
65
+ prefix?: string;
66
+ /** Default 0 (httpSource: 2). */
67
+ minLength?: number;
68
+ /** Default 0 (httpSource: 150). */
69
+ debounceMs?: number;
70
+ /** Default 5000. */
71
+ timeoutMs?: number;
72
+ /** Group order; lower comes first. Default: registration order. */
73
+ priority?: number;
74
+ /** true: the library ranks and filters the results. false (default): keep the source's order. */
75
+ local?: boolean;
76
+ search(query: string, ctx: SpotlightContext): Observable<SpotlightResult> | Promise<SpotlightResult> | SpotlightResult;
77
+ }
78
+ type SpotlightTextValue = string | Signal<string> | Observable<string>;
79
+ interface SpotlightTexts {
80
+ placeholder: SpotlightTextValue;
81
+ all: SpotlightTextValue;
82
+ recent: SpotlightTextValue;
83
+ noResults: SpotlightTextValue;
84
+ loading: SpotlightTextValue;
85
+ error: SpotlightTextValue;
86
+ retry: SpotlightTextValue;
87
+ /** Called inside a computed: reading signals here makes the announcement reactive. */
88
+ resultsAnnouncement: (count: number) => string;
89
+ hintNavigate: SpotlightTextValue;
90
+ hintOpen: SpotlightTextValue;
91
+ hintClose: SpotlightTextValue;
92
+ }
93
+ interface SpotlightHttpRequest {
94
+ method?: string;
95
+ params?: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>>;
96
+ headers?: Record<string, string | string[]>;
97
+ body?: unknown;
98
+ }
99
+ interface SpotlightHttpOptions {
100
+ request?: (query: string, ctx: SpotlightContext) => SpotlightHttpRequest;
101
+ map?: (response: unknown, query: string) => SpotlightResult;
102
+ }
103
+ /** The serialisable subset of SpotlightAction. */
104
+ type SpotlightStoredAction = {
105
+ route: unknown[] | string;
106
+ } | {
107
+ url: string;
108
+ newTab?: boolean;
109
+ };
110
+ interface SpotlightRecentEntry {
111
+ id: string;
112
+ title: string;
113
+ group: string;
114
+ sourceId: string;
115
+ subtitle?: string;
116
+ icon?: string;
117
+ badges?: SpotlightBadge[];
118
+ /** Absent for run() items: those are resolved through their source by id. */
119
+ action?: SpotlightStoredAction;
120
+ }
121
+ interface SpotlightRecentStorage {
122
+ load(): Promise<SpotlightRecentEntry[]>;
123
+ save(items: SpotlightRecentEntry[]): Promise<void>;
124
+ }
125
+ /**
126
+ * Configuration of one provideSpotlight() call. `hotkeys`, `texts`, `recent`, `theme` and `onError`
127
+ * belong to the palette and are read only from the first call (the root or an `isolated` one); a
128
+ * non-isolated child call only adds its `sources` and `http` defaults.
129
+ */
130
+ interface SpotlightConfig {
131
+ /** Default ['ctrl.space', 'mod.k']. 'mod' = Cmd on macOS, Ctrl elsewhere. */
132
+ hotkeys?: string[];
133
+ texts?: Partial<SpotlightTexts>;
134
+ recent?: {
135
+ max?: number;
136
+ storage?: SpotlightRecentStorage;
137
+ };
138
+ /** Global defaults for every httpSource registered by this call (inherited by child calls). */
139
+ http?: SpotlightHttpOptions;
140
+ theme?: 'default' | 'none';
141
+ onError?: (error: unknown, source: SpotlightSource) => void;
142
+ sources?: SpotlightSource[];
143
+ /** true: create a separate palette instead of adding sources to the parent injector's one. */
144
+ isolated?: boolean;
145
+ }
146
+
147
+ /**
148
+ * Recent items in localStorage under `ng-spotlight:recent:<key>`. Every access is wrapped
149
+ * in try/catch, so the palette still works when storage is blocked, full or corrupted.
150
+ */
151
+ declare function localStorageRecent(key: string): SpotlightRecentStorage;
152
+
153
+ interface StaticSourceOptions {
154
+ /** Default `static:<group>`. */
155
+ id?: string;
156
+ group: string;
157
+ items: SpotlightItemInput[];
158
+ prefix?: string;
159
+ priority?: number;
160
+ }
161
+ /** Fixed commands. Results are local: the library ranks and filters them. */
162
+ declare function staticSource(options: StaticSourceOptions): SpotlightSource;
163
+
164
+ interface RouteSourceOptions {
165
+ /** Default 'routes'. */
166
+ id?: string;
167
+ /** Default 'Pages'. */
168
+ group?: string;
169
+ prefix?: string;
170
+ priority?: number;
171
+ /** Return false to hide a route, e.g. to apply the app's role gates. */
172
+ filter?: (route: Route, url: string) => boolean;
173
+ }
174
+ /**
175
+ * Every labelled static route that is a page, including lazy children once loaded. A labelled
176
+ * parent that renders nothing at its own URL (no component, no empty-path child) is skipped;
177
+ * its labelled children still appear. A deeper route with the same URL (an empty-path child)
178
+ * wins over its parent. Results are local.
179
+ */
180
+ declare function routeSource(options?: RouteSourceOptions): SpotlightSource;
181
+
182
+ /** The shape of `route.data.spotlight` (or `false` to opt the route out). */
183
+ interface SpotlightRouteData {
184
+ label?: string;
185
+ keywords?: string[];
186
+ suggested?: boolean;
187
+ /** Not used by the library; apps use it in their own nav-coverage tests. */
188
+ hiddenFromNav?: boolean;
189
+ }
190
+
191
+ interface SpotlightRouteAuditEntry {
192
+ path: string;
193
+ reason: 'missing-label';
194
+ }
195
+ interface SpotlightRouteAuditOptions {
196
+ /** Return true to skip a route. */
197
+ ignore?: (route: Route, url: string) => boolean;
198
+ }
199
+ /**
200
+ * Static pages that have no label (title, data.spotlight.label or data.breadcrumb) and are not
201
+ * opted out with data.spotlight: false. Synchronous: `loadChildren` is not followed, so pass
202
+ * lazy route arrays in directly. A page whose URL a labelled parent already covers passes.
203
+ *
204
+ * expect(spotlightRouteAudit(adminRoutes)).toEqual([]);
205
+ */
206
+ declare function spotlightRouteAudit(routes: Routes, options?: SpotlightRouteAuditOptions): SpotlightRouteAuditEntry[];
207
+
208
+ interface HttpSourceOptions extends Partial<Omit<SpotlightSource, 'search'>>, SpotlightHttpOptions {
209
+ url: string;
210
+ }
211
+ /**
212
+ * Remote results in the server's order. Default request: GET {url}?q={query}[&chip={groupId}].
213
+ * Default response: SpotlightResponse or SpotlightItem[]. Per-source request/map win over
214
+ * provideSpotlight({ http }), which wins over the defaults.
215
+ */
216
+ declare function httpSource(options: HttpSourceOptions): SpotlightSource;
217
+
218
+ /**
219
+ * Sets up a palette. Call it once in the app config; call it again in a child injector (for
220
+ * example a lazy route's `providers`) to add sources there. A child call only contributes
221
+ * `sources` and `http` defaults: its `texts`, `hotkeys`, `recent`, `theme` and `onError` are
222
+ * ignored (dev mode warns), since the first call owns the palette. Pass `isolated: true` for a
223
+ * separate palette with its own settings. Pass a factory to build the config in an injection
224
+ * context, e.g. `provideSpotlight(() => ({ texts: { placeholder: inject(I18n).t$('Search…') } }))`.
225
+ */
226
+ declare function provideSpotlight(config?: SpotlightConfig | (() => SpotlightConfig)): EnvironmentProviders;
227
+
228
+ /**
229
+ * State and behaviour of one palette. Provided by provideSpotlight(); never providedIn root.
230
+ * Deliberately has no ngOnDestroy: a child injector re-provides the parent's instance and
231
+ * must not destroy it. Cleanup runs through the creating injector's DestroyRef.
232
+ */
233
+ declare class SpotlightService {
234
+ private readonly document;
235
+ private readonly router;
236
+ private readonly regs;
237
+ private registrationOrder;
238
+ private configured;
239
+ /** Token of the select() whose action is still running; null when none is. */
240
+ private selecting;
241
+ /** Incremented on every open(), so a late action never closes a palette opened after it. */
242
+ private session;
243
+ private _settings;
244
+ private readonly _texts;
245
+ private readonly _isOpen;
246
+ readonly isOpen: i0.Signal<boolean>;
247
+ private readonly _query;
248
+ private readonly _chip;
249
+ private readonly _results;
250
+ private readonly _recent;
251
+ private readonly _resolvedRuns;
252
+ private readonly _activeKey;
253
+ private readonly _chipGroups;
254
+ private readonly selectedSubject;
255
+ readonly selected: Observable<SpotlightItem>;
256
+ private input$;
257
+ private readonly retry$;
258
+ private engine;
259
+ /** Group labels learned from answers; outlives the engine, which restarts on every open(). */
260
+ private readonly learnedGroups;
261
+ private readonly recentItems;
262
+ constructor();
263
+ open(query?: string): void;
264
+ close(): void;
265
+ private runAction;
266
+ private remember;
267
+ private loadRecent;
268
+ /** run() entries are stored without an action; look them up in their source by id. */
269
+ private resolveRunEntries;
270
+ private restartEngine;
271
+ private report;
272
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightService, never>;
273
+ static ɵprov: i0.ɵɵInjectableDeclaration<SpotlightService>;
274
+ }
275
+
276
+ /** Context of the `input` part. The part must copy every ARIA field onto its <input>. */
277
+ interface SpotlightInputContext {
278
+ id: string;
279
+ value: string;
280
+ placeholder: string;
281
+ label: string;
282
+ expanded: boolean;
283
+ controls: string;
284
+ activeDescendant: string | null;
285
+ onInput: (value: string) => void;
286
+ onKeydown: (event: KeyboardEvent) => void;
287
+ }
288
+ /** Context of the `chips` part (rendered once per chip, inside a host-owned <button>). */
289
+ interface SpotlightChipContext {
290
+ label: string;
291
+ active: boolean;
292
+ }
293
+ interface SpotlightGroupHeaderContext {
294
+ label: string;
295
+ total: number | undefined;
296
+ loading: boolean;
297
+ /** Visually hidden text read after the label while loading; empty when the label already says it. */
298
+ loadingText: string;
299
+ }
300
+ /** Context of the `row` part (rendered inside a host-owned role="option" element). */
301
+ interface SpotlightRowContext {
302
+ item: SpotlightItem;
303
+ active: boolean;
304
+ query: string;
305
+ /** The badge part to render badges with (NgComponentOutlet, input `ctx: SpotlightBadgeContext`). */
306
+ badge: Type<unknown>;
307
+ }
308
+ interface SpotlightBadgeContext {
309
+ badge: SpotlightBadge;
310
+ }
311
+ /**
312
+ * Markup for each part of the palette. Every component takes one `ctx` input with the
313
+ * part's context. Parts left out fall back to the core parts. `dialog` adds classes to the
314
+ * overlay pane and backdrop (the core always owns the CDK overlay itself).
315
+ */
316
+ interface SpotlightRenderer {
317
+ dialog?: {
318
+ panelClass: string;
319
+ backdropClass?: string;
320
+ };
321
+ input?: Type<unknown>;
322
+ chips?: Type<unknown>;
323
+ groupHeader?: Type<unknown>;
324
+ row?: Type<unknown>;
325
+ badge?: Type<unknown>;
326
+ }
327
+ declare const SPOTLIGHT_RENDERER: InjectionToken<SpotlightRenderer>;
328
+
329
+ declare class SpotlightDefaultInput {
330
+ readonly ctx: i0.InputSignal<SpotlightInputContext>;
331
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightDefaultInput, never>;
332
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpotlightDefaultInput, "spotlight-default-input", never, { "ctx": { "alias": "ctx"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
333
+ }
334
+ declare class SpotlightDefaultChip {
335
+ readonly ctx: i0.InputSignal<SpotlightChipContext>;
336
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightDefaultChip, never>;
337
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpotlightDefaultChip, "spotlight-default-chip", never, { "ctx": { "alias": "ctx"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
338
+ }
339
+ declare class SpotlightDefaultGroupHeader {
340
+ readonly ctx: i0.InputSignal<SpotlightGroupHeaderContext>;
341
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightDefaultGroupHeader, never>;
342
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpotlightDefaultGroupHeader, "spotlight-default-group-header", never, { "ctx": { "alias": "ctx"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
343
+ }
344
+ declare class SpotlightDefaultBadge {
345
+ readonly ctx: i0.InputSignal<SpotlightBadgeContext>;
346
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightDefaultBadge, never>;
347
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpotlightDefaultBadge, "spotlight-default-badge", never, { "ctx": { "alias": "ctx"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
348
+ }
349
+ declare class SpotlightDefaultRow {
350
+ readonly ctx: i0.InputSignal<SpotlightRowContext>;
351
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightDefaultRow, never>;
352
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpotlightDefaultRow, "spotlight-default-row", never, { "ctx": { "alias": "ctx"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
353
+ }
354
+
355
+ /** The palette. Place it once in the app shell. */
356
+ declare class SpotlightHost {
357
+ private readonly overlay;
358
+ private readonly focusTrapFactory;
359
+ private readonly breakpoints;
360
+ private readonly viewContainerRef;
361
+ private readonly injector;
362
+ private readonly document;
363
+ private readonly arbiter;
364
+ private readonly hotkeyClient;
365
+ private readonly panel;
366
+ private readonly itemTemplates;
367
+ private overlayRef;
368
+ private focusTrap;
369
+ private restoreFocus;
370
+ private readonly handleInput;
371
+ private readonly handleKeydown;
372
+ constructor();
373
+ private onInputKeydown;
374
+ private attach;
375
+ private detach;
376
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightHost, never>;
377
+ static ɵcmp: i0.ɵɵComponentDeclaration<SpotlightHost, "spotlight-host", never, {}, {}, ["itemTemplates", "emptyTemplate", "footerTemplate"], never, true, never>;
378
+ }
379
+
380
+ interface SpotlightItemTemplateContext {
381
+ $implicit: SpotlightItem;
382
+ active: boolean;
383
+ query: string;
384
+ }
385
+ interface SpotlightEmptyTemplateContext {
386
+ /** The current query. */
387
+ $implicit: string;
388
+ }
389
+ /** `<ng-template spotlightItem="customers" let-item>`: row for one group; without a value: for every group. */
390
+ declare class SpotlightItemTemplate {
391
+ readonly spotlightItem: i0.InputSignal<string>;
392
+ static ngTemplateContextGuard(_dir: SpotlightItemTemplate, _ctx: unknown): _ctx is SpotlightItemTemplateContext;
393
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightItemTemplate, never>;
394
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SpotlightItemTemplate, "[spotlightItem]", never, { "spotlightItem": { "alias": "spotlightItem"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
395
+ }
396
+ /** `<ng-template spotlightEmpty let-query>`: shown when a non-empty query has no results. */
397
+ declare class SpotlightEmptyTemplate {
398
+ static ngTemplateContextGuard(_dir: SpotlightEmptyTemplate, _ctx: unknown): _ctx is SpotlightEmptyTemplateContext;
399
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightEmptyTemplate, never>;
400
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SpotlightEmptyTemplate, "[spotlightEmpty]", never, {}, {}, never, never, true, never>;
401
+ }
402
+ /** `<ng-template spotlightFooter>`: replaces the default key hints. */
403
+ declare class SpotlightFooterTemplate {
404
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightFooterTemplate, never>;
405
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SpotlightFooterTemplate, "[spotlightFooter]", never, {}, {}, never, never, true, never>;
406
+ }
407
+ /** Opens the palette on click. `spotlightTrigger="#"` opens it with an initial query. */
408
+ declare class SpotlightTrigger {
409
+ private readonly svc;
410
+ readonly spotlightTrigger: i0.InputSignal<string>;
411
+ static ɵfac: i0.ɵɵFactoryDeclaration<SpotlightTrigger, never>;
412
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SpotlightTrigger, "[spotlightTrigger]", never, { "spotlightTrigger": { "alias": "spotlightTrigger"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
413
+ }
414
+
415
+ export { SPOTLIGHT_RENDERER, SpotlightDefaultBadge, SpotlightDefaultChip, SpotlightDefaultGroupHeader, SpotlightDefaultInput, SpotlightDefaultRow, SpotlightEmptyTemplate, SpotlightFooterTemplate, SpotlightHost, SpotlightItemTemplate, SpotlightService, SpotlightTrigger, httpSource, localStorageRecent, provideSpotlight, routeSource, spotlightRouteAudit, staticSource };
416
+ export type { HttpSourceOptions, RouteSourceOptions, SpotlightAction, SpotlightBadge, SpotlightBadgeContext, SpotlightBadgeTone, SpotlightChipContext, SpotlightConfig, SpotlightContext, SpotlightEmptyTemplateContext, SpotlightGroup, SpotlightGroupHeaderContext, SpotlightHttpOptions, SpotlightHttpRequest, SpotlightInputContext, SpotlightItem, SpotlightItemInput, SpotlightItemTemplateContext, SpotlightRecentEntry, SpotlightRecentStorage, SpotlightRenderer, SpotlightResponse, SpotlightResult, SpotlightRouteAuditEntry, SpotlightRouteAuditOptions, SpotlightRouteData, SpotlightRowContext, SpotlightSource, SpotlightStoredAction, SpotlightTextValue, SpotlightTexts, StaticSourceOptions };