-
Notifications
You must be signed in to change notification settings - Fork 376
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
How can a custom element detect when it is transcluded into a shadow tree (slotchange)? #504
Comments
I'm not sure what a good title for this issue is. |
Updated the title. Maybe we need something like a Is there any current/recommendable way of detecting when an element has been transcluded? |
Where can I find exactly what the |
@trusktr there's a |
Correct. There is no easy way in v0. e.g. Polymer folks had to spend tremendous efforts to detect such a change in v0. Blink is actively implementing |
@trusktr If you want a partial polyfill that supports a /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --enable-blink-features=ShadowDOMV1 Though, I'm not sure if it's been implemented there yet. |
Regarding the support of |
Awesome!
This sounds nice, but I think that this event will be easy for the owner of a shadow root to use, but what about elements that are being inserted into a slot? I think (please let me know if I'm wrong) that due to Shadow DOM encapsulation that some (potentially custom) element being distributed into a slot will have no idea what slot that is, and in my case, the custom elements that I'm making need to know where their slots are located and what the parent element of the slot is. Is there a way for the |
I can't really answer without knowing your use-case, but I've found that you should be emitting events that parents respond to. Reaching up in the DOM tree creates a tight coupling of your child component's functionality to the DOM structure in which it's placed defeating the purpose of modularity and componentisation. |
You can use |
What if it's closed? My problem is that to construct an efficient WebGL tree from my HTML interface, I need to mirror the flat tree if I want the WebGL portion to match with it. But at the same time, I can't tell ever developer to leave their shadow root open, and nor can one who is merely re-using components make the guarantee about all the components they use having open shadow trees. |
Yeah, no way for a closed mode. We have to honor the intention of a component author who chooses "closed" intentionally. |
So slot-containing elements inside a shadow tree can still see what nodes are distributed into their slots (on If so, I can modify my API to have parent nodes look at their child nodes (or look at the distributed nodes of their slots) in order to create the behind-the-scenes flat-tree mirror that I need. |
I don't think you can create a flat tree mirror for closed shadow trees because all those slots that may have distributed contents would not be accessible from your code. You may need to do something like overriding |
Yes, it's okay for Inner tree to see Outer tree, whether Inner tree is open or closed. |
My code will have access to the distributed content because the elements being distributed into all the All I need to do is change my API so that it is a parent-to-child API instead of a child-to-parent API. This means that to construct the flat tree mirror I will have parent elements observing children instead of child elements observing parents. Get what I mean? I see the path now, so I'm gonna make that change soon with my elements.
Perfect, thanks for the confirmation! :] (The description you linked to is like a completely foreign language to me. x] ) |
Specifically to my case, I see how to detect the following two cases and throw errors for them:
But I am having trouble figuring out how to deal with this scenario:
Basically, the following is fine -- a parent
But, the following is wrong, and I would like to throw a helpful error to the end user so they can learn from it:
Any ideas how to deal with those last two cases @rniwa and @hayatoito? Here's possibilities I think for each case:
|
In the case that a shadow root is created in the unknown future and while no shadow root exists so therefore a light-tree I'd like for these errors/warnings to link to some documentation that I'll have online. |
Can you expand on your specific use case? It seems wrong that a child On Tue, 7 Jun 2016, 07:50 Joseph Orbegoso Pea notifications@github.com
|
In my case, it's just that if a I'd really like to be able to inform the user about this case. AngularJS has nice errors that point to online documentation. I'd like to do this in that specific case, but I don't see how yet. I'm thinking that the best solution (so far, in my mind) would be for Custom Elements to have a I technically now *don't need for a distributed element to be able to view a slot's parents in order to make my flat-tree mirror, but now I need a distributed Does that make it a little more clear the issue is I'm having? |
Maybe the improperly distributed |
I'd like to also note that in React this is super easy to do, thanks again to JavaScript scoping. I think that if a custom element can know not only if it is connected, but if it is distributed (even if the shadow dom into which is it distributed into is closed) then all cases that I need to catch can be caught. Maybe it makes sense for an element to know when it has been distributed, without necessarily needing access the inner tree's content (f.e. the inner tree is closed). |
The solution to this problem is to have parent elements observe which children are attached to them or which children are attached to their slots when the parent is in a ShadowDOM tree. This is better than my current design where children observe parents in order to determine if child was attached to a correct parent, which doesn't work in closed Shadow trees because the child can't see the parent the slot where it is distributed into. |
Oops, nevermind. So parents observing children works in order to detect invalid children (and to detect invalidly distributed children when parent contains a slot and is in a shadow tree). But, if a child is distributed into any element other than my custom elements (for example, on of my The only solutions I can think of right now are:
I've been thinking about this for weeks (in the back of my head while I work on other things) and can't find a solution. @treshugart @hayatoito @rniwa @domenic Any ideas or thoughts? To put my problem concisely, I just need the following
Where When the following happens, I do not want to throw an error:
|
I posted a new issue with the concept of |
@trusktr unfortunately I haven't had the use-case yet of requiring a child to know when it's distributed. In all cases I've come across, the child just may not be fully functioning due to parents not responding to events. I guess in that case, such a feature would allow me to warn the developer about it, but I'm not sure if having this feature outweighs the potential abuse it might get from devs breaking encapsulation. I can definitely see your point, though. |
@treshugart I can't see how to make it work with events. For example, how will the child know when to dispatch an event, as there's no way for the child to know it has been distributed? The only thing I can imagine is a poll-like method where the event is dispatched every so often, which is undesirable. Events seem like a way for an element to shout, but not receive a guaranteed response. Being able to know when an element is attached (we already can with connectedCallback) and when an element is distributed (even if access to the slot is denied) would really help. For example, if a child element knows it has been distributed, then it can fire an event and if it receives no response then we can guarantee that a parent exists or not without a poll-like mechanism, and we'd be able to know this information at the soonest possible moment in time (I'm assuming events are synchronous?). I'm wrestling with this because I really want to be able to create virtual scene graphs that mirror the flat tree. This makes sense from a rendering perspective, because that's what the browser does natively to render things, but in my case I want to have my own WebGL pipeline, so just like the browser I need to be able to know the flat tree structure to finally render what I want in WebGL, and I want to be able to throw meaningful warnings or error in the case that an HTMLElement is not distributed properly into a ShadowDOM tree. My ComposedTreeProxy idea could probably be another way to solve this, whereby a child could read the proxy to determine if a certain parent exists (the proxy might be observable with MutationObserver in order to know when it's structure changes, or similar). But, this seems like a heavy-weight solution for my case of determining if a certain immediate flat-tree parent is present for a given child. Any other ideas? |
@trusktr the |
@treshugart That case is easy to detect, but the case I'm trying to detect is different: I need to ensure that an element is slotted into another element of a certain type. With a closed tree, I don't see any possible way for a child to know it was slotted into some unexpected element. For example, if |
If I understand you correctly this jsbin should work just fine. Just swap v0 for v1 API calls and Bob's your uncle. A child shouldn't need to know where it's slotted; in a composed tree, a child is always slotted into a |
Hey Trey, I really appreciate your effort to help me out. In my case, the situation is a little different. Here is a jsbin showing the problem: http://jsbin.com/dozacacuva/edit?html,js,output In particular note that we are interested in the flat-tree composition being such that |
Ahh, ok. Interesting! I think I get it now. In SD v1 you'll have access to an You can use our polyfill if you want to try it out. EDIT For |
Ah, oops, I forgot to mention! In the jsbin I posted, the assumption was that the trees were closed, so
That's true, but with |
Hah! Yeah, but would that be much different than using Another way of looking at this might be: connectedCallback () {
this.distributedCallback(this.assignedSlot);
} |
That doesn't work because the connected events and distribution events don't necessarily happen at the same time. When we write the following <div>
<motor-scene id="scene">
<motor-node id="node"></motor-node>
</motor-scene>
</div> and it gets rendered, the Suppose we add a shadow root to the #shadow-root
<motor-node id="inner-node">
<slot>
</slot>
</motor-node> Then we get: <div>
<motor-scene id="scene">
#shadow-root
<motor-node id="inner-node">
<slot>
<!-- distributed motor-node -->
<motor-node id="outer-node"></motor-node>
</slot>
</motor-node>
<!-- original motor-node -->
<motor-node id="outer-node"></motor-node>
</motor-scene>
</div> At some point in time after adding the shadow root, With closed shadow trees, the value of
The main reason why I want such insight is that I want to make an API that's as easy to use as possible, and I don't just mean that the API is easy to use when documentation is followed, but that the API is easy to use because it will do a good job of teaching usage patterns to someone when they try the API by trial and error, and which will do a really good job of catching invalid use cases in applications where life and death could possibly even be at hand. Something like |
Is there any event associated with distribution? I know |
I do not see any clear reason why we need distributedCallback. e.g.
|
I think the use case outlined here is an interesting one. If I understand it correctly, this is really about introducing a new markup language on top of HTML to render 3D graphics like MathML does for mathematical questions and SVG does for vector images. And I can see that to build something like that, one has to walk across composed/flat tree to see parent/child relationship as they're presented. Unfortunately, I don't think this is something we currently support to be implemented in the author code. We don't even support using shadow DOM in SVG/MathML context so the scope and usefulness of shadow DOM is quite limited in that respect. Fundamentally, author code can't see the entire flat / composed tree across all shadow and slot boundaries in the presence of closed shadow trees. e.g. if As things stand, you probably need to limit the use of your library to be entirely in a single tree (and not cross any shadow/slot boundaries). This is an interesting food for thought when we're considering imperative API and more fine grained distribution mechanism in general as well as Houdini. |
Yes, I suppose it is. Custom Elements make it possible to design markup language for anything. SVG and MathML elements (if they didn't already exist) would be the sort of API people could design on top of HTML using Custom Elements (as opposed to them being native elements). A great example of a custom renderer is Three.js built on WebGL. A-Frame is a great example: it takes Three.js and builds an HTML interface on top of it with Custom Elements, so that we can write Three.js-based scenes using markup. I'm aiming to do something similar (will be using WebGL, though possibly not Three.js), where my elements also make it possible to create a 3D scene with markup.
Not necessarily. I currently see how to use my elements in ShadowDOM as it currently stands (v0, all roots open). But I think even with closed trees, there is still a way to send events (or for parents to observe their children or their slots distributed nodes when those parents are in a shadow tree) in order to attach the virtual scene graph together. My With closed trees this is fine because in a closed tree a parent can still see what is distributed into it's child slot, so a I'm guessing in v1 that host elements can look at the elements inside their direct shadow root, right? And, if so, how is this possible? Does the code that accesses the shadow root have to run inside the methods defined for that custom element (f.e. inside of the element's To put it more simply, without being able to know the whole flat tree, I believe I can still connect my scene graph together assuming the end developer follows the rules:
I believe all those cases can currently be detected (please verify about host element viewing shadow root children), which means I can use ShadowDOM with my elements. The only thing that I cannot detect with closed shadow trees is
I cannot detect that case because a distributed node cannot observe ancestors of the That is okay, and if they ask for help I can tell them why. But! I'd like to throw an error in this case to inform the user of the situation so that they don't even have to ask for help if they are reading the helpful message in the console, but since it is impossible for my library to detect that scenario (without the above distributedCallback and So, without
It should, and let me explain why. This new ShadowDOM stuff and Custom Elements should work with MathML and SVG too, and basically should just work anywhere where we write HTML DOM in the browser. The WebComponents spec allows an amazing modular component model of designing components (widgets, things to be rendered, UI controls, etc), making code much more re-uasble, easier to organize, and able to accept various type of children via out tree markup to render internally in the inner tree markup (markup --> DOM, markup translates to instantiated DOM). So, with that said, why not have Web Components work anywhere, with SVG, MathML, and custom libraries like A-Frame or my own. SVG can be a more powerful 2D graphical engine than HTML's 2D parts, and I see no reason (especially since event handling is involved) why we should not be able to componentize and modularize a user interface that may be made entirely of SVG elements. I can imagine making user interface components made entirely with SVG elements, and therefore I can imagine how useful WebComponents would be in making those UI components easily re-usable across projects. So, I really think WebComponents (all of it, Custom Elements, ShadowDOM, etc) should apply anywhere where we will write HTML markup (or anywhere where we create elements with
With my library I will be making UI components, and I'd really like to take advantage of ShadowDOM for modularity and componentization. I strongly disagree with limiting my library by not using ShadowDOM. Consider the following! An HTML developer discovers A-Frame, my library, or some other library that exposes custom elements. Then, that developer simply wants to take advantage of modularity and wants to make components with the custom elements by placing them into his/her own custom elements that take advantage of the benefits of ShadowDOM. One day, an author will inevitably try to place A-Frame's If we are to encourage the awesome patterns that ShadowDOM introduces, we should do so by encouraging people to use it everywhere for making components, not just sometimes, and with only elements X,Y,Z. That defeats the purpose of the feature. It should be a broadly generic feature that can work with any elements except for X,Y,Z elements that explicitly define so for good reason. Disabling them with SVG doesn't have a good reason (or at least I bet I can refute whatever the reason currently is). There's a lot of info in my response, but TLDR:
Here's an example, a "push pane layout" where the pane can be swiped into the view from the edge of the screen (logic omitted, but this demonstrates the HTML API that the class PushPaneLayout extends MotorHTMLNode { // motor-node <-> MotorHTMLNode
constructor() {
this.root = this.attachShadow({mode: 'closed'})
this.root.innerHTML = `
<motor-node>
<slot name="header">
</slot>
</motor-node>
<motor-node
position="-100, 0,0" data-info="<-- Here we set the X position so the pane is hidden off the edge of the view."
class="touch-enabled-push-pane">
<slot name="pane">
</slot>
</motor-node>
<motor-node>
<slot name="body">
</slot>
</motor-node>
<motor-node>
<slot name="footer">
</slot>
</motor-node>
`
}
// ...
}
customElements.define('push-pane-layout', PushPaneLayout) Let's use the layout (end user doesn't have to know how layout logic works, just supplies content): <push-pane-layout><!-- this is a motor-node -->
<motor-node slot="header">
<img src="logo.png" />
</motor-node>
<motor-node slot="pane">
<ul>
<li> menu item 1 </li>
<li> menu item 2 </li>
<li> menu item 3 </li>
</ul>
</motor-node>
<motor-node slot="content">
<!-- Some logic changes the content dynamicaly based on a URL route, etc... -->
</motor-node>
<motor-node slot="footer">
Copyright (c) Some Company 2018
</motor-node>
</push-pane-layout> Now, suppose, an end app developer uses the class DynamicMenuLayout extends MotorHTMLNode {
constructor() {
this.root = this.attachShadow({mode: 'closed'})
this.root.innerHTML = `
<push-pane-layout>
<motor-node slot="header">
<img src="logo.png" />
</motor-node>
<motor-node slot="pane">
<slot name="menu"> <!-- <<------------------------ -->
</slot>
</motor-node>
<motor-node slot="content">
<!-- Some other logic changes the content dynamicaly based on a URL route, etc... -->
</motor-node>
<motor-node slot="footer">
Copyright (c) Some Company 2018
</motor-node>
</push-pane-layout>
`
}
// ...
}
customElements.define('dynamic-menu-layout', DynamicMenuLayout) Now using that <dynamic-menu-layout>
<motor-node slot="menu">
<ul>
<!-- Some logic changes this menu content. -->
</ul>
</motor-node>
</dynamic-menu-layout> As we can see here, being able to use ShadowDOM is very useful! We should definitely make ShadowDOM work everywhere possible so that developers who adopt Web technology as their front-end engine can use a single awesome pattern for modularity (Custom Elements + Shadow DOM). As it currently stands (based on what you said), we don't have the option of using ShadowDOM with SVG, and that is definitely a bummer to someone who decides that their UI should be entirely SVG (which is totally possible, maybe they are an Adobe Illustrator expert who just picked up HTML and exported UI designs to HTML markup, then read an awesome article about how to componentize HTML components only to find it completely fails with his nothing-but-SVG UI). |
Note, in my example, I used excess People who adopt libraries like React, Riot.js, etc, they don't have any limitations on what elements they can use. They can render any elements including SVG and MathML with those libraries, and so the web should aim to allow that as well with ShadowDOM. ShadowDOM + Custom Elements + ETC (Web Components) are a design pattern that we should be able to use when defining any HTML structures. |
@treshugart You mentioned,
Mind explaining how to observe this on |
@treshugart I tried using |
@treshugart Nevermind, I think I got it: for a given |
It might still be nice for a Custom Element to detect distribution (without leaking structure of a shadow root) because then with this knowledge it can trigger an event if it needs to. This is not possible with cc @rniwa @hayatoito ^, What do you think? After thinking about this for a while, and after successfully making my library compatible with ShadowDOM V1 including closed shadow roots, I still think the a solution to the need described in this issue would be invaluable because then I would be able to detect certain conditions that I can not otherwise detect without crazy hacks, in order to give the end user of my library helpful console messages like
With |
To get around the expensive checks, you could make it so it can only check
and warn in dev so that the expensive checks aren't run in prod code.
…On Fri., 2 Dec. 2016, 10:59 Joseph Orbegoso Pea, ***@***.***> wrote:
It might still be nice for a Custom Element to detect distribution
(without leaking structure of a shadow root) because then with this
knowledge it can trigger an event if it needs to. This is not possible with
slotchange because there's no way for a slot to determine which event a
distributed node should fire.
After thinking about this for a while, and after successfully making my
library compatible with ShadowDOM V1 including closed shadow roots
<lume/lume#40>, I still think the a
solution to the need described in this issue would be invaluable because
then I would be able to detect certain conditions that I can not otherwise
detect without crazy hacks, in order to give the end user of my library
helpful console messages like "Warning: Your motor-node#someID element does
not work when distributed to such-an-such element. It must be distributed
to a motor-scene or motor-node element.`
With slotchange and assignedNodes, I can only detect proper use cases
where a shadow root is the root of one of my library's custom elements and
when a slot is child of one of my library's custom elements and when one of
my library's elements are connected or distributed directly to another
element of my library. Detecting the other cases would be too hacky and
expensive so far as I can imagine (involving hijacking attachShadow and
traversing all shadow roots).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#504 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAIVbO87Q7bJfosEptFGeRY5aPEsGaO7ks5rD19NgaJpZM4IkCcX>
.
|
True, good idea. I just realized that I would also need to use a Basically, it seems like implementing the equivalent of |
Let me close this since it looks this topic is discussing how to support v0. That is not what we will support, in terms of the spec. |
I opened a more concise version of this question, but pertaining to v1, over in #941. |
(I asked a related question on StackOverflow, in case that helps reach different people knowledgeable on the same topic.)
I made some custom elements for creating 3D scenes, for example:
I noticed
attachedCallback
fires only when attached to the light tree (in Chrome). It breaks when I try to transclude things together (using<content>
), for example:shadowDOM inside of a
<foo-bar>
custom element:<foo-bar>
usage:where
foo-bar
is defined like this:The simple example shows the transclusion. As you can see, the expected is that the
motor-node
is placed inside themotor-scene
(which is a requirement for things to work properly (preserve-3d, CSS transform caching, etc).I noticed that the
attachedCallback
will be called when themotor-node
exists in the light DOM, but not when it is put into it's new place. I guess this makes sense in the perspective of a component user who it attaching themotor-node
into asome-layout
for example, but for me (the component dev), I've stumbled, because my custom elements rely on theirattachedCallback
s in order to set themselves up: motor-nodes can only be children of motor-scene or other motor-node elements, and this is all detected withattachedCallback
(and throws an error if this isn't the case). So, it seems like this design has shot me in the foot when it comes time to try and transclude things the web-components way.In React, the same concept that I am trying to achieve would work just fine, because things in DOM are constructed for real in a single light DOM (so all the attaching/detaching) works just fine.
Unless I'm not seeing the full picture, it seems that if I want to make this work with Web Components that I need to have a component system that sites purely on top of the light DOM and not use shadow DOM (for example, React, or just Custom Elements but without using Shadow DOM inside them).
It also seems that if I want to allow my end users to use Shadow DOM to construct their own trees using my custom elements (which is a possibility), that I might have to have my own shadow wherein my entire scene graph is contained (so it behaves like a light DOM as far as I'm concerned inside of a scene's shadow root), and that I would need to be able to traverse all of the user's shadow dom roots in order to detect and construct a scene graph in my own shadow root. (EDIT, for traversing, if I end up needing to do that, I suppose I can recommend the user to set the
ShadowRootMode
toopen
, but let's first try to avoid that).Basically, this seems like a huge undertaking that is almost completely not worth trying (compared to using something else like React). It seems like if we modified transclusion to behave like attaching (from light DOM) and re-attaching (into shadow DOM) along with firing
attached/detachedCallback
in those cases would let me do what I want to do, because those callbacks trigger calls that construct a tree structure parallel to the DOM structure (I would have to ignore the<content>
elements, and besides warning about "motor-nodes need to be attached to motor-scene or motor-node elementsI would then also be able to warn about "motor-nodes can only be trancluded into motor-scene or motor-node elements"
. In React, we don't have to detect the transclusion case (as far as Custom Elements are concerned), and I would also argue Shadow DOM is not needed when making components with React but some people use it anyways (which I think is unnecessary extra complexity).To show what my elements are doing visually, basically the attachedCallbacks are used to create a parallel tree structure where the
o
s are the motor-scene and motor-node elements:With transclusiong, this strategy for creating the behind-the-scenes tree structure is falling apart. I'll need the structure in order to be able to use that in WebGL (not just DOM). If I ignore WebGL, then I can get rid of my behind-the-scenes tree structure, and let DOM handle's it's own 3D scene graph entirely. Instead of holding matrix transforms in the behind-the-scenes structure, I would just place them right inside the custom element instances and be done with it. Simple.
I guess I'm just trying to figure out how to use transclusion the web components way. If I stick to React, the problem is easily solved: just let React attach all elements into light DOM, and I'm done with it. But, I don't want to settle with a solution like that then not be able to use my components in an Angular app (suppose I switch to a different project with new framework requirements, then I'd have to port the components or start from scratch). I want to truly write once, use everywhere.
Sorry that this is all somewhat random order, because I'm thinking as I go. A-Frame will have similar problems. I'll also ask there to see if anyone has thought about this.
END GOAL: I want to build with WebComponents, not with a 3rd party lib like React, so that my elements can be used anywhere (React, Angular, Meteor, Ember, etc)
The text was updated successfully, but these errors were encountered: