소스 검색

refactor: modifica composable o useScroll

Gustavo Mantovani 1 주 전
부모
커밋
6b52efd74c
1개의 변경된 파일155개의 추가작업 그리고 73개의 파일을 삭제
  1. 155 73
      src/composables/useScroll.js

+ 155 - 73
src/composables/useScroll.js

@@ -1,87 +1,169 @@
-import { unref } from "vue";
-
-export const useScroll = () => {
-  /**
-   * Scrolls to a target element within a scroll container.
-   * The function is agnostic to whether the container is a QScrollArea or a native DOM element.
-   *
-   * @param {Ref<Component|HTMLElement>} targetRef - Ref to the target component or DOM element.
-   * @param {Ref<Component|HTMLElement>} containerRef - Ref to the container (QScrollArea or a scrollable DOM element like body).
-   * @param {Object} options - Scroll options.
-   * @param {number} options.offset - Offset from the top in pixels (default: 50).
-   * @param {number} options.duration - Animation duration for QScrollArea (default: 150). Note: duration is not supported for native scrolling.
-   * @returns {boolean} - Whether the scroll was successful.
-   */
-  const scrollToComponent = (targetRef, containerRef, options = {}) => {
-    const { offset = 150, duration = 150 } = options;
-
-    const targetInstance = unref(targetRef);
-    const providedContainer = unref(containerRef);
-
-    const targetElement = targetInstance?.$el ?? targetInstance;
-    const detectedContainer = targetElement?.closest?.(
-      ".q-scrollarea__container, .scroll, .scroll-y",
-    );
-    const containerInstance = providedContainer ?? detectedContainer ?? document.body;
-    const containerElement = containerInstance?.$el ?? containerInstance;
-
-    if (!targetElement || !containerElement) {
-      console.warn("useScroll: Target or container element not found");
-      return false;
-    }
-
-    try {
-      let currentElement = targetElement;
-      let offsetTop = 0;
-
-      while (currentElement && currentElement !== containerElement) {
-        offsetTop += currentElement.offsetTop;
-        if (
-          containerElement.contains(currentElement.offsetParent) ||
-          containerElement === document.body
-        ) {
-          currentElement = currentElement.offsetParent;
-        } else {
-          currentElement = null;
-        }
+import { nextTick, unref } from "vue";
+
+  export const useScroll = () => {
+    const resolveElement = (value) => {
+      const instance = unref(value);
+
+      return instance?.$el ?? instance ?? null;
+    };
+
+    const resolveContainer = (targetElement, containerRef) => {
+      const containerInstance = unref(containerRef);
+
+      if (typeof containerInstance?.setScrollPosition === "function") {
+        return {
+          instance: containerInstance,
+          element:
+            containerInstance.getScrollTarget?.() ??
+            containerInstance.$el?.querySelector?.(
+              ".q-scrollarea__container",
+            ) ??
+            containerInstance.$el,
+          isQuasar: true,
+        };
+      }
+
+      const providedElement = resolveElement(containerRef);
+
+      const detectedElement = targetElement?.closest?.(
+        ".q-scrollarea__container, .scroll, .scroll-y",
+      );
+
+      const element =
+        providedElement ??
+        detectedElement ??
+        document.scrollingElement ??
+        document.documentElement;
+
+      return {
+        instance: element,
+        element,
+        isQuasar: false,
+      };
+    };
+
+    const calculatePosition = (targetElement, containerElement, offset) => {
+      const targetRect = targetElement.getBoundingClientRect();
+
+      const isDocumentContainer =
+        containerElement === document.body ||
+        containerElement === document.documentElement ||
+        containerElement === document.scrollingElement;
+
+      if (isDocumentContainer) {
+        return Math.max(
+          0,
+          targetRect.top + window.scrollY - offset,
+        );
+      }
+
+      const containerRect = containerElement.getBoundingClientRect();
+
+      return Math.max(
+        0,
+        targetRect.top -
+          containerRect.top +
+          containerElement.scrollTop -
+          offset,
+      );
+    };
+
+    const scrollToComponent = (targetRef, containerRef, options = {}) => {
+      const { offset = 150, duration = 150, behavior = "smooth" } = options;
+
+      const targetElement = resolveElement(targetRef);
+
+      if (!targetElement) {
+        console.warn("useScroll: Target element not found");
+        return false;
       }
 
+      const container = resolveContainer(targetElement, containerRef);
+
+      const containerElement = container.element;
+
+      if (!containerElement) {
+        console.warn("useScroll: Container element not found");
+
+        return false;
+      }
+
+      const isDocumentContainer =
+        containerElement === document.body ||
+        containerElement === document.documentElement ||
+        containerElement === document.scrollingElement;
+
       if (
-        containerElement !== document.body &&
+        !isDocumentContainer &&
         !containerElement.contains(targetElement)
       ) {
-        console.warn("useScroll: Target is not a child of the container");
+        console.warn("useScroll: Target is not inside the container");
         return false;
       }
 
-      const targetPosition = Math.max(0, offsetTop - offset);
-
-      if (typeof containerInstance.setScrollPosition === "function") {
-        containerInstance.setScrollPosition(
-          "vertical",
-          targetPosition,
-          duration,
+      try {
+        const targetPosition = calculatePosition(
+          targetElement,
+          containerElement,
+          offset,
         );
-      } else {
-        const scrollable =
-          containerElement === document.body ||
-          containerElement === document.documentElement
-            ? window
-            : containerElement;
-        scrollable.scrollTo({
-          top: targetPosition,
-          behavior: "smooth",
-        });
+
+        if (container.isQuasar) {
+          container.instance.setScrollPosition(
+            "vertical",
+            targetPosition,
+            duration,
+          );
+        } else if (isDocumentContainer) {
+          window.scrollTo({
+            top: targetPosition,
+            behavior,
+          });
+        } else {
+          containerElement.scrollTo({
+            top: targetPosition,
+            behavior,
+          });
+        }
+
+        return true;
+      } catch (error) {
+        console.error("useScroll: Error while scrolling", error);
+        return false;
+      }
+    };
+
+    const scrollToFirstError = async (
+      formRef,
+      containerRef,
+      options = {},
+    ) => {
+      await nextTick();
+
+      const forms = unref(formRef);
+
+      const formInstances = Array.isArray(forms) ? forms : [forms];
+
+      for (const formInstance of formInstances) {
+        const formElement = resolveElement(formInstance);
+
+        const errorElement =
+          formElement?.matches?.(".q-field--error")
+            ? formElement
+            : formElement?.querySelector?.(".q-field--error");
+
+        if (errorElement) {
+          const scrollFn = options.scrollFn ?? scrollToComponent;
+
+          return scrollFn(errorElement, containerRef, options);
+        }
       }
 
-      return true;
-    } catch (error) {
-      console.error("useScroll: Error while scrolling", error);
       return false;
-    }
-  };
+    };
 
-  return {
-    scrollToComponent,
+    return {
+      scrollToComponent,
+      scrollToFirstError,
+    };
   };
-};