Skip to content

Instantly share code, notes, and snippets.

@jaredcwhite
Last active September 16, 2022 07:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaredcwhite/2dbab990e8d38446da6a866d8921ce6d to your computer and use it in GitHub Desktop.
Save jaredcwhite/2dbab990e8d38446da6a866d8921ce6d to your computer and use it in GitHub Desktop.
Attaching shadow roots on Turbo events
export function attachShadowRoots(root, callback = null) {
const shadowNodes = []
root.querySelectorAll("template[shadowroot]").forEach(template => {
const node = template.parentNode
shadowNodes.push(node)
const mode = template.getAttribute("shadowroot")
const shadowRoot = node.attachShadow({ mode })
shadowRoot.appendChild(template.content)
template.remove()
attachShadowRoots(shadowRoot).forEach(childNode => shadowNodes.push(childNode))
})
if (callback) {
shadowNodes.forEach(node => callback(node))
} else {
return shadowNodes
}
}
document.documentElement.addEventListener("turbo:load", (event) => {
attachShadowRoots(event.target, node => node.attachedShadowRootCallback?.())
})
document.documentElement.addEventListener("turbo:frame-load", (event) => {
attachShadowRoots(event.target, node => node.attachedShadowRootCallback?.())
})
document.documentElement.addEventListener("turbo:before-stream-render", async (event) => {
const prevRender = event.detail.render
event.detail.render = (async (newElement) => {
await prevRender(newElement)
event.target.targetElements.forEach(el => {
attachShadowRoots(el, node => node.attachedShadowRootCallback?.())
})
})
})
@jaredcwhite
Copy link
Author

How this would work in practice for an element:

class TestDsd extends HTMLElement {
  connectedCallback() {
    if (this.shadowRoot) this.attachedShadowRootCallback()
  }
  attachedShadowRootCallback() {
    // this gets called at whichever point the shadow root has been attached via the Turbo events
    console.log(this.shadowRoot.innerHTML)
  }
}
customElements.define("test-dsd", TestDsd)

@jaredcwhite
Copy link
Author

jaredcwhite commented Sep 16, 2022

Here's an even nicer way to do it using Promises and a mixin:

const DSDOM = (superClass) => class extends superClass {
  #shadowRootConnected;

  attachedShadowRootCallback() {
    this.#shadowRootConnected()
  }

  get shadowRootAttached() {
    if (this.shadowRoot) return Promise.resolve()

    const promise = new Promise(resolve => this.#shadowRootConnected = resolve)

    return promise
  }
}

class TestDsd extends DSDOM(HTMLElement) {
  async connectedCallback() {
    await this.shadowRootAttached
    console.log("I'm attached!", this.shadowRoot.innerHTML)
  }
}
customElements.define("test-dsd", TestDsd)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment