fixed # 修正样式BUG

This commit is contained in:
2026-10-29 14:28:19 +08:00
parent 59e62d22c9
commit b9664de9bd
5 changed files with 274 additions and 41 deletions
+60 -4
View File
@@ -7,6 +7,20 @@ const app = path.resolve(__dirname, '../../../..');
const packages = process.env.CODEX_NODE_MODULES || 'C:/Users/Diuse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules';
const { chromium } = require(path.join(packages, 'playwright'));
const prefix = '/extension/' + encodeURIComponent('术樱包') + '/mengsan';
const holdStatus = async (page, locator, pointerType = 'mouse') => {
const box = await locator.boundingBox();
const point = {x:box.x + box.width / 2,y:box.y + box.height / 2};
if (pointerType === 'touch') {
await locator.dispatchEvent('pointerdown', {button:0,pointerId:44,
pointerType,clientX:point.x,clientY:point.y});
await page.waitForTimeout(550);
await page.evaluate(() => window.dispatchEvent(
new PointerEvent('pointerup', {pointerId:44,pointerType:'touch'})));
} else {
await page.mouse.move(point.x,point.y); await page.mouse.down();
await page.waitForTimeout(550); await page.mouse.up();
}
};
// Native themes use different button sizes. Drag onto an exposed point on the
// original player, rather than assuming its center is clear in the dense fixture.
const exposedTarget = (page,index) => page.evaluate(index => {
@@ -265,17 +279,59 @@ const server = http.createServer((req, res) => {
await page.evaluate(()=>{game.players[7].hp=0;hud.refresh();});
assert.equal(await page.evaluate(()=>game.players[7].style.cssText===originalEnemyPresentation[6].style&&!game.players[7].classList.contains('mengsan-hud-defeated-shuying')),true,'death keeps the original battlefield presentation');checks++;
await page.evaluate(()=>{game.players[7].hp=40;game.me.storage.mengsanVulnerable_shuying=3;hud.refresh();});
assert.match(await page.locator('.mengsan-hud-statuses-shuying').innerText(),/3/);checks++;
assert.equal(await page.locator('.mengsan-hud-statuses-shuying').innerText(),'','status chips contain icons only');checks++;
const heldStatus = page.locator('.mengsan-hud-statuses-shuying button').first();
await heldStatus.dispatchEvent('pointerdown', {button:0,pointerId:45,
pointerType:'touch',clientX:10,clientY:10});
await page.waitForTimeout(150);
await page.evaluate(() => {
game.me.storage.mengsanWeak_shuying=1; hud.refresh();
});
await page.waitForTimeout(450);
assert.equal(await page.locator('dialog.mengsan-hud-dialog-shuying').count(),1,'unrelated status update preserves an active hold');checks++;
await page.evaluate(() => window.dispatchEvent(
new PointerEvent('pointerup', {pointerId:45,pointerType:'touch'})));
await page.evaluate(() => {
game.me.storage.mengsanVulnerable_shuying=5; hud.refresh();
});
assert.match(await page.locator('dialog.mengsan-hud-dialog-shuying').innerText(),/易伤 5/,'open details show the current value');checks++;
await page.evaluate(() => {
delete game.me.storage.mengsanVulnerable_shuying; hud.refresh();
});
assert.match(await page.locator('dialog.mengsan-hud-dialog-shuying').innerText(),/已失效/,'removed state is explicitly expired');checks++;
await page.getByRole('button',{name:'关闭',exact:true}).click();
await page.evaluate(() => {
delete game.me.storage.mengsanWeak_shuying;
game.me.storage.mengsanVulnerable_shuying=3; hud.refresh();
});
await page.evaluate(()=>{for(const key of ['Weak','Frail','Artifact','Constrict','Tangled','Shrink','Plow','Slippery'])game.me.storage['mengsan'+key+'_shuying']=2;hud.refresh();});
assert.equal(await page.evaluate(()=>{const row=document.querySelector('.mengsan-hud-statuses-shuying').getBoundingClientRect();return [...document.querySelectorAll('.mengsan-hud-statuses-shuying button')].every(n=>{const r=n.getBoundingClientRect();return r.bottom<=row.bottom+1&&r.right<=row.right+1;});}),true,'matrix overflow keeps the visible statuses and more button inside the grid');checks++;
await page.getByRole('button',{name:'查看全部状态',exact:true}).click();
// Compact icon chips fit more states; register enough real display fields
// to exercise overflow on wide screens as well as phones.
await page.evaluate(async prefix => {
const {bindCombatStatusSource} = await import(prefix+'/battle/status-sources.js');
const keys=['turnStrength','turnEnergy','turnBlock','turnLoseHp',
'infernoLoseHp','lossDamage','lossStrength','vulnerableBonus',
'attackBlock','retaliation','colossus','freeAttack','noDraw',
'noEnergy','doubleTap','plating','exhaustBlock','exhaustDraw',
'vulnerableDraw','juggle','endAttack','retrieveAttack','bodyguard',
'autoStrike','firstBlock','blockDamage','barricade','corruption'];
bindCombatStatusSource(game.me,session,'overflow-test',() => ({
powers:Object.fromEntries(keys.map(key => [key,1])),
}));
hud.refresh();
},prefix);
await holdStatus(page,page.getByRole('button',{
name:'长按查看全部状态',exact:true}));
assert.ok(await page.getByRole('dialog').isVisible(),'overflow states remain available in the details');checks++;
await page.getByRole('button',{name:'关闭',exact:true}).click();
assert.equal(await page.evaluate(()=>{const s=document.querySelector('.mengsan-hud-center-shuying').getBoundingClientRect(),p=game.me.getBoundingClientRect();return s.bottom<=p.top&&p.top-s.bottom<=13;}),true,'status chips automatically appear just above the portrait');checks++;
await page.locator('.mengsan-hud-statuses-shuying button').first().click();
await page.locator('.mengsan-hud-statuses-shuying button').first().tap();
assert.equal(await page.locator('dialog.mengsan-hud-dialog-shuying').count(),0,'short tap does not open status details');checks++;
await holdStatus(page,page.locator('.mengsan-hud-statuses-shuying button').first());
assert.ok(await page.locator('dialog.mengsan-hud-dialog-shuying').isVisible());checks++;
await page.getByRole('button',{name:'关闭',exact:true}).click();
await page.locator('.mengsan-hud-statuses-shuying button').first().tap();
await holdStatus(page,page.locator('.mengsan-hud-statuses-shuying button').first(),'touch');
assert.ok(await page.locator('dialog.mengsan-hud-dialog-shuying').isVisible(),'touch opens the player status details');checks++;
await page.getByRole('button',{name:'关闭',exact:true}).click();
await page.evaluate(()=>game.uncheck());
+13 -7
View File
@@ -81,19 +81,25 @@ export function createHandInput(player, session, adapter, fan, env, refresh, log
aim = document.createElementNS("http://www.w3.org/2000/svg", "svg");
aim.classList.add("mengsan-hand-aim-shuying");
aim.setAttribute("aria-hidden", "true");
aim.setAttribute("preserveAspectRatio", "none");
const path = document.createElementNS(aim.namespaceURI, "path");
aim.appendChild(path); document.body.appendChild(aim);
}
const rect = card.getBoundingClientRect();
const x = rect.left + rect.width / 2, y = rect.top + 12;
aim.setAttribute("viewBox", `0 0 ${window.innerWidth} ${window.innerHeight}`);
// clientX/Y 与卡牌矩形都是视口坐标,SVG 本身却可能受到 body zoom/transform 影响。
// 用 SVG 实际屏幕矩阵反变换,不能重复除 documentZoom 或假定 SVG 没有偏移。
const matrix = aim.getScreenCTM();
if (!matrix) { removeAim(); return; }
const inverse = matrix.inverse();
const start = new window.DOMPoint(x, y).matrixTransform(inverse);
const end = new window.DOMPoint(point.x, point.y).matrixTransform(inverse);
// Use painted bounds instead of relying on WebView CTM handling of zoom.
// With preserveAspectRatio=none, viewport-to-viewBox scaling is linear.
const bounds = aim.getBoundingClientRect();
if (!(bounds.width > 0 && bounds.height > 0)) {
removeAim(); return;
}
const scaleX = window.innerWidth / bounds.width;
const scaleY = window.innerHeight / bounds.height;
const start = { x: (x - bounds.left) * scaleX,
y: (y - bounds.top) * scaleY };
const end = { x: (point.x - bounds.left) * scaleX,
y: (point.y - bounds.top) * scaleY };
if (![start.x, start.y, end.x, end.y].every(Number.isFinite)) { removeAim(); return; }
// 十周年金色细线(本地 CONFIG.LINE_COLOR/LINE_WIDTH),不采用尖塔箭头/骨节造型。
aim.firstElementChild.setAttribute("d", `M ${start.x} ${start.y} L ${end.x} ${end.y}`);
+129 -22
View File
@@ -55,56 +55,160 @@ export function mountBattleHUD(player, battle, env) {
const statuses = create('div', 'mengsan-hud-statuses-shuying', center);
statuses.setAttribute('aria-label', '玩家当前状态');
const link = create('link', '', null); link.rel = 'stylesheet'; link.href = `${assetBase}/ui/hud/style.css`;
let detailTarget = null, detailId = null, detailBody = null;
let detailSignature = null;
const closeDialog = () => {
if (!dialog) return;
const current = dialog; dialog = null;
detailTarget = null; detailId = null; detailBody = null;
detailSignature = null;
if (current.open) current.close(); current.remove();
if (previousFocus?.isConnected) previousFocus.focus();
};
const openDetails = target => {
if (disposed || !battle.session.active) return;
const refreshDetails = () => {
if (!dialog || !detailBody) return;
const entries = collectCombatStatuses(detailTarget, battle)
.filter(entry => detailId === null || entry.id === detailId);
const signature = JSON.stringify(entries);
if (signature === detailSignature) return;
detailSignature = signature; detailBody.replaceChildren();
for (const entry of entries) {
const row = create('section', 'mengsan-hud-detail-row-shuying',
detailBody);
create('strong', '', row, `${entry.name}${entry.value == null ? '' : ` ${entry.value}${entry.unit ? ` ${entry.unit}` : ''}`}`);
create('p', '', row, entry.detail);
}
if (!entries.length) create('p', '', detailBody,
detailId === null ? '暂无已登记状态。' : '该状态已失效。');
};
const openDetails = (target, id = null) => {
if (disposed || frozen || !battle.session.active) return;
closeDialog(); previousFocus = document.activeElement;
dialog = create('dialog', 'mengsan-hud-dialog-shuying', document.body);
const current = dialog;
create('h2', '', dialog, `${nameOf(target)} · 当前状态`);
const close = create('button', 'mengsan-hud-dialog-close-shuying', dialog, '关闭'); close.addEventListener('click', closeDialog);
const entries = collectCombatStatuses(target, battle);
for (const entry of entries) {
const row = create('section', 'mengsan-hud-detail-row-shuying', dialog);
create('strong', '', row, `${entry.name}${entry.value == null ? '' : ` ${entry.value}${entry.unit ? ` ${entry.unit}` : ''}`}`);
create('p', '', row, entry.detail);
}
if (!entries.length) create('p', '', dialog, '暂无已登记状态。');
detailTarget = target; detailId = id;
detailBody = create('div', 'mengsan-hud-detail-body-shuying', dialog);
refreshDetails();
// Unknown/native marks retain their engine intro and are never hidden by the HUD.
dialog.addEventListener('cancel', event => { event.preventDefault(); closeDialog(); });
dialog.addEventListener('close', () => { if (dialog === current) closeDialog(); });
for (const type of ['pointerdown', 'pointerup', 'click', 'touchstart', 'touchend', 'keydown']) dialog.addEventListener(type, event => event.stopPropagation());
dialog.showModal(); close.focus();
};
let statusHold = null, statusTimer = null;
const cancelStatusHold = () => {
if (statusTimer !== null) window.clearTimeout(statusTimer);
statusHold?.button.classList.remove('is-pressed');
statusHold = null; statusTimer = null;
};
const statusButton = event => {
const button = event.target.closest?.('.mengsan-hud-status-shuying');
return button && statuses.contains(button) ? button : null;
};
const statusDown = event => {
cancelStatusHold();
const button = statusButton(event);
if (!button || event.button !== 0 || disposed || frozen) return;
statusHold = { button, id: event.pointerId,
x: event.clientX, y: event.clientY };
button.classList.add('is-pressed');
statusTimer = window.setTimeout(() => {
cancelStatusHold();
if (button.isConnected) {
openDetails(player, button.dataset.statusId || null);
}
}, 500);
};
const statusMove = event => {
if (event.pointerId !== statusHold?.id) return;
if (Math.hypot(event.clientX - statusHold.x,
event.clientY - statusHold.y) > 8) cancelStatusHold();
};
const statusUp = event => {
if (event.pointerId === statusHold?.id) cancelStatusHold();
};
const statusListeners = [
[statuses, 'pointerdown', statusDown],
[statuses, 'scroll', cancelStatusHold],
[window, 'pointermove', statusMove, true],
[window, 'pointerup', statusUp, true],
[window, 'pointercancel', statusUp, true],
[window, 'blur', cancelStatusHold],
];
for (const args of statusListeners) {
args[0].addEventListener(...args.slice(1));
}
const stopStatusInput = () => {
cancelStatusHold();
for (const args of statusListeners) {
args[0].removeEventListener(...args.slice(1));
}
};
statuses.addEventListener('click', event => {
event.preventDefault();
const button = statusButton(event);
// Pointer clicks require a hold; keyboard activation stays accessible.
if (button && event.detail === 0) {
openDetails(player, button.dataset.statusId || null);
}
});
statuses.addEventListener('contextmenu', event => event.preventDefault());
let statusSignature = null;
function renderStatuses() {
const entries = collectCombatStatuses(player, battle);
const columns = layout === 'narrow' ? 1 : 2;
const columns = Math.max(1,
Math.floor(((center.clientWidth || 76) + 6) / 38));
const rows = Math.max(1, Math.floor((statuses.clientHeight || 80) / 38));
const maximum = Math.max(0, columns * rows - (entries.length > columns * rows ? 1 : 0));
const signature = JSON.stringify([maximum, entries]);
if (signature !== statusSignature) {
statusSignature = signature; statuses.replaceChildren();
statusSignature = signature;
center.hidden = entries.length === 0;
const existing = new Map([...statuses.children].map(button =>
[button.dataset.statusId || null, button]));
const wantedButtons = [];
for (const entry of entries.slice(0, maximum)) {
const button = create('button', 'mengsan-hud-status-shuying', statuses);
button.setAttribute('aria-label', `${entry.name} ${entry.value ?? ''} ${entry.unit},查看状态详情`);
if (entry.icon) {
const image = create('img', '', button); image.src = `${assetBase}/assets/status/${entry.icon}`; image.alt = '';
image.addEventListener('error', () => image.remove(), { once: true });
const button = existing.get(entry.id) ||
create('button', 'mengsan-hud-status-shuying', null);
button.dataset.statusId = entry.id;
button.setAttribute('aria-label', `${entry.name} ${entry.value ?? ''} ${entry.unit},长按查看状态详情`);
button.setAttribute('aria-haspopup', 'dialog');
if (button.dataset.icon !== (entry.icon || '')) {
button.dataset.icon = entry.icon || '';
button.replaceChildren();
button.classList.remove('mengsan-hud-status-fallback-shuying');
if (entry.icon) {
const image = create('img', '', button);
image.src = `${assetBase}/assets/status/${entry.icon}`;
image.alt = '';
image.addEventListener('error', () => {
image.remove();
button.classList.add('mengsan-hud-status-fallback-shuying');
}, { once: true });
}
}
create('span', '', button, entry.name);
if (entry.value != null) create('b', '', button, entry.value);
button.addEventListener('click', () => openDetails(player));
wantedButtons.push(button);
}
if (entries.length > maximum) {
const more = create('button', 'mengsan-hud-status-shuying', statuses, `+${entries.length - maximum}`);
more.setAttribute('aria-label', '查看全部状态'); more.addEventListener('click', () => openDetails(player));
const more = existing.get(null) || create('button',
'mengsan-hud-status-shuying mengsan-hud-status-more-shuying',
null);
more.setAttribute('aria-label', '长按查看全部状态');
more.setAttribute('aria-haspopup', 'dialog');
wantedButtons.push(more);
}
if (statusHold && !wantedButtons.includes(statusHold.button)) {
cancelStatusHold();
}
for (const button of [...statuses.children]) {
if (!wantedButtons.includes(button)) button.remove();
}
for (const [index, button] of wantedButtons.entries()) {
if (statuses.children[index] !== button) {
statuses.insertBefore(button, statuses.children[index] || null);
}
}
}
const wanted = new Set();
@@ -236,7 +340,8 @@ export function mountBattleHUD(player, battle, env) {
text(name, nameOf(player)); text(hp, `生命 ${player.hp} / ${player.maxHp}`); text(block, `格挡 ${player.hujia || 0}`);
healthFill.style.width = `${Math.max(0, Math.min(100, player.maxHp > 0 ? player.hp / player.maxHp * 100 : 0))}%`;
text(energy, `费用 ${player.storage.mengsanEnergy_shuying ?? 0} / ${player.storage.mengsanMaxEnergy_shuying ?? 0}`);
syncActors(); syncExternalEquipment(); renderStatuses(); syncControls(); syncNotifications();
syncActors(); syncExternalEquipment(); renderStatuses();
refreshDetails(); syncControls(); syncNotifications();
overlays.refresh({ footerHeight:hudHeightFor(), status:_status });
}
function schedule() {
@@ -246,6 +351,7 @@ export function mountBattleHUD(player, battle, env) {
const timer = window.setInterval(schedule, 200);
function freeze() {
if(disposed || frozen)return;
stopStatusInput();
frozen=true;window.clearInterval(timer);
if(frame!==null){window.cancelAnimationFrame(frame);frame=null;}
observer?.disconnect();resize?.disconnect();closeDialog();overlays.dispose();
@@ -253,6 +359,7 @@ export function mountBattleHUD(player, battle, env) {
}
function dispose() {
if (disposed) return; disposed = true;
stopStatusInput();
window.clearInterval(timer); if (frame !== null) window.cancelAnimationFrame(frame);
observer?.disconnect(); resize?.disconnect(); window.removeEventListener('resize', schedule);
document.removeEventListener('visibilitychange', schedule);
+10 -6
View File
@@ -19,11 +19,16 @@
.mengsan-hud-health-track-shuying>span { height:100%;background:#9bb78d; }
.mengsan-hud-energy-shuying { color:#eacb87;font-size:16px;font-variant-numeric:tabular-nums;white-space:nowrap; }
.mengsan-hud-center-shuying { left:40px;right:auto;top:auto;bottom:var(--mengsan-side-dock-bottom);width:var(--mengsan-profile-width);height:var(--mengsan-status-height); }
.mengsan-hud-shuying .mengsan-hud-statuses-shuying { display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:32px;align-content:end;gap:6px;overflow:hidden;white-space:nowrap;height:100%; }
.mengsan-hud-shuying .mengsan-hud-status-shuying { display:flex;align-items:center;gap:4px;max-width:100%;min-width:0;min-height:32px;padding:3px 6px;background:transparent;border-radius:10px; }
.mengsan-hud-status-shuying span { overflow:hidden;text-overflow:ellipsis;font-size:12px; }
.mengsan-hud-status-shuying b { font-variant-numeric:tabular-nums;font-size:13px; }
.mengsan-hud-status-shuying img { width:22px;height:22px;object-fit:contain; }
.mengsan-hud-shuying .mengsan-hud-statuses-shuying { display:grid;grid-template-columns:repeat(auto-fill,32px);grid-auto-rows:32px;align-content:end;gap:6px;overflow:hidden;white-space:nowrap;height:100%; }
.mengsan-hud-shuying .mengsan-hud-status-shuying { display:flex;align-items:center;justify-content:center;width:32px;height:32px;min-width:0;min-height:32px;padding:2px;background:transparent;border-radius:6px;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;touch-action:pan-y; }
.mengsan-hud-status-shuying img { width:26px;height:26px;object-fit:contain;pointer-events:none; }
.mengsan-hud-status-shuying.is-pressed { border-color:#dcc18e;background:#ffffff12; }
.mengsan-hud-status-more-shuying::after { content:'\2022\2022\2022';letter-spacing:1px; }
.mengsan-hud-status-fallback-shuying::after { content:'';width:12px;height:12px;border:2px solid #dcc18e;transform:rotate(45deg); }
/* Equipment Spine effects live on this body canvas (native z-index 7).
Keep them above the portrait (25) and below action controls (30).
Inline elevations used by awakening animations still take precedence. */
body.mengsan-hud-active-shuying>#decadeUI-canvas { z-index:28; }
.mengsan-hud-hand-shuying { position:absolute!important;left:var(--mengsan-hud-left)!important;right:var(--mengsan-hud-right)!important;bottom:68px!important;top:auto!important;width:calc(100% - var(--mengsan-hud-left) - var(--mengsan-hud-right))!important;height:calc(var(--mengsan-hud-height,274px) - 126px)!important;z-index:24!important;background:none!important;border:0!important;transform:none!important;pointer-events:none; }
.mengsan-hud-hand-shuying .handcards>.card { pointer-events:auto; }
.mengsan-hud-mebg-shuying,.mengsan-hud-hand-shuying>.fakeme.avatar { display:none!important; }
@@ -114,7 +119,6 @@ body.mengsan-hud-active-shuying #arena>.mengsan-hud-controls-shuying>.mengsan-hu
body.mengsan-hud-active-shuying #arena .mengsan-hud-native-skills-shuying>div { width:100%!important;min-width:0!important;box-sizing:border-box!important;padding:8px 10px!important;min-height:40px!important;font:14px/20px "Microsoft YaHei",sans-serif!important;white-space:normal!important; }
[data-mengsan-hud-layout="narrow"] .mengsan-hud-center-shuying { left:22px; }
[data-mengsan-hud-layout="short"] .mengsan-hud-shuying .mengsan-hud-statuses-shuying { height:100%; }
[data-mengsan-hud-layout="narrow"] .mengsan-hud-shuying .mengsan-hud-statuses-shuying { grid-template-columns:1fr; }
.mengsan-hud-prompt-shuying { box-sizing:border-box!important;height:auto!important;min-height:0!important;overflow:auto!important;padding:2px 6px!important;background:none!important;border:0!important;box-shadow:none!important;text-align:center!important;z-index:32!important;color:#f6edcb!important;text-shadow:0 1px 3px #000,0 0 6px #000!important; }
body.mengsan-hud-active-shuying .mengsan-hud-card-intro-shuying { box-sizing:border-box!important;z-index:90!important;height:auto!important;min-height:0!important;overflow:auto!important;border-radius:14px!important;background:#202020f5!important;box-shadow:0 8px 24px #0007!important;color:#f4f4f4!important;transition:none!important; }
body.mengsan-hud-active-shuying #window>.mengsan-hud-card-intro-shuying { z-index:90!important; }
+62 -2
View File
@@ -12,6 +12,7 @@ let sessionVerified = new Set();
let verificationContext = null;
let verificationDirty = false;
let fullVerification = false;
const progressLayers = new WeakMap();
function cacheContext() {
if (lib.node?.fs && typeof window.__dirname == "string") return `node:${window.__dirname}`;
@@ -265,12 +266,69 @@ function setBusy(shuYing, busy) {
}
function ensureProgressNode(shuYing) {
if (!shuYing.text.parentNode) {
document.body.appendChild(shuYing.text);
const node = shuYing.text;
Object.assign(node.style, {
position: "fixed", inset: "14px auto auto 50%", margin: "0",
maxWidth: "calc(100vw - 28px)", zIndex: "2147483647",
});
// Native modal dialogs outrank every z-index. A manual popover is non-modal
// and joins their top layer without intercepting game input or focus.
if (typeof node.showPopover == "function") {
if (node.parentNode !== document.body) document.body.appendChild(node);
node.setAttribute("popover", "manual");
if (node.matches(":popover-open")) node.hidePopover();
node.showPopover();
} else {
// Old WebViews can display progress inside the active modal's top
// layer without opening another modal or making the game inert.
let layer = progressLayers.get(node);
if (!layer) {
layer = { dialogs: [], observer: null };
layer.sync = (records = []) => {
const initial = layer.dialogs.length === 0;
const dialogs = [...document.querySelectorAll('dialog[open]')]
.filter(dialog => {
try { return dialog.matches(':modal'); }
catch { return true; }
});
layer.dialogs = layer.dialogs.filter(dialog =>
dialogs.includes(dialog));
for (const dialog of dialogs) {
if (!layer.dialogs.includes(dialog)) layer.dialogs.push(dialog);
}
if (initial) {
const focused = document.activeElement?.closest('dialog');
if (dialogs.includes(focused)) {
layer.dialogs = layer.dialogs.filter(dialog =>
dialog !== focused);
layer.dialogs.push(focused);
}
}
for (const record of records) {
if (record.type !== 'attributes' ||
!dialogs.includes(record.target)) continue;
layer.dialogs = layer.dialogs.filter(dialog =>
dialog !== record.target);
layer.dialogs.push(record.target);
}
const host = layer.dialogs[layer.dialogs.length - 1] ||
document.body;
if (node.parentNode !== host) host.appendChild(node);
};
layer.observer = new MutationObserver(layer.sync);
layer.observer.observe(document.body, {
childList: true, subtree: true,
attributes: true, attributeFilter: ['open'],
});
progressLayers.set(node, layer);
}
layer.sync();
}
}
function removeProgressNode(shuYing) {
progressLayers.get(shuYing.text)?.observer.disconnect();
progressLayers.delete(shuYing.text);
if (shuYing.text.parentNode) {
shuYing.text.parentNode.removeChild(shuYing.text);
}
@@ -356,6 +414,8 @@ function setProgress(shuYing, options = {}) {
const now = Date.now();
if (!options.force && now - (shuYing._progressRendered || 0) < 120) return;
shuYing._progressRendered = now;
// Re-promote after any Mengsan modal opened since the previous update.
ensureProgressNode(shuYing);
const current = Math.max(0, Number(options.current) || 0);
const total = Math.max(0, Number(options.total) || 0);