Skip to content
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

Declarative Shadow DOM #1834

Open
o-t-w opened this issue Aug 1, 2023 · 8 comments
Open

Declarative Shadow DOM #1834

o-t-w opened this issue Aug 1, 2023 · 8 comments
Labels
enhancement New feature or request

Comments

@o-t-w
Copy link

o-t-w commented Aug 1, 2023

Describe the bug

I would like to have the ability to use declarative shadow DOM (without custom elements) within Solid components just to get style scoping. It doesn't work and I get the error Uncaught TypeError: Cannot read properties of null (reading 'nextSibling')

Your Example Website or App

https://codesandbox.io/p/sandbox/awesome-sky-8fp8z5

Steps to Reproduce the Bug or Issue

export default function ShadowTest() {
  return (
    <div>
      <template shadowrootmode="open">
        <p>This is in the shadow DOM</p>
        <slot>
          <p>This is in the light DOM</p>
          <button>Light button</button>
        </slot>
        <button>Shadow button</button>
      </template>
    </div>
  );
}

Expected behavior

It should render a shadow tree.

Screenshots or Videos

No response

Platform

  • OS: all
  • Browser: Chrome + Safari (Firefox hasn't shipped support for it yet)

Additional context

No response

@ryansolid
Copy link
Member

As a test I just tried in Chrome:

const d = document.createElement("div")
d.innerHTML = `<div>
      <template shadowrootmode="open">
        <p>This is in the shadow DOM</p>
        <slot>
          <p>This is in the light DOM</p>
          <button>Light button</button>
        </slot>
        <button>Shadow button</button>
      </template>
    </div>`

And I got:

Found declarative shadowrootmode attribute on a template, but declarative Shadow DOM has not been enabled by includeShadowRoots.

I was just trying to figure out what the parsing would look like for this. I think it might start with identifying with template elements we need to walk into .content before continuing into firstChild/nextSibling. That would probably fix the error assuming that it stays in the DOM like that and doesn't get swallowed. Does the template element actually have active nodes in this scenario? Most template elements contain only inert nodes.

@o-t-w
Copy link
Author

o-t-w commented Aug 2, 2023

This section of this article might answer that

@ryansolid ryansolid added the enhancement New feature or request label Aug 2, 2023
@ivancuric
Copy link

ivancuric commented Aug 3, 2023

This functionality is already present for Portals using the useShadow prop.

Is it possible to expand this functionality to a generic utility component, eg something like <Shadow> ?

@trusktr
Copy link
Contributor

trusktr commented Aug 9, 2023

A simple workaround is to make a <ShadowRoot> component, and then create the shadow root with JavaScript in there, then finally pass props.children along.

Something like the following (I wrote it quickly, didn't test it, but it shows idea):

function ShadowRoot(props) {
  let div

  onMount(() => {
    const root = div.attachShadow(...)
    render(props.children, root) // or similar
  })

  return <div ref={div}></div>
}

function UsageExample() {
  return <ShadowRoot>
    <div>hello<div>
    <style>{`
      /* scoped style */
      div { background: blue }
    `}</style>
  </ShadowRoot>
}

But this is not compatible with SSR, it creates a new root, and contexts won't automatically flow into the new root, etc. Other than that it has worked great in some places where I used it.

Declarative shadow roots will be, and that'll be really nice! Every framework that doesn't have scoped styles out of the box is gonna now have scoped styles out of the box, with the same sorts of patterns as custom elements.

@o-t-w
Copy link
Author

o-t-w commented May 8, 2024

Revisiting this. innerHTML doesn't play nicely with declarative shadow DOM. When you inject HTML containing <template shadowrootmode="open"> into the page with innerHTML, it remains just a template, whereas it's meant to instantly become shadow DOM. innerHTML doesn't understand shadowrootmode.

There is a new setHTMLUnsafe method (supported in all browsers) and getHTML method (supported only in Chrome 125) that together act as a declarative shadow DOM friendly alternative to innerHTML.

@o-t-w
Copy link
Author

o-t-w commented May 8, 2024

The documentation for all of this is currently pretty terrible so I wrote about it here: https://fullystacked.net/innerhtml-alternatives/

@BleedingDev
Copy link

This would epic to have it working. Combine it with solidjs/solid-router#459 and we can get awesome power of Solid.js together with Server Actions and Server Rendering. ❤️

@ivancuric
Copy link

This seems to work well:

/**
 * A declarative shadow root component
 *
 * Hooks into SolidJS' Portal's `useShadow` prop
 * to handle shadow DOM and the component lifecycle
 */
const ShadowRoot: ParentComponent = (props) => {
  let div: HTMLDivElement;
  return (
    <div ref={div!}>
      <Portal mount={div!} useShadow={true}>
        {props.children}
      </Portal>
    </div>
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants