Commit Graph

3 Commits

Author SHA1 Message Date
Robin Malfait 30a6d51665 Fix focus not returned to SVG Element (#3704)
This PR fixes an issue where the focus is not returned to an `SVG`
element with a `tabIndex` correctly.

There are a few issues going on here:

1. We assume that the element to focus (`e.target`) is an instanceof
`HTMLElement`, but the `SVGElement` is not an instanceof `HTMLElement`.
2. By using `instanceof` we are checking against concrete classes, so if
this happen to cross certain contexts (Shadow DOM, Iframes, ...) then
the instances would be different.

To solve this, we will now:

1. Relax the types and only care about the actual attributes and methods
we are interested in. In most cases this means changing internal types
from `HTMLElement` to `Element` for example.
2. We will check whether certain properties are available in the object
to deduce the correct type from the object.

Fixes: #3660

## Test plan

Added an SVG to open a Dialog component and made sure that pressing
`escape` or clicking outside of the Dialog does restore the focus to the
SVG itself.
```tsx
<svg
  tabIndex={0}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      setIsOpen((v) => !v)
    }
  }}
  onClick={() => setIsOpen((v) => !v)}
  className="h-6 w-6 text-gray-500"
>
  <BookOpenIcon />
</svg>
```


Here is a video of that behavior:


https://github.com/user-attachments/assets/1805ca67-8bc7-4315-98a7-2490cba9230c
2025-04-25 14:52:32 +02:00
Robin Malfait 91e959714b Fix restoring focus to correct element when closing Dialog component (#3365)
* resolve focusable element when recording elements

Right now, we have to record when a click / mousedown / focus event happens on an element. But when you click on a non-focusable element inside of a focusable element then we record the inner element instead of the outer one.

This happens in this scenario:
```html
<button>
  <span>click me</span>
</button>
```

This solves it by resolving the closest focusable element (and we fallback to the e.target as a last resort)

* update changelog
2024-07-05 16:14:50 +02:00
Timur Zurbaev fa952626c7 Add immediate prop to <Combobox /> for immediately opening the Combobox when the input receives focus (#2686)
* Allow to open combobox on input focus

* Close focused combobox with openOnFocus prop when clicking the button

* ensure tabbing through a few fields, doesn't result in an incorrectly selected item

When you have a fwe inputs such as:

```html
<form>
   <input />
   <input />
   <input />
   <Combobox>
      <Combobox.Input />
   </Combobox>
   <input />
   <input />
   <input />
</form>
```

Tabbing through this list will open the combobox once you are on the
input field. When you continue tabbing, the first item would be
selected. However, if the combobox is not marked as nullable, it means
that just going through the form means that we set a value we can't
unset anymore.

We still want to open the combobox, we just don't want to select
anything in this case.

* only `openOnFocus` if the `<Combobox.Input />` is focused from the
outside

If the focus is coming from the `<Combobox.Button />` or as a side
effect of selecting an `<Combobox.Option />` then we don't want to
re-open the `<Combobox />`

* update tests to ensure that the `Combobox.Input` is the active element

* order `handleBlur` and `handleFocus` the same way in Vue & React

* only select the active option when the Combobox wasn't opened by focusing the input field

* convert to `immediate` prop on the `Combobox` itself

* update changelog

* ensure we see the "relatedTarget" in Safari

Safari doesn't fire a `focus` event when clicking a button, therefore it
does not become the `document.activeElement`, and events like `blur` or
`focus` doesn't set the button as the `event.relatedTarget`.

Keeping track of a history like this solves that problem. We already had
the code for the `FocusTrap` component.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-08-31 15:23:56 +02:00