import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { afterNextRender, DestroyRef, inject, Injectable, Injector, PLATFORM_ID, signal, } from '@angular/core'; export type TerminalDockState = 'collapsed' | 'expanded' | 'maximized'; @Injectable({ providedIn: 'root' }) export class TerminalDockService { private readonly document = inject(DOCUMENT); private readonly destroyRef = inject(DestroyRef); private readonly injector = inject(Injector); private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); readonly state = signal('collapsed'); readonly panelId = 'terminal-dock-panel'; readonly inputId = 'terminal-dock-input'; readonly logId = 'terminal-dock-log'; private trigger: HTMLElement | null = null; constructor() { afterNextRender( () => { if (!this.isBrowser) { return; } this.document.addEventListener('keydown', this.onDocumentKeydown); }, { injector: this.injector }, ); this.destroyRef.onDestroy(() => this.teardown()); } registerTrigger(element: HTMLElement | null): void { this.trigger = element; } open(opener?: HTMLElement | null): void { if (this.state() !== 'collapsed') { return; } if (opener instanceof HTMLElement) { this.trigger = opener; } this.state.set('expanded'); } collapse(): void { if (this.state() === 'collapsed') { return; } this.state.set('collapsed'); this.restoreFocus(); } toggle(opener?: HTMLElement | null): void { if (this.state() === 'collapsed') { this.open(opener); return; } this.collapse(); } toggleMaximized(): void { if (this.state() === 'collapsed') { this.open(); this.state.set('maximized'); return; } this.state.update((current) => (current === 'maximized' ? 'expanded' : 'maximized')); } private restoreFocus(): void { const trigger = this.trigger; afterNextRender( () => { if (trigger instanceof HTMLElement && trigger.isConnected) { trigger.focus(); } }, { injector: this.injector }, ); } private teardown(): void { this.document.removeEventListener('keydown', this.onDocumentKeydown); this.state.set('collapsed'); this.trigger = null; } private readonly onDocumentKeydown = (event: KeyboardEvent): void => { if ( event.key.toLowerCase() !== 'k' || !(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey ) { return; } event.preventDefault(); this.toggle(); }; }