-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Should popover/dialog show/hide when already shown/hidden throw? #9045
Comments
I’m inclined to agree - at least with nooping. Guarding these calls is a little unergonomic as iirc the only way to check if a popover is open is via I wonder if it makes sense to alias dialogs show/hide to showPopover/hidePopover? Since it enacts the same mechanics iirc? |
The algorithms are pretty different, so I don't think that's possible. |
This sounds good to me, I wonder why show() isn't doing that. I'm also more supportive of modifying show() than showModal().
This sounds good to me, especially if thats what requestFullscreen does. I don't think that popover on dialog elements is a use case that makes any sense.
But calling dialog.showModal() a second time also throws an exception, I don't see why we can't do the same thing. If a majority of some APIs from the DOM spec don't throw exceptions when they could, I don't find that very motivating.
I guess that's a good point. If yall really want to make it noop then I won't stop you.
Yeah I don't think this is a good idea since we are defining showPopover/hidePopover on all HTML elements regardless of the presence of the popover attribute. If we renamed/aliased them to show/hide, then every element would have a show and hide method even if it isn't a popover which would be confusing. It would even conflict with dialog's show method. |
Can you provide some detail around why you're happy with that option, given the inconsistency with most of the web platform? |
I more meant that for |
showModal() serves more of a purpose than show(), and there is even a consideration to deprecate/remove show(): #7707 (comment) I found the dialog element APIs more motivating than DOM APIs. How about looking at some more HTML APIs?
I couldn't think of any other examples.
Ah, I missed this detail that requestFullscreen does not throw when the element is already fullscreen. I guess it makes sense to make them not throw. It sounds like you're in favor of making dialog.show(), dialog.showModal(), dialog.close(), element.showPopover(), and element.hidePopover() never throw based on whether they are already open or closed? |
Sorry I misinterpreted. So dialog.showPopover() would be the same as dialog.show()? Or dialog.showModal()...? It still sounds funny since dialogs are not popovers. Would the dialog element need the popover attribute? Would the presence of the popover attribute affect what dialog.showPopover() does? |
We shouldn't avoid doing the right thing because it's more work. And we shouldn't assume that
Absolutely, since that's how most of the platform works. There are multiple options for how to do that though (options 2-4). |
This raises another question which I don't think @jakearchibald's OP covers. Right now |
Thanks for raising the detailed issue here, @jakearchibald. (Side note for others, this was briefly discussed on #9036.) Ok, given the good case you've made for consistency with the rest of the platform, I vote for these choices:
In terms of the current spec, I believe the only needed changes would be:
The behaviors for |
These throw |
I don't think that upgrading to a modal dialog is behavior that makes a lot of sense, this feels like it should be an exception. It also would be jarring visually due to modal/non-modal dialogs having different styles. Modal are fixed positioned, non-modal dialogs are absolute positioned, so if you've scrolled in the viewport to a point until the dialog is out of view, Inversely downgrading also doesn't make sense either, due to the same opposite result.
I slightly favor this option for its predictability and consistency with fullscreen, although that means we won't deprecate |
Surely the regular behaviour of Would you want it to throw only in the show then showModal case, or in all show then show cases? |
I do disagree with this one. We discussed this at some length in OpenUI (mostly here, and somewhat here), and there is a very definite use case for I also don't quite understand how this particular case is confusing to developers:
|
Because it's hard to tell if an element is an active popover? It feels like detecting that isn't a first class part of the API. |
For a dialog-popover, I suppose you need to do
|
Right, you have to go via CSS. |
We discussed adding some helpful JS sugar for that state, here. The conclusion was that we could add that, if it was helpful. But I guess the point is that you said "it's hard to tell", and I think the answer is "no, it's easy, just use matches()". |
I guess it's a separate discussion, but As API designers we should be willing to tackle complexity ourselves, rather than pass it on to every developer that has to interact with our work. |
Fair point. I don't actually think this was complex to add, it was just that the developers in the room didn't think it was something that needed a ton of attention. But if you feel differently, I'm certainly happy to propose a quick addition: partial interface HTMLElement : Element {
readonly attribute boolean popoverOpen;
};
I suppose if you follow this line of thought, you'd also want to propose an addition to the |
The design of an equivalent for dialog would likely depend on the outcome of this issue. But hopefully these issues demonstrate that I'm not against improving dialog too. |
Splitting the |
For the call later today, the original post still serves as a summary. |
Open UI resolutions:
Minutes: https://www.w3.org/2023/03/23-openui-minutes.html#t03 |
One use-case that I had for "pop to top behaviour" in my past practice — multiple tooltips. The most recent tooltip would always want to be “on top”, and I had situations, where we wanted to have a delay before the tooltip is hidden (a transition, for example), so the following situation could be possible:
Implementation-wise, just telling the tooltip to show and get it on top would be the easiest, and probably the expected behavior. While this could be theoretically implemented in the future with plain CSS, using a display transition, making the tooltip be “hidden” immediately, I can see cases where developers would still want some reason use JS and keep the tooltip open, and I can also imagine cases for popovers, where we could want to keep multiple of them open, and switching between them when necessary. |
So we're going to start discussing
@josepharhar would it make sense to put up a PR with the above changes, and then we can discuss the details? |
This patch makes the following changes: - Don't throw exceptions when showPopover or hidePopover is called and the popover is already in the requested state. - Don't throw exceptions when dialog.showModal, dialog.show, or dialog.close is called and the dialog is already in the requested state. - Throw exceptions when trying to switch between modal and non-modal dialog modes via dialog.showModal or dialog.show. Fixes whatwg#9045
Here is a PR: #9142
Switching between modal and non-modal does not currently throw. My PR adds new exceptions to make it throw. |
This patch makes the following changes: - Don't throw exceptions when showPopover or hidePopover is called and the popover is already in the requested state. - Don't throw exceptions when dialog.showModal, dialog.show, or dialog.close is called and the dialog is already in the requested state. - Throw exceptions when trying to switch between modal and non-modal dialog modes via dialog.showModal or dialog.show. Fixes #9045.
#9142 as merged doesn't throw when hidePopover is called on a disconnected element, which @mfreed7 I think still wants with rationale here: #9142 (comment) Should the following call to hidePopover throw an exception? const popover = document.createElement('div');
popover.setAttribute('popover', 'auto');
// don't connect to document
popover.hidePopover(); |
I think it might be more convenient for library users that we don't throw in this case? @jakearchibald is probably in a better position to answer though |
Help me understand. When should we throw an exception? My concept of exceptions is that they're for exceptional situations, where the API can't do what you've asked it to do. It seems harder to code around an API that will just silently (or with a console warning) do nothing when the preconditions aren't met. |
In this case, the API can hide a popover that is already closed, doesn't matter if it's disconnected. You could also argue that the API can't hide a popover that is already closed, so I'm not sure this reasoning really works well here. Since this issue was about thinking about developer ergonomics, I'm just pointing out there's a possibility that library users might call To be clear, I don't really have a strong opinion about this, I would rather leave this to someone who knows about how libraries are used out there to determine what is the best thing to do in this case. |
Ahh - this helped my understanding. Thanks. I thought you wanted both Sorry about that - I'm ok with that change. I believe Chromium currently throws, but I'll change that. Unless @josepharhar already has that in progress? |
Closing this issue as I don't think there's anything else to do here? #9142 wrote this in a way that hiding disconnected popovers that were already hidden doesn't throw. @josepharhar @mfreed7 Can you please double check and re-open the issue if needed? |
Current state
close()
on a closed<dialog>
.show()
on an open<dialog>
, unless that dialog is in the popover showing state.…do not throw.
Whereas:
showModal()
on an open dialogshow()
on an open dialog that is also in the popover showing state.showPopover()
on an open popoverhidePopover()
on a closed popover…throw an error.
So the current state of things is that dialog is inconsistent with itself, and popover is inconsistent with dialog.
Elsewhere on the platform
Looking at other cases of hide/remove/delete in the web platform, none of these throw:
new Set().delete('foo')
('hello').replace('world', '')
[].pop()
document.createElement('div').removeAttribute('foo')
document.createElement('div').remove()
document.createElement('div').classList.remove('foo')
document.createElement('div').removeEventListener(() => {})
document.createElement('div').classList.replace('foo', 'bar')
(although it does returnfalse
)Counter examples:
el.removeChild(otherEl)
throws ifotherEl
is not a child ofel
.el.removeAttributeNode(attributeNode)
throws ifattributeNode
is not an attribute node ofel
.document.exitFullscreen()
throws if no element is fullscreen.removeChild
andremoveAttributeNode
are pretty old APIs that developers tend to avoid in favour of friendlier equivalents.exitFullscreen
is the most interesting example, since it's related to top level.Looking at 'add' cases:
new Set(['foo', 'bar']).add('foo')
el.classList.add('foo')
el.addEventListener(callback)
All behave set-like. As in, if the item is already in the set, it doesn't throw, and it doesn't change the order of items. It's a no-op.
el.requestFullscreen(options)
on an already-fullscreen element will adapt to changes inoptions
, but it will not fire afullscreenchange
event.el.append(otherEl)
will removeotherEl
from its parent, and addotherEl
as the last child ofel
. This happens even ifotherEl
is already the last child ofel
, and it's observable in a bunch of ways, including mutation observers. But this is specifically 'append', not 'add'.The current mindset in the frameworks world is to let developer declare the state they want, and the framework figures out what needs to change to get to that state. None of the frameworks throw if the developer re-declares the current state.
Proposal for
<dialog>
It's pretty weird that
show()
followed byshowModal()
will throw, whereasshowModal()
followed byshow()
will no-op.Option 1: 'show' throws if already shown
show()
orshowModal()
followed byshow()
orshowModal()
will throw.This is at least consistent between the two methods, but it doesn't seem to fit with the majority of the platform. You could say the behaviour here is justified in being unusual due to the different ways a dialog can be shown, but that wouldn't be consistent with
requestFullscreen(options)
either.Option 2: 'show' is a no-op if already shown
With
show()
orshowModal()
followed byshow()
orshowModal()
, the second call will be a no-op.This is at least consistent between the two methods, but it seems weird that
showModal()
would no-op resulting in a not-modal dialog.Option 3: Update the type of 'show'
show()
followed byshowModal()
will 'upgrade' the dialog to a modal dialog.showModal()
followed byshow()
will 'downgrade' the dialog to a non-modal dialog.This feels consistent with how
requestFullscreen(options)
will react to changes inoptions
even if the element is already fullscreen.Option 4:
showModal()
is a more specific version ofshow()
show()
followed byshowModal()
will 'upgrade' the dialog to a modal dialog.showModal()
followed byshow()
is a no-op, because the dialog is already shown.This seems less flexible, but it feels like it makes sense. We could add
showModeless()
in future to do the more specific thing, or add an option likeshow({ mode: 'modeless' })
.Proposal for popover
I think we should match the majority of the platform, and open-when-already-open, and close-when-already-closed, should not throw.
Option 1: Second
showPopover()
moves popover to the topThis could be achieved by hiding then re-showing the popover, which would be like
el.append()
. If the developer is callingshowPopover()
, it seems like they feel this popover is important at this time, which suggests move-to-top is the right thing to do.If this makes sense,
showModal
on<dialog>
should do the same.Option 2: Second
showPopover()
is a no-opThat's consistent with most of the platform.
Proposal for popover on
<dialog>
Option 1:
<dialog>
cannot popoverSimilar to
requestFullscreen
, callingshowPopover()
on a<dialog>
will always throw, even if the dialog isn't open. That removes the overlap between these two features, and it's consistent with fullscreen.Option 2: Avoid both features being active at the same time
show()
is not a no-op, then it should throw if the element is in the popover showing state.showModal()
is not a no-op, then it should throw if the element is in the popover showing state.showPopover()
is not a no-op, then it should throw if the element is a dialog, and open.I'm not sure this complexity is worth it, and it's inconsistent with
requestFullscreen
.The text was updated successfully, but these errors were encountered: