focus-trap: focusIn listener causes issues with outside elements

Re: #42

When certain Kendo UI elements are attached to the body outside the focus-trap (i.e. datepicker), it attempts to regain focus in the focusIn listener; even when using allowOutsideClick or clickOutsideDeactivates.

Not sure what the fix needs to be, but from the little time I have spent with this library, I think checking for these options in the focusIn handler is also needed.

Optionally, a new option could be introduced that either takes a string (selector) or a callback that can check if the clicked/focused element is allowed to take focus.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 15 (8 by maintainers)

Most upvoted comments

If anyone else who responded to this issue would still like to have a constructive discussion about a possible solution, I’m happy to. Y’all know where my thoughts are at so far.

Maybe my use case is not exatly the same as Mark’s, but in general when we are talking about elements added directly to BODY I see two main problems (you’ve described it also eralier):

  1. We cannot control 3rd party components to force them to add elements to some concrete container below body. They often add elements to body tag directly. So we cannot pass specific element below body when constructing focus-trap, we must update it using updateContainers() on the fly. I’ve already implemented this for Select2 using MutationObserver to observe changes in document.body and dynamically update list fo containers in focus-trap. Here possible improvement could be to allow to pass some “dynamic” selector to focus-trap, not concrete elements. But it is probably quite big change in code.
  2. Event if we solved this first problem, the second one still exists: proper tab order (as @stefcameron decribed in last comment). And currently I don’t know how to solve this.

The whole problem comes form HTML/CSS limitations to handle such things like modals, dropdowns, etc. These all hacks with adding elements to body are problematic for operating from the keyboard becuase this braeks natural HTML elements order. Maybe someone knows some applications/websites when these things are properly implemented (eg. some government sites, which often are WCAG compliant)?

You mentioned the term slot here, so does that mean your gets placed in a web component’s (i.e. shadow DOM’s) <slot>?

I was referring to Vue.js Slots. It’s a common practice/pattern for wrapping content in Vue. Essentially, it’s just a way to inject content into a component; it could have easily been called <content />, but they opted for <slot /> instead; IDK, it is confusing at times.

From the codesandbox example above, the main contents of the dialog (including the DatePicker) are in the App.vue component while the Dialog.vue component just wraps the provided content (denoted using <slot />) with additional structure relative to the component; in this case, a Kendo UI Dialog.

This allows us to effectively create a single Dialog component that has additional features (focus-trap for example) that can be reused across the codebase. It also allows the contents of the Dialog to live with the implementation which also provides context. If we didn’t follow this pattern, we’d have to implement a custom Dialog for each implementation thus duplicating a bunch of code.

If so, have you tried using the getShadowRoot tabbable option (use focus-trap’s tabbableOptions to specify it)?

No; not applicable

I figured out that at the moment when I attach a new DOM element (open date picker in your case) I deactivated the existing focus trap and activated a new, thus old focus trap returned focus to the element which activated the trap.

The issue here is that the DatePicker is a component nested inside a slot of a custom Dialog component (wrapper around Kendo UI’s); it has no reference of the parent Dialog where the focus trap is. Also, it would require event handlers to be placed on all Kendo UI components that do this, which adds tech debt and code parity issues.

So my solution was to add returnFocusOnDeactivate: false as option.

Unfortunately, due to keyboard power users, this is not an option. When the Dialog closes, the focus must be returned to the component (button/link) that opened it.

Can you provide a minimal working example

Sure! https://codesandbox.io/s/hardcore-joji-zm0ghh

Are the elements always added directly to the body (and so outside any possible trap containers) and positioned at the right place using CSS

Yes. By default, the Kendo UI DatePicker always attaches itself to the BODY element (as with many other similar components).

is it possible to tell KendoUI where to place them in the DOM (ie what their parent should be)? If the latter is possible, can you have it placed inside one of your trap’s containers?

Technically, it is possible via the popupSettings prop. However, the DatePicker cannot be inside the Dialog because it scrolls any overflow; thus hiding the DatePicker if it’s attached inside the dialog where the focus-trap is. This is true of many dynamic UI elements that require positioning its element near or close to the container of the dialog; this is why they’re attached to the BODY in the first place.