1

I am trying to add copy/paste and print functionality to a tree of nested lit-elements. I am trying to get the nested components to render so I may push the full html+style in a string into the copy/paste buffer and also into an iframe or hidden div for printing.

The code can be found here: https://mjmihk.stackblitz.io

My approach is to select the parent and get the shadow DOM's contents, but this appears to yield a 'shallow rendering'. I would expect to see the <p> tags nested within the bounds of the <child-greeting> tags.

var elem: any = document.querySelector("simple-greeting");
var cpRoot = elem.shadowRoot.querySelector("#cproot");

When trying this, the copy paste content is not 'deeply' rendering the child elements:

<p>Hello, World!</p>
<child-greeting name="Peter"></child-greeting>
<child-greeting name="Constance"></child-greeting>

Any help here appreciated!

Sean
  • 53
  • 7

1 Answers1

0

It's much easier to help with this with the source (rather than the output) link at https://stackblitz.com/edit/mjmihk

First of all:

var elem: any = document.querySelector("simple-greeting");

Gets the first instance of this component in the document, so will fail if you have more than one. You don't need this, as any event subscribed with Lit's @ syntax will be called with this as the current element.

That means your next line should be:

const cpRoot = this.shadowRoot.querySelector("#cproot");

However, there's a better way again to get that using LitElement's @query decorator:

@query('#cproot') private cpRoot: HTMLDivElement;

_handlePrintClick() {
    console.log("copy/paste/print content:", this.cpRoot.innerHTML);
}

Your next problem is innerHTML - this gets the light DOM. With web components you now have two types of document - the HTML you put inside the component, and a new isolated HTML that component owns.

For instance, if you had:

<child-greeting name="Peter">
    #shadow-root [<p>I am the child. ...!</p>]

    <p>Something else about Peter</p>
</child-greeting>

(And child-greeting had a <slot>) then innerHTML will give you <p>Something about Peter</p>, which is the light DOM. Your shadow DOM (the <p>I am the child. ...!</p> bit) isn't included.

For most custom elements this is exactly what you want, as the shadow DOM will hold the component part - the dropdown on a select or date picker, the action buttons on a rich text input, etc. Many browsers use then internally - for instance <video> has shadow DOM that renders the play button, progress bar, etc.

You have two options:

  1. Break shadow DOM on elements you want to expose their content to innerHTML. You can do this with LitElement by overriding the method that creates the shadow DOM fragment to return the control instead:
createRenderRoot() {
    return this;
}
  1. Access the DOM directly rather than using innerHTML. This would require recursion down the tree checking for shadowRoot on every element, and any that used <slot> will have to have their DOM tree rebuilt to put nested light DOM content into it.

Neither of these is particularly easy, and both have compromises.

Keith
  • 150,284
  • 78
  • 298
  • 434