interface FlashEntry {
element: HTMLElement;
overlay: HTMLElement;
scrollCleanup?: () => void;
}
const fadeOutTimers = new WeakMap>();
const trackElementPosition = (
element: Element,
callback: (element: Element) => void,
): (() => void) => {
const handleScroll = callback.bind(null, element);
document.addEventListener('scroll', handleScroll, {
passive: true,
capture: true,
});
return () => {
document.removeEventListener('scroll', handleScroll, { capture: true });
};
};
export const flashManager = {
activeFlashes: new Map(),
create(container: HTMLElement) {
const existingOverlay = container.querySelector(
'.react-scan-flash-overlay',
);
const overlay =
existingOverlay instanceof HTMLElement
? existingOverlay
: (() => {
const newOverlay = document.createElement('div');
newOverlay.className = 'react-scan-flash-overlay';
container.appendChild(newOverlay);
const scrollCleanup = trackElementPosition(container, () => {
if (container.querySelector('.react-scan-flash-overlay')) {
this.create(container);
}
});
this.activeFlashes.set(container, {
element: container,
overlay: newOverlay,
scrollCleanup,
});
return newOverlay;
})();
const existingTimer = fadeOutTimers.get(overlay);
if (existingTimer) {
clearTimeout(existingTimer);
fadeOutTimers.delete(overlay);
}
requestAnimationFrame(() => {
overlay.style.transition = 'none';
overlay.style.opacity = '0.9';
const timerId = setTimeout(() => {
overlay.style.transition = 'opacity 150ms ease-out';
overlay.style.opacity = '0';
const cleanupTimer = setTimeout(() => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
const entry = this.activeFlashes.get(container);
if (entry?.scrollCleanup) {
entry.scrollCleanup();
}
this.activeFlashes.delete(container);
fadeOutTimers.delete(overlay);
}, 150);
fadeOutTimers.set(overlay, cleanupTimer);
}, 300);
fadeOutTimers.set(overlay, timerId);
});
},
cleanup(container: HTMLElement) {
const entry = this.activeFlashes.get(container);
if (entry) {
const existingTimer = fadeOutTimers.get(entry.overlay);
if (existingTimer) {
clearTimeout(existingTimer);
fadeOutTimers.delete(entry.overlay);
}
if (entry.overlay.parentNode) {
entry.overlay.parentNode.removeChild(entry.overlay);
}
if (entry.scrollCleanup) {
entry.scrollCleanup();
}
this.activeFlashes.delete(container);
}
},
cleanupAll() {
for (const [, entry] of this.activeFlashes) {
this.cleanup(entry.element);
}
},
};