From 1978c230c8830ee8ea182005937729047b86c962 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Fri, 5 Jul 2024 10:55:58 +0300 Subject: [PATCH 01/20] feat(CustomScrollView): add horizontal scroll support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Доработал компонент customScrollView, добавил поддержку горизонтального скролла --- .../CustomScrollBarController.ts | 14 ++ .../CustomScrollView.module.css | 57 +++-- .../CustomScrollView/CustomScrollView.tsx | 210 ++++++++---------- .../useCustomScrollViewResize.ts | 32 +++ .../useDetectScrollDirection.ts | 45 ++++ .../useHorizontalScrollController.tsx | 122 ++++++++++ .../CustomScrollView/useTransformProp.ts | 20 ++ .../useVerticalScrollController.tsx | 124 +++++++++++ 8 files changed, 491 insertions(+), 133 deletions(-) create mode 100644 packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts create mode 100644 packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts create mode 100644 packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts create mode 100644 packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx create mode 100644 packages/vkui/src/components/CustomScrollView/useTransformProp.ts create mode 100644 packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts b/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts new file mode 100644 index 0000000000..bc11f3ab98 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts @@ -0,0 +1,14 @@ +import * as React from "react"; + +export interface CustomScrollBarController { + barRef: React.RefObject, + trackerRef: React.RefObject, + trackerVisible: boolean, + onResize: () => void, + onMove: (e: MouseEvent) => void, + onUp: () => void, + onScroll: () => void, + onDragStart: (e: React.MouseEvent) => void, + onTrackerMouseEnter: () => void, + onTrackerMouseLeave: () => void, +} \ No newline at end of file diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css index 2615bf1d2a..fa98ac035a 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css @@ -10,8 +10,7 @@ inline-size: 100%; block-size: 100%; overflow-y: scroll; - overflow-x: hidden; - padding-inline-end: 100px; + overflow-x: scroll; position: relative; } @@ -21,35 +20,59 @@ block-size: 100%; } -.CustomScrollView__barY:active + .CustomScrollView__box { +.CustomScrollView__barY:active + .CustomScrollView__box, +.CustomScrollView__barX:active + .CustomScrollView__box { pointer-events: none; } -.CustomScrollView__barY { +.CustomScrollView__barY, +.CustomScrollView__barX { position: absolute; +} + +.CustomScrollView__barY { inset-block-start: 0; inset-inline-end: 0; block-size: 100%; inline-size: 10px; } +.CustomScrollView__barX { + inset-block-end: 0; + inset-inline-start: 0; + inline-size: 100%; + block-size: 10px; +} + +.CustomScrollView__trackerY, +.CustomScrollView__trackerX { + position: absolute; + user-select: none; + box-sizing: border-box; + inset-block-start: 0; + inset-inline-start: 0; +} + .CustomScrollView__trackerY { inline-size: 10px; - box-sizing: border-box; padding-block: 4px 4px; padding-inline: 0 4px; - position: absolute; - inset-block-start: 0; - inset-inline-start: 0; - user-select: none; } -.CustomScrollView__trackerY--hidden { +.CustomScrollView__trackerX { + block-size: 10px; + padding-inline: 4px 4px; + padding-block: 0 4px; +} + +.CustomScrollView__trackerY--hidden, +.CustomScrollView__trackerX--hidden { opacity: 0; transition: opacity 200ms; } -.CustomScrollView__trackerY::before { +.CustomScrollView__trackerY::before, +.CustomScrollView__trackerX::before { content: ''; opacity: 0.48; display: block; @@ -68,6 +91,12 @@ transform: scaleX(1.3333); } +.CustomScrollView__trackerX:hover::before, +.CustomScrollView__trackerX:active::before { + opacity: 0.8; + transform: scaleY(1.3333); +} + /** * CMP: * CustomSelectDropdown @@ -111,12 +140,14 @@ /** * Не отрисовываем полосу прокрутки, если у устройства отсутствует мышь */ -.CustomScrollView--hasPointer-false .CustomScrollView__barY { +.CustomScrollView--hasPointer-false .CustomScrollView__barY, +.CustomScrollView--hasPointer-false .CustomScrollView__barX { display: none; } @media (--pointer-has-not) { - .CustomScrollView--hasPointer-none .CustomScrollView__barY { + .CustomScrollView--hasPointer-none .CustomScrollView__barY, + .CustomScrollView--hasPointer-none .CustomScrollView__barX { display: none; } } diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index ca9295ce1f..6af1b86964 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -3,12 +3,14 @@ import { classNames } from '@vkontakte/vkjs'; import { useAdaptivity } from '../../hooks/useAdaptivity'; import { useEventListener } from '../../hooks/useEventListener'; import { useExternRef } from '../../hooks/useExternRef'; -import { useResizeObserver } from '../../hooks/useResizeObserver'; import { useDOM } from '../../lib/dom'; -import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { stopPropagation } from '../../lib/utils'; import type { HasRootRef } from '../../types'; -import { TrackerOptionsProps, useTrackerVisibility } from './useTrackerVisibility'; +import { useCustomScrollViewResize } from './useCustomScrollViewResize'; +import { useDetectScrollDirection } from './useDetectScrollDirection'; +import { useHorizontalScrollController } from './useHorizontalScrollController'; +import { TrackerOptionsProps } from './useTrackerVisibility'; +import { useVerticalScrollController } from './useVerticalScrollController'; import styles from './CustomScrollView.module.css'; function hasPointerClassName(hasPointer: boolean | undefined) { @@ -41,136 +43,87 @@ export const CustomScrollView = ({ windowResize, autoHideScrollbar = false, autoHideScrollbarDelay, - onScroll, + onScroll: onScrollProp, getRootRef, ...restProps }: CustomScrollViewProps): React.ReactNode => { - const { document, window } = useDOM(); + const { document } = useDOM(); const { hasPointer } = useAdaptivity(); - - const ratio = React.useRef(NaN); - const lastTrackerTop = React.useRef(0); - const clientHeight = React.useRef(0); - const trackerHeight = React.useRef(0); - const scrollHeight = React.useRef(0); - const transformProp = React.useRef(''); - const startY = React.useRef(0); - const trackerTop = React.useRef(0); + const [pressedBar, setPressedBar] = React.useState<'vertical' | 'horizontal' | null>(null); const boxRef = useExternRef(externalBoxRef); const boxContentRef = React.useRef(null); - const barY = React.useRef(null); - const trackerY = React.useRef(null); - - const setTrackerPosition = (scrollTop: number) => { - lastTrackerTop.current = scrollTop; - if (trackerY.current !== null) { - (trackerY.current.style as any)[transformProp.current] = `translate(0, ${scrollTop}px)`; - } - }; + const { + trackerVisible: verticalTrackerVisible, + barRef: barY, + trackerRef: trackerY, + onResize: verticalScrollResize, + onScroll: verticalScroll, + onDragStart: onVerticalDragStart, + onMove: onVerticalMove, + onUp: onVerticalUp, + onTrackerMouseEnter: onVerticalTrackerMouseEnter, + onTrackerMouseLeave: onVerticalTrackerMouseLeave, + } = useVerticalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); - const setTrackerPositionFromScroll = (scrollTop: number) => { - const progress = scrollTop / (scrollHeight.current - clientHeight.current); - setTrackerPosition((clientHeight.current - trackerHeight.current) * progress); - }; + const { + trackerVisible: horizontalTrackerVisible, + barRef: barX, + trackerRef: trackerX, + onResize: horizontalScrollResize, + onScroll: horizontalScroll, + onDragStart: onHorizontalDragStart, + onMove: onHorizontalMove, + onUp: onHorizontalUp, + onTrackerMouseEnter: onHorizontalTrackerMouseEnter, + onTrackerMouseLeave: onHorizontalTrackerMouseLeave, + } = useHorizontalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); + + const scrollDirection = useDetectScrollDirection(boxRef); + + useCustomScrollViewResize({ + windowResize, + boxContentRef, + onResize: () => { + verticalScrollResize(); + horizontalScrollResize(); + }, + }); - const resize = () => { - if (!boxRef.current || !barY.current || !trackerY.current) { + const onUp = (e: MouseEvent) => { + if (!scrollDirection) { return; } - const localClientHeight = boxRef.current.clientHeight; - const localScrollHeight = boxRef.current.scrollHeight; - const localRatio = localClientHeight / localScrollHeight; - const localTrackerHeight = Math.max(localClientHeight * localRatio, 40); - - ratio.current = localRatio; - clientHeight.current = localClientHeight; - scrollHeight.current = localScrollHeight; - trackerHeight.current = localTrackerHeight; - - if (localRatio >= 1) { - barY.current.style.display = 'none'; + e.preventDefault(); + if (scrollDirection === 'vertical') { + onVerticalUp(); } else { - barY.current.style.display = ''; - trackerY.current.style.height = `${localTrackerHeight}px`; - setTrackerPositionFromScroll(boxRef.current.scrollTop); - } - }; - - const resizeHandler = useEventListener('resize', resize); - - useIsomorphicLayoutEffect(() => { - if (windowResize && window) { - resizeHandler.add(window); - } - }, [windowResize, window]); - - useResizeObserver(boxContentRef, resize); - - useIsomorphicLayoutEffect(() => { - let style = trackerY.current?.style; - let prop = ''; - if (style !== undefined) { - if ('transform' in style) { - prop = 'transform'; - } else if ('webkitTransform' in style) { - prop = 'webkitTransform'; - } - } - transformProp.current = prop; - }, []); - - useIsomorphicLayoutEffect(resize); - - const setScrollPositionFromTracker = (trackerTop: number) => { - const progress = trackerTop / (clientHeight.current - trackerHeight.current); - if (boxRef.current !== null) { - boxRef.current.scrollTop = (scrollHeight.current - clientHeight.current) * progress; + onHorizontalUp(); } + setPressedBar(null); + unsubscribe(); }; const onMove = (e: MouseEvent) => { e.preventDefault(); - const diff = e.clientY - startY.current; - const position = Math.min( - Math.max(trackerTop.current + diff, 0), - clientHeight.current - trackerHeight.current, - ); - - setScrollPositionFromTracker(position); - }; - - const { - trackerVisible, - onTargetScroll, - onTrackerDragStart, - onTrackerDragStop, - onTrackerMouseEnter, - onTrackerMouseLeave, - } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); - - const onUp = (e: MouseEvent) => { - e.preventDefault(); - - if (autoHideScrollbar) { - onTrackerDragStop(); + if (pressedBar === 'vertical') { + onVerticalMove(e); + } else { + onHorizontalMove(e); } - - unsubscribe(); }; - const scroll = (event: React.UIEvent) => { - if (ratio.current >= 1 || !boxRef.current) { + const onScroll = (event: React.UIEvent) => { + if (!scrollDirection) { return; } - - if (autoHideScrollbar) { - onTargetScroll(); + if (scrollDirection === 'horizontal') { + horizontalScroll(); + } else { + verticalScroll(); } - - setTrackerPositionFromScroll(boxRef.current.scrollTop); - onScroll?.(event); + onScrollProp?.(event); }; const listeners = [useEventListener('mousemove', onMove), useEventListener('mouseup', onUp)]; @@ -185,13 +138,13 @@ export const CustomScrollView = ({ listeners.forEach((l) => l.remove()); } - const onDragStart = (e: React.MouseEvent) => { + const onDragStart = (e: React.MouseEvent, isVertical: boolean) => { e.preventDefault(); - startY.current = e.clientY; - trackerTop.current = lastTrackerTop.current; - - if (autoHideScrollbar) { - onTrackerDragStart(); + setPressedBar(isVertical ? 'vertical' : 'horizontal'); + if (isVertical) { + onVerticalDragStart(e); + } else { + onHorizontalDragStart(e); } subscribe(document); @@ -203,7 +156,12 @@ export const CustomScrollView = ({ ref={getRootRef} {...restProps} > -
+
{children}
@@ -213,12 +171,24 @@ export const CustomScrollView = ({
onDragStart(e, true)} + /> +
+
+
onDragStart(e, false)} />
diff --git a/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts b/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts new file mode 100644 index 0000000000..0efa062d19 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts @@ -0,0 +1,32 @@ +import { useEventListener } from '../../hooks/useEventListener'; +import { useResizeObserver } from '../../hooks/useResizeObserver'; +import { useStableCallback } from '../../hooks/useStableCallback'; +import { useDOM } from '../../lib/dom'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; + +interface UseCustomScrollViewResizeProps { + windowResize?: boolean; + onResize: () => void; + boxContentRef: React.RefObject; +} + +export const useCustomScrollViewResize = ({ + windowResize, + onResize, + boxContentRef, +}: UseCustomScrollViewResizeProps) => { + const { window } = useDOM(); + const resizeCb = useStableCallback(onResize); + + const resizeHandler = useEventListener('resize', resizeCb); + + useIsomorphicLayoutEffect(() => { + if (windowResize && window) { + resizeHandler.add(window); + } + }, [windowResize, window]); + + useResizeObserver(boxContentRef, resizeCb); + + useIsomorphicLayoutEffect(resizeCb); +}; diff --git a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts new file mode 100644 index 0000000000..f0c210d91f --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { useEventListener } from '../../hooks/useEventListener'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; + +/** + * Хук определяет в каком измерении происходит скролл(в горизонтальном или вертикальном) + */ +export const useDetectScrollDirection = (boxRef: React.RefObject) => { + const lastScrollLeft = React.useRef(0); + const lastScrollTop = React.useRef(0); + const [scrollDirection, setScrollDirection] = React.useState<'vertical' | 'horizontal' | null>( + null, + ); + + const timeoutId = React.useRef(null); + + const updateDirection = (direction: 'vertical' | 'horizontal') => { + setScrollDirection(direction); + timeoutId.current = setTimeout(() => setScrollDirection(null), 200); + }; + + const scrollHandler = useEventListener('scroll', () => { + if (!boxRef || !boxRef.current) { + return; + } + timeoutId.current && clearTimeout(timeoutId.current); + + const scrollTop = boxRef.current.scrollTop; + const scrollLeft = boxRef.current.scrollLeft; + + if (scrollTop !== lastScrollTop.current) { + updateDirection('vertical'); + } else if (scrollLeft !== lastScrollLeft.current) { + updateDirection('horizontal'); + } + lastScrollLeft.current = scrollLeft; + lastScrollTop.current = scrollTop; + }); + + useIsomorphicLayoutEffect(() => { + boxRef.current && scrollHandler.add(boxRef.current); + }, [boxRef]); + + return scrollDirection; +}; diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx new file mode 100644 index 0000000000..cefe944e31 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -0,0 +1,122 @@ +import * as React from "react"; +import { CustomScrollBarController } from "./CustomScrollBarController"; +import { useTrackerVisibility } from "./useTrackerVisibility"; +import { useTransformProp } from "./useTransformProp"; + + +export const useHorizontalScrollController = ( + boxRef: React.RefObject, + autoHideScrollbar: boolean, + autoHideScrollbarDelay?: number, +): CustomScrollBarController => { + const horizontalRatio = React.useRef(NaN); + const lastTrackerLeft = React.useRef(0); + const clientWidth = React.useRef(0); + const trackerWidth = React.useRef(0); + const scrollWidth = React.useRef(0); + const startX = React.useRef(0); + const trackerLeft = React.useRef(0); + + const barX = React.useRef(null); + const trackerX = React.useRef(null); + + const transformProp = useTransformProp(trackerX); + + const { + trackerVisible, + onTargetScroll, + onTrackerDragStart, + onTrackerDragStop, + onTrackerMouseEnter, + onTrackerMouseLeave, + } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); + + const setHorizontalTrackerPosition = (scrollLeft: number) => { + lastTrackerLeft.current = scrollLeft; + if (trackerX.current !== null) { + (trackerX.current.style as any)[transformProp.current] = `translate(${scrollLeft}px, 0)`; + } + }; + + const setTrackerPositionFromScroll = (scrollLeft: number) => { + const progress = scrollLeft / (scrollWidth.current - clientWidth.current); + setHorizontalTrackerPosition((clientWidth.current - trackerWidth.current) * progress); + }; + + const onResize = () => { + if (!boxRef.current || !barX.current || !trackerX.current) { + return; + } + const localClientWidth = boxRef.current.clientWidth; + const localScrollWidth = boxRef.current.scrollWidth; + const localVerticalRatio = localClientWidth / localScrollWidth; + const localTrackerWidth = Math.max(localClientWidth * localVerticalRatio, 40); + + horizontalRatio.current = localVerticalRatio; + clientWidth.current = localClientWidth; + scrollWidth.current = localScrollWidth; + trackerWidth.current = localTrackerWidth; + + if (localVerticalRatio >= 1) { + barX.current.style.display = 'none'; + } else { + barX.current.style.display = ''; + trackerX.current.style.width = `${localTrackerWidth}px`; + setTrackerPositionFromScroll(boxRef.current.scrollLeft); + } + } + + const setScrollPositionFromTracker = (trackerLeft: number) => { + const progress = trackerLeft / (clientWidth.current - trackerWidth.current); + if (boxRef.current !== null) { + boxRef.current.scrollLeft = (scrollWidth.current - clientWidth.current) * progress; + } + }; + + const onMove = (e: MouseEvent) => { + const diff = e.clientX - startX.current; + const position = Math.min( + Math.max(trackerLeft.current + diff, 0), + clientWidth.current - trackerWidth.current, + ); + setScrollPositionFromTracker(position); + }; + + const onUp = () => { + if (autoHideScrollbar) { + onTrackerDragStop(); + } + }; + + const onScroll = () => { + if (!boxRef.current) { + return + } + if (autoHideScrollbar) { + onTargetScroll(); + } + setTrackerPositionFromScroll(boxRef.current.scrollLeft); + } + + const onDragStart = (e: React.MouseEvent) => { + startX.current = e.clientX; + trackerLeft.current = lastTrackerLeft.current; + + if (autoHideScrollbar) { + onTrackerDragStart(); + } + }; + + return { + trackerVisible, + barRef: barX, + trackerRef: trackerX, + onResize, + onMove, + onUp, + onScroll, + onDragStart, + onTrackerMouseEnter, + onTrackerMouseLeave, + } +} \ No newline at end of file diff --git a/packages/vkui/src/components/CustomScrollView/useTransformProp.ts b/packages/vkui/src/components/CustomScrollView/useTransformProp.ts new file mode 100644 index 0000000000..61ed8e02cb --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/useTransformProp.ts @@ -0,0 +1,20 @@ +import * as React from "react"; +import { useIsomorphicLayoutEffect } from "../../lib/useIsomorphicLayoutEffect"; + +export const useTransformProp = (trackerRef: React.RefObject) => { + const transformProp = React.useRef(''); + useIsomorphicLayoutEffect(() => { + let style = trackerRef.current?.style; + let prop = ''; + if (style !== undefined) { + if ('transform' in style) { + prop = 'transform'; + } else if ('webkitTransform' in style) { + prop = 'webkitTransform'; + } + } + transformProp.current = prop; + }, []); + + return transformProp +} diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx new file mode 100644 index 0000000000..258068eb1a --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -0,0 +1,124 @@ +import * as React from "react"; +import { CustomScrollBarController } from "./CustomScrollBarController"; +import { useTrackerVisibility } from "./useTrackerVisibility"; +import { useTransformProp } from "./useTransformProp"; + + +export const useVerticalScrollController = ( + boxRef: React.RefObject, + autoHideScrollbar: boolean, + autoHideScrollbarDelay?: number, +): CustomScrollBarController => { + const verticalRatio = React.useRef(NaN); + const lastTrackerTop = React.useRef(0); + const clientHeight = React.useRef(0); + const trackerHeight = React.useRef(0); + const scrollHeight = React.useRef(0); + const startY = React.useRef(0); + const trackerTop = React.useRef(0); + + const barY = React.useRef(null); + const trackerY = React.useRef(null); + + const transformProp = useTransformProp(trackerY); + + const { + trackerVisible, + onTargetScroll, + onTrackerDragStart, + onTrackerDragStop, + onTrackerMouseEnter, + onTrackerMouseLeave, + } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); + + const setVerticalTrackerPosition = (scrollTop: number) => { + lastTrackerTop.current = scrollTop; + if (trackerY.current !== null) { + (trackerY.current.style as any)[transformProp.current] = `translate(0, ${scrollTop}px)`; + } + }; + + const setTrackerPositionFromScroll = (scrollTop: number) => { + const progress = scrollTop / (scrollHeight.current - clientHeight.current); + setVerticalTrackerPosition((clientHeight.current - trackerHeight.current) * progress); + }; + + const onResize = () => { + if (!boxRef.current || !barY.current || !trackerY.current) { + return; + } + const localClientHeight = boxRef.current.clientHeight; + const localScrollHeight = boxRef.current.scrollHeight; + const localVerticalRatio = localClientHeight / localScrollHeight; + const localTrackerHeight = Math.max(localClientHeight * localVerticalRatio, 40); + + verticalRatio.current = localVerticalRatio; + clientHeight.current = localClientHeight; + scrollHeight.current = localScrollHeight; + trackerHeight.current = localTrackerHeight; + + if (localVerticalRatio >= 1) { + barY.current.style.display = 'none'; + } else { + barY.current.style.display = ''; + trackerY.current.style.height = `${localTrackerHeight}px`; + setTrackerPositionFromScroll(boxRef.current.scrollTop); + } + } + + const setScrollPositionFromTracker = (trackerTop: number) => { + const progress = trackerTop / (clientHeight.current - trackerHeight.current); + if (boxRef.current !== null) { + boxRef.current.scrollTop = (scrollHeight.current - clientHeight.current) * progress; + } + }; + + const onMove = (e: MouseEvent) => { + const diff = e.clientY - startY.current; + const position = Math.min( + Math.max(trackerTop.current + diff, 0), + clientHeight.current - trackerHeight.current, + ); + + setScrollPositionFromTracker(position); + }; + + const onUp = () => { + if (autoHideScrollbar) { + onTrackerDragStop(); + } + }; + + const onScroll = () => { + if (!boxRef.current) { + return + } + if (autoHideScrollbar) { + onTargetScroll(); + } + + setTrackerPositionFromScroll(boxRef.current.scrollTop); + } + + const onDragStart = (e: React.MouseEvent) => { + startY.current = e.clientY; + trackerTop.current = lastTrackerTop.current; + + if (autoHideScrollbar) { + onTrackerDragStart(); + } + }; + + return { + trackerVisible, + barRef: barY, + trackerRef: trackerY, + onResize, + onMove, + onUp, + onScroll, + onDragStart, + onTrackerMouseEnter, + onTrackerMouseLeave, + } +} \ No newline at end of file From 5f7270f85f929d3a86648b7b69a8accfe1c801f9 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Fri, 5 Jul 2024 11:29:40 +0300 Subject: [PATCH 02/20] feat(CustomScrollView): add prop enableHorizontalScroll --- .../CustomScrollView.module.css | 6 +++- .../CustomScrollView/CustomScrollView.tsx | 33 +++++++++++-------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css index fa98ac035a..fb26c07da3 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css @@ -10,10 +10,14 @@ inline-size: 100%; block-size: 100%; overflow-y: scroll; - overflow-x: scroll; + overflow-x: hidden; position: relative; } +.CustomScrollView__box--horizontalEnabled { + overflow-x: scroll; +} + .CustomScrollView__box-content { position: relative; inline-size: 100%; diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index 6af1b86964..7b1a9eeb87 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -34,6 +34,7 @@ export interface CustomScrollViewProps className?: HTMLDivElement['className']; onScroll?: (event: React.UIEvent) => void; children: React.ReactNode; + enableHorizontalScroll?: boolean; } export const CustomScrollView = ({ @@ -43,6 +44,7 @@ export const CustomScrollView = ({ windowResize, autoHideScrollbar = false, autoHideScrollbarDelay, + enableHorizontalScroll = false, onScroll: onScrollProp, getRootRef, ...restProps @@ -157,7 +159,10 @@ export const CustomScrollView = ({ {...restProps} >
onDragStart(e, true)} />
-
-
onDragStart(e, false)} - /> -
+ {enableHorizontalScroll && ( +
+
onDragStart(e, false)} + /> +
+ )}
); }; From a8bf1152dfa8f11c09a16b991f7f3dc43d71c184 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Fri, 5 Jul 2024 12:32:43 +0300 Subject: [PATCH 03/20] fix(CustomScrollView): add doc to prop --- .../vkui/src/components/CustomScrollView/CustomScrollView.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index 7b1a9eeb87..ae55df18ea 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -34,6 +34,9 @@ export interface CustomScrollViewProps className?: HTMLDivElement['className']; onScroll?: (event: React.UIEvent) => void; children: React.ReactNode; + /** + * Включение замены горизонтального скролла + */ enableHorizontalScroll?: boolean; } From b6da2c7c8f43b6df27356b4e35fc505e819e10c5 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Fri, 5 Jul 2024 12:48:16 +0300 Subject: [PATCH 04/20] fix: run prettier --- .../CustomScrollBarController.ts | 24 +- .../useHorizontalScrollController.tsx | 227 +++++++++-------- .../CustomScrollView/useTransformProp.ts | 34 +-- .../useVerticalScrollController.tsx | 231 +++++++++--------- 4 files changed, 257 insertions(+), 259 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts b/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts index bc11f3ab98..8336b26df4 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts @@ -1,14 +1,14 @@ -import * as React from "react"; +import * as React from 'react'; export interface CustomScrollBarController { - barRef: React.RefObject, - trackerRef: React.RefObject, - trackerVisible: boolean, - onResize: () => void, - onMove: (e: MouseEvent) => void, - onUp: () => void, - onScroll: () => void, - onDragStart: (e: React.MouseEvent) => void, - onTrackerMouseEnter: () => void, - onTrackerMouseLeave: () => void, -} \ No newline at end of file + barRef: React.RefObject; + trackerRef: React.RefObject; + trackerVisible: boolean; + onResize: () => void; + onMove: (e: MouseEvent) => void; + onUp: () => void; + onScroll: () => void; + onDragStart: (e: React.MouseEvent) => void; + onTrackerMouseEnter: () => void; + onTrackerMouseLeave: () => void; +} diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index cefe944e31..9371be831c 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -1,122 +1,121 @@ -import * as React from "react"; -import { CustomScrollBarController } from "./CustomScrollBarController"; -import { useTrackerVisibility } from "./useTrackerVisibility"; -import { useTransformProp } from "./useTransformProp"; - +import * as React from 'react'; +import { CustomScrollBarController } from './CustomScrollBarController'; +import { useTrackerVisibility } from './useTrackerVisibility'; +import { useTransformProp } from './useTransformProp'; export const useHorizontalScrollController = ( - boxRef: React.RefObject, - autoHideScrollbar: boolean, - autoHideScrollbarDelay?: number, + boxRef: React.RefObject, + autoHideScrollbar: boolean, + autoHideScrollbarDelay?: number, ): CustomScrollBarController => { - const horizontalRatio = React.useRef(NaN); - const lastTrackerLeft = React.useRef(0); - const clientWidth = React.useRef(0); - const trackerWidth = React.useRef(0); - const scrollWidth = React.useRef(0); - const startX = React.useRef(0); - const trackerLeft = React.useRef(0); - - const barX = React.useRef(null); - const trackerX = React.useRef(null); - - const transformProp = useTransformProp(trackerX); - - const { - trackerVisible, - onTargetScroll, - onTrackerDragStart, - onTrackerDragStop, - onTrackerMouseEnter, - onTrackerMouseLeave, - } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); - - const setHorizontalTrackerPosition = (scrollLeft: number) => { - lastTrackerLeft.current = scrollLeft; - if (trackerX.current !== null) { - (trackerX.current.style as any)[transformProp.current] = `translate(${scrollLeft}px, 0)`; - } - }; - - const setTrackerPositionFromScroll = (scrollLeft: number) => { - const progress = scrollLeft / (scrollWidth.current - clientWidth.current); - setHorizontalTrackerPosition((clientWidth.current - trackerWidth.current) * progress); - }; - - const onResize = () => { - if (!boxRef.current || !barX.current || !trackerX.current) { - return; - } - const localClientWidth = boxRef.current.clientWidth; - const localScrollWidth = boxRef.current.scrollWidth; - const localVerticalRatio = localClientWidth / localScrollWidth; - const localTrackerWidth = Math.max(localClientWidth * localVerticalRatio, 40); - - horizontalRatio.current = localVerticalRatio; - clientWidth.current = localClientWidth; - scrollWidth.current = localScrollWidth; - trackerWidth.current = localTrackerWidth; - - if (localVerticalRatio >= 1) { - barX.current.style.display = 'none'; - } else { - barX.current.style.display = ''; - trackerX.current.style.width = `${localTrackerWidth}px`; - setTrackerPositionFromScroll(boxRef.current.scrollLeft); - } + const horizontalRatio = React.useRef(NaN); + const lastTrackerLeft = React.useRef(0); + const clientWidth = React.useRef(0); + const trackerWidth = React.useRef(0); + const scrollWidth = React.useRef(0); + const startX = React.useRef(0); + const trackerLeft = React.useRef(0); + + const barX = React.useRef(null); + const trackerX = React.useRef(null); + + const transformProp = useTransformProp(trackerX); + + const { + trackerVisible, + onTargetScroll, + onTrackerDragStart, + onTrackerDragStop, + onTrackerMouseEnter, + onTrackerMouseLeave, + } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); + + const setHorizontalTrackerPosition = (scrollLeft: number) => { + lastTrackerLeft.current = scrollLeft; + if (trackerX.current !== null) { + (trackerX.current.style as any)[transformProp.current] = `translate(${scrollLeft}px, 0)`; + } + }; + + const setTrackerPositionFromScroll = (scrollLeft: number) => { + const progress = scrollLeft / (scrollWidth.current - clientWidth.current); + setHorizontalTrackerPosition((clientWidth.current - trackerWidth.current) * progress); + }; + + const onResize = () => { + if (!boxRef.current || !barX.current || !trackerX.current) { + return; + } + const localClientWidth = boxRef.current.clientWidth; + const localScrollWidth = boxRef.current.scrollWidth; + const localVerticalRatio = localClientWidth / localScrollWidth; + const localTrackerWidth = Math.max(localClientWidth * localVerticalRatio, 40); + + horizontalRatio.current = localVerticalRatio; + clientWidth.current = localClientWidth; + scrollWidth.current = localScrollWidth; + trackerWidth.current = localTrackerWidth; + + if (localVerticalRatio >= 1) { + barX.current.style.display = 'none'; + } else { + barX.current.style.display = ''; + trackerX.current.style.width = `${localTrackerWidth}px`; + setTrackerPositionFromScroll(boxRef.current.scrollLeft); } + }; - const setScrollPositionFromTracker = (trackerLeft: number) => { - const progress = trackerLeft / (clientWidth.current - trackerWidth.current); - if (boxRef.current !== null) { - boxRef.current.scrollLeft = (scrollWidth.current - clientWidth.current) * progress; - } - }; - - const onMove = (e: MouseEvent) => { - const diff = e.clientX - startX.current; - const position = Math.min( - Math.max(trackerLeft.current + diff, 0), - clientWidth.current - trackerWidth.current, - ); - setScrollPositionFromTracker(position); - }; - - const onUp = () => { - if (autoHideScrollbar) { - onTrackerDragStop(); - } - }; - - const onScroll = () => { - if (!boxRef.current) { - return - } - if (autoHideScrollbar) { - onTargetScroll(); - } - setTrackerPositionFromScroll(boxRef.current.scrollLeft); + const setScrollPositionFromTracker = (trackerLeft: number) => { + const progress = trackerLeft / (clientWidth.current - trackerWidth.current); + if (boxRef.current !== null) { + boxRef.current.scrollLeft = (scrollWidth.current - clientWidth.current) * progress; } + }; + + const onMove = (e: MouseEvent) => { + const diff = e.clientX - startX.current; + const position = Math.min( + Math.max(trackerLeft.current + diff, 0), + clientWidth.current - trackerWidth.current, + ); + setScrollPositionFromTracker(position); + }; + + const onUp = () => { + if (autoHideScrollbar) { + onTrackerDragStop(); + } + }; + + const onScroll = () => { + if (!boxRef.current) { + return; + } + if (autoHideScrollbar) { + onTargetScroll(); + } + setTrackerPositionFromScroll(boxRef.current.scrollLeft); + }; + + const onDragStart = (e: React.MouseEvent) => { + startX.current = e.clientX; + trackerLeft.current = lastTrackerLeft.current; - const onDragStart = (e: React.MouseEvent) => { - startX.current = e.clientX; - trackerLeft.current = lastTrackerLeft.current; - - if (autoHideScrollbar) { - onTrackerDragStart(); - } - }; - - return { - trackerVisible, - barRef: barX, - trackerRef: trackerX, - onResize, - onMove, - onUp, - onScroll, - onDragStart, - onTrackerMouseEnter, - onTrackerMouseLeave, + if (autoHideScrollbar) { + onTrackerDragStart(); } -} \ No newline at end of file + }; + + return { + trackerVisible, + barRef: barX, + trackerRef: trackerX, + onResize, + onMove, + onUp, + onScroll, + onDragStart, + onTrackerMouseEnter, + onTrackerMouseLeave, + }; +}; diff --git a/packages/vkui/src/components/CustomScrollView/useTransformProp.ts b/packages/vkui/src/components/CustomScrollView/useTransformProp.ts index 61ed8e02cb..e903f01752 100644 --- a/packages/vkui/src/components/CustomScrollView/useTransformProp.ts +++ b/packages/vkui/src/components/CustomScrollView/useTransformProp.ts @@ -1,20 +1,20 @@ -import * as React from "react"; -import { useIsomorphicLayoutEffect } from "../../lib/useIsomorphicLayoutEffect"; +import * as React from 'react'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; export const useTransformProp = (trackerRef: React.RefObject) => { - const transformProp = React.useRef(''); - useIsomorphicLayoutEffect(() => { - let style = trackerRef.current?.style; - let prop = ''; - if (style !== undefined) { - if ('transform' in style) { - prop = 'transform'; - } else if ('webkitTransform' in style) { - prop = 'webkitTransform'; - } - } - transformProp.current = prop; - }, []); + const transformProp = React.useRef(''); + useIsomorphicLayoutEffect(() => { + let style = trackerRef.current?.style; + let prop = ''; + if (style !== undefined) { + if ('transform' in style) { + prop = 'transform'; + } else if ('webkitTransform' in style) { + prop = 'webkitTransform'; + } + } + transformProp.current = prop; + }, []); - return transformProp -} + return transformProp; +}; diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index 258068eb1a..f767855654 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -1,124 +1,123 @@ -import * as React from "react"; -import { CustomScrollBarController } from "./CustomScrollBarController"; -import { useTrackerVisibility } from "./useTrackerVisibility"; -import { useTransformProp } from "./useTransformProp"; - +import * as React from 'react'; +import { CustomScrollBarController } from './CustomScrollBarController'; +import { useTrackerVisibility } from './useTrackerVisibility'; +import { useTransformProp } from './useTransformProp'; export const useVerticalScrollController = ( - boxRef: React.RefObject, - autoHideScrollbar: boolean, - autoHideScrollbarDelay?: number, + boxRef: React.RefObject, + autoHideScrollbar: boolean, + autoHideScrollbarDelay?: number, ): CustomScrollBarController => { - const verticalRatio = React.useRef(NaN); - const lastTrackerTop = React.useRef(0); - const clientHeight = React.useRef(0); - const trackerHeight = React.useRef(0); - const scrollHeight = React.useRef(0); - const startY = React.useRef(0); - const trackerTop = React.useRef(0); - - const barY = React.useRef(null); - const trackerY = React.useRef(null); - - const transformProp = useTransformProp(trackerY); - - const { - trackerVisible, - onTargetScroll, - onTrackerDragStart, - onTrackerDragStop, - onTrackerMouseEnter, - onTrackerMouseLeave, - } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); - - const setVerticalTrackerPosition = (scrollTop: number) => { - lastTrackerTop.current = scrollTop; - if (trackerY.current !== null) { - (trackerY.current.style as any)[transformProp.current] = `translate(0, ${scrollTop}px)`; - } - }; - - const setTrackerPositionFromScroll = (scrollTop: number) => { - const progress = scrollTop / (scrollHeight.current - clientHeight.current); - setVerticalTrackerPosition((clientHeight.current - trackerHeight.current) * progress); - }; - - const onResize = () => { - if (!boxRef.current || !barY.current || !trackerY.current) { - return; - } - const localClientHeight = boxRef.current.clientHeight; - const localScrollHeight = boxRef.current.scrollHeight; - const localVerticalRatio = localClientHeight / localScrollHeight; - const localTrackerHeight = Math.max(localClientHeight * localVerticalRatio, 40); - - verticalRatio.current = localVerticalRatio; - clientHeight.current = localClientHeight; - scrollHeight.current = localScrollHeight; - trackerHeight.current = localTrackerHeight; - - if (localVerticalRatio >= 1) { - barY.current.style.display = 'none'; - } else { - barY.current.style.display = ''; - trackerY.current.style.height = `${localTrackerHeight}px`; - setTrackerPositionFromScroll(boxRef.current.scrollTop); - } + const verticalRatio = React.useRef(NaN); + const lastTrackerTop = React.useRef(0); + const clientHeight = React.useRef(0); + const trackerHeight = React.useRef(0); + const scrollHeight = React.useRef(0); + const startY = React.useRef(0); + const trackerTop = React.useRef(0); + + const barY = React.useRef(null); + const trackerY = React.useRef(null); + + const transformProp = useTransformProp(trackerY); + + const { + trackerVisible, + onTargetScroll, + onTrackerDragStart, + onTrackerDragStop, + onTrackerMouseEnter, + onTrackerMouseLeave, + } = useTrackerVisibility(autoHideScrollbar, autoHideScrollbarDelay); + + const setVerticalTrackerPosition = (scrollTop: number) => { + lastTrackerTop.current = scrollTop; + if (trackerY.current !== null) { + (trackerY.current.style as any)[transformProp.current] = `translate(0, ${scrollTop}px)`; + } + }; + + const setTrackerPositionFromScroll = (scrollTop: number) => { + const progress = scrollTop / (scrollHeight.current - clientHeight.current); + setVerticalTrackerPosition((clientHeight.current - trackerHeight.current) * progress); + }; + + const onResize = () => { + if (!boxRef.current || !barY.current || !trackerY.current) { + return; + } + const localClientHeight = boxRef.current.clientHeight; + const localScrollHeight = boxRef.current.scrollHeight; + const localVerticalRatio = localClientHeight / localScrollHeight; + const localTrackerHeight = Math.max(localClientHeight * localVerticalRatio, 40); + + verticalRatio.current = localVerticalRatio; + clientHeight.current = localClientHeight; + scrollHeight.current = localScrollHeight; + trackerHeight.current = localTrackerHeight; + + if (localVerticalRatio >= 1) { + barY.current.style.display = 'none'; + } else { + barY.current.style.display = ''; + trackerY.current.style.height = `${localTrackerHeight}px`; + setTrackerPositionFromScroll(boxRef.current.scrollTop); + } + }; + + const setScrollPositionFromTracker = (trackerTop: number) => { + const progress = trackerTop / (clientHeight.current - trackerHeight.current); + if (boxRef.current !== null) { + boxRef.current.scrollTop = (scrollHeight.current - clientHeight.current) * progress; } + }; - const setScrollPositionFromTracker = (trackerTop: number) => { - const progress = trackerTop / (clientHeight.current - trackerHeight.current); - if (boxRef.current !== null) { - boxRef.current.scrollTop = (scrollHeight.current - clientHeight.current) * progress; - } - }; - - const onMove = (e: MouseEvent) => { - const diff = e.clientY - startY.current; - const position = Math.min( - Math.max(trackerTop.current + diff, 0), - clientHeight.current - trackerHeight.current, - ); - - setScrollPositionFromTracker(position); - }; - - const onUp = () => { - if (autoHideScrollbar) { - onTrackerDragStop(); - } - }; - - const onScroll = () => { - if (!boxRef.current) { - return - } - if (autoHideScrollbar) { - onTargetScroll(); - } - - setTrackerPositionFromScroll(boxRef.current.scrollTop); + const onMove = (e: MouseEvent) => { + const diff = e.clientY - startY.current; + const position = Math.min( + Math.max(trackerTop.current + diff, 0), + clientHeight.current - trackerHeight.current, + ); + + setScrollPositionFromTracker(position); + }; + + const onUp = () => { + if (autoHideScrollbar) { + onTrackerDragStop(); + } + }; + + const onScroll = () => { + if (!boxRef.current) { + return; } + if (autoHideScrollbar) { + onTargetScroll(); + } + + setTrackerPositionFromScroll(boxRef.current.scrollTop); + }; + + const onDragStart = (e: React.MouseEvent) => { + startY.current = e.clientY; + trackerTop.current = lastTrackerTop.current; - const onDragStart = (e: React.MouseEvent) => { - startY.current = e.clientY; - trackerTop.current = lastTrackerTop.current; - - if (autoHideScrollbar) { - onTrackerDragStart(); - } - }; - - return { - trackerVisible, - barRef: barY, - trackerRef: trackerY, - onResize, - onMove, - onUp, - onScroll, - onDragStart, - onTrackerMouseEnter, - onTrackerMouseLeave, + if (autoHideScrollbar) { + onTrackerDragStart(); } -} \ No newline at end of file + }; + + return { + trackerVisible, + barRef: barY, + trackerRef: trackerY, + onResize, + onMove, + onUp, + onScroll, + onDragStart, + onTrackerMouseEnter, + onTrackerMouseLeave, + }; +}; From 01cf86df01694240237132d73f66895f4e42d883 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Thu, 11 Jul 2024 12:23:05 +0300 Subject: [PATCH 05/20] feat(CustomScrollView): add doc page to component. --- .../CustomScrollView.module.css | 1 + .../CustomScrollView/CustomScrollView.tsx | 7 +- .../src/components/CustomScrollView/Readme.md | 136 ++++++++++++++++++ styleguide/config.js | 1 + 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 packages/vkui/src/components/CustomScrollView/Readme.md diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css index 5718120345..18ea515c26 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css @@ -12,6 +12,7 @@ overflow-y: scroll; overflow-x: hidden; position: relative; + outline: none; } .CustomScrollView__box--overscrollBehavior-contain { diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index 1ddfadc758..3bd3d3a4c5 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -108,11 +108,11 @@ export const CustomScrollView = ({ }); const onUp = (e: MouseEvent) => { - if (!scrollDirection) { + if (!pressedBar) { return; } e.preventDefault(); - if (scrollDirection === 'vertical') { + if (pressedBar === 'vertical') { onVerticalUp(); } else { onHorizontalUp(); @@ -122,6 +122,9 @@ export const CustomScrollView = ({ }; const onMove = (e: MouseEvent) => { + if (!pressedBar) { + return; + } e.preventDefault(); if (pressedBar === 'vertical') { onVerticalMove(e); diff --git a/packages/vkui/src/components/CustomScrollView/Readme.md b/packages/vkui/src/components/CustomScrollView/Readme.md new file mode 100644 index 0000000000..02c331baa2 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/Readme.md @@ -0,0 +1,136 @@ +Компонент, заменяющий браузерный скролл на кастомный + +```jsx { "props": { "layout": false, "adaptivity": true, "iframe": false } } + +const WithVerticalScroll = () => { + return ( + + + {['Say', 'Hello', 'To', 'My', 'Little', 'Friend'].map((item) => ( + }> + {item} + + ))} + + + ) +} + +const WithHorizontalScroll = () => { + const [recentFriends] = useState(getRandomUsers(20)); + return ( + +
+ {recentFriends.map((item) => { + return ( + {}} key={item.id} header={item.first_name}> + + + ); + })} +
+
+ ) +} + +const WithBothScrollAndAutoHide = () => { + return ( + +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a sollicitudin lectus, a + commodo sapien. Vivamus a urna leo. Integer iaculis dignissim urna, sit amet + vestibulum diam bibendum a. Donec eu arcu ut augue porttitor faucibus. Vestibulum nec + pretium tortor, sit amet congue nunc. Aenean ullamcorper ex sem, sed interdum quam + consequat et. Vestibulum a ex non diam fringilla feugiat. Nunc eu tellus sed leo + elementum cursus. Mauris blandit porta egestas. Curabitur eget justo elementum, + malesuada lacus ut, congue mauris. Integer orci nisi, convallis vitae dapibus sit + amet, molestie a risus. Aenean ultricies lacus eros, sit amet condimentum urna + malesuada et. Sed quis dolor tempus orci fringilla volutpat in sed velit. Aenean + aliquet bibendum pretium. +
+
+ Cras pulvinar lobortis purus. Donec placerat suscipit leo vitae sodales. Phasellus + convallis lorem vitae arcu finibus pellentesque. In imperdiet vel leo a euismod. Nam + sed odio a neque venenatis suscipit a placerat magna. Mauris magna nisl, consequat nec + augue vitae, ultricies scelerisque ante. Phasellus pharetra risus eget imperdiet + sodales. Integer dignissim auctor semper. Nulla odio odio, euismod ut interdum in, + bibendum sed massa. Proin rutrum molestie massa in ultrices. Donec eu euismod turpis, + eget lobortis lorem. Nulla facilisi. Nam lacinia posuere turpis, sed laoreet turpis + auctor nec. +
+
+ Curabitur eu fermentum mauris. Phasellus malesuada consectetur mollis. Pellentesque + pulvinar mauris turpis. Integer neque dolor, semper quis neque et, gravida commodo + eros. Duis efficitur ex a turpis blandit tincidunt. Mauris sem mi, imperdiet quis + ligula sit amet, fermentum vulputate felis. Phasellus eu ullamcorper dolor, porttitor + pulvinar diam. Aliquam euismod, mauris nec varius lacinia, ligula libero vulputate + leo, ut tristique massa nisi vitae tortor. Phasellus purus elit, gravida sit amet + neque id, aliquam rutrum dui. Maecenas luctus sem vitae molestie porttitor. Cras + viverra mauris risus, at sollicitudin ipsum interdum eu. Sed sit amet tempor enim. +
+
+ In hac habitasse platea dictumst. Etiam luctus erat metus, quis efficitur quam + vulputate quis. Duis ultricies non mauris condimentum molestie. Maecenas sollicitudin + ex sem, quis ultrices libero blandit eu. Vivamus in turpis pulvinar, malesuada enim + at, hendrerit magna. Proin eu nulla eget arcu pretium pharetra. Sed ullamcorper + pulvinar est eu dapibus. Cras at varius justo. In ex odio, condimentum id pellentesque + a, sodales ut diam. +
+
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia + Curae; Nam aliquet tempor laoreet. Maecenas eu pulvinar diam. Pellentesque habitant + morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas et + elit eros. Quisque ullamcorper sodales nisi, eleifend aliquet metus venenatis in. + Aliquam ornare a lacus in tincidunt. Cras vel tristique metus. Sed vitae nisl at nisl + imperdiet sollicitudin. Sed sit amet enim in lectus imperdiet interdum condimentum et + diam. Proin venenatis sit amet diam ac vulputate. Donec mauris orci, semper volutpat + nunc ut, efficitur condimentum dolor. Vivamus in quam eget quam lacinia pharetra. + Phasellus ipsum magna, aliquet id elit eget, cursus tincidunt ex. In rhoncus turpis + turpis, et viverra ex malesuada vel. Donec nisi tellus, mollis et posuere vel, dictum + eget neque. +
+
+ ) +} + +const Example = () => { + return ( + + + CustomScrollView + Вертикальный скролл}> + + + Горизонтальный скролл}> + + + Вертикальный и горизонтальный скролл с автоматическим сркытием скроллбара}> + + + + + ); +}; + +; +``` diff --git a/styleguide/config.js b/styleguide/config.js index 9803fb58b2..52202e710b 100644 --- a/styleguide/config.js +++ b/styleguide/config.js @@ -166,6 +166,7 @@ const baseConfig = { `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/FixedLayout/FixedLayout.tsx`, `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/AspectRatio/AspectRatio.tsx`, `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/Flex/Flex.tsx`, + `../${VKUI_PACKAGE.PATHS.COMPONENTS_DIR}/CustomScrollView/CustomScrollView.tsx`, ], sections: [ { From 4f6b09059e2959704c8a9ed633c2d4e29bfefcf9 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Thu, 11 Jul 2024 12:33:08 +0300 Subject: [PATCH 06/20] feat(CustomScrollView): add story to component --- .../CustomScrollView.stories.tsx | 89 ++++++++ .../CustomScrollView/CustomScrollView.tsx | 3 + .../src/components/CustomScrollView/Readme.md | 211 +++++++++--------- .../CustomScrollView/useTrackerVisibility.ts | 4 + 4 files changed, 202 insertions(+), 105 deletions(-) create mode 100644 packages/vkui/src/components/CustomScrollView/CustomScrollView.stories.tsx diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.stories.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.stories.tsx new file mode 100644 index 0000000000..698c94fc17 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.stories.tsx @@ -0,0 +1,89 @@ +import { Meta, ReactRenderer, StoryObj } from '@storybook/react'; +import { PartialStoryFn } from '@storybook/types'; +import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants'; +import { Div } from '../Div/Div'; +import { CustomScrollView, CustomScrollViewProps } from './CustomScrollView'; + +const Wrapper = (Story: PartialStoryFn) => ( +
+ +
+); + +const story: Meta = { + title: 'Layout/CustomScrollView', + component: CustomScrollView, + parameters: { ...CanvasFullLayout, ...DisableCartesianParam }, +}; + +export default story; + +type Story = StoryObj; + +export const Playground: Story = { + args: { + style: { height: 300, width: 600 }, + enableHorizontalScroll: true, + children: ( +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a sollicitudin lectus, a + commodo sapien. Vivamus a urna leo. Integer iaculis dignissim urna, sit amet vestibulum diam + bibendum a. Donec eu arcu ut augue porttitor faucibus. Vestibulum nec pretium tortor, sit + amet congue nunc. Aenean ullamcorper ex sem, sed interdum quam consequat et. Vestibulum a ex + non diam fringilla feugiat. Nunc eu tellus sed leo elementum cursus. Mauris blandit porta + egestas. Curabitur eget justo elementum, malesuada lacus ut, congue mauris. Integer orci + nisi, convallis vitae dapibus sit amet, molestie a risus. Aenean ultricies lacus eros, sit + amet condimentum urna malesuada et. Sed quis dolor tempus orci fringilla volutpat in sed + velit. Aenean aliquet bibendum pretium. +
+
+ Cras pulvinar lobortis purus. Donec placerat suscipit leo vitae sodales. Phasellus convallis + lorem vitae arcu finibus pellentesque. In imperdiet vel leo a euismod. Nam sed odio a neque + venenatis suscipit a placerat magna. Mauris magna nisl, consequat nec augue vitae, ultricies + scelerisque ante. Phasellus pharetra risus eget imperdiet sodales. Integer dignissim auctor + semper. Nulla odio odio, euismod ut interdum in, bibendum sed massa. Proin rutrum molestie + massa in ultrices. Donec eu euismod turpis, eget lobortis lorem. Nulla facilisi. Nam lacinia + posuere turpis, sed laoreet turpis auctor nec. +
+
+ Curabitur eu fermentum mauris. Phasellus malesuada consectetur mollis. Pellentesque pulvinar + mauris turpis. Integer neque dolor, semper quis neque et, gravida commodo eros. Duis + efficitur ex a turpis blandit tincidunt. Mauris sem mi, imperdiet quis ligula sit amet, + fermentum vulputate felis. Phasellus eu ullamcorper dolor, porttitor pulvinar diam. Aliquam + euismod, mauris nec varius lacinia, ligula libero vulputate leo, ut tristique massa nisi + vitae tortor. Phasellus purus elit, gravida sit amet neque id, aliquam rutrum dui. Maecenas + luctus sem vitae molestie porttitor. Cras viverra mauris risus, at sollicitudin ipsum + interdum eu. Sed sit amet tempor enim. +
+
+ In hac habitasse platea dictumst. Etiam luctus erat metus, quis efficitur quam vulputate + quis. Duis ultricies non mauris condimentum molestie. Maecenas sollicitudin ex sem, quis + ultrices libero blandit eu. Vivamus in turpis pulvinar, malesuada enim at, hendrerit magna. + Proin eu nulla eget arcu pretium pharetra. Sed ullamcorper pulvinar est eu dapibus. Cras at + varius justo. In ex odio, condimentum id pellentesque a, sodales ut diam. +
+
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nam + aliquet tempor laoreet. Maecenas eu pulvinar diam. Pellentesque habitant morbi tristique + senectus et netus et malesuada fames ac turpis egestas. Maecenas et elit eros. Quisque + ullamcorper sodales nisi, eleifend aliquet metus venenatis in. Aliquam ornare a lacus in + tincidunt. Cras vel tristique metus. Sed vitae nisl at nisl imperdiet sollicitudin. Sed sit + amet enim in lectus imperdiet interdum condimentum et diam. Proin venenatis sit amet diam ac + vulputate. Donec mauris orci, semper volutpat nunc ut, efficitur condimentum dolor. Vivamus + in quam eget quam lacinia pharetra. Phasellus ipsum magna, aliquet id elit eget, cursus + tincidunt ex. In rhoncus turpis turpis, et viverra ex malesuada vel. Donec nisi tellus, + mollis et posuere vel, dictum eget neque. +
+ ), + }, + decorators: [Wrapper], +}; diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index 3bd3d3a4c5..cf7b860ffb 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -50,6 +50,9 @@ export interface CustomScrollViewProps enableHorizontalScroll?: boolean; } +/** + * @see https://vkcom.github.io/VKUI/#/CustomScrollView + */ export const CustomScrollView = ({ className, children, diff --git a/packages/vkui/src/components/CustomScrollView/Readme.md b/packages/vkui/src/components/CustomScrollView/Readme.md index 02c331baa2..68e540e4b5 100644 --- a/packages/vkui/src/components/CustomScrollView/Readme.md +++ b/packages/vkui/src/components/CustomScrollView/Readme.md @@ -1,117 +1,112 @@ Компонент, заменяющий браузерный скролл на кастомный ```jsx { "props": { "layout": false, "adaptivity": true, "iframe": false } } - const WithVerticalScroll = () => { - return ( - - - {['Say', 'Hello', 'To', 'My', 'Little', 'Friend'].map((item) => ( - }> - {item} - - ))} - - - ) -} + return ( + + + {['Say', 'Hello', 'To', 'My', 'Little', 'Friend'].map((item) => ( + }> + {item} + + ))} + + + ); +}; const WithHorizontalScroll = () => { const [recentFriends] = useState(getRandomUsers(20)); - return ( - -
- {recentFriends.map((item) => { - return ( - {}} key={item.id} header={item.first_name}> - - - ); - })} -
-
- ) -} + return ( + +
+ {recentFriends.map((item) => { + return ( + {}} key={item.id} header={item.first_name}> + + + ); + })} +
+
+ ); +}; const WithBothScrollAndAutoHide = () => { - return ( - +
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a sollicitudin lectus, a - commodo sapien. Vivamus a urna leo. Integer iaculis dignissim urna, sit amet - vestibulum diam bibendum a. Donec eu arcu ut augue porttitor faucibus. Vestibulum nec - pretium tortor, sit amet congue nunc. Aenean ullamcorper ex sem, sed interdum quam - consequat et. Vestibulum a ex non diam fringilla feugiat. Nunc eu tellus sed leo - elementum cursus. Mauris blandit porta egestas. Curabitur eget justo elementum, - malesuada lacus ut, congue mauris. Integer orci nisi, convallis vitae dapibus sit - amet, molestie a risus. Aenean ultricies lacus eros, sit amet condimentum urna - malesuada et. Sed quis dolor tempus orci fringilla volutpat in sed velit. Aenean - aliquet bibendum pretium. -
-
- Cras pulvinar lobortis purus. Donec placerat suscipit leo vitae sodales. Phasellus - convallis lorem vitae arcu finibus pellentesque. In imperdiet vel leo a euismod. Nam - sed odio a neque venenatis suscipit a placerat magna. Mauris magna nisl, consequat nec - augue vitae, ultricies scelerisque ante. Phasellus pharetra risus eget imperdiet - sodales. Integer dignissim auctor semper. Nulla odio odio, euismod ut interdum in, - bibendum sed massa. Proin rutrum molestie massa in ultrices. Donec eu euismod turpis, - eget lobortis lorem. Nulla facilisi. Nam lacinia posuere turpis, sed laoreet turpis - auctor nec. -
-
- Curabitur eu fermentum mauris. Phasellus malesuada consectetur mollis. Pellentesque - pulvinar mauris turpis. Integer neque dolor, semper quis neque et, gravida commodo - eros. Duis efficitur ex a turpis blandit tincidunt. Mauris sem mi, imperdiet quis - ligula sit amet, fermentum vulputate felis. Phasellus eu ullamcorper dolor, porttitor - pulvinar diam. Aliquam euismod, mauris nec varius lacinia, ligula libero vulputate - leo, ut tristique massa nisi vitae tortor. Phasellus purus elit, gravida sit amet - neque id, aliquam rutrum dui. Maecenas luctus sem vitae molestie porttitor. Cras - viverra mauris risus, at sollicitudin ipsum interdum eu. Sed sit amet tempor enim. -
-
- In hac habitasse platea dictumst. Etiam luctus erat metus, quis efficitur quam - vulputate quis. Duis ultricies non mauris condimentum molestie. Maecenas sollicitudin - ex sem, quis ultrices libero blandit eu. Vivamus in turpis pulvinar, malesuada enim - at, hendrerit magna. Proin eu nulla eget arcu pretium pharetra. Sed ullamcorper - pulvinar est eu dapibus. Cras at varius justo. In ex odio, condimentum id pellentesque - a, sodales ut diam. -
-
- Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia - Curae; Nam aliquet tempor laoreet. Maecenas eu pulvinar diam. Pellentesque habitant - morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas et - elit eros. Quisque ullamcorper sodales nisi, eleifend aliquet metus venenatis in. - Aliquam ornare a lacus in tincidunt. Cras vel tristique metus. Sed vitae nisl at nisl - imperdiet sollicitudin. Sed sit amet enim in lectus imperdiet interdum condimentum et - diam. Proin venenatis sit amet diam ac vulputate. Donec mauris orci, semper volutpat - nunc ut, efficitur condimentum dolor. Vivamus in quam eget quam lacinia pharetra. - Phasellus ipsum magna, aliquet id elit eget, cursus tincidunt ex. In rhoncus turpis - turpis, et viverra ex malesuada vel. Donec nisi tellus, mollis et posuere vel, dictum - eget neque. -
- - ) -} + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a sollicitudin lectus, a + commodo sapien. Vivamus a urna leo. Integer iaculis dignissim urna, sit amet vestibulum diam + bibendum a. Donec eu arcu ut augue porttitor faucibus. Vestibulum nec pretium tortor, sit + amet congue nunc. Aenean ullamcorper ex sem, sed interdum quam consequat et. Vestibulum a ex + non diam fringilla feugiat. Nunc eu tellus sed leo elementum cursus. Mauris blandit porta + egestas. Curabitur eget justo elementum, malesuada lacus ut, congue mauris. Integer orci + nisi, convallis vitae dapibus sit amet, molestie a risus. Aenean ultricies lacus eros, sit + amet condimentum urna malesuada et. Sed quis dolor tempus orci fringilla volutpat in sed + velit. Aenean aliquet bibendum pretium. +
+
+ Cras pulvinar lobortis purus. Donec placerat suscipit leo vitae sodales. Phasellus convallis + lorem vitae arcu finibus pellentesque. In imperdiet vel leo a euismod. Nam sed odio a neque + venenatis suscipit a placerat magna. Mauris magna nisl, consequat nec augue vitae, ultricies + scelerisque ante. Phasellus pharetra risus eget imperdiet sodales. Integer dignissim auctor + semper. Nulla odio odio, euismod ut interdum in, bibendum sed massa. Proin rutrum molestie + massa in ultrices. Donec eu euismod turpis, eget lobortis lorem. Nulla facilisi. Nam lacinia + posuere turpis, sed laoreet turpis auctor nec. +
+
+ Curabitur eu fermentum mauris. Phasellus malesuada consectetur mollis. Pellentesque pulvinar + mauris turpis. Integer neque dolor, semper quis neque et, gravida commodo eros. Duis + efficitur ex a turpis blandit tincidunt. Mauris sem mi, imperdiet quis ligula sit amet, + fermentum vulputate felis. Phasellus eu ullamcorper dolor, porttitor pulvinar diam. Aliquam + euismod, mauris nec varius lacinia, ligula libero vulputate leo, ut tristique massa nisi + vitae tortor. Phasellus purus elit, gravida sit amet neque id, aliquam rutrum dui. Maecenas + luctus sem vitae molestie porttitor. Cras viverra mauris risus, at sollicitudin ipsum + interdum eu. Sed sit amet tempor enim. +
+
+ In hac habitasse platea dictumst. Etiam luctus erat metus, quis efficitur quam vulputate + quis. Duis ultricies non mauris condimentum molestie. Maecenas sollicitudin ex sem, quis + ultrices libero blandit eu. Vivamus in turpis pulvinar, malesuada enim at, hendrerit magna. + Proin eu nulla eget arcu pretium pharetra. Sed ullamcorper pulvinar est eu dapibus. Cras at + varius justo. In ex odio, condimentum id pellentesque a, sodales ut diam. +
+
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nam + aliquet tempor laoreet. Maecenas eu pulvinar diam. Pellentesque habitant morbi tristique + senectus et netus et malesuada fames ac turpis egestas. Maecenas et elit eros. Quisque + ullamcorper sodales nisi, eleifend aliquet metus venenatis in. Aliquam ornare a lacus in + tincidunt. Cras vel tristique metus. Sed vitae nisl at nisl imperdiet sollicitudin. Sed sit + amet enim in lectus imperdiet interdum condimentum et diam. Proin venenatis sit amet diam ac + vulputate. Donec mauris orci, semper volutpat nunc ut, efficitur condimentum dolor. Vivamus + in quam eget quam lacinia pharetra. Phasellus ipsum magna, aliquet id elit eget, cursus + tincidunt ex. In rhoncus turpis turpis, et viverra ex malesuada vel. Donec nisi tellus, + mollis et posuere vel, dictum eget neque. +
+
+ ); +}; const Example = () => { return ( @@ -124,7 +119,13 @@ const Example = () => { Горизонтальный скролл}> - Вертикальный и горизонтальный скролл с автоматическим сркытием скроллбара}> + + Вертикальный и горизонтальный скролл с автоматическим сркытием скроллбара + + } + > diff --git a/packages/vkui/src/components/CustomScrollView/useTrackerVisibility.ts b/packages/vkui/src/components/CustomScrollView/useTrackerVisibility.ts index 61e61bd316..9579ae3036 100644 --- a/packages/vkui/src/components/CustomScrollView/useTrackerVisibility.ts +++ b/packages/vkui/src/components/CustomScrollView/useTrackerVisibility.ts @@ -23,6 +23,10 @@ export const useTrackerVisibility = ( const isMouseOver = React.useRef(false); const isTrackerDragging = React.useRef(false); + React.useEffect(() => { + setVisibility(autoHideScrollbar ? 'hidden' : 'visible'); + }, [autoHideScrollbar]); + const onTrackerDragStart = React.useCallback(() => { isTrackerDragging.current = true; setVisibility('visible'); From 909d17bb85446682c87d98d9d29973bbcb15bf3e Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Thu, 11 Jul 2024 13:14:34 +0300 Subject: [PATCH 07/20] fix(CustomScrollView): rewrite description to CustomScrollView --- packages/vkui/src/components/CustomScrollView/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vkui/src/components/CustomScrollView/Readme.md b/packages/vkui/src/components/CustomScrollView/Readme.md index 68e540e4b5..416c77ca5f 100644 --- a/packages/vkui/src/components/CustomScrollView/Readme.md +++ b/packages/vkui/src/components/CustomScrollView/Readme.md @@ -1,4 +1,4 @@ -Компонент, заменяющий браузерный скролл на кастомный +Компонент, заменяющий браузерную полосу прокрутки на кастомную ```jsx { "props": { "layout": false, "adaptivity": true, "iframe": false } } const WithVerticalScroll = () => { From b49305d492c8d46471e2644390664a7659721eaa Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Thu, 11 Jul 2024 15:30:08 +0300 Subject: [PATCH 08/20] fix(CustomScrollView): fix text mistake --- packages/vkui/src/components/CustomScrollView/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vkui/src/components/CustomScrollView/Readme.md b/packages/vkui/src/components/CustomScrollView/Readme.md index 416c77ca5f..9c06ff7501 100644 --- a/packages/vkui/src/components/CustomScrollView/Readme.md +++ b/packages/vkui/src/components/CustomScrollView/Readme.md @@ -122,7 +122,7 @@ const Example = () => { - Вертикальный и горизонтальный скролл с автоматическим сркытием скроллбара + Вертикальный и горизонтальный скролл с автоматическим скрытием скроллбара } > From 81a60e6443c20ed8277127f42782fce599eb8342 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Tue, 16 Jul 2024 14:39:57 +0300 Subject: [PATCH 09/20] fix(CustomScrollView): fix style --- .../src/components/CustomScrollView/CustomScrollView.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css index 30b33d12b6..415dbba0c5 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css @@ -74,7 +74,7 @@ .CustomScrollView__trackerX { block-size: 10px; - padding-inline: 4px 4px; + padding-inline: 4px; padding-block: 0 4px; } From 58c32b3e281fe855be55e832b5cb2b6dd5401aa0 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 17 Jul 2024 17:47:01 +0300 Subject: [PATCH 10/20] fix(CustomScrollView): avoid reflow in hooks --- .../components/CustomScrollView/useCustomScrollViewResize.ts | 2 +- .../components/CustomScrollView/useDetectScrollDirection.ts | 3 ++- .../CustomScrollView/useHorizontalScrollController.tsx | 3 ++- .../CustomScrollView/useVerticalScrollController.tsx | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts b/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts index 0efa062d19..77140a09bd 100644 --- a/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts +++ b/packages/vkui/src/components/CustomScrollView/useCustomScrollViewResize.ts @@ -28,5 +28,5 @@ export const useCustomScrollViewResize = ({ useResizeObserver(boxContentRef, resizeCb); - useIsomorphicLayoutEffect(resizeCb); + useIsomorphicLayoutEffect(resizeCb, []); }; diff --git a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts index f0c210d91f..888ed4cb0f 100644 --- a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts +++ b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { useEventListener } from '../../hooks/useEventListener'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; +import {TimeoutId} from "../../types"; /** * Хук определяет в каком измерении происходит скролл(в горизонтальном или вертикальном) @@ -12,7 +13,7 @@ export const useDetectScrollDirection = (boxRef: React.RefObject null, ); - const timeoutId = React.useRef(null); + const timeoutId = React.useRef(null); const updateDirection = (direction: 'vertical' | 'horizontal') => { setScrollDirection(direction); diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index 9371be831c..7a0568c075 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -55,13 +55,14 @@ export const useHorizontalScrollController = ( clientWidth.current = localClientWidth; scrollWidth.current = localScrollWidth; trackerWidth.current = localTrackerWidth; + const currentScrollLeft = boxRef.current.scrollLeft; if (localVerticalRatio >= 1) { barX.current.style.display = 'none'; } else { barX.current.style.display = ''; trackerX.current.style.width = `${localTrackerWidth}px`; - setTrackerPositionFromScroll(boxRef.current.scrollLeft); + setTrackerPositionFromScroll(currentScrollLeft); } }; diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index f767855654..eb32cb8698 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -56,12 +56,14 @@ export const useVerticalScrollController = ( scrollHeight.current = localScrollHeight; trackerHeight.current = localTrackerHeight; + const currentScrollTop = boxRef.current.scrollTop; + if (localVerticalRatio >= 1) { barY.current.style.display = 'none'; } else { barY.current.style.display = ''; trackerY.current.style.height = `${localTrackerHeight}px`; - setTrackerPositionFromScroll(boxRef.current.scrollTop); + setTrackerPositionFromScroll(currentScrollTop); } }; From dd1c29247d490bd69922676348c5383020d61d7a Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 17 Jul 2024 17:51:01 +0300 Subject: [PATCH 11/20] fix(CustomScrollView): run prettier --- .../src/components/CustomScrollView/useDetectScrollDirection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts index 888ed4cb0f..ce7917363f 100644 --- a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts +++ b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { useEventListener } from '../../hooks/useEventListener'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; -import {TimeoutId} from "../../types"; +import { TimeoutId } from '../../types'; /** * Хук определяет в каком измерении происходит скролл(в горизонтальном или вертикальном) From 49f0d53570105b5e70e64173070571f7840c37c1 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Mon, 22 Jul 2024 11:18:20 +0300 Subject: [PATCH 12/20] fix(CustomScrollView): add warning about using enableHorizontalScroll flag --- .../src/components/CustomScrollView/CustomScrollView.module.css | 2 ++ .../vkui/src/components/CustomScrollView/CustomScrollView.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css index 415dbba0c5..6b4412460c 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.module.css @@ -11,6 +11,7 @@ block-size: 100%; overflow-y: scroll; overflow-x: hidden; + padding-inline-end: 100px; position: relative; outline: none; } @@ -24,6 +25,7 @@ } .CustomScrollView__box--horizontalEnabled { + padding-inline-end: 0; overflow-x: scroll; } diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index cf7b860ffb..b192625cd3 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -46,6 +46,8 @@ export interface CustomScrollViewProps overscrollBehavior?: 'auto' | 'contain' | 'none'; /** * Включение замены горизонтального скролла + * + * > ⚠️ Важно: На версиях Firefox ниже 64 могут возникнуть проблемы при использовании этого флага, будьте осторожны */ enableHorizontalScroll?: boolean; } From f28f2623f88d4728801a1589b2a48f8acdacfcef Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 31 Jul 2024 16:13:38 +0300 Subject: [PATCH 13/20] fix(CustomScrollView): remove transform prop calculating --- .../useHorizontalScrollController.tsx | 5 +---- .../CustomScrollView/useTransformProp.ts | 20 ------------------- .../useVerticalScrollController.tsx | 5 +---- 3 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 packages/vkui/src/components/CustomScrollView/useTransformProp.ts diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index 7a0568c075..d85b3e05ca 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { CustomScrollBarController } from './CustomScrollBarController'; import { useTrackerVisibility } from './useTrackerVisibility'; -import { useTransformProp } from './useTransformProp'; export const useHorizontalScrollController = ( boxRef: React.RefObject, @@ -19,8 +18,6 @@ export const useHorizontalScrollController = ( const barX = React.useRef(null); const trackerX = React.useRef(null); - const transformProp = useTransformProp(trackerX); - const { trackerVisible, onTargetScroll, @@ -33,7 +30,7 @@ export const useHorizontalScrollController = ( const setHorizontalTrackerPosition = (scrollLeft: number) => { lastTrackerLeft.current = scrollLeft; if (trackerX.current !== null) { - (trackerX.current.style as any)[transformProp.current] = `translate(${scrollLeft}px, 0)`; + (trackerX.current.style as any)['transform'] = `translate(${scrollLeft}px, 0)`; } }; diff --git a/packages/vkui/src/components/CustomScrollView/useTransformProp.ts b/packages/vkui/src/components/CustomScrollView/useTransformProp.ts deleted file mode 100644 index e903f01752..0000000000 --- a/packages/vkui/src/components/CustomScrollView/useTransformProp.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react'; -import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; - -export const useTransformProp = (trackerRef: React.RefObject) => { - const transformProp = React.useRef(''); - useIsomorphicLayoutEffect(() => { - let style = trackerRef.current?.style; - let prop = ''; - if (style !== undefined) { - if ('transform' in style) { - prop = 'transform'; - } else if ('webkitTransform' in style) { - prop = 'webkitTransform'; - } - } - transformProp.current = prop; - }, []); - - return transformProp; -}; diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index eb32cb8698..d05bbfb3cb 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { CustomScrollBarController } from './CustomScrollBarController'; import { useTrackerVisibility } from './useTrackerVisibility'; -import { useTransformProp } from './useTransformProp'; export const useVerticalScrollController = ( boxRef: React.RefObject, @@ -19,8 +18,6 @@ export const useVerticalScrollController = ( const barY = React.useRef(null); const trackerY = React.useRef(null); - const transformProp = useTransformProp(trackerY); - const { trackerVisible, onTargetScroll, @@ -33,7 +30,7 @@ export const useVerticalScrollController = ( const setVerticalTrackerPosition = (scrollTop: number) => { lastTrackerTop.current = scrollTop; if (trackerY.current !== null) { - (trackerY.current.style as any)[transformProp.current] = `translate(0, ${scrollTop}px)`; + (trackerY.current.style as any)['transform'] = `translate(0, ${scrollTop}px)`; } }; From 009ef4e7a9aca223fae41a74d50563bcf5f5d2cd Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 31 Jul 2024 17:26:30 +0300 Subject: [PATCH 14/20] feat(CustomScrollView): refactor component logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Вынес рендеринг горизонтальный и вертикальный скроллы в отдельные компоненты. Для этого сделал рефакторинг хуков - Вынес хук useDragAndDrop для избавления от дублирования --- .../CustomScrollBarController.ts | 14 -- .../CustomScrollView/CustomScrollView.tsx | 146 ++++-------------- .../components/CustomScrollView/ScrollX.tsx | 53 +++++++ .../components/CustomScrollView/ScrollY.tsx | 53 +++++++ .../src/components/CustomScrollView/types.ts | 23 +++ .../useDetectScrollDirection.ts | 14 +- .../CustomScrollView/useDragAndDrop.tsx | 55 +++++++ .../useHorizontalScrollController.tsx | 14 +- .../useVerticalScrollController.tsx | 14 +- 9 files changed, 235 insertions(+), 151 deletions(-) delete mode 100644 packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts create mode 100644 packages/vkui/src/components/CustomScrollView/ScrollX.tsx create mode 100644 packages/vkui/src/components/CustomScrollView/ScrollY.tsx create mode 100644 packages/vkui/src/components/CustomScrollView/types.ts create mode 100644 packages/vkui/src/components/CustomScrollView/useDragAndDrop.tsx diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts b/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts deleted file mode 100644 index 8336b26df4..0000000000 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollBarController.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; - -export interface CustomScrollBarController { - barRef: React.RefObject; - trackerRef: React.RefObject; - trackerVisible: boolean; - onResize: () => void; - onMove: (e: MouseEvent) => void; - onUp: () => void; - onScroll: () => void; - onDragStart: (e: React.MouseEvent) => void; - onTrackerMouseEnter: () => void; - onTrackerMouseLeave: () => void; -} diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index b192625cd3..ecd46a51e6 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -1,18 +1,21 @@ import * as React from 'react'; -import { classNames } from '@vkontakte/vkjs'; +import { classNames, noop } from '@vkontakte/vkjs'; import { useAdaptivity } from '../../hooks/useAdaptivity'; -import { useEventListener } from '../../hooks/useEventListener'; import { useExternRef } from '../../hooks/useExternRef'; -import { useDOM } from '../../lib/dom'; -import { stopPropagation } from '../../lib/utils'; import type { HasRootRef } from '../../types'; +import { ScrollX } from './ScrollX'; +import { ScrollY } from './ScrollY'; +import { BarHandlers } from './types'; import { useCustomScrollViewResize } from './useCustomScrollViewResize'; import { useDetectScrollDirection } from './useDetectScrollDirection'; -import { useHorizontalScrollController } from './useHorizontalScrollController'; import { TrackerOptionsProps } from './useTrackerVisibility'; -import { useVerticalScrollController } from './useVerticalScrollController'; import styles from './CustomScrollView.module.css'; +const DEFAULT_BAR_HANDLERS: BarHandlers = { + onResize: noop, + onScroll: noop, +}; + function hasPointerClassName(hasPointer: boolean | undefined) { switch (hasPointer) { case true: @@ -68,112 +71,40 @@ export const CustomScrollView = ({ overscrollBehavior = 'auto', ...restProps }: CustomScrollViewProps): React.ReactNode => { - const { document } = useDOM(); const { hasPointer } = useAdaptivity(); - const [pressedBar, setPressedBar] = React.useState<'vertical' | 'horizontal' | null>(null); const boxRef = useExternRef(externalBoxRef); const boxContentRef = React.useRef(null); + const barX = React.useRef(null); + const barY = React.useRef(null); - const { - trackerVisible: verticalTrackerVisible, - barRef: barY, - trackerRef: trackerY, - onResize: verticalScrollResize, - onScroll: verticalScroll, - onDragStart: onVerticalDragStart, - onMove: onVerticalMove, - onUp: onVerticalUp, - onTrackerMouseEnter: onVerticalTrackerMouseEnter, - onTrackerMouseLeave: onVerticalTrackerMouseLeave, - } = useVerticalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); + const { scrollDirection, onScroll: detectScrollDirection } = useDetectScrollDirection(boxRef); - const { - trackerVisible: horizontalTrackerVisible, - barRef: barX, - trackerRef: trackerX, - onResize: horizontalScrollResize, - onScroll: horizontalScroll, - onDragStart: onHorizontalDragStart, - onMove: onHorizontalMove, - onUp: onHorizontalUp, - onTrackerMouseEnter: onHorizontalTrackerMouseEnter, - onTrackerMouseLeave: onHorizontalTrackerMouseLeave, - } = useHorizontalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); - - const scrollDirection = useDetectScrollDirection(boxRef); + const barYHandlers = React.useRef({ ...DEFAULT_BAR_HANDLERS }); + const barXHandlers = React.useRef({ ...DEFAULT_BAR_HANDLERS }); useCustomScrollViewResize({ windowResize, boxContentRef, onResize: () => { - verticalScrollResize(); - horizontalScrollResize(); + barYHandlers.current.onResize(barY.current); + barXHandlers.current.onResize(barX.current); }, }); - const onUp = (e: MouseEvent) => { - if (!pressedBar) { - return; - } - e.preventDefault(); - if (pressedBar === 'vertical') { - onVerticalUp(); - } else { - onHorizontalUp(); - } - setPressedBar(null); - unsubscribe(); - }; - - const onMove = (e: MouseEvent) => { - if (!pressedBar) { - return; - } - e.preventDefault(); - if (pressedBar === 'vertical') { - onVerticalMove(e); - } else { - onHorizontalMove(e); - } - }; - const onScroll = (event: React.UIEvent) => { + detectScrollDirection(); if (!scrollDirection) { return; } if (scrollDirection === 'horizontal') { - horizontalScroll(); + barXHandlers.current.onScroll(); } else { - verticalScroll(); + barYHandlers.current.onScroll(); } onScrollProp?.(event); }; - const listeners = [useEventListener('mousemove', onMove), useEventListener('mouseup', onUp)]; - - function subscribe(el: Document | undefined) { - if (el) { - listeners.forEach((l) => l.add(el)); - } - } - - function unsubscribe() { - listeners.forEach((l) => l.remove()); - } - - const onDragStart = (e: React.MouseEvent, isVertical: boolean) => { - e.preventDefault(); - setPressedBar(isVertical ? 'vertical' : 'horizontal'); - if (isVertical) { - onVerticalDragStart(e); - } else { - onHorizontalDragStart(e); - } - - subscribe(document); - }; - return (
- -
-
onDragStart(e, true)} - /> -
+ {enableHorizontalScroll && ( -
-
onDragStart(e, false)} - /> -
+ )}
); diff --git a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx new file mode 100644 index 0000000000..e4db829bac --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx @@ -0,0 +1,53 @@ +import { classNames } from '@vkontakte/vkjs'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; +import { stopPropagation } from '../../lib/utils'; +import { ScrollProps } from './types'; +import { useDragAndDrop } from './useDragAndDrop'; +import { useHorizontalScrollController } from './useHorizontalScrollController'; +import styles from './CustomScrollView.module.css'; + +export const ScrollX = ({ + bar: barX, + barHandlers, + boxRef, + autoHideScrollbar, + autoHideScrollbarDelay, +}: ScrollProps) => { + const { + trackerVisible: horizontalTrackerVisible, + trackerRef: trackerX, + onResize: horizontalScrollResize, + onScroll: horizontalScroll, + onDragStart: onHorizontalDragStart, + onMove: onHorizontalMove, + onUp: onHorizontalUp, + onTrackerMouseEnter: onHorizontalTrackerMouseEnter, + onTrackerMouseLeave: onHorizontalTrackerMouseLeave, + } = useHorizontalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); + + const { onDragStart: onMouseDown } = useDragAndDrop( + onHorizontalDragStart, + onHorizontalMove, + onHorizontalUp, + ); + + useIsomorphicLayoutEffect(() => { + barHandlers.current.onResize = horizontalScrollResize; + barHandlers.current.onScroll = horizontalScroll; + }, [horizontalScrollResize, horizontalScroll, barHandlers]); + + return ( +
+
+
+ ); +}; diff --git a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx new file mode 100644 index 0000000000..c92a49987e --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx @@ -0,0 +1,53 @@ +import { classNames } from '@vkontakte/vkjs'; +import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; +import { stopPropagation } from '../../lib/utils'; +import { ScrollProps } from './types'; +import { useDragAndDrop } from './useDragAndDrop'; +import { useVerticalScrollController } from './useVerticalScrollController'; +import styles from './CustomScrollView.module.css'; + +export const ScrollY = ({ + bar: barY, + barHandlers, + boxRef, + autoHideScrollbar, + autoHideScrollbarDelay, +}: ScrollProps) => { + const { + trackerVisible: verticalTrackerVisible, + trackerRef: trackerY, + onResize: verticalScrollResize, + onScroll: verticalScroll, + onDragStart: onVerticalDragStart, + onMove: onVerticalMove, + onUp: onVerticalUp, + onTrackerMouseEnter: onVerticalTrackerMouseEnter, + onTrackerMouseLeave: onVerticalTrackerMouseLeave, + } = useVerticalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); + + const { onDragStart: onMouseDown } = useDragAndDrop( + onVerticalDragStart, + onVerticalMove, + onVerticalUp, + ); + + useIsomorphicLayoutEffect(() => { + barHandlers.current.onResize = verticalScrollResize; + barHandlers.current.onScroll = verticalScroll; + }, [verticalScrollResize, verticalScroll, barHandlers]); + + return ( +
+
+
+ ); +}; diff --git a/packages/vkui/src/components/CustomScrollView/types.ts b/packages/vkui/src/components/CustomScrollView/types.ts new file mode 100644 index 0000000000..b8c7687d95 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/types.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; + +export type CustomScrollBarController = { + trackerRef: React.RefObject; + trackerVisible: boolean; + onResize: (bar: HTMLDivElement | null) => void; + onMove: (e: React.MouseEvent) => void; + onUp: () => void; + onScroll: () => void; + onDragStart: (e: React.MouseEvent) => void; + onTrackerMouseEnter: () => void; + onTrackerMouseLeave: () => void; +}; + +export type BarHandlers = Pick; + +export type ScrollProps = { + bar: React.MutableRefObject; + barHandlers: React.MutableRefObject; + boxRef: React.MutableRefObject; + autoHideScrollbar: boolean; + autoHideScrollbarDelay?: number; +}; diff --git a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts index ce7917363f..65253fe7a2 100644 --- a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts +++ b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts @@ -1,6 +1,5 @@ +import { useCallback } from 'react'; import * as React from 'react'; -import { useEventListener } from '../../hooks/useEventListener'; -import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { TimeoutId } from '../../types'; /** @@ -20,7 +19,7 @@ export const useDetectScrollDirection = (boxRef: React.RefObject timeoutId.current = setTimeout(() => setScrollDirection(null), 200); }; - const scrollHandler = useEventListener('scroll', () => { + const onScroll = useCallback(() => { if (!boxRef || !boxRef.current) { return; } @@ -36,11 +35,10 @@ export const useDetectScrollDirection = (boxRef: React.RefObject } lastScrollLeft.current = scrollLeft; lastScrollTop.current = scrollTop; - }); - - useIsomorphicLayoutEffect(() => { - boxRef.current && scrollHandler.add(boxRef.current); }, [boxRef]); - return scrollDirection; + return { + scrollDirection, + onScroll, + }; }; diff --git a/packages/vkui/src/components/CustomScrollView/useDragAndDrop.tsx b/packages/vkui/src/components/CustomScrollView/useDragAndDrop.tsx new file mode 100644 index 0000000000..b2b1d650f7 --- /dev/null +++ b/packages/vkui/src/components/CustomScrollView/useDragAndDrop.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { useEventListener } from '../../hooks/useEventListener'; +import { useDOM } from '../../lib/dom'; + +export const useDragAndDrop = ( + onDragStart: (e: React.MouseEvent) => void, + onDragMove: (e: React.MouseEvent) => void, + onDragEnd: (e: React.MouseEvent) => void, +) => { + const { document } = useDOM(); + const [isPressed, setIsPressed] = React.useState(false); + + const onDragEndImpl = (e: React.MouseEvent) => { + if (!isPressed) { + return; + } + e.preventDefault(); + onDragEnd(e); + unsubscribe(); + }; + + const onDragMoveImpl = (e: React.MouseEvent) => { + if (!isPressed) { + return; + } + e.preventDefault(); + onDragMove(e); + }; + + const listeners = [ + // @ts-expect-error: TS2769 ругается на тип event + useEventListener('mousemove', onDragMoveImpl), + // @ts-expect-error: TS2769 ругается на тип event + useEventListener('mouseup', onDragEndImpl), + ]; + + function subscribe(el: Document | undefined) { + if (el) { + listeners.forEach((l) => l.add(el)); + } + } + + function unsubscribe() { + listeners.forEach((l) => l.remove()); + } + + return { + onDragStart: (e: React.MouseEvent) => { + e.preventDefault(); + setIsPressed(true); + onDragStart(e); + subscribe(document); + }, + }; +}; diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index d85b3e05ca..ce8a7fbd7e 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { CustomScrollBarController } from './CustomScrollBarController'; +import { CustomScrollBarController } from './types'; import { useTrackerVisibility } from './useTrackerVisibility'; export const useHorizontalScrollController = ( @@ -15,7 +15,6 @@ export const useHorizontalScrollController = ( const startX = React.useRef(0); const trackerLeft = React.useRef(0); - const barX = React.useRef(null); const trackerX = React.useRef(null); const { @@ -39,8 +38,8 @@ export const useHorizontalScrollController = ( setHorizontalTrackerPosition((clientWidth.current - trackerWidth.current) * progress); }; - const onResize = () => { - if (!boxRef.current || !barX.current || !trackerX.current) { + const onResize = (barX: HTMLDivElement | null) => { + if (!boxRef.current || !barX || !trackerX.current) { return; } const localClientWidth = boxRef.current.clientWidth; @@ -55,9 +54,9 @@ export const useHorizontalScrollController = ( const currentScrollLeft = boxRef.current.scrollLeft; if (localVerticalRatio >= 1) { - barX.current.style.display = 'none'; + barX.style.display = 'none'; } else { - barX.current.style.display = ''; + barX.style.display = ''; trackerX.current.style.width = `${localTrackerWidth}px`; setTrackerPositionFromScroll(currentScrollLeft); } @@ -70,7 +69,7 @@ export const useHorizontalScrollController = ( } }; - const onMove = (e: MouseEvent) => { + const onMove = (e: React.MouseEvent) => { const diff = e.clientX - startX.current; const position = Math.min( Math.max(trackerLeft.current + diff, 0), @@ -106,7 +105,6 @@ export const useHorizontalScrollController = ( return { trackerVisible, - barRef: barX, trackerRef: trackerX, onResize, onMove, diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index d05bbfb3cb..d42552be2e 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { CustomScrollBarController } from './CustomScrollBarController'; +import { CustomScrollBarController } from './types'; import { useTrackerVisibility } from './useTrackerVisibility'; export const useVerticalScrollController = ( @@ -15,7 +15,6 @@ export const useVerticalScrollController = ( const startY = React.useRef(0); const trackerTop = React.useRef(0); - const barY = React.useRef(null); const trackerY = React.useRef(null); const { @@ -39,8 +38,8 @@ export const useVerticalScrollController = ( setVerticalTrackerPosition((clientHeight.current - trackerHeight.current) * progress); }; - const onResize = () => { - if (!boxRef.current || !barY.current || !trackerY.current) { + const onResize = (barY: HTMLDivElement | null) => { + if (!boxRef.current || !barY || !trackerY.current) { return; } const localClientHeight = boxRef.current.clientHeight; @@ -56,9 +55,9 @@ export const useVerticalScrollController = ( const currentScrollTop = boxRef.current.scrollTop; if (localVerticalRatio >= 1) { - barY.current.style.display = 'none'; + barY.style.display = 'none'; } else { - barY.current.style.display = ''; + barY.style.display = ''; trackerY.current.style.height = `${localTrackerHeight}px`; setTrackerPositionFromScroll(currentScrollTop); } @@ -71,7 +70,7 @@ export const useVerticalScrollController = ( } }; - const onMove = (e: MouseEvent) => { + const onMove = (e: React.MouseEvent) => { const diff = e.clientY - startY.current; const position = Math.min( Math.max(trackerTop.current + diff, 0), @@ -109,7 +108,6 @@ export const useVerticalScrollController = ( return { trackerVisible, - barRef: barY, trackerRef: trackerY, onResize, onMove, From 6e966f0c9a606b2ffa10a0b0b52d99b46d3c3446 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 31 Jul 2024 17:35:19 +0300 Subject: [PATCH 15/20] fix(CustomScrollView): rename methods --- .../CustomScrollView/CustomScrollView.tsx | 12 +++++----- .../components/CustomScrollView/ScrollX.tsx | 22 ++++++++--------- .../components/CustomScrollView/ScrollY.tsx | 22 ++++++++--------- .../src/components/CustomScrollView/types.ts | 16 ++++++------- .../useHorizontalScrollController.tsx | 24 +++++++++---------- .../useVerticalScrollController.tsx | 24 +++++++++---------- 6 files changed, 60 insertions(+), 60 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index ecd46a51e6..ddac74ece1 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -12,8 +12,8 @@ import { TrackerOptionsProps } from './useTrackerVisibility'; import styles from './CustomScrollView.module.css'; const DEFAULT_BAR_HANDLERS: BarHandlers = { - onResize: noop, - onScroll: noop, + resize: noop, + scroll: noop, }; function hasPointerClassName(hasPointer: boolean | undefined) { @@ -87,8 +87,8 @@ export const CustomScrollView = ({ windowResize, boxContentRef, onResize: () => { - barYHandlers.current.onResize(barY.current); - barXHandlers.current.onResize(barX.current); + barYHandlers.current.resize(barY.current); + barXHandlers.current.resize(barX.current); }, }); @@ -98,9 +98,9 @@ export const CustomScrollView = ({ return; } if (scrollDirection === 'horizontal') { - barXHandlers.current.onScroll(); + barXHandlers.current.scroll(); } else { - barYHandlers.current.onScroll(); + barYHandlers.current.scroll(); } onScrollProp?.(event); }; diff --git a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx index e4db829bac..b642694db0 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx @@ -16,24 +16,24 @@ export const ScrollX = ({ const { trackerVisible: horizontalTrackerVisible, trackerRef: trackerX, - onResize: horizontalScrollResize, - onScroll: horizontalScroll, - onDragStart: onHorizontalDragStart, - onMove: onHorizontalMove, - onUp: onHorizontalUp, - onTrackerMouseEnter: onHorizontalTrackerMouseEnter, - onTrackerMouseLeave: onHorizontalTrackerMouseLeave, + resize: horizontalScrollResize, + scroll: horizontalScroll, + dragStart: onHorizontalDragStart, + dragging: onHorizontalDragging, + dragEnd: onHorizontalDragEnd, + trackerMouseEnter: onHorizontalTrackerMouseEnter, + trackerMouseLeave: onHorizontalTrackerMouseLeave, } = useHorizontalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); const { onDragStart: onMouseDown } = useDragAndDrop( onHorizontalDragStart, - onHorizontalMove, - onHorizontalUp, + onHorizontalDragging, + onHorizontalDragEnd, ); useIsomorphicLayoutEffect(() => { - barHandlers.current.onResize = horizontalScrollResize; - barHandlers.current.onScroll = horizontalScroll; + barHandlers.current.resize = horizontalScrollResize; + barHandlers.current.scroll = horizontalScroll; }, [horizontalScrollResize, horizontalScroll, barHandlers]); return ( diff --git a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx index c92a49987e..e141b0c787 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx @@ -16,24 +16,24 @@ export const ScrollY = ({ const { trackerVisible: verticalTrackerVisible, trackerRef: trackerY, - onResize: verticalScrollResize, - onScroll: verticalScroll, - onDragStart: onVerticalDragStart, - onMove: onVerticalMove, - onUp: onVerticalUp, - onTrackerMouseEnter: onVerticalTrackerMouseEnter, - onTrackerMouseLeave: onVerticalTrackerMouseLeave, + resize: verticalScrollResize, + scroll: verticalScroll, + dragStart: onVerticalDragStart, + dragging: onVerticalDragging, + dragEnd: onVerticalDragEnd, + trackerMouseEnter: onVerticalTrackerMouseEnter, + trackerMouseLeave: onVerticalTrackerMouseLeave, } = useVerticalScrollController(boxRef, autoHideScrollbar, autoHideScrollbarDelay); const { onDragStart: onMouseDown } = useDragAndDrop( onVerticalDragStart, - onVerticalMove, - onVerticalUp, + onVerticalDragging, + onVerticalDragEnd, ); useIsomorphicLayoutEffect(() => { - barHandlers.current.onResize = verticalScrollResize; - barHandlers.current.onScroll = verticalScroll; + barHandlers.current.resize = verticalScrollResize; + barHandlers.current.scroll = verticalScroll; }, [verticalScrollResize, verticalScroll, barHandlers]); return ( diff --git a/packages/vkui/src/components/CustomScrollView/types.ts b/packages/vkui/src/components/CustomScrollView/types.ts index b8c7687d95..01b4c295b2 100644 --- a/packages/vkui/src/components/CustomScrollView/types.ts +++ b/packages/vkui/src/components/CustomScrollView/types.ts @@ -3,16 +3,16 @@ import * as React from 'react'; export type CustomScrollBarController = { trackerRef: React.RefObject; trackerVisible: boolean; - onResize: (bar: HTMLDivElement | null) => void; - onMove: (e: React.MouseEvent) => void; - onUp: () => void; - onScroll: () => void; - onDragStart: (e: React.MouseEvent) => void; - onTrackerMouseEnter: () => void; - onTrackerMouseLeave: () => void; + resize: (bar: HTMLDivElement | null) => void; + dragging: (e: React.MouseEvent) => void; + dragEnd: () => void; + scroll: () => void; + dragStart: (e: React.MouseEvent) => void; + trackerMouseEnter: () => void; + trackerMouseLeave: () => void; }; -export type BarHandlers = Pick; +export type BarHandlers = Pick; export type ScrollProps = { bar: React.MutableRefObject; diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index ce8a7fbd7e..b1102c4dd3 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -38,7 +38,7 @@ export const useHorizontalScrollController = ( setHorizontalTrackerPosition((clientWidth.current - trackerWidth.current) * progress); }; - const onResize = (barX: HTMLDivElement | null) => { + const resize = (barX: HTMLDivElement | null) => { if (!boxRef.current || !barX || !trackerX.current) { return; } @@ -69,7 +69,7 @@ export const useHorizontalScrollController = ( } }; - const onMove = (e: React.MouseEvent) => { + const dragging = (e: React.MouseEvent) => { const diff = e.clientX - startX.current; const position = Math.min( Math.max(trackerLeft.current + diff, 0), @@ -78,13 +78,13 @@ export const useHorizontalScrollController = ( setScrollPositionFromTracker(position); }; - const onUp = () => { + const dragEnd = () => { if (autoHideScrollbar) { onTrackerDragStop(); } }; - const onScroll = () => { + const scroll = () => { if (!boxRef.current) { return; } @@ -94,7 +94,7 @@ export const useHorizontalScrollController = ( setTrackerPositionFromScroll(boxRef.current.scrollLeft); }; - const onDragStart = (e: React.MouseEvent) => { + const dragStart = (e: React.MouseEvent) => { startX.current = e.clientX; trackerLeft.current = lastTrackerLeft.current; @@ -106,12 +106,12 @@ export const useHorizontalScrollController = ( return { trackerVisible, trackerRef: trackerX, - onResize, - onMove, - onUp, - onScroll, - onDragStart, - onTrackerMouseEnter, - onTrackerMouseLeave, + resize, + dragging, + dragEnd, + scroll, + dragStart, + trackerMouseEnter: onTrackerMouseEnter, + trackerMouseLeave: onTrackerMouseLeave, }; }; diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index d42552be2e..4f90b7101f 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -38,7 +38,7 @@ export const useVerticalScrollController = ( setVerticalTrackerPosition((clientHeight.current - trackerHeight.current) * progress); }; - const onResize = (barY: HTMLDivElement | null) => { + const resize = (barY: HTMLDivElement | null) => { if (!boxRef.current || !barY || !trackerY.current) { return; } @@ -70,7 +70,7 @@ export const useVerticalScrollController = ( } }; - const onMove = (e: React.MouseEvent) => { + const dragging = (e: React.MouseEvent) => { const diff = e.clientY - startY.current; const position = Math.min( Math.max(trackerTop.current + diff, 0), @@ -80,13 +80,13 @@ export const useVerticalScrollController = ( setScrollPositionFromTracker(position); }; - const onUp = () => { + const dragEnd = () => { if (autoHideScrollbar) { onTrackerDragStop(); } }; - const onScroll = () => { + const scroll = () => { if (!boxRef.current) { return; } @@ -97,7 +97,7 @@ export const useVerticalScrollController = ( setTrackerPositionFromScroll(boxRef.current.scrollTop); }; - const onDragStart = (e: React.MouseEvent) => { + const dragStart = (e: React.MouseEvent) => { startY.current = e.clientY; trackerTop.current = lastTrackerTop.current; @@ -109,12 +109,12 @@ export const useVerticalScrollController = ( return { trackerVisible, trackerRef: trackerY, - onResize, - onMove, - onUp, - onScroll, - onDragStart, - onTrackerMouseEnter, - onTrackerMouseLeave, + resize, + dragging, + dragEnd, + scroll, + dragStart, + trackerMouseEnter: onTrackerMouseEnter, + trackerMouseLeave: onTrackerMouseLeave, }; }; From 042f180bc967fbc633db8abf71f61ce9072901a5 Mon Sep 17 00:00:00 2001 From: EldarMuhamethanov <61377022+EldarMuhamethanov@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:38:17 +0300 Subject: [PATCH 16/20] fix(useVerticalScrollController): fix transform declaration Co-authored-by: Inomdzhon Mirdzhamolov --- .../components/CustomScrollView/useVerticalScrollController.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index 4f90b7101f..e83b4c53ce 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -29,7 +29,7 @@ export const useVerticalScrollController = ( const setVerticalTrackerPosition = (scrollTop: number) => { lastTrackerTop.current = scrollTop; if (trackerY.current !== null) { - (trackerY.current.style as any)['transform'] = `translate(0, ${scrollTop}px)`; + trackerY.current.style.transform = `translate(0, ${scrollTop}px)`; } }; From 319a596bcac0a26abe0615ec124f5b2387f17624 Mon Sep 17 00:00:00 2001 From: EldarMuhamethanov <61377022+EldarMuhamethanov@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:38:26 +0300 Subject: [PATCH 17/20] fix(useVerticalScrollController): fix transform declaration Co-authored-by: Inomdzhon Mirdzhamolov --- .../CustomScrollView/useHorizontalScrollController.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index b1102c4dd3..a3f9a8e1da 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -29,7 +29,7 @@ export const useHorizontalScrollController = ( const setHorizontalTrackerPosition = (scrollLeft: number) => { lastTrackerLeft.current = scrollLeft; if (trackerX.current !== null) { - (trackerX.current.style as any)['transform'] = `translate(${scrollLeft}px, 0)`; + trackerX.current.style.transform = `translate(${scrollLeft}px, 0)`; } }; From 7b412a72d2539d583c01b69a7f9380c4c0e850e0 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 31 Jul 2024 21:43:29 +0300 Subject: [PATCH 18/20] fix: move barRefs in hooks --- .../components/CustomScrollView/CustomScrollView.tsx | 8 ++------ .../vkui/src/components/CustomScrollView/ScrollX.tsx | 2 +- .../vkui/src/components/CustomScrollView/ScrollY.tsx | 2 +- .../vkui/src/components/CustomScrollView/types.ts | 4 ++-- .../useHorizontalScrollController.tsx | 11 +++++++---- .../CustomScrollView/useVerticalScrollController.tsx | 11 +++++++---- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index ddac74ece1..756b47fa6c 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -75,8 +75,6 @@ export const CustomScrollView = ({ const boxRef = useExternRef(externalBoxRef); const boxContentRef = React.useRef(null); - const barX = React.useRef(null); - const barY = React.useRef(null); const { scrollDirection, onScroll: detectScrollDirection } = useDetectScrollDirection(boxRef); @@ -87,8 +85,8 @@ export const CustomScrollView = ({ windowResize, boxContentRef, onResize: () => { - barYHandlers.current.resize(barY.current); - barXHandlers.current.resize(barX.current); + barYHandlers.current.resize(); + barXHandlers.current.resize(); }, }); @@ -126,7 +124,6 @@ export const CustomScrollView = ({
{enableHorizontalScroll && ( { const { + barRef: barX, trackerVisible: horizontalTrackerVisible, trackerRef: trackerX, resize: horizontalScrollResize, diff --git a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx index e141b0c787..de5da1ce16 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx @@ -7,13 +7,13 @@ import { useVerticalScrollController } from './useVerticalScrollController'; import styles from './CustomScrollView.module.css'; export const ScrollY = ({ - bar: barY, barHandlers, boxRef, autoHideScrollbar, autoHideScrollbarDelay, }: ScrollProps) => { const { + barRef: barY, trackerVisible: verticalTrackerVisible, trackerRef: trackerY, resize: verticalScrollResize, diff --git a/packages/vkui/src/components/CustomScrollView/types.ts b/packages/vkui/src/components/CustomScrollView/types.ts index 01b4c295b2..653c4dc909 100644 --- a/packages/vkui/src/components/CustomScrollView/types.ts +++ b/packages/vkui/src/components/CustomScrollView/types.ts @@ -1,9 +1,10 @@ import * as React from 'react'; export type CustomScrollBarController = { + barRef: React.RefObject; trackerRef: React.RefObject; trackerVisible: boolean; - resize: (bar: HTMLDivElement | null) => void; + resize: () => void; dragging: (e: React.MouseEvent) => void; dragEnd: () => void; scroll: () => void; @@ -15,7 +16,6 @@ export type CustomScrollBarController = { export type BarHandlers = Pick; export type ScrollProps = { - bar: React.MutableRefObject; barHandlers: React.MutableRefObject; boxRef: React.MutableRefObject; autoHideScrollbar: boolean; diff --git a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx index a3f9a8e1da..c5d22d56d4 100644 --- a/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useHorizontalScrollController.tsx @@ -7,6 +7,8 @@ export const useHorizontalScrollController = ( autoHideScrollbar: boolean, autoHideScrollbarDelay?: number, ): CustomScrollBarController => { + const barX = React.useRef(null); + const horizontalRatio = React.useRef(NaN); const lastTrackerLeft = React.useRef(0); const clientWidth = React.useRef(0); @@ -38,8 +40,8 @@ export const useHorizontalScrollController = ( setHorizontalTrackerPosition((clientWidth.current - trackerWidth.current) * progress); }; - const resize = (barX: HTMLDivElement | null) => { - if (!boxRef.current || !barX || !trackerX.current) { + const resize = () => { + if (!boxRef.current || !barX.current || !trackerX.current) { return; } const localClientWidth = boxRef.current.clientWidth; @@ -54,9 +56,9 @@ export const useHorizontalScrollController = ( const currentScrollLeft = boxRef.current.scrollLeft; if (localVerticalRatio >= 1) { - barX.style.display = 'none'; + barX.current.style.display = 'none'; } else { - barX.style.display = ''; + barX.current.style.display = ''; trackerX.current.style.width = `${localTrackerWidth}px`; setTrackerPositionFromScroll(currentScrollLeft); } @@ -104,6 +106,7 @@ export const useHorizontalScrollController = ( }; return { + barRef: barX, trackerVisible, trackerRef: trackerX, resize, diff --git a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx index e83b4c53ce..d45abee27d 100644 --- a/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx +++ b/packages/vkui/src/components/CustomScrollView/useVerticalScrollController.tsx @@ -7,6 +7,8 @@ export const useVerticalScrollController = ( autoHideScrollbar: boolean, autoHideScrollbarDelay?: number, ): CustomScrollBarController => { + const barY = React.useRef(null); + const verticalRatio = React.useRef(NaN); const lastTrackerTop = React.useRef(0); const clientHeight = React.useRef(0); @@ -38,8 +40,8 @@ export const useVerticalScrollController = ( setVerticalTrackerPosition((clientHeight.current - trackerHeight.current) * progress); }; - const resize = (barY: HTMLDivElement | null) => { - if (!boxRef.current || !barY || !trackerY.current) { + const resize = () => { + if (!boxRef.current || !barY.current || !trackerY.current) { return; } const localClientHeight = boxRef.current.clientHeight; @@ -55,9 +57,9 @@ export const useVerticalScrollController = ( const currentScrollTop = boxRef.current.scrollTop; if (localVerticalRatio >= 1) { - barY.style.display = 'none'; + barY.current.style.display = 'none'; } else { - barY.style.display = ''; + barY.current.style.display = ''; trackerY.current.style.height = `${localTrackerHeight}px`; setTrackerPositionFromScroll(currentScrollTop); } @@ -107,6 +109,7 @@ export const useVerticalScrollController = ( }; return { + barRef: barY, trackerVisible, trackerRef: trackerY, resize, From eb688b727f3a1744e54153e78eccec8548b56b17 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 31 Jul 2024 21:46:42 +0300 Subject: [PATCH 19/20] fix: fix barHandlers declaration(add null) --- .../CustomScrollView/CustomScrollView.tsx | 19 +++++++------------ .../components/CustomScrollView/ScrollX.tsx | 6 ++++-- .../components/CustomScrollView/ScrollY.tsx | 6 ++++-- .../src/components/CustomScrollView/types.ts | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index 756b47fa6c..8d18e47657 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { classNames, noop } from '@vkontakte/vkjs'; +import { classNames } from '@vkontakte/vkjs'; import { useAdaptivity } from '../../hooks/useAdaptivity'; import { useExternRef } from '../../hooks/useExternRef'; import type { HasRootRef } from '../../types'; @@ -11,11 +11,6 @@ import { useDetectScrollDirection } from './useDetectScrollDirection'; import { TrackerOptionsProps } from './useTrackerVisibility'; import styles from './CustomScrollView.module.css'; -const DEFAULT_BAR_HANDLERS: BarHandlers = { - resize: noop, - scroll: noop, -}; - function hasPointerClassName(hasPointer: boolean | undefined) { switch (hasPointer) { case true: @@ -78,15 +73,15 @@ export const CustomScrollView = ({ const { scrollDirection, onScroll: detectScrollDirection } = useDetectScrollDirection(boxRef); - const barYHandlers = React.useRef({ ...DEFAULT_BAR_HANDLERS }); - const barXHandlers = React.useRef({ ...DEFAULT_BAR_HANDLERS }); + const barYHandlers = React.useRef(null); + const barXHandlers = React.useRef(null); useCustomScrollViewResize({ windowResize, boxContentRef, onResize: () => { - barYHandlers.current.resize(); - barXHandlers.current.resize(); + barYHandlers.current?.resize(); + barXHandlers.current?.resize(); }, }); @@ -96,9 +91,9 @@ export const CustomScrollView = ({ return; } if (scrollDirection === 'horizontal') { - barXHandlers.current.scroll(); + barXHandlers.current?.scroll(); } else { - barYHandlers.current.scroll(); + barYHandlers.current?.scroll(); } onScrollProp?.(event); }; diff --git a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx index bbaa7107bd..9ef0815e99 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx @@ -32,8 +32,10 @@ export const ScrollX = ({ ); useIsomorphicLayoutEffect(() => { - barHandlers.current.resize = horizontalScrollResize; - barHandlers.current.scroll = horizontalScroll; + barHandlers.current = { + resize: horizontalScrollResize, + scroll: horizontalScroll, + }; }, [horizontalScrollResize, horizontalScroll, barHandlers]); return ( diff --git a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx index de5da1ce16..1814605764 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx @@ -32,8 +32,10 @@ export const ScrollY = ({ ); useIsomorphicLayoutEffect(() => { - barHandlers.current.resize = verticalScrollResize; - barHandlers.current.scroll = verticalScroll; + barHandlers.current = { + resize: verticalScrollResize, + scroll: verticalScroll, + }; }, [verticalScrollResize, verticalScroll, barHandlers]); return ( diff --git a/packages/vkui/src/components/CustomScrollView/types.ts b/packages/vkui/src/components/CustomScrollView/types.ts index 653c4dc909..7567552165 100644 --- a/packages/vkui/src/components/CustomScrollView/types.ts +++ b/packages/vkui/src/components/CustomScrollView/types.ts @@ -16,7 +16,7 @@ export type CustomScrollBarController = { export type BarHandlers = Pick; export type ScrollProps = { - barHandlers: React.MutableRefObject; + barHandlers: React.MutableRefObject; boxRef: React.MutableRefObject; autoHideScrollbar: boolean; autoHideScrollbarDelay?: number; From 4d4abda3dfc102dee035b086fe27c102380fe0a4 Mon Sep 17 00:00:00 2001 From: "e.muhamethanov" Date: Wed, 31 Jul 2024 21:53:50 +0300 Subject: [PATCH 20/20] fix: redactor useDetectScrollDirection --- .../CustomScrollView/CustomScrollView.tsx | 18 ++++---- .../components/CustomScrollView/ScrollX.tsx | 12 ++--- .../components/CustomScrollView/ScrollY.tsx | 12 ++--- .../useDetectScrollDirection.ts | 45 +++++++------------ 4 files changed, 40 insertions(+), 47 deletions(-) diff --git a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx index 8d18e47657..1b79caac3a 100644 --- a/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx +++ b/packages/vkui/src/components/CustomScrollView/CustomScrollView.tsx @@ -71,7 +71,7 @@ export const CustomScrollView = ({ const boxRef = useExternRef(externalBoxRef); const boxContentRef = React.useRef(null); - const { scrollDirection, onScroll: detectScrollDirection } = useDetectScrollDirection(boxRef); + const detectScrollDirection = useDetectScrollDirection(); const barYHandlers = React.useRef(null); const barXHandlers = React.useRef(null); @@ -86,14 +86,14 @@ export const CustomScrollView = ({ }); const onScroll = (event: React.UIEvent) => { - detectScrollDirection(); - if (!scrollDirection) { - return; - } - if (scrollDirection === 'horizontal') { - barXHandlers.current?.scroll(); - } else { - barYHandlers.current?.scroll(); + const scrollDirection = detectScrollDirection(event); + switch (scrollDirection) { + case 'horizontal': + barXHandlers.current?.scroll(); + break; + case 'vertical': + barYHandlers.current?.scroll(); + break; } onScrollProp?.(event); }; diff --git a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx index 9ef0815e99..775c5e9536 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollX.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollX.tsx @@ -1,5 +1,5 @@ +import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; -import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { stopPropagation } from '../../lib/utils'; import { ScrollProps } from './types'; import { useDragAndDrop } from './useDragAndDrop'; @@ -31,12 +31,14 @@ export const ScrollX = ({ onHorizontalDragEnd, ); - useIsomorphicLayoutEffect(() => { - barHandlers.current = { + React.useImperativeHandle( + barHandlers, + () => ({ resize: horizontalScrollResize, scroll: horizontalScroll, - }; - }, [horizontalScrollResize, horizontalScroll, barHandlers]); + }), + [horizontalScrollResize, horizontalScroll], + ); return (
diff --git a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx index 1814605764..63db143aca 100644 --- a/packages/vkui/src/components/CustomScrollView/ScrollY.tsx +++ b/packages/vkui/src/components/CustomScrollView/ScrollY.tsx @@ -1,5 +1,5 @@ +import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; -import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { stopPropagation } from '../../lib/utils'; import { ScrollProps } from './types'; import { useDragAndDrop } from './useDragAndDrop'; @@ -31,12 +31,14 @@ export const ScrollY = ({ onVerticalDragEnd, ); - useIsomorphicLayoutEffect(() => { - barHandlers.current = { + React.useImperativeHandle( + barHandlers, + () => ({ resize: verticalScrollResize, scroll: verticalScroll, - }; - }, [verticalScrollResize, verticalScroll, barHandlers]); + }), + [verticalScrollResize, verticalScroll], + ); return (
diff --git a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts index 65253fe7a2..37239dcf7d 100644 --- a/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts +++ b/packages/vkui/src/components/CustomScrollView/useDetectScrollDirection.ts @@ -1,44 +1,33 @@ -import { useCallback } from 'react'; import * as React from 'react'; import { TimeoutId } from '../../types'; +type ScrollDirection = 'vertical' | 'horizontal'; + /** * Хук определяет в каком измерении происходит скролл(в горизонтальном или вертикальном) */ -export const useDetectScrollDirection = (boxRef: React.RefObject) => { +export const useDetectScrollDirection = () => { const lastScrollLeft = React.useRef(0); const lastScrollTop = React.useRef(0); - const [scrollDirection, setScrollDirection] = React.useState<'vertical' | 'horizontal' | null>( - null, - ); const timeoutId = React.useRef(null); + const scrollDirectionRef = React.useRef(null); - const updateDirection = (direction: 'vertical' | 'horizontal') => { - setScrollDirection(direction); - timeoutId.current = setTimeout(() => setScrollDirection(null), 200); - }; - - const onScroll = useCallback(() => { - if (!boxRef || !boxRef.current) { - return; + return React.useCallback((event: React.UIEvent) => { + if (timeoutId.current) { + clearTimeout(timeoutId.current); } - timeoutId.current && clearTimeout(timeoutId.current); - - const scrollTop = boxRef.current.scrollTop; - const scrollLeft = boxRef.current.scrollLeft; - + const { scrollTop, scrollLeft } = event.currentTarget; if (scrollTop !== lastScrollTop.current) { - updateDirection('vertical'); + scrollDirectionRef.current = 'vertical'; + lastScrollTop.current = scrollTop; } else if (scrollLeft !== lastScrollLeft.current) { - updateDirection('horizontal'); + scrollDirectionRef.current = 'horizontal'; + lastScrollLeft.current = scrollLeft; } - lastScrollLeft.current = scrollLeft; - lastScrollTop.current = scrollTop; - }, [boxRef]); - - return { - scrollDirection, - onScroll, - }; + if (scrollDirectionRef.current !== null) { + timeoutId.current = setTimeout(() => (scrollDirectionRef.current = null), 200); + } + return scrollDirectionRef.current; + }, []); };