|
// retoor <retoor@molodetz.nl>
|
|
|
|
import { Component } from "./Component.js";
|
|
|
|
export class AppDialog extends Component {
|
|
connectedCallback() {
|
|
if (this._built) {
|
|
return;
|
|
}
|
|
this._built = true;
|
|
this.resolver = null;
|
|
this.mode = "confirm";
|
|
this.lastFocus = null;
|
|
this.build();
|
|
}
|
|
|
|
build() {
|
|
const overlay = document.createElement("div");
|
|
overlay.className = "dialog-overlay";
|
|
overlay.setAttribute("role", "dialog");
|
|
overlay.setAttribute("aria-modal", "true");
|
|
overlay.innerHTML =
|
|
'<div class="card dialog-card">' +
|
|
'<div class="modal-header"><h3 class="dialog-title"></h3>' +
|
|
'<button type="button" class="modal-close btn-ghost btn-icon dialog-close">×</button></div>' +
|
|
'<p class="dialog-message"></p>' +
|
|
'<div class="dialog-field" hidden><label class="dialog-field-label"></label>' +
|
|
'<input type="text" class="dialog-input" autocomplete="off"></div>' +
|
|
'<div class="modal-footer">' +
|
|
'<button type="button" class="btn btn-secondary dialog-cancel">Cancel</button>' +
|
|
'<button type="button" class="btn btn-primary dialog-confirm">OK</button>' +
|
|
"</div></div>";
|
|
this.appendChild(overlay);
|
|
|
|
this.overlay = overlay;
|
|
this.titleEl = overlay.querySelector(".dialog-title");
|
|
this.messageEl = overlay.querySelector(".dialog-message");
|
|
const uid = Math.random().toString(36).slice(2, 9);
|
|
this.titleEl.id = `dialog-title-${uid}`;
|
|
this.messageEl.id = `dialog-message-${uid}`;
|
|
overlay.setAttribute("aria-labelledby", this.titleEl.id);
|
|
overlay.setAttribute("aria-describedby", this.messageEl.id);
|
|
this.field = overlay.querySelector(".dialog-field");
|
|
this.fieldLabel = overlay.querySelector(".dialog-field-label");
|
|
this.input = overlay.querySelector(".dialog-input");
|
|
this.cancelBtn = overlay.querySelector(".dialog-cancel");
|
|
this.confirmBtn = overlay.querySelector(".dialog-confirm");
|
|
|
|
overlay.addEventListener("click", (e) => {
|
|
if (e.target === overlay) this.dismiss();
|
|
});
|
|
overlay.querySelector(".dialog-close").addEventListener("click", () => this.dismiss());
|
|
this.cancelBtn.addEventListener("click", () => this.dismiss());
|
|
this.confirmBtn.addEventListener("click", () => this.accept());
|
|
this.input.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
this.accept();
|
|
}
|
|
});
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape" && this.overlay.classList.contains("visible")) {
|
|
e.preventDefault();
|
|
this.dismiss();
|
|
}
|
|
});
|
|
overlay.addEventListener("keydown", (e) => {
|
|
if (e.key !== "Tab") return;
|
|
const items = [
|
|
...overlay.querySelectorAll(
|
|
'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
),
|
|
].filter((el) => el.offsetParent !== null);
|
|
if (!items.length) return;
|
|
const first = items[0];
|
|
const last = items[items.length - 1];
|
|
if (e.shiftKey && document.activeElement === first) {
|
|
e.preventDefault();
|
|
last.focus();
|
|
} else if (!e.shiftKey && document.activeElement === last) {
|
|
e.preventDefault();
|
|
first.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
open(mode, options) {
|
|
const opts = options || {};
|
|
this.mode = mode;
|
|
this.titleEl.textContent = opts.title || (mode === "prompt" ? "Rename" : mode === "alert" ? "Notice" : "Confirm");
|
|
this.messageEl.textContent = opts.message || "";
|
|
this.messageEl.hidden = !opts.message;
|
|
this.confirmBtn.textContent = opts.confirmLabel || (mode === "alert" ? "OK" : mode === "prompt" ? "Save" : "Confirm");
|
|
this.cancelBtn.textContent = opts.cancelLabel || "Cancel";
|
|
this.cancelBtn.style.display = mode === "alert" ? "none" : "";
|
|
this.confirmBtn.classList.toggle("dialog-danger", !!opts.danger);
|
|
|
|
if (mode === "prompt") {
|
|
this.field.hidden = false;
|
|
this.fieldLabel.textContent = opts.label || "";
|
|
this.fieldLabel.hidden = !opts.label;
|
|
this.input.value = opts.value || "";
|
|
this.input.placeholder = opts.placeholder || "";
|
|
} else {
|
|
this.field.hidden = true;
|
|
}
|
|
|
|
this.lastFocus = document.activeElement;
|
|
this.overlay.classList.add("visible");
|
|
const focusTarget = mode === "prompt" ? this.input : this.confirmBtn;
|
|
setTimeout(() => {
|
|
focusTarget.focus();
|
|
if (mode === "prompt") this.input.select();
|
|
}, 20);
|
|
|
|
return new Promise((resolve) => { this.resolver = resolve; });
|
|
}
|
|
|
|
accept() {
|
|
const value = this.mode === "prompt" ? this.input.value : true;
|
|
this.close(value);
|
|
}
|
|
|
|
dismiss() {
|
|
this.close(this.mode === "prompt" ? null : this.mode === "alert" ? undefined : false);
|
|
}
|
|
|
|
close(value) {
|
|
this.overlay.classList.remove("visible");
|
|
if (this.lastFocus && this.lastFocus.focus) this.lastFocus.focus();
|
|
const resolve = this.resolver;
|
|
this.resolver = null;
|
|
if (resolve) resolve(value);
|
|
}
|
|
|
|
confirm(options) {
|
|
return this.open("confirm", options);
|
|
}
|
|
|
|
prompt(options) {
|
|
return this.open("prompt", options);
|
|
}
|
|
|
|
alert(options) {
|
|
return this.open("alert", options);
|
|
}
|
|
}
|
|
|
|
customElements.define("dp-dialog", AppDialog);
|