Skip to content

Commit 4978306

Browse files
committed
Improve focus handling
1 parent a8aaaad commit 4978306

File tree

1 file changed

+29
-9
lines changed

1 file changed

+29
-9
lines changed

components/dash-core-components/src/fragments/Dropdown.tsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -217,20 +217,19 @@ const Dropdown = (props: DropdownProps) => {
217217
let sortedOptions = filteredOptions;
218218
if (multi) {
219219
// Sort filtered options: selected first, then unselected
220+
// ES2019+ guarantees stable sort, preserving order within groups
220221
sortedOptions = [...filteredOptions].sort((a, b) => {
221222
const aSelected = sanitizedValues.includes(a.value);
222223
const bSelected = sanitizedValues.includes(b.value);
223-
224224
if (aSelected && !bSelected) {
225225
return -1;
226226
}
227227
if (!aSelected && bSelected) {
228228
return 1;
229229
}
230-
return 0; // Maintain original order within each group
230+
return 0;
231231
});
232232
}
233-
234233
setDisplayOptions(sortedOptions);
235234
}
236235
}, [filteredOptions, isOpen]);
@@ -280,20 +279,41 @@ const Dropdown = (props: DropdownProps) => {
280279
// Handle keyboard navigation in popover
281280
const handleKeyDown = useCallback(
282281
(e: React.KeyboardEvent) => {
283-
// Handle TAB to select first option and close dropdown
282+
// Handle TAB to select highlighted option and close dropdown
284283
if (e.key === 'Tab' && !e.shiftKey) {
285284
if (displayOptions.length > 0) {
286-
const firstOption = displayOptions[0];
287-
if (!firstOption.disabled) {
285+
// Check if an option is currently focused
286+
const focusedElement = document.activeElement;
287+
let optionToSelect = displayOptions[0];
288+
289+
if (
290+
focusedElement instanceof HTMLInputElement &&
291+
focusedElement.classList.contains(
292+
'dash-options-list-option-checkbox'
293+
)
294+
) {
295+
// Find the option matching the focused element's value
296+
const focusedValue = focusedElement.value;
297+
const focusedOption = displayOptions.find(
298+
opt => String(opt.value) === focusedValue
299+
);
300+
if (focusedOption) {
301+
optionToSelect = focusedOption;
302+
}
303+
}
304+
305+
if (!optionToSelect.disabled) {
288306
if (multi) {
289-
if (!sanitizedValues.includes(firstOption.value)) {
307+
if (
308+
!sanitizedValues.includes(optionToSelect.value)
309+
) {
290310
updateSelection([
291311
...sanitizedValues,
292-
firstOption.value,
312+
optionToSelect.value,
293313
]);
294314
}
295315
} else {
296-
updateSelection([firstOption.value]);
316+
updateSelection([optionToSelect.value]);
297317
}
298318
}
299319
}

0 commit comments

Comments
 (0)