WICG / close-watcher

A web API proposal for watching for close requests (e.g. Esc, Android back button, ...)
https://html.spec.whatwg.org/multipage/interaction.html#close-requests-and-close-watchers
71 stars 5 forks source link

Support out-of-bounds clicks as a close signal #24

Open tbondwilkinson opened 2 years ago

tbondwilkinson commented 2 years ago

When thinking about the new Popup API proposal, the close watcher currently supports a number of the close signals for a popup including esc and back button on certain UAs.

One of the new close signals however is clicking or tapping outside of the bounds of the popup.

Clicking or tapping outside the bounds of the pop-up. Any mousedown event will trigger all open pop-ups to be hidden, starting from the top of the pop-up stack, and ending with the nearest open ancestral pop-up of the mousedown event's target Node. This means clicking on a pop-up or its trigger or anchor elements will not hide that pop-up.

I was thinking that it would be good if any new behavior provided automatically by the browser could be encapsulated in APIs that would allow component developers who could not use the popup API for one reason or another to still take advantage of the dismiss behavior.

In the explainer there is the example of the sidebar click, which adds an event listener and then calls into the close watcher directly.

I wonder whether we can have an automatic option instead. So that example would become:

const hamburgerMenuButton = document.querySelector('#hamburger-menu-button');
const sidebar = document.querySelector('#sidebar');

hamburgerMenuButton.addEventListener('click', () => {
  const watcher = new CloseWatcher({element: sidebar, outOfBoundsClick: true});

  sidebar.animate([{ transform: 'translateX(-200px)' }, { transform: 'translateX(0)' }]);

  watcher.onclose = () => {
    sidebar.animate([{ transform: 'translateX(0)' }, { transform: 'translateX(-200px)' }]);
  };
domenic commented 2 years ago

This and #24 are similar to what was mentioned #1, in that they're basically proposing adding sugar for certain things that people might want to use CloseWatcher with. My preference is to keep CloseWatcher as a primitive, not tied to specific UI patterns.

We could contemplate adding a new API on top of CloseWatcher, e.g. createCloseWatcherFor(element, { outOfBoundsClick: true, blur: true }). But it doesn't feel like it'd be part of the base close watcher primitive; it'd just be us providing a way to save a few lines of code.

tbondwilkinson commented 2 years ago

That's fair, let me make a slightly clearer pitch.

I think the benefit of these two features in particular is that they allow for better element scoping. Meaning - in an ideal scenario, script considerations should scope to a specific element. Commonly in UI frameworks, you specify JS that attaches to a specific portion of the DOM as a "component." If you have to listen to clicks outside your element or have to pay attention to focus passing outside of an element (though, focusout bubbles, so that's not so bad), it often means that you now have dependencies on portions of the page outside your element. That can lead to complexity and subtle breakages - now, if a different part of the page does something like swallows a click event you might miss it.

Versus, if you can specify behavior that the browser can take regardless of what's happening with click events or focus, it simplifies your model and makes it easier not to make mistakes.

domenic commented 2 years ago

Yeah, I'm sympathetic to the idea that we should build more APIs, separate from CloseWatcher itself, that make it easy to create CloseWatchers tied to a specific element. It's less clear to me whether we should do that immediately, or whether we should wait to see what types of libraries people create using CloseWatcher and only then add such functionality to the platform.

tbondwilkinson commented 2 years ago

I wonder, should we be thinking about specifying a generic "closable" like thing?

I think the reason I'm trying to get ahead of this is that Popup API is a closable-like thing, and because it's going to be browser native, it means that it sets strong precedent for how other closable-like things should behave.

CloseWatcher is an important part of that - it gives the browser an important way to signal "the user is probably asking for a close." But if you're actually building a UI component, it's only one piece of the puzzle. I'm concerned that Popup API makes too big of a gap between things that are popups that could now take advantage of this API, and everything else that can't. And I'm particularly concerned about the differences between Popup API and <dialog>, which is also "closable" but feels far less full-featured than what Popup is proposing.

domenic commented 2 years ago

I'm not sure. My general feeling is that there are low-level browser primitives, like close signals or (for example) session history, and it is good to give you APIs which have full access to these. (Like CloseWatcher and the navigation API.)

Separately, there are high-level UI patterns like popups or in-page back buttons, and we can consider making it easier to build those with dedicated HTML-only elements or similar.

I'm not sure where the idea of "closeable thing" fits in there. IMO anything that uses a CloseWatcher, including a high-level thing like popup="" or <dialog> or <select> which uses it under the hood, is an example of a closeable thing. But I don't know if there's much design work to do on them in a shared way.

tbondwilkinson commented 2 years ago

I just worry that the gap between things built on top of low-level browser primitives and things that use dedicated HTML-only elements is potentially large.

If people start building on top of dedicated HTML-only elements and then realize their requirements fall outside the design constraints, the step down to low-level primitives could be large. That might mean that the web platform is going to be fielding a lot of requests for expanding the scope of those dedicated HTML elements or frameworks will choose to never use those HTML elements and instead build heavy JS and HTML solutions on top of the low-level browser primitives to more precisely satisfy their requirements, and those high-level HML-only elements will see little usage.

The question I'm asking is "how easy is it for me to build popup="" from scratch with existing primitives?" See the dialog polyfill which is 800 lines of code. I haven't seen the popup API polyfill yet, but if it's similarly sized, that's kind of a representation of the value that the API is providing, but only to use cases that fit the constraints.

I'm basically wondering whether we can build medium APIs, between primitives and high-level components.

tbondwilkinson commented 2 years ago

But... maybe this is a discussion for another group. :) If the vision for CloseWatcher is primitive, then you're right, these considerations don't apply