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

Add inert attribute during Leave transitions #661

Closed
jods4 opened this issue Jan 26, 2020 · 10 comments
Closed

Add inert attribute during Leave transitions #661

jods4 opened this issue Jan 26, 2020 · 10 comments

Comments

@jods4
Copy link
Contributor

jods4 commented Jan 26, 2020

What problem does this feature solve?

Leave transitions can be a source of bugs, since developers usually consider that a removed element is not in the DOM anymore.

Consider a popup to make a purchase.
When the user click on submit, the code performs something like showPopup = false and through the magic of bindings the modal is removed from DOM.
So after that point it's impossible to submit again and that's how devs usually think.

Now here comes the designer who says: "That popup disappearing is not pleasing, let's add a transition like fading it out."

It looks great but now my grandma is using the app. You need to know that my grandma double-clicks on everything (true story).
So she double-clicks the submit button.
The purchase is made, the modal is "closed"... but not really because now it has that nice fade-out transition.
It is likely that the second click will hit the submit button again, which the dev didn't expect, and maybe a second purchase is made.

If you use lots of transitions, it's hard and tedious to protect every interactive element from being used during a Leave transition.

A nice and easy solution is the inert attribute (supported in some modern browsers).
Since the element is supposed to be removed from DOM, adding inert is the logical thing to do:

  • It prevents all user interactions;
  • It hides the element from assistive technologies;
  • It doesn't change its visual appearance.

What does the proposed API look like?

There is no new API surface.

I propose that when a Leave transition starts, the inert attribute is added to the element that is leaving the DOM; and when the transition finishes, inert is removed (at least if it wasn't already there when the transition started).

@posva
Copy link
Member

posva commented Jan 27, 2020

Applying the inert attribute might be the desired behavior for a modal in the scenario you specified, but it's not well supported (https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) which means we cannot always rely on it.

I checked against current Vue 2 version, and the behavior is consistent regarding event listeners still being able to trigger during the transition.
While I think disabling the content that is being transitioned out is a user responsibility because enforcing a behavior that makes sense in many (if not most) scenarios is still something you may want to opt-out and reflects how the DOM works, I don't think there is an easy way to know if an element is transitioning out in the template. What makes this more difficult is that the element does not render once it's being transitioned out, so disabling it would in most scenarios need a two step action that first disables the buttons, waits a tick, then toggles off its visibility, triggering its transition.
It is currently possible for elements like modals to wrap that behavior so it's still easy to use.

@jods4
Copy link
Contributor Author

jods4 commented Jan 27, 2020

Applying the inert attribute might be the desired behavior for a modal

The modal was an example. It's pretty much desirable on every element leaving DOM.

If you have a list with a button (delete, edit, doesn't matter) on every row, you don't want the command to trigger when an item is animated out.

Removed element can still receive focus. If you're tabbing into an element leaving the DOM, you then lose focus completely, not great.

This leads me to:

enforcing a behavior that makes sense in many (if not most) scenarios is still something you may want to opt-out

I thought about it and choose not to mention it because I can't find a single scenario where it would be desirable to opt-out.

That said, you could have an opt-out flag on <Transition> and <TransitionGroup>.

but it's not well supported

Sadly it's true that it's not supported everywhere, at least yet.
But it's still better to have it when you can than never.
Sometimes (e.g. in enterprise) you know exactly what browsers you need to support and you might be ok.

I think disabling the content that is being transitioned out is a user responsibility

  • It can't be set in CSS, so you're forcing everyone who wants this (a good thing) to use JS callbacks just for that.
  • It's pretty much useful every time you animate an element, so including it by default removes boilerplate code.
  • Most devs don't even know about inert, you're helping them out by doing what's right.

I don't think there is an easy way to know if an element is transitioning out in the template. What makes this more difficult is that the element does not render once it's being transitioned out, so disabling it would in most scenarios need a two step action that first disables the buttons, waits a tick, then toggles off its visibility, triggering its transition.

Maybe I'm too ignorant but can't you just add it during onLeave, here:
https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/src/components/Transition.ts#L139
And remove it in leaveFinished?

Why is it different than setting the css classes on target element?

@posva
Copy link
Member

posva commented Jan 27, 2020

It's pretty much desirable on every element leaving DOM
...
It's pretty much useful every time you animate an element, so including it by default removes boilerplate code.

But it's not every time, that's the problem. Also, it would be easy if inert was a silver bullet but it isn't. I agree it would be very nice to toggle that attribute addition via a Transition prop and I would use it but you need a polyfill to make it work with anything but recent Chrome. That's a deal breaker.

Maybe I'm too ignorant but can't you just add it during onLeave, here:
https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/src/components/Transition.ts#L139
And remove it in leaveFinished?

I was referring to user code, not modifying in Vue Core. If you are referring to manually adding inert to the HTML element, yes you are right, you add it there, you don't even need to remove it in leaveFinished. I was thinking more of dealing with other template changes that would require more work than one single attribute, which is doable manually with DOM manipulation

@jods4
Copy link
Contributor Author

jods4 commented Jan 27, 2020

I can't think of a scenario where it's not the right thing (can you?), but you can add a prop noInert just in case.

Chrome/Edge/Opera support is sometimes enough. It's better than nothing. A polyfill exists but is far from trivial.

I'm looking at creating a derived component that adds this in onLeave before calling Transition.

It's easy but:

  • onLeave is called on next frame. Unlikely to cause problems but not perfect.
  • My own controls and components will use my Transition but a lot of libraries out there that I might use, use the built-in one and don't really care about this problem.
  • Could you clarify why you don't need to remove it in leaveFinished? Aren't DOM elements reused, e.g. in a recycling list?

@Justineo
Copy link
Member

You can enforce pointer-events: none on leaving in userland. This at least prevents user interactions from pointer devices.

@jods4
Copy link
Contributor Author

jods4 commented Jan 28, 2020

@Justineo Yes, it's probably the most important bit of inert in this context (for double-clicks and such).
inert also prevents keyboard interaction and removes the contents from assistive technology (the latter is not very important in the case of leave transition, as the element would get removed soon enough).

Another situation where this arises is router transitions.

@DinsmoreDesign
Copy link

Why not just disable the form elements after the first click...?

@jods4
Copy link
Contributor Author

jods4 commented Jan 28, 2020

@DinsmoreDesign
If the button stays on screen during a long running operation (e.g. a server call) I think you absolutely should update the UI (disable the button, show a spinner, etc.).

In the context of elements being removed from DOM, it's not a good solution.

Let's take the router transitioning to another page:
There might be lots of controls on the page you're leaving and obviously at this point you don't want to interact with them anymore. Technically the state of the app is the new page.

  • You must disable all controls manually, somehow. It's tedious. Putting disabled on the top <div> is not gonna disable everything it contains, sadly.
  • Disabled state is depicted visually (normally greyed out). In this case you don't want that because it creates an unwanted flicker/change of state in the screen leaving the screen.
  • Events will still fire. You can't "disable" a link in your app with a click handler. That would be better achieved pointer-events: none.

Devs think of removing an element from DOM as synchronous, so they see no need to disable it, unlike a long running op.

Obviously it's not trivial and it's something that you want to handle in a generic way, at the transition level. Not page by page.

@jods4
Copy link
Contributor Author

jods4 commented Mar 2, 2020

I understand why you don't want to add code that is not supported by all browsers at this point (68% according to caniuse https://caniuse.com/#feat=mdn-api_htmlelement_inert) and there is a workaround: I can use a derived <transition> component that adds inert.
So I'm closing the issue.

@jods4 jods4 closed this as completed Mar 2, 2020
@jakearchibald
Copy link

Should this be reconsidered now the browser support of inert is much-improved (86.73%), as it's now supported in all engines?

Yes, it's really easy to add this yourself, but I'm not sure many developers will realise they should do this in almost all cases.

Adding an opt-in to this behaviour on <Transition> will promote its usage.

It would be even better as an opt-out, but that might be a breaking change.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants