From be4f307ff5593eb61e883624cdbf0a9fcb107c08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?=
 =?UTF-8?q?=D0=BB=D1=8E=D0=B3=D0=B8=D0=BD?= <d.malygin@iqdev.digital>
Date: Tue, 11 Feb 2025 17:14:38 +0500
Subject: [PATCH] feat: trying to add correct animation if many components in
 'Toast'

---
 src/components/Toast/Toast.vue | 122 ++++++++++++++++++---------------
 1 file changed, 68 insertions(+), 54 deletions(-)

diff --git a/src/components/Toast/Toast.vue b/src/components/Toast/Toast.vue
index 35440a4..b7850eb 100644
--- a/src/components/Toast/Toast.vue
+++ b/src/components/Toast/Toast.vue
@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import type { IToastProps } from '@interfaces/componentsProps';
-import { computed, onBeforeUnmount, onMounted, ref, type Ref, watch } from 'vue';
+import { computed, ref, type Ref, watch } from 'vue';
 import { convertThemeToColor, getValueFromSize } from '@helpers/common';
 import type { TToastType } from '@interfaces/componentsProp';
 import { iconsSet } from '@/common/constants/icons';
@@ -39,41 +39,15 @@ const typeToIcon: Record<TToastType, string> = {
 const active = defineModel() as Ref<boolean>;
 
 let toastsContainer = document.querySelector(`.toasts-container.${props.position}`);
-if (!toastsContainer) {
-  toastsContainer = document.createElement('div');
-  toastsContainer.classList.add('toasts-container');
-  toastsContainer.classList.add(`${props.position}`);
-  document.body.appendChild(toastsContainer);
-}
 
 const toast = ref();
-watch(toast, () => {
-  if (toast.value) {
+watch([toast, () => props.static], () => {
+  console.log('watch 2');
+  if (toast.value && !props.static) {
     toastsContainer?.appendChild(toast.value);
   }
-});
-const activeToastsCount = ref(0);
-let observer = null;
-
-const updateCount = () => {
-  activeToastsCount.value = document.querySelectorAll(`.toast-container.${props.position}.active`).length;
-};
-const initObserver = () => {
-  const config = { attributeOldValue: true, attributes: true, characterData: true, childList: true, subtree: true };
-  observer = new MutationObserver(() => {
-    console.log('update type: ', props.type);
-    updateCount();
-  });
-  observer.observe(toastsContainer, config);
-};
-
-onMounted(() => {
-  updateCount();
-  initObserver();
-});
-onBeforeUnmount(() => {
-  if (observer) {
-    observer.disconnect();
+  if (props.static) {
+    toastsContainer?.removeChild(toast.value);
   }
 });
 
@@ -126,39 +100,81 @@ const styles = computed(() => {
   return `${positionParts.value[0]}: -100%; ${positionParts.value[1]}: 20px`;
 });
 
-const calcActiveStyles = () => {
-  const activeToasts = document.querySelectorAll(`.toast-container.${props.position}.active`);
-  let activeToastsHeight = 0;
-  for (const toast of activeToasts) {
-    activeToastsHeight += toast.offsetHeight;
+const initContainer = () => {
+  if (!toastsContainer) {
+    toastsContainer = document.createElement('div');
+    toastsContainer.classList.add('toasts-container');
+    toastsContainer.classList.add(`${props.position}`);
+    toastsContainer.style = `
+    position: fixed;
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+    transition: all 0.4s ease-in-out;
+    ${styles.value}
+  `;
+    document.body.appendChild(toastsContainer);
   }
-
-  const offset = activeToastsHeight + 20 * activeToasts.length + 20 + 'px';
-  console.log('activeToasts: ', `${positionParts.value[0]}: ${offset}`);
-
-  if (positionParts.value.length === 1) return `${positionParts.value[0]}: ${offset}`;
-  return `${positionParts.value[0]}: ${offset}; ${positionParts.value[1]}: 20px`;
 };
+watch(
+  () => props.position,
+  () => {
+    console.log('watch 1');
+
+    initContainer();
+  },
+  {
+    immediate: true,
+  },
+);
+// const activeStyles = computed(() => {
+//   const activeToasts = document.querySelectorAll(`.toast-container.${props.position}.active`);
+//   let activeToastsHeight = 0;
+//   for (const toast of activeToasts) {
+//     activeToastsHeight += toast.offsetHeight;
+//   }
+//
+//   const offset = activeToastsHeight + 20 * activeToasts.length + 20 + 'px';
+//   console.log('activeToasts: ', `${positionParts.value[0]}: ${offset}`);
+//
+//   if (positionParts.value.length === 1) return `${positionParts.value[0]}: ${offset}`;
+//   return `${positionParts.value[0]}: ${offset}; ${positionParts.value[1]}: 20px`;
+// });
 const closeToast = () => (active.value = false);
 
 let timeout: number;
-const key = Math.random();
 
 if (props.duration) {
   watch(active, () => {
     if (active.value) {
+      toast.value.classList.add('active');
+      const activeToasts = document.querySelectorAll(`.toast-container.${props.position}.active`);
+      let activeToastsHeight = 0;
+      for (const toast of activeToasts) {
+        activeToastsHeight += toast.offsetHeight;
+      }
+      console.log('activeToasts.length: ', activeToasts.length);
+      const offset = activeToastsHeight + 20 * activeToasts.length;
+      console.log('offset: ', offset, offset - toastsContainer?.clientHeight, toastsContainer?.clientHeight);
+      toast.value.style.order = 9999;
+      toastsContainer.style[positionParts.value[0]] = offset - toastsContainer?.clientHeight + 'px';
+      console.log('toast.value.style: ', toast.value.style);
       timeout = setTimeout(() => (active.value = false), (props.duration as number) * 1000);
-      toastsContainer?.setAttribute('key', String(key));
     } else {
+      console.log('inactive');
+      toast.value.classList.remove('active');
+      const activeToasts = document.querySelectorAll(`.toast-container.${props.position}.active`);
+      let activeToastsHeight = 0;
+      for (const toast of activeToasts) {
+        activeToastsHeight += toast.offsetHeight;
+      }
+      const offset = activeToastsHeight + 20 * activeToasts.length;
+      toastsContainer.style[positionParts.value[0]] = offset - toastsContainer?.clientHeight + 'px';
+      toast.value.style.order = 1;
       clearTimeout(timeout);
-      toastsContainer?.removeAttribute('key');
     }
   });
 }
-watch(activeToastsCount, () => {
-  console.log(activeToastsCount.value);
-  calcActiveStyles();
-});
 </script>
 
 <template>
@@ -170,9 +186,8 @@ watch(activeToastsCount, () => {
         active,
       },
     ]"
-    :style="`position: ${static ? 'relative' : 'fixed'};
-    ${styles};
-    ${active ? calcActiveStyles() : null}`"
+    :style="`position: relative;
+    ${styles}`"
   >
     <h3 class="toast-header" :style="`font-size: calc(${fontSize} + 4px)`">
       <component :is="iconsSet[icon]" :color="color" :size="iconSize" />
@@ -196,7 +211,6 @@ watch(activeToastsCount, () => {
   border: 1px solid v-bind(borderColor);
   border-radius: 7px;
   width: v-bind(width);
-  transition: all 0.4s ease-in-out;
   ::before {
     content: '';
     position: absolute;
-- 
GitLab