2026-07-06 17:28:59 +00:00
|
|
|
export interface ConfirmDialogOptions {
|
|
|
|
|
icon?: string;
|
|
|
|
|
title: string;
|
|
|
|
|
message: string;
|
|
|
|
|
confirmLabel?: string;
|
|
|
|
|
cancelLabel?: string;
|
|
|
|
|
danger?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ConfirmDialog extends HTMLElement {
|
|
|
|
|
private resolver: ((value: boolean) => void) | null = null;
|
|
|
|
|
|
|
|
|
|
connectedCallback(): void {
|
|
|
|
|
this.style.display = 'none';
|
|
|
|
|
this.innerHTML = `
|
|
|
|
|
<div class="cd-backdrop"></div>
|
|
|
|
|
<div class="cd-box" role="dialog" aria-modal="true">
|
|
|
|
|
<div class="cd-titlebar">
|
|
|
|
|
<span class="cd-icon"></span>
|
|
|
|
|
<span class="cd-title"></span>
|
|
|
|
|
<button class="cd-close" title="Close">✕</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cd-body">
|
|
|
|
|
<p class="cd-message"></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cd-footer"></div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
this.querySelector('.cd-backdrop')!.addEventListener('click', () => this.close(false));
|
|
|
|
|
this.querySelector('.cd-close')!.addEventListener('click', () => this.close(false));
|
|
|
|
|
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
|
|
|
if (e.key === 'Escape' && this.style.display !== 'none') this.close(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
show(options: ConfirmDialogOptions): Promise<boolean> {
|
|
|
|
|
|
|
|
|
|
console.log( "Show dialog" );
|
|
|
|
|
|
|
|
|
|
this.querySelector('.cd-icon')!.textContent = options.icon ?? '';
|
|
|
|
|
this.querySelector('.cd-title')!.textContent = options.title;
|
|
|
|
|
this.querySelector('.cd-message')!.textContent = options.message;
|
|
|
|
|
|
|
|
|
|
const footer = this.querySelector('.cd-footer')!;
|
|
|
|
|
footer.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
if (options.cancelLabel) {
|
|
|
|
|
const cancel = document.createElement('button');
|
|
|
|
|
cancel.className = 'cd-btn cd-btn-cancel';
|
|
|
|
|
cancel.textContent = options.cancelLabel;
|
|
|
|
|
cancel.addEventListener('click', () => this.close(false));
|
|
|
|
|
footer.appendChild(cancel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const confirm = document.createElement('button');
|
|
|
|
|
confirm.className = `cd-btn cd-btn-confirm${options.danger ? ' cd-btn-danger' : ''}`;
|
|
|
|
|
confirm.textContent = options.confirmLabel ?? 'OK';
|
|
|
|
|
confirm.addEventListener('click', () => this.close(true));
|
|
|
|
|
footer.appendChild(confirm);
|
|
|
|
|
|
|
|
|
|
this.style.display = '';
|
|
|
|
|
confirm.focus();
|
|
|
|
|
|
|
|
|
|
return new Promise<boolean>(resolve => { this.resolver = resolve; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private close(value: boolean): void {
|
|
|
|
|
this.style.display = 'none';
|
|
|
|
|
console.log( "Closing:", value );
|
|
|
|
|
if (this.resolver) { this.resolver(value); this.resolver = null; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
customElements.define('confirm-dialog', ConfirmDialog);
|
|
|
|
|
|
|
|
|
|
export function showConfirmDialog(options: ConfirmDialogOptions): Promise<boolean> {
|
|
|
|
|
let dialog = document.querySelector('confirm-dialog') as ConfirmDialog | null;
|
|
|
|
|
if (!dialog) {
|
|
|
|
|
dialog = document.createElement('confirm-dialog') as ConfirmDialog;
|
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
|
}
|
|
|
|
|
return dialog.show(options);
|
|
|
|
|
}
|
2026-07-31 13:43:04 +00:00
|
|
|
|
|
|
|
|
// ── Input dialog ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export interface InputDialogOptions {
|
|
|
|
|
icon?: string;
|
|
|
|
|
title: string;
|
|
|
|
|
label: string;
|
|
|
|
|
defaultValue?: string;
|
|
|
|
|
confirmLabel?: string;
|
|
|
|
|
cancelLabel?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class InputDialog extends HTMLElement {
|
|
|
|
|
private resolver: (( value: string | null ) => void) | null = null;
|
|
|
|
|
|
|
|
|
|
connectedCallback(): void {
|
|
|
|
|
this.style.display = 'none';
|
|
|
|
|
this.innerHTML = `
|
|
|
|
|
<div class="cd-backdrop"></div>
|
|
|
|
|
<div class="cd-box" role="dialog" aria-modal="true">
|
|
|
|
|
<div class="cd-titlebar">
|
|
|
|
|
<span class="cd-icon"></span>
|
|
|
|
|
<span class="cd-title"></span>
|
|
|
|
|
<button class="cd-close" title="Close">✕</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cd-body">
|
|
|
|
|
<label class="cd-input-label"></label>
|
|
|
|
|
<input class="cd-input-field" type="text" autocomplete="off" spellcheck="false">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="cd-footer"></div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
this.querySelector( '.cd-backdrop' )!.addEventListener( 'click', () => this._close( null ) );
|
|
|
|
|
this.querySelector( '.cd-close' )!.addEventListener( 'click', () => this._close( null ) );
|
|
|
|
|
document.addEventListener( 'keydown', ( e: KeyboardEvent ) => {
|
|
|
|
|
if ( e.key === 'Escape' && this.style.display !== 'none' ) this._close( null );
|
|
|
|
|
if ( e.key === 'Enter' && this.style.display !== 'none' ) this._submit();
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
show( options: InputDialogOptions ): Promise<string | null> {
|
|
|
|
|
this.querySelector( '.cd-icon' )!.textContent = options.icon ?? '';
|
|
|
|
|
this.querySelector( '.cd-title' )!.textContent = options.title;
|
|
|
|
|
( this.querySelector( '.cd-input-label' ) as HTMLElement ).textContent = options.label;
|
|
|
|
|
|
|
|
|
|
const input = this.querySelector( '.cd-input-field' ) as HTMLInputElement;
|
|
|
|
|
input.value = options.defaultValue ?? '';
|
|
|
|
|
|
|
|
|
|
const footer = this.querySelector( '.cd-footer' )!;
|
|
|
|
|
footer.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
const cancel = document.createElement( 'button' );
|
|
|
|
|
cancel.className = 'cd-btn cd-btn-cancel';
|
|
|
|
|
cancel.textContent = options.cancelLabel ?? 'Cancel';
|
|
|
|
|
cancel.addEventListener( 'click', () => this._close( null ) );
|
|
|
|
|
footer.appendChild( cancel );
|
|
|
|
|
|
|
|
|
|
const confirm = document.createElement( 'button' );
|
|
|
|
|
confirm.className = 'cd-btn cd-btn-confirm';
|
|
|
|
|
confirm.textContent = options.confirmLabel ?? 'OK';
|
|
|
|
|
confirm.addEventListener( 'click', () => this._submit() );
|
|
|
|
|
footer.appendChild( confirm );
|
|
|
|
|
|
|
|
|
|
this.style.display = '';
|
|
|
|
|
input.focus();
|
|
|
|
|
input.select();
|
|
|
|
|
|
|
|
|
|
return new Promise<string | null>( resolve => { this.resolver = resolve; } );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _submit(): void {
|
|
|
|
|
const value = ( this.querySelector( '.cd-input-field' ) as HTMLInputElement ).value.trim();
|
|
|
|
|
this._close( value || null );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _close( value: string | null ): void {
|
|
|
|
|
this.style.display = 'none';
|
|
|
|
|
if ( this.resolver ) { this.resolver( value ); this.resolver = null; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
customElements.define( 'input-dialog', InputDialog );
|
|
|
|
|
|
|
|
|
|
export function showInputDialog( options: InputDialogOptions ): Promise<string | null> {
|
|
|
|
|
let dialog = document.querySelector( 'input-dialog' ) as InputDialog | null;
|
|
|
|
|
if ( !dialog ) {
|
|
|
|
|
dialog = document.createElement( 'input-dialog' ) as InputDialog;
|
|
|
|
|
document.body.appendChild( dialog );
|
|
|
|
|
}
|
|
|
|
|
return dialog.show( options );
|
|
|
|
|
}
|