components_speaker_context_menu.js


import { ContextMenu } from "./context_menu.js"

/**
 * A context menu for speaker operations (set color, rename, remove).
 *
 * @example
 * new SpeakerContextMenu(event.clientX, event.clientY, speaker, {
 *   onSetColor:  () => speakersPanel.openHuePicker(speaker, swatchEl),
 *   onChangeName: () => speakersPanel.makeSpeakerEditable(nameSpan, speaker),
 *   onRemove:    () => speakersPanel.deleteSpeaker(speaker.id),
 *   onDismiss:   () => {},
 * });
 */
export class SpeakerContextMenu extends ContextMenu {
    /**
     * @param {number} x - Preferred left position in viewport pixels.
     * @param {number} y - Preferred top position in viewport pixels.
     * @param {object} speaker - The speaker object being acted on.
     * @param {object} options - Options and callback functions for the menu.
     * @param {function(): void} options.onSetColor    - Called when "Set color" is clicked.
     * @param {function(): void} options.onChangeName  - Called when "Change name" is clicked.
     * @param {function(): void} options.onRemove      - Called when "Remove speaker" is clicked.
     * @param {function(): void} [options.onDismiss]   - Called when the menu is dismissed via outside click.
     * @param {{message: string, href: (string|undefined)}|null} [options.info] - Info widget config passed to the base ContextMenu.
     */
    constructor(x, y, speaker, { onSetColor, onChangeName, onRemove, onDismiss, info = { message: 'Change the color or name of this speaker, or remove them from the project.', href: '/docs' } } = {}) {
        super('Speaker', onDismiss, info);

        if (speaker) {
            const info = document.createElement('div');
            info.className = 'ctx-info';
            const pairs = [
                ['NAME', speaker.name],
                ['ID',   speaker.id],
            ];
            pairs.forEach(([k, v]) => {
                const key = document.createElement('span');
                key.className = 'ctx-info-key';
                key.textContent = k;
                const val = document.createElement('span');
                val.className = 'ctx-info-val';
                val.textContent = v;
                info.appendChild(key);
                info.appendChild(val);
            });
            this.root.appendChild(info);
        }

        const controls = document.createElement('div');
        controls.className = 'ctx-controls';

        if (onSetColor) {
            controls.appendChild(this.makeItem(
                '◐', 'Set color', null,
                () => { this.close(); this._onDismiss(); onSetColor(); },
            ));
        }

        if (onChangeName) {
            controls.appendChild(this.makeItem(
                '✎', 'Change name', null,
                () => { this.close(); this._onDismiss(); onChangeName(); },
            ));
        }

        if (onRemove) {
            const item = this.makeItem(
                '<span class="icon icon-close" style="width:12px;height:12px;"></span>',
                'Remove speaker', null,
                () => { this.close(); this._onDismiss(); onRemove(); },
            );
            item.classList.add('ctx-item--danger');
            controls.appendChild(item);
        }

        this.root.appendChild(controls);
        this._mount(x, y);
    }
}