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

[AlertDialog, Dialog, Popover] Configure initial focus #732

Merged
merged 20 commits into from
Nov 1, 2024

Conversation

michaldudak
Copy link
Member

@michaldudak michaldudak commented Oct 10, 2024

Added the initialFocus prop to control what element is focused after the Dialog, AlertDialog, or Popover is open.
By default it focuses the first element, unless the component was opened by touch interaction - in that case the popup itself is focused.

Part of #714

@michaldudak michaldudak added enhancement This is not a bug, nor a new feature component: dialog This is the name of the generic UI component, not the React module! labels Oct 10, 2024
@mui-bot
Copy link

mui-bot commented Oct 10, 2024

Netlify deploy preview

https://deploy-preview-732--base-ui.netlify.app/

Generated by 🚫 dangerJS against 3405016

@michaldudak michaldudak force-pushed the dialog-initial-focus branch 2 times, most recently from 3cf1d49 to 76026c0 Compare October 10, 2024 10:22
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 15, 2024
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 17, 2024
@michaldudak michaldudak changed the title [Dialog] Configure initial focus [AlertDialog, Dialog] Configure initial focus Oct 17, 2024
@michaldudak michaldudak added the component: alert dialog This is the name of the generic UI component, not the React module! label Oct 17, 2024
Copy link
Contributor

@vladmoroz vladmoroz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • An existing bug, but just noticed that in /experiments/dialog/, in the second dialog (CSS animation example) the initial focus isn't placed into the popup at all
  • Is "" meant to be among pointer types?
  • Since pointer type includes "keyboard", should we name this argument and type differently?
image

@michaldudak michaldudak added the component: popover The React component. label Oct 21, 2024
@michaldudak michaldudak changed the title [AlertDialog, Dialog] Configure initial focus [AlertDialog, Dialog, Popover] Configure initial focus Oct 21, 2024
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 22, 2024
@michaldudak
Copy link
Member Author

@vladmoroz I'll address the bug in a separate PR.

An empty string is a valid value of PointerEvent's pointerType field, so I included it here as well.

As for naming, yeah, I suppose we can think of something better. triggerType?

@vladmoroz
Copy link
Contributor

@michaldudak what does the empty string mean?

What about "eventType"? "triggerType" sounds like it has to do with the trigger part

@michaldudak
Copy link
Member Author

An empty string is an unknown pointer type (see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType#value).

It's not really the type of the event (as this is PointerEvent or MouseEvent), but the user's input device that caused the event to fire (= triggered it).

@vladmoroz
Copy link
Contributor

An empty string is an unknown pointer type (see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType#value).

Eww 😬

If the browser supports pointer device types other than those listed above, the value should be vendor-prefixed to avoid conflicting names for different types of devices.

Do we return an empty string in this case or do we pass through the original type?

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 29, 2024
@michaldudak
Copy link
Member Author

Do we return an empty string in this case or do we pass through the original type?

We pass through whatever there is originally.

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 29, 2024
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Oct 29, 2024
Copy link
Contributor

@vladmoroz vladmoroz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good functionality-wise, I'll leave the code review to @atomiks though

Comment on lines 2 to 4

export type PointerType = 'mouse' | 'touch' | 'pen' | 'keyboard' | '';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like we need the implementation to be this detailed given we're only checking for touch presently. Instead of adding a click handler, we could simply add a pointerdown handler to read the pointerType, and ignore click.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case activating the trigger with keyboard would not be recognized at all, would it? I agree we don't need this internally, but we expose it to users so they can provide a function to the initialFocus prop with logic dependent on the method of activation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can reduce it to a boolean isTouch, though it could be a breaking change in the future if we need to add more strings

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the API feels less weird with pointerType than isTouch. Also it doesn't have significant perf implications as the extra logic runs only on trigger click, so I'd leave it as it is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree about exposing all types, this makes for a much more flexible API.

My only nit is about PointerType name, where "keyboard" feels out of place. Perhaps both the type and the argument should be something like InteractionType? No big deal though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 yeah, "interaction" sounds better. How about InteractionMethod, though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InteractionType feels more like an analogy to event.pointerType, no? I would be cool with just interaction too.

I don't love "method" personally, it makes me think of something more systematic, like a derivative from "methodology":

image

@@ -39,6 +41,7 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
const [descriptionId, setDescriptionId] = React.useState<string>();
const [triggerElement, setTriggerElement] = React.useState<Element | null>(null);
const [positionerElement, setPositionerElement] = React.useState<HTMLElement | null>(null);
const [openMethod, setOpenMethod] = React.useState<PointerType | null>(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great for this to be a ref instead to avoid an extra re-render, but I suppose there's no way to avoid reading it during render instead of in an effect/event handler?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing it, I never saw this state causing a rerender by itself. It's always batched with other state setters.

@michaldudak michaldudak merged commit 6abbb3b into mui:master Nov 1, 2024
22 checks passed
@michaldudak michaldudak deleted the dialog-initial-focus branch November 1, 2024 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: alert dialog This is the name of the generic UI component, not the React module! component: dialog This is the name of the generic UI component, not the React module! component: popover The React component. enhancement This is not a bug, nor a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants