headlessui
headlessui copied to clipboard
[Vue] - Tabs not working properly when conditionally updating the `selected-index` prop from the `change` event
What package within Headless UI are you using?
@headlessui/vue
What version of that package are you using?
v1.7.1
What browser are you using?
Chrome Latest
Reproduction URL
https://codesandbox.io/s/awesome-thompson-ytuh1o?file=/src/App.vue
Describe your issue
I have forms within TabPanel and I want to implement a "Discard changes" confirmation before switching to another tab, if there are unsaved changes.
I tried implementing this with a custom modal and the built in one, but it doesn't work properly.
When I press Cancel, instead of getting back to the browser, the click event triggers again and again and it enters a permanent loop.
What I'm trying to use is a controlled component, I'm using the change event to conditionally determine whether or not the user should move to the new tab.
Hey! Thank you for your bug report! Much appreciated! 🙏
This is an interesting "bug". The issue is not that there is some infinite loop, however the issue is the moment you press cancel, the browser will focus the element you last clicked again. The tabs are implemented in a way so that the moment you focus a tab, that this tab will become the selected one and an onChange will be fired.
The tricky part is that there is no proper way of solving this in Headless UI itself without making assumptions. We can't just focus "nothing" or focus the first tab because that will also trigger the onChange. We can't also change the behaviour of the confirm method (I don't think at least) to not focus the button you clicked on cancel.
One thing you can do is if you implement it with a custom dialog where you have full control, then you can choose what to do when you cancel (e.g.: focus the field that is not filled in yet or something).
Was your goal to use window.confirm or was this as an initial test and you ran into this roadblock?
Either way, do you think?
I see. Kinda tired today, gonna see if I have some time to look at the source code tomorrow and come up with a proposition.
I tried using a custom modal, but it restores focus to the last element, so it causes the same problem.
Why do you switch tabs on focus?
If I remember correctly the proper ways to implement tabs is to have them switch on left/right and mouse clicks, or have them switch on enter/space when the buttons are focusable.
If following these guidelines, the button receiving focus, but the parent not updating the model value should not be an issue
See here
https://www.w3.org/WAI/ARIA/apg/example-index/tabs/tabs-automatic
This one works similarly to what you have implemented in the default mode, it works on left/right and clicks. You can, however, simulate a focus by clicking on the tab, the dragging your mouse outside of the button and release the click. This causes the button to receive focus, but it doesn't switch the open tab.
Edit: just noticed that they actually use negative tabindex on the inactive buttons so you can't focus them, knew I should wait until tomorrow before replying ☹️
Anyway, let me know what you were thinking about when designing the keyboard navigation and we'll see if we can figure something out.
I just ran into this on the React side of things and the best way I found to get it working was to use the manual option for the Tab.Group. This seems to work in your Vue example even though it leaves focus on the wrong tab in the end. That seems like an easier problem to solve though..
Yeah, this is how I ended up solving this. I'm still not sure why the tab tries to switch on focus
Hey!
I reworked the internals of the Tab component a bit so that it only changes tabs when you use your arrow keys or when you actually click it. Not when you are just "focusing" it. This was an (incorrect) implementation detail that should now be fixed.
This should be fixed by #1887, and will be available in the next release.
You can already try it using:
npm install @headlessui/react@insiders.npm install @headlessui/vue@insiders.