From be91d30f8d090d7459dc1b6edcb2e83656132d18 Mon Sep 17 00:00:00 2001 From: Onyeka Obi Date: Tue, 31 Mar 2026 07:26:34 -0700 Subject: [PATCH] Isolate dev overlay from user CSS via Shadow DOM Wraps the Lamdera devbar in a custom element that renders content inside a shadow root, preventing user stylesheets from leaking into the overlay (fixes #37). Elm's vdom patcher is kept working by proxying DOM methods (appendChild, childNodes, etc.) into the shadow container. Gracefully degrades to current behavior on browsers without custom element support. --- extra/LocalDev/runtime-src/Lamdera/Live.elm | 12 +++++--- extra/live.js | 32 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/extra/LocalDev/runtime-src/Lamdera/Live.elm b/extra/LocalDev/runtime-src/Lamdera/Live.elm index e701df59c..07d48b739 100644 --- a/extra/LocalDev/runtime-src/Lamdera/Live.elm +++ b/extra/LocalDev/runtime-src/Lamdera/Live.elm @@ -1726,10 +1726,14 @@ mapDocument model msg { title, body } = { title = title , body = List.map (Html.map msg) body - ++ lamderaUI - model.devbar - model.resetModelNames - model.nodeType + ++ [ Html.node "lamdera-devbar" + [] + (lamderaUI + model.devbar + model.resetModelNames + model.nodeType + ) + ] } diff --git a/extra/live.js b/extra/live.js index 19c0ffbb5..04fca940e 100644 --- a/extra/live.js +++ b/extra/live.js @@ -6,6 +6,38 @@ import * as Sockette from 'sockette'; import * as Cookie from 'js-cookie'; +// Custom element that isolates Lamdera's dev overlay from user CSS. +// Elm's vdom patcher calls standard DOM methods (appendChild, childNodes, etc.) +// on parent nodes. By proxying these to a container inside a shadow root, Elm +// renders seamlessly while the shadow boundary blocks external stylesheets. +// Inherited CSS properties are cut off by `all: initial` on the host. +if (typeof customElements !== 'undefined') { + class LamderaDevbar extends HTMLElement { + constructor() { + super(); + var shadow = this.attachShadow({ mode: 'open' }); + var style = document.createElement('style'); + style.textContent = ':host { all: initial; display: block; }'; + shadow.appendChild(style); + this._c = document.createElement('div'); + shadow.appendChild(this._c); + } + appendChild(n) { return this._c.appendChild(n); } + insertBefore(n, r) { return this._c.insertBefore(n, r); } + removeChild(n) { return this._c.removeChild(n); } + replaceChild(n, o) { return this._c.replaceChild(n, o); } + get childNodes() { return this._c.childNodes; } + get firstChild() { return this._c.firstChild; } + get lastChild() { return this._c.lastChild; } + get children() { return this._c.children; } + get innerHTML() { return this._c.innerHTML; } + set innerHTML(v) { this._c.innerHTML = v; } + get textContent() { return this._c.textContent; } + set textContent(v) { this._c.textContent = v; } + } + customElements.define('lamdera-devbar', LamderaDevbar); +} + var clientId = "" const sessionId = getSessionId() var connected = false