Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix hover scroll #1161

Merged
merged 4 commits into from
Feb 27, 2022
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Forward the `ref` to all components ([#1116](https://github.com/tailwindlabs/headlessui/pull/1116))
- Ensure links are triggered inside `Popover Panel` components ([#1153](https://github.com/tailwindlabs/headlessui/pull/1153))
- Improve SSR for `Tab` component ([#1155](https://github.com/tailwindlabs/headlessui/pull/1155))
- Fix `hover` scroll ([#1161](https://github.com/tailwindlabs/headlessui/pull/1161))

## [Unreleased - @headlessui/vue]

Expand All @@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure that you can close the combobox initially ([#1148](https://github.com/tailwindlabs/headlessui/pull/1148))
- Fix Dialog usage in Tabs ([#1149](https://github.com/tailwindlabs/headlessui/pull/1149))
- Ensure links are triggered inside `Popover Panel` components ([#1153](https://github.com/tailwindlabs/headlessui/pull/1153))
- Fix `hover` scroll ([#1161](https://github.com/tailwindlabs/headlessui/pull/1161))

## [@headlessui/react@v1.5.0] - 2022-02-17

Expand Down
33 changes: 28 additions & 5 deletions packages/@headlessui-react/src/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ enum ComboboxStates {
Closed,
}

enum ActivationTrigger {
Pointer,
Other,
}

type ComboboxOptionDataRef = MutableRefObject<{
textValue?: string
disabled: boolean
Expand Down Expand Up @@ -70,6 +75,7 @@ interface StateDefinition {
disabled: boolean
options: { id: string; dataRef: ComboboxOptionDataRef }[]
activeOptionIndex: number | null
activationTrigger: ActivationTrigger
}

enum ActionTypes {
Expand All @@ -88,8 +94,12 @@ type Actions =
| { type: ActionTypes.CloseCombobox }
| { type: ActionTypes.OpenCombobox }
| { type: ActionTypes.SetDisabled; disabled: boolean }
| { type: ActionTypes.GoToOption; focus: Focus.Specific; id: string }
| { type: ActionTypes.GoToOption; focus: Exclude<Focus, Focus.Specific> }
| { type: ActionTypes.GoToOption; focus: Focus.Specific; id: string; trigger?: ActivationTrigger }
| {
type: ActionTypes.GoToOption
focus: Exclude<Focus, Focus.Specific>
trigger?: ActivationTrigger
}
| { type: ActionTypes.RegisterOption; id: string; dataRef: ComboboxOptionDataRef }
| { type: ActionTypes.UnregisterOption; id: string }

Expand Down Expand Up @@ -130,7 +140,11 @@ let reducers: {
})

if (state.activeOptionIndex === activeOptionIndex) return state
return { ...state, activeOptionIndex }
return {
...state,
activeOptionIndex,
activationTrigger: action.trigger ?? ActivationTrigger.Other,
}
},
[ActionTypes.RegisterOption]: (state, action) => {
let currentActiveOption =
Expand Down Expand Up @@ -158,6 +172,7 @@ let reducers: {
// the correct index.
return options.indexOf(currentActiveOption)
})(),
activationTrigger: ActivationTrigger.Other,
}

if (
Expand Down Expand Up @@ -189,6 +204,7 @@ let reducers: {
// fix this, we will find the correct (new) index position.
return nextOptions.indexOf(currentActiveOption)
})(),
activationTrigger: ActivationTrigger.Other,
}
},
}
Expand Down Expand Up @@ -275,6 +291,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
disabled,
options: [],
activeOptionIndex: null,
activationTrigger: ActivationTrigger.Other,
} as StateDefinition)
let [{ comboboxState, options, activeOptionIndex, optionsRef, inputRef, buttonRef }, dispatch] =
reducerBag
Expand Down Expand Up @@ -882,12 +899,13 @@ let Option = forwardRefWithAs(function Option<
if (state.comboboxState !== ComboboxStates.Open) return
if (!active) return
if (!enableScrollIntoView.current) return
if (state.activationTrigger === ActivationTrigger.Pointer) return
let d = disposables()
d.requestAnimationFrame(() => {
document.getElementById(id)?.scrollIntoView?.({ block: 'nearest' })
})
return d.dispose
}, [id, active, state.comboboxState, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeOptionIndex])
}, [id, active, state.comboboxState, state.activationTrigger, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeOptionIndex])

let handleClick = useCallback(
(event: { preventDefault: Function }) => {
Expand All @@ -907,7 +925,12 @@ let Option = forwardRefWithAs(function Option<
let handleMove = useCallback(() => {
if (disabled) return
if (active) return
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Specific, id })
dispatch({
type: ActionTypes.GoToOption,
focus: Focus.Specific,
id,
trigger: ActivationTrigger.Pointer,
})
}, [disabled, active, id, dispatch])

let handleLeave = useCallback(() => {
Expand Down
40 changes: 34 additions & 6 deletions packages/@headlessui-react/src/components/listbox/listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ enum ListboxStates {
Closed,
}

enum ActivationTrigger {
Pointer,
Other,
}

type ListboxOptionDataRef = MutableRefObject<{
textValue?: string
disabled: boolean
Expand All @@ -59,6 +64,7 @@ interface StateDefinition {
options: { id: string; dataRef: ListboxOptionDataRef }[]
searchQuery: string
activeOptionIndex: number | null
activationTrigger: ActivationTrigger
}

enum ActionTypes {
Expand All @@ -81,8 +87,12 @@ type Actions =
| { type: ActionTypes.OpenListbox }
| { type: ActionTypes.SetDisabled; disabled: boolean }
| { type: ActionTypes.SetOrientation; orientation: StateDefinition['orientation'] }
| { type: ActionTypes.GoToOption; focus: Focus.Specific; id: string }
| { type: ActionTypes.GoToOption; focus: Exclude<Focus, Focus.Specific> }
| { type: ActionTypes.GoToOption; focus: Focus.Specific; id: string; trigger?: ActivationTrigger }
| {
type: ActionTypes.GoToOption
focus: Exclude<Focus, Focus.Specific>
trigger?: ActivationTrigger
}
| { type: ActionTypes.Search; value: string }
| { type: ActionTypes.ClearSearch }
| { type: ActionTypes.RegisterOption; id: string; dataRef: ListboxOptionDataRef }
Expand Down Expand Up @@ -124,7 +134,12 @@ let reducers: {
})

if (state.searchQuery === '' && state.activeOptionIndex === activeOptionIndex) return state
return { ...state, searchQuery: '', activeOptionIndex }
return {
...state,
searchQuery: '',
activeOptionIndex,
activationTrigger: action.trigger ?? ActivationTrigger.Other,
}
},
[ActionTypes.Search]: (state, action) => {
if (state.disabled) return state
Expand All @@ -151,7 +166,12 @@ let reducers: {
let matchIdx = matchingOption ? state.options.indexOf(matchingOption) : -1

if (matchIdx === -1 || matchIdx === state.activeOptionIndex) return { ...state, searchQuery }
return { ...state, searchQuery, activeOptionIndex: matchIdx }
return {
...state,
searchQuery,
activeOptionIndex: matchIdx,
activationTrigger: ActivationTrigger.Other,
}
},
[ActionTypes.ClearSearch](state) {
if (state.disabled) return state
Expand Down Expand Up @@ -193,6 +213,7 @@ let reducers: {
// fix this, we will find the correct (new) index position.
return nextOptions.indexOf(currentActiveOption)
})(),
activationTrigger: ActivationTrigger.Other,
}
},
}
Expand Down Expand Up @@ -249,6 +270,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
options: [],
searchQuery: '',
activeOptionIndex: null,
activationTrigger: ActivationTrigger.Other,
} as StateDefinition)
let [{ listboxState, propsRef, optionsRef, buttonRef }, dispatch] = reducerBag

Expand Down Expand Up @@ -674,12 +696,13 @@ let Option = forwardRefWithAs(function Option<
useIsoMorphicEffect(() => {
if (state.listboxState !== ListboxStates.Open) return
if (!active) return
if (state.activationTrigger === ActivationTrigger.Pointer) return
let d = disposables()
d.requestAnimationFrame(() => {
document.getElementById(id)?.scrollIntoView?.({ block: 'nearest' })
})
return d.dispose
}, [id, active, state.listboxState, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeOptionIndex])
}, [id, active, state.listboxState, state.activationTrigger, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeOptionIndex])

let handleClick = useCallback(
(event: { preventDefault: Function }) => {
Expand All @@ -699,7 +722,12 @@ let Option = forwardRefWithAs(function Option<
let handleMove = useCallback(() => {
if (disabled) return
if (active) return
dispatch({ type: ActionTypes.GoToOption, focus: Focus.Specific, id })
dispatch({
type: ActionTypes.GoToOption,
focus: Focus.Specific,
id,
trigger: ActivationTrigger.Pointer,
})
}, [disabled, active, id, dispatch])

let handleLeave = useCallback(() => {
Expand Down
40 changes: 34 additions & 6 deletions packages/@headlessui-react/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ enum MenuStates {
Closed,
}

enum ActivationTrigger {
Pointer,
Other,
}

type MenuItemDataRef = MutableRefObject<{ textValue?: string; disabled: boolean }>

interface StateDefinition {
Expand All @@ -50,6 +55,7 @@ interface StateDefinition {
items: { id: string; dataRef: MenuItemDataRef }[]
searchQuery: string
activeItemIndex: number | null
activationTrigger: ActivationTrigger
}

enum ActionTypes {
Expand All @@ -66,8 +72,12 @@ enum ActionTypes {
type Actions =
| { type: ActionTypes.CloseMenu }
| { type: ActionTypes.OpenMenu }
| { type: ActionTypes.GoToItem; focus: Focus.Specific; id: string }
| { type: ActionTypes.GoToItem; focus: Exclude<Focus, Focus.Specific> }
| { type: ActionTypes.GoToItem; focus: Focus.Specific; id: string; trigger?: ActivationTrigger }
| {
type: ActionTypes.GoToItem
focus: Exclude<Focus, Focus.Specific>
trigger?: ActivationTrigger
}
| { type: ActionTypes.Search; value: string }
| { type: ActionTypes.ClearSearch }
| { type: ActionTypes.RegisterItem; id: string; dataRef: MenuItemDataRef }
Expand Down Expand Up @@ -96,7 +106,12 @@ let reducers: {
})

if (state.searchQuery === '' && state.activeItemIndex === activeItemIndex) return state
return { ...state, searchQuery: '', activeItemIndex }
return {
...state,
searchQuery: '',
activeItemIndex,
activationTrigger: action.trigger ?? ActivationTrigger.Other,
}
},
[ActionTypes.Search]: (state, action) => {
let wasAlreadySearching = state.searchQuery !== ''
Expand All @@ -117,7 +132,12 @@ let reducers: {

let matchIdx = matchingItem ? state.items.indexOf(matchingItem) : -1
if (matchIdx === -1 || matchIdx === state.activeItemIndex) return { ...state, searchQuery }
return { ...state, searchQuery, activeItemIndex: matchIdx }
return {
...state,
searchQuery,
activeItemIndex: matchIdx,
activationTrigger: ActivationTrigger.Other,
}
},
[ActionTypes.ClearSearch](state) {
if (state.searchQuery === '') return state
Expand Down Expand Up @@ -156,6 +176,7 @@ let reducers: {
// fix this, we will find the correct (new) index position.
return nextItems.indexOf(currentActiveItem)
})(),
activationTrigger: ActivationTrigger.Other,
}
},
}
Expand Down Expand Up @@ -195,6 +216,7 @@ let MenuRoot = forwardRefWithAs(function Menu<TTag extends ElementType = typeof
items: [],
searchQuery: '',
activeItemIndex: null,
activationTrigger: ActivationTrigger.Other,
} as StateDefinition)
let [{ menuState, itemsRef, buttonRef }, dispatch] = reducerBag
let menuRef = useSyncRefs(ref)
Expand Down Expand Up @@ -543,12 +565,13 @@ let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFA
useIsoMorphicEffect(() => {
if (state.menuState !== MenuStates.Open) return
if (!active) return
if (state.activationTrigger === ActivationTrigger.Pointer) return
let d = disposables()
d.requestAnimationFrame(() => {
document.getElementById(id)?.scrollIntoView?.({ block: 'nearest' })
})
return d.dispose
}, [id, active, state.menuState, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeItemIndex])
}, [id, active, state.menuState, state.activationTrigger, /* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ state.activeItemIndex])

let bag = useRef<MenuItemDataRef['current']>({ disabled })

Expand Down Expand Up @@ -583,7 +606,12 @@ let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFA
let handleMove = useCallback(() => {
if (disabled) return
if (active) return
dispatch({ type: ActionTypes.GoToItem, focus: Focus.Specific, id })
dispatch({
type: ActionTypes.GoToItem,
focus: Focus.Specific,
id,
trigger: ActivationTrigger.Pointer,
})
}, [disabled, active, id, dispatch])

let handleLeave = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,7 @@ describe('Keyboard interactions', () => {
assertActiveElement(getPopoverButton())

// Verify that we got redirected to the href
expect(window.location.hash).toEqual('#closed')
expect(document.location.hash).toEqual('#closed')
})
)
})
Expand Down
Loading