// ==UserScript==
// @name 通用组件库
// @namespace https://greasyfork.org/zh-CN/users/1296281
// @version 1.4.1
// @license GPL-3.0
// @description 通用 UI 组件和工具函数库
// @author ShineByPupil
// @match *
// @grant none
// ==/UserScript==
(function () {
"use strict";
const colors = {
primary: "#4C6EF5",
success: "#67c23a",
info: "#909399",
warning: "#e6a23c",
danger: "#f56c6c",
};
const defaultColors = [];
const lightColors = [];
const darkColors = [];
const mixColor = (color1, color2, percent) => {
// 去掉井号并转换为 0~255 的整数
const c1 = color1.replace(/^#/, "");
const c2 = color2.replace(/^#/, "");
const r1 = parseInt(c1.substr(0, 2), 16);
const g1 = parseInt(c1.substr(2, 2), 16);
const b1 = parseInt(c1.substr(4, 2), 16);
const r2 = parseInt(c2.substr(0, 2), 16);
const g2 = parseInt(c2.substr(2, 2), 16);
const b2 = parseInt(c2.substr(4, 2), 16);
// 百分比转 0~1
const t = Math.min(Math.max(percent, 0), 100) / 100;
// 插值计算
const r = Math.round(r1 + (r2 - r1) * t);
const g = Math.round(g1 + (g2 - g1) * t);
const b = Math.round(b1 + (b2 - b1) * t);
// 转回两位十六进制,不足两位补零
const toHex = (x) => x.toString(16).padStart(2, "0");
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
};
for (let key in colors) {
const color = colors[key];
defaultColors.push(`--${key}-color: ${color};`);
for (let i = 1; i <= 9; i++) {
const p = i * 10;
lightColors.push(
`--${key}-color-light-${i}: ${mixColor(color, "#ffffff", p)};`,
);
darkColors.push(
`--${key}-color-light-${i}: ${mixColor(color, "#141414", p)};`,
);
}
}
const commonCssTemplate = document.createElement("template");
commonCssTemplate.innerHTML = `
<style>
/* 明亮模式 */
:host {
/* 主题色 */
${defaultColors.join("\n")}
/* 明亮渐变色 */
${lightColors.join("\n")}
--border-color: #dcdfe6;
--border-color-hover: #C0C4CC;
--bg-color: #FFFFFF;
--text-color: #333333;
--placeholder-color: #a8abb2;
}
:host {
font-family: Inter, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 微软雅黑, Arial, sans-serif;
}
:host([disabled]) * {
cursor: not-allowed;
}
/* 夜间模式 */
:host-context(:is(.ex, .dark, [data-theme="dark"])) {
/* 夜间渐变色 */
${darkColors.join("\n")}
--border-color: #4C4D4F;
--border-color-hover: #6C6E72;
--bg-color: #141414;
--text-color: #CFD3DC;
--placeholder-color: #8D9095;
}
</style>
`;
class Input extends HTMLElement {
input = null;
constructor() {
super();
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `<input type="text" part="input" />`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
:host {
display: inline-flex;
height: 32px;
color: var(--text-color);
border-color: var(--border-color);
border-radius: 4px;
background-color: var(--bg-color);
}
:host(:not([disabled]):hover) {
border-color: var(--border-color-hover);
}
:host(:not([disabled]):focus) {
border-color: var(--primary-color);
border-inline-end-width: 1px;
}
input::placeholder {
color: var(--placeholder-color);
}
/* 禁用 */
:host([disabled]) {
background-color: #f5f7fa;
}
:host([disabled]):host-context(:is(.ex, .dark, [data-theme="dark"])) {
background-color: #262727;
}
input {
width: -webkit-fill-available;
height: inherit;
color: currentColor;
outline: none;
box-sizing: border-box;
padding: 4px 11px;
border-width: 1px;
border-style: solid;
border-color: inherit;
border-radius: inherit;
background-color: inherit;
vertical-align: top;
transition: all 0.3s;
text-align: inherit;
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(
htmlTemplate.content,
commonCssTemplate.content.cloneNode(true),
cssTemplate.content,
);
this.input = this.shadowRoot.querySelector("input");
}
connectedCallback() {
this.input.addEventListener("input", (e) => {
e.stopPropagation();
this.value = e.target.value;
this.dispatchEvent(new CustomEvent("input", { detail: this.value }));
});
Object.values(this.attributes).forEach((attr) => {
if (!/^on/.test(attr.name)) {
this.input.setAttribute(attr.name, attr.value);
}
});
const mo = new MutationObserver((mutationsList) => {
for (const m of mutationsList) {
if (m.type === "attributes") {
const val = this.getAttribute(m.attributeName);
if (val === null) {
this.input.removeAttribute(m.attributeName);
} else {
this.input.setAttribute(m.attributeName, val);
}
}
}
});
mo.observe(this, { attributes: true });
}
get value() {
return this.input.value;
}
set value(val) {
this.input.value = val;
}
}
// todo
class Option extends HTMLElement {
constructor() {
super();
}
}
// todo
class Select extends HTMLElement {
constructor() {
super();
}
}
class Button extends HTMLElement {
static observedAttributes = ["type", "circle", "disabled", "ripple"];
constructor() {
super();
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `
<button part="button">
<slot></slot>
</button>
`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
:host {
--text-color-hover: var(--primary-color);
--bg-color: var(--bg-color);
--bg-color-hover: var(--primary-color-light-9);
--bg-color-disabled: #FFFFFF;
--border-color-hover: var(--primary-color);
--border-color-disabled: var(--border-color);
--border-radius: 5px;
}
:host-context(:is(.ex, .dark, [data-theme="dark"])) {
--bg-color-disabled: transparent;
--border-color-hover: var(--primary-color);
}
/* 禁用 */
:host([disabled]) {
--text-color: #a8abb2;
--border-color: #dcdfe6;
}
:host([disabled]):host-context(:is(.ex, .dark, [data-theme="dark"])) {
--text-color: rgba(255, 255, 255, .5);
--border-color: #414243;
}
:host([disabled]) button {
background-color: var(--bg-color-disabled);
border-color: var(--border-color-disabled);
}
/* 圆形 */
:host([circle]) {
border-radius: 50%;
--border-radius: 50%;
aspect-ratio: 1 / 1;
}
:host([circle]) button {
padding: 8px;
}
${Object.keys(colors)
.map((type) => {
return `
:host([type='${type}']) {
--text-color: #FFFFFF;
--text-color-hover: #FFFFFF;
--bg-color: var(--${type}-color);
--bg-color-hover: var(--${type}-color-light-3);
--bg-color-disabled: var(--${type}-color-light-5);
--border-color: var(--${type}-color);
--border-color-hover: var(--${type}-color-light-3);
--border-color-disabled: var(--${type}-color-light-5);
}
`;
})
.join("\n")}
:host {
position: relative;
display: inline-flex;
box-sizing: border-box;
height: 32px;
overflow: hidden;
color: var(--text-color);
background-color: var(--bg-color);
border-radius: var(--border-radius);
border-color: var(--border-color);
}
:host([disabled]) {
border-color: var(--border-color-disabled);
}
:host(:not([disabled]):hover) {
color: var(--text-color-hover);
background-color: var(--bg-color-hover);
border-color: var(--border-color-hover);
transition: all 0.3s;
}
button {
width: inherit;
height: inherit;
padding: 8px 15px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
font-family: inherit;
color: currentColor;
background: inherit;
border-width: 1px;
border-style: solid;
border-color: inherit;
border-radius: inherit;
outline: none;
cursor: pointer;
}
/* 波纹元素 */
.ripple {
position: absolute;
border-radius: 50%;
transform: scale(0);
background-color: rgba(255, 255, 255, 0.6);
animation: ripple-animation 600ms linear;
pointer-events: none;
}
/* 波纹动画关键帧 */
@keyframes ripple-animation {
to {
transform: scale(4);
opacity: 0;
}
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(
htmlTemplate.content,
commonCssTemplate.content.cloneNode(true),
cssTemplate.content,
);
}
connectedCallback() {
if (this.hasAttribute("ripple")) {
this.addEventListener("click", function (e) {
// 计算点击位置相对于按钮的位置
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
// 创建波纹元素
const ripple = document.createElement("span");
ripple.classList.add("ripple");
ripple.style.width = ripple.style.height = `${size}px`;
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
// 将波纹添加到按钮,并在动画结束后移除
this.shadowRoot.append(ripple);
ripple.addEventListener("animationend", () => {
ripple.remove();
});
});
}
}
}
class Switch extends HTMLElement {
// 事件来源类型: user | broadcast
#currentChangeSource = "user";
static get observedAttributes() {
return ["checked", "disabled", "@change"];
}
constructor() {
super();
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `
<div class="track">
<div class="thumb"></div>
</div>`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
:host {
--bg-color: #ccc;
--cursor: pointer;
}
:host {
display: inline-block;
aspect-ratio: 2/1;
height: 20px;
}
:host([checked]) {
--bg-color: ${colors.primary};
}
:host([checked]) .thumb {
transform: translateX(calc(100% + 4px));
}
:host([disabled]) {
--cursor: not-allowed;
}
.track {
width: 100%;
height: 100%;
background: var(--bg-color);
border-radius: 14px;
position: relative;
transition: background .3s;
cursor: var(--cursor);
outline: none;
}
.thumb {
aspect-ratio: 1/1;
height: calc(100% - 4px);
background: #fff;
border-radius: 50%;
position: absolute;
top: 2px;
left: 2px;
transition: transform .3s;
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(htmlTemplate.content, cssTemplate.content);
}
connectedCallback() {
const track = this.shadowRoot.querySelector(".track");
track.addEventListener("click", () => this.toggle());
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "checked" && oldValue !== newValue) {
const oldChecked = oldValue !== null;
const newChecked = newValue !== null;
this.dispatchEvent(
new CustomEvent("change", {
detail: {
value: newChecked,
oldValue: oldChecked,
source: this.#currentChangeSource,
},
}),
);
this.#currentChangeSource = "user";
}
}
get checked() {
return this.hasAttribute("checked");
}
set checked(val) {
val ? this.setAttribute("checked", "") : this.removeAttribute("checked");
}
get disabled() {
return this.hasAttribute("disabled");
}
set disabled(val) {
val
? this.setAttribute("disabled", "")
: this.removeAttribute("disabled");
}
toggle() {
if (!this.disabled) this.checked = !this.checked;
}
// 静默更新方法(不触发事件)
updateFromBroadcast(value) {
this.#currentChangeSource = "broadcast";
this.checked = value;
}
}
class Message extends HTMLElement {
static #instance = null;
static observedAttributes = ["type"];
constructor() {
super();
this.type = this.getAttribute("type");
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `
<div class="message-box">
<mx-icon class="icon"></mx-icon>
<span class="message"></span>
</div>
`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
${Object.keys(colors)
.map((type) => {
return `
:host([type='${type}']) {
--text-color: var(--${type}-color);
--bg-color: var(--${type}-color-light-7);
--border-color: var(--${type}-color-light-4);
}
`;
})
.join("\n")}
.message-box {
max-width: 300px;
font-size: 14px;
display: none;
align-items: center;
gap: 8px;
position: fixed;
top: 20px;
left: 50%;
transform: translate(-50%, 20px);
opacity: 0;
background-color: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
padding: 10px 15px;
border-radius: 5px;
z-index: 100;
}
.message-box.show {
transform: translate(-50%, 0);
opacity: 1;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.message-box.hide {
transform: translate(-50%, -20px);
opacity: 0;
transition: transform 0.6s ease, opacity 0.6s ease;
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(
htmlTemplate.content,
commonCssTemplate.content.cloneNode(true),
cssTemplate.content,
);
this.box = this.shadowRoot.querySelector(".message-box");
this.icon = this.shadowRoot.querySelector(".icon");
this.message = this.shadowRoot.querySelector(".message");
}
connectedCallback() {
this.box.addEventListener("transitionend", (e) => {
if (this.box.classList.contains("hide")) {
this.box.style.display = "none";
this.box.classList.remove("hide");
}
});
this.message.addEventListener("click", (e) => {
navigator.clipboard.writeText(e.target.textContent);
});
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (attrName === "type") {
const map = {
primary: "info",
success: "success",
info: "info",
warning: "warning",
danger: "close",
};
const iconType = map[newVal];
this.icon.setAttribute("type", iconType);
}
}
static get instance() {
if (!this.#instance) {
const el = document.createElement(getComponentName(this));
document.documentElement.appendChild(el);
this.#instance = el;
}
return this.#instance;
}
#show(message, type = "info", duration) {
const calcDuration = (message) => {
// 最小 2 秒, 最大 5 秒, 基础 0.5 秒, 每个字符 50 ms
const [min, max, base, perChar] = [2000, 5000, 500, 50];
const lengthTime = message.length * perChar;
return Math.min(max, Math.max(min, base + lengthTime));
};
this.setAttribute("type", type);
this.message.textContent = message; // 设置信息
this.message.title = message;
this.box.style.display = "flex";
requestAnimationFrame(() => {
requestAnimationFrame(() => {
this.box.classList.add("show");
});
});
clearTimeout(this._hideTimer);
this._hideTimer = setTimeout(
() => {
this.box.classList.remove("show");
this.box.classList.add("hide");
},
duration || calcDuration(message),
);
}
primary(message, duration) {
this.#show(message, "primary", duration);
}
info(message, duration) {
this.#show(message, "info", duration);
}
success(message, duration) {
this.#show(message, "success", duration);
}
error(message, duration) {
this.#show(message, "danger", duration);
}
warning(message, duration) {
this.#show(message, "warning", duration);
}
}
class Dialog extends HTMLElement {
visible = false;
#confirmBtn = null;
#cancelBtn = null;
#closeBtn = null;
static get observedAttributes() {
return ["cancel-text", "confirm-text"];
}
constructor() {
super();
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `
<main>
<header>
<slot name="header"></slot>
<button class="close">✕</button>
</header>
<article>
<slot></slot>
</article>
<footer>
<slot name="footer">
<slot name="button-before"></slot>
<mx-button class="cancel">取消</mx-button>
<slot name="button-center"></slot>
<mx-button class="confirm" type="primary">确认</mx-button>
<slot name="button-after"></slot>
</slot>
</footer>
</main>
<div class="mask"></div>
`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
:host {
display: none;
}
main {
min-width: 500px;
padding: 16px;
position: fixed;
left: 50%;
top: calc(20vh);
transform: translateX(-50%);
z-index: 3001;
border-radius: 4px;
background-color: var(--bg-color);
color: var(--text-color);
box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, .04), 0px 8px 20px rgba(0, 0, 0, .08);
}
header {
padding-bottom: 16px;
font-size: 18px;
}
article {
min-width: 500px;
}
footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 16px;
}
.close {
font-size: 16px;
aspect-ratio: 1/1;
padding: 0;
position: fixed;
top: 16px;
right: 16px;
background-color: inherit;
border: 0;
}
.close:hover {
color: #F56C6C;
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 3000;
background: rgba(0, 0, 0, 0.5);
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(
htmlTemplate.content,
commonCssTemplate.content.cloneNode(true),
cssTemplate.content,
);
this.#confirmBtn = this.shadowRoot.querySelector(".confirm");
this.#cancelBtn = this.shadowRoot.querySelector(".cancel");
this.#closeBtn = this.shadowRoot.querySelector(".close");
}
connectedCallback() {
// 按钮文字
{
const cancelText = this.getAttribute("cancel-text") || "取消";
const confirmText = this.getAttribute("confirm-text") || "确认";
this.#cancelBtn.textContent = cancelText;
this.#confirmBtn.textContent = confirmText;
}
// 事件初始化
{
// 提交按钮
this.#confirmBtn?.addEventListener("click", (e) => {
this.visible = false;
this.style.display = "none";
this.dispatchEvent(new CustomEvent("confirm"));
});
const cancel = () => {
this.visible = false;
this.style.display = "none";
this.dispatchEvent(new CustomEvent("cancel"));
};
// 关闭按钮
this.#cancelBtn?.addEventListener("click", cancel);
this.#closeBtn?.addEventListener("click", cancel);
// ESC 键盘事件
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && this.visible) {
cancel();
}
});
}
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "visible" && oldValue !== newValue) {
this.style.display = newValue !== null ? "block" : "none";
}
}
open() {
this.visible = true;
this.style.display = "block";
this.dispatchEvent(new CustomEvent("open"));
}
}
class Icon extends HTMLElement {
#paths = {
info: "M512 64a448 448 0 1 1 0 896.064A448 448 0 0 1 512 64m67.2 275.072c33.28 0 60.288-23.104 60.288-57.344s-27.072-57.344-60.288-57.344c-33.28 0-60.16 23.104-60.16 57.344s26.88 57.344 60.16 57.344M590.912 699.2c0-6.848 2.368-24.64 1.024-34.752l-52.608 60.544c-10.88 11.456-24.512 19.392-30.912 17.28a12.992 12.992 0 0 1-8.256-14.72l87.68-276.992c7.168-35.136-12.544-67.2-54.336-71.296-44.096 0-108.992 44.736-148.48 101.504 0 6.784-1.28 23.68.064 33.792l52.544-60.608c10.88-11.328 23.552-19.328 29.952-17.152a12.8 12.8 0 0 1 7.808 16.128L388.48 728.576c-10.048 32.256 8.96 63.872 55.04 71.04 67.84 0 107.904-43.648 147.456-100.416z",
success:
"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.272 38.272 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336z",
warning:
"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 192a58.432 58.432 0 0 0-58.24 63.744l23.36 256.384a35.072 35.072 0 0 0 69.76 0l23.296-256.384A58.432 58.432 0 0 0 512 256m0 512a51.2 51.2 0 1 0 0-102.4 51.2 51.2 0 0 0 0 102.4",
close:
"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 393.664L407.936 353.6a38.4 38.4 0 1 0-54.336 54.336L457.664 512 353.6 616.064a38.4 38.4 0 1 0 54.336 54.336L512 566.336 616.064 670.4a38.4 38.4 0 1 0 54.336-54.336L566.336 512 670.4 407.936a38.4 38.4 0 1 0-54.336-54.336z",
closeBold:
"M195.2 195.2a64 64 0 0 1 90.496 0L512 421.504 738.304 195.2a64 64 0 0 1 90.496 90.496L602.496 512 828.8 738.304a64 64 0 0 1-90.496 90.496L512 602.496 285.696 828.8a64 64 0 0 1-90.496-90.496L421.504 512 195.2 285.696a64 64 0 0 1 0-90.496z",
setting:
"M600.704 64a32 32 0 0 1 30.464 22.208l35.2 109.376c14.784 7.232 28.928 15.36 42.432 24.512l112.384-24.192a32 32 0 0 1 34.432 15.36L944.32 364.8a32 32 0 0 1-4.032 37.504l-77.12 85.12a357.12 357.12 0 0 1 0 49.024l77.12 85.248a32 32 0 0 1 4.032 37.504l-88.704 153.6a32 32 0 0 1-34.432 15.296L708.8 803.904c-13.44 9.088-27.648 17.28-42.368 24.512l-35.264 109.376A32 32 0 0 1 600.704 960H423.296a32 32 0 0 1-30.464-22.208L357.696 828.48a351.616 351.616 0 0 1-42.56-24.64l-112.32 24.256a32 32 0 0 1-34.432-15.36L79.68 659.2a32 32 0 0 1 4.032-37.504l77.12-85.248a357.12 357.12 0 0 1 0-48.896l-77.12-85.248A32 32 0 0 1 79.68 364.8l88.704-153.6a32 32 0 0 1 34.432-15.296l112.32 24.256c13.568-9.152 27.776-17.408 42.56-24.64l35.2-109.312A32 32 0 0 1 423.232 64H600.64zm-23.424 64H446.72l-36.352 113.088-24.512 11.968a294.113 294.113 0 0 0-34.816 20.096l-22.656 15.36-116.224-25.088-65.28 113.152 79.68 88.192-1.92 27.136a293.12 293.12 0 0 0 0 40.192l1.92 27.136-79.808 88.192 65.344 113.152 116.224-25.024 22.656 15.296a294.113 294.113 0 0 0 34.816 20.096l24.512 11.968L446.72 896h130.688l36.48-113.152 24.448-11.904a288.282 288.282 0 0 0 34.752-20.096l22.592-15.296 116.288 25.024 65.28-113.152-79.744-88.192 1.92-27.136a293.12 293.12 0 0 0 0-40.256l-1.92-27.136 79.808-88.128-65.344-113.152-116.288 24.96-22.592-15.232a287.616 287.616 0 0 0-34.752-20.096l-24.448-11.904L577.344 128zM512 320a192 192 0 1 1 0 384 192 192 0 0 1 0-384m0 64a128 128 0 1 0 0 256 128 128 0 0 0 0-256",
search:
"m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704",
refresh:
"M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64h149.056a32 32 0 0 1 32 32v148.928a32 32 0 1 1-64 0v-50.56zM276.288 295.616h92.992a32 32 0 0 1 0 64H220.16a32 32 0 0 1-32-32V178.56a32 32 0 0 1 64 0v50.56A384 384 0 0 1 896.128 512h-64a320 320 0 0 0-555.776-216.384z",
};
static observedAttributes = ["type"];
constructor() {
super();
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `<svg viewBox="0 0 1024 1024"><path d=""></path></svg>`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
:host {
display: inline-block;
width: 1em;
height: 1em;
color: currentColor;
}
svg {
width: 100%;
height: 100%;
fill: currentColor;
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(
htmlTemplate.content,
commonCssTemplate.content.cloneNode(true),
cssTemplate.content,
);
this.path = this.shadowRoot.querySelector("path");
}
connectedCallback() {}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === "type") {
this.toggle();
}
}
toggle() {
if (this.hasAttribute("type")) {
this.type = this.getAttribute("type");
if (this.type in this.#paths) {
this.path.setAttribute("d", this.#paths[this.type]);
} else {
console.warn("出现未知的 icon 类型", this);
}
}
}
}
class Badge extends HTMLElement {
constructor() {
super();
const htmlTemplate = document.createElement("template");
htmlTemplate.innerHTML = `<slot></slot><sup></sup>`;
const cssTemplate = document.createElement("template");
cssTemplate.innerHTML = `
<style>
:host {
position: relative;
}
sup {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
background-color: #f56c6c;
border-radius: 10px;
padding: 0 4px;
color: #FFFFFF;
}
</style>
`;
this.attachShadow({ mode: "open" });
this.shadowRoot.append(htmlTemplate.content, cssTemplate.content);
this.sup = this.shadowRoot.querySelector("sup");
}
set value(val) {
this.sup.textContent = val;
}
get value() {
return this.sup.textContent;
}
}
// todo
class MessageBox {}
class ConcurrencyManager {
#activeCount = 0;
#queue = [];
#max;
constructor(max = 5) {
this.#max = max;
}
enqueue(fn) {
return new Promise((resolve, reject) => {
this.#queue.push({ fn, resolve, reject });
this.#next();
});
}
setConcurrency(n) {
this.#max = n;
this.#next();
}
#next() {
if (this.#activeCount >= this.#max) return;
const job = this.#queue.shift();
if (!job) return;
this.#activeCount++;
job
.fn()
.then(job.resolve, job.reject)
.finally(() => {
this.#activeCount--;
this.#next();
});
}
}
const getComponentName = (component) =>
`mx-${component.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}`;
// 注册组件
[Input, Select, Button, Option, Switch, Message, Dialog, Icon, Badge].forEach(
(n) => {
const name = getComponentName(n);
if (!customElements.get(name)) {
customElements.define(name, n);
} else {
console.error(`${name} 组件已注册`);
}
},
);
Object.assign(window, {
MxMessage: Message.instance,
MxMgr: new ConcurrencyManager(),
});
})();