Skip to content

Commit fb0fbd2

Browse files
authored
Merge 4132c4c into f43c1bc
2 parents f43c1bc + 4132c4c commit fb0fbd2

File tree

3 files changed

+43
-2
lines changed

3 files changed

+43
-2
lines changed

core/pfe-core/controllers/at-focus-controller.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ export abstract class ATFocusController<Item extends HTMLElement> {
4949

5050
set atFocusedItemIndex(index: number) {
5151
const previousIndex = this.#atFocusedItemIndex;
52-
const direction = index > previousIndex ? 1 : -1;
5352
const { items, atFocusableItems } = this;
53+
// - Home (index=0): always search forward to find first focusable item
54+
// - End (index=last): always search backward to find last focusable item
55+
// - Other cases: use comparison to determine direction
56+
const direction = index === 0 ? 1
57+
: index >= items.length - 1 ? -1
58+
: index > previousIndex ? 1 : -1;
5459
const itemsIndexOfLastATFocusableItem = items.indexOf(this.atFocusableItems.at(-1)!);
5560
let itemToGainFocus = items.at(index);
5661
let itemToGainFocusIsFocusable = atFocusableItems.includes(itemToGainFocus!);

core/pfe-core/controllers/combobox-controller.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ export class ComboboxController<
242242
#button: HTMLElement | null = null;
243243
#listbox: HTMLElement | null = null;
244244
#buttonInitialRole: string | null = null;
245+
#buttonHasMouseDown = false;
245246
#mo = new MutationObserver(() => this.#initItems());
246247
#microcopy = new Map<string, Record<Lang, string>>(Object.entries({
247248
dimmed: {
@@ -425,6 +426,8 @@ export class ComboboxController<
425426
#initButton() {
426427
this.#button?.removeEventListener('click', this.#onClickButton);
427428
this.#button?.removeEventListener('keydown', this.#onKeydownButton);
429+
this.#button?.removeEventListener('mousedown', this.#onMousedownButton);
430+
this.#button?.removeEventListener('mouseup', this.#onMouseupButton);
428431
this.#button = this.options.getToggleButton();
429432
if (!this.#button) {
430433
throw new Error('ComboboxController getToggleButton() option must return an element');
@@ -434,6 +437,8 @@ export class ComboboxController<
434437
this.#button.setAttribute('aria-controls', this.#listbox?.id ?? '');
435438
this.#button.addEventListener('click', this.#onClickButton);
436439
this.#button.addEventListener('keydown', this.#onKeydownButton);
440+
this.#button.addEventListener('mousedown', this.#onMousedownButton);
441+
this.#button.addEventListener('mouseup', this.#onMouseupButton);
437442
}
438443

439444
#initInput() {
@@ -580,6 +585,17 @@ export class ComboboxController<
580585
}
581586
};
582587

588+
/**
589+
* Distinguish click-to-toggle vs Tab/Shift+Tab
590+
*/
591+
#onMousedownButton = () => {
592+
this.#buttonHasMouseDown = true;
593+
};
594+
595+
#onMouseupButton = () => {
596+
this.#buttonHasMouseDown = false;
597+
};
598+
583599
#onClickListbox = (event: MouseEvent) => {
584600
if (!this.multi && event.composedPath().some(this.options.isItem)) {
585601
this.#hide();
@@ -735,9 +751,14 @@ export class ComboboxController<
735751
#onFocusoutListbox = (event: FocusEvent) => {
736752
if (!this.#hasTextInput && this.options.isExpanded()) {
737753
const root = this.#element?.getRootNode();
754+
// Check if focus moved to the toggle button via mouse click
755+
// If so, let the click handler manage toggle (prevents double-toggle)
756+
// But if focus moved via Shift+Tab (no mousedown), we should still hide
757+
const isClickOnToggleButton =
758+
event.relatedTarget === this.#button && this.#buttonHasMouseDown;
738759
if ((root instanceof ShadowRoot || root instanceof Document)
739760
&& !this.items.includes(event.relatedTarget as Item)
740-
) {
761+
&& !isClickOnToggleButton) {
741762
this.#hide();
742763
}
743764
}

core/pfe-core/controllers/roving-tabindex-controller.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ export class RovingTabindexController<
7777
if (container instanceof HTMLElement) {
7878
container.addEventListener('focusin', () =>
7979
this.#gainedInitialFocus = true, { once: true });
80+
// Sync atFocusedItemIndex when an item receives DOM focus (e.g., via mouse click)
81+
// This ensures keyboard navigation starts from the correct position
82+
container.addEventListener('focusin', (event: FocusEvent) => {
83+
const target = event.target as Item;
84+
const index = this.items.indexOf(target);
85+
// Only update if the target is a valid item and index differs
86+
if (index >= 0 && index !== this.atFocusedItemIndex) {
87+
// Update index via setter, but avoid the focus() call by temporarily
88+
// clearing #gainedInitialFocus to prevent redundant focus
89+
const hadInitialFocus = this.#gainedInitialFocus;
90+
this.#gainedInitialFocus = false;
91+
this.atFocusedItemIndex = index;
92+
this.#gainedInitialFocus = hadInitialFocus;
93+
}
94+
});
8095
} else {
8196
this.#logger.warn('RovingTabindexController requires a getItemsContainer function');
8297
}

0 commit comments

Comments
 (0)