fix: center the update action tooltip

Remove the duplicate native title tooltip and center the single custom update hint directly below the available-update action.
This commit is contained in:
Sucukdeluxe
2026-08-21 00:21:33 +02:00
parent e09cbcad8d
commit e73143e2b7
5 changed files with 8 additions and 7 deletions
-1
View File
@@ -820,7 +820,6 @@ function _syncHeaderUpdateState() {
button.tabIndex = available ? 0 : -1; button.tabIndex = available ? 0 : -1;
button.setAttribute('aria-busy', _updateCheckBusy ? 'true' : 'false'); button.setAttribute('aria-busy', _updateCheckBusy ? 'true' : 'false');
button.setAttribute('aria-label', hint); button.setAttribute('aria-label', hint);
button.title = hint;
button.dataset.tooltip = hint; button.dataset.tooltip = hint;
_setHeaderUpdateLabel(_updateCheckBusy ? 'Prüfen…' : (available ? 'Update verfügbar' : 'Update')); _setHeaderUpdateLabel(_updateCheckBusy ? 'Prüfen…' : (available ? 'Update verfügbar' : 'Update'));
} }
+1 -1
View File
@@ -55,7 +55,7 @@
<div class="header-spacer" aria-hidden="true"></div> <div class="header-spacer" aria-hidden="true"></div>
<div class="header-cluster header-utilities"> <div class="header-cluster header-utilities">
<div class="header-update-slot" id="headerUpdateSlot" aria-hidden="true"> <div class="header-update-slot" id="headerUpdateSlot" aria-hidden="true">
<button class="header-update-button" id="headerUpdateBtn" title="Nach Aktualisierungen suchen" aria-label="Nach Aktualisierungen suchen" data-tooltip="Nach Aktualisierungen suchen" tabindex="-1" disabled> <button class="header-update-button" id="headerUpdateBtn" aria-label="Nach Aktualisierungen suchen" data-tooltip="Nach Aktualisierungen suchen" tabindex="-1" disabled>
<svg class="header-action-icon" aria-hidden="true"><use href="#icon-download"></use></svg> <svg class="header-action-icon" aria-hidden="true"><use href="#icon-download"></use></svg>
<span class="header-update-label">Update</span> <span class="header-update-label">Update</span>
</button> </button>
+4 -3
View File
@@ -2172,7 +2172,8 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
content: attr(data-tooltip); content: attr(data-tooltip);
position: absolute; position: absolute;
top: calc(100% + 10px); top: calc(100% + 10px);
right: 0; left: 50%;
right: auto;
width: max-content; width: max-content;
max-width: min(240px, calc(100vw - 24px)); max-width: min(240px, calc(100vw - 24px));
padding: 8px 10px; padding: 8px 10px;
@@ -2188,7 +2189,7 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
white-space: normal; white-space: normal;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transform: translateY(-3px); transform: translate(-50%, -3px);
transition: opacity .14s, transform .14s; transition: opacity .14s, transform .14s;
z-index: 80; z-index: 80;
} }
@@ -2196,7 +2197,7 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
.header-update-button:hover::after, .header-update-button:hover::after,
.header-update-button:focus-visible::after { .header-update-button:focus-visible::after {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translate(-50%, 0);
} }
.header-update-button:hover { .header-update-button:hover {
+1
View File
@@ -160,6 +160,7 @@ test('header occupies its final geometry before asynchronous initialization', ()
const asynchronousInitialization = appSource.lastIndexOf('\ninit().then('); const asynchronousInitialization = appSource.lastIndexOf('\ninit().then(');
assert.doesNotMatch(updateButton, /\shidden(?:\s|>)/u); assert.doesNotMatch(updateButton, /\shidden(?:\s|>)/u);
assert.doesNotMatch(updateButton, /\stitle=/u);
assert.ok(updateSlotIndex >= 0 && updateSlotIndex < speedWidgetIndex); assert.ok(updateSlotIndex >= 0 && updateSlotIndex < speedWidgetIndex);
assert.match(css, /\.header-update-slot\s*\{[^}]*width:\s*0;[^}]*flex:\s*0 0 0;[^}]*overflow:\s*hidden;/su); assert.match(css, /\.header-update-slot\s*\{[^}]*width:\s*0;[^}]*flex:\s*0 0 0;[^}]*overflow:\s*hidden;/su);
assert.match(css, /\.header-update-slot\.is-visible\s*\{[^}]*width:\s*146px;[^}]*flex-basis:\s*146px;/su); assert.match(css, /\.header-update-slot\.is-visible\s*\{[^}]*width:\s*146px;[^}]*flex-basis:\s*146px;/su);
+2 -2
View File
@@ -2450,8 +2450,8 @@ setTimeout(async () => {
const updateDialogChangelog = await wc.executeJavaScript('(() => { const title = document.querySelector(".update-release-notes-title"); const body = document.getElementById("updateReleaseNotesBody"); const titleRect = title?.getBoundingClientRect(); const bodyRect = body?.getBoundingClientRect(); return { hidden: document.getElementById("updateReleaseNotes")?.hidden, title: title?.textContent?.trim(), body: body?.textContent, gap: bodyRect && titleRect ? bodyRect.top - titleRect.bottom : null }; })()'); const updateDialogChangelog = await wc.executeJavaScript('(() => { const title = document.querySelector(".update-release-notes-title"); const body = document.getElementById("updateReleaseNotesBody"); const titleRect = title?.getBoundingClientRect(); const bodyRect = body?.getBoundingClientRect(); return { hidden: document.getElementById("updateReleaseNotes")?.hidden, title: title?.textContent?.trim(), body: body?.textContent, gap: bodyRect && titleRect ? bodyRect.top - titleRect.bottom : null }; })()');
check('Update dialog renders a compact normalized changelog', updateDialogChangelog.hidden === false && updateDialogChangelog.title === 'Changelog' && updateDialogChangelog.body === 'New in this version\\n\\nMenus and navigation\\n\\n• Added live language switching.\\n• Improved settings layout.' && updateDialogChangelog.gap <= 10); check('Update dialog renders a compact normalized changelog', updateDialogChangelog.hidden === false && updateDialogChangelog.title === 'Changelog' && updateDialogChangelog.body === 'New in this version\\n\\nMenus and navigation\\n\\n• Added live language switching.\\n• Improved settings layout.' && updateDialogChangelog.gap <= 10);
const updateHeaderHint = await wc.executeJavaScript('(() => { const button = document.getElementById("headerUpdateBtn"); return [button?.textContent?.trim(), button?.getAttribute("aria-label"), button?.dataset.tooltip].join("|"); })()'); const updateHeaderHint = await wc.executeJavaScript('(() => { const button = document.getElementById("headerUpdateBtn"); const tooltip = getComputedStyle(button, "::after"); return [button?.textContent?.trim(), button?.getAttribute("aria-label"), button?.dataset.tooltip, button?.hasAttribute("title"), tooltip.left, tooltip.right].join("|"); })()');
check('Available update gives the header action a matching hint', updateHeaderHint === 'Update verfügbar|Update v9.9.9 verfügbar. Klicken zum Installieren.|Update v9.9.9 verfügbar. Klicken zum Installieren.'); check('Available update uses one centered custom tooltip below the header action', updateHeaderHint === 'Update verfügbar|Update v9.9.9 verfügbar. Klicken zum Installieren.|Update v9.9.9 verfügbar. Klicken zum Installieren.|false|73px|auto');
const updateDialogDismissed = await wc.executeJavaScript('document.getElementById("dismissUpdateBtn")?.click(); (() => { const overlay = document.getElementById("updateBanner"); return [overlay?.style.display, overlay?.getAttribute("aria-hidden"), document.activeElement?.id, document.querySelector(".app-header")?.inert, document.querySelector(".view.active")?.inert].join("|"); })()'); const updateDialogDismissed = await wc.executeJavaScript('document.getElementById("dismissUpdateBtn")?.click(); (() => { const overlay = document.getElementById("updateBanner"); return [overlay?.style.display, overlay?.getAttribute("aria-hidden"), document.activeElement?.id, document.querySelector(".app-header")?.inert, document.querySelector(".view.active")?.inert].join("|"); })()');
check('Update dialog closes and restores focus and background', updateDialogDismissed === 'none|true|headerUpdateBtn|false|false'); check('Update dialog closes and restores focus and background', updateDialogDismissed === 'none|true|headerUpdateBtn|false|false');