я косяк, поправил веб. Капитан, работайте!
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* Диалоги alert/confirm для веб-оболочки зоотехника (не скрипты страниц киоска).
|
||||
* API: WespDialog.alert(msg, opts?) -> Promise<void>
|
||||
* WespDialog.confirm(msg, opts?) -> Promise<boolean>
|
||||
* opts.timerOnly — закрытие только через WespDialog.closeActive() (таймер); без кнопки, без Escape и клика по фону.
|
||||
*/
|
||||
(function (global) {
|
||||
var OVERLAY_ID = "wespShellDialogOverlay";
|
||||
var STYLE_ID = "wesp-shell-dialog-styles";
|
||||
|
||||
function injectStyles() {
|
||||
if (document.getElementById(STYLE_ID)) return;
|
||||
var s = document.createElement("style");
|
||||
s.id = STYLE_ID;
|
||||
s.textContent =
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
"{" +
|
||||
"position:fixed;inset:0;z-index:5000;" +
|
||||
"display:none;align-items:center;justify-content:center;" +
|
||||
"padding:16px;background:rgba(0,0,0,.55);" +
|
||||
"box-sizing:border-box;" +
|
||||
"opacity:0;transition:opacity .22s cubic-bezier(.22,1,.36,1);" +
|
||||
"backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);" +
|
||||
"}" +
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
'[aria-hidden="false"]{display:flex;opacity:1;}' +
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
".wesp-shell-dialog-overlay-closing{opacity:0;}" +
|
||||
".wesp-shell-dialog{" +
|
||||
"width:min(420px,100%);max-height:min(80vh,520px);overflow:auto;" +
|
||||
"background:#fff;color:#111827;border-radius:8px;" +
|
||||
"border:1px solid #e5e7eb;box-shadow:0 8px 32px rgba(0,0,0,.18);" +
|
||||
"padding:20px 22px;font-family:var(--zootech-font,Inter,system-ui,sans-serif);" +
|
||||
"-webkit-font-smoothing:antialiased;" +
|
||||
"text-align:left;" +
|
||||
"opacity:0;transform:scale(.97) translateY(8px);" +
|
||||
"transition:opacity .22s cubic-bezier(.22,1,.36,1),transform .22s cubic-bezier(.22,1,.36,1);" +
|
||||
"}" +
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
'[aria-hidden="false"]:not(.wesp-shell-dialog-overlay-closing) .wesp-shell-dialog{' +
|
||||
"opacity:1;transform:scale(1) translateY(0);" +
|
||||
"}" +
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
".wesp-shell-dialog-overlay-closing .wesp-shell-dialog{" +
|
||||
"opacity:0;transform:scale(.97) translateY(8px);" +
|
||||
"}" +
|
||||
"@media (prefers-reduced-motion:reduce){" +
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
",.wesp-shell-dialog{transition:none!important;transform:none!important;}" +
|
||||
"#" +
|
||||
OVERLAY_ID +
|
||||
"{backdrop-filter:none;-webkit-backdrop-filter:none;}" +
|
||||
".wesp-shell-dialog{opacity:1!important;}" +
|
||||
"}" +
|
||||
".wesp-shell-dialog-title{" +
|
||||
"margin:0 0 12px 0;font-size:1.125rem;font-weight:600;color:#111827;" +
|
||||
"}" +
|
||||
".wesp-shell-dialog-body{" +
|
||||
"margin:0;font-size:0.95rem;line-height:1.5;color:#374151;white-space:pre-wrap;word-break:break-word;" +
|
||||
"}" +
|
||||
".wesp-shell-dialog-actions{" +
|
||||
"display:flex;justify-content:flex-end;flex-wrap:wrap;gap:10px;margin-top:20px;" +
|
||||
"}" +
|
||||
".wesp-shell-dialog-btn{" +
|
||||
"min-width:120px;padding:10px 18px;border-radius:6px;font-size:0.95rem;font-weight:500;" +
|
||||
"cursor:pointer;font-family:inherit;border:1px solid #e5e7eb;background:#fff;color:#374151;" +
|
||||
"transition:background .2s,border-color .2s,box-shadow .2s;" +
|
||||
"}" +
|
||||
".wesp-shell-dialog-btn:hover{filter:brightness(.97);}" +
|
||||
".wesp-shell-dialog-btn-primary{" +
|
||||
"background:#2563eb;border-color:#2563eb;color:#fff;" +
|
||||
"}" +
|
||||
".wesp-shell-dialog-btn-primary:hover{box-shadow:0 4px 12px rgba(37,99,235,.25);}" +
|
||||
".wesp-shell-dialog-btn-danger{" +
|
||||
"background:#ef4444;border-color:#ef4444;color:#fff;" +
|
||||
"}" +
|
||||
".wesp-shell-dialog--danger{border-top:4px solid #ef4444;}" +
|
||||
".wesp-shell-dialog--success{border-top:4px solid #10b981;}" +
|
||||
"html[data-theme=dark] #" +
|
||||
OVERLAY_ID +
|
||||
" .wesp-shell-dialog{" +
|
||||
"background:var(--dm-surface,#2c3338);color:var(--dm-text,#f5f5f5);border-color:var(--dm-border,rgba(255,255,255,.12));" +
|
||||
"}" +
|
||||
"html[data-theme=dark] #" +
|
||||
OVERLAY_ID +
|
||||
" .wesp-shell-dialog-title{color:var(--dm-text,#f5f5f5);}" +
|
||||
"html[data-theme=dark] #" +
|
||||
OVERLAY_ID +
|
||||
" .wesp-shell-dialog-body{color:var(--dm-text,#f5f5f5);}" +
|
||||
"html[data-theme=dark] #" +
|
||||
OVERLAY_ID +
|
||||
" .wesp-shell-dialog-btn{" +
|
||||
"background:var(--dm-field,#3e444a);border-color:var(--dm-border,rgba(255,255,255,.12));color:var(--dm-text,#f5f5f5);" +
|
||||
"}" +
|
||||
"html[data-theme=dark] #" +
|
||||
OVERLAY_ID +
|
||||
" .wesp-shell-dialog-btn-primary{background:#2563eb;border-color:#2563eb;color:#fff;}" +
|
||||
"html[data-theme=dark] #" +
|
||||
OVERLAY_ID +
|
||||
" .wesp-shell-dialog-btn-danger{background:#dc2626;border-color:#dc2626;color:#fff;}" +
|
||||
"";
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
function ensureDom() {
|
||||
injectStyles();
|
||||
var el = document.getElementById(OVERLAY_ID);
|
||||
if (el) return el;
|
||||
el = document.createElement("div");
|
||||
el.id = OVERLAY_ID;
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
el.innerHTML =
|
||||
'<div class="wesp-shell-dialog" role="dialog" aria-modal="true" aria-labelledby="wespShellDialogTitle">' +
|
||||
'<h2 class="wesp-shell-dialog-title" id="wespShellDialogTitle"></h2>' +
|
||||
'<p class="wesp-shell-dialog-body" id="wespShellDialogBody"></p>' +
|
||||
'<div class="wesp-shell-dialog-actions" id="wespShellDialogActions"></div>' +
|
||||
"</div>";
|
||||
el.addEventListener("click", function (e) {
|
||||
if (e.target === el) {
|
||||
backdropDismiss();
|
||||
}
|
||||
});
|
||||
document.body.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
var pendingResolve = null;
|
||||
var pendingMode = null;
|
||||
var blockUserDismiss = false;
|
||||
|
||||
function backdropDismiss() {
|
||||
if (blockUserDismiss) return;
|
||||
if (pendingMode === "confirm" && pendingResolve) {
|
||||
var fn = pendingResolve;
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
fn(false);
|
||||
} else if (pendingMode === "alert" && pendingResolve) {
|
||||
var fn2 = pendingResolve;
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
fn2();
|
||||
}
|
||||
}
|
||||
|
||||
function prefersReducedMotion() {
|
||||
return (
|
||||
global.matchMedia &&
|
||||
global.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
var el = document.getElementById(OVERLAY_ID);
|
||||
if (!el || el.getAttribute("aria-hidden") === "true") return;
|
||||
var dlg = el.querySelector(".wesp-shell-dialog");
|
||||
if (dlg) dlg.removeAttribute("tabindex");
|
||||
blockUserDismiss = false;
|
||||
document.removeEventListener("keydown", onKey);
|
||||
|
||||
if (prefersReducedMotion()) {
|
||||
el.classList.remove("wesp-shell-dialog-overlay-closing");
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
el.classList.add("wesp-shell-dialog-overlay-closing");
|
||||
var done = false;
|
||||
var finish = function () {
|
||||
if (done) return;
|
||||
done = true;
|
||||
el.classList.remove("wesp-shell-dialog-overlay-closing");
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
};
|
||||
el.addEventListener(
|
||||
"transitionend",
|
||||
function (e) {
|
||||
if (e.target === el) finish();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
global.setTimeout(finish, 280);
|
||||
}
|
||||
|
||||
function closeActive() {
|
||||
if (pendingMode === "confirm" && pendingResolve) {
|
||||
var fnC = pendingResolve;
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
fnC(false);
|
||||
} else if (pendingMode === "alert" && pendingResolve) {
|
||||
var fnA = pendingResolve;
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
fnA();
|
||||
}
|
||||
}
|
||||
|
||||
function onKey(e) {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
if (blockUserDismiss) return;
|
||||
backdropDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
var el = document.getElementById(OVERLAY_ID);
|
||||
if (!el) return;
|
||||
el.classList.remove("wesp-shell-dialog-overlay-closing");
|
||||
el.setAttribute("aria-hidden", "false");
|
||||
document.addEventListener("keydown", onKey);
|
||||
if (!prefersReducedMotion()) {
|
||||
var dlg = el.querySelector(".wesp-shell-dialog");
|
||||
if (dlg) {
|
||||
dlg.style.opacity = "0";
|
||||
dlg.style.transform = "scale(0.97) translateY(8px)";
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () {
|
||||
dlg.style.opacity = "";
|
||||
dlg.style.transform = "";
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setDialogVariant(dialogEl, variant) {
|
||||
dialogEl.classList.remove("wesp-shell-dialog--danger", "wesp-shell-dialog--success");
|
||||
if (variant === "danger") dialogEl.classList.add("wesp-shell-dialog--danger");
|
||||
else if (variant === "success") dialogEl.classList.add("wesp-shell-dialog--success");
|
||||
}
|
||||
|
||||
function displayDialogMessage(message, opts) {
|
||||
const variant = opts && opts.variant;
|
||||
const fallback = opts && opts.fallback;
|
||||
if (variant === "danger" && global.WespUserMessages?.userFacingMessage) {
|
||||
return global.WespUserMessages.userFacingMessage(message, fallback || "Не удалось выполнить операцию. Попробуйте ещё раз.");
|
||||
}
|
||||
return message == null ? "" : String(message);
|
||||
}
|
||||
|
||||
function alert(message, opts) {
|
||||
opts = opts || {};
|
||||
return new Promise(function (resolve) {
|
||||
var overlay = ensureDom();
|
||||
var dialog = overlay.querySelector(".wesp-shell-dialog");
|
||||
var titleEl = document.getElementById("wespShellDialogTitle");
|
||||
var bodyEl = document.getElementById("wespShellDialogBody");
|
||||
var actions = document.getElementById("wespShellDialogActions");
|
||||
titleEl.textContent = opts.title != null ? String(opts.title) : "Сообщение";
|
||||
bodyEl.textContent = displayDialogMessage(message, opts);
|
||||
setDialogVariant(dialog, opts.variant || "info");
|
||||
actions.innerHTML = "";
|
||||
blockUserDismiss = !!opts.timerOnly;
|
||||
pendingMode = "alert";
|
||||
pendingResolve = resolve;
|
||||
if (!opts.timerOnly) {
|
||||
var ok = document.createElement("button");
|
||||
ok.type = "button";
|
||||
ok.className = "wesp-shell-dialog-btn wesp-shell-dialog-btn-primary";
|
||||
ok.textContent = opts.okText || "Понятно";
|
||||
ok.addEventListener("click", function () {
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
resolve();
|
||||
});
|
||||
actions.appendChild(ok);
|
||||
show();
|
||||
ok.focus();
|
||||
} else {
|
||||
dialog.setAttribute("tabindex", "-1");
|
||||
show();
|
||||
dialog.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function confirm(message, opts) {
|
||||
opts = opts || {};
|
||||
return new Promise(function (resolve) {
|
||||
blockUserDismiss = false;
|
||||
var overlay = ensureDom();
|
||||
var dialog = overlay.querySelector(".wesp-shell-dialog");
|
||||
var titleEl = document.getElementById("wespShellDialogTitle");
|
||||
var bodyEl = document.getElementById("wespShellDialogBody");
|
||||
var actions = document.getElementById("wespShellDialogActions");
|
||||
titleEl.textContent = opts.title != null ? String(opts.title) : "Подтверждение";
|
||||
bodyEl.textContent = message == null ? "" : String(message);
|
||||
setDialogVariant(dialog, opts.variant || "info");
|
||||
actions.innerHTML = "";
|
||||
var cancel = document.createElement("button");
|
||||
cancel.type = "button";
|
||||
cancel.className = "wesp-shell-dialog-btn";
|
||||
cancel.textContent = opts.cancelText || "Отмена";
|
||||
var ok = document.createElement("button");
|
||||
ok.type = "button";
|
||||
ok.className =
|
||||
"wesp-shell-dialog-btn " +
|
||||
(opts.danger ? "wesp-shell-dialog-btn-danger" : "wesp-shell-dialog-btn-primary");
|
||||
ok.textContent = opts.okText || "Да";
|
||||
cancel.addEventListener("click", function () {
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
resolve(false);
|
||||
});
|
||||
ok.addEventListener("click", function () {
|
||||
pendingResolve = null;
|
||||
pendingMode = null;
|
||||
hide();
|
||||
resolve(true);
|
||||
});
|
||||
actions.appendChild(cancel);
|
||||
actions.appendChild(ok);
|
||||
pendingMode = "confirm";
|
||||
pendingResolve = resolve;
|
||||
show();
|
||||
ok.focus();
|
||||
});
|
||||
}
|
||||
|
||||
global.WespDialog = {
|
||||
alert: alert,
|
||||
confirm: confirm,
|
||||
closeActive: closeActive,
|
||||
};
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Reference in New Issue
Block a user