11import type { Ref } from 'vue' ;
22import { onUnmounted , ref } from 'vue' ;
33import { createSharedComposable } from '@vueuse/core' ;
4- import type { AlertOptions , AlertType } from './Alert.types' ;
4+ import type { AlertOptions } from './Alert.types' ;
55
66/**
77 * Return values of useAlert composable
@@ -52,22 +52,51 @@ export const useAlert = createSharedComposable((): UseAlertComposableState => {
5252 const maxAlerts = 10 ; // Default maximum number of alerts
5353 const alerts = ref < AlertOptions [ ] > ( [ ] ) ;
5454 const animationFrameIds = new Map < number , number > ( ) ;
55+ const ANIMATION_DELAY = 50 ; // ms delay for smooth animations
5556
57+ // Batch removal of expired alerts to prevent layout thrashing
5658 function removeExpiredAlerts ( ) : void {
5759 const currentTime = new Date ( ) . getTime ( ) ;
58-
59- alerts . value = alerts . value . filter ( alert => alert . timeout > currentTime ) ;
60+ // Check if any alerts have expired by comparing their timeout
61+ // timestamps to the current time. If any have expired, we'll
62+ // need to remove them from the list of visible alerts.
63+ const hasExpiredAlerts = alerts . value . some (
64+ alert => alert . timeout <= currentTime
65+ ) ;
66+
67+ if ( hasExpiredAlerts ) {
68+ // Use requestAnimationFrame to batch DOM updates
69+ requestAnimationFrame ( ( ) => {
70+ alerts . value = alerts . value . filter ( alert => alert . timeout > currentTime ) ;
71+ } ) ;
72+ }
6073 }
6174
6275 function scheduleRemoval ( alertId : number , timeout : number ) : void {
6376 const startTime = performance . now ( ) ;
77+ let lastFrameTime = startTime ;
78+ const FRAME_TIME_MS = 16.67 ; // ~60fps
79+ const removalThreshold = FRAME_TIME_MS * 2 ; // ~2 frames at 60fps
6480
6581 const checkExpiry = ( timestamp : number ) : void => {
66- const elpased = timestamp - startTime ;
82+ // Skip frames if we're calling too frequently
83+ if ( timestamp - lastFrameTime < removalThreshold ) {
84+ const frameId = requestAnimationFrame ( checkExpiry ) ;
85+
86+ animationFrameIds . set ( alertId , frameId ) ;
87+
88+ return ;
89+ }
90+ lastFrameTime = timestamp ;
91+
92+ const elapsed = timestamp - startTime ;
6793
68- if ( elpased >= timeout ) {
69- removeExpiredAlerts ( ) ;
70- animationFrameIds . delete ( alertId ) ;
94+ if ( elapsed >= timeout ) {
95+ // Use a slight delay to ensure smooth animation
96+ setTimeout ( ( ) => {
97+ removeExpiredAlerts ( ) ;
98+ animationFrameIds . delete ( alertId ) ;
99+ } , ANIMATION_DELAY ) ;
71100 } else {
72101 const frameId = requestAnimationFrame ( checkExpiry ) ;
73102
@@ -82,41 +111,45 @@ export const useAlert = createSharedComposable((): UseAlertComposableState => {
82111
83112 /**
84113 * Trigger alert component
85- * @param type alert type (success, error, warning, info and default)
86- * @param opt alert optiontimeout, message and icon)
114+ * @param opt alert options
87115 */
88- function triggerAlert ( type : AlertType , opt : AlertOptions ) : void {
116+ function triggerAlert ( opt : AlertOptions ) : void {
89117 if ( opt . timeout === Infinity ) {
90118 return ;
91119 }
92120
93121 const currentTime = new Date ( ) . getTime ( ) ;
94122 const currentTimeout = currentTime + opt . timeout ;
95123
96- if ( alerts . value . length >= maxAlerts ) {
97- // Find and remove the oldest alert (smallest ID)
98- const oldestAlert = alerts . value . reduce ( ( prev , current ) => {
99- if ( prev ?. id === undefined || current . id === undefined ) {
100- return prev ;
101- }
102-
103- return ( prev . id < current . id ) ? prev : current ;
104- } ) ;
105-
106- alerts . value = alerts . value . filter ( alert => alert . id !== oldestAlert ?. id ) ;
107- }
124+ // Use requestAnimationFrame to batch DOM updates
125+ requestAnimationFrame ( ( ) => {
126+ if ( alerts . value . length >= maxAlerts ) {
127+ // Find and remove the oldest alert (smallest ID)
128+ const oldestAlert = alerts . value . reduce ( ( prev , current ) => {
129+ if ( prev ?. id === undefined || current . id === undefined ) {
130+ return current ;
131+ }
132+
133+ return ( prev . id < current . id ) ? prev : current ;
134+ } ) ;
135+
136+ // Remove the oldest alert
137+ alerts . value = alerts . value . filter ( alert => alert . id !== oldestAlert ?. id ) ;
138+ }
108139
109- const newAlert = {
110- ...opt ,
111- id : counter . value ++ ,
112- type,
113- timeout : currentTimeout ,
114- } ;
140+ const newAlert = {
141+ ...opt ,
142+ id : counter . value ++ ,
143+ timeout : currentTimeout ,
144+ } ;
115145
116- alerts . value = [ newAlert , ...alerts . value ] ;
146+ // Add new alert at the beginning of the array
147+ alerts . value = [ newAlert , ...alerts . value ] ;
117148
118- requestAnimationFrame ( ( ) => {
119- scheduleRemoval ( Number ( newAlert . id ) , opt . timeout ) ;
149+ // Schedule removal with a small delay to ensure smooth animation
150+ setTimeout ( ( ) => {
151+ scheduleRemoval ( Number ( newAlert . id ) , opt . timeout ) ;
152+ } , ANIMATION_DELAY ) ;
120153 } ) ;
121154 }
122155
@@ -130,10 +163,15 @@ export const useAlert = createSharedComposable((): UseAlertComposableState => {
130163
131164 return {
132165 alerts,
133- success : ( opt : Omit < AlertOptions , 'id' > ) => triggerAlert ( 'success' , opt ) ,
134- error : ( opt : Omit < AlertOptions , 'id' > ) => triggerAlert ( 'error' , opt ) ,
135- warning : ( opt : Omit < AlertOptions , 'id' > ) => triggerAlert ( 'warning' , opt ) ,
136- info : ( opt : Omit < AlertOptions , 'id' > ) => triggerAlert ( 'info' , opt ) ,
137- alert : ( opt : Omit < AlertOptions , 'id' > ) => triggerAlert ( 'default' , opt ) ,
166+ success : ( opt : Omit < AlertOptions , 'id' | 'type' > ) => triggerAlert ( { ...opt ,
167+ type : 'success' } ) ,
168+ error : ( opt : Omit < AlertOptions , 'id' | 'type' > ) => triggerAlert ( { ...opt ,
169+ type : 'error' } ) ,
170+ warning : ( opt : Omit < AlertOptions , 'id' | 'type' > ) => triggerAlert ( { ...opt ,
171+ type : 'warning' } ) ,
172+ info : ( opt : Omit < AlertOptions , 'id' | 'type' > ) => triggerAlert ( { ...opt ,
173+ type : 'info' } ) ,
174+ alert : ( opt : Omit < AlertOptions , 'id' | 'type' > ) => triggerAlert ( { ...opt ,
175+ type : 'default' } ) ,
138176 } ;
139177} ) ;
0 commit comments