Re: [w3c/webcomponents] How can a custom element detect when it is transcluded into a shadow tree (slotchange)? (#504)

@rniwa 

> 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.

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](http://threejs.org/) built on WebGL. [A-Frame](https://aframe.io/) 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.

> 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. 

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 `motor-node` elements have a requirement: they must be children of each other in the flat tree so the scene is easy to reason about, and there won't be any sort of positioning based on an ancestor that is not a direct parent like there is with CSS `position:absolute`). Placing a `motor-node` in any element besides another `motor-node` or a `motor-scene` element won't work, and a connection will simply not be made because parents are observing direct children.

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 `motor-node` element can make a connection with `motor-node` elements distributed into `slot`s that are children of that `motor-node`; crossing a ShadowDOM boundary therefore isn't necessary for building the scene graph, with one exception: shadow host elements need to know what are the immediate root-most elements of their shadow root.

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 `connectedCallback`)?

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:

- A `motor-node` element can only be child of
  - other `motor-node` elements.
  - `motor-scene` elements.
  - `slot` elements that are children of
    - `motor-node` elements.
    - `motor-scene` elements.
  - a shadow root who's host is a
    - `motor-node` element.
    - `motor-scene` element.

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

- A `motor-node` is child of
  - a `slot` who's parent is not a `motor-node` or a `motor-scene`.

I cannot detect that case because a distributed node cannot observe ancestors of the `slot` in which the node is distributed. This is just fine though, as far as functionality, because in that case I do not want my elements to make a scene graph connection. So, if a user of my library does that, then it will simply not render anything.

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 `slotchange` events being fired first), then I cannot throw a helpful error message. Instead, my library will silently just not make a connection from a parent `motor-node` to the distributed `motor-node`, and that lonesome distributed `motor-node` will just sit there inertly, doing nothing. That is *okay*, but not as nice as if I could legitimately detect that case.

So, without `distributedCallback`, my library can still render scenes if the elements are used as in the list of scenarios above, and those ways of arranging `motor-node` elements in DOM will be the way that is publicly documented.

> 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.

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 `createElement()`, without regard to the type of elements being used. It might make sense for *certain* elements not to have shadow roots, and it can be that element's definition that decides that, but we should definitely not void an entire set of elements (SVG) from taking advantage of it.

> 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.

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 `<a-entity>`, my `<motor-node>`, or `<whatever-it-may-be>` into a shadow root and/or distribute nodes into one of those elements using `slot`s, due to the mere fact that this new ShadowDOM API exists.

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:

- `distributedCallback` is a small change that completes a small part of the big picture, and I think it can allow for useful behaviors like an element being able to detect when it has not connected with a specific parent in a shadow tree (without needing to reveal the shadow tree structure)
- ShadowDOM should work with all elements as much as possible. It would be especially nice to use ShadowDOM with SVG and MathML. Those rendering engines can simply get the flat-tree from the HTML engine to render what they will finally render, so if the reason for not supporting SVG or MathML is technical, then refactoring should happen, but from the outer perspective of an HTML developer, there's no theoretical problem with writing SVG using ShadowDOM to distributes SVG elements into other places of an overall SVG DOM.

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 `push-pane-layout` allows for):

```js
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):

```html
<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 `push-pain-layout` asa template for various pages of an app. Maybe depending on URL routes, the menu content might change:

```js
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` in HTML would be used like this (imagine this could be the markup in the shadow root of yet another Custom Element):

```html
<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).

---
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/504#issuecomment-233103077

Received on Saturday, 16 July 2016 02:16:04 UTC