Last active
December 5, 2023 02:43
-
-
Save dfkaye/e6b72291d3e31517d71229d703aad754 to your computer and use it in GitHub Desktop.
attaching shadow DOM to a built-in element: what are the implications?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 25 August 2023 | |
// attaching shadow DOM to a built-in element: | |
// what are the implications? | |
// 4 December 2023 | |
// see CSSStyleSheet() injection at bottom of this gist... | |
// The shadow DOM capability normally associated with custom elements is, to my | |
// surprise, available to normal elements, according to MDN (a.k.a "Mozilla | |
// Developer Network") - here's a quote: | |
// ``` | |
// Shadow DOM allows hidden DOM trees to be attached to elements in the regular | |
// DOM tree. | |
// ``` | |
// In addition to custom elements, however, the list of such elements is limited | |
// to the following 18 elements: | |
// | |
// article | |
// aside | |
// blockquote | |
// body | |
// div | |
// footer | |
// h1 | |
// h2 | |
// h3 | |
// h4 | |
// h5 | |
// h6 | |
// header | |
// main | |
// nav | |
// p | |
// section | |
// span | |
// Note that the elements in that list are *static* rather than interactive, as, | |
// for example, form elements, details, dialog, media (video or audio), and are | |
// semantically displayable rather than hidden, such as template elements. | |
// Using `attachShadow` on an element turns that element into the shadow *host*, | |
// the tree of descendant nodes in the element is the shadow *tree*, the root | |
// node in that tree is the *root*, and the point where the shadow DOM ends and | |
// the regular DOM begins is the *boundary* (not sure any of that helps). | |
/****************************** One implication ******************************/ | |
// We can send data updates/changes to the DOM by setting a template's attribute | |
// or a hidden element's attribute and reading the attribute changes with a | |
// mutation observer. | |
// Instead of modifying the target element, update the slot in its shadow DOM. | |
// That way a dedicated element can listen for updates from a worker that sends | |
// either data updates or HTML string updates and apply them in animation frame | |
// requests. | |
// Thesis is that this should be least-blocking (as opposed to non-blocking or | |
// render-blocking). | |
// We shall see. Here's some boilerplate for that, without the worker part... | |
// Findings: | |
// 1. The slot is visible to the console inspector. | |
// 2. The slot is not visible as content in the rendered DOM. | |
var body = document.body; | |
var div = document.createElement('div'); | |
div.style = ` | |
background: gray; | |
color: blue; | |
font-family: sans-serif; | |
outline: 1px solid aqua; | |
padding: 2em; | |
`; | |
div.attachShadow({ | |
mode: 'open', | |
delegatesFocus: true, | |
slotAssignment: 'named' | |
}); | |
body.replaceChildren(div); | |
var observer = new MutationObserver(function (list, observer) { | |
console.log("MutationObserver"); | |
for (var item of list) { | |
var { type, target, attributeName } = item; | |
console.log({ type, target, attributeName }); | |
if (attributeName == "changes") { | |
var json = target.getAttribute(attributeName); | |
var data; | |
try { data = JSON.parse(json) } | |
catch (error) { console.warn(`malformed JSON has caused "${error}".`); } | |
console.log("hasAttribute", attributeName, target.hasAttribute(attributeName)); | |
console.log({json}); | |
console.log({data}) | |
} | |
} | |
}); | |
observer.observe(div, { | |
attributes: true, | |
childList: true, | |
subtree: true | |
}); | |
var slot = document.createElement("slot"); | |
slot.setAttribute("name", "changes"); | |
slot.style = "background: blue; padding: 1em;"; | |
div.appendChild(slot); | |
console.log(slot === div.children["changes"]); | |
var data = { | |
"profile": { | |
"field": 30, | |
"radio": "individual", | |
"checkbox": "red", | |
//"checkbox2": "aqua", | |
"array": [true, false], | |
"address": { | |
"street": "1234 5th Street", | |
"street2": "Suite Sixteen", | |
"city": null, | |
"state": NaN, | |
"postalCode": {} | |
} | |
}, | |
"right": { "path" : { "name": "wrong keyname" } } | |
}; | |
// update set attribute | |
div.children["changes"].setAttribute("changes", JSON.stringify(data)); | |
// update slot content | |
div.children["changes"].textContent = JSON.stringify(data); | |
// update own attribute | |
div.setAttribute("changes", JSON.stringify(data)); | |
////////////////////////////////////////////////////////////////////////// | |
// 4 December 2023 | |
// add styleSheet to shadowRoot via CSSStyleSheet() and adoptedStyleSheets | |
////////////////////////////////////////////////////////////////////////// | |
var sheet = new CSSStyleSheet(); | |
sheet.replaceSync("p { color: red; border: 2px dotted black;}"); | |
div.shadowRoot.adoptedStyleSheets = [sheet]; | |
var p = document.createElement("p"); | |
p.textContent = " crazy content "; | |
div.shadowRoot.appendChild(p); | |
// verify shadow style doesn't leak out | |
document.body.appendChild(p.cloneNode(true)); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment