Skip to content

Commit

Permalink
Update with recent OpenUI discussion results (#593)
Browse files Browse the repository at this point in the history
* Add recent OpenUI discussion results

The largest change is the result of [this discussion](#581) about the differences between dialog and popup=manual. There's a new section attempting to describe those differences. This PR also includes recent changes to the `show` event, and an update on the interaction between top layer element types.

This PR also removes an obsolete section of the `<selectmenu>`
page that talks about replaceable shadow DOM.

* Address comment

* Address ::backdrop comment

* Address comments

Co-authored-by: Mason Freed <[email protected]>
  • Loading branch information
mfreed7 and mfreed7 authored Sep 8, 2022
1 parent ed40789 commit c61b228
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 87 deletions.
43 changes: 35 additions & 8 deletions research/src/pages/popup/popup.research.explainer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,31 @@ This proposal was discussed on [Issue 455](https://github.com/openui/open-ui/iss

There have been **many** discussions and resolutions at OpenUI of various aspects of this proposal, and those are listed in the [Design Decisions](#design-decisions) section.

## Pop-Up vs. Dialog

A natural question that [commonly arises](https://github.com/openui/open-ui/issues/581) is: what's the difference between a "pop-up" and a "dialog"? There are two ways to interpret this question:
1. What are the difference between these general UX patterns and words?
2. What are the technical implementation differences between `<div popup>` and `<dialog>`?

In both cases, an important distinction needs to be made between **modal** and **non-modal** (or **modeless**) dialogs. With a **modal** dialog, the rest of the page (outside the dialog) is rendered **`inert`**, so that only the contents of the dialog are interactable. Importantly, a pop-up is **non-modal**. Almost by definition, a pop-up is not permanent: it goes away (via light dismiss) when the user changes their attention to something else, by clicking or tabbing to something else. For these reasons, if the dialog in question needs to be **modal**, then the `<dialog>` element is the way to go.

Having said that, dialogs can also be **non-modal**, and pop-ups can be set to not light-dismiss (via **`popup=manual`**). There is a significant area of overlap between the two Web features. Some use cases that lie in this area of overlap include:

1. "Toasts" or asynchronous notifications, which stay onscreen until dismissed manually or via a timer.
2. Persistent UI, such as teaching-UI, that needs to stay on screen while the user interacts with the page.
3. Custom components that need to "manually" control pop-up behavior.

Given these use cases, it's important to call out the technical differences between a non-modal `<dialog>` and a `<div popup=manual>`:

- A `<div popup=manual>` is **in the top layer**, so it draws on top of other content. The same is not true for a non-modal `<dialog>`. This is likely the most impactful difference, as it tends to be difficult to ensure that a non-modal `<dialog>` is painted on top of other page content.
- A `<dialog>` element always has **`role=dialog`**, while the `popup` attribute can be applied to the **most-semantically-relevant HTML element**, including the `<dialog>` element itself: `<dialog popup>`.
- The pop-up API comes with some **"nice to have's"**:
- pop-ups are easy to animate both the show and hide transitions, via pure CSS. In contrast, JS is required in order to animate `dialog.close()`.
- pop-ups work with the invoking attributes (e.g. `popuptoggletarget`) to declaratively show/hide them with pure HTML. In contrast, JS is required to show/close a `<dialog>`.
- pop-ups fire both a "show" and a "hide" event. In contrast, a `<dialog>` only fires `cancel` and `close`, but no event is fired when it is shown.

For the above reasons, it seems clear that for use cases that need a **non-modal** dialog which has pop-up behavior, a `<dialog popup>` (with the most appropriate value for the `popup` attribute) should be preferred. Importantly, if the use case is **not** meant to be exposed as a "dialog", then another (non-`<dialog>`) element should be used with the `popup` attribute, or a generic `<div popup role=something>` should be used with the appropriate role added.

# API Shape

This section lays out the full details of this proposal. If you'd prefer, you can **[skip to the examples section](#example-use-cases) to see the code**.
Expand Down Expand Up @@ -265,8 +290,8 @@ The above CSS will result in all pop-ups fading in and out when they show/hide.

**`showPopUp()`:**

1. Move the pop-up to the top layer, and remove the UA `display:none` style.
2. Fire the `show` event, synchronously.
1. Fire the `show` event, synchronously. If the event is cancelled, stop here.
2. Move the pop-up to the top layer, and remove the UA `display:none` style.
3. Update style. (Transition initial style can be specified in this state.)
4. Set the `:open` pseudo class.
5. Update style. (Animations/transitions happen here.)
Expand Down Expand Up @@ -359,8 +384,7 @@ popUp.addEventListener('show',() => console.log('Pop-up is being shown!'));
popUp.addEventListener('hide',() => console.log('Pop-up is being hidden!'));
```

Neither of these events are cancellable, and both are fired synchronously.

The `show` event is cancellable, and doing so keeps the pop-up from being shown. The `hide` event is not cancellable, to avoid interfering with the one-at-a-time and light-dismiss behaviors. Both `show` and `hide` are fired synchronously.


## Focus Management
Expand Down Expand Up @@ -396,7 +420,7 @@ The intention of the above set of behaviors is to return focus to the previously
A new attribute, `anchor`, can be used on a pop-up element to refer to the pop-up's "anchor". The value of the attribute is a string idref corresponding to the `id` of another element (the "anchor element"). This anchoring relationship is used for two things:

1. Establish the provided anchor element as an [“ancestor”](#nearest-open-ancestral-pop-up) of this pop-up, for light-dismiss behaviors. In other words, the `anchor` attribute can be used to form nested pop-ups.
2. The referenced anchor element could be used by the [Anchor Positioning feature](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/CSSAnchoredPositioning/explainer.md).
2. The referenced anchor element could be used by the **Anchor Positioning feature** ([(dated) explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/CSSAnchoredPositioning/explainer.md); [more up-to-date draft spec](https://tabatkins.github.io/specs/css-anchor-position/)).


## Backdrop
Expand All @@ -412,7 +436,7 @@ Akin to modal `<dialog>` and fullscreen elements, pop-ups allow access to a `::b
</style>
```

Note that in contrast to the `::backdrop` pseudo element for modal dialogs and fullscreen elements, the `::backdrop` for a pop-up is styled by a UA stylesheet rule `pointer-events: none !important`, which means it cannot trap clicks outside the pop-up. This ensures the "click outside" light dismiss behavior continues to function.
Note that in contrast to the `::backdrop` pseudo element for modal dialogs and fullscreen elements, the `::backdrop` for a pop-up is styled by a UA stylesheet rule `pointer-events: none !important`, which means it cannot trap clicks outside the pop-up. This ensures the "click outside" light dismiss behavior continues to function. For this reason, and because `popup=manual` pop-ups do not have light-dismiss behavior, it is not recommended that the `::backdrop` be styled in a non-transparent way for `popup=manual` pop-ups. Doing so would obscure the rest of the page, while still allowing the user to click or keyboard navigate to obscured elements. In this case, a better approach might be to use a modal `<dialog>` element, which will trap focus within the `<dialog>`.

# Behaviors

Expand Down Expand Up @@ -525,7 +549,7 @@ As mentioned in the [Declarative Triggers](#declarative-triggers) section, acces

## Disallowed elements

While the pop-up API can be used on most elements, there are some limitations. For example, calling `showPopUp()` on a modal (via `.showModal()`) `<dialog>` element will result in an exception being thrown, as will calling it on an [active fullscreen element](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen). All other element types are valid.
While the pop-up API can be used on most elements, there are some limitations. It is legal to apply the `popup` attribute to a `<dialog>` element, for example. However, doing so causes `dialog.showModal()` (the `<dialog>` API to show it modally) to throw an exception. This is because it is confusing that an element with `popup` can be shown in a modal fashion. Similarly, calling `element.requestFullscreen()` on an element that has the `popup` attribute will return a rejected promise.


# Example Use Cases
Expand Down Expand Up @@ -685,7 +709,8 @@ Many small (and large!) behavior questions were answered via discussions at Open
- [Naming of the `:top-layer`/`:open` pseudo class](https://github.com/openui/open-ui/issues/470)
- [Support for "boolean-like" behavior for `popup` attribute](https://github.com/openui/open-ui/issues/533)
- [Returning focus to previously-focused element](https://github.com/openui/open-ui/issues/327)
- [The `show` and `hide` events should not be cancelable](https://github.com/openui/open-ui/issues/321)
- [The `show` and `hide` events should not be cancellable](https://github.com/openui/open-ui/issues/321)
- [The `show` event should be cancellable after all](https://github.com/openui/open-ui/issues/579)
- [The `popup` attribute confers behavior and not semantics](https://github.com/openui/open-ui/issues/495#issuecomment-1164827851)
- [Mouse down vs mouse up for light dismiss](https://github.com/openui/open-ui/issues/529)
- [Imperative API for content attributes](https://github.com/openui/open-ui/issues/382)
Expand All @@ -694,6 +719,8 @@ Many small (and large!) behavior questions were answered via discussions at Open
- [Show and hide animation behavior](https://github.com/openui/open-ui/issues/335)
- [`popuptoggletarget`, `popupshowtarget`, `popuphidetarget`](https://github.com/openui/open-ui/issues/382#issuecomment-1184773425)
- [Invoking attributes only supported on buttons](https://github.com/openui/open-ui/issues/420)
- [Differences between dialog and pop-up](https://github.com/openui/open-ui/issues/581)
- [Interactions between pop-up, dialog, and fullscreen](https://github.com/openui/open-ui/issues/520)

Here are all non-spec-text related OpenUI pop-up issues, both open and closed:

Expand Down
79 changes: 0 additions & 79 deletions research/src/pages/prototypes/selectmenu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -252,85 +252,6 @@ Using custom markup to wrap the list of options, the above example creates secti
alt="The rendering of a selectmenu control with the above HTML and CSS code"
/>

### Replacing the default shadow DOM

Another way to extend the control's markup is to replace its default shadow DOM altogether
by calling [`attachShadow()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow).
For example, the demo in the [previous section](#extending-the-markup) could be modified as follows:

```html
<selectmenu id="my-custom-select"></selectmenu>
<script>
const myCustomSelect = document.querySelector('#my-custom-select')
const shadow = myCustomSelect.attachShadow({ mode: 'closed' })
shadow.innerHTML = `
<style>
.button-container {
display: flex;
align-items: center;
gap: 1rem;
}
button {
border: none;
margin: 0;
padding: 0;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: grid;
place-content: center;
}
button::before {
content: '\\0025BC';
}
[popup] {
padding: 0;
}
.section {
padding: 1rem 0 0;
background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
}
h3 {
margin: 0 0 1rem 0;
text-align: center;
color: white;
}
option {
text-align: center;
padding: 0.5rem;
}
option:hover {
background-color: lightgrey;
}
</style>
<div class="button-container">
<span class="label">Choose a plant</span>
<span behavior="selected-value" slot="selected-value"></span>
<button behavior="button"></button>
</div>
<div popup behavior="listbox">
<div class="section">
<h3>Flowers</h3>
<option>Rose</option>
<option>Lily</option>
<option>Orchid</option>
<option>Tulip</option>
</div>
<div class="section">
<h3>Trees</h3>
<option>Weeping willow</option>
<option>Dragon tree</option>
<option>Giant sequoia</option>
</div>
</div>
`
</script>
```

Written this way, the `<selectmenu>`'s custom markup is fully encapsulated in its shadow DOM. The
`<selectmenu>` can therefore be dropped into any page without risk of interference from the
surrounding content's styles.

## Examples

You can find multiple examples of `<selectmenu>` on our [demo page](https://microsoftedge.github.io/Demos/selectmenu/).

0 comments on commit c61b228

Please sign in to comment.