PageSourceSearch

https://accounts.purwakartakab.go.id/assets/controllers/theme_controller-2Lz_afx.js

js purwakartakab.go.id collected 2026-10-02 16:49:05 UTC 6,890 bytes, 190 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Connects to data-controller="theme"
4export default class extends Controller {
5    static targets = ["toggle", "iconMoon", "iconSun", "switcher", "activeIcon"]
6    static values = { mode: String }
7
8    connect() {
9        // Initialize theme based on stored preference or system preference
10        this.initializeTheme()
11        
12        // Listen for system theme changes
13        if (window.matchMedia) {
14            window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
15                const storedTheme = this.getStoredTheme()
16                if (storedTheme === 'auto' || !storedTheme) {
17                    this.applyTheme(e.matches ? 'dark' : 'light')
18                    this.updateUI()
19                }
20            })
21        }
22    }
23
24    initializeTheme() {
25        const storedTheme = this.getStoredTheme()
26        const preferredTheme = this.getPreferredTheme()
27        
28        this.applyTheme(this.resolveTheme(preferredTheme))
29        this.updateUI()
30    }
31
32    getStoredTheme() {
33        return localStorage.getItem('theme')
34    }
35
36    getPreferredTheme() {
37        const storedTheme = this.getStoredTheme()
38        if (storedTheme) {
39            return storedTheme
40        }
41        return 'auto'
42    }
43
44    resolveTheme(theme) {
45        if (theme === 'auto') {
46            return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
47        }
48        return theme
49    }
50
51    // For backwards compatibility with simple toggle
52    toggle() {
53        const currentTheme = document.documentElement.getAttribute('data-bs-theme')
54        const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
55        this.setTheme(newTheme)
56    }
57
58    // New method for dropdown theme selection
59    selectTheme(event) {
60        const theme = event.currentTarget.getAttribute('data-bs-theme-value')
61        this.setTheme(theme)
62        
63        // Close dropdown if Bootstrap is available
64        const dropdown = event.currentTarget.closest('.dropdown')
65        if (dropdown && window.bootstrap) {
66            const dropdownInstance = bootstrap.Dropdown.getInstance(dropdown.querySelector('[data-bs-toggle="dropdown"]'))
67            if (dropdownInstance) {
68                dropdownInstance.hide()
69            }
70        }
71    }
72
73    setTheme(theme) {
74        // Store the preference
75        localStorage.setItem('theme', theme)
76        
77        // Apply the resolved theme
78        this.applyTheme(this.resolveTheme(theme))
79        
80        // Update UI
81        this.updateUI()
82        
83        // Dispatch event for other controllers
84        const event = new CustomEvent('theme:themeChanged', {
85            detail: { 
86                theme: theme,
87                resolvedTheme: this.resolveTheme(theme),
88                systemTheme: this.getSystemTheme()
89            }
90        })
91        document.dispatchEvent(event)
92        
93        console.log(`Theme set to: ${theme} (resolved: ${this.resolveTheme(theme)})`)
94    }
95
96    applyTheme(resolvedTheme) {
97        // Set theme on document using Bootstrap's data-bs-theme attribute
98        document.documentElement.setAttribute('data-bs-theme', resolvedTheme)
99    }
100
101    updateUI() {
102        const preferredTheme = this.getPreferredTheme()
103        
104        // Update simple toggle button (backwards compatibility)
105        this.updateToggleButton(this.resolveTheme(preferredTheme))
106        
107        // Update dropdown theme switcher
108        this.updateThemeSwitcher(preferredTheme)
109    }
110
111    updateToggleButton(resolvedTheme) {
112        // Handle both old toggle button and new dropdown button
113        if (this.hasIconMoonTarget && this.hasIconSunTarget && this.hasToggleTarget) {
114            // Original toggle button logic
115            if (resolvedTheme === 'dark') {
116                this.iconMoonTarget.style.display = 'inline-block'
117                this.iconSunTarget.style.display = 'none'
118                this.toggleTarget.title = 'Current theme: Dark. Click to change theme.'
119            } else {
120                this.iconMoonTarget.style.display = 'none'
121                this.iconSunTarget.style.display = 'inline-block'
122                this.toggleTarget.title = 'Current theme: Light. Click to change theme.'
123            }
124        }
125        
126        // Handle dropdown theme button
127        const sunIcon = this.element.querySelector('[data-theme-target="iconSun"]')
128        const moonIcon = this.element.querySelector('[data-theme-target="iconMoon"]')
129        const mainButton = this.element.querySelector('#bd-theme')
130        
131        if (sunIcon && moonIcon) {
132            if (resolvedTheme === 'dark') {
133                moonIcon.style.display = 'inline-block'
134                moonIcon.classList.remove('d-none')
135                sunIcon.style.display = 'none'
136                sunIcon.classList.add('d-none')
137                if (mainButton) {
138                    mainButton.setAttribute('aria-label', 'Current theme: Dark. Click to change theme.')
139                }
140            } else {
141                sunIcon.style.display = 'inline-block'
142                sunIcon.classList.remove('d-none')
143                moonIcon.style.display = 'none'
144                moonIcon.classList.add('d-none')
145                if (mainButton) {
146                    mainButton.setAttribute('aria-label', 'Current theme: Light. Click to change theme.')
147                }
148            }
149        }
150    }
151
152    updateThemeSwitcher(preferredTheme) {
153        // Update dropdown items
154        this.element.querySelectorAll('[data-bs-theme-value]').forEach(element => {
155            element.classList.remove('active')
156            element.setAttribute('aria-pressed', 'false')
157            
158            // Hide check mark
159            const checkIcon = element.querySelector('svg.ms-auto')
160            if (checkIcon) {
161                checkIcon.classList.add('d-none')
162            }
163        })
164        
165        // Show active theme
166        const activeButton = this.element.querySelector(`[data-bs-theme-value="${preferredTheme}"]`)
167        if (activeButton) {
168            activeButton.classList.add('active')
169            activeButton.setAttribute('aria-pressed', 'true')
170            
171            // Show check mark
172            const checkIcon = activeButton.querySelector('svg.ms-auto')
173            if (checkIcon) {
174                checkIcon.classList.remove('d-none')
175            }
176        }
177        
178        // Update main button icon based on resolved theme (reuse existing toggle logic)
179        const resolvedTheme = this.resolveTheme(preferredTheme)
180        this.updateToggleButton(resolvedTheme)
181    }
182
183    getSystemTheme() {
184        return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
185    }
186
187    hasStoredPreference() {
188        return localStorage.getItem('theme') !== null
189    }
190}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.