Skip to content

Commit

Permalink
fix(useListNavigation): correct scrollIntoView and focus behavior…
Browse files Browse the repository at this point in the history
… with virtual focus and inner focused element (#2911)
  • Loading branch information
atomiks committed May 19, 2024
1 parent d7f76f6 commit 197f1fd
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/proud-suits-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@floating-ui/react": patch
---

fix(useListNavigation): correct `scrollIntoView` and `focus` behavior with virtual focus and inner DOM-focused element + `FloatingList`
64 changes: 38 additions & 26 deletions packages/react/src/hooks/useListNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,35 +319,47 @@ export function useListNavigation(
indexRef: React.MutableRefObject<number>,
forceScrollIntoView = false,
) => {
const item = listRef.current[indexRef.current];
function runFocus(item: HTMLElement) {
if (virtual) {
setActiveId(item.id);
tree?.events.emit('virtualfocus', item);
if (virtualItemRef) {
virtualItemRef.current = item;
}
} else {
enqueueFocus(item, {
preventScroll: true,
// Mac Safari does not move the virtual cursor unless the focus call
// is sync. However, for the very first focus call, we need to wait
// for the position to be ready in order to prevent unwanted
// scrolling. This means the virtual cursor will not move to the first
// item when first opening the floating element, but will on
// subsequent calls. `preventScroll` is supported in modern Safari,
// so we can use that instead.
// iOS Safari must be async or the first item will not be focused.
sync:
isMac() && isSafari()
? isPreventScrollSupported || forceSyncFocus.current
: false,
});
}
}

if (!item) return;
const initialItem = listRef.current[indexRef.current];

if (virtual) {
setActiveId(item.id);
tree?.events.emit('virtualfocus', item);
if (virtualItemRef) {
virtualItemRef.current = item;
}
} else {
enqueueFocus(item, {
preventScroll: true,
// Mac Safari does not move the virtual cursor unless the focus call
// is sync. However, for the very first focus call, we need to wait
// for the position to be ready in order to prevent unwanted
// scrolling. This means the virtual cursor will not move to the first
// item when first opening the floating element, but will on
// subsequent calls. `preventScroll` is supported in modern Safari,
// so we can use that instead.
// iOS Safari must be async or the first item will not be focused.
sync:
isMac() && isSafari()
? isPreventScrollSupported || forceSyncFocus.current
: false,
});
if (initialItem) {
runFocus(initialItem);
}

requestAnimationFrame(() => {
const waitedItem = listRef.current[indexRef.current] || initialItem;

if (!waitedItem) return;

if (!initialItem) {
runFocus(waitedItem);
}

const scrollIntoViewOptions = scrollItemIntoViewRef.current;
const shouldScrollIntoView =
scrollIntoViewOptions &&
Expand All @@ -357,7 +369,7 @@ export function useListNavigation(
if (shouldScrollIntoView) {
// JSDOM doesn't support `.scrollIntoView()` but it's widely supported
// by all browsers.
item.scrollIntoView?.(
waitedItem.scrollIntoView?.(
typeof scrollIntoViewOptions === 'boolean'
? {block: 'nearest', inline: 'nearest'}
: scrollIntoViewOptions,
Expand Down Expand Up @@ -919,7 +931,7 @@ export function useListNavigation(
}
},
onFocus() {
if (open) {
if (open && !virtual) {
onNavigate(null);
}
},
Expand Down

0 comments on commit 197f1fd

Please sign in to comment.