find/omnibox: prevent page scroll while overlay visible; ensure list scrolls on arrow nav; fade overlay when navigating matches; unfade on input focus; stopPropagation on arrows
- commit
- 9de5dcabe5bef728a2f12f58f941dd1618f08c17
- author
- bamen · · 2025-09-09 18:20 UTC
- committer
- bamen · · 2025-09-09 17:50 UTC
- parent
- 87979c4
- signature
- unsigned
1 file · +51 -7
M pkg/webkit/ucm_js_cgo.go +51 -7
| @@ -14,6 +14,7 @@ func getOmniboxScript() string { | |||
| 14 | 14 | const MAX_MATCHES = 2000; | |
| 15 | 15 | const H = { | |
| 16 | 16 | el: null, | |
| 17 | + | box: null, | |
| 17 | 18 | input: null, | |
| 18 | 19 | list: null, | |
| 19 | 20 | visible: false, | |
| @@ -23,6 +24,8 @@ func getOmniboxScript() string { | |||
| 23 | 24 | selectedIndex: -1, | |
| 24 | 25 | debounceTimer: 0, | |
| 25 | 26 | highlightNodes: [], | |
| 27 | + | faded: false, | |
| 28 | + | prevOverflow: '', | |
| 26 | 29 | post(msg){ try { window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify(msg)); } catch(_){} }, | |
| 27 | 30 | render(){ | |
| 28 | 31 | if (!H.el) H.mount(); | |
| @@ -31,10 +34,47 @@ func getOmniboxScript() string { | |||
| 31 | 34 | H.input.placeholder = H.mode === 'find' ? 'Find in page…' : 'Type URL or search…'; | |
| 32 | 35 | H.input.focus(); | |
| 33 | 36 | }, | |
| 34 | - | setMode(m){ H.mode = m === 'find' ? 'find' : 'omnibox'; H.selectedIndex = -1; H.paintList(); }, | |
| 37 | + | setMode(m){ H.mode = m === 'find' ? 'find' : 'omnibox'; H.selectedIndex = -1; H.setFaded(false); H.paintList(); }, | |
| 35 | 38 | open(mode, initial){ H.setMode(mode||'omnibox'); H.toggle(true); if (typeof initial==='string') { H.input.value = initial; H.onInput(); } }, | |
| 36 | - | close(){ if (H.mode==='find') H.clearHighlights(); H.toggle(false); }, | |
| 37 | - | toggle(v){ H.visible = (typeof v==='boolean')? v : !H.visible; H.render(); }, | |
| 39 | + | close(){ if (H.mode==='find') H.clearHighlights(); H.setFaded(false); H.toggle(false); }, | |
| 40 | + | toggle(v){ | |
| 41 | + | const newState = (typeof v==='boolean')? v : !H.visible; | |
| 42 | + | if (newState && !H.visible) { | |
| 43 | + | // Disable page scrolling while overlay is visible | |
| 44 | + | H.prevOverflow = document.documentElement.style.overflow; | |
| 45 | + | document.documentElement.style.overflow = 'hidden'; | |
| 46 | + | } else if (!newState && H.visible) { | |
| 47 | + | // Restore page scrolling on close | |
| 48 | + | document.documentElement.style.overflow = H.prevOverflow || ''; | |
| 49 | + | H.prevOverflow = ''; | |
| 50 | + | } | |
| 51 | + | H.visible = newState; | |
| 52 | + | H.render(); | |
| 53 | + | }, | |
| 54 | + | setFaded(v){ | |
| 55 | + | H.faded = !!v; | |
| 56 | + | if (!H.box || !H.input || !H.list) return; | |
| 57 | + | if (H.faded) { | |
| 58 | + | H.box.style.background = 'rgba(27,27,27,0.25)'; | |
| 59 | + | H.input.style.background = 'rgba(18,18,18,0.35)'; | |
| 60 | + | H.input.style.color = '#eee'; | |
| 61 | + | } else { | |
| 62 | + | H.box.style.background = '#1b1b1b'; | |
| 63 | + | H.input.style.background = '#121212'; | |
| 64 | + | H.input.style.color = '#eee'; | |
| 65 | + | } | |
| 66 | + | }, | |
| 67 | + | scrollListToSelection(){ | |
| 68 | + | if (!H.list) return; | |
| 69 | + | let idx = H.selectedIndex|0; | |
| 70 | + | if (idx < 0) return; | |
| 71 | + | // Account for header in find mode | |
| 72 | + | const childIdx = (H.mode==='find' ? 1 : 0) + idx; | |
| 73 | + | const el = H.list.children[childIdx]; | |
| 74 | + | if (el && el.scrollIntoView) { | |
| 75 | + | try { el.scrollIntoView({block:'nearest'}); } catch(_) { el.scrollIntoView(); } | |
| 76 | + | } | |
| 77 | + | }, | |
| 38 | 78 | mount(){ | |
| 39 | 79 | const root = document.createElement('div'); | |
| 40 | 80 | root.id = 'dumber-omnibox-root'; | |
| @@ -51,9 +91,12 @@ func getOmniboxScript() string { | |||
| 51 | 91 | style.textContent = '.dumber-find-highlight{background:#ffeb3b;color:#000;padding:0 1px;border-radius:2px;box-shadow:0 0 0 1px #c8b900 inset}'; | |
| 52 | 92 | document.documentElement.appendChild(style); | |
| 53 | 93 | box.appendChild(input); box.appendChild(list); root.appendChild(box); document.documentElement.appendChild(root); | |
| 94 | + | H.box = box; | |
| 54 | 95 | // Focus and select input on hover for quick typing | |
| 55 | 96 | input.addEventListener('mouseenter', ()=>{ if (H.visible) { try { input.focus({preventScroll:true}); input.select(); } catch(_) { input.focus(); } } }); | |
| 56 | 97 | root.addEventListener('mouseenter', ()=>{ if (H.visible) { try { input.focus({preventScroll:true}); } catch(_) { input.focus(); } } }); | |
| 98 | + | // Clear fading when user goes back to input typing | |
| 99 | + | input.addEventListener('focus', ()=> H.setFaded(false)); | |
| 57 | 100 | input.addEventListener('keydown', (e)=>{ | |
| 58 | 101 | if (e.key === 'Escape'){ H.close(); } | |
| 59 | 102 | else if (H.mode==='omnibox' && e.key === 'Enter'){ | |
| @@ -74,13 +117,14 @@ func getOmniboxScript() string { | |||
| 74 | 117 | H.close(); | |
| 75 | 118 | } | |
| 76 | 119 | } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp'){ | |
| 77 | - | e.preventDefault(); | |
| 120 | + | e.preventDefault(); e.stopPropagation(); | |
| 78 | 121 | const n = (H.mode==='find'? H.matches.length : H.suggestions.length); | |
| 79 | 122 | if (n){ | |
| 80 | 123 | H.selectedIndex = (H.selectedIndex||0) + (e.key==='ArrowDown'?1:-1); | |
| 81 | 124 | if (H.selectedIndex<0) H.selectedIndex = n-1; if (H.selectedIndex>=n) H.selectedIndex = 0; | |
| 82 | 125 | H.paintList(); | |
| 83 | - | H.revealSelection(); | |
| 126 | + | H.scrollListToSelection(); | |
| 127 | + | if (H.mode==='find') { H.revealSelection(); H.setFaded(true); } | |
| 84 | 128 | } | |
| 85 | 129 | } | |
| 86 | 130 | }); | |
| @@ -105,7 +149,7 @@ func getOmniboxScript() string { | |||
| 105 | 149 | const url = document.createElement('div'); url.textContent = s.url || ''; url.style.cssText = 'flex:1;color:#9ad;word-break:break-all;'; | |
| 106 | 150 | const title = document.createElement('div'); title.textContent = s.title || ''; title.style.cssText = 'flex:1;color:#ccc;opacity:.9;'; | |
| 107 | 151 | item.appendChild(title); item.appendChild(url); | |
| 108 | - | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); }); | |
| 152 | + | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); H.scrollListToSelection(); }); | |
| 109 | 153 | item.addEventListener('click',()=>{ H.post({type:'navigate', url:s.url}); H.toggle(false); }); | |
| 110 | 154 | list.appendChild(item); | |
| 111 | 155 | }); | |
| @@ -120,7 +164,7 @@ func getOmniboxScript() string { | |||
| 120 | 164 | item.style.cssText = 'padding:8px 10px;cursor:pointer;border-bottom:1px solid #2a2a2a;'+(i===H.selectedIndex?'background:#0a0a0a;':''); | |
| 121 | 165 | const ctx = document.createElement('div'); ctx.textContent = m.context || ''; ctx.style.cssText = 'color:#ddd;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'; | |
| 122 | 166 | item.appendChild(ctx); | |
| 123 | - | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); }); | |
| 167 | + | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); H.scrollListToSelection(); H.setFaded(true); }); | |
| 124 | 168 | item.addEventListener('click',()=>{ H.selectedIndex=i; H.revealSelection(); H.paintList(); H.close(); }); | |
| 125 | 169 | list.appendChild(item); | |
| 126 | 170 | }); | |
| @@ -14,6 +14,7 @@ func getOmniboxScript() string { | |||||
| 14 | const MAX_MATCHES = 2000; | 14 | const MAX_MATCHES = 2000; | ||
| 15 | const H = { | 15 | const H = { | ||
| 16 | el: null, | 16 | el: null, | ||
| 17 | + | box: null, | |||
| 17 | input: null, | 18 | input: null, | ||
| 18 | list: null, | 19 | list: null, | ||
| 19 | visible: false, | 20 | visible: false, | ||
| @@ -23,6 +24,8 @@ func getOmniboxScript() string { | |||||
| 23 | selectedIndex: -1, | 24 | selectedIndex: -1, | ||
| 24 | debounceTimer: 0, | 25 | debounceTimer: 0, | ||
| 25 | highlightNodes: [], | 26 | highlightNodes: [], | ||
| 27 | + | faded: false, | |||
| 28 | + | prevOverflow: '', | |||
| 26 | post(msg){ try { window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify(msg)); } catch(_){} }, | 29 | post(msg){ try { window.webkit?.messageHandlers?.dumber?.postMessage(JSON.stringify(msg)); } catch(_){} }, | ||
| 27 | render(){ | 30 | render(){ | ||
| 28 | if (!H.el) H.mount(); | 31 | if (!H.el) H.mount(); | ||
| @@ -31,10 +34,47 @@ func getOmniboxScript() string { | |||||
| 31 | H.input.placeholder = H.mode === 'find' ? 'Find in page…' : 'Type URL or search…'; | 34 | H.input.placeholder = H.mode === 'find' ? 'Find in page…' : 'Type URL or search…'; | ||
| 32 | H.input.focus(); | 35 | H.input.focus(); | ||
| 33 | }, | 36 | }, | ||
| 34 | - | setMode(m){ H.mode = m === 'find' ? 'find' : 'omnibox'; H.selectedIndex = -1; H.paintList(); }, | 37 | + | setMode(m){ H.mode = m === 'find' ? 'find' : 'omnibox'; H.selectedIndex = -1; H.setFaded(false); H.paintList(); }, |
| 35 | open(mode, initial){ H.setMode(mode||'omnibox'); H.toggle(true); if (typeof initial==='string') { H.input.value = initial; H.onInput(); } }, | 38 | open(mode, initial){ H.setMode(mode||'omnibox'); H.toggle(true); if (typeof initial==='string') { H.input.value = initial; H.onInput(); } }, | ||
| 36 | - | close(){ if (H.mode==='find') H.clearHighlights(); H.toggle(false); }, | 39 | + | close(){ if (H.mode==='find') H.clearHighlights(); H.setFaded(false); H.toggle(false); }, |
| 37 | - | toggle(v){ H.visible = (typeof v==='boolean')? v : !H.visible; H.render(); }, | 40 | + | toggle(v){ |
| 41 | + | const newState = (typeof v==='boolean')? v : !H.visible; | |||
| 42 | + | if (newState && !H.visible) { | |||
| 43 | + | // Disable page scrolling while overlay is visible | |||
| 44 | + | H.prevOverflow = document.documentElement.style.overflow; | |||
| 45 | + | document.documentElement.style.overflow = 'hidden'; | |||
| 46 | + | } else if (!newState && H.visible) { | |||
| 47 | + | // Restore page scrolling on close | |||
| 48 | + | document.documentElement.style.overflow = H.prevOverflow || ''; | |||
| 49 | + | H.prevOverflow = ''; | |||
| 50 | + | } | |||
| 51 | + | H.visible = newState; | |||
| 52 | + | H.render(); | |||
| 53 | + | }, | |||
| 54 | + | setFaded(v){ | |||
| 55 | + | H.faded = !!v; | |||
| 56 | + | if (!H.box || !H.input || !H.list) return; | |||
| 57 | + | if (H.faded) { | |||
| 58 | + | H.box.style.background = 'rgba(27,27,27,0.25)'; | |||
| 59 | + | H.input.style.background = 'rgba(18,18,18,0.35)'; | |||
| 60 | + | H.input.style.color = '#eee'; | |||
| 61 | + | } else { | |||
| 62 | + | H.box.style.background = '#1b1b1b'; | |||
| 63 | + | H.input.style.background = '#121212'; | |||
| 64 | + | H.input.style.color = '#eee'; | |||
| 65 | + | } | |||
| 66 | + | }, | |||
| 67 | + | scrollListToSelection(){ | |||
| 68 | + | if (!H.list) return; | |||
| 69 | + | let idx = H.selectedIndex|0; | |||
| 70 | + | if (idx < 0) return; | |||
| 71 | + | // Account for header in find mode | |||
| 72 | + | const childIdx = (H.mode==='find' ? 1 : 0) + idx; | |||
| 73 | + | const el = H.list.children[childIdx]; | |||
| 74 | + | if (el && el.scrollIntoView) { | |||
| 75 | + | try { el.scrollIntoView({block:'nearest'}); } catch(_) { el.scrollIntoView(); } | |||
| 76 | + | } | |||
| 77 | + | }, | |||
| 38 | mount(){ | 78 | mount(){ | ||
| 39 | const root = document.createElement('div'); | 79 | const root = document.createElement('div'); | ||
| 40 | root.id = 'dumber-omnibox-root'; | 80 | root.id = 'dumber-omnibox-root'; | ||
| @@ -51,9 +91,12 @@ func getOmniboxScript() string { | |||||
| 51 | style.textContent = '.dumber-find-highlight{background:#ffeb3b;color:#000;padding:0 1px;border-radius:2px;box-shadow:0 0 0 1px #c8b900 inset}'; | 91 | style.textContent = '.dumber-find-highlight{background:#ffeb3b;color:#000;padding:0 1px;border-radius:2px;box-shadow:0 0 0 1px #c8b900 inset}'; | ||
| 52 | document.documentElement.appendChild(style); | 92 | document.documentElement.appendChild(style); | ||
| 53 | box.appendChild(input); box.appendChild(list); root.appendChild(box); document.documentElement.appendChild(root); | 93 | box.appendChild(input); box.appendChild(list); root.appendChild(box); document.documentElement.appendChild(root); | ||
| 94 | + | H.box = box; | |||
| 54 | // Focus and select input on hover for quick typing | 95 | // Focus and select input on hover for quick typing | ||
| 55 | input.addEventListener('mouseenter', ()=>{ if (H.visible) { try { input.focus({preventScroll:true}); input.select(); } catch(_) { input.focus(); } } }); | 96 | input.addEventListener('mouseenter', ()=>{ if (H.visible) { try { input.focus({preventScroll:true}); input.select(); } catch(_) { input.focus(); } } }); | ||
| 56 | root.addEventListener('mouseenter', ()=>{ if (H.visible) { try { input.focus({preventScroll:true}); } catch(_) { input.focus(); } } }); | 97 | root.addEventListener('mouseenter', ()=>{ if (H.visible) { try { input.focus({preventScroll:true}); } catch(_) { input.focus(); } } }); | ||
| 98 | + | // Clear fading when user goes back to input typing | |||
| 99 | + | input.addEventListener('focus', ()=> H.setFaded(false)); | |||
| 57 | input.addEventListener('keydown', (e)=>{ | 100 | input.addEventListener('keydown', (e)=>{ | ||
| 58 | if (e.key === 'Escape'){ H.close(); } | 101 | if (e.key === 'Escape'){ H.close(); } | ||
| 59 | else if (H.mode==='omnibox' && e.key === 'Enter'){ | 102 | else if (H.mode==='omnibox' && e.key === 'Enter'){ | ||
| @@ -74,13 +117,14 @@ func getOmniboxScript() string { | |||||
| 74 | H.close(); | 117 | H.close(); | ||
| 75 | } | 118 | } | ||
| 76 | } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp'){ | 119 | } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp'){ | ||
| 77 | - | e.preventDefault(); | 120 | + | e.preventDefault(); e.stopPropagation(); |
| 78 | const n = (H.mode==='find'? H.matches.length : H.suggestions.length); | 121 | const n = (H.mode==='find'? H.matches.length : H.suggestions.length); | ||
| 79 | if (n){ | 122 | if (n){ | ||
| 80 | H.selectedIndex = (H.selectedIndex||0) + (e.key==='ArrowDown'?1:-1); | 123 | H.selectedIndex = (H.selectedIndex||0) + (e.key==='ArrowDown'?1:-1); | ||
| 81 | if (H.selectedIndex<0) H.selectedIndex = n-1; if (H.selectedIndex>=n) H.selectedIndex = 0; | 124 | if (H.selectedIndex<0) H.selectedIndex = n-1; if (H.selectedIndex>=n) H.selectedIndex = 0; | ||
| 82 | H.paintList(); | 125 | H.paintList(); | ||
| 83 | - | H.revealSelection(); | 126 | + | H.scrollListToSelection(); |
| 127 | + | if (H.mode==='find') { H.revealSelection(); H.setFaded(true); } | |||
| 84 | } | 128 | } | ||
| 85 | } | 129 | } | ||
| 86 | }); | 130 | }); | ||
| @@ -105,7 +149,7 @@ func getOmniboxScript() string { | |||||
| 105 | const url = document.createElement('div'); url.textContent = s.url || ''; url.style.cssText = 'flex:1;color:#9ad;word-break:break-all;'; | 149 | const url = document.createElement('div'); url.textContent = s.url || ''; url.style.cssText = 'flex:1;color:#9ad;word-break:break-all;'; | ||
| 106 | const title = document.createElement('div'); title.textContent = s.title || ''; title.style.cssText = 'flex:1;color:#ccc;opacity:.9;'; | 150 | const title = document.createElement('div'); title.textContent = s.title || ''; title.style.cssText = 'flex:1;color:#ccc;opacity:.9;'; | ||
| 107 | item.appendChild(title); item.appendChild(url); | 151 | item.appendChild(title); item.appendChild(url); | ||
| 108 | - | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); }); | 152 | + | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); H.scrollListToSelection(); }); |
| 109 | item.addEventListener('click',()=>{ H.post({type:'navigate', url:s.url}); H.toggle(false); }); | 153 | item.addEventListener('click',()=>{ H.post({type:'navigate', url:s.url}); H.toggle(false); }); | ||
| 110 | list.appendChild(item); | 154 | list.appendChild(item); | ||
| 111 | }); | 155 | }); | ||
| @@ -120,7 +164,7 @@ func getOmniboxScript() string { | |||||
| 120 | item.style.cssText = 'padding:8px 10px;cursor:pointer;border-bottom:1px solid #2a2a2a;'+(i===H.selectedIndex?'background:#0a0a0a;':''); | 164 | item.style.cssText = 'padding:8px 10px;cursor:pointer;border-bottom:1px solid #2a2a2a;'+(i===H.selectedIndex?'background:#0a0a0a;':''); | ||
| 121 | const ctx = document.createElement('div'); ctx.textContent = m.context || ''; ctx.style.cssText = 'color:#ddd;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'; | 165 | const ctx = document.createElement('div'); ctx.textContent = m.context || ''; ctx.style.cssText = 'color:#ddd;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'; | ||
| 122 | item.appendChild(ctx); | 166 | item.appendChild(ctx); | ||
| 123 | - | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); }); | 167 | + | item.addEventListener('mouseenter', ()=>{ H.selectedIndex = i; H.paintList(); H.scrollListToSelection(); H.setFaded(true); }); |
| 124 | item.addEventListener('click',()=>{ H.selectedIndex=i; H.revealSelection(); H.paintList(); H.close(); }); | 168 | item.addEventListener('click',()=>{ H.selectedIndex=i; H.revealSelection(); H.paintList(); H.close(); }); | ||
| 125 | list.appendChild(item); | 169 | list.appendChild(item); | ||
| 126 | }); | 170 | }); | ||