spectrum-web-components icon indicating copy to clipboard operation
spectrum-web-components copied to clipboard

[Feat]: Abstract Overlay API Review

Open Westbrook opened this issue 2 years ago • 5 comments

Code of conduct

  • [X] I agree to follow this project's code of conduct.

Impacted component(s)

overlay

Description of the requested feature

openOverlay currently accepts:

{
    target: HTMLElement,
    interaction:
      | 'click'
      | 'longpress'
      | 'hover'
      | 'custom'
      | 'replace'
      | 'inline'
      | 'modal',
    content: HTMLElement,
    options: {
      delayed?: boolean;
      placement?: Placement;
      offset?: number;
      receivesFocus?: 'auto';
      notImmediatelyClosable?: boolean;
      abortPromise?: Promise<boolean>;
      virtualTrigger?: VirtualTrigger;
    }
}

A content element managed for overlay looks like:

interface ManagedOverlayContent {
    open: boolean;
    overlayWillOpenCallback?: (args: { trigger: HTMLElement }) => void;
    overlayOpenCallback?: (args: { trigger: HTMLElement }) => void;
    overlayOpenCancelledCallback?: (args: { trigger: HTMLElement }) => void;
    overlayCloseCallback?: (args: { trigger: HTMLElement }) => void;
}

Important question

If we created a new API could we smooth transition by wrapping it with the old API for the initial term?

Things we might want instead:

content is required but trigger is not

  • points to a usage of the "Active Stack" concept where the "trigger" was the previous "active element" and that content would manage things like theme, focus return, etc.

tabOrder: 'none' | 'inline' | 'replace' | 'trap'

  • 'none' = tooltip and similar that are given aria-* based content relations
  • 'inline' = before trigger <=> trigger <=> overlay <=> after overlay
  • 'replace' = before trigger <=> trigger when closed, overlay when open <=> after overlay
  • 'trap' = modal This should absolve the need for receivedFocus as all overlays need focus for the screen reader to step into them when aria-* attributes are not managing the content relations.

interaction:

  • likely the only interaction not managed by tabOrder is longpress which really only manages the first click event.

backdrop management:

  • not all "modals" have a backdrop and we need to manage it somehow
  • it may be that backdrops need to be handled 100% of the time by the overlaid content and not by the overlay system

stack management:

  • overlays need access to the stack to prevent DOM based state resolution
  • adding/removing something from the stack should be synchronous
  • any new non-tabOrder='none' content should dismiss all preceeding tabOrder='none' content on the stack

overlay management:

  • (on or off the stack) should manage their own asyncrony
  • lifecycle should be both explicit and easy to apply

Does abortPromise belong on the overlay lifecycle? Does making the stack syncronous prevent needing to maintain this code path.

Configurable and re-configurable overlays:

  • elements like sp-picker should be able to surface more options around the overlay they throw, see coment and issue
  • overlays thrown in this way should be "updatable", for instance when going between mobile and desktop so that the wrapper (Tray vs Popover) and placement (specific vs 'none') can be swapped

Westbrook avatar Feb 01 '22 16:02 Westbrook

Ref: https://open-ui.org/components/popup.research.explainer

Westbrook avatar Mar 21 '22 01:03 Westbrook

What do you think of two ergonomic overlay ideas?

  1. Optionally encapsulating overlays by providing a projection surface (and using body, as today, if none is provided)
  2. Exposing a 'controlled' overlay container that declaratively projects, or not, based on a boolean (vs the imperative Overlay.open() or the less-flexible <overlay-trigger>)

For example:

<foo-app>
  <bar-context>
    <overlay-surface>
      <!-- ... -->
        <overlay-projector ?open=${this.selectedFile == null}>
          <choose-file-dialog></choose-file-dialog>
        </overlay-projector>
      <!-- ... -->
      <active-overlay>
         <!-- choose-file-dialog is reparented here when open -->
      </active-overlay>
    </overlay-surface>
  </bar-context>
</foo-app>
<!-- today, this is where choose-file-dialog would be reparented -->

Motivations:

  • more user control over where content is displayed / how it's stacked
  • better-encapsulated in the "app as a component" paradigm. For instance, two app components could coexist side-by-side on the same page, with neither of their overlay systems conflicting with the other.
  • optional. Users who don't want or need this do nothing, and the overlay system works as it does now.
  • eliminates the need to clone context-like wrappers (like sp-theme). Currently, anything that depends on DOM structure (CSS vars, event bubbling) must be duplicated into the body-level active-overlay.

hunterloftis avatar May 24 '22 17:05 hunterloftis

Re: #2, funny that you ask. We have been looking into an <sp-modal> element that works more like a self managed <sp-tooltip> that might align with that: https://modal--spectrum-web-components.netlify.app/storybook/?path=/story/modal--default

Westbrook avatar May 24 '22 17:05 Westbrook

That looks exactly like the API I was hoping for re: 2! 🔥

hunterloftis avatar May 25 '22 14:05 hunterloftis

Copying from #1783

Description of issue

Clarify usage documentation and/or API surface of the Overlay API around the support of multiple "overlay roots". This comes into play when two sibling overlay triggers exist and one of the triggers have a complex trail of overlays open without using type="modal" to occlude interaction with the second sibling. How should the library/a user manage interactions with the second trigger?

Context:

  • visit https://webcomponents.dev/edit/d7PlCqV4TsBtHJWbRdqy/src/index.ts
  • open the "Complex Popover"
  • open the Picker in that popover
  • attempt to open the "Second Popover"

Questions:

  • should this close the first popover?
    • always
    • sometimes
    • never
  • Complex Popover[type="modal"] prevents interaction with "Second Popover" until closed, should this be the suggested usage?
    • modal traps tab
    • modal currently passes contextmenu interaction to the page once the popover is closed, should more things do that?
  • The stack:
    • The overlay system is currently a single stack where we generally push and pop the last item, should there be there be a deeper concept of "groups" or similar in the stack that close all at once?
    • Should "groups" be a usage location concept that a developer needs to manage in some way themselves?
    • This come to bear in the concept of sub/flyout menus, as well:
      • Visit https://sub-menu--spectrum-web-components.netlify.app/storybook/?path=/story/menu--submenu
      • use the pointer to expand multiple levels of sub menu
      • click and option in the deepest submenu
      • see all sub menus close
      • is this a "group"?

Westbrook avatar Jun 23 '22 01:06 Westbrook

It would be great if there was an SWC counterpart to what Angular Material Overlay is doing with scroll strategies: https://material.angular.io/cdk/overlay/overview#scroll-strategies

  • @nickschaap

Westbrook avatar Oct 06 '22 19:10 Westbrook

Consider this when rewriting the API. https://github.com/adobe/spectrum-web-components/issues/1831

najikahalsema avatar Oct 26 '22 18:10 najikahalsema