From 156c7fcc089385479c94e41272659350f178057c Mon Sep 17 00:00:00 2001 From: librelad Date: Fri, 21 Aug 2026 00:01:18 +0100 Subject: [PATCH] fix(webui): stop the styled tooltip doubling with the browser's own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four rules rendered their bubble with `content: attr(title)` while leaving the title attribute in place, so the browser drew its native tooltip over the styled one — two bubbles, offset, one of them unthemed. That is the overlap. setup-wizard.css already had it right with attr(data-tip). Everything else now matches: the text moved to data-tip, which the browser does not render, and aria-label carries it for assistive tech. 24 emitters migrated across 9 files, including two that assign the text at runtime — those set both attributes so neither goes stale. The same sweep found the bubble was defined twice globally. core/forms/css had the plain one; port-manager.css carried a nicer bordered, wrapping version commented "Enhanced tooltips for main config help icons" — which never applied to them, because forms.css loads afterwards at equal specificity and won. So every help icon outside the port manager had been getting the plain bubble, with `white-space: nowrap` and no max-width: a sentence-length tooltip rendered as one unbroken line running past its own background. The wrapping version is now the one in forms.css, the shadowed copy is gone, and the .port-manager-scoped rule stays. Verified on Bookstack's config page: 26 help icons, none carrying a title, all carrying data-tip, and hovering one flips ::after content from `none` to the text with a single themed bubble on screen. No port-descriptor tooltip, deliberately. PORT_N fields are replaced at runtime by the port manager, whose hidePortFieldLabels() sets the field's help icon to display:none — anything put there is unreachable, confirmed in the DOM. The format is already documented column by column by the port manager's own 13 help icons, several in more detail than a single descriptor string could manage. --- .../admin/config/js/config-renderer.js | 2 +- .../admin/config/js/toggle-manager.js | 4 +-- .../components/apps/core/js/config-form.js | 2 +- .../migrate/html/migrate-content.html | 2 +- .../apps/port-manager/css/port-manager.css | 28 ++++----------- .../apps/port-manager/js/port-manager.js | 26 +++++++------- .../js/backup-retention-presets.js | 14 ++++++-- .../backup/core/html/backup-content.html | 2 +- .../locations/js/backup-location-fields.js | 2 +- .../frontend/core/config/js/field-factory.js | 10 +++--- .../frontend/core/forms/css/forms.css | 35 ++++++++++++------- .../frontend/core/theme/css/base.css | 2 +- 12 files changed, 66 insertions(+), 63 deletions(-) diff --git a/containers/libreportal/frontend/components/admin/config/js/config-renderer.js b/containers/libreportal/frontend/components/admin/config/js/config-renderer.js index 69c689f..cad140f 100755 --- a/containers/libreportal/frontend/components/admin/config/js/config-renderer.js +++ b/containers/libreportal/frontend/components/admin/config/js/config-renderer.js @@ -33,7 +33,7 @@ class ConfigRenderer { ${masterKey.title || 'Enable Advanced Configuration'} - ℹ️ + ℹ️ diff --git a/containers/libreportal/frontend/components/admin/config/js/toggle-manager.js b/containers/libreportal/frontend/components/admin/config/js/toggle-manager.js index 095d7fc..335a7ea 100755 --- a/containers/libreportal/frontend/components/admin/config/js/toggle-manager.js +++ b/containers/libreportal/frontend/components/admin/config/js/toggle-manager.js @@ -170,7 +170,7 @@ class ToggleManager { html += `
- +
- +
- +
- + @@ -340,43 +340,43 @@ class PortManager {
- +
- +
- +
- +
- +
- +
- +
diff --git a/containers/libreportal/frontend/components/backup/configuration/js/backup-retention-presets.js b/containers/libreportal/frontend/components/backup/configuration/js/backup-retention-presets.js index 03bad93..a0b12d2 100644 --- a/containers/libreportal/frontend/components/backup/configuration/js/backup-retention-presets.js +++ b/containers/libreportal/frontend/components/backup/configuration/js/backup-retention-presets.js @@ -50,7 +50,7 @@ Object.assign(BackupPage.prototype, { block.className = 'backup-retention-preset-block'; block.innerHTML = ` `; @@ -75,7 +75,11 @@ Object.assign(BackupPage.prototype, { const tooltipEl = block.querySelector('[data-retention-tooltip]'); select.addEventListener('change', () => { const chosen = select.value; - if (tooltipEl) tooltipEl.title = BACKUP_RETENTION_PRESET_META[chosen]?.hint || ''; + if (tooltipEl) { + const hint = BACKUP_RETENTION_PRESET_META[chosen]?.hint || ''; + tooltipEl.dataset.tip = hint; + tooltipEl.setAttribute('aria-label', hint); + } applyVisibility(chosen); if (chosen === 'custom') return; const p = BACKUP_RETENTION_PRESETS[chosen]; @@ -106,7 +110,11 @@ Object.assign(BackupPage.prototype, { const allowInherit = block.dataset.retentionAllowInherit === '1'; const preset = selectEl.value; const tooltipEl = block.querySelector('[data-retention-tooltip]'); - if (tooltipEl) tooltipEl.title = BACKUP_RETENTION_PRESET_META[preset]?.hint || ''; + if (tooltipEl) { + const hint = BACKUP_RETENTION_PRESET_META[preset]?.hint || ''; + tooltipEl.dataset.tip = hint; + tooltipEl.setAttribute('aria-label', hint); + } if (preset === 'custom') { if (advanced) advanced.hidden = false; diff --git a/containers/libreportal/frontend/components/backup/core/html/backup-content.html b/containers/libreportal/frontend/components/backup/core/html/backup-content.html index 31eec4d..39a5ea6 100644 --- a/containers/libreportal/frontend/components/backup/core/html/backup-content.html +++ b/containers/libreportal/frontend/components/backup/core/html/backup-content.html @@ -27,7 +27,7 @@
-

Backup status ℹ️

+

Backup status ℹ️