ui/TitleBarView.tstypescript
import type { EventsHandler } from "../Events";
import { View, type IViewParams, type ViewEventsList } from "./View";

const ICON_EXPANDED_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <path d="M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6z"/>
</svg>`;

const ICON_COLLAPSED_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <path d="M8.59 16.59 13.17 12 8.59 7.41 10 6l6 6-6 6z"/>
</svg>`;

interface ITitleBarViewParams extends IViewParams {
    title: string;
    isCollapsed?: boolean;
}

type TitleBarViewEventsList = ["change"];

const TITLE_BAR_VIEW_EVENTS: TitleBarViewEventsList = ["change"];

const TEMPLATE = `<div class="og-titlebar">
    <div class="og-titlebar__title">{title}</div>
    <button type="button" class="og-titlebar__toggle"></button>
</div>`;

export class TitleBarView extends View<null> {
    public override events: EventsHandler<TitleBarViewEventsList> & EventsHandler<ViewEventsList>;

    protected _title: string;
    protected _isCollapsed: boolean;
    protected _toggleBtn: HTMLButtonElement | null;

    constructor(params: ITitleBarViewParams) {
        super({
            template: TEMPLATE
        });

        //@ts-ignore
        this.events = this.events.registerNames(TITLE_BAR_VIEW_EVENTS);

        this._title = params.title;
        this._isCollapsed = params.isCollapsed || false;
        this._toggleBtn = null;
    }

    public get isCollapsed(): boolean {
        return this._isCollapsed;
    }

    public setCollapsed(isCollapsed: boolean, stopPropagation: boolean = false): void {
        if (this._isCollapsed !== isCollapsed) {
            this._isCollapsed = isCollapsed;
            this._syncToggleButton();
            if (!stopPropagation) {
                this.events.dispatch(this.events.change, isCollapsed, this);
            }
        }
    }

    public override render(params?: unknown): this {
        super.render({
            title: this._title
        });

        this._toggleBtn = this.select<HTMLButtonElement>(".og-titlebar__toggle");
        this._toggleBtn!.addEventListener("click", this._onToggleClick);
        this._syncToggleButton();

        return this;
    }

    public override remove(): void {
        if (this._toggleBtn) {
            this._toggleBtn.removeEventListener("click", this._onToggleClick);
            this._toggleBtn = null;
        }

        super.remove();
    }

    protected _syncToggleButton(): void {
        if (!this._toggleBtn) return;

        this._toggleBtn.innerHTML = this._isCollapsed ? ICON_COLLAPSED_SVG : ICON_EXPANDED_SVG;
        this._toggleBtn.title = this._isCollapsed ? "Expand" : "Collapse";
        this._toggleBtn.setAttribute("aria-expanded", (!this._isCollapsed).toString());
    }

    protected _onToggleClick = (e: MouseEvent): void => {
        e.preventDefault();
        e.stopPropagation();
        this.setCollapsed(!this._isCollapsed);
    };
}