@iasiv5/dsh-quota-watch 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/client.js +25 -6
- package/package.json +1 -1
- package/src/client.mjs +38 -6
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- **面板**:点击胶囊展开浮动详情面板(半透明毛玻璃底,宽 `min(320px, 100vw−24px)`,靠边自动翻侧,滚动 / 缩放跟随;≤480px 窄屏自动改为底部弹层并避开 safe-area),头部带标题与 ✕,Esc 或点击面板外关闭。概览列两个 provider 行,点击进入详情:GLM 为今日 Tokens / 调用次数双大数字 + 五列对齐窗口网格(MCP(月)→ 5 小时 → 每月 → 每周固定排序,周窗口未下发时显示「每周额度 · ♾️(无限)」占位)+ 分模型用量行;Copilot 为可用额度、本期已用(credits 消耗 + 弱化百分比)、重置时间三行。底部一行 **等宽双按钮**(grid 1fr 1fr,宽度与文案长度和语言无关):「显示 / 隐藏侧边栏卡片」(文案随状态翻转)与「立即刷新」(触发宿主立即探测);加载 / 出错空态下按钮同样在场。接口未下发有效 `remaining` / `percent_remaining` 时该行不渲染,`credits_used` 绝不用于推算余额。
|
|
11
11
|
- **侧边栏卡片(可选,默认隐藏)**:胶囊是默认界面;需要侧栏卡片时在面板底部按钮开启「显示侧边栏卡片」(Web profile 侧栏底部区块存在时才出现该按钮)。卡片显示时提供摘要行 + 行点击开面板 + 行首 provider 名称点击立即探测。侧栏收起(Web 窄栏)或容器过窄(<160px,跨 profile 通用判据)时卡片自动隐藏,侧栏整体不存在(Desktop 收起态)同样自然缺席、无报错——入口始终由悬浮胶囊承载。
|
|
12
12
|
- 凭据缺失的 provider 不渲染摘要行与胶囊分段(两者皆缺时悬浮胶囊与卡片一并隐藏);探测失败时保留最后一次成功数据并在行上加 ⚠ 过期标记(⚠ 只出现在面板与卡片行内,不在胶囊上显示)。
|
|
13
|
+
- **语言**:界面文案跟随 DSH「设置 → 语言」——宿主 locale 特性把解析结果实时镜像到 `<html lang>`,插件监听该属性,切换即热重渲染(无需刷新页面);宿主未同步时回退浏览器语言。面板 footer 按钮文字与行内弱化数值(如「3.8亿」)同字号(10px/14px)、同 72% 静息透明度,hover 恢复全强度。
|
|
13
14
|
- 宿主默认每 60 秒探测(窗口 + 用量 + Copilot);profile patch 可将 `pollIntervalSec` 设置在 30–3600 秒。浏览器页签可见时每 30 秒刷新视图,隐藏时暂停。
|
|
14
15
|
|
|
15
16
|
## 兼容与配置
|
|
@@ -46,4 +47,4 @@ dsh plugin --profile web add @iasiv5/dsh-quota-watch
|
|
|
46
47
|
|
|
47
48
|
## English summary
|
|
48
49
|
|
|
49
|
-
DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%. Dragging it is grab-accurate (transform-based, rAF-batched) and free to place anywhere on the screen, with position remembered across restarts and viewport boundaries automatically clamped. Touch devices drag directly (wider slop against accidental drags), a 500ms long-press opens the context menu (iOS has no right-click), the hit area is padded to 44px and placement respects `env(safe-area-inset-*)`. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click, and viewports ≤480px dock it to the bottom as a sheet. The panel footer holds two equal-width buttons — sidebar-card visibility and an immediate provider re-probe — replacing the retired right-click/long-press menu, so the capsule itself carries no hidden gestures. A sidebar card remains an opportunistic extra entry on web profiles, opted in from that footer button, and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
|
|
50
|
+
DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%. Dragging it is grab-accurate (transform-based, rAF-batched) and free to place anywhere on the screen, with position remembered across restarts and viewport boundaries automatically clamped. Touch devices drag directly (wider slop against accidental drags), a 500ms long-press opens the context menu (iOS has no right-click), the hit area is padded to 44px and placement respects `env(safe-area-inset-*)`. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click, and viewports ≤480px dock it to the bottom as a sheet. The panel footer holds two equal-width buttons — sidebar-card visibility and an immediate provider re-probe — replacing the retired right-click/long-press menu, so the capsule itself carries no hidden gestures. A sidebar card remains an opportunistic extra entry on web profiles, opted in from that footer button, and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. UI copy follows the DSH language preference (mirrored live onto `<html lang>`, re-rendering on switch without a reload) and falls back to the browser language; footer buttons rest at the same size and opacity as the muted row extras. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
|
package/lib/client.js
CHANGED
|
@@ -205,6 +205,10 @@ var COPY = {
|
|
|
205
205
|
weekUnlimited: "\u6BCF\u5468\u989D\u5EA6",
|
|
206
206
|
unlimited: "\u267E\uFE0F",
|
|
207
207
|
unlimitedNote: "\uFF08 \u65E0\u9650 \uFF09",
|
|
208
|
+
// Full-width paren pair for zh: both window-grid notes share the same
|
|
209
|
+
// glyph class so their left edges line up.
|
|
210
|
+
noteOpen: "\uFF08 ",
|
|
211
|
+
noteClose: " \uFF09",
|
|
208
212
|
windows: {
|
|
209
213
|
"5h": "5 \u5C0F\u65F6\u989D\u5EA6",
|
|
210
214
|
week: "\u6BCF\u5468\u989D\u5EA6",
|
|
@@ -237,7 +241,13 @@ var COPY = {
|
|
|
237
241
|
todayCalls: "Calls",
|
|
238
242
|
weekUnlimited: "Weekly quota",
|
|
239
243
|
unlimited: "\u267E\uFE0F",
|
|
240
|
-
unlimitedNote: "
|
|
244
|
+
unlimitedNote: "( unlimited )",
|
|
245
|
+
// ASCII parens for en — and NO leading space: the note must start with
|
|
246
|
+
// the same glyph as the reset notes or the grid's left edges diverge
|
|
247
|
+
// (0.1.15 shipped ' ( unlimited )', which broke alignment against the
|
|
248
|
+
// full-width ( Resets: … ) notes the template used to emit in en too).
|
|
249
|
+
noteOpen: "( ",
|
|
250
|
+
noteClose: " )",
|
|
241
251
|
windows: {
|
|
242
252
|
"5h": "5-hour quota",
|
|
243
253
|
week: "Weekly quota",
|
|
@@ -291,8 +301,8 @@ var FLOAT_STYLE_TEXT = `
|
|
|
291
301
|
@media print { :host { display: none !important; } }
|
|
292
302
|
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
293
303
|
.dqw-panel-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
294
|
-
.dqw-panel-action { min-width: 0; margin: 0; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); font: inherit; font-size:
|
|
295
|
-
.dqw-panel-action:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
304
|
+
.dqw-panel-action { min-width: 0; margin: 0; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); font: inherit; font-size: 10px; line-height: 14px; opacity: .72; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
305
|
+
.dqw-panel-action:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); opacity: 1; }
|
|
296
306
|
.dqw-panel { position: fixed; z-index: 2147483000; min-width: 166px; max-width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
297
307
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
298
308
|
.dqw-panel--sheet { left:8px; right:8px; top:auto; bottom:calc(8px + env(safe-area-inset-bottom, 0px)); width:auto; max-width:none; max-height:calc(100dvh - 24px); }
|
|
@@ -345,6 +355,8 @@ var FLOAT_STYLE_TEXT = `
|
|
|
345
355
|
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
346
356
|
`;
|
|
347
357
|
function localeFor(win) {
|
|
358
|
+
const host = String(win?.document?.documentElement?.lang ?? "").trim();
|
|
359
|
+
if (host !== "") return host.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
348
360
|
return String(win?.navigator?.language ?? "zh").toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
349
361
|
}
|
|
350
362
|
function formatNumber(value, locale) {
|
|
@@ -464,8 +476,8 @@ function mountQuotaCard({
|
|
|
464
476
|
};
|
|
465
477
|
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {
|
|
466
478
|
};
|
|
467
|
-
|
|
468
|
-
|
|
479
|
+
let locale = localeFor(win);
|
|
480
|
+
let copy = COPY[locale];
|
|
469
481
|
const container = doc.createElement("div");
|
|
470
482
|
container.dataset.dshQuotaWatchCard = "";
|
|
471
483
|
container.dataset.dshPlugin = "quota-watch";
|
|
@@ -841,7 +853,7 @@ function mountQuotaCard({
|
|
|
841
853
|
wins.append(text(doc, "span", "dqw-win-mid", "\xB7"));
|
|
842
854
|
wins.append(text(doc, "span", "dqw-win-used", copy.windowUsed));
|
|
843
855
|
wins.append(text(doc, "span", "dqw-win-val", percent !== void 0 ? formatPercent(percent, locale) : "\u2014"));
|
|
844
|
-
wins.append(text(doc, "span", "dqw-kv-sub", reset ?
|
|
856
|
+
wins.append(text(doc, "span", "dqw-kv-sub", reset ? `${copy.noteOpen}${copy.reset}: ${reset}${copy.noteClose}` : ""));
|
|
845
857
|
}
|
|
846
858
|
detail.append(wins);
|
|
847
859
|
if (Array.isArray(usage?.models) && usage.models.length > 0) {
|
|
@@ -1007,6 +1019,8 @@ function mountQuotaCard({
|
|
|
1007
1019
|
syncCardVisibility();
|
|
1008
1020
|
}) : null;
|
|
1009
1021
|
const renderAll = () => {
|
|
1022
|
+
locale = localeFor(win);
|
|
1023
|
+
copy = COPY[locale];
|
|
1010
1024
|
const providers = snapshot?.providers ?? [];
|
|
1011
1025
|
const glm = providers.find((provider) => provider?.key === "glm");
|
|
1012
1026
|
const copilot = providers.find((provider) => provider?.key === "copilot");
|
|
@@ -1071,6 +1085,10 @@ function mountQuotaCard({
|
|
|
1071
1085
|
schedulePlace();
|
|
1072
1086
|
});
|
|
1073
1087
|
observer.observe(doc.body, { childList: true, subtree: true });
|
|
1088
|
+
const langObserver = typeof win.MutationObserver === "function" ? new win.MutationObserver(() => {
|
|
1089
|
+
if (!disposed) renderAll();
|
|
1090
|
+
}) : null;
|
|
1091
|
+
langObserver?.observe(doc.documentElement, { attributeFilter: ["lang"] });
|
|
1074
1092
|
place();
|
|
1075
1093
|
const startPolling = () => {
|
|
1076
1094
|
if (timer !== void 0 || doc.visibilityState === "hidden" || disposed) return;
|
|
@@ -1197,6 +1215,7 @@ function mountQuotaCard({
|
|
|
1197
1215
|
disposed = true;
|
|
1198
1216
|
stopPolling();
|
|
1199
1217
|
observer.disconnect();
|
|
1218
|
+
langObserver?.disconnect();
|
|
1200
1219
|
doc.removeEventListener("visibilitychange", onVisibilityChange);
|
|
1201
1220
|
doc.removeEventListener("pointerdown", onDocPointerDown, true);
|
|
1202
1221
|
doc.removeEventListener("scroll", onDocScroll, true);
|
package/package.json
CHANGED
package/src/client.mjs
CHANGED
|
@@ -45,6 +45,10 @@ const COPY = {
|
|
|
45
45
|
weekUnlimited: '每周额度',
|
|
46
46
|
unlimited: '♾️',
|
|
47
47
|
unlimitedNote: '( 无限 )',
|
|
48
|
+
// Full-width paren pair for zh: both window-grid notes share the same
|
|
49
|
+
// glyph class so their left edges line up.
|
|
50
|
+
noteOpen: '( ',
|
|
51
|
+
noteClose: ' )',
|
|
48
52
|
windows: {
|
|
49
53
|
'5h': '5 小时额度',
|
|
50
54
|
week: '每周额度',
|
|
@@ -77,7 +81,13 @@ const COPY = {
|
|
|
77
81
|
todayCalls: 'Calls',
|
|
78
82
|
weekUnlimited: 'Weekly quota',
|
|
79
83
|
unlimited: '♾️',
|
|
80
|
-
unlimitedNote: '
|
|
84
|
+
unlimitedNote: '( unlimited )',
|
|
85
|
+
// ASCII parens for en — and NO leading space: the note must start with
|
|
86
|
+
// the same glyph as the reset notes or the grid's left edges diverge
|
|
87
|
+
// (0.1.15 shipped ' ( unlimited )', which broke alignment against the
|
|
88
|
+
// full-width ( Resets: … ) notes the template used to emit in en too).
|
|
89
|
+
noteOpen: '( ',
|
|
90
|
+
noteClose: ' )',
|
|
81
91
|
windows: {
|
|
82
92
|
'5h': '5-hour quota',
|
|
83
93
|
week: 'Weekly quota',
|
|
@@ -138,8 +148,8 @@ const FLOAT_STYLE_TEXT = `
|
|
|
138
148
|
@media print { :host { display: none !important; } }
|
|
139
149
|
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
140
150
|
.dqw-panel-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
141
|
-
.dqw-panel-action { min-width: 0; margin: 0; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); font: inherit; font-size:
|
|
142
|
-
.dqw-panel-action:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
151
|
+
.dqw-panel-action { min-width: 0; margin: 0; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); font: inherit; font-size: 10px; line-height: 14px; opacity: .72; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
152
|
+
.dqw-panel-action:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); opacity: 1; }
|
|
143
153
|
.dqw-panel { position: fixed; z-index: 2147483000; min-width: 166px; max-width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
144
154
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
145
155
|
.dqw-panel--sheet { left:8px; right:8px; top:auto; bottom:calc(8px + env(safe-area-inset-bottom, 0px)); width:auto; max-width:none; max-height:calc(100dvh - 24px); }
|
|
@@ -192,7 +202,13 @@ const FLOAT_STYLE_TEXT = `
|
|
|
192
202
|
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
193
203
|
`
|
|
194
204
|
|
|
205
|
+
/** Locale resolution follows the host, not just the browser: the DSH locale
|
|
206
|
+
* feature mirrors its resolved preference (设置 → 语言) onto
|
|
207
|
+
* `<html lang>` and keeps it updated on every switch, so prefer that signal
|
|
208
|
+
* and fall back to navigator.language when the host has not synced yet. */
|
|
195
209
|
function localeFor(win) {
|
|
210
|
+
const host = String(win?.document?.documentElement?.lang ?? '').trim()
|
|
211
|
+
if (host !== '') return host.toLowerCase().startsWith('zh') ? 'zh' : 'en'
|
|
196
212
|
return String(win?.navigator?.language ?? 'zh').toLowerCase().startsWith('zh') ? 'zh' : 'en'
|
|
197
213
|
}
|
|
198
214
|
|
|
@@ -361,8 +377,11 @@ export function mountQuotaCard({
|
|
|
361
377
|
if (!doc?.body || typeof fetchImpl !== 'function') return () => {}
|
|
362
378
|
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {}
|
|
363
379
|
|
|
364
|
-
|
|
365
|
-
|
|
380
|
+
// Mutable so a host language switch (设置 → 语言 → <html lang>) re-renders
|
|
381
|
+
// every surface with the new dictionary on the next pass — renderAll is the
|
|
382
|
+
// one funnel all render paths share.
|
|
383
|
+
let locale = localeFor(win)
|
|
384
|
+
let copy = COPY[locale]
|
|
366
385
|
const container = doc.createElement('div')
|
|
367
386
|
container.dataset.dshQuotaWatchCard = ''
|
|
368
387
|
container.dataset.dshPlugin = 'quota-watch'
|
|
@@ -796,7 +815,7 @@ export function mountQuotaCard({
|
|
|
796
815
|
wins.append(text(doc, 'span', 'dqw-win-mid', '·'))
|
|
797
816
|
wins.append(text(doc, 'span', 'dqw-win-used', copy.windowUsed))
|
|
798
817
|
wins.append(text(doc, 'span', 'dqw-win-val', percent !== undefined ? formatPercent(percent, locale) : '—'))
|
|
799
|
-
wins.append(text(doc, 'span', 'dqw-kv-sub', reset ?
|
|
818
|
+
wins.append(text(doc, 'span', 'dqw-kv-sub', reset ? `${copy.noteOpen}${copy.reset}: ${reset}${copy.noteClose}` : ''))
|
|
800
819
|
}
|
|
801
820
|
detail.append(wins)
|
|
802
821
|
if (Array.isArray(usage?.models) && usage.models.length > 0) {
|
|
@@ -990,6 +1009,8 @@ export function mountQuotaCard({
|
|
|
990
1009
|
? new win.ResizeObserver(() => { syncCardVisibility() })
|
|
991
1010
|
: null
|
|
992
1011
|
const renderAll = () => {
|
|
1012
|
+
locale = localeFor(win)
|
|
1013
|
+
copy = COPY[locale]
|
|
993
1014
|
const providers = snapshot?.providers ?? []
|
|
994
1015
|
const glm = providers.find((provider) => provider?.key === 'glm')
|
|
995
1016
|
const copilot = providers.find((provider) => provider?.key === 'copilot')
|
|
@@ -1063,6 +1084,13 @@ export function mountQuotaCard({
|
|
|
1063
1084
|
schedulePlace()
|
|
1064
1085
|
})
|
|
1065
1086
|
observer.observe(doc.body, { childList: true, subtree: true })
|
|
1087
|
+
// The DSH locale feature mirrors every language switch onto <html lang>;
|
|
1088
|
+
// re-render on that flip so the UI follows 设置 → 语言 without a reload.
|
|
1089
|
+
// A no-op flip (same value) never fires, so this costs nothing at rest.
|
|
1090
|
+
const langObserver = typeof win.MutationObserver === 'function'
|
|
1091
|
+
? new win.MutationObserver(() => { if (!disposed) renderAll() })
|
|
1092
|
+
: null
|
|
1093
|
+
langObserver?.observe(doc.documentElement, { attributeFilter: ['lang'] })
|
|
1066
1094
|
place()
|
|
1067
1095
|
|
|
1068
1096
|
const startPolling = () => {
|
|
@@ -1121,6 +1149,9 @@ export function mountQuotaCard({
|
|
|
1121
1149
|
syncPop()
|
|
1122
1150
|
}
|
|
1123
1151
|
}
|
|
1152
|
+
// Both handlers ride the card's inner body element — INSIDE the card shadow
|
|
1153
|
+
// root — so shadow-host retargeting never applies and event.target is the
|
|
1154
|
+
// real row/label in every browser.
|
|
1124
1155
|
const onBodyClick = (event) => {
|
|
1125
1156
|
if (event.target?.closest?.('[data-action="refresh"]')) {
|
|
1126
1157
|
void poll(true)
|
|
@@ -1195,6 +1226,7 @@ export function mountQuotaCard({
|
|
|
1195
1226
|
disposed = true
|
|
1196
1227
|
stopPolling()
|
|
1197
1228
|
observer.disconnect()
|
|
1229
|
+
langObserver?.disconnect()
|
|
1198
1230
|
doc.removeEventListener('visibilitychange', onVisibilityChange)
|
|
1199
1231
|
doc.removeEventListener('pointerdown', onDocPointerDown, true)
|
|
1200
1232
|
doc.removeEventListener('scroll', onDocScroll, true)
|