refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
+75
View File
@@ -0,0 +1,75 @@
// React ref cleanup keeps native animation lifetimes tied to the mounted icon.
const bindIconAnimation = (icon: null | SVGSVGElement) => {
if (!icon) return;
const target = icon.closest<HTMLElement>("a, button") ?? icon.parentElement;
if (!target) return;
let isHovered = false;
let isFocused = false;
let isRunning = false;
let isDisposed = false;
const release = () => {
if (!isRunning && !isHovered && !isFocused) {
delete icon.dataset.iconAnimating;
}
};
const start = () => {
if (Object.hasOwn(icon.dataset, "iconAnimating")) return;
icon.dataset.iconAnimating = "";
isRunning = true;
// Includes staggered/delayed children and finite iterations, without timers.
const animations = icon.getAnimations({ subtree: true });
void Promise.allSettled(
animations.map(animation => animation.finished),
).then(() => {
if (isDisposed) return;
isRunning = false;
release();
});
};
const enter = () => {
if (!matchMedia("(hover: hover)").matches) return;
isHovered = true;
start();
};
const leave = () => {
isHovered = false;
release();
};
const focus = () => {
isFocused
= target.matches(":focus-visible")
|| !!target.querySelector(":focus-visible");
if (isFocused) start();
};
const blur = (event: FocusEvent) => {
if (
event.relatedTarget instanceof Node
&& target.contains(event.relatedTarget)
)
return;
isFocused = false;
release();
};
target.addEventListener("mouseenter", enter);
target.addEventListener("mouseleave", leave);
target.addEventListener("focusin", focus);
target.addEventListener("focusout", blur);
return () => {
isDisposed = true;
target.removeEventListener("mouseenter", enter);
target.removeEventListener("mouseleave", leave);
target.removeEventListener("focusin", focus);
target.removeEventListener("focusout", blur);
delete icon.dataset.iconAnimating;
};
};
export { bindIconAnimation };