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

There is no way to navigate to a fragment inside a shadow tree #924

Open
acarlstein opened this issue Apr 29, 2021 · 14 comments
Open

There is no way to navigate to a fragment inside a shadow tree #924

acarlstein opened this issue Apr 29, 2021 · 14 comments

Comments

@acarlstein
Copy link

Example

Let's assume you have this Web Component:

let template = `
	<div>
		<a href="#message">Make the message red</a>
		<h2 id="message">Message in a bottle</h2>
	</div>
	<style>
		#message:target {
			color: red;
		}
	</style>
`;

customElements.define('my-component', class myElement extends HTMLElement {
	constructor() {
		super();
		let shadowRoot = this.attachShadow({ mode: 'open' });
		shadowRoot.innerHTML = template;
	}
});

Problem

This code will fail because the event :target is missing in the root of the shadow.

Also, since it isn't in the specifications, there are not signs that other browsers will fix this issue.

Expectations

I was expecting to see the header turn to red when clicking on the link.
This works outside the web component but fails to do so inside the web component.

@rniwa rniwa changed the title :target failing inside web components. There is no way to navigate to a fragment inside a shadow tree Apr 29, 2021
@rniwa
Copy link
Collaborator

rniwa commented Apr 29, 2021

This was previously discussed in #66.

@acarlstein acarlstein changed the title There is no way to navigate to a fragment inside a shadow tree Target Event Missing in shadow-root. Cannot apply pure CSS on header. Apr 30, 2021
@acarlstein
Copy link
Author

@rniwa , the discussion on #66 is about navigate to a fragment inside a shadow tree. This is not my case. My case is a pure CSS trick that doesn't work inside the Web Component when it should.

For example, by clicking a link, I could change the color of a header:
Before:
image
After:
image

Here is the code:

<style>
  #message:target { color: red; }
</style>
 <body>
    <a href="#message">Make the message red</a>
    <h2 id="message">Message in a bottle</h2>
 </body>

Another example, I could create a toolbar in which the function selection remains active:
image

However, since target isn't part of the root of shadow-root, this feature isn't available.
This pure CSS trick doesn't work inside the Web Component.

@annevk
Copy link
Collaborator

annevk commented Apr 30, 2021

It's not a pure CSS trick, you are literally navigating to a fragment and that fragment's corresponding ID cannot be found as its inside a shadow tree.

@annevk
Copy link
Collaborator

annevk commented Apr 30, 2021

Solving this would need something like delegatesIds = [ list of IDs ].

@acarlstein
Copy link
Author

acarlstein commented Apr 30, 2021

It's not a pure CSS trick, you are literally navigating to a fragment and that fragment's corresponding ID cannot be found as its inside a shadow tree.

@annevk , I called it "trick" to make it easy to understand; however, the fact is that the code below works fine outside the Web Component:

<style>
  #message:target {
    color: red;
  }
</style>
<div>
  <a href="#message">Make the message red</a>
  <h2 id="message">Message in a bottle</h2>
</div>

The header changes colors when the link is click due :target.
However, when moved this code into a component itself it fails to do so.

It works fine until its move into a Web Component fails because the target isn't there in the shadow-root.

I understand that Web Components should be encapsulated and outside links shouldn't be allowed to navigate to the fragment; however, the selector :target shouldn't be affected.

This piece of the CSS code, #message:target is affected.
So, at least there should be an alternative than having to write extra JS code to obtain the same effect.

@rniwa rniwa changed the title Target Event Missing in shadow-root. Cannot apply pure CSS on header. There is no way to navigate to a fragment inside a shadow tree May 1, 2021
@rniwa
Copy link
Collaborator

rniwa commented May 1, 2021

@rniwa , the discussion on #66 is about navigate to a fragment inside a shadow tree. This is not my case. My case is a pure CSS trick that doesn't work inside the Web Component when it should.

No, you're misunderstanding the cause of the problem. The fragment navigation is precisely what's missing & what's causing :target to never apply inside a shadow tree.

Please go read the definition of :target before leaving any new comments.

@acarlstein
Copy link
Author

@rniwa , the discussion on #66 is about navigate to a fragment inside a shadow tree. This is not my case. My case is a pure CSS trick that doesn't work inside the Web Component when it should.

