Skip to content

Commit d104aa9

Browse files
changes based on comment
1 parent 2612d1d commit d104aa9

2 files changed

Lines changed: 87 additions & 41 deletions

File tree

@codexteam/ui/src/vue/components/alert/AlertTransition.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@
1616
/* Enter/Leave transitions */
1717
.alert-enter-active {
1818
transition: all 0.3s ease-out;
19+
position: relative;
20+
z-index: 1;
1921
}
2022
2123
.alert-leave-active {
22-
transition: all 0.4s ease-in;
23-
position: absolute;
24-
width: 100%;
24+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
25+
position: relative;
26+
z-index: 0;
27+
height: 0;
28+
margin: 0;
29+
padding: 0;
30+
overflow: hidden;
2531
}
2632
2733
.alert-enter-from {
@@ -36,12 +42,14 @@
3642
3743
.alert-leave-to {
3844
opacity: 0;
39-
transform: translateX(-100%);
45+
transform: translateY(-10px);
46+
margin: 0;
47+
padding: 0;
4048
}
4149
4250
/* Move other items smoothly when one is removed */
4351
.alert-move {
44-
transition: transform 0.4s ease;
52+
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
4553
}
4654
4755
.alert-container {

@codexteam/ui/src/vue/components/alert/useAlert.ts

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Ref } from 'vue';
22
import { onUnmounted, ref } from 'vue';
33
import { 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

Comments
 (0)