useScroll.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { nextTick, unref } from "vue";
  2. export const useScroll = () => {
  3. const resolveElement = (value) => {
  4. const instance = unref(value);
  5. return instance?.$el ?? instance ?? null;
  6. };
  7. const resolveContainer = (targetElement, containerRef) => {
  8. const containerInstance = unref(containerRef);
  9. if (typeof containerInstance?.setScrollPosition === "function") {
  10. return {
  11. instance: containerInstance,
  12. element:
  13. containerInstance.getScrollTarget?.() ??
  14. containerInstance.$el?.querySelector?.(
  15. ".q-scrollarea__container",
  16. ) ??
  17. containerInstance.$el,
  18. isQuasar: true,
  19. };
  20. }
  21. const providedElement = resolveElement(containerRef);
  22. const detectedElement = targetElement?.closest?.(
  23. ".q-scrollarea__container, .scroll, .scroll-y",
  24. );
  25. const element =
  26. providedElement ??
  27. detectedElement ??
  28. document.scrollingElement ??
  29. document.documentElement;
  30. return {
  31. instance: element,
  32. element,
  33. isQuasar: false,
  34. };
  35. };
  36. //
  37. const calculatePosition = (targetElement, containerElement, offset) => {
  38. const targetRect = targetElement.getBoundingClientRect();
  39. const isDocumentContainer =
  40. containerElement === document.body ||
  41. containerElement === document.documentElement ||
  42. containerElement === document.scrollingElement;
  43. if (isDocumentContainer) {
  44. return Math.max(
  45. 0,
  46. targetRect.top + window.scrollY - offset,
  47. );
  48. }
  49. const containerRect = containerElement.getBoundingClientRect();
  50. return Math.max(
  51. 0,
  52. targetRect.top -
  53. containerRect.top +
  54. containerElement.scrollTop -
  55. offset,
  56. );
  57. };
  58. const scrollToComponent = (targetRef, containerRef, options = {}) => {
  59. const { offset = 150, duration = 150, behavior = "smooth" } = options;
  60. const targetElement = resolveElement(targetRef);
  61. if (!targetElement) {
  62. console.warn("useScroll: Target element not found");
  63. return false;
  64. }
  65. const container = resolveContainer(targetElement, containerRef);
  66. const containerElement = container.element;
  67. if (!containerElement) {
  68. console.warn("useScroll: Container element not found");
  69. return false;
  70. }
  71. const isDocumentContainer =
  72. containerElement === document.body ||
  73. containerElement === document.documentElement ||
  74. containerElement === document.scrollingElement;
  75. if (
  76. !isDocumentContainer &&
  77. !containerElement.contains(targetElement)
  78. ) {
  79. console.warn("useScroll: Target is not inside the container");
  80. return false;
  81. }
  82. try {
  83. const targetPosition = calculatePosition(
  84. targetElement,
  85. containerElement,
  86. offset,
  87. );
  88. if (container.isQuasar) {
  89. container.instance.setScrollPosition(
  90. "vertical",
  91. targetPosition,
  92. duration,
  93. );
  94. } else if (isDocumentContainer) {
  95. window.scrollTo({
  96. top: targetPosition,
  97. behavior,
  98. });
  99. } else {
  100. containerElement.scrollTo({
  101. top: targetPosition,
  102. behavior,
  103. });
  104. }
  105. return true;
  106. } catch (error) {
  107. console.error("useScroll: Error while scrolling", error);
  108. return false;
  109. }
  110. };
  111. const scrollToFirstError = async (
  112. formRef,
  113. containerRef,
  114. options = {},
  115. ) => {
  116. await nextTick();
  117. const forms = unref(formRef);
  118. const formInstances = Array.isArray(forms) ? forms : [forms];
  119. for (const formInstance of formInstances) {
  120. const formElement = resolveElement(formInstance);
  121. const errorElement =
  122. formElement?.matches?.(".q-field--error")
  123. ? formElement
  124. : formElement?.querySelector?.(".q-field--error");
  125. if (errorElement) {
  126. const scrollFn = options.scrollFn ?? scrollToComponent;
  127. return scrollFn(errorElement, containerRef, options);
  128. }
  129. }
  130. return false;
  131. };
  132. return {
  133. scrollToComponent,
  134. scrollToFirstError,
  135. };
  136. };