Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active December 5, 2023 02:43
Show Gist options
  • Save dfkaye/e6b72291d3e31517d71229d703aad754 to your computer and use it in GitHub Desktop.
Save dfkaye/e6b72291d3e31517d71229d703aad754 to your computer and use it in GitHub Desktop.
attaching shadow DOM to a built-in element: what are the implications?
// 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