-
Notifications
You must be signed in to change notification settings - Fork 1k
[DropdownMenu][Menu] Allow dialog composition #818
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| releases: | ||
| "@radix-ui/react-context-menu": patch | ||
| "@radix-ui/react-dropdown-menu": patch | ||
| "@radix-ui/react-menu": patch | ||
|
|
||
| declined: | ||
| - primitives |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -644,15 +644,19 @@ const MenuItem = React.forwardRef((props, forwardedRef) => { | |
| const context = useMenuContext(ITEM_NAME); | ||
| const contentContext = useMenuContentContext(ITEM_NAME); | ||
| const composedRefs = useComposedRefs(forwardedRef, ref); | ||
| const isPointerDownRef = React.useRef(false); | ||
|
|
||
| const handleSelect = () => { | ||
| const menuItem = ref.current; | ||
| if (!disabled && menuItem) { | ||
| const itemSelectEvent = new Event(ITEM_SELECT, { bubbles: true, cancelable: true }); | ||
| menuItem.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), { once: true }); | ||
| menuItem.dispatchEvent(itemSelectEvent); | ||
| if (itemSelectEvent.defaultPrevented) return; | ||
| context.onRootClose(); | ||
| if (itemSelectEvent.defaultPrevented) { | ||
| isPointerDownRef.current = false; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to make sure I understand fully, we're resetting here because the menu won't close, the item won't unmount and so the ref won't be reset automatically on remount? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the ref will always be reset automatically on remount, this is specifically for a case where someone prevents it from closing. Without this, this would break:
That pointer up on the item from step one would think they pointered down on it too because it wouldn't have been reset when it was kept open. |
||
| } else { | ||
| context.onRootClose(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -661,15 +665,28 @@ const MenuItem = React.forwardRef((props, forwardedRef) => { | |
| {...itemProps} | ||
| ref={composedRefs} | ||
| disabled={disabled} | ||
| // we handle selection on `pointerUp` rather than `click` to match native menus implementation | ||
| onPointerUp={composeEventHandlers(props.onPointerUp, handleSelect)} | ||
| onClick={composeEventHandlers(props.onClick, handleSelect)} | ||
| onPointerDown={(event) => { | ||
| props.onPointerDown?.(event); | ||
| isPointerDownRef.current = true; | ||
| }} | ||
| onPointerUp={composeEventHandlers(props.onPointerUp, (event) => { | ||
| // Pointer down can move to a different menu item which should activate it on pointer up. | ||
| // We dispatch a click for selection to allow composition with click based triggers. | ||
| if (!isPointerDownRef.current) event.currentTarget?.click(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it a bit odd that if users add an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes and I've also just realised this conveniently fixes #745 too. I'm okay with it firing a click tbh because that's what we're logically determining to be a click on the item - the circumstances in which it would activate. I guess we can see what complaints we get here from consumers, if any. |
||
| })} | ||
| onKeyDown={composeEventHandlers(props.onKeyDown, (event) => { | ||
| const isTypingAhead = contentContext.searchRef.current !== ''; | ||
| if (disabled || (isTypingAhead && event.key === ' ')) return; | ||
| if (SELECTION_KEYS.includes(event.key)) { | ||
| // prevent page scroll if using the space key to select an item | ||
| if (event.key === ' ') event.preventDefault(); | ||
| handleSelect(); | ||
| event.currentTarget.click(); | ||
| /** | ||
| * We prevent default browser behaviour for selection keys as they should trigger | ||
| * a selection only: | ||
| * - prevents space from scrolling the page. | ||
| * - if keydown causes focus to move, prevents keydown from firing on the new target. | ||
| */ | ||
| event.preventDefault(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed for Any ideas? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not knowing the "stuff" is bugging me too. I realised that the Could this be related to any of the implicit form submission behaviour associated with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This should only be happening if you hold the Is it happening when you just press enter and don't hold it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Seems so, without preventing default the dialogs close button There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's have a quick call because I'm not seeing that here 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh gosh, I see what you mean now hahaha... when I don't put the
Missed that part. Interesting... yeah okay, let me try something quick. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, we've updated the comment to explain why the https://jsbin.com/dohiyiwele/edit?html,css,js,console,output There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the same thing I was running into here. 🤔 https://discord.com/channels/752614004387610674/872115819490996244/872416709607309353 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yep! we were actually using one of those examples to help debug. I was quite surprised to see the events working this way after calling |
||
| } | ||
| })} | ||
| /> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benoitgrelard We shouldn't really have to do this because the
DropdownMenualready has logic to prevent its trigger from focusing if there was a focus outside of it. However, thesetTimeoutinFocusScopeforonCloseAutoFocusis re-ordering events and breaking the expected behaviour here.We have discussed removing the
setTimeoutfromFocusScopegiven that v17 has been out for a while and we are a new modern library (still in alpha) but we're reluctant to make that change as part of this PR (not sure of the knock on effects in other primz).We're going to leave this in the story for now to demonstrate the workaround but it would be good to discuss this in more detail with you when you return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm more curious why we have to do it only in the non-modal scenario?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the
modalscenario traps focus in the dialog so when it tries to focus the dropdown trigger when the dropdopwn closes (onCloseAutoFocus) it can't.