No, you're misunderstanding the cause of the problem. The fragment navigation is precisely what's missing & what's causing :target to never apply inside a shadow tree.

Please go read the definition of :target before leaving any new comments.

Thank you. That's is what I'm trying to say which means there is a bug because that piece is missing.
If you search for the documentation on the CSS pseudo-class, there is nothing that says "It doesn't work in Web Components by the way"

So my point is, if there is a decision to not include a fragment navigation, at least put something in place so the code I shared doesn't break.

@rniwa
Copy link
Collaborator

rniwa commented May 10, 2021

That's is what I'm trying to say which means there is a bug because that piece is missing.
If you search for the documentation on the CSS pseudo-class, there is nothing that says "It doesn't work in Web Components by the way"

I don't think this is a bug per se. It's more of a missing feature. This is very much the intended behavior. In general, id references work within each tree, not across shadow boundaries. For example, document.getElementsById(id) will only find elements with the specified id in the document tree, not any of the shadow trees connected to it.

@Jamesernator
Copy link

Jamesernator commented May 12, 2021

Solving this would need something like delegatesIds = [ list of IDs ].

The downside of something like this is that the parent tree might not be aware of all ids being exposed via all shadow roots, this would make it easy to potentially have conflicting ids.

An alternative would be to just allow associating an id to a given shadow ::part e.g.:

<style>
  /* Ideally works, although using it .querySelector wouldn't select anything
     as it's in a shadow root, so this should arguably be a pseudo-element */
  :target {
    color: red;
  }
</style>

<a href="#myId">Works</a>

<my-element partids="myId:myPart">
  <template shadowroot="closed">
    <div part="myPart">Foo bar!</div>
    <div part="fooPart">Bazz</div>
  </template>
</my-element>

The one problem with parts though is that many elements can have the same part name, although this might not be a big deal as we can already have multiple elements with the same id attribute, if we were being consistent with that we'd just ignore all but the first.

Alternatively again this could even be a part of the url similar to scroll-to-text-fragment, e.g. <a href="#myId:~:part=myPart">, although this would be trickier with CSS (maybe?).

@rniwa
Copy link
Collaborator

rniwa commented May 13, 2021

FWIW, for this kind of API, it's very important that there is an opt-in from both component author & user. We can't pollute the global or outer tree's fragment ID from within shadow trees or let users of a component make random parts of a component addressable via a fragment ID in order to maintain the encapsulation we have at shadow boundaries.

@calebdwilliams
Copy link

Feels like if anything the host should have to map the exported id back to another name.

<c-e importnodes="foo as bar">
  <template shadowroot="open">
    <div id="foo" exportnode></div>
  </template>
</c-e>

@acarlstein
Copy link
Author

That's is what I'm trying to say which means there is a bug because that piece is missing.
If you search for the documentation on the CSS pseudo-class, there is nothing that says "It doesn't work in Web Components by the way"

I don't think this is a bug per se. It's more of a missing feature. This is very much the intended behavior. In general, id references work within each tree, not across shadow boundaries. For example, document.getElementsById(id) will only find elements with the specified id in the document tree, not any of the shadow trees connected to it.

I will present it in a different way...

There no need to cross boundaries.
You use the example document.getElementById(id), did you? Then, how about this.shadowRoot.getElementById(id)?
Make the :target work inside the shadow boundaries by itself, as a separate :target.

I should be able to copy any HTML and CSS code and have it run inside the shadow without surprises.
If there is something that shouldn't cross boundaries, then have two, one for document and one for shadow, but both doing what the documentation says it does.

@claviska
Copy link

Make the :target work inside the shadow boundaries by itself, as a separate :target.

I don't think this is possible because it's a breaking change, but I agree with your sentiment:

I should be able to copy any HTML and CSS code and have it run inside the shadow without surprises.

Like @rniwa said earlier, it's less a bug and more a missing feature that would probably need to be implemented as a new selector, e.g. :local-target or :host-target.

@keithamus
Copy link

WCCG had their spring F2F in which this was discussed. You can read the full notes of the discussion (#978 (comment)) in which this was discussed, heading entitled "ARIA Mixin & Cross Root ARIA" - where this issue was specifically discussed.

In the meeting, present members of WCCG reached a consensus to discuss further in breakout sessions. I'd like to call out that #1005 is the tracking issue for that breakout, in which this will likely be discussed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants