From 272b8acd66ab267c8cc1ab36d35f3bafb004dbb8 Mon Sep 17 00:00:00 2001 From: Guillem Arias Fauste Date: Wed, 20 May 2026 18:13:07 +0200 Subject: [PATCH] feat(theme): broadcast theme:change so SVG/canvas consumers can repaint (#1839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `theme_controller#setTheme` already toggles `data-theme` on the document element, but D3/SVG/canvas consumers that bake color into attributes (`fill`, `stroke`, `stop-color`) can't observe a CSS variable change — they need an imperative re-render hook. Dispatch a `theme:change` CustomEvent on the document element after the attribute flips, with `detail: { theme: "dark" | "light" }`. Consumers subscribe via standard connect/disconnect listeners. Refactor the if/else into a single path while at it — same behavior, half the lines. Closes #1764. --- app/javascript/controllers/theme_controller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/javascript/controllers/theme_controller.js b/app/javascript/controllers/theme_controller.js index 361ae95ed..ff64fd41c 100644 --- a/app/javascript/controllers/theme_controller.js +++ b/app/javascript/controllers/theme_controller.js @@ -39,15 +39,15 @@ export default class extends Controller { } } - // Sets or removes the data-theme attribute + // Sets the data-theme attribute and broadcasts a `theme:change` event so + // imperative consumers (D3/SVG/canvas) can repaint without polling. setTheme(isDark) { - if (isDark) { - localStorage.theme = "dark"; - document.documentElement.setAttribute("data-theme", "dark"); - } else { - localStorage.theme = "light"; - document.documentElement.setAttribute("data-theme", "light"); - } + const theme = isDark ? "dark" : "light"; + localStorage.theme = theme; + document.documentElement.setAttribute("data-theme", theme); + document.documentElement.dispatchEvent( + new CustomEvent("theme:change", { detail: { theme } }), + ); } systemPrefersDark() {