search bar

This commit is contained in:
2026-02-20 17:38:29 -05:00
parent 089af5e299
commit 402789e763
10 changed files with 566 additions and 29 deletions
@@ -0,0 +1,54 @@
import type { ReactiveController, ReactiveControllerHost } from 'lit';
import { searchStore } from '../search-store';
/**
* SearchController connects a Lit component to the SearchStore.
*
* Usage in a component:
*
* private search = new SearchController(this);
*
* render() {
* const term = this.search.term;
* ...
* }
*/
export class SearchController implements ReactiveController {
private host: ReactiveControllerHost;
private unsubscribe?: () => void;
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
// ===================================================================
// LIFECYCLE HOOKS
// ===================================================================
hostConnected(): void {
this.unsubscribe = searchStore.subscribe(() => {
this.host.requestUpdate();
});
}
hostDisconnected(): void {
this.unsubscribe?.();
}
// ===================================================================
// DATA ACCESS
// ===================================================================
get term(): string {
return searchStore.getTerm();
}
set term(value: string) {
searchStore.setTerm(value);
}
get isSearchableView(): boolean {
return searchStore.isSearchableView();
}
}
+2
View File
@@ -7,3 +7,5 @@ export { QueueController } from './controllers/queue-controller';
export { themeStore } from './theme-store';
export type { ThemeState, BackgroundShade } from './theme-store';
export { ThemeController } from './controllers/theme-controller';
export { searchStore } from './search-store';
export { SearchController } from './controllers/search-controller';
+61
View File
@@ -0,0 +1,61 @@
/** Searchable views that respond to the global search term. */
const SEARCHABLE_VIEWS = new Set(['tracks', 'albums', 'playlists']);
type Subscriber = () => void;
class SearchStore {
private term = '';
private currentView = 'tracks';
private subscribers = new Set<Subscriber>();
// ===================================================================
// SEARCH TERM
// ===================================================================
getTerm(): string {
return this.term;
}
setTerm(term: string): void {
if (term === this.term) return;
this.term = term;
this.notify();
}
// ===================================================================
// CURRENT VIEW
// ===================================================================
getCurrentView(): string {
return this.currentView;
}
setCurrentView(view: string): void {
if (view === this.currentView) return;
this.currentView = view;
this.notify();
}
isSearchableView(): boolean {
return SEARCHABLE_VIEWS.has(this.currentView);
}
// ===================================================================
// SUBSCRIPTION SYSTEM
// ===================================================================
subscribe(callback: Subscriber): () => void {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
}
private notify(): void {
this.subscribers.forEach((callback) => callback());
}
}
// Singleton instance.
export const searchStore = new SearchStore();