1

I'm currently diving into Webcomponents with Litelements: In Webcomponents you can change the way the shadowdom works by defining the mode property as 'open' or 'closed'. Like this in vanilla Javascript without LitElements:

Javascript:

var shadow = this.attachShadow({mode: 'open'});

from: https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow

Now in Litelements with Typescript this is achieved like this:

Typescript:

export class MyWebcomponents extends LitElement {
  static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
}

from: https://lit.dev/docs/components/shadow-dom/

he simplest way to customize the render root is to set the shadowRootOptions static property. The default implementation of createRenderRoot passes shadowRootOptions as the options argument to attachShadow when creating the component's shadow root. It can be set to customize any options allowed in the ShadowRootInit dictionary, for example mode and delegatesFocus.

To be honest, I was not able to get this to work, I tried a lot of things, like this:

Typescript:

static shadowRootOptions = {...{mode: 'open'}, delegatesFocus: true}; 

The upper try gives me an error that I'm not correctly extending the class LitElement.

Typescript:

static shadowRootOptions = {mode: 'open', delegatesFocus: true};

Gives me silly errormessage only VS Code and Typescript can produce boils down to wrong class extensions as well.

Typescript:

static shadowRootOptions = {{mode: 'open'}, delegatesFocus: true};

The upper try gives me a message telling me that Im not extending correctly and also moans about syntax.

