Add found elements count in desktop (#10799)

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko
2026-04-24 11:55:39 +07:00
committed by GitHub
parent f188db9888
commit 69c9422d3b
2 changed files with 60 additions and 2 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ const pagesWithFoundInPageListener = new WeakSet<WebContents>()
const overlayViewsByWindowId = new Map<number, BrowserView>()
const overlayVisibleByWindowId = new Map<number, boolean>()
const OVERLAY_WIDTH = 392
const OVERLAY_WIDTH = 432
const OVERLAY_HEIGHT = 64
function resolveFindTarget (sender: WebContents): WebContents {
+59 -1
View File
@@ -20,7 +20,8 @@ const DEBOUNCE_MS = 300
/**
* Find UI is loaded in a separate BrowserView (see `findInPageOverlayHost.ts`) so
* `findInPage` on the page webContents does not search the query in this input.
* Shadow DOM keeps chrome text minimal. Match counts are not shown.
* Shadow DOM keeps chrome text minimal. Match count / current index come from
* `found-in-page` forwarded as `onFindInPageResult`.
*
* Chromium moves focus to the matched text in the **page** webContents after
* `findInPage`, so the overlay stops receiving keystrokes unless we put focus back
@@ -49,6 +50,11 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
input.spellcheck = false
input.setAttribute('aria-label', 'Find in page')
const matchCount = document.createElement('span')
matchCount.className = 'match-count'
matchCount.setAttribute('aria-live', 'polite')
matchCount.setAttribute('aria-atomic', 'true')
const nav = document.createElement('div')
nav.className = 'nav'
nav.setAttribute('role', 'group')
@@ -76,6 +82,7 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
closeBtn.title = 'Close'
root.appendChild(input)
root.appendChild(matchCount)
root.appendChild(nav)
root.appendChild(closeBtn)
shadow.appendChild(style)
@@ -89,6 +96,19 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
return input.value.trim()
}
function clearMatchCount (): void {
matchCount.textContent = ''
matchCount.removeAttribute('title')
}
function describeMatchCount (matches: number, activeOrdinal: number): { label: string, title: string } {
if (matches <= 0) {
return { label: '0/0', title: 'No matches' }
}
const cur = activeOrdinal > 0 ? Math.min(activeOrdinal, matches) : 1
return { label: `${cur}/${matches}`, title: `Match ${cur} of ${matches}` }
}
function updateNavState (): void {
const q = getQuery()
prevBtn.disabled = q === ''
@@ -114,6 +134,7 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
function hide (): void {
visible = false
host.style.display = 'none'
clearMatchCount()
prevBtn.disabled = true
nextBtn.disabled = true
try {
@@ -165,6 +186,7 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
debounceTimer = undefined
const q = getQuery()
if (q === '') {
clearMatchCount()
void electronApi.stopFindInPage('clearSelection').catch(() => {})
updateNavState()
return
@@ -226,6 +248,20 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
},
true
)
electronApi.onFindInPageResult((result) => {
if (!visible) {
return
}
const q = getQuery()
if (q === '') {
clearMatchCount()
return
}
const { label, title } = describeMatchCount(result.matches, result.activeMatchOrdinal)
matchCount.textContent = label
matchCount.title = title
})
}
/** Left-pointing chevron; `.next::after` mirrors with `scaleX(-1)` for identical vertical alignment. */
@@ -314,6 +350,28 @@ const shadowStyles = `
color: transparent;
}
.match-count {
min-width: 1.5rem;
padding: 0 2px;
font-size: 12px;
line-height: 1.35;
font-variant-numeric: tabular-nums;
text-align: right;
color: rgba(0, 0, 0, 0.52);
user-select: none;
flex-shrink: 0;
}
@media (prefers-color-scheme: dark) {
.match-count {
color: rgba(255, 255, 255, 0.48);
}
}
:host-context([data-theme='theme-dark']) .match-count {
color: rgba(255, 255, 255, 0.48);
}
.nav {
display: flex;
align-items: center;