1- import { usePollingState } from '~/states/polling' ;
2-
31// ============================================================================
42// Constants
53// ============================================================================
64const SHOW_DELAY = 150 ;
75const MIN_DURATION = 300 ;
86
97// ============================================================================
10- // Global State (shared across all components )
8+ // Global State (module-level singleton )
119// ============================================================================
12- const loaderState = ( ) => useState < number > ( 'loader_active_requests' , ( ) => 0 ) ;
13- const loaderVisible = ( ) => useState < boolean > ( 'loader_visible' , ( ) => false ) ;
10+ const activeRequests = ref < number > ( 0 ) ;
11+ const isVisible = ref < boolean > ( false ) ;
1412
15- // Timer references (module-level for singleton behavior)
13+ // Timer references
1614let showDelayTimer : ReturnType < typeof setTimeout > | null = null ;
1715let minDurationTimer : ReturnType < typeof setTimeout > | null = null ;
18- let visibleSince : number = 0 ;
16+ let visibleSince = 0 ;
1917
2018export function useLoader ( ) {
21- // ============================================================================
22- // Composables
23- // ============================================================================
24- const { isPolling } = usePollingState ( ) ;
25-
26- // ============================================================================
27- // Variables
28- // ============================================================================
29- const activeRequests = loaderState ( ) ;
30- const isVisible = loaderVisible ( ) ;
31-
32- // ============================================================================
33- // Computed Properties
34- // ============================================================================
35- const shouldShow = computed < boolean > ( ( ) => activeRequests . value > 0 && ! isPolling . value ) ;
36-
3719 // ============================================================================
3820 // Functions
3921 // ============================================================================
@@ -53,102 +35,63 @@ export function useLoader() {
5335 }
5436
5537 /**
56- * Show the loader after delay
57- */
58- function scheduleShow ( ) : void {
59- if ( showDelayTimer || isVisible . value ) {
60- return ;
61- }
62-
63- showDelayTimer = setTimeout ( ( ) => {
64- showDelayTimer = null ;
65- if ( shouldShow . value ) {
66- isVisible . value = true ;
67- visibleSince = Date . now ( ) ;
68- }
69- } , SHOW_DELAY ) ;
70- }
71-
72- /**
73- * Hide the loader, respecting minimum duration
74- */
75- function scheduleHide ( ) : void {
76- if ( ! isVisible . value ) {
77- clearTimers ( ) ;
78- return ;
79- }
80-
81- const elapsed : number = Date . now ( ) - visibleSince ;
82- const remaining : number = MIN_DURATION - elapsed ;
83-
84- if ( remaining <= 0 ) {
85- isVisible . value = false ;
86- clearTimers ( ) ;
87- } else {
88- if ( minDurationTimer ) {
89- return ;
90- }
91-
92- minDurationTimer = setTimeout ( ( ) => {
93- minDurationTimer = null ;
94- if ( ! shouldShow . value ) {
95- isVisible . value = false ;
96- }
97- } , remaining ) ;
98- }
99- }
100-
101- /**
102- * Update visibility based on current state
38+ * Update loader visibility with anti-flicker logic
10339 */
10440 function updateVisibility ( ) : void {
105- if ( shouldShow . value && ! isVisible . value ) {
106- scheduleShow ( ) ;
107- } else if ( ! shouldShow . value && isVisible . value ) {
108- scheduleHide ( ) ;
109- } else if ( ! shouldShow . value && ! isVisible . value ) {
41+ const shouldShow : boolean = activeRequests . value > 0 ;
42+
43+ if ( shouldShow && ! isVisible . value && ! showDelayTimer ) {
44+ // Schedule showing after delay
45+ showDelayTimer = setTimeout ( ( ) => {
46+ showDelayTimer = null ;
47+ if ( activeRequests . value > 0 ) {
48+ isVisible . value = true ;
49+ visibleSince = Date . now ( ) ;
50+ }
51+ } , SHOW_DELAY ) ;
52+ } else if ( ! shouldShow && isVisible . value ) {
53+ // Hide with minimum duration
54+ const elapsed : number = Date . now ( ) - visibleSince ;
55+ const remaining : number = MIN_DURATION - elapsed ;
56+
57+ if ( remaining <= 0 ) {
58+ isVisible . value = false ;
59+ clearTimers ( ) ;
60+ } else if ( ! minDurationTimer ) {
61+ minDurationTimer = setTimeout ( ( ) => {
62+ minDurationTimer = null ;
63+ if ( activeRequests . value === 0 ) {
64+ isVisible . value = false ;
65+ }
66+ } , remaining ) ;
67+ }
68+ } else if ( ! shouldShow && ! isVisible . value ) {
11069 clearTimers ( ) ;
11170 }
11271 }
11372
11473 /**
115- * Increment active request count
74+ * Start loading indicator
11675 */
117- function increment ( ) : void {
76+ function start ( ) : void {
11877 activeRequests . value ++ ;
11978 updateVisibility ( ) ;
12079 }
12180
12281 /**
123- * Decrement active request count
82+ * Stop loading indicator
12483 */
125- function decrement ( ) : void {
84+ function stop ( ) : void {
12685 if ( activeRequests . value > 0 ) {
12786 activeRequests . value -- ;
12887 }
12988 updateVisibility ( ) ;
13089 }
13190
132- /**
133- * Legacy: Start loading (alias for increment)
134- */
135- function start ( ) : void {
136- increment ( ) ;
137- }
138-
139- /**
140- * Legacy: Stop loading (alias for decrement)
141- */
142- function stop ( ) : void {
143- decrement ( ) ;
144- }
145-
14691 // ============================================================================
14792 // Return
14893 // ============================================================================
14994 return {
150- decrement,
151- increment,
15295 loading : readonly ( isVisible ) ,
15396 start,
15497 stop,
0 commit comments