I then tried to find out what type LitElement.shadowRootOptions is and provide something similar, but then I got into a rabbit hole of raising more questions and find this simple oneliner even more obscure. ( Yes I did read into the the spread syntax, and I think I understand it, I also read this posts according spread syntax: I don't understand about spread syntax inside objects and Is it spread "syntax" or the spread "operator"? )

So for the sake of keeping this question simple:

Can anybody point me into the right direction on how I have to write the syntax so this does work? I just want to be able to set the mode to open or close in the way its ment to be set.

Thanks a lot!

Greetings

Alex

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
Alex
  • 17
  • 5
  • The only difference between the typescript and the JavaScript should be optional static type annotations. You're almost certainly wasting your time trying to make it work without the properties from `LitElement.shadowRootOptions`. You're learning like three things together, including object spread, Shadow Dom, and how to use typescript with web components APIs and you're getting confused. Don't do it all at once. Learn the language, learn what it is and what it is not and don't confuse language with Library – Aluan Haddad Jan 19 '22 at 11:29
  • 1
    And don't confuse Tools vs. Technology. Native Web Components is the Technology, Lit (or any of the 57 alternatives, that _"make it easier"_) is the Tool. Just like jQuery had X alternatives 15 years ago, only one of those 57 will become popular.. and then obsolete again when more and more (of the good) concepts become part of the Technology. Talk to anyone one who "upgraded" from Angular 1 to Angular 2 – Danny '365CSI' Engelman Jan 19 '22 at 17:00
  • @Aluan Haddad "You're learning like three things together" Youre right, but this happens often. I have a lot of expierence in other languages. I think I know what is language, what is library etc. But still I need to know how to write it in typescript? So its either open or closed? " it work without the properties from LitElement.shadowRootOptions" I dont want to make it work without this properties, I just want to make it work, because using – Alex Jan 19 '22 at 17:12
  • Stackoverflow cut me off What I wanted to say, I dont want to make it work without this properties, I just want to know how to rewrite this line of code so its closed, because this: LitElement.shadowRootOptions Will default to mode:'open' I think. Thanks for your input! – Alex Jan 19 '22 at 17:19
  • Fair enough. I'm not questioning your overall experience or knowledge of languages generally. However when you say **“But still I need to know how to write it in typescript“** it suggests that you don't understand the TS. The way you write TS _is_ the way that you write JS. It's probably the most common misunderstanding about the language it's widespread. It sounds like you're missing some type declarations for that API. You need fix that, but in the interim you would declare some stub type or go as far as suppressing the error, but never change a value a type is missing. That's nonsense. – Aluan Haddad Jan 19 '22 at 19:38
  • Yes, I don' understand it you are right. I invested already hours in this and I can't figure it out that was the reason I was asking here. If I check the type, the interfaces etc from LitElement.shadowRootOptions I cant figure out what to do, or what I could thing of makes no sense because in the end its just a object like this: { mode : open } --> I can log it with console.log(). Its not more... Thanks anyway – Alex Jan 20 '22 at 07:08

2 Answers2

1

By default lit element is acting in mode open. So customLitElement.shadowRoot returns a shadow-root.

If you want to run it in closed mode set the options as follows:

 static shadowRootOptions = {...LitElement.shadowRootOptions, mode: 'closed', delegatesFocus: true};

<script type="module">
import {
  LitElement,
  html,
  css
} from "https://unpkg.com/lit-element/lit-element.js?module";

class ClosedEl extends LitElement {
 static shadowRootOptions = {...LitElement.shadowRootOptions, mode: 'closed', delegatesFocus: true};

 render() {
  return html`
      <h1>Example Closed</h1>
  `;
 }
}

class OpenedEl extends LitElement {

 render() {
  return html`
      <h1>Example Opened</h1>
  `;
 }
}

customElements.define("my-closed", ClosedEl);
customElements.define("my-opened", OpenedEl);

console.log(`ShadowRoot of closed El: ${document.getElementById('closed').shadowRoot}`);
console.log(`ShadowRoot of opened El: ${document.getElementById('opened').shadowRoot}`);

console.log(`Access to opened El: ${document.getElementById('opened').shadowRoot.innerHTML}`);
</script>
<my-closed id="closed"></my-closed>
<my-opened id="opened"></my-opened>

To proove, that a shadow dom is of course created, here a screenshot: screenshot of shadow dom

Christian
  • 3,503
  • 1
  • 26
  • 47
  • Thanks a lot, that was what I was looking for!!! – Alex Jan 21 '22 at 13:29
  • This is not running the shadow DOM in closed mode, this is not creating any shadow root at all. – Fox Sleigh Mar 22 '23 at 17:14
  • 1
    @FoxSleigh That's not true. Of course creates the element my-opened a shadow dom. So you should better proove it. Btw, you are rude. – Christian Mar 22 '23 at 19:40
  • Where is the proof, that the ClosedEl element has a closed shadow root? even in your screenshot `` does not showe a shadow root, and both crhromium and firefox inspections do not show one either. In `createRenderRoot` you return `this`, so the render root is the element itself and not a closed shadow root. Indeed I should have nuanced it, `` has an open shadow root. But that doesn't make your answer on how to create a closed shadow root any more correct. – Fox Sleigh Mar 26 '23 at 15:19
  • Accepted, bad description. The closed example is bad named. It renders in the main dom tree. Though, the answer was accepted. – Christian Mar 27 '23 at 08:43
  • Edited the example now to show a closed shadow root. – Christian Mar 27 '23 at 08:49
0

There are different ways to use a closed Shadow Root as the render root for a LitElement, but only one of them will allow you to get the full functionality of the LitElement.

The following code snippet shows the correct way (ClosedRoot) and the incorrect way (M̀isconfiguredRoot) to do it in JavaScript:

<script type="module">
import {
  LitElement,
  html,
  css
} from "https://unpkg.com/lit-element/lit-element.js?module";

class ClosedRoot extends LitElement {
 static shadowRootOptions = {mode: 'closed'};

 render() {
  return html`
      <h1>Example Closed</h1>
  `;
 }
 
 static styles = css`h1{background: green}`;
}

class NoRoot extends LitElement {
 createRenderRoot() {
    return this;
 } 

 render() {
  return html`
      <h1>Example Without Shadow</h1>
  `;
 }
 
 static styles = css`h1{background: green}`;
}

class OpenRoot extends LitElement {
 render() {
  return html`
      <h1>Example Opened</h1>
  `;
 }
 
 static styles = css`h1{background: green}`;
}

class MisconfiguredRoot extends LitElement {
 createRenderRoot() {
    return this.attachShadow({mode: 'closed'});
 } 

 render() {
  return html`
      <h1>Example Misconfigured Closed Shadow</h1>
  `;
 }
 
 static styles = css`h1{background: green}`;
}

customElements.define("closed-root", ClosedRoot);
customElements.define("no-root", NoRoot);
customElements.define("open-root", OpenRoot);
customElements.define("misconfigured-root", MisconfiguredRoot);

console.log(`Inaccessible closed shadow root: ${document.getElementById('closed').shadowRoot}`);
console.log(`Missing shadow root: ${document.getElementById('none').shadowRoot}`);
console.log(`Open shadow root: ${document.getElementById('open').shadowRoot}`);
</script>
<closed-root id="closed"></closed-root>
<no-root id="none"></no-root>
<open-root id="open"></open-root>
<misconfigured-root id="misconfigured"></misconfigured-root>

You can verify whether these custom elements have a Shadow DOM by inspecting them with your browser's debugging tools. The browser may also show you whether the Shadow Root is open or closed.

The Typescript compiler seems to infer the type {mode: string} for the static property shadowRootOptions, before checking whether it is compatible with the ShadowRootInit expected by the superclass LitElement. This certainly is one of the errors you might have stumbled upon. To solve this we should first try restricting the type of shadowRootOptions to the type we expect it to have. We thus add a type annotation:

static shadowRootOptions: ShadowRootInit = {mode: 'closed'};

Now the compiler knows that shadowRootOptions on your class is of the stricter type ShadowRootInit. The compiler can immediately check that 'closed' is of the type ShadowRootMode, and the compiler is happy.

Note that delegatesFocus: true is not necessary and instead modifies a different behaviour of the Shadow DOM.

Now as to why you usually shouldn't use MisconfiguredRoot, this is because it breaks some features of LitElement, such as styling using the static styles property, as shown above.

As for NoRoot, it does not create a Shadow Root at all, as evident by its missing styling and from inspecting it. There definitely are use cases for this, but you should be aware that it is completely different from a closed Shadow DOM, since you are losing all forms of encapsulation, be it style, DOM, or anything else.

Fox Sleigh
  • 327
  • 4
  • 8