Skip to content

Commit 8b6213a

Browse files
committed
[FIX] website: Restore arrow key navigation in search bar
Issue: Arrow key navigation for search result items regressed with the new search bar layout. The previous logic in onKeydown was no longer triggered due to the updated DOM structure. Fix: Introduce a dedicated onSearchResultKeydown handler and wire it to search result items. The new method restores ArrowUp and ArrowDown focus management while keeping the existing input keydown behavior unchanged. task-5424392
1 parent ee7dcef commit 8b6213a

File tree

2 files changed

+135
-8
lines changed

2 files changed

+135
-8
lines changed

addons/website/static/src/scss/website.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,6 +2357,10 @@ $ribbon-padding: 100px;
23572357
color: inherit;
23582358
}
23592359
}
2360+
2361+
&:focus {
2362+
background: var(--tertiary-bg) !important;
2363+
}
23602364
}
23612365

23622366
ul.o_checklist > li.o_checked::after {

addons/website/static/src/snippets/s_searchbar/search_bar.js

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export class SearchBar extends Interaction {
2323
"t-on-keydown": this.onKeydown,
2424
"t-on-search": this.onSearch,
2525
},
26+
".o_search_result_item": {
27+
"t-on-keydown": this.onSearchResultKeydown,
28+
},
2629
};
2730
autocompleteMinWidth = 300;
2831

@@ -197,17 +200,13 @@ export class SearchBar extends Interaction {
197200
case "Escape":
198201
this.render();
199202
break;
200-
case "ArrowUp":
201203
case "ArrowDown":
202204
ev.preventDefault();
203205
if (this.menuEl) {
204-
const focusableEls = [this.inputEl, ...this.menuEl.children];
205-
const focusedEl = document.activeElement;
206-
const currentIndex = focusableEls.indexOf(focusedEl) || 0;
207-
const delta = ev.key === "ArrowUp" ? focusableEls.length - 1 : 1;
208-
const nextIndex = (currentIndex + delta) % focusableEls.length;
209-
const nextFocusedEl = focusableEls[nextIndex];
210-
nextFocusedEl.focus();
206+
const firstResult = this.menuEl.querySelector(".o_search_result_item");
207+
if (firstResult) {
208+
firstResult.focus();
209+
}
211210
}
212211
break;
213212
case "Enter":
@@ -216,6 +215,130 @@ export class SearchBar extends Interaction {
216215
}
217216
}
218217

218+
/**
219+
* Handle keyboard navigation within search results
220+
* @param {KeyboardEvent} ev
221+
*/
222+
onSearchResultKeydown(ev) {
223+
const allResults = [...this.menuEl.querySelectorAll(".o_search_result_item")];
224+
const currentResult = ev.currentTarget;
225+
const currentIndex = allResults.indexOf(currentResult);
226+
227+
switch (ev.key) {
228+
case "Escape":
229+
this.render();
230+
break;
231+
case "ArrowUp": {
232+
ev.preventDefault();
233+
// Check if current element is in the first row
234+
const currentRect = currentResult.getBoundingClientRect();
235+
const isFirstRow = allResults.every((el, idx) => {
236+
if (idx === currentIndex) {
237+
return true;
238+
}
239+
const rect = el.getBoundingClientRect();
240+
return rect.top >= currentRect.top - 5; // Allow small tolerance
241+
});
242+
243+
if (isFirstRow) {
244+
// Focus back to input when in first row
245+
this.inputEl.focus();
246+
} else {
247+
this.navigateByDirection(currentIndex, allResults, "up");
248+
}
249+
break;
250+
}
251+
case "ArrowDown":
252+
ev.preventDefault();
253+
this.navigateByDirection(currentIndex, allResults, "down");
254+
break;
255+
case "ArrowLeft":
256+
ev.preventDefault();
257+
this.navigateByDirection(currentIndex, allResults, "left");
258+
break;
259+
case "ArrowRight":
260+
ev.preventDefault();
261+
this.navigateByDirection(currentIndex, allResults, "right");
262+
break;
263+
}
264+
}
265+
266+
/**
267+
* Navigate through search results based on their visual position
268+
* @param {number} currentIndex
269+
* @param {Array} allResults
270+
* @param {string} direction - "up", "down", "left", "right"
271+
*/
272+
navigateByDirection(currentIndex, allResults, direction) {
273+
const currentRect = allResults[currentIndex].getBoundingClientRect();
274+
const currentCenterX = currentRect.left + currentRect.width / 2;
275+
const currentCenterY = currentRect.top + currentRect.height / 2;
276+
277+
let nextIndex = -1;
278+
let bestDistance = Infinity;
279+
280+
allResults.forEach((el, index) => {
281+
if (index === currentIndex) {
282+
return;
283+
}
284+
285+
const rect = el.getBoundingClientRect();
286+
const centerX = rect.left + rect.width / 2;
287+
const centerY = rect.top + rect.height / 2;
288+
289+
let isInDirection = false;
290+
let distance = 0;
291+
292+
switch (direction) {
293+
case "down":
294+
if (centerY > currentCenterY) {
295+
isInDirection = true;
296+
distance = Math.sqrt(
297+
Math.pow(centerY - currentCenterY, 2) +
298+
Math.pow(Math.abs(centerX - currentCenterX) / 2, 2)
299+
);
300+
}
301+
break;
302+
case "up":
303+
if (centerY < currentCenterY) {
304+
isInDirection = true;
305+
distance = Math.sqrt(
306+
Math.pow(currentCenterY - centerY, 2) +
307+
Math.pow(Math.abs(centerX - currentCenterX) / 2, 2)
308+
);
309+
}
310+
break;
311+
case "right":
312+
if (centerX > currentCenterX) {
313+
isInDirection = true;
314+
distance = Math.sqrt(
315+
Math.pow(centerX - currentCenterX, 2) +
316+
Math.pow(Math.abs(centerY - currentCenterY) / 2, 2)
317+
);
318+
}
319+
break;
320+
case "left":
321+
if (centerX < currentCenterX) {
322+
isInDirection = true;
323+
distance = Math.sqrt(
324+
Math.pow(currentCenterX - centerX, 2) +
325+
Math.pow(Math.abs(centerY - currentCenterY) / 2, 2)
326+
);
327+
}
328+
break;
329+
}
330+
331+
if (isInDirection && distance < bestDistance) {
332+
bestDistance = distance;
333+
nextIndex = index;
334+
}
335+
});
336+
337+
if (nextIndex >= 0) {
338+
allResults[nextIndex].focus();
339+
}
340+
}
341+
219342
/**
220343
* @param {MouseEvent} ev
221344
*/

0 commit comments

Comments
 (0)