Closed iamdriz closed 4 years ago
Yep this is definitely supported. With web components you need a name attribute on your slot element, and then you need to add a slot attribute to your nested components that matches the slot's name
@jordanaustin Tried that. And the result is the same.
<parent-element>
<nested-element slot="inner"></nested-element>
<nested-element slot="inner"></nested-element>
</parent-element>
class ParentElement extends LitElement {
constructor() {
super();
}
render() {
return html`<div class="parent"><slot name="inner"></slot></div>`;
}
}
customElements.define('parent-element', ParentElement);
class NestedElement extends LitElement {
constructor() {
super();
}
render() {
return html`<div class="nested"></div>`;
}
}
customElements.define('nested-element', NestedElement);
<parent-element>
<!---->
<div class="parent"><slot name="inner"></slot></div>
<!---->
<nested-element slot="inner">
<!---->
<div class="nested"></div>
<!---->
</nested-element>
<nested-element slot="inner">
<!---->
<div class="nested"></div>
<!---->
</nested-element>
</parent-element>
Using lit-element
version: "^2.2.1"
You're initial samples seem to be working fine in my test: https://stackblitz.com/edit/as9syq?file=my-element.js
@abraham Ah bugger so I forgot to add to my question I use:
createRenderRoot() {
return this;
}
This is because I want all components to inherit the CSS from the main stylesheet of the page and NOT have encapsulation. But it seems this then causes the problem I have posted above...
So how can I allow the nesting of components but allow the components to use the styles from the top-level page they are used on?
You can't not use shadow DOM and use <slot>
, because <slot>
is a feature of shadow DOM.
I'd really recommend not using createRenderRoot
. It's for very, very special cases and probably a misfeature for general use.
@justinfagnani How can I nest Web Components but allow them to inherit the styling from the page that they are on? Without the createRenderRoot
it blocks the CSS from the host page from affecting the components which isn't what I want. We really need the page CSS to be able to bleed into the components and we also need to be able to nest components.
I can't imagine how Light DOM rendering would be conceived as a misfeature for anyone who seeks to build entire apps or sites with a pure LitElement approach and not least for the reason @iamdriz states. I would prefer to offer a static light = true
setting to enable this feature along with an upfront explanation of its consequences with regards to slot
support and style encapsulation. Until components may offer an interface for controlled bleeding of the context cascade, official support for Light DOM components will only make the library stronger since CSS encapsulation is simply not always desired, at least not by everyone, and would in any case often be better served with <style scoped>
instead of manhandling of the Shadow DOM. I having fun with LitElement both in and out the Shadow DOM and it has certainly restored faith in my career path.
Is this actually a limitation of LitElement or is it Web Components themselves that are where this limitation lies? If it's purely the way in which LitElement currently works then it'd be great to see someway of solving this... if not... I'm not sure what we can do... seems a shame if the ability to have a Light DOM and nest components is impossible though.
This has nothing to do with web components or lit-element, it's simply how HTML and the DOM work.
In most js frameworks, the framework has control over the entire tree so it can render things in different places as it sees fit. Web components are actual dom nodes, so anything they render goes into that dom node. Rendering web components is async, so it cannot assume anything about it's parent or child nodes.
In the browser the slotting issue is solved with shadow dom.
You can nest web components just fine:
<element-a>
<element-b></element-b>
</element-a>
This will render element-a, with element-b inside. However, if element-a
says:
this.innerHTML = ''
it will remove element-b
.
The default behavior of lit-element
is to render into a shadow root, in which it case it doesn't overwrite it's child nodes. In your case you changed createRenderRoot
to return this
, so you're always overwriting your child nodes.
You could do something like this:
class ElementA extends LitElement {
createRenderRoot() {
const contentWrapper = document.createElement('div');
this.appendChild(contentWrapper);
return contentWrapper;
}
}
this way element-a will render into a wrapper div, and won't overwrite the other child nodes.
If you want to wrap the content of element-a around the "slotted" content given by the parent, it gets pretty complex. Web components render async, so the child nodes could come in at any later time, and the parent could make changes which need to be tracked. Moving around live nodes like that isn't a good idea.
However, if both elements use lit-html, you could set the content to render as a property:
class ElementA extends LitElement {
static get properties() {
return {
slotTemplate: { type: Object }
};
}
createRenderRoot() {
return this;
}
render() {
return html`
<div>${this.slotTemplate}</div>
`;
}
}
<element-a .slotTemplate=${html`<element-b></element-b>`}></element-a>
This is essentially how most frameworks work as well, just with a more declarative syntax.
There has been a similar discussion in lit/lit-element#553 and it was closed. See https://github.com/Polymer/lit-element/issues/553#issuecomment-531004706.
So this is likely a duplicate and should be closed as well.
the root of this issue, and lit/lit-element#533, is that developers are frustrated by the shadow-dom's lack of options for application-level themeing
our current options for application-level themeing:
future solutions for application-level themeing:
adoptedStyleSheets
feature via "constructible stylesheets" so components can adopt stylesheets from the parent pageFor those who want to "pierce" or "inject" a broader theme stylesheet into all instances of a LitElement, there's another option that hasn't been mentioned so far: LitElement's static styles
getter can return an Array
instead of a single CSSResult
.
So, instead of this:
customElements.define('x-my-element', class extends LitElement {
static get styles() {
return css`.whatever{}`
}
})
You can export something like this:
export default theme => {
customElements.define('x-my-element', class extends LitElement {
static get styles() {
return [theme, css`.whatever{}`]
}
}
}
Then you can use your globally-themed component like:
import MyElement from './my-element.js'
const theme = css`.global-things{}`
MyElement(theme)
You can also use themes in an inheritance pattern.
class BaseElement extends LitElement {
static get styles() {
return css`...`;
}
}
class SpecificElement extends BaseElement {
static get styles() {
return [
super.styles,
css`...`,
];
}
}
I wonder if combining the two strategies above you could arrive at a generic 'Styled component' that would inject a theme into whatever LitElement you provide, eg:
styled.js:
export default (Base, theme) => {
return class extends Base {
static get styles() {
return [theme, super.styles]
}
}
}
import Component from './component.js'
import Styled from './styled.js'
import theme from './theme.js'
customElements.define('my-component', Styled(Component, theme))
@justinfagnani How can I nest Web Components but allow them to inherit the styling from the page that they are on? Without the
createRenderRoot
it blocks the CSS from the host page from affecting the components which isn't what I want. We really need the page CSS to be able to bleed into the components and we also need to be able to nest components.
I have solved the inherit problem + shadowRoot with a base class like this:
import { LitElement, html, css, unsafeCSS } from 'lit-element'
import i18next from 'i18next'
import style from './style.styl'
/**
* This class contains the generic things
* who need to have in all components and modules
*/
class LitElementTranslation extends LitElement {
static get styles() {
return [css`${unsafeCSS(style)}`]
}
connectedCallback() {
super.connectedCallback()
this.i18nextLanguageWatcher()
}
/**
* Funcs
*/
i18nextLanguageWatcher() {
i18next.on('languageChanged', () => {
this.requestUpdate()
})
}
/**
* HTML
*/
render() {
return html`
<link rel="stylesheet" href="https://unpkg.com/material-components-web@0.8.0/dist/material-components-web.min.css">
${this.icons()}
`
}
/**
* Import material design icons
*/
icons() {
return html`
<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin>
<link href='https://fonts.googleapis.com/icon?family=Material+Icons' rel='stylesheet'>
`
}
}
export default LitElementTranslation
import { html, css, unsafeCSS } from 'lit-element'
import LitElement from 'utils/lit-element'
import style from './style.styl'
class Button extends LitElement {
static get properties() { return {
id: { type: String },
label: { type: String }
}}
static get styles() {
return [
super.styles,
css`${unsafeCSS(style)}`
]
}
constructor() {
super()
this.id = ''
this.label = ''
}
/**
* Funcs
*/
handleClick() {}
/**
* HTML
*/
render() {
return html`
${super.render()}
${this.main()}
`
}
main() {
return html`
<div
class='button'
id=${this.id ? this.id : ''}
@click=${this.handleClick}
>
${this.label}
</div>
`
}
}
customElements.define('wc-button', Button)
We just need export the base component as we want, import him and call super methods.
Lit-element are very smart, the import of material icons (request) will occur just one time if you take a look in console network, so, this is very cool! Thank you Lit devs!
So... this issue is mostly about how standard shadow DOM works. Shadow roots don't inherit page styles, and it's required if you want to use <slot>
. We think these are important topics for future standards and we'll be bringing this issue up in the relevant meetings soon.
Until then the approach of importing styles is good, and we're working on a theming system that you can track here: https://github.com/Polymer/lit-element/issues/835
I'm going to close this in favor of the more specific and actionable topic in lit/lit-element#825 and for the standards side I just opened https://github.com/w3c/webcomponents/issues/864 . It'd be great to get some actual web component users in there with their real-world use cases.
It doesn't seem litElement allows you to nest elements in the HTML page itself.
For example If I wanted to do this:
And I declared these elements like so:
It ignores the nesting and prints them like:
So whilst it keeps the nested components it's not honouring that the HTML itself should be nested inside each other.
In fact, if I remove the
<slot></slot>
it still prints it exactly the same... so it seems litElement doesn't care about the nesting of the components...Does litElement support nested components? Or is this an error in the code?