Skip to content
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

Reference Target #10707

Open
dandclark opened this issue Oct 16, 2024 · 8 comments
Open

Reference Target #10707

dandclark opened this issue Oct 16, 2024 · 8 comments
Labels
accessibility Affects accessibility addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest stage: 1 Incubation topic: shadow Relates to shadow trees (as defined in DOM)

Comments

@dandclark
Copy link
Contributor

What problem are you trying to solve?

Element-to-element relationships that are specified based on element IDs, like many of the aria- attributes, run into problems when they need to be created between elements on the opposite sides of a shadow DOM boundary.

The ARIAMixin IDL attributes (such as ariaLabelledbyElements and ariaActiveDescendantElement) solve this problem in one direction, allowing references to be created from inside a shadow DOM to an element outside the shadow DOM (unless the target element is in another shadow DOM).

But no solution exists for creating a reference from outside a shadow targeting an element inside a shadow. RererenceTarget allows this kind of relationship to be established. When combined with the AriaMixin attributes it can be used to create references from inside one shadow to inside another.

What solutions exist today?

The ARIAMixin IDL attributes (such as ariaLabelledbyElements and ariaActiveDescendantElement) allow references to be created from inside a shadow DOM to an element outside the shadow DOM, but not in the other direction. Nothing exists today for creating references from outside a shadow to inside a shadow.

How would you solve it?

The proposed referenceTarget property on ShadowRoot that causes all ID references pointing to the host to be forwarded to an element inside the shadow with the given ID.

This can be done imperatively:

<script>
  customElements.define(
    "fancy-input",
    class FancyInput extends HTMLElement {
      constructor() {
        super();
        this.shadowRoot_ = this.attachShadow({ 
          mode: "closed",
          referenceTarget: "real-input",
        });
        this.shadowRoot_.innerHTML = `<input id="real-input">`;        
        // Optionally, set referenceTarget on the ShadowRoot object.
        // Not needed in this case since it was set in attachShadow() instead.
        // this.shadowRoot_.referenceTarget = "real-input";
      }
    }
  );
</script>

<label for="fancy-input">Fancy input</label>
<fancy-input id="fancy-input"></fancy-input>

Or declaratively:

<label for="fancy-input">Fancy input</label>
<fancy-input id="fancy-input">
  <template
    shadowrootmode="closed"
    shadowrootreferencetarget="real-input"
  >
    <input id="real-input">
  </template>
</fancy-input>

This would work with any attribute that works with ID; for the full list see here.

A Phase 2 of this proposal would extend the idea with referenceTargetMap, which allows individual attributes to be redirected to separate elements in the shadow DOM.

<input
  role="combobox"
  aria-controls="fancy-listbox"
  aria-activedescendant="fancy-listbox"
/>
<fancy-listbox id="fancy-listbox">
  <template
    shadowrootmode="closed"
    shadowrootreferencetargetmap="aria-controls: real-listbox,
                                  aria-activedescendant: option-1"
  >
    <div id="real-listbox" role="listbox">
      <div id="option-1" role="option">Option 1</div>
      <div id="option-2" role="option">Option 2</div>
    </div>
  </template>
</fancy-listbox>

Anything else?

For more info and alternatives considered, see the explainer: https://github.com/WICG/webcomponents/blob/gh-pages/proposals/reference-target-explainer.md

This was discussed in a TPAC breakout session. See notes and slides.

Phase 1 is prototyped in Chromium behind --enable-experimental-web-platform-features.

@dandclark dandclark added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest agenda+ To be discussed at a triage meeting labels Oct 16, 2024
@cwilso cwilso added stage: 1 Incubation and removed agenda+ To be discussed at a triage meeting labels Oct 17, 2024
@alice
Copy link
Contributor

alice commented Dec 5, 2024

I've started compiling some rough notes on the work I think would need to happen in the various specs: https://gist.github.com/alice/70e9c836ee9629f11fea9d37c4432dc5

@smaug----
Copy link

FWIW, I think it would be nice if the syntax for phase 1 and 2 could be somehow merged, to avoid too many new attributes.

@jakearchibald
Copy link
Contributor

The proposed referenceTarget property on ShadowRoot that causes all ID references pointing to the host to be forwarded to an element inside the shadow with the given ID

I assume this excludes things like getElementById and CSS selectors.

@jakearchibald
Copy link
Contributor

An interesting case here:

<button popovertarget="component-a"></button>
<component-a id="component-a">
  <template shadowrootmode="closed" shadowrootreferencetarget="inner-thing">
    <div id="inner-thing" popover></div>
  </template>
</component-a>

As the owner of the button, is it weird that I don't get a toggle event on component-a, despite the popover being successfully opened?

This case also came up in a discussion of commandFor.

@dandclark
Copy link
Contributor Author

I assume this excludes things like getElementById and CSS selectors.

That's right.

As the owner of the button, is it weird that I don't get a toggle event on component-a, despite the popover being successfully opened?

Hmm, maybe. The component could choose to re-dispatch the event against the host if it wants to expose it, though. I'm unsure that we'd want to leak it from the shadow by default.

@jakearchibald
Copy link
Contributor

I think the trickiness here is that from the outside's perspective, it isn't clear that a retarget happened. So it seems like the event just vanished for some reason.

@dandclark
Copy link
Contributor Author

If the component does something like this, from the outside it should look pretty much as if the event had originally fired on the host element, right?
The component could use this approach to choose whichever of its internal events it wanted to expose.

<button popovertarget="component-a"></button>
<component-a id="component-a">
  <template shadowrootmode="closed" shadowrootreferencetarget="inner-thing">
    <div id="inner-thing" popover>popover</div>
  </template>
</component-a>
<script>
  class ComponentA extends HTMLElement {
    constructor() {
      super();
      const shadow = this.attachInternals().shadowRoot;
      const innerThing = shadow.querySelector("#inner-thing");
      innerThing.addEventListener("toggle", (e) => this.redispatch(e));
    }
    
    redispatch(e) {
      const retargetedEvent = new e.constructor(e.type);
      this.dispatchEvent(retargetedEvent);
    }
  }
  window.customElements.define("component-a", ComponentA);
  
  const componentA = document.querySelector("component-a");
  componentA.addEventListener("toggle", () => console.log("toggle on component-a"));
</script>

@jakearchibald
Copy link
Contributor

Sure, I know it can be fixed manually, I just wasn't sure if that was the best design for the platform, as it puts the burden on every component author.

#11148 (comment) - @smaug---- seems to think this shouldn't be on every component author to fix.

It seems worth exploring options that do the right thing by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accessibility Affects accessibility addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest stage: 1 Incubation topic: shadow Relates to shadow trees (as defined in DOM)
Development

No branches or pull requests

6 participants