Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion core/pfe-core/controllers/at-focus-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ export abstract class ATFocusController<Item extends HTMLElement> {

set atFocusedItemIndex(index: number) {
const previousIndex = this.#atFocusedItemIndex;
const direction = index > previousIndex ? 1 : -1;
const { items, atFocusableItems } = this;
// - Home (index=0): always search forward to find first focusable item
// - End (index=last): always search backward to find last focusable item
// - Other cases: use comparison to determine direction
const direction = index === 0 ? 1
: index >= items.length - 1 ? -1
: index > previousIndex ? 1 : -1;
const itemsIndexOfLastATFocusableItem = items.indexOf(this.atFocusableItems.at(-1)!);
let itemToGainFocus = items.at(index);
let itemToGainFocusIsFocusable = atFocusableItems.includes(itemToGainFocus!);
Expand Down
5 changes: 4 additions & 1 deletion core/pfe-core/controllers/combobox-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,9 +735,12 @@ export class ComboboxController<
#onFocusoutListbox = (event: FocusEvent) => {
if (!this.#hasTextInput && this.options.isExpanded()) {
const root = this.#element?.getRootNode();
// Check if focus moved to the toggle button
// If so, let the click handler manage toggle
const isToggleButton = event.relatedTarget === this.#button;
if ((root instanceof ShadowRoot || root instanceof Document)
&& !this.items.includes(event.relatedTarget as Item)
) {
&& !isToggleButton) {
this.#hide();
}
}
Expand Down
15 changes: 15 additions & 0 deletions core/pfe-core/controllers/roving-tabindex-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ export class RovingTabindexController<
if (container instanceof HTMLElement) {
container.addEventListener('focusin', () =>
this.#gainedInitialFocus = true, { once: true });
// Sync atFocusedItemIndex when an item receives DOM focus (e.g., via mouse click)
// This ensures keyboard navigation starts from the correct position
container.addEventListener('focusin', (event: FocusEvent) => {
const target = event.target as Item;
const index = this.items.indexOf(target);
// Only update if the target is a valid item and index differs
if (index >= 0 && index !== this.atFocusedItemIndex) {
// Update index via setter, but avoid the focus() call by temporarily
// clearing #gainedInitialFocus to prevent redundant focus
const hadInitialFocus = this.#gainedInitialFocus;
this.#gainedInitialFocus = false;
this.atFocusedItemIndex = index;
this.#gainedInitialFocus = hadInitialFocus;
}
});
} else {
this.#logger.warn('RovingTabindexController requires a getItemsContainer function');
}
Expand Down
Loading