diff --git a/packages/@headlessui-react/src/components/menu/menu.test.tsx b/packages/@headlessui-react/src/components/menu/menu.test.tsx index 2a11abf..5267dc3 100644 --- a/packages/@headlessui-react/src/components/menu/menu.test.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.test.tsx @@ -17,12 +17,11 @@ import { import { click, focus, - hover, mouseMove, + mouseLeave, press, shift, type, - unHover, word, Keys, } from '../../test-utils/interactions' @@ -633,7 +632,7 @@ describe('Keyboard interactions', () => { // Activate the first menu item const items = getMenuItems() - await hover(items[0]) + await mouseMove(items[0]) // Close menu, and invoke the item await press(Keys.Enter) @@ -682,7 +681,7 @@ describe('Keyboard interactions', () => { // Activate the second menu item const items = getMenuItems() - await hover(items[1]) + await mouseMove(items[1]) // Close menu, and invoke the item await press(Keys.Enter) @@ -698,7 +697,7 @@ describe('Keyboard interactions', () => { await click(getMenuButton()) // Active the last menu item - await hover(getMenuItems()[2]) + await mouseMove(getMenuItems()[2]) // Close menu, and invoke the item await press(Keys.Enter) @@ -2323,15 +2322,15 @@ describe('Mouse interactions', () => { const items = getMenuItems() // We should be able to go to the second item - await hover(items[1]) + await mouseMove(items[1]) assertMenuLinkedWithMenuItem(getMenu(), items[1]) // We should be able to go to the first item - await hover(items[0]) + await mouseMove(items[0]) assertMenuLinkedWithMenuItem(getMenu(), items[0]) // We should be able to go to the last item - await hover(items[2]) + await mouseMove(items[2]) assertMenuLinkedWithMenuItem(getMenu(), items[2]) }) ) @@ -2438,7 +2437,7 @@ describe('Mouse interactions', () => { const items = getMenuItems() // Try to hover over item 1, which is disabled - await hover(items[1]) + await mouseMove(items[1]) // We should not have an active item now assertNoActiveMenuItem(getMenu()) @@ -2465,24 +2464,24 @@ describe('Mouse interactions', () => { const items = getMenuItems() // We should be able to go to the second item - await hover(items[1]) + await mouseMove(items[1]) assertMenuLinkedWithMenuItem(getMenu(), items[1]) - await unHover(items[1]) + await mouseLeave(items[1]) assertNoActiveMenuItem(getMenu()) // We should be able to go to the first item - await hover(items[0]) + await mouseMove(items[0]) assertMenuLinkedWithMenuItem(getMenu(), items[0]) - await unHover(items[0]) + await mouseLeave(items[0]) assertNoActiveMenuItem(getMenu()) // We should be able to go to the last item - await hover(items[2]) + await mouseMove(items[2]) assertMenuLinkedWithMenuItem(getMenu(), items[2]) - await unHover(items[2]) + await mouseLeave(items[2]) assertNoActiveMenuItem(getMenu()) }) ) @@ -2509,10 +2508,10 @@ describe('Mouse interactions', () => { const items = getMenuItems() // Try to hover over item 1, which is disabled - await hover(items[1]) + await mouseMove(items[1]) assertNoActiveMenuItem(getMenu()) - await unHover(items[1]) + await mouseLeave(items[1]) assertNoActiveMenuItem(getMenu()) }) ) diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index d7e12d2..1a8d69a 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -535,27 +535,6 @@ function Item( return () => dispatch({ type: ActionTypes.UnregisterItem, id }) }, [bag, id]) - const handlePointerEnter = React.useCallback(() => { - if (disabled) return - dispatch({ type: ActionTypes.GoToItem, focus: Focus.SpecificItem, id }) - }, [disabled, id, dispatch]) - - const handleFocus = React.useCallback(() => { - if (disabled) return dispatch({ type: ActionTypes.GoToItem, focus: Focus.Nothing }) - dispatch({ type: ActionTypes.GoToItem, focus: Focus.SpecificItem, id }) - }, [disabled, id, dispatch]) - - const handlePointerLeave = React.useCallback(() => { - if (disabled) return - dispatch({ type: ActionTypes.GoToItem, focus: Focus.Nothing }) - }, [disabled, dispatch]) - - const handleMouseMove = React.useCallback(() => { - if (disabled) return - if (active) return - dispatch({ type: ActionTypes.GoToItem, focus: Focus.SpecificItem, id }) - }, [disabled, active, id, dispatch]) - const handleClick = React.useCallback( (event: { preventDefault: Function }) => { if (disabled) return event.preventDefault() @@ -566,6 +545,22 @@ function Item( [d, dispatch, state.buttonRef, disabled, onClick] ) + const handleFocus = React.useCallback(() => { + if (disabled) return dispatch({ type: ActionTypes.GoToItem, focus: Focus.Nothing }) + dispatch({ type: ActionTypes.GoToItem, focus: Focus.SpecificItem, id }) + }, [disabled, id, dispatch]) + + const handlePointerMove = React.useCallback(() => { + if (disabled) return + if (active) return + dispatch({ type: ActionTypes.GoToItem, focus: Focus.SpecificItem, id }) + }, [disabled, active, id, dispatch]) + + const handlePointerLeave = React.useCallback(() => { + if (disabled) return + dispatch({ type: ActionTypes.GoToItem, focus: Focus.Nothing }) + }, [disabled, dispatch]) + const propsBag = React.useMemo(() => ({ active, disabled }), [active, disabled]) const propsWeControl = { id, @@ -575,8 +570,7 @@ function Item( 'aria-disabled': disabled === true ? true : undefined, onClick: handleClick, onFocus: handleFocus, - onMouseMove: handleMouseMove, - onPointerEnter: handlePointerEnter, + onPointerMove: handlePointerMove, onPointerLeave: handlePointerLeave, } diff --git a/packages/@headlessui-react/src/test-utils/interactions.ts b/packages/@headlessui-react/src/test-utils/interactions.ts index 791e363..48db62b 100644 --- a/packages/@headlessui-react/src/test-utils/interactions.ts +++ b/packages/@headlessui-react/src/test-utils/interactions.ts @@ -89,13 +89,28 @@ export async function focus(element: Document | Element | Window | Node | null) throw err } } - -export async function mouseMove(element: Document | Element | Window | Node | null) { +export async function mouseEnter(element: Document | Element | Window | null) { try { if (element === null) return expect(element).not.toBe(null) - const d = disposables() + fireEvent.pointerOver(element) + fireEvent.pointerEnter(element) + fireEvent.mouseOver(element) + + await new Promise(d.nextFrame) + } catch (err) { + if (Error.captureStackTrace) Error.captureStackTrace(err, mouseEnter) + throw err + } +} + +export async function mouseMove(element: Document | Element | Window | null) { + try { + if (element === null) return expect(element).not.toBe(null) + const d = disposables() + + fireEvent.pointerMove(element) fireEvent.mouseMove(element) await new Promise(d.nextFrame) @@ -105,27 +120,9 @@ export async function mouseMove(element: Document | Element | Window | Node | nu } } -export async function hover(element: Document | Element | Window | Node | null) { +export async function mouseLeave(element: Document | Element | Window | null) { try { if (element === null) return expect(element).not.toBe(null) - - const d = disposables() - - fireEvent.pointerOver(element) - fireEvent.pointerEnter(element) - fireEvent.mouseOver(element) - - await new Promise(d.nextFrame) - } catch (err) { - if (Error.captureStackTrace) Error.captureStackTrace(err, hover) - throw err - } -} - -export async function unHover(element: Document | Element | Window | Node | null) { - try { - if (element === null) return expect(element).not.toBe(null) - const d = disposables() fireEvent.pointerOut(element) @@ -135,7 +132,7 @@ export async function unHover(element: Document | Element | Window | Node | null await new Promise(d.nextFrame) } catch (err) { - if (Error.captureStackTrace) Error.captureStackTrace(err, unHover) + if (Error.captureStackTrace) Error.captureStackTrace(err, mouseLeave) throw err } } diff --git a/packages/@headlessui-vue/src/components/menu/menu.test.tsx b/packages/@headlessui-vue/src/components/menu/menu.test.tsx index c68ae69..9976dc5 100644 --- a/packages/@headlessui-vue/src/components/menu/menu.test.tsx +++ b/packages/@headlessui-vue/src/components/menu/menu.test.tsx @@ -16,12 +16,11 @@ import { import { click, focus, - hover, mouseMove, + mouseLeave, press, shift, type, - unHover, Keys, word, } from '../../test-utils/interactions' @@ -812,7 +811,7 @@ describe('Keyboard interactions', () => { // Activate the first menu item const items = getMenuItems() - await hover(items[0]) + await mouseMove(items[0]) // Close menu, and invoke the item await press(Keys.Enter) @@ -861,7 +860,7 @@ describe('Keyboard interactions', () => { // Activate the second menu item const items = getMenuItems() - await hover(items[1]) + await mouseMove(items[1]) // Close menu, and invoke the item await press(Keys.Enter) @@ -877,7 +876,7 @@ describe('Keyboard interactions', () => { await click(getMenuButton()) // Active the last menu item - await hover(getMenuItems()[2]) + await mouseMove(getMenuItems()[2]) // Close menu, and invoke the item await press(Keys.Enter) @@ -2273,15 +2272,15 @@ describe('Mouse interactions', () => { const items = getMenuItems() // We should be able to go to the second item - await hover(items[1]) + await mouseMove(items[1]) assertMenuLinkedWithMenuItem(getMenu(), items[1]) // We should be able to go to the first item - await hover(items[0]) + await mouseMove(items[0]) assertMenuLinkedWithMenuItem(getMenu(), items[0]) // We should be able to go to the last item - await hover(items[2]) + await mouseMove(items[2]) assertMenuLinkedWithMenuItem(getMenu(), items[2]) }) @@ -2372,7 +2371,7 @@ describe('Mouse interactions', () => { const items = getMenuItems() // Try to hover over item 1, which is disabled - await hover(items[1]) + await mouseMove(items[1]) // We should not have an active item now assertNoActiveMenuItem(getMenu()) @@ -2396,24 +2395,24 @@ describe('Mouse interactions', () => { const items = getMenuItems() // We should be able to go to the second item - await hover(items[1]) + await mouseMove(items[1]) assertMenuLinkedWithMenuItem(getMenu(), items[1]) - await unHover(items[1]) + await mouseLeave(items[1]) assertNoActiveMenuItem(getMenu()) // We should be able to go to the first item - await hover(items[0]) + await mouseMove(items[0]) assertMenuLinkedWithMenuItem(getMenu(), items[0]) - await unHover(items[0]) + await mouseLeave(items[0]) assertNoActiveMenuItem(getMenu()) // We should be able to go to the last item - await hover(items[2]) + await mouseMove(items[2]) assertMenuLinkedWithMenuItem(getMenu(), items[2]) - await unHover(items[2]) + await mouseLeave(items[2]) assertNoActiveMenuItem(getMenu()) }) @@ -2435,10 +2434,10 @@ describe('Mouse interactions', () => { const items = getMenuItems() // Try to hover over item 1, which is disabled - await hover(items[1]) + await mouseMove(items[1]) assertNoActiveMenuItem(getMenu()) - await unHover(items[1]) + await mouseLeave(items[1]) assertNoActiveMenuItem(getMenu()) }) diff --git a/packages/@headlessui-vue/src/components/menu/menu.ts b/packages/@headlessui-vue/src/components/menu/menu.ts index e259fce..d1d7bd2 100644 --- a/packages/@headlessui-vue/src/components/menu/menu.ts +++ b/packages/@headlessui-vue/src/components/menu/menu.ts @@ -419,9 +419,10 @@ export const MenuItem = defineComponent({ onMounted(() => api.registerItem(id, dataRef)) onUnmounted(() => api.unregisterItem(id)) - function handlePointerEnter() { - if (disabled) return - api.goToItem(Focus.SpecificItem, id) + function handleClick(event: MouseEvent) { + if (disabled) return event.preventDefault() + api.closeMenu() + nextTick(() => api.buttonRef.value?.focus()) } function handleFocus() { @@ -429,21 +430,15 @@ export const MenuItem = defineComponent({ api.goToItem(Focus.SpecificItem, id) } - function handlePointerLeave() { - if (disabled) return - api.goToItem(Focus.Nothing) - } - - function handleMouseMove() { + function handlePointerMove() { if (disabled) return if (active.value) return api.goToItem(Focus.SpecificItem, id) } - function handleClick(event: MouseEvent) { - if (disabled) return event.preventDefault() - api.closeMenu() - nextTick(() => api.buttonRef.value?.focus()) + function handlePointerLeave() { + if (disabled) return + api.goToItem(Focus.Nothing) } return () => { @@ -456,8 +451,7 @@ export const MenuItem = defineComponent({ 'aria-disabled': disabled === true ? true : undefined, onClick: handleClick, onFocus: handleFocus, - onMouseMove: handleMouseMove, - onPointerEnter: handlePointerEnter, + onPointerMove: handlePointerMove, onPointerLeave: handlePointerLeave, } diff --git a/packages/@headlessui-vue/src/test-utils/interactions.ts b/packages/@headlessui-vue/src/test-utils/interactions.ts index f46042e..aff3fc8 100644 --- a/packages/@headlessui-vue/src/test-utils/interactions.ts +++ b/packages/@headlessui-vue/src/test-utils/interactions.ts @@ -85,20 +85,7 @@ export async function focus(element: Document | Element | Window | null) { } } -export async function mouseMove(element: Document | Element | Window | null) { - try { - if (element === null) return expect(element).not.toBe(null) - - fireEvent.mouseMove(element) - - await new Promise(nextTick) - } catch (err) { - if (Error.captureStackTrace) Error.captureStackTrace(err, mouseMove) - throw err - } -} - -export async function hover(element: Document | Element | Window | null) { +export async function mouseEnter(element: Document | Element | Window | null) { try { if (element === null) return expect(element).not.toBe(null) @@ -108,12 +95,26 @@ export async function hover(element: Document | Element | Window | null) { await new Promise(nextTick) } catch (err) { - if (Error.captureStackTrace) Error.captureStackTrace(err, hover) + if (Error.captureStackTrace) Error.captureStackTrace(err, mouseEnter) throw err } } -export async function unHover(element: Document | Element | Window | null) { +export async function mouseMove(element: Document | Element | Window | null) { + try { + if (element === null) return expect(element).not.toBe(null) + + fireEvent.pointerMove(element) + fireEvent.mouseMove(element) + + await new Promise(nextTick) + } catch (err) { + if (Error.captureStackTrace) Error.captureStackTrace(err, mouseMove) + throw err + } +} + +export async function mouseLeave(element: Document | Element | Window | null) { try { if (element === null) return expect(element).not.toBe(null) @@ -124,7 +125,7 @@ export async function unHover(element: Document | Element | Window | null) { await new Promise(nextTick) } catch (err) { - if (Error.captureStackTrace) Error.captureStackTrace(err, unHover) + if (Error.captureStackTrace) Error.captureStackTrace(err, mouseLeave) throw err } }