@flxgde/gigamenu 0.0.1 → 0.0.2
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.
- package/README.md +127 -127
- package/fesm2022/flxgde-gigamenu.mjs +645 -0
- package/fesm2022/flxgde-gigamenu.mjs.map +1 -0
- package/package.json +43 -39
- package/types/flxgde-gigamenu.d.ts +336 -0
- package/.github/workflows/publish.yml +0 -31
- package/ng-package.json +0 -8
- package/src/lib/frecency.service.ts +0 -185
- package/src/lib/gigamenu-templates.directive.ts +0 -178
- package/src/lib/gigamenu.component.html +0 -170
- package/src/lib/gigamenu.component.ts +0 -305
- package/src/lib/gigamenu.service.ts +0 -143
- package/src/lib/types.ts +0 -101
- package/src/public-api.ts +0 -6
- package/tsconfig.json +0 -14
- package/tsconfig.lib.json +0 -12
- package/tsconfig.lib.prod.json +0 -6
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
signal,
|
|
4
|
-
computed,
|
|
5
|
-
effect,
|
|
6
|
-
ElementRef,
|
|
7
|
-
viewChild,
|
|
8
|
-
contentChild,
|
|
9
|
-
HostListener,
|
|
10
|
-
PLATFORM_ID,
|
|
11
|
-
Inject,
|
|
12
|
-
TemplateRef,
|
|
13
|
-
} from '@angular/core';
|
|
14
|
-
import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
|
|
15
|
-
import { GigamenuService } from './gigamenu.service';
|
|
16
|
-
import { FrecencyService } from './frecency.service';
|
|
17
|
-
import { GigamenuItem } from './types';
|
|
18
|
-
import {
|
|
19
|
-
GigamenuItemTemplate,
|
|
20
|
-
GigamenuEmptyTemplate,
|
|
21
|
-
GigamenuHeaderTemplate,
|
|
22
|
-
GigamenuFooterTemplate,
|
|
23
|
-
GigamenuPanelTemplate,
|
|
24
|
-
GigamenuItemContext,
|
|
25
|
-
GigamenuEmptyContext,
|
|
26
|
-
GigamenuHeaderContext,
|
|
27
|
-
GigamenuFooterContext,
|
|
28
|
-
GigamenuPanelContext,
|
|
29
|
-
} from './gigamenu-templates.directive';
|
|
30
|
-
|
|
31
|
-
@Component({
|
|
32
|
-
selector: 'gm-gigamenu',
|
|
33
|
-
standalone: true,
|
|
34
|
-
imports: [NgTemplateOutlet],
|
|
35
|
-
templateUrl: 'gigamenu.component.html',
|
|
36
|
-
styles: `
|
|
37
|
-
:host {
|
|
38
|
-
display: contents;
|
|
39
|
-
}
|
|
40
|
-
`,
|
|
41
|
-
})
|
|
42
|
-
export class GigamenuComponent {
|
|
43
|
-
private readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');
|
|
44
|
-
private readonly listContainer = viewChild<ElementRef<HTMLDivElement>>('listContainer');
|
|
45
|
-
private readonly isBrowser: boolean;
|
|
46
|
-
|
|
47
|
-
// Template queries
|
|
48
|
-
protected readonly itemTemplate = contentChild(GigamenuItemTemplate);
|
|
49
|
-
protected readonly emptyTemplate = contentChild(GigamenuEmptyTemplate);
|
|
50
|
-
protected readonly headerTemplate = contentChild(GigamenuHeaderTemplate);
|
|
51
|
-
protected readonly footerTemplate = contentChild(GigamenuFooterTemplate);
|
|
52
|
-
protected readonly panelTemplate = contentChild(GigamenuPanelTemplate);
|
|
53
|
-
|
|
54
|
-
protected readonly query = signal('');
|
|
55
|
-
protected readonly selectedIndex = signal(0);
|
|
56
|
-
|
|
57
|
-
/** Parsed search term (before first separator) */
|
|
58
|
-
protected readonly searchTerm = computed(() => {
|
|
59
|
-
const q = this.query();
|
|
60
|
-
const separator = this.service.config().argSeparator ?? ' ';
|
|
61
|
-
const sepIndex = q.indexOf(separator);
|
|
62
|
-
if (sepIndex === -1) return q;
|
|
63
|
-
return q.substring(0, sepIndex);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/** Parsed arguments (after first separator) */
|
|
67
|
-
protected readonly args = computed(() => {
|
|
68
|
-
const q = this.query();
|
|
69
|
-
const separator = this.service.config().argSeparator ?? ' ';
|
|
70
|
-
const sepIndex = q.indexOf(separator);
|
|
71
|
-
if (sepIndex === -1) return '';
|
|
72
|
-
return q.substring(sepIndex + separator.length);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
protected readonly filteredItems = computed(() => {
|
|
76
|
-
const searchTerm = this.searchTerm().toLowerCase().trim();
|
|
77
|
-
const items = this.service.items();
|
|
78
|
-
const maxResults = this.service.config().maxResults ?? 10;
|
|
79
|
-
|
|
80
|
-
if (!searchTerm) {
|
|
81
|
-
// No query: sort by frecency scores from empty searches
|
|
82
|
-
const scores = this.frecency.getScores('');
|
|
83
|
-
return this.sortByFrecency(items, scores).slice(0, maxResults);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Filter matching items using only search term (not args)
|
|
87
|
-
const matched = items.filter((item) => this.matchesQuery(item, searchTerm));
|
|
88
|
-
|
|
89
|
-
// Sort by frecency for this search term
|
|
90
|
-
const scores = this.frecency.getScores(searchTerm);
|
|
91
|
-
return this.sortByFrecency(matched, scores).slice(0, maxResults);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
constructor(
|
|
95
|
-
protected readonly service: GigamenuService,
|
|
96
|
-
private readonly frecency: FrecencyService,
|
|
97
|
-
@Inject(PLATFORM_ID) platformId: object
|
|
98
|
-
) {
|
|
99
|
-
this.isBrowser = isPlatformBrowser(platformId);
|
|
100
|
-
|
|
101
|
-
effect(() => {
|
|
102
|
-
if (this.service.isOpen() && this.isBrowser) {
|
|
103
|
-
setTimeout(() => this.searchInput()?.nativeElement.focus(), 0);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
effect(() => {
|
|
108
|
-
const items = this.filteredItems();
|
|
109
|
-
const searchTerm = this.searchTerm();
|
|
110
|
-
|
|
111
|
-
// Check for auto-select based on frecency
|
|
112
|
-
if (searchTerm && items.length > 0) {
|
|
113
|
-
const topMatch = this.frecency.getTopMatch(searchTerm);
|
|
114
|
-
if (topMatch) {
|
|
115
|
-
const idx = items.findIndex((item) => item.id === topMatch);
|
|
116
|
-
if (idx !== -1) {
|
|
117
|
-
this.selectedIndex.set(idx);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.selectedIndex.set(0);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@HostListener('document:keydown', ['$event'])
|
|
128
|
-
onGlobalKeydown(event: KeyboardEvent): void {
|
|
129
|
-
if (!this.isBrowser) return;
|
|
130
|
-
|
|
131
|
-
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
|
132
|
-
event.preventDefault();
|
|
133
|
-
this.service.toggle();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (event.key === '/' && !this.isInputFocused()) {
|
|
138
|
-
event.preventDefault();
|
|
139
|
-
this.service.open();
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (event.key === 'Escape' && this.service.isOpen()) {
|
|
144
|
-
event.preventDefault();
|
|
145
|
-
this.close();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
protected onInputKeydown(event: KeyboardEvent): void {
|
|
150
|
-
const items = this.filteredItems();
|
|
151
|
-
|
|
152
|
-
switch (event.key) {
|
|
153
|
-
case 'ArrowDown':
|
|
154
|
-
event.preventDefault();
|
|
155
|
-
this.selectedIndex.update((i) => Math.min(i + 1, items.length - 1));
|
|
156
|
-
this.scrollSelectedIntoView();
|
|
157
|
-
break;
|
|
158
|
-
|
|
159
|
-
case 'ArrowUp':
|
|
160
|
-
event.preventDefault();
|
|
161
|
-
this.selectedIndex.update((i) => Math.max(i - 1, 0));
|
|
162
|
-
this.scrollSelectedIntoView();
|
|
163
|
-
break;
|
|
164
|
-
|
|
165
|
-
case 'Enter':
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
const selected = items[this.selectedIndex()];
|
|
168
|
-
if (selected) {
|
|
169
|
-
this.executeItem(selected);
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
|
|
173
|
-
case 'Escape':
|
|
174
|
-
event.preventDefault();
|
|
175
|
-
this.close();
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private scrollSelectedIntoView(): void {
|
|
181
|
-
const container = this.listContainer()?.nativeElement;
|
|
182
|
-
if (!container) return;
|
|
183
|
-
|
|
184
|
-
const selectedButton = container.querySelector(
|
|
185
|
-
`[data-index="${this.selectedIndex()}"]`
|
|
186
|
-
) as HTMLElement | null;
|
|
187
|
-
|
|
188
|
-
if (selectedButton) {
|
|
189
|
-
selectedButton.scrollIntoView({ block: 'nearest' });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
protected onQueryChange(event: Event): void {
|
|
194
|
-
const value = (event.target as HTMLInputElement).value;
|
|
195
|
-
this.query.set(value);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
protected onBackdropClick(event: MouseEvent): void {
|
|
199
|
-
if (event.target === event.currentTarget) {
|
|
200
|
-
this.close();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
protected executeItem(item: GigamenuItem): void {
|
|
205
|
-
// Record the selection for frecency learning (use search term, not full query)
|
|
206
|
-
const searchTerm = this.searchTerm();
|
|
207
|
-
this.frecency.recordSelection(searchTerm, item.id);
|
|
208
|
-
|
|
209
|
-
// Get args before closing (which resets query)
|
|
210
|
-
const args = this.args() || undefined;
|
|
211
|
-
|
|
212
|
-
this.close();
|
|
213
|
-
item.action(args);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Template context getters
|
|
217
|
-
protected getItemContext(item: GigamenuItem, index: number): GigamenuItemContext {
|
|
218
|
-
return {
|
|
219
|
-
$implicit: item,
|
|
220
|
-
index,
|
|
221
|
-
selected: this.selectedIndex() === index,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
protected getEmptyContext(): GigamenuEmptyContext {
|
|
226
|
-
return {
|
|
227
|
-
$implicit: this.query(),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
protected getHeaderContext(): GigamenuHeaderContext {
|
|
232
|
-
return {
|
|
233
|
-
$implicit: this.query(),
|
|
234
|
-
searchTerm: this.searchTerm(),
|
|
235
|
-
args: this.args(),
|
|
236
|
-
onQueryChange: (value: string) => this.query.set(value),
|
|
237
|
-
onKeydown: (event: KeyboardEvent) => this.onInputKeydown(event),
|
|
238
|
-
placeholder: this.service.config().placeholder ?? '',
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
protected getFooterContext(): GigamenuFooterContext {
|
|
243
|
-
return {
|
|
244
|
-
$implicit: this.filteredItems().length,
|
|
245
|
-
total: this.service.items().length,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
protected getPanelContext(): GigamenuPanelContext {
|
|
250
|
-
return {
|
|
251
|
-
$implicit: this.filteredItems(),
|
|
252
|
-
query: this.query(),
|
|
253
|
-
searchTerm: this.searchTerm(),
|
|
254
|
-
args: this.args(),
|
|
255
|
-
selectedIndex: this.selectedIndex(),
|
|
256
|
-
executeItem: (item: GigamenuItem) => this.executeItem(item),
|
|
257
|
-
setSelectedIndex: (index: number) => this.selectedIndex.set(index),
|
|
258
|
-
setQuery: (query: string) => this.query.set(query),
|
|
259
|
-
close: () => this.close(),
|
|
260
|
-
placeholder: this.service.config().placeholder ?? '',
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
private close(): void {
|
|
265
|
-
this.service.close();
|
|
266
|
-
this.query.set('');
|
|
267
|
-
this.selectedIndex.set(0);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private sortByFrecency(items: GigamenuItem[], scores: Map<string, number>): GigamenuItem[] {
|
|
271
|
-
if (scores.size === 0) return items;
|
|
272
|
-
|
|
273
|
-
return [...items].sort((a, b) => {
|
|
274
|
-
const scoreA = scores.get(a.id) ?? 0;
|
|
275
|
-
const scoreB = scores.get(b.id) ?? 0;
|
|
276
|
-
return scoreB - scoreA;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private matchesQuery(item: GigamenuItem, query: string): boolean {
|
|
281
|
-
const searchableText = [
|
|
282
|
-
item.label,
|
|
283
|
-
item.description,
|
|
284
|
-
...(item.keywords ?? []),
|
|
285
|
-
]
|
|
286
|
-
.filter(Boolean)
|
|
287
|
-
.join(' ')
|
|
288
|
-
.toLowerCase();
|
|
289
|
-
|
|
290
|
-
const words = query.split(/\s+/);
|
|
291
|
-
return words.every((word) => searchableText.includes(word));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private isInputFocused(): boolean {
|
|
295
|
-
const activeElement = document.activeElement;
|
|
296
|
-
if (!activeElement) return false;
|
|
297
|
-
|
|
298
|
-
const tagName = activeElement.tagName.toLowerCase();
|
|
299
|
-
return (
|
|
300
|
-
tagName === 'input' ||
|
|
301
|
-
tagName === 'textarea' ||
|
|
302
|
-
(activeElement as HTMLElement).isContentEditable
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { Injectable, signal, computed } from '@angular/core';
|
|
2
|
-
import { Router, Route, Routes } from '@angular/router';
|
|
3
|
-
import {
|
|
4
|
-
GigamenuItem,
|
|
5
|
-
GigamenuCommand,
|
|
6
|
-
GigamenuPage,
|
|
7
|
-
GigamenuConfig,
|
|
8
|
-
DEFAULT_CONFIG,
|
|
9
|
-
DiscoverRoutesOptions,
|
|
10
|
-
RouteInfo,
|
|
11
|
-
} from './types';
|
|
12
|
-
|
|
13
|
-
@Injectable({ providedIn: 'root' })
|
|
14
|
-
export class GigamenuService {
|
|
15
|
-
private readonly _items = signal<Map<string, GigamenuItem>>(new Map());
|
|
16
|
-
private readonly _isOpen = signal(false);
|
|
17
|
-
private readonly _config = signal<GigamenuConfig>(DEFAULT_CONFIG);
|
|
18
|
-
|
|
19
|
-
readonly items = computed(() => Array.from(this._items().values()));
|
|
20
|
-
readonly isOpen = this._isOpen.asReadonly();
|
|
21
|
-
readonly config = this._config.asReadonly();
|
|
22
|
-
|
|
23
|
-
constructor(private readonly router: Router) {}
|
|
24
|
-
|
|
25
|
-
configure(config: Partial<GigamenuConfig>): void {
|
|
26
|
-
this._config.update((current) => ({ ...current, ...config }));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
open(): void {
|
|
30
|
-
this._isOpen.set(true);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
close(): void {
|
|
34
|
-
this._isOpen.set(false);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
toggle(): void {
|
|
38
|
-
this._isOpen.update((v) => !v);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
registerItem(item: GigamenuItem): void {
|
|
42
|
-
this._items.update((items) => {
|
|
43
|
-
const newItems = new Map(items);
|
|
44
|
-
newItems.set(item.id, item);
|
|
45
|
-
return newItems;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
unregisterItem(id: string): void {
|
|
50
|
-
this._items.update((items) => {
|
|
51
|
-
const newItems = new Map(items);
|
|
52
|
-
newItems.delete(id);
|
|
53
|
-
return newItems;
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
registerCommand(command: GigamenuCommand): void {
|
|
58
|
-
this.registerItem({
|
|
59
|
-
...command,
|
|
60
|
-
category: 'command',
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
registerPage(page: GigamenuPage): void {
|
|
65
|
-
this.registerItem({
|
|
66
|
-
...page,
|
|
67
|
-
category: 'page',
|
|
68
|
-
action: () => this.router.navigate([page.path]),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
discoverRoutes(options?: DiscoverRoutesOptions): void;
|
|
73
|
-
discoverRoutes(routes?: Routes, options?: DiscoverRoutesOptions): void;
|
|
74
|
-
discoverRoutes(
|
|
75
|
-
routesOrOptions?: Routes | DiscoverRoutesOptions,
|
|
76
|
-
maybeOptions?: DiscoverRoutesOptions
|
|
77
|
-
): void {
|
|
78
|
-
let routes: Routes;
|
|
79
|
-
let options: DiscoverRoutesOptions | undefined;
|
|
80
|
-
|
|
81
|
-
if (Array.isArray(routesOrOptions)) {
|
|
82
|
-
routes = routesOrOptions;
|
|
83
|
-
options = maybeOptions;
|
|
84
|
-
} else {
|
|
85
|
-
routes = this.router.config;
|
|
86
|
-
options = routesOrOptions;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
this.extractPagesFromRoutes(routes, '', options?.filter);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
private extractPagesFromRoutes(
|
|
93
|
-
routes: Routes,
|
|
94
|
-
parentPath: string,
|
|
95
|
-
filter?: (route: RouteInfo) => boolean
|
|
96
|
-
): void {
|
|
97
|
-
for (const route of routes) {
|
|
98
|
-
if (route.redirectTo !== undefined) continue;
|
|
99
|
-
|
|
100
|
-
const fullPath = parentPath
|
|
101
|
-
? `${parentPath}/${route.path ?? ''}`
|
|
102
|
-
: route.path ?? '';
|
|
103
|
-
|
|
104
|
-
if (route.path !== undefined && route.path !== '**') {
|
|
105
|
-
const routeInfo: RouteInfo = {
|
|
106
|
-
path: route.path,
|
|
107
|
-
fullPath: `/${fullPath}`,
|
|
108
|
-
data: route.data as Record<string, unknown> | undefined,
|
|
109
|
-
title: typeof route.title === 'string' ? route.title : undefined,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Apply filter if provided
|
|
113
|
-
if (filter && !filter(routeInfo)) {
|
|
114
|
-
// Still process children even if this route is filtered
|
|
115
|
-
if (route.children) {
|
|
116
|
-
this.extractPagesFromRoutes(route.children, fullPath, filter);
|
|
117
|
-
}
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const label = routeInfo.title || this.pathToLabel(route.path || 'Home');
|
|
122
|
-
this.registerPage({
|
|
123
|
-
id: `page:${fullPath || '/'}`,
|
|
124
|
-
label,
|
|
125
|
-
path: `/${fullPath}`,
|
|
126
|
-
description: `Navigate to ${label}`,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (route.children) {
|
|
131
|
-
this.extractPagesFromRoutes(route.children, fullPath, filter);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private pathToLabel(path: string): string {
|
|
137
|
-
return path
|
|
138
|
-
.split('/')
|
|
139
|
-
.pop()!
|
|
140
|
-
.replace(/[-_]/g, ' ')
|
|
141
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
142
|
-
}
|
|
143
|
-
}
|
package/src/lib/types.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
export type GigamenuItemCategory = 'page' | 'command';
|
|
2
|
-
|
|
3
|
-
export interface GigamenuItem {
|
|
4
|
-
id: string;
|
|
5
|
-
label: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
/** Emoji or text icon */
|
|
8
|
-
icon?: string;
|
|
9
|
-
/** CSS class for icon libraries (e.g., 'pi pi-home', 'fa fa-home') */
|
|
10
|
-
iconClass?: string;
|
|
11
|
-
keywords?: string[];
|
|
12
|
-
category: GigamenuItemCategory;
|
|
13
|
-
/** Action to execute. Receives args string if user typed text after the separator. */
|
|
14
|
-
action: (args?: string) => void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface GigamenuPage extends Omit<GigamenuItem, 'category' | 'action'> {
|
|
18
|
-
path: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface GigamenuCommand extends Omit<GigamenuItem, 'category'> {
|
|
22
|
-
shortcut?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface GigamenuConfig {
|
|
26
|
-
placeholder?: string;
|
|
27
|
-
maxResults?: number;
|
|
28
|
-
autoDiscoverRoutes?: boolean;
|
|
29
|
-
/** Separator between search query and arguments (default: ' ') */
|
|
30
|
-
argSeparator?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const DEFAULT_CONFIG: GigamenuConfig = {
|
|
34
|
-
placeholder: 'Search pages and commands...',
|
|
35
|
-
maxResults: 10,
|
|
36
|
-
autoDiscoverRoutes: true,
|
|
37
|
-
argSeparator: ' ',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Base interface for defining a command in a separate file.
|
|
42
|
-
* Each command file should export a constant implementing this interface.
|
|
43
|
-
*/
|
|
44
|
-
export interface CommandDefinition {
|
|
45
|
-
readonly id: string;
|
|
46
|
-
readonly label: string;
|
|
47
|
-
readonly description?: string;
|
|
48
|
-
readonly icon?: string;
|
|
49
|
-
readonly iconClass?: string;
|
|
50
|
-
readonly keywords?: string[];
|
|
51
|
-
readonly shortcut?: string;
|
|
52
|
-
execute(): void;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Helper function to define a command with type safety.
|
|
57
|
-
*/
|
|
58
|
-
export function defineCommand(command: CommandDefinition): CommandDefinition {
|
|
59
|
-
return command;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Information about a route passed to the filter function.
|
|
64
|
-
*/
|
|
65
|
-
export interface RouteInfo {
|
|
66
|
-
path: string;
|
|
67
|
-
fullPath: string;
|
|
68
|
-
data?: Record<string, unknown>;
|
|
69
|
-
title?: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Filter function to include/exclude routes from discovery.
|
|
74
|
-
* Return true to include the route, false to exclude it.
|
|
75
|
-
*/
|
|
76
|
-
export type RouteFilter = (route: RouteInfo) => boolean;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Mapped page data returned from the map function.
|
|
80
|
-
*/
|
|
81
|
-
export interface MappedPage {
|
|
82
|
-
label?: string;
|
|
83
|
-
description?: string;
|
|
84
|
-
icon?: string;
|
|
85
|
-
iconClass?: string;
|
|
86
|
-
keywords?: string[];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Map function to customize page data for discovered routes.
|
|
91
|
-
* Return partial page data to override defaults, or null to skip the route.
|
|
92
|
-
*/
|
|
93
|
-
export type RouteMapper = (route: RouteInfo) => MappedPage | null;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Options for route discovery.
|
|
97
|
-
*/
|
|
98
|
-
export interface DiscoverRoutesOptions {
|
|
99
|
-
filter?: RouteFilter;
|
|
100
|
-
map?: RouteMapper;
|
|
101
|
-
}
|
package/src/public-api.ts
DELETED
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ES2022",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2022", "DOM"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"experimentalDecorators": true,
|
|
12
|
-
"useDefineForClassFields": false
|
|
13
|
-
}
|
|
14
|
-
}
|
package/tsconfig.lib.json
DELETED