components_confirm_dialog.js
/**
* Modal confirmation dialog with optional injected content (e.g. a select element).
* Clicking the overlay background counts as a cancel.
*/
export class ConfirmDialog {
/**
* @param {string} titleText - Header text for the dialog (may contain HTML)
* @param {object} callbacks - Callback functions for dialog actions
* @param {function} [callbacks.onConfirm] - Called when the confirm button is clicked
* @param {function} [callbacks.onDismiss] - Called when cancel or the overlay is clicked
* @param {string} [warningText=''] - Body text shown below the title
* @param {string} [confirmText='Confirm'] - Label for the confirm button
* @param {string} [cancelText='Cancel'] - Label for the cancel button
* @param {HTMLElement|null} [contentInjection=null] - Optional element inserted into the body
*/
constructor(titleText, { onConfirm, onDismiss }, warningText = '', confirmText = 'Confirm', cancelText='Cancel', contentInjection = null) {
this._onConfirm = onConfirm ?? (() => {});
this._onDismiss = onDismiss ?? (() => {});
this.#buildDialog(titleText, warningText, confirmText, cancelText, contentInjection);
}
/**
* Builds and appends the dialog overlay and modal to the document body.
* @param {string} titleText - Header text for the dialog (may contain HTML)
* @param {string} warningText - Body text shown below the title
* @param {string} confirmText - Label for the confirm button
* @param {string} cancelText - Label for the cancel button
* @param {HTMLElement|null} contentInjection - Optional element inserted into the body
*/
#buildDialog(titleText, warningText, confirmText, cancelText, contentInjection) {
const overlay = document.createElement('div');
overlay.className = 'confirm-dialog-overlay';
const modal = document.createElement('div');
modal.className = 'confirm-dialog-modal';
modal.style.cssText = 'max-height:none;width:380px;padding:0;';
const header = document.createElement('div');
header.className = 'confirm-dialog-header';
header.innerHTML = `<span>${titleText}</span>`;
modal.appendChild(header);
const body = document.createElement('div');
body.style.cssText = 'padding:1rem 1.25rem;display:flex;flex-direction:column;gap:0.85rem;';
const warning = document.createElement('div');
warning.style.cssText = 'font-family:var(--font-mono);font-size:var(--fs-caption);color:var(--text);line-height:1.6;';
warning.textContent = warningText;
body.appendChild(warning);
// If an injection has been provided, inject it now
if (contentInjection) {
body.appendChild(contentInjection);
}
const actions = document.createElement('div');
actions.style.cssText = 'display:flex;gap:0.5rem;justify-content:flex-end;margin-top:0.25rem;';
const cancelBtn = document.createElement('button');
cancelBtn.className = 'sample-btn';
cancelBtn.textContent = cancelText;
cancelBtn.style.cssText = 'padding:0.35rem 0.75rem;font-size:var(--fs-label);';
cancelBtn.addEventListener('click', () => {
overlay.remove();
this._onDismiss();
});
actions.appendChild(cancelBtn);
const confirmBtn = document.createElement('button');
confirmBtn.className = 'sample-btn';
confirmBtn.textContent = confirmText;
confirmBtn.style.cssText = 'padding:0.35rem 0.75rem;font-size:var(--fs-label);border-color:var(--danger-border);color:var(--danger-muted);';
confirmBtn.onmouseenter = () => { confirmBtn.style.background = 'var(--danger-faint)'; confirmBtn.style.borderColor = 'var(--danger)'; };
confirmBtn.onmouseleave = () => { confirmBtn.style.background = ''; confirmBtn.style.borderColor = 'var(--danger-border)'; };
confirmBtn.addEventListener('click', () => {
overlay.remove();
this._onConfirm();
});
actions.appendChild(confirmBtn);
body.appendChild(actions);
modal.appendChild(body);
overlay.appendChild(modal);
// if the background of the window is clicked while this dialog is opened, it counts as cancel
// (only when the mousedown also started on the overlay, so drag-release doesn't dismiss)
let _mouseDownOnOverlay = false;
overlay.addEventListener('mousedown', (e) => { _mouseDownOnOverlay = e.target === overlay; });
overlay.addEventListener('click', (e) => {
if (e.target === overlay && _mouseDownOnOverlay) {
overlay.remove();
this._onDismiss();
}
});
document.body.appendChild(overlay);
}
}