import { Controller } from "@hotwired/stimulus"; export default class extends Controller { static values = { userPreference: String }; connect() { this.startSystemThemeListener(); } disconnect() { this.stopSystemThemeListener(); } // Called automatically by Stimulus when the userPreferenceValue changes (e.g., after form submit/page reload) userPreferenceValueChanged() { this.applyTheme(); } // Called when a theme radio button is clicked updateTheme(event) { const selectedTheme = event.currentTarget.value; if (selectedTheme === "system") { this.setTheme(this.systemPrefersDark()); } else if (selectedTheme === "dark") { this.setTheme(true); } else { this.setTheme(false); } } // Applies theme based on the userPreferenceValue (from server) applyTheme() { if (this.userPreferenceValue === "system") { this.setTheme(this.systemPrefersDark()); } else if (this.userPreferenceValue === "dark") { this.setTheme(true); } else { this.setTheme(false); } } // Sets the data-theme attribute and broadcasts a `theme:change` event so // imperative consumers (D3/SVG/canvas) can repaint without polling. setTheme(isDark) { const theme = isDark ? "dark" : "light"; localStorage.theme = theme; document.documentElement.setAttribute("data-theme", theme); document.documentElement.dispatchEvent( new CustomEvent("theme:change", { detail: { theme } }), ); } systemPrefersDark() { return window.matchMedia("(prefers-color-scheme: dark)").matches; } handleSystemThemeChange = (event) => { // Only apply system theme changes if the user preference is currently 'system' if (this.userPreferenceValue === "system") { this.setTheme(event.matches); } }; toggle() { const currentTheme = document.documentElement.getAttribute("data-theme"); if (currentTheme === "dark") { this.setTheme(false); } else { this.setTheme(true); } } startSystemThemeListener() { this.darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); this.darkMediaQuery.addEventListener( "change", this.handleSystemThemeChange, ); } stopSystemThemeListener() { if (this.darkMediaQuery) { this.darkMediaQuery.removeEventListener( "change", this.handleSystemThemeChange, ); } } }