console
((name, root) => {
const { console, imports } = (function (root) {
if (typeof root.imports === "undefined" || !Object.isSealed(root.imports)) {
function imports(dependencies, callback) {
const deps = [].concat(dependencies);
addEventListener("import", _imports);
function _imports() {
if (callback != null) {
if (deps.some(name => !root.imports.hasOwnProperty(name))) {
return;
}
removeEventListener("import", _imports);
const values = deps.map(name => root.imports[name]);
if (typeof requestIdleCallback === "function") {
requestIdleCallback(() => callback(...values), { timeout: 100 });
} else if (typeof setTimeout === "function") {
setTimeout(() => callback(...values));
} else {
callback(...values);
}
}
}
_imports();
}
root.imports = new Proxy(imports, {
get: (target, prop) => target[prop],
set: (target, prop, value) => {
if (target.hasOwnProperty(prop)) {
console.warn(`import: ${prop} has been replaced!`, { old: target[prop], new: value });
}
target[prop] = value;
if (typeof window !== "undefined") {
window[prop] = value;
}
dispatchEvent(new CustomEvent("import", { detail: { name, value } }));
return true;
}
});
Object.defineProperty(root, "imports", {
configurable: false,
enumerable: false,
writable: false,
});
Promise.createTask = function () {
const task = {};
const promise = new Promise((resolve, reject) => {
task.resolve = resolve;
task.reject = function (msg, unhandledrejection) {
const error = msg instanceof Error ? msg : new Error(msg || "promise rejected", { cause: false });
if (error.cause == null && unhandledrejection !== true) {
error.cause = false;
}
reject(error);
}
});
task.promise = promise;
task.then = promise.then.bind(promise);
task.catch = promise.catch && promise.catch.bind(promise);
task.finally = promise.finally && promise.finally.bind(promise);
return task;
};
addEventListener("unhandledrejection", e => e.reason && e.reason.cause === false && e.preventDefault());
}
return {
console: new Proxy(root.console || console, {
get: (target, prop) => target && target.debugs && target.debugs.some(x => x === name || x === "*") ? target[prop] : () => { },
}),
imports: root.imports,
}
})(root || this);
if (imports[name]) {
return;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function (selector) {
let elem = this;
while (elem) {
if (elem.matches(selector)) {
return elem;
}
elem = elem.parentElement;
}
return null;
}
}
Element.prototype.isMouseover = function (e, rect) {
if (e.clientX == null || e.clientY == null) {
return false;
}
if (rect == null) {
rect = this.getBoundingClientRect();
}
return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
};
Element.prototype.setAttributes = function (attrs) {
if (attrs == null) {
return;
}
for (let key in attrs) {
if (attrs[key] !== false) {
this.setAttribute(key, attrs[key]);
}
}
};
Element.prototype.addEventListeners = function (events, useCapture) {
if (events == null) {
return;
}
const controller = new AbortController();
for (const key in events) {
const handler = events[key];
for (const ev of key.split(",")) {
this.addEventListener(ev.trim(), handler, { signal: controller.signal, useCapture });
}
}
return () => controller.abort();
};
(function (events) {
for (const type of events) {
HTMLDialogElement.prototype[type] = function (returnValue) {
const event = new CustomEvent(type, { cancelable: true, detail: { returnValue } });
this.dispatchEvent(event);
if (event.defaultPrevented) {
return;
}
this.close(returnValue);
};
}
})(["cancel", "confirm"])
Object.defineProperty(HTMLDialogElement.prototype, "isModal", {
configurable: false,
enumerable: false,
get() { return this.matches("dialog:modal"); }
});
Object.defineProperty(root, "activeDialog", {
configurable: false,
enumerable: false,
get() { return document.activeElement.closest("dialog"); }
});
addEventListener("DOMContentLoaded", e => {
function redirectMouseEvent(dialog) {
if (dialog.isModal) {
const controller = new AbortController();
let mouse = null;
dialog.addEventListener("mousemove", e => {
const mouse2 = dialog.isMouseover(e) ? 0 : 1;
if (mouse === mouse2) {
return;
}
mouse = mouse2;
const events1 = ["mouseover", "mouseout"];
const events2 = ["mouseenter", "mouseleave"];
dialog.dispatchEvent(new MouseEvent(events1[mouse], e));
dialog.dispatchEvent(new MouseEvent(events2[mouse], e));
console.log("手动触发:" + events1[mouse]);
console.log("手动触发:" + events2[mouse]);
}, { signal: controller.signal });
dialog.addEventListener("close", e => controller.abort(), { signal: controller.signal, once: true });
return controller;
}
}
function dialogOpen(dialog) {
let controller = redirectMouseEvent(dialog);
const delayclose = dialog.getAttribute("delayclose");
if (!delayclose) {
return;
}
let [delay, refresh] = delayclose.split(",").map(x => parseInt(x));
if (!Number.isFinite(delay) || delay <= 0) {
return;
}
if (!Number.isFinite(refresh)) {
refresh = delay;
}
const firstCloseTime = Date.now() + delay;
let timer = setTimeout(() => dialog.cancel(), delay);
if (controller == null) {
controller = new AbortController();
dialog.addEventListener("close", e => controller.abort(), { signal: controller.signal, once: true });
}
controller.signal.addEventListener("abort", e => clearTimeout(timer), { once: true });
if (refresh >= 0) {
dialog.addEventListeners({
"mouseenter": e => {
console.log(e.type, e);
if (dialog.isModal || !dialog.isMouseover(e)) {
return;
}
clearTimeout(timer);
},
"mouseleave": e => {
console.log(e.type, e);
clearTimeout(timer);
timer = setTimeout(() => dialog.cancel(), Math.max(refresh, firstCloseTime - Date.now()));
},
}, { signal: controller.signal });
}
}
const config = { subtree: true, attributeFilter: ["open"], attributeOldValue: true };
const observer = new MutationObserver(r => callback(r));
observer.observe(document.body, config);
setInterval(() => callback(observer.takeRecords()), 100);
dispatchEvent(document.querySelectorAll("dialog[open]"));
function callback(records) {
const elems = records.filter(x => x.type === "attributes" && x.oldValue == null && x.target.nodeName === 'DIALOG' && x.target.hasAttribute("open")).map(x => x.target);
dispatchEvent(elems);
}
function dispatchEvent(elems) {
for (const elem of elems) {
console.log("dispatchEvent:open", elem);
elem.setAttribute("open", "true");
dialogOpen(elem);
elem.dispatchEvent(new Event("open"), { cancelable: false, bubbles: false });
}
}
});
(function (eventType) {
addEventListener(eventType + "down", e => {
if (e.button !== 0 || !e.target.matches || !e.target.matches("dialog[open][moveable] > header, dialog[open][moveable] ")) {
return;
}
const dialog = e.target.closest("dialog[open][moveable]");
if (!dialog) {
return;
}
const rect = dialog.getBoundingClientRect();
if (!dialog.isMouseover(e, rect)) {
return;
}
console.log(e);
e.preventDefault();
const mover = (function () {
const start = (() => {
const [left, top] = getComputedStyle(dialog).getPropertyValue("transform").split(", ").slice(-2).map(x => parseInt(x));
return { left: left || 0, top: top || 0, x: e.clientX, y: e.clientY };
})();
const little = 20;
const limit = {
top: {
min: -rect.top + start.top - rect.height + little,
max: window.innerHeight - rect.top - rect.height + start.top + rect.height - little
},
left: {
min: -rect.left + start.left - rect.width + little,
max: window.innerWidth - rect.left - rect.width + start.left + rect.width - little
}
};
const result = { top: start.top, left: start.left };
result.move = (x, y) => {
const to = {
top: start.top + y - start.y,
left: start.left + x - start.x,
}
if (to.top < limit.top.min) {
to.top = limit.top.min;
} else if (to.top > limit.top.max) {
to.top = limit.top.max;
}
if (to.left < limit.left.min) {
to.left = limit.left.min;
} else if (to.left > limit.left.max) {
to.left = limit.left.max;
}
result.top = to.top;
result.left = to.left;
console.log({ x, y, top: result.top, left: result.left, limit });
}
return result;
})();
const frame = 30;
const transition = dialog.style.transition;
dialog.style.setProperty("transition", `all ${(1 / frame).toFixed(2)}s linear`, "important");
const timer = setInterval(() => moveDialog(dialog, mover.left, mover.top), 1000 / frame);
const controller = new AbortController();
controller.signal.addEventListener("abort", () => {
clearInterval(timer);
dialog.style.transition = transition;
e.pointerId && dialog.releasePointerCapture(e.pointerId);
});
addEventListener(eventType + "move", e => mover.move(e.clientX, e.clientY), { signal: controller.signal });
addEventListener(eventType + "up", e => controller.abort(), { signal: controller.signal });
e.pointerId && dialog.setPointerCapture(e.pointerId);
});
})(typeof root.PointerEvent === "undefined" ? "mouse" : "pointer");
addEventListener("click", e => {
const clickHandlers = {
"dialog[open] button": (x, e) => {
const method = x.getAttribute("formmethod") || (x.form && x.form.method);
if (method !== "dialog") {
return;
}
e.preventDefault();
const dialog = x.closest("dialog[open]");
(x.type === "submit") ? dialog.confirm(x.value) : dialog.cancel(x.value);
},
"dialog[open][autoclose]": (x, e) => (!x.isMouseover(e)) && x.cancel(),
};
for (let selector in clickHandlers) {
if (e.target.matches && e.target.matches(selector)) {
clickHandlers[selector](e.target, e);
return;
}
}
});
addEventListener("cancel", e => {
const dialog = e.target;
if (!dialog.matches || !dialog.matches("dialog[open]")) {
return;
}
if (!e.defaultPrevented) {
dialog.setAttribute("open", "");
}
const duration = getComputedStyle(dialog).getPropertyValue("transition-duration") || "";
const ms = duration.slice(-2) === "ms" ? parseFloat(duration) : parseFloat(duration) * 1000;
const resultValue = e.detail.returnValue;
const animationClose = function () {
dialog.style.transform = "";
setTimeout(() => dialog.close(resultValue), ms);
}
const text = dialog.getAttribute("confirm");
if (text != null) {
e.preventDefault();
msgbox(ebuilder.h3(text || "确定要关闭吗?"), "confirm", dialog).then(animationClose);
}
if (ms > 0) {
e.preventDefault();
animationClose();
}
}, true);
function DialogQueue(change) {
const dialogs = [];
function active(dialog) {
dialog && dialog.dispatchEvent(new Event("active"));
}
function inactive(dialog) {
dialog && dialog.dispatchEvent(new Event("inactive"));
}
this.push = dialog => {
dialog.addEventListener("close", e => {
const index = dialogs.indexOf(dialog);
if (index >= 0) {
dialogs.splice(index, 1);
if (index > 0 && index === dialogs.length) {
active(dialogs[index - 1]);
}
change && change(dialogs, { type: "remove", dialog });
}
});
dialogs.push(dialog);
if (dialogs.length > 1) {
inactive(dialogs[dialogs.length - 2]);
}
change && change(dialogs, { type: "add", dialog });
};
}
const ebuilder = (function () {
function createElement(tag, children, attrs, events) {
const elem = document.createElement(tag);
if (children) {
if (typeof children === "string") {
elem.innerHTML = children;
} else {
for (const item of [].concat(children).map(x => typeof x === "function" ? x() : x)) {
if (item instanceof Node) {
elem.appendChild(item);
} else if (item) {
elem.appendChild(document.createTextNode(item));
}
}
}
}
elem.setAttributes(attrs);
elem.addEventListeners(events);
return elem;
}
return new Proxy({}, {
get(target, prop) {
return (children, attrs, events) => createElement(prop, children, attrs, events);
}
});
})();
function moveDialog(dialog, x, y) {
x = typeof x === "number" ? (x + "px") : (x || "0");
y = typeof y === "number" ? (y + "px") : (y || "0");
dialog.style.transform = `translate(${x}, ${y})`;
};
const presetMsgBox = {
"": {},
default: {
dialogQueue: new DialogQueue(),
closeBtn: true,
attrs: { style: "max-width: 40%;min-width: 280px;max-height:50%", "moveable": "" }
},
};
function mergeOptions(...options) {
const merge = (target, source) => {
if (source == null) {
return target;
}
if (typeof source !== "object" || typeof target !== "object" ||
target == null || Array.isArray(source) || Array.isArray(target)) {
return source;
}
for (let key in source) {
target[key] = merge(target[key], source[key]);
}
return target;
};
return options.reduce(merge);
}
function msgbox(content, option, owner) {
(typeof option === "string") && (option = { type: option });
const type = (option && option.type) || "";
option = mergeOptions({}, presetMsgBox.default, presetMsgBox[type], { ...option }, { type });
const dialog = ebuilder.dialog([
option.closeBtn && ebuilder.button("×", { type: "button", formmethod: "dialog" }),
option.title && ebuilder.header(option.title),
ebuilder.main(content),
option.buttons && ebuilder.footer(option.buttons),
], option.attrs);
type && dialog.classList.add(type);
const task = Promise.createTask();
dialog.addEventListeners({
close(e) { this.remove(); },
confirm(e) { task.resolve(e.detail && e.detail.returnValue); },
cancel(e) { task.reject(e.detail && e.detail.returnValue); },
});
dialog.then = task.then;
dialog.catch = task.catch;
dialog.finally = task.finally;
(owner || document.body).appendChild(dialog);
option.show ? option.show(dialog, owner) : dialog.showModal(owner);
if (dialog.isModal) {
dialog.addEventListener("active", e => e.target.style.opacity = "");
dialog.addEventListener("inactive", e => e.target.style.opacity = 0.2);
}
option.dialogQueue && option.dialogQueue.push(dialog);
return dialog;
}
function beautify() {
const css = `
dialog {
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
min-width: 260px;
left:0;
top:0;
opacity: 0;
}
dialog form[method=dialog]:empty {
display: none;
}
dialog.tips * {
cursor: pointer;
padding: 0;
}
dialog :focus-visible ,dialog:focus-visible {
outline: 0;
}
dialog[open]{
display: flex;
flex-direction: column;
justify-content: space-between;
opacity: 1;
}
dialog[moveable], dialog[moveable] > header {
cursor: move;
}
dialog * {
cursor: initial;
}
dialog > header {
align-items: center;
padding: 10px;
padding-top: 0;
border-bottom: 1px solid #ccc;
font-size: .9em;
color: #777;
font-weight: bold;
}
dialog > main, dialog > article {
padding: 10px;
border-radius: 5px 5px 0 0;
word-wrap: break-word;
overflow-y: auto;
flex-grow: 1;
}
dialog > footer {
border-top: 1px solid #ccc;
text-align: center;
padding: 10px;
padding-bottom: 0;
}
dialog > footer > button {
border: none;
color: white;
padding: 10px 24px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
border-radius: 5px;
margin-top: 10px;
cursor: pointer;
}
dialog > footer > button ~ button {
margin-left: 5%;
}
dialog > footer > button[type=submit] {
background-color: #4CAF50;
}
dialog > footer > button[type=reset] {
background-color: #f44336;
}
dialog > button[formmethod=dialog],dialog > header > button[formmethod=dialog],dialog button.close {
background-color: transparent;
position: absolute;
right: -0;
top: -0;
min-width: 15px;
height: 15px;
line-height: 12px;
text-align: center;
color: #777;
border: 0;
padding: 0;
height: 1em;
width: 1em;
line-height: 1em;
cursor: default;
}
dialog > button[formmethod=dialog]:hover,dialog > header > button[formmethod=dialog]:hover{
background-color: #ddd;
}
dialog > button[formmethod=dialog]:active,dialog > header > button[formmethod=dialog]:active{
background-color: #eee;
}
dialog[closeBtn]::before {
pointer-events:none;
content: "×";
background-color: transparent;
position: absolute;
right: -0;
top: -0;
min-width: 15px;
height: 15px;
line-height: 12px;
text-align: center;
color: #777;
border: 0;
padding: 0;
height: 1em;
width: 1em;
line-height: 1em;
cursor: default;
}
dialog[nobackdrop]::backdrop {
opacity: 0;
}
dialog.alert main, dialog.confirm main {
display: flex;
flex-direction: colum;
align-content: center;
justify-content: center;
align-items: center;
font-size: 1.2em;
}
dialog.tips {
background-color: #f0f9eb;
border-color: #e1f3d8;
color: #67c23a;
max-width: 40%;
min-height: 0;
transition: all 0.2s ease-in;
cursor: pointer;
margin-bottom: 10px;
transform:translate(0, -100%);
}
dialog.tips[open=true]{
transform:translate(0, 0);
}
dialog.duang {
background-color: #fdf6ec;
border-color: #faecd8;
color: #e6a23c;
max-width: 40%;
min-height: 0;
transform:scale(0.5);
transition: all 0.05s cubic-bezier(0.68, -0.55, 0, 1.98);
}
dialog.duang[open=true]{
transform:scale(1);
}
dialog.toast {
background-color: #edf2fc;
border-color: #ebeef5;
color: #909399;
max-width: 40%;
min-height: 0;
margin-right: 5px;
margin-top: 5px;
margin-bottom: 5px;
transform-origin: center right;
transition: all 0.3s ease-in;
transform: scale(0, 1);
}
dialog.toast[open=true]{
transform: scale(1, 1)
}
.toast-container {
position: fixed;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-start;
right: 0;
top: 0;
}
`;
const style = ebuilder.style(css, { type: "text/css", id: name });
unbeautify();
document.head.prepend(style);
}
function unbeautify() {
document.querySelectorAll("head > style#" + name).forEach(x => x.remove());
}
Object.assign(presetMsgBox, {
alert: {
buttons: [
() => ebuilder.button("确定", { type: "submit", formmethod: "dialog" }),
]
},
confirm: {
buttons: [
() => ebuilder.button("确定", { type: "submit", formmethod: "dialog" }),
() => ebuilder.button("取消", { type: "reset", formmethod: "dialog" })
]
},
tips: {
dialogQueue: new DialogQueue(function (dialogs) {
let top = 0;
for (const dialog of dialogs) {
moveDialog(dialog, 0, top);
const rect = dialog.getBoundingClientRect();
const marginBottom = parseInt(getComputedStyle(dialog).getPropertyValue("margin-bottom"));
top += rect.height + marginBottom;
}
}),
closeBtn: false,
show(dialog, owner) {
dialog.addEventListener("click", e => dialog.cancel());
dialog.show(owner);
},
attrs: {
moveable: false,
delayclose: "3000, 3000",
}
},
toast: {
dialogQueue: new DialogQueue(function (dialogs) {
let top = 0;
for (let i = dialogs.length - 1; i >= 0; i--) {
const dialog = dialogs[i];
dialog.style.top = `${top}px`;
const rect = dialog.getBoundingClientRect();
const marginBottom = parseInt(getComputedStyle(dialog).getPropertyValue("margin-bottom"));
top += rect.height + marginBottom;
}
}),
closeBtn: false,
show(dialog, owner) {
dialog.addEventListener("click", e => dialog.cancel());
dialog.show(owner);
},
attrs: {
moveable: false,
delayclose: "3000, 3000",
}
},
duang: {
dialogQueue: new DialogQueue(),
closeBtn: false,
show(dialog, owner) {
dialog.addEventListener("click", e => dialog.cancel());
dialog.addEventListener("inactive", e => e.target.close());
dialog.showModal(owner);
},
attrs: {
moveable: false,
autoclose: "",
nobackdrop: "",
delayclose: "1000, 0",
}
}
});
imports[name] = {
msgbox,
beautify,
unbeautify,
presetMsgBox,
alert(message) { return msgbox(message, "alert"); },
confirm(message) { return msgbox(message, "confirm"); }
};
imports.msgbox = msgbox;
beautify();
})("DialogExtensions", this);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html原生弹窗扩展</title>
</head>
<body>
<dialog id="d1">
<button type="submit" form="form1" formnovalidate="true" class="close">x</button>
<main>
<form id="form1" action="/post.html" method="dialog" enctype="application/json" target="_ajax" alert
onerror="console.log(event)">
<input type="text" name="username" id="username" placeholder="用户名" required> <br />
<input type="password" name="password" id="password" placeholder="密码" required> <br />
<input type="text" name="code" id="code" placeholder="验证码" required> <br />
</form>
</main>
<footer>
<button type="submit" form="form1">确定</button>
<button type="reset" formmethod="dialog">取消</button>
</footer>
</dialog>
<dialog id="d2" autoclose confirm moveable>
<button type="reset" formmethod="dialog">x</button>
<header>标题</header>
<main>
点击对话框以外内容触发自动关闭 <br />
关闭前有2次确认 <br />
可以使用鼠标拖动对话框
</main>
<footer>
<button type="submit" formmethod="dialog">确定</button>
<button type="reset" formmethod="dialog">取消</button>
</footer>
</dialog>
<dialog id="d3" delayclose="3000">
3秒自动关闭
</dialog>
<script>
let duang = 0;
let tips = 0;
let toast = 0;
</script>
<button onclick="d1.showModal()">弹窗1</button> <br />
<button onclick="d2.showModal()">弹窗2</button> <br />
<button onclick="msgbox('这是一个提示!' + (++tips),'tips')">tips</button> <br />
<button onclick="msgbox('这是一个警告!' + (++duang),'duang')">duang</button> <br />
<button onclick="msgbox('这是一个横幅!' + (++toast),'toast')">toast</button> <br />
<button onclick="openModal()">一直弹</button> <br />
<button onclick="d3.showModal()">3秒自动关闭</button> <br />
<script>
let num = 0;
function openModal() {
num++;
const dialog = msgbox("点确定打开一个新弹窗,<br /> 当前会暂时隐藏, <br />新弹窗关闭后再显示", { type: "confirm", title: num + ') 这是一个弹窗:' + new Date().toLocaleString(), });
dialog.addEventListener('confirm', e => {
e.preventDefault();
openModal();
});
}
</script>
</body>
</html>