prevent scrolling when refocusing items

Fixes #161
This commit is contained in:
Robin Malfait
2020-11-30 13:12:41 +01:00
parent 75527fb1b1
commit 78ebd3eb23
6 changed files with 39 additions and 38 deletions
@@ -181,7 +181,7 @@ export function Listbox<
if (!optionsRef.current?.contains(target)) dispatch({ type: ActionTypes.CloseListbox })
if (active !== document.body && active?.contains(target)) return // Keep focus on newly clicked/focused element
if (!event.defaultPrevented) buttonRef.current?.focus()
if (!event.defaultPrevented) buttonRef.current?.focus({ preventScroll: true })
}
window.addEventListener('click', handler)
@@ -237,7 +237,7 @@ const Button = forwardRefWithAs(function Button<
event.preventDefault()
dispatch({ type: ActionTypes.OpenListbox })
d.nextFrame(() => {
state.optionsRef.current?.focus()
state.optionsRef.current?.focus({ preventScroll: true })
if (!state.propsRef.current.value)
dispatch({ type: ActionTypes.GoToOption, focus: Focus.First })
})
@@ -247,7 +247,7 @@ const Button = forwardRefWithAs(function Button<
event.preventDefault()
dispatch({ type: ActionTypes.OpenListbox })
d.nextFrame(() => {
state.optionsRef.current?.focus()
state.optionsRef.current?.focus({ preventScroll: true })
if (!state.propsRef.current.value)
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Last })
})
@@ -262,11 +262,11 @@ const Button = forwardRefWithAs(function Button<
if (props.disabled) return
if (state.listboxState === ListboxStates.Open) {
dispatch({ type: ActionTypes.CloseListbox })
d.nextFrame(() => state.buttonRef.current?.focus())
d.nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
} else {
event.preventDefault()
dispatch({ type: ActionTypes.OpenListbox })
d.nextFrame(() => state.optionsRef.current?.focus())
d.nextFrame(() => state.optionsRef.current?.focus({ preventScroll: true }))
}
},
[dispatch, d, state, props.disabled]
@@ -309,9 +309,10 @@ function Label<TTag extends React.ElementType = typeof DEFAULT_LABEL_TAG>(
const [state] = useListboxContext([Listbox.name, Label.name].join('.'))
const id = `headlessui-listbox-label-${useId()}`
const handlePointerUp = React.useCallback(() => state.buttonRef.current?.focus(), [
state.buttonRef,
])
const handlePointerUp = React.useCallback(
() => state.buttonRef.current?.focus({ preventScroll: true }),
[state.buttonRef]
)
const propsBag = React.useMemo<OptionsRenderPropArg>(
() => ({ open: state.listboxState === ListboxStates.Open }),
@@ -370,7 +371,7 @@ const Options = forwardRefWithAs(function Options<
const { dataRef } = state.options[state.activeOptionIndex]
state.propsRef.current.onChange(dataRef.current.value)
}
disposables().nextFrame(() => state.buttonRef.current?.focus())
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
break
case Keys.ArrowDown:
@@ -394,7 +395,7 @@ const Options = forwardRefWithAs(function Options<
case Keys.Escape:
event.preventDefault()
dispatch({ type: ActionTypes.CloseListbox })
return d.nextFrame(() => state.buttonRef.current?.focus())
return d.nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
case Keys.Tab:
return event.preventDefault()
@@ -516,7 +517,7 @@ function Option<
if (disabled) return event.preventDefault()
select()
dispatch({ type: ActionTypes.CloseListbox })
disposables().nextFrame(() => state.buttonRef.current?.focus())
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
},
[dispatch, state.buttonRef, disabled, select]
)
@@ -157,7 +157,7 @@ export function Menu<TTag extends React.ElementType = typeof DEFAULT_MENU_TAG>(
if (!itemsRef.current?.contains(target)) dispatch({ type: ActionTypes.CloseMenu })
if (active !== document.body && active?.contains(target)) return // Keep focus on newly clicked/focused element
if (!event.defaultPrevented) buttonRef.current?.focus()
if (!event.defaultPrevented) buttonRef.current?.focus({ preventScroll: true })
}
window.addEventListener('click', handler)
@@ -209,7 +209,7 @@ const Button = forwardRefWithAs(function Button<
event.preventDefault()
dispatch({ type: ActionTypes.OpenMenu })
d.nextFrame(() => {
state.itemsRef.current?.focus()
state.itemsRef.current?.focus({ preventScroll: true })
dispatch({ type: ActionTypes.GoToItem, focus: Focus.First })
})
break
@@ -218,7 +218,7 @@ const Button = forwardRefWithAs(function Button<
event.preventDefault()
dispatch({ type: ActionTypes.OpenMenu })
d.nextFrame(() => {
state.itemsRef.current?.focus()
state.itemsRef.current?.focus({ preventScroll: true })
dispatch({ type: ActionTypes.GoToItem, focus: Focus.Last })
})
break
@@ -232,11 +232,11 @@ const Button = forwardRefWithAs(function Button<
if (props.disabled) return
if (state.menuState === MenuStates.Open) {
dispatch({ type: ActionTypes.CloseMenu })
d.nextFrame(() => state.buttonRef.current?.focus())
d.nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
} else {
event.preventDefault()
dispatch({ type: ActionTypes.OpenMenu })
d.nextFrame(() => state.itemsRef.current?.focus())
d.nextFrame(() => state.itemsRef.current?.focus({ preventScroll: true }))
}
},
[dispatch, d, state, props.disabled]
@@ -306,7 +306,7 @@ const Items = forwardRefWithAs(function Items<
const { id } = state.items[state.activeItemIndex]
document.getElementById(id)?.click()
}
disposables().nextFrame(() => state.buttonRef.current?.focus())
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
break
case Keys.ArrowDown:
@@ -330,7 +330,7 @@ const Items = forwardRefWithAs(function Items<
case Keys.Escape:
event.preventDefault()
dispatch({ type: ActionTypes.CloseMenu })
disposables().nextFrame(() => state.buttonRef.current?.focus())
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
break
case Keys.Tab:
@@ -415,7 +415,7 @@ function Item<TTag extends React.ElementType = typeof DEFAULT_ITEM_TAG>(
(event: { preventDefault: Function }) => {
if (disabled) return event.preventDefault()
dispatch({ type: ActionTypes.CloseMenu })
disposables().nextFrame(() => state.buttonRef.current?.focus())
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
if (onClick) return onClick(event)
},
[dispatch, state.buttonRef, disabled, onClick]
@@ -134,7 +134,7 @@ function Label<TTag extends React.ElementType = typeof DEFAULT_LABEL_TAG>(
const handlePointerUp = React.useCallback(() => {
if (!state.switch) return
state.switch.click()
state.switch.focus()
state.switch.focus({ preventScroll: true })
}, [state.switch])
const propsWeControl = { ref: state.setLabel, id, onPointerUp: handlePointerUp }
@@ -167,7 +167,7 @@ export const Listbox = defineComponent({
if (!optionsRef.value?.contains(target)) api.closeListbox()
if (active !== document.body && active?.contains(target)) return // Keep focus on newly clicked/focused element
if (!event.defaultPrevented) buttonRef.value?.focus()
if (!event.defaultPrevented) buttonRef.value?.focus({ preventScroll: true })
}
window.addEventListener('click', handler)
@@ -210,7 +210,7 @@ export const ListboxLabel = defineComponent({
id,
el: api.labelRef,
handlePointerUp() {
api.buttonRef.value?.focus()
api.buttonRef.value?.focus({ preventScroll: true })
},
}
},
@@ -263,7 +263,7 @@ export const ListboxButton = defineComponent({
event.preventDefault()
api.openListbox()
nextTick(() => {
api.optionsRef.value?.focus()
api.optionsRef.value?.focus({ preventScroll: true })
if (!api.value.value) api.goToOption(Focus.First)
})
break
@@ -272,7 +272,7 @@ export const ListboxButton = defineComponent({
event.preventDefault()
api.openListbox()
nextTick(() => {
api.optionsRef.value?.focus()
api.optionsRef.value?.focus({ preventScroll: true })
if (!api.value.value) api.goToOption(Focus.Last)
})
break
@@ -283,11 +283,11 @@ export const ListboxButton = defineComponent({
if (props.disabled) return
if (api.listboxState.value === ListboxStates.Open) {
api.closeListbox()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
} else {
event.preventDefault()
api.openListbox()
nextFrame(() => api.optionsRef.value?.focus())
nextFrame(() => api.optionsRef.value?.focus({ preventScroll: true }))
}
}
@@ -356,7 +356,7 @@ export const ListboxOptions = defineComponent({
api.select(dataRef.value)
}
api.closeListbox()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
break
case Keys.ArrowDown:
@@ -380,7 +380,7 @@ export const ListboxOptions = defineComponent({
case Keys.Escape:
event.preventDefault()
api.closeListbox()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
break
case Keys.Tab:
@@ -456,7 +456,7 @@ export const ListboxOption = defineComponent({
if (disabled) return event.preventDefault()
api.select(value)
api.closeListbox()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
}
function handleFocus() {
@@ -144,7 +144,7 @@ export const Menu = defineComponent({
if (!itemsRef.value?.contains(target)) api.closeMenu()
if (active !== document.body && active?.contains(target)) return // Keep focus on newly clicked/focused element
if (!event.defaultPrevented) buttonRef.value?.focus()
if (!event.defaultPrevented) buttonRef.value?.focus({ preventScroll: true })
}
window.addEventListener('click', handler)
@@ -202,7 +202,7 @@ export const MenuButton = defineComponent({
event.preventDefault()
api.openMenu()
nextTick(() => {
api.itemsRef.value?.focus()
api.itemsRef.value?.focus({ preventScroll: true })
api.goToItem(Focus.First)
})
break
@@ -211,7 +211,7 @@ export const MenuButton = defineComponent({
event.preventDefault()
api.openMenu()
nextTick(() => {
api.itemsRef.value?.focus()
api.itemsRef.value?.focus({ preventScroll: true })
api.goToItem(Focus.Last)
})
break
@@ -222,11 +222,11 @@ export const MenuButton = defineComponent({
if (props.disabled) return
if (api.menuState.value === MenuStates.Open) {
api.closeMenu()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
} else {
event.preventDefault()
api.openMenu()
nextFrame(() => api.itemsRef.value?.focus())
nextFrame(() => api.itemsRef.value?.focus({ preventScroll: true }))
}
}
@@ -297,7 +297,7 @@ export const MenuItems = defineComponent({
document.getElementById(id)?.click()
}
api.closeMenu()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
break
case Keys.ArrowDown:
@@ -321,7 +321,7 @@ export const MenuItems = defineComponent({
case Keys.Escape:
event.preventDefault()
api.closeMenu()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
break
case Keys.Tab:
@@ -373,7 +373,7 @@ export const MenuItem = defineComponent({
function handleClick(event: MouseEvent) {
if (disabled) return event.preventDefault()
api.closeMenu()
nextTick(() => api.buttonRef.value?.focus())
nextTick(() => api.buttonRef.value?.focus({ preventScroll: true }))
}
function handleFocus() {
@@ -136,7 +136,7 @@ export const SwitchLabel = defineComponent({
el: api.labelRef,
handlePointerUp() {
api.switchRef.value?.click()
api.switchRef.value?.focus()
api.switchRef.value?.focus({ preventScroll: true })
},
}
},