2026-01-26 12:59:59 +09:00

244 lines
7.4 KiB
JavaScript

class UiDialog {
constructor(rootId = "uiDialog") {
this.root = document.getElementById(rootId);
if (!this.root) return;
this.titleEl = this.root.querySelector("#uiDialogTitle");
this.msgEl = this.root.querySelector("#uiDialogMessage");
this.okBtn = this.root.querySelector("#uiDialogOk");
this.cancelBtn = this.root.querySelector("#uiDialogCancel");
this._resolver = null;
this._type = "alert";
this._lastFocus = null;
// close triggers
this.root.addEventListener("click", (e) => {
if (!e.target?.dataset?.uidialogClose) return;
// 어떤 close인지 구분 (backdrop / x)
const isBackdrop = !!e.target.closest(".ui-dialog__backdrop");
const isX = !!e.target.closest(".ui-dialog__x");
// 정책에 따라 차단 (기본: 모두 차단)
if (isBackdrop && !this._closePolicy?.backdrop) return;
if (isX && !this._closePolicy?.x) return;
this._resolve(false);
});
// keyboard
document.addEventListener("keydown", (e) => {
if (!this.isOpen()) return;
if (e.key === "Escape") {
if (this._closePolicy?.esc) this._resolve(false);
return;
}
if (e.key === "Enter") {
// Enter는 OK로 처리 (기본 true)
if (this._closePolicy?.enter !== false) this._resolve(true);
return;
}
});
this.okBtn.addEventListener("click", () => this._resolve(true));
this.cancelBtn.addEventListener("click", () => this._resolve(false));
}
isOpen() {
return !!this.root && this.root.classList.contains("is-open");
}
alert(message, options = {}) {
return this._open("alert", message, options);
}
confirm(message, options = {}) {
return this._open("confirm", message, options);
}
_open(type, message, options) {
if (!this.root) return Promise.resolve(false);
const {
title = type === "confirm" ? "확인" : "알림",
okText = "확인",
cancelText = "취소",
dangerous = false,
// ✅ 기본: 밖 클릭/닫기(X)/ESC로 닫기 금지
closeOnBackdrop = false,
closeOnX = false,
closeOnEsc = false,
closeOnEnter = true,
} = options;
this._closePolicy = {
backdrop: !!closeOnBackdrop,
x: !!closeOnX,
esc: !!closeOnEsc,
enter: closeOnEnter !== false,
};
this._type = type;
this._lastFocus = document.activeElement;
this.titleEl.textContent = title;
this.msgEl.textContent = message ?? "";
this.okBtn.textContent = okText;
this.cancelBtn.textContent = cancelText;
// alert면 cancel 숨김
if (type === "alert") {
this.cancelBtn.style.display = "none";
} else {
this.cancelBtn.style.display = "";
}
// danger 스타일
this.okBtn.classList.toggle("ui-dialog__btn--danger", !!dangerous);
// open
this.root.classList.add("is-open");
this.root.setAttribute("aria-hidden", "false");
document.documentElement.style.overflow = "hidden";
// focus
setTimeout(() => this.okBtn.focus(), 0);
return new Promise((resolve) => {
this._resolver = resolve;
});
}
_resolve(ok) {
if (!this.isOpen()) return;
// close
this.root.classList.remove("is-open");
this.root.setAttribute("aria-hidden", "true");
document.documentElement.style.overflow = "";
const r = this._resolver;
this._resolver = null;
// focus restore
try { this._lastFocus?.focus?.(); } catch (e) {}
if (typeof r === "function") r(!!ok);
}
}
// 전역으로 노출 (모든 페이지에서 바로 호출)
window.uiDialog = new UiDialog();
// ======================================================
// ✅ Global showMsg / clearMsg (공통 사용)
// - 페이지에서는 showMsg/clearMsg만 호출
// - await showMsg(...) 하면 버튼 클릭 전까지 다음 코드 진행이 멈춤
// ======================================================
(function () {
let cachedHelpEl = null;
function getHelpEl(opt = {}) {
// opt.helpId를 주면 그걸 우선 사용
if (opt.helpId) {
const el = document.getElementById(opt.helpId);
if (el) return el;
}
// 기본 fallback id (너가 쓰는 reg_phone_help)
if (cachedHelpEl && document.contains(cachedHelpEl)) return cachedHelpEl;
cachedHelpEl = document.getElementById("reg_phone_help");
return cachedHelpEl;
}
/**
* showMsg(msg, opt)
* opt: {
* type:'alert'|'confirm',
* title, okText, cancelText, dangerous,
* redirect, helpId
* }
* return: Promise<boolean> (confirm: true/false, alert: true)
*/
window.showMsg = async function (msg, opt = {}) {
const d = Object.assign({
type: "alert",
title: "알림",
okText: "확인",
cancelText: "취소",
dangerous: false,
redirect: "",
helpId: "",
}, opt || {});
// ✅ 모달이 있으면 모달로 (여기서 await로 멈춤)
if (window.uiDialog && typeof window.uiDialog[d.type] === "function") {
const ok = await window.uiDialog[d.type](msg || "", d);
if (d.redirect) {
// alert: OK(true)일 때만 / confirm: OK(true)일 때만
if (ok) {
window.location.href = d.redirect;
}
}
return d.type === "confirm" ? !!ok : true;
}
// ✅ fallback (모달이 없으면 help 영역에 표시)
const helpEl = getHelpEl(d);
if (helpEl) {
helpEl.style.display = "block";
helpEl.textContent = msg || "";
return true;
}
// 마지막 fallback
alert(msg || "");
return true;
};
/**
* clearMsg(helpId?)
*/
window.clearMsg = function (helpId = "") {
const el = helpId ? document.getElementById(helpId) : getHelpEl({});
if (!el) return;
el.style.display = "none";
el.textContent = "";
};
})();
// ======================================================
// 서버 flash 자동 실행 (기존 유지)
// ======================================================
document.addEventListener("DOMContentLoaded", async () => {
const flashEl = document.getElementById("uiDialogFlash");
if (!flashEl || !window.uiDialog) return;
let payload = null;
try { payload = JSON.parse(flashEl.textContent || "{}"); } catch (e) {}
if (!payload) return;
/**
* payload 예시:
* { type:'alert'|'confirm', message:'...', title:'...', okText:'...', cancelText:'...', redirect:'/...' }
*/
const type = payload.type || "alert";
const ok = await window.uiDialog[type](payload.message || "", payload);
// confirm이고 OK일 때만 redirect 같은 후속 처리
if (type === "confirm") {
if (ok && payload.redirect) window.location.href = payload.redirect;
} else {
if (payload.redirect) window.location.href = payload.redirect;
}
});