import {Controller} from '@hotwired/stimulus' export default class extends Controller { static targets = ['indicator'] static classes = ['hidden'] connect() { this.isVisible = false this.debounceTimeout = null // Bind event handlers to preserve context this.boundHandleBeforeRequest = this.handleBeforeRequest.bind(this) this.boundHandleAfterRequest = this.handleAfterRequest.bind(this) this.boundHandleTimeout = this.handleTimeout.bind(this) this.boundHandlePageShow = this.handlePageShow.bind(this) this.boundHandleHistoryRestore = this.handleHistoryRestore.bind(this) // Listen to HTMX events document.body.addEventListener('htmx:beforeRequest', this.boundHandleBeforeRequest) document.body.addEventListener('htmx:afterRequest', this.boundHandleAfterRequest) document.body.addEventListener('htmx:timeout', this.boundHandleTimeout) document.body.addEventListener('htmx:historyRestore', this.boundHandleHistoryRestore) // Listen for browser back/forward navigation window.addEventListener('pageshow', this.boundHandlePageShow) } disconnect() { // Clean up event listeners document.body.removeEventListener('htmx:beforeRequest', this.boundHandleBeforeRequest) document.body.removeEventListener('htmx:afterRequest', this.boundHandleAfterRequest) document.body.removeEventListener('htmx:timeout', this.boundHandleTimeout) document.body.removeEventListener('htmx:historyRestore', this.boundHandleHistoryRestore) window.removeEventListener('pageshow', this.boundHandlePageShow) // Clear any pending timeout if (this.debounceTimeout) { clearTimeout(this.debounceTimeout) } } handleBeforeRequest(event) { // Don't start a new debounce if already visible if (this.isVisible) { return } // Show indicator after 200ms delay for field refreshes this.debounceTimeout = setTimeout(() => { this.show() }, 200) } handleAfterRequest(event) { // Clear debounce timer if request completes before 200ms if (this.debounceTimeout) { clearTimeout(this.debounceTimeout) this.debounceTimeout = null } // Check if response contains HX-Redirect header const xhr = event.detail.xhr const hxRedirect = xhr.getResponseHeader('HX-Redirect') // Keep loading indicator visible if redirecting if (hxRedirect) { // Ensure indicator is visible during redirect if (!this.isVisible) { this.show() } return } // Hide indicator if it's visible this.hide() } handleTimeout(event) { // Hide loading indicator this.hide() // Show user-friendly error message alert('Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut. Falls das Problem weiterhin besteht, kontaktieren Sie bitte unseren Support.') } handlePageShow(event) { // event.persisted is true when page is restored from bfcache (back/forward navigation) if (event.persisted) { this.hide() } } handleHistoryRestore(event) { // HTMX history restore - hide loading indicator this.hide() } show() { this.isVisible = true this.indicatorTarget.classList.remove(this.hiddenClass) } hide() { this.isVisible = false this.indicatorTarget.classList.add(this.hiddenClass) } toggle() { this.indicatorTarget.classList.remove(this.hiddenClass) } }